@dotobokuri/fleet-console 1.40.0 → 1.41.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.
Files changed (70) hide show
  1. package/README.md +2 -3
  2. package/dist/cli.d.ts +2 -1
  3. package/dist/cli.mjs +151 -134
  4. package/dist/client/assets/{_baseUniq-Bao5GOIa.js → _baseUniq-CImzMrtU.js} +1 -1
  5. package/dist/client/assets/{arc-DnhHMLy9.js → arc-CKb0rHjN.js} +1 -1
  6. package/dist/client/assets/{architectureDiagram-Q4EWVU46-DAbURr0a.js → architectureDiagram-Q4EWVU46-BaZipXSe.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-DXYQGD6D-DE1raZQ6.js → blockDiagram-DXYQGD6D-D4sS9TjR.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-AHTNJAMY-beLZlUB3.js → c4Diagram-AHTNJAMY-DvjpMsKx.js} +1 -1
  9. package/dist/client/assets/channel-dSuGhtYS.js +1 -0
  10. package/dist/client/assets/{chunk-4BX2VUAB-DEqdunZS.js → chunk-4BX2VUAB-B47BryhY.js} +1 -1
  11. package/dist/client/assets/{chunk-4TB4RGXK-D8i5nx4L.js → chunk-4TB4RGXK-DKIhSC52.js} +1 -1
  12. package/dist/client/assets/{chunk-55IACEB6-eM3-fH_e.js → chunk-55IACEB6-B3Gvpef7.js} +1 -1
  13. package/dist/client/assets/{chunk-EDXVE4YY-nQoyt_Pd.js → chunk-EDXVE4YY-C4gFtvU8.js} +1 -1
  14. package/dist/client/assets/{chunk-FMBD7UC4-D8Wq8CYj.js → chunk-FMBD7UC4-BADPieOa.js} +1 -1
  15. package/dist/client/assets/{chunk-OYMX7WX6-Cx4Wr2ZT.js → chunk-OYMX7WX6-BUdtWGUY.js} +1 -1
  16. package/dist/client/assets/{chunk-QZHKN3VN-BM2v7Krr.js → chunk-QZHKN3VN-CMxkeGnM.js} +1 -1
  17. package/dist/client/assets/{chunk-YZCP3GAM-zTniPxm7.js → chunk-YZCP3GAM-Br6N7t8d.js} +1 -1
  18. package/dist/client/assets/classDiagram-6PBFFD2Q-DEN4mh7x.js +1 -0
  19. package/dist/client/assets/classDiagram-v2-HSJHXN6E-DEN4mh7x.js +1 -0
  20. package/dist/client/assets/clone-BbTnmCoq.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-S5V4N54A-B5pl2A_p.js → cose-bilkent-S5V4N54A-D1Uw7i5_.js} +1 -1
  22. package/dist/client/assets/{dagre-KV5264BT-KHPgW9C2.js → dagre-KV5264BT-kop0bHkM.js} +1 -1
  23. package/dist/client/assets/{diagram-5BDNPKRD-DCv28c7S.js → diagram-5BDNPKRD-B9e41qNA.js} +1 -1
  24. package/dist/client/assets/{diagram-G4DWMVQ6-mT5WQ415.js → diagram-G4DWMVQ6-Wew8X03k.js} +1 -1
  25. package/dist/client/assets/{diagram-MMDJMWI5-iKYfjiRC.js → diagram-MMDJMWI5-DGBb5Oqa.js} +1 -1
  26. package/dist/client/assets/{diagram-TYMM5635-_uqBP7vH.js → diagram-TYMM5635-CiVNsAz6.js} +1 -1
  27. package/dist/client/assets/{erDiagram-SMLLAGMA-DxNN1WhN.js → erDiagram-SMLLAGMA-BdemYTgC.js} +1 -1
  28. package/dist/client/assets/{flowDiagram-DWJPFMVM-DGTsrBvM.js → flowDiagram-DWJPFMVM-CXC6yxhU.js} +1 -1
  29. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-C17hg4lr.js → ganttDiagram-T4ZO3ILL-SMUpaiUg.js} +1 -1
  30. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-CeJ25hAd.js → gitGraphDiagram-UUTBAWPF-UaFgTHEh.js} +1 -1
  31. package/dist/client/assets/{graph-BQvb-BfT.js → graph-Ch8F0gMI.js} +1 -1
  32. package/dist/client/assets/index-D38r12i5.css +1 -0
  33. package/dist/client/assets/index-DkTcJcbj.js +452 -0
  34. package/dist/client/assets/{infoDiagram-42DDH7IO-BctsofOZ.js → infoDiagram-42DDH7IO-BHBzw0bu.js} +1 -1
  35. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-PiPmxcg9.js → ishikawaDiagram-UXIWVN3A-CnDea8bw.js} +1 -1
  36. package/dist/client/assets/{journeyDiagram-VCZTEJTY-BwkjYq3n.js → journeyDiagram-VCZTEJTY-CI91m8BP.js} +1 -1
  37. package/dist/client/assets/{kanban-definition-6JOO6SKY-CSV4dYfF.js → kanban-definition-6JOO6SKY-D-rn-rG2.js} +1 -1
  38. package/dist/client/assets/{layout-DdReLJnP.js → layout-Cd9zpG94.js} +1 -1
  39. package/dist/client/assets/{linear-B54euy_k.js → linear-Cucr1ojd.js} +1 -1
  40. package/dist/client/assets/{mermaid.core-CF92I5mT.js → mermaid.core-G36P12JZ.js} +4 -4
  41. package/dist/client/assets/{min-BcETQ2qJ.js → min-CzfCLG7v.js} +1 -1
  42. package/dist/client/assets/{mindmap-definition-QFDTVHPH-CrmVPycO.js → mindmap-definition-QFDTVHPH-4e1VjBV7.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-DEJITSTG-B5d4jR0H.js → pieDiagram-DEJITSTG-m2CGbU51.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-DlgCNExa.js → quadrantDiagram-34T5L4WZ-BKNPGqMc.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-MS252O5E-CeuVib_X.js → requirementDiagram-MS252O5E-D2BMwnpz.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-XADWPNL6-Bzxv73ik.js → sankeyDiagram-XADWPNL6-Bm6hTqEh.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-FGHM5R23-B0Ondg0u.js → sequenceDiagram-FGHM5R23-Ds2FEGPj.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-FHFEXIEX-CuNHg5DJ.js → stateDiagram-FHFEXIEX-CrXn2Tb7.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BbzpXIlW.js +1 -0
  50. package/dist/client/assets/{timeline-definition-GMOUNBTQ-DXcye6z7.js → timeline-definition-GMOUNBTQ-mw053fb5.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-DHZGUBPP-CL4rbpzL.js → vennDiagram-DHZGUBPP-CAws0Vt2.js} +1 -1
  52. package/dist/client/assets/{wardley-RL74JXVD-rVZBTA_R.js → wardley-RL74JXVD-DGad2cmz.js} +1 -1
  53. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-Cb8Arj1F.js → wardleyDiagram-NUSXRM2D-BlIyft8I.js} +1 -1
  54. package/dist/client/assets/{xychartDiagram-5P7HB3ND-C-qp4Say.js → xychartDiagram-5P7HB3ND-DHmSULAI.js} +1 -1
  55. package/dist/client/index.html +2 -2
  56. package/dist/fleet-plugins/file-explorer/routes.mjs +484 -56
  57. package/dist/fleet-plugins/ledger/routes.mjs +56 -47
  58. package/dist/fleet-plugins/quota/routes.mjs +19252 -118
  59. package/dist/fleet-plugins/repository/routes.mjs +274 -17
  60. package/dist/fleet-plugins/scuttlebutt/routes.mjs +6 -81
  61. package/dist/fleet-plugins/skills/routes.mjs +1 -46
  62. package/dist/fleet-plugins/terminal/routes.mjs +12090 -1060
  63. package/package.json +1 -1
  64. package/dist/client/assets/channel-Bn1wyUhs.js +0 -1
  65. package/dist/client/assets/classDiagram-6PBFFD2Q-DBE3Ix3c.js +0 -1
  66. package/dist/client/assets/classDiagram-v2-HSJHXN6E-DBE3Ix3c.js +0 -1
  67. package/dist/client/assets/clone-COL47Wy_.js +0 -1
  68. package/dist/client/assets/index-Ca6HkGPQ.css +0 -1
  69. package/dist/client/assets/index-Cr3Xq85X.js +0 -452
  70. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BVPbUfrG.js +0 -1
