draftgo-cli 3.0.33 → 3.0.38
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/README.md +220 -269
- package/package.json +6 -2
- package/resources/skill/SKILL.md +114 -55
- package/resources/skill/init/SKILL.md +29 -15
- package/resources/skill/manifest.json +13 -5
- package/resources/skill/push/SKILL.md +41 -29
- package/resources/skill/references/aihub.md +8 -5
- package/resources/skill/references/api-endpoints.md +5 -3
- package/resources/skill/references/architecture.md +1 -1
- package/resources/skill/references/checkout.md +116 -0
- package/resources/skill/references/custom-services.md +9 -10
- package/resources/skill/references/data.md +4 -2
- package/resources/skill/references/frontend.md +99 -23
- package/resources/skill/references/mcp.md +101 -0
- package/resources/skill/references/modules.md +8 -8
- package/resources/skill/references/parallel.md +6 -3
- package/resources/skill/references/runtime.md +7 -10
- package/resources/skill/scripts/README.md +8 -0
- package/resources/skill/story/SKILL.md +8 -8
- package/src/cli.js +5 -0
- package/src/commandRegistry.js +7 -1
- package/src/commands/api.js +24 -187
- package/src/commands/autoPush.js +48 -17
- package/src/commands/check.js +17 -47
- package/src/commands/checkout.js +18 -0
- package/src/commands/commit.js +21 -0
- package/src/commands/conflict.js +30 -0
- package/src/commands/conflicts.js +16 -0
- package/src/commands/connect.js +60 -48
- package/src/commands/delete.js +79 -64
- package/src/commands/deploy.js +18 -10
- package/src/commands/diff.js +23 -0
- package/src/commands/help.js +99 -75
- package/src/commands/init.js +4 -10
- package/src/commands/local.js +23 -6
- package/src/commands/map.js +89 -89
- package/src/commands/mcp.js +126 -0
- package/src/commands/sync.js +28 -43
- package/src/commands/verifyUi.js +3 -2
- package/src/localdev/index.js +37 -7
- package/src/localdev/mysqlClient.js +1 -1
- package/src/mcp/client.js +275 -0
- package/src/mcp/hosts.js +520 -0
- package/src/mcp/protocol.js +173 -0
- package/src/mcp/stdio.js +300 -0
- package/src/mcp/tools.js +37 -0
- package/src/platforms.js +3 -4
- package/src/projectConfig.js +91 -49
- package/src/projectMap.js +123 -460
- package/src/skill.js +6 -28
- package/src/worktree/backend.js +250 -0
- package/src/worktree/errors.js +28 -0
- package/src/worktree/index.js +461 -0
- package/src/worktree/manifest.js +75 -0
- package/src/worktree/streams.js +200 -0
- package/src/worktree/types.js +103 -0
- package/src/worktree/validate.js +37 -0
- package/resources/skill/pull/SKILL.md +0 -33
- package/resources/skill/references/api.json +0 -20248
- package/resources/skill/scripts/draftgo_delete.py +0 -149
- package/resources/skill/scripts/draftgo_init.py +0 -80
- package/resources/skill/scripts/draftgo_pull.py +0 -427
- package/resources/skill/scripts/draftgo_push.py +0 -1022
- package/src/python.js +0 -27
package/src/commands/api.js
CHANGED
|
@@ -1,203 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
3
|
const log = require('../logger');
|
|
4
|
+
const { loadProjectConfig } = require('../projectConfig');
|
|
5
|
+
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
});
|
|
7
|
+
function printResult(title, result) {
|
|
8
|
+
log.title(title);
|
|
9
|
+
if (Array.isArray(result)) {
|
|
10
|
+
for (const item of result) log.plain(` ${JSON.stringify(item)}`);
|
|
64
11
|
return;
|
|
65
12
|
}
|
|
66
|
-
|
|
67
|
-
|
|
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.');
|
|
13
|
+
if (result && Array.isArray(result.items)) {
|
|
14
|
+
for (const item of result.items) log.plain(` ${JSON.stringify(item)}`);
|
|
92
15
|
return;
|
|
93
16
|
}
|
|
94
|
-
console.log(
|
|
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
|
-
}
|
|
17
|
+
console.log(JSON.stringify(result, null, 2));
|
|
138
18
|
}
|
|
139
19
|
|
|
140
|
-
function apiCommand(positional, flags = {}) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
20
|
+
async function apiCommand(projectDir, positional, flags = {}) {
|
|
21
|
+
const first = String(positional[0] || '').toLowerCase();
|
|
22
|
+
const describe = first === 'describe';
|
|
23
|
+
const queryParts = first === 'search' || describe ? positional.slice(1) : positional;
|
|
24
|
+
const query = queryParts.join(' ').trim();
|
|
25
|
+
if (!query) {
|
|
26
|
+
log.err('Usage: draftgo api <query> | draftgo api search <query> | draftgo api describe <operation_id>');
|
|
145
27
|
return 1;
|
|
146
28
|
}
|
|
147
29
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const words = keyword.split(/\s+/).filter(Boolean);
|
|
157
|
-
const matches = [];
|
|
158
|
-
for (const [route, methods] of Object.entries(spec.paths || {})) {
|
|
159
|
-
for (const [method, operation] of Object.entries(methods || {})) {
|
|
160
|
-
if (!HTTP_METHODS.has(method.toLowerCase())) continue;
|
|
161
|
-
const haystack = [route, method, operation.summary, operation.operationId, ...(operation.tags || [])]
|
|
162
|
-
.filter(Boolean).join(' ').toLowerCase();
|
|
163
|
-
if (!words.every((word) => haystack.includes(word))) continue;
|
|
164
|
-
matches.push({
|
|
165
|
-
method: method.toUpperCase(),
|
|
166
|
-
route,
|
|
167
|
-
summary: operation.summary || '',
|
|
168
|
-
operationId: operation.operationId || '',
|
|
169
|
-
tags: operation.tags || [],
|
|
170
|
-
operation,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (flags.output === 'json') {
|
|
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));
|
|
190
|
-
return matches.length ? 0 : 1;
|
|
191
|
-
}
|
|
192
|
-
if (!matches.length) {
|
|
193
|
-
log.warn(`未找到包含“${keyword}”的 API。`);
|
|
194
|
-
return 1;
|
|
195
|
-
}
|
|
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.`);
|
|
30
|
+
const config = loadProjectConfig(projectDir);
|
|
31
|
+
const expected = describe ? TOOL_NAMES.apiDescribe : TOOL_NAMES.apiSearch;
|
|
32
|
+
const session = await openToolSession(config, [expected]);
|
|
33
|
+
const result = await callStructured(session, expected, describe
|
|
34
|
+
? { operation_id: query }
|
|
35
|
+
: { query });
|
|
36
|
+
if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
|
|
37
|
+
else printResult(describe ? `DraftGo API ${query}` : `DraftGo API search: ${query}`, result);
|
|
201
38
|
return 0;
|
|
202
39
|
}
|
|
203
40
|
|
package/src/commands/autoPush.js
CHANGED
|
@@ -1,41 +1,72 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
4
|
const log = require('../logger');
|
|
6
5
|
const check = require('./check');
|
|
7
|
-
const
|
|
6
|
+
const { loadProjectConfig } = require('../projectConfig');
|
|
7
|
+
const { loadManifest, absolutePath } = require('../worktree/manifest');
|
|
8
|
+
const { hashFile } = require('../worktree/streams');
|
|
9
|
+
const { listConflicts, commitResources } = require('../worktree');
|
|
8
10
|
|
|
9
|
-
function
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
async function changedEntries(projectDir, manifest) {
|
|
12
|
+
const changed = [];
|
|
13
|
+
for (const entry of Object.values(manifest.entries)) {
|
|
14
|
+
const local = absolutePath(projectDir, entry.local_path);
|
|
15
|
+
if (!fs.existsSync(local)) continue;
|
|
16
|
+
if ((await hashFile(local)).hash !== entry.base_hash) changed.push(entry);
|
|
14
17
|
}
|
|
18
|
+
return changed;
|
|
19
|
+
}
|
|
15
20
|
|
|
21
|
+
async function autoPush(projectDir, positional, flags = {}) {
|
|
16
22
|
let config;
|
|
17
23
|
try {
|
|
18
|
-
config =
|
|
19
|
-
} catch
|
|
20
|
-
log.
|
|
24
|
+
config = loadProjectConfig(projectDir);
|
|
25
|
+
} catch {
|
|
26
|
+
log.dim('auto-push skipped: this project is not connected to DraftGo.');
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (config.auto_push !== true) {
|
|
31
|
+
log.dim('auto-push skipped: .draftgo/config.json does not set auto_push to true.');
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const conflicts = listConflicts(projectDir);
|
|
35
|
+
if (conflicts.length) {
|
|
36
|
+
log.err(`auto-push stopped: ${conflicts.length} unresolved DraftGo conflict(s).`);
|
|
21
37
|
return 1;
|
|
22
38
|
}
|
|
23
39
|
|
|
24
|
-
log.step('auto-push: 先执行 check …');
|
|
25
40
|
const checkCode = check(projectDir, { strict: flags.strict });
|
|
26
41
|
if (checkCode !== 0) {
|
|
27
|
-
log.err('
|
|
42
|
+
log.err('auto-push stopped because local validation failed.');
|
|
28
43
|
return checkCode;
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
const groups = new Map();
|
|
47
|
+
if (positional.length) {
|
|
48
|
+
const [resourceType, ...ids] = positional;
|
|
49
|
+
if (!resourceType || !ids.length) {
|
|
50
|
+
log.err('Usage: draftgo auto-push [<pages|nav|docs> <id...>]');
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
groups.set(resourceType, ids);
|
|
54
|
+
} else {
|
|
55
|
+
for (const entry of await changedEntries(projectDir, loadManifest(projectDir))) {
|
|
56
|
+
if (!groups.has(entry.resource_type)) groups.set(entry.resource_type, []);
|
|
57
|
+
groups.get(entry.resource_type).push(entry.resource_id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!groups.size) {
|
|
61
|
+
log.dim('auto-push: no checked-out content changed.');
|
|
33
62
|
return 0;
|
|
34
63
|
}
|
|
35
64
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
for (const [resourceType, ids] of groups) {
|
|
66
|
+
await commitResources(projectDir, resourceType, ids, { config });
|
|
67
|
+
}
|
|
68
|
+
log.ok('auto-push committed all changed checked-out content.');
|
|
69
|
+
return 0;
|
|
39
70
|
}
|
|
40
71
|
|
|
41
72
|
module.exports = autoPush;
|
package/src/commands/check.js
CHANGED
|
@@ -1,62 +1,32 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
3
5
|
const log = require('../logger');
|
|
4
6
|
const { analyzeProject } = require('../projectMap');
|
|
5
7
|
|
|
8
|
+
function legacyCacheDetected(projectDir) {
|
|
9
|
+
return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
|
|
10
|
+
.some((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
function check(projectDir, flags = {}) {
|
|
7
14
|
const result = analyzeProject(projectDir);
|
|
8
|
-
|
|
9
15
|
if (flags.output === 'json') {
|
|
10
|
-
console.log(JSON.stringify(result, null, 2));
|
|
16
|
+
console.log(JSON.stringify({ ...result, legacy_cache_ignored: legacyCacheDetected(projectDir) }, null, 2));
|
|
11
17
|
return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
log.title('draftgo check');
|
|
15
|
-
log.info(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
['aihub', m.aihub.length],
|
|
25
|
-
['docs', m.docs.length],
|
|
26
|
-
];
|
|
27
|
-
console.log(' ' + overview.map(([k, v]) => `${log.c.cyan(k)} ${v}`).join(' '));
|
|
28
|
-
const pageAreas = [
|
|
29
|
-
['home', m.pageGroups.home.length],
|
|
30
|
-
['admin', m.pageGroups.admin.length],
|
|
31
|
-
['business', m.pageGroups.business.length],
|
|
32
|
-
['system', m.pageGroups.system.length],
|
|
33
|
-
];
|
|
34
|
-
console.log(' ' + pageAreas.map(([k, v]) => `${log.c.cyan(k)} ${v}`).join(' '));
|
|
35
|
-
|
|
36
|
-
if (result.errors.length) {
|
|
37
|
-
console.log('');
|
|
38
|
-
result.errors.forEach((msg) => console.log(` ${log.c.red('x')} ${msg}`));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (result.warnings.length) {
|
|
42
|
-
console.log('');
|
|
43
|
-
result.warnings.forEach((msg) => {
|
|
44
|
-
const detail = (result.warningDetails || []).find((item) => item.message === msg);
|
|
45
|
-
const prefix = detail ? `[${detail.code}/${detail.confidence}] ` : '';
|
|
46
|
-
console.log(` ${log.c.yellow('!')} ${prefix}${msg}`);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
console.log('');
|
|
51
|
-
if (result.errors.length) {
|
|
52
|
-
log.err('检查未通过:请先修复错误。');
|
|
53
|
-
return 1;
|
|
54
|
-
}
|
|
55
|
-
if (flags.strict && result.warnings.length) {
|
|
56
|
-
log.err('strict 模式:存在提醒项,检查未通过。');
|
|
57
|
-
return 1;
|
|
58
|
-
}
|
|
59
|
-
log.ok(result.warnings.length ? '检查完成(有提醒项)。' : '检查通过。');
|
|
21
|
+
log.info(`Checked-out pages: ${result.map.pages.length}`);
|
|
22
|
+
log.info(`Checked-out navigations: ${result.map.navigations.length}`);
|
|
23
|
+
log.info(`Checked-out docs: ${result.map.docs.length}`);
|
|
24
|
+
for (const message of result.errors) log.err(message);
|
|
25
|
+
for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
|
|
26
|
+
if (legacyCacheDetected(projectDir)) log.dim(' Legacy .draftgo index caches were detected and ignored.');
|
|
27
|
+
|
|
28
|
+
if (result.errors.length || (flags.strict && result.warnings.length)) return 1;
|
|
29
|
+
log.ok(result.warnings.length ? 'Local checkout validation completed with warnings.' : 'Local checkout validation passed.');
|
|
60
30
|
return 0;
|
|
61
31
|
}
|
|
62
32
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { checkoutResources } = require('../worktree');
|
|
5
|
+
|
|
6
|
+
async function checkout(projectDir, positional, flags = {}) {
|
|
7
|
+
const [resourceType, ...ids] = positional;
|
|
8
|
+
if (!resourceType || !ids.length) {
|
|
9
|
+
log.err('Usage: draftgo checkout <pages|nav|docs> <id...>');
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
const results = await checkoutResources(projectDir, resourceType, ids, { force: Boolean(flags.force) });
|
|
13
|
+
if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
|
|
14
|
+
else for (const entry of results) log.ok(`Checked out ${entry.resource_type} ${entry.resource_id} -> ${entry.local_path}`);
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = checkout;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { commitResources } = require('../worktree');
|
|
5
|
+
|
|
6
|
+
async function commit(projectDir, positional, flags = {}) {
|
|
7
|
+
const [resourceType, ...ids] = positional;
|
|
8
|
+
if (!resourceType || !ids.length) {
|
|
9
|
+
log.err('Usage: draftgo commit <pages|nav|docs> <id...>');
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
const results = await commitResources(projectDir, resourceType, ids);
|
|
13
|
+
if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
|
|
14
|
+
else for (const result of results) {
|
|
15
|
+
if (result.status === 'unchanged') log.dim(`${result.resource_type} ${result.resource_id}: unchanged`);
|
|
16
|
+
else log.ok(`Committed ${result.resource_type} ${result.resource_id}`);
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = commit;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { showConflict, resolveConflict } = require('../worktree');
|
|
5
|
+
|
|
6
|
+
function printRecord(record) {
|
|
7
|
+
log.info(`${record.resource_type} ${record.resource_id}: ${record.status}`);
|
|
8
|
+
log.plain(` base: ${record.base_path}`);
|
|
9
|
+
log.plain(` local: ${record.local_path}`);
|
|
10
|
+
log.plain(` remote: ${record.remote_path}`);
|
|
11
|
+
log.plain(` worktree local: ${record.worktree_local_path}`);
|
|
12
|
+
log.plain(` manifest: ${record.manifest_path}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function conflict(projectDir, positional, flags = {}) {
|
|
16
|
+
const [action, resourceType, resourceId] = positional;
|
|
17
|
+
if (!['show', 'resolve'].includes(action) || !resourceType || !resourceId) {
|
|
18
|
+
log.err('Usage: draftgo conflict <show|resolve> <pages|nav|docs> <id>');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const record = action === 'show'
|
|
22
|
+
? showConflict(projectDir, resourceType, resourceId)
|
|
23
|
+
: await resolveConflict(projectDir, resourceType, resourceId);
|
|
24
|
+
if (flags.output === 'json') console.log(JSON.stringify(record, null, 2));
|
|
25
|
+
else printRecord(record);
|
|
26
|
+
if (action === 'resolve') log.info('The remote revision is now the base; run check, diff, and commit the merged worktree file.');
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = conflict;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { listConflicts } = require('../worktree');
|
|
5
|
+
|
|
6
|
+
function conflicts(projectDir, _positional, flags = {}) {
|
|
7
|
+
const records = listConflicts(projectDir, { all: Boolean(flags.all) });
|
|
8
|
+
if (flags.output === 'json') console.log(JSON.stringify(records, null, 2));
|
|
9
|
+
else if (!records.length) log.ok('No unresolved DraftGo conflicts.');
|
|
10
|
+
else for (const record of records) {
|
|
11
|
+
log.warn(`${record.resource_type} ${record.resource_id}: ${record.status} (${record.manifest_path})`);
|
|
12
|
+
}
|
|
13
|
+
return records.some((record) => record.status === 'unresolved') ? 1 : 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = conflicts;
|
package/src/commands/connect.js
CHANGED
|
@@ -1,61 +1,73 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// `draftgo connect` — wire this project to an already-deployed DraftGo base.
|
|
4
|
-
// Prompts for server URL + access token, writes .draftgo/config.json, and
|
|
5
|
-
// (if Python is available) runs draftgo_init.py to pull initial context.
|
|
6
|
-
|
|
7
3
|
const log = require('../logger');
|
|
8
|
-
const { ask,
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let v = String(raw || '').trim();
|
|
13
|
-
if (!v) return '';
|
|
14
|
-
if (!/^https?:\/\//i.test(v)) v = 'http://' + v;
|
|
15
|
-
return v.replace(/\/+$/, '');
|
|
16
|
-
}
|
|
4
|
+
const { ask, askPassword } = require('../prompt');
|
|
5
|
+
const { normalizeServer, writeProjectConfig } = require('../projectConfig');
|
|
6
|
+
const { testConnection } = require('../mcp/client');
|
|
7
|
+
const { redactText } = require('../mcp/protocol');
|
|
17
8
|
|
|
18
|
-
async function promptServer(
|
|
9
|
+
async function promptServer(defaultValue) {
|
|
19
10
|
while (true) {
|
|
20
|
-
const
|
|
21
|
-
const norm = normalizeServer(v);
|
|
22
|
-
if (!norm) { log.dim(' 不能为空'); continue; }
|
|
11
|
+
const raw = await ask('DraftGo server (Base URL)', { default: defaultValue });
|
|
23
12
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
log.dim(` 不是合法 URL:${norm}`);
|
|
29
|
-
continue;
|
|
13
|
+
const value = normalizeServer(raw);
|
|
14
|
+
if (value) return value;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
log.dim(` ${error.message}`);
|
|
30
17
|
}
|
|
31
|
-
return norm;
|
|
32
18
|
}
|
|
33
19
|
}
|
|
34
20
|
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
log.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
21
|
+
async function promptToken() {
|
|
22
|
+
while (true) {
|
|
23
|
+
const value = String(await askPassword('DraftGo system access token (SAT)') || '').trim();
|
|
24
|
+
if (value) return value;
|
|
25
|
+
log.dim(' A SAT is required.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function connect(projectDir, positional, flags = {}) {
|
|
30
|
+
log.title('draftgo connect');
|
|
31
|
+
const server = flags.server ? normalizeServer(flags.server) : await promptServer('https://');
|
|
32
|
+
const token = flags.token ? String(flags.token).trim() : await promptToken();
|
|
33
|
+
if (!token) {
|
|
34
|
+
log.err('A non-empty SAT is required.');
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
log.step('Validating SAT and DraftGo MCP capabilities...');
|
|
39
|
+
try {
|
|
40
|
+
const diagnostic = await testConnection({ server, token }, {
|
|
41
|
+
timeoutMs: flags.timeout ? Number(flags.timeout) : undefined,
|
|
42
|
+
});
|
|
43
|
+
log.ok(`MCP ready (${diagnostic.tools.length} tools; tested ${diagnostic.testedTool}).`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const message = redactText(error && error.message ? error.message : error, [token]);
|
|
46
|
+
if (!flags['allow-offline']) {
|
|
47
|
+
log.err(message);
|
|
48
|
+
log.dim(' The project config was not changed. Run `draftgo mcp test` after checking the server and SAT.');
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
log.warn(`MCP validation unavailable: ${message}`);
|
|
52
|
+
log.warn('Continuing only because --allow-offline was explicitly supplied.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const configFile = writeProjectConfig(projectDir, server, token);
|
|
56
|
+
log.ok(`Project configuration written: ${configFile}`);
|
|
57
|
+
|
|
58
|
+
if (!flags['no-mcp-setup']) {
|
|
59
|
+
try {
|
|
60
|
+
const setupCode = require('./mcp').setup(projectDir, positional, flags);
|
|
61
|
+
if (setupCode !== 0) log.dim(' Run `draftgo mcp setup <target>` when the AI host is known.');
|
|
62
|
+
} catch (error) {
|
|
63
|
+
log.warn(`MCP host setup was not completed: ${redactText(error.message || error, [token])}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log.title('Connected');
|
|
68
|
+
log.plain(` server: ${server}`);
|
|
69
|
+
log.plain(` config: ${configFile}`);
|
|
70
|
+
log.dim(' No DraftGo business resources were downloaded. Use MCP discovery or checkout long content explicitly.');
|
|
59
71
|
return 0;
|
|
60
72
|
}
|
|
61
73
|
|