@thinkingsage/kanon 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bridge/mcp-server.cjs +14171 -0
- package/package.json +98 -0
- package/src/adapters/capabilities.ts +178 -0
- package/src/adapters/claude-code.ts +110 -0
- package/src/adapters/cline.ts +98 -0
- package/src/adapters/codex.ts +173 -0
- package/src/adapters/copilot.ts +106 -0
- package/src/adapters/cursor.ts +97 -0
- package/src/adapters/degradation.ts +95 -0
- package/src/adapters/index.ts +324 -0
- package/src/adapters/kiro-frontmatter.ts +139 -0
- package/src/adapters/kiro-inclusion.ts +86 -0
- package/src/adapters/kiro.ts +412 -0
- package/src/adapters/qdeveloper.ts +115 -0
- package/src/adapters/types.ts +81 -0
- package/src/adapters/windsurf.ts +96 -0
- package/src/admin.ts +283 -0
- package/src/asset-conventions.ts +118 -0
- package/src/attribution-backfill.ts +319 -0
- package/src/attribution-report.ts +95 -0
- package/src/attribution.ts +239 -0
- package/src/backends/github.ts +194 -0
- package/src/backends/http.ts +122 -0
- package/src/backends/index.ts +39 -0
- package/src/backends/local.ts +47 -0
- package/src/backends/s3.ts +157 -0
- package/src/backends/types.ts +59 -0
- package/src/base-cache.ts +270 -0
- package/src/browse-ui.ts +3754 -0
- package/src/browse.ts +1038 -0
- package/src/build.ts +1108 -0
- package/src/catalog.ts +204 -0
- package/src/cli-deprecated.ts +29 -0
- package/src/cli.ts +773 -0
- package/src/collection-admin.ts +287 -0
- package/src/collection-builder.ts +464 -0
- package/src/collections.ts +116 -0
- package/src/compatibility.ts +105 -0
- package/src/config.ts +743 -0
- package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
- package/src/eval.ts +1169 -0
- package/src/file-writer.ts +61 -0
- package/src/format-registry.ts +141 -0
- package/src/guild/auto-updater.ts +163 -0
- package/src/guild/backend-resolver.ts +49 -0
- package/src/guild/cli.ts +592 -0
- package/src/guild/collection-expander.ts +47 -0
- package/src/guild/global-cache.ts +247 -0
- package/src/guild/hook-generator.ts +100 -0
- package/src/guild/manifest.ts +154 -0
- package/src/guild/path-utils.ts +12 -0
- package/src/guild/sync.ts +622 -0
- package/src/guild/version-resolver.ts +42 -0
- package/src/help/metadata.ts +445 -0
- package/src/help/renderer.ts +265 -0
- package/src/help/typo-suggester.ts +25 -0
- package/src/hooks/expression.ts +493 -0
- package/src/hooks/pipeline.ts +141 -0
- package/src/import.ts +773 -0
- package/src/importers/claude-code.ts +134 -0
- package/src/importers/cline.ts +103 -0
- package/src/importers/codex.ts +140 -0
- package/src/importers/copilot.ts +103 -0
- package/src/importers/cursor.ts +105 -0
- package/src/importers/index.ts +390 -0
- package/src/importers/kiro.ts +110 -0
- package/src/importers/qdeveloper.ts +103 -0
- package/src/importers/types.ts +54 -0
- package/src/importers/windsurf.ts +104 -0
- package/src/install.ts +1005 -0
- package/src/manifest-admin.ts +306 -0
- package/src/mcp-bridge.ts +240 -0
- package/src/mutation/delta.ts +50 -0
- package/src/mutation/history.ts +66 -0
- package/src/mutation/operators.ts +524 -0
- package/src/mutation/runner.ts +332 -0
- package/src/new.ts +106 -0
- package/src/outcomes/collision.ts +127 -0
- package/src/outcomes/normalize.ts +208 -0
- package/src/outcomes/registry.ts +173 -0
- package/src/parser.ts +446 -0
- package/src/provenance-backfill-cli.ts +319 -0
- package/src/provenance-backfill.ts +520 -0
- package/src/publish.ts +354 -0
- package/src/reconcile-orchestrator.ts +502 -0
- package/src/reconcile-report-renderer.ts +176 -0
- package/src/resolve-body.ts +15 -0
- package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
- package/src/rosetta/builtins/contracts.ts +1033 -0
- package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
- package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
- package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
- package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
- package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
- package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
- package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
- package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
- package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
- package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
- package/src/rosetta/builtins/sources/cline-native.ts +176 -0
- package/src/rosetta/builtins/sources/codex-native.ts +343 -0
- package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
- package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
- package/src/rosetta/builtins/sources/index.ts +95 -0
- package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
- package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
- package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
- package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
- package/src/rosetta/builtins/sources/superpowers.ts +240 -0
- package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
- package/src/rosetta/builtins/targets/claude-code.ts +181 -0
- package/src/rosetta/builtins/targets/cline.ts +87 -0
- package/src/rosetta/builtins/targets/codex.ts +226 -0
- package/src/rosetta/builtins/targets/copilot.ts +103 -0
- package/src/rosetta/builtins/targets/cursor.ts +87 -0
- package/src/rosetta/builtins/targets/index.ts +60 -0
- package/src/rosetta/builtins/targets/kiro.ts +278 -0
- package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
- package/src/rosetta/builtins/targets/windsurf.ts +87 -0
- package/src/rosetta/canonical.ts +729 -0
- package/src/rosetta/compatibility.ts +432 -0
- package/src/rosetta/contracts.ts +329 -0
- package/src/rosetta/detector.ts +724 -0
- package/src/rosetta/diagnostics.ts +630 -0
- package/src/rosetta/engine-bootstrap.ts +103 -0
- package/src/rosetta/engine.ts +744 -0
- package/src/rosetta/index.ts +381 -0
- package/src/rosetta/inspection.ts +530 -0
- package/src/rosetta/plan.ts +448 -0
- package/src/rosetta/provenance-digest.ts +369 -0
- package/src/rosetta/reconcile.ts +812 -0
- package/src/rosetta/redaction.ts +467 -0
- package/src/rosetta/registry.ts +712 -0
- package/src/rosetta/renderers.ts +571 -0
- package/src/rosetta/request-guard.ts +335 -0
- package/src/rosetta/resolution.ts +419 -0
- package/src/rosetta/source-accounting.ts +233 -0
- package/src/rosetta/templates.ts +129 -0
- package/src/rosetta-cli.ts +717 -0
- package/src/rosetta-docs-generator.ts +793 -0
- package/src/rosetta-profiles-cli.ts +367 -0
- package/src/schemas.ts +1712 -0
- package/src/spec-coordination.ts +1141 -0
- package/src/temper.ts +747 -0
- package/src/template-bundle-loader.ts +312 -0
- package/src/template-engine.ts +53 -0
- package/src/translation-application-policy.ts +496 -0
- package/src/translation-orchestrator.ts +1013 -0
- package/src/translation-plan-applier.ts +473 -0
- package/src/tutorial.ts +305 -0
- package/src/validate.ts +1093 -0
- package/src/versioning.ts +553 -0
- package/src/wizard.ts +660 -0
- package/src/workspace.ts +237 -0
- package/templates/eval-contexts/claude-code.md.njk +6 -0
- package/templates/eval-contexts/cline.md.njk +6 -0
- package/templates/eval-contexts/copilot.md.njk +6 -0
- package/templates/eval-contexts/cursor.md.njk +6 -0
- package/templates/eval-contexts/kiro.md.njk +10 -0
- package/templates/eval-contexts/qdeveloper.md.njk +6 -0
- package/templates/eval-contexts/windsurf.md.njk +6 -0
- package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
- package/templates/harness-adapters/_base/base.md.njk +16 -0
- package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
- package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
- package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
- package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
- package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
- package/templates/harness-adapters/cline/hook.sh.njk +4 -0
- package/templates/harness-adapters/cline/mcp.json.njk +1 -0
- package/templates/harness-adapters/cline/rule.md.njk +1 -0
- package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
- package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
- package/templates/harness-adapters/codex/skill.md.njk +27 -0
- package/templates/harness-adapters/copilot/agents.md.njk +1 -0
- package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
- package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
- package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
- package/templates/harness-adapters/cursor/rule.md.njk +6 -0
- package/templates/harness-adapters/kiro/hook.json.njk +1 -0
- package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
- package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
- package/templates/harness-adapters/kiro/power.md.njk +12 -0
- package/templates/harness-adapters/kiro/steering.md.njk +16 -0
- package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
- package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
- package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
- package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
- package/templates/knowledge/hooks.yaml.njk +4 -0
- package/templates/knowledge/knowledge.md.njk +53 -0
- package/templates/knowledge/mcp-servers.yaml.njk +2 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
if (typeof globalThis.Bun === "undefined") {
|
|
4
|
+
console.error(
|
|
5
|
+
"Error: Kanon requires Bun (https://bun.sh) to run.\n" +
|
|
6
|
+
"Install it with: curl -fsSL https://bun.sh/install | bash\n" +
|
|
7
|
+
"Then run: bunx @thinkingsage/kanon <command>",
|
|
8
|
+
);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
import { writeFile } from "node:fs/promises";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
import { runAttributionBackfill } from "./attribution-backfill";
|
|
16
|
+
import { renderAttributionReport } from "./attribution-report";
|
|
17
|
+
import { browseCommand, exportCommand } from "./browse";
|
|
18
|
+
import { buildCommand } from "./build";
|
|
19
|
+
import { catalogCommand, generateCatalog, SOURCE_DIRS } from "./catalog";
|
|
20
|
+
import {
|
|
21
|
+
collectionBuildCommand,
|
|
22
|
+
collectionNewCommand,
|
|
23
|
+
collectionStatusCommand,
|
|
24
|
+
} from "./collection-builder";
|
|
25
|
+
import { evalCommand } from "./eval";
|
|
26
|
+
import { registerGuildCommands } from "./guild/cli";
|
|
27
|
+
import { commandMetaRegistry } from "./help/metadata";
|
|
28
|
+
import {
|
|
29
|
+
type RootCommand,
|
|
30
|
+
renderCommandHelp,
|
|
31
|
+
renderRootHelp,
|
|
32
|
+
renderVersion,
|
|
33
|
+
} from "./help/renderer";
|
|
34
|
+
import { suggestCommand } from "./help/typo-suggester";
|
|
35
|
+
import { importCommand as kiroImportCommand } from "./import";
|
|
36
|
+
import { importCommand as multiHarnessImportCommand } from "./importers/index";
|
|
37
|
+
import { installCommand } from "./install";
|
|
38
|
+
import { newCommand } from "./new";
|
|
39
|
+
import { publishCommand } from "./publish";
|
|
40
|
+
import { registerRosettaCommands } from "./rosetta-cli";
|
|
41
|
+
import type { HarnessName } from "./schemas";
|
|
42
|
+
import { SUPPORTED_HARNESSES } from "./schemas";
|
|
43
|
+
import {
|
|
44
|
+
specClaimCommand,
|
|
45
|
+
specDoneCommand,
|
|
46
|
+
specHandoffCommand,
|
|
47
|
+
specListCommand,
|
|
48
|
+
specNextCommand,
|
|
49
|
+
specReconcileCommand,
|
|
50
|
+
specReleaseCommand,
|
|
51
|
+
specStatusCommand,
|
|
52
|
+
} from "./spec-coordination";
|
|
53
|
+
import {
|
|
54
|
+
formatComparisonOutput,
|
|
55
|
+
formatJsonOutput,
|
|
56
|
+
formatTerminalOutput,
|
|
57
|
+
renderComparison,
|
|
58
|
+
renderTemper,
|
|
59
|
+
startTemperServer,
|
|
60
|
+
} from "./temper";
|
|
61
|
+
import { tutorialCommand } from "./tutorial";
|
|
62
|
+
import { validateCommand } from "./validate";
|
|
63
|
+
import { upgradeCommand } from "./versioning";
|
|
64
|
+
|
|
65
|
+
// Banner lines — stored without trailing padding; printBanner normalises widths.
|
|
66
|
+
const bannerLines = [
|
|
67
|
+
"",
|
|
68
|
+
" ____ _ _ _ _ _____",
|
|
69
|
+
" / ___|| | _(_) | | | ___|__ _ __ __ _ ___",
|
|
70
|
+
" \\___ \\| |/ / | | | | |_ / _ \\| '__/ _` |/ _ \\",
|
|
71
|
+
" ___) | <| | | | | _| (_) | | | (_| | __/",
|
|
72
|
+
" |____/|_|\\_\\_|_|_| |_| \\___/|_| \\__, |\\___|",
|
|
73
|
+
" |___/",
|
|
74
|
+
" ⚡ author → catalog → harness ⚡",
|
|
75
|
+
"",
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
// Orange → Cyan gradient mapped across the banner lines
|
|
79
|
+
const gradientSteps = [
|
|
80
|
+
[255, 140, 0], // orange
|
|
81
|
+
[255, 180, 40], // gold
|
|
82
|
+
[100, 220, 180], // teal
|
|
83
|
+
[0, 210, 255], // bright cyan
|
|
84
|
+
[0, 180, 255], // azure
|
|
85
|
+
[0, 160, 240], // mid blue
|
|
86
|
+
[0, 200, 255], // cyan
|
|
87
|
+
[80, 220, 240], // light cyan
|
|
88
|
+
[0, 210, 255], // bright cyan
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Visual display width of a string — counts wide characters (emoji, CJK)
|
|
93
|
+
* as 2 columns. Handles the ⚡ in the tagline correctly.
|
|
94
|
+
*/
|
|
95
|
+
function visualWidth(s: string): number {
|
|
96
|
+
let w = 0;
|
|
97
|
+
for (const cp of s) {
|
|
98
|
+
const code = cp.codePointAt(0) ?? 0;
|
|
99
|
+
// Wide: emoji, CJK, fullwidth ranges
|
|
100
|
+
const wide =
|
|
101
|
+
(code >= 0x1100 && code <= 0x115f) || // Hangul Jamo
|
|
102
|
+
(code >= 0x2600 && code <= 0x27bf) || // Misc symbols, Dingbats (⚡ is U+26A1)
|
|
103
|
+
(code >= 0x2e80 && code <= 0x303e) || // CJK radicals
|
|
104
|
+
(code >= 0x3040 && code <= 0xa4cf) || // CJK unified
|
|
105
|
+
(code >= 0xac00 && code <= 0xd7a3) || // Hangul syllables
|
|
106
|
+
(code >= 0xf900 && code <= 0xfaff) || // CJK compatibility
|
|
107
|
+
(code >= 0xfe10 && code <= 0xfe1f) || // Vertical forms
|
|
108
|
+
(code >= 0xfe30 && code <= 0xfe4f) || // CJK compatibility forms
|
|
109
|
+
(code >= 0xff00 && code <= 0xff60) || // Fullwidth forms
|
|
110
|
+
(code >= 0xffe0 && code <= 0xffe6) || // Fullwidth signs
|
|
111
|
+
(code >= 0x1f300 && code <= 0x1faff); // Emoji block
|
|
112
|
+
w += wide ? 2 : 1;
|
|
113
|
+
}
|
|
114
|
+
return w;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function printBanner() {
|
|
118
|
+
const bg = chalk.bgRgb(15, 20, 35);
|
|
119
|
+
const leadingMargin = 2; // left padding
|
|
120
|
+
const trailingMargin = 4; // right padding (more to visually balance the logo's left indent)
|
|
121
|
+
const maxContent = Math.max(...bannerLines.map(visualWidth));
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < bannerLines.length; i++) {
|
|
124
|
+
const [r, g, b] = gradientSteps[i % gradientSteps.length];
|
|
125
|
+
const line = bannerLines[i];
|
|
126
|
+
const trailing = " ".repeat(
|
|
127
|
+
maxContent - visualWidth(line) + trailingMargin,
|
|
128
|
+
);
|
|
129
|
+
console.log(
|
|
130
|
+
bg(
|
|
131
|
+
chalk
|
|
132
|
+
.rgb(r, g, b)
|
|
133
|
+
.bold(`${" ".repeat(leadingMargin)}${line}${trailing}`),
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Compact plain-text banner for log files — no ANSI, no emoji. */
|
|
141
|
+
export function logBanner(): string {
|
|
142
|
+
const ts = new Date().toISOString();
|
|
143
|
+
return ["--- kanon v0.4.1 ---", `started: ${ts}`, "-".repeat(26)].join("\n");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Detect --no-color early and disable chalk styling
|
|
147
|
+
const useColor = !process.argv.includes("--no-color");
|
|
148
|
+
if (!useColor) {
|
|
149
|
+
chalk.level = 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Rewrite trailing "help" to "--help" so `kanon build help` shows the help screen
|
|
153
|
+
// instead of erroring with "too many arguments".
|
|
154
|
+
if (
|
|
155
|
+
process.argv.length >= 3 &&
|
|
156
|
+
process.argv[process.argv.length - 1] === "help"
|
|
157
|
+
) {
|
|
158
|
+
// Only rewrite when "help" isn't the registered subcommand itself (argv[2])
|
|
159
|
+
if (process.argv[2] !== "help") {
|
|
160
|
+
process.argv[process.argv.length - 1] = "--help";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const hasHelpFlag =
|
|
165
|
+
process.argv.includes("--help") || process.argv.includes("-h");
|
|
166
|
+
const hasHelpCommand = process.argv[2] === "help";
|
|
167
|
+
if (process.argv.length <= 2 && !hasHelpFlag && !hasHelpCommand) {
|
|
168
|
+
printBanner();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Only parse CLI when run directly (not when imported for logBanner)
|
|
172
|
+
if (import.meta.main !== false) {
|
|
173
|
+
const program = new Command()
|
|
174
|
+
.name("kanon")
|
|
175
|
+
.description("Kanon — write knowledge once, compile to every harness");
|
|
176
|
+
|
|
177
|
+
program
|
|
178
|
+
.command("build")
|
|
179
|
+
.description("Compile knowledge artifacts to harness-native formats")
|
|
180
|
+
.option("--harness <name>", "Build for a single harness only")
|
|
181
|
+
.option("--strict", "Treat compatibility warnings as errors")
|
|
182
|
+
.action(buildCommand);
|
|
183
|
+
|
|
184
|
+
program
|
|
185
|
+
.command("install [artifact]")
|
|
186
|
+
.description("Install compiled artifacts into the current project")
|
|
187
|
+
.option("--harness <name>", "Install for a specific harness")
|
|
188
|
+
.option("--all", "Install for all harnesses")
|
|
189
|
+
.option("--force", "Overwrite without confirmation")
|
|
190
|
+
.option("--dry-run", "Show what would be installed without writing files")
|
|
191
|
+
.option("--source <path>", "Path to kanon repository")
|
|
192
|
+
.option("--from-release <tag>", "Download from GitHub release")
|
|
193
|
+
.option("--backend <name>", "Named backend from kanon.config.yaml")
|
|
194
|
+
.option("--global", "Install artifact into the global cache")
|
|
195
|
+
.option("--project <name>", "Install into a specific workspace project")
|
|
196
|
+
.option(
|
|
197
|
+
"--max-always <N>",
|
|
198
|
+
"Max number of always-mode Kiro steering files to install (-1 = no limit)",
|
|
199
|
+
(val: string) => parseInt(val, 10),
|
|
200
|
+
)
|
|
201
|
+
.action(installCommand);
|
|
202
|
+
|
|
203
|
+
program
|
|
204
|
+
.command("new <artifact-name>")
|
|
205
|
+
.description("Scaffold a new knowledge artifact")
|
|
206
|
+
.option("--yes", "Skip interactive wizard, use template defaults")
|
|
207
|
+
.option(
|
|
208
|
+
"--type <type>",
|
|
209
|
+
"Asset type: skill, power, rule, workflow, agent, prompt, template, reference-pack",
|
|
210
|
+
)
|
|
211
|
+
.action(newCommand);
|
|
212
|
+
|
|
213
|
+
program
|
|
214
|
+
.command("tutorial")
|
|
215
|
+
.description("Guided walkthrough for first-time artifact authors")
|
|
216
|
+
.action(tutorialCommand);
|
|
217
|
+
|
|
218
|
+
program
|
|
219
|
+
.command("validate [artifact-path]")
|
|
220
|
+
.description("Validate knowledge artifacts")
|
|
221
|
+
.option(
|
|
222
|
+
"--security",
|
|
223
|
+
"Run additional security checks (prompt injection, dangerous hooks, obfuscation)",
|
|
224
|
+
)
|
|
225
|
+
.action((artifactPath, options) => validateCommand(artifactPath, options));
|
|
226
|
+
|
|
227
|
+
const catalogCmd = program
|
|
228
|
+
.command("catalog")
|
|
229
|
+
.description("Manage the artifact catalog");
|
|
230
|
+
|
|
231
|
+
catalogCmd
|
|
232
|
+
.command("generate")
|
|
233
|
+
.description("Generate catalog.json")
|
|
234
|
+
.action(catalogCommand);
|
|
235
|
+
|
|
236
|
+
catalogCmd
|
|
237
|
+
.command("browse")
|
|
238
|
+
.description("Browse the artifact catalog in your browser")
|
|
239
|
+
.option("--port <number>", "Port to serve on", "3131")
|
|
240
|
+
.option(
|
|
241
|
+
"--all",
|
|
242
|
+
"Include unlisted artifacts in the listing (private remain hidden)",
|
|
243
|
+
)
|
|
244
|
+
.action(browseCommand);
|
|
245
|
+
|
|
246
|
+
catalogCmd
|
|
247
|
+
.command("export")
|
|
248
|
+
.description(
|
|
249
|
+
"Export a self-contained static catalog site for GitHub Pages or any static host",
|
|
250
|
+
)
|
|
251
|
+
.option(
|
|
252
|
+
"--output <dir>",
|
|
253
|
+
"Output directory for index.html and catalog.json",
|
|
254
|
+
"dist/web",
|
|
255
|
+
)
|
|
256
|
+
.action(exportCommand);
|
|
257
|
+
|
|
258
|
+
const collectionCmd = program
|
|
259
|
+
.command("collection")
|
|
260
|
+
.description("Manage knowledge collections")
|
|
261
|
+
.allowExcessArguments(true)
|
|
262
|
+
.action(collectionStatusCommand);
|
|
263
|
+
|
|
264
|
+
collectionCmd
|
|
265
|
+
.command("new [name]")
|
|
266
|
+
.description("Scaffold a new collection manifest")
|
|
267
|
+
.action(collectionNewCommand);
|
|
268
|
+
|
|
269
|
+
collectionCmd
|
|
270
|
+
.command("build")
|
|
271
|
+
.description("Build collection bundles from dist artifacts")
|
|
272
|
+
.option("--harness <name>", "Build for a single harness only")
|
|
273
|
+
.action(collectionBuildCommand);
|
|
274
|
+
|
|
275
|
+
program
|
|
276
|
+
.command("import [path]")
|
|
277
|
+
.description(
|
|
278
|
+
"Import knowledge artifacts from an external source (Kiro powers, skills, or harness-native files)",
|
|
279
|
+
)
|
|
280
|
+
.option("--all", "Import all artifact subdirectories within <path>")
|
|
281
|
+
.option(
|
|
282
|
+
"--format <format>",
|
|
283
|
+
"Source format: kiro-power, kiro-skill, superpowers (default: auto-detect)",
|
|
284
|
+
)
|
|
285
|
+
.option("--harness <name>", "Scan for and import harness-native files")
|
|
286
|
+
.option("--force", "Overwrite existing artifacts without confirmation")
|
|
287
|
+
.option("--dry-run", "Show what would be imported without writing files")
|
|
288
|
+
.option(
|
|
289
|
+
"--collections <names>",
|
|
290
|
+
"Comma-separated collection names to assign to imported artifacts",
|
|
291
|
+
)
|
|
292
|
+
.option(
|
|
293
|
+
"--knowledge-dir <dir>",
|
|
294
|
+
"Target knowledge directory (default: knowledge)",
|
|
295
|
+
)
|
|
296
|
+
.option(
|
|
297
|
+
"--attribution-defaults",
|
|
298
|
+
"Accept derived upstream attribution without prompting (relationship=verbatim)",
|
|
299
|
+
)
|
|
300
|
+
.option(
|
|
301
|
+
"--no-attribution",
|
|
302
|
+
"Skip attribution capture; write no attribution block",
|
|
303
|
+
)
|
|
304
|
+
.action(async (path, options) => {
|
|
305
|
+
// If --harness is provided or no path argument, use multi-harness import
|
|
306
|
+
if (options.harness || !path) {
|
|
307
|
+
await multiHarnessImportCommand({
|
|
308
|
+
harness: options.harness as HarnessName | undefined,
|
|
309
|
+
force: options.force,
|
|
310
|
+
dryRun: options.dryRun,
|
|
311
|
+
knowledgeDir: options.knowledgeDir,
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
// Delegate to existing Kiro import (path-based)
|
|
315
|
+
await kiroImportCommand(path, options);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const attributeCmd = program
|
|
320
|
+
.command("attribute")
|
|
321
|
+
.description(
|
|
322
|
+
"Generate a NOTICES report of upstream attribution, grouped by license",
|
|
323
|
+
)
|
|
324
|
+
.option(
|
|
325
|
+
"--output <file>",
|
|
326
|
+
"Write the report to a file instead of stdout (e.g. NOTICES)",
|
|
327
|
+
)
|
|
328
|
+
.action(async (options) => {
|
|
329
|
+
const entries = await generateCatalog([...SOURCE_DIRS]);
|
|
330
|
+
const report = renderAttributionReport(entries);
|
|
331
|
+
if (options.output) {
|
|
332
|
+
await writeFile(String(options.output), report, "utf-8");
|
|
333
|
+
console.error(
|
|
334
|
+
chalk.green(`✓ Wrote attribution report to ${options.output}`),
|
|
335
|
+
);
|
|
336
|
+
} else {
|
|
337
|
+
console.log(report);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
attributeCmd
|
|
342
|
+
.command("backfill")
|
|
343
|
+
.description(
|
|
344
|
+
"Backfill attribution blocks for imported artifacts (author untouched)",
|
|
345
|
+
)
|
|
346
|
+
.option("--dry-run", "Classify and report without writing any file")
|
|
347
|
+
.action(async (options) => {
|
|
348
|
+
const summary = await runAttributionBackfill({
|
|
349
|
+
knowledgeDirs: [...SOURCE_DIRS],
|
|
350
|
+
dryRun: Boolean(options.dryRun),
|
|
351
|
+
});
|
|
352
|
+
const verb = options.dryRun ? "would backfill" : "backfilled";
|
|
353
|
+
console.error("");
|
|
354
|
+
console.error(
|
|
355
|
+
` ${chalk.green(`${summary.clean} ${verb} clean`)}, ` +
|
|
356
|
+
`${chalk.yellow(`${summary.manualReview} need manual review`)}, ` +
|
|
357
|
+
`${chalk.dim(`${summary.skipInHouse} in-house`)}, ` +
|
|
358
|
+
`${chalk.dim(`${summary.skipHasAttribution} already attributed`)}`,
|
|
359
|
+
);
|
|
360
|
+
for (const plan of summary.plans) {
|
|
361
|
+
if (plan.classification === "manual-review") {
|
|
362
|
+
console.error(
|
|
363
|
+
chalk.yellow(
|
|
364
|
+
` ⚠ ${plan.name} — ${plan.reason ?? "manual review"}`,
|
|
365
|
+
),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
console.error("");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
program
|
|
373
|
+
.command("publish")
|
|
374
|
+
.description(
|
|
375
|
+
"Publish compiled artifacts to a release backend (GitHub, S3, or HTTP)",
|
|
376
|
+
)
|
|
377
|
+
.option(
|
|
378
|
+
"--backend <name>",
|
|
379
|
+
"Named backend from kanon.config.yaml (default: github)",
|
|
380
|
+
)
|
|
381
|
+
.option(
|
|
382
|
+
"--tag <version>",
|
|
383
|
+
"Release tag, e.g. v1.2.0 (default: package.json version)",
|
|
384
|
+
)
|
|
385
|
+
.option("--dry-run", "Validate and package without uploading")
|
|
386
|
+
.option("--notes <file>", "Markdown file to use as release notes")
|
|
387
|
+
.action(publishCommand);
|
|
388
|
+
|
|
389
|
+
program
|
|
390
|
+
.command("eval [artifact]")
|
|
391
|
+
.description("Run eval tests against compiled artifacts")
|
|
392
|
+
.option("--harness <name>", "Run evals for a specific harness only")
|
|
393
|
+
.option(
|
|
394
|
+
"--rubric <name>",
|
|
395
|
+
"Run a named rubric (default: progressive-steering for kiro harness)",
|
|
396
|
+
)
|
|
397
|
+
.option(
|
|
398
|
+
"--build <dir>",
|
|
399
|
+
"Point the rubric grader at an already-compiled build directory",
|
|
400
|
+
)
|
|
401
|
+
.option(
|
|
402
|
+
"--workload <path>",
|
|
403
|
+
"Workload JSON for the rubric grader (default: the scenario matching --build)",
|
|
404
|
+
)
|
|
405
|
+
.option("--json", "Output rubric result as canonical JSON")
|
|
406
|
+
.option("--threshold <score>", "Minimum passing score (0.0–1.0)", "0.7")
|
|
407
|
+
.option("--output <path>", "Write detailed results as JSON")
|
|
408
|
+
.option("--ci", "Machine-readable output for CI pipelines")
|
|
409
|
+
.option("--provider <name>", "Run against a single provider")
|
|
410
|
+
.option("--no-context", "Skip harness context wrapping")
|
|
411
|
+
.option("--init <artifact>", "Scaffold eval suite for an artifact")
|
|
412
|
+
.option(
|
|
413
|
+
"--record",
|
|
414
|
+
"Append results to evals/history.jsonl for trend tracking",
|
|
415
|
+
)
|
|
416
|
+
.option("--trend", "Show score progression from evals/history.jsonl")
|
|
417
|
+
.option("--mutation", "Run mutation testing on adapter source files")
|
|
418
|
+
.option(
|
|
419
|
+
"--delta",
|
|
420
|
+
"Only mutate files changed since last mutation run (nightly-delta strategy)",
|
|
421
|
+
)
|
|
422
|
+
.action(evalCommand);
|
|
423
|
+
|
|
424
|
+
program
|
|
425
|
+
.command("upgrade")
|
|
426
|
+
.description("Upgrade installed artifacts to their latest versions")
|
|
427
|
+
.option("--force", "Upgrade without confirmation prompts")
|
|
428
|
+
.option("--dry-run", "Show what would be upgraded without modifying files")
|
|
429
|
+
.option(
|
|
430
|
+
"--project <name>",
|
|
431
|
+
"Upgrade only within a specific workspace project",
|
|
432
|
+
)
|
|
433
|
+
.action(async (options) => {
|
|
434
|
+
try {
|
|
435
|
+
await upgradeCommand({
|
|
436
|
+
force: options.force,
|
|
437
|
+
dryRun: options.dryRun,
|
|
438
|
+
project: options.project,
|
|
439
|
+
});
|
|
440
|
+
} catch (err: unknown) {
|
|
441
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
442
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
443
|
+
console.error(
|
|
444
|
+
chalk.dim(
|
|
445
|
+
" Run `kanon install` to install artifacts first, then retry.",
|
|
446
|
+
),
|
|
447
|
+
);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
program
|
|
453
|
+
.command("temper <artifact>")
|
|
454
|
+
.description(
|
|
455
|
+
"Preview the compiled AI experience for an artifact-harness pair",
|
|
456
|
+
)
|
|
457
|
+
.option("--harness <name>", "Target harness (default: kiro)")
|
|
458
|
+
.option("--compare", "Compare artifact across all targeted harnesses")
|
|
459
|
+
.option("--web", "Open interactive web preview in browser")
|
|
460
|
+
.option("--json", "Output as JSON conforming to TemperOutputSchema")
|
|
461
|
+
.option("--no-color", "Disable color output for deterministic results")
|
|
462
|
+
.action(async (artifact, options) => {
|
|
463
|
+
const harness = (options.harness ?? "kiro") as HarnessName;
|
|
464
|
+
|
|
465
|
+
if (!SUPPORTED_HARNESSES.includes(harness)) {
|
|
466
|
+
console.error(chalk.red(`Error: Unknown harness "${harness}".`));
|
|
467
|
+
console.error(
|
|
468
|
+
chalk.dim(` Supported harnesses: ${SUPPORTED_HARNESSES.join(", ")}`),
|
|
469
|
+
);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
if (options.compare) {
|
|
475
|
+
const result = await renderComparison({
|
|
476
|
+
artifactName: artifact,
|
|
477
|
+
harnesses: [...SUPPORTED_HARNESSES],
|
|
478
|
+
});
|
|
479
|
+
console.log(formatComparisonOutput(result, !options.color));
|
|
480
|
+
} else if (options.web) {
|
|
481
|
+
const output = await renderTemper({
|
|
482
|
+
artifactName: artifact,
|
|
483
|
+
harness,
|
|
484
|
+
});
|
|
485
|
+
await startTemperServer(output);
|
|
486
|
+
} else {
|
|
487
|
+
const output = await renderTemper({
|
|
488
|
+
artifactName: artifact,
|
|
489
|
+
harness,
|
|
490
|
+
noColor: !options.color,
|
|
491
|
+
});
|
|
492
|
+
if (options.json) {
|
|
493
|
+
console.log(formatJsonOutput(output));
|
|
494
|
+
} else {
|
|
495
|
+
console.log(formatTerminalOutput(output, !options.color));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch (err: unknown) {
|
|
499
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
500
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
501
|
+
console.error(
|
|
502
|
+
chalk.dim(
|
|
503
|
+
" Run `kanon catalog generate` to see available artifacts.",
|
|
504
|
+
),
|
|
505
|
+
);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Kiro Spec coordination — read/write COORDINATION.md + tasks.md across agents
|
|
511
|
+
const specCmd = program
|
|
512
|
+
.command("spec")
|
|
513
|
+
.description(
|
|
514
|
+
"Coordinate multi-agent work on Kiro Specs (.kiro/specs/) via COORDINATION.md",
|
|
515
|
+
)
|
|
516
|
+
.action(() => specListCommand());
|
|
517
|
+
|
|
518
|
+
specCmd
|
|
519
|
+
.command("list")
|
|
520
|
+
.description("List specs with type, workflow, and task progress")
|
|
521
|
+
.option("--json", "Output as JSON")
|
|
522
|
+
.action((options) => specListCommand(options));
|
|
523
|
+
|
|
524
|
+
specCmd
|
|
525
|
+
.command("status [spec]")
|
|
526
|
+
.description("Show task ownership, progress, deps, leases, and handoffs")
|
|
527
|
+
.option("--json", "Output as JSON")
|
|
528
|
+
.action((spec, options) => specStatusCommand(spec, options));
|
|
529
|
+
|
|
530
|
+
specCmd
|
|
531
|
+
.command("next [spec]")
|
|
532
|
+
.alias("channel")
|
|
533
|
+
.description(
|
|
534
|
+
"Select and claim the next actionable task (deps satisfied, unclaimed) for an agent",
|
|
535
|
+
)
|
|
536
|
+
.option("--agent <name>", "Agent requesting work")
|
|
537
|
+
.option("--lease <minutes>", "Lease duration in minutes")
|
|
538
|
+
.option("--dry-run", "Report the next task without claiming it")
|
|
539
|
+
.option("--json", "Output as JSON")
|
|
540
|
+
.action((spec, options) => specNextCommand(spec, options));
|
|
541
|
+
|
|
542
|
+
specCmd
|
|
543
|
+
.command("claim [spec] [taskId]")
|
|
544
|
+
.description(
|
|
545
|
+
"Claim a task for an agent (fails if already owned or blocked)",
|
|
546
|
+
)
|
|
547
|
+
.option("--agent <name>", "Agent claiming the task")
|
|
548
|
+
.option("--force", "Take over a task already owned by another agent")
|
|
549
|
+
.option("--lease <minutes>", "Lease duration in minutes")
|
|
550
|
+
.option("--ignore-deps", "Claim even if dependencies are unmet")
|
|
551
|
+
.action((spec, taskId, options) => specClaimCommand(spec, taskId, options));
|
|
552
|
+
|
|
553
|
+
specCmd
|
|
554
|
+
.command("release [spec] [taskId]")
|
|
555
|
+
.description("Release a claimed task back to open")
|
|
556
|
+
.action((spec, taskId) => specReleaseCommand(spec, taskId));
|
|
557
|
+
|
|
558
|
+
specCmd
|
|
559
|
+
.command("done [spec] [taskId]")
|
|
560
|
+
.description(
|
|
561
|
+
"Mark a task complete: check the box in tasks.md and update coordination",
|
|
562
|
+
)
|
|
563
|
+
.option("--agent <name>", "Agent completing the task")
|
|
564
|
+
.action((spec, taskId, options) => specDoneCommand(spec, taskId, options));
|
|
565
|
+
|
|
566
|
+
specCmd
|
|
567
|
+
.command("reconcile [spec]")
|
|
568
|
+
.description(
|
|
569
|
+
"Sync coordination rows to tasks.md checkboxes (adds new, marks done)",
|
|
570
|
+
)
|
|
571
|
+
.action(specReconcileCommand);
|
|
572
|
+
|
|
573
|
+
specCmd
|
|
574
|
+
.command("handoff [spec] [message]")
|
|
575
|
+
.description("Record a handoff note from one agent to another")
|
|
576
|
+
.option("--from <agent>", "Agent handing off")
|
|
577
|
+
.option("--to <agent>", "Agent receiving")
|
|
578
|
+
.action((spec, message, options) =>
|
|
579
|
+
specHandoffCommand(spec, message, options),
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
// Register guild commands
|
|
583
|
+
registerGuildCommands(program);
|
|
584
|
+
|
|
585
|
+
// Register rosetta commands
|
|
586
|
+
registerRosettaCommands(program);
|
|
587
|
+
|
|
588
|
+
// Register `kanon help [command]` subcommand
|
|
589
|
+
program
|
|
590
|
+
.command("help [command]")
|
|
591
|
+
.description("Show help for a command")
|
|
592
|
+
.action((cmdName?: string) => {
|
|
593
|
+
if (!cmdName) {
|
|
594
|
+
// No argument — show root help
|
|
595
|
+
const commands = program.commands
|
|
596
|
+
.filter((c) => c.name() !== "help")
|
|
597
|
+
.map((cmd) => ({
|
|
598
|
+
name: cmd.name() + (cmd.usage() ? ` ${cmd.usage()}` : ""),
|
|
599
|
+
description: cmd.description(),
|
|
600
|
+
}));
|
|
601
|
+
commands.push({
|
|
602
|
+
name: "help [command]",
|
|
603
|
+
description: "Show help for a command",
|
|
604
|
+
});
|
|
605
|
+
console.log(renderRootHelp(commands, { useColor }));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Find matching command
|
|
610
|
+
const targetCmd = program.commands.find((c) => c.name() === cmdName);
|
|
611
|
+
if (targetCmd) {
|
|
612
|
+
console.log(targetCmd.helpInformation());
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Unknown command — suggest typo fix
|
|
617
|
+
const validNames = program.commands
|
|
618
|
+
.filter((c) => c.name() !== "help")
|
|
619
|
+
.map((c) => c.name());
|
|
620
|
+
const suggestion = suggestCommand(cmdName, validNames);
|
|
621
|
+
|
|
622
|
+
console.error(`error: unknown command '${cmdName}'`);
|
|
623
|
+
if (suggestion) {
|
|
624
|
+
console.error(`Did you mean "${suggestion}"?`);
|
|
625
|
+
}
|
|
626
|
+
console.error("");
|
|
627
|
+
console.error("Available commands:");
|
|
628
|
+
for (const name of validNames) {
|
|
629
|
+
console.error(` ${name}`);
|
|
630
|
+
}
|
|
631
|
+
process.exit(1);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Custom version option using renderVersion
|
|
635
|
+
program.option("-V, --version", "Output version information");
|
|
636
|
+
program.on("option:version", () => {
|
|
637
|
+
console.log(renderVersion("0.2.0", { useColor }));
|
|
638
|
+
process.exit(0);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Override helpInformation() on the root program
|
|
642
|
+
program.helpInformation = () => {
|
|
643
|
+
const commands: RootCommand[] = program.commands
|
|
644
|
+
.filter((cmd) => cmd.name() !== "help")
|
|
645
|
+
.map((cmd) => {
|
|
646
|
+
// Strip [options] from usage — it's noise at the overview level
|
|
647
|
+
const rawUsage = cmd.usage() ?? "";
|
|
648
|
+
const cleanUsage = rawUsage
|
|
649
|
+
.replace(/\[options\]\s*/g, "")
|
|
650
|
+
.replace(/\[options\]$/g, "")
|
|
651
|
+
.trim();
|
|
652
|
+
const name = cleanUsage ? `${cmd.name()} ${cleanUsage}` : cmd.name();
|
|
653
|
+
|
|
654
|
+
const subcommands =
|
|
655
|
+
cmd.commands.length > 0
|
|
656
|
+
? cmd.commands.map((sub) => ({
|
|
657
|
+
name: sub.name(),
|
|
658
|
+
description: sub.description(),
|
|
659
|
+
}))
|
|
660
|
+
: undefined;
|
|
661
|
+
|
|
662
|
+
return { name, description: cmd.description(), subcommands };
|
|
663
|
+
});
|
|
664
|
+
// Add help at the end
|
|
665
|
+
commands.push({
|
|
666
|
+
name: "help [command]",
|
|
667
|
+
description: "Show help for a command",
|
|
668
|
+
});
|
|
669
|
+
return renderRootHelp(commands, { useColor });
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// Override helpInformation() on each subcommand
|
|
673
|
+
for (const cmd of program.commands) {
|
|
674
|
+
const cmdName = cmd.name();
|
|
675
|
+
cmd.helpInformation = () => {
|
|
676
|
+
const opts = cmd.options.map((o) => ({
|
|
677
|
+
flags: o.flags,
|
|
678
|
+
description: o.description,
|
|
679
|
+
}));
|
|
680
|
+
const meta = commandMetaRegistry[cmdName];
|
|
681
|
+
const subs =
|
|
682
|
+
cmd.commands.length > 0
|
|
683
|
+
? cmd.commands.map((sub) => ({
|
|
684
|
+
name: sub.name(),
|
|
685
|
+
description: sub.description(),
|
|
686
|
+
}))
|
|
687
|
+
: undefined;
|
|
688
|
+
return renderCommandHelp(
|
|
689
|
+
cmdName,
|
|
690
|
+
cmd.description(),
|
|
691
|
+
`kanon ${cmdName} ${cmd.usage()}`.trim(),
|
|
692
|
+
opts,
|
|
693
|
+
meta,
|
|
694
|
+
{ useColor },
|
|
695
|
+
subs,
|
|
696
|
+
);
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// Handle subcommands (e.g., catalog generate, catalog browse)
|
|
700
|
+
if (cmd.commands && cmd.commands.length > 0) {
|
|
701
|
+
for (const sub of cmd.commands) {
|
|
702
|
+
const subName = `${cmdName} ${sub.name()}`;
|
|
703
|
+
sub.helpInformation = () => {
|
|
704
|
+
const opts = sub.options.map((o) => ({
|
|
705
|
+
flags: o.flags,
|
|
706
|
+
description: o.description,
|
|
707
|
+
}));
|
|
708
|
+
const meta = commandMetaRegistry[subName];
|
|
709
|
+
const nestedSubs =
|
|
710
|
+
sub.commands.length > 0
|
|
711
|
+
? sub.commands.map((s) => ({
|
|
712
|
+
name: s.name(),
|
|
713
|
+
description: s.description(),
|
|
714
|
+
}))
|
|
715
|
+
: undefined;
|
|
716
|
+
return renderCommandHelp(
|
|
717
|
+
subName,
|
|
718
|
+
sub.description(),
|
|
719
|
+
`kanon ${subName} ${sub.usage()}`.trim(),
|
|
720
|
+
opts,
|
|
721
|
+
meta,
|
|
722
|
+
{ useColor },
|
|
723
|
+
nestedSubs,
|
|
724
|
+
);
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// Handle third-level subcommands (e.g., guild hook install)
|
|
728
|
+
if (sub.commands && sub.commands.length > 0) {
|
|
729
|
+
for (const nested of sub.commands) {
|
|
730
|
+
const nestedName = `${subName} ${nested.name()}`;
|
|
731
|
+
nested.helpInformation = () => {
|
|
732
|
+
const opts = nested.options.map((o) => ({
|
|
733
|
+
flags: o.flags,
|
|
734
|
+
description: o.description,
|
|
735
|
+
}));
|
|
736
|
+
const meta = commandMetaRegistry[nestedName];
|
|
737
|
+
return renderCommandHelp(
|
|
738
|
+
nestedName,
|
|
739
|
+
nested.description(),
|
|
740
|
+
`kanon ${nestedName} ${nested.usage()}`.trim(),
|
|
741
|
+
opts,
|
|
742
|
+
meta,
|
|
743
|
+
{ useColor },
|
|
744
|
+
);
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Handle unknown commands with typo suggestions
|
|
753
|
+
program.on("command:*", (operands: string[]) => {
|
|
754
|
+
const unknown = operands[0];
|
|
755
|
+
const validNames = program.commands
|
|
756
|
+
.filter((c) => c.name() !== "help")
|
|
757
|
+
.map((c) => c.name());
|
|
758
|
+
const suggestion = suggestCommand(unknown, validNames);
|
|
759
|
+
|
|
760
|
+
console.error(`error: unknown command '${unknown}'`);
|
|
761
|
+
if (suggestion) {
|
|
762
|
+
console.error(`Did you mean "${suggestion}"?`);
|
|
763
|
+
}
|
|
764
|
+
console.error("");
|
|
765
|
+
console.error("Available commands:");
|
|
766
|
+
for (const name of validNames) {
|
|
767
|
+
console.error(` ${name}`);
|
|
768
|
+
}
|
|
769
|
+
process.exit(1);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
program.parse();
|
|
773
|
+
}
|