dsh-skills-manager 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A tree-external DeepSeek Harness plugin for managing reusable Agent Skills.
4
4
 
5
- It adds a **Skill 管理** item to the Web Settings sidebar and a wrench control beside the composer attachment button. Skills are copied into a private Harness library, then enabled independently at two scopes:
5
+ It adds a **Skill 管理** item to the Web Settings sidebar and a wrench control beside the composer attachment button. Skills are installed into a private Harness library, then enabled independently at two scopes:
6
6
 
7
7
  - **Global**: available from every workspace.
8
8
  - **Project**: available from one registered workspace and its child directories.
@@ -11,7 +11,7 @@ An imported skill is disabled by default. The plugin uses the official `ctx.skil
11
11
 
12
12
  ## Install
13
13
 
14
- Build a tarball or install the package from a trusted source:
14
+ The bundled `skills` CLI requires Node.js 22.20.0 or newer. Build a tarball or install the package from a trusted source:
15
15
 
16
16
  ```sh
17
17
  pnpm install
@@ -32,25 +32,26 @@ After changing the bundle or installed dependencies, restart the Harness service
32
32
  1. Open Web Settings, then select **Skill 管理** in the sidebar.
33
33
  2. Choose **本机** to select a local skill folder, or **仓库** to enter a GitHub `owner/repo`, HTTPS URL, SSH URL, or other Git URL.
34
34
  3. Enable **全局** or select a workspace and enable **项目**.
35
- 4. Choose **检查更新** to compare installed content with local sources or freshly cloned repositories. Use the refresh icon on a row to install a detected change.
35
+ 4. Use the refresh icon on a row to reinstall that skill from the source recorded in `skills-lock.json`.
36
36
  5. Use the wrench control beside the composer attachment button to toggle installed skills for the current session workspace only.
37
- 6. Remove an installed skill with **移除**. This deletes only the plugin's copied library entry, not the local source or remote repository.
37
+ 6. Remove an installed skill with **移除**. This deletes only the plugin's private library entry, not the local source or remote repository.
38
38
 
39
- Repository imports discover a root `SKILL.md`, immediate entries under `skills/`, and immediate entries under `.agents/skills/`. Private repositories use the machine's existing Git credential helper or SSH configuration. Do not put credentials in repository URLs; the plugin stores the repository URL and skill subpath, never a token.
39
+ Repository imports and refreshes are delegated to the `skills` CLI. It supports GitHub `owner/repo`, HTTPS, SSH, Git and file URLs, and reuses the machine's existing Git credential helper or SSH configuration. Do not put credentials in repository URLs; the plugin rejects embedded credentials before invoking the CLI.
40
40
 
41
- Updates preserve global and project enablement. Imports created by older plugin versions have no recorded source; their first update asks for a local source directory and tracks it for later checks.
42
-
43
- The imported format follows the official filesystem provider: directory bundles use `<name>/SKILL.md`; flat skills use `<name>.md`. YAML frontmatter must contain a kebab-case `name` and non-empty `description`. Companion resources are copied with directory bundles.
41
+ Updates preserve global and project enablement. New imports and refreshes require standard directory bundles: `<name>/SKILL.md` with kebab-case `name` and non-empty `description`. Flat `<name>.md` imports are not supported.
44
42
 
45
43
  ## Storage
46
44
 
47
45
  By default the plugin uses the actual Harness home resolved from `DSH_HOME`:
48
46
 
49
47
  ```text
50
- $DSH_HOME/skill-manager/library/
48
+ $DSH_HOME/skill-manager/.agents/skills/
49
+ $DSH_HOME/skill-manager/skills-lock.json
51
50
  $DSH_HOME/skill-manager/state.json
52
51
  ```
53
52
 
53
+ `skills-lock.json` is the only source-of-origin record. `state.json` stores only global and project enablement.
54
+
54
55
  Override the storage root in `cordis.patch.yml`:
55
56
 
56
57
  ```yaml
@@ -69,13 +70,13 @@ pnpm test
69
70
  pnpm validate
70
71
  ```
71
72
 
