@amplitude/wizard 1.0.0-beta.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/bin.js +42 -8
  2. package/dist/package.json +2 -1
  3. package/dist/src/lib/agent-interface.js +10 -22
  4. package/dist/src/lib/agent-runner.js +4 -6
  5. package/dist/src/lib/commandments.js +1 -1
  6. package/dist/src/lib/constants.d.ts +0 -3
  7. package/dist/src/lib/constants.js +1 -4
  8. package/dist/src/lib/wizard-session.d.ts +6 -0
  9. package/dist/src/lib/wizard-session.js +1 -0
  10. package/dist/src/run.js +1 -1
  11. package/dist/src/steps/add-mcp-server-to-clients/index.js +3 -3
  12. package/dist/src/steps/add-or-update-environment-variables.js +5 -17
  13. package/dist/src/steps/run-prettier.js +1 -1
  14. package/dist/src/steps/upload-environment-variables/index.js +2 -2
  15. package/dist/src/ui/tui/components/ConsoleView.js +16 -2
  16. package/dist/src/ui/tui/console-commands.d.ts +5 -0
  17. package/dist/src/ui/tui/console-commands.js +15 -0
  18. package/dist/src/ui/tui/screens/AuthScreen.js +72 -14
  19. package/dist/src/ui/tui/screens/ChecklistScreen.js +1 -1
  20. package/dist/src/ui/tui/screens/IntroScreen.js +2 -2
  21. package/dist/src/ui/tui/screens/McpScreen.js +42 -27
  22. package/dist/src/ui/tui/screens/OutroScreen.js +1 -1
  23. package/dist/src/ui/tui/store.d.ts +17 -0
  24. package/dist/src/ui/tui/store.js +55 -19
  25. package/dist/src/utils/analytics.d.ts +27 -3
  26. package/dist/src/utils/analytics.js +83 -44
  27. package/dist/src/utils/oauth.js +1 -1
  28. package/dist/src/utils/setup-utils.d.ts +11 -0
  29. package/dist/src/utils/setup-utils.js +81 -4
  30. package/dist/src/utils/shell-completions.d.ts +2 -2
  31. package/dist/src/utils/shell-completions.js +8 -1
  32. package/dist/src/utils/track-wizard-feedback.d.ts +5 -0
  33. package/dist/src/utils/track-wizard-feedback.js +25 -0
  34. package/package.json +2 -1
package/dist/bin.js CHANGED
@@ -142,11 +142,6 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
142
142
  if (options.ci) {
143
143
  // Use LoggingUI for CI mode (no dependencies, no prompts)
144
144
  (0, ui_1.setUI)(new logging_ui_1.LoggingUI());
145
- if (!options.apiKey) {
146
- (0, ui_1.getUI)().intro(chalk_1.default.inverse(`Amplitude Wizard`));
147
- (0, ui_1.getUI)().log.error('CI mode requires --api-key (Amplitude project API key)');
148
- process.exit(1);
149
- }
150
145
  if (!options.installDir) {
151
146
  (0, ui_1.getUI)().intro(chalk_1.default.inverse(`Amplitude Wizard`));
152
147
  (0, ui_1.getUI)().log.error('CI mode requires --install-dir (directory to install Amplitude in)');
@@ -161,7 +156,8 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
161
156
  'It appears you are running in a non-interactive environment.\n' +
162
157
  'Please run the wizard in an interactive terminal.\n\n' +
163
158
  'For CI/CD environments, use --ci mode:\n' +
164
- ' npx @amplitude/wizard --ci --api-key <your-key> --install-dir .');
159
+ ' npx @amplitude/wizard --ci --install-dir . [--api-key <your-key>]\n' +
160
+ ' (--api-key is optional when a key can be resolved from env or stored credentials.)');
165
161
  process.exit(1);
166
162
  }
167
163
  else {
@@ -476,11 +472,19 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
476
472
  const localDetection = detectAmplitudeInProject(installDir);
477
473
  if (localDetection.confidence !== 'none') {
478
474
  const { logToFile: log } = await import('./src/utils/debug.js');
479
- log(`[bin] Amplitude already detected (${localDetection.reason ?? 'unknown'}) — skipping agent`);
475
+ log(`[bin] Amplitude already detected (${localDetection.reason ?? 'unknown'}) — prompting on MCP screen (continue vs run wizard)`);
480
476
  const { RunPhase, OutroKind } = await import('./src/lib/wizard-session.js');
481
477
  tui.store.setAmplitudePreDetected();
482
- tui.store.setOutroData({ kind: OutroKind.Success });
483
478
  tui.store.setRunPhase(RunPhase.Completed);
479
+ const runWizardAnyway = await tui.store.waitForPreDetectedChoice();
480
+ if (runWizardAnyway) {
481
+ log('[bin] user chose to run setup wizard despite pre-detection');
482
+ tui.store.resetForAgentAfterPreDetected();
483
+ await (0, run_1.runWizard)(options, tui.store.session);
484
+ }
485
+ else {
486
+ tui.store.setOutroData({ kind: OutroKind.Success });
487
+ }
484
488
  }
485
489
  else {
486
490
  await (0, run_1.runWizard)(options, tui.store.session);
@@ -589,6 +593,36 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
589
593
  }
590
594
  process.exit(0);
591
595
  })();
