dsh-codex-port 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # 更新记录
2
+
3
+ [返回简介](README.md) · [使用说明](docs/USAGE.md) · [验证记录](docs/VALIDATION.md)
4
+
5
+ [历史英文记录](docs/CHANGELOG.en.md)
6
+
7
+ ## 0.3.0 (2026-10-05)
8
+
9
+ - 增加 dryRun 预览,先验证并显示安装、替换、跳过清单,不创建目标目录;完整解析 YAML 多行描述,相对目标按会话工作区解析,取消前不写入。
10
+
11
+ ## 0.2.4 (2026-09-28)
12
+
13
+ - 更新官方 Harness 0.2.0-rc.1 的兼容声明和共同加载验证;运行时代码未变。验证范围见[验证记录](docs/VALIDATION.md)。
14
+
15
+ ## 0.2.3 (2026-09-27)
16
+
17
+ Codex 目录按显式 codexHome、CODEX_HOME、默认 ~/.codex 的顺序解析,修复自定义或隔离目录无法发现插件技能的问题。
18
+
19
+ ## 更早的改动
20
+
21
+ 完整历史可查阅 [GitHub 提交记录](https://github.com/STARDUSTLC666/dsh-codex-port/commits/master)。
package/README.en.md CHANGED
@@ -1,94 +1,46 @@
1
- [中文](README.md)
2
-
3
- ![npm](https://img.shields.io/npm/v/dsh-codex-port) ![downloads](https://img.shields.io/npm/dm/dsh-codex-port) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-codex-port) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-codex-port?style=social)
4
-
5
1
  # dsh-codex-port
6
2
 
7
- ## 0.2.3 update (2026-09-27)
8
-
9
- Resolves the Codex directory from explicit codexHome, then CODEX_HOME, then ~/.codex. Custom and isolated homes can now discover plugin skills.
10
-
11
- Validation host: Harness 0.1.7-rc.2 built from official sources, retaining the local tool-scheduler fix. Build and automated checks pass; interactive coverage and external-service limits are recorded in this release round.
12
-
13
- [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
14
-
15
- Move the whole **official Codex plugin family** into DSH: scan `~/.codex` unpacked plugins and plugin caches, then batch-port their skills into DSH skills (automatic frontmatter conversion, codex-only files stripped, name sanitization, idempotent skips).
3
+ [中文](README.md)
16
4
 
17
- > Measured on a real machine: 186 official Codex plugins, 583 skills — one port run moved 577 successfully, 0 failures.
5
+ ![dsh-codex-port whale girl plugin cover](https://raw.githubusercontent.com/STARDUSTLC666/dsh-codex-port/master/assets/cover-whale-girl.png)
18
6
 
19
- ## Compatibility
7
+ Convert locally installed Codex skills into DSH-compatible skills.
20
8
 
21
- Verified with official `@deepseek-ai/dsh@0.1.5-rc.1` and Node `24.16.0` on 2026-09-11: all 18 components load alongside Modlens, with passing tool-schema, skill-registration and offline read-only invocation checks. Uses the `cordis.patch.yml` + `dsh.bundle.patch` bundle model. Node requirements match this Harness release: 22.19 or later within 22.x, or 24 or later. Live external-service workflows require separate configuration and validation.
9
+ [![npm](https://img.shields.io/npm/v/dsh-codex-port)](https://www.npmjs.com/package/dsh-codex-port) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-codex-port-downloads.svg)](https://www.npmjs.com/package/dsh-codex-port)
22
10
 
23
- ## Installation
11
+ ## What it does
24
12
 
25
- ```bash
26
- dsh plugin --profile web add dsh-codex-port
27
- ```
13
+ - Discover local Codex skills, including a custom CODEX_HOME.
14
+ - Convert frontmatter and normalize skill names.
15
+ - Import selected skills, skip duplicates and report results.
28
16
 
29
- Requires the Codex CLI to be installed (the `~/.codex` directory must exist).
17
+ ## Install
30
18
 
31
- ## Uninstall
19
+ In DSH Desktop, install `dsh-codex-port` from the Plugins panel. If the bundled dsh command is available:
32
20
 
33
21
  ```bash
34
- dsh plugin --profile web remove dsh-codex-port
22
+ dsh plugin --profile desktop add dsh-codex-port
35
23
  ```
36
24
 
37
- Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
25
+ For the web version, replace `desktop` with `web`. Restart DSH after installation.
38
26
 
27
+ ## Start using it
39
28
 
40
- ## Configuration
29
+ Ask the assistant to list available local skills, then import the ones you select.
41
30
 
42
- Everything is optional; defaults just work:
31
+ ## Requirements and configuration
43
32
 
44
- ```yaml
45
- - id: codex-port
46
- name: 'dsh-codex-port'
47
- config:
48
- # codexHome: C:\Users\you\.codex # Codex home (default ~/.codex)
49
- # targetDir: C:\Users\you\.dsh\skills # target skills dir (default <DSH_HOME>/skills)
50
- # overwrite: true # overwrite same-name skills (default skip)
51
- ```
52
-
53
- ## Tools
54
-
55
- | Tool | Purpose | Key parameters |
56
- | :-- | :-- | :-- |
57
- | `codex_list` | List discovered Codex plugins and skills | `plugin`/`skill` filters, `limit` 1-200 |
58
- | `codex_port` | Batch-port skills into DSH | `plugins`/`skills` filters, `targetDir`, `overwrite` |
59
- | `codex_status` | Compare source vs target: ported/missing counts | none |
60
-
61
- ### Examples
62
-
63
- ```text
64
- codex_list {} # what does Codex have?
65
- codex_list { plugin: remotion } # one plugin only
66
- codex_port {} # port everything (same names auto-skip)
67
- codex_port { plugins: [remotion, hyperframes] }
68
- codex_port { skills: [video-best], overwrite: true }
69
- codex_status {} # how many are left?
70
- ```
71
-
72
- Ported skills are immediately usable from the DSH skills directory; the agent triggers them by their descriptions.
73
-
74
- ## Porting rules
33
+ Requires a local Codex skills directory. Imported skills may still need their original tools, services or credentials.
75
34
 
76
- - **Frontmatter conversion**: Codex `name/description/metadata` → DSH `name/description/compatibility/allowed-tools`, multi-line descriptions preserved
77
- - **Codex-only files stripped**: `agents/*.yaml` and friends stay behind
78
- - **Name sanitization**: invalid characters become underscores; `..` traversal rejected outright
79
- - **Host loadability checks**: before replacement, the converted name must follow DSH's lowercase kebab-case grammar, exactly match its target directory, and have a nonempty description. Names with uppercase letters, underscores, dots, or a different sanitized directory name cannot replace an existing skill.
80
- - **Idempotent**: same-name skills are skipped by default; `overwrite=true` to replace
81
- - **Recoverable replacement**: copying, conversion, and read-back validation finish inside a unique `.dsh-port-<skill>-*` directory under the target root before the old skill moves to `previous/` and the new skill takes its place. Copy/conversion failures leave the old skill untouched. A failed switch attempts rollback; if rollback also fails, both copies remain and the error includes their recovery location. Content created by another writer is never deleted.
82
- - **Backups and path checks**: successful replacements retain `previous/` and `recovery.json`, using additional disk space; archive them manually when recovery is no longer needed. No `SKILL.md` sits at the transaction directory's top level, so current DSH one-level skill discovery does not register backups. Overlapping source/target paths, target symlinks/junctions, paths outside the target root, and Windows device names are rejected. Multiple renames are not one atomic Windows transaction; after process or system interruption, inspect `recovery.json` to restore the appropriate directory.
83
- - **Safe**: pure filesystem operations, zero runtime dependencies (yaml parsing only)
35
+ Detailed configuration, tool arguments and troubleshooting are in the [usage guide](docs/USAGE.en.md). For standalone development, follow the Node requirement in [package.json](package.json).
84
36
 
85
- ## Development
37
+ ## Documentation
86
38
 
87
- ```bash
88
- pnpm install
89
- pnpm test # build + offline tests; fixtures stay under workspace .harness-validation/codex-port-tests
90
- ```
39
+ - [Usage and troubleshooting](docs/USAGE.en.md)
40
+ - [Changelog](CHANGELOG.md)
41
+ - [Validation scope and history](docs/VALIDATION.md)
42
+ - [Report a problem or suggest a feature](https://github.com/STARDUSTLC666/dsh-codex-port/issues)
91
43
 
92
44
  ## License
93
45
 
94
- MIT
46
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,113 +1,46 @@
1
- [English](README.en.md)
2
-
3
1
  # dsh-codex-port
4
2
 
5
- ## 0.2.3 更新(2026-09-27)
6
-
7
- Codex 目录按显式 codexHome、CODEX_HOME、默认 ~/.codex 的顺序解析,修复自定义或隔离目录无法发现插件技能的问题。
8
-
9
- 验证宿主:官方源码构建的 Harness 0.1.7-rc.2(保留本地工具调度器修复)。构建与自动测试通过;实际操作和外部服务限制见本轮验收记录。
10
-
11
- > **Codex 全家桶,一条命令进 DSH**:实测 186 插件、583 技能、移植 577 个 0 失败。
12
-
13
- ![npm version](https://img.shields.io/npm/v/dsh-codex-port?label=npm&color=blue) ![npm downloads](https://img.shields.io/npm/dm/dsh-codex-port) ![license](https://img.shields.io/npm/l/dsh-codex-port) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-codex-port?style=social)
14
-
15
- [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
3
+ [English](README.en.md)
16
4
 
5
+ ![dsh-codex-port 鲸鱼娘插件封面](https://raw.githubusercontent.com/STARDUSTLC666/dsh-codex-port/master/assets/cover-whale-girl.png)
17
6
 
18
- 把 **Codex 官方插件全家桶**一键搬进 DSH:扫描 `~/.codex` 里的解包插件与插件缓存,把它们的技能批量移植为 DSH 技能(frontmatter 自动转换、codex 专属文件剔除、名称清洗、幂等跳过)。
7
+ 把本机 Codex 技能转换为 DSH 可使用的技能。
19
8
 
20
- > 本机实测:186 个 Codex 官方插件、583 个技能,一次移植 577 个成功、0 失败。
9
+ [![npm](https://img.shields.io/npm/v/dsh-codex-port)](https://www.npmjs.com/package/dsh-codex-port) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-codex-port-downloads.svg)](https://www.npmjs.com/package/dsh-codex-port)
21
10
 
22
- ## 兼容性
11
+ ## 功能
23
12
 
24
- 已在官方 `@deepseek-ai/dsh@0.1.5-rc.1`、Node `24.16.0` 上验证(2026-09-11):18 个组件与 Modlens 同载,工具 schema、技能注册及离线只读调用检查通过。采用 `cordis.patch.yml` + `dsh.bundle.patch` 组合包模型。Node 要求与该版本 Harness 一致:22.19 及以上的 22.x,或 24 及以上。外部服务的实际业务操作需按各组件配置单独验证。
13
+ - 发现本机 Codex 技能,支持自定义 CODEX_HOME。
14
+ - 转换 frontmatter,清洗技能名称。
15
+ - 支持选择性移植、重复跳过和迁移报告。
25
16
 
26
17
  ## 安装
27
18
 
28
- ```bash
29
- dsh plugin --profile web add dsh-codex-port
30
- ```
31
-
32
- 需要本机装有 Codex CLI(`~/.codex` 目录存在即可)。
33
-
34
- ## 卸载
19
+ 桌面版可在「插件」面板按包名 `dsh-codex-port` 安装。已配置 dsh 命令时也可使用:
35
20
 
36
21
  ```bash
37
- dsh plugin --profile web remove dsh-codex-port
38
- ```
39
-
40
- 卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
41
-
42
-
43
- ## 配置
44
-
45
- 全部可选,默认即可用:
46
-
47
- ```yaml
48
- - id: codex-port
49
- name: 'dsh-codex-port'
50
- config:
51
- # codexHome: C:\Users\you\.codex # Codex 家目录(默认 ~/.codex)
52
- # targetDir: C:\Users\you\.dsh\skills # 目标技能目录(默认 <DSH_HOME>/skills)
53
- # overwrite: true # 覆盖同名技能(默认跳过)
54
- ```
55
-
56
- ## 工具一览
57
-
58
- | 工具 | 作用 | 关键参数 |
59
- | :-- | :-- | :-- |
60
- | `codex_list` | 列出发现的 Codex 插件与技能 | `plugin`/`skill` 过滤,`limit` 1-200 |
61
- | `codex_port` | 批量移植为 DSH 技能 | `plugins`/`skills` 过滤,`targetDir`,`overwrite` |
62
- | `codex_status` | 对比源与目标:已移植/未移植统计 | 无 |
63
-
64
- ### 示例
65
-
66
- ```text
67
- codex_list {} # 看看 Codex 里有什么
68
- codex_list { plugin: remotion } # 只看某个插件
69
- codex_port {} # 全部移植(同名自动跳过)
70
- codex_port { plugins: [remotion, hyperframes] }
71
- codex_port { skills: [video-best], overwrite: true }
72
- codex_status {} # 还有多少没移植
22
+ dsh plugin --profile desktop add dsh-codex-port
73
23
  ```
74
24
 
75
- 移植后 DSH 技能目录立即可用,agent 按技能描述自动触发。
25
+ 网页版把命令中的 `desktop` 改为 `web`。安装后重启 DSH。
76
26
 
77
- ## 跨平台使用
27
+ ## 开始使用
78
28
 
79
- `targetDir` 不限于 DSH:指向任何支持 Agent Skills(SKILL.md)格式的 agent 技能目录,即可把 Codex 全家桶移植给它们:
29
+ 先让助手列出可移植技能,再说:“把这些选中的技能移植到 DSH。”
80
30
 
81
- | Agent | 建议 targetDir |
82
- | :-- | :-- |
83
- | DSH | `~/.dsh/skills`(默认)|
84
- | Claude Code | `~/.claude/skills/` |
85
- | Cursor | `.cursor/skills/` |
86
- | Gemini CLI | `~/.gemini/skills/` |
87
-
88
- ```text
89
- codex_port { targetDir: ~/.claude/skills }
90
- ```
31
+ ## 依赖与配置
91
32
 
33
+ 需要本机已有 Codex 技能目录。移植结果仍可能需要原技能要求的服务、工具或账号。
92
34
 
93
- ## 移植规则
35
+ 详细配置、工具参数与排错见[使用说明](docs/USAGE.md)。从源码独立开发时,Node 要求以 [package.json](package.json) 为准。
94
36
 
95
- - **frontmatter 转换**:Codex 的 `name/description/metadata` → DSH 的 `name/description/compatibility/allowed-tools`,多行描述完整保留
96
- - **剔除 codex 专属文件**:`agents/*.yaml` 等子代理描述不带走
97
- - **名称清洗**:非法字符替换为下划线,`..` 路径穿越直接拒绝
98
- - **宿主可加载性校验**:覆盖前要求转换后的技能名符合 DSH 的小写短横线命名规则、与目标目录同名,且描述为非空字符串;大写、下划线、点号或清洗后不同名的技能不会替换旧版。
99
- - **幂等**:同名技能默认跳过,`overwrite=true` 才覆盖
100
- - **覆盖失败可恢复**:先在目标根内的唯一 `.dsh-port-<技能名>-*` 目录中复制、转换并回读校验;成功后才把旧技能移到事务目录的 `previous/`,再切换新技能。复制或转换失败不会改动旧技能,切换失败会尝试恢复旧技能;恢复失败时保留两份资料,并在失败原因中给出恢复路径。不会删除已存在的其他写入。
101
- - **备份与目录边界**:成功覆盖也保留 `previous/` 及 `recovery.json`,会额外占用磁盘空间;确认不需要恢复后可自行归档。事务目录顶层不含 `SKILL.md`,不会被当前 DSH 的单层技能发现重复注册。拒绝源/目标重叠、目标符号链接或目录联接、路径越界及 Windows 设备名称。多次目录重命名不构成 Windows 上全程原子的事务;进程或系统中断后可按 `recovery.json` 检查和恢复。
102
- - **安全**:纯文件系统操作,零运行时依赖(仅 yaml 解析)
37
+ ## 文档
103
38
 
104
- ## 开发
105
-
106
- ```bash
107
- pnpm install
108
- pnpm test # 构建 + 离线测试;fixture 只写入工作区 .harness-validation/codex-port-tests
109
- ```
39
+ - [使用与排错](docs/USAGE.md)
40
+ - [更新记录](CHANGELOG.md)
41
+ - [验证范围与历史记录](docs/VALIDATION.md)
42
+ - [问题反馈与功能建议](https://github.com/STARDUSTLC666/dsh-codex-port/issues)
110
43
 
111
44
  ## License
112
45
 
113
- MIT
46
+ [MIT](LICENSE)
@@ -0,0 +1,15 @@
1
+ # Historical release notes
2
+
3
+ [Current changelog](../CHANGELOG.md) · [Overview](../README.en.md)
4
+
5
+ These English notes preserve the earlier translations. The main changelog contains the consolidated version history.
6
+
7
+ ## 0.3.0 (2026-10-05)
8
+
9
+ - Add dryRun previews of installs, replacements and skips without creating target directories. Parse multiline YAML descriptions, resolve relative targets against the session workspace and honor pre-cancellation.
10
+
11
+ ## 0.2.3 (2026-09-27)
12
+
13
+ Resolves the Codex directory from explicit codexHome, then CODEX_HOME, then ~/.codex. Custom and isolated homes can now discover plugin skills.
14
+
15
+ Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-28. All 53 plugin tests pass in an isolated environment; all 18 plugins mount together in one host registering 4 tools, with tool schemas and health-check contracts passing. No live ports or external services were exercised in this round.
@@ -0,0 +1,79 @@
1
+ # dsh-codex-port usage guide
2
+
3
+ [Overview](../README.en.md) · [Changelog](../CHANGELOG.md) · [Validation](VALIDATION.md)
4
+
5
+ ## Current improvements
6
+
7
+ Run codex_port with dryRun=true and targetDir="skills-import" first, then remove dryRun to install. An overwrite preview creates no backups or replacements; actual overwrites retain previous / recovery.json.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ dsh plugin --profile web add dsh-codex-port
13
+ ```
14
+
15
+ Requires the Codex CLI to be installed (the `~/.codex` directory must exist).
16
+
17
+ ## Uninstall
18
+
19
+ ```bash
20
+ dsh plugin --profile web remove dsh-codex-port
21
+ ```
22
+
23
+ Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
24
+
25
+ ## Configuration
26
+
27
+ Everything is optional; defaults just work:
28
+
29
+ ```yaml
30
+ - id: codex-port
31
+ name: 'dsh-codex-port'
32
+ config:
33
+ # codexHome: C:\Users\you\.codex # Codex home (default ~/.codex)
34
+ # targetDir: C:\Users\you\.dsh\skills # target skills dir (default <DSH_HOME>/skills)
35
+ # overwrite: true # overwrite same-name skills (default skip)
36
+ ```
37
+
38
+ ## Tools
39
+
40
+ | Tool | Purpose | Key parameters |
41
+ | :-- | :-- | :-- |
42
+ | `codex_list` | List discovered Codex plugins and skills | `plugin`/`skill` filters, `limit` 1-200 |
43
+ | `codex_port` | Batch-port skills into DSH | `plugins`/`skills` filters, `targetDir`, `overwrite` |
44
+ | `codex_status` | Compare source vs target: ported/missing counts | none |
45
+
46
+ ### Examples
47
+
48
+ ```text
49
+ codex_list {} # what does Codex have?
50
+ codex_list { plugin: remotion } # one plugin only
51
+ codex_port {} # port everything (same names auto-skip)
52
+ codex_port { plugins: [remotion, hyperframes] }
53
+ codex_port { skills: [video-best], overwrite: true }
54
+ codex_status {} # how many are left?
55
+ ```
56
+
57
+ Ported skills are immediately usable from the DSH skills directory; the agent triggers them by their descriptions.
58
+
59
+ ## Porting rules
60
+
61
+ - **Frontmatter conversion**: Codex `name/description/metadata` → DSH `name/description/compatibility/allowed-tools`, multi-line descriptions preserved
62
+ - **Codex-only files stripped**: `agents/*.yaml` and friends stay behind
63
+ - **Name sanitization**: invalid characters become underscores; `..` traversal rejected outright
64
+ - **Host loadability checks**: before replacement, the converted name must follow DSH's lowercase kebab-case grammar, exactly match its target directory, and have a nonempty description. Names with uppercase letters, underscores, dots, or a different sanitized directory name cannot replace an existing skill.
65
+ - **Idempotent**: same-name skills are skipped by default; `overwrite=true` to replace
66
+ - **Recoverable replacement**: copying, conversion, and read-back validation finish inside a unique `.dsh-port-<skill>-*` directory under the target root before the old skill moves to `previous/` and the new skill takes its place. Copy/conversion failures leave the old skill untouched. A failed switch attempts rollback; if rollback also fails, both copies remain and the error includes their recovery location. Content created by another writer is never deleted.
67
+ - **Backups and path checks**: successful replacements retain `previous/` and `recovery.json`, using additional disk space; archive them manually when recovery is no longer needed. No `SKILL.md` sits at the transaction directory's top level, so current DSH one-level skill discovery does not register backups. Overlapping source/target paths, target symlinks/junctions, paths outside the target root, and Windows device names are rejected. Multiple renames are not one atomic Windows transaction; after process or system interruption, inspect `recovery.json` to restore the appropriate directory.
68
+ - **Safe**: pure filesystem operations, with a YAML parser as the only runtime dependency
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ pnpm install
74
+ pnpm test # build + offline tests; fixtures stay under workspace .harness-validation/codex-port-tests
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT
package/docs/USAGE.md ADDED
@@ -0,0 +1,94 @@
1
+ # dsh-codex-port 使用说明
2
+
3
+ [返回简介](../README.md) · [更新记录](../CHANGELOG.md) · [验证记录](VALIDATION.md)
4
+
5
+ ## 本次改进
6
+
7
+ 先执行 codex_port { dryRun: true, targetDir: "skills-import" };确认清单后移除 dryRun 执行。overwrite=true 的预览不会备份或替换,实际覆盖才保留 previous / recovery.json。
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ dsh plugin --profile web add dsh-codex-port
13
+ ```
14
+
15
+ 需要本机装有 Codex CLI(`~/.codex` 目录存在即可)。
16
+
17
+ ## 卸载
18
+
19
+ ```bash
20
+ dsh plugin --profile web remove dsh-codex-port
21
+ ```
22
+
23
+ 卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
24
+
25
+ ## 配置
26
+
27
+ 全部可选,默认即可用:
28
+
29
+ ```yaml
30
+ - id: codex-port
31
+ name: 'dsh-codex-port'
32
+ config:
33
+ # codexHome: C:\Users\you\.codex # Codex 家目录(默认 ~/.codex)
34
+ # targetDir: C:\Users\you\.dsh\skills # 目标技能目录(默认 <DSH_HOME>/skills)
35
+ # overwrite: true # 覆盖同名技能(默认跳过)
36
+ ```
37
+
38
+ ## 工具一览
39
+
40
+ | 工具 | 作用 | 关键参数 |
41
+ | :-- | :-- | :-- |
42
+ | `codex_list` | 列出发现的 Codex 插件与技能 | `plugin`/`skill` 过滤,`limit` 1-200 |
43
+ | `codex_port` | 批量移植为 DSH 技能 | `plugins`/`skills` 过滤,`targetDir`,`overwrite` |
44
+ | `codex_status` | 对比源与目标:已移植/未移植统计 | 无 |
45
+
46
+ ### 示例
47
+
48
+ ```text
49
+ codex_list {} # 看看 Codex 里有什么
50
+ codex_list { plugin: remotion } # 只看某个插件
51
+ codex_port {} # 全部移植(同名自动跳过)
52
+ codex_port { plugins: [remotion, hyperframes] }
53
+ codex_port { skills: [video-best], overwrite: true }
54
+ codex_status {} # 还有多少没移植
55
+ ```
56
+
57
+ 移植后 DSH 技能目录立即可用,agent 按技能描述自动触发。
58
+
59
+ ## 跨平台使用
60
+
61
+ `targetDir` 不限于 DSH:指向任何支持 Agent Skills(SKILL.md)格式的 agent 技能目录,即可把 Codex 全家桶移植给它们:
62
+
63
+ | Agent | 建议 targetDir |
64
+ | :-- | :-- |
65
+ | DSH | `~/.dsh/skills`(默认)|
66
+ | Claude Code | `~/.claude/skills/` |
67
+ | Cursor | `.cursor/skills/` |
68
+ | Gemini CLI | `~/.gemini/skills/` |
69
+
70
+ ```text
71
+ codex_port { targetDir: ~/.claude/skills }
72
+ ```
73
+
74
+ ## 移植规则
75
+
76
+ - **frontmatter 转换**:Codex 的 `name/description/metadata` → DSH 的 `name/description/compatibility/allowed-tools`,多行描述完整保留
77
+ - **剔除 codex 专属文件**:`agents/*.yaml` 等子代理描述不带走
78
+ - **名称清洗**:非法字符替换为下划线,`..` 路径穿越直接拒绝
79
+ - **宿主可加载性校验**:覆盖前要求转换后的技能名符合 DSH 的小写短横线命名规则、与目标目录同名,且描述为非空字符串;大写、下划线、点号或清洗后不同名的技能不会替换旧版。
80
+ - **幂等**:同名技能默认跳过,`overwrite=true` 才覆盖
81
+ - **覆盖失败可恢复**:先在目标根内的唯一 `.dsh-port-<技能名>-*` 目录中复制、转换并回读校验;成功后才把旧技能移到事务目录的 `previous/`,再切换新技能。复制或转换失败不会改动旧技能,切换失败会尝试恢复旧技能;恢复失败时保留两份资料,并在失败原因中给出恢复路径。不会删除已存在的其他写入。
82
+ - **备份与目录边界**:成功覆盖也保留 `previous/` 及 `recovery.json`,会额外占用磁盘空间;确认不需要恢复后可自行归档。事务目录顶层不含 `SKILL.md`,不会被当前 DSH 的单层技能发现重复注册。拒绝源/目标重叠、目标符号链接或目录联接、路径越界及 Windows 设备名称。多次目录重命名不构成 Windows 上全程原子的事务;进程或系统中断后可按 `recovery.json` 检查和恢复。
83
+ - **安全**:纯文件系统操作,仅依赖 YAML 解析库
84
+
85
+ ## 开发
86
+
87
+ ```bash
88
+ pnpm install
89
+ pnpm test # 构建 + 离线测试;fixture 只写入工作区 .harness-validation/codex-port-tests
90
+ ```
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,9 @@
1
+ # dsh-codex-port 验证记录
2
+
3
+ 本页整理原 README 的历史验证说明,保留当时的版本、日期与范围。自动测试、启动检查、浏览器操作和真实服务验收分别记录,不能相互替代。更详细的版本验收文件仍保留在仓库中。
4
+
5
+ ## 原中文记录
6
+
7
+ ## 0.2.4 原验证说明
8
+
9
+ 验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。53 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 4 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
package/lib/discover.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
8
8
  import { join } from 'node:path';
9
+ import { parseSkillFile } from './frontmatter.js';
9
10
  function asRecord(value) {
10
11
  return typeof value === 'object' && value !== null ? value : {};
11
12
  }
@@ -75,16 +76,11 @@ function enumerateSkills(pluginDir) {
75
76
  let description = '';
76
77
  try {
77
78
  const text = readFileSync(skillFile, 'utf8');
78
- const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
79
- if (match) {
80
- // 粗略读取 name/description(完整解析在移植时用 YAML 做)
81
- const nameMatch = /^name:\s*(.+)$/m.exec(match[1]);
82
- const descMatch = /^description:\s*(.+)$/m.exec(match[1]);
83
- if (nameMatch)
84
- skillName = nameMatch[1].trim().replace(/^["']|["']$/g, '');
85
- if (descMatch)
86
- description = descMatch[1].trim().replace(/^["']|["']$/g, '').slice(0, 160);
87
- }
79
+ const parsed = parseSkillFile(text);
80
+ if (typeof parsed.frontmatter.name === 'string')
81
+ skillName = parsed.frontmatter.name.trim() || entry.name;
82
+ if (typeof parsed.frontmatter.description === 'string')
83
+ description = parsed.frontmatter.description.trim().slice(0, 160);
88
84
  }
89
85
  catch { /* 读取失败保持目录名 */ }
90
86
  result.push({ skillDirName: entry.name, skillName, description, sourceDir: skillDir, skillFile });
package/lib/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 插件导出 apply(ctx, config):注册三个面向模型的工具(codex_list / codex_port /
5
5
  * codex_status),扫描 ~/.codex 的解包插件与缓存,把官方 Codex 插件技能批量移植为
6
- * DSH 技能。纯文件系统操作,零运行时依赖(仅 yaml 解析)。
6
+ * DSH 技能。纯文件系统操作,仅依赖 YAML 解析库。
7
7
  *
8
8
  * @module dsh-codex-port
9
9
  */
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 插件导出 apply(ctx, config):注册三个面向模型的工具(codex_list / codex_port /
5
5
  * codex_status),扫描 ~/.codex 的解包插件与缓存,把官方 Codex 插件技能批量移植为
6
- * DSH 技能。纯文件系统操作,零运行时依赖(仅 yaml 解析)。
6
+ * DSH 技能。纯文件系统操作,仅依赖 YAML 解析库。
7
7
  *
8
8
  * @module dsh-codex-port
9
9
  */
package/lib/port.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type CodexPluginInfo, type CodexSkillSource } from './discover.js';
3
3
  export interface PortResult {
4
4
  skill: string;
5
5
  plugin: string;
6
- status: 'ported' | 'skipped' | 'failed';
6
+ status: 'ported' | 'skipped' | 'failed' | 'planned';
7
7
  reason: string;
8
8
  files: number;
9
9
  }
@@ -11,4 +11,4 @@ export interface PortResult {
11
11
  * Port one skill without deleting its previous installation. Each rename is a
12
12
  * filesystem operation; the complete replacement is not atomic on Windows.
13
13
  */
14
- export declare function portSkill(skill: CodexSkillSource, plugin: CodexPluginInfo, targetDir: string, overwrite: boolean): PortResult;
14
+ export declare function portSkill(skill: CodexSkillSource, plugin: CodexPluginInfo, targetDir: string, overwrite: boolean, dryRun?: boolean): PortResult;
package/lib/port.js CHANGED
@@ -98,7 +98,7 @@ function copyTree(source, target, sourceRoot, root) {
98
98
  * Port one skill without deleting its previous installation. Each rename is a
99
99
  * filesystem operation; the complete replacement is not atomic on Windows.
100
100
  */
101
- export function portSkill(skill, plugin, targetDir, overwrite) {
101
+ export function portSkill(skill, plugin, targetDir, overwrite, dryRun = false) {
102
102
  const safeName = sanitizeSkillName(skill.skillName !== '' ? skill.skillName : skill.skillDirName);
103
103
  if (safeName === null || safeName.endsWith('.') || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(safeName)) {
104
104
  return { skill: skill.skillDirName, plugin: plugin.name, status: 'failed', reason: '技能名清洗后为空或不合法,已跳过(可能是纯中文或含危险字符的名称)', files: 0 };
@@ -134,6 +134,19 @@ export function portSkill(skill, plugin, targetDir, overwrite) {
134
134
  if (source === root.path || isWithin(source, root.path) || source === target || isWithin(target, source)) {
135
135
  throw new Error('技能源与安装目标重叠,拒绝递归复制或移动源目录');
136
136
  }
137
+ if (dryRun) {
138
+ const skillInfo = fs.lstatSync(join(source, 'SKILL.md'));
139
+ if (!skillInfo.isFile() || skillInfo.isSymbolicLink())
140
+ throw new Error('SKILL.md 必须是普通文件,不能是符号链接。');
141
+ const converted = convertSkillFile(fs.readFileSync(join(source, 'SKILL.md'), 'utf8'), {
142
+ pluginName: plugin.name, homepage: plugin.homepage, license: plugin.license,
143
+ }, safeName);
144
+ const parsed = parseSkillFile(converted);
145
+ if (!parsed.hasFrontmatter || parsed.frontmatter.name !== safeName || !DSH_SKILL_NAME.test(safeName)
146
+ || typeof parsed.frontmatter.description !== 'string' || parsed.frontmatter.description.length === 0)
147
+ throw new Error('转换后的技能名称或描述无效,不能安装。');
148
+ return { skill: safeName, plugin: plugin.name, status: 'planned', reason: existing ? '将备份并替换原技能' : '将安装新技能', files: 0 };
149
+ }
137
150
  fs.mkdirSync(root.path, { recursive: true });
138
151
  root.check(target);
139
152
  transaction = fs.mkdtempSync(root.check(join(root.path, '.dsh-port-' + safeName + '-')));
package/lib/tools.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module dsh-codex-port/tools
5
5
  */
6
- import { existsSync, mkdirSync } from 'node:fs';
6
+ import { existsSync } from 'node:fs';
7
7
  import { resolve } from 'node:path';
8
8
  import { assertCodexHome } from './config.js';
9
9
  import { discoverPlugins } from './discover.js';
@@ -165,12 +165,15 @@ export function buildCodexPortTools(config) {
165
165
  skills: { type: 'array', items: { type: 'string' }, description: '只移植这些技能(技能名,不区分大小写,可选)。' },
166
166
  targetDir: { type: 'string', description: '目标技能目录(可选,默认 <DSH_HOME>/skills)。' },
167
167
  overwrite: { type: 'boolean', description: '是否覆盖同名技能(默认 false=跳过)。' },
168
+ dryRun: { type: 'boolean', description: '为 true 时,只验证并预览安装/替换/跳过清单,不创建目录或修改文件。' },
168
169
  }),
169
170
  output: {
170
171
  schema: portSchema,
171
172
  render: (_args, value) => {
172
173
  const rec = asRecord(value);
173
174
  const counts = asRecord(rec.counts);
175
+ if (rec.dryRun === true)
176
+ return [{ type: 'text', text: '移植预览:计划 ' + rec.plannedCount + ' 个,跳过 ' + counts.skipped + ' 个,失败 ' + counts.failed + ' 个。未修改文件。\n' + JSON.stringify(rec.planned, null, 2) }];
174
177
  const lines = ['移植完成:新移植 ' + counts.ported + ' 个,跳过 ' + counts.skipped + ' 个,失败 ' + counts.failed + ' 个。目标目录:' + rec.targetDir];
175
178
  const ported = Array.isArray(rec.ported) ? rec.ported : [];
176
179
  for (const item of ported.slice(0, 20)) {
@@ -180,15 +183,18 @@ export function buildCodexPortTools(config) {
180
183
  return [{ type: 'text', text: lines.join('\n') }];
181
184
  },
182
185
  },
183
- async execute(rawArgs) {
186
+ async execute(rawArgs, exec) {
187
+ const signal = exec?.signal;
188
+ signal?.throwIfAborted();
184
189
  const args = asRecord(rawArgs);
185
190
  const plugins = getPlugins();
186
191
  const pluginFilter = stringArray(args, 'plugins').map((name) => name.toLowerCase());
187
192
  const skillFilter = stringArray(args, 'skills').map((name) => name.toLowerCase());
188
193
  const targetRaw = optionalString(args, 'targetDir');
189
- const targetDir = targetRaw !== undefined ? resolve(targetRaw) : config.targetDir;
194
+ const cwd = exec?.agent?.session?.header?.cwd || process.cwd();
195
+ const targetDir = resolve(cwd, targetRaw ?? config.targetDir);
190
196
  const overwrite = args.overwrite === true ? true : config.overwrite;
191
- mkdirSync(targetDir, { recursive: true });
197
+ const dryRun = args.dryRun === true;
192
198
  const selected = [];
193
199
  for (const plugin of plugins) {
194
200
  if (pluginFilter.length > 0 && !pluginFilter.includes(plugin.name.toLowerCase()))
@@ -202,24 +208,29 @@ export function buildCodexPortTools(config) {
202
208
  const ported = [];
203
209
  const skipped = [];
204
210
  const failed = [];
211
+ const planned = [];
205
212
  for (const group of selected) {
206
213
  for (const skill of group.skills) {
207
- const result = portSkill(skill, group.plugin, targetDir, overwrite);
214
+ signal?.throwIfAborted();
215
+ const result = portSkill(skill, group.plugin, targetDir, overwrite, dryRun);
208
216
  if (result.status === 'ported')
209
217
  ported.push(result);
210
218
  else if (result.status === 'skipped')
211
219
  skipped.push(result);
220
+ else if (result.status === 'planned')
221
+ planned.push(result);
212
222
  else
213
223
  failed.push(result);
214
224
  }
215
225
  }
216
226
  return {
217
227
  targetDir,
218
- total: ported.length + skipped.length + failed.length,
228
+ total: ported.length + skipped.length + failed.length + planned.length,
219
229
  counts: { ported: ported.length, skipped: skipped.length, failed: failed.length },
220
230
  ported: ported.map((r) => ({ skill: r.skill, plugin: r.plugin, files: r.files })),
221
231
  skipped: skipped.map((r) => ({ skill: r.skill, plugin: r.plugin, reason: r.reason })),
222
232
  failed: failed.map((r) => ({ skill: r.skill, plugin: r.plugin, reason: r.reason })),
233
+ ...(dryRun ? { dryRun, plannedCount: planned.length, planned: planned.map(r => ({ skill: r.skill, plugin: r.plugin, reason: r.reason })) } : {}),
223
234
  };
224
235
  },
225
236
  };
@@ -234,7 +245,9 @@ export function buildCodexPortTools(config) {
234
245
  return [{ type: 'text', text: 'Codex 技能 ' + rec.skills + ' 个,已移植 ' + rec.installed + ' 个,未移植 ' + rec.missing + ' 个。目标目录:' + rec.targetDir }];
235
246
  },
236
247
  },
237
- async execute() {
248
+ async execute(_args, exec) {
249
+ const cwd = exec?.agent?.session?.header?.cwd || process.cwd();
250
+ const targetDir = resolve(cwd, config.targetDir);
238
251
  const plugins = getPlugins();
239
252
  const allSkills = [];
240
253
  for (const plugin of plugins) {
@@ -245,7 +258,7 @@ export function buildCodexPortTools(config) {
245
258
  let installed = 0;
246
259
  for (const skill of allSkills) {
247
260
  const safeName = sanitizeSkillName(skill.name);
248
- if (safeName !== null && existsSync(resolve(config.targetDir, safeName))) {
261
+ if (safeName !== null && existsSync(resolve(targetDir, safeName))) {
249
262
  installed += 1;
250
263
  }
251
264
  else {
@@ -254,7 +267,7 @@ export function buildCodexPortTools(config) {
254
267
  }
255
268
  return {
256
269
  codexHome: config.codexHome,
257
- targetDir: config.targetDir,
270
+ targetDir,
258
271
  plugins: plugins.length,
259
272
  skills: allSkills.length,
260
273
  installed,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-codex-port",
3
- "version": "0.2.3",
4
- "description": "DSH 技能移植插件:把 Codex 官方技能一键移植为 DSH 技能,frontmatter 自动转换、名称清洗、幂等跳过。",
3
+ "version": "0.3.0",
4
+ "description": "把本机 Codex 技能转换为 DSH 可使用的技能。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -17,7 +17,9 @@
17
17
  "lib",
18
18
  "cordis.patch.yml",
19
19
  "README.md",
20
- "README.en.md"
20
+ "README.en.md",
21
+ "docs",
22
+ "CHANGELOG.md"
21
23
  ],
22
24
  "scripts": {
23
25
  "build": "tsc -p tsconfig.json",