Skip to content

feat: implement --no-color, --dry-run, and --debug flags - #119

Merged
bespoyasov merged 8 commits into
mainfrom
feat/optional-flags
Oct 20, 2026
Merged

bespoyasov merged 8 commits into
mainfrom
feat/optional-flags

Conversation

@bespoyasov

@bespoyasov bespoyasov commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

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).

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>

@filipmyllari filipmyllari left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review notes (AI-assisted). All findings are inline comments below.

Comment on lines +31 to +35
const args = Object.entries(argv)
.filter(([k]) => !k.startsWith('$') && k !== '_' && k !== 'debug')
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
.join(' ');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread packages/cli/src/utils/no-color.ts Outdated
@@ -0,0 +1,5 @@
export function noColorMiddleware(argv: Record<string, unknown>): void {
if (argv.color === false) {
process.env.FORCE_COLOR = '0';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread packages/cli/bin/cli.ts
default: true,
describe: 'Enable colors (use --no-color to disable)',
})
.option('dry-run', {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

--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.

Comment thread packages/cli/src/network/client.ts Outdated
import { mcpCallTool as mcpToolCallBase } from '@spotify-confidence/core';
import { withDebug } from '@utils/debug.js';

export const mcpCallTool = withDebug(mcpToolCallBase);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread packages/cli/src/utils/debug.ts Outdated
debug('command', fn.name || 'anonymous');
const start = performance.now();

const result = await fn(...args);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread packages/cli/src/composition.ts Outdated
@@ -0,0 +1,5 @@
import { compose } from './utils/compose.js';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

bespoyasov and others added 7 commits October 19, 2026 15:22
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>
@bespoyasov
bespoyasov merged commit 4596f66 into main Oct 20, 2026
6 checks passed
@bespoyasov
bespoyasov deleted the feat/optional-flags branch October 20, 2026 07:07
@github-actions github-actions Bot mentioned this pull request Oct 20, 2026
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