@aragon-agent/cli 0.6.29 → 0.6.32
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/CHANGELOG.md +2223 -2103
- package/README.md +2957 -2759
- package/dist/agent/controller.d.ts +46 -0
- package/dist/agent/controller.js +175 -7
- package/dist/agent/job-notices.d.ts +143 -0
- package/dist/agent/job-notices.js +182 -0
- package/dist/agent/project-guidance-prompt.js +20 -20
- package/dist/agent/reducer.d.ts +21 -1
- package/dist/agent/reducer.js +7 -0
- package/dist/agent/system-prompt.d.ts +12 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/cli.js +399 -62
- package/dist/commands/builtins.js +27 -8
- package/dist/commands/perf.d.ts +79 -2
- package/dist/commands/perf.js +51 -3
- package/dist/commands/registry.d.ts +6 -0
- package/dist/config/cli-commands.d.ts +27 -0
- package/dist/config/cli-commands.js +54 -1
- package/dist/config/env.js +42 -0
- package/dist/config/load.d.ts +3 -1
- package/dist/config/load.js +61 -1
- package/dist/config/schema.d.ts +100 -0
- package/dist/config/schema.js +43 -0
- package/dist/config/store.js +14 -1
- package/dist/config/ui-state.d.ts +9 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +29 -3
- package/dist/exec/events.js +3 -12
- package/dist/exec/runner.d.ts +24 -0
- package/dist/exec/runner.js +79 -0
- package/dist/input/cursor-report.d.ts +61 -0
- package/dist/input/cursor-report.js +97 -0
- package/dist/input/stdin-filter.d.ts +22 -0
- package/dist/input/stdin-filter.js +48 -3
- package/dist/proc/limits.d.ts +76 -2
- package/dist/proc/limits.js +83 -2
- package/dist/proc/log-mirror.d.ts +59 -0
- package/dist/proc/log-mirror.js +203 -0
- package/dist/proc/log-ring.d.ts +22 -0
- package/dist/proc/log-ring.js +20 -1
- package/dist/proc/notices.d.ts +57 -0
- package/dist/proc/notices.js +101 -0
- package/dist/proc/progress.d.ts +40 -0
- package/dist/proc/progress.js +135 -0
- package/dist/proc/prompt.d.ts +24 -2
- package/dist/proc/prompt.js +51 -2
- package/dist/proc/supervisor.d.ts +35 -2
- package/dist/proc/supervisor.js +142 -43
- package/dist/proc/types.d.ts +89 -1
- package/dist/proc/wait.d.ts +57 -0
- package/dist/proc/wait.js +133 -0
- package/dist/session/persist.js +3 -1
- package/dist/session/validate-session.js +17 -0
- package/dist/tools/bash-tool.d.ts +12 -1
- package/dist/tools/bash-tool.js +100 -13
- package/dist/tools/index.d.ts +26 -2
- package/dist/tools/index.js +43 -2
- package/dist/tools/job-tools.d.ts +54 -0
- package/dist/tools/job-tools.js +329 -0
- package/dist/tools/proc-tools.d.ts +1 -1
- package/dist/tools/proc-tools.js +49 -17
- package/dist/ui/ActivityLine.js +7 -0
- package/dist/ui/App.d.ts +11 -1
- package/dist/ui/App.js +181 -63
- package/dist/ui/ScrollbackShell.d.ts +54 -0
- package/dist/ui/ScrollbackShell.js +15 -0
- package/dist/ui/StatusBar.d.ts +5 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.d.ts +14 -0
- package/dist/ui/Transcript.js +14 -2
- package/dist/ui/entries/ServiceCard.d.ts +17 -1
- package/dist/ui/entries/ServiceCard.js +39 -4
- package/dist/ui/frame-differ.d.ts +12 -0
- package/dist/ui/layout/status-layout.d.ts +13 -1
- package/dist/ui/layout/status-layout.js +10 -0
- package/dist/ui/layout/virtual-window.js +14 -1
- package/dist/ui/overlays/HelpOverlay.d.ts +1 -1
- package/dist/ui/overlays/HelpOverlay.js +21 -3
- package/dist/ui/run-status-row.d.ts +12 -0
- package/dist/ui/screen.d.ts +29 -0
- package/dist/ui/screen.js +40 -0
- package/dist/ui/scrollback/LiveTail.d.ts +64 -0
- package/dist/ui/scrollback/LiveTail.js +27 -0
- package/dist/ui/scrollback/band.d.ts +105 -0
- package/dist/ui/scrollback/band.js +109 -0
- package/dist/ui/scrollback/budget.d.ts +72 -0
- package/dist/ui/scrollback/budget.js +81 -0
- package/dist/ui/scrollback/canvas.d.ts +99 -0
- package/dist/ui/scrollback/canvas.js +113 -0
- package/dist/ui/scrollback/cursor-probe.d.ts +71 -0
- package/dist/ui/scrollback/cursor-probe.js +133 -0
- package/dist/ui/scrollback/drain.d.ts +32 -0
- package/dist/ui/scrollback/drain.js +64 -0
- package/dist/ui/scrollback/exit-summary.d.ts +23 -0
- package/dist/ui/scrollback/exit-summary.js +25 -0
- package/dist/ui/scrollback/limits.d.ts +74 -0
- package/dist/ui/scrollback/limits.js +74 -0
- package/dist/ui/scrollback/live-preview.d.ts +26 -0
- package/dist/ui/scrollback/live-preview.js +106 -0
- package/dist/ui/scrollback/renderer.d.ts +38 -0
- package/dist/ui/scrollback/renderer.js +158 -0
- package/dist/ui/scrollback/scrollback-writer.d.ts +162 -0
- package/dist/ui/scrollback/scrollback-writer.js +549 -0
- package/dist/ui/scrollback/settled-boundary.d.ts +42 -0
- package/dist/ui/scrollback/settled-boundary.js +106 -0
- package/dist/ui/scrollback/use-scrollback-view.d.ts +103 -0
- package/dist/ui/scrollback/use-scrollback-view.js +294 -0
- package/dist/ui/scrollback/writer-shapes.d.ts +127 -0
- package/dist/ui/scrollback/writer-shapes.js +159 -0
- package/dist/ui/stdout-frame-writer.js +12 -2
- package/dist/ui/terminal-plan.d.ts +52 -0
- package/dist/ui/terminal-plan.js +49 -0
- package/dist/ui/transcript-text.d.ts +11 -0
- package/dist/ui/transcript-text.js +21 -7
- package/dist/ui/use-startup-notices.d.ts +12 -1
- package/dist/ui/use-startup-notices.js +28 -2
- package/package.json +3 -3
- package/skills/project-indexer/SKILL.md +163 -163
- package/skills/project-indexer/templates/cleancode-overrides.md +46 -46
- package/skills/project-indexer/templates/cleancode-template.md +94 -94
- package/skills/project-indexer/templates/config-template.md +69 -69
- package/skills/project-indexer/templates/index-template.md +129 -129
- package/skills/skill-creator/SKILL.md +96 -96
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte shapes of the native-scroll writer (tui-native-terminal-scroll §4.3 /
|
|
3
|
+
* Appendix A). Stateless string assembly only — the state machine that decides
|
|
4
|
+
* WHICH shape to emit lives in `scrollback-writer.ts`.
|
|
5
|
+
*
|
|
6
|
+
* THE WHITELIST (I-S5 / I-S6). Every byte produced here is drawn from: SGR, CUU,
|
|
7
|
+
* CUD, CR, CRLF, ED0, EL0, DEC 2026 and DECTCEM. There is no absolute CUP, no
|
|
8
|
+
* scroll region, no `CSI S/T`, no `ED2/ED3`. Relative addressing is what makes the
|
|
9
|
+
* geometry independent of the terminal scrolling underneath it (spec appendix B).
|
|
10
|
+
*
|
|
11
|
+
* Each row is `SGR0 · text · SGR0`: the leading reset stops one row's style leaking
|
|
12
|
+
* into the next, and the trailing one stops a background-colour-erase terminal from
|
|
13
|
+
* painting the following ED0/EL0 in the row's last colour.
|
|
14
|
+
*/
|
|
15
|
+
import stringWidth from 'string-width';
|
|
16
|
+
import stripAnsi from 'strip-ansi';
|
|
17
|
+
const CSI = '\x1b[';
|
|
18
|
+
export const SGR0 = `${CSI}0m`;
|
|
19
|
+
const ED0 = `${CSI}J`;
|
|
20
|
+
const EL0 = `${CSI}K`;
|
|
21
|
+
const SYNC_BEGIN = `${CSI}?2026h`;
|
|
22
|
+
const SYNC_END = `${CSI}?2026l`;
|
|
23
|
+
const SHOW_CURSOR = `${CSI}?25h`;
|
|
24
|
+
const OSC8_CLOSE = '\x1b]8;;\x07';
|
|
25
|
+
const up = (n) => (n > 0 ? `${CSI}${n}A` : '');
|
|
26
|
+
const down = (n) => (n > 0 ? `${CSI}${n}B` : '');
|
|
27
|
+
const row = (text) => SGR0 + text + SGR0 + '\r\n';
|
|
28
|
+
const rows = (list) => list.map(row).join('');
|
|
29
|
+
const wrapSync = (payload, sync) => sync ? SYNC_BEGIN + payload + SYNC_END : payload;
|
|
30
|
+
/** First write of a session: the cursor is already on a fresh line at column 0. */
|
|
31
|
+
export function seedBytes(input) {
|
|
32
|
+
const lead = input.reseed ? '\r\n' : '';
|
|
33
|
+
return wrapSync(lead + '\r' + SGR0 + rows(input.statics) + rows(input.lines), input.sync);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* CUD(n): move down n rows WITHOUT writing a single cell.
|
|
37
|
+
*
|
|
38
|
+
* This is how the canvas spends its `gap` (D3). A blank row that is never written
|
|
39
|
+
* cannot be pushed into the scrollback by a later overflow, which is the whole
|
|
40
|
+
* reason the gap is not a run of `\r\n` (I-B3). The sequence is already whitelisted
|
|
41
|
+
* (`diffBytes` uses it to step between changed rows); this is the same CUD used to
|
|
42
|
+
* skip empty space instead.
|
|
43
|
+
*/
|
|
44
|
+
export function skipRows(rows) {
|
|
45
|
+
return down(Math.max(0, Math.floor(Number.isFinite(rows) ? rows : 0)));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* First canvas batch (or the first one after a reset): the screen below the cursor
|
|
49
|
+
* is assumed empty, so there is nothing to erase — only `[gap][content][region]` to
|
|
50
|
+
* write, ending with the cursor parked on the last row.
|
|
51
|
+
*/
|
|
52
|
+
export function canvasSeedBytes(input) {
|
|
53
|
+
const lead = input.reseed ? '\r\n' : '';
|
|
54
|
+
const skip = skipRows(input.downToTop) + skipRows(input.gap);
|
|
55
|
+
return wrapSync(lead + '\r' + SGR0 + skip + rows(input.held) + rows(input.lines), input.sync);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Redraw the whole canvas: erase the previous one, then write `[gap][content][region]`.
|
|
59
|
+
*
|
|
60
|
+
* ORDER IS SEMANTICS: `ED0`, then the gap, then the content, then the region. Moving
|
|
61
|
+
* the gap after the content puts the blank space back between the reply and the
|
|
62
|
+
* composer, which is the exact defect this feature fixes (M1 in the spec).
|
|
63
|
+
*
|
|
64
|
+
* `held` is the FULL content sequence, not the part that will still be on screen: the
|
|
65
|
+
* rows that do not fit are supposed to leave through the bottom, and the terminal can
|
|
66
|
+
* only scroll away what it was given.
|
|
67
|
+
*/
|
|
68
|
+
export function canvasBytes(input) {
|
|
69
|
+
return wrapSync(up(input.eraseUp) + '\r' + SGR0 + ED0 + skipRows(input.gap) +
|
|
70
|
+
rows(input.held) + rows(input.lines), input.sync);
|
|
71
|
+
}
|
|
72
|
+
/** Erase exactly the previous live region, print static rows, redraw the region. */
|
|
73
|
+
export function fullBytes(input) {
|
|
74
|
+
return wrapSync(up(input.eraseUp) + '\r' + SGR0 + ED0 + rows(input.statics) + rows(input.lines), input.sync);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fill the screen down to the LAST row before the first batch (D4 rung 2 / §4.3).
|
|
78
|
+
*
|
|
79
|
+
* This is the fallback for a terminal that never answers `ESC[6n`: from any row, a
|
|
80
|
+
* run of `rows - 1` line feeds is guaranteed to leave the cursor on the bottom row
|
|
81
|
+
* (extra feeds only scroll, which is why the count is the caller's business). It is
|
|
82
|
+
* written ONCE, before anything else, so the rows it scrolls away are the host's old
|
|
83
|
+
* output — which is still in the scrollback — and never this session's content.
|
|
84
|
+
*
|
|
85
|
+
* The argument is the NUMBER OF LINE FEEDS, not the terminal height: `cli.tsx` passes
|
|
86
|
+
* `rows - 1`. Naming it after the height instead made the caller subtract twice and
|
|
87
|
+
* left the cursor one row short of the bottom.
|
|
88
|
+
*/
|
|
89
|
+
export function fillBytes(count) {
|
|
90
|
+
return '\r\n'.repeat(Math.max(0, Math.floor(count)));
|
|
91
|
+
}
|
|
92
|
+
/** Same height, no static rows: rewrite only the rows that changed. `''` if none did. */
|
|
93
|
+
export function diffBytes(input) {
|
|
94
|
+
const { lines, prev } = input;
|
|
95
|
+
const height = lines.length;
|
|
96
|
+
let out = '';
|
|
97
|
+
let cursor = -1;
|
|
98
|
+
for (let i = 0; i < height; i += 1) {
|
|
99
|
+
if (lines[i] === prev[i])
|
|
100
|
+
continue;
|
|
101
|
+
// Paint, THEN clear the tail: never ESC[2K first, which would blank the row
|
|
102
|
+
// the user is looking at for the instant between the two writes.
|
|
103
|
+
out += (cursor < 0 ? up(height - i) : down(i - cursor)) + '\r' + SGR0 + lines[i] + SGR0 + EL0;
|
|
104
|
+
cursor = i;
|
|
105
|
+
}
|
|
106
|
+
if (cursor < 0)
|
|
107
|
+
return '';
|
|
108
|
+
// Park on the row below the region, column 0 (I-S4).
|
|
109
|
+
return wrapSync(out + down(height - cursor) + '\r', input.sync);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Exit: erase the live region, flush leftover static rows, print the summary.
|
|
113
|
+
* `eraseUp < 0` means the geometry is unknown (a foreign write intervened), in
|
|
114
|
+
* which case the only safe move is to start on a fresh line and erase nothing.
|
|
115
|
+
*/
|
|
116
|
+
export function finalizeBytes(input) {
|
|
117
|
+
const head = input.eraseUp < 0 ? '\r\n' : up(input.eraseUp) + '\r' + SGR0 + ED0;
|
|
118
|
+
return head + rows(input.statics) + rows(input.summary) + SHOW_CURSOR;
|
|
119
|
+
}
|
|
120
|
+
const ESCAPE = /(\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\))/;
|
|
121
|
+
const segmenter = typeof Intl !== 'undefined' && 'Segmenter' in Intl
|
|
122
|
+
? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
|
|
123
|
+
: null;
|
|
124
|
+
function graphemes(text) {
|
|
125
|
+
return segmenter ? Array.from(segmenter.segment(text), (part) => part.segment) : Array.from(text);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Cut a row to `maxCols` DISPLAY columns (I-S13), keeping SGR / OSC 8 intact.
|
|
129
|
+
*
|
|
130
|
+
* Returns the input unchanged (same reference) when it already fits, which is the
|
|
131
|
+
* overwhelmingly common case. Width is measured the way Ink measures it
|
|
132
|
+
* (`string-width` over the ANSI-stripped text): that is the number Ink used to
|
|
133
|
+
* wrap, so a disagreement with the terminal is the only way a row can exceed it.
|
|
134
|
+
*/
|
|
135
|
+
export function clipLine(line, maxCols) {
|
|
136
|
+
if (maxCols < 1)
|
|
137
|
+
return { text: '', clipped: line.length > 0 };
|
|
138
|
+
if (stringWidth(stripAnsi(line)) <= maxCols)
|
|
139
|
+
return { text: line, clipped: false };
|
|
140
|
+
let out = '';
|
|
141
|
+
let width = 0;
|
|
142
|
+
let linkOpen = false;
|
|
143
|
+
for (const part of line.split(ESCAPE)) {
|
|
144
|
+
if (part.startsWith('\x1b')) {
|
|
145
|
+
out += part;
|
|
146
|
+
if (part.startsWith('\x1b]8;'))
|
|
147
|
+
linkOpen = !/^\x1b\]8;[^;]*;(?:\x07|\x1b\\)$/.test(part);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
for (const glyph of graphemes(part)) {
|
|
151
|
+
const glyphWidth = stringWidth(glyph);
|
|
152
|
+
if (width + glyphWidth > maxCols)
|
|
153
|
+
return { text: out + SGR0 + (linkOpen ? OSC8_CLOSE : ''), clipped: true };
|
|
154
|
+
out += glyph;
|
|
155
|
+
width += glyphWidth;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { text: out, clipped: false };
|
|
159
|
+
}
|
|
@@ -97,7 +97,14 @@ export function wrapStdoutForFrames(stdout, differ) {
|
|
|
97
97
|
}
|
|
98
98
|
return writeReal(out, encoding, cb);
|
|
99
99
|
};
|
|
100
|
-
|
|
100
|
+
// Optional differ hooks keep the full-screen path byte-identical: a differ that
|
|
101
|
+
// does not implement them is invalidated exactly as before.
|
|
102
|
+
const onResize = () => {
|
|
103
|
+
if (differ.onResize)
|
|
104
|
+
differ.onResize();
|
|
105
|
+
else
|
|
106
|
+
differ.invalidate();
|
|
107
|
+
};
|
|
101
108
|
stdout.on('resize', onResize);
|
|
102
109
|
let disposed = false;
|
|
103
110
|
const proxy = new Proxy(stdout, {
|
|
@@ -130,7 +137,10 @@ export function wrapStdoutForFrames(stdout, differ) {
|
|
|
130
137
|
return true;
|
|
131
138
|
},
|
|
132
139
|
writeForeign(text) {
|
|
133
|
-
differ.
|
|
140
|
+
if (differ.cursorNeutralWrite)
|
|
141
|
+
differ.cursorNeutralWrite();
|
|
142
|
+
else
|
|
143
|
+
differ.invalidate();
|
|
134
144
|
if (disposed || text.length === 0)
|
|
135
145
|
return;
|
|
136
146
|
writeReal(text);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal plan — the ONE decision point for how the interactive TUI occupies the
|
|
3
|
+
* terminal (tui-native-terminal-scroll §4.1 / D10).
|
|
4
|
+
*
|
|
5
|
+
* `cli.tsx::runInteractive` used to derive `wantMouse`, `mouseOn`, `wantSelect`,
|
|
6
|
+
* `pasteOn` and `enhancedKeysOn` inline, each with its own comment about why the
|
|
7
|
+
* others must agree. Adding a second screen mode multiplies that surface, so the
|
|
8
|
+
* agreement is computed here, once, and the call site only READS the result.
|
|
9
|
+
*
|
|
10
|
+
* PURE: no I/O, no `process` access, so every row of the rules below is a table
|
|
11
|
+
* test with plain literals.
|
|
12
|
+
*
|
|
13
|
+
* I-S9 · `app` reproduces the pre-feature expressions term for term. The
|
|
14
|
+
* regression test diffs this function against a verbatim copy of those
|
|
15
|
+
* expressions, because "the same by inspection" is how mouse and paste drifted
|
|
16
|
+
* apart the last time.
|
|
17
|
+
*/
|
|
18
|
+
import type { ScrollMode } from '../config/schema.js';
|
|
19
|
+
export type { ScrollMode } from '../config/schema.js';
|
|
20
|
+
export interface TerminalPlanInput {
|
|
21
|
+
/** The resolved `scrollMode` config value. */
|
|
22
|
+
scrollMode: ScrollMode;
|
|
23
|
+
configMouse: boolean;
|
|
24
|
+
configMouseSelect: boolean;
|
|
25
|
+
configPaste: boolean;
|
|
26
|
+
configKeyboardEnhancement: boolean;
|
|
27
|
+
configDiffRender: boolean;
|
|
28
|
+
stdinIsTTY: boolean;
|
|
29
|
+
stdoutIsTTY: boolean;
|
|
30
|
+
platform: NodeJS.Platform;
|
|
31
|
+
/** Measured on win32 (`forceVtInputForThisConsole`); irrelevant elsewhere. */
|
|
32
|
+
vtInputSupported: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface TerminalPlan {
|
|
35
|
+
scrollMode: ScrollMode;
|
|
36
|
+
/** `app`: enter the alternate screen. `native`: never. */
|
|
37
|
+
altScreen: boolean;
|
|
38
|
+
/** Whether the terminal is asked for SGR mouse reports (and a mouse filter is wanted). */
|
|
39
|
+
mouseReporting: boolean;
|
|
40
|
+
/** Application-drawn drag selection. Always `false` in native mode (the terminal selects). */
|
|
41
|
+
dragSelect: boolean;
|
|
42
|
+
bracketedPaste: boolean;
|
|
43
|
+
enhancedKeys: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Which frame writer fronts Ink's stdout. `native` ALWAYS needs the scrollback
|
|
46
|
+
* writer, even with `diffRender` off: static rows are injected through it.
|
|
47
|
+
*/
|
|
48
|
+
differ: 'fullscreen' | 'scrollback' | 'none';
|
|
49
|
+
/** Native + `diffRender:false` — every frame rewrites the whole live region. */
|
|
50
|
+
fullEveryFrame: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function resolveTerminalPlan(input: TerminalPlanInput): TerminalPlan;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal plan — the ONE decision point for how the interactive TUI occupies the
|
|
3
|
+
* terminal (tui-native-terminal-scroll §4.1 / D10).
|
|
4
|
+
*
|
|
5
|
+
* `cli.tsx::runInteractive` used to derive `wantMouse`, `mouseOn`, `wantSelect`,
|
|
6
|
+
* `pasteOn` and `enhancedKeysOn` inline, each with its own comment about why the
|
|
7
|
+
* others must agree. Adding a second screen mode multiplies that surface, so the
|
|
8
|
+
* agreement is computed here, once, and the call site only READS the result.
|
|
9
|
+
*
|
|
10
|
+
* PURE: no I/O, no `process` access, so every row of the rules below is a table
|
|
11
|
+
* test with plain literals.
|
|
12
|
+
*
|
|
13
|
+
* I-S9 · `app` reproduces the pre-feature expressions term for term. The
|
|
14
|
+
* regression test diffs this function against a verbatim copy of those
|
|
15
|
+
* expressions, because "the same by inspection" is how mouse and paste drifted
|
|
16
|
+
* apart the last time.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveTerminalPlan(input) {
|
|
19
|
+
const bothTty = input.stdinIsTTY && input.stdoutIsTTY;
|
|
20
|
+
// Paste and keyboard enhancement ride the stdin filter and say nothing about
|
|
21
|
+
// scrolling, so the two modes agree on them (rule 3).
|
|
22
|
+
const bracketedPaste = input.configPaste && input.stdinIsTTY;
|
|
23
|
+
const enhancedKeys = input.configKeyboardEnhancement &&
|
|
24
|
+
input.stdinIsTTY &&
|
|
25
|
+
(input.platform !== 'win32' || input.vtInputSupported);
|
|
26
|
+
if (input.scrollMode === 'native') {
|
|
27
|
+
return {
|
|
28
|
+
scrollMode: 'native',
|
|
29
|
+
altScreen: false,
|
|
30
|
+
mouseReporting: false,
|
|
31
|
+
dragSelect: false,
|
|
32
|
+
bracketedPaste,
|
|
33
|
+
enhancedKeys,
|
|
34
|
+
differ: 'scrollback',
|
|
35
|
+
fullEveryFrame: !input.configDiffRender,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const mouseReporting = input.configMouse && bothTty && input.vtInputSupported;
|
|
39
|
+
return {
|
|
40
|
+
scrollMode: 'app',
|
|
41
|
+
altScreen: true,
|
|
42
|
+
mouseReporting,
|
|
43
|
+
dragSelect: mouseReporting && input.configMouseSelect,
|
|
44
|
+
bracketedPaste,
|
|
45
|
+
enhancedKeys,
|
|
46
|
+
differ: input.configDiffRender ? 'fullscreen' : 'none',
|
|
47
|
+
fullEveryFrame: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -26,5 +26,16 @@ export interface TranscriptTextOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
droppedEntries?: number;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The three figures every session summary carries: turns, tokens, cost.
|
|
31
|
+
*
|
|
32
|
+
* One implementation for the exit replay and the native-scroll exit summary
|
|
33
|
+
* (tui-native-terminal-scroll §4.8), so the two can never disagree.
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatSessionStats(entries: readonly Entry[], usageTotal: UsageTotal, g: Pick<Glyphs, 'arrowUp' | 'arrowDown'>): {
|
|
36
|
+
turns: string;
|
|
37
|
+
tokens: string;
|
|
38
|
+
cost: string;
|
|
39
|
+
};
|
|
29
40
|
/** Render the whole session (summary line + entries) as plain text. */
|
|
30
41
|
export declare function renderTranscriptText(entries: Entry[], opts: TranscriptTextOptions): string;
|
|
@@ -198,21 +198,35 @@ function renderEntry(entry, glyphs) {
|
|
|
198
198
|
return [];
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
/**
|
|
202
|
-
|
|
201
|
+
/**
|
|
202
|
+
* The three figures every session summary carries: turns, tokens, cost.
|
|
203
|
+
*
|
|
204
|
+
* One implementation for the exit replay and the native-scroll exit summary
|
|
205
|
+
* (tui-native-terminal-scroll §4.8), so the two can never disagree.
|
|
206
|
+
*/
|
|
207
|
+
export function formatSessionStats(entries, usageTotal, g) {
|
|
203
208
|
const turns = entries.filter((e) => e.kind === 'user').length;
|
|
204
|
-
const g = opts.glyphs;
|
|
205
209
|
// THE SAME PROMPT-SIDE TOTAL THE STATUS BAR SHOWS (context-usage-gauge-accuracy
|
|
206
210
|
// §3.6). This line is what a user compares against the bar they were watching a
|
|
207
211
|
// second earlier, so a different unit here would look like the exit summary
|
|
208
212
|
// losing tokens.
|
|
209
|
-
const tokens = `${formatTokens(promptTokensOf(
|
|
213
|
+
const tokens = `${formatTokens(promptTokensOf(usageTotal))}${g.arrowUp} ${formatTokens(usageTotal.outputTokens)}${g.arrowDown}`;
|
|
214
|
+
return {
|
|
215
|
+
turns: `${turns} ${turns === 1 ? 'turn' : 'turns'}`,
|
|
216
|
+
tokens,
|
|
217
|
+
cost: formatCost(usageTotal.costUsd),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** Render the whole session (summary line + entries) as plain text. */
|
|
221
|
+
export function renderTranscriptText(entries, opts) {
|
|
222
|
+
const g = opts.glyphs;
|
|
223
|
+
const stats = formatSessionStats(entries, opts.usageTotal, g);
|
|
210
224
|
const summary = [
|
|
211
225
|
'aragon',
|
|
212
226
|
`${opts.provider}:${opts.model}`,
|
|
213
|
-
|
|
214
|
-
tokens,
|
|
215
|
-
|
|
227
|
+
stats.turns,
|
|
228
|
+
stats.tokens,
|
|
229
|
+
stats.cost,
|
|
216
230
|
formatDuration(Math.max(0, opts.elapsedMs)),
|
|
217
231
|
].join(` ${g.midDot} `);
|
|
218
232
|
const max = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
@@ -53,6 +53,17 @@ export interface RetainNoticeOptions {
|
|
|
53
53
|
requested: number;
|
|
54
54
|
resolved: number;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The revision of the native-scroll notice a user must have seen for it to stay
|
|
58
|
+
* quiet (`state.json::scrollModeNoticeVersion`). BUMP IT WHENEVER THE ADVICE
|
|
59
|
+
* CHANGES, never for a rewording (tui-native-terminal-scroll D12).
|
|
60
|
+
*/
|
|
61
|
+
export declare const SCROLL_MODE_NOTICE_VERSION = 1;
|
|
62
|
+
export declare function scrollModeNoticeText(): string;
|
|
63
|
+
export interface ScrollModeNoticeOptions {
|
|
64
|
+
/** Record that `SCROLL_MODE_NOTICE_VERSION` was shown (`state.json`). */
|
|
65
|
+
onSeen: () => void;
|
|
66
|
+
}
|
|
56
67
|
export interface MouseNoticeOptions {
|
|
57
68
|
/**
|
|
58
69
|
* Whether reporting is ACTUALLY in effect: a filter was installed AND the
|
|
@@ -98,4 +109,4 @@ export interface VtInputNoticeOptions {
|
|
|
98
109
|
*/
|
|
99
110
|
onSeen: () => void;
|
|
100
111
|
}
|
|
101
|
-
export declare function useStartupNotices(dispatch: (action: ViewAction) => void, mouseNotice?: MouseNoticeOptions, retainNotice?: RetainNoticeOptions, vtInputNotice?: VtInputNoticeOptions): void;
|
|
112
|
+
export declare function useStartupNotices(dispatch: (action: ViewAction) => void, mouseNotice?: MouseNoticeOptions, retainNotice?: RetainNoticeOptions, vtInputNotice?: VtInputNoticeOptions, scrollModeNotice?: ScrollModeNoticeOptions): void;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { useEffect, useRef } from 'react';
|
|
15
15
|
import { MODE_TOGGLE_KEYS } from '../agent/agent-mode.js';
|
|
16
16
|
import { readConfigFile } from '../config/store.js';
|
|
17
|
-
import { getMouseNoticeVersion, getVtInputNoticeVersion } from '../config/ui-state.js';
|
|
17
|
+
import { getMouseNoticeVersion, getScrollModeNoticeVersion, getVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
18
18
|
import { getLogger } from '../logging/logger.js';
|
|
19
19
|
/**
|
|
20
20
|
* The revision of the mouse notice a user must have seen for it to stay quiet
|
|
@@ -93,7 +93,17 @@ export function vtInputDeadNotice(nodeVersion) {
|
|
|
93
93
|
`as a PLAIN TAB - ${cost}${toggle}, and Shift+Up / Shift+Down to scroll. ` +
|
|
94
94
|
'Node 22.17.0+ (or 24.2.0+) restores both.');
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The revision of the native-scroll notice a user must have seen for it to stay
|
|
98
|
+
* quiet (`state.json::scrollModeNoticeVersion`). BUMP IT WHENEVER THE ADVICE
|
|
99
|
+
* CHANGES, never for a rewording (tui-native-terminal-scroll D12).
|
|
100
|
+
*/
|
|
101
|
+
export const SCROLL_MODE_NOTICE_VERSION = 1;
|
|
102
|
+
export function scrollModeNoticeText() {
|
|
103
|
+
return ('Scrolling now uses your terminal: wheel, scrollbar, selection and search all work as ' +
|
|
104
|
+
'usual. Prefer the old full-screen view? Run: aragon config set scrollMode app');
|
|
105
|
+
}
|
|
106
|
+
export function useStartupNotices(dispatch, mouseNotice, retainNotice, vtInputNotice, scrollModeNotice) {
|
|
97
107
|
// The sink gives up after repeated write failures (a read-only log directory,
|
|
98
108
|
// a full disk). Silently logging nothing is exactly the state a user must not
|
|
99
109
|
// discover later, from an empty file, while trying to explain a bug.
|
|
@@ -167,6 +177,22 @@ export function useStartupNotices(dispatch, mouseNotice, retainNotice, vtInputNo
|
|
|
167
177
|
});
|
|
168
178
|
vtNoticeRef.current?.onSeen();
|
|
169
179
|
}, [vtNodeVersion, dispatch]);
|
|
180
|
+
// The one-shot native-scroll notice (D12): a default-behaviour change has to be
|
|
181
|
+
// discoverable and reversible. `>=`, like the notices above: a state file written
|
|
182
|
+
// by a newer build has already told this user.
|
|
183
|
+
const scrollNoticeRef = useRef(scrollModeNotice);
|
|
184
|
+
scrollNoticeRef.current = scrollModeNotice;
|
|
185
|
+
const scrollNoticeWanted = scrollModeNotice !== undefined;
|
|
186
|
+
const scrollNoticeShown = useRef(false);
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (!scrollNoticeWanted || scrollNoticeShown.current)
|
|
189
|
+
return;
|
|
190
|
+
if (getScrollModeNoticeVersion() >= SCROLL_MODE_NOTICE_VERSION)
|
|
191
|
+
return;
|
|
192
|
+
scrollNoticeShown.current = true;
|
|
193
|
+
dispatch({ type: 'notice', level: 'info', text: scrollModeNoticeText() });
|
|
194
|
+
scrollNoticeRef.current?.onSeen();
|
|
195
|
+
}, [scrollNoticeWanted, dispatch]);
|
|
170
196
|
// A `warn` notice rather than a toast: it is about a value the user typed, so
|
|
171
197
|
// it belongs in the transcript where it can be scrolled back to, not in a row
|
|
172
198
|
// that expires. DEPENDS ON THE TWO SCALARS, never on the options object —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.32",
|
|
4
4
|
"description": "AragonAgent CLI — a Claude-Code / Codex-style interactive terminal UI (TUI) on top of the @aragon-agent/core engine. Installable and runnable via npm / npx.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AragonAgent contributors",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"prepublishOnly": "npm run build"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aragon-agent/core": "^0.2.
|
|
59
|
+
"@aragon-agent/core": "^0.2.44",
|
|
60
60
|
"chalk": "^5.6.2",
|
|
61
61
|
"cli-highlight": "^2.0.0",
|
|
62
62
|
"commander": "^12.1.0",
|
|
@@ -76,6 +76,6 @@
|
|
|
76
76
|
"@types/react": "^18.3.0",
|
|
77
77
|
"ink-testing-library": "^4.0.0",
|
|
78
78
|
"typescript": "^5.9.3",
|
|
79
|
-
"vitest": "
|
|
79
|
+
"vitest": "4.1.9"
|
|
80
80
|
}
|
|
81
81
|
}
|