@squadbase/vantage 0.2.1 → 0.2.3

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.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { PAGE_EXTENSIONS, scanProject, vantagePlugin, generateArtifacts, VANTAGE_DIR, MANIFEST_SCHEMA_VERSION, FORBIDDEN_FILES, API_METHODS, PUBLIC_ENV_PREFIX } from './chunk-UHZ7XSAN.js';
2
+ import { PAGE_EXTENSIONS, scanProject, vantagePlugin, generateArtifacts, VANTAGE_DIR, MANIFEST_SCHEMA_VERSION, FORBIDDEN_FILES, API_METHODS, PUBLIC_ENV_PREFIX, extractPageMeta } from './chunk-3QKPNWKJ.js';
3
3
  import './chunk-46QI6GFC.js';
4
4
  import './chunk-B7LLBNLV.js';
5
5
  import path2 from 'path';
@@ -12,7 +12,21 @@ import { fileURLToPath } from 'url';
12
12
  import { spawn } from 'child_process';
13
13
 
14
14
  // @squadbase/vantage — generated build. Do not edit.
15
- var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "force", "verbose", "version", "all", "list", "regex", "h", "v"]);
15
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
16
+ "json",
17
+ "help",
18
+ "force",
19
+ "verbose",
20
+ "version",
21
+ "all",
22
+ "list",
23
+ "regex",
24
+ "pages",
25
+ "apis",
26
+ "detail",
27
+ "h",
28
+ "v"
29
+ ]);
16
30
  function parseArgs(argv) {
17
31
  const positionals = [];
18
32
  const flags = {};
@@ -147,30 +161,75 @@ function resolvePackageDir(root, sub) {
147
161
  function resolveRegistryDir(root) {
148
162
  return resolvePackageDir(root, "registry");
149
163
  }
164
+ var SKILL_FILE = "SKILL.md";
165
+ var SKILL_SCAN_SKIP = /* @__PURE__ */ new Set([
166
+ "node_modules",
167
+ ".git",
168
+ ".vantage",
169
+ "dist",
170
+ "build",
171
+ "out",
172
+ "coverage",
173
+ ".next",
174
+ ".turbo",
175
+ ".cache",
176
+ ".vercel"
177
+ ]);
178
+ var SKILL_SCAN_DEPTH = 4;
150
179
  function listBundledSkills(skillsDir) {
151
180
  try {
152
- return fs2.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && fs2.existsSync(path2.join(skillsDir, e.name, "SKILL.md"))).map((e) => e.name).sort();
181
+ return fs2.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && fs2.existsSync(path2.join(skillsDir, e.name, SKILL_FILE))).map((e) => e.name).sort();
153
182
  } catch {
154
183
  return [];
155
184
  }
156
185
  }
