@aragon-agent/cli 0.6.34 → 0.6.36
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 +21 -0
- package/README.md +173 -7
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +67 -3
- package/dist/agent/controller.js +106 -5
- package/dist/agent/headless.js +31 -0
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- package/dist/agent/reducer.d.ts +13 -2
- package/dist/agent/reducer.js +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/agents-command.d.ts +15 -0
- package/dist/commands/agents-command.js +27 -0
- package/dist/commands/builtins.js +135 -4
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- package/dist/commands/perf.d.ts +11 -0
- package/dist/commands/perf.js +2 -1
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/app-paths.d.ts +15 -0
- package/dist/config/app-paths.js +33 -2
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +28 -2
- package/dist/config/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.js +39 -0
- package/dist/config/store.js +12 -1
- package/dist/config/ui-state.d.ts +18 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +25 -0
- package/dist/exec/events.js +4 -0
- package/dist/exec/index.js +16 -1
- package/dist/exec/runner.js +10 -0
- package/dist/input/keymap.js +18 -4
- package/dist/proc/limits.d.ts +16 -0
- package/dist/proc/limits.js +16 -0
- package/dist/proc/supervisor.d.ts +20 -1
- package/dist/proc/supervisor.js +42 -14
- package/dist/prompts/command.d.ts +34 -0
- package/dist/prompts/command.js +237 -0
- package/dist/prompts/copy.d.ts +89 -0
- package/dist/prompts/copy.js +131 -0
- package/dist/prompts/fill.d.ts +51 -0
- package/dist/prompts/fill.js +46 -0
- package/dist/prompts/limits.d.ts +53 -0
- package/dist/prompts/limits.js +53 -0
- package/dist/prompts/normalize.d.ts +53 -0
- package/dist/prompts/normalize.js +190 -0
- package/dist/prompts/resolve.d.ts +31 -0
- package/dist/prompts/resolve.js +60 -0
- package/dist/prompts/search.d.ts +58 -0
- package/dist/prompts/search.js +146 -0
- package/dist/prompts/store.d.ts +86 -0
- package/dist/prompts/store.js +453 -0
- package/dist/prompts/types.d.ts +204 -0
- package/dist/prompts/types.js +13 -0
- package/dist/session/checkpoint.d.ts +8 -1
- package/dist/session/checkpoint.js +6 -0
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +13 -1
- package/dist/session/startup-session.d.ts +7 -2
- package/dist/session/startup-session.js +5 -2
- package/dist/session/store.js +3 -0
- package/dist/session/validate-session.js +64 -1
- package/dist/team/limits.d.ts +25 -0
- package/dist/team/limits.js +26 -0
- package/dist/team/runtime.js +27 -2
- package/dist/team/settled.d.ts +57 -0
- package/dist/team/settled.js +87 -0
- package/dist/team/subagent.js +48 -2
- package/dist/team/timeline.d.ts +74 -0
- package/dist/team/timeline.js +101 -0
- package/dist/team/types.d.ts +24 -1
- package/dist/todo/acceptance.d.ts +120 -0
- package/dist/todo/acceptance.js +399 -0
- package/dist/todo/follow-through.d.ts +48 -23
- package/dist/todo/follow-through.js +110 -44
- package/dist/todo/limits.d.ts +62 -1
- package/dist/todo/limits.js +62 -1
- package/dist/todo/panel-rows.d.ts +29 -39
- package/dist/todo/panel-rows.js +37 -83
- package/dist/todo/plan-sections.d.ts +60 -0
- package/dist/todo/plan-sections.js +108 -0
- package/dist/todo/prompt.d.ts +17 -12
- package/dist/todo/prompt.js +59 -40
- package/dist/todo/row-window.d.ts +51 -0
- package/dist/todo/row-window.js +107 -0
- package/dist/todo/store.d.ts +139 -31
- package/dist/todo/store.js +271 -73
- package/dist/todo/todo-tool.d.ts +33 -8
- package/dist/todo/todo-tool.js +186 -30
- package/dist/todo/types.d.ts +58 -0
- package/dist/ui/App.js +414 -54
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +17 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +22 -2
- package/dist/ui/ScrollbackShell.d.ts +22 -7
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +18 -0
- package/dist/ui/StatusBar.js +11 -3
- package/dist/ui/TeamPanel.d.ts +35 -41
- package/dist/ui/TeamPanel.js +53 -163
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +23 -3
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- package/dist/ui/entries/TodoCard.d.ts +26 -11
- package/dist/ui/entries/TodoCard.js +56 -15
- package/dist/ui/glyphs.d.ts +19 -0
- package/dist/ui/glyphs.js +4 -0
- package/dist/ui/layout/agents-board.d.ts +102 -0
- package/dist/ui/layout/agents-board.js +389 -0
- package/dist/ui/layout/cells.d.ts +61 -0
- package/dist/ui/layout/cells.js +156 -0
- package/dist/ui/layout/header-hints.d.ts +23 -3
- package/dist/ui/layout/header-hints.js +73 -7
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- package/dist/ui/layout/status-detail-layout.js +9 -0
- package/dist/ui/layout/status-layout.d.ts +13 -0
- package/dist/ui/layout/team-row.d.ts +139 -0
- package/dist/ui/layout/team-row.js +246 -0
- package/dist/ui/layout/todo-layout.d.ts +38 -0
- package/dist/ui/layout/todo-layout.js +75 -0
- package/dist/ui/layout/virtual-window.js +32 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +24 -2
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
- package/dist/ui/overlays/PromptEditorPage.js +227 -0
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
- package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
- package/dist/ui/overlays/PromptPicker.d.ts +36 -0
- package/dist/ui/overlays/PromptPicker.js +129 -0
- package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
- package/dist/ui/overlays/PromptTextArea.js +141 -0
- package/dist/ui/overlays/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
- package/dist/ui/overlays/prompt-library-state.js +335 -0
- package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
- package/dist/ui/overlays/prompt-picker-state.js +98 -0
- package/dist/ui/scrollback/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/tail-window.d.ts +12 -0
- package/dist/ui/scrollback/tail-window.js +17 -2
- package/dist/ui/scrollback/use-scrollback-view.js +7 -0
- package/dist/ui/team-status.d.ts +166 -0
- package/dist/ui/team-status.js +252 -0
- package/dist/ui/transcript-text.js +13 -4
- package/dist/ui/use-startup-notices.d.ts +35 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.js +10 -2
- package/package.json +2 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Settings > Kit pack: the manager page (agent-model-kits §4.5 / D-9).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* CRUD LIVES HERE AND NOWHERE ELSE: `New kit` and `e` open the editor, `d` asks
|
|
8
|
+
* a confirmation that NAMES the consequences, `s` runs exit A (three slots plus
|
|
9
|
+
* the persisted pointer, because a default that only moved a pointer would not
|
|
10
|
+
* be in effect at the next launch - RV-P0-3), and `Enter` applies a kit to the
|
|
11
|
+
* transaction draft. Nothing on this page writes the config file.
|
|
12
|
+
*/
|
|
13
|
+
import { useState } from 'react';
|
|
14
|
+
import { Text, useInput } from 'ink';
|
|
15
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
16
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
17
|
+
import { clipProfileText, useProfileScroll } from './ModelProfilePicker.js';
|
|
18
|
+
/** `New kit` first, then one row per kit, then the three actions. */
|
|
19
|
+
const NEW_KIT = 'New kit';
|
|
20
|
+
const ACTIONS = ['Duplicate', 'Delete', 'Set default'];
|
|
21
|
+
export function KitManager(props) {
|
|
22
|
+
const glyphs = pickGlyphs(props.caps);
|
|
23
|
+
const [index, setIndex] = useState(0);
|
|
24
|
+
// NULL, NOT THE FIRST KIT: `selected` is "the last kit the cursor was on", and
|
|
25
|
+
// starting it at `rows[0]` would let `d` on the `New kit` row delete a kit the
|
|
26
|
+
// user never pointed at.
|
|
27
|
+
const [selected, setSelected] = useState(null);
|
|
28
|
+
const count = props.rows.length + 1 + ACTIONS.length;
|
|
29
|
+
const cursor = Math.min(index, count - 1);
|
|
30
|
+
const focused = props.rows[cursor - 1];
|
|
31
|
+
const offset = useProfileScroll(props, cursor);
|
|
32
|
+
const current = focused?.id ?? selected;
|
|
33
|
+
function action(name) {
|
|
34
|
+
if (!current)
|
|
35
|
+
return;
|
|
36
|
+
if (name === 'Duplicate')
|
|
37
|
+
props.dispatch({ type: 'kit-duplicate', id: current });
|
|
38
|
+
if (name === 'Delete')
|
|
39
|
+
props.dispatch({ type: 'kit-delete', id: current });
|
|
40
|
+
// EXIT A, NOT A POINTER WRITE (RV-P0-3): the three slots and the pointer move
|
|
41
|
+
// together, because "the default" IS the disk binding (D-13).
|
|
42
|
+
if (name === 'Set default')
|
|
43
|
+
props.dispatch({ type: 'kit-default', id: current });
|
|
44
|
+
}
|
|
45
|
+
useInput((input, key) => {
|
|
46
|
+
if (props.isActive === false)
|
|
47
|
+
return;
|
|
48
|
+
if (key.escape) {
|
|
49
|
+
props.onBack();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (key.upArrow || (key.tab && key.shift)) {
|
|
53
|
+
const next = (cursor - 1 + count) % count;
|
|
54
|
+
setIndex(next);
|
|
55
|
+
if (props.rows[next - 1])
|
|
56
|
+
setSelected(props.rows[next - 1].id);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (key.downArrow || key.tab) {
|
|
60
|
+
const next = (cursor + 1) % count;
|
|
61
|
+
setIndex(next);
|
|
62
|
+
if (props.rows[next - 1])
|
|
63
|
+
setSelected(props.rows[next - 1].id);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (key.return) {
|
|
67
|
+
if (cursor === 0) {
|
|
68
|
+
props.dispatch({ type: 'kit-open', id: null });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (focused) {
|
|
72
|
+
props.dispatch({ type: 'kit-apply', id: focused.id });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
action(ACTIONS[cursor - 1 - props.rows.length]);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (key.ctrl || key.meta)
|
|
79
|
+
return;
|
|
80
|
+
const letter = input.toLowerCase();
|
|
81
|
+
// `e` opens the focused kit; `d` and `s` are the two destructive/intent-bearing
|
|
82
|
+
// single keys. ALL THREE require a focused KIT ROW, so none of them can fire
|
|
83
|
+
// from `New kit` or from the action rows at the bottom.
|
|
84
|
+
if (!focused)
|
|
85
|
+
return;
|
|
86
|
+
if (letter === 'e') {
|
|
87
|
+
props.dispatch({ type: 'kit-open', id: focused.id });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (letter === 'd') {
|
|
91
|
+
props.dispatch({ type: 'kit-delete', id: focused.id });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (letter === 's')
|
|
95
|
+
action('Set default');
|
|
96
|
+
});
|
|
97
|
+
const lines = [NEW_KIT, ...props.rows.map((row) => `${row.label} ${row.summary}`),
|
|
98
|
+
...ACTIONS];
|
|
99
|
+
const rows = lines.map((line, at) => (_jsx(Text, { wrap: "truncate", color: at === cursor ? props.theme.accent : props.theme.muted, children: `${at === cursor ? glyphs.caret : ' '} ${clipProfileText(line, props.cols - 8)}` }, at)));
|
|
100
|
+
if (!props.rows.length) {
|
|
101
|
+
rows.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: clipProfileText('No kits yet. Press Enter on New kit to build one from this setup.', props.cols - 8) }, "empty"));
|
|
102
|
+
}
|
|
103
|
+
return _jsx(OverlayFrame, { ...props, title: props.error ?? 'Agent kits', hint: "Enter apply | e edit | d delete | s set default | Esc back", rows: rows, scrollOffset: offset });
|
|
104
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent-kit picker: `ctrl+K` (agent-model-kits §4.6 / D-10 / D-11).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* A LIST PLUS A DETAIL BLOCK, not one row per kit with three columns: three
|
|
7
|
+
* connections never fit on a 72-column terminal, and the `OverlayFrame` detail
|
|
8
|
+
* idiom is how this package already says everything that does not fit in a row
|
|
9
|
+
* (`ModelProfilePicker`, the compaction card). The list carries names and
|
|
10
|
+
* markers only, so the choice stays scannable at any width.
|
|
11
|
+
*
|
|
12
|
+
* THE OVERLAY IS READ-ONLY (R-11): Enter applies for this session, Esc leaves
|
|
13
|
+
* everything as it was. That is what makes moving `killToEnd` off `Ctrl+K`
|
|
14
|
+
* (D-15) a one-time surprise instead of a risk - a user who presses the old
|
|
15
|
+
* key gets a reversible screen, not a deleted line.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import type { ModelProfilesConfig } from '../../config/model-profiles.js';
|
|
19
|
+
import type { CliConfig } from '../../config/schema.js';
|
|
20
|
+
import type { ActiveKit } from '../../config/kit-resolution.js';
|
|
21
|
+
import type { Theme } from '../theme.js';
|
|
22
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
23
|
+
import { type KitConnectionsSource } from './kit-picker-state.js';
|
|
24
|
+
export interface KitPickerProps {
|
|
25
|
+
isActive?: boolean;
|
|
26
|
+
theme: Theme;
|
|
27
|
+
caps: TermCapabilities;
|
|
28
|
+
cols: number;
|
|
29
|
+
maxRows: number;
|
|
30
|
+
scrollOffset: number;
|
|
31
|
+
onScrollClamp?: (offset: number) => void;
|
|
32
|
+
library?: ModelProfilesConfig | undefined;
|
|
33
|
+
config: CliConfig;
|
|
34
|
+
/**
|
|
35
|
+
* The config file's bindings, for the `Current setup` detail block (§4.6).
|
|
36
|
+
*
|
|
37
|
+
* OMITTED BY EMBEDDED HOSTS AND TESTS, in which case the block falls back to
|
|
38
|
+
* `config` - which is exactly right when no kit is in effect, since the live
|
|
39
|
+
* config then IS the file.
|
|
40
|
+
*/
|
|
41
|
+
diskConfig?: KitConnectionsSource | undefined;
|
|
42
|
+
active: ActiveKit;
|
|
43
|
+
/** Only the APPLY is refused while work is in flight; browsing still works. */
|
|
44
|
+
busy: boolean;
|
|
45
|
+
/** `null` = `Current setup`, i.e. release the session kit. */
|
|
46
|
+
onChoose: (kitId: string | null) => void;
|
|
47
|
+
onClose: () => void;
|
|
48
|
+
}
|
|
49
|
+
export declare function KitPicker(props: KitPickerProps): React.ReactElement;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The Agent-kit picker: `ctrl+K` (agent-model-kits §4.6 / D-10 / D-11).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* A LIST PLUS A DETAIL BLOCK, not one row per kit with three columns: three
|
|
8
|
+
* connections never fit on a 72-column terminal, and the `OverlayFrame` detail
|
|
9
|
+
* idiom is how this package already says everything that does not fit in a row
|
|
10
|
+
* (`ModelProfilePicker`, the compaction card). The list carries names and
|
|
11
|
+
* markers only, so the choice stays scannable at any width.
|
|
12
|
+
*
|
|
13
|
+
* THE OVERLAY IS READ-ONLY (R-11): Enter applies for this session, Esc leaves
|
|
14
|
+
* everything as it was. That is what makes moving `killToEnd` off `Ctrl+K`
|
|
15
|
+
* (D-15) a one-time surprise instead of a risk - a user who presses the old
|
|
16
|
+
* key gets a reversible screen, not a deleted line.
|
|
17
|
+
*/
|
|
18
|
+
import { useState } from 'react';
|
|
19
|
+
import { Text, useInput } from 'ink';
|
|
20
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
21
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
22
|
+
import { clipProfileText, useProfileScroll } from './ModelProfilePicker.js';
|
|
23
|
+
import { kitDetail, kitPickHint, kitRows, NO_KITS_HINT } from './kit-picker-state.js';
|
|
24
|
+
export function KitPicker(props) {
|
|
25
|
+
const rows = kitRows({ library: props.library, active: props.active });
|
|
26
|
+
const glyphs = pickGlyphs(props.caps);
|
|
27
|
+
const [index, setIndex] = useState(0);
|
|
28
|
+
const count = rows.length;
|
|
29
|
+
const cursor = Math.min(index, count - 1);
|
|
30
|
+
const focused = rows[cursor];
|
|
31
|
+
const offset = useProfileScroll(props, cursor);
|
|
32
|
+
const hasKits = (props.library?.kits ?? []).length > 0;
|
|
33
|
+
const body = rows.map((row, at) => (_jsx(Text, { wrap: "truncate", color: at === cursor ? props.theme.accent : props.theme.muted, children: `${at === cursor ? glyphs.caret : ' '} ${clipProfileText(row.label, props.cols - 8)}` }, row.id ?? 'current')));
|
|
34
|
+
if (!hasKits) {
|
|
35
|
+
body.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: clipProfileText(NO_KITS_HINT, props.cols - 8) }, "empty"));
|
|
36
|
+
}
|
|
37
|
+
// THE DETAIL BLOCK DESCRIBES THE FOCUSED ROW ONLY. Shown last so a narrow
|
|
38
|
+
// terminal clips the detail, never the choice.
|
|
39
|
+
for (const [at, line] of kitDetail({ row: focused, library: props.library,
|
|
40
|
+
config: props.config, diskConfig: props.diskConfig }).entries()) {
|
|
41
|
+
body.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: clipProfileText(` ${line}`, props.cols - 8) }, `detail-${at}`));
|
|
42
|
+
}
|
|
43
|
+
useInput((_input, key) => {
|
|
44
|
+
if (props.isActive === false)
|
|
45
|
+
return;
|
|
46
|
+
if (key.escape) {
|
|
47
|
+
props.onClose();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (key.upArrow || (key.tab && key.shift))
|
|
51
|
+
setIndex((cursor - 1 + count) % count);
|
|
52
|
+
else if (key.downArrow || key.tab)
|
|
53
|
+
setIndex((cursor + 1) % count);
|
|
54
|
+
else if (key.return)
|
|
55
|
+
props.onChoose(focused.id);
|
|
56
|
+
});
|
|
57
|
+
return _jsx(OverlayFrame, { title: props.busy ? 'Agent kits (running)' : 'Agent kits', hint: kitPickHint({ busy: props.busy, hasKits }), rows: body, scrollOffset: offset, onScrollClamp: props.onScrollClamp, maxRows: props.maxRows, cols: props.cols, theme: props.theme, caps: props.caps });
|
|
58
|
+
}
|
|
@@ -64,7 +64,12 @@ function usePickerState(props) {
|
|
|
64
64
|
const actions = props.manager ? ['New', 'Edit', 'Duplicate', 'Delete'] : ['Create profile'];
|
|
65
65
|
// THE SUBAGENT PICKER swaps the `Current custom` row for TWO built-in rows:
|
|
66
66
|
// `Same as main` (selection id null) and `Fast tier model` (the sentinel).
|
|
67
|
-
|
|
67
|
+
//
|
|
68
|
+
// THE BINDING PICKER HAS NEITHER (agent-model-kits §4.5): a kit slot can only
|
|
69
|
+
// name a profile, so `prefix` is 0 there even though it is a selection page -
|
|
70
|
+
// "no profile" is not a choice a kit may make.
|
|
71
|
+
const prefix = props.role === 'subagent' ? 2 : props.role === 'binding' ? 0
|
|
72
|
+
: props.manager ? 0 : 1;
|
|
68
73
|
const count = entries.length + prefix + actions.length;
|
|
69
74
|
const cursor = Math.min(index, count - 1);
|
|
70
75
|
const focused = entries[cursor - prefix];
|
|
@@ -89,7 +94,7 @@ function usePickerInput(props, state) {
|
|
|
89
94
|
return;
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
|
-
else if (!props.manager && cursor === 0) {
|
|
97
|
+
else if (!props.manager && props.role !== 'binding' && cursor === 0) {
|
|
93
98
|
props.onSelect(null);
|
|
94
99
|
return;
|
|
95
100
|
}
|
|
@@ -164,7 +169,10 @@ function usePickerInput(props, state) {
|
|
|
164
169
|
function currentSelectionId(props) {
|
|
165
170
|
if (props.selectedIdOverride !== undefined)
|
|
166
171
|
return props.selectedIdOverride;
|
|
167
|
-
|
|
172
|
+
// `subagent` and `binding` have no `${role}Id` key: their selection arrives
|
|
173
|
+
// through `selectedIdOverride`, so looking one up would read a key that does
|
|
174
|
+
// not exist on the library.
|
|
175
|
+
if (props.role === 'subagent' || props.role === 'binding')
|
|
168
176
|
return undefined;
|
|
169
177
|
return props.profiles[`${props.role}Id`];
|
|
170
178
|
}
|
|
@@ -190,7 +198,7 @@ function buildPickerRows(props, state) {
|
|
|
190
198
|
lines.push(`${current === SUBAGENT_FAST_ROW_ID ? '[Selected] ' : ''}Fast tier model`
|
|
191
199
|
+ `${props.fastTierOff === true ? ' (fast tier off)' : ''}`);
|
|
192
200
|
}
|
|
193
|
-
else if (!props.manager)
|
|
201
|
+
else if (!props.manager && props.role !== 'binding')
|
|
194
202
|
lines.push('Current custom');
|
|
195
203
|
lines.push(...entries.map((entry) => profilePickerLabel(props, entry)));
|
|
196
204
|
lines.push(...actions);
|
|
@@ -200,7 +208,8 @@ function buildPickerRows(props, state) {
|
|
|
200
208
|
// The subagent picker has no `Current custom` row to describe; its
|
|
201
209
|
// built-in rows already say everything there is to say.
|
|
202
210
|
const detail = props.role === 'subagent' ? focused
|
|
203
|
-
: !props.manager && cursor === 0 ? props.customProfile
|
|
211
|
+
: !props.manager && props.role !== 'binding' && cursor === 0 ? props.customProfile
|
|
212
|
+
: focused ?? selected;
|
|
204
213
|
if (detail) {
|
|
205
214
|
rows.push(_jsxs(Text, { wrap: "truncate", color: props.theme.muted, children: [detail.provider, ":", detail.model || '(not set)', " ", host(detail), detail === props.customProfile ? '' : ` #${detail.id}`] }, "detail"));
|
|
206
215
|
rows.push(_jsx(Text, { wrap: "truncate", color: props.theme.muted, children: detail.apiKey ? 'Profile key: Configured'
|
|
@@ -215,7 +224,8 @@ export function ModelProfilePicker(props) {
|
|
|
215
224
|
usePickerInput(props, state);
|
|
216
225
|
return _jsx(OverlayFrame, { ...props, title: props.error ?? (props.manager ? 'Manage profiles'
|
|
217
226
|
: props.role === 'subagent' ? 'Subagent model'
|
|
218
|
-
:
|
|
227
|
+
: props.role === 'binding' ? 'Choose profile'
|
|
228
|
+
: `${props.role === 'main' ? 'Main' : 'Fast'} profile`), rows: buildPickerRows(props, state), scrollOffset: state.searching ? 0 : state.offset, hint: state.searching ? 'Type search | Enter done | Esc back'
|
|
219
229
|
: props.manager ? 'Enter action | e edit | d delete | Esc back'
|
|
220
230
|
: 'Enter select | e edit | d delete | Esc back' });
|
|
221
231
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prompt editor page (quick-prompt-library §4.4 / D-11).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`. Labels and the footer
|
|
5
|
+
* are English like every other overlay; every sentence that explains a REFUSAL comes
|
|
6
|
+
* from `prompts/copy.ts`.
|
|
7
|
+
*
|
|
8
|
+
* SELF-MANAGED `OverlayFrame`, not the controlled one. The two modes are a
|
|
9
|
+
* discriminated union (`children?: undefined`), and this page lays out a multi-row
|
|
10
|
+
* body field that owns its own scrolling: handing it over as `rows` would mean the
|
|
11
|
+
* frame counts a body's lines as rows and windows them again (P2-2).
|
|
12
|
+
*
|
|
13
|
+
* TWO WAYS TO SAVE, AND THE SECOND ONE IS NOT DECORATION. `Ctrl+S` is the fast path,
|
|
14
|
+
* and it is a byte a terminal can eat (XON/XOFF) - so `Tab` to the `Save` row and
|
|
15
|
+
* `Enter` must do exactly the same thing. That is what keeps "I cannot save" from
|
|
16
|
+
* being a bug a whole class of terminals can reproduce.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import type { Theme } from '../theme.js';
|
|
20
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
21
|
+
import { type PromptLibraryState } from './prompt-library-state.js';
|
|
22
|
+
export interface PromptEditorPageProps {
|
|
23
|
+
isActive: boolean;
|
|
24
|
+
state: PromptLibraryState;
|
|
25
|
+
cols: number;
|
|
26
|
+
maxRows: number;
|
|
27
|
+
theme: Theme;
|
|
28
|
+
caps: TermCapabilities;
|
|
29
|
+
reducedMotion?: boolean;
|
|
30
|
+
/** One field changed; the page never owns the text, only the caret. */
|
|
31
|
+
onDraft: (patch: {
|
|
32
|
+
title?: string;
|
|
33
|
+
content?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
tags?: string;
|
|
36
|
+
}) => void;
|
|
37
|
+
onSave: () => void;
|
|
38
|
+
onEsc: () => void;
|
|
39
|
+
onCancel: () => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The page.
|
|
43
|
+
*
|
|
44
|
+
* The draft lives in the parent (the reducer owns it, so `Save` and `Esc` can see the
|
|
45
|
+
* same text); this component owns only what no other surface needs: which row has
|
|
46
|
+
* focus, and where each single-line field's caret sits.
|
|
47
|
+
*/
|
|
48
|
+
export declare function PromptEditorPage(props: PromptEditorPageProps): React.ReactElement;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The prompt editor page (quick-prompt-library §4.4 / D-11).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`. Labels and the footer
|
|
6
|
+
* are English like every other overlay; every sentence that explains a REFUSAL comes
|
|
7
|
+
* from `prompts/copy.ts`.
|
|
8
|
+
*
|
|
9
|
+
* SELF-MANAGED `OverlayFrame`, not the controlled one. The two modes are a
|
|
10
|
+
* discriminated union (`children?: undefined`), and this page lays out a multi-row
|
|
11
|
+
* body field that owns its own scrolling: handing it over as `rows` would mean the
|
|
12
|
+
* frame counts a body's lines as rows and windows them again (P2-2).
|
|
13
|
+
*
|
|
14
|
+
* TWO WAYS TO SAVE, AND THE SECOND ONE IS NOT DECORATION. `Ctrl+S` is the fast path,
|
|
15
|
+
* and it is a byte a terminal can eat (XON/XOFF) - so `Tab` to the `Save` row and
|
|
16
|
+
* `Enter` must do exactly the same thing. That is what keeps "I cannot save" from
|
|
17
|
+
* being a bug a whole class of terminals can reproduce.
|
|
18
|
+
*/
|
|
19
|
+
import { useEffect, useState } from 'react';
|
|
20
|
+
import { Box, Text, useInput } from 'ink';
|
|
21
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
22
|
+
import { stripPasteFrames } from '../paste-frames.js';
|
|
23
|
+
import { stripEnterFrames } from '../enter-frames.js';
|
|
24
|
+
import { stepGrapheme } from '../editor-navigation.js';
|
|
25
|
+
import { OverlayFrame, overlayBodyRows } from '../layout/OverlayFrame.js';
|
|
26
|
+
import { promptErrorCopy } from '../../prompts/copy.js';
|
|
27
|
+
import { PROMPT_EDITOR_FIELDS, fieldIndex, } from './prompt-library-state.js';
|
|
28
|
+
import { EMPTY_TEXT_AREA, PromptTextArea, promptTextAreaReducer, } from './PromptTextArea.js';
|
|
29
|
+
/** Columns the field labels occupy. Fixed, so the values line up. */
|
|
30
|
+
const LABEL_CELLS = 10;
|
|
31
|
+
/** Body rows the page reserves for chrome when the frame is generous. */
|
|
32
|
+
const MIN_BODY_ROWS = 3;
|
|
33
|
+
/** Text fields of the page, in the order Tab visits them. */
|
|
34
|
+
function nextField(at, delta) {
|
|
35
|
+
const count = PROMPT_EDITOR_FIELDS.length;
|
|
36
|
+
return (at + delta + count) % count;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Which deletion key was pressed, if any.
|
|
40
|
+
*
|
|
41
|
+
* `key.delete` IS TREATED AS BACKSPACE, and the reason is measured rather than assumed:
|
|
42
|
+
* ink's parser reports the `0x7f` byte - which is what the BACKSPACE KEY sends on every
|
|
43
|
+
* terminal this product runs on - as `name: 'delete'`, and it hands the component an
|
|
44
|
+
* EMPTY `input` for both it and `ESC [ 3 ~`. The two are therefore indistinguishable at
|
|
45
|
+
* this layer, and the safe reading is the one the keyboard actually produces: backward.
|
|
46
|
+
* A forward-delete binding that fired on Backspace would delete the character the user
|
|
47
|
+
* was not looking at, and there is no undo in this field.
|
|
48
|
+
*/
|
|
49
|
+
function deletionKind(_input, key) {
|
|
50
|
+
return key.backspace === true || key.delete === true ? 'backspace' : null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The page.
|
|
54
|
+
*
|
|
55
|
+
* The draft lives in the parent (the reducer owns it, so `Save` and `Esc` can see the
|
|
56
|
+
* same text); this component owns only what no other surface needs: which row has
|
|
57
|
+
* focus, and where each single-line field's caret sits.
|
|
58
|
+
*/
|
|
59
|
+
export function PromptEditorPage(props) {
|
|
60
|
+
const { state, cols, maxRows, theme, caps } = props;
|
|
61
|
+
const draft = state.draft;
|
|
62
|
+
const glyphs = pickGlyphs(caps);
|
|
63
|
+
const [focus, setFocus] = useState(0);
|
|
64
|
+
// THE CARET STARTS AT THE END OF EACH FIELD, which is where a user expects to append:
|
|
65
|
+
// starting at 0 would put every typed character in FRONT of the prefilled text, so an
|
|
66
|
+
// edit to `Clean code review` would read `FixClean code review`.
|
|
67
|
+
const [cursors, setCursors] = useState(() => ({
|
|
68
|
+
title: draft.title.length,
|
|
69
|
+
description: draft.description.length,
|
|
70
|
+
tags: draft.tags.length,
|
|
71
|
+
}));
|
|
72
|
+
const [content, setContent] = useState({
|
|
73
|
+
...EMPTY_TEXT_AREA,
|
|
74
|
+
buffer: draft.content,
|
|
75
|
+
cursor: draft.content.length,
|
|
76
|
+
});
|
|
77
|
+
const field = PROMPT_EDITOR_FIELDS[Math.min(focus, PROMPT_EDITOR_FIELDS.length - 1)];
|
|
78
|
+
const bodyRows = Math.max(MIN_BODY_ROWS, overlayBodyRows(maxRows, cols) - 5);
|
|
79
|
+
const fieldCols = Math.max(8, cols - LABEL_CELLS - 6);
|
|
80
|
+
// A REFUSAL OWNS THE FOCUS (D-12): the store already names the row that failed, so the
|
|
81
|
+
// caret is moved there instead of leaving the user to read an error in a footer and
|
|
82
|
+
// work out which of four fields it is about. Keyed on `state.error`, so it fires once
|
|
83
|
+
// per distinct refusal rather than on every render.
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (state.error === null)
|
|
86
|
+
return;
|
|
87
|
+
setFocus(fieldIndex(state.errorField));
|
|
88
|
+
}, [state.error, state.errorField]);
|
|
89
|
+
const editSingle = (name, text, key, deletion) => {
|
|
90
|
+
const value = draft[name];
|
|
91
|
+
const cursor = Math.min(cursors[name], value.length);
|
|
92
|
+
if (key.leftArrow || key.rightArrow) {
|
|
93
|
+
setCursors({
|
|
94
|
+
...cursors,
|
|
95
|
+
[name]: stepGrapheme(value, cursor, key.leftArrow ? 'left' : 'right'),
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (deletion === 'backspace') {
|
|
100
|
+
if (cursor <= 0)
|
|
101
|
+
return;
|
|
102
|
+
const from = stepGrapheme(value, cursor, 'left');
|
|
103
|
+
props.onDraft({ [name]: value.slice(0, from) + value.slice(cursor) });
|
|
104
|
+
setCursors({ ...cursors, [name]: from });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (text.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
const next = value.slice(0, cursor) + text + value.slice(cursor);
|
|
110
|
+
props.onDraft({ [name]: next });
|
|
111
|
+
setCursors({ ...cursors, [name]: cursor + text.length });
|
|
112
|
+
};
|
|
113
|
+
const applyContent = (action) => {
|
|
114
|
+
const current = { ...content, buffer: draft.content };
|
|
115
|
+
const next = promptTextAreaReducer(current, action);
|
|
116
|
+
// COMPARED ON THE FIELDS, not on object identity: the reducer echoes its input object
|
|
117
|
+
// back for a no-op move, and that object is rebuilt on every keystroke - so an
|
|
118
|
+
// identity check would both hide the no-op and trigger a render for it.
|
|
119
|
+
if (next.buffer !== current.buffer || next.cursor !== current.cursor)
|
|
120
|
+
setContent(next);
|
|
121
|
+
if (next.buffer !== draft.content)
|
|
122
|
+
props.onDraft({ content: next.buffer });
|
|
123
|
+
};
|
|
124
|
+
useInput((input, key) => {
|
|
125
|
+
if (props.isActive === false)
|
|
126
|
+
return;
|
|
127
|
+
// BEFORE EVERYTHING: a refused save and a dirty Esc are both two-step states, and
|
|
128
|
+
// the accelerator that resolves them must not be swallowed by the text handling
|
|
129
|
+
// below. `Ctrl+S` is the same precedent `SettingsScreen.tsx` set.
|
|
130
|
+
if (key.ctrl && (input === 's' || input === 'S')) {
|
|
131
|
+
props.onSave();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (key.escape) {
|
|
135
|
+
props.onEsc();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (key.tab) {
|
|
139
|
+
setFocus(nextField(focus, key.shift ? -1 : 1));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const clean = stripPasteFrames(stripEnterFrames(input));
|
|
143
|
+
if (field === 'save') {
|
|
144
|
+
if (key.return)
|
|
145
|
+
props.onSave();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (field === 'cancel') {
|
|
149
|
+
if (key.return)
|
|
150
|
+
props.onCancel();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (field === 'content') {
|
|
154
|
+
if (key.upArrow) {
|
|
155
|
+
applyContent({ type: 'up', cols: fieldCols });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (key.downArrow) {
|
|
159
|
+
applyContent({ type: 'down', cols: fieldCols });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (key.return || clean.includes('\n')) {
|
|
163
|
+
applyContent({ type: 'newline' });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const contentDeletion = deletionKind(input, key);
|
|
167
|
+
if (contentDeletion !== null) {
|
|
168
|
+
applyContent({ type: contentDeletion });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (key.leftArrow) {
|
|
172
|
+
applyContent({ type: 'left' });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (key.rightArrow) {
|
|
176
|
+
applyContent({ type: 'right' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (key.ctrl || key.meta)
|
|
180
|
+
return;
|
|
181
|
+
if (clean.length > 0)
|
|
182
|
+
applyContent({ type: 'insert', text: clean });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Single-line fields: Enter advances rather than saving, because there is nothing
|
|
186
|
+
// for a newline to mean in a one-line field.
|
|
187
|
+
if (key.return) {
|
|
188
|
+
setFocus(nextField(focus, 1));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (key.upArrow) {
|
|
192
|
+
setFocus(nextField(focus, -1));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (key.downArrow) {
|
|
196
|
+
setFocus(nextField(focus, 1));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (key.ctrl || key.meta)
|
|
200
|
+
return;
|
|
201
|
+
editSingle(field, clean, key, deletionKind(input, key));
|
|
202
|
+
});
|
|
203
|
+
/** One text field, or an action row. The caret marks the focused one. */
|
|
204
|
+
const row = (name) => {
|
|
205
|
+
const focused = field === name;
|
|
206
|
+
const caret = focused ? glyphs.caret : ' ';
|
|
207
|
+
if (name === 'save' || name === 'cancel') {
|
|
208
|
+
const label = name === 'save' ? 'Save' : 'Cancel';
|
|
209
|
+
return (_jsx(Text, { wrap: "truncate", color: focused ? theme.accent : theme.muted, children: `${caret} [ ${label} ]` }, name));
|
|
210
|
+
}
|
|
211
|
+
const label = name === 'title' ? 'Title' : name === 'content' ? 'Body'
|
|
212
|
+
: name === 'description' ? 'Note' : 'Tags';
|
|
213
|
+
const value = draft[name];
|
|
214
|
+
if (name === 'content') {
|
|
215
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { wrap: "truncate", color: focused ? theme.accent : theme.muted, children: `${caret} ${label.padEnd(LABEL_CELLS)}` }), _jsx(Box, { flexDirection: "column", paddingLeft: 2, children: _jsx(PromptTextArea, { state: { ...content, buffer: draft.content }, cols: fieldCols, rows: bodyRows, focused: focused, theme: theme, caps: caps, ...(props.reducedMotion === undefined ? {} : { reducedMotion: props.reducedMotion }) }) })] }, name));
|
|
216
|
+
}
|
|
217
|
+
const shown = value.length === 0 ? '(empty)' : value;
|
|
218
|
+
return (_jsx(Text, { wrap: "truncate", color: focused ? theme.accent : theme.muted, children: `${caret} ${label.padEnd(LABEL_CELLS)}${shown}` }, name));
|
|
219
|
+
};
|
|
220
|
+
const title = state.error === null
|
|
221
|
+
? (draft.id === null ? 'New prompt' : 'Edit prompt')
|
|
222
|
+
: promptErrorCopy(state.error);
|
|
223
|
+
const hint = state.escArmed
|
|
224
|
+
? 'Esc again discards the draft | Tab next field'
|
|
225
|
+
: 'Tab next field | Enter next line | Ctrl+S save | Esc close';
|
|
226
|
+
return (_jsxs(OverlayFrame, { title: title, hint: hint, maxRows: maxRows, cols: cols, theme: theme, caps: caps, children: [_jsxs(Text, { wrap: "truncate", color: theme.muted, children: [_jsx(Text, { color: draft.favorite ? theme.toolDone : theme.muted, children: draft.favorite ? '[fav] ' : ' ' }), `${[...draft.content].length} chars`] }), _jsx(Box, { flexDirection: "column", children: PROMPT_EDITOR_FIELDS.map((name) => row(name)) })] }));
|
|
227
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prompt manager: `Ctrl+P` (quick-prompt-library §4.4 / §6.4).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* A LIST PLUS A DETAIL BLOCK, the `KitPicker` idiom (F-17): three columns of prompt
|
|
7
|
+
* metadata never fit on a 72-column terminal, and the detail block is how this
|
|
8
|
+
* package already shows everything that does not fit in a row.
|
|
9
|
+
*
|
|
10
|
+
* THE OVERLAY OWNS NO DATA. Every mutation goes out through `onMutate`, and the new
|
|
11
|
+
* snapshot comes back through `library`. An overlay that patched its own copy would
|
|
12
|
+
* show a prompt the disk does not have (I-6), and the first time that happened the
|
|
13
|
+
* user would find out at the next launch.
|
|
14
|
+
*/
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import type { Theme } from '../theme.js';
|
|
17
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
18
|
+
import type { PromptLibrary, PromptLibraryIntent, PromptLibraryMutation, PromptMutationResult } from '../../prompts/types.js';
|
|
19
|
+
export interface PromptLibraryOverlayProps {
|
|
20
|
+
isActive: boolean;
|
|
21
|
+
library: PromptLibrary;
|
|
22
|
+
/** D-21's one-shot hand-off; consumed at most once (I-8). */
|
|
23
|
+
intent?: PromptLibraryIntent;
|
|
24
|
+
cols: number;
|
|
25
|
+
maxRows: number;
|
|
26
|
+
scrollOffset: number;
|
|
27
|
+
onScrollClamp?: (offset: number) => void;
|
|
28
|
+
theme: Theme;
|
|
29
|
+
caps: TermCapabilities;
|
|
30
|
+
reducedMotion?: boolean;
|
|
31
|
+
/** The store, reached through `App` so this component stays a view. */
|
|
32
|
+
onMutate: (action: PromptLibraryMutation) => PromptMutationResult;
|
|
33
|
+
/** One-shot feedback (save, delete, write failure) for `App`'s toast channel. */
|
|
34
|
+
onNotice?: (level: 'info' | 'warn' | 'error', text: string) => void;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
}
|
|
37
|
+
export declare function PromptLibraryOverlay(props: PromptLibraryOverlayProps): React.ReactElement;
|