kodelyth-ecc 1.5.9 → 1.7.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/.github/workflows/publish.yml +19 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +455 -0
- package/CLAUDE.md +48 -16
- package/README.md +265 -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 +286 -4
- 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 +154 -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 +5 -5
- package/social/github-social-preview.svg +119 -100
- package/social/readme-hero.svg +11 -11
- 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 +159 -0
- package/tests/mcp/catalog.test.js +98 -0
- package/tests/mcp/client.test.js +109 -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 -28
- package/wiki/Hook-Reference.md +1 -1
- package/wiki/Installation-Guide.md +109 -6
- package/wiki/Platform-Support.md +239 -25
- package/wiki/Skill-Reference.md +101 -6
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kodelyth ECC — MCP Catalog
|
|
3
|
+
// Read-only loader for agents, skills, commands, and rule files.
|
|
4
|
+
// Pure file reads. Zero side effects. Cached after first call.
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const ROOT = path.join(__dirname, '..', '..');
|
|
13
|
+
|
|
14
|
+
const PATHS = {
|
|
15
|
+
agents: path.join(ROOT, 'agents'),
|
|
16
|
+
skills: path.join(ROOT, 'skills'),
|
|
17
|
+
commands: path.join(ROOT, 'commands'),
|
|
18
|
+
rules: path.join(ROOT, 'rules', 'common'),
|
|
19
|
+
bundles: path.join(ROOT, 'bundles'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ── Frontmatter parser ───────────────────────────────────────────────────────
|
|
23
|
+
// Minimal YAML frontmatter parser (key: value, supports basic > folded scalars).
|
|
24
|
+
// We don't need full YAML — agent/skill files use a tiny subset.
|
|
25
|
+
function parseFrontmatter(raw) {
|
|
26
|
+
if (typeof raw !== 'string') return { meta: {}, body: '' };
|
|
27
|
+
if (!raw.startsWith('---')) return { meta: {}, body: raw };
|
|
28
|
+
|
|
29
|
+
const end = raw.indexOf('\n---', 3);
|
|
30
|
+
if (end === -1) return { meta: {}, body: raw };
|
|
31
|
+
|
|
32
|
+
const fmText = raw.slice(3, end).replace(/^\r?\n/, '');
|
|
33
|
+
const body = raw.slice(end + 4).replace(/^\r?\n/, '');
|
|
34
|
+
|
|
35
|
+
const meta = {};
|
|
36
|
+
const lines = fmText.split(/\r?\n/);
|
|
37
|
+
let i = 0;
|
|
38
|
+
while (i < lines.length) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
if (!line.trim() || line.trim().startsWith('#')) { i++; continue; }
|
|
41
|
+
const m = /^([A-Za-z0-9_\-]+)\s*:\s*(.*)$/.exec(line);
|
|
42
|
+
if (!m) { i++; continue; }
|
|
43
|
+
const key = m[1];
|
|
44
|
+
let val = m[2].trim();
|
|
45
|
+
// Folded scalar: `>` or `>-`
|
|
46
|
+
if (val === '>' || val === '>-' || val === '|' || val === '|-') {
|
|
47
|
+
const folded = [];
|
|
48
|
+
let j = i + 1;
|
|
49
|
+
const baseIndent = (lines[j] || '').match(/^(\s*)/)[1].length;
|
|
50
|
+
while (j < lines.length) {
|
|
51
|
+
const next = lines[j];
|
|
52
|
+
if (next.trim() && (next.match(/^(\s*)/)[1].length < baseIndent)) break;
|
|
53
|
+
folded.push(next.slice(baseIndent));
|
|
54
|
+
j++;
|
|
55
|
+
}
|
|
56
|
+
meta[key] = folded.join(val.startsWith('>') ? ' ' : '\n').trim();
|
|
57
|
+
i = j;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Quoted strings
|
|
61
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
62
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
63
|
+
val = val.slice(1, -1);
|
|
64
|
+
}
|
|
65
|
+
meta[key] = val;
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
return { meta, body };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Generic directory loader ─────────────────────────────────────────────────
|
|
72
|
+
function safeReadFile(p) {
|
|
73
|
+
try { return fs.readFileSync(p, 'utf8'); }
|
|
74
|
+
catch { return null; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function listMarkdownFiles(dir) {
|
|
78
|
+
if (!fs.existsSync(dir)) return [];
|
|
79
|
+
return fs.readdirSync(dir)
|
|
80
|
+
.filter(f => f.endsWith('.md'))
|
|
81
|
+
.map(f => path.join(dir, f));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Agents ───────────────────────────────────────────────────────────────────
|
|
85
|
+
let _agentsCache = null;
|
|
86
|
+
function loadAgents() {
|
|
87
|
+
if (_agentsCache) return _agentsCache;
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const file of listMarkdownFiles(PATHS.agents)) {
|
|
90
|
+
const raw = safeReadFile(file);
|
|
91
|
+
if (raw === null) continue;
|
|
92
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
93
|
+
const name = meta.name || path.basename(file, '.md');
|
|
94
|
+
out.push({
|
|
95
|
+
name,
|
|
96
|
+
description: meta.description || '',
|
|
97
|
+
file,
|
|
98
|
+
relpath: path.relative(ROOT, file),
|
|
99
|
+
meta,
|
|
100
|
+
body,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
104
|
+
_agentsCache = out;
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Skills ───────────────────────────────────────────────────────────────────
|
|
109
|
+
let _skillsCache = null;
|
|
110
|
+
function loadSkills() {
|
|
111
|
+
if (_skillsCache) return _skillsCache;
|
|
112
|
+
const out = [];
|
|
113
|
+
if (!fs.existsSync(PATHS.skills)) { _skillsCache = out; return out; }
|
|
114
|
+
for (const dir of fs.readdirSync(PATHS.skills)) {
|
|
115
|
+
const skillFile = path.join(PATHS.skills, dir, 'SKILL.md');
|
|
116
|
+
const raw = safeReadFile(skillFile);
|
|
117
|
+
if (raw === null) continue;
|
|
118
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
119
|
+
out.push({
|
|
120
|
+
name: meta.name || dir,
|
|
121
|
+
description: meta.description || '',
|
|
122
|
+
origin: meta.origin || '',
|
|
123
|
+
file: skillFile,
|
|
124
|
+
relpath: path.relative(ROOT, skillFile),
|
|
125
|
+
meta,
|
|
126
|
+
body,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
130
|
+
_skillsCache = out;
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Commands ─────────────────────────────────────────────────────────────────
|
|
135
|
+
let _commandsCache = null;
|
|
136
|
+
function loadCommands() {
|
|
137
|
+
if (_commandsCache) return _commandsCache;
|
|
138
|
+
const out = [];
|
|
139
|
+
for (const file of listMarkdownFiles(PATHS.commands)) {
|
|
140
|
+
const raw = safeReadFile(file);
|
|
141
|
+
if (raw === null) continue;
|
|
142
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
143
|
+
const name = meta.name || path.basename(file, '.md');
|
|
144
|
+
out.push({
|
|
145
|
+
name,
|
|
146
|
+
description: meta.description || '',
|
|
147
|
+
argumentHint: meta['argument-hint'] || '',
|
|
148
|
+
file,
|
|
149
|
+
relpath: path.relative(ROOT, file),
|
|
150
|
+
meta,
|
|
151
|
+
body,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
155
|
+
_commandsCache = out;
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Rules ────────────────────────────────────────────────────────────────────
|
|
160
|
+
function loadRule(name) {
|
|
161
|
+
const file = path.join(PATHS.rules, `${name}.md`);
|
|
162
|
+
const raw = safeReadFile(file);
|
|
163
|
+
if (raw === null) return null;
|
|
164
|
+
return { name, file, relpath: path.relative(ROOT, file), body: raw };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function loadAllRules() {
|
|
168
|
+
if (!fs.existsSync(PATHS.rules)) return [];
|
|
169
|
+
return listMarkdownFiles(PATHS.rules).map(file => {
|
|
170
|
+
const name = path.basename(file, '.md');
|
|
171
|
+
return {
|
|
172
|
+
name,
|
|
173
|
+
file,
|
|
174
|
+
relpath: path.relative(ROOT, file),
|
|
175
|
+
body: safeReadFile(file) || '',
|
|
176
|
+
};
|
|
177
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Bundles ──────────────────────────────────────────────────────────────────
|
|
181
|
+
function loadBundles() {
|
|
182
|
+
const out = [];
|
|
183
|
+
if (!fs.existsSync(PATHS.bundles)) return out;
|
|
184
|
+
for (const file of listMarkdownFiles(PATHS.bundles)) {
|
|
185
|
+
const raw = safeReadFile(file);
|
|
186
|
+
if (raw === null) continue;
|
|
187
|
+
const name = path.basename(file, '.md');
|
|
188
|
+
out.push({
|
|
189
|
+
name,
|
|
190
|
+
file,
|
|
191
|
+
relpath: path.relative(ROOT, file),
|
|
192
|
+
body: raw,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Lookups ──────────────────────────────────────────────────────────────────
|
|
199
|
+
function findAgent(name) {
|
|
200
|
+
return loadAgents().find(a => a.name === name) || null;
|
|
201
|
+
}
|
|
202
|
+
function findSkill(name) {
|
|
203
|
+
return loadSkills().find(s => s.name === name) || null;
|
|
204
|
+
}
|
|
205
|
+
function findCommand(name) {
|
|
206
|
+
const target = String(name || '').replace(/^\//, '');
|
|
207
|
+
return loadCommands().find(c => c.name === target) || null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Stats ────────────────────────────────────────────────────────────────────
|
|
211
|
+
function stats() {
|
|
212
|
+
return {
|
|
213
|
+
agents: loadAgents().length,
|
|
214
|
+
skills: loadSkills().length,
|
|
215
|
+
commands: loadCommands().length,
|
|
216
|
+
rules: loadAllRules().length,
|
|
217
|
+
bundles: loadBundles().length,
|
|
218
|
+
root: ROOT,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Cache reset (mainly for tests) ───────────────────────────────────────────
|
|
223
|
+
function resetCache() {
|
|
224
|
+
_agentsCache = null;
|
|
225
|
+
_skillsCache = null;
|
|
226
|
+
_commandsCache = null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
PATHS,
|
|
231
|
+
ROOT,
|
|
232
|
+
parseFrontmatter,
|
|
233
|
+
loadAgents,
|
|
234
|
+
loadSkills,
|
|
235
|
+
loadCommands,
|
|
236
|
+
loadRule,
|
|
237
|
+
loadAllRules,
|
|
238
|
+
loadBundles,
|
|
239
|
+
findAgent,
|
|
240
|
+
findSkill,
|
|
241
|
+
findCommand,
|
|
242
|
+
stats,
|
|
243
|
+
resetCache,
|
|
244
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kodelyth ECC — MCP Client (registry + connect helpers)
|
|
3
|
+
//
|
|
4
|
+
// Lets ECC consume external MCP servers (Stripe, GitHub, Postgres, Brave,
|
|
5
|
+
// Redis, Filesystem, etc.). Pairs with scripts/mcp/server.js — together they
|
|
6
|
+
// make ECC the MCP HUB: serve to any framework, consume from any provider.
|
|
7
|
+
//
|
|
8
|
+
// Storage:
|
|
9
|
+
// ~/.kodelyth/mcp-clients.json registry of named external servers
|
|
10
|
+
//
|
|
11
|
+
// Public API (all functions pure or local file-only):
|
|
12
|
+
// loadRegistry(), saveRegistry(reg)
|
|
13
|
+
// addServer({ name, command, args, env })
|
|
14
|
+
// removeServer(name)
|
|
15
|
+
// listServers()
|
|
16
|
+
// getServer(name)
|
|
17
|
+
//
|
|
18
|
+
// Connection helpers (require @modelcontextprotocol/sdk):
|
|
19
|
+
// connect(name) -> { client, transport, close }
|
|
20
|
+
// listTools(name)
|
|
21
|
+
// callTool(name, tool, args)
|
|
22
|
+
// listResources(name)
|
|
23
|
+
// readResource(name, uri)
|
|
24
|
+
// listPrompts(name)
|
|
25
|
+
// getPrompt(name, prompt, args)
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const os = require('os');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
const REGISTRY_DIR = process.env.KODELYTH_MCP_CLIENT_DIR
|
|
35
|
+
|| path.join(os.homedir(), '.kodelyth');
|
|
36
|
+
const REGISTRY_FILE = path.join(REGISTRY_DIR, 'mcp-clients.json');
|
|
37
|
+
|
|
38
|
+
// ── Registry I/O ─────────────────────────────────────────────────────────────
|
|
39
|
+
function ensureDir(d) {
|
|
40
|
+
try { if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); } catch {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function loadRegistry() {
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.existsSync(REGISTRY_FILE)) return { servers: {} };
|
|
46
|
+
const raw = fs.readFileSync(REGISTRY_FILE, 'utf8');
|
|
47
|
+
const obj = JSON.parse(raw);
|
|
48
|
+
if (!obj || typeof obj !== 'object' || !obj.servers) return { servers: {} };
|
|
49
|
+
return obj;
|
|
50
|
+
} catch {
|
|
51
|
+
return { servers: {} };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function saveRegistry(reg) {
|
|
56
|
+
ensureDir(REGISTRY_DIR);
|
|
57
|
+
fs.writeFileSync(REGISTRY_FILE, JSON.stringify(reg, null, 2) + '\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Registry mutations ───────────────────────────────────────────────────────
|
|
61
|
+
function addServer({ name, command, args = [], env = {}, description = '' }) {
|
|
62
|
+
if (!name || typeof name !== 'string') throw new Error('addServer: `name` is required');
|
|
63
|
+
if (!/^[a-z0-9][a-z0-9_\-]*$/i.test(name)) {
|
|
64
|
+
throw new Error('addServer: name must be alphanumeric / dash / underscore (e.g. "redis", "github-mcp")');
|
|
65
|
+
}
|
|
66
|
+
if (!command || typeof command !== 'string') throw new Error('addServer: `command` is required');
|
|
67
|
+
const reg = loadRegistry();
|
|
68
|
+
reg.servers[name] = {
|
|
69
|
+
name,
|
|
70
|
+
command,
|
|
71
|
+
args: Array.isArray(args) ? args.map(String) : [],
|
|
72
|
+
env: (env && typeof env === 'object') ? env : {},
|
|
73
|
+
description: String(description || ''),
|
|
74
|
+
added_at: new Date().toISOString(),
|
|
75
|
+
};
|
|
76
|
+
saveRegistry(reg);
|
|
77
|
+
return reg.servers[name];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function removeServer(name) {
|
|
81
|
+
const reg = loadRegistry();
|
|
82
|
+
if (!reg.servers[name]) return false;
|
|
83
|
+
delete reg.servers[name];
|
|
84
|
+
saveRegistry(reg);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function listServers() {
|
|
89
|
+
const reg = loadRegistry();
|
|
90
|
+
return Object.values(reg.servers).sort((a, b) => a.name.localeCompare(b.name));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getServer(name) {
|
|
94
|
+
const reg = loadRegistry();
|
|
95
|
+
return reg.servers[name] || null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── SDK lazy load ────────────────────────────────────────────────────────────
|
|
99
|
+
function loadSdk() {
|
|
100
|
+
try {
|
|
101
|
+
return {
|
|
102
|
+
Client: require('@modelcontextprotocol/sdk/client/index.js').Client,
|
|
103
|
+
StdioClientTransport: require('@modelcontextprotocol/sdk/client/stdio.js').StdioClientTransport,
|
|
104
|
+
};
|
|
105
|
+
} catch (e) {
|
|
106
|
+
const err = new Error(
|
|
107
|
+
'Kodelyth MCP client requires `@modelcontextprotocol/sdk`. ' +
|
|
108
|
+
'Install with `npm install @modelcontextprotocol/sdk` or run via `npx -y kodelyth-ecc ...`. ' +
|
|
109
|
+
`Underlying error: ${e.message}`
|
|
110
|
+
);
|
|
111
|
+
err.code = 'MCP_SDK_MISSING';
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Connection helper ────────────────────────────────────────────────────────
|
|
117
|
+
async function connect(name) {
|
|
118
|
+
const spec = getServer(name);
|
|
119
|
+
if (!spec) {
|
|
120
|
+
const err = new Error(`MCP server "${name}" is not registered. Use \`kodelyth-ecc mcp-add\` first.`);
|
|
121
|
+
err.code = 'MCP_SERVER_NOT_REGISTERED';
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
const { Client, StdioClientTransport } = loadSdk();
|
|
125
|
+
|
|
126
|
+
const transport = new StdioClientTransport({
|
|
127
|
+
command: spec.command,
|
|
128
|
+
args: spec.args,
|
|
129
|
+
env: { ...process.env, ...spec.env },
|
|
130
|
+
});
|
|
131
|
+
const client = new Client(
|
|
132
|
+
{ name: 'kodelyth-ecc-client', version: '1.7.0' },
|
|
133
|
+
{ capabilities: {} }
|
|
134
|
+
);
|
|
135
|
+
await client.connect(transport);
|
|
136
|
+
const close = async () => {
|
|
137
|
+
try { await client.close(); } catch {}
|
|
138
|
+
};
|
|
139
|
+
return { client, transport, close, spec };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Convenience wrappers ─────────────────────────────────────────────────────
|
|
143
|
+
async function withClient(name, fn) {
|
|
144
|
+
const session = await connect(name);
|
|
145
|
+
try {
|
|
146
|
+
return await fn(session.client);
|
|
147
|
+
} finally {
|
|
148
|
+
await session.close();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function listTools(name) { return withClient(name, c => c.listTools()); }
|
|
153
|
+
async function callTool(name, tool, args = {}) {
|
|
154
|
+
return withClient(name, c => c.callTool({ name: tool, arguments: args }));
|
|
155
|
+
}
|
|
156
|
+
async function listResources(name) { return withClient(name, c => c.listResources()); }
|
|
157
|
+
async function readResource(name, uri) {
|
|
158
|
+
return withClient(name, c => c.readResource({ uri }));
|
|
159
|
+
}
|
|
160
|
+
async function listPrompts(name) { return withClient(name, c => c.listPrompts()); }
|
|
161
|
+
async function getPrompt(name, promptName, args = {}) {
|
|
162
|
+
return withClient(name, c => c.getPrompt({ name: promptName, arguments: args }));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
REGISTRY_FILE,
|
|
167
|
+
loadRegistry,
|
|
168
|
+
saveRegistry,
|
|
169
|
+
addServer,
|
|
170
|
+
removeServer,
|
|
171
|
+
listServers,
|
|
172
|
+
getServer,
|
|
173
|
+
connect,
|
|
174
|
+
withClient,
|
|
175
|
+
listTools,
|
|
176
|
+
callTool,
|
|
177
|
+
listResources,
|
|
178
|
+
readResource,
|
|
179
|
+
listPrompts,
|
|
180
|
+
getPrompt,
|
|
181
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kodelyth ECC — MCP Prompts
|
|
3
|
+
// Exposes ECC's intent routing rule, handoff chains, and parallel commands
|
|
4
|
+
// as MCP prompts that any compatible client can summon by name.
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const catalog = require('./catalog');
|
|
10
|
+
|
|
11
|
+
// Each prompt definition:
|
|
12
|
+
// { name, description, arguments?: [{ name, description, required }] }
|
|
13
|
+
const PROMPT_DEFINITIONS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'routing-rule',
|
|
16
|
+
description: 'The full ECC intent routing rule — 10-tier priority system mapping user intent to specialist agents.',
|
|
17
|
+
arguments: [],
|
|
18
|
+
handler: () => {
|
|
19
|
+
const r = catalog.loadRule('agent-intent-routing');
|
|
20
|
+
if (!r) throw new Error('Routing rule not found.');
|
|
21
|
+
return {
|
|
22
|
+
description: 'ECC intent routing rule',
|
|
23
|
+
messages: [{
|
|
24
|
+
role: 'user',
|
|
25
|
+
content: { type: 'text', text: r.body },
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'agents-overview',
|
|
32
|
+
description: 'A compact overview of every ECC agent (name + one-line description). Useful for orientation.',
|
|
33
|
+
arguments: [],
|
|
34
|
+
handler: () => {
|
|
35
|
+
const lines = catalog.loadAgents().map(a =>
|
|
36
|
+
`- **${a.name}** — ${a.description.replace(/\s+/g, ' ').trim().slice(0, 200)}`
|
|
37
|
+
);
|
|
38
|
+
return {
|
|
39
|
+
description: 'ECC agents overview',
|
|
40
|
+
messages: [{
|
|
41
|
+
role: 'user',
|
|
42
|
+
content: {
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: `# Kodelyth ECC — Agents (${lines.length})\n\n${lines.join('\n')}\n`,
|
|
45
|
+
},
|
|
46
|
+
}],
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'skills-overview',
|
|
52
|
+
description: 'A compact overview of every ECC skill (name + one-line description).',
|
|
53
|
+
arguments: [],
|
|
54
|
+
handler: () => {
|
|
55
|
+
const lines = catalog.loadSkills().map(s =>
|
|
56
|
+
`- **${s.name}** — ${s.description.replace(/\s+/g, ' ').trim().slice(0, 200)}`
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
description: 'ECC skills overview',
|
|
60
|
+
messages: [{
|
|
61
|
+
role: 'user',
|
|
62
|
+
content: {
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: `# Kodelyth ECC — Skills (${lines.length})\n\n${lines.join('\n')}\n`,
|
|
65
|
+
},
|
|
66
|
+
}],
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'commands-overview',
|
|
72
|
+
description: 'A compact overview of every ECC slash command.',
|
|
73
|
+
arguments: [],
|
|
74
|
+
handler: () => {
|
|
75
|
+
const lines = catalog.loadCommands().map(c => {
|
|
76
|
+
const hint = c.argumentHint ? ` _(args: ${c.argumentHint})_` : '';
|
|
77
|
+
return `- **/${c.name}**${hint} — ${c.description.replace(/\s+/g, ' ').trim().slice(0, 200)}`;
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
description: 'ECC commands overview',
|
|
81
|
+
messages: [{
|
|
82
|
+
role: 'user',
|
|
83
|
+
content: {
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: `# Kodelyth ECC — Commands (${lines.length})\n\n${lines.join('\n')}\n`,
|
|
86
|
+
},
|
|
87
|
+
}],
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'handoff-chains',
|
|
93
|
+
description: 'Standard ECC multi-agent handoff chains for common workflows (new feature, bug fix, refactor, incident, etc.).',
|
|
94
|
+
arguments: [],
|
|
95
|
+
handler: () => {
|
|
96
|
+
const skill = catalog.findSkill('agent-handoff');
|
|
97
|
+
const text = skill
|
|
98
|
+
? `# Skill: agent-handoff\n\n${skill.description}\n\n---\n\n${skill.body}`
|
|
99
|
+
: '# Handoff chains\n\n(agent-handoff skill not found — fall back to README)';
|
|
100
|
+
return {
|
|
101
|
+
description: 'ECC handoff chains',
|
|
102
|
+
messages: [{
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: { type: 'text', text },
|
|
105
|
+
}],
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'devil-mode',
|
|
111
|
+
description: 'The /devil-mode adversarial parallel command — fires the red-team crew at a target.',
|
|
112
|
+
arguments: [],
|
|
113
|
+
handler: () => {
|
|
114
|
+
const cmd = catalog.findCommand('devil-mode');
|
|
115
|
+
const text = cmd
|
|
116
|
+
? `# Command: /devil-mode\n\n${cmd.description}\n\n---\n\n${cmd.body}`
|
|
117
|
+
: '# /devil-mode\n\n(devil-mode command not found)';
|
|
118
|
+
return {
|
|
119
|
+
description: 'ECC /devil-mode parallel command',
|
|
120
|
+
messages: [{
|
|
121
|
+
role: 'user',
|
|
122
|
+
content: { type: 'text', text },
|
|
123
|
+
}],
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const PROMPT_HANDLERS = Object.fromEntries(
|
|
130
|
+
PROMPT_DEFINITIONS.map(p => [p.name, p.handler])
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
module.exports = { PROMPT_DEFINITIONS, PROMPT_HANDLERS };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kodelyth ECC — MCP Resources
|
|
3
|
+
// Exposes every agent, skill, command, rule, and bundle as an MCP resource.
|
|
4
|
+
// URI scheme:
|
|
5
|
+
// kodelyth://agents/<name>
|
|
6
|
+
// kodelyth://skills/<name>
|
|
7
|
+
// kodelyth://commands/<name>
|
|
8
|
+
// kodelyth://rules/<name>
|
|
9
|
+
// kodelyth://bundles/<name>
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const catalog = require('./catalog');
|
|
15
|
+
|
|
16
|
+
const SCHEME = 'kodelyth';
|
|
17
|
+
|
|
18
|
+
function buildList() {
|
|
19
|
+
const out = [];
|
|
20
|
+
|
|
21
|
+
for (const a of catalog.loadAgents()) {
|
|
22
|
+
out.push({
|
|
23
|
+
uri: `${SCHEME}://agents/${a.name}`,
|
|
24
|
+
name: `Agent: ${a.name}`,
|
|
25
|
+
description: a.description,
|
|
26
|
+
mimeType: 'text/markdown',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
for (const s of catalog.loadSkills()) {
|
|
30
|
+
out.push({
|
|
31
|
+
uri: `${SCHEME}://skills/${s.name}`,
|
|
32
|
+
name: `Skill: ${s.name}`,
|
|
33
|
+
description: s.description,
|
|
34
|
+
mimeType: 'text/markdown',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
for (const c of catalog.loadCommands()) {
|
|
38
|
+
out.push({
|
|
39
|
+
uri: `${SCHEME}://commands/${c.name}`,
|
|
40
|
+
name: `Command: /${c.name}`,
|
|
41
|
+
description: c.description,
|
|
42
|
+
mimeType: 'text/markdown',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
for (const r of catalog.loadAllRules()) {
|
|
46
|
+
out.push({
|
|
47
|
+
uri: `${SCHEME}://rules/${r.name}`,
|
|
48
|
+
name: `Rule: ${r.name}`,
|
|
49
|
+
description: `ECC rule file (${r.relpath})`,
|
|
50
|
+
mimeType: 'text/markdown',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
for (const b of catalog.loadBundles()) {
|
|
54
|
+
out.push({
|
|
55
|
+
uri: `${SCHEME}://bundles/${b.name}`,
|
|
56
|
+
name: `Bundle: ${b.name}`,
|
|
57
|
+
description: `Power bundle cheat sheet (${b.relpath})`,
|
|
58
|
+
mimeType: 'text/markdown',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readResource(uri) {
|
|
65
|
+
const m = /^kodelyth:\/\/(agents|skills|commands|rules|bundles)\/([^/]+)$/.exec(uri || '');
|
|
66
|
+
if (!m) {
|
|
67
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
68
|
+
}
|
|
69
|
+
const kind = m[1];
|
|
70
|
+
const name = decodeURIComponent(m[2]);
|
|
71
|
+
|
|
72
|
+
let body = null;
|
|
73
|
+
if (kind === 'agents') { const a = catalog.findAgent(name); if (a) body = `# Agent: ${a.name}\n\n${a.description}\n\n---\n\n${a.body}`; }
|
|
74
|
+
if (kind === 'skills') { const s = catalog.findSkill(name); if (s) body = `# Skill: ${s.name}\n\n${s.description}\n\n---\n\n${s.body}`; }
|
|
75
|
+
if (kind === 'commands') { const c = catalog.findCommand(name); if (c) body = `# Command: /${c.name}\n\n${c.description}\n\n---\n\n${c.body}`; }
|
|
76
|
+
if (kind === 'rules') { const r = catalog.loadRule(name); if (r) body = r.body; }
|
|
77
|
+
if (kind === 'bundles') {
|
|
78
|
+
const b = catalog.loadBundles().find(x => x.name === name);
|
|
79
|
+
if (b) body = b.body;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (body === null) {
|
|
83
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
contents: [{
|
|
87
|
+
uri,
|
|
88
|
+
mimeType: 'text/markdown',
|
|
89
|
+
text: body,
|
|
90
|
+
}],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { SCHEME, buildList, readResource };
|