@userreason/wizard 0.2.0 → 0.3.0

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 (38) hide show
  1. package/README.md +103 -0
  2. package/dist/agent-note.js +6 -6
  3. package/dist/agent-skill.js +5 -5
  4. package/dist/analytics.js +9 -0
  5. package/dist/bin.js +201 -34
  6. package/dist/{chunk-FC7PZNYW.js → chunk-2UEUVLCB.js} +3 -3
  7. package/dist/{chunk-6DCCEVPD.js → chunk-5IO5LY22.js} +33 -11
  8. package/dist/{chunk-GGARVSPT.js → chunk-77SK7TKZ.js} +8894 -5663
  9. package/dist/{chunk-YMSGDC4B.js → chunk-CADA5WZ3.js} +28 -6
  10. package/dist/{chunk-HACTNBV3.js → chunk-F4FKAUQN.js} +3 -3
  11. package/dist/{chunk-SJVLAYGG.js → chunk-GOYBYAUH.js} +8 -1
  12. package/dist/chunk-HIF6NUNK.js +33 -0
  13. package/dist/{chunk-6CJ4ANMN.js → chunk-MFGJ2KPT.js} +1 -1
  14. package/dist/{chunk-OEGNGGZ2.js → chunk-NMHTFC5Q.js} +10 -3
  15. package/dist/{chunk-5Z5J6PY5.js → chunk-NZ65XDNL.js} +4 -4
  16. package/dist/{chunk-3RDJORRS.js → chunk-P6DOSGRZ.js} +1 -1
  17. package/dist/chunk-SFODN2YB.js +196 -0
  18. package/dist/chunk-UN24JFV2.js +44 -0
  19. package/dist/chunk-VXWLSQYK.js +130 -0
  20. package/dist/chunk-YYFC7TM2.js +286 -0
  21. package/dist/{chunk-KV6GSBTQ.js → chunk-Z6ZY7X2K.js} +5 -5
  22. package/dist/{chunk-JD5U4U26.js → chunk-ZHHIHHLU.js} +4 -4
  23. package/dist/cli-access.js +3 -3
  24. package/dist/connection.js +2 -2
  25. package/dist/first-survey.js +11 -0
  26. package/dist/footer.js +16 -0
  27. package/dist/global-cli.js +5 -5
  28. package/dist/integrate.js +4 -4
  29. package/dist/project.js +7 -3
  30. package/dist/registry.js +5 -3
  31. package/dist/setup-flow.js +24 -0
  32. package/dist/setup.js +32 -0
  33. package/dist/skills/userreason/SKILL.md +43 -0
  34. package/dist/skills/userreason/references/management.md +6 -0
  35. package/dist/startup.js +2 -2
  36. package/dist/state.js +5 -5
  37. package/dist/ui.js +2 -2
  38. package/package.json +1 -2
package/dist/bin.js CHANGED
@@ -3,21 +3,33 @@ import { createRequire as createWizardRequire } from 'node:module'; import { fil
3
3
  import {
4
4
  offerAgentNote,
5
5
  planAgentNote
6
- } from "./chunk-KV6GSBTQ.js";
7
- import {
8
- offerAgentSkill,
9
- planAgentSkill
10
- } from "./chunk-5Z5J6PY5.js";
6
+ } from "./chunk-Z6ZY7X2K.js";
11
7
  import {
12
8
  offerCliAccess
13
- } from "./chunk-HACTNBV3.js";
9
+ } from "./chunk-F4FKAUQN.js";
14
10
  import {
15
11
  offerGlobalCli
16
- } from "./chunk-JD5U4U26.js";
12
+ } from "./chunk-ZHHIHHLU.js";
13
+ import {
14
+ WizardAnalytics
15
+ } from "./chunk-UN24JFV2.js";
16
+ import {
17
+ beginFirstSurveyPreparation,
18
+ offerFirstSurvey
19
+ } from "./chunk-HIF6NUNK.js";
20
+ import {
21
+ runBrowserWizard
22
+ } from "./chunk-VXWLSQYK.js";
23
+ import "./chunk-YYFC7TM2.js";
24
+ import "./chunk-SFODN2YB.js";
25
+ import {
26
+ offerAgentSkill,
27
+ planAgentSkill
28
+ } from "./chunk-NZ65XDNL.js";
17
29
  import {
18
30
  ensureCurrentWizard,
19
31
  wizardVersion
20
- } from "./chunk-6CJ4ANMN.js";
32
+ } from "./chunk-MFGJ2KPT.js";
21
33
  import {
22
34
  Cancelled,
23
35
  Terminal,
@@ -25,26 +37,26 @@ import {
25
37
  clean,
26
38
  normalizeOrigin,
27
39
  openBrowser
28
- } from "./chunk-SJVLAYGG.js";
40
+ } from "./chunk-GOYBYAUH.js";
29
41
  import {
30
42
  BrowserConnectionUnavailable,
31
43
  compatibilityCredentials,
32
44
  connectInstallation,
33
45
  memoryCredentials
34
- } from "./chunk-3RDJORRS.js";
46
+ } from "./chunk-P6DOSGRZ.js";
35
47
  import {
36
48
  clearInstallation,
37
49
  resolveInstallation,
38
50
  saveInstallation,
39
51
  savedInstallation
40
- } from "./chunk-FC7PZNYW.js";
52
+ } from "./chunk-2UEUVLCB.js";
41
53
  import {
42
54
  applyIntegration,
43
55
  detectInstallation,
44
56
  integrationAdditions,
45
57
  integrationPreview,
46
58
  planIntegration
47
- } from "./chunk-6DCCEVPD.js";
59
+ } from "./chunk-5IO5LY22.js";
48
60
  import {
49
61
  SdkInstallError,
50
62
  cliCommand,
@@ -58,8 +70,8 @@ import {
58
70
  sdkInstallCommand,
59
71
  verifyConfiguration,
60
72
  writeIntegration
61
- } from "./chunk-YMSGDC4B.js";
62
- import "./chunk-OEGNGGZ2.js";
73
+ } from "./chunk-CADA5WZ3.js";
74
+ import "./chunk-NMHTFC5Q.js";
63
75
  import {
64
76
  ApiError,
65
77
  AuthenticationError,
@@ -67,13 +79,14 @@ import {
67
79
  apiUrl,
68
80
  credentialStoreKind,
69
81
  detectAgentHarness,
82
+ firstSurveyHandoff,
70
83
  login,
71
84
  packageProfile,
72
85
  resolveConfig,
73
86
  resolveCredentials,
74
87
  saveConfig,
75
88
  uuid
76
- } from "./chunk-GGARVSPT.js";
89
+ } from "./chunk-77SK7TKZ.js";
77
90
  import "./chunk-WL3NOIM6.js";
