Repository navigation
feat: implement --no-color, --dry-run, and --debug flags - #119
Conversation
Wire up the three global CLI flags that were declared but only partially implemented. --debug: A yargs middleware sets process.env.DEBUG so core's isDebug() works everywhere. Logs the resolved command, args, and per-command timing to stderr via debugLog(). A withDebug decorator wraps handlers and the MCP client (network/client.ts) with name + timing output. --dry-run: Extracted isDryRun() and dryRunGuard() helpers that standardise the evaluate-then-execute split. Migrated the seven existing inline checks in flags/ and events/ commands, and added dry-run support to all remaining write commands in recordings (4) and warehouse (5), plus events/update. --no-color: Declared as `color` (so yargs' --no-color negation works with strict mode). The middleware sets FORCE_COLOR=0; the quickstart standalone CLI sets it inline before launching the TUI. Cross-cutting decorator composition: Added a compose() utility and a withCrossCutting decorator (compose(withAuth, withDebug)) at @composition, replacing the direct withAuth calls in all 30 command handlers. withAuth stays pure (auth only), withDebug stays pure (timing only), and composition is explicit. All three flags are now visible in --help (no longer hidden). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| const args = Object.entries(argv) | ||
| .filter(([k]) => !k.startsWith('$') && k !== '_' && k !== 'debug') | ||
| .map(([k, v]) => `${k}=${JSON.stringify(v)}`) | ||
| .join(' '); | ||
|
|
There was a problem hiding this comment.
Security: This writes every argv value to stderr. For warehouse create --config-json '{"password":...}' or a --from-file config, warehouse credentials end up in terminal scrollback and CI logs, and --debug is no longer hidden. Fix: log only the arg keys, or redact config-json, sql, token-like keys and file contents.
| @@ -0,0 +1,5 @@ | |||
| export function noColorMiddleware(argv: Record<string, unknown>): void { | |||
| if (argv.color === false) { | |||
| process.env.FORCE_COLOR = '0'; | |||
There was a problem hiding this comment.
FORCE_COLOR=0 is set in middleware, after bin/cli.ts has already imported ora, which pulls in chalk. Chalk reads its colour support once, at import, so --no-color probably does nothing for spinners or for the Ink TUI loaded later from the same chalk instance. Fix: read process.argv for --no-color and set FORCE_COLOR/NO_COLOR before the first import, or pass color: false to ora and Ink directly. No test covers real colour output.
| } | ||
|
|
||
| if (noColor) { | ||
| process.env.FORCE_COLOR = '0'; |
There was a problem hiding this comment.
Same import-timing problem in the standalone quickstart binary. startTui is imported statically, so Ink and chalk are already loaded when the env var is set.
| default: true, | ||
| describe: 'Enable colors (use --no-color to disable)', | ||
| }) | ||
| .option('dry-run', { |
There was a problem hiding this comment.
--dry-run is now shown as a global option on every command, but only the flags, events, recordings and warehouse mutations respect it. mcp install, plugin install/uninstall/update, sdk install, config set and update still make real changes. A user running confidence plugin install --dry-run will get a real install. Fix: guard those commands too, reject --dry-run where it isn't supported, or keep it hidden or per-command.
| import { mcpCallTool as mcpToolCallBase } from '@spotify-confidence/core'; | ||
| import { withDebug } from '@utils/debug.js'; | ||
|
|
||
| export const mcpCallTool = withDebug(mcpToolCallBase); |
There was a problem hiding this comment.
withDebug is reused to wrap mcpCallTool, so every network call logs [debug] command: mcpCallTool. That repeats the real command line, never names the tool being called, and leaves out the REST calls in network/flags.ts (apiRequest). Fix: use a separate network debug wrapper that logs the tool name and timing, and apply it to apiRequest as well.
| debug('command', fn.name || 'anonymous'); | ||
| const start = performance.now(); | ||
|
|
||
| const result = await fn(...args); |
There was a problem hiding this comment.
There is no try/finally, so a call that throws or rejects logs no timing. Those are exactly the runs you'd use --debug for. Fix: move the timing line into finally.
| @@ -0,0 +1,5 @@ | |||
| import { compose } from './utils/compose.js'; | |||
There was a problem hiding this comment.
Nit: Uses relative imports even though everything else in the PR imports @utils/*. The new @composition alias also sits outside the documented alias list, so it should be added to CLAUDE.md and the cli skill.
| @@ -1,5 +1,9 @@ | |||
| export * from './auth.js'; | |||
| export * from './compose.js'; | |||
There was a problem hiding this comment.
Nit: compose is re-exported from the public utils barrel but only composition.ts uses it, and it can only chain exactly two wrappers. It could stay internal to that file.
Prevent credentials, config JSON, SQL, and file contents from leaking to stderr and CI logs when --debug is enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The yargs middleware ran after all static imports resolved, meaning chalk had already cached its color support. Move the check into a core prelude module imported first in both CLI entry points, and remove the now-redundant middleware and duplicate quickstart setter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mcp install/uninstall/auth, plugin install/update/uninstall, sdk install, config set/reset, and update now respect --dry-run by printing the intended action as JSON and returning early without side effects. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
withDebug now accepts an optional formatter so the debug log includes context like the MCP tool name or REST method+path, always prefixed with fn.name. apiRequest calls in flags.ts now go through the debug- wrapped client so REST calls get the same call/timing logs as MCP. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the timing debug line into a finally block so rejected calls still get their duration logged — the exact scenario where --debug matters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch composition.ts from relative imports to @utils/* aliases to match the rest of the CLI package. Add @composition to the documented alias list in AGENTS.md and the cli skill. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up the three global CLI flags that were declared but only partially implemented.
--debug: A yargs middleware setsprocess.env.DEBUGso core'sisDebug()works everywhere. Logs the resolved command, args, and per-command timing to stderr viadebugLog(). AwithDebugdecorator wraps handlers and the MCP client (network/client.ts) with name + timing output.--dry-run: ExtractedisDryRun()anddryRunGuard()helpers that standardise the evaluate-then-execute split. Migrated the seven existing inline checks in flags/ and events/ commands, and added dry-run support to all remaining write commands in recordings (4) and warehouse (5), plus events/update.--no-color: Declared ascolor(so yargs' --no-color negation works with strict mode). The middleware sets FORCE_COLOR=0; the quickstart standalone CLI sets it inline before launching the TUI.Cross-cutting decorator composition: Added a compose() utility and a withCrossCutting decorator (compose(withAuth, withDebug)) at
@composition, replacing the direct withAuth calls in all 30 command handlers. withAuth stays pure (auth only), withDebug stays pure (timing only), and composition is explicit.All three flags are now visible in --help (no longer hidden).