|
|
@@ -1,13 +1,15 @@
|
|
|
#!/usr/bin/env node
|
|
|
// agent-sync — installs the bundled skills and agent prompts into the canonical
|
|
|
-// user-level directory (~/.agents) and links them into every detected AI agent's
|
|
|
-// own directory (same architecture as the vercel-labs/skills CLI).
|
|
|
+// user-level directory (~/.agents) and links the skills into every detected AI
|
|
|
+// agent's own directory (same architecture as the vercel-labs/skills CLI).
|
|
|
+// Agent prompts are managed only inside ~/.agents/agents via plain copies
|
|
|
+// (file links on windows require privileges).
|
|
|
// Selection is explicit (--all, --skills, --agents, --targets): without a
|
|
|
// selection flag the tool prints its help. See HELP for the full surface.
|
|
|
|
|
|
import { createHash } from 'node:crypto';
|
|
|
import {
|
|
|
- cpSync, existsSync, lstatSync, mkdirSync, readdirSync,
|
|
|
+ cpSync, copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync,
|
|
|
readFileSync, realpathSync, rmSync, statSync, symlinkSync,
|
|
|
} from 'node:fs';
|
|
|
import { homedir, platform } from 'node:os';
|
|
|
@@ -208,10 +210,10 @@ function pointsTo(linkPath, targetPath) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Ensures the canonical agents directory exists as a real directory, so the
|
|
|
-// individual .md links have a place to live. An earlier version linked the
|
|
|
-// whole folder with a junction: such a link to our own source is unwound
|
|
|
-// here (removing the junction never touches the source files).
|
|
|
+// Ensures the canonical agents directory exists as a real directory receiving
|
|
|
+// the copied prompt files. An earlier version linked the whole folder with a
|
|
|
+// junction: such a link to our own source is unwound here (removing the
|
|
|
+// junction never touches the source files).
|
|
|
function ensureCanonicalAgentsDir() {
|
|
|
const current = lstatOrNull(CANONICAL_AGENTS_DIR);
|
|
|
if (!current) {
|
|
|
@@ -220,7 +222,6 @@ function ensureCanonicalAgentsDir() {
|
|
|
return { label: 'agents', status: 'added' };
|
|
|
}
|
|
|
if (current.isSymbolicLink()) {
|
|
|
- if (!pointsTo(CANONICAL_AGENTS_DIR, SOURCE_AGENTS_DIR)) return { label: 'agents', status: 'skipped' };
|
|
|
if (DRY_RUN) return { label: 'agents', status: 'relink' };
|
|
|
rmSync(CANONICAL_AGENTS_DIR, { recursive: true, force: true });
|
|
|
mkdirSync(CANONICAL_AGENTS_DIR, { recursive: true });
|
|
|
@@ -229,8 +230,9 @@ function ensureCanonicalAgentsDir() {
|
|
|
return current.isDirectory() ? { label: 'agents', status: 'up-to-date' } : { label: 'agents', status: 'skipped' };
|
|
|
}
|
|
|
|
|
|
-// Copies the selected skills into the canonical directory and links the agent
|
|
|
-// prompt files individually (no copies, ever). Returns the list of actions.
|
|
|
+// Copies the selected skills into the canonical directory and copies the
|
|
|
+// agent prompts into ~/.agents/agents (files on windows cannot be linked
|
|
|
+// without privileges, so agents use plain copies). Returns the list of actions.
|
|
|
function syncCanonical(skills, agentFiles) {
|
|
|
const actions = [];
|
|
|
for (const name of skills) {
|
|
|
@@ -244,7 +246,13 @@ function syncCanonical(skills, agentFiles) {
|
|
|
actions.push({ label: `skills/${name}`, status });
|
|
|
}
|
|
|
for (const file of agentFiles) {
|
|
|
- actions.push({ label: `agents/${file}`, status: ensureLink(join(CANONICAL_AGENTS_DIR, file), join(SOURCE_AGENTS_DIR, file)) });
|
|
|
+ const source = join(SOURCE_AGENTS_DIR, file);
|
|
|
+ const dest = join(CANONICAL_AGENTS_DIR, file);
|
|
|
+ const status = fileHash(source) === fileHash(dest) ? 'up-to-date' : existsSync(dest) ? 'updated' : 'added';
|
|
|
+ if (status !== 'up-to-date' && !DRY_RUN) {
|
|
|
+ copyFileSync(source, dest);
|
|
|
+ }
|
|
|
+ actions.push({ label: `agents/${file}`, status });
|
|
|
}
|
|
|
return actions;
|
|
|
}
|