157
- function copySkill(root, skillsDir, name, force, destDir) {
158
- const src = path2.join(skillsDir, name);
159
- if (!fs2.existsSync(path2.join(src, "SKILL.md"))) {
160
- const available = listBundledSkills(skillsDir);
161
- log.error(`Unknown skill "${name}". Available: ${available.join(", ") || "(none)"}`);
162
- return 1;
163
- }
164
- const dest = path2.join(destDir, name);
165
- const rel2 = path2.relative(root, dest);
166
- const label = !rel2 ? name : rel2.startsWith("..") ? dest : rel2;
167
- if (fs2.existsSync(dest) && !force) {
168
- log.error(`${label} already exists. Use --force to overwrite.`);
169
- return 1;
170
- }
186
+ function displayPath(root, target) {
187
+ const rel2 = path2.relative(root, target);
188
+ return !rel2 ? "." : rel2.startsWith("..") ? target : rel2;
189
+ }
190
+ function findPlacedSkills(root, names, skillsDir) {
191
+ const wanted = new Set(names);
192
+ const found = /* @__PURE__ */ new Map();
193
+ const isSource = (p) => !path2.relative(skillsDir, p).startsWith("..");
194
+ const walk = (dir, depth) => {
195
+ let entries;
196
+ try {
197
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
198
+ } catch {
199
+ return;
200
+ }
201
+ for (const entry of entries) {
202
+ if (!entry.isDirectory() || SKILL_SCAN_SKIP.has(entry.name)) continue;
203
+ const abs = path2.join(dir, entry.name);
204
+ if (wanted.has(entry.name) && fs2.existsSync(path2.join(abs, SKILL_FILE))) {
205
+ if (!isSource(abs)) found.set(entry.name, [...found.get(entry.name) ?? [], abs]);
206
+ continue;
207
+ }
208
+ if (depth < SKILL_SCAN_DEPTH) walk(abs, depth + 1);
209
+ }
210
+ };
211
+ walk(root, 1);
212
+ for (const list of found.values()) list.sort();
213
+ return found;
214
+ }
215
+ function writeSkill(root, skillsDir, name, dest, verb) {
171
216
  fs2.mkdirSync(path2.dirname(dest), { recursive: true });
172
- fs2.cpSync(src, dest, { recursive: true, force: true });
173
- log.ok(`Added skill ${label}/`);
217
+ fs2.cpSync(path2.join(skillsDir, name), dest, { recursive: true, force: true });
218
+ log.ok(`${verb} skill ${displayPath(root, dest)}/`);
219
+ }
220
+ function printSkillList(root, available, placed) {
221
+ const width = Math.max(...available.map((n) => n.length));
222
+ log.info("Bundled skills:");
223
+ for (const name of available) {
224
+ const where = placed.get(name);
225
+ const status = where?.length ? where.map((p) => displayPath(root, p) + "/").join(", ") : "not placed";
226
+ log.info(` ${name.padEnd(width)} ${status}`);
227
+ }
228
+ const missing = available.filter((n) => !placed.get(n)?.length);
229
+ if (missing.length) {
230
+ log.info("");
231
+ log.info("Place the missing ones with: vantage add skill --all [--dir <path>]");
232
+ }
174
233
  return 0;
175
234
  }
176
235
  function addSkill(root, name, all, force, dir) {
@@ -184,15 +243,34 @@ function addSkill(root, name, all, force, dir) {
184
243
  log.error("No skills are bundled with this version of Vantage.");
185
244
  return 1;
186
245
  }
187
- const targets = all ? available : name ? [name] : [];
188
- if (targets.length === 0) {
189
- log.error(`Usage: vantage add skill <name>|--all. Available: ${available.join(", ")}`);
190
- return 1;
191
- }
246
+ const placed = findPlacedSkills(root, available, skillsDir);
247
+ if (!all && !name) return printSkillList(root, available, placed);
192
248
  const destDir = path2.resolve(root, dir ?? ".");
249
+ const targets = all ? available : [name];
193
250
  let failed = 0;
194
251
  for (const target of targets) {
195
- if (copySkill(root, skillsDir, target, force, destDir) !== 0) failed++;
252
+ if (!available.includes(target)) {
253
+ log.error(`Unknown skill "${target}". Available: ${available.join(", ")}`);
254
+ failed++;
255
+ continue;
256
+ }
257
+ const existing = placed.get(target) ?? [];
258
+ if (existing.length > 0 && !force) {
259
+ const where = existing.map((p) => displayPath(root, p) + "/").join(", ");
260
+ log.info(`Skill ${target} is already placed at ${where} \u2014 skipped (--force to refresh).`);
261
+ continue;
262
+ }
263
+ const inDestDir = existing.filter((p) => path2.dirname(p) === destDir);
264
+ const refresh = dir === void 0 ? existing : inDestDir;
265
+ if (refresh.length > 0) {
266
+ for (const dest of refresh) writeSkill(root, skillsDir, target, dest, "Updated");
267
+ continue;
268
+ }
269
+ if (existing.length > 0) {
270
+ const where = existing.map((p) => displayPath(root, p) + "/").join(", ");
271
+ log.warn(`Skill ${target} is also placed at ${where} \u2014 left untouched.`);
272
+ }
273
+ writeSkill(root, skillsDir, target, path2.join(destDir, target), "Added");
196
274
  }
197
275
  return failed === 0 ? 0 : 1;
198
276
  }
@@ -294,10 +372,202 @@ async function add(root, args) {
294
372
  log.error(`Unknown add target "${kind}". Use one of: page, api, ui, block, skill, agents.`);
295
373
  return 1;
296
374
  }
297
- var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["MODE", "DEV", "PROD", "SSR", "BASE_URL", "PUBLIC_"]);
375
+
376
+ // src/core/source.ts
298
377
  function stripComments(src) {
299
378
  return src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`\\])\/\/[^\n]*/g, "$1");
300
379
  }
380
+ var OPENERS = { "(": ")", "[": "]", "{": "}" };
381
+ function readBracketBody(src, openIndex) {
382
+ const open = src[openIndex];
383
+ if (!open || !OPENERS[open]) return null;
384
+ let depth = 0;
385
+ let quote = null;
386
+ for (let i = openIndex; i < src.length; i++) {
387
+ const c = src[i];
388
+ if (quote) {
389
+ if (c === "\\") i++;
390
+ else if (c === quote) quote = null;
391
+ continue;
392
+ }
393
+ if (c === '"' || c === "'" || c === "`") {
394
+ quote = c;
395
+ continue;
396
+ }
397
+ if (OPENERS[c]) depth++;
398
+ else if (c === ")" || c === "]" || c === "}") {
399
+ if (--depth === 0) return src.slice(openIndex + 1, i);
400
+ }
401
+ }
402
+ return null;
403
+ }
404
+ function splitTopLevel(body) {
405
+ const parts = [];
406
+ let depth = 0;
407
+ let quote = null;
408
+ let start = 0;
409
+ for (let i = 0; i < body.length; i++) {
410
+ const c = body[i];
411
+ if (quote) {
412
+ if (c === "\\") i++;
413
+ else if (c === quote) quote = null;
414
+ continue;
415
+ }
416
+ if (c === '"' || c === "'" || c === "`") {
417
+ quote = c;
418
+ continue;
419
+ }
420
+ if (OPENERS[c]) depth++;
421
+ else if (c === ")" || c === "]" || c === "}") depth--;
422
+ else if (c === "," && depth === 0) {
423
+ parts.push(body.slice(start, i));
424
+ start = i + 1;
425
+ }
426
+ }
427
+ parts.push(body.slice(start));
428
+ return parts.map((p) => p.trim()).filter(Boolean);
429
+ }
430
+
431
+ // src/core/route-spec.ts
432
+ function detectApiMethods(src) {
433
+ return API_METHODS.filter(
434
+ (m) => new RegExp(`export\\s+(async\\s+)?(function|const|let|var)\\s+${m}\\b`).test(src) || new RegExp(`export\\s*\\{[^}]*\\b${m}\\b[^}]*\\}`).test(src)
435
+ );
436
+ }
437
+ function methodRegions(src) {
438
+ const re = new RegExp(
439
+ `export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+(${API_METHODS.join("|")})\\b`,
440
+ "g"
441
+ );
442
+ const decls = [];
443
+ let m;
444
+ while (m = re.exec(src)) decls.push({ method: m[1], index: m.index });
445
+ const regions = /* @__PURE__ */ new Map();
446
+ decls.forEach((d, i) => {
447
+ regions.set(d.method, src.slice(d.index, decls[i + 1]?.index ?? src.length));
448
+ });
449
+ for (const method of detectApiMethods(src)) {
450
+ if (!regions.has(method)) regions.set(method, src);
451
+ }
452
+ return regions;
453
+ }
454
+ function objectKeys(expr) {
455
+ const trimmed = expr.trim();
456
+ if (!trimmed.startsWith("{")) return void 0;
457
+ const body = readBracketBody(trimmed, 0);
458
+ if (body == null) return void 0;
459
+ const keys = [];
460
+ for (const part of splitTopLevel(body)) {
461
+ if (part.startsWith("...")) {
462
+ keys.push("\u2026");
463
+ continue;
464
+ }
465
+ const name = part.match(/^["'`]?([A-Za-z0-9_$]+)/);
466
+ if (name) keys.push(name[1]);
467
+ }
468
+ return keys.length ? keys : void 0;
469
+ }
470
+ function initStatus(init) {
471
+ const m = init?.match(/\bstatus\s*:\s*(\d{3})\b/);
472
+ return m ? Number(m[1]) : void 0;
473
+ }
474
+ function initContentType(init) {
475
+ const m = init?.match(/["']?content-type["']?\s*:\s*["']([^"';]+)/i);
476
+ return m ? m[1].trim() : void 0;
477
+ }
478
+ function extractResponses(region) {
479
+ const re = /\bnew\s+Response\s*\(|\bResponse\s*\.\s*json\s*\(|(?<![.\w$])json\s*\(/g;
480
+ const out = [];
481
+ const seen = /* @__PURE__ */ new Set();
482
+ let m;
483
+ while (m = re.exec(region)) {
484
+ const isJson = !m[0].startsWith("new");
485
+ const args = readBracketBody(region, m.index + m[0].length - 1);
486
+ if (args == null) continue;
487
+ const [payload, init] = splitTopLevel(args);
488
+ const contentType = isJson ? "application/json" : initContentType(init) ?? (payload?.startsWith("JSON.stringify") ? "application/json" : void 0);
489
+ const keys = isJson && payload ? objectKeys(payload) : void 0;
490
+ const spec = {
491
+ status: initStatus(init) ?? 200,
492
+ ...contentType ? { contentType } : {},
493
+ ...keys ? { keys } : {}
494
+ };
495
+ const key = JSON.stringify(spec);
496
+ if (seen.has(key)) continue;
497
+ seen.add(key);
498
+ out.push(spec);
499
+ }
500
+ return out;
501
+ }
502
+ function extractErrors(region) {
503
+ const re = /\bHttpError\s*\(\s*(\d{3})\s*(?:,\s*(?:(["'`])((?:\\.|(?!\2)[\s\S])*)\2))?/g;
504
+ const out = [];
505
+ const seen = /* @__PURE__ */ new Set();
506
+ let m;
507
+ while (m = re.exec(region)) {
508
+ const message = m[3]?.replace(/\\(.)/g, "$1");
509
+ const spec = { status: Number(m[1]), ...message ? { message } : {} };
510
+ const key = JSON.stringify(spec);
511
+ if (seen.has(key)) continue;
512
+ seen.add(key);
513
+ out.push(spec);
514
+ }
515
+ return out;
516
+ }
517
+ function extractQuery(region) {
518
+ const re = /searchParams\s*\.\s*(?:get|getAll|has)\s*\(\s*["'`]([^"'`]+)["'`]/g;
519
+ const out = /* @__PURE__ */ new Set();
520
+ let m;
521
+ while (m = re.exec(region)) out.add(m[1]);
522
+ return [...out];
523
+ }
524
+ function extractBody(region) {
525
+ const read = region.match(/\b(?:request|req)\s*\.\s*(json|formData|text|arrayBuffer|blob)\s*\(/);
526
+ if (!read) return void 0;
527
+ const method = read[1];
528
+ const format = method === "json" || method === "formData" || method === "text" ? method : "binary";
529
+ const destructured = region.match(
530
+ /(?:const|let|var)\s*(\{[\s\S]*?\})\s*=\s*(?:await\s+)?[\w.]*\.json\s*\(/
531
+ );
532
+ const keys = format === "json" && destructured ? objectKeys(destructured[1]) : void 0;
533
+ return { format, ...keys ? { keys } : {} };
534
+ }
535
+ function toParams(segments) {
536
+ const out = [];
537
+ for (const s of segments) {
538
+ if (s.kind === "static") continue;
539
+ out.push({ name: s.name, kind: s.kind });
540
+ }
541
+ return out;
542
+ }
543
+ function extractPageSpec(page) {
544
+ return { params: toParams(page.segments), ...extractPageMeta(page.absFile) };
545
+ }
546
+ function extractApiSpec(api) {
547
+ const params = toParams(api.segments);
548
+ let src;
549
+ try {
550
+ src = stripComments(fs2.readFileSync(api.absFile, "utf8"));
551
+ } catch {
552
+ return { params, methods: [] };
553
+ }
554
+ const regions = methodRegions(src);
555
+ const methods = API_METHODS.filter((m) => regions.has(m)).map((method) => {
556
+ const region = regions.get(method);
557
+ const body = extractBody(region);
558
+ return {
559
+ method,
560
+ query: extractQuery(region),
561
+ ...body ? { body } : {},
562
+ responses: extractResponses(region),
563
+ errors: extractErrors(region)
564
+ };
565
+ });
566
+ return { params, methods };
567
+ }
568
+
569
+ // src/core/diagnostics.ts
570
+ var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["MODE", "DEV", "PROD", "SSR", "BASE_URL", "PUBLIC_"]);
301
571
  function hasDefaultExport(src) {
302
572
  return /\bexport\s+default\b/.test(src) || /\bexport\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(src) || /\bexport\s*\{[^}]*\bdefault\b[^}]*\}\s*from\b/.test(src);
303
573
  }
@@ -408,9 +678,7 @@ function checkApiExports(root, model) {
408
678
  } catch {
409
679
  continue;
410
680
  }
411
- const exported = API_METHODS.filter(
412
- (m) => new RegExp(`export\\s+(async\\s+)?(function|const|let|var)\\s+${m}\\b`).test(src) || new RegExp(`export\\s*\\{[^}]*\\b${m}\\b[^}]*\\}`).test(src)
413
- );
681
+ const exported = detectApiMethods(src);
414
682
  if (exported.length === 0) {
415
683
  const lower = ["get", "post", "put", "patch", "delete", "options"].filter(
416
684
  (m) => new RegExp(`export\\s+(async\\s+)?function\\s+${m}\\b`).test(src)
@@ -877,39 +1145,110 @@ async function preview(root, args) {
877
1145
  return new Promise(() => {
878
1146
  });
879
1147
  }
880
- async function routes(root, args) {
881
- const model = scanProject(root);
882
- if (args.flags.json) {
883
- printJson({
884
- pages: model.pages.map((p) => ({ route: p.routePath, file: p.file, dynamic: p.dynamic })),
885
- layouts: model.layouts.map((l) => ({ dir: l.dir === "" ? "/" : l.dir, file: l.file })),
886
- notFound: model.notFound,
887
- error: model.error,
888
- apis: model.apis.map((a) => ({ route: a.routePath, file: a.file, params: a.params })),
889
- hasServer: model.hasServer
890
- });
891
- return 0;
1148
+ var ROUTE_COLUMN = 28;
1149
+ var LABEL_COLUMN = 9;
1150
+ function detailLine(indent, label, value) {
1151
+ console.log(`${indent}${pc3.dim(label.padEnd(LABEL_COLUMN))}${value}`);
1152
+ }
1153
+ function formatParams(params) {
1154
+ return params.map((p) => p.kind === "catchall" ? `*${p.name}` : `:${p.name}`).join(", ");
1155
+ }
1156
+ function formatKeys(keys) {
1157
+ return keys?.length ? ` { ${keys.join(", ")} }` : "";
1158
+ }
1159
+ function printPageSpec(spec) {
1160
+ const indent = " ";
1161
+ if (spec.title) detailLine(indent, "title", spec.title);
1162
+ if (spec.navLabel) detailLine(indent, "nav", spec.navLabel);
1163
+ if (spec.description) detailLine(indent, "desc", spec.description);
1164
+ if (spec.params.length) detailLine(indent, "params", formatParams(spec.params));
1165
+ }
1166
+ function printApiMethodSpec(method, params) {
1167
+ const indent = " ";
1168
+ console.log(` ${pc3.bold(method.method)}`);
1169
+ if (params.length) detailLine(indent, "path", formatParams(params));
1170
+ if (method.query.length) detailLine(indent, "query", method.query.join(", "));
1171
+ if (method.body) detailLine(indent, "body", method.body.format + formatKeys(method.body.keys));
1172
+ for (const res of method.responses) {
1173
+ const parts = [String(res.status), res.contentType].filter(Boolean).join(" ");
1174
+ detailLine(indent, "response", parts + formatKeys(res.keys));
1175
+ }
1176
+ for (const err of method.errors) {
1177
+ detailLine(indent, "error", `${err.status}${err.message ? ` "${err.message}"` : ""}`);
892
1178
  }
1179
+ }
1180
+ function printPages(model, detail) {
893
1181
  console.log(pc3.bold("\nPages"));
894
1182
  if (model.pages.length === 0) console.log(pc3.dim(" (none)"));
895
1183
  for (const p of model.pages) {
896
- console.log(` ${pc3.green(p.routePath.padEnd(28))} ${pc3.dim(p.file)}`);
1184
+ console.log(` ${pc3.green(p.routePath.padEnd(ROUTE_COLUMN))} ${pc3.dim(p.file)}`);
1185
+ if (detail) printPageSpec(extractPageSpec(p));
897
1186
  }
898
1187
  if (model.layouts.length || model.notFound || model.error) {
899
1188
  console.log(pc3.bold("\nSpecial"));
900
1189
  for (const l of model.layouts) {
901
- console.log(` ${pc3.cyan(("layout " + (l.dir === "" ? "/" : "/" + l.dir)).padEnd(28))} ${pc3.dim(l.file)}`);
1190
+ const label = "layout " + (l.dir === "" ? "/" : "/" + l.dir);
1191
+ console.log(` ${pc3.cyan(label.padEnd(ROUTE_COLUMN))} ${pc3.dim(l.file)}`);
902
1192
  }
903
- if (model.notFound) console.log(` ${pc3.cyan("404".padEnd(28))} ${pc3.dim(model.notFound)}`);
904
- if (model.error) console.log(` ${pc3.cyan("error".padEnd(28))} ${pc3.dim(model.error)}`);
1193
+ if (model.notFound) console.log(` ${pc3.cyan("404".padEnd(ROUTE_COLUMN))} ${pc3.dim(model.notFound)}`);
1194
+ if (model.error) console.log(` ${pc3.cyan("error".padEnd(ROUTE_COLUMN))} ${pc3.dim(model.error)}`);
905
1195
  }
906
- if (model.hasServer) {
907
- console.log(pc3.bold("\nAPI"));
908
- if (model.apis.length === 0) console.log(pc3.dim(" (none)"));
909
- for (const a of model.apis) {
910
- console.log(` ${pc3.yellow(a.routePath.padEnd(28))} ${pc3.dim(a.file)}`);
1196
+ }
1197
+ function printApis(model, detail) {
1198
+ console.log(pc3.bold("\nAPI"));
1199
+ if (!model.hasServer) {
1200
+ console.log(pc3.dim(" (none \u2014 this project has no server/ directory)"));
1201
+ return;
1202
+ }
1203
+ if (model.apis.length === 0) console.log(pc3.dim(" (none)"));
1204
+ for (const a of model.apis) {
1205
+ console.log(` ${pc3.yellow(a.routePath.padEnd(ROUTE_COLUMN))} ${pc3.dim(a.file)}`);
1206
+ if (!detail) continue;
1207
+ const spec = extractApiSpec(a);
1208
+ if (spec.methods.length === 0) console.log(` ${pc3.dim("(no HTTP method handler exported)")}`);
1209
+ for (const method of spec.methods) printApiMethodSpec(method, spec.params);
1210
+ }
1211
+ }
1212
+ function pageJson(p, detail) {
1213
+ return {
1214
+ route: p.routePath,
1215
+ file: p.file,
1216
+ dynamic: p.dynamic,
1217
+ ...detail ? { spec: extractPageSpec(p) } : {}
1218
+ };
1219
+ }
1220
+ function apiJson(a, detail) {
1221
+ return {
1222
+ route: a.routePath,
1223
+ file: a.file,
1224
+ params: a.params,
1225
+ ...detail ? { spec: extractApiSpec(a) } : {}
1226
+ };
1227
+ }
1228
+ async function routes(root, args) {
1229
+ const model = scanProject(root);
1230
+ const onlyPages = Boolean(args.flags.pages);
1231
+ const onlyApis = Boolean(args.flags.apis);
1232
+ const showPages = onlyPages || !onlyApis;
1233
+ const showApis = onlyApis || !onlyPages;
1234
+ const detail = Boolean(args.flags.detail);
1235
+ if (args.flags.json) {
1236
+ const payload = {};
1237
+ if (showPages) {
1238
+ payload.pages = model.pages.map((p) => pageJson(p, detail));
1239
+ payload.layouts = model.layouts.map((l) => ({ dir: l.dir === "" ? "/" : l.dir, file: l.file }));
1240
+ payload.notFound = model.notFound;
1241
+ payload.error = model.error;
1242
+ }
1243
+ if (showApis) {
1244
+ payload.apis = model.apis.map((a) => apiJson(a, detail));
1245
+ payload.hasServer = model.hasServer;
911
1246
  }
1247
+ printJson(payload);
1248
+ return 0;
912
1249
  }
1250
+ if (showPages) printPages(model, detail);
1251
+ if (showApis && (model.hasServer || onlyApis)) printApis(model, detail);
913
1252
  console.log("");
914
1253
  return 0;
915
1254
  }
@@ -1258,8 +1597,9 @@ ${pc3.bold("Commands")}
1258
1597
  build Build client and optional server into dist/
1259
1598
  preview Run the production build locally
1260
1599
  check Static verification (routes, boundaries, forbidden files)
1261
- routes Show page and API URL map
1600
+ routes Show page and API URL map (--pages | --apis | --detail)
1262
1601
  add <kind> <name> Scaffold a page | api | ui | block | skill | agents
1602
+ (add skill with no name lists the skills and where they sit)
1263
1603
  docs [name] Show the bundled guide and component reference
1264
1604
  search <query> Find components and docs by meaning, or by regex
1265
1605
  doctor Environment and installation health checks
@@ -1270,12 +1610,15 @@ ${pc3.bold("Options")}
1270
1610
  --port <n> Port for dev/preview
1271
1611
  --root <dir> Project root (default: cwd)
1272
1612
  --force Overwrite when scaffolding
1613
+ --pages Show only page routes (routes)
1614
+ --apis Show only API routes (routes)
1615
+ --detail Add each route's static spec (routes)
1273
1616
  --list List pages without showing one (docs)
1274
1617
  --lang <ja|en> Documentation language (docs, search, default: ja)
1275
1618
  --regex Treat the query as a regular expression (search)
1276
1619
  --limit <n> Max results (search, default: 10)
1277
1620
  --all Add every bundled skill (add skill --all) | show every page (docs)
1278
- --dir <path> Destination for skills (add skill, default: project root)
1621
+ --dir <path> Where to place skills when none is found (add skill, default: project root)
1279
1622
  -v, --version Print version
1280
1623
  -h, --help Print this help
1281
1624
  `;