beehaven 0.8.16
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 +201 -0
- package/dist/__tests__/_cli-switch.js +105 -0
- package/dist/__tests__/agent-wrap-probe.js +136 -0
- package/dist/agent-attribution.js +192 -0
- package/dist/agent-identity-daemon.js +135 -0
- package/dist/agent-onboard.js +156 -0
- package/dist/agent-scope.js +79 -0
- package/dist/agent-sessions.js +370 -0
- package/dist/agent-state.js +482 -0
- package/dist/app-manifest.js +221 -0
- package/dist/app-signer.js +54 -0
- package/dist/app-signing.js +127 -0
- package/dist/audio-play.js +98 -0
- package/dist/avatar-protocol.js +25 -0
- package/dist/avatar-route.js +79 -0
- package/dist/bee/cloud-proxy.js +130 -0
- package/dist/blocked-actions.js +120 -0
- package/dist/build-channel.js +101 -0
- package/dist/buildings/daemon-workspace.js +475 -0
- package/dist/claude-bridge.js +310 -0
- package/dist/claude-credential-store.js +633 -0
- package/dist/clearly-sync.js +518 -0
- package/dist/clearly.js +616 -0
- package/dist/cli-state.js +334 -0
- package/dist/cli.js +6973 -0
- package/dist/commands/agent-skills.js +302 -0
- package/dist/commands/agent.js +1310 -0
- package/dist/commands/app.js +1123 -0
- package/dist/commands/auth.js +171 -0
- package/dist/commands/building-shell.js +392 -0
- package/dist/commands/canvas.js +932 -0
- package/dist/commands/claude.js +218 -0
- package/dist/commands/compliance.js +387 -0
- package/dist/commands/compose.js +507 -0
- package/dist/commands/composition.js +95 -0
- package/dist/commands/connect-session.js +409 -0
- package/dist/commands/conversations.js +160 -0
- package/dist/commands/costs.js +443 -0
- package/dist/commands/d1.js +253 -0
- package/dist/commands/daemon-cmd.js +162 -0
- package/dist/commands/deploy.js +164 -0
- package/dist/commands/design.js +617 -0
- package/dist/commands/dev-generate.js +125 -0
- package/dist/commands/dev.js +109 -0
- package/dist/commands/devices.js +135 -0
- package/dist/commands/explain-router.js +24 -0
- package/dist/commands/explain.js +882 -0
- package/dist/commands/explore.js +264 -0
- package/dist/commands/help.js +2418 -0
- package/dist/commands/init.js +408 -0
- package/dist/commands/install.js +129 -0
- package/dist/commands/library.js +229 -0
- package/dist/commands/marketplace.js +138 -0
- package/dist/commands/mcp-install.js +476 -0
- package/dist/commands/mention.js +257 -0
- package/dist/commands/omni.js +529 -0
- package/dist/commands/secrets.js +323 -0
- package/dist/commands/self-update.js +206 -0
- package/dist/commands/sheet.js +264 -0
- package/dist/commands/shell-output.js +309 -0
- package/dist/commands/shell.js +1048 -0
- package/dist/commands/shop.js +368 -0
- package/dist/commands/skills.js +311 -0
- package/dist/commands/slides.js +418 -0
- package/dist/commands/swarm.js +341 -0
- package/dist/commands/ticket.js +1301 -0
- package/dist/commands/tool.js +312 -0
- package/dist/commands/trash.js +106 -0
- package/dist/commands/treehouse.js +120 -0
- package/dist/commands/user.js +240 -0
- package/dist/commands/voice-tts.js +319 -0
- package/dist/commands/voice.js +166 -0
- package/dist/commands/watch-git.js +401 -0
- package/dist/commands/webhook.js +159 -0
- package/dist/commands/workspace.js +3354 -0
- package/dist/composition-repos.js +91 -0
- package/dist/daemon-avatar.js +130 -0
- package/dist/daemon-claude-code-auto-install.js +52 -0
- package/dist/daemon-client.js +363 -0
- package/dist/daemon-codex.js +349 -0
- package/dist/daemon-entry.js +101 -0
- package/dist/daemon-fetch-allowlist.js +194 -0
- package/dist/daemon-gemini.js +224 -0
- package/dist/daemon-mcp-clearly.js +353 -0
- package/dist/daemon-mcp-sandbox.js +127 -0
- package/dist/daemon-omni-company.js +338 -0
- package/dist/daemon-omni-hooks.js +375 -0
- package/dist/daemon-omni-identity.js +229 -0
- package/dist/daemon-omni-pipeline.js +251 -0
- package/dist/daemon-omni-policies.js +346 -0
- package/dist/daemon-omni-principal.js +580 -0
- package/dist/daemon-omni-tool-gate.js +116 -0
- package/dist/daemon-omni.js +1442 -0
- package/dist/daemon-path-transform.js +120 -0
- package/dist/daemon-write-sandbox.js +191 -0
- package/dist/daemon.js +4471 -0
- package/dist/dashboard.js +846 -0
- package/dist/dep-installer.js +286 -0
- package/dist/dev-mode.js +67 -0
- package/dist/doc-indexer.js +169 -0
- package/dist/e2e-crypto.js +94 -0
- package/dist/feature-flags.js +154 -0
- package/dist/file-browser.js +88 -0
- package/dist/first-party-hives.js +211 -0
- package/dist/folder-sync.js +226 -0
- package/dist/health.js +56 -0
- package/dist/ipc-auth.js +299 -0
- package/dist/ipc-client.js +495 -0
- package/dist/ipc-server.js +684 -0
- package/dist/keychain.js +224 -0
- package/dist/lib/admin-client.js +73 -0
- package/dist/lib/mobile-build-parser.js +156 -0
- package/dist/local-app-manager.js +660 -0
- package/dist/log-redaction.js +89 -0
- package/dist/logger.js +108 -0
- package/dist/manifest.js +143 -0
- package/dist/marketplace.js +793 -0
- package/dist/min-version-copy.js +51 -0
- package/dist/min-version.js +214 -0
- package/dist/public-surface.js +23 -0
- package/dist/relay-presets.js +89 -0
- package/dist/relay.js +2723 -0
- package/dist/repl.js +619 -0
- package/dist/setup-relay.js +363 -0
- package/dist/setup-service.js +536 -0
- package/dist/shared/bee-memory/index.js +6 -0
- package/dist/shared/bee-memory/types.js +682 -0
- package/dist/shared/claude-code/daemon-types.js +112 -0
- package/dist/shared/claude-code/index.js +2 -0
- package/dist/shared/claude-code/types.js +1 -0
- package/dist/shared/status/schema.js +133 -0
- package/dist/shell-session.js +198 -0
- package/dist/speak-lock.js +97 -0
- package/dist/spinner.js +57 -0
- package/dist/telemetry.js +166 -0
- package/dist/terminal/block-parser.js +207 -0
- package/dist/terminal/shell-integration.js +108 -0
- package/dist/transcript-tailer.js +215 -0
- package/dist/tts-qwen3.js +547 -0
- package/dist/types.js +4 -0
- package/dist/utm.js +38 -0
- package/dist/verb-surface.js +331 -0
- package/dist/version-utils.js +85 -0
- package/dist/watcher.js +207 -0
- package/package.json +88 -0
- package/python/qwen3_tts.py +230 -0
- package/scripts/stamp-build-channel.mjs +115 -0
- package/scripts/sync-installed.mjs +159 -0
- package/scripts/sync-skills.mjs +112 -0
- package/skills/README.md +24 -0
- package/skills/agent-doodle-space/SKILL.md +118 -0
- package/skills/agent-home/SKILL.md +98 -0
- package/skills/beehaven-cli/SKILL.md +144 -0
- package/skills/claude-shopper/README.md +90 -0
- package/skills/claude-shopper/SKILL.md +151 -0
- package/skills/clearly-agent/SKILL.md +183 -0
- package/skills/clearly-canvas/SKILL.md +222 -0
- package/skills/clearly-canvas/authoring.md +160 -0
- package/skills/clearly-canvas/communications.md +32 -0
- package/skills/clearly-canvas/composition-patterns.md +84 -0
- package/skills/clearly-canvas/example.mjs +78 -0
- package/skills/clearly-canvas/project-plans.md +156 -0
- package/skills/clearly-canvas/review.mjs +68 -0
- package/skills/clearly-workspace/SKILL.md +79 -0
- package/skills/clearly-workspace/survey.mjs +101 -0
- package/skills/codebase-map/SKILL.md +142 -0
- package/skills/design-craft/SKILL.md +300 -0
- package/skills/index.json +79 -0
- package/skills/layout-systems/SKILL.md +184 -0
- package/skills/pair-on-canvas/SKILL.md +92 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-skills — install the shipped Clearly SKILL.md files into every agent's home.
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ THIS IS THE OTHER HALF OF "just install the CLI". `mcp install` gives an agent the TOOLS;
|
|
5
|
+
* this gives it the instructions for using them well. The Mac app has installed both on every
|
|
6
|
+
* launch for a while; the CLI installed neither, so a headless or Linux user got a bare tool list
|
|
7
|
+
* and no guidance.
|
|
8
|
+
*
|
|
9
|
+
* ⚠ NOT THE SAME AS `beehaven skills` (hive-paired skills). That command manages SKILL.md files
|
|
10
|
+
* that ship WITH an installed hive, and writes only to `~/.claude/skills`. These five are the
|
|
11
|
+
* product's own, they ship in this package, and they go to every agent home.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ A SYMLINKED SKILL IS LEFT ALONE, IN BOTH DIRECTIONS. A developer who links a skill to iterate
|
|
14
|
+
* on it has made a deliberate statement — the repo owns that file. Writing through the symlink
|
|
15
|
+
* would silently EDIT THEIR CHECKOUT (`writeFileSync` follows links), and removing it would break
|
|
16
|
+
* the loop it was created for. Same rule the Mac app's installer follows.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import crypto from 'crypto';
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
/**
|
|
24
|
+
* Where each agent looks for skills.
|
|
25
|
+
*
|
|
26
|
+
* ⚠ `~/.agents/skills` is the SHARED, cross-agent location and takes precedence over the per-agent
|
|
27
|
+
* directories for the agents that read it. The per-agent homes are kept because not every agent
|
|
28
|
+
* does.
|
|
29
|
+
*/
|
|
30
|
+
export const AGENT_SKILL_DIRS = [
|
|
31
|
+
{ agent: 'shared', dir: path.join(os.homedir(), '.agents', 'skills') },
|
|
32
|
+
{ agent: 'claude', dir: path.join(os.homedir(), '.claude', 'skills') },
|
|
33
|
+
{ agent: 'codex', dir: path.join(os.homedir(), '.codex', 'skills') },
|
|
34
|
+
{ agent: 'gemini', dir: path.join(os.homedir(), '.gemini', 'skills') },
|
|
35
|
+
{ agent: 'pi', dir: path.join(os.homedir(), '.pi', 'agent', 'skills') },
|
|
36
|
+
];
|
|
37
|
+
/** Where this package's copies live — written by `scripts/sync-skills.mjs` at build time. */
|
|
38
|
+
function skillSourceDir() {
|
|
39
|
+
// dist/commands/agent-skills.js → ../../skills
|
|
40
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'skills');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The shipped manifest.
|
|
44
|
+
*
|
|
45
|
+
* ⚠⚠ ACCEPTS BOTH SHAPES, IN BOTH DIRECTIONS. `index.json` was a bare `string[]` and is now
|
|
46
|
+
* `{name, files, sha}[]`. A CLI and its bundled skills travel together in one tarball, but a
|
|
47
|
+
* DEVELOPER checkout mixes them constantly — a rebuilt `dist/` against an unsynced `skills/`, or
|
|
48
|
+
* the reverse — and a manifest reader that handles only one shape turns that into a failed install
|
|
49
|
+
* with no skills at all. Reading both costs one `typeof`.
|
|
50
|
+
*/
|
|
51
|
+
export function shippedManifest() {
|
|
52
|
+
try {
|
|
53
|
+
const raw = JSON.parse(fs.readFileSync(path.join(skillSourceDir(), 'index.json'), 'utf8'));
|
|
54
|
+
if (!Array.isArray(raw))
|
|
55
|
+
return [];
|
|
56
|
+
return raw.map((e) => (typeof e === 'string' ? { name: e } : e)).filter((e) => e?.name);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Names only — the old signature, kept so existing callers do not move. */
|
|
63
|
+
export function shippedSkills() {
|
|
64
|
+
return shippedManifest().map((s) => s.name);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Has the user edited this installed skill?
|
|
68
|
+
*
|
|
69
|
+
* ⚠⚠ THE POINT IS TO STOP OVERWRITING SOMEBODY'S WORK IN SILENCE. The installer compared content
|
|
70
|
+
* and wrote whenever it differed, which cannot tell "stale, update it" from "the user changed it,
|
|
71
|
+
* leave it alone" — those are opposite instructions and both look like `existing !== desired`.
|
|
72
|
+
*
|
|
73
|
+
* The `.beehaven-skill` stamp records the sha that was INSTALLED. On the next run:
|
|
74
|
+
* · on-disk matches the stamp ⇒ untouched, safe to update
|
|
75
|
+
* · on-disk differs from the stamp ⇒ the user edited it, skip and say so
|
|
76
|
+
* · no stamp (hand-placed, or older) ⇒ treated as untouched, because assuming an edit would
|
|
77
|
+
* freeze every skill installed before stamping existed and updates would never land again
|
|
78
|
+
*/
|
|
79
|
+
function localSha(destDir, files) {
|
|
80
|
+
const h = crypto.createHash('sha256');
|
|
81
|
+
for (const f of files.slice().sort()) {
|
|
82
|
+
h.update(f);
|
|
83
|
+
try {
|
|
84
|
+
h.update(fs.readFileSync(path.join(destDir, f)));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
h.update('\0missing');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return h.digest('hex').slice(0, 16);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Install a skill's SIBLING FILES — the scripts and data that make it more than prose.
|
|
94
|
+
*
|
|
95
|
+
* ⚠⚠ THE INSTALLER CARRIED EXACTLY ONE FILE PER SKILL, so a skill could only ever BE prose. That
|
|
96
|
+
* is a ceiling, not an oversight to route around: the two most reliable skills in this product
|
|
97
|
+
* (`growth-report`, `rank-report`) work because they ship `.mjs` files which collapse a dozen
|
|
98
|
+
* fragile commands into one, and neither could be distributed through this path. A prose skill
|
|
99
|
+
* tells the model to run twelve commands and it drifts on all twelve.
|
|
100
|
+
*
|
|
101
|
+
* ⚠ EXECUTABLE BIT ON `.mjs`/`.sh`, because a skill that documents `node x.mjs` still works
|
|
102
|
+
* without it but one that documents `./x.sh` does not — and the failure is a permission error the
|
|
103
|
+
* model cannot diagnose.
|
|
104
|
+
*
|
|
105
|
+
* ⚠ NEVER FATAL. A skill whose script fails to copy is a degraded skill; a throw here would abort
|
|
106
|
+
* the install of every remaining skill for every remaining agent home.
|
|
107
|
+
*/
|
|
108
|
+
/**
|
|
109
|
+
* Copies a skill's non-SKILL.md files (scripts, data) and RETURNS HOW MANY IT WROTE.
|
|
110
|
+
*
|
|
111
|
+
* ⚠⚠ THE COUNT IS NOT BOOKKEEPING — WITHOUT IT A SCRIPT-ONLY UPDATE REPORTED `current`. The caller
|
|
112
|
+
* decides `current` vs `updated` by comparing SKILL.md alone, so a skill whose SKILL.md is
|
|
113
|
+
* byte-identical and whose `survey.mjs` was rewritten landed the new script and told the operator
|
|
114
|
+
* nothing happened. Verified live before the fix: probe appended to `survey.mjs` → the installed
|
|
115
|
+
* copy contained it → every step still said `current`. Silent success is the same shape as silent
|
|
116
|
+
* failure to anyone reading the output, and a scripted skill is exactly where that matters.
|
|
117
|
+
*/
|
|
118
|
+
function installExtras(srcDir, name, destDir, warnings) {
|
|
119
|
+
const from = path.join(srcDir, name);
|
|
120
|
+
let wrote = 0;
|
|
121
|
+
let files = [];
|
|
122
|
+
try {
|
|
123
|
+
files = fs.readdirSync(from);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
for (const f of files) {
|
|
129
|
+
if (f === 'SKILL.md')
|
|
130
|
+
continue;
|
|
131
|
+
try {
|
|
132
|
+
const s = path.join(from, f);
|
|
133
|
+
if (!fs.statSync(s).isFile())
|
|
134
|
+
continue;
|
|
135
|
+
const d = path.join(destDir, f);
|
|
136
|
+
// ⚠ A symlinked extra is left alone, exactly as a symlinked SKILL.md is — same reasoning.
|
|
137
|
+
try {
|
|
138
|
+
if (fs.lstatSync(d).isSymbolicLink())
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
catch { /* absent */ }
|
|
142
|
+
const body = fs.readFileSync(s);
|
|
143
|
+
let existing = null;
|
|
144
|
+
try {
|
|
145
|
+
existing = fs.readFileSync(d);
|
|
146
|
+
}
|
|
147
|
+
catch { /* not installed */ }
|
|
148
|
+
if (existing && existing.equals(body))
|
|
149
|
+
continue;
|
|
150
|
+
fs.mkdirSync(destDir, { recursive: true, mode: 0o755 });
|
|
151
|
+
fs.writeFileSync(d, body, { mode: /\.(mjs|js|sh)$/.test(f) ? 0o755 : 0o644 });
|
|
152
|
+
wrote++;
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
warnings.push(`${name}/${f}: ${e?.message ?? String(e)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return wrote;
|
|
159
|
+
}
|
|
160
|
+
/** Filename of the install stamp. ⚠ Dot-prefixed so it never reads as part of the skill. */
|
|
161
|
+
const STAMP = '.beehaven-skill';
|
|
162
|
+
/**
|
|
163
|
+
* Record what was installed, so the NEXT run can tell a stale copy from an edited one.
|
|
164
|
+
*
|
|
165
|
+
* ⚠ WRITTEN AFTER THE FILES, ALWAYS. Stamped first, a failed copy leaves a stamp claiming content
|
|
166
|
+
* that is not there — and the next run then reads the mismatch as a user edit and refuses to fix
|
|
167
|
+
* it. That is a self-inflicted permanent freeze.
|
|
168
|
+
*
|
|
169
|
+
* ⚠ NON-FATAL. An unstampable skill degrades to the old always-overwrite behaviour, which is what
|
|
170
|
+
* every skill did until now.
|
|
171
|
+
*/
|
|
172
|
+
function stamp(destDir, files, warnings) {
|
|
173
|
+
try {
|
|
174
|
+
fs.writeFileSync(path.join(destDir, STAMP), JSON.stringify({ sha: localSha(destDir, files), files, at: new Date().toISOString() }, null, 2), { mode: 0o644 });
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
warnings.push(`stamp ${destDir}: ${e?.message ?? String(e)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
export function installAgentSkills() {
|
|
181
|
+
const steps = [];
|
|
182
|
+
const warnings = [];
|
|
183
|
+
const srcDir = skillSourceDir();
|
|
184
|
+
const names = shippedSkills();
|
|
185
|
+
if (!names.length) {
|
|
186
|
+
warnings.push(`no skills bundled in this build (${srcDir}) — run scripts/sync-skills.mjs before packing`);
|
|
187
|
+
return { steps, warnings };
|
|
188
|
+
}
|
|
189
|
+
const manifest = shippedManifest();
|
|
190
|
+
for (const name of names) {
|
|
191
|
+
const entry = manifest.find((m) => m.name === name);
|
|
192
|
+
/** Every file this skill ships — used for the stamp, so a script change counts as a change. */
|
|
193
|
+
const skillFiles = entry?.files?.length ? entry.files : ['SKILL.md'];
|
|
194
|
+
const src = path.join(srcDir, name, 'SKILL.md');
|
|
195
|
+
if (!fs.existsSync(src)) {
|
|
196
|
+
warnings.push(`source missing: ${name}`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const desired = fs.readFileSync(src, 'utf8');
|
|
200
|
+
for (const { agent, dir } of AGENT_SKILL_DIRS) {
|
|
201
|
+
const destDir = path.join(dir, name);
|
|
202
|
+
const dest = path.join(destDir, 'SKILL.md');
|
|
203
|
+
try {
|
|
204
|
+
/**
|
|
205
|
+
* ⚠ ONLY INTO A HOME THAT EXISTS. Creating `~/.gemini/skills` on a machine with no Gemini
|
|
206
|
+
* scatters directories for software the user does not run.
|
|
207
|
+
*/
|
|
208
|
+
if (!fs.existsSync(dir) && !fs.existsSync(path.dirname(dir))) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
let link = null;
|
|
212
|
+
try {
|
|
213
|
+
if (fs.lstatSync(dest).isSymbolicLink())
|
|
214
|
+
link = fs.readlinkSync(dest);
|
|
215
|
+
}
|
|
216
|
+
catch { /* absent */ }
|
|
217
|
+
if (link) {
|
|
218
|
+
warnings.push(`${agent}/${name}: symlink → ${link} (left alone)`);
|
|
219
|
+
steps.push(`${agent}/${name}=symlink-skipped`);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
let existing = '';
|
|
223
|
+
try {
|
|
224
|
+
existing = fs.readFileSync(dest, 'utf8');
|
|
225
|
+
}
|
|
226
|
+
catch { /* not installed */ }
|
|
227
|
+
/**
|
|
228
|
+
* ⚠⚠ A LOCALLY EDITED SKILL IS LEFT ALONE AND REPORTED. Without this the installer could
|
|
229
|
+
* only ask "does it differ?" — which is true both when the shipped copy moved on and when
|
|
230
|
+
* the USER changed theirs, and those are opposite instructions. Someone who tuned a skill
|
|
231
|
+
* lost it on the next `beehaven skills install`, silently.
|
|
232
|
+
*
|
|
233
|
+
* ⚠ THE STAMP IS THE ARBITER, NOT THE SHIPPED SHA. Comparing on-disk against SHIPPED only
|
|
234
|
+
* says "different"; comparing against what was INSTALLED says WHO changed it.
|
|
235
|
+
*
|
|
236
|
+
* ⚠ NO STAMP ⇒ TREATED AS UNTOUCHED. Every skill installed before stamping existed has
|
|
237
|
+
* none, and assuming an edit there would freeze them permanently — updates would silently
|
|
238
|
+
* stop landing, which is the failure mode this whole change exists to remove.
|
|
239
|
+
*/
|
|
240
|
+
if (existing) {
|
|
241
|
+
let stamped = '';
|
|
242
|
+
try {
|
|
243
|
+
stamped = JSON.parse(fs.readFileSync(path.join(destDir, STAMP), 'utf8')).sha ?? '';
|
|
244
|
+
}
|
|
245
|
+
catch { /* unstamped */ }
|
|
246
|
+
if (stamped && localSha(destDir, skillFiles) !== stamped) {
|
|
247
|
+
warnings.push(`${agent}/${name}: edited locally — left alone (delete it to take the shipped copy)`);
|
|
248
|
+
steps.push(`${agent}/${name}=locally-modified`);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (existing === desired) {
|
|
253
|
+
// ⚠ THE EXTRAS DECIDE THE WORD. SKILL.md matching does not mean the skill is unchanged —
|
|
254
|
+
// see `installExtras`. A rewritten script is an update and must say so.
|
|
255
|
+
const wrote = installExtras(srcDir, name, destDir, warnings);
|
|
256
|
+
stamp(destDir, skillFiles, warnings);
|
|
257
|
+
steps.push(`${agent}/${name}=${wrote ? 'updated-scripts' : 'current'}`);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
fs.mkdirSync(destDir, { recursive: true, mode: 0o755 });
|
|
261
|
+
fs.writeFileSync(dest, desired, { mode: 0o644 });
|
|
262
|
+
installExtras(srcDir, name, destDir, warnings);
|
|
263
|
+
stamp(destDir, skillFiles, warnings);
|
|
264
|
+
steps.push(`${agent}/${name}=${existing ? 'updated' : 'written'}`);
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
warnings.push(`${agent}/${name}: ${e?.message ?? String(e)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { steps, warnings };
|
|
272
|
+
}
|
|
273
|
+
export function removeAgentSkills() {
|
|
274
|
+
const steps = [];
|
|
275
|
+
const warnings = [];
|
|
276
|
+
for (const name of shippedSkills()) {
|
|
277
|
+
for (const { agent, dir } of AGENT_SKILL_DIRS) {
|
|
278
|
+
const destDir = path.join(dir, name);
|
|
279
|
+
const dest = path.join(destDir, 'SKILL.md');
|
|
280
|
+
try {
|
|
281
|
+
if (!fs.existsSync(destDir))
|
|
282
|
+
continue;
|
|
283
|
+
let link = null;
|
|
284
|
+
try {
|
|
285
|
+
if (fs.lstatSync(dest).isSymbolicLink())
|
|
286
|
+
link = fs.readlinkSync(dest);
|
|
287
|
+
}
|
|
288
|
+
catch { /* absent */ }
|
|
289
|
+
if (link) {
|
|
290
|
+
steps.push(`${agent}/${name}=symlink-kept`);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
294
|
+
steps.push(`${agent}/${name}=removed`);
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
warnings.push(`${agent}/${name}: ${e?.message ?? String(e)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return { steps, warnings };
|
|
302
|
+
}
|