dsh-claude-slides 2.9.2 → 2.10.1

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/lib/index.js CHANGED
@@ -1,484 +1,32 @@
1
- // dsh-claude-slides 2.9.2: built from baoyu-design.
1
+ // dsh-claude-slides 2.10.1: built from baoyu-design.
2
2
  var define_PLUGIN_default = { name: "claude-slides", provider: "dsh-claude-slides", skills: [{ name: "claude-slides", description: "Make and revise slide decks in the Slides artifact format (project/deck.json plus one HTML file per slide) as local files, then open them in a local slide editor where the user can present, retype, drag, and export PDF, HTML or PowerPoint. Use when the user asks for an editable slide deck or presentation in this format, names claude-slides or the Slides type, or wants to revise a folder that holds project/deck.json. Not for a one-off single-file HTML deck or prototype.", comments: true }] };
3
3
 
4
- import { spawn as spawn2 } from "node:child_process";
5
- import { randomUUID } from "node:crypto";
6
- import { statSync as statSync2 } from "node:fs";
7
4
  import { readFile } from "node:fs/promises";
8
- import { basename as basename2, delimiter, dirname as dirname2, isAbsolute, join as join2, posix, sep, win32 } from "node:path";
5
+ import { dirname as dirname2, join as join2, posix, sep, win32 } from "node:path";
9
6
  import { fileURLToPath } from "node:url";
10
7
 
