dsh-claude-compat 0.3.0 → 0.6.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
@@ -9,12 +9,12 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <img src="https://img.shields.io/badge/version-0.3.0-blue?style=flat-square" alt="Version">
12
+ <img src="https://img.shields.io/badge/version-0.6.2-blue?style=flat-square" alt="Version">
13
13
  <img src="https://img.shields.io/badge/node-%3E%3D20-green?style=flat-square&logo=node.js&logoColor=white" alt="Node">
14
14
  <img src="https://img.shields.io/badge/license-MIT-orange?style=flat-square" alt="License">
15
15
  </p>
16
16
 
17
- <p align="center">DeepSeek Harness plugin that bridges Claude Code's <code>.claude/</code> directory into DSH natively — reuse your skills, slash commands, and rules with zero migration.</p>
17
+ <p align="center">DeepSeek Harness plugin that bridges Claude Code's <code>.claude/</code> directory into DSH natively — reuse your skills, slash commands, rules, agents, hooks, and MCP servers with zero migration.</p>
18
18
 
19
19
  ## What it does
20
20
 
@@ -23,27 +23,44 @@
23
23
  | `skills/**/SKILL.md` | DSH skill provider | Name + description in the model-visible catalog; body loads on demand via the `skill` tool. New skills appear on the next catalog reconcile — no restart. |
24
24
  | `commands/*.md` | DSH skill provider | Same, plus user-invocable: `/command-name` works in the slash menu. |
25
25
  | `rules/*.md` | Message-stream injection | Rules are concatenated, wrapped in a `<system-reminder>` envelope, and prepended as a user-role message at the front of the message array once per session — the same channel Claude Code uses (`prependUserContext`), which models follow reliably. |
