@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/README.md +6 -2
- package/dist/{chunk-UHZ7XSAN.js → chunk-3QKPNWKJ.js} +3 -3
- package/dist/{chunk-UHZ7XSAN.js.map → chunk-3QKPNWKJ.js.map} +1 -1
- package/dist/cli.js +395 -52
- package/dist/cli.js.map +1 -1
- package/dist/components/index.js +40 -10
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/vite/index.js +1 -1
- package/docs/en/agent-skills.md +21 -4
- package/docs/en/changelog.md +55 -0
- package/docs/en/cli-reference.md +53 -2
- package/docs/en/parts/echart.md +4 -2
- package/docs/ja/agent-skills.md +21 -4
- package/docs/ja/changelog.md +50 -0
- package/docs/ja/cli-reference.md +53 -2
- package/docs/ja/parts/echart.md +3 -2
- package/package.json +1 -1
- package/skills/vantage-add-feature/SKILL.md +12 -2
- package/skills/vantage-app/SKILL.md +1 -1
- package/skills/vantage-pitfalls/SKILL.md +3 -2
- package/templates/AGENTS.md +14 -10
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-
|
|
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([
|
|
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,
|
|
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
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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(
|
|
173
|
-
log.ok(
|
|
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
|
|
188
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
904
|
-
if (model.error) console.log(` ${pc3.cyan("error".padEnd(
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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>
|
|
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
|
`;
|