@dawn0608/dsh-skill-manager 0.1.5 → 0.1.6
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/README.md +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +2 -10
- package/lib/client.js.map +2 -2
- package/lib/sync-daemon.js +42 -45
- package/lib/sync-daemon.js.map +3 -3
- package/package.json +1 -1
package/lib/sync-daemon.js
CHANGED
|
@@ -7414,7 +7414,7 @@ var require_dist = __commonJS({
|
|
|
7414
7414
|
// src/sync-daemon.js
|
|
7415
7415
|
import { spawn } from "node:child_process";
|
|
7416
7416
|
import { realpathSync } from "node:fs";
|
|
7417
|
-
import { chmod, lstat as lstat2, mkdir, readlink, realpath as realpath2, rename, symlink, unlink, writeFile } from "node:fs/promises";
|
|
7417
|
+
import { chmod, lstat as lstat2, mkdir, readlink, realpath as realpath2, rename, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
7418
7418
|
import { createServer } from "node:net";
|
|
7419
7419
|
import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } from "node:path";
|
|
7420
7420
|
import { pathToFileURL } from "node:url";
|
|
@@ -8260,13 +8260,13 @@ async function regularFile(path, limit) {
|
|
|
8260
8260
|
const before = await lstat(path);
|
|
8261
8261
|
if (!before.isFile() || before.size > limit) throw new ManagerError("invalid");
|
|
8262
8262
|
const file = __using(_stack, await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0)), true);
|
|
8263
|
-
const
|
|
8264
|
-
if (!
|
|
8263
|
+
const stat2 = await file.stat();
|
|
8264
|
+
if (!stat2.isFile() || stat2.dev !== before.dev || stat2.ino !== before.ino || stat2.size > limit) {
|
|
8265
8265
|
throw new ManagerError("conflict");
|
|
8266
8266
|
}
|
|
8267
|
-
const bytes = Buffer.alloc(
|
|
8267
|
+
const bytes = Buffer.alloc(stat2.size + 1);
|
|
8268
8268
|
const { bytesRead } = await file.read(bytes);
|
|
8269
|
-
if (bytesRead !==
|
|
8269
|
+
if (bytesRead !== stat2.size) throw new ManagerError("conflict");
|
|
8270
8270
|
return bytes.subarray(0, bytesRead);
|
|
8271
8271
|
} catch (_) {
|
|
8272
8272
|
var _error = _, _hasError = true;
|
|
@@ -8352,6 +8352,11 @@ async function readDocument(path, parse3 = JSON.parse) {
|
|
|
8352
8352
|
if (!isRecord(value)) throw new ManagerError("invalid");
|
|
8353
8353
|
return { path, bytes, value };
|
|
8354
8354
|
}
|
|
8355
|
+
async function readNativeDocument(path, parse3, required = true) {
|
|
8356
|
+
const document = await readDocument(path, parse3);
|
|
8357
|
+
if (required && !document.bytes?.length) throw new ManagerError("unavailable");
|
|
8358
|
+
return document;
|
|
8359
|
+
}
|
|
8355
8360
|
async function writeDocument({ path, bytes }, value) {
|
|
8356
8361
|
const content = Buffer.from(JSON.stringify(value, null, 2) + "\n");
|
|
8357
8362
|
if (content.equals(bytes ?? Buffer.alloc(0))) return;
|
|
@@ -8429,43 +8434,23 @@ async function writeCodex(binary, codexHome, path, enabled) {
|
|
|
8429
8434
|
child.kill();
|
|
8430
8435
|
}
|
|
8431
8436
|
}
|
|
8432
|
-
function
|
|
8433
|
-
return { dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs };
|
|
8434
|
-
}
|
|
8435
|
-
function sameLink(stat, identity) {
|
|
8436
|
-
return identity && stat.dev === identity.dev && stat.ino === identity.ino && stat.birthtimeMs === identity.birthtimeMs;
|
|
8437
|
-
}
|
|
8438
|
-
async function reconcileLink(claudeRoot, skill, old) {
|
|
8437
|
+
async function reconcileLink(claudeRoot, skill) {
|
|
8439
8438
|
const link = join2(claudeRoot, skill.name);
|
|
8440
8439
|
const target = dirname2(skill.path);
|
|
8441
8440
|
const existing = await lstat2(link).catch(missing);
|
|
8442
|
-
if (existing)
|
|
8443
|
-
const valid = existing.isSymbolicLink() && await realpath2(link).catch(missing) === target;
|
|
8444
|
-
return {
|
|
8445
|
-
valid,
|
|
8446
|
-
owned: Boolean(valid && old?.linkOwned && sameLink(existing, old.linkIdentity)),
|
|
8447
|
-
...valid && old?.linkOwned && sameLink(existing, old.linkIdentity) && { identity: old.linkIdentity }
|
|
8448
|
-
};
|
|
8449
|
-
}
|
|
8441
|
+
if (existing) return existing.isSymbolicLink() && await realpath2(link).catch(missing) === target;
|
|
8450
8442
|
try {
|
|
8451
8443
|
await symlink(target, link, "dir");
|
|
8452
|
-
return
|
|
8444
|
+
return true;
|
|
8453
8445
|
} catch (error) {
|
|
8454
|
-
if (error.code === "EEXIST") return
|
|
8446
|
+
if (error.code === "EEXIST") return false;
|
|
8455
8447
|
throw error;
|
|
8456
8448
|
}
|
|
8457
8449
|
}
|
|
8458
|
-
async function dropLink(root, entry, skillPath) {
|
|
8459
|
-
if (!entry.linkOwned || !entry.linkIdentity || !isManagedName(entry.name)) return;
|
|
8460
|
-
const link = join2(root, entry.name);
|
|
8461
|
-
const existing = await lstat2(link).catch(missing);
|
|
8462
|
-
if (!existing?.isSymbolicLink() || !sameLink(existing, entry.linkIdentity)) return;
|
|
8463
|
-
if (resolve2(root, await readlink(link)) === dirname2(skillPath)) await unlink(link).catch(missing);
|
|
8464
|
-
}
|
|
8465
8450
|
async function localDocument(workspace) {
|
|
8466
8451
|
const directory = join2(workspace, ".claude");
|
|
8467
|
-
const
|
|
8468
|
-
if (
|
|
8452
|
+
const stat2 = await lstat2(directory).catch(missing);
|
|
8453
|
+
if (stat2 && (!stat2.isDirectory() || await realpath2(directory) !== directory)) throw new ManagerError("conflict");
|
|
8469
8454
|
return readDocument(join2(directory, "settings.local.json"));
|
|
8470
8455
|
}
|
|
8471
8456
|
async function projectPaths(cwd) {
|
|
@@ -8511,11 +8496,18 @@ async function createCoordinator({ home, agentsHome, claudeHome, codexHome, code
|
|
|
8511
8496
|
const stateDocument = await readDocument(statePath);
|
|
8512
8497
|
const previous = stateDocument.bytes ? await readSyncState(home) : { version: 1, entries: {}, projects: {} };
|
|
8513
8498
|
if (!previous || !isRecord(previous.projects)) throw new ManagerError("invalid");
|
|
8499
|
+
const sharedRoot = await stat(join2(agentsHome, "skills")).catch(missing);
|
|
8500
|
+
if (!sharedRoot?.isDirectory()) throw new ManagerError("unavailable");
|
|
8514
8501
|
const skills = await sharedSkills(agentsHome);
|
|
8515
8502
|
const codexPath = join2(codexHome, "config.toml");
|
|
8516
8503
|
const claudePath = join2(claudeHome, "settings.json");
|
|
8517
|
-
const
|
|
8518
|
-
const
|
|
8504
|
+
const hasBaseline = Object.keys(previous.entries).length > 0;
|
|
8505
|
+
const knownNative = previous.nativePresent ?? { codex: hasBaseline, claude: hasBaseline };
|
|
8506
|
+
const codexDocument = await readNativeDocument(codexPath, parse, knownNative.codex);
|
|
8507
|
+
const claudeDocument = await readNativeDocument(claudePath, JSON.parse, knownNative.claude);
|
|
8508
|
+
const native = await codexRules(codexDocument);
|
|
8509
|
+
const initialClaude = overrides(claudeDocument);
|
|
8510
|
+
const nativePresent = { codex: Boolean(codexDocument.bytes?.length), claude: Boolean(claudeDocument.bytes?.length) };
|
|
8519
8511
|
const projects = [];
|
|
8520
8512
|
const enrollment = { ...previous.projects };
|
|
8521
8513
|
if (action.type === "enroll") {
|
|
@@ -8538,14 +8530,22 @@ async function createCoordinator({ home, agentsHome, claudeHome, codexHome, code
|
|
|
8538
8530
|
throw new ManagerError("conflict");
|
|
8539
8531
|
}
|
|
8540
8532
|
const entries = {};
|
|
8533
|
+
for (const [path, old] of Object.entries(previous.entries)) {
|
|
8534
|
+
if (skills.has(path)) continue;
|
|
8535
|
+
entries[path] = { ...old, error: "unavailable" };
|
|
8536
|
+
for (const project of projects) {
|
|
8537
|
+
const baseline = enrollment[project.directory];
|
|
8538
|
+
if (Object.hasOwn(project.previous, old.name)) baseline[old.name] = project.previous[old.name];
|
|
8539
|
+
else delete baseline[old.name];
|
|
8540
|
+
}
|
|
8541
|
+
}
|
|
8541
8542
|
const cleanup = /* @__PURE__ */ new Map();
|
|
8542
8543
|
for (const skill of skills.values()) {
|
|
8543
8544
|
const { name, path } = skill;
|
|
8544
8545
|
const old = previous.entries[path];
|
|
8545
8546
|
const codex = readCodex(native, skill);
|
|
8546
8547
|
const claude = initialClaude[name] ?? null;
|
|
8547
|
-
const
|
|
8548
|
-
const linkOk = link.valid;
|
|
8548
|
+
const linkOk = await reconcileLink(claudeRoot, skill).catch(() => false);
|
|
8549
8549
|
const rawIntents = localIntents(projects, name);
|
|
8550
8550
|
const local = [];
|
|
8551
8551
|
let projectCollision = false;
|
|
@@ -8573,8 +8573,6 @@ async function createCoordinator({ home, agentsHome, claudeHome, codexHome, code
|
|
|
8573
8573
|
enabled: conflict ? old?.enabled ?? enabled : enabled,
|
|
8574
8574
|
codex: old?.pending && !explicit ? old.codex : codex.enabled,
|
|
8575
8575
|
claude: old?.pending && !explicit ? old.claude : claude,
|
|
8576
|
-
linkOwned: link.owned,
|
|
8577
|
-
...link.identity && { linkIdentity: link.identity },
|
|
8578
8576
|
...contested && { contested: true },
|
|
8579
8577
|
...conflict && { error: "conflict" }
|
|
8580
8578
|
};
|
|
@@ -8588,7 +8586,7 @@ async function createCoordinator({ home, agentsHome, claudeHome, codexHome, code
|
|
|
8588
8586
|
}
|
|
8589
8587
|
entries[path] = entry;
|
|
8590
8588
|
}
|
|
8591
|
-
const state = { version: 1, entries, projects: enrollment, health: {
|
|
8589
|
+
const state = { version: 1, entries, projects: enrollment, nativePresent, health: {
|
|
8592
8590
|
...previous.health,
|
|
8593
8591
|
status: "syncing",
|
|
8594
8592
|
lastAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8601,22 +8599,24 @@ async function createCoordinator({ home, agentsHome, claudeHome, codexHome, code
|
|
|
8601
8599
|
try {
|
|
8602
8600
|
const skill = skills.get(path);
|
|
8603
8601
|
if (entry.pending) {
|
|
8604
|
-
const current = readCodex(await codexRules(await
|
|
8602
|
+
const current = readCodex(await codexRules(await readNativeDocument(codexPath, parse, nativePresent.codex)), skill);
|
|
8605
8603
|
if (current.enabled !== enabled) {
|
|
8606
8604
|
if (current.byName || current.enabled !== entry.codex) throw new ManagerError("conflict");
|
|
8607
8605
|
await writeSkill(codexBinary, codexHome, current.path, enabled);
|
|
8608
|
-
const confirmed = readCodex(await codexRules(await
|
|
8606
|
+
const confirmed = readCodex(await codexRules(await readNativeDocument(codexPath, parse)), skill);
|
|
8609
8607
|
if (confirmed.enabled !== enabled) throw new ManagerError("conflict");
|
|
8608
|
+
nativePresent.codex = true;
|
|
8610
8609
|
}
|
|
8611
8610
|
entry.codex = enabled;
|
|
8612
8611
|
await checkpoint();
|
|
8613
|
-
const settings = await
|
|
8612
|
+
const settings = await readNativeDocument(claudePath, JSON.parse, nativePresent.claude);
|
|
8614
8613
|
const currentClaude = overrides(settings)[name] ?? null;
|
|
8615
8614
|
if (currentClaude !== "off" !== enabled) {
|
|
8616
8615
|
if (currentClaude !== entry.claude) throw new ManagerError("conflict");
|
|
8617
8616
|
await writeClaude(settings, name, enabled ? void 0 : "off");
|
|
8617
|
+
nativePresent.claude = true;
|
|
8618
8618
|
}
|
|
8619
|
-
entry.claude = overrides(await
|
|
8619
|
+
entry.claude = overrides(await readNativeDocument(claudePath, JSON.parse, nativePresent.claude))[name] ?? null;
|
|
8620
8620
|
if (entry.claude !== "off" !== enabled) throw new ManagerError("conflict");
|
|
8621
8621
|
await checkpoint();
|
|
8622
8622
|
delete entry.pending;
|
|
@@ -8634,9 +8634,6 @@ async function createCoordinator({ home, agentsHome, claudeHome, codexHome, code
|
|
|
8634
8634
|
await checkpoint();
|
|
8635
8635
|
}
|
|
8636
8636
|
}
|
|
8637
|
-
for (const [path, old] of Object.entries(previous.entries)) {
|
|
8638
|
-
if (!skills.has(path)) await dropLink(claudeRoot, old, path);
|
|
8639
|
-
}
|
|
8640
8637
|
state.health.status = Object.values(entries).some((entry) => entry.error) ? "degraded" : "ok";
|
|
8641
8638
|
if (state.health.status === "ok") state.health.lastSuccessAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8642
8639
|
await checkpoint();
|