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.
Files changed (25) hide show
  1. package/README.md +110 -49
  2. package/agents/bioresearcher-dr-worker.md +32 -20
  3. package/connector-meta.json +6 -5
  4. package/index.js +146 -61
  5. package/package.json +4 -2
  6. package/skill-bundle.json +3 -5
  7. package/skills/bioresearcher-deep-research/SKILL.md +52 -6
  8. package/skills/bioresearcher-deep-research/references/analysis-methods.md +15 -0
  9. package/skills/bioresearcher-deep-research/references/article-literature.md +36 -18
  10. package/skills/bioresearcher-deep-research/references/cartography.md +190 -0
  11. package/skills/bioresearcher-deep-research/references/diseases.md +1 -1
  12. package/skills/bioresearcher-deep-research/references/drugs.md +5 -4
  13. package/skills/bioresearcher-deep-research/references/functional-genomics.md +7 -3
  14. package/skills/bioresearcher-deep-research/references/genes.md +5 -3
  15. package/skills/bioresearcher-deep-research/references/patents.md +9 -3
  16. package/skills/bioresearcher-deep-research/references/rate-limiting-auth.md +7 -2
  17. package/skills/bioresearcher-deep-research/references/report-template.md +17 -0
  18. package/skills/bioresearcher-deep-research/references/tool-selection.md +8 -5
  19. package/skills/bioresearcher-deep-research/references/utility-config.md +2 -2
  20. package/skills/bioresearcher-deep-research/references/worker-protocol.md +16 -1
  21. package/skills/bioresearcher-onboard/SKILL.md +114 -0
  22. package/skills/bioresearcher-onboard/scripts/bootstrap.ps1 +96 -0
  23. package/skills/bioresearcher-onboard/scripts/bootstrap.sh +127 -0
  24. package/skills/bioresearcher-onboard/scripts/onboard.mjs +512 -0
  25. package/loader.js +0 -3
@@ -67,6 +67,23 @@ the source of truth for citations]
67
67
  [queries that failed after retries, with reasons]
