@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
@@ -1,10 +1,10 @@
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
  configurePackageRegistry
4
- } from "./chunk-OEGNGGZ2.js";
4
+ } from "./chunk-NMHTFC5Q.js";
5
5
  import {
6
6
  packageProfile
7
- } from "./chunk-GGARVSPT.js";
7
+ } from "./chunk-77SK7TKZ.js";
8
8
 
9
9
  // src/project.ts
10
10
  import { lstat, readFile, writeFile, access, mkdtemp, realpath, rm } from "node:fs/promises";
@@ -140,7 +140,7 @@ async function hasCompatibleSdk(project, profile = packageProfile()) {
140
140
  try {
141
141
  const manifest = JSON.parse(await readFile(join(project.directory, "package.json"), "utf8"));
142
142
  const version = await installedVersion(project, "@userreason/js");
143
- return Boolean(manifest.dependencies?.["@userreason/js"] && (profile.kind === "local-development" ? version === profile.sdkVersion : /^0\.4\.\d+$/.test(version)));
143
+ return Boolean(manifest.dependencies?.["@userreason/js"] && (profile.kind === "local-development" ? version === profile.sdkVersion : compatibleProductionVersion(version, profile.sdkVersion)));
144
144
  } catch {
145
145
  return false;
146
146
  }
@@ -157,8 +157,13 @@ async function hasCompatibleCli(project, profile = packageProfile()) {
157
157
  return false;
158
158
  }
159
159
  }
160
+ function compatibleProductionVersion(version, requested) {
161
+ const installed = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
162
+ const target = /^(\d+)\.(\d+)\.(\d+)$/.exec(requested.replace(/^[~^]/, ""));
163
+ return Boolean(installed && target && installed[1] === target[1] && installed[2] === target[2] && Number(installed[3]) >= Number(target[3]));
164
+ }
160
165
  function compatibleCliVersion(version, profile) {
161
- return profile.kind === "local-development" ? version === profile.cliVersion : /^0\.1\.\d+$/.test(version) && Number(version.split(".")[2]) >= 1;
166
+ return profile.kind === "local-development" ? version === profile.cliVersion : compatibleProductionVersion(version, profile.cliVersion);
162
167
  }
