@aragon-agent/cli 0.6.29 → 0.6.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +2123 -2103
  2. package/README.md +81 -14
  3. package/dist/agent/project-guidance-prompt.js +20 -20
  4. package/dist/cli.js +169 -56
  5. package/dist/commands/builtins.js +4 -0
  6. package/dist/commands/perf.d.ts +25 -1
  7. package/dist/commands/perf.js +26 -2
  8. package/dist/commands/registry.d.ts +6 -0
  9. package/dist/config/env.js +4 -0
  10. package/dist/config/load.d.ts +3 -1
  11. package/dist/config/load.js +12 -1
  12. package/dist/config/schema.d.ts +27 -0
  13. package/dist/config/schema.js +9 -0
  14. package/dist/config/ui-state.d.ts +9 -0
  15. package/dist/config/ui-state.js +9 -0
  16. package/dist/ui/App.d.ts +11 -1
  17. package/dist/ui/App.js +122 -62
  18. package/dist/ui/ScrollbackShell.d.ts +28 -0
  19. package/dist/ui/ScrollbackShell.js +10 -0
  20. package/dist/ui/frame-differ.d.ts +12 -0
  21. package/dist/ui/overlays/HelpOverlay.d.ts +1 -1
  22. package/dist/ui/overlays/HelpOverlay.js +21 -3
  23. package/dist/ui/screen.d.ts +16 -0
  24. package/dist/ui/screen.js +32 -0
  25. package/dist/ui/scrollback/budget.d.ts +43 -0
  26. package/dist/ui/scrollback/budget.js +44 -0
  27. package/dist/ui/scrollback/drain.d.ts +32 -0
  28. package/dist/ui/scrollback/drain.js +64 -0
  29. package/dist/ui/scrollback/exit-summary.d.ts +23 -0
  30. package/dist/ui/scrollback/exit-summary.js +25 -0
  31. package/dist/ui/scrollback/limits.d.ts +25 -0
  32. package/dist/ui/scrollback/limits.js +25 -0
  33. package/dist/ui/scrollback/live-preview.d.ts +26 -0
  34. package/dist/ui/scrollback/live-preview.js +106 -0
  35. package/dist/ui/scrollback/renderer.d.ts +38 -0
  36. package/dist/ui/scrollback/renderer.js +158 -0
  37. package/dist/ui/scrollback/scrollback-writer.d.ts +74 -0
  38. package/dist/ui/scrollback/scrollback-writer.js +263 -0
  39. package/dist/ui/scrollback/settled-boundary.d.ts +42 -0
  40. package/dist/ui/scrollback/settled-boundary.js +106 -0
  41. package/dist/ui/scrollback/use-scrollback-view.d.ts +92 -0
  42. package/dist/ui/scrollback/use-scrollback-view.js +263 -0
  43. package/dist/ui/scrollback/writer-shapes.d.ts +61 -0
  44. package/dist/ui/scrollback/writer-shapes.js +114 -0
  45. package/dist/ui/stdout-frame-writer.js +12 -2
  46. package/dist/ui/terminal-plan.d.ts +52 -0
  47. package/dist/ui/terminal-plan.js +49 -0
  48. package/dist/ui/transcript-text.d.ts +11 -0
  49. package/dist/ui/transcript-text.js +21 -7
  50. package/dist/ui/use-startup-notices.d.ts +12 -1
  51. package/dist/ui/use-startup-notices.js +28 -2
  52. package/package.json +2 -2
  53. package/skills/project-indexer/SKILL.md +163 -163
  54. package/skills/project-indexer/templates/cleancode-overrides.md +46 -46
  55. package/skills/project-indexer/templates/cleancode-template.md +94 -94
  56. package/skills/project-indexer/templates/config-template.md +69 -69
  57. package/skills/project-indexer/templates/index-template.md +129 -129
  58. package/skills/skill-creator/SKILL.md +96 -96
