draftgo-cli 4.0.24 → 4.0.26

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 (88) hide show
  1. package/README.md +23 -37
  2. package/package.json +3 -5
  3. package/resources/skill/SKILL.md +9 -5
  4. package/resources/skill/manifest.json +2 -5
  5. package/resources/skill/references/ai.md +41 -0
  6. package/resources/skill/references/app-api.md +2 -50
  7. package/resources/skill/references/architecture.md +1 -1
  8. package/resources/skill/references/chat-sdk.md +29 -37
  9. package/resources/skill/references/checkout.md +4 -4
  10. package/resources/skill/references/data.md +0 -46
  11. package/resources/skill/references/delivery.md +3 -3
  12. package/resources/skill/references/diagnostics.md +10 -11
  13. package/resources/skill/references/frontend.md +23 -20
  14. package/resources/skill/references/mcp.md +4 -14
  15. package/resources/skill/references/methods.md +15 -68
  16. package/resources/skill/references/modules.md +23 -44
  17. package/resources/skill/references/runtime.md +3 -20
  18. package/resources/skill/story/SKILL.md +2 -2
  19. package/src/apiContractCache.js +14 -6
  20. package/src/cli.js +0 -7
  21. package/src/commandRegistry.js +0 -6
  22. package/src/commands/api.js +87 -17
  23. package/src/commands/apiKey.js +2 -6
  24. package/src/commands/autoPush.js +15 -51
  25. package/src/commands/capabilities.js +22 -15
  26. package/src/commands/check.js +19 -53
  27. package/src/commands/checkout.js +1 -4
  28. package/src/commands/clean.js +1 -1
  29. package/src/commands/commit.js +1 -4
  30. package/src/commands/components.js +12 -8
  31. package/src/commands/conflict.js +4 -6
  32. package/src/commands/conflicts.js +1 -2
  33. package/src/commands/connect.js +0 -8
  34. package/src/commands/delete.js +15 -11
  35. package/src/commands/deploy.js +64 -26
  36. package/src/commands/diff.js +1 -4
  37. package/src/commands/group.js +2 -3
  38. package/src/commands/help.js +22 -43
  39. package/src/commands/init.js +13 -6
  40. package/src/commands/local.js +4 -1
  41. package/src/commands/map.js +138 -23
  42. package/src/commands/reconcile.js +1 -15
  43. package/src/commands/role.js +1 -2
  44. package/src/commands/status.js +12 -40
  45. package/src/commands/update.js +18 -24
  46. package/src/commands/verify.js +8 -7
  47. package/src/commands/worklog.js +11 -5
  48. package/src/contractCompatibility.js +10 -2
  49. package/src/localRuntime/compose.js +41 -27
  50. package/src/localRuntime/detect.js +6 -6
  51. package/src/localRuntime/index.js +47 -47
  52. package/src/localRuntime/services.js +2 -39
  53. package/src/mcp/client.js +99 -134
  54. package/src/mcp/parallel.js +25 -2
  55. package/src/mcp/protocol.js +38 -9
  56. package/src/mcp/tools.js +10 -19
  57. package/src/projectConfig.js +1 -4
  58. package/src/{workspaceHealth.js → projectHealth.js} +5 -5
  59. package/src/projectMap.js +1 -1
  60. package/src/runtimeFiles.js +2 -1
  61. package/src/worklog.js +3 -2
  62. package/src/worktree/backend.js +127 -15
  63. package/src/worktree/index.js +64 -22
  64. package/src/worktree/locks.js +52 -0
  65. package/src/worktree/manifest.js +18 -4
  66. package/src/worktree/status.js +4 -2
  67. package/resources/custom-service-sdk/ai.go +0 -520
  68. package/resources/custom-service-sdk/ai_test.go +0 -156
  69. package/resources/custom-service-sdk/auth_test.go +0 -56
  70. package/resources/custom-service-sdk/billing.go +0 -596
  71. package/resources/custom-service-sdk/billing_test.go +0 -150
  72. package/resources/custom-service-sdk/go.mod +0 -3
  73. package/resources/custom-service-sdk/manifest.json +0 -77
  74. package/resources/custom-service-sdk/platform.go +0 -352
  75. package/resources/custom-service-sdk/platform_logger_test.go +0 -24
  76. package/resources/custom-service-sdk/registration_test.go +0 -39
  77. package/resources/custom-service-sdk/resources.go +0 -247
  78. package/resources/custom-service-sdk/resources_billing_test.go +0 -115
  79. package/resources/custom-service-sdk/resources_files_test.go +0 -57
  80. package/resources/custom-service-sdk/resources_scope_test.go +0 -92
  81. package/resources/custom-service-sdk/sdk.go +0 -209
  82. package/resources/skill/references/aihub.md +0 -116
  83. package/resources/skill/references/custom-services.md +0 -201
  84. package/src/commands/customService.js +0 -95
  85. package/src/commands/dataRange.js +0 -33
  86. package/src/commands/grant.js +0 -29
  87. package/src/commands/space.js +0 -41
  88. package/src/customServices.js +0 -484