72
- The test suite covers scope resolution, path containment, local updates, Git clone/update behavior, credential URL rejection, and cleanup of persisted references. `pnpm validate` checks the DSH bundle shape.
73
+ The test suite covers scope resolution, current-state validation, local and Git refreshes through the private CLI library, flat-format rejection, credential URL rejection, and removal cleanup. `pnpm validate` checks the DSH bundle shape.
73
74
 
74
75
  ## Design boundaries
75
76
 
76
- - This plugin does not call or shell out to the `skills` CLI. Git repositories are fetched with `git clone --depth 1` using an argv array, never through a shell.
77
- - It does not rewrite an imported `SKILL.md` and does not alter the source folder or repository. Skill symlinks that escape their source root are rejected.
78
- - Skills already present in other filesystem roots remain owned by those roots; import them into this library when they should be managed here.
77
+ - This plugin runs the packaged `skills` CLI with an argv array and a private storage `cwd`; it never modifies the active workspace's `.agents/skills` directory.
78
+ - It does not rewrite an imported `SKILL.md` or alter the source folder or repository. Broken or escaping source symlinks are rejected before the CLI copies anything.
79
+ - Skills already present in other filesystem roots remain owned by those roots; import them into this private library when they should be managed here.
79
80
  - Import and mutation RPCs are loopback-only because they write local files.
80
81
  - DSH is in developer preview; bundle and Cordis APIs can change between Harness releases.
81
82
 
