@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
@@ -13,7 +13,8 @@ import { atom, map } from 'nanostores';
13
13
  import { TaskStatus } from '../wizard-ui.js';
14
14
  import { AdditionalFeature, McpOutcome, SlackOutcome, RunPhase, buildSession, } from '../../lib/wizard-session.js';
15
15
  import { WizardRouter, Screen, Overlay, Flow, } from './router.js';
16
- import { analytics, sessionProperties } from '../../utils/analytics.js';
16
+ import { analytics, sessionPropertiesCompact } from '../../utils/analytics.js';
17
+ import { FLAG_LLM_ANALYTICS } from '../../lib/feature-flags.js';
17
18
  export { TaskStatus, Screen, Overlay, Flow, RunPhase, McpOutcome, SlackOutcome, };
18
19
  export class WizardStore {
19
20
  // ── Internal nanostore atoms ─────────────────────────────────────
@@ -51,6 +52,8 @@ export class WizardStore {
51
52
  _backupAndFixSettings = null;
52
53
  /** Pending confirmation or choice prompt from the agent. */
53
54
  $pendingPrompt = atom(null);
55
+ /** Resolves when the user picks continue vs run wizard after Amplitude pre-detection. */
56
+ _preDetectedChoiceResolver = null;
54
57
  constructor(flow = Flow.Wizard) {
55
58
  this.router = new WizardRouter(flow);
56
59
  }
@@ -103,7 +106,7 @@ export class WizardStore {
103
106
  */
104
107
  completeSetup() {
105
108
  this.$session.setKey('setupConfirmed', true);
106
- analytics.wizardCapture('setup confirmed', sessionProperties(this.session));
109
+ analytics.wizardCapture('Setup Confirmed', sessionPropertiesCompact(this.session));
107
110
  this._resolveSetup();
108
111
  this.emitChange();
109
112
  }
@@ -116,15 +119,20 @@ export class WizardStore {
116
119
  if (credentials?.projectId) {
117
120
  analytics.setDistinctId(String(credentials.projectId));
118
121
  }
119
- analytics.wizardCapture('auth complete', {
120
- project_id: credentials?.projectId,
121
- });
122
- analytics.wizardCapture('credentials updated', {
122
+ analytics.wizardCapture('Auth Complete', {
123
123
  project_id: credentials?.projectId,
124
124
  region: this.session.region,
125
125
  });
126
126
  this.emitChange();
127
127
  }
128
+ setApiKeyNotice(notice) {
129
+ this.$session.setKey('apiKeyNotice', notice);
130
+ this.emitChange();
131
+ }
132
+ setSelectedProjectName(name) {
133
+ this.$session.setKey('selectedProjectName', name);
134
+ this.emitChange();
135
+ }
128
136
  setFrameworkConfig(integration, config) {
129
137
  this.$session.setKey('integration', integration);
130
138
  this.$session.setKey('frameworkConfig', config);
@@ -194,7 +202,7 @@ export class WizardStore {
194
202
  const prompt = this.$pendingPrompt.get();
195
203
  if (!prompt || prompt.kind === 'event-plan')
196
204
  return;
197
- analytics.wizardCapture('prompt response', {
205
+ analytics.wizardCapture('Prompt Response', {
198
206
  prompt_kind: prompt.kind,
199
207
  response: String(answer),
200
208
  });
@@ -219,7 +227,7 @@ export class WizardStore {
219
227
  const prompt = this.$pendingPrompt.get();
220
228
  if (!prompt || prompt.kind !== 'event-plan')
221
229
  return;
222
- analytics.wizardCapture('prompt response', {
230
+ analytics.wizardCapture('Prompt Response', {
223
231
  prompt_kind: 'event-plan',
224
232
  response: typeof decision === 'object' ? 'feedback' : String(decision),
225
233
  });
@@ -303,7 +311,7 @@ export class WizardStore {
303
311
  }
304
312
  setDataIngestionConfirmed() {
305
313
  this.$session.setKey('dataIngestionConfirmed', true);
306
- analytics.wizardCapture('data ingestion confirmed', sessionProperties(this.session));
314
+ analytics.wizardCapture('Data Ingestion Confirmed', sessionPropertiesCompact(this.session));
307
315
  this.emitChange();
308
316
  }
309
317
  setChecklistChartComplete() {
@@ -316,10 +324,10 @@ export class WizardStore {
316
324
  }
317
325
  setChecklistComplete() {
318
326
  this.$session.setKey('checklistComplete', true);
319
- analytics.wizardCapture('checklist completed', {
327
+ analytics.wizardCapture('Checklist Completed', {
320
328
  chart_complete: this.session.checklistChartComplete,
321
329
  dashboard_complete: this.session.checklistDashboardComplete,
322
- ...sessionProperties(this.session),
330
+ ...sessionPropertiesCompact(this.session),
323
331
  });
324
332
  this.emitChange();
325
333
  }
@@ -402,8 +410,16 @@ export class WizardStore {
402
410
  /**
403
411
  * Enable an additional feature: enqueue it for the stop hook
404
412
  * and set any feature-specific session flags.
413
+ * Respects Amplitude Experiment feature flags — if the corresponding
414
+ * flag is off the feature is silently skipped.
405
415
  */
406
416
  enableFeature(feature) {
417
+ // Gate LLM analytics behind the wizard-llm-analytics feature flag
418
+ if (feature === AdditionalFeature.LLM) {
419
+ if (!analytics.isFeatureFlagEnabled(FLAG_LLM_ANALYTICS)) {
420
+ return;
421
+ }
422
+ }
407
423
  if (!this.session.additionalFeatureQueue.includes(feature)) {
408
424
  this.session.additionalFeatureQueue.push(feature);
409
425
  }
@@ -411,30 +427,62 @@ export class WizardStore {
411
427
  if (feature === AdditionalFeature.LLM) {
412
428
  this.session.llmOptIn = true;
413
429
  }
414
- analytics.wizardCapture('feature enabled', { feature });
430
+ analytics.wizardCapture('Feature Enabled', { feature });
415
431
  this.emitChange();
416
432
  }
417
433
  setAmplitudePreDetected() {
418
434
  this.$session.setKey('amplitudePreDetected', true);
435
+ this.$session.setKey('amplitudePreDetectedChoicePending', true);
436
+ this.emitChange();
437
+ }
438
+ /**
439
+ * Blocks bin.ts until McpScreen resolves via resolvePreDetectedChoice().
440
+ */
441
+ waitForPreDetectedChoice() {
442
+ return new Promise((resolve) => {
443
+ this._preDetectedChoiceResolver = resolve;
444
+ });
445
+ }
446
+ /**
447
+ * Called from McpScreen when the user chooses to skip the agent (continue to
448
+ * MCP) or run the full setup wizard anyway.
449
+ */
450
+ resolvePreDetectedChoice(runWizardAnyway) {
451
+ const resolveFn = this._preDetectedChoiceResolver;
452
+ this._preDetectedChoiceResolver = null;
453
+ if (!runWizardAnyway) {
454
+ this.$session.setKey('amplitudePreDetectedChoicePending', false);
455
+ }
456
+ this.emitChange();
457
+ resolveFn?.(runWizardAnyway);
458
+ }
459
+ /**
460
+ * Undo the pre-detection fast-path so runWizard can run; used when the user
461
+ * opts into the setup agent after Amplitude was already found in the project.
462
+ */
463
+ resetForAgentAfterPreDetected() {
464
+ this.$session.setKey('amplitudePreDetected', false);
465
+ this.$session.setKey('amplitudePreDetectedChoicePending', false);
466
+ this.$session.setKey('runPhase', RunPhase.Idle);
419
467
  this.emitChange();
420
468
  }
421
469
  setMcpComplete(outcome = McpOutcome.Skipped, installedClients = []) {
422
470
  this.$session.setKey('mcpComplete', true);
423
471
  this.$session.setKey('mcpOutcome', outcome);
424
472
  this.$session.setKey('mcpInstalledClients', installedClients);
425
- analytics.wizardCapture('mcp complete', {
473
+ analytics.wizardCapture('MCP Complete', {
426
474
  mcp_outcome: outcome,
427
475
  mcp_installed_clients: installedClients,
428
- ...sessionProperties(this.session),
476
+ ...sessionPropertiesCompact(this.session),
429
477
  });
430
478
  this.emitChange();
431
479
  }
432
480
  setSlackComplete(outcome = SlackOutcome.Skipped) {
433
481
  this.$session.setKey('slackComplete', true);
434
482
  this.$session.setKey('slackOutcome', outcome);
435
- analytics.wizardCapture('slack complete', {
483
+ analytics.wizardCapture('Slack Complete', {
436
484
  slack_outcome: outcome,
437
- ...sessionProperties(this.session),
485
+ ...sessionPropertiesCompact(this.session),
438
486
  });
439
487
  this.emitChange();
440
488
  }
@@ -512,9 +560,10 @@ export class WizardStore {
512
560
  for (const fn of hooks)
513
561
  fn();
514
562
  }
515
- analytics.wizardCapture(`screen ${next}`, {
516
- from_screen: prev,
517
- ...sessionProperties(this.session),
563
+ analytics.wizardCapture('Wizard Screen Entered', {
564
+ screen_name: next,
565
+ previous_screen: prev,
566
+ ...sessionPropertiesCompact(this.session),
518
567
  });
519
568
  }
520
569
  }
@@ -1,17 +1,29 @@
1
1
  import type { WizardSession } from '../lib/wizard-session';
2
+ /**
3
+ * Telemetry project API key. Empty or whitespace-only env value means “no key”
4
+ * (use default only when the variable is unset).
5
+ */
6
+ export declare function resolveTelemetryApiKey(): string;
7
+ /** HTTP API URL for `@amplitude/analytics-node` (same shape as manual HTTP ingest). */
8
+ export declare function getAmplitudeNodeServerUrl(): string;
2
9
  /**
3
10
  * Extract a standard property bag from the current session.
4
11
  * Used by store-level analytics and available for ad-hoc captures.
5
12
  */
6
13
  export declare function sessionProperties(session: WizardSession): Record<string, unknown>;
14
+ /**
15
+ * Smaller session bag for high-volume wizard events (taxonomy: keep event
16
+ * properties chart-useful and within a small count).
17
+ */
18
+ export declare function sessionPropertiesCompact(session: WizardSession): Record<string, unknown>;
7
19
  export declare class Analytics {
8
20
  private tags;
9
21
  private distinctId?;
10
22
  private anonymousId;
11
23
  private appName;
12
24
  private activeFlags;
13
- private pendingEvents;
14
- private flushTimer;
25
+ private readonly client;
26
+ private initPromise;
15
27
  constructor();
16
28
  setDistinctId(distinctId: string): void;
17
29
  setTag(key: string, value: string | boolean | number | null | undefined): void;
@@ -20,10 +32,30 @@ export declare class Analytics {
20
32
  /**
21
33
  * Capture a wizard-specific event. Automatically prepends "wizard: " to the event name.
22
34
  * All new wizard analytics should use this method instead of capture() directly.
35
+ * Use lowercase with spaces for eventName (e.g. "agent started", "api key submitted")
36
+ * per Amplitude quickstart taxonomy guidelines.
23
37
  */
24
38
  wizardCapture(eventName: string, properties?: Record<string, unknown>): void;
25
- private flush;
39
+ /**
40
+ * Apply feature-flag–based opt-out to the Amplitude SDK.
41
+ * Defaults to ON — only opts out when `wizard-agent-analytics` is explicitly 'off'/'false'.
42
+ */
43
+ applyOptOut(): void;
44
+ private ensureInitStarted;
45
+ /**
46
+ * Initialize the Amplitude Experiment feature-flag client.
47
+ * Call once early in startup (e.g. after obtaining a user/device ID).
48
+ */
49
+ initFlags(): Promise<void>;
50
+ /**
51
+ * Re-evaluate flags after the user identity changes (e.g. post-login).
52
+ */
53
+ refreshFlags(): Promise<void>;
26
54
  getFeatureFlag(flagKey: string): Promise<string | boolean | undefined>;
55
+ /**
56
+ * Check if a flag is enabled (variant is 'on' or 'true').
57
+ */
58
+ isFeatureFlagEnabled(flagKey: string): boolean;
27
59
  /**
28
60
  * Evaluate all feature flags for the current user at the start of a run.
29
61
  * Result is cached; subsequent calls in the same run return the same map.
@@ -32,4 +64,14 @@ export declare class Analytics {
32
64
  getAllFlagsForWizard(): Promise<Record<string, string>>;
33
65
  shutdown(status: 'success' | 'error' | 'cancelled'): Promise<void>;
34
66
  }
67
+ /**
68
+ * Full Amplitude `event_type` for CLI/TUI product feedback.
69
+ * Same string as `wizardCapture('Feedback Submitted', …)`.
70
+ */
71
+ export declare const WIZARD_FEEDBACK_EVENT_TYPE = "wizard: feedback submitted";
35
72
  export declare const analytics: Analytics;
73
+ /**
74
+ * Unified wizard error telemetry (aligns with starter taxonomy “Error Encountered”).
75
+ * Emits `wizard: error encountered` with category / message / context.
76
+ */
77
+ export declare function captureWizardError(errorCategory: string, errorMessage: string, errorContext: string, extra?: Record<string, unknown>): void;
@@ -1,11 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.analytics = exports.Analytics = void 0;
3
+ exports.analytics = exports.WIZARD_FEEDBACK_EVENT_TYPE = exports.Analytics = void 0;
4
+ exports.resolveTelemetryApiKey = resolveTelemetryApiKey;
5
+ exports.getAmplitudeNodeServerUrl = getAmplitudeNodeServerUrl;
4
6
  exports.sessionProperties = sessionProperties;
7
+ exports.sessionPropertiesCompact = sessionPropertiesCompact;
8
+ exports.captureWizardError = captureWizardError;
9
+ const analytics_node_1 = require("@amplitude/analytics-node");
5
10
  const uuid_1 = require("uuid");
6
11
  const debug_1 = require("./debug");
7
- const AMPLITUDE_API_KEY = process.env.AMPLITUDE_API_KEY ?? 'e5a2c9bdffe949f7da77e6b481e118fa';
8
- const AMPLITUDE_SERVER_URL = process.env.AMPLITUDE_SERVER_URL ?? 'https://api2.amplitude.com';
12
+ const feature_flags_1 = require("../lib/feature-flags");
13
+ const DEFAULT_TELEMETRY_API_KEY = 'e5a2c9bdffe949f7da77e6b481e118fa';
14
+ /**
15
+ * Telemetry project API key. Empty or whitespace-only env value means “no key”
16
+ * (use default only when the variable is unset).
17
+ */
18
+ function resolveTelemetryApiKey() {
19
+ const fromEnv = process.env.AMPLITUDE_API_KEY;
20
+ const raw = fromEnv !== undefined ? fromEnv : DEFAULT_TELEMETRY_API_KEY;
21
+ return raw.trim();
22
+ }
23
+ /** HTTP API URL for `@amplitude/analytics-node` (same shape as manual HTTP ingest). */
24
+ function getAmplitudeNodeServerUrl() {
25
+ const base = process.env.AMPLITUDE_SERVER_URL ?? 'https://api2.amplitude.com';
26
+ return `${base.replace(/\/$/, '')}/2/httpapi`;
27
+ }
9
28
  /**
10
29
  * Extract a standard property bag from the current session.
11
30
  * Used by store-level analytics and available for ad-hoc captures.
@@ -21,18 +40,31 @@ function sessionProperties(session) {
21
40
  run_phase: session.runPhase,
22
41
  };
23
42
  }
43
+ /**
44
+ * Smaller session bag for high-volume wizard events (taxonomy: keep event
45
+ * properties chart-useful and within a small count).
46
+ */
47
+ function sessionPropertiesCompact(session) {
48
+ return {
49
+ integration: session.integration,
50
+ detected_framework: session.detectedFrameworkLabel,
51
+ run_phase: session.runPhase,
52
+ project_id: session.credentials?.projectId,
53
+ };
54
+ }
24
55
  class Analytics {
25
56
  tags = {};
26
57
  distinctId;
27
58
  anonymousId;
28
59
  appName = 'wizard';
29
60
  activeFlags = null;
30
- pendingEvents = [];
31
- flushTimer = null;
61
+ client;
62
+ initPromise = null;
32
63
  constructor() {
33
64
  this.tags = { $app_name: this.appName };
34
65
  this.anonymousId = (0, uuid_1.v4)();
35
66
  this.distinctId = undefined;
67
+ this.client = (0, analytics_node_1.createInstance)();
36
68
  }
37
69
  setDistinctId(distinctId) {
38
70
  this.distinctId = distinctId;
@@ -48,64 +80,81 @@ class Analytics {
48
80
  });
49
81
  }
50
82
  capture(eventName, properties) {
51
- if (!AMPLITUDE_API_KEY) {
83
+ const apiKey = resolveTelemetryApiKey();
84
+ if (!apiKey) {
52
85
  (0, debug_1.debug)('capture (no API key):', eventName, properties);
53
86
  return;
54
87
  }
55
- const event = {
56
- event_type: eventName,
88
+ this.ensureInitStarted();
89
+ const eventProps = {
90
+ ...this.tags,
91
+ ...properties,
92
+ };
93
+ const options = {
57
94
  device_id: this.anonymousId,
58
- time: Date.now(),
59
- event_properties: {
60
- ...this.tags,
61
- ...properties,
62
- },
63
95
  };
64
96
  if (this.distinctId) {
65
- event.user_id = this.distinctId;
97
+ options.user_id = this.distinctId;
66
98
  }
67
- this.pendingEvents.push(event);
99
+ this.client.track(eventName, eventProps, options);
68
100
  (0, debug_1.debug)('capture:', eventName, properties);
69
- // Debounce flush to batch events
70
- if (this.flushTimer) {
71
- clearTimeout(this.flushTimer);
72
- }
73
- this.flushTimer = setTimeout(() => {
74
- void this.flush();
75
- }, 500);
76
101
  }
77
102
  /**
78
103
  * Capture a wizard-specific event. Automatically prepends "wizard: " to the event name.
79
104
  * All new wizard analytics should use this method instead of capture() directly.
105
+ * Use lowercase with spaces for eventName (e.g. "agent started", "api key submitted")
106
+ * per Amplitude quickstart taxonomy guidelines.
80
107
  */
81
108
  wizardCapture(eventName, properties) {
82
109
  this.capture(`wizard: ${eventName}`, properties);
83
110
  }
84
- async flush() {
85
- if (this.pendingEvents.length === 0)
86
- return;
87
- if (!AMPLITUDE_API_KEY)
111
+ /**
112
+ * Apply feature-flag–based opt-out to the Amplitude SDK.
113
+ * Defaults to ON — only opts out when `wizard-agent-analytics` is explicitly 'off'/'false'.
114
+ */
115
+ applyOptOut() {
116
+ const flagValue = (0, feature_flags_1.getFlag)(feature_flags_1.FLAG_AGENT_ANALYTICS);
117
+ const optOut = flagValue === 'off' || flagValue === 'false';
118
+ this.client.setOptOut(optOut);
119
+ if (optOut) {
120
+ (0, debug_1.debug)('analytics: opted out via wizard-agent-analytics flag');
121
+ }
122
+ }
123
+ ensureInitStarted() {
124
+ if (this.initPromise !== null) {
88
125
  return;
89
- const events = [...this.pendingEvents];
90
- this.pendingEvents = [];
91
- try {
92
- const response = await fetch(`${AMPLITUDE_SERVER_URL}/2/httpapi`, {
93
- method: 'POST',
94
- headers: { 'Content-Type': 'application/json' },
95
- body: JSON.stringify({ api_key: AMPLITUDE_API_KEY, events }),
96
- });
97
- if (!response.ok) {
98
- (0, debug_1.debug)('Amplitude upload failed:', response.status, await response.text());
99
- }
100
126
  }
101
- catch (err) {
102
- (0, debug_1.debug)('Amplitude upload error:', err);
127
+ const apiKey = resolveTelemetryApiKey();
128
+ if (!apiKey) {
129
+ return;
103
130
  }
131
+ this.initPromise = this.client.init(apiKey, {
132
+ serverUrl: getAmplitudeNodeServerUrl(),
133
+ }).promise;
134
+ }
135
+ /**
136
+ * Initialize the Amplitude Experiment feature-flag client.
137
+ * Call once early in startup (e.g. after obtaining a user/device ID).
138
+ */
139
+ async initFlags() {
140
+ await (0, feature_flags_1.initFeatureFlags)(this.distinctId, this.anonymousId);
141
+ }
142
+ /**
143
+ * Re-evaluate flags after the user identity changes (e.g. post-login).
144
+ */
145
+ async refreshFlags() {
146
+ await (0, feature_flags_1.refreshFlags)(this.distinctId, this.anonymousId);
147
+ this.activeFlags = (0, feature_flags_1.getAllFlags)();
104
148
  }
105
149
  // eslint-disable-next-line @typescript-eslint/require-await
106
150
  async getFeatureFlag(flagKey) {
107
- (0, debug_1.debug)('getFeatureFlag (noop):', flagKey);
108
- return undefined;
151
+ return (0, feature_flags_1.getFlag)(flagKey);
152
+ }
153
+ /**
154
+ * Check if a flag is enabled (variant is 'on' or 'true').
155
+ */
156
+ isFeatureFlagEnabled(flagKey) {
157
+ return (0, feature_flags_1.isFlagEnabled)(flagKey);
109
158
  }
110
159
  /**
111
160
  * Evaluate all feature flags for the current user at the start of a run.
@@ -117,17 +166,39 @@ class Analytics {
117
166
  if (this.activeFlags !== null) {
118
167
  return this.activeFlags;
119
168
  }
120
- this.activeFlags = {};
169
+ this.activeFlags = (0, feature_flags_1.getAllFlags)();
121
170
  return this.activeFlags;
122
171
  }
123
172
  async shutdown(status) {
124
- if (this.flushTimer) {
125
- clearTimeout(this.flushTimer);
126
- this.flushTimer = null;
173
+ this.wizardCapture('Session Ended', { status });
174
+ if (this.initPromise === null) {
175
+ return;
176
+ }
177
+ try {
178
+ await this.initPromise;
179
+ await this.client.flush().promise;
180
+ }
181
+ catch (err) {
182
+ (0, debug_1.debug)('analytics shutdown flush error:', err);
127
183
  }
128
- this.capture('wizard: session ended', { status });
129
- await this.flush();
130
184
  }
131
185
  }
132
186
  exports.Analytics = Analytics;
187
+ /**
188
+ * Full Amplitude `event_type` for CLI/TUI product feedback.
189
+ * Same string as `wizardCapture('Feedback Submitted', …)`.
190
+ */
191
+ exports.WIZARD_FEEDBACK_EVENT_TYPE = 'wizard: feedback submitted';
133
192
  exports.analytics = new Analytics();
193
+ /**
194
+ * Unified wizard error telemetry (aligns with starter taxonomy “Error Encountered”).
195
+ * Emits `wizard: error encountered` with category / message / context.
196
+ */
197
+ function captureWizardError(errorCategory, errorMessage, errorContext, extra) {
198
+ exports.analytics.wizardCapture('Error Encountered', {
199
+ error_category: errorCategory,
200
+ error_message: errorMessage,
201
+ error_context: errorContext,
202
+ ...extra,
203
+ });
204
+ }
@@ -315,7 +315,7 @@ async function performAmplitudeAuth(options) {
315
315
  (0, index_js_1.getUI)().log.info(`${chalk_1.default.yellow('Authorization was cancelled.')}\n\nRe-run the wizard to try again.`);
316
316
  }
317
317
  else {
318
- (0, index_js_1.getUI)().log.error(`${chalk_1.default.red('Authorization failed:')}\n\n${error.message}\n\n${chalk_1.default.dim(`File an issue:\n${constants_js_1.ISSUES_URL}`)}`);
318
+ (0, index_js_1.getUI)().log.error(`${chalk_1.default.red('Authorization failed:')}\n\n${error.message}\n\n${chalk_1.default.dim(`File an issue:\n${constants_js_1.OUTBOUND_URLS.githubIssues}`)}`);
319
319
  }
320
320
  analytics_js_1.analytics.captureException(error, { step: 'oauth_flow' });
321
321
  await (0, setup_utils_js_1.abort)();
@@ -60,6 +60,17 @@ export declare function getPackageManager(options: Pick<WizardOptions, 'installD
60
60
  ci?: boolean;
61
61
  }): Promise<PackageManager>;
62
62
  export declare function isUsingTypeScript({ installDir, }: Pick<WizardOptions, 'installDir'>): boolean;
63
+ /**
64
+ * Best-effort credentials for `--ci` when `--api-key` / AMPLITUDE_WIZARD_API_KEY
65
+ * is not set: project-local key (.env.local / keychain), then OAuth id token from
66
+ * ~/.ampli.json plus org/workspace from ampli.json (same resolution as interactive bootstrap).
67
+ */
68
+ export declare function tryResolveCredentialsForCi(installDir: string): Promise<{
69
+ host: string;
70
+ projectApiKey: string;
71
+ accessToken: string;
72
+ cloudRegion: CloudRegion;
73
+ } | null>;
63
74
  /**
64
75
  * Get project data for the wizard via Amplitude OAuth or CI API key.
65
76
  *
@@ -46,6 +46,7 @@ exports.tryGetPackageJson = tryGetPackageJson;
46
46
  exports.updatePackageDotJson = updatePackageDotJson;
47
47
  exports.getPackageManager = getPackageManager;
48
48
  exports.isUsingTypeScript = isUsingTypeScript;
49
+ exports.tryResolveCredentialsForCi = tryResolveCredentialsForCi;
49
50
  exports.getOrAskForProjectData = getOrAskForProjectData;
50
51
  exports.createNewConfigFile = createNewConfigFile;
51
52
  const childProcess = __importStar(require("node:child_process"));
@@ -63,7 +64,6 @@ const ui_1 = require("../ui");
63
64
  const oauth_1 = require("./oauth");
64
65
  const api_1 = require("../lib/api");
65
66
  const ampli_settings_1 = require("./ampli-settings");
66
- const constants_2 = require("../lib/constants");
67
67
  const urls_1 = require("./urls");
68
68
  const semver_1 = require("./semver");
69
69
  const wizard_abort_1 = require("./wizard-abort");
@@ -151,11 +151,11 @@ async function installPackage({ packageName, alreadyInstalled, packageNameDispla
151
151
  }
152
152
  catch (e) {
153
153
  sdkInstallSpinner.stop('Installation failed.');
154
- (0, ui_1.getUI)().log.error(`${chalk_1.default.red('Encountered the following error during installation:')}\n\n${e}\n\n${chalk_1.default.dim(`The wizard has created a \`amplitude-wizard-installation-error-*.log\` file. If you think this issue is caused by the Amplitude wizard, create an issue on GitHub and include the log file's content:\n${constants_1.ISSUES_URL}`)}`);
154
+ (0, ui_1.getUI)().log.error(`${chalk_1.default.red('Encountered the following error during installation:')}\n\n${e}\n\n${chalk_1.default.dim(`The wizard has created a \`amplitude-wizard-installation-error-*.log\` file. If you think this issue is caused by the Amplitude wizard, create an issue on GitHub and include the log file's content:\n${constants_1.OUTBOUND_URLS.githubIssues}`)}`);
155
155
  await abort();
156
156
  }
157
157
  sdkInstallSpinner.stop(`${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk_1.default.bold.cyan(packageNameDisplayLabel ?? packageName)} with ${chalk_1.default.bold(pkgManager.label)}.`);
158
- analytics_1.analytics.wizardCapture('package installed', {
158
+ analytics_1.analytics.wizardCapture('Package Installed', {
159
159
  package_name: packageName,
160
160
  package_manager: pkgManager.name,
161
161
  integration,
@@ -237,6 +237,57 @@ function isUsingTypeScript({ installDir, }) {
237
237
  return false;
238
238
  }
239
239
  }
240
+ /**
241
+ * Best-effort credentials for `--ci` when `--api-key` / AMPLITUDE_WIZARD_API_KEY
242
+ * is not set: project-local key (.env.local / keychain), then OAuth id token from
243
+ * ~/.ampli.json plus org/workspace from ampli.json (same resolution as interactive bootstrap).
244
+ */
245
+ async function tryResolveCredentialsForCi(installDir) {
246
+ const { readApiKeyWithSource } = await import('./api-key-store.js');
247
+ const local = readApiKeyWithSource(installDir);
248
+ if (local) {
249
+ return {
250
+ host: constants_1.DEFAULT_HOST_URL,
251
+ projectApiKey: local.key,
252
+ accessToken: local.key,
253
+ cloudRegion: 'us',
254
+ };
255
+ }
256
+ const { getStoredUser, getStoredToken } = await import('./ampli-settings.js');
257
+ const { readAmpliConfig } = await import('../lib/ampli-config.js');
258
+ const { getAPIKey } = await import('./get-api-key.js');
259
+ const { getHostFromRegion } = await import('./urls.js');
260
+ const storedUser = getStoredUser();
261
+ const realUser = storedUser && storedUser.id !== 'pending' ? storedUser : null;
262
+ const projectConfig = readAmpliConfig(installDir);
263
+ const projectZone = projectConfig.ok ? projectConfig.config.Zone : undefined;
264
+ const zone = realUser?.zone ?? projectZone ?? constants_1.DEFAULT_AMPLITUDE_ZONE;
265
+ const storedToken = realUser
266
+ ? getStoredToken(realUser.id, realUser.zone)
267
+ : getStoredToken(undefined, zone);
268
+ if (!storedToken?.idToken) {
269
+ return null;
270
+ }
271
+ const workspaceId = projectConfig.ok
272
+ ? projectConfig.config.WorkspaceId
273
+ : undefined;
274
+ const projectApiKey = await getAPIKey({
275
+ installDir,
276
+ idToken: storedToken.idToken,
277
+ zone,
278
+ workspaceId,
279
+ });
280
+ if (!projectApiKey) {
281
+ return null;
282
+ }
283
+ const cloudRegion = zone === 'eu' ? 'eu' : 'us';
284
+ return {
285
+ host: getHostFromRegion(cloudRegion),
286
+ projectApiKey,
287
+ accessToken: storedToken.idToken,
288
+ cloudRegion,
289
+ };
290
+ }
240
291
  /**
241
292
  * Get project data for the wizard via Amplitude OAuth or CI API key.
242
293
  *
@@ -258,6 +309,32 @@ async function getOrAskForProjectData(_options) {
258
309
  cloudRegion: 'us',
259
310
  };
260
311
  }
312
+ if (_options.ci) {
313
+ const ciInstallDir = _options.installDir;
314
+ if (!ciInstallDir) {
315
+ (0, ui_1.getUI)().log.error(chalk_1.default.red('CI mode requires --install-dir (or AMPLITUDE_WIZARD_INSTALL_DIR).'));
316
+ await (0, wizard_abort_1.wizardAbort)({
317
+ message: 'CI mode requires an install directory.',
318
+ });
319
+ }
320
+ else {
321
+ const resolved = await tryResolveCredentialsForCi(ciInstallDir);
322
+ if (resolved) {
323
+ (0, ui_1.getUI)().log.info(chalk_1.default.dim('Resolved Amplitude API key non-interactively (CI mode).'));
324
+ return {
325
+ host: resolved.host,
326
+ projectApiKey: resolved.projectApiKey,
327
+ accessToken: resolved.accessToken,
328
+ projectId: _options.projectId ?? 0,
329
+ cloudRegion: resolved.cloudRegion,
330
+ };
331
+ }
332
+ (0, ui_1.getUI)().log.error(chalk_1.default.red('CI mode could not resolve a project API key. Pass --api-key or AMPLITUDE_WIZARD_API_KEY, store a key in the project (.env.local / keychain), or ensure ~/.ampli.json has a valid OAuth session (and ampli.json includes WorkspaceId if needed).'));
333
+ await (0, wizard_abort_1.wizardAbort)({
334
+ message: 'CI mode requires a project API key or resolvable Amplitude credentials.',
335
+ });
336
+ }
337
+ }
261
338
  // Force fresh OAuth for projects that haven't been set up yet — no local
262
339
  // ampli.json means we don't know which Amplitude org this project belongs to.
263
340
  let forceFresh = false;
@@ -280,7 +357,7 @@ async function getOrAskForProjectData(_options) {
280
357
  async function askForWizardLogin(opts = {}) {
281
358
  // ── 1. Authenticate via Amplitude OAuth (reuses ampli CLI session) ──
282
359
  const auth = await (0, oauth_1.performAmplitudeAuth)({
283
- zone: constants_2.DEFAULT_AMPLITUDE_ZONE,
360
+ zone: constants_1.DEFAULT_AMPLITUDE_ZONE,
284
361
  forceFresh: opts.forceFresh,
285
362
  });
286
363
  // ── 2. Detect actual cloud region (EU users auth via US endpoint but