@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.
@@ -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 stat = await file.stat();
8264
- if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino || stat.size > limit) {
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(stat.size + 1);
8267
+ const bytes = Buffer.alloc(stat2.size + 1);
8268
8268
  const { bytesRead } = await file.read(bytes);
8269
- if (bytesRead !== stat.size) throw new ManagerError("conflict");
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 linkIdentity(stat) {
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 { valid: true, owned: true, identity: linkIdentity(await lstat2(link)) };
8444
+ return true;
8453
8445
  } catch (error) {
8454
- if (error.code === "EEXIST") return { valid: false, owned: false };
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 stat = await lstat2(directory).catch(missing);
8468
- if (stat && (!stat.isDirectory() || await realpath2(directory) !== directory)) throw new ManagerError("conflict");
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 native = await codexRules(await readDocument(codexPath, parse));
8518
- const initialClaude = overrides(await readDocument(claudePath));
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 link = await reconcileLink(claudeRoot, skill, old).catch(() => ({ valid: false, owned: false }));
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 readDocument(codexPath, parse)), skill);
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 readDocument(codexPath, parse)), skill);
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 readDocument(claudePath);
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 readDocument(claudePath))[name] ?? null;
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();