dsh-codex-port 0.2.4 → 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 +21 -0
- package/README.en.md +23 -71
- package/README.md +22 -89
- package/docs/CHANGELOG.en.md +15 -0
- package/docs/USAGE.en.md +79 -0
- package/docs/USAGE.md +94 -0
- package/docs/VALIDATION.md +9 -0
- package/lib/discover.js +6 -10
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/port.d.ts +2 -2
- package/lib/port.js +14 -1
- package/lib/tools.js +22 -9
- package/package.json +5 -3
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
|
-
   
|
|
4
|
-
|
|
5
1
|
# dsh-codex-port
|
|
6
2
|
|
|
7
|
-
|
|
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.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.
|
|
12
|
-
|
|
13
|
-
[](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
|
-
|
|
5
|
+

|
|
18
6
|
|
|
19
|
-
|
|
7
|
+
Convert locally installed Codex skills into DSH-compatible skills.
|
|
20
8
|
|
|
21
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-codex-port) [](https://www.npmjs.com/package/dsh-codex-port)
|
|
22
10
|
|
|
23
|
-
##
|
|
11
|
+
## What it does
|
|
24
12
|
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
17
|
+
## Install
|
|
30
18
|
|
|
31
|
-
|
|
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
|
|
22
|
+
dsh plugin --profile desktop add dsh-codex-port
|
|
35
23
|
```
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
For the web version, replace `desktop` with `web`. Restart DSH after installation.
|
|
38
26
|
|
|
27
|
+
## Start using it
|
|
39
28
|
|
|
40
|
-
|
|
29
|
+
Ask the assistant to list available local skills, then import the ones you select.
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
## Requirements and configuration
|
|
43
32
|
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
37
|
+
## Documentation
|
|
86
38
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
Codex 目录按显式 codexHome、CODEX_HOME、默认 ~/.codex 的顺序解析,修复自定义或隔离目录无法发现插件技能的问题。
|
|
8
|
-
|
|
9
|
-
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。53 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 4 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
|
|
10
|
-
|
|
11
|
-
> **Codex 全家桶,一条命令进 DSH**:实测 186 插件、583 技能、移植 577 个 0 失败。
|
|
12
|
-
|
|
13
|
-
   
|
|
14
|
-
|
|
15
|
-
[](https://awesome-dsh-plugin.com)
|
|
3
|
+
[English](README.en.md)
|
|
16
4
|
|
|
5
|
+

|
|
17
6
|
|
|
18
|
-
|
|
7
|
+
把本机 Codex 技能转换为 DSH 可使用的技能。
|
|
19
8
|
|
|
20
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-codex-port) [](https://www.npmjs.com/package/dsh-codex-port)
|
|
21
10
|
|
|
22
|
-
##
|
|
11
|
+
## 功能
|
|
23
12
|
|
|
24
|
-
|
|
13
|
+
- 发现本机 Codex 技能,支持自定义 CODEX_HOME。
|
|
14
|
+
- 转换 frontmatter,清洗技能名称。
|
|
15
|
+
- 支持选择性移植、重复跳过和迁移报告。
|
|
25
16
|
|
|
26
17
|
## 安装
|
|
27
18
|
|
|
28
|
-
|
|
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
|
|
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
|
-
|
|
25
|
+
网页版把命令中的 `desktop` 改为 `web`。安装后重启 DSH。
|
|
76
26
|
|
|
77
|
-
##
|
|
27
|
+
## 开始使用
|
|
78
28
|
|
|
79
|
-
|
|
29
|
+
先让助手列出可移植技能,再说:“把这些选中的技能移植到 DSH。”
|
|
80
30
|
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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.
|
package/docs/USAGE.en.md
ADDED
|
@@ -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
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
package/lib/index.js
CHANGED
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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.
|
|
4
|
-
"description": "
|
|
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",
|