draftgo-cli 3.0.29 → 3.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -139
- package/package.json +10 -2
- package/resources/skill/SKILL.md +61 -184
- package/resources/skill/init/SKILL.md +18 -66
- package/resources/skill/manifest.json +27 -0
- package/resources/skill/pull/SKILL.md +18 -52
- package/resources/skill/push/SKILL.md +30 -282
- package/resources/skill/references/aihub.md +86 -0
- package/resources/skill/{quickref → references}/api-endpoints.md +39 -13
- package/resources/skill/references/api.json +20248 -0
- package/resources/skill/{quickref → references}/app-api.md +40 -0
- package/resources/skill/{core → references}/architecture.md +2 -2
- package/resources/skill/references/chat-sdk.md +201 -0
- package/resources/skill/references/custom-services.md +308 -0
- package/resources/skill/{specs → references}/data.md +5 -5
- package/resources/skill/{rules → references}/frontend.md +41 -11
- package/resources/skill/{core → references}/modules.md +7 -5
- package/resources/skill/references/parallel.md +48 -0
- package/resources/skill/{specs → references}/runtime.md +1 -1
- package/resources/skill/scripts/draftgo_push.py +80 -12
- package/resources/skill/story/SKILL.md +11 -16
- package/src/cli.js +13 -7
- package/src/commandRegistry.js +34 -0
- package/src/commands/api.js +153 -8
- package/src/commands/help.js +24 -29
- package/src/commands/init.js +17 -18
- package/src/commands/local.js +9 -3
- package/src/commands/sync.js +1 -1
- package/src/commands/update.js +40 -12
- package/src/index.js +13 -57
- package/src/localdev/compose.js +44 -200
- package/src/localdev/index.js +116 -216
- package/src/localdev/mysqlClient.js +12 -9
- package/src/localdev/services.js +163 -0
- package/src/projectConfig.js +1 -1
- package/src/projectMap.js +17 -80
- package/src/skill.js +1 -1
- package/src/updateCheck.js +2 -12
- package/resources/skill/practices/anti-patterns.md +0 -80
- package/resources/skill/practices/best-practices.md +0 -60
- package/resources/skill/practices/dev-declaration.md +0 -114
- package/resources/skill/quickref/api.json +0 -17784
- package/resources/skill/rules/dev-workflow.md +0 -749
- package/resources/skill/rules/parallel.md +0 -263
- package/resources/skill/scripts/__pycache__/draftgo_pull.cpython-312.pyc +0 -0
- package/resources/skill/scripts/__pycache__/draftgo_push.cpython-312.pyc +0 -0
- package/resources/skill/specs/custom-services.md +0 -199
- package/src/commands/doctor.js +0 -54
- package/src/commands/new.js +0 -186
- package/src/commands/projectScript.js +0 -37
- package/src/commands/upgrade.js +0 -52
- /package/resources/skill/{specs → references}/db-relations.md +0 -0
- /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
- /package/resources/skill/{specs → references}/security.md +0 -0
- /package/resources/skill/{specs → references}/ui-protocol.md +0 -0
package/src/localdev/compose.js
CHANGED
|
@@ -1,29 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Generate a docker-compose.yaml for the local-dev DraftGo stack.
|
|
4
|
-
//
|
|
5
|
-
// Stored *inside the project* at:
|
|
6
|
-
// <project>/.draftgo/docker/
|
|
7
|
-
// ├── docker-compose.yaml
|
|
8
|
-
// ├── .env # random secrets, kept across re-runs
|
|
9
|
-
// └── data/ # bind-mounted volumes
|
|
10
|
-
//
|
|
11
|
-
// Each project gets its own stack — no cross-project sharing — so multiple
|
|
12
|
-
// DraftGo projects can run in parallel without container/volume collisions.
|
|
13
|
-
//
|
|
14
|
-
// Stack composition is dynamic:
|
|
15
|
-
// - Always: the `app` service (cabinai/draftgo:latest)
|
|
16
|
-
// - Optional: `mysql` service (omitted when user wires up an existing MySQL)
|
|
17
|
-
// - Optional: `redis` service (omitted when user wires up an existing Redis)
|
|
18
|
-
// The app talks to in-stack services by service-name, and to host services via
|
|
19
|
-
// `host.docker.internal` (works on Windows/macOS natively; `host-gateway` extra
|
|
20
|
-
// host is added so it also works on Linux).
|
|
21
|
-
//
|
|
22
|
-
// Container naming: every service gets an explicit `container_name:` derived
|
|
23
|
-
// from the user-supplied compose project name, so the actual containers are
|
|
24
|
-
// named `<projectName>` / `<projectName>-mysql` / `<projectName>-redis`
|
|
25
|
-
// instead of compose's default `<projectName>-<service>-1`.
|
|
26
|
-
|
|
27
3
|
const crypto = require('crypto');
|
|
28
4
|
const path = require('path');
|
|
29
5
|
const fs = require('fs');
|
|
@@ -33,232 +9,100 @@ function stackDir(projectDir) { return path.join(projectDir, '.draftgo', 'docker
|
|
|
33
9
|
function composePath(projectDir) { return path.join(stackDir(projectDir), 'docker-compose.yaml'); }
|
|
34
10
|
function envPath(projectDir) { return path.join(stackDir(projectDir), '.env'); }
|
|
35
11
|
|
|
36
|
-
function
|
|
37
|
-
// URL-safe alphanumeric (no ambiguous shell metachars).
|
|
38
|
-
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
39
|
-
const bytes = crypto.randomBytes(len);
|
|
40
|
-
let out = '';
|
|
41
|
-
for (let i = 0; i < len; i++) out += charset[bytes[i] % charset.length];
|
|
42
|
-
return out;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function randomHex(bytes) {
|
|
46
|
-
return crypto.randomBytes(bytes).toString('hex');
|
|
47
|
-
}
|
|
12
|
+
function randomHex(bytes) { return crypto.randomBytes(bytes).toString('hex'); }
|
|
48
13
|
|
|
49
14
|
function readEnv(projectDir) {
|
|
50
|
-
// Parse existing .env into a flat object so we can preserve previously
|
|
51
|
-
// generated secrets across re-runs of the wizard.
|
|
52
15
|
if (!exists(envPath(projectDir))) return {};
|
|
53
16
|
const out = {};
|
|
54
17
|
for (const line of readText(envPath(projectDir)).split(/\r?\n/)) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
18
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
19
|
+
if (match) out[match[1]] = match[2];
|
|
57
20
|
}
|
|
58
21
|
return out;
|
|
59
22
|
}
|
|
60
23
|
|
|
61
24
|
function writeEnv(projectDir, env) {
|
|
62
|
-
|
|
63
|
-
writeText(envPath(projectDir), lines.join('\n') + '\n');
|
|
25
|
+
writeText(envPath(projectDir), `${Object.entries(env).map(([key, value]) => `${key}=${value}`).join('\n')}\n`);
|
|
64
26
|
}
|
|
65
27
|
|
|
66
|
-
// Compose project names must match [a-z0-9][a-z0-9_-]*.
|
|
67
|
-
// Trim/normalise user input so we never write an invalid compose file.
|
|
68
28
|
function sanitizeProjectName(raw, fallback = 'draftgo') {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return v || fallback;
|
|
29
|
+
const value = String(raw || '').trim().toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/^[-_]+/, '');
|
|
30
|
+
return value || fallback;
|
|
72
31
|
}
|
|
73
32
|
|
|
74
33
|
function defaultRedisKeyPrefix(projectName, mysql) {
|
|
75
|
-
|
|
76
|
-
|
|
34
|
+
return (mysql && mysql.database) || projectName;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function dockerHost(host) {
|
|
38
|
+
return host === 'localhost' || host === '127.0.0.1' ? 'host.docker.internal' : host;
|
|
77
39
|
}
|
|
78
40
|
|
|
79
|
-
// `opts`:
|
|
80
|
-
// projectName: docker compose project name (also drives container_name)
|
|
81
|
-
// appPort: host port for the app (default 3000)
|
|
82
|
-
// mysql: { useContainer, host, port, user, password, database }
|
|
83
|
-
// redis: { useContainer, host, port, password }
|
|
84
41
|
function generate(projectDir, opts) {
|
|
85
42
|
const root = stackDir(projectDir);
|
|
86
43
|
ensureDir(root);
|
|
87
44
|
const env = readEnv(projectDir);
|
|
88
45
|
const projectName = sanitizeProjectName(opts.projectName, 'draftgo');
|
|
46
|
+
const database = opts.mysql.database;
|
|
47
|
+
const milvus = opts.milvus || { host: '127.0.0.1', port: 19530, username: '', password: '' };
|
|
89
48
|
|
|
90
|
-
|
|
91
|
-
env.APP_PORT = String(appPort);
|
|
49
|
+
env.APP_PORT = String(opts.appPort || 3000);
|
|
92
50
|
env.SECRET_KEY = env.SECRET_KEY || randomHex(32);
|
|
93
|
-
env.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
env.MYSQL_USER = opts.mysql.user || env.MYSQL_USER || 'draftgo';
|
|
104
|
-
env.MYSQL_PASSWORD = env.MYSQL_PASSWORD || randomAlnum(24);
|
|
105
|
-
services.mysql = [
|
|
106
|
-
' mysql:',
|
|
107
|
-
' image: mysql:8.0',
|
|
108
|
-
` container_name: ${projectName}-mysql`,
|
|
109
|
-
' restart: unless-stopped',
|
|
110
|
-
' logging:',
|
|
111
|
-
' driver: json-file',
|
|
112
|
-
' options:',
|
|
113
|
-
' max-size: "10m"',
|
|
114
|
-
' max-file: "3"',
|
|
115
|
-
' environment:',
|
|
116
|
-
' MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}',
|
|
117
|
-
' MYSQL_DATABASE: ${MYSQL_DATABASE}',
|
|
118
|
-
' MYSQL_USER: ${MYSQL_USER}',
|
|
119
|
-
' MYSQL_PASSWORD: ${MYSQL_PASSWORD}',
|
|
120
|
-
' command:',
|
|
121
|
-
' - --character-set-server=utf8mb4',
|
|
122
|
-
' - --collation-server=utf8mb4_unicode_ci',
|
|
123
|
-
' volumes:',
|
|
124
|
-
' - ./data/mysql:/var/lib/mysql',
|
|
125
|
-
' healthcheck:',
|
|
126
|
-
' test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]',
|
|
127
|
-
' interval: 10s',
|
|
128
|
-
' timeout: 5s',
|
|
129
|
-
' retries: 12',
|
|
130
|
-
' start_period: 30s',
|
|
131
|
-
].join('\n');
|
|
132
|
-
dependsOn.push(['mysql', 'service_healthy']);
|
|
133
|
-
dbUrl = 'mysql+pymysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE}';
|
|
134
|
-
} else {
|
|
135
|
-
const u = encodeURIComponent(opts.mysql.user);
|
|
136
|
-
const p = encodeURIComponent(opts.mysql.password);
|
|
137
|
-
const host = opts.mysql.host === 'localhost' || opts.mysql.host === '127.0.0.1'
|
|
138
|
-
? 'host.docker.internal'
|
|
139
|
-
: opts.mysql.host;
|
|
140
|
-
dbUrl = `mysql+pymysql://${u}:${p}@${host}:${opts.mysql.port}/${opts.mysql.database}`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ── Redis ────────────────────────────────────────────────────────────
|
|
144
|
-
let redisHost, redisPort, redisPass;
|
|
145
|
-
if (opts.redis.useContainer) {
|
|
146
|
-
env.REDIS_PASSWORD = env.REDIS_PASSWORD || randomAlnum(24);
|
|
147
|
-
services.redis = [
|
|
148
|
-
' redis:',
|
|
149
|
-
' image: redis:7-alpine',
|
|
150
|
-
` container_name: ${projectName}-redis`,
|
|
151
|
-
' restart: unless-stopped',
|
|
152
|
-
' logging:',
|
|
153
|
-
' driver: json-file',
|
|
154
|
-
' options:',
|
|
155
|
-
' max-size: "10m"',
|
|
156
|
-
' max-file: "3"',
|
|
157
|
-
' command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]',
|
|
158
|
-
' volumes:',
|
|
159
|
-
' - ./data/redis:/data',
|
|
160
|
-
' healthcheck:',
|
|
161
|
-
' test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]',
|
|
162
|
-
' interval: 10s',
|
|
163
|
-
' timeout: 3s',
|
|
164
|
-
' retries: 10',
|
|
165
|
-
' start_period: 10s',
|
|
166
|
-
].join('\n');
|
|
167
|
-
dependsOn.push(['redis', 'service_healthy']);
|
|
168
|
-
redisHost = 'redis';
|
|
169
|
-
redisPort = '6379';
|
|
170
|
-
redisPass = '${REDIS_PASSWORD}';
|
|
171
|
-
} else {
|
|
172
|
-
redisHost = (opts.redis.host === 'localhost' || opts.redis.host === '127.0.0.1')
|
|
173
|
-
? 'host.docker.internal'
|
|
174
|
-
: opts.redis.host;
|
|
175
|
-
redisPort = String(opts.redis.port);
|
|
176
|
-
redisPass = opts.redis.password || '';
|
|
177
|
-
}
|
|
51
|
+
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.MILVUS_ADDRESS = `${dockerHost(milvus.host)}:${milvus.port}`;
|
|
57
|
+
env.MILVUS_USERNAME = milvus.username || '';
|
|
58
|
+
env.MILVUS_PASSWORD = milvus.password || '';
|
|
59
|
+
env.MILVUS_DATABASE = 'default';
|
|
60
|
+
env.MILVUS_COLLECTION_PREFIX = database;
|
|
178
61
|
|
|
179
|
-
|
|
180
|
-
|
|
62
|
+
const yaml = [
|
|
63
|
+
'# Generated by draftgo-cli. Shared MySQL, Redis, and Milvus stay on the host.',
|
|
64
|
+
`name: ${projectName}`,
|
|
65
|
+
'services:',
|
|
181
66
|
' app:',
|
|
182
67
|
' image: cabinai/draftgo:latest',
|
|
183
68
|
` container_name: ${projectName}`,
|
|
184
69
|
' restart: unless-stopped',
|
|
185
|
-
'
|
|
186
|
-
'
|
|
187
|
-
' options:',
|
|
188
|
-
' max-size: "10m"',
|
|
189
|
-
' max-file: "3"',
|
|
190
|
-
` ports:`,
|
|
191
|
-
` - "${appPort}:3000"`,
|
|
70
|
+
' ports:',
|
|
71
|
+
' - "${APP_PORT}:3000"',
|
|
192
72
|
' environment:',
|
|
193
73
|
' APP_HOST: 0.0.0.0',
|
|
194
74
|
' APP_PORT: "3000"',
|
|
195
|
-
|
|
196
|
-
'
|
|
197
|
-
` DATABASE_URL: "${dbUrl}"`,
|
|
75
|
+
' PUBLIC_BASE_URL: http://localhost:${APP_PORT}',
|
|
76
|
+
' DATABASE_URL: ${DATABASE_URL}',
|
|
198
77
|
' SECRET_KEY: ${SECRET_KEY}',
|
|
199
|
-
|
|
200
|
-
|
|
78
|
+
' REDIS_HOST: ${REDIS_HOST}',
|
|
79
|
+
' REDIS_PORT: ${REDIS_PORT}',
|
|
80
|
+
' REDIS_PASSWORD: ${REDIS_PASSWORD}',
|
|
201
81
|
' REDIS_DB: "0"',
|
|
202
|
-
` REDIS_PASSWORD: "${redisPass}"`,
|
|
203
82
|
' REDIS_KEY_PREFIX: ${REDIS_KEY_PREFIX}',
|
|
204
|
-
'
|
|
205
|
-
'
|
|
206
|
-
'
|
|
207
|
-
'
|
|
208
|
-
'
|
|
209
|
-
' LOG_TO_CONSOLE: "true"',
|
|
210
|
-
' LOG_CONSOLE_JSON: "true"',
|
|
211
|
-
' LOG_UVICORN_ACCESS: "true"',
|
|
212
|
-
' LOG_RUNTIME_HEARTBEAT_INTERVAL_SECONDS: "300"',
|
|
213
|
-
' UPLOAD_PROVIDER: local',
|
|
214
|
-
' UPLOAD_MAX_SIZE: "10485760"',
|
|
83
|
+
' MILVUS_ADDRESS: ${MILVUS_ADDRESS}',
|
|
84
|
+
' MILVUS_USERNAME: ${MILVUS_USERNAME}',
|
|
85
|
+
' MILVUS_PASSWORD: ${MILVUS_PASSWORD}',
|
|
86
|
+
' MILVUS_DATABASE: ${MILVUS_DATABASE}',
|
|
87
|
+
' MILVUS_COLLECTION_PREFIX: ${MILVUS_COLLECTION_PREFIX}',
|
|
215
88
|
' LOCAL_UPLOAD_DIR: /app/backend/storage/uploads',
|
|
216
|
-
' UPLOAD_URL_PREFIX: /uploads',
|
|
217
89
|
' volumes:',
|
|
218
90
|
' - ./data/uploads:/app/backend/storage/uploads',
|
|
219
91
|
' - ./data/logs:/app/backend/logs',
|
|
220
92
|
' - ./data/db:/app/backend/db',
|
|
221
93
|
' extra_hosts:',
|
|
222
94
|
' - "host.docker.internal:host-gateway"',
|
|
223
|
-
];
|
|
224
|
-
if (dependsOn.length > 0) {
|
|
225
|
-
appLines.push(' depends_on:');
|
|
226
|
-
for (const [name, cond] of dependsOn) {
|
|
227
|
-
appLines.push(` ${name}:`);
|
|
228
|
-
appLines.push(` condition: ${cond}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
services.app = appLines.join('\n');
|
|
232
|
-
|
|
233
|
-
const order = ['app', 'mysql', 'redis'].filter((k) => services[k]);
|
|
234
|
-
const yaml = [
|
|
235
|
-
'# Auto-generated by draftgo-cli (local-dev wizard).',
|
|
236
|
-
'# Secrets live in the sibling .env file. Re-running the wizard preserves them.',
|
|
237
|
-
`name: ${projectName}`,
|
|
238
|
-
'services:',
|
|
239
|
-
...order.map((k) => services[k]),
|
|
240
95
|
'',
|
|
241
96
|
].join('\n');
|
|
242
|
-
|
|
243
97
|
writeText(composePath(projectDir), yaml);
|
|
244
98
|
writeEnv(projectDir, env);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
for (const sub of ['data/uploads', 'data/logs', 'data/db', 'data/mysql', 'data/redis']) {
|
|
248
|
-
try { fs.mkdirSync(path.join(root, sub), { recursive: true }); } catch {}
|
|
99
|
+
for (const subdir of ['data/uploads', 'data/logs', 'data/db']) {
|
|
100
|
+
try { fs.mkdirSync(path.join(root, subdir), { recursive: true }); } catch {}
|
|
249
101
|
}
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
dir: root,
|
|
253
|
-
composeFile: composePath(projectDir),
|
|
254
|
-
envFile: envPath(projectDir),
|
|
255
|
-
projectName,
|
|
256
|
-
env,
|
|
257
|
-
};
|
|
102
|
+
return { dir: root, composeFile: composePath(projectDir), envFile: envPath(projectDir), projectName, env };
|
|
258
103
|
}
|
|
259
104
|
|
|
260
105
|
module.exports = {
|
|
261
106
|
stackDir, composePath, envPath,
|
|
262
|
-
generate, sanitizeProjectName, defaultRedisKeyPrefix,
|
|
263
|
-
randomAlnum, randomHex,
|
|
107
|
+
generate, sanitizeProjectName, defaultRedisKeyPrefix, randomHex,
|
|
264
108
|
};
|