@amplitude/wizard 1.0.0-beta.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +42 -8
- package/dist/package.json +2 -1
- 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 +0 -3
- package/dist/src/lib/constants.js +1 -4
- package/dist/src/lib/wizard-session.d.ts +6 -0
- package/dist/src/lib/wizard-session.js +1 -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/components/ConsoleView.js +16 -2
- package/dist/src/ui/tui/console-commands.d.ts +5 -0
- package/dist/src/ui/tui/console-commands.js +15 -0
- package/dist/src/ui/tui/screens/AuthScreen.js +72 -14
- package/dist/src/ui/tui/screens/ChecklistScreen.js +1 -1
- 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 -1
- package/dist/src/ui/tui/store.d.ts +17 -0
- package/dist/src/ui/tui/store.js +55 -19
- package/dist/src/utils/analytics.d.ts +27 -3
- package/dist/src/utils/analytics.js +83 -44
- 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 +2 -1
|
@@ -46,7 +46,7 @@ export const IntroScreen = ({ store }) => {
|
|
|
46
46
|
{ label: 'Cancel', value: 'cancel' },
|
|
47
47
|
], onSelect: (value) => {
|
|
48
48
|
const choice = Array.isArray(value) ? value[0] : value;
|
|
49
|
-
analytics.wizardCapture('
|
|
49
|
+
analytics.wizardCapture('Intro Action', {
|
|
50
50
|
action: choice,
|
|
51
51
|
integration: session.integration,
|
|
52
52
|
detected_framework: session.detectedFrameworkLabel,
|
|
@@ -75,7 +75,7 @@ const FrameworkPicker = ({ store, onComplete, }) => {
|
|
|
75
75
|
}));
|
|
76
76
|
return (_jsx(PickerMenu, { centered: true, columns: 2, message: "Select your framework", options: options, onSelect: (value) => {
|
|
77
77
|
const integration = Array.isArray(value) ? value[0] : value;
|
|
78
|
-
analytics.wizardCapture('
|
|
78
|
+
analytics.wizardCapture('Framework Manually Selected', { integration });
|
|
79
79
|
void import('../../../lib/registry.js').then(({ FRAMEWORK_REGISTRY }) => {
|
|
80
80
|
const config = FRAMEWORK_REGISTRY[integration];
|
|
81
81
|
store.setFrameworkConfig(integration, config);
|
|
@@ -17,7 +17,7 @@ import { useSyncExternalStore } from 'react';
|
|
|
17
17
|
import { McpOutcome, RunPhase } from '../store.js';
|
|
18
18
|
import { ConfirmationInput, PickerMenu } from '../primitives/index.js';
|
|
19
19
|
import { Colors } from '../styles.js';
|
|
20
|
-
import { analytics } from '../../../utils/analytics.js';
|
|
20
|
+
import { analytics, captureWizardError } from '../../../utils/analytics.js';
|
|
21
21
|
var Phase;
|
|
22
22
|
(function (Phase) {
|
|
23
23
|
Phase["Detecting"] = "detecting";
|
|
@@ -41,22 +41,25 @@ const markDone = (store, outcome, clients = [], standalone = false, onComplete)
|
|
|
41
41
|
export const McpScreen = ({ store, installer, mode = 'install', standalone = false, onComplete, }) => {
|
|
42
42
|
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
43
43
|
const isRemove = mode === 'remove';
|
|
44
|
-
const { runPhase, amplitudePreDetected } = store.session;
|
|
44
|
+
const { runPhase, amplitudePreDetected, amplitudePreDetectedChoicePending } = store.session;
|
|
45
45
|
const dataSetupComplete = runPhase === RunPhase.Completed;
|
|
46
46
|
const [phase, setPhase] = useState(Phase.Detecting);
|
|
47
47
|
const [clients, setClients] = useState([]);
|
|
48
48
|
const [resultClients, setResultClients] = useState([]);
|
|
49
49
|
useEffect(() => {
|
|
50
|
+
if (amplitudePreDetectedChoicePending) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
50
53
|
void (async () => {
|
|
51
54
|
try {
|
|
52
55
|
const detected = await installer.detectClients();
|
|
53
56
|
if (detected.length === 0) {
|
|
54
|
-
analytics.wizardCapture('
|
|
57
|
+
analytics.wizardCapture('MCP No Clients Detected', { mode });
|
|
55
58
|
setPhase(Phase.None);
|
|
56
59
|
setTimeout(() => markDone(store, McpOutcome.NoClients, [], standalone, onComplete), 1500);
|
|
57
60
|
}
|
|
58
61
|
else {
|
|
59
|
-
analytics.wizardCapture('
|
|
62
|
+
analytics.wizardCapture('MCP Clients Detected', {
|
|
60
63
|
mode,
|
|
61
64
|
clients: detected.map((c) => c.name),
|
|
62
65
|
count: detected.length,
|
|
@@ -66,31 +69,31 @@ export const McpScreen = ({ store, installer, mode = 'install', standalone = fal
|
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
catch {
|
|
69
|
-
|
|
72
|
+
captureWizardError('MCP Client Detection', 'Editor client detection failed', 'McpScreen', { mode });
|
|
70
73
|
setPhase(Phase.None);
|
|
71
74
|
setTimeout(() => markDone(store, McpOutcome.Failed, [], standalone, onComplete), 1500);
|
|
72
75
|
}
|
|
73
76
|
})();
|
|
74
|
-
}, [installer]);
|
|
77
|
+
}, [installer, amplitudePreDetectedChoicePending]);
|
|
75
78
|
const handleConfirm = () => {
|
|
76
79
|
if (isRemove) {
|
|
77
|
-
analytics.wizardCapture('
|
|
80
|
+
analytics.wizardCapture('MCP Remove Confirmed');
|
|
78
81
|
void doRemove();
|
|
79
82
|
}
|
|
80
83
|
else if (clients.length === 1) {
|
|
81
84
|
const names = clients.map((c) => c.name);
|
|
82
|
-
analytics.wizardCapture('
|
|
85
|
+
analytics.wizardCapture('MCP Install Confirmed', { clients: names });
|
|
83
86
|
void doInstall(names);
|
|
84
87
|
}
|
|
85
88
|
else {
|
|
86
|
-
analytics.wizardCapture('
|
|
89
|
+
analytics.wizardCapture('MCP Client Picker Shown', {
|
|
87
90
|
available_clients: clients.map((c) => c.name),
|
|
88
91
|
});
|
|
89
92
|
setPhase(Phase.Pick);
|
|
90
93
|
}
|
|
91
94
|
};
|
|
92
95
|
const handleSkip = () => {
|
|
93
|
-
analytics.wizardCapture('
|
|
96
|
+
analytics.wizardCapture('MCP Skipped', { mode });
|
|
94
97
|
markDone(store, McpOutcome.Skipped, [], standalone, onComplete);
|
|
95
98
|
};
|
|
96
99
|
const doInstall = async (names) => {
|
|
@@ -104,7 +107,7 @@ export const McpScreen = ({ store, installer, mode = 'install', standalone = fal
|
|
|
104
107
|
setResultClients([]);
|
|
105
108
|
}
|
|
106
109
|
const failed = names.filter((n) => !result.includes(n));
|
|
107
|
-
analytics.wizardCapture('
|
|
110
|
+
analytics.wizardCapture('MCP Install Complete', {
|
|
108
111
|
installed: result,
|
|
109
112
|
failed,
|
|
110
113
|
attempted: names,
|
|
@@ -123,26 +126,38 @@ export const McpScreen = ({ store, installer, mode = 'install', standalone = fal
|
|
|
123
126
|
catch {
|
|
124
127
|
setResultClients([]);
|
|
125
128
|
}
|
|
126
|
-
analytics.wizardCapture('
|
|
129
|
+
analytics.wizardCapture('MCP Remove Complete', { removed: result });
|
|
127
130
|
setPhase(Phase.Done);
|
|
128
131
|
const outcome = result.length > 0 ? McpOutcome.Installed : McpOutcome.Failed;
|
|
129
132
|
setTimeout(() => markDone(store, outcome, result, standalone, onComplete), 2000);
|
|
130
133
|
};
|
|
131
134
|
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [dataSetupComplete && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "green", bold: true, children: amplitudePreDetected
|
|
132
135
|
? '\u2714 Amplitude is already configured in this project!'
|
|
133
|
-
: '\u2714 Data setup complete!' }) })),
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
: '\u2714 Data setup complete!' }) })), amplitudePreDetectedChoicePending && !isRemove && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: Colors.muted, children: "The installer skipped the automated setup step because Amplitude is already present. You can continue to editor MCP setup, or run the full setup wizard if you want to review or change the integration." }), _jsx(Box, { marginTop: 1, children: _jsx(PickerMenu, { message: "How would you like to proceed?", options: [
|
|
137
|
+
{ label: 'Continue to MCP setup', value: 'continue' },
|
|
138
|
+
{
|
|
139
|
+
label: 'Run setup wizard anyway',
|
|
140
|
+
value: 'wizard',
|
|
141
|
+
},
|
|
142
|
+
], onSelect: (value) => {
|
|
143
|
+
const runWizard = value === 'wizard';
|
|
144
|
+
analytics.wizardCapture('Amplitude Pre-Detected Choice', {
|
|
145
|
+
run_wizard_anyway: runWizard,
|
|
146
|
+
});
|
|
147
|
+
store.resolvePreDetectedChoice(runWizard);
|
|
148
|
+
} }) })] })), !amplitudePreDetectedChoicePending && (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, color: Colors.accent, children: ["MCP Server ", isRemove ? 'Removal' : 'Setup'] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [phase === Phase.Detecting && (_jsx(Text, { color: Colors.muted, children: "Detecting supported editors..." })), phase === Phase.None && (_jsxs(Text, { color: Colors.muted, children: ["No ", isRemove ? 'installed' : 'supported', " MCP clients detected. Skipping..."] })), phase === Phase.Ask && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: Colors.muted, children: ["Detected: ", clients.map((c) => c.name).join(', ')] }), _jsx(Box, { marginTop: 1, children: _jsx(ConfirmationInput, { message: isRemove
|
|
149
|
+
? 'Remove the Amplitude MCP server from your editor?'
|
|
150
|
+
: 'Install the Amplitude MCP server to your editor?', confirmLabel: isRemove ? 'Remove MCP' : 'Install MCP', cancelLabel: "No thanks", onConfirm: handleConfirm, onCancel: handleSkip }) })] })), phase === Phase.Pick && (_jsx(PickerMenu, { message: "Select editor to install MCP server", options: clients.map((c) => ({
|
|
151
|
+
label: c.name,
|
|
152
|
+
value: c.name,
|
|
153
|
+
})), mode: "multi", onSelect: (selected) => {
|
|
154
|
+
const names = Array.isArray(selected) ? selected : [selected];
|
|
155
|
+
if (names.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
analytics.wizardCapture('MCP Clients Selected', {
|
|
158
|
+
selected_clients: names,
|
|
159
|
+
available_clients: clients.map((c) => c.name),
|
|
160
|
+
});
|
|
161
|
+
void doInstall(names);
|
|
162
|
+
} })), phase === Phase.Working && (_jsxs(Text, { color: Colors.muted, children: [isRemove ? 'Removing' : 'Installing', " MCP server..."] })), phase === Phase.Done && (_jsx(Box, { flexDirection: "column", children: resultClients.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "green", bold: true, children: ['\u2714', " MCP server", ' ', isRemove ? 'removed from' : 'installed for', ":"] }), resultClients.map((name, i) => (_jsxs(Text, { children: [' ', '\u2022', " ", name] }, i)))] })) : (_jsxs(Text, { color: Colors.muted, children: [isRemove ? 'Removal' : 'Installation', " skipped."] })) }))] })] }))] }));
|
|
148
163
|
};
|
|
@@ -47,7 +47,7 @@ export const OutroScreen = ({ store }) => {
|
|
|
47
47
|
{ label: 'Exit', value: 'exit' },
|
|
48
48
|
], onSelect: (value) => {
|
|
49
49
|
const choice = Array.isArray(value) ? value[0] : value;
|
|
50
|
-
analytics.wizardCapture('
|
|
50
|
+
analytics.wizardCapture('Outro Action', {
|
|
51
51
|
action: choice,
|
|
52
52
|
outro_kind: outroData.kind,
|
|
53
53
|
});
|
|
@@ -72,6 +72,8 @@ export declare class WizardStore {
|
|
|
72
72
|
private _backupAndFixSettings;
|
|
73
73
|
/** Pending confirmation or choice prompt from the agent. */
|
|
74
74
|
private $pendingPrompt;
|
|
75
|
+
/** Resolves when the user picks continue vs run wizard after Amplitude pre-detection. */
|
|
76
|
+
private _preDetectedChoiceResolver;
|
|
75
77
|
constructor(flow?: Flow);
|
|
76
78
|
get session(): WizardSession;
|
|
77
79
|
set session(value: WizardSession);
|
|
@@ -94,6 +96,7 @@ export declare class WizardStore {
|
|
|
94
96
|
completeSetup(): void;
|
|
95
97
|
setRunPhase(phase: RunPhase): void;
|
|
96
98
|
setCredentials(credentials: WizardSession['credentials']): void;
|
|
99
|
+
setApiKeyNotice(notice: string | null): void;
|
|
97
100
|
setFrameworkConfig(integration: WizardSession['integration'], config: WizardSession['frameworkConfig']): void;
|
|
98
101
|
setDetectionComplete(): void;
|
|
99
102
|
setDetectedFramework(label: string): void;
|
|
@@ -188,6 +191,20 @@ export declare class WizardStore {
|
|
|
188
191
|
*/
|
|
189
192
|
enableFeature(feature: AdditionalFeature): void;
|
|
190
193
|
setAmplitudePreDetected(): void;
|
|
194
|
+
/**
|
|
195
|
+
* Blocks bin.ts until McpScreen resolves via resolvePreDetectedChoice().
|
|
196
|
+
*/
|
|
197
|
+
waitForPreDetectedChoice(): Promise<boolean>;
|
|
198
|
+
/**
|
|
199
|
+
* Called from McpScreen when the user chooses to skip the agent (continue to
|
|
200
|
+
* MCP) or run the full setup wizard anyway.
|
|
201
|
+
*/
|
|
202
|
+
resolvePreDetectedChoice(runWizardAnyway: boolean): void;
|
|
203
|
+
/**
|
|
204
|
+
* Undo the pre-detection fast-path so runWizard can run; used when the user
|
|
205
|
+
* opts into the setup agent after Amplitude was already found in the project.
|
|
206
|
+
*/
|
|
207
|
+
resetForAgentAfterPreDetected(): void;
|
|
191
208
|
setMcpComplete(outcome?: McpOutcome, installedClients?: string[]): void;
|
|
192
209
|
setSlackComplete(outcome?: SlackOutcome): void;
|
|
193
210
|
setOutroData(data: OutroData): void;
|
package/dist/src/ui/tui/store.js
CHANGED
|
@@ -13,7 +13,7 @@ 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
17
|
export { TaskStatus, Screen, Overlay, Flow, RunPhase, McpOutcome, SlackOutcome, };
|
|
18
18
|
export class WizardStore {
|
|
19
19
|
// ── Internal nanostore atoms ─────────────────────────────────────
|
|
@@ -51,6 +51,8 @@ export class WizardStore {
|
|
|
51
51
|
_backupAndFixSettings = null;
|
|
52
52
|
/** Pending confirmation or choice prompt from the agent. */
|
|
53
53
|
$pendingPrompt = atom(null);
|
|
54
|
+
/** Resolves when the user picks continue vs run wizard after Amplitude pre-detection. */
|
|
55
|
+
_preDetectedChoiceResolver = null;
|
|
54
56
|
constructor(flow = Flow.Wizard) {
|
|
55
57
|
this.router = new WizardRouter(flow);
|
|
56
58
|
}
|
|
@@ -103,7 +105,7 @@ export class WizardStore {
|
|
|
103
105
|
*/
|
|
104
106
|
completeSetup() {
|
|
105
107
|
this.$session.setKey('setupConfirmed', true);
|
|
106
|
-
analytics.wizardCapture('
|
|
108
|
+
analytics.wizardCapture('Setup Confirmed', sessionPropertiesCompact(this.session));
|
|
107
109
|
this._resolveSetup();
|
|
108
110
|
this.emitChange();
|
|
109
111
|
}
|
|
@@ -116,15 +118,16 @@ export class WizardStore {
|
|
|
116
118
|
if (credentials?.projectId) {
|
|
117
119
|
analytics.setDistinctId(String(credentials.projectId));
|
|
118
120
|
}
|
|
119
|
-
analytics.wizardCapture('
|
|
120
|
-
project_id: credentials?.projectId,
|
|
121
|
-
});
|
|
122
|
-
analytics.wizardCapture('credentials updated', {
|
|
121
|
+
analytics.wizardCapture('Auth Complete', {
|
|
123
122
|
project_id: credentials?.projectId,
|
|
124
123
|
region: this.session.region,
|
|
125
124
|
});
|
|
126
125
|
this.emitChange();
|
|
127
126
|
}
|
|
127
|
+
setApiKeyNotice(notice) {
|
|
128
|
+
this.$session.setKey('apiKeyNotice', notice);
|
|
129
|
+
this.emitChange();
|
|
130
|
+
}
|
|
128
131
|
setFrameworkConfig(integration, config) {
|
|
129
132
|
this.$session.setKey('integration', integration);
|
|
130
133
|
this.$session.setKey('frameworkConfig', config);
|
|
@@ -194,7 +197,7 @@ export class WizardStore {
|
|
|
194
197
|
const prompt = this.$pendingPrompt.get();
|
|
195
198
|
if (!prompt || prompt.kind === 'event-plan')
|
|
196
199
|
return;
|
|
197
|
-
analytics.wizardCapture('
|
|
200
|
+
analytics.wizardCapture('Prompt Response', {
|
|
198
201
|
prompt_kind: prompt.kind,
|
|
199
202
|
response: String(answer),
|
|
200
203
|
});
|
|
@@ -219,7 +222,7 @@ export class WizardStore {
|
|
|
219
222
|
const prompt = this.$pendingPrompt.get();
|
|
220
223
|
if (!prompt || prompt.kind !== 'event-plan')
|
|
221
224
|
return;
|
|
222
|
-
analytics.wizardCapture('
|
|
225
|
+
analytics.wizardCapture('Prompt Response', {
|
|
223
226
|
prompt_kind: 'event-plan',
|
|
224
227
|
response: typeof decision === 'object' ? 'feedback' : String(decision),
|
|
225
228
|
});
|
|
@@ -303,7 +306,7 @@ export class WizardStore {
|
|
|
303
306
|
}
|
|
304
307
|
setDataIngestionConfirmed() {
|
|
305
308
|
this.$session.setKey('dataIngestionConfirmed', true);
|
|
306
|
-
analytics.wizardCapture('
|
|
309
|
+
analytics.wizardCapture('Data Ingestion Confirmed', sessionPropertiesCompact(this.session));
|
|
307
310
|
this.emitChange();
|
|
308
311
|
}
|
|
309
312
|
setChecklistChartComplete() {
|
|
@@ -316,10 +319,10 @@ export class WizardStore {
|
|
|
316
319
|
}
|
|
317
320
|
setChecklistComplete() {
|
|
318
321
|
this.$session.setKey('checklistComplete', true);
|
|
319
|
-
analytics.wizardCapture('
|
|
322
|
+
analytics.wizardCapture('Checklist Completed', {
|
|
320
323
|
chart_complete: this.session.checklistChartComplete,
|
|
321
324
|
dashboard_complete: this.session.checklistDashboardComplete,
|
|
322
|
-
...
|
|
325
|
+
...sessionPropertiesCompact(this.session),
|
|
323
326
|
});
|
|
324
327
|
this.emitChange();
|
|
325
328
|
}
|
|
@@ -411,30 +414,62 @@ export class WizardStore {
|
|
|
411
414
|
if (feature === AdditionalFeature.LLM) {
|
|
412
415
|
this.session.llmOptIn = true;
|
|
413
416
|
}
|
|
414
|
-
analytics.wizardCapture('
|
|
417
|
+
analytics.wizardCapture('Feature Enabled', { feature });
|
|
415
418
|
this.emitChange();
|
|
416
419
|
}
|
|
417
420
|
setAmplitudePreDetected() {
|
|
418
421
|
this.$session.setKey('amplitudePreDetected', true);
|
|
422
|
+
this.$session.setKey('amplitudePreDetectedChoicePending', true);
|
|
423
|
+
this.emitChange();
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Blocks bin.ts until McpScreen resolves via resolvePreDetectedChoice().
|
|
427
|
+
*/
|
|
428
|
+
waitForPreDetectedChoice() {
|
|
429
|
+
return new Promise((resolve) => {
|
|
430
|
+
this._preDetectedChoiceResolver = resolve;
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Called from McpScreen when the user chooses to skip the agent (continue to
|
|
435
|
+
* MCP) or run the full setup wizard anyway.
|
|
436
|
+
*/
|
|
437
|
+
resolvePreDetectedChoice(runWizardAnyway) {
|
|
438
|
+
const resolveFn = this._preDetectedChoiceResolver;
|
|
439
|
+
this._preDetectedChoiceResolver = null;
|
|
440
|
+
if (!runWizardAnyway) {
|
|
441
|
+
this.$session.setKey('amplitudePreDetectedChoicePending', false);
|
|
442
|
+
}
|
|
443
|
+
this.emitChange();
|
|
444
|
+
resolveFn?.(runWizardAnyway);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Undo the pre-detection fast-path so runWizard can run; used when the user
|
|
448
|
+
* opts into the setup agent after Amplitude was already found in the project.
|
|
449
|
+
*/
|
|
450
|
+
resetForAgentAfterPreDetected() {
|
|
451
|
+
this.$session.setKey('amplitudePreDetected', false);
|
|
452
|
+
this.$session.setKey('amplitudePreDetectedChoicePending', false);
|
|
453
|
+
this.$session.setKey('runPhase', RunPhase.Idle);
|
|
419
454
|
this.emitChange();
|
|
420
455
|
}
|
|
421
456
|
setMcpComplete(outcome = McpOutcome.Skipped, installedClients = []) {
|
|
422
457
|
this.$session.setKey('mcpComplete', true);
|
|
423
458
|
this.$session.setKey('mcpOutcome', outcome);
|
|
424
459
|
this.$session.setKey('mcpInstalledClients', installedClients);
|
|
425
|
-
analytics.wizardCapture('
|
|
460
|
+
analytics.wizardCapture('MCP Complete', {
|
|
426
461
|
mcp_outcome: outcome,
|
|
427
462
|
mcp_installed_clients: installedClients,
|
|
428
|
-
...
|
|
463
|
+
...sessionPropertiesCompact(this.session),
|
|
429
464
|
});
|
|
430
465
|
this.emitChange();
|
|
431
466
|
}
|
|
432
467
|
setSlackComplete(outcome = SlackOutcome.Skipped) {
|
|
433
468
|
this.$session.setKey('slackComplete', true);
|
|
434
469
|
this.$session.setKey('slackOutcome', outcome);
|
|
435
|
-
analytics.wizardCapture('
|
|
470
|
+
analytics.wizardCapture('Slack Complete', {
|
|
436
471
|
slack_outcome: outcome,
|
|
437
|
-
...
|
|
472
|
+
...sessionPropertiesCompact(this.session),
|
|
438
473
|
});
|
|
439
474
|
this.emitChange();
|
|
440
475
|
}
|
|
@@ -512,9 +547,10 @@ export class WizardStore {
|
|
|
512
547
|
for (const fn of hooks)
|
|
513
548
|
fn();
|
|
514
549
|
}
|
|
515
|
-
analytics.wizardCapture(
|
|
516
|
-
|
|
517
|
-
|
|
550
|
+
analytics.wizardCapture('Wizard Screen Entered', {
|
|
551
|
+
screen_name: next,
|
|
552
|
+
previous_screen: prev,
|
|
553
|
+
...sessionPropertiesCompact(this.session),
|
|
518
554
|
});
|
|
519
555
|
}
|
|
520
556
|
}
|
|
@@ -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,9 +32,11 @@ 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
|
|
39
|
+
private ensureInitStarted;
|
|
26
40
|
getFeatureFlag(flagKey: string): Promise<string | boolean | undefined>;
|
|
27
41
|
/**
|
|
28
42
|
* Evaluate all feature flags for the current user at the start of a run.
|
|
@@ -32,4 +46,14 @@ export declare class Analytics {
|
|
|
32
46
|
getAllFlagsForWizard(): Promise<Record<string, string>>;
|
|
33
47
|
shutdown(status: 'success' | 'error' | 'cancelled'): Promise<void>;
|
|
34
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Full Amplitude `event_type` for CLI/TUI product feedback.
|
|
51
|
+
* Same string as `wizardCapture('Feedback Submitted', …)`.
|
|
52
|
+
*/
|
|
53
|
+
export declare const WIZARD_FEEDBACK_EVENT_TYPE = "wizard: feedback submitted";
|
|
35
54
|
export declare const analytics: Analytics;
|
|
55
|
+
/**
|
|
56
|
+
* Unified wizard error telemetry (aligns with starter taxonomy “Error Encountered”).
|
|
57
|
+
* Emits `wizard: error encountered` with category / message / context.
|
|
58
|
+
*/
|
|
59
|
+
export declare function captureWizardError(errorCategory: string, errorMessage: string, errorContext: string, extra?: Record<string, unknown>): void;
|
|
@@ -1,11 +1,29 @@
|
|
|
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
|
-
|
|
12
|
+
const DEFAULT_TELEMETRY_API_KEY = 'e5a2c9bdffe949f7da77e6b481e118fa';
|
|
13
|
+
/**
|
|
14
|
+
* Telemetry project API key. Empty or whitespace-only env value means “no key”
|
|
15
|
+
* (use default only when the variable is unset).
|
|
16
|
+
*/
|
|
17
|
+
function resolveTelemetryApiKey() {
|
|
18
|
+
const fromEnv = process.env.AMPLITUDE_API_KEY;
|
|
19
|
+
const raw = fromEnv !== undefined ? fromEnv : DEFAULT_TELEMETRY_API_KEY;
|
|
20
|
+
return raw.trim();
|
|
21
|
+
}
|
|
22
|
+
/** HTTP API URL for `@amplitude/analytics-node` (same shape as manual HTTP ingest). */
|
|
23
|
+
function getAmplitudeNodeServerUrl() {
|
|
24
|
+
const base = process.env.AMPLITUDE_SERVER_URL ?? 'https://api2.amplitude.com';
|
|
25
|
+
return `${base.replace(/\/$/, '')}/2/httpapi`;
|
|
26
|
+
}
|
|
9
27
|
/**
|
|
10
28
|
* Extract a standard property bag from the current session.
|
|
11
29
|
* Used by store-level analytics and available for ad-hoc captures.
|
|
@@ -21,18 +39,31 @@ function sessionProperties(session) {
|
|
|
21
39
|
run_phase: session.runPhase,
|
|
22
40
|
};
|
|
23
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Smaller session bag for high-volume wizard events (taxonomy: keep event
|
|
44
|
+
* properties chart-useful and within a small count).
|
|
45
|
+
*/
|
|
46
|
+
function sessionPropertiesCompact(session) {
|
|
47
|
+
return {
|
|
48
|
+
integration: session.integration,
|
|
49
|
+
detected_framework: session.detectedFrameworkLabel,
|
|
50
|
+
run_phase: session.runPhase,
|
|
51
|
+
project_id: session.credentials?.projectId,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
24
54
|
class Analytics {
|
|
25
55
|
tags = {};
|
|
26
56
|
distinctId;
|
|
27
57
|
anonymousId;
|
|
28
58
|
appName = 'wizard';
|
|
29
59
|
activeFlags = null;
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
client;
|
|
61
|
+
initPromise = null;
|
|
32
62
|
constructor() {
|
|
33
63
|
this.tags = { $app_name: this.appName };
|
|
34
64
|
this.anonymousId = (0, uuid_1.v4)();
|
|
35
65
|
this.distinctId = undefined;
|
|
66
|
+
this.client = (0, analytics_node_1.createInstance)();
|
|
36
67
|
}
|
|
37
68
|
setDistinctId(distinctId) {
|
|
38
69
|
this.distinctId = distinctId;
|
|
@@ -48,59 +79,45 @@ class Analytics {
|
|
|
48
79
|
});
|
|
49
80
|
}
|
|
50
81
|
capture(eventName, properties) {
|
|
51
|
-
|
|
82
|
+
const apiKey = resolveTelemetryApiKey();
|
|
83
|
+
if (!apiKey) {
|
|
52
84
|
(0, debug_1.debug)('capture (no API key):', eventName, properties);
|
|
53
85
|
return;
|
|
54
86
|
}
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
this.ensureInitStarted();
|
|
88
|
+
const eventProps = {
|
|
89
|
+
...this.tags,
|
|
90
|
+
...properties,
|
|
91
|
+
};
|
|
92
|
+
const options = {
|
|
57
93
|
device_id: this.anonymousId,
|
|
58
|
-
time: Date.now(),
|
|
59
|
-
event_properties: {
|
|
60
|
-
...this.tags,
|
|
61
|
-
...properties,
|
|
62
|
-
},
|
|
63
94
|
};
|
|
64
95
|
if (this.distinctId) {
|
|
65
|
-
|
|
96
|
+
options.user_id = this.distinctId;
|
|
66
97
|
}
|
|
67
|
-
this.
|
|
98
|
+
this.client.track(eventName, eventProps, options);
|
|
68
99
|
(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
100
|
}
|
|
77
101
|
/**
|
|
78
102
|
* Capture a wizard-specific event. Automatically prepends "wizard: " to the event name.
|
|
79
103
|
* All new wizard analytics should use this method instead of capture() directly.
|
|
104
|
+
* Use lowercase with spaces for eventName (e.g. "agent started", "api key submitted")
|
|
105
|
+
* per Amplitude quickstart taxonomy guidelines.
|
|
80
106
|
*/
|
|
81
107
|
wizardCapture(eventName, properties) {
|
|
82
108
|
this.capture(`wizard: ${eventName}`, properties);
|
|
83
109
|
}
|
|
84
|
-
|
|
85
|
-
if (this.
|
|
86
|
-
return;
|
|
87
|
-
if (!AMPLITUDE_API_KEY)
|
|
110
|
+
ensureInitStarted() {
|
|
111
|
+
if (this.initPromise !== null) {
|
|
88
112
|
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
113
|
}
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
const apiKey = resolveTelemetryApiKey();
|
|
115
|
+
if (!apiKey) {
|
|
116
|
+
return;
|
|
103
117
|
}
|
|
118
|
+
this.initPromise = this.client.init(apiKey, {
|
|
119
|
+
serverUrl: getAmplitudeNodeServerUrl(),
|
|
120
|
+
}).promise;
|
|
104
121
|
}
|
|
105
122
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
106
123
|
async getFeatureFlag(flagKey) {
|
|
@@ -121,13 +138,35 @@ class Analytics {
|
|
|
121
138
|
return this.activeFlags;
|
|
122
139
|
}
|
|
123
140
|
async shutdown(status) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
this.wizardCapture('Session Ended', { status });
|
|
142
|
+
if (this.initPromise === null) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await this.initPromise;
|
|
147
|
+
await this.client.flush().promise;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
(0, debug_1.debug)('analytics shutdown flush error:', err);
|
|
127
151
|
}
|
|
128
|
-
this.capture('wizard: session ended', { status });
|
|
129
|
-
await this.flush();
|
|
130
152
|
}
|
|
131
153
|
}
|
|
132
154
|
exports.Analytics = Analytics;
|
|
155
|
+
/**
|
|
156
|
+
* Full Amplitude `event_type` for CLI/TUI product feedback.
|
|
157
|
+
* Same string as `wizardCapture('Feedback Submitted', …)`.
|
|
158
|
+
*/
|
|
159
|
+
exports.WIZARD_FEEDBACK_EVENT_TYPE = 'wizard: feedback submitted';
|
|
133
160
|
exports.analytics = new Analytics();
|
|
161
|
+
/**
|
|
162
|
+
* Unified wizard error telemetry (aligns with starter taxonomy “Error Encountered”).
|
|
163
|
+
* Emits `wizard: error encountered` with category / message / context.
|
|
164
|
+
*/
|
|
165
|
+
function captureWizardError(errorCategory, errorMessage, errorContext, extra) {
|
|
166
|
+
exports.analytics.wizardCapture('Error Encountered', {
|
|
167
|
+
error_category: errorCategory,
|
|
168
|
+
error_message: errorMessage,
|
|
169
|
+
error_context: errorContext,
|
|
170
|
+
...extra,
|
|
171
|
+
});
|
|
172
|
+
}
|
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)();
|