@userreason/wizard 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +95 -0
  2. package/dist/agent-note.js +7 -6
  3. package/dist/agent-skill.js +6 -5
  4. package/dist/analytics.js +9 -0
  5. package/dist/bin.js +222 -51
  6. package/dist/chunk-4INLEQE2.js +196 -0
  7. package/dist/{chunk-GGARVSPT.js → chunk-7AAH6C4L.js} +9198 -5670
  8. package/dist/{chunk-ULHKVBPD.js → chunk-7O7H3PIM.js} +7 -7
  9. package/dist/chunk-CSZW2ULJ.js +152 -0
  10. package/dist/{chunk-3RDJORRS.js → chunk-DMXUXEQI.js} +1 -1
  11. package/dist/{chunk-SJVLAYGG.js → chunk-EVNEX5N5.js} +8 -1
  12. package/dist/chunk-KF37D5WL.js +44 -0
  13. package/dist/{chunk-5SC2K42Q.js → chunk-LEJFE4U5.js} +2 -2
  14. package/dist/{chunk-OEGNGGZ2.js → chunk-NMHTFC5Q.js} +10 -3
  15. package/dist/{chunk-6CJ4ANMN.js → chunk-NNPYUATH.js} +1 -1
  16. package/dist/chunk-PJQOSDYS.js +50 -0
  17. package/dist/chunk-Q5U5UOQN.js +288 -0
  18. package/dist/{chunk-5Z5J6PY5.js → chunk-SB3AYQWG.js} +6 -6
  19. package/dist/{chunk-YMSGDC4B.js → chunk-TOID3NYP.js} +40 -11
  20. package/dist/{chunk-JD5U4U26.js → chunk-TRBSH5G7.js} +6 -6
  21. package/dist/{chunk-ZGZOBW2B.js → chunk-XASPGFYK.js} +3 -3
  22. package/dist/{chunk-HACTNBV3.js → chunk-XPFKXLIZ.js} +3 -3
  23. package/dist/chunk-ZHJV247H.js +31 -0
  24. package/dist/cli-access.js +3 -3
  25. package/dist/connection.js +2 -2
  26. package/dist/first-survey.js +11 -0
  27. package/dist/footer.js +17 -0
  28. package/dist/global-cli.js +6 -5
  29. package/dist/install-diagnostics.js +9 -0
  30. package/dist/integrate.js +5 -4
  31. package/dist/project.js +8 -3
  32. package/dist/registry.js +5 -3
  33. package/dist/setup-flow.js +25 -0
  34. package/dist/setup.js +33 -0
  35. package/dist/skills/userreason/SKILL.md +43 -0
  36. package/dist/skills/userreason/references/management.md +6 -0
  37. package/dist/startup.js +2 -2
  38. package/dist/state.js +6 -5
  39. package/dist/ui.js +2 -2
  40. package/package.json +1 -2
