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
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynaBun — the desktop app's protection of the data before an update
|
|
3
|
+
* (`<entry> update-snapshot --json`, packaged/runtime/bootstrap.mjs).
|
|
4
|
+
*
|
|
5
|
+
* A new version never runs on user state without a verified pre-update
|
|
6
|
+
* snapshot that is still exact (lib/first-launch-protection.js). The desktop
|
|
7
|
+
* app takes that snapshot itself, before it replaces its own files, and hands it
|
|
8
|
+
* over to the new version's first start, which then takes none when nothing
|
|
9
|
+
* changed since: the slow part of an update happens while the old version still
|
|
10
|
+
* shows its window, never in a window that is gone.
|
|
11
|
+
*
|
|
12
|
+
* prepare while SynaBun still runs and the update waits for its moment (a
|
|
13
|
+
* countdown, an idle minute after the download): the snapshot is
|
|
14
|
+
* taken in the background, or the one taken earlier (--reuse) is
|
|
15
|
+
* kept when it is still exact
|
|
16
|
+
* install after the app stopped its server, right before it replaces its
|
|
17
|
+
* files: the prepared snapshot when it is still exact, otherwise a
|
|
18
|
+
* new one; then the handoff in data/update-handoff.json (status
|
|
19
|
+
* `prepared`, the install's id, the archive's stamp). The new copy
|
|
20
|
+
* marks it `installed` once it runs
|
|
21
|
+
* (packaging/desktop/src/updater/state.mjs); a swap that failed or
|
|
22
|
+
* rolled back marks it `cancelled`
|
|
23
|
+
*
|
|
24
|
+
* "Still exact" is decided from the data itself, never from what one process
|
|
25
|
+
* saw: the snapshot records what it was taken from (every database's
|
|
26
|
+
* fingerprint, every other file's content) and checkUpgradeSnapshotFreshness
|
|
27
|
+
* compares the data home with it now. A memory saved meanwhile by any process
|
|
28
|
+
* (the server on its way out, an AI tool's own MCP server) makes it stale. The
|
|
29
|
+
* new version's first start makes the same check before its server, and so any
|
|
30
|
+
* migration, runs: a write during the swap is caught there.
|
|
31
|
+
*
|
|
32
|
+
* The snapshot is the backup service's upgrade snapshot (createUpgradeSnapshot:
|
|
33
|
+
* a clone of each database where the file system clones, stored uncompressed,
|
|
34
|
+
* checksummed as it is written, every database copy passing SQLite's integrity
|
|
35
|
+
* check, read back once), so Settings > restore and `synabun restore-backup`
|
|
36
|
+
* restore it as any backup. Its stamp (device, inode, size, modification time)
|
|
37
|
+
* lets the new version know the file is the one that was read back, without
|
|
38
|
+
* reading it again.
|
|
39
|
+
*
|
|
40
|
+
* Every step is one JSON line on stdout: { type: 'progress', phase, ... },
|
|
41
|
+
* then { type: 'done', ... } or { type: 'failed', code, message }. Phases:
|
|
42
|
+
* `check` (a prepared snapshot is compared with the data), `stale` (it is not
|
|
43
|
+
* used: `reason`), `reuse`, then those of createUpgradeSnapshot for a new one
|
|
44
|
+
* (collect, copy, archive, verify, integrity).
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
48
|
+
import { basename, dirname, relative, resolve, isAbsolute } from 'node:path';
|
|
49
|
+
import { dataHomeHasState } from './data-home-migration.js';
|
|
50
|
+
|
|
51
|
+
export const UPDATE_HANDOFF_FILE = 'update-handoff.json';
|
|
52
|
+
export const handoffPathFor = (dataHome) => resolve(dataHome, 'data', UPDATE_HANDOFF_FILE);
|
|
53
|
+
export const updateSnapshotFolder = (dataHome) => resolve(dataHome, 'backups', 'updates');
|
|
54
|
+
|
|
55
|
+
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
56
|
+
const INSTALL_ID = /^[A-Za-z0-9_-]{6,64}$/;
|
|
57
|
+
const SNAPSHOT_NAME = /^synabun-pre-update-[\dT:.\-Z]+\.zip$/;
|
|
58
|
+
|
|
59
|
+
function readJson(path) {
|
|
60
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeJsonAtomic(path, value) {
|
|
64
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
65
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
66
|
+
writeFileSync(temp, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
67
|
+
renameSync(temp, path);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function inside(folder, path) {
|
|
71
|
+
const rel = relative(resolve(folder), resolve(path));
|
|
72
|
+
return !!rel && !rel.startsWith('..') && !isAbsolute(rel);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The command line: --target <version> [--current <version>] [--install-id <id>] [--prepare]
|
|
77
|
+
* [--reuse <file> --reuse-stamp <json>] [--discard <file>] (a prepared snapshot that is no longer exact: its room
|
|
78
|
+
* goes to the new one).
|
|
79
|
+
*/
|
|
80
|
+
export function parseUpdateSnapshotArgs(args = []) {
|
|
81
|
+
const value = (name) => {
|
|
82
|
+
const at = args.indexOf(name);
|
|
83
|
+
return at >= 0 && typeof args[at + 1] === 'string' ? args[at + 1] : null;
|
|
84
|
+
};
|
|
85
|
+
const target = value('--target');
|
|
86
|
+
const current = value('--current');
|
|
87
|
+
const installId = value('--install-id');
|
|
88
|
+
const reuse = value('--reuse');
|
|
89
|
+
const discard = value('--discard');
|
|
90
|
+
let reuseStamp = null;
|
|
91
|
+
try { reuseStamp = value('--reuse-stamp') ? JSON.parse(value('--reuse-stamp')) : null; } catch { reuseStamp = null; }
|
|
92
|
+
if (!target || !VERSION.test(target)) return { error: 'a --target version' };
|
|
93
|
+
if (current && !VERSION.test(current)) return { error: 'a --current version' };
|
|
94
|
+
if (installId && !INSTALL_ID.test(installId)) return { error: 'an --install-id of letters, digits, _ and -' };
|
|
95
|
+
const prepare = args.includes('--prepare');
|
|
96
|
+
if (!prepare && !installId) return { error: 'an --install-id (or --prepare)' };
|
|
97
|
+
return { target, current, installId, prepare, reuse, reuseStamp, discard };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Why the prepared snapshot at `path` cannot stand for this install, or null
|
|
102
|
+
* when it can: it is a pre-update snapshot in the update folder, the very file
|
|
103
|
+
* the preparation wrote and read back (its stamp is unchanged), its database
|
|
104
|
+
* passed SQLite's integrity check, and the data home is still what it was taken
|
|
105
|
+
* from (checkUpgradeSnapshotFreshness: whoever wrote since, it is stale).
|
|
106
|
+
*/
|
|
107
|
+
export async function preparedSnapshotProblem({ path, stamp, folderPath, service, dataHome = null, databasePath = null }) {
|
|
108
|
+
if (!path) return 'none was prepared';
|
|
109
|
+
if (!inside(folderPath, path) || !SNAPSHOT_NAME.test(basename(path))) return 'it is not a snapshot of the update folder';
|
|
110
|
+
if (!existsSync(path)) return 'its file is gone';
|
|
111
|
+
if (!service.sameBackupFile(path, stamp)) return 'its file changed after it was checked';
|
|
112
|
+
let manifest;
|
|
113
|
+
try {
|
|
114
|
+
manifest = await service.readBackupArchiveManifest(path);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return `its manifest cannot be read (${error?.message || error})`;
|
|
117
|
+
}
|
|
118
|
+
if (manifest?.kind !== 'pre-update' || manifest?.verified !== true) return 'it is not a verified pre-update snapshot';
|
|
119
|
+
if (manifest.database && manifest.database.integrity !== 'ok') return 'its database copy was not checked with SQLite\'s integrity check';
|
|
120
|
+
if (!dataHome) return null;
|
|
121
|
+
const freshness = await service.checkUpgradeSnapshotFreshness({ dataHome, manifest, ...(databasePath ? { databasePath } : {}) });
|
|
122
|
+
if (!freshness?.fresh) return `the data changed after it was taken (${freshness?.reason || 'unknown'})`;
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Take (or reuse) the pre-update snapshot and, for an install, write the
|
|
128
|
+
* handoff. Everything that touches the system is a parameter, for the tests.
|
|
129
|
+
* Resolves the last event ({ type: 'done' | 'failed' }); `emit` gets every one.
|
|
130
|
+
*/
|
|
131
|
+
export async function runUpdateSnapshot({
|
|
132
|
+
dataHome,
|
|
133
|
+
current = null,
|
|
134
|
+
target,
|
|
135
|
+
installId = null,
|
|
136
|
+
prepare = false,
|
|
137
|
+
reuse = null,
|
|
138
|
+
reuseStamp = null,
|
|
139
|
+
discard = null,
|
|
140
|
+
emit = () => {},
|
|
141
|
+
loadBackupService = () => import('../neural-interface/lib/backup-service.js'),
|
|
142
|
+
now = () => new Date(),
|
|
143
|
+
} = {}) {
|
|
144
|
+
const say = (event) => { try { emit(event); } catch { /* the caller went away: the work goes on */ } return event; };
|
|
145
|
+
const folderPath = updateSnapshotFolder(dataHome);
|
|
146
|
+
const databasePath = resolve(dataHome, 'mcp-data', 'memory.db');
|
|
147
|
+
const removable = (path) => !!path && existsSync(path) && inside(folderPath, path) && SNAPSHOT_NAME.test(basename(path));
|
|
148
|
+
// A data home with nothing of the user's in it needs no snapshot (the new version's first start agrees:
|
|
149
|
+
// lib/first-launch-protection.js), and gets no handoff.
|
|
150
|
+
if (!dataHomeHasState(dataHome)) return say({ type: 'done', skipped: 'no-state', prepared: !!prepare, reused: false, path: null });
|
|
151
|
+
let service;
|
|
152
|
+
try {
|
|
153
|
+
service = await loadBackupService();
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return say({ type: 'failed', code: 'BACKUP_SERVICE', message: `the backup code could not be loaded: ${error?.message || error}` });
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
let snapshot = null;
|
|
159
|
+
let reused = false;
|
|
160
|
+
let stale = null;
|
|
161
|
+
if (discard && discard !== reuse && removable(discard)) {
|
|
162
|
+
say({ type: 'progress', phase: 'stale', reason: 'the app said it is no longer exact' });
|
|
163
|
+
try { rmSync(discard, { force: true }); } catch { /* left for retention */ }
|
|
164
|
+
}
|
|
165
|
+
if (reuse) {
|
|
166
|
+
// The copy made ahead, compared with the data as it is now.
|
|
167
|
+
say({ type: 'progress', phase: 'check', path: reuse });
|
|
168
|
+
const checkStarted = Date.now();
|
|
169
|
+
stale = await preparedSnapshotProblem({ path: reuse, stamp: reuseStamp, folderPath, service, dataHome, databasePath });
|
|
170
|
+
if (!stale) {
|
|
171
|
+
snapshot = { path: reuse, name: basename(reuse), stamp: reuseStamp, timings: { check: Date.now() - checkStarted } };
|
|
172
|
+
reused = true;
|
|
173
|
+
say({ type: 'progress', phase: 'reuse', path: reuse });
|
|
174
|
+
} else {
|
|
175
|
+
say({ type: 'progress', phase: 'stale', reason: stale });
|
|
176
|
+
// A snapshot of an earlier moment: its room goes to the new one (it is never a user's file: the name and the
|
|
177
|
+
// folder are SynaBun's own).
|
|
178
|
+
if (removable(reuse)) {
|
|
179
|
+
try { rmSync(reuse, { force: true }); } catch { /* left for retention */ }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!snapshot) {
|
|
184
|
+
const taken = await service.createUpgradeSnapshot({
|
|
185
|
+
dataHome,
|
|
186
|
+
databasePath,
|
|
187
|
+
folderPath,
|
|
188
|
+
kind: 'pre-update',
|
|
189
|
+
appVersion: current,
|
|
190
|
+
onProgress: (detail) => say({ type: 'progress', ...detail }),
|
|
191
|
+
now,
|
|
192
|
+
});
|
|
193
|
+
snapshot = { path: taken.path, name: taken.name, stamp: taken.stamp, sizeBytes: taken.sizeBytes, database: taken.database, timings: taken.timings };
|
|
194
|
+
}
|
|
195
|
+
if (prepare) {
|
|
196
|
+
return say({ type: 'done', prepared: true, reused, stale, path: snapshot.path, stamp: snapshot.stamp, sizeBytes: snapshot.sizeBytes ?? null, timings: snapshot.timings ?? null });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Older snapshots go by the usual rules; the one just made is the newest, and so kept.
|
|
200
|
+
try { service.applyBackupRetention({ folderPath }); } catch { /* housekeeping never stops an update */ }
|
|
201
|
+
const handoff = {
|
|
202
|
+
version: 1,
|
|
203
|
+
status: 'prepared',
|
|
204
|
+
source: 'desktop',
|
|
205
|
+
preparedAt: now().toISOString(),
|
|
206
|
+
current,
|
|
207
|
+
target,
|
|
208
|
+
installId,
|
|
209
|
+
snapshotPath: snapshot.path,
|
|
210
|
+
verifiedStamp: snapshot.stamp,
|
|
211
|
+
verifiedAt: now().toISOString(),
|
|
212
|
+
reused,
|
|
213
|
+
};
|
|
214
|
+
writeJsonAtomic(handoffPathFor(dataHome), handoff);
|
|
215
|
+
return say({ type: 'done', prepared: false, reused, stale, path: snapshot.path, stamp: snapshot.stamp, handoff: handoffPathFor(dataHome), timings: snapshot.timings ?? null });
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return say({
|
|
218
|
+
type: 'failed',
|
|
219
|
+
code: error?.code === 'ENOSPC' ? 'NO_SPACE' : error?.code === 'EBACKUPDRIVE' ? 'NO_DRIVE' : error?.code === 'EINTEGRITY' ? 'INTEGRITY' : 'SNAPSHOT_FAILED',
|
|
220
|
+
message: String(error?.message || error).split('\n')[0].slice(0, 400),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The handoff as it is now, or null. */
|
|
226
|
+
export function readUpdateHandoff(dataHome) {
|
|
227
|
+
return readJson(handoffPathFor(dataHome));
|
|
228
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/** Release-only VirusTotal API v3 client. No API calls occur on import. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { createReadStream } from 'node:fs';
|
|
4
|
+
import { stat } from 'node:fs/promises';
|
|
5
|
+
import { basename } from 'node:path';
|
|
6
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
+
|
|
8
|
+
export const DIRECT_LIMIT = 32 * 1024 * 1024;
|
|
9
|
+
export const UPLOAD_LIMIT = 650 * 1024 * 1024;
|
|
10
|
+
export const API = 'https://www.virustotal.com/api/v3/';
|
|
11
|
+
export const README_VERIFICATION = '<!-- synabun:verification:start -->\nPackage verification: [downloadable VirusTotal reports and SHA-256 hashes](https://github.com/danilokhury/Synabun/releases/latest). Match the report to your exact version and file hash. Reports record engine coverage and analysis dates; they are not a guarantee of safety. Website results describe URL reputation, not a vulnerability audit.\n<!-- synabun:verification:end -->';
|
|
12
|
+
export function verificationReadme(text) {
|
|
13
|
+
return text.includes('<!-- synabun:verification:start -->')
|
|
14
|
+
? text.replace(/<!-- synabun:verification:start -->[\s\S]*?<!-- synabun:verification:end -->/, README_VERIFICATION)
|
|
15
|
+
: `${text.trimEnd()}\n\n${README_VERIFICATION}\n`;
|
|
16
|
+
}
|
|
17
|
+
export async function hashFile(path) {
|
|
18
|
+
const hash = createHash('sha256');
|
|
19
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
20
|
+
return hash.digest('hex');
|
|
21
|
+
}
|
|
22
|
+
export const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
23
|
+
export function evidenceDigest(item) {
|
|
24
|
+
const { source, analysis_id, evidence_digest, review, ...evidence } = item;
|
|
25
|
+
return digest(evidence);
|
|
26
|
+
}
|
|
27
|
+
export function credentials(key = process.env.VIRUSTOTAL_API_KEY) {
|
|
28
|
+
if (typeof key !== 'string' || !key.trim()) throw new Error('Set VIRUSTOTAL_API_KEY locally or as a GitHub Actions secret. Eligible VirusTotal API access and public sample-sharing consent are required; see docs/release-pipeline.md.');
|
|
29
|
+
return key.trim();
|
|
30
|
+
}
|
|
31
|
+
const safeName = name => name.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
32
|
+
export function trustedUrl(value) {
|
|
33
|
+
const url = new URL(value);
|
|
34
|
+
if (url.protocol !== 'https:' || !(url.hostname === 'virustotal.com' || url.hostname.endsWith('.virustotal.com')) || url.username || url.password || (url.port && url.port !== '443')) throw new Error('VirusTotal returned an untrusted upload URL');
|
|
35
|
+
return url.href;
|
|
36
|
+
}
|
|
37
|
+
export async function* multipart(path, boundary) {
|
|
38
|
+
yield Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${safeName(basename(path))}"\r\nContent-Type: application/octet-stream\r\n\r\n`);
|
|
39
|
+
yield* createReadStream(path);
|
|
40
|
+
yield Buffer.from(`\r\n--${boundary}--\r\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class VirusTotal {
|
|
44
|
+
constructor({ key, fetchImpl = globalThis.fetch, sleep = delay, now = Date.now, intervalMs = 15500, requestMs = 120000, uploadMs = 900000, scanMs = 1200000, pollMs = 30000, retries = 3 } = {}) {
|
|
45
|
+
this.key = credentials(key); this.fetch = fetchImpl; this.sleep = sleep; this.now = now;
|
|
46
|
+
Object.assign(this, { intervalMs, requestMs, uploadMs, scanMs, pollMs, retries }); this.nextRequest = 0; this.queue = Promise.resolve();
|
|
47
|
+
}
|
|
48
|
+
async wait(ms, deadline) {
|
|
49
|
+
if (this.now() + ms >= deadline) throw new Error('VirusTotal analysis deadline exceeded; retry the same bytes later');
|
|
50
|
+
if (ms > 0) await this.sleep(ms);
|
|
51
|
+
}
|
|
52
|
+
async pace(deadline) {
|
|
53
|
+
// Reserve request starts serially, even when library callers scan concurrently.
|
|
54
|
+
const turn = this.queue.then(async () => {
|
|
55
|
+
await this.wait(Math.max(0, this.nextRequest - this.now()), deadline);
|
|
56
|
+
if (this.now() >= deadline) throw new Error('VirusTotal analysis deadline exceeded');
|
|
57
|
+
this.nextRequest = this.now() + this.intervalMs;
|
|
58
|
+
});
|
|
59
|
+
this.queue = turn.catch(() => {});
|
|
60
|
+
await turn;
|
|
61
|
+
}
|
|
62
|
+
async request(path, { method = 'GET', body, headers = {}, deadline = this.now() + this.scanMs, notFound = false, timeoutMs = this.requestMs } = {}) {
|
|
63
|
+
const url = trustedUrl(path.startsWith('https:') ? path : API + path);
|
|
64
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
65
|
+
await this.pace(deadline);
|
|
66
|
+
let response;
|
|
67
|
+
let payload;
|
|
68
|
+
try {
|
|
69
|
+
response = await this.fetch(url, { method, headers: { ...headers, 'x-apikey': this.key },
|
|
70
|
+
body: typeof body === 'function' ? body() : body, duplex: 'half', redirect: 'error',
|
|
71
|
+
signal: AbortSignal.timeout(Math.max(1, Math.min(timeoutMs, deadline - this.now()))) });
|
|
72
|
+
// Read a bounded JSON response, including its body in the HTTP timeout.
|
|
73
|
+
const chunks = []; let size = 0;
|
|
74
|
+
for await (const chunk of response.body || []) {
|
|
75
|
+
size += chunk.length;
|
|
76
|
+
if (size > 4 * 1024 * 1024) throw new Error('response too large');
|
|
77
|
+
chunks.push(Buffer.from(chunk));
|
|
78
|
+
}
|
|
79
|
+
payload = Buffer.concat(chunks).toString('utf8');
|
|
80
|
+
} catch {
|
|
81
|
+
if (attempt === this.retries) throw new Error('VirusTotal request failed or timed out; no verdict available');
|
|
82
|
+
await this.wait(1000 * 2 ** attempt, deadline); continue;
|
|
83
|
+
}
|
|
84
|
+
if (response.status === 404 && notFound) return null;
|
|
85
|
+
if (response.status === 429 || response.status >= 500) {
|
|
86
|
+
if (attempt === this.retries) throw new Error(`VirusTotal HTTP ${response.status}; retry budget exhausted`);
|
|
87
|
+
const retry = response.headers.get('retry-after');
|
|
88
|
+
const ms = retry && /^\d+(\.\d+)?$/.test(retry) ? Number(retry) * 1000 : retry ? Date.parse(retry) - this.now() : 1000 * 2 ** attempt;
|
|
89
|
+
await this.wait(Math.max(1000, Number.isFinite(ms) ? ms : 1000), deadline); continue;
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok) throw new Error(`VirusTotal HTTP ${response.status}; no verdict available`);
|
|
92
|
+
try { return JSON.parse(payload); } catch { throw new Error('VirusTotal returned invalid JSON'); }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async poll(id, deadline) {
|
|
96
|
+
if (typeof id !== 'string' || !id) throw new Error('VirusTotal did not return an analysis ID');
|
|
97
|
+
for (let count = 0; count < 40; count++) {
|
|
98
|
+
const result = await this.request(`analyses/${encodeURIComponent(id)}`, { deadline });
|
|
99
|
+
const status = result?.data?.attributes?.status;
|
|
100
|
+
if (status === 'completed') return result;
|
|
101
|
+
if (!['queued', 'in-progress'].includes(status)) throw new Error('VirusTotal returned an unknown analysis status');
|
|
102
|
+
await this.wait(this.pollMs, deadline);
|
|
103
|
+
}
|
|
104
|
+
throw new Error('VirusTotal analysis unfinished after 40 polls; retry later');
|
|
105
|
+
}
|
|
106
|
+
async file(path, expectedHash) {
|
|
107
|
+
const size = (await stat(path)).size;
|
|
108
|
+
if (size > UPLOAD_LIMIT) throw new Error(`VirusTotal 650 MB upload limit exceeded: ${basename(path)}`);
|
|
109
|
+
const sha256 = await hashFile(path);
|
|
110
|
+
if (expectedHash && sha256 !== expectedHash) throw new Error(`SHA-256 mismatch: ${basename(path)}`);
|
|
111
|
+
const deadline = this.now() + this.scanMs;
|
|
112
|
+
const cached = await this.request(`files/${sha256}`, { deadline, notFound: true });
|
|
113
|
+
let report;
|
|
114
|
+
const a = cached?.data?.attributes;
|
|
115
|
+
if (cached && (cached.data.id !== sha256 || a?.sha256 !== sha256)) throw new Error('VirusTotal file SHA-256 mismatch');
|
|
116
|
+
if (cached && this.fresh(a?.last_analysis_date)) {
|
|
117
|
+
report = this.evidence({ kind: 'file', name: basename(path), sha256, size }, a.last_analysis_stats, a.last_analysis_results, a.last_analysis_date, 'cached');
|
|
118
|
+
} else {
|
|
119
|
+
let analysis;
|
|
120
|
+
if (cached) analysis = await this.request(`files/${sha256}/analyse`, { method: 'POST', deadline });
|
|
121
|
+
else {
|
|
122
|
+
const upload = size > DIRECT_LIMIT ? (await this.request('files/upload_url', { deadline }))?.data : API + 'files';
|
|
123
|
+
if (typeof upload !== 'string') throw new Error('VirusTotal did not return an upload URL');
|
|
124
|
+
const boundary = `synabun-${sha256}`;
|
|
125
|
+
const head = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${safeName(basename(path))}"\r\nContent-Type: application/octet-stream\r\n\r\n`;
|
|
126
|
+
const tail = `\r\n--${boundary}--\r\n`;
|
|
127
|
+
analysis = await this.request(upload, { method: 'POST', deadline, timeoutMs: this.uploadMs, body: () => multipart(path, boundary),
|
|
128
|
+
headers: { 'content-type': `multipart/form-data; boundary=${boundary}`, 'content-length': String(Buffer.byteLength(head) + size + Buffer.byteLength(tail)) } });
|
|
129
|
+
}
|
|
130
|
+
const result = await this.poll(analysis?.data?.id, deadline);
|
|
131
|
+
if (result.meta?.file_info?.sha256 !== sha256) throw new Error('VirusTotal analysis SHA-256 mismatch');
|
|
132
|
+
const attrs = result.data.attributes;
|
|
133
|
+
report = this.evidence({ kind: 'file', name: basename(path), sha256, size }, attrs.stats, attrs.results, attrs.date, cached ? 'reanalysed' : 'uploaded', analysis.data.id);
|
|
134
|
+
}
|
|
135
|
+
if (await hashFile(path) !== sha256 || (await stat(path)).size !== size) throw new Error(`Artifact changed during scanning: ${basename(path)}`);
|
|
136
|
+
return report;
|
|
137
|
+
}
|
|
138
|
+
fresh(date) { return Number.isInteger(date) && date > 0 && date * 1000 <= this.now() + 300000 && this.now() - date * 1000 <= 7 * 86400000; }
|
|
139
|
+
async url(value) {
|
|
140
|
+
const url = new URL(value);
|
|
141
|
+
if (url.protocol !== 'https:' || url.username || url.password) throw new Error('Website reputation scan requires a public HTTPS URL');
|
|
142
|
+
const subject = url.href; const id = Buffer.from(subject).toString('base64url');
|
|
143
|
+
const deadline = this.now() + this.scanMs;
|
|
144
|
+
const submitted = await this.request('urls', { method: 'POST', body: new URLSearchParams({ url: subject }), deadline });
|
|
145
|
+
const result = await this.poll(submitted?.data?.id, deadline);
|
|
146
|
+
// Verify the URL object too; do not attribute an analysis to an unrelated URL.
|
|
147
|
+
const object = await this.request(`urls/${id}`, { deadline });
|
|
148
|
+
if (object?.data?.attributes?.url !== subject) throw new Error('VirusTotal URL mismatch');
|
|
149
|
+
if (result.meta?.url_info?.url !== subject || !/^[a-f0-9]{64}$/.test(object.data.id)) throw new Error('VirusTotal URL analysis identity mismatch');
|
|
150
|
+
const a = result.data.attributes;
|
|
151
|
+
return this.evidence({ kind: 'url', name: subject, sha256: createHash('sha256').update(subject).digest('hex'), url: subject }, a.stats, a.results, a.date, 'submitted', submitted.data.id, object.data.id);
|
|
152
|
+
}
|
|
153
|
+
evidence(subject, stats, results, date, source, analysisId = null, urlId = null) {
|
|
154
|
+
if (!stats || !results || typeof results !== 'object' || !this.fresh(date)) throw new Error('VirusTotal evidence missing or stale; no verdict available');
|
|
155
|
+
const counts = Object.fromEntries(Object.keys(stats).sort().map(key => [key, stats[key]]));
|
|
156
|
+
if (!Object.values(counts).every(n => Number.isInteger(n) && n >= 0)) throw new Error('VirusTotal invalid engine counts');
|
|
157
|
+
const engines = Object.keys(results).sort().map(key => ({ id: key, category: results[key].category ?? null, engine: results[key].engine_name ?? key, version: results[key].engine_version ?? null, update: results[key].engine_update ?? null, result: results[key].result ?? null }));
|
|
158
|
+
for (const [category, count] of Object.entries(counts)) if (engines.filter(engine => engine.category === category).length !== count) throw new Error('VirusTotal engine counts do not match evidence');
|
|
159
|
+
if (Object.values(counts).reduce((sum, count) => sum + count, 0) !== engines.length || engines.some(engine => !Object.hasOwn(counts, engine.category))) throw new Error('VirusTotal engine counts do not match evidence');
|
|
160
|
+
const supported = ['harmless', 'undetected', 'malicious', 'suspicious'].reduce((n, key) => n + (counts[key] || 0), 0);
|
|
161
|
+
const detections = (counts.malicious || 0) + (counts.suspicious || 0);
|
|
162
|
+
const status = supported < (subject.kind === 'file' ? 10 : 20) ? 'inconclusive' : detections ? 'detections' : 'no-detections';
|
|
163
|
+
const evidence = { ...subject, status, analysis_status: 'completed', analysis_date: new Date(date * 1000).toISOString(), source, analysis_id: analysisId,
|
|
164
|
+
supported_engines: supported, total_engines: engines.length, counts, engines,
|
|
165
|
+
permalink: `https://www.virustotal.com/gui/${subject.kind === 'file' ? 'file/' + subject.sha256 : 'url/' + urlId}` };
|
|
166
|
+
return { ...evidence, evidence_digest: evidenceDigest(evidence), review: null };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function applyReviews(items, reviews = [], now = Date.now()) {
|
|
171
|
+
if (!Array.isArray(reviews)) throw new Error('Review file must contain an array');
|
|
172
|
+
return items.map(item => {
|
|
173
|
+
if (item.status !== 'detections') return item;
|
|
174
|
+
const review = reviews.find(r => r.sha256 === item.sha256 && r.name === item.name && r.evidence_digest === item.evidence_digest);
|
|
175
|
+
if (!review) return item;
|
|
176
|
+
const at = Date.parse(review.reviewed_at), expires = Date.parse(review.expires_at);
|
|
177
|
+
if (![review.reviewer, review.reason, review.reference].every(v => typeof v === 'string' && v.trim().length >= 3) || !Number.isFinite(at) || !Number.isFinite(expires) || at > now || expires <= now || expires - at > 7 * 86400000) throw new Error('Matching VirusTotal review is incomplete or expired');
|
|
178
|
+
return { ...item, review: { reviewer: review.reviewer, reason: review.reason, reference: review.reference, reviewed_at: review.reviewed_at, expires_at: review.expires_at } };
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export const approved = items => items.length > 0 && items.every(item => item.status === 'no-detections' || (item.status === 'detections' && item.review));
|
|
182
|
+
const md = value => String(value).replace(/[|\r\n<>`\[\]\\]/g, ' ');
|
|
183
|
+
export function reports(items) {
|
|
184
|
+
const sorted = [...items].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
185
|
+
const report = { schema: 1, policy: { minimum_file_engines: 10, minimum_url_engines: 20, max_age_days: 7 }, approved: approved(sorted), items: sorted };
|
|
186
|
+
const markdown = ['## VirusTotal verification', '', 'Match the exact file and SHA-256. Results describe engine detections and coverage at the analysis date, not a guarantee of safety. Website results describe URL reputation, not a vulnerability audit.', '',
|
|
187
|
+
'| Package / URL | SHA-256 | Status | Supported / total engines | Analysis date | Report |', '| --- | --- | --- | --- | --- | --- |',
|
|
188
|
+
...sorted.map(i => `| ${md(i.name)} | ${i.sha256} | ${i.status}${i.review ? ' (owner reviewed)' : ''} | ${i.supported_engines ?? 0} / ${i.total_engines ?? 0} | ${md(i.analysis_date ?? 'unavailable')} | ${i.permalink ? `[VirusTotal](${i.permalink})` : 'unavailable'} |`), '',
|
|
189
|
+
...sorted.filter(i => i.review).map(i => `Review for ${md(i.name)} (${i.sha256}), evidence ${i.evidence_digest}: ${md(i.review.reviewer)}; ${md(i.review.reason)}; reference ${md(i.review.reference)}; expires ${md(i.review.expires_at)}.`),
|
|
190
|
+
...sorted.filter(i => i.error).map(i => `${md(i.name)}: ${md(i.error)}`), ''].join('\n');
|
|
191
|
+
return { json: JSON.stringify(report, null, 2) + '\n', markdown, report };
|
|
192
|
+
}
|