596
+ })
597
+ .command('feedback', 'Send product feedback (Amplitude event: wizard: Feedback Submitted)', (yargs) => {
598
+ return yargs.options({
599
+ message: {
600
+ alias: 'm',
601
+ describe: 'Feedback message',
602
+ type: 'string',
603
+ },
604
+ });
605
+ }, (argv) => {
606
+ void (async () => {
607
+ (0, ui_1.setUI)(new logging_ui_1.LoggingUI());
608
+ const fromFlag = typeof argv.message === 'string' ? argv.message.trim() : '';
609
+ const argvRest = argv._.slice(1).join(' ').trim();
610
+ const message = (fromFlag || argvRest).trim();
611
+ if (!message) {
612
+ (0, ui_1.getUI)().log.error('Usage: amplitude-wizard feedback <message> or feedback --message <message>');
613
+ process.exit(1);
614
+ }
615
+ try {
616
+ const { trackWizardFeedback } = await import('./src/utils/track-wizard-feedback.js');
617
+ await trackWizardFeedback(message);
618
+ console.log(chalk_1.default.green('✔ Thanks — your feedback was sent.'));
619
+ process.exit(0);
620
+ }
621
+ catch (e) {
622
+ console.error(chalk_1.default.red(`Feedback failed: ${e instanceof Error ? e.message : String(e)}`));
623
+ process.exit(1);
624
+ }
625
+ })();
592
626
  })