package/dist/bin.js CHANGED
@@ -1,50 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as createWizardRequire } from 'node:module'; import { fileURLToPath as wizardFileURLToPath } from 'node:url'; import { dirname as wizardDirname } from 'node:path'; const require = createWizardRequire(import.meta.url); const __filename = wizardFileURLToPath(import.meta.url); const __dirname = wizardDirname(__filename);
3
+ import {
4
+ runBrowserWizard
5
+ } from "./chunk-CSZW2ULJ.js";
6
+ import {
7
+ clearInstallation,
8
+ resolveInstallation,
9
+ saveInstallation,
10
+ savedInstallation
11
+ } from "./chunk-XASPGFYK.js";
3
12
  import {
4
13
  offerAgentNote,
5
14
  planAgentNote
6
- } from "./chunk-ULHKVBPD.js";
7
- import {
8
- offerAgentSkill,
9
- planAgentSkill
10
- } from "./chunk-5Z5J6PY5.js";
15
+ } from "./chunk-7O7H3PIM.js";
11
16
  import {
12
17
  offerCliAccess
13
- } from "./chunk-HACTNBV3.js";
18
+ } from "./chunk-XPFKXLIZ.js";
14
19
  import {
15
20
  offerGlobalCli
16
- } from "./chunk-JD5U4U26.js";
21
+ } from "./chunk-TRBSH5G7.js";
17
22
  import {
18
- ensureCurrentWizard,
19
- wizardVersion
20
- } from "./chunk-6CJ4ANMN.js";
23
+ WizardAnalytics
24
+ } from "./chunk-KF37D5WL.js";
21
25
  import {
22
- Cancelled,
23
- Terminal,
24
- approvalStatus,
25
- clean,
26
- normalizeOrigin,
27
- openBrowser
28
- } from "./chunk-SJVLAYGG.js";
26
+ beginFirstSurveyPreparation,
27
+ offerFirstSurvey
28
+ } from "./chunk-ZHJV247H.js";
29
+ import "./chunk-Q5U5UOQN.js";
30
+ import "./chunk-4INLEQE2.js";
29
31
  import {
30
- BrowserConnectionUnavailable,
31
- compatibilityCredentials,
32
- connectInstallation,
33
- memoryCredentials
34
- } from "./chunk-3RDJORRS.js";
32
+ offerAgentSkill,
33
+ planAgentSkill
34
+ } from "./chunk-SB3AYQWG.js";
35
35
  import {
36
- clearInstallation,
37
- resolveInstallation,
38
- saveInstallation,
39
- savedInstallation
40
- } from "./chunk-ZGZOBW2B.js";
36
+ ensureCurrentWizard,
37
+ wizardVersion
38
+ } from "./chunk-NNPYUATH.js";
41
39
  import {
42
40
  applyIntegration,
43
41
  detectInstallation,
44
42
  integrationAdditions,
45
43
  integrationPreview,
46
44
  planIntegration
47
- } from "./chunk-5SC2K42Q.js";
45
+ } from "./chunk-LEJFE4U5.js";
48
46
  import {
49
47
  SdkInstallError,
50
48
  cliCommand,
@@ -58,8 +56,23 @@ import {
58
56
  sdkInstallCommand,
59
57
  verifyConfiguration,
60
58
  writeIntegration
61
- } from "./chunk-YMSGDC4B.js";
62
- import "./chunk-OEGNGGZ2.js";
59
+ } from "./chunk-TOID3NYP.js";
60
+ import "./chunk-PJQOSDYS.js";
61
+ import "./chunk-NMHTFC5Q.js";
62
+ import {
63
+ Cancelled,
64
+ Terminal,
65
+ approvalStatus,
66
+ clean,
67
+ normalizeOrigin,
68
+ openBrowser
69
+ } from "./chunk-EVNEX5N5.js";
70
+ import {
71
+ BrowserConnectionUnavailable,
72
+ compatibilityCredentials,
73
+ connectInstallation,
74
+ memoryCredentials
75
+ } from "./chunk-DMXUXEQI.js";
63
76
  import {
64
77
  ApiError,
65
78
  AuthenticationError,
@@ -67,13 +80,14 @@ import {
67
80
  apiUrl,
68
81
  credentialStoreKind,
69
82
  detectAgentHarness,
83
+ firstSurveyHandoff,
70
84
  login,
71
85
  packageProfile,
72
86
  resolveConfig,
73
87
  resolveCredentials,
74
88
  saveConfig,
75
89
  uuid
76
- } from "./chunk-GGARVSPT.js";
90
+ } from "./chunk-7AAH6C4L.js";
77
91
  import "./chunk-WL3NOIM6.js";
78
92
 
79
93
  // src/bin.ts
