draftgo-cli 3.0.29 → 3.0.35

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 +41 -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 +34 -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 +138 -32
  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
@@ -1,16 +1,11 @@
1
1
  ---
2
2
  name: draftgo-story
3
- description: "Use this skill to build, load, evolve, or conflict-check the project's Story file (.draftgo/story.yaml). Required for high-risk development tasks when story.yaml is missing, and used whenever a development decision conflicts with the existing Story."
4
- version: 1.0.0
5
- allowed-tools: Read, Write, Glob, Bash(find:*)
3
+ description: "Use this skill to build, load, evolve, or conflict-check the project's Story file (.draftgo/story.yaml). Use it when the user requests product-story work, when an existing Story conflicts with a requested product decision, or when preserving a durable product decision is valuable."
6
4
  ---
7
5
 
8
6
  # DraftGo Story System
9
7
 
10
- > **Story 是系统的灵魂档案。高风险开发必须先确认 Story 存在;功能开发若 Story 已存在则静默加载,小修不强制触发 Story。**
11
- >
12
- > Story 不是文档,不是规范,是创作者对"这个系统是什么、为什么存在、不做什么"的持续叙述。
13
- > 文件只是理解的副产品。
8
+ > Story 记录产品身份、设计总述、长期决策和当前方向。项目已有 Story 且请求触及定位或既有决策时加载;用户要求或形成需要长期保留的产品决策时创建或更新。
14
9
 
15
10
  ---
16
11
 
@@ -18,11 +13,11 @@ allowed-tools: Read, Write, Glob, Bash(find:*)
18
13
 
19
14
  | 原则 | 含义 |
20
15
  |------|------|
21
- | Gate, not Doc | 高风险任务没有 Story 就不开发;普通功能把 Story 作为上下文而不是阻塞器 |
16
+ | Decision record | 记录会影响后续取舍的产品定位与决策 |
22
17
  | AI 维护,人确认 | 开发者只聊天,AI 负责提炼和写入 |
23
18
  | 三层结构 | 身份(稳) → 决策(只增) → 当前(频变) |
24
19
  | 决策日志最值钱 | 取舍依据是代码永远表达不了的信息 |
25
- | 工具无关 | yaml 文件谁都能读,不绑定任何 AI 生态 |
20
+ | 工具无关 | 使用可移植的 YAML 文件 |
26
21
 
27
22
  ---
28
23
 
@@ -43,7 +38,7 @@ identity:
43
38
  what: "一句话讲清这是什么,不超过 30 字"
44
39
  why: |
45
40
  解决什么具体痛点,给谁用。
46
- 两三句话,要有画面感,不要空话。
41
+ 用两三句具体描述说明使用者和场景。
47
42
  not:
48
43
  - "不是 XX"
49
44
  - "不变成 YY"
@@ -71,7 +66,7 @@ decisions:
71
66
  # === 第四层:当前方向(频繁更新)===
72
67
  now:
73
68
  focus: "当前阶段在做什么"
74
- next: "下一步大致方向(粗粒度,不是 TODO)"
69
+ next: "下一步的大致方向"
75
70
  open_questions:
76
71
  - "还没想清楚的事"
77
72
  ```
@@ -81,12 +76,12 @@ now:
81
76
  ## 触发协议
82
77
 
83
78
  ```
84
- 任何开发对话开始时:
79
+ 需要 Story 时:
85
80
 
86
81
  1. 静默检查 .draftgo/story.yaml
87
82
  2. 分流:
88
- ├─ 不存在 → 进入「构建流程」,暂存开发请求
89
- ├─ maturity = seed → 加载 + 提示"Story 还稚嫩,开发中会持续补充"
83
+ ├─ 不存在 → 用户要求创建或需要沉淀关键决策时,进入「构建流程」
84
+ ├─ maturity = seed → 加载并把不确定项作为后续判断背景
90
85
  └─ growing / stable → 静默加载,直接开始
91
86
  3. 整个对话期间,Story 作为最高优先级上下文锚定
92
87
  ```
@@ -141,9 +136,9 @@ now:
141
136
  ```
142
137
  输出:[ 检测到全新项目,先聊一下你想做什么 ]
143
138
 
144
- 对话式采集(不是问卷,是聊天,问到够用就停):
139
+ 通过简短对话采集,信息足够后即进入提炼:
145
140
 
146
- Q1: 这个系统整体是干嘛的?(存在的理由,不是功能列表)
141
+ Q1: 这个系统的整体定位和存在理由是什么?
147
142
  Q2: 谁会用它?什么场景下用?
