@dotobokuri/fleet-console 1.28.0 → 1.30.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 (65) hide show
  1. package/dist/cli.mjs +209 -387
  2. package/dist/client/assets/{_baseUniq-0TpVlb56.js → _baseUniq-Dvbxpxj-.js} +1 -1
  3. package/dist/client/assets/{arc-BIRhKPNZ.js → arc-Cqerxs8I.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-Q4EWVU46-CJzkivmk.js → architectureDiagram-Q4EWVU46-gNhmCKnU.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-DXYQGD6D-PkVBy6UB.js → blockDiagram-DXYQGD6D-CM5HRpKC.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AHTNJAMY-TOO0Zc07.js → c4Diagram-AHTNJAMY-DxLhQuvg.js} +1 -1
  7. package/dist/client/assets/channel-DKkQpLWI.js +1 -0
  8. package/dist/client/assets/{chunk-4BX2VUAB-Ugm2Ht26.js → chunk-4BX2VUAB-HX6C_GAC.js} +1 -1
  9. package/dist/client/assets/{chunk-4TB4RGXK-Byfwafb_.js → chunk-4TB4RGXK-CoBQUhS4.js} +1 -1
  10. package/dist/client/assets/{chunk-55IACEB6-2I_nwtuS.js → chunk-55IACEB6-DWsWxGBm.js} +1 -1
  11. package/dist/client/assets/{chunk-EDXVE4YY-JYP41aJo.js → chunk-EDXVE4YY-BXNRu1M4.js} +1 -1
  12. package/dist/client/assets/{chunk-FMBD7UC4-Duh-U1jY.js → chunk-FMBD7UC4-DzKgP1_A.js} +1 -1
  13. package/dist/client/assets/{chunk-OYMX7WX6-64cqkTOO.js → chunk-OYMX7WX6-aqXdvKUD.js} +1 -1
  14. package/dist/client/assets/{chunk-QZHKN3VN-engflZJB.js → chunk-QZHKN3VN-BbMqaUb0.js} +1 -1
  15. package/dist/client/assets/{chunk-YZCP3GAM-BwnK_1vX.js → chunk-YZCP3GAM-D8Mo0nv8.js} +1 -1
  16. package/dist/client/assets/classDiagram-6PBFFD2Q-CQurO5ix.js +1 -0
  17. package/dist/client/assets/classDiagram-v2-HSJHXN6E-CQurO5ix.js +1 -0
  18. package/dist/client/assets/clone-IodjsAGU.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-S5V4N54A-D43_BnnE.js → cose-bilkent-S5V4N54A-BOuVjjXz.js} +1 -1
  20. package/dist/client/assets/{dagre-KV5264BT-Wh_p74YO.js → dagre-KV5264BT-BdUFzAj_.js} +1 -1
  21. package/dist/client/assets/{diagram-5BDNPKRD-BNcxA6wC.js → diagram-5BDNPKRD-C0iFrsGo.js} +1 -1
  22. package/dist/client/assets/{diagram-G4DWMVQ6-HQdA06kX.js → diagram-G4DWMVQ6-CGxGeQZt.js} +1 -1
  23. package/dist/client/assets/{diagram-MMDJMWI5-DlMdX_ng.js → diagram-MMDJMWI5-Dex5uqlA.js} +1 -1
  24. package/dist/client/assets/{diagram-TYMM5635-C1hKAjZD.js → diagram-TYMM5635-O48PEpaS.js} +1 -1
  25. package/dist/client/assets/{erDiagram-SMLLAGMA-CsSiuv9G.js → erDiagram-SMLLAGMA-B7PGw3YA.js} +1 -1
  26. package/dist/client/assets/{flowDiagram-DWJPFMVM-DfjQ3Ug9.js → flowDiagram-DWJPFMVM-CCXWJIMw.js} +1 -1
  27. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-B0QHKePg.js → ganttDiagram-T4ZO3ILL-B26RghpR.js} +1 -1
  28. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-_zejoqTH.js → gitGraphDiagram-UUTBAWPF-BJsjMn6A.js} +1 -1
  29. package/dist/client/assets/{graph-Dr6IPcji.js → graph-DuEL0RWH.js} +1 -1
  30. package/dist/client/assets/index-Dj775lUe.css +1 -0
  31. package/dist/client/assets/index-r6rDZQCs.js +451 -0
  32. package/dist/client/assets/{infoDiagram-42DDH7IO-dhruAwPt.js → infoDiagram-42DDH7IO-D32dz1SM.js} +1 -1
  33. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DPoZZiH0.js → ishikawaDiagram-UXIWVN3A-4YPQjln0.js} +1 -1
  34. package/dist/client/assets/{journeyDiagram-VCZTEJTY-DQk8gmrd.js → journeyDiagram-VCZTEJTY-BSvi22HM.js} +1 -1
  35. package/dist/client/assets/{kanban-definition-6JOO6SKY-B9rOgVFq.js → kanban-definition-6JOO6SKY-SozOXS7m.js} +1 -1
  36. package/dist/client/assets/{layout-D54BXsv2.js → layout-CNXntIRV.js} +1 -1
  37. package/dist/client/assets/{linear-DJxG7kFF.js → linear-9RZPFtO4.js} +1 -1
  38. package/dist/client/assets/{mermaid.core-BBFWA-zA.js → mermaid.core-kwH-UxKY.js} +4 -4
  39. package/dist/client/assets/{min-Dv_aLAYb.js → min-BdN2p8Q_.js} +1 -1
  40. package/dist/client/assets/{mindmap-definition-QFDTVHPH-Dv5woCon.js → mindmap-definition-QFDTVHPH-H-3dJmH9.js} +1 -1
  41. package/dist/client/assets/{pieDiagram-DEJITSTG-B_V_vCG8.js → pieDiagram-DEJITSTG-CspzYvcs.js} +1 -1
  42. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-QHqq3tFD.js → quadrantDiagram-34T5L4WZ-D1C7e3-s.js} +1 -1
  43. package/dist/client/assets/{requirementDiagram-MS252O5E-BhPhfMzK.js → requirementDiagram-MS252O5E-BJwKRtMi.js} +1 -1
  44. package/dist/client/assets/{sankeyDiagram-XADWPNL6-D2-ZsaS_.js → sankeyDiagram-XADWPNL6-7oc42Z3P.js} +1 -1
  45. package/dist/client/assets/{sequenceDiagram-FGHM5R23-Di0qgM3v.js → sequenceDiagram-FGHM5R23-TP5H_DnF.js} +1 -1
  46. package/dist/client/assets/{stateDiagram-FHFEXIEX-BAuqrwY8.js → stateDiagram-FHFEXIEX-BWnrNgJY.js} +1 -1
  47. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Cmp8tobq.js +1 -0
  48. package/dist/client/assets/{timeline-definition-GMOUNBTQ-BcMGkC6T.js → timeline-definition-GMOUNBTQ-xXFvCsrr.js} +1 -1
  49. package/dist/client/assets/{vennDiagram-DHZGUBPP-C9dkwyKX.js → vennDiagram-DHZGUBPP-D5unm_iH.js} +1 -1
  50. package/dist/client/assets/{wardley-RL74JXVD-BLLCncPa.js → wardley-RL74JXVD-B-eT02rs.js} +1 -1
  51. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-Yb9mlL0_.js → wardleyDiagram-NUSXRM2D-BTQ1Yqmb.js} +1 -1
  52. package/dist/client/assets/{xychartDiagram-5P7HB3ND-BAHLdje7.js → xychartDiagram-5P7HB3ND-Bialvg0l.js} +1 -1
  53. package/dist/client/index.html +2 -2
  54. package/dist/fleet-plugins/file-explorer/routes.mjs +199 -38
  55. package/dist/fleet-plugins/repository/routes.mjs +407 -108
  56. package/dist/fleet-plugins/skills/routes.mjs +60 -79
  57. package/dist/fleet-plugins/terminal/routes.mjs +577 -102
  58. package/package.json +1 -1
  59. package/dist/client/assets/channel-CqmujVSi.js +0 -1
  60. package/dist/client/assets/classDiagram-6PBFFD2Q-DLXkYur6.js +0 -1
  61. package/dist/client/assets/classDiagram-v2-HSJHXN6E-DLXkYur6.js +0 -1
  62. package/dist/client/assets/clone-KonJWRw9.js +0 -1
  63. package/dist/client/assets/index-CsmxZ8UQ.js +0 -453
  64. package/dist/client/assets/index-DC8tBAf_.css +0 -1
  65. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DhNebJyd.js +0 -1
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'child_process';
3
3
  import 'fs';