@@ -34,7 +34,8 @@ export interface PerfSnapshot {
34
34
  heightsEstimated: number;
35
35
  cols: number;
36
36
  /** Geometry. */
37
- mode: 'fullscreen';
37
+ mode: 'fullscreen' | 'native';
38
+ /** Full-screen: the transcript viewport. Native: the live region height. */
38
39
  viewportRows: number;
39
40
  offset: number;
40
41
  /** Right-edge scrollbar diagnostics; read lazily so it reflects the live bridge. */
@@ -74,6 +75,29 @@ export declare function setPerfResetHook(fn: (() => void) | null): void;
74
75
  /** Called by `cli.tsx` after building the writer. Passing `null` detaches. */
75
76
  export declare function setFrameStatsProvider(fn: (() => FrameWriterStats) | null): void;
76
77
  export declare function readFrameStats(): FrameWriterStats | null;
78
+ /**
79
+ * Native-scroll writer counters (tui-native-terminal-scroll §5.1). A third
80
+ * module-level slot for the reason the one above records: the writer lives outside
81
+ * React. `guardHitsOutsideResize > 0` on a session with no resize is the defect
82
+ * signal (a live region reached the terminal height); `clippedRows > 0` means Ink
83
+ * and the terminal disagreed about a character's width (I-S13).
84
+ */
85
+ export interface ScrollbackPerf {
86
+ regionRows: number;
87
+ framesSeed: number;
88
+ framesDiff: number;
89
+ framesFull: number;
90
+ staticRowsWritten: number;
91
+ elidedEntries: number;
92
+ guardHits: number;
93
+ guardHitsOutsideResize: number;
94
+ clippedRows: number;
95
+ fallbacks: number;
96
+ resizes: number;
97
+ repaints: number;
98
+ }
99
+ /** Called by `cli.tsx` in native mode. Passing `null` detaches. */
100
+ export declare function setScrollbackStatsProvider(fn: (() => ScrollbackPerf) | null): void;
77
101
  /** Format a snapshot as the six-line block documented in §5.4 (plus the scrollbar line). */
78
102
  export declare function formatPerfReport(snap: PerfSnapshot): string;
79
103
  export declare const perfCommand: SlashCommand;
@@ -68,6 +68,24 @@ export function setFrameStatsProvider(fn) {
68
68
  export function readFrameStats() {
69
69
  return frameStats ? frameStats() : null;
70
70
  }
71
+ let scrollbackStats = null;
72
+ /** Called by `cli.tsx` in native mode. Passing `null` detaches. */
73
+ export function setScrollbackStatsProvider(fn) {
74
+ scrollbackStats = fn;
75
+ }
76
+ function formatScrollbackLine() {
77
+ const stats = scrollbackStats?.();
78
+ if (!stats)
79
+ return [];
80
+ return [
81
+ `scrollback region ${stats.regionRows} rows - batches seed ${stats.framesSeed}, ` +
82
+ `diff ${stats.framesDiff}, full ${stats.framesFull}, repaint ${stats.repaints} - ` +
83
+ `static rows ${stats.staticRowsWritten} - elided ${stats.elidedEntries}`,
84
+ ` guardHits ${stats.guardHits} (outside resize ${stats.guardHitsOutsideResize}) - ` +
85
+ `clippedRows ${stats.clippedRows} - fallbacks ${stats.fallbacks} - ` +
86
+ `resizes ${stats.resizes}`,
87
+ ];
88
+ }
71
89
  function mb(bytes) {
72
90
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
73
91
  }
@@ -107,7 +125,10 @@ export function formatPerfReport(snap) {
107
125
  const caches = renderCacheStats();
108
126
  const eco = snap.rung > 0 ? ' - eco' : '';
109
127
  const governor = snap.governorEnabled ? '' : ' - governor off';
110
- const scrollbar = snap.scrollbar ? [formatScrollbarLine(snap.scrollbar)] : [];
128
+ const native = snap.mode === 'native';
129
+ const scrollbar = native
130
+ ? ['scrollbar n/a (native scroll)']
131
+ : snap.scrollbar ? [formatScrollbarLine(snap.scrollbar)] : [];
111
132
  return [
112
133
  `render rung ${snap.rung} - interval ${snap.intervalMs}ms - last commit ` +
113
134
  `${Math.round(snap.lastCommitMs)}ms${eco}${governor}`,
@@ -118,8 +139,11 @@ export function formatPerfReport(snap) {
118
139
  `caches md ${caches.markdownEntries} (${mb(caches.markdownBytes)}) - ` +
119
140
  `hl ${caches.highlightEntries} (${mb(caches.highlightBytes)}) - ` +
120
141
  `lines ${caches.lineEntries}`,
121
- `mode ${snap.mode} - viewport ${snap.viewportRows} rows - offset ${snap.offset}`,
142
+ native
143
+ ? `mode native - live region ${snap.viewportRows} rows`
144
+ : `mode ${snap.mode} - viewport ${snap.viewportRows} rows - offset ${snap.offset}`,
122
145
  formatWriterLine(),
146
+ ...formatScrollbackLine(),
123
147
  ...scrollbar,
124
148
  ].join('\n');
125
149
  }
@@ -100,6 +100,12 @@ export interface CommandContext {
100
100
  * missing, which is the same reason `update` above it is optional.
101
101
  */
102
102
  mouse?: MouseCommandPort;
103
+ /**
104
+ * True in native-scroll mode (tui-native-terminal-scroll): the terminal owns
105
+ * scrolling and selection, so there is no capture to toggle. Lets `/mouse` say so
106
+ * instead of the generic "not captured" answer.
107
+ */
108
+ nativeScroll?: boolean;
103
109
  /**
104
110
  * The foreign-write door, for `/copy`'s OSC 52 half (§4.4.5).
105
111
  *
@@ -392,6 +392,10 @@ export function readEnvConfig() {
392
392
  partial.diffRender =
393
393
  diffRender === '1' || diffRender === 'true' || diffRender === 'on' || diffRender === 'yes';
394
394
  }
395
+ // Invalid values are ignored, like every other ARAGON_* key.
396
+ const scrollMode = process.env.ARAGON_SCROLL_MODE?.trim().toLowerCase();
397
+ if (scrollMode === 'native' || scrollMode === 'app')
398
+ partial.scrollMode = scrollMode;
395
399
  const syncOutput = process.env.ARAGON_SYNC_OUTPUT?.trim().toLowerCase();
396
400
  if (syncOutput !== undefined && syncOutput.length > 0) {
397
401
  partial.syncOutput =
@@ -5,7 +5,7 @@
5
5
  * layers, `idleTimeout` is re-derived to be strictly ≥ `toolTimeout` so a
6
6
  * legitimately long single tool run can never be watchdog-killed.
7
7
  */
8
- import { type CliConfig } from './schema.js';
8
+ import { type CliConfig, type ScrollMode } from './schema.js';
9
9
  import type { ModelRole } from './model-profiles.js';
10
10
  export interface CliFlags {
11
11
  provider?: string;
@@ -149,6 +149,8 @@ export interface CliFlags {
149
149
  * all — i.e. the documented escape hatch would quietly un-set itself.
150
150
  */
151
151
  diffRender?: boolean;
152
+ /** `--scroll-mode <native|app>`; already validated by `cli.tsx` (exit 2 on junk). */
153
+ scrollMode?: ScrollMode;
152
154
  /** `--sync-output` ⇒ true, `--no-sync-output` ⇒ false, absent ⇒ undefined (§5.2). */
153
155
  syncOutput?: boolean;
154
156
  /**
@@ -6,7 +6,7 @@
6
6
  * legitimately long single tool run can never be watchdog-killed.
7
7
  */
8
8
  import process from 'node:process';
9
- import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, legacyTeamCeilingPatch, parseThresholdInput, } from './schema.js';
9
+ import { DEFAULT_CONFIG, DEFAULT_SCROLL_MODE, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollMode, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, legacyTeamCeilingPatch, parseThresholdInput, } from './schema.js';
10
10
  import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
11
11
  import { readConfigFile, updatePersistedConfig } from './store.js';
12
12
  import { projectModelProfiles, resolveModelProfileState, resolveModelRoleKey, } from './model-profile-resolution.js';
@@ -334,6 +334,16 @@ function resolveDiffRender(flags, env, file) {
334
334
  return file.diffRender;
335
335
  return DEFAULT_CONFIG.diffRender;
336
336
  }
337
+ /** flags > env > file > default, like `resolveDiffRender` (tui-native-terminal-scroll §6.1). */
338
+ function resolveScrollMode(flags, env, file) {
339
+ if (flags.scrollMode !== undefined)
340
+ return flags.scrollMode;
341
+ if (env.scrollMode !== undefined)
342
+ return env.scrollMode;
343
+ if (file.scrollMode !== undefined)
344
+ return clampScrollMode(file.scrollMode, DEFAULT_SCROLL_MODE);
345
+ return DEFAULT_SCROLL_MODE;
346
+ }
337
347
  /** Same ladder for the DEC 2026 envelope (§5.1). */
338
348
  function resolveSyncOutput(flags, env, file) {
339
349
  if (flags.syncOutput !== undefined)
@@ -638,6 +648,7 @@ export function loadConfig(flags = {}, options = {}) {
638
648
  renderGovernor: resolveRenderGovernor(flags, env.partial, file),
639
649
  maxRenderIntervalMs: clampMaxRenderInterval(pick(flags.maxRenderInterval, env.partial.maxRenderIntervalMs, file.maxRenderIntervalMs), DEFAULT_CONFIG.maxRenderIntervalMs),
640
650
  diffRender: resolveDiffRender(flags, env.partial, file),
651
+ scrollMode: resolveScrollMode(flags, env.partial, file),
641
652
  syncOutput: resolveSyncOutput(flags, env.partial, file),
642
653
  confirmTools,
643
654
  toolTimeoutMs,
@@ -39,6 +39,17 @@ export declare function isThemeName(v: unknown): v is ThemeName;
39
39
  * and would not match `clampThinkingLevel` / `clampTranscriptWindow`.
40
40
  */
41
41
  export declare function clampTheme(v: unknown, fallback: ThemeName): ThemeName;
42
+ export declare const SCROLL_MODES: readonly ["native", "app"];
43
+ /**
44
+ * How the interactive TUI scrolls (tui-native-terminal-scroll D1).
45
+ *
46
+ * `native` prints settled conversation into the host terminal's own scrollback and
47
+ * keeps only a bounded live region on screen; `app` is the original full-screen
48
+ * alternate-screen layout with application-owned scrolling.
49
+ */
50
+ export type ScrollMode = (typeof SCROLL_MODES)[number];
51
+ export declare const DEFAULT_SCROLL_MODE: ScrollMode;
52
+ export declare function clampScrollMode(v: unknown, fallback: ScrollMode): ScrollMode;
42
53
  export declare const DENSITY_MODES: readonly ["comfortable", "compact"];
43
54
  export type DensityMode = (typeof DENSITY_MODES)[number];
44
55
  export declare function clampDensity(v: unknown, fallback: DensityMode): DensityMode;
@@ -864,6 +875,8 @@ export interface PersistedConfig {
864
875
  maxRenderIntervalMs: number;
865
876
  /** Repaint only the frame rows that changed (tui-input-flicker-fix §5.1). */
866
877
  diffRender: boolean;
878
+ /** Native terminal scrolling vs the full-screen app layout (tui-native-terminal-scroll D1). */
879
+ scrollMode: ScrollMode;
867
880
  /** Wrap each repaint batch in DEC 2026 synchronized update (§5.1). */
868
881
  syncOutput: boolean;
869
882
  confirmTools: boolean;
@@ -1068,6 +1081,20 @@ export interface CliConfig {
1068
1081
  * with no compile error and no runtime error.
1069
1082
  */
1070
1083
  diffRender: boolean;
1084
+ /**
1085
+ * Native terminal scrolling vs the full-screen app layout
1086
+ * (tui-native-terminal-scroll D1).
1087
+ *
1088
+ * IT MUST EXIST HERE AND NOT ONLY IN `PersistedConfig`, for the reason
1089
+ * `diffRender` above records: `runInteractive` reads `config.scrollMode`, where
1090
+ * `config` is a `CliConfig`, so a key added to the persisted shape alone would
1091
+ * round-trip through `config set` and never reach the screen plan.
1092
+ *
1093
+ * OPTIONAL ON THIS SHAPE ONLY so the many hand-built `CliConfig` fixtures in the
1094
+ * test suite stay valid without edits (I-S9); `loadConfig` always sets it and
1095
+ * the one reader (`runInteractive`) falls back to `DEFAULT_SCROLL_MODE`.
1096
+ */
1097
+ scrollMode?: ScrollMode;
1071
1098
  /** Wrap each repaint batch in DEC 2026 synchronized update (§5.1). */
1072
1099
  syncOutput: boolean;
1073
1100
  confirmTools: boolean;
@@ -69,6 +69,14 @@ export function clampTheme(v, fallback) {
69
69
  // ---------------------------------------------------------------------------
70
70
  // Density / hints (v0.4.0)
71
71
  // ---------------------------------------------------------------------------
72
+ export const SCROLL_MODES = ['native', 'app'];
73
+ export const DEFAULT_SCROLL_MODE = 'native';
74
+ export function clampScrollMode(v, fallback) {
75
+ if (typeof v !== 'string')
76
+ return fallback;
77
+ const normalized = v.trim().toLowerCase();
78
+ return normalized === 'native' || normalized === 'app' ? normalized : fallback;
79
+ }
72
80
  export const DENSITY_MODES = ['comfortable', 'compact'];
73
81
  export function clampDensity(v, fallback) {
74
82
  return v === 'comfortable' || v === 'compact' ? v : fallback;
@@ -970,6 +978,7 @@ export const DEFAULT_CONFIG = {
970
978
  renderGovernor: DEFAULT_RENDER_GOVERNOR,
971
979
  maxRenderIntervalMs: DEFAULT_MAX_RENDER_INTERVAL_MS,
972
980
  diffRender: DEFAULT_DIFF_RENDER,
981
+ scrollMode: DEFAULT_SCROLL_MODE,
973
982
  syncOutput: DEFAULT_SYNC_OUTPUT,
974
983
  confirmTools: false,
975
984
  toolTimeoutMs: DEFAULT_TOOL_TIMEOUT_MS,
@@ -84,6 +84,12 @@ export interface UiState {
84
84
  * older file, which is the behaviour wanted here anyway.
85
85
  */
86
86
  vtInputNoticeVersion: number;
87
+ /**
88
+ * Which revision of the "scrolling now uses your terminal" notice this user has
89
+ * been shown (`0` = none; tui-native-terminal-scroll D12). A scalar beside the
90
+ * others for the same reasons the two above record, and NOT a schema bump.
91
+ */
92
+ scrollModeNoticeVersion: number;
87
93
  }
88
94
  /** Read the state, caching it for the life of the process. Never throws. */
89
95
  export declare function loadUiState(): UiState;
@@ -98,6 +104,9 @@ export declare function setMouseNoticeVersion(version: number): void;
98
104
  /** `0` on a file written before the field existed — see the field's comment. */
99
105
  export declare function getVtInputNoticeVersion(): number;
100
106
  export declare function setVtInputNoticeVersion(version: number): void;
107
+ /** `0` on a file written before the field existed. */
108
+ export declare function getScrollModeNoticeVersion(): number;
109
+ export declare function setScrollModeNoticeVersion(version: number): void;
101
110
  /**
102
111
  * Migration-only: fill in fields that are still at their default, and never
103
112
  * overwrite a value already here. A second import — after a half-finished
@@ -37,6 +37,7 @@ const DEFAULT_UI_STATE = {
37
37
  mouseNoticeSeen: false,
38
38
  mouseNoticeVersion: 0,
39
39
  vtInputNoticeVersion: 0,
40
+ scrollModeNoticeVersion: 0,
40
41
  };
41
42
  let memory = null;
42
43
  /** Non-negative integer or `fallback`; shared by both numeric fields. */
@@ -69,6 +70,7 @@ function readFromDisk() {
69
70
  : DEFAULT_UI_STATE.mouseNoticeSeen,
70
71
  mouseNoticeVersion: coerceCount(parsed.mouseNoticeVersion, DEFAULT_UI_STATE.mouseNoticeVersion),
71
72
  vtInputNoticeVersion: coerceCount(parsed.vtInputNoticeVersion, DEFAULT_UI_STATE.vtInputNoticeVersion),
73
+ scrollModeNoticeVersion: coerceCount(parsed.scrollModeNoticeVersion, DEFAULT_UI_STATE.scrollModeNoticeVersion),
72
74
  };
73
75
  }
74
76
  catch {
@@ -142,6 +144,13 @@ export function getVtInputNoticeVersion() {
142
144
  export function setVtInputNoticeVersion(version) {
143
145
  update({ vtInputNoticeVersion: version });
144
146
  }
147
+ /** `0` on a file written before the field existed. */
148
+ export function getScrollModeNoticeVersion() {
149
+ return loadUiState().scrollModeNoticeVersion;
150
+ }
151
+ export function setScrollModeNoticeVersion(version) {
152
+ update({ scrollModeNoticeVersion: version });
153
+ }
145
154
  /**
146
155
  * Migration-only: fill in fields that are still at their default, and never
147
156
  * overwrite a value already here. A second import — after a half-finished
package/dist/ui/App.d.ts CHANGED
@@ -10,6 +10,8 @@ import type { AgentController } from '../agent/controller.js';
10
10
  import type { ConfirmRequest } from '../tools/index.js';
11
11
  import { type Entry, type Overlay, type ToastLevel } from '../agent/reducer.js';
12
12
  import type { UpdateBridge } from '../update/types.js';
13
+ import { type ScrollbackBridge } from './scrollback/use-scrollback-view.js';
14
+ import type { ScrollMode } from '../config/schema.js';
13
15
  import type { MouseSource } from '../input/stdin-filter.js';
14
16
  import { type PasteBridge } from '../input/limits.js';
15
17
  import type { CheckpointResult } from '../session/checkpoint.js';
@@ -86,6 +88,14 @@ export interface AppProps {
86
88
  */
87
89
  pasteBridge?: PasteBridge;
88
90
  terminal?: TerminalBridge;
91
+ /**
92
+ * `'native'` prints settled conversation into the terminal's own scrollback and
93
+ * renders only a bounded live region (tui-native-terminal-scroll). DEFAULTS TO
94
+ * `'app'` so every existing render test and call site is untouched (I-S9).
95
+ */
96
+ scrollMode?: ScrollMode;
97
+ /** The writer / feed channel `cli.tsx` shares with the tree; required for `'native'`. */
98
+ scrollback?: ScrollbackBridge;
89
99
  /**
90
100
  * The transcript to seed the first frame with, from a resumed session
91
101
  * (tui-session-id-resume 3.12).
@@ -148,4 +158,4 @@ export interface TerminalBridge {
148
158
  writeForeign?: (text: string) => void;
149
159
  selection?: SelectionBridge;
150
160
  }
151
- export declare function App({ controller, version, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, initialEntries, session, sessionBridge, sessionNotice, }: AppProps): React.ReactElement;
161
+ export declare function App({ controller, version, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, scrollMode, scrollback, initialEntries, session, sessionBridge, sessionNotice, }: AppProps): React.ReactElement;