78
91
 
79
92
  // src/bin.ts
@@ -83,6 +96,9 @@ var help = `UserReason installation wizard
83
96
 
84
97
  userreason-wizard [--directory PATH] [--origin URL]
85
98
 
99
+ --browser-setup (explicit browser-guided setup; falls back when unavailable)
100
+ --resume-setup UUID (resume this project's saved browser setup)
101
+ --browser-origin ORIGIN (trusted dashboard origin for a custom API)
86
102
  --workspace UUID / --workspace-name NAME
87
103
  --site UUID / --site-name NAME
88
104
  --environment development|production (customer site environment)
@@ -101,6 +117,7 @@ userreason-wizard [--directory PATH] [--origin URL]
101
117
  --diff (show the full patch for the application edit and the proposed guidance text)
102
118
  --interactive (ask questions even when an agent harness or CI is detected)
103
119
  --no-color (also NO_COLOR)
120
+ --no-telemetry (also UR_TELEMETRY_DISABLED=1)
104
121
  --no-open (print the browser link without opening it)
105
122
  --session-only (keep compatibility login only in memory; do not persist credentials)
106
123
  --force (replace differing generated files)
@@ -118,6 +135,10 @@ Prompts are off when stdin or stdout is not a terminal, with --yes or --json, in
118
135
  var nameError = (value) => !value.trim() || value.trim().length > 240 ? "Enter a name between 1 and 240 characters." : void 0;
119
136
  var currentStep = "Reading wizard options";
120
137
  var activeTerminal;
138
+ var analytics;
139
+ var surveyPreparation;
140
+ var surveyAbort = new AbortController();
141
+ var measured = (step, operation) => analytics ? analytics.measure(step, operation) : operation();
121
142
  var frameworkLabel = (framework) => framework === "next" ? "Next.js" : framework;
122
143
  var lowerFirst = (value) => value.charAt(0).toLowerCase() + value.slice(1);
123
144
  async function main() {
@@ -127,7 +148,11 @@ async function main() {
127
148
  help: { type: "boolean", short: "h" },
128
149
  version: { type: "boolean", short: "v" },
129
150
  dev: { type: "boolean" },
151
+ "browser-setup": { type: "boolean" },
152
+ "resume-setup": { type: "string" },
153
+ "browser-origin": { type: "string" },
130
154
  "api-environment": { type: "string" },
155
+ "no-telemetry": { type: "boolean" },
131
156
  yes: { type: "boolean" },
132
157
  install: { type: "boolean" },
133
158
  refresh: { type: "boolean" },
@@ -173,6 +198,17 @@ async function main() {
173
198
  process.stdout.write(help);
174
199
  return;
175
200
  }
201
+ const browserMode = Boolean(values["browser-setup"] || values["resume-setup"]);
202
+ if (values["browser-setup"] && values["resume-setup"]) throw new Error("Choose --browser-setup or --resume-setup");
203
+ if (values["resume-setup"]) uuid(values["resume-setup"], "Setup ID");
204
+ if (values["browser-origin"]) {
205
+ if (!browserMode) throw new Error("--browser-origin requires browser setup");
206
+ apiUrl(values["browser-origin"], "Browser origin");
207
+ }
208
+ if (browserMode) {
209
+ const conflicts = ["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"];
210
+ if (conflicts.some((key) => values[key] !== void 0 && values[key] !== false)) throw new Error("Browser setup choices are reviewed in the browser. Omit terminal installation/resource flags; ongoing CLI access is a separate login.");
211
+ }
176
212
  if (values["credential-store"] !== void 0) credentialStoreKind(values["credential-store"]);
177
213
  if (values["api-environment"] !== void 0 && !["production", "development"].includes(values["api-environment"]))
178
214
  throw new Error("API environment must be development or production");
@@ -218,8 +254,25 @@ async function main() {
218
254
  const tty = Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.TERM !== "dumb");
219
255
  const explicitlyQuiet = Boolean(values.yes || values.json);
220
256
  const interactive = tty && !explicitlyQuiet && (!harness || Boolean(values.interactive));
221
- const terminal = new Terminal(interactive, Boolean(values.json), { color: !values["no-color"] });
257
+ const terminal = new Terminal(interactive, Boolean(values.json), { color: !values["no-color"], onCancel: () => {
258
+ surveyAbort.abort(new Cancelled());
259
+ surveyPreparation?.cancel();
260
+ if (browserMode) {
261
+ process.exitCode = 130;
262
+ return;
263
+ }
264
+ analytics?.finish(true);
265
+ process.exitCode = 130;
266
+ void (analytics?.flush(true) ?? Promise.resolve()).finally(() => process.exit(130));
267
+ } });
222
268
  activeTerminal = terminal;
269
+ try {
270
+ 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"] });
271
+ } catch {
272
+ }
273
+ if (!values.refresh) analytics?.configure({ context: { source_site_environment: newSiteEnvironment === "development" ? "development" : "production" } });
274
+ analytics?.begin("run");
275
+ await analytics?.checkpoint();
223
276
  const { c, S } = terminal.kit;
224
277
  const dot = ` ${S.dot} `;
225
278
  const environmentNote = developmentApi ? `development API${newSiteEnvironment !== apiEnvironment ? `, new sites are ${newSiteEnvironment}` : ""}` : newSiteEnvironment !== apiEnvironment ? `new sites are ${newSiteEnvironment}` : void 0;
@@ -239,8 +292,27 @@ async function main() {
239
292
  values.directory ?? process.cwd(),
240
293
  values.framework
241
294
  );
295
+ analytics?.configure({ wizard: { framework: project.framework, package_manager: project.packageManager } });
242
296
  terminal.info(`${clean(project.name)} ${c.muted(`(${frameworkLabel(project.framework)}, ${project.packageManager})`)}`);
297
+ if (browserMode) {
298
+ currentStep = values["resume-setup"] ? "Resuming browser setup" : "Connecting browser setup";
299
+ const interrupted = () => surveyAbort.abort(new Cancelled());
300
+ process.once("SIGINT", interrupted);
301
+ process.once("SIGTERM", interrupted);
302
+ terminal.cancelState = "the local runner stopped; browser approvals, applied changes and saved drafts remain available when you resume";
303
+ let handled;
304
+ try {
305
+ 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, noOpen: Boolean(values["no-open"]), json: Boolean(values.json), signal: surveyAbort.signal, measurementDisabled: values["no-telemetry"] });
306
+ } finally {
307
+ process.off("SIGINT", interrupted);
308
+ process.off("SIGTERM", interrupted);
309
+ }
310
+ if (handled) return;
311
+ terminal.cancelState = "nothing was written";
312
+ terminal.info("This server has not enabled browser setup. Continuing with the existing terminal wizard.");
313
+ }
243
314
  if (values.refresh) {
315
+ analytics?.configure({ wizard: { resumed: true } });
244
316
  currentStep = "Refreshing UserReason packages";
245
317
  const existing = await detectInstallation(project);
246
318
  if (!existing) throw new Error("No UserReason initialization found. Run the wizard without --refresh to set up this application.");
@@ -253,18 +325,49 @@ async function main() {
253
325
  const started = Date.now();
254
326
  await terminal.task("Updating UserReason packages", async () => {
255
327
  if (!await hasCompatibleSdk(project, profile) || values.install) {
256
- await installSdk(project, options);
328
+ await measured("sdk_install", () => installSdk(project, options));
257
329
  updated.push("@userreason/js");
258
330
  }
259
331
  if (!await hasCompatibleCli(project, profile) || values.install) {
260
- await installCli(project, options);
332
+ await measured("cli_install", () => installCli(project, options));
261
333
  updated.push("@userreason/cli");
262
334
  }
263
335
  }, () => 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" };
336
+ const refreshOption = async (step, requested, skipped, eligible, operation) => {
337
+ if (!requested) {
338
+ analytics?.step(step, "skipped", { selection_source: skipped ? "flag" : "default", reason: skipped ? "explicit_skip" : "not_requested" });
339
+ return operation();
340
+ }
341
+ if (await eligible().catch(() => false)) analytics?.step(step, "eligible");
342
+ analytics?.step(step, "selected", { selection_source: "flag" });
343
+ return measured(step, operation);
344
+ };
345
+ const globalCli2 = await refreshOption(
346
+ "global_cli",
347
+ values["global-cli"],
348
+ values["skip-global-cli"],
349
+ async () => true,
350
+ () => offerGlobalCli(project, terminal, { installed: true, requested: values["global-cli"], skip: !values["global-cli"], force: values.install, verbose: values.debug, profile, preview: Boolean(values.diff) })
351
+ );
352
+ const agentNote2 = await refreshOption(
353
+ "agent_note",
354
+ values["agent-note"],
355
+ values["skip-agent-note"],
356
+ async () => "path" in await planAgentNote(project),
357
+ () => offerAgentNote(project, terminal, { installed: true, requested: values["agent-note"], skip: !values["agent-note"], preview: Boolean(values.diff) })
358
+ );
359
+ const agentSkill2 = await refreshOption(
360
+ "agent_skill",
361
+ values["agent-skill"],
362
+ values["skip-agent-skill"],
363
+ async () => "files" in await planAgentSkill(project),
364
+ () => offerAgentSkill(project, terminal, { installed: true, requested: values["agent-skill"], skip: !values["agent-skill"], preview: Boolean(values.diff) })
365
+ );
366
+ const cliAccess2 = await refreshOption("cli_access", values["cli-access"], values["skip-cli-access"], async () => true, async () => {
367
+ if (!values["cli-access"]) return { status: "skipped" };
368
+ const accessConfig = await resolveConfig({ development: developmentApi, apiUrl: existing.apiBaseUrl, clientId: values["client-id"], credentialStore: values["credential-store"] });
369
+ return offerCliAccess(accessConfig, terminal, { installed: true, requested: true, noOpen: values["no-open"], development: developmentApi, preview: Boolean(values.diff) });
370
+ });
268
371
  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
372
  terminal.block("Ready", [
270
373
  c.muted("Reload or restart your app to load the updated SDK."),
@@ -334,6 +437,7 @@ async function main() {
334
437
  clientId: values["client-id"],
335
438
  credentialStore: values["credential-store"]
336
439
  });
440
+ analytics?.configure({ apiUrl: config.apiUrl });
337
441
  let resumeStorageBlocked = false;
338
442
  const warnResumeStorage = () => {
339
443
  if (resumeStorageBlocked) return;
@@ -350,12 +454,14 @@ async function main() {
350
454
  let workspaceChoice = values.workspace ? uuid(values.workspace) : saved?.workspaceId;
351
455
  const siteChoice = values.site ? uuid(values.site) : saved?.siteId;
352
456
  const resumed = Boolean(saved && !values.workspace && !values.site);
457
+ analytics?.configure({ wizard: { resumed } });
353
458
  if (siteChoice && !workspaceChoice) {
354
459
  currentStep = "Resolving the existing site";
355
460
  const origin = values.origin ? normalizeOrigin(values.origin, newSiteEnvironment === "development" ? "development" : "production") : void 0;
356
461
  workspaceChoice = (await resolveInstallation({ siteId: siteChoice, apiBaseUrl: config.apiUrl }, origin)).workspaceId;
357
462
  }
358
- let credentials = resolveCredentials(config), client = new UserReasonClient(config, credentials), connection;
463
+ const rawCredentials = resolveCredentials(config);
464
+ let credentials = analytics?.observeCredentials(rawCredentials) ?? rawCredentials, client = new UserReasonClient(config, credentials), connection;
359
465
  const open = async (url) => !values["no-open"] && await openBrowser(url);
360
466
  const approvalWait = async (label, url, note) => {
361
467
  const opened = await open(url);
@@ -373,6 +479,8 @@ async function main() {
373
479
  let waiting;
374
480
  let unrestrictedOrigins = false;
375
481
  currentStep = "Connecting browser sign-in and approval";
482
+ analytics?.begin("authentication");
483
+ await analytics?.checkpoint();
376
484
  try {
377
485
  connection = await connectInstallation(
378
486
  config,
@@ -399,7 +507,9 @@ async function main() {
399
507
  }
400
508
  );
401
509
  waiting?.stop();
402
- credentials = memoryCredentials(connection.session);
510
+ const temporaryCredentials = memoryCredentials(connection.session);
511
+ credentials = analytics?.observeCredentials(temporaryCredentials) ?? temporaryCredentials;
512
+ analytics?.setSession(connection.session);
403
513
  client = new UserReasonClient(config, credentials, {
404
514
  lock: (operation) => operation()
405
515
  });
@@ -419,7 +529,8 @@ async function main() {
419
529
  return terminal.confirm("Keep this session in memory only? No credentials will be saved.");
420
530
  }
421
531
  );
422
- credentials = compatibility.credentials;
532
+ credentials = analytics?.observeCredentials(compatibility.credentials) ?? compatibility.credentials;
533
+ analytics?.setSession(compatibility.session);
423
534
  client = new UserReasonClient(
424
535
  config,
425
536
  credentials,
@@ -471,6 +582,11 @@ async function main() {
471
582
  await deviceSignIn();
472
583
  account = await client.account();
473
584
  }
585
+ analytics?.end("authentication");
586
+ if (developmentApi && terminal.interactive && !values.yes && !values.json && !values.diff) {
587
+ surveyPreparation = beginFirstSurveyPreparation(project.directory, { development: developmentApi, interactive: terminal.interactive });
588
+ }
589
+ analytics?.begin("target_selection");
474
590
  const who = connection ? clean(connection.session.email) : void 0;
475
591
  let selectedWorkspace;
476
592
  const approvedWorkspace = connection?.workspaceId ?? workspaceChoice;
@@ -555,6 +671,8 @@ async function main() {
555
671
  siteId: selectedSite?.id ?? siteIds.siteId,
556
672
  apiBaseUrl: config.apiUrl
557
673
  };
674
+ analytics?.end("target_selection");
675
+ analytics?.configure({ context: { ...selectedWorkspace ? { workspace_id: selectedWorkspace.id } : {}, ...selectedSite ? { site_id: selectedSite.id } : {}, source_site_environment: selectedSite?.environment ?? environment } });
558
676
  currentStep = "Preparing the application integration";
559
677
  const reviewed = values["skip-integrate"] ? void 0 : await planIntegration(project, installation);
560
678
  const additions = reviewed ? integrationAdditions(reviewed) : void 0;
@@ -577,18 +695,37 @@ async function main() {
577
695
  ]);
578
696
  } else terminal.block("Changes", [reviewed?.status === "integrated" && !packages.length ? c.muted(`nothing to edit${dot}@userreason/js and @userreason/cli are current`) : installRow]);
579
697
  const extras = [];
698
+ if (developmentApi && interactive && !values.diff) extras.push({ value: "surveys", label: "Recommend surveys (share selected app code with Cerebras and OpenAI)", hint: "Optional. Suggestions first; drafts only when selected. No publishing or event edits.", initial: true });
580
699
  if (reviewed?.status === "ready" && additions) extras.push({ value: "edit", label: `${additions.created ? "Create" : "Edit"} ${additions.path}`, hint: "add UserReason.init", initial: true });
581
700
  if (noteProbe && "path" in noteProbe) extras.push({ value: "note", label: `${noteProbe.file} ${noteProbe.replacement ? "note update" : "note"}`, hint: "user-feedback guidance", initial: true });
582
701
  if (skillProbe && "files" in skillProbe) extras.push({ value: "skill", label: skillProbe.update ? "Agent skill update" : "Agent skill", hint: ".agents/skills/userreason", initial: true });
583
702
  if (!values["skip-cli-access"] && !values["session-only"]) extras.push({ value: "access", label: "Ongoing CLI access", hint: "second approval (90 days)", initial: true });
584
703
  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"]) });
704
+ for (const item of extras) {
705
+ if (item.value === "surveys") continue;
706
+ const step = { edit: "initialization", note: "agent_note", skill: "agent_skill", access: "cli_access", global: "global_cli" }[item.value];
707
+ analytics?.step(step, "eligible");
708
+ if (interactive) analytics?.step(step, "offered", { selection_source: "prompt" });
709
+ }
710
+ if (interactive && extras.length) analytics?.begin("options");
711
+ await analytics?.checkpoint();
585
712
  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;
713
+ if (interactive && extras.length) {
714
+ analytics?.end("options");
715
+ for (const item of extras) {
716
+ if (item.value === "surveys") continue;
717
+ const step = { edit: "initialization", note: "agent_note", skill: "agent_skill", access: "cli_access", global: "global_cli" }[item.value];
718
+ analytics?.step(step, picked?.has(item.value) ? "selected" : "declined", { selection_source: "prompt" });
719
+ }
720
+ }
586
721
  const integrate = Boolean(reviewed?.status === "ready" && (picked ? picked.has("edit") : values.integrate || values.yes));
587
722
  const choice = (key, flag, skipFlag) => picked ? { requested: picked.has(key), skip: !picked.has(key) } : { requested: Boolean(values[flag]), skip: skipFlag };
723
+ 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
724
  const noteChoice = choice("note", "agent-note", Boolean(values["skip-agent-note"]));
589
725
  const skillChoice = choice("skill", "agent-skill", Boolean(values["skip-agent-skill"]));
590
726
  const globalChoice = choice("global", "global-cli", Boolean(values["skip-global-cli"] || values["skip-install"]));
591
727
  const accessChoice = choice("access", "cli-access", Boolean(values["skip-cli-access"] || values["session-only"]));
728
+ 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
729
  terminal.cancelState = "some steps are done; rerun the wizard to continue";
593
730
  if (!selectedWorkspace) {
594
731
  currentStep = "Creating the workspace";
@@ -625,6 +762,7 @@ async function main() {
625
762
  );
626
763
  terminal.success(`Created site ${c.code(clean(selectedSite.name))}`);
627
764
  }
765
+ analytics?.configure({ context: { workspace_id: selectedWorkspace.id, site_id: selectedSite.id, source_site_environment: selectedSite.environment } });
628
766
  currentStep = "Saving optional installation resume details";
629
767
  if (!await saveInstallation(project.directory, installation)) warnResumeStorage();
630
768
  currentStep = "Preparing initialization files";
@@ -645,9 +783,10 @@ async function main() {
645
783
  await terminal.task("Installing @userreason/js, @userreason/cli", async () => {
646
784
  if (shouldInstall) {
647
785
  try {
648
- await installSdk(project, { profile, verbose: values.debug, onWarning: (message) => {
786
+ const operation = () => installSdk(project, { profile, verbose: values.debug, onWarning: (message) => {
649
787
  warnings.push(message);
650
788
  } });
789
+ await measured("sdk_install", operation);
651
790
  sdkInstalled = true;
652
791
  installed.push("@userreason/js");
653
792
  } catch (error) {
@@ -658,9 +797,10 @@ async function main() {
658
797
  }
659
798
  if (shouldInstallCli) {
660
799
  try {
661
- await installCli(project, { profile, verbose: values.debug, onWarning: (message) => {
800
+ const operation = () => installCli(project, { profile, verbose: values.debug, onWarning: (message) => {
662
801
  warnings.push(message);
663
802
  } });
803
+ await measured("cli_install", operation);
664
804
  cliInstalled = true;
665
805
  installed.push("@userreason/cli");
666
806
  } catch (error) {
@@ -684,16 +824,17 @@ Then rerun the wizard. Your saved workspace and site will be reused.`, failure.t
684
824
  const concrete = await planIntegration(project, installation);
