Skip to content

feat!: consent-gated agent setup and flat CLI output#200

Open
nicknisi wants to merge 5 commits into
mainfrom
nicknisi/cli-ux
Open

feat!: consent-gated agent setup and flat CLI output#200
nicknisi wants to merge 5 commits into
mainfrom
nicknisi/cli-ux

Conversation

@nicknisi

@nicknisi nicknisi commented Jul 22, 2026

Copy link
Copy Markdown
Member

Why

Two problems, one branch:

  1. Consent. A customer (SHPIT) called the CLI's skill auto-install "prompt injection malware": workos auth login and workos install silently and unconditionally installed all bundled skills to every detected coding agent, with no consent and no opt-out.
  2. Aesthetic. clack's gutter/box styling buried the important asks (install skills? connect MCP?) in cruft, and the post-login sequence felt spammy.

What changed

  • One consented setup moment. A new workos setup command owns skills + MCP together. Automatic offers after login/install are gated: silent in agent/CI/non-TTY/JSON, never re-ask after a decline or completion, and nothing is written to a coding agent until the user confirms (or passes --yes). This replaces the silent auto-install.
  • Flat, gutterless output. Swapped the clack engine for @inquirer/prompts behind the existing single UI facade (src/utils/ui.ts). Output is hand-styled: branded intro (WorkOS · AuthKit installer), aligned key/value rows, nested sub-steps, minimal glyphs, one accent color.
  • De-boxed notices. The telemetry notice is now a flat line; the unclaimed-environment warning uses a WARN pill (new renderStderrNotice).
  • Security fix. allowedTools: [] so installer tool calls route through canUseTool — this re-arms the Bash safety gate that bare tool names were bypassing, and silences the SDK's CLAUDE_SDK_CAN_USE_TOOL_SHADOWED warning.

BREAKING CHANGE

workos auth login and workos install no longer auto-install skills. Skills + MCP are installed only through the consented setup flow (workos setup, or the post-login/install offer on a human TTY). Agent / CI / non-TTY / JSON runs write nothing.

Test plan

  • pnpm build
  • pnpm typecheck
  • pnpm lint
  • pnpm test (2175 tests pass)
  • New commands include JSON mode support and tests

Screenshots

capture_20260722_111428 capture_20260722_122854 capture_20260722_123045 capture_20260722_123149

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds consent-based coding-agent setup and simplifies CLI output. The main changes are:

  • A new workos setup command for installing skills and MCP configuration.
  • Interactive setup offers after login and install, with machine-mode and preference gates.
  • An Inquirer-backed UI facade with flat prompts, notices, and status output.
  • Updated recovery guidance, telemetry, agent permissions, and command tests.

Confidence Score: 5/5

The latest changes look safe to merge.

  • No additional blocking issue qualifies for this follow-up review.

Important Files Changed

Filename Overview
src/commands/setup.ts Adds the consent-gated setup command, automatic offer gates, installation reporting, and persisted setup state.
src/utils/ui.ts Replaces the prompt engine behind the shared UI facade and introduces flat CLI rendering.
src/lib/preferences.ts Adds persisted setup decline and completion preferences.
src/lib/agent-runner.ts Routes installer tool requests through the configured permission callback.

Reviews (5): Last reviewed commit: "fix: gate workos setup on JSON mode and ..." | Re-trigger Greptile

