uidex 0.3.0 → 0.5.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 (44) hide show
  1. package/dist/cli/cli.cjs +1116 -112
  2. package/dist/cli/cli.cjs.map +1 -1
  3. package/dist/cloud/index.cjs +395 -72
  4. package/dist/cloud/index.cjs.map +1 -1
  5. package/dist/cloud/index.d.cts +60 -86
  6. package/dist/cloud/index.d.ts +60 -86
  7. package/dist/cloud/index.js +396 -71
  8. package/dist/cloud/index.js.map +1 -1
  9. package/dist/headless/index.cjs +1505 -791
  10. package/dist/headless/index.cjs.map +1 -1
  11. package/dist/headless/index.d.cts +83 -75
  12. package/dist/headless/index.d.ts +83 -75
  13. package/dist/headless/index.js +1514 -791
  14. package/dist/headless/index.js.map +1 -1
  15. package/dist/index.cjs +6281 -3190
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +337 -229
  18. package/dist/index.d.ts +337 -229
  19. package/dist/index.js +6362 -3231
  20. package/dist/index.js.map +1 -1
  21. package/dist/playwright/index.cjs +4 -4
  22. package/dist/playwright/index.cjs.map +1 -1
  23. package/dist/playwright/index.js +3 -3
  24. package/dist/playwright/index.js.map +1 -1
  25. package/dist/playwright/reporter.cjs +3 -3
  26. package/dist/playwright/reporter.cjs.map +1 -1
  27. package/dist/playwright/reporter.js +3 -3
  28. package/dist/playwright/reporter.js.map +1 -1
  29. package/dist/react/index.cjs +6291 -3206
  30. package/dist/react/index.cjs.map +1 -1
  31. package/dist/react/index.d.cts +239 -186
  32. package/dist/react/index.d.ts +239 -186
  33. package/dist/react/index.js +6338 -3208
  34. package/dist/react/index.js.map +1 -1
  35. package/dist/scan/index.cjs +212 -82
  36. package/dist/scan/index.cjs.map +1 -1
  37. package/dist/scan/index.d.cts +31 -0
  38. package/dist/scan/index.d.ts +31 -0
  39. package/dist/scan/index.js +211 -81
  40. package/dist/scan/index.js.map +1 -1
  41. package/package.json +10 -8
  42. package/templates/claude/api.md +110 -0
  43. package/templates/claude/audit.md +8 -2
  44. package/templates/claude/rules.md +15 -0
package/dist/cli/cli.cjs CHANGED
@@ -23,18 +23,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
- // src/scan/cli.ts
26
+ // src/scanner/scan/cli.ts
27
27
  var fs7 = __toESM(require("fs"), 1);
28
- var path8 = __toESM(require("path"), 1);
28
+ var path9 = __toESM(require("path"), 1);
29
29
 
30
- // src/scan/ai/index.ts
30
+ // src/scanner/scan/ai/index.ts
31
31
  var p = __toESM(require("@clack/prompts"), 1);
32
32
 
33
- // src/scan/ai/providers/claude.ts
33
+ // src/scanner/scan/ai/providers/claude.ts
34
34
  var fs2 = __toESM(require("fs"), 1);
35
35
  var path2 = __toESM(require("path"), 1);
36
36
 
37
- // src/scan/ai/templates.ts
37
+ // src/scanner/scan/ai/templates.ts
38
38
  var fs = __toESM(require("fs"), 1);
39
39
  var path = __toESM(require("path"), 1);
