@dawn0608/dsh-skill-manager 0.1.7 → 0.2.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/lib/index.js CHANGED
@@ -7413,10 +7413,10 @@ var require_dist = __commonJS({
7413
7413
 
7414
7414
  // src/index.js
7415
7415
  import { homedir } from "node:os";
7416
- import { join as join6, resolve as resolve2 } from "node:path";
7416
+ import { join as join4, resolve as resolve2 } from "node:path";
7417
7417
 
7418
7418
  // src/host.js
7419
- import { isAbsolute as isAbsolute2, join as join5, normalize } from "node:path";
7419
+ import { isAbsolute as isAbsolute2, join as join3, normalize } from "node:path";
7420
7420
 
7421
7421
  // src/host-bundle.js
7422
7422
  var import_yaml = __toESM(require_dist(), 1);
@@ -7586,7 +7586,7 @@ import {
7586
7586
  unlinkSync,
7587
7587
  writeFileSync
7588
7588
  } from "node:fs";
7589
- import { dirname, join as join2 } from "node:path";
7589
+ import { dirname, join as join2, resolve, toNamespacedPath } from "node:path";
7590
7590
  import { randomUUID } from "node:crypto";
7591
7591
  function stat(path) {
7592
7592
  try {
@@ -7599,6 +7599,13 @@ function stat(path) {
7599
7599
  function identity(stats) {
7600
7600
  return `${stats.dev}:${stats.ino}:${stats.birthtimeNs}`;
7601
7601
  }
7602
+ function linkTargets(path, expected) {
7603
+ const target = resolve(dirname(path), readlinkSync(path));
7604
+ return process.platform === "win32" ? toNamespacedPath(target).toLowerCase() === toNamespacedPath(resolve(expected)).toLowerCase() : target === resolve(expected);
7605
+ }
7606
+ function directoryLink(target, path) {
7607
+ symlinkSync(resolve(target), path, process.platform === "win32" ? "junction" : "dir");
7608
+ }
7602
7609
  function safeDirectory(path, create) {
7603
7610
  let stats = stat(path);
7604
7611
  if (!stats && create) {
@@ -7685,19 +7692,30 @@ function createSkillStore(home) {
7685
7692
  function takeLock() {
7686
7693
  for (let generation = 0; ; generation += 1) {
7687
7694
  const path = join2(manager, `.write-lock-${generation}`);
7695
+ const temp = join2(manager, `.lock-${randomUUID()}`);
7696
+ writeFileSync(temp, JSON.stringify({ pid: process.pid, nonce: randomUUID() }), { flag: "wx", mode: 384 });
7697
+ const token = identity(stat(temp));
7688
7698
  try {
7689
- symlinkSync(`dsh-skill-manager-pid-${process.pid}`, path, "file");
7690
- const token = identity(stat(path));
7699
+ linkSync(temp, path);
7700
+ if (!sameEntry(path, token)) throw new ManagerError("conflict");
7691
7701
  return () => unlinkOwned(path, token);
7692
7702
  } catch (error) {
7693
7703
  if (error.code !== "EEXIST") throw error;
7704
+ } finally {
7705
+ unlinkSync(temp);
7694
7706
  }
7695
7707
  const stats = stat(path);
7696
- if (!stats) throw new ManagerError("conflict");
7697
- if (!stats.isSymbolicLink()) throw new ManagerError("conflict");
7698
- const owner = /^dsh-skill-manager-pid-([1-9][0-9]*)$/.exec(readlinkSync(path));
7699
- const pid = Number(owner?.[1]);
7700
- if (!Number.isSafeInteger(pid) || pid > 2147483647) throw new ManagerError("conflict");
7708
+ if (!stats?.isFile()) throw new ManagerError("conflict");
7709
+ let owner;
7710
+ try {
7711
+ owner = JSON.parse(readRegularFile(path, 1024, identity(stats)));
7712
+ } catch {
7713
+ throw new ManagerError("conflict");
7714
+ }
7715
+ const pid = owner?.pid;
7716
+ if (!Number.isSafeInteger(pid) || pid < 1 || pid > 2147483647 || !MANAGED_ID.test(owner?.nonce ?? "")) {
7717
+ throw new ManagerError("conflict");
7718
+ }
7701
7719
  try {
7702
7720
  process.kill(pid, 0);
7703
7721
  } catch (error) {
@@ -7713,6 +7731,9 @@ function createSkillStore(home) {
7713
7731
  try {
7714
7732
  if (!roots()) throw new ManagerError("conflict");
7715
7733
  return operation();
7734
+ } catch (error) {
7735
+ if (["EBUSY", "EACCES", "EPERM"].includes(error.code)) throw new ManagerError("conflict");
7736
+ throw error;
7716
7737
  } finally {
7717
7738
  release();
7718
7739
  }
@@ -7732,13 +7753,13 @@ function createSkillStore(home) {
7732
7753
  if (!activation) return false;
7733
7754
  if (!stats?.isSymbolicLink() || activation.link !== null && identity(stats) !== activation.link) return false;
7734
7755
  const anchor = anchorPath(activation);
7735
- return readlinkSync(paths.active) === anchor && stat(anchor)?.isSymbolicLink() === true && readlinkSync(anchor) === paths.bundle;
7756
+ return linkTargets(paths.active, anchor) && stat(anchor)?.isSymbolicLink() === true && linkTargets(anchor, paths.bundle);
7736
7757
  }
7737
7758
  function discardAnchor(paths, activation) {
7738
7759
  if (!activation) return;
7739
7760
  const anchor = anchorPath(activation);
7740
7761
  const stats = stat(anchor);
7741
- if (stats?.isSymbolicLink() && readlinkSync(anchor) === paths.bundle) unlinkOwned(anchor, identity(stats));
7762
+ if (stats?.isSymbolicLink() && linkTargets(anchor, paths.bundle)) unlinkOwned(anchor, identity(stats));
7742
7763
  }
7743
7764
  function lookup(name, id) {
7744
7765
  const paths = pathsFor(name);
@@ -7804,7 +7825,7 @@ function createSkillStore(home) {
7804
7825
  assertFree(skills, [name, `${name}.md`]);
7805
7826
  const activation = { anchor: randomUUID(), link: null };
7806
7827
  const anchor = anchorPath(activation);
7807
- symlinkSync(paths.bundle, anchor, "dir");
7828
+ directoryLink(paths.bundle, anchor);
7808
7829
  let prepared;
7809
7830
  try {
7810
7831
  prepared = writeRecord(paths.record, { ...record.data, activation }, record.token);
@@ -7814,7 +7835,7 @@ function createSkillStore(home) {
7814
7835
  }
7815
7836
  discardAnchor(paths, record.data.activation);
7816
7837
  try {
7817
- symlinkSync(anchor, paths.active, "dir");
7838
+ directoryLink(anchor, paths.active);
7818
7839
  } catch (error) {
7819
7840
  if (error.code === "EEXIST") throw new ManagerError("conflict");
7820
7841
  throw error;
@@ -7929,181 +7950,6 @@ function createSkillStore(home) {
7929
7950
  return { install, list, read, remove, setEnabled };
7930
7951
  }
7931
7952
 
7932
- // src/shared-policy.js
7933
- import { randomUUID as randomUUID2 } from "node:crypto";
7934
- import { mkdir, readlink, rename, symlink, unlink, writeFile, lstat as lstat3 } from "node:fs/promises";
7935
- import { dirname as dirname3, join as join4 } from "node:path";
7936
-
7937
- // src/shared-sync.js
7938
- import { constants as constants3, watchFile, unwatchFile } from "node:fs";
7939
- import { lstat as lstat2, open as open2, readdir, realpath as realpath2 } from "node:fs/promises";
7940
- import { dirname as dirname2, join as join3, resolve } from "node:path";
7941
- var syncDirectory = (home) => join3(home, "skill-manager", "shared-sync");
7942
- var syncStateFile = (home) => join3(syncDirectory(home), "state.json");
7943
- var MAX_STATE_BYTES = 2 * 1024 * 1024;
7944
- var disabled = { modelInvocable: false, userInvocable: false };
7945
- async function regularFile(path, limit) {
7946
- var _stack = [];
7947
- try {
7948
- const before = await lstat2(path);
7949
- if (!before.isFile() || before.size > limit) throw new ManagerError("invalid");
7950
- const file = __using(_stack, await open2(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0) | (constants3.O_NONBLOCK ?? 0)), true);
7951
- const stat2 = await file.stat();
7952
- if (!stat2.isFile() || stat2.dev !== before.dev || stat2.ino !== before.ino || stat2.size > limit) {
7953
- throw new ManagerError("conflict");
7954
- }
7955
- const bytes = Buffer.alloc(stat2.size + 1);
7956
- const { bytesRead } = await file.read(bytes);
7957
- if (bytesRead !== stat2.size) throw new ManagerError("conflict");
7958
- return bytes.subarray(0, bytesRead);
7959
- } catch (_) {
7960
- var _error = _, _hasError = true;
7961
- } finally {
7962
- var _promise = __callDispose(_stack, _error, _hasError);
7963
- _promise && await _promise;
7964
- }
7965
- }
7966
- async function sharedSkills(agentsHome) {
7967
- if (!agentsHome) return /* @__PURE__ */ new Map();
7968
- const root = await realpath2(join3(agentsHome, "skills")).catch((error) => {
7969
- if (error.code !== "ENOENT") throw error;
7970
- });
7971
- const skills = /* @__PURE__ */ new Map();
7972
- if (!root) return skills;
7973
- for (const entry of await readdir(root, { withFileTypes: true })) {
7974
- if (!entry.isDirectory() || !isManagedName(entry.name) || entry.name === "synced" || entry.name === "anthropic-skills") continue;
7975
- const path = join3(root, entry.name, "SKILL.md");
7976
- try {
7977
- if (await realpath2(path) !== path) continue;
7978
- const parsed = parseSkillFile(await regularFile(path, MAX_BUNDLE_BYTES));
7979
- if (parsed.name === entry.name) skills.set(path, { path, name: parsed.name, description: parsed.description });
7980
- } catch (error) {
7981
- }
7982
- }
7983
- return skills;
7984
- }
7985
- async function readSyncState(home) {
7986
- try {
7987
- const value = JSON.parse(await regularFile(syncStateFile(home), MAX_STATE_BYTES));
7988
- if (![1, 2].includes(value.version) || !isRecord(value.entries)) throw new ManagerError("unavailable");
7989
- return value;
7990
- } catch (error) {
7991
- if (error.code === "ENOENT") return null;
7992
- throw new ManagerError("unavailable");
7993
- }
7994
- }
7995
- function sharedPolicyEnabled(entry) {
7996
- if (!entry) return void 0;
7997
- if (typeof entry.policyEnabled === "boolean") return entry.policyEnabled;
7998
- if (entry.pending && (entry.codex === false || entry.claude === "off")) return false;
7999
- return entry.enabled;
8000
- }
8001
- function registerSharedPolicy(skills, home, agentsHome) {
8002
- if (typeof skills.registerProvider !== "function") return () => {
8003
- };
8004
- let invalidate;
8005
- const dispose = skills.registerProvider((control) => {
8006
- invalidate = control.invalidate;
8007
- return {
8008
- name: "skill-manager-shared-policy",
8009
- async list() {
8010
- const state = await readSharedPolicy(home);
8011
- if (!Object.values(state.entries).some((enabled) => enabled === false)) return [];
8012
- return [...(await sharedSkills(agentsHome)).values()].filter((skill) => state.entries[skill.path] === false).map((skill) => ({
8013
- ...skill,
8014
- provider: "skill-manager-shared-policy",
8015
- source: "user-agents",
8016
- // Immediately ahead of the default user-agents provider. Project and user-dsh win.
8017
- rank: 499,
8018
- invocation: disabled,
8019
- locator: skill.path,
8020
- resourceBase: { kind: "directory", path: dirname2(skill.path) }
8021
- }));
8022
- },
8023
- async get(candidate) {
8024
- try {
8025
- if (await realpath2(candidate.path) !== candidate.path) return void 0;
8026
- const { name, description, body } = parseSkillFile(await regularFile(candidate.path, MAX_BUNDLE_BYTES));
8027
- return { ...candidate, name, description, content: body };
8028
- } catch (error) {
8029
- if (error.code === "ENOENT" || error.code === "invalid") return void 0;
8030
- throw error;
8031
- }
8032
- }
8033
- };
8034
- });
8035
- const path = policyFile(home);
8036
- watchFile(path, { persistent: false, interval: 500 }, invalidate);
8037
- return () => {
8038
- unwatchFile(path, invalidate);
8039
- dispose();
8040
- };
8041
- }
8042
-
8043
- // src/shared-policy.js
8044
- var policyFile = (home) => join4(home, "skill-manager", "shared-policy.json");
8045
- var missing = (error) => {
8046
- if (error.code !== "ENOENT") throw error;
8047
- return null;
8048
- };
8049
- async function readSharedPolicy(home) {
8050
- const bytes = await regularFile(policyFile(home), 2 * 1024 * 1024).catch(missing);
8051
- if (bytes) {
8052
- const state = JSON.parse(bytes);
8053
- if (state.version !== 1 || !isRecord(state.entries) || Object.values(state.entries).some((x) => typeof x !== "boolean")) {
8054
- throw new ManagerError("unavailable");
8055
- }
8056
- return state;
8057
- }
8058
- const previous = await readSyncState(home);
8059
- return { version: 1, entries: Object.fromEntries(Object.entries(previous?.entries ?? {}).map(([path, entry]) => [path, sharedPolicyEnabled(entry)]).filter(([, enabled]) => typeof enabled === "boolean")) };
8060
- }
8061
- async function locked(home, operation) {
8062
- const path = policyFile(home);
8063
- await mkdir(dirname3(path), { recursive: true, mode: 448 });
8064
- for (let generation = 0; generation < 1e3; generation++) {
8065
- const lock = path + ".lock-" + generation;
8066
- const owner = `${process.pid}-${randomUUID2()}`;
8067
- try {
8068
- await symlink(owner, lock);
8069
- } catch (error) {
8070
- if (error.code !== "EEXIST") throw error;
8071
- const target = await readlink(lock).catch(() => null);
8072
- const pid = Number(/^([1-9][0-9]*)-/.exec(target)?.[1]);
8073
- if (!Number.isSafeInteger(pid) || pid > 2147483647) throw new ManagerError("conflict");
8074
- try {
8075
- process.kill(pid, 0);
8076
- } catch (failure) {
8077
- if (failure.code === "ESRCH") continue;
8078
- }
8079
- throw new ManagerError("conflict");
8080
- }
8081
- try {
8082
- return await operation(path);
8083
- } finally {
8084
- if (await readlink(lock).catch(() => null) === owner) await unlink(lock);
8085
- }
8086
- }
8087
- throw new ManagerError("conflict");
8088
- }
8089
- async function save(path, state) {
8090
- const temp = path + "." + randomUUID2();
8091
- try {
8092
- await writeFile(temp, JSON.stringify(state) + "\n", { flag: "wx", mode: 384 });
8093
- await rename(temp, path);
8094
- } finally {
8095
- await unlink(temp).catch(missing);
8096
- }
8097
- }
8098
- async function setSharedEnabled(home, skillPath, enabled) {
8099
- if (typeof enabled !== "boolean") throw new ManagerError("invalid");
8100
- return locked(home, async (path) => {
8101
- const state = await readSharedPolicy(home);
8102
- state.entries[skillPath] = enabled;
8103
- await save(path, state);
8104
- });
8105
- }
8106
-
8107
7953
  // src/host.js
8108
7954
  var ROUTE = "/api/skill-manager";
8109
7955
  var MAX_REQUEST_BYTES = 12 * 1024 * 1024;
@@ -8144,7 +7990,7 @@ function parseId(id) {
8144
7990
  function skillProvenance(skill, provenance) {
8145
7991
  return skill.source === "bundled" ? { origin: "builtin" } : provenance(skill);
8146
7992
  }
8147
- function catalogRow(skill, provenance, shared, available) {
7993
+ function catalogRow(skill, provenance) {
8148
7994
  const origin = skillProvenance(skill, provenance)?.origin;
8149
7995
  return {
8150
7996
  id: `catalog:${skill.name}`,
@@ -8152,7 +7998,7 @@ function catalogRow(skill, provenance, shared, available) {
8152
7998
  description: skill.description,
8153
7999
  group: SOURCES.get(skill.source) ?? "local",
8154
8000
  source: SOURCES.has(skill.source) ? skill.source : "other",
8155
- enabled: skill.source === "user-agents" && shared?.[skill.path] !== void 0 ? shared[skill.path] : skill.source === "user-agents" && available.has(skill.path) ? true : null,
8001
+ enabled: null,
8156
8002
  shadowed: false,
8157
8003
  ...origin && { origin }
8158
8004
  };
@@ -8161,7 +8007,7 @@ function matchesManaged(skill, managed) {
8161
8007
  if (!skill || skill.source !== "user-dsh" || typeof skill.path !== "string") return false;
8162
8008
  if (!managed.enabled && !managed.activeMissing) return false;
8163
8009
  const path = normalize(skill.path);
8164
- return path === managed.resolvedFile || path === join5(managed.bundle, "SKILL.md") || path === join5(managed.active, "SKILL.md");
8010
+ return path === managed.resolvedFile || path === join3(managed.bundle, "SKILL.md") || path === join3(managed.active, "SKILL.md");
8165
8011
  }
8166
8012
  function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agentPresets }) {
8167
8013
  const store = createSkillStore(home);
@@ -8195,12 +8041,9 @@ function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agent
8195
8041
  }
8196
8042
  async function list(options) {
8197
8043
  const workspaces = workspaceRegistry.list().map(({ id, title }) => ({ id, title }));
8198
- const [catalog, provenance, sync, policy, available] = await Promise.all([
8044
+ const [catalog, provenance] = await Promise.all([
8199
8045
  snapshot(options),
8200
- readProvenance(agentsHome),
8201
- readSyncState(home).catch(() => ({ error: "unavailable" })),
8202
- readSharedPolicy(home),
8203
- sharedSkills(agentsHome)
8046
+ readProvenance(agentsHome)
8204
8047
  ]);
8205
8048
  const managed = store.list();
8206
8049
  const byName = new Map(catalog.skills.map((skill) => [skill.name, skill]));
@@ -8219,17 +8062,12 @@ function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agent
8219
8062
  shadowed: skill.enabled && Boolean(match) && !ownWinner
8220
8063
  });
8221
8064
  }
8222
- rows.push(...[...byName.values()].map((skill) => catalogRow(skill, provenance, policy.entries, available)));
8065
+ rows.push(...[...byName.values()].map((skill) => catalogRow(skill, provenance)));
8223
8066
  rows.sort((left, right) => left.name.localeCompare(right.name) || Number(right.group === "imported") - Number(left.group === "imported"));
8224
- const syncError = sync?.error ?? Object.values(sync?.entries ?? {}).find((entry) => entry.error)?.error;
8225
- const stopped = sync && !sync.error && (!Number.isFinite(Date.parse(sync.health?.lastAttemptAt)) || Date.now() - Date.parse(sync.health.lastAttemptAt) > 15e3);
8226
- const healthError = stopped ? "sharedStopped" : sync?.health?.status === "unavailable" ? "sharedUnavailable" : null;
8227
8067
  return {
8228
- ...sync?.health && { syncHealth: sync.health },
8229
8068
  skills: rows,
8230
8069
  workspaces,
8231
- complete: catalog.complete && managed.complete,
8232
- ...(healthError || syncError) && { syncError: healthError ?? (syncError === "conflict" ? "sharedConflict" : "sharedUnavailable") }
8070
+ complete: catalog.complete && managed.complete
8233
8071
  };
8234
8072
  }
8235
8073
  async function detail(id, options) {
@@ -8276,17 +8114,6 @@ function createSkillManager({ home, agentsHome, skills, workspaceRegistry, agent
8276
8114
  case "setEnabled":
8277
8115
  case "remove": {
8278
8116
  const { kind, name, installId } = parseId(payload.id);
8279
- if (kind === "catalog" && action === "setEnabled") {
8280
- if (typeof payload.enabled !== "boolean") throw new ManagerError("invalid");
8281
- return scoped(options, async (scopedOptions) => {
8282
- const skill = await load(name, scopedOptions);
8283
- const shared = await sharedSkills(agentsHome);
8284
- if (skill?.source !== "user-agents" || !shared.has(skill.path)) throw new ManagerError("forbidden");
8285
- signal?.throwIfAborted();
8286
- await setSharedEnabled(home, skill.path, payload.enabled);
8287
- return null;
8288
- });
8289
- }
8290
8117
  if (kind !== "managed") throw new ManagerError("forbidden");
8291
8118
  signal?.throwIfAborted();
8292
8119
  if (action === "setEnabled") {
@@ -8364,8 +8191,8 @@ function createFetchHandler(manager) {
8364
8191
  var inject = ["agentPresets", "connection", "skills", "workspaceRegistry"];
8365
8192
  function resolveHome(configured, fallback) {
8366
8193
  return configured?.trim() ? resolve2(
8367
- configured === "~" ? homedir() : /^~[/\\]/.test(configured) ? join6(homedir(), configured.slice(2)) : configured
8368
- ) : join6(homedir(), fallback);
8194
+ configured === "~" ? homedir() : /^~[/\\]/.test(configured) ? join4(homedir(), configured.slice(2)) : configured
8195
+ ) : join4(homedir(), fallback);
8369
8196
  }
8370
8197
  function apply(ctx) {
8371
8198
  const home = resolveHome(process.env.DSH_HOME, ".dsh");
@@ -8377,9 +8204,6 @@ function apply(ctx) {
8377
8204
  workspaceRegistry: ctx.workspaceRegistry,
8378
8205
  agentPresets: ctx.agentPresets
8379
8206
  });
8380
- if (typeof ctx.skills.registerProvider === "function") {
8381
- ctx.effect(() => registerSharedPolicy(ctx.skills, home, agentsHome), "skill-manager: shared invocation policy");
8382
- }
8383
8207
  ctx.effect(() => ctx.connection.fetch.register({
8384
8208
  path: ROUTE,
8385
8209
  methods: ["POST"],