draftgo-cli 3.0.38 → 3.0.39
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/package.json +1 -1
- package/resources/skill/manifest.json +1 -1
- package/src/mcp/tools.js +12 -2
- package/src/worktree/backend.js +114 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "draftgo-cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.39",
|
|
4
4
|
"description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"draftgo": "bin/draftgo.js"
|
package/src/mcp/tools.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { DraftGoMcpClient } = require('./client');
|
|
4
4
|
const { WorktreeError } = require('../worktree/errors');
|
|
5
|
-
const { unwrapToolResult, toolNameMatches } = require('../worktree/backend');
|
|
5
|
+
const { unwrapToolResult, unwrapProtectedResult, toolNameMatches } = require('../worktree/backend');
|
|
6
6
|
|
|
7
7
|
const TOOL_NAMES = Object.freeze({
|
|
8
8
|
projectOverview: 'draftgo_project_overview',
|
|
@@ -15,6 +15,13 @@ const TOOL_NAMES = Object.freeze({
|
|
|
15
15
|
apiCall: 'draftgo_api_call',
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
const PROTECTED_RESOURCE_TOOLS = new Set([
|
|
19
|
+
TOOL_NAMES.resourceList,
|
|
20
|
+
TOOL_NAMES.resourceSearch,
|
|
21
|
+
TOOL_NAMES.resourceMetadata,
|
|
22
|
+
TOOL_NAMES.resourceFragment,
|
|
23
|
+
]);
|
|
24
|
+
|
|
18
25
|
async function openToolSession(config, expected = [], options = {}) {
|
|
19
26
|
const client = options.client || new DraftGoMcpClient(config);
|
|
20
27
|
await client.initialize(options);
|
|
@@ -31,7 +38,10 @@ async function openToolSession(config, expected = [], options = {}) {
|
|
|
31
38
|
async function callStructured(session, expectedName, args = {}, options = {}) {
|
|
32
39
|
const name = session.names[expectedName] || expectedName;
|
|
33
40
|
const result = await session.client.toolsCall(name, args, options);
|
|
34
|
-
|
|
41
|
+
const data = unwrapToolResult(result);
|
|
42
|
+
return PROTECTED_RESOURCE_TOOLS.has(expectedName)
|
|
43
|
+
? unwrapProtectedResult(data, expectedName)
|
|
44
|
+
: data;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
module.exports = { TOOL_NAMES, openToolSession, callStructured };
|
package/src/worktree/backend.js
CHANGED
|
@@ -24,25 +24,111 @@ function toolNameMatches(name, expected) {
|
|
|
24
24
|
|
|
25
25
|
function parseJsonText(text, label) {
|
|
26
26
|
try { return JSON.parse(text); } catch {
|
|
27
|
-
throw new WorktreeError('INVALID_BACKEND_RESPONSE', `${label} did not return structured JSON
|
|
27
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', `${label} did not return structured JSON.`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
31
|
+
function hasOwn(value, key) {
|
|
32
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isMcpPayload(value) {
|
|
36
|
+
return isObject(value) && (
|
|
37
|
+
hasOwn(value, 'jsonrpc')
|
|
38
|
+
|| hasOwn(value, 'result')
|
|
39
|
+
|| hasOwn(value, 'structuredContent')
|
|
40
|
+
|| hasOwn(value, 'structured_content')
|
|
41
|
+
|| hasOwn(value, 'content')
|
|
42
|
+
|| hasOwn(value, 'isError')
|
|
43
|
+
|| hasOwn(value, 'ok')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toolError(envelope, fallbackCode, fallbackMessage) {
|
|
48
|
+
const error = isObject(envelope && envelope.error) ? envelope.error : {};
|
|
49
|
+
return new WorktreeError(
|
|
50
|
+
String(firstValue(error.code, fallbackCode)),
|
|
51
|
+
String(firstValue(error.message, fallbackMessage)),
|
|
52
|
+
isObject(error.details) ? error.details : {},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unwrapToolResult(payload) {
|
|
57
|
+
if (!isObject(payload)) {
|
|
58
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP tool returned no structured result.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let result = payload;
|
|
62
|
+
if (hasOwn(payload, 'jsonrpc') || hasOwn(payload, 'result')) {
|
|
63
|
+
if (isObject(payload.error)) {
|
|
64
|
+
throw toolError({ error: payload.error }, 'MCP_RPC_ERROR', 'DraftGo MCP request failed.');
|
|
65
|
+
}
|
|
66
|
+
if (!isObject(payload.result)) {
|
|
67
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned no tool result.');
|
|
68
|
+
}
|
|
69
|
+
result = payload.result;
|
|
70
|
+
}
|
|
71
|
+
|
|
32
72
|
if (!isObject(result)) {
|
|
33
|
-
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo
|
|
73
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned an invalid tool result.');
|
|
74
|
+
}
|
|
75
|
+
if (hasOwn(result, 'isError') && typeof result.isError !== 'boolean') {
|
|
76
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned an invalid isError value.');
|
|
34
77
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
|
|
79
|
+
let envelope;
|
|
80
|
+
if (hasOwn(result, 'structuredContent')) {
|
|
81
|
+
if (!isObject(result.structuredContent)) {
|
|
82
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned invalid structuredContent.');
|
|
83
|
+
}
|
|
84
|
+
envelope = result.structuredContent;
|
|
85
|
+
} else if (hasOwn(result, 'structured_content')) {
|
|
86
|
+
if (!isObject(result.structured_content)) {
|
|
87
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP returned invalid structured_content.');
|
|
88
|
+
}
|
|
89
|
+
envelope = result.structured_content;
|
|
90
|
+
} else if (Array.isArray(result.content)) {
|
|
40
91
|
const textPart = result.content.find((part) => part && part.type === 'text' && typeof part.text === 'string');
|
|
41
92
|
if (textPart && Buffer.byteLength(textPart.text, 'utf8') <= MAX_JSON_BYTES) {
|
|
42
|
-
|
|
93
|
+
envelope = parseJsonText(textPart.text, 'DraftGo MCP tool');
|
|
43
94
|
}
|
|
95
|
+
} else if (hasOwn(result, 'ok')) {
|
|
96
|
+
envelope = result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isObject(envelope)) {
|
|
100
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo MCP tool returned no structured JSON result.');
|
|
101
|
+
}
|
|
102
|
+
if (result.isError === true) {
|
|
103
|
+
throw toolError(envelope, 'MCP_TOOL_ERROR', 'DraftGo MCP tool reported an error.');
|
|
104
|
+
}
|
|
105
|
+
if (envelope.ok !== true) {
|
|
106
|
+
throw toolError(envelope, 'INVALID_BACKEND_RESPONSE', 'DraftGo MCP tool did not report success.');
|
|
44
107
|
}
|
|
45
|
-
return
|
|
108
|
+
return envelope.data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function unwrapProtectedResult(data, label = 'DraftGo resource tool') {
|
|
112
|
+
if (!isObject(data) || !hasOwn(data, 'value') || typeof data.omitted !== 'boolean') {
|
|
113
|
+
throw new WorktreeError(
|
|
114
|
+
'INVALID_PROTECTED_RESULT',
|
|
115
|
+
`${label} returned an invalid protected result.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (data.artifacts !== undefined && !Array.isArray(data.artifacts)) {
|
|
119
|
+
throw new WorktreeError(
|
|
120
|
+
'INVALID_PROTECTED_RESULT',
|
|
121
|
+
`${label} returned invalid artifact descriptors.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (data.omitted || (Array.isArray(data.artifacts) && data.artifacts.length > 0)) {
|
|
125
|
+
throw new WorktreeError(
|
|
126
|
+
'PROTECTED_RESULT_OMITTED',
|
|
127
|
+
`${label} omitted fields required by the CLI.`,
|
|
128
|
+
{ artifacts: data.artifacts || [] },
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return data.value;
|
|
46
132
|
}
|
|
47
133
|
|
|
48
134
|
function firstObject(...values) {
|
|
@@ -53,12 +139,6 @@ function firstValue(...values) {
|
|
|
53
139
|
return values.find((value) => value !== undefined && value !== null && value !== '');
|
|
54
140
|
}
|
|
55
141
|
|
|
56
|
-
function fillTemplate(template, resourceType, resourceId) {
|
|
57
|
-
return String(template)
|
|
58
|
-
.replace(/\{resource_type\}/g, encodeURIComponent(resourceType))
|
|
59
|
-
.replace(/\{resource_id\}/g, encodeURIComponent(String(resourceId)));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
142
|
function normalizeUrl(config, value, purpose) {
|
|
63
143
|
if (!value) return null;
|
|
64
144
|
let url;
|
|
@@ -84,25 +164,20 @@ function normalizeUrl(config, value, purpose) {
|
|
|
84
164
|
}
|
|
85
165
|
|
|
86
166
|
function normalizeMetadata(config, resourceType, resourceId, payload) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
167
|
+
const data = isMcpPayload(payload)
|
|
168
|
+
? unwrapProtectedResult(unwrapToolResult(payload), 'DraftGo metadata tool')
|
|
169
|
+
: payload;
|
|
170
|
+
if (!isObject(data)) {
|
|
171
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo returned invalid checkout metadata.');
|
|
172
|
+
}
|
|
173
|
+
const checkout = isObject(data.checkout) ? data.checkout : {};
|
|
90
174
|
const checkoutConfig = firstObject(config.checkout);
|
|
91
|
-
const downloadTemplate = checkoutConfig.download_url_template;
|
|
92
|
-
const commitTemplate = checkoutConfig.commit_url_template;
|
|
93
175
|
const version = firstValue(data.base_version, data.version, data.revision, data.base_revision);
|
|
94
|
-
const hash =
|
|
95
|
-
const contentType =
|
|
96
|
-
const backendExtension =
|
|
97
|
-
const downloadRaw = firstValue(
|
|
98
|
-
|
|
99
|
-
downloadTemplate && fillTemplate(downloadTemplate, resourceType, resourceId),
|
|
100
|
-
);
|
|
101
|
-
const commitRaw = firstValue(
|
|
102
|
-
data.commit_url, data.commitUrl, data.upload_url, data.uploadUrl,
|
|
103
|
-
links.commit, links.upload,
|
|
104
|
-
commitTemplate && fillTemplate(commitTemplate, resourceType, resourceId),
|
|
105
|
-
);
|
|
176
|
+
const hash = data.content_hash;
|
|
177
|
+
const contentType = data.content_type;
|
|
178
|
+
const backendExtension = data.file_extension;
|
|
179
|
+
const downloadRaw = firstValue(checkout.download, data.download_url);
|
|
180
|
+
const commitRaw = firstValue(checkout.commit, data.commit_url);
|
|
106
181
|
|
|
107
182
|
if (!contentType || !hash || version === undefined || !downloadRaw || !commitRaw) {
|
|
108
183
|
throw new WorktreeError(
|
|
@@ -115,18 +190,18 @@ function normalizeMetadata(config, resourceType, resourceId, payload) {
|
|
|
115
190
|
|
|
116
191
|
return {
|
|
117
192
|
resource_type: canonicalResourceType(firstValue(data.resource_type, resourceType)),
|
|
118
|
-
resource_id: String(firstValue(data.resource_id,
|
|
119
|
-
title: String(firstValue(data.title,
|
|
193
|
+
resource_id: String(firstValue(data.resource_id, resourceId)),
|
|
194
|
+
title: String(firstValue(data.title, '')),
|
|
120
195
|
route: firstValue(data.route, null),
|
|
121
196
|
code: firstValue(data.code, null),
|
|
122
197
|
slug: firstValue(data.slug, null),
|
|
123
198
|
content_type: String(contentType),
|
|
124
199
|
file_extension: normalizeExtension(contentType, backendExtension),
|
|
125
|
-
content_size: data.content_size == null
|
|
200
|
+
content_size: data.content_size == null ? null : Number(data.content_size),
|
|
126
201
|
content_hash: normalizedHash,
|
|
127
202
|
base_version: data.base_version !== undefined ? data.base_version : (data.version !== undefined ? data.version : null),
|
|
128
203
|
base_revision: data.base_revision !== undefined ? data.base_revision : (data.revision !== undefined ? data.revision : null),
|
|
129
|
-
etag: firstValue(data.etag,
|
|
204
|
+
etag: firstValue(data.etag, null),
|
|
130
205
|
updated_at: firstValue(data.updated_at, null),
|
|
131
206
|
updated_by: firstValue(data.updated_by, null),
|
|
132
207
|
download_url: normalizeUrl(config, downloadRaw, 'download'),
|
|
@@ -202,7 +277,7 @@ async function commit(config, metadata, localPath, current, options = {}) {
|
|
|
202
277
|
if (!metadata.commit_url) {
|
|
203
278
|
throw new WorktreeError(
|
|
204
279
|
'COMMIT_URL_UNAVAILABLE',
|
|
205
|
-
'DraftGo metadata did not provide a commit URL
|
|
280
|
+
'DraftGo metadata did not provide a commit URL.',
|
|
206
281
|
);
|
|
207
282
|
}
|
|
208
283
|
if (!['POST', 'PUT', 'PATCH'].includes(metadata.commit_method)) {
|
|
@@ -241,6 +316,7 @@ module.exports = {
|
|
|
241
316
|
METADATA_TOOL,
|
|
242
317
|
toolNameMatches,
|
|
243
318
|
unwrapToolResult,
|
|
319
|
+
unwrapProtectedResult,
|
|
244
320
|
normalizeMetadata,
|
|
245
321
|
resolveMetadata,
|
|
246
322
|
download,
|