aoaoe 0.75.0 → 0.77.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/dist/index.js +19 -0
- package/dist/input.d.ts +3 -0
- package/dist/input.js +12 -0
- package/dist/tui.d.ts +6 -1
- package/dist/tui.js +42 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -325,6 +325,25 @@ async function main() {
|
|
|
325
325
|
tui.log("system", `viewing session #${sessionIdx}`);
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
|
+
// wire quick-switch: bare digit 1-9 jumps to session (or switches in drilldown)
|
|
329
|
+
input.onQuickSwitch((num) => {
|
|
330
|
+
if (tui.getViewMode() === "drilldown") {
|
|
331
|
+
// in drilldown, switch to a different session
|
|
332
|
+
const ok = tui.enterDrilldown(num);
|
|
333
|
+
if (ok)
|
|
334
|
+
tui.log("system", `switched to session #${num}`);
|
|
335
|
+
else
|
|
336
|
+
tui.log("system", `session #${num} not found`);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
// in overview, drill into session
|
|
340
|
+
const ok = tui.enterDrilldown(num);
|
|
341
|
+
if (ok)
|
|
342
|
+
tui.log("system", `viewing session #${num}`);
|
|
343
|
+
else
|
|
344
|
+
tui.log("system", `session #${num} not found`);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
328
347
|
// wire /search command to TUI activity filter
|
|
329
348
|
input.onSearch((pattern) => {
|
|
330
349
|
tui.setSearch(pattern);
|
package/dist/input.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type ScrollDirection = "up" | "down" | "top" | "bottom";
|
|
|
2
2
|
export declare const INSIST_PREFIX = "__INSIST__";
|
|
3
3
|
export type ViewHandler = (target: string | null) => void;
|
|
4
4
|
export type SearchHandler = (pattern: string | null) => void;
|
|
5
|
+
export type QuickSwitchHandler = (sessionNum: number) => void;
|
|
5
6
|
export interface MouseEvent {
|
|
6
7
|
button: number;
|
|
7
8
|
col: number;
|
|
@@ -26,6 +27,7 @@ export declare class InputReader {
|
|
|
26
27
|
private mouseMoveHandler;
|
|
27
28
|
private lastMoveRow;
|
|
28
29
|
private searchHandler;
|
|
30
|
+
private quickSwitchHandler;
|
|
29
31
|
private mouseDataListener;
|
|
30
32
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
31
33
|
onQueueChange(handler: (count: number) => void): void;
|
|
@@ -34,6 +36,7 @@ export declare class InputReader {
|
|
|
34
36
|
onMouseWheel(handler: MouseWheelHandler): void;
|
|
35
37
|
onMouseMove(handler: MouseMoveHandler): void;
|
|
36
38
|
onSearch(handler: SearchHandler): void;
|
|
39
|
+
onQuickSwitch(handler: QuickSwitchHandler): void;
|
|
37
40
|
private notifyQueueChange;
|
|
38
41
|
start(): void;
|
|
39
42
|
drain(): string[];
|
package/dist/input.js
CHANGED
|
@@ -34,6 +34,7 @@ export class InputReader {
|
|
|
34
34
|
mouseMoveHandler = null;
|
|
35
35
|
lastMoveRow = 0; // debounce: only fire move handler when row changes
|
|
36
36
|
searchHandler = null;
|
|
37
|
+
quickSwitchHandler = null;
|
|
37
38
|
mouseDataListener = null;
|
|
38
39
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
39
40
|
onScroll(handler) {
|
|
@@ -63,6 +64,10 @@ export class InputReader {
|
|
|
63
64
|
onSearch(handler) {
|
|
64
65
|
this.searchHandler = handler;
|
|
65
66
|
}
|
|
67
|
+
// register a callback for quick-switch (bare digit 1-9 on empty input line)
|
|
68
|
+
onQuickSwitch(handler) {
|
|
69
|
+
this.quickSwitchHandler = handler;
|
|
70
|
+
}
|
|
66
71
|
notifyQueueChange() {
|
|
67
72
|
this.queueChangeHandler?.(this.queue.length);
|
|
68
73
|
}
|
|
@@ -191,6 +196,12 @@ export class InputReader {
|
|
|
191
196
|
this.rl?.prompt();
|
|
192
197
|
return;
|
|
193
198
|
}
|
|
199
|
+
// quick-switch: bare digit 1-9 jumps to that session
|
|
200
|
+
if (/^[1-9]$/.test(line) && this.quickSwitchHandler) {
|
|
201
|
+
this.quickSwitchHandler(parseInt(line, 10));
|
|
202
|
+
this.rl?.prompt();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
194
205
|
// built-in slash commands
|
|
195
206
|
if (line.startsWith("/")) {
|
|
196
207
|
this.handleCommand(line);
|
|
@@ -231,6 +242,7 @@ ${BOLD}controls:${RESET}
|
|
|
231
242
|
ESC ESC same as /interrupt (shortcut)
|
|
232
243
|
|
|
233
244
|
${BOLD}navigation:${RESET}
|
|
245
|
+
1-9 quick-switch: jump to session N (type digit + Enter)
|
|
234
246
|
/view [N|name] drill into a session's live output (default: 1)
|
|
235
247
|
/back return to overview from drill-down
|
|
236
248
|
/search <pattern> filter activity entries by substring (case-insensitive)
|
package/dist/tui.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class TUI {
|
|
|
25
25
|
private pendingCount;
|
|
26
26
|
private searchPattern;
|
|
27
27
|
private hoverSessionIdx;
|
|
28
|
+
private activityTimestamps;
|
|
28
29
|
private viewMode;
|
|
29
30
|
private drilldownSessionId;
|
|
30
31
|
private sessionOutputs;
|
|
@@ -116,6 +117,10 @@ declare function computeScrollSlice(bufferLen: number, visibleLines: number, scr
|
|
|
116
117
|
};
|
|
117
118
|
declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
|
|
118
119
|
declare function formatDrilldownScrollIndicator(offset: number, totalLines: number, visibleLines: number, newCount: number): string;
|
|
120
|
+
/** Compute sparkline bucket counts from activity timestamps. Returns array of SPARK_BUCKETS counts. */
|
|
121
|
+
declare function computeSparkline(timestamps: number[], now?: number, buckets?: number, windowMs?: number): number[];
|
|
122
|
+
/** Format sparkline bucket counts as a colored Unicode block string. Returns empty string if all zeros. */
|
|
123
|
+
declare function formatSparkline(counts: number[]): string;
|
|
119
124
|
/** Case-insensitive substring match against an activity entry's tag, text, and time. */
|
|
120
125
|
declare function matchesSearch(entry: ActivityEntry, pattern: string): boolean;
|
|
121
126
|
/** Format the search indicator text for the separator bar. */
|
|
@@ -128,5 +133,5 @@ declare function formatSearchIndicator(pattern: string, matchCount: number, tota
|
|
|
128
133
|
* (row = headerHeight + 2 + i for 0-indexed session i)
|
|
129
134
|
*/
|
|
130
135
|
export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
|
|
131
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator };
|
|
136
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline };
|
|
132
137
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.js
CHANGED
|
@@ -65,6 +65,7 @@ export class TUI {
|
|
|
65
65
|
pendingCount = 0; // queued user messages awaiting next tick
|
|
66
66
|
searchPattern = null; // active search filter pattern
|
|
67
67
|
hoverSessionIdx = null; // 1-indexed session under mouse cursor (null = none)
|
|
68
|
+
activityTimestamps = []; // epoch ms of each log() call for sparkline
|
|
68
69
|
// drill-down mode: show a single session's full output
|
|
69
70
|
viewMode = "overview";
|
|
70
71
|
drilldownSessionId = null;
|
|
@@ -158,8 +159,10 @@ export class TUI {
|
|
|
158
159
|
const time = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}`;
|
|
159
160
|
const entry = { time, tag, text };
|
|
160
161
|
this.activityBuffer.push(entry);
|
|
162
|
+
this.activityTimestamps.push(now.getTime());
|
|
161
163
|
if (this.activityBuffer.length > this.maxActivity) {
|
|
162
164
|
this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
|
|
165
|
+
this.activityTimestamps = this.activityTimestamps.slice(-this.maxActivity);
|
|
163
166
|
}
|
|
164
167
|
if (this.active) {
|
|
165
168
|
if (this.searchPattern) {
|
|
@@ -520,7 +523,9 @@ export class TUI {
|
|
|
520
523
|
hints = formatScrollIndicator(this.scrollOffset, this.activityBuffer.length, this.scrollBottom - this.scrollTop + 1, this.newWhileScrolled);
|
|
521
524
|
}
|
|
522
525
|
else {
|
|
523
|
-
|
|
526
|
+
// live mode: show sparkline + minimal hints
|
|
527
|
+
const spark = formatSparkline(computeSparkline(this.activityTimestamps));
|
|
528
|
+
hints = spark ? ` ${spark} /help ` : " click agent to view esc esc: interrupt /help ";
|
|
524
529
|
}
|
|
525
530
|
const totalLen = prefix.length + hints.length;
|
|
526
531
|
const fill = Math.max(0, this.cols - totalLen);
|
|
@@ -803,6 +808,41 @@ function formatDrilldownScrollIndicator(offset, totalLines, visibleLines, newCou
|
|
|
803
808
|
const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
|
|
804
809
|
return ` ↑ ${offset} lines │ ${position}/${totalLines} │ scroll: navigate End=live${newTag} `;
|
|
805
810
|
}
|
|
811
|
+
// ── Sparkline helpers (pure, exported for testing) ──────────────────────────
|
|
812
|
+
const SPARK_BLOCKS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
813
|
+
const SPARK_BUCKETS = 20; // number of time buckets
|
|
814
|
+
const SPARK_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
|
|
815
|
+
/** Compute sparkline bucket counts from activity timestamps. Returns array of SPARK_BUCKETS counts. */
|
|
816
|
+
function computeSparkline(timestamps, now, buckets, windowMs) {
|
|
817
|
+
const n = buckets ?? SPARK_BUCKETS;
|
|
818
|
+
const window = windowMs ?? SPARK_WINDOW_MS;
|
|
819
|
+
const nowMs = now ?? Date.now();
|
|
820
|
+
const cutoff = nowMs - window;
|
|
821
|
+
const bucketSize = window / n;
|
|
822
|
+
const counts = new Array(n).fill(0);
|
|
823
|
+
for (const ts of timestamps) {
|
|
824
|
+
if (ts < cutoff)
|
|
825
|
+
continue;
|
|
826
|
+
const idx = Math.min(n - 1, Math.floor((ts - cutoff) / bucketSize));
|
|
827
|
+
counts[idx]++;
|
|
828
|
+
}
|
|
829
|
+
return counts;
|
|
830
|
+
}
|
|
831
|
+
/** Format sparkline bucket counts as a colored Unicode block string. Returns empty string if all zeros. */
|
|
832
|
+
function formatSparkline(counts) {
|
|
833
|
+
const max = Math.max(...counts);
|
|
834
|
+
if (max === 0)
|
|
835
|
+
return "";
|
|
836
|
+
const blocks = counts.map((c) => {
|
|
837
|
+
if (c === 0)
|
|
838
|
+
return `${SLATE} ${RESET}`;
|
|
839
|
+
const level = Math.min(SPARK_BLOCKS.length - 1, Math.floor((c / max) * (SPARK_BLOCKS.length - 1)));
|
|
840
|
+
// color gradient: low=SLATE, mid=SKY, high=LIME
|
|
841
|
+
const color = level < 3 ? SLATE : level < 6 ? SKY : LIME;
|
|
842
|
+
return `${color}${SPARK_BLOCKS[level]}${RESET}`;
|
|
843
|
+
});
|
|
844
|
+
return blocks.join("");
|
|
845
|
+
}
|
|
806
846
|
// ── Search helpers (pure, exported for testing) ─────────────────────────────
|
|
807
847
|
/** Case-insensitive substring match against an activity entry's tag, text, and time. */
|
|
808
848
|
function matchesSearch(entry, pattern) {
|
|
@@ -835,5 +875,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
|
|
|
835
875
|
return row - firstSessionRow + 1; // 1-indexed
|
|
836
876
|
}
|
|
837
877
|
// ── Exported pure helpers (for testing) ─────────────────────────────────────
|
|
838
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator };
|
|
878
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline };
|
|
839
879
|
//# sourceMappingURL=tui.js.map
|