synabun 2.0.10 → 2.1.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/CHANGELOG.md +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -16,11 +16,18 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
16
16
|
import { homedir } from 'node:os';
|
|
17
17
|
import {
|
|
18
18
|
mergeGeminiMcpConfig,
|
|
19
|
-
mergeOpenCodeMcpConfig,
|
|
20
19
|
} from '../neural-interface/lib/mcp-client-config.js';
|
|
20
|
+
import {
|
|
21
|
+
mcpServersOf,
|
|
22
|
+
opencodeConfigDir,
|
|
23
|
+
opencodeConfigFilePath,
|
|
24
|
+
opencodeMcpServerEntry,
|
|
25
|
+
setMcpServer,
|
|
26
|
+
} from '../neural-interface/lib/opencode-config.js';
|
|
21
27
|
import {
|
|
22
28
|
inspectCodexConfig,
|
|
23
29
|
upsertCodexMcpConfig,
|
|
30
|
+
upsertCodexMcpContent,
|
|
24
31
|
} from '../mcp-server/dist/services/codex-config-heal.js';
|
|
25
32
|
import { mcpStdioCommand } from './packaged-runtime.js';
|
|
26
33
|
|
|
@@ -60,11 +67,12 @@ export function auditAndRepairClientConfigs({
|
|
|
60
67
|
} = {}) {
|
|
61
68
|
if (!dataHome || !packageRoot) throw new Error('dataHome and packageRoot are required');
|
|
62
69
|
const definition = buildCanonicalMcpDefinition({ dataHome, packageRoot });
|
|
70
|
+
// Codex loads every SynaBun tool (its deferred catalog); a Codex runtime's own list is only its focus.
|
|
63
71
|
const codexDefinition = {
|
|
64
72
|
...definition,
|
|
65
73
|
env: {
|
|
66
74
|
...definition.env,
|
|
67
|
-
|
|
75
|
+
SYNABUN_TOOLS: 'all',
|
|
68
76
|
SYNABUN_TOOL_CATALOG_MODE: 'deferred',
|
|
69
77
|
},
|
|
70
78
|
};
|
|
@@ -105,11 +113,12 @@ export function auditAndRepairClientConfigs({
|
|
|
105
113
|
results.gemini = { exists: existsSync(geminiPath), connected: false, changed: false, path: geminiPath };
|
|
106
114
|
}
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
// OpenCode 2's global opencode.json (it never reads the config.json of OpenCode 1), its mcp.servers.SynaBun:
|
|
117
|
+
// the folder the rest of SynaBun writes (OPENCODE_CONFIG_DIR, else XDG_CONFIG_HOME's, else ~/.config/opencode).
|
|
118
|
+
const openCodePath = opencodeConfigFilePath(opencodeConfigDir({ home }));
|
|
110
119
|
const openCode = readJson(openCodePath);
|
|
111
|
-
if (openCode
|
|
112
|
-
const next =
|
|
120
|
+
if (mcpServersOf(openCode).SynaBun) {
|
|
121
|
+
const next = setMcpServer(structuredClone(openCode), 'SynaBun', opencodeMcpServerEntry(definition));
|
|
113
122
|
const changed = jsonChanged(openCode, next);
|
|
114
123
|
if (apply && changed) writeJsonAtomic(openCodePath, next);
|
|
115
124
|
results.opencode = { exists: true, connected: true, changed, path: openCodePath };
|
|
@@ -126,9 +135,9 @@ export function auditAndRepairClientConfigs({
|
|
|
126
135
|
upsertCodexMcpConfig(codexPath, codexDefinition);
|
|
127
136
|
changed = before !== readFileSync(codexPath, 'utf-8');
|
|
128
137
|
} else {
|
|
138
|
+
// What the repair would write, compared with what is there: a value like "all" is in most files anyway.
|
|
129
139
|
const content = readFileSync(codexPath, 'utf-8');
|
|
130
|
-
changed =
|
|
131
|
-
|| !Object.values(codexDefinition.env).every(value => content.includes(value));
|
|
140
|
+
changed = upsertCodexMcpContent(content, codexDefinition) !== content;
|
|
132
141
|
}
|
|
133
142
|
results.codex = { exists: true, connected: true, changed, path: codexPath, health: codexHealth };
|
|
134
143
|
} else {
|
package/lib/desktop-app.js
CHANGED
|
@@ -193,9 +193,15 @@ export async function runAppBackend({
|
|
|
193
193
|
// 3. Start, through the launcher (lock, supervisor record, background start).
|
|
194
194
|
let launcherPid = null;
|
|
195
195
|
let lastPhase = null;
|
|
196
|
+
let lastStep = null;
|
|
197
|
+
// A phase is said when it begins; the snapshot also says how far it is (its step, the bytes done), for the
|
|
198
|
+
// window's progress bar.
|
|
196
199
|
const reportPhase = (current) => {
|
|
197
|
-
if (!current?.phase
|
|
200
|
+
if (!current?.phase) return;
|
|
201
|
+
const step = current.phase === 'snapshot' ? `${current.detail?.step ?? ''}:${current.detail?.done ?? ''}:${current.detail?.state ?? ''}` : null;
|
|
202
|
+
if (current.phase === lastPhase && step === lastStep) return;
|
|
198
203
|
lastPhase = current.phase;
|
|
204
|
+
lastStep = step;
|
|
199
205
|
emit({ type: 'supervisor', phase: current.phase, detail: current.detail || null });
|
|
200
206
|
};
|
|
201
207
|
const reporter = () => ({
|
package/lib/external-tools.js
CHANGED
|
@@ -66,7 +66,7 @@ export const EXTERNAL_TOOLS = Object.freeze([
|
|
|
66
66
|
label: 'OpenCode',
|
|
67
67
|
command: 'opencode',
|
|
68
68
|
packages: Object.freeze(['opencode-ai']),
|
|
69
|
-
// (
|
|
69
|
+
// (No OpenCode client package ships: SynaBun speaks to OpenCode 2's serve itself, neural-interface/lib/opencode2-client.js.)
|
|
70
70
|
patterns: Object.freeze([new RegExp(`^opencode-${PLATFORMS}-`)]),
|
|
71
71
|
}),
|
|
72
72
|
Object.freeze({
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
// The launcher also leaves its stop message in the server's stderr log
|
|
12
12
|
// (recordLauncherFailure), because a detached launch has no terminal: the
|
|
13
13
|
// updater's relaunch reads it back to tell the user why SynaBun did not start.
|
|
14
|
+
//
|
|
15
|
+
// The snapshot is the backup service's upgrade snapshot (createUpgradeSnapshot):
|
|
16
|
+
// a consistent copy of each database (a clone where the file system clones)
|
|
17
|
+
// that passed SQLite's integrity check, stored uncompressed, checksummed as it
|
|
18
|
+
// is written and read back once. The desktop app takes it itself before it
|
|
19
|
+
// replaces its own files (lib/update-snapshot.js) and hands it over; this start
|
|
20
|
+
// uses it only when the data home is still what it was taken from
|
|
21
|
+
// (checkUpgradeSnapshotFreshness), whatever process wrote since: an AI tool's
|
|
22
|
+
// own MCP server may have saved a memory while the app swapped itself. Then it
|
|
23
|
+
// takes a new one, here, before the server and so any migration runs.
|
|
14
24
|
|
|
15
25
|
import { appendFileSync, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
16
26
|
import { resolve } from 'node:path';
|
|
@@ -31,19 +41,41 @@ function readJson(path, fallback) {
|
|
|
31
41
|
/**
|
|
32
42
|
* Why the snapshot an in-app update handed over cannot stand in for a fresh
|
|
33
43
|
* one, or null when it can. It can only when the updater recorded that it
|
|
34
|
-
* installed exactly the version now launching,
|
|
35
|
-
*
|
|
36
|
-
*
|
|
44
|
+
* installed exactly the version now launching, the archive passes verification
|
|
45
|
+
* and its database copy passed SQLite's integrity check, and (the desktop app's
|
|
46
|
+
* snapshot, which records what it was taken from) nothing it protects changed
|
|
47
|
+
* since it was taken. A `prepared` update is not enough: its updater may never
|
|
48
|
+
* have run while the old server went on changing data.
|
|
37
49
|
*/
|
|
38
|
-
async function handoffSnapshotProblem({ handoff, version, backupService }) {
|
|
50
|
+
async function handoffSnapshotProblem({ handoff, version, backupService, dataHome }) {
|
|
39
51
|
if (handoff.status !== 'installed') return 'that update was prepared but never recorded as installed';
|
|
40
52
|
if (typeof handoff.target !== 'string' || handoff.target !== version) return 'it belongs to an update to another version';
|
|
41
53
|
if (typeof handoff.snapshotPath !== 'string' || !handoff.snapshotPath) return 'it names no snapshot';
|
|
42
54
|
if (!existsSync(handoff.snapshotPath)) return 'its snapshot file is gone';
|
|
43
55
|
try {
|
|
44
56
|
const service = await backupService();
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
let manifest;
|
|
58
|
+
// The desktop app read its snapshot back once, right after writing it, and
|
|
59
|
+
// recorded which file that was. The very same file, untouched since (device,
|
|
60
|
+
// inode, size, modification time), is not read a second time: only its
|
|
61
|
+
// manifest. Any other file is read in full.
|
|
62
|
+
if (handoff.verifiedStamp && typeof service.sameBackupFile === 'function' && typeof service.readBackupArchiveManifest === 'function'
|
|
63
|
+
&& service.sameBackupFile(handoff.snapshotPath, handoff.verifiedStamp)) {
|
|
64
|
+
manifest = await service.readBackupArchiveManifest(handoff.snapshotPath);
|
|
65
|
+
if (manifest?.verified !== true) return 'its file is not a verified snapshot';
|
|
66
|
+
} else {
|
|
67
|
+
manifest = await service.verifyBackupArchive(handoff.snapshotPath);
|
|
68
|
+
}
|
|
69
|
+
if (manifest?.kind !== 'pre-update') return 'its file is not a pre-update snapshot';
|
|
70
|
+
// The restore runs SQLite's integrity check on the database it puts back: a copy that never passed it is no copy.
|
|
71
|
+
if (manifest.database && manifest.database.integrity !== 'ok') return 'its database copy was not checked with SQLite\'s integrity check';
|
|
72
|
+
// The data as it is now, before anything of this version touches it. The desktop app's snapshot says what it was
|
|
73
|
+
// taken from; the npm updater's (taken by the old server right before it stopped) does not, and is used as before.
|
|
74
|
+
if (handoff.source === 'desktop' || manifest.inputs) {
|
|
75
|
+
if (typeof service.checkUpgradeSnapshotFreshness !== 'function') return 'this version cannot tell whether the data changed since';
|
|
76
|
+
const freshness = await service.checkUpgradeSnapshotFreshness({ dataHome, manifest, databasePath: resolve(dataHome, 'mcp-data', 'memory.db') });
|
|
77
|
+
if (!freshness?.fresh) return `the data changed after it was taken (${freshness?.reason || 'unknown'})`;
|
|
78
|
+
}
|
|
47
79
|
} catch (error) {
|
|
48
80
|
return `its snapshot did not pass verification (${error?.message || error})`;
|
|
49
81
|
}
|
|
@@ -94,7 +126,10 @@ export async function protectFirstLaunchAfterUpdate({
|
|
|
94
126
|
let snapshotPath = null;
|
|
95
127
|
if (pendingHandoff) {
|
|
96
128
|
info('Checking the snapshot taken by the updater...');
|
|
97
|
-
|
|
129
|
+
// The desktop app's splash says it: the copy made before the update is compared with the data (supervisor phase
|
|
130
|
+
// `snapshot`, step `check`).
|
|
131
|
+
progress({ step: 'check' });
|
|
132
|
+
const problem = await handoffSnapshotProblem({ handoff, version, backupService, dataHome });
|
|
98
133
|
if (problem) info(`The updater's snapshot is not used: ${problem}. Taking a new one.`);
|
|
99
134
|
else {
|
|
100
135
|
snapshotPath = handoff.snapshotPath;
|
|
@@ -106,11 +141,16 @@ export async function protectFirstLaunchAfterUpdate({
|
|
|
106
141
|
if (!snapshotPath) {
|
|
107
142
|
const folderPath = resolve(dataHome, 'backups', 'updates');
|
|
108
143
|
let service;
|
|
144
|
+
let lastPhase = null;
|
|
109
145
|
try {
|
|
110
146
|
info(`Protecting user state before first launch of v${version}...`);
|
|
111
147
|
progress({});
|
|
112
148
|
service = await backupService();
|
|
113
|
-
|
|
149
|
+
// The upgrade snapshot where the service has it (every SynaBun since 2026-10-21); the full backup otherwise.
|
|
150
|
+
const takeSnapshot = typeof service.createUpgradeSnapshot === 'function'
|
|
151
|
+
? options => service.createUpgradeSnapshot(options)
|
|
152
|
+
: options => service.createVerifiedBackup(options);
|
|
153
|
+
const snapshot = await takeSnapshot({
|
|
114
154
|
dataHome,
|
|
115
155
|
databasePath: resolve(dataHome, 'mcp-data', 'memory.db'),
|
|
116
156
|
folderPath,
|
|
@@ -118,17 +158,22 @@ export async function protectFirstLaunchAfterUpdate({
|
|
|
118
158
|
appVersion: manifest.lastAppVersion || null,
|
|
119
159
|
// Without this the checksum and archive phases run silently for minutes
|
|
120
160
|
// on a large data home, which users read as a hang and kill.
|
|
121
|
-
onProgress: ({ phase, files, bytes, skippedFiles, skippedBytes }) => {
|
|
122
|
-
progress({ step: phase, files, bytes, skippedFiles });
|
|
161
|
+
onProgress: ({ phase, files, bytes, skippedFiles, skippedBytes, done, total, method, state }) => {
|
|
162
|
+
progress({ step: phase, files, bytes, skippedFiles, done, total, ...(state ? { state } : {}) });
|
|
163
|
+
// Each phase is said once, when it begins; the steps within it only reach the progress bar.
|
|
164
|
+
if (phase === lastPhase) return;
|
|
165
|
+
lastPhase = phase;
|
|
123
166
|
const size = bytes ? ` (${(bytes / 1024 / 1024).toFixed(0)} MB)` : '';
|
|
124
167
|
if (phase === 'collect') {
|
|
125
168
|
info(` ${files} files to protect${size}`);
|
|
126
169
|
if (skippedFiles) {
|
|
127
170
|
info(` Skipping ${skippedFiles} generated media files (${(skippedBytes / 1024 / 1024).toFixed(0)} MB) — an upgrade never touches them`);
|
|
128
171
|
}
|
|
129
|
-
} else if (phase === '
|
|
130
|
-
else if (phase === '
|
|
172
|
+
} else if (phase === 'copy') info(` Copying the databases${method === 'clone' ? ' (a file system clone)' : ''}...`);
|
|
173
|
+
else if (phase === 'checksum') info(` Checksumming ${files} files...`);
|
|
174
|
+
else if (phase === 'archive') info(total ? ` Writing the archive (${(total / 1024 / 1024).toFixed(0)} MB)...` : ` Compressing${size}...`);
|
|
131
175
|
else if (phase === 'verify') info(' Verifying archive integrity...');
|
|
176
|
+
else if (phase === 'integrity') info(' Finishing SQLite\'s integrity check of the database copies...');
|
|
132
177
|
},
|
|
133
178
|
});
|
|
134
179
|
snapshotPath = snapshot.path;
|
package/lib/mcp-dist.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// What "the MCP server is built" means, in one place: mcp-server/dist/index.js and every module it imports by a
|
|
2
|
+
// relative path, transitively, are on disk. mcp-server/dist/ is build output that git ignores, yet a checkout can
|
|
3
|
+
// hold a tracked dist/index.js whose newer imports were never committed (they are ignored too): a server started
|
|
4
|
+
// from it fails to load /mcp. postinstall.js and setup.js build when this says no; the Neural Interface's restore,
|
|
5
|
+
// onboarding status and build route, the install details, the release and the packaged app's checks ask it too.
|
|
6
|
+
//
|
|
7
|
+
// The imports are read from the code itself, never from a comment or a string: a light tokenizer skips comments,
|
|
8
|
+
// strings, template literals and regular expressions, so a word "import" in a text cannot ask for a module that is
|
|
9
|
+
// not loaded (a packaged app has no compiler: a false "missing" there would stop it). Only plain fs and path:
|
|
10
|
+
// postinstall.js and setup.js import this on any Node.
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
13
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const MAX_MODULES = 5000;
|
|
16
|
+
const MAX_MODULE_BYTES = 8 * 1024 * 1024;
|
|
17
|
+
const CODE_EXT = /\.(?:m?js|cjs)$/i;
|
|
18
|
+
// After one of these a `/` starts a regular expression, after anything else it divides.
|
|
19
|
+
const REGEX_AFTER_PUNCT = new Set(['(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '+', '-', '*', '%', '<', '>', '~', '^']);
|
|
20
|
+
const REGEX_AFTER_WORD = new Set(['return', 'typeof', 'instanceof', 'in', 'of', 'new', 'delete', 'void', 'throw', 'case', 'do', 'else', 'yield', 'await']);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The tokens of a module that matter for its imports: words, punctuation and string literals (their values), with
|
|
24
|
+
* comments, template literal text and regular expressions left out.
|
|
25
|
+
* @returns {{ t: 'word'|'punct'|'string', v: string }[]}
|
|
26
|
+
*/
|
|
27
|
+
export function codeTokens(text) {
|
|
28
|
+
const src = String(text ?? '');
|
|
29
|
+
const out = [];
|
|
30
|
+
const frames = []; // one per open `${` of a template literal: the brace depth that closes it
|
|
31
|
+
let depth = 0;
|
|
32
|
+
let i = 0;
|
|
33
|
+
const last = () => out[out.length - 1];
|
|
34
|
+
const regexAllowed = () => {
|
|
35
|
+
const p = last();
|
|
36
|
+
if (!p) return true;
|
|
37
|
+
if (p.t === 'punct') return REGEX_AFTER_PUNCT.has(p.v);
|
|
38
|
+
if (p.t === 'word') return REGEX_AFTER_WORD.has(p.v);
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
const template = () => {
|
|
42
|
+
// From just after a backtick (or a closing `}` of a `${`): to the closing backtick or the next `${`.
|
|
43
|
+
while (i < src.length) {
|
|
44
|
+
const c = src[i];
|
|
45
|
+
if (c === '\\') { i += 2; continue; }
|
|
46
|
+
if (c === '`') { i += 1; out.push({ t: 'string', v: '' }); return; }
|
|
47
|
+
if (c === '$' && src[i + 1] === '{') { i += 2; frames.push(depth); depth += 1; return; }
|
|
48
|
+
i += 1;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
while (i < src.length) {
|
|
52
|
+
const c = src[i];
|
|
53
|
+
if (c === '/' && src[i + 1] === '/') { const nl = src.indexOf('\n', i); i = nl < 0 ? src.length : nl; continue; }
|
|
54
|
+
if (c === '/' && src[i + 1] === '*') { const end = src.indexOf('*/', i + 2); i = end < 0 ? src.length : end + 2; continue; }
|
|
55
|
+
if (c === '\'' || c === '"') {
|
|
56
|
+
let j = i + 1; let v = '';
|
|
57
|
+
while (j < src.length && src[j] !== c && src[j] !== '\n') { if (src[j] === '\\') { v += src[j + 1] ?? ''; j += 2; continue; } v += src[j]; j += 1; }
|
|
58
|
+
out.push({ t: 'string', v });
|
|
59
|
+
i = j + 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (c === '`') { i += 1; template(); continue; }
|
|
63
|
+
if (c === '/' && regexAllowed()) {
|
|
64
|
+
let j = i + 1; let inClass = false;
|
|
65
|
+
while (j < src.length && src[j] !== '\n') {
|
|
66
|
+
const d = src[j];
|
|
67
|
+
if (d === '\\') { j += 2; continue; }
|
|
68
|
+
if (d === '[') inClass = true;
|
|
69
|
+
else if (d === ']') inClass = false;
|
|
70
|
+
else if (d === '/' && !inClass) break;
|
|
71
|
+
j += 1;
|
|
72
|
+
}
|
|
73
|
+
i = j + 1;
|
|
74
|
+
while (i < src.length && /[a-z]/i.test(src[i])) i += 1;
|
|
75
|
+
out.push({ t: 'punct', v: ')' }); // a value, as far as a following `/` is concerned
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/[A-Za-z_$]/.test(c)) {
|
|
79
|
+
let j = i + 1;
|
|
80
|
+
while (j < src.length && /[\w$]/.test(src[j])) j += 1;
|
|
81
|
+
out.push({ t: 'word', v: src.slice(i, j) });
|
|
82
|
+
i = j;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (/\s/.test(c)) { i += 1; continue; }
|
|
86
|
+
if (/[0-9]/.test(c)) { let j = i + 1; while (j < src.length && /[\w.]/.test(src[j])) j += 1; out.push({ t: 'word', v: src.slice(i, j) }); i = j; continue; }
|
|
87
|
+
if (c === '{') depth += 1;
|
|
88
|
+
if (c === '}') {
|
|
89
|
+
if (frames.length && depth - 1 === frames[frames.length - 1]) { depth -= 1; frames.pop(); i += 1; template(); continue; }
|
|
90
|
+
depth = Math.max(0, depth - 1);
|
|
91
|
+
}
|
|
92
|
+
out.push({ t: 'punct', v: c });
|
|
93
|
+
i += 1;
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The module specifiers a module loads: static imports, `export … from`, side-effect imports, `import('…')`. */
|
|
99
|
+
export function importSpecifiers(text) {
|
|
100
|
+
const tokens = codeTokens(text);
|
|
101
|
+
const found = [];
|
|
102
|
+
const at = (k) => tokens[k] || { t: 'end', v: '' };
|
|
103
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
104
|
+
const tok = tokens[k];
|
|
105
|
+
if (tok.t !== 'word' || (tok.v !== 'import' && tok.v !== 'export')) continue;
|
|
106
|
+
if (k > 0 && at(k - 1).t === 'punct' && at(k - 1).v === '.') continue; // obj.import
|
|
107
|
+
const next = at(k + 1);
|
|
108
|
+
if (tok.v === 'import') {
|
|
109
|
+
if (next.t === 'punct' && next.v === '.') continue; // import.meta
|
|
110
|
+
if (next.t === 'punct' && next.v === '(') { if (at(k + 2).t === 'string' && at(k + 3).v === ')') found.push(at(k + 2).v); continue; }
|
|
111
|
+
if (next.t === 'string') { found.push(next.v); continue; }
|
|
112
|
+
}
|
|
113
|
+
// import a, { b as c } from '…'; import * as d from '…'; export { e } from '…'; export * from '…'
|
|
114
|
+
let j = k + 1;
|
|
115
|
+
while (j < tokens.length) {
|
|
116
|
+
const x = at(j);
|
|
117
|
+
if (x.t === 'word' && x.v === 'from') break;
|
|
118
|
+
if (x.t === 'word' || (x.t === 'punct' && (x.v === ',' || x.v === '{' || x.v === '}' || x.v === '*'))) { j += 1; continue; }
|
|
119
|
+
j = -1;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (j > k + 1 && at(j).v === 'from' && at(j + 1).t === 'string') {
|
|
123
|
+
if (tok.v === 'export' && !(next.v === '*' || next.v === '{')) continue;
|
|
124
|
+
found.push(at(j + 1).v);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const posix = (p) => p.split(sep).join('/');
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Every module dist/index.js needs that is not on disk (or cannot be read), as paths from the package root.
|
|
134
|
+
* Modules inside mcp-server/dist are followed; one outside it (the package's lib/) is only looked for.
|
|
135
|
+
* @param {string} packageRoot
|
|
136
|
+
* @param {{ exists?: (p: string) => boolean, readText?: (p: string) => string, entry?: string }} [o]
|
|
137
|
+
* @returns {{ complete: boolean, missing: string[], checked: number }}
|
|
138
|
+
*/
|
|
139
|
+
export function mcpDistMissing(packageRoot, { exists = existsSync, readText = null, entry = 'index.js' } = {}) {
|
|
140
|
+
const root = resolve(packageRoot);
|
|
141
|
+
const dist = join(root, 'mcp-server', 'dist');
|
|
142
|
+
const read = readText || ((p) => {
|
|
143
|
+
const st = statSync(p);
|
|
144
|
+
if (!st.isFile()) throw new Error('not a regular file');
|
|
145
|
+
if (st.size > MAX_MODULE_BYTES) return null;
|
|
146
|
+
return readFileSync(p, 'utf8');
|
|
147
|
+
});
|
|
148
|
+
const missing = new Set();
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
const queue = [join(dist, entry)];
|
|
151
|
+
let checked = 0;
|
|
152
|
+
while (queue.length && checked < MAX_MODULES) {
|
|
153
|
+
const file = queue.shift();
|
|
154
|
+
if (seen.has(file)) continue;
|
|
155
|
+
seen.add(file);
|
|
156
|
+
if (!exists(file)) { missing.add(posix(relative(root, file))); continue; }
|
|
157
|
+
const inside = file.startsWith(dist + sep);
|
|
158
|
+
if (!inside || !CODE_EXT.test(file)) continue;
|
|
159
|
+
let text;
|
|
160
|
+
try { text = read(file); } catch { missing.add(posix(relative(root, file))); continue; }
|
|
161
|
+
checked += 1;
|
|
162
|
+
if (text === null) continue; // too large to read here: present, not followed
|
|
163
|
+
for (const spec of importSpecifiers(text)) {
|
|
164
|
+
if (!spec.startsWith('./') && !spec.startsWith('../')) continue;
|
|
165
|
+
const target = resolve(dirname(file), spec.split(/[?#]/)[0]);
|
|
166
|
+
if (!seen.has(target)) queue.push(target);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const list = [...missing].sort();
|
|
170
|
+
return { complete: list.length === 0, missing: list, checked };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Whether the MCP server is built: dist/index.js and everything it imports are there. */
|
|
174
|
+
export function mcpDistBuilt(packageRoot, options) {
|
|
175
|
+
return mcpDistMissing(packageRoot, options).complete;
|
|
176
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Shared CI identity, OIDC-only publication and exact-byte handoff contract. */
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { createReadStream } from 'node:fs';
|
|
6
|
+
import { appendFile, lstat, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { hashFile } from './virustotal.mjs';
|
|
10
|
+
import { publishNpm, runNpm, verifyPacked } from './release-publish.mjs';
|
|
11
|
+
|
|
12
|
+
export const REPOSITORY = 'danilokhury/Synabun';
|
|
13
|
+
export const WORKFLOW = '.github/workflows/publish.yml';
|
|
14
|
+
export const PUBLISH_NPM = '11.21.0'; // Verified OIDC-compatible CLI; builder stays 10.9.3.
|
|
15
|
+
export const stableVersion = value => typeof value === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
|
|
16
|
+
export const exactCommit = value => typeof value === 'string' && /^[a-f0-9]{40}$/.test(value);
|
|
17
|
+
export const requestId = value => typeof value === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(value);
|
|
18
|
+
export const runTitle = ({ version, commit, request }) => `npm ${version} ${commit} ${request}`;
|
|
19
|
+
export const artifactName = (id, attempt) => `npm-publication-${id}-${attempt}`;
|
|
20
|
+
const numberId = value => /^\d+$/.test(String(value)) && Number.isSafeInteger(Number(value)) && Number(value) > 0;
|
|
21
|
+
|
|
22
|
+
export function validateIdentity({ env, event, pkg, head, main }) {
|
|
23
|
+
if (env.GITHUB_REPOSITORY !== REPOSITORY || pkg.name !== 'synabun' || !stableVersion(pkg.version)
|
|
24
|
+
|| pkg.repository?.url !== `git+https://github.com/${REPOSITORY}.git`) throw new Error('Unexpected npm package/repository identity');
|
|
25
|
+
if (!exactCommit(head) || head !== main || !numberId(env.GITHUB_RUN_ID) || !numberId(env.GITHUB_RUN_ATTEMPT)) throw new Error('Release commit must be the current main tip; require exact run identity');
|
|
26
|
+
let request = null;
|
|
27
|
+
if (env.GITHUB_EVENT_NAME === 'workflow_dispatch') {
|
|
28
|
+
if (env.GITHUB_REF !== 'refs/heads/main' || env.GITHUB_SHA !== head || event.inputs?.commit !== head
|
|
29
|
+
|| event.inputs?.version !== pkg.version || !requestId(event.inputs?.request_id)) throw new Error('Invalid dispatch ref/commit/version/request identity');
|
|
30
|
+
request = event.inputs.request_id;
|
|
31
|
+
} else if (env.GITHUB_EVENT_NAME === 'release') {
|
|
32
|
+
const tag = event.release?.tag_name;
|
|
33
|
+
if (event.action !== 'published' || event.release?.draft !== false || event.release?.prerelease !== false
|
|
34
|
+
|| !new RegExp(`^v?\\.?${pkg.version.replaceAll('.', '\\.')}$`).test(tag || '')
|
|
35
|
+
|| env.GITHUB_REF !== `refs/tags/${tag}` || env.GITHUB_SHA !== head) throw new Error('Invalid stable release event/tag');
|
|
36
|
+
} else throw new Error('Only main workflow_dispatch or stable release.published can publish');
|
|
37
|
+
if (env.GITHUB_WORKFLOW_REF !== `${REPOSITORY}/${WORKFLOW}@${env.GITHUB_REF}`) throw new Error('Unexpected publisher workflow identity');
|
|
38
|
+
return { repository: REPOSITORY, workflow: WORKFLOW, event: env.GITHUB_EVENT_NAME,
|
|
39
|
+
version: pkg.version, commit: head, request, run_id: Number(env.GITHUB_RUN_ID), run_attempt: Number(env.GITHUB_RUN_ATTEMPT) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function oidcEnvironment(env, { cli, userconfig, globalconfig, cache }) {
|
|
43
|
+
if (env.RUNNER_ENVIRONMENT !== 'github-hosted' || !env.ACTIONS_ID_TOKEN_REQUEST_URL || !env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) throw new Error('GitHub-hosted OIDC runner and id-token: write are required');
|
|
44
|
+
// Modern npm tries OIDC first. Remove legacy credentials anyway: no silent token fallback.
|
|
45
|
+
const clean = Object.fromEntries(Object.entries(env).filter(([key]) => !/^(NPM|NPM_TOKEN|NODE_AUTH_TOKEN)$/i.test(key) && !/^npm_config_/i.test(key)));
|
|
46
|
+
return { ...clean, npm_execpath: cli, NPM_CONFIG_USERCONFIG: userconfig, NPM_CONFIG_GLOBALCONFIG: globalconfig,
|
|
47
|
+
NPM_CONFIG_CACHE: cache, NPM_CONFIG_REGISTRY: 'https://registry.npmjs.org/', NPM_CONFIG_UPDATE_NOTIFIER: 'false' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function verifyToolchain(node, npm) {
|
|
51
|
+
if (node.replace(/^v/, '') !== '22.19.0' || npm.trim() !== PUBLISH_NPM) throw new Error(`Publication requires Node 22.19.0 and isolated npm ${PUBLISH_NPM}; builder npm is unchanged`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function registryVersion(version, { fetchImpl = fetch, allowMissing = false } = {}) {
|
|
55
|
+
if (!stableVersion(version)) throw new Error('Invalid registry version');
|
|
56
|
+
const response = await fetchImpl(`https://registry.npmjs.org/synabun/${version}`, { signal: AbortSignal.timeout(30000) });
|
|
57
|
+
if (response.status === 404 && allowMissing) return null;
|
|
58
|
+
if (!response.ok) throw new Error(`npm registry lookup failed (${response.status})`);
|
|
59
|
+
const pkg = await response.json();
|
|
60
|
+
if (pkg?.name !== 'synabun' || pkg.version !== version || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(pkg.dist?.integrity || '')) throw new Error('npm registry identity/integrity is unverified');
|
|
61
|
+
return pkg.dist.integrity;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function sha512(path) {
|
|
65
|
+
const hash = createHash('sha512');
|
|
66
|
+
for await (const bytes of createReadStream(path)) hash.update(bytes);
|
|
67
|
+
return `sha512-${hash.digest('base64')}`;
|
|
68
|
+
}
|
|
69
|
+
export const bundleFiles = version => ['npm-virustotal.json', 'npm-virustotal.md', `synabun-${version}.tgz`];
|
|
70
|
+
|
|
71
|
+
export async function publicationReceipt({ out, identity, registry = registryVersion }) {
|
|
72
|
+
const packed = await verifyPacked({ out });
|
|
73
|
+
if (packed.tarball !== join(out, `synabun-${identity.version}.tgz`)) throw new Error('npm receipt version mismatch');
|
|
74
|
+
const integrity = await sha512(packed.tarball);
|
|
75
|
+
if (await registry(identity.version) !== integrity) throw new Error('npm registry SHA512 differs from scanned tarball');
|
|
76
|
+
const files = {};
|
|
77
|
+
for (const name of bundleFiles(identity.version)) files[name] = { sha256: await hashFile(join(out, name)), size: (await lstat(join(out, name))).size };
|
|
78
|
+
const receipt = { schema: 1, ...identity, integrity, files };
|
|
79
|
+
await writeFile(join(out, 'npm-publication.json'), JSON.stringify(receipt, null, 2) + '\n');
|
|
80
|
+
return receipt;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function verifyPublication({ out, expected, registry = registryVersion, strictFiles = true }) {
|
|
84
|
+
const receipt = JSON.parse(await readFile(join(out, 'npm-publication.json'), 'utf8'));
|
|
85
|
+
if (receipt.schema !== 1 || receipt.repository !== REPOSITORY || receipt.workflow !== WORKFLOW
|
|
86
|
+
|| !stableVersion(receipt.version) || !exactCommit(receipt.commit) || !numberId(receipt.run_id)
|
|
87
|
+
|| !numberId(receipt.run_attempt) || !['workflow_dispatch', 'release'].includes(receipt.event)
|
|
88
|
+
|| (receipt.event === 'workflow_dispatch' && !requestId(receipt.request))) throw new Error('Unknown or invalid npm publication receipt');
|
|
89
|
+
for (const [key, value] of Object.entries(expected)) if (receipt[key] !== value) throw new Error(`npm publication identity mismatch: ${key}`);
|
|
90
|
+
const names = bundleFiles(expected.version);
|
|
91
|
+
if (JSON.stringify(Object.keys(receipt.files || {}).sort()) !== JSON.stringify([...names].sort())
|
|
92
|
+
|| (strictFiles && JSON.stringify((await readdir(out)).sort()) !== JSON.stringify([...names, 'npm-publication.json'].sort()))) throw new Error('Unexpected npm artifact file set');
|
|
93
|
+
for (const name of [...names, 'npm-publication.json']) {
|
|
94
|
+
const info = await lstat(join(out, name));
|
|
95
|
+
if (!info.isFile() || info.isSymbolicLink()) throw new Error('npm artifact must contain regular files only');
|
|
96
|
+
if (name !== 'npm-publication.json' && (info.size !== receipt.files[name].size || await hashFile(join(out, name)) !== receipt.files[name].sha256)) throw new Error(`npm artifact integrity mismatch: ${name}`);
|
|
97
|
+
}
|
|
98
|
+
const packed = await verifyPacked({ out });
|
|
99
|
+
if (packed.tarball !== join(out, `synabun-${expected.version}.tgz`) || await sha512(packed.tarball) !== receipt.integrity
|
|
100
|
+
|| await registry(expected.version) !== receipt.integrity) throw new Error('npm publication version/registry integrity mismatch');
|
|
101
|
+
// Read only the packed manifest; never extract arbitrary paths into the checkout.
|
|
102
|
+
const tar = spawnSync('tar', ['-xOf', packed.tarball, 'package/package.json'], { encoding: 'utf8', maxBuffer: 1024 * 1024, timeout: 30000 });
|
|
103
|
+
if (tar.status !== 0) throw new Error('Cannot inspect npm tarball manifest');
|
|
104
|
+
const pkg = JSON.parse(tar.stdout);
|
|
105
|
+
if (pkg.name !== 'synabun' || pkg.version !== expected.version || pkg.repository?.url !== `git+https://github.com/${REPOSITORY}.git`) throw new Error('Packed npm manifest identity mismatch');
|
|
106
|
+
return receipt;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function git(...args) {
|
|
110
|
+
const result = spawnSync('git', args, { encoding: 'utf8', timeout: 120000 });
|
|
111
|
+
if (result.status !== 0) throw new Error('Cannot verify current main commit');
|
|
112
|
+
return result.stdout.trim();
|
|
113
|
+
}
|
|
114
|
+
async function ciIdentity() {
|
|
115
|
+
git('fetch', '--no-tags', '--depth=1', 'origin', 'main');
|
|
116
|
+
return validateIdentity({ env: process.env, event: JSON.parse(await readFile(process.env.GITHUB_EVENT_PATH, 'utf8')),
|
|
117
|
+
pkg: JSON.parse(await readFile('package.json', 'utf8')), head: git('rev-parse', 'HEAD'), main: git('rev-parse', 'FETCH_HEAD') });
|
|
118
|
+
}
|
|
119
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
120
|
+
try {
|
|
121
|
+
const stage = process.argv[2], identity = await ciIdentity(), out = join(process.env.RUNNER_TEMP, 'npm-release');
|
|
122
|
+
if (stage === 'identity') {
|
|
123
|
+
const published = await registryVersion(identity.version, { allowMissing: true });
|
|
124
|
+
if (published && identity.event === 'workflow_dispatch') throw new Error('Version already published: recover the original successful run artifacts; never repack an existing version');
|
|
125
|
+
await appendFile(process.env.GITHUB_OUTPUT, `published=${!!published}\n`);
|
|
126
|
+
} else if (stage === 'publish') {
|
|
127
|
+
try { await lstat('.npmrc'); throw new Error('Project .npmrc is forbidden in the publishing checkout'); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
128
|
+
const base = process.env.RUNNER_TEMP;
|
|
129
|
+
const userconfig = join(base, 'npm-oidc-userconfig'), globalconfig = join(base, 'npm-oidc-globalconfig');
|
|
130
|
+
await writeFile(userconfig, ''); await writeFile(globalconfig, '');
|
|
131
|
+
const env = oidcEnvironment(process.env, { cli: join(base, 'npm-oidc-cli/node_modules/npm/bin/npm-cli.js'), userconfig, globalconfig, cache: join(base, 'npm-oidc-cache') });
|
|
132
|
+
const run = (args, options) => runNpm(args, { ...options, env });
|
|
133
|
+
verifyToolchain(process.version, run(['--version'], { capture: true }));
|
|
134
|
+
await publishNpm({ cwd: process.cwd(), out, run, publishPacked: true, provenance: true });
|
|
135
|
+
await publicationReceipt({ out, identity });
|
|
136
|
+
} else throw new Error('Use identity or publish');
|
|
137
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
138
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** The only documented npm publication path: check, pack, scan, publish the same tgz. */
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { access, chmod, mkdir, mkdtemp, readFile, copyFile, rm } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
import { approved, credentials, evidenceDigest, hashFile } from './virustotal.mjs';
|
|
9
|
+
import { scanRelease } from './release-scan.mjs';
|
|
10
|
+
|
|
11
|
+
export function runNpm(args, { cwd, env = process.env, capture = false } = {}) {
|
|
12
|
+
// npm_execpath works with npm.cmd on Windows without shell argument interpolation.
|
|
13
|
+
const npm = env.npm_execpath;
|
|
14
|
+
if (!npm && process.platform === 'win32') throw new Error('On Windows run npm run release:publish so npm_execpath is available');
|
|
15
|
+
const command = npm ? process.execPath : 'npm';
|
|
16
|
+
const result = spawnSync(command, npm ? [npm, ...args] : args, { cwd, env, encoding: 'utf8', stdio: capture ? 'pipe' : 'inherit', timeout: 1800000, maxBuffer: 8 * 1024 * 1024 });
|
|
17
|
+
// Never replay npm's captured output (it may contain authentication diagnostics).
|
|
18
|
+
if (result.error || result.status !== 0) throw new Error(`npm ${args[0]} failed; publication stopped`);
|
|
19
|
+
return result.stdout;
|
|
20
|
+
}
|
|
21
|
+
export async function verifyPacked({ out, now = Date.now() }) {
|
|
22
|
+
const report = JSON.parse(await readFile(join(out, 'npm-virustotal.json'), 'utf8'));
|
|
23
|
+
if (report.schema !== 1 || !report.approved || report.items?.length !== 1 || !approved(report.items)) throw new Error('npm scan report is not approved');
|
|
24
|
+
const item = report.items[0];
|
|
25
|
+
if (item.kind !== 'file' || basename(item.name) !== item.name || !/^synabun-.*\.tgz$/.test(item.name)) throw new Error('Invalid npm scan subject');
|
|
26
|
+
const { evidence_digest, review, ...evidence } = item;
|
|
27
|
+
if (evidenceDigest(evidence) !== evidence_digest) throw new Error('npm report evidence digest mismatch');
|
|
28
|
+
const at = Date.parse(item.analysis_date);
|
|
29
|
+
if (!Number.isFinite(at) || at > now + 300000 || now - at > 7 * 86400000 || item.analysis_status !== 'completed' || item.supported_engines < 10) throw new Error('npm scan report stale, unfinished or inconclusive');
|
|
30
|
+
if (item.status === 'detections') {
|
|
31
|
+
const { applyReviews } = await import('./virustotal.mjs');
|
|
32
|
+
if (!approved(applyReviews([{ ...item, review: null }], [{ ...review, name: item.name, sha256: item.sha256, evidence_digest }], now))) throw new Error('npm detection review required');
|
|
33
|
+
}
|
|
34
|
+
const tarball = join(out, item.name);
|
|
35
|
+
if (await hashFile(tarball) !== item.sha256) throw new Error('npm tarball SHA-256 mismatch; repack and scan');
|
|
36
|
+
return { tarball, sha256: item.sha256 };
|
|
37
|
+
}
|
|
38
|
+
export async function publishNpm({ cwd, out, reviewFile, provenance = false, packOnly = false, publishPacked = false, run = runNpm, scan = scanRelease }) {
|
|
39
|
+
credentials(); // Before installs or registry mutations.
|
|
40
|
+
const rel = relative(resolve(cwd), resolve(out));
|
|
41
|
+
if (!rel || (!isAbsolute(rel) && rel !== '..' && !rel.startsWith('../') && !rel.startsWith('..\\'))) throw new Error('Release output must be outside the checkout to keep reports/tarballs out of npm packages');
|
|
42
|
+
const pkg = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8'));
|
|
43
|
+
if (pkg.name !== 'synabun' || pkg.scripts?.prepublishOnly?.includes('refuse-publish-from-dev') || !pkg.scripts?.prepublishOnly?.includes('release-publish.mjs guard')) throw new Error('Publish from a fresh prepared Clean clone with npm run release:publish; Dev publication is refused');
|
|
44
|
+
await mkdir(out, { recursive: true });
|
|
45
|
+
if (!publishPacked) {
|
|
46
|
+
run(['run', 'deps:check'], { cwd });
|
|
47
|
+
run(['ci', '--ignore-scripts'], { cwd });
|
|
48
|
+
// Preserve Clean's entire install/build/file/pack chain. npm publish TGZ skips it.
|
|
49
|
+
run(['run', 'prepublishOnly'], { cwd, env: { ...process.env, SYNABUN_RELEASE_PACK: '1' } });
|
|
50
|
+
run(['run', 'prepack'], { cwd });
|
|
51
|
+
for (const file of ['mcp-server/dist/index.js', 'NOTICE', 'THIRD-PARTY-LICENSES.md']) await access(join(cwd, file));
|
|
52
|
+
const staging = await mkdtemp(join(tmpdir(), 'synabun-npm-pack-'));
|
|
53
|
+
try {
|
|
54
|
+
const packed = JSON.parse(run(['pack', '--ignore-scripts', '--json', '--pack-destination', staging], { cwd, capture: true }));
|
|
55
|
+
const name = packed?.[0]?.filename;
|
|
56
|
+
if (packed.length !== 1 || !name || basename(name) !== name || name !== `synabun-${pkg.version}.tgz`) throw new Error('npm pack returned an unexpected tarball');
|
|
57
|
+
const tarball = join(out, name);
|
|
58
|
+
await rm(tarball, { force: true });
|
|
59
|
+
await copyFile(join(staging, name), tarball);
|
|
60
|
+
await chmod(tarball, 0o444);
|
|
61
|
+
await scan({ files: [tarball], out, prefix: 'npm-virustotal', reviewFile });
|
|
62
|
+
} finally { await rm(staging, { recursive: true, force: true }); }
|
|
63
|
+
}
|
|
64
|
+
const packed = await verifyPacked({ out });
|
|
65
|
+
if (basename(packed.tarball) !== `synabun-${pkg.version}.tgz`) throw new Error('npm tarball version mismatch');
|
|
66
|
+
if (!packOnly) {
|
|
67
|
+
run(['publish', packed.tarball, '--ignore-scripts', '--access', 'public', '--tag', 'latest', ...(provenance ? ['--provenance'] : [])], { cwd });
|
|
68
|
+
if (await hashFile(packed.tarball) !== packed.sha256) throw new Error('npm tarball changed during publication; inspect registry immediately');
|
|
69
|
+
}
|
|
70
|
+
return packed;
|
|
71
|
+
}
|
|
72
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
73
|
+
try {
|
|
74
|
+
if (process.argv[2] === 'guard') {
|
|
75
|
+
if (process.env.SYNABUN_RELEASE_PACK !== '1') throw new Error('Direct npm publish is disabled: use npm run release:publish -- --out /absolute/release/folder (packs, scans and publishes the exact tgz).');
|
|
76
|
+
} else {
|
|
77
|
+
const args = process.argv.slice(2); let out, reviewFile, provenance = false, packOnly = false, publishPacked = false;
|
|
78
|
+
while (args.length) {
|
|
79
|
+
const arg = args.shift();
|
|
80
|
+
if (arg === '--out') out = args.shift();
|
|
81
|
+
else if (arg === '--review') reviewFile = args.shift();
|
|
82
|
+
else if (arg === '--provenance') provenance = true;
|
|
83
|
+
else if (arg === '--pack-only') packOnly = true;
|
|
84
|
+
else if (arg === '--publish-packed') publishPacked = true;
|
|
85
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
86
|
+
}
|
|
87
|
+
if (!out || (packOnly && publishPacked)) throw new Error('Use --out DIR; --pack-only and --publish-packed are mutually exclusive');
|
|
88
|
+
await publishNpm({ cwd: dirname(dirname(fileURLToPath(import.meta.url))), out: resolve(out), reviewFile, provenance, packOnly, publishPacked });
|
|
89
|
+
}
|
|
90
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
91
|
+
}
|