11
- import { spawn } from "node:child_process";
12
- import { createHash } from "node:crypto";
13
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
14
- import { homedir } from "node:os";
15
- import { basename, dirname, join, parse } from "node:path";
16
-
17
- var base = (skill) => `/api/baoyu-design.${skill}.artifacts`;
18
- var listPath = base;
19
- var openPath = (skill) => `${base(skill)}.open`;
20
- var previewPath = (skill) => `${base(skill)}.preview`;
21
- var releasePath = (skill) => `${base(skill)}.release`;
22
- var thumbnailPath = (skill) => `${base(skill)}.thumbnail`;
23
- var updatePath = (skill) => `${base(skill)}.update`;
24
- var deletePath = (skill) => `${base(skill)}.delete`;
25
- var restorePath = (skill) => `${base(skill)}.restore`;
26
-
27
- var TITLES = {
28
- "claude-slides": ["project/deck.json", "title"],
29
- "claude-design": ["project/canvas.json", "title"],
30
- "claude-design-system": ["project/design-system.json", "title"],
31
- "claude-dashboard": ["db/dash/meta.json", "title"],
32
- "claude-docs": ["doc.json", "name"],
33
- "claude-motion": ["db/film/meta.json", "title"]
34
- };
35
- var OPEN_TIMEOUT_MS = 3e4;
36
- var TITLE_MAX = 200;
37
- var EDITOR_FILES = [".artifact-server.json", ".artifact-server.log", ".sharing.json", ".gitignore", ".DS_Store", "comments.json", "history", "exports"];
38
- var FOLDER = {
39
- "claude-slides": ["project"],
40
- "claude-design": ["project"],
41
- "claude-design-system": ["project"],
42
- "claude-dashboard": ["db", "_blobs", "src"],
43
- "claude-docs": ["doc.json", "tabs", "_blobs", ".sessions.json"],
44
- "claude-motion": ["db", "_blobs", "src"]
45
- };
46
- var NOT_CONTENT = /* @__PURE__ */ new Set([...EDITOR_FILES, ".sessions.json", ".git", "node_modules"]);
47
- var STAMP_MAX_ENTRIES = 2e4;
48
- function dshHome() {
49
- const set = process.env.DSH_HOME?.trim();
50
- return set ? set : join(homedir(), ".dsh");
51
- }
52
- function artifactsDir() {
53
- return join(dshHome(), "baoyu-design", "artifacts");
54
- }
55
- var folderHash = (root) => createHash("sha256").update(root).digest("hex").slice(0, 24);
56
- function recordFile(root) {
57
- return join(artifactsDir(), `${folderHash(root)}.json`);
58
- }
59
- function thumbnailFile(root) {
60
- return join(dshHome(), "baoyu-design", "thumbnails", `${folderHash(root)}.jpg`);
61
- }
62
- function trashDir() {
63
- return join(dshHome(), "baoyu-design", "trash");
64
- }
65
- function artifactTitle(skill, root) {
66
- const at = TITLES[skill];
67
- if (at === void 0) return null;
68
- try {
69
- const value = JSON.parse(readFileSync(join(root, at[0]), "utf8"))[at[1]];
70
- const title = typeof value === "string" ? value.trim() : "";
71
- if (title && title !== "project") return title;
72
- } catch {
73
- }
74
- return basename(root);
75
- }
76
- function isRecord(value) {
77
- const r = value;
78
- return typeof r === "object" && r !== null && r.v === 1 && typeof r.root === "string" && typeof r.skill === "string" && typeof r.plugin === "string" && typeof r.sessionId === "string" && typeof r.title === "string" && typeof r.openedAt === "string";
79
- }
80
- function readRecord(file) {
81
- try {
82
- const value = JSON.parse(readFileSync(file, "utf8"));
83
- return isRecord(value) ? value : null;
84
- } catch {
85
- return null;
86
- }
87
- }
88
- function writeWhole(file, data) {
89
- mkdirSync(dirname(file), { recursive: true });
90
- const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
91
- writeFileSync(tmp, data);
92
- renameSync(tmp, file);
93
- }
94
- function writeRecord(record) {
95
- writeWhole(recordFile(record.root), `${JSON.stringify(record, null, 2)}
96
- `);
97
- }
98
- function recordOpened(skill, plugin, sessionId, root, now = /* @__PURE__ */ new Date()) {
99
- const file = recordFile(root);
100
- const before = readRecord(file);
101
- const mine = before?.root === root ? before : null;
102
- const record = {
103
- v: 1,
104
- root,
105
- skill,
106
- plugin,
107
- sessionId,
108
- title: artifactTitle(skill, root) ?? before?.title ?? basename(root),
109
- openedAt: now.toISOString(),
110
- firstOpenedAt: mine ? mine.firstOpenedAt ?? mine.openedAt : now.toISOString(),
111
- ...mine?.pinned ? { pinned: true } : {},
112
- ...mine?.thumbStamp ? { thumbStamp: mine.thumbStamp } : {}
113
- };
114
- writeRecord(record);
115
- return record;
116
- }
117
- function contentStamp(root) {
118
- let newest = 0;
119
- let entries = 0;
120
- const walk = (dir, top) => {
121
- let names;
122
- try {
123
- names = readdirSync(dir);
124
- } catch {
125
- return;
126
- }
127
- for (const name2 of names) {
128
- if (++entries > STAMP_MAX_ENTRIES) return;
129
- if (name2.endsWith(".tmp") || top && NOT_CONTENT.has(name2)) continue;
130
- const path = join(dir, name2);
131
- try {
132
- const stat = statSync(path);
133
- if (stat.isDirectory()) walk(path, false);
134
- else if (stat.mtimeMs > newest) newest = stat.mtimeMs;
135
- } catch {
136
- }
137
- }
138
- };
139
- walk(root, true);
140
- return newest > 0 ? new Date(newest).toISOString() : "";
141
- }
142
- function isShared(root) {
143
- try {
144
- return JSON.parse(readFileSync(join(root, ".sharing.json"), "utf8")).lan === true;
145
- } catch {
146
- return false;
147
- }
148
- }
149
- function isDeletable(skill, root) {
150
- const own = FOLDER[skill];
151
- if (own === void 0) return false;
152
- if (root === homedir() || root === parse(root).root) return false;
153
- try {
154
- return readdirSync(root).every((name2) => own.includes(name2) || EDITOR_FILES.includes(name2) || name2.endsWith(".tmp"));
155
- } catch {
156
- return false;
157
- }
158
- }
159
- function listArtifacts() {
160
- let names;
161
- try {
162
- names = readdirSync(artifactsDir());
163
- } catch {
164
- return [];
165
- }
166
- const views = [];
167
- for (const name2 of names) {
168
- if (!name2.endsWith(".json")) continue;
169
- const record = readRecord(join(artifactsDir(), name2));
170
- if (record === null || !existsSync(record.root)) continue;
171
- views.push({
172
- root: record.root,
173
- skill: record.skill,
174
- plugin: record.plugin,
175
- sessionId: record.sessionId,
176
- title: artifactTitle(record.skill, record.root) ?? record.title,
177
- openedAt: record.openedAt,
178
- firstOpenedAt: record.firstOpenedAt ?? record.openedAt,
179
- updatedAt: contentStamp(record.root) || record.openedAt,
180
- pinned: record.pinned === true,
181
- shared: isShared(record.root),
182
- deletable: isDeletable(record.skill, record.root),
183
- thumbnail: record.thumbStamp !== void 0 && existsSync(thumbnailFile(record.root)) ? record.thumbStamp : null
184
- });
185
- }
186
- return views.sort((a, b) => a.openedAt < b.openedAt ? 1 : a.openedAt > b.openedAt ? -1 : 0);
187
- }
188
- function findArtifact(root) {
189
- const record = readRecord(recordFile(root));
190
- return record?.root === root ? record : null;
191
- }
192
- function scriptEnv(node) {
193
- const env = { ...process.env };
194
- if (node.env) {
195
- const [name2, value] = node.env.split("=");
196
- if (name2) env[name2] = value ?? "";
197
- }
198
- return env;
199
- }
200
- function runScript(node, script, args, input) {
201
- return new Promise((resolve, reject) => {
202
- const child = spawn(node.path, [script, ...args], { env: scriptEnv(node), stdio: [input === void 0 ? "ignore" : "pipe", "pipe", "pipe"], windowsHide: true });
203
- let stdout = "";
204
- let stderr = "";
205
- const timer = setTimeout(() => {
206
- child.kill();
207
- reject(new Error(`${args[0]} did not finish in time`));
208
- }, OPEN_TIMEOUT_MS);
209
- child.stdout?.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
210
- child.stderr?.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
211
- child.stdin?.end(input);
212
- child.on("error", (error) => {
213
- clearTimeout(timer);
214
- reject(error);
215
- });
216
- child.on("close", (code) => {
217
- clearTimeout(timer);
218
- if (code === 0) return resolve(firstJson(stdout));
219
- const failure = firstJson(stderr) ?? firstJson(stdout);
220
- const message = typeof failure?.message === "string" ? failure.message : stderr.trim() || `${args[0]} exited with ${code ?? "a signal"}`;
221
- reject(new Error(message));
222
- });
223
- });
224
- }
225
- async function runOpen(node, script, root) {
226
- const out = await runScript(node, script, ["open", root]);
227
- if (typeof out?.url !== "string") throw new Error("open printed no address");
228
- return { url: out.url, reused: out.reused === true };
229
- }
230
- async function renameArtifact(node, script, record, title) {
231
- const { root, skill } = record;
232
- const at = TITLES[skill];
233
- if (at === void 0) throw new Error(`no title to change for ${skill}`);
234
- if (skill === "claude-dashboard" || skill === "claude-motion") {
235
- const path = at[0].replace(/^db\//, "").replace(/\.json$/, "");
236
- await runScript(node, script, ["write-db", root, "-"], JSON.stringify([{ op: "update", path, data: { [at[1]]: title } }]));
237
- return;
238
- }
239
- const file = join(root, at[0]);
240
- const index = JSON.parse(readFileSync(file, "utf8"));
241
- if (skill === "claude-docs") {
242
- await runScript(node, script, ["update", root, JSON.stringify({ ref: { object: "project", id: String(index.id) }, payload: { name: title } })]);
243
- return;
244
- }
245
- writeWhole(file, `${JSON.stringify({ ...index, [at[1]]: title }, null, 2)}
246
- `);
247
- }
248
- function cleanTitle(raw) {
249
- if (typeof raw !== "string") return null;
250
- const title = raw.replace(/\s+/g, " ").trim().slice(0, TITLE_MAX);
251
- return title || null;
252
- }
253
- function moveFolder(from, to) {
254
- mkdirSync(dirname(to), { recursive: true });
255
- try {
256
- renameSync(from, to);
257
- } catch (error) {
258
- if (error.code !== "EXDEV") throw error;
259
- cpSync(from, to, { recursive: true, preserveTimestamps: true });
260
- rmSync(from, { recursive: true, force: true });
261
- }
262
- }
263
- var TRASH_ID = /^[0-9a-f]{24}-\d+$/;
264
- function trashArtifact(record) {
265
- const id = `${folderHash(record.root)}-${Date.now()}`;
266
- const entry = join(trashDir(), id);
267
- try {
268
- for (const name2 of readdirSync(trashDir())) {
269
- if (TRASH_ID.test(name2) && !existsSync(join(trashDir(), name2, "folder"))) rmSync(join(trashDir(), name2), { recursive: true, force: true });
270
- }
271
- } catch {
272
- }
273
- mkdirSync(entry, { recursive: true });
274
- writeFileSync(join(entry, "record.json"), `${JSON.stringify(record, null, 2)}
275
- `);
276
- const thumbnail = thumbnailFile(record.root);
277
- if (existsSync(thumbnail)) renameSync(thumbnail, join(entry, "thumbnail.jpg"));
278
- const moved = isDeletable(record.skill, record.root);
279
- const path = join(entry, "folder", basename(record.root));
280
- if (moved) moveFolder(record.root, path);
281
- rmSync(recordFile(record.root), { force: true });
282
- return { trash: id, moved, path: moved ? path : record.root };
283
- }
284
- function restoreArtifact(id) {
285
- if (!TRASH_ID.test(id)) throw new Error("not a trash entry");
286
- const entry = join(trashDir(), id);
287
- const record = readRecord(join(entry, "record.json"));
288
- if (record === null) throw new Error("nothing to put back");
289
- const folder = join(entry, "folder", basename(record.root));
290
- if (existsSync(folder)) {
291
- if (existsSync(record.root)) throw new Error(`${record.root} is taken`);
292
- moveFolder(folder, record.root);
293
- }
294
- writeRecord(record);
295
- const thumbnail = join(entry, "thumbnail.jpg");
296
- if (existsSync(thumbnail)) {
297
- mkdirSync(dirname(thumbnailFile(record.root)), { recursive: true });
298
- renameSync(thumbnail, thumbnailFile(record.root));
299
- }
300
- rmSync(entry, { recursive: true, force: true });
301
- return record;
302
- }
303
- function firstJson(text) {
304
- const start = text.indexOf("{");
305
- const end = text.lastIndexOf("}");
306
- if (start < 0 || end <= start) return null;
307
- try {
308
- const value = JSON.parse(text.slice(start, end + 1));
309
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
310
- } catch {
311
- return null;
312
- }
8
+ var HOST = Symbol.for("baoyu-design/dsh-artifacts-host");
9
+ function hostHub() {
10
+ const g = globalThis;
11
+ return g[HOST] ??= { members: [] };
313
12
  }
314
- var json = (body, status = 200) => Response.json(body, { status, headers: { "cache-control": "no-store" } });
315
- function registerRoutes(routes, skill, opened) {
316
- routes.register({
317
- path: listPath(skill.name),
318
- methods: ["GET"],
319
- requestBody: "buffered",
320
- fetch: () => Promise.resolve(json({ artifacts: listArtifacts() }))
321
- });
322
- routes.register({
323
- path: openPath(skill.name),
324
- methods: ["POST"],
325
- requestBody: "buffered",
326
- fetch: async (request) => {
327
- const root = new URL(request.url).searchParams.get("root") ?? "";
328
- const record = root ? findArtifact(root) : null;
329
- if (record === null || record.skill !== skill.name) return json({ error: "not a recorded artifact of this skill" }, 404);
330
- if (!existsSync(root)) return json({ error: "the folder is gone" }, 404);
331
- try {
332
- const { url, reused } = await runOpen(skill.node, skill.script, root);
333
- const again = recordOpened(skill.name, skill.plugin, record.sessionId, root);
334
- opened(again);
335
- return json({ url, reused, sessionId: again.sessionId, title: again.title });
336
- } catch (error) {
337
- return json({ error: error.message }, 500);
338
- }
339
- }
340
- });
341
- const rootOf = (request) => new URL(request.url).searchParams.get("root") ?? "";
342
- const mine = (root) => {
343
- const record = root ? findArtifact(root) : null;
344
- return record?.skill === skill.name && existsSync(root) ? record : null;
345
- };
346
- const notMine = () => json({ error: "not a recorded artifact of this skill" }, 404);
347
- const started = /* @__PURE__ */ new Map();
348
- routes.register({
349
- path: previewPath(skill.name),
350
- methods: ["POST"],
351
- requestBody: "buffered",
352
- fetch: async (request) => {
353
- const root = rootOf(request);
354
- const record = mine(root);
355
- if (record === null) return notMine();
356
- try {
357
- const { url, reused } = await runOpen(skill.node, skill.script, root);
358
- if (!reused) started.set(root, record.openedAt);
359
- return json({ url, reused });
360
- } catch (error) {
361
- return json({ error: error.message }, 500);
362
- }
363
- }
364
- });
365
- routes.register({
366
- path: releasePath(skill.name),
367
- methods: ["POST"],
368
- requestBody: "buffered",
369
- fetch: async (request) => {
370
- const root = rootOf(request);
371
- const since = started.get(root);
372
- started.delete(root);
373
- const record = mine(root);
374
- if (record === null) return notMine();
375
- if (since === void 0 || since !== record.openedAt || new URL(request.url).searchParams.get("keep") === "1") return json({ stopped: false });
376
- try {
377
- await runScript(skill.node, skill.script, ["stop", root]);
378
- return json({ stopped: true });
379
- } catch (error) {
380
- return json({ error: error.message }, 500);
381
- }
382
- }
383
- });
384
- routes.register({
385
- path: thumbnailPath(skill.name),
386
- methods: ["GET", "POST"],
387
- requestBody: "buffered",
388
- fetch: async (request) => {
389
- const root = rootOf(request);
390
- const record = root ? findArtifact(root) : null;
391
- if (record === null) return json({ error: "not a recorded artifact" }, 404);
392
- if (request.method === "GET") {
393
- try {
394
- const bytes2 = readFileSync(thumbnailFile(root));
395
- return new Response(bytes2, { headers: { "content-type": "image/jpeg", "cache-control": "private, max-age=31536000, immutable" } });
396
- } catch {
397
- return json({ error: "no thumbnail yet" }, 404);
398
- }
399
- }
400
- const bytes = new Uint8Array(await request.arrayBuffer());
401
- if (bytes.length < 3 || bytes[0] !== 255 || bytes[1] !== 216) return json({ error: "a thumbnail is a JPEG" }, 400);
402
- writeWhole(thumbnailFile(root), bytes);
403
- const stamp = contentStamp(root) || record.openedAt;
404
- writeRecord({ ...findArtifact(root) ?? record, thumbStamp: stamp });
405
- return json({ thumbnail: stamp, updatedAt: stamp });
406
- }
407
- });
408
- routes.register({
409
- path: updatePath(skill.name),
410
- methods: ["POST"],
411
- requestBody: "buffered",
412
- fetch: async (request) => {
413
- const root = rootOf(request);
414
- const record = mine(root);
415
- if (record === null) return notMine();
416
- const patch = await request.json().catch(() => null);
417
- if (patch === null || typeof patch !== "object") return json({ error: "not a JSON object" }, 400);
418
- try {
419
- if (patch.title !== void 0) {
420
- const title = cleanTitle(patch.title);
421
- if (title === null) return json({ error: "a title cannot be empty" }, 400);
422
- await renameArtifact(skill.node, skill.script, record, title);
423
- }
424
- const now = findArtifact(root) ?? record;
425
- const next = { ...now, title: artifactTitle(skill.name, root) ?? now.title };
426
- if (patch.pinned === true) next.pinned = true;
427
- else if (patch.pinned === false) delete next.pinned;
428
- writeRecord(next);
429
- return json({ title: next.title, pinned: next.pinned === true });
430
- } catch (error) {
431
- return json({ error: error.message }, 500);
432
- }
433
- }
434
- });
435
- routes.register({
436
- path: deletePath(skill.name),
437
- methods: ["POST"],
438
- requestBody: "buffered",
439
- fetch: async (request) => {
440
- const root = rootOf(request);
441
- const record = mine(root);
442
- if (record === null) return notMine();
443
- try {
444
- if (isDeletable(skill.name, root)) await runScript(skill.node, skill.script, ["stop", root]).catch(() => null);
445
- started.delete(root);
446
- return json(trashArtifact(record));
447
- } catch (error) {
448
- return json({ error: error.message }, 500);
449
- }
450
- }
451
- });
452
- routes.register({
453
- path: restorePath(skill.name),
454
- methods: ["POST"],
455
- requestBody: "buffered",
456
- fetch: (request) => {
457
- try {
458
- return Promise.resolve(json({ root: restoreArtifact(new URL(request.url).searchParams.get("trash") ?? "").root }));
459
- } catch (error) {
460
- return Promise.resolve(json({ error: error.message }, 400));
461
- }
462
- }
463
- });
13
+ function hostRunning() {
14
+ return hostHub().members.length > 0;
464
15
  }
465
16
 
466
- var PLUGIN = define_PLUGIN_default;
467
- var name = PLUGIN.name;
468
- var inject = ["skills"];
469
- var BUNDLED_SKILL_RANK = 600;
470
- var packageRoot = fileURLToPath(new URL("../", import.meta.url));
17
+ import { statSync } from "node:fs";
18
+ import { delimiter, dirname, isAbsolute, join } from "node:path";
471
19
  function isFile(path) {
472
20
  try {
473
- return statSync2(path).isFile();
21
+ return statSync(path).isFile();
474
22
  } catch {
475
23
  return false;
476
24
  }
477
25
  }
478
- function resolveNode(config = {}) {
26
+ function resolveNode(plugin, config = {}) {
479
27
  if (config.node !== void 0) {
480
28
  if (!isAbsolute(config.node) || !isFile(config.node)) {
481
- throw new Error(`${PLUGIN.name}: config.node must be an absolute path to an executable file: ${config.node}`);
29
+ throw new Error(`${plugin}: config.node must be an absolute path to an executable file: ${config.node}`);
482
30
  }
483
31
  return { path: config.node, from: "config" };
484
32
  }
@@ -488,24 +36,30 @@ function resolveNode(config = {}) {
488
36
  const roots = [];
489
37
  const resourcesPath = process.resourcesPath;
490
38
  if (typeof resourcesPath === "string") roots.push(resourcesPath);
491
- let dir = dirname2(process.execPath);
39
+ let dir = dirname(process.execPath);
492
40
  for (let i = 0; i < 6; i++) {
493
- roots.push(join2(dir, "Resources"), join2(dir, "resources"));
494
- const parent = dirname2(dir);
41
+ roots.push(join(dir, "Resources"), join(dir, "resources"));
42
+ const parent = dirname(dir);
495
43
  if (parent === dir) break;
496
44
  dir = parent;
497
45
  }
498
46
  for (const root of roots) {
499
- const candidate = join2(root, ...runtime);
47
+ const candidate = join(root, ...runtime);
500
48
  if (isFile(candidate)) return { path: candidate, from: "desktop runtime" };
501
49
  }
502
50
  for (const entry of (process.env.PATH ?? "").split(delimiter)) {
503
51
  if (entry === "" || !isAbsolute(entry)) continue;
504
- const candidate = join2(entry, exe);
52
+ const candidate = join(entry, exe);
505
53
  if (isFile(candidate)) return { path: candidate, from: "PATH" };
506
54
  }
507
55
  return { path: process.execPath, env: "ELECTRON_RUN_AS_NODE=1", from: "electron" };
508
56
  }
57
+
58
+ var PLUGIN = define_PLUGIN_default;
59
+ var name = PLUGIN.name;
60
+ var inject = ["skills"];
61
+ var BUNDLED_SKILL_RANK = 600;
62
+ var packageRoot = fileURLToPath(new URL("../", import.meta.url));
509
63
  function shellQuote(path) {
510
64
  return `"${path.replace(/(["\\$`])/g, "\\$1")}"`;
511
65
  }
@@ -518,7 +72,7 @@ function scriptCommand(node, script, args, platform) {
518
72
  const env = name2 ? `$env:${name2} = ${powerShellQuote(value ?? "")};` : "";
519
73
  return [env, "&", powerShellQuote(node.path), powerShellQuote(script), ...args].filter(Boolean).join(" ");
520
74
  }
521
- function runtimeSection(skill, skillDir, node, platform = process.platform) {
75
+ function runtimeSection(skill, skillDir, node, platform = process.platform, watched = true) {
522
76
  const windows = platform === "win32";
523
77
  const script = (windows ? win32 : posix).join(skillDir, "scripts", "artifact.mjs");
524
78
  const written = skill.scriptAlias === void 0 ? "`node <this skill's folder>/scripts/artifact.mjs <command>`" : `\`node $${skill.scriptAlias} <command>\` (\`${skill.scriptAlias}\` being \`<this skill's folder>/scripts/artifact.mjs\`)`;
@@ -542,7 +96,7 @@ function runtimeSection(skill, skillDir, node, platform = process.platform) {
542
96
  "`open` prints the editor's `url` (on 127.0.0.1): give it to the user as a Markdown link. In the DeepSeek Harness",
543
97
  "app a chat link opens in the right sidebar's Browser, next to this conversation."
544
98
  ];
545
- if (skill.comments) {
99
+ if (skill.comments && watched) {
546
100
  const sent = skill.commentsBy === "mention" ? "A comment, reply or edit in the viewer that says `@agent` is sent to you (the viewer has no Send to Agent box)." : "The editor shows **Send to Agent** on a comment while comments can reach you; a comment sent with it is sent to you.";
547
101
  lines.push(
548
102
  "",
@@ -568,266 +122,13 @@ function runtimeSection(skill, skillDir, node, platform = process.platform) {
568
122
  }
569
123
  return lines.join("\n");
570
124
  }
571
- var COMMENT_SOURCE = "baoyu-design-comment";
572
- var SUMMARY_MAX = 120;
573
- var SESSION_CHECK_MS = 6e4;
574
- var RETRY_MS = 5e3;
575
- var MAX_QUICK_FAILURES = 3;
576
- var IDLE_CHECK_MS = 500;
577
- var AGENT_GRACE_MS = 3e4;
578
- var TURN_START_MS = 3e3;
579
- var TURN_MAX_MS = 30 * 6e4;
580
- function jsonObject(text) {
581
- const trimmed = text.trim();
582
- const starts = [0];
583
- for (let i = trimmed.indexOf("\n{"); i >= 0; i = trimmed.indexOf("\n{", i + 1)) starts.push(i + 1);
584
- for (const start of starts) {
585
- if (trimmed[start] !== "{") continue;
586
- const end = trimmed.lastIndexOf("}");
587
- for (let stop = end; stop > start; stop = trimmed.lastIndexOf("}", stop - 1)) {
588
- try {
589
- const value = JSON.parse(trimmed.slice(start, stop + 1));
590
- if (value && typeof value === "object" && !Array.isArray(value)) return value;
591
- } catch {
592
- }
593
- }
594
- }
595
- return null;
596
- }
597
- function jsonEnd(text, start) {
598
- let depth = 0;
599
- let inString = false;
600
- for (let i = start; i < text.length; i++) {
601
- const c = text[i];
602
- if (inString) {
603
- if (c === "\\") i++;
604
- else if (c === '"') inString = false;
605
- } else if (c === '"') inString = true;
606
- else if (c === "{" || c === "[") depth++;
607
- else if ((c === "}" || c === "]") && --depth === 0) return i;
608
- }
609
- return -1;
610
- }
611
- function jsonObjects(text) {
612
- const found = [];
613
- const starts = text.startsWith("{") ? [0] : [];
614
- for (let i = text.indexOf("\n{"); i >= 0; i = text.indexOf("\n{", i + 1)) starts.push(i + 1);
615
- let after = 0;
616
- for (const start of starts) {
617
- if (start < after) continue;
618
- const end = jsonEnd(text, start);
619
- if (end < 0) continue;
620
- try {
621
- const value = JSON.parse(text.slice(start, end + 1));
622
- if (value && typeof value === "object" && !Array.isArray(value)) {
623
- found.push(value);
624
- after = end + 1;
625
- }
626
- } catch {
627
- }
628
- }
629
- return found;
630
- }
631
- function bashStdout(result) {
632
- const value = result.value;
633
- if (typeof value === "string") return value;
634
- if (value && typeof value === "object") {
635
- if (value.kind !== "foreground" || value.exitCode !== 0) return null;
636
- return typeof value.stdout?.text === "string" ? value.stdout.text : null;
637
- }
638
- const text = result.content.map((block) => block.type === "text" ? block.text ?? "" : "").join("\n");
639
- return text || null;
640
- }
641
- function openedEditor(script, exec, result) {
642
- if (exec.name !== "bash" && exec.name !== "pwsh" || exec.agent === void 0 || result.isError) return null;
643
- const args = exec.arguments;
644
- const command = args?.command;
645
- if (typeof command !== "string" || args?.run_in_background === true) return null;
646
- if (!/(?:^|[\s"'])open(?:[\s"']|$)/.test(command)) return null;
647
- const stdout = bashStdout(result);
648
- const opened = stdout === null ? void 0 : jsonObjects(stdout).findLast((o) => typeof o.root === "string" && typeof o.url === "string");
649
- if (typeof opened?.root !== "string" || typeof opened.url !== "string") return null;
650
- if (!ranScript(script, command, opened.stop)) return null;
651
- return { session: String(exec.agent.id), root: opened.root, url: opened.url };
652
- }
653
- function ranScript(script, command, stop) {
654
- if (typeof stop !== "string") return command.includes(script);
655
- if (stop.includes(script)) return true;
656
- const folder = basename2(dirname2(dirname2(script)));
657
- return new RegExp(`[/\\\\]${folder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[/\\\\]scripts[/\\\\]artifact\\.mjs`).test(stop);
658
- }
659
- var bound = (text) => text.length <= SUMMARY_MAX ? text : `${text.slice(0, SUMMARY_MAX - 1)}…`;
660
- function commentMessage(skill, plugin, out, root) {
661
- const title = typeof out.title === "string" && out.title ? out.title : root.split(/[\\/]/).pop() || root;
662
- const url = typeof out.url === "string" ? out.url : "";
663
- const threads = (out.threads ?? []).map((t) => {
664
- const mine = (t.messages ?? []).filter((m) => m.from === "user");
665
- return { id: t.id, where: t.where ?? "", text: (mine[mine.length - 1] ?? t.messages?.[t.messages.length - 1])?.text ?? "" };
666
- });
667
- const requests = (out.requests ?? []).filter((r) => r && typeof r.text === "string").map((r) => ({ id: String(r.id), label: typeof r.label === "string" ? r.label : "", text: r.text }));
668
- const { next: _next, timedOut: _timedOut, about: _about, ...rest } = out;
669
- const how = skill.commentsBy === "mention" ? "read the block, change the doc with `update`, then `comment-reply`" : "change the files, then `comment-reply`";
670
- const where = `"${title}" (${url || root})`;
671
- const heading = threads.length > 0 && requests.length > 0 ? `Comments, and requests from the page's buttons, sent to you from the editor of ${where}:` : threads.length > 0 ? `Comments sent to you from the editor of ${where}:` : `Requests sent to you by the buttons on ${where}:`;
672
- const text = [
673
- heading,
674
- "",
675
- "```json",
676
- JSON.stringify(rest, null, 2),
677
- "```",
678
- "",
679
- ...threads.length > 0 ? [`Handle the comments as Comments says (${how}).`] : [],
680
- ...requests.length > 0 ? [
681
- "Each request is one of the page's buttons (`label`) asking for its `text`: those are the user's words, as if",
682
- "typed here. Handle each as the skill says for those words."
683
- ] : [],
684
- 'Run the script as "Running the calls in DeepSeek Harness" says.',
685
- "DeepSeek Harness is already waiting for the next ones: do not run `comments --wait`."
686
- ].join("\n");
687
- const first = threads[0];
688
- const asked = requests[0];
689
- const summary = bound(
690
- first ? `Comment on "${title}": ${first.text.replace(/\s+/g, " ")}`.trim() : `${asked?.label || "Request"} on "${title}": ${(asked?.text ?? "").replace(/\s+/g, " ")}`.trim()
691
- );
692
- const message = {
693
- role: "user",
694
- id: randomUUID(),
695
- content: [{ type: "text", text }],
696
- source: {
697
- kind: COMMENT_SOURCE,
698
- form: "notice",
699
- summary,
700
- plugin,
701
- title,
702
- url,
703
- root,
704
- threads,
705
- ...requests.length > 0 ? { requests } : {}
706
- }
707
- };
708
- return Object.freeze(message);
709
- }
710
- var CommentWatcher = class {
711
- watches = /* @__PURE__ */ new Map();
712
- check;
713
- ctx;
714
- skill;
715
- script;
716
- node;
717
- constructor(ctx, skill, script, node) {
718
- this.ctx = ctx;
719
- this.skill = skill;
720
- this.script = script;
721
- this.node = node;
722
- this.check = setInterval(() => this.dropGoneSessions(), SESSION_CHECK_MS);
723
- this.check.unref?.();
724
- }
725
- log(level, message) {
726
- this.ctx.logger?.[level]?.(`${PLUGIN.name}: ${message}`);
727
- }
728
- agent(session) {
729
- return this.ctx.get("agents")?.get(session);
730
- }
731
- watch(session, root) {
732
- const key = `${session}\0${root}`;
733
- if (this.watches.has(key)) return;
734
- const watch = { child: null, stopped: false, quickFailures: 0, graceUntil: Date.now() + AGENT_GRACE_MS };
735
- this.watches.set(key, watch);
736
- this.log("info", `waiting for comments on ${root}`);
737
- this.run(key, session, root, watch);
738
- }
739
- stop(key, watch, why) {
740
- if (watch.stopped) return;
741
- watch.stopped = true;
742
- if (watch.retry) clearTimeout(watch.retry);
743
- watch.child?.kill();
744
- watch.child = null;
745
- if (this.watches.get(key) === watch) this.watches.delete(key);
746
- this.log("info", `stopped waiting for comments (${why})`);
747
- }
748
- run(key, session, root, watch) {
749
- watch.retry = void 0;
750
- if (watch.stopped) return;
751
- if (this.agent(session) === void 0) {
752
- if (Date.now() >= watch.graceUntil) return this.stop(key, watch, "the session is gone");
753
- watch.retry = setTimeout(() => this.run(key, session, root, watch), IDLE_CHECK_MS);
754
- return;
755
- }
756
- const started = Date.now();
757
- const child = spawn2(this.node.path, [this.script, "comments", root, "--wait"], { env: scriptEnv(this.node), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
758
- watch.child = child;
759
- let stdout = "";
760
- let stderr = "";
761
- child.stdout?.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
762
- child.stderr?.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
763
- child.on("error", (error) => this.stop(key, watch, `the wait did not start: ${error.message}`));
764
- child.on("close", (code) => {
765
- if (watch.child === child) watch.child = null;
766
- if (watch.stopped) return;
767
- if (code === 0) {
768
- watch.quickFailures = 0;
769
- const out = jsonObject(stdout);
770
- const came = (list) => Array.isArray(list) && list.length > 0;
771
- if (out && (came(out.threads) || came(out.requests))) {
772
- this.deliver(session, out, root);
773
- return this.runWhenIdle(key, session, root, watch);
774
- }
775
- this.run(key, session, root, watch);
776
- return;
777
- }
778
- const failure = jsonObject(stderr);
779
- const reason = typeof failure?.code === "string" ? failure.code : `exit ${code ?? "signal"}`;
780
- if (failure && typeof failure.code === "string") return this.stop(key, watch, reason);
781
- if (Date.now() - started < RETRY_MS && ++watch.quickFailures > MAX_QUICK_FAILURES) return this.stop(key, watch, reason);
782
- watch.retry = setTimeout(() => this.run(key, session, root, watch), RETRY_MS);
783
- });
784
- }
785
- /** Start the next wait once the agent is done with the batch it was just handed. */
786
- runWhenIdle(key, session, root, watch) {
787
- const handed = Date.now();
788
- let working = false;
789
- const look = () => {
790
- watch.retry = void 0;
791
- if (watch.stopped) return;
792
- const agent = this.agent(session);
793
- if (agent === void 0) return this.stop(key, watch, "the session is gone");
794
- const idle = agent.status === "idle";
795
- if (!idle) working = true;
796
- const waited = Date.now() - handed;
797
- if (idle && (working || waited > TURN_START_MS) || waited > TURN_MAX_MS) return this.run(key, session, root, watch);
798
- watch.retry = setTimeout(look, IDLE_CHECK_MS);
799
- };
800
- look();
801
- }
802
- deliver(session, out, root) {
803
- const agent = this.agent(session);
804
- if (agent === void 0) return;
805
- const message = commentMessage(this.skill, PLUGIN.provider, out, root);
806
- try {
807
- if (agent.status === "idle") agent.followup(message);
808
- else agent.inject(message);
809
- } catch (error) {
810
- this.log("warn", `could not hand the comments or requests to the agent: ${error.message}`);
811
- }
812
- }
813
- dropGoneSessions() {
814
- for (const [key, watch] of [...this.watches]) {
815
- const session = key.slice(0, key.indexOf("\0"));
816
- if (this.agent(session) === void 0 && Date.now() >= watch.graceUntil) this.stop(key, watch, "the session is gone");
817
- }
818
- }
819
- dispose() {
820
- clearInterval(this.check);
821
- for (const [key, watch] of [...this.watches]) this.stop(key, watch, "the plugin stopped");
822
- }
823
- };
824
125
  function skillBody(raw, path) {
825
126
  const frontmatter = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u.exec(raw);
826
127
  if (frontmatter === null) throw new Error(`${PLUGIN.name}: ${path} has no YAML frontmatter`);
827
128
  return raw.slice(frontmatter[0].length).trim();
828
129
  }
829
130
  function apply(ctx, config = {}) {
830
- const node = resolveNode(config ?? {});
131
+ const node = resolveNode(PLUGIN.name, config ?? {});
831
132
  const skills = new Map(PLUGIN.skills.map((skill) => [skill.name, skill]));
832
133
  const candidates = PLUGIN.skills.map(({ name: skillName, description }) => {
833
134
  const directory = join2(packageRoot, "skills", skillName);
@@ -853,40 +154,14 @@ function apply(ctx, config = {}) {
853
154
  if (skill === void 0) throw new Error(`${PLUGIN.name}: no skill named ${candidate.name}`);
854
155
  const raw = await readFile(candidate.path, { encoding: "utf8", signal: options?.signal });
855
156
  const directory = dirname2(candidate.path);
856
- return { ...summary, content: skillBody(raw, candidate.path) + runtimeSection(skill, directory, node) };
157
+ return { ...summary, content: skillBody(raw, candidate.path) + runtimeSection(skill, directory, node, process.platform, hostRunning()) };
857
158
  }
858
159
  };
859
160
  ctx.skills.registerProvider(() => provider);
860
- for (const skill of PLUGIN.skills) {
861
- const script = join2(packageRoot, "skills", skill.name, "scripts", "artifact.mjs");
862
- const watcher = skill.comments ? new CommentWatcher(ctx, skill, script, node) : null;
863
- if (watcher) ctx.effect(() => () => watcher.dispose(), `${PLUGIN.name}: comment waits`);
864
- ctx.on("tools/result", (exec, result) => {
865
- const opened = openedEditor(script, exec, result);
866
- if (opened === null) return;
867
- watcher?.watch(opened.session, opened.root);
868
- try {
869
- recordOpened(skill.name, PLUGIN.provider, opened.session, opened.root);
870
- } catch (error) {
871
- ctx.logger?.warn?.(`${PLUGIN.name}: could not note ${opened.root} for the Artifacts page: ${error.message}`);
872
- }
873
- });
874
- ctx.inject?.(["connection"], (sub) => {
875
- registerRoutes(sub.connection.fetch, { name: skill.name, plugin: PLUGIN.provider, script, node }, (record) => {
876
- watcher?.watch(record.sessionId, record.root);
877
- });
878
- });
879
- }
880
161
  }
881
162
  export {
882
- COMMENT_SOURCE,
883
163
  apply,
884
- commentMessage,
885
164
  inject,
886
- jsonObject,
887
- jsonObjects,
888
165
  name,
889
- openedEditor,
890
- resolveNode,
891
166
  runtimeSection
892
167
  };