Summary
packages/stx/bin/cli.ts validates process.argv[1] against a hardcoded knownCommands array before CAC parses anything. That array has drifted from the actual cli.command(...) registrations, so three shipped commands are unreachable: bundle, pwa and images. Each is fully implemented, documented with .example() lines, and then refused by the gate with a "did you mean" suggestion for something else.
Repro (local HEAD)
$ stx bundle dist/ --text
❌ Unknown command: bundle
💡 Did you mean: build?
$ stx pwa
❌ Unknown command: pwa
💡 Did you mean: dev?
$ stx images
❌ Unknown command or path: imagesAll three appear in stx --help output as registered CAC commands.
Evidence in stx
packages/stx/bin/cli.ts:2755-2758
const knownCommands = [
'docs', 'iconify', 'dev', 'a11y', 'build', 'components:build', 'start', 'build:native', 'compile', 'test', 'init', 'new',
'format', 'perf', 'debug', 'doctor', 'status', 'watch', 'analyze', 'version', 'deploy',
'interactive', 'i', 'story', 'story:build', 'story:test', 'screenshot'
]:2772 if (!knownCommands.includes(command)) → :2800 console.error('❌ Unknown command: …') → process.exit(1), all before CAC sees argv.
Diffing that array against every .command('…') in the same file:
- registered but missing from the gate:
bundle(:1415),images(:1288),pwa(:566) - in the gate but not registered:
i,new
bundle is a complete implementation — :1416-1423 declares -o/--output, --json, --text, --markdown, --no-open, --no-gzip, --threshold <size> and --top <n>, and the action imports analyze, formatBytes, parseSize from ../src/bundle-analyzer.
Impact (real app)
analyticshq ships dist/dashboard.html at 182,245 bytes and dist/login.html at 169,987 bytes with no size measurement anywhere in CI. Its scaffolded export-size.yml:26-29 is a fully commented-out antfu/export-size-action step pointing at ~50 framework-internal directories that do not exist in the app, so the job runs checkout + setup-bun on every PR and does nothing.
The command that would have replaced it — stx bundle dist/ --text --threshold 500KB, exit non-zero over budget — was in the CLI the app already invokes for stx build, and could not be run.
Prior art
CAC (and commander, yargs, oclif) already reject unknown commands from the registry they built. A second hand-maintained list of command names is a redundant source of truth that can only ever drift — the same failure shape as #1819, where a hand-maintained regex of runtime globals went stale.
Ask
- Delete
knownCommandsand derive the set from the CAC instance (cli.commands.map(c => c.name)), so registration is the only source of truth. - Keep the
stx <path>→stx dev <path>shorthand and the Levenshtein suggestion, both driven off that derived set. - Add a test that asserts every
.command()name is accepted by the gate, so this cannot drift again. - Meanwhile, add
bundle,imagesandpwato the array and drop the two entries that are not registered.