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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -139
  3. package/package.json +10 -2
  4. package/resources/skill/SKILL.md +61 -184
  5. package/resources/skill/init/SKILL.md +18 -66
  6. package/resources/skill/manifest.json +27 -0
  7. package/resources/skill/pull/SKILL.md +18 -52
  8. package/resources/skill/push/SKILL.md +30 -282
  9. package/resources/skill/references/aihub.md +86 -0
  10. package/resources/skill/{quickref → references}/api-endpoints.md +39 -13
  11. package/resources/skill/references/api.json +20248 -0
  12. package/resources/skill/{quickref → references}/app-api.md +40 -0
  13. package/resources/skill/{core → references}/architecture.md +2 -2
  14. package/resources/skill/references/chat-sdk.md +201 -0
  15. package/resources/skill/references/custom-services.md +308 -0
  16. package/resources/skill/{specs → references}/data.md +5 -5
  17. package/resources/skill/{rules → references}/frontend.md +41 -11
  18. package/resources/skill/{core → references}/modules.md +7 -5
  19. package/resources/skill/references/parallel.md +48 -0
  20. package/resources/skill/{specs → references}/runtime.md +1 -1
  21. package/resources/skill/scripts/draftgo_push.py +80 -12
  22. package/resources/skill/story/SKILL.md +11 -16
  23. package/src/cli.js +13 -7
  24. package/src/commandRegistry.js +34 -0
  25. package/src/commands/api.js +153 -8
  26. package/src/commands/help.js +24 -29
  27. package/src/commands/init.js +17 -18
  28. package/src/commands/local.js +9 -3
  29. package/src/commands/sync.js +1 -1
  30. package/src/commands/update.js +40 -12
  31. package/src/index.js +13 -57
  32. package/src/localdev/compose.js +44 -200
  33. package/src/localdev/index.js +116 -216
  34. package/src/localdev/mysqlClient.js +12 -9
  35. package/src/localdev/services.js +163 -0
  36. package/src/projectConfig.js +1 -1
  37. package/src/projectMap.js +17 -80
  38. package/src/skill.js +1 -1
  39. package/src/updateCheck.js +2 -12
  40. package/resources/skill/practices/anti-patterns.md +0 -80
  41. package/resources/skill/practices/best-practices.md +0 -60
  42. package/resources/skill/practices/dev-declaration.md +0 -114
  43. package/resources/skill/quickref/api.json +0 -17784
  44. package/resources/skill/rules/dev-workflow.md +0 -749
  45. package/resources/skill/rules/parallel.md +0 -263
  46. package/resources/skill/scripts/__pycache__/draftgo_pull.cpython-312.pyc +0 -0
  47. package/resources/skill/scripts/__pycache__/draftgo_push.cpython-312.pyc +0 -0
  48. package/resources/skill/specs/custom-services.md +0 -199
  49. package/src/commands/doctor.js +0 -54
  50. package/src/commands/new.js +0 -186
  51. package/src/commands/projectScript.js +0 -37
  52. package/src/commands/upgrade.js +0 -52
  53. /package/resources/skill/{specs → references}/db-relations.md +0 -0
  54. /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
  55. /package/resources/skill/{specs → references}/security.md +0 -0
  56. /package/resources/skill/{specs → references}/ui-protocol.md +0 -0
@@ -4,7 +4,138 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const log = require('../logger');
6
6
 
7
- const API_FILE = path.resolve(__dirname, '..', '..', 'resources', 'skill', 'quickref', 'api.json');
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
+ });
64
+ return;
65
+ }
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.');
92
+ return;
93
+ }
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
+ }
138
+ }
8
139
 
9
140
  function apiCommand(positional, flags = {}) {
10
141
  const keyword = String(positional.join(' ') || '').trim().toLowerCase();
@@ -26,7 +157,7 @@ function apiCommand(positional, flags = {}) {
26
157
  const matches = [];
27
158
  for (const [route, methods] of Object.entries(spec.paths || {})) {
28
159
  for (const [method, operation] of Object.entries(methods || {})) {
29
- if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method.toLowerCase())) continue;
160
+ if (!HTTP_METHODS.has(method.toLowerCase())) continue;
30
161
  const haystack = [route, method, operation.summary, operation.operationId, ...(operation.tags || [])]
31
162
  .filter(Boolean).join(' ').toLowerCase();
32
163
  if (!words.every((word) => haystack.includes(word))) continue;
@@ -36,23 +167,37 @@ function apiCommand(positional, flags = {}) {
36
167
  summary: operation.summary || '',
37
168
  operationId: operation.operationId || '',
38
169
  tags: operation.tags || [],
170
+ operation,
39
171
  });
40
172
  }
