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,105 +1,105 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
4
+ const path = require('path');
3
5
  const log = require('../logger');
4
- const { buildProjectMap } = require('../projectMap');
6
+ const { loadProjectConfig } = require('../projectConfig');
7
+ const { loadManifest, absolutePath } = require('../worktree/manifest');
8
+ const { hashFile } = require('../worktree/streams');
9
+ const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
10
+
11
+ function itemsFrom(value) {
12
+ if (Array.isArray(value)) return value;
13
+ if (!value || typeof value !== 'object') return [];
14
+ if (Array.isArray(value.items)) return value.items;
15
+ if (Array.isArray(value.resources)) return value.resources;
16
+ if (value.data) return itemsFrom(value.data);
17
+ return [];
18
+ }
19
+
20
+ function nextCursor(value) {
21
+ return value && (value.next_cursor || value.nextCursor || value.cursor && value.has_more && value.cursor);
22
+ }
23
+
24
+ async function listRemoteResources(session, flags) {
25
+ const resources = [];
26
+ const seen = new Set();
27
+ let cursor = null;
28
+ for (let page = 0; page < 100; page += 1) {
29
+ const args = {};
30
+ if (cursor) args.cursor = cursor;
31
+ if (flags.type) args.resource_type = String(flags.type);
32
+ const payload = await callStructured(session, TOOL_NAMES.resourceList, args);
33
+ resources.push(...itemsFrom(payload));
34
+ cursor = nextCursor(payload);
35
+ if (!cursor) break;
36
+ if (seen.has(cursor)) throw new Error('DraftGo resource_list repeated a cursor.');
37
+ seen.add(cursor);
38
+ }
39
+ if (cursor) throw new Error('DraftGo resource_list exceeded 100 pages.');
40
+ return resources;
41
+ }
5
42
 
