kodelyth-ecc 1.5.10 → 1.7.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.
- package/.github/workflows/publish.yml +19 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +475 -0
- package/CLAUDE.md +48 -16
- package/README.md +285 -81
- package/VERSION +1 -1
- package/actions/ecc-review/README.md +243 -0
- package/actions/ecc-review/action.yml +158 -0
- package/actions/ecc-review/post-comment.js +171 -0
- package/actions/ecc-review/run-review.js +285 -0
- package/agents/backdoor-hunter.md +260 -0
- package/agents/chaos-engineer.md +251 -0
- package/agents/code-stealer-detector.md +228 -0
- package/agents/jailbreak-tester.md +222 -0
- package/agents/license-violation-finder.md +212 -0
- package/agents/prompt-injection-hunter.md +126 -0
- package/agents/secret-hunter.md +209 -0
- package/agents/supply-chain-auditor.md +195 -0
- package/bin/kodelyth-ecc.js +875 -1
- package/bundles/enterprise.md +172 -0
- package/bundles/indie-hacker.md +106 -0
- package/bundles/red-team.md +138 -0
- package/cat +0 -0
- package/commands/dashboard.md +67 -0
- package/commands/devil-mode.md +121 -0
- package/commands/memory-evolve.md +71 -0
- package/commands/replay.md +61 -0
- package/commands/route-model.md +48 -0
- package/commands/swarm.md +68 -0
- package/commands/verify-supply-chain.md +59 -0
- package/docs/dashboard.md +211 -0
- package/docs/evolve.md +303 -0
- package/docs/mcp-clients.md +167 -0
- package/docs/mcp.md +178 -0
- package/docs/replay.md +244 -0
- package/docs/supply-chain.md +207 -0
- package/docs/swarm.md +243 -0
- package/hooks/hooks.json +52 -0
- package/hooks/memory/auto-recall.js +29 -1
- package/hooks/safety/README.md +124 -0
- package/hooks/safety/lib/patterns.js +179 -0
- package/hooks/safety/prompt-injection-guard.js +179 -0
- package/hooks/safety/token-budget.js +229 -0
- package/install.ps1 +199 -2
- package/install.sh +285 -3
- package/package.json +30 -4
- package/rules/common/agent-intent-routing.md +182 -0
- package/rules/common/cost-aware-model-routing.md +152 -0
- package/scripts/dashboard/data.js +353 -0
- package/scripts/dashboard/server.js +324 -0
- package/scripts/dashboard/static/index.html +645 -0
- package/scripts/evolve/analyze.js +303 -0
- package/scripts/evolve/proposals.js +162 -0
- package/scripts/evolve/stats.js +219 -0
- package/scripts/mcp/catalog.js +244 -0
- package/scripts/mcp/client.js +181 -0
- package/scripts/mcp/prompts.js +133 -0
- package/scripts/mcp/resources.js +94 -0
- package/scripts/mcp/server.js +153 -0
- package/scripts/mcp/tools.js +465 -0
- package/scripts/replay/bundle.js +191 -0
- package/scripts/replay/replay.js +107 -0
- package/scripts/router/classify.js +232 -0
- package/scripts/supply-chain/manifest.js +155 -0
- package/scripts/supply-chain/sbom.js +202 -0
- package/scripts/supply-chain/verify.js +102 -0
- package/scripts/swarm/build-plan.js +193 -0
- package/skills/cost-aware-model-routing/SKILL.md +153 -0
- package/skills/kodelyth-quickstart/SKILL.md +7 -0
- package/skills/observability-dashboard/SKILL.md +119 -0
- package/skills/self-evolving-memory/SKILL.md +175 -0
- package/skills/session-replay/SKILL.md +199 -0
- package/skills/supply-chain-verification/SKILL.md +201 -0
- package/skills/swarm-orchestrator/SKILL.md +177 -0
- package/social/card-install.svg +1 -1
- package/social/facebook-group/POST.md +121 -0
- package/social/facebook-group/fb-1-3am-debug.png +0 -0
- package/social/facebook-group/fb-1-3am-debug.svg +97 -0
- package/social/facebook-group/fb-2-cpu-upgrade.png +0 -0
- package/social/facebook-group/fb-2-cpu-upgrade.svg +132 -0
- package/social/facebook-group/fb-3-before-after.png +0 -0
- package/social/facebook-group/fb-3-before-after.svg +94 -0
- package/social/facebook-v150.svg +6 -6
- package/social/github-social-preview.svg +119 -100
- package/social/readme-hero.svg +12 -12
- package/social/section-agents.svg +57 -0
- package/social/section-author.svg +54 -0
- package/social/section-dashboard.svg +59 -0
- package/social/section-devil.svg +54 -0
- package/social/section-hooks.svg +51 -0
- package/social/section-install.svg +42 -0
- package/social/section-learning.svg +52 -0
- package/social/section-mcp.svg +46 -0
- package/social/section-memory.svg +57 -0
- package/social/section-parallel.svg +72 -0
- package/social/section-routing.svg +51 -0
- package/social/x-card-agents-grid.svg +6 -6
- package/tests/dashboard/data.test.js +235 -0
- package/tests/dashboard/server.test.js +240 -0
- package/tests/evolve/analyze.test.js +169 -0
- package/tests/evolve/proposals.test.js +173 -0
- package/tests/evolve/stats.test.js +167 -0
- package/tests/mcp/catalog.test.js +98 -0
- package/tests/mcp/client.test.js +113 -0
- package/tests/mcp/resources-prompts.test.js +70 -0
- package/tests/mcp/tools.test.js +159 -0
- package/tests/replay/bundle.test.js +181 -0
- package/tests/replay/replay.test.js +131 -0
- package/tests/router/classify.test.js +164 -0
- package/tests/safety/patterns.test.js +94 -0
- package/tests/safety/prompt-injection-guard.test.js +111 -0
- package/tests/safety/token-budget.test.js +119 -0
- package/tests/supply-chain/manifest.test.js +147 -0
- package/tests/supply-chain/sbom.test.js +170 -0
- package/tests/supply-chain/verify.test.js +146 -0
- package/tests/swarm/build-plan.test.js +188 -0
- package/wiki/Agent-Reference.md +58 -7
- package/wiki/FAQ.md +204 -7
- package/wiki/Home.md +104 -29
- package/wiki/Hook-Reference.md +1 -1
- package/wiki/Installation-Guide.md +109 -6
- package/wiki/Platform-Support.md +189 -72
- package/wiki/Skill-Reference.md +101 -6
- package/.agent/rules/README.md +0 -108
- package/.agent/rules/agents.md +0 -94
- package/.agent/rules/code-review.md +0 -124
- package/.agent/rules/coding-style.md +0 -122
- package/.agent/rules/design-quality.md +0 -63
- package/.agent/rules/development-workflow.md +0 -44
- package/.agent/rules/git-mastery.md +0 -366
- package/.agent/rules/git-workflow.md +0 -24
- package/.agent/rules/hooks.md +0 -25
- package/.agent/rules/kodelyth-always-on.md +0 -63
- package/.agent/rules/kodelyth-quickstart.md +0 -207
- package/.agent/rules/observability.md +0 -390
- package/.agent/rules/patterns.md +0 -50
- package/.agent/rules/performance.md +0 -178
- package/.agent/rules/security.md +0 -58
- package/.agent/rules/smart-debug.md +0 -241
- package/.agent/rules/soul.md +0 -32
- package/.agent/rules/testing.md +0 -46
- package/.agent/skills/api-guardian.md +0 -279
- package/.agent/skills/architect.md +0 -211
- package/.agent/skills/build-error-resolver.md +0 -114
- package/.agent/skills/chief-of-staff.md +0 -151
- package/.agent/skills/code-architect.md +0 -71
- package/.agent/skills/code-explorer.md +0 -69
- package/.agent/skills/code-reviewer.md +0 -237
- package/.agent/skills/code-simplifier.md +0 -47
- package/.agent/skills/comment-analyzer.md +0 -45
- package/.agent/skills/conversation-analyzer.md +0 -52
- package/.agent/skills/cpp-build-resolver.md +0 -90
- package/.agent/skills/cpp-reviewer.md +0 -72
- package/.agent/skills/csharp-reviewer.md +0 -101
- package/.agent/skills/dart-build-resolver.md +0 -201
- package/.agent/skills/database-reviewer.md +0 -91
- package/.agent/skills/debug-detective.md +0 -409
- package/.agent/skills/doc-updater.md +0 -107
- package/.agent/skills/docs-lookup.md +0 -68
- package/.agent/skills/e2e-runner.md +0 -107
- package/.agent/skills/flutter-reviewer.md +0 -243
- package/.agent/skills/gan-evaluator.md +0 -209
- package/.agent/skills/gan-generator.md +0 -131
- package/.agent/skills/gan-planner.md +0 -99
- package/.agent/skills/go-build-resolver.md +0 -94
- package/.agent/skills/go-reviewer.md +0 -76
- package/.agent/skills/harness-optimizer.md +0 -35
- package/.agent/skills/healthcare-reviewer.md +0 -83
- package/.agent/skills/java-build-resolver.md +0 -153
- package/.agent/skills/java-reviewer.md +0 -92
- package/.agent/skills/kodelyth-advisor.md +0 -172
- package/.agent/skills/kotlin-build-resolver.md +0 -118
- package/.agent/skills/kotlin-reviewer.md +0 -159
- package/.agent/skills/loop-operator.md +0 -36
- package/.agent/skills/migration-guide.md +0 -368
- package/.agent/skills/opensource-forker.md +0 -198
- package/.agent/skills/opensource-packager.md +0 -249
- package/.agent/skills/opensource-sanitizer.md +0 -188
- package/.agent/skills/pair-programmer.md +0 -260
- package/.agent/skills/performance-optimizer.md +0 -446
- package/.agent/skills/planner.md +0 -212
- package/.agent/skills/pr-test-analyzer.md +0 -45
- package/.agent/skills/python-reviewer.md +0 -98
- package/.agent/skills/pytorch-build-resolver.md +0 -120
- package/.agent/skills/refactor-cleaner.md +0 -85
- package/.agent/skills/rust-build-resolver.md +0 -148
- package/.agent/skills/rust-reviewer.md +0 -94
- package/.agent/skills/security-reviewer.md +0 -108
- package/.agent/skills/seo-specialist.md +0 -62
- package/.agent/skills/silent-failure-hunter.md +0 -50
- package/.agent/skills/tdd-guide.md +0 -91
- package/.agent/skills/type-design-analyzer.md +0 -41
- package/.agent/skills/typescript-reviewer.md +0 -112
- package/.agent/skills/ux-reviewer.md +0 -483
- package/.agent/workflows/agent-sort.md +0 -23
- package/.agent/workflows/aside.md +0 -164
- package/.agent/workflows/build-fix.md +0 -62
- package/.agent/workflows/checkpoint.md +0 -74
- package/.agent/workflows/claw.md +0 -23
- package/.agent/workflows/code-review.md +0 -289
- package/.agent/workflows/context-budget.md +0 -23
- package/.agent/workflows/cpp-build.md +0 -173
- package/.agent/workflows/cpp-review.md +0 -132
- package/.agent/workflows/cpp-test.md +0 -251
- package/.agent/workflows/devfleet.md +0 -23
- package/.agent/workflows/docs.md +0 -23
- package/.agent/workflows/e2e.md +0 -268
- package/.agent/workflows/eval.md +0 -23
- package/.agent/workflows/evolve.md +0 -178
- package/.agent/workflows/feature-dev.md +0 -49
- package/.agent/workflows/flutter-build.md +0 -164
- package/.agent/workflows/flutter-review.md +0 -116
- package/.agent/workflows/flutter-test.md +0 -144
- package/.agent/workflows/gan-build.md +0 -99
- package/.agent/workflows/gan-design.md +0 -35
- package/.agent/workflows/go-build.md +0 -183
- package/.agent/workflows/go-review.md +0 -148
- package/.agent/workflows/go-test.md +0 -268
- package/.agent/workflows/gradle-build.md +0 -70
- package/.agent/workflows/harness-audit.md +0 -73
- package/.agent/workflows/hookify-configure.md +0 -14
- package/.agent/workflows/hookify-help.md +0 -46
- package/.agent/workflows/hookify-list.md +0 -21
- package/.agent/workflows/hookify.md +0 -50
- package/.agent/workflows/instinct-export.md +0 -66
- package/.agent/workflows/instinct-import.md +0 -114
- package/.agent/workflows/instinct-status.md +0 -59
- package/.agent/workflows/jira.md +0 -106
- package/.agent/workflows/kotlin-build.md +0 -174
- package/.agent/workflows/kotlin-review.md +0 -140
- package/.agent/workflows/kotlin-test.md +0 -312
- package/.agent/workflows/learn-eval.md +0 -116
- package/.agent/workflows/learn.md +0 -70
- package/.agent/workflows/loop-start.md +0 -32
- package/.agent/workflows/loop-status.md +0 -24
- package/.agent/workflows/model-route.md +0 -26
- package/.agent/workflows/multi-backend.md +0 -158
- package/.agent/workflows/multi-execute.md +0 -315
- package/.agent/workflows/multi-frontend.md +0 -158
- package/.agent/workflows/multi-plan.md +0 -268
- package/.agent/workflows/multi-workflow.md +0 -191
- package/.agent/workflows/orchestrate.md +0 -135
- package/.agent/workflows/plan.md +0 -117
- package/.agent/workflows/pm2.md +0 -272
- package/.agent/workflows/projects.md +0 -39
- package/.agent/workflows/promote.md +0 -41
- package/.agent/workflows/prompt-optimize.md +0 -23
- package/.agent/workflows/prp-commit.md +0 -112
- package/.agent/workflows/prp-implement.md +0 -385
- package/.agent/workflows/prp-plan.md +0 -502
- package/.agent/workflows/prp-pr.md +0 -184
- package/.agent/workflows/prp-prd.md +0 -447
- package/.agent/workflows/prune.md +0 -31
- package/.agent/workflows/python-review.md +0 -297
- package/.agent/workflows/quality-gate.md +0 -29
- package/.agent/workflows/refactor-clean.md +0 -80
- package/.agent/workflows/resume-session.md +0 -156
- package/.agent/workflows/review-pr.md +0 -37
- package/.agent/workflows/rules-distill.md +0 -20
- package/.agent/workflows/rust-build.md +0 -187
- package/.agent/workflows/rust-review.md +0 -142
- package/.agent/workflows/rust-test.md +0 -308
- package/.agent/workflows/santa-loop.md +0 -175
- package/.agent/workflows/save-session.md +0 -275
- package/.agent/workflows/sessions.md +0 -333
- package/.agent/workflows/setup-pm.md +0 -80
- package/.agent/workflows/skill-create.md +0 -174
- package/.agent/workflows/skill-health.md +0 -54
- package/.agent/workflows/tdd.md +0 -231
- package/.agent/workflows/test-coverage.md +0 -69
- package/.agent/workflows/update-codemaps.md +0 -72
- package/.agent/workflows/update-docs.md +0 -84
- package/.agent/workflows/verify.md +0 -23
package/bin/kodelyth-ecc.js
CHANGED
|
@@ -28,13 +28,873 @@ const args = process.argv.slice(2).filter((a, i, arr) => {
|
|
|
28
28
|
});
|
|
29
29
|
const isWin = os.platform() === 'win32';
|
|
30
30
|
|
|
31
|
+
// ── Subcommand: mcp (start MCP server over stdio) ────────────────────────────
|
|
32
|
+
// Usage: npx kodelyth-ecc mcp
|
|
33
|
+
// Exposes 70 agents, 194 skills, 97 commands, the routing rule, and the local
|
|
34
|
+
// memory store to any MCP-compatible client (Claude Desktop, LangGraph, etc.).
|
|
35
|
+
if (args[0] === 'mcp') {
|
|
36
|
+
// Hand off entirely to the MCP server — it owns stdio from here on.
|
|
37
|
+
require(path.join(ROOT, 'scripts', 'mcp', 'server.js')).main()
|
|
38
|
+
.catch(err => {
|
|
39
|
+
process.stderr.write(`[kodelyth-mcp] fatal: ${err.stack || err.message}\n`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Subcommand: mcp-* (consume external MCP servers) ──────────────────────────
|
|
46
|
+
// Usage:
|
|
47
|
+
// kodelyth-ecc mcp-add <name> -- <command> [args...]
|
|
48
|
+
// kodelyth-ecc mcp-add github -- npx -y @modelcontextprotocol/server-github
|
|
49
|
+
// kodelyth-ecc mcp-add postgres --env DB_URL=... -- npx -y @modelcontextprotocol/server-postgres
|
|
50
|
+
// kodelyth-ecc mcp-list
|
|
51
|
+
// kodelyth-ecc mcp-remove <name>
|
|
52
|
+
// kodelyth-ecc mcp-tools <name>
|
|
53
|
+
// kodelyth-ecc mcp-call <name> <tool> [--json '{"arg":"value"}']
|
|
54
|
+
if (args[0] && args[0].startsWith('mcp-')) {
|
|
55
|
+
(async () => {
|
|
56
|
+
const sub = args[0].slice(4);
|
|
57
|
+
const client = require(path.join(ROOT, 'scripts', 'mcp', 'client.js'));
|
|
58
|
+
const rest = args.slice(1);
|
|
59
|
+
|
|
60
|
+
function findSeparator(arr) {
|
|
61
|
+
const i = arr.indexOf('--');
|
|
62
|
+
return i;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
if (sub === 'list') {
|
|
67
|
+
const servers = client.listServers();
|
|
68
|
+
if (servers.length === 0) {
|
|
69
|
+
console.log('No MCP servers registered. Use `kodelyth-ecc mcp-add <name> -- <command> [args...]` to add one.');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const s of servers) {
|
|
73
|
+
const argList = s.args && s.args.length ? ' ' + s.args.join(' ') : '';
|
|
74
|
+
const envKeys = Object.keys(s.env || {}).join(', ');
|
|
75
|
+
console.log(`• ${s.name}`);
|
|
76
|
+
console.log(` cmd: ${s.command}${argList}`);
|
|
77
|
+
if (envKeys) console.log(` env: ${envKeys}`);
|
|
78
|
+
if (s.description) console.log(` desc: ${s.description}`);
|
|
79
|
+
console.log(` added: ${s.added_at}`);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (sub === 'add') {
|
|
85
|
+
// Parse: <name> [--env KEY=VAL]* [--desc "..."] -- <command> [args...]
|
|
86
|
+
const sepIdx = findSeparator(rest);
|
|
87
|
+
if (sepIdx < 0) throw new Error('mcp-add requires `--` before the command. Example: kodelyth-ecc mcp-add github -- npx -y @modelcontextprotocol/server-github');
|
|
88
|
+
const head = rest.slice(0, sepIdx);
|
|
89
|
+
const tail = rest.slice(sepIdx + 1);
|
|
90
|
+
if (head.length === 0) throw new Error('mcp-add: missing <name>');
|
|
91
|
+
if (tail.length === 0) throw new Error('mcp-add: missing command after `--`');
|
|
92
|
+
|
|
93
|
+
const name = head[0];
|
|
94
|
+
const env = {};
|
|
95
|
+
let description = '';
|
|
96
|
+
for (let i = 1; i < head.length; i++) {
|
|
97
|
+
const a = head[i];
|
|
98
|
+
if (a === '--env') { const kv = head[++i] || ''; const eq = kv.indexOf('='); if (eq > 0) env[kv.slice(0, eq)] = kv.slice(eq + 1); continue; }
|
|
99
|
+
if (a === '--desc' || a === '--description') { description = head[++i] || ''; continue; }
|
|
100
|
+
}
|
|
101
|
+
const command = tail[0];
|
|
102
|
+
const argv = tail.slice(1);
|
|
103
|
+
|
|
104
|
+
const spec = client.addServer({ name, command, args: argv, env, description });
|
|
105
|
+
console.log(`✓ registered "${spec.name}" (cmd: ${spec.command} ${spec.args.join(' ')})`);
|
|
106
|
+
console.log(` Try: kodelyth-ecc mcp-tools ${spec.name}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (sub === 'remove' || sub === 'rm') {
|
|
111
|
+
if (rest.length === 0) throw new Error('mcp-remove: missing <name>');
|
|
112
|
+
const ok = client.removeServer(rest[0]);
|
|
113
|
+
console.log(ok ? `✓ removed "${rest[0]}"` : `✗ "${rest[0]}" was not registered`);
|
|
114
|
+
process.exit(ok ? 0 : 1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (sub === 'tools') {
|
|
118
|
+
if (rest.length === 0) throw new Error('mcp-tools: missing <name>');
|
|
119
|
+
const out = await client.listTools(rest[0]);
|
|
120
|
+
console.log(`tools on "${rest[0]}" (${out.tools.length}):`);
|
|
121
|
+
for (const t of out.tools) {
|
|
122
|
+
console.log(` • ${t.name}${t.description ? ' — ' + String(t.description).split('\n')[0].slice(0, 100) : ''}`);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (sub === 'resources') {
|
|
128
|
+
if (rest.length === 0) throw new Error('mcp-resources: missing <name>');
|
|
129
|
+
const out = await client.listResources(rest[0]);
|
|
130
|
+
console.log(`resources on "${rest[0]}" (${out.resources.length}):`);
|
|
131
|
+
for (const r of out.resources.slice(0, 30)) {
|
|
132
|
+
console.log(` • ${r.uri}`);
|
|
133
|
+
}
|
|
134
|
+
if (out.resources.length > 30) console.log(` …and ${out.resources.length - 30} more.`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (sub === 'prompts') {
|
|
139
|
+
if (rest.length === 0) throw new Error('mcp-prompts: missing <name>');
|
|
140
|
+
const out = await client.listPrompts(rest[0]);
|
|
141
|
+
console.log(`prompts on "${rest[0]}" (${out.prompts.length}):`);
|
|
142
|
+
for (const p of out.prompts) {
|
|
143
|
+
console.log(` • ${p.name}${p.description ? ' — ' + p.description.slice(0, 100) : ''}`);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (sub === 'call') {
|
|
149
|
+
if (rest.length < 2) throw new Error('mcp-call: usage: mcp-call <name> <tool> [--json \'{"arg":"value"}\']');
|
|
150
|
+
const [name, tool, ...flags] = rest;
|
|
151
|
+
let toolArgs = {};
|
|
152
|
+
for (let i = 0; i < flags.length; i++) {
|
|
153
|
+
if (flags[i] === '--json') { toolArgs = JSON.parse(flags[++i] || '{}'); continue; }
|
|
154
|
+
}
|
|
155
|
+
const out = await client.callTool(name, tool, toolArgs);
|
|
156
|
+
for (const c of out.content || []) {
|
|
157
|
+
if (c.type === 'text') process.stdout.write(c.text + '\n');
|
|
158
|
+
else process.stdout.write(JSON.stringify(c) + '\n');
|
|
159
|
+
}
|
|
160
|
+
if (out.isError) process.exit(2);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.error(`unknown subcommand: mcp-${sub}`);
|
|
165
|
+
console.error('Available: mcp-add, mcp-list, mcp-remove, mcp-tools, mcp-resources, mcp-prompts, mcp-call');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
process.stderr.write(`[kodelyth-mcp-client] ${e.message}\n`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Subcommand: route (cost-aware model tier recommendation) ──────────────────
|
|
176
|
+
// Usage: npx kodelyth-ecc route "<task description>" [--files N] [--agent <name>] [--current <model-id>]
|
|
177
|
+
if (args[0] === 'route') {
|
|
178
|
+
const router = require(path.join(ROOT, 'scripts', 'router', 'classify.js'));
|
|
179
|
+
|
|
180
|
+
// Parse flags out of args[1..].
|
|
181
|
+
const rest = args.slice(1);
|
|
182
|
+
const opts = { project_root: process.cwd() };
|
|
183
|
+
const taskParts = [];
|
|
184
|
+
for (let i = 0; i < rest.length; i++) {
|
|
185
|
+
const a = rest[i];
|
|
186
|
+
if (a === '--files' || a === '--file-count') { opts.file_count = Number(rest[++i]); continue; }
|
|
187
|
+
if (a === '--agent') { opts.active_agent = rest[++i]; continue; }
|
|
188
|
+
if (a === '--current' || a === '--model') { opts.current_model = rest[++i]; continue; }
|
|
189
|
+
if (a === '--budget') { opts.budget_tokens = Number(rest[++i]); continue; }
|
|
190
|
+
if (a === '--used') { opts.session_tokens = Number(rest[++i]); continue; }
|
|
191
|
+
if (a === '--json') { opts.json = true; continue; }
|
|
192
|
+
taskParts.push(a);
|
|
193
|
+
}
|
|
194
|
+
const task = taskParts.join(' ').trim();
|
|
195
|
+
if (!task) {
|
|
196
|
+
console.error('usage: kodelyth-ecc route "<task description>" [--files N] [--agent <name>] [--current <model>] [--json]');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const r = router.recommend(task, opts);
|
|
201
|
+
if (opts.json) {
|
|
202
|
+
console.log(JSON.stringify(r, null, 2));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (r.disabled) {
|
|
206
|
+
console.log('[model-router] disabled (KODELYTH_ROUTER=off).');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const status = r.mismatched ? `· current=${r.current_model} (mismatched)` : '';
|
|
210
|
+
console.log(`[model-router] task=${r.tier} · suggested=${r.recommended_model} ${status}`.trim());
|
|
211
|
+
console.log(` why: ${r.reasons.slice(0, 3).join(' · ')}`);
|
|
212
|
+
if (r.notes) console.log(` notes: ${r.notes}`);
|
|
213
|
+
console.log(` next: configure your client to use "${r.recommended_model}" for this turn.`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Subcommand: swarm (parallel agent execution in tmux + git worktrees) ──────
|
|
218
|
+
// Usage:
|
|
219
|
+
// kodelyth-ecc swarm --task "audit oauth flow" --agents 4
|
|
220
|
+
// kodelyth-ecc swarm --task "..." --agents code-reviewer,security-reviewer,architect --execute
|
|
221
|
+
// kodelyth-ecc swarm --plan plan.json --execute
|
|
222
|
+
// kodelyth-ecc swarm --task "..." --agents 6 --harness codex --replace --execute
|
|
223
|
+
if (args[0] === 'swarm') {
|
|
224
|
+
(async () => {
|
|
225
|
+
const builder = require(path.join(ROOT, 'scripts', 'swarm', 'build-plan.js'));
|
|
226
|
+
const orchestrator = require(path.join(ROOT, 'scripts', 'lib', 'tmux-worktree-orchestrator.js'));
|
|
227
|
+
const fs = require('fs');
|
|
228
|
+
|
|
229
|
+
const rest = args.slice(1);
|
|
230
|
+
const opts = { repoRoot: process.cwd(), execute: false, dryRun: false, writeOnly: false };
|
|
231
|
+
|
|
232
|
+
let task = '';
|
|
233
|
+
let agentsSpec = null;
|
|
234
|
+
let count = 4;
|
|
235
|
+
|
|
236
|
+
for (let i = 0; i < rest.length; i++) {
|
|
237
|
+
const a = rest[i];
|
|
238
|
+
if (a === '--task') { task = rest[++i] || ''; continue; }
|
|
239
|
+
if (a === '--agents') { agentsSpec = rest[++i] || ''; continue; }
|
|
240
|
+
if (a === '--count') { count = Number(rest[++i]); continue; }
|
|
241
|
+
if (a === '--harness') { opts.harness = rest[++i]; continue; }
|
|
242
|
+
if (a === '--launcher-cmd') { opts.launcherCmd = rest[++i]; continue; }
|
|
243
|
+
if (a === '--session') { opts.sessionName = rest[++i]; continue; }
|
|
244
|
+
if (a === '--worktree-root') { opts.worktreeRoot = rest[++i]; continue; }
|
|
245
|
+
if (a === '--coordination-root') { opts.coordinationRoot = rest[++i]; continue; }
|
|
246
|
+
if (a === '--base-ref') { opts.baseRef = rest[++i]; continue; }
|
|
247
|
+
if (a === '--seed') { (opts.seedPaths ||= []).push(rest[++i]); continue; }
|
|
248
|
+
if (a === '--replace') { opts.replaceExisting = true; continue; }
|
|
249
|
+
if (a === '--execute') { opts.execute = true; continue; }
|
|
250
|
+
if (a === '--write-only') { opts.writeOnly = true; continue; }
|
|
251
|
+
if (a === '--dry-run') { opts.dryRun = true; continue; }
|
|
252
|
+
if (a === '--plan') { opts.planPath = rest[++i]; continue; }
|
|
253
|
+
if (a === '--json') { opts.json = true; continue; }
|
|
254
|
+
if (a === '--help' || a === '-h') {
|
|
255
|
+
console.log(`
|
|
256
|
+
kodelyth-ecc swarm — parallel agent execution in tmux + git worktrees
|
|
257
|
+
|
|
258
|
+
Auto-build a plan from flags (recommended):
|
|
259
|
+
kodelyth-ecc swarm --task "<task>" [--agents N|name1,name2,...] [--harness claude|codex|opencode|windsurf|echo]
|
|
260
|
+
[--seed path] [--session NAME] [--base-ref REF] [--replace] [--execute|--write-only|--dry-run]
|
|
261
|
+
|
|
262
|
+
Use a hand-written plan.json:
|
|
263
|
+
kodelyth-ecc swarm --plan plan.json [--execute|--write-only]
|
|
264
|
+
|
|
265
|
+
Examples:
|
|
266
|
+
kodelyth-ecc swarm --task "audit oauth flow" --agents 4
|
|
267
|
+
kodelyth-ecc swarm --task "ship v2.0" --agents release-captain,security-reviewer,e2e-runner --execute
|
|
268
|
+
kodelyth-ecc swarm --task "scale API to 100k req/sec" --agents 6 --harness codex --replace --execute
|
|
269
|
+
|
|
270
|
+
Default: prints the plan as JSON without spawning anything (safe to inspect).
|
|
271
|
+
--execute creates worktrees + tmux session and launches agents.
|
|
272
|
+
--write-only just writes coordination files (task.md / handoff.md / status.md).
|
|
273
|
+
`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let planConfig;
|
|
279
|
+
try {
|
|
280
|
+
if (opts.planPath) {
|
|
281
|
+
// Power-user: load a hand-written plan JSON.
|
|
282
|
+
const raw = fs.readFileSync(path.resolve(opts.planPath), 'utf8');
|
|
283
|
+
planConfig = JSON.parse(raw);
|
|
284
|
+
} else {
|
|
285
|
+
// Auto-build from flags.
|
|
286
|
+
if (!task) {
|
|
287
|
+
console.error('swarm: --task is required (or use --plan plan.json). See --help.');
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
let agents = null;
|
|
291
|
+
if (agentsSpec) {
|
|
292
|
+
if (/^\d+$/.test(agentsSpec)) count = Number(agentsSpec);
|
|
293
|
+
else agents = agentsSpec.split(',').map(s => s.trim()).filter(Boolean);
|
|
294
|
+
}
|
|
295
|
+
planConfig = builder.buildSwarmPlan({
|
|
296
|
+
task,
|
|
297
|
+
agents,
|
|
298
|
+
count,
|
|
299
|
+
harness: opts.harness || 'claude',
|
|
300
|
+
launcherCmd: opts.launcherCmd,
|
|
301
|
+
sessionName: opts.sessionName,
|
|
302
|
+
repoRoot: opts.repoRoot,
|
|
303
|
+
worktreeRoot: opts.worktreeRoot,
|
|
304
|
+
coordinationRoot: opts.coordinationRoot,
|
|
305
|
+
baseRef: opts.baseRef,
|
|
306
|
+
seedPaths: opts.seedPaths,
|
|
307
|
+
replaceExisting: !!opts.replaceExisting,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const plan = orchestrator.buildOrchestrationPlan(planConfig);
|
|
312
|
+
|
|
313
|
+
if (opts.json) {
|
|
314
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (opts.writeOnly) {
|
|
319
|
+
orchestrator.materializePlan(plan);
|
|
320
|
+
console.log(`✓ wrote orchestration files to ${plan.coordinationDir}`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!opts.execute) {
|
|
325
|
+
// Dry-run summary.
|
|
326
|
+
console.log(`[swarm] dry-run · session=${plan.sessionName} · workers=${plan.workerPlans.length}`);
|
|
327
|
+
console.log(` repo: ${plan.repoRoot}`);
|
|
328
|
+
console.log(` coord: ${plan.coordinationDir}`);
|
|
329
|
+
console.log(` base: ${plan.baseRef}`);
|
|
330
|
+
for (const w of plan.workerPlans) {
|
|
331
|
+
console.log(` • ${w.workerName.padEnd(24)} → ${w.worktreePath}`);
|
|
332
|
+
console.log(` branch: ${w.branchName}`);
|
|
333
|
+
console.log(` launcher: ${String(w.launchCommand).slice(0, 100)}${w.launchCommand.length > 100 ? '…' : ''}`);
|
|
334
|
+
}
|
|
335
|
+
console.log('\nRe-run with --execute to spawn worktrees + tmux session.');
|
|
336
|
+
console.log('Or --write-only to just create coordination files.');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const result = orchestrator.executePlan(plan);
|
|
341
|
+
console.log(`✓ started tmux session "${result.sessionName}" with ${result.workerCount} worker panes`);
|
|
342
|
+
console.log(` attach: tmux attach -t ${result.sessionName}`);
|
|
343
|
+
console.log(` coord: ${result.coordinationDir}`);
|
|
344
|
+
} catch (e) {
|
|
345
|
+
process.stderr.write(`[swarm] ${e.message}\n`);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
})();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Subcommand: session-export / session-import / replay ─────────────────────
|
|
353
|
+
// Usage:
|
|
354
|
+
// kodelyth-ecc session-export <session> [--out file.json] [--task "..."] [--agents a,b] [--harness h]
|
|
355
|
+
// kodelyth-ecc session-import <bundle.json> [--target dir] [--overwrite]
|
|
356
|
+
// kodelyth-ecc replay <bundle.json|session-name> [--harness h] [--agents a,b] [--base-ref ref]
|
|
357
|
+
// [--session NAME] [--execute|--write-only]
|
|
358
|
+
if (args[0] === 'session-export' || args[0] === 'session-import' || args[0] === 'replay') {
|
|
359
|
+
(async () => {
|
|
360
|
+
const fs = require('fs');
|
|
361
|
+
const sub = args[0];
|
|
362
|
+
const rest = args.slice(1);
|
|
363
|
+
const bundleLib = require(path.join(ROOT, 'scripts', 'replay', 'bundle.js'));
|
|
364
|
+
const replayLib = require(path.join(ROOT, 'scripts', 'replay', 'replay.js'));
|
|
365
|
+
const orchestrator = require(path.join(ROOT, 'scripts', 'lib', 'tmux-worktree-orchestrator.js'));
|
|
366
|
+
|
|
367
|
+
function flag(name) {
|
|
368
|
+
const i = rest.indexOf(name);
|
|
369
|
+
return i >= 0 ? rest[i + 1] : undefined;
|
|
370
|
+
}
|
|
371
|
+
function has(name) { return rest.includes(name); }
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
// ── session-export ─────────────────────────────────────────────────────
|
|
375
|
+
if (sub === 'session-export') {
|
|
376
|
+
const session = rest.find(a => !a.startsWith('--') && rest.indexOf(a) === 0);
|
|
377
|
+
if (!session) throw new Error('session-export: <session> is required (the directory name under .orchestration/)');
|
|
378
|
+
|
|
379
|
+
const coordRoot = flag('--coord-root') || path.join(process.cwd(), '.orchestration');
|
|
380
|
+
const sessionDir = path.isAbsolute(session) ? session : path.join(coordRoot, session);
|
|
381
|
+
const out = flag('--out') || path.join(coordRoot, `${path.basename(sessionDir)}.bundle.json`);
|
|
382
|
+
|
|
383
|
+
const meta = {};
|
|
384
|
+
const taskFlag = flag('--task'); if (taskFlag) meta.task = taskFlag;
|
|
385
|
+
const agFlag = flag('--agents'); if (agFlag) meta.agents = agFlag.split(',').map(s => s.trim()).filter(Boolean);
|
|
386
|
+
const hFlag = flag('--harness'); if (hFlag) meta.harness = hFlag;
|
|
387
|
+
const refFlag = flag('--base-ref'); if (refFlag) meta.base_ref = refFlag;
|
|
388
|
+
|
|
389
|
+
const bundle = bundleLib.exportBundle({
|
|
390
|
+
sessionDir,
|
|
391
|
+
sessionName: path.basename(sessionDir),
|
|
392
|
+
meta,
|
|
393
|
+
});
|
|
394
|
+
bundleLib.writeBundle(bundle, out);
|
|
395
|
+
console.log(`✓ exported bundle: ${out}`);
|
|
396
|
+
console.log(` session: ${bundle.session}`);
|
|
397
|
+
console.log(` workers: ${bundle.workers.length}`);
|
|
398
|
+
if (Object.keys(bundle.meta).length > 0) console.log(` meta: ${JSON.stringify(bundle.meta)}`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── session-import ─────────────────────────────────────────────────────
|
|
403
|
+
if (sub === 'session-import') {
|
|
404
|
+
const bundlePath = rest.find(a => !a.startsWith('--'));
|
|
405
|
+
if (!bundlePath) throw new Error('session-import: <bundle.json> is required');
|
|
406
|
+
|
|
407
|
+
const bundle = bundleLib.readBundle(bundlePath);
|
|
408
|
+
const coordRoot = flag('--coord-root') || path.join(process.cwd(), '.orchestration');
|
|
409
|
+
const targetDir = flag('--target') || path.join(coordRoot, bundle.session);
|
|
410
|
+
const overwrite = has('--overwrite');
|
|
411
|
+
|
|
412
|
+
const out = bundleLib.importBundle(bundle, { targetDir, overwrite });
|
|
413
|
+
console.log(`✓ imported bundle into: ${out.targetDir}`);
|
|
414
|
+
console.log(` workers: ${out.workers.join(', ')}`);
|
|
415
|
+
console.log(` inspect: cat ${path.join(out.targetDir, out.workers[0], 'handoff.md')}`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── replay ─────────────────────────────────────────────────────────────
|
|
420
|
+
if (sub === 'replay') {
|
|
421
|
+
const target = rest.find(a => !a.startsWith('--'));
|
|
422
|
+
if (!target) throw new Error('replay: <bundle.json|session-name> is required');
|
|
423
|
+
|
|
424
|
+
// Resolve target → bundle object.
|
|
425
|
+
let bundle;
|
|
426
|
+
if (target.endsWith('.json') && fs.existsSync(path.resolve(target))) {
|
|
427
|
+
bundle = bundleLib.readBundle(path.resolve(target));
|
|
428
|
+
} else {
|
|
429
|
+
// Treat as session name under .orchestration/.
|
|
430
|
+
const coordRoot = flag('--coord-root') || path.join(process.cwd(), '.orchestration');
|
|
431
|
+
const sessionDir = path.join(coordRoot, target);
|
|
432
|
+
if (!fs.existsSync(sessionDir)) {
|
|
433
|
+
throw new Error(`replay: not a bundle file or known session: ${target}`);
|
|
434
|
+
}
|
|
435
|
+
bundle = bundleLib.exportBundle({ sessionDir, sessionName: target });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Build the replay plan.
|
|
439
|
+
const opts = {
|
|
440
|
+
repoRoot: process.cwd(),
|
|
441
|
+
baseRef: flag('--base-ref'),
|
|
442
|
+
harness: flag('--harness'),
|
|
443
|
+
sessionName: flag('--session'),
|
|
444
|
+
agents: (flag('--agents') || '').split(',').map(s => s.trim()).filter(Boolean),
|
|
445
|
+
replaceExisting: has('--replace'),
|
|
446
|
+
};
|
|
447
|
+
if (opts.agents.length === 0) opts.agents = null;
|
|
448
|
+
|
|
449
|
+
const planConfig = replayLib.buildReplayPlanConfig(bundle, opts);
|
|
450
|
+
const plan = orchestrator.buildOrchestrationPlan(planConfig);
|
|
451
|
+
|
|
452
|
+
if (has('--json')) {
|
|
453
|
+
console.log(JSON.stringify({ planConfig, plan }, null, 2));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (has('--write-only')) {
|
|
458
|
+
orchestrator.materializePlan(plan);
|
|
459
|
+
console.log(`✓ replay coordination files written to ${plan.coordinationDir}`);
|
|
460
|
+
console.log(` origin session: ${bundle.session}`);
|
|
461
|
+
console.log(` replay session: ${plan.sessionName}`);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (!has('--execute')) {
|
|
466
|
+
console.log(`[replay] dry-run · origin=${bundle.session} → replay=${plan.sessionName}`);
|
|
467
|
+
console.log(` workers: ${plan.workerPlans.length}`);
|
|
468
|
+
console.log(` base ref: ${plan.baseRef}`);
|
|
469
|
+
for (const w of plan.workerPlans) {
|
|
470
|
+
console.log(` • ${w.workerName.padEnd(24)} → ${w.worktreePath}`);
|
|
471
|
+
}
|
|
472
|
+
console.log('\nRe-run with --execute to spawn worktrees + tmux session.');
|
|
473
|
+
console.log('Or --write-only to just create coordination files.');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const result = orchestrator.executePlan(plan);
|
|
478
|
+
console.log(`✓ replay started: tmux session "${result.sessionName}"`);
|
|
479
|
+
console.log(` origin: ${bundle.session}`);
|
|
480
|
+
console.log(` attach: tmux attach -t ${result.sessionName}`);
|
|
481
|
+
console.log(` coord: ${result.coordinationDir}`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
} catch (e) {
|
|
485
|
+
process.stderr.write(`[${sub}] ${e.message}\n`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
})();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ── Subcommand: sbom / manifest / verify ─────────────────────────────────────
|
|
493
|
+
// Usage:
|
|
494
|
+
// kodelyth-ecc sbom [--root DIR] [--out FILE] [--json]
|
|
495
|
+
// kodelyth-ecc manifest [--root DIR] [--out FILE] [--json]
|
|
496
|
+
// kodelyth-ecc verify [--root DIR] [--manifest FILE] [--json]
|
|
497
|
+
if (args[0] === 'sbom' || args[0] === 'manifest' || args[0] === 'verify') {
|
|
498
|
+
(async () => {
|
|
499
|
+
const fs = require('fs');
|
|
500
|
+
const sub = args[0];
|
|
501
|
+
const rest = args.slice(1);
|
|
502
|
+
|
|
503
|
+
function flag(name, dflt) {
|
|
504
|
+
const i = rest.indexOf(name);
|
|
505
|
+
return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
|
|
506
|
+
}
|
|
507
|
+
function has(name) { return rest.includes(name); }
|
|
508
|
+
|
|
509
|
+
const rootDir = path.resolve(flag('--root', ROOT));
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
if (sub === 'sbom') {
|
|
513
|
+
const { generateSBOM } = require(path.join(ROOT, 'scripts', 'supply-chain', 'sbom.js'));
|
|
514
|
+
const bom = generateSBOM({ rootDir });
|
|
515
|
+
const json = JSON.stringify(bom, null, 2);
|
|
516
|
+
const out = flag('--out');
|
|
517
|
+
if (out) {
|
|
518
|
+
fs.writeFileSync(out, json + '\n');
|
|
519
|
+
console.log(`✓ SBOM written: ${out}`);
|
|
520
|
+
console.log(` format: ${bom.bomFormat} ${bom.specVersion}`);
|
|
521
|
+
console.log(` package: ${bom.metadata.component.name}@${bom.metadata.component.version}`);
|
|
522
|
+
console.log(` components: ${bom.components.length}`);
|
|
523
|
+
} else if (has('--json')) {
|
|
524
|
+
process.stdout.write(json + '\n');
|
|
525
|
+
} else {
|
|
526
|
+
console.log(`${bom.bomFormat} ${bom.specVersion}`);
|
|
527
|
+
console.log(`package: ${bom.metadata.component.name}@${bom.metadata.component.version}`);
|
|
528
|
+
console.log(`components: ${bom.components.length}`);
|
|
529
|
+
console.log(`(use --out FILE or --json to emit the full document)`);
|
|
530
|
+
}
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (sub === 'manifest') {
|
|
535
|
+
const { generateManifest } = require(path.join(ROOT, 'scripts', 'supply-chain', 'manifest.js'));
|
|
536
|
+
const m = generateManifest({ rootDir });
|
|
537
|
+
const json = JSON.stringify(m, null, 2);
|
|
538
|
+
const out = flag('--out');
|
|
539
|
+
if (out) {
|
|
540
|
+
fs.writeFileSync(out, json + '\n');
|
|
541
|
+
console.log(`✓ manifest written: ${out}`);
|
|
542
|
+
console.log(` package: ${m.package}@${m.pkg_version}`);
|
|
543
|
+
console.log(` files: ${m.file_count}`);
|
|
544
|
+
console.log(` digest: ${m.digest.slice(0, 16)}…`);
|
|
545
|
+
} else if (has('--json')) {
|
|
546
|
+
process.stdout.write(json + '\n');
|
|
547
|
+
} else {
|
|
548
|
+
console.log(`package: ${m.package}@${m.pkg_version}`);
|
|
549
|
+
console.log(`files: ${m.file_count}`);
|
|
550
|
+
console.log(`digest: ${m.digest}`);
|
|
551
|
+
console.log(`(use --out FILE or --json to emit the full document)`);
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (sub === 'verify') {
|
|
557
|
+
const { verifyAgainstManifest } = require(path.join(ROOT, 'scripts', 'supply-chain', 'verify.js'));
|
|
558
|
+
const manifestPath = flag('--manifest', path.join(rootDir, 'manifest.json'));
|
|
559
|
+
if (!fs.existsSync(manifestPath)) {
|
|
560
|
+
throw new Error(`verify: manifest not found at ${manifestPath} (use --manifest FILE)`);
|
|
561
|
+
}
|
|
562
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
563
|
+
const report = verifyAgainstManifest({ rootDir, manifest });
|
|
564
|
+
|
|
565
|
+
if (has('--json')) {
|
|
566
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
567
|
+
process.exit(report.ok ? 0 : 1);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const s = report.summary;
|
|
571
|
+
console.log(`Kodelyth ECC supply-chain verify`);
|
|
572
|
+
console.log(` package: ${manifest.package}@${manifest.pkg_version}`);
|
|
573
|
+
console.log(` manifest digest: ${manifest.digest}`);
|
|
574
|
+
console.log(` files in manifest: ${s.total_in_manifest}`);
|
|
575
|
+
console.log(` ✓ ok: ${s.ok}`);
|
|
576
|
+
console.log(` ✗ modified: ${s.modified}`);
|
|
577
|
+
console.log(` ✗ missing: ${s.missing}`);
|
|
578
|
+
console.log(` ⚠ extra: ${s.extra} (advisory)`);
|
|
579
|
+
|
|
580
|
+
if (report.details.modified.length) {
|
|
581
|
+
console.log(`\nModified files:`);
|
|
582
|
+
for (const m of report.details.modified.slice(0, 20)) {
|
|
583
|
+
console.log(` • ${m.path}`);
|
|
584
|
+
}
|
|
585
|
+
if (report.details.modified.length > 20) {
|
|
586
|
+
console.log(` …and ${report.details.modified.length - 20} more (use --json for full list)`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (report.details.missing.length) {
|
|
590
|
+
console.log(`\nMissing files:`);
|
|
591
|
+
for (const m of report.details.missing.slice(0, 20)) {
|
|
592
|
+
console.log(` • ${m.path}`);
|
|
593
|
+
}
|
|
594
|
+
if (report.details.missing.length > 20) {
|
|
595
|
+
console.log(` …and ${report.details.missing.length - 20} more (use --json for full list)`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
console.log(report.ok ? `\n✓ verify OK` : `\n✗ verify FAILED`);
|
|
599
|
+
process.exit(report.ok ? 0 : 1);
|
|
600
|
+
}
|
|
601
|
+
} catch (e) {
|
|
602
|
+
process.stderr.write(`[${sub}] ${e.message}\n`);
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
})();
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ── Subcommand: dashboard (local observability) ───────────────────────────────
|
|
610
|
+
// Usage:
|
|
611
|
+
// kodelyth-ecc dashboard [--port N] [--host 127.0.0.1] [--no-open]
|
|
612
|
+
if (args[0] === 'dashboard') {
|
|
613
|
+
(async () => {
|
|
614
|
+
const rest = args.slice(1);
|
|
615
|
+
function flag(name, dflt) {
|
|
616
|
+
const i = rest.indexOf(name);
|
|
617
|
+
return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
|
|
618
|
+
}
|
|
619
|
+
function has(name) { return rest.includes(name); }
|
|
620
|
+
try {
|
|
621
|
+
const { start } = require(path.join(ROOT, 'scripts', 'dashboard', 'server.js'));
|
|
622
|
+
const port = Number(flag('--port', '5747'));
|
|
623
|
+
const host = flag('--host', '127.0.0.1');
|
|
624
|
+
const openBrowser = !has('--no-open');
|
|
625
|
+
await start({ port, host, openBrowser });
|
|
626
|
+
// Keep process alive — http server is already listening.
|
|
627
|
+
} catch (e) {
|
|
628
|
+
process.stderr.write(`[dashboard] ${e.message}\n`);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
})();
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ── Subcommand: evolve (Phase 3.4 — self-evolving memory) ────────────────────
|
|
636
|
+
// Usage:
|
|
637
|
+
// kodelyth-ecc evolve analyze [--json] [--reuse-min N] [--miss-min N]
|
|
638
|
+
// kodelyth-ecc evolve list [--status STATUS] [--json]
|
|
639
|
+
// kodelyth-ecc evolve show <proposalId> [--json]
|
|
640
|
+
// kodelyth-ecc evolve accept <proposalId> [--root DIR] [--overwrite]
|
|
641
|
+
// kodelyth-ecc evolve reject <proposalId> [--note "..."]
|
|
642
|
+
// kodelyth-ecc evolve stats [--json]
|
|
643
|
+
if (args[0] === 'evolve') {
|
|
644
|
+
(async () => {
|
|
645
|
+
const fs = require('fs');
|
|
646
|
+
const sub = args[1] || '';
|
|
647
|
+
const rest = args.slice(2);
|
|
648
|
+
|
|
649
|
+
function flag(name, dflt) {
|
|
650
|
+
const i = rest.indexOf(name);
|
|
651
|
+
return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
|
|
652
|
+
}
|
|
653
|
+
function has(name) { return rest.includes(name); }
|
|
654
|
+
function positional() { return rest.find(a => !a.startsWith('--')); }
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
const statsLib = require(path.join(ROOT, 'scripts', 'evolve', 'stats.js'));
|
|
658
|
+
const analyzeLib = require(path.join(ROOT, 'scripts', 'evolve', 'analyze.js'));
|
|
659
|
+
const proposalsLib = require(path.join(ROOT, 'scripts', 'evolve', 'proposals.js'));
|
|
660
|
+
const memoryStore = require(path.join(ROOT, 'scripts', 'memory', 'store.js'));
|
|
661
|
+
|
|
662
|
+
// ── stats ────────────────────────────────────────────────────────────
|
|
663
|
+
if (sub === 'stats') {
|
|
664
|
+
const reuse = statsLib.getReuseStats();
|
|
665
|
+
const miss = statsLib.getRoutingMissStats();
|
|
666
|
+
if (has('--json')) {
|
|
667
|
+
process.stdout.write(JSON.stringify({ reuse, miss }, null, 2) + '\n');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
console.log('Kodelyth ECC — self-evolving memory stats');
|
|
671
|
+
console.log(` reuse:`);
|
|
672
|
+
console.log(` memories tracked: ${reuse.total_memories_tracked}`);
|
|
673
|
+
console.log(` total surfaces: ${reuse.total_surfaces}`);
|
|
674
|
+
console.log(` last updated: ${reuse.last_updated || '(none)'}`);
|
|
675
|
+
if (reuse.entries.length) {
|
|
676
|
+
console.log(` top reused:`);
|
|
677
|
+
for (const e of reuse.entries.slice(0, 5)) {
|
|
678
|
+
console.log(` • ${e.id} count=${e.count} sessions=${(e.sessions||[]).length}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
console.log(` routing misses:`);
|
|
682
|
+
console.log(` total: ${miss.total_misses}`);
|
|
683
|
+
console.log(` unique prompts: ${miss.unique_prompts}`);
|
|
684
|
+
if (miss.entries.length) {
|
|
685
|
+
console.log(` top clusters:`);
|
|
686
|
+
for (const e of miss.entries.slice(0, 5)) {
|
|
687
|
+
console.log(` • count=${e.count} tokens=[${(e.tokens||[]).slice(0,4).join(', ')}]`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// ── analyze ──────────────────────────────────────────────────────────
|
|
694
|
+
if (sub === 'analyze') {
|
|
695
|
+
const reuseStats = statsLib.getReuseStats();
|
|
696
|
+
const missStats = statsLib.getRoutingMissStats();
|
|
697
|
+
const memories = memoryStore.listAll();
|
|
698
|
+
const thresholds = {
|
|
699
|
+
reuseMinCount: Number(flag('--reuse-min')) || undefined,
|
|
700
|
+
reuseMinSessions: Number(flag('--reuse-min-sessions')) || undefined,
|
|
701
|
+
missMinCount: Number(flag('--miss-min')) || undefined,
|
|
702
|
+
missMinClusterDistinct: Number(flag('--miss-min-distinct')) || undefined,
|
|
703
|
+
};
|
|
704
|
+
const fresh = analyzeLib.analyzeAll({ reuseStats, missStats, memories, thresholds });
|
|
705
|
+
|
|
706
|
+
let appended = 0;
|
|
707
|
+
for (const p of fresh) {
|
|
708
|
+
const before = proposalsLib.findById(p.id);
|
|
709
|
+
proposalsLib.appendProposal(p);
|
|
710
|
+
if (!before) appended += 1;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (has('--json')) {
|
|
714
|
+
process.stdout.write(JSON.stringify({
|
|
715
|
+
scanned: { reuse_entries: reuseStats.entries.length, miss_entries: missStats.entries.length },
|
|
716
|
+
generated: fresh.length,
|
|
717
|
+
new: appended,
|
|
718
|
+
proposals: fresh,
|
|
719
|
+
}, null, 2) + '\n');
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
console.log(`✓ analyzed signals`);
|
|
724
|
+
console.log(` reuse entries scanned: ${reuseStats.entries.length}`);
|
|
725
|
+
console.log(` miss entries scanned: ${missStats.entries.length}`);
|
|
726
|
+
console.log(` proposals generated: ${fresh.length}`);
|
|
727
|
+
console.log(` new proposals (added): ${appended}`);
|
|
728
|
+
if (fresh.length === 0) {
|
|
729
|
+
console.log(`\n No proposals yet. Either signals are below thresholds or there's nothing to propose.`);
|
|
730
|
+
console.log(` Defaults: reuse-min=${analyzeLib.DEFAULT_REUSE_MIN_COUNT} reuse-min-sessions=${analyzeLib.DEFAULT_REUSE_MIN_SESSIONS} miss-min=${analyzeLib.DEFAULT_MISS_MIN_COUNT}`);
|
|
731
|
+
} else {
|
|
732
|
+
console.log(`\n Run 'kodelyth-ecc evolve list' to review.`);
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// ── list ─────────────────────────────────────────────────────────────
|
|
738
|
+
if (sub === 'list') {
|
|
739
|
+
const wantStatus = flag('--status');
|
|
740
|
+
const proposals = proposalsLib.listByStatus(wantStatus || null);
|
|
741
|
+
if (has('--json')) {
|
|
742
|
+
process.stdout.write(JSON.stringify(proposals, null, 2) + '\n');
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (proposals.length === 0) {
|
|
746
|
+
console.log(`No proposals${wantStatus ? ` with status="${wantStatus}"` : ''}. Try 'kodelyth-ecc evolve analyze' first.`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
console.log(`Kodelyth ECC — self-evolving memory proposals (${proposals.length})`);
|
|
750
|
+
for (const p of proposals) {
|
|
751
|
+
const tag = p.status === 'pending' ? '⏸' : p.status === 'accepted' ? '✓' : p.status === 'rejected' ? '✗' : p.status === 'applied' ? '★' : '?';
|
|
752
|
+
console.log(` ${tag} [${p.status}] ${p.id} ${p.type}`);
|
|
753
|
+
console.log(` target: ${p.proposal?.target_path || '(none)'}`);
|
|
754
|
+
console.log(` why: ${p.proposal?.rationale || '(none)'}`);
|
|
755
|
+
}
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// ── show ─────────────────────────────────────────────────────────────
|
|
760
|
+
if (sub === 'show') {
|
|
761
|
+
const id = positional();
|
|
762
|
+
if (!id) throw new Error('show: <proposalId> is required');
|
|
763
|
+
const p = proposalsLib.findById(id);
|
|
764
|
+
if (!p) throw new Error(`show: proposal "${id}" not found`);
|
|
765
|
+
if (has('--json')) {
|
|
766
|
+
process.stdout.write(JSON.stringify(p, null, 2) + '\n');
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
console.log(`Proposal ${p.id} (${p.type}, status=${p.status})`);
|
|
770
|
+
console.log(` target_path: ${p.proposal?.target_path}`);
|
|
771
|
+
console.log(` rationale: ${p.proposal?.rationale}`);
|
|
772
|
+
console.log(`\n--- proposed file content ---\n`);
|
|
773
|
+
console.log(p.proposal?.diff || '(no diff)');
|
|
774
|
+
console.log(`\n--- evidence ---\n`);
|
|
775
|
+
console.log(JSON.stringify(p.evidence, null, 2));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// ── accept ───────────────────────────────────────────────────────────
|
|
780
|
+
if (sub === 'accept') {
|
|
781
|
+
const id = positional();
|
|
782
|
+
if (!id) throw new Error('accept: <proposalId> is required');
|
|
783
|
+
const p = proposalsLib.findById(id);
|
|
784
|
+
if (!p) throw new Error(`accept: proposal "${id}" not found`);
|
|
785
|
+
if (p.status === 'accepted' || p.status === 'applied') {
|
|
786
|
+
console.log(`Proposal ${id} is already ${p.status}.`);
|
|
787
|
+
if (p.applied_path) console.log(` draft at: ${p.applied_path}`);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const repoRoot = path.resolve(flag('--root', ROOT));
|
|
792
|
+
const overwrite = has('--overwrite');
|
|
793
|
+
const result = proposalsLib.applyProposalToDisk(p, { repoRoot, overwrite });
|
|
794
|
+
proposalsLib.setStatus(id, 'accepted', undefined, { applied_path: result.path });
|
|
795
|
+
|
|
796
|
+
console.log(`✓ accepted ${id}`);
|
|
797
|
+
console.log(` draft written: ${result.path}`);
|
|
798
|
+
console.log(`\n Review the draft. When you're happy with it, commit it.`);
|
|
799
|
+
console.log(` Then mark it applied: kodelyth-ecc evolve list --status applied`);
|
|
800
|
+
console.log(` (Evolve NEVER auto-commits proposals.)`);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// ── reject ───────────────────────────────────────────────────────────
|
|
805
|
+
if (sub === 'reject') {
|
|
806
|
+
const id = positional();
|
|
807
|
+
if (!id) throw new Error('reject: <proposalId> is required');
|
|
808
|
+
const p = proposalsLib.findById(id);
|
|
809
|
+
if (!p) throw new Error(`reject: proposal "${id}" not found`);
|
|
810
|
+
const note = flag('--note');
|
|
811
|
+
proposalsLib.setStatus(id, 'rejected', undefined, note ? { note } : {});
|
|
812
|
+
console.log(`✗ rejected ${id}${note ? ` (note: ${note})` : ''}`);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
throw new Error(
|
|
817
|
+
`unknown evolve subcommand "${sub}". ` +
|
|
818
|
+
`Valid: analyze, list, show, accept, reject, stats.`
|
|
819
|
+
);
|
|
820
|
+
} catch (e) {
|
|
821
|
+
process.stderr.write(`[evolve] ${e.message}\n`);
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
})();
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
|
|
31
828
|
// ── Help shortcut ─────────────────────────────────────────────────────────────
|
|
32
829
|
if (args.includes('--help') || args.includes('-h')) {
|
|
33
830
|
console.log(`
|
|
34
831
|
Kodelyth ECC — AI Coding Toolkit installer
|
|
35
832
|
|
|
36
833
|
Usage:
|
|
37
|
-
npx kodelyth-ecc [--target TARGET] [--profile PROFILE] [languages...]
|
|
834
|
+
npx kodelyth-ecc [--target TARGET] [--bundle BUNDLE] [--profile PROFILE] [languages...]
|
|
835
|
+
npx kodelyth-ecc mcp Start the Kodelyth MCP server over stdio
|
|
836
|
+
npx kodelyth-ecc mcp-add <name> -- <cmd> Register an external MCP server
|
|
837
|
+
npx kodelyth-ecc mcp-list List registered MCP servers
|
|
838
|
+
npx kodelyth-ecc mcp-tools <name> List tools on a registered server
|
|
839
|
+
npx kodelyth-ecc mcp-call <name> <tool> Call a tool on a registered server
|
|
840
|
+
npx kodelyth-ecc route "<task>" Cost-aware model-tier recommendation for a task
|
|
841
|
+
npx kodelyth-ecc swarm --task "<task>" Parallel agent execution in tmux + git worktrees
|
|
842
|
+
npx kodelyth-ecc session-export <session> Export a coordination dir as a portable bundle
|
|
843
|
+
npx kodelyth-ecc replay <bundle|session> Re-run a session with optional variations
|
|
844
|
+
npx kodelyth-ecc sbom [--out file.json] Emit a CycloneDX 1.5 SBOM for the package
|
|
845
|
+
npx kodelyth-ecc manifest [--out file.json] Emit a sha256 content manifest of all assets
|
|
846
|
+
npx kodelyth-ecc verify [--manifest file] Verify installed copy against a content manifest
|
|
847
|
+
npx kodelyth-ecc evolve analyze Generate skill / routing proposals from memory + miss signals
|
|
848
|
+
npx kodelyth-ecc dashboard Boot localhost-only observability dashboard (zero telemetry)
|
|
849
|
+
|
|
850
|
+
Subcommands:
|
|
851
|
+
mcp Start the Kodelyth MCP server over stdio (universal adapter for
|
|
852
|
+
Claude Desktop, LangGraph, AutoGen, CrewAI, OpenAI Agents SDK, etc.).
|
|
853
|
+
See docs/mcp.md for client config snippets.
|
|
854
|
+
mcp-add <name> Register an external MCP server (Stripe, GitHub, Postgres, Redis…).
|
|
855
|
+
Usage: kodelyth-ecc mcp-add github -- npx -y @modelcontextprotocol/server-github
|
|
856
|
+
Or with env vars: kodelyth-ecc mcp-add postgres --env DB_URL=postgres://...
|
|
857
|
+
-- npx -y @modelcontextprotocol/server-postgres
|
|
858
|
+
mcp-list List all registered external MCP servers.
|
|
859
|
+
mcp-remove <name> Unregister an external MCP server.
|
|
860
|
+
mcp-tools <name> List tools exposed by a registered server.
|
|
861
|
+
mcp-resources <name> List resources exposed by a registered server.
|
|
862
|
+
mcp-prompts <name> List prompts exposed by a registered server.
|
|
863
|
+
mcp-call <name> <tool> [--json '{"arg":"value"}']
|
|
864
|
+
Call a tool on a registered server. See docs/mcp-clients.md.
|
|
865
|
+
route Recommend trivial/standard/hard model tier for a task. Reads
|
|
866
|
+
.kodelyth/router.json and KODELYTH_ROUTER_* env vars. Disable with
|
|
867
|
+
KODELYTH_ROUTER=off. Use --json for machine-readable output.
|
|
868
|
+
swarm Run N specialist agents in parallel inside isolated git worktrees +
|
|
869
|
+
a tmux session. Auto-picks agents from --task signals or accepts
|
|
870
|
+
--agents name1,name2,... Smart defaults for 4/6/8-agent rotations.
|
|
871
|
+
Default is dry-run; pass --execute to actually spawn. See docs/swarm.md.
|
|
872
|
+
session-export Export a coordination dir as a portable bundle JSON (task + handoffs
|
|
873
|
+
+ status). Optional --task / --agents / --harness / --base-ref to
|
|
874
|
+
enrich the bundle's meta for cleaner replay. See docs/replay.md.
|
|
875
|
+
session-import Restore a bundle into a coordination dir (--target dir, --overwrite).
|
|
876
|
+
replay Re-run a session or bundle. Accepts --harness / --agents / --base-ref
|
|
877
|
+
/ --session for A/B variations. Default dry-run; --execute to spawn.
|
|
878
|
+
sbom Generate a CycloneDX 1.5 SBOM from package.json + package-lock.json.
|
|
879
|
+
Use --out FILE to write, --json to stream. See docs/supply-chain.md.
|
|
880
|
+
manifest Generate a sha256 content manifest of every shipped asset (agents,
|
|
881
|
+
skills, commands, rules, hooks, scripts, bin, bundles, root files).
|
|
882
|
+
Output is deterministic and diff-friendly.
|
|
883
|
+
verify Verify an installed copy against a manifest. Reports ok/modified/
|
|
884
|
+
missing/extra. Exits non-zero on any modified or missing file.
|
|
885
|
+
dashboard Local observability dashboard. Boots a localhost-only HTTP
|
|
886
|
+
server (default 127.0.0.1:5747) showing memory, evolve signals,
|
|
887
|
+
catalog, sessions, token-budget snapshot. Auto-opens browser unless
|
|
888
|
+
--no-open. Refuses to bind non-localhost without
|
|
889
|
+
KODELYTH_DASHBOARD_ALLOW_REMOTE=1. Zero telemetry. See docs/dashboard.md.
|
|
890
|
+
evolve Self-evolving memory. Subactions:
|
|
891
|
+
analyze Convert reuse + routing-miss signals into proposals
|
|
892
|
+
list Show pending / accepted / rejected proposals
|
|
893
|
+
show ID Print full proposed file content + evidence
|
|
894
|
+
accept ID Write the draft to disk (NEVER auto-commits)
|
|
895
|
+
reject ID Mark a proposal rejected
|
|
896
|
+
stats Snapshot of recorded signals
|
|
897
|
+
See docs/evolve.md.
|
|
38
898
|
|
|
39
899
|
Targets:
|
|
40
900
|
claude-home Claude Code — global install (default)
|
|
@@ -44,10 +904,24 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
44
904
|
cursor-project Cursor IDE — project install (.cursor/)
|
|
45
905
|
codex-home Codex CLI — global install (~/.codex/)
|
|
46
906
|
opencode OpenCode — project install (.opencode/)
|
|
907
|
+
cline Cline (VS Code) — project install (.clinerules/)
|
|
908
|
+
roocode Roo Code (VS Code) — project install (.roo/)
|
|
909
|
+
aider Aider terminal agent — project install (.aider-ecc/ + CONVENTIONS.md)
|
|
910
|
+
kimi Kimi Code — project install (.kimi/)
|
|
911
|
+
gemini-project Gemini CLI — project install (.gemini/)
|
|
912
|
+
gemini-home Gemini CLI — global install (~/.gemini/)
|
|
913
|
+
|
|
914
|
+
Bundles (audience-tailored — installs everything + curated cheat sheet):
|
|
915
|
+
--bundle indie-hacker Solo founders / SaaS — ship fast, validate, harden
|
|
916
|
+
--bundle red-team Security engineers — devil-mode crew + adversarial workflows
|
|
917
|
+
--bundle enterprise Compliance / audit teams — SBOM, license, supply chain
|
|
47
918
|
|
|
48
919
|
Examples:
|
|
49
920
|
npx kodelyth-ecc
|
|
50
921
|
npx kodelyth-ecc --target windsurf-project
|
|
922
|
+
npx kodelyth-ecc --bundle indie-hacker
|
|
923
|
+
npx kodelyth-ecc --bundle red-team --target windsurf-project
|
|
924
|
+
npx kodelyth-ecc --bundle enterprise
|
|
51
925
|
npx kodelyth-ecc --target antigravity --profile nextjs
|
|
52
926
|
npx kodelyth-ecc --target claude-home typescript python
|
|
53
927
|
|