draftgo-cli 1.0.4 → 1.0.5

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
@@ -85,7 +85,7 @@ draftgo connect [target...]
85
85
 
86
86
  `draftgo status` 会通过只读 MCP 诊断显示服务连接健康状态、服务版本和 Registry revision;`--output json` 适合读取 `connection.health` 与 `connection.registry_revision`。
87
87
 
88
- `draftgo update` 和 `draftgo update all` 只刷新项目中已经安装的 DraftGo Skill,不会升级全局 CLI,也不会因为检测到 `.cursor`、`AGENTS.md` 等工具痕迹而新增配置。`draftgo init` 的自动识别只看宿主自有目录或文件(例如 `.cursor`、`.codex`),不把 `AGENTS.md`、`CLAUDE.md`、`GEMINI.md` 这类可共享说明文件当成安装目标。显式指定 target 时(例如 `draftgo update cursor` 或 `draftgo init cursor`),如果该 Skill 尚未安装,会按你的明确请求直接安装;首次接入请使用 `draftgo init <target>`。
88
+ `draftgo update` 和 `draftgo update all` 只刷新项目中已经安装的 DraftGo Skill,不会升级全局 CLI,也不会因为检测到 `.cursor`、`AGENTS.md` 等工具痕迹而新增配置。`draftgo init` 的自动识别只看宿主自有目录或文件(例如 `.cursor`、`.codex`、`.zcode`),不把 `AGENTS.md`、`CLAUDE.md`、`GEMINI.md` 这类可共享说明文件当成安装目标。显式指定 target 时(例如 `draftgo update cursor` 或 `draftgo init cursor`),如果该 Skill 尚未安装,会按你的明确请求直接安装;首次接入请使用 `draftgo init <target>`。
89
89
 
90
90
  ### 发现与正文
91
91
 
@@ -105,7 +105,7 @@ draftgo conflict resolve <type> <id>
105
105
 
106
106
  `checkout` 下载正文和经过 hash 校验的 base。`commit` 先并发完成所有目标的本地与远端 preflight,全部通过后再并发上传;任何 409/412 都保留 base/local/remote 冲突材料,不 force、不覆盖、不自动合并。
107
107
 
108
- `--route`、`--title` 为精确匹配,同时给出时取交集。CLI 可用资源搜索缩小候选,但最终在本地做精确判定;服务端 `resource_search` 只接受通用 `query`,不能传 route/title。`map --summary` 不输出 project overview、资源列表或 hash。`diff --stat` 输出文件与增删行数,`--summary` 输出资源、版本和变更概要;只有不带这两个参数的 diff 才展开正文。
108
+ `--route`、`--title` 为精确匹配,同时给出时取交集。CLI 可用资源搜索缩小候选,但最终在本地做精确判定。生产 DraftGo MCP 没有 `resource_search` 时,`map --type pages --route` 会改走 `listPageByRoute`。Windows Git Bash 传入的 `/admin/...` 会被还原成页面路由,而不是 Git 安装目录下的绝对路径。`map --summary` 不输出 project overview、资源列表或 hash。`diff --stat` 输出文件与增删行数,`--summary` 输出资源、版本和变更概要;只有不带这两个参数的 diff 才展开正文。
109
109
 
110
110
  ### Page 组件库
111
111
 
@@ -221,7 +221,7 @@ draftgo clean [--dry-run|--yes]
221
221
 
222
222
  Skill 只放稳定的领域知识、项目边界和操作规则;动态 operation schema 由 MCP 提供并缓存在项目运行时。Reference 不拆成更小文件,保留按任务路由、前端运行时、安全、checkout 和 MCP 契约所需的完整上下文,避免过度拆分损失效果。
223
223
 
224
- 支持的 Skill target 包括 Claude Code、Cursor、Windsurf、Antigravity、Kiro、GitHub Copilot、Codex CLI、Gemini CLI 和 Pi。Pi 的项目级 Skill 安装到 `.agents/skills/draftgo/`,可被 Pi 按 Agent Skills 标准自动发现。
224
+ 支持的 Skill target 包括 Claude Code、Cursor、Windsurf、Antigravity、Kiro、GitHub Copilot、Codex CLI、Gemini CLI、PiZCodeCodex 与 Pi 的项目级 Skill 都安装到 `.agents/skills/draftgo/`,符合 Agent Skills 仓库级发现路径;Codex 仍用 `.codex` 识别并写入 `.codex/config.toml`,旧的 `.codex/skills/draftgo/` 会在 update 时迁走。ZCode 安装到 `.zcode/skills/draftgo/`,MCP 写入 `.zcode/config.json` 的 `mcp.servers`。
225
225
 
226
226
  ## 开发与测试
227
227
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "draftgo-cli",
3
- "version": "1.0.4",
4
- "description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro, Pi).",
3
+ "version": "1.0.5",
4
+ "description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro, Pi, ZCode).",
5
5
  "bin": {
6
6
  "draftgo": "bin/draftgo.js"
7
7
  },
@@ -30,7 +30,8 @@
30
30
  "copilot",
31
31
  "gemini",
32
32
  "kiro",
33
- "pi"
33
+ "pi",
34
+ "zcode"
34
35
  ],
35
36
  "author": "draftgo <cabinai@163.com> (https://github.com/draftgo)",
36
37
  "license": "MIT",
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1.0",
3
3
  "id": "draftgo",
4
4
  "name": "DraftGo 开发助手",
5
- "version": "1.0.4",
5
+ "version": "1.0.5",
6
6
  "entry": "SKILL.md",
7
7
  "description": "以 Skill/reference 任务路由、MCP 实时发现、长正文 checkout/commit、统一验证和完成日志为边界的 DraftGo 工作流。",
8
8
  "license": "MIT",
@@ -86,7 +86,7 @@ draftgo mcp serve
86
86
  - `test` 建立独立新连接并验证关键工具;它不证明宿主持有的旧 session 仍有效。
87
87
  - `serve` 是 stdio bridge。远端 session 明确失效时重新 initialize 并重放当前请求一次;普通超时、服务错误和工具错误不重试。
88
88
 
