@aragon-agent/cli 0.5.0 → 0.5.1
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 +131 -0
- package/README.md +227 -3
- 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 +183 -20
- 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 +45 -1
- package/dist/config/load.d.ts +8 -0
- package/dist/config/load.js +59 -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 +61 -0
- package/dist/config/schema.js +83 -0
- package/dist/config/store.d.ts +30 -12
- package/dist/config/store.js +47 -35
- 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 +4 -1
- package/dist/ui/App.js +182 -11
- package/dist/ui/Composer.d.ts +4 -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 +8 -1
- package/dist/ui/PromptInput.js +12 -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 +9 -0
- package/dist/ui/glyphs.js +8 -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 +13 -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/use-startup-notices.d.ts +15 -0
- package/dist/ui/use-startup-notices.js +38 -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,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuestionOverlay — the keyboard-driven multiple-choice wizard `ask_user`
|
|
3
|
+
* renders (plan-mode §6.5).
|
|
4
|
+
*
|
|
5
|
+
* `OverlayFrame` MODE B (self-managed): the content is a small state machine,
|
|
6
|
+
* not a list of rows, and slicing it by element would be meaningless.
|
|
7
|
+
*
|
|
8
|
+
* THE SINGLE HIGHEST-VALUE DECISION HERE is that the recommended option is the
|
|
9
|
+
* initial highlight for every question, so `Enter Enter Enter` is a correct,
|
|
10
|
+
* deliberate "use your judgement" path rather than an accident.
|
|
11
|
+
*
|
|
12
|
+
* `Esc` IS NOT HANDLED HERE. `App` owns it, and the bridge's `settle()` is the
|
|
13
|
+
* only place a human request is ever answered. `ConfirmDialog` does the
|
|
14
|
+
* opposite — both it and `App` call `resolve` — which is harmless there only by
|
|
15
|
+
* accident of promise semantics, and is not a pattern to copy (R-P12).
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import type { Theme } from '../theme.js';
|
|
19
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
20
|
+
import type { Answer, NormalizedQuestion } from '../../tools/human-input.js';
|
|
21
|
+
interface QuestionOverlayProps {
|
|
22
|
+
questions: NormalizedQuestion[];
|
|
23
|
+
maxRows: number;
|
|
24
|
+
cols: number;
|
|
25
|
+
theme: Theme;
|
|
26
|
+
caps: TermCapabilities;
|
|
27
|
+
onSubmit: (answers: Answer[]) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function QuestionOverlay({ questions, maxRows, cols, theme, caps, onSubmit, }: QuestionOverlayProps): React.ReactElement;
|
|
30
|
+
export {};
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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 declare function useStartupNotices(dispatch: (action: ViewAction) => void): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
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 } from 'react';
|
|
15
|
+
import { readConfigFile } from '../config/store.js';
|
|
16
|
+
import { getLogger } from '../logging/logger.js';
|
|
17
|
+
export function useStartupNotices(dispatch) {
|
|
18
|
+
// The sink gives up after repeated write failures (a read-only log directory,
|
|
19
|
+
// a full disk). Silently logging nothing is exactly the state a user must not
|
|
20
|
+
// discover later, from an empty file, while trying to explain a bug.
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
getLogger().onFailure((reason) => dispatch({ type: 'pushToast', level: 'warn', text: `Logging stopped: ${reason}` }));
|
|
23
|
+
}, [dispatch]);
|
|
24
|
+
// `main()` already wrote this to stderr, but entering the alternate screen
|
|
25
|
+
// wipes it — so full-screen users would be left with the very failure the
|
|
26
|
+
// message exists to prevent: every setting back at its default, and nothing
|
|
27
|
+
// anywhere connecting that to the comma they typed into config.json.
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const { parseError } = readConfigFile();
|
|
30
|
+
if (!parseError)
|
|
31
|
+
return;
|
|
32
|
+
dispatch({
|
|
33
|
+
type: 'pushToast',
|
|
34
|
+
level: 'warn',
|
|
35
|
+
text: `Using default settings: could not parse ${parseError}`,
|
|
36
|
+
});
|
|
37
|
+
}, [dispatch]);
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aragon-agent/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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.1",
|
|
54
55
|
"chalk": "^5.6.2",
|
|
55
56
|
"cli-highlight": "^2.0.0",
|
|
56
57
|
"commander": "^12.1.0",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const INSECURE_TLS_WARNING =
|
|
4
|
+
"Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' " +
|
|
5
|
+
'makes TLS connections and HTTPS requests insecure by disabling ' +
|
|
6
|
+
'certificate verification.';
|
|
7
|
+
|
|
8
|
+
const FILTER_STATE_KEY = Symbol.for('@aragon-agent/cli.insecure-tls-warning-filter');
|
|
9
|
+
|
|
10
|
+
function installInsecureTlsWarningFilter(target = process) {
|
|
11
|
+
const existingState = target[FILTER_STATE_KEY];
|
|
12
|
+
if (existingState) return existingState.restore;
|
|
13
|
+
|
|
14
|
+
const originalEmitWarning = target.emitWarning;
|
|
15
|
+
let installedState;
|
|
16
|
+
|
|
17
|
+
function wrappedEmitWarning(warning) {
|
|
18
|
+
const message =
|
|
19
|
+
typeof warning === 'string'
|
|
20
|
+
? warning
|
|
21
|
+
: warning instanceof Error
|
|
22
|
+
? warning.message
|
|
23
|
+
: undefined;
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
target.env.NODE_TLS_REJECT_UNAUTHORIZED === '0' &&
|
|
27
|
+
message === INSECURE_TLS_WARNING
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Reflect.apply(originalEmitWarning, target, arguments);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function restore() {
|
|
36
|
+
if (
|
|
37
|
+
target[FILTER_STATE_KEY] !== installedState ||
|
|
38
|
+
target.emitWarning !== wrappedEmitWarning
|
|
39
|
+
) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
target.emitWarning = originalEmitWarning;
|
|
44
|
+
delete target[FILTER_STATE_KEY];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
installedState = {
|
|
48
|
+
originalEmitWarning,
|
|
49
|
+
wrappedEmitWarning,
|
|
50
|
+
restore,
|
|
51
|
+
};
|
|
52
|
+
target[FILTER_STATE_KEY] = installedState;
|
|
53
|
+
target.emitWarning = wrappedEmitWarning;
|
|
54
|
+
return restore;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
INSECURE_TLS_WARNING,
|
|
59
|
+
installInsecureTlsWarningFilter,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
installInsecureTlsWarningFilter();
|