draftgo-cli 3.0.35 → 3.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +220 -272
  2. package/package.json +6 -2
  3. package/resources/skill/SKILL.md +114 -55
  4. package/resources/skill/init/SKILL.md +29 -15
  5. package/resources/skill/manifest.json +5 -4
  6. package/resources/skill/push/SKILL.md +41 -29
  7. package/resources/skill/references/aihub.md +8 -5
  8. package/resources/skill/references/api-endpoints.md +5 -3
  9. package/resources/skill/references/architecture.md +1 -1
  10. package/resources/skill/references/checkout.md +116 -0
  11. package/resources/skill/references/custom-services.md +9 -10
  12. package/resources/skill/references/data.md +4 -2
  13. package/resources/skill/references/frontend.md +1 -1
  14. package/resources/skill/references/mcp.md +101 -0
  15. package/resources/skill/references/modules.md +8 -8
  16. package/resources/skill/references/parallel.md +6 -3
  17. package/resources/skill/references/runtime.md +7 -10
  18. package/resources/skill/scripts/README.md +8 -0
  19. package/resources/skill/story/SKILL.md +8 -8
  20. package/src/cli.js +5 -0
  21. package/src/commandRegistry.js +7 -1
  22. package/src/commands/api.js +24 -187
  23. package/src/commands/autoPush.js +48 -17
  24. package/src/commands/check.js +17 -47
  25. package/src/commands/checkout.js +18 -0
  26. package/src/commands/commit.js +21 -0
  27. package/src/commands/conflict.js +30 -0
  28. package/src/commands/conflicts.js +16 -0
  29. package/src/commands/connect.js +60 -48
  30. package/src/commands/delete.js +79 -64
  31. package/src/commands/deploy.js +18 -10
  32. package/src/commands/diff.js +23 -0
  33. package/src/commands/help.js +99 -75
  34. package/src/commands/init.js +4 -10
  35. package/src/commands/local.js +23 -6
  36. package/src/commands/map.js +89 -89
  37. package/src/commands/mcp.js +126 -0
  38. package/src/commands/sync.js +28 -43
  39. package/src/commands/verifyUi.js +3 -2
  40. package/src/localdev/index.js +37 -7
  41. package/src/localdev/mysqlClient.js +1 -1
  42. package/src/mcp/client.js +275 -0
  43. package/src/mcp/hosts.js +520 -0
  44. package/src/mcp/protocol.js +173 -0
  45. package/src/mcp/stdio.js +300 -0
  46. package/src/mcp/tools.js +47 -0
  47. package/src/platforms.js +3 -4
  48. package/src/projectConfig.js +91 -49
  49. package/src/projectMap.js +123 -460
  50. package/src/skill.js +6 -28
  51. package/src/worktree/backend.js +326 -0
  52. package/src/worktree/errors.js +28 -0
  53. package/src/worktree/index.js +461 -0
  54. package/src/worktree/manifest.js +75 -0
  55. package/src/worktree/streams.js +200 -0
  56. package/src/worktree/types.js +103 -0
  57. package/src/worktree/validate.js +37 -0
  58. package/resources/skill/pull/SKILL.md +0 -33
  59. package/resources/skill/references/api.json +0 -20248
  60. package/resources/skill/scripts/draftgo_delete.py +0 -149
  61. package/resources/skill/scripts/draftgo_init.py +0 -80
  62. package/resources/skill/scripts/draftgo_pull.py +0 -427
  63. package/resources/skill/scripts/draftgo_push.py +0 -1022
  64. package/src/python.js +0 -27
@@ -1,61 +1,46 @@
1
1
  'use strict';
2
2
 
3
- const path = require('path');
4
- const { spawnSync } = require('child_process');
5
3
  const log = require('../logger');
6
- const { exists } = require('../fsx');
7
- const { findPython } = require('../python');
8
- const { platforms } = require('../platforms');
4
+ const { canonicalResourceType } = require('../worktree/types');
9
5
 