41
173
  }
42
174
 
43
175
  if (flags.output === 'json') {
44
- console.log(JSON.stringify(matches, null, 2));
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));
45
190
  return matches.length ? 0 : 1;
46
191
  }
47
192
  if (!matches.length) {
48
193
  log.warn(`未找到包含“${keyword}”的 API。`);
49
194
  return 1;
50
195
  }
51
- log.title(`draftgo api — ${keyword}`);
52
- for (const item of matches.slice(0, 50)) {
53
- console.log(` ${item.method.padEnd(7)} ${item.route}${item.summary ? ` ${item.summary}` : ''}`);
54
- }
55
- if (matches.length > 50) log.dim(` 仅显示前 50 条,共 ${matches.length} 条;使用 --output json 获取完整结果。`);
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.`);
56
201
  return 0;
57
202
  }
58
203
 
@@ -14,32 +14,25 @@ The CLI is the workbench layer for local runtime, resource sync, checks, and pus
14
14
  Usage:
15
15
  draftgo init [<target>...] Install skill. No target = auto-detect.
16
16
  Use "all" to install every target.
17
- draftgo update [<target>...] Atomically update skill + entry files.
18
- Reports newer CLI versions without
19
- modifying the global installation.
17
+ draftgo update [<target>...] Fully update the global CLI, then atomically
18
+ refresh skill + entry files.
20
19
  draftgo uninstall <target|all> Remove entry files for target(s).
21
20
  Use explicit "all" to remove every target.
22
21
  --purge also removes .draftgo/ (runtime data).
23
22
  draftgo status Show installed targets and skill version.
24
- draftgo doctor Diagnose environment (python, targets,
25
- CLI freshness).
26
- draftgo upgrade Explicitly upgrade the global CLI, then
27
- refresh installed skill files.
28
23
  draftgo map Print a local DraftGo project resource map
29
24
  for fast AI orientation.
30
- draftgo check Check local resource closure: routes,
31
- entry binding, files, mock/color risks.
25
+ draftgo check Check page entry binding, page/navigation
26
+ HTML parsing, and unregistered local files.
32
27
  draftgo verify-ui <url> Run a deterministic browser smoke check.
33
28
  Uses one viewport and screenshots on failure.
34
- draftgo api <keyword> Search the bundled OpenAPI specification.
35
- draftgo dev Run this project's npm dev script.
36
- draftgo build Run this project's npm build script.
37
- draftgo new <type> <slug> Scaffold a new resource locally (no network).
38
- Creates template file + index.json entry.
39
- Types: page | nav | db_meta | script | doc
29
+ draftgo api <keyword> Search DraftGo base APIs and show request/
30
+ response/authentication contracts.
31
+ Custom service routes come from app.Route;
32
+ they are not part of the bundled OpenAPI.
40
33
  draftgo delete <type> <id> Delete resource from server + local index.
41
34
  Types: pages | nav | db_meta | custom_scripts
42
- | docs | doc_categories
35
+ | docs | doc_categories | aihub
43
36
  Alias: del, rm. Flags: --yes / -y skip confirm.
44
37
  draftgo pull [type] [id...] Pull DraftGo resources via the bundled
45
38
  sync script. Defaults to --all.
@@ -50,24 +43,27 @@ Usage:
50
43
  draftgo auto-push [type] [id...] Run check, then push only when
51
44
  config.auto_push is true.
52
45
  Supports --batch <type> <ids> ... too.
53
- draftgo local up|down|logs|status
46
+ draftgo local setup|start|stop|logs|status
54
47
  Manage .draftgo/docker/docker-compose.yaml
55
- generated by draftgo local-dev.
48
+ generated by local setup. up/down remain
49
+ compatible aliases for start/stop.
56
50
  draftgo list-targets List supported AI tools.
57
51
  draftgo connect Bind this project to an existing DraftGo
58
52
  server. Prompts for BaseURL + access
59
53
  token (or pass --server / --token).
60
- draftgo local-dev One-click local DraftGo stack via Docker
61
- (MySQL + Redis + app). Generates
62
- <project>/.draftgo/docker/docker-compose.yaml
63
- with random secrets, then guides you to
64
- finish setup and paste an access token.
54
+ draftgo local setup One-click local DraftGo stack via Docker
55
+ (shared MySQL, Redis, Milvus + app). It
56
+ generates an app-only project compose,
57
+ <project>/.draftgo/docker/docker-compose.yaml,
58
+ then guides you to finish setup and paste
59
+ an access token.
60
+ draftgo local-dev Compatibility alias for \`draftgo local setup\`.
65
61
  draftgo -v | --version Print CLI version.
66
62
  draftgo -h | --help Show this help.
67
63
 
68
64
  v3 Workbench:
69
- draftgo local up|down|logs Local stack lifecycle commands.
70
- draftgo dev|build|check Project workflow gates.
65
+ draftgo local start|stop|logs Local stack lifecycle commands.
66
+ draftgo check Local resource closure gate.
71
67
  draftgo pull|push First-class resource sync wrappers.
72
68
 
73
69
  Flags:
@@ -77,7 +73,7 @@ Flags:
77
73
  --skip-update-check Do not contact npm to check for a newer CLI.
78
74
  --yes Assume "yes" for interactive prompts.
79
75
  --connect (init) Skip the prompt and run \`draftgo connect\`.
80
- --local-dev (init) Skip the prompt and run \`draftgo local-dev\`.
76
+ --local-dev (init) Skip the prompt and run \`draftgo local setup\`.
81
77
  --no-setup (init) Don't offer either flow after installing.
82
78
  --server <url> (connect) Provide the DraftGo BaseURL non-interactively.
83
79
  --token <sat> (connect) Provide the access token non-interactively.
@@ -101,10 +97,9 @@ Examples:
101
97
  draftgo init claudecode # install for Claude Code only
102
98
  draftgo init claudecode kiro # install for both
103
99
  draftgo init all # install for every supported target
104
- draftgo update # refresh skill and report newer CLI versions
105
- draftgo upgrade # explicitly upgrade CLI and refresh skill
100
+ draftgo update # fully update CLI and installed skills
106
101
  draftgo map # inspect pages/nav/db/scripts before development
107
- draftgo check --strict # fail on closure warnings before push
102
+ draftgo check --strict # fail on any resource-check warning before push
108
103
  draftgo verify-ui http://localhost:5173 --mobile-check auto
109
104
  draftgo uninstall all --purge # full removal incl. runtime data
110
105
  `);
