@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.
- package/dist/bin.js +191 -47
- package/dist/src/lib/agent-interface.js +10 -22
- package/dist/src/lib/agent-runner.js +4 -6
- package/dist/src/lib/commandments.js +1 -1
- package/dist/src/lib/constants.d.ts +1 -4
- package/dist/src/lib/constants.js +9 -8
- package/dist/src/lib/feature-flags.d.ts +37 -0
- package/dist/src/lib/feature-flags.js +119 -0
- package/dist/src/lib/wizard-session.d.ts +16 -0
- package/dist/src/lib/wizard-session.js +2 -0
- package/dist/src/run.js +1 -1
- package/dist/src/steps/add-mcp-server-to-clients/index.js +3 -3
- package/dist/src/steps/add-or-update-environment-variables.js +5 -17
- package/dist/src/steps/run-prettier.js +1 -1
- package/dist/src/steps/upload-environment-variables/index.js +2 -2
- package/dist/src/ui/tui/App.js +1 -1
- package/dist/src/ui/tui/components/ConsoleView.js +17 -6
- package/dist/src/ui/tui/components/TitleBar.d.ts +3 -1
- package/dist/src/ui/tui/components/TitleBar.js +17 -6
- package/dist/src/ui/tui/console-commands.d.ts +5 -2
- package/dist/src/ui/tui/console-commands.js +14 -5
- package/dist/src/ui/tui/screens/AuthScreen.d.ts +2 -1
- package/dist/src/ui/tui/screens/AuthScreen.js +166 -26
- package/dist/src/ui/tui/screens/ChecklistScreen.js +1 -1
- package/dist/src/ui/tui/screens/DataIngestionCheckScreen.js +13 -2
- package/dist/src/ui/tui/screens/IntroScreen.js +2 -2
- package/dist/src/ui/tui/screens/McpScreen.js +42 -27
- package/dist/src/ui/tui/screens/OutroScreen.js +1 -2
- package/dist/src/ui/tui/screens/SlackScreen.d.ts +0 -5
- package/dist/src/ui/tui/screens/SlackScreen.js +1 -11
- package/dist/src/ui/tui/store.d.ts +20 -0
- package/dist/src/ui/tui/store.js +68 -19
- package/dist/src/utils/analytics.d.ts +45 -3
- package/dist/src/utils/analytics.js +118 -47
- package/dist/src/utils/oauth.js +1 -1
- package/dist/src/utils/setup-utils.d.ts +11 -0
- package/dist/src/utils/setup-utils.js +81 -4
- package/dist/src/utils/shell-completions.d.ts +2 -2
- package/dist/src/utils/shell-completions.js +8 -1
- package/dist/src/utils/track-wizard-feedback.d.ts +5 -0
- package/dist/src/utils/track-wizard-feedback.js +25 -0
- package/package.json +13 -13
- package/dist/package.json +0 -144
package/dist/src/ui/tui/store.js
CHANGED
|
@@ -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,
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
327
|
+
analytics.wizardCapture('Checklist Completed', {
|
|
320
328
|
chart_complete: this.session.checklistChartComplete,
|
|
321
329
|
dashboard_complete: this.session.checklistDashboardComplete,
|
|
322
|
-
...
|
|
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('
|
|
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('
|
|
473
|
+
analytics.wizardCapture('MCP Complete', {
|
|
426
474
|
mcp_outcome: outcome,
|
|
427
475
|
mcp_installed_clients: installedClients,
|
|
428
|
-
...
|
|
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('
|
|
483
|
+
analytics.wizardCapture('Slack Complete', {
|
|
436
484
|
slack_outcome: outcome,
|
|
437
|
-
...
|
|
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(
|
|
516
|
-
|
|
517
|
-
|
|
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
|
|
14
|
-
private
|
|
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
|
-
|
|
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
|
|
8
|
-
const
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
97
|
+
options.user_id = this.distinctId;
|
|
66
98
|
}
|
|
67
|
-
this.
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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,
|
|
108
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
}
|
package/dist/src/utils/oauth.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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('
|
|
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:
|
|
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
|