@@ -1,18 +1,135 @@
1
1
  #!/usr/bin/env node
2
- import fs from 'fs';
3
- import path2 from 'path';
4
- import fs5 from 'fs/promises';
2
+ import { execFile, spawn } from 'child_process';
3
+ import fs3, { constants } from 'fs';
4
+ import fs8 from 'fs/promises';
5
+ import path4 from 'path';
5
6
 
6
7
  // sdk/plugin/node.ts
7
8
  function definePlugin(definition) {
8
9
  return definition;
9
10
  }
10
- function registerRouter(ctx, path6, handler) {
11
- ctx.registerRouter(normalizePath(path6), handler);
11
+ function registerRouter(ctx, path10, handler) {
12
+ ctx.registerRouter(normalizePath(path10), handler);
12
13
  }
13
- function normalizePath(path6) {
14
- if (path6.length === 0 || path6 === "/") return "/";
15
- return path6;
14
+ function normalizePath(path10) {
15
+ if (path10.length === 0 || path10 === "/") return "/";
16
+ return path10;
17
+ }
18
+ var PathActionError = class extends Error {
19
+ code;
20
+ constructor(code) {
21
+ super(code);
22
+ this.name = "PathActionError";
23
+ this.code = code;
24
+ }
25
+ };
26
+ async function resolveContainedActionPath(theaterPath, relativePath) {
27
+ if (path4.isAbsolute(relativePath)) throw new PathActionError("path_outside_theater");
28
+ const rootPath = path4.resolve(theaterPath);
29
+ const candidatePath = path4.resolve(rootPath, relativePath);
30
+ if (!isPathContained(rootPath, candidatePath)) throw new PathActionError("path_outside_theater");
31
+ let realRootPath;
32
+ let realCandidatePath;
33
+ try {
34
+ [realRootPath, realCandidatePath] = await Promise.all([
35
+ fs8.realpath(rootPath),
36
+ fs8.realpath(candidatePath)
37
+ ]);
38
+ } catch (error) {
39
+ const code = error.code;
40
+ if (code === "EACCES" || code === "EPERM") throw new PathActionError("forbidden");
41
+ throw new PathActionError("not_found");
42
+ }
43
+ if (!isPathContained(realRootPath, realCandidatePath)) {
44
+ throw new PathActionError("path_outside_theater");
45
+ }
46
+ return {
47
+ // Preserve the row the user acted on for display-only actions such as copying path text.
48
+ logicalPath: candidatePath,
49
+ // Process-launch actions must use the same resolved path that passed containment.
50
+ realPath: realCandidatePath
51
+ };
52
+ }
53
+ function isPathContained(rootPath, candidatePath) {
54
+ const relative = path4.relative(rootPath, candidatePath);
55
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path4.sep}`) && !path4.isAbsolute(relative);
56
+ }
57
+
58
+ // ../fleet-plugins/file-explorer/server/clipboard.ts
59
+ var ClipboardUnavailableError = class extends Error {
60
+ constructor() {
61
+ super("clipboard_unavailable");
62
+ this.name = "ClipboardUnavailableError";
63
+ }
64
+ };
65
+ var LINUX_CLIPBOARD_TOOLS = [
66
+ { name: "wl-copy", args: [] },
67
+ { name: "xclip", args: ["-selection", "clipboard"] },
68
+ { name: "xsel", args: ["--clipboard", "--input"] }
69
+ ];
70
+ var DEFAULT_DEPENDENCIES = {
71
+ platform: process.platform,
72
+ findExecutable: findExecutableOnPath,
73
+ runWithInput: execFileWithInput
74
+ };
75
+ async function copyPathToClipboard(theaterPath, relativePath, dependencies = {}) {
76
+ const deps = { ...DEFAULT_DEPENDENCIES, ...dependencies };
77
+ const { logicalPath } = await resolveContainedActionPath(theaterPath, relativePath);
78
+ const command = await resolveClipboardCommand(deps.platform, deps.findExecutable);
79
+ if (!command) throw new ClipboardUnavailableError();
80
+ try {
81
+ await deps.runWithInput(command.file, command.args, logicalPath);
82
+ } catch (error) {
83
+ if (error.code === "ENOENT") throw new ClipboardUnavailableError();
84
+ throw error;
85
+ }
86
+ }
87
+ async function resolveClipboardCommand(platform, findExecutable = findExecutableOnPath) {
88
+ if (platform === "darwin") return { file: "pbcopy", args: [] };
89
+ if (platform === "win32") return { file: "clip.exe", args: [] };
90
+ if (platform !== "linux") return null;
91
+ for (const candidate of LINUX_CLIPBOARD_TOOLS) {
92
+ const executable = await findExecutable(candidate.name);
93
+ if (executable) return { file: executable, args: candidate.args };
94
+ }
95
+ return null;
96
+ }
97
+ async function findExecutableOnPath(name) {
98
+ const rawPath = process.env.PATH ?? "";
99
+ for (const directory of rawPath.split(path4.delimiter)) {
100
+ if (!directory) continue;
101
+ const candidate = path4.join(directory, name);
102
+ try {
103
+ await fs8.access(candidate, constants.X_OK);
104
+ return candidate;
105
+ } catch {
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ function execFileWithInput(file, args, input) {
111
+ return new Promise((resolve, reject) => {
112
+ let settled = false;
113
+ const finish = (error) => {
114
+ if (settled) return;
115
+ settled = true;
116
+ if (error) reject(error);
117
+ else resolve();
118
+ };
119
+ const child = execFile(
120
+ file,
121
+ [...args],
122
+ { shell: false, timeout: 5e3, windowsHide: true },
123
+ (error) => finish(error)
124
+ );
125
+ child.once("error", finish);
126
+ if (!child.stdin) {
127
+ finish(new Error("clipboard stdin unavailable"));
128
+ return;
129
+ }
130
+ child.stdin.once("error", finish);
131
+ child.stdin.end(input);
132
+ });
16
133
  }
17
134
  var FileReadError = class extends Error {
18
135
  code;
@@ -69,14 +186,14 @@ var EXT_LANG_MAP = {
69
186
  ".txt": "plaintext"
70
187
  };
71
188
  async function readFileForTheater(theaterPath, relativePath) {
72
- const resolved = path2.resolve(theaterPath, relativePath);
189
+ const resolved = path4.resolve(theaterPath, relativePath);
73
190
  if (!isWithinRoot(resolved, theaterPath)) throw new FileReadError("path_outside_theater");
74
191
  let realResolved;
75
192
  let realRoot;
76
193
  try {
77
194
  [realResolved, realRoot] = await Promise.all([
78
- fs.promises.realpath(resolved),
79
- fs.promises.realpath(theaterPath)
195
+ fs3.promises.realpath(resolved),
196
+ fs3.promises.realpath(theaterPath)
80
197
  ]);
81
198
  } catch (error) {
82
199
  const code = error.code;
@@ -86,7 +203,7 @@ async function readFileForTheater(theaterPath, relativePath) {
86
203
  if (!isWithinRoot(realResolved, realRoot)) throw new FileReadError("path_outside_theater");
87
204
  let stat;
88
205
  try {
89
- stat = await fs.promises.stat(realResolved);
206
+ stat = await fs3.promises.stat(realResolved);
90
207
  } catch (error) {
91
208
  const code = error.code;
92
209
  if (code === "EACCES" || code === "EPERM") throw new FileReadError("forbidden");
@@ -94,29 +211,29 @@ async function readFileForTheater(theaterPath, relativePath) {
94
211
  }
95
212
  if (!stat.isFile()) throw new FileReadError("not_a_file");
96
213
  if (stat.size > FILE_SIZE_CAP) {
97
- const fd = await fs.promises.open(realResolved, "r");
214
+ const fd = await fs3.promises.open(realResolved, "r");
98
215
  const buffer2 = Buffer.alloc(FILE_SIZE_CAP);
99
216
  const { bytesRead } = await fd.read(buffer2, 0, FILE_SIZE_CAP, 0);
100
217
  await fd.close();
101
218
  const chunk = buffer2.subarray(0, bytesRead);
102
219
  if (isBinaryBuffer(chunk)) throw new FileReadError("binary_file");
103
220
  return {
104
- relativePath: path2.relative(realRoot, realResolved),
221
+ relativePath: path4.relative(realRoot, realResolved),
105
222
  content: chunk.toString("utf8"),
106
223
  lang: detectLang(realResolved),
107
224
  truncated: true
108
225
  };
109
226
  }
110
- const buffer = await fs.promises.readFile(realResolved);
227
+ const buffer = await fs3.promises.readFile(realResolved);
111
228
  if (isBinaryBuffer(buffer)) throw new FileReadError("binary_file");
112
229
  return {
113
- relativePath: path2.relative(realRoot, realResolved),
230
+ relativePath: path4.relative(realRoot, realResolved),
114
231
  content: buffer.toString("utf8"),
115
232
  lang: detectLang(realResolved)
116
233
  };
117
234
  }
118
235
  function isWithinRoot(resolved, root) {
119
- const normalizedRoot = root.endsWith(path2.sep) ? root : root + path2.sep;
236
+ const normalizedRoot = root.endsWith(path4.sep) ? root : root + path4.sep;
120
237
  return resolved === root || resolved.startsWith(normalizedRoot);
121
238
  }
122
239
  function isBinaryBuffer(buffer) {
@@ -129,7 +246,7 @@ function isBinaryBuffer(buffer) {
129
246
  return nulCount / checkLen > BINARY_NUL_THRESHOLD;
130
247
  }
131
248
  function detectLang(filePath) {
132
- const ext = path2.extname(filePath).toLowerCase();
249
+ const ext = path4.extname(filePath).toLowerCase();
133
250
  return EXT_LANG_MAP[ext] ?? "plaintext";
134
251
  }
135
252
  var FolderBrowserError = class extends Error {
@@ -142,10 +259,10 @@ var FolderBrowserError = class extends Error {
142
259
  };
143
260
  var DIRECTORY_ENTRY_CAP = 500;
144
261
  async function listTheaterContents(theaterPath, relativePath, deps = {}) {
145
- const opendir = deps.opendir ?? fs.promises.opendir;
146
- const stat = deps.stat ?? fs.promises.stat;
147
- const targetAbs = path2.resolve(theaterPath, relativePath);
148
- const normalizedRoot = theaterPath.endsWith(path2.sep) ? theaterPath : theaterPath + path2.sep;
262
+ const opendir = deps.opendir ?? fs3.promises.opendir;
263
+ const stat = deps.stat ?? fs3.promises.stat;
264
+ const targetAbs = path4.resolve(theaterPath, relativePath);
265
+ const normalizedRoot = theaterPath.endsWith(path4.sep) ? theaterPath : theaterPath + path4.sep;
149
266
  if (targetAbs !== theaterPath && !targetAbs.startsWith(normalizedRoot)) {
150
267
  throw new FolderBrowserError("forbidden");
151
268
  }
@@ -153,13 +270,13 @@ async function listTheaterContents(theaterPath, relativePath, deps = {}) {
153
270
  let realTargetAbs;
154
271
  try {
155
272
  [realRoot, realTargetAbs] = await Promise.all([
156
- fs.promises.realpath(theaterPath),
157
- fs.promises.realpath(targetAbs)
273
+ fs3.promises.realpath(theaterPath),
274
+ fs3.promises.realpath(targetAbs)
158
275
  ]);
159
276
  } catch (error) {
160
277
  throw mapFsError(error);
161
278
  }
162
- const realNormalizedRoot = realRoot.endsWith(path2.sep) ? realRoot : realRoot + path2.sep;
279
+ const realNormalizedRoot = realRoot.endsWith(path4.sep) ? realRoot : realRoot + path4.sep;
163
280
  if (realTargetAbs !== realRoot && !realTargetAbs.startsWith(realNormalizedRoot)) {
164
281
  throw new FolderBrowserError("forbidden");
165
282
  }
@@ -171,8 +288,8 @@ async function listTheaterContents(theaterPath, relativePath, deps = {}) {
171
288
  if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
172
289
  return a.name.localeCompare(b.name);
173
290
  });
174
- const rel = path2.relative(realRoot, realTargetAbs);
175
- const parentRel = rel === "" ? null : path2.relative(realRoot, path2.dirname(realTargetAbs));
291
+ const rel = path4.relative(realRoot, realTargetAbs);
292
+ const parentRel = rel === "" ? null : path4.relative(realRoot, path4.dirname(realTargetAbs));
176
293
  return {
177
294
  relativePath: rel,
178
295
  parentRelativePath: parentRel === "" ? null : parentRel ?? null,
@@ -212,14 +329,14 @@ async function openDirectory(targetPath, opendir) {
212
329
  }
213
330
  }
214
331
  async function toContentsEntry(targetPath, theaterPath, dirent, stat) {
215
- const entryPath = path2.join(targetPath, dirent.name);
216
- const rel = path2.relative(theaterPath, entryPath);
332
+ const entryPath = path4.join(targetPath, dirent.name);
333
+ const rel = path4.relative(theaterPath, entryPath);
217
334
  if (dirent.isDirectory()) return { name: dirent.name, relativePath: rel, kind: "dir" };
218
335
  if (dirent.isFile()) return { name: dirent.name, relativePath: rel, kind: "file" };
219
336
  if (dirent.isSymbolicLink()) {
220
337
  try {
221
- const realEntryPath = await fs.promises.realpath(entryPath);
222
- const realNormalizedRoot = theaterPath.endsWith(path2.sep) ? theaterPath : theaterPath + path2.sep;
338
+ const realEntryPath = await fs3.promises.realpath(entryPath);
339
+ const realNormalizedRoot = theaterPath.endsWith(path4.sep) ? theaterPath : theaterPath + path4.sep;
223
340
  if (realEntryPath !== theaterPath && !realEntryPath.startsWith(realNormalizedRoot)) return null;
224
341
  const s = await stat(realEntryPath);
225
342
  if (s.isDirectory()) return { name: dirent.name, relativePath: rel, kind: "dir" };
@@ -236,6 +353,142 @@ function mapFsError(error) {
236
353
  if (code === "ENOENT" || code === "ENOTDIR") return new FolderBrowserError("not_found");
237
354
  return new FolderBrowserError("invalid_path");
238
355
  }
356
+ var GitStatusPathError = class extends Error {
357
+ code;
358
+ constructor(code) {
359
+ super(code);
360
+ this.name = "GitStatusPathError";
361
+ this.code = code;
362
+ }
363
+ };
364
+ var GIT_TIMEOUT_MS = 5e3;
365
+ var GIT_MAX_BUFFER = 8 * 1024 * 1024;
366
+ var GIT_STATUS_CAP = 1e4;
367
+ var UNMERGED_STATUS_PAIRS = /* @__PURE__ */ new Set(["UU", "UD", "DU", "AA", "AU", "UA", "DD"]);
368
+ async function readTheaterGitStatus(theaterPath, deps = {}) {
369
+ const realpath = deps.realpath ?? ((target) => fs8.realpath(target));
370
+ const execGit = deps.execGit ?? executeGit;
371
+ let theaterRootAbs;
372
+ try {
373
+ theaterRootAbs = await realpath(path4.resolve(theaterPath));
374
+ } catch (error) {
375
+ throw mapFsError2(error);
376
+ }
377
+ try {
378
+ const options = {
379
+ env: sanitizeGitEnvironment(deps.environment ?? process.env),
380
+ killSignal: "SIGKILL",
381
+ maxBuffer: GIT_MAX_BUFFER,
382
+ timeout: GIT_TIMEOUT_MS
383
+ };
384
+ const gitArgs = (args) => [
385
+ "-c",
386
+ "core.fsmonitor=false",
387
+ "--no-optional-locks",
388
+ "-C",
389
+ theaterRootAbs,
390
+ ...args
391
+ ];
392
+ const [prefixOutput, statusOutput] = await Promise.all([
393
+ execGit(gitArgs(["rev-parse", "--show-prefix"]), options),
394
+ execGit(gitArgs(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", "."]), options)
395
+ ]);
396
+ const prefix = stripTrailingLineBreak(prefixOutput);
397
+ const statuses = scopeGitStatusesToTheater(parseGitStatusPorcelainV1Z(statusOutput), prefix);
398
+ const truncated = statuses.length > GIT_STATUS_CAP;
399
+ return {
400
+ ok: true,
401
+ gitAvailable: true,
402
+ statuses: statuses.slice(0, GIT_STATUS_CAP),
403
+ ...truncated ? { truncated: true } : {}
404
+ };
405
+ } catch {
406
+ return { ok: true, gitAvailable: false, statuses: [] };
407
+ }
408
+ }
409
+ function parseGitStatusPorcelainV1Z(output) {
410
+ const records = output.split("\0");
411
+ const statuses = [];
412
+ for (let index = 0; index < records.length; index += 1) {
413
+ const record = records[index];
414
+ if (!record || record.length < 4 || record[2] !== " ") continue;
415
+ const indexStatus = record[0] ?? " ";
416
+ const worktreeStatus = record[1] ?? " ";
417
+ const statusPair = `${indexStatus}${worktreeStatus}`;
418
+ const gitPath = record.slice(3);
419
+ const isRenameOrCopy = statusPair.includes("R") || statusPair.includes("C");
420
+ const status = classifyGitStatus(statusPair);
421
+ if (status && gitPath) statuses.push({ gitPath, status });
422
+ if (isRenameOrCopy && index + 1 < records.length) index += 1;
423
+ }
424
+ return statuses;
425
+ }
426
+ function scopeGitStatusesToTheater(entries, prefix, separator = path4.sep) {
427
+ const statuses = [];
428
+ for (const entry of entries) {
429
+ if (prefix && !entry.gitPath.startsWith(prefix)) continue;
430
+ const relativeGitPath = prefix ? entry.gitPath.slice(prefix.length) : entry.gitPath;
431
+ const segments = relativeGitPath.split("/");
432
+ if (relativeGitPath === "" || relativeGitPath.startsWith("/") || segments.some((segment) => segment === "" || segment === "." || segment === "..")) continue;
433
+ const relativePath = segments.join(separator);
434
+ if (path4.isAbsolute(relativePath)) continue;
435
+ statuses.push({ path: relativePath, status: entry.status });
436
+ }
437
+ return statuses;
438
+ }
439
+ function classifyGitStatus(statusPair) {
440
+ if (statusPair === "??") return "untracked";
441
+ if (UNMERGED_STATUS_PAIRS.has(statusPair)) return "modified";
442
+ if (statusPair.includes("D")) return "deleted";
443
+ if ([...statusPair].some((status) => status === "A" || status === "M" || status === "R" || status === "C" || status === "T")) {
444
+ return "modified";
445
+ }
446
+ return null;
447
+ }
448
+ function executeGit(args, options) {
449
+ return new Promise((resolve, reject) => {
450
+ execFile(
451
+ "git",
452
+ [...args],
453
+ {
454
+ encoding: "utf8",
455
+ env: options.env,
456
+ killSignal: options.killSignal,
457
+ maxBuffer: options.maxBuffer,
458
+ shell: false,
459
+ timeout: options.timeout,
460
+ windowsHide: true
461
+ },
462
+ (error, stdout) => {
463
+ if (error) {
464
+ reject(error);
465
+ return;
466
+ }
467
+ resolve(stdout);
468
+ }
469
+ );
470
+ });
471
+ }
472
+ function sanitizeGitEnvironment(environment) {
473
+ const sanitized = {};
474
+ for (const [key, value] of Object.entries(environment)) {
475
+ if (key.toUpperCase().startsWith("GIT_")) continue;
476
+ if (value !== void 0) sanitized[key] = value;
477
+ }
478
+ sanitized.GIT_OPTIONAL_LOCKS = "0";
479
+ return sanitized;
480
+ }
481
+ function stripTrailingLineBreak(output) {
482
+ if (!output.endsWith("\n")) return output;
483
+ const withoutLineFeed = output.slice(0, -1);
484
+ return withoutLineFeed.endsWith("\r") ? withoutLineFeed.slice(0, -1) : withoutLineFeed;
485
+ }
486
+ function mapFsError2(error) {
487
+ const code = error.code;
488
+ if (code === "EACCES" || code === "EPERM") return new GitStatusPathError("forbidden");
489
+ if (code === "ENOENT" || code === "ENOTDIR") return new GitStatusPathError("not_found");
490
+ return new GitStatusPathError("invalid_path");
491
+ }
239
492
  var ImageServeError = class extends Error {
240
493
  code;
241
494
  constructor(code) {
@@ -258,17 +511,17 @@ var IMAGE_SECURITY_HEADERS = {
258
511
  "Cache-Control": "private, no-store"
259
512
  };
260
513
  async function readImageForTheater(theaterPath, relativePath) {
261
- const resolved = path2.resolve(theaterPath, relativePath);
514
+ const resolved = path4.resolve(theaterPath, relativePath);
262
515
  if (!isWithinRoot2(resolved, theaterPath)) throw new ImageServeError("path_outside_theater");
263
- const ext = path2.extname(resolved).toLowerCase();
516
+ const ext = path4.extname(resolved).toLowerCase();
264
517
  const mimeType = MIME_ALLOWLIST[ext];
265
518
  if (!mimeType) throw new ImageServeError("mime_not_allowed");
266
519
  let realResolved;
267
520
  let realRoot;
268
521
  try {
269
522
  [realResolved, realRoot] = await Promise.all([
270
- fs.promises.realpath(resolved),
271
- fs.promises.realpath(theaterPath)
523
+ fs3.promises.realpath(resolved),
524
+ fs3.promises.realpath(theaterPath)
272
525
  ]);
273
526
  } catch (error) {
274
527
  const code = error.code;
@@ -278,7 +531,7 @@ async function readImageForTheater(theaterPath, relativePath) {
278
531
  if (!isWithinRoot2(realResolved, realRoot)) throw new ImageServeError("path_outside_theater");
279
532
  let stat;
280
533
  try {
281
- stat = await fs.promises.stat(realResolved);
534
+ stat = await fs3.promises.stat(realResolved);
282
535
  } catch (error) {
283
536
  const code = error.code;
284
537
  if (code === "EACCES" || code === "EPERM") throw new ImageServeError("forbidden");
@@ -286,7 +539,7 @@ async function readImageForTheater(theaterPath, relativePath) {
286
539
  }
287
540
  if (!stat.isFile()) throw new ImageServeError("not_a_file");
288
541
  if (stat.size > IMAGE_SIZE_CAP) throw new ImageServeError("size_exceeded");
289
- const buffer = await fs.promises.readFile(realResolved);
542
+ const buffer = await fs3.promises.readFile(realResolved);
290
543
  return { mimeType, buffer };
291
544
  }
292
545
  function writeImageResponse(res, result) {
@@ -298,13 +551,65 @@ function writeImageResponse(res, result) {
298
551
  res.end(result.buffer);
299
552
  }
300
553
  function isWithinRoot2(resolved, root) {
301
- const normalizedRoot = root.endsWith(path2.sep) ? root : root + path2.sep;
554
+ const normalizedRoot = root.endsWith(path4.sep) ? root : root + path4.sep;
302
555
  return resolved === root || resolved.startsWith(normalizedRoot);
303
556
  }
557
+ var FileActionUnavailableError = class extends Error {
558
+ constructor() {
559
+ super("action_unavailable");
560
+ this.name = "FileActionUnavailableError";
561
+ }
562
+ };
563
+ var DEFAULT_DEPENDENCIES2 = {
564
+ platform: process.platform,
565
+ launch: spawnDetached
566
+ };
567
+ async function revealPath(theaterPath, relativePath, mode, dependencies = {}) {
568
+ const deps = { ...DEFAULT_DEPENDENCIES2, ...dependencies };
569
+ const { realPath } = await resolveContainedActionPath(theaterPath, relativePath);
570
+ const command = resolveRevealCommand(deps.platform, mode, realPath);
571
+ if (!command) throw new FileActionUnavailableError();
572
+ try {
573
+ await deps.launch(command.file, command.args);
574
+ } catch (error) {
575
+ if (error.code === "ENOENT") throw new FileActionUnavailableError();
576
+ throw error;
577
+ }
578
+ }
579
+ function resolveRevealCommand(platform, mode, absolutePath) {
580
+ if (platform === "darwin") {
581
+ return mode === "reveal" ? { file: "open", args: ["-R", absolutePath] } : { file: "open", args: [absolutePath] };
582
+ }
583
+ if (platform === "linux") {
584
+ return {
585
+ file: "xdg-open",
586
+ args: [mode === "reveal" ? path4.dirname(absolutePath) : absolutePath]
587
+ };
588
+ }
589
+ if (platform === "win32") {
590
+ return mode === "reveal" ? { file: "explorer", args: [`/select,${absolutePath}`] } : { file: "explorer", args: [absolutePath] };
591
+ }
592
+ return null;
593
+ }
594
+ function spawnDetached(file, args) {
595
+ return new Promise((resolve, reject) => {
596
+ const child = spawn(file, [...args], {
597
+ detached: true,
598
+ shell: false,
599
+ stdio: "ignore",
600
+ windowsHide: true
601
+ });
602
+ child.once("error", reject);
603
+ child.once("spawn", () => {
604
+ child.unref();
605
+ resolve();
606
+ });
607
+ });
608
+ }
304
609
  var DEBOUNCE_MS = 200;
305
610
  var LINUX_DIRECTORY_WATCH_CAP = 1024;
306
611
  var watcherRegistry = createWatcherRegistry();
307
- function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opts, (ev, fn) => cb(ev, typeof fn === "string" ? fn : null)), debounceMs = DEBOUNCE_MS, platform = process.platform) {
612
+ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs3.watch(p, opts, (ev, fn) => cb(ev, typeof fn === "string" ? fn : null)), debounceMs = DEBOUNCE_MS, platform = process.platform) {
308
613
  const entries = /* @__PURE__ */ new Map();
309
614
  function scheduleChange(entry, relDir) {
310
615
  const pending = entry.debounceTimers.get(relDir);
@@ -355,7 +660,7 @@ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opt
355
660
  }
356
661
  }
357
662
  function invalidateWatcherTree(entry, watchKey) {
358
- const descendantPrefix = watchKey + path2.sep;
663
+ const descendantPrefix = watchKey + path4.sep;
359
664
  for (const [candidateKey, candidateWatcher] of entry.watchers) {
360
665
  if (candidateKey !== watchKey && !candidateKey.startsWith(descendantPrefix)) continue;
361
666
  entry.watchers.delete(candidateKey);
@@ -371,7 +676,7 @@ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opt
371
676
  let identityCheckRunning = false;
372
677
  let identityCheckRequested = false;
373
678
  let activeWatcher = null;
374
- const lexicalWatchPath = path2.resolve(entry.theaterPath, watchKey);
679
+ const lexicalWatchPath = path4.resolve(entry.theaterPath, watchKey);
375
680
  const checkDirectoryIdentity = async (expectedWatcher) => {
376
681
  identityCheckRequested = true;
377
682
  if (identityCheckRunning || !watchIdentity) return;
@@ -381,7 +686,7 @@ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opt
381
686
  identityCheckRequested = false;
382
687
  let replaced = false;
383
688
  try {
384
- const stats = await fs.promises.stat(lexicalWatchPath);
689
+ const stats = await fs3.promises.stat(lexicalWatchPath);
385
690
  replaced = stats.dev !== watchIdentity.device || stats.ino !== watchIdentity.inode;
386
691
  } catch (error) {
387
692
  replaced = error.code === "ENOENT";
@@ -403,7 +708,7 @@ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opt
403
708
  if (!entry.recursive && event === "rename") {
404
709
  if (watchKey !== "" && watchIdentity) void checkDirectoryIdentity(currentWatcher);
405
710
  if (filename) {
406
- const changedChildKey = path2.join(watchKey, filename);
711
+ const changedChildKey = path4.join(watchKey, filename);
407
712
  entry.identityChecks.get(changedChildKey)?.();
408
713
  }
409
714
  }
@@ -507,16 +812,16 @@ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opt
507
812
  return { subscribe, trackDirectory };
508
813
  }
509
814
  async function resolveTrackedDirectory(theaterPath, relativePath) {
510
- const resolvedRoot = path2.resolve(theaterPath);
511
- const targetPath = path2.resolve(resolvedRoot, relativePath);
815
+ const resolvedRoot = path4.resolve(theaterPath);
816
+ const targetPath = path4.resolve(resolvedRoot, relativePath);
512
817
  if (!isWithinRoot3(targetPath, resolvedRoot)) return null;
513
818
  try {
514
819
  const [realRoot, realTarget] = await Promise.all([
515
- fs.promises.realpath(theaterPath),
516
- fs.promises.realpath(targetPath)
820
+ fs3.promises.realpath(theaterPath),
821
+ fs3.promises.realpath(targetPath)
517
822
  ]);
518
823
  if (!isWithinRoot3(realTarget, realRoot)) return null;
519
- const stats = await fs.promises.stat(realTarget);
824
+ const stats = await fs3.promises.stat(realTarget);
520
825
  if (!stats.isDirectory()) return null;
521
826
  return {
522
827
  path: realTarget,
@@ -527,7 +832,7 @@ async function resolveTrackedDirectory(theaterPath, relativePath) {
527
832
  }
528
833
  }
529
834
  function isWithinRoot3(candidate, root) {
530
- const normalizedRoot = root.endsWith(path2.sep) ? root : root + path2.sep;
835
+ const normalizedRoot = root.endsWith(path4.sep) ? root : root + path4.sep;
531
836
  return candidate === root || candidate.startsWith(normalizedRoot);
532
837
  }
533
838
  function computeRelDir(filename) {
@@ -538,6 +843,8 @@ function computeRelDir(filename) {
538
843
  }
539
844
 
540
845
  // ../fleet-plugins/file-explorer/server/handlers.ts
846
+ var DEFAULT_CLIPBOARD_DEPENDENCIES = { copyPath: copyPathToClipboard };
847
+ var DEFAULT_REVEAL_DEPENDENCIES = { revealPath };
541
848
  function isPlainObject(value) {
542
849
  return typeof value === "object" && value !== null && !Array.isArray(value);
543
850
  }
@@ -587,6 +894,36 @@ async function handleFilesList(req, res, ctx) {
587
894
  throw error;
588
895
  }
589
896
  }
897
+ async function handleFilesGitStatus(req, res, ctx) {
898
+ if (req.method !== "POST") {
899
+ ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
900
+ return;
901
+ }
902
+ if (!ctx.host.security.isTerminalAuthorized(req)) {
903
+ ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
904
+ return;
905
+ }
906
+ const body = await ctx.host.http.readJsonBody(req);
907
+ if (!isPlainObject(body) || typeof body.theaterId !== "string") {
908
+ ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
909
+ return;
910
+ }
911
+ const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
912
+ if (!theaterPath) {
913
+ ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
914
+ return;
915
+ }
916
+ try {
917
+ ctx.host.http.writeJson(res, 200, await readTheaterGitStatus(theaterPath));
918
+ } catch (error) {
919
+ if (error instanceof GitStatusPathError) {
920
+ const httpStatus = error.code === "forbidden" ? 403 : error.code === "not_found" ? 404 : 400;
921
+ ctx.host.http.writeJson(res, httpStatus, { error: error.code });
922
+ return;
923
+ }
924
+ throw error;
925
+ }
926
+ }
590
927
  async function handleFilesRead(req, res, ctx) {
591
928
  if (req.method !== "POST") {
592
929
  ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
@@ -660,6 +997,74 @@ async function handleFilesImage(req, res, ctx) {
660
997
  throw error;
661
998
  }
662
999
  }
1000
+ async function handleFilesClipboard(req, res, ctx, dependencies = DEFAULT_CLIPBOARD_DEPENDENCIES) {
1001
+ if (req.method !== "POST") {
1002
+ ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
1003
+ return;
1004
+ }
1005
+ if (!ctx.host.security.isTerminalAuthorized(req)) {
1006
+ ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
1007
+ return;
1008
+ }
1009
+ const body = await ctx.host.http.readJsonBody(req);
1010
+ if (!isPlainObject(body) || typeof body.theaterId !== "string" || typeof body.relativePath !== "string") {
1011
+ ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
1012
+ return;
1013
+ }
1014
+ const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
1015
+ if (!theaterPath) {
1016
+ ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
1017
+ return;
1018
+ }
1019
+ try {
1020
+ await dependencies.copyPath(theaterPath, body.relativePath);
1021
+ writeNoContent(res);
1022
+ } catch (error) {
1023
+ if (error instanceof PathActionError) {
1024
+ writePathActionError(res, ctx, error);
1025
+ return;
1026
+ }
1027
+ if (error instanceof ClipboardUnavailableError) {
1028
+ ctx.host.http.writeJson(res, 501, { error: "clipboard_unavailable" });
1029
+ return;
1030
+ }
1031
+ ctx.host.http.writeJson(res, 500, { error: "clipboard_failed" });
1032
+ }
1033
+ }
1034
+ async function handleFilesReveal(req, res, ctx, dependencies = DEFAULT_REVEAL_DEPENDENCIES) {
1035
+ if (req.method !== "POST") {
1036
+ ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
1037
+ return;
1038
+ }
1039
+ if (!ctx.host.security.isTerminalAuthorized(req)) {
1040
+ ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
1041
+ return;
1042
+ }
1043
+ const body = await ctx.host.http.readJsonBody(req);
1044
+ if (!isPlainObject(body) || typeof body.theaterId !== "string" || typeof body.relativePath !== "string" || !isFileRevealMode(body.mode)) {
1045
+ ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
1046
+ return;
1047
+ }
1048
+ const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
1049
+ if (!theaterPath) {
1050
+ ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
1051
+ return;
1052
+ }
1053
+ try {
1054
+ await dependencies.revealPath(theaterPath, body.relativePath, body.mode);
1055
+ writeNoContent(res);
1056
+ } catch (error) {
1057
+ if (error instanceof PathActionError) {
1058
+ writePathActionError(res, ctx, error);
1059
+ return;
1060
+ }
1061
+ if (error instanceof FileActionUnavailableError) {
1062
+ ctx.host.http.writeJson(res, 501, { error: "action_unavailable" });
1063
+ return;
1064
+ }
1065
+ ctx.host.http.writeJson(res, 500, { error: "action_failed" });
1066
+ }
1067
+ }
663
1068
  function handleFilesWatch(req, res, ctx) {
664
1069
  if (req.method !== "GET") {
665
1070
  ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
@@ -711,10 +1116,21 @@ data: ${data}
711
1116
  }
712
1117
  });
713
1118
  }
1119
+ function isFileRevealMode(value) {
1120
+ return value === "reveal" || value === "open";
1121
+ }
1122
+ function writeNoContent(res) {
1123
+ res.statusCode = 204;
1124
+ res.end();
1125
+ }
1126
+ function writePathActionError(res, ctx, error) {
1127
+ const status = error.code === "not_found" ? 404 : 403;
1128
+ ctx.host.http.writeJson(res, status, { error: error.code });
1129
+ }
714
1130
  var SEARCH_DIRECTORY_CAP = 500;
715
1131
  var SEARCH_ENTRY_CAP = 25e3;
716
1132
  async function searchTheaterFiles(theaterPath, query, limit) {
717
- const realRoot = await fs5.realpath(theaterPath);
1133
+ const realRoot = await fs8.realpath(theaterPath);
718
1134
  const tokens = query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean);
719
1135
  const pending = [{ absolutePath: realRoot, relativePath: "" }];
720
1136
  const visited = /* @__PURE__ */ new Set();
@@ -728,7 +1144,7 @@ async function searchTheaterFiles(theaterPath, query, limit) {
728
1144
  directoryCount += 1;
729
1145
  let entries;
730
1146
  try {
731
- entries = await fs5.readdir(directory.absolutePath, { withFileTypes: true });
1147
+ entries = await fs8.readdir(directory.absolutePath, { withFileTypes: true });
732
1148
  } catch {
733
1149
  continue;
734
1150
  }
@@ -737,14 +1153,14 @@ async function searchTheaterFiles(theaterPath, query, limit) {
737
1153
  if (entryCount >= SEARCH_ENTRY_CAP) break;
738
1154
  entryCount += 1;
739
1155
  const relativePath = directory.relativePath ? `${directory.relativePath}/${entry.name}` : entry.name;
740
- const absolutePath = path2.join(directory.absolutePath, entry.name);
1156
+ const absolutePath = path4.join(directory.absolutePath, entry.name);
741
1157
  let kind = entry.isDirectory() ? "dir" : entry.isFile() ? "file" : null;
742
1158
  let realPath = absolutePath;
743
1159
  if (entry.isSymbolicLink()) {
744
1160
  try {
745
- realPath = await fs5.realpath(absolutePath);
1161
+ realPath = await fs8.realpath(absolutePath);
746
1162
  if (!isContained(realRoot, realPath)) continue;
747
- const stat = await fs5.stat(realPath);
1163
+ const stat = await fs8.stat(realPath);
748
1164
  kind = stat.isDirectory() ? "dir" : stat.isFile() ? "file" : null;
749
1165
  } catch {
750
1166
  continue;
@@ -797,8 +1213,8 @@ function compareFileSearchItem(left, right, query) {
797
1213
  return leftRank - rightRank || left.relativePath.localeCompare(right.relativePath);
798
1214
  }
799
1215
  function isContained(root, candidate) {
800
- const relative = path2.relative(root, candidate);
801
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
1216
+ const relative = path4.relative(root, candidate);
1217
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path4.sep}`) && !path4.isAbsolute(relative);
802
1218
  }
