uidex 0.4.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 (42) hide show
  1. package/dist/cli/cli.cjs +996 -73
  2. package/dist/cli/cli.cjs.map +1 -1
  3. package/dist/cloud/index.cjs +375 -72
  4. package/dist/cloud/index.cjs.map +1 -1
  5. package/dist/cloud/index.d.cts +82 -0
  6. package/dist/cloud/index.d.ts +82 -0
  7. package/dist/cloud/index.js +376 -71
  8. package/dist/cloud/index.js.map +1 -1
  9. package/dist/headless/index.cjs +620 -469
  10. package/dist/headless/index.cjs.map +1 -1
  11. package/dist/headless/index.d.cts +77 -75
  12. package/dist/headless/index.d.ts +77 -75
  13. package/dist/headless/index.js +624 -469
  14. package/dist/headless/index.js.map +1 -1
  15. package/dist/index.cjs +4255 -2884
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +275 -234
  18. package/dist/index.d.ts +275 -234
  19. package/dist/index.js +4277 -2890
  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 +4298 -2908
  30. package/dist/react/index.cjs.map +1 -1
  31. package/dist/react/index.d.cts +206 -200
  32. package/dist/react/index.d.ts +206 -200
  33. package/dist/react/index.js +4334 -2924
  34. package/dist/react/index.js.map +1 -1
  35. package/dist/scan/index.cjs +91 -40
  36. package/dist/scan/index.cjs.map +1 -1
  37. package/dist/scan/index.d.cts +26 -0
  38. package/dist/scan/index.d.ts +26 -0
  39. package/dist/scan/index.js +90 -39
  40. package/dist/scan/index.js.map +1 -1
  41. package/package.json +23 -22
  42. package/templates/claude/api.md +110 -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
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,11 +235,11 @@ 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";
243
244
  var WELL_KNOWN_FILES = {
244
245
  page: "uidex.page.ts",
@@ -402,7 +403,7 @@ var DEFAULT_CONVENTIONS = {
402
403
  regions: "landmarks"
403
404
  };
404
405
 
405
- // src/scan/discover.ts
406
+ // src/scanner/scan/discover.ts
406
407
  var CONFIG_FILENAME = ".uidex.json";
407
408
  var SKIP_DIRS = /* @__PURE__ */ new Set([
408
409
  "node_modules",
@@ -461,14 +462,14 @@ function discover(options = {}) {
461
462
  return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
462
463
  }
463
464
 
464
- // src/scan/pipeline.ts
465
+ // src/scanner/scan/pipeline.ts
465
466
  var fs5 = __toESM(require("fs"), 1);
466
467
  var path7 = __toESM(require("path"), 1);
467
468
 
468
- // src/scan/audit.ts
469
+ // src/scanner/scan/audit.ts
469
470
  var path4 = __toESM(require("path"), 1);
470
471
 
471
- // src/entities/types.ts
472
+ // src/shared/entities/types.ts
472
473
  var ENTITY_KINDS = [
473
474
  "route",
474
475
  "page",
@@ -501,7 +502,7 @@ function assertEntityKind(kind) {
501
502
  if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
502
503
  }
503
504
 
504
- // src/entities/registry.ts
505
+ // src/shared/entities/registry.ts
505
506
  function emptyStore() {
506
507
  return {
507
508
  route: /* @__PURE__ */ new Map(),
@@ -565,11 +566,11 @@ function createRegistry() {
565
566
  };
566
567
  const query = (predicate) => {
567
568
  const flows = getFlows();
568
- const result = [];
569
+ const result2 = [];
569
570
  for (const entity of allEntities()) {
570
- if (predicate(entity)) result.push(freezeEntity(entity, flows));
571
+ if (predicate(entity)) result2.push(freezeEntity(entity, flows));
571
572
  }
572
- return result;
573
+ return result2;
573
574
  };
574
575
  const byScope = (scope) => query(
575
576
  (entity) => "scopes" in entity && Array.isArray(entity.scopes) && entity.scopes.includes(scope)
@@ -583,10 +584,33 @@ function createRegistry() {
583
584
  return ids.has(entity.id);
584
585
  });
585
586
  };
586
- 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
+ };
587
611
  }
588
612
 
589
- // src/scan/audit.ts
613
+ // src/scanner/scan/audit.ts
590
614
  var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
591
615
  function audit(opts) {
592
616
  const diagnostics = [];
@@ -740,6 +764,15 @@ function audit(opts) {
740
764
  const primitives = registry.list("primitive");
741
765
  const byName = /* @__PURE__ */ new Map();
742
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
+ }
743
776
  for (const f of files) {
744
777
  const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
745
778
  let m;
@@ -754,14 +787,19 @@ function audit(opts) {
754
787
  if (!scope) continue;
755
788
  const [kind, id] = scope.split(":");
756
789
  const importerSegments = f.displayPath.split("/");
757
- if (!importerSegments.includes(id) || !importerSegments.includes(kind + "s")) {
758
- diagnostics.push({
759
- code: "scope-leak",
760
- severity: "warning",
761
- message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
762
- file: f.displayPath
763
- });
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;
764
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
+ });
765
803
  }
766
804
  }
767
805
  }
@@ -935,7 +973,7 @@ function stableReplacer(_key, value) {
935
973
  return value;
936
974
  }
937
975
 
938
- // src/scan/emit.ts
976
+ // src/scanner/scan/emit.ts
939
977
  function sortById(arr) {
940
978
  return [...arr].sort((a, b) => a.id.localeCompare(b.id));
941
979
  }
@@ -1059,6 +1097,7 @@ function emit(opts) {
1059
1097
  lines.push(" export interface Feature {");
1060
1098
  lines.push(" feature: FeatureId | false");
1061
1099
  lines.push(" name?: string");
1100
+ lines.push(" features?: readonly FeatureId[]");
1062
1101
  lines.push(" acceptance?: readonly string[]");
1063
1102
  lines.push(" description?: string");
1064
1103
  lines.push(" }");
@@ -1115,7 +1154,7 @@ function emit(opts) {
1115
1154
  return lines.join("\n");
1116
1155
  }
1117
1156
 
1118
- // src/scan/extract-uidex-export.ts
1157
+ // src/scanner/scan/extract-uidex-export.ts
1119
1158
  var KIND_DISCRIMINATORS = [
1120
1159
  "page",
1121
1160
  "feature",
@@ -1133,7 +1172,13 @@ var ALLOWED_FIELDS = {
1133
1172
  "acceptance",
1134
1173
  "description"
1135
1174
  ]),
1136
- feature: /* @__PURE__ */ new Set(["feature", "name", "acceptance", "description"]),
1175
+ feature: /* @__PURE__ */ new Set([
1176
+ "feature",
1177
+ "name",
1178
+ "features",
1179
+ "acceptance",
1180
+ "description"
1181
+ ]),
1137
1182
  primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
1138
1183
  widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
1139
1184
  flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
@@ -1869,7 +1914,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
1869
1914
  line: pos.line
1870
1915
  });
1871
1916
  }
