uidex 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli/cli.cjs +1111 -87
  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 +623 -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 +627 -469
  14. package/dist/headless/index.js.map +1 -1
  15. package/dist/index.cjs +4258 -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 +4280 -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 +4299 -2906
  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 +4339 -2926
  34. package/dist/react/index.js.map +1 -1
  35. package/dist/scan/index.cjs +201 -49
  36. package/dist/scan/index.cjs.map +1 -1
  37. package/dist/scan/index.d.cts +27 -1
  38. package/dist/scan/index.d.ts +27 -1
  39. package/dist/scan/index.js +200 -48
  40. package/dist/scan/index.js.map +1 -1
  41. package/package.json +8 -14
  42. package/templates/claude/api.md +110 -0
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // src/scan/index.ts
30
+ // src/scanner/scan/index.ts
31
31
  var scan_exports = {};
32
32
  __export(scan_exports, {
33
33
  CONFIG_FILENAME: () => CONFIG_FILENAME,
@@ -54,11 +54,11 @@ __export(scan_exports, {
54
54
  });
55
55
  module.exports = __toCommonJS(scan_exports);
56
56
 
57
- // src/scan/discover.ts
57
+ // src/scanner/scan/discover.ts
58
58
  var fs = __toESM(require("fs"), 1);
59
59
  var path = __toESM(require("path"), 1);
60
60
 
61
- // src/scan/config.ts
61
+ // src/scanner/scan/config.ts
62
62
  var DEFAULT_TYPE_MODE = "strict";
63
63
  var WELL_KNOWN_FILES = {
64
64
  page: "uidex.page.ts",
@@ -222,7 +222,7 @@ var DEFAULT_CONVENTIONS = {
222
222
  regions: "landmarks"
223
223
  };
224
224
 
225
- // src/scan/discover.ts
225
+ // src/scanner/scan/discover.ts
226
226
  var CONFIG_FILENAME = ".uidex.json";
227
227
  var SKIP_DIRS = /* @__PURE__ */ new Set([
228
228
  "node_modules",
@@ -281,7 +281,7 @@ function discover(options = {}) {
281
281
  return results.sort((a, b) => a.configPath.localeCompare(b.configPath));
282
282
  }
283
283
 
284
- // src/scan/walk.ts
284
+ // src/scanner/scan/walk.ts
285
285
  var fs2 = __toESM(require("fs"), 1);
286
286
  var path2 = __toESM(require("path"), 1);
287
287
  var DEFAULT_INCLUDES = ["**/*.{ts,tsx,js,jsx,mjs,cjs}"];
@@ -406,12 +406,13 @@ function* walkDir(root, dir) {
406
406
  }
407
407
  }
408
408
 
409
- // src/scan/extract-uidex-export.ts
409
+ // src/scanner/scan/extract-uidex-export.ts
410
410
  var KIND_DISCRIMINATORS = [
411
411
  "page",
412
412
  "feature",
413
413
  "primitive",
414
414
  "widget",
415
+ "region",
415
416
  "flow",
416
417
  "notFlow"
417
418
  ];
@@ -424,15 +425,23 @@ var ALLOWED_FIELDS = {
424
425
  "acceptance",
425
426
  "description"
426
427
  ]),
427
- feature: /* @__PURE__ */ new Set(["feature", "name", "acceptance", "description"]),
428
+ feature: /* @__PURE__ */ new Set([
429
+ "feature",
430
+ "name",
431
+ "features",
432
+ "acceptance",
433
+ "description"
434
+ ]),
428
435
  primitive: /* @__PURE__ */ new Set(["primitive", "name", "description"]),
429
436
  widget: /* @__PURE__ */ new Set(["widget", "name", "acceptance", "description"]),
437
+ region: /* @__PURE__ */ new Set(["region", "name", "description"]),
430
438
  flow: /* @__PURE__ */ new Set(["flow", "notFlow", "name", "description"])
431
439
  };
432
440
  var FALSEABLE = /* @__PURE__ */ new Set([
433
441
  "page",
434
442
  "feature",
435
- "primitive"
443
+ "primitive",
444
+ "region"
436
445
  ]);
437
446
  var ExtractError = class extends Error {
438
447
  code;
@@ -1160,7 +1169,7 @@ function buildMetadata(value, file, headerPos, diagnostics) {
1160
1169
  line: pos.line
1161
1170
  });
1162
1171
  }
1163
- const features = kind === "page" ? readStringArrayField(byKey, "features") : void 0;
1172
+ const features = kind === "page" || kind === "feature" ? readStringArrayField(byKey, "features") : void 0;
1164
1173
  const widgets = kind === "page" ? readStringArrayField(byKey, "widgets") : void 0;
1165
1174
  const notFlow = kind === "flow" && discriminator === "notFlow" ? true : void 0;
1166
1175
  const metadata = {
@@ -1255,7 +1264,7 @@ function posAt(content, offset) {
1255
1264
  return { offset, line, column: offset - lineStart + 1 };
1256
1265
  }
1257
1266
 
1258
- // src/scan/jsx-ancestry.ts
1267
+ // src/scanner/scan/jsx-ancestry.ts
1259
1268
  var DATA_ATTR_RE = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
1260
1269
  function parseDataAttrs(tagSource) {
1261
1270
  if (!tagSource.includes("data-uidex")) return [];
@@ -1444,7 +1453,7 @@ function findTagEnd(content, start) {
1444
1453
  return -1;
1445
1454
  }
1446
1455
 
1447
- // src/scan/extract.ts
1456
+ // src/scanner/scan/extract.ts
1448
1457
  var JSDOC_BLOCK = /\/\*\*([\s\S]*?)\*\//g;
1449
1458
  function lineAt(content, index) {
1450
1459
  let line = 1;
@@ -1547,10 +1556,10 @@ function extractOne(file) {
1547
1556
  return annotations;
1548
1557
  }
1549
1558
 
1550
- // src/scan/resolve.ts
1559
+ // src/scanner/scan/resolve.ts
1551
1560
  var path3 = __toESM(require("path"), 1);
1552
1561
 
1553
- // src/entities/types.ts
1562
+ // src/shared/entities/types.ts
1554
1563
  var ENTITY_KINDS = [
1555
1564
  "route",
1556
1565
  "page",
@@ -1583,7 +1592,7 @@ function assertEntityKind(kind) {
1583
1592
  if (!KIND_SET.has(kind)) throw new UnknownEntityKindError(kind);
1584
1593
  }
1585
1594
 
1586
- // src/entities/registry.ts
1595
+ // src/shared/entities/registry.ts
1587
1596
  function emptyStore() {
1588
1597
  return {
1589
1598
  route: /* @__PURE__ */ new Map(),
@@ -1665,10 +1674,33 @@ function createRegistry() {
1665
1674
  return ids.has(entity.id);
1666
1675
  });
1667
1676
  };
1668
- return { add, get, list, query, byScope, touchedBy };
1677
+ const reports = /* @__PURE__ */ new Map();
1678
+ const reportsCbs = /* @__PURE__ */ new Set();
1679
+ const setReports = (kind, id, records) => {
1680
+ reports.set(`${kind}:${id}`, records);
1681
+ for (const cb of reportsCbs) cb();
1682
+ };
1683
+ const getReports = (kind, id) => reports.get(`${kind}:${id}`) ?? [];
1684
+ const listReportKeys = () => Array.from(reports.keys());
1685
+ const onReportsChange = (cb) => {
1686
+ reportsCbs.add(cb);
1687
+ return () => reportsCbs.delete(cb);
1688
+ };
1689
+ return {
1690
+ add,
1691
+ get,
1692
+ list,
1693
+ query,
1694
+ byScope,
1695
+ touchedBy,
1696
+ setReports,
1697
+ getReports,
1698
+ listReportKeys,
1699
+ onReportsChange
1700
+ };
1669
1701
  }
1670
1702
 
1671
- // src/scan/routes.ts
1703
+ // src/scanner/scan/routes.ts
1672
1704
  var PAGE_BASENAME = /^page\.(tsx|ts|jsx|js|mjs|cjs)$/;
1673
1705
  var PAGES_ROUTER_BASENAME = /\.(tsx|ts|jsx|js|mjs|cjs)$/;
1674
1706
  var ROUTE_BASENAME = /^route\.(tsx|ts|jsx|js|mjs|cjs)$/;
@@ -1734,7 +1766,7 @@ function pathToId(routePath) {
1734
1766
  return routePath.replace(/^\/+/, "").replace(/\[\.{3}([^\]]+)\]/g, "$1").replace(/\[([^\]]+)\]/g, "$1").replace(/\//g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1735
1767
  }
1736
1768
 
1737
- // src/scan/resolve.ts
1769
+ // src/scanner/scan/resolve.ts
1738
1770
  var DOM_ATTR_KINDS = /* @__PURE__ */ new Set([
1739
1771
  "element",
1740
1772
  "region",
@@ -2041,6 +2073,21 @@ function resolve2(ctx) {
2041
2073
  };
2042
2074
  registry.add(region);
2043
2075
  }
2076
+ for (const ef of ctx.extracted) {
2077
+ const exp = exportFor(ef.file.displayPath, "region");
2078
+ if (!exp) continue;
2079
+ if (exp.id === false) continue;
2080
+ if (typeof exp.id === "string" && !registry.get("region", exp.id)) {
2081
+ const meta = buildMetaFromExport(exp);
2082
+ const region = {
2083
+ kind: "region",
2084
+ id: exp.id,
2085
+ loc: { file: ef.file.displayPath, line: exp.loc.line },
2086
+ ...meta ? { meta } : {}
2087
+ };
2088
+ registry.add(region);
2089
+ }
2090
+ }
2044
2091
  const primitiveConventions = conventions.primitives;
2045
2092
  for (const ef of ctx.extracted) {
2046
2093
  const file = ef.file.displayPath;
@@ -2219,7 +2266,7 @@ function dedupe(arr) {
2219
2266
  return Array.from(new Set(arr));
2220
2267
  }
2221
2268
 
2222
- // src/scan/audit.ts
2269
+ // src/scanner/scan/audit.ts
2223
2270
  var path4 = __toESM(require("path"), 1);
2224
2271
  var MARKER_FILENAMES = ["UIDEX_PAGE.md", "UIDEX_FEATURE.md"];
2225
2272
  function audit(opts) {
@@ -2349,17 +2396,38 @@ function audit(opts) {
2349
2396
  }
2350
2397
  }
2351
2398
  if (lint) {
2399
+ const dynamicAttrRe = /\bdata-uidex(?:-(region|widget|primitive))?\s*=\s*\{/g;
2352
2400
  for (const f of files) {
2353
- const lines = f.content.split("\n");
2354
- const candidateRe = /<(button|a|input|select|textarea)(?=[\s/>])([^>]*)>/g;
2355
2401
  let m;
2356
- while ((m = candidateRe.exec(f.content)) !== null) {
2357
- const attrs = m[2];
2358
- if (attrs && attrs.includes("data-uidex")) continue;
2359
- const idx = m.index;
2402
+ dynamicAttrRe.lastIndex = 0;
2403
+ while ((m = dynamicAttrRe.exec(f.content)) !== null) {
2404
+ const kind = m[1] ?? "element";
2360
2405
  let line = 1;
2361
- for (let i = 0; i < idx; i++) if (f.content[i] === "\n") line++;
2362
- void lines;
2406
+ for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
2407
+ const attrName = m[1] ? `data-uidex-${m[1]}` : "data-uidex";
2408
+ diagnostics.push({
2409
+ code: "dynamic-attr",
2410
+ severity: "warning",
2411
+ message: `\`${attrName}={\u2026}\` uses a dynamic expression; the scanner cannot resolve the ${kind} id statically`,
2412
+ file: f.displayPath,
2413
+ line,
2414
+ hint: dynamicAttrHint(kind)
2415
+ });
2416
+ }
2417
+ }
2418
+ }
2419
+ if (lint) {
2420
+ for (const f of files) {
2421
+ const tagRe = /<(button|a|input|select|textarea)(?=[\s/>])/g;
2422
+ let m;
2423
+ while ((m = tagRe.exec(f.content)) !== null) {
2424
+ const afterTag = m.index + m[0].length;
2425
+ const closeIdx = findJsxOpeningEnd(f.content, afterTag);
2426
+ if (closeIdx === -1) continue;
2427
+ const attrs = f.content.slice(afterTag, closeIdx);
2428
+ if (attrs.includes("data-uidex")) continue;
2429
+ let line = 1;
2430
+ for (let i = 0; i < m.index; i++) if (f.content[i] === "\n") line++;
2363
2431
  diagnostics.push({
2364
2432
  code: "missing-element-annotation",
2365
2433
  severity: "info",
@@ -2374,6 +2442,15 @@ function audit(opts) {
2374
2442
  const primitives = registry.list("primitive");
2375
2443
  const byName = /* @__PURE__ */ new Map();
2376
2444
  for (const p2 of primitives) byName.set(p2.id, p2);
2445
+ const declaredFeatures = /* @__PURE__ */ new Map();
2446
+ for (const ef of extracted) {
2447
+ if (!ef.metadata) continue;
2448
+ for (const m of ef.metadata) {
2449
+ if (m.features && m.features.length > 0) {
2450
+ declaredFeatures.set(ef.file.displayPath, new Set(m.features));
2451
+ }
2452
+ }
2453
+ }
2377
2454
  for (const f of files) {
2378
2455
  const importRe = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
2379
2456
  let m;
@@ -2388,14 +2465,19 @@ function audit(opts) {
2388
2465
  if (!scope) continue;
2389
2466
  const [kind, id] = scope.split(":");
2390
2467
  const importerSegments = f.displayPath.split("/");
2391
- if (!importerSegments.includes(id) || !importerSegments.includes(kind + "s")) {
2392
- diagnostics.push({
2393
- code: "scope-leak",
2394
- severity: "warning",
2395
- message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
2396
- file: f.displayPath
2397
- });
2468
+ if (importerSegments.includes(id) && importerSegments.includes(kind + "s")) {
2469
+ continue;
2470
+ }
2471
+ if (kind === "feature" && importerSegments.includes(id)) continue;
2472
+ if (kind === "feature" && declaredFeatures.get(f.displayPath)?.has(id)) {
2473
+ continue;
2398
2474
  }
2475
+ diagnostics.push({
2476
+ code: "scope-leak",
2477
+ severity: "warning",
2478
+ message: `Primitive "${primitive.id}" is scoped to ${scope} but is imported from ${f.displayPath}`,
2479
+ file: f.displayPath
2480
+ });
2399
2481
  }
2400
2482
  }
2401
2483
  }
@@ -2555,6 +2637,63 @@ function extractEntitiesArray(source) {
2555
2637
  }
2556
2638
  return null;
2557
2639
  }
2640
+ function findJsxOpeningEnd(src, start) {
2641
+ let i = start;
2642
+ while (i < src.length) {
2643
+ const ch = src[i];
2644
+ if (ch === ">" || ch === "/" && src[i + 1] === ">") return i;
2645
+ if (ch === '"' || ch === "'" || ch === "`") {
2646
+ i = skipString2(src, i);
2647
+ } else if (ch === "{") {
2648
+ i = skipBraces(src, i);
2649
+ } else {
2650
+ i++;
2651
+ }
2652
+ }
2653
+ return -1;
2654
+ }
2655
+ function skipString2(src, start) {
2656
+ const quote = src[start];
2657
+ let i = start + 1;
2658
+ while (i < src.length) {
2659
+ if (src[i] === "\\" && quote !== "`") {
2660
+ i += 2;
2661
+ continue;
2662
+ }
2663
+ if (quote === "`" && src[i] === "$" && src[i + 1] === "{") {
2664
+ i = skipBraces(src, i + 1);
2665
+ continue;
2666
+ }
2667
+ if (src[i] === quote) return i + 1;
2668
+ i++;
2669
+ }
2670
+ return i;
2671
+ }
2672
+ function skipBraces(src, start) {
2673
+ let depth = 1;
2674
+ let i = start + 1;
2675
+ while (i < src.length && depth > 0) {
2676
+ const ch = src[i];
2677
+ if (ch === "{") {
2678
+ depth++;
2679
+ i++;
2680
+ } else if (ch === "}") {
2681
+ depth--;
2682
+ i++;
2683
+ } else if (ch === '"' || ch === "'" || ch === "`") {
2684
+ i = skipString2(src, i);
2685
+ } else {
2686
+ i++;
2687
+ }
2688
+ }
2689
+ return i;
2690
+ }
2691
+ function dynamicAttrHint(kind) {
2692
+ if (kind === "region") {
2693
+ return `Use a string literal: \`data-uidex-region="id"\`, or declare the region via \`export const uidex = { region: "id" } as const satisfies Uidex.Region\` on the file that passes the region value`;
2694
+ }
2695
+ return `The scanner requires string-literal attribute values. If this component forwards the annotation via a prop, restructure so the caller provides the annotated element directly (e.g. via a slot or render prop) with a string-literal \`data-uidex\` attribute`;
2696
+ }
2558
2697
  function stableStringify(value) {
2559
2698
  return JSON.stringify(value, stableReplacer);
2560
2699
  }
@@ -2569,7 +2708,7 @@ function stableReplacer(_key, value) {
2569
2708
  return value;
2570
2709
  }
2571
2710
 
2572
- // src/scan/emit.ts
2711
+ // src/scanner/scan/emit.ts
2573
2712
  function sortById(arr) {
2574
2713
  return [...arr].sort((a, b) => a.id.localeCompare(b.id));
2575
2714
  }
@@ -2693,6 +2832,7 @@ function emit(opts) {
2693
2832
  lines.push(" export interface Feature {");
2694
2833
  lines.push(" feature: FeatureId | false");
2695
2834
  lines.push(" name?: string");
2835
+ lines.push(" features?: readonly FeatureId[]");
2696
2836
  lines.push(" acceptance?: readonly string[]");
2697
2837
  lines.push(" description?: string");
2698
2838
  lines.push(" }");
@@ -2707,6 +2847,11 @@ function emit(opts) {
2707
2847
  lines.push(" acceptance?: readonly string[]");
2708
2848
  lines.push(" description?: string");
2709
2849
  lines.push(" }");
2850
+ lines.push(" export interface Region {");
2851
+ lines.push(" region: RegionId | false");
2852
+ lines.push(" name?: string");
2853
+ lines.push(" description?: string");
2854
+ lines.push(" }");
2710
2855
  lines.push(" export interface Flow {");
2711
2856
  lines.push(" flow: FlowId");
2712
2857
  lines.push(" name?: string");
@@ -2749,7 +2894,7 @@ function emit(opts) {
2749
2894
  return lines.join("\n");
2750
2895
  }
2751
2896
 
2752
- // src/scan/git.ts
2897
+ // src/scanner/scan/git.ts
2753
2898
  var import_node_child_process = require("child_process");
2754
2899
  function runGit(args, cwd) {
2755
2900
  try {
@@ -2781,7 +2926,7 @@ function parseGitHubRef(ref) {
2781
2926
  return m ? m[1] : null;
2782
2927
  }
2783
2928
 
2784
- // src/scan/scaffold.ts
2929
+ // src/scanner/scan/scaffold.ts
2785
2930
  var fs3 = __toESM(require("fs"), 1);
2786
2931
  var path5 = __toESM(require("path"), 1);
2787
2932
  function scaffoldWidgetSpec(opts) {
@@ -2844,7 +2989,7 @@ function renderSpec(args) {
2844
2989
  return lines.join("\n");
2845
2990
  }
2846
2991
 
2847
- // src/scan/pipeline.ts
2992
+ // src/scanner/scan/pipeline.ts
2848
2993
  var fs4 = __toESM(require("fs"), 1);
2849
2994
  var path6 = __toESM(require("path"), 1);
2850
2995
  function runScan(opts = {}) {
@@ -2918,18 +3063,18 @@ function writeScanResult(result) {
2918
3063
  fs4.writeFileSync(result.outputPath, result.generated, "utf8");
2919
3064
  }
2920
3065
 
2921
- // src/scan/cli.ts
3066
+ // src/scanner/scan/cli.ts
2922
3067
  var fs7 = __toESM(require("fs"), 1);
2923
3068
  var path9 = __toESM(require("path"), 1);
2924
3069
 
2925
- // src/scan/ai/index.ts
3070
+ // src/scanner/scan/ai/index.ts
2926
3071
  var p = __toESM(require("@clack/prompts"), 1);
2927
3072
 
2928
- // src/scan/ai/providers/claude.ts
3073
+ // src/scanner/scan/ai/providers/claude.ts
2929
3074
  var fs6 = __toESM(require("fs"), 1);
2930
3075
  var path8 = __toESM(require("path"), 1);
2931
3076
 
2932
- // src/scan/ai/templates.ts
3077
+ // src/scanner/scan/ai/templates.ts
2933
3078
  var fs5 = __toESM(require("fs"), 1);
2934
3079
  var path7 = __toESM(require("path"), 1);
2935
3080
  function templatePath(rel) {
@@ -2956,15 +3101,16 @@ function readTemplate(rel) {
2956
3101
  return fs5.readFileSync(templatePath(rel), "utf8");
2957
3102
  }
2958
3103
 
2959
- // src/scan/ai/providers/claude.ts
3104
+ // src/scanner/scan/ai/providers/claude.ts
2960
3105
  var CLAUDE_FILES = [
2961
3106
  { dest: ".claude/rules/uidex.md", template: "claude/rules.md" },
2962
- { dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" }
3107
+ { dest: ".claude/commands/uidex/audit.md", template: "claude/audit.md" },
3108
+ { dest: ".claude/commands/uidex/api.md", template: "claude/api.md" }
2963
3109
  ];
2964
3110
  var claudeProvider = {
2965
3111
  id: "claude",
2966
3112
  label: "Claude Code",
2967
- description: "Adds .claude/rules/uidex.md and the /uidex:audit slash command.",
3113
+ description: "Adds .claude/rules/uidex.md, /uidex:audit, and /uidex:api slash commands.",
2968
3114
  async install({ cwd, force }) {
2969
3115
  const changes = [];
2970
3116
  for (const file of CLAUDE_FILES) {
@@ -3012,13 +3158,13 @@ function cleanupEmpty(dir) {
3012
3158
  }
3013
3159
  }
3014
3160
 
3015
- // src/scan/ai/providers/index.ts
3161
+ // src/scanner/scan/ai/providers/index.ts
3016
3162
  var PROVIDERS = [claudeProvider];
3017
3163
  function getProvider(id) {
3018
3164
  return PROVIDERS.find((p2) => p2.id === id);
3019
3165
  }
3020
3166
 
3021
- // src/scan/ai/index.ts
3167
+ // src/scanner/scan/ai/index.ts
3022
3168
  async function runAiCommand(opts) {
3023
3169
  const { cwd, argv } = opts;
3024
3170
  const sub = argv[0];
@@ -3129,8 +3275,8 @@ function err(exitCode, stderr) {
3129
3275
  return { exitCode, stdout: "", stderr };
3130
3276
  }
3131
3277
 
3132
- // src/scan/cli.ts
3133
- function parseFlags2(args) {
3278
+ // src/scanner/cli/parse-args.ts
3279
+ function parseArgs(args) {
3134
3280
  const positional = [];
3135
3281
  const flags = {};
3136
3282
  for (let i = 0; i < args.length; i++) {
@@ -3154,9 +3300,11 @@ function parseFlags2(args) {
3154
3300
  }
3155
3301
  return { positional, flags };
3156
3302
  }
3303
+
3304
+ // src/scanner/scan/cli.ts
3157
3305
  async function run(opts) {
3158
3306
  const cwd = opts.cwd ?? process.cwd();
3159
- const { positional, flags } = parseFlags2(opts.argv);
3307
+ const { positional, flags } = parseArgs(opts.argv);
3160
3308
  const command = positional[0] ?? "help";
3161
3309
  const writer = createWriter();
3162
3310
  try {
@@ -3200,6 +3348,10 @@ function helpText2() {
3200
3348
  " scan [flags] Run the scanner pipeline",
3201
3349
  " scaffold widget <id> Emit a Playwright spec from a widget's acceptance",
3202
3350
  " ai <install|uninstall|providers> Manage AI assistant integrations",
3351
+ " api <METHOD> <PATH> Call the uidex API",
3352
+ " api --list Show available API routes",
3353
+ " api login Authenticate via browser",
3354
+ " api login --token <tok> Store an auth token directly",
3203
3355
  "",
3204
3356
  "Flags:",
3205
3357
  " --check Verify the on-disk gen file matches a fresh scan; exit non-zero on drift (read-only)",