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.
Files changed (147) hide show
  1. package/bin/install.cjs +156 -32
  2. package/bin/runtime-adapters.cjs +396 -0
  3. package/dist/server/api/types.d.ts +7 -0
  4. package/dist/server/api/types.d.ts.map +1 -1
  5. package/dist/server/cost/tracker.d.ts +38 -2
  6. package/dist/server/cost/tracker.d.ts.map +1 -1
  7. package/dist/server/cost/tracker.js +87 -15
  8. package/dist/server/cost/tracker.js.map +1 -1
  9. package/dist/server/server.d.ts.map +1 -1
  10. package/dist/server/server.js +74 -30
  11. package/dist/server/server.js.map +1 -1
  12. package/dist/server/tools/__tests__/augmentation.test.d.ts +8 -0
  13. package/dist/server/tools/__tests__/augmentation.test.d.ts.map +1 -0
  14. package/dist/server/tools/__tests__/augmentation.test.js +76 -0
  15. package/dist/server/tools/__tests__/augmentation.test.js.map +1 -0
  16. package/dist/server/tools/__tests__/route-helpers.test.d.ts +5 -0
  17. package/dist/server/tools/__tests__/route-helpers.test.d.ts.map +1 -0
  18. package/dist/server/tools/__tests__/route-helpers.test.js +49 -0
  19. package/dist/server/tools/__tests__/route-helpers.test.js.map +1 -0
  20. package/dist/server/tools/adaptive.js +1 -1
  21. package/dist/server/tools/adaptive.js.map +1 -1
  22. package/dist/server/tools/agent-spawn.d.ts +25 -0
  23. package/dist/server/tools/agent-spawn.d.ts.map +1 -0
  24. package/dist/server/tools/agent-spawn.js +95 -0
  25. package/dist/server/tools/agent-spawn.js.map +1 -0
  26. package/dist/server/tools/agents-index.js +3 -3
  27. package/dist/server/tools/agents-index.js.map +1 -1
  28. package/dist/server/tools/agents.js +5 -5
  29. package/dist/server/tools/agents.js.map +1 -1
  30. package/dist/server/tools/augmentation.d.ts +45 -0
  31. package/dist/server/tools/augmentation.d.ts.map +1 -0
  32. package/dist/server/tools/augmentation.js +167 -0
  33. package/dist/server/tools/augmentation.js.map +1 -0
  34. package/dist/server/tools/behaviors.js +4 -4
  35. package/dist/server/tools/behaviors.js.map +1 -1
  36. package/dist/server/tools/context.js +7 -7
  37. package/dist/server/tools/context.js.map +1 -1
  38. package/dist/server/tools/cost.d.ts +3 -1
  39. package/dist/server/tools/cost.d.ts.map +1 -1
  40. package/dist/server/tools/cost.js +66 -13
  41. package/dist/server/tools/cost.js.map +1 -1
  42. package/dist/server/tools/discoveries.js +6 -6
  43. package/dist/server/tools/discoveries.js.map +1 -1
  44. package/dist/server/tools/index.d.ts +4 -0
  45. package/dist/server/tools/index.d.ts.map +1 -1
  46. package/dist/server/tools/index.js +4 -0
  47. package/dist/server/tools/index.js.map +1 -1
  48. package/dist/server/tools/learning.d.ts +12 -0
  49. package/dist/server/tools/learning.d.ts.map +1 -0
  50. package/dist/server/tools/learning.js +269 -0
  51. package/dist/server/tools/learning.js.map +1 -0
  52. package/dist/server/tools/project.js +7 -7
  53. package/dist/server/tools/project.js.map +1 -1
  54. package/dist/server/tools/promote.d.ts +11 -0
  55. package/dist/server/tools/promote.d.ts.map +1 -0
  56. package/dist/server/tools/promote.js +315 -0
  57. package/dist/server/tools/promote.js.map +1 -0
  58. package/dist/server/tools/route-helpers.d.ts +45 -0
  59. package/dist/server/tools/route-helpers.d.ts.map +1 -0
  60. package/dist/server/tools/route-helpers.js +93 -0
  61. package/dist/server/tools/route-helpers.js.map +1 -0
  62. package/dist/server/tools/route.d.ts +4 -3
  63. package/dist/server/tools/route.d.ts.map +1 -1
  64. package/dist/server/tools/route.js +80 -284
  65. package/dist/server/tools/route.js.map +1 -1
  66. package/dist/server/tools/session-restore.d.ts +18 -0
  67. package/dist/server/tools/session-restore.d.ts.map +1 -0
  68. package/dist/server/tools/session-restore.js +154 -0
  69. package/dist/server/tools/session-restore.js.map +1 -0
  70. package/dist/server/tools/session-search.d.ts +16 -0
  71. package/dist/server/tools/session-search.d.ts.map +1 -0
  72. package/dist/server/tools/session-search.js +240 -0
  73. package/dist/server/tools/session-search.js.map +1 -0
  74. package/dist/server/tools/sights-index.js +2 -2
  75. package/dist/server/tools/sights-index.js.map +1 -1
  76. package/dist/server/tools/smart-route.d.ts.map +1 -1
  77. package/dist/server/tools/smart-route.js +4 -5
  78. package/dist/server/tools/smart-route.js.map +1 -1
  79. package/dist/server/tools/verification.js +1 -1
  80. package/dist/server/tools/verification.js.map +1 -1
  81. package/files/agents/code-organization-supervisor.md +1 -0
  82. package/files/agents/context-guardian.md +1 -0
  83. package/files/agents/docs-keeper.md +1 -0
  84. package/files/agents/dry-refactor.md +1 -0
  85. package/files/agents/elite-code-refactorer.md +1 -0
  86. package/files/agents/hardening-guard.md +1 -0
  87. package/files/agents/implementation-dev.md +1 -0
  88. package/files/agents/merlin-access-control-reviewer.md +248 -0
  89. package/files/agents/merlin-codebase-mapper.md +1 -1
  90. package/files/agents/merlin-dependency-auditor.md +216 -0
  91. package/files/agents/merlin-executor.md +1 -0
  92. package/files/agents/merlin-input-validator.md +247 -0
  93. package/files/agents/merlin-reviewer.md +1 -0
  94. package/files/agents/merlin-sast-reviewer.md +182 -0
  95. package/files/agents/merlin-secret-scanner.md +203 -0
  96. package/files/agents/tests-qa.md +1 -0
  97. package/files/commands/merlin/execute-phase.md +94 -197
  98. package/files/commands/merlin/execute-plan.md +116 -180
  99. package/files/commands/merlin/health.md +385 -0
  100. package/files/commands/merlin/loop-recipes.md +93 -36
  101. package/files/commands/merlin/optimize-prompts.md +158 -0
  102. package/files/commands/merlin/profiles.md +215 -0
  103. package/files/commands/merlin/promote.md +176 -0
  104. package/files/commands/merlin/quick.md +229 -0
  105. package/files/commands/merlin/resume-work.md +27 -1
  106. package/files/commands/merlin/route.md +43 -1
  107. package/files/commands/merlin/sandbox.md +359 -0
  108. package/files/commands/merlin/usage.md +55 -0
  109. package/files/docker/Dockerfile.merlin +20 -0
  110. package/files/docker/docker-compose.merlin.yml +23 -0
  111. package/files/hook-templates/auto-commit.sh +64 -0
  112. package/files/hook-templates/auto-format.sh +95 -0
  113. package/files/hook-templates/auto-test.sh +117 -0
  114. package/files/hook-templates/branch-protection.sh +72 -0
  115. package/files/hook-templates/changelog-reminder.sh +76 -0
  116. package/files/hook-templates/complexity-check.sh +112 -0
  117. package/files/hook-templates/import-audit.sh +83 -0
  118. package/files/hook-templates/license-header.sh +84 -0
  119. package/files/hook-templates/pr-description.sh +100 -0
  120. package/files/hook-templates/todo-tracker.sh +80 -0
  121. package/files/hooks/check-file-size.sh +17 -4
  122. package/files/hooks/config-change.sh +44 -16
  123. package/files/hooks/instructions-loaded.sh +22 -5
  124. package/files/hooks/notify-desktop.sh +157 -0
  125. package/files/hooks/notify-webhook.sh +141 -0
  126. package/files/hooks/pre-edit-sights-check.sh +76 -9
  127. package/files/hooks/security-scanner.sh +153 -0
  128. package/files/hooks/session-end-memory-sync.sh +97 -0
  129. package/files/hooks/session-end.sh +274 -1
  130. package/files/hooks/session-start.sh +19 -6
  131. package/files/hooks/smart-approve.sh +270 -0
  132. package/files/hooks/teammate-idle-verify.sh +87 -12
  133. package/files/hooks/worktree-create.sh +20 -3
  134. package/files/hooks/worktree-remove.sh +21 -3
  135. package/files/merlin/references/plan-format.md +37 -9
  136. package/files/merlin/sandbox.json +9 -0
  137. package/files/merlin/security.json +11 -0
  138. package/files/merlin/templates/ci/docs-update.yml +81 -0
  139. package/files/merlin/templates/ci/pr-review.yml +50 -0
  140. package/files/merlin/templates/ci/security-audit.yml +74 -0
  141. package/files/merlin/templates/config.json +9 -1
  142. package/files/rules/api-rules.md +30 -0
  143. package/files/rules/frontend-rules.md +25 -0
  144. package/files/rules/hooks-rules.md +36 -0
  145. package/files/rules/mcp-rules.md +30 -0
  146. package/files/rules/worker-rules.md +29 -0
  147. 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, _taskComplexity: string): void;
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;;;;;GAKG;AAeH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;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,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAyBD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAoB1E;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAY5C;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAOnC"}
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
- // Estimated cost per routing call (rough approximation)
9
- // These are per-message estimates for a typical agentic task
10
- const COST_PER_CALL = {
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
- // Baseline: cost if every routing call used Opus
16
- const OPUS_BASELINE_COST = COST_PER_CALL.opus;
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
- // Singleton session state
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, _taskComplexity) {
85
+ export function recordRouting(model, taskComplexity, opts = {}) {
43
86
  const key = normalizeModel(model);
44
- const cost = COST_PER_CALL[key] ?? COST_PER_CALL.opus;
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 = parseFloat((state.byModel[key].estimatedCost + cost).toFixed(6));
48
- state.totalEstimatedCost = parseFloat((state.totalEstimatedCost + cost).toFixed(6));
49
- // Savings: how much cheaper vs always using Opus
50
- const baselineCostForThisCall = OPUS_BASELINE_COST;
51
- const saved = baselineCostForThisCall - cost;
52
- state.savedVsBaseline = parseFloat((state.savedVsBaseline + saved).toFixed(6));
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
  }