skillops-cli 1.2.1 → 1.3.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 (5) hide show
  1. package/README.md +92 -65
  2. package/dist/cli.js +2530 -430
  3. package/dist/index.d.ts +444 -188
  4. package/dist/index.js +3085 -1181
  5. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,20 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import fs9 from "fs";
5
- import path9 from "path";
4
+ import fs10 from "fs";
5
+ import path10 from "path";
6
6
  import { fileURLToPath, pathToFileURL } from "url";
7
- import YAML5 from "yaml";
8
-
9
- // src/auditor/parser.ts
10
- import fs2 from "fs";
11
- import path from "path";
7
+ import YAML6 from "yaml";
12
8
 
13
9
  // src/models.ts
14
10
  import fs from "fs";
15
11
  import YAML from "yaml";
16
12
  import { z } from "zod";
17
- var SKILLOPS_VERSION = "1.2.1";
13
+ var SKILLOPS_VERSION = "1.3.1";
18
14
  var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
19
15
  var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
20
16
  var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -30,7 +26,9 @@ function estimateTokenCount(text) {
30
26
  return 0;
31
27
  }
32
28
  const words = countWords(stripped);
33
- const punctBonus = Math.floor((stripped.match(PUNCT_CODE_REGEX)?.length ?? 0) / 4);
29
+ const punctBonus = Math.floor(
30
+ (stripped.match(PUNCT_CODE_REGEX)?.length ?? 0) / 4
31
+ );
34
32
  return Math.max(1, Math.ceil(words * 1.33) + punctBonus);
35
33
  }
36
34
  function splitFrontmatter(rawText, sourceLabel) {
@@ -44,7 +42,9 @@ function splitFrontmatter(rawText, sourceLabel) {
44
42
  const body = match[2] ?? "";
45
43
  const parsed = YAML.parse(yamlText);
46
44
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
47
- throw new Error(`YAML frontmatter in '${sourceLabel}' must evaluate to a key-value mapping.`);
45
+ throw new Error(
46
+ `YAML frontmatter in '${sourceLabel}' must evaluate to a key-value mapping.`
47
+ );
48
48
  }
49
49
  return [parsed, body];
50
50
  }
@@ -543,7 +543,9 @@ var SubagentManifestSchema = z.object({
543
543
  });
544
544
  }
545
545
  }),
