@os-eco/overstory-cli 0.7.5 → 0.7.7
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/README.md +12 -8
- package/package.json +1 -1
- package/src/commands/agents.ts +21 -3
- package/src/commands/completions.ts +7 -1
- package/src/commands/costs.test.ts +45 -2
- package/src/commands/costs.ts +42 -13
- package/src/commands/dashboard.test.ts +101 -10
- package/src/commands/dashboard.ts +95 -61
- package/src/commands/doctor.ts +3 -1
- package/src/commands/init.test.ts +366 -27
- package/src/commands/init.ts +194 -2
- package/src/doctor/providers.test.ts +373 -0
- package/src/doctor/providers.ts +250 -0
- package/src/doctor/types.ts +2 -1
- package/src/e2e/init-sling-lifecycle.test.ts +12 -7
- package/src/index.ts +11 -2
- package/src/metrics/pricing.ts +57 -2
- package/src/metrics/store.test.ts +38 -0
- package/src/metrics/store.ts +10 -0
- package/src/metrics/transcript.test.ts +84 -2
- package/src/metrics/transcript.ts +1 -1
- package/src/runtimes/claude.test.ts +1 -1
- package/src/runtimes/codex.test.ts +741 -0
- package/src/runtimes/codex.ts +228 -0
- package/src/runtimes/copilot.test.ts +507 -0
- package/src/runtimes/copilot.ts +226 -0
- package/src/runtimes/pi.test.ts +1 -1
- package/src/runtimes/registry.test.ts +26 -6
- package/src/runtimes/registry.ts +4 -0
|
@@ -24,6 +24,7 @@ import { createEventStore } from "../events/store.ts";
|
|
|
24
24
|
import { accent, brand, color, visibleLength } from "../logging/color.ts";
|
|
25
25
|
import {
|
|
26
26
|
buildAgentColorMap,
|
|
27
|
+
extendAgentColorMap,
|
|
27
28
|
formatDuration,
|
|
28
29
|
formatEventLine,
|
|
29
30
|
formatRelativeTime,
|
|
@@ -128,10 +129,10 @@ export { pad, truncate, horizontalLine };
|
|
|
128
129
|
|
|
129
130
|
/**
|
|
130
131
|
* Compute agent panel height from screen height and agent count.
|
|
131
|
-
* min 8 rows, max floor(height * 0.
|
|
132
|
+
* min 8 rows, max floor(height * 0.35), grows with agent count (+4 for chrome).
|
|
132
133
|
*/
|
|
133
134
|
export function computeAgentPanelHeight(height: number, agentCount: number): number {
|
|
134
|
-
return Math.max(8, Math.min(Math.floor(height * 0.
|
|
135
|
+
return Math.max(8, Math.min(Math.floor(height * 0.35), agentCount + 4));
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
/**
|
|
@@ -241,6 +242,49 @@ export function closeDashboardStores(stores: DashboardStores): void {
|
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Rolling event buffer with incremental dedup by lastSeenId.
|
|
247
|
+
* Maintains a fixed-size window of the most recent events.
|
|
248
|
+
*/
|
|
249
|
+
export class EventBuffer {
|
|
250
|
+
private events: StoredEvent[] = [];
|
|
251
|
+
private lastSeenId = 0;
|
|
252
|
+
private colorMap: Map<string, (s: string) => string> = new Map();
|
|
253
|
+
private readonly maxSize: number;
|
|
254
|
+
|
|
255
|
+
constructor(maxSize = 100) {
|
|
256
|
+
this.maxSize = maxSize;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
poll(eventStore: EventStore): void {
|
|
260
|
+
const since = new Date(Date.now() - 60 * 1000).toISOString();
|
|
261
|
+
const allEvents = eventStore.getTimeline({ since, limit: 1000 });
|
|
262
|
+
const newEvents = allEvents.filter((e) => e.id > this.lastSeenId);
|
|
263
|
+
|
|
264
|
+
if (newEvents.length === 0) return;
|
|
265
|
+
|
|
266
|
+
extendAgentColorMap(this.colorMap, newEvents);
|
|
267
|
+
this.events = [...this.events, ...newEvents].slice(-this.maxSize);
|
|
268
|
+
|
|
269
|
+
const lastEvent = newEvents[newEvents.length - 1];
|
|
270
|
+
if (lastEvent) {
|
|
271
|
+
this.lastSeenId = lastEvent.id;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
getEvents(): StoredEvent[] {
|
|
276
|
+
return this.events;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getColorMap(): Map<string, (s: string) => string> {
|
|
280
|
+
return this.colorMap;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
get size(): number {
|
|
284
|
+
return this.events.length;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
244
288
|
/** Tracker data cached between dashboard ticks (10s TTL). */
|
|
245
289
|
interface TrackerCache {
|
|
246
290
|
tasks: TrackerIssue[];
|
|
@@ -263,6 +307,7 @@ interface DashboardData {
|
|
|
263
307
|
};
|
|
264
308
|
tasks: TrackerIssue[];
|
|
265
309
|
recentEvents: StoredEvent[];
|
|
310
|
+
feedColorMap: Map<string, (s: string) => string>;
|
|
266
311
|
}
|
|
267
312
|
|
|
268
313
|
/**
|
|
@@ -289,6 +334,7 @@ async function loadDashboardData(
|
|
|
289
334
|
stores: DashboardStores,
|
|
290
335
|
runId?: string | null,
|
|
291
336
|
thresholds?: { staleMs: number; zombieMs: number },
|
|
337
|
+
eventBuffer?: EventBuffer,
|
|
292
338
|
): Promise<DashboardData> {
|
|
293
339
|
// Get all sessions from the pre-opened session store
|
|
294
340
|
const allSessions = stores.sessionStore.getAll();
|
|
@@ -450,13 +496,14 @@ async function loadDashboardData(
|
|
|
450
496
|
tasks = trackerCache.tasks;
|
|
451
497
|
}
|
|
452
498
|
|
|
453
|
-
// Load recent events
|
|
499
|
+
// Load recent events via incremental buffer (or fallback to empty)
|
|
454
500
|
let recentEvents: StoredEvent[] = [];
|
|
455
|
-
|
|
501
|
+
let feedColorMap: Map<string, (s: string) => string> = new Map();
|
|
502
|
+
if (eventBuffer && stores.eventStore) {
|
|
456
503
|
try {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
504
|
+
eventBuffer.poll(stores.eventStore);
|
|
505
|
+
recentEvents = [...eventBuffer.getEvents()].reverse();
|
|
506
|
+
feedColorMap = eventBuffer.getColorMap();
|
|
460
507
|
} catch {
|
|
461
508
|
/* best effort */
|
|
462
509
|
}
|
|
@@ -470,6 +517,7 @@ async function loadDashboardData(
|
|
|
470
517
|
metrics: { totalSessions, avgDuration, byCapability },
|
|
471
518
|
tasks,
|
|
472
519
|
recentEvents,
|
|
520
|
+
feedColorMap,
|
|
473
521
|
};
|
|
474
522
|
}
|
|
475
523
|
|
|
@@ -496,7 +544,7 @@ export function renderAgentPanel(
|
|
|
496
544
|
panelHeight: number,
|
|
497
545
|
startRow: number,
|
|
498
546
|
): string {
|
|
499
|
-
const leftWidth =
|
|
547
|
+
const leftWidth = fullWidth;
|
|
500
548
|
let output = "";
|
|
501
549
|
|
|
502
550
|
// Panel header
|
|
@@ -650,7 +698,7 @@ export function renderFeedPanel(
|
|
|
650
698
|
let output = "";
|
|
651
699
|
|
|
652
700
|
// Header
|
|
653
|
-
const headerLine = `${dimBox.vertical} ${brand.bold("Feed")} (
|
|
701
|
+
const headerLine = `${dimBox.vertical} ${brand.bold("Feed")} (live)`;
|
|
654
702
|
const headerPadding = " ".repeat(
|
|
655
703
|
Math.max(0, panelWidth - visibleLength(headerLine) - visibleLength(dimBox.vertical)),
|
|
656
704
|
);
|
|
@@ -676,7 +724,8 @@ export function renderFeedPanel(
|
|
|
676
724
|
return output;
|
|
677
725
|
}
|
|
678
726
|
|
|
679
|
-
const colorMap =
|
|
727
|
+
const colorMap =
|
|
728
|
+
data.feedColorMap.size > 0 ? data.feedColorMap : buildAgentColorMap(data.recentEvents);
|
|
680
729
|
const visibleEvents = data.recentEvents.slice(0, maxRows);
|
|
681
730
|
|
|
682
731
|
for (let i = 0; i < visibleEvents.length; i++) {
|
|
@@ -733,12 +782,10 @@ export function renderFeedPanel(
|
|
|
733
782
|
*/
|
|
734
783
|
function renderMailPanel(
|
|
735
784
|
data: DashboardData,
|
|
736
|
-
|
|
737
|
-
|
|
785
|
+
panelWidth: number,
|
|
786
|
+
panelHeight: number,
|
|
738
787
|
startRow: number,
|
|
739
788
|
): string {
|
|
740
|
-
const panelHeight = Math.floor(height * 0.3);
|
|
741
|
-
const panelWidth = Math.floor(width * 0.5);
|
|
742
789
|
let output = "";
|
|
743
790
|
|
|
744
791
|
const unreadCount = data.status.unreadMailCount;
|
|
@@ -787,13 +834,11 @@ function renderMailPanel(
|
|
|
787
834
|
*/
|
|
788
835
|
function renderMergeQueuePanel(
|
|
789
836
|
data: DashboardData,
|
|
790
|
-
|
|
791
|
-
|
|
837
|
+
panelWidth: number,
|
|
838
|
+
panelHeight: number,
|
|
792
839
|
startRow: number,
|
|
793
840
|
startCol: number,
|
|
794
841
|
): string {
|
|
795
|
-
const panelHeight = Math.floor(height * 0.3);
|
|
796
|
-
const panelWidth = width - startCol + 1;
|
|
797
842
|
let output = "";
|
|
798
843
|
|
|
799
844
|
const headerLine = `${dimBox.vertical} ${brand.bold("Merge Queue")} (${data.mergeQueue.length})`;
|
|
@@ -847,26 +892,20 @@ function renderMetricsPanel(
|
|
|
847
892
|
const separator = dimHorizontalLine(width, dimBox.tee, dimBox.teeRight);
|
|
848
893
|
output += `${CURSOR.cursorTo(startRow, 1)}${separator}\n`;
|
|
849
894
|
|
|
850
|
-
const headerLine = `${dimBox.vertical} ${brand.bold("Metrics")}`;
|
|
851
|
-
const headerPadding = " ".repeat(
|
|
852
|
-
Math.max(0, width - visibleLength(headerLine) - visibleLength(dimBox.vertical)),
|
|
853
|
-
);
|
|
854
|
-
output += `${CURSOR.cursorTo(startRow + 1, 1)}${headerLine}${headerPadding}${dimBox.vertical}\n`;
|
|
855
|
-
|
|
856
895
|
const totalSessions = data.metrics.totalSessions;
|
|
857
896
|
const avgDur = formatDuration(data.metrics.avgDuration);
|
|
858
897
|
const byCapability = Object.entries(data.metrics.byCapability)
|
|
859
898
|
.map(([cap, count]) => `${cap}:${count}`)
|
|
860
899
|
.join(", ");
|
|
861
900
|
|
|
862
|
-
const metricsLine = `${dimBox.vertical} Total
|
|
901
|
+
const metricsLine = `${dimBox.vertical} ${brand.bold("Metrics")} Total: ${totalSessions} | Avg: ${avgDur} | ${byCapability}`;
|
|
863
902
|
const metricsPadding = " ".repeat(
|
|
864
903
|
Math.max(0, width - visibleLength(metricsLine) - visibleLength(dimBox.vertical)),
|
|
865
904
|
);
|
|
866
|
-
output += `${CURSOR.cursorTo(startRow +
|
|
905
|
+
output += `${CURSOR.cursorTo(startRow + 1, 1)}${metricsLine}${metricsPadding}${dimBox.vertical}\n`;
|
|
867
906
|
|
|
868
907
|
const bottomBorder = dimHorizontalLine(width, dimBox.bottomLeft, dimBox.bottomRight);
|
|
869
|
-
output += `${CURSOR.cursorTo(startRow +
|
|
908
|
+
output += `${CURSOR.cursorTo(startRow + 2, 1)}${bottomBorder}\n`;
|
|
870
909
|
|
|
871
910
|
return output;
|
|
872
911
|
}
|
|
@@ -883,50 +922,42 @@ function renderDashboard(data: DashboardData, interval: number): void {
|
|
|
883
922
|
// Header (rows 1-2)
|
|
884
923
|
output += renderHeader(width, interval, data.currentRunId);
|
|
885
924
|
|
|
886
|
-
// Agent panel
|
|
925
|
+
// Agent panel: full width, capped at 35% of height
|
|
887
926
|
const agentPanelStart = 3;
|
|
888
|
-
|
|
889
|
-
// Dynamic agent panel height
|
|
890
927
|
const agentCount = data.status.agents.length;
|
|
891
928
|
const agentPanelHeight = computeAgentPanelHeight(height, agentCount);
|
|
929
|
+
output += renderAgentPanel(data, width, agentPanelHeight, agentPanelStart);
|
|
892
930
|
|
|
893
|
-
//
|
|
894
|
-
const
|
|
895
|
-
const
|
|
896
|
-
const
|
|
931
|
+
// Middle zone: Feed (left 60%) | Tasks (right 40%)
|
|
932
|
+
const middleStart = agentPanelStart + agentPanelHeight + 1;
|
|
933
|
+
const compactPanelHeight = 5; // fixed for mail/merge panels
|
|
934
|
+
const metricsHeight = 3; // separator + data + border
|
|
935
|
+
const middleHeight = Math.max(6, height - middleStart - compactPanelHeight - metricsHeight);
|
|
897
936
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const feedHeight = agentPanelHeight - rightHalf;
|
|
937
|
+
const feedWidth = Math.floor(width * 0.6);
|
|
938
|
+
output += renderFeedPanel(data, 1, feedWidth, middleHeight, middleStart);
|
|
901
939
|
|
|
902
|
-
|
|
903
|
-
|
|
940
|
+
const taskWidth = width - feedWidth;
|
|
941
|
+
const taskStartCol = feedWidth + 1;
|
|
942
|
+
output += renderTasksPanel(data, taskStartCol, taskWidth, middleHeight, middleStart);
|
|
904
943
|
|
|
905
|
-
//
|
|
906
|
-
|
|
944
|
+
// Compact panels: Mail (left 50%) | Merge Queue (right 50%) — fixed 5 rows
|
|
945
|
+
const compactStart = middleStart + middleHeight;
|
|
946
|
+
const mailWidth = Math.floor(width * 0.5);
|
|
947
|
+
output += renderMailPanel(data, mailWidth, compactPanelHeight, compactStart);
|
|
907
948
|
|
|
908
|
-
|
|
909
|
-
|
|
949
|
+
const mergeStartCol = mailWidth + 1;
|
|
950
|
+
const mergeWidth = width - mailWidth;
|
|
951
|
+
output += renderMergeQueuePanel(
|
|
910
952
|
data,
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
953
|
+
mergeWidth,
|
|
954
|
+
compactPanelHeight,
|
|
955
|
+
compactStart,
|
|
956
|
+
mergeStartCol,
|
|
915
957
|
);
|
|
916
958
|
|
|
917
|
-
//
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
// Mail panel (left 50%)
|
|
921
|
-
output += renderMailPanel(data, width, height, middlePanelStart);
|
|
922
|
-
|
|
923
|
-
// Merge queue panel (right 50%)
|
|
924
|
-
const mergeQueueCol = Math.floor(width * 0.5) + 1;
|
|
925
|
-
output += renderMergeQueuePanel(data, width, height, middlePanelStart, mergeQueueCol);
|
|
926
|
-
|
|
927
|
-
// Metrics panel (bottom strip)
|
|
928
|
-
const middlePanelHeight = Math.floor(height * 0.3);
|
|
929
|
-
const metricsStart = middlePanelStart + middlePanelHeight + 1;
|
|
959
|
+
// Metrics footer
|
|
960
|
+
const metricsStart = compactStart + compactPanelHeight;
|
|
930
961
|
output += renderMetricsPanel(data, width, height, metricsStart);
|
|
931
962
|
|
|
932
963
|
process.stdout.write(output);
|
|
@@ -963,6 +994,9 @@ async function executeDashboard(opts: DashboardOpts): Promise<void> {
|
|
|
963
994
|
// Open stores once for the entire poll loop lifetime
|
|
964
995
|
const stores = openDashboardStores(root);
|
|
965
996
|
|
|
997
|
+
// Create rolling event buffer (persisted across poll ticks)
|
|
998
|
+
const eventBuffer = new EventBuffer(100);
|
|
999
|
+
|
|
966
1000
|
// Compute health thresholds once from config (reused across poll ticks)
|
|
967
1001
|
const thresholds = {
|
|
968
1002
|
staleMs: config.watchdog.staleThresholdMs,
|
|
@@ -984,7 +1018,7 @@ async function executeDashboard(opts: DashboardOpts): Promise<void> {
|
|
|
984
1018
|
|
|
985
1019
|
// Poll loop
|
|
986
1020
|
while (running) {
|
|
987
|
-
const data = await loadDashboardData(root, stores, runId, thresholds);
|
|
1021
|
+
const data = await loadDashboardData(root, stores, runId, thresholds, eventBuffer);
|
|
988
1022
|
renderDashboard(data, interval);
|
|
989
1023
|
await Bun.sleep(interval);
|
|
990
1024
|
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { checkDependencies } from "../doctor/dependencies.ts";
|
|
|
15
15
|
import { checkEcosystem } from "../doctor/ecosystem.ts";
|
|
16
16
|
import { checkLogs } from "../doctor/logs.ts";
|
|
17
17
|
import { checkMergeQueue } from "../doctor/merge-queue.ts";
|
|
18
|
+
import { checkProviders } from "../doctor/providers.ts";
|
|
18
19
|
import { checkStructure } from "../doctor/structure.ts";
|
|
19
20
|
import type { DoctorCategory, DoctorCheck, DoctorCheckFn } from "../doctor/types.ts";
|
|
20
21
|
import { checkVersion } from "../doctor/version.ts";
|
|
@@ -35,6 +36,7 @@ const ALL_CHECKS: Array<{ category: DoctorCategory; fn: DoctorCheckFn }> = [
|
|
|
35
36
|
{ category: "logs", fn: checkLogs },
|
|
36
37
|
{ category: "version", fn: checkVersion },
|
|
37
38
|
{ category: "ecosystem", fn: checkEcosystem },
|
|
39
|
+
{ category: "providers", fn: checkProviders },
|
|
38
40
|
];
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -166,7 +168,7 @@ export function createDoctorCommand(options?: DoctorCommandOptions): Command {
|
|
|
166
168
|
.option("--fix", "Attempt to auto-fix issues")
|
|
167
169
|
.addHelpText(
|
|
168
170
|
"after",
|
|
169
|
-
"\nCategories: dependencies, structure, config, databases, consistency, agents, merge, logs, version, ecosystem",
|
|
171
|
+
"\nCategories: dependencies, structure, config, databases, consistency, agents, merge, logs, version, ecosystem, providers",
|
|
170
172
|
)
|
|
171
173
|
.action(
|
|
172
174
|
async (opts: { json?: boolean; verbose?: boolean; category?: string; fix?: boolean }) => {
|