148
143
  Q3: 类似的东西可能有哪些?你想做出什么不同?
149
144
  Q4: 大致有哪些模块/功能块?它们之间怎么串起来?
package/src/cli.js CHANGED
@@ -24,7 +24,7 @@ const VALUE_FLAGS = new Set([
24
24
 
25
25
  function parse(argv) {
26
26
  const args = argv.slice();
27
- const out = { command: null, positional: [], flags: {} };
27
+ const out = { command: null, positional: [], flags: {}, errors: [] };
28
28
  while (args.length) {
29
29
  const a = args.shift();
30
30
  if (a === '--') { out.positional.push(...args); break; }
@@ -32,16 +32,22 @@ function parse(argv) {
32
32
  const raw = a.slice(2);
33
33
  const eq = raw.indexOf('=');
34
34
  if (eq >= 0) {
35
- out.flags[raw.slice(0, eq)] = raw.slice(eq + 1);
35
+ const key = raw.slice(0, eq);
36
+ const value = raw.slice(eq + 1);
37
+ if (VALUE_FLAGS.has(key) && !value) {
38
+ out.errors.push(`选项 --${key} 需要一个值。`);
39
+ } else {
40
+ out.flags[key] = value;
41
+ }
36
42
  } else {
37
43
  const next = args[0];
38
- if (next !== undefined && !next.startsWith('-')) {
39
- // Only consume next token as value for a known set of flags.
40
- if (VALUE_FLAGS.has(raw)) {
44
+ if (VALUE_FLAGS.has(raw)) {
45
+ // A declared value flag must never silently become boolean true.
46
+ if (next === undefined || next === '--' || next.startsWith('--')) {
47
+ out.errors.push(`选项 --${raw} 需要一个值。`);
48
+ } else {
41
49
  out.flags[raw] = next;
42
50
  args.shift();
43
- } else {
44
- out.flags[raw] = true;
45
51
  }
46
52
  } else {
47
53
  out.flags[raw] = true;
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ // Keep command names, aliases, and dispatch behavior in one place. This avoids
4
+ // aliases silently drifting away from the implementation as the CLI grows.
5
+ const commands = [
6
+ { name: 'init', run: (dir, args, flags) => require('./commands/init')(dir, args, flags) },
7
+ { name: 'update', run: (dir, args, flags) => require('./commands/update')(dir, args, flags) },
8
+ { name: 'uninstall', aliases: ['remove'], run: (dir, args, flags) => require('./commands/uninstall')(dir, args, flags) },
9
+ { name: 'status', run: (dir) => require('./commands/status')(dir) },
10
+ { name: 'map', run: (dir, _args, flags) => require('./commands/map')(dir, flags) },
11
+ { name: 'check', run: (dir, _args, flags) => require('./commands/check')(dir, flags) },
12
+ { name: 'verify-ui', aliases: ['verifyui'], run: (dir, args, flags) => require('./commands/verifyUi')(dir, args, flags) },
13
+ { name: 'api', run: (_dir, args, flags) => require('./commands/api')(args, flags) },
14
+ { name: 'delete', aliases: ['del', 'rm'], run: (dir, args, flags) => require('./commands/delete')(dir, args, flags) },
15
+ { name: 'deploy', run: (dir, args, flags) => require('./commands/deploy')(dir, args, flags) },
16
+ { name: 'auto-push', aliases: ['autopush'], run: (dir, args, flags) => require('./commands/autoPush')(dir, args, flags) },
17
+ { name: 'pull', run: (dir, args, flags) => require('./commands/sync')(dir, 'pull', args, flags) },
18
+ { name: 'push', run: (dir, args, flags) => require('./commands/sync')(dir, 'push', args, flags) },
19
+ { name: 'local', run: (dir, args, flags) => require('./commands/local')(dir, args, flags) },
20
+ { name: 'list-targets', aliases: ['targets'], run: () => require('./commands/listTargets')() },
21
+ { name: 'local-dev', aliases: ['localdev'], run: (dir, args, flags) => require('./commands/localDev')(dir, args, flags) },
22
+ { name: 'connect', aliases: ['login'], run: (dir, args, flags) => require('./commands/connect')(dir, args, flags) },
23
+ ];
24
+
25
+ const aliases = new Map();
26
+ for (const command of commands) {
27
+ for (const alias of [command.name, ...(command.aliases || [])]) aliases.set(alias, command);
28
+ }
29
+
30
+ function resolveCommand(name) {
31
+ return aliases.get(name);
32
+ }
33
+
34
+ module.exports = { commands, resolveCommand };
@@ -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;