dsh-skill-hub 0.2.0 → 0.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.
Files changed (45) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +181 -249
  3. package/README.zh.md +275 -0
  4. package/lib/client.js +395 -257
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +126 -26
  7. package/lib/types/client/SkillHubSettingsCard.d.ts +23 -17
  8. package/lib/types/client/api.d.ts +9 -3
  9. package/lib/types/client/grouping.d.ts +3 -0
  10. package/lib/types/client/index.d.ts +12 -6
  11. package/lib/types/client/locales.d.ts +8 -4
  12. package/lib/types/client/market-catalog.d.ts +1 -1
  13. package/lib/types/client/panel/MarketView.d.ts +5 -5
  14. package/lib/types/client/panel/SkillHubPanel.d.ts +1 -1
  15. package/lib/types/client/panel/SourcesView.d.ts +5 -3
  16. package/lib/types/client/panel/useSkillHub.d.ts +4 -0
  17. package/lib/types/client/settings-card.d.ts +5 -4
  18. package/lib/types/index.d.ts +12 -1
  19. package/lib/types/protocol.d.ts +23 -0
  20. package/lib/types/skillfs.d.ts +1 -1
  21. package/lib/types/update.d.ts +1 -1
  22. package/package.json +28 -27
  23. package/src/client/SkillHubSettingsCard.tsx +25 -14
  24. package/src/client/api.ts +9 -6
  25. package/src/client/grouping.test.ts +12 -0
  26. package/src/client/grouping.ts +10 -1
  27. package/src/client/index.tsx +25 -19
  28. package/src/client/locales.ts +16 -8
  29. package/src/client/market-catalog.ts +1 -1
  30. package/src/client/panel/MarketView.tsx +77 -64
  31. package/src/client/panel/SkillHubPanel.tsx +18 -1
  32. package/src/client/panel/SkillRow.tsx +17 -2
  33. package/src/client/panel/SourcesView.tsx +92 -5
  34. package/src/client/panel/panel.module.css +9 -1
  35. package/src/client/panel/useSkillHub.ts +25 -11
  36. package/src/client/settings-card.tsx +5 -4
  37. package/src/index.ts +78 -31
  38. package/src/protocol.ts +24 -0
  39. package/src/routes.test.ts +55 -0
  40. package/src/routes.ts +90 -10
  41. package/src/skillfs.ts +1 -1
  42. package/src/update.ts +1 -1
  43. package/lib/types/client/api-config-scope.d.ts +0 -48
  44. package/src/client/api-config-scope.test.ts +0 -79
  45. package/src/client/api-config-scope.ts +0 -119
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
+ import { settingsNamespace } from "@deepseek-ai/dsh-settings";
2
3
  import z from "schemastery";