26
+ | `agents/*.md` | DSH skill provider (delegation shim) | DSH has no markdown subagent format, so each agent file becomes a **skill** whose body leads with an explicit "delegate with this persona" instruction. Model- and user-invocable, so `/agent-name` works. Same-name agents dedupe by rank like skills. |
27
+ | `.claude/settings.json` → `hooks` | Tool/prompt hooks | Claude Code hooks subset bridged onto DSH's `tools/pre-execute` (PreToolUse), `tools/post-execute` (PostToolUse) and `agent/pre-step` (UserPromptSubmit) waterfalls. Commands run via `/bin/sh -c` with a Claude-style JSON payload on stdin; exit code 2 denies/blocks, `hookSpecificOutput` overrides are honored, timeouts allow through with a warning. |
28
+ | `<projectRoot>/.mcp.json` | MCP servers | Claude Code-format MCP server definitions are translated to `dsh-mcp-client` plugin instances at DSH startup from the launch workspace: `command` → stdio, `url` → streamable-http. Malformed entries or a missing `@deepseek-ai/dsh-mcp-client` degrade to a warning, never a crash. |
29
+ | `~/.claude/plugins` (installed plugins) | DSH skill provider | Installed Claude Code plugin-marketplace plugins contribute their skills/commands/agents (via each install's `.claude-plugin/plugin.json` manifest, or a directory scan when manifest-less) at rank `750` — the long tail: project `.claude`, DSH native, and `~/.claude` all win collisions. Plugin MCP servers (`mcpServers` in the manifest) mount only when `enablePluginMcp` is opted in — mounting third-party MCP servers is a bigger trust step than listing skills. |
26
30
 
27
- The same three directories are also read from the **user-level** `~/.claude/` (skills, commands, rules). Same-name skills/commands/rules are deduped with a fixed priority:
31
+ The same directories are also read from the **user-level** `~/.claude/` (skills, commands, rules, agents, and `~/.claude/settings.json` hooks). Same-name skills/commands/rules/agents are deduped with a fixed priority:
28
32
 
29
- **project `.claude` > DSH native > `~/.claude`**
33
+ **project `.claude` > DSH native (`.dsh`) > `~/.claude`**
30
34
 
31
- - Project entries carry rank `50`, `~/.claude` entries rank `700`, and DSH's own bundled skills sit at rank `600` (`BUNDLED_SKILL_RANK`) — so a project skill always overrides the DSH-bundled and user copies, and a user skill never overrides a DSH-native one.
35
+ - Project `.claude` entries carry rank `50`; DSH's own skills — project `.dsh` roots, `.agents` roots, and bundled skills (ranks `100`–`600`, `BUNDLED_SKILL_RANK`) — sit in between; `~/.claude` entries rank `700`. So a project skill always overrides the DSH-native and user copies, and a user skill never overrides a DSH-native one.
32
36
  - Rule files with the same basename in `~/.claude/rules` are skipped when the project already provides one.
33
37
 
34
38
  `CLAUDE.md` / `AGENTS.md` are **not** touched — DSH's built-in `dsh-agent-instructions` already handles those.
35
39
 
40
+ ## Built-in commands
41
+
42
+ Installing this plugin adds three management skills to the catalog:
43
+
44
+ | Command | What it does |
45
+ |---|---|
46
+ | `/cc-plugin` | Full Claude Code plugin management: `list`, `install <name>[@marketplace]`, `uninstall`, `enable`, `disable`, `update [name]`, `search <term>`, `marketplace list\|add\|remove\|update`. One-shot syntax `/cc-plugin <name>@<marketplace>` installs directly. Engine: the `claude` CLI when available, otherwise a built-in fallback (direct JSON + git, with timestamped backups of every file it touches). All state stays in Claude-native locations (`~/.claude/plugins`, `~/.claude/settings.json` `enabledPlugins`) so Claude Code and DSH read the same truth. |
47
+ | `/reload-cc-plugins` | Hot-reload the skill catalog: drop cached provider lists and notify observers so newly installed/removed skills appear in the **current session** — no restart, no new session. |
48
+ | `/reload-skills` | Alias of `/reload-cc-plugins`. |
49
+
50
+ Typical loop: `/cc-plugin install ralph-loop@claude-plugins-official` → `/reload-cc-plugins` → new skills visible immediately. Plugin-shipped MCP servers still require a DSH restart (process-lifetime mount).
51
+
36
52
  ## Requirements
37
53
 
38
54
  - DSH with a profile (e.g. `web`)
39
- - A project using Claude Code conventions: `.claude/skills/`, `.claude/commands/`, `.claude/rules/` (all optional; `~/.claude/` equivalents are also picked up)
55
+ - A project using Claude Code conventions: `.claude/skills/`, `.claude/commands/`, `.claude/rules/`, `.claude/agents/`, `.claude/settings.json`, and a project-root `.mcp.json` (all optional; `~/.claude/` equivalents are also picked up)
56
+ - `pnpm` on `PATH` — `dsh plugin` is a thin pnpm forwarder
40
57
 
41
- ## Install
58
+ ## Install / Update
42
59
 
43
- One command — the package declares `dsh.bundle`, so DSH activates it automatically (no manual `cordis.patch.yml` editing):
60
+ One command — the package declares `dsh.bundle`, so DSH activates it automatically (no manual `cordis.patch.yml` editing). Install or update to the latest release:
44
61
 
45
62
  ```bash
46
- dsh plugin --profile web add dsh-claude-compat
63
+ dsh plugin --profile web add dsh-claude-compat@latest
47
64
  ```
48
65
 
49
66
  Or from GitHub:
@@ -58,8 +75,13 @@ Restart DSH (`dsh web`). Done — skills show up in `/`, rules are injected into
58
75
 
59
76
  | Option | Default | Description |
60
77
  |---|---|---|
61
- | `enableSkills` | `true` | Register the `.claude/skills` + `.claude/commands` provider (project and `~/.claude`) |
78
+ | `enableSkills` | `true` | Register the `.claude/skills` + `.claude/commands` + `.claude/agents` provider (project and `~/.claude`) |
62
79
  | `enableRules` | `true` | Inject project + `~/.claude` `rules/*.md` into the message stream |
80
+ | `enableMcp` | `true` | Translate `<projectRoot>/.mcp.json` into mounted MCP server plugins |
81
+ | `mcpFailOnStartupError` | `false` | Forward to `dsh-mcp-client`: fail plugin startup when an MCP server fails to connect |
82
+ | `enableHooks` | `true` | Run `.claude/settings.json` hooks (Pre/PostToolUse, UserPromptSubmit) |
83
+ | `hooksTimeoutMs` | `60000` | Per-hook run timeout (UserPromptSubmit capped at 10s regardless) |
84
+ | `enableAgents` | `true` | Surface `.claude/agents/*.md` as delegation-shim skills |
63
85
  | `rulesMaxBytes` | `65536` | Hard cap on total injected project rules text |
64
86
  | `userRulesMaxBytes` | `65536` | Hard cap on total injected `~/.claude/rules` text |
65
87
  | `projectRootMarkers` | `[".git"]` | Ancestor markers for project-root discovery |
@@ -68,12 +90,38 @@ Restart DSH (`dsh web`). Done — skills show up in `/`, rules are injected into
68
90
  | `userSkillRank` | `700` | Provider rank for `~/.claude` skills (loses to DSH-native `600`) |
69
91
  | `userSkillSource` | `user-claude` | Source tag for `~/.claude` catalog entries |
70
92
  | `userClaudeDir` | `~/.claude` | User-level `.claude` directory (`~` expands to the home dir) |
93
+ | `enablePlugins` | `true` | Surface skills/commands/agents from installed Claude Code plugins (`~/.claude/plugins`) |
94
+ | `pluginSkillRank` | `750` | Provider rank for plugin content (the long tail — everything else wins) |
95
+ | `pluginSkillSource` | `claude-plugin` | Source tag for plugin catalog entries |
96
+ | `pluginsRoot` | `~/.claude/plugins` | Plugin-marketplace root (`installed_plugins.json` + `cache/`) |
97
+ | `enablePluginMcp` | `false` | Mount plugin-declared MCP servers (opt-in; requires `enablePlugins` and `enableMcp`) |
98
+ | `enablePluginManager` | `true` | Register the `/cc-plugin`, `/reload-cc-plugins`, `/reload-skills` management skills |
99
+ | `pluginManagerRank` | `40` | Rank for the built-in management skills (top of the catalog) |
71
100
 
72
101
  ## Notes
73
102
 
74
103
  - **Skill naming**: DSH requires kebab-case skill names. Nested skill directories are flattened (`gitnexus/gitnexus-guide` → `gitnexus-gitnexus-guide`); invalid frontmatter names fall back to the directory name.
104
+ - **MCP lifecycle**: `.mcp.json` is read once at DSH startup from the launch workspace — not per session — and each server mounts for the process lifetime. Restart DSH to pick up edits.
105
+ - **Hooks scope**: a deliberately small subset of Claude Code hooks: PreToolUse / PostToolUse / UserPromptSubmit. Matchers support exact names, `*` wildcards, and `|` alternation; commands run with `stdin` carrying the Claude-style JSON payload. Exit code 2 = deny (Pre) / block (Post); other non-zero exits and timeouts allow through with a warning.
75
106
  - **Rules granularity**: rules are read per new session (cached per session cwd). Editing a rule mid-session takes effect in the next session.
76
107
  - **Rules content**: rules are injected verbatim as instructions to the model. Only commit rules you want the model to follow — same trust level as `CLAUDE.md`.
108
+ - **Catalog snapshot timing**: the skill catalog is snapshotted when a session is created. Skills installed or edited mid-session surface after `/reload-cc-plugins` (hot reload) or in the next session.
109
+
110
+ ## Troubleshooting
111
+
112
+ **DSH won't start back up after a restart / port 3080 stuck.** Old process still holding the port (symptom: `EADDRINUSE` in logs). Use the bundled restart script — it waits for a clean stop, falls back to SIGKILL, and verifies the port before reporting success:
113
+
114
+ ```bash
115
+ npx dsh-claude-compat-restart # bin alias (installed with the package)
116
+ bash node_modules/dsh-claude-compat/scripts/dsh-restart.sh # direct
117
+ bash scripts/dsh-restart.sh --no-patch # skip the prompt patch, restart only
118
+ ```
119
+
120
+ The script also re-applies the idempotent `dsh-terminal-bash` prompt patch, which npx/npm updates silently revert. `DSH_RESTART_PORT` overrides the port (default 3080).
121
+
122
+ **Installed a plugin via `/cc-plugin` but its skills don't show.** Run `/reload-cc-plugins`. Still missing → restart DSH (plugin-shipped MCP servers always need a restart).
123
+
124
+ **`/cc-plugin` reports "claude CLI unavailable".** The fallback engine handles install/enable/disable; for marketplace add/update, install Claude Code (`npm install -g @anthropic-ai/claude-code`) or manage marketplaces from Claude Code directly.
77
125
 
78
126
  ## Acknowledgments
79
127
 
package/README.zh-CN.md CHANGED
@@ -9,12 +9,12 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <img src="https://img.shields.io/badge/version-0.3.0-blue?style=flat-square" alt="Version">
12
+ <img src="https://img.shields.io/badge/version-0.6.2-blue?style=flat-square" alt="Version">
13
13
  <img src="https://img.shields.io/badge/node-%3E%3D20-green?style=flat-square&logo=node.js&logoColor=white" alt="Node">
14
14
  <img src="https://img.shields.io/badge/license-MIT-orange?style=flat-square" alt="License">
15
15
  </p>
16
16
 
17
- <p align="center">DeepSeek Harness 插件:把 Claude Code 的 <code>.claude/</code> 目录原生桥接进 DSH —— skills、斜杠命令、rules 零迁移直接复用。</p>
17
+ <p align="center">DeepSeek Harness 插件:把 Claude Code 的 <code>.claude/</code> 目录原生桥接进 DSH —— skills、斜杠命令、rules、agents、hooks、MCP 服务器零迁移直接复用。</p>
18
18
 
19
19
  ## 功能
20
20
 
@@ -23,27 +23,44 @@
23
23
  | `skills/**/SKILL.md` | DSH skill provider | 仅 name + description 进模型可见目录;正文按需加载(`skill` 工具)。新增 skill 下次 catalog 刷新即出现,无需重启。 |
24
24
  | `commands/*.md` | DSH skill provider | 同上,且用户可直接调用:斜杠菜单里 `/command-name` 可用。 |
25
25
  | `rules/*.md` | 消息流注入 | rules 全文拼接,包 `<system-reminder>` 信封,每会话一次以 user-role 消息插在消息数组最前 —— 与 Claude Code 同通道(`prependUserContext`),模型可靠遵循。 |
26
+ | `agents/*.md` | DSH skill provider(委派 shim) | DSH 没有 markdown 子代理格式,因此每个 agent 文件变成 **skill**,正文开头带显式"以此人设委派子代理"指令。模型与用户均可调用,`/agent-name` 可用。同名 agent 与 skill 一样按 rank 去重。 |
27
+ | `.claude/settings.json` → `hooks` | 工具/Prompt 钩子 | Claude Code hooks 子集桥接到 DSH 的 `tools/pre-execute`(PreToolUse)、`tools/post-execute`(PostToolUse)与 `agent/pre-step`(UserPromptSubmit)瀑布。命令经 `/bin/sh -c` 运行,stdin 携带 Claude 风格 JSON;exit 2 拒绝/阻断,`hookSpecificOutput` 覆盖生效,超时放行并告警。 |
28
+ | `<projectRoot>/.mcp.json` | MCP 服务器 | Claude Code 格式的 MCP 服务器定义在 DSH 启动时(启动工区)翻译成 `dsh-mcp-client` 插件实例:`command` → stdio,`url` → streamable-http。畸形条目或缺少 `@deepseek-ai/dsh-mcp-client` 时降级为告警,绝不崩溃。 |
29
+ | `~/.claude/plugins`(已安装插件) | DSH skill provider | 已安装的 Claude Code 插件市场插件贡献其 skills/commands/agents(读取每个安装的 `.claude-plugin/plugin.json` 清单,无清单时回退目录扫描),rank `750` —— 长尾:project `.claude`、DSH 原生、`~/.claude` 在同名冲突时全部优先。插件的 MCP 服务器(清单 `mcpServers`)仅在显式开启 `enablePluginMcp` 后挂载 —— 挂载第三方 MCP 比列出 skills 信任门槛更高。 |
26
30
 
27
- 同类三个目录同样读取**用户级** `~/.claude/`(skills、commands、rules)。同名 skill/command/rule 去重,优先级固定:
31
+ 同类目录同样读取**用户级** `~/.claude/`(skills、commands、rules、agents 与 `~/.claude/settings.json` hooks)。同名 skill/command/rule/agent 去重,优先级固定:
28
32
 
29
- **项目 `.claude` > DSH 原生 > `~/.claude`**
33
+ **项目 `.claude` > DSH 原生(`.dsh`)> `~/.claude`**
30
34
 
31
- - 项目条目 rank=`50`,`~/.claude` 条目 rank=`700`,DSH 自带 bundled skills 固定 rank=`600`(`BUNDLED_SKILL_RANK`)——项目 skill 永远压过 DSH 内置与用户副本;用户 skill 永远压不过 DSH 原生。
35
+ - 项目 `.claude` 条目 rank=`50`;DSH 自身 skills —— 项目 `.dsh` 根、`.agents` 根与 bundled skills(rank=`100`–`600`,`BUNDLED_SKILL_RANK`)—— 位于两者之间;`~/.claude` 条目 rank=`700`。项目 skill 永远压过 DSH 原生与用户副本;用户 skill 永远压不过 DSH 原生。
32
36
  - `~/.claude/rules` 中与项目同 basename 的 rule 文件被跳过(项目优先)。
33
37
 
34
38
  `CLAUDE.md` / `AGENTS.md` **不碰** —— DSH 内置 `dsh-agent-instructions` 已处理。
35
39
 
40
+ ## 内置命令
41
+
42
+ 安装本插件后 catalog 多出三个管理 skill:
43
+
44
+ | 命令 | 作用 |
45
+ |---|---|
46
+ | `/cc-plugin` | 完整 Claude Code 插件管理:`list`、`install <名>[@市场]`、`uninstall`、`enable`、`disable`、`update [名]`、`search <词>`、`marketplace list\|add\|remove\|update`。一键语法 `/cc-plugin <名>@<市场>` 直接安装。引擎:有 `claude` CLI 时优先调度,否则内置降级(直接操作 JSON + git,被改文件自动时间戳备份)。状态全部落在 Claude 原生位置(`~/.claude/plugins`、`~/.claude/settings.json` 的 `enabledPlugins`),Claude Code 与 DSH 读同一份真相。 |
47
+ | `/reload-cc-plugins` | 热重载 skill catalog:清缓存并广播变更,新装/卸载的插件技能**当前会话**立即可见 —— 无需重启、无需新会话。 |
48
+ | `/reload-skills` | `/reload-cc-plugins` 的别名。 |
49
+
50
+ 典型闭环:`/cc-plugin install ralph-loop@claude-plugins-official` → `/reload-cc-plugins` → 新技能立即可见。插件自带的 MCP 服务器仍需重启 DSH(进程级挂载)。
51
+
36
52
  ## 环境要求
37
53
 
38
54
  - DSH 及其 profile(如 `web`)
39
- - 使用 Claude Code 约定的项目:`.claude/skills/`、`.claude/commands/`、`.claude/rules/`(均可选;`~/.claude/` 对应目录同样生效)
55
+ - 使用 Claude Code 约定的项目:`.claude/skills/`、`.claude/commands/`、`.claude/rules/`、`.claude/agents/`、`.claude/settings.json` 与项目根 `.mcp.json`(均可选;`~/.claude/` 对应目录同样生效)
56
+ - `PATH` 上有 `pnpm` —— `dsh plugin` 是 pnpm 的薄转发层
40
57
 
41
- ## 安装
58
+ ## 安装 / 更新
42
59
 
43
- 一条命令 —— 包声明了 `dsh.bundle`,DSH 自动激活(无需手改 `cordis.patch.yml`):
60
+ 一条命令 —— 包声明了 `dsh.bundle`,DSH 自动激活(无需手改 `cordis.patch.yml`)。安装或更新到最新版:
44
61
 
45
62
  ```bash
46
- dsh plugin --profile web add dsh-claude-compat
63
+ dsh plugin --profile web add dsh-claude-compat@latest
47
64
  ```
48
65
 
49
66
  或从 GitHub:
@@ -58,8 +75,13 @@ dsh plugin --profile web add github:biedongbin/dsh-claude-compat
58
75
 
59
76
  | 选项 | 默认值 | 说明 |
60
77
  |---|---|---|
61
- | `enableSkills` | `true` | 注册 `.claude/skills` + `.claude/commands` provider(项目与 `~/.claude` 均含) |
78
+ | `enableSkills` | `true` | 注册 `.claude/skills` + `.claude/commands` + `.claude/agents` provider(项目与 `~/.claude` 均含) |
62
79
  | `enableRules` | `true` | 注入项目 + `~/.claude` 的 `rules/*.md` 到消息流 |
80
+ | `enableMcp` | `true` | 把 `<projectRoot>/.mcp.json` 翻译为挂载的 MCP 服务器插件 |
81
+ | `mcpFailOnStartupError` | `false` | 转发给 `dsh-mcp-client`:MCP 服务器连接失败时让插件启动失败 |
82
+ | `enableHooks` | `true` | 运行 `.claude/settings.json` hooks(Pre/PostToolUse、UserPromptSubmit) |
83
+ | `hooksTimeoutMs` | `60000` | 单个 hook 运行超时(UserPromptSubmit 无论如何上限 10s) |
84
+ | `enableAgents` | `true` | 把 `.claude/agents/*.md` 暴露为委派 shim skill |
63
85
  | `rulesMaxBytes` | `65536` | 注入项目 rules 总量硬上限 |
64
86
  | `userRulesMaxBytes` | `65536` | 注入 `~/.claude/rules` 总量硬上限 |
65
87
  | `projectRootMarkers` | `[".git"]` | 项目根发现的祖先标记 |
@@ -68,12 +90,38 @@ dsh plugin --profile web add github:biedongbin/dsh-claude-compat
68
90
  | `userSkillRank` | `700` | `~/.claude` skills 的 provider 排名(输给 DSH 原生 `600`) |
69
91
  | `userSkillSource` | `user-claude` | `~/.claude` catalog 条目来源标签 |
70
92
  | `userClaudeDir` | `~/.claude` | 用户级 `.claude` 目录(`~` 展开为 home 目录) |
93
+ | `enablePlugins` | `true` | 暴露已安装 Claude Code 插件(`~/.claude/plugins`)的 skills/commands/agents |
94
+ | `pluginSkillRank` | `750` | 插件内容的 provider 排名(长尾 —— 其他一切优先) |
95
+ | `pluginSkillSource` | `claude-plugin` | 插件目录条目来源标签 |
96
+ | `pluginsRoot` | `~/.claude/plugins` | 插件市场根目录(`installed_plugins.json` + `cache/`) |
97
+ | `enablePluginMcp` | `false` | 挂载插件声明的 MCP 服务器(需显式开启;要求 `enablePlugins` 与 `enableMcp`) |
98
+ | `enablePluginManager` | `true` | 注册 `/cc-plugin`、`/reload-cc-plugins`、`/reload-skills` 管理 skill |
99
+ | `pluginManagerRank` | `40` | 内置管理 skill 的 rank(catalog 顶部) |
71
100
 
72
101
  ## 说明
73
102
 
74
103
  - **Skill 命名**:DSH 要求 kebab-case。嵌套 skill 目录扁平化(`gitnexus/gitnexus-guide` → `gitnexus-gitnexus-guide`);frontmatter 名字非法时回退目录名。
104
+ - **MCP 生命周期**:`.mcp.json` 仅在 DSH 启动时(启动工区)读取一次,每个服务器随进程生命周期挂载 —— 非按会话。修改后需重启 DSH。
105
+ - **Hooks 范围**:刻意只实现 Claude Code hooks 的一个小子集:PreToolUse / PostToolUse / UserPromptSubmit。matcher 支持精确名、`*` 通配与 `|` 或;命令 stdin 携带 Claude 风格 JSON。exit 2 = 拒绝(Pre)/ 阻断(Post);其它非零退出与超时放行并告警。
75
106
  - **Rules 粒度**:每个新会话读取(按会话 cwd 缓存)。会话中改 rule,下一会话生效。
76
107
  - **Rules 内容**:rules 原文注入为模型指令。只提交你想让模型遵循的 rule —— 信任级别同 `CLAUDE.md`。
108
+ - **Catalog 快照时机**:skill catalog 在会话创建时快照。会话中途安装/修改的 skill 通过 `/reload-cc-plugins` 热生效,或下一会话生效。
109
+
110
+ ## 故障排查
111
+
112
+ **重启后 DSH 起不来 / 3080 端口卡死。** 旧进程占着端口(日志特征 `EADDRINUSE`)。用自带重启脚本 —— 干净等待停止、SIGKILL 兜底、探活端口后才报成功:
113
+
114
+ ```bash
115
+ npx dsh-claude-compat-restart # bin 别名(装包即得)
116
+ bash node_modules/dsh-claude-compat/scripts/dsh-restart.sh # 直接跑
117
+ bash scripts/dsh-restart.sh --no-patch # 跳过 prompt 补丁,只重启
118
+ ```
119
+
120
+ 脚本同时会重新幂等打 `dsh-terminal-bash` 的 prompt 补丁(npx/npm 更新会悄悄还原它)。`DSH_RESTART_PORT` 可覆盖端口(默认 3080)。
121
+
122
+ **`/cc-plugin` 装了插件但技能没出现。** 先 `/reload-cc-plugins`。仍没有 → 重启 DSH(插件自带 MCP 服务器必须重启)。
123
+
124
+ **`/cc-plugin` 提示 claude CLI 不可用。** 降级引擎已覆盖 install/enable/disable;marketplace add/update 需要安装 Claude Code(`npm install -g @anthropic-ai/claude-code`)或直接在 Claude Code 里管理市场。
77
125
 
78
126
  ## 社区鸣谢
79
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-claude-compat",
3
- "version": "0.3.0",
3
+ "version": "0.6.2",
4
4
  "description": "DeepSeek Harness plugin: bridge Claude Code's .claude/ directories (project and ~/.claude: skills, commands, rules) into DSH native skill registry and message-stream rules injection.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -16,7 +16,8 @@
16
16
  "src",
17
17
  "README.md",
18
18
  "README.zh-CN.md",
19
- "cordis.patch.yml"
19
+ "cordis.patch.yml",
20
+ "scripts"
20
21
  ],