593
627
  .command('slack', 'Set up Amplitude Slack integration', (y) => y, (argv) => {
594
628
  void (async () => {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amplitude/wizard",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "homepage": "https://github.com/amplitude/wizard",
5
5
  "repository": "https://github.com/amplitude/wizard",
6
6
  "description": "The Amplitude wizard helps you to configure your project",
@@ -37,6 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@amplitude/analytics-browser": "^2.0.0",
40
+ "@amplitude/analytics-node": "^1.5.51",
40
41
  "@anthropic-ai/claude-agent-sdk": "0.2.7",
41
42
  "@inkjs/ui": "^2.0.0",
42
43
  "@inquirer/prompts": "^8.3.0",
@@ -153,7 +153,7 @@ function backupAndFixClaudeSettings(workingDirectory) {
153
153
  for (const name of ['settings.json', 'settings']) {
154
154
  const filePath = path_1.default.join(workingDirectory, '.claude', name);
155
155
  const backupPath = `${filePath}.wizard-backup`;
156
- analytics_1.analytics.wizardCapture('backedup-claude-settings');
156
+ analytics_1.analytics.wizardCapture('Claude Settings Backed Up');
157
157
  try {
158
158
  fs.copyFileSync(filePath, backupPath);
159
159
  fs.unlinkSync(filePath);
@@ -182,7 +182,7 @@ function restoreClaudeSettings(workingDirectory) {
182
182
  const backup = path_1.default.join(workingDirectory, '.claude', `${name}.wizard-backup`);
183
183
  try {
184
184
  fs.copyFileSync(backup, path_1.default.join(workingDirectory, '.claude', name));
185
- analytics_1.analytics.wizardCapture('restored-claude-settings');
185
+ analytics_1.analytics.wizardCapture('Claude Settings Restored');
186
186
  return;
187
187
  }
188
188
  catch (error) {
@@ -490,10 +490,7 @@ function wizardCanUseTool(toolName, input) {
490
490
  if (DANGEROUS_OPERATORS.test(command)) {
491
491
  (0, debug_1.logToFile)(`Denying bash command with dangerous operators: ${command}`);
492
492
  (0, debug_1.debug)(`Denying bash command with dangerous operators: ${command}`);
493
- analytics_1.analytics.wizardCapture('bash denied', {
494
- reason: 'dangerous operators',
495
- command,
496
- });
493
+ (0, analytics_1.captureWizardError)('Bash Policy', 'Dangerous shell operators are not permitted', 'wizardCanUseBash', { deny_reason: 'dangerous operators', command });
497
494
  return {
498
495
  behavior: 'deny',
499
496
  message: `Bash command not allowed. Shell operators like ; \` $ ( ) are not permitted.`,
@@ -509,10 +506,7 @@ function wizardCanUseTool(toolName, input) {
509
506
  if (/[|&]/.test(baseCommand)) {
510
507
  (0, debug_1.logToFile)(`Denying bash command with multiple pipes: ${command}`);
511
508
  (0, debug_1.debug)(`Denying bash command with multiple pipes: ${command}`);
512
- analytics_1.analytics.wizardCapture('bash denied', {
513
- reason: 'multiple pipes',
514
- command,
515
- });
509
+ (0, analytics_1.captureWizardError)('Bash Policy', 'Multiple pipes are not permitted', 'wizardCanUseBash', { deny_reason: 'multiple pipes', command });
516
510
  return {
517
511
  behavior: 'deny',
518
512
  message: `Bash command not allowed. Only single pipe to tail/head is permitted.`,
@@ -528,10 +522,7 @@ function wizardCanUseTool(toolName, input) {
528
522
  if (/[|&]/.test(normalized)) {
529
523
  (0, debug_1.logToFile)(`Denying bash command with pipe/&: ${command}`);
530
524
  (0, debug_1.debug)(`Denying bash command with pipe/&: ${command}`);
531
- analytics_1.analytics.wizardCapture('bash denied', {
532
- reason: 'disallowed pipe',
533
- command,
534
- });
525
+ (0, analytics_1.captureWizardError)('Bash Policy', 'Pipes are only allowed with tail/head', 'wizardCanUseBash', { deny_reason: 'disallowed pipe', command });
535
526
  return {
536
527
  behavior: 'deny',
537
528
  message: `Bash command not allowed. Pipes are only permitted with tail/head for output limiting.`,
@@ -545,10 +536,7 @@ function wizardCanUseTool(toolName, input) {
545
536
  }
546
537
  (0, debug_1.logToFile)(`Denying bash command: ${command}`);
547
538
  (0, debug_1.debug)(`Denying bash command: ${command}`);
548
- analytics_1.analytics.wizardCapture('bash denied', {
549
- reason: 'not in allowlist',
550
- command,
551
- });
539
+ (0, analytics_1.captureWizardError)('Bash Policy', 'Command not in allowlist', 'wizardCanUseBash', { deny_reason: 'not in allowlist', command });
552
540
  return {
553
541
  behavior: 'deny',
554
542
  message: `Bash command not allowed. Only install, build, typecheck, lint, and formatting commands are permitted.`,
@@ -783,10 +771,10 @@ async function runAgent(agentConfig, prompt, options, spinner, config, middlewar
783
771
  if (remarkMatch && remarkMatch[1]) {
784
772
  const remark = remarkMatch[1].trim();
785
773
  if (remark) {
786
- analytics_1.analytics.capture(constants_1.WIZARD_REMARK_EVENT_NAME, { remark });
774
+ analytics_1.analytics.wizardCapture('Wizard Remark', { remark });
787
775
  }
788
776
  }
789
- analytics_1.analytics.wizardCapture('agent completed', {
777
+ analytics_1.analytics.wizardCapture('Agent Completed', {
790
778
  duration_ms: durationMs,
791
779
  duration_seconds: durationSeconds,
792
780
  });
@@ -883,7 +871,7 @@ async function runAgent(agentConfig, prompt, options, spinner, config, middlewar
883
871
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
884
872
  if (attempt > 0) {
885
873
  (0, debug_1.logToFile)(`Agent stall retry: attempt ${attempt + 1} of ${MAX_RETRIES + 1}`);
886
- analytics_1.analytics.wizardCapture('agent stall retry', { attempt });
874
+ analytics_1.analytics.wizardCapture('Agent Stall Retry', { attempt });
887
875
  // Clear per-attempt output so stale error markers don't affect the fresh run
888
876
  collectedText.length = 0;
889
877
  recentStatuses.length = 0;
@@ -910,7 +898,7 @@ async function runAgent(agentConfig, prompt, options, spinner, config, middlewar
910
898
  clearTimeout(staleTimer);
911
899
  staleTimer = setTimeout(() => {
912
900
  (0, debug_1.logToFile)(`Agent stalled — no message for ${STALL_TIMEOUT_MS / 1000}s (attempt ${attempt + 1})`);
913
- analytics_1.analytics.wizardCapture('agent stall detected', {
901
+ analytics_1.analytics.wizardCapture('Agent Stall Detected', {
914
902
  attempt: attempt + 1,
915
903
  stall_timeout_ms: STALL_TIMEOUT_MS,
916
904
  });
@@ -140,7 +140,7 @@ async function runAgentWizard(config, session) {
140
140
  const versionBucket = config.detection.getVersionBucket(frameworkVersion);
141
141
  analytics_1.analytics.setTag(`${config.metadata.integration}-version`, versionBucket);
142
142
  }
143
- analytics_1.analytics.wizardCapture('agent started', {
143
+ analytics_1.analytics.wizardCapture('Agent Started', {
144
144
  integration: config.metadata.integration,
145
145
  });
146
146
  // Credentials are pre-set by bin.ts (TUI mode) via the AuthScreen SUSI flow.
@@ -256,9 +256,7 @@ async function runAgentWizard(config, session) {
256
256
  }, middleware);
257
257
  // Handle error cases detected in agent output
258
258
  if (agentResult.error === agent_interface_1.AgentErrorType.AUTH_ERROR) {
259
- analytics_1.analytics.wizardCapture('agent auth error', {
260
- integration: config.metadata.integration,
261
- });
259
+ (0, analytics_1.captureWizardError)('Agent Authentication', 'Session expired or invalid during agent run', 'agent-runner', { integration: config.metadata.integration });
262
260
  const authMessage = `Authentication failed\n\nYour Amplitude session has expired. Please run the wizard again to log in.`;
263
261
  session.credentials = null;
264
262
  session.outroData = {
@@ -297,10 +295,9 @@ async function runAgentWizard(config, session) {
297
295
  }
298
296
  if (agentResult.error === agent_interface_1.AgentErrorType.RATE_LIMIT ||
299
297
  agentResult.error === agent_interface_1.AgentErrorType.API_ERROR) {
300
- analytics_1.analytics.wizardCapture('agent api error', {
298
+ (0, analytics_1.captureWizardError)('Agent API', agentResult.message ?? 'Unknown API error', 'agent-runner', {
301
299
  integration: config.metadata.integration,
302
300
  error_type: agentResult.error,
303
- error_message: agentResult.message,
304
301
  });
305
302
  await (0, wizard_abort_1.wizardAbort)({
306
303
  message: `API Error\n\n${agentResult.message || 'Unknown error'}\n\nPlease report this error to: wizard@amplitude.com`,
@@ -401,6 +398,7 @@ STEP 5: Set up environment variables for Amplitude using the wizard-tools MCP se
401
398
  - Reference these environment variables in the code files you create instead of hardcoding the public token and host.
402
399
 
403
400
  STEP 6: Add event tracking to this project using the instrumentation skills.
401
+ - Call load_skill_menu with category "taxonomy" and install **amplitude-quickstart-taxonomy-agent** using install_skill. Load its SKILL.md and follow it when **naming events**, choosing **properties**, and scoping a **starter-kit taxonomy** (business-outcome events, property limits, funnel/linkage rules). Keep using this skill alongside instrumentation so names stay analysis-ready.
404
402
  - Call load_skill_menu with category "instrumentation" to see available instrumentation skills.
405
403
  - Install the "add-analytics-instrumentation" skill using install_skill.
406
404
  - Load the installed skill's SKILL.md file to understand the workflow.
@@ -13,7 +13,7 @@ const WIZARD_COMMANDMENTS = [
13
13
  'Always use the detect_package_manager tool from the wizard-tools MCP server to determine the package manager. Do not guess based on lockfiles or hard-code npm, yarn, pnpm, bun, pip, etc.',
14
14
  'When installing packages, start the installation as a background task and then continue with other work. Do not block waiting for installs to finish unless explicitly instructed.',
15
15
  'Before writing to any file, you MUST read that exact file immediately beforehand using the Read tool, even if you have already read it earlier in the run. This avoids tool failures and stale edits.',
16
- 'Treat feature flags, custom properties, and event names as part of an analytics contract. Prefer reusing existing names and patterns in the project. When you must introduce new ones, make them clear, descriptive, and consistent with existing conventions, and avoid scattering the same flag or property across many unrelated callsites.',
16
+ 'Treat feature flags, custom properties, and event names as part of an analytics contract. Prefer reusing existing names and patterns in the project. When you must introduce new ones, make them clear, descriptive, and consistent with existing conventions, and avoid scattering the same flag or property across many unrelated callsites. For instrumentation runs, load the bundled **amplitude-quickstart-taxonomy-agent** skill (taxonomy category via wizard-tools) and align new event names and properties with its starter-kit rules (business-outcome naming, small property sets, no redundant pageview events, funnel-friendly linkage).',
17
17
  'Prefer minimal, targeted edits that achieve the requested behavior while preserving existing structure and style. Avoid large refactors, broad reformatting, or unrelated changes unless explicitly requested.',
18
18
  'Do not spawn subagents unless explicitly instructed to do so.',
19
19
  'Use the TodoWrite tool to track your progress. Create a todo list at the start describing the high-level areas of work, mark each as in_progress when you begin it, and completed when done.',
@@ -105,11 +105,8 @@ export declare const OUTBOUND_URLS: {
105
105
  /** Bug reports and feedback. */
106
106
  githubIssues: string;
107
107
  };
108
- /** @deprecated Use OUTBOUND_URLS.githubIssues */
109
- export declare const ISSUES_URL: string;
110
108
  /** Placeholder embedded in generated code when the user skips key entry. */
111
109
  export declare const DUMMY_PROJECT_API_KEY = "_YOUR_AMPLITUDE_API_KEY_";
112
- export declare const WIZARD_REMARK_EVENT_NAME = "wizard remark";
113
110
  /** Feature flag key whose value selects a variant from WIZARD_VARIANTS. */
114
111
  export declare const WIZARD_VARIANT_FLAG_KEY = "wizard-variant";
115
112
  /** Variant key -> metadata for wizard run (VARIANT flag selects which entry to use). */
@@ -3,7 +3,7 @@
3
3
  * Shared constants for the Amplitude wizard.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DETECTION_TIMEOUT_MS = exports.AMPLITUDE_FLAG_HEADER_PREFIX = exports.AMPLITUDE_PROPERTY_HEADER_PREFIX = exports.WIZARD_USER_AGENT = exports.WIZARD_VARIANTS = exports.WIZARD_VARIANT_FLAG_KEY = exports.WIZARD_REMARK_EVENT_NAME = exports.DUMMY_PROJECT_API_KEY = exports.ISSUES_URL = exports.OUTBOUND_URLS = exports.DEFAULT_AMPLITUDE_ZONE = exports.AMPLITUDE_ZONE_SETTINGS = exports.OAUTH_PORT = exports.ANALYTICS_TEAM_TAG = exports.ANALYTICS_HOST_URL = exports.ANALYTICS_AMPLITUDE_PUBLIC_PROJECT_WRITE_KEY = exports.DEFAULT_HOST_URL = exports.DEFAULT_URL = exports.DEBUG = exports.IS_DEV = exports.Integration = void 0;
6
+ exports.DETECTION_TIMEOUT_MS = exports.AMPLITUDE_FLAG_HEADER_PREFIX = exports.AMPLITUDE_PROPERTY_HEADER_PREFIX = exports.WIZARD_USER_AGENT = exports.WIZARD_VARIANTS = exports.WIZARD_VARIANT_FLAG_KEY = exports.DUMMY_PROJECT_API_KEY = exports.OUTBOUND_URLS = exports.DEFAULT_AMPLITUDE_ZONE = exports.AMPLITUDE_ZONE_SETTINGS = exports.OAUTH_PORT = exports.ANALYTICS_TEAM_TAG = exports.ANALYTICS_HOST_URL = exports.ANALYTICS_AMPLITUDE_PUBLIC_PROJECT_WRITE_KEY = exports.DEFAULT_HOST_URL = exports.DEFAULT_URL = exports.DEBUG = exports.IS_DEV = exports.Integration = void 0;
7
7
  const package_json_1 = require("../../package.json");
8
8
  // ── Integration / CLI ───────────────────────────────────────────────
9
9
  /**
@@ -145,12 +145,9 @@ exports.OUTBOUND_URLS = {
145
145
  /** Bug reports and feedback. */
146
146
  githubIssues: 'https://github.com/amplitude/wizard/issues',
147
147
  };
148
- /** @deprecated Use OUTBOUND_URLS.githubIssues */
149
- exports.ISSUES_URL = exports.OUTBOUND_URLS.githubIssues;
150
148
  /** Placeholder embedded in generated code when the user skips key entry. */
151
149
  exports.DUMMY_PROJECT_API_KEY = '_YOUR_AMPLITUDE_API_KEY_';
152
150
  // ── Wizard run / variants ───────────────────────────────────────────
153
- exports.WIZARD_REMARK_EVENT_NAME = 'wizard remark';
154
151
  /** Feature flag key whose value selects a variant from WIZARD_VARIANTS. */
155
152
  exports.WIZARD_VARIANT_FLAG_KEY = 'wizard-variant';
156
153
  /** Variant key -> metadata for wizard run (VARIANT flag selects which entry to use). */
@@ -185,6 +185,12 @@ export interface WizardSession {
185
185
  * runPhase is set to Completed.
186
186
  */
187
187
  amplitudePreDetected: boolean;
188
+ /**
189
+ * While true, McpScreen shows a prompt: skip the agent (default) or run
190
+ * the setup wizard anyway. Cleared when the user chooses or when resetting
191
+ * for a forced wizard run.
192
+ */
193
+ amplitudePreDetectedChoicePending: boolean;
188
194
  /**
189
195
  * True once the activation check confirms events are flowing into the project.
190
196
  * Set immediately if activationLevel is already 'full', otherwise set after
@@ -119,6 +119,7 @@ function buildSession(args) {
119
119
  additionalFeatureQueue: [],
120
120
  frameworkConfig: null,
121
121
  amplitudePreDetected: false,
122
+ amplitudePreDetectedChoicePending: false,
122
123
  dataIngestionConfirmed: false,
123
124
  checklistChartComplete: false,
124
125
  checklistDashboardComplete: false,
package/dist/src/run.js CHANGED
@@ -59,7 +59,7 @@ async function runWizard(argv, session) {
59
59
  const integration = session.integration ?? (await detectAndResolveIntegration(session));
60
60
  session.integration = integration;
61
61
  analytics_1.analytics.setTag('integration', integration);
62
- analytics_1.analytics.wizardCapture('session started', {
62
+ analytics_1.analytics.wizardCapture('Session Started', {
63
63
  integration,
64
64
  ci: session.ci ?? false,
65
65
  });
@@ -58,7 +58,7 @@ const addMCPServerToClientsStep = async ({ integration, local = false, ci = fals
58
58
  });
59
59
  ui.log.success(`Added the MCP server to:
60
60
  ${supportedClients.map((c) => `- ${c.name}`).join('\n ')} `);
61
- analytics_1.analytics.wizardCapture('mcp servers added', {
61
+ analytics_1.analytics.wizardCapture('MCP Servers Added', {
62
62
  clients: supportedClients.map((c) => c.name),
63
63
  integration,
64
64
  });
@@ -68,7 +68,7 @@ exports.addMCPServerToClientsStep = addMCPServerToClientsStep;
68
68
  const removeMCPServerFromClientsStep = async ({ integration, local = false, }) => {
69
69
  const installedClients = await (0, exports.getInstalledClients)(local);
70
70
  if (installedClients.length === 0) {
71
- analytics_1.analytics.wizardCapture('mcp no servers to remove', {
71
+ analytics_1.analytics.wizardCapture('MCP No Servers To Remove', {
72
72
  integration,
73
73
  });
74
74
  return [];
@@ -78,7 +78,7 @@ const removeMCPServerFromClientsStep = async ({ integration, local = false, }) =
78
78
  await (0, exports.removeMCPServer)(installedClients, local);
79
79
  return installedClients.map((c) => c.name);
80
80
  });
81
- analytics_1.analytics.wizardCapture('mcp servers removed', {
81
+ analytics_1.analytics.wizardCapture('MCP Servers Removed', {
82
82
  clients: results,
83
83
  integration,
84
84
  });
@@ -90,10 +90,7 @@ async function addOrUpdateEnvironmentVariablesStep({ installDir, variables, inte
90
90
  }
91
91
  catch (error) {
92
92
  (0, ui_1.getUI)().log.warn(`Failed to update environment variables in ${chalk_1.default.bold.cyan(relativeEnvFilePath)}. Please update them manually.`);
93
- analytics_1.analytics.wizardCapture('env vars error', {
94
- integration,
95
- error: error instanceof Error ? error.message : 'Unknown error',
96
- });
93
+ (0, analytics_1.captureWizardError)('Environment Variables', error instanceof Error ? error.message : 'Unknown error', 'add-or-update-env:update-existing', { integration });
97
94
  return {
98
95
  relativeEnvFilePath,
99
96
  addedEnvVariables,
@@ -112,10 +109,7 @@ async function addOrUpdateEnvironmentVariablesStep({ installDir, variables, inte
112
109
  }
113
110
  catch (error) {
114
111
  (0, ui_1.getUI)().log.warn(`Failed to create ${chalk_1.default.bold.cyan(relativeEnvFilePath)} with environment variables. Please add them manually.`);
115
- analytics_1.analytics.wizardCapture('env vars error', {
116
- integration,
117
- error: error instanceof Error ? error.message : 'Unknown error',
118
- });
112
+ (0, analytics_1.captureWizardError)('Environment Variables', error instanceof Error ? error.message : 'Unknown error', 'add-or-update-env:create-new', { integration });
119
113
  return {
120
114
  relativeEnvFilePath,
121
115
  addedEnvVariables,
@@ -141,10 +135,7 @@ async function addOrUpdateEnvironmentVariablesStep({ installDir, variables, inte
141
135
  }
142
136
  catch (error) {
143
137
  (0, ui_1.getUI)().log.warn(`Failed to update ${chalk_1.default.bold.cyan('.gitignore')} to include ${chalk_1.default.bold.cyan(envFileName)}.`);
144
- analytics_1.analytics.wizardCapture('env vars error', {
145
- integration,
146
- error: error instanceof Error ? error.message : 'Unknown error',
147
- });
138
+ (0, analytics_1.captureWizardError)('Environment Variables', error instanceof Error ? error.message : 'Unknown error', 'add-or-update-env:gitignore-update', { integration });
148
139
  return {
149
140
  relativeEnvFilePath,
150
141
  addedEnvVariables,
@@ -165,10 +156,7 @@ async function addOrUpdateEnvironmentVariablesStep({ installDir, variables, inte
165
156
  }
166
157
  catch (error) {
167
158
  (0, ui_1.getUI)().log.warn(`Failed to create ${chalk_1.default.bold.cyan('.gitignore')} with environment files.`);
168
- analytics_1.analytics.wizardCapture('env vars error', {
169
- integration,
170
- error: error instanceof Error ? error.message : 'Unknown error',
171
- });
159
+ (0, analytics_1.captureWizardError)('Environment Variables', error instanceof Error ? error.message : 'Unknown error', 'add-or-update-env:gitignore-create', { integration });
172
160
  return {
173
161
  relativeEnvFilePath,
174
162
  addedEnvVariables,
@@ -176,7 +164,7 @@ async function addOrUpdateEnvironmentVariablesStep({ installDir, variables, inte
176
164
  };
177
165
  }
178
166
  }
179
- analytics_1.analytics.wizardCapture('env vars added', {
167
+ analytics_1.analytics.wizardCapture('Environment Variables Added', {
180
168
  integration,
181
169
  });
182
170
  return {
@@ -83,7 +83,7 @@ async function runPrettierStep({ installDir, integration, }) {
83
83
  return;
84
84
  }
85
85
  prettierSpinner.stop('Prettier has formatted your files.');
86
- analytics_1.analytics.wizardCapture('ran prettier', {
86
+ analytics_1.analytics.wizardCapture('Prettier Ran', {
87
87
  integration,
88
88
  });
89
89
  });
@@ -17,7 +17,7 @@ const uploadEnvironmentVariablesStep = async (envVars, { integration, session, }
17
17
  }
18
18
  }
19
19
  if (!provider) {
20
- analytics_1.analytics.wizardCapture('env upload skipped', {
20
+ analytics_1.analytics.wizardCapture('Env Upload Skipped', {
21
21
  reason: 'no environment provider found',
22
22
  integration,
23
23
  });
@@ -28,7 +28,7 @@ const uploadEnvironmentVariablesStep = async (envVars, { integration, session, }
28
28
  const results = await (0, telemetry_1.traceStep)('uploading environment variables', async () => {
29
29
  return await provider.uploadEnvVars(envVars);
30
30
  });
31
- analytics_1.analytics.wizardCapture('env uploaded', {
31
+ analytics_1.analytics.wizardCapture('Env Uploaded', {
32
32
  provider: provider.name,
33
33
  integration,
34
34
  });
@@ -21,8 +21,9 @@ import { PickerMenu } from '../primitives/PickerMenu.js';
21
21
  import { Colors, Icons } from '../styles.js';
22
22
  import { Overlay } from '../router.js';
23
23
  import { queryConsole, resolveConsoleCredentials, buildSessionContext, } from '../../../lib/console-query.js';
24
- import { COMMANDS, getWhoamiText, getHelpText, TEST_PROMPT, } from '../console-commands.js';
24
+ import { COMMANDS, getWhoamiText, getHelpText, parseFeedbackSlashInput, TEST_PROMPT, } from '../console-commands.js';
25
25
  import { analytics } from '../../../utils/analytics.js';
26
+ import { trackWizardFeedback } from '../../../utils/track-wizard-feedback.js';
26
27
  function executeCommand(raw, store) {
27
28
  const [cmd] = raw.trim().split(/\s+/);
28
29
  switch (cmd) {
@@ -41,6 +42,19 @@ function executeCommand(raw, store) {
41
42
  case '/slack':
42
43
  store.showSlackOverlay();
43
44
  break;
45
+ case '/feedback': {
46
+ const message = parseFeedbackSlashInput(raw);
47
+ if (!message) {
48
+ store.setCommandFeedback('Usage: /feedback <your message>');
49
+ break;
50
+ }
51
+ void trackWizardFeedback(message)
52
+ .then(() => store.setCommandFeedback('Thanks — your feedback was sent.'))
53
+ .catch((err) => {
54
+ store.setCommandFeedback(`Could not send feedback: ${err instanceof Error ? err.message : String(err)}`);
55
+ });
56
+ break;
57
+ }
44
58
  case '/test':
45
59
  return TEST_PROMPT;
46
60
  case '/mcp':
@@ -116,7 +130,7 @@ export const ConsoleView = ({ store, width, height, children, }) => {
116
130
  }, { isActive: !inputActive });
117
131
  const handleSubmit = (value) => {
118
132
  const isSlashCommand = value.startsWith('/');
119
- analytics.wizardCapture('agent message sent', {
133
+ analytics.wizardCapture('Agent Message Sent', {
120
134
  message_length: value.length,
121
135
  is_slash_command: isSlashCommand,
122
136
  });
@@ -14,3 +14,8 @@ export declare const TEST_PROMPT: string;
14
14
  export declare function getWhoamiText(session: Pick<WizardSession, 'selectedOrgName' | 'selectedWorkspaceName' | 'region'>): string;
15
15
  /** Returns the feedback text for the /help command. */
16
16
  export declare function getHelpText(): string;
17
+ /**
18
+ * Parses `/feedback <message>` from a slash command line.
19
+ * Returns `undefined` if the line is not a feedback command or the message is empty.
20
+ */
21
+ export declare function parseFeedbackSlashInput(raw: string): string | undefined;
@@ -11,6 +11,10 @@ export const COMMANDS = [
11
11
  { cmd: '/whoami', desc: 'Show current user, org, and project' },
12
12
  { cmd: '/mcp', desc: 'Install or remove the Amplitude MCP server' },
13
13
  { cmd: '/slack', desc: 'Set up Amplitude Slack integration' },
14
+ {
15
+ cmd: '/feedback',
16
+ desc: 'Send product feedback (event: wizard: feedback submitted)',
17
+ },
14
18
  { cmd: '/test', desc: 'Run a prompt-skill demo (confirm + choose)' },
15
19
  { cmd: '/snake', desc: 'Play Snake' },
16
20
  { cmd: '/exit', desc: 'Exit the wizard' },
@@ -29,3 +33,14 @@ export function getHelpText() {
29
33
  const maxCmd = Math.max(...COMMANDS.map((c) => c.cmd.length));
30
34
  return COMMANDS.map((c) => `${c.cmd.padEnd(maxCmd)} ${c.desc}`).join('\n');
31
35
  }
36
+ /**
37
+ * Parses `/feedback <message>` from a slash command line.
38
+ * Returns `undefined` if the line is not a feedback command or the message is empty.
39
+ */
40
+ export function parseFeedbackSlashInput(raw) {
41
+ const m = /^\s*\/feedback(?:\s+(.*))?\s*$/i.exec(raw);
42
+ if (!m)
43
+ return undefined;
44
+ const body = m[1]?.trim();
45
+ return body || undefined;
46
+ }
@@ -16,7 +16,7 @@ import { useState, useEffect, useSyncExternalStore } from 'react';
16
16
  import { TextInput } from '@inkjs/ui';
17
17
  import { LoadingBox, PickerMenu } from '../primitives/index.js';
18
18
  import { Colors } from '../styles.js';
19
- import { DEFAULT_HOST_URL } from '../../../lib/constants.js';
19
+ import { DEFAULT_HOST_URL, } from '../../../lib/constants.js';
20
20
  import { analytics } from '../../../utils/analytics.js';
21
21
  export const AuthScreen = ({ store }) => {
22
22
  useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
@@ -37,28 +37,85 @@ export const AuthScreen = ({ store }) => {
37
37
  }, [effectiveOrg?.id, singleWorkspace?.id, session.selectedWorkspaceId]);
38
38
  const workspaceChosen = session.selectedWorkspaceId !== null ||
39
39
  (effectiveOrg !== null && effectiveOrg.workspaces.length === 1);
40
- // Auto-advance past API key step if a saved key exists for this project
40
+ // Resolve API key: local storage first, then Amplitude backend (same as bin.ts
41
+ // for returning users) once org/workspace are on the session after OAuth.
41
42
  useEffect(() => {
42
43
  if (!workspaceChosen || session.credentials !== null)
43
44
  return;
44
- void import('../../../utils/api-key-store.js').then(({ readApiKeyWithSource }) => {
45
- const result = readApiKeyWithSource(session.installDir);
46
- if (result) {
47
- setSavedKeySource(result.source);
48
- analytics.wizardCapture('api key submitted', {
49
- key_source: result.source,
45
+ let cancelled = false;
46
+ void (async () => {
47
+ const s = store.session;
48
+ if (s.credentials !== null)
49
+ return;
50
+ const { readApiKeyWithSource, persistApiKey } = await import('../../../utils/api-key-store.js');
51
+ if (cancelled)
52
+ return;
53
+ const local = readApiKeyWithSource(s.installDir);
54
+ if (local) {
55
+ setSavedKeySource(local.source);
56
+ analytics.wizardCapture('API Key Submitted', {
57
+ key_source: local.source,
50
58
  });
51
59
  store.setCredentials({
52
- accessToken: session.pendingAuthAccessToken ?? '',
53
- idToken: session.pendingAuthIdToken ?? undefined,
54
- projectApiKey: result.key,
60
+ accessToken: s.pendingAuthAccessToken ?? '',
61
+ idToken: s.pendingAuthIdToken ?? undefined,
62
+ projectApiKey: local.key,
55
63
  host: DEFAULT_HOST_URL,
56
64
  projectId: 0,
57
65
  });
58
66
  store.setProjectHasData(false);
67
+ store.setApiKeyNotice(null);
68
+ return;
59
69
  }
60
- });
61
- }, [workspaceChosen, session.credentials]);
70
+ const idToken = s.pendingAuthIdToken;
71
+ if (!idToken)
72
+ return;
73
+ const zone = (s.region ??
74
+ s.pendingAuthCloudRegion ??
75
+ 'us');
76
+ const { getAPIKey } = await import('../../../utils/get-api-key.js');
77
+ const { getHostFromRegion } = await import('../../../utils/urls.js');
78
+ const projectApiKey = await getAPIKey({
79
+ installDir: s.installDir,
80
+ idToken,
81
+ zone,
82
+ workspaceId: s.selectedWorkspaceId ?? undefined,
83
+ });
84
+ if (cancelled || store.session.credentials !== null)
85
+ return;
86
+ if (projectApiKey) {
87
+ persistApiKey(projectApiKey, s.installDir);
88
+ analytics.wizardCapture('API Key Submitted', {
89
+ key_source: 'backend_fetch',
90
+ });
91
+ store.setCredentials({
92
+ accessToken: s.pendingAuthAccessToken ?? '',
93
+ idToken: s.pendingAuthIdToken ?? undefined,
94
+ projectApiKey,
95
+ host: getHostFromRegion(zone),
96
+ projectId: 0,
97
+ });
98
+ store.setProjectHasData(false);
99
+ store.setApiKeyNotice(null);
100
+ }
101
+ else {
102
+ store.setApiKeyNotice("Your API key couldn't be fetched automatically. " +
103
+ 'Only organization admins can access project API keys — ' +
104
+ 'if you need one, ask an admin to share it with you.');
105
+ }
106
+ })();
107
+ return () => {
108
+ cancelled = true;
109
+ };
110
+ }, [
111
+ workspaceChosen,
112
+ session.credentials,
113
+ session.selectedWorkspaceId,
114
+ session.pendingAuthIdToken,
115
+ session.region,
116
+ session.pendingAuthCloudRegion,
117
+ session.installDir,
118
+ ]);
62
119
  const needsOrgPick = pendingOrgs !== null && pendingOrgs.length > 1 && effectiveOrg === null;
63
120
  const needsWorkspacePick = effectiveOrg !== null &&
64
121
  effectiveOrg.workspaces.length > 1 &&
@@ -71,9 +128,10 @@ export const AuthScreen = ({ store }) => {
71
128
  return;
72
129
  }
73
130
  setApiKeyError('');
74
- analytics.wizardCapture('api key submitted', {
131
+ analytics.wizardCapture('API Key Submitted', {
75
132
  key_source: 'manual_entry',
76
133
  });
134
+ store.setApiKeyNotice(null);
77
135
  store.setCredentials({
78
136
  accessToken: session.pendingAuthAccessToken ?? '',
79
137
  idToken: session.pendingAuthIdToken ?? undefined,
@@ -56,7 +56,7 @@ export const ChecklistScreen = ({ store }) => {
56
56
  const dashboardUrl = OUTBOUND_URLS.newDashboard(zone, selectedOrgId);
57
57
  function openInBrowser(url, item) {
58
58
  setOpening(item);
59
- analytics.wizardCapture('checklist item opened', { item });
59
+ analytics.wizardCapture('Checklist Step Opened', { item });
60
60
  opn(url, { wait: false })
61
61
  .catch(() => {
62
62
  /* fire-and-forget */