draftgo-cli 4.0.1 → 4.0.22
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 +87 -11
- package/package.json +9 -4
- package/resources/custom-service-sdk/ai.go +520 -0
- package/resources/custom-service-sdk/ai_test.go +156 -0
- package/resources/custom-service-sdk/billing.go +596 -0
- package/resources/custom-service-sdk/billing_test.go +150 -0
- package/resources/custom-service-sdk/go.mod +3 -0
- package/resources/custom-service-sdk/manifest.json +72 -0
- package/resources/custom-service-sdk/platform.go +360 -0
- package/resources/custom-service-sdk/platform_logger_test.go +24 -0
- package/resources/custom-service-sdk/registration_test.go +39 -0
- package/resources/custom-service-sdk/resources.go +246 -0
- package/resources/custom-service-sdk/resources_billing_test.go +115 -0
- package/resources/custom-service-sdk/resources_files_test.go +57 -0
- package/resources/custom-service-sdk/resources_scope_test.go +87 -0
- package/resources/custom-service-sdk/sdk.go +208 -0
- package/resources/skill/SKILL.md +36 -88
- package/resources/skill/init/SKILL.md +4 -4
- package/resources/skill/manifest.json +5 -1
- package/resources/skill/references/aihub.md +25 -2
- package/resources/skill/references/app-api.md +56 -6
- package/resources/skill/references/architecture.md +2 -2
- package/resources/skill/references/chat-sdk.md +4 -2
- package/resources/skill/references/checkout.md +17 -3
- package/resources/skill/references/custom-services.md +111 -46
- package/resources/skill/references/data.md +19 -4
- package/resources/skill/references/delivery.md +33 -0
- package/resources/skill/references/diagnostics.md +51 -0
- package/resources/skill/references/frontend.md +34 -46
- package/resources/skill/references/mcp.md +33 -5
- package/resources/skill/references/methods.md +189 -0
- package/resources/skill/references/modules.md +36 -8
- package/resources/skill/references/runtime.md +23 -1
- package/src/cli.js +24 -0
- package/src/commandRegistry.js +9 -1
- package/src/commands/api.js +21 -10
- package/src/commands/apiKey.js +34 -0
- package/src/commands/capabilities.js +93 -0
- package/src/commands/checkout.js +1 -1
- package/src/commands/commit.js +1 -1
- package/src/commands/components.js +550 -0
- package/src/commands/conflict.js +1 -1
- package/src/commands/connect.js +18 -8
- package/src/commands/customService.js +20 -4
- package/src/commands/dataRange.js +33 -0
- package/src/commands/delete.js +12 -1
- package/src/commands/diff.js +18 -2
- package/src/commands/grant.js +29 -0
- package/src/commands/group.js +38 -0
- package/src/commands/help.js +64 -20
- package/src/commands/init.js +3 -3
- package/src/commands/map.js +145 -17
- package/src/commands/mcp.js +2 -2
- package/src/commands/reconcile.js +1 -1
- package/src/commands/role.js +32 -0
- package/src/commands/space.js +41 -0
- package/src/commands/status.js +110 -7
- package/src/commands/update.js +23 -11
- package/src/commands/verify.js +75 -0
- package/src/commands/worklog.js +6 -2
- package/src/consoleEncoding.js +34 -0
- package/src/contractCompatibility.js +57 -0
- package/src/customServices.js +138 -18
- package/src/diffReport.js +106 -0
- package/src/index.js +2 -0
- package/src/localRuntime/compose.js +14 -17
- package/src/localRuntime/index.js +22 -23
- package/src/localRuntime/services.js +27 -36
- package/src/mcp/client.js +11 -2
- package/src/mcp/protocol.js +2 -2
- package/src/mcp/tools.js +14 -1
- package/src/platforms.js +9 -0
- package/src/projectConfig.js +6 -4
- package/src/releaseInstall.js +105 -0
- package/src/updateCheck.js +48 -28
- package/src/worklog.js +2 -1
- package/src/worktree/backend.js +1 -1
- package/src/worktree/index.js +7 -2
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const log = require('../logger');
|
|
5
6
|
const services = require('../customServices');
|
|
7
|
+
const { report, formatSummary, formatStat } = require('../diffReport');
|
|
6
8
|
|
|
7
9
|
function ids(positional) { return positional.map(String).filter(Boolean); }
|
|
8
10
|
function isServiceType(value) {
|
|
@@ -28,9 +30,10 @@ async function checkout(projectDir, positional, flags = {}) {
|
|
|
28
30
|
async function commit(projectDir, positional, flags = {}) {
|
|
29
31
|
const values = ids(positional);
|
|
30
32
|
if (!values.length) { log.err('Usage: draftgo commit custom-services <id...>'); return 1; }
|
|
31
|
-
|
|
33
|
+
// The user-facing custom-service commit is a complete delivery operation.
|
|
34
|
+
const result = await services.publish(projectDir, values);
|
|
32
35
|
if (flags.output === 'json') output(flags, result);
|
|
33
|
-
else result.forEach((item) => log.ok(`custom service ${item.resource_id}:
|
|
36
|
+
else result.forEach((item) => log.ok(`custom service ${item.id || item.resource_id}: published (commit + validate + publish)`));
|
|
34
37
|
return 0;
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -38,7 +41,18 @@ async function diff(projectDir, positional, flags = {}) {
|
|
|
38
41
|
const id = positional[0];
|
|
39
42
|
if (!id) { log.err('Usage: draftgo diff custom-services <id>'); return 1; }
|
|
40
43
|
const result = services.diff(projectDir, id);
|
|
41
|
-
|
|
44
|
+
const concise = Boolean(flags.stat || flags.summary);
|
|
45
|
+
const value = concise ? report(result.entry, result.file_outputs.map((file) => ({
|
|
46
|
+
path: file.filename,
|
|
47
|
+
changed: file.changed,
|
|
48
|
+
output: file.output,
|
|
49
|
+
base_path: path.join(result.entry.base_dir, file.filename),
|
|
50
|
+
local_path: path.join(result.entry.local_dir, file.filename),
|
|
51
|
+
}))) : null;
|
|
52
|
+
if (flags.output === 'json' && concise) output(flags, value);
|
|
53
|
+
else if (flags.output === 'json') output(flags, result);
|
|
54
|
+
else if (flags.stat) process.stdout.write(formatStat(value));
|
|
55
|
+
else if (flags.summary) process.stdout.write(formatSummary(value));
|
|
42
56
|
else if (result.changed) process.stdout.write(result.output);
|
|
43
57
|
else log.dim(`custom service ${id}: no local changes`);
|
|
44
58
|
return 0;
|
|
@@ -60,8 +74,10 @@ async function test(projectDir, positional, flags = {}) {
|
|
|
60
74
|
const readJSON = (value) => value ? JSON.parse(fs.readFileSync(String(value), 'utf8')) : undefined;
|
|
61
75
|
const policy = String(flags['side-effect-policy'] || 'deny').toLowerCase();
|
|
62
76
|
if (!['deny', 'mock', 'live'].includes(policy)) throw new Error('--side-effect-policy must be deny, mock, or live.');
|
|
77
|
+
const source = String(flags.source || 'draft').toLowerCase();
|
|
78
|
+
if (!['auto', 'draft', 'published'].includes(source)) throw new Error('--source must be auto, draft, or published.');
|
|
63
79
|
const result = await services.test(projectDir, id, input, { handler: flags.handler, headers: readJSON(flags.headers), user: readJSON(flags.user),
|
|
64
|
-
testWrite: Boolean(flags['test-write']), sideEffectPolicy: policy });
|
|
80
|
+
testWrite: Boolean(flags['test-write']), sideEffectPolicy: policy, source });
|
|
65
81
|
if (flags.output === 'json') output(flags, result);
|
|
66
82
|
else log[result.status === 'success' ? 'ok' : 'err'](`custom service ${id}: ${result.status}`);
|
|
67
83
|
return result.status === 'success' ? 0 : 1;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { callOperation } = require('./api');
|
|
5
|
+
|
|
6
|
+
// DataRange is represented by db_meta record policies; it is not a second
|
|
7
|
+
// authorization scope or a free-form client-side filter.
|
|
8
|
+
const OPERATIONS = Object.freeze({
|
|
9
|
+
list: 'list_meta_api_db_meta_get',
|
|
10
|
+
create: 'create_meta_api_db_meta_post',
|
|
11
|
+
get: 'get_meta_api_db_meta__type__get',
|
|
12
|
+
update: 'update_meta_api_db_meta__id__put',
|
|
13
|
+
batch: 'update_meta_batch_api_db_meta_batch_patch',
|
|
14
|
+
delete: 'delete_meta_api_db_meta__id__delete',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function operationKey(value) {
|
|
18
|
+
const action = String(value || 'list').trim().toLowerCase();
|
|
19
|
+
return OPERATIONS[action] ? action : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function dataRangeCommand(projectDir, positional, flags = {}) {
|
|
23
|
+
const action = operationKey(positional[0]);
|
|
24
|
+
if (!action) {
|
|
25
|
+
log.err('Usage: draftgo data-range list|create|get|update|batch|delete --input <json-file>');
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
return callOperation(projectDir, OPERATIONS[action], flags);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = dataRangeCommand;
|
|
32
|
+
module.exports.OPERATIONS = OPERATIONS;
|
|
33
|
+
module.exports.operationKey = operationKey;
|
package/src/commands/delete.js
CHANGED
|
@@ -7,6 +7,12 @@ const { confirm } = require('../prompt');
|
|
|
7
7
|
const { loadProjectConfig } = require('../projectConfig');
|
|
8
8
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
9
9
|
const { cachedDescription, normalizeDescription, storeDescription } = require('../apiContractCache');
|
|
10
|
+
const {
|
|
11
|
+
isContractCompatibilityError,
|
|
12
|
+
isHardContractIncompatibility,
|
|
13
|
+
sameOperationContract,
|
|
14
|
+
incompatibleOperationError,
|
|
15
|
+
} = require('../contractCompatibility');
|
|
10
16
|
|
|
11
17
|
function readParams(projectDir, flags) {
|
|
12
18
|
const raw = flags.params ? String(flags.params)
|
|
@@ -65,10 +71,15 @@ async function deleteResource(projectDir, positional, flags = {}) {
|
|
|
65
71
|
try {
|
|
66
72
|
result = await invoke();
|
|
67
73
|
} catch (error) {
|
|
68
|
-
if (error
|
|
74
|
+
if (!isContractCompatibilityError(error)) throw error;
|
|
75
|
+
if (isHardContractIncompatibility(error)) throw incompatibleOperationError(selectedOperation, error);
|
|
76
|
+
const previousContract = contract;
|
|
69
77
|
const described = await callStructured(session, TOOL_NAMES.apiDescribe, { operation_id: selectedOperation });
|
|
70
78
|
contract = storeDescription(projectDir, config.server, described) || normalizeDescription(described);
|
|
71
79
|
if (!contract) throw new Error('DraftGo API description is missing contract revision metadata.');
|
|
80
|
+
if (!sameOperationContract(previousContract, contract)) {
|
|
81
|
+
throw incompatibleOperationError(selectedOperation, error);
|
|
82
|
+
}
|
|
72
83
|
result = await invoke();
|
|
73
84
|
}
|
|
74
85
|
if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
|
package/src/commands/diff.js
CHANGED
|
@@ -2,19 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
const log = require('../logger');
|
|
4
4
|
const { diffResource } = require('../worktree');
|
|
5
|
+
const { report, formatSummary, formatStat } = require('../diffReport');
|
|
6
|
+
const { absolutePath } = require('../worktree/manifest');
|
|
5
7
|
|
|
6
8
|
function diff(projectDir, positional, flags = {}) {
|
|
7
9
|
const [resourceType, resourceId] = positional;
|
|
8
10
|
if (!resourceType || !resourceId) {
|
|
9
|
-
log.err('Usage: draftgo diff <pages|nav|docs> <id>');
|
|
11
|
+
log.err('Usage: draftgo diff <pages|nav|docs|custom-services> <id>');
|
|
10
12
|
return 1;
|
|
11
13
|
}
|
|
12
14
|
if (require('./customService').isServiceType(resourceType)) {
|
|
13
15
|
return require('./customService').diff(projectDir, [resourceId], flags);
|
|
14
16
|
}
|
|
15
17
|
const result = diffResource(projectDir, resourceType, resourceId);
|
|
16
|
-
|
|
18
|
+
const concise = Boolean(flags.stat || flags.summary);
|
|
19
|
+
const value = concise ? report(result.entry, [{
|
|
20
|
+
path: result.entry.local_path,
|
|
21
|
+
changed: result.changed,
|
|
22
|
+
output: result.output,
|
|
23
|
+
base_path: absolutePath(projectDir, result.entry.base_path),
|
|
24
|
+
local_path: absolutePath(projectDir, result.entry.local_path),
|
|
25
|
+
}]) : null;
|
|
26
|
+
if (flags.output === 'json' && concise) {
|
|
27
|
+
console.log(JSON.stringify(value, null, 2));
|
|
28
|
+
} else if (flags.output === 'json') {
|
|
17
29
|
console.log(JSON.stringify({ changed: result.changed, entry: result.entry, diff: result.output }, null, 2));
|
|
30
|
+
} else if (flags.stat) {
|
|
31
|
+
process.stdout.write(formatStat(value));
|
|
32
|
+
} else if (flags.summary) {
|
|
33
|
+
process.stdout.write(formatSummary(value));
|
|
18
34
|
} else if (result.changed) {
|
|
19
35
|
process.stdout.write(result.output);
|
|
20
36
|
} else {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { callOperation } = require('./api');
|
|
5
|
+
|
|
6
|
+
const OPERATIONS = Object.freeze({
|
|
7
|
+
list: 'listAccessGrants',
|
|
8
|
+
create: 'createAccessGrant',
|
|
9
|
+
get: 'getAccessGrant',
|
|
10
|
+
revoke: 'revokeAccessGrant',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function operationKey(value) {
|
|
14
|
+
const action = String(value || 'list').trim().toLowerCase();
|
|
15
|
+
return OPERATIONS[action] ? action : '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function grantCommand(projectDir, positional, flags = {}) {
|
|
19
|
+
const action = operationKey(positional[0]);
|
|
20
|
+
if (!action) {
|
|
21
|
+
log.err('Usage: draftgo grant list|create|get|revoke [--input <json-file>]');
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
return callOperation(projectDir, OPERATIONS[action], flags);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = grantCommand;
|
|
28
|
+
module.exports.OPERATIONS = OPERATIONS;
|
|
29
|
+
module.exports.operationKey = operationKey;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { callOperation } = require('./api');
|
|
5
|
+
|
|
6
|
+
const OPERATIONS = Object.freeze({
|
|
7
|
+
list: 'listUserGroups',
|
|
8
|
+
create: 'createUserGroup',
|
|
9
|
+
get: 'getUserGroup',
|
|
10
|
+
update: 'updateUserGroup',
|
|
11
|
+
replace: 'replaceUserGroup',
|
|
12
|
+
remove: 'removeUserGroup',
|
|
13
|
+
'members.list': 'listUserGroupMembers',
|
|
14
|
+
'members.add': 'addUserGroupMember',
|
|
15
|
+
'members.remove': 'removeUserGroupMember',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function operationKey(parts) {
|
|
19
|
+
const values = parts.map((part) => String(part || '').trim().toLowerCase()).filter(Boolean);
|
|
20
|
+
if (values.length === 1) return values[0] === 'delete' ? 'remove' : values[0];
|
|
21
|
+
if (values.length === 2 && ['member', 'members'].includes(values[0])) {
|
|
22
|
+
return `members.${values[1] === 'delete' ? 'remove' : values[1]}`;
|
|
23
|
+
}
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function groupCommand(projectDir, positional, flags = {}) {
|
|
28
|
+
const key = operationKey(positional);
|
|
29
|
+
if (!OPERATIONS[key]) {
|
|
30
|
+
log.err('Usage: draftgo group list|create|get|update|replace|remove | members list|add|remove [--input <json-file>]');
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
return callOperation(projectDir, OPERATIONS[key], flags);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = groupCommand;
|
|
37
|
+
module.exports.OPERATIONS = OPERATIONS;
|
|
38
|
+
module.exports.operationKey = operationKey;
|
package/src/commands/help.js
CHANGED
|
@@ -13,8 +13,8 @@ function help() {
|
|
|
13
13
|
|
|
14
14
|
console.log(`draftgo v${getPackageVersion()} - DraftGo workbench CLI for AI coding agents
|
|
15
15
|
|
|
16
|
-
DraftGo
|
|
17
|
-
|
|
16
|
+
DraftGo shell baseline: React + Vite. Database Pages default to native HTML,
|
|
17
|
+
Tailwind CSS 4, and Basecoat UI; Oat UI remains an optional Web Components layer.
|
|
18
18
|
MCP handles live discovery and structured resources; complete page, navigation,
|
|
19
19
|
document, and custom-service bodies use checkout/commit outside MCP context.
|
|
20
20
|
The installed Skill routes agents to task-specific References and MCP tools.
|
|
@@ -26,9 +26,9 @@ Usage:
|
|
|
26
26
|
refresh installed or detected Skills.
|
|
27
27
|
draftgo uninstall <target|all> Remove Skill files. --purge also removes
|
|
28
28
|
the entire .draftgo/ runtime directory.
|
|
29
|
-
draftgo status Show
|
|
29
|
+
draftgo status Show connection health, current scope, targets, and Skill version.
|
|
30
30
|
|
|
31
|
-
draftgo connect [<target>...] Verify and save server/
|
|
31
|
+
draftgo connect [<target>...] Verify and save server/API Key, then configure
|
|
32
32
|
a detected or explicit MCP host. This never
|
|
33
33
|
downloads DraftGo business resources.
|
|
34
34
|
draftgo mcp setup [<target>...] Merge project-level stdio MCP configuration.
|
|
@@ -36,24 +36,42 @@ Usage:
|
|
|
36
36
|
draftgo mcp test Test project, resource, and API MCP calls.
|
|
37
37
|
draftgo mcp serve Bridge local stdio to the remote /mcp endpoint.
|
|
38
38
|
|
|
39
|
-
draftgo map
|
|
40
|
-
overlay local checkout state.
|
|
41
|
-
|
|
39
|
+
draftgo map [--type <type>] Locate bounded remote resource metadata and
|
|
40
|
+
overlay matching local checkout state.
|
|
41
|
+
Use --route/--title for exact selection.
|
|
42
|
+
draftgo checkout <type> <id...> Download a long-form body + verified base,
|
|
43
|
+
including custom-service worktrees.
|
|
42
44
|
--force explicitly replaces local changes.
|
|
43
45
|
draftgo check [custom-services <id...>]
|
|
44
46
|
Validate all checkouts or selected custom services.
|
|
45
47
|
--remote also compares remote hash/version.
|
|
46
48
|
draftgo verify [<type> <id...>] Run the default delivery verification.
|
|
47
49
|
Browser options are explicit and optional.
|
|
48
|
-
draftgo diff <type> <id> Show checkout base versus local body.
|
|
49
|
-
|
|
50
|
+
draftgo diff <type> <id> Show checkout base versus local body/files.
|
|
51
|
+
--stat/--summary omit the full patch.
|
|
52
|
+
draftgo commit <type> <id...> Validate and upload complete checked-out bodies/files.
|
|
53
|
+
draftgo components search <query> Search the live component catalog.
|
|
54
|
+
draftgo components show <library/component>
|
|
55
|
+
Show HTML, props, slots, CSS variables and revision.
|
|
56
|
+
draftgo components expand --page <id> --instance <data-dg-instance>
|
|
57
|
+
Expand one component in a checked-out Page worktree.
|
|
58
|
+
draftgo components libraries list|show|create|update|delete
|
|
59
|
+
Manage component libraries.
|
|
60
|
+
draftgo components list|create|copy|delete
|
|
61
|
+
Manage component catalog entries.
|
|
62
|
+
draftgo components checkout|diff|verify|commit|publish <library/component>
|
|
63
|
+
Develop a component draft locally, then publish explicitly.
|
|
64
|
+
draftgo components import <zip> [--dry-run]
|
|
65
|
+
draftgo components export <library> [--file <zip>]
|
|
66
|
+
Transfer standard DraftGo component archives.
|
|
50
67
|
draftgo refresh <type> <id...> Safely refresh a clean checkout from the cloud.
|
|
51
68
|
draftgo validate custom-services <id>
|
|
52
69
|
Build and inspect the current cloud draft.
|
|
53
|
-
draftgo test custom-services <id> [--handler <selector>]
|
|
70
|
+
draftgo test custom-services <id> [--source <source>] [--input <file>] [--handler <selector>]
|
|
54
71
|
Run the cloud draft in the DraftGo Runner.
|
|
55
72
|
draftgo publish custom-services <id...>
|
|
56
|
-
Publish validated drafts
|
|
73
|
+
Publish current validated drafts; batch errors
|
|
74
|
+
report services already completed.
|
|
57
75
|
draftgo reconcile <type> <id...> Repair stale metadata only when local equals remote.
|
|
58
76
|
draftgo conflicts List unresolved conflicts; --all includes resolved.
|
|
59
77
|
draftgo conflict show <type> <id>
|
|
@@ -73,6 +91,18 @@ Usage:
|
|
|
73
91
|
Describe one live API operation through MCP.
|
|
74
92
|
draftgo api call <operation_id> --input <json-file>
|
|
75
93
|
Call one described operation with UTF-8 JSON.
|
|
94
|
+
draftgo capabilities list|search|show|audit
|
|
95
|
+
Discover and audit the current server's live operation catalog.
|
|
96
|
+
draftgo space <action> Manage spaces and workspace members.
|
|
97
|
+
draftgo group <action> Manage workspace groups and group members.
|
|
98
|
+
draftgo grant list|create|get|revoke
|
|
99
|
+
Manage platform/space AccessGrants.
|
|
100
|
+
draftgo api-key status|create|update|delete|rotate|reset|enable|disable
|
|
101
|
+
Manage the current user's API key through MCP.
|
|
102
|
+
draftgo role permissions|list|create|get|update|delete
|
|
103
|
+
Manage structured permission templates.
|
|
104
|
+
draftgo data-range list|create|get|update|batch|delete
|
|
105
|
+
Manage db_meta record policies (none|owner|all).
|
|
76
106
|
draftgo delete <operation_id> [id]
|
|
77
107
|
Confirm and call a live delete operation.
|
|
78
108
|
draftgo deploy [<type> <id...>] Check, then diff or commit checked-out content.
|
|
@@ -96,7 +126,9 @@ Important flags:
|
|
|
96
126
|
--server <url> (connect) DraftGo base URL.
|
|
97
127
|
--mcp-url <url> (connect) Explicit endpoint; standard /mcp derives the
|
|
98
128
|
base URL; custom paths also need --server.
|
|
99
|
-
--
|
|
129
|
+
--api-key <key> (connect) DraftGo user API Key for non-interactive use.
|
|
130
|
+
--scope-type space --space-id <id>
|
|
131
|
+
(connect) Save a target space for workspace resources.
|
|
100
132
|
--timeout <ms> (connect/mcp test) Network timeout.
|
|
101
133
|
--allow-offline (connect) Save only after explicitly accepting a
|
|
102
134
|
failed MCP validation.
|
|
@@ -107,15 +139,25 @@ Important flags:
|
|
|
107
139
|
--connect (init) Continue into DraftGo server connection.
|
|
108
140
|
--no-setup (init) Install the Skill without either setup flow.
|
|
109
141
|
--output json Print machine-readable output where supported.
|
|
110
|
-
--type <type> (map) pages | nav/navigations | docs/articles
|
|
142
|
+
--type <type> (map) pages | nav/navigations | docs/articles |
|
|
143
|
+
custom-services.
|
|
144
|
+
--route <path> (map) Select resources with this exact route.
|
|
145
|
+
--title <title> (map) Select resources with this exact title.
|
|
146
|
+
--limit <1-100> (map) Maximum resources returned per type/page;
|
|
147
|
+
defaults to 20.
|
|
148
|
+
--cursor <cursor> (map) Continue one typed map result page.
|
|
149
|
+
--summary (map/diff) Print bounded resource or change summary.
|
|
150
|
+
--stat (diff) Print per-file and total change statistics.
|
|
111
151
|
--strict Treat check warnings as failures.
|
|
112
152
|
--remote (check) Compare checkout hashes/versions with remote.
|
|
113
153
|
--yes Skip supported confirmation prompts.
|
|
114
154
|
--operation-id <id> (delete) Select an operation explicitly.
|
|
115
155
|
--params <json> (api call/delete) Pass an API parameter object.
|
|
116
|
-
--input <file> (api call/delete) Read a UTF-8
|
|
156
|
+
--input <file> (api call/delete/custom-service test) Read a UTF-8
|
|
157
|
+
JSON object.
|
|
117
158
|
--handler <selector> (custom-service test) route:METHOD:/path, event:name,
|
|
118
159
|
scheduled:name, or a handler name.
|
|
160
|
+
--source <source> (custom-service test) draft (default), auto, or published.
|
|
119
161
|
--headers/--user <file> (custom-service test) Read JSON test identity data.
|
|
120
162
|
--test-write (custom-service test) Permit declared write testing.
|
|
121
163
|
--side-effect-policy <mode>
|
|
@@ -127,7 +169,7 @@ Important flags:
|
|
|
127
169
|
--viewport <mode> (verify) mobile | desktop | both.
|
|
128
170
|
--frame <mode> (verify) auto | top | all | <iframe-selector>.
|
|
129
171
|
--token <mode> (verify) auto | never; auto appends the configured
|
|
130
|
-
|
|
172
|
+
API Key to same-origin URLs as the token query parameter.
|
|
131
173
|
--screenshot <mode> (verify) always | never; always alone captures a screenshot.
|
|
132
174
|
--browser <name> (verify) chromium | chrome | msedge.
|
|
133
175
|
--browser-path <file> (verify) Explicit browser executable; environment
|
|
@@ -136,8 +178,8 @@ Important flags:
|
|
|
136
178
|
--width/--height <px> (verify) Override the default 1440x900 desktop viewport.
|
|
137
179
|
|
|
138
180
|
Security:
|
|
139
|
-
.draftgo/config.json stores the server and
|
|
140
|
-
files contain only "draftgo mcp serve"; they never contain the
|
|
181
|
+
.draftgo/config.json stores the server and API Key and is gitignored. Host MCP
|
|
182
|
+
files contain only "draftgo mcp serve"; they never contain the API Key.
|
|
141
183
|
|
|
142
184
|
MCP targets (project config or status):
|
|
143
185
|
${mcpTargets}
|
|
@@ -147,13 +189,15 @@ ${targets}
|
|
|
147
189
|
|
|
148
190
|
Examples:
|
|
149
191
|
draftgo init codex
|
|
150
|
-
draftgo connect codex --server https://draftgo.example --
|
|
192
|
+
draftgo connect codex --server https://draftgo.example --api-key <key>
|
|
151
193
|
draftgo mcp test
|
|
152
|
-
draftgo map --output json
|
|
194
|
+
draftgo map --type pages --route /admin/channel-ops --summary --output json
|
|
153
195
|
draftgo checkout pages 42
|
|
154
196
|
draftgo verify pages 42
|
|
155
|
-
draftgo diff pages 42
|
|
197
|
+
draftgo diff pages 42 --stat
|
|
156
198
|
draftgo commit pages 42
|
|
199
|
+
draftgo checkout custom-services 12
|
|
200
|
+
draftgo test custom-services 12 --handler route:POST:/health --input request.json
|
|
157
201
|
draftgo work start "Build document management and role permissions"
|
|
158
202
|
draftgo work complete 1 --note "Verification and delivery passed"
|
|
159
203
|
draftgo deploy docs 7 --delivery preview
|
package/src/commands/init.js
CHANGED
|
@@ -80,8 +80,8 @@ async function init(projectDir, positional, flags) {
|
|
|
80
80
|
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
81
81
|
|
|
82
82
|
if (!hasConfig && !flagSkip) {
|
|
83
|
-
//
|
|
84
|
-
if (flags.server && flags.token && !flagConnect) {
|
|
83
|
+
// Non-interactive setup can connect immediately with a server and API Key.
|
|
84
|
+
if (flags.server && (flags['api-key'] || flags.token) && !flagConnect) {
|
|
85
85
|
return await require('./connect')(projectDir, [], flags);
|
|
86
86
|
}
|
|
87
87
|
if (flagConnect) {
|
|
@@ -92,7 +92,7 @@ async function init(projectDir, positional, flags) {
|
|
|
92
92
|
log.plain(` ${log.c.cyan('[A]')} 连接基座服务`);
|
|
93
93
|
log.dim(' 使用已有 DraftGo 服务,接下来输入 BaseURL 和系统访问令牌。');
|
|
94
94
|
log.plain(` ${log.c.cyan('[B]')} 本地启动基座`);
|
|
95
|
-
log.dim(' 使用 Docker 启动 DraftGo;按需准备共享的 MySQL、Redis 和
|
|
95
|
+
log.dim(' 使用 Docker 启动 DraftGo;按需准备共享的 MySQL、Redis 和 Qdrant。');
|
|
96
96
|
log.plain(` ${log.c.cyan('[C]')} 稍后自行处理`);
|
|
97
97
|
log.dim(' 仅安装 Skill,不写入服务配置,也不创建容器。');
|
|
98
98
|
const choice = (await ask('请选择 [A/B/C]', { default: 'C' })).trim().toUpperCase();
|
package/src/commands/map.js
CHANGED
|
@@ -6,6 +6,7 @@ const { canonicalResourceType } = require('../worktree/types');
|
|
|
6
6
|
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
7
7
|
const { allWithAbort } = require('../mcp/parallel');
|
|
8
8
|
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
9
|
+
const { loadManifest } = require('../worktree/manifest');
|
|
9
10
|
const customServices = require('../customServices');
|
|
10
11
|
|
|
11
12
|
const REMOTE_RESOURCE_TYPES = Object.freeze({
|
|
@@ -40,6 +41,11 @@ function claimsMorePages(value) {
|
|
|
40
41
|
&& (value.has_more === true || value.hasMore === true);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function reportedTotal(value) {
|
|
45
|
+
const total = value && typeof value === 'object' ? value.total : null;
|
|
46
|
+
return Number.isInteger(total) && total >= 0 ? total : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
function normalizeMapResourceType(value) {
|
|
44
50
|
if (require('./customService').isServiceType(value)) return 'custom_services';
|
|
45
51
|
return REMOTE_RESOURCE_TYPES[canonicalResourceType(value)];
|
|
@@ -51,6 +57,79 @@ function requestedResourceTypes(flags = {}) {
|
|
|
51
57
|
: [normalizeMapResourceType(flags.type)];
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
function normalizeRoute(value) {
|
|
61
|
+
// DraftGo stores page routes without surrounding slashes. Accept either the
|
|
62
|
+
// stored form or the browser form users naturally paste into the CLI.
|
|
63
|
+
return String(value == null ? '' : value).trim().replace(/^\/+|\/+$/g, '');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function positiveLimit(value, fallback = 20) {
|
|
67
|
+
if (value == null) return fallback;
|
|
68
|
+
if (!/^\d+$/.test(String(value)) || Number(value) < 1 || Number(value) > 100) {
|
|
69
|
+
throw new Error('Map --limit must be an integer between 1 and 100.');
|
|
70
|
+
}
|
|
71
|
+
return Number(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resourceType(resource) {
|
|
75
|
+
return resource.resource_type || resource.type || 'unknown';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resourceMatches(resource, flags = {}) {
|
|
79
|
+
const route = flags.route == null ? null : normalizeRoute(flags.route);
|
|
80
|
+
const title = flags.title == null ? null : String(flags.title);
|
|
81
|
+
return (route == null || normalizeRoute(resource.route) === route)
|
|
82
|
+
&& (title == null || String(resource.title == null ? '' : resource.title) === title);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resourceKey(resource) {
|
|
86
|
+
const type = resourceType(resource);
|
|
87
|
+
if (type === 'custom_services') return `custom_services:${resource.resource_id}`;
|
|
88
|
+
return `${canonicalResourceType(type)}:${resource.resource_id}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function paginationFrom(payload, resourceTypeName) {
|
|
92
|
+
const cursor = nextCursor(payload);
|
|
93
|
+
if ((cursor == null || cursor === '') && claimsMorePages(payload)) {
|
|
94
|
+
throw new Error(`DraftGo resource_list (${resourceTypeName}) reported more pages without a cursor.`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
next_cursor: cursor == null || cursor === '' ? null : String(cursor),
|
|
98
|
+
has_more: claimsMorePages(payload) || (cursor != null && cursor !== ''),
|
|
99
|
+
total: reportedTotal(payload),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function listRemoteResourcePage(session, resourceTypeName, flags = {}, options = {}) {
|
|
104
|
+
const filtered = flags.route != null || flags.title != null;
|
|
105
|
+
const args = { resource_type: resourceTypeName, limit: positiveLimit(flags.limit) };
|
|
106
|
+
if (flags.cursor != null && flags.cursor !== '') args.cursor = String(flags.cursor);
|
|
107
|
+
if (filtered) {
|
|
108
|
+
// resource_search narrows the remote candidate set; the strict equality
|
|
109
|
+
// checks below keep --route/--title deterministic even when search is fuzzy.
|
|
110
|
+
args.query = flags.route != null ? normalizeRoute(flags.route) : String(flags.title);
|
|
111
|
+
}
|
|
112
|
+
const payload = await callStructured(
|
|
113
|
+
session,
|
|
114
|
+
filtered ? TOOL_NAMES.resourceSearch : TOOL_NAMES.resourceList,
|
|
115
|
+
args,
|
|
116
|
+
options,
|
|
117
|
+
);
|
|
118
|
+
return { resources: itemsFrom(payload).filter((resource) => resourceMatches(resource, flags)), pagination: paginationFrom(payload, resourceTypeName) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function listMapResources(session, resourceTypes, flags, options = {}) {
|
|
122
|
+
if (flags.cursor != null && flags.cursor !== '' && resourceTypes.length !== 1) {
|
|
123
|
+
throw new Error('Map --cursor requires exactly one --type.');
|
|
124
|
+
}
|
|
125
|
+
const groups = await allWithAbort(resourceTypes.map((resourceTypeName) =>
|
|
126
|
+
(queryOptions) => listRemoteResourcePage(session, resourceTypeName, flags, queryOptions)), options);
|
|
127
|
+
return {
|
|
128
|
+
resources: groups.flatMap((group) => group.resources),
|
|
129
|
+
pagination: Object.fromEntries(groups.map((group, index) => [resourceTypes[index], group.pagination])),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
54
133
|
async function listRemoteResourceType(session, resourceType, options = {}) {
|
|
55
134
|
const resources = [];
|
|
56
135
|
const seen = new Set();
|
|
@@ -92,19 +171,32 @@ function checkoutChanged(entry) {
|
|
|
92
171
|
async function mapCommand(projectDir, flags = {}) {
|
|
93
172
|
const resourceTypes = requestedResourceTypes(flags);
|
|
94
173
|
const config = loadProjectConfig(projectDir);
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
174
|
+
const searching = flags.route != null || flags.title != null;
|
|
175
|
+
const session = await openToolSession(config, [searching ? TOOL_NAMES.resourceSearch : TOOL_NAMES.resourceList]);
|
|
176
|
+
const remote = await listMapResources(session, resourceTypes, flags);
|
|
177
|
+
const manifestEntries = Object.values(loadManifest(projectDir).entries);
|
|
178
|
+
const selected = new Set(remote.resources.map(resourceKey));
|
|
179
|
+
// A filtered map is a focused inspection, so do not make unrelated local
|
|
180
|
+
// worktree state dominate its output. The unfiltered command retains the
|
|
181
|
+
// established full-worktree status behavior.
|
|
182
|
+
const statusEntries = searching
|
|
183
|
+
? manifestEntries.filter((entry) => entry.resource_type === 'custom_services'
|
|
184
|
+
? selected.has(`custom_services:${entry.resource_id}`)
|
|
185
|
+
: selected.has(`${canonicalResourceType(entry.resource_type)}:${entry.resource_id}`))
|
|
186
|
+
: manifestEntries;
|
|
187
|
+
const contentEntries = statusEntries.filter((entry) => entry.resource_type !== 'custom_services');
|
|
188
|
+
const serviceIds = statusEntries
|
|
189
|
+
.filter((entry) => entry.resource_type === 'custom_services')
|
|
190
|
+
.map((entry) => entry.resource_id);
|
|
191
|
+
const checkouts = (await allWithAbort([
|
|
101
192
|
async (options) => {
|
|
102
193
|
const [contentStatus, serviceStatus] = await Promise.all([inspectRemoteCheckouts(projectDir, {
|
|
103
194
|
config,
|
|
104
195
|
client: session.client,
|
|
105
196
|
tools: session.tools,
|
|
106
197
|
signal: options.signal,
|
|
107
|
-
|
|
198
|
+
entries: contentEntries,
|
|
199
|
+
}), customServices.inspectRemote(projectDir, { config, signal: options.signal, ids: serviceIds })]);
|
|
108
200
|
return [...contentStatus, ...serviceStatus].map((entry) => ({
|
|
109
201
|
...entry,
|
|
110
202
|
exists: entry.local_hash !== null,
|
|
@@ -112,28 +204,58 @@ async function mapCommand(projectDir, flags = {}) {
|
|
|
112
204
|
changed: checkoutChanged(entry),
|
|
113
205
|
}));
|
|
114
206
|
},
|
|
115
|
-
]);
|
|
116
|
-
const
|
|
207
|
+
]))[0];
|
|
208
|
+
const resources = remote.resources;
|
|
209
|
+
const counts = new Map();
|
|
210
|
+
for (const resource of resources) {
|
|
211
|
+
const type = resourceType(resource);
|
|
212
|
+
counts.set(type, (counts.get(type) || 0) + 1);
|
|
213
|
+
}
|
|
214
|
+
const checkoutStates = new Map();
|
|
215
|
+
for (const entry of checkouts) {
|
|
216
|
+
const state = entry.state || (!entry.exists ? 'missing' : entry.changed ? 'modified' : 'clean');
|
|
217
|
+
checkoutStates.set(state, (checkoutStates.get(state) || 0) + 1);
|
|
218
|
+
}
|
|
117
219
|
const result = {
|
|
118
220
|
server: config.server,
|
|
119
|
-
overview,
|
|
120
221
|
resources,
|
|
121
222
|
checkouts,
|
|
223
|
+
pagination: remote.pagination,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const summary = {
|
|
227
|
+
server: config.server,
|
|
228
|
+
summary: {
|
|
229
|
+
resources: {
|
|
230
|
+
returned: resources.length,
|
|
231
|
+
matched: resources.length,
|
|
232
|
+
returned_by_type: Object.fromEntries([...counts.entries()].sort()),
|
|
233
|
+
// This is the server's fuzzy-search/list candidate count before the
|
|
234
|
+
// client-side exact route/title check. It is null when not reported.
|
|
235
|
+
candidate_total_by_type: Object.fromEntries(Object.entries(remote.pagination)
|
|
236
|
+
.map(([type, page]) => [type, page.total])),
|
|
237
|
+
},
|
|
238
|
+
checkouts: { total: checkouts.length, by_state: Object.fromEntries([...checkoutStates.entries()].sort()) },
|
|
239
|
+
truncated: Object.values(remote.pagination).some((page) => page.has_more),
|
|
240
|
+
pagination: remote.pagination,
|
|
241
|
+
},
|
|
122
242
|
};
|
|
123
243
|
|
|
124
244
|
if (flags.output === 'json') {
|
|
125
|
-
console.log(JSON.stringify(result, null, 2));
|
|
245
|
+
console.log(JSON.stringify(flags.summary ? summary : result, null, 2));
|
|
126
246
|
return 0;
|
|
127
247
|
}
|
|
128
248
|
log.title('draftgo map');
|
|
129
|
-
log.info(`Remote resources: ${resources.length}`);
|
|
130
|
-
const counts = new Map();
|
|
131
|
-
for (const resource of resources) {
|
|
132
|
-
const type = resource.resource_type || resource.type || 'unknown';
|
|
133
|
-
counts.set(type, (counts.get(type) || 0) + 1);
|
|
134
|
-
}
|
|
249
|
+
log.info(`Remote resources returned: ${resources.length}`);
|
|
135
250
|
for (const [type, count] of [...counts.entries()].sort()) log.plain(` ${type}: ${count}`);
|
|
136
251
|
log.info(`Local checkouts: ${checkouts.length}`);
|
|
252
|
+
if (flags.summary) {
|
|
253
|
+
for (const [state, count] of [...checkoutStates.entries()].sort()) log.plain(` ${state}: ${count}`);
|
|
254
|
+
for (const [type, page] of Object.entries(remote.pagination)) {
|
|
255
|
+
if (page.has_more) log.plain(` ${type}: more results; use --type ${type} --cursor ${page.next_cursor}`);
|
|
256
|
+
}
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
137
259
|
for (const entry of checkouts) {
|
|
138
260
|
const state = entry.state || (!entry.exists ? 'missing' : entry.changed ? 'modified' : 'clean');
|
|
139
261
|
log.plain(` ${entry.resource_type} ${entry.resource_id}: ${state}`
|
|
@@ -149,4 +271,10 @@ module.exports.itemsFrom = itemsFrom;
|
|
|
149
271
|
module.exports.normalizeMapResourceType = normalizeMapResourceType;
|
|
150
272
|
module.exports.requestedResourceTypes = requestedResourceTypes;
|
|
151
273
|
module.exports.listRemoteResources = listRemoteResources;
|
|
274
|
+
module.exports.listRemoteResourcePage = listRemoteResourcePage;
|
|
275
|
+
module.exports.listMapResources = listMapResources;
|
|
276
|
+
module.exports.normalizeRoute = normalizeRoute;
|
|
277
|
+
module.exports.resourceMatches = resourceMatches;
|
|
278
|
+
module.exports.positiveLimit = positiveLimit;
|
|
279
|
+
module.exports.reportedTotal = reportedTotal;
|
|
152
280
|
module.exports.checkoutChanged = checkoutChanged;
|
package/src/commands/mcp.js
CHANGED
|
@@ -17,7 +17,7 @@ function classifyMcpFailure(error) {
|
|
|
17
17
|
const message = String(error && error.message || error || '');
|
|
18
18
|
const code = error && error.code;
|
|
19
19
|
const status = error && error.status;
|
|
20
|
-
if (status === 401 || status === 403) return '
|
|
20
|
+
if (status === 401 || status === 403) return 'API Key authentication or authorization failed.';
|
|
21
21
|
if (/session|uninitialized|initialize first|not initialized/i.test(message)
|
|
22
22
|
|| ['SESSION_EXPIRED', 'MCP_SESSION_EXPIRED', -32002].includes(code)) {
|
|
23
23
|
return 'The MCP session was lost or the server rejected the initialized state; check sticky sessions and service restarts.';
|
|
@@ -79,7 +79,7 @@ function status(projectDir, positional = [], flags = {}) {
|
|
|
79
79
|
let exitCode = 0;
|
|
80
80
|
try {
|
|
81
81
|
const config = loadProjectConfig(projectDir);
|
|
82
|
-
log.plain(` project configured (${config.server};
|
|
82
|
+
log.plain(` project configured (${config.server}; API Key present)`);
|
|
83
83
|
} catch (error) {
|
|
84
84
|
log.plain(` project not ready (${error.message})`);
|
|
85
85
|
exitCode = 1;
|