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

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 (43) hide show
  1. package/dist/bin.js +191 -47
  2. package/dist/src/lib/agent-interface.js +10 -22
  3. package/dist/src/lib/agent-runner.js +4 -6
  4. package/dist/src/lib/commandments.js +1 -1
  5. package/dist/src/lib/constants.d.ts +1 -4
  6. package/dist/src/lib/constants.js +9 -8
  7. package/dist/src/lib/feature-flags.d.ts +37 -0
  8. package/dist/src/lib/feature-flags.js +119 -0
  9. package/dist/src/lib/wizard-session.d.ts +16 -0
  10. package/dist/src/lib/wizard-session.js +2 -0
  11. package/dist/src/run.js +1 -1
  12. package/dist/src/steps/add-mcp-server-to-clients/index.js +3 -3
  13. package/dist/src/steps/add-or-update-environment-variables.js +5 -17
  14. package/dist/src/steps/run-prettier.js +1 -1
  15. package/dist/src/steps/upload-environment-variables/index.js +2 -2
  16. package/dist/src/ui/tui/App.js +1 -1
  17. package/dist/src/ui/tui/components/ConsoleView.js +17 -6
  18. package/dist/src/ui/tui/components/TitleBar.d.ts +3 -1
  19. package/dist/src/ui/tui/components/TitleBar.js +17 -6
  20. package/dist/src/ui/tui/console-commands.d.ts +5 -2
  21. package/dist/src/ui/tui/console-commands.js +14 -5
  22. package/dist/src/ui/tui/screens/AuthScreen.d.ts +2 -1
  23. package/dist/src/ui/tui/screens/AuthScreen.js +166 -26
  24. package/dist/src/ui/tui/screens/ChecklistScreen.js +1 -1
  25. package/dist/src/ui/tui/screens/DataIngestionCheckScreen.js +13 -2
  26. package/dist/src/ui/tui/screens/IntroScreen.js +2 -2
  27. package/dist/src/ui/tui/screens/McpScreen.js +42 -27
  28. package/dist/src/ui/tui/screens/OutroScreen.js +1 -2
  29. package/dist/src/ui/tui/screens/SlackScreen.d.ts +0 -5
  30. package/dist/src/ui/tui/screens/SlackScreen.js +1 -11
  31. package/dist/src/ui/tui/store.d.ts +20 -0
  32. package/dist/src/ui/tui/store.js +68 -19
  33. package/dist/src/utils/analytics.d.ts +45 -3
  34. package/dist/src/utils/analytics.js +118 -47
  35. package/dist/src/utils/oauth.js +1 -1
  36. package/dist/src/utils/setup-utils.d.ts +11 -0
  37. package/dist/src/utils/setup-utils.js +81 -4
  38. package/dist/src/utils/shell-completions.d.ts +2 -2
  39. package/dist/src/utils/shell-completions.js +8 -1
  40. package/dist/src/utils/track-wizard-feedback.d.ts +5 -0
  41. package/dist/src/utils/track-wizard-feedback.js +25 -0
  42. package/package.json +13 -13
  43. package/dist/package.json +0 -144
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 {
@@ -205,12 +201,13 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
205
201
  // returning users. This skips RegionSelect and Auth without requiring
206
202
  // a persisted OAuth token.
207
203
  const installDir = session.installDir;
208
- const [{ getStoredUser, getStoredToken }, { readAmpliConfig }, { getAPIKey }, { getHostFromRegion }, { logToFile },] = await Promise.all([
204
+ const [{ getStoredUser, getStoredToken }, { readAmpliConfig }, { getAPIKey }, { getHostFromRegion }, { logToFile }, { fetchAmplitudeUser },] = await Promise.all([
209
205
  import('./src/utils/ampli-settings.js'),
210
206
  import('./src/lib/ampli-config.js'),
211
207
  import('./src/utils/get-api-key.js'),
212
208
  import('./src/utils/urls.js'),
213
209
  import('./src/utils/debug.js'),
210
+ import('./src/lib/api.js'),
214
211
  ]);
215
212
  // Zone: prefer a real (non-pending) stored user, fall back to
216
213
  // the Zone field in the project-level ampli.json.
@@ -226,43 +223,108 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
226
223
  }
227
224
  // Skip Auth when we have a stored OAuth token — use it to fetch
228
225
  // (or look up) the project API key, then pre-populate credentials.
226
+ // When the workspace has multiple environments (projects), defer to
227
+ // AuthScreen so the user can pick which project to instrument.
229
228
  if (zone) {
230
229
  const storedToken = realUser
231
230
  ? getStoredToken(realUser.id, realUser.zone)
232
231
  : getStoredToken(undefined, zone);
233
232
  if (storedToken) {
234
- logToFile(`[bin] getAPIKey: zone=${zone} hasWorkspaceId=${!!session.selectedWorkspaceId}`);
235
- const projectApiKey = await getAPIKey({
236
- installDir,
237
- idToken: storedToken.idToken,
238
- zone,
239
- workspaceId: session.selectedWorkspaceId ?? undefined,
240
- });
241
- if (projectApiKey) {
242
- logToFile('[bin] getAPIKey: resolved project API key');
243
- (0, api_key_store_1.persistApiKey)(projectApiKey, installDir);
233
+ // Check local storage first — if a key is already persisted
234
+ // for this install dir, use it without fetching user data.
235
+ const { readApiKeyWithSource } = await import('./src/utils/api-key-store.js');
236
+ const localKey = readApiKeyWithSource(installDir);
237
+ if (localKey) {
238
+ logToFile('[bin] using locally stored API key');
244
239
  session.credentials = {
245
240
  accessToken: storedToken.idToken,
246
241
  idToken: storedToken.idToken,
247
- projectApiKey,
242
+ projectApiKey: localKey.key,
248
243
  host: getHostFromRegion(zone),
249
244
  projectId: 0,
250
245
  };
251
- // Pre-populate activationLevel so DataSetup is also skipped,
252
- // giving a single wipe from Intro → Run/Setup.
253
- // DataSetup would set 'none' anyway (projectId=0 prevents the
254
- // real check), so this is equivalent — just earlier.
255
246
  session.activationLevel = 'none';
256
247
  session.projectHasData = false;
257
248
  }
258
249
  else {
259
- logToFile('[bin] getAPIKey: returned null showing apiKeyNotice');
260
- // Region is already pre-populated above; prompt for the
261
- // key manually with a hint about org-admin permissions.
262
- session.apiKeyNotice =
263
- "Your API key couldn't be fetched automatically. " +
264
- 'Only organization admins can access project API keys — ' +
265
- 'if you need one, ask an admin to share it with you.';
250
+ // Fetch user data to check how many environments are available.
251
+ const { fetchAmplitudeUser } = await import('./src/lib/api.js');
252
+ try {
253
+ const userInfo = await fetchAmplitudeUser(storedToken.idToken, zone);
254
+ const workspaceId = session.selectedWorkspaceId ?? undefined;
255
+ // Find the relevant workspace and its environments
256
+ let envsWithKey = [];
257
+ for (const org of userInfo.orgs) {
258
+ const ws = workspaceId
259
+ ? org.workspaces.find((w) => w.id === workspaceId)
260
+ : org.workspaces[0];
261
+ if (ws?.environments) {
262
+ envsWithKey = ws.environments
263
+ .filter((env) => env.app?.apiKey)
264
+ .sort((a, b) => a.rank - b.rank);
265
+ break;
266
+ }
267
+ }
268
+ if (envsWithKey.length === 1) {
269
+ // Single environment — auto-select as before
270
+ const apiKey = envsWithKey[0].app.apiKey;
271
+ session.selectedProjectName = envsWithKey[0].name;
272
+ logToFile('[bin] single environment — auto-selecting API key');
273
+ (0, api_key_store_1.persistApiKey)(apiKey, installDir);
274
+ session.credentials = {
275
+ accessToken: storedToken.idToken,
276
+ idToken: storedToken.idToken,
277
+ projectApiKey: apiKey,
278
+ host: getHostFromRegion(zone),
279
+ projectId: 0,
280
+ };
281
+ session.activationLevel = 'none';
282
+ session.projectHasData = false;
283
+ }
284
+ else if (envsWithKey.length > 1) {
285
+ // Multiple environments — show the project picker via
286
+ // AuthScreen instead of auto-selecting.
287
+ logToFile(`[bin] ${envsWithKey.length} environments found — deferring to project picker`);
288
+ session.pendingOrgs = userInfo.orgs;
289
+ session.pendingAuthIdToken = storedToken.idToken;
290
+ session.pendingAuthAccessToken = storedToken.idToken;
291
+ }
292
+ else {
293
+ logToFile('[bin] no environments with API keys — showing apiKeyNotice');
294
+ session.apiKeyNotice =
295
+ "Your API key couldn't be fetched automatically. " +
296
+ 'Only organization admins can access project API keys — ' +
297
+ 'if you need one, ask an admin to share it with you.';
298
+ }
299
+ }
300
+ catch (err) {
301
+ logToFile(`[bin] fetchAmplitudeUser failed: ${err instanceof Error ? err.message : 'unknown'}`);
302
+ // Fall back to getAPIKey for backward compatibility
303
+ const projectApiKey = await getAPIKey({
304
+ installDir,
305
+ idToken: storedToken.idToken,
306
+ zone,
307
+ workspaceId: session.selectedWorkspaceId ?? undefined,
308
+ });
309
+ if (projectApiKey) {
310
+ (0, api_key_store_1.persistApiKey)(projectApiKey, installDir);
311
+ session.credentials = {
312
+ accessToken: storedToken.idToken,
313
+ idToken: storedToken.idToken,
314
+ projectApiKey,
315
+ host: getHostFromRegion(zone),
316
+ projectId: 0,
317
+ };
318
+ session.activationLevel = 'none';
319
+ session.projectHasData = false;
320
+ }
321
+ else {
322
+ session.apiKeyNotice =
323
+ "Your API key couldn't be fetched automatically. " +
324
+ 'Only organization admins can access project API keys — ' +
325
+ 'if you need one, ask an admin to share it with you.';
326
+ }
327
+ }
266
328
  }
267
329
  }
268
330
  }
@@ -275,8 +337,46 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
275
337
  if (projectConfig.ok && projectConfig.config.WorkspaceId) {
276
338
  session.selectedWorkspaceId = projectConfig.config.WorkspaceId;
277
339
  }
340
+ // Resolve org/workspace display names so /whoami shows them.
341
+ // Uses the stored token to fetch user info — fire-and-forget so it
342
+ // doesn't block startup.
343
+ if (zone && session.selectedOrgId) {
344
+ const storedToken = realUser
345
+ ? getStoredToken(realUser.id, realUser.zone)
346
+ : getStoredToken(undefined, zone);
347
+ if (storedToken) {
348
+ fetchAmplitudeUser(storedToken.idToken, zone)
349
+ .then((userInfo) => {
350
+ const org = userInfo.orgs.find((o) => o.id === session.selectedOrgId);
351
+ if (org) {
352
+ session.selectedOrgName = org.name;
353
+ const ws = session.selectedWorkspaceId
354
+ ? org.workspaces.find((w) => w.id === session.selectedWorkspaceId)
355
+ : undefined;
356
+ if (ws) {
357
+ session.selectedWorkspaceName = ws.name;
358
+ }
359
+ // Update the store if it's already been assigned
360
+ if (tui.store.session === session) {
361
+ tui.store.emitChange();
362
+ }
363
+ }
364
+ })
365
+ .catch(() => {
366
+ // Non-fatal — /whoami will just show (none)
367
+ });
368
+ }
369
+ }
278
370
  }
279
371
  tui.store.session = session;
372
+ // Initialize Amplitude Experiment feature flags (non-blocking).
373
+ const { initFeatureFlags } = await import('./src/lib/feature-flags.js');
374
+ await initFeatureFlags().catch(() => {
375
+ // Flag init failure is non-fatal — all flags default to off
376
+ });
377
+ // Apply SDK-level opt-out based on feature flags
378
+ const { analytics } = await import('./src/utils/analytics.js');
379
+ analytics.applyOptOut();
280
380
  const { FRAMEWORK_REGISTRY } = await import('./src/lib/registry.js');
281
381
  const { detectIntegration } = await import('./src/run.js');
282
382
  const installDir = session.installDir ?? process.cwd();
@@ -431,22 +531,27 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
431
531
  tui.store.addDiscoveredFeature(DiscoveredFeature.Stripe);
432
532
  }
433
533
  // LLM SDK detection — sourced from Amplitude LLM analytics skill
434
- const LLM_PACKAGES = [
435
- 'openai',
436
- '@anthropic-ai/sdk',
437
- 'ai',
438
- '@ai-sdk/openai',
439
- 'langchain',
440
- '@langchain/openai',
441
- '@langchain/langgraph',
442
- '@google/generative-ai',
443
- '@google/genai',
444
- '@instructor-ai/instructor',
445
- '@mastra/core',
446
- 'portkey-ai',
447
- ];
448
- if (depNames.some((d) => LLM_PACKAGES.includes(d))) {
449
- tui.store.addDiscoveredFeature(DiscoveredFeature.LLM);
534
+ // Gated by the wizard-llm-analytics feature flag.
535
+ const { isFlagEnabled } = await import('./src/lib/feature-flags.js');
536
+ const { FLAG_LLM_ANALYTICS } = await import('./src/lib/feature-flags.js');
537
+ if (isFlagEnabled(FLAG_LLM_ANALYTICS)) {
538
+ const LLM_PACKAGES = [
539
+ 'openai',
540
+ '@anthropic-ai/sdk',
541
+ 'ai',
542
+ '@ai-sdk/openai',
543
+ 'langchain',
544
+ '@langchain/openai',
545
+ '@langchain/langgraph',
546
+ '@google/generative-ai',
547
+ '@google/genai',
548
+ '@instructor-ai/instructor',
549
+ '@mastra/core',
550
+ 'portkey-ai',
551
+ ];
552
+ if (depNames.some((d) => LLM_PACKAGES.includes(d))) {
553
+ tui.store.addDiscoveredFeature(DiscoveredFeature.LLM);
554
+ }
450
555
  }
451
556
  }
452
557
  catch {
@@ -476,11 +581,19 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
476
581
  const localDetection = detectAmplitudeInProject(installDir);
477
582
  if (localDetection.confidence !== 'none') {
478
583
  const { logToFile: log } = await import('./src/utils/debug.js');
479
- log(`[bin] Amplitude already detected (${localDetection.reason ?? 'unknown'}) — skipping agent`);
584
+ log(`[bin] Amplitude already detected (${localDetection.reason ?? 'unknown'}) — prompting on MCP screen (continue vs run wizard)`);
480
585
  const { RunPhase, OutroKind } = await import('./src/lib/wizard-session.js');
481
586
  tui.store.setAmplitudePreDetected();
482
- tui.store.setOutroData({ kind: OutroKind.Success });
483
587
  tui.store.setRunPhase(RunPhase.Completed);
588
+ const runWizardAnyway = await tui.store.waitForPreDetectedChoice();
589
+ if (runWizardAnyway) {
590
+ log('[bin] user chose to run setup wizard despite pre-detection');
591
+ tui.store.resetForAgentAfterPreDetected();
592
+ await (0, run_1.runWizard)(options, tui.store.session);
593
+ }
594
+ else {
595
+ tui.store.setOutroData({ kind: OutroKind.Success });
596
+ }
484
597
  }
485
598
  else {
486
599
  await (0, run_1.runWizard)(options, tui.store.session);
@@ -589,6 +702,37 @@ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
589
702
  }
590
703
  process.exit(0);
591
704
  })();