68
68
  ```
69
69
 
70
+ ## Map-first optional sections
71
+
72
+ When the run used empirical cartography (SKILL.md Step 1.5), two OPTIONAL
73
+ sections extend the mandatory structure. Both cite through the normal
74
+ merged-ledger markers - cartography anchors are ordinary ledger records.
75
+
76
+ - **Landscape** (inside Findings, after the selected aspects): one line per
77
+ discovered-but-unselected category, citing its cartography anchors -
78
+ acknowledged, not researched; the report must say so explicitly.
79
+ - **Decision guide by question scenario** (after the findings/evidence
80
+ tables; DEFAULT-ON in map-first mode, skippable only if the user
81
+ declines): one compact table per user-confirmed question scenario -
82
+ scenario | recommended method sequence | tier | caveat - built only from
83
+ ledger-verified sources, with tiers labeled scope-relative.
84
+ - Taxonomy disagreements between the field's own syntheses are reported in
85
+ Analysis Methodology or Findings - never silently resolved.
86
+
70
87
  ## Citation placement rules
71
88
 
72
89
  - Authored text uses semantic cite-key markers `[@pmid:...]`, groups
@@ -5,10 +5,11 @@ Route a research question to the correct biomcp tool, then shape the call with
5
5
 
6
6
  ## Overview
7
7
 
8
- biomcp (npm `biomcp`, pinned `biomcp@1.4`) exposes 56 tools: 41 core plus 15
9
- environment-gated optional tools (3 database, 4 R analysis, 8 biowasm). This
10
- file routes question types to tools; per-domain parameter detail lives in the
11
- domain reference files.
8
+ biomcp (npm `biomcp`, pinned `biomcp@1.6`) exposes 58 tools: 43 core plus 15
9
+ environment-gated optional tools (3 database, 4 R analysis, 8 biowasm). The
10
+ 1.6 core set adds `journal_metrics` and `preprint_fulltext` to the article
11
+ family. This file routes question types to tools; per-domain parameter
12
+ detail lives in the domain reference files.
12
13
 
13
14
  > In MCP clients that prefix server tools (e.g. opencode with server name
14
15
  > 'biomcp'), tools appear as biomcp_article_search etc.
@@ -20,6 +21,8 @@ QUESTION TYPE
20
21
  ├─ Literature / papers / PubMed
21
22
  │ → article_search (query, source?, dateRange?, limit, offset)
22
23
  │ → article_get (id: PMID/PMCID/DOI, sections?, citation_mode?)
24
+ │ → preprint_fulltext (bioRxiv/medRxiv preprint DOI, version?) # on-demand full text; license disclosed
25
+ │ → journal_metrics (journal name / NLM abbreviation / ISSN, limit <= 25) # OpenAlex venue quality (2-yr citedness, h-index, i10, OA) before citing
23
26
  │ → details: references/article-literature.md
24
27
  │
25
28
  ├─ Clinical trials / NCT IDs
@@ -131,7 +134,7 @@ Question: "What evidence links BRAF V600E to melanoma drug resistance?"
131
134
  |---------|-------|-----|
132
135
  | `no_such_tool` for a db/analysis tool | feature not enabled, or enabled after server start (tools register at startup only) | set `DB_TYPE`/`ANALYSIS_R`/`ANALYSIS_BIOWASM` in the client env block, restart the client |
133
136
  | Tool error mentioning OncoKB token | `variant_oncokb` without `ONCOKB_TOKEN` | get a token (oncokb.org registration) and set it, or skip OncoKB |
134
- | gene_diseases returns an `_error` about DisGeNET | no `DISGENET_API_KEY` | the tool falls back to OpenTargets associations; add the key for DisGeNET data |
137
+ | gene_diseases returns an `_error` row | BOTH DisGeNET and OpenTargets failed - not merely a missing key | without `DISGENET_API_KEY` the tool silently returns OpenTargets rows (source: "opentargets"); add the key for DisGeNET-grade associations |
135
138
  | variant_search returns nothing for "BRAF V600E" as `query` | compound free text is not a variant ID | use `gene="BRAF"`, `hgvsp="V600E"` as separate params (the tool also auto-splits a bare "GENE V600E" query, but explicit params are reliable) |
136
139
  | Trial pages repeat or skip | offset paging used on trial_search | use `page_token` cursor paging |
137
140
 
@@ -104,13 +104,13 @@ Check a feature's parameters in detail:
104
104
  | `no_such_tool` after enabling a feature | tools register at server start only | restart the client/session, then verify with `biomcp_configure` `{}` (`running_now`) |
105
105
  | set refused: "sensitive" | sensitive key class requires confirmation | re-send identical call with `confirm_sensitive: true` |
106
106
  | `cwd_refused` | server cwd is / or $HOME | use the client env block (error carries the translation) |
107
- | batch_get item row contains `_error` | that single ID failed (typo, unknown ID) | fix the ID and re-request just that item; other items succeeded |
107
+ | batch_get item row contains `success: false` + `error` | that single ID failed (typo, unknown ID) | fix the ID and re-request just that item; the batch continues and other items succeeded |
108
108
 
109
109
  ## Integration notes
110
110
 
111
111
  - Smoke-test sequence for a fresh setup: `biomcp_configure` with `{}` ->
112
112
  confirm expected features `running_now` -> one cheap domain call.
113
- - `doctor` CLI complements this: `npx -y biomcp@1.4 doctor` (exit 0 = clear);
113
+ - `doctor` CLI complements this: `npx -y biomcp@1.6 doctor` (exit 0 = clear);
114
114
  `--client opencode` emits a paste-ready client entry.
115
115
  - Multi-entity literature pulls (e.g. 10 PMIDs from article_search) belong in
116
116
  ONE `batch_get` call, not 10 article_get calls.
@@ -38,6 +38,18 @@ EXECUTION RULE: Do NOT read or inspect evidence-ledger.py or other skill scripts
38
38
  startup (via `${CLAUDE_PLUGIN_ROOT}`); the orchestrator sends ONLY the
39
39
  filled-in template below.
40
40
 
41
+ ## Cartographer workers (map-first mode)
42
+
43
+ Cartography axes use the SAME worker contract (rules 1-9, file protocol,
44
+ completion gate) with two substitutions: the deliverable is a CATEGORY MAP
45
+ (`reports/<TOPIC>/category_map_<axis>.md`: one subsection per anchored
46
+ category - name, one-line description, 1-3 anchor markers, maturity signals,
47
+ surfacing queries - plus a Query Log and Evidence Gaps) instead of an aspect
48
+ report, and the focus name is `cartography_<axis>`. The dispatch prompt
49
+ template, axis archetypes, anti-pinning contract, and anchoring rule live in
50
+ `references/cartography.md`. Tier A workers read that file too when the
51
+ assigned focus starts with `cartography_`.
52
+
41
53
  ## File protocol
42
54
 
43
55
  - Output files (exactly TWO - together they are the self-contained
@@ -132,7 +144,10 @@ EXECUTION RULE: Do NOT read or inspect evidence-ledger.py or other skill scripts
132
144
  `python3 <SKILL_DIR>/scripts/evidence-ledger.py add <file> --stdin`,
133
145
  substituting the SKILL_DIR value from your prompt LITERALLY - it is a
134
146
  path string, NOT an environment variable (`$SKILL_DIR` in a shell
135
- resolves to nothing and breaks the call). Pass records via a shell heredoc
147
+ resolves to nothing and breaks the call). Anchor every shell command
148
+ to the workspace root (`cd <workspace root> && python3 <SKILL_DIR>/...`)
149
+ so relative report paths resolve regardless of the shell's starting
150
+ directory. Pass records via a shell heredoc
136
151
  or with an array/JSONL file (`add <file> @<batch.json>`):
137
152
  ```bash
