Skip to content

feat(cli): non-interactive subcommands for agent-driven configuration (#602) - #11

Merged
axisrow merged 3 commits into
mainfrom
feat/cli-self-configuration
Sep 26, 2026
Merged

axisrow merged 3 commits into
mainfrom
feat/cli-self-configuration

Conversation

@axisrow

@axisrow axisrow commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the non-interactive CLI proposed in sirmalloc#602, so coding agents (Claude Code) can self-configure the status line without driving the interactive TUI.

New src/utils/cli.ts, wired into src/ccstatusline.ts mode detection: command-line args + TTY stdin → CLI mode before the interactive TUI; the piped render path is unchanged.

Commands

ccstatusline get [--json]                     # effective post-migration config
ccstatusline widget add <line> <widget> [--index N] [--option value ...]
ccstatusline widget remove <line> <index-or-type>
ccstatusline widget move <line> <index> --to <index>
ccstatusline set <option-path> <value>        # global options
ccstatusline validate [--file <path>]         # exit 0/1, machine-readable report
ccstatusline help

Guarantees preserved

  • Mutations reuse config.ts load/save: atomic temp+rename writes, symlink write-through, --config <path> honored.
  • Refuses to mutate an invalid on-disk config; validate-before-write via the settings schema.
  • Errors are single-line and machine-parseable, with a --json variant — designed for agent read-modify-write loops.
  • Widget options mirror WidgetItem fields; indices are 0-based.
  • No new dependencies; no JS syntax newer than what src/ already uses.

End-to-end demo (real entrypoint, temp config via --config)

get → widget add custom-text --index 0 --customText ... --color red → validate (exit 0) → get --json returns one-line JSON containing the new widget; unknown widget type → exit 1, single-line error; piped render unaffected.

Tests

  • 34 new vitest cases (subcommands, error paths, idempotency, invalid-config refusal, symlink write-through)
  • bun test: 2408 pass / 0 fail (151 files, single final run)
  • bun run lint: clean, no suppressions

Scope cuts (deliberate, for review)

  • install / uninstall subcommands not included (TUI flow stays authoritative for now).
  • README/docs updates intentionally deferred to review.

Refs: sirmalloc#602.

🤖 Generated with Claude Code

…sirmalloc#602)

Add `src/utils/cli.ts` and wire it into mode detection in
src/ccstatusline.ts: args present + TTY stdin dispatches the CLI before
the interactive TUI; piped stdin keeps the statusline render path.

Subcommands operating on the same settings file (load/save/migration via
src/utils/config.ts):

- get [--json] - print the effective post-migration config
- widget add <line> <widget> [--index N] [--option value ...]
- widget remove <line> <index-or-type>
- widget move <line> <index> --to <index>
- set <option-path> <value> - global options, JSON or plain string values
- validate [--file <path>] - exit 0/1 with a machine-readable report
- help

Guarantees preserved: mutations refuse to run on an unreadable/invalid
config (never overwrite it with in-memory defaults), every write is
validate-before-write through SettingsSchema and persists via
saveSettings() (atomic temp+rename, symlink write-through). All errors
are single-line and machine-parseable with a --json variant. No new
dependencies; runtime stays Bun+Node compatible.

Vitest coverage (34 cases) for every subcommand including error paths,
idempotency, invalid-config refusal, and symlink write-through.

Co-Authored-By: Claude Code <[email protected]>

@axisrow axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full PR diff (new src/utils/cli.ts, wiring in src/ccstatusline.ts, 443 lines of tests) against main. Verdict: approving, no blocking findings.

The design is right: every mutation routes through loadSettings/saveSettings (atomic temp+rename, symlink write-through, --config honored), refuses to touch an invalid on-disk config, validates the result with the settings schema before writing, and the piped render path is untouched since isCliMode requires a TTY stdin. Error output is single-line and machine-parseable, exit codes are consistent, and tests cover the error paths, idempotency, invalid-config refusal, and symlink behavior.

Three minor inline findings below; only the first (unknown-option guard bypass in set) is worth fixing before this goes upstream. Deferred README updates are fine as a follow-up.

Comment thread src/utils/cli.ts Resolved
Comment thread src/utils/cli.ts Resolved
Comment thread src/utils/cli.ts Resolved
axisrow and others added 2 commits September 26, 2026 10:48
…README section

- set <path>: guard option paths with Object.prototype.hasOwnProperty instead
  of `in`, so inherited keys like toString/constructor cannot pass the check,
  get stripped by the schema, and report a false success (Node 14-safe; no
  Object.hasOwn). Regression test: set toString / powerline.constructor fail
  and leave the file untouched.
- extractJsonFlag(): walk argv and treat only a standalone --json as the
  output flag; tokens filling a value or boolean option are kept intact, so
  'widget add 0 custom-text --customText --json' no longer loses its value.
  Unit tests for the flag/value boundary plus an add-persistence case.
- README: new 'Non-Interactive CLI' section documenting the subcommands and
  noting that get/validate write defaults on a missing settings.json (first
  run) while never overwriting an existing unreadable/invalid file.

Co-Authored-By: Claude Code <[email protected]>
Adds a line to the CLI usage block so agent loops see that get and
validate write the default config when settings.json is missing (the
behavior itself is unchanged, matching the TUI first-run contract).

Co-Authored-By: Claude Code <[email protected]>
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.

1 participant