@@ -23,6 +23,7 @@ function readEnv(projectDir) {
23
23
 
24
24
  function writeEnv(projectDir, env) {
25
25
  writeText(envPath(projectDir), `${Object.entries(env).map(([key, value]) => `${key}=${value}`).join('\n')}\n`);
26
+ fs.chmodSync(envPath(projectDir), 0o600);
26
27
  }
27
28
 
28
29
  function sanitizeProjectName(raw, fallback = 'draftgo') {
@@ -30,76 +31,89 @@ function sanitizeProjectName(raw, fallback = 'draftgo') {
30
31
  return value || fallback;
31
32
  }
32
33
 
33
- function defaultRedisKeyPrefix(projectName, mysql) {
34
- return (mysql && mysql.database) || projectName;
35
- }
36
-
37
34
  function dockerHost(host) {
38
35
  return host === 'localhost' || host === '127.0.0.1' ? 'host.docker.internal' : host;
39
36
  }
40
37
 
41
38
  function generate(projectDir, opts) {
42
39
  const root = stackDir(projectDir);
40
+ if (exists(composePath(projectDir)) && readText(composePath(projectDir)).includes('./data/storage:/app/data/storage')) {
41
+ throw new Error('Existing runtime uses legacy storage. Back up and migrate its data to the final DraftGo /app/data layout before regenerating Compose; existing files were preserved.');
42
+ }
43
43
  ensureDir(root);
44
44
  const env = readEnv(projectDir);
45
45
  const projectName = sanitizeProjectName(opts.projectName, 'draftgo');
46
46
  const database = opts.mysql.database;
47
47
  const qdrant = opts.qdrant || { host: '127.0.0.1', port: 26333, apiKey: '' };
48
48
 
49
- env.APP_PORT = String(opts.appPort || 3000);
49
+ env.APP_PORT = String(opts.appPort || 7777);
50
50
  env.SECRET_KEY = env.SECRET_KEY || randomHex(32);
51
+ env.SYSTEM_CONFIG_MASTER_KEY = env.SYSTEM_CONFIG_MASTER_KEY || randomHex(32);
51
52
  env.DATABASE_URL = `mysql://${encodeURIComponent(opts.mysql.user)}:${encodeURIComponent(opts.mysql.password)}@${dockerHost(opts.mysql.host)}:${opts.mysql.port}/${encodeURIComponent(database)}`;
52
- env.REDIS_HOST = dockerHost(opts.redis.host);
53
- env.REDIS_PORT = String(opts.redis.port);
54
- env.REDIS_PASSWORD = opts.redis.password || '';
55
- env.REDIS_KEY_PREFIX = database;
56
- env.QDRANT_URL = `http://${dockerHost(qdrant.host)}:${qdrant.port}`;
57
- env.QDRANT_API_KEY = qdrant.apiKey || '';
53
+ env.REDIS_URL = 'redis://redis:6379/0';
54
+ for (const key of ['REDIS_HOST', 'REDIS_PORT', 'REDIS_PASSWORD', 'REDIS_KEY_PREFIX']) delete env[key];
55
+ env.QDRANT_URL = qdrant ? `http://${dockerHost(qdrant.host)}:${qdrant.port}` : '';
56
+ env.QDRANT_API_KEY = qdrant ? (qdrant.apiKey || '') : '';
58
57
  env.QDRANT_COLLECTION_PREFIX = database;
59
58
 
60
59
  const yaml = [
61
- '# Generated by draftgo-cli. Shared MySQL, Redis, and Qdrant stay on the host.',
60
+ '# Generated by draftgo-cli. MySQL and Qdrant are shared; Redis is project-isolated.',
62
61
  `name: ${projectName}`,
63
62
  'services:',
64
63
  ' app:',
65
- ' image: cabinai/draftgo:latest',
64
+ ` image: ${opts.image || 'cabinai/draftgo:latest'}`,
66
65
  ` container_name: ${projectName}`,
67
66
  ' restart: unless-stopped',
68
67
  ' ports:',
69
68
  ' - "${APP_PORT}:3000"',
70
69
  ' environment:',
70
+ ' APP_ENV: production',
71
71
  ' APP_HOST: 0.0.0.0',
72
72
  ' APP_PORT: "3000"',
73
73
  ' PUBLIC_BASE_URL: http://localhost:${APP_PORT}',
74
74
  ' DATABASE_URL: ${DATABASE_URL}',
75
75
  ' SECRET_KEY: ${SECRET_KEY}',
76
- ' REDIS_HOST: ${REDIS_HOST}',
77
- ' REDIS_PORT: ${REDIS_PORT}',
78
- ' REDIS_PASSWORD: ${REDIS_PASSWORD}',
79
- ' REDIS_DB: "0"',
80
- ' REDIS_KEY_PREFIX: ${REDIS_KEY_PREFIX}',
76
+ ' SYSTEM_CONFIG_MASTER_KEY: ${SYSTEM_CONFIG_MASTER_KEY}',
77
+ ' REDIS_URL: ${REDIS_URL}',
81
78
  ' QDRANT_URL: ${QDRANT_URL}',
82
79
  ' QDRANT_API_KEY: ${QDRANT_API_KEY}',
83
80
  ' QDRANT_COLLECTION_PREFIX: ${QDRANT_COLLECTION_PREFIX}',
84
- ' LOCAL_UPLOAD_DIR: /app/data/storage/uploads',
85
- ' KB_STORAGE_DIR: /app/data/storage/kb',
86
- ' DRAFTGO_GO_SERVICE_CACHE_DIR: /app/data/storage/custom-services/cache',
87
- ' MCP_TEMP_DIR: /app/data/storage/mcp/tmp',
81
+ ' FILE_STORAGE_ROOT: /app/data/files',
88
82
  ' volumes:',
89
- ' - ./data/storage:/app/data/storage',
83
+ ' - app_data:/app/data',
84
+ ' healthcheck:',
85
+ ' test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/api/system/health"]',
86
+ ' interval: 10s',
87
+ ' timeout: 5s',
88
+ ' start_period: 10s',
89
+ ' retries: 6',
90
+ ' depends_on:',
91
+ ' redis:',
92
+ ' condition: service_healthy',
90
93
  ' extra_hosts:',
91
94
  ' - "host.docker.internal:host-gateway"',
95
+ ' redis:',
96
+ ' image: redis:7.4-alpine',
97
+ ' restart: unless-stopped',
98
+ ' command: ["redis-server", "--appendonly", "yes"]',
99
+ ' volumes:',
100
+ ' - redis_data:/data',
101
+ ' healthcheck:',
102
+ ' test: ["CMD", "redis-cli", "ping"]',
103
+ ' interval: 5s',
104
+ ' timeout: 5s',
105
+ ' retries: 20',
106
+ 'volumes:',
107
+ ' app_data:',
108
+ ' redis_data:',
92
109
  '',
93
110
  ].join('\n');
94
111
  writeText(composePath(projectDir), yaml);
95
112
  writeEnv(projectDir, env);
96
- for (const subdir of ['data/storage', 'data/storage/uploads', 'data/storage/kb', 'data/storage/mcp/tmp', 'data/storage/custom-services/cache']) {
97
- try { fs.mkdirSync(path.join(root, subdir), { recursive: true }); } catch {}
98
- }
99
113
  return { dir: root, composeFile: composePath(projectDir), envFile: envPath(projectDir), projectName, env };
100
114
  }
101
115
 
102
116
  module.exports = {
103
117
  stackDir, composePath, envPath,
104
- generate, sanitizeProjectName, defaultRedisKeyPrefix, randomHex,
118
+ generate, sanitizeProjectName, randomHex,
105
119
  };
@@ -31,9 +31,8 @@ function probeHttpOnce(url, timeoutMs = 2000) {
31
31
  return new Promise((resolve) => {
32
32
  try {
33
33
  const req = http.get(url, { timeout: timeoutMs }, (res) => {
34
- // Any HTTP response (including 4xx/5xx) means the app is listening.
35
- res.resume();
36
- resolve(true);
34
+ res.resume();
35
+ resolve(res.statusCode >= 200 && res.statusCode < 300);
37
36
  });
38
37
  req.on('timeout', () => { req.destroy(); resolve(false); });
39
38
  req.on('error', () => resolve(false));
@@ -61,9 +60,10 @@ function tryCmd(cmd, args) {
61
60
  }
62
61
  }
63
62
 
64
- function detectDocker() {
65
- const hasDocker = tryCmd('docker', ['version', '--format', '{{.Client.Version}}']);
66
- if (!hasDocker) return { ok: false, reason: 'docker-missing' };
63
+ function detectDocker() {
64
+ const hasDocker = tryCmd('docker', ['--version']);
65
+ if (!hasDocker) return { ok: false, reason: 'docker-missing' };
66
+ if (!tryCmd('docker', ['info', '--format', '{{.ServerVersion}}'])) return { ok: false, reason: 'docker-not-running' };
67
67
  // Prefer `docker compose` (plugin); fall back to legacy `docker-compose`.
68
68
  if (tryCmd('docker', ['compose', 'version'])) {
69
69
  return { ok: true, composeCmd: 'docker', composeArgs: ['compose'] };
@@ -1,16 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  const { spawnSync } = require('child_process');
4
+ const path = require('path');
4
5
  const log = require('../logger');
5
6
  const { askRequired, askPassword, confirm } = require('../prompt');
6
7
  const { probePort, probeHttp, detectDocker } = require('./detect');
7
8
  const compose = require('./compose');
8
9
  const { ensureDatabase, testConnection, describeClient } = require('./mysqlClient');
9
- const { defaults, portOpen, probeRedis, probeQdrant, startService } = require('./services');
10
+ const { defaults, portOpen, probeQdrant, startService } = require('./services');
10
11
  const { writeProjectConfig } = require('../projectConfig');
11
- const { appendGitignoreLine } = require('../fsx');
12
+ const { appendGitignoreLine, exists, readText } = require('../fsx');
12
13
 
13
- const DEFAULT_APP_PORT = 3000;
14
+ const DEFAULT_APP_PORT = 7777;
14
15
 
15
16
  async function promptMysqlConnection() {
16
17
  while (true) {
@@ -25,10 +26,11 @@ async function promptMysqlConnection() {
25
26
  }
26
27
  }
27
28
 
28
- async function ensureProjectDatabase(conn, database) {
29
+ async function ensureProjectDatabase(conn, database, interactive = true) {
29
30
  while (true) {
30
31
  const result = await ensureDatabase({ ...conn, database }, {
31
32
  rootPromptFn: async () => {
33
+ if (!interactive) return null;
32
34
  log.warn(` ${conn.user} cannot create database ${database}.`);
33
35
  if (!await confirm(' Create it with another MySQL account?', { default: true })) return null;
34
36
  return {
@@ -39,13 +41,14 @@ async function ensureProjectDatabase(conn, database) {
39
41
  });
40
42
  if (result.ok) return { ...conn, database };
41
43
  log.err(` Database setup failed: ${(result.detail || result.reason || 'unknown error').split('\n')[0]}`);
44
+ if (!interactive) throw new Error('Database setup needs credentials. Run `draftgo local setup` in a terminal to continue.');
42
45
  const retry = await confirm(' Re-enter MySQL connection settings?', { default: true });
43
46
  if (!retry) throw new Error('MySQL database was not prepared.');
44
47
  conn = await promptMysqlConnection();
45
48
  }
46
49
  }
47
50
 
48
- async function planMysql(docker, defaultDatabase) {
51
+ async function planMysql(docker, defaultDatabase, interactive = true) {
49
52
  log.step('Checking MySQL');
50
53
  let conn = { ...defaults.mysql };
51
54
  if (!await portOpen(conn.host, conn.port)) {
@@ -54,40 +57,15 @@ async function planMysql(docker, defaultDatabase) {
54
57
  }
55
58
  if (!testConnection(conn).ok) {
56
59
  log.warn(' Default MySQL credentials were not accepted.');
60
+ if (!interactive) throw new Error('MySQL needs credentials. Run `draftgo local setup` in a terminal to continue.');
57
61
  conn = await promptMysqlConnection();
58
62
  }
59
- const database = await askRequired(' Project database name', { default: defaultDatabase });
60
- const out = await ensureProjectDatabase(conn, database);
63
+ const database = interactive ? await askRequired(' Project database name', { default: defaultDatabase }) : defaultDatabase;
64
+ const out = await ensureProjectDatabase(conn, database, interactive);
61
65
  log.ok(` MySQL ready: ${out.host}:${out.port}/${out.database} (${describeClient()})`);
62
66
  return out;
63
67
  }
64
68
 
65
- async function promptRedisConnection() {
66
- while (true) {
67
- const host = await askRequired(' Redis host', { default: '127.0.0.1' });
68
- const port = Number(await askRequired(' Redis port', { default: '6379' }));
69
- const password = await askPassword(' Redis password (empty for none)', { default: '' });
70
- const conn = { host, port, password };
71
- if (await probeRedis(conn)) return conn;
72
- log.err(' Redis PING failed. Check the address and password.');
73
- }
74
- }
75
-
76
- async function planRedis(docker) {
77
- log.step('Checking Redis');
78
- let conn = { ...defaults.redis };
79
- if (!await portOpen(conn.host, conn.port)) {
80
- log.dim(' Redis is not running locally; starting the shared local service.');
81
- if (!startService(docker, 'redis')) throw new Error('Unable to start shared Redis.');
82
- }
83
- if (!await probeRedis(conn)) {
84
- log.warn(' Default passwordless Redis PING failed.');
85
- conn = await promptRedisConnection();
86
- }
87
- log.ok(` Redis ready: ${conn.host}:${conn.port}`);
88
- return conn;
89
- }
90
-
91
69
  async function promptQdrantConnection() {
92
70
  while (true) {
93
71
  const host = await askRequired(' Qdrant host', { default: '127.0.0.1' });
@@ -99,7 +77,7 @@ async function promptQdrantConnection() {
99
77
  }
100
78
  }
101
79
 
102
- async function planQdrant(docker) {
80
+ async function planQdrant(docker, interactive = true) {
103
81
  log.step('Checking Qdrant');
104
82
  let conn = { ...defaults.qdrant };
105
83
  if (!await portOpen(conn.host, conn.port)) {
@@ -108,15 +86,22 @@ async function planQdrant(docker) {
108
86
  }
109
87
  if (!await probeQdrant(conn)) {
110
88
  log.warn(' Default unauthenticated Qdrant REST health check failed.');
89
+ if (!interactive) throw new Error('Qdrant needs credentials. Run `draftgo local setup` in a terminal to continue.');
111
90
  conn = await promptQdrantConnection();
112
91
  }
113
92
  log.ok(` Qdrant ready: ${conn.host}:${conn.port}`);
114
93
  return conn;
115
94
  }
116
95
 
117
- async function pickAppPort() {
96
+ async function pickAppPort(interactive = true) {
97
+ if (!interactive) {
98
+ for (let port = DEFAULT_APP_PORT; port < DEFAULT_APP_PORT + 100; port += 1) {
99
+ if (!await probePort('127.0.0.1', port)) return port;
100
+ }
101
+ throw new Error('No free local port found. Run `draftgo local setup` in a terminal to select a port.');
102
+ }
118
103
  const taken = await probePort('127.0.0.1', DEFAULT_APP_PORT);
119
- const suggested = taken ? '3001' : String(DEFAULT_APP_PORT);
104
+ const suggested = String(taken ? DEFAULT_APP_PORT + 1 : DEFAULT_APP_PORT);
120
105
  while (true) {
121
106
  const port = Number(await askRequired(' DraftGo host port', { default: suggested }));
122
107
  if (!Number.isInteger(port) || port < 1 || port > 65535) continue;
@@ -141,7 +126,7 @@ function composeUp(docker, dir) {
141
126
  }
142
127
 
143
128
  async function waitForApp(appPort) {
144
- const ok = await probeHttp(`http://127.0.0.1:${appPort}/`, { totalMs: 120000, intervalMs: 2000 });
129
+ const ok = await probeHttp(`http://127.0.0.1:${appPort}/api/system/health`, { totalMs: 120000, intervalMs: 2000 });
145
130
  if (!ok) log.err(' Timed out waiting for DraftGo. Run `draftgo local logs` to inspect the app.');
146
131
  return ok;
147
132
  }
@@ -174,35 +159,50 @@ async function finishMcpSetup(projectDir, mcpCommands) {
174
159
  return setupOk && testOk;
175
160
  }
176
161
 
177
- async function runWizard(projectDir, { yes = false, mcpCommands } = {}) {
162
+ async function runWizard(projectDir, { yes = false, mcpCommands, interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !yes, params = {} } = {}) {
178
163
  log.title('draftgo local setup');
164
+ if (!yes && !interactive) throw new Error('Run `draftgo local setup --yes` for unattended setup, or use a terminal for guided setup.');
179
165
  if (!yes && !await confirm('Continue with local DraftGo setup?', { default: true })) return 0;
180
166
 
181
167
  const docker = detectDocker();
182
168
  if (!docker.ok) {
183
169
  throw new Error(docker.reason === 'compose-missing'
184
- ? 'Docker is installed but Docker Compose is unavailable.'
185
- : 'Docker is required to start missing local dependencies.');
170
+ ? 'Install Docker Compose: https://docs.docker.com/compose/install/ then retry `draftgo local setup`.'
171
+ : docker.reason === 'docker-not-running'
172
+ ? 'Start Docker Desktop or the Docker daemon, then retry `draftgo local setup`. Installed Skills are preserved.'
173
+ : 'Install Docker: https://docs.docker.com/get-started/get-docker/ then start it and retry `draftgo local setup`. Installed Skills are preserved.');
186
174
  }
187
175
 
188
- const appPort = await pickAppPort();
189
- const projectName = await pickProjectName();
190
- const mysql = await planMysql(docker, projectName);
191
- const redis = await planRedis(docker);
192
- const qdrant = await planQdrant(docker);
176
+ if (exists(compose.composePath(projectDir))) {
177
+ if (readText(compose.composePath(projectDir)).includes('./data/storage:/app/data/storage')) {
178
+ throw new Error('Existing runtime uses legacy storage. Back up and migrate it to final DraftGo before setup; Compose and data were preserved.');
179
+ }
180
+ log.dim(' Existing project runtime preserved. Use `draftgo local start` to resume it, then `draftgo connect` to bind your API Key.');
181
+ return 0;
182
+ }
183
+ const appPort = params.appPort || await pickAppPort(interactive);
184
+ const projectName = params.projectName || (interactive ? await pickProjectName() : compose.sanitizeProjectName(path.basename(projectDir)));
185
+ const mysql = params.mysql || await planMysql(docker, projectName, interactive);
186
+ const qdrant = params.qdrant === false ? null : (params.qdrant || await planQdrant(docker, interactive));
193
187
 
194
- const out = compose.generate(projectDir, { projectName, appPort, mysql, redis, qdrant });
188
+ const out = compose.generate(projectDir, { projectName, appPort, mysql, qdrant, image: params.image });
195
189
  appendGitignoreLine(projectDir, '.draftgo/docker/');
196
190
  if (!composeUp(docker, out.dir) || !await waitForApp(appPort)) return 1;
197
191
 
198
192
  const url = `http://localhost:${appPort}`;
193
+ log.ok(`DraftGo runtime is ready at ${url}`);
194
+ log.dim(' Open the site, register or sign in, and obtain your personal API Key.');
195
+ if (!interactive) {
196
+ log.dim(` Connection pending. Run \`draftgo connect --server ${url}\` in a terminal to enter your API Key.`);
197
+ return 0;
198
+ }
199
199
  let token = '';
200
200
  while (token.length < 10) {
201
201
  token = String(await askPassword('Paste a DraftGo user API Key')).trim();
202
202
  if (token.length < 10) log.err(' API Key appears too short. Please try again.');
203
203
  }
204
204
  const cfgPath = writeProjectConfig(projectDir, url, token);
205
- await finishMcpSetup(projectDir, mcpCommands);
205
+ if (!await finishMcpSetup(projectDir, mcpCommands)) return 1;
206
206
  log.ok(`DraftGo is ready at ${url}`);
207
207
  log.dim(`Project config: ${cfgPath}`);
208
208
  return 0;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // Shared local runtime dependencies live outside individual projects. Project Compose
4
- // files only start DraftGo itself and connect back to these loopback services.
4
+ // files run DraftGo and isolated Redis, then connect back to MySQL and Qdrant.
5
5
  const net = require('net');
6
6
  const os = require('os');
7
7
  const path = require('path');
@@ -16,17 +16,15 @@ const MYSQL_INIT_FILE = path.join(SERVICES_DIR, 'mysql-init.sql');
16
16
 
17
17
  const defaults = {
18
18
  mysql: { host: '127.0.0.1', port: 3306, user: 'draftgo', password: 'draftgo' },
19
- redis: { host: '127.0.0.1', port: 6379, password: '' },
20
19
  qdrant: { host: '127.0.0.1', port: 26333, apiKey: '' },
21
20
  };
22
21
 
23
22
  function writeServiceFiles() {
24
23
  ensureDir(SERVICES_DIR);
25
24
  ensureDir(path.join(SERVICES_DIR, 'data', 'mysql'));
26
- ensureDir(path.join(SERVICES_DIR, 'data', 'redis'));
27
25
  ensureDir(path.join(SERVICES_DIR, 'data', 'qdrant'));
28
26
  writeText(MYSQL_INIT_FILE, [
29
- "GRANT ALL PRIVILEGES ON *.* TO 'draftgo'@'%' WITH GRANT OPTION;",
27
+ "GRANT ALL PRIVILEGES ON `draftgo\\_%`.* TO 'draftgo'@'%';",
30
28
  'FLUSH PRIVILEGES;',
31
29
  '',
32
30
  ].join('\n'));
@@ -54,20 +52,6 @@ function writeServiceFiles() {
54
52
  ' timeout: 5s',
55
53
  ' retries: 24',
56
54
  ' start_period: 20s',
57
- ' redis:',
58
- ' image: redis:7.4-alpine',
59
- ' container_name: draftgo-local-redis',
60
- ' restart: unless-stopped',
61
- ' command: ["redis-server", "--appendonly", "yes"]',
62
- ' ports:',
63
- ' - "127.0.0.1:6379:6379"',
64
- ' volumes:',
65
- ' - ./data/redis:/data',
66
- ' healthcheck:',
67
- ' test: ["CMD", "redis-cli", "ping"]',
68
- ' interval: 5s',
69
- ' timeout: 5s',
70
- ' retries: 20',
71
55
  ' qdrant:',
72
56
  ' image: qdrant/qdrant:v1.19.0',
73
57
  ' container_name: draftgo-local-qdrant',
@@ -97,26 +81,6 @@ function portOpen(host, port, timeoutMs = 800) {
97
81
  });
98
82
  }
99
83
 
100
- function probeRedis({ host, port, password = '' }) {
101
- return new Promise((resolve) => {
102
- const socket = net.createConnection({ host, port });
103
- let response = '';
104
- const finish = (ok) => { socket.destroy(); resolve(ok); };
105
- socket.setTimeout(2000);
106
- socket.once('connect', () => {
107
- const auth = password ? `*2\r\n$4\r\nAUTH\r\n$${Buffer.byteLength(password)}\r\n${password}\r\n` : '';
108
- socket.write(`${auth}*1\r\n$4\r\nPING\r\n`);
109
- });
110
- socket.on('data', (chunk) => {
111
- response += chunk.toString('utf8');
112
- if (response.includes('+PONG')) finish(true);
113
- if (response.includes('-NOAUTH') || response.includes('-WRONGPASS') || response.includes('-ERR')) finish(false);
114
- });
115
- socket.once('timeout', () => finish(false));
116
- socket.once('error', () => finish(false));
117
- });
118
- }
119
-
120
84
  function probeQdrant({ host, port, apiKey = '' }) {
121
85
  return new Promise((resolve) => {
122
86
  const client = String(host || '').startsWith('https://') ? https : http;
@@ -148,7 +112,6 @@ module.exports = {
148
112
  SERVICES_DIR,
149
113
  defaults,
150
114
  portOpen,
151
- probeRedis,
152
115
  probeQdrant,
153
116
  startService,
154
117
  };