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
|
@@ -18,11 +18,13 @@ import { html, raw, setHTML, ICONS, healthIcon, relativeTime, fullTime, rovingFo
|
|
|
18
18
|
import { dropKept, dropDraft } from './sx-docs.js';
|
|
19
19
|
import { encodeArtifactId } from '../ss-ids.js';
|
|
20
20
|
import { revealHidden } from '../ss-unicode.js';
|
|
21
|
+
import { issuesOf } from './sx-fixes.js';
|
|
22
|
+
import { openFixPanel, fixAllSafe, fixNoticeHTML, unreadableFingerprint } from './sx-fixes-view.js';
|
|
21
23
|
|
|
22
24
|
let _life = null; // the listeners of the drawn header (filters): they end with the next drawing
|
|
23
25
|
let _lastChecked = null;
|
|
24
26
|
|
|
25
|
-
const RT_LETTER = { claude: 'C', codex: 'X', opencode: 'O' };
|
|
27
|
+
const RT_LETTER = { claude: 'C', codex: 'X', opencode: 'O', antigravity: 'A' };
|
|
26
28
|
const DIAG_REASONS = ['no-skill-md', 'unreadable-head', 'link', 'too-deep', 'unreadable', 'outside-project', 'not-a-folder'];
|
|
27
29
|
|
|
28
30
|
/** Where a row comes from, in words: a label and a detail. */
|
|
@@ -65,11 +67,22 @@ export function healthWhy(h, t) {
|
|
|
65
67
|
return '';
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* `fix`: the capsule opens the row's fix panel (sx-fixes-view.js) when it holds a problem: in the Library a
|
|
72
|
+
* `data-fix` button, in a workspace a `data-ws="fix-issues"` one. A capsule with nothing to fix stays a label.
|
|
73
|
+
*/
|
|
74
|
+
export function healthCapsule(row, t, { fix = null } = {}) {
|
|
69
75
|
const worst = worstHealth(row);
|
|
70
76
|
const rest = row.health.filter((h) => h !== worst && h.sev !== 'ok');
|
|
71
77
|
const all = row.health.map((h) => { const why = healthWhy(h, t); return why ? `${healthWords(h, t)}: ${why}` : healthWords(h, t); }).join(' · ');
|
|
72
|
-
|
|
78
|
+
const inner = html`${healthIcon(worst.code)}<span>${healthWords(worst, t)}</span>${rest.length ? html`<span class="sx-health-more">+${rest.length}</span>` : ''}`;
|
|
79
|
+
if (fix && issuesOf(row, { open: true }).length) {
|
|
80
|
+
const label = t('skillsStudio.fix.open', { problems: all });
|
|
81
|
+
return fix === 'library'
|
|
82
|
+
? html`<button type="button" class="sx-health sx-health--${worst.sev} sx-health--fix" data-fix="${row.key}" tabindex="-1" title="${label}" aria-label="${label}">${inner}</button>`
|
|
83
|
+
: html`<button type="button" class="sx-health sx-health--${worst.sev} sx-health--fix" data-ws="fix-issues" title="${label}" aria-label="${label}">${inner}</button>`;
|
|
84
|
+
}
|
|
85
|
+
return html`<span class="sx-health sx-health--${worst.sev}" title="${all}">${inner}</span>`;
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
/**
|
|
@@ -80,12 +93,12 @@ export function healthCapsule(row, t) {
|
|
|
80
93
|
export function statusPill(status, ctx, { button = null, id = null } = {}) {
|
|
81
94
|
const { t } = ctx;
|
|
82
95
|
const where = status.where.map((w) => {
|
|
83
|
-
const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`);
|
|
96
|
+
const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`);
|
|
84
97
|
if (w.scope === 'project') return t('skillsStudio.publish.targetProject', { runtime, project: ctx.projects?.find((p) => p.path === w.projectPath)?.label || w.projectPath || '' });
|
|
85
98
|
if (w.accountId && w.accountId !== 'default') return t('skillsStudio.publish.targetAccount', { runtime, account: w.accountId });
|
|
86
99
|
return t('skillsStudio.publish.targetUser', { runtime });
|
|
87
100
|
});
|
|
88
|
-
const runtimes = [...new Set(status.where.map((w) => t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`)))].join(', ');
|
|
101
|
+
const runtimes = [...new Set(status.where.map((w) => t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`)))].join(', ');
|
|
89
102
|
const text = status.code === 'installed' ? t('skillsStudio.status.installed', { where: runtimes }) : status.code === 'outdated' ? t('skillsStudio.status.outdated', { where: runtimes }) : t(`skillsStudio.status.${status.code}`);
|
|
90
103
|
const title = where.length ? where.join(', ') : t(`skillsStudio.status.help.${status.code}`);
|
|
91
104
|
// Not installed is no tick: the dashed circle (healthIcon has no 'off' and would fall through to the tick).
|
|
@@ -96,7 +109,21 @@ export function statusPill(status, ctx, { button = null, id = null } = {}) {
|
|
|
96
109
|
: html`<span class="sx-status sx-status--${status.code}"${id ? html` id="${id}"` : ''} title="${title}">${inner}</span>`;
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* The status and health pills of a row, once each, in the Library cell and the workspace head alike: a library skill
|
|
114
|
+
* shows its status, and its health only when something is wrong (an info item such as "draft" or "in review" is
|
|
115
|
+
* what the status already says); anything else shows its health.
|
|
116
|
+
*/
|
|
117
|
+
export function statusPills(row, ctx, { status = installStatus({ skill: row.skill, row }), button = null, id = null, fix = null } = {}) {
|
|
118
|
+
const how = ctx.guest ? null : fix;
|
|
119
|
+
if (!status) return healthCapsule(row, ctx.t, { fix: how });
|
|
120
|
+
const sev = worstHealth(row).sev;
|
|
121
|
+
// An installation older than the latest version is said too: the status reads "installed" for it.
|
|
122
|
+
const stale = row.health.some((h) => h.code === 'stale-install');
|
|
123
|
+
return html`${statusPill(status, ctx, { button, id })}${sev === 'err' || sev === 'warn' || stale ? healthCapsule(row, ctx.t, { fix: how }) : ''}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The runtime capsules of a row (C X O A), each named in full. */
|
|
100
127
|
export function runtimeCapsules(row, t) {
|
|
101
128
|
return html`<span class="sx-rts">${RUNTIMES.map((rt) => {
|
|
102
129
|
const st = row.runtimes[rt]?.state || 'none';
|
|
@@ -169,11 +196,10 @@ function rowHTML(row, ctx, selected, first) {
|
|
|
169
196
|
function healthCellHTML(row, ctx) {
|
|
170
197
|
const { t } = ctx;
|
|
171
198
|
const status = installStatus({ skill: row.skill, row });
|
|
172
|
-
if (!status) return healthCapsule(row, t);
|
|
173
|
-
const worst = worstHealth(row);
|
|
199
|
+
if (!status) return healthCapsule(row, t, { fix: ctx.guest ? null : 'library' });
|
|
174
200
|
// Installed for one assistant and not the others in effect: one press from the row adds them.
|
|
175
201
|
const also = ['installed', 'outdated'].includes(status.code) && !ctx.guest ? alsoInstall(ctx, { skill: row.skill, row }) : { targets: [], words: '' };
|
|
176
|
-
return html`<span class="sx-health-cell">${
|
|
202
|
+
return html`<span class="sx-health-cell">${statusPills(row, ctx, { status, fix: 'library' })}${status.code === 'approved' && !ctx.guest ? html`<button type="button" class="sx-btn sx-btn--sm sx-btn--primary" data-install="${row.key}" tabindex="-1" title="${t('skillsStudio.library.installHelp')}">${t('skillsStudio.library.install')}</button>` : ''}${also.targets.length ? html`<button type="button" class="sx-btn sx-btn--sm" data-also-install="${row.key}" tabindex="-1" title="${t('skillsStudio.library.alsoInstallHelp')}">${t('skillsStudio.library.alsoInstall', { targets: also.words })}</button>` : ''}</span>`;
|
|
177
203
|
}
|
|
178
204
|
|
|
179
205
|
/**
|
|
@@ -269,9 +295,11 @@ function summaryHTML(ctx, shown) {
|
|
|
269
295
|
const parts = [t('skillsStudio.library.shown', { shown: shown.length, total })];
|
|
270
296
|
if (counts?.views?.attention) parts.push(tp('skillsStudio.library.attentionCount', counts.views.attention));
|
|
271
297
|
const diag = data?.catalog?.diagnostics || [];
|
|
298
|
+
// Folders the owner said to leave as they are are not said again until their number changes.
|
|
299
|
+
const diagSaid = diag.length && !ctx.dismissed?.().has(unreadableFingerprint(diag.length));
|
|
272
300
|
// A server older than the page reads no change times: the dashes are said once, with what brings them.
|
|
273
301
|
const restart = knowsUpdated(ctx) ? '' : html` · <span class="sx-note-warn" id="sx-lib-updated-restart">${t('skillsStudio.library.updatedRestart')}</span>`;
|
|
274
|
-
return html`<p class="sx-summary" role="status">${parts.join(' · ')}${
|
|
302
|
+
return html`<p class="sx-summary" role="status">${parts.join(' · ')}${diagSaid ? html` · <button type="button" class="sx-link" data-lib="diagnostics">${tp('skillsStudio.library.diagnostics', diag.length)}</button>` : ''}${data?.catalog?.truncated ? html` · <span class="sx-warn-text">${t('skillsStudio.library.truncated')}</span>` : ''}${restart}</p>`;
|
|
275
303
|
}
|
|
276
304
|
|
|
277
305
|
/**
|
|
@@ -394,7 +422,7 @@ export function renderLibraryPlace(host, ctx) {
|
|
|
394
422
|
const shown = visible(ctx);
|
|
395
423
|
setHTML(host, html`<section class="sx-place sx-place--library" aria-labelledby="sx-lib-title">
|
|
396
424
|
${filterHeadHTML(ctx)}
|
|
397
|
-
<div class="sx-place-notices">${installNoticeHTML(ctx)}${migrationNoticeHTML(ctx)}${keptNoticeHTML(ctx)}${updateNoticeHTML(ctx)}</div>
|
|
425
|
+
<div class="sx-place-notices">${fixNoticeHTML(ctx, deleteKind)}${installNoticeHTML(ctx)}${migrationNoticeHTML(ctx)}${keptNoticeHTML(ctx)}${updateNoticeHTML(ctx)}</div>
|
|
398
426
|
<div class="sx-place-scroll" id="sx-lib-scroll">
|
|
399
427
|
<div id="sx-lib-summary">${summaryHTML(ctx, shown)}</div>
|
|
400
428
|
<div id="sx-lib-table">${tableHTML(ctx, shown)}</div>
|
|
@@ -450,6 +478,8 @@ async function onClick(e, host, ctx) {
|
|
|
450
478
|
if (also && host.contains(also) && !also.disabled) { onAlsoInstall(also, host, ctx); return; }
|
|
451
479
|
const restore = e.target.closest?.('[data-restore]');
|
|
452
480
|
if (restore && host.contains(restore) && !restore.disabled) { onRestore(restore, host, ctx); return; }
|
|
481
|
+
const fix = e.target.closest?.('[data-fix]');
|
|
482
|
+
if (fix && host.contains(fix)) { const row = ctx.rows.find((r) => r.key === fix.dataset.fix); ctx.lib.focus = fix.dataset.fix; if (row) openFixPanel(ctx, row, { deleteKind, bulkDelete }); return; }
|
|
453
483
|
const open = e.target.closest?.('[data-open]');
|
|
454
484
|
if (open && host.contains(open)) { ctx.lib.focus = open.dataset.open; ctx.openRow(open.dataset.open); return; }
|
|
455
485
|
const btn = e.target.closest?.('[data-lib]');
|
|
@@ -458,6 +488,9 @@ async function onClick(e, host, ctx) {
|
|
|
458
488
|
if (act === 'new') ctx.goPlace('create');
|
|
459
489
|
else if (act === 'retry') ctx.reload();
|
|
460
490
|
else if (act === 'install-notice-ok') { ctx.lib.installNotice = null; ctx.render(); }
|
|
491
|
+
else if (act === 'fix-notice-ok') { ctx.lib.fixNotice = null; ctx.render(); }
|
|
492
|
+
else if (act === 'fix-all') fixAllSafe(ctx, ctx.rows, deleteKind);
|
|
493
|
+
else if (act === 'fix-show') { Object.assign(ctx.lib, { view: 'all', health: 'attention' }); ctx.saveLibraryView(); ctx.render(); }
|
|
461
494
|
else if (act === 'clear-filters') { Object.assign(ctx.lib, { view: 'all', kind: 'all', source: 'all', runtime: 'all', health: 'any', q: '' }); ctx.saveLibraryView(); ctx.render(); }
|
|
462
495
|
else if (act === 'bulk-clear') { ctx.lib.selected.clear(); redrawRows(host, ctx); }
|
|
463
496
|
else if (act === 'diagnostics') showDiagnostics(ctx);
|
|
@@ -515,7 +548,18 @@ async function showDiagnostics(ctx) {
|
|
|
515
548
|
li.title = d.message || '';
|
|
516
549
|
list.append(li);
|
|
517
550
|
}
|
|
518
|
-
|
|
551
|
+
const buttons = ctx.guest ? [{ id: 'ok', label: t('skillsStudio.dialog.close'), kind: 'primary' }]
|
|
552
|
+
: [{ id: 'dismiss', label: t('skillsStudio.fix.action.dismiss') }, { id: 'ok', label: t('skillsStudio.dialog.close') }, { id: 'rescan', label: t('skillsStudio.fix.action.rescan'), kind: 'primary' }];
|
|
553
|
+
const answer = await ctx.ask({ title: t('skillsStudio.library.diagnosticsTitle'), lines: [t('skillsStudio.library.diagnosticsHelp'), t('skillsStudio.fix.explain.unreadable')], extra: list, buttons, cancelId: 'ok', wide: true });
|
|
554
|
+
if (answer === 'dismiss') { ctx.dismissIssues([unreadableFingerprint(diag.length)]); ctx.toast(t('skillsStudio.fix.dismissed')); return; }
|
|
555
|
+
if (answer !== 'rescan') return;
|
|
556
|
+
const gen = ctx.gen();
|
|
557
|
+
try {
|
|
558
|
+
await ctx.api.rescanSkillsCatalog();
|
|
559
|
+
if (!ctx.alive(gen)) return;
|
|
560
|
+
ctx.toast(t('skillsStudio.fix.rescanned'));
|
|
561
|
+
} catch (err) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.fix.failed', { error: studioErrorText(ctx.t, err) }), 'error'); }
|
|
562
|
+
if (ctx.alive(gen)) await ctx.reload();
|
|
519
563
|
}
|
|
520
564
|
|
|
521
565
|
async function reopenKept(ctx) {
|
|
@@ -581,7 +625,7 @@ function bulkExport(ctx) {
|
|
|
581
625
|
ctx.toast(ctx.tp('skillsStudio.library.bulk.exported', n));
|
|
582
626
|
}
|
|
583
627
|
|
|
584
|
-
async function bulkDelete(ctx) {
|
|
628
|
+
export async function bulkDelete(ctx) {
|
|
585
629
|
const { t, tp, api } = ctx;
|
|
586
630
|
const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key)).map((r) => ({ row: r, s: subjectOf(r, { caps: ctx.caps }) }))
|
|
587
631
|
.map((x) => ({ ...x, kind: deleteKind(ctx, x.row, x.s) })).filter((x) => x.kind);
|
|
@@ -590,32 +634,25 @@ async function bulkDelete(ctx) {
|
|
|
590
634
|
const inLibrary = chosen.filter((x) => x.kind === 'library');
|
|
591
635
|
// Unsaved text in an open workspace of one of them goes with it only when the owner says so.
|
|
592
636
|
const unsaved = chosen.filter((x) => ctx.workspaces.get(x.row.key)?.tabs.some((tab) => tab.dirty));
|
|
593
|
-
const
|
|
594
|
-
box
|
|
595
|
-
const label = document.createElement('label');
|
|
596
|
-
label.className = 'sx-field-label';
|
|
597
|
-
label.htmlFor = 'sx-del-confirm';
|
|
598
|
-
label.textContent = t('skillsStudio.library.bulk.deleteType', { word: t('skillsStudio.library.bulk.deleteWord') });
|
|
599
|
-
const input = document.createElement('input');
|
|
600
|
-
input.id = 'sx-del-confirm';
|
|
601
|
-
input.className = 'sx-input';
|
|
602
|
-
input.autocomplete = 'off';
|
|
603
|
-
const err = document.createElement('p');
|
|
604
|
-
err.className = 'ss-dlg-error';
|
|
605
|
-
err.setAttribute('role', 'alert');
|
|
606
|
-
box.append(label, input, err);
|
|
637
|
+
const word = t('skillsStudio.library.bulk.deleteWord');
|
|
638
|
+
const { box, input, err } = deleteConfirmBox(ctx, chosen, { onDisk, inLibrary, unsaved, word });
|
|
607
639
|
const answer = await ctx.ask({
|
|
608
640
|
title: tp('skillsStudio.library.bulk.deleteTitle', chosen.length),
|
|
609
|
-
// Two kinds, said apart: what is removed from where it is, and what only leaves the library.
|
|
610
|
-
lines: [
|
|
611
|
-
...(onDisk.length ? [t('skillsStudio.library.bulk.deleteExplain'), ...onDisk.slice(0, 8).map((x) => `• ${x.row.label} (${x.row.path || ''})`)] : []),
|
|
612
|
-
...(inLibrary.length ? [t('skillsStudio.library.bulk.deleteLibraryExplain'), ...inLibrary.slice(0, 8).map((x) => `• ${x.row.label}`)] : []),
|
|
613
|
-
...(unsaved.length ? [tp('skillsStudio.library.bulk.deleteUnsaved', unsaved.length)] : []),
|
|
614
|
-
],
|
|
615
641
|
extra: box,
|
|
616
|
-
|
|
642
|
+
className: 'ss-dlg--confirm',
|
|
643
|
+
buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'delete', label: t('skillsStudio.library.bulk.deleteConfirm'), kind: 'danger', disabled: true }],
|
|
617
644
|
focus: 'cancel',
|
|
618
|
-
|
|
645
|
+
// Delete stays off until the word is typed; Enter in the field answers once it is.
|
|
646
|
+
onOpen: (ui) => {
|
|
647
|
+
input.addEventListener('input', () => { ui.setDisabled('delete', !deleteWordMatches(input.value, word)); if (deleteWordMatches(input.value, word)) err.textContent = ''; });
|
|
648
|
+
input.addEventListener('keydown', (e) => {
|
|
649
|
+
if (e.key !== 'Enter' || e.isComposing) return;
|
|
650
|
+
e.preventDefault();
|
|
651
|
+
if (deleteWordMatches(input.value, word)) ui.done('delete'); else err.textContent = t('skillsStudio.library.bulk.deleteTypeError');
|
|
652
|
+
});
|
|
653
|
+
input.focus({ preventScroll: true });
|
|
654
|
+
},
|
|
655
|
+
accept: (id) => { if (id === 'delete' && !deleteWordMatches(input.value, word)) { err.textContent = t('skillsStudio.library.bulk.deleteTypeError'); return false; } return true; },
|
|
619
656
|
});
|
|
620
657
|
if (answer !== 'delete') return;
|
|
621
658
|
const gen = ctx.gen();
|
|
@@ -635,5 +672,77 @@ async function bulkDelete(ctx) {
|
|
|
635
672
|
await ctx.reload();
|
|
636
673
|
}
|
|
637
674
|
|
|
675
|
+
/** Whether what was typed is the confirmation word (case and surrounding spaces aside). */
|
|
676
|
+
export function deleteWordMatches(value, word) {
|
|
677
|
+
return String(value ?? '').trim().toLocaleLowerCase() === String(word ?? '').trim().toLocaleLowerCase() && !!String(word ?? '').trim();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* The body of the delete question: one tidy row per item (its name, its kind, where it is, shortened, with the full
|
|
682
|
+
* path on hover, and what happens to it), what that means, and the field the word is typed in.
|
|
683
|
+
*/
|
|
684
|
+
export function deleteConfirmBox(ctx, chosen, { onDisk = [], inLibrary = [], unsaved = [], word }) {
|
|
685
|
+
const { t, tp } = ctx;
|
|
686
|
+
const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text !== undefined) n.textContent = text; return n; };
|
|
687
|
+
const box = el('div', 'sx-del');
|
|
688
|
+
const list = el('ul', 'sx-del-list');
|
|
689
|
+
list.setAttribute('aria-label', t('skillsStudio.library.bulk.deleteListLabel'));
|
|
690
|
+
const SHOWN = 6;
|
|
691
|
+
for (const x of chosen.slice(0, SHOWN)) {
|
|
692
|
+
const li = el('li', 'sx-del-item');
|
|
693
|
+
const icon = el('span', 'sx-del-icon');
|
|
694
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
695
|
+
setHTML(icon, x.row.kind === 'skill' ? ICONS.folder : ICONS.file);
|
|
696
|
+
const main = el('span', 'sx-del-main');
|
|
697
|
+
main.append(el('span', 'sx-del-name', x.row.label));
|
|
698
|
+
const where = x.kind === 'disk' ? shortPath(x.row.path || '') : t('skillsStudio.library.bulk.deleteInLibrary');
|
|
699
|
+
const path = el('span', 'sx-del-path', where);
|
|
700
|
+
if (x.kind === 'disk' && x.row.path) path.title = x.row.path;
|
|
701
|
+
main.append(path);
|
|
702
|
+
const kind = el('span', 'sx-del-kind', kindLabel(x.row, t));
|
|
703
|
+
const effect = el('span', `sx-del-effect sx-del-effect--${x.kind}`, x.kind === 'disk' ? t('skillsStudio.library.bulk.deleteEffectDisk') : t('skillsStudio.library.bulk.deleteEffectLibrary'));
|
|
704
|
+
li.append(icon, main, kind, effect);
|
|
705
|
+
list.append(li);
|
|
706
|
+
}
|
|
707
|
+
if (chosen.length > SHOWN) list.append(el('li', 'sx-del-more', t('skillsStudio.library.bulk.deleteMore', { count: chosen.length - SHOWN })));
|
|
708
|
+
box.append(list);
|
|
709
|
+
// Two kinds, said apart: what is removed from where it is (for good), and what only leaves the library.
|
|
710
|
+
if (onDisk.length) {
|
|
711
|
+
const note = el('p', 'sx-del-note sx-del-note--danger');
|
|
712
|
+
setHTML(note, html`${ICONS.warn}<span>${t('skillsStudio.library.bulk.deleteExplain')}</span>`);
|
|
713
|
+
box.append(note);
|
|
714
|
+
}
|
|
715
|
+
if (inLibrary.length) {
|
|
716
|
+
const note = el('p', 'sx-del-note');
|
|
717
|
+
setHTML(note, html`${ICONS.info}<span>${t('skillsStudio.library.bulk.deleteLibraryExplain')}</span>`);
|
|
718
|
+
box.append(note);
|
|
719
|
+
}
|
|
720
|
+
if (unsaved.length) {
|
|
721
|
+
const note = el('p', 'sx-del-note sx-del-note--danger');
|
|
722
|
+
setHTML(note, html`${ICONS.draft}<span>${tp('skillsStudio.library.bulk.deleteUnsaved', unsaved.length)}</span>`);
|
|
723
|
+
box.append(note);
|
|
724
|
+
}
|
|
725
|
+
const field = el('div', 'sx-del-field');
|
|
726
|
+
const label = el('label', 'sx-del-label');
|
|
727
|
+
label.htmlFor = 'sx-del-confirm';
|
|
728
|
+
// "Type {word} to confirm", with the word set apart as what is to be typed.
|
|
729
|
+
const sentence = t('skillsStudio.library.bulk.deleteType', { word: '\u0000' });
|
|
730
|
+
const [before, after = ''] = sentence.split('\u0000');
|
|
731
|
+
label.append(before, el('code', 'sx-del-word', word), after);
|
|
732
|
+
const input = el('input', 'sx-del-input');
|
|
733
|
+
input.id = 'sx-del-confirm';
|
|
734
|
+
input.type = 'text';
|
|
735
|
+
input.autocomplete = 'off';
|
|
736
|
+
input.spellcheck = false;
|
|
737
|
+
input.setAttribute('autocapitalize', 'off');
|
|
738
|
+
input.setAttribute('aria-describedby', 'sx-del-error');
|
|
739
|
+
const err = el('p', 'ss-dlg-error');
|
|
740
|
+
err.id = 'sx-del-error';
|
|
741
|
+
err.setAttribute('role', 'alert');
|
|
742
|
+
field.append(label, input, err);
|
|
743
|
+
box.append(field);
|
|
744
|
+
return { box, input, err };
|
|
745
|
+
}
|
|
746
|
+
|
|
638
747
|
/** The P0 id of a row's entry (exported for the workspace's Export of an agent or a command). */
|
|
639
748
|
export const v1ExportId = (entry) => (entry?.path ? encodeArtifactId(`${entry.kind}:${entry.path}`) : null);
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// Health is a list of codes with a severity (err, warn, info, off, ok), never a colour alone: the view writes the
|
|
19
19
|
// words (i18n `skillsStudio.health.*`). No DOM and no imports.
|
|
20
20
|
|
|
21
|
-
export const RUNTIMES = Object.freeze(['claude', 'codex', 'opencode']);
|
|
21
|
+
export const RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
|
|
22
22
|
export const SORTS = Object.freeze(['attention', 'name', 'updated', 'kind', 'source']);
|
|
23
23
|
export const KINDS = Object.freeze(['skill', 'agent', 'command']);
|
|
24
24
|
export const SOURCES = Object.freeze(['mine', 'project', 'synabun', 'plugin', 'account', 'system', 'shared', 'library']);
|
|
@@ -322,6 +322,10 @@ function finishRow(row) {
|
|
|
322
322
|
row.label = revealHidden(row.name || row.folderName || '');
|
|
323
323
|
row.shownDescription = revealHidden(row.description || '');
|
|
324
324
|
row.runtimes = runtimeStates(row);
|
|
325
|
+
// A runtime that lists two copies of this very row (SynaBun installed one library skill into ~/.claude/skills and
|
|
326
|
+
// ~/.config/opencode/skills, both read by OpenCode) has no other skill to confuse it with: no "duplicate".
|
|
327
|
+
const own = new Set(row.entries.map((e) => e.id));
|
|
328
|
+
row.collisions = row.collisions.filter((c) => !(Array.isArray(c.with) && c.with.length && c.with.every((id) => own.has(id))));
|
|
325
329
|
row.health = healthOf(row);
|
|
326
330
|
row.attention = row.health.reduce((n, h) => n + (ATTENTION[h.code] || 0) * (h.code === 'invalid' || h.code === 'warnings' ? Math.max(1, Math.min(9, num(h.count))) : 1), 0);
|
|
327
331
|
row.needsAttention = row.attention > 0;
|
|
@@ -338,7 +342,7 @@ export function healthMatches(row, health) {
|
|
|
338
342
|
case 'invalid': return codes.has('invalid') || codes.has('not-text');
|
|
339
343
|
case 'warnings': return codes.has('warnings');
|
|
340
344
|
case 'update': return codes.has('update');
|
|
341
|
-
case 'duplicates': return codes.has('shadowed') || codes.has('twice') || codes.has('same-description');
|
|
345
|
+
case 'duplicates': return codes.has('shadowed') || codes.has('twice') || codes.has('same-description') || codes.has('duplicate-record');
|
|
342
346
|
case 'drafts': return codes.has('draft');
|
|
343
347
|
case 'readonly': return codes.has('readonly');
|
|
344
348
|
case 'ok': return codes.has('ok');
|
|
@@ -25,6 +25,7 @@ import { askInPanel, dialogOpen, closeDialog } from '../ss-dialog.js';
|
|
|
25
25
|
import { closeDropdowns } from '../../cc-dropdown.js';
|
|
26
26
|
import * as api from '../../api.js';
|
|
27
27
|
import { readWholeCatalog, buildRows, countRows, projectsOf, keepSelection } from './sx-library.js';
|
|
28
|
+
import { applyFixState, readDismissed, addDismissed, removeDismissed, DISMISSED_KEY } from './sx-fixes.js';
|
|
28
29
|
import { initialRect, parseOpen } from './sx-geometry.js';
|
|
29
30
|
import { frameRect } from '../../app-frame.js';
|
|
30
31
|
import { updateCards, reviewCount } from './sx-review.js';
|
|
@@ -134,6 +135,9 @@ function deriveRows() {
|
|
|
134
135
|
// Review cards and the rail's count all leave a kept update out.
|
|
135
136
|
const kept = readJson(KEPT_UPDATES_KEY, {});
|
|
136
137
|
S.rows = buildRows({ entries: d.catalog.items, skills: d.skills, status: d.status, reviews: d.reviews.filter((r) => ['pending', 'changes_requested', 'approved', 'partial', 'publish_failed'].includes(r.state)), upstream: d.upstream, kept });
|
|
138
|
+
// The problems only the fix system finds (a second library record, an older installation), and the ones the owner
|
|
139
|
+
// dismissed taken out (sx-fixes.js), before anything counts them.
|
|
140
|
+
applyFixState(S.rows, readDismissed(storage.getItem(DISMISSED_KEY)));
|
|
137
141
|
S.counts = countRows(S.rows);
|
|
138
142
|
S.counts.views.archived = Array.isArray(d.archived) ? d.archived.length : 0;
|
|
139
143
|
S.projects = projectsOf(d.catalog.roots, S.rows);
|
|
@@ -174,7 +178,7 @@ export async function openStudio(detail = {}) {
|
|
|
174
178
|
S.opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
175
179
|
S.docked = storage.getItem(DOCK_KEY) === 'true';
|
|
176
180
|
S.dim = storage.getItem(DIM_KEY) === 'true' && !S.docked;
|
|
177
|
-
S.lib = { ...S.lib, ...readJson(LIB_KEY, {}), selected: new Set(), focus: null, installNotice: null };
|
|
181
|
+
S.lib = { ...S.lib, ...readJson(LIB_KEY, {}), selected: new Set(), focus: null, installNotice: null, fixNotice: null };
|
|
178
182
|
S.backdrop = document.createElement('div');
|
|
179
183
|
S.backdrop.className = `studio-backdrop sx-backdrop${S.dim ? '' : ' backdrop-hidden'}`;
|
|
180
184
|
document.body.appendChild(S.backdrop);
|
|
@@ -538,7 +542,7 @@ function runtimeFootHTML() {
|
|
|
538
542
|
if (own.some((r) => r.status === 'ok')) return 'ready';
|
|
539
543
|
return own.length ? 'empty' : 'unknown';
|
|
540
544
|
};
|
|
541
|
-
const rows = ['claude', 'codex', 'opencode'].map((rt) => {
|
|
545
|
+
const rows = ['claude', 'codex', 'opencode', 'antigravity'].map((rt) => {
|
|
542
546
|
const st = state(rt);
|
|
543
547
|
const name = t(`skillsStudio.runtime.${rt}`);
|
|
544
548
|
const label = st === 'bridge' ? t('skillsStudio.rail.runtimeFix', { runtime: name }) : `${name}: ${t(`skillsStudio.rail.runtimeState.${st}`)}`;
|
|
@@ -673,6 +677,11 @@ function ctx() {
|
|
|
673
677
|
keepUpdate: (name, revisionId) => { const kept = readJson(KEPT_UPDATES_KEY, {}); kept[name] = revisionId; storage.setItem(KEPT_UPDATES_KEY, JSON.stringify(kept)); deriveRows(); },
|
|
674
678
|
saveLibraryView: () => storage.setItem(LIB_KEY, JSON.stringify({ view: S.lib.view, kind: S.lib.kind, source: S.lib.source, runtime: S.lib.runtime, health: S.lib.health, sort: S.lib.sort })),
|
|
675
679
|
keptDrafts: () => keptDrafts(),
|
|
680
|
+
// Problems the owner has seen and leaves as they are (sx-fixes.js fingerprints): kept, and said again when they change.
|
|
681
|
+
dismissed: () => readDismissed(storage.getItem(DISMISSED_KEY)),
|
|
682
|
+
dismissIssues: (fingerprints) => { storage.setItem(DISMISSED_KEY, addDismissed(storage.getItem(DISMISSED_KEY), fingerprints)); deriveRows(); render({ keepFocus: true }); },
|
|
683
|
+
restoreIssues: (fingerprints) => { storage.setItem(DISMISSED_KEY, removeDismissed(storage.getItem(DISMISSED_KEY), fingerprints)); deriveRows(); render({ keepFocus: true }); },
|
|
684
|
+
openSkillFolders,
|
|
676
685
|
workspaces: S.workspaces,
|
|
677
686
|
closeWorkspace: (ws) => { disposeWorkspace(ws); S.workspaces.delete(ws.key); if (S.ws === ws) { S.ws = null; S.place = 'library'; render(); } },
|
|
678
687
|
key: idempotencyKey,
|
|
@@ -51,7 +51,7 @@ function destinationHTML(ctx, card, dest) {
|
|
|
51
51
|
// One tile per runtime, as Publish shows them: where this card's install goes (Codex and OpenCode are first-class).
|
|
52
52
|
if (dest.targets.length) {
|
|
53
53
|
const picked = (rt) => [...dest.keys].some((k) => String(k).startsWith(`${rt}:`));
|
|
54
|
-
lines.push(html`<li class="sx-dest-tiles" role="group" aria-label="${t('skillsStudio.review.destTilesLabel')}">${['claude', 'codex', 'opencode'].map((rt) => html`<span class="sx-dest-tile${picked(rt) ? ' is-on' : ''}" title="${picked(rt) ? t('skillsStudio.review.destTileOn', { runtime: t(`skillsStudio.runtime.${rt}`) }) : t('skillsStudio.review.destTileOff', { runtime: t(`skillsStudio.runtime.${rt}`) })}">${picked(rt) ? ICONS.ok : ''}<span>${t(`skillsStudio.runtime.${rt}`)}</span></span>`)}</li>`);
|
|
54
|
+
lines.push(html`<li class="sx-dest-tiles" role="group" aria-label="${t('skillsStudio.review.destTilesLabel')}">${['claude', 'codex', 'opencode', 'antigravity'].map((rt) => html`<span class="sx-dest-tile${picked(rt) ? ' is-on' : ''}" title="${picked(rt) ? t('skillsStudio.review.destTileOn', { runtime: t(`skillsStudio.runtime.${rt}`) }) : t('skillsStudio.review.destTileOff', { runtime: t(`skillsStudio.runtime.${rt}`) })}">${picked(rt) ? ICONS.ok : ''}<span>${t(`skillsStudio.runtime.${rt}`)}</span></span>`)}</li>`);
|
|
55
55
|
}
|
|
56
56
|
return html`${lines}`;
|
|
57
57
|
}
|
|
@@ -142,7 +142,7 @@ export function sameAsk(a, b) {
|
|
|
142
142
|
return x.length > 0 && x.length === y.length && x.every((k, i) => k === y[i]);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
const RUNTIME_OF_FAMILY = { claude: 'claude', codex: 'codex', opencode: 'opencode', shared: 'agents' };
|
|
145
|
+
const RUNTIME_OF_FAMILY = { claude: 'claude', codex: 'codex', opencode: 'opencode', antigravity: 'antigravity', shared: 'agents' };
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
148
|
* The targets a skill can be installed into: each runtime's own skills folder for each account, the shared
|
|
@@ -175,12 +175,13 @@ export function installTargets({ roots = [], config = {}, projects = [] } = {})
|
|
|
175
175
|
for (const runtime of ['claude', 'codex', 'opencode']) {
|
|
176
176
|
const target = { runtime, scope: 'project', projectPath: p.path };
|
|
177
177
|
const sub = runtime === 'claude' ? '.claude/skills' : runtime === 'codex' ? '.agents/skills' : '.opencode/skills';
|
|
178
|
-
// Who reads a project's folders (lib/skills/roots.js): .claude/skills and .agents/skills are read by OpenCode too
|
|
179
|
-
|
|
178
|
+
// Who reads a project's folders (lib/skills/roots.js): .claude/skills and .agents/skills are read by OpenCode too,
|
|
179
|
+
// .agents/skills by Antigravity as well (it has no project folder of its own).
|
|
180
|
+
const readers = runtime === 'opencode' ? ['opencode'] : runtime === 'codex' ? ['codex', 'opencode', 'antigravity'] : [runtime, 'opencode'];
|
|
180
181
|
push({ key: targetKey(target), target, runtime, scope: 'project', accountId: 'default', projectPath: p.path, projectLabel: p.label || p.path, path: `${p.path.replace(/[\\/]+$/, '')}/${sub}`, status: null, policy: null, shared: false, off: false, runtimes: readers });
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
|
-
const order = { claude: 0, codex: 1, opencode: 2,
|
|
184
|
+
const order = { claude: 0, codex: 1, opencode: 2, antigravity: 3, agents: 4 };
|
|
184
185
|
return out.sort((a, b) => (a.scope === b.scope ? 0 : a.scope === 'user' ? -1 : 1) || (a.projectLabel || '').localeCompare(b.projectLabel || '') || (order[a.runtime] - order[b.runtime]) || a.accountId.localeCompare(b.accountId));
|
|
185
186
|
}
|
|
186
187
|
|
|
@@ -221,11 +222,11 @@ export function defaultTargets(targets, { skill = null, row = null, assistants =
|
|
|
221
222
|
const there = targets.filter((t) => !t.off && ['installed', 'outdated', 'present'].includes(targetState(t, { skill, row }).state));
|
|
222
223
|
if (there.length) return new Set(there.map((t) => t.key));
|
|
223
224
|
const own = (runtime) => targets.find((t) => t.runtime === runtime && t.scope === 'user' && t.accountId === 'default' && !t.off) || null;
|
|
224
|
-
const wanted = Array.isArray(assistants) && assistants.length ? assistants.filter((r) => ['claude', 'codex', 'opencode'].includes(r)) : ['claude'];
|
|
225
|
+
const wanted = Array.isArray(assistants) && assistants.length ? assistants.filter((r) => ['claude', 'codex', 'opencode', 'antigravity'].includes(r)) : ['claude'];
|
|
225
226
|
// Every assistant gets its own folder: a terminal OpenCode reads Claude Code's folder, but SynaBun's own OpenCode
|
|
226
227
|
// serves (the panel, an Assistant brain) start with OPENCODE_DISABLE_CLAUDE_CODE=1 and never see it (checked with
|
|
227
228
|
// opencode 2.1.4), so an OpenCode among the assistants is ticked for a real copy.
|
|
228
|
-
const picked = ['claude', 'codex', 'opencode'].filter((runtime) => wanted.includes(runtime)).map(own).filter(Boolean);
|
|
229
|
+
const picked = ['claude', 'codex', 'opencode', 'antigravity'].filter((runtime) => wanted.includes(runtime)).map(own).filter(Boolean);
|
|
229
230
|
if (!picked.length) { const claude = own('claude'); return new Set(claude ? [claude.key] : []); }
|
|
230
231
|
return new Set(picked.map((t) => t.key));
|
|
231
232
|
}
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import { studioErrorText } from './sx-errors.js';
|
|
14
14
|
import { html, setHTML, ICONS } from './sx-dom.js';
|
|
15
15
|
import { loadWorkspace, dirtyTabs, saveVersion } from './sx-docs.js';
|
|
16
|
-
import {
|
|
16
|
+
import { sourceLabel, statusPills, deleteKind, bulkDelete } from './sx-library-view.js';
|
|
17
|
+
import { openFixPanel } from './sx-fixes-view.js';
|
|
17
18
|
import { installStatus } from './sx-install.js';
|
|
18
19
|
import { reviewTargets, installFlow, flowWords, installBusy, acquireInstall, alsoInstall, alsoInstallNow } from './sx-install-flow.js';
|
|
19
20
|
import { changedFiles } from './sx-diff.js';
|
|
@@ -117,7 +118,7 @@ function bannersHTML(ctx, ws) {
|
|
|
117
118
|
const also = ['installed', 'outdated'].includes(status?.code) ? alsoInstall(ctx, { skill: ws.skill || row.skill, row }) : { targets: [] };
|
|
118
119
|
if (also.targets.length) {
|
|
119
120
|
const busy = installBusy((ws.skill || row.skill)?.skillId);
|
|
120
|
-
const where = [...new Set(status.where.map((w) => t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`)))].join(' · ');
|
|
121
|
+
const where = [...new Set(status.where.map((w) => t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`)))].join(' · ');
|
|
121
122
|
out.push(banner('info', ICONS.publish, t('skillsStudio.ws.banner.alsoInstall', { where, targets: also.words }),
|
|
122
123
|
html`<button type="button" class="sx-btn sx-btn--primary" data-ws="also-install" id="sx-ws-also-install"${busy ? ' disabled' : ''} aria-disabled="${busy ? 'true' : 'false'}">${ICONS.publish}<span>${busy ? t('skillsStudio.review.installing') : t('skillsStudio.ws.alsoInstallFor', { targets: also.words })}</span></button>`));
|
|
123
124
|
}
|
|
@@ -163,7 +164,6 @@ function headHTML(ctx, ws) {
|
|
|
163
164
|
// A library skill shows one status (it opens Publish) and its health only when something is wrong; the rest shows
|
|
164
165
|
// where it comes from and its health.
|
|
165
166
|
const status = installStatus({ skill: ws.skill || row.skill, row });
|
|
166
|
-
const worst = row.health[0] || { sev: 'ok' };
|
|
167
167
|
return html`<div class="sx-ws-head">
|
|
168
168
|
<nav class="sx-crumb" aria-label="${t('skillsStudio.ws.breadcrumb')}">
|
|
169
169
|
<button type="button" class="sx-link" data-ws="back">${ICONS.back}<span>${t('skillsStudio.rail.library')}</span></button>
|
|
@@ -172,8 +172,8 @@ function headHTML(ctx, ws) {
|
|
|
172
172
|
<div class="sx-ws-title">
|
|
173
173
|
<h3 class="sx-ws-name" id="sx-ws-name" tabindex="-1">${ws.label}</h3>
|
|
174
174
|
<span class="sx-tag">${kind}</span>
|
|
175
|
-
${status ?
|
|
176
|
-
${
|
|
175
|
+
${status ? '' : html`<span class="sx-tag" title="${src.detail}">${src.label}${src.detail ? html` · ${src.detail}` : ''}</span>`}
|
|
176
|
+
${statusPills(row, ctx, { status, button: s.canPublish ? 'go-publish' : null, id: 'sx-ws-status', fix: 'workspace' })}
|
|
177
177
|
<span class="sx-spacer"></span>
|
|
178
178
|
${s.editable ? html`<button type="button" class="sx-btn" data-ws="save" id="sx-save"${dirty ? '' : ' disabled'} aria-keyshortcuts="${ctx.mac ? 'Meta+S' : 'Control+S'}">${t('skillsStudio.ws.save')} <kbd>${ctx.mac ? '⌘S' : 'Ctrl+S'}</kbd></button>` : ''}
|
|
179
179
|
<button type="button" class="sx-icon-btn" data-ws="more" aria-haspopup="menu" aria-expanded="false" aria-label="${t('skillsStudio.ws.more')}" title="${t('skillsStudio.ws.more')}">${ICONS.more}</button>
|
|
@@ -292,6 +292,7 @@ async function onAction(act, btn, ctx, ws) {
|
|
|
292
292
|
case 'retry': ws.error = null; ws.loaded = false; ctx.render(); return;
|
|
293
293
|
case 'save': saveActive(ctx, ws); return;
|
|
294
294
|
case 'go-publish': switchTab(ctx, ws, 'publish'); return;
|
|
295
|
+
case 'fix-issues': if (ws.row) openFixPanel(ctx, ws.row, { deleteKind, bulkDelete }); return;
|
|
295
296
|
case 'more': openMore(btn, ctx, ws); return;
|
|
296
297
|
case 'customize': customizeCopy(ctx, ws); return;
|
|
297
298
|
case 'adopt': adoptNow(ctx, ws); return;
|
|
@@ -46,7 +46,7 @@ export const state = {
|
|
|
46
46
|
linkedSessionIds: new Set(), // session IDs currently in an active link
|
|
47
47
|
|
|
48
48
|
// ── Side panel routing ──
|
|
49
|
-
lastActivePanel: 'claude', // 'claude' | 'codex' | 'opencode' | 'assistant' — used by whiteboard "Send to Panel"
|
|
49
|
+
lastActivePanel: 'claude', // 'claude' | 'codex' | 'opencode' | 'antigravity' | 'assistant' — used by whiteboard "Send to Panel"
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Promote complete Markdown blocks once; only the unfinished tail is rebuilt.
|
|
2
|
+
// A final whole-document parse resolves cross-block constructs (link references,
|
|
3
|
+
// loose lists). The conservative boundary follows the Claude panel's live path.
|
|
4
|
+
export function findStableMarkdownBoundary(text, from = 0, minTail = 120) {
|
|
5
|
+
let best = from, pos = from, prevBlank = false;
|
|
6
|
+
let fenceChar = '', fenceLen = 0;
|
|
7
|
+
const limit = text.length - minTail;
|
|
8
|
+
while (pos < limit) {
|
|
9
|
+
const nl = text.indexOf('\n', pos);
|
|
10
|
+
if (nl === -1) break;
|
|
11
|
+
const line = text.slice(pos, nl);
|
|
12
|
+
const trimmed = line.replace(/^[ \t]+/, '');
|
|
13
|
+
const fence = /^(`{3,}|~{3,})/.exec(trimmed);
|
|
14
|
+
if (fence) {
|
|
15
|
+
if (!fenceChar) { fenceChar = fence[1][0]; fenceLen = fence[1].length; }
|
|
16
|
+
else if (fence[1][0] === fenceChar && fence[1].length >= fenceLen && /^\s*$/.test(trimmed.slice(fence[1].length))) fenceChar = '';
|
|
17
|
+
}
|
|
18
|
+
const lineStart = pos;
|
|
19
|
+
const blank = trimmed === '';
|
|
20
|
+
pos = nl + 1;
|
|
21
|
+
if (fenceChar) { prevBlank = false; continue; }
|
|
22
|
+
if (prevBlank && !blank && lineStart <= limit) {
|
|
23
|
+
if (!/^[ \t]/.test(line) && !/^([-*+] |\d{1,9}[.)] |>|\|)/.test(trimmed)) best = lineStart;
|
|
24
|
+
}
|
|
25
|
+
prevBlank = blank;
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const prefixes = new WeakMap();
|
|
31
|
+
|
|
32
|
+
// `render` sanitizes into its destination, exactly as for a finished message.
|
|
33
|
+
export function renderStreamingMarkdown(body, text, render, { streaming = false } = {}) {
|
|
34
|
+
if (!streaming) { prefixes.delete(body); render(body, text); return; }
|
|
35
|
+
let state = prefixes.get(body);
|
|
36
|
+
if (!state || text.length < state.length) {
|
|
37
|
+
state = { length: 0, nodes: 0 };
|
|
38
|
+
prefixes.set(body, state);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const boundary = findStableMarkdownBoundary(text, state.length);
|
|
42
|
+
while (body.childNodes.length > state.nodes) body.lastChild.remove();
|
|
43
|
+
const fragment = body.ownerDocument.createElement('div');
|
|
44
|
+
if (boundary > state.length) {
|
|
45
|
+
render(fragment, text.slice(state.length, boundary));
|
|
46
|
+
body.append(...fragment.childNodes);
|
|
47
|
+
state.nodes = body.childNodes.length;
|
|
48
|
+
state.length = boundary;
|
|
49
|
+
}
|
|
50
|
+
if (text.length > state.length) {
|
|
51
|
+
render(fragment, text.slice(state.length));
|
|
52
|
+
body.append(...fragment.childNodes);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
prefixes.delete(body);
|
|
56
|
+
render(body, text);
|
|
57
|
+
}
|
|
58
|
+
}
|