1872
- const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
1917
+ const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
1873
1918
  const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
1874
1919
  const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
1875
1920
  const metadata = {
@@ -1964,7 +2009,7 @@ function posAt(content, offset) {
1964
2009
  return { offset, line, column: offset - lineStart + 1 };
1965
2010
  }
1966
2011
 
1967
- // src/scan/jsx-ancestry.ts
2012
+ // src/scanner/scan/jsx-ancestry.ts
1968
2013
  var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
1969
2014
  function parseDataAttrs(tagSource) {
1970
2015
  if (!tagSource.includes("data-uidex")) return [];
@@ -2153,7 +2198,7 @@ function findTagEnd(content, start) {
2153
2198
  return -1;
2154
2199
  }
2155
2200
 
2156
- // src/scan/extract.ts
2201
+ // src/scanner/scan/extract.ts
2157
2202
  var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
2158
2203
  function lineAt(content, index) {
2159
2204
  let line = 1;
@@ -2256,7 +2301,7 @@ function extractOne(file) {
2256
2301
  return annotations;
2257
2302
  }
2258
2303
 
2259
- // src/scan/git.ts
2304
+ // src/scanner/scan/git.ts
2260
2305
  var import_node_child_process = require("child_process");
2261
2306
  function runGit(args, cwd) {
2262
2307
  try {
@@ -2288,10 +2333,10 @@ function parseGitHubRef(ref) {
2288
2333
  return m ? m[1] : null;
2289
2334
  }
2290
2335
 
2291
- // src/scan/resolve.ts
2336
+ // src/scanner/scan/resolve.ts
2292
2337
  var path6 = __toESM(require("path"), 1);
2293
2338
 
2294
- // src/scan/routes.ts
2339
+ // src/scanner/scan/routes.ts
2295
2340
  var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
2296
2341
  var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
2297
2342
  var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
@@ -2357,7 +2402,7 @@ function pathToId(routePath) {
2357
2402
  return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2358
2403
  }
2359
2404
 
2360
- // src/scan/walk.ts
2405
+ // src/scanner/scan/walk.ts
2361
2406
  var fs4 = __toESM(require("fs"), 1);
2362
2407
  var path5 = __toESM(require("path"), 1);
2363
2408
  var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
@@ -2482,7 +2527,7 @@ function* walkDir(root, dir) {
2482
2527
  }
2483
2528
  }
2484
2529
 
2485
- // src/scan/resolve.ts
2530
+ // src/scanner/scan/resolve.ts
2486
2531
  var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
2487
2532
  "element",
2488
2533
  "region",
@@ -2967,7 +3012,7 @@ function dedupe(arr) {
2967
3012
  return Array.from(new Set(arr));
2968
3013
  }
2969
3014
 
2970
- // src/scan/pipeline.ts
3015
+ // src/scanner/scan/pipeline.ts
2971
3016
  function runScan(opts = {}) {
2972
3017
  const cwd = opts.cwd ?? process.cwd();
2973
3018
  const configs = opts.configs ?? discover({ cwd });
@@ -3034,12 +3079,12 @@ function runOne(dc, opts) {
3034
3079
  outputPath
3035
3080
  };
3036
3081
  }
3037
- function writeScanResult(result) {
3038
- fs5.mkdirSync(path7.dirname(result.outputPath), { recursive: true });
3039
- 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");
3040
3085
  }
3041
3086
 
3042
- // src/scan/scaffold.ts
3087
+ // src/scanner/scan/scaffold.ts
3043
3088
  var fs6 = __toESM(require("fs"), 1);
3044
3089
  var path8 = __toESM(require("path"), 1);
3045
3090
  function scaffoldWidgetSpec(opts) {
@@ -3102,8 +3147,8 @@ function renderSpec(args) {
3102
3147
  return lines.join("\n");
3103
3148
  }
3104
3149
 
3105
- // src/scan/cli.ts
3106
- function parseFlags2(args) {
3150
+ // src/scanner/cli/parse-args.ts
3151
+ function parseArgs(args) {
3107
3152
  const positional = [];
3108
3153
  const flags = {};
3109
3154
  for (let i = 0; i < args.length; i++) {
@@ -3127,13 +3172,15 @@ function parseFlags2(args) {
3127
3172
  }
3128
3173
  return { positional, flags };
3129
3174
  }
3175
+
3176
+ // src/scanner/scan/cli.ts
3130
3177
  async function run(opts) {
3131
3178
  const cwd = opts.cwd ?? process.cwd();
3132
- const { positional, flags } = parseFlags2(opts.argv);
3133
- const command = positional[0] ?? "help";
3179
+ const { positional, flags } = parseArgs(opts.argv);
3180
+ const command2 = positional[0] ?? "help";
3134
3181
  const writer = createWriter();
3135
3182
  try {
3136
- switch (command) {
3183
+ switch (command2) {
3137
3184
  case "help":
3138
3185
  case "--help":
3139
3186
  case "-h":
@@ -3146,16 +3193,16 @@ async function run(opts) {
3146
3193
  case "scaffold":
3147
3194
  return runScaffold(cwd, positional.slice(1), flags, writer);
3148
3195
  case "ai": {
3149
- const result = await runAiCommand({
3196
+ const result2 = await runAiCommand({
3150
3197
  cwd,
3151
3198
  argv: opts.argv.slice(1)
3152
3199
  });
3153
- if (result.stdout) writer.out(result.stdout.replace(/\n$/, ""));
3154
- if (result.stderr) writer.err(result.stderr.replace(/\n$/, ""));
3155
- 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);
3156
3203
  }
3157
3204
  default:
3158
- writer.err(`Unknown command: ${command}`);
3205
+ writer.err(`Unknown command: ${command2}`);
3159
3206
  writer.err(helpText2());
3160
3207
  return writer.result(1);
3161
3208
  }
@@ -3173,6 +3220,10 @@ function helpText2() {
3173
3220
  " scan [flags] Run the scanner pipeline",
3174
3221
  " scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
3175
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",
3176
3227
  "",
3177
3228
  "Flags:",
3178
3229
  " --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",
@@ -3275,17 +3326,17 @@ function runScaffold(cwd, args, flags, w) {
3275
3326
  const widget = r.registry.get("widget", id);
3276
3327
  if (!widget) continue;
3277
3328
  const outDir = path9.resolve(r.configDir, "e2e");
3278
- const result = scaffoldWidgetSpec({
3329
+ const result2 = scaffoldWidgetSpec({
3279
3330
  registry: r.registry,
3280
3331
  widgetId: id,
3281
3332
  outDir,
3282
3333
  force: Boolean(flags.force)
3283
3334
  });
3284
- if (result.skipped) {
3285
- w.err(result.reason ?? "skipped");
3335
+ if (result2.skipped) {
3336
+ w.err(result2.reason ?? "skipped");
3286
3337
  return w.result(1);
3287
3338
  }
3288
- w.out(`Wrote ${result.outputPath}`);
3339
+ w.out(`Wrote ${result2.outputPath}`);
3289
3340
  return w.result(0);
3290
3341
  }
3291
3342
  w.err(`Widget "${id}" not found in registry`);
@@ -3307,12 +3358,884 @@ function createWriter() {
3307
3358
  };
3308
3359
  }
3309
3360
 
3310
- // src/cli/cli.ts
3311
- run({ argv: process.argv.slice(2) }).then(
3312
- (result) => {
3313
- if (result.stdout) process.stdout.write(result.stdout);
3314
- if (result.stderr) process.stderr.write(result.stderr);
3315
- 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);
3316
4239
  },
3317
4240
  (error) => {
3318
4241
  process.stderr.write(