@@ -83,6 +97,10 @@ var help = `UserReason installation wizard
83
97
 
84
98
  userreason-wizard [--directory PATH] [--origin URL]
85
99
 
100
+ --browser-setup (browser-guided setup; default for development onboarding)
101
+ --terminal-setup (use terminal onboarding instead)
102
+ --resume-setup UUID (resume this project's saved browser setup)
103
+ --browser-origin ORIGIN (trusted dashboard origin for a custom API)
86
104
  --workspace UUID / --workspace-name NAME
87
105
  --site UUID / --site-name NAME
88
106
  --environment development|production (customer site environment)
@@ -101,6 +119,7 @@ userreason-wizard [--directory PATH] [--origin URL]
101
119
  --diff (show the full patch for the application edit and the proposed guidance text)
102
120
  --interactive (ask questions even when an agent harness or CI is detected)
103
121
  --no-color (also NO_COLOR)
122
+ --no-telemetry (also UR_TELEMETRY_DISABLED=1)
104
123
  --no-open (print the browser link without opening it)
105
124
  --session-only (keep compatibility login only in memory; do not persist credentials)
106
125
  --force (replace differing generated files)
@@ -118,6 +137,10 @@ Prompts are off when stdin or stdout is not a terminal, with --yes or --json, in
118
137
  var nameError = (value) => !value.trim() || value.trim().length > 240 ? "Enter a name between 1 and 240 characters." : void 0;
119
138
  var currentStep = "Reading wizard options";
120
139
  var activeTerminal;
140
+ var analytics;
141
+ var surveyPreparation;
142
+ var surveyAbort = new AbortController();
143
+ var measured = (step, operation) => analytics ? analytics.measure(step, operation) : operation();
121
144
  var frameworkLabel = (framework) => framework === "next" ? "Next.js" : framework;
122
145
  var lowerFirst = (value) => value.charAt(0).toLowerCase() + value.slice(1);
123
146
  async function main() {
@@ -127,7 +150,12 @@ async function main() {
127
150
  help: { type: "boolean", short: "h" },
128
151
  version: { type: "boolean", short: "v" },
129
152
  dev: { type: "boolean" },
153
+ "browser-setup": { type: "boolean" },
154
+ "terminal-setup": { type: "boolean" },
155
+ "resume-setup": { type: "string" },
156
+ "browser-origin": { type: "string" },
130
157
  "api-environment": { type: "string" },
158
+ "no-telemetry": { type: "boolean" },
131
159
  yes: { type: "boolean" },
132
160
  install: { type: "boolean" },
133
161
  refresh: { type: "boolean" },
@@ -173,7 +201,6 @@ async function main() {
173
201
  process.stdout.write(help);
174
202
  return;
175
203
  }
176
- if (values["credential-store"] !== void 0) credentialStoreKind(values["credential-store"]);
177
204
  if (values["api-environment"] !== void 0 && !["production", "development"].includes(values["api-environment"]))
178
205
  throw new Error("API environment must be development or production");
179
206
  if (values.dev && values["api-environment"] === "production")
@@ -181,6 +208,20 @@ async function main() {
181
208
  const embeddedProfile = packageProfile();
182
209
  const apiEnvironment = values["api-environment"] ?? (values.dev ? "development" : embeddedProfile.apiEnvironment);
183
210
  const developmentApi = apiEnvironment === "development";
211
+ const explicitBrowserMode = Boolean(values["browser-setup"] || values["resume-setup"]);
212
+ if (values["browser-setup"] && values["resume-setup"]) throw new Error("Choose --browser-setup or --resume-setup");
213
+ if (values["terminal-setup"] && explicitBrowserMode) throw new Error("Choose --terminal-setup or browser setup");
214
+ if (values["resume-setup"]) uuid(values["resume-setup"], "Setup ID");
215
+ const terminalFlags = ["refresh", "dry-run", "install", "skip-install", "integrate", "skip-integrate", "agent-note", "skip-agent-note", "agent-skill", "skip-agent-skill", "global-cli", "skip-global-cli", "cli-access", "skip-cli-access", "workspace", "workspace-name", "site", "site-name", "origin", "environment", "force", "diff", "session-only", "client-id", "credential-store"];
216
+ const hasTerminalFlags = terminalFlags.some((key) => values[key] !== void 0 && values[key] !== false);
217
+ const browserMode = explicitBrowserMode || developmentApi && !values["terminal-setup"] && !hasTerminalFlags && !values.yes && !values.json;
218
+ if (values["browser-origin"]) {
219
+ if (!browserMode) throw new Error("--browser-origin requires browser setup");
220
+ apiUrl(values["browser-origin"], "Browser origin");
221
+ }
222
+ if (browserMode && hasTerminalFlags)
223
+ throw new Error("Browser setup choices are reviewed in the browser. Omit terminal installation/resource flags; ongoing CLI access is a separate login.");
224
+ if (values["credential-store"] !== void 0) credentialStoreKind(values["credential-store"]);
184
225
  const newSiteEnvironment = values.environment ?? apiEnvironment;
185
226
  if (values["global-cli"] && values["skip-global-cli"]) throw new Error("Choose --global-cli or --skip-global-cli");
186
227
  if (values["global-cli"] && values["skip-install"]) throw new Error("--global-cli requires project package installation; omit --skip-install");
@@ -218,8 +259,25 @@ async function main() {
218
259
  const tty = Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.TERM !== "dumb");
219
260
  const explicitlyQuiet = Boolean(values.yes || values.json);
220
261
  const interactive = tty && !explicitlyQuiet && (!harness || Boolean(values.interactive));
221
- const terminal = new Terminal(interactive, Boolean(values.json), { color: !values["no-color"] });
262
+ const terminal = new Terminal(interactive, Boolean(values.json), { color: !values["no-color"], onCancel: () => {
263
+ surveyAbort.abort(new Cancelled());
264
+ surveyPreparation?.cancel();
265
+ if (browserMode) {
266
+ process.exitCode = 130;
267
+ return;
268
+ }
269
+ analytics?.finish(true);
270
+ process.exitCode = 130;
271
+ void (analytics?.flush(true) ?? Promise.resolve()).finally(() => process.exit(130));
272
+ } });
222
273
  activeTerminal = terminal;
274
+ try {
275
+ analytics = new WizardAnalytics({ surface: "wizard", releaseVersion: version, executionMode: interactive ? "interactive" : process.env.CI ? "ci" : harness ? "agent" : "noninteractive", apiUrl: values["api-url"] ?? (developmentApi ? "https://api-dev.userreason.com" : "https://api.userreason.com"), disabled: values["no-telemetry"] || values["dry-run"] });
276
+ } catch {
277
+ }
278
+ if (!values.refresh) analytics?.configure({ context: { source_site_environment: newSiteEnvironment === "development" ? "development" : "production" } });
279
+ analytics?.begin("run");
280
+ await analytics?.checkpoint();
223
281
  const { c, S } = terminal.kit;
224
282
  const dot = ` ${S.dot} `;
225
283
  const environmentNote = developmentApi ? `development API${newSiteEnvironment !== apiEnvironment ? `, new sites are ${newSiteEnvironment}` : ""}` : newSiteEnvironment !== apiEnvironment ? `new sites are ${newSiteEnvironment}` : void 0;
@@ -239,8 +297,27 @@ async function main() {
239
297
  values.directory ?? process.cwd(),
240
298
  values.framework
241
299
  );
300
+ analytics?.configure({ wizard: { framework: project.framework, package_manager: project.packageManager } });
242
301
  terminal.info(`${clean(project.name)} ${c.muted(`(${frameworkLabel(project.framework)}, ${project.packageManager})`)}`);
302
+ if (browserMode) {
303
+ currentStep = values["resume-setup"] ? "Resuming browser setup" : "Connecting browser setup";
304
+ const interrupted = () => surveyAbort.abort(new Cancelled());
305
+ process.once("SIGINT", interrupted);
306
+ process.once("SIGTERM", interrupted);
307
+ terminal.cancelState = "the local runner stopped; browser approvals, applied changes and saved drafts remain available when you resume";
308
+ let handled;
309
+ try {
310
+ handled = await runBrowserWizard({ project, apiOrigin: apiUrl(values["api-url"] ?? (developmentApi ? "https://api-dev.userreason.com" : "https://api.userreason.com")), browserOrigin: values["browser-origin"], resumeId: values["resume-setup"], profile, version, terminal, development: developmentApi, noOpen: Boolean(values["no-open"]), json: Boolean(values.json), signal: surveyAbort.signal, measurementDisabled: values["no-telemetry"] });
311
+ } finally {
312
+ process.off("SIGINT", interrupted);
313
+ process.off("SIGTERM", interrupted);
314
+ }
315
+ if (handled) return;
316
+ terminal.cancelState = "nothing was written";
317
+ terminal.info("This server has not enabled browser setup. Continuing with the existing terminal wizard.");
318
+ }
243
319
  if (values.refresh) {
320
+ analytics?.configure({ wizard: { resumed: true } });
244
321
  currentStep = "Refreshing UserReason packages";
245
322
  const existing = await detectInstallation(project);
246
323
  if (!existing) throw new Error("No UserReason initialization found. Run the wizard without --refresh to set up this application.");
@@ -253,18 +330,49 @@ async function main() {
253
330
  const started = Date.now();
254
331
  await terminal.task("Updating UserReason packages", async () => {
255
332
  if (!await hasCompatibleSdk(project, profile) || values.install) {
256
- await installSdk(project, options);
333
+ await measured("sdk_install", () => installSdk(project, options));
257
334
  updated.push("@userreason/js");
258
335
  }
259
336
  if (!await hasCompatibleCli(project, profile) || values.install) {
260
- await installCli(project, options);
337
+ await measured("cli_install", () => installCli(project, options));
261
338
  updated.push("@userreason/cli");
262
339
  }
263
340
  }, () => updated.length ? `Updated ${updated.map((name) => c.code(name)).join(", ")} ${c.muted(`(${project.packageManager}, ${Math.max(1, Math.round((Date.now() - started) / 1e3))}s)`)}` : "UserReason packages are already current");
264
- const globalCli2 = await offerGlobalCli(project, terminal, { installed: true, requested: values["global-cli"], skip: !values["global-cli"], force: values.install, verbose: values.debug, profile, preview: Boolean(values.diff) });
265
- const agentNote2 = await offerAgentNote(project, terminal, { installed: true, requested: values["agent-note"], skip: !values["agent-note"], preview: Boolean(values.diff) });
266
- const agentSkill2 = await offerAgentSkill(project, terminal, { installed: true, requested: values["agent-skill"], skip: !values["agent-skill"], preview: Boolean(values.diff) });
267
- const cliAccess2 = values["cli-access"] ? await offerCliAccess(await resolveConfig({ development: developmentApi, apiUrl: existing.apiBaseUrl, clientId: values["client-id"], credentialStore: values["credential-store"] }), terminal, { installed: true, requested: true, noOpen: values["no-open"], development: developmentApi, preview: Boolean(values.diff) }) : { status: "skipped" };
341
+ const refreshOption = async (step, requested, skipped, eligible, operation) => {
342
+ if (!requested) {
343
+ analytics?.step(step, "skipped", { selection_source: skipped ? "flag" : "default", reason: skipped ? "explicit_skip" : "not_requested" });
344
+ return operation();
345
+ }
346
+ if (await eligible().catch(() => false)) analytics?.step(step, "eligible");
347
+ analytics?.step(step, "selected", { selection_source: "flag" });
348
+ return measured(step, operation);
349
+ };
350
+ const globalCli2 = await refreshOption(
351
+ "global_cli",
352
+ values["global-cli"],
353
+ values["skip-global-cli"],
354
+ async () => true,
355
+ () => offerGlobalCli(project, terminal, { installed: true, requested: values["global-cli"], skip: !values["global-cli"], force: values.install, verbose: values.debug, profile, preview: Boolean(values.diff) })
356
+ );
357
+ const agentNote2 = await refreshOption(
358
+ "agent_note",
359
+ values["agent-note"],
360
+ values["skip-agent-note"],
361
+ async () => "path" in await planAgentNote(project),
362
+ () => offerAgentNote(project, terminal, { installed: true, requested: values["agent-note"], skip: !values["agent-note"], preview: Boolean(values.diff) })
363
+ );
364
+ const agentSkill2 = await refreshOption(
365
+ "agent_skill",
366
+ values["agent-skill"],
367
+ values["skip-agent-skill"],
368
+ async () => "files" in await planAgentSkill(project),
369
+ () => offerAgentSkill(project, terminal, { installed: true, requested: values["agent-skill"], skip: !values["agent-skill"], preview: Boolean(values.diff) })
370
+ );
371
+ const cliAccess2 = await refreshOption("cli_access", values["cli-access"], values["skip-cli-access"], async () => true, async () => {
372
+ if (!values["cli-access"]) return { status: "skipped" };
373
+ const accessConfig = await resolveConfig({ development: developmentApi, apiUrl: existing.apiBaseUrl, clientId: values["client-id"], credentialStore: values["credential-store"] });
374
+ return offerCliAccess(accessConfig, terminal, { installed: true, requested: true, noOpen: values["no-open"], development: developmentApi, preview: Boolean(values.diff) });
375
+ });
268
376
  const untouched = [!values["global-cli"] && ["global command", "--global-cli"], !values["agent-note"] && ["AGENTS.md note", "--agent-note"], !values["agent-skill"] && ["agent skill", "--agent-skill"], !values["cli-access"] && ["CLI access", "--cli-access"]].filter(Boolean);
269
377
  terminal.block("Ready", [
270
378
  c.muted("Reload or restart your app to load the updated SDK."),
@@ -334,6 +442,7 @@ async function main() {
334
442
  clientId: values["client-id"],
335
443
  credentialStore: values["credential-store"]
336
444
  });
445
+ analytics?.configure({ apiUrl: config.apiUrl });
337
446
  let resumeStorageBlocked = false;
338
447
  const warnResumeStorage = () => {
339
448
  if (resumeStorageBlocked) return;
@@ -350,12 +459,14 @@ async function main() {
350
459
  let workspaceChoice = values.workspace ? uuid(values.workspace) : saved?.workspaceId;
351
460
  const siteChoice = values.site ? uuid(values.site) : saved?.siteId;
352
461
  const resumed = Boolean(saved && !values.workspace && !values.site);
462
+ analytics?.configure({ wizard: { resumed } });
353
463
  if (siteChoice && !workspaceChoice) {
354
464
  currentStep = "Resolving the existing site";
355
465
  const origin = values.origin ? normalizeOrigin(values.origin, newSiteEnvironment === "development" ? "development" : "production") : void 0;
356
466
  workspaceChoice = (await resolveInstallation({ siteId: siteChoice, apiBaseUrl: config.apiUrl }, origin)).workspaceId;
357
467
  }
358
- let credentials = resolveCredentials(config), client = new UserReasonClient(config, credentials), connection;
468
+ const rawCredentials = resolveCredentials(config);
469
+ let credentials = analytics?.observeCredentials(rawCredentials) ?? rawCredentials, client = new UserReasonClient(config, credentials), connection;
359
470
  const open = async (url) => !values["no-open"] && await openBrowser(url);
360
471
  const approvalWait = async (label, url, note) => {
361
472
  const opened = await open(url);
@@ -373,6 +484,8 @@ async function main() {
373
484
  let waiting;
374
485
  let unrestrictedOrigins = false;
375
486
  currentStep = "Connecting browser sign-in and approval";
487
+ analytics?.begin("authentication");
488
+ await analytics?.checkpoint();
376
489
  try {
377
490
  connection = await connectInstallation(
378
491
  config,
@@ -399,7 +512,9 @@ async function main() {
399
512
  }
400
513
  );
401
514
  waiting?.stop();
402
- credentials = memoryCredentials(connection.session);
515
+ const temporaryCredentials = memoryCredentials(connection.session);
516
+ credentials = analytics?.observeCredentials(temporaryCredentials) ?? temporaryCredentials;
517
+ analytics?.setSession(connection.session);
403
518
  client = new UserReasonClient(config, credentials, {
404
519
  lock: (operation) => operation()
405
520
  });
@@ -419,7 +534,8 @@ async function main() {
419
534
  return terminal.confirm("Keep this session in memory only? No credentials will be saved.");
420
535
  }
421
536
  );
422
- credentials = compatibility.credentials;
537
+ credentials = analytics?.observeCredentials(compatibility.credentials) ?? compatibility.credentials;
538
+ analytics?.setSession(compatibility.session);
423
539
  client = new UserReasonClient(
424
540
  config,
425
541
  credentials,
@@ -471,6 +587,11 @@ async function main() {
471
587
  await deviceSignIn();
472
588
  account = await client.account();
473
589
  }
590
+ analytics?.end("authentication");
591
+ if (developmentApi && terminal.interactive && !values.yes && !values.json && !values.diff) {
592
+ surveyPreparation = beginFirstSurveyPreparation(project.directory, { development: developmentApi, interactive: terminal.interactive });
593
+ }
594
+ analytics?.begin("target_selection");
474
595
  const who = connection ? clean(connection.session.email) : void 0;
475
596
  let selectedWorkspace;
476
597
  const approvedWorkspace = connection?.workspaceId ?? workspaceChoice;
@@ -555,6 +676,8 @@ async function main() {
555
676
  siteId: selectedSite?.id ?? siteIds.siteId,
556
677
  apiBaseUrl: config.apiUrl
557
678
  };
679
+ analytics?.end("target_selection");
680
+ analytics?.configure({ context: { ...selectedWorkspace ? { workspace_id: selectedWorkspace.id } : {}, ...selectedSite ? { site_id: selectedSite.id } : {}, source_site_environment: selectedSite?.environment ?? environment } });
558
681
  currentStep = "Preparing the application integration";
559
682
  const reviewed = values["skip-integrate"] ? void 0 : await planIntegration(project, installation);
560
683
  const additions = reviewed ? integrationAdditions(reviewed) : void 0;
@@ -582,13 +705,31 @@ async function main() {
582
705
  if (skillProbe && "files" in skillProbe) extras.push({ value: "skill", label: skillProbe.update ? "Agent skill update" : "Agent skill", hint: ".agents/skills/userreason", initial: true });
583
706
  if (!values["skip-cli-access"] && !values["session-only"]) extras.push({ value: "access", label: "Ongoing CLI access", hint: "second approval (90 days)", initial: true });
584
707
  if (!values["skip-global-cli"] && !values["skip-install"]) extras.push({ value: "global", label: "Global userreason command", hint: `optional${dot}npm i -g, replaces the current global CLI`, initial: Boolean(values["global-cli"]) });
708
+ for (const item of extras) {
709
+ if (item.value === "surveys") continue;
710
+ const step = { edit: "initialization", note: "agent_note", skill: "agent_skill", access: "cli_access", global: "global_cli" }[item.value];
711
+ analytics?.step(step, "eligible");
712
+ if (interactive) analytics?.step(step, "offered", { selection_source: "prompt" });
713
+ }
714
+ if (interactive && extras.length) analytics?.begin("options");
715
+ await analytics?.checkpoint();
585
716
  const picked = interactive ? new Set(extras.length ? await terminal.multiselect(`Set up ${c.muted(`${dot.trim()} recommended steps are selected`)}`, extras, extras.filter((item) => item.initial).map((item) => item.value)) : []) : void 0;
717
+ if (interactive && extras.length) {
718
+ analytics?.end("options");
719
+ for (const item of extras) {
720
+ if (item.value === "surveys") continue;
721
+ const step = { edit: "initialization", note: "agent_note", skill: "agent_skill", access: "cli_access", global: "global_cli" }[item.value];
722
+ analytics?.step(step, picked?.has(item.value) ? "selected" : "declined", { selection_source: "prompt" });
723
+ }
724
+ }
586
725
  const integrate = Boolean(reviewed?.status === "ready" && (picked ? picked.has("edit") : values.integrate || values.yes));
587
726
  const choice = (key, flag, skipFlag) => picked ? { requested: picked.has(key), skip: !picked.has(key) } : { requested: Boolean(values[flag]), skip: skipFlag };
727
+ if (!interactive) analytics?.step("initialization", integrate ? "selected" : "skipped", { selection_source: values.integrate || values["skip-integrate"] || values.yes ? "flag" : "default", ...!integrate ? { reason: values["skip-integrate"] ? "explicit_skip" : "not_requested" } : {} });
588
728
  const noteChoice = choice("note", "agent-note", Boolean(values["skip-agent-note"]));
589
729
  const skillChoice = choice("skill", "agent-skill", Boolean(values["skip-agent-skill"]));
590
730
  const globalChoice = choice("global", "global-cli", Boolean(values["skip-global-cli"] || values["skip-install"]));
591
731
  const accessChoice = choice("access", "cli-access", Boolean(values["skip-cli-access"] || values["session-only"]));
732
+ if (!interactive) for (const [step, decision] of [["agent_note", noteChoice], ["agent_skill", skillChoice], ["global_cli", globalChoice], ["cli_access", accessChoice]]) analytics?.step(step, decision.requested ? "selected" : "skipped", { selection_source: decision.requested || decision.skip ? "flag" : "default", ...!decision.requested ? { reason: decision.skip ? "explicit_skip" : "not_requested" } : {} });
592
733
  terminal.cancelState = "some steps are done; rerun the wizard to continue";
593
734
  if (!selectedWorkspace) {
594
735
  currentStep = "Creating the workspace";
@@ -625,6 +766,7 @@ async function main() {
625
766
  );
626
767
  terminal.success(`Created site ${c.code(clean(selectedSite.name))}`);
627
768
  }
769
+ analytics?.configure({ context: { workspace_id: selectedWorkspace.id, site_id: selectedSite.id, source_site_environment: selectedSite.environment } });
628
770
  currentStep = "Saving optional installation resume details";
629
771
  if (!await saveInstallation(project.directory, installation)) warnResumeStorage();
630
772
  currentStep = "Preparing initialization files";
@@ -645,9 +787,10 @@ async function main() {
645
787
  await terminal.task("Installing @userreason/js, @userreason/cli", async () => {
646
788
  if (shouldInstall) {
647
789
  try {
648
- await installSdk(project, { profile, verbose: values.debug, onWarning: (message) => {
790
+ const operation = () => installSdk(project, { profile, verbose: values.debug, onWarning: (message) => {
649
791
  warnings.push(message);
650
792
  } });
793
+ await measured("sdk_install", operation);
651
794
  sdkInstalled = true;
652
795
  installed.push("@userreason/js");
653
796
  } catch (error) {
@@ -658,9 +801,10 @@ async function main() {
658
801
  }
659
802
  if (shouldInstallCli) {
660
803
  try {
661
- await installCli(project, { profile, verbose: values.debug, onWarning: (message) => {
804
+ const operation = () => installCli(project, { profile, verbose: values.debug, onWarning: (message) => {
662
805
  warnings.push(message);
663
806
  } });
807
+ await measured("cli_install", operation);
664
808
  cliInstalled = true;
665
809
  installed.push("@userreason/cli");
666
810
  } catch (error) {
@@ -684,16 +828,17 @@ Then rerun the wizard. Your saved workspace and site will be reused.`, failure.t
684
828
  const concrete = await planIntegration(project, installation);