705
+ })
706
+ .command('feedback', 'Send product feedback (Amplitude event: wizard: Feedback Submitted)', (yargs) => {
707
+ return yargs.options({
708
+ message: {
709
+ alias: 'm',
710
+ describe: 'Feedback message',
711
+ type: 'string',
712
+ },
713
+ });
714
+ }, (argv) => {
715
+ void (async () => {
716
+ (0, ui_1.setUI)(new logging_ui_1.LoggingUI());
717
+ const fromFlag = typeof argv.message === 'string' ? argv.message.trim() : '';
718
+ const argvRest = argv._.slice(1).join(' ').trim();
719
+ const message = (fromFlag || argvRest).trim();
720
+ if (!message) {
721
+ (0, ui_1.getUI)().log.error('Usage: amplitude-wizard feedback <message> or feedback --message <message>');
722
+ process.exit(1);
723
+ return;
724
+ }
725
+ try {
726
+ const { trackWizardFeedback } = await import('./src/utils/track-wizard-feedback.js');
727
+ await trackWizardFeedback(message);
728
+ console.log(chalk_1.default.green('✔ Thanks — your feedback was sent.'));
729
+ process.exit(0);
730
+ }
731
+ catch (e) {
732
+ console.error(chalk_1.default.red(`Feedback failed: ${e instanceof Error ? e.message : String(e)}`));
733
+ process.exit(1);
734
+ }
735
+ })();
592
736
  })
