@dai_ming/plugin-deliverables 1.0.22 → 1.1.0

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.
@@ -1,515 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- /**
5
- * deliverables MCP Server
6
- *
7
- * Provides tools for uploading AI-generated content (articles, games, images, etc.)
8
- * to OSS via the claw-gateway API and returning shareable URLs.
9
- *
10
- * Env vars (injected by helm_deployer.go):
11
- * CLAW_GATEWAY_URL — base URL of claw-gateway, e.g. http://claw-gateway:8080
12
- * CLAW_GATEWAY_API_KEY / OPENCLAW_GATEWAY_API_KEY — API key with access to /openclaw-gateway/be/*
13
- */
14
-
15
- var fs = require("fs");
16
- var http = require("http");
17
- var https = require("https");
18
- var path = require("path");
19
-
20
- var GATEWAY_URL = (process.env.CLAW_GATEWAY_URL || "http://claw-gateway:8080").replace(/\/$/, "");
21
- var GATEWAY_PUBLIC = (process.env.CLAW_GATEWAY_PUBLIC_URL || GATEWAY_URL).replace(/\/$/, "");
22
- // Some existing pods were created without CLAW_GATEWAY_API_KEY injected.
23
- // Keep env-driven behavior first, but provide a dev-compatible fallback to avoid
24
- // breaking deliverable uploads during rolling migration.
25
- var API_KEY = process.env.CLAW_GATEWAY_API_KEY || process.env.OPENCLAW_GATEWAY_API_KEY || "api-key-1";
26
-
27
- function trimString(value) {
28
- return typeof value === "string" ? value.trim() : "";
29
- }
30
-
31
- function hasExtension(fileName) {
32
- return /\.[A-Za-z0-9]+$/.test(trimString(fileName));
33
- }
34
-
35
- function looksLikeHTMLContent(contentText) {
36
- var text = trimString(contentText).toLowerCase();
37
- if (!text) {
38
- return false;
39
- }
40
- if (text.indexOf("<!doctype html") === 0 || text.indexOf("<html") === 0) {
41
- return true;
42
- }
43
- return text.indexOf("<head") >= 0 ||
44
- text.indexOf("<body") >= 0 ||
45
- text.indexOf("<main") >= 0 ||
46
- text.indexOf("<section") >= 0 ||
47
- text.indexOf("<article") >= 0 ||
48
- text.indexOf("<style") >= 0 ||
49
- text.indexOf("<script") >= 0;
50
- }
51
-
52
- function defaultExtensionForDeliverable(args) {
53
- var deliverableType = trimString(args && args.type);
54
- switch (deliverableType) {
55
- case "article":
56
- case "game":
57
- return looksLikeHTMLContent(args && args.content_text) ? ".html" : ".md";
58
- case "zip":
59
- return ".zip";
60
- case "ppt":
61
- return ".pptx";
62
- case "image":
63
- return ".png";
64
- case "video":
65
- return ".mp4";
66
- default:
67
- return "";
68
- }
69
- }
70
-
71
- function normalizeDeliverableFileName(args) {
72
- var fileName = trimString(args && args.file_name);
73
- if (!fileName && trimString(args && args.file_path)) {
74
- fileName = path.basename(trimString(args && args.file_path));
75
- }
76
- if (!fileName) {
77
- fileName = "file";
78
- }
79
- if (trimString(args && args.type) === "link") {
80
- return fileName;
81
- }
82
- if (Array.isArray(args && args.files) && args.files.length > 0) {
83
- return fileName;
84
- }
85
- if (hasExtension(fileName)) {
86
- return fileName;
87
- }
88
- var ext = defaultExtensionForDeliverable(args);
89
- return ext ? fileName + ext : fileName;
90
- }
91
-
92
- var TOOL_DEFS = [
93
- {
94
- name: "upload_deliverable",
95
- description: [
96
- "将 AI 生成的内容(文章、游戏、图片等)上传为交付物,返回可分享的下载/预览链接。",
97
- "单文件交付物:文本内容提供 content_text;PDF/PPT/图片/zip 等二进制文件优先提供 file_path,由工具读取文件并自动编码,不要手工复制 base64。",
98
- "多文件交付物(网页游戏/静态站点等):必须优先提供 files 列表,每项包含 name(相对路径)和 content_text、content_base64 或 file_path,不要先打 zip。",
99
- "静态多文件预览建议在根目录提供 index.html;需要单独启动端口/后端服务的项目不属于交付物预览范围,应走部署流程。",
100
- "返回的 download_url / preview_url 为长期可用链接。"
101
- ].join(" "),
102
- inputSchema: {
103
- type: "object",
104
- properties: {
105
- resource_id: {
106
- type: "string",
107
- description: "当前聊天框/会话的唯一 ID(必填)。对应消息上下文中的 resource_id 字段,直接使用该值即可。"
108
- },
109
- group_id: {
110
- type: "string",
111
- description: "当前会话所属的群聊 ID(可选)"
112
- },
113
- user_id: {
114
- type: "string",
115
- description: "请求交付物的用户 ID(可选)"
116
- },
117
- type: {
118
- type: "string",
119
- enum: ["article", "game", "zip", "image", "video", "ppt", "link"],
120
- description: "交付物类型:article=文章/文档,game=静态多文件网页/游戏(优先使用该类型并传 files,除非用户明确要 zip),zip=通用压缩包,image=图片,video=视频,ppt=演示文稿,link=外部链接(无需上传内容,fileName 填 URL)"
121
- },
122
- file_name: {
123
- type: "string",
124
- description: "用户可见的文件名,单文件必须带扩展名,例如 report.md 或 report.html。若用户未指定文档格式,默认使用 .md。多文件交付时这里应是目录名/项目名,不要写成 .zip,除非用户明确要求压缩包。"
125
- },
126
- content_text: {
127
- type: "string",
128
- description: "文本内容(文章、HTML、Markdown 等),单文件时使用"
129
- },
130
- content_base64: {
131
- type: "string",
132
- description: "Base64 编码的二进制内容,单文件时使用。二进制文件更推荐 file_path,避免复制/截断导致上传失败。"
133
- },
134
- file_path: {
135
- type: "string",
136
- description: "本地文件路径,推荐用于 PDF/PPT/图片/zip 等二进制交付物。工具会读取文件并自动生成 contentBase64。"
137
- },
138
- files: {
139
- type: "array",
140
- description: "多文件列表(游戏/静态站点场景)必须优先使用,服务端会保留目录结构,不再打 zip。静态预览场景建议包含根目录 index.html。",
141
- items: {
142
- type: "object",
143
- properties: {
144
- name: { type: "string", description: "文件在交付物目录内的相对路径,例如 index.html 或 assets/main.js" },
145
- content_text: { type: "string", description: "文本内容" },
146
- content_base64: { type: "string", description: "Base64 二进制内容" },
147
- file_path: { type: "string", description: "本地文件路径,工具会读取并自动编码" }
148
- },
149
- required: ["name"]
150
- }
151
- }
152
- },
153
- required: ["resource_id", "type", "file_name"]
154
- }
155
- }
156
- ];
157
-
158
- // ─── HTTP helpers ─────────────────────────────────────────────────────────────
159
-
160
- function parseURL(rawURL) {
161
- try {
162
- return new URL(rawURL);
163
- } catch(e) {
164
- return null;
165
- }
166
- }
167
-
168
- function httpRequest(method, path, body) {
169
- return new Promise(function(resolve, reject) {
170
- var parsed = parseURL(GATEWAY_URL + path);
171
- if (!parsed) {
172
- return reject(new Error("invalid gateway URL: " + GATEWAY_URL + path));
173
- }
174
- var isHTTPS = parsed.protocol === "https:";
175
- var transport = isHTTPS ? https : http;
176
- var bodyStr = body ? JSON.stringify(body) : "";
177
- var options = {
178
- hostname: parsed.hostname,
179
- port: parsed.port || (isHTTPS ? 443 : 80),
180
- path: parsed.pathname + (parsed.search || ""),
181
- method: method,
182
- headers: {
183
- "Content-Type": "application/json",
184
- "X-API-Key": API_KEY,
185
- "Content-Length": Buffer.byteLength(bodyStr)
186
- }
187
- };
188
- var req = transport.request(options, function(res) {
189
- var chunks = [];
190
- var traceID = res.headers["x-trace-id"] || "";
191
- res.on("data", function(c) { chunks.push(c); });
192
- res.on("end", function() {
193
- var text = Buffer.concat(chunks).toString();
194
- try {
195
- var obj = JSON.parse(text);
196
- if (res.statusCode >= 400) {
197
- reject(new Error(formatGatewayError(res.statusCode, obj.message || text, traceID)));
198
- } else {
199
- resolve({ body: obj, traceID: traceID });
200
- }
201
- } catch(e) {
202
- reject(new Error(formatGatewayError(res.statusCode, "non-JSON response: " + text.slice(0, 200), traceID)));
203
- }
204
- });
205
- });
206
- req.on("error", reject);
207
- if (bodyStr) req.write(bodyStr);
208
- req.end();
209
- });
210
- }
211
-
212
- function formatGatewayError(statusCode, message, traceID) {
213
- var msg = "gateway " + statusCode + ": " + message;
214
- if (traceID) {
215
- msg += " (trace_id=" + traceID + ")";
216
- }
217
- return msg;
218
- }
219
-
220
- function buildReplyMarkdown(opts) {
221
- var previewURL = opts.previewURL || "";
222
- var downloadURL = opts.downloadURL || "";
223
- var deliverableType = opts.type || "";
224
- var isDirectory = !!opts.isDirectory;
225
- var lines = [];
226
- if (previewURL) {
227
- lines.push("预览链接:[点击预览](" + previewURL + ")");
228
- }
229
- if (downloadURL) {
230
- if (isDirectory || deliverableType === "game") {
231
- lines.push("文件列表:[查看目录](" + downloadURL + ")");
232
- } else {
233
- lines.push("下载链接:[点击下载](" + downloadURL + ")");
234
- }
235
- }
236
- if (lines.length === 0 && !previewURL && !downloadURL) {
237
- return "交付物已上传成功。";
238
- }
239
- return lines.join("\n");
240
- }
241
-
242
- function normalizeBase64Content(value, label) {
243
- var text = trimString(value);
244
- if (!text) {
245
- return "";
246
- }
247
- if (/^data:/i.test(text)) {
248
- var commaIndex = text.indexOf(",");
249
- if (commaIndex >= 0) {
250
- text = text.slice(commaIndex + 1);
251
- }
252
- }
253
- text = text.replace(/\s+/g, "");
254
- if (!text) {
255
- return "";
256
- }
257
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(text)) {
258
- throw new Error("invalid base64 for " + label + ": contains non-base64 characters; use file_path for binary files");
259
- }
260
- var unpadded = text.replace(/=+$/, "");
261
- if (unpadded.indexOf("=") >= 0) {
262
- throw new Error("invalid base64 for " + label + ": padding must be at the end; use file_path for binary files");
263
- }
264
- var remainder = unpadded.length % 4;
265
- if (remainder === 1) {
266
- throw new Error("invalid base64 for " + label + ": truncated input; use file_path for binary files");
267
- }
268
- var padded = unpadded;
269
- if (remainder === 2) {
270
- padded += "==";
271
- } else if (remainder === 3) {
272
- padded += "=";
273
- }
274
- return Buffer.from(padded, "base64").toString("base64");
275
- }
276
-
277
- function candidateWorkspaceRoots() {
278
- var roots = [];
279
- [
280
- process.env.OPENCLAW_WORKSPACE_PATH,
281
- process.env.WORKSPACE_PATH,
282
- "/home/node/.openclaw/workspace-main",
283
- "/home/node/.openclaw/workspace",
284
- "/data/workspace",
285
- "/data/workspace-main"
286
- ].forEach(function(root) {
287
- root = trimString(root);
288
- if (root && roots.indexOf(root) < 0) {
289
- roots.push(root);
290
- }
291
- });
292
- try {
293
- fs.readdirSync("/data").forEach(function(entry) {
294
- if (entry && entry.indexOf("workspace-") === 0) {
295
- var root = path.join("/data", entry);
296
- if (roots.indexOf(root) < 0) {
297
- roots.push(root);
298
- }
299
- }
300
- });
301
- } catch (_err) {}
302
- return roots;
303
- }
304
-
305
- function resolveReadableFilePath(filePath) {
306
- var rawPath = trimString(filePath);
307
- if (!rawPath) {
308
- return "";
309
- }
310
- var candidates = [];
311
- function add(candidate) {
312
- if (candidate && candidates.indexOf(candidate) < 0) {
313
- candidates.push(candidate);
314
- }
315
- }
316
- if (path.isAbsolute(rawPath)) {
317
- add(rawPath);
318
- } else {
319
- add(path.resolve(process.cwd(), rawPath));
320
- candidateWorkspaceRoots().forEach(function(root) {
321
- add(path.join(root, rawPath));
322
- });
323
- }
324
- for (var i = 0; i < candidates.length; i += 1) {
325
- try {
326
- var stat = fs.statSync(candidates[i]);
327
- if (stat.isFile()) {
328
- return candidates[i];
329
- }
330
- } catch (_err) {}
331
- }
332
- return candidates[0] || rawPath;
333
- }
334
-
335
- function readFileAsBase64(filePath, label) {
336
- var rawPath = trimString(filePath);
337
- if (!rawPath) {
338
- return "";
339
- }
340
- var resolvedPath = resolveReadableFilePath(rawPath);
341
- var stat;
342
- try {
343
- stat = fs.statSync(resolvedPath);
344
- } catch (err) {
345
- throw new Error("cannot read " + label + " file_path " + rawPath + ": " + err.message);
346
- }
347
- if (!stat.isFile()) {
348
- throw new Error("cannot read " + label + " file_path " + rawPath + ": not a file");
349
- }
350
- return fs.readFileSync(resolvedPath).toString("base64");
351
- }
352
-
353
- function buildContentPayload(entry, label) {
354
- var filePath = trimString(entry && entry.file_path);
355
- if (filePath) {
356
- return {
357
- contentText: "",
358
- contentBase64: readFileAsBase64(filePath, label)
359
- };
360
- }
361
- return {
362
- contentText: (entry && entry.content_text) || "",
363
- contentBase64: normalizeBase64Content(entry && entry.content_base64, label)
364
- };
365
- }
366
-
367
- function deriveReleaseName() {
368
- var release = process.env.botID || process.env.OPENCLAW_RELEASE || process.env.BOT_ID || "";
369
- if (!release) {
370
- var tok = process.env.OPENCLAW_GATEWAY_TOKEN || "";
371
- if (tok.indexOf("oc-") === 0) release = tok.slice(3);
372
- }
373
- return release;
374
- }
375
-
376
- function buildUploadRequestBody(args) {
377
- args = args || {};
378
- var normalizedFileName = normalizeDeliverableFileName(args);
379
- var body = {
380
- resourceId: args.resource_id,
381
- groupId: args.group_id,
382
- userId: args.user_id,
383
- type: args.type,
384
- fileName: normalizedFileName,
385
- release: deriveReleaseName()
386
- };
387
-
388
- if (args.files && args.files.length > 0) {
389
- body.files = args.files.map(function(f, index) {
390
- var payload = buildContentPayload(f, "files[" + index + "] " + f.name);
391
- return {
392
- name: f.name,
393
- contentText: payload.contentText,
394
- contentBase64: payload.contentBase64
395
- };
396
- });
397
- } else {
398
- var singlePayload = buildContentPayload(args, "content_base64");
399
- body.contentText = singlePayload.contentText;
400
- body.contentBase64 = singlePayload.contentBase64;
401
- }
402
-
403
- return body;
404
- }
405
-
406
- // ─── Tool implementations ─────────────────────────────────────────────────────
407
-
408
- function uploadDeliverable(args) {
409
- return Promise.resolve().then(function() {
410
- return buildUploadRequestBody(args);
411
- }).then(function(body) {
412
- return httpRequest("POST", "/openclaw-gateway/be/deliverables", body);
413
- }).then(function(resp) {
414
- var d = resp.body.data || resp.body;
415
- // Prefer backend-aware previewUrl returned by gateway (OSS/output/link).
416
- // Fallback to legacy gateway preview endpoint for compatibility.
417
- var previewURL = d.previewUrl || (GATEWAY_PUBLIC + "/openclaw-gateway/preview/" + d.uuid);
418
- var isDirectory = !!(args.files && args.files.length > 0);
419
- if (!isDirectory && d.downloadUrl && previewURL && d.downloadUrl !== previewURL && /(?:\?|&)list=1(?:&|$)/.test(d.downloadUrl)) {
420
- isDirectory = true;
421
- }
422
- var replyMarkdown = buildReplyMarkdown({
423
- previewURL: previewURL,
424
- downloadURL: d.downloadUrl,
425
- type: args.type,
426
- isDirectory: isDirectory
427
- });
428
- return {
429
- uuid: d.uuid,
430
- backend: d.backend || "",
431
- download_url: d.downloadUrl,
432
- preview_url: previewURL,
433
- expire_at: d.expireAt,
434
- reply_markdown: replyMarkdown,
435
- trace_id: resp.traceID || "",
436
- message: replyMarkdown
437
- };
438
- });
439
- }
440
-
441
- // ─── MCP JSON-RPC loop ────────────────────────────────────────────────────────
442
-
443
- function callTool(name, args) {
444
- switch (name) {
445
- case "upload_deliverable": return uploadDeliverable(args);
446
- default: return Promise.reject(new Error("unknown tool: " + name));
447
- }
448
- }
449
-
450
- function send(obj) {
451
- process.stdout.write(JSON.stringify(obj) + "\n");
452
- }
453
-
454
- function handleMessage(msg) {
455
- var id = msg.id, method = msg.method, params = msg.params || {};
456
-
457
- switch (method) {
458
- case "initialize":
459
- send({ jsonrpc: "2.0", id: id, result: {
460
- protocolVersion: "2024-11-05",
461
- capabilities: { tools: {} },
462
- serverInfo: { name: "deliverables", version: "1.0.3" }
463
- }});
464
- return Promise.resolve();
465
-
466
- case "notifications/initialized":
467
- return Promise.resolve();
468
-
469
- case "tools/list":
470
- send({ jsonrpc: "2.0", id: id, result: { tools: TOOL_DEFS } });
471
- return Promise.resolve();
472
-
473
- case "tools/call":
474
- return callTool(params.name, params.arguments || {}).then(
475
- function(result) {
476
- send({ jsonrpc: "2.0", id: id, result: {
477
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
478
- }});
479
- },
480
- function(err) {
481
- send({ jsonrpc: "2.0", id: id, result: {
482
- content: [{ type: "text", text: "Error: " + err.message }],
483
- isError: true
484
- }});
485
- }
486
- );
487
-
488
- default:
489
- send({ jsonrpc: "2.0", id: id, error: { code: -32601, message: "Method not found: " + method } });
490
- return Promise.resolve();
491
- }
492
- }
493
-
494
- function startMCPServer() {
495
- var rl = require("readline").createInterface({ input: process.stdin, terminal: false });
496
- rl.on("line", function(line) {
497
- if (!line.trim()) return;
498
- var msg;
499
- try { msg = JSON.parse(line); } catch(e) { return; }
500
- handleMessage(msg).catch(function(err) {
501
- process.stderr.write("[deliverables] unhandled error: " + err.message + "\n");
502
- });
503
- });
504
- }
505
-
506
- module.exports.__test = {
507
- buildContentPayload: buildContentPayload,
508
- buildUploadRequestBody: buildUploadRequestBody,
509
- normalizeBase64Content: normalizeBase64Content,
510
- resolveReadableFilePath: resolveReadableFilePath
511
- };
512
-
513
- if (require.main === module) {
514
- startMCPServer();
515
- }
@@ -1,114 +0,0 @@
1
- "use strict";
2
-
3
- const assert = require("assert");
4
-
5
- process.env.CLAW_GATEWAY_PUBLIC_URL = "https://claw-gateway.csagentai.com";
6
-
7
- const plugin = require("../index.js");
8
- const {
9
- deliverableTypeForPath,
10
- extractFileReferencesFromText,
11
- findUntrustedOSSDeliverableURL,
12
- isDeliverableLinkLine,
13
- isTrustedDeliverableURL,
14
- selectPalzFileURL,
15
- splitDeliverableMessage,
16
- } = plugin.__test;
17
-
18
- const gatewayURL =
19
- "https://claw-gateway.csagentai.com/openclaw-gateway/output/user/res/uuid/sample.pdf";
20
- const bogusOSSURL =
21
- "https://palz-deliverable.oss-cn-shanghai.aliyuncs.com/user/res/sample.pdf?OSSAccessKeyId=fake";
22
-
23
- assert.strictEqual(isTrustedDeliverableURL(gatewayURL), true);
24
- assert.strictEqual(isTrustedDeliverableURL(bogusOSSURL), false);
25
- assert.strictEqual(
26
- isDeliverableLinkLine(`预览链接:[点击预览](${gatewayURL})`),
27
- true,
28
- );
29
- assert.strictEqual(
30
- isDeliverableLinkLine(`预览链接:[点击预览](${bogusOSSURL})`),
31
- false,
32
- );
33
-
34
- const split = splitDeliverableMessage(
35
- [
36
- "我生成了一个 1 页 PDF。",
37
- "",
38
- `预览链接:[点击预览](${gatewayURL})`,
39
- `下载链接:[点击下载](${gatewayURL})`,
40
- ].join("\n"),
41
- );
42
- assert.ok(split);
43
- assert.strictEqual(split.primaryLinkUrl, gatewayURL);
44
- assert.strictEqual(split.fileLinkUrl, gatewayURL);
45
-
46
- const outputURL =
47
- "https://claw-gateway.csagentai.com/openclaw-gateway/output/user/res/uuid/7a9e27b7-08ca-4453-81e8-fe6a630d4566/report.md";
48
- const previewURL =
49
- "https://claw-gateway.csagentai.com/openclaw-gateway/preview/7a9e27b7-08ca-4453-81e8-fe6a630d4566";
50
- assert.strictEqual(
51
- selectPalzFileURL([
52
- { line: `预览链接:[点击预览](${previewURL})`, url: previewURL },
53
- { line: `下载链接:[点击下载](${outputURL})`, url: outputURL },
54
- ]),
55
- outputURL,
56
- );
57
-
58
- assert.strictEqual(
59
- splitDeliverableMessage(
60
- [
61
- "我生成了一个 1 页 PDF。",
62
- "",
63
- `预览链接:[点击预览](${bogusOSSURL})`,
64
- `下载链接:[点击下载](${bogusOSSURL})`,
65
- ].join("\n"),
66
- ),
67
- null,
68
- );
69
- assert.strictEqual(
70
- findUntrustedOSSDeliverableURL(
71
- [
72
- "我生成了一个 1 页 PDF。",
73
- "",
74
- `预览链接:[点击预览](${bogusOSSURL})`,
75
- ].join("\n"),
76
- ),
77
- bogusOSSURL,
78
- );
79
- assert.strictEqual(
80
- findUntrustedOSSDeliverableURL(
81
- [
82
- "我生成了一个大文件。",
83
- "",
84
- `预览链接:[点击预览](${gatewayURL})`,
85
- `下载链接:[点击下载](${bogusOSSURL})`,
86
- ].join("\n"),
87
- ),
88
- "",
89
- );
90
-
91
- const multiSplit = splitDeliverableMessage(
92
- [
93
- "已生成多个文件。",
94
- "",
95
- `预览链接:[设计文档](${gatewayURL})`,
96
- `下载链接:[测试报告](https://claw-gateway.csagentai.com/openclaw-gateway/output/user/res/uuid/report.md)`,
97
- ].join("\n"),
98
- );
99
- assert.ok(multiSplit);
100
- assert.strictEqual(multiSplit.linkUrls.length, 2);
101
-
102
- const refs = extractFileReferencesFromText(
103
- "设计文档:`game-brief.md`\n开发路径:/data/workspace-lobster_abc/output/app/index.html\n相对路径 output/report.pdf",
104
- );
105
- assert.deepStrictEqual(
106
- refs.map((ref) => ref.value),
107
- ["/data/workspace-lobster_abc/output/app/index.html", "game-brief.md", "output/report.pdf"],
108
- );
109
-
110
- assert.strictEqual(deliverableTypeForPath("intro.pdf", false), "article");
111
- assert.strictEqual(deliverableTypeForPath("cover.png", false), "image");
112
- assert.strictEqual(deliverableTypeForPath("slides.pptx", false), "ppt");
113
- assert.strictEqual(deliverableTypeForPath("dist.zip", false), "zip");
114
- assert.strictEqual(deliverableTypeForPath("/data/workspace-lobster_abc/output/site", true), "game");
@@ -1,60 +0,0 @@
1
- "use strict";
2
-
3
- const assert = require("assert");
4
- const fs = require("fs");
5
- const os = require("os");
6
- const path = require("path");
7
-
8
- const {
9
- buildUploadRequestBody,
10
- normalizeBase64Content,
11
- resolveReadableFilePath,
12
- } = require("../mcp-servers/deliverables.js").__test;
13
-
14
- assert.strictEqual(
15
- normalizeBase64Content("SGVs\nbG8", "content_base64"),
16
- Buffer.from("Hello").toString("base64"),
17
- );
18
- assert.strictEqual(
19
- normalizeBase64Content("data:application/pdf;base64,JVBERi0xLjM", "content_base64"),
20
- Buffer.from("%PDF-1.3").toString("base64"),
21
- );
22
- assert.throws(
23
- () => normalizeBase64Content("SGVsb", "content_base64"),
24
- /truncated input/,
25
- );
26
-
27
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "deliverables-mcp-"));
28
- process.env.OPENCLAW_WORKSPACE_PATH = tmpDir;
29
- const pdfPath = path.join(tmpDir, "sample.pdf");
30
- const pdfBytes = Buffer.from("%PDF-1.3\nsample\n", "utf8");
31
- fs.writeFileSync(pdfPath, pdfBytes);
32
-
33
- const body = buildUploadRequestBody({
34
- resource_id: "res-1",
35
- group_id: "group-1",
36
- user_id: "user-1",
37
- type: "article",
38
- file_name: "sample.pdf",
39
- file_path: pdfPath,
40
- content_base64: "not-used",
41
- });
42
-
43
- assert.strictEqual(body.resourceId, "res-1");
44
- assert.strictEqual(body.fileName, "sample.pdf");
45
- assert.strictEqual(body.contentText, "");
46
- assert.strictEqual(body.contentBase64, pdfBytes.toString("base64"));
47
-
48
- const outputDir = path.join(tmpDir, "output");
49
- fs.mkdirSync(outputDir, { recursive: true });
50
- const relativeDoc = path.join(outputDir, "relative.md");
51
- fs.writeFileSync(relativeDoc, "# Relative\n", "utf8");
52
- assert.strictEqual(resolveReadableFilePath("output/relative.md"), relativeDoc);
53
-
54
- const relativeBody = buildUploadRequestBody({
55
- resource_id: "res-1",
56
- type: "article",
57
- file_name: "relative.md",
58
- file_path: "output/relative.md",
59
- });
60
- assert.strictEqual(relativeBody.contentBase64, Buffer.from("# Relative\n").toString("base64"));