685
829
  if (reviewed.status !== "ready" || concrete.status !== "ready" || concrete.path !== reviewed.path || concrete.before !== reviewed.before || concrete.after !== reviewed.after)
686
830
  throw new Error("The application entry changed after the preview. Rerun to review it again.");
687
- await applyIntegration(concrete);
831
+ await measured("initialization", () => applyIntegration(concrete));
688
832
  if (concrete.before === null) generated.files.push(concrete.path);
689
833
  terminal.success(`${concrete.before === null ? "Created" : "Edited"} ${c.code(additions?.path ?? "")}`);
690
834
  }
835
+ if (existingPlan?.status === "integrated") analytics?.step("initialization", "already_present");
691
836
  const integrated = Boolean(integrate && !installFailed || existingPlan?.status === "integrated");
692
837
  if (!integrated && !installFailed) terminal.note(generated.instruction, "Application integration");
693
838
  currentStep = "Checking site configuration";
694
839
  const verification = await terminal.task(
695
840
  "Checking site configuration",
696
- () => verifyConfiguration(installation, hostOrigin ?? "https://installation.userreason.invalid"),
841
+ () => analytics ? analytics.measure("configuration_check", () => verifyConfiguration(installation, hostOrigin ?? "https://installation.userreason.invalid"), "completed") : verifyConfiguration(installation, hostOrigin ?? "https://installation.userreason.invalid"),
697
842
  "Site configuration reachable"
698
843
  );
