Skip to content

feat: a scriptable CLI with experiment, model and artifact commands - #40

Merged
Marc-AnthonyG merged 32 commits into
mainfrom
feat/scriptable-cli-and-reads
Oct 14, 2026
Merged

Marc-AnthonyG merged 32 commits into
mainfrom
feat/scriptable-cli-and-reads

Conversation

@jwric

@jwric jwric commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

tracel becomes 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

  • Results go to stdout and everything else to stderr. --json, -o, --output <auto|human|json> or TRACEL_OUTPUT picks the format. auto is human on a terminal and JSON otherwise.
  • JSON is one line: {"ok":true,"data":…} on success, {"ok":false,"error":{"code","message","hint","exit_code"}} on failure.
  • Exit codes say what went wrong: 2 usage, 3 not authenticated, 4 forbidden, 5 not found, 6 conflict, 7 confirmation required, 8 limit reached, 10 timeout, 11 unavailable. A not-found error names what is missing and how to list what exists.
  • No question is asked without a terminal:
    • a command that needs an answer it can't get fails with USAGE or CONFIRMATION_REQUIRED, and a hint names the flag that answers it;
    • --no-input and CI turn questions off;
    • declining ends the command without changes, with exit code 0.
  • Logging in from a script takes two steps: login --no-wait, then login --complete [--timeout]. auth status, auth token and me show the credential in use.
  • The project comes from --project, then TRACEL_NAMESPACE/TRACEL_PROJECT, then tracel.toml, and TRACEL_ENV is 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 --follow streams new entries.
  • models list|get|versions|pull|push|promote|alias, artifacts list|download, datasets list|get|versions, project list.
  • Downloads check each file's size and checksum, and refuse paths that leave their directory.

On a terminal

  • Every command draws with cliclack:
    • a read is one box titled with its summary, the project and the environment;
    • a command that changes something is a session of steps and questions, ending on what it did;
    • a failure ends on one red line, with its hint under it.
  • login offers to open its link in the browser, and links open with a click where the terminal supports it.
  • Tables fit the terminal width, and times are in your time zone.

Also

  • package uploads only the files the Console asks for when the code version exists, and reads cargo's build messages with cargo_metadata.
  • The README is a user guide. docs/cli.md is 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.

@jwric
jwric added this pull request to stack #42 October 7, 2026 14:03
@jwric jwric changed the title Feat/scriptable cli and reads feat: a scriptable CLI with experiment, model and artifact commands Oct 14, 2026
@jwric
jwric marked this pull request as ready for review October 14, 2026 21:24
jwric added 25 commits October 14, 2026 17:25
- 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.
jwric added 6 commits October 14, 2026 17:25
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
jwric force-pushed the feat/scriptable-cli-and-reads branch from a32e70a to 8b53389 Compare October 14, 2026 21:26
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
jwric force-pushed the feat/scriptable-cli-and-reads branch from 8b53389 to 1cb95e5 Compare October 14, 2026 22:02
@Marc-AnthonyG
Marc-AnthonyG merged commit 2ac3f9d into main Oct 14, 2026
1 check passed
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.
@Marc-AnthonyG
Marc-AnthonyG deleted the feat/scriptable-cli-and-reads branch October 29, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants