@amoree-code/ocean 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +284 -2
- package/dist/application/auth/auth-orchestrator.js +77 -0
- package/dist/application/clients/clients.js +646 -0
- package/dist/application/clients/editors.js +398 -0
- package/dist/application/clients/hooks.js +335 -0
- package/dist/application/clients/native.js +410 -0
- package/dist/application/clients/opencode-plugin.js +158 -0
- package/dist/application/clients/registry.js +188 -0
- package/dist/application/gateway/webhook-gateway.js +137 -0
- package/dist/application/install/provider-installer.js +184 -0
- package/dist/application/integrations/charter-copies.js +76 -0
- package/dist/application/integrations/claude-vscode.js +106 -0
- package/dist/application/integrations/engine-entry-repair.js +105 -0
- package/dist/application/integrations/skill-filter.js +254 -0
- package/dist/application/layout/layout-apply.js +443 -0
- package/dist/application/layout/layout-plan.js +236 -0
- package/dist/application/layout/workspace-template.js +34 -0
- package/dist/application/onboarding/state.js +23 -0
- package/dist/application/onboarding/steps.js +249 -0
- package/dist/application/profiles/profile-distribution.js +53 -0
- package/dist/application/profiles/profile-suggestion.js +39 -0
- package/dist/application/profiles/profile-upgrade-service.js +208 -0
- package/dist/application/pull/history-index.js +476 -0
- package/dist/application/pull/project-match.js +132 -0
- package/dist/application/pull/pull.js +202 -0
- package/dist/application/pull/server-capture.js +307 -0
- package/dist/application/scheduler/local-scheduler.js +210 -0
- package/dist/composition/runtime.js +64 -0
- package/dist/engine-root.js +9 -0
- package/dist/infrastructure/filesystem/profile-loader.js +46 -0
- package/dist/infrastructure/filesystem/runtime-config-loader.js +29 -0
- package/dist/infrastructure/filesystem/skill-loader.js +84 -0
- package/dist/infrastructure/filesystem/workspace-capability.js +37 -0
- package/dist/infrastructure/observability/runtime-logger.js +16 -0
- package/dist/infrastructure/process/agent-tool-executor.js +99 -0
- package/dist/infrastructure/process/cli-process.js +49 -0
- package/dist/infrastructure/process/interactive-process.js +145 -0
- package/dist/infrastructure/process/process-supervisor.js +194 -0
- package/dist/infrastructure/process/service.js +252 -0
- package/dist/infrastructure/providers/browser-provider.js +2 -0
- package/dist/infrastructure/providers/claude-sdk.js +137 -0
- package/dist/infrastructure/providers/client-home.js +14 -0
- package/dist/infrastructure/providers/fake-browser-provider.js +146 -0
- package/dist/infrastructure/providers/ollama-embedder.js +74 -0
- package/dist/infrastructure/providers/openai-compatible.js +268 -0
- package/dist/infrastructure/providers/playwright-browser-provider.js +380 -0
- package/dist/infrastructure/providers/provider-capabilities.js +50 -0
- package/dist/infrastructure/providers/provider-invocation.js +184 -0
- package/dist/infrastructure/providers/provider-registry.js +77 -0
- package/dist/infrastructure/providers/providers.js +134 -0
- package/dist/infrastructure/wrappers/wrapper-manager.js +374 -0
- package/dist/interfaces/cli/auth-command.js +54 -0
- package/dist/interfaces/cli/browser-bridge-command.js +93 -0
- package/dist/interfaces/cli/browser-command.js +119 -0
- package/dist/interfaces/cli/capture-command.js +125 -0
- package/dist/interfaces/cli/client-test-command.js +133 -0
- package/dist/interfaces/cli/clients-command.js +129 -0
- package/dist/interfaces/cli/command-registry.js +1211 -0
- package/dist/interfaces/cli/context-command.js +172 -0
- package/dist/interfaces/cli/daily-command.js +131 -0
- package/dist/interfaces/cli/governance-command.js +55 -0
- package/dist/interfaces/cli/handoff-command.js +35 -0
- package/dist/interfaces/cli/help-command.js +207 -0
- package/dist/interfaces/cli/idea-command.js +47 -0
- package/dist/interfaces/cli/intercept-command.js +317 -0
- package/dist/interfaces/cli/layout-command.js +27 -0
- package/dist/interfaces/cli/market-command.js +52 -0
- package/dist/interfaces/cli/mcp-gateway-command.js +30 -0
- package/dist/interfaces/cli/memory-command.js +317 -0
- package/dist/interfaces/cli/migrate-command.js +26 -0
- package/dist/interfaces/cli/observe-command.js +78 -0
- package/dist/interfaces/cli/onboard-command.js +123 -0
- package/dist/interfaces/cli/operate-command.js +59 -0
- package/dist/interfaces/cli/profiles-command.js +70 -0
- package/dist/interfaces/cli/pull-command.js +54 -0
- package/dist/interfaces/cli/setup-command.js +143 -0
- package/dist/interfaces/cli/tasks-command.js +249 -0
- package/dist/main.js +49 -0
- package/dist/node-warnings.js +17 -0
- package/dist/version.js +8 -0
- package/market/mcp/chrome-devtools/ocean.capability.json +28 -0
- package/market/mcp/gateway/ocean.capability.json +21 -0
- package/market/mcp/obsidian/ocean.capability.json +21 -0
- package/market/mcp/ocean/ocean.capability.json +21 -0
- package/market/mcp/playwright/ocean.capability.json +23 -0
- package/node_modules/@ocean/brain/dist/brain/brain-eval.js +29 -0
- package/node_modules/@ocean/brain/dist/brain/brain-markdown.js +226 -0
- package/node_modules/@ocean/brain/dist/brain/brain-reindex.js +247 -0
- package/node_modules/@ocean/brain/dist/brain/brain-service.js +320 -0
- package/node_modules/@ocean/brain/dist/capture/inbox-sync.js +22 -0
- package/node_modules/@ocean/brain/dist/evidence/verification-report.js +13 -0
- package/node_modules/@ocean/brain/dist/infrastructure/brain/brain-index.js +285 -0
- package/node_modules/@ocean/brain/dist/memory/brain-dump.js +145 -0
- package/node_modules/@ocean/brain/dist/memory/daily-narrative.js +142 -0
- package/node_modules/@ocean/brain/dist/memory/index-sync.js +124 -0
- package/node_modules/@ocean/brain/dist/memory/lesson-activation.js +93 -0
- package/node_modules/@ocean/brain/dist/memory/lesson-learning.js +188 -0
- package/node_modules/@ocean/brain/dist/memory/lessons.js +321 -0
- package/node_modules/@ocean/brain/dist/memory/memory-audit.js +100 -0
- package/node_modules/@ocean/brain/dist/memory/memory-digest.js +49 -0
- package/node_modules/@ocean/brain/dist/memory/memory-harvest.js +345 -0
- package/node_modules/@ocean/brain/dist/memory/memory-paths.js +174 -0
- package/node_modules/@ocean/brain/dist/memory/memory-review.js +278 -0
- package/node_modules/@ocean/brain/dist/memory/memory-trash.js +152 -0
- package/node_modules/@ocean/brain/dist/memory/memory-write.js +216 -0
- package/node_modules/@ocean/brain/dist/memory/model-narrative.js +129 -0
- package/node_modules/@ocean/brain/dist/memory/profile-facts.js +103 -0
- package/node_modules/@ocean/brain/dist/memory/session-promotion.js +61 -0
- package/node_modules/@ocean/brain/dist/memory/session-summary.js +113 -0
- package/node_modules/@ocean/brain/dist/memory/signal-detection.js +66 -0
- package/node_modules/@ocean/brain/package.json +11 -0
- package/node_modules/@ocean/context/dist/context/context-compression.js +66 -0
- package/node_modules/@ocean/context/dist/context/context-cost.js +587 -0
- package/node_modules/@ocean/context/dist/context/context-ladder.js +363 -0
- package/node_modules/@ocean/context/dist/context/context-packet.js +213 -0
- package/node_modules/@ocean/context/dist/context/context-references.js +255 -0
- package/node_modules/@ocean/context/dist/context/intent-router.js +259 -0
- package/node_modules/@ocean/context/dist/context/resource-injection.js +36 -0
- package/node_modules/@ocean/context/package.json +11 -0
- package/node_modules/@ocean/core/dist/domain/brain/brain-record.js +22 -0
- package/node_modules/@ocean/core/dist/domain/brain/record-scope.js +22 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/browser-contract.js +64 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/capability-contract.js +16 -0
- package/node_modules/@ocean/core/dist/domain/capabilities/capability-manifest.js +72 -0
- package/node_modules/@ocean/core/dist/domain/context/context-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/context/context.js +34 -0
- package/node_modules/@ocean/core/dist/domain/context/project-id.js +30 -0
- package/node_modules/@ocean/core/dist/domain/evidence/evidence.js +16 -0
- package/node_modules/@ocean/core/dist/domain/mcp/mcp-contract.js +36 -0
- package/node_modules/@ocean/core/dist/domain/mcp/memory-contract.js +41 -0
- package/node_modules/@ocean/core/dist/domain/ports/brain-index-port.js +7 -0
- package/node_modules/@ocean/core/dist/domain/ports/browser-port.js +8 -0
- package/node_modules/@ocean/core/dist/domain/ports/embedder-port.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/platform-ports.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/runtime-ports.js +2 -0
- package/node_modules/@ocean/core/dist/domain/ports/session-store-port.js +2 -0
- package/node_modules/@ocean/core/dist/domain/process/process-events.js +5 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-distribution.js +19 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-policy.js +34 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-selection.js +73 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-upgrade.js +123 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/profiles/profile.js +218 -0
- package/node_modules/@ocean/core/dist/domain/profiles/runtime-config.js +30 -0
- package/node_modules/@ocean/core/dist/domain/providers/provider-capability.js +28 -0
- package/node_modules/@ocean/core/dist/domain/providers/provider.js +2 -0
- package/node_modules/@ocean/core/dist/domain/redaction/redaction.js +86 -0
- package/node_modules/@ocean/core/dist/domain/runs/agent-tools.js +92 -0
- package/node_modules/@ocean/core/dist/domain/runs/execution-plan.js +71 -0
- package/node_modules/@ocean/core/dist/domain/runs/run-contract.js +38 -0
- package/node_modules/@ocean/core/dist/domain/sessions/entry-contract.js +31 -0
- package/node_modules/@ocean/core/dist/domain/sessions/handoff.js +78 -0
- package/node_modules/@ocean/core/dist/domain/sessions/session.js +58 -0
- package/node_modules/@ocean/core/dist/domain/skills/default-skills.js +29 -0
- package/node_modules/@ocean/core/dist/domain/skills/skill-validator.js +5 -0
- package/node_modules/@ocean/core/dist/domain/skills/skill.js +11 -0
- package/node_modules/@ocean/core/dist/domain/tasks/task-id.js +85 -0
- package/node_modules/@ocean/core/dist/shared/fs-utils.js +212 -0
- package/node_modules/@ocean/core/dist/shared/local-date.js +16 -0
- package/node_modules/@ocean/core/dist/shared/paths.js +324 -0
- package/node_modules/@ocean/core/dist/shared/project-resolution.js +1065 -0
- package/node_modules/@ocean/core/dist/shared/task-reference.js +133 -0
- package/node_modules/@ocean/core/package.json +14 -0
- package/node_modules/@ocean/loop/dist/handoff/handoff-service.js +133 -0
- package/node_modules/@ocean/loop/dist/hooks/lifecycle-hooks.js +15 -0
- package/node_modules/@ocean/loop/dist/hooks/session-end-hook.js +183 -0
- package/node_modules/@ocean/loop/dist/hooks/session-start-hook.js +237 -0
- package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-store.js +690 -0
- package/node_modules/@ocean/loop/dist/infrastructure/persistence/session-validator.js +5 -0
- package/node_modules/@ocean/loop/dist/loops/iteration-graph.js +147 -0
- package/node_modules/@ocean/loop/dist/loops/task-loop-record.js +2 -0
- package/node_modules/@ocean/loop/dist/loops/task-loop.js +281 -0
- package/node_modules/@ocean/loop/dist/operations/memory-knowledge-operations.js +263 -0
- package/node_modules/@ocean/loop/dist/operations/operation-contract.js +227 -0
- package/node_modules/@ocean/loop/dist/operations/operation-scope.js +28 -0
- package/node_modules/@ocean/loop/dist/operations/project-operations.js +93 -0
- package/node_modules/@ocean/loop/dist/operations/record-io.js +65 -0
- package/node_modules/@ocean/loop/dist/operations/record-operations.js +45 -0
- package/node_modules/@ocean/loop/dist/operations/task-linker.js +40 -0
- package/node_modules/@ocean/loop/dist/operations/task-operations.js +279 -0
- package/node_modules/@ocean/loop/dist/operations/write-guard.js +157 -0
- package/node_modules/@ocean/loop/dist/runs/prompt-assembly.js +112 -0
- package/node_modules/@ocean/loop/dist/runs/run-agent.js +425 -0
- package/node_modules/@ocean/loop/dist/runs/run-authorization.js +24 -0
- package/node_modules/@ocean/loop/dist/runs/tool-loop.js +173 -0
- package/node_modules/@ocean/loop/dist/sessions/session-closeout.js +150 -0
- package/node_modules/@ocean/loop/dist/sessions/session-pointer.js +133 -0
- package/node_modules/@ocean/loop/dist/sessions/session-projects.js +26 -0
- package/node_modules/@ocean/loop/dist/sessions/session-retention.js +132 -0
- package/node_modules/@ocean/loop/dist/tasks/archive-tasks.js +210 -0
- package/node_modules/@ocean/loop/dist/tasks/checkpoint-task.js +31 -0
- package/node_modules/@ocean/loop/dist/tasks/claim-task.js +126 -0
- package/node_modules/@ocean/loop/dist/tasks/list-tasks.js +113 -0
- package/node_modules/@ocean/loop/dist/tasks/new-task.js +42 -0
- package/node_modules/@ocean/loop/dist/tasks/renumber-tasks.js +134 -0
- package/node_modules/@ocean/loop/dist/tasks/verification-readiness.js +78 -0
- package/node_modules/@ocean/loop/dist/tasks/verify-task.js +234 -0
- package/node_modules/@ocean/loop/package.json +11 -0
- package/node_modules/@ocean/market/dist/adapters/mcp-adapter.js +64 -0
- package/node_modules/@ocean/market/dist/adapters/owned-directory.js +89 -0
- package/node_modules/@ocean/market/dist/adapters/plugin-adapter.js +39 -0
- package/node_modules/@ocean/market/dist/adapters/skill-adapter.js +32 -0
- package/node_modules/@ocean/market/dist/capability/adapter.js +2 -0
- package/node_modules/@ocean/market/dist/capability/catalog.js +63 -0
- package/node_modules/@ocean/market/dist/capability/content-digest.js +40 -0
- package/node_modules/@ocean/market/dist/capability/registry.js +81 -0
- package/node_modules/@ocean/market/dist/install/default-marketplace.js +47 -0
- package/node_modules/@ocean/market/dist/install/lock.js +34 -0
- package/node_modules/@ocean/market/dist/install/marketplace-service.js +165 -0
- package/node_modules/@ocean/market/dist/mcp/browser-bridge.js +263 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-connection.js +67 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-gateway.js +317 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-launch.js +102 -0
- package/node_modules/@ocean/market/dist/mcp/mcp-runner.js +32 -0
- package/node_modules/@ocean/market/dist/security/description-scan.js +45 -0
- package/node_modules/@ocean/market/dist/security/permissions.js +57 -0
- package/node_modules/@ocean/market/dist/security/sandbox.js +74 -0
- package/node_modules/@ocean/market/dist/skills/core-skill-sync.js +113 -0
- package/node_modules/@ocean/market/dist/skills/signal-detection.js +84 -0
- package/node_modules/@ocean/market/dist/skills/skill-curation.js +123 -0
- package/node_modules/@ocean/market/dist/skills/skill-hub.js +220 -0
- package/node_modules/@ocean/market/dist/skills/task-observer.js +165 -0
- package/node_modules/@ocean/market/package.json +11 -0
- package/node_modules/@ocean/services/dist/browser/browser-service.js +124 -0
- package/node_modules/@ocean/services/dist/browser/browser-session.js +287 -0
- package/node_modules/@ocean/services/dist/browser/browser-task-runner.js +70 -0
- package/node_modules/@ocean/services/dist/doctor/workspace-doctor.js +547 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/call-ledger.js +114 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/mcp-client.js +162 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/obsidian-server.js +251 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/ocean-server.js +880 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/server-version.js +20 -0
- package/node_modules/@ocean/services/dist/infrastructure/mcp/tool-registry.js +29 -0
- package/node_modules/@ocean/services/dist/obsidian/conflict-log.js +68 -0
- package/node_modules/@ocean/services/dist/obsidian/inbox-promotion.js +80 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-discovery.js +113 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-ingestion.js +77 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-sync.js +73 -0
- package/node_modules/@ocean/services/dist/obsidian/vault-writer.js +75 -0
- package/node_modules/@ocean/services/package.json +11 -0
- package/ocean-marketplace.json +415 -0
- package/package.json +67 -4
- package/skills/core/business-logic/SKILL.md +22 -0
- package/skills/core/catch-up/SKILL.md +17 -0
- package/skills/core/core-thinking/SKILL.md +11 -0
- package/skills/core/design-thinking/SKILL.md +19 -0
- package/skills/core/graft/SKILL.md +156 -0
- package/skills/core/loop/SKILL.md +20 -0
- package/skills/core/session-handoff/SKILL.md +15 -0
- package/skills/core/use-browser/SKILL.md +69 -0
- package/skills/core/verification/SKILL.md +11 -0
- package/skills/index.json +56 -0
- package/templates/README.md +4 -0
- package/templates/profiles/default.json +51 -0
- package/templates/profiles/developer.json +51 -0
- package/templates/profiles/reviewer.json +51 -0
- package/templates/profiles/strategist.json +51 -0
- package/templates/workspace/00-inbox/INBOX.md +7 -0
- package/templates/workspace/00-inbox/candidates/.gitkeep +0 -0
- package/templates/workspace/00-inbox/native/.gitkeep +0 -0
- package/templates/workspace/01-daily/.gitkeep +0 -0
- package/templates/workspace/02-personal/CONFLICTS.md +7 -0
- package/templates/workspace/02-personal/MEMORY.md +15 -0
- package/templates/workspace/02-personal/goals/.gitkeep +0 -0
- package/templates/workspace/02-personal/identity/.gitkeep +0 -0
- package/templates/workspace/02-personal/preferences/.gitkeep +0 -0
- package/templates/workspace/03-professional/.gitkeep +0 -0
- package/templates/workspace/04-projects/PROJECTS.md +5 -0
- package/templates/workspace/04-projects/_template/docs/.gitkeep +0 -0
- package/templates/workspace/04-projects/_template/memory/.gitkeep +0 -0
- package/templates/workspace/04-projects/_template/tasks/.gitkeep +0 -0
- package/templates/workspace/05-knowledge/KNOWLEDGE.md +8 -0
- package/templates/workspace/05-knowledge/decisions/.gitkeep +0 -0
- package/templates/workspace/05-knowledge/lessons/.gitkeep +0 -0
- package/templates/workspace/06-templates/TEMPLATES.md +25 -0
- package/templates/workspace/06-templates/decision.md +8 -0
- package/templates/workspace/06-templates/intelligence.md +32 -0
- package/templates/workspace/06-templates/project-claude.md +48 -0
- package/templates/workspace/06-templates/session.md +38 -0
- package/templates/workspace/06-templates/task.md +42 -0
- package/templates/workspace/MANIFEST.json +51 -0
- package/templates/workspace/bridge/config/CONFIG.md +21 -0
- package/templates/workspace/bridge/config/runtime.json +4 -0
- package/templates/workspace/charter/core.md +104 -0
- package/templates/workspace/charter/policies/README.md +31 -0
- package/templates/workspace/charter/policies/claude-code-context.md +90 -0
- package/templates/workspace/charter/policies/concise-output.md +29 -0
- package/templates/workspace/charter/policies/context.md +91 -0
- package/templates/workspace/charter/policies/contracts/task.md +189 -0
- package/templates/workspace/charter/policies/git.md +60 -0
- package/templates/workspace/charter/policies/graph.md +47 -0
- package/templates/workspace/charter/policies/knowledge.md +33 -0
- package/templates/workspace/charter/policies/lifecycle.md +119 -0
- package/templates/workspace/charter/policies/memory.md +67 -0
- package/templates/workspace/charter/policies/minimal-change.md +43 -0
- package/templates/workspace/charter/policies/models.md +57 -0
- package/templates/workspace/charter/policies/privacy-terms.txt +3 -0
- package/templates/workspace/charter/policies/response.md +39 -0
- package/templates/workspace/charter/policies/skill-observer.md +44 -0
- package/templates/workspace/charter/policies/stack.md +23 -0
- package/templates/workspace/charter/policies/task.md +139 -0
- package/templates/workspace/charter/policies/verification.md +29 -0
- package/templates/workspace/sessions/.gitkeep +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { cp, lstat, mkdir, readdir, readFile, rm, stat, } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { enginePath } from "@ocean/core/shared/paths.js";
|
|
6
|
+
export const clientSkillRoots = {
|
|
7
|
+
claude: (home) => path.join(home, ".claude", "skills"),
|
|
8
|
+
codex: (home) => path.join(home, ".codex", "skills"),
|
|
9
|
+
gemini: (home) => path.join(home, ".gemini", "skills"),
|
|
10
|
+
hermes: (home) => path.join(home, ".hermes", "skills"),
|
|
11
|
+
cursor: (home) => path.join(home, ".cursor", "skills"),
|
|
12
|
+
antigravity: (home) => path.join(home, ".config", "antigravity", "skills"),
|
|
13
|
+
};
|
|
14
|
+
async function coreSkillNames() {
|
|
15
|
+
const core = path.join(enginePath("skills"), "core");
|
|
16
|
+
return (await readdir(core, { withFileTypes: true }))
|
|
17
|
+
.filter((entry) => entry.isDirectory())
|
|
18
|
+
.map((entry) => entry.name)
|
|
19
|
+
.sort();
|
|
20
|
+
}
|
|
21
|
+
export async function coreSkillReports(home = os.homedir()) {
|
|
22
|
+
const names = await coreSkillNames();
|
|
23
|
+
return Promise.all(Object.keys(clientSkillRoots).map(async (client) => {
|
|
24
|
+
const root = clientSkillRoots[client](home);
|
|
25
|
+
try {
|
|
26
|
+
await stat(root);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {
|
|
30
|
+
client,
|
|
31
|
+
root,
|
|
32
|
+
status: "unavailable",
|
|
33
|
+
missing: names,
|
|
34
|
+
drifted: [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const missing = [];
|
|
38
|
+
const drifted = [];
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
const source = await readFile(path.join(enginePath("skills"), "core", name, "SKILL.md"));
|
|
41
|
+
const targetPath = path.join(root, name, "SKILL.md");
|
|
42
|
+
try {
|
|
43
|
+
const target = await readFile(targetPath);
|
|
44
|
+
if (!same(source, target))
|
|
45
|
+
drifted.push(name);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
missing.push(name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
client,
|
|
53
|
+
root,
|
|
54
|
+
status: missing.length
|
|
55
|
+
? "missing"
|
|
56
|
+
: drifted.length
|
|
57
|
+
? "drifted"
|
|
58
|
+
: "synchronized",
|
|
59
|
+
missing,
|
|
60
|
+
drifted,
|
|
61
|
+
};
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
export async function syncCoreSkills(home = os.homedir()) {
|
|
65
|
+
const names = await coreSkillNames();
|
|
66
|
+
const results = [];
|
|
67
|
+
for (const report of await coreSkillReports(home)) {
|
|
68
|
+
if (report.status === "unavailable") {
|
|
69
|
+
results.push({
|
|
70
|
+
client: report.client,
|
|
71
|
+
root: report.root,
|
|
72
|
+
copied: [],
|
|
73
|
+
status: report.status,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const copied = [];
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
const source = path.join(enginePath("skills"), "core", name, "SKILL.md");
|
|
80
|
+
const target = path.join(report.root, name, "SKILL.md");
|
|
81
|
+
// A link left behind by a moved source makes mkdir throw ENOENT; it holds nothing.
|
|
82
|
+
const skillDir = path.dirname(target);
|
|
83
|
+
if ((await lstat(skillDir).catch(() => null))?.isSymbolicLink() &&
|
|
84
|
+
!(await stat(skillDir).catch(() => null)))
|
|
85
|
+
await rm(skillDir);
|
|
86
|
+
await mkdir(skillDir, { recursive: true });
|
|
87
|
+
const sourceBytes = await readFile(source);
|
|
88
|
+
let current = null;
|
|
89
|
+
try {
|
|
90
|
+
current = await readFile(target);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* new client skill */
|
|
94
|
+
}
|
|
95
|
+
if (!current || !same(sourceBytes, current)) {
|
|
96
|
+
await cp(source, target);
|
|
97
|
+
copied.push(name);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
results.push({
|
|
101
|
+
client: report.client,
|
|
102
|
+
root: report.root,
|
|
103
|
+
copied,
|
|
104
|
+
status: "synchronized",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
function same(left, right) {
|
|
110
|
+
return (createHash("sha256").update(left).digest("hex") ===
|
|
111
|
+
createHash("sha256").update(right).digest("hex"));
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=core-skill-sync.js.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { redactRuntimeText } from "@ocean/core/domain/redaction/redaction.js";
|
|
2
|
+
const CORRECTION_PATTERN = /\bno,|\bactually,|\bnot that\b|\binstead of\b|\bshould use\b|\bthat'?s wrong\b/i;
|
|
3
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching terminal escape sequences
|
|
4
|
+
const ANSI_PATTERN = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
5
|
+
// Runtime chatter (hook_response JSON, terminal redraws) repeats by construction, not by lesson.
|
|
6
|
+
function isRuntimeNoise(text) {
|
|
7
|
+
if (text.length <= 12)
|
|
8
|
+
return true;
|
|
9
|
+
if (!text.startsWith("{"))
|
|
10
|
+
return false;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(text)?.type === "system";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function userInputTexts(events) {
|
|
19
|
+
return events
|
|
20
|
+
.filter((event) => event.type === "user_input")
|
|
21
|
+
.map((event) => ({
|
|
22
|
+
id: event.eventId,
|
|
23
|
+
text: redactRuntimeText(event.data).replace(/\s+/g, " ").trim(),
|
|
24
|
+
}))
|
|
25
|
+
.filter((event) => event.text.length > 12)
|
|
26
|
+
.slice(0, 80);
|
|
27
|
+
}
|
|
28
|
+
// True when the user pushed back in this session ("no, use X", "that's wrong").
|
|
29
|
+
export function hasUserCorrection(events) {
|
|
30
|
+
return userInputTexts(events).some((event) => CORRECTION_PATTERN.test(event.text));
|
|
31
|
+
}
|
|
32
|
+
// What a completed, proven session is worth remembering. Pure: reads events, writes nothing.
|
|
33
|
+
export function detectSessionSignals(events) {
|
|
34
|
+
const proven = events.filter((event) => event.type === "evidence" && /"result"\s*:\s*"proven"/.test(event.data));
|
|
35
|
+
if (!proven.length)
|
|
36
|
+
return [];
|
|
37
|
+
const outputs = events
|
|
38
|
+
.filter((event) => ["provider_output", "text", "json"].includes(event.type))
|
|
39
|
+
.map((event) => ({
|
|
40
|
+
id: event.eventId,
|
|
41
|
+
text: redactRuntimeText(event.data)
|
|
42
|
+
.replace(ANSI_PATTERN, "")
|
|
43
|
+
.replace(/\s+/g, " ")
|
|
44
|
+
.trim(),
|
|
45
|
+
}))
|
|
46
|
+
.filter((event) => !isRuntimeNoise(event.text))
|
|
47
|
+
.slice(0, 80);
|
|
48
|
+
const userInputs = userInputTexts(events);
|
|
49
|
+
const candidates = [];
|
|
50
|
+
const decisions = outputs.filter((event) => /\bdecision\s*:/i.test(event.text));
|
|
51
|
+
if (decisions.length)
|
|
52
|
+
candidates.push({
|
|
53
|
+
signalType: "explicit-decision",
|
|
54
|
+
summary: decisions[0].text.slice(0, 500),
|
|
55
|
+
refs: decisions.slice(0, 4).map((event) => `event:${event.id}`),
|
|
56
|
+
confidence: 0.9,
|
|
57
|
+
});
|
|
58
|
+
const counts = new Map();
|
|
59
|
+
for (const event of outputs) {
|
|
60
|
+
const normalized = event.text.toLocaleLowerCase();
|
|
61
|
+
const current = counts.get(normalized) ?? { count: 0, refs: [] };
|
|
62
|
+
current.count += 1;
|
|
63
|
+
current.refs.push(`event:${event.id}`);
|
|
64
|
+
counts.set(normalized, current);
|
|
65
|
+
}
|
|
66
|
+
const repeated = [...counts.entries()].find(([, value]) => value.count >= 2);
|
|
67
|
+
if (repeated)
|
|
68
|
+
candidates.push({
|
|
69
|
+
signalType: "repeated-procedure",
|
|
70
|
+
summary: repeated[0].slice(0, 500),
|
|
71
|
+
refs: repeated[1].refs.slice(0, 4),
|
|
72
|
+
confidence: 0.8,
|
|
73
|
+
});
|
|
74
|
+
const corrections = userInputs.filter((event) => CORRECTION_PATTERN.test(event.text));
|
|
75
|
+
if (corrections.length)
|
|
76
|
+
candidates.push({
|
|
77
|
+
signalType: "repeated-correction",
|
|
78
|
+
summary: corrections[0].text.slice(0, 500),
|
|
79
|
+
refs: corrections.slice(0, 4).map((event) => `event:${event.id}`),
|
|
80
|
+
confidence: 0.7,
|
|
81
|
+
});
|
|
82
|
+
return candidates;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=signal-detection.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { redactRuntimeText } from "@ocean/core/domain/redaction/redaction.js";
|
|
4
|
+
import { truncateUtf8 } from "@ocean/core/shared/fs-utils.js";
|
|
5
|
+
import { oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
|
|
6
|
+
const file = () => oceanPath(SYSTEM_DIR, "skills", "candidates.json");
|
|
7
|
+
async function load() {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(await readFile(file(), "utf8"));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error.code === "ENOENT")
|
|
13
|
+
return [];
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function save(items) {
|
|
18
|
+
await mkdir(path.dirname(file()), { recursive: true });
|
|
19
|
+
await writeFile(file(), `${JSON.stringify(items, null, 2)}\n`, {
|
|
20
|
+
mode: 0o600,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export async function addSkillCandidate(candidate) {
|
|
24
|
+
if (!candidate.id ||
|
|
25
|
+
!candidate.name ||
|
|
26
|
+
!candidate.instructions ||
|
|
27
|
+
candidate.instructions.length > 32_000 ||
|
|
28
|
+
/(api[_ -]?key|token|password|secret)\s*[:=]/i.test(candidate.instructions))
|
|
29
|
+
throw new Error("Invalid or sensitive skill candidate");
|
|
30
|
+
const items = await load();
|
|
31
|
+
if (items.some((item) => item.id === candidate.id || item.name === candidate.name))
|
|
32
|
+
throw new Error("Duplicate skill candidate");
|
|
33
|
+
const result = {
|
|
34
|
+
...candidate,
|
|
35
|
+
version: "1.0.0",
|
|
36
|
+
status: "candidate",
|
|
37
|
+
verification: "unverified",
|
|
38
|
+
createdAt: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
await save([...items, result]);
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
export async function listSkillCandidates() {
|
|
44
|
+
return load();
|
|
45
|
+
}
|
|
46
|
+
export async function loadPromotedSkills(prompt, maxBytes = 32_000) {
|
|
47
|
+
const normalizedPrompt = prompt.toLocaleLowerCase();
|
|
48
|
+
const candidates = (await load()).filter((candidate) => candidate.status === "promoted" &&
|
|
49
|
+
candidate.verification === "owner-reviewed");
|
|
50
|
+
const selected = [];
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
const aliases = [candidate.id, candidate.name].map((value) => value.toLocaleLowerCase().replaceAll("-", " "));
|
|
54
|
+
if (!aliases.some((alias) => normalizedPrompt.includes(alias)))
|
|
55
|
+
continue;
|
|
56
|
+
const remaining = maxBytes - bytes;
|
|
57
|
+
if (remaining <= 0)
|
|
58
|
+
break;
|
|
59
|
+
const size = Buffer.byteLength(candidate.instructions);
|
|
60
|
+
if (size <= remaining) {
|
|
61
|
+
selected.push({ ...candidate, truncated: false });
|
|
62
|
+
bytes += size;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// A usable pointer: the store file and the command that prints it. The store is not
|
|
66
|
+
// granted to restricted providers because it also holds unreviewed candidates.
|
|
67
|
+
const marker = `\n[truncated to the run budget; full text: entry id "${candidate.id}" in \`ocean skill list\` or ${file()}]`;
|
|
68
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
69
|
+
if (remaining <= markerBytes)
|
|
70
|
+
break;
|
|
71
|
+
const instructions = `${truncateUtf8(candidate.instructions, remaining - markerBytes)}${marker}`;
|
|
72
|
+
selected.push({ ...candidate, instructions, truncated: true });
|
|
73
|
+
bytes += Buffer.byteLength(instructions);
|
|
74
|
+
}
|
|
75
|
+
return selected;
|
|
76
|
+
}
|
|
77
|
+
export async function learnSkillFromSession(sessionId, openStore) {
|
|
78
|
+
const store = await openStore();
|
|
79
|
+
try {
|
|
80
|
+
const session = store.get(sessionId);
|
|
81
|
+
if (!session)
|
|
82
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
83
|
+
if (session.status !== "completed")
|
|
84
|
+
throw new Error(`Only completed sessions can teach a skill: ${session.status}`);
|
|
85
|
+
if (!store
|
|
86
|
+
.listEvents(sessionId)
|
|
87
|
+
.some((event) => event.type === "evidence" &&
|
|
88
|
+
/"result"\s*:\s*"proven"/.test(event.data)))
|
|
89
|
+
throw new Error("Session lacks independently recorded successful evidence");
|
|
90
|
+
const instructions = store
|
|
91
|
+
.listEvents(sessionId)
|
|
92
|
+
.filter((event) => event.type === "provider_output" ||
|
|
93
|
+
event.type === "text" ||
|
|
94
|
+
event.type === "json")
|
|
95
|
+
.map((event) => redactRuntimeText(event.data).trim())
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.join("\n\n")
|
|
98
|
+
.slice(0, 32_000);
|
|
99
|
+
if (!instructions)
|
|
100
|
+
throw new Error("Session has no provider output to learn from");
|
|
101
|
+
const id = `learned-${session.provider}-${sessionId.slice(0, 8)}`;
|
|
102
|
+
return addSkillCandidate({
|
|
103
|
+
id,
|
|
104
|
+
name: `Learned ${session.provider} skill`,
|
|
105
|
+
instructions,
|
|
106
|
+
sourceSessionId: sessionId,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
store.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function reviewSkillCandidate(id, status) {
|
|
114
|
+
const items = await load();
|
|
115
|
+
const item = items.find((candidate) => candidate.id === id);
|
|
116
|
+
if (!item)
|
|
117
|
+
throw new Error(`Skill candidate not found: ${id}`);
|
|
118
|
+
item.status = status;
|
|
119
|
+
item.verification = status === "promoted" ? "owner-reviewed" : "unverified";
|
|
120
|
+
await save(items);
|
|
121
|
+
return item;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=skill-curation.js.map
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { cp, lstat, mkdir, readdir, readFile, readlink, rename, rm, stat, symlink, } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { enginePath, oceanPath, SYSTEM_DIR } from "@ocean/core/shared/paths.js";
|
|
6
|
+
import { clientSkillRoots } from "./core-skill-sync.js";
|
|
7
|
+
// The hub is the one place skills live inside Ocean. Clients hold symlinks into it, so
|
|
8
|
+
// there is a single copy to edit and nothing drifts per client.
|
|
9
|
+
export function skillHubRoot() {
|
|
10
|
+
return oceanPath(SYSTEM_DIR, "skills");
|
|
11
|
+
}
|
|
12
|
+
async function skillDirectories(root) {
|
|
13
|
+
const names = [];
|
|
14
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
15
|
+
// Follow symlinks: a client may expose another store's skills as links.
|
|
16
|
+
const full = path.join(root, entry.name);
|
|
17
|
+
const isDirectory = entry.isDirectory() || (await stat(full)).isDirectory();
|
|
18
|
+
if (!isDirectory)
|
|
19
|
+
continue;
|
|
20
|
+
try {
|
|
21
|
+
await stat(path.join(full, "SKILL.md"));
|
|
22
|
+
names.push(entry.name);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* not a skill directory */
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return names.sort();
|
|
29
|
+
}
|
|
30
|
+
// Additive: copies the engine's core skills and any extra source folders into the hub.
|
|
31
|
+
// An existing hub skill is never overwritten.
|
|
32
|
+
export async function populateSkillHub(extraSources = []) {
|
|
33
|
+
const hub = skillHubRoot();
|
|
34
|
+
await mkdir(hub, { recursive: true });
|
|
35
|
+
const added = [];
|
|
36
|
+
const kept = [];
|
|
37
|
+
for (const source of [enginePath("skills", "core"), ...extraSources]) {
|
|
38
|
+
for (const name of await skillDirectories(source)) {
|
|
39
|
+
const target = path.join(hub, name);
|
|
40
|
+
try {
|
|
41
|
+
await lstat(target);
|
|
42
|
+
kept.push(name);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
await cp(path.join(source, name), target, {
|
|
46
|
+
recursive: true,
|
|
47
|
+
dereference: true,
|
|
48
|
+
});
|
|
49
|
+
added.push(name);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { added, kept };
|
|
54
|
+
}
|
|
55
|
+
// Dry run unless `apply`. Skills a client does not already have are only added with `all`:
|
|
56
|
+
// every extra skill costs that client always-on context. A real directory (or a link elsewhere) already at the target is
|
|
57
|
+
// moved into a timestamped backup under the bridge archive before the link is made.
|
|
58
|
+
export async function linkClientSkills(options) {
|
|
59
|
+
const home = options.home ?? os.homedir();
|
|
60
|
+
const hub = options.hub ?? skillHubRoot();
|
|
61
|
+
const backup = path.join(oceanPath(SYSTEM_DIR, "archive"), `skills-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
62
|
+
const actions = [];
|
|
63
|
+
let backedUp = false;
|
|
64
|
+
const names = await skillDirectories(hub);
|
|
65
|
+
for (const client of Object.keys(clientSkillRoots)) {
|
|
66
|
+
const root = clientSkillRoots[client](home);
|
|
67
|
+
try {
|
|
68
|
+
await stat(root);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
continue; // client not installed
|
|
72
|
+
}
|
|
73
|
+
for (const skill of names) {
|
|
74
|
+
const target = path.join(root, skill);
|
|
75
|
+
const wanted = path.join(hub, skill);
|
|
76
|
+
let action = "create";
|
|
77
|
+
try {
|
|
78
|
+
const info = await lstat(target);
|
|
79
|
+
if (info.isSymbolicLink() &&
|
|
80
|
+
path.resolve(root, await readlink(target)) === wanted) {
|
|
81
|
+
action = "linked";
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
action = "replace";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* absent */
|
|
89
|
+
}
|
|
90
|
+
if (action === "create" && !options.all)
|
|
91
|
+
action = "skip";
|
|
92
|
+
actions.push({ client, skill, action });
|
|
93
|
+
if (!options.apply || action === "linked" || action === "skip")
|
|
94
|
+
continue;
|
|
95
|
+
if (action === "replace") {
|
|
96
|
+
const saved = path.join(backup, client);
|
|
97
|
+
await mkdir(saved, { recursive: true });
|
|
98
|
+
await rename(target, path.join(saved, skill));
|
|
99
|
+
backedUp = true;
|
|
100
|
+
}
|
|
101
|
+
await symlink(wanted, target, "dir");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { actions, backup: backedUp ? backup : null };
|
|
105
|
+
}
|
|
106
|
+
// Clients that get the hub's skills as copies. Hermes manages its own bundle and curator,
|
|
107
|
+
// so it is left alone unless asked for by name.
|
|
108
|
+
export const COPY_CLIENTS = [
|
|
109
|
+
"claude",
|
|
110
|
+
"codex",
|
|
111
|
+
"gemini",
|
|
112
|
+
"hermes",
|
|
113
|
+
];
|
|
114
|
+
// Content hash of a whole skill directory: paths and bytes, so a changed script or reference
|
|
115
|
+
// counts as drift, not only SKILL.md.
|
|
116
|
+
// A symlink inside a skill is refused: copying it would either dereference into data outside
|
|
117
|
+
// the hub or leave a dangling link in the client, and a loop would never finish.
|
|
118
|
+
async function directoryHash(directory) {
|
|
119
|
+
const hash = createHash("sha256");
|
|
120
|
+
async function walk(current, prefix) {
|
|
121
|
+
const entries = (await readdir(current, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const full = path.join(current, entry.name);
|
|
124
|
+
const relative = path.posix.join(prefix, entry.name);
|
|
125
|
+
const info = await lstat(full);
|
|
126
|
+
if (info.isSymbolicLink())
|
|
127
|
+
throw new Error(`${relative} is a symlink inside a skill`);
|
|
128
|
+
if (info.isDirectory())
|
|
129
|
+
await walk(full, relative);
|
|
130
|
+
else
|
|
131
|
+
hash.update(relative).update(await readFile(full));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
await walk(directory, "");
|
|
135
|
+
return hash.digest("hex");
|
|
136
|
+
}
|
|
137
|
+
// Gives every client a real copy of every hub skill, replacing the symlink farm. Dry run
|
|
138
|
+
// unless `apply`. A symlink is only unlinked (its target is untouched); a real directory
|
|
139
|
+
// that differs from the hub is moved into a timestamped backup, never deleted. Anything in a
|
|
140
|
+
// client that the hub does not name is left alone.
|
|
141
|
+
export async function copyClientSkills(options) {
|
|
142
|
+
const home = options.home ?? os.homedir();
|
|
143
|
+
const hub = options.hub ?? skillHubRoot();
|
|
144
|
+
const backup = path.join(options.archive ?? oceanPath(SYSTEM_DIR, "archive"), `skills-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
145
|
+
const actions = [];
|
|
146
|
+
let backedUp = false;
|
|
147
|
+
const names = await skillDirectories(hub);
|
|
148
|
+
for (const client of options.clients ?? COPY_CLIENTS) {
|
|
149
|
+
const root = clientSkillRoots[client](home);
|
|
150
|
+
try {
|
|
151
|
+
await stat(root);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
continue; // client not installed
|
|
155
|
+
}
|
|
156
|
+
for (const skill of names) {
|
|
157
|
+
const target = path.join(root, skill);
|
|
158
|
+
const source = path.join(hub, skill);
|
|
159
|
+
try {
|
|
160
|
+
let action = "create";
|
|
161
|
+
let info = null;
|
|
162
|
+
try {
|
|
163
|
+
info = await lstat(target);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* absent */
|
|
167
|
+
}
|
|
168
|
+
const wanted = await directoryHash(source);
|
|
169
|
+
let ours = false;
|
|
170
|
+
if (info?.isSymbolicLink()) {
|
|
171
|
+
action = "unlink";
|
|
172
|
+
// A link into the hub holds nothing of its own. Any other link is moved into the
|
|
173
|
+
// backup (the link itself, intact) so what it pointed at stays reachable.
|
|
174
|
+
ours = path
|
|
175
|
+
.resolve(root, await readlink(target))
|
|
176
|
+
.startsWith(hub + path.sep);
|
|
177
|
+
}
|
|
178
|
+
else if (info) {
|
|
179
|
+
action =
|
|
180
|
+
info.isDirectory() && (await directoryHash(target)) === wanted
|
|
181
|
+
? "synchronized"
|
|
182
|
+
: "replace";
|
|
183
|
+
}
|
|
184
|
+
actions.push({ client, skill, action });
|
|
185
|
+
if (!options.apply || action === "synchronized")
|
|
186
|
+
continue;
|
|
187
|
+
if (action === "replace" || (action === "unlink" && !ours)) {
|
|
188
|
+
const saved = path.join(backup, client);
|
|
189
|
+
await mkdir(saved, { recursive: true });
|
|
190
|
+
await rename(target, path.join(saved, skill));
|
|
191
|
+
backedUp = true;
|
|
192
|
+
}
|
|
193
|
+
else if (action === "unlink")
|
|
194
|
+
await rm(target);
|
|
195
|
+
// Copy beside the target and rename, so a failed copy never leaves a half skill.
|
|
196
|
+
const staging = path.join(root, `.${skill}.copying`);
|
|
197
|
+
await rm(staging, { recursive: true, force: true });
|
|
198
|
+
try {
|
|
199
|
+
await cp(source, staging, { recursive: true });
|
|
200
|
+
await rename(staging, target);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
await rm(staging, { recursive: true, force: true });
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
// One bad skill must not abort the run halfway through every client.
|
|
209
|
+
actions.push({
|
|
210
|
+
client,
|
|
211
|
+
skill,
|
|
212
|
+
action: "failed",
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { actions, backup: backedUp ? backup : null };
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=skill-hub.js.map
|