synabun 2.0.9 → 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 +115 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +68 -4
- 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/launcher.mjs +9 -3
- 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 +43 -5
- 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 +3680 -206
- package/neural-interface/i18n/pt-BR.json +3628 -157
- package/neural-interface/lib/account-usage.js +5 -3
- package/neural-interface/lib/app-controls-api.js +161 -0
- package/neural-interface/lib/assistant-api.js +148 -35
- 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 +122 -44
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +59 -0
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +97 -0
- package/neural-interface/lib/assistant-catalog.js +23 -9
- package/neural-interface/lib/assistant-dispatch.js +874 -156
- package/neural-interface/lib/assistant-envelope.js +22 -2
- 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 +183 -26
- 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-model-catalog.js +64 -14
- 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-settings-schema.js +109 -47
- 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 +164 -67
- package/neural-interface/lib/exec-loop-command.js +4 -1
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/launch-cache.js +293 -0
- package/neural-interface/lib/login-start.js +524 -0
- 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 +565 -50
- package/neural-interface/lib/native-loop-runtime.js +287 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +58 -0
- package/neural-interface/lib/opencode-history.js +49 -204
- 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 +310 -0
- package/neural-interface/lib/opencode-provider-view.js +81 -0
- package/neural-interface/lib/opencode-runtime-config.js +32 -15
- package/neural-interface/lib/opencode-serve-records.js +624 -0
- package/neural-interface/lib/opencode-v2-client.js +53 -131
- package/neural-interface/lib/opencode-v2-ws-requests.js +35 -195
- package/neural-interface/lib/opencode-worker-activity.js +229 -0
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +796 -0
- package/neural-interface/lib/opencode2-proxy.js +76 -0
- package/neural-interface/lib/opencode2-translate.js +1057 -0
- 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/process-identity.js +361 -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 +18 -10
- package/neural-interface/lib/session-title-generator.js +58 -18
- 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/style-guide/api.js +4 -3
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/untrusted-file-headers.js +27 -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 +1354 -50
- package/neural-interface/package.json +6 -2
- package/neural-interface/public/index.html +5 -3
- 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 +76 -16
- 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 +205 -19
- package/neural-interface/public/shared/assistant/asst-api.js +11 -3
- 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 +241 -112
- package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
- 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-hosts.js +49 -2
- 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 +84 -12
- package/neural-interface/public/shared/assistant/asst-panel.js +303 -44
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +93 -58
- package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
- package/neural-interface/public/shared/assistant/asst-render.js +770 -100
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
- package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
- package/neural-interface/public/shared/assistant/asst-scenes.js +530 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
- 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 +25 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +102 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +165 -13
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +550 -0
- 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 +58 -150
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +62 -32
- 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 +15 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +201 -74
- 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/cp/cp-markdown.js +4 -57
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/data-cache.js +223 -0
- package/neural-interface/public/shared/desktop-bridge.js +11 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
- package/neural-interface/public/shared/desktop-titlebar.js +214 -28
- package/neural-interface/public/shared/desktop-update-view.js +35 -5
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/file-links.js +73 -8
- package/neural-interface/public/shared/html-shell.js +78 -86
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/launch-prefetch.js +59 -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-caps.js +3 -2
- 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 +9 -2
- 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-modelpicker.js +54 -17
- 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 +140 -254
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +97 -203
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -22
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +20 -13
- 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 +53 -82
- 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 +48 -40
- 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 +205 -62
- package/neural-interface/public/shared/opencode-providers.js +39 -0
- 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 +452 -0
- package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
- package/neural-interface/public/shared/settings/settings-app.js +243 -0
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +90 -0
- package/neural-interface/public/shared/settings/settings-ia.js +23 -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/sidepanel-session-status.js +49 -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 +2065 -2150
- 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-assistant.js +27 -5
- package/neural-interface/public/shared/ui-automation-studio.js +155 -152
- package/neural-interface/public/shared/ui-bookmarks.js +2 -1
- package/neural-interface/public/shared/ui-claude-panel.js +153 -13
- package/neural-interface/public/shared/ui-codex-panel.js +1 -0
- 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 +21 -9
- package/neural-interface/public/shared/ui-file-links.js +1 -0
- package/neural-interface/public/shared/ui-image-gallery.js +56 -52
- package/neural-interface/public/shared/ui-invite.js +18 -2
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-link.js +9 -9
- package/neural-interface/public/shared/ui-menubar.js +229 -40
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +9 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +2267 -953
- package/neural-interface/public/shared/ui-sessions.js +20 -7
- package/neural-interface/public/shared/ui-settings.js +679 -496
- package/neural-interface/public/shared/ui-sidebar.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +56 -5
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +42 -2781
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +22 -5
- package/neural-interface/public/shared/ui-terminal.js +159 -199
- 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 +14 -5
- 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 -61
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -499
- package/neural-interface/public/shared/ui-update.js +121 -16
- package/neural-interface/public/shared/ui-whiteboard.js +313 -59
- 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 +36 -2
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +72 -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/highlight/LICENSE +29 -0
- package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
- package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
- package/neural-interface/public/vendor/highlight/manifest.json +5 -0
- package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
- package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
- package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
- package/neural-interface/public/vendor/marked/manifest.json +4 -0
- package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
- package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
- package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
- package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
- package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -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 +116 -3
- package/neural-interface/server.js +4834 -2541
- 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 +5 -5
- 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,564 @@
|
|
|
1
|
+
// ── Installing library revisions into runtime folders ──
|
|
2
|
+
// An install puts one revision of a library skill (lib/skills/library.js) into one or more runtime folders as a
|
|
3
|
+
// native skill folder: Claude Code (<config dir>/skills or a project's .claude/skills), Codex ($CODEX_HOME/skills
|
|
4
|
+
// or a project's .agents/skills), OpenCode (<config>/opencode/skills or a project's .opencode/skills), and the
|
|
5
|
+
// shared ~/.agents/skills only when the owner turned it on. It is two steps:
|
|
6
|
+
//
|
|
7
|
+
// plan(…) what each target would receive (the export of the revision for that runtime, with its digest),
|
|
8
|
+
// where, which runtimes see that folder (and what else of that name they already see), what is there
|
|
9
|
+
// now (absent, SynaBun's, changed since, not SynaBun's), the checks (the target profile's lint, the
|
|
10
|
+
// scan of exactly those bytes, the review of an Assistant's revision), what each target needs
|
|
11
|
+
// confirmed, and how a running session picks the change up. The plan has a digest.
|
|
12
|
+
// install(…) takes the plan's digest: the plan is worked out again and must be the same, or nothing happens
|
|
13
|
+
// (409 plan_changed, with the new plan). Then each target on its own: the export is written into a
|
|
14
|
+
// staging folder beside the runtime's folder (never inside it), checked against its digest, what is
|
|
15
|
+
// at the destination is backed up and checked again, swapped out, the export swapped in, checked
|
|
16
|
+
// again, and only then the old copy goes. A target that fails is put back as it was; the others are
|
|
17
|
+
// not undone (there is no transaction across runtimes, and the answer never pretends there is).
|
|
18
|
+
//
|
|
19
|
+
// Every install is recorded (DATA_HOME/data/skills/installations.json, schema 2): the revision, the export and its
|
|
20
|
+
// digest, the destination and the runtimes it reaches, what it replaced (its backup), and its state: written
|
|
21
|
+
// (checked by digest), discoverable (the runtime's rules, read statically), eligible (enabled in the runtime's
|
|
22
|
+
// settings, when that can be read), discovered (only what a runtime itself answered), loaded (never claimed), and
|
|
23
|
+
// the refresh a running session needs. A rollback puts back exactly what the install replaced; an uninstall
|
|
24
|
+
// removes only what SynaBun installed and still holds those bytes (or what the owner confirmed as it is now).
|
|
25
|
+
|
|
26
|
+
import { effectiveInstallName, nameChecks } from './scan-context.js';
|
|
27
|
+
export { effectiveInstallName, nameChecks } from './scan-context.js';
|
|
28
|
+
import { randomBytes } from 'node:crypto';
|
|
29
|
+
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync } from 'node:fs';
|
|
30
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
31
|
+
import { PathConfineError, isInside, isPlainName, realPathOf } from '../path-confine.js';
|
|
32
|
+
import { makeConfinedDir, readConfinedFile, removeConfinedEntry, replaceFileAtomic, writeConfinedFile } from '../confined-fs.js';
|
|
33
|
+
import { readManifest } from './digest.js';
|
|
34
|
+
import { LibraryError, digestOf } from './library.js';
|
|
35
|
+
import { buildExport, readHeadOf, readSidecar } from './export.js';
|
|
36
|
+
import { scanArtifact, scanAllows, withExternalScans, scanEvidenceKey } from './scan.js';
|
|
37
|
+
import { bundleNameProblem } from './bundle.js';
|
|
38
|
+
import { PORTABLE_NAME_RE, TARGET_PROFILE, getProfile, lintForProfile } from '../../public/shared/skills/ss-profiles.js';
|
|
39
|
+
import { analyzeResources, argumentDiagnostics } from '../../public/shared/skills/ss-resources.js';
|
|
40
|
+
import { isEditableText } from '../../public/shared/skills/ss-doc.js';
|
|
41
|
+
|
|
42
|
+
export const INSTALL_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'agents']);
|
|
43
|
+
export const INSTALLATIONS_SCHEMA = 2;
|
|
44
|
+
const MAX_INSTALLATIONS = 2000;
|
|
45
|
+
|
|
46
|
+
export class DeployError extends PathConfineError {
|
|
47
|
+
constructor(status, message, code, extra = {}) {
|
|
48
|
+
super(status, message);
|
|
49
|
+
this.code = code;
|
|
50
|
+
Object.assign(this, extra);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// How a running session sees a change (checked 2026-10-23 in temporary homes: codex-cli 0.164.0's app-server lists
|
|
55
|
+
// a new or changed skill only when skills/list is asked with forceReload, which SynaBun's Codex panel does on every
|
|
56
|
+
// skills:changed; opencode 2.1.4's serve reads its skill folders again on every /api/skill, so SynaBun's OpenCode panel
|
|
57
|
+
// lists it at once. A Codex or OpenCode session open elsewhere, in a terminal, is not SynaBun's to refresh: it needs a
|
|
58
|
+
// new session. Claude Code watches the folders that existed when it started. A Claude Code skills folder is read by
|
|
59
|
+
// a terminal OpenCode only: SynaBun starts every OpenCode serve of its own, the panel's and an Assistant brain's, with
|
|
60
|
+
// OPENCODE_DISABLE_CLAUDE_CODE=1, so those never see it, `terminal-only`.)
|
|
61
|
+
const REFRESH = {
|
|
62
|
+
claude: (rootExisted) => (rootExisted
|
|
63
|
+
? { kind: 'automatic', message: 'Claude Code picks up the change in sessions that are already open.' }
|
|
64
|
+
: { kind: 'reload-skills', message: 'This skills folder did not exist before: run /reload-skills in Claude Code sessions that are already open (new sessions see it).' }),
|
|
65
|
+
codex: () => ({ kind: 'panel-reload', message: 'SynaBun\'s Codex panel reloads its skills now; a Codex open in a terminal needs a new session.' }),
|
|
66
|
+
opencode: (rootExisted, root) => (root?.family === 'claude'
|
|
67
|
+
? { kind: 'terminal-only', message: 'An OpenCode open in a terminal reads Claude Code\'s folder (new session); SynaBun\'s own OpenCode panel and an OpenCode Assistant do not: install for OpenCode for those.' }
|
|
68
|
+
: { kind: 'panel-reload', message: 'SynaBun\'s OpenCode panel lists it now; an OpenCode open in a terminal needs a new session.' }),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const RUNTIME_LABEL = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', agents: 'the shared agents folder' };
|
|
72
|
+
|
|
73
|
+
/** A target as a request may name it, checked. */
|
|
74
|
+
export function normalizeTarget(t) {
|
|
75
|
+
if (!t || typeof t !== 'object' || Array.isArray(t)) throw new DeployError(400, 'A target is { runtime, scope }.', 'invalid_target');
|
|
76
|
+
const runtime = t.runtime;
|
|
77
|
+
if (!INSTALL_TARGETS.includes(runtime)) throw new DeployError(400, `A target's runtime is one of ${INSTALL_TARGETS.join(', ')}.`, 'invalid_target');
|
|
78
|
+
const scope = t.scope === undefined ? 'user' : t.scope;
|
|
79
|
+
if (scope !== 'user' && scope !== 'project') throw new DeployError(400, 'A target\'s scope is user or project.', 'invalid_target');
|
|
80
|
+
if (runtime === 'agents' && scope !== 'user') throw new DeployError(400, 'The shared agents folder is a user folder; a project\'s .agents/skills is the codex target with scope project.', 'invalid_target');
|
|
81
|
+
const accountId = t.accountId === undefined || t.accountId === null || t.accountId === '' ? 'default' : t.accountId;
|
|
82
|
+
if (typeof accountId !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(accountId)) throw new DeployError(400, 'Not an account id.', 'invalid_target');
|
|
83
|
+
if (scope === 'project' && (typeof t.projectPath !== 'string' || !t.projectPath)) throw new DeployError(400, 'A project target names its project (projectPath).', 'invalid_target');
|
|
84
|
+
const projectPath = scope === 'project' ? resolve(t.projectPath) : null;
|
|
85
|
+
const key = scope === 'project' ? `${runtime}:project:${projectPath}` : `${runtime}:user:${accountId}`;
|
|
86
|
+
return { runtime, scope, accountId, projectPath, key };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write a folder into place by swapping it in: staged beside the runtime's folder, checked, then renamed over
|
|
91
|
+
* what is there (which is checked again first and kept until the new folder is in place and checked).
|
|
92
|
+
* @param {object} o
|
|
93
|
+
* @param {string} o.root the runtime's skills folder (made when it is missing)
|
|
94
|
+
* @param {string|null} o.anchor a project the root must really be inside
|
|
95
|
+
* @param {string} o.name the folder's name in the root
|
|
96
|
+
* @param {{ files, dirs }} o.folder
|
|
97
|
+
* @param {string} o.digest what the folder must read back as
|
|
98
|
+
* @param {string|null} o.expectedCurrentDigest what must be at the destination now (null: nothing)
|
|
99
|
+
* @returns {{ destination: string, leftover: string|null, rootCreated: boolean }}
|
|
100
|
+
*/
|
|
101
|
+
export function swapFolderIn({ root, anchor = null, name, folder, digest, expectedCurrentDigest, random = () => randomBytes(8).toString('hex') }) {
|
|
102
|
+
if (!isPlainName(name) || bundleNameProblem(name)) throw new DeployError(400, `"${name}" cannot be a skill folder's name.`, 'invalid_name');
|
|
103
|
+
let rootCreated = false;
|
|
104
|
+
try { lstatSync(root); } catch { rootCreated = true; }
|
|
105
|
+
// The runtime's folder: made when missing, and it must be where it says (in its project, when it has one).
|
|
106
|
+
const parent = dirname(root);
|
|
107
|
+
if (anchor) {
|
|
108
|
+
let realParent;
|
|
109
|
+
try { realParent = realPathOf(parent); } catch { throw new DeployError(403, 'The runtime\'s folder leads somewhere that cannot be resolved.', 'root_unsafe'); }
|
|
110
|
+
if (!isInside(realPathOf(anchor), realParent, { allowRoot: true })) throw new DeployError(403, 'The runtime\'s folder leads out of the project through a link.', 'root_unsafe');
|
|
111
|
+
}
|
|
112
|
+
mkdirSync(root, { recursive: true });
|
|
113
|
+
const realRoot = realPathOf(root);
|
|
114
|
+
if (anchor && !isInside(realPathOf(anchor), realRoot)) throw new DeployError(403, 'The runtime\'s folder leads out of the project through a link.', 'root_unsafe');
|
|
115
|
+
const realParent = dirname(realRoot);
|
|
116
|
+
const destination = join(realRoot, name);
|
|
117
|
+
// Staging beside the folder (on the same disk when it can be), never inside it: a runtime that scans its folder
|
|
118
|
+
// deep would otherwise see the staged copy for a moment.
|
|
119
|
+
let stageHome = realParent;
|
|
120
|
+
try { if (lstatSync(realParent).dev !== lstatSync(realRoot).dev) stageHome = realRoot; } catch { stageHome = realRoot; }
|
|
121
|
+
const tag = random();
|
|
122
|
+
const stage = join(stageHome, `.synabun-stage-${tag}`);
|
|
123
|
+
const staged = join(stage, name);
|
|
124
|
+
const old = join(stageHome, `.synabun-old-${tag}`);
|
|
125
|
+
mkdirSync(stage, { mode: 0o700 });
|
|
126
|
+
try {
|
|
127
|
+
makeConfinedDir(stage, staged);
|
|
128
|
+
for (const d of folder.dirs || []) makeConfinedDir(staged, join(staged, d));
|
|
129
|
+
// A file's own mode when the folder carries one (a backup put back by a rollback: a private 0600 file stays
|
|
130
|
+
// 0600); else 0755 / 0644 by its executable bit, as an install writes them.
|
|
131
|
+
for (const f of folder.files) {
|
|
132
|
+
const mode = Number.isInteger(f.mode) ? f.mode & 0o777 : (f.executable ? 0o755 : 0o644);
|
|
133
|
+
writeConfinedFile(staged, join(staged, f.path), f.bytes, { exclusive: true, mode });
|
|
134
|
+
if (Number.isInteger(f.mode)) chmodSync(join(staged, f.path), mode);
|
|
135
|
+
}
|
|
136
|
+
const written = readManifest(staged);
|
|
137
|
+
if (written.digest !== digest) throw new DeployError(500, 'The staged files do not match what was meant to be written. Nothing was changed.', 'stage_mismatch');
|
|
138
|
+
const now = readManifest(destination);
|
|
139
|
+
const nowDigest = now.kind === 'absent' ? null : now.digest;
|
|
140
|
+
if (now.kind !== 'absent' && !now.digest) throw new DeployError(409, `"${destination}" could not be read whole: nothing was replaced.`, 'destination_unreadable');
|
|
141
|
+
if (nowDigest !== (expectedCurrentDigest ?? null)) throw new DeployError(409, `"${destination}" changed since the plan was made: nothing was replaced. Plan again.`, 'destination_changed', { currentDigest: nowDigest });
|
|
142
|
+
let moved = false;
|
|
143
|
+
if (now.kind !== 'absent') { renameSync(destination, old); moved = true; }
|
|
144
|
+
try {
|
|
145
|
+
renameSync(staged, destination);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (moved) { try { renameSync(old, destination); } catch { /* the backup holds it */ } }
|
|
148
|
+
throw new DeployError(500, `The new folder could not be put in place (${err?.code || err?.message}). What was there is back.`, 'swap_failed');
|
|
149
|
+
}
|
|
150
|
+
const check = readManifest(destination);
|
|
151
|
+
if (check.digest !== digest) {
|
|
152
|
+
try { rmSync(destination, { recursive: true, force: true }); } catch {}
|
|
153
|
+
if (moved) { try { renameSync(old, destination); } catch {} }
|
|
154
|
+
throw new DeployError(500, 'The installed folder does not read back as it was written. What was there is back.', 'verify_failed');
|
|
155
|
+
}
|
|
156
|
+
// The folders' own modes last (deepest first, the folder itself at the end), once the folder reads back.
|
|
157
|
+
const dirModes = Object.entries(folder.dirModes || {}).sort((a, b) => (b[0] === '.' ? -1 : b[0].split('/').length) - (a[0] === '.' ? -1 : a[0].split('/').length));
|
|
158
|
+
for (const [rel, mode] of dirModes) { try { chmodSync(rel === '.' ? destination : join(destination, rel), mode & 0o777); } catch { /* keeps the mode it was made with */ } }
|
|
159
|
+
let leftover = null;
|
|
160
|
+
if (moved) { try { rmSync(old, { recursive: true, force: true }); } catch { leftover = old; } }
|
|
161
|
+
return { destination, leftover, rootCreated };
|
|
162
|
+
} finally {
|
|
163
|
+
try { rmSync(stage, { recursive: true, force: true }); } catch {}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {object} o
|
|
169
|
+
* @param {object} o.library lib/skills/library.js
|
|
170
|
+
* @param {object} o.roots lib/skills/roots.js resolver
|
|
171
|
+
* @param {object} o.ownership lib/skills/ownership.js installer (ledger, backups)
|
|
172
|
+
* @param {string} o.dataDir DATA_HOME/data/skills
|
|
173
|
+
* @param {() => object} [o.config] { sharedAgentsRoot }
|
|
174
|
+
* @param {(id: string) => Promise<object|null>} [o.catalogEntries] every catalog entry (for what else a runtime sees)
|
|
175
|
+
* @param {(o) => Promise<object|null>} [o.probe] ask a runtime itself whether it lists the skill
|
|
176
|
+
* @param {(o) => string} [o.eligibility] 'enabled' | 'disabled' | 'unknown' from the runtime's settings
|
|
177
|
+
* @param {(o) => { state: string, reason: object|null }} [o.eligibilityDetail] the same, with the setting that turns it off
|
|
178
|
+
* @param {(event) => void} [o.emit]
|
|
179
|
+
* @param {() => string[]} [o.shippedNames] the names of the skills SynaBun ships (what a scan checks names against)
|
|
180
|
+
* @param {(revisionId: string) => ({ installName: string|null }|null)} [o.approvalOf] the approval a revision has
|
|
181
|
+
* (lib/skills/reviews.js approvalOf): an agent's revision installs only under the name it was approved for
|
|
182
|
+
*/
|
|
183
|
+
export function createDeployer({ library, roots, ownership, dataDir, config = () => ({}), catalogEntries = async () => [], probe = null, eligibility = () => 'unknown', eligibilityDetail = null, emit = () => {}, now = () => new Date().toISOString(), newId = (p) => `${p}_${randomBytes(10).toString('hex')}`, shippedNames = () => [], approvalOf = null }) {
|
|
184
|
+
let externalScanner = async () => [];
|
|
185
|
+
function setExternalScanner(scan) { externalScanner = scan; }
|
|
186
|
+
const storePath = join(dataDir, 'installations.json');
|
|
187
|
+
|
|
188
|
+
function readStore() {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(readConfinedFile(dataDir, storePath, { maxBytes: 32 * 1024 * 1024, encoding: 'utf-8' }));
|
|
191
|
+
if (parsed?.schemaVersion === INSTALLATIONS_SCHEMA && Array.isArray(parsed.records)) return parsed;
|
|
192
|
+
} catch { /* none yet */ }
|
|
193
|
+
return { schemaVersion: INSTALLATIONS_SCHEMA, records: [] };
|
|
194
|
+
}
|
|
195
|
+
function writeStore(store) {
|
|
196
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
197
|
+
const records = store.records.length > MAX_INSTALLATIONS ? [...store.records.filter((r) => r.active), ...store.records.filter((r) => !r.active).slice(-(MAX_INSTALLATIONS - store.records.filter((r) => r.active).length))] : store.records;
|
|
198
|
+
replaceFileAtomic(storePath, `${JSON.stringify({ schemaVersion: INSTALLATIONS_SCHEMA, records }, null, 2)}\n`, { mode: 0o600 });
|
|
199
|
+
}
|
|
200
|
+
const sameDest = (a, b) => resolve(a) === resolve(b);
|
|
201
|
+
|
|
202
|
+
function listInstallations({ skillId = null, active = null } = {}) {
|
|
203
|
+
return readStore().records.filter((r) => (!skillId || r.skillId === skillId) && (active === null || r.active === active));
|
|
204
|
+
}
|
|
205
|
+
function getInstallation(id) {
|
|
206
|
+
const rec = readStore().records.find((r) => r.installationId === id);
|
|
207
|
+
if (!rec) throw new DeployError(404, 'There is no such installation.', 'installation_not_found');
|
|
208
|
+
return rec;
|
|
209
|
+
}
|
|
210
|
+
function activeAt(destination) {
|
|
211
|
+
return readStore().records.find((r) => r.active && sameDest(r.destination.path, destination)) || null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const installName = effectiveInstallName;
|
|
215
|
+
const shipped = () => { try { return shippedNames() || []; } catch { return []; } };
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The checks of one target: the profile of each runtime that reads that folder. An error blocks only for a
|
|
219
|
+
* runtime the folder is meant for (`primary`); a runtime that reads it only for compatibility (OpenCode in
|
|
220
|
+
* ~/.claude/skills) turns its errors into warnings that name it.
|
|
221
|
+
*/
|
|
222
|
+
function lintTarget(runtimes, folder, name, primary = runtimes) {
|
|
223
|
+
const { parsed } = readHeadOf(folder.files);
|
|
224
|
+
const sidecar = readSidecar(folder.files);
|
|
225
|
+
const diagnostics = [];
|
|
226
|
+
const profiles = [];
|
|
227
|
+
for (const runtime of runtimes) {
|
|
228
|
+
const profileId = TARGET_PROFILE[runtime];
|
|
229
|
+
if (!profileId || profiles.includes(profileId)) continue;
|
|
230
|
+
profiles.push(profileId);
|
|
231
|
+
const secondary = !primary.includes(runtime);
|
|
232
|
+
const own = [
|
|
233
|
+
...lintForProfile(profileId, { parsed, folderName: name, files: folder.files.map((f) => ({ path: f.path, size: f.bytes.length })), sidecar }).all,
|
|
234
|
+
...argumentDiagnostics(getProfile(profileId).runtime, { body: parsed.body, bodyLine: parsed.bodyLine, declared: [] }).map((d) => ({ ...d, profile: profileId })),
|
|
235
|
+
];
|
|
236
|
+
diagnostics.push(...own.map((d) => (secondary && d.severity === 'error' ? { ...d, severity: 'warning', message: `${RUNTIME_LABEL[runtime]}, which also reads this folder: ${d.message}` } : d)));
|
|
237
|
+
}
|
|
238
|
+
const textFiles = folder.files.map((f) => ({ path: f.path, size: f.bytes.length, text: isEditableText(f.bytes) ? f.bytes.toString('utf-8') : null }));
|
|
239
|
+
diagnostics.push(...analyzeResources({ files: textFiles, dirs: folder.dirs }).diagnostics);
|
|
240
|
+
// One problem said once, even when two profiles see it.
|
|
241
|
+
const seen = new Set();
|
|
242
|
+
const unique = diagnostics.filter((d) => { const k = `${d.ruleId}\0${d.file}\0${d.line}\0${d.message}`; if (seen.has(k)) return false; seen.add(k); return true; });
|
|
243
|
+
return { profiles, diagnostics: unique, errors: unique.filter((d) => d.severity === 'error').length, warnings: unique.filter((d) => d.severity === 'warning').length };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* What installing a revision into these targets would do.
|
|
248
|
+
* @param {{ revisionId: string, targets: object[], reviewId?: string }} o
|
|
249
|
+
*/
|
|
250
|
+
async function plan({ revisionId, targets }) {
|
|
251
|
+
if (!Array.isArray(targets) || !targets.length || targets.length > 12) throw new DeployError(400, 'Name 1 to 12 targets.', 'invalid_target');
|
|
252
|
+
const revision = library.getRevision(revisionId);
|
|
253
|
+
const skill = library.getSkill(revision.skillId);
|
|
254
|
+
const source = library.readRevisionFiles(revisionId);
|
|
255
|
+
const name = installName(skill, source.files);
|
|
256
|
+
const wanted = targets.map(normalizeTarget);
|
|
257
|
+
if (new Set(wanted.map((t) => t.key)).size !== wanted.length) throw new DeployError(400, 'A target is named twice.', 'invalid_target');
|
|
258
|
+
let entries = [];
|
|
259
|
+
try { entries = await catalogEntries(); } catch { entries = []; }
|
|
260
|
+
const agentMade = revision.author?.kind === 'assistant' || revision.author?.kind === 'agent';
|
|
261
|
+
const reviewNeeded = agentMade && skill.pointers.approved !== revisionId;
|
|
262
|
+
// An approval covers the name it was given under: one without a name (from before this check) or under another
|
|
263
|
+
// name is stale, and the revision goes back to review.
|
|
264
|
+
let approvalStale = null;
|
|
265
|
+
if (agentMade && !reviewNeeded && typeof approvalOf === 'function') {
|
|
266
|
+
let approval = null;
|
|
267
|
+
try { approval = approvalOf(revisionId); } catch { approval = null; }
|
|
268
|
+
if (!approval || approval.installName !== name) {
|
|
269
|
+
approvalStale = approval?.installName
|
|
270
|
+
? `It was approved to install as "${approval.installName}"; it would install as "${name}": review it again.`
|
|
271
|
+
: `Its approval does not say which name it covers (it installs as "${name}"): review it again.`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const names = nameChecks(skill, name, shipped());
|
|
275
|
+
const out = [];
|
|
276
|
+
for (const target of wanted) {
|
|
277
|
+
const blocked = [];
|
|
278
|
+
const needs = [];
|
|
279
|
+
const root = roots.installRoot(target);
|
|
280
|
+
if (!root) {
|
|
281
|
+
out.push({ targetKey: target.key, ...target, name, blocked: [{ code: 'no_root', message: target.scope === 'project' ? 'That project is not registered in SynaBun.' : `There is no ${RUNTIME_LABEL[target.runtime]} folder for that account.` }], needs, ready: false });
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (root.policy?.write === 'owner-opt-in') blocked.push({ code: 'shared_root_off', message: 'Installing into ~/.agents/skills is off: turn on the shared agents folder in Settings → Skills first.' });
|
|
285
|
+
else if (root.policy?.write !== 'owner') blocked.push({ code: 'root_read_only', message: root.policy?.reason || 'SynaBun does not write into this folder.' });
|
|
286
|
+
if (root.status !== 'ok' && root.status !== 'absent') blocked.push({ code: 'root_unusable', message: `The folder is ${root.status === 'not-a-folder' ? 'not a folder' : 'unreadable'}: ${root.path}` });
|
|
287
|
+
const runtimes = root.runtimes.length ? root.runtimes : [target.runtime];
|
|
288
|
+
const profileId = target.runtime === 'agents' ? 'portable-six-field' : TARGET_PROFILE[target.runtime];
|
|
289
|
+
// One export per folder: for a folder several runtimes read, the strictest invocation each needs is applied.
|
|
290
|
+
let exported = { files: source.files, dirs: source.dirs, digest: source.revision.contentDigest, transforms: [], diagnostics: [] };
|
|
291
|
+
for (const runtime of runtimes) {
|
|
292
|
+
const next = buildExport({ files: exported.files, dirs: exported.dirs }, TARGET_PROFILE[runtime]);
|
|
293
|
+
exported = { ...next, transforms: [...exported.transforms, ...next.transforms], diagnostics: [...exported.diagnostics, ...next.diagnostics] };
|
|
294
|
+
}
|
|
295
|
+
// Who the folder is for: the target's runtime, or every runtime of the shared folder.
|
|
296
|
+
const primary = target.runtime === 'agents' ? runtimes : [target.runtime];
|
|
297
|
+
const lint = lintTarget(runtimes, exported, name, primary);
|
|
298
|
+
const exportDiagnostics = exported.diagnostics;
|
|
299
|
+
if (lint.errors || exportDiagnostics.some((d) => d.severity === 'error')) blocked.push({ code: 'lint_errors', message: `${lint.errors + exportDiagnostics.filter((d) => d.severity === 'error').length} problem(s) keep it from working in ${runtimes.map((r) => RUNTIME_LABEL[r]).join(' and ')}.` });
|
|
300
|
+
const { parsed } = readHeadOf(exported.files);
|
|
301
|
+
const scan = withExternalScans(scanArtifact({ type: 'skill', name, files: exported.files, frontmatter: parsed.data, digest: exported.digest, knownNames: names.knownNames, reservedNames: names.reservedNames }), await externalScanner({ ...revision, contentDigest: exported.digest }, exported));
|
|
302
|
+
// What SynaBun ships was reviewed with the app it ships in: its findings are shown, not asked about again.
|
|
303
|
+
// Everything else (an import, a folder of the user's, an agent's revision) needs the findings confirmed.
|
|
304
|
+
const trustedBundled = revision.source?.kind === 'bundled' && !scan.external?.length && !['error', 'incomplete'].includes(scan.status);
|
|
305
|
+
if (scan.gate === 'blocked') blocked.push({ code: 'scan_blocked', message: scan.status === 'error' ? `The scan failed (${scan.error}): nothing is installed without one.` : 'The scan found something that blocks this install.' });
|
|
306
|
+
else if (scan.gate === 'review' && !trustedBundled) needs.push({ code: 'acknowledge_scan', digest: exported.digest, message: 'Review the scan\'s findings and confirm them for these exact files.' });
|
|
307
|
+
if (reviewNeeded) blocked.push({ code: 'review_required', message: 'The Assistant made this revision: approve it in the Review queue before it is installed.' });
|
|
308
|
+
else if (approvalStale) blocked.push({ code: 'approval_stale', message: approvalStale });
|
|
309
|
+
const destination = join(root.path, name);
|
|
310
|
+
const inspected = ownership.inspect({ destination, payload: { kind: 'dir', digest: exported.digest } });
|
|
311
|
+
const state = inspected.state;
|
|
312
|
+
const currentDigest = inspected.current.kind === 'absent' ? null : inspected.current.digest;
|
|
313
|
+
if (state === 'unreadable') blocked.push({ code: 'destination_unreadable', message: `"${destination}" cannot be read whole: move it away yourself first.` });
|
|
314
|
+
if (state === 'modified' || state === 'unowned') needs.push({ code: 'confirm_replace', currentDigest, message: `"${destination}" ${state === 'modified' ? 'was installed by SynaBun but has been changed since' : 'was not installed by SynaBun'}. Replace it? A backup is kept.` });
|
|
315
|
+
// What else these runtimes see under this name, elsewhere.
|
|
316
|
+
const overlap = entries.filter((e) => e.kind === 'skill' && (e.invocationName === name || e.name === name) && e.rootId !== root.rootId && e.runtimes.some((r) => runtimes.includes(r)))
|
|
317
|
+
.map((e) => {
|
|
318
|
+
// The page words it from `code`, `name`, `runtimes` and `path` (skillsStudio.publish.overlapOne / overlapMany);
|
|
319
|
+
// `why` is the English for a caller without the page's strings (the MCP tool, a log).
|
|
320
|
+
const seen = e.runtimes.filter((r) => runtimes.includes(r));
|
|
321
|
+
return { id: e.id, code: 'overlap', name, path: e.path, scope: e.scope, runtimes: seen, why: `${seen.map((r) => RUNTIME_LABEL[r]).join(' and ')} also ${seen.length > 1 ? 'see' : 'sees'} a "${name}" skill at ${e.path}.` };
|
|
322
|
+
});
|
|
323
|
+
const refresh = runtimes.map((r) => ({ runtime: r, ...(REFRESH[r] ? REFRESH[r](root.status === 'ok', root) : { kind: 'unknown', message: '' }) }));
|
|
324
|
+
// Whether each runtime's own settings let a skill of this name, at this place, through (read statically; the
|
|
325
|
+
// page offers the one-click fix for a runtime that turns it off). Not part of the plan's digest: it changes
|
|
326
|
+
// nothing of what is written.
|
|
327
|
+
const eligible = {};
|
|
328
|
+
for (const r of runtimes) {
|
|
329
|
+
try {
|
|
330
|
+
const d = typeof eligibilityDetail === 'function' ? eligibilityDetail({ runtime: r, name, skillMd: join(destination, 'SKILL.md'), accountId: target.accountId, projectPath: target.projectPath }) : { state: eligibility({ runtime: r, name, skillMd: join(destination, 'SKILL.md'), accountId: target.accountId, projectPath: target.projectPath }) || 'unknown', reason: null };
|
|
331
|
+
eligible[r] = { state: ['enabled', 'disabled', 'unknown'].includes(d?.state) ? d.state : 'unknown', reason: d?.reason || null };
|
|
332
|
+
} catch { eligible[r] = { state: 'unknown', reason: null }; }
|
|
333
|
+
}
|
|
334
|
+
out.push({
|
|
335
|
+
targetKey: target.key, ...target, name,
|
|
336
|
+
root: { rootId: root.rootId, path: root.path, status: root.status, scope: root.scope, sourceKind: root.sourceKind, policy: root.policy },
|
|
337
|
+
destination, affectedRuntimes: runtimes, overlap,
|
|
338
|
+
export: { profileId, profileVersion: getProfile(profileId)?.version || null, artifactDigest: exported.digest, sourceDigest: revision.contentDigest, transforms: exported.transforms },
|
|
339
|
+
lint: { profiles: lint.profiles, errors: lint.errors, warnings: lint.warnings, diagnostics: [...exportDiagnostics, ...lint.diagnostics].slice(0, 300) },
|
|
340
|
+
scan: { status: scan.status, gate: scan.gate, digest: scan.digest, engineVersion: scan.engineVersion, policy: scan.policy, evidenceKey: scanEvidenceKey(scan), external: scan.external || [], counts: scan.counts, findings: scan.findings.slice(0, 50), coverage: scan.coverage, disclosure: scan.disclosure, trustedBundled },
|
|
341
|
+
destinationState: { state, currentDigest, installedBySynabun: !!inspected.record, activeInstallationId: activeAt(destination)?.installationId || null },
|
|
342
|
+
refresh, eligibility: eligible, blocked, needs, ready: !blocked.length && !needs.length,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const subject = { revisionId, contentDigest: revision.contentDigest, targets: out.map((t) => [t.targetKey, t.destination || null, t.export?.artifactDigest || null, t.destinationState?.currentDigest ?? null, t.destinationState?.state || null, t.blocked.map((b) => b.code), t.scan?.evidenceKey || null, t.needs.map((n) => [n.code, n.digest || null])]) };
|
|
346
|
+
return {
|
|
347
|
+
skillId: skill.skillId, skillName: skill.name, revisionId, contentDigest: revision.contentDigest, author: revision.author, name,
|
|
348
|
+
targets: out, planDigest: digestOf(subject), createdAt: now(),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Install a planned revision. `decisions[targetKey]`: `{ expectedDestinationDigest, acknowledgedDigest }` (the
|
|
354
|
+
* person confirmed replacing exactly what is there, and the scan's findings for exactly these bytes).
|
|
355
|
+
*/
|
|
356
|
+
async function install({ revisionId, targets, planDigest, decisions = {}, principal = { kind: 'owner' }, reviewId = null, migration = null }) {
|
|
357
|
+
const fresh = await plan({ revisionId, targets });
|
|
358
|
+
if (typeof planDigest !== 'string' || planDigest !== fresh.planDigest) {
|
|
359
|
+
throw new DeployError(409, 'What would be installed changed since the plan was shown. Nothing was installed; look at the new plan.', 'plan_changed', { plan: fresh });
|
|
360
|
+
}
|
|
361
|
+
const results = [];
|
|
362
|
+
for (const t of fresh.targets) {
|
|
363
|
+
const decision = decisions && typeof decisions === 'object' ? (decisions[t.targetKey] || {}) : {};
|
|
364
|
+
if (t.blocked.length) { results.push({ targetKey: t.targetKey, status: 'skipped', reasons: t.blocked }); continue; }
|
|
365
|
+
const missing = t.needs.filter((n) => (n.code === 'confirm_replace' && decision.expectedDestinationDigest !== n.currentDigest)
|
|
366
|
+
|| (n.code === 'acknowledge_scan' && decision.acknowledgedDigest !== n.digest));
|
|
367
|
+
if (missing.length) { results.push({ targetKey: t.targetKey, status: 'needs-confirmation', reasons: missing }); continue; }
|
|
368
|
+
const scanOk = scanAllows({ digest: t.scan.digest, status: t.scan.status, gate: t.scan.gate }, { digest: t.export.artifactDigest, acknowledgedDigest: t.scan.trustedBundled ? t.export.artifactDigest : (decision.acknowledgedDigest || null) });
|
|
369
|
+
if (!scanOk.ok) { results.push({ targetKey: t.targetKey, status: 'skipped', reasons: [{ code: scanOk.code, message: scanOk.message }] }); continue; }
|
|
370
|
+
try {
|
|
371
|
+
const source = library.readRevisionFiles(revisionId);
|
|
372
|
+
let exported = { files: source.files, dirs: source.dirs };
|
|
373
|
+
for (const runtime of t.affectedRuntimes) exported = buildExport(exported, TARGET_PROFILE[runtime]);
|
|
374
|
+
if (exported.digest !== t.export.artifactDigest) throw new DeployError(409, 'The export changed while it was being installed.', 'export_changed');
|
|
375
|
+
let backupId = null;
|
|
376
|
+
const previous = activeAt(t.destination);
|
|
377
|
+
if (t.destinationState.state !== 'absent' && t.destinationState.state !== 'identical') {
|
|
378
|
+
backupId = ownership.backup({ destination: t.destination, target: t.runtime, name: t.name, reason: `install of ${revisionId} over ${t.destinationState.state}` });
|
|
379
|
+
}
|
|
380
|
+
let swap = { destination: t.destination, leftover: null, rootCreated: false };
|
|
381
|
+
if (t.destinationState.state !== 'identical') {
|
|
382
|
+
swap = swapFolderIn({ root: t.root.path, anchor: t.scope === 'project' ? t.projectPath : null, name: t.name, folder: exported, digest: t.export.artifactDigest, expectedCurrentDigest: t.destinationState.currentDigest });
|
|
383
|
+
}
|
|
384
|
+
// What the ledger said of this place before: a rollback puts that claim back (or none, when there was none).
|
|
385
|
+
const priorRecord = ownership.recordFor(t.destination);
|
|
386
|
+
ownership.recordWrite({ target: t.runtime, name: t.name, destination: t.destination, digest: t.export.artifactDigest, kind: 'dir', source: { kind: 'library', skillId: fresh.skillId, revisionId, digest: fresh.contentDigest } });
|
|
387
|
+
const installationId = newId('ins');
|
|
388
|
+
const record = {
|
|
389
|
+
schemaVersion: INSTALLATIONS_SCHEMA, installationId, skillId: fresh.skillId, sourceRevisionId: revisionId, sourceDigest: fresh.contentDigest,
|
|
390
|
+
export: { profileId: t.export.profileId, profileVersion: t.export.profileVersion, artifactDigest: t.export.artifactDigest, manifestDigest: t.export.artifactDigest, transforms: t.export.transforms },
|
|
391
|
+
target: { runtime: t.runtime, scope: t.scope, accountId: t.accountId, projectPath: t.projectPath },
|
|
392
|
+
destination: { rootId: t.root.rootId, root: t.root.path, path: t.destination, relativePath: t.name, method: 'copy', affectedRuntimes: t.affectedRuntimes },
|
|
393
|
+
ownership: {
|
|
394
|
+
managedBy: 'synabun', adopted: false, replaced: t.destinationState.state, expectedDestinationDigest: t.destinationState.currentDigest,
|
|
395
|
+
// 'identical': the bytes were there already and nothing was written; a rollback then changes no file.
|
|
396
|
+
wrote: t.destinationState.state !== 'identical',
|
|
397
|
+
priorRecord: priorRecord ? { target: priorRecord.target, name: priorRecord.name, kind: priorRecord.kind, digest: priorRecord.digest, source: priorRecord.source || null } : null,
|
|
398
|
+
},
|
|
399
|
+
previousInstallationId: previous?.installationId || null,
|
|
400
|
+
backup: backupId ? { backupId, manifestDigest: t.destinationState.currentDigest } : null,
|
|
401
|
+
migration, reviewId,
|
|
402
|
+
scanAcknowledgement: (decision.acknowledgedDigest === t.scan.digest || t.scan.trustedBundled) ? { digest: t.scan.digest, evidenceKey: t.scan.evidenceKey, engineVersion: t.scan.engineVersion, policy: t.scan.policy, at: now() } : null,
|
|
403
|
+
authorization: { principal: principal?.kind || 'owner', planDigest },
|
|
404
|
+
transaction: { state: 'committed', at: now(), leftover: swap.leftover },
|
|
405
|
+
state: { written: true, verifiedDigest: t.export.artifactDigest, discoverable: null, eligibility: 'unknown', discovered: null, loadedRevisionId: null, refresh: t.refresh, tested: false, checkedAt: now() },
|
|
406
|
+
active: true, createdAt: now(),
|
|
407
|
+
};
|
|
408
|
+
const store = readStore();
|
|
409
|
+
for (const r of store.records) if (r.active && sameDest(r.destination.path, t.destination)) { r.active = false; r.supersededBy = installationId; }
|
|
410
|
+
store.records.push(record);
|
|
411
|
+
writeStore(store);
|
|
412
|
+
results.push({ targetKey: t.targetKey, status: 'committed', installationId, backupId, destination: t.destination, refresh: t.refresh, unchanged: t.destinationState.state === 'identical', leftover: swap.leftover });
|
|
413
|
+
emit({ type: 'installation.changed', skillId: fresh.skillId, installationId, rootIds: [t.root.rootId], action: 'install' });
|
|
414
|
+
} catch (err) {
|
|
415
|
+
results.push({ targetKey: t.targetKey, status: 'failed', error: err?.message || String(err), code: err?.code || null });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const committed = results.filter((r) => r.status === 'committed').length;
|
|
419
|
+
return { ok: committed === results.length, partial: committed > 0 && committed < results.length, results, plan: fresh };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Put back what an installation replaced (or remove it, when nothing was there). An installation that found its
|
|
424
|
+
* exact bytes already there wrote nothing: its rollback changes no file, and gives the place back to whoever had
|
|
425
|
+
* it before (the earlier installation, the ledger's earlier record, or nobody: the user's own folder).
|
|
426
|
+
*/
|
|
427
|
+
function rollback(installationId, { expectedDestinationDigest = null, principal = { kind: 'owner' } } = {}) {
|
|
428
|
+
const inst = getInstallation(installationId);
|
|
429
|
+
if (!inst.active) throw new DeployError(409, 'This installation is no longer the one in place.', 'not_active');
|
|
430
|
+
const destination = inst.destination.path;
|
|
431
|
+
const current = readManifest(destination);
|
|
432
|
+
const currentDigest = current.kind === 'absent' ? null : current.digest;
|
|
433
|
+
if (current.kind !== 'absent' && !current.digest) throw new DeployError(409, `"${destination}" cannot be read whole: nothing was changed.`, 'destination_unreadable');
|
|
434
|
+
if (currentDigest !== inst.export.artifactDigest && expectedDestinationDigest !== currentDigest) {
|
|
435
|
+
throw new DeployError(409, `"${destination}" was changed since it was installed. To roll back anyway, confirm it as it is now: a backup is kept.`, 'rollback_conflict', { currentDigest });
|
|
436
|
+
}
|
|
437
|
+
let restoredTo = 'absent';
|
|
438
|
+
const wroteNothing = inst.ownership?.wrote === false || inst.ownership?.replaced === 'identical';
|
|
439
|
+
const safety = !wroteNothing && current.kind !== 'absent' ? ownership.backup({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `rollback of ${installationId}` }) : null;
|
|
440
|
+
if (wroteNothing) {
|
|
441
|
+
restoredTo = 'unchanged';
|
|
442
|
+
} else if (inst.backup?.backupId) {
|
|
443
|
+
const kept = ownership.readBackup(inst.backup.backupId);
|
|
444
|
+
if (kept.kind === 'dir' && !kept.links.length) {
|
|
445
|
+
swapFolderIn({ root: inst.destination.root, anchor: inst.target.scope === 'project' ? inst.target.projectPath : null, name: inst.destination.relativePath, folder: { files: kept.files, dirs: kept.dirs, dirModes: kept.dirModes }, digest: kept.meta.digest, expectedCurrentDigest: currentDigest });
|
|
446
|
+
} else {
|
|
447
|
+
if (current.kind !== 'absent') removeConfinedEntry(inst.destination.root, destination);
|
|
448
|
+
ownership.restore(inst.backup.backupId, { root: inst.destination.root });
|
|
449
|
+
}
|
|
450
|
+
restoredTo = 'previous';
|
|
451
|
+
} else if (current.kind !== 'absent') {
|
|
452
|
+
removeConfinedEntry(inst.destination.root, destination);
|
|
453
|
+
}
|
|
454
|
+
const store = readStore();
|
|
455
|
+
const rec = store.records.find((r) => r.installationId === installationId);
|
|
456
|
+
rec.active = false;
|
|
457
|
+
rec.rolledBackAt = now();
|
|
458
|
+
rec.rollback = { by: principal?.kind || 'owner', safetyBackupId: safety, restoredTo };
|
|
459
|
+
// What was there before is SynaBun's again only when it was an earlier SynaBun install of exactly those bytes.
|
|
460
|
+
const prior = inst.previousInstallationId ? store.records.find((r) => r.installationId === inst.previousInstallationId) : null;
|
|
461
|
+
const back = readManifest(destination);
|
|
462
|
+
const priorRecord = inst.ownership?.priorRecord || null;
|
|
463
|
+
if (prior && back.digest && back.digest === prior.export.artifactDigest) {
|
|
464
|
+
prior.active = true;
|
|
465
|
+
prior.supersededBy = null;
|
|
466
|
+
ownership.recordWrite({ target: prior.target.runtime, name: prior.destination.relativePath, destination, digest: back.digest, kind: 'dir', source: { kind: 'library', skillId: prior.skillId, revisionId: prior.sourceRevisionId, digest: prior.sourceDigest } });
|
|
467
|
+
} else if (priorRecord && back.digest && back.digest === priorRecord.digest) {
|
|
468
|
+
// An install SynaBun made another way (the ledger's record, no installation of it): its claim is back.
|
|
469
|
+
ownership.recordWrite({ target: priorRecord.target, name: priorRecord.name, destination, digest: priorRecord.digest, kind: priorRecord.kind || 'dir', source: priorRecord.source });
|
|
470
|
+
} else {
|
|
471
|
+
ownership.forget({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `rolled back ${installationId}` });
|
|
472
|
+
}
|
|
473
|
+
writeStore(store);
|
|
474
|
+
emit({ type: 'installation.changed', skillId: inst.skillId, installationId, rootIds: [inst.destination.rootId], action: 'rollback' });
|
|
475
|
+
return { ok: true, installationId, restoredTo, safetyBackupId: safety, reactivated: prior && back.digest === prior.export.artifactDigest ? prior.installationId : null };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Remove an installation's folder: SynaBun's own bytes, or what the owner confirmed as it is now. A backup is kept. */
|
|
479
|
+
function uninstall(installationId, { expectedDestinationDigest = null, principal = { kind: 'owner' } } = {}) {
|
|
480
|
+
const inst = getInstallation(installationId);
|
|
481
|
+
if (!inst.active) throw new DeployError(409, 'This installation is no longer the one in place.', 'not_active');
|
|
482
|
+
const destination = inst.destination.path;
|
|
483
|
+
const current = readManifest(destination);
|
|
484
|
+
const currentDigest = current.kind === 'absent' ? null : current.digest;
|
|
485
|
+
let backupId = null;
|
|
486
|
+
if (current.kind !== 'absent') {
|
|
487
|
+
if (!current.digest) throw new DeployError(409, `"${destination}" cannot be read whole: nothing was removed.`, 'destination_unreadable');
|
|
488
|
+
if (currentDigest !== inst.export.artifactDigest && expectedDestinationDigest !== currentDigest) {
|
|
489
|
+
throw new DeployError(409, `"${destination}" was changed since it was installed. To remove it anyway, confirm it as it is now: a backup is kept.`, 'uninstall_conflict', { currentDigest });
|
|
490
|
+
}
|
|
491
|
+
backupId = ownership.backup({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `uninstall of ${installationId}` });
|
|
492
|
+
const again = readManifest(destination);
|
|
493
|
+
if (again.digest !== currentDigest) throw new DeployError(409, 'That folder changed while it was being removed. Nothing was removed.', 'destination_changed');
|
|
494
|
+
removeConfinedEntry(inst.destination.root, destination);
|
|
495
|
+
}
|
|
496
|
+
ownership.forget({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `uninstalled ${installationId}` });
|
|
497
|
+
const store = readStore();
|
|
498
|
+
const rec = store.records.find((r) => r.installationId === installationId);
|
|
499
|
+
rec.active = false;
|
|
500
|
+
rec.removedAt = now();
|
|
501
|
+
rec.removal = { by: principal?.kind || 'owner', backupId };
|
|
502
|
+
writeStore(store);
|
|
503
|
+
emit({ type: 'installation.changed', skillId: inst.skillId, installationId, rootIds: [inst.destination.rootId], action: 'uninstall' });
|
|
504
|
+
return { ok: true, installationId, backupId, removed: current.kind !== 'absent' };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Check an installation: the bytes (by digest), what the runtime's rules say of the folder, its settings
|
|
509
|
+
* (eligibility), and — only when asked and a probe exists — whether the runtime itself lists it now.
|
|
510
|
+
*/
|
|
511
|
+
async function verify(installationId, { probeRuntime = false } = {}) {
|
|
512
|
+
const inst = getInstallation(installationId);
|
|
513
|
+
const destination = inst.destination.path;
|
|
514
|
+
const current = readManifest(destination);
|
|
515
|
+
const written = current.kind === 'absent' ? 'missing' : (current.digest === inst.export.artifactDigest ? 'verified' : 'changed');
|
|
516
|
+
const checks = [];
|
|
517
|
+
let discoverable = written === 'verified';
|
|
518
|
+
let live = null;
|
|
519
|
+
if (written === 'verified') {
|
|
520
|
+
// The folder must still be one the runtime reads: OPENCODE_CONFIG_DIR set after the install, or an account
|
|
521
|
+
// folder that moved, leaves the skill where nothing looks.
|
|
522
|
+
try { live = roots.listRoots().find((r) => resolve(r.path) === resolve(inst.destination.root)) || null; } catch { live = null; }
|
|
523
|
+
for (const runtime of inst.destination.affectedRuntimes) {
|
|
524
|
+
if (live && live.runtimes.includes(runtime)) continue;
|
|
525
|
+
discoverable = false;
|
|
526
|
+
checks.push({ runtime, ok: false, message: `${RUNTIME_LABEL[runtime] || runtime} does not read ${inst.destination.root} now${live?.policy?.reason ? `: ${live.policy.reason}` : '.'}` });
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const head = readConfinedFile(destination, join(destination, 'SKILL.md'), { maxBytes: 2 * 1024 * 1024 });
|
|
530
|
+
const files = [{ path: 'SKILL.md', bytes: head }];
|
|
531
|
+
const { parsed } = readHeadOf(files);
|
|
532
|
+
for (const runtime of inst.destination.affectedRuntimes) {
|
|
533
|
+
const lint = lintForProfile(TARGET_PROFILE[runtime], { parsed, folderName: basename(destination) });
|
|
534
|
+
if (lint.errors.length) { discoverable = false; checks.push({ runtime, ok: false, message: lint.errors[0].message }); }
|
|
535
|
+
else if (live && live.runtimes.includes(runtime)) checks.push({ runtime, ok: true, message: `${RUNTIME_LABEL[runtime]}'s rules find it at ${destination}.` });
|
|
536
|
+
}
|
|
537
|
+
} catch (err) { discoverable = false; checks.push({ ok: false, message: `SKILL.md cannot be read (${err?.message || err}).` }); }
|
|
538
|
+
}
|
|
539
|
+
const eligibilityByRuntime = {};
|
|
540
|
+
for (const runtime of inst.destination.affectedRuntimes) {
|
|
541
|
+
try { eligibilityByRuntime[runtime] = eligibility({ runtime, name: inst.destination.relativePath, path: destination, skillMd: join(destination, 'SKILL.md'), accountId: inst.target.accountId, projectPath: inst.target.projectPath }) || 'unknown'; } catch { eligibilityByRuntime[runtime] = 'unknown'; }
|
|
542
|
+
}
|
|
543
|
+
const discovered = {};
|
|
544
|
+
if (probeRuntime && typeof probe === 'function' && written === 'verified') {
|
|
545
|
+
for (const runtime of inst.destination.affectedRuntimes) {
|
|
546
|
+
// A Claude Code folder reaches a terminal OpenCode only: SynaBun's own serve (OPENCODE_DISABLE_CLAUDE_CODE=1)
|
|
547
|
+
// would answer "not listed" for a folder it never reads, so it is not asked (REFRESH `terminal-only`).
|
|
548
|
+
if (runtime === 'opencode' && live?.family === 'claude') { discovered[runtime] = { discovered: null, code: 'terminal-only', method: 'none', reason: 'SynaBun\'s OpenCode never reads Claude Code\'s folder; an OpenCode in a terminal does.' }; continue; }
|
|
549
|
+
try { discovered[runtime] = await probe({ runtime, name: inst.destination.relativePath, skillMd: join(destination, 'SKILL.md'), accountId: inst.target.accountId, projectPath: inst.target.projectPath }); } catch (err) { discovered[runtime] = { discovered: null, code: 'no-answer', error: err?.message || String(err) }; }
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const store = readStore();
|
|
553
|
+
const rec = store.records.find((r) => r.installationId === installationId);
|
|
554
|
+
rec.state = {
|
|
555
|
+
...rec.state, written: written === 'verified', writtenCheck: written, verifiedDigest: current.digest || null, discoverable, checks,
|
|
556
|
+
eligibility: Object.values(eligibilityByRuntime).includes('disabled') ? 'disabled' : (Object.values(eligibilityByRuntime).every((v) => v === 'enabled') ? 'enabled' : 'unknown'),
|
|
557
|
+
eligibilityByRuntime, discovered: Object.keys(discovered).length ? discovered : rec.state.discovered, loadedRevisionId: null, checkedAt: now(),
|
|
558
|
+
};
|
|
559
|
+
writeStore(store);
|
|
560
|
+
return rec;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return { setExternalScanner, plan, install, rollback, uninstall, verify, listInstallations, getInstallation, activeAt, paths: { storePath } };
|
|
564
|
+
}
|