feat(cli): add interactive setup wizard and modernize init command#247
feat(cli): add interactive setup wizard and modernize init command#247cameroncooke wants to merge 8 commits intomainfrom
Conversation
commit: |
Add `xcodebuildmcp setup` — an interactive terminal wizard that walks users through configuring project defaults (project/workspace, scheme, simulator, workflows, debug mode, Sentry opt-out) and persists the result to .xcodebuildmcp/config.yaml. Key changes: - New setup command with clack-based interactive prompts - Shared Prompter abstraction for testable TTY/non-interactive prompts - Promote sentryDisabled from env-var-only to first-class config key - Extract reusable functions from discover_projs, list_schemes, list_sims so both MCP tools and CLI can call them directly - Modernize init command to use clack prompts and interactive selection - Replace Cursor/Codex client targets with generic Agents Skills target - Add persistProjectConfigPatch for atomic config file updates
Extract shared isInteractiveTTY() to prompts.ts and add try/catch to withSpinner so the spinner is stopped if the task throws, preventing garbled terminal output on error.
…malizeLogLevel
Align internal log level naming with Sentry SDK conventions ('warn' instead
of 'warning'). Add normalizeLogLevel() to safely map external level strings
(including MCP protocol's 'warning') to internal LogLevel values.
Also removes the CLI daemon logLevel passthrough (debug flag no longer
overrides daemon log level) and filters 'log-level' from tool command args.
… debug Pass existing project config into workflow option evaluation so that debug-gated workflows (e.g. doctor) appear in the setup wizard when the user's config already has debug: true.
Return structured JSON from init install/uninstall flows when no TTY is available. Keep clack-based output for interactive sessions. This gives agent and CI callers a stable machine-readable contract without introducing additional flags or output modes.
Capture AGENTS guidance outcomes during non-interactive init and embed the status in the emitted JSON report. Include path and error details when AGENTS updates fail so automation can reason about partial success without parsing stderr. Preserve existing failure behavior by throwing after JSON emission on AGENTS errors, and extend init command tests to cover created, updated, and error statuses in JSON output.
e00170d to
25e5a8b
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: Interactive Claude selection silently skipped for MCP skill
- Added check for Claude in user-selected targets array to treat interactive selection as explicit intent, same as --client flag.
- ✅ Fixed: Internal key filter misses camelCase
logLevelvariant- Added 'logLevel' to the internalKeys set to filter out the camelCase variant created by yargs camel-case-expansion.
- ✅ Fixed: Inconsistent TTY check in
ensureAgentsGuidanceafter refactor- Changed guard from process.stdin.isTTY to isInteractiveTTY() to match the check used by promptConfirm function.
Or push these changes by commenting:
@cursor push 3034395277
Preview (3034395277)
diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts
--- a/src/cli/commands/init.ts
+++ b/src/cli/commands/init.ts
@@ -286,7 +286,7 @@
}
if (!force) {
- if (!process.stdin.isTTY) {
+ if (!isInteractiveTTY()) {
throw new Error(
`${AGENTS_FILE_NAME} exists and requires confirmation to update. Re-run with --force to apply the change in non-interactive mode.`,
);
@@ -665,6 +665,11 @@
return { allowedTargets: targets, skippedClients: [] };
}
+ const hasClaudeTarget = targets.some((t) => t.id === 'claude');
+ if (clientFlag === 'claude' || hasClaudeTarget) {
+ return { allowedTargets: targets, skippedClients: [] };
+ }
+
const allowedTargets: ClientInfo[] = [];
const skippedClients: Array<{ client: string; reason: string }> = [];
@@ -679,9 +684,5 @@
allowedTargets.push(target);
}
- if (clientFlag === 'claude') {
- return { allowedTargets: targets, skippedClients: [] };
- }
-
return { allowedTargets, skippedClients };
}
diff --git a/src/cli/register-tool-commands.ts b/src/cli/register-tool-commands.ts
--- a/src/cli/register-tool-commands.ts
+++ b/src/cli/register-tool-commands.ts
@@ -162,7 +162,16 @@
// Convert CLI argv to tool params (kebab-case -> camelCase)
// Filter out internal CLI options before converting
- const internalKeys = new Set(['json', 'output', 'style', 'socket', 'log-level', '_', '$0']);
+ const internalKeys = new Set([
+ 'json',
+ 'output',
+ 'style',
+ 'socket',
+ 'log-level',
+ 'logLevel',
+ '_',
+ '$0',
+ ]);
const flagArgs: Record<string, unknown> = {};
for (const [key, value] of Object.entries(argv as Record<string, unknown>)) {
if (!internalKeys.has(key)) {| allowedTargets.push(target); | ||
| } | ||
|
|
||
| if (clientFlag === 'claude') { | ||
| if (clientFlag === 'claude' || selectionMode === 'interactive') { | ||
| return { allowedTargets: targets, skippedClients: [] }; | ||
| } | ||
|
|
There was a problem hiding this comment.
Bug: The enforceInstallPolicy function incorrectly bypasses MCP skill filtering for the 'claude' client when selectionMode is 'interactive', installing an unnecessary skill.
Severity: MEDIUM
Suggested Fix
Remove the || selectionMode === 'interactive' condition from the if statement within the enforceInstallPolicy function. This will ensure the filtering logic that excludes Claude for MCP skills is applied consistently across all selection modes.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/cli/commands/init.ts#L683-L689
Potential issue: The `enforceInstallPolicy` function is designed to prevent the
installation of 'mcp' skills for the 'claude' client, as they are unnecessary. However,
a conditional check `if (clientFlag === 'claude' || selectionMode === 'interactive')`
bypasses this filtering logic when a user selects clients in interactive mode. This
results in the function returning the original, unfiltered `targets` array, leading to
the installation of an unnecessary skill for Claude. This scenario is not covered by
existing tests.
Did we get this right? 👍 / 👎 to inform future reviews.

Add
xcodebuildmcp setup— an interactive terminal wizard that configures project defaults (project/workspace, scheme, simulator, workflows, debug mode, Sentry opt-out) and persists results to.xcodebuildmcp/config.yaml.Previously users had to hand-edit YAML to configure their project. The setup wizard discovers available projects, schemes, and simulators, presents them in a polished terminal UI via
@clack/prompts, and writes a complete config file with a summary of what changed.Supporting changes:
Prompterabstraction (src/cli/interactive/prompts.ts) — wraps@clack/promptswith a testable interface that auto-selects defaults in non-interactive modesentryDisabledpromoted to first-class config key — previously env-var-only (XCODEBUILDMCP_SENTRY_DISABLED), now settable inconfig.yamland resolved through the standard config layering. A newhydrateSentryDisabledEnvFromProjectConfig()bridges the config file to the env var before Sentry init in all three entrypoints (cli, daemon, mcp server)discoverProjects(),listSchemes(), andlistSimulators()functions from the MCP*Logicwrappers so both the MCP layer and CLI can call them directly. No behavior change to MCP tool responsesinitcommand modernized — switched from rawreadlineto@clack/prompts, added interactive skill type and client selection when no flags are provided, replaced Cursor/Codex targets with generic "Agents Skills" target, added~expansion for--destpersistProjectConfigPatch()— new utility for atomic config file updates with support for mutually exclusive key cleanup (projectPath vs workspacePath)