4
- import path2 from 'path';
5
- import fs from 'fs/promises';
4
+ import path4 from 'path';
5
+ import fs2 from 'fs/promises';
6
6
 
7
7
  // sdk/plugin/node.ts
8
8
  function definePlugin(definition) {
9
9
  return definition;
10
10
  }
11
- function registerRouter(ctx, path5, handler) {
12
- ctx.registerRouter(normalizePath(path5), handler);
11
+ function registerRouter(ctx, path8, handler) {
12
+ ctx.registerRouter(normalizePath(path8), handler);
13
13
  }
14
- function normalizePath(path5) {
15
- if (path5.length === 0 || path5 === "/") return "/";
16
- return path5;
14
+ function normalizePath(path8) {
15
+ if (path8.length === 0 || path8 === "/") return "/";
16
+ return path8;
17
17
  }
18
18
 
19
19
  // ../../packages/core-process/src/hidden.ts
@@ -97,6 +97,72 @@ function runGit(args, opts) {
97
97
  });
98
98
  });
99
99
  }
100
+ function isPathContained(root, candidate) {
101
+ const relative = path4.relative(root, candidate);
102
+ return relative === "" || !path4.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path4.sep}`);
103
+ }
104
+ function isSelectableRepoRel(repoRel) {
105
+ if (repoRel === "") return true;
106
+ if (path4.isAbsolute(repoRel)) return false;
107
+ const normalized = path4.normalize(repoRel);
108
+ return !normalized.startsWith("-") && normalized !== ".." && !normalized.startsWith(`..${path4.sep}`);
109
+ }
110
+
111
+ // ../fleet-plugins/repository/server/git-marker.ts
112
+ var GITDIR_PREFIX = "gitdir:";
113
+ async function resolveContainedGitDir(repoDir, realTheaterPath) {
114
+ const markerPath = path4.join(repoDir, ".git");
115
+ let marker;
116
+ try {
117
+ marker = await fs2.stat(markerPath);
118
+ } catch {
119
+ return null;
120
+ }
121
+ let gitDir;
122
+ if (marker.isFile()) {
123
+ let content;
124
+ try {
125
+ content = await fs2.readFile(markerPath, "utf8");
126
+ } catch {
127
+ return null;
128
+ }
129
+ const line = content.split("\n").find((entry) => entry.trim().startsWith(GITDIR_PREFIX));
130
+ if (!line) return null;
131
+ const target = line.trim().slice(GITDIR_PREFIX.length).trim();
132
+ if (!target) return null;
133
+ gitDir = path4.resolve(repoDir, target);
134
+ } else if (marker.isDirectory()) {
135
+ gitDir = markerPath;
136
+ } else {
137
+ return null;
138
+ }
139
+ let realGitDir;
140
+ try {
141
+ realGitDir = await fs2.realpath(gitDir);
142
+ } catch {
143
+ return null;
144
+ }
145
+ if (!isPathContained(realTheaterPath, realGitDir)) return null;
146
+ let commonRaw = null;
147
+ try {
148
+ commonRaw = await fs2.readFile(path4.join(realGitDir, "commondir"), "utf8");
149
+ } catch {
150
+ }
151
+ if (commonRaw !== null) {
152
+ const commonTarget = commonRaw.trim();
153
+ if (!commonTarget) return null;
154
+ let realCommonDir;
155
+ try {
156
+ realCommonDir = await fs2.realpath(path4.resolve(realGitDir, commonTarget));
157
+ } catch {
158
+ return null;
159
+ }
160
+ if (!isPathContained(realTheaterPath, realCommonDir)) return null;
161
+ }
162
+ return realGitDir;
163
+ }
164
+
165
+ // ../fleet-plugins/repository/server/diff.ts
100
166
  function isPlainObject(value) {
101
167
  return typeof value === "object" && value !== null && !Array.isArray(value);
102
168
  }
@@ -154,24 +220,29 @@ function isNoHeadError(error) {
154
220
  if (error.code !== "non_zero_exit") return false;
155
221
  return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision");
156
222
  }
157
- async function resolveGitCwd(theaterPath, rawSubPath) {
158
- if (rawSubPath === "") return { gitCwd: theaterPath };
159
- const resolvedSub = path2.resolve(theaterPath, rawSubPath);
160
- if (!resolvedSub.startsWith(theaterPath + path2.sep) && resolvedSub !== theaterPath) return null;
161
- if (path2.normalize(rawSubPath).startsWith("..")) return null;
223
+ var InvalidRepoError = class extends Error {
224
+ code = "invalid_repo";
225
+ };
226
+ async function resolveGitCwd(theaterPath, repoRel = "") {
227
+ if (repoRel === void 0 || repoRel === "") return { gitCwd: theaterPath };
228
+ if (typeof repoRel !== "string" || path4.isAbsolute(repoRel)) throw new InvalidRepoError("Repository path must be relative");
229
+ if (!isSelectableRepoRel(repoRel)) throw new InvalidRepoError("Repository path is invalid");
230
+ const resolved = path4.resolve(theaterPath, path4.normalize(repoRel));
231
+ if (!isPathContained(theaterPath, resolved)) throw new InvalidRepoError("Repository path escapes Theater");
162
232
  let realTheater;
163
- let realSub;
233
+ let realRepo;
164
234
  try {
165
- [realTheater, realSub] = await Promise.all([
166
- fs.realpath(theaterPath),
167
- fs.realpath(resolvedSub)
168
- ]);
235
+ [realTheater, realRepo] = await Promise.all([fs2.realpath(theaterPath), fs2.realpath(resolved)]);
169
236
  } catch {
170
- return null;
237
+ throw new InvalidRepoError("Repository path does not exist");
238
+ }
239
+ if (!isPathContained(realTheater, realRepo)) {
240
+ throw new InvalidRepoError("Repository path escapes Theater");
241
+ }
242
+ if (await resolveContainedGitDir(realRepo, realTheater) === null) {
243
+ throw new InvalidRepoError("Repository marker does not exist");
171
244
  }
172
- const norm = realTheater.endsWith(path2.sep) ? realTheater : realTheater + path2.sep;
173
- if (realSub !== realTheater && !realSub.startsWith(norm)) return null;
174
- return { gitCwd: resolvedSub };
245
+ return { gitCwd: realRepo };
175
246
  }
176
247
  async function handleRepositoryChanged(req, res, ctx) {
177
248
  if (req.method !== "POST") {
@@ -183,7 +254,7 @@ async function handleRepositoryChanged(req, res, ctx) {
183
254
  return;
184
255
  }
185
256
  const body = await ctx.host.http.readJsonBody(req);
186
- if (!isPlainObject(body)) {
257
+ if (!isPlainObject(body) || "subPath" in body) {
187
258
  ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
188
259
  return;
189
260
  }
@@ -197,11 +268,15 @@ async function handleRepositoryChanged(req, res, ctx) {
197
268
  ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
198
269
  return;
199
270
  }
200
- const rawSubPath = typeof body.subPath === "string" ? body.subPath : "";
201
- const cwdResult = await resolveGitCwd(theaterPath, rawSubPath);
202
- if (!cwdResult) {
203
- ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
204
- return;
271
+ let cwdResult;
272
+ try {
273
+ cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
274
+ } catch (error) {
275
+ if (error instanceof InvalidRepoError) {
276
+ ctx.host.http.writeJson(res, 400, { error: error.code });
277
+ return;
278
+ }
279
+ throw error;
205
280
  }
206
281
  const { gitCwd } = cwdResult;
207
282
  try {
@@ -252,7 +327,7 @@ async function handleRepositoryFile(req, res, ctx) {
252
327
  return;
253
328
  }
254
329
  const body = await ctx.host.http.readJsonBody(req);
255
- if (!isPlainObject(body) || typeof body.filePath !== "string") {
330
+ if (!isPlainObject(body) || "subPath" in body || typeof body.filePath !== "string") {
256
331
  ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
257
332
  return;
258
333
  }
@@ -271,20 +346,24 @@ async function handleRepositoryFile(req, res, ctx) {
271
346
  ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
272
347
  return;
273
348
  }
274
- const rawSubPath = typeof body.subPath === "string" ? body.subPath : "";
275
- const cwdResult = await resolveGitCwd(theaterPath, rawSubPath);
276
- if (!cwdResult) {
277
- ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
278
- return;
349
+ let cwdResult;
350
+ try {
351
+ cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
352
+ } catch (error) {
353
+ if (error instanceof InvalidRepoError) {
354
+ ctx.host.http.writeJson(res, 400, { error: error.code });
355
+ return;
356
+ }
357
+ throw error;
279
358
  }
280
359
  const { gitCwd } = cwdResult;
281
360
  const rawFilePath = body.filePath;
282
- const resolvedFilePath = path2.resolve(gitCwd, rawFilePath);
283
- if (!resolvedFilePath.startsWith(gitCwd + path2.sep) && resolvedFilePath !== gitCwd) {
361
+ const resolvedFilePath = path4.resolve(gitCwd, rawFilePath);
362
+ if (!resolvedFilePath.startsWith(gitCwd + path4.sep) && resolvedFilePath !== gitCwd) {
284
363
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
285
364
  return;
286
365
  }
287
- const relativePath = path2.normalize(rawFilePath);
366
+ const relativePath = path4.normalize(rawFilePath);
288
367
  if (relativePath.startsWith("..")) {
289
368
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
290
369
  return;
@@ -295,14 +374,14 @@ async function handleRepositoryFile(req, res, ctx) {
295
374
  let realFile;
296
375
  try {
297
376
  [realGitCwd, realFile] = await Promise.all([
298
- fs.realpath(gitCwd),
299
- fs.realpath(resolvedFilePath)
377
+ fs2.realpath(gitCwd),
378
+ fs2.realpath(resolvedFilePath)
300
379
  ]);
301
380
  } catch {
302
381
  ctx.host.http.writeJson(res, 404, { error: "file_not_found" });
303
382
  return;
304
383
  }
305
- if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path2.sep)) {
384
+ if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path4.sep)) {
306
385
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
307
386
  return;
308
387
  }
@@ -315,10 +394,10 @@ async function handleRepositoryFile(req, res, ctx) {
315
394
  }
316
395
  try {
317
396
  const [realGitCwd, realFile] = await Promise.all([
318
- fs.realpath(gitCwd),
319
- fs.realpath(resolvedFilePath)
397
+ fs2.realpath(gitCwd),
398
+ fs2.realpath(resolvedFilePath)
320
399
  ]);
321
- if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path2.sep)) {
400
+ if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path4.sep)) {
322
401
  ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
323
402
  return;
324
403
  }
@@ -376,7 +455,7 @@ async function handleRepositoryCommit(req, res, ctx) {
376
455
  return;
377
456
  }
378
457
  const body = await ctx.host.http.readJsonBody(req);
379
- if (!isPlainObject2(body)) {
458
+ if (!isPlainObject2(body) || "subPath" in body) {
380
459
  ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
381
460
  return;
382
461
  }
@@ -395,11 +474,15 @@ async function handleRepositoryCommit(req, res, ctx) {
395
474
  ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
396
475
  return;
397
476
  }
398
- const rawSubPath = typeof body.subPath === "string" ? body.subPath : "";
399
- const cwdResult = await resolveGitCwd(theaterPath, rawSubPath);
400
- if (!cwdResult) {
401
- ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
402
- return;
477
+ let cwdResult;
478
+ try {
479
+ cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
480
+ } catch (error) {
481
+ if (error instanceof InvalidRepoError) {
482
+ ctx.host.http.writeJson(res, 400, { error: error.code });
483
+ return;
484
+ }
485
+ throw error;
403
486
  }
404
487
  const { gitCwd } = cwdResult;
405
488
  try {
@@ -434,9 +517,9 @@ function isPlainObject3(value) {
434
517
  return typeof value === "object" && value !== null && !Array.isArray(value);
435
518
  }
436
519
  function resolveLiteralFilePath(gitCwd, rawPath) {
437
- const relativePath = path2.normalize(rawPath);
438
- const resolved = path2.resolve(gitCwd, relativePath);
439
- if (relativePath === ".." || relativePath.startsWith(".." + path2.sep) || !resolved.startsWith(gitCwd + path2.sep)) return null;
520
+ const relativePath = path4.normalize(rawPath);
521
+ const resolved = path4.resolve(gitCwd, relativePath);
522
+ if (relativePath === ".." || relativePath.startsWith(".." + path4.sep) || !resolved.startsWith(gitCwd + path4.sep)) return null;
440
523
  return relativePath;
441
524
  }
442
525
  function literalPathspec2(relativePath) {
@@ -452,7 +535,7 @@ async function handleRepositoryCommitFile(req, res, ctx) {
452
535
  return;
453
536
  }
454
537
  const body = await ctx.host.http.readJsonBody(req);
455
- if (!isPlainObject3(body) || typeof body.theaterId !== "string" || typeof body.ref !== "string" || typeof body.filePath !== "string") {
538
+ if (!isPlainObject3(body) || "subPath" in body || typeof body.theaterId !== "string" || typeof body.ref !== "string" || typeof body.filePath !== "string") {
456
539
  ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
457
540
  return;
458
541
  }
@@ -473,10 +556,15 @@ async function handleRepositoryCommitFile(req, res, ctx) {
473
556
  ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
474
557
  return;
475
558
  }
476
- const cwdResult = await resolveGitCwd(theaterPath, typeof body.subPath === "string" ? body.subPath : "");
477
- if (!cwdResult) {
478
- ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
479
- return;
559
+ let cwdResult;
560
+ try {
561
+ cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
562
+ } catch (error) {
563
+ if (error instanceof InvalidRepoError) {
564
+ ctx.host.http.writeJson(res, 400, { error: error.code });
565
+ return;
566
+ }
567
+ throw error;
480
568
  }
481
569
  const relativePath = resolveLiteralFilePath(cwdResult.gitCwd, body.filePath);
482
570
  const oldPath = typeof body.oldPath === "string" ? resolveLiteralFilePath(cwdResult.gitCwd, body.oldPath) : void 0;
@@ -542,7 +630,7 @@ function parseLogOutput(stdout) {
542
630
  }
543
631
  return commits;
544
632
  }
545
- async function parseWorktreePorcelain(stdout, currentWorktreePath) {
633
+ function parseWorktreePorcelainEntries(stdout) {
546
634
  const worktrees = [];
547
635
  let worktreePath = null;
548
636
  let sha = "";
@@ -573,10 +661,14 @@ async function parseWorktreePorcelain(stdout, currentWorktreePath) {
573
661
  }
574
662
  }
575
663
  pushCurrent();
664
+ return worktrees;
665
+ }
666
+ async function parseWorktreePorcelain(stdout, currentWorktreePath) {
667
+ const worktrees = parseWorktreePorcelainEntries(stdout);
576
668
  const normalizedCurrentWorktreePath = await normalizeWorktreePath(currentWorktreePath);
577
- return Promise.all(worktrees.map(async ({ worktreePath: worktreePath2, ...checkout }) => ({
669
+ return Promise.all(worktrees.map(async ({ worktreePath, ...checkout }) => ({
578
670
  ...checkout,
579
- isCurrent: normalizedCurrentWorktreePath !== "" && await normalizeWorktreePath(worktreePath2) === normalizedCurrentWorktreePath
671
+ isCurrent: normalizedCurrentWorktreePath !== "" && await normalizeWorktreePath(worktreePath) === normalizedCurrentWorktreePath
580
672
  })));
581
673
  }
582
674
  function annotateHeadReachability(commits, revListStdout) {
@@ -592,7 +684,7 @@ function isNoHeadError2(error) {
592
684
  async function normalizeWorktreePath(worktreePath) {
593
685
  if (!worktreePath) return "";
594
686
  try {
595
- return await fs.realpath(worktreePath);
687
+ return await fs2.realpath(worktreePath);
596
688
  } catch {
597
689
  return worktreePath;
598
690
  }
@@ -623,7 +715,7 @@ async function handleRepositoryLog(req, res, ctx) {
623
715
  return;
624
716
  }
625
717
  const body = await ctx.host.http.readJsonBody(req);
626
- if (!isPlainObject4(body)) {
718
+ if (!isPlainObject4(body) || "subPath" in body) {
627
719
  ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
628
720
  return;
629
721
  }
@@ -637,11 +729,15 @@ async function handleRepositoryLog(req, res, ctx) {
637
729
  ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
638
730
  return;
639
731
  }
640
- const rawSubPath = typeof body.subPath === "string" ? body.subPath : "";
641
- const cwdResult = await resolveGitCwd(theaterPath, rawSubPath);
642
- if (!cwdResult) {
643
- ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
644
- return;
732
+ let cwdResult;
733
+ try {
734
+ cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
735
+ } catch (error) {
736
+ if (error instanceof InvalidRepoError) {
737
+ ctx.host.http.writeJson(res, 400, { error: error.code });
738
+ return;
739
+ }
740
+ throw error;
645
741
  }
646
742
  const { gitCwd } = cwdResult;
647
743
  const requestedRef = body.ref;
@@ -690,6 +786,8 @@ async function handleRepositoryLog(req, res, ctx) {
690
786
  throw error;
691
787
  }
692
788
  }
789
+
790
+ // ../fleet-plugins/repository/server/refs.ts
693
791
  function isObject(value) {
694
792
  return typeof value === "object" && value !== null && !Array.isArray(value);
695
793
  }
@@ -702,14 +800,6 @@ function parseRefItems(stdout, current) {
702
800
  return ref && label ? [{ ref, label, current: ref === current }] : [];
703
801
  });
704
802
  }
705
- async function readCurrentWorktreePath2(gitCwd) {
706
- try {
707
- return (await runGit(["rev-parse", "--show-toplevel"], { cwd: gitCwd })).stdout.trim();
708
- } catch (error) {
709
- if (error instanceof GitExecutorError) return "";
710
- throw error;
711
- }
712
- }
713
803
  async function readStashes(gitCwd) {
714
804
  try {
715
805
  return (await runGit(["stash", "list", "--format=%gd%x00%s"], { cwd: gitCwd })).stdout;
@@ -718,28 +808,6 @@ async function readStashes(gitCwd) {
718
808
  throw error;
719
809
  }
720
810
  }
721
- async function parseWorktrees(stdout, currentWorktreePath, theaterPath, deps = {}) {
722
- const records = stdout.split("\n\n").map((record) => record.split("\n"));
723
- const realpath = deps.realpath ?? fs.realpath;
724
- const normalizedCurrent = currentWorktreePath ? await realpath(currentWorktreePath).catch(() => currentWorktreePath) : "";
725
- const realTheaterPath = await realpath(theaterPath).catch(() => null);
726
- return Promise.all(records.flatMap((record) => {
727
- const rawPath = record.find((line) => line.startsWith("worktree "))?.slice(9);
728
- if (!rawPath) return [];
729
- const branchRef = record.find((line) => line.startsWith("branch "))?.slice(7) ?? null;
730
- const branch = branchRef?.startsWith("refs/heads/") ? branchRef.slice(11) : null;
731
- return [{ rawPath, name: path2.basename(rawPath) || "worktree", branch }];
732
- }).map(async ({ rawPath, ...item }) => {
733
- const realWorktreePath = await realpath(rawPath).catch(() => null);
734
- const currentPath = realWorktreePath ?? rawPath;
735
- const contextRelPath = realTheaterPath !== null && realWorktreePath !== null && isWithinTheater(realWorktreePath, realTheaterPath) ? path2.relative(realTheaterPath, realWorktreePath).split(path2.sep).join("/") : null;
736
- return { ...item, current: normalizedCurrent !== "" && currentPath === normalizedCurrent, contextRelPath };
737
- }));
738
- }
739
- function isWithinTheater(candidate, theaterPath) {
740
- const normalizedTheaterPath = theaterPath.endsWith(path2.sep) ? theaterPath : `${theaterPath}${path2.sep}`;
741
- return candidate === theaterPath || candidate.startsWith(normalizedTheaterPath);
742
- }
743
811
  async function handleRepositoryRefs(req, res, ctx) {
744
812
  if (req.method !== "POST") {
745
813
  ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
@@ -750,7 +818,7 @@ async function handleRepositoryRefs(req, res, ctx) {
750
818
  return;
751
819
  }
752
820
  const body = await ctx.host.http.readJsonBody(req);
753
- if (!isObject(body) || typeof body.theaterId !== "string") {
821
+ if (!isObject(body) || "subPath" in body || typeof body.theaterId !== "string") {
754
822
  ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
755
823
  return;
756
824
  }
@@ -759,22 +827,25 @@ async function handleRepositoryRefs(req, res, ctx) {
759
827
  ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
760
828
  return;
761
829
  }
762
- const resolved = await resolveGitCwd(theaterPath, typeof body.subPath === "string" ? body.subPath : "");
763
- if (!resolved) {
764
- ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
765
- return;
830
+ let resolved;
831
+ try {
832
+ resolved = await resolveGitCwd(theaterPath, body.repoRel);
833
+ } catch (error) {
834
+ if (error instanceof InvalidRepoError) {
835
+ ctx.host.http.writeJson(res, 400, { error: error.code });
836
+ return;
837
+ }
838
+ throw error;
766
839
  }
767
840
  try {
768
- const [head, local, remote, tags, stashes, worktrees] = await Promise.all([
841
+ const [head, local, remote, tags, stashes] = await Promise.all([
769
842
  runGit(["symbolic-ref", "--quiet", "HEAD"], { cwd: resolved.gitCwd, allowExitCodes: [1] }),
770
843
  runGit(["for-each-ref", "--format=%(refname)%00%(refname:short)", "refs/heads"], { cwd: resolved.gitCwd }),
771
844
  runGit(["for-each-ref", "--format=%(refname)%00%(refname:short)", "refs/remotes"], { cwd: resolved.gitCwd }),
772
845
  runGit(["for-each-ref", "--format=%(refname)%00%(refname:short)", "refs/tags"], { cwd: resolved.gitCwd }),
773
- readStashes(resolved.gitCwd),
774
- runGit(["worktree", "list", "--porcelain"], { cwd: resolved.gitCwd })
846
+ readStashes(resolved.gitCwd)
775
847
  ]);
776
848
  const current = head.stdout.trim();
777
- const currentWorktreePath = await readCurrentWorktreePath2(resolved.gitCwd);
778
849
  ctx.host.http.writeJson(res, 200, {
779
850
  branches: parseRefItems(local.stdout, current),
780
851
  remotes: parseRefItems(remote.stdout, current),
@@ -782,12 +853,11 @@ async function handleRepositoryRefs(req, res, ctx) {
782
853
  stashes: lines(stashes).map((line) => {
783
854
  const [name, subject = ""] = line.split("\0");
784
855
  return { name, subject };
785
- }),
786
- worktrees: await parseWorktrees(worktrees.stdout, currentWorktreePath, theaterPath)
856
+ })
787
857
  });
788
858
  } catch (error) {
789
859
  if (error instanceof GitExecutorError && error.code === "no_git_repo") {
790
- ctx.host.http.writeJson(res, 200, { branches: [], remotes: [], tags: [], stashes: [], worktrees: [] });
860
+ ctx.host.http.writeJson(res, 200, { branches: [], remotes: [], tags: [], stashes: [] });
791
861
  return;
792
862
  }
793
863
  if (error instanceof GitExecutorError && error.code === "git_unavailable") {
@@ -797,11 +867,240 @@ async function handleRepositoryRefs(req, res, ctx) {
797
867
  ctx.host.http.writeJson(res, 500, { error: "git_failed" });
798
868
  }
799
869
  }
870
+ var REPOS_CAP = 200;
871
+ var HARD_CAP_DEPTH = 8;
872
+ var NESTED_BRANCH_CAP = 64;
873
+ var SCAN_VISIT_BUDGET = 1e4;
874
+ var DEFAULT_DEPTH = 3;
875
+ function isPlainObject5(value) {
876
+ return typeof value === "object" && value !== null && !Array.isArray(value);
877
+ }
878
+ async function resolveRepoBranch(repoDir) {
879
+ try {
880
+ const result = await runGit(["status", "--porcelain=v2", "--branch", "--untracked-files=no"], { cwd: repoDir });
881
+ const branch = result.stdout.split("\n").find((line) => line.startsWith("# branch.head "))?.slice(14).trim() ?? "";
882
+ const oid = result.stdout.split("\n").find((line) => line.startsWith("# branch.oid "))?.slice(13).trim() ?? "";
883
+ return branch === "(detached)" ? oid.slice(0, 7) : branch;
884
+ } catch {
885
+ return "";
886
+ }
887
+ }
888
+ async function scanRepos(theaterPath, realTheaterPath, dir, currentDepth, maxDepth, repos, cap = REPOS_CAP, visits = { remaining: SCAN_VISIT_BUDGET }) {
889
+ if (repos.length >= cap) return true;
890
+ if (visits.remaining <= 0) return true;
891
+ visits.remaining -= 1;
892
+ const relPath = path4.relative(realTheaterPath, dir);
893
+ const gitDir = isSelectableRepoRel(relPath) ? await resolveContainedGitDir(dir, realTheaterPath) : null;
894
+ let linkedWorktree = false;
895
+ if (gitDir !== null) {
896
+ try {
897
+ await fs2.access(path4.join(gitDir, "commondir"));
898
+ linkedWorktree = true;
899
+ } catch {
900
+ }
901
+ }
902
+ if (gitDir !== null && !linkedWorktree) {
903
+ repos.push({ relPath, name: relPath === "" ? path4.basename(realTheaterPath) : path4.basename(dir), repoDir: dir });
904
+ if (repos.length >= cap) return true;
905
+ }
906
+ if (currentDepth >= maxDepth) return false;
907
+ let dirHandle = null;
908
+ try {
909
+ dirHandle = await fs2.opendir(dir);
910
+ let dirent = await dirHandle.read();
911
+ while (dirent !== null) {
912
+ if (repos.length >= cap || visits.remaining <= 0) return true;
913
+ if (dirent.isDirectory() && dirent.name !== "node_modules" && dirent.name !== ".git") {
914
+ const childPath = path4.join(dir, dirent.name);
915
+ let realChildPath;
916
+ try {
917
+ realChildPath = await fs2.realpath(childPath);
918
+ } catch {
919
+ dirent = await dirHandle.read();
920
+ continue;
921
+ }
922
+ if (isPathContained(realTheaterPath, realChildPath)) {
923
+ const truncated = await scanRepos(realTheaterPath, realTheaterPath, realChildPath, currentDepth + 1, maxDepth, repos, cap, visits);
924
+ if (truncated) return true;
925
+ }
926
+ }
927
+ dirent = await dirHandle.read();
928
+ }
929
+ return false;
930
+ } catch {
931
+ return false;
932
+ } finally {
933
+ if (dirHandle) {
934
+ try {
935
+ await dirHandle.close();
936
+ } catch {
937
+ }
938
+ }
939
+ }
940
+ }
941
+ async function resolveNestedRepoCandidates(candidates, availableSlots, resolver = resolveRepoBranch) {
942
+ const resolved = [];
943
+ const selected = candidates.slice(0, Math.max(0, availableSlots));
944
+ for (const [index, candidate] of selected.entries()) {
945
+ resolved.push({
946
+ relPath: candidate.relPath,
947
+ name: candidate.name,
948
+ branch: index < NESTED_BRANCH_CAP ? await resolver(candidate.repoDir) : "",
949
+ kind: "nested"
950
+ });
951
+ }
952
+ return resolved;
953
+ }
954
+ async function handleRepositoryRepos(req, res, ctx) {
955
+ if (req.method !== "POST") {
956
+ ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
957
+ return;
958
+ }
959
+ if (!ctx.host.security.isTerminalAuthorized(req)) {
960
+ ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
961
+ return;
962
+ }
963
+ const body = await ctx.host.http.readJsonBody(req);
964
+ if (!isPlainObject5(body) || typeof body.theaterId !== "string") {
965
+ ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
966
+ return;
967
+ }
968
+ const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
969
+ if (!theaterPath) {
970
+ ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
971
+ return;
972
+ }
973
+ let realTheaterPath;
974
+ try {
975
+ realTheaterPath = await fs2.realpath(theaterPath);
976
+ } catch {
977
+ ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
978
+ return;
979
+ }
980
+ const maxDepth = typeof body.maxDepth === "number" && Number.isFinite(body.maxDepth) ? Math.max(1, Math.min(HARD_CAP_DEPTH, Math.floor(body.maxDepth))) : DEFAULT_DEPTH;
981
+ const repos = [];
982
+ const seen = /* @__PURE__ */ new Set();
983
+ let truncated = false;
984
+ let rootIsRepo = await resolveContainedGitDir(realTheaterPath, realTheaterPath) !== null;
985
+ if (!rootIsRepo) {
986
+ try {
987
+ const inside = await runGit(["rev-parse", "--is-inside-work-tree"], { cwd: theaterPath });
988
+ rootIsRepo = inside.stdout.trim() === "true";
989
+ } catch {
990
+ }
991
+ }
992
+ if (rootIsRepo) {
993
+ repos.push({ relPath: "", name: path4.basename(theaterPath), branch: await resolveRepoBranch(theaterPath), kind: "root" });
994
+ seen.add("");
995
+ }
996
+ const scanned = [];
997
+ const scanCap = Math.max(0, REPOS_CAP - repos.length);
998
+ truncated = await scanRepos(realTheaterPath, realTheaterPath, realTheaterPath, 0, maxDepth, scanned, scanCap) || truncated;
999
+ const unseenNested = scanned.filter((candidate) => !seen.has(candidate.relPath));
1000
+ const remainingSlots = Math.max(0, REPOS_CAP - repos.length);
1001
+ if (unseenNested.length > remainingSlots) truncated = true;
1002
+ const nestedCandidates = await resolveNestedRepoCandidates(unseenNested, remainingSlots);
1003
+ for (const candidate of nestedCandidates) {
1004
+ if (repos.length >= REPOS_CAP) {
1005
+ truncated = true;
1006
+ break;
1007
+ }
1008
+ repos.push(candidate);
1009
+ seen.add(candidate.relPath);
1010
+ }
1011
+ const result = { repos, ...truncated ? { truncated: true } : {} };
1012
+ ctx.host.http.writeJson(res, 200, result);
1013
+ }
1014
+ function isPlainObject6(value) {
1015
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1016
+ }
1017
+ async function handleRepositoryWorktrees(req, res, ctx) {
1018
+ if (req.method !== "POST") {
1019
+ ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
1020
+ return;
1021
+ }
1022
+ if (!ctx.host.security.isTerminalAuthorized(req)) {
1023
+ ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
1024
+ return;
1025
+ }
1026
+ const body = await ctx.host.http.readJsonBody(req);
1027
+ if (!isPlainObject6(body) || "subPath" in body || typeof body.theaterId !== "string") {
1028
+ ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
1029
+ return;
1030
+ }
1031
+ const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
1032
+ if (!theaterPath) {
1033
+ ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
1034
+ return;
1035
+ }
1036
+ let gitCwd;
1037
+ try {
1038
+ ({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
1039
+ } catch (error) {
1040
+ if (error instanceof InvalidRepoError) {
1041
+ ctx.host.http.writeJson(res, 400, { error: error.code });
1042
+ return;
1043
+ }
1044
+ throw error;
1045
+ }
1046
+ let realTheaterPath;
1047
+ let realGitCwd;
1048
+ try {
1049
+ [realTheaterPath, realGitCwd] = await Promise.all([fs2.realpath(theaterPath), fs2.realpath(gitCwd)]);
1050
+ } catch {
1051
+ ctx.host.http.writeJson(res, 400, { error: "invalid_repo" });
1052
+ return;
1053
+ }
1054
+ try {
1055
+ const result = await runGit(["worktree", "list", "--porcelain"], { cwd: gitCwd });
1056
+ const worktrees = [];
1057
+ const seen = /* @__PURE__ */ new Set();
1058
+ for (const worktree of parseWorktreePorcelainEntries(result.stdout)) {
1059
+ let realWorktree;
1060
+ try {
1061
+ realWorktree = await fs2.realpath(worktree.worktreePath);
1062
+ } catch {
1063
+ continue;
1064
+ }
1065
+ if (!isPathContained(realTheaterPath, realWorktree)) continue;
1066
+ const relPath = path4.relative(realTheaterPath, realWorktree);
1067
+ if (!isSelectableRepoRel(relPath) || seen.has(relPath)) continue;
1068
+ if (await resolveContainedGitDir(realWorktree, realTheaterPath) === null) continue;
1069
+ worktrees.push({
1070
+ relPath,
1071
+ name: path4.basename(realWorktree),
1072
+ branch: worktree.branch ?? worktree.sha.slice(0, 7),
1073
+ current: realWorktree === realGitCwd
1074
+ });
1075
+ seen.add(relPath);
1076
+ }
1077
+ const payload = { worktrees };
1078
+ ctx.host.http.writeJson(res, 200, payload);
1079
+ } catch (error) {
1080
+ if (error instanceof GitExecutorError) {
1081
+ if (error.code === "no_git_repo" || error.code === "git_unavailable") {
1082
+ ctx.host.http.writeJson(res, 422, { error: error.code });
1083
+ return;
1084
+ }
1085
+ ctx.host.http.writeJson(res, 500, { error: "git_failed" });
1086
+ return;
1087
+ }
1088
+ throw error;
1089
+ }
1090
+ }
800
1091
 
801
1092
  // ../fleet-plugins/repository/routes.ts
802
1093
  var routes_default = definePlugin({
803
1094
  id: "repository",
804
1095
  register(ctx) {
1096
+ registerRouter(ctx, "repos", async ({ req, res }) => {
1097
+ await handleRepositoryRepos(req, res, ctx);
1098
+ return true;
1099
+ });
1100
+ registerRouter(ctx, "worktrees", async ({ req, res }) => {
1101
+ await handleRepositoryWorktrees(req, res, ctx);
1102
+ return true;
1103
+ });
805
1104
  registerRouter(ctx, "changed", async ({ req, res }) => {
806
1105
  await handleRepositoryChanged(req, res, ctx);
807
1106
  return true;