803
1219
  function isPlainObject2(value) {
804
1220
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -812,6 +1228,10 @@ var routes_default = definePlugin({
812
1228
  await handleFilesList(req, res, ctx);
813
1229
  return true;
814
1230
  });
1231
+ registerRouter(ctx, "files/git-status", async ({ req, res }) => {
1232
+ await handleFilesGitStatus(req, res, ctx);
1233
+ return true;
1234
+ });
815
1235
  registerRouter(ctx, "files/read", async ({ req, res }) => {
816
1236
  await handleFilesRead(req, res, ctx);
817
1237
  return true;
@@ -820,6 +1240,14 @@ var routes_default = definePlugin({
820
1240
  await handleFilesImage(req, res, ctx);
821
1241
  return true;
822
1242
  });
1243
+ registerRouter(ctx, "files/clipboard", async ({ req, res }) => {
1244
+ await handleFilesClipboard(req, res, ctx);
1245
+ return true;
1246
+ });
1247
+ registerRouter(ctx, "files/reveal", async ({ req, res }) => {
1248
+ await handleFilesReveal(req, res, ctx);
1249
+ return true;
1250
+ });
823
1251
  registerRouter(ctx, "files/watch", ({ req, res }) => {
824
1252
  handleFilesWatch(req, res, ctx);
825
1253
  return true;