593
737
  .command('slack', 'Set up Amplitude Slack integration', (y) => y, (argv) => {
594
738
  void (async () => {
@@ -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.',
@@ -77,7 +77,7 @@ export declare const OUTBOUND_URLS: {
77
77
  /** New dashboard — opened from the Checklist screen. */
78
78
  newDashboard: (zone: AmplitudeZone, orgId?: string | null) => string;
79
79
  /** Slack integration settings — opened from the Slack screen. */
80
- slackSettings: (zone: AmplitudeZone, orgName?: string | null) => string;
80
+ slackSettings: (zone: AmplitudeZone, orgId?: string | null, orgName?: string | null) => string;
81
81
  /** Products page — shown in the Outro for sign-up users. */
82
82
  products: (zone: AmplitudeZone) => string;
83
83
  /** SDK overview — opened from the Activation Options screen. */
@@ -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
  /**
@@ -108,11 +108,15 @@ exports.OUTBOUND_URLS = {
108
108
  },
109
109
  // ── Post-setup ────────────────────────────────────────────────────────────
110
110
  /** Slack integration settings — opened from the Slack screen. */
111
- slackSettings: (zone, orgName) => {
111
+ slackSettings: (zone, orgId, orgName) => {
112
112
  const base = exports.OUTBOUND_URLS.overview[zone];
113
- return orgName
114
- ? `${base}/analytics/${encodeURIComponent(orgName)}/settings/profile`
115
- : `${base}/settings/profile`;
113
+ if (orgName) {
114
+ return `${base}/analytics/${encodeURIComponent(orgName)}/settings/profile`;
115
+ }
116
+ if (orgId) {
117
+ return `${base}/${orgId}/settings/profile`;
118
+ }
119
+ return `${base}/settings/profile`;
116
120
  },
117
121
  /** Products page — shown in the Outro for sign-up users. */
118
122
  products: (zone) => `${exports.OUTBOUND_URLS.overview[zone]}/products?source=wizard`,
@@ -145,12 +149,9 @@ exports.OUTBOUND_URLS = {
145
149
  /** Bug reports and feedback. */
146
150
  githubIssues: 'https://github.com/amplitude/wizard/issues',
147
151
  };
148
- /** @deprecated Use OUTBOUND_URLS.githubIssues */
149
- exports.ISSUES_URL = exports.OUTBOUND_URLS.githubIssues;
150
152
  /** Placeholder embedded in generated code when the user skips key entry. */
151
153
  exports.DUMMY_PROJECT_API_KEY = '_YOUR_AMPLITUDE_API_KEY_';
152
154
  // ── Wizard run / variants ───────────────────────────────────────────
153
- exports.WIZARD_REMARK_EVENT_NAME = 'wizard remark';
154
155
  /** Feature flag key whose value selects a variant from WIZARD_VARIANTS. */
155
156
  exports.WIZARD_VARIANT_FLAG_KEY = 'wizard-variant';
156
157
  /** Variant key -> metadata for wizard run (VARIANT flag selects which entry to use). */
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Feature flags powered by Amplitude Experiment (server-side local evaluation).
3
+ *
4
+ * Flags are fetched once at startup via `initFeatureFlags()` and evaluated
5
+ * synchronously thereafter — no per-check network calls.
6
+ */
7
+ /** Gate for the LLM analytics additional-feature flow. */
8
+ export declare const FLAG_LLM_ANALYTICS = "wizard-llm-analytics";
9
+ /** Gate for agent-level analytics / telemetry instrumented by the wizard. */
10
+ export declare const FLAG_AGENT_ANALYTICS = "wizard-agent-analytics";
11
+ /**
12
+ * Initialize the Experiment local-evaluation client and pre-fetch flag configs.
13
+ * Safe to call multiple times — subsequent calls are no-ops.
14
+ *
15
+ * @param userId Optional user ID for targeted flag evaluation.
16
+ * @param deviceId Optional device ID for targeted flag evaluation.
17
+ */
18
+ export declare function initFeatureFlags(userId?: string, deviceId?: string): Promise<void>;
19
+ /**
20
+ * Evaluate a single feature flag. Returns the string variant value,
21
+ * or `undefined` if the flag is not set / client not initialized.
22
+ */
23
+ export declare function getFlag(flagKey: string): string | undefined;
24
+ /**
25
+ * Check whether a flag is enabled (variant is `'on'` or `'true'`).
26
+ * Returns `false` when the flag is absent or the client is not initialized.
27
+ */
28
+ export declare function isFlagEnabled(flagKey: string): boolean;
29
+ /**
30
+ * Return a snapshot of all evaluated flags (key -> string value).
31
+ */
32
+ export declare function getAllFlags(): Record<string, string>;
33
+ /**
34
+ * Re-evaluate flags for a specific user (e.g. after login).
35
+ * Updates the cached flags in place.
36
+ */
37
+ export declare function refreshFlags(userId?: string, deviceId?: string): Promise<void>;