21
22
  "keywords": [
22
23
  "dsh",
@@ -38,15 +39,26 @@
38
39
  "@deepseek-ai/schemastery": "*"
39
40
  },
40
41
  "peerDependenciesMeta": {
41
- "@deepseek-ai/cordis": { "optional": true },
42
- "@deepseek-ai/dsh-skill": { "optional": true },
43
- "@deepseek-ai/dsh-llm": { "optional": true },
44
- "@deepseek-ai/schemastery": { "optional": true }
42
+ "@deepseek-ai/cordis": {
43
+ "optional": true
44
+ },
45
+ "@deepseek-ai/dsh-skill": {
46
+ "optional": true
47
+ },
48
+ "@deepseek-ai/dsh-llm": {
49
+ "optional": true
50
+ },
51
+ "@deepseek-ai/schemastery": {
52
+ "optional": true
53
+ }
45
54
  },
46
55
  "engines": {
47
56
  "node": ">=20"
48
57
  },
49
58
  "scripts": {
50
- "check": "node --check src/index.js && node --test test/self-check.test.js"
59
+ "check": "node --check src/index.js && node --check src/lib.js && node --check src/mcp.js && node --check src/hooks.js && node --check src/agents.js && node --check src/plugins.js && node --test test/self-check.test.js test/v040.test.js test/v050.test.js"
60
+ },
61
+ "bin": {
62
+ "dsh-claude-compat-restart": "scripts/dsh-restart.sh"
51
63
  }
