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/skill.js
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// Per-platform skill renderer.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// is shared once under .draftgo/skill-shared to avoid redundant copies.
|
|
5
|
+
// Every AI-tool target receives the complete Skill and its domain references
|
|
6
|
+
// in that target's own project directory.
|
|
8
7
|
//
|
|
9
8
|
// Templating in .md files:
|
|
10
9
|
// {{SKILL_DIR}} → platform's project-relative skill dir (forward slashes)
|
|
11
10
|
// {{SKILL_SCRIPTS}} → {{SKILL_DIR}}/scripts
|
|
12
|
-
// {{SKILL_SHARED}} → .draftgo/skill-shared
|
|
13
11
|
|
|
14
12
|
const path = require('path');
|
|
15
13
|
const fs = require('fs');
|
|
@@ -21,8 +19,6 @@ const {
|
|
|
21
19
|
} = require('./fsx');
|
|
22
20
|
|
|
23
21
|
const SKILL_SOURCE_DIR = path.join(RESOURCES_DIR, 'skill');
|
|
24
|
-
const SHARED_RESOURCE_DIR = path.join('.draftgo', 'skill-shared');
|
|
25
|
-
const SHARED_FILES = new Set(['references/api.json']);
|
|
26
22
|
|
|
27
23
|
function getPackageVersion() {
|
|
28
24
|
try {
|
|
@@ -80,8 +76,7 @@ function substitute(text, platform) {
|
|
|
80
76
|
const scriptsDir = `${skillDir}/scripts`;
|
|
81
77
|
return text
|
|
82
78
|
.replace(/\{\{SKILL_DIR\}\}/g, skillDir)
|
|
83
|
-
.replace(/\{\{SKILL_SCRIPTS\}\}/g, scriptsDir)
|
|
84
|
-
.replace(/\{\{SKILL_SHARED\}\}/g, SHARED_RESOURCE_DIR.replace(/\\/g, '/'));
|
|
79
|
+
.replace(/\{\{SKILL_SCRIPTS\}\}/g, scriptsDir);
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
function walk(root, onFile) {
|
|
@@ -98,7 +93,6 @@ function renderInto(destDir, platform) {
|
|
|
98
93
|
const srcRoot = SKILL_SOURCE_DIR;
|
|
99
94
|
walk(srcRoot, (absSrc) => {
|
|
100
95
|
const rel = path.relative(srcRoot, absSrc);
|
|
101
|
-
if (SHARED_FILES.has(rel.replace(/\\/g, '/'))) return;
|
|
102
96
|
const absDst = path.join(destDir, rel);
|
|
103
97
|
ensureDir(path.dirname(absDst));
|
|
104
98
|
if (absSrc.endsWith('.md')) {
|
|
@@ -116,21 +110,6 @@ function renderInto(destDir, platform) {
|
|
|
116
110
|
|
|
117
111
|
}
|
|
118
112
|
|
|
119
|
-
function ensureSharedResources(projectDir) {
|
|
120
|
-
const sharedRoot = path.join(projectDir, SHARED_RESOURCE_DIR);
|
|
121
|
-
for (const rel of SHARED_FILES) {
|
|
122
|
-
const src = path.join(SKILL_SOURCE_DIR, rel);
|
|
123
|
-
const dest = path.join(sharedRoot, rel);
|
|
124
|
-
if (exists(dest)) {
|
|
125
|
-
const sourceStat = fs.statSync(src);
|
|
126
|
-
const destStat = fs.statSync(dest);
|
|
127
|
-
if (sourceStat.size === destStat.size && destStat.mtimeMs >= sourceStat.mtimeMs) continue;
|
|
128
|
-
}
|
|
129
|
-
copyFile(src, dest);
|
|
130
|
-
}
|
|
131
|
-
return sharedRoot;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
113
|
function validateRenderedSkill(assetDir) {
|
|
135
114
|
const main = path.join(assetDir, 'SKILL.md');
|
|
136
115
|
if (!exists(main)) throw new Error('渲染结果缺少 SKILL.md');
|
|
@@ -140,7 +119,7 @@ function validateRenderedSkill(assetDir) {
|
|
|
140
119
|
if (!text.startsWith('---\n') || firstEnd < 0 || body.startsWith('---\n')) {
|
|
141
120
|
throw new Error('SKILL.md frontmatter 必须且只能有一个');
|
|
142
121
|
}
|
|
143
|
-
if (/\{\{(?:SKILL_DIR|SKILL_SCRIPTS
|
|
122
|
+
if (/\{\{(?:SKILL_DIR|SKILL_SCRIPTS)\}\}/.test(text)) {
|
|
144
123
|
throw new Error('SKILL.md 仍包含未替换占位符');
|
|
145
124
|
}
|
|
146
125
|
}
|
|
@@ -157,7 +136,8 @@ function ensureRuntime(projectDir) {
|
|
|
157
136
|
}
|
|
158
137
|
appendGitignoreLine(projectDir, '.draftgo/config.json');
|
|
159
138
|
appendGitignoreLine(projectDir, '.draftgo/token');
|
|
160
|
-
|
|
139
|
+
appendGitignoreLine(projectDir, '.draftgo/worktree/');
|
|
140
|
+
appendGitignoreLine(projectDir, '.draftgo/conflicts/');
|
|
161
141
|
}
|
|
162
142
|
|
|
163
143
|
function installPlatform(projectDir, platform, opts = {}) {
|
|
@@ -173,7 +153,6 @@ function installPlatform(projectDir, platform, opts = {}) {
|
|
|
173
153
|
return { path: platform.mainFile, skipped: true };
|
|
174
154
|
}
|
|
175
155
|
|
|
176
|
-
ensureSharedResources(projectDir);
|
|
177
156
|
const token = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
178
157
|
const stageAsset = `${destAsset}.tmp-${token}`;
|
|
179
158
|
const backupAsset = `${destAsset}.bak-${token}`;
|
|
@@ -259,6 +238,5 @@ module.exports = {
|
|
|
259
238
|
statusPlatform,
|
|
260
239
|
installAll,
|
|
261
240
|
ensureRuntime,
|
|
262
|
-
ensureSharedResources,
|
|
263
241
|
splitFrontmatter,
|
|
264
242
|
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { Readable } = require('stream');
|
|
5
|
+
const { DraftGoMcpClient } = require('../mcp/client');
|
|
6
|
+
const { BackendHttpError, WorktreeError } = require('./errors');
|
|
7
|
+
const { canonicalResourceType, mediaType, normalizeExtension } = require('./types');
|
|
8
|
+
const { normalizeSha256 } = require('./streams');
|
|
9
|
+
|
|
10
|
+
const METADATA_TOOL = 'draftgo_resource_get_metadata';
|
|
11
|
+
const MAX_ERROR_BYTES = 64 * 1024;
|
|
12
|
+
const MAX_JSON_BYTES = 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
function isObject(value) {
|
|
15
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toolNameMatches(name, expected) {
|
|
19
|
+
return name === expected
|
|
20
|
+
|| name.endsWith(`.${expected}`)
|
|
21
|
+
|| name.endsWith(`/${expected}`)
|
|
22
|
+
|| name.endsWith(`:${expected}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseJsonText(text, label) {
|
|
26
|
+
try { return JSON.parse(text); } catch {
|
|
27
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', `${label} did not return structured JSON metadata.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function unwrapToolResult(result) {
|
|
32
|
+
if (!isObject(result)) {
|
|
33
|
+
throw new WorktreeError('INVALID_BACKEND_RESPONSE', 'DraftGo metadata tool returned no structured result.');
|
|
34
|
+
}
|
|
35
|
+
if (isObject(result.structuredContent)) return result.structuredContent;
|
|
36
|
+
if (isObject(result.structured_content)) return result.structured_content;
|
|
37
|
+
if (isObject(result.data)) return result.data;
|
|
38
|
+
if (isObject(result.metadata) || isObject(result.resource)) return result;
|
|
39
|
+
if (Array.isArray(result.content)) {
|
|
40
|
+
const textPart = result.content.find((part) => part && part.type === 'text' && typeof part.text === 'string');
|
|
41
|
+
if (textPart && Buffer.byteLength(textPart.text, 'utf8') <= MAX_JSON_BYTES) {
|
|
42
|
+
return parseJsonText(textPart.text, 'DraftGo metadata tool');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function firstObject(...values) {
|
|
49
|
+
return values.find(isObject) || {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function firstValue(...values) {
|
|
53
|
+
return values.find((value) => value !== undefined && value !== null && value !== '');
|
|
54
|
+
}
|
|
55
|
+
|
|
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
|
+
function normalizeUrl(config, value, purpose) {
|
|
63
|
+
if (!value) return null;
|
|
64
|
+
let url;
|
|
65
|
+
try { url = new URL(String(value), `${config.server.replace(/\/+$/, '')}/`); } catch {
|
|
66
|
+
throw new WorktreeError('INVALID_BACKEND_URL', `DraftGo returned an invalid ${purpose} URL.`);
|
|
67
|
+
}
|
|
68
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) {
|
|
69
|
+
throw new WorktreeError('INVALID_BACKEND_URL', `DraftGo returned an unsafe ${purpose} URL.`);
|
|
70
|
+
}
|
|
71
|
+
const serverOrigin = new URL(config.server).origin;
|
|
72
|
+
const allowed = new Set([
|
|
73
|
+
serverOrigin,
|
|
74
|
+
...((config.checkout && config.checkout.allowed_origins) || []).map((origin) => new URL(origin).origin),
|
|
75
|
+
]);
|
|
76
|
+
if (!allowed.has(url.origin)) {
|
|
77
|
+
throw new WorktreeError(
|
|
78
|
+
'UNTRUSTED_BACKEND_ORIGIN',
|
|
79
|
+
`${purpose} URL origin is not allowlisted; refusing to send the SAT.`,
|
|
80
|
+
{ origin: url.origin },
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return url.toString();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeMetadata(config, resourceType, resourceId, payload) {
|
|
87
|
+
const outer = unwrapToolResult(payload);
|
|
88
|
+
const data = firstObject(outer.metadata, outer.resource, outer.data, outer);
|
|
89
|
+
const links = firstObject(data.links, outer.links, data.urls, outer.urls);
|
|
90
|
+
const checkoutConfig = firstObject(config.checkout);
|
|
91
|
+
const downloadTemplate = checkoutConfig.download_url_template;
|
|
92
|
+
const commitTemplate = checkoutConfig.commit_url_template;
|
|
93
|
+
const version = firstValue(data.base_version, data.version, data.revision, data.base_revision);
|
|
94
|
+
const hash = firstValue(data.content_hash, data.hash, data.sha256, data.base_hash);
|
|
95
|
+
const contentType = firstValue(data.content_type, data.contentType);
|
|
96
|
+
const backendExtension = firstValue(data.file_extension, data.extension);
|
|
97
|
+
const downloadRaw = firstValue(
|
|
98
|
+
data.download_url, data.downloadUrl, links.download, links.content, links.checkout,
|
|
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
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!contentType || !hash || version === undefined || !downloadRaw || !commitRaw) {
|
|
108
|
+
throw new WorktreeError(
|
|
109
|
+
'INCOMPLETE_CHECKOUT_METADATA',
|
|
110
|
+
'DraftGo checkout metadata must include content_type, SHA-256, base version/revision, download URL, and commit URL.',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const normalizedHash = normalizeSha256(hash);
|
|
114
|
+
if (!normalizedHash) throw new WorktreeError('INVALID_CONTENT_HASH', 'DraftGo returned an invalid content SHA-256.');
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
resource_type: canonicalResourceType(firstValue(data.resource_type, resourceType)),
|
|
118
|
+
resource_id: String(firstValue(data.resource_id, data.id, resourceId)),
|
|
119
|
+
title: String(firstValue(data.title, data.name, '')),
|
|
120
|
+
route: firstValue(data.route, null),
|
|
121
|
+
code: firstValue(data.code, null),
|
|
122
|
+
slug: firstValue(data.slug, null),
|
|
123
|
+
content_type: String(contentType),
|
|
124
|
+
file_extension: normalizeExtension(contentType, backendExtension),
|
|
125
|
+
content_size: data.content_size == null && data.size == null ? null : Number(firstValue(data.content_size, data.size)),
|
|
126
|
+
content_hash: normalizedHash,
|
|
127
|
+
base_version: data.base_version !== undefined ? data.base_version : (data.version !== undefined ? data.version : null),
|
|
128
|
+
base_revision: data.base_revision !== undefined ? data.base_revision : (data.revision !== undefined ? data.revision : null),
|
|
129
|
+
etag: firstValue(data.etag, data.e_tag, null),
|
|
130
|
+
updated_at: firstValue(data.updated_at, null),
|
|
131
|
+
updated_by: firstValue(data.updated_by, null),
|
|
132
|
+
download_url: normalizeUrl(config, downloadRaw, 'download'),
|
|
133
|
+
commit_url: commitRaw ? normalizeUrl(config, commitRaw, 'commit') : null,
|
|
134
|
+
commit_method: String(firstValue(data.commit_method, checkoutConfig.commit_method, 'PUT')).toUpperCase(),
|
|
135
|
+
raw: data,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function resolveMetadata(config, resourceType, resourceId, options = {}) {
|
|
140
|
+
if (options.metadata) return normalizeMetadata(config, resourceType, resourceId, options.metadata);
|
|
141
|
+
const client = options.client || new DraftGoMcpClient(config);
|
|
142
|
+
if (!options.clientInitialized) await client.initialize(options);
|
|
143
|
+
let toolName = METADATA_TOOL;
|
|
144
|
+
if (typeof client.listAllTools === 'function') {
|
|
145
|
+
const tools = options.tools || await client.listAllTools(options);
|
|
146
|
+
const found = tools.find((tool) => tool && toolNameMatches(String(tool.name || ''), METADATA_TOOL));
|
|
147
|
+
if (!found) throw new WorktreeError('MCP_TOOL_UNAVAILABLE', `${METADATA_TOOL} is not available.`);
|
|
148
|
+
toolName = found.name;
|
|
149
|
+
}
|
|
150
|
+
const result = await client.toolsCall(toolName, {
|
|
151
|
+
resource_type: canonicalResourceType(resourceType),
|
|
152
|
+
resource_id: String(resourceId),
|
|
153
|
+
}, options);
|
|
154
|
+
return normalizeMetadata(config, resourceType, resourceId, result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function authHeaders(config, extra = {}) {
|
|
158
|
+
return { Authorization: `Bearer ${config.token || config.sat}`, ...extra };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function readBounded(response, maximum = MAX_ERROR_BYTES) {
|
|
162
|
+
if (!response.body) return '';
|
|
163
|
+
const reader = response.body.getReader();
|
|
164
|
+
const chunks = [];
|
|
165
|
+
let size = 0;
|
|
166
|
+
let ended = false;
|
|
167
|
+
while (size < maximum) {
|
|
168
|
+
const { done, value } = await reader.read();
|
|
169
|
+
if (done) { ended = true; break; }
|
|
170
|
+
const chunk = Buffer.from(value);
|
|
171
|
+
const remaining = maximum - size;
|
|
172
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
173
|
+
size += Math.min(chunk.length, remaining);
|
|
174
|
+
}
|
|
175
|
+
if (!ended) await reader.cancel().catch(() => {});
|
|
176
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function errorForResponse(response, token) {
|
|
180
|
+
const text = await readBounded(response);
|
|
181
|
+
let details = {};
|
|
182
|
+
try { details = text ? JSON.parse(text) : {}; } catch { details = { response: text }; }
|
|
183
|
+
const error = firstObject(details.error, details);
|
|
184
|
+
const code = firstValue(error.code, details.code, response.status === 409 || response.status === 412
|
|
185
|
+
? 'RESOURCE_VERSION_CONFLICT' : 'BACKEND_HTTP_ERROR');
|
|
186
|
+
let message = String(firstValue(error.message, details.message, `DraftGo HTTP ${response.status}`));
|
|
187
|
+
if (token) message = message.split(String(token)).join('[REDACTED]');
|
|
188
|
+
return new BackendHttpError(response.status, code, message, isObject(error.details) ? error.details : details);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function download(config, metadata, options = {}) {
|
|
192
|
+
const response = await (options.fetch || fetch)(metadata.download_url, {
|
|
193
|
+
method: 'GET',
|
|
194
|
+
headers: authHeaders(config, { Accept: '*/*' }),
|
|
195
|
+
signal: options.signal,
|
|
196
|
+
});
|
|
197
|
+
if (!response.ok) throw await errorForResponse(response, config.token || config.sat);
|
|
198
|
+
return response;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function commit(config, metadata, localPath, current, options = {}) {
|
|
202
|
+
if (!metadata.commit_url) {
|
|
203
|
+
throw new WorktreeError(
|
|
204
|
+
'COMMIT_URL_UNAVAILABLE',
|
|
205
|
+
'DraftGo metadata did not provide a commit URL and no checkout.commit_url_template is configured.',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (!['POST', 'PUT', 'PATCH'].includes(metadata.commit_method)) {
|
|
209
|
+
throw new WorktreeError('INVALID_COMMIT_METHOD', 'DraftGo returned an unsupported commit method.');
|
|
210
|
+
}
|
|
211
|
+
const headers = authHeaders(config, {
|
|
212
|
+
'Content-Type': metadata.content_type,
|
|
213
|
+
'Content-Length': String(current.size),
|
|
214
|
+
'X-Content-SHA256': current.hash,
|
|
215
|
+
});
|
|
216
|
+
if (metadata.etag) headers['If-Match'] = String(metadata.etag);
|
|
217
|
+
else if (metadata.base_version != null) headers['If-Match'] = String(metadata.base_version);
|
|
218
|
+
else if (metadata.base_revision != null) headers['If-Match'] = String(metadata.base_revision);
|
|
219
|
+
if (metadata.base_version != null) headers['DraftGo-Base-Version'] = String(metadata.base_version);
|
|
220
|
+
if (metadata.base_revision != null) headers['DraftGo-Base-Revision'] = String(metadata.base_revision);
|
|
221
|
+
|
|
222
|
+
const response = await (options.fetch || fetch)(metadata.commit_url, {
|
|
223
|
+
method: metadata.commit_method,
|
|
224
|
+
headers,
|
|
225
|
+
body: fs.createReadStream(localPath),
|
|
226
|
+
duplex: 'half',
|
|
227
|
+
signal: options.signal,
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok) throw await errorForResponse(response, config.token || config.sat);
|
|
230
|
+
const text = await readBounded(response, MAX_JSON_BYTES);
|
|
231
|
+
if (!text.trim()) return {};
|
|
232
|
+
return parseJsonText(text, 'DraftGo commit endpoint');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function nodeReadable(response) {
|
|
236
|
+
if (!response.body) throw new WorktreeError('DOWNLOAD_INCOMPLETE', 'DraftGo response has no body.');
|
|
237
|
+
return typeof response.body.getReader === 'function' ? Readable.fromWeb(response.body) : response.body;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
METADATA_TOOL,
|
|
242
|
+
toolNameMatches,
|
|
243
|
+
unwrapToolResult,
|
|
244
|
+
normalizeMetadata,
|
|
245
|
+
resolveMetadata,
|
|
246
|
+
download,
|
|
247
|
+
commit,
|
|
248
|
+
nodeReadable,
|
|
249
|
+
errorForResponse,
|
|
250
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class WorktreeError extends Error {
|
|
4
|
+
constructor(code, message, details = {}) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'WorktreeError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.details = details;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class BackendHttpError extends WorktreeError {
|
|
13
|
+
constructor(status, code, message, details = {}) {
|
|
14
|
+
super(code || 'BACKEND_HTTP_ERROR', message || `Backend request failed with HTTP ${status}.`, details);
|
|
15
|
+
this.name = 'BackendHttpError';
|
|
16
|
+
this.status = status;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isVersionConflict(error) {
|
|
21
|
+
return Boolean(error) && (
|
|
22
|
+
error.code === 'RESOURCE_VERSION_CONFLICT'
|
|
23
|
+
|| error.status === 409
|
|
24
|
+
|| error.status === 412
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { WorktreeError, BackendHttpError, isVersionConflict };
|