685
825
  if (reviewed.status !== "ready" || concrete.status !== "ready" || concrete.path !== reviewed.path || concrete.before !== reviewed.before || concrete.after !== reviewed.after)
686
826
  throw new Error("The application entry changed after the preview. Rerun to review it again.");
687
- await applyIntegration(concrete);
827
+ await measured("initialization", () => applyIntegration(concrete));
688
828
  if (concrete.before === null) generated.files.push(concrete.path);
689
829
  terminal.success(`${concrete.before === null ? "Created" : "Edited"} ${c.code(additions?.path ?? "")}`);
690
830
  }
831
+ if (existingPlan?.status === "integrated") analytics?.step("initialization", "already_present");
691
832
  const integrated = Boolean(integrate && !installFailed || existingPlan?.status === "integrated");
692
833
  if (!integrated && !installFailed) terminal.note(generated.instruction, "Application integration");
693
834
  currentStep = "Checking site configuration";
694
835
  const verification = await terminal.task(
695
836
  "Checking site configuration",
696
- () => verifyConfiguration(installation, hostOrigin ?? "https://installation.userreason.invalid"),
837
+ () => analytics ? analytics.measure("configuration_check", () => verifyConfiguration(installation, hostOrigin ?? "https://installation.userreason.invalid"), "completed") : verifyConfiguration(installation, hostOrigin ?? "https://installation.userreason.invalid"),
697
838
  "Site configuration reachable"
698
839
  );
