create-merlin-brain 3.11.0 → 3.13.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/bin/install.cjs +156 -32
- package/bin/runtime-adapters.cjs +396 -0
- package/dist/server/api/types.d.ts +7 -0
- package/dist/server/api/types.d.ts.map +1 -1
- package/dist/server/cost/tracker.d.ts +38 -2
- package/dist/server/cost/tracker.d.ts.map +1 -1
- package/dist/server/cost/tracker.js +87 -15
- package/dist/server/cost/tracker.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +74 -30
- package/dist/server/server.js.map +1 -1
- package/dist/server/tools/__tests__/augmentation.test.d.ts +8 -0
- package/dist/server/tools/__tests__/augmentation.test.d.ts.map +1 -0
- package/dist/server/tools/__tests__/augmentation.test.js +76 -0
- package/dist/server/tools/__tests__/augmentation.test.js.map +1 -0
- package/dist/server/tools/__tests__/route-helpers.test.d.ts +5 -0
- package/dist/server/tools/__tests__/route-helpers.test.d.ts.map +1 -0
- package/dist/server/tools/__tests__/route-helpers.test.js +49 -0
- package/dist/server/tools/__tests__/route-helpers.test.js.map +1 -0
- package/dist/server/tools/adaptive.js +1 -1
- package/dist/server/tools/adaptive.js.map +1 -1
- package/dist/server/tools/agent-spawn.d.ts +25 -0
- package/dist/server/tools/agent-spawn.d.ts.map +1 -0
- package/dist/server/tools/agent-spawn.js +95 -0
- package/dist/server/tools/agent-spawn.js.map +1 -0
- package/dist/server/tools/agents-index.js +3 -3
- package/dist/server/tools/agents-index.js.map +1 -1
- package/dist/server/tools/agents.js +5 -5
- package/dist/server/tools/agents.js.map +1 -1
- package/dist/server/tools/augmentation.d.ts +45 -0
- package/dist/server/tools/augmentation.d.ts.map +1 -0
- package/dist/server/tools/augmentation.js +167 -0
- package/dist/server/tools/augmentation.js.map +1 -0
- package/dist/server/tools/behaviors.js +4 -4
- package/dist/server/tools/behaviors.js.map +1 -1
- package/dist/server/tools/context.js +7 -7
- package/dist/server/tools/context.js.map +1 -1
- package/dist/server/tools/cost.d.ts +3 -1
- package/dist/server/tools/cost.d.ts.map +1 -1
- package/dist/server/tools/cost.js +66 -13
- package/dist/server/tools/cost.js.map +1 -1
- package/dist/server/tools/discoveries.js +6 -6
- package/dist/server/tools/discoveries.js.map +1 -1
- package/dist/server/tools/index.d.ts +4 -0
- package/dist/server/tools/index.d.ts.map +1 -1
- package/dist/server/tools/index.js +4 -0
- package/dist/server/tools/index.js.map +1 -1
- package/dist/server/tools/learning.d.ts +12 -0
- package/dist/server/tools/learning.d.ts.map +1 -0
- package/dist/server/tools/learning.js +269 -0
- package/dist/server/tools/learning.js.map +1 -0
- package/dist/server/tools/project.js +7 -7
- package/dist/server/tools/project.js.map +1 -1
- package/dist/server/tools/promote.d.ts +11 -0
- package/dist/server/tools/promote.d.ts.map +1 -0
- package/dist/server/tools/promote.js +315 -0
- package/dist/server/tools/promote.js.map +1 -0
- package/dist/server/tools/route-helpers.d.ts +45 -0
- package/dist/server/tools/route-helpers.d.ts.map +1 -0
- package/dist/server/tools/route-helpers.js +93 -0
- package/dist/server/tools/route-helpers.js.map +1 -0
- package/dist/server/tools/route.d.ts +4 -3
- package/dist/server/tools/route.d.ts.map +1 -1
- package/dist/server/tools/route.js +80 -284
- package/dist/server/tools/route.js.map +1 -1
- package/dist/server/tools/session-restore.d.ts +18 -0
- package/dist/server/tools/session-restore.d.ts.map +1 -0
- package/dist/server/tools/session-restore.js +154 -0
- package/dist/server/tools/session-restore.js.map +1 -0
- package/dist/server/tools/session-search.d.ts +16 -0
- package/dist/server/tools/session-search.d.ts.map +1 -0
- package/dist/server/tools/session-search.js +240 -0
- package/dist/server/tools/session-search.js.map +1 -0
- package/dist/server/tools/sights-index.js +2 -2
- package/dist/server/tools/sights-index.js.map +1 -1
- package/dist/server/tools/smart-route.d.ts.map +1 -1
- package/dist/server/tools/smart-route.js +4 -5
- package/dist/server/tools/smart-route.js.map +1 -1
- package/dist/server/tools/verification.js +1 -1
- package/dist/server/tools/verification.js.map +1 -1
- package/files/agents/code-organization-supervisor.md +1 -0
- package/files/agents/context-guardian.md +1 -0
- package/files/agents/docs-keeper.md +1 -0
- package/files/agents/dry-refactor.md +1 -0
- package/files/agents/elite-code-refactorer.md +1 -0
- package/files/agents/hardening-guard.md +1 -0
- package/files/agents/implementation-dev.md +1 -0
- package/files/agents/merlin-access-control-reviewer.md +248 -0
- package/files/agents/merlin-codebase-mapper.md +1 -1
- package/files/agents/merlin-dependency-auditor.md +216 -0
- package/files/agents/merlin-executor.md +1 -0
- package/files/agents/merlin-input-validator.md +247 -0
- package/files/agents/merlin-reviewer.md +1 -0
- package/files/agents/merlin-sast-reviewer.md +182 -0
- package/files/agents/merlin-secret-scanner.md +203 -0
- package/files/agents/tests-qa.md +1 -0
- package/files/commands/merlin/execute-phase.md +94 -197
- package/files/commands/merlin/execute-plan.md +116 -180
- package/files/commands/merlin/health.md +385 -0
- package/files/commands/merlin/loop-recipes.md +93 -36
- package/files/commands/merlin/optimize-prompts.md +158 -0
- package/files/commands/merlin/profiles.md +215 -0
- package/files/commands/merlin/promote.md +176 -0
- package/files/commands/merlin/quick.md +229 -0
- package/files/commands/merlin/resume-work.md +27 -1
- package/files/commands/merlin/route.md +43 -1
- package/files/commands/merlin/sandbox.md +359 -0
- package/files/commands/merlin/usage.md +55 -0
- package/files/docker/Dockerfile.merlin +20 -0
- package/files/docker/docker-compose.merlin.yml +23 -0
- package/files/hook-templates/auto-commit.sh +64 -0
- package/files/hook-templates/auto-format.sh +95 -0
- package/files/hook-templates/auto-test.sh +117 -0
- package/files/hook-templates/branch-protection.sh +72 -0
- package/files/hook-templates/changelog-reminder.sh +76 -0
- package/files/hook-templates/complexity-check.sh +112 -0
- package/files/hook-templates/import-audit.sh +83 -0
- package/files/hook-templates/license-header.sh +84 -0
- package/files/hook-templates/pr-description.sh +100 -0
- package/files/hook-templates/todo-tracker.sh +80 -0
- package/files/hooks/check-file-size.sh +17 -4
- package/files/hooks/config-change.sh +44 -16
- package/files/hooks/instructions-loaded.sh +22 -5
- package/files/hooks/notify-desktop.sh +157 -0
- package/files/hooks/notify-webhook.sh +141 -0
- package/files/hooks/pre-edit-sights-check.sh +76 -9
- package/files/hooks/security-scanner.sh +153 -0
- package/files/hooks/session-end-memory-sync.sh +97 -0
- package/files/hooks/session-end.sh +274 -1
- package/files/hooks/session-start.sh +19 -6
- package/files/hooks/smart-approve.sh +270 -0
- package/files/hooks/teammate-idle-verify.sh +87 -12
- package/files/hooks/worktree-create.sh +20 -3
- package/files/hooks/worktree-remove.sh +21 -3
- package/files/merlin/references/plan-format.md +37 -9
- package/files/merlin/sandbox.json +9 -0
- package/files/merlin/security.json +11 -0
- package/files/merlin/templates/ci/docs-update.yml +81 -0
- package/files/merlin/templates/ci/pr-review.yml +50 -0
- package/files/merlin/templates/ci/security-audit.yml +74 -0
- package/files/merlin/templates/config.json +9 -1
- package/files/rules/api-rules.md +30 -0
- package/files/rules/frontend-rules.md +25 -0
- package/files/rules/hooks-rules.md +36 -0
- package/files/rules/mcp-rules.md +30 -0
- package/files/rules/worker-rules.md +29 -0
- package/package.json +5 -2
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// MERLIN RUNTIME ADAPTERS
|
|
2
|
+
// Detects and configures Merlin for non-Claude-Code runtimes.
|
|
3
|
+
// Claude Code is always handled by the main installer (install.cjs).
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Runtime definitions
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
const RUNTIMES = [
|
|
17
|
+
{
|
|
18
|
+
id: 'codex',
|
|
19
|
+
label: 'Codex CLI',
|
|
20
|
+
configDir: path.join(os.homedir(), '.codex'),
|
|
21
|
+
binary: 'codex',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'opencode',
|
|
25
|
+
label: 'OpenCode',
|
|
26
|
+
configDir: path.join(os.homedir(), '.opencode'),
|
|
27
|
+
binary: 'opencode',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'gemini',
|
|
31
|
+
label: 'Gemini CLI',
|
|
32
|
+
configDir: path.join(os.homedir(), '.gemini'),
|
|
33
|
+
binary: 'gemini',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Detection helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function inPath(binary) {
|
|
42
|
+
try {
|
|
43
|
+
execSync(`which ${binary} 2>/dev/null`, { stdio: 'pipe' });
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function detectRuntimes() {
|
|
51
|
+
return RUNTIMES.map((rt) => ({
|
|
52
|
+
...rt,
|
|
53
|
+
found: fs.existsSync(rt.configDir) || inPath(rt.binary),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// MCP server command helper (mirrors logic in install.cjs)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
function buildMcpCommand(useGlobalBinary) {
|
|
62
|
+
if (useGlobalBinary) {
|
|
63
|
+
return { command: 'merlin-brain', args: undefined };
|
|
64
|
+
}
|
|
65
|
+
return { command: 'node', args: [path.join(__dirname, 'serve.js')] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// AGENTS.md content for instruction-file runtimes
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
function buildAgentsMd() {
|
|
73
|
+
return `# Merlin Brain — AI Development System
|
|
74
|
+
|
|
75
|
+
> Installed by Merlin (https://merlin.build). Keep this file to preserve Merlin context.
|
|
76
|
+
>
|
|
77
|
+
> Note: Tool names in these instructions reflect Claude Code conventions (Read, Write, Edit,
|
|
78
|
+
> Bash, Grep, Glob). Names may differ in your runtime — adapt as needed.
|
|
79
|
+
|
|
80
|
+
## Boot Sequence (run at session start)
|
|
81
|
+
|
|
82
|
+
1. Call \`merlin_get_selected_repo\` — connect Merlin Sights to this project.
|
|
83
|
+
2. Call \`merlin_get_project_status\` — load current project state and active tasks.
|
|
84
|
+
3. Show a brief status summary, then handle the user's request.
|
|
85
|
+
|
|
86
|
+
Do NOT skip these steps. Do NOT start working without Merlin context.
|
|
87
|
+
|
|
88
|
+
## Before Every File Edit
|
|
89
|
+
|
|
90
|
+
Call \`merlin_get_context("your task")\` before writing or modifying any code.
|
|
91
|
+
|
|
92
|
+
## Routing Specialist Work
|
|
93
|
+
|
|
94
|
+
Route tasks to the right specialist agent:
|
|
95
|
+
- Architecture decisions → system-architect
|
|
96
|
+
- Implementation → implementation-dev
|
|
97
|
+
- Testing → tests-qa
|
|
98
|
+
- Security review → merlin-security
|
|
99
|
+
- Documentation → docs-keeper
|
|
100
|
+
|
|
101
|
+
## MCP Tools Available
|
|
102
|
+
|
|
103
|
+
The Merlin MCP server exposes these tools:
|
|
104
|
+
- \`merlin_get_selected_repo\` — identify current repository
|
|
105
|
+
- \`merlin_get_project_status\` — load tasks and project state
|
|
106
|
+
- \`merlin_get_context(task)\` — fetch relevant code context for a task
|
|
107
|
+
- \`merlin_find_files(query)\` — locate files by description
|
|
108
|
+
- \`merlin_search(query)\` — semantic code search
|
|
109
|
+
|
|
110
|
+
## Core Principles
|
|
111
|
+
|
|
112
|
+
- Always search before writing — reuse existing functions and patterns.
|
|
113
|
+
- Keep files under 400 lines — split into sub-modules when approaching the limit.
|
|
114
|
+
- Graceful failure — if Merlin Sights is unavailable, continue with file exploration.
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Codex CLI adapter
|
|
120
|
+
// Writes: ~/.codex/AGENTS.md + ~/.codex/config.toml (MCP section)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
function configureCodex(rt, useGlobalBinary, apiKey) {
|
|
124
|
+
const results = [];
|
|
125
|
+
|
|
126
|
+
// Ensure config dir exists
|
|
127
|
+
if (!fs.existsSync(rt.configDir)) {
|
|
128
|
+
fs.mkdirSync(rt.configDir, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Write AGENTS.md
|
|
132
|
+
const agentsMdPath = path.join(rt.configDir, 'AGENTS.md');
|
|
133
|
+
const agentsMd = buildAgentsMd();
|
|
134
|
+
const existingAgentsMd = fs.existsSync(agentsMdPath)
|
|
135
|
+
? fs.readFileSync(agentsMdPath, 'utf8')
|
|
136
|
+
: '';
|
|
137
|
+
|
|
138
|
+
if (existingAgentsMd.includes('Merlin Brain')) {
|
|
139
|
+
results.push('AGENTS.md already has Merlin (skipped)');
|
|
140
|
+
} else {
|
|
141
|
+
const combined = existingAgentsMd
|
|
142
|
+
? existingAgentsMd.trimEnd() + '\n\n---\n\n' + agentsMd
|
|
143
|
+
: agentsMd;
|
|
144
|
+
fs.writeFileSync(agentsMdPath, combined);
|
|
145
|
+
results.push('Wrote ~/.codex/AGENTS.md');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Write / update config.toml with MCP section
|
|
149
|
+
const configTomlPath = path.join(rt.configDir, 'config.toml');
|
|
150
|
+
const { command, args } = buildMcpCommand(useGlobalBinary);
|
|
151
|
+
const argsToml = args
|
|
152
|
+
? `args = [${args.map((a) => `"${a}"`).join(', ')}]`
|
|
153
|
+
: '';
|
|
154
|
+
const envToml = apiKey ? `\n MERLIN_API_KEY = "${apiKey}"` : '';
|
|
155
|
+
const mcpToml = `
|
|
156
|
+
[mcp.merlin]
|
|
157
|
+
command = "${command}"
|
|
158
|
+
${argsToml ? argsToml + '\n' : ''}${apiKey ? `[mcp.merlin.env]${envToml}\n` : ''}`;
|
|
159
|
+
|
|
160
|
+
let tomlContent = fs.existsSync(configTomlPath)
|
|
161
|
+
? fs.readFileSync(configTomlPath, 'utf8')
|
|
162
|
+
: '';
|
|
163
|
+
|
|
164
|
+
if (tomlContent.includes('[mcp.merlin]')) {
|
|
165
|
+
results.push('config.toml already has Merlin MCP (skipped)');
|
|
166
|
+
} else {
|
|
167
|
+
fs.appendFileSync(configTomlPath, mcpToml);
|
|
168
|
+
results.push('Added Merlin MCP to ~/.codex/config.toml');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// OpenCode adapter
|
|
176
|
+
// Writes: ~/.opencode/config.json (mcpServers section) + ~/.opencode/AGENTS.md
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
function configureOpenCode(rt, useGlobalBinary, apiKey) {
|
|
180
|
+
const results = [];
|
|
181
|
+
|
|
182
|
+
if (!fs.existsSync(rt.configDir)) {
|
|
183
|
+
fs.mkdirSync(rt.configDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// MCP config JSON
|
|
187
|
+
const configJsonPath = path.join(rt.configDir, 'config.json');
|
|
188
|
+
let config = {};
|
|
189
|
+
if (fs.existsSync(configJsonPath)) {
|
|
190
|
+
try {
|
|
191
|
+
config = JSON.parse(fs.readFileSync(configJsonPath, 'utf8'));
|
|
192
|
+
} catch {
|
|
193
|
+
config = {};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
config.mcpServers = config.mcpServers || {};
|
|
198
|
+
if (config.mcpServers.merlin) {
|
|
199
|
+
results.push('config.json already has Merlin MCP (skipped)');
|
|
200
|
+
} else {
|
|
201
|
+
const { command, args } = buildMcpCommand(useGlobalBinary);
|
|
202
|
+
const mcpEntry = { command, type: 'stdio' };
|
|
203
|
+
if (args) mcpEntry.args = args;
|
|
204
|
+
if (apiKey) mcpEntry.env = { MERLIN_API_KEY: apiKey };
|
|
205
|
+
config.mcpServers.merlin = mcpEntry;
|
|
206
|
+
fs.writeFileSync(configJsonPath, JSON.stringify(config, null, 2));
|
|
207
|
+
results.push('Added Merlin MCP to ~/.opencode/config.json');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Instructions file
|
|
211
|
+
const agentsMdPath = path.join(rt.configDir, 'AGENTS.md');
|
|
212
|
+
const existingAgentsMd = fs.existsSync(agentsMdPath)
|
|
213
|
+
? fs.readFileSync(agentsMdPath, 'utf8')
|
|
214
|
+
: '';
|
|
215
|
+
if (existingAgentsMd.includes('Merlin Brain')) {
|
|
216
|
+
results.push('AGENTS.md already has Merlin (skipped)');
|
|
217
|
+
} else {
|
|
218
|
+
const combined = existingAgentsMd
|
|
219
|
+
? existingAgentsMd.trimEnd() + '\n\n---\n\n' + buildAgentsMd()
|
|
220
|
+
: buildAgentsMd();
|
|
221
|
+
fs.writeFileSync(agentsMdPath, combined);
|
|
222
|
+
results.push('Wrote ~/.opencode/AGENTS.md');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return results;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Gemini CLI adapter
|
|
230
|
+
// Writes: ~/.gemini/GEMINI.md + ~/.gemini/settings.json (mcpServers section)
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
function configureGemini(rt, useGlobalBinary, apiKey) {
|
|
234
|
+
const results = [];
|
|
235
|
+
|
|
236
|
+
if (!fs.existsSync(rt.configDir)) {
|
|
237
|
+
fs.mkdirSync(rt.configDir, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// GEMINI.md instructions file
|
|
241
|
+
const geminiMdPath = path.join(rt.configDir, 'GEMINI.md');
|
|
242
|
+
const existingGeminiMd = fs.existsSync(geminiMdPath)
|
|
243
|
+
? fs.readFileSync(geminiMdPath, 'utf8')
|
|
244
|
+
: '';
|
|
245
|
+
if (existingGeminiMd.includes('Merlin Brain')) {
|
|
246
|
+
results.push('GEMINI.md already has Merlin (skipped)');
|
|
247
|
+
} else {
|
|
248
|
+
const combined = existingGeminiMd
|
|
249
|
+
? existingGeminiMd.trimEnd() + '\n\n---\n\n' + buildAgentsMd()
|
|
250
|
+
: buildAgentsMd();
|
|
251
|
+
fs.writeFileSync(geminiMdPath, combined);
|
|
252
|
+
results.push('Wrote ~/.gemini/GEMINI.md');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// settings.json MCP config
|
|
256
|
+
const settingsPath = path.join(rt.configDir, 'settings.json');
|
|
257
|
+
let settings = {};
|
|
258
|
+
if (fs.existsSync(settingsPath)) {
|
|
259
|
+
try {
|
|
260
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
261
|
+
} catch {
|
|
262
|
+
settings = {};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
267
|
+
if (settings.mcpServers.merlin) {
|
|
268
|
+
results.push('settings.json already has Merlin MCP (skipped)');
|
|
269
|
+
} else {
|
|
270
|
+
const { command, args } = buildMcpCommand(useGlobalBinary);
|
|
271
|
+
const mcpEntry = { command };
|
|
272
|
+
if (args) mcpEntry.args = args;
|
|
273
|
+
if (apiKey) mcpEntry.env = { MERLIN_API_KEY: apiKey };
|
|
274
|
+
settings.mcpServers.merlin = mcpEntry;
|
|
275
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
276
|
+
results.push('Added Merlin MCP to ~/.gemini/settings.json');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return results;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Per-project runtime files
|
|
284
|
+
// Writes AGENTS.md alongside CLAUDE.md when initializing a project.
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Generate per-project runtime instruction files next to a CLAUDE.md.
|
|
289
|
+
* Call this whenever Merlin creates or updates a project's CLAUDE.md.
|
|
290
|
+
*
|
|
291
|
+
* @param {string} projectDir - Absolute path to the project directory
|
|
292
|
+
* @param {string[]} runtimeIds - Which runtime files to generate ('codex'|'opencode'|'gemini'|'all')
|
|
293
|
+
*/
|
|
294
|
+
function generateProjectRuntimeFiles(projectDir, runtimeIds = ['all']) {
|
|
295
|
+
const results = [];
|
|
296
|
+
const targets = runtimeIds.includes('all')
|
|
297
|
+
? ['codex', 'opencode', 'gemini']
|
|
298
|
+
: runtimeIds;
|
|
299
|
+
|
|
300
|
+
const agentsMd = buildAgentsMd();
|
|
301
|
+
|
|
302
|
+
// AGENTS.md — used by Codex and OpenCode
|
|
303
|
+
if (targets.includes('codex') || targets.includes('opencode')) {
|
|
304
|
+
const agentsMdPath = path.join(projectDir, 'AGENTS.md');
|
|
305
|
+
if (!fs.existsSync(agentsMdPath)) {
|
|
306
|
+
fs.writeFileSync(agentsMdPath, agentsMd);
|
|
307
|
+
results.push(`Created ${agentsMdPath}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// GEMINI.md — used by Gemini CLI
|
|
312
|
+
if (targets.includes('gemini')) {
|
|
313
|
+
const geminiMdPath = path.join(projectDir, 'GEMINI.md');
|
|
314
|
+
if (!fs.existsSync(geminiMdPath)) {
|
|
315
|
+
fs.writeFileSync(geminiMdPath, agentsMd);
|
|
316
|
+
results.push(`Created ${geminiMdPath}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return results;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// Main entry point called from install.cjs
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Detect and configure all non-Claude-Code runtimes.
|
|
329
|
+
*
|
|
330
|
+
* @param {object} opts
|
|
331
|
+
* @param {string} opts.runtimeFlag - Value of --runtime flag ('all'|'claude'|runtime-id)
|
|
332
|
+
* @param {boolean} opts.useGlobalBinary - Whether merlin-brain global binary is available
|
|
333
|
+
* @param {string} opts.apiKey - Merlin Sights API key (may be empty)
|
|
334
|
+
* @param {Function} opts.logSuccess - Logging helper
|
|
335
|
+
* @param {Function} opts.logWarn - Logging helper
|
|
336
|
+
* @param {Function} opts.logInfo - Plain log helper (console.log compatible)
|
|
337
|
+
* @param {boolean} [opts.quiet] - If true, skip printing the detection summary
|
|
338
|
+
* @returns {{ detected: object[], configured: string[] }}
|
|
339
|
+
*/
|
|
340
|
+
function configureRuntimes({ runtimeFlag, useGlobalBinary, apiKey, logSuccess, logWarn, logInfo, quiet }) {
|
|
341
|
+
const detected = detectRuntimes();
|
|
342
|
+
|
|
343
|
+
// Print detection summary (skipped when caller already printed it)
|
|
344
|
+
if (!quiet) {
|
|
345
|
+
for (const rt of detected) {
|
|
346
|
+
const icon = rt.found ? '\u2705' : '\u2B1A';
|
|
347
|
+
const suffix = rt.found ? '' : ' (not found)';
|
|
348
|
+
logInfo(` ${icon} ${rt.label}${suffix}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Determine which runtimes to configure
|
|
353
|
+
const skip = runtimeFlag === 'claude'; // --runtime claude → Claude Code only
|
|
354
|
+
if (skip) {
|
|
355
|
+
if (!quiet) logInfo(' Skipping other runtimes (--runtime claude)');
|
|
356
|
+
return { detected, configured: [] };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const filterToOne = !['all', 'claude', undefined, ''].includes(runtimeFlag);
|
|
360
|
+
const targetRuntimes = filterToOne
|
|
361
|
+
? detected.filter((rt) => rt.found && rt.id === runtimeFlag)
|
|
362
|
+
: detected.filter((rt) => rt.found);
|
|
363
|
+
|
|
364
|
+
const configured = [];
|
|
365
|
+
|
|
366
|
+
for (const rt of targetRuntimes) {
|
|
367
|
+
let results = [];
|
|
368
|
+
try {
|
|
369
|
+
if (rt.id === 'codex') {
|
|
370
|
+
results = configureCodex(rt, useGlobalBinary, apiKey);
|
|
371
|
+
} else if (rt.id === 'opencode') {
|
|
372
|
+
results = configureOpenCode(rt, useGlobalBinary, apiKey);
|
|
373
|
+
} else if (rt.id === 'gemini') {
|
|
374
|
+
results = configureGemini(rt, useGlobalBinary, apiKey);
|
|
375
|
+
} else {
|
|
376
|
+
logWarn(` Unknown runtime "${rt.id}" — skipped`);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
for (const msg of results) {
|
|
380
|
+
logSuccess(msg);
|
|
381
|
+
configured.push(msg);
|
|
382
|
+
}
|
|
383
|
+
} catch (err) {
|
|
384
|
+
logWarn(` Failed to configure ${rt.label}: ${err.message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return { detected, configured };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
module.exports = {
|
|
392
|
+
detectRuntimes,
|
|
393
|
+
configureRuntimes,
|
|
394
|
+
generateProjectRuntimeFiles,
|
|
395
|
+
buildAgentsMd,
|
|
396
|
+
};
|
|
@@ -196,6 +196,13 @@ export interface AgentIndexResult {
|
|
|
196
196
|
rating: number;
|
|
197
197
|
userThumbsUp: number;
|
|
198
198
|
}
|
|
199
|
+
/** Augmentation data injected into agent handoff from cloud catalog */
|
|
200
|
+
export interface AugmentationResult {
|
|
201
|
+
slug: string;
|
|
202
|
+
grade: string;
|
|
203
|
+
description: string;
|
|
204
|
+
systemPrompt: string;
|
|
205
|
+
}
|
|
199
206
|
/** Agent index detail */
|
|
200
207
|
export interface AgentIndexDetail extends AgentIndexResult {
|
|
201
208
|
systemPrompt: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/api/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/api/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,11 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tracks routing decisions and estimates cost savings vs always using Opus.
|
|
5
5
|
* Resets on each MCP server restart (per session).
|
|
6
|
+
*
|
|
7
|
+
* v2: Adds per-agent breakdown, token counts, and token-based pricing.
|
|
6
8
|
*/
|
|
9
|
+
export declare const MODEL_PRICING: Record<string, {
|
|
10
|
+
inputPerM: number;
|
|
11
|
+
outputPerM: number;
|
|
12
|
+
}>;
|
|
7
13
|
export interface ModelUsage {
|
|
8
14
|
calls: number;
|
|
9
15
|
estimatedCost: number;
|
|
10
16
|
}
|
|
17
|
+
export interface AgentUsage {
|
|
18
|
+
calls: number;
|
|
19
|
+
inputTokens: number;
|
|
20
|
+
outputTokens: number;
|
|
21
|
+
estimatedCost: number;
|
|
22
|
+
}
|
|
23
|
+
export interface TokenCounts {
|
|
24
|
+
totalInput: number;
|
|
25
|
+
totalOutput: number;
|
|
26
|
+
cacheRead: number;
|
|
27
|
+
cacheWrite: number;
|
|
28
|
+
}
|
|
11
29
|
export interface SessionCost {
|
|
12
30
|
sessionId: string;
|
|
13
31
|
totalCalls: number;
|
|
@@ -16,15 +34,33 @@ export interface SessionCost {
|
|
|
16
34
|
sonnet: ModelUsage;
|
|
17
35
|
opus: ModelUsage;
|
|
18
36
|
};
|
|
37
|
+
byAgent: Record<string, AgentUsage>;
|
|
38
|
+
tokenCounts: TokenCounts;
|
|
19
39
|
savedVsBaseline: number;
|
|
20
40
|
totalEstimatedCost: number;
|
|
21
41
|
}
|
|
42
|
+
export interface RoutingRecord {
|
|
43
|
+
model: string;
|
|
44
|
+
taskComplexity: string;
|
|
45
|
+
agent?: string;
|
|
46
|
+
inputTokens?: number;
|
|
47
|
+
outputTokens?: number;
|
|
48
|
+
cacheReadTokens?: number;
|
|
49
|
+
cacheWriteTokens?: number;
|
|
50
|
+
}
|
|
22
51
|
/**
|
|
23
52
|
* Record a routing decision. Call this each time a task is routed to an agent.
|
|
53
|
+
* Supports optional token counts for accurate pricing; falls back to flat-rate.
|
|
24
54
|
*/
|
|
25
|
-
export declare function recordRouting(model: string,
|
|
55
|
+
export declare function recordRouting(model: string, taskComplexity: string, opts?: {
|
|
56
|
+
agent?: string;
|
|
57
|
+
inputTokens?: number;
|
|
58
|
+
outputTokens?: number;
|
|
59
|
+
cacheReadTokens?: number;
|
|
60
|
+
cacheWriteTokens?: number;
|
|
61
|
+
}): void;
|
|
26
62
|
/**
|
|
27
|
-
* Get a snapshot of current session costs.
|
|
63
|
+
* Get a snapshot of current session costs (deep copy).
|
|
28
64
|
*/
|
|
29
65
|
export declare function getSessionCost(): SessionCost;
|
|
30
66
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/server/cost/tracker.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/server/cost/tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAInF,CAAC;AAeF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,KAAK,EAAE,UAAU,CAAC;QAClB,MAAM,EAAE,UAAU,CAAC;QACnB,IAAI,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAoED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,IAAI,GAAE;IACJ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACtB,GACL,IAAI,CA2CN;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAgB5C;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CASnC"}
|
|
@@ -3,17 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tracks routing decisions and estimates cost savings vs always using Opus.
|
|
5
5
|
* Resets on each MCP server restart (per session).
|
|
6
|
+
*
|
|
7
|
+
* v2: Adds per-agent breakdown, token counts, and token-based pricing.
|
|
6
8
|
*/
|
|
7
9
|
import { randomUUID } from 'crypto';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
// Token-based pricing per million tokens (input / output)
|
|
14
|
+
export const MODEL_PRICING = {
|
|
15
|
+
haiku: { inputPerM: 0.25, outputPerM: 1.25 },
|
|
16
|
+
sonnet: { inputPerM: 3.00, outputPerM: 15.00 },
|
|
17
|
+
opus: { inputPerM: 15.00, outputPerM: 75.00 },
|
|
18
|
+
};
|
|
19
|
+
// Fallback per-call estimate when token counts are not available.
|
|
20
|
+
// These mirror the original values so existing behaviour is preserved.
|
|
21
|
+
const COST_PER_CALL_FALLBACK = {
|
|
11
22
|
haiku: 0.003,
|
|
12
23
|
sonnet: 0.015,
|
|
13
24
|
opus: 0.075,
|
|
14
25
|
};
|
|
15
|
-
//
|
|
16
|
-
const
|
|
26
|
+
// Opus baseline for savings calculation (per call, no token data)
|
|
27
|
+
const OPUS_BASELINE_PER_CALL = COST_PER_CALL_FALLBACK.opus;
|
|
28
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
17
29
|
/** Normalize model string to haiku | sonnet | opus key */
|
|
18
30
|
function normalizeModel(model) {
|
|
19
31
|
const lower = model.toLowerCase();
|
|
@@ -23,7 +35,35 @@ function normalizeModel(model) {
|
|
|
23
35
|
return 'sonnet';
|
|
24
36
|
return 'opus';
|
|
25
37
|
}
|
|
26
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Estimate call cost.
|
|
40
|
+
* Uses token counts when available; falls back to per-call flat rate.
|
|
41
|
+
*/
|
|
42
|
+
function estimateCallCost(modelKey, inputTokens, outputTokens) {
|
|
43
|
+
if (inputTokens > 0 || outputTokens > 0) {
|
|
44
|
+
const pricing = MODEL_PRICING[modelKey];
|
|
45
|
+
return (inputTokens / 1_000_000) * pricing.inputPerM
|
|
46
|
+
+ (outputTokens / 1_000_000) * pricing.outputPerM;
|
|
47
|
+
}
|
|
48
|
+
return COST_PER_CALL_FALLBACK[modelKey];
|
|
49
|
+
}
|
|
50
|
+
function round6(n) {
|
|
51
|
+
return parseFloat(n.toFixed(6));
|
|
52
|
+
}
|
|
53
|
+
// ─── Persistence ─────────────────────────────────────────────────────────────
|
|
54
|
+
const COST_DIR = join(homedir(), '.claude', 'merlin');
|
|
55
|
+
const COST_FILE = join(COST_DIR, 'session-cost.json');
|
|
56
|
+
/** Write current state to disk so session-end.sh can read it. Fire-and-forget. */
|
|
57
|
+
function persistCost() {
|
|
58
|
+
try {
|
|
59
|
+
mkdirSync(COST_DIR, { recursive: true });
|
|
60
|
+
writeFileSync(COST_FILE, JSON.stringify(state, null, 2), 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Non-fatal — in-memory state is authoritative
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ─── Singleton state ──────────────────────────────────────────────────────────
|
|
27
67
|
const SESSION_ID = randomUUID();
|
|
28
68
|
const state = {
|
|
29
69
|
sessionId: SESSION_ID,
|
|
@@ -33,26 +73,54 @@ const state = {
|
|
|
33
73
|
sonnet: { calls: 0, estimatedCost: 0 },
|
|
34
74
|
opus: { calls: 0, estimatedCost: 0 },
|
|
35
75
|
},
|
|
76
|
+
byAgent: {},
|
|
77
|
+
tokenCounts: { totalInput: 0, totalOutput: 0, cacheRead: 0, cacheWrite: 0 },
|
|
36
78
|
savedVsBaseline: 0,
|
|
37
79
|
totalEstimatedCost: 0,
|
|
38
80
|
};
|
|
39
81
|
/**
|
|
40
82
|
* Record a routing decision. Call this each time a task is routed to an agent.
|
|
83
|
+
* Supports optional token counts for accurate pricing; falls back to flat-rate.
|
|
41
84
|
*/
|
|
42
|
-
export function recordRouting(model,
|
|
85
|
+
export function recordRouting(model, taskComplexity, opts = {}) {
|
|
43
86
|
const key = normalizeModel(model);
|
|
44
|
-
const
|
|
87
|
+
const inputTokens = opts.inputTokens ?? 0;
|
|
88
|
+
const outputTokens = opts.outputTokens ?? 0;
|
|
89
|
+
const cacheRead = opts.cacheReadTokens ?? 0;
|
|
90
|
+
const cacheWrite = opts.cacheWriteTokens ?? 0;
|
|
91
|
+
const cost = estimateCallCost(key, inputTokens, outputTokens);
|
|
92
|
+
// ── byModel ──────────────────────────────────────────────────────────────
|
|
45
93
|
state.totalCalls += 1;
|
|
46
94
|
state.byModel[key].calls += 1;
|
|
47
|
-
state.byModel[key].estimatedCost =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
state.
|
|
95
|
+
state.byModel[key].estimatedCost = round6(state.byModel[key].estimatedCost + cost);
|
|
96
|
+
// ── totals ────────────────────────────────────────────────────────────────
|
|
97
|
+
state.totalEstimatedCost = round6(state.totalEstimatedCost + cost);
|
|
98
|
+
// ── token counts ──────────────────────────────────────────────────────────
|
|
99
|
+
state.tokenCounts.totalInput += inputTokens;
|
|
100
|
+
state.tokenCounts.totalOutput += outputTokens;
|
|
101
|
+
state.tokenCounts.cacheRead += cacheRead;
|
|
102
|
+
state.tokenCounts.cacheWrite += cacheWrite;
|
|
103
|
+
// ── byAgent ───────────────────────────────────────────────────────────────
|
|
104
|
+
const agentKey = opts.agent ?? 'unknown';
|
|
105
|
+
if (!state.byAgent[agentKey]) {
|
|
106
|
+
state.byAgent[agentKey] = { calls: 0, inputTokens: 0, outputTokens: 0, estimatedCost: 0 };
|
|
107
|
+
}
|
|
108
|
+
const agentEntry = state.byAgent[agentKey];
|
|
109
|
+
agentEntry.calls += 1;
|
|
110
|
+
agentEntry.inputTokens += inputTokens;
|
|
111
|
+
agentEntry.outputTokens += outputTokens;
|
|
112
|
+
agentEntry.estimatedCost = round6(agentEntry.estimatedCost + cost);
|
|
113
|
+
// ── savings vs all-Opus baseline ─────────────────────────────────────────
|
|
114
|
+
// Baseline uses per-call flat rate (Opus) since we can't know what token
|
|
115
|
+
// counts would have been on Opus.
|
|
116
|
+
const baselineCost = OPUS_BASELINE_PER_CALL;
|
|
117
|
+
const saved = baselineCost - cost;
|
|
118
|
+
state.savedVsBaseline = round6(state.savedVsBaseline + saved);
|
|
119
|
+
// Persist so session-end.sh can read it without querying the MCP
|
|
120
|
+
persistCost();
|
|
53
121
|
}
|
|
54
122
|
/**
|
|
55
|
-
* Get a snapshot of current session costs.
|
|
123
|
+
* Get a snapshot of current session costs (deep copy).
|
|
56
124
|
*/
|
|
57
125
|
export function getSessionCost() {
|
|
58
126
|
return {
|
|
@@ -63,6 +131,8 @@ export function getSessionCost() {
|
|
|
63
131
|
sonnet: { ...state.byModel.sonnet },
|
|
64
132
|
opus: { ...state.byModel.opus },
|
|
65
133
|
},
|
|
134
|
+
byAgent: Object.fromEntries(Object.entries(state.byAgent).map(([k, v]) => [k, { ...v }])),
|
|
135
|
+
tokenCounts: { ...state.tokenCounts },
|
|
66
136
|
savedVsBaseline: state.savedVsBaseline,
|
|
67
137
|
totalEstimatedCost: state.totalEstimatedCost,
|
|
68
138
|
};
|
|
@@ -75,6 +145,8 @@ export function resetSession() {
|
|
|
75
145
|
state.byModel.haiku = { calls: 0, estimatedCost: 0 };
|
|
76
146
|
state.byModel.sonnet = { calls: 0, estimatedCost: 0 };
|
|
77
147
|
state.byModel.opus = { calls: 0, estimatedCost: 0 };
|
|
148
|
+
state.byAgent = {};
|
|
149
|
+
state.tokenCounts = { totalInput: 0, totalOutput: 0, cacheRead: 0, cacheWrite: 0 };
|
|
78
150
|
state.savedVsBaseline = 0;
|
|
79
151
|
state.totalEstimatedCost = 0;
|
|
80
152
|
}
|