@dotobokuri/fleet-console 1.59.0 → 1.60.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.
- package/AGENTS.md +1 -1
- package/dist/cli.mjs +12 -2
- package/dist/client/assets/{_baseUniq-CKkDoWU8.js → _baseUniq-CA3I-KTP.js} +1 -1
- package/dist/client/assets/{arc-CcRdX8oF.js → arc-DOkIWugv.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-gaFzE1W7.js → architectureDiagram-Q4EWVU46--Z_t3Gn9.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-Bzybih30.js → blockDiagram-DXYQGD6D-CrRA5qvl.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-Dz8bwV9j.js → c4Diagram-AHTNJAMY-DKvbMOAM.js} +1 -1
- package/dist/client/assets/channel-Bjfd2afp.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-DmgZtGyV.js → chunk-4BX2VUAB-D6wc2KrB.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-DX1WxV3U.js → chunk-4TB4RGXK-B4QvYh7f.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-Bk4b3T9u.js → chunk-55IACEB6-B0VZPoad.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-BzGadfvT.js → chunk-EDXVE4YY-BdHIgvVh.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-44f7YyvW.js → chunk-FMBD7UC4-BAYTd_pU.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-CyUAky9W.js → chunk-OYMX7WX6-DkZ-neug.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-C11OUntR.js → chunk-QZHKN3VN-B5h7E8GE.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-DVTmWawU.js → chunk-YZCP3GAM-COMJ2EwJ.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DgpHvFOU.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DgpHvFOU.js +1 -0
- package/dist/client/assets/clone-sFXqwEZz.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CEatGsZv.js → cose-bilkent-S5V4N54A-JvC7pCqe.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-DlrgN02H.js → dagre-KV5264BT-jZdVPQcJ.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-BZ3hRO5t.js → diagram-5BDNPKRD-B6-u7AU2.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-B-izS46x.js → diagram-G4DWMVQ6-CV5G3KNM.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-Cw-wnJGs.js → diagram-MMDJMWI5-ZLrEkNww.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-BoWnyd4_.js → diagram-TYMM5635-D1tv2JX9.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-BUmyXZbP.js → erDiagram-SMLLAGMA-Bta0Mt3t.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-COS3kjS0.js → flowDiagram-DWJPFMVM-C282oRLT.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-BTr1fdio.js → ganttDiagram-T4ZO3ILL-D9P4CVIW.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-BkGkaosN.js → gitGraphDiagram-UUTBAWPF-DLQnTjJ2.js} +1 -1
- package/dist/client/assets/{graph-DmbgK-mH.js → graph-Cd8Q-8WG.js} +1 -1
- package/dist/client/assets/index-B3gusU1E.js +465 -0
- package/dist/client/assets/index-_PHObHQ-.css +1 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-B8zI6k1l.js → infoDiagram-42DDH7IO-ClhLpons.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-cIcEzrcK.js → ishikawaDiagram-UXIWVN3A-BDeZ2vOG.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-DhuabHCk.js → journeyDiagram-VCZTEJTY-DbJhbFKN.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-uHTkqxhX.js → kanban-definition-6JOO6SKY-CYKcE2fE.js} +1 -1
- package/dist/client/assets/{layout-Cfgk_duA.js → layout-CuAgTRkn.js} +1 -1
- package/dist/client/assets/{linear-BKvV0sr5.js → linear-3EL9MakS.js} +1 -1
- package/dist/client/assets/{mermaid.core-DozmrCuG.js → mermaid.core-7qKaxHEA.js} +4 -4
- package/dist/client/assets/{min-ke46otlW.js → min-4mJsdKjT.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-d7xzxhzf.js → mindmap-definition-QFDTVHPH-B-Zve2OH.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-C4S9Jy5A.js → pieDiagram-DEJITSTG-DlsdbAii.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-CoQnj3kv.js → quadrantDiagram-34T5L4WZ-Cb4h0xwt.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-CdopMDtg.js → requirementDiagram-MS252O5E-CZNGYqJt.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-DdR4zdh4.js → sankeyDiagram-XADWPNL6-DeVSGR6W.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-NeYZWVQm.js → sequenceDiagram-FGHM5R23-Te7TkLIe.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-D0CQGKV_.js → stateDiagram-FHFEXIEX-Be-0oYyv.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BnQ2lBhr.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-Bz2b3nfQ.js → timeline-definition-GMOUNBTQ-DQyUgC-K.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-CVO2Ja80.js → vennDiagram-DHZGUBPP-BrArIWI8.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-Ba_GKs5u.js → wardley-RL74JXVD-6-sWm8kk.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-yP3xvrPy.js → wardleyDiagram-NUSXRM2D-C4MvFXGS.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-BLSs6w4C.js → xychartDiagram-5P7HB3ND-CGrtjhmr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/fleet-plugins/file-explorer/routes.mjs +132 -13
- package/dist/fleet-plugins/ledger/routes.mjs +223 -16
- package/dist/fleet-plugins/quota/routes.mjs +33 -2
- package/dist/fleet-plugins/scuttlebutt/routes.mjs +5 -5
- package/dist/fleet-plugins/terminal/routes.mjs +411 -61
- package/dist/fleet.mjs +274 -43
- package/package.json +1 -1
- package/dist/client/assets/channel-CAhLkt5l.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-BSvLoE2t.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-BSvLoE2t.js +0 -1
- package/dist/client/assets/clone-o26VS_sk.js +0 -1
- package/dist/client/assets/index-C-3spb_b.js +0 -465
- package/dist/client/assets/index-FU1PGEfG.css +0 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-hM1GV37r.js +0 -1
|
@@ -567,6 +567,7 @@ function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs2.watch(p, op
|
|
|
567
567
|
try {
|
|
568
568
|
startWatcher(theaterId, entry, relativePath, target.path, target.identity);
|
|
569
569
|
entry.pendingDirectories.delete(relativePath);
|
|
570
|
+
scheduleChange(entry, relativePath);
|
|
570
571
|
} catch {
|
|
571
572
|
for (const notify of entry.stateSubscribers) notify("degraded");
|
|
572
573
|
}
|
|
@@ -614,6 +615,8 @@ var FolderBrowserError = class extends Error {
|
|
|
614
615
|
}
|
|
615
616
|
};
|
|
616
617
|
var DIRECTORY_ENTRY_CAP = 500;
|
|
618
|
+
var CLASSIFY_CONCURRENCY = 16;
|
|
619
|
+
var VCS_INTERNAL_NAMES = /* @__PURE__ */ new Set([".git", ".svn", ".hg"]);
|
|
617
620
|
async function listTheaterContents(theaterPath, relativePath, deps = {}) {
|
|
618
621
|
const opendir = deps.opendir ?? fs2.promises.opendir;
|
|
619
622
|
const stat = deps.stat ?? fs2.promises.stat;
|
|
@@ -636,10 +639,12 @@ async function listTheaterContents(theaterPath, relativePath, deps = {}) {
|
|
|
636
639
|
if (realTargetAbs !== realRoot && !realTargetAbs.startsWith(realNormalizedRoot)) {
|
|
637
640
|
throw new FolderBrowserError("forbidden");
|
|
638
641
|
}
|
|
642
|
+
if (vcsSegmentOf(realRoot, realTargetAbs)) throw new FolderBrowserError("forbidden");
|
|
639
643
|
const targetStat = await statDirectory(realTargetAbs, stat);
|
|
640
644
|
if (!targetStat.isDirectory()) throw new FolderBrowserError("invalid_path");
|
|
641
645
|
const entries = [];
|
|
642
|
-
const
|
|
646
|
+
const hiddenVcs = [];
|
|
647
|
+
const truncated = await collectContentsEntries(realTargetAbs, realRoot, opendir, stat, entries, hiddenVcs);
|
|
643
648
|
entries.sort((a, b) => {
|
|
644
649
|
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
|
|
645
650
|
return a.name.localeCompare(b.name);
|
|
@@ -650,7 +655,8 @@ async function listTheaterContents(theaterPath, relativePath, deps = {}) {
|
|
|
650
655
|
relativePath: rel,
|
|
651
656
|
parentRelativePath: parentRel === "" ? null : parentRel ?? null,
|
|
652
657
|
entries,
|
|
653
|
-
...truncated ? { truncated: true } : {}
|
|
658
|
+
...truncated ? { truncated: true, cap: DIRECTORY_ENTRY_CAP } : {},
|
|
659
|
+
...hiddenVcs.length > 0 ? { hiddenVcsInternals: [...new Set(hiddenVcs)].sort() } : {}
|
|
654
660
|
};
|
|
655
661
|
}
|
|
656
662
|
async function statDirectory(targetPath, stat) {
|
|
@@ -660,17 +666,100 @@ async function statDirectory(targetPath, stat) {
|
|
|
660
666
|
throw mapFolderBrowserFsError(error);
|
|
661
667
|
}
|
|
662
668
|
}
|
|
663
|
-
async function collectContentsEntries(targetPath, theaterPath, opendir, stat, entries) {
|
|
669
|
+
async function collectContentsEntries(targetPath, theaterPath, opendir, stat, entries, hiddenVcs) {
|
|
664
670
|
const directory = await openDirectory(targetPath, opendir);
|
|
665
671
|
try {
|
|
672
|
+
const pending = [];
|
|
673
|
+
let truncated = false;
|
|
674
|
+
const accept = (entry) => {
|
|
675
|
+
if (entry === null) return false;
|
|
676
|
+
if ("vcsInternal" in entry) {
|
|
677
|
+
hiddenVcs.push(entry.vcsInternal);
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
if (entries.length >= DIRECTORY_ENTRY_CAP) return true;
|
|
681
|
+
entries.push(entry);
|
|
682
|
+
return false;
|
|
683
|
+
};
|
|
684
|
+
const flushPending = async () => {
|
|
685
|
+
while (pending.length > 0) {
|
|
686
|
+
if (entries.length >= DIRECTORY_ENTRY_CAP) {
|
|
687
|
+
pending.length = 0;
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
const remaining = DIRECTORY_ENTRY_CAP - entries.length;
|
|
691
|
+
const batch = pending.splice(0, Math.min(CLASSIFY_CONCURRENCY, remaining));
|
|
692
|
+
const classified = await Promise.all(
|
|
693
|
+
batch.map((item) => toContentsEntry(targetPath, theaterPath, item, stat))
|
|
694
|
+
);
|
|
695
|
+
let overflowed = false;
|
|
696
|
+
for (const entry of classified) {
|
|
697
|
+
if (overflowed) {
|
|
698
|
+
if (entry && "vcsInternal" in entry) hiddenVcs.push(entry.vcsInternal);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (accept(entry)) overflowed = true;
|
|
702
|
+
}
|
|
703
|
+
if (overflowed) {
|
|
704
|
+
pending.length = 0;
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return false;
|
|
709
|
+
};
|
|
710
|
+
const finishCheapTail = async (first) => {
|
|
711
|
+
pending.length = 0;
|
|
712
|
+
let current = first;
|
|
713
|
+
let omitted = false;
|
|
714
|
+
while (current !== null) {
|
|
715
|
+
if (VCS_INTERNAL_NAMES.has(current.name)) hiddenVcs.push(current.name);
|
|
716
|
+
else {
|
|
717
|
+
omitted = true;
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
current = await directory.read();
|
|
721
|
+
}
|
|
722
|
+
return omitted;
|
|
723
|
+
};
|
|
666
724
|
let dirent = await directory.read();
|
|
667
725
|
while (dirent !== null) {
|
|
668
|
-
if (
|
|
669
|
-
|
|
670
|
-
|
|
726
|
+
if (VCS_INTERNAL_NAMES.has(dirent.name)) {
|
|
727
|
+
hiddenVcs.push(dirent.name);
|
|
728
|
+
dirent = await directory.read();
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (entries.length >= DIRECTORY_ENTRY_CAP) {
|
|
732
|
+
truncated = await finishCheapTail(dirent);
|
|
733
|
+
dirent = null;
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
if (dirent.isDirectory() || dirent.isFile()) {
|
|
737
|
+
if (pending.length > 0 && await flushPending()) {
|
|
738
|
+
truncated = true;
|
|
739
|
+
await finishCheapTail(dirent);
|
|
740
|
+
dirent = null;
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
const rel = path5.relative(theaterPath, path5.join(targetPath, dirent.name));
|
|
744
|
+
if (accept({ name: dirent.name, relativePath: rel, kind: dirent.isDirectory() ? "dir" : "file" })) {
|
|
745
|
+
truncated = true;
|
|
746
|
+
await finishCheapTail(await directory.read());
|
|
747
|
+
dirent = null;
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
} else {
|
|
751
|
+
pending.push(dirent);
|
|
752
|
+
if (pending.length >= CLASSIFY_CONCURRENCY && await flushPending()) {
|
|
753
|
+
truncated = true;
|
|
754
|
+
await finishCheapTail(await directory.read());
|
|
755
|
+
dirent = null;
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
671
759
|
dirent = await directory.read();
|
|
672
760
|
}
|
|
673
|
-
|
|
761
|
+
if (!truncated && await flushPending()) truncated = true;
|
|
762
|
+
return truncated;
|
|
674
763
|
} catch (error) {
|
|
675
764
|
throw mapFolderBrowserFsError(error);
|
|
676
765
|
} finally {
|
|
@@ -684,6 +773,14 @@ async function openDirectory(targetPath, opendir) {
|
|
|
684
773
|
throw mapFolderBrowserFsError(error);
|
|
685
774
|
}
|
|
686
775
|
}
|
|
776
|
+
function vcsSegmentOf(realRoot, realPath) {
|
|
777
|
+
const rel = path5.relative(realRoot, realPath);
|
|
778
|
+
if (!rel) return null;
|
|
779
|
+
for (const segment of rel.split(path5.sep)) {
|
|
780
|
+
if (VCS_INTERNAL_NAMES.has(segment)) return segment;
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
687
784
|
async function toContentsEntry(targetPath, theaterPath, dirent, stat) {
|
|
688
785
|
const entryPath = path5.join(targetPath, dirent.name);
|
|
689
786
|
const rel = path5.relative(theaterPath, entryPath);
|
|
@@ -694,6 +791,8 @@ async function toContentsEntry(targetPath, theaterPath, dirent, stat) {
|
|
|
694
791
|
const realEntryPath = await fs2.promises.realpath(entryPath);
|
|
695
792
|
const realNormalizedRoot = theaterPath.endsWith(path5.sep) ? theaterPath : theaterPath + path5.sep;
|
|
696
793
|
if (realEntryPath !== theaterPath && !realEntryPath.startsWith(realNormalizedRoot)) return null;
|
|
794
|
+
const vcsSegment = vcsSegmentOf(theaterPath, realEntryPath);
|
|
795
|
+
if (vcsSegment) return { vcsInternal: vcsSegment };
|
|
697
796
|
const s = await stat(realEntryPath);
|
|
698
797
|
if (s.isDirectory()) return { name: dirent.name, relativePath: rel, kind: "dir" };
|
|
699
798
|
if (s.isFile()) return { name: dirent.name, relativePath: rel, kind: "file" };
|
|
@@ -756,7 +855,7 @@ async function readTheaterGitStatus(theaterPath, deps = {}) {
|
|
|
756
855
|
ok: true,
|
|
757
856
|
gitAvailable: true,
|
|
758
857
|
statuses: statuses.slice(0, GIT_STATUS_CAP),
|
|
759
|
-
...truncated ? { truncated: true } : {}
|
|
858
|
+
...truncated ? { truncated: true, cap: GIT_STATUS_CAP } : {}
|
|
760
859
|
};
|
|
761
860
|
} catch {
|
|
762
861
|
return { ok: true, gitAvailable: false, statuses: [] };
|
|
@@ -859,7 +958,12 @@ async function searchTheaterFiles(theaterPath, query, limit) {
|
|
|
859
958
|
const matches = [];
|
|
860
959
|
let directoryCount = 0;
|
|
861
960
|
let entryCount = 0;
|
|
862
|
-
|
|
961
|
+
let walkCapped = false;
|
|
962
|
+
while (pending.length > 0) {
|
|
963
|
+
if (directoryCount >= SEARCH_DIRECTORY_CAP || entryCount >= SEARCH_ENTRY_CAP) {
|
|
964
|
+
walkCapped = true;
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
863
967
|
const directory = pending.shift();
|
|
864
968
|
if (!directory || visited.has(directory.absolutePath)) continue;
|
|
865
969
|
visited.add(directory.absolutePath);
|
|
@@ -872,8 +976,12 @@ async function searchTheaterFiles(theaterPath, query, limit) {
|
|
|
872
976
|
}
|
|
873
977
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
874
978
|
for (const entry of entries) {
|
|
875
|
-
if (entryCount >= SEARCH_ENTRY_CAP)
|
|
979
|
+
if (entryCount >= SEARCH_ENTRY_CAP) {
|
|
980
|
+
walkCapped = true;
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
876
983
|
entryCount += 1;
|
|
984
|
+
if (VCS_INTERNAL_NAMES.has(entry.name)) continue;
|
|
877
985
|
const relativePath = directory.relativePath ? `${directory.relativePath}/${entry.name}` : entry.name;
|
|
878
986
|
const absolutePath = path5.join(directory.absolutePath, entry.name);
|
|
879
987
|
let kind = entry.isDirectory() ? "dir" : entry.isFile() ? "file" : null;
|
|
@@ -882,6 +990,7 @@ async function searchTheaterFiles(theaterPath, query, limit) {
|
|
|
882
990
|
try {
|
|
883
991
|
realPath = await fsp.realpath(absolutePath);
|
|
884
992
|
if (!isContained(realRoot, realPath)) continue;
|
|
993
|
+
if (vcsSegmentOf(realRoot, realPath)) continue;
|
|
885
994
|
const stat = await fsp.stat(realPath);
|
|
886
995
|
kind = stat.isDirectory() ? "dir" : stat.isFile() ? "file" : null;
|
|
887
996
|
} catch {
|
|
@@ -896,8 +1005,14 @@ async function searchTheaterFiles(theaterPath, query, limit) {
|
|
|
896
1005
|
const low = relativePath.toLocaleLowerCase();
|
|
897
1006
|
if (tokens.every((token) => low.includes(token))) matches.push({ relativePath });
|
|
898
1007
|
}
|
|
1008
|
+
if (walkCapped) break;
|
|
899
1009
|
}
|
|
900
|
-
|
|
1010
|
+
const sorted = matches.sort((left, right) => compareFileSearchItem(left, right, query));
|
|
1011
|
+
return {
|
|
1012
|
+
files: sorted.slice(0, limit),
|
|
1013
|
+
totalMatches: sorted.length,
|
|
1014
|
+
...walkCapped ? { walkCapped: true } : {}
|
|
1015
|
+
};
|
|
901
1016
|
}
|
|
902
1017
|
async function handleFilesSearch(req, res, ctx) {
|
|
903
1018
|
if (req.method !== "POST") {
|
|
@@ -919,8 +1034,11 @@ async function handleFilesSearch(req, res, ctx) {
|
|
|
919
1034
|
return;
|
|
920
1035
|
}
|
|
921
1036
|
try {
|
|
1037
|
+
const outcome = await searchTheaterFiles(theaterPath, body.query, body.limit);
|
|
922
1038
|
ctx.host.http.writeJson(res, 200, {
|
|
923
|
-
files:
|
|
1039
|
+
files: outcome.files,
|
|
1040
|
+
totalMatches: outcome.totalMatches,
|
|
1041
|
+
...outcome.walkCapped ? { walkCapped: true } : {}
|
|
924
1042
|
});
|
|
925
1043
|
} catch {
|
|
926
1044
|
ctx.host.http.writeJson(res, 500, { error: "search_failed" });
|
|
@@ -978,7 +1096,8 @@ async function handleFilesList(req, res, ctx) {
|
|
|
978
1096
|
}
|
|
979
1097
|
try {
|
|
980
1098
|
const result = await listTheaterContents(theaterPath, relPath);
|
|
981
|
-
|
|
1099
|
+
void watcherRegistry.trackDirectory(rawTheaterId, theaterPath, relPath).catch(() => {
|
|
1100
|
+
});
|
|
982
1101
|
ctx.host.http.writeJson(res, 200, result);
|
|
983
1102
|
} catch (error) {
|
|
984
1103
|
if (error instanceof FolderBrowserError) {
|
|
@@ -338,7 +338,7 @@ async function handleSummary(req, res, ctx, service) {
|
|
|
338
338
|
|
|
339
339
|
// ../fleet-plugins/ledger/server/parser.ts
|
|
340
340
|
var CLIENT_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
341
|
-
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,63}
|
|
341
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,63}(?:\[1[mM]\])?$/;
|
|
342
342
|
var UUID_FRAGMENT_RE = /(?:^|[^0-9a-f])[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:$|[^0-9a-f])/i;
|
|
343
343
|
function isRecord(value) {
|
|
344
344
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -398,8 +398,112 @@ function parseTokscaleOutput(stdout) {
|
|
|
398
398
|
if (parsed.length > 0 && sessions.length === 0) return { status: "unreadable", sessions: [], skippedSessions };
|
|
399
399
|
return { status: skippedSessions > 0 ? "degraded" : "ok", sessions, skippedSessions };
|
|
400
400
|
}
|
|
401
|
+
function parseModelEntry(value) {
|
|
402
|
+
if (!isRecord(value)) return null;
|
|
403
|
+
const models = sanitizeModels(typeof value.model === "string" ? [value.model] : []);
|
|
404
|
+
if (models.length !== 1 || !isNonNegativeSafeInteger(value.input) || !isNonNegativeSafeInteger(value.output) || !isNonNegativeSafeInteger(value.cacheRead) || !isNonNegativeFinite(value.cost) || !isNonNegativeSafeInteger(value.messageCount)) return null;
|
|
405
|
+
return {
|
|
406
|
+
modelId: models[0],
|
|
407
|
+
input: value.input,
|
|
408
|
+
output: value.output,
|
|
409
|
+
cacheRead: value.cacheRead,
|
|
410
|
+
costUsd: value.cost,
|
|
411
|
+
messages: value.messageCount
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function parseTokscaleModelsOutput(stdout) {
|
|
415
|
+
let parsed;
|
|
416
|
+
try {
|
|
417
|
+
parsed = JSON.parse(stdout);
|
|
418
|
+
} catch {
|
|
419
|
+
return { status: "unreadable", entries: [], skippedEntries: 0 };
|
|
420
|
+
}
|
|
421
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.entries)) {
|
|
422
|
+
return { status: "unreadable", entries: [], skippedEntries: 0 };
|
|
423
|
+
}
|
|
424
|
+
const entries = parsed.entries.map(parseModelEntry).filter((entry) => entry !== null);
|
|
425
|
+
const skippedEntries = parsed.entries.length - entries.length;
|
|
426
|
+
if (parsed.entries.length > 0 && entries.length === 0) {
|
|
427
|
+
return { status: "unreadable", entries: [], skippedEntries };
|
|
428
|
+
}
|
|
429
|
+
return { status: skippedEntries > 0 ? "degraded" : "ok", entries, skippedEntries };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ../fleet-plugins/ledger/server/identity.ts
|
|
433
|
+
var LEDGER_LAUNCH_PROVIDERS = ["claude", "codex", "cursor", "kimi", "opencode", "xai"];
|
|
434
|
+
var GATEWAY_MODEL_PREFIX = "claude-gateway--";
|
|
435
|
+
var NATIVE_SUPPLIER = "native";
|
|
436
|
+
var PROVIDER_SET = new Set(LEDGER_LAUNCH_PROVIDERS);
|
|
437
|
+
function isLaunchProvider(value) {
|
|
438
|
+
return typeof value === "string" && PROVIDER_SET.has(value);
|
|
439
|
+
}
|
|
440
|
+
function readLaunchProvider(payload) {
|
|
441
|
+
return isLaunchProvider(payload.launchProvider) ? payload.launchProvider : null;
|
|
442
|
+
}
|
|
443
|
+
function parseModelIdentity(modelId) {
|
|
444
|
+
let rest = modelId;
|
|
445
|
+
let supplier = NATIVE_SUPPLIER;
|
|
446
|
+
if (rest.startsWith(GATEWAY_MODEL_PREFIX)) {
|
|
447
|
+
rest = rest.slice(GATEWAY_MODEL_PREFIX.length);
|
|
448
|
+
const separator = rest.indexOf("--");
|
|
449
|
+
if (separator > 0) {
|
|
450
|
+
const candidate = rest.slice(0, separator);
|
|
451
|
+
if (PROVIDER_SET.has(candidate)) {
|
|
452
|
+
supplier = candidate;
|
|
453
|
+
rest = rest.slice(separator + 2);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
const inferred = inferUnprefixedSupplier(rest);
|
|
458
|
+
supplier = inferred.supplier;
|
|
459
|
+
rest = inferred.bare;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
modelId,
|
|
463
|
+
supplier,
|
|
464
|
+
bare: rest,
|
|
465
|
+
label: humanizeBareModel(rest)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function inferUnprefixedSupplier(modelId) {
|
|
469
|
+
const slash = modelId.indexOf("/");
|
|
470
|
+
if (slash > 0) {
|
|
471
|
+
const candidate = modelId.slice(0, slash).toLowerCase();
|
|
472
|
+
if (PROVIDER_SET.has(candidate)) {
|
|
473
|
+
return { supplier: candidate, bare: modelId.slice(slash + 1) };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const lower = modelId.toLowerCase();
|
|
477
|
+
if (lower.startsWith("claude")) return { supplier: "claude", bare: modelId };
|
|
478
|
+
if (lower.startsWith("gpt-")) return { supplier: "codex", bare: modelId };
|
|
479
|
+
if (lower.startsWith("grok")) return { supplier: "xai", bare: modelId };
|
|
480
|
+
if (lower.startsWith("kimi")) return { supplier: "kimi", bare: modelId };
|
|
481
|
+
return { supplier: NATIVE_SUPPLIER, bare: modelId };
|
|
482
|
+
}
|
|
483
|
+
function normalizeModelKey(modelId) {
|
|
484
|
+
return parseModelIdentity(modelId).bare.toLowerCase().replace(/\bgpt-(\d)-(\d)\b/g, "gpt-$1.$2");
|
|
485
|
+
}
|
|
486
|
+
function humanizeBareModel(bare) {
|
|
487
|
+
const bracketMillion = /\[1m\]$/i.test(bare);
|
|
488
|
+
const tokens = bare.replace(/\[1m\]$/i, "").split(/[-_]/).filter(Boolean);
|
|
489
|
+
const trailingMillion = tokens.at(-1)?.toLowerCase() === "1m";
|
|
490
|
+
const nameTokens = trailingMillion ? tokens.slice(0, -1) : tokens;
|
|
491
|
+
const words = nameTokens.map((token) => {
|
|
492
|
+
if (/^\d/.test(token)) return token;
|
|
493
|
+
const lower = token.toLowerCase();
|
|
494
|
+
if (lower === "gpt") return "GPT";
|
|
495
|
+
return `${token.charAt(0).toUpperCase()}${token.slice(1)}`;
|
|
496
|
+
});
|
|
497
|
+
const joined = words.join(" ").replace(/\bGPT (\d) (\d)\b/g, "GPT $1.$2");
|
|
498
|
+
return bracketMillion || trailingMillion ? `${joined} (1M)` : joined;
|
|
499
|
+
}
|
|
401
500
|
|
|
402
501
|
// ../fleet-plugins/ledger/server/summary.ts
|
|
502
|
+
var EMPTY_MODEL_BREAKDOWN = {
|
|
503
|
+
entries: [],
|
|
504
|
+
status: "ok",
|
|
505
|
+
skippedEntries: 0
|
|
506
|
+
};
|
|
403
507
|
var AggregateOverflowError = class extends Error {
|
|
404
508
|
};
|
|
405
509
|
var MAX_DAILY_DAYS = 366;
|
|
@@ -475,7 +579,8 @@ function readClaim(operation) {
|
|
|
475
579
|
sessionId: candidate.sessionId,
|
|
476
580
|
provider: candidate.provider,
|
|
477
581
|
cliId: stringField(operation.payload, "cliId"),
|
|
478
|
-
cliLabel: stringField(operation.payload, "cliLabel")
|
|
582
|
+
cliLabel: stringField(operation.payload, "cliLabel"),
|
|
583
|
+
launchProvider: readLaunchProvider(operation.payload)
|
|
479
584
|
};
|
|
480
585
|
}
|
|
481
586
|
function nativeSessionId(session) {
|
|
@@ -489,9 +594,17 @@ function nativeSessionId(session) {
|
|
|
489
594
|
const match = session.sessionId.match(new RegExp(`^rollout-${timestamp}-(${uuid})$`, "i"));
|
|
490
595
|
return match?.[1]?.toLowerCase() ?? null;
|
|
491
596
|
}
|
|
492
|
-
function
|
|
597
|
+
function emptyModelFields(status = "ok", skippedEntries = 0) {
|
|
598
|
+
return {
|
|
599
|
+
suppliers: [],
|
|
600
|
+
modelRows: [],
|
|
601
|
+
modelTotals: { costUsd: 0, input: 0, output: 0, cacheRead: 0, messages: 0, models: 0 },
|
|
602
|
+
modelSource: { status, skippedEntries }
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function buildSummary(sessions, operations, scope, status = "ok", generatedAtMs = Date.now(), skippedSessions = 0, modelBreakdown = EMPTY_MODEL_BREAKDOWN) {
|
|
493
606
|
try {
|
|
494
|
-
return buildSummaryUnchecked(sessions, operations, scope, status, generatedAtMs, skippedSessions);
|
|
607
|
+
return buildSummaryUnchecked(sessions, operations, scope, status, generatedAtMs, skippedSessions, modelBreakdown);
|
|
495
608
|
} catch (error) {
|
|
496
609
|
if (!(error instanceof AggregateOverflowError)) throw error;
|
|
497
610
|
return {
|
|
@@ -505,6 +618,7 @@ function buildSummary(sessions, operations, scope, status = "ok", generatedAtMs
|
|
|
505
618
|
otherTheaterTotals: { costUsd: 0, input: 0, output: 0, cacheRead: 0, messages: 0 },
|
|
506
619
|
deviceTotals: { costUsd: 0, input: 0, output: 0, cacheRead: 0, messages: 0, sessions: 0 },
|
|
507
620
|
clients: [],
|
|
621
|
+
...emptyModelFields("unreadable", modelBreakdown.skippedEntries + modelBreakdown.entries.length),
|
|
508
622
|
daily: [],
|
|
509
623
|
dailyAttributed: [],
|
|
510
624
|
source: {
|
|
@@ -514,7 +628,7 @@ function buildSummary(sessions, operations, scope, status = "ok", generatedAtMs
|
|
|
514
628
|
};
|
|
515
629
|
}
|
|
516
630
|
}
|
|
517
|
-
function buildSummaryUnchecked(sessions, operations, scope, status, generatedAtMs, skippedSessions) {
|
|
631
|
+
function buildSummaryUnchecked(sessions, operations, scope, status, generatedAtMs, skippedSessions, modelBreakdown) {
|
|
518
632
|
const operationClaims = operations.map(readClaim).filter((claim) => claim !== null).sort((a, b) => b.operation.ts.updatedAt - a.operation.ts.updatedAt || (a.operation.id < b.operation.id ? -1 : a.operation.id > b.operation.id ? 1 : 0));
|
|
519
633
|
const claims = /* @__PURE__ */ new Map();
|
|
520
634
|
for (const claim of operationClaims) {
|
|
@@ -547,6 +661,7 @@ function buildSummaryUnchecked(sessions, operations, scope, status, generatedAtM
|
|
|
547
661
|
title: claim.operation.title,
|
|
548
662
|
cliId: claim.cliId,
|
|
549
663
|
cliLabel: claim.cliLabel,
|
|
664
|
+
launchProvider: claim.launchProvider,
|
|
550
665
|
client: claimed[0]?.client ?? claim.provider,
|
|
551
666
|
messages: totals.messages,
|
|
552
667
|
usage: usageOf(totals),
|
|
@@ -560,6 +675,7 @@ function buildSummaryUnchecked(sessions, operations, scope, status, generatedAtM
|
|
|
560
675
|
title: claim.operation.title,
|
|
561
676
|
cliId: claim.cliId,
|
|
562
677
|
cliLabel: claim.cliLabel,
|
|
678
|
+
launchProvider: claim.launchProvider,
|
|
563
679
|
lastActivityAtMs: claim.operation.ts.updatedAt
|
|
564
680
|
}));
|
|
565
681
|
const unmatched = unmatchedAll.slice(0, MAX_UNMATCHED);
|
|
@@ -608,11 +724,77 @@ function buildSummaryUnchecked(sessions, operations, scope, status, generatedAtM
|
|
|
608
724
|
otherTheaterTotals: { ...usageOf(otherTheaterValues), costUsd: otherTheaterValues.costUsd, messages: otherTheaterValues.messages },
|
|
609
725
|
deviceTotals: { ...usageOf(deviceValues), costUsd: deviceValues.costUsd, messages: deviceValues.messages, sessions: deviceSessions },
|
|
610
726
|
clients,
|
|
727
|
+
...buildModelFields(modelBreakdown),
|
|
611
728
|
daily,
|
|
612
729
|
dailyAttributed,
|
|
613
730
|
source: { status, skippedSessions }
|
|
614
731
|
};
|
|
615
732
|
}
|
|
733
|
+
var MAX_MODEL_ROWS = 80;
|
|
734
|
+
function buildModelFields(modelBreakdown) {
|
|
735
|
+
if (modelBreakdown.status === "unavailable" || modelBreakdown.status === "unreadable" || modelBreakdown.status === "bootstrapping") {
|
|
736
|
+
return emptyModelFields(modelBreakdown.status, modelBreakdown.skippedEntries);
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
return buildModelFieldsUnchecked(modelBreakdown);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (!(error instanceof AggregateOverflowError)) throw error;
|
|
742
|
+
return emptyModelFields("unreadable", modelBreakdown.skippedEntries + modelBreakdown.entries.length);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function buildModelFieldsUnchecked(modelBreakdown) {
|
|
746
|
+
const modelMap = /* @__PURE__ */ new Map();
|
|
747
|
+
const supplierMap = /* @__PURE__ */ new Map();
|
|
748
|
+
const grand = emptyAccumulator();
|
|
749
|
+
for (const entry of modelBreakdown.entries) {
|
|
750
|
+
const identity = parseModelIdentity(entry.modelId);
|
|
751
|
+
const key = `${identity.supplier}:${normalizeModelKey(entry.modelId)}`;
|
|
752
|
+
const model = modelMap.get(key) ?? { identity, totals: emptyAccumulator() };
|
|
753
|
+
addModelEntry(model.totals, entry);
|
|
754
|
+
modelMap.set(key, model);
|
|
755
|
+
const supplier = supplierMap.get(identity.supplier) ?? { models: /* @__PURE__ */ new Set(), totals: emptyAccumulator() };
|
|
756
|
+
supplier.models.add(key);
|
|
757
|
+
addModelEntry(supplier.totals, entry);
|
|
758
|
+
supplierMap.set(identity.supplier, supplier);
|
|
759
|
+
addModelEntry(grand, entry);
|
|
760
|
+
}
|
|
761
|
+
const modelRows = [...modelMap.values()].map(({ identity, totals }) => ({
|
|
762
|
+
modelId: identity.modelId,
|
|
763
|
+
label: identity.label,
|
|
764
|
+
supplier: identity.supplier,
|
|
765
|
+
usage: usageOf(totals),
|
|
766
|
+
costUsd: totals.costUsd,
|
|
767
|
+
messages: totals.messages
|
|
768
|
+
})).sort((a, b) => b.costUsd - a.costUsd || b.messages - a.messages || (a.label < b.label ? -1 : 1)).slice(0, MAX_MODEL_ROWS);
|
|
769
|
+
const suppliers = [...supplierMap.entries()].map(([supplier, value]) => ({
|
|
770
|
+
supplier,
|
|
771
|
+
models: value.models.size,
|
|
772
|
+
usage: usageOf(value.totals),
|
|
773
|
+
costUsd: value.totals.costUsd,
|
|
774
|
+
messages: value.totals.messages
|
|
775
|
+
})).sort((a, b) => b.costUsd - a.costUsd || (a.supplier < b.supplier ? -1 : 1));
|
|
776
|
+
return {
|
|
777
|
+
suppliers,
|
|
778
|
+
modelRows,
|
|
779
|
+
modelTotals: {
|
|
780
|
+
...usageOf(grand),
|
|
781
|
+
costUsd: grand.costUsd,
|
|
782
|
+
messages: grand.messages,
|
|
783
|
+
models: modelMap.size
|
|
784
|
+
},
|
|
785
|
+
modelSource: {
|
|
786
|
+
status: modelBreakdown.status,
|
|
787
|
+
skippedEntries: modelBreakdown.skippedEntries
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function addModelEntry(target, entry) {
|
|
792
|
+
target.input = addFinite(target.input, entry.input);
|
|
793
|
+
target.output = addFinite(target.output, entry.output);
|
|
794
|
+
target.cacheRead = addFinite(target.cacheRead, entry.cacheRead);
|
|
795
|
+
target.costUsd = addFinite(target.costUsd, entry.costUsd);
|
|
796
|
+
target.messages = addFinite(target.messages, entry.messages);
|
|
797
|
+
}
|
|
616
798
|
|
|
617
799
|
// ../fleet-plugins/ledger/server/service.ts
|
|
618
800
|
var CACHE_TTL_MS = 15e3;
|
|
@@ -626,14 +808,17 @@ function createLedgerService(deps) {
|
|
|
626
808
|
function cacheKey(request) {
|
|
627
809
|
return request.window;
|
|
628
810
|
}
|
|
629
|
-
function
|
|
630
|
-
return buildSummary(report.sessions, request.operations, {
|
|
811
|
+
function summaryFromWindow(request, window) {
|
|
812
|
+
return buildSummary(window.report.sessions, request.operations, {
|
|
631
813
|
theaterId: request.theaterId,
|
|
632
814
|
window: request.window
|
|
633
|
-
}, report.status, report.generatedAtMs, report.skippedSessions);
|
|
815
|
+
}, window.report.status, window.report.generatedAtMs, window.report.skippedSessions, window.models);
|
|
634
816
|
}
|
|
635
817
|
function statusDto(request, status) {
|
|
636
|
-
return
|
|
818
|
+
return summaryFromWindow(request, {
|
|
819
|
+
report: { sessions: [], status, skippedSessions: 0, generatedAtMs: now() },
|
|
820
|
+
models: { ...EMPTY_MODEL_BREAKDOWN, status }
|
|
821
|
+
});
|
|
637
822
|
}
|
|
638
823
|
async function executeReport(window) {
|
|
639
824
|
const args = ["report", "--json", "--no-summarize", `--${window}`];
|
|
@@ -654,18 +839,40 @@ function createLedgerService(deps) {
|
|
|
654
839
|
return { sessions: [], status: "unavailable", skippedSessions: 0, generatedAtMs: now() };
|
|
655
840
|
}
|
|
656
841
|
}
|
|
842
|
+
async function executeModels(window) {
|
|
843
|
+
const args = ["models", "--json", "--no-spinner", "--group-by", "model", `--${window}`];
|
|
844
|
+
try {
|
|
845
|
+
const result = await deps.executor(args, {
|
|
846
|
+
cwd: os.homedir(),
|
|
847
|
+
timeout: TOKSCALE_TIMEOUT_MS
|
|
848
|
+
});
|
|
849
|
+
if (result.exitCode !== 0) return { entries: [], status: "unavailable", skippedEntries: 0 };
|
|
850
|
+
const parsed = parseTokscaleModelsOutput(result.stdout);
|
|
851
|
+
return {
|
|
852
|
+
entries: parsed.entries,
|
|
853
|
+
status: parsed.status,
|
|
854
|
+
skippedEntries: parsed.skippedEntries
|
|
855
|
+
};
|
|
856
|
+
} catch {
|
|
857
|
+
return { entries: [], status: "unavailable", skippedEntries: 0 };
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async function executeWindow(window) {
|
|
861
|
+
const [report, models] = await Promise.all([executeReport(window), executeModels(window)]);
|
|
862
|
+
return { report, models };
|
|
863
|
+
}
|
|
657
864
|
function getOrStartReport(key, window) {
|
|
658
865
|
const existing = reportInFlight.get(key);
|
|
659
866
|
if (existing) return existing;
|
|
660
|
-
const task =
|
|
867
|
+
const task = executeWindow(window).finally(() => {
|
|
661
868
|
reportInFlight.delete(key);
|
|
662
869
|
});
|
|
663
870
|
reportInFlight.set(key, task);
|
|
664
871
|
return task;
|
|
665
872
|
}
|
|
666
873
|
function startBootstrapAndReport(key, request) {
|
|
667
|
-
const task = bootstrap(deps.cliHome).then(() => getOrStartReport(key, request.window)).then((
|
|
668
|
-
cache.set(key, {
|
|
874
|
+
const task = bootstrap(deps.cliHome).then(() => getOrStartReport(key, request.window)).then((window) => {
|
|
875
|
+
cache.set(key, { window, expiresAt: now() + CACHE_TTL_MS });
|
|
669
876
|
}).catch(() => {
|
|
670
877
|
}).finally(() => {
|
|
671
878
|
bootstrapPending.delete(key);
|
|
@@ -677,7 +884,7 @@ function createLedgerService(deps) {
|
|
|
677
884
|
const key = cacheKey(request);
|
|
678
885
|
if (request.refresh) cache.delete(key);
|
|
679
886
|
const cached = cache.get(key);
|
|
680
|
-
if (cached && cached.expiresAt > now()) return
|
|
887
|
+
if (cached && cached.expiresAt > now()) return summaryFromWindow(request, cached.window);
|
|
681
888
|
cache.delete(key);
|
|
682
889
|
if (bootstrapPending.has(key)) return statusDto(request, "bootstrapping");
|
|
683
890
|
if (!await isInstalled(deps.cliHome)) {
|
|
@@ -685,9 +892,9 @@ function createLedgerService(deps) {
|
|
|
685
892
|
startBootstrapAndReport(key, request);
|
|
686
893
|
return statusDto(request, "bootstrapping");
|
|
687
894
|
}
|
|
688
|
-
const
|
|
689
|
-
cache.set(key, {
|
|
690
|
-
return
|
|
895
|
+
const window = await getOrStartReport(key, request.window);
|
|
896
|
+
cache.set(key, { window, expiresAt: now() + CACHE_TTL_MS });
|
|
897
|
+
return summaryFromWindow(request, window);
|
|
691
898
|
}
|
|
692
899
|
};
|
|
693
900
|
}
|