163
168
  function cliInstallCommand(project, profile = packageProfile()) {
164
169
  return sdkInstallCommand(project, profile).replace("@userreason/js@" + profile.sdkVersion, "--save-dev @userreason/cli@" + profile.cliVersion).replace("yarn add --mode=skip-build --save-dev", "yarn add --mode=skip-build --dev").replace("yarn add --ignore-scripts --save-dev", "yarn add --ignore-scripts --dev");
@@ -177,10 +182,11 @@ async function installDependency(project, dependency, options) {
177
182
  const spec = dependency === "sdk" ? `@userreason/js@${profile.sdkVersion}` : `@userreason/cli@${profile.cliVersion}`;
178
183
  const args = project.packageManager === "npm" ? ["install", "--ignore-scripts", spec] : project.packageManager === "pnpm" ? ["add", "--ignore-scripts", spec] : (project.yarnMajor ?? 1) >= 2 ? ["add", "--mode=skip-build", spec] : ["add", "--ignore-scripts", spec];
179
184
  if (dependency === "cli") args.push(project.packageManager === "yarn" ? "--dev" : "--save-dev");
180
- if (profile.kind === "local-development") args.push(project.packageManager === "yarn" ? "--exact" : "--save-exact");
185
+ if (profile.kind === "local-development" || options.exact) args.push(project.packageManager === "yarn" ? "--exact" : "--save-exact");
181
186
  let cache;
182
187
  try {
183
- await configurePackageRegistry(project, profile);
188
+ options.signal?.throwIfAborted();
189
+ if (options.configureRegistry !== false) await configurePackageRegistry(project, profile);
184
190
  if (project.packageManager === "npm") {
185
191
  cache = await mkdtemp(join(await realpath(project.directory), ".userreason-npm-"));
186
192
  args.push("--cache", cache);
@@ -197,6 +203,14 @@ async function installDependency(project, dependency, options) {
197
203
  ...cache ? { npm_config_cache: cache, NPM_CONFIG_CACHE: cache } : {}
198
204
  }
199
205
  });
206
+ let killTimer;
207
+ const abort = () => {
208
+ child.kill("SIGTERM");
209
+ killTimer = setTimeout(() => child.kill("SIGKILL"), 2e3);
210
+ killTimer.unref();
211
+ };
212
+ options.signal?.addEventListener("abort", abort, { once: true });
213
+ if (options.signal?.aborted) abort();
200
214
  let logs = "";
201
215
  const capture = (chunk) => {
202
216
  const text = chunk.toString();
@@ -207,6 +221,12 @@ async function installDependency(project, dependency, options) {
207
221
  child.stderr?.on("data", capture);
208
222
  child.on("error", () => reject(new SdkInstallError(`Unable to start ${project.packageManager}.`)));
209
223
  child.on("close", (code) => {
224
+ options.signal?.removeEventListener("abort", abort);
225
+ if (killTimer) clearTimeout(killTimer);
226
+ if (options.signal?.aborted) {
227
+ reject(options.signal.reason);
228
+ return;
229
+ }
210
230
  if (code === 0) {
211
231
  if (/\b[1-9]\d*\s+vulnerabilit(?:y|ies)\b|\bWARN\b|\bwarning\b/i.test(logs))
212
232
  options.onWarning?.(
@@ -266,10 +286,12 @@ export {
266
286
  initializationSource,
267
287
  integrationSource,
268
288
  writeIntegration,
289
+ installedVersion,
269
290
  cliCommand,
270
291
  hasCompatibleSdk,
271
292
  sdkInstallCommand,
272
293
  hasCompatibleCli,
294
+ compatibleProductionVersion,
273
295
  compatibleCliVersion,
274
296
  cliInstallCommand,
275
297
  SdkInstallError,
@@ -2,14 +2,14 @@ import { createRequire as createWizardRequire } from 'node:module'; import { fil
2
2
  import {
3
3
  Cancelled,
4
4
  openBrowser
5
- } from "./chunk-SJVLAYGG.js";
5
+ } from "./chunk-GOYBYAUH.js";
6
6
  import {
7
7
  authorizeCli,
8
8
  grantSummary,
9
9
  printable,
10
10
  resolveCredentials,
11
11
  saveConfig
12
- } from "./chunk-GGARVSPT.js";
12
+ } from "./chunk-77SK7TKZ.js";
13
13
 
14
14
  // src/cli-access.ts
15
15
  async function offerCliAccess(config, terminal, options) {
@@ -39,7 +39,7 @@ async function offerCliAccess(config, terminal, options) {
39
39
  return { status: "approved", grant };
40
40
  } catch (error) {
41
41
  waiting?.stop();
42
- if (error instanceof Cancelled) return { status: "declined" };
42
+ if (error instanceof Cancelled) return { status: "declined", reason: "user_cancelled" };
43
43
  if (error instanceof Error) terminal.warn(printable(error.message));
44
44
  terminal.warn("The application is installed, but ongoing CLI access was not connected. Run the project CLI with login --open to finish.");
45
45
  return { status: "failed" };
@@ -7,7 +7,7 @@ import {
7
7
  plainLine,
8
8
  stripAnsi,
9
9
  textWidth
10
- } from "./chunk-GGARVSPT.js";
10
+ } from "./chunk-77SK7TKZ.js";
11
11
  import {
12
12
  __commonJS,
13
13
  __toESM
@@ -1235,6 +1235,7 @@ function approvalProgressMessage(progress) {
1235
1235
  var Cancelled = class extends Error {
1236
1236
  constructor() {
1237
1237
  super("Installation cancelled.");
1238
+ this.name = "Cancelled";
1238
1239
  }
1239
1240
  };
1240
1241
  var clean = (value) => value.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\x00-\x09\x0b-\x1f\x7f-\x9f]/g, "");
@@ -1279,6 +1280,7 @@ var Terminal = class {
1279
1280
  constructor(interactive, json, options = {}) {
1280
1281
  this.interactive = interactive;
1281
1282
  this.json = json;
1283
+ this.onCancel = options.onCancel;
1282
1284
  this.kit = createUiKit({ level: detectColorLevel({ stream: process.stdout, off: options.color === false }) });
1283
1285
  }
1284
1286
  interactive;
@@ -1288,6 +1290,7 @@ var Terminal = class {
1288
1290
  cancelState = "nothing was written";
1289
1291
  /** True after output that leaves no guide line behind, so the next event adds one. */
1290
1292
  gap = false;
1293
+ onCancel;
1291
1294
  header(options) {
1292
1295
  const lines = this.kit.header({ ...options, trademark: this.interactive });
1293
1296
  if (this.interactive) for (const line of lines) process.stdout.write(cleanStyled(line) + "\n");
@@ -1373,6 +1376,10 @@ var Terminal = class {
1373
1376
  cancelled(afterSpinner = false) {
1374
1377
  if (this.interactive && afterSpinner) process.stdout.write("\n");
1375
1378
  this.error(`Cancelled ${this.kit.c.muted(`${this.kit.S.dot} ${this.cancelState}`)}`);
1379
+ if (this.onCancel) {
1380
+ this.onCancel();
1381
+ return;
1382
+ }
1376
1383
  process.exit(130);
1377
1384
  }
1378
1385
  async text(message, initialValue, validate) {
@@ -0,0 +1,33 @@
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
+ import {
3
+ firstSurvey,
4
+ firstSurveyHandoff,
5
+ prepareFirstSurveyLocally,
6
+ recommendedSurveys
7
+ } from "./chunk-77SK7TKZ.js";
8
+
9
+ // src/first-survey.ts
10
+ function beginFirstSurveyPreparation(root, mode, prepare = prepareFirstSurveyLocally) {
11
+ return mode.development && mode.interactive && !mode.preview ? prepare(root) : void 0;
12
+ }
13
+ async function offerFirstSurvey(client, root, workspace, site, ui, mode) {
14
+ if (!mode.interactive || mode.preview) return { state: "skipped" };
15
+ if (mode.development) return recommendedSurveys(client, root, workspace, site, ui, mode);
16
+ const choice = await ui.choose(
17
+ "Find your first useful survey? Nothing changes or publishes automatically.",
18
+ [
19
+ { value: "guided", label: "Review suggestions" },
20
+ { value: "agent", label: "Continue with my coding agent" },
21
+ { value: "finish", label: "Finish setup" }
22
+ ],
23
+ "finish"
24
+ );
25
+ if (choice === "agent") ui.show(firstSurveyHandoff(root, client.config.apiUrl, workspace, site));
26
+ if (choice === "guided") return firstSurvey(client, root, workspace, site, ui);
27
+ return { state: "skipped" };
28
+ }
29
+
30
+ export {
31
+ beginFirstSurveyPreparation,
32
+ offerFirstSurvey
33
+ };
@@ -1,7 +1,7 @@
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
  currentPackageProfile
4
- } from "./chunk-GGARVSPT.js";
4
+ } from "./chunk-77SK7TKZ.js";
5
5
 
6
6
  // src/startup.ts
7
7
  import { readFile } from "node:fs/promises";
@@ -7342,8 +7342,8 @@ import { lstat, readFile, open } from "node:fs/promises";
7342
7342
  import { join } from "node:path";
7343
7343
  import { execFile } from "node:child_process";
7344
7344
  import { promisify } from "node:util";
7345
- async function configurePackageRegistry(project, profile) {
7346
- if (profile.kind !== "local-development") return;
7345
+ async function planPackageRegistry(project, profile) {
7346
+ if (profile.kind !== "local-development") return null;
7347
7347
  const modernYarn = project.packageManager === "yarn" && (project.yarnMajor ?? 1) >= 2;
7348
7348
  const path = join(project.directory, modernYarn ? ".yarnrc.yml" : ".npmrc");
7349
7349
  let before = "", metadata;
@@ -7382,7 +7382,13 @@ async function configurePackageRegistry(project, profile) {
7382
7382
  after = configured === profile.registry ? before : `${before}${before && !before.endsWith("\n") ? "\n" : ""}${line}
7383
7383
  `;
7384
7384
  }
7385
- if (after === before) return;
7385
+ return { path, before: metadata ? before : null, after };
7386
+ }
7387
+ async function configurePackageRegistry(project, profile) {
7388
+ const plan = await planPackageRegistry(project, profile);
7389
+ if (!plan || plan.after === plan.before) return;
7390
+ const { path, after } = plan, before = plan.before ?? "";
7391
+ const metadata = plan.before === null ? null : await lstat(path);
7386
7392
  const handle = await open(path, metadata ? constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0) : "wx", 420);
7387
7393
  try {
7388
7394
  if (metadata) {
@@ -7399,5 +7405,6 @@ async function configurePackageRegistry(project, profile) {
7399
7405
 
7400
7406
  export {
7401
7407
  require_dist,
7408
+ planPackageRegistry,
7402
7409
  configurePackageRegistry
7403
7410
  };
@@ -1,13 +1,13 @@
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
  Cancelled
4
- } from "./chunk-SJVLAYGG.js";
4
+ } from "./chunk-GOYBYAUH.js";
5
5
  import {
6
6
  cliCommand
7
- } from "./chunk-YMSGDC4B.js";
7
+ } from "./chunk-CADA5WZ3.js";
8
8
  import {
9
9
  require_dist
10
- } from "./chunk-OEGNGGZ2.js";
10
+ } from "./chunk-NMHTFC5Q.js";
11
11
  import {
12
12
  __toESM
13
13
  } from "./chunk-WL3NOIM6.js";
@@ -245,7 +245,7 @@ Agents can propose feedback investigations, read the current documentation and u
245
245
  terminal.success("Feedback skill installed in .agents/skills/userreason");
246
246
  return { status: "added", path: ".agents/skills/userreason" };
247
247
  } catch (error) {
248
- if (error instanceof Cancelled) return { status: "declined" };
248
+ if (error instanceof Cancelled) return { status: "declined", reason: "user_cancelled" };
249
249
  terminal.warn("Installation is complete. The optional agent skill could not be installed; the skill directory needs review.");
250
250
  return { status: "failed", reason: "skill_directory_needs_review" };
251
251
  }
@@ -4,7 +4,7 @@ import {
4
4
  object,
5
5
  string,
6
6
  uuid
7
- } from "./chunk-GGARVSPT.js";
7
+ } from "./chunk-77SK7TKZ.js";
8
8
 
9
9
  // src/connection.ts
10
10
  import { createHash, randomBytes } from "node:crypto";
@@ -0,0 +1,196 @@
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
+ import {
3
+ applyIntegration
4
+ } from "./chunk-5IO5LY22.js";
5
+ import {
6
+ require_typescript
7
+ } from "./chunk-77SK7TKZ.js";
8
+ import {
9
+ __toESM
10
+ } from "./chunk-WL3NOIM6.js";
11
+
12
+ // src/footer.ts
13
+ var import_typescript = __toESM(require_typescript(), 1);
14
+ import { createHash } from "node:crypto";
15
+ import { lstat, readFile } from "node:fs/promises";
16
+ import { dirname, extname, join, relative, resolve } from "node:path";
17
+ var FOOTER_LINK = '<a href="https://userreason.com/" data-userreason-footer="v1">Powered by UserReason</a>';
18
+ var digest = (text) => createHash("sha256").update(text).digest("hex");
19
+ async function source(root, path, overlay) {
20
+ const local = relative(root, path);
21
+ if (!local || local.startsWith("..") || local.includes("\\")) return;
22
+ let current = root;
23
+ for (const part of local.split("/")) {
24
+ current = join(current, part);
25
+ let info2;
26
+ try {
27
+ info2 = await lstat(current);
28
+ } catch (error) {
29
+ if (error.code === "ENOENT") return;
30
+ throw error;
31
+ }
32
+ if (info2.isSymbolicLink()) return;
33
+ }
34
+ const info = await lstat(path);
35
+ if (!info.isFile() || info.nlink !== 1 || info.size > 1e6) return;
36
+ return overlay?.get(path) ?? readFile(path, "utf8");
37
+ }
38
+ function parse(path, text) {
39
+ const ast = import_typescript.default.createSourceFile(path, text, import_typescript.default.ScriptTarget.Latest, true, /\.(?:jsx|tsx|js)$/.test(path) ? import_typescript.default.ScriptKind.TSX : import_typescript.default.ScriptKind.TS);
40
+ return ast.parseDiagnostics?.length ? void 0 : ast;
41
+ }
42
+ function jsxFooters(path, text) {
43
+ const ast = parse(path, text);
44
+ if (!ast) return [];
45
+ const result = [];
46
+ const visit = (node) => {
47
+ if (import_typescript.default.isJsxElement(node) && node.openingElement.tagName.getText(ast) === "footer") {
48
+ result.push({ path, source: text, start: node.getStart(ast), insertion: node.closingElement.getStart(ast), end: node.end });
49
+ }
50
+ import_typescript.default.forEachChild(node, visit);
51
+ };
52
+ visit(ast);
53
+ return result;
54
+ }
55
+ function htmlFooters(path, text, start = 0, stop = text.length) {
56
+ const result = [], stack = [];
57
+ let at = start;
58
+ while (at < stop) {
59
+ const open = text.indexOf("<", at);
60
+ if (open < 0 || open >= stop) break;
61
+ if (text.startsWith("<!--", open)) {
62
+ const close = text.indexOf("-->", open + 4);
63
+ if (close < 0) return [];
64
+ at = close + 3;
65
+ continue;
66
+ }
67
+ const name = /^<\s*(\/?)\s*([A-Za-z][A-Za-z0-9:-]*)\b/.exec(text.slice(open));
68
+ if (!name) {
69
+ at = open + 1;
70
+ continue;
71
+ }
72
+ let end = open + name[0].length, quote = "";
73
+ for (; end < stop; end++) {
74
+ const char = text[end];
75
+ if (quote) {
76
+ if (char === quote) quote = "";
77
+ } else if (char === '"' || char === "'") quote = char;
78
+ else if (char === ">") break;
79
+ }
80
+ if (end >= stop) return [];
81
+ const tag = name[2].toLowerCase(), closing = Boolean(name[1]), self = /\/\s*$/.test(text.slice(open, end));
82
+ if (!closing && ["script", "style", "textarea"].includes(tag)) {
83
+ const tail = new RegExp(`<\\/\\s*${tag}\\s*>`, "ig");
84
+ tail.lastIndex = end + 1;
85
+ const match = tail.exec(text);
86
+ if (!match) return [];
87
+ at = tail.lastIndex;
88
+ continue;
89
+ }
90
+ if (tag === "footer") {
91
+ if (self) return [];
92
+ if (!closing) stack.push(open);
93
+ else {
94
+ const begin = stack.pop();
95
+ if (begin === void 0) return [];
96
+ result.push({ path, source: text, start: begin, insertion: open, end: end + 1 });
97
+ }
98
+ }
99
+ at = end + 1;
100
+ }
101
+ return stack.length ? [] : result;
102
+ }
103
+ function vueParts(text) {
104
+ const starts = [...text.matchAll(/<template(?:\s[^>]*|)>/g)];
105
+ const first = starts[0];
106
+ const end = text.lastIndexOf("</template>");
107
+ return {
108
+ template: first && end > first.index ? { start: first.index + first[0].length, end } : void 0,
109
+ scripts: [...text.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/g)].map((match) => match[1])
110
+ };
111
+ }
112
+ async function imports(root, path, text, overlay) {
113
+ const parts = extname(path) === ".vue" ? vueParts(text) : void 0;
114
+ const script = parts ? parts.scripts.join("\n") : text;
115
+ const ast = parse(path + ".tsx", script);
116
+ if (!ast) return [];
117
+ const result = [];
118
+ for (const statement of ast.statements) {
119
+ if (!import_typescript.default.isImportDeclaration(statement) || !import_typescript.default.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith(".")) continue;
120
+ const bindings = [statement.importClause?.name?.text, ...statement.importClause?.namedBindings && import_typescript.default.isNamedImports(statement.importClause.namedBindings) ? statement.importClause.namedBindings.elements.map((item) => item.name.text) : []].filter((name) => Boolean(name));
121
+ const body = script.slice(0, statement.pos) + script.slice(statement.end) + (parts?.template ? text.slice(parts.template.start, parts.template.end) : "");
122
+ if (!bindings.some((name) => new RegExp(`(?:<|\\b)${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/>.(),])`).test(body))) continue;
123
+ const base = resolve(dirname(path), statement.moduleSpecifier.text);
124
+ for (const candidate of [base, ...[".tsx", ".jsx", ".ts", ".js", ".vue"].map((extension) => base + extension), ...["index.tsx", "index.jsx", "index.ts", "index.js", "index.vue"].map((file) => join(base, file))]) {
125
+ if (await source(root, candidate, overlay) !== void 0) {
126
+ result.push(candidate);
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+ async function planFooter(project, overlay) {
134
+ const root = resolve(project.directory);
135
+ const entries = project.framework === "vanilla" ? ["index.html"] : project.framework === "next" ? ["app/layout.tsx", "app/layout.jsx", "app/layout.js", "src/app/layout.tsx", "src/app/layout.jsx", "src/app/layout.js", "pages/_app.tsx", "pages/_app.jsx", "src/pages/_app.tsx", "src/pages/_app.jsx"] : project.framework === "vue" ? ["src/main.ts", "src/main.js", "main.ts", "main.js"] : ["src/main.tsx", "src/main.jsx", "src/index.tsx", "src/index.jsx", "src/index.js", "src/main.js", "index.jsx"];
136
+ const queue = [];
137
+ for (const file of entries) {
138
+ const path = join(root, file), text = await source(root, path, overlay);
139
+ if (text !== void 0) queue.push({ path, source: text });
140
+ }
141
+ if (queue.length !== 1) return { status: "manual", code: "unsupported_footer", candidates: [] };
142
+ const seen = /* @__PURE__ */ new Set(), candidates = [];
143
+ while (queue.length) {
144
+ const file = queue.shift();
145
+ if (seen.has(file.path)) continue;
146
+ if (seen.size >= 80) return { status: "manual", code: "unsupported_footer", candidates: [] };
147
+ seen.add(file.path);
148
+ if (extname(file.path) === ".html") candidates.push(...htmlFooters(file.path, file.source));
149
+ else if (extname(file.path) === ".vue") {
150
+ const range = vueParts(file.source).template;
151
+ if (range) candidates.push(...htmlFooters(file.path, file.source, range.start, range.end));
152
+ } else candidates.push(...jsxFooters(file.path, file.source));
153
+ for (const path of await imports(root, file.path, file.source, overlay)) {
154
+ const text = await source(root, path, overlay);
155
+ if (text !== void 0) queue.push({ path, source: text });
156
+ }
157
+ }
158
+ const listed = candidates.map((candidate2) => ({ path: relative(root, candidate2.path), line: candidate2.source.slice(0, candidate2.start).split("\n").length }));
159
+ const attribution = /<a\b[^>]*href\s*=\s*["']https:\/\/(?:www\.)?userreason\.com\/?["'][^>]*>\s*Powered by UserReason\s*<\/a\s*>/i;
160
+ const marked = candidates.filter((candidate2) => attribution.test(candidate2.source.slice(candidate2.start, candidate2.end)));
161
+ if (marked.length === 1) {
162
+ const candidate2 = marked[0];
163
+ return { status: "already_present", directory: root, path: candidate2.path, before: candidate2.source, after: candidate2.source, anchorHash: digest(candidate2.source.slice(candidate2.start, candidate2.end)) };
164
+ }
165
+ if (candidates.length !== 1) return { status: "manual", code: candidates.length ? "ambiguous_footer" : "missing_footer", candidates: listed };
166
+ const candidate = candidates[0];
167
+ const existing = candidate.source.slice(candidate.start, candidate.end);
168
+ if (attribution.test(existing))
169
+ return { status: "already_present", directory: root, path: candidate.path, before: candidate.source, after: candidate.source, anchorHash: digest(existing) };
170
+ const line = candidate.source.slice(0, candidate.insertion).split("\n").at(-1) ?? "";
171
+ const indent = /^\s*/.exec(line)?.[0] ?? "";
172
+ const insertion = `
173
+ ${indent} ${FOOTER_LINK}
174
+ ${indent}`;
175
+ return {
176
+ status: "ready",
177
+ directory: root,
178
+ path: candidate.path,
179
+ before: candidate.source,
180
+ after: candidate.source.slice(0, candidate.insertion) + insertion + candidate.source.slice(candidate.insertion),
181
+ anchorHash: digest(existing)
182
+ };
183
+ }
184
+ async function applyFooter(plan) {
185
+ if (plan.status === "already_present") {
186
+ if (await source(plan.directory, plan.path) !== plan.before) throw new Error("Footer changed after review");
187
+ return;
188
+ }
189
+ await applyIntegration({ ...plan, status: "ready", description: "Add UserReason attribution to the existing page footer" });
190
+ }
191
+
192
+ export {
193
+ FOOTER_LINK,
194
+ planFooter,
195
+ applyFooter
196
+ };
@@ -0,0 +1,44 @@
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
+ import {
3
+ TELEMETRY_REASONS,
4
+ ToolTelemetry
5
+ } from "./chunk-77SK7TKZ.js";
6
+
7
+ // src/analytics.ts
8
+ var WizardAnalytics = class extends ToolTelemetry {
9
+ starts = /* @__PURE__ */ new Map();
10
+ begin(step) {
11
+ this.starts.set(step, performance.now());
12
+ this.step(step, "started");
13
+ }
14
+ end(step, phase = "completed") {
15
+ const start = this.starts.get(step);
16
+ this.step(step, phase, start === void 0 ? {} : { duration_ms: performance.now() - start });
17
+ this.starts.delete(step);
18
+ }
19
+ async measure(step, operation, phase = "applied") {
20
+ const start = performance.now();
21
+ this.begin(step);
22
+ await this.checkpoint();
23
+ try {
24
+ const value = await operation();
25
+ const result = value && typeof value === "object" ? value : void 0;
26
+ const outcome = result?.reason === "user_cancelled" ? "cancelled" : result?.status === "already_present" ? "already_present" : result?.status === "declined" ? "declined" : result?.status === "skipped" ? "skipped" : result?.status === "failed" ? "failed" : phase;
27
+ const reason = result?.reason && TELEMETRY_REASONS.includes(result.reason) ? result.reason : result?.reason?.includes("review") ? "validation_failed" : result?.reason?.includes("preserved") || result?.reason === "existing_feedback_section" ? "existing_content_preserved" : outcome === "failed" ? "operation_failed" : outcome === "skipped" ? "unsupported" : void 0;
28
+ this.step(step, outcome, { duration_ms: performance.now() - start, ...reason ? { reason } : {}, ...result?.file === "AGENTS.md" || result?.file === "CLAUDE.md" ? { guidance_file: result.file } : {} });
29
+ this.starts.delete(step);
30
+ return value;
31
+ } catch (error) {
32
+ this.step(step, error instanceof Error && error.name === "Cancelled" ? "cancelled" : "failed", { duration_ms: performance.now() - start, reason: error instanceof Error && error.name === "Cancelled" ? "user_cancelled" : "operation_failed" });
33
+ this.starts.delete(step);
34
+ throw error;
35
+ }
36
+ }
37
+ finish(cancelled = false, failed = false) {
38
+ for (const step of [...this.starts.keys()].reverse()) this.end(step, cancelled ? "cancelled" : failed ? "failed" : "completed");
39
+ }
40
+ };
41
+
42
+ export {
43
+ WizardAnalytics
44
+ };
@@ -0,0 +1,130 @@
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
+ import {
3
+ connectSetup,
4
+ resumeSetup,
5
+ runSetup
6
+ } from "./chunk-YYFC7TM2.js";
7
+ import {
8
+ clean,
9
+ openBrowser
10
+ } from "./chunk-GOYBYAUH.js";
11
+ import {
12
+ BrowserSetupUnavailable
13
+ } from "./chunk-77SK7TKZ.js";
14
+
15
+ // src/setup-flow.ts
16
+ var shell = (value) => /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
17
+ function setupSampleCommand(setupId, apiOrigin, cliVersion) {
18
+ return ["npx", "-y", `@userreason/cli@${cliVersion}`, "setup", "sample", "--setup", setupId, "--api-url", apiOrigin, "--directory", ".", "--json"].map(shell).join(" ");
19
+ }
20
+ function setupResumeCommand(setupId, apiOrigin, version) {
21
+ return ["npx", "-y", `@userreason/wizard@${version}`, "--resume-setup", setupId, "--api-url", apiOrigin, "--directory", "."].map(shell).join(" ");
22
+ }
23
+ var setupManualGuidance = {
24
+ install_packages: "Review the project dependency and registry settings, install compatible @userreason/js and @userreason/cli versions with the project package manager, then rerun setup. See https://docs.userreason.com/get-started/quickstart/.",
25
+ initialize_sdk: "Identify the existing browser entry and initialize UserReason once with the selected site\u2019s public ID, preserving any existing initialization. See https://docs.userreason.com/sdk/initialization/.",
26
+ install_skill: "Review the existing agent guidance and merge the UserReason skill manually from the installed @userreason/cli skills/userreason directory. Preserve your existing edits and do not overwrite a customized skill.",
27
+ insert_footer: "Identify the intended existing site footer and manually add a Powered by UserReason link to https://userreason.com/. Preserve its content and layout; resolve ambiguous placement before editing."
28
+ };
29
+ function setupSummary(session) {
30
+ return {
31
+ setup_id: session.setup_id,
32
+ phase: session.phase,
33
+ workspace_id: session.workspace_id,
34
+ site_id: session.site_id,
35
+ local_operations: session.commands.map((command) => ({ kind: command.kind, status: command.status, code: command.result?.code ?? null })),
36
+ manual_steps: session.local_plan?.manual_steps ?? [],
37
+ source: session.source?.status ?? "not_started",
38
+ drafts: session.draft_outcomes.map((draft) => ({ candidate_id: draft.candidate_id, prompt_id: draft.prompt_id, status: draft.status, revision: draft.revision })),
39
+ sample: session.sample.status,
40
+ cli_access: "not_requested"
41
+ };
42
+ }
43
+ async function runBrowserWizard(input) {
44
+ const { terminal, project, apiOrigin, signal } = input;
45
+ let connected;
46
+ try {
47
+ connected = input.resumeId ? await resumeSetup(apiOrigin, input.resumeId, project, { signal, measurementDisabled: input.measurementDisabled }) : await connectSetup(apiOrigin, project, async (url, code) => {
48
+ terminal.block("Continue in your browser", [url, `Verification code: ${code}`, "Review the project, local changes and source permissions there."]);
49
+ if (!input.noOpen && !await openBrowser(url)) terminal.info("Open the link above to continue.");
50
+ }, { signal, browserOrigin: input.browserOrigin, measurementDisabled: input.measurementDisabled });
51
+ } catch (error) {
52
+ if (!input.resumeId && error instanceof BrowserSetupUnavailable) return false;
53
+ throw error;
54
+ }
55
+ const resume = setupResumeCommand(connected.client.identity.setup_id, apiOrigin, input.version);
56
+ terminal.block("Setup connected", [`Setup: ${connected.client.identity.setup_id}`, "Keep this terminal open. The browser controls approvals, draft choices and cancellation.", `To reconnect from this project: ${resume}`]);
57
+ let status = "", displayedPacket = "";
58
+ const shownManual = /* @__PURE__ */ new Set();
59
+ try {
60
+ const result = await runSetup(connected.client, project, {
61
+ signal,
62
+ onSession(session) {
63
+ for (const step of session.local_plan?.manual_steps ?? []) {
64
+ const key = `${step.kind}:${step.code}`;
65
+ if (shownManual.has(key)) continue;
66
+ shownManual.add(key);
67
+ terminal.block(`Manual step: ${step.kind}`, [setupManualGuidance[step.kind]]);
68
+ }
69
+ const next = `${session.phase}:${session.source?.status ?? ""}:${session.sample.status}`;
70
+ if (next === status) return;
71
+ status = next;
72
+ const messages = {
73
+ configuring: "Choose the workspace, site and installation options in the browser.",
74
+ awaiting_local_approval: "Review and approve the exact local changes in the browser.",
75
+ applying: "Applying browser-approved local changes.",
76
+ ready_for_recommendations: session.source?.status === "pending" ? "Review source files and provider details in the browser before any source upload." : "Continue with recommendations or skip that step in the browser.",
77
+ analyzing: "Analyzing the source packet approved in the browser.",
78
+ selecting_drafts: "Review recommendations and select drafts in the browser.",
79
+ creating_drafts: "Creating only the drafts selected in the browser.",
80
+ agent_test: session.sample.status === "received" ? "Simulated sample received. Finish or skip in the browser." : "Complete or skip the optional agent test in the browser.",
81
+ complete: "Browser setup completed.",
82
+ cancelled: "Browser setup cancelled. Applied changes and created drafts are listed below.",
83
+ expired: "Browser setup expired. Applied changes and created drafts are listed below."
84
+ };
85
+ terminal.info(messages[session.phase]);
86
+ },
87
+ onSourceIssue(code) {
88
+ terminal.warn(`Source analysis needs review (${clean(code)}). Check the browser status, then retry source review or skip it. Changed local source requires fresh approval.`);
89
+ },
90
+ onSourcePrepared(packet) {
91
+ if (displayedPacket === packet.manifest.packet_digest) return;
92
+ displayedPacket = packet.manifest.packet_digest;
93
+ terminal.block("Local source review", [
94
+ "The exact bounded excerpts below stay local until you approve this packet and its providers in the browser.",
95
+ `Packet SHA-256: ${packet.manifest.packet_digest}`,
96
+ ...packet.evidence.flatMap((item) => [`${clean(item.path)}:${item.start_line}-${item.end_line} (file SHA-256 ${item.file_hash})`, ...item.excerpt.split("\n").map((line) => `> ${clean(line)}`)]),
97
+ `Omitted files: ${packet.manifest.omitted_files}. Truncated: ${packet.manifest.truncated ? "yes" : "no"}. Git history is excluded.`
98
+ ]);
99
+ },
100
+ onSample(session) {
101
+ terminal.block("Try your agent", ["Ask: What do UserReason dummy respondents think?", "Tell your agent to read this setup\u2019s simulated sample, using:", setupSampleCommand(session.setup_id, apiOrigin, input.profile.cliVersion), "This is example data. It does not read production responses or grant ongoing CLI access."]);
102
+ }
103
+ });
104
+ const summary = setupSummary(result);
105
+ terminal.block("Setup result", [
106
+ `Status: ${result.phase}`,
107
+ ...summary.local_operations.map((operation) => `${operation.kind}: ${operation.status}${operation.code ? ` (${operation.code})` : ""}`),
108
+ ...summary.manual_steps.map((step) => `${step.kind}: manual (${step.code})`),
109
+ ...summary.drafts.map((draft) => `Draft ${draft.prompt_id}: ${draft.status}${draft.revision ? ` (revision ${draft.revision})` : ""}`),
110
+ `Simulated sample: ${summary.sample}`,
111
+ "Ongoing CLI access was not requested. Use a separate `userreason login --open` when you want to authorize it."
112
+ ]);
113
+ if (input.json) process.stdout.write(`${JSON.stringify(summary)}
114
+ `);
115
+ if (result.phase === "cancelled") process.exitCode = 130;
116
+ else if (result.phase !== "complete") process.exitCode = 1;
117
+ return true;
118
+ } catch (error) {
119
+ terminal.info(`Resume from this project: ${resume}`);
120
+ throw error;
121
+ }
122
+ }
123
+
124
+ export {
125
+ setupSampleCommand,
126
+ setupResumeCommand,
127
+ setupManualGuidance,
128
+ setupSummary,
129
+ runBrowserWizard
130
+ };