Skip to content

feat(cli): add interactive setup wizard and modernize init command#247

Open
cameroncooke wants to merge 8 commits intomainfrom
feat/setup-wizard
Open

feat(cli): add interactive setup wizard and modernize init command#247
cameroncooke wants to merge 8 commits intomainfrom
feat/setup-wizard

Conversation

@cameroncooke
Copy link
Collaborator

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:

  • Shared Prompter abstraction (src/cli/interactive/prompts.ts) — wraps @clack/prompts with a testable interface that auto-selects defaults in non-interactive mode
  • sentryDisabled promoted to first-class config key — previously env-var-only (XCODEBUILDMCP_SENTRY_DISABLED), now settable in config.yaml and resolved through the standard config layering. A new hydrateSentryDisabledEnvFromProjectConfig() bridges the config file to the env var before Sentry init in all three entrypoints (cli, daemon, mcp server)
  • MCP tool refactors — extracted reusable discoverProjects(), listSchemes(), and listSimulators() functions from the MCP *Logic wrappers so both the MCP layer and CLI can call them directly. No behavior change to MCP tool responses
  • init command modernized — switched from raw readline to @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 --dest
  • persistProjectConfigPatch() — new utility for atomic config file updates with support for mutually exclusive key cleanup (projectPath vs workspacePath)

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/xcodebuildmcp@247

commit: 116725f

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.
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

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 logLevel variant
    • Added 'logLevel' to the internalKeys set to filter out the camelCase variant created by yargs camel-case-expansion.
  • ✅ Fixed: Inconsistent TTY check in ensureAgentsGuidance after refactor
    • Changed guard from process.stdin.isTTY to isInteractiveTTY() to match the check used by promptConfirm function.

Create PR

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)) {
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment on lines 683 to 689
allowedTargets.push(target);
}

if (clientFlag === 'claude') {
if (clientFlag === 'claude' || selectionMode === 'interactive') {
return { allowedTargets: targets, skippedClients: [] };
}

Copy link

Choose a reason for hiding this comment

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

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.

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