699
844
  if (integrated && sdkInstalled && !installFailed) {
@@ -705,13 +850,30 @@ Then rerun the wizard. Your saved workspace and site will be reused.`, failure.t
705
850
  if (picked && !completed && !installFailed && (picked.has("note") || picked.has("skill")))
706
851
  terminal.warn("The note and skill wait for an initialized SDK. Rerun with the entry edit selected, or apply the initialization yourself first.");
707
852
  currentStep = "Offering the optional user feedback note";
708
- const agentNote = await offerAgentNote(project, terminal, { installed: completed, ...noteChoice, preview: Boolean(values.diff) });
853
+ const agentNote = await measured("agent_note", () => offerAgentNote(project, terminal, { installed: completed, ...noteChoice, preview: Boolean(values.diff) }));
709
854
  currentStep = "Offering the optional feedback agent skill";
710
- const agentSkill = await offerAgentSkill(project, terminal, { installed: completed, ...skillChoice, preview: Boolean(values.diff) });
855
+ const agentSkill = await measured("agent_skill", () => offerAgentSkill(project, terminal, { installed: completed, ...skillChoice, preview: Boolean(values.diff) }));
711
856
  currentStep = "Offering the optional global CLI";
712
- const globalCli = await offerGlobalCli(project, terminal, { installed: cliInstalled && !installFailed, ...globalChoice, force: values.install, verbose: values.debug, profile, preview: Boolean(values.diff) });
857
+ const globalCli = await measured("global_cli", () => offerGlobalCli(project, terminal, { installed: cliInstalled && !installFailed, ...globalChoice, force: values.install, verbose: values.debug, profile, preview: Boolean(values.diff) }));
713
858
  currentStep = "Offering ongoing CLI access";
714
- const cliAccess = await offerCliAccess(config, terminal, { installed: cliInstalled && !installFailed, ...accessChoice, noOpen: values["no-open"], development: developmentApi, preview: Boolean(values.diff) });
859
+ const cliAccess = await measured("cli_access", () => offerCliAccess(config, terminal, { installed: cliInstalled && !installFailed, ...accessChoice, noOpen: values["no-open"], development: developmentApi, preview: Boolean(values.diff) }));
860
+ if (!developmentApi && completed && terminal.interactive && !values.yes && !values.json && !values.diff) {
861
+ try {
862
+ currentStep = "Creating the first survey";
863
+ await offerFirstSurvey(new UserReasonClient(config, resolveCredentials(config)), project.directory, selectedWorkspace.id, selectedSite.id, {
864
+ show: (message) => terminal.note(message, developmentApi ? "Recommended surveys" : "First survey"),
865
+ confirm: (message) => terminal.confirm(message, false),
866
+ choose: (message, options, initialValue) => terminal.select(message, options, initialValue),
867
+ answer: (message) => terminal.text(message),
868
+ chooseMany: (message, options, initial) => terminal.multiselect(message, options, initial),
869
+ loading: (message, work) => terminal.task(message, work, () => void 0),
870
+ signal: surveyAbort.signal
871
+ }, { development: developmentApi, interactive: terminal.interactive, product: selectedSite.name, preparation: surveyPreparation, sourceConsent: picked?.has("surveys") });
872
+ } catch (error) {
873
+ terminal.warn("Optional first-survey assistance did not finish. Installation is preserved. Use the ongoing CLI connection with site:read and survey:write; no installation grant is broadened.");
874
+ terminal.note(firstSurveyHandoff(project.directory, config.apiUrl, selectedWorkspace.id, selectedSite.id), "Resume with your agent");
875
+ }
876
+ }
715
877
  const nextStep = installFailed ? `Run ${sdkInstalled ? cliInstallCommand(project, profile) : sdkInstallCommand(project, profile)} in your normal terminal, then rerun the wizard to finish setup.` : integrated ? "Start your app and open it in the browser to connect the SDK." : generated.instruction;
716
878
  const siteLine = `${clean(selectedWorkspace.name)} ${S.arrow} ${clean(selectedSite.name)} ${c.muted(`(${siteDetail(selectedSite)})`)}`;
717
879
  terminal.block(installFailed ? "Saved" : "Ready", [c.muted(installFailed ? "SDK installation is pending. After installing, rerun the wizard; your site is reused." : "SDK not connected yet. Start your app to connect it.")], { suffix: siteLine, kind: installFailed ? "warn" : "done" });
@@ -749,11 +911,16 @@ var approvalProgress;
749
911
  main().catch((error) => {
750
912
  const detail = error instanceof Error ? error.message : "Installation failed";
751
913
  if (error instanceof Cancelled) {
752
- if (activeTerminal) activeTerminal.cancelled();
914
+ analytics?.finish(true);
915
+ if (activeTerminal) {
916
+ activeTerminal.cancelled();
917
+ return;
918
+ }
753
919
  process.stderr.write(detail + "\n");
754
920
  process.exitCode = 130;
755
921
  return;
756
922
  }
923
+ analytics?.finish(false, true);
757
924
  const message = `${currentStep}: ${detail}`;
758
925
  if (process.argv.includes("--json"))
759
926
  process.stdout.write(JSON.stringify({ error: "wizard_failed", step: currentStep, message }) + "\n");
@@ -761,4 +928,8 @@ main().catch((error) => {
761
928
  activeTerminal.error(clean(detail), [`while ${lowerFirst(currentStep)}`]);
762
929
  else process.stderr.write(message + "\n");
763
930
  process.exitCode = 1;
931
+ }).finally(async () => {
932
+ surveyPreparation?.cancel();
933
+ analytics?.finish(false, Boolean(process.exitCode));
934
+ await analytics?.flush(true);
764
935
  });