89
- 支持项目级 setup:Codex、Claude Code、Cursor、Gemini CLI、Kiro、GitHub Copilot。Windsurf 和 Antigravity 没有可靠项目级 MCP 配置,CLI 应明确提示不支持。
89
+ 支持项目级 setup:Codex、Claude Code、Cursor、Gemini CLI、Kiro、GitHub Copilot、ZCode。Windsurf 和 Antigravity 没有可靠项目级 MCP 配置,CLI 应明确提示不支持。
90
90
 
91
91
  宿主配置只运行:
92
92
 
@@ -94,7 +94,7 @@ draftgo mcp serve
94
94
  {"command":"draftgo","args":["mcp","serve"]}
95
95
  ```
96
96
 
97
- Codex 使用 `[mcp_servers.draftgo]`;GitHub Copilot 使用 `servers` 且声明 `type="stdio"`;其他支持宿主使用各自 `mcpServers`。配置不得包含 API Key、token、Authorization、headers、远端 `/mcp` URL、`env` 或带凭据命令。bridge 从当前项目 `.draftgo/config.json` 读取 server/API Key。
97
+ Codex 使用 `.codex/config.toml` 的 `[mcp_servers.draftgo]`;GitHub Copilot 使用 `servers` 且声明 `type="stdio"`;ZCode 使用 `.zcode/config.json` 的 `mcp.servers.draftgo` 且声明 `type="stdio"`;其他支持宿主使用各自 `mcpServers`。配置不得包含 API Key、token、Authorization、headers、远端 `/mcp` URL、`env` 或带凭据命令。bridge 从当前项目 `.draftgo/config.json` 读取 server/API Key。
98
98
 
99
99
  `draftgo connect` 只保存并验证连接,不下载业务资源或创建本地镜像。MCP 不可用时运行 `draftgo mcp test` 收集证据。
100
100
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { all } = require('../targets');
3
+ const { all } = require('../targets');
4
4
  const { HOSTS } = require('../mcp/hosts');
5
5
  const { getPackageVersion } = require('../skill');
6
6
 
@@ -13,158 +13,158 @@ function help() {
13
13
 
14
14
  console.log(`draftgo v${getPackageVersion()} - DraftGo workbench CLI for AI coding agents
15
15
 
