draftgo-cli 3.0.33 → 3.0.38

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 -269
  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 +13 -5
  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 +99 -23
  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 +37 -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 +250 -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,203 +1,40 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
3
  const log = require('../logger');
4
+ const { loadProjectConfig } = require('../projectConfig');
5
+ const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
6
6
 
7
- const API_FILE = path.resolve(__dirname, '..', '..', 'resources', 'skill', 'references', 'api.json');
8
- const HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
9
- const AUTH_HEADER_NAMES = new Set(['authorization', 'proxy-authorization', 'x-api-key']);
10
-
11
- function resolveRef(spec, ref) {
12
- if (!ref || !ref.startsWith('#/')) return null;
13
- return ref.slice(2).split('/').reduce((value, part) => value && value[part], spec);
14
- }
15
-
16
- function schemaType(schema, spec) {
17
- if (!schema || !Object.keys(schema).length) return 'unspecified';
18
- if (schema.$ref) {
19
- const target = resolveRef(spec, schema.$ref);
20
- return target && (target.title || schema.$ref.split('/').pop()) || schema.$ref;
21
- }
22
- if (schema.anyOf) return schema.anyOf.map((item) => schemaType(item, spec)).join(' | ');
23
- if (schema.oneOf) return schema.oneOf.map((item) => schemaType(item, spec)).join(' | ');
24
- if (schema.allOf) return schema.allOf.map((item) => schemaType(item, spec)).join(' & ');
25
- if (schema.type === 'array') return `${schemaType(schema.items, spec)}[]`;
26
- if (schema.type) return schema.format ? `${schema.type} (${schema.format})` : schema.type;
27
- if (schema.properties) return 'object';
28
- return 'unspecified';
29
- }
30
-
31
- function schemaNotes(schema) {
32
- const notes = [];
33
- if (schema.description) notes.push(schema.description);
34
- if (schema.default !== undefined) notes.push(`default: ${JSON.stringify(schema.default)}`);
35
- if (schema.enum) notes.push(`enum: ${schema.enum.map((item) => JSON.stringify(item)).join(', ')}`);
36
- return notes.length ? ` (${notes.join('; ')})` : '';
37
- }
38
-
39
- function printSchema(schema, spec, indent = ' ', seen = new Set()) {
40
- if (!schema || !Object.keys(schema).length) {
41
- console.log(`${indent}schema: unspecified`);
42
- return;
43
- }
44
-
45
- let resolved = schema;
46
- let refName = '';
47
- if (schema.$ref) {
48
- refName = schema.$ref.split('/').pop();
49
- if (seen.has(schema.$ref)) {
50
- console.log(`${indent}${refName} (recursive)`);
51
- return;
52
- }
53
- resolved = resolveRef(spec, schema.$ref) || schema;
54
- seen = new Set(seen).add(schema.$ref);
55
- }
56
-
57
- console.log(`${indent}schema: ${refName ? `${refName} (${schemaType(resolved, spec)})` : schemaType(resolved, spec)}${schemaNotes(resolved)}`);
58
- const variants = resolved.anyOf || resolved.oneOf || resolved.allOf;
59
- if (variants) {
60
- variants.forEach((variant, index) => {
61
- console.log(`${indent} variant ${index + 1}:`);
62
- printSchema(variant, spec, `${indent} `, seen);
63
- });
7
+ function printResult(title, result) {
8
+ log.title(title);
9
+ if (Array.isArray(result)) {
10
+ for (const item of result) log.plain(` ${JSON.stringify(item)}`);
64
11
  return;
65
12
  }
66
- const required = new Set(resolved.required || []);
67
- for (const [name, property] of Object.entries(resolved.properties || {})) {
68
- const fieldSchema = property.$ref ? resolveRef(spec, property.$ref) || property : property;
69
- const marker = required.has(name) ? ' required' : ' optional';
70
- console.log(`${indent} ${name}: ${schemaType(property, spec)}${marker}${schemaNotes(fieldSchema)}`);
71
- }
72
- if (resolved.additionalProperties === true) console.log(`${indent} additional properties: allowed`);
73
- if (resolved.items && !resolved.properties) printSchema(resolved.items, spec, `${indent} `, seen);
74
- }
75
-
76
- function authenticationFor(operation, spec) {
77
- const security = operation.security === undefined ? spec.security : operation.security;
78
- const schemes = (security || []).flatMap((requirement) => Object.keys(requirement).map((name) => {
79
- const definition = spec.components && spec.components.securitySchemes && spec.components.securitySchemes[name] || {};
80
- return { name, type: definition.type || 'unknown', scheme: definition.scheme, bearerFormat: definition.bearerFormat };
81
- }));
82
- const headers = (operation.parameters || []).filter((parameter) =>
83
- parameter.in === 'header' && AUTH_HEADER_NAMES.has(String(parameter.name).toLowerCase())
84
- );
85
- return { schemes, headers };
86
- }
87
-
88
- function printAuthentication(operation, spec) {
89
- const auth = authenticationFor(operation, spec);
90
- if (!auth.schemes.length && !auth.headers.length) {
91
- console.log(' Authentication: no requirement declared in the bundled OpenAPI.');
13
+ if (result && Array.isArray(result.items)) {
14
+ for (const item of result.items) log.plain(` ${JSON.stringify(item)}`);
92
15
  return;
93
16
  }
94
- console.log(' Authentication:');
95
- for (const scheme of auth.schemes) {
96
- const detail = [scheme.type, scheme.scheme, scheme.bearerFormat].filter(Boolean).join(', ');
97
- console.log(` security scheme ${scheme.name}${detail ? ` (${detail})` : ''}`);
98
- }
99
- for (const header of auth.headers) {
100
- console.log(` header ${header.name}: ${schemaType(header.schema, spec)}${header.required ? ' (required)' : ' (optional)'}${schemaNotes(header)}`);
101
- }
102
- }
103
-
104
- function printOperation(item, spec) {
105
- console.log(`\n${item.method} ${item.route}${item.summary ? ` ${item.summary}` : ''}`);
106
- if (item.operationId) console.log(` Operation: ${item.operationId}`);
107
- printAuthentication(item.operation, spec);
108
-
109
- const parameters = (item.operation.parameters || []).filter((parameter) =>
110
- !(parameter.in === 'header' && AUTH_HEADER_NAMES.has(String(parameter.name).toLowerCase()))
111
- );
112
- if (parameters.length) {
113
- console.log(' Parameters:');
114
- for (const parameter of parameters) {
115
- console.log(` ${parameter.in} ${parameter.name}: ${schemaType(parameter.schema, spec)}${parameter.required ? ' (required)' : ' (optional)'}${schemaNotes(parameter)}`);
116
- }
117
- }
118
-
119
- if (item.operation.requestBody) {
120
- console.log(` Request body: ${item.operation.requestBody.required ? 'required' : 'optional'}`);
121
- for (const [contentType, media] of Object.entries(item.operation.requestBody.content || {})) {
122
- console.log(` ${contentType}:`);
123
- printSchema(media.schema, spec, ' ');
124
- }
125
- }
126
-
127
- const responses = Object.entries(item.operation.responses || {});
128
- if (responses.length) {
129
- console.log(' Responses:');
130
- for (const [status, response] of responses) {
131
- console.log(` ${status}${response.description ? ` ${response.description}` : ''}`);
132
- for (const [contentType, media] of Object.entries(response.content || {})) {
133
- console.log(` ${contentType}:`);
134
- printSchema(media.schema, spec, ' ');
135
- }
136
- }
137
- }
17
+ console.log(JSON.stringify(result, null, 2));
138
18
  }
139
19
 
140
- function apiCommand(positional, flags = {}) {
141
- const keyword = String(positional.join(' ') || '').trim().toLowerCase();
142
- if (!keyword) {
143
- log.err('用法:draftgo api <keyword>');
144
- log.dim(' 示例:draftgo api pages batch');
20
+ async function apiCommand(projectDir, positional, flags = {}) {
21
+ const first = String(positional[0] || '').toLowerCase();
22
+ const describe = first === 'describe';
23
+ const queryParts = first === 'search' || describe ? positional.slice(1) : positional;
24
+ const query = queryParts.join(' ').trim();
25
+ if (!query) {
26
+ log.err('Usage: draftgo api <query> | draftgo api search <query> | draftgo api describe <operation_id>');
145
27
  return 1;
146
28
  }
147
29
 
148
- let spec;
149
- try {
150
- spec = JSON.parse(fs.readFileSync(API_FILE, 'utf8'));
151
- } catch (err) {
152
- log.err(`无法读取 OpenAPI:${err.message}`);
153
- return 1;
154
- }
155
-
156
- const words = keyword.split(/\s+/).filter(Boolean);
157
- const matches = [];
158
- for (const [route, methods] of Object.entries(spec.paths || {})) {
159
- for (const [method, operation] of Object.entries(methods || {})) {
160
- if (!HTTP_METHODS.has(method.toLowerCase())) continue;
161
- const haystack = [route, method, operation.summary, operation.operationId, ...(operation.tags || [])]
162
- .filter(Boolean).join(' ').toLowerCase();
163
- if (!words.every((word) => haystack.includes(word))) continue;
164
- matches.push({
165
- method: method.toUpperCase(),
166
- route,
167
- summary: operation.summary || '',
168
- operationId: operation.operationId || '',
169
- tags: operation.tags || [],
170
- operation,
171
- });
172
- }
173
- }
174
-
175
- if (flags.output === 'json') {
176
- console.log(JSON.stringify({
177
- source: {
178
- kind: 'DraftGo bundled base OpenAPI',
179
- openapi: spec.openapi || null,
180
- version: spec.info && spec.info.version || null,
181
- customServiceRoutes: {
182
- included: false,
183
- reason: 'Custom service routes are registered dynamically from local app.Route calls.',
184
- derive: '<METHOD> /api/x/<slug><route-path>',
185
- },
186
- },
187
- query: keyword,
188
- matches,
189
- }, null, 2));
190
- return matches.length ? 0 : 1;
191
- }
192
- if (!matches.length) {
193
- log.warn(`未找到包含“${keyword}”的 API。`);
194
- return 1;
195
- }
196
- log.title(`DraftGo base API — ${keyword}`);
197
- log.dim(`Source: bundled DraftGo base OpenAPI v${spec.info && spec.info.version || 'unknown'}; these are platform APIs, not project-defined APIs.`);
198
- log.dim('Custom service routes are not indexed here: derive them from local app.Route calls as <METHOD> /api/x/<slug><route-path>.');
199
- for (const item of matches.slice(0, 50)) printOperation(item, spec);
200
- if (matches.length > 50) log.dim(`\nOnly the first 50 of ${matches.length} matches are shown; use --output json for all contracts.`);
30
+ const config = loadProjectConfig(projectDir);
31
+ const expected = describe ? TOOL_NAMES.apiDescribe : TOOL_NAMES.apiSearch;
32
+ const session = await openToolSession(config, [expected]);
33
+ const result = await callStructured(session, expected, describe
34
+ ? { operation_id: query }
35
+ : { query });
36
+ if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
37
+ else printResult(describe ? `DraftGo API ${query}` : `DraftGo API search: ${query}`, result);
201
38
  return 0;
202
39
  }
203
40
 
@@ -1,41 +1,72 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
- const path = require('path');
5
4
  const log = require('../logger');
6
5
  const check = require('./check');
7
- const sync = require('./sync');
6
+ const { loadProjectConfig } = require('../projectConfig');
7
+ const { loadManifest, absolutePath } = require('../worktree/manifest');
8
+ const { hashFile } = require('../worktree/streams');
9
+ const { listConflicts, commitResources } = require('../worktree');
8
10
 
9
- function autoPush(projectDir, positional, flags = {}) {
10
- const configPath = path.join(projectDir, '.draftgo', 'config.json');
11
- if (!fs.existsSync(configPath)) {
12
- log.dim('自动推送未执行:未连接 DraftGo 服务器(缺少 .draftgo/config.json)。');
13
- return 0;
11
+ async function changedEntries(projectDir, manifest) {
12
+ const changed = [];
13
+ for (const entry of Object.values(manifest.entries)) {
14
+ const local = absolutePath(projectDir, entry.local_path);
15
+ if (!fs.existsSync(local)) continue;
16
+ if ((await hashFile(local)).hash !== entry.base_hash) changed.push(entry);
14
17
  }
18
+ return changed;
19
+ }
15
20
 
21
+ async function autoPush(projectDir, positional, flags = {}) {
16
22
  let config;
17
23
  try {
18
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
19
- } catch (err) {
20
- log.err(`自动推送未执行:config.json 解析失败:${err.message}`);
24
+ config = loadProjectConfig(projectDir);
25
+ } catch {
26
+ log.dim('auto-push skipped: this project is not connected to DraftGo.');
27
+ return 0;
28
+ }
29
+
30
+ if (config.auto_push !== true) {
31
+ log.dim('auto-push skipped: .draftgo/config.json does not set auto_push to true.');
32
+ return 0;
33
+ }
34
+ const conflicts = listConflicts(projectDir);
35
+ if (conflicts.length) {
36
+ log.err(`auto-push stopped: ${conflicts.length} unresolved DraftGo conflict(s).`);
21
37
  return 1;
22
38
  }
23
39
 
24
- log.step('auto-push: 先执行 check …');
25
40
  const checkCode = check(projectDir, { strict: flags.strict });
26
41
  if (checkCode !== 0) {
27
- log.err('自动推送终止:check 未通过,请修复后重试。');
42
+ log.err('auto-push stopped because local validation failed.');
28
43
  return checkCode;
29
44
  }
30
45
 
31
- if (config.auto_push !== true) {
32
- log.dim('自动推送已跳过:check 已通过,但 .draftgo/config.json 的 auto_push 不为 true。');
46
+ const groups = new Map();
47
+ if (positional.length) {
48
+ const [resourceType, ...ids] = positional;
49
+ if (!resourceType || !ids.length) {
50
+ log.err('Usage: draftgo auto-push [<pages|nav|docs> <id...>]');
51
+ return 1;
52
+ }
53
+ groups.set(resourceType, ids);
54
+ } else {
55
+ for (const entry of await changedEntries(projectDir, loadManifest(projectDir))) {
56
+ if (!groups.has(entry.resource_type)) groups.set(entry.resource_type, []);
57
+ groups.get(entry.resource_type).push(entry.resource_id);
58
+ }
59
+ }
60
+ if (!groups.size) {
61
+ log.dim('auto-push: no checked-out content changed.');
33
62
  return 0;
34
63
  }
35
64
 
36
- log.step('auto_push=true:开始 push …');
37
- const pushArgs = flags.batch ? ['--batch', ...positional] : positional;
38
- return sync(projectDir, 'push', pushArgs, flags);
65
+ for (const [resourceType, ids] of groups) {
66
+ await commitResources(projectDir, resourceType, ids, { config });
67
+ }
68
+ log.ok('auto-push committed all changed checked-out content.');
69
+ return 0;
39
70
  }
40
71
 
41
72
  module.exports = autoPush;
@@ -1,62 +1,32 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
4
+ const path = require('path');
3
5
  const log = require('../logger');
4
6
  const { analyzeProject } = require('../projectMap');
5
7
 
8
+ function legacyCacheDetected(projectDir) {
9
+ return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
10
+ .some((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
11
+ }
12
+
6
13
  function check(projectDir, flags = {}) {
7
14
  const result = analyzeProject(projectDir);
8
-
9
15
  if (flags.output === 'json') {
10
- console.log(JSON.stringify(result, null, 2));
16
+ console.log(JSON.stringify({ ...result, legacy_cache_ignored: legacyCacheDetected(projectDir) }, null, 2));
11
17
  return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
12
18
  }
13
19
 
14
20
  log.title('draftgo check');
15
- log.info(`项目目录:${projectDir}`);
16
-
17
- const m = result.map;
18
- console.log('');
19
- const overview = [
20
- ['pages', m.pages.length],
21
- ['nav', m.navigations.length],
22
- ['db', m.db_meta.length],
23
- ['scripts', m.custom_scripts.length],
24
- ['aihub', m.aihub.length],
25
- ['docs', m.docs.length],
26
- ];
27
- console.log(' ' + overview.map(([k, v]) => `${log.c.cyan(k)} ${v}`).join(' '));
28
- const pageAreas = [
29
- ['home', m.pageGroups.home.length],
30
- ['admin', m.pageGroups.admin.length],
31
- ['business', m.pageGroups.business.length],
32
- ['system', m.pageGroups.system.length],
33
- ];
34
- console.log(' ' + pageAreas.map(([k, v]) => `${log.c.cyan(k)} ${v}`).join(' '));
35
-
36
- if (result.errors.length) {
37
- console.log('');
38
- result.errors.forEach((msg) => console.log(` ${log.c.red('x')} ${msg}`));
39
- }
40
-
41
- if (result.warnings.length) {
42
- console.log('');
43
- result.warnings.forEach((msg) => {
44
- const detail = (result.warningDetails || []).find((item) => item.message === msg);
45
- const prefix = detail ? `[${detail.code}/${detail.confidence}] ` : '';
46
- console.log(` ${log.c.yellow('!')} ${prefix}${msg}`);
47
- });
48
- }
49
-
50
- console.log('');
51
- if (result.errors.length) {
52
- log.err('检查未通过:请先修复错误。');
53
- return 1;
54
- }
55
- if (flags.strict && result.warnings.length) {
56
- log.err('strict 模式:存在提醒项,检查未通过。');
57
- return 1;
58
- }
59
- log.ok(result.warnings.length ? '检查完成(有提醒项)。' : '检查通过。');
21
+ log.info(`Checked-out pages: ${result.map.pages.length}`);
22
+ log.info(`Checked-out navigations: ${result.map.navigations.length}`);
23
+ log.info(`Checked-out docs: ${result.map.docs.length}`);
24
+ for (const message of result.errors) log.err(message);
25
+ for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
26
+ if (legacyCacheDetected(projectDir)) log.dim(' Legacy .draftgo index caches were detected and ignored.');
27
+
28
+ if (result.errors.length || (flags.strict && result.warnings.length)) return 1;
29
+ log.ok(result.warnings.length ? 'Local checkout validation completed with warnings.' : 'Local checkout validation passed.');
60
30
  return 0;
61
31
  }
62
32
 
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { checkoutResources } = require('../worktree');
5
+
6
+ async function checkout(projectDir, positional, flags = {}) {
7
+ const [resourceType, ...ids] = positional;
8
+ if (!resourceType || !ids.length) {
9
+ log.err('Usage: draftgo checkout <pages|nav|docs> <id...>');
10
+ return 1;
11
+ }
12
+ const results = await checkoutResources(projectDir, resourceType, ids, { force: Boolean(flags.force) });
13
+ if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
14
+ else for (const entry of results) log.ok(`Checked out ${entry.resource_type} ${entry.resource_id} -> ${entry.local_path}`);
15
+ return 0;
16
+ }
17
+
18
+ module.exports = checkout;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { commitResources } = require('../worktree');
5
+
6
+ async function commit(projectDir, positional, flags = {}) {
7
+ const [resourceType, ...ids] = positional;
8
+ if (!resourceType || !ids.length) {
9
+ log.err('Usage: draftgo commit <pages|nav|docs> <id...>');
10
+ return 1;
11
+ }
12
+ const results = await commitResources(projectDir, resourceType, ids);
13
+ if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
14
+ else for (const result of results) {
15
+ if (result.status === 'unchanged') log.dim(`${result.resource_type} ${result.resource_id}: unchanged`);
16
+ else log.ok(`Committed ${result.resource_type} ${result.resource_id}`);
17
+ }
18
+ return 0;
19
+ }
20
+
21
+ module.exports = commit;
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { showConflict, resolveConflict } = require('../worktree');
5
+
6
+ function printRecord(record) {
7
+ log.info(`${record.resource_type} ${record.resource_id}: ${record.status}`);
8
+ log.plain(` base: ${record.base_path}`);
9
+ log.plain(` local: ${record.local_path}`);
10
+ log.plain(` remote: ${record.remote_path}`);
11
+ log.plain(` worktree local: ${record.worktree_local_path}`);
12
+ log.plain(` manifest: ${record.manifest_path}`);
13
+ }
14
+
15
+ async function conflict(projectDir, positional, flags = {}) {
16
+ const [action, resourceType, resourceId] = positional;
17
+ if (!['show', 'resolve'].includes(action) || !resourceType || !resourceId) {
18
+ log.err('Usage: draftgo conflict <show|resolve> <pages|nav|docs> <id>');
19
+ return 1;
20
+ }
21
+ const record = action === 'show'
22
+ ? showConflict(projectDir, resourceType, resourceId)
23
+ : await resolveConflict(projectDir, resourceType, resourceId);
24
+ if (flags.output === 'json') console.log(JSON.stringify(record, null, 2));
25
+ else printRecord(record);
26
+ if (action === 'resolve') log.info('The remote revision is now the base; run check, diff, and commit the merged worktree file.');
27
+ return 0;
28
+ }
29
+
30
+ module.exports = conflict;
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { listConflicts } = require('../worktree');
5
+
6
+ function conflicts(projectDir, _positional, flags = {}) {
7
+ const records = listConflicts(projectDir, { all: Boolean(flags.all) });
8
+ if (flags.output === 'json') console.log(JSON.stringify(records, null, 2));
9
+ else if (!records.length) log.ok('No unresolved DraftGo conflicts.');
10
+ else for (const record of records) {
11
+ log.warn(`${record.resource_type} ${record.resource_id}: ${record.status} (${record.manifest_path})`);
12
+ }
13
+ return records.some((record) => record.status === 'unresolved') ? 1 : 0;
14
+ }
15
+
16
+ module.exports = conflicts;
@@ -1,61 +1,73 @@
1
1
  'use strict';
2
2
 
3
- // `draftgo connect` — wire this project to an already-deployed DraftGo base.
4
- // Prompts for server URL + access token, writes .draftgo/config.json, and
5
- // (if Python is available) runs draftgo_init.py to pull initial context.
6
-
7
3
  const log = require('../logger');
8
- const { ask, askRequired, askPassword } = require('../prompt');
9
- const { writeProjectConfig, maybeRunInit } = require('../projectConfig');
10
-
11
- function normalizeServer(raw) {
12
- let v = String(raw || '').trim();
13
- if (!v) return '';
14
- if (!/^https?:\/\//i.test(v)) v = 'http://' + v;
15
- return v.replace(/\/+$/, '');
16
- }
4
+ const { ask, askPassword } = require('../prompt');
5
+ const { normalizeServer, writeProjectConfig } = require('../projectConfig');
6
+ const { testConnection } = require('../mcp/client');
7
+ const { redactText } = require('../mcp/protocol');
17
8
 
18
- async function promptServer(defaultVal) {
9
+ async function promptServer(defaultValue) {
19
10
  while (true) {
20
- const v = await ask('DraftGo 服务器地址 (BaseURL)', { default: defaultVal });
21
- const norm = normalizeServer(v);
22
- if (!norm) { log.dim(' 不能为空'); continue; }
11
+ const raw = await ask('DraftGo server (Base URL)', { default: defaultValue });
23
12
  try {
24
- // Sanity-parse the URL so we fail fast on obvious typos.
25
- // eslint-disable-next-line no-new
26
- new URL(norm);
27
- } catch {
28
- log.dim(` 不是合法 URL:${norm}`);
29
- continue;
13
+ const value = normalizeServer(raw);
14
+ if (value) return value;
15
+ } catch (error) {
16
+ log.dim(` ${error.message}`);
30
17
  }
31
- return norm;
32
18
  }
33
19
  }
34
20
 
35
- async function connect(projectDir, positional, flags) {
36
- log.title('draftgo connect — 连接已有的 DraftGo 服务器');
37
- log.dim(' 把本项目绑定到一台已部署的 DraftGo 基座。');
38
-
39
- const server = flags.server
40
- ? normalizeServer(flags.server)
41
- : await promptServer('https://');
42
- const token = flags.token
43
- ? String(flags.token)
44
- : await askRequired('系统访问令牌 (SAT)', {
45
- validate: (v) => v.length < 10 ? 'token 看起来太短了,请检查后重试' : '',
46
- });
47
-
48
- const cfgPath = writeProjectConfig(projectDir, server, token);
49
- log.ok(`已写入 ${cfgPath}`);
50
- maybeRunInit(projectDir, server, token);
51
-
52
- log.title('完成');
53
- log.plain(` - 服务器: ${server}`);
54
- log.plain(` - 配置文件: ${cfgPath}`);
55
- log.dim(' 之后在 AI 工具里说「同步页面 xxx」即可。');
56
- // Silence unused-arg lint if any future tooling complains.
57
- void positional;
58
- void askPassword;
21
+ async function promptToken() {
22
+ while (true) {
23
+ const value = String(await askPassword('DraftGo system access token (SAT)') || '').trim();
24
+ if (value) return value;
25
+ log.dim(' A SAT is required.');
26
+ }
27
+ }
28
+
29
+ async function connect(projectDir, positional, flags = {}) {
30
+ log.title('draftgo connect');
31
+ const server = flags.server ? normalizeServer(flags.server) : await promptServer('https://');
32
+ const token = flags.token ? String(flags.token).trim() : await promptToken();
33
+ if (!token) {
34
+ log.err('A non-empty SAT is required.');
35
+ return 1;
36
+ }
37
+
38
+ log.step('Validating SAT and DraftGo MCP capabilities...');
39
+ try {
40
+ const diagnostic = await testConnection({ server, token }, {
41
+ timeoutMs: flags.timeout ? Number(flags.timeout) : undefined,
42
+ });
43
+ log.ok(`MCP ready (${diagnostic.tools.length} tools; tested ${diagnostic.testedTool}).`);
44
+ } catch (error) {
45
+ const message = redactText(error && error.message ? error.message : error, [token]);
46
+ if (!flags['allow-offline']) {
47
+ log.err(message);
48
+ log.dim(' The project config was not changed. Run `draftgo mcp test` after checking the server and SAT.');
49
+ return 1;
50
+ }
51
+ log.warn(`MCP validation unavailable: ${message}`);
52
+ log.warn('Continuing only because --allow-offline was explicitly supplied.');
53
+ }
54
+
55
+ const configFile = writeProjectConfig(projectDir, server, token);
56
+ log.ok(`Project configuration written: ${configFile}`);
57
+
58
+ if (!flags['no-mcp-setup']) {
59
+ try {
60
+ const setupCode = require('./mcp').setup(projectDir, positional, flags);
61
+ if (setupCode !== 0) log.dim(' Run `draftgo mcp setup <target>` when the AI host is known.');
62
+ } catch (error) {
63
+ log.warn(`MCP host setup was not completed: ${redactText(error.message || error, [token])}`);
64
+ }
65
+ }
66
+
67
+ log.title('Connected');
68
+ log.plain(` server: ${server}`);
69
+ log.plain(` config: ${configFile}`);
70
+ log.dim(' No DraftGo business resources were downloaded. Use MCP discovery or checkout long content explicitly.');
59
71
  return 0;
60
72
  }
61
73