opencode-bioresearcher 1.11.1 → 1.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/README.md +110 -49
- package/agents/bioresearcher-dr-worker.md +32 -20
- package/connector-meta.json +6 -5
- package/index.js +146 -61
- package/package.json +4 -2
- package/skill-bundle.json +3 -5
- package/skills/bioresearcher-deep-research/SKILL.md +52 -6
- package/skills/bioresearcher-deep-research/references/analysis-methods.md +15 -0
- package/skills/bioresearcher-deep-research/references/article-literature.md +36 -18
- package/skills/bioresearcher-deep-research/references/cartography.md +190 -0
- package/skills/bioresearcher-deep-research/references/diseases.md +1 -1
- package/skills/bioresearcher-deep-research/references/drugs.md +5 -4
- package/skills/bioresearcher-deep-research/references/functional-genomics.md +7 -3
- package/skills/bioresearcher-deep-research/references/genes.md +5 -3
- package/skills/bioresearcher-deep-research/references/patents.md +9 -3
- package/skills/bioresearcher-deep-research/references/rate-limiting-auth.md +7 -2
- package/skills/bioresearcher-deep-research/references/report-template.md +17 -0
- package/skills/bioresearcher-deep-research/references/tool-selection.md +8 -5
- package/skills/bioresearcher-deep-research/references/utility-config.md +2 -2
- package/skills/bioresearcher-deep-research/references/worker-protocol.md +16 -1
- package/skills/bioresearcher-onboard/SKILL.md +114 -0
- package/skills/bioresearcher-onboard/scripts/bootstrap.ps1 +96 -0
- package/skills/bioresearcher-onboard/scripts/bootstrap.sh +127 -0
- package/skills/bioresearcher-onboard/scripts/onboard.mjs +512 -0
- package/loader.js +0 -3
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stage 1 Onboarding Engine for bioresearcher-onboard.
|
|
4
|
+
* Installs biomcp@1.6.0 into .bioresearcher-runtime/node_modules, configures
|
|
5
|
+
* optional features via .biomcp.json, non-destructively merges harness
|
|
6
|
+
* configuration files, and runs pre-flight doctor verification.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
|
|
14
|
+
const CWD = process.cwd();
|
|
15
|
+
const RUNTIME_ROOT = process.env.BIORESEARCHER_RUNTIME_DIR || path.resolve(CWD, ".bioresearcher-runtime");
|
|
16
|
+
const CACHE_DIR = path.join(RUNTIME_ROOT, "npm-cache");
|
|
17
|
+
const IS_WIN = process.platform === "win32";
|
|
18
|
+
|
|
19
|
+
// 1. Resolve Node and npm executables
|
|
20
|
+
let nodeBin = process.execPath;
|
|
21
|
+
const vendoredNode = IS_WIN
|
|
22
|
+
? path.join(RUNTIME_ROOT, "node", "node.exe")
|
|
23
|
+
: path.join(RUNTIME_ROOT, "node", "bin", "node");
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(vendoredNode)) {
|
|
26
|
+
nodeBin = vendoredNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let npmBin = IS_WIN
|
|
30
|
+
? path.join(RUNTIME_ROOT, "node", "npm.cmd")
|
|
31
|
+
: path.join(RUNTIME_ROOT, "node", "bin", "npm");
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(npmBin)) {
|
|
34
|
+
npmBin = "npm";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Parse CLI options
|
|
38
|
+
const args = process.argv.slice(2);
|
|
39
|
+
const getArgVal = (prefix) => {
|
|
40
|
+
const match = args.find((a) => a.startsWith(prefix));
|
|
41
|
+
return match ? match.slice(match.indexOf("=") + 1) : null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const options = {
|
|
45
|
+
withR: args.includes("--with-r") || process.env.ONBOARD_R === "1",
|
|
46
|
+
withMysql: args.includes("--with-mysql") || process.env.ONBOARD_MYSQL === "1",
|
|
47
|
+
withBiowasm: args.includes("--with-biowasm") || process.env.ONBOARD_BIOWASM === "1",
|
|
48
|
+
sqlitePath: getArgVal("--sqlite-path=") || process.env.ONBOARD_SQLITE_PATH || null,
|
|
49
|
+
client: getArgVal("--client=") || null,
|
|
50
|
+
registry: getArgVal("--registry=") || process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org",
|
|
51
|
+
nonInteractive: args.includes("--non-interactive") || !process.stdin.isTTY,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
console.log("[bioresearcher-onboard] Initializing project-local BioMCP runtime...");
|
|
55
|
+
|
|
56
|
+
// 3. Prepare isolated .bioresearcher-runtime directory
|
|
57
|
+
fs.mkdirSync(RUNTIME_ROOT, { recursive: true });
|
|
58
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
59
|
+
|
|
60
|
+
const runtimePkgJson = path.join(RUNTIME_ROOT, "package.json");
|
|
61
|
+
if (!fs.existsSync(runtimePkgJson)) {
|
|
62
|
+
fs.writeFileSync(runtimePkgJson, JSON.stringify({ name: "bioresearcher-runtime", private: true }, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 4. Determine packages to install
|
|
66
|
+
const packagesToInstall = ["biomcp@1.6.0"];
|
|
67
|
+
if (options.withR) packagesToInstall.push("webr@0.6");
|
|
68
|
+
if (options.withMysql) packagesToInstall.push("mysql2@3");
|
|
69
|
+
|
|
70
|
+
console.log(`[bioresearcher-onboard] Installing dependencies (${packagesToInstall.join(", ")}) via ${options.registry}...`);
|
|
71
|
+
|
|
72
|
+
const npmEnv = {
|
|
73
|
+
...process.env,
|
|
74
|
+
npm_config_cache: CACHE_DIR,
|
|
75
|
+
npm_config_userconfig: IS_WIN ? "NUL" : "/dev/null",
|
|
76
|
+
npm_config_audit: "false",
|
|
77
|
+
npm_config_fund: "false",
|
|
78
|
+
npm_config_update_notifier: "false",
|
|
79
|
+
npm_config_registry: options.registry,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const installArgs = ["install", "--prefix", RUNTIME_ROOT, "--ignore-scripts", ...packagesToInstall];
|
|
83
|
+
const installRes = spawnSync(npmBin, installArgs, {
|
|
84
|
+
env: npmEnv,
|
|
85
|
+
stdio: "inherit",
|
|
86
|
+
cwd: RUNTIME_ROOT,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (installRes.status !== 0) {
|
|
90
|
+
console.error("[bioresearcher-onboard] Error: Failed to install packages into .bioresearcher-runtime");
|
|
91
|
+
process.exit(installRes.status || 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 5. Verify bundle path
|
|
95
|
+
const bundlePath = path.join(RUNTIME_ROOT, "node_modules", "biomcp", "dist", "bundle.js");
|
|
96
|
+
if (!fs.existsSync(bundlePath)) {
|
|
97
|
+
console.error(`[bioresearcher-onboard] Error: BioMCP bundle not found at ${bundlePath}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 6. Generate .biomcp.json if optional features are requested
|
|
102
|
+
const featuresConfig = {};
|
|
103
|
+
if (options.withR) {
|
|
104
|
+
featuresConfig.analysis_r = { enabled: true };
|
|
105
|
+
}
|
|
106
|
+
if (options.withBiowasm) {
|
|
107
|
+
featuresConfig.analysis_biowasm = { enabled: true };
|
|
108
|
+
}
|
|
109
|
+
if (options.sqlitePath) {
|
|
110
|
+
featuresConfig.database = {
|
|
111
|
+
enabled: true,
|
|
112
|
+
type: "sqlite",
|
|
113
|
+
sqlite_path: options.sqlitePath,
|
|
114
|
+
};
|
|
115
|
+
} else if (options.withMysql) {
|
|
116
|
+
featuresConfig.database = {
|
|
117
|
+
enabled: true,
|
|
118
|
+
type: "mysql",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const biomcpJsonPath = path.resolve(CWD, ".biomcp.json");
|
|
123
|
+
if (Object.keys(featuresConfig).length > 0) {
|
|
124
|
+
let existing = {};
|
|
125
|
+
if (fs.existsSync(biomcpJsonPath)) {
|
|
126
|
+
try {
|
|
127
|
+
existing = JSON.parse(fs.readFileSync(biomcpJsonPath, "utf8"));
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
130
|
+
existing.features = { ...existing.features, ...featuresConfig };
|
|
131
|
+
|
|
132
|
+
// Write atomically with 0o600 permissions
|
|
133
|
+
const tmpPath = `${biomcpJsonPath}.${Date.now()}.tmp`;
|
|
134
|
+
fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), { mode: 0o600 });
|
|
135
|
+
fs.renameSync(tmpPath, biomcpJsonPath);
|
|
136
|
+
console.log(`[bioresearcher-onboard] Configured optional features in .biomcp.json`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 7. Non-destructive deep-merge of client harness configuration files
|
|
140
|
+
// Normalize Windows paths to POSIX forward slashes for valid JSON strings
|
|
141
|
+
const normalizedNodePath = IS_WIN ? nodeBin.replace(/\\/g, "/") : nodeBin;
|
|
142
|
+
const normalizedBundlePath = IS_WIN ? bundlePath.replace(/\\/g, "/") : bundlePath;
|
|
143
|
+
|
|
144
|
+
function stripJsonc(content) {
|
|
145
|
+
let result = "";
|
|
146
|
+
let inString = false;
|
|
147
|
+
let inSingleComment = false;
|
|
148
|
+
let inMultiComment = false;
|
|
149
|
+
let isEscaped = false;
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < content.length; i++) {
|
|
152
|
+
const ch = content[i];
|
|
153
|
+
const next = content[i + 1];
|
|
154
|
+
|
|
155
|
+
if (inSingleComment) {
|
|
156
|
+
if (ch === "\n") {
|
|
157
|
+
inSingleComment = false;
|
|
158
|
+
result += ch;
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (inMultiComment) {
|
|
164
|
+
if (ch === "*" && next === "/") {
|
|
165
|
+
inMultiComment = false;
|
|
166
|
+
i++;
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (inString) {
|
|
172
|
+
result += ch;
|
|
173
|
+
if (isEscaped) {
|
|
174
|
+
isEscaped = false;
|
|
175
|
+
} else if (ch === "\\") {
|
|
176
|
+
isEscaped = true;
|
|
177
|
+
} else if (ch === "\"") {
|
|
178
|
+
inString = false;
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (ch === "\"") {
|
|
184
|
+
inString = true;
|
|
185
|
+
isEscaped = false;
|
|
186
|
+
result += ch;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (ch === "/" && next === "/") {
|
|
191
|
+
inSingleComment = true;
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (ch === "/" && next === "*") {
|
|
197
|
+
inMultiComment = true;
|
|
198
|
+
i++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (ch === ",") {
|
|
203
|
+
let j = i + 1;
|
|
204
|
+
let isTrailing = false;
|
|
205
|
+
let tempInSingle = false;
|
|
206
|
+
let tempInMulti = false;
|
|
207
|
+
while (j < content.length) {
|
|
208
|
+
const cj = content[j];
|
|
209
|
+
const nj = content[j + 1];
|
|
210
|
+
if (tempInSingle) {
|
|
211
|
+
if (cj === "\n") tempInSingle = false;
|
|
212
|
+
j++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (tempInMulti) {
|
|
216
|
+
if (cj === "*" && nj === "/") {
|
|
217
|
+
tempInMulti = false;
|
|
218
|
+
j += 2;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
j++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (cj === "/" && nj === "/") {
|
|
225
|
+
tempInSingle = true;
|
|
226
|
+
j += 2;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (cj === "/" && nj === "*") {
|
|
230
|
+
tempInMulti = true;
|
|
231
|
+
j += 2;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (/\s/.test(cj)) {
|
|
235
|
+
j++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (cj === "}" || cj === "]") {
|
|
239
|
+
isTrailing = true;
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
if (isTrailing) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
result += ch;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return result.trim();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function safeMergeJson(filePath, updater) {
|
|
255
|
+
let data = {};
|
|
256
|
+
let backedUp = false;
|
|
257
|
+
if (fs.existsSync(filePath)) {
|
|
258
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
259
|
+
if (raw.trim().length > 0) {
|
|
260
|
+
try {
|
|
261
|
+
data = JSON.parse(stripJsonc(raw));
|
|
262
|
+
} catch (e) {
|
|
263
|
+
console.error(`[bioresearcher-onboard] Error: Failed to parse existing JSONC at ${filePath}: ${e.message}`);
|
|
264
|
+
console.error(`[bioresearcher-onboard] Aborting modification to prevent data loss. Existing file was not modified.`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
fs.copyFileSync(filePath, `${filePath}.bak`);
|
|
269
|
+
backedUp = true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
273
|
+
data = {};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const updated = updater(data);
|
|
277
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
278
|
+
const tmp = `${filePath}.${Date.now()}.tmp`;
|
|
279
|
+
fs.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
280
|
+
fs.renameSync(tmp, filePath);
|
|
281
|
+
if (backedUp) {
|
|
282
|
+
console.log(`[bioresearcher-onboard] Updated ${path.basename(filePath)} (backup saved to ${path.basename(filePath)}.bak)`);
|
|
283
|
+
} else {
|
|
284
|
+
console.log(`[bioresearcher-onboard] Created ${path.basename(filePath)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Probe harness configs upfront to avoid generating unintended files
|
|
289
|
+
const opencodePath = path.resolve(CWD, "opencode.json");
|
|
290
|
+
const claudePath = path.resolve(CWD, ".mcp.json");
|
|
291
|
+
const cursorDir = path.resolve(CWD, ".cursor");
|
|
292
|
+
const cursorPath = path.join(cursorDir, "mcp.json");
|
|
293
|
+
|
|
294
|
+
const zcodeDir = path.resolve(CWD, ".zcode");
|
|
295
|
+
const zcodePath = path.join(zcodeDir, "config.json");
|
|
296
|
+
|
|
297
|
+
const piDir = path.resolve(CWD, ".pi");
|
|
298
|
+
const piPath = path.join(piDir, "mcp.json");
|
|
299
|
+
|
|
300
|
+
const codebuddyDir = path.resolve(CWD, ".codebuddy");
|
|
301
|
+
const codebuddySettingsPath = path.join(codebuddyDir, "settings.json");
|
|
302
|
+
const codebuddyMdPath = path.resolve(CWD, "CODEBUDDY.md");
|
|
303
|
+
|
|
304
|
+
const workbuddyDir = path.resolve(CWD, ".workbuddy");
|
|
305
|
+
const workbuddyPath = path.join(workbuddyDir, "mcp.json");
|
|
306
|
+
const workbuddyMdPath = path.resolve(CWD, "WORKBUDDY.md");
|
|
307
|
+
|
|
308
|
+
const opencodeExists = fs.existsSync(opencodePath);
|
|
309
|
+
const claudeExists = fs.existsSync(claudePath);
|
|
310
|
+
const cursorExists = fs.existsSync(cursorDir) || fs.existsSync(cursorPath);
|
|
311
|
+
const zcodeExists = fs.existsSync(zcodeDir) || fs.existsSync(zcodePath) || Boolean(process.env.ZCODE_WORKSPACE);
|
|
312
|
+
const piExists = fs.existsSync(piDir) || fs.existsSync(piPath) || Boolean(process.env.PI_CODING_AGENT_DIR);
|
|
313
|
+
const codebuddyExists = fs.existsSync(codebuddyDir) || fs.existsSync(codebuddyMdPath) || Boolean(process.env.CODEBUDDY_CLI);
|
|
314
|
+
const workbuddyExists = fs.existsSync(workbuddyDir) || fs.existsSync(workbuddyPath) || fs.existsSync(workbuddyMdPath);
|
|
315
|
+
|
|
316
|
+
// Normalize client argument with aliases
|
|
317
|
+
const clientArg = (options.client || "").toLowerCase().trim();
|
|
318
|
+
const clientAliases = {
|
|
319
|
+
"claude": "claude-code",
|
|
320
|
+
"code-buddy": "codebuddy",
|
|
321
|
+
"work-buddy": "workbuddy",
|
|
322
|
+
"pi-agent": "pi",
|
|
323
|
+
};
|
|
324
|
+
const resolvedClient = clientAliases[clientArg] || clientArg;
|
|
325
|
+
|
|
326
|
+
const validClients = ["opencode", "claude-code", "cursor", "zcode", "pi", "codebuddy", "workbuddy"];
|
|
327
|
+
if (resolvedClient && !validClients.includes(resolvedClient)) {
|
|
328
|
+
console.error(`[bioresearcher-onboard] Error: Unknown client "${options.client}". Valid options: ${validClients.join(", ")}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let targetOpencode = resolvedClient === "opencode" || (!resolvedClient && opencodeExists);
|
|
333
|
+
let targetClaude = resolvedClient === "claude-code" || (!resolvedClient && claudeExists && !codebuddyExists);
|
|
334
|
+
let targetCursor = resolvedClient === "cursor" || (!resolvedClient && cursorExists);
|
|
335
|
+
let targetZcode = resolvedClient === "zcode" || (!resolvedClient && zcodeExists);
|
|
336
|
+
let targetPi = resolvedClient === "pi" || (!resolvedClient && piExists);
|
|
337
|
+
let targetCodebuddy = resolvedClient === "codebuddy" || (!resolvedClient && codebuddyExists);
|
|
338
|
+
let targetWorkbuddy = resolvedClient === "workbuddy" || (!resolvedClient && workbuddyExists);
|
|
339
|
+
|
|
340
|
+
// Default to opencode.json if no client specified and no config file exists yet
|
|
341
|
+
const anyDetected = opencodeExists || claudeExists || cursorExists || zcodeExists || piExists || codebuddyExists || workbuddyExists;
|
|
342
|
+
if (!resolvedClient && !anyDetected) {
|
|
343
|
+
targetOpencode = true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// OpenCode: opencode.json (canonical v2 shape mcp.servers.<name>)
|
|
347
|
+
if (targetOpencode) {
|
|
348
|
+
const npxCmd = IS_WIN ? "npx.cmd" : "npx";
|
|
349
|
+
safeMergeJson(opencodePath, (data) => {
|
|
350
|
+
data.$schema = data.$schema || "https://opencode.ai/config.json";
|
|
351
|
+
data.mcp = data.mcp || {};
|
|
352
|
+
data.mcp.servers = data.mcp.servers || {};
|
|
353
|
+
// Migrate legacy flat entry (pre-v2 onboard / old plugin config hook):
|
|
354
|
+
// preserve environment, convert flat timeout N to the triple form.
|
|
355
|
+
const legacy = data.mcp.biomcp;
|
|
356
|
+
if (data.mcp.servers.biomcp === undefined && legacy && typeof legacy === "object" && !Array.isArray(legacy)) {
|
|
357
|
+
const flatTimeout = typeof legacy.timeout === "number" ? legacy.timeout : 120000;
|
|
358
|
+
data.mcp.servers.biomcp = {
|
|
359
|
+
type: "local",
|
|
360
|
+
command: [npxCmd, "-y", "-p", "biomcp@1.6.0", "biomcp"],
|
|
361
|
+
environment: legacy.environment || {},
|
|
362
|
+
timeout: { startup: flatTimeout, catalog: flatTimeout, execution: flatTimeout },
|
|
363
|
+
};
|
|
364
|
+
} else if (data.mcp.servers.biomcp === undefined) {
|
|
365
|
+
data.mcp.servers.biomcp = {
|
|
366
|
+
type: "local",
|
|
367
|
+
command: [npxCmd, "-y", "-p", "biomcp@1.6.0", "biomcp"],
|
|
368
|
+
environment: {},
|
|
369
|
+
timeout: { startup: 120000, catalog: 120000, execution: 120000 },
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
// An existing canonical entry is left untouched; drop the migrated flat key.
|
|
373
|
+
if (legacy !== undefined) delete data.mcp.biomcp;
|
|
374
|
+
if (Object.keys(data.mcp).length === 0) delete data.mcp;
|
|
375
|
+
return data;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Claude Code: .mcp.json
|
|
380
|
+
if (targetClaude) {
|
|
381
|
+
safeMergeJson(claudePath, (data) => {
|
|
382
|
+
data.mcpServers = data.mcpServers || {};
|
|
383
|
+
data.mcpServers.biomcp = {
|
|
384
|
+
type: "stdio",
|
|
385
|
+
command: normalizedNodePath,
|
|
386
|
+
args: [normalizedBundlePath],
|
|
387
|
+
env: data.mcpServers.biomcp?.env || {},
|
|
388
|
+
};
|
|
389
|
+
return data;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Cursor: .cursor/mcp.json
|
|
394
|
+
if (targetCursor) {
|
|
395
|
+
safeMergeJson(cursorPath, (data) => {
|
|
396
|
+
data.mcpServers = data.mcpServers || {};
|
|
397
|
+
data.mcpServers.biomcp = {
|
|
398
|
+
command: normalizedNodePath,
|
|
399
|
+
args: [normalizedBundlePath],
|
|
400
|
+
env: data.mcpServers.biomcp?.env || {},
|
|
401
|
+
};
|
|
402
|
+
return data;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ZCode: .zcode/config.json
|
|
407
|
+
if (targetZcode) {
|
|
408
|
+
safeMergeJson(zcodePath, (data) => {
|
|
409
|
+
data.mcp = data.mcp || {};
|
|
410
|
+
data.mcp.servers = data.mcp.servers || {};
|
|
411
|
+
data.mcp.servers.biomcp = {
|
|
412
|
+
command: normalizedNodePath,
|
|
413
|
+
args: [normalizedBundlePath],
|
|
414
|
+
env: data.mcp.servers.biomcp?.env || {},
|
|
415
|
+
enable: true,
|
|
416
|
+
};
|
|
417
|
+
return data;
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Pi Coding Agent: .pi/mcp.json
|
|
422
|
+
if (targetPi) {
|
|
423
|
+
safeMergeJson(piPath, (data) => {
|
|
424
|
+
data.mcpServers = data.mcpServers || {};
|
|
425
|
+
data.mcpServers.biomcp = {
|
|
426
|
+
command: normalizedNodePath,
|
|
427
|
+
args: [normalizedBundlePath],
|
|
428
|
+
env: data.mcpServers.biomcp?.env || {},
|
|
429
|
+
};
|
|
430
|
+
return data;
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// CodeBuddy: .mcp.json + pre-approval in .codebuddy/settings.json
|
|
435
|
+
if (targetCodebuddy) {
|
|
436
|
+
safeMergeJson(claudePath, (data) => {
|
|
437
|
+
data.mcpServers = data.mcpServers || {};
|
|
438
|
+
data.mcpServers.biomcp = {
|
|
439
|
+
type: "stdio",
|
|
440
|
+
command: normalizedNodePath,
|
|
441
|
+
args: [normalizedBundlePath],
|
|
442
|
+
env: data.mcpServers.biomcp?.env || {},
|
|
443
|
+
};
|
|
444
|
+
return data;
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
safeMergeJson(codebuddySettingsPath, (data) => {
|
|
448
|
+
const approved = new Set(data.enabledMcpjsonServers || []);
|
|
449
|
+
approved.add("biomcp");
|
|
450
|
+
data.enabledMcpjsonServers = Array.from(approved);
|
|
451
|
+
return data;
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// WorkBuddy: .workbuddy/mcp.json
|
|
456
|
+
if (targetWorkbuddy) {
|
|
457
|
+
safeMergeJson(workbuddyPath, (data) => {
|
|
458
|
+
data.mcpServers = data.mcpServers || {};
|
|
459
|
+
data.mcpServers.biomcp = {
|
|
460
|
+
type: "stdio",
|
|
461
|
+
command: normalizedNodePath,
|
|
462
|
+
args: [normalizedBundlePath],
|
|
463
|
+
env: data.mcpServers.biomcp?.env || {},
|
|
464
|
+
};
|
|
465
|
+
return data;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 8. Run pre-flight verification via biomcp doctor --json
|
|
470
|
+
console.log("[bioresearcher-onboard] Running pre-flight verification via biomcp doctor...");
|
|
471
|
+
const cliPath = path.join(RUNTIME_ROOT, "node_modules", "biomcp", "dist", "cli.js");
|
|
472
|
+
const doctorRes = spawnSync(nodeBin, [cliPath, "doctor", "--json"], {
|
|
473
|
+
cwd: CWD,
|
|
474
|
+
encoding: "utf8",
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
if (doctorRes.status === 0) {
|
|
478
|
+
try {
|
|
479
|
+
const report = JSON.parse(doctorRes.stdout);
|
|
480
|
+
console.log(`[bioresearcher-onboard] ✓ Pre-flight check PASSED (schema v${report.schema_version})`);
|
|
481
|
+
console.log(` Node: ${report.node.version} (required ${report.node.required})`);
|
|
482
|
+
console.log(` Install mode: ${report.server_context.install_mode}`);
|
|
483
|
+
for (const f of report.features) {
|
|
484
|
+
console.log(` Feature ${f.id}: ${f.running_after_restart ? "ON after restart" : "off"}`);
|
|
485
|
+
}
|
|
486
|
+
} catch {
|
|
487
|
+
console.log("[bioresearcher-onboard] ✓ Doctor exit code 0");
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
console.warn("[bioresearcher-onboard] Warning: Doctor reported potential blockers:");
|
|
491
|
+
console.log(doctorRes.stdout || doctorRes.stderr);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// 9. Git Hygiene: Ensure .bioresearcher-runtime is gitignored
|
|
495
|
+
const gitignorePath = path.resolve(CWD, ".gitignore");
|
|
496
|
+
let gitignoreContent = "";
|
|
497
|
+
if (fs.existsSync(gitignorePath)) {
|
|
498
|
+
gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
|
|
499
|
+
}
|
|
500
|
+
if (!gitignoreContent.includes(".bioresearcher-runtime")) {
|
|
501
|
+
const newline = gitignoreContent.length > 0 && !gitignoreContent.endsWith("\n") ? "\n" : "";
|
|
502
|
+
fs.appendFileSync(gitignorePath, `${newline}# BioMCP project-local runtime\n.bioresearcher-runtime/\n`);
|
|
503
|
+
console.log("[bioresearcher-onboard] Added .bioresearcher-runtime/ to .gitignore");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log("\n========================================================");
|
|
507
|
+
console.log("✓ BioMCP runtime bootstrapped successfully!");
|
|
508
|
+
console.log(` Runtime directory: .bioresearcher-runtime/`);
|
|
509
|
+
console.log(` Node.js: ${nodeBin}`);
|
|
510
|
+
console.log(` BioMCP bundle: ${bundlePath}`);
|
|
511
|
+
console.log("\n→ NEXT STEP: Restart your agent/client session to activate biomcp tools.");
|
|
512
|
+
console.log("========================================================\n");
|
package/loader.js
DELETED