Comment thread src/commands/setup.ts Outdated

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment thread src/commands/setup.ts Outdated
Comment on lines +114 to +120
} else if (!isPromptAllowed() && !opts.assumeYes) {
// Explicit `workos setup` in a non-interactive context needs --yes.
exitWithError({
code: 'confirmation_required',
message: `Interactive setup needs a TTY. Re-run \`${formatWorkOSCommand('setup --yes')}\` to install non-interactively.`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 workos setup --json on a TTY prompts interactively and corrupts JSON stdout

In the command trigger path, gating only blocks non-interactive contexts: else if (!isPromptAllowed() && !opts.assumeYes) errors, but there is no isJsonMode() guard (src/commands/setup.ts:114-120). If a user runs workos setup --json on an interactive TTY, output mode is JSON while interaction mode stays human, so isPromptAllowed() is true and assumeYes is false. The flow then falls through to the offer block (src/commands/setup.ts:143-160) and calls ui.heading/ui.note (which console.log human text) and ui.confirm (an interactive prompt), polluting the machine-readable stdout stream. The automatic (login/install) path explicitly guards isJsonMode() (src/commands/setup.ts:112) and the api command errors in JSON mode rather than prompting, so this is an inconsistency. Note that connection/directory delete share the same TTY-prompt-without-JSON-guard pattern, so this may be an accepted repo convention; worth confirming whether workos setup --json on a TTY should instead require --yes.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/commands/setup.ts
Comment on lines +176 to +195
const [skillResult, mcpResults] = await Promise.all([
skillAgents.length > 0 ? refreshWorkOSSkills({ agents: skillAgents }) : Promise.resolve(null),
Promise.all(mcpTargets.map((target) => target.add())),
]);
const skillAgentNames = skillResult?.agents.map((a) => a.displayName) ?? [];

recordSetupCompleted();

const mcpInstalled = mcpResults
.filter((r) => r.outcome === 'installed' || r.outcome === 'already-installed')
.map((r) => r.agent);
const mcpFailed = mcpResults.filter((r) => r.outcome === 'failed').map((r) => r.agent);

emitSetupEvent(opts.trigger, startedAt, true, {
skills: skillResult?.agents.map((a) => a.name) ?? [],
mcpInstalled,
mcpFailed,
});

reportResults(skillResult ? { agents: skillAgentNames, count: skillResult.skills.length } : null, mcpResults);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 recordSetupCompleted() fires even when every install fails

installAndReport calls recordSetupCompleted() (src/commands/setup.ts:182) unconditionally after the install attempt, before checking outcomes. If the user consented but every MCP add() returned failed (and/or refreshWorkOSSkills returned null), completion is still persisted. Because isSetupCompleted() suppresses all future automatic offers (src/commands/setup.ts:113), a user whose setup failed transiently during a post-login/post-install offer will never be auto-offered again; they'd have to run workos setup manually. The old standalone MCP offer only recorded a decline on an explicit "no", never marking completion on failure. This is a behavioral trade-off (treat consent as completion) rather than a clear defect, but may not be the intended UX for failed installs.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Two problems, one change:

1. Consent — a customer called the CLI's skill auto-install "prompt injection
   malware": `workos auth login` / `workos install` silently and unconditionally
   installed all bundled skills to every detected coding agent.
2. Aesthetic — clack's gutter/box styling buried the important asks and the
   post-login sequence felt spammy.

What changed:
- One consented setup moment: a new `workos setup` owns skills + MCP together.
  Automatic offers after login/install are gated (silent in agent/CI/non-TTY/
  JSON, never re-ask after a decline/completion) and nothing is written until
  the user confirms (or passes --yes). Replaces the silent auto-install.
- Flat, gutterless output: swapped the clack engine for @inquirer/prompts behind
  the single UI facade (src/utils/ui.ts). Hand-styled: branded intro, aligned
  key/value rows, nested sub-steps, one accent color. De-boxed notices
  (telemetry = flat line, unclaimed-env = WARN pill).
- Security fix: installer tool calls route through canUseTool (allowedTools: [])
  so installerCanUseTool's Bash allowlist actually runs — bare tool names had
  been auto-approving Bash and bypassing the gate (also silenced the SDK
  CLAUDE_SDK_CAN_USE_TOOL_SHADOWED warning).

BREAKING CHANGE: `workos auth login` and `workos install` no longer auto-install
skills. Skills + MCP are installed only through the consented setup flow
(`workos setup`, or the post-login/install offer on a human TTY). Agent / CI /
non-TTY / JSON runs write nothing.
nicknisi added 4 commits July 22, 2026 10:57
…etry

Follow-up UX + reliability pass on the CLI overhaul (PR #200).

Prompts
- Serialize all prompts through a single-flight mutex in the ui facade and
  pause any active spinner around a prompt. Fixes the concurrent git-dirty +
  protected-branch prompts that opened on one stdin (the "asks a question but
  moves on" bug), and stops a spinner's redraw from clobbering a prompt.
- Guard prompts against JSON / non-TTY contexts; route JSON output to the
  headless adapter and have the CLI adapter fail fast on an unanswerable prompt
  instead of hanging or silently crashing.
- Abort queued sibling prompts when a run is cancelled, so a cancelled run can't
  leave a now-moot question open.
- Remove the 30s deadline that auto-dismissed the "Set up now?" confirm.

Output
- Replace the block-letter banner with a compact lock brand mark, flatten the
  completion summary and the provisioned/unclaimed stderr notices, and make the
  unclaimed-environment warning bolder with a clear claim CTA.

Errors + telemetry
- Rewrite install errors for humans with recovery hints and word-boundary
  matching (no more "author" reading as "auth").
- Emit telemetry for login staging/refresh/auth failures, setup cancel/errors,
  and MCP install failure reasons.
- Fix abortIfCancelled flushing a "cancelled" session on every prompt (bogus
  session end plus ~3s of dead-time per prompt).
Address Devin review findings on the consent-gated setup flow.

- `workos setup --json` on a TTY no longer prompts. Output mode is JSON
  while interaction mode stays human, so isPromptAllowed() is true and the
  command path fell through to ui.confirm/ui.note, corrupting the
  machine-readable stdout stream. Require --yes under --json (mirrors the
  api command), consistent with the automatic path's isJsonMode() guard.
- recordSetupCompleted() now fires only when something actually installed.
  A run where every install failed (e.g. transient `claude mcp add`
  timeouts, no skills to fall back on) previously persisted completion,
  and isSetupCompleted() suppresses all future automatic offers -- so a
  transient failure permanently stranded the user. Leave the pref unset on
  total failure so the next login/install re-offers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant