mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
* fix(cli): every --json failure answers with one envelope, and --json only where supported
Under --json, failures came back as a bare string (`"error": "Command failed"`
from lint), a command-specific shape, or nothing at all on stdout (info,
compositions, render, snapshot, cloud get). Now every failure is
`{"ok": false, "error": {"code", "message", "hint"?}}`:
- jsonFailure() in utils/jsonError.ts builds it. The commands that printed
`{ok: false, error: "<text>"}` use it, keeping their extra fields (lint's
findings, validate's errors). A refused timeline edit now writes it to stdout
instead of stderr, and keeps its `reason` and `fix` fields.
- When a --json run fails without writing anything to stdout, cli.ts writes the
envelope itself: usage errors carry their own code (unknown_flag,
unknown_command, json_unsupported, confirmation_required), anything else is
runtime_error with the message the command printed on stderr.
--json is no longer accepted by every command. A command supports it by
declaring a `json` arg; the others reject it with exit 2 and the code
json_unsupported, instead of accepting the flag and printing prose.
The contract test now checks every command's --json path, and packages/cli
gets an AGENTS.md with the rules: stdout is data, stderr is messages, never
prompt without a TTY, exit 2 for usage errors, --json always returns an
envelope.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): put the real failure in the --json envelope, never the generic text or a notice
lint --json and check --json in a directory with no project answered
"message": "Command failed". The project check reports the problem, then
throws a presented "Command failed" with the real error as its cause, and the
command's own catch serialized that generic text. failureMessage() now returns
the cause, or else what the command reported through errorBox, and every
command catch that writes a --json failure uses it (lint, check, validate,
layout, snapshot, compare, grade-compare, media-treatment, tts,
remove-background, add, skills, lambda policies). compare, grade-compare and
add share failCaught() for that catch.
The boundary envelope follows the same order: the error's own message unless
it is the generic one, then its cause, then the errorBox title and hint, and
only then the stderr text. Notices (the telemetry disclosure, deprecation
lines, diag progress) are no longer captured, so validate, layout and inspect
no longer put "is deprecated" in the error message.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): render --json keeps human output off stdout so a failure answers with the envelope
Under --json a single render printed its header, lint findings and progress on
stdout, so a later failure such as `render --variables '{bad' --json` ended
with no JSON envelope. --json now silences that output the way --batch --json
already did, and --variables is parsed before the browser check so a bad value
fails without a Chrome download.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): a --strict lint abort says why, even under --json
Under --json the render is quiet, so a --strict lint abort printed nothing and
the envelope said only "Command failed". The abort is now an error box on
stderr in every mode, naming the error and warning counts and the first
finding, and the --json envelope carries the same message. Lint now runs
before the browser check, so the abort never waits on a Chrome download.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(cli): time the render setup cancel from the wrapper's exit
The 1 s budget started when the command did. With lint now running before the
browser check, its subprocess (about 250 ms here) sat inside that budget, and
the test failed once under load. The budget now starts when the wrapper exits,
which is the latency the test is about.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1.6 KiB
1.6 KiB
Working on the CLI
Read this before you add or change a command in packages/cli.
CLI rules for agents
Coding agents and CI run this CLI far more often than people do, with no
terminal and nobody to answer a question. Every command follows these rules,
and src/cli.agentMode.test.ts runs every registered command the way an agent
does to hold them.
- Stdout is data, stderr is messages. Results go to stdout. Progress, warnings, notices and errors go to stderr.
- Never prompt without a person.
isUnattended()insrc/utils/attendedTerminal.tsis true without a TTY, under CI, inside a coding agent, or withHYPERFRAMES_AGENT=1. Import prompts and spinners fromsrc/ui/prompts.ts, never from@clack/prompts(lint enforces it): its prompts refuse to ask when unattended. A command that needs a confirmation takes--yesand callsconfirmationRequired()when it is missing. - Exit codes. 0 success, 1 the command ran and failed, 2 the command line
is wrong. Throw
CliUsageErrorfor a usage error; it exits 2. --jsonalways returns an envelope. A command supports--jsonby declaring ajsonarg; every other command rejects the flag with exit 2. On failure, stdout is{"ok": false, "error": {"code", "message", "hint"?}}. Build it withjsonFailure()fromsrc/utils/jsonError.ts. If a failing--jsonrun printed nothing,cli.tswrites the envelope for it.- Define commands with
defineCommandfromsrc/utils/defineCommand.tsand register them incommandLoadersinsrc/cli.ts. The contract test picks up a new command from there, with no change to the test.