@aragon-agent/cli 0.6.35 → 0.6.37
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 +3173 -3071
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +38 -3
- package/dist/agent/controller.js +64 -4
- package/dist/agent/headless.js +31 -0
- 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 +131 -4
- 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/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/schema.d.ts +68 -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/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 +310 -46
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +9 -1
- package/dist/ui/Header.js +2 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +21 -1
- package/dist/ui/ScrollbackShell.d.ts +19 -19
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +5 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/TeamPanel.d.ts +26 -44
- package/dist/ui/TeamPanel.js +46 -135
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- 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 +13 -3
- package/dist/ui/layout/header-hints.js +48 -14
- 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 +23 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +17 -2
- 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/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/band.js +14 -1
- package/dist/ui/team-status.d.ts +25 -0
- package/dist/ui/team-status.js +31 -3
- 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 +9 -1
- package/package.json +2 -2
|
@@ -34,7 +34,12 @@ function allKeyRows(times) {
|
|
|
34
34
|
// terminal (`showHint`) and `hints: false`. A key that only exists behind
|
|
35
35
|
// either of them is a key the affected user never learns about, which is the
|
|
36
36
|
// one-shot notice's failure repeated with a different switch.
|
|
37
|
-
|
|
37
|
+
// BOTH KEYS ARE SPELLED OUT, and `Ctrl+N` is written in FULL: `^N` is this
|
|
38
|
+
// product's word for transcript line offset (the header chip and the row below),
|
|
39
|
+
// so abbreviating the mode fallback would put two meanings of `^` on one screen
|
|
40
|
+
// (quick-prompt-library D-1 / P1-7).
|
|
41
|
+
['Shift+Tab / Ctrl+N', 'Toggle plan mode (same as /plan)'],
|
|
42
|
+
['Ctrl+P', 'Quick prompt library: manage, create, reorder, pick'],
|
|
38
43
|
// The ladder clears a draft first (ctrl-c-clear-draft-exit):
|
|
39
44
|
// with a draft x2 clears the input and x3 exits; an empty input
|
|
40
45
|
// keeps the previous x2 exit.
|
|
@@ -53,6 +58,10 @@ function allKeyRows(times) {
|
|
|
53
58
|
['Ctrl+K', 'Agent kits: pick a preset for this session (see /kit)'],
|
|
54
59
|
['Alt+K', 'Delete from the cursor to the end of the line (was Ctrl+K)'],
|
|
55
60
|
['Ctrl+G', 'Expand/collapse status details; disabled while an overlay is open'],
|
|
61
|
+
// The board's key is listed HERE and in the team table below: the roster shows
|
|
62
|
+
// `ctrl+b` only on a terminal wide enough for the hint cell, and a key a user can
|
|
63
|
+
// only learn from a wide terminal is a key the rest never discover (AC-18).
|
|
64
|
+
['Ctrl+B', 'Agents board: every subagent, its facts and its timeline (read-only)'],
|
|
56
65
|
['PgUp / PgDn', 'Scroll a page (transcript, or this overlay)'],
|
|
57
66
|
['Shift+Up / Shift+Down', 'Scroll the transcript a line (full-screen mode)'],
|
|
58
67
|
['Wheel', 'Scroll the transcript (or the open overlay)'],
|
|
@@ -112,6 +121,11 @@ const COMMANDS = [
|
|
|
112
121
|
['/branch', 'Same as Ctrl+L: branch into a new terminal'],
|
|
113
122
|
['/copy', 'Copy last answer; native completion confirmed, OSC 52 request unconfirmed'],
|
|
114
123
|
['/queue', 'Read every pending message in full; no replay or queue mutation'],
|
|
124
|
+
['/prompts', 'Quick prompt library: pick one and fill the draft (Enter fills)'],
|
|
125
|
+
['/agents', 'Open the subagent board: every child, its facts and its timeline'],
|
|
126
|
+
['/prompts list | add | edit | rm', 'Manage the library; `add` opens the editor'],
|
|
127
|
+
['/prompts move <n> <to> | sort',
|
|
128
|
+
'Reorder by hand, or rewrite the order by name/recent/favorite'],
|
|
115
129
|
['/mouse [on|off]', 'Release the mouse to your terminal, or take it back'],
|
|
116
130
|
['/terminal-setup', 'Show newline key bindings without changing terminal settings'],
|
|
117
131
|
['/exit', 'Exit'],
|
|
@@ -133,7 +147,7 @@ const PLAN_ROWS = [
|
|
|
133
147
|
// this one is the reader whose `Shift+Tab` does nothing, and what they need is
|
|
134
148
|
// the reason, not another synonym. Listing the key in both places instead just
|
|
135
149
|
// says it twice and explains it nowhere.
|
|
136
|
-
['Ctrl+
|
|
150
|
+
['Ctrl+N', 'The same toggle, for Windows consoles that deliver Shift+Tab as a plain Tab'],
|
|
137
151
|
['In PLAN mode', 'write_file, edit_file, bash, skill_install and skill_create are refused'],
|
|
138
152
|
['bash', 'Refused in full, including git status - use read_file / glob / grep'],
|
|
139
153
|
['ask_user', 'The agent asks 1-5 multiple-choice questions; Enter takes the recommendation'],
|
|
@@ -147,6 +161,7 @@ const TEAM_ROWS = [
|
|
|
147
161
|
['/team max <n>', 'Fan-out width, 1-10 (5 by default)'],
|
|
148
162
|
['--team / --no-team', 'Start a session with team mode on or off (also ARAGON_TEAM=0)'],
|
|
149
163
|
['team_send / team_wait', 'How subagents message each other; 6 each, one every 15s'],
|
|
164
|
+
['Ctrl+B / /agents', 'The board: every child, its full facts, its 24-event timeline'],
|
|
150
165
|
];
|
|
151
166
|
const FAST_ROWS = [
|
|
152
167
|
['/fast', 'Status: on/off, the model, the cadence, and this session\'s totals'],
|
|
@@ -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;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The prompt manager: `Ctrl+P` (quick-prompt-library §4.4 / §6.4).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* A LIST PLUS A DETAIL BLOCK, the `KitPicker` idiom (F-17): three columns of prompt
|
|
8
|
+
* metadata never fit on a 72-column terminal, and the detail block is how this
|
|
9
|
+
* package already shows everything that does not fit in a row.
|
|
10
|
+
*
|
|
11
|
+
* THE OVERLAY OWNS NO DATA. Every mutation goes out through `onMutate`, and the new
|
|
12
|
+
* snapshot comes back through `library`. An overlay that patched its own copy would
|
|
13
|
+
* show a prompt the disk does not have (I-6), and the first time that happened the
|
|
14
|
+
* user would find out at the next launch.
|
|
15
|
+
*/
|
|
16
|
+
import { useEffect, useReducer, useRef } from 'react';
|
|
17
|
+
import { Text, useInput } from 'ink';
|
|
18
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
19
|
+
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
20
|
+
import { clipProfileText, useProfileScroll } from './ModelProfilePicker.js';
|
|
21
|
+
import { promptDeletedCopy, promptErrorCopy, promptSavedCopy, PROMPT_EMPTY_BODY_COPY, PROMPT_EMPTY_TITLE_COPY } from '../../prompts/copy.js';
|
|
22
|
+
import { INITIAL_PROMPT_LIBRARY_STATE, deleteDetail, finishPromptEditor, libraryDetail, libraryHint, libraryRows, promptLibraryReducer, } from './prompt-library-state.js';
|
|
23
|
+
import { PromptEditorPage } from './PromptEditorPage.js';
|
|
24
|
+
/** The reducer takes the library as a third argument, so it is bound per dispatch. */
|
|
25
|
+
function reduce(state, dispatched) {
|
|
26
|
+
return promptLibraryReducer(state, dispatched.action, dispatched.library);
|
|
27
|
+
}
|
|
28
|
+
export function PromptLibraryOverlay(props) {
|
|
29
|
+
const { library, cols, theme, caps } = props;
|
|
30
|
+
const glyphs = pickGlyphs(caps);
|
|
31
|
+
const [state, rawDispatch] = useReducer(reduce, INITIAL_PROMPT_LIBRARY_STATE);
|
|
32
|
+
const dispatch = (action) => rawDispatch({ action, library });
|
|
33
|
+
const rows = libraryRows(library);
|
|
34
|
+
const count = rows.length;
|
|
35
|
+
const cursor = count === 0 ? 0 : Math.min(state.index, count - 1);
|
|
36
|
+
const focused = rows[cursor];
|
|
37
|
+
const offset = useProfileScroll(props, cursor);
|
|
38
|
+
/**
|
|
39
|
+
* CONSUME THE INTENT EXACTLY ONCE (I-8).
|
|
40
|
+
*
|
|
41
|
+
* A `useRef`, not state: the ref is written before the dispatch is processed, so a
|
|
42
|
+
* StrictMode double-invoke of this effect sees `nonce <= consumed` and does nothing.
|
|
43
|
+
* State would be one render behind and would open the editor twice.
|
|
44
|
+
*/
|
|
45
|
+
const consumed = useRef(0);
|
|
46
|
+
const intent = props.intent;
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!intent || intent.nonce <= consumed.current)
|
|
49
|
+
return;
|
|
50
|
+
consumed.current = intent.nonce;
|
|
51
|
+
dispatch({ type: 'init', entry: intent.entry, library, nonce: intent.nonce });
|
|
52
|
+
}, [intent]);
|
|
53
|
+
/** One mutation, its refusal routed to the same two channels every time. */
|
|
54
|
+
const mutate = (action) => {
|
|
55
|
+
const result = props.onMutate(action);
|
|
56
|
+
if (!result.ok)
|
|
57
|
+
props.onNotice?.('warn', promptErrorCopy(result.code));
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
60
|
+
const save = () => {
|
|
61
|
+
const verdict = finishPromptEditor(state.draft);
|
|
62
|
+
if (!verdict.ok) {
|
|
63
|
+
dispatch({ type: 'failed', code: verdict.code, field: verdict.field });
|
|
64
|
+
props.onNotice?.('warn', promptErrorCopy(verdict.code));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const draft = state.draft;
|
|
68
|
+
const result = draft.id === null
|
|
69
|
+
? mutate({ kind: 'create', draft })
|
|
70
|
+
: mutate({ kind: 'update', id: draft.id, draft });
|
|
71
|
+
if (!result.ok) {
|
|
72
|
+
// THE PAGE DOES NOT CLOSE AND THE DRAFT IS NOT TOUCHED (D-12): the editor is
|
|
73
|
+
// where the user has invested the most, so a refusal must cost nothing.
|
|
74
|
+
dispatch({ type: 'failed', code: result.code });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
dispatch({ type: 'commit', id: result.id ?? draft.id });
|
|
78
|
+
props.onNotice?.('info', promptSavedCopy(draft.title.trim()));
|
|
79
|
+
};
|
|
80
|
+
const confirmDelete = () => {
|
|
81
|
+
const id = state.pendingDeleteId;
|
|
82
|
+
if (!id) {
|
|
83
|
+
dispatch({ type: 'cancelled' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const title = library.prompts.find((prompt) => prompt.id === id)?.title ?? '';
|
|
87
|
+
const result = mutate({ kind: 'delete', id });
|
|
88
|
+
dispatch({ type: 'cancelled' });
|
|
89
|
+
// `promptDeletedCopy`, not `promptSavedCopy`: the two sentences are one word apart
|
|
90
|
+
// and the wrong one tells the user their prompt still exists.
|
|
91
|
+
if (result.ok)
|
|
92
|
+
props.onNotice?.('info', promptDeletedCopy(title));
|
|
93
|
+
};
|
|
94
|
+
useInput((input, key) => {
|
|
95
|
+
if (props.isActive === false)
|
|
96
|
+
return;
|
|
97
|
+
// THE EDITOR PAGE OWNS EVERY KEY WHILE IT IS OPEN, Esc INCLUDED. Two `useInput`
|
|
98
|
+
// hooks are live at once (this one and the page's), so handling Esc in both would
|
|
99
|
+
// dispatch `esc` TWICE for one keypress: the first arms the discard and the second
|
|
100
|
+
// executes it, which silently throws away a drafted body on a single Esc. The page
|
|
101
|
+
// already reports Esc through `onEsc`, so this branch must stay silent.
|
|
102
|
+
if (state.page === 'editor')
|
|
103
|
+
return;
|
|
104
|
+
if (state.page === 'delete') {
|
|
105
|
+
if (key.escape || input === 'n' || input === 'N') {
|
|
106
|
+
dispatch({ type: 'cancelled' });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (key.return || input === 'y' || input === 'Y')
|
|
110
|
+
confirmDelete();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// --- list page ---------------------------------------------------------
|
|
114
|
+
if (key.escape) {
|
|
115
|
+
props.onClose();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// THE RAW SEQUENCE **AND** THE PARSED CHORD (D-23, corrected by measurement).
|
|
119
|
+
// Ink 5's `parse-keypress` recognises `\x1b[1;5A`, reports it as
|
|
120
|
+
// `ctrl + upArrow`, and hands the component an EMPTY `input` - so the sequence
|
|
121
|
+
// comparison alone would be a dead branch on this runtime, and the chord
|
|
122
|
+
// comparison alone would miss a host that passes the raw bytes through. Either
|
|
123
|
+
// shape reorders; `\x1b[A` (modifier lost) matches neither and only moves focus.
|
|
124
|
+
const reorderUp = input === '\x1b[1;5A' || (key.ctrl && key.upArrow);
|
|
125
|
+
const reorderDown = input === '\x1b[1;5B' || (key.ctrl && key.downArrow);
|
|
126
|
+
if (reorderUp || reorderDown) {
|
|
127
|
+
if (!focused)
|
|
128
|
+
return;
|
|
129
|
+
const result = mutate({ kind: 'move', id: focused.id, delta: reorderUp ? -1 : 1 });
|
|
130
|
+
if (result.ok)
|
|
131
|
+
dispatch({ type: 'list', focusId: focused.id });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (key.upArrow || (key.tab && key.shift)) {
|
|
135
|
+
dispatch({ type: 'focus', index: count === 0 ? 0 : (cursor - 1 + count) % count });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (key.downArrow) {
|
|
139
|
+
dispatch({ type: 'focus', index: count === 0 ? 0 : (cursor + 1) % count });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (key.return) {
|
|
143
|
+
if (focused)
|
|
144
|
+
dispatch({ type: 'edit', id: focused.id, library });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (key.ctrl || key.meta)
|
|
148
|
+
return;
|
|
149
|
+
if (input === 'a') {
|
|
150
|
+
dispatch({ type: 'create' });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (input === 'd' && focused) {
|
|
154
|
+
dispatch({ type: 'delete', id: focused.id });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (input === 'f' && focused) {
|
|
158
|
+
const prompt = library.prompts.find((entry) => entry.id === focused.id);
|
|
159
|
+
mutate({ kind: 'favorite', id: focused.id, favorite: prompt?.favorite !== true });
|
|
160
|
+
dispatch({ type: 'list', focusId: focused.id });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (state.page === 'editor') {
|
|
164
|
+
return (_jsx(PromptEditorPage, { isActive: props.isActive, state: state, cols: cols, maxRows: props.maxRows, theme: theme, caps: caps, ...(props.reducedMotion === undefined ? {} : { reducedMotion: props.reducedMotion }), onDraft: (patch) => dispatch({ type: 'draft', patch }), onSave: save, onEsc: () => dispatch({ type: 'esc' }), onCancel: () => dispatch({ type: 'cancelled' }) }));
|
|
165
|
+
}
|
|
166
|
+
const body = [];
|
|
167
|
+
if (state.page === 'delete') {
|
|
168
|
+
for (const [at, line] of deleteDetail(library, state.pendingDeleteId).entries()) {
|
|
169
|
+
body.push(_jsx(Text, { wrap: "truncate", color: at === 0 ? theme.accent : theme.muted, children: clipProfileText(line, cols - 8) }, `del-${at}`));
|
|
170
|
+
}
|
|
171
|
+
return (_jsx(OverlayFrame, { title: "Delete prompt", hint: libraryHint(state, count), rows: body, scrollOffset: 0, maxRows: props.maxRows, cols: cols, theme: theme, caps: caps }));
|
|
172
|
+
}
|
|
173
|
+
for (const [at, row] of rows.entries()) {
|
|
174
|
+
const isCursor = at === cursor;
|
|
175
|
+
body.push(_jsx(Text, { wrap: "truncate", color: isCursor ? theme.accent : theme.muted, children: `${isCursor ? glyphs.caret : ' '} ${clipProfileText(row.label, cols - 8)}` }, row.id));
|
|
176
|
+
}
|
|
177
|
+
if (count === 0) {
|
|
178
|
+
// TWO ROWS, NOT AN EMPTY SCREEN (§9.7): an empty list that says nothing reads as a
|
|
179
|
+
// broken panel, and the key that fixes it (`a`) is not otherwise discoverable. The
|
|
180
|
+
// sentences come from `prompts/copy.ts` so this state and the picker's own empty
|
|
181
|
+
// state cannot drift into two wordings of the same advice.
|
|
182
|
+
body.push(_jsx(Text, { wrap: "truncate", color: theme.muted, children: clipProfileText(PROMPT_EMPTY_TITLE_COPY, cols - 8) }, "empty-title"));
|
|
183
|
+
body.push(_jsx(Text, { wrap: "truncate", color: theme.muted, children: clipProfileText(PROMPT_EMPTY_BODY_COPY, cols - 8) }, "empty-body"));
|
|
184
|
+
}
|
|
185
|
+
// THE DETAIL BLOCK DESCRIBES THE FOCUSED ROW ONLY, and it is last so a narrow
|
|
186
|
+
// terminal clips the description rather than the list.
|
|
187
|
+
for (const [at, line] of libraryDetail(focused, library, cols).entries()) {
|
|
188
|
+
body.push(_jsx(Text, { wrap: "truncate", color: theme.muted, children: clipProfileText(` ${line}`, cols - 8) }, `detail-${at}`));
|
|
189
|
+
}
|
|
190
|
+
return (_jsx(OverlayFrame, { title: `Prompts${count > 0 ? ` (${count})` : ''}`, hint: libraryHint(state, count), rows: body, scrollOffset: offset, onScrollClamp: props.onScrollClamp, maxRows: props.maxRows, cols: cols, theme: theme, caps: caps }));
|
|
191
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prompt picker: `/prompts` (quick-prompt-library §4.5).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
|
|
5
|
+
*
|
|
6
|
+
* THE QUERY BOX IS THE LIST. There is no separate search field and no `/` mode: the
|
|
7
|
+
* picker exists for one keystroke-to-answer gesture, and an extra mode to enter is an
|
|
8
|
+
* extra thing that can be wrong about which component owns the next key.
|
|
9
|
+
*
|
|
10
|
+
* `Ctrl+Enter` IS RECOGNISED BY ITS BYTES, NOT BY `key.ctrl && key.return`. The stdin
|
|
11
|
+
* filter normalises every modified Enter (`\x1b[13;5u`, the win32 record, `\x1b[13;2u`)
|
|
12
|
+
* into `ENTER_NEWLINE_FRAME` before Ink sees it (enter-sequences.ts), so the parsed
|
|
13
|
+
* chord never occurs - a branch written that way is dead code that LOOKS implemented
|
|
14
|
+
* (R-11). On a terminal without keyboard enhancement the chord degrades to a plain
|
|
15
|
+
* `Enter`, which fills without sending; the footer therefore names `Enter fill` first.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import type { Theme } from '../theme.js';
|
|
19
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
20
|
+
import type { PromptLibrary, PromptLibraryEntry } from '../../prompts/types.js';
|
|
21
|
+
export interface PromptPickerProps {
|
|
22
|
+
isActive: boolean;
|
|
23
|
+
library: PromptLibrary;
|
|
24
|
+
cols: number;
|
|
25
|
+
maxRows: number;
|
|
26
|
+
scrollOffset: number;
|
|
27
|
+
onScrollClamp?: (offset: number) => void;
|
|
28
|
+
theme: Theme;
|
|
29
|
+
caps: TermCapabilities;
|
|
30
|
+
/** `submit` is true only for `Ctrl+Enter` (see `SUBMIT_FRAMES`). */
|
|
31
|
+
onFill: (id: string, submit: boolean) => void;
|
|
32
|
+
/** `Tab` / `a`: hand over to the manager without leaving the overlay stack. */
|
|
33
|
+
onManage: (entry: PromptLibraryEntry) => void;
|
|
34
|
+
onClose: () => void;
|
|
35
|
+
}
|
|
36
|
+
export declare function PromptPicker(props: PromptPickerProps): React.ReactElement;
|