6
- function printList(title, rows, render) {
7
- console.log('');
8
- log.info(`${title}:${rows.length}`);
9
- if (rows.length === 0) {
10
- log.dim(' (无)');
11
- return;
43
+ async function localCheckouts(projectDir) {
44
+ const manifest = loadManifest(projectDir);
45
+ const entries = [];
46
+ for (const entry of Object.values(manifest.entries)) {
47
+ const local = absolutePath(projectDir, entry.local_path);
48
+ let currentHash = null;
49
+ if (fs.existsSync(local)) currentHash = (await hashFile(local)).hash;
50
+ entries.push({
51
+ ...entry,
52
+ exists: currentHash !== null,
53
+ current_hash: currentHash,
54
+ changed: currentHash !== null && currentHash !== entry.base_hash,
55
+ });
12
56
  }
13
- for (const row of rows) log.dim(` • ${render(row)}`);
57
+ return entries;
14
58
  }
15
59
 
16
- function mapCommand(projectDir, flags = {}) {
17
- const map = buildProjectMap(projectDir);
60
+ function legacyCaches(projectDir) {
61
+ return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
62
+ .filter((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
63
+ }
64
+
65
+ async function mapCommand(projectDir, flags = {}) {
66
+ const config = loadProjectConfig(projectDir);
67
+ const session = await openToolSession(config, [TOOL_NAMES.projectOverview, TOOL_NAMES.resourceList]);
68
+ const [overview, resources, checkouts] = await Promise.all([
69
+ callStructured(session, TOOL_NAMES.projectOverview, {}),
70
+ listRemoteResources(session, flags),
71
+ localCheckouts(projectDir),
72
+ ]);
73
+ const result = {
74
+ server: config.server,
75
+ overview,
76
+ resources,
77
+ checkouts,
78
+ legacy_cache: { ignored: true, detected: legacyCaches(projectDir) },
79
+ };
18
80
 
19
81
  if (flags.output === 'json') {
20
- console.log(JSON.stringify(map, null, 2));
82
+ console.log(JSON.stringify(result, null, 2));
21
83
  return 0;
22
84
  }
23
-
24
85
  log.title('draftgo map');
25
- log.info(`项目目录:${projectDir}`);
26
-
27
- printList('页面', map.pages, (p) => {
28
- const id = p.id == null ? 'new' : p.id;
29
- return `${String(id).padEnd(4)} ${p.route || '(无 route)'} ${p.title || '未命名'}${p.html_file ? ` ${p.html_file}` : ''}`;
30
- });
31
-
32
- printList('导航', map.navigations, (n) => {
33
- const id = n.id == null ? 'new' : n.id;
34
- return `${String(id).padEnd(4)} ${n.code || 'default'} ${n.name || '未命名'}${n.html_file ? ` ${n.html_file}` : ''}`;
35
- });
36
-
37
- printList('动态 DB', map.db_meta, (m) => {
38
- const fields = m.fields.length ? ` (${m.fields.slice(0, 8).join(', ')}${m.fields.length > 8 ? ', ...' : ''})` : '';
39
- return `${m.type || '(无 type)'} ${m.label || ''}${fields}`;
40
- });
41
-
42
- printList('自定义脚本', map.custom_scripts, (s) => {
43
- const bindings = [
44
- ...(s.routes || []),
45
- ...(s.events || []).map((event) => `EVENT ${event}`),
46
- ...(s.schedules || []).map((cron) => `CRON ${cron}`),
47
- ];
48
- const summary = bindings.length ? ` [${bindings.join(' | ')}]` : '';
49
- return `${s.slug || '(无 slug)'} ${s.mode || 'mode?'} ${s.name || ''}${summary}${s.code_file ? ` ${s.code_file}` : ''}`;
50
- });
51
-
52
- printList('文档', map.docs, (d) => {
53
- return `${d.id || 'new'} ${d.slug || '(无 slug)'} ${d.title || '未命名'}${d.content_file ? ` ${d.content_file}` : ''}`;
54
- });
55
-
56
- printList('文档分类', map.doc_categories, (c) => {
57
- const parent = c.parent_id == null ? 'root' : `parent:${c.parent_id}`;
58
- return `${c.id || 'new'} ${c.slug || '(无 slug)'} ${c.name || '未命名'} ${parent}`;
59
- });
60
-
61
- printList('系统配置', map.system_config, (c) => {
62
- const sensitive = c.is_sensitive ? ' sensitive' : '';
63
- return `${c.config_key || '(无 key)'} ${c.category || 'default'} ${c.value_type || 'value'}${sensitive}`;
64
- });
65
-
66
- printList('AIHub', map.aihub, (a) => {
67
- const extra = [];
68
- if (a.type === 'agent') {
69
- if (a.mode) extra.push(a.mode);
70
- if (a.output_format && a.output_format.mode === 'json') extra.push(`json:${a.output_format.json_strategy || 'auto'}`);
71
- if (a.model_selection && a.model_selection.user_selectable) extra.push('user-model');
72
- if (a.tools && a.tools.sources.length) extra.push(`tools:${a.tools.sources.length}`);
73
- } else if (a.type === 'model') {
74
- extra.push(`models:${a.models_count || 0}`);
75
- if (a.supports_response_format === true) extra.push('response_format');
76
- if (a.supports_json_schema === true) extra.push('json_schema');
77
- } else if (a.type === 'mcp') {
78
- extra.push(a.transport || 'transport?');
79
- extra.push(`tools:${a.tools_count || 0}`);
80
- }
81
- return `${a.id || 'new'} ${a.type || ''} ${a.name || ''}${extra.length ? ` [${extra.join(', ')}]` : ''}`;
82
- });
83
- printList('角色', map.roles, (r) => `${r.code || r.id || 'new'} ${r.name || ''}`);
84
-
85
- console.log('');
86
- log.info('页面分区:');
87
- const areaRows = [
88
- ['home', map.pageGroups.home.length],
89
- ['admin', map.pageGroups.admin.length],
90
- ['business', map.pageGroups.business.length],
91
- ['system', map.pageGroups.system.length],
92
- ['unknown', map.pageGroups.unknown.length],
93
- ];
94
- log.dim(' ' + areaRows.map(([k, v]) => `${k} ${v}`).join(' '));
95
-
96
- console.log('');
97
- log.info('入口引用:');
98
- const routes = Object.keys(map.routeRefs).sort();
99
- if (routes.length === 0) log.dim(' (未发现 data-page-route / href 引用)');
100
- else routes.forEach((route) => log.dim(` • ${route} ← ${map.routeRefs[route].join(', ')}`));
101
-
86
+ log.info(`Remote resources: ${resources.length}`);
87
+ const counts = new Map();
88
+ for (const resource of resources) {
89
+ const type = resource.resource_type || resource.type || 'unknown';
90
+ counts.set(type, (counts.get(type) || 0) + 1);
91
+ }
92
+ for (const [type, count] of [...counts.entries()].sort()) log.plain(` ${type}: ${count}`);
93
+ log.info(`Local checkouts: ${checkouts.length}`);
94
+ for (const entry of checkouts) {
95
+ const state = !entry.exists ? 'missing' : entry.changed ? 'modified' : 'clean';
96
+ log.plain(` ${entry.resource_type} ${entry.resource_id}: ${state} (${entry.local_path})`);
97
+ }
98
+ if (result.legacy_cache.detected.length) {
99
+ log.warn(`Ignored legacy cache indexes: ${result.legacy_cache.detected.join(', ')}`);
100
+ }
102
101
  return 0;
103
102
  }
104
103
 
105
104
  module.exports = mapCommand;
105
+ module.exports.itemsFrom = itemsFrom;
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { loadProjectConfig } = require('../projectConfig');
5
+ const { testConnection } = require('../mcp/client');
6
+ const { redactText } = require('../mcp/protocol');
7
+ const {
8
+ HOSTS,
9
+ detectHostTargets,
10
+ setupHosts,
11
+ statusHosts,
12
+ } = require('../mcp/hosts');
13
+ const { serveStdio } = require('../mcp/stdio');
14
+
15
+ function targetArgs(positional, flags) {
16
+ const values = positional.slice();
17
+ if (flags.target) values.push(...String(flags.target).split(','));
18
+ return values.map((value) => String(value).trim()).filter(Boolean);
19
+ }
20
+
21
+ function printUsage() {
22
+ log.plain('Usage:');
23
+ log.plain(' draftgo mcp setup [target...]');
24
+ log.plain(' draftgo mcp status [target...]');
25
+ log.plain(' draftgo mcp test');
26
+ log.plain(' draftgo mcp serve');
27
+ log.dim(` targets: ${HOSTS.map((host) => host.name).join(', ')}`);
28
+ }
29
+
30
+ function setup(projectDir, positional = [], flags = {}) {
31
+ const targets = targetArgs(positional, flags);
32
+ const detected = targets.length ? null : detectHostTargets(projectDir);
33
+ if (!targets.length && detected.length === 0) {
34
+ log.err('No supported AI host was detected in this project.');
35
+ log.dim(' Run `draftgo mcp setup <target>` to select one explicitly.');
36
+ return 1;
37
+ }
38
+
39
+ const results = setupHosts(projectDir, targets.length ? targets : detected.map((host) => host.name));
40
+ let configured = 0;
41
+ let unsupported = 0;
42
+ for (const result of results) {
43
+ if (!result.supported) {
44
+ unsupported += 1;
45
+ log.warn(`${result.host.displayName}: ${result.reason}`);
46
+ continue;
47
+ }
48
+ configured += 1;
49
+ if (result.changed) log.ok(`${result.host.displayName}: ${result.host.path}`);
50
+ else log.info(`${result.host.displayName}: already configured`);
51
+ }
52
+ if (!configured && unsupported) return 1;
53
+ if (!configured) {
54
+ log.err('No MCP host configuration was written.');
55
+ return 1;
56
+ }
57
+ return 0;
58
+ }
59
+
60
+ function status(projectDir, positional = [], flags = {}) {
61
+ const targets = targetArgs(positional, flags);
62
+ const results = statusHosts(projectDir, targets);
63
+ log.title('draftgo mcp status');
64
+ let exitCode = 0;
65
+ try {
66
+ const config = loadProjectConfig(projectDir);
67
+ log.plain(` project configured (${config.server}; SAT present)`);
68
+ } catch (error) {
69
+ log.plain(` project not ready (${error.message})`);
70
+ exitCode = 1;
71
+ }
72
+ for (const result of results) {
73
+ if (!result.supported) {
74
+ log.plain(` - ${result.host.name.padEnd(12)} unsupported`);
75
+ } else if (result.configured && result.secure) {
76
+ log.plain(` OK ${result.host.name.padEnd(12)} ${result.host.path}`);
77
+ } else if (result.error) {
78
+ log.plain(` x ${result.host.name.padEnd(12)} invalid config`);
79
+ exitCode = 1;
80
+ } else {
81
+ log.plain(` - ${result.host.name.padEnd(12)} not configured`);
82
+ }
83
+ }
84
+ return exitCode;
85
+ }
86
+
87
+ async function test(projectDir, _positional = [], flags = {}) {
88
+ let config;
89
+ try {
90
+ config = loadProjectConfig(projectDir);
91
+ const result = await testConnection(config, {
92
+ timeoutMs: flags.timeout ? Number(flags.timeout) : undefined,
93
+ });
94
+ log.ok('DraftGo MCP initialize succeeded.');
95
+ log.ok(`tools/list returned ${result.tools.length} tools.`);
96
+ log.ok(`tools/call succeeded: ${result.testedTool}`);
97
+ log.dim(` protocol: ${result.protocolVersion}`);
98
+ return 0;
99
+ } catch (error) {
100
+ const token = config && String(config.token || config.sat || '');
101
+ log.err(redactText(error && error.message ? error.message : error, [token]));
102
+ return 1;
103
+ }
104
+ }
105
+
106
+ async function serve(projectDir, _positional = [], _flags = {}) {
107
+ return serveStdio(projectDir);
108
+ }
109
+
110
+ async function mcp(projectDir, positional = [], flags = {}) {
111
+ const [action, ...rest] = positional;
112
+ if (action === 'setup') return setup(projectDir, rest, flags);
113
+ if (action === 'status') return status(projectDir, rest, flags);
114
+ if (action === 'test') return test(projectDir, rest, flags);
115
+ if (action === 'serve') return serve(projectDir, rest, flags);
116
+ printUsage();
117
+ return 1;
118
+ }
119
+
120
+ mcp.setup = setup;
121
+ mcp.status = status;
122
+ mcp.test = test;
123
+ mcp.serve = serve;
124
+ mcp.printUsage = printUsage;
125
+
126
+ module.exports = mcp;
@@ -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),