40
40
  function templatePath(rel) {
@@ -61,15 +61,16 @@ function readTemplate(rel) {
61
61
  return fs.readFileSync(templatePath(rel), "utf8");
62
62
  }
63
63
 
64
- // src/scan/ai/providers/claude.ts
64
+ // src/scanner/scan/ai/providers/claude.ts
65
65
  var CLAUDE_FILES = [
66
66
  { dest: ".claude/rules/uidex.md", template: "claude/rules.md" },
67
- { dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" }
67
+ { dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" },
68
+ { dest: ".claude/commands/uidex/api.md", template: "claude/api.md" }
68
69
  ];
69
70
  var claudeProvider = {
70
71
  id: "claude",
71
72
  label: "Claude Code",
72
- description: "Adds .claude/rules/uidex.md and the /uidex:audit slash command.",
73
+ description: "Adds .claude/rules/uidex.md, /uidex:audit, and /uidex:api slash commands.",
73
74
  async install({ cwd, force }) {
74
75
  const changes = [];
75
76
  for (const file of CLAUDE_FILES) {
@@ -117,16 +118,16 @@ function cleanupEmpty(dir) {
117
118
  }
118
119
  }
119
120
 
120
- // src/scan/ai/providers/index.ts
121
+ // src/scanner/scan/ai/providers/index.ts
121
122
  var PROVIDERS = [claudeProvider];
122
123
  function getProvider(id) {
123
124
  return PROVIDERS.find((p2) => p2.id === id);
124
125
  }
125
126
 
126
- // src/scan/ai/index.ts
127
+ // src/scanner/scan/ai/index.ts
127
128
  async function runAiCommand(opts) {
128
- const { cwd, argv } = opts;
129
- const sub = argv[0];
129
+ const { cwd, argv: argv2 } = opts;
130
+ const sub = argv2[0];
130
131
  if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
131
132
  return out(0, helpText());
132
133
  }
@@ -141,18 +142,18 @@ async function runAiCommand(opts) {
141
142
 
142
143
  ${helpText()}`);
143
144
  }
144
- const flags = parseFlags(argv.slice(1));
145
+ const flags = parseFlags(argv2.slice(1));
145
146
  const provider = await selectProvider(opts, flags.provider);
146
147
  if (!provider) return out(0, "Cancelled.\n");
147
148
  if (sub === "install") {
148
- const result2 = await provider.install({
149
+ const result3 = await provider.install({
149
150
  cwd,
150
151
  force: flags.force === true
151
152
  });
152
- return out(0, formatChanges(provider, "Installed", result2));
153
+ return out(0, formatChanges(provider, "Installed", result3));
153
154
  }
154
- const result = await provider.uninstall({ cwd });
155
- return out(0, formatChanges(provider, "Uninstalled", result));
155
+ const result2 = await provider.uninstall({ cwd });
156
+ return out(0, formatChanges(provider, "Uninstalled", result2));
156
157
  }
157
158
  async function selectProvider(opts, explicit) {
158
159
  if (explicit) {
@@ -195,9 +196,9 @@ function parseFlags(args) {
195
196
  }
196
197
  return flags;
197
198
  }
198
- function formatChanges(provider, verb, result) {
199
+ function formatChanges(provider, verb, result2) {
199
200
  const lines = [`${verb} ${provider.label}:`];
200
- for (const c of result.changes) lines.push(` ${describe(c)}`);
201
+ for (const c of result2.changes) lines.push(` ${describe(c)}`);
201
202
  return lines.join("\n") + "\n";
202
203
  }
203
204
  function describe(c) {
@@ -234,12 +235,16 @@ function err(exitCode, stderr) {
234
235
  return { exitCode, stdout: "", stderr };
235
236
  }
236
237
 
237
- // src/scan/discover.ts
238
+ // src/scanner/scan/discover.ts
238
239
  var fs3 = __toESM(require("fs"), 1);
239
240
  var path3 = __toESM(require("path"), 1);
240
241
 
241
- // src/scan/config.ts
242
+ // src/scanner/scan/config.ts
242
243
  var DEFAULT_TYPE_MODE = "strict";
244
+ var WELL_KNOWN_FILES = {
245
+ page: "uidex.page.ts",
246
+ feature: "uidex.feature.ts"
247
+ };
243
248
  var ConfigError = class extends Error {
244
249
  constructor(message) {
245
250
  super(message);
@@ -277,14 +282,14 @@ var ALLOWED_AUDIT_KEYS = /* @__PURE__ */ new Set(["scopeLeak", "coverage", "acce
277
282
  function fail(msg) {
278
283
  throw new ConfigError(`Invalid .uidex.json: ${msg}`);
279
284
  }
280
- function assertObject(value, path9) {
285
+ function assertObject(value, path10) {
281
286
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
282
- fail(`${path9} must be an object`);
287
+ fail(`${path10} must be an object`);
283
288
  }
284
289
  }
285
- function assertStringArray(value, path9) {
290
+ function assertStringArray(value, path10) {
286
291
  if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
287
- fail(`${path9} must be a string[]`);
292
+ fail(`${path10} must be a string[]`);
288
293
  }
289
294
  }
290
295
  function validateConfig(raw) {
@@ -398,7 +403,7 @@ var DEFAULT_CONVENTIONS = {
398
403
  regions: "landmarks"
399
404
  };
400
405
 
401
- // src/scan/discover.ts
406
+ // src/scanner/scan/discover.ts
402
407
  var CONFIG_FILENAME = ".uidex.json";
403
408
  var SKIP_DIRS = /* @__PURE__ */ new Set([
404
409
  "node_modules",
@@ -457,11 +462,14 @@ function discover(options = {}) {
457
462
  return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
458
463
  }
459
464
 
460
- // src/scan/pipeline.ts
465
+ // src/scanner/scan/pipeline.ts
461
466
  var fs5 = __toESM(require("fs"), 1);
462
- var path6 = __toESM(require("path"), 1);
467
+ var path7 = __toESM(require("path"), 1);
468
+
469
+ // src/scanner/scan/audit.ts
470
+ var path4 = __toESM(require("path"), 1);
463
471
 
464
- // src/entities/types.ts
472
+ // src/shared/entities/types.ts
465
473
  var ENTITY_KINDS = [
466
474
  "route",
467
475
  "page",
@@ -494,7 +502,7 @@ function assertEntityKind(kind) {
494
502
  if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
495
503
  }
496
504
 
497
- // src/entities/registry.ts
505
+ // src/shared/entities/registry.ts
498
506
  function emptyStore() {
499
507
  return {
500
508
  route: /* @__PURE__ */ new Map(),
@@ -558,11 +566,11 @@ function createRegistry() {
558
566
  };
559
567
  const query = (predicate) => {
560
568
  const flows = getFlows();
561
- const result = [];
569
+ const result2 = [];
562
570
  for (const entity of allEntities()) {
563
- if (predicate(entity)) result.push(freezeEntity(entity, flows));
571
+ if (predicate(entity)) result2.push(freezeEntity(entity, flows));
564
572
  }
565
- return result;
573
+ return result2;
566
574
  };
567
575
  const byScope = (scope) => query(
568
576
  (entity) => "scopes" in entity && Array.isArray(entity.scopes) && entity.scopes.includes(scope)
@@ -576,10 +584,33 @@ function createRegistry() {
576
584
  return ids.has(entity.id);
577
585
  });
578
586
  };
579
- return { add, get, list, query, byScope, touchedBy };
587
+ const reports = /* @__PURE__ */ new Map();
588
+ const reportsCbs = /* @__PURE__ */ new Set();
589
+ const setReports = (kind, id, records) => {
590
+ reports.set(`${kind}:${id}`, records);
591
+ for (const cb of reportsCbs) cb();
592
+ };
593
+ const getReports = (kind, id) => reports.get(`${kind}:${id}`) ?? [];
594
+ const listReportKeys = () => Array.from(reports.keys());
595
+ const onReportsChange = (cb) => {
596
+ reportsCbs.add(cb);
597
+ return () => reportsCbs.delete(cb);
598
+ };
599
+ return {
600
+ add,
601
+ get,
602
+ list,
603
+ query,
604
+ byScope,
605
+ touchedBy,
606
+ setReports,
607
+ getReports,
608
+ listReportKeys,
609
+ onReportsChange
610
+ };
580
611
  }
581
612
 
582
- // src/scan/audit.ts
613
+ // src/scanner/scan/audit.ts
583
614
  var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
584
615
  function audit(opts) {
585
616
  const diagnostics = [];
@@ -681,6 +712,32 @@ function audit(opts) {
681
712
  }
682
713
  }
683
714
  }
715
+ if (lint) {
716
+ const scannedPaths = new Set(files.map((f) => f.displayPath));
717
+ for (const ef of extracted) {
718
+ if (!ef.metadata) continue;
719
+ for (const m of ef.metadata) {
720
+ if (m.kind !== "page" && m.kind !== "feature") continue;
721
+ if (typeof m.id !== "string") continue;
722
+ const filePath = ef.file.displayPath;
723
+ const wellKnownName = WELL_KNOWN_FILES[m.kind];
724
+ if (path4.posix.basename(filePath) === wellKnownName) continue;
725
+ const dir = path4.posix.dirname(filePath);
726
+ const wellKnownPath = dir === "." ? wellKnownName : `${dir}/${wellKnownName}`;
727
+ if (scannedPaths.has(wellKnownPath)) continue;
728
+ const kindLabel = m.kind === "page" ? "Page" : "Feature";
729
+ diagnostics.push({
730
+ code: "prefer-well-known-file",
731
+ severity: "info",
732
+ message: `${kindLabel} "${m.id}" metadata lives on ${filePath}; prefer ${wellKnownPath}`,
733
+ file: filePath,
734
+ line: m.loc.line,
735
+ entity: { kind: m.kind, id: m.id },
736
+ hint: `Move the \`export const uidex\` block to ${wellKnownPath} and remove it from ${filePath}.`
737
+ });
738
+ }
739
+ }
740
+ }
684
741
  if (lint) {
685
742
  for (const f of files) {
686
743
  const lines = f.content.split("\n");
@@ -707,6 +764,15 @@ function audit(opts) {
707
764
  const primitives = registry.list("primitive");
708
765
  const byName = /* @__PURE__ */ new Map();
709
766
  for (const p2 of primitives) byName.set(p2.id, p2);
767
+ const declaredFeatures = /* @__PURE__ */ new Map();
768
+ for (const ef of extracted) {
769
+ if (!ef.metadata) continue;
770
+ for (const m of ef.metadata) {
771
+ if (m.features && m.features.length > 0) {
772
+ declaredFeatures.set(ef.file.displayPath, new Set(m.features));
773
+ }
774
+ }
775
+ }
710
776
  for (const f of files) {
711
777
  const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
712
778
  let m;
@@ -717,18 +783,23 @@ function audit(opts) {
717
783
  baseName2.replace(/\.(tsx|ts|jsx|js|mjs|cjs)$/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
718
784
  );
719
785
  if (!primitive) continue;
720
- const scope = primitive.scopes?.[0] ?? "global";
721
- if (scope === "global") continue;
786
+ const scope = primitive.scopes?.[0];
787
+ if (!scope) continue;
722
788
  const [kind, id] = scope.split(":");
723
789
  const importerSegments = f.displayPath.split("/");
724
- if (!importerSegments.includes(id) || !importerSegments.includes(kind + "s")) {
725
- diagnostics.push({
726
- code: "scope-leak",
727
- severity: "warning",
728
- message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
729
- file: f.displayPath
730
- });
790
+ if (importerSegments.includes(id) && importerSegments.includes(kind + "s")) {
791
+ continue;
792
+ }
793
+ if (kind === "feature" && importerSegments.includes(id)) continue;
794
+ if (kind === "feature" && declaredFeatures.get(f.displayPath)?.has(id)) {
795
+ continue;
731
796
  }
797
+ diagnostics.push({
798
+ code: "scope-leak",
799
+ severity: "warning",
800
+ message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
801
+ file: f.displayPath
802
+ });
732
803
  }
733
804
  }
734
805
  }
@@ -902,7 +973,7 @@ function stableReplacer(_key, value) {
902
973
  return value;
903
974
  }
904
975
 
905
- // src/scan/emit.ts
976
+ // src/scanner/scan/emit.ts
906
977
  function sortById(arr) {
907
978
  return [...arr].sort((a, b) => a.id.localeCompare(b.id));
908
979
  }
@@ -1026,6 +1097,7 @@ function emit(opts) {
1026
1097
  lines.push(" export interface Feature {");
1027
1098
  lines.push(" feature: FeatureId | false");
1028
1099
  lines.push(" name?: string");
1100
+ lines.push(" features?: readonly FeatureId[]");
1029
1101
  lines.push(" acceptance?: readonly string[]");
1030
1102
  lines.push(" description?: string");
1031
1103
  lines.push(" }");
@@ -1082,7 +1154,7 @@ function emit(opts) {
1082
1154
  return lines.join("\n");
1083
1155
  }
1084
1156
 
1085
- // src/scan/extract-uidex-export.ts
1157
+ // src/scanner/scan/extract-uidex-export.ts
1086
1158
  var KIND_DISCRIMINATORS = [
1087
1159
  "page",
1088
1160
  "feature",
@@ -1100,7 +1172,13 @@ var ALLOWED_FIELDS = {
1100
1172
  "acceptance",
1101
1173
  "description"
1102
1174
  ]),
1103
- feature: /* @__PURE__ */ new Set(["feature", "name", "acceptance", "description"]),
1175
+ feature: /* @__PURE__ */ new Set([
1176
+ "feature",
1177
+ "name",
1178
+ "features",
1179
+ "acceptance",
1180
+ "description"
1181
+ ]),
1104
1182
  primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
1105
1183
  widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
1106
1184
  flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
@@ -1836,7 +1914,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
1836
1914
  line: pos.line
1837
1915
  });
1838
1916
  }
1839
- const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
1917
+ const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
1840
1918
  const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
1841
1919
  const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
1842
1920
  const metadata = {
@@ -1931,7 +2009,7 @@ function posAt(content, offset) {
1931
2009
  return { offset, line, column: offset - lineStart + 1 };
1932
2010
  }
1933
2011
 
1934
- // src/scan/jsx-ancestry.ts
2012
+ // src/scanner/scan/jsx-ancestry.ts
1935
2013
  var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
1936
2014
  function parseDataAttrs(tagSource) {
1937
2015
  if (!tagSource.includes("data-uidex")) return [];
@@ -2120,7 +2198,7 @@ function findTagEnd(content, start) {
2120
2198
  return -1;
2121
2199
  }
2122
2200
 
2123
- // src/scan/extract.ts
2201
+ // src/scanner/scan/extract.ts
2124
2202
  var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
2125
2203
  function lineAt(content, index) {
2126
2204
  let line = 1;
@@ -2223,7 +2301,7 @@ function extractOne(file) {
2223
2301
  return annotations;
2224
2302
  }
2225
2303
 
2226
- // src/scan/git.ts
2304
+ // src/scanner/scan/git.ts
2227
2305
  var import_node_child_process = require("child_process");
2228
2306
  function runGit(args, cwd) {
2229
2307
  try {
@@ -2255,10 +2333,10 @@ function parseGitHubRef(ref) {
2255
2333
  return m ? m[1] : null;
2256
2334
  }
2257
2335
 
2258
- // src/scan/resolve.ts
2259
- var path5 = __toESM(require("path"), 1);
2336
+ // src/scanner/scan/resolve.ts
2337
+ var path6 = __toESM(require("path"), 1);
2260
2338
 
2261
- // src/scan/routes.ts
2339
+ // src/scanner/scan/routes.ts
2262
2340
  var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
2263
2341
  var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
2264
2342
  var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
@@ -2324,9 +2402,9 @@ function pathToId(routePath) {
2324
2402
  return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2325
2403
  }
2326
2404
 
2327
- // src/scan/walk.ts
2405
+ // src/scanner/scan/walk.ts
2328
2406
  var fs4 = __toESM(require("fs"), 1);
2329
- var path4 = __toESM(require("path"), 1);
2407
+ var path5 = __toESM(require("path"), 1);
2330
2408
  var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
2331
2409
  var BASE_EXCLUDES = [
2332
2410
  "**/node_modules/**",
@@ -2390,7 +2468,7 @@ function globToRegExp(glob) {
2390
2468
  return new RegExp(`^${out2}$`);
2391
2469
  }
2392
2470
  function toPosix(p2) {
2393
- return p2.split(path4.sep).join("/");
2471
+ return p2.split(path5.sep).join("/");
2394
2472
  }
2395
2473
  function matchesAny(rel, patterns) {
2396
2474
  return patterns.some((g) => globToRegExp(g).test(rel));
@@ -2406,9 +2484,9 @@ function walk(sources, options) {
2406
2484
  ...globalExcludes,
2407
2485
  ...source.exclude ?? []
2408
2486
  ];
2409
- const absRoot = path4.resolve(cwd, source.rootDir);
2487
+ const absRoot = path5.resolve(cwd, source.rootDir);
2410
2488
  for (const filePath of walkDir(absRoot, absRoot)) {
2411
- const rel = toPosix(path4.relative(absRoot, filePath));
2489
+ const rel = toPosix(path5.relative(absRoot, filePath));
2412
2490
  if (matchesAny(rel, excludes)) continue;
2413
2491
  if (!matchesAny(rel, includes)) continue;
2414
2492
  let content;
@@ -2417,7 +2495,7 @@ function walk(sources, options) {
2417
2495
  } catch {
2418
2496
  continue;
2419
2497
  }
2420
- const relFromCwd = toPosix(path4.relative(cwd, filePath));
2498
+ const relFromCwd = toPosix(path5.relative(cwd, filePath));
2421
2499
  const displayPath = source.prefix ? `${source.prefix.replace(/\/$/, "")}/${rel}` : relFromCwd;
2422
2500
  out2.push({
2423
2501
  sourcePath: filePath,
@@ -2437,7 +2515,7 @@ function* walkDir(root, dir) {
2437
2515
  return;
2438
2516
  }
2439
2517
  for (const entry of entries) {
2440
- const full = path4.join(dir, entry.name);
2518
+ const full = path5.join(dir, entry.name);
2441
2519
  if (entry.isDirectory()) {
2442
2520
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === "build" || entry.name === ".next") {
2443
2521
  continue;
@@ -2449,7 +2527,7 @@ function* walkDir(root, dir) {
2449
2527
  }
2450
2528
  }
2451
2529
 
2452
- // src/scan/resolve.ts
2530
+ // src/scanner/scan/resolve.ts
2453
2531
  var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
2454
2532
  "element",
2455
2533
  "region",
@@ -2469,7 +2547,7 @@ function kebab(str) {
2469
2547
  return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").toLowerCase();
2470
2548
  }
2471
2549
  function baseName(file) {
2472
- const b = path5.posix.basename(file);
2550
+ const b = path6.posix.basename(file);
2473
2551
  return b.replace(/\.(tsx|ts|jsx|js|mjs|cjs)$/, "");
2474
2552
  }
2475
2553
  var LANDMARK_RE = /<(header|nav|main|aside|footer)(\s[^>]*)?>|role=["']region["']/gi;
@@ -2544,7 +2622,24 @@ function resolve3(ctx) {
2544
2622
  const routes = conventions.pages === "auto" ? detectRoutes(ctx.extracted.map((e) => e.file)) : [];
2545
2623
  const handledPageFiles = /* @__PURE__ */ new Set();
2546
2624
  for (const route of routes) {
2547
- const exp = exportFor(route.file, "page");
2625
+ const routeDir = path6.posix.dirname(route.file);
2626
+ const wellKnownPath = `${routeDir}/${WELL_KNOWN_FILES.page}`;
2627
+ const wellKnownExp = exportFor(wellKnownPath, "page");
2628
+ const routeExp = exportFor(route.file, "page");
2629
+ const exp = wellKnownExp ?? routeExp;
2630
+ const locFile = wellKnownExp ? wellKnownPath : route.file;
2631
+ if (wellKnownExp) handledPageFiles.add(wellKnownPath);
2632
+ handledPageFiles.add(route.file);
2633
+ if (wellKnownExp && routeExp) {
2634
+ diagnostics.push({
2635
+ code: "competing-uidex-export",
2636
+ severity: "warning",
2637
+ message: `Page metadata declared in both ${wellKnownPath} and ${route.file}; ${wellKnownPath} takes precedence.`,
2638
+ file: route.file,
2639
+ line: routeExp.loc.line,
2640
+ hint: `Remove the export from ${route.file} or delete ${wellKnownPath}.`
2641
+ });
2642
+ }
2548
2643
  if (exp && exp.id === false) continue;
2549
2644
  const effectiveId = exp && typeof exp.id === "string" ? exp.id : route.id;
2550
2645
  const meta = exp ? buildMetaFromExport(exp) : void 0;
@@ -2552,11 +2647,10 @@ function resolve3(ctx) {
2552
2647
  const page = {
2553
2648
  kind: "page",
2554
2649
  id: effectiveId,
2555
- loc: { file: route.file, line: exp?.loc.line },
2650
+ loc: { file: locFile, line: exp?.loc.line },
2556
2651
  ...meta ? { meta } : {}
2557
2652
  };
2558
2653
  registry.add(page);
2559
- handledPageFiles.add(route.file);
2560
2654
  }
2561
2655
  for (const ef of ctx.extracted) {
2562
2656
  const exp = exportFor(ef.file.displayPath, "page");
@@ -2572,7 +2666,8 @@ function resolve3(ctx) {
2572
2666
  }
2573
2667
  const featureGlob = typeof conventions.features === "string" ? conventions.features : null;
2574
2668
  const conventionalFeatureDirs = /* @__PURE__ */ new Set();
2575
- const featureExportsByDir = /* @__PURE__ */ new Map();
2669
+ const featureExportFilesByDir = /* @__PURE__ */ new Map();
2670
+ const wellKnownFeatureFileByDir = /* @__PURE__ */ new Map();
2576
2671
  const suppressedFeatureDirs = /* @__PURE__ */ new Set();
2577
2672
  if (featureGlob) {
2578
2673
  const re = globToRegExp(featureGlob + "/**");
@@ -2581,17 +2676,44 @@ function resolve3(ctx) {
2581
2676
  const dir = extractFeatureDir(ef.file.displayPath, featureGlob);
2582
2677
  if (!dir) continue;
2583
2678
  conventionalFeatureDirs.add(dir);
2679
+ const isWellKnown = path6.posix.basename(ef.file.displayPath) === WELL_KNOWN_FILES.feature;
2680
+ if (isWellKnown) wellKnownFeatureFileByDir.set(dir, ef.file.displayPath);
2584
2681
  const exp = exportFor(ef.file.displayPath, "feature");
2585
2682
  if (exp) {
2586
2683
  if (exp.id === false) suppressedFeatureDirs.add(dir);
2587
- else if (!featureExportsByDir.has(dir))
2588
- featureExportsByDir.set(dir, exp);
2684
+ else {
2685
+ let arr = featureExportFilesByDir.get(dir);
2686
+ if (!arr) {
2687
+ arr = [];
2688
+ featureExportFilesByDir.set(dir, arr);
2689
+ }
2690
+ arr.push({ file: ef.file.displayPath, exp });
2691
+ }
2589
2692
  }
2590
2693
  }
2591
2694
  for (const dir of conventionalFeatureDirs) {
2592
2695
  if (suppressedFeatureDirs.has(dir)) continue;
2593
- const exp = featureExportsByDir.get(dir);
2594
- const id = exp && typeof exp.id === "string" ? exp.id : path5.posix.basename(dir);
2696
+ const allExports = featureExportFilesByDir.get(dir) ?? [];
2697
+ const wellKnownPath = wellKnownFeatureFileByDir.get(dir);
2698
+ const wellKnownEntry = wellKnownPath ? allExports.find((e) => e.file === wellKnownPath) : void 0;
2699
+ let exp;
2700
+ if (wellKnownEntry) {
2701
+ exp = wellKnownEntry.exp;
2702
+ for (const other of allExports) {
2703
+ if (other.file === wellKnownEntry.file) continue;
2704
+ diagnostics.push({
2705
+ code: "competing-uidex-export",
2706
+ severity: "warning",
2707
+ message: `Feature metadata declared in both ${wellKnownEntry.file} and ${other.file}; ${wellKnownEntry.file} takes precedence.`,
2708
+ file: other.file,
2709
+ line: other.exp.loc.line,
2710
+ hint: `Remove the export from ${other.file} or delete ${wellKnownEntry.file}.`
2711
+ });
2712
+ }
2713
+ } else if (allExports.length > 0) {
2714
+ exp = allExports[0].exp;
2715
+ }
2716
+ const id = exp && typeof exp.id === "string" ? exp.id : path6.posix.basename(dir);
2595
2717
  const meta = exp ? buildMetaFromExport(exp) : void 0;
2596
2718
  const feature = {
2597
2719
  kind: "feature",
@@ -2723,11 +2845,12 @@ function resolve3(ctx) {
2723
2845
  exp.id,
2724
2846
  buildMetaFromExport(exp)
2725
2847
  );
2848
+ const scope = computeScope(file);
2726
2849
  const primitive = {
2727
2850
  kind: "primitive",
2728
2851
  id: exp.id,
2729
2852
  loc: { file, line: exp.loc.line },
2730
- scopes: [computeScope(file)],
2853
+ ...scope ? { scopes: [scope] } : {},
2731
2854
  ...meta ? { meta } : {}
2732
2855
  };
2733
2856
  registry.add(primitive);
@@ -2739,11 +2862,12 @@ function resolve3(ctx) {
2739
2862
  if (domPrimitives.length > 0) {
2740
2863
  for (const p2 of domPrimitives) {
2741
2864
  const meta = metaWithComposes("primitive", p2.id);
2865
+ const domScope = computeScope(p2.file);
2742
2866
  const primitive = {
2743
2867
  kind: "primitive",
2744
2868
  id: p2.id,
2745
2869
  loc: { file: p2.file, line: p2.line },
2746
- scopes: [computeScope(p2.file)],
2870
+ ...domScope ? { scopes: [domScope] } : {},
2747
2871
  ...meta ? { meta } : {}
2748
2872
  };
2749
2873
  registry.add(primitive);
@@ -2753,13 +2877,13 @@ function resolve3(ctx) {
2753
2877
  if (primitiveConventions && fileMatchesAny(file, primitiveConventions)) {
2754
2878
  const name = kebab(baseName(file));
2755
2879
  if (!name) continue;
2756
- const scope = computeScope(file);
2880
+ const convScope = computeScope(file);
2757
2881
  const meta = metaWithComposes("primitive", name);
2758
2882
  const primitive = {
2759
2883
  kind: "primitive",
2760
2884
  id: name,
2761
2885
  loc: { file },
2762
- scopes: [scope],
2886
+ ...convScope ? { scopes: [convScope] } : {},
2763
2887
  ...meta ? { meta } : {}
2764
2888
  };
2765
2889
  registry.add(primitive);
@@ -2789,7 +2913,8 @@ function resolve3(ctx) {
2789
2913
  kind: "flow",
2790
2914
  id: flowExport.id,
2791
2915
  loc: base.loc,
2792
- touches: base.touches
2916
+ touches: base.touches,
2917
+ steps: base.steps
2793
2918
  };
2794
2919
  registry.add(flow);
2795
2920
  } else {
@@ -2834,7 +2959,7 @@ function computeScope(displayPath) {
2834
2959
  if (pagesIdx !== -1 && parts[pagesIdx + 1]) {
2835
2960
  return `page:${parts[pagesIdx + 1]}`;
2836
2961
  }
2837
- return "global";
2962
+ return null;
2838
2963
  }
2839
2964
  function extractFlowsFromSource(file) {
2840
2965
  const flows = [];
@@ -2868,17 +2993,18 @@ function extractFlowsFromSource(file) {
2868
2993
  kind: "flow",
2869
2994
  id,
2870
2995
  loc: { file: file.displayPath, line },
2871
- touches: dedupe(touches.map((t) => t.id))
2996
+ touches: dedupe(touches.map((t) => t.id)),
2997
+ steps: touches.filter((t) => t.action).map((t) => ({ entityId: t.id, action: t.action }))
2872
2998
  });
2873
2999
  }
2874
3000
  return flows;
2875
3001
  }
2876
3002
  function captureUidexIds(body) {
2877
3003
  const out2 = [];
2878
- const re = /uidex\(\s*(?:'([^']+)'|"([^"]+)"|`([^`$]+)`)\s*\)/g;
3004
+ const re = /uidex\(\s*(?:'([^']+)'|"([^"]+)"|`([^`$]+)`)\s*\)(?:\.(\w+)\s*\()?/g;
2879
3005
  let m;
2880
3006
  while ((m = re.exec(body)) !== null) {
2881
- out2.push({ id: m[1] || m[2] || m[3] });
3007
+ out2.push({ id: m[1] || m[2] || m[3], action: m[4] });
2882
3008
  }
2883
3009
  return out2;
2884
3010
  }
@@ -2886,7 +3012,7 @@ function dedupe(arr) {
2886
3012
  return Array.from(new Set(arr));
2887
3013
  }
2888
3014
 
2889
- // src/scan/pipeline.ts
3015
+ // src/scanner/scan/pipeline.ts
2890
3016
  function runScan(opts = {}) {
2891
3017
  const cwd = opts.cwd ?? process.cwd();
2892
3018
  const configs = opts.configs ?? discover({ cwd });
@@ -2918,7 +3044,7 @@ function runOne(dc, opts) {
2918
3044
  gitContext,
2919
3045
  typeMode: config.typeMode
2920
3046
  });
2921
- const outputPath = path6.resolve(configDir, config.output);
3047
+ const outputPath = path7.resolve(configDir, config.output);
2922
3048
  const outputRel = config.output;
2923
3049
  let existingOnDisk = null;
2924
3050
  if (opts.check) {
@@ -2953,14 +3079,14 @@ function runOne(dc, opts) {
2953
3079
  outputPath
2954
3080
  };
2955
3081
  }
2956
- function writeScanResult(result) {
2957
- fs5.mkdirSync(path6.dirname(result.outputPath), { recursive: true });
2958
- fs5.writeFileSync(result.outputPath, result.generated, "utf8");
3082
+ function writeScanResult(result2) {
3083
+ fs5.mkdirSync(path7.dirname(result2.outputPath), { recursive: true });
3084
+ fs5.writeFileSync(result2.outputPath, result2.generated, "utf8");
2959
3085
  }
2960
3086
 
2961
- // src/scan/scaffold.ts
3087
+ // src/scanner/scan/scaffold.ts
2962
3088
  var fs6 = __toESM(require("fs"), 1);
2963
- var path7 = __toESM(require("path"), 1);
3089
+ var path8 = __toESM(require("path"), 1);
2964
3090
  function scaffoldWidgetSpec(opts) {
2965
3091
  const {
2966
3092
  registry,
@@ -2975,7 +3101,7 @@ function scaffoldWidgetSpec(opts) {
2975
3101
  }
2976
3102
  const criteria = widget.meta?.acceptance ?? [];
2977
3103
  const filename = `widget-${widgetId}.spec.ts`;
2978
- const outputPath = path7.resolve(outDir, filename);
3104
+ const outputPath = path8.resolve(outDir, filename);
2979
3105
  if (fs6.existsSync(outputPath) && !force) {
2980
3106
  return {
2981
3107
  outputPath,
@@ -2989,7 +3115,7 @@ function scaffoldWidgetSpec(opts) {
2989
3115
  criteria,
2990
3116
  fixtureImport
2991
3117
  });
2992
- fs6.mkdirSync(path7.dirname(outputPath), { recursive: true });
3118
+ fs6.mkdirSync(path8.dirname(outputPath), { recursive: true });
2993
3119
  fs6.writeFileSync(outputPath, content, "utf8");
2994
3120
  return { outputPath, written: true, skipped: false };
2995
3121
  }
@@ -3021,8 +3147,8 @@ function renderSpec(args) {
3021
3147
  return lines.join("\n");
3022
3148
  }
3023
3149
 
3024
- // src/scan/cli.ts
3025
- function parseFlags2(args) {
3150
+ // src/scanner/cli/parse-args.ts
3151
+ function parseArgs(args) {
3026
3152
  const positional = [];
3027
3153
  const flags = {};
3028
3154
  for (let i = 0; i < args.length; i++) {
@@ -3046,13 +3172,15 @@ function parseFlags2(args) {
3046
3172
  }
3047
3173
  return { positional, flags };
3048
3174
  }
3175
+
3176
+ // src/scanner/scan/cli.ts
3049
3177
  async function run(opts) {
3050
3178
  const cwd = opts.cwd ?? process.cwd();
3051
- const { positional, flags } = parseFlags2(opts.argv);
3052
- const command = positional[0] ?? "help";
3179
+ const { positional, flags } = parseArgs(opts.argv);
3180
+ const command2 = positional[0] ?? "help";
3053
3181
  const writer = createWriter();
3054
3182
  try {
3055
- switch (command) {
3183
+ switch (command2) {
3056
3184
  case "help":
3057
3185
  case "--help":
3058
3186
  case "-h":
@@ -3065,16 +3193,16 @@ async function run(opts) {
3065
3193
  case "scaffold":
3066
3194
  return runScaffold(cwd, positional.slice(1), flags, writer);
3067
3195
  case "ai": {
3068
- const result = await runAiCommand({
3196
+ const result2 = await runAiCommand({
3069
3197
  cwd,
3070
3198
  argv: opts.argv.slice(1)
3071
3199
  });
3072
- if (result.stdout) writer.out(result.stdout.replace(/\n$/, ""));
3073
- if (result.stderr) writer.err(result.stderr.replace(/\n$/, ""));
3074
- return writer.result(result.exitCode);
3200
+ if (result2.stdout) writer.out(result2.stdout.replace(/\n$/, ""));
3201
+ if (result2.stderr) writer.err(result2.stderr.replace(/\n$/, ""));
3202
+ return writer.result(result2.exitCode);
3075
3203
  }
3076
3204
  default:
3077
- writer.err(`Unknown command: ${command}`);
3205
+ writer.err(`Unknown command: ${command2}`);
3078
3206
  writer.err(helpText2());
3079
3207
  return writer.result(1);
3080
3208
  }
@@ -3092,6 +3220,10 @@ function helpText2() {
3092
3220
  " scan [flags] Run the scanner pipeline",
3093
3221
  " scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
3094
3222
  " ai <install|uninstall|providers> Manage AI assistant integrations",
3223
+ " api <METHOD> <PATH> Call the uidex API",
3224
+ " api --list Show available API routes",
3225
+ " api login Authenticate via browser",
3226
+ " api login --token <tok> Store an auth token directly",
3095
3227
  "",
3096
3228
  "Flags:",
3097
3229
  " --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",
@@ -3103,7 +3235,7 @@ function helpText2() {
3103
3235
  ].join("\n");
3104
3236
  }
3105
3237
  function runInit(cwd, w) {
3106
- const configPath = path8.join(cwd, CONFIG_FILENAME);
3238
+ const configPath = path9.join(cwd, CONFIG_FILENAME);
3107
3239
  if (fs7.existsSync(configPath)) {
3108
3240
  w.err(`.uidex.json already exists at ${configPath}`);
3109
3241
  return w.result(1);
@@ -3115,7 +3247,7 @@ function runInit(cwd, w) {
3115
3247
  };
3116
3248
  fs7.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
3117
3249
  w.out(`Created ${configPath}`);
3118
- const gitignorePath = path8.join(cwd, ".gitignore");
3250
+ const gitignorePath = path9.join(cwd, ".gitignore");
3119
3251
  const entry = "*.gen.ts";
3120
3252
  if (fs7.existsSync(gitignorePath)) {
3121
3253
  const existing = fs7.readFileSync(gitignorePath, "utf8");
@@ -3193,18 +3325,18 @@ function runScaffold(cwd, args, flags, w) {
3193
3325
  for (const r of results) {
3194
3326
  const widget = r.registry.get("widget", id);
3195
3327
  if (!widget) continue;
3196
- const outDir = path8.resolve(r.configDir, "e2e");
3197
- const result = scaffoldWidgetSpec({
3328
+ const outDir = path9.resolve(r.configDir, "e2e");
3329
+ const result2 = scaffoldWidgetSpec({
3198
3330
  registry: r.registry,
3199
3331
  widgetId: id,
3200
3332
  outDir,
3201
3333
  force: Boolean(flags.force)
3202
3334
  });
3203
- if (result.skipped) {
3204
- w.err(result.reason ?? "skipped");
3335
+ if (result2.skipped) {
3336
+ w.err(result2.reason ?? "skipped");
3205
3337
  return w.result(1);
3206
3338
  }
3207
- w.out(`Wrote ${result.outputPath}`);
3339
+ w.out(`Wrote ${result2.outputPath}`);
3208
3340
  return w.result(0);
3209
3341
  }
3210
3342
  w.err(`Widget "${id}" not found in registry`);
@@ -3226,12 +3358,884 @@ function createWriter() {
3226
3358
  };
3227
3359
  }
3228
3360
 
3229
- // src/cli/cli.ts
3230
- run({ argv: process.argv.slice(2) }).then(
3231
- (result) => {
3232
- if (result.stdout) process.stdout.write(result.stdout);
3233
- if (result.stderr) process.stderr.write(result.stderr);
3234
- process.exit(result.exitCode);
3361
+ // src/scanner/cli/auth.ts
3362
+ function createMemoryTokenStorage(initial) {
3363
+ let token = initial ?? null;
3364
+ return {
3365
+ get: () => token,
3366
+ set: (t) => {
3367
+ token = t;
3368
+ },
3369
+ clear: () => {
3370
+ token = null;
3371
+ }
3372
+ };
3373
+ }
3374
+ function createFileTokenStorage(options) {
3375
+ const nodeFs = require("fs");
3376
+ const nodePath = require("path");
3377
+ const file = options.path;
3378
+ function read() {
3379
+ if (!nodeFs.existsSync(file)) return null;
3380
+ try {
3381
+ const raw = nodeFs.readFileSync(file, "utf8");
3382
+ const parsed = JSON.parse(raw);
3383
+ if (parsed && typeof parsed === "object") {
3384
+ return parsed;
3385
+ }
3386
+ } catch {
3387
+ return null;
3388
+ }
3389
+ return null;
3390
+ }
3391
+ return {
3392
+ get: () => read()?.token ?? null,
3393
+ set: (token) => {
3394
+ nodeFs.mkdirSync(nodePath.dirname(file), { recursive: true });
3395
+ nodeFs.writeFileSync(file, JSON.stringify({ token }, null, 2));
3396
+ },
3397
+ clear: () => {
3398
+ if (nodeFs.existsSync(file)) nodeFs.unlinkSync(file);
3399
+ }
3400
+ };
3401
+ }
3402
+ function defaultTokenPath() {
3403
+ const os = require("os");
3404
+ const path10 = require("path");
3405
+ return path10.join(os.homedir(), ".uidex", "cloud-token.json");
3406
+ }
3407
+
3408
+ // src/scanner/cli/http.ts
3409
+ function createHttpClient(options) {
3410
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
3411
+ async function request(path10, opts = {}) {
3412
+ let url = `${baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
3413
+ if (opts.query) {
3414
+ url += (url.includes("?") ? "&" : "?") + opts.query;
3415
+ }
3416
+ const headers = {
3417
+ Accept: "application/json",
3418
+ ...opts.headers
3419
+ };
3420
+ const token = options.token();
3421
+ if (token) headers.Authorization = `Bearer ${token}`;
3422
+ if (opts.body !== void 0) {
3423
+ headers["Content-Type"] ??= "application/json";
3424
+ }
3425
+ const res = await fetch(url, {
3426
+ method: opts.method ?? (opts.body ? "POST" : "GET"),
3427
+ headers,
3428
+ body: opts.body
3429
+ });
3430
+ const raw = await res.text();
3431
+ let body = raw;
3432
+ try {
3433
+ body = JSON.parse(raw);
3434
+ } catch {
3435
+ }
3436
+ return { status: res.status, body, raw };
3437
+ }
3438
+ return { request };
3439
+ }
3440
+
3441
+ // src/scanner/cli/json-highlight.ts
3442
+ var RESET = "\x1B[0m";
3443
+ var BOLD = "\x1B[1m";
3444
+ var DIM = "\x1B[2m";
3445
+ var RED = "\x1B[31m";
3446
+ var CYAN = "\x1B[36m";
3447
+ var GREEN = "\x1B[32m";
3448
+ var YELLOW = "\x1B[33m";
3449
+ var MAGENTA = "\x1B[35m";
3450
+ function highlightJson(value, color) {
3451
+ const json = JSON.stringify(value, null, 2);
3452
+ if (!color) return json;
3453
+ return json.replace(
3454
+ /("(?:\\.|[^"\\])*")\s*:|("(?:\\.|[^"\\])*")|(\b(?:true|false|null)\b)|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g,
3455
+ (match, key, str, literal, num) => {
3456
+ if (key) return `${CYAN}${key}${RESET}:`;
3457
+ if (str) return `${GREEN}${str}${RESET}`;
3458
+ if (literal) return `${MAGENTA}${literal}${RESET}`;
3459
+ if (num) return `${YELLOW}${num}${RESET}`;
3460
+ return match;
3461
+ }
3462
+ );
3463
+ }
3464
+ function formatStatus(status, color) {
3465
+ if (!color) return `HTTP ${status}`;
3466
+ const code = status >= 400 ? RED : status >= 300 ? YELLOW : GREEN;
3467
+ return `${code}HTTP ${status}${RESET}`;
3468
+ }
3469
+ function boldText(text, color) {
3470
+ if (!color) return text;
3471
+ return `${BOLD}${text}${RESET}`;
3472
+ }
3473
+ function dimText(text, color) {
3474
+ if (!color) return text;
3475
+ return `${DIM}${text}${RESET}`;
3476
+ }
3477
+
3478
+ // src/scanner/cli/api-routes.gen.ts
3479
+ var API_ROUTES = [
3480
+ {
3481
+ "method": "POST",
3482
+ "path": "/api/ingest",
3483
+ "operationId": "submitReport",
3484
+ "summary": "Submit feedback from the SDK.",
3485
+ "tag": "Ingest",
3486
+ "params": []
3487
+ },
3488
+ {
3489
+ "method": "GET",
3490
+ "path": "/api/ingest/config",
3491
+ "operationId": "getIngestConfig",
3492
+ "summary": "Get integration configuration for the project.",
3493
+ "tag": "Ingest",
3494
+ "params": []
3495
+ },
3496
+ {
3497
+ "method": "GET",
3498
+ "path": "/api/ingest/reports",
3499
+ "operationId": "listIngestReports",
3500
+ "summary": "List feedback for the project (SDK view).",
3501
+ "tag": "Ingest",
3502
+ "params": []
3503
+ },
3504
+ {
3505
+ "method": "GET",
3506
+ "path": "/api/ingest/pins",
3507
+ "operationId": "listPins",
3508
+ "summary": "List pins by route and/or entity.",
3509
+ "tag": "Ingest",
3510
+ "params": []
3511
+ },
3512
+ {
3513
+ "method": "POST",
3514
+ "path": "/api/ingest/pins/archive",
3515
+ "operationId": "archivePin",
3516
+ "summary": "Archive a pin.",
3517
+ "tag": "Ingest",
3518
+ "params": []
3519
+ },
3520
+ {
3521
+ "method": "POST",
3522
+ "path": "/api/cli-auth/authorize",
3523
+ "operationId": "authorizeCli",
3524
+ "summary": "Authorize a CLI session by delivering a token.",
3525
+ "tag": "CLI Auth",
3526
+ "params": []
3527
+ },
3528
+ {
3529
+ "method": "GET",
3530
+ "path": "/api/organizations",
3531
+ "operationId": "listOrganizations",
3532
+ "summary": "List organizations for the current user.",
3533
+ "tag": "Organizations",
3534
+ "params": []
3535
+ },
3536
+ {
3537
+ "method": "POST",
3538
+ "path": "/api/organizations",
3539
+ "operationId": "createOrganization",
3540
+ "summary": "Create an organization.",
3541
+ "tag": "Organizations",
3542
+ "params": []
3543
+ },
3544
+ {
3545
+ "method": "POST",
3546
+ "path": "/api/organizations/switch",
3547
+ "operationId": "switchOrganization",
3548
+ "summary": "Switch the active organization.",
3549
+ "tag": "Organizations",
3550
+ "params": []
3551
+ },
3552
+ {
3553
+ "method": "GET",
3554
+ "path": "/api/organizations/{orgId}",
3555
+ "operationId": "getOrganization",
3556
+ "summary": "Get organization details.",
3557
+ "tag": "Organizations",
3558
+ "params": [
3559
+ "orgId"
3560
+ ]
3561
+ },
3562
+ {
3563
+ "method": "PATCH",
3564
+ "path": "/api/organizations/{orgId}",
3565
+ "operationId": "updateOrganization",
3566
+ "summary": "Update an organization.",
3567
+ "tag": "Organizations",
3568
+ "params": [
3569
+ "orgId"
3570
+ ]
3571
+ },
3572
+ {
3573
+ "method": "DELETE",
3574
+ "path": "/api/organizations/{orgId}",
3575
+ "operationId": "deleteOrganization",
3576
+ "summary": "Delete an organization.",
3577
+ "tag": "Organizations",
3578
+ "params": [
3579
+ "orgId"
3580
+ ]
3581
+ },
3582
+ {
3583
+ "method": "GET",
3584
+ "path": "/api/organizations/{orgId}/members",
3585
+ "operationId": "listMembers",
3586
+ "summary": "List organization members.",
3587
+ "tag": "Members",
3588
+ "params": [
3589
+ "orgId"
3590
+ ]
3591
+ },
3592
+ {
3593
+ "method": "DELETE",
3594
+ "path": "/api/organizations/{orgId}/members",
3595
+ "operationId": "removeMember",
3596
+ "summary": "Remove a member from the organization.",
3597
+ "tag": "Members",
3598
+ "params": [
3599
+ "orgId"
3600
+ ]
3601
+ },
3602
+ {
3603
+ "method": "PATCH",
3604
+ "path": "/api/organizations/{orgId}/members/{memberId}",
3605
+ "operationId": "updateMember",
3606
+ "summary": "Update a member's role.",
3607
+ "tag": "Members",
3608
+ "params": [
3609
+ "orgId",
3610
+ "memberId"
3611
+ ]
3612
+ },
3613
+ {
3614
+ "method": "GET",
3615
+ "path": "/api/organizations/{orgId}/invitations",
3616
+ "operationId": "listInvitations",
3617
+ "summary": "List organization invitations.",
3618
+ "tag": "Invitations",
3619
+ "params": [
3620
+ "orgId"
3621
+ ]
3622
+ },
3623
+ {
3624
+ "method": "POST",
3625
+ "path": "/api/organizations/{orgId}/invitations",
3626
+ "operationId": "createInvitation",
3627
+ "summary": "Create an invitation link.",
3628
+ "tag": "Invitations",
3629
+ "params": [
3630
+ "orgId"
3631
+ ]
3632
+ },
3633
+ {
3634
+ "method": "DELETE",
3635
+ "path": "/api/organizations/{orgId}/invitations/{inviteId}",
3636
+ "operationId": "deleteInvitation",
3637
+ "summary": "Revoke an invitation.",
3638
+ "tag": "Invitations",
3639
+ "params": [
3640
+ "orgId",
3641
+ "inviteId"
3642
+ ]
3643
+ },
3644
+ {
3645
+ "method": "GET",
3646
+ "path": "/api/organizations/{orgId}/projects",
3647
+ "operationId": "listProjects",
3648
+ "summary": "List projects in an organization.",
3649
+ "tag": "Projects",
3650
+ "params": [
3651
+ "orgId"
3652
+ ]
3653
+ },
3654
+ {
3655
+ "method": "POST",
3656
+ "path": "/api/organizations/{orgId}/projects",
3657
+ "operationId": "createProject",
3658
+ "summary": "Create a project.",
3659
+ "tag": "Projects",
3660
+ "params": [
3661
+ "orgId"
3662
+ ]
3663
+ },
3664
+ {
3665
+ "method": "GET",
3666
+ "path": "/api/organizations/{orgId}/projects/{projectId}",
3667
+ "operationId": "getProject",
3668
+ "summary": "Get project details.",
3669
+ "tag": "Projects",
3670
+ "params": [
3671
+ "orgId",
3672
+ "projectId"
3673
+ ]
3674
+ },
3675
+ {
3676
+ "method": "PATCH",
3677
+ "path": "/api/organizations/{orgId}/projects/{projectId}",
3678
+ "operationId": "updateProject",
3679
+ "summary": "Update a project.",
3680
+ "tag": "Projects",
3681
+ "params": [
3682
+ "orgId",
3683
+ "projectId"
3684
+ ]
3685
+ },
3686
+ {
3687
+ "method": "DELETE",
3688
+ "path": "/api/organizations/{orgId}/projects/{projectId}",
3689
+ "operationId": "deleteProject",
3690
+ "summary": "Delete a project.",
3691
+ "tag": "Projects",
3692
+ "params": [
3693
+ "orgId",
3694
+ "projectId"
3695
+ ]
3696
+ },
3697
+ {
3698
+ "method": "GET",
3699
+ "path": "/api/projects/resolve",
3700
+ "operationId": "resolveProject",
3701
+ "summary": "Resolve an API key to its project and organization.",
3702
+ "tag": "Projects",
3703
+ "params": []
3704
+ },
3705
+ {
3706
+ "method": "GET",
3707
+ "path": "/api/organizations/{orgId}/projects/{projectId}/api-keys",
3708
+ "operationId": "listApiKeys",
3709
+ "summary": "List API keys for a project.",
3710
+ "tag": "API Keys",
3711
+ "params": [
3712
+ "orgId",
3713
+ "projectId"
3714
+ ]
3715
+ },
3716
+ {
3717
+ "method": "POST",
3718
+ "path": "/api/organizations/{orgId}/projects/{projectId}/api-keys",
3719
+ "operationId": "createApiKey",
3720
+ "summary": "Create an API key.",
3721
+ "tag": "API Keys",
3722
+ "params": [
3723
+ "orgId",
3724
+ "projectId"
3725
+ ]
3726
+ },
3727
+ {
3728
+ "method": "PATCH",
3729
+ "path": "/api/organizations/{orgId}/projects/{projectId}/api-keys/{keyId}",
3730
+ "operationId": "revokeApiKey",
3731
+ "summary": "Revoke an API key.",
3732
+ "tag": "API Keys",
3733
+ "params": [
3734
+ "orgId",
3735
+ "projectId",
3736
+ "keyId"
3737
+ ]
3738
+ },
3739
+ {
3740
+ "method": "DELETE",
3741
+ "path": "/api/organizations/{orgId}/projects/{projectId}/api-keys/{keyId}",
3742
+ "operationId": "deleteApiKey",
3743
+ "summary": "Permanently delete an API key.",
3744
+ "tag": "API Keys",
3745
+ "params": [
3746
+ "orgId",
3747
+ "projectId",
3748
+ "keyId"
3749
+ ]
3750
+ },
3751
+ {
3752
+ "method": "GET",
3753
+ "path": "/api/organizations/{orgId}/projects/{projectId}/reports",
3754
+ "operationId": "listProjectReports",
3755
+ "summary": "List feedback for a project.",
3756
+ "tag": "Reports",
3757
+ "params": [
3758
+ "orgId",
3759
+ "projectId"
3760
+ ]
3761
+ },
3762
+ {
3763
+ "method": "GET",
3764
+ "path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
3765
+ "operationId": "getReport",
3766
+ "summary": "Get feedback details.",
3767
+ "tag": "Reports",
3768
+ "params": [
3769
+ "orgId",
3770
+ "projectId",
3771
+ "reportId"
3772
+ ]
3773
+ },
3774
+ {
3775
+ "method": "PATCH",
3776
+ "path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
3777
+ "operationId": "updateReport",
3778
+ "summary": "Update feedback status, priority, or assignment.",
3779
+ "tag": "Reports",
3780
+ "params": [
3781
+ "orgId",
3782
+ "projectId",
3783
+ "reportId"
3784
+ ]
3785
+ },
3786
+ {
3787
+ "method": "DELETE",
3788
+ "path": "/api/organizations/{orgId}/projects/{projectId}/feedback/{reportId}",
3789
+ "operationId": "deleteReport",
3790
+ "summary": "Delete a feedback record.",
3791
+ "tag": "Reports",
3792
+ "params": [
3793
+ "orgId",
3794
+ "projectId",
3795
+ "reportId"
3796
+ ]
3797
+ },
3798
+ {
3799
+ "method": "POST",
3800
+ "path": "/api/organizations/{orgId}/projects/{projectId}/reports/archive",
3801
+ "operationId": "bulkArchiveReport",
3802
+ "summary": "Archive multiple feedback records.",
3803
+ "tag": "Reports",
3804
+ "params": [
3805
+ "orgId",
3806
+ "projectId"
3807
+ ]
3808
+ },
3809
+ {
3810
+ "method": "GET",
3811
+ "path": "/api/organizations/{orgId}/integrations",
3812
+ "operationId": "listIntegrations",
3813
+ "summary": "List integrations for an organization.",
3814
+ "tag": "Integrations",
3815
+ "params": [
3816
+ "orgId"
3817
+ ]
3818
+ },
3819
+ {
3820
+ "method": "POST",
3821
+ "path": "/api/organizations/{orgId}/integrations",
3822
+ "operationId": "createIntegration",
3823
+ "summary": "Create an integration.",
3824
+ "tag": "Integrations",
3825
+ "params": [
3826
+ "orgId"
3827
+ ]
3828
+ },
3829
+ {
3830
+ "method": "GET",
3831
+ "path": "/api/organizations/{orgId}/integrations/{integrationId}",
3832
+ "operationId": "getIntegration",
3833
+ "summary": "Get integration details.",
3834
+ "tag": "Integrations",
3835
+ "params": [
3836
+ "orgId",
3837
+ "integrationId"
3838
+ ]
3839
+ },
3840
+ {
3841
+ "method": "DELETE",
3842
+ "path": "/api/organizations/{orgId}/integrations/{integrationId}",
3843
+ "operationId": "deleteIntegration",
3844
+ "summary": "Delete an integration.",
3845
+ "tag": "Integrations",
3846
+ "params": [
3847
+ "orgId",
3848
+ "integrationId"
3849
+ ]
3850
+ },
3851
+ {
3852
+ "method": "GET",
3853
+ "path": "/api/organizations/{orgId}/integrations/{integrationId}/targets",
3854
+ "operationId": "listIntegrationTargets",
3855
+ "summary": "List available targets (e.g. Jira projects/boards).",
3856
+ "tag": "Integrations",
3857
+ "params": [
3858
+ "orgId",
3859
+ "integrationId"
3860
+ ]
3861
+ },
3862
+ {
3863
+ "method": "POST",
3864
+ "path": "/api/organizations/{orgId}/integrations/{integrationId}/test",
3865
+ "operationId": "testIntegration",
3866
+ "summary": "Test integration connectivity.",
3867
+ "tag": "Integrations",
3868
+ "params": [
3869
+ "orgId",
3870
+ "integrationId"
3871
+ ]
3872
+ },
3873
+ {
3874
+ "method": "GET",
3875
+ "path": "/api/organizations/{orgId}/external-issues",
3876
+ "operationId": "listExternalIssues",
3877
+ "summary": "List external issues, optionally filtered by feedback.",
3878
+ "tag": "External Issues",
3879
+ "params": [
3880
+ "orgId"
3881
+ ]
3882
+ },
3883
+ {
3884
+ "method": "GET",
3885
+ "path": "/api/organizations/{orgId}/issue-drafts",
3886
+ "operationId": "listIssueDrafts",
3887
+ "summary": "List issue drafts for a project.",
3888
+ "tag": "Issue Drafts",
3889
+ "params": [
3890
+ "orgId"
3891
+ ]
3892
+ },
3893
+ {
3894
+ "method": "POST",
3895
+ "path": "/api/organizations/{orgId}/issue-drafts",
3896
+ "operationId": "createIssueDraft",
3897
+ "summary": "Manually create an issue draft.",
3898
+ "tag": "Issue Drafts",
3899
+ "params": [
3900
+ "orgId"
3901
+ ]
3902
+ },
3903
+ {
3904
+ "method": "PATCH",
3905
+ "path": "/api/organizations/{orgId}/issue-drafts/{draftId}",
3906
+ "operationId": "updateIssueDraft",
3907
+ "summary": "Update an issue draft.",
3908
+ "tag": "Issue Drafts",
3909
+ "params": [
3910
+ "orgId",
3911
+ "draftId"
3912
+ ]
3913
+ },
3914
+ {
3915
+ "method": "DELETE",
3916
+ "path": "/api/organizations/{orgId}/issue-drafts/{draftId}",
3917
+ "operationId": "deleteIssueDraft",
3918
+ "summary": "Delete an issue draft.",
3919
+ "tag": "Issue Drafts",
3920
+ "params": [
3921
+ "orgId",
3922
+ "draftId"
3923
+ ]
3924
+ },
3925
+ {
3926
+ "method": "POST",
3927
+ "path": "/api/organizations/{orgId}/issue-drafts/{draftId}/submit",
3928
+ "operationId": "submitIssueDraft",
3929
+ "summary": "Submit a draft to the configured integration.",
3930
+ "tag": "Issue Drafts",
3931
+ "params": [
3932
+ "orgId",
3933
+ "draftId"
3934
+ ]
3935
+ },
3936
+ {
3937
+ "method": "POST",
3938
+ "path": "/api/organizations/{orgId}/projects/{projectId}/triage",
3939
+ "operationId": "runTriage",
3940
+ "summary": "Run LLM-powered triage on untriaged feedback.",
3941
+ "tag": "Triage",
3942
+ "params": [
3943
+ "orgId",
3944
+ "projectId"
3945
+ ]
3946
+ },
3947
+ {
3948
+ "method": "GET",
3949
+ "path": "/api/user/tokens",
3950
+ "operationId": "listUserTokens",
3951
+ "summary": "List personal access tokens.",
3952
+ "tag": "User Tokens",
3953
+ "params": []
3954
+ },
3955
+ {
3956
+ "method": "POST",
3957
+ "path": "/api/user/tokens",
3958
+ "operationId": "createUserToken",
3959
+ "summary": "Create a personal access token.",
3960
+ "tag": "User Tokens",
3961
+ "params": []
3962
+ },
3963
+ {
3964
+ "method": "DELETE",
3965
+ "path": "/api/user/tokens/{tokenId}",
3966
+ "operationId": "deleteUserToken",
3967
+ "summary": "Revoke and delete a personal access token.",
3968
+ "tag": "User Tokens",
3969
+ "params": [
3970
+ "tokenId"
3971
+ ]
3972
+ }
3973
+ ];
3974
+
3975
+ // src/scanner/cli/api.ts
3976
+ var METHODS = /* @__PURE__ */ new Set([
3977
+ "GET",
3978
+ "POST",
3979
+ "PUT",
3980
+ "PATCH",
3981
+ "DELETE",
3982
+ "HEAD",
3983
+ "OPTIONS"
3984
+ ]);
3985
+ var HELP = `uidex api \u2014 call the uidex API
3986
+
3987
+ Usage:
3988
+ uidex api <METHOD> <PATH> [flags] Make an API request
3989
+ uidex api --list [--tag <tag>] List available routes
3990
+ uidex api login Authenticate via browser
3991
+ uidex api login --token <tok> Store an auth token directly
3992
+ uidex api status Check auth state
3993
+
3994
+ Flags:
3995
+ --body <json> Request body (JSON string)
3996
+ --query <params> Query string (key=val&key2=val2)
3997
+ --base-url <url> Override the API base URL
3998
+ --token <tok> Use a specific token (instead of stored)
3999
+ --raw Disable JSON pretty-printing
4000
+ --no-color Disable colored output
4001
+ --list List available API routes
4002
+ --tag <tag> Filter --list by tag
4003
+ --json Emit --list as JSON
4004
+ `;
4005
+ async function runApiCommand(opts) {
4006
+ const { positional, flags } = parseArgs(opts.argv);
4007
+ const stdout = [];
4008
+ const stderr = [];
4009
+ const color = opts.color ?? (flags["no-color"] ? false : process.stdout.isTTY ?? false);
4010
+ const ctx = { flags, opts, color, stdout, stderr };
4011
+ const sub = positional[0];
4012
+ if (!sub && !flags.list) {
4013
+ stdout.push(HELP);
4014
+ return result(0, stdout, stderr);
4015
+ }
4016
+ if (flags.list) return listRoutes(ctx);
4017
+ if (sub === "help" || sub === "--help" || sub === "-h") {
4018
+ stdout.push(HELP);
4019
+ return result(0, stdout, stderr);
4020
+ }
4021
+ if (sub === "login") return runLogin(ctx);
4022
+ if (sub === "status") return runStatus(ctx);
4023
+ const method = sub?.toUpperCase();
4024
+ const path10 = positional[1];
4025
+ if (!method || !METHODS.has(method)) {
4026
+ stderr.push(`Unknown command or method: ${sub}`);
4027
+ stderr.push(HELP);
4028
+ return result(1, stdout, stderr);
4029
+ }
4030
+ if (!path10) {
4031
+ stderr.push("Missing path. Usage: uidex api GET /api/organizations");
4032
+ return result(1, stdout, stderr);
4033
+ }
4034
+ return runRequest(ctx, method, path10);
4035
+ }
4036
+ function listRoutes(ctx) {
4037
+ const { flags, color, stdout, stderr } = ctx;
4038
+ let routes = API_ROUTES;
4039
+ const tagFilter = flags.tag;
4040
+ if (typeof tagFilter === "string") {
4041
+ routes = routes.filter(
4042
+ (r) => r.tag.toLowerCase() === tagFilter.toLowerCase()
4043
+ );
4044
+ }
4045
+ if (flags.json) {
4046
+ stdout.push(JSON.stringify(routes, null, 2));
4047
+ return result(0, stdout, stderr);
4048
+ }
4049
+ const grouped = /* @__PURE__ */ new Map();
4050
+ for (const r of routes) {
4051
+ const tag = r.tag || "Other";
4052
+ let list = grouped.get(tag);
4053
+ if (!list) {
4054
+ list = [];
4055
+ grouped.set(tag, list);
4056
+ }
4057
+ list.push(r);
4058
+ }
4059
+ const methodPad = 7;
4060
+ for (const [tag, tagRoutes] of grouped) {
4061
+ stdout.push("");
4062
+ stdout.push(boldText(tag, color));
4063
+ for (const r of tagRoutes) {
4064
+ const m = r.method.padEnd(methodPad);
4065
+ const desc = r.summary ? dimText(` ${r.operationId} \u2014 ${r.summary}`, color) : dimText(` ${r.operationId}`, color);
4066
+ stdout.push(` ${m} ${r.path}${desc}`);
4067
+ }
4068
+ }
4069
+ stdout.push("");
4070
+ return result(0, stdout, stderr);
4071
+ }
4072
+ async function runLogin(ctx) {
4073
+ const { flags, opts, color, stdout, stderr } = ctx;
4074
+ const token = flags.token;
4075
+ if (typeof token === "string" && token.length > 0) {
4076
+ const storage = resolveTokenStorage(opts);
4077
+ storage.set(token);
4078
+ stdout.push("Token stored.");
4079
+ return result(0, stdout, stderr);
4080
+ }
4081
+ return runBrowserLogin(ctx);
4082
+ }
4083
+ async function runBrowserLogin(ctx) {
4084
+ const { flags, opts, color, stdout, stderr } = ctx;
4085
+ const http = await import("http");
4086
+ const crypto = await import("crypto");
4087
+ const { exec } = await import("child_process");
4088
+ const baseUrl = resolveBaseUrl(flags, opts);
4089
+ const code = crypto.randomBytes(3).toString("hex").toUpperCase();
4090
+ return new Promise((resolve7) => {
4091
+ let settled = false;
4092
+ const settle = (exitCode) => {
4093
+ if (settled) return;
4094
+ settled = true;
4095
+ server.close();
4096
+ clearTimeout(timeout);
4097
+ resolve7(result(exitCode, stdout, stderr));
4098
+ };
4099
+ const timeout = setTimeout(() => {
4100
+ stderr.push("Timed out waiting for browser authorization.");
4101
+ settle(1);
4102
+ }, 12e4);
4103
+ const server = http.createServer((req, res) => {
4104
+ if (!req.url?.startsWith("/callback")) {
4105
+ res.writeHead(404);
4106
+ res.end();
4107
+ return;
4108
+ }
4109
+ const url = new URL(req.url, `http://127.0.0.1`);
4110
+ const receivedToken = url.searchParams.get("token");
4111
+ const receivedCode = url.searchParams.get("code");
4112
+ if (receivedCode !== code) {
4113
+ res.writeHead(400, { "Content-Type": "application/json" });
4114
+ res.end(JSON.stringify({ error: "Code mismatch" }));
4115
+ stderr.push("Received callback with mismatched code. Aborting.");
4116
+ settle(1);
4117
+ return;
4118
+ }
4119
+ if (!receivedToken) {
4120
+ res.writeHead(400, { "Content-Type": "application/json" });
4121
+ res.end(JSON.stringify({ error: "Missing token" }));
4122
+ stderr.push("Received callback without token. Aborting.");
4123
+ settle(1);
4124
+ return;
4125
+ }
4126
+ res.writeHead(200, { "Content-Type": "application/json" });
4127
+ res.end(JSON.stringify({ ok: true }));
4128
+ const storage = resolveTokenStorage(opts);
4129
+ storage.set(receivedToken);
4130
+ stdout.push(dimText("Authenticated successfully.", color));
4131
+ settle(0);
4132
+ });
4133
+ server.listen(0, "127.0.0.1", () => {
4134
+ const addr = server.address();
4135
+ if (!addr || typeof addr === "string") {
4136
+ stderr.push("Failed to start local server.");
4137
+ settle(1);
4138
+ return;
4139
+ }
4140
+ const port = addr.port;
4141
+ const authUrl = `${baseUrl}/cli-auth?code=${encodeURIComponent(code)}&port=${port}`;
4142
+ stdout.push(dimText("Opening browser to authorize...", color));
4143
+ stdout.push("");
4144
+ stdout.push(` Confirmation code: ${boldText(code, color)}`);
4145
+ stdout.push("");
4146
+ stdout.push(
4147
+ dimText("If the browser doesn't open, visit this URL manually:", color)
4148
+ );
4149
+ stdout.push(` ${authUrl}`);
4150
+ stdout.push("");
4151
+ process.stdout.write(result(0, stdout, stderr).stdout);
4152
+ stdout.length = 0;
4153
+ openBrowser(authUrl);
4154
+ });
4155
+ server.on("error", (err2) => {
4156
+ stderr.push(`Failed to start local server: ${err2.message}`);
4157
+ settle(1);
4158
+ });
4159
+ });
4160
+ }
4161
+ function openBrowser(url) {
4162
+ const { exec } = require("child_process");
4163
+ const platform = process.platform;
4164
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
4165
+ exec(`${cmd} ${JSON.stringify(url)}`);
4166
+ }
4167
+ function runStatus(ctx) {
4168
+ const { flags, opts, stdout, stderr } = ctx;
4169
+ const storage = resolveTokenStorage(opts);
4170
+ const baseUrl = resolveBaseUrl(flags, opts);
4171
+ const token = storage.get();
4172
+ stdout.push(`baseUrl: ${baseUrl}`);
4173
+ stdout.push(`auth: ${token ? "authenticated" : "not authenticated"}`);
4174
+ return result(token ? 0 : 1, stdout, stderr);
4175
+ }
4176
+ async function runRequest(ctx, method, path10) {
4177
+ const { flags, opts, color, stdout, stderr } = ctx;
4178
+ const tokenFromFlag = flags.token;
4179
+ const token = typeof tokenFromFlag === "string" ? tokenFromFlag : resolveTokenStorage(opts).get();
4180
+ if (!token) {
4181
+ stderr.push("Not authenticated. Run: uidex api login --token <value>");
4182
+ return result(1, stdout, stderr);
4183
+ }
4184
+ const baseUrl = resolveBaseUrl(flags, opts);
4185
+ const http = opts.http ?? createHttpClient({ baseUrl, token: () => token });
4186
+ const bodyStr = typeof flags.body === "string" ? flags.body : void 0;
4187
+ if (bodyStr !== void 0) {
4188
+ try {
4189
+ JSON.parse(bodyStr);
4190
+ } catch {
4191
+ stderr.push("Invalid JSON in --body");
4192
+ return result(1, stdout, stderr);
4193
+ }
4194
+ }
4195
+ const res = await http.request(path10, {
4196
+ method,
4197
+ body: bodyStr,
4198
+ query: typeof flags.query === "string" ? flags.query : void 0
4199
+ });
4200
+ const isSuccess = res.status >= 200 && res.status < 300;
4201
+ if (!isSuccess) {
4202
+ stderr.push(formatStatus(res.status, color));
4203
+ }
4204
+ if (res.body !== void 0 && res.body !== null && res.body !== "") {
4205
+ if (flags.raw) {
4206
+ stdout.push(res.raw);
4207
+ } else {
4208
+ stdout.push(highlightJson(res.body, color));
4209
+ }
4210
+ }
4211
+ return result(isSuccess ? 0 : 1, stdout, stderr);
4212
+ }
4213
+ function resolveTokenStorage(opts) {
4214
+ if (opts.tokenStorage) return opts.tokenStorage;
4215
+ const envToken = opts.env?.UIDEX_TOKEN;
4216
+ if (envToken) return createMemoryTokenStorage(envToken);
4217
+ return createFileTokenStorage({ path: defaultTokenPath() });
4218
+ }
4219
+ function resolveBaseUrl(flags, opts) {
4220
+ return (typeof flags["base-url"] === "string" ? flags["base-url"] : void 0) ?? opts.baseUrl ?? opts.env?.UIDEX_API_URL ?? "https://app.uidex.dev";
4221
+ }
4222
+ function result(exitCode, stdout, stderr) {
4223
+ return {
4224
+ exitCode,
4225
+ stdout: stdout.join("\n") + (stdout.length ? "\n" : ""),
4226
+ stderr: stderr.join("\n") + (stderr.length ? "\n" : "")
4227
+ };
4228
+ }
4229
+
4230
+ // src/scanner/cli/cli.ts
4231
+ var argv = process.argv.slice(2);
4232
+ var command = argv[0];
4233
+ var resultP = command === "api" ? runApiCommand({ argv: argv.slice(1) }) : run({ argv });
4234
+ resultP.then(
4235
+ (result2) => {
4236
+ if (result2.stdout) process.stdout.write(result2.stdout);
4237
+ if (result2.stderr) process.stderr.write(result2.stderr);
4238
+ process.exit(result2.exitCode);
3235
4239
  },
3236
4240
  (error) => {
3237
4241
  process.stderr.write(