546
- tools: z.array(z.string().min(1)).min(1, { message: "Subagent must declare an explicit non-empty tool allowlist per INV-SKILL-04." }).superRefine((tools, ctx) => {
546
+ tools: z.array(z.string().min(1)).min(1, {
547
+ message: "Subagent must declare an explicit non-empty tool allowlist per INV-SKILL-04."
548
+ }).superRefine((tools, ctx) => {
547
549
  for (const tool of tools) {
548
550
  if (FORBIDDEN_WILDCARD_TOOLS.has(tool.trim().toLowerCase())) {
549
551
  ctx.addIssue({
@@ -734,7 +736,14 @@ var SyncedArtifactSchema = z.object({
734
736
  skill_name: z.string().min(1),
735
737
  relative_path: z.string().min(1),
736
738
  sha256: z.string().regex(/^[0-9a-f]{64}$/),
737
- action: z.enum(["created", "updated", "unchanged", "removed", "planned", "conflict"])
739
+ action: z.enum([
740
+ "created",
741
+ "updated",
742
+ "unchanged",
743
+ "removed",
744
+ "planned",
745
+ "conflict"
746
+ ])
738
747
  }).strict();
739
748
  var SyncLockEntrySchema = z.object({
740
749
  target: RuntimeTargetSchema,
@@ -820,6 +829,8 @@ function loadSkillOpsConfig(repoPath) {
820
829
  }
821
830
 
822
831
  // src/auditor/parser.ts
832
+ import fs2 from "fs";
833
+ import path from "path";
823
834
  function walkForFilesNamed(rootDir, targetFilename) {
824
835
  if (!fs2.existsSync(rootDir)) {
825
836
  return [];
@@ -852,7 +863,10 @@ function parseSkillDirectory(skillDir, options = {}) {
852
863
  const rawText = fs2.readFileSync(skillMdPath, "utf-8");
853
864
  const lines = rawText.split(/\r?\n/);
854
865
  const totalLines = rawText.endsWith("\n") ? lines.length - 1 : lines.length;
855
- const [, markdownBody] = splitFrontmatter(rawText, skillMdPath.replace(/\\/g, "/"));
866
+ const [, markdownBody] = splitFrontmatter(
867
+ rawText,
868
+ skillMdPath.replace(/\\/g, "/")
869
+ );
856
870
  const manifest = parseSkillMdFile(skillMdPath);
857
871
  const declaredScripts = [];
858
872
  const missingDeclaredScripts = [];
@@ -886,7 +900,9 @@ function parseSkillDirectory(skillDir, options = {}) {
886
900
  const referencesDir = path.join(skillDir, "references");
887
901
  const referenceFiles = [];
888
902
  if (fs2.existsSync(referencesDir) && fs2.statSync(referencesDir).isDirectory()) {
889
- for (const entry of fs2.readdirSync(referencesDir, { withFileTypes: true })) {
903
+ for (const entry of fs2.readdirSync(referencesDir, {
904
+ withFileTypes: true
905
+ })) {
890
906
  if (entry.isFile() && entry.name.endsWith(".md")) {
891
907
  referenceFiles.push(path.join(referencesDir, entry.name));
892
908
  }
@@ -951,112 +967,6 @@ function parseSubagentFile(filePath) {
951
967
  };
952
968
  }
953
969
 
954
- // src/auditor/token_budget.ts
955
- function auditSkillTokenBudget(skillDir, options = {}) {
956
- const requireLayout = options.requireLayout ?? false;
957
- try {
958
- const parsed = parseSkillDirectory(skillDir, { requireLayout });
959
- const warnings = [];
960
- const errors = [];
961
- if (parsed.manifest.routing_signals.negative_triggers.length === 0 && !parsed.manifest.description.toLowerCase().includes("do not trigger")) {
962
- warnings.push(
963
- `Skill '${parsed.manifest.name}' has no explicit negative triggers ('Do not trigger for...' or routing_signals.negative_triggers).`
964
- );
965
- }
966
- if (requireLayout && parsed.missingDeclaredScripts.length > 0) {
967
- errors.push(
968
- `Skill '${parsed.manifest.name}' is missing declared script(s): ${parsed.missingDeclaredScripts.join(", ")}`
969
- );
970
- }
971
- return {
972
- skillName: parsed.manifest.name,
973
- skillDir: skillDir.replace(/\\/g, "/"),
974
- l1Words: parsed.manifest.description_words,
975
- l1Tokens: parsed.manifest.description_tokens,
976
- l2Lines: parsed.manifest.body_lines,
977
- l2Tokens: parsed.manifest.body_tokens,
978
- totalLines: parsed.totalLines,
979
- scriptsCount: parsed.declaredScripts.length,
980
- passed: errors.length === 0,
981
- errors,
982
- warnings
983
- };
984
- } catch (err) {
985
- const message = err instanceof Error ? err.message : String(err);
986
- return {
987
- skillName: skillDir.split(/[\\/]/).pop() ?? "unknown",
988
- skillDir: skillDir.replace(/\\/g, "/"),
989
- l1Words: 0,
990
- l1Tokens: 0,
991
- l2Lines: 0,
992
- l2Tokens: 0,
993
- totalLines: 0,
994
- scriptsCount: 0,
995
- passed: false,
996
- errors: [message],
997
- warnings: []
998
- };
999
- }
1000
- }
1001
- function auditSubagentTokenBudget(filePath) {
1002
- try {
1003
- const parsed = parseSubagentFile(filePath);
1004
- return {
1005
- subagentName: parsed.manifest.name,
1006
- filePath: filePath.replace(/\\/g, "/"),
1007
- descriptionTokens: parsed.manifest.description_tokens,
1008
- systemPromptLines: parsed.manifest.system_prompt_lines,
1009
- systemPromptTokens: parsed.manifest.system_prompt_tokens,
1010
- maxOutputTokens: parsed.manifest.max_output_tokens,
1011
- tools: parsed.manifest.tools,
1012
- passed: true,
1013
- errors: []
1014
- };
1015
- } catch (err) {
1016
- const message = err instanceof Error ? err.message : String(err);
1017
- return {
1018
- subagentName: filePath.split(/[\\/]/).pop() ?? "unknown",
1019
- filePath: filePath.replace(/\\/g, "/"),
1020
- descriptionTokens: 0,
1021
- systemPromptLines: 0,
1022
- systemPromptTokens: 0,
1023
- maxOutputTokens: 0,
1024
- tools: [],
1025
- passed: false,
1026
- errors: [message]
1027
- };
1028
- }
1029
- }
1030
- function auditCatalogTokenBudgets(catalogDir, options = {}) {
1031
- const skillDirs = discoverSkillDirectories(catalogDir);
1032
- const subagentFiles = discoverSubagentFiles(catalogDir);
1033
- const skills = skillDirs.map((dir) => auditSkillTokenBudget(dir, options));
1034
- const subagents = subagentFiles.map((file) => auditSubagentTokenBudget(file));
1035
- const errors = [];
1036
- const warnings = [];
1037
- let totalL1Tokens = 0;
1038
- let totalSubagentPromptTokens = 0;
1039
- for (const s of skills) {
1040
- totalL1Tokens += s.l1Tokens;
1041
- errors.push(...s.errors);
1042
- warnings.push(...s.warnings);
1043
- }
1044
- for (const sa of subagents) {
1045
- totalSubagentPromptTokens += sa.systemPromptTokens;
1046
- errors.push(...sa.errors);
1047
- }
1048
- return {
1049
- catalogPath: catalogDir.replace(/\\/g, "/"),
1050
- skills,
1051
- subagents,
1052
- totalL1Tokens,
1053
- totalSubagentPromptTokens,
1054
- errors,
1055
- warnings,
1056
- passed: errors.length === 0
1057
- };
1058
- }
1059
-
1060
970
  // src/auditor/collision.ts
1061
971
  var ROUTING_STOPWORDS = /* @__PURE__ */ new Set([
1062
972
  "a",
@@ -1385,10 +1295,318 @@ function auditCatalog(catalogDir, options = {}) {
1385
1295
  });
1386
1296
  }
1387
1297
 
1298
+ // src/auditor/doctor.ts
1299
+ import { spawnSync } from "child_process";
1300
+ import crypto from "crypto";
1301
+ import fs4 from "fs";
1302
+ import path3 from "path";
1303
+
1388
1304
  // src/auditor/token_economy.ts
1389
1305
  import fs3 from "fs";
1390
1306
  import path2 from "path";
1391
1307
  import YAML2 from "yaml";
1308
+
1309
+ // src/ui/terminal.ts
1310
+ var ESC = String.fromCharCode(27);
1311
+ var ANSI_ESCAPE_REGEX = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
1312
+ function isColorEnabled(options) {
1313
+ const env = options?.env ?? process.env;
1314
+ const isTTY = options?.isTTY ?? Boolean(process.stdout?.isTTY);
1315
+ if (env.NO_COLOR !== void 0 && env.NO_COLOR !== "") {
1316
+ return false;
1317
+ }
1318
+ if (env.FORCE_COLOR !== void 0) {
1319
+ const val = env.FORCE_COLOR.trim().toLowerCase();
1320
+ if (val === "0" || val === "false") {
1321
+ return false;
1322
+ }
1323
+ if (val === "1" || val === "2" || val === "3" || val === "true") {
1324
+ return true;
1325
+ }
1326
+ }
1327
+ if (env.TERM === "dumb") {
1328
+ return false;
1329
+ }
1330
+ return isTTY;
1331
+ }
1332
+ function wrapAnsi(openCode, closeCode, text, enabled) {
1333
+ const useColor = enabled ?? isColorEnabled();
1334
+ if (!useColor || text.length === 0) {
1335
+ return text;
1336
+ }
1337
+ return `${ESC}[${openCode}m${text}${ESC}[${closeCode}m`;
1338
+ }
1339
+ var style = {
1340
+ bold: (text, enabled) => wrapAnsi("1", "22", text, enabled),
1341
+ dim: (text, enabled) => wrapAnsi("2", "22", text, enabled),
1342
+ italic: (text, enabled) => wrapAnsi("3", "23", text, enabled),
1343
+ underline: (text, enabled) => wrapAnsi("4", "24", text, enabled),
1344
+ cyan: (text, enabled) => wrapAnsi("36", "39", text, enabled),
1345
+ green: (text, enabled) => wrapAnsi("32", "39", text, enabled),
1346
+ yellow: (text, enabled) => wrapAnsi("33", "39", text, enabled),
1347
+ red: (text, enabled) => wrapAnsi("31", "39", text, enabled),
1348
+ magenta: (text, enabled) => wrapAnsi("35", "39", text, enabled),
1349
+ blue: (text, enabled) => wrapAnsi("34", "39", text, enabled),
1350
+ gray: (text, enabled) => wrapAnsi("90", "39", text, enabled),
1351
+ white: (text, enabled) => wrapAnsi("37", "39", text, enabled)
1352
+ };
1353
+ function stripAnsi(text) {
1354
+ return text.replace(ANSI_ESCAPE_REGEX, "");
1355
+ }
1356
+ function visibleLength(text) {
1357
+ return Array.from(stripAnsi(text)).length;
1358
+ }
1359
+ function padVisible(text, width, align = "left") {
1360
+ const len = visibleLength(text);
1361
+ if (len >= width) {
1362
+ return text;
1363
+ }
1364
+ const diff = width - len;
1365
+ if (align === "right") {
1366
+ return `${" ".repeat(diff)}${text}`;
1367
+ }
1368
+ if (align === "center") {
1369
+ const left = Math.floor(diff / 2);
1370
+ const right = diff - left;
1371
+ return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
1372
+ }
1373
+ return `${text}${" ".repeat(diff)}`;
1374
+ }
1375
+ function renderHeaderBanner(subtitle = "Framework & Library for AI Skills & Subagents", options) {
1376
+ const ver = options?.version ?? SKILLOPS_VERSION;
1377
+ const c = options?.colorEnabled;
1378
+ return `${style.bold(style.cyan(`\u25C6 SkillOps v${ver}`, c), c)} ${style.dim("\u2014", c)} ${subtitle}`;
1379
+ }
1380
+ function renderTable(headers, rows, options) {
1381
+ const indent = " ".repeat(options?.indent ?? 2);
1382
+ const c = options?.colorEnabled;
1383
+ const colCount = headers.length;
1384
+ const widths = headers.map((h) => visibleLength(h));
1385
+ for (const row of rows) {
1386
+ for (let i = 0; i < colCount; i++) {
1387
+ const cell = row[i] ?? "";
1388
+ widths[i] = Math.max(widths[i] ?? 0, visibleLength(cell));
1389
+ }
1390
+ }
1391
+ const aligns = options?.align ?? headers.map(() => "left");
1392
+ const headerLine = headers.map(
1393
+ (h, i) => style.bold(
1394
+ style.cyan(padVisible(h, widths[i] ?? 0, aligns[i] ?? "left"), c),
1395
+ c
1396
+ )
1397
+ ).join(" ");
1398
+ const separatorLine = widths.map((w) => style.dim("\u2500".repeat(Math.max(1, w)), c)).join(" ");
1399
+ const bodyLines = rows.map(
1400
+ (row) => headers.map(
1401
+ (_, i) => padVisible(row[i] ?? "", widths[i] ?? 0, aligns[i] ?? "left")
1402
+ ).join(" ")
1403
+ );
1404
+ return [
1405
+ `${indent}${headerLine}`,
1406
+ `${indent}${separatorLine}`,
1407
+ ...bodyLines.map((line) => `${indent}${line}`)
1408
+ ].join("\n");
1409
+ }
1410
+ function renderTokenBar(current, max, options) {
1411
+ const width = options?.width ?? 10;
1412
+ const unit = options?.unit ?? "tokens";
1413
+ const c = options?.colorEnabled;
1414
+ const safeMax = max > 0 ? max : 1;
1415
+ const ratio = Math.max(0, current / safeMax);
1416
+ const clampedRatio = Math.min(1, ratio);
1417
+ const filledCount = Math.round(clampedRatio * width);
1418
+ const emptyCount = Math.max(0, width - filledCount);
1419
+ const colorFn = ratio > 1 ? style.red : ratio > 0.85 ? style.yellow : style.green;
1420
+ const filledStr = colorFn("\u2588".repeat(filledCount), c);
1421
+ const emptyStr = style.dim("\u2591".repeat(emptyCount), c);
1422
+ const pctStr = `${(ratio * 100).toFixed(1)}%`;
1423
+ const prefix = options?.label ? `${style.bold(options.label, c)} ` : "";
1424
+ return `${prefix}[${filledStr}${emptyStr}] ${style.bold(pctStr, c)} (${current} / ${max} ${unit})`;
1425
+ }
1426
+ var renderProgressBar = renderTokenBar;
1427
+ function renderSectionHeader(title, colorEnabled) {
1428
+ const c = colorEnabled;
1429
+ return `${style.dim("\u2502", c)}
1430
+ ${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan(title, c), c)}`;
1431
+ }
1432
+ function formatBadge(type, customLabel, colorEnabled) {
1433
+ switch (type) {
1434
+ case "pass":
1435
+ return style.bold(
1436
+ style.green(`\u2714 ${customLabel ?? "PASS"}`, colorEnabled),
1437
+ colorEnabled
1438
+ );
1439
+ case "warn":
1440
+ return style.bold(
1441
+ style.yellow(`\u26A0 ${customLabel ?? "WARN"}`, colorEnabled),
1442
+ colorEnabled
1443
+ );
1444
+ case "fail":
1445
+ return style.bold(
1446
+ style.red(`\u2716 ${customLabel ?? "FAIL"}`, colorEnabled),
1447
+ colorEnabled
1448
+ );
1449
+ case "info":
1450
+ return style.bold(
1451
+ style.cyan(`\u2139 ${customLabel ?? "INFO"}`, colorEnabled),
1452
+ colorEnabled
1453
+ );
1454
+ case "skill":
1455
+ return style.bold(
1456
+ style.cyan(`[${customLabel ?? "SKILL"}]`, colorEnabled),
1457
+ colorEnabled
1458
+ );
1459
+ case "subagent":
1460
+ return style.bold(
1461
+ style.magenta(`[${customLabel ?? "SUBAGENT"}]`, colorEnabled),
1462
+ colorEnabled
1463
+ );
1464
+ }
1465
+ }
1466
+ function renderStep(step, total, title, detail, status = "done", colorEnabled) {
1467
+ const icon = status === "done" ? style.green("\u2714", colorEnabled) : status === "warn" ? style.yellow("\u26A0", colorEnabled) : style.dim("\u25CB", colorEnabled);
1468
+ const stepBadge = style.dim(`[${step}/${total}]`, colorEnabled);
1469
+ const titleStyled = style.bold(padVisible(title, 20, "left"), colorEnabled);
1470
+ const detailStyled = detail ? ` ${style.dim("\xB7", colorEnabled)} ${detail}` : "";
1471
+ return `${style.dim("\u2502", colorEnabled)} ${icon} ${stepBadge} ${titleStyled}${detailStyled}`;
1472
+ }
1473
+ function renderNextSteps(hints, colorEnabled) {
1474
+ if (hints.length === 0) {
1475
+ return "";
1476
+ }
1477
+ const c = colorEnabled;
1478
+ const maxCmdLen = Math.min(
1479
+ 44,
1480
+ hints.reduce((m, h) => Math.max(m, visibleLength(h.command)), 0)
1481
+ );
1482
+ const lines = [
1483
+ style.dim("\u2502", c),
1484
+ `${style.dim("\u2570\u2500", c)} ${style.bold(style.cyan("Next Steps:", c), c)}`,
1485
+ ...hints.map((h) => {
1486
+ const cmdPadded = padVisible(h.command, maxCmdLen, "left");
1487
+ const prefixCols = 5 + visibleLength(cmdPadded) + 3;
1488
+ const maxDescCols = Math.max(10, 76 - prefixCols);
1489
+ const desc = h.description.length > maxDescCols ? `${h.description.slice(0, maxDescCols - 1)}\u2026` : h.description;
1490
+ return ` ${style.dim("$", c)} ${style.green(cmdPadded, c)} ${style.dim(`\u2014 ${desc}`, c)}`;
1491
+ })
1492
+ ];
1493
+ return `${lines.join("\n")}
1494
+ `;
1495
+ }
1496
+ function renderAiUsageGuide(targets, skills, subagents, colorEnabled) {
1497
+ const c = colorEnabled;
1498
+ const sampleSkill = skills[0] ?? "systematic-debugging";
1499
+ const sampleAgent = subagents[0] ?? "context-scout";
1500
+ const bar = style.dim("\u2502", c);
1501
+ const lines = [
1502
+ bar,
1503
+ `${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Using Your Skills & Subagents", c), c)}`
1504
+ ];
1505
+ const targetSet = new Set(targets);
1506
+ if (targetSet.has("claude") || targetSet.size === 0) {
1507
+ lines.push(
1508
+ `${bar} \u2022 ${style.bold("Claude Code:", c)} Auto-routed (${sampleSkill}) or @${sampleAgent}`
1509
+ );
1510
+ }
1511
+ if (targetSet.has("cursor")) {
1512
+ lines.push(
1513
+ `${bar} \u2022 ${style.bold("Cursor IDE:", c)} Type ${style.cyan(`@${sampleSkill}`, c)} in Chat (.cursor/rules/)`
1514
+ );
1515
+ }
1516
+ if (targetSet.has("antigravity")) {
1517
+ lines.push(
1518
+ `${bar} \u2022 ${style.bold("Antigravity:", c)} Auto-activated (.agent/skills/${sampleSkill})`
1519
+ );
1520
+ }
1521
+ if (targetSet.has("copilot")) {
1522
+ lines.push(
1523
+ `${bar} \u2022 ${style.bold("Copilot:", c)} Use ${style.cyan(`/${sampleSkill}`, c)} in Chat (.github/instructions/)`
1524
+ );
1525
+ }
1526
+ if (targetSet.has("windsurf") || targetSet.has("cline") || targetSet.has("roocode") || targetSet.has("kilocode")) {
1527
+ lines.push(
1528
+ `${bar} \u2022 ${style.bold("Windsurf/Cline/Roo/Kilo:", c)} Workspace rules load automatically`
1529
+ );
1530
+ }
1531
+ lines.push(
1532
+ `${bar} \u2022 ${style.bold("Inspect:", c)} ${style.green(`npx skillops-cli info ${sampleSkill}`, c)}`
1533
+ );
1534
+ return lines.join("\n");
1535
+ }
1536
+ function levenshteinDistance(a, b) {
1537
+ const s1 = a.toLowerCase();
1538
+ const s2 = b.toLowerCase();
1539
+ const m = s1.length;
1540
+ const n = s2.length;
1541
+ let prevRow = Array.from({ length: n + 1 }, (_, j) => j);
1542
+ for (let i = 1; i <= m; i++) {
1543
+ const currRow = [i];
1544
+ for (let j = 1; j <= n; j++) {
1545
+ const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
1546
+ const deletion = (prevRow[j] ?? 0) + 1;
1547
+ const insertion = (currRow[j - 1] ?? 0) + 1;
1548
+ const substitution = (prevRow[j - 1] ?? 0) + cost;
1549
+ currRow[j] = Math.min(deletion, insertion, substitution);
1550
+ }
1551
+ prevRow = currRow;
1552
+ }
1553
+ return prevRow[n] ?? 0;
1554
+ }
1555
+ var DEFAULT_VALID_COMMANDS = [
1556
+ "quickstart",
1557
+ "setup",
1558
+ "init",
1559
+ "list",
1560
+ "search",
1561
+ "find",
1562
+ "info",
1563
+ "show",
1564
+ "inspect",
1565
+ "add",
1566
+ "install",
1567
+ "remove",
1568
+ "uninstall",
1569
+ "rm",
1570
+ "clean",
1571
+ "create",
1572
+ "new",
1573
+ "harnesses",
1574
+ "doctor",
1575
+ "status",
1576
+ "tokens",
1577
+ "budget",
1578
+ "audit",
1579
+ "route",
1580
+ "recommend",
1581
+ "sync",
1582
+ "eval",
1583
+ "mcp",
1584
+ "mcp-setup"
1585
+ ];
1586
+ function suggestCommand(unknownCommand, validCommands = DEFAULT_VALID_COMMANDS) {
1587
+ const needle = unknownCommand.trim().toLowerCase();
1588
+ if (!needle) {
1589
+ return null;
1590
+ }
1591
+ for (const candidate of validCommands) {
1592
+ if (candidate.startsWith(needle) || needle.startsWith(candidate)) {
1593
+ return candidate;
1594
+ }
1595
+ }
1596
+ let bestMatch = null;
1597
+ let bestDistance = Number.POSITIVE_INFINITY;
1598
+ for (const candidate of validCommands) {
1599
+ const dist = levenshteinDistance(needle, candidate);
1600
+ if (dist < bestDistance) {
1601
+ bestDistance = dist;
1602
+ bestMatch = candidate;
1603
+ }
1604
+ }
1605
+ const maxAllowedDistance = Math.max(2, Math.floor(needle.length * 0.45));
1606
+ return bestDistance <= maxAllowedDistance ? bestMatch : null;
1607
+ }
1608
+
1609
+ // src/auditor/token_economy.ts
1392
1610
  var ALWAYS_ON_INSTRUCTION_CANDIDATES = [
1393
1611
  "CLAUDE.md",
1394
1612
  ".claude/CLAUDE.md",
@@ -1533,7 +1751,9 @@ function analyzeRepoTokenEconomy(repoPath, options = {}) {
1533
1751
  ];
1534
1752
  for (const ruleGroup of scopedRuleDirs) {
1535
1753
  if (fs3.existsSync(ruleGroup.dirPath) && fs3.statSync(ruleGroup.dirPath).isDirectory()) {
1536
- const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter((e) => e.isFile() && (e.name.endsWith(".mdc") || e.name.endsWith(".md"))).sort((a, b) => a.name.localeCompare(b.name));
1754
+ const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter(
1755
+ (e) => e.isFile() && (e.name.endsWith(".mdc") || e.name.endsWith(".md"))
1756
+ ).sort((a, b) => a.name.localeCompare(b.name));
1537
1757
  for (const entry of entries) {
1538
1758
  const fullPath = path2.join(ruleGroup.dirPath, entry.name);
1539
1759
  const relPath = `${ruleGroup.relPrefix}/${entry.name}`;
@@ -1670,7 +1890,11 @@ function analyzeRepoTokenEconomy(repoPath, options = {}) {
1670
1890
  0,
1671
1891
  monolithicEquivalentTokens - progressiveDisclosureL1Tokens
1672
1892
  );
1673
- const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number((estimatedTokensSaved / monolithicEquivalentTokens * 100).toFixed(2)) : 0;
1893
+ const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number(
1894
+ (estimatedTokensSaved / monolithicEquivalentTokens * 100).toFixed(
1895
+ 2
1896
+ )
1897
+ ) : 0;
1674
1898
  return TokenEconomyReportSchema.parse({
1675
1899
  repo_path: resolvedRepo.replace(/\\/g, "/"),
1676
1900
  catalog_path: resolvedCatalog.replace(/\\/g, "/"),
@@ -1742,9 +1966,15 @@ function formatTokenEconomyReport(report, format = "table") {
1742
1966
  return `${lines2.join("\n")}
1743
1967
  `;
1744
1968
  }
1969
+ const startupBar = renderProgressBar(
1970
+ report.startup_token_tax,
1971
+ report.startup_tax_budget,
1972
+ { width: 10, unit: "tokens" }
1973
+ );
1745
1974
  const lines = [
1746
1975
  "=== SkillOps Token Economy & Startup Context Tax Analyzer ===",
1747
1976
  `Startup Token Tax : ${report.startup_token_tax} / ${report.startup_tax_budget} tokens [${report.passed_budget ? "PASS" : "OVER BUDGET"}]`,
1977
+ `Startup Budget Meter : ${startupBar}`,
1748
1978
  `Monolithic Equivalent : ${report.monolithic_equivalent_tokens} tokens`,
1749
1979
  `Progressive L1 Footprint : ${report.progressive_disclosure_l1_tokens} tokens`,
1750
1980
  `Token Savings : ${report.estimated_tokens_saved} tokens (${report.token_savings_pct.toFixed(2)}% saved)`,
@@ -1760,15 +1990,23 @@ function formatTokenEconomyReport(report, format = "table") {
1760
1990
  );
1761
1991
  }
1762
1992
  }
1993
+ lines.push(
1994
+ renderNextSteps([
1995
+ {
1996
+ command: "npx skillops-cli quickstart",
1997
+ description: "Sync Progressive Disclosure Skills & Subagents"
1998
+ },
1999
+ {
2000
+ command: "npx skillops-cli doctor",
2001
+ description: "Inspect lockfile & startup tax diagnostics"
2002
+ }
2003
+ ]).trimEnd()
2004
+ );
1763
2005
  return `${lines.join("\n")}
1764
2006
  `;
1765
2007
  }
1766
2008
 
1767
2009
  // src/auditor/doctor.ts
1768
- import { spawnSync } from "child_process";
1769
- import crypto from "crypto";
1770
- import fs4 from "fs";
1771
- import path3 from "path";
1772
2010
  function computeFileSha256(content) {
1773
2011
  return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
1774
2012
  }
@@ -2000,10 +2238,12 @@ function inspectHarnesses(repoPath) {
2000
2238
  const repoLabel = path3.basename(resolvedRepo) || ".";
2001
2239
  const detectedHarnesses = detectInstalledHarnesses(resolvedRepo);
2002
2240
  const detectedSet = new Set(detectedHarnesses);
2003
- const harnesses = ALL_RUNTIME_TARGETS.map((target) => ({
2004
- ...RUNTIME_HARNESS_SPECS[target],
2005
- detected: detectedSet.has(target)
2006
- }));
2241
+ const harnesses = ALL_RUNTIME_TARGETS.map(
2242
+ (target) => ({
2243
+ ...RUNTIME_HARNESS_SPECS[target],
2244
+ detected: detectedSet.has(target)
2245
+ })
2246
+ );
2007
2247
  return {
2008
2248
  repo_path: repoLabel,
2009
2249
  total_supported: harnesses.length,
@@ -2060,9 +2300,13 @@ function formatDoctorReport(report, format = "table") {
2060
2300
  }
2061
2301
  for (const check of report.checks) {
2062
2302
  if (check.status === "fail") {
2063
- lines2.push(`::error title=SkillOps Doctor (${check.title})::${check.summary}`);
2303
+ lines2.push(
2304
+ `::error title=SkillOps Doctor (${check.title})::${check.summary}`
2305
+ );
2064
2306
  } else if (check.status === "warn") {
2065
- lines2.push(`::warning title=SkillOps Doctor (${check.title})::${check.summary}`);
2307
+ lines2.push(
2308
+ `::warning title=SkillOps Doctor (${check.title})::${check.summary}`
2309
+ );
2066
2310
  }
2067
2311
  }
2068
2312
  lines2.push(
@@ -2132,7 +2376,9 @@ function formatDoctorReport(report, format = "table") {
2132
2376
  lines.push("-".repeat(64), "Tracked Lockfile Artifacts:");
2133
2377
  for (const art of report.artifacts) {
2134
2378
  const badge = art.status === "healthy" ? "[HEALTHY]" : art.status === "drifted" ? "[DRIFTED]" : "[MISSING]";
2135
- lines.push(` ${badge} ${art.relative_path} (${art.target} :: ${art.item_name})`);
2379
+ lines.push(
2380
+ ` ${badge} ${art.relative_path} (${art.target} :: ${art.item_name})`
2381
+ );
2136
2382
  }
2137
2383
  }
2138
2384
  if (report.remediation.length > 0) {
@@ -2144,43 +2390,149 @@ function formatDoctorReport(report, format = "table") {
2144
2390
  return lines.join("\n");
2145
2391
  }
2146
2392
 
2147
- // src/eval/index.ts
2148
- import fs5 from "fs";
2149
- import path4 from "path";
2150
- import { z as z2 } from "zod";
2151
- var EvalAssertionSchema = z2.object({
2152
- id: z2.string().min(1),
2153
- type: z2.string().min(1),
2154
- target: z2.string().min(1),
2155
- description: z2.string().min(1)
2156
- }).strict();
2157
- var EvalCaseSchema = z2.object({
2158
- id: z2.string().min(1),
2159
- name: z2.string().min(1),
2160
- prompt: z2.string().min(1),
2161
- fixtures: z2.array(z2.string()).default([]),
2162
- assertions: z2.array(EvalAssertionSchema).min(1)
2163
- }).strict();
2164
- var SkillEvalSuiteSchema = z2.object({
2165
- skill_name: z2.string().min(1),
2166
- schema_version: z2.string().default("1.0"),
2167
- evals: z2.array(EvalCaseSchema).min(1)
2168
- }).strict();
2169
- var BenchmarkArmSchema = z2.object({
2170
- pass_rate_mean: z2.number().min(0).max(1),
2171
- pass_rate_stddev: z2.number().min(0).max(1),
2172
- tokens_mean: z2.number().min(0),
2173
- latency_ms_mean: z2.number().min(0)
2174
- }).strict();
2175
- var BenchmarkDeltaSchema = z2.object({
2176
- pass_rate_gain: z2.number(),
2177
- token_reduction_pct: z2.number(),
2178
- verdict: z2.enum(["CERTIFIED_IMPROVEMENT", "NEUTRAL", "REGRESSION"])
2179
- }).strict();
2180
- var SkillBenchmarkSchema = z2.object({
2181
- skill_name: z2.string().min(1),
2182
- runner: z2.string().default("deterministic-ast"),
2183
- timestamp: z2.string().min(1),
2393
+ // src/auditor/token_budget.ts
2394
+ function auditSkillTokenBudget(skillDir, options = {}) {
2395
+ const requireLayout = options.requireLayout ?? false;
2396
+ try {
2397
+ const parsed = parseSkillDirectory(skillDir, { requireLayout });
2398
+ const warnings = [];
2399
+ const errors = [];
2400
+ if (parsed.manifest.routing_signals.negative_triggers.length === 0 && !parsed.manifest.description.toLowerCase().includes("do not trigger")) {
2401
+ warnings.push(
2402
+ `Skill '${parsed.manifest.name}' has no explicit negative triggers ('Do not trigger for...' or routing_signals.negative_triggers).`
2403
+ );
2404
+ }
2405
+ if (requireLayout && parsed.missingDeclaredScripts.length > 0) {
2406
+ errors.push(
2407
+ `Skill '${parsed.manifest.name}' is missing declared script(s): ${parsed.missingDeclaredScripts.join(", ")}`
2408
+ );
2409
+ }
2410
+ return {
2411
+ skillName: parsed.manifest.name,
2412
+ skillDir: skillDir.replace(/\\/g, "/"),
2413
+ l1Words: parsed.manifest.description_words,
2414
+ l1Tokens: parsed.manifest.description_tokens,
2415
+ l2Lines: parsed.manifest.body_lines,
2416
+ l2Tokens: parsed.manifest.body_tokens,
2417
+ totalLines: parsed.totalLines,
2418
+ scriptsCount: parsed.declaredScripts.length,
2419
+ passed: errors.length === 0,
2420
+ errors,
2421
+ warnings
2422
+ };
2423
+ } catch (err) {
2424
+ const message = err instanceof Error ? err.message : String(err);
2425
+ return {
2426
+ skillName: skillDir.split(/[\\/]/).pop() ?? "unknown",
2427
+ skillDir: skillDir.replace(/\\/g, "/"),
2428
+ l1Words: 0,
2429
+ l1Tokens: 0,
2430
+ l2Lines: 0,
2431
+ l2Tokens: 0,
2432
+ totalLines: 0,
2433
+ scriptsCount: 0,
2434
+ passed: false,
2435
+ errors: [message],
2436
+ warnings: []
2437
+ };
2438
+ }
2439
+ }
2440
+ function auditSubagentTokenBudget(filePath) {
2441
+ try {
2442
+ const parsed = parseSubagentFile(filePath);
2443
+ return {
2444
+ subagentName: parsed.manifest.name,
2445
+ filePath: filePath.replace(/\\/g, "/"),
2446
+ descriptionTokens: parsed.manifest.description_tokens,
2447
+ systemPromptLines: parsed.manifest.system_prompt_lines,
2448
+ systemPromptTokens: parsed.manifest.system_prompt_tokens,
2449
+ maxOutputTokens: parsed.manifest.max_output_tokens,
2450
+ tools: parsed.manifest.tools,
2451
+ passed: true,
2452
+ errors: []
2453
+ };
2454
+ } catch (err) {
2455
+ const message = err instanceof Error ? err.message : String(err);
2456
+ return {
2457
+ subagentName: filePath.split(/[\\/]/).pop() ?? "unknown",
2458
+ filePath: filePath.replace(/\\/g, "/"),
2459
+ descriptionTokens: 0,
2460
+ systemPromptLines: 0,
2461
+ systemPromptTokens: 0,
2462
+ maxOutputTokens: 0,
2463
+ tools: [],
2464
+ passed: false,
2465
+ errors: [message]
2466
+ };
2467
+ }
2468
+ }
2469
+ function auditCatalogTokenBudgets(catalogDir, options = {}) {
2470
+ const skillDirs = discoverSkillDirectories(catalogDir);
2471
+ const subagentFiles = discoverSubagentFiles(catalogDir);
2472
+ const skills = skillDirs.map((dir) => auditSkillTokenBudget(dir, options));
2473
+ const subagents = subagentFiles.map((file) => auditSubagentTokenBudget(file));
2474
+ const errors = [];
2475
+ const warnings = [];
2476
+ let totalL1Tokens = 0;
2477
+ let totalSubagentPromptTokens = 0;
2478
+ for (const s of skills) {
2479
+ totalL1Tokens += s.l1Tokens;
2480
+ errors.push(...s.errors);
2481
+ warnings.push(...s.warnings);
2482
+ }
2483
+ for (const sa of subagents) {
2484
+ totalSubagentPromptTokens += sa.systemPromptTokens;
2485
+ errors.push(...sa.errors);
2486
+ }
2487
+ return {
2488
+ catalogPath: catalogDir.replace(/\\/g, "/"),
2489
+ skills,
2490
+ subagents,
2491
+ totalL1Tokens,
2492
+ totalSubagentPromptTokens,
2493
+ errors,
2494
+ warnings,
2495
+ passed: errors.length === 0
2496
+ };
2497
+ }
2498
+
2499
+ // src/eval/index.ts
2500
+ import fs5 from "fs";
2501
+ import path4 from "path";
2502
+ import { z as z2 } from "zod";
2503
+ var EvalAssertionSchema = z2.object({
2504
+ id: z2.string().min(1),
2505
+ type: z2.string().min(1),
2506
+ target: z2.string().min(1),
2507
+ description: z2.string().min(1)
2508
+ }).strict();
2509
+ var EvalCaseSchema = z2.object({
2510
+ id: z2.string().min(1),
2511
+ name: z2.string().min(1),
2512
+ prompt: z2.string().min(1),
2513
+ fixtures: z2.array(z2.string()).default([]),
2514
+ assertions: z2.array(EvalAssertionSchema).min(1)
2515
+ }).strict();
2516
+ var SkillEvalSuiteSchema = z2.object({
2517
+ skill_name: z2.string().min(1),
2518
+ schema_version: z2.string().default("1.0"),
2519
+ evals: z2.array(EvalCaseSchema).min(1)
2520
+ }).strict();
2521
+ var BenchmarkArmSchema = z2.object({
2522
+ pass_rate_mean: z2.number().min(0).max(1),
2523
+ pass_rate_stddev: z2.number().min(0).max(1),
2524
+ tokens_mean: z2.number().min(0),
2525
+ latency_ms_mean: z2.number().min(0)
2526
+ }).strict();
2527
+ var BenchmarkDeltaSchema = z2.object({
2528
+ pass_rate_gain: z2.number(),
2529
+ token_reduction_pct: z2.number(),
2530
+ verdict: z2.enum(["CERTIFIED_IMPROVEMENT", "NEUTRAL", "REGRESSION"])
2531
+ }).strict();
2532
+ var SkillBenchmarkSchema = z2.object({
2533
+ skill_name: z2.string().min(1),
2534
+ runner: z2.string().default("deterministic-ast"),
2535
+ timestamp: z2.string().min(1),
2184
2536
  eval_cases_count: z2.number().int().min(1).default(1),
2185
2537
  assertions_count: z2.number().int().min(1).default(1),
2186
2538
  with_skill: BenchmarkArmSchema,
@@ -2203,10 +2555,15 @@ function evaluateSkillDirectory(skillDir) {
2203
2555
  const evalsPath = path4.join(skillDir, "evals", "evals.json");
2204
2556
  const rawSuite = JSON.parse(fs5.readFileSync(evalsPath, "utf-8"));
2205
2557
  const suite = SkillEvalSuiteSchema.parse(rawSuite);
2206
- const assertionsCount = suite.evals.reduce((acc, c) => acc + c.assertions.length, 0);
2558
+ const assertionsCount = suite.evals.reduce(
2559
+ (acc, c) => acc + c.assertions.length,
2560
+ 0
2561
+ );
2207
2562
  const benchmarkPath = path4.join(skillDir, "evals", "benchmark.json");
2208
2563
  if (fs5.existsSync(benchmarkPath) && fs5.statSync(benchmarkPath).isFile()) {
2209
- const rawBench = JSON.parse(fs5.readFileSync(benchmarkPath, "utf-8"));
2564
+ const rawBench = JSON.parse(
2565
+ fs5.readFileSync(benchmarkPath, "utf-8")
2566
+ );
2210
2567
  const bench = SkillBenchmarkSchema.parse({
2211
2568
  ...rawBench,
2212
2569
  eval_cases_count: suite.evals.length,
@@ -2262,18 +2619,25 @@ function evaluateCatalog(catalogDir, options = {}) {
2262
2619
  }
2263
2620
  const count = benchmarks.length;
2264
2621
  const meanWith = count > 0 ? Number(
2265
- (benchmarks.reduce((acc, b) => acc + b.with_skill.pass_rate_mean, 0) / count).toFixed(4)
2622
+ (benchmarks.reduce(
2623
+ (acc, b) => acc + b.with_skill.pass_rate_mean,
2624
+ 0
2625
+ ) / count).toFixed(4)
2266
2626
  ) : 0;
2267
2627
  const meanWithout = count > 0 ? Number(
2268
- (benchmarks.reduce((acc, b) => acc + b.without_skill.pass_rate_mean, 0) / count).toFixed(
2269
- 4
2270
- )
2628
+ (benchmarks.reduce(
2629
+ (acc, b) => acc + b.without_skill.pass_rate_mean,
2630
+ 0
2631
+ ) / count).toFixed(4)
2271
2632
  ) : 0;
2272
2633
  const meanGain = count > 0 ? Number(
2273
2634
  (benchmarks.reduce((acc, b) => acc + b.delta.pass_rate_gain, 0) / count).toFixed(4)
2274
2635
  ) : 0;
2275
2636
  const meanTokRed = count > 0 ? Number(
2276
- (benchmarks.reduce((acc, b) => acc + b.delta.token_reduction_pct, 0) / count).toFixed(2)
2637
+ (benchmarks.reduce(
2638
+ (acc, b) => acc + b.delta.token_reduction_pct,
2639
+ 0
2640
+ ) / count).toFixed(2)
2277
2641
  ) : 0;
2278
2642
  return CatalogEvalSummarySchema.parse({
2279
2643
  catalog_path: resolvedCatalog.replace(/\\/g, "/"),
@@ -2368,12 +2732,20 @@ ${rows}
2368
2732
  ].join("\n");
2369
2733
  }
2370
2734
 
2735
+ // src/framework/index.ts
2736
+ import fs9 from "fs";
2737
+ import path9 from "path";
2738
+ import YAML5 from "yaml";
2739
+
2371
2740
  // src/mcp/index.ts
2372
2741
  import fs7 from "fs";
2373
2742
  import path7 from "path";
2374
2743
  import readline from "readline";
2375
2744
  import YAML3 from "yaml";
2376
2745
 
2746
+ // src/router/bundle_compiler.ts
2747
+ import path6 from "path";
2748
+
2377
2749
  // src/router/fingerprinter.ts
2378
2750
  import fs6 from "fs";
2379
2751
  import path5 from "path";
@@ -2473,13 +2845,17 @@ var RepoFingerprinter = class {
2473
2845
  python312Plus = true;
2474
2846
  signals.add("python3.12");
2475
2847
  }
2476
- const quotedSpecs = content.match(/["']([a-zA-Z0-9._-]+(?:\[[a-zA-Z0-9,_-]+\])?\s*(?:[><=~!][^"']+)?)["']/g) ?? [];
2848
+ const quotedSpecs = content.match(
2849
+ /["']([a-zA-Z0-9._-]+(?:\[[a-zA-Z0-9,_-]+\])?\s*(?:[><=~!][^"']+)?)["']/g
2850
+ ) ?? [];
2477
2851
  for (const spec of quotedSpecs) {
2478
2852
  recordDep(spec);
2479
2853
  }
2480
2854
  }
2481
2855
  if (fs6.existsSync(this.repoPath)) {
2482
- for (const entry of fs6.readdirSync(this.repoPath, { withFileTypes: true })) {
2856
+ for (const entry of fs6.readdirSync(this.repoPath, {
2857
+ withFileTypes: true
2858
+ })) {
2483
2859
  if (entry.isFile() && /^requirements.*\.txt$/i.test(entry.name)) {
2484
2860
  detectedFiles.add(entry.name);
2485
2861
  languages.add("python");
@@ -2499,8 +2875,14 @@ var RepoFingerprinter = class {
2499
2875
  signals.add("nodejs");
2500
2876
  signals.add("package.json");
2501
2877
  try {
2502
- const pkgJson = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
2503
- const depSections = ["dependencies", "devDependencies", "peerDependencies"];
2878
+ const pkgJson = JSON.parse(
2879
+ fs6.readFileSync(pkgJsonPath, "utf-8")
2880
+ );
2881
+ const depSections = [
2882
+ "dependencies",
2883
+ "devDependencies",
2884
+ "peerDependencies"
2885
+ ];
2504
2886
  for (const section of depSections) {
2505
2887
  const obj = pkgJson[section];
2506
2888
  if (obj && typeof obj === "object") {
@@ -2604,7 +2986,6 @@ function fingerprintRepository(repoPath) {
2604
2986
  }
2605
2987
 
2606
2988
  // src/router/bundle_compiler.ts
2607
- import path6 from "path";
2608
2989
  function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
2609
2990
  const manifest = parsedSkill.manifest;
2610
2991
  const repoSignalSet = new Set(
@@ -2639,7 +3020,10 @@ function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
2639
3020
  matchedSignals.push(`lang:${lang}`);
2640
3021
  }
2641
3022
  }
2642
- for (const dep of [...manifest.routing_signals.dependencies_any, ...manifest.tags]) {
3023
+ for (const dep of [
3024
+ ...manifest.routing_signals.dependencies_any,
3025
+ ...manifest.tags
3026
+ ]) {
2643
3027
  const lowerDep = dep.toLowerCase();
2644
3028
  const depTokens = tokenizeRoutingText(lowerDep);
2645
3029
  if (repoSignalSet.has(lowerDep) || depTokens.some((t) => repoSignalSet.has(t))) {
@@ -2676,7 +3060,9 @@ function compileSkillBundle(profile, catalogDir, options = {}) {
2676
3060
  const taskPrompt = options.taskPrompt ?? "";
2677
3061
  const resolvedCatalog = path6.resolve(catalogDir);
2678
3062
  const skillDirs = discoverSkillDirectories(resolvedCatalog);
2679
- const parsedSkills = skillDirs.map((dir) => parseSkillDirectory(dir, { requireLayout: false }));
3063
+ const parsedSkills = skillDirs.map(
3064
+ (dir) => parseSkillDirectory(dir, { requireLayout: false })
3065
+ );
2680
3066
  const featureVectors = /* @__PURE__ */ new Map();
2681
3067
  for (const p of parsedSkills) {
2682
3068
  featureVectors.set(p.manifest.name, extractSkillFeatures(p.manifest));
@@ -2792,7 +3178,7 @@ function routeRepository(repoPath, catalogDir, options = {}) {
2792
3178
  const profile = fingerprintRepository(repoPath);
2793
3179
  return compileSkillBundle(profile, catalogDir, options);
2794
3180
  }
2795
- function formatRoutedBundle(bundle, format = "table") {
3181
+ function formatRoutedBundle(bundle, format = "table", colorEnabled) {
2796
3182
  if (format === "json") {
2797
3183
  return `${JSON.stringify(bundle, null, 2)}
2798
3184
  `;
@@ -2815,16 +3201,40 @@ function formatRoutedBundle(bundle, format = "table") {
2815
3201
  ""
2816
3202
  ].join("\n");
2817
3203
  }
3204
+ const topSkill = bundle.selected_skills[0]?.name ?? "security-sast-gate";
3205
+ const l1Bar = renderProgressBar(
3206
+ bundle.total_l1_tokens,
3207
+ bundle.max_l1_tokens,
3208
+ {
3209
+ width: 10,
3210
+ unit: "tokens",
3211
+ colorEnabled
3212
+ }
3213
+ );
3214
+ const nextSteps = renderNextSteps(
3215
+ [
3216
+ {
3217
+ command: "npx skillops-cli quickstart",
3218
+ description: "Install recommended bundle & wire MCP"
3219
+ },
3220
+ {
3221
+ command: `npx skillops-cli info ${topSkill}`,
3222
+ description: `Inspect ${topSkill} triggers & L3 scripts`
3223
+ }
3224
+ ],
3225
+ colorEnabled
3226
+ );
2818
3227
  return [
2819
3228
  "=== SkillOps Context-Aware Router ===",
2820
3229
  `Repo : ${bundle.repo_profile.repo_path}`,
2821
3230
  `Stack : languages=[${bundle.repo_profile.languages.join(", ")}] frameworks=[${bundle.repo_profile.frameworks.join(", ")}] db=[${bundle.repo_profile.databases.join(", ")}]`,
2822
3231
  `Selected : ${bundle.selected_skills.length}/${bundle.max_skills} skills (${bundle.total_l1_tokens}/${bundle.max_l1_tokens} L1 tokens, max cosine=${bundle.max_pairwise_similarity.toFixed(4)})`,
3232
+ `L1 Budget : ${l1Bar}`,
2823
3233
  `Subagents : ${bundle.recommended_subagents.join(", ") || "none"}`,
2824
3234
  ...bundle.selected_skills.map(
2825
3235
  (s) => ` + ${s.name} [${s.track}] score=${s.relevance_score} (${s.l1_tokens} L1 tokens) signals=[${s.matched_signals.join(", ")}]`
2826
3236
  ),
2827
- ""
3237
+ nextSteps
2828
3238
  ].join("\n");
2829
3239
  }
2830
3240
 
@@ -2836,7 +3246,10 @@ var MCP_TOOLS = [
2836
3246
  inputSchema: {
2837
3247
  type: "object",
2838
3248
  properties: {
2839
- catalog_dir: { type: "string", description: "Optional path to catalog directory." }
3249
+ catalog_dir: {
3250
+ type: "string",
3251
+ description: "Optional path to catalog directory."
3252
+ }
2840
3253
  },
2841
3254
  additionalProperties: false
2842
3255
  }
@@ -2847,8 +3260,14 @@ var MCP_TOOLS = [
2847
3260
  inputSchema: {
2848
3261
  type: "object",
2849
3262
  properties: {
2850
- repo_path: { type: "string", description: "Target repository root path." },
2851
- startup_tax_budget: { type: "number", description: "Max startup token budget." }
3263
+ repo_path: {
3264
+ type: "string",
3265
+ description: "Target repository root path."
3266
+ },
3267
+ startup_tax_budget: {
3268
+ type: "number",
3269
+ description: "Max startup token budget."
3270
+ }
2852
3271
  },
2853
3272
  additionalProperties: false
2854
3273
  }
@@ -2859,8 +3278,14 @@ var MCP_TOOLS = [
2859
3278
  inputSchema: {
2860
3279
  type: "object",
2861
3280
  properties: {
2862
- repo_path: { type: "string", description: "Target repository root path." },
2863
- task_prompt: { type: "string", description: "Optional developer task description." }
3281
+ repo_path: {
3282
+ type: "string",
3283
+ description: "Target repository root path."
3284
+ },
3285
+ task_prompt: {
3286
+ type: "string",
3287
+ description: "Optional developer task description."
3288
+ }
2864
3289
  },
2865
3290
  additionalProperties: false
2866
3291
  }
@@ -2883,7 +3308,10 @@ var MCP_TOOLS = [
2883
3308
  inputSchema: {
2884
3309
  type: "object",
2885
3310
  properties: {
2886
- skill_name: { type: "string", description: "Optional skill name filter." }
3311
+ skill_name: {
3312
+ type: "string",
3313
+ description: "Optional skill name filter."
3314
+ }
2887
3315
  },
2888
3316
  additionalProperties: false
2889
3317
  }
@@ -2903,8 +3331,14 @@ var MCP_TOOLS = [
2903
3331
  inputSchema: {
2904
3332
  type: "object",
2905
3333
  properties: {
2906
- repo_path: { type: "string", description: "Target repository root path." },
2907
- startup_tax_budget: { type: "number", description: "Optional startup token budget override." }
3334
+ repo_path: {
3335
+ type: "string",
3336
+ description: "Target repository root path."
3337
+ },
3338
+ startup_tax_budget: {
3339
+ type: "number",
3340
+ description: "Optional startup token budget override."
3341
+ }
2908
3342
  },
2909
3343
  additionalProperties: false
2910
3344
  }
@@ -2915,7 +3349,10 @@ var MCP_TOOLS = [
2915
3349
  inputSchema: {
2916
3350
  type: "object",
2917
3351
  properties: {
2918
- repo_path: { type: "string", description: "Target repository root path." }
3352
+ repo_path: {
3353
+ type: "string",
3354
+ description: "Target repository root path."
3355
+ }
2919
3356
  },
2920
3357
  additionalProperties: false
2921
3358
  }
@@ -2997,7 +3434,9 @@ function handleMcpJsonRpcRequest(request, context) {
2997
3434
  const skillFilter = typeof args.skill_name === "string" ? args.skill_name : void 0;
2998
3435
  payload = evaluateCatalog(context.catalogDir, { skillFilter });
2999
3436
  } else if (toolName === "list_catalog_items") {
3000
- payload = auditCatalogTokenBudgets(context.catalogDir, { requireLayout: false });
3437
+ payload = auditCatalogTokenBudgets(context.catalogDir, {
3438
+ requireLayout: false
3439
+ });
3001
3440
  } else if (toolName === "diagnose_repo") {
3002
3441
  const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3003
3442
  const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : void 0;
@@ -3202,7 +3641,11 @@ function setupMcpForHarnesses(repoPath, options = {}) {
3202
3641
  const absPath = path7.join(resolvedRepo, spec.mcpConfigPath);
3203
3642
  const exists = fs7.existsSync(absPath) && fs7.statSync(absPath).isFile();
3204
3643
  const existingRaw = exists ? fs7.readFileSync(absPath, "utf-8") : null;
3205
- const updatedRaw = buildUpdatedMcpConfigContent(target, existingRaw, serverName);
3644
+ const updatedRaw = buildUpdatedMcpConfigContent(
3645
+ target,
3646
+ existingRaw,
3647
+ serverName
3648
+ );
3206
3649
  let status;
3207
3650
  if (!exists) {
3208
3651
  status = "created";
@@ -3222,7 +3665,9 @@ function setupMcpForHarnesses(repoPath, options = {}) {
3222
3665
  status
3223
3666
  });
3224
3667
  }
3225
- const configuredCount = actions.filter((a) => a.status !== "unsupported").length;
3668
+ const configuredCount = actions.filter(
3669
+ (a) => a.status !== "unsupported"
3670
+ ).length;
3226
3671
  return {
3227
3672
  repo_path: path7.basename(resolvedRepo) || ".",
3228
3673
  dry_run: dryRun,
@@ -3701,8 +4146,12 @@ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle,
3701
4146
  total_l1_tokens: routedBundle.total_l1_tokens,
3702
4147
  artifacts: newLockArtifacts
3703
4148
  });
3704
- fs8.writeFileSync(lockfilePath, `${JSON.stringify(lockData, null, 2)}
3705
- `, "utf-8");
4149
+ fs8.writeFileSync(
4150
+ lockfilePath,
4151
+ `${JSON.stringify(lockData, null, 2)}
4152
+ `,
4153
+ "utf-8"
4154
+ );
3706
4155
  }
3707
4156
  return SyncReportSchema.parse({
3708
4157
  repo_path: resolvedRepo.replace(/\\/g, "/"),
@@ -3753,15 +4202,26 @@ function syncRepository(repoPath, catalogDir, options = {}) {
3753
4202
  target: "codex",
3754
4203
  itemName: "codex-agents-index",
3755
4204
  relativePath: "AGENTS.md",
3756
- content: renderCodexAgentsIndex(existingAgentsMd, selectedSkills, selectedSubagents)
4205
+ content: renderCodexAgentsIndex(
4206
+ existingAgentsMd,
4207
+ selectedSkills,
4208
+ selectedSubagents
4209
+ )
3757
4210
  });
3758
4211
  }
3759
4212
  }
3760
- return applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, {
3761
- dryRun,
3762
- force,
3763
- preserveUnlistedLockEntries: false
3764
- });
4213
+ return applyPlannedFiles(
4214
+ resolvedRepo,
4215
+ resolvedCatalog,
4216
+ targets,
4217
+ routedBundle,
4218
+ plannedFiles,
4219
+ {
4220
+ dryRun,
4221
+ force,
4222
+ preserveUnlistedLockEntries: false
4223
+ }
4224
+ );
3765
4225
  }
3766
4226
  function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3767
4227
  const resolvedRepo = path8.resolve(repoPath);
@@ -3805,15 +4265,24 @@ function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3805
4265
  plannedFiles.push(...renderSkillFilesForRuntime(matchedSkill, target));
3806
4266
  }
3807
4267
  if (matchedSubagent) {
3808
- plannedFiles.push(...renderSubagentFilesForRuntime(matchedSubagent, target));
4268
+ plannedFiles.push(
4269
+ ...renderSubagentFilesForRuntime(matchedSubagent, target)
4270
+ );
3809
4271
  }
3810
4272
  }
3811
4273
  const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3812
- return applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, {
3813
- dryRun,
3814
- force,
3815
- preserveUnlistedLockEntries: true
3816
- });
4274
+ return applyPlannedFiles(
4275
+ resolvedRepo,
4276
+ resolvedCatalog,
4277
+ targets,
4278
+ routedBundle,
4279
+ plannedFiles,
4280
+ {
4281
+ dryRun,
4282
+ force,
4283
+ preserveUnlistedLockEntries: true
4284
+ }
4285
+ );
3817
4286
  }
3818
4287
  function pruneEmptyParentDirs(filePath, stopAtRepoRoot) {
3819
4288
  const normalizedStop = path8.resolve(stopAtRepoRoot);
@@ -3867,7 +4336,9 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3867
4336
  `No synced artifacts found for '${itemName}' in .skillops-lock.json.`
3868
4337
  );
3869
4338
  }
3870
- const newLockArtifacts = { ...existingLock.artifacts };
4339
+ const newLockArtifacts = {
4340
+ ...existingLock.artifacts
4341
+ };
3871
4342
  const artifacts = [];
3872
4343
  const driftConflicts = [];
3873
4344
  for (const [relPath, entry] of matchingEntries) {
@@ -3913,8 +4384,12 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3913
4384
  selected_subagents: updatedSubagents,
3914
4385
  artifacts: newLockArtifacts
3915
4386
  });
3916
- fs8.writeFileSync(lockfilePath, `${JSON.stringify(lockData, null, 2)}
3917
- `, "utf-8");
4387
+ fs8.writeFileSync(
4388
+ lockfilePath,
4389
+ `${JSON.stringify(lockData, null, 2)}
4390
+ `,
4391
+ "utf-8"
4392
+ );
3918
4393
  }
3919
4394
  const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3920
4395
  return SyncReportSchema.parse({
@@ -4009,7 +4484,7 @@ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
4009
4484
  passed: driftConflicts.length === 0
4010
4485
  });
4011
4486
  }
4012
- function formatSyncReport(report, format = "table") {
4487
+ function formatSyncReport(report, format = "table", colorEnabled) {
4013
4488
  if (format === "json") {
4014
4489
  return `${JSON.stringify(report, null, 2)}
4015
4490
  `;
@@ -4031,6 +4506,30 @@ function formatSyncReport(report, format = "table") {
4031
4506
  ""
4032
4507
  ].join("\n");
4033
4508
  }
4509
+ const c = colorEnabled;
4510
+ const activeArtifacts = report.artifacts.filter(
4511
+ (a) => a.action === "created" || a.action === "updated" || a.action === "unchanged"
4512
+ );
4513
+ const firstSkill = activeArtifacts.find((a) => a.skill_name !== "codex-agents-index")?.skill_name ?? report.routed_bundle.selected_skills[0]?.name ?? "security-sast-gate";
4514
+ const usageBox = activeArtifacts.length > 0 ? renderAiUsageGuide(
4515
+ report.targets,
4516
+ [firstSkill],
4517
+ report.routed_bundle.recommended_subagents,
4518
+ c
4519
+ ) : "";
4520
+ const nextSteps = renderNextSteps(
4521
+ [
4522
+ {
4523
+ command: `npx skillops-cli info ${firstSkill}`,
4524
+ description: "Inspect triggers, token budget & L3 scripts"
4525
+ },
4526
+ {
4527
+ command: "npx skillops-cli doctor",
4528
+ description: "Verify lockfile SHA-256 & Startup Token Tax"
4529
+ }
4530
+ ],
4531
+ c
4532
+ );
4034
4533
  return [
4035
4534
  "=== SkillOps Multi-Runtime Transpiler & Sync Engine ===",
4036
4535
  `Repo : ${report.repo_path}`,
@@ -4040,101 +4539,1644 @@ function formatSyncReport(report, format = "table") {
4040
4539
  (a) => ` [${a.action.toUpperCase()}] (${a.target}) ${a.relative_path}`
4041
4540
  ),
4042
4541
  ...report.drift_conflicts.map(
4043
- (c) => ` [DRIFT CONFLICT] ${c} modified locally. Re-run with --force to overwrite.`
4542
+ (cItem) => ` [DRIFT CONFLICT] ${cItem} modified locally. Re-run with --force to overwrite.`
4044
4543
  ),
4045
- ""
4046
- ].join("\n");
4544
+ usageBox,
4545
+ nextSteps
4546
+ ].filter(Boolean).join("\n");
4047
4547
  }
4048
4548
 
4049
- // src/cli.ts
4050
- var PACKAGE_ROOT = path9.resolve(path9.dirname(fileURLToPath(import.meta.url)), "..");
4051
- function resolveCatalogDir(args, repoPath, loadedConfig) {
4052
- const catalogIdx = args.indexOf("--catalog");
4053
- if (catalogIdx !== -1 && args[catalogIdx + 1]) {
4054
- return path9.resolve(args[catalogIdx + 1]);
4055
- }
4056
- const baseRepo = repoPath ? path9.resolve(repoPath) : process.cwd();
4057
- if (loadedConfig?.catalog_dir) {
4058
- const cfgCatalog = path9.resolve(baseRepo, loadedConfig.catalog_dir);
4059
- if (fs9.existsSync(cfgCatalog)) {
4060
- return cfgCatalog;
4061
- }
4062
- }
4063
- if (repoPath) {
4064
- const repoCatalog = path9.join(path9.resolve(repoPath), "catalog");
4065
- if (fs9.existsSync(repoCatalog)) {
4066
- return repoCatalog;
4067
- }
4068
- }
4069
- const cwdCatalog = path9.resolve(process.cwd(), "catalog");
4070
- if (fs9.existsSync(cwdCatalog)) {
4071
- return cwdCatalog;
4072
- }
4073
- return path9.join(PACKAGE_ROOT, "catalog");
4074
- }
4075
- function runCli(argv) {
4076
- const args = argv.slice(2);
4077
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
4078
- process.stdout.write(
4079
- [
4080
- `SkillOps v${SKILLOPS_VERSION} \u2014 The Zero-API NPX Engine for High-End AI Skills & Subagents`,
4081
- "",
4082
- "Usage: npx skillops@latest <command> [options]",
4083
- "",
4084
- "Commands:",
4085
- " init Scaffold .skillops.yml and detect repository agent harnesses",
4086
- " list Browse bundled high-end Skills & Subagents with token budgets",
4087
- " add Install a specific high-end Skill or Subagent into active runtimes",
4088
- " remove Surgically uninstall a synced Skill or Subagent from active runtimes",
4089
- " clean Remove all SkillOps-managed runtime artifacts tracked in .skillops-lock.json",
4090
- " harnesses List all 20 supported AI coding agents, target paths, MCP support & repo detection",
4091
- " doctor Diagnose Node/Python environment, .skillops.yml config & lockfile SHA-256 drift",
4092
- " status Alias for 'doctor' \u2014 inspect repository lockfile drift and token tax status",
4093
- " tokens Analyze repository Startup Token Tax and Progressive Disclosure savings",
4094
- " audit Run L1/L2/L3 token budget linter, Subagent check & collision matrix",
4095
- " route Fingerprint repository stack and compile optimal <=12 skill bundle",
4096
- " sync Transpile Skills & Subagents across all 20 AI coding agents (Claude, Cursor, Windsurf, etc.)",
4097
- " eval Execute zero-API deterministic TDD benchmarks (with_skill vs without_skill)",
4098
- " mcp Start native JSON-RPC 2.0 stdio Model Context Protocol server",
4099
- " mcp-setup Register SkillOps MCP server across all MCP-compatible AI coding IDEs & agents",
4100
- ""
4101
- ].join("\n")
4102
- );
4103
- return ExitCode.SUCCESS;
4104
- }
4105
- if (args.includes("--version") || args.includes("-V") || args[0] === "version") {
4106
- process.stdout.write(`skillops ${SKILLOPS_VERSION}
4107
- `);
4108
- return ExitCode.SUCCESS;
4109
- }
4110
- const command = args[0];
4111
- if (command === "init") {
4112
- const repoIdx = args.indexOf("--repo");
4113
- const repoPath = path9.resolve(
4114
- repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
4115
- );
4116
- const catalogDir = resolveCatalogDir(args, repoPath);
4117
- const profile = fingerprintRepository(repoPath);
4118
- const targetsIdx = args.indexOf("--targets");
4119
- const initTargets = targetsIdx !== -1 && args[targetsIdx + 1] ? resolveRuntimeTargets(args[targetsIdx + 1], repoPath) : profile.detected_harnesses.length > 0 ? profile.detected_harnesses : [...CORE_RUNTIME_TARGETS];
4120
- const configPayload = SkillOpsConfigSchema.parse({
4121
- version: "1.0",
4122
- catalog_dir: path9.relative(repoPath, catalogDir).replace(/\\/g, "/") || "catalog",
4123
- token_economy: {
4124
- startup_tax_budget: 2500,
4125
- max_active_skills: 12,
4126
- max_l1_tokens: 1500,
4127
- max_similarity: 0.78
4549
+ // src/ui/help.ts
4550
+ var COMMAND_HELP_SPECS = [
4551
+ {
4552
+ name: "quickstart",
4553
+ aliases: ["setup"],
4554
+ pillar: "library",
4555
+ summary: "1-step onboarding: detect, route & sync",
4556
+ usage: "npx skillops-cli quickstart [options]",
4557
+ options: [
4558
+ {
4559
+ flags: "-p, --project <dir>",
4560
+ description: "Target repository directory (default: cwd)"
4128
4561
  },
4129
- detected_stack: {
4130
- languages: profile.languages,
4131
- frameworks: profile.frameworks
4562
+ {
4563
+ flags: "-t, --harness <list|auto|all>",
4564
+ description: "Target AI coding harnesses (claude,cursor...)"
4132
4565
  },
4133
- targets: initTargets
4134
- });
4135
- const configPath = path9.join(repoPath, ".skillops.yml");
4136
- fs9.mkdirSync(repoPath, { recursive: true });
4566
+ {
4567
+ flags: "--top, --max-skills <n>",
4568
+ description: "Max Skills to activate in bundle (default: 12)"
4569
+ },
4570
+ {
4571
+ flags: "--skip-mcp, --no-mcp",
4572
+ description: "Skip automatic MCP server registration"
4573
+ },
4574
+ {
4575
+ flags: "--dry-run",
4576
+ description: "Preview planned changes without writing files"
4577
+ },
4578
+ {
4579
+ flags: "--force",
4580
+ description: "Overwrite locally modified artifacts"
4581
+ },
4582
+ {
4583
+ flags: "-f, --format <table|json|md>",
4584
+ description: "Output report format (default: table)"
4585
+ }
4586
+ ],
4587
+ examples: [
4588
+ {
4589
+ command: "npx skillops-cli quickstart",
4590
+ description: "Auto-detect stack, sync Skills & wire MCP"
4591
+ },
4592
+ {
4593
+ command: "npx skillops-cli quickstart -t cursor,claude --top 6",
4594
+ description: "Sync top 6 Skills to Cursor & Claude Code"
4595
+ }
4596
+ ]
4597
+ },
4598
+ {
4599
+ name: "list",
4600
+ aliases: [],
4601
+ pillar: "library",
4602
+ summary: "Browse all 10 Skills & 9 Subagents",
4603
+ usage: "npx skillops-cli list [options]",
4604
+ options: [
4605
+ {
4606
+ flags: "--catalog <dir>",
4607
+ description: "Custom catalog directory to list"
4608
+ },
4609
+ {
4610
+ flags: "-f, --format <table|json|md>",
4611
+ description: "Output format (default: table)"
4612
+ }
4613
+ ],
4614
+ examples: [
4615
+ {
4616
+ command: "npx skillops-cli list",
4617
+ description: "Display all 10 Skills & 9 Subagents"
4618
+ },
4619
+ {
4620
+ command: "npx skillops-cli info security-sast-gate",
4621
+ description: "Inspect a specific Skill from the Library"
4622
+ }
4623
+ ]
4624
+ },
4625
+ {
4626
+ name: "search",
4627
+ aliases: ["find"],
4628
+ pillar: "library",
4629
+ summary: "Search catalog by keyword, stack or domain",
4630
+ usage: "npx skillops-cli search <query> [options]",
4631
+ options: [
4632
+ {
4633
+ flags: "--catalog <dir>",
4634
+ description: "Custom catalog directory to search"
4635
+ },
4636
+ {
4637
+ flags: "-f, --format <table|json|md>",
4638
+ description: "Output format (default: table)"
4639
+ }
4640
+ ],
4641
+ examples: [
4642
+ {
4643
+ command: "npx skillops-cli search security",
4644
+ description: "Find Skills/Subagents matching security/SAST"
4645
+ },
4646
+ {
4647
+ command: "npx skillops-cli find postgres",
4648
+ description: "Find database & PostgreSQL optimization Skills"
4649
+ }
4650
+ ]
4651
+ },
4652
+ {
4653
+ name: "info",
4654
+ aliases: ["show", "inspect"],
4655
+ pillar: "library",
4656
+ summary: "Inspect triggers, L2 budget & AI usage",
4657
+ usage: "npx skillops-cli info <name> [options]",
4658
+ options: [
4659
+ {
4660
+ flags: "--catalog <dir>",
4661
+ description: "Custom catalog directory"
4662
+ },
4663
+ {
4664
+ flags: "-f, --format <table|json|md>",
4665
+ description: "Output format (default: table)"
4666
+ }
4667
+ ],
4668
+ examples: [
4669
+ {
4670
+ command: "npx skillops-cli info bug-reproducer-tdd",
4671
+ description: "Inspect triggers, L3 scripts & IDE invocation"
4672
+ },
4673
+ {
4674
+ command: "npx skillops-cli show red-team-breaker",
4675
+ description: "Inspect the red-team-breaker Subagent card"
4676
+ }
4677
+ ]
4678
+ },
4679
+ {
4680
+ name: "add",
4681
+ aliases: ["install"],
4682
+ pillar: "library",
4683
+ summary: "Install Skills/Subagents into AI harnesses",
4684
+ usage: "npx skillops-cli add <name...> [options]",
4685
+ options: [
4686
+ {
4687
+ flags: "-p, --project <dir>",
4688
+ description: "Target repository directory"
4689
+ },
4690
+ {
4691
+ flags: "-t, --harness <list|auto|all>",
4692
+ description: "Target AI harnesses (claude,cursor,all...)"
4693
+ },
4694
+ {
4695
+ flags: "--dry-run",
4696
+ description: "Preview planned writes without modifying disk"
4697
+ },
4698
+ {
4699
+ flags: "--force",
4700
+ description: "Overwrite locally drifted files"
4701
+ },
4702
+ {
4703
+ flags: "-f, --format <table|json|md>",
4704
+ description: "Output format (default: table)"
4705
+ }
4706
+ ],
4707
+ examples: [
4708
+ {
4709
+ command: "npx skillops-cli install security-sast-gate",
4710
+ description: "Install security-sast-gate into harnesses"
4711
+ },
4712
+ {
4713
+ command: "npx skillops-cli add mcp-architect -t claude,cursor",
4714
+ description: "Install mcp-architect into Claude & Cursor"
4715
+ }
4716
+ ]
4717
+ },
4718
+ {
4719
+ name: "remove",
4720
+ aliases: ["uninstall", "rm"],
4721
+ pillar: "library",
4722
+ summary: "Remove a Skill/Subagent from harnesses",
4723
+ usage: "npx skillops-cli remove <name...> [options]",
4724
+ options: [
4725
+ {
4726
+ flags: "-p, --project <dir>",
4727
+ description: "Target repository directory"
4728
+ },
4729
+ {
4730
+ flags: "-t, --harness <list>",
4731
+ description: "Restrict removal to specific harnesses"
4732
+ },
4733
+ {
4734
+ flags: "--force",
4735
+ description: "Remove even if SHA-256 drift is detected"
4736
+ }
4737
+ ],
4738
+ examples: [
4739
+ {
4740
+ command: "npx skillops-cli uninstall mcp-architect",
4741
+ description: "Remove runtime files & update lockfile"
4742
+ }
4743
+ ]
4744
+ },
4745
+ {
4746
+ name: "route",
4747
+ aliases: ["recommend"],
4748
+ pillar: "library",
4749
+ summary: "Recommend optimal <=12 Skill bundle",
4750
+ usage: "npx skillops-cli recommend [options]",
4751
+ options: [
4752
+ {
4753
+ flags: "-p, --project <dir>",
4754
+ description: "Repository directory to fingerprint"
4755
+ },
4756
+ {
4757
+ flags: "--task <prompt>",
4758
+ description: "Task prompt to boost task-specific Skills"
4759
+ },
4760
+ {
4761
+ flags: "--top, --max-skills <n>",
4762
+ description: "Maximum Skills to select (default: 12)"
4763
+ },
4764
+ {
4765
+ flags: "--max-l1-tokens <n>",
4766
+ description: "Maximum L1 routing token budget (default: 1500)"
4767
+ },
4768
+ {
4769
+ flags: "-f, --format <table|json|md>",
4770
+ description: "Output format (default: table)"
4771
+ }
4772
+ ],
4773
+ examples: [
4774
+ {
4775
+ command: "npx skillops-cli recommend",
4776
+ description: "Recommend best Skills for current repo stack"
4777
+ },
4778
+ {
4779
+ command: 'npx skillops-cli route --task "slow postgres" --top 4',
4780
+ description: "Route top 4 Skills for a specific task"
4781
+ }
4782
+ ]
4783
+ },
4784
+ {
4785
+ name: "create",
4786
+ aliases: ["new"],
4787
+ pillar: "framework",
4788
+ summary: "Scaffold custom Skill or Subagent template",
4789
+ usage: "npx skillops-cli create <skill|subagent> <name> [options]",
4790
+ options: [
4791
+ {
4792
+ flags: "-d, --description <text>",
4793
+ description: "Custom trigger description (INV-SKILL-01..04)"
4794
+ },
4795
+ {
4796
+ flags: "--catalog <dir>",
4797
+ description: "Target catalog directory (default: ./catalog)"
4798
+ },
4799
+ {
4800
+ flags: "--force",
4801
+ description: "Overwrite existing Skill or Subagent template"
4802
+ },
4803
+ {
4804
+ flags: "-f, --format <table|json|md>",
4805
+ description: "Output format (default: table)"
4806
+ }
4807
+ ],
4808
+ examples: [
4809
+ {
4810
+ command: "npx skillops-cli create skill graphql-schema-guard",
4811
+ description: "Scaffold a new Skill with L3 script & evals"
4812
+ },
4813
+ {
4814
+ command: "npx skillops-cli create subagent migration-reviewer",
4815
+ description: "Scaffold a new Context-Isolated Subagent"
4816
+ }
4817
+ ]
4818
+ },
4819
+ {
4820
+ name: "audit",
4821
+ aliases: [],
4822
+ pillar: "framework",
4823
+ summary: "Verify L1/L2 budgets & TF-IDF collisions",
4824
+ usage: "npx skillops-cli audit [options]",
4825
+ options: [
4826
+ {
4827
+ flags: "--catalog <dir>",
4828
+ description: "Catalog directory to audit"
4829
+ },
4830
+ {
4831
+ flags: "--require-layout",
4832
+ description: "Enforce evals/evals.json and L3 scripts"
4833
+ },
4834
+ {
4835
+ flags: "--strict",
4836
+ description: "Fail on HIGH_RISK as well as BLOCKING collisions"
4837
+ },
4838
+ {
4839
+ flags: "-f, --format <table|json|gh>",
4840
+ description: "Output format (default: table)"
4841
+ }
4842
+ ],
4843
+ examples: [
4844
+ {
4845
+ command: "npx skillops-cli audit --require-layout --strict",
4846
+ description: "Certify token budgets & zero collisions"
4847
+ }
4848
+ ]
4849
+ },
4850
+ {
4851
+ name: "eval",
4852
+ aliases: [],
4853
+ pillar: "framework",
4854
+ summary: "Run deterministic A/B TDD benchmarks",
4855
+ usage: "npx skillops-cli eval [options]",
4856
+ options: [
4857
+ {
4858
+ flags: "--catalog <dir>",
4859
+ description: "Catalog directory to evaluate"
4860
+ },
4861
+ {
4862
+ flags: "--skill <name>",
4863
+ description: "Evaluate a single Skill by name"
4864
+ },
4865
+ {
4866
+ flags: "--out <file>",
4867
+ description: "Write benchmark report (HTML/Markdown) to disk"
4868
+ },
4869
+ {
4870
+ flags: "--strict",
4871
+ description: "Exit 4 if any Skill fails CERTIFIED_IMPROVEMENT"
4872
+ },
4873
+ {
4874
+ flags: "-f, --format <table|json|html>",
4875
+ description: "Output format (default: table)"
4876
+ }
4877
+ ],
4878
+ examples: [
4879
+ {
4880
+ command: "npx skillops-cli eval --strict",
4881
+ description: "Run deterministic A/B evals across catalog"
4882
+ },
4883
+ {
4884
+ command: "npx skillops-cli eval -f html --out benchmark.html",
4885
+ description: "Export standalone HTML benchmark dashboard"
4886
+ }
4887
+ ]
4888
+ },
4889
+ {
4890
+ name: "init",
4891
+ aliases: [],
4892
+ pillar: "runtime",
4893
+ summary: "Create .skillops.yml and detect harnesses",
4894
+ usage: "npx skillops-cli init [options]",
4895
+ options: [
4896
+ {
4897
+ flags: "-p, --project <dir>",
4898
+ description: "Target repository directory"
4899
+ },
4900
+ {
4901
+ flags: "-t, --harness <list|auto|all>",
4902
+ description: "Initial target harnesses for .skillops.yml"
4903
+ }
4904
+ ],
4905
+ examples: [
4906
+ {
4907
+ command: "npx skillops-cli init",
4908
+ description: "Create .skillops.yml with detected harnesses"
4909
+ }
4910
+ ]
4911
+ },
4912
+ {
4913
+ name: "sync",
4914
+ aliases: [],
4915
+ pillar: "runtime",
4916
+ summary: "Transpile bundle across 20 AI agents",
4917
+ usage: "npx skillops-cli sync [options]",
4918
+ options: [
4919
+ {
4920
+ flags: "-p, --project <dir>",
4921
+ description: "Target repository directory"
4922
+ },
4923
+ {
4924
+ flags: "-t, --harness <list|auto|all>",
4925
+ description: "Target AI harnesses (default: .skillops.yml)"
4926
+ },
4927
+ {
4928
+ flags: "--top, --max-skills <n>",
4929
+ description: "Maximum Skills to sync"
4930
+ },
4931
+ {
4932
+ flags: "--dry-run",
4933
+ description: "Preview planned artifacts without writing"
4934
+ },
4935
+ {
4936
+ flags: "--force",
4937
+ description: "Overwrite locally drifted files"
4938
+ }
4939
+ ],
4940
+ examples: [
4941
+ {
4942
+ command: "npx skillops-cli sync --auto",
4943
+ description: "Sync bundle into all detected AI harnesses"
4944
+ },
4945
+ {
4946
+ command: "npx skillops-cli sync --harness all",
4947
+ description: "Transpile across all 20 supported AI agents"
4948
+ }
4949
+ ]
4950
+ },
4951
+ {
4952
+ name: "clean",
4953
+ aliases: [],
4954
+ pillar: "runtime",
4955
+ summary: "Remove all tracked runtime artifacts",
4956
+ usage: "npx skillops-cli clean [options]",
4957
+ options: [
4958
+ {
4959
+ flags: "-p, --project <dir>",
4960
+ description: "Target repository directory"
4961
+ },
4962
+ {
4963
+ flags: "--dry-run",
4964
+ description: "Preview files that would be removed"
4965
+ },
4966
+ {
4967
+ flags: "--force",
4968
+ description: "Remove even if local edits are detected"
4969
+ }
4970
+ ],
4971
+ examples: [
4972
+ {
4973
+ command: "npx skillops-cli clean",
4974
+ description: "Remove all synced artifacts & lockfile"
4975
+ }
4976
+ ]
4977
+ },
4978
+ {
4979
+ name: "tokens",
4980
+ aliases: ["budget"],
4981
+ pillar: "runtime",
4982
+ summary: "Analyze Startup Token Tax & context bloat",
4983
+ usage: "npx skillops-cli budget [options]",
4984
+ options: [
4985
+ {
4986
+ flags: "-p, --project <dir>",
4987
+ description: "Repository directory to inspect"
4988
+ },
4989
+ {
4990
+ flags: "--budget <tokens>",
4991
+ description: "Startup Token Tax ceiling (default: 2500)"
4992
+ },
4993
+ {
4994
+ flags: "--strict",
4995
+ description: "Exit 3 if Startup Token Tax exceeds budget"
4996
+ },
4997
+ {
4998
+ flags: "-f, --format <table|json|md>",
4999
+ description: "Output format (default: table)"
5000
+ }
5001
+ ],
5002
+ examples: [
5003
+ {
5004
+ command: "npx skillops-cli budget",
5005
+ description: "Inspect repository Startup Token Tax & savings"
5006
+ }
5007
+ ]
5008
+ },
5009
+ {
5010
+ name: "doctor",
5011
+ aliases: ["status"],
5012
+ pillar: "runtime",
5013
+ summary: "Diagnose environment, drift & token budget",
5014
+ usage: "npx skillops-cli doctor [options]",
5015
+ options: [
5016
+ {
5017
+ flags: "-p, --project <dir>",
5018
+ description: "Repository directory to diagnose"
5019
+ },
5020
+ {
5021
+ flags: "--strict",
5022
+ description: "Fail on warnings as well as errors"
5023
+ },
5024
+ {
5025
+ flags: "-f, --format <table|json|md>",
5026
+ description: "Output format (default: table)"
5027
+ }
5028
+ ],
5029
+ examples: [
5030
+ {
5031
+ command: "npx skillops-cli doctor",
5032
+ description: "Run full repository & lockfile health check"
5033
+ }
5034
+ ]
5035
+ },
5036
+ {
5037
+ name: "harnesses",
5038
+ aliases: [],
5039
+ pillar: "runtime",
5040
+ summary: "List 20 supported AI agents & repo status",
5041
+ usage: "npx skillops-cli harnesses [options]",
5042
+ options: [
5043
+ {
5044
+ flags: "-p, --project <dir>",
5045
+ description: "Repository directory to scan for AI harnesses"
5046
+ },
5047
+ {
5048
+ flags: "-f, --format <table|json|md>",
5049
+ description: "Output format (default: table)"
5050
+ }
5051
+ ],
5052
+ examples: [
5053
+ {
5054
+ command: "npx skillops-cli harnesses",
5055
+ description: "View all 20 supported AI IDEs/CLI agents"
5056
+ }
5057
+ ]
5058
+ },
5059
+ {
5060
+ name: "mcp-setup",
5061
+ aliases: [],
5062
+ pillar: "runtime",
5063
+ summary: "Wire MCP server across 13 AI IDEs & agents",
5064
+ usage: "npx skillops-cli mcp-setup [options]",
5065
+ options: [
5066
+ {
5067
+ flags: "-p, --project <dir>",
5068
+ description: "Target repository directory"
5069
+ },
5070
+ {
5071
+ flags: "-t, --harness <list|auto|all>",
5072
+ description: "Target harnesses to configure MCP for"
5073
+ },
5074
+ {
5075
+ flags: "--dry-run",
5076
+ description: "Preview MCP config files without writing"
5077
+ }
5078
+ ],
5079
+ examples: [
5080
+ {
5081
+ command: "npx skillops-cli mcp-setup --auto",
5082
+ description: "Wire SkillOps MCP into detected IDEs"
5083
+ }
5084
+ ]
5085
+ },
5086
+ {
5087
+ name: "mcp",
5088
+ aliases: [],
5089
+ pillar: "runtime",
5090
+ summary: "Start stdio JSON-RPC 2.0 MCP server",
5091
+ usage: "npx skillops-cli mcp [options]",
5092
+ options: [
5093
+ {
5094
+ flags: "-p, --project <dir>",
5095
+ description: "Repository root served over MCP"
5096
+ }
5097
+ ],
5098
+ examples: [
5099
+ {
5100
+ command: "npx skillops-cli mcp",
5101
+ description: "Start stdio MCP server for AI IDEs"
5102
+ }
5103
+ ]
5104
+ }
5105
+ ];
5106
+ function findCommandHelpSpec(commandOrAlias) {
5107
+ const normalized = commandOrAlias.trim().toLowerCase();
5108
+ return COMMAND_HELP_SPECS.find(
5109
+ (spec) => spec.name === normalized || spec.aliases.includes(normalized)
5110
+ );
5111
+ }
5112
+ function renderGlobalHelp(version, colorEnabled) {
5113
+ const c = colorEnabled;
5114
+ const bar = style.dim("\u2502", c);
5115
+ const banner = renderHeaderBanner(
5116
+ "Framework & Library for AI Skills & Subagents",
5117
+ {
5118
+ version,
5119
+ colorEnabled: c
5120
+ }
5121
+ );
5122
+ const formatCmdSection = (pillar) => {
5123
+ const specs = COMMAND_HELP_SPECS.filter((s) => s.pillar === pillar);
5124
+ return specs.map((s) => {
5125
+ const aliasPart = s.aliases.length > 0 ? style.dim(` (${s.aliases.join(", ")})`, c) : "";
5126
+ const cmdCol = padVisible(
5127
+ `${style.bold(style.cyan(s.name, c), c)}${aliasPart}`,
5128
+ 23,
5129
+ "left"
5130
+ );
5131
+ return `${bar} ${cmdCol}${s.summary}`;
5132
+ }).join("\n");
5133
+ };
5134
+ return [
5135
+ banner,
5136
+ `${bar} Usage: npx skillops-cli <command> [options]`,
5137
+ renderSectionHeader("Quickstart (Library + Framework)", c),
5138
+ `${bar} ${style.dim("$", c)} ${style.bold(style.green("npx skillops-cli quickstart", c), c)} ${style.dim("\u2014 Detect stack, sync Skills & MCP", c)}`,
5139
+ `${bar} ${style.dim("$", c)} ${style.bold(style.cyan("npx skillops-cli info <name>", c), c)} ${style.dim("\u2014 Inspect Skill/Subagent & usage", c)}`,
5140
+ `${bar} ${style.dim("$", c)} ${style.bold(style.magenta("npx skillops-cli create skill <name>", c), c)} ${style.dim("\u2014 Scaffold a custom Skill", c)}`,
5141
+ renderSectionHeader("1. Library of High-End Skills & Subagents", c),
5142
+ formatCmdSection("library"),
5143
+ renderSectionHeader(
5144
+ "2. Framework for Building Custom Skills & Subagents",
5145
+ c
5146
+ ),
5147
+ formatCmdSection("framework"),
5148
+ renderSectionHeader(
5149
+ "3. Universal Runtime Sync, Token Economy & Diagnostics",
5150
+ c
5151
+ ),
5152
+ formatCmdSection("runtime"),
5153
+ bar,
5154
+ `${style.dim("\u2570\u2500", c)} ${style.bold("Global Flags:", c)} ${style.cyan("-p <dir>", c)} \xB7 ${style.cyan("-t <harnesses>", c)} \xB7 ${style.cyan("-f <table|json|md>", c)} \xB7 ${style.cyan("-h <cmd>", c)}`
5155
+ ].join("\n");
5156
+ }
5157
+ function renderCommandHelp(spec, version, colorEnabled) {
5158
+ const c = colorEnabled;
5159
+ const bar = style.dim("\u2502", c);
5160
+ const pillarBadge = spec.pillar === "library" ? formatBadge("info", "LIBRARY", c) : spec.pillar === "framework" ? formatBadge("pass", "FRAMEWORK", c) : formatBadge("subagent", "RUNTIME", c);
5161
+ const aliasText = spec.aliases.length > 0 ? ` (aliases: ${spec.aliases.map((a) => style.cyan(a, c)).join(", ")})` : "";
5162
+ const headerLines = [
5163
+ `${style.bold(style.cyan(`\u25C6 SkillOps v${version}`, c), c)} ${style.dim("\u2014", c)} ${style.bold(`skillops ${spec.name}`, c)}${aliasText} ${pillarBadge}`,
5164
+ `${bar} ${spec.summary}`,
5165
+ `${bar} ${style.bold("Usage:", c)} ${style.green(spec.usage, c)}`
5166
+ ].join("\n");
5167
+ const optionsSection = spec.options.length > 0 ? [
5168
+ renderSectionHeader("Options & Flags", c),
5169
+ renderTable(
5170
+ ["Flag", "Description"],
5171
+ spec.options.map((o) => [style.cyan(o.flags, c), o.description]),
5172
+ { colorEnabled: c, indent: 3 }
5173
+ )
5174
+ ].join("\n") : "";
5175
+ const examplesSection = renderNextSteps(spec.examples, c);
5176
+ return [headerLines, optionsSection, examplesSection].filter(Boolean).join("\n");
5177
+ }
5178
+
5179
+ // src/framework/index.ts
5180
+ function extractMarkdownHeadings(markdownBody) {
5181
+ const headings = [];
5182
+ for (const rawLine of markdownBody.split(/\r?\n/)) {
5183
+ const trimmed = rawLine.trim();
5184
+ if (trimmed.startsWith("## ")) {
5185
+ headings.push(trimmed.slice(3).trim());
5186
+ }
5187
+ }
5188
+ return headings;
5189
+ }
5190
+ function inspectCatalogItem(catalogDir, itemName) {
5191
+ const needle = itemName.trim().toLowerCase();
5192
+ for (const skillDir of discoverSkillDirectories(catalogDir)) {
5193
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
5194
+ if (parsed.manifest.name.toLowerCase() === needle) {
5195
+ const l1Tokens = parsed.manifest.description_tokens;
5196
+ const l2Lines = parsed.manifest.body_lines;
5197
+ const l2Tokens = parsed.manifest.body_tokens;
5198
+ const totalRaw = l1Tokens + l2Tokens;
5199
+ const savingsPct = totalRaw > 0 ? Number(((totalRaw - l1Tokens) / totalRaw * 100).toFixed(1)) : 0;
5200
+ let evalCasesCount = 0;
5201
+ if (parsed.evalsPath && fs9.existsSync(parsed.evalsPath)) {
5202
+ try {
5203
+ const rawEvals = JSON.parse(
5204
+ fs9.readFileSync(parsed.evalsPath, "utf-8")
5205
+ );
5206
+ evalCasesCount = Array.isArray(rawEvals.evals) ? rawEvals.evals.length : 0;
5207
+ } catch {
5208
+ evalCasesCount = 0;
5209
+ }
5210
+ }
5211
+ return {
5212
+ kind: "skill",
5213
+ name: parsed.manifest.name,
5214
+ description: parsed.manifest.description,
5215
+ path: parsed.skillMdPath.replace(/\\/g, "/"),
5216
+ l1Tokens,
5217
+ l2Lines,
5218
+ l2Tokens,
5219
+ savingsPct,
5220
+ scripts: parsed.manifest.entrypoints.scripts.map((s) => s.path),
5221
+ evalCasesCount,
5222
+ tools: [],
5223
+ maxOutputTokens: null,
5224
+ isolation: null,
5225
+ headings: extractMarkdownHeadings(parsed.markdownBody)
5226
+ };
5227
+ }
5228
+ }
5229
+ for (const subPath of discoverSubagentFiles(catalogDir)) {
5230
+ const parsed = parseSubagentFile(subPath);
5231
+ if (parsed.manifest.name.toLowerCase() === needle) {
5232
+ return {
5233
+ kind: "subagent",
5234
+ name: parsed.manifest.name,
5235
+ description: parsed.manifest.description,
5236
+ path: parsed.filePath.replace(/\\/g, "/"),
5237
+ l1Tokens: parsed.manifest.system_prompt_tokens,
5238
+ l2Lines: parsed.manifest.system_prompt_lines,
5239
+ l2Tokens: parsed.manifest.system_prompt_tokens,
5240
+ savingsPct: 0,
5241
+ scripts: [],
5242
+ evalCasesCount: 0,
5243
+ tools: parsed.manifest.tools,
5244
+ maxOutputTokens: parsed.manifest.max_output_tokens,
5245
+ isolation: parsed.manifest.context_isolation ? "context-isolated" : "shared",
5246
+ headings: extractMarkdownHeadings(parsed.systemPromptBody)
5247
+ };
5248
+ }
5249
+ }
5250
+ throw new Error(
5251
+ `Skill or Subagent '${itemName}' not found in catalog '${catalogDir.replace(/\\/g, "/")}'. Run 'npx skillops-cli list' to see all available items.`
5252
+ );
5253
+ }
5254
+ function formatCatalogItemInfo(info, format = "table", colorEnabled) {
5255
+ if (format === "json") {
5256
+ return `${JSON.stringify(info, null, 2)}
5257
+ `;
5258
+ }
5259
+ if (format === "markdown") {
5260
+ const lines = [
5261
+ `## ${info.kind === "skill" ? "Skill" : "Subagent"}: \`${info.name}\``,
5262
+ "",
5263
+ `- **Type:** ${info.kind.toUpperCase()}`,
5264
+ `- **Description:** ${info.description}`,
5265
+ info.kind === "skill" ? `- **Token Economy:** L1=${info.l1Tokens}t | L2=${info.l2Lines} lines (${info.l2Tokens}t) | Savings=${info.savingsPct}%` : `- **Isolation Contract:** Prompt=${info.l1Tokens}t | Max Output <= ${info.maxOutputTokens}t | Isolation=${info.isolation} | Tools=${info.tools.join(", ")}`,
5266
+ ...info.scripts.length > 0 ? [`- **Executable L3 Scripts:** \`${info.scripts.join("`, `")}\``] : [],
5267
+ ...info.headings.length > 0 ? [`- **Workflow Sections:** ${info.headings.join(" \u2022 ")}`] : [],
5268
+ "",
5269
+ "### Quick Install",
5270
+ "```bash",
5271
+ `npx skillops-cli add ${info.name}`,
5272
+ "```",
5273
+ ""
5274
+ ];
5275
+ return lines.join("\n");
5276
+ }
5277
+ const c = colorEnabled;
5278
+ const bar = style.dim("\u2502", c);
5279
+ const badge = info.kind === "skill" ? formatBadge("skill", "HYBRID EXECUTABLE SKILL", c) : formatBadge("subagent", "CONTEXT-ISOLATED SUBAGENT", c);
5280
+ const shortDesc = info.description.length > 68 ? `${info.description.slice(0, 65)}...` : info.description;
5281
+ const cardLines = [
5282
+ `${style.bold(style.cyan(`\u25C6 SkillOps Library Inspector \u2014 ${info.name}`, c), c)}`,
5283
+ `${bar} ${badge}`,
5284
+ `${bar} ${shortDesc}`,
5285
+ bar
5286
+ ];
5287
+ if (info.kind === "skill") {
5288
+ cardLines.push(
5289
+ `${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Token Economy (INV-SKILL-01..03)", c), c)}`,
5290
+ `${bar} \u2022 L1 Routing : ${style.green(`${info.l1Tokens} tokens`, c)} (<= 160 budget)`,
5291
+ `${bar} \u2022 L2 Playbook : ${info.l2Lines} lines (${info.l2Tokens}t, <= 250 lines)`,
5292
+ `${bar} \u2022 Token Savings : ${style.bold(style.green(`${info.savingsPct}%`, c), c)} Startup Tax saved`,
5293
+ `${bar} \u2022 L3 Scripts (${info.scripts.length}) : ${info.scripts.length > 0 ? style.cyan(info.scripts.join(", "), c) : "none"}`,
5294
+ `${bar} \u2022 TDD Evals : ${info.evalCasesCount} deterministic cases`
5295
+ );
5296
+ } else {
5297
+ cardLines.push(
5298
+ `${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Context Isolation Contract (INV-SKILL-04)", c), c)}`,
5299
+ `${bar} \u2022 System Prompt : ${style.green(`${info.l1Tokens} tokens`, c)}`,
5300
+ `${bar} \u2022 Max Output Cap : <= ${style.bold(`${info.maxOutputTokens} tokens`, c)}`,
5301
+ `${bar} \u2022 Isolation Mode : ${style.cyan(info.isolation ?? "context-isolated", c)}`,
5302
+ `${bar} \u2022 Allowed Tools : ${info.tools.join(", ")}`
5303
+ );
5304
+ }
5305
+ if (info.headings.length > 0) {
5306
+ cardLines.push(
5307
+ bar,
5308
+ `${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Playbook Sections", c), c)}`,
5309
+ ...info.headings.slice(0, 5).map((h) => `${bar} \u2022 ${h}`)
5310
+ );
5311
+ }
5312
+ cardLines.push(
5313
+ bar,
5314
+ `${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("How to Invoke in Your AI Agent", c), c)}`,
5315
+ `${bar} \u2022 ${style.bold("Install:", c)} ${style.green(`npx skillops-cli add ${info.name}`, c)}`,
5316
+ info.kind === "skill" ? `${bar} \u2022 ${style.bold("IDE Chat:", c)} Reference ${style.cyan(`@${info.name}`, c)} or ${style.cyan(`/${info.name}`, c)}` : `${bar} \u2022 ${style.bold("Delegate:", c)} Ask main agent to run ${style.magenta(`@${info.name}`, c)}`
5317
+ );
5318
+ const nextSteps = renderNextSteps(
5319
+ [
5320
+ {
5321
+ command: `npx skillops-cli add ${info.name}`,
5322
+ description: "Install into AI agents"
5323
+ },
5324
+ ...info.kind === "skill" ? [
5325
+ {
5326
+ command: `npx skillops-cli eval --skill ${info.name}`,
5327
+ description: "Run TDD benchmark"
5328
+ }
5329
+ ] : [],
5330
+ {
5331
+ command: "npx skillops-cli list",
5332
+ description: "Browse 10 Skills & 9 Subagents"
5333
+ }
5334
+ ],
5335
+ c
5336
+ );
5337
+ return `${cardLines.join("\n")}
5338
+ ${nextSteps}`;
5339
+ }
5340
+ function searchCatalog(catalogDir, query) {
5341
+ const rawTerms = query.trim().toLowerCase().split(/[^a-z0-9_-]+/).filter((t) => t.length > 0);
5342
+ const matches = [];
5343
+ for (const skillDir of discoverSkillDirectories(catalogDir)) {
5344
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
5345
+ const haystackName = parsed.manifest.name.toLowerCase();
5346
+ const haystackDesc = parsed.manifest.description.toLowerCase();
5347
+ const haystackBody = parsed.markdownBody.toLowerCase();
5348
+ let score = rawTerms.length === 0 ? 1 : 0;
5349
+ for (const term of rawTerms) {
5350
+ if (haystackName.includes(term)) {
5351
+ score += 5;
5352
+ }
5353
+ if (haystackDesc.includes(term)) {
5354
+ score += 3;
5355
+ }
5356
+ if (haystackBody.includes(term)) {
5357
+ score += 1;
5358
+ }
5359
+ }
5360
+ if (score > 0) {
5361
+ matches.push({
5362
+ kind: "skill",
5363
+ name: parsed.manifest.name,
5364
+ score,
5365
+ tokens: parsed.manifest.description_tokens,
5366
+ summary: parsed.manifest.description.length > 30 ? `${parsed.manifest.description.slice(0, 27)}...` : parsed.manifest.description
5367
+ });
5368
+ }
5369
+ }
5370
+ for (const subPath of discoverSubagentFiles(catalogDir)) {
5371
+ const parsed = parseSubagentFile(subPath);
5372
+ const haystackName = parsed.manifest.name.toLowerCase();
5373
+ const haystackDesc = parsed.manifest.description.toLowerCase();
5374
+ const haystackTools = parsed.manifest.tools.join(" ").toLowerCase();
5375
+ let score = rawTerms.length === 0 ? 1 : 0;
5376
+ for (const term of rawTerms) {
5377
+ if (haystackName.includes(term)) {
5378
+ score += 5;
5379
+ }
5380
+ if (haystackDesc.includes(term)) {
5381
+ score += 3;
5382
+ }
5383
+ if (haystackTools.includes(term)) {
5384
+ score += 2;
5385
+ }
5386
+ }
5387
+ if (score > 0) {
5388
+ matches.push({
5389
+ kind: "subagent",
5390
+ name: parsed.manifest.name,
5391
+ score,
5392
+ tokens: parsed.manifest.system_prompt_tokens,
5393
+ summary: parsed.manifest.description.length > 30 ? `${parsed.manifest.description.slice(0, 27)}...` : parsed.manifest.description
5394
+ });
5395
+ }
5396
+ }
5397
+ matches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
5398
+ return {
5399
+ query,
5400
+ total_matches: matches.length,
5401
+ matches
5402
+ };
5403
+ }
5404
+ function formatCatalogSearchReport(report, format = "table", colorEnabled) {
5405
+ if (format === "json") {
5406
+ return `${JSON.stringify(report, null, 2)}
5407
+ `;
5408
+ }
5409
+ if (format === "markdown") {
5410
+ const lines = [
5411
+ `## SkillOps Library Search \u2014 \`${report.query || "*"}\` (${report.total_matches} matches)`,
5412
+ "",
5413
+ "| Type | Name | Tokens | Summary |",
5414
+ "| :--- | :--- | ---: | :--- |",
5415
+ ...report.matches.map(
5416
+ (m) => `| ${m.kind.toUpperCase()} | \`${m.name}\` | ${m.tokens}t | ${m.summary} |`
5417
+ ),
5418
+ ""
5419
+ ];
5420
+ return lines.join("\n");
5421
+ }
5422
+ const c = colorEnabled;
5423
+ const bar = style.dim("\u2502", c);
5424
+ const header = `${style.bold(style.cyan("\u25C6 SkillOps Library Search", c), c)} ${style.dim("\u2014", c)} Query: ${style.bold(`"${report.query || "*"}"`, c)} (${report.total_matches} matches)`;
5425
+ if (report.matches.length === 0) {
5426
+ return `${header}
5427
+ ${bar} ${formatBadge("warn", "No matching Skills or Subagents found.", c)}
5428
+ ${renderNextSteps([{ command: "npx skillops-cli list", description: "View all 10 Skills & 9 Subagents" }], c)}`;
5429
+ }
5430
+ const table = renderTable(
5431
+ ["Type", "Name", "Tokens", "Summary"],
5432
+ report.matches.map((m) => [
5433
+ m.kind === "skill" ? formatBadge("skill", "SKILL", c) : formatBadge("subagent", "SUBAGENT", c),
5434
+ style.bold(m.name, c),
5435
+ `${m.tokens}t`,
5436
+ m.summary
5437
+ ]),
5438
+ { align: ["left", "left", "right", "left"], colorEnabled: c, indent: 3 }
5439
+ );
5440
+ const topItem = report.matches[0]?.name ?? "bug-reproducer-tdd";
5441
+ const nextSteps = renderNextSteps(
5442
+ [
5443
+ {
5444
+ command: `npx skillops-cli info ${topItem}`,
5445
+ description: "Inspect triggers & usage"
5446
+ },
5447
+ {
5448
+ command: `npx skillops-cli add ${topItem}`,
5449
+ description: "Install into AI agents"
5450
+ }
5451
+ ],
5452
+ c
5453
+ );
5454
+ return `${header}
5455
+ ${bar}
5456
+ ${table}
5457
+ ${nextSteps}`;
5458
+ }
5459
+ function buildValidSkillDescription(name, humanTitle, customDesc) {
5460
+ const domainPhrase = name.replace(/-/g, " ");
5461
+ const defaultDescription = `Use when implementing, auditing, or troubleshooting ${domainPhrase} workflows across production codebases to enforce deterministic ${humanTitle} invariants, structural contracts, boundary-value assertions, and automated Level 3 verification scripts before applying surgical changes. Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes.`;
5462
+ if (!customDesc?.trim()) {
5463
+ return defaultDescription;
5464
+ }
5465
+ let candidate = customDesc.trim().replace(/\s+/g, " ");
5466
+ if (!candidate.startsWith("Use when")) {
5467
+ candidate = `Use when working on ${domainPhrase}: ${candidate}`;
5468
+ }
5469
+ const words = countWords(candidate);
5470
+ if (words < 40) {
5471
+ candidate = `${candidate} Enforces deterministic ${humanTitle} invariants, automated Level 3 script verification, architectural boundary checks, and zero-regression guardrails across production repositories. Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes.`;
5472
+ }
5473
+ return candidate;
5474
+ }
5475
+ function scaffoldFrameworkItem(catalogDir, kind, rawName, options = {}) {
5476
+ const name = rawName.trim().toLowerCase();
5477
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
5478
+ throw new Error(
5479
+ `Invalid ${kind} name '${rawName}': must be kebab-case ('^[a-z0-9]+(-[a-z0-9]+)*$').`
5480
+ );
5481
+ }
5482
+ const createdFiles = [];
5483
+ if (kind === "skill") {
5484
+ const skillDir = path9.join(catalogDir, "skills", name);
5485
+ const skillMdPath = path9.join(skillDir, "SKILL.md");
5486
+ const snakeName = name.replace(/-/g, "_");
5487
+ const scriptRelPath = `scripts/verify_${snakeName}.py`;
5488
+ const scriptAbsPath = path9.join(
5489
+ skillDir,
5490
+ "scripts",
5491
+ `verify_${snakeName}.py`
5492
+ );
5493
+ const evalsAbsPath = path9.join(skillDir, "evals", "evals.json");
5494
+ if (fs9.existsSync(skillMdPath) && !options.force) {
5495
+ throw new Error(
5496
+ `Skill '${name}' already exists at '${skillMdPath.replace(/\\/g, "/")}'. Pass --force to overwrite.`
5497
+ );
5498
+ }
5499
+ const humanTitle2 = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5500
+ const description = buildValidSkillDescription(
5501
+ name,
5502
+ humanTitle2,
5503
+ options.description
5504
+ );
5505
+ const skillMdContent = [
5506
+ "---",
5507
+ `name: "${name}"`,
5508
+ `description: "${description.replace(/"/g, '\\"')}"`,
5509
+ 'version: "1.0.0"',
5510
+ 'track: "arch-quality"',
5511
+ "tags:",
5512
+ ` - "${name}"`,
5513
+ ' - "verification"',
5514
+ ' - "deterministic-validation"',
5515
+ "routing_signals:",
5516
+ ' languages: ["typescript", "python"]',
5517
+ ' files_any: ["package.json", "pyproject.toml"]',
5518
+ " dependencies_any: []",
5519
+ " negative_triggers:",
5520
+ ' - "Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes."',
5521
+ "entrypoints:",
5522
+ " scripts:",
5523
+ ` - path: "${scriptRelPath}"`,
5524
+ ` description: "Executes deterministic structural verification and invariant checks for ${humanTitle2}."`,
5525
+ "---",
5526
+ "",
5527
+ `# ${humanTitle2} (\`${name}\`)`,
5528
+ "",
5529
+ "## When to Use",
5530
+ `- Activate this skill when working on **${humanTitle2}** tasks requiring deterministic validation.`,
5531
+ "- Delegate exploratory repository scans to a Context-Isolated Subagent before modifying production files.",
5532
+ "",
5533
+ "## Step-by-Step Playbook",
5534
+ "1. **Baseline Discovery:** Inspect target modules and identify architectural constraints.",
5535
+ "2. **Deterministic Verification (Level 3 Script):** Run the bundled verification script before and after changes:",
5536
+ " ```bash",
5537
+ ` python ${scriptRelPath} --target .`,
5538
+ " ```",
5539
+ "3. **Minimal Surgical Implementation:** Apply focused changes and verify zero regressions.",
5540
+ "",
5541
+ "## Anti-Patterns to Avoid",
5542
+ "- Never skip Level 3 deterministic verification before declaring completion.",
5543
+ "- Avoid loading large raw logs into the primary context window.",
5544
+ ""
5545
+ ].join("\n");
5546
+ const scriptContent = [
5547
+ '"""Deterministic Level 3 verification script generated by SkillOps Framework."""',
5548
+ "",
5549
+ "from __future__ import annotations",
5550
+ "",
5551
+ "import argparse",
5552
+ "import json",
5553
+ "import sys",
5554
+ "",
5555
+ "",
5556
+ "def main() -> int:",
5557
+ ` parser = argparse.ArgumentParser(description="Verify ${humanTitle2} invariants.")`,
5558
+ ' parser.add_argument("--target", default=".", help="Target directory or file path to verify.")',
5559
+ " args = parser.parse_args()",
5560
+ ` payload = {"skill": "${name}", "target": args.target, "status": "pass"}`,
5561
+ " print(json.dumps(payload, indent=2))",
5562
+ " return 0",
5563
+ "",
5564
+ "",
5565
+ 'if __name__ == "__main__":',
5566
+ " sys.exit(main())",
5567
+ ""
5568
+ ].join("\n");
5569
+ const evalsContent = JSON.stringify(
5570
+ {
5571
+ skill_name: name,
5572
+ schema_version: "1.0",
5573
+ evals: [
5574
+ {
5575
+ id: `eval-01-verify-${name}`,
5576
+ name: `Verify ${humanTitle2} deterministic workflow and Level 3 script execution`,
5577
+ prompt: `Audit and enforce ${humanTitle2} invariants using the deterministic verification script.`,
5578
+ fixtures: [],
5579
+ assertions: [
5580
+ {
5581
+ id: "assert-ran-verification-script",
5582
+ type: "tool_call_contains",
5583
+ target: `verify_${snakeName}.py`,
5584
+ description: `Agent executed verify_${snakeName}.py before finalizing changes.`
5585
+ }
5586
+ ]
5587
+ }
5588
+ ]
5589
+ },
5590
+ null,
5591
+ 2
5592
+ );
5593
+ fs9.mkdirSync(path9.dirname(scriptAbsPath), { recursive: true });
5594
+ fs9.mkdirSync(path9.dirname(evalsAbsPath), { recursive: true });
5595
+ fs9.writeFileSync(skillMdPath, skillMdContent, "utf-8");
5596
+ fs9.writeFileSync(scriptAbsPath, scriptContent, "utf-8");
5597
+ fs9.writeFileSync(evalsAbsPath, `${evalsContent}
5598
+ `, "utf-8");
5599
+ createdFiles.push(
5600
+ skillMdPath.replace(/\\/g, "/"),
5601
+ scriptAbsPath.replace(/\\/g, "/"),
5602
+ evalsAbsPath.replace(/\\/g, "/")
5603
+ );
5604
+ return {
5605
+ kind: "skill",
5606
+ name,
5607
+ target_dir: skillDir.replace(/\\/g, "/"),
5608
+ created_files: createdFiles
5609
+ };
5610
+ }
5611
+ const subagentDir = path9.join(catalogDir, "subagents", name);
5612
+ const subagentMdPath = path9.join(subagentDir, "SUBAGENT.md");
5613
+ if (fs9.existsSync(subagentMdPath) && !options.force) {
5614
+ throw new Error(
5615
+ `Subagent '${name}' already exists at '${subagentMdPath.replace(/\\/g, "/")}'. Pass --force to overwrite.`
5616
+ );
5617
+ }
5618
+ const humanTitle = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5619
+ const rawSubDesc = options.description?.trim();
5620
+ const defaultSubDescription = rawSubDesc && countWords(rawSubDesc) >= 10 ? rawSubDesc : `Context-isolated specialist subagent for ${humanTitle}. Use when delegating high-volume repository analysis, structural inspection, or verification without polluting the main agent context window.`;
5621
+ const subagentMdContent = [
5622
+ "---",
5623
+ `name: "${name}"`,
5624
+ `description: "${defaultSubDescription.replace(/"/g, '\\"')}"`,
5625
+ "tools:",
5626
+ ' - "Read"',
5627
+ ' - "Grep"',
5628
+ ' - "Glob"',
5629
+ "user_invocable: true",
5630
+ "context_isolation: true",
5631
+ "max_output_tokens: 350",
5632
+ 'model_tier: "fast"',
5633
+ "tags:",
5634
+ ` - "${name}"`,
5635
+ ' - "context-isolated"',
5636
+ "---",
5637
+ "",
5638
+ `# ${humanTitle} Subagent`,
5639
+ "",
5640
+ "## Mission & Context Isolation Contract (`INV-SKILL-04`)",
5641
+ `You are the **${humanTitle}** context-isolated subagent. Execute deep inspection in your isolated context window and return a concise structured summary (\`<= 350\` tokens) to the parent orchestrator.`,
5642
+ "",
5643
+ "## Output Format",
5644
+ "1. **Findings Summary:** High-signal bullet points with exact file paths and line numbers.",
5645
+ "2. **Recommended Actions:** Concrete next steps for the parent agent.",
5646
+ ""
5647
+ ].join("\n");
5648
+ fs9.mkdirSync(subagentDir, { recursive: true });
5649
+ fs9.writeFileSync(subagentMdPath, subagentMdContent, "utf-8");
5650
+ createdFiles.push(subagentMdPath.replace(/\\/g, "/"));
5651
+ return {
5652
+ kind: "subagent",
5653
+ name,
5654
+ target_dir: subagentDir.replace(/\\/g, "/"),
5655
+ created_files: createdFiles
5656
+ };
5657
+ }
5658
+ function formatScaffoldReport(result, format = "table", colorEnabled) {
5659
+ if (format === "json") {
5660
+ return `${JSON.stringify(result, null, 2)}
5661
+ `;
5662
+ }
5663
+ if (format === "markdown") {
5664
+ return [
5665
+ `## SkillOps Framework Scaffolder \u2014 Created ${result.kind.toUpperCase()} \`${result.name}\``,
5666
+ "",
5667
+ `- **Target Directory:** \`${result.target_dir}\``,
5668
+ ...result.created_files.map((f) => `- **Created:** \`${f}\``),
5669
+ ""
5670
+ ].join("\n");
5671
+ }
5672
+ const c = colorEnabled;
5673
+ const bar = style.dim("\u2502", c);
5674
+ const treeHeader = [
5675
+ `${style.bold(style.cyan("\u25C6 SkillOps Framework Scaffolder", c), c)} ${style.dim("\u2014", c)} ${formatBadge("pass", "CREATED", c)} ${style.bold(result.kind.toUpperCase(), c)} ${style.bold(style.cyan(result.name, c), c)}`,
5676
+ bar,
5677
+ `${style.dim("\u251C\u2500", c)} ${style.bold("Generated Files:", c)}`,
5678
+ ...result.created_files.map((f) => `${bar} \u2022 ${style.green(f, c)}`)
5679
+ ].join("\n");
5680
+ const nextSteps = renderNextSteps(
5681
+ [
5682
+ {
5683
+ command: `npx skillops-cli info ${result.name}`,
5684
+ description: `Inspect new ${result.kind} metadata & budget`
5685
+ },
5686
+ {
5687
+ command: "npx skillops-cli audit --require-layout",
5688
+ description: "Verify INV-SKILL-01..04 & collisions"
5689
+ },
5690
+ {
5691
+ command: `npx skillops-cli add ${result.name}`,
5692
+ description: `Install '${result.name}' into AI agents`
5693
+ }
5694
+ ],
5695
+ c
5696
+ );
5697
+ return `${treeHeader}
5698
+ ${nextSteps}`;
5699
+ }
5700
+ function runQuickstart(repoPath, catalogDir, options = {}) {
5701
+ const absRepo = path9.resolve(repoPath);
5702
+ const dryRun = options.dryRun ?? false;
5703
+ const force = options.force ?? false;
5704
+ const skipMcp = options.skipMcp ?? false;
5705
+ const profile = fingerprintRepository(absRepo);
5706
+ const existingCfg = loadSkillOpsConfig(absRepo);
5707
+ let activeTargets;
5708
+ if (options.targetsArg) {
5709
+ activeTargets = resolveRuntimeTargets(options.targetsArg, absRepo);
5710
+ } else if (existingCfg.config?.targets && existingCfg.config.targets.length > 0) {
5711
+ activeTargets = [...existingCfg.config.targets];
5712
+ } else if (profile.detected_harnesses.length > 0) {
5713
+ activeTargets = [...profile.detected_harnesses];
5714
+ } else {
5715
+ activeTargets = [...CORE_RUNTIME_TARGETS];
5716
+ }
5717
+ const maxSkills = options.maxSkills ?? existingCfg.config?.token_economy.max_active_skills ?? 12;
5718
+ const maxL1Tokens = existingCfg.config?.token_economy.max_l1_tokens ?? 1500;
5719
+ const maxSimilarity = existingCfg.config?.token_economy.max_similarity ?? 0.78;
5720
+ const configPath = path9.join(absRepo, ".skillops.yml");
5721
+ if (!dryRun) {
5722
+ const configPayload = SkillOpsConfigSchema.parse({
5723
+ version: "1.0",
5724
+ catalog_dir: path9.relative(absRepo, catalogDir).replace(/\\/g, "/") || "catalog",
5725
+ token_economy: {
5726
+ startup_tax_budget: existingCfg.config?.token_economy.startup_tax_budget ?? 2500,
5727
+ max_active_skills: maxSkills,
5728
+ max_l1_tokens: maxL1Tokens,
5729
+ max_similarity: maxSimilarity
5730
+ },
5731
+ detected_stack: {
5732
+ languages: profile.languages,
5733
+ frameworks: profile.frameworks
5734
+ },
5735
+ targets: activeTargets
5736
+ });
5737
+ fs9.mkdirSync(absRepo, { recursive: true });
4137
5738
  fs9.writeFileSync(configPath, YAML5.stringify(configPayload), "utf-8");
5739
+ }
5740
+ const bundle = routeRepository(absRepo, catalogDir, {
5741
+ maxSkills,
5742
+ maxL1Tokens,
5743
+ maxSimilarity
5744
+ });
5745
+ const syncReport = syncRepository(absRepo, catalogDir, {
5746
+ targets: activeTargets,
5747
+ dryRun,
5748
+ force,
5749
+ maxSkills,
5750
+ maxL1Tokens,
5751
+ maxSimilarity
5752
+ });
5753
+ const mcpReport = skipMcp ? null : setupMcpForHarnesses(absRepo, {
5754
+ targets: activeTargets,
5755
+ dryRun
5756
+ });
5757
+ return {
5758
+ repo_path: absRepo.replace(/\\/g, "/"),
5759
+ config_path: configPath.replace(/\\/g, "/"),
5760
+ dry_run: dryRun,
5761
+ detected_languages: profile.languages,
5762
+ detected_frameworks: profile.frameworks,
5763
+ detected_harnesses: profile.detected_harnesses,
5764
+ active_targets: activeTargets,
5765
+ selected_skills: bundle.selected_skills.map((s) => s.name),
5766
+ selected_subagents: bundle.recommended_subagents,
5767
+ total_l1_tokens: bundle.total_l1_tokens,
5768
+ max_l1_budget: maxL1Tokens,
5769
+ synced_files_count: syncReport.artifacts.filter(
5770
+ (a) => a.action === "created" || a.action === "updated" || a.action === "unchanged" || a.action === "planned"
5771
+ ).length,
5772
+ mcp_configured_count: mcpReport ? mcpReport.configured_count : 0,
5773
+ passed: bundle.passed_invariants && syncReport.passed
5774
+ };
5775
+ }
5776
+ function formatQuickstartReport(report, format = "table", colorEnabled) {
5777
+ if (format === "json") {
5778
+ return `${JSON.stringify(report, null, 2)}
5779
+ `;
5780
+ }
5781
+ if (format === "markdown") {
5782
+ return [
5783
+ `## SkillOps Quickstart Report (${report.dry_run ? "DRY-RUN" : "INSTALLED"})`,
5784
+ "",
5785
+ `- **Repository:** \`${report.repo_path}\``,
5786
+ `- **Stack Detected:** ${[...report.detected_languages, ...report.detected_frameworks].join(", ") || "generic"}`,
5787
+ `- **Target AI Agents (${report.active_targets.length}):** \`${report.active_targets.join("`, `")}\``,
5788
+ `- **Routed Skills (${report.selected_skills.length}):** \`${report.selected_skills.join("`, `")}\``,
5789
+ `- **Routed Subagents (${report.selected_subagents.length}):** \`${report.selected_subagents.join("`, `")}\``,
5790
+ `- **Token Budget:** \`${report.total_l1_tokens} / ${report.max_l1_budget}\` L1 tokens`,
5791
+ `- **Synced Artifacts:** ${report.synced_files_count} files | **MCP Configs:** ${report.mcp_configured_count}`,
5792
+ ""
5793
+ ].join("\n");
5794
+ }
5795
+ const c = colorEnabled;
5796
+ const bar = style.dim("\u2502", c);
5797
+ const stackLabel = [...report.detected_languages, ...report.detected_frameworks].join(", ") || "general";
5798
+ const targetsLabel = report.active_targets.length > 4 ? `${report.active_targets.slice(0, 4).join(", ")} (+${report.active_targets.length - 4})` : report.active_targets.join(", ");
5799
+ const banner = renderHeaderBanner(
5800
+ `Quickstart Setup ${report.dry_run ? "[DRY-RUN]" : "[READY]"}`,
5801
+ { colorEnabled: c }
5802
+ );
5803
+ const stepsBlock = [
5804
+ renderStep(
5805
+ 1,
5806
+ 4,
5807
+ "Detect Stack & Agents",
5808
+ `${style.cyan(stackLabel, c)} \xB7 ${style.bold(targetsLabel, c)}`,
5809
+ "done",
5810
+ c
5811
+ ),
5812
+ renderStep(
5813
+ 2,
5814
+ 4,
5815
+ "Route Library Bundle",
5816
+ `${style.green(`${report.selected_skills.length} Skills`, c)} + ${style.magenta(`${report.selected_subagents.length} Subagents`, c)}`,
5817
+ "done",
5818
+ c
5819
+ ),
5820
+ renderStep(
5821
+ 3,
5822
+ 4,
5823
+ "Transpile & Sync",
5824
+ `${report.synced_files_count} files (${report.dry_run ? "dry-run" : ".skillops.yml + lockfile"})`,
5825
+ "done",
5826
+ c
5827
+ ),
5828
+ renderStep(
5829
+ 4,
5830
+ 4,
5831
+ "Register MCP Server",
5832
+ `${report.mcp_configured_count} MCP agent config(s) wired`,
5833
+ "done",
5834
+ c
5835
+ )
5836
+ ].join("\n");
5837
+ const tokenBar = `${bar} ${renderTokenBar(
5838
+ report.total_l1_tokens,
5839
+ report.max_l1_budget,
5840
+ {
5841
+ label: "L1 Routing Budget:",
5842
+ colorEnabled: c
5843
+ }
5844
+ )}`;
5845
+ const bundleTree = [
5846
+ bar,
5847
+ `${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan(`Active Bundle (${report.selected_skills.length} Skills \xB7 ${report.selected_subagents.length} Subagents)`, c), c)}`,
5848
+ `${bar} \u2022 ${style.bold("Skills:", c)} ${report.selected_skills.slice(0, 2).map((s) => style.cyan(s, c)).join(
5849
+ ", "
5850
+ )}${report.selected_skills.length > 2 ? ` (+${report.selected_skills.length - 2} more)` : ""}`,
5851
+ `${bar} \u2022 ${style.bold("Subagents:", c)} ${report.selected_subagents.slice(0, 2).map((a) => style.magenta(a, c)).join(
5852
+ ", "
5853
+ )}${report.selected_subagents.length > 2 ? ` (+${report.selected_subagents.length - 2} more)` : ""}`
5854
+ ].join("\n");
5855
+ const usageGuide = renderAiUsageGuide(
5856
+ report.active_targets,
5857
+ report.selected_skills,
5858
+ report.selected_subagents,
5859
+ c
5860
+ );
5861
+ const nextSteps = renderNextSteps(
5862
+ [
5863
+ {
5864
+ command: `npx skillops-cli info ${report.selected_skills[0] ?? "bug-reproducer-tdd"}`,
5865
+ description: "Inspect Skill triggers & L3 scripts"
5866
+ },
5867
+ {
5868
+ command: "npx skillops-cli list",
5869
+ description: "Browse all 10 Skills & 9 Subagents"
5870
+ },
5871
+ {
5872
+ command: "npx skillops-cli create skill my-skill",
5873
+ description: "Create a custom Skill"
5874
+ },
5875
+ {
5876
+ command: "npx skillops-cli doctor",
5877
+ description: "Verify lockfile & Startup Token Tax"
5878
+ }
5879
+ ],
5880
+ c
5881
+ );
5882
+ return [
5883
+ banner,
5884
+ bar,
5885
+ stepsBlock,
5886
+ tokenBar,
5887
+ bundleTree,
5888
+ usageGuide,
5889
+ nextSteps
5890
+ ].join("\n");
5891
+ }
5892
+
5893
+ // src/cli.ts
5894
+ var PACKAGE_ROOT = path10.resolve(
5895
+ path10.dirname(fileURLToPath(import.meta.url)),
5896
+ ".."
5897
+ );
5898
+ function getFlagValue(args, ...flagNames) {
5899
+ for (const flag of flagNames) {
5900
+ const idx = args.indexOf(flag);
5901
+ const val = idx !== -1 ? args[idx + 1] : void 0;
5902
+ if (val && !val.startsWith("-")) {
5903
+ return val;
5904
+ }
5905
+ }
5906
+ return void 0;
5907
+ }
5908
+ function getPositionalArgs(args) {
5909
+ const valueTakingFlags = /* @__PURE__ */ new Set([
5910
+ "--repo",
5911
+ "--project",
5912
+ "-p",
5913
+ "--targets",
5914
+ "--harness",
5915
+ "--harnesses",
5916
+ "--target",
5917
+ "-t",
5918
+ "--catalog",
5919
+ "--format",
5920
+ "-f",
5921
+ "--top",
5922
+ "--max-skills",
5923
+ "--max-l1-tokens",
5924
+ "--max-similarity",
5925
+ "--budget",
5926
+ "--task",
5927
+ "--skill",
5928
+ "--out",
5929
+ "--description",
5930
+ "-d"
5931
+ ]);
5932
+ const positionals = [];
5933
+ for (let i = 1; i < args.length; i++) {
5934
+ const arg = args[i];
5935
+ if (!arg) {
5936
+ continue;
5937
+ }
5938
+ if (arg.startsWith("-")) {
5939
+ if (valueTakingFlags.has(arg)) {
5940
+ i += 1;
5941
+ }
5942
+ continue;
5943
+ }
5944
+ positionals.push(arg);
5945
+ }
5946
+ return positionals;
5947
+ }
5948
+ function resolveTargetsArg(args) {
5949
+ const explicit = getFlagValue(
5950
+ args,
5951
+ "--targets",
5952
+ "--harness",
5953
+ "--harnesses",
5954
+ "--target",
5955
+ "-t"
5956
+ );
5957
+ if (explicit) {
5958
+ return explicit;
5959
+ }
5960
+ if (args.includes("--auto")) {
5961
+ return "auto";
5962
+ }
5963
+ return void 0;
5964
+ }
5965
+ function resolveCatalogDir(args, repoPath, loadedConfig) {
5966
+ const catalogFlag = getFlagValue(args, "--catalog");
5967
+ if (catalogFlag) {
5968
+ return path10.resolve(catalogFlag);
5969
+ }
5970
+ const baseRepo = repoPath ? path10.resolve(repoPath) : process.cwd();
5971
+ if (loadedConfig?.catalog_dir) {
5972
+ const cfgCatalog = path10.resolve(baseRepo, loadedConfig.catalog_dir);
5973
+ if (fs10.existsSync(cfgCatalog)) {
5974
+ return cfgCatalog;
5975
+ }
5976
+ }
5977
+ if (repoPath) {
5978
+ const repoCatalog = path10.join(path10.resolve(repoPath), "catalog");
5979
+ if (fs10.existsSync(repoCatalog)) {
5980
+ return repoCatalog;
5981
+ }
5982
+ }
5983
+ const cwdCatalog = path10.resolve(process.cwd(), "catalog");
5984
+ if (fs10.existsSync(cwdCatalog)) {
5985
+ return cwdCatalog;
5986
+ }
5987
+ return path10.join(PACKAGE_ROOT, "catalog");
5988
+ }
5989
+ function runCli(argv) {
5990
+ const args = argv.slice(2);
5991
+ if (args.includes("--version") || args.includes("-V") || args[0] === "version") {
5992
+ process.stdout.write(`skillops ${SKILLOPS_VERSION}
5993
+ `);
5994
+ return ExitCode.SUCCESS;
5995
+ }
5996
+ if (args[0] === "help") {
5997
+ const targetCmd = args[1];
5998
+ if (targetCmd && !targetCmd.startsWith("-")) {
5999
+ const spec = findCommandHelpSpec(targetCmd);
6000
+ if (spec) {
6001
+ process.stdout.write(`${renderCommandHelp(spec, SKILLOPS_VERSION)}
6002
+ `);
6003
+ return ExitCode.SUCCESS;
6004
+ }
6005
+ }
6006
+ process.stdout.write(`${renderGlobalHelp(SKILLOPS_VERSION)}
6007
+ `);
6008
+ return ExitCode.SUCCESS;
6009
+ }
6010
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
6011
+ process.stdout.write(`${renderGlobalHelp(SKILLOPS_VERSION)}
6012
+ `);
6013
+ return ExitCode.SUCCESS;
6014
+ }
6015
+ if (args.includes("--help") || args.includes("-h")) {
6016
+ const firstCmd = args[0];
6017
+ if (firstCmd) {
6018
+ const spec = findCommandHelpSpec(firstCmd);
6019
+ if (spec) {
6020
+ process.stdout.write(`${renderCommandHelp(spec, SKILLOPS_VERSION)}
6021
+ `);
6022
+ return ExitCode.SUCCESS;
6023
+ }
6024
+ }
6025
+ process.stdout.write(`${renderGlobalHelp(SKILLOPS_VERSION)}
6026
+ `);
6027
+ return ExitCode.SUCCESS;
6028
+ }
6029
+ const command = args[0] ?? "";
6030
+ if (command === "quickstart" || command === "setup") {
6031
+ const repoPath = path10.resolve(
6032
+ getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
6033
+ );
6034
+ const cfgRes = loadSkillOpsConfig(repoPath);
6035
+ if (cfgRes.error) {
6036
+ process.stderr.write(
6037
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6038
+ `
6039
+ );
6040
+ return ExitCode.BUDGET_OR_CONTRACT_ERROR;
6041
+ }
6042
+ const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
6043
+ const targetsArg = resolveTargetsArg(args);
6044
+ const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
6045
+ const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : void 0;
6046
+ const dryRun = args.includes("--dry-run");
6047
+ const force = args.includes("--force");
6048
+ const skipMcp = args.includes("--skip-mcp") || args.includes("--no-mcp");
6049
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
6050
+ const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
6051
+ try {
6052
+ const report = runQuickstart(repoPath, catalogDir, {
6053
+ targetsArg,
6054
+ maxSkills,
6055
+ dryRun,
6056
+ force,
6057
+ skipMcp
6058
+ });
6059
+ process.stdout.write(formatQuickstartReport(report, format));
6060
+ return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
6061
+ } catch (err) {
6062
+ const msg = err instanceof Error ? err.message : String(err);
6063
+ process.stderr.write(`Invalid --targets: ${msg}
6064
+ `);
6065
+ return ExitCode.BUDGET_OR_CONTRACT_ERROR;
6066
+ }
6067
+ }
6068
+ if (command === "info" || command === "show" || command === "inspect") {
6069
+ const positionals = getPositionalArgs(args);
6070
+ const itemName = positionals[0];
6071
+ if (!itemName) {
6072
+ process.stderr.write(
6073
+ "Usage: npx skillops-cli info <skill-or-subagent-name> [options]\n"
6074
+ );
6075
+ return ExitCode.ERROR;
6076
+ }
6077
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
6078
+ const cfgRes = loadSkillOpsConfig(repoPath);
6079
+ const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
6080
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
6081
+ const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
6082
+ try {
6083
+ const info = inspectCatalogItem(catalogDir, itemName);
6084
+ process.stdout.write(formatCatalogItemInfo(info, format));
6085
+ return ExitCode.SUCCESS;
6086
+ } catch (err) {
6087
+ const msg = err instanceof Error ? err.message : String(err);
6088
+ process.stderr.write(`Error: ${msg}
6089
+ `);
6090
+ return ExitCode.ERROR;
6091
+ }
6092
+ }
6093
+ if (command === "search" || command === "find") {
6094
+ const positionals = getPositionalArgs(args);
6095
+ const query = positionals.join(" ");
6096
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
6097
+ const cfgRes = loadSkillOpsConfig(repoPath);
6098
+ const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
6099
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
6100
+ const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
6101
+ const report = searchCatalog(catalogDir, query);
6102
+ process.stdout.write(formatCatalogSearchReport(report, format));
6103
+ return ExitCode.SUCCESS;
6104
+ }
6105
+ if (command === "create" || command === "new") {
6106
+ const positionals = getPositionalArgs(args);
6107
+ let kind = "skill";
6108
+ let rawName;
6109
+ const firstPos = positionals[0]?.toLowerCase();
6110
+ if (firstPos === "skill" || firstPos === "skills") {
6111
+ kind = "skill";
6112
+ rawName = positionals[1];
6113
+ } else if (firstPos === "subagent" || firstPos === "subagents" || firstPos === "agent" || firstPos === "agents") {
6114
+ kind = "subagent";
6115
+ rawName = positionals[1];
6116
+ } else {
6117
+ rawName = positionals[0];
6118
+ }
6119
+ if (!rawName) {
6120
+ process.stderr.write(
6121
+ "Usage: npx skillops-cli create <skill|subagent> <kebab-case-name> [--description <text>] [--force]\n"
6122
+ );
6123
+ return ExitCode.ERROR;
6124
+ }
6125
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
6126
+ const cfgRes = loadSkillOpsConfig(repoPath);
6127
+ const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
6128
+ const description = getFlagValue(args, "--description", "-d");
6129
+ const force = args.includes("--force");
6130
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
6131
+ const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
6132
+ try {
6133
+ const result = scaffoldFrameworkItem(catalogDir, kind, rawName, {
6134
+ description,
6135
+ force
6136
+ });
6137
+ process.stdout.write(formatScaffoldReport(result, format));
6138
+ return ExitCode.SUCCESS;
6139
+ } catch (err) {
6140
+ const msg = err instanceof Error ? err.message : String(err);
6141
+ process.stderr.write(`Error: ${msg}
6142
+ `);
6143
+ return ExitCode.ERROR;
6144
+ }
6145
+ }
6146
+ if (command === "init") {
6147
+ const repoPath = path10.resolve(
6148
+ getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
6149
+ );
6150
+ const catalogDir = resolveCatalogDir(args, repoPath);
6151
+ const profile = fingerprintRepository(repoPath);
6152
+ const targetsArg = resolveTargetsArg(args);
6153
+ let initTargets;
6154
+ try {
6155
+ initTargets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : profile.detected_harnesses.length > 0 ? profile.detected_harnesses : [...CORE_RUNTIME_TARGETS];
6156
+ } catch (err) {
6157
+ const msg = err instanceof Error ? err.message : String(err);
6158
+ process.stderr.write(`Invalid --targets: ${msg}
6159
+ `);
6160
+ return ExitCode.BUDGET_OR_CONTRACT_ERROR;
6161
+ }
6162
+ const configPayload = SkillOpsConfigSchema.parse({
6163
+ version: "1.0",
6164
+ catalog_dir: path10.relative(repoPath, catalogDir).replace(/\\/g, "/") || "catalog",
6165
+ token_economy: {
6166
+ startup_tax_budget: 2500,
6167
+ max_active_skills: 12,
6168
+ max_l1_tokens: 1500,
6169
+ max_similarity: 0.78
6170
+ },
6171
+ detected_stack: {
6172
+ languages: profile.languages,
6173
+ frameworks: profile.frameworks
6174
+ },
6175
+ targets: initTargets
6176
+ });
6177
+ const configPath = path10.join(repoPath, ".skillops.yml");
6178
+ fs10.mkdirSync(repoPath, { recursive: true });
6179
+ fs10.writeFileSync(configPath, YAML6.stringify(configPayload), "utf-8");
4138
6180
  process.stdout.write(
4139
6181
  `Initialized SkillOps configuration at ${configPath.replace(/\\/g, "/")}
4140
6182
  `
@@ -4142,18 +6184,20 @@ function runCli(argv) {
4142
6184
  return ExitCode.SUCCESS;
4143
6185
  }
4144
6186
  if (command === "list") {
4145
- const repoIdx = args.indexOf("--repo");
4146
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6187
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4147
6188
  const cfgRes = loadSkillOpsConfig(repoPath);
4148
6189
  if (cfgRes.error) {
4149
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4150
- `);
6190
+ process.stderr.write(
6191
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6192
+ `
6193
+ );
4151
6194
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4152
6195
  }
4153
6196
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4154
- const formatIdx = args.indexOf("--format");
4155
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
4156
- const summary = auditCatalogTokenBudgets(catalogDir, { requireLayout: false });
6197
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
6198
+ const summary = auditCatalogTokenBudgets(catalogDir, {
6199
+ requireLayout: false
6200
+ });
4157
6201
  if (rawFormat === "json") {
4158
6202
  process.stdout.write(`${JSON.stringify(summary, null, 2)}
4159
6203
  `);
@@ -4185,9 +6229,15 @@ function runCli(argv) {
4185
6229
  process.stdout.write(lines2.join("\n"));
4186
6230
  return ExitCode.SUCCESS;
4187
6231
  }
6232
+ const l1Bar = renderProgressBar(summary.totalL1Tokens, 1500, {
6233
+ width: 10,
6234
+ unit: "tokens"
6235
+ });
6236
+ const firstSkill = summary.skills[0]?.skillName ?? "security-sast-gate";
4188
6237
  const lines = [
4189
6238
  `=== SkillOps Catalog (v${SKILLOPS_VERSION}) ===`,
4190
6239
  `Skills (${summary.skills.length}) \u2014 Total L1 Routing Tax: ${summary.totalL1Tokens} tokens`,
6240
+ `L1 Budget Meter: ${l1Bar}`,
4191
6241
  ...summary.skills.map(
4192
6242
  (s) => ` [SKILL] ${s.skillName} \u2014 L1: ${s.l1Tokens}t | L2: ${s.l2Lines} lines (${s.l2Tokens}t) | L3 Scripts: ${s.scriptsCount}`
4193
6243
  ),
@@ -4195,25 +6245,38 @@ function runCli(argv) {
4195
6245
  ...summary.subagents.map(
4196
6246
  (a) => ` [SUBAGENT] ${a.subagentName} (prompt=${a.systemPromptTokens}t, max_out<=${a.maxOutputTokens}t, tools=${a.tools.length})`
4197
6247
  ),
4198
- ""
6248
+ renderNextSteps([
6249
+ {
6250
+ command: `npx skillops-cli info ${firstSkill}`,
6251
+ description: "Inspect triggers & usage"
6252
+ },
6253
+ {
6254
+ command: "npx skillops-cli search <keyword>",
6255
+ description: "Search Skills & Subagents"
6256
+ },
6257
+ {
6258
+ command: "npx skillops-cli quickstart",
6259
+ description: "Auto-detect & install"
6260
+ }
6261
+ ])
4199
6262
  ];
4200
6263
  process.stdout.write(lines.join("\n"));
4201
6264
  return ExitCode.SUCCESS;
4202
6265
  }
4203
6266
  if (command === "audit") {
4204
- const repoIdx = args.indexOf("--repo");
4205
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6267
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4206
6268
  const cfgRes = loadSkillOpsConfig(repoPath);
4207
6269
  if (cfgRes.error) {
4208
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4209
- `);
6270
+ process.stderr.write(
6271
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6272
+ `
6273
+ );
4210
6274
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4211
6275
  }
4212
6276
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4213
6277
  const strict = args.includes("--strict");
4214
6278
  const requireLayout = args.includes("--require-layout");
4215
- const formatIdx = args.indexOf("--format");
4216
- const format = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6279
+ const format = getFlagValue(args, "--format", "-f") ?? "table";
4217
6280
  const report = auditCatalog(catalogDir, { requireLayout });
4218
6281
  if (format === "json") {
4219
6282
  process.stdout.write(`${JSON.stringify(report, null, 2)}
@@ -4262,20 +6325,20 @@ function runCli(argv) {
4262
6325
  }
4263
6326
  return ExitCode.SUCCESS;
4264
6327
  }
4265
- if (command === "tokens") {
4266
- const repoIdx = args.indexOf("--repo");
4267
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6328
+ if (command === "tokens" || command === "budget") {
6329
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4268
6330
  const cfgRes = loadSkillOpsConfig(repoPath);
4269
6331
  if (cfgRes.error) {
4270
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4271
- `);
6332
+ process.stderr.write(
6333
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6334
+ `
6335
+ );
4272
6336
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4273
6337
  }
4274
6338
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4275
- const budgetIdx = args.indexOf("--budget");
4276
- const startupTaxBudget = budgetIdx !== -1 && args[budgetIdx + 1] ? Number.parseInt(args[budgetIdx + 1], 10) : cfgRes.config?.token_economy.startup_tax_budget ?? 2500;
4277
- const formatIdx = args.indexOf("--format");
4278
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6339
+ const budgetRaw = getFlagValue(args, "--budget");
6340
+ const startupTaxBudget = budgetRaw ? Number.parseInt(budgetRaw, 10) : cfgRes.config?.token_economy.startup_tax_budget ?? 2500;
6341
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4279
6342
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "github" ? rawFormat : "table";
4280
6343
  const strict = args.includes("--strict");
4281
6344
  const report = analyzeRepoTokenEconomy(repoPath, {
@@ -4288,26 +6351,25 @@ function runCli(argv) {
4288
6351
  }
4289
6352
  return ExitCode.SUCCESS;
4290
6353
  }
4291
- if (command === "route") {
4292
- const repoIdx = args.indexOf("--repo");
4293
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6354
+ if (command === "route" || command === "recommend") {
6355
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4294
6356
  const cfgRes = loadSkillOpsConfig(repoPath);
4295
6357
  if (cfgRes.error) {
4296
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4297
- `);
6358
+ process.stderr.write(
6359
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6360
+ `
6361
+ );
4298
6362
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4299
6363
  }
4300
6364
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4301
- const taskIdx = args.indexOf("--task");
4302
- const taskPrompt = taskIdx !== -1 && args[taskIdx + 1] ? args[taskIdx + 1] : "";
4303
- const maxSkillsIdx = args.indexOf("--max-skills");
4304
- const maxSkills = maxSkillsIdx !== -1 && args[maxSkillsIdx + 1] ? Number.parseInt(args[maxSkillsIdx + 1], 10) : cfgRes.config?.token_economy.max_active_skills ?? 12;
4305
- const maxL1Idx = args.indexOf("--max-l1-tokens");
4306
- const maxL1Tokens = maxL1Idx !== -1 && args[maxL1Idx + 1] ? Number.parseInt(args[maxL1Idx + 1], 10) : cfgRes.config?.token_economy.max_l1_tokens ?? 1500;
4307
- const maxSimIdx = args.indexOf("--max-similarity");
4308
- const maxSimilarity = maxSimIdx !== -1 && args[maxSimIdx + 1] ? Number.parseFloat(args[maxSimIdx + 1]) : cfgRes.config?.token_economy.max_similarity ?? 0.78;
4309
- const formatIdx = args.indexOf("--format");
4310
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6365
+ const taskPrompt = getFlagValue(args, "--task") ?? "";
6366
+ const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
6367
+ const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : cfgRes.config?.token_economy.max_active_skills ?? 12;
6368
+ const maxL1Raw = getFlagValue(args, "--max-l1-tokens");
6369
+ const maxL1Tokens = maxL1Raw ? Number.parseInt(maxL1Raw, 10) : cfgRes.config?.token_economy.max_l1_tokens ?? 1500;
6370
+ const maxSimRaw = getFlagValue(args, "--max-similarity");
6371
+ const maxSimilarity = maxSimRaw ? Number.parseFloat(maxSimRaw) : cfgRes.config?.token_economy.max_similarity ?? 0.78;
6372
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4311
6373
  const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
4312
6374
  const bundle = routeRepository(repoPath, catalogDir, {
4313
6375
  taskPrompt,
@@ -4319,60 +6381,59 @@ function runCli(argv) {
4319
6381
  return bundle.passed_invariants ? ExitCode.SUCCESS : ExitCode.ERROR;
4320
6382
  }
4321
6383
  if (command === "sync") {
4322
- const repoIdx = args.indexOf("--repo");
4323
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6384
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4324
6385
  const cfgRes = loadSkillOpsConfig(repoPath);
4325
6386
  if (cfgRes.error) {
4326
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4327
- `);
6387
+ process.stderr.write(
6388
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6389
+ `
6390
+ );
4328
6391
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4329
6392
  }
4330
6393
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4331
- const targetsIdx = args.indexOf("--targets");
6394
+ const targetsArg = resolveTargetsArg(args);
4332
6395
  let targets;
4333
6396
  try {
4334
- targets = targetsIdx !== -1 && args[targetsIdx + 1] ? resolveRuntimeTargets(args[targetsIdx + 1], repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
6397
+ targets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
4335
6398
  } catch (err) {
4336
6399
  const msg = err instanceof Error ? err.message : String(err);
4337
6400
  process.stderr.write(`Invalid --targets: ${msg}
4338
6401
  `);
4339
6402
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4340
6403
  }
6404
+ const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
6405
+ const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : cfgRes.config?.token_economy.max_active_skills;
4341
6406
  const dryRun = args.includes("--dry-run");
4342
6407
  const force = args.includes("--force");
4343
- const formatIdx = args.indexOf("--format");
4344
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6408
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4345
6409
  const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
4346
6410
  const report = syncRepository(repoPath, catalogDir, {
4347
6411
  targets,
4348
6412
  dryRun,
4349
6413
  force,
4350
- maxSkills: cfgRes.config?.token_economy.max_active_skills,
6414
+ maxSkills,
4351
6415
  maxL1Tokens: cfgRes.config?.token_economy.max_l1_tokens,
4352
6416
  maxSimilarity: cfgRes.config?.token_economy.max_similarity
4353
6417
  });
4354
6418
  process.stdout.write(formatSyncReport(report, format));
4355
6419
  return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
4356
6420
  }
4357
- if (command === "add") {
4358
- const itemName = args[1];
4359
- if (!itemName || itemName.startsWith("-")) {
4360
- process.stderr.write("Usage: npx skillops@latest add <skill-or-subagent-name> [options]\n");
4361
- return ExitCode.ERROR;
4362
- }
4363
- const repoIdx = args.indexOf("--repo");
4364
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6421
+ if (command === "add" || command === "install" || command === "i") {
6422
+ const positionals = getPositionalArgs(args);
6423
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4365
6424
  const cfgRes = loadSkillOpsConfig(repoPath);
4366
6425
  if (cfgRes.error) {
4367
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4368
- `);
6426
+ process.stderr.write(
6427
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6428
+ `
6429
+ );
4369
6430
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4370
6431
  }
4371
6432
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4372
- const targetsIdx = args.indexOf("--targets");
6433
+ const targetsArg = resolveTargetsArg(args);
4373
6434
  let targets;
4374
6435
  try {
4375
- targets = targetsIdx !== -1 && args[targetsIdx + 1] ? resolveRuntimeTargets(args[targetsIdx + 1], repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
6436
+ targets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
4376
6437
  } catch (err) {
4377
6438
  const msg = err instanceof Error ? err.message : String(err);
4378
6439
  process.stderr.write(`Invalid --targets: ${msg}
@@ -4381,14 +6442,42 @@ function runCli(argv) {
4381
6442
  }
4382
6443
  const dryRun = args.includes("--dry-run");
4383
6444
  const force = args.includes("--force");
4384
- try {
4385
- const report = addSkillOrSubagent(repoPath, catalogDir, itemName, {
6445
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
6446
+ const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
6447
+ if (positionals.length === 0) {
6448
+ if (command === "add") {
6449
+ process.stderr.write(
6450
+ "Usage: npx skillops-cli add <skill-or-subagent-name> [more-names...] [options]\n"
6451
+ );
6452
+ return ExitCode.ERROR;
6453
+ }
6454
+ const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
6455
+ const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : cfgRes.config?.token_economy.max_active_skills;
6456
+ const report = syncRepository(repoPath, catalogDir, {
4386
6457
  targets,
4387
6458
  dryRun,
4388
- force
6459
+ force,
6460
+ maxSkills,
6461
+ maxL1Tokens: cfgRes.config?.token_economy.max_l1_tokens,
6462
+ maxSimilarity: cfgRes.config?.token_economy.max_similarity
4389
6463
  });
4390
- process.stdout.write(formatSyncReport(report, "table"));
6464
+ process.stdout.write(formatSyncReport(report, format));
4391
6465
  return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
6466
+ }
6467
+ try {
6468
+ let allPassed = true;
6469
+ for (const itemName of positionals) {
6470
+ const report = addSkillOrSubagent(repoPath, catalogDir, itemName, {
6471
+ targets,
6472
+ dryRun,
6473
+ force
6474
+ });
6475
+ process.stdout.write(formatSyncReport(report, format));
6476
+ if (!report.passed) {
6477
+ allPassed = false;
6478
+ }
6479
+ }
6480
+ return allPassed ? ExitCode.SUCCESS : ExitCode.ERROR;
4392
6481
  } catch (err) {
4393
6482
  const msg = err instanceof Error ? err.message : String(err);
4394
6483
  process.stderr.write(`Error: ${msg}
@@ -4396,37 +6485,44 @@ function runCli(argv) {
4396
6485
  return ExitCode.ERROR;
4397
6486
  }
4398
6487
  }
4399
- if (command === "remove") {
4400
- const itemName = args[1];
4401
- if (!itemName || itemName.startsWith("-")) {
4402
- process.stderr.write("Usage: npx skillops@latest remove <skill-or-subagent-name> [options]\n");
6488
+ if (command === "remove" || command === "uninstall" || command === "rm") {
6489
+ const positionals = getPositionalArgs(args);
6490
+ if (positionals.length === 0) {
6491
+ process.stderr.write(
6492
+ "Usage: npx skillops-cli remove <skill-or-subagent-name> [options]\n"
6493
+ );
4403
6494
  return ExitCode.ERROR;
4404
6495
  }
4405
- const repoIdx = args.indexOf("--repo");
4406
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6496
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4407
6497
  const cfgRes = loadSkillOpsConfig(repoPath);
4408
6498
  if (cfgRes.error) {
4409
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4410
- `);
6499
+ process.stderr.write(
6500
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6501
+ `
6502
+ );
4411
6503
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4412
6504
  }
4413
6505
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4414
- const targetsIdx = args.indexOf("--targets");
4415
- const rawTargetsArg = targetsIdx !== -1 && args[targetsIdx + 1] ? args[targetsIdx + 1] : void 0;
6506
+ const rawTargetsArg = resolveTargetsArg(args);
4416
6507
  const dryRun = args.includes("--dry-run");
4417
6508
  const force = args.includes("--force");
4418
- const formatIdx = args.indexOf("--format");
4419
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6509
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4420
6510
  const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
4421
6511
  try {
4422
6512
  const targets = rawTargetsArg ? resolveRuntimeTargets(rawTargetsArg, repoPath) : void 0;
4423
- const report = removeSkillOrSubagent(repoPath, catalogDir, itemName, {
4424
- targets,
4425
- dryRun,
4426
- force
4427
- });
4428
- process.stdout.write(formatSyncReport(report, format));
4429
- return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
6513
+ let allPassed = true;
6514
+ for (const itemName of positionals) {
6515
+ const report = removeSkillOrSubagent(repoPath, catalogDir, itemName, {
6516
+ targets,
6517
+ dryRun,
6518
+ force
6519
+ });
6520
+ process.stdout.write(formatSyncReport(report, format));
6521
+ if (!report.passed) {
6522
+ allPassed = false;
6523
+ }
6524
+ }
6525
+ return allPassed ? ExitCode.SUCCESS : ExitCode.ERROR;
4430
6526
  } catch (err) {
4431
6527
  const msg = err instanceof Error ? err.message : String(err);
4432
6528
  process.stderr.write(`Error: ${msg}
@@ -4435,39 +6531,40 @@ function runCli(argv) {
4435
6531
  }
4436
6532
  }
4437
6533
  if (command === "clean") {
4438
- const repoIdx = args.indexOf("--repo");
4439
- const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
6534
+ const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
4440
6535
  const cfgRes = loadSkillOpsConfig(repoPath);
4441
6536
  if (cfgRes.error) {
4442
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4443
- `);
6537
+ process.stderr.write(
6538
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6539
+ `
6540
+ );
4444
6541
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4445
6542
  }
4446
6543
  const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
4447
6544
  const dryRun = args.includes("--dry-run");
4448
6545
  const force = args.includes("--force");
4449
- const formatIdx = args.indexOf("--format");
4450
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6546
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4451
6547
  const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
4452
- const report = cleanSyncedArtifacts(repoPath, catalogDir, { dryRun, force });
6548
+ const report = cleanSyncedArtifacts(repoPath, catalogDir, {
6549
+ dryRun,
6550
+ force
6551
+ });
4453
6552
  process.stdout.write(formatSyncReport(report, format));
4454
6553
  return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
4455
6554
  }
4456
6555
  if (command === "eval") {
4457
6556
  const catalogDir = resolveCatalogDir(args);
4458
- const skillIdx = args.indexOf("--skill");
4459
- const skillFilter = skillIdx !== -1 && args[skillIdx + 1] ? args[skillIdx + 1] : void 0;
4460
- const formatIdx = args.indexOf("--format");
4461
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6557
+ const skillFilter = getFlagValue(args, "--skill");
6558
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4462
6559
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "html" || rawFormat === "github" ? rawFormat : "table";
4463
- const outIdx = args.indexOf("--out");
4464
- const outPath = outIdx !== -1 && args[outIdx + 1] ? path9.resolve(args[outIdx + 1]) : null;
6560
+ const outRaw = getFlagValue(args, "--out");
6561
+ const outPath = outRaw ? path10.resolve(outRaw) : null;
4465
6562
  const strict = args.includes("--strict");
4466
6563
  const summary = evaluateCatalog(catalogDir, { skillFilter });
4467
6564
  const rendered = formatEvalSummary(summary, format);
4468
6565
  if (outPath) {
4469
- fs9.mkdirSync(path9.dirname(outPath), { recursive: true });
4470
- fs9.writeFileSync(outPath, rendered, "utf-8");
6566
+ fs10.mkdirSync(path10.dirname(outPath), { recursive: true });
6567
+ fs10.writeFileSync(outPath, rendered, "utf-8");
4471
6568
  }
4472
6569
  process.stdout.write(rendered);
4473
6570
  if (strict && !summary.passed) {
@@ -4476,26 +6573,26 @@ function runCli(argv) {
4476
6573
  return ExitCode.SUCCESS;
4477
6574
  }
4478
6575
  if (command === "harnesses") {
4479
- const repoIdx = args.indexOf("--repo");
4480
- const repoPath = path9.resolve(
4481
- repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
6576
+ const repoPath = path10.resolve(
6577
+ getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
4482
6578
  );
4483
- const formatIdx = args.indexOf("--format");
4484
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6579
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4485
6580
  const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
4486
6581
  const report = inspectHarnesses(repoPath);
4487
6582
  process.stdout.write(formatHarnessInspectionReport(report, format));
4488
6583
  return ExitCode.SUCCESS;
4489
6584
  }
4490
6585
  if (command === "doctor" || command === "status") {
4491
- const repoIdx = args.indexOf("--repo");
4492
- const repoPath = path9.resolve(
4493
- repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
6586
+ const repoPath = path10.resolve(
6587
+ getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
4494
6588
  );
4495
6589
  const loadedConfigRes = loadSkillOpsConfig(repoPath);
4496
- const catalogDir = resolveCatalogDir(args, repoPath, loadedConfigRes.config);
4497
- const formatIdx = args.indexOf("--format");
4498
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6590
+ const catalogDir = resolveCatalogDir(
6591
+ args,
6592
+ repoPath,
6593
+ loadedConfigRes.config
6594
+ );
6595
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4499
6596
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "github" ? rawFormat : "table";
4500
6597
  const strict = args.includes("--strict");
4501
6598
  const report = diagnoseRepository(repoPath, { catalogDir });
@@ -4513,29 +6610,29 @@ function runCli(argv) {
4513
6610
  return ExitCode.SUCCESS;
4514
6611
  }
4515
6612
  if (command === "mcp") {
4516
- const repoIdx = args.indexOf("--repo");
4517
- const repoPath = path9.resolve(
4518
- repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
6613
+ const repoPath = path10.resolve(
6614
+ getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
4519
6615
  );
4520
6616
  const catalogDir = resolveCatalogDir(args, repoPath);
4521
6617
  startMcpStdioServer({ repoPath, catalogDir });
4522
6618
  return ExitCode.SUCCESS;
4523
6619
  }
4524
6620
  if (command === "mcp-setup") {
4525
- const repoIdx = args.indexOf("--repo");
4526
- const repoPath = path9.resolve(
4527
- repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
6621
+ const repoPath = path10.resolve(
6622
+ getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
4528
6623
  );
4529
6624
  const cfgRes = loadSkillOpsConfig(repoPath);
4530
6625
  if (cfgRes.error) {
4531
- process.stderr.write(`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
4532
- `);
6626
+ process.stderr.write(
6627
+ `Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
6628
+ `
6629
+ );
4533
6630
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4534
6631
  }
4535
- const targetsIdx = args.indexOf("--targets");
6632
+ const targetsArg = resolveTargetsArg(args);
4536
6633
  let targets;
4537
6634
  try {
4538
- targets = targetsIdx !== -1 && args[targetsIdx + 1] ? resolveRuntimeTargets(args[targetsIdx + 1], repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
6635
+ targets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
4539
6636
  } catch (err) {
4540
6637
  const msg = err instanceof Error ? err.message : String(err);
4541
6638
  process.stderr.write(`Invalid --targets: ${msg}
@@ -4543,15 +6640,18 @@ function runCli(argv) {
4543
6640
  return ExitCode.BUDGET_OR_CONTRACT_ERROR;
4544
6641
  }
4545
6642
  const dryRun = args.includes("--dry-run");
4546
- const formatIdx = args.indexOf("--format");
4547
- const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
6643
+ const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
4548
6644
  const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
4549
6645
  const report = setupMcpForHarnesses(repoPath, { targets, dryRun });
4550
6646
  process.stdout.write(formatMcpSetupReport(report, format));
4551
6647
  return ExitCode.SUCCESS;
4552
6648
  }
4553
- process.stderr.write(`Unknown command '${command}'. Run 'npx skillops@latest --help'.
4554
- `);
6649
+ const suggestion = suggestCommand(command);
6650
+ const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
6651
+ process.stderr.write(
6652
+ `Unknown command '${command}'.${hint} Run 'npx skillops-cli --help'.
6653
+ `
6654
+ );
4555
6655
  return ExitCode.ERROR;
4556
6656
  }
4557
6657
  function isDirectCliInvocation() {
@@ -4560,10 +6660,10 @@ function isDirectCliInvocation() {
4560
6660
  return false;
4561
6661
  }
4562
6662
  try {
4563
- const resolvedArg = fs9.existsSync(entryArg) ? fs9.realpathSync(entryArg) : path9.resolve(entryArg);
6663
+ const resolvedArg = fs10.existsSync(entryArg) ? fs10.realpathSync(entryArg) : path10.resolve(entryArg);
4564
6664
  const selfPath = fileURLToPath(import.meta.url);
4565
- const resolvedSelf = fs9.existsSync(selfPath) ? fs9.realpathSync(selfPath) : selfPath;
4566
- const base = path9.basename(resolvedArg, path9.extname(resolvedArg));
6665
+ const resolvedSelf = fs10.existsSync(selfPath) ? fs10.realpathSync(selfPath) : selfPath;
6666
+ const base = path10.basename(resolvedArg, path10.extname(resolvedArg));
4567
6667
  return pathToFileURL(resolvedArg).href === pathToFileURL(resolvedSelf).href || base === "skillops" || base === "skillops-cli";
4568
6668
  } catch {
4569
6669
  return false;