138
153
  python3 <SKILL_DIR>/scripts/evidence-ledger.py add reports/<TOPIC>/evidence/<YOUR-FOCUS>.jsonl --stdin << 'EOF'
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: bioresearcher-onboard
3
+ description: "Bootstraps a project-local biomcp MCP server runtime in .bioresearcher-runtime/: downloads portable Node.js 22 (if missing), vendors biomcp with fast mirror support (official or npmmirror), configures optional features (R, Biowasm, SQLite), and registers the server in OpenCode, Claude Code, Cursor, ZCode, Pi, CodeBuddy, or WorkBuddy. Use when biomcp is not installed, when biomedical tools are missing, or on requests like setup bioresearcher, install biomcp, or onboard."
4
+ license: Apache-2.0
5
+ compatibility: "Unix-like shells (Linux, macOS) and Windows (PowerShell, cmd.exe); OpenCode, Claude Code, Cursor, ZCode, Pi Coding Agent, CodeBuddy, WorkBuddy"
6
+ metadata:
7
+ version: "1.2.0"
8
+ source: "bioresearcher-skills"
9
+ allowed-tools: Bash Read Write Edit
10
+ ---
11
+
12
+ # BioMCP Project-Local Onboarding
13
+
14
+ This skill sets up a self-contained, project-local BioMCP runtime in your
15
+ working directory under `.bioresearcher-runtime/`.
16
+
17
+ ## What it does
18
+
19
+ - Checks if the host has Node.js >= 22.13. If missing, downloads a pinned,
20
+ cryptographically verified portable Node.js LTS binary into
21
+ `.bioresearcher-runtime/node/` (zero global installation, does not touch `~`
22
+ or `/usr/local`).
23
+ - Auto-detects network conditions (official distribution vs. fast npmmirror in
24
+ mainland China).
25
+ - Installs `biomcp` locally into `.bioresearcher-runtime/node_modules/` with
26
+ sub-100ms startup latency and offline resilience.
27
+ - Configures optional features (R analysis, Biowasm genomics, SQLite database)
28
+ and registers the server in your harness configuration (`opencode.json`
29
+ under the canonical `mcp.servers.biomcp` key, `.mcp.json`, etc.) without
30
+ overwriting existing settings.
31
+ - Verifies the configuration using the built-in diagnostic doctor.
32
+
33
+ ## Steps
34
+
35
+ Follow the steps below in order.
36
+
37
+ ### Step 1: Run Stage 0 Bootstrap
38
+
39
+ Execute the bootstrap script corresponding to your operating system.
40
+
41
+ **For Unix-like shells (Linux, macOS, Git Bash):**
42
+
43
+ ```bash
44
+ bash skills/bioresearcher-onboard/scripts/bootstrap.sh
45
+ ```
46
+
47
+ **For Windows (PowerShell):**
48
+
49
+ ```powershell
50
+ powershell -NoProfile -ExecutionPolicy Bypass -File skills\bioresearcher-onboard\scripts\bootstrap.ps1
51
+ ```
52
+
53
+ The script automatically:
54
+ 1. Reuses your host Node.js if version >= 22.13 is available.
55
+ 2. If missing, downloads and verifies the official portable Node.js archive into
56
+ `.bioresearcher-runtime/node/`.
57
+ 3. Installs `biomcp@1.6.0` into `.bioresearcher-runtime/node_modules/`.
58
+ 4. Updates your harness configuration (`opencode.json` or `.mcp.json`) with
59
+ absolute executable paths.
60
+
61
+ ### Step 2: Configure Optional Features (Optional)
62
+
63
+ By default, the core biomedical tools (PubMed/literature, clinical trials,
64
+ genes, variants, drugs, diseases, patents) and built-in SQLite are installed.
65
+
66
+ If the user needs specialized capabilities, pass the corresponding flags to the
67
+ bootstrap script or set environment variables before running:
68
+
69
+ - **R / Bioconductor Analysis** (DESeq2, edgeR, limma; requires ~2 GB RAM):
70
+ Pass `--with-r` to install the `webr@0.6` peer dependency and enable R
71
+ analysis in `.biomcp.json`.
72
+ - **Biowasm Analysis** (SAM/BAM/BED/BCF genomics tools):
73
+ Pass `--with-biowasm` to enable Biowasm in `.biomcp.json`.
74
+ - **Local SQLite Database**:
75
+ Pass `--sqlite-path=<path_to_db>` (e.g. `--sqlite-path=data/research.db`).
76
+ - **Client Override**:
77
+ Pass `--client=opencode`, `--client=claude-code`, `--client=cursor`,
78
+ `--client=zcode`, `--client=pi`, `--client=codebuddy`, or
79
+ `--client=workbuddy` if auto-detection should be overridden.
80
+
81
+ Example with R analysis and SQLite enabled:
82
+
83
+ ```bash
84
+ bash skills/bioresearcher-onboard/scripts/bootstrap.sh --with-r --sqlite-path=data/research.db
85
+ ```
86
+
87
+ ### Step 3: In-Session Feature Reconfiguration
88
+
89
+ Once BioMCP is registered and running, you do not need to hand-edit
90
+ configuration files. Use the built-in `biomcp_configure` tool to inspect or
91
+ adjust features dynamically:
92
+
93
+ - Inspect feature status:
94
+ `biomcp_configure(action="status")`
95
+ - Enable or disable features:
96
+ `biomcp_configure(action="set", values={"features.analysis_r.enabled": true})`
97
+ - Configure SQLite database:
98
+ `biomcp_configure(action="set", values={"features.database.enabled": true, "features.database.type": "sqlite", "features.database.sqlite_path": "data/research.db"}, confirm_sensitive=true)`
99
+
100
+ ### Step 4: Restart the Harness
101
+
102
+ MCP servers are spawned at client startup. Instruct the user to restart their
103
+ agent session or IDE to activate the newly connected BioMCP tools:
104
+
105
+ - **OpenCode**: Restart the session or open a new terminal session.
106
+ - **Claude Code**: Start a new session (`claude`).
107
+ - **Cursor / VS Code**: Reload the window or restart the application.
108
+ - **ZCode**: Reload the workspace or start a new ADE session.
109
+ - **Pi Coding Agent**: Start a new agent session (`pi`).
110
+ - **CodeBuddy**: Reload the window or restart the CLI session.
111
+ - **WorkBuddy**: Restart the application or switch workspace.
112
+
113
+ After restarting, verify connectivity by asking the agent to search for a gene
114
+ or article (e.g., `gene_search(query="BRAF")`).
@@ -0,0 +1,96 @@
1
+ # Stage 0 Zero-Dependency PowerShell Bootstrap for bioresearcher-onboard (Windows)
2
+ # Checks for existing Node >= 22.13; if missing, downloads pinned portable
3
+ # Node.js LTS into .bioresearcher-runtime\node, verifies SHA256, and hands off
4
+ # execution to onboard.mjs.
5
+ $ErrorActionPreference = "Stop"
6
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
7
+ $RuntimeRoot = if ($env:BIORESEARCHER_RUNTIME_DIR) { $env:BIORESEARCHER_RUNTIME_DIR } else { Join-Path (Get-Location).Path ".bioresearcher-runtime" }
8
+ $NodeDir = Join-Path $RuntimeRoot "node"
9
+ $NodeExe = Join-Path $NodeDir "node.exe"
10
+
11
+ $NodeVer = "v22.14.0"
12
+
13
+ # 1. Probe if host already satisfies Node >= 22.13
14
+ if (Get-Command node -ErrorAction SilentlyContinue) {
15
+ try {
16
+ $check = node -e "var v=process.versions.node.split('.');process.exit((v[0]>22||(v[0]==22&&v[1]>=13))?0:1)"
17
+ if ($LASTEXITCODE -eq 0) {
18
+ Write-Host "[bioresearcher-onboard] Found host $(node -v) on PATH"
19
+ & node "$ScriptDir\onboard.mjs" @args
20
+ exit $LASTEXITCODE
21
+ }
22
+ } catch {}
23
+ }
24
+
25
+ # If already vendored in working directory, reuse directly
26
+ if (Test-Path $NodeExe) {
27
+ $env:PATH = "$NodeDir;$env:PATH"
28
+ & "$NodeExe" "$ScriptDir\onboard.mjs" @args
29
+ exit $LASTEXITCODE
30
+ }
31
+
32
+ Write-Host "[bioresearcher-onboard] Host Node >= 22.13 not found. Bootstrapping portable Node.js $NodeVer into .bioresearcher-runtime\..."
33
+
34
+ # 2. Architecture Detection
35
+ $Arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) {
36
+ "arm64"
37
+ } else {
38
+ "x64"
39
+ }
40
+
41
+ # 3. Mirror Selection Probe (2-second integer timeout)
42
+ $MirrorBase = "https://nodejs.org/dist/$NodeVer"
43
+ $NpmRegistry = "https://registry.npmjs.org"
44
+ try {
45
+ $req = [System.Net.WebRequest]::Create("https://nodejs.org/dist/")
46
+ $req.Timeout = 2000
47
+ $res = $req.GetResponse()
48
+ $res.Close()
49
+ } catch {
50
+ Write-Host "[bioresearcher-onboard] nodejs.org unreachable; using npmmirror.com fast mirror"
51
+ $MirrorBase = "https://npmmirror.com/mirrors/node/$NodeVer"
52
+ $NpmRegistry = "https://registry.npmmirror.com"
53
+ }
54
+
55
+ $File = "node-$NodeVer-win-$Arch.zip"
56
+ $DownloadUrl = "$MirrorBase/$File"
57
+
58
+ $Checksums = @{
59
+ "x64" = "55b639295920b219bb2acbcfa00f90393a2789095b7323f79475c9f34795f217"
60
+ "arm64" = "2d71f5f9b2fffa33baa108c07d74b0d24e0c3dd8f441d567772ae0e3dd4b1a22"
61
+ }
62
+ $ExpectedSha = $Checksums[$Arch]
63
+
64
+ New-Item -ItemType Directory -Force -Path $RuntimeRoot | Out-Null
65
+ $TmpArchive = Join-Path $RuntimeRoot "node.zip"
66
+
67
+ Write-Host "[bioresearcher-onboard] Downloading $File..."
68
+ Invoke-WebRequest -Uri $DownloadUrl -OutFile $TmpArchive -UseBasicParsing
69
+
70
+ # 4. Checksum verification
71
+ $ActualSha = (Get-FileHash -Path $TmpArchive -Algorithm SHA256).Hash.ToLower()
72
+ if ($ActualSha -ne $ExpectedSha) {
73
+ Remove-Item -Force $TmpArchive -ErrorAction SilentlyContinue
74
+ Write-Error "SHA256 checksum verification failed for $File.`nExpected: $ExpectedSha`nActual: $ActualSha"
75
+ exit 1
76
+ }
77
+
78
+ # 5. Extract and flatten layout
79
+ $TmpExtract = Join-Path $RuntimeRoot "node-extract-tmp"
80
+ if (Test-Path $TmpExtract) { Remove-Item -Recurse -Force $TmpExtract }
81
+ New-Item -ItemType Directory -Force -Path $TmpExtract | Out-Null
82
+ Expand-Archive -Path $TmpArchive -DestinationPath $TmpExtract -Force
83
+ Remove-Item -Force $TmpArchive
84
+
85
+ New-Item -ItemType Directory -Force -Path $NodeDir | Out-Null
86
+ $ExtractedFolder = Get-ChildItem -Path $TmpExtract | Select-Object -First 1
87
+ Get-ChildItem -Path $ExtractedFolder.FullName | Move-Item -Destination $NodeDir -Force
88
+ Remove-Item -Recurse -Force $TmpExtract
89
+
90
+ Write-Host "[bioresearcher-onboard] Portable Node.js ready: $(& $NodeExe -v)"
91
+
92
+ # 6. Hand off to Stage 1
93
+ $env:PATH = "$NodeDir;$env:PATH"
94
+ $env:NPM_CONFIG_REGISTRY = $NpmRegistry
95
+ & "$NodeExe" "$ScriptDir\onboard.mjs" @args
96
+ exit $LASTEXITCODE
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env bash
2
+ # Stage 0 Zero-Dependency Bootstrap for bioresearcher-onboard.
3
+ # Checks for existing Node >= 22.13; if missing, downloads pinned portable
4
+ # Node.js LTS into .bioresearcher-runtime/node, verifies SHA256, and hands off
5
+ # execution to onboard.mjs.
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ RUNTIME_ROOT="${BIORESEARCHER_RUNTIME_DIR:-$PWD/.bioresearcher-runtime}"
10
+ NODE_DIR="$RUNTIME_ROOT/node"
11
+ NODE_BIN="$NODE_DIR/bin/node"
12
+
13
+ NODE_REQ_MAJ=22
14
+ NODE_REQ_MIN=13
15
+ NODE_VER="v22.14.0"
16
+
17
+ # 1. Probe if host already satisfies Node >= 22.13
18
+ if command -v node >/dev/null 2>&1; then
19
+ if node -e "var v=process.versions.node.split('.');process.exit((v[0]>$NODE_REQ_MAJ||(v[0]==$NODE_REQ_MAJ&&v[1]>=$NODE_REQ_MIN))?0:1)" 2>/dev/null; then
20
+ echo "[bioresearcher-onboard] Found host $(node -v) at $(command -v node)"
21
+ exec node "$SCRIPT_DIR/onboard.mjs" "$@"
22
+ fi
23
+ fi
24
+
25
+ # If already vendored in working directory, reuse directly
26
+ if [ -x "$NODE_BIN" ]; then
27
+ export PATH="$NODE_DIR/bin:$PATH"
28
+ exec "$NODE_BIN" "$SCRIPT_DIR/onboard.mjs" "$@"
29
+ fi
30
+
31
+ echo "[bioresearcher-onboard] Host Node >= 22.13 not found. Bootstrapping portable Node.js $NODE_VER into .bioresearcher-runtime/..."
32
+
33
+ # 2. OS and Architecture Detection
34
+ OS_RAW="$(uname -s)"
35
+ case "$OS_RAW" in
36
+ Linux*) OS="linux" ;;
37
+ Darwin*) OS="darwin" ;;
38
+ *) echo "Error: Unsupported operating system $OS_RAW. Windows users should run bootstrap.ps1." >&2; exit 1 ;;
39
+ esac
40
+
41
+ # Linux C library gate
42
+ if [ "$OS" = "linux" ]; then
43
+ if ldd /bin/ls 2>&1 | grep -qi "musl"; then
44
+ echo "Error: Alpine/musl Linux detected. Official Node prebuilts require glibc." >&2
45
+ echo "Please install Node >= 22.13 via your system package manager (e.g. apk add nodejs)." >&2
46
+ exit 1
47
+ fi
48
+ fi
49
+
50
+ ARCH_RAW="$(uname -m)"
51
+ case "$ARCH_RAW" in
52
+ x86_64|amd64) ARCH="x64" ;;
53
+ arm64|aarch64) ARCH="arm64" ;;
54
+ *) echo "Error: Unsupported CPU architecture $ARCH_RAW." >&2; exit 1 ;;
55
+ esac
56
+
57
+ # macOS Rosetta detection: probe true hardware arch
58
+ if [ "$OS" = "darwin" ]; then
59
+ if [ "$(sysctl -in sysctl.proc_translated 2>/dev/null || echo 0)" = "1" ]; then
60
+ ARCH="arm64"
61
+ fi
62
+ fi
63
+
64
+ # 3. Mirror Selection Probe (1.2s timeout)
65
+ if curl -s -I --connect-timeout 1.0 --max-time 1.2 "https://nodejs.org/dist/" >/dev/null 2>&1; then
66
+ MIRROR_BASE="https://nodejs.org/dist/${NODE_VER}"
67
+ NPM_REGISTRY="https://registry.npmjs.org"
68
+ else
69
+ echo "[bioresearcher-onboard] nodejs.org unreachable or timed out; using npmmirror.com fast mirror"
70
+ MIRROR_BASE="https://npmmirror.com/mirrors/node/${NODE_VER}"
71
+ NPM_REGISTRY="https://registry.npmmirror.com"
72
+ fi
73
+
74
+ FILE="node-${NODE_VER}-${OS}-${ARCH}.tar.gz"
75
+ DOWNLOAD_URL="${MIRROR_BASE}/${FILE}"
76
+
77
+ # Pinned SHA256 checksums from official Node.js SHASUMS256.txt
78
+ case "${OS}-${ARCH}" in
79
+ linux-x64) EXPECTED_SHA="9d942932535988091034dc94cc5f42b6dc8784d6366df3a36c4c9ccb3996f0c2" ;;
80
+ linux-arm64) EXPECTED_SHA="8cf30ff7250f9463b53c18f89c6c606dfda70378215b2c905d0a9a8b08bd45e0" ;;
81
+ darwin-x64) EXPECTED_SHA="6698587713ab565a94a360e091df9f6d91c8fadda6d00f0cf6526e9b40bed250" ;;
82
+ darwin-arm64) EXPECTED_SHA="e9404633bc02a5162c5c573b1e2490f5fb44648345d64a958b17e325729a5e42" ;;
83
+ *) echo "Error: Missing checksum for target ${OS}-${ARCH}." >&2; exit 1 ;;
84
+ esac
85
+
86
+ mkdir -p "$RUNTIME_ROOT"
87
+ TMP_ARCHIVE="$RUNTIME_ROOT/node.tar.gz"
88
+
89
+ echo "[bioresearcher-onboard] Downloading $FILE..."
90
+ curl -f -s -L --retry 2 -o "$TMP_ARCHIVE" "$DOWNLOAD_URL"
91
+
92
+ # 4. Checksum verification
93
+ if command -v sha256sum >/dev/null 2>&1; then
94
+ ACTUAL_SHA="$(sha256sum "$TMP_ARCHIVE" | awk '{print $1}')"
95
+ elif command -v shasum >/dev/null 2>&1; then
96
+ ACTUAL_SHA="$(shasum -a 256 "$TMP_ARCHIVE" | awk '{print $1}')"
97
+ else
98
+ ACTUAL_SHA=""
99
+ fi
100
+
101
+ if [ -n "$ACTUAL_SHA" ] && [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
102
+ echo "Error: SHA256 checksum verification failed for $FILE." >&2
103
+ echo "Expected: $EXPECTED_SHA" >&2
104
+ echo "Actual: $ACTUAL_SHA" >&2
105
+ rm -f "$TMP_ARCHIVE"
106
+ exit 1
107
+ fi
108
+
109
+ # 5. Extract and prune
110
+ mkdir -p "$NODE_DIR"
111
+ tar -xzf "$TMP_ARCHIVE" -C "$NODE_DIR" --strip-components=1
112
+ rm -f "$TMP_ARCHIVE"
113
+
114
+ # Remove non-runtime artifacts (saves ~49 MB)
115
+ rm -rf "$NODE_DIR/include" "$NODE_DIR/share" "$NODE_DIR"/*.md "$NODE_DIR"/LICENSE
116
+
117
+ # macOS Gatekeeper quarantine removal
118
+ if [ "$OS" = "darwin" ]; then
119
+ xattr -dr com.apple.quarantine "$NODE_DIR" 2>/dev/null || true
120
+ fi
121
+
122
+ echo "[bioresearcher-onboard] Portable Node.js ready: $("$NODE_BIN" -v)"
123
+
124
+ # 6. Hand off to Stage 1
125
+ export PATH="$NODE_DIR/bin:$PATH"
126
+ export NPM_CONFIG_REGISTRY="$NPM_REGISTRY"
127
+ exec "$NODE_BIN" "$SCRIPT_DIR/onboard.mjs" "$@"