699
840
  if (integrated && sdkInstalled && !installFailed) {
@@ -705,13 +846,30 @@ Then rerun the wizard. Your saved workspace and site will be reused.`, failure.t
705
846
  if (picked && !completed && !installFailed && (picked.has("note") || picked.has("skill")))
706
847
  terminal.warn("The note and skill wait for an initialized SDK. Rerun with the entry edit selected, or apply the initialization yourself first.");
707
848
  currentStep = "Offering the optional user feedback note";
708
- const agentNote = await offerAgentNote(project, terminal, { installed: completed, ...noteChoice, preview: Boolean(values.diff) });
849
+ const agentNote = await measured("agent_note", () => offerAgentNote(project, terminal, { installed: completed, ...noteChoice, preview: Boolean(values.diff) }));
709
850
  currentStep = "Offering the optional feedback agent skill";
710
- const agentSkill = await offerAgentSkill(project, terminal, { installed: completed, ...skillChoice, preview: Boolean(values.diff) });
851
+ const agentSkill = await measured("agent_skill", () => offerAgentSkill(project, terminal, { installed: completed, ...skillChoice, preview: Boolean(values.diff) }));
711
852
  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) });
853
+ 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
854
  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) });
855
+ const cliAccess = await measured("cli_access", () => offerCliAccess(config, terminal, { installed: cliInstalled && !installFailed, ...accessChoice, noOpen: values["no-open"], development: developmentApi, preview: Boolean(values.diff) }));
856
+ if (completed && terminal.interactive && !values.yes && !values.json && !values.diff) {
857
+ try {
858
+ currentStep = "Creating the first survey";
859
+ await offerFirstSurvey(new UserReasonClient(config, resolveCredentials(config)), project.directory, selectedWorkspace.id, selectedSite.id, {
860
+ show: (message) => terminal.note(message, developmentApi ? "Recommended surveys" : "First survey"),
861
+ confirm: (message) => terminal.confirm(message, false),
862
+ choose: (message, options, initialValue) => terminal.select(message, options, initialValue),
863
+ answer: (message) => terminal.text(message),
864
+ chooseMany: (message, options, initial) => terminal.multiselect(message, options, initial),
865
+ loading: (message, work) => terminal.task(message, work, () => void 0),
866
+ signal: surveyAbort.signal
867
+ }, { development: developmentApi, interactive: terminal.interactive, product: selectedSite.name, preparation: surveyPreparation, sourceConsent: picked?.has("surveys") });
868
+ } catch (error) {
869
+ 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.");
870
+ terminal.note(firstSurveyHandoff(project.directory, config.apiUrl, selectedWorkspace.id, selectedSite.id), "Resume with your agent");
871
+ }
872
+ }
715
873
  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
874
  const siteLine = `${clean(selectedWorkspace.name)} ${S.arrow} ${clean(selectedSite.name)} ${c.muted(`(${siteDetail(selectedSite)})`)}`;
717
875
  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 +907,16 @@ var approvalProgress;
749
907
  main().catch((error) => {
750
908
  const detail = error instanceof Error ? error.message : "Installation failed";
751
909
  if (error instanceof Cancelled) {
752
- if (activeTerminal) activeTerminal.cancelled();
910
+ analytics?.finish(true);
911
+ if (activeTerminal) {
912
+ activeTerminal.cancelled();
913
+ return;
914
+ }
753
915
  process.stderr.write(detail + "\n");
754
916
  process.exitCode = 130;
755
917
  return;
756
918
  }
919
+ analytics?.finish(false, true);
757
920
  const message = `${currentStep}: ${detail}`;
758
921
  if (process.argv.includes("--json"))
759
922
  process.stdout.write(JSON.stringify({ error: "wizard_failed", step: currentStep, message }) + "\n");
@@ -761,4 +924,8 @@ main().catch((error) => {
761
924
  activeTerminal.error(clean(detail), [`while ${lowerFirst(currentStep)}`]);
762
925
  else process.stderr.write(message + "\n");
763
926
  process.exitCode = 1;
927
+ }).finally(async () => {
928
+ surveyPreparation?.cancel();
929
+ analytics?.finish(false, Boolean(process.exitCode));
930
+ await analytics?.flush(true);
764
931
  });
@@ -1,15 +1,15 @@
1
1
  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);
2
2
  import {
3
3
  detectInstallation
4
- } from "./chunk-6DCCEVPD.js";
4
+ } from "./chunk-5IO5LY22.js";
5
5
  import {
6
6
  DEFAULT_API
7
- } from "./chunk-YMSGDC4B.js";
7
+ } from "./chunk-CADA5WZ3.js";
8
8
  import {
9
9
  apiUrl,
10
10
  configDirectory,
11
11
  uuid
12
- } from "./chunk-GGARVSPT.js";
12
+ } from "./chunk-77SK7TKZ.js";
13
13
 
14
14
  // src/state.ts
15
15
  import { mkdir, readFile, writeFile, rename, rm, realpath, lstat } from "node:fs/promises";
@@ -3,10 +3,10 @@ import {
3
3
  DEFAULT_API,
4
4
  initializationSource,
5
5
  integrationSource
6
- } from "./chunk-YMSGDC4B.js";
6
+ } from "./chunk-CADA5WZ3.js";
7
7
  import {
8
8
  require_typescript
9
- } from "./chunk-GGARVSPT.js";
9
+ } from "./chunk-77SK7TKZ.js";
10
10
  import {
11
11
  __toESM
12
12
  } from "./chunk-WL3NOIM6.js";
@@ -673,8 +673,26 @@ function reactRoots(ast) {
673
673
  if (!creationCount && renders.length > 1) return [];
674
674
  return renders.flatMap((node) => node.arguments[0] ? [node.arguments[0]] : []);
675
675
  }
676
- function existing(text, config = dummy) {
677
- const ast = parse("entry.tsx", text), calls = ast ? sdkCalls(ast) : [];
676
+ function integrationReferences(ast) {
677
+ const modules = nodes(ast, import_typescript.default.isStringLiteral).filter((literal) => {
678
+ const parent = literal.parent;
679
+ return (import_typescript.default.isImportDeclaration(parent) || import_typescript.default.isExportDeclaration(parent)) && parent.moduleSpecifier === literal || import_typescript.default.isExternalModuleReference(parent) && parent.expression === literal || import_typescript.default.isCallExpression(parent) && parent.arguments[0] === literal && (parent.expression.kind === import_typescript.default.SyntaxKind.ImportKeyword || import_typescript.default.isIdentifier(parent.expression) && parent.expression.text === "require");
680
+ }).map((literal) => literal.text);
681
+ const tags = nodes(ast, (node) => import_typescript.default.isJsxOpeningElement(node) || import_typescript.default.isJsxSelfClosingElement(node)).map((node) => node.tagName.getText(ast));
682
+ const globalAccess = nodes(ast, import_typescript.default.isElementAccessExpression).some((node) => import_typescript.default.isIdentifier(node.expression) && ["window", "globalThis", "self", "global"].includes(node.expression.text) && import_typescript.default.isStringLiteralLike(node.argumentExpression) && node.argumentExpression.text === "UserReason");
683
+ const identifier = nodes(ast, import_typescript.default.isIdentifier).some((node) => {
684
+ if (node.text !== "UserReason") return false;
685
+ const parent = node.parent;
686
+ if ((import_typescript.default.isPropertyAssignment(parent) || import_typescript.default.isPropertyDeclaration(parent) || import_typescript.default.isPropertySignature(parent) || import_typescript.default.isMethodDeclaration(parent) || import_typescript.default.isMethodSignature(parent) || import_typescript.default.isGetAccessorDeclaration(parent) || import_typescript.default.isSetAccessorDeclaration(parent) || import_typescript.default.isJsxAttribute(parent)) && parent.name === node) return false;
687
+ if ((import_typescript.default.isBindingElement(parent) || import_typescript.default.isImportSpecifier(parent)) && parent.propertyName === node) return false;
688
+ if (import_typescript.default.isExportSpecifier(parent) && parent.name === node && parent.propertyName) return false;
689
+ if (import_typescript.default.isLabeledStatement(parent) || import_typescript.default.isBreakStatement(parent) || import_typescript.default.isContinueStatement(parent)) return false;
690
+ return true;
691
+ });
692
+ return { modules, tags, identifier: identifier || globalAccess };
693
+ }
694
+ function existing(text, config = dummy, path = "entry.tsx") {
695
+ const ast = parse(path, text), calls = ast ? sdkCalls(ast) : [];
678
696
  if (calls.length) {
679
697
  try {
680
698
  let first;
@@ -690,11 +708,13 @@ function existing(text, config = dummy) {
690
708
  }
691
709
  return { status: "integrated", reason: "SDK initialization is already present; its configuration is preserved." };
692
710
  }
693
- if (/userreason\.generated/.test(text) && /<UserReasonProvider\s*\/?\s*>/.test(text))
711
+ if (!ast) return;
712
+ const references = integrationReferences(ast);
713
+ if (references.modules.some((name) => /userreason\.generated/.test(name)) && references.tags.includes("UserReasonProvider"))
694
714
  return { status: "integrated", reason: "Existing generated initialization is already present." };
695
- if (/userreason\.client/.test(text) && /<UserReasonInit\s*\/?\s*>/.test(text))
715
+ if (references.modules.some((name) => /userreason\.client/.test(name)) && references.tags.includes("UserReasonInit"))
696
716
  return { status: "integrated", reason: "Browser initialization is already present." };
697
- if (/\bUserReason\b|userreason\.generated|from\s*['"]@userreason\/js['"]/.test(text))
717
+ if (references.identifier || references.modules.some((name) => name === "@userreason/js" || /userreason\.(?:generated|client)/.test(name)))
698
718
  return { status: "manual", reason: "An existing UserReason integration needs review before editing the entry." };
699
719
  }
700
720
  async function planIntegration(project, config = dummy) {
@@ -716,11 +736,13 @@ async function planIntegration(project, config = dummy) {
716
736
  const found2 = files.filter((value) => value.text !== void 0);
717
737
  if (found2.length > 1) return { status: "manual", reason: "Multiple instrumentation-client files need review." };
718
738
  for (const root of ["app/layout.tsx", "app/layout.jsx", "app/layout.js", "pages/_app.tsx", "pages/_app.jsx", "pages/_app.js"]) {
719
- const rootText = await source(project, join(directory, root));
720
- if (rootText && /userreason\.(?:generated|client)/.test(rootText)) return existing(rootText) ?? { status: "manual", reason: "Existing root initialization needs review." };
739
+ const rootPath = join(directory, root), rootText = await source(project, rootPath);
740
+ const rootAst = rootText ? parse(rootPath, rootText) : void 0;
741
+ if (rootText && rootAst && integrationReferences(rootAst).modules.some((name2) => /userreason\.(?:generated|client)/.test(name2)))
742
+ return existing(rootText, dummy, rootPath) ?? { status: "manual", reason: "Existing root initialization needs review." };
721
743
  }
722
744
  const { path: path2, text: text2 } = found2[0] ?? files[project.typescript ? 0 : 1];
723
- const current2 = text2 ? existing(text2, config) : void 0;
745
+ const current2 = text2 ? existing(text2, config, path2) : void 0;
724
746
  if (current2) return current2;
725
747
  const ast = parse(path2, text2 ?? "");
726
748
  if (!ast) return { status: "manual", reason: "Client instrumentation could not be parsed safely." };
@@ -776,7 +798,7 @@ ${initializationSource(config)}`),
776
798
  };
777
799
  const { path, text } = found[0];
778
800
  const name = project.framework === "next" ? "UserReasonInit" : "UserReason";
779
- const current = existing(text, config);
801
+ const current = existing(text, config, path);
780
802
  if (current) return current;
781
803
  let after;
782
804
  {