@michengai/dsh-skills-manager 1.2.0 → 1.2.2

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.
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
- import { readFile } from "node:fs/promises";
3
+ import { readFile, rename, unlink, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { basename, delimiter, dirname, isAbsolute, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -212,6 +212,162 @@ async function status(options, target) {
212
212
  canAutoUpdate: target.canAutoUpdate
213
213
  };
214
214
  }
215
+ function releaseAgeSpec(packageSpec) {
216
+ const at = packageSpec.lastIndexOf("@");
217
+ if (at <= 0) return void 0;
218
+ const name = packageSpec.slice(0, at);
219
+ const version = packageSpec.slice(at + 1);
220
+ if (!isPackageName(name) || !isReleaseVersion(version)) return void 0;
221
+ return { name, version };
222
+ }
223
+ function isPackageName(name) {
224
+ return /^@[^/\s]+\/[^/\s]+$/.test(name) || /^[^@/\s]+$/.test(name);
225
+ }
226
+ function isReleaseVersion(version) {
227
+ return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version);
228
+ }
229
+ function unreadableReleaseAgeExclude() {
230
+ return new Error("\u53D1\u5E03\u5E74\u9F84\u767D\u540D\u5355\u65E0\u6CD5\u5B89\u5168\u5408\u5E76\uFF0C\u5DF2\u505C\u6B62\u66F4\u65B0\uFF0C\u907F\u514D\u8FD9\u4E2A profile \u4E4B\u540E\u65E0\u6CD5\u5B89\u88C5\u63D2\u4EF6\u3002");
231
+ }
232
+ function excludeEntry(indent, name, versions, comment = "") {
233
+ const entry = `${indent}- '${name}@${versions.join(" || ")}'`;
234
+ return comment === "" ? entry : `${entry} ${comment}`;
235
+ }
236
+ function parseExcludeLine(line) {
237
+ const dash = line.indexOf("-");
238
+ if (!/^\s+-/.test(line) || dash < 0) return void 0;
239
+ const indent = line.slice(0, dash);
240
+ const raw = line.slice(dash + 1).trim();
241
+ let body = raw;
242
+ let comment = "";
243
+ if (raw.startsWith("'") || raw.startsWith('"')) {
244
+ const quote = raw[0] ?? "'";
245
+ const end = raw.lastIndexOf(quote);
246
+ if (end <= 0) return void 0;
247
+ const rest = raw.slice(end + 1).trim();
248
+ if (rest !== "" && !rest.startsWith("#")) return void 0;
249
+ if (rest.startsWith("#")) comment = rest;
250
+ body = raw.slice(1, end);
251
+ } else if (raw.includes("#")) return void 0;
252
+ if (body.includes("#")) return void 0;
253
+ const versionAt = body.lastIndexOf("@");
254
+ if (versionAt > 0) {
255
+ const name = body.slice(0, versionAt);
256
+ const versions = body.slice(versionAt + 1).split(" || ");
257
+ if (isPackageName(name) && versions.every(isReleaseVersion)) return { indent, name, versions, comment };
258
+ return void 0;
259
+ }
260
+ if (isPackageName(body) || /^@[^/\s]+\/\*$/.test(body)) return { indent, name: body, versions: void 0, comment };
261
+ return void 0;
262
+ }
263
+ function mergeMinimumReleaseAgeExclude(yaml, packageSpec) {
264
+ const spec = releaseAgeSpec(packageSpec);
265
+ if (spec === void 0) throw new Error("\u66F4\u65B0\u7248\u672C\u65E0\u6CD5\u5199\u5165\u53D1\u5E03\u5E74\u9F84\u767D\u540D\u5355\uFF0C\u5DF2\u505C\u6B62\u66F4\u65B0\u3002");
266
+ const eol = yaml.includes("\r\n") ? "\r\n" : "\n";
267
+ const text = yaml.replaceAll("\r\n", "\n");
268
+ const next = mergeReleaseAgeText(text, spec.name, spec.version);
269
+ return eol === "\r\n" ? next.replaceAll("\n", "\r\n") : next;
270
+ }
271
+ function mergeReleaseAgeText(yaml, name, version) {
272
+ if (/minimumReleaseAgeExclude:[^\n\r]/.test(yaml)) throw unreadableReleaseAgeExclude();
273
+ const marker = /(?:^|\n)minimumReleaseAgeExclude:[ \t]*(?:\n|$)/.exec(yaml);
274
+ if (marker === null) {
275
+ const base = yaml.endsWith("\n") || yaml.length === 0 ? yaml : `${yaml}
276
+ `;
277
+ const gap = base.length === 0 || base.endsWith("\n\n") ? "" : "\n";
278
+ return `${base}${gap}minimumReleaseAgeExclude:
279
+ - '${name}@${version}'
280
+ `;
281
+ }
282
+ const keyAt = (marker.index ?? 0) + (marker[0].startsWith("\n") ? 1 : 0);
283
+ let cursor = keyAt + "minimumReleaseAgeExclude:".length;
284
+ while (cursor < yaml.length && (yaml[cursor] === " " || yaml[cursor] === " ")) cursor += 1;
285
+ if (yaml[cursor] !== "\n") throw unreadableReleaseAgeExclude();
286
+ const bodyStart = cursor + 1;
287
+ const lines = [];
288
+ let bodyEnd = bodyStart;
289
+ while (bodyEnd <= yaml.length) {
290
+ const nextNl = yaml.indexOf("\n", bodyEnd);
291
+ const lineEnd = nextNl === -1 ? yaml.length : nextNl;
292
+ const line = yaml.slice(bodyEnd, lineEnd);
293
+ const ended = line.trim() !== "" && !/^[ \t]*#/.test(line) && !/^[ \t]/.test(line);
294
+ if (ended) break;
295
+ if (line.trim() !== "" && !/^[ \t]*#/.test(line) && !/^[ \t]+-/.test(line)) throw unreadableReleaseAgeExclude();
296
+ lines.push(line);
297
+ if (nextNl === -1) {
298
+ bodyEnd = yaml.length;
299
+ break;
300
+ }
301
+ bodyEnd = nextNl + 1;
302
+ if (bodyEnd === yaml.length) break;
303
+ }
304
+ let ours;
305
+ const rewritten = lines.map((line, index) => {
306
+ if (line.trim() === "" || /^[ \t]*#/.test(line)) return line;
307
+ const parsed = parseExcludeLine(line);
308
+ if (parsed === void 0) throw unreadableReleaseAgeExclude();
309
+ if (parsed.name !== name) return line;
310
+ if (ours === void 0) {
311
+ ours = { index, indent: parsed.indent, versions: parsed.versions, comment: parsed.comment };
312
+ return line;
313
+ }
314
+ if (ours.versions !== void 0 && parsed.versions !== void 0) ours.versions = [...ours.versions, ...parsed.versions.filter((item) => !ours?.versions?.includes(item))];
315
+ return "";
316
+ });
317
+ if (ours?.versions === void 0 && ours !== void 0) return yaml;
318
+ const indent = ours?.indent ?? lines.find((line) => /^[ \t]+-/.test(line))?.match(/^([ \t]+)/)?.[1] ?? " ";
319
+ const versions = ours?.versions === void 0 ? [version] : ours.versions.includes(version) ? ours.versions : [...ours.versions, version];
320
+ const entry = excludeEntry(indent, name, versions, ours?.comment ?? "");
321
+ if (ours === void 0) rewritten.push(entry);
322
+ else rewritten[ours.index] = entry;
323
+ const body = rewritten.filter((line) => line !== "").join("\n");
324
+ const prefix = yaml.slice(0, bodyStart);
325
+ const suffix = yaml.slice(bodyEnd);
326
+ const joined = `${prefix}${body}${body.length === 0 || body.endsWith("\n") ? "" : "\n"}${suffix}`;
327
+ return joined.endsWith("\n") ? joined : `${joined}
328
+ `;
329
+ }
330
+ async function guardReleaseAgeExclude(profileDir, packageSpec) {
331
+ const file = resolve(profileDir, "pnpm-workspace.yaml");
332
+ const current = existsSync(file) ? await readFile(file, "utf8") : "packages:\n - .\n";
333
+ mergeMinimumReleaseAgeExclude(current, packageSpec);
334
+ }
335
+ async function replaceTextFile(file, next) {
336
+ const tmp = `${file}.${process.pid}.tmp`;
337
+ const backup = `${file}.${process.pid}.bak`;
338
+ await writeFile(tmp, next, "utf8");
339
+ try {
340
+ await rename(tmp, file);
341
+ return;
342
+ } catch (error) {
343
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
344
+ if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES") {
345
+ await unlink(tmp).catch(() => {
346
+ });
347
+ throw error;
348
+ }
349
+ }
350
+ await unlink(backup).catch(() => {
351
+ });
352
+ await rename(file, backup);
353
+ try {
354
+ await rename(tmp, file);
355
+ } catch (error) {
356
+ await rename(backup, file).catch(() => {
357
+ });
358
+ await unlink(tmp).catch(() => {
359
+ });
360
+ throw error;
361
+ }
362
+ await unlink(backup).catch(() => {
363
+ });
364
+ }
365
+ async function recordReleaseAgeExclude(profileDir, packageSpec) {
366
+ const file = resolve(profileDir, "pnpm-workspace.yaml");
367
+ const current = existsSync(file) ? await readFile(file, "utf8") : "packages:\n - .\n";
368
+ const next = mergeMinimumReleaseAgeExclude(current, packageSpec);
369
+ if (next !== current) await replaceTextFile(file, next);
370
+ }
215
371
  async function runCliInstall(target, packageSpec) {
216
372
  if (target.cliEntry === void 0) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002");
217
373
  await new Promise((resolvePromise, reject) => {
@@ -245,12 +401,15 @@ async function runCliInstall(target, packageSpec) {
245
401
  }
246
402
  async function install(target, packageSpec) {
247
403
  if (target.desktopPnpm !== void 0) {
404
+ await guardReleaseAgeExclude(target.profileDir, packageSpec);
248
405
  const handle = target.desktopPnpm.runPlugin(["add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], target.profileDir);
249
406
  const result = await handle.done;
250
407
  if (result.exitCode !== 0) throw new Error(`\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(result.exitCode)}\u3002`);
408
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
251
409
  return;
252
410
  }
253
411
  if (target.packageManager !== void 0) {
412
+ await guardReleaseAgeExclude(target.profileDir, packageSpec);
254
413
  const manager = target.packageManager;
255
414
  await new Promise((resolvePromise, reject) => {
256
415
  const child = spawn(manager.command, [...manager.args, "add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], {
@@ -280,14 +439,21 @@ async function install(target, packageSpec) {
280
439
  else reject(new Error(detail.trim() || `\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(code)}`));
281
440
  });
282
441
  });
442
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
283
443
  return;
284
444
  }
285
445
  if (target.pluginManager !== void 0) {
446
+ await guardReleaseAgeExclude(target.profileDir, packageSpec);
286
447
  const result = await target.pluginManager.installBundle(packageSpec);
287
- if (result.application === "applied" || result.application === "restart-required" || result.application === "overridden") return;
448
+ if (result.application === "applied" || result.application === "restart-required" || result.application === "overridden") {
449
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
450
+ return;
451
+ }
288
452
  throw new Error(result.error?.message || result.packageResult?.output || "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u670D\u52A1\u7AEF\u65E5\u5FD7\u3002");
289
453
  }
290
- return runCliInstall(target, packageSpec);
454
+ await guardReleaseAgeExclude(target.profileDir, packageSpec);
455
+ await runCliInstall(target, packageSpec);
456
+ await recordReleaseAgeExclude(target.profileDir, packageSpec);
291
457
  }
292
458
  function json(response, statusCode, value) {
293
459
  response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
@@ -360,6 +526,8 @@ export {
360
526
  isDshCliEntry,
361
527
  isNewerVersion,
362
528
  isTrustedUpdateRequest,
529
+ mergeMinimumReleaseAgeExclude,
530
+ recordReleaseAgeExclude,
363
531
  registerPluginUpdater,
364
532
  resolveUpdateRuntime,
365
533
  shouldNotifyParent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-skills-manager",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "DSH Skills Manager — 在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills · Load and safely manage local Agent Skills in DSH",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -35,14 +35,15 @@
35
35
  "client": {
36
36
  "inject": [
37
37
  "@deepseek-ai/dsh-client-ui-slots",
38
- "@deepseek-ai/dsh-client-locale"
38
+ "@deepseek-ai/dsh-client-locale",
39
+ "@deepseek-ai/dsh-client-ui-primitives"
39
40
  ],
40
41
  "platform": "web"
41
42
  }
42
43
  },
43
44
  "scripts": {
44
45
  "build": "node scripts/build.mjs",
45
- "test": "npm run typecheck && npm run build && node test/build-test.mjs && node test/route-lifecycle.test.mjs && node test/repositories.test.mjs && node test/repository-install-targets.test.mjs && node test/workspace-target.test.mjs && node test/workspace-routes.test.mjs && node test/workspace-list.test.mjs && node test/destination-picker.test.mjs && node test/workspace-project-page.test.mjs && node test/repository-ui.test.mjs && node test/catalog-skill.test.mjs && node test/skills-sh-catalog.test.mjs && node test/clawhub-catalog.test.mjs && node --test test/catalog-state.test.mjs test/catalog-lifecycle.test.mjs && node test/theme-select.test.mjs && node --test test/antd-theme.browser.test.mjs && node test/host-fallback.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/disabled-external-native.test.mjs && node test/categories.test.mjs && node test/categories-ui.test.mjs && node --experimental-strip-types --test test/settings-nav-icon.test.mjs && node test/readonly-discovery.test.mjs && node test/project-root-skills.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node --test test/plugin-updater.test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
46
+ "test": "node scripts/check-committed-lib.mjs && npm run typecheck && npm run build && node test/build-test.mjs && node test/route-lifecycle.test.mjs && node test/repositories.test.mjs && node test/repository-install-targets.test.mjs && node test/workspace-target.test.mjs && node test/workspace-routes.test.mjs && node test/workspace-list.test.mjs && node test/destination-picker.test.mjs && node test/workspace-project-page.test.mjs && node test/repository-ui.test.mjs && node test/catalog-skill.test.mjs && node test/skills-sh-catalog.test.mjs && node test/clawhub-catalog.test.mjs && node --test test/catalog-state.test.mjs test/catalog-lifecycle.test.mjs && node test/theme-select.test.mjs && node --test test/antd-theme.browser.test.mjs && node test/host-fallback.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/disabled-external-native.test.mjs && node test/categories.test.mjs && node test/categories-ui.test.mjs && node --experimental-strip-types --test test/settings-nav-icon.test.mjs && node test/readonly-discovery.test.mjs && node test/project-root-skills.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node --test test/plugin-updater.test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
46
47
  "verify": "node scripts/sync-hosts.mjs && npm test && npm run pack:check && node test/pack-test.mjs",
47
48
  "pack:check": "npm pack --dry-run --json",
48
49
  "prepack": "npm run typecheck && npm run build",
@@ -56,13 +57,14 @@
56
57
  },
57
58
  "peerDependencies": {
58
59
  "@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
59
- "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
60
- "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
61
- "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
62
- "@deepseek-ai/dsh-session": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
63
- "@deepseek-ai/dsh-skill": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
64
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
65
- "@deepseek-ai/dsh-web-app": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
60
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
61
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
62
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
63
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
64
+ "@deepseek-ai/dsh-session": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
65
+ "@deepseek-ai/dsh-skill": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
66
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
67
+ "@deepseek-ai/dsh-web-app": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
66
68
  "react": "^18.2.0",
67
69
  "react-dom": "^18.2.0"
68
70
  },