3
4
  import { mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
5
  import { basename, dirname, join, resolve } from "node:path";
@@ -842,7 +843,7 @@ async function findProjectRoot(cwd) {
842
843
  }
843
844
  /**
844
845
  * Scan one writable root for files the provider ignores, so the GUI can
845
- * show why a skill never appears (the zcode-style diagnostics lesson: a
846
+ * show why a skill never appears a
846
847
  * missing frontmatter must be visible, not silent). .disabled files belong
847
848
  * to the hub and are skipped.
848
849
  */
@@ -1493,7 +1494,7 @@ function repoSkillEntry(name, root, repo) {
1493
1494
  //#endregion
1494
1495
  //#region src/update.ts
1495
1496
  /**
1496
- * Self-update check for dsh-skill-hub, mirroring cc-switch's behavior:
1497
+ * Self-update check for dsh-skill-hub
1497
1498
  * query GitHub's latest release and compare it to the installed version.
1498
1499
  *
1499
1500
  * This stays dependency-free: Node's global fetch is used and the version
@@ -1794,13 +1795,72 @@ async function knownSkillNames(deps) {
1794
1795
  for (const disabled of await deps.store.listDisabled()) names.add(disabled.name);
1795
1796
  return names;
1796
1797
  }
1797
- /** Build the full catalog response (shared by catalog/toggle/create handlers). */
1798
+ /**
1799
+ * 读取 dsh 的已知工作区清单(~/.dsh/storages/workspace.json 的
1800
+ * tables.workspaces 表)。面板默认视图据此合并所有工作区的项目技能;
1801
+ * 文件缺失/损坏时返回空清单(回退为仅用户级视图)。
1802
+ */
1803
+ async function workspaceEntries(home) {
1804
+ try {
1805
+ const raw = await readFile(join(home, "storages", "workspace.json"), "utf8");
1806
+ const parsed = JSON.parse(raw);
1807
+ const tables = typeof parsed === "object" && parsed !== null ? parsed.tables : void 0;
1808
+ const workspaces = tables !== void 0 && typeof tables === "object" ? tables.workspaces : void 0;
1809
+ const entries = [];
1810
+ if (workspaces !== void 0 && typeof workspaces === "object") for (const record of Object.values(workspaces)) {
1811
+ const entry = record;
1812
+ if (entry !== null && typeof entry === "object" && typeof entry.path === "string" && entry.path !== "") entries.push({
1813
+ path: entry.path,
1814
+ title: typeof entry.title === "string" && entry.title !== "" ? entry.title : entry.path
1815
+ });
1816
+ }
1817
+ return entries;
1818
+ } catch {
1819
+ return [];
1820
+ }
1821
+ }
1822
+ /** 项目技能来源(workspace 字段只对它们设置)。 */
1823
+ function isProjectSource(source) {
1824
+ return source === "project-dsh" || source === "project-agents";
1825
+ }
1826
+ /**
1827
+ * Build the full catalog response (shared by catalog/toggle/create handlers).
1828
+ * 显式 cwd 只看该工作区;否则合并所有已知工作区(workspace.json)的项目技能
1829
+ * + 用户级技能,同名技能先到先得;没有任何工作区时回退为仅用户级视图。
1830
+ */
1798
1831
  async function buildCatalog(deps, cwd) {
1799
- const lookup = cwd !== void 0 ? { cwd } : void 0;
1800
- const snapshot = await deps.skills.snapshot(lookup);
1801
1832
  const home = homeOf(deps);
1833
+ let workspaces;
1834
+ if (cwd !== void 0 && cwd !== "") workspaces = [{
1835
+ path: cwd,
1836
+ title: cwd
1837
+ }];
1838
+ else {
1839
+ workspaces = await workspaceEntries(home);
1840
+ if (workspaces.length === 0) workspaces = [{
1841
+ path: "",
1842
+ title: ""
1843
+ }];
1844
+ }
1845
+ const byName = /* @__PURE__ */ new Map();
1846
+ let complete = true;
1847
+ for (const ws of workspaces) {
1848
+ const lookup = ws.path !== "" ? { cwd: ws.path } : void 0;
1849
+ const snapshot = await deps.skills.snapshot(lookup);
1850
+ if (!snapshot.complete) complete = false;
1851
+ for (const skill of snapshot.skills) {
1852
+ if (byName.has(skill.name)) continue;
1853
+ byName.set(skill.name, {
1854
+ skill,
1855
+ ...isProjectSource(skill.source) && ws.path !== "" ? {
1856
+ workspace: ws.path,
1857
+ workspaceTitle: ws.title
1858
+ } : {}
1859
+ });
1860
+ }
1861
+ }
1802
1862
  const timesByName = /* @__PURE__ */ new Map();
1803
- await Promise.all(snapshot.skills.map(async (skill) => {
1863
+ await Promise.all([...byName.values()].map(async ({ skill }) => {
1804
1864
  if (!isWritableSource(skill.source)) return;
1805
1865
  const base = rootPath(skill.source, home);
1806
1866
  for (const candidate of [
@@ -1816,7 +1876,7 @@ async function buildCatalog(deps, cwd) {
1816
1876
  return;
1817
1877
  } catch {}
1818
1878
  }));
1819
- const skills = snapshot.skills.map((skill) => {
1879
+ const skills = [...byName.values()].map(({ skill, workspace, workspaceTitle }) => {
1820
1880
  const row = {
1821
1881
  name: skill.name,
1822
1882
  description: skill.description,
@@ -1826,7 +1886,12 @@ async function buildCatalog(deps, cwd) {
1826
1886
  userInvocable: skill.invocation.userInvocable
1827
1887
  },
1828
1888
  provider: skill.provider,
1829
- writable: isWritableSource(skill.source)
1889
+ writable: isWritableSource(skill.source),
1890
+ source: skill.source,
1891
+ ...workspace !== void 0 ? {
1892
+ workspace,
1893
+ workspaceTitle: workspaceTitle ?? workspace
1894
+ } : {}
1830
1895
  };
1831
1896
  const times = timesByName.get(skill.name);
1832
1897
  if (times !== void 0) {
@@ -1839,7 +1904,7 @@ async function buildCatalog(deps, cwd) {
1839
1904
  const diagnostics = [...await scanDiagnostics("user-dsh", home), ...await scanDiagnostics("user-agents", home)];
1840
1905
  return {
1841
1906
  ok: true,
1842
- complete: snapshot.complete,
1907
+ complete,
1843
1908
  skills,
1844
1909
  disabled,
1845
1910
  diagnostics
@@ -1944,7 +2009,15 @@ function makeRoutes(deps) {
1944
2009
  return;
1945
2010
  }
1946
2011
  const cwd = queryParam(url, "cwd");
1947
- const skill = await deps.skills.get(name, cwd !== void 0 ? { cwd } : void 0);
2012
+ let skill;
2013
+ if (cwd !== void 0 && cwd !== "") skill = await deps.skills.get(name, { cwd });
2014
+ else {
2015
+ for (const ws of await workspaceEntries(homeOf(deps))) {
2016
+ skill = await deps.skills.get(name, { cwd: ws.path });
2017
+ if (skill !== void 0) break;
2018
+ }
2019
+ if (skill === void 0) skill = await deps.skills.get(name);
2020
+ }
1948
2021
  if (skill === void 0) {
1949
2022
  writeError(res, 404, "skill not found: " + name);
1950
2023
  return;
@@ -3025,7 +3098,8 @@ const name = "skill-hub";
3025
3098
  const inject = [
3026
3099
  "webServer",
3027
3100
  "skills",
3028
- "systemPrompt"
3101
+ "systemPrompt",
3102
+ "settings"
3029
3103
  ];
3030
3104
  const Config = z.object({
3031
3105
  announceToAgent: z.boolean().default(HUB_CONFIG_DEFAULTS.announceToAgent),
@@ -3034,32 +3108,52 @@ const Config = z.object({
3034
3108
  showUseTime: z.boolean().default(HUB_CONFIG_DEFAULTS.showUseTime),
3035
3109
  showGroupSummary: z.boolean().default(HUB_CONFIG_DEFAULTS.showGroupSummary)
3036
3110
  });
3111
+ /**
3112
+ * Settings namespace hosting the hub's runtime config. Since dsh rc.7 the
3113
+ * host serves every registered settings namespace to the web client (the
3114
+ * dsh-host-apiproxy allowlist is gone), so the browser card and the settings
3115
+ * page edit this namespace through the official settings transport, and the
3116
+ * plugin consumes the same resolved value — one source of truth.
3117
+ */
3118
+ const CONFIG_NAMESPACE = settingsNamespace("dsh-skill-hub");
3119
+ /** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
3120
+ const HubSettingsSchema = z.object({
3121
+ enabled: z.boolean().default(HUB_CONFIG_DEFAULTS.enabled),
3122
+ announceToAgent: z.boolean().default(HUB_CONFIG_DEFAULTS.announceToAgent),
3123
+ showUseCount: z.boolean().default(HUB_CONFIG_DEFAULTS.showUseCount),
3124
+ showUseTime: z.boolean().default(HUB_CONFIG_DEFAULTS.showUseTime),
3125
+ showGroupSummary: z.boolean().default(HUB_CONFIG_DEFAULTS.showGroupSummary),
3126
+ dotModelColor: z.string().pattern(HEX_COLOR_RE),
3127
+ dotUserColor: z.string().pattern(HEX_COLOR_RE)
3128
+ });
3037
3129
  /** Order of the announcement section within the tool-guidance band. */
3038
3130
  const SECTION_ORDER = 152;
3039
3131
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
3040
3132
  const SKILL_HUB_GUIDANCE = ["本机已安装 dsh-skill-hub 插件(DSH Web GUI 技能中枢):设置 →「技能」分区为管理主页;设置 → 插件列表中有本插件的配置卡片(启用/公告开关)。能力:完整本地技能目录(项目/自定义/用户/内置全部来源,走官方 ctx.skills 注册表,含第三方 provider);按来源与自定义分组浏览,分组/来源头部的滑动开关可一键启用/禁用整组(跨组冲突时询问);市场:内置市场目录(精选仓库一键添加)加自定义仓库源,扫描后勾选安装,每个市场源行显示已装/可更新/上游已删数量,支持「检查全部」与「全部更新」;来源跟踪:从 GitHub 仓库(市场源或直接地址)导入的技能记录上游 repo/commit 快照,可检查更新、选择同步、上游删除时跟进删除(移入回收站可恢复,恢复后保留来源与场景归属);个人技能(无来源记录)不跟踪;调用次数与最近使用时间统计;查看技能正文;发现诊断;新建技能向导(写入 ~/.dsh/skills 或 ~/.agents/skills)。限制:仅用户级技能(user-dsh/user-agents 根目录)可写,项目/内置/运行时技能只读展示;路由仅回环可访问。用户提到「技能管理 / 技能列表 / 技能开关 / 技能同步 / 技能市场 / 更新技能 / 新建技能」时即指本插件,请据此协作。", "The dsh-skill-hub plugin is installed (the DSH Web GUI skill hub): Settings → \"Skills\" is the management page; Settings → Plugins lists this plugin's configuration card (enable / announcement toggles). Capabilities: full local skill catalog (project / custom / user / bundled roots via the official ctx.skills registry, including third-party providers); browsing by source and custom groups, each group header carrying a sliding switch to enable/disable the whole group in one click (cross-group conflicts prompt the user); market: a built-in catalog of curated repos (one-click add) plus custom repo sources, scan-and-install import, per-source installed / updatable / deleted-upstream badges with \"check all\" and \"update all\" actions; upstream source tracking: skills imported from GitHub repos (market sources or direct URLs) record the repo/commit snapshot, support update checks, selective sync, and follow-up deletion when the upstream removes a skill (moves it into a restorable trash; restoring keeps the source and scene membership); personal skills (no source record) are never tracked; invocation counts and last-used times; skill body inspection; discovery diagnostics; new-skill wizard (writes to ~/.dsh/skills or ~/.agents/skills). Limits: only user-level skills (user-dsh/user-agents roots) are writable; project/bundled/runtime skills are read-only; routes are loopback-only. When the user mentions \"skill management / skill list / skill toggle / skill sync / skill market / update skills / new skill\", this plugin is what they mean — collaborate accordingly."].join("\n\n");
3041
3133
  /**
3042
3134
  * Mount the skill hub routes and announcement.
3043
- * @param ctx - host plugin context carrying webServer/skills/systemPrompt.
3135
+ * @param ctx - host plugin context carrying webServer/skills/systemPrompt/settings.
3044
3136
  * @param config - resolved plugin config (schema defaults applied by the loader).
3045
3137
  */
3046
3138
  function apply(ctx, config) {
3047
3139
  const base = config ?? {};
3048
- let current = () => resolveHubConfig({}, base);
3049
- let savedState = {};
3140
+ const settingsScope = ctx.settings.register(CONFIG_NAMESPACE, HubSettingsSchema, { base });
3141
+ const current = () => settingsScope.get();
3050
3142
  const store = new SkillHubStore();
3051
3143
  let disposeRoutes;
3052
3144
  let disposeSection;
3053
3145
  let disposeProvider;
3054
3146
  let providerControl;
3055
3147
  let stats;
3148
+ const saved = () => {
3149
+ return ctx.settings.describe().find((entry) => entry.ns === CONFIG_NAMESPACE)?.user ?? {};
3150
+ };
3056
3151
  const updateConfig = async (patch) => {
3057
- await store.setConfig(patch);
3058
- savedState = await store.getConfig();
3059
- const next = resolveHubConfig(savedState, base);
3060
- current = () => next;
3061
- sync();
3062
- return next;
3152
+ const user = { ...saved() };
3153
+ for (const [key, value] of Object.entries(patch)) if (value === void 0) delete user[key];
3154
+ else user[key] = value;
3155
+ await settingsScope.replace(user);
3156
+ return settingsScope.get();
3063
3157
  };
3064
3158
  const sync = () => {
3065
3159
  if (disposeSection !== void 0) {
@@ -3096,7 +3190,7 @@ function apply(ctx, config) {
3096
3190
  },
3097
3191
  stats,
3098
3192
  config: current,
3099
- saved: () => savedState,
3193
+ saved,
3100
3194
  updateConfig
3101
3195
  }).map((route) => ctx.webServer.register(route));
3102
3196
  return () => {
@@ -3105,15 +3199,21 @@ function apply(ctx, config) {
3105
3199
  }, "dsh-skill-hub: routes");
3106
3200
  };
3107
3201
  sync();
3108
- store.getConfig().then((saved) => {
3109
- savedState = saved;
3110
- current = () => resolveHubConfig(saved, base);
3202
+ ctx.effect(() => settingsScope.watch(() => {
3111
3203
  sync();
3112
- });
3204
+ }), "dsh-skill-hub: settings config watch");
3205
+ (async () => {
3206
+ const legacy = await store.getConfig();
3207
+ if (Object.keys(legacy).length > 0 && Object.keys(saved()).length === 0) try {
3208
+ await settingsScope.update(legacy);
3209
+ } catch (error) {
3210
+ ctx.logger.warn("[dsh-skill-hub] sidecar config migration into the settings namespace failed", error);
3211
+ }
3212
+ })();
3113
3213
  ctx.inject(["sessionQuery"], (sctx) => {
3114
3214
  stats = createSkillStatsReader(sctx.sessionQuery);
3115
3215
  sync();
3116
3216
  });
3117
3217
  }
3118
3218
  //#endregion
3119
- export { Config, SKILL_HUB_GUIDANCE, apply, inject, name };
3219
+ export { CONFIG_NAMESPACE, Config, HubSettingsSchema, SKILL_HUB_GUIDANCE, apply, inject, name };
@@ -1,14 +1,20 @@
1
1
  /**
2
- * The dsh-skill-hub plugin settings card: bridges the hub's own config route
3
- * (ApiConfigScope /api/skill-hub/config) onto the family-style staged card
4
- * form (enabled master switch + agent announcement). Registered into the
5
- * official `settings.plugin.item` slot so the plugin shows up in Settings →
6
- * 插件. The scope is a FormScope, so the card never touches the settings
7
- * service (the host refuses third-party namespaces).
2
+ * The dsh-skill-hub plugin settings card: bridges the hub's settings
3
+ * namespace (bound through the official settings transport) onto the
4
+ * family-style staged card form (enabled master switch + agent announcement).
5
+ * Registered into the official `settings.plugin.item` slot keyed by that
6
+ * namespace, so the plugin shows up in Settings 插件 on dsh rc.7+.
8
7
  */
9
8
  import type { ReactElement } from 'react';
10
9
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
11
- import { CardForm, type CardShell, type FieldState, type FormScope } from './settings-form.ts';
10
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
11
+ import { type CardShell, type FieldState, type FormScope } from './settings-form.ts';
12
+ /** Model-invocable dot color default. Single source for the TS side; the
13
+ * panel's CSS mirrors it via --hub-model (panel.module.css). */
14
+ export declare const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
15
+ /** User-invocable dot color default. Single source for the TS side; the
16
+ * panel's CSS mirrors it via --hub-user (panel.module.css). */
17
+ export declare const DEFAULT_DOT_USER_COLOR = "#3fb950";
12
18
  /** The card's projected state. */
13
19
  export interface SkillHubSettingsState extends CardShell {
14
20
  enabled: FieldState;
@@ -19,31 +25,31 @@ export interface SkillHubSettingsState extends CardShell {
19
25
  showUseTime: FieldState;
20
26
  showGroupSummary: FieldState;
21
27
  }
22
- /** Props the slot renderer receives (locale copy + injected form actions). */
23
- export interface SkillHubSettingsCardProps {
24
- t: (key: string) => string;
25
- useSkillHubSettingsCard: <T>(selector: (snapshot: SkillHubSettingsState) => T) => T;
28
+ /** The business face the card's slot registration injects. */
29
+ export interface SkillHubSettingsCardFace {
30
+ hooks: {
31
+ /** The card's snapshot store (projected form state). */
32
+ skillHubSettingsCard: SnapshotStore<SkillHubSettingsState>;
33
+ };
26
34
  save: () => void;
27
35
  discard: () => void;
28
36
  edit: (field: string, text: string) => void;
29
37
  resetField: (field: string) => void;
30
38
  }
39
+ /** Props the slot renderer binds (locale copy + injected form actions). */
40
+ export type SkillHubSettingsCardProps = PropsRuntime<'settings.plugin.item'> & PropsLocale<'dsh-skill-hub'> & InjectFace<SkillHubSettingsCardFace>;
31
41
  /** Bridges the hub's config scope onto the card's staged form. */
32
42
  export declare class SkillHubSettingsCardController {
33
43
  private readonly form;
34
44
  private readonly store;
35
- /** @param scope - the hub config scope the card edits (ApiConfigScope). */
45
+ /** @param scope - the hub settings scope the card edits (FormScope-compatible). */
36
46
  constructor(scope: FormScope);
37
47
  private projection;
38
48
  /**
39
49
  * Build the face the card's slot registration injects.
40
50
  * @returns the card's snapshot hook and its form actions.
41
51
  */
42
- inject(): {
43
- hooks: {
44
- skillHubSettingsCard: SnapshotStore<SkillHubSettingsState>;
45
- };
46
- } & ReturnType<CardForm['actions']>;
52
+ inject(): SkillHubSettingsCardFace;
47
53
  }
48
54
  /**
49
55
  * Render the dsh-skill-hub card.
@@ -9,8 +9,14 @@ export declare class SkillHubApiError extends Error {
9
9
  }
10
10
  /** The browser half's only data entry point. */
11
11
  export declare class SkillHubApi {
12
- catalog(): Promise<CatalogResponse>;
13
- skill(name: string): Promise<SkillDetail>;
12
+ /** Catalog lookup options (cwd selects a workspace's project skills). */
13
+ catalog(options?: {
14
+ cwd?: string;
15
+ }): Promise<CatalogResponse>;
16
+ /** One skill's detail (cwd selects a workspace's project skills). */
17
+ skill(name: string, options?: {
18
+ cwd?: string;
19
+ }): Promise<SkillDetail>;
14
20
  /** Move one writable skill into the restorable trash. */
15
21
  deleteSkill(name: string): Promise<SkillDeleteResponse>;
16
22
  /** Toggle one skill; resolves with the fresh catalog from the route. */
@@ -19,7 +25,7 @@ export declare class SkillHubApi {
19
25
  stats(): Promise<StatsResponse>;
20
26
  /** Toggle a whole group in one write; resolves with the fresh catalog + failures. */
21
27
  toggleBatch(names: string[], enabled: boolean): Promise<ToggleBatchResponse>;
22
- /** The user's added market sources (codex-style repo slugs). */
28
+ /** The user's added market sources. */
23
29
  market(): Promise<MarketSourcesResponse>;
24
30
  /** Add a market source repo; resolves with the fresh list. */
25
31
  addMarketSource(repo: string): Promise<MarketSourcesResponse>;
@@ -40,11 +40,14 @@ export declare function conflictsOnClose(members: readonly string[], enabledName
40
40
  }>): string[];
41
41
  /** Origin-repo filter value: skills with no source record (private skills). */
42
42
  export declare const PRIVATE_SOURCE = "private";
43
+ /** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
44
+ export declare function isProjectSource(source: string): boolean;
43
45
  /**
44
46
  * Apply the origin filter ('all' or a specific origin repo; skills without a
45
47
  * source record count as PRIVATE_SOURCE). The origins map is the store's
46
48
  * skillName → repo derivation, so filtering follows the tracked source
47
49
  * records instead of the filesystem root a skill happens to live under.
50
+ * 项目级技能(有 workspace 归属)永远不算「个人」。
48
51
  */
49
52
  export declare function filterBySource(skills: readonly CatalogSkill[], source: string, origins: Readonly<Record<string, string>>): CatalogSkill[];
50
53
  /** Catalog sort keys offered by the filter bar. */
@@ -5,11 +5,11 @@
5
5
  * Registers the dsh-skill-hub locale dictionaries and mounts two Settings
6
6
  * surfaces, both through official slots (no DOM injection):
7
7
  * - a plugin-management card in the `settings.plugin.item` slot (Settings →
8
- * 插件 → 可配置插件列表), bound to the hub's own /api/skill-hub/config
9
- * route via ApiConfigScope the family-bucket card pattern
10
- * (PluginSettingsCard + CardForm vendored from dsh-task-board). The host's
11
- * settings service refuses third-party namespaces, so the card never
12
- * touches the settings transport;
8
+ * 插件 → 可配置插件列表), keyed by the hub's settings namespace and bound
9
+ * through the official settings transport (dsh rc.7 serves every
10
+ * registered namespace to the web client, and the tab dispatches cards by
11
+ * namespace) the family-bucket card pattern (PluginSettingsCard +
12
+ * CardForm vendored from dsh-task-board);
13
13
  * - a top-level Settings section (Settings → 技能) hosting the skill hub
14
14
  * panel: catalog, search, enable/disable, diagnostics, new-skill form.
15
15
  *
@@ -29,7 +29,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
29
29
  'dsh-skill-hub': HubKey;
30
30
  }
31
31
  }
32
- /** Required services (fiber inject waiting — the runtime must be up first). */
32
+ /**
33
+ * Required services (fiber inject waiting — the runtime must be up first).
34
+ * `connection`/`remote` are the settings transport's own prerequisites
35
+ * (`ctx.settingsScope.bind` resolves them on the caller's fiber), and
36
+ * `settingsScope` is the namespace-scope binder itself; mirror the official
37
+ * settings-plugins inject list.
38
+ */
33
39
  export declare const inject: string[];
34
40
  /** Type-only surface (export discipline: no value exports beyond the plugin contract). */
35
41
  export type { SkillHubPanelProps } from './panel/SkillHubPanel.tsx';
@@ -26,19 +26,19 @@ export declare const zh: {
26
26
  readonly 'panel.emptyAll': "本地还没有技能。点击「新建技能」创建第一个。";
27
27
  readonly 'panel.loading': "读取中…";
28
28
  readonly 'panel.incomplete': "(目录可能不完整)";
29
- readonly 'market.addHint': "像 codex 一样添加仓库源:输入 owner/repo 或 GitHub 链接,扫描其中的技能并一键导入;导入后自动跟踪上游更新。";
29
+ readonly 'panel.workspacePlaceholder': "工作区路径(回车应用)";
30
+ readonly 'panel.workspaceHint': "填写项目路径后,面板同时显示该项目 .dsh/skills 与 .agents/skills 下的技能(只读);留空回车恢复用户级视图。";
31
+ readonly 'panel.workspaceClear': "清除工作区";
30
32
  readonly 'market.addPlaceholder': "owner/repo 或 https://github.com/…";
31
33
  readonly 'market.addSource': "添加源";
32
34
  readonly 'market.noSources': "还没有市场源。从下方内置市场添加,或输入一个 GitHub 仓库。";
33
- readonly 'market.catalogTitle': "内置市场";
35
+ readonly 'market.title': "市场";
34
36
  readonly 'market.catalogHint': "精选技能仓库,添加后即可扫描安装,并持续检查更新。";
35
37
  readonly 'market.catalog.anthropics': "Anthropic 官方技能合集(Claude Skills)";
36
38
  readonly 'market.catalog.superpowers': "Superpowers 社区技能合集";
37
39
  readonly 'market.catalog.mattpocock': "Matt Pocock 技能集";
38
40
  readonly 'market.catalog.openDesign': "设计技能合集(design-templates)";
39
- readonly 'market.added': "已添加";
40
41
  readonly 'market.add': "添加";
41
- readonly 'market.mySources': "我的市场源";
42
42
  readonly 'market.installed': "已装 {count}";
43
43
  readonly 'market.updatable': "可更新 {count}";
44
44
  readonly 'market.deletedUpstream': "上游已删 {count}";
@@ -103,6 +103,9 @@ export declare const zh: {
103
103
  readonly 'groups.empty': "还没有场景。先新建一个,再把技能归类。";
104
104
  readonly 'groups.noCollections': "暂无来源组。从市场导入技能后自动按来源聚合。";
105
105
  readonly 'groups.personal': "个人";
106
+ readonly 'groups.project': "项目级";
107
+ readonly 'groups.subdivide': "细分";
108
+ readonly 'groups.merge': "合并";
106
109
  readonly 'groups.noWritable': "该组没有可写技能(只读技能无法由中枢开关)";
107
110
  readonly 'groups.closeAll': "全部关闭";
108
111
  readonly 'groups.keepOn': "保留开启";
@@ -129,6 +132,7 @@ export declare const zh: {
129
132
  readonly 'row.disable': "禁用";
130
133
  readonly 'row.enable': "启用";
131
134
  readonly 'row.delete': "移入回收站";
135
+ readonly 'row.open': "查看 {name} 详情(回车)";
132
136
  readonly 'delete.confirmTitle': "移入回收站?";
133
137
  readonly 'delete.confirmText': "确定把「{name}」移入回收站吗?可随时恢复。";
134
138
  readonly 'delete.confirm': "移入回收站";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Built-in market catalog: well-known skill repos shown on the market tab so
3
- * users can browse and install without knowing repo URLs (cc-switch-style).
3
+ * users can browse and install without knowing repo URLs.
4
4
  * Only the repo slug lives here; adding goes through the normal
5
5
  * market-source route and records upstream tracking like any manual source.
6
6
  */
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Market tab: the built-in market catalog (one-click add), the user's market
3
- * sources with aggregated per-repo state (installed / updatable / deleted
4
- * upstream), check-all and update-all actions, and the repo scan result with
5
- * the importable skill checklist.
2
+ * Market tab: one unified market list — built-in catalog entries (add
3
+ * button while not yet added) and the user's market sources (full state
4
+ * badges + actions once added, plus custom sources), with check-all and
5
+ * update-all actions and the repo scan result with the importable checklist.
6
6
  */
7
- import type { JSX } from 'react';
7
+ import { type JSX } from 'react';
8
8
  import type { SkillHubState } from './useSkillHub.ts';
9
9
  export declare function MarketView(props: {
10
10
  hub: SkillHubState;
@@ -2,7 +2,7 @@
2
2
  * The skill hub panel: catalog grouped by tags + source collections, search
3
3
  * and filter in one row, per-group tri-state switches with conflict dialogs,
4
4
  * upstream source tracking (check / sync / follow upstream deletion into a
5
- * restorable trash), codex-style market sources, disabled re-enable,
5
+ * restorable trash), market sources, disabled re-enable,
6
6
  * detail inspection, and the new-skill scaffold form.
7
7
  *
8
8
  * Thin shell: state and flows live in useSkillHub, the tab contents live in
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Sources tab: the flat skill list or the grouped view (one card per
3
- * upstream collection with check/sync/follow-delete actions and the
4
- * tri-state switch, plus the uncategorized "personal" card).
2
+ * Sources tab: the flat skill list or the grouped view a project-level
3
+ * three-tier tree (workspaces from workspace.json, each optionally split by
4
+ * .dsh/.agents), one card per upstream collection with check/sync/
5
+ * follow-delete actions and the tri-state switch, plus the uncategorized
6
+ * "personal" card (project skills never count as personal).
5
7
  */
6
8
  import type { JSX } from 'react';
7
9
  import type { SkillHubState } from './useSkillHub.ts';
@@ -53,6 +53,7 @@ export declare function useSkillHub(api: SkillHubApi): {
53
53
  repoImporting: boolean;
54
54
  repoResult: RepoImportResponse | null;
55
55
  search: string;
56
+ workspace: string;
56
57
  detail: SkillDetail | null;
57
58
  detailLoading: boolean;
58
59
  busyNames: ReadonlySet<string>;
@@ -108,6 +109,7 @@ export declare function useSkillHub(api: SkillHubApi): {
108
109
  tagBusy: boolean;
109
110
  editSearch: string;
110
111
  collapsedGroups: ReadonlySet<string>;
112
+ subdividedProjects: ReadonlySet<string>;
111
113
  showLegend: boolean;
112
114
  actionNames: Set<string>;
113
115
  viewNames: Set<string>;
@@ -119,6 +121,7 @@ export declare function useSkillHub(api: SkillHubApi): {
119
121
  setLoadError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
120
122
  setSuccessBanner: import("react").Dispatch<import("react").SetStateAction<string | null>>;
121
123
  setSearch: import("react").Dispatch<import("react").SetStateAction<string>>;
124
+ setWorkspace: import("react").Dispatch<import("react").SetStateAction<string>>;
122
125
  setDetail: import("react").Dispatch<import("react").SetStateAction<SkillDetail | null>>;
123
126
  setShowForm: import("react").Dispatch<import("react").SetStateAction<boolean>>;
124
127
  setFormName: import("react").Dispatch<import("react").SetStateAction<string>>;
@@ -148,6 +151,7 @@ export declare function useSkillHub(api: SkillHubApi): {
148
151
  setEditSearch: import("react").Dispatch<import("react").SetStateAction<string>>;
149
152
  setShowLegend: import("react").Dispatch<import("react").SetStateAction<boolean>>;
150
153
  toggleGroupCollapse: (key: string) => void;
154
+ toggleSubdivide: (key: string) => void;
151
155
  checkUpdate: () => Promise<void>;
152
156
  loadMarket: () => Promise<void>;
153
157
  openDetail: (name: string) => Promise<void>;
@@ -6,15 +6,16 @@
6
6
  * plugin should show no trace of it.
7
7
  */
8
8
  import { type ReactElement } from 'react';
9
+ import type { HubKey } from './locales.ts';
9
10
  import type { CardShell } from './settings-form.ts';
10
11
  /** Card-level chrome props. */
11
12
  export interface PluginSettingsCardProps {
12
- /** Locale translator for the owning plugin's namespace. */
13
- t: (key: string) => string;
13
+ /** Locale translator for the owning plugin's namespace (its own key domain). */
14
+ t: (key: HubKey) => string;
14
15
  /** Locale key of the card title. */
15
- titleKey: string;
16
+ titleKey: HubKey;
16
17
  /** Locale key of the card description. */
17
- descriptionKey: string;
18
+ descriptionKey: HubKey;
18
19
  /** The form shell state. */
19
20
  state: CardShell;
20
21
  onSave: () => void;
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
10
  import z from 'schemastery';
11
+ import { type HubSettingsValue } from './protocol.ts';
11
12
  /** Stable cordis plugin name (matches cordis.patch.yml insert id). */
12
13
  export declare const name = "skill-hub";
13
14
  /** Services required before the skill-hub surfaces can mount. */
@@ -26,11 +27,21 @@ export interface Config {
26
27
  showGroupSummary?: boolean;
27
28
  }
28
29
  export declare const Config: z<Config>;
30
+ /**
31
+ * Settings namespace hosting the hub's runtime config. Since dsh rc.7 the
32
+ * host serves every registered settings namespace to the web client (the
33
+ * dsh-host-apiproxy allowlist is gone), so the browser card and the settings
34
+ * page edit this namespace through the official settings transport, and the
35
+ * plugin consumes the same resolved value — one source of truth.
36
+ */
37
+ export declare const CONFIG_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
38
+ /** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
39
+ export declare const HubSettingsSchema: z<HubSettingsValue>;
29
40
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
30
41
  export declare const SKILL_HUB_GUIDANCE: string;
31
42
  /**
32
43
  * Mount the skill hub routes and announcement.
33
- * @param ctx - host plugin context carrying webServer/skills/systemPrompt.
44
+ * @param ctx - host plugin context carrying webServer/skills/systemPrompt/settings.
34
45
  * @param config - resolved plugin config (schema defaults applied by the loader).
35
46
  */
36
47
  export declare function apply(ctx: Context, config?: Config): void;
@@ -52,10 +52,16 @@ export interface CatalogSkill {
52
52
  provider: string;
53
53
  /** Whether the hub may toggle this skill (user-level filesystem skills only). */
54
54
  writable: boolean;
55
+ /** 技能来源标识(user-dsh/user-agents/project-dsh/project-agents/...)。 */
56
+ source: string;
55
57
  /** SKILL.md creation time (epoch ms); used for "added" sorting. Absent when unknown. */
56
58
  addedAt?: number;
57
59
  /** SKILL.md last-modified time (epoch ms); used for "updated" display. Absent when unknown. */
58
60
  updatedAt?: number;
61
+ /** 项目技能的所属工作区路径(仅 project-dsh/project-agents 来源携带)。 */
62
+ workspace?: string;
63
+ /** 工作区显示标题(来自 workspace.json;无则回退为路径)。 */
64
+ workspaceTitle?: string;
59
65
  }
60
66
  /** One disabled skill tracked by the hub sidecar (SKILL.md renamed away). */
61
67
  export interface DisabledSkill {
@@ -194,6 +200,23 @@ export interface HubConfig {
194
200
  /** Show group-header usage summaries (count + last used). Default true. */
195
201
  showGroupSummary?: boolean;
196
202
  }
203
+ /**
204
+ * The resolved shape of the hub's settings namespace (schema defaults, then
205
+ * the composition base, then the user layer). Kept as a type alias so the
206
+ * browser-side settings scope snapshot is index-compatible with the card
207
+ * form's record shape.
208
+ */
209
+ export type HubSettingsValue = {
210
+ enabled: boolean;
211
+ announceToAgent: boolean;
212
+ showUseCount: boolean;
213
+ showUseTime: boolean;
214
+ showGroupSummary: boolean;
215
+ /** Model-invocable dot color (#rrggbb); absent means the panel default. */
216
+ dotModelColor?: string;
217
+ /** User-invocable dot color (#rrggbb); absent means the panel default. */
218
+ dotUserColor?: string;
219
+ };
197
220
  /**
198
221
  * Hub config defaults — the single source every layer reads: the cordis
199
222
  * schema (index.ts), the host's saved-override merge, and the routes'
@@ -88,7 +88,7 @@ export declare function listSkillEntries(root: WritableRoot, home?: string): Pro
88
88
  export declare function findProjectRoot(cwd: string): Promise<string>;
89
89
  /**
90
90
  * Scan one writable root for files the provider ignores, so the GUI can
91
- * show why a skill never appears (the zcode-style diagnostics lesson: a
91
+ * show why a skill never appears a
92
92
  * missing frontmatter must be visible, not silent). .disabled files belong
93
93
  * to the hub and are skipped.
94
94
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Self-update check for dsh-skill-hub, mirroring cc-switch's behavior:
2
+ * Self-update check for dsh-skill-hub
3
3
  * query GitHub's latest release and compare it to the installed version.
4
4
  *
5
5
  * This stays dependency-free: Node's global fetch is used and the version