@@ -12,8 +12,8 @@ const { ask } = require('../prompt');
12
12
  function warnIfNoPython() {
13
13
  const py = findPython();
14
14
  if (!py) {
15
- log.warn('Python 未检测到。DraftGo init/sync 脚本需要 Python 3.9+。');
16
- log.dim(' 请安装 Python 3.9+ 后再运行 /draftgo init 子技能。');
15
+ log.warn('Python 未检测到。DraftGo 资源同步需要 Python 3.9+。');
16
+ log.dim(' 安装 Python 3.9+ 后运行 `draftgo pull` 获取项目资源。');
17
17
  } else {
18
18
  log.dim(` detected ${py.bin} (${py.version})`);
19
19
  }
@@ -47,7 +47,7 @@ async function init(projectDir, positional, flags) {
47
47
 
48
48
  // 2) Render skill body into each target's own directory (no shared
49
49
  // .draftgo/skill/ indirection anymore — each AI tool gets a real copy).
50
- log.step('写入各 AI 工具 skill 目录(含 SKILL.md / 子技能 / rules / scripts)');
50
+ log.step('写入各 AI 工具 skill 目录(含 SKILL.md / 子技能 / references / scripts)');
51
51
  ensureRuntime(projectDir);
52
52
  const failures = [];
53
53
  let installedCount = 0;
@@ -101,29 +101,28 @@ async function init(projectDir, positional, flags) {
101
101
  return await require('./localDev')(projectDir, [], flags);
102
102
  }
103
103
  if (interactive) {
104
- log.title('未检测到 DraftGo 服务器配置');
105
- log.plain('选择一种方式完成绑定:');
106
- log.plain(` ${log.c.cyan('[1]')} 连接已有 DraftGo 服务器 ${log.c.gray('(输入 BaseURL + 系统访问令牌)')}`);
107
- log.plain(` ${log.c.cyan('[2]')} 进入本地开发模式 ${log.c.gray('(没部署基座,用 Docker 一键启动)')}`);
108
- log.plain(` ${log.c.cyan('[3]')} 稍后再说`);
109
- const choice = (await ask('请选择 [1/2/3]', { default: '1' })).trim();
110
- if (choice === '1') {
104
+ log.title('选择 DraftGo 基座处理方式');
105
+ log.plain(` ${log.c.cyan('[A]')} 连接基座服务`);
106
+ log.dim(' 使用已有 DraftGo 服务,接下来输入 BaseURL 和系统访问令牌。');
107
+ log.plain(` ${log.c.cyan('[B]')} 本地启动基座`);
108
+ log.dim(' 使用 Docker 启动 DraftGo;按需准备共享的 MySQL、Redis 和 Milvus。');
109
+ log.plain(` ${log.c.cyan('[C]')} 稍后自行处理`);
110
+ log.dim(' 仅安装 Skill,不写入服务配置,也不创建容器。');
111
+ const choice = (await ask('请选择 [A/B/C]', { default: 'C' })).trim().toUpperCase();
112
+ if (choice === 'A') {
111
113
  return await require('./connect')(projectDir, [], flags);
112
114
  }
113
- if (choice === '2') {
114
- return await require('./localDev')(projectDir, [], flags);
115
+ if (choice === 'B') {
116
+ return await require('./localDev')(projectDir, [], { ...flags, yes: true });
115
117
  }
116
- if (choice !== '3') {
117
- log.warn('Invalid choice; skipped server setup. Run `draftgo connect` or `draftgo local-dev` later.');
118
+ if (choice !== 'C') {
119
+ log.warn('无效选择,已跳过基座处理。');
118
120
  }
119
121
  }
120
122
  }
121
123
 
122
124
  log.title('完成');
123
- log.plain('下一步:在你的 AI 工具里说「初始化 draftgo 项目」,它会引导你配置服务器和 token。');
124
- log.dim('或直接运行:');
125
- log.dim(' draftgo connect # 连接已有 DraftGo 服务器');
126
- log.dim(' draftgo local-dev # 本地一键部署 DraftGo');
125
+ log.plain(hasConfig ? 'Skill 已安装,项目已连接 DraftGo。' : 'Skill 已安装。');
127
126
  return 0;
128
127
  }
129
128
 
@@ -5,6 +5,7 @@ const { spawnSync } = require('child_process');
5
5
  const log = require('../logger');
6
6
  const { exists } = require('../fsx');
7
7
  const { detectDocker } = require('../localdev/detect');
8
+ const { runWizard } = require('../localdev');
8
9
 
9
10
  function composeFile(projectDir) {
10
11
  return path.join(projectDir, '.draftgo', 'docker', 'docker-compose.yaml');
@@ -22,7 +23,7 @@ function runCompose(projectDir, args, stdio = 'inherit') {
22
23
  const file = composeFile(projectDir);
23
24
  if (!exists(file)) {
24
25
  log.err('未找到 .draftgo/docker/docker-compose.yaml。');
25
- log.dim(' 请先运行 `draftgo local-dev` 生成本地 DraftGo stack。');
26
+ log.dim(' 请先运行 `draftgo local setup` 生成本地 DraftGo stack。');
26
27
  return 1;
27
28
  }
28
29
 
@@ -34,13 +35,17 @@ function runCompose(projectDir, args, stdio = 'inherit') {
34
35
  return r.status || 0;
35
36
  }
36
37
 
37
- function local(projectDir, positional) {
38
+ function local(projectDir, positional, flags = {}) {
38
39
  const action = positional[0] || 'status';
39
40
 
40
41
  switch (action) {
42
+ case 'setup':
43
+ return runWizard(projectDir, { yes: !!(flags.yes || flags.y) });
41
44
  case 'up':
45
+ case 'start':
42
46
  return runCompose(projectDir, ['up', '-d']);
43
47
  case 'down':
48
+ case 'stop':
44
49
  return runCompose(projectDir, ['down']);
45
50
  case 'logs':
46
51
  return runCompose(projectDir, ['logs', ...(positional.slice(1).length ? positional.slice(1) : ['-f', 'app'])]);
@@ -49,7 +54,8 @@ function local(projectDir, positional) {
49
54
  return runCompose(projectDir, ['ps']);
50
55
  default:
51
56
  log.err(`未知 local 子命令:${action}`);
52
- log.dim(' 可用:draftgo local up | down | logs | status');
57
+ log.dim(' 可用:draftgo local setup | start | stop | logs | status');
58
+ log.dim(' 兼容别名:up | down');
53
59
  return 1;
54
60
  }
55
61
  }
@@ -35,7 +35,7 @@ function sync(projectDir, command, positional, flags = {}) {
35
35
 
36
36
  if (!exists(path.join(projectDir, '.draftgo', 'config.json'))) {
37
37
  log.err('未找到 .draftgo/config.json。');
38
- log.dim(' 请先运行 `draftgo connect` 或 `/draftgo init`。');
38
+ log.dim(' 请先运行 `draftgo connect`。');
39
39
  return 1;
40
40
  }
41
41
 
@@ -1,35 +1,63 @@
1
1
  'use strict';
2
2
 
3
+ const { spawnSync } = require('child_process');
3
4
  const log = require('../logger');
4
5
  const { all, byName, resolveTargets } = require('../installers');
5
6
  const { ensureRuntime, writeInstalledVersion, readInstalledVersion, getPackageVersion } = require('../skill');
6
7
  const { fetchLatestVersion, cmpSemver } = require('../updateCheck');
7
8
  const { detectTargets } = require('../detect');
8
9
 
9
- async function reportAvailableUpgrade(flags) {
10
- if (flags['skip-update-check'] || process.env.DRAFTGO_NO_UPDATE_CHECK === '1') return;
10
+ const REENTRY_FLAG = 'DRAFTGO_UPDATE_REENTERED';
11
+
12
+ async function updateCliIfNeeded(projectDir, positional, flags) {
13
+ if (process.env[REENTRY_FLAG] === '1' || flags['skip-update-check'] || process.env.DRAFTGO_NO_UPDATE_CHECK === '1') {
14
+ return null;
15
+ }
11
16
  const current = getPackageVersion();
12
- const latest = await fetchLatestVersion();
17
+ const latest = await fetchLatestVersion(5000, { force: true });
13
18
  if (!latest) {
14
- log.dim(' (查询最新版失败,按当前 CLI 继续)');
15
- return;
19
+ log.warn('无法查询 npm 最新版本;将用当前 CLI 刷新 skill。');
20
+ return null;
16
21
  }
17
- if (cmpSemver(latest, current) > 0) {
18
- log.warn(`draftgo-cli 有新版:${current} → ${latest}`);
19
- log.dim(' 运行 `draftgo upgrade` 可显式升级;本次继续使用当前版本刷新 skill。');
20
- } else {
22
+ if (cmpSemver(latest, current) <= 0) {
21
23
  log.dim(` CLI 已是最新(${current})`);
24
+ return null;
25
+ }
26
+
27
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
28
+ log.step(`更新 draftgo-cli:${current} → ${latest}`);
29
+ const install = spawnSync(npm, ['install', '-g', `draftgo-cli@${latest}`], { stdio: 'inherit', shell: false });
30
+ if (install.error) {
31
+ log.err(`CLI 更新启动失败:${install.error.message}`);
32
+ return 1;
22
33
  }
34
+ if (install.status !== 0) {
35
+ log.err(`npm install 返回码 ${install.status}`);
36
+ return Number.isInteger(install.status) ? install.status : 1;
37
+ }
38
+
39
+ const command = process.platform === 'win32' ? 'draftgo.cmd' : 'draftgo';
40
+ const rerun = spawnSync(command, ['update', ...positional, '--project', projectDir, '--skip-update-check'], {
41
+ stdio: 'inherit',
42
+ shell: false,
43
+ env: { ...process.env, [REENTRY_FLAG]: '1' },
44
+ });
45
+ if (rerun.error) {
46
+ log.err(`新版 CLI 刷新 skill 失败:${rerun.error.message}`);
47
+ return 1;
48
+ }
49
+ return Number.isInteger(rerun.status) ? rerun.status : 1;
23
50
  }
24
51
 
25
52
  async function update(projectDir, positional, flags) {
26
53
  log.title('draftgo update');
27
54
 
55
+ const reentryCode = await updateCliIfNeeded(projectDir, positional, flags);
56
+ if (reentryCode !== null) return reentryCode;
57
+
28
58
  const prev = readInstalledVersion(projectDir);
29
59
  log.info(`当前已装 skill:${prev || '未安装'},CLI 版本:${getPackageVersion()}`);
30
60
 
31
- await reportAvailableUpgrade(flags);
32
-
33
61
  // Determine which entry files to refresh:
34
62
  // - no positional → refresh installed targets plus project-level tool signals
35
63
  // - "all" → refresh every known target
@@ -84,4 +112,4 @@ async function update(projectDir, positional, flags) {
84
112
  }
85
113
 
86
114
  module.exports = update;
87
- module.exports.reportAvailableUpgrade = reportAvailableUpgrade;
115
+ module.exports.updateCliIfNeeded = updateCliIfNeeded;
package/src/index.js CHANGED
@@ -2,10 +2,17 @@
2
2
 
3
3
  const path = require('path');
4
4
  const { parse } = require('./cli');
5
+ const { resolveCommand } = require('./commandRegistry');
5
6
  const log = require('./logger');
6
7
 
7
8
  async function run(argv) {
8
- const { command, positional, flags } = parse(argv);
9
+ const { command, positional, flags, errors } = parse(argv);
10
+
11
+ if (errors.length) {
12
+ for (const error of errors) log.err(error);
13
+ require('./commands/help')();
14
+ return 1;
15
+ }
9
16
 
10
17
  // --version / --help shortcuts
11
18
  if (flags.version || flags.v || command === 'version') {
@@ -22,62 +29,11 @@ async function run(argv) {
22
29
  : process.cwd();
23
30
 
24
31
  try {
25
- switch (command) {
26
- case 'init':
27
- return await require('./commands/init')(projectDir, positional, flags);
28
- case 'update':
29
- return await require('./commands/update')(projectDir, positional, flags);
30
- case 'uninstall':
31
- case 'remove':
32
- return await require('./commands/uninstall')(projectDir, positional, flags);
33
- case 'status':
34
- return require('./commands/status')(projectDir);
35
- case 'doctor':
36
- return await require('./commands/doctor')(projectDir, flags);
37
- case 'upgrade':
38
- return await require('./commands/upgrade')(projectDir, positional, flags);
39
- case 'map':
40
- return require('./commands/map')(projectDir, flags);
41
- case 'check':
42
- return require('./commands/check')(projectDir, flags);
43
- case 'verify-ui':
44
- case 'verifyui':
45
- return await require('./commands/verifyUi')(projectDir, positional, flags);
46
- case 'api':
47
- return require('./commands/api')(positional, flags);
48
- case 'dev':
49
- case 'build':
50
- return require('./commands/projectScript')(projectDir, command, flags);
51
- case 'new':
52
- return require('./commands/new')(projectDir, positional, flags);
53
- case 'delete':
54
- case 'del':
55
- case 'rm':
56
- return await require('./commands/delete')(projectDir, positional, flags);
57
- case 'deploy':
58
- return require('./commands/deploy')(projectDir, positional, flags);
59
- case 'auto-push':
60
- case 'autopush':
61
- return require('./commands/autoPush')(projectDir, positional, flags);
62
- case 'pull':
63
- case 'push':
64
- return require('./commands/sync')(projectDir, command, positional, flags);
65
- case 'local':
66
- return require('./commands/local')(projectDir, positional, flags);
67
- case 'list-targets':
68
- case 'targets':
69
- return require('./commands/listTargets')();
70
- case 'local-dev':
71
- case 'localdev':
72
- return await require('./commands/localDev')(projectDir, positional, flags);
73
- case 'connect':
74
- case 'login':
75
- return await require('./commands/connect')(projectDir, positional, flags);
76
- default:
77
- log.err(`未知命令:${command}`);
78
- require('./commands/help')();
79
- return 1;
80
- }
32
+ const definition = resolveCommand(command);
33
+ if (definition) return await definition.run(projectDir, positional, flags);
34
+ log.err(`未知命令:${command}`);
35
+ require('./commands/help')();
36
+ return 1;
81
37
  } catch (e) {
82
38
  log.err(e.message || String(e));
83
39
  if (process.env.DEBUG) console.error(e.stack);