package/lib/client.js CHANGED
@@ -135,26 +135,26 @@ const createLucideIcon = (iconName, iconNode) => {
135
135
 
136
136
  //#endregion
137
137
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/wrench.mjs
138
- const __iconNode$12 = [["path", {
138
+ const __iconNode$11 = [["path", {
139
139
  d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",
140
140
  key: "1ngwbx"
141
141
  }]];
142
- const Wrench = createLucideIcon("wrench", __iconNode$12);
142
+ const Wrench = createLucideIcon("wrench", __iconNode$11);
143
143
 
144
144
  //#endregion
145
145
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/plus.mjs
146
- const __iconNode$11 = [["path", {
146
+ const __iconNode$10 = [["path", {
147
147
  d: "M5 12h14",
148
148
  key: "1ays0h"
149
149
  }], ["path", {
150
150
  d: "M12 5v14",
151
151
  key: "s699le"
152
152
  }]];
153
- const Plus = createLucideIcon("plus", __iconNode$11);
153
+ const Plus = createLucideIcon("plus", __iconNode$10);
154
154
 
155
155
  //#endregion
156
156
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/refresh-cw.mjs
157
- const __iconNode$10 = [
157
+ const __iconNode$9 = [
158
158
  ["path", {
159
159
  d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",
160
160
  key: "v9h5vc"
@@ -172,11 +172,11 @@ const __iconNode$10 = [
172
172
  key: "1cv678"
173
173
  }]
174
174
  ];
175
- const RefreshCw = createLucideIcon("refresh-cw", __iconNode$10);
175
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$9);
176
176
 
177
177
  //#endregion
178
178
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/search.mjs
179
- const __iconNode$9 = [["path", {
179
+ const __iconNode$8 = [["path", {
180
180
  d: "m21 21-4.34-4.34",
181
181
  key: "14j7rj"
182
182
  }], ["circle", {
@@ -185,22 +185,22 @@ const __iconNode$9 = [["path", {
185
185
  r: "8",
186
186
  key: "4ej97u"
187
187
  }]];
188
- const Search = createLucideIcon("search", __iconNode$9);
188
+ const Search = createLucideIcon("search", __iconNode$8);
189
189
 
190
190
  //#endregion
191
191
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/x.mjs
192
- const __iconNode$8 = [["path", {
192
+ const __iconNode$7 = [["path", {
193
193
  d: "M18 6 6 18",
194
194
  key: "1bl5f8"
195
195
  }], ["path", {
196
196
  d: "m6 6 12 12",
197
197
  key: "d8bk6v"
198
198
  }]];
199
- const X = createLucideIcon("x", __iconNode$8);
199
+ const X = createLucideIcon("x", __iconNode$7);
200
200
 
201
201
  //#endregion
202
202
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/trash-2.mjs
203
- const __iconNode$7 = [
203
+ const __iconNode$6 = [
204
204
  ["path", {
205
205
  d: "M10 11v6",
206
206
  key: "nco0om"
@@ -222,11 +222,11 @@ const __iconNode$7 = [
222
222
  key: "e791ji"
223
223
  }]
224
224
  ];
225
- const Trash2 = createLucideIcon("trash-2", __iconNode$7);
225
+ const Trash2 = createLucideIcon("trash-2", __iconNode$6);
226
226
 
227
227
  //#endregion
228
228
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/globe.mjs
229
- const __iconNode$6 = [
229
+ const __iconNode$5 = [
230
230
  ["circle", {
231
231
  cx: "12",
232
232
  cy: "12",
@@ -242,11 +242,11 @@ const __iconNode$6 = [
242
242
  key: "9i4pu4"
243
243
  }]
244
244
  ];
245
- const Globe = createLucideIcon("globe", __iconNode$6);
245
+ const Globe = createLucideIcon("globe", __iconNode$5);
246
246
 
247
247
  //#endregion
248
248
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/folder-git.mjs
249
- const __iconNode$5 = [
249
+ const __iconNode$4 = [
250
250
  ["circle", {
251
251
  cx: "12",
252
252
  cy: "13",
@@ -266,11 +266,11 @@ const __iconNode$5 = [
266
266
  key: "1pygq7"
267
267
  }]
268
268
  ];
269
- const FolderGit = createLucideIcon("folder-git", __iconNode$5);
269
+ const FolderGit = createLucideIcon("folder-git", __iconNode$4);
270
270
 
271
271
  //#endregion
272
272
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/git-branch.mjs
273
- const __iconNode$4 = [
273
+ const __iconNode$3 = [
274
274
  ["path", {
275
275
  d: "M15 6a9 9 0 0 0-9 9V3",
276
276
  key: "1cii5b"
@@ -288,11 +288,11 @@ const __iconNode$4 = [
288
288
  key: "fqmcym"
289
289
  }]
290
290
  ];
291
- const GitBranch = createLucideIcon("git-branch", __iconNode$4);
291
+ const GitBranch = createLucideIcon("git-branch", __iconNode$3);
292
292
 
293
293
  //#endregion
294
294
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/package-open.mjs
295
- const __iconNode$3 = [
295
+ const __iconNode$2 = [
296
296
  ["path", {
297
297
  d: "M12 22v-9",
298
298
  key: "x3hkom"
@@ -310,19 +310,19 @@ const __iconNode$3 = [
310
310
  key: "12ttoo"
311
311
  }]
312
312
  ];
313
- const PackageOpen = createLucideIcon("package-open", __iconNode$3);
313
+ const PackageOpen = createLucideIcon("package-open", __iconNode$2);
314
314
 
315
315
  //#endregion
316
316
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/puzzle.mjs
317
- const __iconNode$2 = [["path", {
317
+ const __iconNode$1 = [["path", {
318
318
  d: "M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z",
319
319
  key: "w46dr5"
320
320
  }]];
321
- const Puzzle = createLucideIcon("puzzle", __iconNode$2);
321
+ const Puzzle = createLucideIcon("puzzle", __iconNode$1);
322
322
 
323
323
  //#endregion
324
324
  //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/circle-alert.mjs
325
- const __iconNode$1 = [
325
+ const __iconNode = [
326
326
  ["circle", {
327
327
  cx: "12",
328
328
  cy: "12",
@@ -344,23 +344,14 @@ const __iconNode$1 = [
344
344
  key: "4dfq90"
345
345
  }]
346
346
  ];
347
- const CircleAlert = createLucideIcon("circle-alert", __iconNode$1);
348
-
349
- //#endregion
350
- //#region node_modules/.pnpm/lucide-react@1.31.0_react@18.3.1/node_modules/lucide-react/dist/esm/icons/check.mjs
351
- const __iconNode = [["path", {
352
- d: "M20 6 9 17l-5-5",
353
- key: "1gmf2c"
354
- }]];
355
- const Check = createLucideIcon("check", __iconNode);
347
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode);
356
348
 
357
349
  //#endregion
358
350
  //#region lib/types/client/index.js
359
351
  const inject = [
360
352
  "connection",
361
353
  "slots",
362
- "workspaces",
363
- "sessions"
354
+ "workspaces"
364
355
  ];
365
356
  const ICON_SPIN = ["dsh-skill-manager-spin", "to { transform: rotate(360deg); }"];
366
357
  function ensureKeyframes() {
@@ -370,6 +361,10 @@ function ensureKeyframes() {
370
361
  style.id = ICON_SPIN[0];
371
362
  style.textContent = `
372
363
  @keyframes ${ICON_SPIN[0]} { ${ICON_SPIN[1]} }
364
+ .dsh-skill-manager-row:hover {
365
+ border-color: ${tokens.borderStrong} !important;
366
+ background: ${tokens.surfaceHover} !important;
367
+ }
373
368
  @container (max-width: 360px) {
374
369
  .dsh-skill-manager-row { grid-template-columns: minmax(0, 1fr) !important; }
375
370
  .dsh-skill-manager-row-icon { display: none !important; }
@@ -390,8 +385,7 @@ const tokens = {
390
385
  success: "var(--fg-success, #16a34a)",
391
386
  danger: "var(--fg-danger, #dc2626)",
392
387
  secondary: "var(--fg-secondary, #64748b)",
393
- surfaceHover: "var(--bg-hover, rgba(148, 163, 184, .08))",
394
- surfaceActive: "var(--bg-active, rgba(99, 102, 241, .12))"
388
+ surfaceHover: "var(--bg-hover, rgba(148, 163, 184, .08))"
395
389
  };
396
390
  const panelStyle = {
397
391
  containerType: "inline-size",
@@ -424,12 +418,6 @@ function buttonStyle(tone, disabled) {
424
418
  color: "var(--fg-on-accent, #fff)",
425
419
  border: "none"
426
420
  };
427
- if (tone === "danger") return {
428
- ...base,
429
- background: "transparent",
430
- color: tokens.danger,
431
- border: `1px solid ${tokens.border}`
432
- };
433
421
  return {
434
422
  ...base,
435
423
  background: "transparent",
@@ -471,18 +459,22 @@ const selectStyle = {
471
459
  color: "inherit",
472
460
  fontSize: 13
473
461
  };
462
+ const checkboxStyle = {
463
+ width: 16,
464
+ height: 16,
465
+ margin: 0,
466
+ accentColor: tokens.accent,
467
+ flex: "0 0 auto"
468
+ };
474
469
  function apply(ctx) {
475
- const client = ctx;
476
- const list = async (projectRoot) => {
477
- const result = await client.connection.rpc.call("/skill-manager", "list", projectRoot ? { projectRoot } : {});
478
- if (!result.ok) throw new Error(result.error.message);
479
- return result.value;
480
- };
481
- const mutate = async (endpoint, payload) => {
482
- const result = await client.connection.rpc.call("/skill-manager", endpoint, payload);
470
+ const connection = ctx.connection;
471
+ const request = async (endpoint, payload = {}) => {
472
+ const result = await connection.rpc.call("/skill-manager", endpoint, payload);
483
473
  if (!result.ok) throw new Error(result.error.message);
484
474
  return result.value;
485
475
  };
476
+ const list = (projectRoot) => request("list", projectRoot ? { projectRoot } : {});
477
+ const mutate = request;
486
478
  ctx.slots.inject("settings.section", () => ctx.slots.register({
487
479
  name: "settings.section",
488
480
  id: "skills",
@@ -492,7 +484,7 @@ function apply(ctx) {
492
484
  list,
493
485
  mutate,
494
486
  pickDirectory: () => ctx.workspaces.pickDirectory(),
495
- loopback: client.connection.isLoopback
487
+ loopback: connection.isLoopback
496
488
  })
497
489
  }, SkillManagerSection));
498
490
  ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
@@ -501,8 +493,7 @@ function apply(ctx) {
501
493
  order: 10,
502
494
  inject: () => ({
503
495
  list,
504
- mutate,
505
- sessions: ctx.sessions.list
496
+ mutate
506
497
  })
507
498
  }, ComposerSkillButton));
508
499
  }
@@ -519,8 +510,9 @@ function SkillManagerSection(props) {
519
510
  (0, react.useEffect)(() => {
520
511
  ensureKeyframes();
521
512
  }, []);
513
+ const userChoseGlobal = (0, react.useRef)(false);
522
514
  (0, react.useEffect)(() => {
523
- if (projectRoot === void 0 && workspaces.items.length > 0) setProjectRoot(workspaces.items[0].path);
515
+ if (projectRoot === void 0 && !userChoseGlobal.current && workspaces.items.length > 0) setProjectRoot(workspaces.items[0].path);
524
516
  if (projectRoot !== void 0 && !workspaces.items.some((item) => item.path === projectRoot)) setProjectRoot(workspaces.items[0]?.path);
525
517
  }, [projectRoot, workspaces.items]);
526
518
  const refresh = (0, react.useCallback)(async () => {
@@ -550,18 +542,8 @@ function SkillManagerSection(props) {
550
542
  setBusy(false);
551
543
  }
552
544
  }, []);
553
- const filtered = (0, react.useMemo)(() => {
554
- const normalized = query.trim().toLocaleLowerCase();
555
- return (catalog?.entries ?? []).filter((entry) => normalized.length === 0 || `${entry.name} ${entry.description}`.toLocaleLowerCase().includes(normalized));
556
- }, [catalog, query]);
557
- const stats = (0, react.useMemo)(() => {
558
- const entries = catalog?.entries ?? [];
559
- return {
560
- total: entries.length,
561
- global: entries.filter((entry) => entry.globalEnabled).length,
562
- project: entries.filter((entry) => entry.projectEnabled).length
563
- };
564
- }, [catalog]);
545
+ const normalizedQuery = query.trim().toLocaleLowerCase();
546
+ const filtered = (catalog?.entries ?? []).filter((entry) => normalizedQuery.length === 0 || `${entry.name} ${entry.description}`.toLocaleLowerCase().includes(normalizedQuery));
565
547
  const importSkill = async () => {
566
548
  const path = await pickDirectory();
567
549
  if (path) await run(() => mutate("install", {
@@ -582,12 +564,8 @@ function SkillManagerSection(props) {
582
564
  }
583
565
  };
584
566
  const updateSkill = async (entry) => {
585
- let path;
586
- const remote = entry.sourceType === "git" || Boolean(entry.sourceUrl);
587
- if (!entry.updateSupported || entry.updateError && !remote) {
588
- path = await pickDirectory() ?? void 0;
589
- if (!path) return;
590
- }
567
+ const path = entry.updateSupported ? void 0 : await pickDirectory() ?? void 0;
568
+ if (!entry.updateSupported && !path) return;
591
569
  await run(() => mutate("update", {
592
570
  name: entry.name,
593
571
  path,
@@ -625,15 +603,6 @@ function SkillManagerSection(props) {
625
603
  gap: 8,
626
604
  flexWrap: "wrap"
627
605
  } }, (0, react.createElement)("button", {
628
- type: "button",
629
- style: buttonStyle("ghost", busy),
630
- onClick: () => void run(() => mutate("check-updates", { projectRoot })),
631
- disabled: busy,
632
- title: "检查已记录来源是否有变化"
633
- }, (0, react.createElement)(RefreshCw, {
634
- size: 14,
635
- "aria-hidden": true
636
- }), "检查更新"), (0, react.createElement)("button", {
637
606
  type: "button",
638
607
  style: buttonStyle("ghost", busy || !loopback),
639
608
  onClick: () => setRepositoryOpen((value) => !value),
@@ -727,35 +696,30 @@ function SkillManagerSection(props) {
727
696
  minWidth: 0,
728
697
  alignItems: "center",
729
698
  gap: 8,
730
- flexWrap: "wrap",
731
699
  fontSize: 13,
732
700
  color: tokens.secondary
733
- } }, "项目范围", (0, react.createElement)("select", {
734
- value: projectRoot ?? "",
735
- onChange: (event) => setProjectRoot(event.currentTarget.value || void 0),
701
+ } }, (0, react.createElement)("span", { style: { flex: "0 0 auto" } }, "项目范围"), (0, react.createElement)("select", {
702
+ value: projectRoot ?? "__global__",
703
+ onChange: (event) => {
704
+ const value = event.currentTarget.value;
705
+ if (value === "__global__") {
706
+ userChoseGlobal.current = true;
707
+ setProjectRoot(void 0);
708
+ } else {
709
+ userChoseGlobal.current = false;
710
+ setProjectRoot(value);
711
+ }
712
+ },
736
713
  style: {
737
714
  ...selectStyle,
738
- width: "100%"
715
+ flex: "1 1 auto",
716
+ minWidth: 0,
717
+ maxWidth: "none"
739
718
  }
740
- }, (0, react.createElement)("option", { value: "" }, "只看全局"), ...workspaces.items.map((workspace) => (0, react.createElement)("option", {
719
+ }, (0, react.createElement)("option", { value: "__global__" }, "只看全局"), ...workspaces.items.map((workspace) => (0, react.createElement)("option", {
741
720
  key: workspace.workspaceId,
742
721
  value: workspace.path
743
- }, workspace.title))))), (0, react.createElement)("div", { style: {
744
- display: "flex",
745
- gap: 8,
746
- flexWrap: "wrap"
747
- } }, (0, react.createElement)(StatChip, {
748
- label: "已安装",
749
- value: stats.total
750
- }), (0, react.createElement)(StatChip, {
751
- label: "全局启用",
752
- value: stats.global,
753
- tone: "accent"
754
- }), projectRoot !== void 0 ? (0, react.createElement)(StatChip, {
755
- label: "项目启用",
756
- value: stats.project,
757
- tone: "accent"
758
- }) : null), error ? (0, react.createElement)("div", {
722
+ }, workspace.title))))), error ? (0, react.createElement)("div", {
759
723
  role: "alert",
760
724
  style: {
761
725
  display: "flex",
@@ -811,23 +775,6 @@ function SkillManagerSection(props) {
811
775
  }))
812
776
  }))));
813
777
  }
814
- function StatChip(props) {
815
- const active = props.tone === "accent" && props.value > 0;
816
- return (0, react.createElement)("span", { style: {
817
- display: "inline-flex",
818
- alignItems: "baseline",
819
- gap: 6,
820
- padding: "4px 10px",
821
- borderRadius: 999,
822
- fontSize: 12,
823
- border: `1px solid ${active ? "rgba(99, 102, 241, .4)" : tokens.border}`,
824
- color: active ? tokens.accent : tokens.secondary,
825
- background: active ? "rgba(99, 102, 241, .08)" : "transparent"
826
- } }, (0, react.createElement)("span", null, props.label), (0, react.createElement)("strong", { style: {
827
- fontSize: 13,
828
- fontWeight: 600
829
- } }, String(props.value)));
830
- }
831
778
  function EmptyState(props) {
832
779
  return (0, react.createElement)("div", { style: {
833
780
  display: "grid",
@@ -848,8 +795,8 @@ function EmptyState(props) {
848
795
  } }, props.hasAny ? "没有匹配的 skill,试试调整搜索词。" : "还没有导入任何 skill。点击右上角「导入」,从本机目录开始。"));
849
796
  }
850
797
  function ComposerSkillButton(props) {
851
- const { session, sessions, list, mutate } = props;
852
- const projectRoot = (0, react.useSyncExternalStore)(sessions.subscribe, sessions.getSnapshot, sessions.getSnapshot).byId[session.sessionId]?.cwd;
798
+ const { session, list, mutate } = props;
799
+ const projectRoot = session.cwd;
853
800
  const [open, setOpen] = (0, react.useState)(false);
854
801
  const containerRef = (0, react.useRef)(null);
855
802
  (0, react.useEffect)(() => {
@@ -1051,11 +998,13 @@ function ProjectSkillPopover(props) {
1051
998
  overflow: "hidden",
1052
999
  textOverflow: "ellipsis",
1053
1000
  whiteSpace: "nowrap"
1054
- } }, entry.name), (0, react.createElement)(Toggle, {
1001
+ } }, entry.name), (0, react.createElement)("input", {
1002
+ type: "checkbox",
1055
1003
  checked: entry.projectEnabled,
1056
1004
  disabled: busy,
1057
- onChange: (enabled) => void toggle(entry, enabled),
1058
- label: entry.name
1005
+ onChange: (event) => void toggle(entry, event.currentTarget.checked),
1006
+ "aria-label": `${entry.name} 项目开关`,
1007
+ style: checkboxStyle
1059
1008
  })))), busy ? (0, react.createElement)("div", { style: {
1060
1009
  display: "flex",
1061
1010
  justifyContent: "flex-end",
@@ -1068,7 +1017,6 @@ function ProjectSkillPopover(props) {
1068
1017
  }
1069
1018
  function SkillRow(props) {
1070
1019
  const { entry, projectLabel, projectAvailable, busy, onGlobalChange, onProjectChange, onUpdate, onRemove } = props;
1071
- const [hover, setHover] = (0, react.useState)(false);
1072
1020
  return (0, react.createElement)("article", {
1073
1021
  className: "dsh-skill-manager-row",
1074
1022
  style: {
@@ -1083,12 +1031,10 @@ function SkillRow(props) {
1083
1031
  boxSizing: "border-box",
1084
1032
  padding: "12px 14px",
1085
1033
  borderRadius: 10,
1086
- border: `1px solid ${hover ? tokens.borderStrong : tokens.border}`,
1087
- background: hover ? tokens.surfaceHover : "transparent",
1034
+ border: `1px solid ${tokens.border}`,
1035
+ background: "transparent",
1088
1036
  transition: "border-color .15s ease, background .15s ease"
1089
- },
1090
- onMouseEnter: () => setHover(true),
1091
- onMouseLeave: () => setHover(false)
1037
+ }
1092
1038
  }, (0, react.createElement)("div", {
1093
1039
  className: "dsh-skill-manager-row-icon",
1094
1040
  style: {
@@ -1128,18 +1074,7 @@ function SkillRow(props) {
1128
1074
  active: entry.projectEnabled,
1129
1075
  icon: FolderGit,
1130
1076
  label: projectLabel
1131
- }) : null, entry.updateAvailable ? (0, react.createElement)("span", { style: {
1132
- flex: "0 0 auto",
1133
- fontSize: 11,
1134
- color: tokens.accent
1135
- } }, "有更新") : null, entry.updateError ? (0, react.createElement)("span", {
1136
- style: {
1137
- flex: "0 0 auto",
1138
- fontSize: 11,
1139
- color: tokens.danger
1140
- },
1141
- title: entry.updateError
1142
- }, "来源异常") : null), entry.description ? (0, react.createElement)("p", { style: {
1077
+ }) : null), entry.description ? (0, react.createElement)("p", { style: {
1143
1078
  margin: 0,
1144
1079
  color: tokens.secondary,
1145
1080
  lineHeight: 1.45,
@@ -1160,27 +1095,31 @@ function SkillRow(props) {
1160
1095
  gap: 10,
1161
1096
  flexWrap: "wrap"
1162
1097
  }
1163
- }, (0, react.createElement)(Toggle, {
1098
+ }, (0, react.createElement)("input", {
1099
+ type: "checkbox",
1164
1100
  checked: entry.globalEnabled,
1165
1101
  disabled: busy,
1166
- onChange: onGlobalChange,
1167
- label: `${entry.name} 全局开关`,
1168
- title: "全局启用"
1169
- }), projectAvailable ? (0, react.createElement)(Toggle, {
1102
+ onChange: (event) => onGlobalChange(event.currentTarget.checked),
1103
+ "aria-label": `${entry.name} 全局开关`,
1104
+ title: "全局启用",
1105
+ style: checkboxStyle
1106
+ }), projectAvailable ? (0, react.createElement)("input", {
1107
+ type: "checkbox",
1170
1108
  checked: entry.projectEnabled,
1171
1109
  disabled: busy,
1172
- onChange: onProjectChange,
1173
- label: `${entry.name} 项目开关`,
1174
- title: `${projectLabel} 启用`
1110
+ onChange: (event) => onProjectChange(event.currentTarget.checked),
1111
+ "aria-label": `${entry.name} 项目开关`,
1112
+ title: `${projectLabel} 启用`,
1113
+ style: checkboxStyle
1175
1114
  }) : null, (0, react.createElement)("button", {
1176
1115
  type: "button",
1177
1116
  style: {
1178
1117
  ...iconButtonStyle,
1179
- color: entry.updateAvailable ? tokens.accent : tokens.secondary
1118
+ color: tokens.secondary
1180
1119
  },
1181
1120
  disabled: busy,
1182
1121
  onClick: onUpdate,
1183
- title: entry.updateSupported && !entry.updateError ? entry.sourceType === "git" ? "从远程仓库更新" : "从原始目录更新" : "选择来源目录并更新",
1122
+ title: entry.updateSupported ? entry.sourceType === "git" ? "从远程仓库更新" : "从原始目录更新" : "选择来源目录并更新",
1184
1123
  "aria-label": `更新 ${entry.name}`
1185
1124
  }, (0, react.createElement)(RefreshCw, {
1186
1125
  size: 15,
@@ -1201,12 +1140,12 @@ function SkillRow(props) {
1201
1140
  }))));
1202
1141
  }
1203
1142
  function SourceBadge(props) {
1204
- const remote = props.entry.sourceType === "git" || Boolean(props.entry.sourceUrl);
1205
- const local = props.entry.sourceType === "local" || Boolean(props.entry.sourcePath);
1143
+ const remote = props.entry.sourceType === "git";
1144
+ const local = props.entry.sourceType === "local";
1206
1145
  const Icon$1 = remote ? GitBranch : local ? FolderGit : CircleAlert;
1207
1146
  const label = remote ? "Git" : local ? "本机" : "未知";
1208
1147
  return (0, react.createElement)("span", {
1209
- title: remote ? props.entry.sourceUrl : local ? props.entry.sourcePath : "旧版本导入,首次更新需重新选择来源",
1148
+ title: remote ? props.entry.sourceUrl : local ? props.entry.sourcePath : "来源未知,更新时需选择本机来源目录",
1210
1149
  style: {
1211
1150
  display: "inline-flex",
1212
1151
  alignItems: "center",
@@ -1252,49 +1191,6 @@ function StatusBadge(props) {
1252
1191
  "aria-hidden": true
1253
1192
  }), props.active ? `${props.label}开` : `${props.label}关`);
1254
1193
  }
1255
- function Toggle(props) {
1256
- const { checked, disabled, onChange, label, title } = props;
1257
- return (0, react.createElement)("button", {
1258
- type: "button",
1259
- role: "switch",
1260
- "aria-checked": checked,
1261
- "aria-label": label,
1262
- title,
1263
- disabled,
1264
- onClick: () => onChange(!checked),
1265
- style: {
1266
- position: "relative",
1267
- flex: "0 0 auto",
1268
- width: 30,
1269
- height: 18,
1270
- padding: 0,
1271
- border: "none",
1272
- borderRadius: 999,
1273
- background: checked ? tokens.accent : "rgba(148, 163, 184, .35)",
1274
- cursor: disabled ? "default" : "pointer",
1275
- opacity: disabled ? .55 : 1,
1276
- transition: "background .15s ease"
1277
- }
1278
- }, (0, react.createElement)("span", {
1279
- "aria-hidden": true,
1280
- style: {
1281
- position: "absolute",
1282
- top: 2,
1283
- left: checked ? 14 : 2,
1284
- width: 14,
1285
- height: 14,
1286
- borderRadius: 999,
1287
- background: "#fff",
1288
- boxShadow: "0 1px 2px rgba(0, 0, 0, .3)",
1289
- transition: "left .15s ease",
1290
- display: "grid",
1291
- placeItems: "center"
1292
- }
1293
- }, checked ? (0, react.createElement)(Check, {
1294
- size: 10,
1295
- "aria-hidden": true
1296
- }) : null));
1297
- }
1298
1194
  function currentThemeColors() {
1299
1195
  if (typeof document === "undefined") return {
1300
1196
  background: "#151517",