synabun 2.1.0 → 2.2.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 +121 -0
- package/README.md +1 -1
- package/docs/whatsapp.md +11 -11
- package/lib/client-config-repair.js +13 -9
- package/lib/external-tools.js +6 -5
- package/lib/update-snapshot.js +77 -3
- package/lib/virustotal.mjs +12 -1
- package/mcp-server/dist/index.js +1 -1
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +2 -1
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +2 -1
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.js +1 -1
- package/mcp-server/dist/services/tool-catalog.js.map +1 -1
- package/mcp-server/dist/services/tool-list.d.ts +1 -1
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.js +1 -1
- package/mcp-server/dist/services/tool-runtime.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +8 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +10 -9
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/skill-tools.js +11 -8
- package/mcp-server/dist/tools/skill-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/index.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +2 -1
- package/mcp-server/src/services/server-instructions.ts +2 -1
- package/mcp-server/src/services/tool-catalog.ts +1 -1
- package/mcp-server/src/services/tool-list.ts +1 -1
- package/mcp-server/src/services/tool-runtime.ts +2 -2
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/tools/agents.ts +10 -9
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +11 -8
- package/neural-interface/i18n/en.json +501 -39
- package/neural-interface/i18n/pt-BR.json +501 -39
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +1 -0
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +76 -9
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +207 -49
- package/neural-interface/lib/assistant-envelope.js +153 -1
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +16 -11
- package/neural-interface/lib/assistant-plan-permissions.js +15 -0
- package/neural-interface/lib/assistant-router.js +237 -35
- package/neural-interface/lib/assistant-runtime.js +158 -12
- package/neural-interface/lib/assistant-task-prompt.js +10 -8
- package/neural-interface/lib/assistant-tools.js +2 -2
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +6 -1
- package/neural-interface/lib/browser-guard-cdp.js +5 -4
- package/neural-interface/lib/browser-network-guard.js +274 -82
- package/neural-interface/lib/browser-tool-policy.js +12 -7
- package/neural-interface/lib/claude-agent-bridge.js +7 -0
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +160 -31
- package/neural-interface/lib/cli-resolver.js +9 -2
- package/neural-interface/lib/cli-update-planner.js +25 -15
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +25 -6
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +9 -2
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/mcp-client-config.js +37 -6
- package/neural-interface/lib/mcp-external-servers.js +13 -4
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +147 -25
- package/neural-interface/lib/native-loop-providers.js +420 -21
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +11 -2
- package/neural-interface/lib/owner-authority.js +1 -1
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +32 -2
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +3 -0
- package/neural-interface/lib/rulesets/api.js +6 -5
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +89 -10
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +64 -1
- package/neural-interface/lib/skills/catalog.js +5 -1
- package/neural-interface/lib/skills/creation-brief.js +1 -1
- package/neural-interface/lib/skills/creation-sources.js +19 -1
- package/neural-interface/lib/skills/deploy.js +9 -4
- package/neural-interface/lib/skills/draft-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-trace.js +16 -0
- package/neural-interface/lib/skills/export.js +5 -2
- package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
- package/neural-interface/lib/skills/ownership.js +1 -1
- package/neural-interface/lib/skills/roots.js +10 -3
- package/neural-interface/lib/skills/runtime-checks.js +6 -3
- package/neural-interface/lib/skills/service.js +15 -7
- package/neural-interface/lib/skills/telemetry.js +6 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +77 -1
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-resume.js +13 -4
- package/neural-interface/lib/worker-sandbox.js +46 -16
- package/neural-interface/package-lock.json +18 -2
- package/neural-interface/package.json +2 -1
- package/neural-interface/public/onboarding.html +1 -1
- package/neural-interface/public/shared/agent-runtime-options.js +89 -11
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +4 -4
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
- package/neural-interface/public/shared/assistant/asst-api.js +1 -1
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-control.js +48 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
- package/neural-interface/public/shared/assistant/asst-panel.js +45 -8
- package/neural-interface/public/shared/assistant/asst-render.js +51 -12
- package/neural-interface/public/shared/assistant/asst-route.js +237 -57
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scenes.js +54 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
- package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
- package/neural-interface/public/shared/assistant/asst-state.js +76 -9
- package/neural-interface/public/shared/assistant/asst-styles.js +34 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
- package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
- package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +7 -4
- package/neural-interface/public/shared/cli-status.js +9 -8
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/context-menu-model.js +1 -0
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +1 -0
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +21 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +27 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
- package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-creation.js +6 -3
- package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
- package/neural-interface/public/shared/skills/ss-lint.js +9 -1
- package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
- package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
- package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
- package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
- package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
- package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +354 -34
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +2 -2
- package/neural-interface/public/shared/tool-list-view.js +2 -2
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +20 -12
- package/neural-interface/public/shared/ui-claude-panel.js +299 -77
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +8 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-menubar.js +2 -2
- package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
- package/neural-interface/public/shared/ui-navbar.js +45 -0
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +22 -12
- package/neural-interface/public/shared/ui-settings.js +90 -65
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
- package/neural-interface/public/shared/ui-terminal.js +146 -10
- package/neural-interface/public/shared/ui-tool-updates.js +3 -3
- package/neural-interface/public/shared/ui-tooltip.js +71 -46
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1 -1
- package/neural-interface/public/shared/ui-worker-end.js +1 -1
- package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/server.js +644 -426
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
- package/neural-interface/templates/rulesets/manifest.json +1 -1
- package/npm-shrinkwrap.json +18 -2
- package/package.json +2 -1
- package/skills/skill-forge/SKILL.md +4 -4
- package/skills/synabun/SKILL.md +1 -1
- package/skills/synabun/modules/schedule.md +3 -3
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -11,14 +11,15 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Invocation is kept as intent, apart from any one runtime's spelling of it: who may start the skill (the user
|
|
13
13
|
// by name, the model on its own), read from every dialect a folder carries (Claude Code fields, Codex's
|
|
14
|
-
// agents/openai.yaml, OpenCode's metadata) and compared with what a target
|
|
15
|
-
// so an install never lets a model start a skill its author kept
|
|
14
|
+
// agents/openai.yaml, OpenCode's metadata, Antigravity's disable-slash-command) and compared with what a target
|
|
15
|
+
// would read from the exported bytes, so an install never lets a model start a skill its author kept
|
|
16
|
+
// explicit-only (invocationPlan).
|
|
16
17
|
//
|
|
17
18
|
// No DOM, no I/O: the page and the server load it; YAML is parsed by the caller.
|
|
18
19
|
|
|
19
20
|
import { findHiddenChars } from './ss-unicode.js';
|
|
20
21
|
|
|
21
|
-
export const PROFILE_SET_VERSION = '2026-10-
|
|
22
|
+
export const PROFILE_SET_VERSION = '2026-10-30';
|
|
22
23
|
|
|
23
24
|
const RETRIEVED = '2026-10-21';
|
|
24
25
|
const SOURCES = Object.freeze({
|
|
@@ -29,6 +30,7 @@ const SOURCES = Object.freeze({
|
|
|
29
30
|
codexPrompts: { title: 'Codex custom prompts (deprecated)', url: 'https://learn.chatgpt.com/docs/custom-prompts', retrieved: RETRIEVED },
|
|
30
31
|
opencode: { title: 'OpenCode skills', url: 'https://opencode.ai/docs/skills/', retrieved: RETRIEVED },
|
|
31
32
|
opencodeCommands: { title: 'OpenCode commands', url: 'https://opencode.ai/docs/commands/', retrieved: RETRIEVED },
|
|
33
|
+
antigravity: { title: 'Antigravity CLI reference', url: 'https://antigravity.google/docs/cli/reference', retrieved: '2026-10-30' },
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
export const PORTABLE_FIELDS = Object.freeze(['name', 'description', 'license', 'compatibility', 'metadata', 'allowed-tools']);
|
|
@@ -39,6 +41,9 @@ export const CLAUDE_FIELDS = Object.freeze([
|
|
|
39
41
|
]);
|
|
40
42
|
const OPENCODE_FIELDS = Object.freeze(['name', 'description', 'license', 'compatibility', 'metadata', 'disable-model-invocation']);
|
|
41
43
|
const CODEX_FIELDS = Object.freeze(['name', 'description', 'metadata']);
|
|
44
|
+
const ANTIGRAVITY_FIELDS = Object.freeze(['name', 'description', 'disable-slash-command']);
|
|
45
|
+
/** The most of a SKILL.md body Antigravity reads (UTF-8 bytes after the frontmatter). */
|
|
46
|
+
export const ANTIGRAVITY_BODY_BYTES = 24 * 1024;
|
|
42
47
|
|
|
43
48
|
export const PORTABLE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
44
49
|
export const CODEX_SIDECAR = 'agents/openai.yaml';
|
|
@@ -121,6 +126,19 @@ export const PROFILES = Object.freeze({
|
|
|
121
126
|
],
|
|
122
127
|
reload: { file: 'new-session', newRoot: 'new-session', note: 'A running OpenCode may not see a new skill; start a new session (or restart the server) to be sure.' },
|
|
123
128
|
}),
|
|
129
|
+
'antigravity-native': Object.freeze({
|
|
130
|
+
id: 'antigravity-native', version: '2026-10-30', title: 'Antigravity', runtime: 'antigravity', status: 'current',
|
|
131
|
+
sources: [SOURCES.antigravity], verified: null,
|
|
132
|
+
fields: { recognized: ANTIGRAVITY_FIELDS, required: ['name', 'description'], strictTopLevel: false },
|
|
133
|
+
limits: { nameChars: 64, descriptionChars: 1024, bodyBytes: ANTIGRAVITY_BODY_BYTES },
|
|
134
|
+
invocation: { model: 'chosen by its description; no field keeps the model from starting it', user: '/name; `disable-slash-command: true` hides it' },
|
|
135
|
+
substitutions: null,
|
|
136
|
+
roots: [
|
|
137
|
+
{ scope: 'user', path: '~/.gemini/config/skills', note: 'where SynaBun installs' },
|
|
138
|
+
{ scope: 'project', path: '<project>/.agents/skills', note: 'the Codex project folder, which Antigravity reads too' },
|
|
139
|
+
],
|
|
140
|
+
reload: { file: 'automatic', newRoot: 'automatic', note: 'Antigravity reads its skill folders again by itself; there is no command to run.' },
|
|
141
|
+
}),
|
|
124
142
|
'codex-prompt': Object.freeze({
|
|
125
143
|
id: 'codex-prompt', version: '2026-10-21', title: 'Codex custom prompt (deprecated)', runtime: 'codex', status: 'legacy',
|
|
126
144
|
sources: [SOURCES.codexPrompts], verified: null,
|
|
@@ -144,7 +162,7 @@ export const PROFILES = Object.freeze({
|
|
|
144
162
|
});
|
|
145
163
|
|
|
146
164
|
/** The profile an install into a runtime is checked against. */
|
|
147
|
-
export const TARGET_PROFILE = Object.freeze({ claude: 'claude-code', codex: 'codex-native', opencode: 'opencode-native', agents: 'portable-six-field' });
|
|
165
|
+
export const TARGET_PROFILE = Object.freeze({ claude: 'claude-code', codex: 'codex-native', opencode: 'opencode-native', antigravity: 'antigravity-native', agents: 'portable-six-field' });
|
|
148
166
|
|
|
149
167
|
export function getProfile(id) {
|
|
150
168
|
return Object.prototype.hasOwnProperty.call(PROFILES, id) ? PROFILES[id] : null;
|
|
@@ -162,6 +180,11 @@ export function profileList() {
|
|
|
162
180
|
|
|
163
181
|
const has = (data, key) => !!data && Object.prototype.hasOwnProperty.call(data, key);
|
|
164
182
|
const isMap = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
183
|
+
const ENCODER = new TextEncoder();
|
|
184
|
+
/** A text's length in UTF-8 bytes (0 for anything else). */
|
|
185
|
+
const utf8Length = (text) => (typeof text === 'string' ? ENCODER.encode(text).length : 0);
|
|
186
|
+
/** "24 KB", "25.3 KB", "812 bytes". */
|
|
187
|
+
const kb = (bytes) => (bytes < 1024 ? `${bytes} bytes` : `${Math.round((bytes / 1024) * 10) / 10} KB`);
|
|
165
188
|
const kindOf = (value) => {
|
|
166
189
|
if (value === undefined) return 'absent';
|
|
167
190
|
if (value === null) return 'empty';
|
|
@@ -202,9 +225,11 @@ export function readInvocationIntent({ data, sidecar = null }) {
|
|
|
202
225
|
const meta = isMap(data?.metadata) ? data.metadata : null;
|
|
203
226
|
if (meta && has(meta, 'opencode/autoinvoke')) said.opencodeModel = openSwitch(meta['opencode/autoinvoke']);
|
|
204
227
|
if (meta && has(meta, 'opencode/slash')) said.opencodeSlash = openSwitch(meta['opencode/slash']);
|
|
228
|
+
// Antigravity's `disable-slash-command: true` keeps /name from the person, as Claude Code's user-invocable: false does.
|
|
229
|
+
if (has(data, 'disable-slash-command')) said.antigravityUser = claudeBoolean(data['disable-slash-command']) === true ? false : (claudeBoolean(data['disable-slash-command']) === false ? true : null);
|
|
205
230
|
const modelSays = [said.claudeModel, said.codexModel, said.opencodeModel].filter((v) => v === true || v === false);
|
|
206
231
|
const model = !modelSays.includes(false);
|
|
207
|
-
const user = said.claudeUser !== false;
|
|
232
|
+
const user = said.claudeUser !== false && said.antigravityUser !== false;
|
|
208
233
|
const conflicts = [];
|
|
209
234
|
if (modelSays.includes(false) && modelSays.includes(true)) conflicts.push('model');
|
|
210
235
|
return { user, model, slash: said.opencodeSlash ?? null, said, conflicts };
|
|
@@ -228,6 +253,10 @@ export function effectiveInvocation(profileId, { data, sidecar = null }) {
|
|
|
228
253
|
const claudeOff = claudeBoolean(data?.['disable-model-invocation']) === true;
|
|
229
254
|
return { model: !(openSwitch(meta?.['opencode/autoinvoke']) === false || claudeOff), user: true, slash: openSwitch(meta?.['opencode/slash']) === true };
|
|
230
255
|
}
|
|
256
|
+
if (runtime === 'antigravity') {
|
|
257
|
+
// Only a YAML true is taken as hiding /name (anything else is not counted on); nothing keeps its model from starting a skill.
|
|
258
|
+
return { model: true, user: data?.['disable-slash-command'] !== true };
|
|
259
|
+
}
|
|
231
260
|
return { model: true, user: true };
|
|
232
261
|
}
|
|
233
262
|
|
|
@@ -254,6 +283,7 @@ export function invocationPlan(profileId, { intent, data, sidecar = null }) {
|
|
|
254
283
|
}
|
|
255
284
|
if (!intent.user && now.user) {
|
|
256
285
|
if (runtime === 'claude') transforms.push({ file: 'SKILL.md', op: 'frontmatter', key: 'user-invocable', value: false, reason: 'Keeps /name hidden in Claude Code, as the skill\'s other settings say.' });
|
|
286
|
+
else if (runtime === 'antigravity') transforms.push({ file: 'SKILL.md', op: 'frontmatter', key: 'disable-slash-command', value: true, reason: 'Keeps /name hidden in Antigravity, as the skill\'s other settings say.' });
|
|
257
287
|
else diagnostics.push({ ruleId: 'L-08-user-invocation-broader', severity: 'warning', message: `${profile.title} has no setting that hides a skill from the person: there it can still be started by name.`, file: 'SKILL.md', line: null });
|
|
258
288
|
}
|
|
259
289
|
return { transforms, diagnostics };
|
|
@@ -306,7 +336,7 @@ export function lintForProfile(profileId, { parsed, folderName = '', headPath =
|
|
|
306
336
|
if (hidden.length || /[<>]/.test(name)) add('L-03-name-chars', 'error', `"name" holds characters ${profile.title} refuses (${hidden.length ? hidden.map((h) => h.label).join(', ') : 'an angle bracket'}).`, lineOf('name'));
|
|
307
337
|
const portableName = PORTABLE_NAME_RE.test(name);
|
|
308
338
|
if (!portableName) {
|
|
309
|
-
if (strictPortable || runtime === 'opencode') add('L-03-name-format', 'error', `"name" must be lowercase letters, digits and single hyphens for ${profile.title}.`, lineOf('name'));
|
|
339
|
+
if (strictPortable || runtime === 'opencode' || runtime === 'antigravity') add('L-03-name-format', 'error', `"name" must be lowercase letters, digits and single hyphens for ${profile.title}.`, lineOf('name'));
|
|
310
340
|
else add('L-03-name-format', 'warning', `"name" is not lowercase letters, digits and single hyphens: other runtimes may refuse it.`, lineOf('name'));
|
|
311
341
|
}
|
|
312
342
|
if (folderName && name !== folderName) {
|
|
@@ -398,6 +428,14 @@ export function lintForProfile(profileId, { parsed, folderName = '', headPath =
|
|
|
398
428
|
if (has(data, 'hooks') && !isMap(data.hooks)) add('L-04-hooks-type', 'error', `"hooks" must be a mapping of events; it is ${kindOf(data.hooks)}.`, lineOf('hooks'));
|
|
399
429
|
}
|
|
400
430
|
|
|
431
|
+
// Antigravity's own field and limit
|
|
432
|
+
if (runtime === 'antigravity') {
|
|
433
|
+
if (has(data, 'disable-slash-command') && typeof data['disable-slash-command'] !== 'boolean') add('L-04-disable-slash-command-type', 'error', `"disable-slash-command" must be true or false; it is ${kindOf(data['disable-slash-command'])}.`, lineOf('disable-slash-command'));
|
|
434
|
+
const bytes = utf8Length(parsed?.body);
|
|
435
|
+
const max = profile.limits.bodyBytes || ANTIGRAVITY_BODY_BYTES;
|
|
436
|
+
if (bytes > max) add('L-03-body-size', 'error', `The body is ${kb(bytes)}; ${profile.title} reads ${kb(max)} at most. Move detail into files the skill points to.`, parsed?.bodyLine || null);
|
|
437
|
+
}
|
|
438
|
+
|
|
401
439
|
// Keys the profile does not read
|
|
402
440
|
for (const key of Object.keys(data)) {
|
|
403
441
|
if (profile.fields.recognized.includes(key)) continue;
|
|
@@ -20,7 +20,7 @@ export const REVIEW_STATE_LABEL = Object.freeze({
|
|
|
20
20
|
withdrawn: 'Withdrawn',
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
export const RUNTIME_LABEL = Object.freeze({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', agents: 'Shared (~/.agents/skills)' });
|
|
23
|
+
export const RUNTIME_LABEL = Object.freeze({ claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', agents: 'Shared (~/.agents/skills)' });
|
|
24
24
|
|
|
25
25
|
/** A target as a person reads it: the runtime, and the project or the account when it is not the default one. */
|
|
26
26
|
export function targetLabel(t) {
|
|
@@ -123,7 +123,7 @@ export function targetChoices({ sharedAgentsRoot = false } = {}, suggested = [])
|
|
|
123
123
|
choices.push({ key, runtime: target.runtime, target, label: targetLabel(target), on: true });
|
|
124
124
|
}
|
|
125
125
|
const plainAsked = new Set(asked.filter(plainTarget).map((t) => t.runtime));
|
|
126
|
-
for (const runtime of ['claude', 'codex', 'opencode', ...(sharedAgentsRoot ? ['agents'] : [])]) {
|
|
126
|
+
for (const runtime of ['claude', 'codex', 'opencode', 'antigravity', ...(sharedAgentsRoot ? ['agents'] : [])]) {
|
|
127
127
|
if (plainAsked.has(runtime)) continue;
|
|
128
128
|
const target = { runtime };
|
|
129
129
|
choices.push({ key: JSON.stringify(target), runtime, target, label: targetLabel(target), on: !asked.length && runtime === 'claude' });
|
|
@@ -165,7 +165,7 @@ export async function deleteFromLibrary(ctx, ws) {
|
|
|
165
165
|
if (!ctx.alive(gen)) return;
|
|
166
166
|
const installs = skill.installations || [];
|
|
167
167
|
if (installs.length) {
|
|
168
|
-
const where = installs.slice(0, 6).map((i) => `• ${t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'agents'].includes(i.runtime) ? i.runtime : 'claude'}`)} · ${i.destination || ''}`);
|
|
168
|
+
const where = installs.slice(0, 6).map((i) => `• ${t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(i.runtime) ? i.runtime : 'claude'}`)} · ${i.destination || ''}`);
|
|
169
169
|
const answer = await ctx.ask({
|
|
170
170
|
title: t('skillsStudio.ws.libraryDeleteInstalledTitle', { name: ws.label }),
|
|
171
171
|
lines: [tp('skillsStudio.ws.libraryDeleteInstalled', installs.length), ...where],
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
|
|
26
26
|
const PROMPT_KEY = 'skills-studio-create-prompt';
|
|
27
27
|
const SCAN_COUNTED = ['blocked', 'incomplete', 'review', 'findings'];
|
|
28
|
-
const PROVIDER_RUNTIME = new Map([['claude-code', 'claude'], ['codex', 'codex'], ['opencode', 'opencode']]);
|
|
28
|
+
const PROVIDER_RUNTIME = new Map([['claude-code', 'claude'], ['codex', 'codex'], ['opencode', 'opencode'], ['antigravity', 'antigravity']]);
|
|
29
29
|
|
|
30
30
|
// What the box keeps between drawings of the place (the form itself is kept in storage).
|
|
31
31
|
const AI = {
|
|
@@ -13,7 +13,8 @@ import { CREATION_LIMITS, DRAFT_PROVIDERS, isActiveCreation, normalizeCreationRe
|
|
|
13
13
|
export const AI_FORM_KEY = 'skills-studio-create-ai';
|
|
14
14
|
/** The places a source is added from (describe is the prompt itself). */
|
|
15
15
|
export const AI_SOURCE_KINDS = Object.freeze(['conversation', 'url', 'repo', 'memory', 'import']);
|
|
16
|
-
|
|
16
|
+
/** The runtimes a drafted skill is meant for (its install targets, ss-creation.js CREATION_TARGET_RUNTIMES), not who drafts it. */
|
|
17
|
+
export const DRAFT_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
|
|
17
18
|
export const WORK_MINUTES = Object.freeze([20, 30, 45, 60, 90]);
|
|
18
19
|
/** The steps a creation shows, in order. */
|
|
19
20
|
export const CREATION_STEPS = Object.freeze(['sources', 'drafting', 'checking', 'review']);
|
|
@@ -97,7 +97,8 @@ export function healthIcon(code) {
|
|
|
97
97
|
case 'invalid': case 'not-text': return ICONS.err;
|
|
98
98
|
case 'warnings': return ICONS.warn;
|
|
99
99
|
case 'update': return ICONS.update;
|
|
100
|
-
case 'shadowed': case 'twice': case 'same-description': return ICONS.flag;
|
|
100
|
+
case 'shadowed': case 'twice': case 'same-description': case 'duplicate-record': return ICONS.flag;
|
|
101
|
+
case 'stale-install': return ICONS.update;
|
|
101
102
|
case 'bridge': return ICONS.bridge;
|
|
102
103
|
case 'readonly': return ICONS.lock;
|
|
103
104
|
case 'draft': return ICONS.draft;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Skills Studio 2.0: the fix panel and "Fix safe issues"
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// A health pill of the Library (or of a workspace's head) opens the fix panel of its row: every problem in plain
|
|
6
|
+
// words, what each fix does, and its buttons (sx-fixes.js says which). The Library's attention notice offers
|
|
7
|
+
// "Fix safe issues…": every one-press repair is read first and listed (what will be added, which record archived),
|
|
8
|
+
// nothing is written until the owner applies that list, and what was done or not is said item by item.
|
|
9
|
+
// Every write is a Studio API call (X-SynaBun-UI): a library skill's text goes to its draft (PATCH /v2/drafts), an
|
|
10
|
+
// agent's file is saved over the revision it was read at, a second library record is archived (Restore brings it
|
|
11
|
+
// back). Install locations change only through Publish and the existing remove flows.
|
|
12
|
+
|
|
13
|
+
import { studioErrorText } from './sx-errors.js';
|
|
14
|
+
import { subjectOf } from './sx-subject.js';
|
|
15
|
+
import { issuesOf, planTextFix, safeFixCandidates, issueCounts, fingerprintOf } from './sx-fixes.js';
|
|
16
|
+
import { healthWords } from './sx-library-view.js';
|
|
17
|
+
import { HEAD } from './sx-docs.js';
|
|
18
|
+
import { createFrontmatter } from '../ss-frontmatter.js';
|
|
19
|
+
import { lintArtifact } from '../ss-lint.js';
|
|
20
|
+
import { html, setHTML, ICONS, healthIcon } from './sx-dom.js';
|
|
21
|
+
|
|
22
|
+
const YAML_MODULE = '/vendor/yaml/yaml-2.9.1.min.js';
|
|
23
|
+
let _fm = null;
|
|
24
|
+
const frontmatter = async () => (_fm ||= createFrontmatter(await import(YAML_MODULE)));
|
|
25
|
+
|
|
26
|
+
/** The most text repairs "Fix safe issues" reads in one go (each is one or two reads). */
|
|
27
|
+
export const MAX_TEXT_READS = 40;
|
|
28
|
+
|
|
29
|
+
/** What a row allows the fix panel to do. */
|
|
30
|
+
export function canOf(ctx, deleteKind) {
|
|
31
|
+
return (row) => {
|
|
32
|
+
const s = subjectOf(row, { guest: ctx.guest, caps: ctx.caps });
|
|
33
|
+
const library = s.mode === 'library' && !!row.skill?.skillId;
|
|
34
|
+
return {
|
|
35
|
+
edit: !ctx.guest && !row.binary && (library || (s.mode === 'inplace' && !!s.v1Id && row.kind === 'agent')),
|
|
36
|
+
open: true,
|
|
37
|
+
delete: !!deleteKind?.(ctx, row, s),
|
|
38
|
+
archive: !ctx.guest && (!ctx.capsKnown || ctx.caps.has('library-delete')),
|
|
39
|
+
dismiss: !ctx.guest,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read a row's head file and the safe repair of it, without writing anything.
|
|
46
|
+
* @returns {Promise<{ changes: object[], apply: () => Promise<void> } | null>} null: nothing safe to do
|
|
47
|
+
*/
|
|
48
|
+
export async function readTextFix(ctx, row) {
|
|
49
|
+
const { api } = ctx;
|
|
50
|
+
const s = subjectOf(row, { guest: ctx.guest, caps: ctx.caps });
|
|
51
|
+
if (s.mode === 'inplace' && s.v1Id && row.kind === 'agent') {
|
|
52
|
+
const got = await api.fetchSkillsArtifact(s.v1Id);
|
|
53
|
+
if (got.binary || got.readOnly) return null;
|
|
54
|
+
const plan = planTextFix({ raw: got.rawContent, diagnostics: got.diagnostics, type: 'agent', folderName: row.folderName });
|
|
55
|
+
if (!plan) return null;
|
|
56
|
+
return { changes: plan.changes, apply: () => api.saveSkillsArtifact(s.v1Id, plan.text, { expectedRevision: got.revision }) };
|
|
57
|
+
}
|
|
58
|
+
if (s.mode !== 'library' || !row.skill?.skillId) return null;
|
|
59
|
+
const skill = (await api.fetchSkill(row.skill.skillId)).skill;
|
|
60
|
+
let draft = null;
|
|
61
|
+
if (skill.pointers?.draft) {
|
|
62
|
+
try { draft = (await api.fetchSkillsDraft(skill.pointers.draft)).draft; } catch (err) { if (err?.status !== 404) throw err; }
|
|
63
|
+
}
|
|
64
|
+
const latest = skill.pointers?.latest || null;
|
|
65
|
+
const file = draft ? await api.fetchSkillsDraftFile(draft.draftId, HEAD) : latest ? await api.fetchSkillsRevisionFile(latest, HEAD) : null;
|
|
66
|
+
if (!file || typeof file.content !== 'string') return null;
|
|
67
|
+
const fm = await frontmatter();
|
|
68
|
+
const folderName = String(skill.name || row.folderName || '');
|
|
69
|
+
const lint = lintArtifact({ type: 'skill', parsed: fm.parse(file.content), folderName });
|
|
70
|
+
const plan = planTextFix({ raw: file.content, diagnostics: lint.all, type: 'skill', folderName });
|
|
71
|
+
if (!plan) return null;
|
|
72
|
+
return {
|
|
73
|
+
changes: plan.changes,
|
|
74
|
+
apply: async () => {
|
|
75
|
+
const target = draft || (await api.createSkillsDraft({ skillId: skill.skillId, baseRevisionId: latest }, { idempotencyKey: ctx.key('fix-draft') })).draft;
|
|
76
|
+
await api.patchSkillsDraft(target.draftId, { expectedEtag: target.etag, ops: [{ op: 'put', path: HEAD, content: plan.text }] });
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Archive the second library record of a skill (Restore in the Archived view brings it back). */
|
|
82
|
+
const archiveRecord = (ctx, row) => ctx.api.deleteLibrarySkill(row.skill.skillId, row.skill.etag ? { expectedEtag: row.skill.etag } : {});
|
|
83
|
+
|
|
84
|
+
/** The words of a step of the preview. */
|
|
85
|
+
function stepWords(ctx, step) {
|
|
86
|
+
const { t } = ctx;
|
|
87
|
+
if (step.kind === 'archive') return t('skillsStudio.fix.step.archive', { name: step.row.label, original: step.issue.health.name || '' });
|
|
88
|
+
return t('skillsStudio.fix.step.text', { name: step.row.label, added: step.changes.map((c) => c.added).join(' · ') });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Say what was done (a notice kept above the list until OK, a toast, the live region). */
|
|
92
|
+
function report(ctx, done, failed) {
|
|
93
|
+
const { t, tp } = ctx;
|
|
94
|
+
const line = failed.length ? t('skillsStudio.fix.resultPartial', { done: done.length, failed: failed.length, first: failed[0] }) : tp('skillsStudio.fix.resultDone', done.length);
|
|
95
|
+
ctx.lib.fixNotice = { kind: failed.length ? 'warn' : 'info', line, lines: [...done, ...failed] };
|
|
96
|
+
ctx.toast(line, failed.length ? 'error' : 'info');
|
|
97
|
+
ctx.announce(line);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* "Fix safe issues…": read every candidate, list exactly what would change, apply only that list on the owner's word.
|
|
102
|
+
* @param {object} ctx @param {object[]} rows the Library's rows @param {Function} deleteKind
|
|
103
|
+
*/
|
|
104
|
+
export async function fixAllSafe(ctx, rows, deleteKind) {
|
|
105
|
+
const { t, tp } = ctx;
|
|
106
|
+
const { steps } = safeFixCandidates(rows, canOf(ctx, deleteKind));
|
|
107
|
+
const gen = ctx.gen();
|
|
108
|
+
ctx.toast(t('skillsStudio.fix.reading'));
|
|
109
|
+
const ready = []; const unreadable = [];
|
|
110
|
+
let reads = 0;
|
|
111
|
+
for (const step of steps) {
|
|
112
|
+
if (step.kind !== 'text') { ready.push(step); continue; }
|
|
113
|
+
if (reads++ >= MAX_TEXT_READS) break;
|
|
114
|
+
try {
|
|
115
|
+
const fix = await readTextFix(ctx, step.row);
|
|
116
|
+
if (fix) ready.push({ ...step, changes: fix.changes, apply: fix.apply });
|
|
117
|
+
} catch (err) { unreadable.push(`${step.row.label}: ${studioErrorText(t, err)}`); }
|
|
118
|
+
if (!ctx.alive(gen)) return;
|
|
119
|
+
}
|
|
120
|
+
if (!ctx.alive(gen)) return;
|
|
121
|
+
const list = document.createElement('ul');
|
|
122
|
+
list.className = 'sx-fix-preview';
|
|
123
|
+
for (const step of ready) {
|
|
124
|
+
const li = document.createElement('li');
|
|
125
|
+
li.className = 'sx-fix-preview-row';
|
|
126
|
+
li.textContent = stepWords(ctx, step);
|
|
127
|
+
list.append(li);
|
|
128
|
+
}
|
|
129
|
+
if (!ready.length) {
|
|
130
|
+
await ctx.ask({ title: t('skillsStudio.fix.previewTitle'), lines: [t('skillsStudio.fix.previewNothing'), ...unreadable], buttons: [{ id: 'ok', label: t('skillsStudio.dialog.close'), kind: 'primary' }], cancelId: 'ok' });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const answer = await ctx.ask({
|
|
134
|
+
title: t('skillsStudio.fix.previewTitle'),
|
|
135
|
+
lines: [t('skillsStudio.fix.previewIntro'), ...unreadable],
|
|
136
|
+
extra: list,
|
|
137
|
+
wide: true,
|
|
138
|
+
buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'apply', label: tp('skillsStudio.fix.apply', ready.length), kind: 'primary' }],
|
|
139
|
+
focus: 'apply',
|
|
140
|
+
});
|
|
141
|
+
if (answer !== 'apply' || !ctx.alive(gen)) return;
|
|
142
|
+
const done = []; const failed = [];
|
|
143
|
+
for (const step of ready) {
|
|
144
|
+
try {
|
|
145
|
+
if (step.kind === 'archive') await archiveRecord(ctx, step.row); else await step.apply();
|
|
146
|
+
done.push(stepWords(ctx, step));
|
|
147
|
+
} catch (err) { failed.push(`${step.row.label}: ${studioErrorText(t, err)}`); }
|
|
148
|
+
}
|
|
149
|
+
if (!ctx.alive(gen)) return;
|
|
150
|
+
report(ctx, done, failed);
|
|
151
|
+
await ctx.reload();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** One issue as a card of the panel: its word, what it means, how it is fixed, and its buttons. */
|
|
155
|
+
function issueCard(ctx, row, issue, index) {
|
|
156
|
+
const { t } = ctx;
|
|
157
|
+
const card = document.createElement('section');
|
|
158
|
+
card.className = `sx-fix-card sx-fix-card--${issue.sev}`;
|
|
159
|
+
card.setAttribute('aria-labelledby', `sx-fix-${index}-title`);
|
|
160
|
+
const head = document.createElement('div');
|
|
161
|
+
head.className = 'sx-fix-card-head';
|
|
162
|
+
setHTML(head, html`${healthIcon(issue.code)}<strong id="sx-fix-${index}-title">${healthWords(issue.health, t)}</strong><span class="sx-fix-mode sx-fix-mode--${issue.mode}">${issue.mode === 'auto' ? t('skillsStudio.fix.mode.auto') : t('skillsStudio.fix.mode.guided')}</span>`);
|
|
163
|
+
const explain = document.createElement('p');
|
|
164
|
+
explain.className = 'sx-fix-explain';
|
|
165
|
+
explain.textContent = explainOf(ctx, row, issue);
|
|
166
|
+
card.append(head, explain);
|
|
167
|
+
const bar = document.createElement('div');
|
|
168
|
+
bar.className = 'sx-fix-actions';
|
|
169
|
+
for (const action of issue.actions) {
|
|
170
|
+
const b = document.createElement('button');
|
|
171
|
+
b.type = 'button';
|
|
172
|
+
b.className = `sx-btn sx-btn--sm${action === 'dismiss' ? ' sx-btn--ghost' : action === 'delete' ? ' sx-btn--danger' : (action === 'auto-text' || action === 'archive-duplicate') ? ' sx-btn--primary' : ''}`;
|
|
173
|
+
b.dataset.fix = `${index}:${action}`;
|
|
174
|
+
b.textContent = t(`skillsStudio.fix.action.${action}`);
|
|
175
|
+
bar.append(b);
|
|
176
|
+
}
|
|
177
|
+
if (!issue.actions.length) {
|
|
178
|
+
const p = document.createElement('p');
|
|
179
|
+
p.className = 'sx-dim';
|
|
180
|
+
p.textContent = t('skillsStudio.fix.nothingHere');
|
|
181
|
+
bar.append(p);
|
|
182
|
+
}
|
|
183
|
+
card.append(bar);
|
|
184
|
+
return card;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** What a problem means for this row, in plain words. */
|
|
188
|
+
export function explainOf(ctx, row, issue) {
|
|
189
|
+
const { t } = ctx;
|
|
190
|
+
const h = issue.health;
|
|
191
|
+
const runtime = h.runtime ? t(`skillsStudio.runtime.${h.runtime}`) : '';
|
|
192
|
+
switch (issue.code) {
|
|
193
|
+
case 'shadowed': return t('skillsStudio.fix.explain.shadowed', { runtime: runtime || '—' });
|
|
194
|
+
case 'twice': return t('skillsStudio.fix.explain.twice', { runtime: runtime || '—' });
|
|
195
|
+
case 'same-description': return t('skillsStudio.fix.explain.same-description', { names: (h.names || []).join(', ') });
|
|
196
|
+
case 'duplicate-record': return `${t('skillsStudio.fix.explain.duplicate-record', { original: h.name || '' })}${h.why ? ` ${t(`skillsStudio.fix.duplicateWhy.${h.why}`)}` : ''}`;
|
|
197
|
+
default: return t(`skillsStudio.fix.explain.${issue.code}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The fix panel of one row. Resolves when the owner is done (a fix applied, a place opened, or closed).
|
|
203
|
+
* @param {object} ctx @param {object} row @param {{ deleteKind: Function, bulkDelete: Function }} hooks
|
|
204
|
+
*/
|
|
205
|
+
export async function openFixPanel(ctx, row, { deleteKind, bulkDelete } = {}) {
|
|
206
|
+
const { t } = ctx;
|
|
207
|
+
const issues = issuesOf(row, canOf(ctx, deleteKind)(row));
|
|
208
|
+
const box = document.createElement('div');
|
|
209
|
+
box.className = 'sx-fix-panel';
|
|
210
|
+
if (!issues.length) {
|
|
211
|
+
const p = document.createElement('p');
|
|
212
|
+
p.textContent = t('skillsStudio.fix.none');
|
|
213
|
+
box.append(p);
|
|
214
|
+
}
|
|
215
|
+
issues.forEach((issue, i) => box.append(issueCard(ctx, row, issue, i)));
|
|
216
|
+
const answer = await ctx.ask({
|
|
217
|
+
title: t('skillsStudio.fix.title', { name: row.label }),
|
|
218
|
+
lines: issues.length ? [t('skillsStudio.fix.intro')] : [],
|
|
219
|
+
extra: box,
|
|
220
|
+
wide: true,
|
|
221
|
+
buttons: [{ id: 'close', label: t('skillsStudio.dialog.close') }],
|
|
222
|
+
cancelId: 'close',
|
|
223
|
+
onOpen: (ui) => box.addEventListener('click', (e) => { const b = e.target.closest?.('[data-fix]'); if (b) ui.done(b.dataset.fix); }),
|
|
224
|
+
});
|
|
225
|
+
if (!answer || answer === 'close') return;
|
|
226
|
+
const [index, action] = [Number(answer.split(':')[0]), answer.split(':').slice(1).join(':')];
|
|
227
|
+
const issue = issues[index];
|
|
228
|
+
if (!issue) return;
|
|
229
|
+
await runAction(ctx, row, issue, action, { bulkDelete });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function runAction(ctx, row, issue, action, { bulkDelete }) {
|
|
233
|
+
const { t, tp } = ctx;
|
|
234
|
+
const gen = ctx.gen();
|
|
235
|
+
try {
|
|
236
|
+
switch (action) {
|
|
237
|
+
case 'dismiss':
|
|
238
|
+
ctx.dismissIssues([issue.fingerprint]);
|
|
239
|
+
ctx.toast(t('skillsStudio.fix.dismissed'));
|
|
240
|
+
return;
|
|
241
|
+
case 'open-edit': ctx.openRow(row.key, { tab: 'edit' }); return;
|
|
242
|
+
case 'open-publish': ctx.openRow(row.key, { tab: 'publish' }); return;
|
|
243
|
+
case 'open-update': ctx.openRow(row.key); return;
|
|
244
|
+
case 'keep-mine':
|
|
245
|
+
ctx.keepUpdate(row.folderName, row.update?.upstreamRevisionId);
|
|
246
|
+
ctx.toast(t('skillsStudio.toast.keptMine', { name: row.folderName }));
|
|
247
|
+
ctx.render();
|
|
248
|
+
return;
|
|
249
|
+
case 'move-bridges': await ctx.openSkillFolders(); return;
|
|
250
|
+
case 'delete':
|
|
251
|
+
ctx.lib.selected = new Set([row.key]);
|
|
252
|
+
await bulkDelete?.(ctx);
|
|
253
|
+
return;
|
|
254
|
+
case 'auto-text': {
|
|
255
|
+
const fix = await readTextFix(ctx, row);
|
|
256
|
+
if (!ctx.alive(gen)) return;
|
|
257
|
+
if (!fix) {
|
|
258
|
+
const go = await ctx.ask({ title: t('skillsStudio.fix.title', { name: row.label }), lines: [t('skillsStudio.fix.textNothing')], buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.close') }, { id: 'open', label: t('skillsStudio.fix.action.open-edit'), kind: 'primary' }], focus: 'open' });
|
|
259
|
+
if (go === 'open') ctx.openRow(row.key, { tab: 'edit' });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const step = { kind: 'text', row, issue, changes: fix.changes };
|
|
263
|
+
const ok = await ctx.ask({ title: t('skillsStudio.fix.previewTitle'), lines: [t('skillsStudio.fix.previewIntro'), stepWords(ctx, step), row.skill ? t('skillsStudio.fix.textToDraft') : t('skillsStudio.fix.textInPlace')], buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'apply', label: tp('skillsStudio.fix.apply', 1), kind: 'primary' }], focus: 'apply' });
|
|
264
|
+
if (ok !== 'apply' || !ctx.alive(gen)) return;
|
|
265
|
+
await fix.apply();
|
|
266
|
+
report(ctx, [stepWords(ctx, step)], []);
|
|
267
|
+
await ctx.reload();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
case 'archive-duplicate': {
|
|
271
|
+
const step = { kind: 'archive', row, issue };
|
|
272
|
+
const ok = await ctx.ask({ title: t('skillsStudio.fix.previewTitle'), lines: [stepWords(ctx, step)], buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'apply', label: tp('skillsStudio.fix.apply', 1), kind: 'primary' }], focus: 'apply' });
|
|
273
|
+
if (ok !== 'apply' || !ctx.alive(gen)) return;
|
|
274
|
+
await archiveRecord(ctx, row);
|
|
275
|
+
report(ctx, [stepWords(ctx, step)], []);
|
|
276
|
+
await ctx.reload();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
case 'choose-copy': await chooseCopy(ctx, row, issue); return;
|
|
280
|
+
default: return;
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (ctx.alive(gen)) ctx.toast(t('skillsStudio.fix.failed', { error: studioErrorText(t, err) }), 'error');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Two records of one skill that differ: the owner picks the one that keeps the name; the other is archived. */
|
|
288
|
+
async function chooseCopy(ctx, row, issue) {
|
|
289
|
+
const { t, tp } = ctx;
|
|
290
|
+
const original = ctx.rows.find((r) => r.key === issue.health.of);
|
|
291
|
+
const pair = [original, row].filter(Boolean);
|
|
292
|
+
const archivable = (r) => !(r.skill?.installations || []).length && !r.review;
|
|
293
|
+
const box = document.createElement('div');
|
|
294
|
+
box.className = 'sx-fix-choose';
|
|
295
|
+
box.setAttribute('role', 'radiogroup');
|
|
296
|
+
box.setAttribute('aria-label', t('skillsStudio.fix.chooseTitle'));
|
|
297
|
+
let picked = null;
|
|
298
|
+
for (const r of pair) {
|
|
299
|
+
const label = document.createElement('label');
|
|
300
|
+
label.className = 'sx-fix-choice';
|
|
301
|
+
const input = document.createElement('input');
|
|
302
|
+
input.type = 'radio';
|
|
303
|
+
input.name = 'sx-fix-keep';
|
|
304
|
+
input.value = r.key;
|
|
305
|
+
// Keeping one archives the other: only a record that can be archived (not installed, not in review) may go.
|
|
306
|
+
const other = pair.find((x) => x !== r);
|
|
307
|
+
input.disabled = !other || !archivable(other);
|
|
308
|
+
input.addEventListener('change', () => { picked = r.key; });
|
|
309
|
+
const text = document.createElement('span');
|
|
310
|
+
const versions = Number(r.skill?.revisions) || 0;
|
|
311
|
+
const installs = (r.skill?.installations || []).length;
|
|
312
|
+
text.textContent = t('skillsStudio.fix.keepThis', { name: r.label, versions: tp('skillsStudio.fix.versions', versions), installs: tp('skillsStudio.fix.installs', installs) });
|
|
313
|
+
label.append(input, text);
|
|
314
|
+
box.append(label);
|
|
315
|
+
}
|
|
316
|
+
if (!pair.some((r) => archivable(r))) {
|
|
317
|
+
await ctx.ask({ title: t('skillsStudio.fix.chooseTitle'), lines: [t('skillsStudio.fix.chooseNone')], buttons: [{ id: 'ok', label: t('skillsStudio.dialog.close'), kind: 'primary' }], cancelId: 'ok' });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const answer = await ctx.ask({
|
|
321
|
+
title: t('skillsStudio.fix.chooseTitle'),
|
|
322
|
+
lines: [t('skillsStudio.fix.chooseIntro')],
|
|
323
|
+
extra: box,
|
|
324
|
+
buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'keep', label: t('skillsStudio.fix.keepConfirm'), kind: 'primary' }],
|
|
325
|
+
accept: () => !!picked,
|
|
326
|
+
});
|
|
327
|
+
if (answer !== 'keep' || !picked) return;
|
|
328
|
+
const gone = pair.find((r) => r.key !== picked);
|
|
329
|
+
await archiveRecord(ctx, gone);
|
|
330
|
+
report(ctx, [t('skillsStudio.fix.step.archive', { name: gone.label, original: pair.find((r) => r.key === picked)?.label || '' })], []);
|
|
331
|
+
await ctx.reload();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** The Library's attention notice: how many problems, and "Fix safe issues…" / "Show them". */
|
|
335
|
+
export function fixNoticeHTML(ctx, deleteKind) {
|
|
336
|
+
if (ctx.guest || ctx.lib.view === 'archived' || ctx.loading) return '';
|
|
337
|
+
const { t, tp } = ctx;
|
|
338
|
+
const out = [];
|
|
339
|
+
const n = ctx.lib.fixNotice;
|
|
340
|
+
if (n) {
|
|
341
|
+
out.push(html`<div class="sx-notice sx-notice--${n.kind === 'info' ? 'info' : 'warn'}" role="status" data-notice-box="fix">
|
|
342
|
+
${n.kind === 'info' ? ICONS.ok : ICONS.warn}<span class="sx-notice-text">${n.line}${n.lines.slice(0, 8).map((l) => html`<span class="sx-block">${l}</span>`)}</span>
|
|
343
|
+
<button type="button" class="sx-btn sx-btn--ghost" data-lib="fix-notice-ok">${t('skillsStudio.notice.ok')}</button>
|
|
344
|
+
</div>`);
|
|
345
|
+
}
|
|
346
|
+
const counts = issueCounts(ctx.rows, canOf(ctx, deleteKind));
|
|
347
|
+
if (counts.total) {
|
|
348
|
+
const { steps } = safeFixCandidates(ctx.rows, canOf(ctx, deleteKind));
|
|
349
|
+
out.push(html`<div class="sx-notice sx-notice--warn sx-fix-notice" role="note" data-notice-box="attention">
|
|
350
|
+
${ICONS.warn}<span class="sx-notice-text">${tp('skillsStudio.fix.notice', counts.total)}${steps.length ? ` ${tp('skillsStudio.fix.noticeSafe', steps.length)}` : ''}</span>
|
|
351
|
+
${steps.length ? html`<button type="button" class="sx-btn sx-btn--primary" data-lib="fix-all">${t('skillsStudio.fix.fixAll')}</button>` : ''}
|
|
352
|
+
${ctx.lib.health === 'attention' ? '' : html`<button type="button" class="sx-btn" data-lib="fix-show">${t('skillsStudio.fix.showIssues')}</button>`}
|
|
353
|
+
</div>`);
|
|
354
|
+
}
|
|
355
|
+
return html`${out}`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** The unreadable folders of the catalog, said with what can be done: read again, or leave them (dismissed). */
|
|
359
|
+
export const unreadableFingerprint = (count) => fingerprintOf('library', { code: 'unreadable', count });
|