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,823 @@
|
|
|
1
|
+
import { effectiveInstallName, nameChecks } from './scan-context.js';
|
|
2
|
+
import { scanArtifact, withExternalScans } from './scan.js';
|
|
3
|
+
import { frontmatterData } from './frontmatter.js';
|
|
4
|
+
// ── The managed skill library: complete folders, immutable revisions ──
|
|
5
|
+
// SynaBun's own copy of every skill it manages (owner decision, 2026-10-22): a skill is a whole folder, kept as
|
|
6
|
+
// immutable revisions under the data home, and installed into each runtime's folders from there
|
|
7
|
+
// (lib/skills/deploy.js). Runtime folders are destinations; nothing here is read by a runtime, and nothing here is
|
|
8
|
+
// inside a folder a runtime reads skills from.
|
|
9
|
+
//
|
|
10
|
+
// DATA_HOME/data/skills/library/
|
|
11
|
+
// blobs/<2 hex>/<64 hex> file bytes, by their SHA-256 (written once, checked on every read)
|
|
12
|
+
// revisions/<revisionId>.json one revision: the manifest of every file (path, bytes, digest, mode, encoding,
|
|
13
|
+
// BOM, line endings), its parent, where it came from and who made it. Written once,
|
|
14
|
+
// never changed.
|
|
15
|
+
// skills/<skillId>.json the skill: its name (a label), origin, the revisions it has, and its pointers
|
|
16
|
+
// (latest, draft, candidate, best, approved, upstream) with a log of every move
|
|
17
|
+
// drafts/<draftId>.json an editable workspace: a list of files by blob, over a base revision
|
|
18
|
+
// archive/<skillId>.<time>.json a skill the owner deleted from the library: its record and its draft as they
|
|
19
|
+
// were, kept (nothing lists them); its revisions and blobs stay where they are
|
|
20
|
+
//
|
|
21
|
+
// A revision's `contentDigest` is the digest of the folder (lib/skills/digest.js manifestDigest), the same one an
|
|
22
|
+
// install records and compares: a folder installed from a revision has the revision's digest unless an export
|
|
23
|
+
// changed it on purpose. Ids are opaque (`skl_`, `rev_`, `drf_` + random hex); names are labels. `approved` moves
|
|
24
|
+
// only through a review's approval and `upstream` only through the bundled-skill sync: no caller can point them at
|
|
25
|
+
// a revision directly. Source identity (where the bytes came from: a bundled skill of an app version, a folder, a
|
|
26
|
+
// repository at a requested ref and the commit it resolved to) is kept field by field and checked
|
|
27
|
+
// (normalizeSource), never folded into one string.
|
|
28
|
+
//
|
|
29
|
+
// Everything here is synchronous: the server's one process is the only writer, and a JavaScript turn is never
|
|
30
|
+
// interleaved with another's.
|
|
31
|
+
|
|
32
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
33
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, unlinkSync } from 'node:fs';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
import { PathConfineError } from '../path-confine.js';
|
|
36
|
+
import { readConfinedFile, replaceFileAtomic, writeConfinedFile } from '../confined-fs.js';
|
|
37
|
+
import { fileEntry, manifestDigest, sha256 } from './digest.js';
|
|
38
|
+
import { BUNDLE_LIMITS, bundleNameProblem, bundlePathProblem } from './bundle.js';
|
|
39
|
+
import { isEditableText } from '../../public/shared/skills/ss-doc.js';
|
|
40
|
+
|
|
41
|
+
export const LIBRARY_SCHEMA = 2;
|
|
42
|
+
export const POINTERS = Object.freeze(['latest', 'draft', 'candidate', 'best', 'approved', 'upstream']);
|
|
43
|
+
const PROTECTED_POINTERS = new Set(['approved', 'upstream']);
|
|
44
|
+
const MOVABLE_POINTERS = new Set(['candidate', 'best']);
|
|
45
|
+
export const SOURCE_KINDS = Object.freeze(['bundled', 'local', 'import', 'assistant', 'adopted', 'customize', 'restore', 'github', 'draft']);
|
|
46
|
+
export const AUTHOR_KINDS = Object.freeze(['owner', 'assistant', 'agent', 'system']);
|
|
47
|
+
const POINTER_LOG_MAX = 200;
|
|
48
|
+
const ID = { skill: /^skl_[0-9a-f]{20}$/, revision: /^rev_[0-9a-f]{20}$/, draft: /^drf_[0-9a-f]{20}$/ };
|
|
49
|
+
const COMMIT_RE = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
50
|
+
const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
51
|
+
|
|
52
|
+
export class LibraryError extends PathConfineError {
|
|
53
|
+
constructor(status, message, code, extra = {}) {
|
|
54
|
+
super(status, message);
|
|
55
|
+
this.code = code;
|
|
56
|
+
Object.assign(this, extra);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const isSkillId = (v) => typeof v === 'string' && ID.skill.test(v);
|
|
61
|
+
export const isRevisionId = (v) => typeof v === 'string' && ID.revision.test(v);
|
|
62
|
+
export const isDraftId = (v) => typeof v === 'string' && ID.draft.test(v);
|
|
63
|
+
|
|
64
|
+
const clipText = (v, n = 300) => (typeof v === 'string' ? v.slice(0, n) : null);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Where bytes came from, field by field. A requested ref (a branch, a tag) and the commit it resolved to are two
|
|
68
|
+
* fields: one never stands for the other. Throws 400 on a value that is not what its field holds.
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeSource(source = {}) {
|
|
71
|
+
const s = source && typeof source === 'object' && !Array.isArray(source) ? source : {};
|
|
72
|
+
const kind = typeof s.kind === 'string' ? s.kind : 'local';
|
|
73
|
+
if (!SOURCE_KINDS.includes(kind)) throw new LibraryError(400, `A source kind is one of ${SOURCE_KINDS.join(', ')}.`, 'invalid_source');
|
|
74
|
+
if (s.resolvedCommit !== undefined && s.resolvedCommit !== null && !(typeof s.resolvedCommit === 'string' && COMMIT_RE.test(s.resolvedCommit))) {
|
|
75
|
+
throw new LibraryError(400, 'resolvedCommit is a full commit id (40 or 64 hex characters), or nothing.', 'invalid_source');
|
|
76
|
+
}
|
|
77
|
+
if (s.digest !== undefined && s.digest !== null && !(typeof s.digest === 'string' && DIGEST_RE.test(s.digest))) {
|
|
78
|
+
throw new LibraryError(400, 'A source digest is sha256:<64 hex>.', 'invalid_source');
|
|
79
|
+
}
|
|
80
|
+
for (const key of ['uri', 'subpath', 'requestedRef', 'name', 'appVersion', 'path', 'rootId', 'artifactId', 'stagingId', 'reviewId']) {
|
|
81
|
+
if (s[key] !== undefined && s[key] !== null && typeof s[key] !== 'string') throw new LibraryError(400, `The source's ${key} is text.`, 'invalid_source');
|
|
82
|
+
}
|
|
83
|
+
const out = { kind };
|
|
84
|
+
for (const key of ['uri', 'subpath', 'requestedRef', 'resolvedCommit', 'digest', 'name', 'appVersion', 'path', 'rootId', 'artifactId', 'stagingId', 'reviewId']) {
|
|
85
|
+
if (typeof s[key] === 'string' && s[key]) out[key] = s[key].slice(0, 2000);
|
|
86
|
+
}
|
|
87
|
+
if (kind === 'github' && !out.uri) throw new LibraryError(400, 'A GitHub source names its repository (uri).', 'invalid_source');
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Who made something: a kind and what is known of them (never a credential). */
|
|
92
|
+
export function normalizeAuthor(author = {}) {
|
|
93
|
+
const a = author && typeof author === 'object' && !Array.isArray(author) ? author : {};
|
|
94
|
+
const kind = AUTHOR_KINDS.includes(a.kind) ? a.kind : 'owner';
|
|
95
|
+
const out = { kind };
|
|
96
|
+
for (const key of ['provider', 'modelProvider', 'model', 'runId', 'terminal', 'sessionId', 'principalId']) {
|
|
97
|
+
const v = clipText(a[key], 200);
|
|
98
|
+
if (v) out[key] = v;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What a file's bytes are, for a manifest: text or binary, BOM, and its line endings. */
|
|
104
|
+
export function describeBytes(bytes) {
|
|
105
|
+
const binary = !isEditableText(bytes);
|
|
106
|
+
if (binary) return { encoding: 'binary', bom: false, eol: 'none' };
|
|
107
|
+
const bom = bytes.length >= 3 && bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF;
|
|
108
|
+
let crlf = 0; let lf = 0; let cr = 0;
|
|
109
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
110
|
+
const b = bytes[i];
|
|
111
|
+
if (b === 13) { if (bytes[i + 1] === 10) { crlf++; i++; } else cr++; }
|
|
112
|
+
else if (b === 10) lf++;
|
|
113
|
+
}
|
|
114
|
+
const kinds = [crlf && 'crlf', lf && 'lf', cr && 'cr'].filter(Boolean);
|
|
115
|
+
return { encoding: 'utf-8', bom, eol: kinds.length === 0 ? 'none' : (kinds.length === 1 ? kinds[0] : 'mixed') };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check a set of files as one skill folder: every path writable everywhere, within the import limits, the head
|
|
120
|
+
* file there. Returns them sorted, with every folder on the way.
|
|
121
|
+
*/
|
|
122
|
+
export function checkFolder({ files, dirs = [] }, { headPath = 'SKILL.md', limits = BUNDLE_LIMITS } = {}) {
|
|
123
|
+
if (!Array.isArray(files) || !files.length) throw new LibraryError(400, 'A skill holds at least its SKILL.md.', 'invalid_folder');
|
|
124
|
+
const problems = [];
|
|
125
|
+
const seen = new Map();
|
|
126
|
+
let total = 0;
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const f of files) {
|
|
129
|
+
const path = f?.path;
|
|
130
|
+
const why = bundlePathProblem(path, limits);
|
|
131
|
+
if (why) { problems.push({ path: String(path), message: `This path cannot be written: ${why}.` }); continue; }
|
|
132
|
+
if (!Buffer.isBuffer(f.bytes)) { problems.push({ path, message: 'No bytes.' }); continue; }
|
|
133
|
+
if (f.bytes.length > limits.maxFileBytes) { problems.push({ path, message: `It is larger than ${Math.round(limits.maxFileBytes / (1024 * 1024))} MB.` }); continue; }
|
|
134
|
+
const key = path.normalize('NFC').toLowerCase();
|
|
135
|
+
if (seen.has(key)) { problems.push({ path, message: `"${path}" and "${seen.get(key)}" name the same file on macOS and Windows.` }); continue; }
|
|
136
|
+
seen.set(key, path);
|
|
137
|
+
total += f.bytes.length;
|
|
138
|
+
out.push({ path, bytes: f.bytes, executable: f.executable === true });
|
|
139
|
+
}
|
|
140
|
+
if (out.length > limits.maxEntries) problems.push({ path: null, message: `${out.length} files; the limit is ${limits.maxEntries}.` });
|
|
141
|
+
if (total > limits.maxTotalBytes) problems.push({ path: null, message: `${Math.round(total / (1024 * 1024))} MB; the limit is ${Math.round(limits.maxTotalBytes / (1024 * 1024))} MB.` });
|
|
142
|
+
const dirSet = new Set();
|
|
143
|
+
for (const d of dirs || []) {
|
|
144
|
+
const why = bundlePathProblem(d, limits);
|
|
145
|
+
if (why) problems.push({ path: String(d), message: `This folder cannot be made: ${why}.` });
|
|
146
|
+
else dirSet.add(d);
|
|
147
|
+
}
|
|
148
|
+
for (const f of out) { const parts = f.path.split('/'); for (let i = 1; i < parts.length; i++) dirSet.add(parts.slice(0, i).join('/')); }
|
|
149
|
+
for (const d of dirSet) if (seen.has(d.normalize('NFC').toLowerCase())) problems.push({ path: d, message: `"${d}" is both a file and a folder.` });
|
|
150
|
+
const head = out.find((f) => f.path === headPath);
|
|
151
|
+
if (!head) problems.push({ path: headPath, message: `There is no ${headPath}.` });
|
|
152
|
+
if (problems.length) {
|
|
153
|
+
throw new LibraryError(problems.some((p) => /limit is/.test(p.message)) ? 413 : 400,
|
|
154
|
+
problems.length === 1 ? `${problems[0].path ? `${problems[0].path}: ` : ''}${problems[0].message}` : `${problems.length} problems with these files.`,
|
|
155
|
+
'invalid_folder', { problems: problems.slice(0, 200) });
|
|
156
|
+
}
|
|
157
|
+
return { files: out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)), dirs: [...dirSet].sort() };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The digest of a folder's files and folders, as an install and an import compute it. */
|
|
161
|
+
export function folderDigest({ files, dirs = [] }) {
|
|
162
|
+
return manifestDigest([...dirs.map((d) => ({ path: d, type: 'dir' })), ...files.map((f) => fileEntry(f.path, f.bytes, { executable: f.executable }))]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Line diff (Myers, O(ND)), bounded ──
|
|
166
|
+
|
|
167
|
+
const DIFF_MAX_LINES = 20000;
|
|
168
|
+
const DIFF_MAX_D = 4000;
|
|
169
|
+
|
|
170
|
+
function myers(a, b) {
|
|
171
|
+
const n = a.length; const m = b.length; const max = n + m;
|
|
172
|
+
const v = new Map([[1, 0]]);
|
|
173
|
+
const trace = [];
|
|
174
|
+
for (let d = 0; d <= Math.min(max, DIFF_MAX_D); d++) {
|
|
175
|
+
trace.push(new Map(v));
|
|
176
|
+
for (let k = -d; k <= d; k += 2) {
|
|
177
|
+
let x = (k === -d || (k !== d && (v.get(k - 1) ?? -1) < (v.get(k + 1) ?? -1))) ? (v.get(k + 1) ?? 0) : (v.get(k - 1) ?? 0) + 1;
|
|
178
|
+
let y = x - k;
|
|
179
|
+
while (x < n && y < m && a[x] === b[y]) { x++; y++; }
|
|
180
|
+
v.set(k, x);
|
|
181
|
+
if (x >= n && y >= m) {
|
|
182
|
+
// Walk back to the edit script.
|
|
183
|
+
const ops = [];
|
|
184
|
+
let cx = n; let cy = m;
|
|
185
|
+
for (let dd = d; dd > 0; dd--) {
|
|
186
|
+
const pv = trace[dd];
|
|
187
|
+
const kk = cx - cy;
|
|
188
|
+
const prevK = (kk === -dd || (kk !== dd && (pv.get(kk - 1) ?? -1) < (pv.get(kk + 1) ?? -1))) ? kk + 1 : kk - 1;
|
|
189
|
+
const px = pv.get(prevK) ?? 0; const py = px - prevK;
|
|
190
|
+
while (cx > px && cy > py) { ops.push({ t: ' ', a: cx - 1, b: cy - 1 }); cx--; cy--; }
|
|
191
|
+
if (cx === px) ops.push({ t: '+', b: cy - 1 }); else ops.push({ t: '-', a: cx - 1 });
|
|
192
|
+
cx = px; cy = py;
|
|
193
|
+
}
|
|
194
|
+
while (cx > 0 && cy > 0) { ops.push({ t: ' ', a: cx - 1, b: cy - 1 }); cx--; cy--; }
|
|
195
|
+
return ops.reverse();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** A unified diff of two texts (3 lines of context), or null when they are too far apart or too long to compare. */
|
|
203
|
+
export function unifiedDiff(before, after, { context = 3 } = {}) {
|
|
204
|
+
const a = String(before).split('\n'); const b = String(after).split('\n');
|
|
205
|
+
if (a.length > DIFF_MAX_LINES || b.length > DIFF_MAX_LINES) return null;
|
|
206
|
+
const ops = myers(a, b);
|
|
207
|
+
if (!ops) return null;
|
|
208
|
+
// Changed lines in groups: two changes closer than twice the context share one hunk.
|
|
209
|
+
const changed = [];
|
|
210
|
+
ops.forEach((o, i) => { if (o.t !== ' ') changed.push(i); });
|
|
211
|
+
const groups = [];
|
|
212
|
+
for (const i of changed) {
|
|
213
|
+
const last = groups[groups.length - 1];
|
|
214
|
+
if (last && i - last.end <= context * 2 + 1) last.end = i;
|
|
215
|
+
else groups.push({ start: i, end: i });
|
|
216
|
+
}
|
|
217
|
+
return groups.map(({ start, end }) => {
|
|
218
|
+
const slice = ops.slice(Math.max(0, start - context), Math.min(ops.length, end + 1 + context));
|
|
219
|
+
const aLines = slice.filter((o) => o.t !== '+').length;
|
|
220
|
+
const bLines = slice.filter((o) => o.t !== '-').length;
|
|
221
|
+
const aFirst = slice.find((o) => o.a !== undefined)?.a;
|
|
222
|
+
const bFirst = slice.find((o) => o.b !== undefined)?.b;
|
|
223
|
+
const aStart = aLines ? aFirst + 1 : (aFirst ?? 0);
|
|
224
|
+
const bStart = bLines ? bFirst + 1 : (bFirst ?? 0);
|
|
225
|
+
const lines = slice.map((o) => (o.t === '+' ? `+${b[o.b]}` : o.t === '-' ? `-${a[o.a]}` : ` ${a[o.a]}`));
|
|
226
|
+
return { header: `@@ -${aStart},${aLines} +${bStart},${bLines} @@`, lines };
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── The store ──
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {object} o
|
|
234
|
+
* @param {string} o.dir DATA_HOME/data/skills/library
|
|
235
|
+
* @param {() => string} [o.now]
|
|
236
|
+
* @param {(prefix: string) => string} [o.newId]
|
|
237
|
+
*/
|
|
238
|
+
export function createSkillLibrary({ dir, now = () => new Date().toISOString(), newId = (prefix) => `${prefix}_${randomBytes(10).toString('hex')}` }) {
|
|
239
|
+
let shippedNames = () => listSkills().filter((s) => s.origin?.kind === 'bundled').map((s) => s.origin.name || s.name);
|
|
240
|
+
let externalEvidence = () => [];
|
|
241
|
+
function setExternalEvidence(read) { externalEvidence = read; }
|
|
242
|
+
const scanCache = new Map();
|
|
243
|
+
function setShippedNames(read) { shippedNames = read; scanCache.clear(); }
|
|
244
|
+
function scanSkill(skillId, fallback) {
|
|
245
|
+
const active = readSkill(skillId);
|
|
246
|
+
if (active) return active;
|
|
247
|
+
// Archived revisions remain readable by reviews and installations.
|
|
248
|
+
let files = [];
|
|
249
|
+
try { files = readdirSync(archiveDir).filter((n) => n.startsWith(`${skillId}.`) && n.endsWith('.json')).sort().reverse(); } catch { /* no archive */ }
|
|
250
|
+
for (const file of files) { const record = readJson(archiveDir, file); if (record?.skill?.skillId === skillId) return record.skill; }
|
|
251
|
+
return { name: fallback || 'unknown', origin: null };
|
|
252
|
+
}
|
|
253
|
+
function scanFolderEvidence(folder, skill, digest) {
|
|
254
|
+
const fm = frontmatterData(folder.files.find((f) => f.path === 'SKILL.md')?.bytes.toString('utf-8') || '').data;
|
|
255
|
+
let name;
|
|
256
|
+
try { name = effectiveInstallName(skill, folder.files); } catch { name = skill.name; }
|
|
257
|
+
const shipped = shippedNames();
|
|
258
|
+
const names = nameChecks(skill, name, shipped);
|
|
259
|
+
const key = JSON.stringify([digest, name, names]);
|
|
260
|
+
if (!scanCache.has(key)) {
|
|
261
|
+
if (scanCache.size >= 200) scanCache.clear();
|
|
262
|
+
scanCache.set(key, scanArtifact({ type: 'skill', name, files: folder.files, frontmatter: fm, digest, ...names }));
|
|
263
|
+
}
|
|
264
|
+
return withExternalScans(structuredClone(scanCache.get(key)), externalEvidence(digest));
|
|
265
|
+
}
|
|
266
|
+
const blobsDir = join(dir, 'blobs');
|
|
267
|
+
const revisionsDir = join(dir, 'revisions');
|
|
268
|
+
const skillsDir = join(dir, 'skills');
|
|
269
|
+
const draftsDir = join(dir, 'drafts');
|
|
270
|
+
const archiveDir = join(dir, 'archive');
|
|
271
|
+
|
|
272
|
+
function ensure() {
|
|
273
|
+
for (const d of [dir, blobsDir, revisionsDir, skillsDir, draftsDir]) mkdirSync(d, { recursive: true, mode: 0o700 });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function readJson(base, file) {
|
|
277
|
+
try { return JSON.parse(readConfinedFile(base, join(base, file), { maxBytes: 16 * 1024 * 1024, encoding: 'utf-8' })); } catch { return null; }
|
|
278
|
+
}
|
|
279
|
+
function writeJson(path, value) {
|
|
280
|
+
replaceFileAtomic(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Blobs ──
|
|
284
|
+
|
|
285
|
+
const blobPath = (hex) => join(blobsDir, hex.slice(0, 2), hex);
|
|
286
|
+
|
|
287
|
+
/** Store bytes by their hash (once). @returns 'sha256:<hex>' */
|
|
288
|
+
function putBlob(bytes) {
|
|
289
|
+
const digest = sha256(bytes);
|
|
290
|
+
const hex = digest.slice(7);
|
|
291
|
+
const path = blobPath(hex);
|
|
292
|
+
if (existsSync(path)) return digest;
|
|
293
|
+
mkdirSync(join(blobsDir, hex.slice(0, 2)), { recursive: true, mode: 0o700 });
|
|
294
|
+
const tmp = join(blobsDir, hex.slice(0, 2), `.${hex}.${randomBytes(6).toString('hex')}.tmp`);
|
|
295
|
+
writeConfinedFile(blobsDir, tmp, bytes, { exclusive: true, mode: 0o600 });
|
|
296
|
+
try { renameSync(tmp, path); } catch (err) { try { unlinkSync(tmp); } catch {} if (!existsSync(path)) throw err; }
|
|
297
|
+
return digest;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The bytes of a blob, checked against their hash (a changed or damaged blob is an error, never content). */
|
|
301
|
+
function getBlob(digest) {
|
|
302
|
+
if (typeof digest !== 'string' || !DIGEST_RE.test(digest)) throw new LibraryError(500, 'Not a blob id.', 'blob_invalid');
|
|
303
|
+
const hex = digest.slice(7);
|
|
304
|
+
let bytes;
|
|
305
|
+
try { bytes = readConfinedFile(blobsDir, blobPath(hex), { maxBytes: BUNDLE_LIMITS.maxFileBytes }); } catch {
|
|
306
|
+
throw new LibraryError(500, 'A file of this revision is missing from the library.', 'blob_missing', { digest });
|
|
307
|
+
}
|
|
308
|
+
if (sha256(bytes) !== digest) throw new LibraryError(500, 'A file of this revision was changed or damaged in the library.', 'blob_corrupt', { digest });
|
|
309
|
+
return bytes;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ── Skills ──
|
|
313
|
+
|
|
314
|
+
function readSkill(skillId) {
|
|
315
|
+
if (!isSkillId(skillId)) return null;
|
|
316
|
+
const rec = readJson(skillsDir, `${skillId}.json`);
|
|
317
|
+
return rec && rec.schemaVersion === LIBRARY_SCHEMA && rec.skillId === skillId ? rec : null;
|
|
318
|
+
}
|
|
319
|
+
function getSkill(skillId) {
|
|
320
|
+
const rec = readSkill(skillId);
|
|
321
|
+
if (!rec) throw new LibraryError(404, 'There is no such skill in the library.', 'skill_not_found');
|
|
322
|
+
return rec;
|
|
323
|
+
}
|
|
324
|
+
function saveSkill(rec) {
|
|
325
|
+
ensure();
|
|
326
|
+
rec.updatedAt = now();
|
|
327
|
+
writeJson(join(skillsDir, `${rec.skillId}.json`), rec);
|
|
328
|
+
return rec;
|
|
329
|
+
}
|
|
330
|
+
function listSkills() {
|
|
331
|
+
let names = [];
|
|
332
|
+
try { names = readdirSync(skillsDir).filter((n) => /^skl_[0-9a-f]{20}\.json$/.test(n)).sort(); } catch { names = []; }
|
|
333
|
+
return names.map((n) => readSkill(n.slice(0, -5))).filter(Boolean);
|
|
334
|
+
}
|
|
335
|
+
function findSkill(predicate) { return listSkills().find(predicate) || null; }
|
|
336
|
+
|
|
337
|
+
function newSkill({ name, origin = { kind: 'local' }, author }) {
|
|
338
|
+
// The name is a label, and the folder an install falls back to: a plain name, visible characters only.
|
|
339
|
+
const why = bundleNameProblem(String(name || ''));
|
|
340
|
+
if (why) throw new LibraryError(400, `"${String(name || '')}" cannot be a skill's name: ${why}.`, 'invalid_name');
|
|
341
|
+
ensure();
|
|
342
|
+
const skillId = newId('skl');
|
|
343
|
+
if (!isSkillId(skillId)) throw new LibraryError(500, 'Bad skill id.', 'bad_id');
|
|
344
|
+
const rec = {
|
|
345
|
+
schemaVersion: LIBRARY_SCHEMA, skillId, name: String(name || '').slice(0, 128), createdAt: now(), updatedAt: now(), etag: 1,
|
|
346
|
+
origin: { ...origin }, createdBy: normalizeAuthor(author), revisions: [],
|
|
347
|
+
pointers: Object.fromEntries(POINTERS.map((p) => [p, null])), log: [],
|
|
348
|
+
};
|
|
349
|
+
return saveSkill(rec);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function logPointer(rec, entry) {
|
|
353
|
+
rec.log.push({ at: now(), ...entry });
|
|
354
|
+
if (rec.log.length > POINTER_LOG_MAX) rec.log = rec.log.slice(-POINTER_LOG_MAX);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ── Revisions ──
|
|
358
|
+
|
|
359
|
+
function readRevision(revisionId) {
|
|
360
|
+
if (!isRevisionId(revisionId)) return null;
|
|
361
|
+
const rec = readJson(revisionsDir, `${revisionId}.json`);
|
|
362
|
+
if (!rec || rec.schemaVersion !== LIBRARY_SCHEMA || rec.revisionId !== revisionId) return null;
|
|
363
|
+
const folder = { files: rec.manifest.files.map((e) => ({ path: e.path, bytes: getBlob(e.blobId), executable: e.mode === '0755' })), dirs: rec.manifest.dirs || [] };
|
|
364
|
+
if (folderDigest(folder) !== rec.contentDigest) throw new LibraryError(500, 'This revision does not read back as it was written.', 'revision_corrupt');
|
|
365
|
+
rec.scan = scanFolderEvidence(folder, scanSkill(rec.skillId, rec.scan?.name), rec.contentDigest);
|
|
366
|
+
return rec;
|
|
367
|
+
}
|
|
368
|
+
function getRevision(revisionId) {
|
|
369
|
+
const rec = readRevision(revisionId);
|
|
370
|
+
if (!rec) throw new LibraryError(404, 'There is no such revision.', 'revision_not_found');
|
|
371
|
+
return rec;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* A new immutable revision of a skill from whole files. It becomes `latest` unless `asCandidate`: a proposal (an
|
|
376
|
+
* evaluation's optimised description) is labelled `candidate` and leaves `latest` (what the editor opens, what an
|
|
377
|
+
* install offers first) where it was, so nothing it proposes is applied by itself.
|
|
378
|
+
* @returns the revision record
|
|
379
|
+
*/
|
|
380
|
+
function putRevision(skillId, { files, dirs = [], parentRevisionId = null, source, author, profiles = [], invocationIntent = null, note = '', asCandidate = false }) {
|
|
381
|
+
const rec = getSkill(skillId);
|
|
382
|
+
const folder = checkFolder({ files, dirs });
|
|
383
|
+
if (parentRevisionId !== null && !rec.revisions.includes(parentRevisionId)) throw new LibraryError(400, 'The parent revision is not one of this skill\'s.', 'invalid_parent');
|
|
384
|
+
const entries = folder.files.map((f) => {
|
|
385
|
+
const digest = putBlob(f.bytes);
|
|
386
|
+
return { path: f.path, type: 'file', digest, bytes: f.bytes.length, mode: f.executable ? '0755' : '0644', ...describeBytes(f.bytes), blobId: digest };
|
|
387
|
+
});
|
|
388
|
+
const contentDigest = folderDigest(folder);
|
|
389
|
+
const revisionId = newId('rev');
|
|
390
|
+
if (!isRevisionId(revisionId)) throw new LibraryError(500, 'Bad revision id.', 'bad_id');
|
|
391
|
+
const revision = {
|
|
392
|
+
schemaVersion: LIBRARY_SCHEMA, skillId, revisionId, parentRevisionId, contentDigest, entrypoint: 'SKILL.md',
|
|
393
|
+
manifest: { digest: contentDigest, files: entries, dirs: folder.dirs },
|
|
394
|
+
scan: scanFolderEvidence(folder, rec, contentDigest),
|
|
395
|
+
source: normalizeSource(source), author: normalizeAuthor(author),
|
|
396
|
+
profiles: (Array.isArray(profiles) ? profiles : []).filter((p) => p && typeof p.id === 'string').map((p) => ({ id: p.id, version: String(p.version || '') })),
|
|
397
|
+
invocationIntent: invocationIntent && typeof invocationIntent === 'object' ? { user: invocationIntent.user !== false, model: invocationIntent.model !== false } : null,
|
|
398
|
+
note: String(note || '').slice(0, 2000), noteCode: source?.kind === 'bundled' && /^Shipped with SynaBun(?: [\d.]+)?\.$/.test(note) ? 'bundled' : null, createdAt: now(),
|
|
399
|
+
};
|
|
400
|
+
ensure();
|
|
401
|
+
writeConfinedFile(revisionsDir, join(revisionsDir, `${revisionId}.json`), `${JSON.stringify(revision, null, 2)}\n`, { exclusive: true, mode: 0o600 });
|
|
402
|
+
rec.revisions.push(revisionId);
|
|
403
|
+
const pointer = asCandidate ? 'candidate' : 'latest';
|
|
404
|
+
const from = rec.pointers[pointer];
|
|
405
|
+
rec.pointers[pointer] = revisionId;
|
|
406
|
+
rec.etag += 1;
|
|
407
|
+
logPointer(rec, { pointer, from, to: revisionId, by: revision.author.kind, reason: asCandidate ? 'proposed by an evaluation' : 'revision created' });
|
|
408
|
+
saveSkill(rec);
|
|
409
|
+
return revision;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function listRevisions(skillId) {
|
|
413
|
+
const rec = getSkill(skillId);
|
|
414
|
+
return rec.revisions.map((id) => {
|
|
415
|
+
try {
|
|
416
|
+
const revision = readRevision(id);
|
|
417
|
+
if (revision) return { ...revision, integrity: 'intact' };
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if (!['blob_missing', 'blob_corrupt', 'blob_invalid', 'revision_corrupt'].includes(err.code)) throw err;
|
|
420
|
+
const metadata = readJson(revisionsDir, `${id}.json`);
|
|
421
|
+
return { ...metadata, revisionId: id, scan: null, integrity: 'damaged', error: { code: err.code } };
|
|
422
|
+
}
|
|
423
|
+
return { revisionId: id, skillId, scan: null, integrity: 'damaged', error: { code: 'revision_not_found' } };
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** A revision's files, read back from their blobs and checked. */
|
|
428
|
+
function readRevisionFiles(revisionId) {
|
|
429
|
+
const rev = getRevision(revisionId);
|
|
430
|
+
const files = rev.manifest.files.map((e) => ({ path: e.path, bytes: getBlob(e.blobId), executable: e.mode === '0755' }));
|
|
431
|
+
const out = { files, dirs: rev.manifest.dirs || [] };
|
|
432
|
+
if (folderDigest(out) !== rev.contentDigest) throw new LibraryError(500, 'This revision does not read back as it was written.', 'revision_corrupt');
|
|
433
|
+
return { ...out, revision: rev };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function readRevisionFile(revisionId, path) {
|
|
437
|
+
const rev = getRevision(revisionId);
|
|
438
|
+
const entry = rev.manifest.files.find((e) => e.path === path);
|
|
439
|
+
if (!entry) throw new LibraryError(404, 'This revision has no such file.', 'file_not_found');
|
|
440
|
+
return { entry, bytes: getBlob(entry.blobId) };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ── Pointers ──
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Move one pointer. `candidate` and `best` move by request; `approved` only with `allowProtected: 'review'`
|
|
447
|
+
* (a review's approval) and `upstream` only with `allowProtected: 'bundled'`.
|
|
448
|
+
*/
|
|
449
|
+
function setPointer(skillId, pointer, revisionId, { expectedEtag = null, by = 'owner', reason = '', allowProtected = null } = {}) {
|
|
450
|
+
const rec = getSkill(skillId);
|
|
451
|
+
if (!POINTERS.includes(pointer)) throw new LibraryError(400, `A pointer is one of ${POINTERS.join(', ')}.`, 'invalid_pointer');
|
|
452
|
+
if (PROTECTED_POINTERS.has(pointer)) {
|
|
453
|
+
const allowed = (pointer === 'approved' && allowProtected === 'review') || (pointer === 'upstream' && allowProtected === 'bundled');
|
|
454
|
+
if (!allowed) throw new LibraryError(403, `"${pointer}" moves only through ${pointer === 'approved' ? 'a review\'s approval' : 'the bundled skills\' update'}.`, 'pointer_protected');
|
|
455
|
+
} else if (!MOVABLE_POINTERS.has(pointer) && allowProtected !== 'internal') {
|
|
456
|
+
throw new LibraryError(403, `"${pointer}" is kept by the library itself.`, 'pointer_protected');
|
|
457
|
+
}
|
|
458
|
+
if (expectedEtag !== null && expectedEtag !== rec.etag) throw new LibraryError(409, 'The skill changed since it was read.', 'skill_conflict', { currentEtag: rec.etag });
|
|
459
|
+
if (revisionId !== null && !(rec.revisions.includes(revisionId) || (pointer === 'draft' && isDraftId(revisionId)))) {
|
|
460
|
+
throw new LibraryError(400, 'That revision is not one of this skill\'s.', 'invalid_revision');
|
|
461
|
+
}
|
|
462
|
+
const from = rec.pointers[pointer];
|
|
463
|
+
if (from === revisionId) return rec;
|
|
464
|
+
rec.pointers[pointer] = revisionId;
|
|
465
|
+
rec.etag += 1;
|
|
466
|
+
logPointer(rec, { pointer, from, to: revisionId, by, reason: String(reason || '').slice(0, 300) });
|
|
467
|
+
return saveSkill(rec);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Every pointer of a skill that names this revision. */
|
|
471
|
+
function labelsOf(rec, revisionId) {
|
|
472
|
+
return POINTERS.filter((p) => rec.pointers[p] === revisionId);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ── Creating skills ──
|
|
476
|
+
|
|
477
|
+
/** A new skill from whole files: the skill, and its first revision as `latest`. */
|
|
478
|
+
function createSkill({ name, files, dirs = [], source, author, origin = null, profiles = [], invocationIntent = null, note = '' }) {
|
|
479
|
+
checkFolder({ files, dirs });
|
|
480
|
+
const rec = newSkill({ name, origin: origin || { kind: normalizeSource(source).kind }, author });
|
|
481
|
+
const revision = putRevision(rec.skillId, { files, dirs, source, author, profiles, invocationIntent, note });
|
|
482
|
+
return { skill: getSkill(rec.skillId), revision };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Taking a skill out ──
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Take a skill out of the library (the owner deleted it): its record and its draft are written whole into
|
|
489
|
+
* `archive/` first, then removed from `skills/` and `drafts/`, so nothing lists, opens or installs it any more. Its
|
|
490
|
+
* revisions and their blobs stay where they are: written once, shared by content, and still readable by an
|
|
491
|
+
* installation or a review that names them. Whether it may go (installed somewhere, shipped with SynaBun) is the
|
|
492
|
+
* service's to decide; this only moves it.
|
|
493
|
+
* @param {string} skillId
|
|
494
|
+
* @param {{ expectedEtag?: number|null, by?: string, reason?: string }} [o] the etag the owner read: another one is a 409
|
|
495
|
+
* @returns {{ skillId: string, archivedAt: string, archived: string }} `archived`: the archive file's name
|
|
496
|
+
*/
|
|
497
|
+
function archiveSkill(skillId, { expectedEtag = null, by = 'owner', reason = '' } = {}) {
|
|
498
|
+
const rec = getSkill(skillId);
|
|
499
|
+
if (expectedEtag !== null && expectedEtag !== rec.etag) throw new LibraryError(409, 'The skill changed since it was read.', 'skill_conflict', { currentEtag: rec.etag });
|
|
500
|
+
const draft = rec.pointers.draft ? readDraft(rec.pointers.draft) : null;
|
|
501
|
+
const archivedAt = now();
|
|
502
|
+
mkdirSync(archiveDir, { recursive: true, mode: 0o700 });
|
|
503
|
+
// One file per deletion (a name of its own: an earlier attempt that stopped half way is never written over).
|
|
504
|
+
const archived = `${skillId}.${archivedAt.replace(/[^0-9]/g, '')}.${randomBytes(4).toString('hex')}.json`;
|
|
505
|
+
const record = { schemaVersion: LIBRARY_SCHEMA, archivedAt, archivedBy: String(by || 'owner').slice(0, 40), reason: String(reason || '').slice(0, 300), skill: rec, draft };
|
|
506
|
+
writeConfinedFile(archiveDir, join(archiveDir, archived), `${JSON.stringify(record, null, 2)}\n`, { exclusive: true, mode: 0o600 });
|
|
507
|
+
unlinkSync(join(skillsDir, `${skillId}.json`));
|
|
508
|
+
if (draft) { try { unlinkSync(join(draftsDir, `${draft.draftId}.json`)); } catch { /* the skill is out already: a draft nothing points to */ } }
|
|
509
|
+
return { skillId, archivedAt, archived };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* The skills taken out of the library, newest first: `{ skillId, name, archivedAt, archivedBy, archived, revisions }`
|
|
514
|
+
* (`revisions`: how many of its versions still read back, the ones a Restore brings with it).
|
|
515
|
+
*/
|
|
516
|
+
function listArchived() {
|
|
517
|
+
let names = [];
|
|
518
|
+
try { names = readdirSync(archiveDir).filter((n) => /^skl_[0-9a-f]{20}\.[0-9]+\.[0-9a-f]{8}\.json$/.test(n)); } catch { names = []; }
|
|
519
|
+
return names.map((n) => {
|
|
520
|
+
const rec = readJson(archiveDir, n);
|
|
521
|
+
if (!rec?.skill?.skillId) return null;
|
|
522
|
+
const revisions = (Array.isArray(rec.skill.revisions) ? rec.skill.revisions : []).filter((id) => isRevisionId(id) && readRevision(id)).length;
|
|
523
|
+
return { skillId: rec.skill.skillId, name: rec.skill.name, archivedAt: rec.archivedAt, archivedBy: rec.archivedBy, archived: n, revisions };
|
|
524
|
+
}).filter(Boolean).sort((a, b) => String(b.archivedAt).localeCompare(String(a.archivedAt)));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Put an archived skill back as it was: its record and its draft, over the revisions and blobs that never left.
|
|
529
|
+
* 404 when nothing of it is archived (`archive_not_found`: a second Restore of the same deletion); refused while a
|
|
530
|
+
* skill of that id is in the library (409 `skill_exists`: never two records of one id). The newest archive file of
|
|
531
|
+
* the id is the one restored and then removed (an older one of the same id stays, readable).
|
|
532
|
+
* @returns {{ skillId: string, name: string, restoredAt: string, archivedAt: string }}
|
|
533
|
+
*/
|
|
534
|
+
function restoreSkill(skillId) {
|
|
535
|
+
if (!isSkillId(skillId)) throw new LibraryError(400, 'Not a skill id.', 'invalid_id');
|
|
536
|
+
const entry = listArchived().find((a) => a.skillId === skillId);
|
|
537
|
+
if (!entry) throw new LibraryError(404, 'Nothing of that skill is archived.', 'archive_not_found');
|
|
538
|
+
if (readSkill(skillId)) throw new LibraryError(409, 'A skill of that id is in the library already.', 'skill_exists');
|
|
539
|
+
const rec = readJson(archiveDir, entry.archived);
|
|
540
|
+
if (!rec?.skill?.skillId || rec.skill.skillId !== skillId) throw new LibraryError(500, 'The archive file does not read back.', 'archive_corrupt');
|
|
541
|
+
const skill = { ...rec.skill };
|
|
542
|
+
skill.log = [...(Array.isArray(skill.log) ? skill.log : []), { at: now(), by: 'owner', action: 'restored', note: `restored from the archive (deleted ${rec.archivedAt})` }].slice(-200);
|
|
543
|
+
skill.etag = (Number(skill.etag) || 0) + 1;
|
|
544
|
+
ensure();
|
|
545
|
+
if (rec.draft?.draftId && !readDraft(rec.draft.draftId)) writeJson(join(draftsDir, `${rec.draft.draftId}.json`), rec.draft);
|
|
546
|
+
else if (!rec.draft?.draftId) skill.pointers = { ...skill.pointers, draft: null };
|
|
547
|
+
saveSkill(skill);
|
|
548
|
+
try { unlinkSync(join(archiveDir, entry.archived)); } catch { /* the record is back: an archive file left behind is listed no more than once */ }
|
|
549
|
+
return { skillId, name: skill.name, restoredAt: now(), archivedAt: rec.archivedAt };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ── Drafts ──
|
|
553
|
+
|
|
554
|
+
function readDraft(draftId) {
|
|
555
|
+
if (!isDraftId(draftId)) return null;
|
|
556
|
+
const rec = readJson(draftsDir, `${draftId}.json`);
|
|
557
|
+
if (!rec || rec.schemaVersion !== LIBRARY_SCHEMA || rec.draftId !== draftId) return null;
|
|
558
|
+
const folder = { files: rec.files.map((f) => ({ path: f.path, bytes: getBlob(f.blobId), executable: !!f.executable })), dirs: rec.dirs };
|
|
559
|
+
rec.scan = scanFolderEvidence(folder, getSkill(rec.skillId), folderDigest(folder));
|
|
560
|
+
return rec;
|
|
561
|
+
}
|
|
562
|
+
function getDraft(draftId) {
|
|
563
|
+
const rec = readDraft(draftId);
|
|
564
|
+
if (!rec) throw new LibraryError(404, 'There is no such draft.', 'draft_not_found');
|
|
565
|
+
return rec;
|
|
566
|
+
}
|
|
567
|
+
const draftTag = (files, dirs) => sha256(JSON.stringify([files.map((f) => [f.path, f.blobId, !!f.executable]), dirs]));
|
|
568
|
+
function saveDraft(rec) {
|
|
569
|
+
ensure();
|
|
570
|
+
rec.updatedAt = now();
|
|
571
|
+
rec.etag = draftTag(rec.files, rec.dirs);
|
|
572
|
+
const folder = { files: rec.files.map((f) => ({ path: f.path, bytes: getBlob(f.blobId), executable: !!f.executable })), dirs: rec.dirs };
|
|
573
|
+
rec.scan = scanFolderEvidence(folder, getSkill(rec.skillId), folderDigest(folder));
|
|
574
|
+
writeJson(join(draftsDir, `${rec.draftId}.json`), rec);
|
|
575
|
+
return rec;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* An editable workspace. Over a skill (from its `latest`, or `baseRevisionId`), or a new skill with `name` and
|
|
580
|
+
* `files`. A new skill's record exists from here on, without revisions until the first snapshot.
|
|
581
|
+
*/
|
|
582
|
+
function createDraft({ skillId = null, baseRevisionId = null, name = '', files = null, dirs = [], author, origin = null }) {
|
|
583
|
+
let rec;
|
|
584
|
+
let base = null;
|
|
585
|
+
let entries = [];
|
|
586
|
+
let folders = [];
|
|
587
|
+
if (skillId) {
|
|
588
|
+
rec = getSkill(skillId);
|
|
589
|
+
base = baseRevisionId || rec.pointers.latest;
|
|
590
|
+
if (base) {
|
|
591
|
+
if (!rec.revisions.includes(base)) throw new LibraryError(400, 'That revision is not one of this skill\'s.', 'invalid_revision');
|
|
592
|
+
const rev = getRevision(base);
|
|
593
|
+
entries = rev.manifest.files.map((e) => ({ path: e.path, blobId: e.blobId, executable: e.mode === '0755' }));
|
|
594
|
+
folders = rev.manifest.dirs || [];
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
if (!Array.isArray(files)) throw new LibraryError(400, 'A new skill\'s draft starts from its files.', 'invalid_draft');
|
|
598
|
+
const folder = checkFolder({ files, dirs });
|
|
599
|
+
rec = newSkill({ name, origin: origin || { kind: 'draft' }, author });
|
|
600
|
+
entries = folder.files.map((f) => ({ path: f.path, blobId: putBlob(f.bytes), executable: f.executable }));
|
|
601
|
+
folders = folder.dirs;
|
|
602
|
+
}
|
|
603
|
+
const draftId = newId('drf');
|
|
604
|
+
if (!isDraftId(draftId)) throw new LibraryError(500, 'Bad draft id.', 'bad_id');
|
|
605
|
+
const draft = saveDraft({
|
|
606
|
+
schemaVersion: LIBRARY_SCHEMA, draftId, skillId: rec.skillId, baseRevisionId: base, files: entries, dirs: folders,
|
|
607
|
+
createdAt: now(), updatedAt: now(), createdBy: normalizeAuthor(author), etag: null,
|
|
608
|
+
});
|
|
609
|
+
setPointer(rec.skillId, 'draft', draftId, { by: normalizeAuthor(author).kind, reason: 'draft opened', allowProtected: 'internal' });
|
|
610
|
+
return draft;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Change a draft: `ops` is a list of { op: 'put', path, content | contentBase64, executable? }, { op: 'delete', path },
|
|
615
|
+
* { op: 'mkdir', path }, { op: 'rename', from, to }. `expectedEtag` must be the draft's: a stale one is refused (409).
|
|
616
|
+
* The whole result is checked as a folder before anything is kept.
|
|
617
|
+
*/
|
|
618
|
+
function patchDraft(draftId, { expectedEtag, ops }) {
|
|
619
|
+
const draft = getDraft(draftId);
|
|
620
|
+
if (typeof expectedEtag !== 'string' || expectedEtag !== draft.etag) throw new LibraryError(409, 'The draft changed since it was read.', 'draft_conflict', { currentEtag: draft.etag });
|
|
621
|
+
if (!Array.isArray(ops) || !ops.length || ops.length > 500) throw new LibraryError(400, 'A change is a list of 1 to 500 operations.', 'invalid_ops');
|
|
622
|
+
const files = new Map(draft.files.map((f) => [f.path, { ...f }]));
|
|
623
|
+
const dirs = new Set(draft.dirs);
|
|
624
|
+
const bytesOf = new Map();
|
|
625
|
+
for (const [i, op] of ops.entries()) {
|
|
626
|
+
const at = `Operation ${i + 1}`;
|
|
627
|
+
if (!op || typeof op !== 'object' || Array.isArray(op)) throw new LibraryError(400, `${at} is not an operation.`, 'invalid_ops');
|
|
628
|
+
if (op.op === 'put') {
|
|
629
|
+
const why = bundlePathProblem(op.path);
|
|
630
|
+
if (why) throw new LibraryError(400, `${at}: ${String(op.path)} cannot be written: ${why}.`, 'invalid_path');
|
|
631
|
+
let bytes;
|
|
632
|
+
if (typeof op.content === 'string' && op.contentBase64 === undefined) bytes = Buffer.from(op.content, 'utf-8');
|
|
633
|
+
else if (typeof op.contentBase64 === 'string' && op.content === undefined) {
|
|
634
|
+
bytes = Buffer.from(op.contentBase64, 'base64');
|
|
635
|
+
if (bytes.toString('base64') !== op.contentBase64) throw new LibraryError(400, `${at}: contentBase64 is not canonical base64.`, 'invalid_content');
|
|
636
|
+
} else throw new LibraryError(400, `${at}: give either content (text) or contentBase64.`, 'invalid_content');
|
|
637
|
+
if (bytes.length > BUNDLE_LIMITS.maxFileBytes) throw new LibraryError(413, `${at}: larger than ${Math.round(BUNDLE_LIMITS.maxFileBytes / (1024 * 1024))} MB.`, 'too_large');
|
|
638
|
+
const prior = files.get(op.path);
|
|
639
|
+
files.set(op.path, { path: op.path, blobId: null, executable: typeof op.executable === 'boolean' ? op.executable : !!prior?.executable });
|
|
640
|
+
bytesOf.set(op.path, bytes);
|
|
641
|
+
} else if (op.op === 'delete') {
|
|
642
|
+
if (op.path === 'SKILL.md') throw new LibraryError(400, 'SKILL.md is the skill: it is not deleted from a draft.', 'head_protected');
|
|
643
|
+
// Bytes written earlier in this same batch go with the file (or the folder) they were written to.
|
|
644
|
+
const inside = (p) => p.startsWith(`${op.path}/`);
|
|
645
|
+
if (files.has(op.path)) { files.delete(op.path); bytesOf.delete(op.path); }
|
|
646
|
+
else if (dirs.has(op.path) || [...files.keys()].some(inside)) {
|
|
647
|
+
for (const p of [...files.keys()]) if (inside(p)) { files.delete(p); bytesOf.delete(p); }
|
|
648
|
+
for (const d of [...dirs]) if (d === op.path || inside(d)) dirs.delete(d);
|
|
649
|
+
} else throw new LibraryError(404, `${at}: ${String(op.path)} is not in the draft.`, 'file_not_found');
|
|
650
|
+
} else if (op.op === 'mkdir') {
|
|
651
|
+
const why = bundlePathProblem(op.path);
|
|
652
|
+
if (why) throw new LibraryError(400, `${at}: ${String(op.path)} cannot be made: ${why}.`, 'invalid_path');
|
|
653
|
+
dirs.add(op.path);
|
|
654
|
+
} else if (op.op === 'rename') {
|
|
655
|
+
const why = bundlePathProblem(op.to);
|
|
656
|
+
if (why) throw new LibraryError(400, `${at}: ${String(op.to)} cannot be written: ${why}.`, 'invalid_path');
|
|
657
|
+
if (op.from === 'SKILL.md') throw new LibraryError(400, 'SKILL.md keeps its name.', 'head_protected');
|
|
658
|
+
const f = files.get(op.from);
|
|
659
|
+
if (!f) throw new LibraryError(404, `${at}: ${String(op.from)} is not in the draft.`, 'file_not_found');
|
|
660
|
+
if (files.has(op.to)) throw new LibraryError(409, `${at}: ${op.to} is there already.`, 'exists');
|
|
661
|
+
files.delete(op.from);
|
|
662
|
+
files.set(op.to, { ...f, path: op.to });
|
|
663
|
+
if (bytesOf.has(op.from)) { bytesOf.set(op.to, bytesOf.get(op.from)); bytesOf.delete(op.from); }
|
|
664
|
+
} else throw new LibraryError(400, `${at}: unknown operation ${JSON.stringify(op.op ?? null)}.`, 'invalid_ops');
|
|
665
|
+
}
|
|
666
|
+
// The result as a folder: the same checks as a revision, before a blob is written.
|
|
667
|
+
const check = [...files.values()].map((f) => ({ path: f.path, bytes: bytesOf.get(f.path) || Buffer.alloc(0), executable: f.executable }));
|
|
668
|
+
// Sizes of files that were not changed are the sizes of their blobs.
|
|
669
|
+
for (const c of check) if (!bytesOf.has(c.path)) c.bytes = getBlob(files.get(c.path).blobId);
|
|
670
|
+
checkFolder({ files: check, dirs: [...dirs] });
|
|
671
|
+
for (const [path, bytes] of bytesOf) files.get(path).blobId = putBlob(bytes);
|
|
672
|
+
draft.files = [...files.values()].sort((a, b) => (a.path < b.path ? -1 : 1));
|
|
673
|
+
const dirList = new Set(dirs);
|
|
674
|
+
for (const f of draft.files) { const p = f.path.split('/'); for (let i = 1; i < p.length; i++) dirList.add(p.slice(0, i).join('/')); }
|
|
675
|
+
draft.dirs = [...dirList].sort();
|
|
676
|
+
return saveDraft(draft);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** The files of a draft, read back from their blobs. */
|
|
680
|
+
function readDraftFiles(draftId) {
|
|
681
|
+
const draft = getDraft(draftId);
|
|
682
|
+
return { files: draft.files.map((f) => ({ path: f.path, bytes: getBlob(f.blobId), executable: !!f.executable })), dirs: draft.dirs, draft };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Freeze a draft as a revision (its parent: the draft's base). The draft goes on over the new revision. */
|
|
686
|
+
function snapshotDraft(draftId, { expectedEtag, source, author, profiles = [], invocationIntent = null, note = '' }) {
|
|
687
|
+
const draft = getDraft(draftId);
|
|
688
|
+
if (typeof expectedEtag !== 'string' || expectedEtag !== draft.etag) throw new LibraryError(409, 'The draft changed since it was read.', 'draft_conflict', { currentEtag: draft.etag });
|
|
689
|
+
const { files, dirs } = readDraftFiles(draftId);
|
|
690
|
+
const rec = getSkill(draft.skillId);
|
|
691
|
+
// A snapshot of exactly the base's bytes is that revision: nothing new is made.
|
|
692
|
+
if (draft.baseRevisionId) {
|
|
693
|
+
const base = getRevision(draft.baseRevisionId);
|
|
694
|
+
if (base.contentDigest === folderDigest({ files, dirs })) return { revision: base, unchanged: true };
|
|
695
|
+
}
|
|
696
|
+
const revision = putRevision(rec.skillId, { files, dirs, parentRevisionId: draft.baseRevisionId, source: source || { kind: 'draft' }, author, profiles, invocationIntent, note });
|
|
697
|
+
const again = getDraft(draftId);
|
|
698
|
+
again.baseRevisionId = revision.revisionId;
|
|
699
|
+
saveDraft(again);
|
|
700
|
+
return { revision, unchanged: false };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// ── Comparing ──
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* File by file, what changed from one set of manifest entries to another, with a line diff of text files.
|
|
707
|
+
* `readLeft` / `readRight` give an entry's bytes (only asked for text files small enough to compare).
|
|
708
|
+
*/
|
|
709
|
+
function diffEntries(leftEntries, rightEntries, readLeft, readRight, { maxTextBytes = 512 * 1024 } = {}) {
|
|
710
|
+
const left = new Map(leftEntries.map((e) => [e.path, e]));
|
|
711
|
+
const right = new Map(rightEntries.map((e) => [e.path, e]));
|
|
712
|
+
const paths = [...new Set([...left.keys(), ...right.keys()])].sort();
|
|
713
|
+
const files = [];
|
|
714
|
+
for (const path of paths) {
|
|
715
|
+
const x = left.get(path); const y = right.get(path);
|
|
716
|
+
let status;
|
|
717
|
+
if (!x) status = 'added';
|
|
718
|
+
else if (!y) status = 'removed';
|
|
719
|
+
else if (x.digest !== y.digest) status = 'modified';
|
|
720
|
+
else if (x.mode !== y.mode) status = 'mode';
|
|
721
|
+
else status = 'unchanged';
|
|
722
|
+
const row = { path, status, before: x ? { bytes: x.bytes, mode: x.mode, digest: x.digest, encoding: x.encoding, eol: x.eol } : null, after: y ? { bytes: y.bytes, mode: y.mode, digest: y.digest, encoding: y.encoding, eol: y.eol } : null };
|
|
723
|
+
if (status === 'added' || status === 'removed' || status === 'modified') {
|
|
724
|
+
const textual = (!x || x.encoding === 'utf-8') && (!y || y.encoding === 'utf-8');
|
|
725
|
+
const small = (!x || x.bytes <= maxTextBytes) && (!y || y.bytes <= maxTextBytes);
|
|
726
|
+
if (textual && small) {
|
|
727
|
+
const before = x ? readLeft(x).toString('utf-8') : '';
|
|
728
|
+
const after = y ? readRight(y).toString('utf-8') : '';
|
|
729
|
+
row.hunks = unifiedDiff(before, after);
|
|
730
|
+
if (row.hunks === null) row.note = 'too different to compare line by line';
|
|
731
|
+
} else row.note = textual ? 'too large to compare line by line' : 'binary';
|
|
732
|
+
}
|
|
733
|
+
files.push(row);
|
|
734
|
+
}
|
|
735
|
+
return { files: files.filter((f) => f.status !== 'unchanged'), unchanged: files.filter((f) => f.status === 'unchanged').length };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** What changed from one revision to another, file by file, with a line diff of text files. */
|
|
739
|
+
function diffRevisions(fromId, toId, opts = {}) {
|
|
740
|
+
const a = getRevision(fromId); const b = getRevision(toId);
|
|
741
|
+
const read = (e) => getBlob(e.blobId);
|
|
742
|
+
const { files, unchanged } = diffEntries(a.manifest.files, b.manifest.files, read, read, opts);
|
|
743
|
+
return {
|
|
744
|
+
from: { revisionId: a.revisionId, contentDigest: a.contentDigest }, to: { revisionId: b.revisionId, contentDigest: b.contentDigest },
|
|
745
|
+
same: a.contentDigest === b.contentDigest, files, unchanged,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* What changed from a revision to a folder as it is now (a skill's installed copy, read whole by the caller):
|
|
751
|
+
* the same answer as diffRevisions, `to` naming the folder by its digest.
|
|
752
|
+
* @param {string} fromId @param {{ files: {path, bytes, executable}[], dirs?: string[] }} folder @param {{ path?: string }} [about]
|
|
753
|
+
*/
|
|
754
|
+
function diffRevisionFolder(fromId, folder, about = {}, opts = {}) {
|
|
755
|
+
const a = getRevision(fromId);
|
|
756
|
+
const checked = checkFolder({ files: folder.files, dirs: folder.dirs || [] });
|
|
757
|
+
const entries = checked.files.map((f) => ({ path: f.path, digest: sha256(f.bytes), bytes: f.bytes.length, mode: f.executable ? '0755' : '0644', ...describeBytes(f.bytes), data: f.bytes }));
|
|
758
|
+
const { files, unchanged } = diffEntries(a.manifest.files, entries, (e) => getBlob(e.blobId), (e) => e.data, opts);
|
|
759
|
+
const contentDigest = folderDigest(checked);
|
|
760
|
+
return {
|
|
761
|
+
from: { revisionId: a.revisionId, contentDigest: a.contentDigest }, to: { path: typeof about.path === 'string' ? about.path : null, contentDigest },
|
|
762
|
+
same: a.contentDigest === contentDigest, files, unchanged,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// ── Bundled skills ──
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Keep a library skill for every skill the app ships. A shipped folder whose bytes differ from the last one seen
|
|
770
|
+
* becomes a new revision under `upstream`; installs stay on the revision they have until the owner installs the
|
|
771
|
+
* new one (lib/skills/deploy.js), and a skill installed and changed locally stays the user's.
|
|
772
|
+
* @param {{ name: string, files, dirs }[]} shipped what PACKAGE_ROOT/skills holds now (read without following links)
|
|
773
|
+
* @returns {{ name, skillId, revisionId, changed: boolean, created: boolean }[]}
|
|
774
|
+
*/
|
|
775
|
+
function syncBundled(shipped, { appVersion = '' } = {}) {
|
|
776
|
+
const out = [];
|
|
777
|
+
for (const item of shipped) {
|
|
778
|
+
const digest = folderDigest(item);
|
|
779
|
+
let rec = findSkill((s) => s.origin?.kind === 'bundled' && s.origin?.name === item.name);
|
|
780
|
+
const source = { kind: 'bundled', name: item.name, appVersion: String(appVersion || ''), digest, uri: `synabun://bundled/${encodeURIComponent(item.name)}` };
|
|
781
|
+
if (!rec) {
|
|
782
|
+
const created = createSkill({ name: item.name, files: item.files, dirs: item.dirs, source, author: { kind: 'system' }, origin: { kind: 'bundled', name: item.name }, note: 'Shipped with SynaBun.' });
|
|
783
|
+
rec = created.skill;
|
|
784
|
+
setPointer(rec.skillId, 'upstream', created.revision.revisionId, { by: 'system', reason: 'shipped with SynaBun', allowProtected: 'bundled' });
|
|
785
|
+
setPointer(rec.skillId, 'approved', created.revision.revisionId, { by: 'system', reason: 'shipped with SynaBun', allowProtected: 'review' });
|
|
786
|
+
out.push({ name: item.name, skillId: rec.skillId, revisionId: created.revision.revisionId, changed: true, created: true });
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const current = rec.pointers.upstream ? readRevision(rec.pointers.upstream) : null;
|
|
790
|
+
if (current && current.contentDigest === digest) { out.push({ name: item.name, skillId: rec.skillId, revisionId: current.revisionId, changed: false, created: false }); continue; }
|
|
791
|
+
const revision = putRevision(rec.skillId, { files: item.files, dirs: item.dirs, parentRevisionId: rec.pointers.upstream, source, author: { kind: 'system' }, note: `Shipped with SynaBun${appVersion ? ` ${appVersion}` : ''}.` });
|
|
792
|
+
setPointer(rec.skillId, 'upstream', revision.revisionId, { by: 'system', reason: 'a new version shipped with SynaBun', allowProtected: 'bundled' });
|
|
793
|
+
out.push({ name: item.name, skillId: rec.skillId, revisionId: revision.revisionId, changed: true, created: false });
|
|
794
|
+
}
|
|
795
|
+
return out;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function stats() {
|
|
799
|
+
const skills = listSkills();
|
|
800
|
+
return { skills: skills.length, revisions: skills.reduce((n, s) => n + s.revisions.length, 0) };
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
setShippedNames, setExternalEvidence,
|
|
805
|
+
paths: { dir, blobsDir, revisionsDir, skillsDir, draftsDir, archiveDir },
|
|
806
|
+
putBlob, getBlob,
|
|
807
|
+
getSkill, readSkill, listSkills, findSkill, createSkill, labelsOf, archiveSkill, listArchived, restoreSkill,
|
|
808
|
+
getRevision, readRevision, putRevision, listRevisions, readRevisionFiles, readRevisionFile,
|
|
809
|
+
setPointer,
|
|
810
|
+
createDraft, getDraft, readDraft, patchDraft, readDraftFiles, snapshotDraft,
|
|
811
|
+
diffRevisions, diffRevisionFolder, syncBundled, stats,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/** True when nothing is at `path` (a link counts as something). */
|
|
816
|
+
export function absent(path) {
|
|
817
|
+
try { lstatSync(path); return false; } catch { return true; }
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/** The SHA-256 of a canonical JSON value (a plan, a review's subject). */
|
|
821
|
+
export function digestOf(value) {
|
|
822
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
823
|
+
}
|