16
- DraftGo shell baseline: React + Vite. Database Pages default to native HTML,
17
- Tailwind CSS 4, and the DraftGo built-in component library (draftgo/*).
18
- Provider brands and shared icons are served from the local /assets/providers and
19
- /assets/icons directories; the Page runtime does not require third-party CDNs.
20
- System MCP handles live discovery and structured resources; complete page,
21
- navigation, and document bodies use checkout/commit outside MCP context.
22
- The installed Skill routes agents to task-specific References and MCP tools.
16
+ DraftGo shell baseline: React + Vite. Database Pages default to native HTML,
17
+ Tailwind CSS 4, and the DraftGo built-in component library (draftgo/*).
18
+ Provider brands and shared icons are served from the local /assets/providers and
19
+ /assets/icons directories; the Page runtime does not require third-party CDNs.
20
+ System MCP handles live discovery and structured resources; complete page,
21
+ navigation, and document bodies use checkout/commit outside MCP context.
22
+ The installed Skill routes agents to task-specific References and MCP tools.
23
23
 
24
24
  Usage:
25
- draftgo init [<target>...] Install the DraftGo Skill. Omit the target
26
- to detect host-owned directories such as
27
- .cursor or .codex, not shared files like
28
- AGENTS.md. Use "all" for every Skill target.
29
- draftgo update [<target>...] Refresh already installed Skills. Does not
30
- upgrade the global CLI. An explicit target
31
- may also install that target when missing.
25
+ draftgo init [<target>...] Install the DraftGo Skill. Omit the target
26
+ to detect host-owned directories such as
27
+ .cursor, .codex, or .zcode, not shared files like
28
+ AGENTS.md. Use "all" for every Skill target.
29
+ draftgo update [<target>...] Refresh already installed Skills. Does not
30
+ upgrade the global CLI. An explicit target
31
+ may also install that target when missing.
32
32
  draftgo uninstall <target|all> Remove Skill files. --purge also removes
33
33
  the entire .draftgo/ runtime directory.
34
- draftgo status Show connection health, Registry revision, targets, and Skill version.
34
+ draftgo status Show connection health, Registry revision, targets, and Skill version.
35
35
 
36
- draftgo connect [<target>...] Verify and save server/API Key, then configure
36
+ draftgo connect [<target>...] Verify and save server/API Key, then configure
37
37
  a detected or explicit MCP host. This never
38
38
  downloads DraftGo business resources.
39
39
  draftgo mcp setup [<target>...] Merge project-level stdio MCP configuration.
40
40
  draftgo mcp status [<target>...] Check host configuration and secret safety.
41
- draftgo mcp test Test project, resource, and API MCP calls.
42
- draftgo mcp serve Bridge local stdio to the remote /mcp endpoint.
43
-
44
- draftgo map [--type <type>] Locate bounded remote resource metadata and
45
- overlay matching local checkout state.
46
- Use --route/--title for exact selection.
47
- draftgo checkout <type> <id...> Download a long-form body + verified base,
48
- --force explicitly replaces local changes.
49
- draftgo check Validate all checked-out content.
50
- --remote also compares remote hash/version.
51
- draftgo verify [<type> <id...>] Run the default delivery verification.
52
- Browser options are explicit and optional.
53
- draftgo diff <type> <id> Show checkout base versus local body/files.
54
- --stat/--summary omit the full patch.
55
- draftgo commit <type> <id...> Validate and upload complete checked-out bodies/files.
56
- draftgo components search <query> Search the live component catalog.
57
- draftgo components show <library/component>
58
- Show HTML, props, slots, CSS variables and revision.
59
- draftgo components expand --page <id> --instance <data-dg-instance>
60
- Expand one component in a checked-out Page worktree.
61
- draftgo components libraries list|show|create|update|delete
62
- Manage component libraries.
63
- draftgo components list|create|copy|delete
64
- Manage component catalog entries.
65
- draftgo components checkout|diff|verify|commit|publish <library/component>
66
- Develop a component draft locally, then publish explicitly.
67
- draftgo components import <zip> [--dry-run]
68
- draftgo components export <library> [--file <zip>]
69
- Transfer standard DraftGo component archives.
70
- draftgo refresh <type> <id...> Safely refresh a clean checkout from the cloud.
71
- draftgo reconcile <type> <id...> Repair stale metadata only when local equals remote.
41
+ draftgo mcp test Test project, resource, and API MCP calls.
42
+ draftgo mcp serve Bridge local stdio to the remote /mcp endpoint.
43
+
44
+ draftgo map [--type <type>] Locate bounded remote resource metadata and
45
+ overlay matching local checkout state.
46
+ Use --route/--title for exact selection.
47
+ draftgo checkout <type> <id...> Download a long-form body + verified base,
48
+ --force explicitly replaces local changes.
49
+ draftgo check Validate all checked-out content.
50
+ --remote also compares remote hash/version.
51
+ draftgo verify [<type> <id...>] Run the default delivery verification.
52
+ Browser options are explicit and optional.
53
+ draftgo diff <type> <id> Show checkout base versus local body/files.
54
+ --stat/--summary omit the full patch.
55
+ draftgo commit <type> <id...> Validate and upload complete checked-out bodies/files.
56
+ draftgo components search <query> Search the live component catalog.
57
+ draftgo components show <library/component>
58
+ Show HTML, props, slots, CSS variables and revision.
59
+ draftgo components expand --page <id> --instance <data-dg-instance>
60
+ Expand one component in a checked-out Page worktree.
61
+ draftgo components libraries list|show|create|update|delete
62
+ Manage component libraries.
63
+ draftgo components list|create|copy|delete
64
+ Manage component catalog entries.
65
+ draftgo components checkout|diff|verify|commit|publish <library/component>
66
+ Develop a component draft locally, then publish explicitly.
67
+ draftgo components import <zip> [--dry-run]
68
+ draftgo components export <library> [--file <zip>]
69
+ Transfer standard DraftGo component archives.
70
+ draftgo refresh <type> <id...> Safely refresh a clean checkout from the cloud.
71
+ draftgo reconcile <type> <id...> Repair stale metadata only when local equals remote.
72
72
  draftgo conflicts List unresolved conflicts; --all includes resolved.
73
73
  draftgo conflict show <type> <id>
74
74
  Show preserved base/local/remote paths.
75
75
  draftgo conflict resolve <type> <id>
76
76
  Mark a merged worktree file ready against the
77
77
  preserved remote base; then check/diff/commit.
78
- draftgo clean [--dry-run|--yes] Plan or remove all tmp and registered artifacts.
79
- draftgo work start <item> Add an active item to .draftgo/worklog.md.
80
- draftgo work add <item> Add an item awaiting a decision.
81
- draftgo work wait <ref> Mark an item waiting; --note records the reason.
82
- draftgo work start-item <ref> Mark an item active; ref is number or date#number.
83
- draftgo work complete <ref> Mark an item complete; --note appends evidence.
84
- draftgo work show [<ref>] Show all history or one item with notes.
85
- draftgo work list Latest first; --status active|waiting|completed,
86
- --date YYYY-MM-DD, --limit 1-100 (20), --offset N.
78
+ draftgo clean [--dry-run|--yes] Plan or remove all tmp and registered artifacts.
79
+ draftgo work start <item> Add an active item to .draftgo/worklog.md.
80
+ draftgo work add <item> Add an item awaiting a decision.
81
+ draftgo work wait <ref> Mark an item waiting; --note records the reason.
82
+ draftgo work start-item <ref> Mark an item active; ref is number or date#number.
83
+ draftgo work complete <ref> Mark an item complete; --note appends evidence.
84
+ draftgo work show [<ref>] Show all history or one item with notes.
85
+ draftgo work list Latest first; --status active|waiting|completed,
86
+ --date YYYY-MM-DD, --limit 1-100 (20), --offset N.
87
87
 
88
- draftgo api <query> Search the live API contract through MCP.
89
- draftgo api search <query> Search the live operation registry explicitly.
90
- draftgo api describe <operation_id>
91
- Describe one live API operation through MCP.
92
- draftgo api call <operation_id> --input <json-file>
93
- Call one described operation with UTF-8 JSON.
94
- draftgo capabilities list|search|show|audit
95
- Discover and audit the current server's live operation catalog.
96
- draftgo group <action> Manage RBAC groups and group members.
97
- draftgo api-key status|create|update|delete|rotate
98
- Manage the current user's API key through MCP.
99
- draftgo role permissions|list|create|get|update|delete
100
- Manage structured permission templates.
101
- draftgo delete <operation_id> [id]
102
- Confirm and call a live delete operation.
88
+ draftgo api <query> Search the live API contract through MCP.
89
+ draftgo api search <query> Search the live operation registry explicitly.
90
+ draftgo api describe <operation_id>
91
+ Describe one live API operation through MCP.
92
+ draftgo api call <operation_id> --input <json-file>
93
+ Call one described operation with UTF-8 JSON.
94
+ draftgo capabilities list|search|show|audit
95
+ Discover and audit the current server's live operation catalog.
96
+ draftgo group <action> Manage RBAC groups and group members.
97
+ draftgo api-key status|create|update|delete|rotate
98
+ Manage the current user's API key through MCP.
99
+ draftgo role permissions|list|create|get|update|delete
100
+ Manage structured permission templates.
101
+ draftgo delete <operation_id> [id]
102
+ Confirm and call a live delete operation.
103
103
  draftgo deploy [<type> <id...>] Check, then diff or commit checked-out content.
104
104
  --delivery local needs no IDs; preview/deploy
105
105
  require an explicit type and IDs.
106
- draftgo auto-push [<type> <id...>]
107
- With config.auto_push=true, check and commit
108
- changed checkouts. Any conflict stops the run.
109
- draftgo local setup|start|stop|logs|status
110
- Manage the project-local Docker stack.
106
+ draftgo auto-push [<type> <id...>]
107
+ With config.auto_push=true, check and commit
108
+ changed checkouts. Any conflict stops the run.
109
+ draftgo local setup|start|stop|logs|status
110
+ Manage the project-local Docker stack.
111
111
  draftgo list-targets List Skill installation targets.
112
112
  draftgo -v | --version Print CLI version.
113
113
  draftgo -h | --help Show this help.
114
114
 
115
- Resource types:
116
- pages | nav/navigations | docs/articles
115
+ Resource types:
116
+ pages | nav/navigations | docs/articles
117
117
 
118
118
  Important flags:
119
119
  --project <dir> Operate on <dir> instead of the current directory.
120
120
  --target <name,...> Select one or more MCP setup/status targets.
121
- --server <url> (connect) DraftGo base URL.
122
- --mcp-url <url> (connect) Explicit endpoint; standard /mcp derives the
123
- base URL; custom paths also need --server.
124
- --api-key <key> (connect) DraftGo user API Key for non-interactive use.
125
- --timeout <ms> (connect/mcp test) Network timeout.
121
+ --server <url> (connect) DraftGo base URL.
122
+ --mcp-url <url> (connect) Explicit endpoint; standard /mcp derives the
123
+ base URL; custom paths also need --server.
124
+ --api-key <key> (connect) DraftGo user API Key for non-interactive use.
125
+ --timeout <ms> (connect/mcp test) Network timeout.
126
126
  --allow-offline (connect) Save only after explicitly accepting a
127
127
  failed MCP validation.
128
128
  --no-mcp-setup (connect) Do not write host MCP configuration.
129
129
  --force (init/checkout) Overwrite Skill or local changes.
130
130
  --purge (uninstall) Also remove the .draftgo/ directory.
131
- --skip-update-check (update) Compatibility flag; update never upgrades CLI.
132
- --connect (init) Continue into DraftGo server connection.
133
- --no-setup (init) Install the Skill without either setup flow.
134
- --output json Print machine-readable output where supported.
135
- --type <type> (map) pages | nav/navigations | docs/articles;
136
- (capabilities) alias for a Registry module filter.
137
- --route <path> (map) Select resources with this exact route.
138
- --title <title> (map) Select resources with this exact title.
139
- --limit <1-100> (map) Maximum resources returned per type/page;
140
- defaults to 20.
141
- --cursor <cursor> (map) Continue one typed map result page.
142
- --summary (map/diff) Print bounded resource or change summary.
143
- --stat (diff) Print per-file and total change statistics.
144
- --strict Treat check warnings as failures.
145
- --remote (check) Compare checkout hashes/versions with remote.
131
+ --skip-update-check (update) Compatibility flag; update never upgrades CLI.
132
+ --connect (init) Continue into DraftGo server connection.
133
+ --no-setup (init) Install the Skill without either setup flow.
134
+ --output json Print machine-readable output where supported.
135
+ --type <type> (map) pages | nav/navigations | docs/articles;
136
+ (capabilities) alias for a Registry module filter.
137
+ --route <path> (map) Select resources with this exact route.
138
+ --title <title> (map) Select resources with this exact title.
139
+ --limit <1-100> (map) Maximum resources returned per type/page;
140
+ defaults to 20.
141
+ --cursor <cursor> (map) Continue one typed map result page.
142
+ --summary (map/diff) Print bounded resource or change summary.
143
+ --stat (diff) Print per-file and total change statistics.
144
+ --strict Treat check warnings as failures.
145
+ --remote (check) Compare checkout hashes/versions with remote.
146
146
  --yes Skip supported confirmation prompts.
147
147
  --operation-id <id> (delete) Select an operation explicitly.
148
- --params <json> (api call/delete) Pass an API parameter object.
149
- --input <file> (api call/delete) Read a UTF-8 JSON object.
150
- --delivery <mode> (deploy) local | preview | deploy.
151
- --dry-run Preview supported cleanup or delivery operations.
152
- --ui <mode> (verify) always | never; default never.
153
- --remote (check/verify) Compare checkout hashes with remote.
154
- --viewport <mode> (verify) mobile | desktop | both.
155
- --frame <mode> (verify) auto | top | all | <iframe-selector>.
156
- --token <mode> (verify) auto | never; auto appends the configured
157
- API Key to same-origin URLs as the token query parameter.
158
- --screenshot <mode> (verify) always | never; always alone captures a screenshot.
159
- --browser <name> (verify) chromium | chrome | msedge.
160
- --browser-path <file> (verify) Explicit browser executable; environment
161
- fallback: DRAFTGO_BROWSER_PATH.
162
- --selector <css> (verify) Require a visible element in top or iframe DOM.
163
- --width/--height <px> (verify) Override the default 1440x900 desktop viewport.
148
+ --params <json> (api call/delete) Pass an API parameter object.
149
+ --input <file> (api call/delete) Read a UTF-8 JSON object.
150
+ --delivery <mode> (deploy) local | preview | deploy.
151
+ --dry-run Preview supported cleanup or delivery operations.
152
+ --ui <mode> (verify) always | never; default never.
153
+ --remote (check/verify) Compare checkout hashes with remote.
154
+ --viewport <mode> (verify) mobile | desktop | both.
155
+ --frame <mode> (verify) auto | top | all | <iframe-selector>.
156
+ --token <mode> (verify) auto | never; auto appends the configured
157
+ API Key to same-origin URLs as the token query parameter.
158
+ --screenshot <mode> (verify) always | never; always alone captures a screenshot.
159
+ --browser <name> (verify) chromium | chrome | msedge.
160
+ --browser-path <file> (verify) Explicit browser executable; environment
161
+ fallback: DRAFTGO_BROWSER_PATH.
162
+ --selector <css> (verify) Require a visible element in top or iframe DOM.
163
+ --width/--height <px> (verify) Override the default 1440x900 desktop viewport.
164
164
 
165
165
  Security:
166
- .draftgo/config.json stores the server and API Key and is gitignored. Host MCP
167
- files contain only "draftgo mcp serve"; they never contain the API Key.
166
+ .draftgo/config.json stores the server and API Key and is gitignored. Host MCP
167
+ files contain only "draftgo mcp serve"; they never contain the API Key.
168
168
 
169
169
  MCP targets (project config or status):
170
170
  ${mcpTargets}
@@ -174,15 +174,15 @@ ${targets}
174
174
 
175
175
  Examples:
176
176
  draftgo init codex
177
- draftgo connect codex --server https://draftgo.example --api-key <key>
178
- draftgo mcp test
179
- draftgo map --type pages --route /admin/channel-ops --summary --output json
177
+ draftgo connect codex --server https://draftgo.example --api-key <key>
178
+ draftgo mcp test
179
+ draftgo map --type pages --route /admin/channel-ops --summary --output json
180
180
  draftgo checkout pages 42
181
- draftgo verify pages 42
182
- draftgo diff pages 42 --stat
183
- draftgo commit pages 42
184
- draftgo work start "Build document management and role permissions"
185
- draftgo work complete 1 --note "Verification and delivery passed"
181
+ draftgo verify pages 42
182
+ draftgo diff pages 42 --stat
183
+ draftgo commit pages 42
184
+ draftgo work start "Build document management and role permissions"
185
+ draftgo work complete 1 --note "Verification and delivery passed"
186
186
  draftgo deploy docs 7 --delivery preview
187
187
  `);
188
188
  }
@@ -19,6 +19,9 @@ const LIST_OPERATIONS = Object.freeze({
19
19
  navigations: 'listNavigation',
20
20
  'docs/articles': 'listDocAdminArticle',
21
21
  });
22
+ const EXACT_ROUTE_OPERATIONS = Object.freeze({
23
+ pages: 'listPageByRoute',
24
+ });
22
25
 
23
26
  function itemsFrom(value) {
24
27
  if (Array.isArray(value)) return value;
@@ -29,6 +32,15 @@ function itemsFrom(value) {
29
32
  return [];
30
33
  }
31
34
 
35
+ function resourcesFrom(value) {
36
+ const items = itemsFrom(value);
37
+ if (items.length) return items;
38
+ if (value && typeof value === 'object' && (value.id != null || value.resource_id != null)) {
39
+ return [value];
40
+ }
41
+ return [];
42
+ }
43
+
32
44
  function nextCursor(value) {
33
45
  if (!value || typeof value !== 'object') return null;
34
46
  if (Object.prototype.hasOwnProperty.call(value, 'next_cursor')) return value.next_cursor;
@@ -70,20 +82,28 @@ function queryProperties(operation) {
70
82
  return query && query.properties && typeof query.properties === 'object' ? query.properties : {};
71
83
  }
72
84
 
85
+ function registryOperationID(type, flags) {
86
+ return flags.route != null && EXACT_ROUTE_OPERATIONS[type]
87
+ ? EXACT_ROUTE_OPERATIONS[type]
88
+ : LIST_OPERATIONS[type];
89
+ }
90
+
73
91
  function registryQuery(operation, operationID, flags) {
74
92
  const properties = queryProperties(operation);
75
93
  const query = {};
76
- const limit = positiveLimit(flags.limit);
77
- const cursor = flags.cursor == null ? '' : String(flags.cursor);
78
- if (Object.prototype.hasOwnProperty.call(properties, 'cursor')) {
79
- if (cursor) query.cursor = cursor;
80
- if (Object.prototype.hasOwnProperty.call(properties, 'limit')) query.limit = limit;
81
- } else if (Object.prototype.hasOwnProperty.call(properties, 'page')
82
- && Object.prototype.hasOwnProperty.call(properties, 'page_size')) {
83
- query.page = registryPageFromCursor(cursor, operationID);
84
- query.page_size = limit;
85
- } else if (cursor) {
86
- throw new Error(`${operationID} does not expose a paginated Registry contract.`);
94
+ if (operationID !== 'listPageByRoute') {
95
+ const limit = positiveLimit(flags.limit);
96
+ const cursor = flags.cursor == null ? '' : String(flags.cursor);
97
+ if (Object.prototype.hasOwnProperty.call(properties, 'cursor')) {
98
+ if (cursor) query.cursor = cursor;
99
+ if (Object.prototype.hasOwnProperty.call(properties, 'limit')) query.limit = limit;
100
+ } else if (Object.prototype.hasOwnProperty.call(properties, 'page')
101
+ && Object.prototype.hasOwnProperty.call(properties, 'page_size')) {
102
+ query.page = registryPageFromCursor(cursor, operationID);
103
+ query.page_size = limit;
104
+ } else if (cursor) {
105
+ throw new Error(`${operationID} does not expose a paginated Registry contract.`);
106
+ }
87
107
  }
88
108
 
89
109
  const search = flags.route != null ? normalizeRoute(flags.route)
@@ -93,6 +113,7 @@ function registryQuery(operation, operationID, flags) {
93
113
  ? 'route'
94
114
  : ['search', 'q', 'query'].find((candidate) => Object.prototype.hasOwnProperty.call(properties, candidate));
95
115
  if (key) query[key] = search;
116
+ else if (operationID === 'listPageByRoute') query.route = search;
96
117
  }
97
118
  return query;
98
119
  }
@@ -131,10 +152,22 @@ function requestedResourceTypes(flags = {}) {
131
152
  : [normalizeMapResourceType(flags.type)];
132
153
  }
133
154
 
155
+ function recoverCliRoute(value) {
156
+ // Git Bash converts POSIX paths like /admin/channel-ops into a Windows path
157
+ // under the Git install root. Recover the original route before matching.
158
+ const text = String(value == null ? '' : value).trim().replace(/\\/g, '/');
159
+ const converted = text.match(/^[A-Za-z]:\/(?:[^/]+\/)*Git(\/.*)$/i);
160
+ return converted ? converted[1] : text;
161
+ }
162
+
134
163
  function normalizeRoute(value) {
135
- // DraftGo stores page routes without surrounding slashes. Accept either the
136
- // stored form or the browser form users naturally paste into the CLI.
137
- return String(value == null ? '' : value).trim().replace(/^\/+|\/+$/g, '');
164
+ // DraftGo stores page routes with a leading slash and no trailing slash,
165
+ // except for `/`. Accept stored, browser, and Git-Bash-converted forms.
166
+ let route = recoverCliRoute(value);
167
+ if (!route) return '';
168
+ if (!route.startsWith('/')) route = `/${route}`;
169
+ if (route !== '/') route = route.replace(/\/+$/g, '');
170
+ return route;
138
171
  }
139
172
 
140
173
  function positiveLimit(value, fallback = 20) {
@@ -209,7 +242,7 @@ async function listRegistryResources(projectDir, config, session, resourceTypes,
209
242
  throw new Error('Map --cursor requires exactly one --type.');
210
243
  }
211
244
  const groups = await allWithAbort(resourceTypes.map((type) => async (options) => {
212
- const operationID = LIST_OPERATIONS[type];
245
+ const operationID = registryOperationID(type, flags);
213
246
  const revision = await registryRevision(session, operationID);
214
247
  const description = await descriptionForRevision(projectDir, config, session, operationID, revision);
215
248
  const operation = description.operation || {};
@@ -220,16 +253,25 @@ async function listRegistryResources(projectDir, config, session, resourceTypes,
220
253
  };
221
254
  if (Object.keys(query).length) invoke.query = query;
222
255
  const payload = await callStructured(session, TOOL_NAMES.apiCall, invoke, options);
256
+ const status = payload && (payload.status_code ?? payload.statusCode);
257
+ if (Number(status) === 404) {
258
+ return {
259
+ resources: [],
260
+ pagination: { next_cursor: null, has_more: false, total: 0 },
261
+ };
262
+ }
223
263
  const response = payload && payload.response !== undefined ? payload.response
224
264
  : payload && payload.data !== undefined ? payload.data : payload;
225
- const responseItems = itemsFrom(response);
265
+ const responseItems = resourcesFrom(response);
226
266
  const resources = responseItems
227
267
  .map((item) => ({ ...item, resource_type: type, resource_id: String(item.resource_id ?? item.id) }))
228
268
  .filter((item) => item.resource_id && item.resource_id !== 'undefined')
229
269
  .filter((item) => resourceMatches(item, flags));
230
270
  return {
231
271
  resources,
232
- pagination: registryPagination(response, operationID, query, responseItems.length),
272
+ pagination: operationID === 'listPageByRoute'
273
+ ? { next_cursor: null, has_more: false, total: resources.length }
274
+ : registryPagination(response, operationID, query, responseItems.length),
233
275
  };
234
276
  }));
235
277
  return {
@@ -378,6 +420,7 @@ async function mapCommand(projectDir, flags = {}) {
378
420
 
379
421
  module.exports = mapCommand;
380
422
  module.exports.itemsFrom = itemsFrom;
423
+ module.exports.resourcesFrom = resourcesFrom;
381
424
  module.exports.normalizeMapResourceType = normalizeMapResourceType;
382
425
  module.exports.requestedResourceTypes = requestedResourceTypes;
383
426
  module.exports.listRemoteResources = listRemoteResources;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const log = require('../logger');
4
- const { all, resolveTargets } = require('../targets');
4
+ const { all, resolveTargets } = require('../targets');
5
5
  const { removePath } = require('../fsx');
6
6
  const { dgDir } = require('../paths');
7
7
 
@@ -25,11 +25,18 @@ async function uninstall(projectDir, positional, flags) {
25
25
  }
26
26
  }
27
27
 
28
+ const resolvedNames = new Set(resolved.map((target) => target.name));
28
29
  log.step('移除入口文件');
29
30
  for (const t of resolved) {
30
31
  try {
31
- const removed = t.uninstall(projectDir);
32
- if (removed) log.ok(`${t.displayName} 入口已移除`);
32
+ const keepSharedSkill = all.some((other) =>
33
+ other.name !== t.name
34
+ && !resolvedNames.has(other.name)
35
+ && other.assetDir === t.assetDir
36
+ && other.mainFile === t.mainFile);
37
+ const removed = t.uninstall(projectDir, { keepSharedSkill });
38
+ if (keepSharedSkill) log.dim(`${t.displayName} 与其他宿主共用 Skill 目录,已保留`);
39
+ else if (removed) log.ok(`${t.displayName} 入口已移除`);
33
40
  else log.dim(`${t.displayName} 入口不存在,跳过`);
34
41
  } catch (e) {
35
42
  log.err(`${t.displayName} 卸载失败:${e.message}`);
@@ -38,12 +45,12 @@ async function uninstall(projectDir, positional, flags) {
38
45
 
39
46
  // Each AI-tool skill directory is self-contained, so per-target uninstall
40
47
  // already wiped its content above. Only --purge nukes runtime data under
41
- // .draftgo/ (config/worklog/lessons/cache etc.).
42
- if (flags.purge) {
43
- if (removePath(dgDir(projectDir))) {
44
- log.warn('已使用 --purge:.draftgo/(含 config/worklog/lessons 等运行时数据)已删除。');
45
- }
46
- }
48
+ // .draftgo/ (config/worklog/lessons/cache etc.).
49
+ if (flags.purge) {
50
+ if (removePath(dgDir(projectDir))) {
51
+ log.warn('已使用 --purge:.draftgo/(含 config/worklog/lessons 等运行时数据)已删除。');
52
+ }
53
+ }
47
54
 
48
55
  log.title('完成');
49
56
  return 0;
package/src/mcp/hosts.js CHANGED
@@ -22,6 +22,7 @@ const HOSTS = Object.freeze([
22
22
  { name: 'gemini', displayName: 'Gemini CLI', path: '.gemini/settings.json', format: 'jsonc', registry: 'mcpServers', signals: ['.gemini'] },
23
23
  { name: 'kiro', displayName: 'Kiro', path: '.kiro/settings/mcp.json', format: 'jsonc', registry: 'mcpServers', signals: ['.kiro'] },
24
24
  { name: 'copilot', aliases: ['github-copilot'], displayName: 'GitHub Copilot', path: '.vscode/mcp.json', format: 'jsonc', registry: 'servers', vscode: true, signals: ['.vscode', '.github/prompts', '.github/copilot-instructions.md'] },
25
+ { name: 'zcode', displayName: 'ZCode', path: '.zcode/config.json', format: 'jsonc', registry: 'mcp.servers', vscode: true, signals: ['.zcode'] },
25
26
  { name: 'windsurf', displayName: 'Windsurf', supported: false, reason: 'Windsurf has no supported project-level MCP configuration adapter.', signals: ['.windsurf'] },
26
27
  { name: 'antigravity', displayName: 'Antigravity', supported: false, reason: 'Antigravity has no supported project-level MCP configuration adapter.', signals: ['.agent'] },
27
28
  ]);
@@ -249,41 +250,85 @@ function insertObjectProperty(source, open, close, key, value, newline) {
249
250
  );
250
251
  }
251
252
 
253
+ function registryPath(registry) {
254
+ return String(registry || '').split('.').map((part) => part.trim()).filter(Boolean);
255
+ }
256
+
257
+ function nestedValue(value, keys) {
258
+ let current = value;
259
+ for (const key of keys) {
260
+ if (!current || typeof current !== 'object' || Array.isArray(current) || !Object.prototype.hasOwnProperty.call(current, key)) {
261
+ return undefined;
262
+ }
263
+ current = current[key];
264
+ }
265
+ return current;
266
+ }
267
+
268
+ function assertRegistryObjects(parsed, keys, file) {
269
+ let current = parsed;
270
+ for (let index = 0; index < keys.length; index += 1) {
271
+ const key = keys[index];
272
+ if (current[key] === undefined) return;
273
+ if (!current[key] || typeof current[key] !== 'object' || Array.isArray(current[key])) {
274
+ throw new Error(`Invalid ${file}: ${keys.slice(0, index + 1).join('.')} must be an object.`);
275
+ }
276
+ current = current[key];
277
+ }
278
+ }
279
+
280
+ function findNestedObject(source, open, close, keys) {
281
+ let currentOpen = open;
282
+ let currentClose = close;
283
+ let matched = 0;
284
+ for (const key of keys) {
285
+ const property = objectProperties(source, currentOpen, currentClose).find((item) => item.key === key);
286
+ if (!property) break;
287
+ currentOpen = property.valueStart;
288
+ currentClose = scanComposite(source, currentOpen) - 1;
289
+ matched += 1;
290
+ }
291
+ return { open: currentOpen, close: currentClose, matched };
292
+ }
293
+
294
+ function upsertDraftGoEntry(source, open, close, entry, newline) {
295
+ const serverProperties = objectProperties(source, open, close);
296
+ const draftgo = serverProperties.find((item) => item.key === 'draftgo');
297
+ if (draftgo) {
298
+ const indent = lineIndent(source, draftgo.propertyStart);
299
+ return replaceRange(
300
+ source,
301
+ draftgo.valueStart,
302
+ draftgo.valueEnd,
303
+ formatJsonValue(entry, indent, newline),
304
+ );
305
+ }
306
+ return insertObjectProperty(source, open, close, 'draftgo', entry, newline);
307
+ }
308
+
252
309
  function mergeJsoncConfig(source, registry, entry, file = 'MCP config') {
310
+ const keys = registryPath(registry);
311
+ if (!keys.length) throw new Error(`Invalid ${file}: MCP registry path is missing.`);
253
312
  const initial = source.trim() ? source : '{\n}\n';
254
313
  const parsed = parseJsonc(initial, file);
255
- if (parsed[registry] !== undefined
256
- && (!parsed[registry] || typeof parsed[registry] !== 'object' || Array.isArray(parsed[registry]))) {
257
- throw new Error(`Invalid ${file}: ${registry} must be an object.`);
258
- }
314
+ assertRegistryObjects(parsed, keys, file);
259
315
 
260
316
  const newline = initial.includes('\r\n') ? '\r\n' : '\n';
261
317
  const rootOffset = initial.charCodeAt(0) === 0xfeff ? 1 : 0;
262
318
  const rootOpen = skipTrivia(initial, rootOffset);
263
319
  const rootEnd = scanComposite(initial, rootOpen);
264
320
  const rootClose = rootEnd - 1;
265
- const rootProperties = objectProperties(initial, rootOpen, rootClose);
266
- const registryProperty = rootProperties.find((item) => item.key === registry);
321
+ const nested = findNestedObject(initial, rootOpen, rootClose, keys);
267
322
  let result;
268
323
 
269
- if (!registryProperty) {
270
- result = insertObjectProperty(initial, rootOpen, rootClose, registry, { draftgo: entry }, newline);
324
+ if (nested.matched === keys.length) {
325
+ result = upsertDraftGoEntry(initial, nested.open, nested.close, entry, newline);
271
326
  } else {
272
- const registryOpen = registryProperty.valueStart;
273
- const registryClose = scanComposite(initial, registryOpen) - 1;
274
- const serverProperties = objectProperties(initial, registryOpen, registryClose);
275
- const draftgo = serverProperties.find((item) => item.key === 'draftgo');
276
- if (draftgo) {
277
- const indent = lineIndent(initial, draftgo.propertyStart);
278
- result = replaceRange(
279
- initial,
280
- draftgo.valueStart,
281
- draftgo.valueEnd,
282
- formatJsonValue(entry, indent, newline),
283
- );
284
- } else {
285
- result = insertObjectProperty(initial, registryOpen, registryClose, 'draftgo', entry, newline);
327
+ let value = { draftgo: entry };
328
+ for (let index = keys.length - 1; index > nested.matched; index -= 1) {
329
+ value = { [keys[index]]: value };
286
330
  }
331
+ result = insertObjectProperty(initial, nested.open, nested.close, keys[nested.matched], value, newline);
287
332
  }
288
333
 
289
334
  parseJsonc(result, file);
@@ -482,7 +527,7 @@ function statusHost(projectDir, target) {
482
527
  if (host.format === 'toml') return { host, supported: true, path: file, ...codexStatus(source) };
483
528
  try {
484
529
  const value = parseJsonc(source, host.path);
485
- const registry = value[host.registry];
530
+ const registry = nestedValue(value, registryPath(host.registry));
486
531
  const entry = registry && typeof registry === 'object' && !Array.isArray(registry)
487
532
  ? registry.draftgo
488
533
  : undefined;
package/src/platforms.js CHANGED
@@ -79,10 +79,13 @@ const platforms = [
79
79
  {
80
80
  name: 'codex',
81
81
  displayName: 'Codex CLI',
82
- mainFile: '.codex/skills/draftgo/SKILL.md',
83
- assetDir: '.codex/skills/draftgo',
84
- skillDir: '.codex/skills/draftgo',
82
+ // Repo-level Codex skills now follow the Agent Skills path.
83
+ // MCP config still lives in .codex/config.toml, so detection stays on .codex.
84
+ mainFile: '.agents/skills/draftgo/SKILL.md',
85
+ assetDir: '.agents/skills/draftgo',
86
+ skillDir: '.agents/skills/draftgo',
85
87
  signals: ['.codex'],
88
+ legacyAssetDirs: ['.codex/skills/draftgo'],
86
89
  frontmatter: { name: SKILL_NAME, description: DESCRIPTION },
87
90
  },
88
91
  {
@@ -100,7 +103,16 @@ const platforms = [
100
103
  mainFile: '.agents/skills/draftgo/SKILL.md',
101
104
  assetDir: '.agents/skills/draftgo',
102
105
  skillDir: '.agents/skills/draftgo',
103
- signals: ['.agents/skills', '.pi'],
106
+ signals: ['.pi'],
107
+ frontmatter: { name: SKILL_NAME, description: DESCRIPTION },
108
+ },
109
+ {
110
+ name: 'zcode',
111
+ displayName: 'ZCode',
112
+ mainFile: '.zcode/skills/draftgo/SKILL.md',
113
+ assetDir: '.zcode/skills/draftgo',
114
+ skillDir: '.zcode/skills/draftgo',
115
+ signals: ['.zcode'],
104
116
  frontmatter: { name: SKILL_NAME, description: DESCRIPTION },
105
117
  },
106
118
  ];
package/src/skill.js CHANGED
@@ -193,6 +193,19 @@ function ensureRuntime(projectDir) {
193
193
  appendGitignoreLine(projectDir, '.draftgo/conflicts/');
194
194
  }
195
195
 
196
+ function legacySkillDirs(projectDir, platform) {
197
+ const destAsset = path.resolve(path.join(projectDir, platform.assetDir));
198
+ return (platform.legacyAssetDirs || [])
199
+ .map((relative) => path.join(projectDir, relative))
200
+ .filter((absolute) => path.resolve(absolute) !== destAsset && exists(absolute));
201
+ }
202
+
203
+ function skillAlreadyInstalled(projectDir, platform) {
204
+ const destAsset = path.join(projectDir, platform.assetDir);
205
+ const destMain = path.join(projectDir, platform.mainFile);
206
+ return exists(destAsset) || exists(destMain) || legacySkillDirs(projectDir, platform).length > 0;
207
+ }
208
+
196
209
  function installPlatform(projectDir, platform, opts = {}) {
197
210
  if (!exists(SKILL_SOURCE_DIR)) {
198
211
  throw new Error(
@@ -202,7 +215,7 @@ function installPlatform(projectDir, platform, opts = {}) {
202
215
  const destAsset = path.join(projectDir, platform.assetDir);
203
216
  const destMain = path.join(projectDir, platform.mainFile);
204
217
  const mainOutsideAsset = path.relative(destAsset, destMain).startsWith('..');
205
- if ((exists(destAsset) || exists(destMain)) && !opts.force) {
218
+ if (skillAlreadyInstalled(projectDir, platform) && !opts.force) {
206
219
  return { path: platform.mainFile, skipped: true };
207
220
  }
208
221
 
@@ -251,24 +264,31 @@ function installPlatform(projectDir, platform, opts = {}) {
251
264
 
252
265
  if (exists(backupAsset)) removePath(backupAsset);
253
266
  if (backupMain && exists(backupMain)) removePath(backupMain);
267
+ for (const legacyDir of legacySkillDirs(projectDir, platform)) removePath(legacyDir);
254
268
  if (!exists(destMain)) throw new Error(`安装后缺少入口文件:${platform.mainFile}`);
255
269
 
256
270
  return { path: platform.mainFile };
257
271
  }
258
272
 
259
- function uninstallPlatform(projectDir, platform) {
273
+ function uninstallPlatform(projectDir, platform, opts = {}) {
260
274
  const destAsset = path.join(projectDir, platform.assetDir);
261
275
  const destMain = path.join(projectDir, platform.mainFile);
262
276
  let removed = false;
263
- if (exists(destAsset)) { removePath(destAsset); removed = true; }
264
- if (path.relative(destAsset, destMain).startsWith('..') && exists(destMain)) {
265
- removePath(destMain); removed = true;
277
+ if (!opts.keepSharedSkill) {
278
+ if (exists(destAsset)) { removePath(destAsset); removed = true; }
279
+ if (path.relative(destAsset, destMain).startsWith('..') && exists(destMain)) {
280
+ removePath(destMain); removed = true;
281
+ }
282
+ }
283
+ for (const legacyDir of legacySkillDirs(projectDir, platform)) {
284
+ removePath(legacyDir);
285
+ removed = true;
266
286
  }
267
287
  return removed;
268
288
  }
269
289
 
270
290
  function statusPlatform(projectDir, platform) {
271
- return { installed: exists(path.join(projectDir, platform.mainFile)) };
291
+ return { installed: skillAlreadyInstalled(projectDir, platform) };
272
292
  }
273
293
 
274
294
  function installAll(projectDir, platforms, opts = {}) {
package/src/targets.js CHANGED
@@ -14,8 +14,8 @@ function makeInstaller(p) {
14
14
  install(projectDir, opts) {
15
15
  return skill.installPlatform(projectDir, p, opts || {});
16
16
  },
17
- uninstall(projectDir) {
18
- return skill.uninstallPlatform(projectDir, p);
17
+ uninstall(projectDir, opts) {
18
+ return skill.uninstallPlatform(projectDir, p, opts || {});
19
19
  },
20
20
  status(projectDir) {
21
21
  return skill.statusPlatform(projectDir, p);