@aragon-agent/cli 0.5.0 → 0.5.2
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 +158 -0
- package/README.md +254 -7
- package/dist/agent/agent-mode.d.ts +53 -0
- package/dist/agent/agent-mode.js +69 -0
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +154 -1
- package/dist/agent/plan-prompt.d.ts +18 -0
- package/dist/agent/plan-prompt.js +93 -0
- package/dist/agent/reducer.d.ts +14 -1
- package/dist/agent/reducer.js +9 -0
- package/dist/agent/system-prompt.d.ts +15 -0
- package/dist/agent/system-prompt.js +8 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +247 -23
- package/dist/commands/builtins.js +60 -0
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +71 -4
- package/dist/config/app-paths.js +139 -8
- package/dist/config/cli-commands.d.ts +43 -0
- package/dist/config/cli-commands.js +196 -0
- package/dist/config/env.d.ts +7 -1
- package/dist/config/env.js +53 -1
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +82 -2
- package/dist/config/migrate-home.d.ts +63 -0
- package/dist/config/migrate-home.js +165 -0
- package/dist/config/migrate-legacy-state.d.ts +10 -0
- package/dist/config/migrate-legacy-state.js +15 -6
- package/dist/config/schema.d.ts +81 -0
- package/dist/config/schema.js +85 -0
- package/dist/config/store.d.ts +30 -12
- package/dist/config/store.js +47 -35
- package/dist/input/mouse-events.d.ts +61 -0
- package/dist/input/mouse-events.js +153 -0
- package/dist/input/stdin-mouse-filter.d.ts +42 -0
- package/dist/input/stdin-mouse-filter.js +153 -0
- package/dist/logging/cli-commands.d.ts +25 -0
- package/dist/logging/cli-commands.js +221 -0
- package/dist/logging/file-sink.d.ts +114 -0
- package/dist/logging/file-sink.js +326 -0
- package/dist/logging/install.d.ts +163 -0
- package/dist/logging/install.js +302 -0
- package/dist/logging/levels.d.ts +40 -0
- package/dist/logging/levels.js +59 -0
- package/dist/logging/logger.d.ts +110 -0
- package/dist/logging/logger.js +237 -0
- package/dist/logging/redact.d.ts +28 -0
- package/dist/logging/redact.js +90 -0
- package/dist/logging/secret-registry.d.ts +30 -0
- package/dist/logging/secret-registry.js +55 -0
- package/dist/skills/paths.d.ts +2 -4
- package/dist/skills/paths.js +7 -9
- package/dist/tools/human-input.d.ts +179 -0
- package/dist/tools/human-input.js +227 -0
- package/dist/tools/index.d.ts +35 -3
- package/dist/tools/index.js +96 -9
- package/dist/tools/plan-tools.d.ts +64 -0
- package/dist/tools/plan-tools.js +198 -0
- package/dist/ui/App.d.ts +12 -1
- package/dist/ui/App.js +223 -17
- package/dist/ui/Composer.d.ts +9 -1
- package/dist/ui/Composer.js +26 -7
- package/dist/ui/ModeChip.d.ts +29 -0
- package/dist/ui/ModeChip.js +15 -0
- package/dist/ui/PromptInput.d.ts +22 -1
- package/dist/ui/PromptInput.js +49 -2
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +10 -2
- package/dist/ui/entries/ToolPreview.d.ts +2 -0
- package/dist/ui/entries/ToolPreview.js +64 -0
- package/dist/ui/glyphs.d.ts +21 -0
- package/dist/ui/glyphs.js +12 -0
- package/dist/ui/layout/AppShell.d.ts +10 -1
- package/dist/ui/layout/AppShell.js +38 -3
- package/dist/ui/layout/ScrollIndicator.d.ts +31 -0
- package/dist/ui/layout/ScrollIndicator.js +15 -0
- package/dist/ui/layout/ScrollViewport.d.ts +8 -2
- package/dist/ui/layout/ScrollViewport.js +24 -4
- package/dist/ui/layout/regions.d.ts +55 -0
- package/dist/ui/layout/regions.js +52 -0
- package/dist/ui/layout/scroll-indicator.d.ts +27 -0
- package/dist/ui/layout/scroll-indicator.js +35 -0
- package/dist/ui/layout/scroll.d.ts +9 -0
- package/dist/ui/layout/scroll.js +23 -0
- package/dist/ui/layout/wrap-rows.d.ts +24 -0
- package/dist/ui/layout/wrap-rows.js +74 -0
- package/dist/ui/overlays/HelpOverlay.js +15 -0
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +50 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +117 -0
- package/dist/ui/overlays/QuestionOverlay.d.ts +30 -0
- package/dist/ui/overlays/QuestionOverlay.js +193 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +8 -1
- package/dist/ui/screen.d.ts +14 -1
- package/dist/ui/screen.js +37 -3
- package/dist/ui/use-startup-notices.d.ts +25 -0
- package/dist/ui/use-startup-notices.js +64 -0
- package/dist/ui/use-wheel-routing.d.ts +68 -0
- package/dist/ui/use-wheel-routing.js +139 -0
- package/package.json +3 -2
- package/runtime/insecure-tls-warning.cjs +62 -0
- package/runtime/insecure-tls-warning.d.cts +5 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* QuestionOverlay — the keyboard-driven multiple-choice wizard `ask_user`
|
|
4
|
+
* renders (plan-mode §6.5).
|
|
5
|
+
*
|
|
6
|
+
* `OverlayFrame` MODE B (self-managed): the content is a small state machine,
|
|
7
|
+
* not a list of rows, and slicing it by element would be meaningless.
|
|
8
|
+
*
|
|
9
|
+
* THE SINGLE HIGHEST-VALUE DECISION HERE is that the recommended option is the
|
|
10
|
+
* initial highlight for every question, so `Enter Enter Enter` is a correct,
|
|
11
|
+
* deliberate "use your judgement" path rather than an accident.
|
|
12
|
+
*
|
|
13
|
+
* `Esc` IS NOT HANDLED HERE. `App` owns it, and the bridge's `settle()` is the
|
|
14
|
+
* only place a human request is ever answered. `ConfirmDialog` does the
|
|
15
|
+
* opposite — both it and `App` call `resolve` — which is harmless there only by
|
|
16
|
+
* accident of promise semantics, and is not a pattern to copy (R-P12).
|
|
17
|
+
*/
|
|
18
|
+
import { useState } from 'react';
|
|
19
|
+
import { Box, Text, useInput } from 'ink';
|
|
20
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
21
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
22
|
+
/** Which option index each question starts on: the recommended one. */
|
|
23
|
+
function initialCursors(questions) {
|
|
24
|
+
return questions.map((q) => Math.max(0, q.options.findIndex((o) => o.recommended)));
|
|
25
|
+
}
|
|
26
|
+
function initialSelections(questions) {
|
|
27
|
+
return questions.map((q) => {
|
|
28
|
+
const recommended = q.options.findIndex((o) => o.recommended);
|
|
29
|
+
return new Set(recommended >= 0 ? [recommended] : [0]);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmit, }) {
|
|
33
|
+
const glyphs = pickGlyphs(caps);
|
|
34
|
+
const [step, setStep] = useState(0);
|
|
35
|
+
const [phase, setPhase] = useState('choosing');
|
|
36
|
+
const [cursors, setCursors] = useState(() => initialCursors(questions));
|
|
37
|
+
const [selections, setSelections] = useState(() => initialSelections(questions));
|
|
38
|
+
const [customs, setCustoms] = useState(() => questions.map(() => ''));
|
|
39
|
+
const [draft, setDraft] = useState('');
|
|
40
|
+
const question = questions[Math.min(step, questions.length - 1)];
|
|
41
|
+
const cursor = cursors[step] ?? 0;
|
|
42
|
+
const setCursorAt = (index, value) => setCursors((prev) => prev.map((c, i) => (i === index ? value : c)));
|
|
43
|
+
const setCustomAt = (index, value) => setCustoms((prev) => prev.map((c, i) => (i === index ? value : c)));
|
|
44
|
+
const toggleAt = (index, option) => setSelections((prev) => prev.map((set, i) => {
|
|
45
|
+
if (i !== index)
|
|
46
|
+
return set;
|
|
47
|
+
const next = new Set(set);
|
|
48
|
+
if (next.has(option))
|
|
49
|
+
next.delete(option);
|
|
50
|
+
else
|
|
51
|
+
next.add(option);
|
|
52
|
+
return next;
|
|
53
|
+
}));
|
|
54
|
+
const chooseAt = (index, option) => setSelections((prev) => prev.map((set, i) => (i === index ? new Set([option]) : set)));
|
|
55
|
+
const buildAnswers = () => questions.map((q, i) => {
|
|
56
|
+
const chosen = [...(selections[i] ?? new Set())].sort((a, b) => a - b);
|
|
57
|
+
const labels = chosen.map((index) => q.options[index]?.label ?? '').filter((l) => l.length > 0);
|
|
58
|
+
const pickedOther = chosen.some((index) => q.options[index]?.isOther);
|
|
59
|
+
return {
|
|
60
|
+
id: q.id,
|
|
61
|
+
question: q.question,
|
|
62
|
+
selected: labels,
|
|
63
|
+
custom: pickedOther && (customs[i] ?? '').length > 0 ? customs[i] : null,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const advance = () => {
|
|
67
|
+
if (step + 1 >= questions.length)
|
|
68
|
+
setPhase('review');
|
|
69
|
+
else {
|
|
70
|
+
setStep(step + 1);
|
|
71
|
+
setPhase('choosing');
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const goBack = () => {
|
|
75
|
+
if (step === 0)
|
|
76
|
+
return; // No-op on the first question, by design.
|
|
77
|
+
setStep(step - 1);
|
|
78
|
+
setPhase('choosing');
|
|
79
|
+
};
|
|
80
|
+
useInput((input, key) => {
|
|
81
|
+
// App owns Esc for both new overlays (R-P12). Swallow it here so the
|
|
82
|
+
// free-text editor below cannot eat the raw escape byte as a character.
|
|
83
|
+
if (key.escape)
|
|
84
|
+
return;
|
|
85
|
+
if (phase === 'review') {
|
|
86
|
+
if (key.return) {
|
|
87
|
+
onSubmit(buildAnswers());
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (key.leftArrow) {
|
|
91
|
+
setStep(questions.length - 1);
|
|
92
|
+
setPhase('choosing');
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (phase === 'typingOther') {
|
|
97
|
+
if (key.return) {
|
|
98
|
+
// An empty commit falls back to the recommended option rather than
|
|
99
|
+
// recording a blank answer: the user pressed Enter, which everywhere
|
|
100
|
+
// else in this wizard means "accept the sensible default".
|
|
101
|
+
if (draft.trim().length === 0) {
|
|
102
|
+
const recommended = Math.max(0, question.options.findIndex((o) => o.recommended));
|
|
103
|
+
chooseAt(step, recommended);
|
|
104
|
+
setCursorAt(step, recommended);
|
|
105
|
+
setCustomAt(step, '');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
setCustomAt(step, draft.trim());
|
|
109
|
+
}
|
|
110
|
+
setDraft('');
|
|
111
|
+
setPhase('choosing');
|
|
112
|
+
advance();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (key.leftArrow) {
|
|
116
|
+
setDraft('');
|
|
117
|
+
setPhase('choosing');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (key.backspace || key.delete) {
|
|
121
|
+
setDraft(draft.slice(0, -1));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (input && !key.ctrl && !key.meta && !key.tab)
|
|
125
|
+
setDraft(draft + input);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// phase === 'choosing'
|
|
129
|
+
if (key.upArrow) {
|
|
130
|
+
setCursorAt(step, (cursor - 1 + question.options.length) % question.options.length);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (key.downArrow) {
|
|
134
|
+
setCursorAt(step, (cursor + 1) % question.options.length);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (key.leftArrow) {
|
|
138
|
+
goBack();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (input === ' ' && question.allowMultiple) {
|
|
142
|
+
toggleAt(step, cursor);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (key.return) {
|
|
146
|
+
if (question.options[cursor]?.isOther) {
|
|
147
|
+
chooseAt(step, cursor);
|
|
148
|
+
setDraft(customs[step] ?? '');
|
|
149
|
+
setPhase('typingOther');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!question.allowMultiple)
|
|
153
|
+
chooseAt(step, cursor);
|
|
154
|
+
else if (!(selections[step] ?? new Set()).has(cursor))
|
|
155
|
+
toggleAt(step, cursor);
|
|
156
|
+
advance();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
const title = phase === 'review' ? 'Review your answers' : 'Clarify the request';
|
|
160
|
+
const position = phase === 'review' ? '' : ` ${step + 1}/${questions.length}`;
|
|
161
|
+
const hint = phase === 'typingOther'
|
|
162
|
+
? `type your answer ${glyphs.midDot} ${glyphs.enterKey} commit ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`
|
|
163
|
+
: phase === 'review'
|
|
164
|
+
? `${glyphs.enterKey} submit ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`
|
|
165
|
+
: `${glyphs.arrowUp}${glyphs.arrowDown} choose ${glyphs.midDot} ${glyphs.enterKey} next${question.allowMultiple ? ` ${glyphs.midDot} space toggle` : ''} ${glyphs.midDot} ${glyphs.arrowLeft} back ${glyphs.midDot} esc cancel`;
|
|
166
|
+
return (_jsx(OverlayFrame, { title: `${title}${position}`, hint: hint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: phase === 'review' ? (_jsx(ReviewBody, { questions: questions, answers: buildAnswers(), theme: theme })) : (_jsx(QuestionBody, { question: question, cursor: cursor, selected: selections[step] ?? new Set(), typing: phase === 'typingOther', draft: draft, theme: theme, caps: caps })) }));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* ONE QUESTION PER SCREEN, not all of them at once. Three questions x four
|
|
170
|
+
* options x a description line is 20+ rows against a ~15-row viewport on a
|
|
171
|
+
* 24-row terminal.
|
|
172
|
+
*/
|
|
173
|
+
function QuestionBody({ question, cursor, selected, typing, draft, theme, caps, }) {
|
|
174
|
+
const glyphs = pickGlyphs(caps);
|
|
175
|
+
const markOn = question.allowMultiple ? glyphs.boxChecked : glyphs.keyOn;
|
|
176
|
+
const markOff = question.allowMultiple ? glyphs.boxEmpty : glyphs.keyOff;
|
|
177
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { wrap: "truncate", color: theme.accent, bold: true, children: question.header }), _jsx(Text, { wrap: "truncate", color: theme.assistant, children: question.question }), _jsx(Text, { children: " " }), question.options.map((option, i) => {
|
|
178
|
+
const active = i === cursor;
|
|
179
|
+
const on = selected.has(i);
|
|
180
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: active ? theme.primary : theme.muted, children: [active ? glyphs.caret : ' ', ' '] }), _jsxs(Text, { color: on ? theme.primary : theme.muted, children: [on ? markOn : markOff, " "] }), _jsx(Text, { color: active ? theme.assistant : undefined, bold: active, children: option.isOther ? `${option.label}${glyphs.ellipsis}` : option.label }), option.description.length > 0 && (_jsxs(Text, { color: theme.muted, children: [' ', option.description] })), option.recommended && (_jsxs(Text, { backgroundColor: theme.chip.bg, color: theme.chip.fg, children: [' ', "RECOMMENDED", ' '] }))] }, `${question.id}-${i}`));
|
|
181
|
+
}), typing && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: theme.primary, bold: true, children: [glyphs.caret, ' '] }), draft.length > 0 ? (_jsx(Text, { children: draft })) : (_jsx(Text, { color: theme.muted, children: "type your own answer" })), _jsx(Text, { inverse: true, children: " " })] })] }))] }));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The review step exists because a wizard that fires on the last `Enter` with no
|
|
185
|
+
* confirmation makes a mis-keyed answer unrecoverable.
|
|
186
|
+
*/
|
|
187
|
+
function ReviewBody({ questions, answers, theme, }) {
|
|
188
|
+
return (_jsx(Box, { flexDirection: "column", children: questions.map((q, i) => {
|
|
189
|
+
const answer = answers[i];
|
|
190
|
+
const value = answer?.custom ?? answer?.selected.join(', ') ?? '';
|
|
191
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.accent, children: q.header.padEnd(14) }), _jsx(Text, { color: theme.assistant, children: value })] }, q.id));
|
|
192
|
+
}) }));
|
|
193
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import type { ThinkingLevel } from '@aragon-agent/core';
|
|
12
|
+
import { type LogLevelName } from '../../logging/levels.js';
|
|
12
13
|
import type { Theme } from '../theme.js';
|
|
13
14
|
import type { TermCapabilities } from '../capabilities.js';
|
|
14
15
|
export interface SettingsValues {
|
|
@@ -18,6 +19,12 @@ export interface SettingsValues {
|
|
|
18
19
|
thinkingLevel: ThinkingLevel;
|
|
19
20
|
maxTokens: string;
|
|
20
21
|
apiKey: string;
|
|
22
|
+
/**
|
|
23
|
+
* Only the LEVEL is editable here. The rest of `LogConfig` — and above all
|
|
24
|
+
* `redactSecrets` — stays command-line-only, so the one switch that can put
|
|
25
|
+
* credentials on disk cannot be flipped by a stray arrow key.
|
|
26
|
+
*/
|
|
27
|
+
logLevel: LogLevelName;
|
|
21
28
|
}
|
|
22
29
|
interface SettingsScreenProps {
|
|
23
30
|
initial: SettingsValues;
|
|
@@ -11,6 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import { Text, useInput } from 'ink';
|
|
13
13
|
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret } from '../../config/schema.js';
|
|
14
|
+
import { LOG_LEVEL_NAMES } from '../../logging/levels.js';
|
|
14
15
|
import { pickGlyphs } from '../glyphs.js';
|
|
15
16
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
16
17
|
const FIELDS = [
|
|
@@ -20,6 +21,7 @@ const FIELDS = [
|
|
|
20
21
|
{ key: 'thinkingLevel', label: 'Thinking', kind: 'enum', options: THINKING_LEVELS },
|
|
21
22
|
{ key: 'maxTokens', label: 'Max tokens', kind: 'text' },
|
|
22
23
|
{ key: 'apiKey', label: 'API key', kind: 'secret' },
|
|
24
|
+
{ key: 'logLevel', label: 'Log level', kind: 'enum', options: LOG_LEVEL_NAMES },
|
|
23
25
|
];
|
|
24
26
|
export function SettingsScreen({ initial, apiKeys, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onSave, }) {
|
|
25
27
|
const [values, setValues] = useState(initial);
|
|
@@ -51,7 +53,12 @@ export function SettingsScreen({ initial, apiKeys, maxRows, cols, scrollOffset,
|
|
|
51
53
|
setIndex((i) => (i - 1 + FIELDS.length) % FIELDS.length);
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
|
-
|
|
56
|
+
// `&& !key.shift` is a real key collision, not a style choice: Shift+Tab is
|
|
57
|
+
// the global mode toggle, and without the guard it would also advance the
|
|
58
|
+
// field cursor here — the user changes mode and their selection jumps
|
|
59
|
+
// (AC-P4). The mode toggle itself is a no-op while an overlay is open, so
|
|
60
|
+
// this branch simply has to stay out of the way.
|
|
61
|
+
if (key.downArrow || (key.tab && !key.shift)) {
|
|
55
62
|
setIndex((i) => (i + 1) % FIELDS.length);
|
|
56
63
|
return;
|
|
57
64
|
}
|
package/dist/ui/screen.d.ts
CHANGED
|
@@ -15,11 +15,24 @@ export interface ScreenHandle {
|
|
|
15
15
|
/** Leave the alternate screen. Idempotent — safe from every exit path. */
|
|
16
16
|
restore(): void;
|
|
17
17
|
}
|
|
18
|
+
export interface AltScreenOptions {
|
|
19
|
+
/**
|
|
20
|
+
* TRUE MEANS "A MOUSE FILTER IS ALREADY INSTALLED", NOT "THE USER WANTS MOUSE
|
|
21
|
+
* SUPPORT" (invariant I-8).
|
|
22
|
+
*
|
|
23
|
+
* This module must never derive the value from config, env or platform.
|
|
24
|
+
* Enabling reporting without a filter in front of Ink does not degrade the
|
|
25
|
+
* feature — it types `[<0;12;5M` into the user's message on every click and
|
|
26
|
+
* every notch, which is strictly worse than the bug the feature fixes. The
|
|
27
|
+
* only caller is `cli.tsx::runInteractive`, and it passes `filter !== null`.
|
|
28
|
+
*/
|
|
29
|
+
readonly mouse: boolean;
|
|
30
|
+
}
|
|
18
31
|
/**
|
|
19
32
|
* Enter the alternate screen. A non-TTY stdout is a hard no-op (tests, pipes,
|
|
20
33
|
* CI) — writing screen-control sequences into a redirected file corrupts it.
|
|
21
34
|
*/
|
|
22
|
-
export declare function enterAltScreen(stdout: NodeJS.WriteStream | undefined): ScreenHandle;
|
|
35
|
+
export declare function enterAltScreen(stdout: NodeJS.WriteStream | undefined, options?: AltScreenOptions): ScreenHandle;
|
|
23
36
|
/**
|
|
24
37
|
* Replay the session summary into the normal buffer after `restore()`, so the
|
|
25
38
|
* conversation does not evaporate along with the alternate screen.
|
package/dist/ui/screen.js
CHANGED
|
@@ -17,6 +17,32 @@ const CURSOR_HOME = '\x1b[H';
|
|
|
17
17
|
const CLEAR_VISIBLE = '\x1b[2J';
|
|
18
18
|
/** Ink's log-update hides the cursor; a signal kill never gives it back. */
|
|
19
19
|
const SHOW_CURSOR = '\x1b[?25h';
|
|
20
|
+
/**
|
|
21
|
+
* `?1000` is NORMAL TRACKING (press / release), the minimum mode that reports
|
|
22
|
+
* wheel buttons; `?1006` selects SGR encoding so coordinates arrive as decimal
|
|
23
|
+
* text rather than raw bytes above 0x7F that `setEncoding('utf8')` would
|
|
24
|
+
* mangle. Motion tracking (`?1002` / `?1003`) is deliberately NOT enabled: it
|
|
25
|
+
* floods stdin on every pointer move and buys nothing this feature uses.
|
|
26
|
+
*/
|
|
27
|
+
const ENABLE_MOUSE = '\x1b[?1000h\x1b[?1006h';
|
|
28
|
+
const DISABLE_MOUSE = '\x1b[?1006l\x1b[?1000l';
|
|
29
|
+
/**
|
|
30
|
+
* Alternate scroll (DEC private mode 1007) is what turns a wheel notch into a
|
|
31
|
+
* burst of arrow keys, which `PromptInput` reads as prompt-history recall. With
|
|
32
|
+
* mouse reporting ON we do not touch it at all: 1007 is defined to translate
|
|
33
|
+
* the wheel only while no application is tracking the mouse, so `?1000h`
|
|
34
|
+
* already suppresses it and a second mutation of shared state buys nothing.
|
|
35
|
+
*
|
|
36
|
+
* With mouse reporting OFF we turn it off so the wheel is INERT rather than
|
|
37
|
+
* destructive — the fallback rung of the fail-safe ladder. But 1007 is GLOBAL
|
|
38
|
+
* TERMINAL STATE THIS APP DOES NOT OWN, so it is SAVED and RESTORED (XTSAVE /
|
|
39
|
+
* XTRESTORE) rather than force-set on the way out: a user who deliberately
|
|
40
|
+
* disabled alternate scroll in their terminal config must not have it switched
|
|
41
|
+
* back on by running `aragon --no-mouse` once. A terminal implementing neither
|
|
42
|
+
* sequence ignores both, so the fallback is no worse than writing nothing.
|
|
43
|
+
*/
|
|
44
|
+
const SAVE_ALT_SCROLL_OFF = '\x1b[?1007s\x1b[?1007l';
|
|
45
|
+
const RESTORE_ALT_SCROLL = '\x1b[?1007r';
|
|
20
46
|
const NOOP_HANDLE = { restore() { } };
|
|
21
47
|
function safeWrite(stdout, data) {
|
|
22
48
|
try {
|
|
@@ -30,17 +56,25 @@ function safeWrite(stdout, data) {
|
|
|
30
56
|
* Enter the alternate screen. A non-TTY stdout is a hard no-op (tests, pipes,
|
|
31
57
|
* CI) — writing screen-control sequences into a redirected file corrupts it.
|
|
32
58
|
*/
|
|
33
|
-
export function enterAltScreen(stdout) {
|
|
59
|
+
export function enterAltScreen(stdout, options = { mouse: false }) {
|
|
34
60
|
if (!stdout || !stdout.isTTY || typeof stdout.write !== 'function')
|
|
35
61
|
return NOOP_HANDLE;
|
|
36
|
-
|
|
62
|
+
const mouse = options.mouse === true;
|
|
63
|
+
safeWrite(stdout, ENTER_ALT + CLEAR_VISIBLE + CURSOR_HOME + (mouse ? ENABLE_MOUSE : SAVE_ALT_SCROLL_OFF));
|
|
37
64
|
let restored = false;
|
|
38
65
|
return {
|
|
39
66
|
restore() {
|
|
40
67
|
if (restored)
|
|
41
68
|
return;
|
|
42
69
|
restored = true;
|
|
43
|
-
|
|
70
|
+
// Mouse reporting is disabled BEFORE leaving the alternate screen (I-2).
|
|
71
|
+
// This handle is what all four exit paths converge on — `process.on
|
|
72
|
+
// ('exit')`, the crash handler's `setScreenRestore`, the signal
|
|
73
|
+
// terminator, and `waitUntilExit()` — which is why the sequences live
|
|
74
|
+
// here and not in `App.tsx`, a component that is on none of them.
|
|
75
|
+
// Leaving reporting on prints `[<0;12;5M` in the user's shell on every
|
|
76
|
+
// click, forever, until they run `reset`.
|
|
77
|
+
safeWrite(stdout, (mouse ? DISABLE_MOUSE : RESTORE_ALT_SCROLL) + SHOW_CURSOR + LEAVE_ALT);
|
|
44
78
|
},
|
|
45
79
|
};
|
|
46
80
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two things the user needs to be told about that happened BEFORE the frame
|
|
3
|
+
* existed, and the one that can happen at any moment afterwards.
|
|
4
|
+
*
|
|
5
|
+
* Both arrive as toasts, and that is not a style choice: with the TUI mounted, a
|
|
6
|
+
* toast is the only legal user-visible channel. Any direct write to stdout or
|
|
7
|
+
* stderr shifts the fixed frame by a row and breaks its line accounting for good
|
|
8
|
+
* (invariant I-4).
|
|
9
|
+
*
|
|
10
|
+
* Lives outside `App.tsx` because that file is already at its size ceiling, and
|
|
11
|
+
* because these two effects share one job — surfacing a condition the user could
|
|
12
|
+
* not otherwise see — rather than belonging to the view.
|
|
13
|
+
*/
|
|
14
|
+
import type { ViewAction } from '../agent/reducer.js';
|
|
15
|
+
export interface MouseNoticeOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Whether reporting is ACTUALLY in effect — i.e. a filter was installed, not
|
|
18
|
+
* merely that the config asked for one. A user on a terminal where R-1 fires
|
|
19
|
+
* must never be given advice about a mode that is not running.
|
|
20
|
+
*/
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
/** Persist `mouseNoticeSeen`. Called once, after the notice is dispatched. */
|
|
23
|
+
onSeen: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function useStartupNotices(dispatch: (action: ViewAction) => void, mouseNotice?: MouseNoticeOptions): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two things the user needs to be told about that happened BEFORE the frame
|
|
3
|
+
* existed, and the one that can happen at any moment afterwards.
|
|
4
|
+
*
|
|
5
|
+
* Both arrive as toasts, and that is not a style choice: with the TUI mounted, a
|
|
6
|
+
* toast is the only legal user-visible channel. Any direct write to stdout or
|
|
7
|
+
* stderr shifts the fixed frame by a row and breaks its line accounting for good
|
|
8
|
+
* (invariant I-4).
|
|
9
|
+
*
|
|
10
|
+
* Lives outside `App.tsx` because that file is already at its size ceiling, and
|
|
11
|
+
* because these two effects share one job — surfacing a condition the user could
|
|
12
|
+
* not otherwise see — rather than belonging to the view.
|
|
13
|
+
*/
|
|
14
|
+
import { useEffect, useRef } from 'react';
|
|
15
|
+
import { readConfigFile } from '../config/store.js';
|
|
16
|
+
import { getLogger } from '../logging/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* The §6.1 notice (mouse-wheel-region-routing). Shown at most once, ever.
|
|
19
|
+
*
|
|
20
|
+
* R-3 is the only part of wheel region routing that reaches users who never
|
|
21
|
+
* scroll, and it REMOVES a gesture — drag to select — that is used more often
|
|
22
|
+
* than the one being added. A README is consulted only after the belief
|
|
23
|
+
* "aragon broke copy/paste" has already formed.
|
|
24
|
+
*/
|
|
25
|
+
const MOUSE_SELECTION_NOTICE = 'Mouse wheel scrolls the transcript. Hold Shift to select text, ' +
|
|
26
|
+
'or turn it off with `aragon config set mouse false`.';
|
|
27
|
+
export function useStartupNotices(dispatch, mouseNotice) {
|
|
28
|
+
// The sink gives up after repeated write failures (a read-only log directory,
|
|
29
|
+
// a full disk). Silently logging nothing is exactly the state a user must not
|
|
30
|
+
// discover later, from an empty file, while trying to explain a bug.
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
getLogger().onFailure((reason) => dispatch({ type: 'pushToast', level: 'warn', text: `Logging stopped: ${reason}` }));
|
|
33
|
+
}, [dispatch]);
|
|
34
|
+
// `main()` already wrote this to stderr, but entering the alternate screen
|
|
35
|
+
// wipes it — so full-screen users would be left with the very failure the
|
|
36
|
+
// message exists to prevent: every setting back at its default, and nothing
|
|
37
|
+
// anywhere connecting that to the comma they typed into config.json.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const { parseError } = readConfigFile();
|
|
40
|
+
if (!parseError)
|
|
41
|
+
return;
|
|
42
|
+
dispatch({
|
|
43
|
+
type: 'pushToast',
|
|
44
|
+
level: 'warn',
|
|
45
|
+
text: `Using default settings: could not parse ${parseError}`,
|
|
46
|
+
});
|
|
47
|
+
}, [dispatch]);
|
|
48
|
+
// A `notice`, not a toast: it lands in the transcript and scrolls away
|
|
49
|
+
// instead of stealing a row. §6's argument against toasts is about per-notch
|
|
50
|
+
// chatter and does not extend to a one-shot.
|
|
51
|
+
const noticeRef = useRef(mouseNotice);
|
|
52
|
+
noticeRef.current = mouseNotice;
|
|
53
|
+
const mouseEnabled = mouseNotice?.enabled ?? false;
|
|
54
|
+
const mouseNoticeShown = useRef(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!mouseEnabled || mouseNoticeShown.current)
|
|
57
|
+
return;
|
|
58
|
+
if (readConfigFile().config?.mouseNoticeSeen)
|
|
59
|
+
return;
|
|
60
|
+
mouseNoticeShown.current = true;
|
|
61
|
+
dispatch({ type: 'notice', level: 'info', text: MOUSE_SELECTION_NOTICE });
|
|
62
|
+
noticeRef.current?.onSeen();
|
|
63
|
+
}, [mouseEnabled, dispatch]);
|
|
64
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wheel routing (mouse-wheel-region-routing §4.6 / §4.7).
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to the mouse source, turns each wheel notch into a band via
|
|
5
|
+
* `hitTestWheel`, and dispatches it to the transcript, an open overlay, or the
|
|
6
|
+
* prompt-history stepper. Lives outside `App.tsx` because that file is exactly
|
|
7
|
+
* at the 1000-line ceiling `CLAUDE.md` sets, and because the routing table is
|
|
8
|
+
* the most testable part of this feature and does not want to be inside a
|
|
9
|
+
* 1000-line component (D-13).
|
|
10
|
+
*
|
|
11
|
+
* THE TWO BANDS HAVE DIFFERENT PHYSICS, and that is the requirement rather than
|
|
12
|
+
* a tuning choice (§4.7). A physical notch produces ONE SGR report, whereas
|
|
13
|
+
* alternate-scroll used to produce three arrow keys:
|
|
14
|
+
*
|
|
15
|
+
* - transcript: 3 rows per notch, matching the terminal's own convention and
|
|
16
|
+
* the muscle memory users bring from every other full-screen app;
|
|
17
|
+
* - composer: exactly ONE history step per notch. Multiplying here would make
|
|
18
|
+
* a single flick jump three prompts back — unusable, and destructive when a
|
|
19
|
+
* draft is in play.
|
|
20
|
+
*
|
|
21
|
+
* Bursts are handled differently for the same reason: transcript events are
|
|
22
|
+
* coalesced into one intent carrying `repeat`, composer events never are (they
|
|
23
|
+
* are rate-limited by the human, and each must produce its own visible step).
|
|
24
|
+
*/
|
|
25
|
+
import { type MutableRefObject } from 'react';
|
|
26
|
+
import type { Overlay } from '../agent/reducer.js';
|
|
27
|
+
import type { MouseSource } from '../input/stdin-mouse-filter.js';
|
|
28
|
+
import { type FrameGeometry } from './layout/regions.js';
|
|
29
|
+
import type { ScrollIntent } from './layout/scroll.js';
|
|
30
|
+
/**
|
|
31
|
+
* Rows a PgUp/PgDn — and a Shift+wheel notch — moves inside a controlled
|
|
32
|
+
* overlay. Declared here rather than in `App.tsx` because both the keyboard
|
|
33
|
+
* branch and the wheel branch need it and `App` imports this module; the other
|
|
34
|
+
* direction would be a cycle.
|
|
35
|
+
*/
|
|
36
|
+
export declare const OVERLAY_PAGE = 8;
|
|
37
|
+
export interface WheelRoutingOptions {
|
|
38
|
+
/** Absent when mouse support is off, in inline mode, or on a non-TTY. */
|
|
39
|
+
mouseSource?: MouseSource;
|
|
40
|
+
/** Live frame geometry, written by `AppShell`'s layout effect (§4.5). */
|
|
41
|
+
geometryRef: MutableRefObject<FrameGeometry>;
|
|
42
|
+
/**
|
|
43
|
+
* Test-only seam (§8.2): when set, the router reads this instead of the
|
|
44
|
+
* measured ref, so a routing test never depends on `measureElement` working
|
|
45
|
+
* under `ink-testing-library` — a dependency no other test in this package
|
|
46
|
+
* exercises, and one that would make a routing regression present as a
|
|
47
|
+
* measurement flake.
|
|
48
|
+
*/
|
|
49
|
+
geometryOverride?: FrameGeometry;
|
|
50
|
+
/** Read at EVENT time for the routing branch, and again at FLUSH time (P1-3). */
|
|
51
|
+
getOverlay: () => Overlay | null;
|
|
52
|
+
onScroll: (kind: ScrollIntent, repeat: number) => void;
|
|
53
|
+
/** Signed row delta; the caller clamps at 0 the way the keyboard branch does. */
|
|
54
|
+
onOverlayScroll: (delta: number) => void;
|
|
55
|
+
onHistoryStep: (dir: 'up' | 'down') => void;
|
|
56
|
+
}
|
|
57
|
+
export interface WheelRouting {
|
|
58
|
+
/**
|
|
59
|
+
* Drop any accumulated-but-unflushed transcript scroll.
|
|
60
|
+
*
|
|
61
|
+
* Called by `App` from its existing `state.overlay` effect rather than from a
|
|
62
|
+
* second subscriber to the same transition. This is guard #2 of the pair that
|
|
63
|
+
* has to ship together (§14 condition 3); guard #1 is the overlay re-check
|
|
64
|
+
* inside the flush, and guard #3 is `ScrollViewport`'s I-9 mount-skip.
|
|
65
|
+
*/
|
|
66
|
+
clearCoalescer: () => void;
|
|
67
|
+
}
|
|
68
|
+
export declare function useWheelRouting(options: WheelRoutingOptions): WheelRouting;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wheel routing (mouse-wheel-region-routing §4.6 / §4.7).
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to the mouse source, turns each wheel notch into a band via
|
|
5
|
+
* `hitTestWheel`, and dispatches it to the transcript, an open overlay, or the
|
|
6
|
+
* prompt-history stepper. Lives outside `App.tsx` because that file is exactly
|
|
7
|
+
* at the 1000-line ceiling `CLAUDE.md` sets, and because the routing table is
|
|
8
|
+
* the most testable part of this feature and does not want to be inside a
|
|
9
|
+
* 1000-line component (D-13).
|
|
10
|
+
*
|
|
11
|
+
* THE TWO BANDS HAVE DIFFERENT PHYSICS, and that is the requirement rather than
|
|
12
|
+
* a tuning choice (§4.7). A physical notch produces ONE SGR report, whereas
|
|
13
|
+
* alternate-scroll used to produce three arrow keys:
|
|
14
|
+
*
|
|
15
|
+
* - transcript: 3 rows per notch, matching the terminal's own convention and
|
|
16
|
+
* the muscle memory users bring from every other full-screen app;
|
|
17
|
+
* - composer: exactly ONE history step per notch. Multiplying here would make
|
|
18
|
+
* a single flick jump three prompts back — unusable, and destructive when a
|
|
19
|
+
* draft is in play.
|
|
20
|
+
*
|
|
21
|
+
* Bursts are handled differently for the same reason: transcript events are
|
|
22
|
+
* coalesced into one intent carrying `repeat`, composer events never are (they
|
|
23
|
+
* are rate-limited by the human, and each must produce its own visible step).
|
|
24
|
+
*/
|
|
25
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
26
|
+
import { hitTestWheel } from './layout/regions.js';
|
|
27
|
+
/**
|
|
28
|
+
* Rows a PgUp/PgDn — and a Shift+wheel notch — moves inside a controlled
|
|
29
|
+
* overlay. Declared here rather than in `App.tsx` because both the keyboard
|
|
30
|
+
* branch and the wheel branch need it and `App` imports this module; the other
|
|
31
|
+
* direction would be a cycle.
|
|
32
|
+
*/
|
|
33
|
+
export const OVERLAY_PAGE = 8;
|
|
34
|
+
/** Transcript rows per notch. The terminal's own convention. */
|
|
35
|
+
const CONTENT_LINES_PER_NOTCH = 3;
|
|
36
|
+
/** Overlay rows per notch. Overlay content is short; a page needs Shift. */
|
|
37
|
+
const OVERLAY_LINES_PER_NOTCH = 3;
|
|
38
|
+
/**
|
|
39
|
+
* Coalescing window for transcript scrolling — one `setState`, one render, no
|
|
40
|
+
* matter how fast the wheel spins (R-10). Deliberately tighter than the 33 ms
|
|
41
|
+
* streaming coalescer: this one is in the direct path of a hand gesture.
|
|
42
|
+
*/
|
|
43
|
+
const WHEEL_COALESCE_MS = 16;
|
|
44
|
+
export function useWheelRouting(options) {
|
|
45
|
+
// Every mutable input reaches the subscription through this ref, so the
|
|
46
|
+
// effect below subscribes exactly once per `mouseSource` instead of tearing
|
|
47
|
+
// the listener down and rebuilding it on every parent render.
|
|
48
|
+
const optionsRef = useRef(options);
|
|
49
|
+
optionsRef.current = options;
|
|
50
|
+
const accumulator = useRef(null);
|
|
51
|
+
const timer = useRef(null);
|
|
52
|
+
const clearCoalescer = useCallback(() => {
|
|
53
|
+
if (timer.current) {
|
|
54
|
+
clearTimeout(timer.current);
|
|
55
|
+
timer.current = null;
|
|
56
|
+
}
|
|
57
|
+
accumulator.current = null;
|
|
58
|
+
}, []);
|
|
59
|
+
const flush = useCallback(() => {
|
|
60
|
+
if (timer.current) {
|
|
61
|
+
clearTimeout(timer.current);
|
|
62
|
+
timer.current = null;
|
|
63
|
+
}
|
|
64
|
+
const pending = accumulator.current;
|
|
65
|
+
accumulator.current = null;
|
|
66
|
+
if (!pending)
|
|
67
|
+
return;
|
|
68
|
+
// P1-3: THE DECISION BELONGS AT FLUSH TIME, not at event time, because the
|
|
69
|
+
// event-time decision is the one that goes stale. `App` swaps
|
|
70
|
+
// `ScrollViewport` out for an overlay, and 16 ms is more than wide enough
|
|
71
|
+
// for an agent-raised `confirm` or an `ask_user` to land inside the window
|
|
72
|
+
// — the intent would then be applied on a component that is not there, and
|
|
73
|
+
// the transcript would jump by itself when the overlay closed.
|
|
74
|
+
if (optionsRef.current.getOverlay())
|
|
75
|
+
return;
|
|
76
|
+
optionsRef.current.onScroll(pending.kind, pending.repeat);
|
|
77
|
+
}, []);
|
|
78
|
+
const accumulate = useCallback((kind, repeat) => {
|
|
79
|
+
// A fast up-then-down must never cancel itself into a no-op, so a change
|
|
80
|
+
// of direction (or of granularity) flushes what is already held.
|
|
81
|
+
if (accumulator.current && accumulator.current.kind !== kind)
|
|
82
|
+
flush();
|
|
83
|
+
const held = accumulator.current;
|
|
84
|
+
accumulator.current = { kind, repeat: (held?.repeat ?? 0) + repeat };
|
|
85
|
+
if (!timer.current) {
|
|
86
|
+
timer.current = setTimeout(flush, WHEEL_COALESCE_MS);
|
|
87
|
+
timer.current.unref?.();
|
|
88
|
+
}
|
|
89
|
+
}, [flush]);
|
|
90
|
+
const route = useCallback((event) => {
|
|
91
|
+
const current = optionsRef.current;
|
|
92
|
+
const geometry = current.geometryOverride ?? current.geometryRef.current;
|
|
93
|
+
const band = hitTestWheel(event.y, geometry);
|
|
94
|
+
const overlay = current.getOverlay();
|
|
95
|
+
// THE OVERLAY BRANCH IS EVALUATED FIRST AND RETURNS FOR EVERY BAND IT
|
|
96
|
+
// RECOGNIZES — the same two ordering rules the keyboard handler follows,
|
|
97
|
+
// and for the same reason (R-P1-7).
|
|
98
|
+
if (overlay) {
|
|
99
|
+
// The composer is inactive behind an overlay; stepping a hidden draft
|
|
100
|
+
// is worse than doing nothing.
|
|
101
|
+
if (band !== 'content')
|
|
102
|
+
return;
|
|
103
|
+
// Modes A only. `model` / `confirm` / `question` own their own keys and
|
|
104
|
+
// scrolling them would leave an inconsistent cursor (R-11).
|
|
105
|
+
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan';
|
|
106
|
+
if (!controlled)
|
|
107
|
+
return;
|
|
108
|
+
const step = event.shift ? OVERLAY_PAGE : OVERLAY_LINES_PER_NOTCH;
|
|
109
|
+
current.onOverlayScroll(event.dir === 'up' ? -step : step);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (band === 'composer') {
|
|
113
|
+
// §4.7: the accumulator flushes when the BAND changes, not only when
|
|
114
|
+
// the direction does. The composer path is never coalesced, so without
|
|
115
|
+
// this the transcript scroll that preceded the pointer moving down
|
|
116
|
+
// lands ~16 ms AFTER the history step it happened before — the two
|
|
117
|
+
// halves of one gesture, applied out of order.
|
|
118
|
+
flush();
|
|
119
|
+
current.onHistoryStep(event.dir);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (event.shift) {
|
|
123
|
+
accumulate(event.dir === 'up' ? 'pageUp' : 'pageDown', 1);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
accumulate(event.dir === 'up' ? 'lineUp' : 'lineDown', CONTENT_LINES_PER_NOTCH);
|
|
127
|
+
}, [accumulate, flush]);
|
|
128
|
+
const { mouseSource } = options;
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!mouseSource)
|
|
131
|
+
return undefined;
|
|
132
|
+
const unsubscribe = mouseSource.subscribe(route);
|
|
133
|
+
return () => {
|
|
134
|
+
unsubscribe();
|
|
135
|
+
clearCoalescer();
|
|
136
|
+
};
|
|
137
|
+
}, [mouseSource, route, clearCoalescer]);
|
|
138
|
+
return { clearCoalescer };
|
|
139
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
+
"runtime",
|
|
35
36
|
"skills",
|
|
36
37
|
"README.md",
|
|
37
38
|
"CHANGELOG.md"
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"prepublishOnly": "npm run build"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"@aragon-agent/core": "^0.2.
|
|
54
|
+
"@aragon-agent/core": "^0.2.2",
|
|
54
55
|
"chalk": "^5.6.2",
|
|
55
56
|
"cli-highlight": "^2.0.0",
|
|
56
57
|
"commander": "^12.1.0",
|