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.
- package/LICENSE +21 -0
- package/README.md +41 -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 +34 -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 +138 -32
- 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
|
@@ -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).
|
|
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
|
-
>
|
|
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
|
-
|
|
|
16
|
+
| Decision record | 记录会影响后续取舍的产品定位与决策 |
|
|
22
17
|
| AI 维护,人确认 | 开发者只聊天,AI 负责提炼和写入 |
|
|
23
18
|
| 三层结构 | 身份(稳) → 决策(只增) → 当前(频变) |
|
|
24
19
|
| 决策日志最值钱 | 取舍依据是代码永远表达不了的信息 |
|
|
25
|
-
| 工具无关 |
|
|
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: "
|
|
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 →
|
|
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
|
-
|
|
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 (
|
|
39
|
-
//
|
|
40
|
-
if (
|
|
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 };
|
package/src/commands/api.js
CHANGED
|
@@ -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', '
|
|
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 (!
|
|
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(
|
|
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(`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (matches.length > 50) log.dim(
|
|
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
|
|
package/src/commands/help.js
CHANGED
|
@@ -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>...]
|
|
18
|
-
|
|
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
|
|
31
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
46
|
+
draftgo local setup|start|stop|logs|status
|
|
54
47
|
Manage .draftgo/docker/docker-compose.yaml
|
|
55
|
-
generated by
|
|
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
|
|
61
|
-
(MySQL
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
finish setup and paste
|
|
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
|
|
70
|
-
draftgo
|
|
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
|
|
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 #
|
|
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
|
|
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
|
`);
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
16
|
-
log.dim('
|
|
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 / 子技能 /
|
|
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('
|
|
105
|
-
log.plain('
|
|
106
|
-
log.
|
|
107
|
-
log.plain(` ${log.c.cyan('[
|
|
108
|
-
log.
|
|
109
|
-
|
|
110
|
-
|
|
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 === '
|
|
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 !== '
|
|
117
|
-
log.warn('
|
|
118
|
+
if (choice !== 'C') {
|
|
119
|
+
log.warn('无效选择,已跳过基座处理。');
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
log.title('完成');
|
|
123
|
-
log.plain('
|
|
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
|
|
package/src/commands/local.js
CHANGED
|
@@ -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
|
|
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
|
|
57
|
+
log.dim(' 可用:draftgo local setup | start | stop | logs | status');
|
|
58
|
+
log.dim(' 兼容别名:up | down');
|
|
53
59
|
return 1;
|
|
54
60
|
}
|
|
55
61
|
}
|
package/src/commands/sync.js
CHANGED
|
@@ -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
|
|
38
|
+
log.dim(' 请先运行 `draftgo connect`。');
|
|
39
39
|
return 1;
|
|
40
40
|
}
|
|
41
41
|
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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.
|
|
15
|
-
return;
|
|
19
|
+
log.warn('无法查询 npm 最新版本;将用当前 CLI 刷新 skill。');
|
|
20
|
+
return null;
|
|
16
21
|
}
|
|
17
|
-
if (cmpSemver(latest, current)
|
|
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.
|
|
115
|
+
module.exports.updateCliIfNeeded = updateCliIfNeeded;
|