52
- }
64
+ }
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # dsh-restart.sh — restart the DSH Web GUI safely, with an optional
3
+ # terminal-bash prompt patch.
4
+ #
5
+ # What it does:
6
+ # 1. (optional, --no-patch to skip) Patch the dsh-terminal-bash plugin's
7
+ # hardcoded "dsh> " prompt so DSH's own placeholder prompt is honored.
8
+ # Idempotent: skips when already patched. Re-run after every npx/npm
9
+ # refresh of DSH (the cache overwrite reverts the patch).
10
+ # 2. Stop any running `dsh web` (graceful wait, up to 10s).
11
+ # 3. Start `dsh web` in the background (nohup), log to a temp file.
12
+ # 4. Wait until http://127.0.0.1:3080 responds (up to 30s); on timeout
13
+ # print the last 20 log lines and exit 1.
14
+ #
15
+ # Requires: bash, curl. Works with a global `dsh`, an nvm-installed `dsh`,
16
+ # or the npx cache layout (`~/.npm/_npx/*/node_modules/.bin/dsh`).
17
+ set -euo pipefail
18
+
19
+ PORT="${DSH_RESTART_PORT:-3080}"
20
+ PATCH=1
21
+ for arg in "$@"; do
22
+ case "$arg" in
23
+ --no-patch) PATCH=0 ;;
24
+ -h|--help) sed -n '2,16p' "$0"; exit 0 ;;
25
+ *) echo "unknown arg: $arg (see --help)" >&2; exit 2 ;;
26
+ esac
27
+ done
28
+
29
+ # ── 1. terminal-bash prompt patch (idempotent) ───────────────────────────────
30
+ if [ "$PATCH" = 1 ]; then
31
+ OLD_PROMPT='const CONTROLLED_PROMPT = "dsh> ";'
32
+ NEW_PROMPT='const CONTROLLED_PROMPT = "__DSH_PERSISTENT_BASH_PROMPT__ ";'
33
+ OLD_LEN='Math.max(0, 6 - this.promptTail.length)'
34
+ NEW_LEN='Math.max(0, CONTROLLED_PROMPT.length + 1 - this.promptTail.length)'
35
+
36
+ # Find every installed copy of dsh-terminal-bash: npx cache + any global
37
+ # node_modules reachable from `dsh`'s real location.
38
+ CANDIDATES="$(ls "$HOME"/.npm/_npx/*/node_modules/@deepseek-ai/dsh-terminal-bash/lib/index.js 2>/dev/null || true)"
39
+ DSH_ON_PATH="$(command -v dsh || true)"
40
+ if [ -n "$DSH_ON_PATH" ]; then
41
+ REAL="$(readlink -f "$DSH_ON_PATH" 2>/dev/null || echo "$DSH_ON_PATH")"
42
+ ROOT="$(dirname "$(dirname "$(dirname "$(dirname "$REAL")")")")"
43
+ [ -f "$ROOT/@deepseek-ai/dsh-terminal-bash/lib/index.js" ] && CANDIDATES="$CANDIDATES
44
+ $ROOT/@deepseek-ai/dsh-terminal-bash/lib/index.js"
45
+ fi
46
+
47
+ if [ -z "$CANDIDATES" ]; then
48
+ echo "[fix] no dsh-terminal-bash installation found; skipping patch"
49
+ else
50
+ for FILE in $CANDIDATES; do
51
+ if grep -qF "$NEW_PROMPT" "$FILE" && grep -qF "$NEW_LEN" "$FILE"; then
52
+ echo "[fix] already patched: $FILE"
53
+ continue
54
+ fi
55
+ echo "[fix] patching: $FILE"
56
+ if sed -i.bak -e "s|$OLD_PROMPT|$NEW_PROMPT|" -e "s|$OLD_LEN|$NEW_LEN|" "$FILE" 2>/dev/null \
57
+ || sed -i '' -e "s|$OLD_PROMPT|$NEW_PROMPT|" -e "s|$OLD_LEN|$NEW_LEN|" "$FILE"; then
58
+ rm -f "$FILE.bak"
59
+ grep -qF "$NEW_PROMPT" "$FILE" && grep -qF "$NEW_LEN" "$FILE" \
60
+ || { echo "[fix] patch failed on $FILE"; exit 1; }
61
+ else
62
+ echo "[fix] cannot write $FILE (permissions?); skipping"; continue
63
+ fi
64
+ done
65
+ fi
66
+ fi
67
+
68
+ # ── 2. stop running dsh web ──────────────────────────────────────────────────
69
+ echo "[restart] stopping dsh web"
70
+ pkill -f "dsh web" 2>/dev/null || true
71
+ for _ in $(seq 1 20); do pgrep -f "dsh web" >/dev/null || break; sleep 0.5; done
72
+ if pgrep -f "dsh web" >/dev/null; then
73
+ echo "[restart] dsh web did not stop; last resort SIGKILL"
74
+ pkill -9 -f "dsh web" 2>/dev/null || true
75
+ sleep 1
76
+ pgrep -f "dsh web" >/dev/null && { echo "[restart] cannot stop dsh web"; exit 1; }
77
+ fi
78
+
79
+ # ── 3. locate the dsh binary ─────────────────────────────────────────────────
80
+ DSH_BIN="$(command -v dsh || true)"
81
+ if [ -z "$DSH_BIN" ]; then
82
+ # newest npx cache entry first (npm exec resolves to the same one)
83
+ DSH_BIN="$(ls -t "$HOME"/.npm/_npx/*/node_modules/.bin/dsh 2>/dev/null | head -1 || true)"
84
+ fi
85
+ if [ -z "$DSH_BIN" ]; then
86
+ echo "[restart] no dsh binary found (PATH or npx cache). Install with: npm install -g @deepseek-ai/dsh" >&2
87
+ exit 1
88
+ fi
89
+ echo "[restart] using: $DSH_BIN"
90
+
91
+ # ── 4. start + health-check ──────────────────────────────────────────────────
92
+ LOG="$(mktemp -t dsh-web-restart)"
93
+ echo "[restart] starting dsh web on :$PORT"
94
+ nohup "$DSH_BIN" web >"$LOG" 2>&1 &
95
+ PID=$!
96
+ echo "[restart] pid=$PID log=$LOG"
97
+ for _ in $(seq 1 30); do
98
+ if curl -sf -o /dev/null "http://127.0.0.1:$PORT"; then
99
+ echo "[restart] http://127.0.0.1:$PORT is up ✅"
100
+ exit 0
101
+ fi
102
+ kill -0 "$PID" 2>/dev/null || break
103
+ sleep 1
104
+ done
105
+ echo "[restart] failed; last log lines:"
106
+ tail -20 "$LOG"
107
+ exit 1
package/src/agents.js ADDED
@@ -0,0 +1,77 @@
1
+ // dsh-claude-compat: .claude/agents/*.md compatibility. DSH has no markdown
2
+ // subagent-definition format, so each agent file is surfaced as a DSH *skill*
3
+ // candidate: the frontmatter name (or filename stem) becomes the skill name
4
+ // (model- and user-invocable, so `/agent-name` works), the description doubles
5
+ // as whenToUse, and get() prefixes the body with an explicit subagent-delegation
6
+ // instruction. Project .claude/agents wins over ~/.claude/agents by name via
7
+ // the provider's rank (50 < 700) plus the shared dedupe flow.
8
+
9
+ import { join } from 'node:path';
10
+ import { isSkillName } from '@deepseek-ai/dsh-skill';
11
+ import {
12
+ readTextSafe,
13
+ stringField,
14
+ parseFrontmatter,
15
+ pathExists,
16
+ } from './lib.js';
17
+
18
+ const AGENT_HEADER = 'Agent delegation: use the subagent/background-agent capability with this persona and instructions.';
19
+
20
+ // Discover agent candidates from one agents dir (flat: *.md). Returns DSH skill
21
+ // candidates in the same shape as discoverSkills/discoverCommands so the
22
+ // registry's dedupe treats them uniformly.
23
+ export async function discoverAgents(rootDir, providerName, source, rank) {
24
+ const out = [];
25
+ if (!(await pathExists(rootDir))) return out;
26
+ let entries;
27
+ try {
28
+ const { readdir } = await import('node:fs/promises');
29
+ entries = await readdir(rootDir, { withFileTypes: true, encoding: 'utf8' });
30
+ } catch { return out; }
31
+ for (const entry of entries) {
32
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
33
+ const path = join(rootDir, entry.name);
34
+ const raw = await readTextSafe(path);
35
+ if (raw === undefined) continue;
36
+ const parsed = parseFrontmatter(raw);
37
+ const data = parsed?.data ?? {};
38
+ const stem = entry.name.slice(0, -3);
39
+ let name = stringField(data, 'name');
40
+ if (name === undefined || !isSkillName(name)) name = stem;
41
+ if (!isSkillName(name)) continue; // non-kebab names rejected by registry anyway
42
+ const description = stringField(data, 'description')
43
+ ?? firstLine(parsed?.body ?? raw, 100);
44
+ const tools = stringField(data, 'tools');
45
+ out.push({
46
+ name,
47
+ description,
48
+ whenToUse: description,
49
+ invocation: { modelInvocable: true, userInvocable: true },
50
+ provider: providerName,
51
+ source,
52
+ rank,
53
+ locator: { path, directory: rootDir },
54
+ resourceBase: { kind: 'directory', path: rootDir },
55
+ path,
56
+ agentTools: tools,
57
+ });
58
+ }
59
+ return out;
60
+ }
61
+
62
+ function firstLine(body, max) {
63
+ if (typeof body !== 'string') return undefined;
64
+ const line = body.split('\n').find((l) => l.trim() !== '') ?? '';
65
+ const trimmed = line.trim();
66
+ if (trimmed === '') return undefined;
67
+ return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
68
+ }
69
+
70
+ // Render the content the skill tool returns for an agent-backed candidate.
71
+ export function renderAgentContent(candidate, body) {
72
+ const lines = [AGENT_HEADER];
73
+ if (candidate.agentTools !== undefined) lines.push(`Tools: ${candidate.agentTools}`);
74
+ lines.push('');
75
+ lines.push(body);
76
+ return lines.join('\n');
77
+ }