10
- function findSyncScript(projectDir, command) {
11
- const scriptName = command === 'pull' ? 'draftgo_pull.py' : 'draftgo_push.py';
12
-
13
- for (const platform of platforms) {
14
- const candidate = path.join(projectDir, platform.assetDir, 'scripts', scriptName);
15
- if (exists(candidate)) return candidate;
6
+ async function sync(projectDir, command, positional, flags = {}) {
7
+ if (command === 'pull') {
8
+ log.err('`draftgo pull` no longer downloads DraftGo resources.');
9
+ log.dim(' Use MCP project/resource discovery. For complete pages, navigation, or docs, use `draftgo checkout <type> <id...>`.');
10
+ log.dim(' Existing legacy .draftgo indexes are left untouched and ignored.');
11
+ return 1;
16
12
  }
17
13
 
18
- const bundled = path.resolve(__dirname, '..', '..', 'resources', 'skill', 'scripts', scriptName);
19
- return exists(bundled) ? bundled : null;
20
- }
21
-
22
- function sync(projectDir, command, positional, flags = {}) {
23
- const py = findPython();
24
- if (!py) {
25
- log.err('未检测到 Python,无法运行 DraftGo 同步脚本。');
14
+ const [rawType, ...ids] = positional;
15
+ if (!rawType) {
16
+ log.err('The legacy push-all workflow has been removed.');
17
+ log.dim(' Use `draftgo commit <pages|nav|docs> <id...>` for checked-out long content.');
18
+ log.dim(' Use DraftGo MCP for structured resources.');
26
19
  return 1;
27
20
  }
28
21
 
29
- const script = findSyncScript(projectDir, command);
30
- if (!script) {
31
- log.err(`未找到 ${command} 同步脚本。`);
32
- log.dim(' 请先运行 `draftgo init` 安装 DraftGo skill,或重新安装 CLI。');
22
+ let resourceType;
23
+ try {
24
+ resourceType = canonicalResourceType(rawType);
25
+ } catch {
26
+ log.err(`Legacy push for ${rawType} is no longer supported.`);
27
+ log.dim(' Discover the live API with `draftgo api <query>` and use MCP api_call for structured resources.');
33
28
  return 1;
34
29
  }
35
-
36
- if (!exists(path.join(projectDir, '.draftgo', 'config.json'))) {
37
- log.err('未找到 .draftgo/config.json。');
38
- log.dim(' 请先运行 `draftgo connect`。');
30
+ if (!ids.length) {
31
+ log.err('Refusing legacy push-all behavior; specify checked-out resource ids explicitly.');
39
32
  return 1;
40
33
  }
41
34
 
42
- const args = positional.length ? positional : ['--all'];
43
- if (command === 'push' && flags['dry-run']) {
44
- args.push('--dry-run');
45
- }
46
- if (command === 'push' && flags['probe-routes']) {
47
- args.push('--probe-routes');
48
- }
49
- const r = spawnSync(py.bin, [script, ...args], {
50
- cwd: projectDir,
51
- stdio: 'inherit',
52
- shell: false,
53
- });
54
- if (r.error) {
55
- log.err(`${command} 启动失败:${r.error.message}`);
56
- return 1;
35
+ log.warn('`draftgo push` is deprecated; forwarding checked-out long content to `draftgo commit`.');
36
+ if (flags['dry-run']) {
37
+ for (const id of ids) {
38
+ const code = require('./diff')(projectDir, [resourceType, id], flags);
39
+ if (code !== 0) return code;
40
+ }
41
+ return 0;
57
42
  }
58
- return Number.isInteger(r.status) ? r.status : 1;
43
+ return require('./commit')(projectDir, [resourceType, ...ids], flags);
59
44
  }
60
45
 
61
46
  module.exports = sync;
@@ -14,8 +14,9 @@ function isUiFile(file) {
14
14
  if (!['.js', '.ts'].includes(ext)) return false;
15
15
  return /(^|\/)(frontend|web|ui|components|pages|views|client)(\/|$)/.test(normalized)
16
16
  || /(^|\/)(app|main|client)\.(js|ts)$/.test(normalized)
17
- || normalized.startsWith('.draftgo/pages/')
18
- || normalized.startsWith('.draftgo/navigations/');
17
+ || normalized.startsWith('.draftgo/worktree/pages/')
18
+ || normalized.startsWith('.draftgo/worktree/navigations/')
19
+ || normalized.startsWith('.draftgo/worktree/docs/');
19
20
  }
20
21
 
21
22
  function gitChangedFiles(projectDir) {
@@ -7,7 +7,7 @@ const { probePort, probeHttp, detectDocker } = require('./detect');
7
7
  const compose = require('./compose');
8
8
  const { ensureDatabase, testConnection, describeClient } = require('./mysqlClient');
9
9
  const { defaults, portOpen, probeRedis, probeMilvus, startService } = require('./services');
10
- const { writeProjectConfig, maybeRunInit } = require('../projectConfig');
10
+ const { writeProjectConfig } = require('../projectConfig');
11
11
  const { appendGitignoreLine } = require('../fsx');
12
12
 
13
13
  const DEFAULT_APP_PORT = 3000;
@@ -147,7 +147,35 @@ async function waitForApp(appPort) {
147
147
  return ok;
148
148
  }
149
149
 
150
- async function runWizard(projectDir, { yes = false } = {}) {
150
+ async function finishMcpSetup(projectDir, mcpCommands) {
151
+ const commands = mcpCommands || require('../commands/mcp');
152
+ const setupMcp = commands.setupMcp || commands.setup;
153
+ const testMcp = commands.testMcp || commands.test;
154
+ if (typeof setupMcp !== 'function' || typeof testMcp !== 'function') {
155
+ log.warn(' MCP setup helpers are unavailable. Run `draftgo mcp setup` after updating the CLI.');
156
+ return false;
157
+ }
158
+
159
+ let setupOk = false;
160
+ try {
161
+ setupOk = await setupMcp(projectDir, [], { yes: true }) === 0;
162
+ } catch {
163
+ log.warn(' MCP host setup failed. Retry with `draftgo mcp setup`.');
164
+ }
165
+
166
+ let testOk = false;
167
+ try {
168
+ testOk = await testMcp(projectDir, {}) === 0;
169
+ } catch {
170
+ log.warn(' MCP validation failed. Run `draftgo mcp test` for diagnostics.');
171
+ }
172
+ if (!testOk) {
173
+ log.dim(' The local stack and project config are ready; MCP may still be starting.');
174
+ }
175
+ return setupOk && testOk;
176
+ }
177
+
178
+ async function runWizard(projectDir, { yes = false, mcpCommands } = {}) {
151
179
  log.title('draftgo local setup');
152
180
  if (!yes && !await confirm('Continue with local DraftGo setup?', { default: true })) return 0;
153
181
 
@@ -169,14 +197,16 @@ async function runWizard(projectDir, { yes = false } = {}) {
169
197
  if (!composeUp(docker, out.dir) || !await waitForApp(appPort)) return 1;
170
198
 
171
199
  const url = `http://localhost:${appPort}`;
172
- const token = await askRequired('Paste a DraftGo system access token (SAT)', {
173
- validate: (value) => value.length < 10 ? 'Token appears too short.' : '',
174
- });
200
+ let token = '';
201
+ while (token.length < 10) {
202
+ token = String(await askPassword('Paste a DraftGo system access token (SAT)')).trim();
203
+ if (token.length < 10) log.err(' Token appears too short. Please try again.');
204
+ }
175
205
  const cfgPath = writeProjectConfig(projectDir, url, token);
176
- maybeRunInit(projectDir, url, token);
206
+ await finishMcpSetup(projectDir, mcpCommands);
177
207
  log.ok(`DraftGo is ready at ${url}`);
178
208
  log.dim(`Project config: ${cfgPath}`);
179
209
  return 0;
180
210
  }
181
211
 
182
- module.exports = { runWizard };
212
+ module.exports = { finishMcpSetup, runWizard };
@@ -60,7 +60,7 @@ function runSQL(client, conn, sql) {
60
60
  const args = [
61
61
  'run', '--rm', '-i',
62
62
  '--add-host', 'host.docker.internal:host-gateway',
63
- '-e', `MYSQL_PWD=${conn.password || ''}`,
63
+ '-e', 'MYSQL_PWD',
64
64
  'mysql:8.0',
65
65
  'mysql',
66
66
  '-h', dockerHost(conn.host),
@@ -0,0 +1,275 @@
1
+ 'use strict';
2
+
3
+ const pkg = require('../../package.json');
4
+ const {
5
+ DEFAULT_PROTOCOL_VERSION,
6
+ McpHttpError,
7
+ postJsonRpc,
8
+ redactText,
9
+ } = require('./protocol');
10
+
11
+ const SAFE_TEST_TOOLS = [
12
+ 'draftgo_project_overview',
13
+ 'draftgo_resource_list',
14
+ 'draftgo_api_search',
15
+ ];
16
+
17
+ const REQUIRED_DRAFTGO_TOOLS = [
18
+ 'draftgo_project_overview',
19
+ 'draftgo_resource_list',
20
+ 'draftgo_resource_search',
21
+ 'draftgo_resource_get_metadata',
22
+ 'draftgo_resource_read_fragment',
23
+ 'draftgo_api_search',
24
+ 'draftgo_api_describe',
25
+ 'draftgo_api_call',
26
+ ];
27
+
28
+ class McpRpcError extends Error {
29
+ constructor(message, { code = -32000, data, id = null } = {}) {
30
+ super(message);
31
+ this.name = 'McpRpcError';
32
+ this.code = code;
33
+ this.data = data;
34
+ this.id = id;
35
+ }
36
+ }
37
+
38
+ function redactValue(value, secrets = [], seen = new WeakMap()) {
39
+ if (typeof value === 'string') return redactText(value, secrets);
40
+ if (!value || typeof value !== 'object') return value;
41
+ if (seen.has(value)) return seen.get(value);
42
+ if (Array.isArray(value)) {
43
+ const result = [];
44
+ seen.set(value, result);
45
+ for (const item of value) result.push(redactValue(item, secrets, seen));
46
+ return result;
47
+ }
48
+ const result = {};
49
+ seen.set(value, result);
50
+ for (const [key, item] of Object.entries(value)) {
51
+ result[key] = redactValue(item, secrets, seen);
52
+ }
53
+ return result;
54
+ }
55
+
56
+ function isObject(value) {
57
+ return !!value && typeof value === 'object' && !Array.isArray(value);
58
+ }
59
+
60
+ function sameId(left, right) {
61
+ return left === right;
62
+ }
63
+
64
+ function findInitializeRequest(message) {
65
+ const messages = Array.isArray(message) ? message : [message];
66
+ return messages.find((item) => isObject(item) && item.method === 'initialize' && item.id != null);
67
+ }
68
+
69
+ function findResponse(messages, id) {
70
+ return messages.find((message) => isObject(message)
71
+ && Object.prototype.hasOwnProperty.call(message, 'id')
72
+ && sameId(message.id, id)
73
+ && !message.method);
74
+ }
75
+
76
+ function toolMatches(name, candidate) {
77
+ return name === candidate || name.endsWith(`.${candidate}`)
78
+ || name.endsWith(`/${candidate}`) || name.endsWith(`:${candidate}`);
79
+ }
80
+
81
+ class DraftGoMcpClient {
82
+ constructor(config, options = {}) {
83
+ if (!config || typeof config !== 'object') throw new TypeError('DraftGo MCP config is required.');
84
+ const token = String(config.token || config.sat || '').trim();
85
+ if (!token) throw new Error('DraftGo MCP config is missing SAT.');
86
+ if (!config.server && !config.mcp_url) throw new Error('DraftGo MCP config is missing server.');
87
+
88
+ this.config = { ...config, token };
89
+ this.secrets = [token];
90
+ this.sessionId = null;
91
+ this.protocolVersion = options.protocolVersion || DEFAULT_PROTOCOL_VERSION;
92
+ this.nextId = 1;
93
+ this.onMessage = typeof options.onMessage === 'function' ? options.onMessage : null;
94
+ }
95
+
96
+ async forward(message, options = {}) {
97
+ if (!isObject(message) && !Array.isArray(message)) {
98
+ throw new TypeError('MCP message must be a JSON object or batch.');
99
+ }
100
+
101
+ const initialize = findInitializeRequest(message);
102
+ const requestedVersion = initialize
103
+ && initialize.params
104
+ && initialize.params.protocolVersion;
105
+ const delivered = [];
106
+
107
+ try {
108
+ await postJsonRpc(this.config, message, {
109
+ signal: options.signal,
110
+ timeoutMs: options.timeoutMs,
111
+ sessionId: this.sessionId,
112
+ protocolVersion: requestedVersion || this.protocolVersion,
113
+ onSession: (sessionId) => { this.sessionId = sessionId; },
114
+ onMessage: (remoteMessage) => {
115
+ const safe = redactValue(remoteMessage, this.secrets);
116
+ delivered.push(safe);
117
+ if (this.onMessage) this.onMessage(safe);
118
+ if (typeof options.onMessage === 'function') options.onMessage(safe);
119
+ },
120
+ });
121
+ } catch (error) {
122
+ if (error && error.rpc) error.rpc = redactValue(error.rpc, this.secrets);
123
+ if (error && error.message) error.message = redactText(error.message, this.secrets);
124
+ throw error;
125
+ }
126
+
127
+ if (initialize) {
128
+ const response = findResponse(delivered, initialize.id);
129
+ const negotiated = response && response.result && response.result.protocolVersion;
130
+ if (negotiated) this.protocolVersion = negotiated;
131
+ }
132
+ return delivered;
133
+ }
134
+
135
+ async request(method, params, options = {}) {
136
+ const id = options.id == null ? this.nextId++ : options.id;
137
+ const message = { jsonrpc: '2.0', id, method };
138
+ if (params !== undefined) message.params = params;
139
+ const messages = await this.forward(message, options);
140
+ const response = findResponse(messages, id);
141
+ if (!response) {
142
+ throw new McpRpcError(`DraftGo MCP returned no response for ${method}.`, { id });
143
+ }
144
+ if (response.error) {
145
+ throw new McpRpcError(
146
+ redactText(response.error.message || `MCP error ${response.error.code}`, this.secrets),
147
+ {
148
+ code: response.error.code,
149
+ data: redactValue(response.error.data, this.secrets),
150
+ id,
151
+ },
152
+ );
153
+ }
154
+ return response.result;
155
+ }
156
+
157
+ async notify(method, params, options = {}) {
158
+ const message = { jsonrpc: '2.0', method };
159
+ if (params !== undefined) message.params = params;
160
+ await this.forward(message, options);
161
+ }
162
+
163
+ async initialize(options = {}) {
164
+ const result = await this.request('initialize', {
165
+ protocolVersion: options.protocolVersion || this.protocolVersion,
166
+ capabilities: options.capabilities || {},
167
+ clientInfo: options.clientInfo || {
168
+ name: 'draftgo-cli',
169
+ version: pkg.version,
170
+ },
171
+ }, options);
172
+ if (result && result.protocolVersion) this.protocolVersion = result.protocolVersion;
173
+ await this.notify('notifications/initialized', undefined, options);
174
+ return result;
175
+ }
176
+
177
+ async toolsList(cursor, options = {}) {
178
+ const params = cursor ? { cursor } : {};
179
+ return this.request('tools/list', params, options);
180
+ }
181
+
182
+ async listAllTools(options = {}) {
183
+ const tools = [];
184
+ const seenCursors = new Set();
185
+ let cursor;
186
+ const maxPages = Number(options.maxPages || 100);
187
+ for (let page = 0; page < maxPages; page += 1) {
188
+ const result = await this.toolsList(cursor, options);
189
+ if (result && Array.isArray(result.tools)) tools.push(...result.tools);
190
+ const next = result && result.nextCursor;
191
+ if (!next) return tools;
192
+ if (seenCursors.has(next)) throw new McpRpcError('DraftGo MCP repeated a tools/list cursor.');
193
+ seenCursors.add(next);
194
+ cursor = next;
195
+ }
196
+ throw new McpRpcError(`DraftGo MCP tools/list exceeded ${maxPages} pages.`);
197
+ }
198
+
199
+ async toolsCall(name, args = {}, options = {}) {
200
+ const result = await this.request('tools/call', { name, arguments: args }, options);
201
+ if (result && result.isError === true) {
202
+ const structured = result.structuredContent || result.structured_content || {};
203
+ const text = Array.isArray(result.content)
204
+ ? result.content.find((part) => part && part.type === 'text' && typeof part.text === 'string')
205
+ : null;
206
+ const message = structured.message || structured.error && structured.error.message
207
+ || text && text.text || `DraftGo tool failed: ${name}`;
208
+ throw new McpRpcError(redactText(String(message).slice(0, 4096), this.secrets), {
209
+ code: structured.code || structured.error && structured.error.code || -32000,
210
+ data: redactValue(structured, this.secrets),
211
+ });
212
+ }
213
+ return result;
214
+ }
215
+
216
+ async testConnection(options = {}) {
217
+ const initialized = await this.initialize(options);
218
+ const tools = await this.listAllTools(options);
219
+ const requiredTools = options.requiredTools || REQUIRED_DRAFTGO_TOOLS;
220
+ const missing = requiredTools.filter((expected) => !tools.some((tool) =>
221
+ tool && typeof tool.name === 'string' && toolMatches(tool.name, expected)));
222
+ if (missing.length) {
223
+ throw new McpRpcError(`DraftGo MCP is missing required tools: ${missing.join(', ')}.`, { code: -32601 });
224
+ }
225
+ const required = options.safeTools || SAFE_TEST_TOOLS;
226
+ const selected = tools.find((tool) => tool && typeof tool.name === 'string'
227
+ && required.some((candidate) => toolMatches(tool.name, candidate)));
228
+ if (!selected) {
229
+ throw new McpRpcError(
230
+ `DraftGo MCP exposes no safe diagnostic tool (${required.join(', ')}).`,
231
+ { code: -32601 },
232
+ );
233
+ }
234
+
235
+ const canonical = required.find((candidate) => toolMatches(selected.name, candidate));
236
+ const defaultArguments = canonical === 'draftgo_api_search' ? { query: 'project' } : {};
237
+ const toolResult = await this.toolsCall(
238
+ selected.name,
239
+ options.toolArguments || defaultArguments,
240
+ options,
241
+ );
242
+ return {
243
+ initialized,
244
+ protocolVersion: this.protocolVersion,
245
+ sessionId: this.sessionId,
246
+ tools,
247
+ testedTool: selected.name,
248
+ toolResult,
249
+ };
250
+ }
251
+ }
252
+
253
+ async function testConnection(config, options = {}) {
254
+ const client = new DraftGoMcpClient(config, options);
255
+ return client.testConnection(options);
256
+ }
257
+
258
+ async function callTool(config, name, args = {}, options = {}) {
259
+ const client = options.client || new DraftGoMcpClient(config, options);
260
+ if (!options.initialized) await client.initialize(options);
261
+ return client.toolsCall(name, args, options);
262
+ }
263
+
264
+ module.exports = {
265
+ DraftGoMcpClient,
266
+ McpClient: DraftGoMcpClient,
267
+ McpRpcError,
268
+ McpHttpError,
269
+ SAFE_TEST_TOOLS,
270
+ REQUIRED_DRAFTGO_TOOLS,
271
+ redactValue,
272
+ testConnection,
273
+ callTool,
274
+ toolMatches,
275
+ };