Repository navigation
feat: a scriptable CLI with experiment, model and artifact commands - #40
Merged
Merged
Conversation
jwric
marked this pull request as ready for review
October 14, 2026 21:24
- Failing commands exit non-zero: `project` and `init` no longer exit 0 on errors, and an error prints once, with its full cause. - `unlink` no longer panics without a terminal and accepts `--yes`. - Bare `tracel` prints help instead of logging in, initializing a project or running it. - `train` replaces itself with `cargo run`, so signals sent to `tracel` reach the program instead of orphaning it. - `--dev` and `--staging` also work after the subcommand, `--version` prints `tracel`, prompts check stderr for a terminal, and URLs are plain when stderr is not a terminal.
- Global `--json` and `-o, --output <auto|human|json>`, plus
`TRACEL_OUTPUT`. `auto` is human on a terminal and JSON otherwise.
- JSON mode prints one line on stdout: `{"ok":true,"data":...}`, or
`{"ok":false,"error":{"code","message","hint","exit_code"}}`.
- Errors map to fixed exit codes: 1 internal, 2 usage, 3 not
authenticated, 4 forbidden, 5 not found, 6 conflict, 7 confirmation
required, 8 limit reached, 11 unavailable. Client errors keep their
kind through added context.
- Human output is plain lines when stderr is not a terminal.
- `me`, `project`, `auth status`, `auth token`, `unlink` and `logout`
return structured data. `auth token` prints the bare token unless
JSON is requested.
- Prompts only appear when stdin and stderr are terminals, the output is not JSON, `CI` is unset and `--no-input` is not passed. Otherwise a missing answer is a usage error naming the flag to pass. - Every prompt has a flag: `init --owner --name --description --yes --commit --allow-dirty`, `package --mode --target --bin --install-targets --commit`, and `model upload --auto-create`. - `init` no longer waits for a manual commit, makes no request when the repository is already linked, and fails with a conflict when asked to link a different project without `--force`. - `init`, `package`, `model upload` and `login` return structured data. - Project setup errors carry their kind: no Cargo project is a usage error and an unlinked repository is not found.
- `--project <namespace>/<name>` overrides `TRACEL_NAMESPACE` and `TRACEL_PROJECT`, which override tracel.toml, each part falling back on its own. `project` and `model upload` work outside a Cargo project, and `project` reports where the identity came from. - `model upload` drops its own `-n` and `-p` flags for `--project`. - `TRACEL_ENV` selects the environment when no flag does. - `train` passes the resolved project to the program. - `-C <dir>` runs as if started in that directory.
- `tracel login --no-wait` starts the device flow, keeps the pending login in an owner-only file next to the session, prints the approval link and code, and exits. - `tracel login --complete [--timeout <seconds>]` finishes it. A timeout exits 10 and keeps the pending login. A denied or expired login has to be started again. An unreachable server exits 11 and keeps it. - Requests that never get a response are classified as unavailable. - The not-logged-in error suggests the two-step login.
The next commands need client APIs that are not released yet. The workspace points at a sibling burn-central-client checkout until that release is published.
- A yes/no prompt that cannot be shown fails with CONFIRMATION_REQUIRED (exit 7) instead of USAGE, with a hint naming the flag, such as `unlink --yes`. - A server error carrying the LimitReached code maps to LIMIT_REACHED (exit 8).
- Every module is private and the library re-exports `cli_main`, the only item the binary uses. Items use plain `pub` instead of `pub(crate)` or `pub(super)`. - Private modules surfaced unused code, now removed: the exported logging macros (callers use `tracing` directly), an unused spinner method, an unread archive name and build profile, and an unused error helper.
- `tracel experiments` (alias `exp`) covers `list`, `get <num|latest>`, `metrics` (definitions, a series, or a summary) and `logs`. `logs --follow` tails until the experiment ends and streams NDJSON in JSON mode.
- A 400 or 422 response maps to USAGE instead of INTERNAL, so a request the server rejects as invalid reads as a usage error. - The LIMIT_REACHED code still wins over the status.
- `tracel models` replaces `tracel model`: `list`, `get [--version REF]`, `versions [--all]`, `pull`, `push` (today's upload, plus `--metadata`), `promote --experiment --artifact [--alias]`, and `alias list|set|remove`. - `tracel artifacts list|download` for an experiment. - `tracel datasets list|get|versions`. - `tracel project list [NAMESPACE]` covers your own and your organizations' projects; bare `tracel project` is unchanged. - Downloads check every path and conflict before fetching, verify declared sizes and SHA-256 checksums, and need `--force` to overwrite. - JSON data is the server response; human output uses compact tables.
- `models`, `artifacts` and `datasets` report which model, version, alias, experiment or dataset is missing, with a hint naming the command that lists the valid values. - Filesystem errors no longer print a period before their cause.
- Commands return an `Outcome` made from a typed result (`Render`: `Serialize` plus its text view) instead of `serde_json::Value`. Only `output` writes to stdout, and only `Terminal` draws with cliclack. - The output format is resolved once, with a per-command `auto`: `auth token` and `train` stay text when piped. This replaces the dispatcher's special cases. - Reads print their result on stdout without a frame. Actions report on stderr and print nothing on stdout. `me`, `project` and `auth status` move to stdout. - Tables and details share one layout: aligned values, "-" for empty cells, and "Showing N of TOTAL" on every list with rows left out. - Errors are reported once, on both channels, and close an open frame with the hint. A reader closing stdout ends the command quietly. - `Terminal` moves next to `output`. Upload progress moves behind it, and its dead parts are removed.
- `output` and `terminal` become one `ui` module. `Output` and `Terminal` come from `ui::channels` and share a private `Screen`. It knows the format, which streams are terminals, the open frame, and the live spinner or bar. - One thing draws at a time. Log lines, prompts, frames and stdout writes settle the live widget first, since cliclack cannot pause one. A failure marks it failed. - Stdout written inside an open frame gets the frame's gutter. Redirected output stays plain. - `Render` writes to a `Human` writer that knows the width and whether colors are on. Tables shorten the columns marked `shrink` to fit a terminal. Headers, labels and empty values are styled when colors are on. - `fail` reports through the command's own channels, replacing the static that tracked the frame.
- Pin tracel-client to commit 1f0095d of feat/cli-read-api in burn-central-client, so the branch builds without a sibling checkout.
- `ui::channels` opens the frame from the command's title. Actions close it with `outro` or `outro_cancel`, which replace `finalize` and `cancel_finalize`. Reads are closed by `Output::finish` with the result's new `Render::summary`. Commands no longer open frames themselves. - Inside the frame, details are a card of bullets, JSON values such as an experiment's config are listed as fields under a heading, and tables sit after the bar. - Tables fit the width: a shrinkable column, header included, is cut down to four columns. A list with no rows says so in its summary. Server times show in the local zone, to the minute. - Errors always end on a red badge on a terminal, and the --dev warning now comes after the title. - Redirected output stays plain and ends with the summary line. JSON output is unchanged.
When stdout and stderr are the same terminal, a command's result is drawn with cliclack's own log lines inside its frame: a line per card field, a step per heading with the lines under it, and a remark per table or block of text. Everywhere else results are plain text on stdout, with no colors or symbols, ending with their summary line also when stderr shows a frame. Streamed output stays raw between cliclack's lines. JSON is unchanged. Human keeps its builder API and drops the hand-drawn bar, bullets and headings; commands lose their framed branches.
- On a terminal, `tracel login` asks whether to open the approval page in a browser, answered yes with Enter. It skips the question when nobody can answer or no browser is available, and says to open the link when the browser fails to start. `$BROWSER` picks the browser. - The links in the CLI's messages are terminal hyperlinks in terminals that render them, so a click opens them. Plain output keeps them as text.
On a terminal, a line of a table or of text that is wider than the frame now wraps after cliclack's bar instead of running past it, as cliclack 0.5's termwrap does it. Tables already fitted to the frame are unchanged. Builds on cliclack 0.5.6 and console 0.16, the console its progress bars already used, so one console is linked.
On a terminal, a command that reads shows its result in one box, titled with its summary, the project, and the environment when it is not production. An empty result is just that line. The intro, the per-field lines, and the development warning are gone from reads. A command that does work opens one session under a tracel badge, says what it does and where, and ends on its outcome or on the commands to run next. An error is its message and hint in one block; a failed session ends on what it did not do, anything else on a bare corner. Piped output is unchanged, except that `me` and `auth status` no longer repeat the environment in their summary, which already names it in their fields.
…build fails A result's box no longer dims everything in it: headers stay bold, labels and blanks dim, and links coloured. cliclack dims a box's text the way it dims a prompt's answer, so the box is drawn with a theme that leaves the text as written, and prompts keep cliclack's own. `tracel package` builds its binaries, native or cross, under a spinner that says what cargo compiles. Cargo's own output appears only when the build fails, after the spinner's error.
…, login retries quietly - `tracel init` links to the web Console of the environment it ran in: localhost:9000 for --dev, s<N>-console.tracel.ai for --staging, and the production Console otherwise. A user's project link uses the namespace, as the Console's routes do. - A prompt answered no ends the command on what it didn't do, such as "Nothing was linked.", and exits 0, in init, unlink, models push and promote, and package's target install. Before, init and models push failed with an internal error. - package commits once when asked to commit first; it asked twice and then failed when the second answer was no. - When the Tracel Console can't be reached, commands retry three times with a short backoff and report the failure once, without a line per attempt. - `experiments logs` lists entries oldest first, as `--follow` writes them, with local times to the second. - `experiments get`, `metrics` and `logs` name a missing experiment instead of "Resource not found: UNKNOWN".
Outcomes, errors, hints and warnings are sentences; steps, spinners, prompts and help are fragments. Names the user chose are in single quotes and commands in backticks. - A flow ends on one outcome line, which carries the id worth keeping, such as "Uploaded code version 9e24f16700f2." Steps that only said they were done clear instead: builds, uploads, downloads, checksums. A spinner and a progress bar are one type, which can clear. - A failure is its message and hint, then a bare corner, without a second line saying what didn't happen. - Prompts are short questions or labels: "Targets", "Owner", "Project name", "Unlink test/demo?". When nobody can answer, the error names what needed the answer and the flag that gives it: "Linking test/demo needs confirmation." with "Pass --yes to link it without asking." - Errors are sentences ending in a period, and contexts start with "Can't". A value that isn't one of a set is said once: "No target 'x'. Targets: ...". TRACEL_API_KEY, connection and login errors name what to do without repeating the environment. - Terminals show 12-character digests, sizes in KB, MB and GB, numbers to about four significant digits, and no UUIDs or internal ids. Box rows no longer repeat the box's title, and experiment statuses read as words. - The packager's steps say what it does, and its tracing calls, which nothing recorded, are gone. - Help is one line per command and flag, and every positional argument has one. The top-level help says what tracel is for. - One word per concept: the Tracel Console, the workspace on disk, the project on the Console, a code version, logging in. JSON success data is unchanged. Error envelopes carry the new messages.
- Doc comments open with a plain sentence: a verb for functions and a noun phrase for types, in two lines or fewer. Narrating line comments are gone, and the box, session and flow vocabulary stays in ui. - The environment has one module: how `--dev`, `--staging` and TRACEL_ENV choose it, its name in JSON and on the terminal, its value for the SDK, and its web Console. - The ui speaks of intro and outro, settling and clearing. Stderr's methods are info, warning, step and error, and a progress handle no longer needs a terminal when cliclack doesn't draw. - A result for JSON only is `Outcome::data(value)`, which replaces the empty `Render` impls and the transparent newtypes; `render` is now required of every result. - The project field `owner` is `namespace`, the name tracel.toml, JSON and the Console use; `init --owner` keeps its flag. init chooses a `Namespace` that knows its Console path and how to create a project. - One credential lookup for `auth status` and every client, one login error, one approval message, and one deadline check per poll step. - Helper modules are private behind curated exports, items used only in their file are private, internal errors without a hint are plain anyhow errors, and the colored, directories and tracing dependencies and an unused packager parameter are gone.
Tests live beside the code they exercise and are named as one present-tense sentence about one behaviour. Three or more cases are one table, and a shared test_support module parses arguments through `CliArgs::try_parse_args`, reports an error's kind, exit code and hint, writes a result's plain text, and gives a terminal that never draws or asks. Temporary directories come from tempfile. - Gone: tests that restate clap's derive (flags setting fields, declared defaults and ranges, conflicts), all but one `debug_assert`, tests of removed flags and commands, and a test of an empty project's linker. - Error tests assert the kind, exit code, hint and the offending value instead of whole sentences. Classification is one table, as are the workspace name, linker, build driver, part layout, download path and project resolution cases. - New: where a result goes on a terminal and when a session opens, a closed stdout and a declined prompt exiting 0, the format of an error in arguments that did not parse, a confirmation that can't be asked, TRACEL_ENV reading back what the CLI and the SDK write, TRACEL_API_KEY coming before a stored login, logs oldest first, and the formatting of numbers, sizes and digests. To make them testable, how a stopped command ends, the format of a parse error, and what a broken pipe means are now functions of their inputs.
The README says what tracel is for, how to install it and get started, and shows each command with an example. docs/cli.md states once what the commands share: output formats and the JSON each command returns, exit codes, questions and --no-input, how the project and the credential are chosen, and the rules of packaging, downloads, models and experiments. The SDK link no longer reads "tracetracell", the source command is `cargo run --bin tracel -- --help`, and the README no longer documents a hidden flag.
The errors about an upload response's parts say that the Tracel Console sent them, two context messages start with "Can't", fs uses "isn't" as the rest of the CLI does, and the warning about a missing cross-build tool fits in the frame.
On a terminal a failed command ends on cliclack's red closing line, in a session or not, instead of an error line, a spacer and an empty corner. The hint sits under the message, dimmed, and long lines wrap inside the frame.
A code version that already has some of the package's files gets no upload URL for them. Those files are skipped instead of failing the upload, and when the Console asks for none, the code version is reported as already uploaded.
Tests are named given/when/then, and inputs that share a behavior are named rstest cases instead of tuple tables in a loop. Dropped the tests of other layers and of one-liners: clap's argument rules, serde's JSON for a type, the terminal width cliclack wraps at, exit codes mirrored from their match, plurals, short digests, and the place line, which the session header test covers.
The build's executables come from cargo_metadata's typed messages instead of JSON read by hand, and the workspace manifest from `cargo locate-project --message-format plain`.
jwric
force-pushed
the
feat/scriptable-cli-and-reads
branch
from
October 14, 2026 21:26
a32e70a to
8b53389
Compare
This branch still moves bytes with the client's presigned URL transfers, which tracel-client 0.22.0 removes. It builds against the tracel-client commit that merged the reads and still has them, until the transfers move to tracel-artifact.
jwric
force-pushed
the
feat/scriptable-cli-and-reads
branch
from
October 14, 2026 22:02
8b53389 to
1cb95e5
Compare
Marc-AnthonyG
approved these changes
Oct 14, 2026
Marc-AnthonyG
pushed a commit
that referenced
this pull request
Oct 14, 2026
) * fix: report failures and stop side effects of the CLI - Failing commands exit non-zero: `project` and `init` no longer exit 0 on errors, and an error prints once, with its full cause. - `unlink` no longer panics without a terminal and accepts `--yes`. - Bare `tracel` prints help instead of logging in, initializing a project or running it. - `train` replaces itself with `cargo run`, so signals sent to `tracel` reach the program instead of orphaning it. - `--dev` and `--staging` also work after the subcommand, `--version` prints `tracel`, prompts check stderr for a terminal, and URLs are plain when stderr is not a terminal. * feat: add JSON output and typed exit codes - Global `--json` and `-o, --output <auto|human|json>`, plus `TRACEL_OUTPUT`. `auto` is human on a terminal and JSON otherwise. - JSON mode prints one line on stdout: `{"ok":true,"data":...}`, or `{"ok":false,"error":{"code","message","hint","exit_code"}}`. - Errors map to fixed exit codes: 1 internal, 2 usage, 3 not authenticated, 4 forbidden, 5 not found, 6 conflict, 7 confirmation required, 8 limit reached, 11 unavailable. Client errors keep their kind through added context. - Human output is plain lines when stderr is not a terminal. - `me`, `project`, `auth status`, `auth token`, `unlink` and `logout` return structured data. `auth token` prints the bare token unless JSON is requested. * feat: never prompt without a terminal - Prompts only appear when stdin and stderr are terminals, the output is not JSON, `CI` is unset and `--no-input` is not passed. Otherwise a missing answer is a usage error naming the flag to pass. - Every prompt has a flag: `init --owner --name --description --yes --commit --allow-dirty`, `package --mode --target --bin --install-targets --commit`, and `model upload --auto-create`. - `init` no longer waits for a manual commit, makes no request when the repository is already linked, and fails with a conflict when asked to link a different project without `--force`. - `init`, `package`, `model upload` and `login` return structured data. - Project setup errors carry their kind: no Cargo project is a usage error and an unlinked repository is not found. * feat: resolve the project and environment like the SDK - `--project <namespace>/<name>` overrides `TRACEL_NAMESPACE` and `TRACEL_PROJECT`, which override tracel.toml, each part falling back on its own. `project` and `model upload` work outside a Cargo project, and `project` reports where the identity came from. - `model upload` drops its own `-n` and `-p` flags for `--project`. - `TRACEL_ENV` selects the environment when no flag does. - `train` passes the resolved project to the program. - `-C <dir>` runs as if started in that directory. * feat: add a two-step login for scripts - `tracel login --no-wait` starts the device flow, keeps the pending login in an owner-only file next to the session, prints the approval link and code, and exits. - `tracel login --complete [--timeout <seconds>]` finishes it. A timeout exits 10 and keeps the pending login. A denied or expired login has to be started again. An unreachable server exits 11 and keeps it. - Requests that never get a response are classified as unavailable. - The not-logged-in error suggests the two-step login. * chore: build against the local tracel-client The next commands need client APIs that are not released yet. The workspace points at a sibling burn-central-client checkout until that release is published. * feat: exit 7 for missing confirmations and 8 for server limits - A yes/no prompt that cannot be shown fails with CONFIRMATION_REQUIRED (exit 7) instead of USAGE, with a hint naming the flag, such as `unlink --yes`. - A server error carrying the LimitReached code maps to LIMIT_REACHED (exit 8). * refactor: expose only cli_main from the library - Every module is private and the library re-exports `cli_main`, the only item the binary uses. Items use plain `pub` instead of `pub(crate)` or `pub(super)`. - Private modules surfaced unused code, now removed: the exported logging macros (callers use `tracing` directly), an unused spinner method, an unread archive name and build profile, and an unused error helper. * feat: add experiment commands - `tracel experiments` (alias `exp`) covers `list`, `get <num|latest>`, `metrics` (definitions, a series, or a summary) and `logs`. `logs --follow` tails until the experiment ends and streams NDJSON in JSON mode. * fix: input the server rejects exits 2 - A 400 or 422 response maps to USAGE instead of INTERNAL, so a request the server rejects as invalid reads as a usage error. - The LIMIT_REACHED code still wins over the status. * feat: add models, artifacts, datasets and project list - `tracel models` replaces `tracel model`: `list`, `get [--version REF]`, `versions [--all]`, `pull`, `push` (today's upload, plus `--metadata`), `promote --experiment --artifact [--alias]`, and `alias list|set|remove`. - `tracel artifacts list|download` for an experiment. - `tracel datasets list|get|versions`. - `tracel project list [NAMESPACE]` covers your own and your organizations' projects; bare `tracel project` is unchanged. - Downloads check every path and conflict before fetching, verify declared sizes and SHA-256 checksums, and need `--force` to overwrite. - JSON data is the server response; human output uses compact tables. * feat: name the missing resource in not-found errors - `models`, `artifacts` and `datasets` report which model, version, alias, experiment or dataset is missing, with a hint naming the command that lists the valid values. - Filesystem errors no longer print a period before their cause. * refactor: typed command results behind one stdout seam - Commands return an `Outcome` made from a typed result (`Render`: `Serialize` plus its text view) instead of `serde_json::Value`. Only `output` writes to stdout, and only `Terminal` draws with cliclack. - The output format is resolved once, with a per-command `auto`: `auth token` and `train` stay text when piped. This replaces the dispatcher's special cases. - Reads print their result on stdout without a frame. Actions report on stderr and print nothing on stdout. `me`, `project` and `auth status` move to stdout. - Tables and details share one layout: aligned values, "-" for empty cells, and "Showing N of TOTAL" on every list with rows left out. - Errors are reported once, on both channels, and close an open frame with the hint. A reader closing stdout ends the command quietly. - `Terminal` moves next to `output`. Upload progress moves behind it, and its dead parts are removed. * refactor: draw stdout and stderr on one screen - `output` and `terminal` become one `ui` module. `Output` and `Terminal` come from `ui::channels` and share a private `Screen`. It knows the format, which streams are terminals, the open frame, and the live spinner or bar. - One thing draws at a time. Log lines, prompts, frames and stdout writes settle the live widget first, since cliclack cannot pause one. A failure marks it failed. - Stdout written inside an open frame gets the frame's gutter. Redirected output stays plain. - `Render` writes to a `Human` writer that knows the width and whether colors are on. Tables shorten the columns marked `shrink` to fit a terminal. Headers, labels and empty values are styled when colors are on. - `fail` reports through the command's own channels, replacing the static that tracked the frame. * chore: build against tracel-client from git - Pin tracel-client to commit 1f0095d of feat/cli-read-api in burn-central-client, so the branch builds without a sibling checkout. * feat: draw every command in a cliclack frame on a terminal - `ui::channels` opens the frame from the command's title. Actions close it with `outro` or `outro_cancel`, which replace `finalize` and `cancel_finalize`. Reads are closed by `Output::finish` with the result's new `Render::summary`. Commands no longer open frames themselves. - Inside the frame, details are a card of bullets, JSON values such as an experiment's config are listed as fields under a heading, and tables sit after the bar. - Tables fit the width: a shrinkable column, header included, is cut down to four columns. A list with no rows says so in its summary. Server times show in the local zone, to the minute. - Errors always end on a red badge on a terminal, and the --dev warning now comes after the title. - Redirected output stays plain and ends with the summary line. JSON output is unchanged. * refactor: results on a terminal draw through cliclack When stdout and stderr are the same terminal, a command's result is drawn with cliclack's own log lines inside its frame: a line per card field, a step per heading with the lines under it, and a remark per table or block of text. Everywhere else results are plain text on stdout, with no colors or symbols, ending with their summary line also when stderr shows a frame. Streamed output stays raw between cliclack's lines. JSON is unchanged. Human keeps its builder API and drops the hand-drawn bar, bullets and headings; commands lose their framed branches. * feat: tracel login offers to open its link, and links open with a click - On a terminal, `tracel login` asks whether to open the approval page in a browser, answered yes with Enter. It skips the question when nobody can answer or no browser is available, and says to open the link when the browser fails to start. `$BROWSER` picks the browser. - The links in the CLI's messages are terminal hyperlinks in terminals that render them, so a click opens them. Plain output keeps them as text. * feat: long lines of a table or text wrap inside the frame On a terminal, a line of a table or of text that is wider than the frame now wraps after cliclack's bar instead of running past it, as cliclack 0.5's termwrap does it. Tables already fitted to the frame are unchanged. Builds on cliclack 0.5.6 and console 0.16, the console its progress bars already used, so one console is linked. * feat: a read draws in one box, a command that does work in a session On a terminal, a command that reads shows its result in one box, titled with its summary, the project, and the environment when it is not production. An empty result is just that line. The intro, the per-field lines, and the development warning are gone from reads. A command that does work opens one session under a tracel badge, says what it does and where, and ends on its outcome or on the commands to run next. An error is its message and hint in one block; a failed session ends on what it did not do, anything else on a bare corner. Piped output is unchanged, except that `me` and `auth status` no longer repeat the environment in their summary, which already names it in their fields. * fix: a result keeps its colours, and package shows cargo only when a build fails A result's box no longer dims everything in it: headers stay bold, labels and blanks dim, and links coloured. cliclack dims a box's text the way it dims a prompt's answer, so the box is drawn with a theme that leaves the text as written, and prompts keep cliclack's own. `tracel package` builds its binaries, native or cross, under a spinner that says what cargo compiles. Cargo's own output appears only when the build fails, after the spinner's error. * fix: init links the Console of its environment, declines are outcomes, login retries quietly - `tracel init` links to the web Console of the environment it ran in: localhost:9000 for --dev, s<N>-console.tracel.ai for --staging, and the production Console otherwise. A user's project link uses the namespace, as the Console's routes do. - A prompt answered no ends the command on what it didn't do, such as "Nothing was linked.", and exits 0, in init, unlink, models push and promote, and package's target install. Before, init and models push failed with an internal error. - package commits once when asked to commit first; it asked twice and then failed when the second answer was no. - When the Tracel Console can't be reached, commands retry three times with a short backoff and report the failure once, without a line per attempt. - `experiments logs` lists entries oldest first, as `--follow` writes them, with local times to the second. - `experiments get`, `metrics` and `logs` name a missing experiment instead of "Resource not found: UNKNOWN". * refactor: messages follow one style Outcomes, errors, hints and warnings are sentences; steps, spinners, prompts and help are fragments. Names the user chose are in single quotes and commands in backticks. - A flow ends on one outcome line, which carries the id worth keeping, such as "Uploaded code version 9e24f16700f2." Steps that only said they were done clear instead: builds, uploads, downloads, checksums. A spinner and a progress bar are one type, which can clear. - A failure is its message and hint, then a bare corner, without a second line saying what didn't happen. - Prompts are short questions or labels: "Targets", "Owner", "Project name", "Unlink test/demo?". When nobody can answer, the error names what needed the answer and the flag that gives it: "Linking test/demo needs confirmation." with "Pass --yes to link it without asking." - Errors are sentences ending in a period, and contexts start with "Can't". A value that isn't one of a set is said once: "No target 'x'. Targets: ...". TRACEL_API_KEY, connection and login errors name what to do without repeating the environment. - Terminals show 12-character digests, sizes in KB, MB and GB, numbers to about four significant digits, and no UUIDs or internal ids. Box rows no longer repeat the box's title, and experiment statuses read as words. - The packager's steps say what it does, and its tracing calls, which nothing recorded, are gone. - Help is one line per command and flag, and every positional argument has one. The top-level help says what tracel is for. - One word per concept: the Tracel Console, the workspace on disk, the project on the Console, a code version, logging in. JSON success data is unchanged. Error envelopes carry the new messages. * refactor: plainer doc comments and less duplication - Doc comments open with a plain sentence: a verb for functions and a noun phrase for types, in two lines or fewer. Narrating line comments are gone, and the box, session and flow vocabulary stays in ui. - The environment has one module: how `--dev`, `--staging` and TRACEL_ENV choose it, its name in JSON and on the terminal, its value for the SDK, and its web Console. - The ui speaks of intro and outro, settling and clearing. Stderr's methods are info, warning, step and error, and a progress handle no longer needs a terminal when cliclack doesn't draw. - A result for JSON only is `Outcome::data(value)`, which replaces the empty `Render` impls and the transparent newtypes; `render` is now required of every result. - The project field `owner` is `namespace`, the name tracel.toml, JSON and the Console use; `init --owner` keeps its flag. init chooses a `Namespace` that knows its Console path and how to create a project. - One credential lookup for `auth status` and every client, one login error, one approval message, and one deadline check per poll step. - Helper modules are private behind curated exports, items used only in their file are private, internal errors without a hint are plain anyhow errors, and the colored, directories and tracing dependencies and an unused packager parameter are gone. * test: one convention, and tests for what matters Tests live beside the code they exercise and are named as one present-tense sentence about one behaviour. Three or more cases are one table, and a shared test_support module parses arguments through `CliArgs::try_parse_args`, reports an error's kind, exit code and hint, writes a result's plain text, and gives a terminal that never draws or asks. Temporary directories come from tempfile. - Gone: tests that restate clap's derive (flags setting fields, declared defaults and ranges, conflicts), all but one `debug_assert`, tests of removed flags and commands, and a test of an empty project's linker. - Error tests assert the kind, exit code, hint and the offending value instead of whole sentences. Classification is one table, as are the workspace name, linker, build driver, part layout, download path and project resolution cases. - New: where a result goes on a terminal and when a session opens, a closed stdout and a declined prompt exiting 0, the format of an error in arguments that did not parse, a confirmation that can't be asked, TRACEL_ENV reading back what the CLI and the SDK write, TRACEL_API_KEY coming before a stored login, logs oldest first, and the formatting of numbers, sizes and digests. To make them testable, how a stopped command ends, the format of a parse error, and what a broken pipe means are now functions of their inputs. * docs: the README is a user guide, the reference moves to docs/cli.md The README says what tracel is for, how to install it and get started, and shows each command with an example. docs/cli.md states once what the commands share: output formats and the JSON each command returns, exit codes, questions and --no-input, how the project and the credential are chosen, and the rules of packaging, downloads, models and experiments. The SDK link no longer reads "tracetracell", the source command is `cargo run --bin tracel -- --help`, and the README no longer documents a hidden flag. * refactor: the remaining internal messages follow the style The errors about an upload response's parts say that the Tracel Console sent them, two context messages start with "Can't", fs uses "isn't" as the rest of the CLI does, and the warning about a missing cross-build tool fits in the frame. * fix: a failure ends on one red line, with its hint under it On a terminal a failed command ends on cliclack's red closing line, in a session or not, instead of an error line, a spacer and an empty corner. The hint sits under the message, dimmed, and long lines wrap inside the frame. * fix: packaging again uploads only the files the Console asks for A code version that already has some of the package's files gets no upload URL for them. Those files are skipped instead of failing the upload, and when the Console asks for none, the code version is reported as already uploaded. * test: one readable test per behavior Tests are named given/when/then, and inputs that share a behavior are named rstest cases instead of tuple tables in a loop. Dropped the tests of other layers and of one-liners: clap's argument rules, serde's JSON for a type, the terminal width cliclack wraps at, exit codes mirrored from their match, plurals, short digests, and the place line, which the session header test covers. * refactor: read cargo's machine output with cargo_metadata The build's executables come from cargo_metadata's typed messages instead of JSON read by hand, and the workspace manifest from `cargo locate-project --message-format plain`. * chore: build against tracel-client after its experiment and model reads This branch still moves bytes with the client's presigned URL transfers, which tracel-client 0.22.0 removes. It builds against the tracel-client commit that merged the reads and still has them, until the transfers move to tracel-artifact. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ex4o1LntLPL1RrM35YpRjE
Marc-AnthonyG
pushed a commit
that referenced
this pull request
Oct 14, 2026
) * fix: report failures and stop side effects of the CLI - Failing commands exit non-zero: `project` and `init` no longer exit 0 on errors, and an error prints once, with its full cause. - `unlink` no longer panics without a terminal and accepts `--yes`. - Bare `tracel` prints help instead of logging in, initializing a project or running it. - `train` replaces itself with `cargo run`, so signals sent to `tracel` reach the program instead of orphaning it. - `--dev` and `--staging` also work after the subcommand, `--version` prints `tracel`, prompts check stderr for a terminal, and URLs are plain when stderr is not a terminal. * feat: add JSON output and typed exit codes - Global `--json` and `-o, --output <auto|human|json>`, plus `TRACEL_OUTPUT`. `auto` is human on a terminal and JSON otherwise. - JSON mode prints one line on stdout: `{"ok":true,"data":...}`, or `{"ok":false,"error":{"code","message","hint","exit_code"}}`. - Errors map to fixed exit codes: 1 internal, 2 usage, 3 not authenticated, 4 forbidden, 5 not found, 6 conflict, 7 confirmation required, 8 limit reached, 11 unavailable. Client errors keep their kind through added context. - Human output is plain lines when stderr is not a terminal. - `me`, `project`, `auth status`, `auth token`, `unlink` and `logout` return structured data. `auth token` prints the bare token unless JSON is requested. * feat: never prompt without a terminal - Prompts only appear when stdin and stderr are terminals, the output is not JSON, `CI` is unset and `--no-input` is not passed. Otherwise a missing answer is a usage error naming the flag to pass. - Every prompt has a flag: `init --owner --name --description --yes --commit --allow-dirty`, `package --mode --target --bin --install-targets --commit`, and `model upload --auto-create`. - `init` no longer waits for a manual commit, makes no request when the repository is already linked, and fails with a conflict when asked to link a different project without `--force`. - `init`, `package`, `model upload` and `login` return structured data. - Project setup errors carry their kind: no Cargo project is a usage error and an unlinked repository is not found. * feat: resolve the project and environment like the SDK - `--project <namespace>/<name>` overrides `TRACEL_NAMESPACE` and `TRACEL_PROJECT`, which override tracel.toml, each part falling back on its own. `project` and `model upload` work outside a Cargo project, and `project` reports where the identity came from. - `model upload` drops its own `-n` and `-p` flags for `--project`. - `TRACEL_ENV` selects the environment when no flag does. - `train` passes the resolved project to the program. - `-C <dir>` runs as if started in that directory. * feat: add a two-step login for scripts - `tracel login --no-wait` starts the device flow, keeps the pending login in an owner-only file next to the session, prints the approval link and code, and exits. - `tracel login --complete [--timeout <seconds>]` finishes it. A timeout exits 10 and keeps the pending login. A denied or expired login has to be started again. An unreachable server exits 11 and keeps it. - Requests that never get a response are classified as unavailable. - The not-logged-in error suggests the two-step login. * chore: build against the local tracel-client The next commands need client APIs that are not released yet. The workspace points at a sibling burn-central-client checkout until that release is published. * feat: exit 7 for missing confirmations and 8 for server limits - A yes/no prompt that cannot be shown fails with CONFIRMATION_REQUIRED (exit 7) instead of USAGE, with a hint naming the flag, such as `unlink --yes`. - A server error carrying the LimitReached code maps to LIMIT_REACHED (exit 8). * refactor: expose only cli_main from the library - Every module is private and the library re-exports `cli_main`, the only item the binary uses. Items use plain `pub` instead of `pub(crate)` or `pub(super)`. - Private modules surfaced unused code, now removed: the exported logging macros (callers use `tracing` directly), an unused spinner method, an unread archive name and build profile, and an unused error helper. * feat: add experiment commands - `tracel experiments` (alias `exp`) covers `list`, `get <num|latest>`, `metrics` (definitions, a series, or a summary) and `logs`. `logs --follow` tails until the experiment ends and streams NDJSON in JSON mode. * fix: input the server rejects exits 2 - A 400 or 422 response maps to USAGE instead of INTERNAL, so a request the server rejects as invalid reads as a usage error. - The LIMIT_REACHED code still wins over the status. * feat: add models, artifacts, datasets and project list - `tracel models` replaces `tracel model`: `list`, `get [--version REF]`, `versions [--all]`, `pull`, `push` (today's upload, plus `--metadata`), `promote --experiment --artifact [--alias]`, and `alias list|set|remove`. - `tracel artifacts list|download` for an experiment. - `tracel datasets list|get|versions`. - `tracel project list [NAMESPACE]` covers your own and your organizations' projects; bare `tracel project` is unchanged. - Downloads check every path and conflict before fetching, verify declared sizes and SHA-256 checksums, and need `--force` to overwrite. - JSON data is the server response; human output uses compact tables. * feat: name the missing resource in not-found errors - `models`, `artifacts` and `datasets` report which model, version, alias, experiment or dataset is missing, with a hint naming the command that lists the valid values. - Filesystem errors no longer print a period before their cause. * refactor: typed command results behind one stdout seam - Commands return an `Outcome` made from a typed result (`Render`: `Serialize` plus its text view) instead of `serde_json::Value`. Only `output` writes to stdout, and only `Terminal` draws with cliclack. - The output format is resolved once, with a per-command `auto`: `auth token` and `train` stay text when piped. This replaces the dispatcher's special cases. - Reads print their result on stdout without a frame. Actions report on stderr and print nothing on stdout. `me`, `project` and `auth status` move to stdout. - Tables and details share one layout: aligned values, "-" for empty cells, and "Showing N of TOTAL" on every list with rows left out. - Errors are reported once, on both channels, and close an open frame with the hint. A reader closing stdout ends the command quietly. - `Terminal` moves next to `output`. Upload progress moves behind it, and its dead parts are removed. * refactor: draw stdout and stderr on one screen - `output` and `terminal` become one `ui` module. `Output` and `Terminal` come from `ui::channels` and share a private `Screen`. It knows the format, which streams are terminals, the open frame, and the live spinner or bar. - One thing draws at a time. Log lines, prompts, frames and stdout writes settle the live widget first, since cliclack cannot pause one. A failure marks it failed. - Stdout written inside an open frame gets the frame's gutter. Redirected output stays plain. - `Render` writes to a `Human` writer that knows the width and whether colors are on. Tables shorten the columns marked `shrink` to fit a terminal. Headers, labels and empty values are styled when colors are on. - `fail` reports through the command's own channels, replacing the static that tracked the frame. * chore: build against tracel-client from git - Pin tracel-client to commit 1f0095d of feat/cli-read-api in burn-central-client, so the branch builds without a sibling checkout. * feat: draw every command in a cliclack frame on a terminal - `ui::channels` opens the frame from the command's title. Actions close it with `outro` or `outro_cancel`, which replace `finalize` and `cancel_finalize`. Reads are closed by `Output::finish` with the result's new `Render::summary`. Commands no longer open frames themselves. - Inside the frame, details are a card of bullets, JSON values such as an experiment's config are listed as fields under a heading, and tables sit after the bar. - Tables fit the width: a shrinkable column, header included, is cut down to four columns. A list with no rows says so in its summary. Server times show in the local zone, to the minute. - Errors always end on a red badge on a terminal, and the --dev warning now comes after the title. - Redirected output stays plain and ends with the summary line. JSON output is unchanged. * refactor: results on a terminal draw through cliclack When stdout and stderr are the same terminal, a command's result is drawn with cliclack's own log lines inside its frame: a line per card field, a step per heading with the lines under it, and a remark per table or block of text. Everywhere else results are plain text on stdout, with no colors or symbols, ending with their summary line also when stderr shows a frame. Streamed output stays raw between cliclack's lines. JSON is unchanged. Human keeps its builder API and drops the hand-drawn bar, bullets and headings; commands lose their framed branches. * feat: tracel login offers to open its link, and links open with a click - On a terminal, `tracel login` asks whether to open the approval page in a browser, answered yes with Enter. It skips the question when nobody can answer or no browser is available, and says to open the link when the browser fails to start. `$BROWSER` picks the browser. - The links in the CLI's messages are terminal hyperlinks in terminals that render them, so a click opens them. Plain output keeps them as text. * feat: long lines of a table or text wrap inside the frame On a terminal, a line of a table or of text that is wider than the frame now wraps after cliclack's bar instead of running past it, as cliclack 0.5's termwrap does it. Tables already fitted to the frame are unchanged. Builds on cliclack 0.5.6 and console 0.16, the console its progress bars already used, so one console is linked. * feat: a read draws in one box, a command that does work in a session On a terminal, a command that reads shows its result in one box, titled with its summary, the project, and the environment when it is not production. An empty result is just that line. The intro, the per-field lines, and the development warning are gone from reads. A command that does work opens one session under a tracel badge, says what it does and where, and ends on its outcome or on the commands to run next. An error is its message and hint in one block; a failed session ends on what it did not do, anything else on a bare corner. Piped output is unchanged, except that `me` and `auth status` no longer repeat the environment in their summary, which already names it in their fields. * fix: a result keeps its colours, and package shows cargo only when a build fails A result's box no longer dims everything in it: headers stay bold, labels and blanks dim, and links coloured. cliclack dims a box's text the way it dims a prompt's answer, so the box is drawn with a theme that leaves the text as written, and prompts keep cliclack's own. `tracel package` builds its binaries, native or cross, under a spinner that says what cargo compiles. Cargo's own output appears only when the build fails, after the spinner's error. * fix: init links the Console of its environment, declines are outcomes, login retries quietly - `tracel init` links to the web Console of the environment it ran in: localhost:9000 for --dev, s<N>-console.tracel.ai for --staging, and the production Console otherwise. A user's project link uses the namespace, as the Console's routes do. - A prompt answered no ends the command on what it didn't do, such as "Nothing was linked.", and exits 0, in init, unlink, models push and promote, and package's target install. Before, init and models push failed with an internal error. - package commits once when asked to commit first; it asked twice and then failed when the second answer was no. - When the Tracel Console can't be reached, commands retry three times with a short backoff and report the failure once, without a line per attempt. - `experiments logs` lists entries oldest first, as `--follow` writes them, with local times to the second. - `experiments get`, `metrics` and `logs` name a missing experiment instead of "Resource not found: UNKNOWN". * refactor: messages follow one style Outcomes, errors, hints and warnings are sentences; steps, spinners, prompts and help are fragments. Names the user chose are in single quotes and commands in backticks. - A flow ends on one outcome line, which carries the id worth keeping, such as "Uploaded code version 9e24f16700f2." Steps that only said they were done clear instead: builds, uploads, downloads, checksums. A spinner and a progress bar are one type, which can clear. - A failure is its message and hint, then a bare corner, without a second line saying what didn't happen. - Prompts are short questions or labels: "Targets", "Owner", "Project name", "Unlink test/demo?". When nobody can answer, the error names what needed the answer and the flag that gives it: "Linking test/demo needs confirmation." with "Pass --yes to link it without asking." - Errors are sentences ending in a period, and contexts start with "Can't". A value that isn't one of a set is said once: "No target 'x'. Targets: ...". TRACEL_API_KEY, connection and login errors name what to do without repeating the environment. - Terminals show 12-character digests, sizes in KB, MB and GB, numbers to about four significant digits, and no UUIDs or internal ids. Box rows no longer repeat the box's title, and experiment statuses read as words. - The packager's steps say what it does, and its tracing calls, which nothing recorded, are gone. - Help is one line per command and flag, and every positional argument has one. The top-level help says what tracel is for. - One word per concept: the Tracel Console, the workspace on disk, the project on the Console, a code version, logging in. JSON success data is unchanged. Error envelopes carry the new messages. * refactor: plainer doc comments and less duplication - Doc comments open with a plain sentence: a verb for functions and a noun phrase for types, in two lines or fewer. Narrating line comments are gone, and the box, session and flow vocabulary stays in ui. - The environment has one module: how `--dev`, `--staging` and TRACEL_ENV choose it, its name in JSON and on the terminal, its value for the SDK, and its web Console. - The ui speaks of intro and outro, settling and clearing. Stderr's methods are info, warning, step and error, and a progress handle no longer needs a terminal when cliclack doesn't draw. - A result for JSON only is `Outcome::data(value)`, which replaces the empty `Render` impls and the transparent newtypes; `render` is now required of every result. - The project field `owner` is `namespace`, the name tracel.toml, JSON and the Console use; `init --owner` keeps its flag. init chooses a `Namespace` that knows its Console path and how to create a project. - One credential lookup for `auth status` and every client, one login error, one approval message, and one deadline check per poll step. - Helper modules are private behind curated exports, items used only in their file are private, internal errors without a hint are plain anyhow errors, and the colored, directories and tracing dependencies and an unused packager parameter are gone. * test: one convention, and tests for what matters Tests live beside the code they exercise and are named as one present-tense sentence about one behaviour. Three or more cases are one table, and a shared test_support module parses arguments through `CliArgs::try_parse_args`, reports an error's kind, exit code and hint, writes a result's plain text, and gives a terminal that never draws or asks. Temporary directories come from tempfile. - Gone: tests that restate clap's derive (flags setting fields, declared defaults and ranges, conflicts), all but one `debug_assert`, tests of removed flags and commands, and a test of an empty project's linker. - Error tests assert the kind, exit code, hint and the offending value instead of whole sentences. Classification is one table, as are the workspace name, linker, build driver, part layout, download path and project resolution cases. - New: where a result goes on a terminal and when a session opens, a closed stdout and a declined prompt exiting 0, the format of an error in arguments that did not parse, a confirmation that can't be asked, TRACEL_ENV reading back what the CLI and the SDK write, TRACEL_API_KEY coming before a stored login, logs oldest first, and the formatting of numbers, sizes and digests. To make them testable, how a stopped command ends, the format of a parse error, and what a broken pipe means are now functions of their inputs. * docs: the README is a user guide, the reference moves to docs/cli.md The README says what tracel is for, how to install it and get started, and shows each command with an example. docs/cli.md states once what the commands share: output formats and the JSON each command returns, exit codes, questions and --no-input, how the project and the credential are chosen, and the rules of packaging, downloads, models and experiments. The SDK link no longer reads "tracetracell", the source command is `cargo run --bin tracel -- --help`, and the README no longer documents a hidden flag. * refactor: the remaining internal messages follow the style The errors about an upload response's parts say that the Tracel Console sent them, two context messages start with "Can't", fs uses "isn't" as the rest of the CLI does, and the warning about a missing cross-build tool fits in the frame. * fix: a failure ends on one red line, with its hint under it On a terminal a failed command ends on cliclack's red closing line, in a session or not, instead of an error line, a spacer and an empty corner. The hint sits under the message, dimmed, and long lines wrap inside the frame. * fix: packaging again uploads only the files the Console asks for A code version that already has some of the package's files gets no upload URL for them. Those files are skipped instead of failing the upload, and when the Console asks for none, the code version is reported as already uploaded. * test: one readable test per behavior Tests are named given/when/then, and inputs that share a behavior are named rstest cases instead of tuple tables in a loop. Dropped the tests of other layers and of one-liners: clap's argument rules, serde's JSON for a type, the terminal width cliclack wraps at, exit codes mirrored from their match, plurals, short digests, and the place line, which the session header test covers. * refactor: read cargo's machine output with cargo_metadata The build's executables come from cargo_metadata's typed messages instead of JSON read by hand, and the workspace manifest from `cargo locate-project --message-format plain`. * chore: build against tracel-client after its experiment and model reads This branch still moves bytes with the client's presigned URL transfers, which tracel-client 0.22.0 removes. It builds against the tracel-client commit that merged the reads and still has them, until the transfers move to tracel-artifact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
tracelbecomes usable from scripts and CI, gains the commands that read a project's experiments, models, artifacts and datasets, and draws one consistent output on a terminal.Scripting
--json,-o, --output <auto|human|json>orTRACEL_OUTPUTpicks the format.autois human on a terminal and JSON otherwise.{"ok":true,"data":…}on success,{"ok":false,"error":{"code","message","hint","exit_code"}}on failure.USAGEorCONFIRMATION_REQUIRED, and a hint names the flag that answers it;--no-inputandCIturn questions off;login --no-wait, thenlogin --complete [--timeout].auth status,auth tokenandmeshow the credential in use.--project, thenTRACEL_NAMESPACE/TRACEL_PROJECT, thentracel.toml, andTRACEL_ENVis read as the SDK writes it, so the CLI and a job binary agree on where they record.Commands
experiments list|get|metrics|logs;logs --followstreams new entries.models list|get|versions|pull|push|promote|alias,artifacts list|download,datasets list|get|versions,project list.On a terminal
loginoffers to open its link in the browser, and links open with a click where the terminal supports it.Also
packageuploads only the files the Console asks for when the code version exists, and reads cargo's build messages withcargo_metadata.docs/cli.mdis the reference for output, JSON data, exit codes, questions, the project and credentials.Builds against tracel-ai/tracel-client#54, pinned by git revision for review, which becomes a crates.io release before merging. #41 is stacked on this.