@crouton-kit/humanloop 0.1.2 → 0.1.3
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/dist/cli.js +81 -91
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/tui/app.d.ts +7 -3
- package/dist/tui/app.js +284 -152
- package/dist/tui/input.d.ts +2 -1
- package/dist/tui/input.js +123 -120
- package/dist/tui/render.d.ts +9 -5
- package/dist/tui/render.js +199 -149
- package/dist/tui/tmux.d.ts +6 -2
- package/dist/types.d.ts +57 -45
- package/dist/types.js +1 -1
- package/dist/visuals/generate.d.ts +9 -4
- package/dist/visuals/generate.js +30 -39
- package/package.json +14 -2
package/dist/tui/app.js
CHANGED
|
@@ -1,201 +1,333 @@
|
|
|
1
1
|
import { readFileSync, existsSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
2
2
|
import { setupTerminal, restoreTerminal, parseKeypress, getTerminalSize } from './terminal.js';
|
|
3
|
-
import {
|
|
4
|
-
import { handleKeypress } from './input.js';
|
|
3
|
+
import { diffFrame, renderOverview, renderItemReview, renderFinal } from './render.js';
|
|
4
|
+
import { handleKeypress, assignShortcuts } from './input.js';
|
|
5
5
|
import { readConversation } from '../conversation/reader.js';
|
|
6
|
-
import {
|
|
7
|
-
// Validate the parsed JSON before opening the terminal so bad agent input
|
|
8
|
-
// fails with a clear error instead of crashing inside the TUI.
|
|
6
|
+
import { defaultGenerateVisual } from '../visuals/generate.js';
|
|
9
7
|
export function validateInput(parsed) {
|
|
10
8
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
11
|
-
throw new Error('
|
|
9
|
+
throw new Error('Deck file must be a JSON object with an `interactions` array');
|
|
12
10
|
}
|
|
13
11
|
const obj = parsed;
|
|
14
|
-
if (!Array.isArray(obj.
|
|
15
|
-
throw new Error('`
|
|
12
|
+
if (!Array.isArray(obj.interactions)) {
|
|
13
|
+
throw new Error('`interactions` must be an array');
|
|
16
14
|
}
|
|
17
|
-
if (obj.
|
|
18
|
-
throw new Error('No
|
|
15
|
+
if (obj.interactions.length === 0) {
|
|
16
|
+
throw new Error('No interactions in deck file');
|
|
19
17
|
}
|
|
20
18
|
if (obj.title !== undefined && typeof obj.title !== 'string') {
|
|
21
19
|
throw new Error('`title` must be a string when present');
|
|
22
20
|
}
|
|
23
21
|
const seen = new Set();
|
|
24
22
|
const validated = [];
|
|
25
|
-
for (let i = 0; i < obj.
|
|
26
|
-
const
|
|
27
|
-
const where = `
|
|
28
|
-
if (typeof
|
|
23
|
+
for (let i = 0; i < obj.interactions.length; i++) {
|
|
24
|
+
const raw = obj.interactions[i];
|
|
25
|
+
const where = `interactions[${i}]`;
|
|
26
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
29
27
|
throw new Error(`${where} must be an object`);
|
|
30
28
|
}
|
|
31
|
-
if (typeof
|
|
29
|
+
if (typeof raw.id !== 'string' || raw.id === '') {
|
|
32
30
|
throw new Error(`${where}.id must be a non-empty string`);
|
|
33
31
|
}
|
|
34
|
-
if (seen.has(
|
|
35
|
-
throw new Error(`Duplicate
|
|
36
|
-
}
|
|
37
|
-
seen.add(
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (typeof
|
|
49
|
-
throw new Error(`${
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
throw new Error(`${where}.options must have at least 2 items (got ${q.options.length})`);
|
|
54
|
-
const opts = [];
|
|
55
|
-
for (let j = 0; j < q.options.length; j++) {
|
|
56
|
-
if (typeof q.options[j] !== 'string')
|
|
57
|
-
throw new Error(`${where}.options[${j}] must be a string`);
|
|
58
|
-
opts.push(q.options[j]);
|
|
32
|
+
if (seen.has(raw.id)) {
|
|
33
|
+
throw new Error(`Duplicate interaction id: ${JSON.stringify(raw.id)}`);
|
|
34
|
+
}
|
|
35
|
+
seen.add(raw.id);
|
|
36
|
+
if (typeof raw.title !== 'string' || raw.title === '') {
|
|
37
|
+
throw new Error(`${where}.title must be a non-empty string`);
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(raw.options)) {
|
|
40
|
+
throw new Error(`${where}.options must be an array`);
|
|
41
|
+
}
|
|
42
|
+
const opts = [];
|
|
43
|
+
for (let j = 0; j < raw.options.length; j++) {
|
|
44
|
+
const o = raw.options[j];
|
|
45
|
+
const owhere = `${where}.options[${j}]`;
|
|
46
|
+
if (typeof o !== 'object' || o === null || Array.isArray(o)) {
|
|
47
|
+
throw new Error(`${owhere} must be an object`);
|
|
48
|
+
}
|
|
49
|
+
if (typeof o.id !== 'string' || o.id === '') {
|
|
50
|
+
throw new Error(`${owhere}.id must be a non-empty string`);
|
|
59
51
|
}
|
|
60
|
-
|
|
52
|
+
if (typeof o.label !== 'string') {
|
|
53
|
+
throw new Error(`${owhere}.label must be a string`);
|
|
54
|
+
}
|
|
55
|
+
const opt = { id: o.id, label: o.label };
|
|
56
|
+
if (o.description !== undefined) {
|
|
57
|
+
if (typeof o.description !== 'string')
|
|
58
|
+
throw new Error(`${owhere}.description must be a string`);
|
|
59
|
+
opt.description = o.description;
|
|
60
|
+
}
|
|
61
|
+
if (o.shortcut !== undefined) {
|
|
62
|
+
if (typeof o.shortcut !== 'string')
|
|
63
|
+
throw new Error(`${owhere}.shortcut must be a string`);
|
|
64
|
+
opt.shortcut = o.shortcut;
|
|
65
|
+
}
|
|
66
|
+
opts.push(opt);
|
|
67
|
+
}
|
|
68
|
+
const interaction = { id: raw.id, title: raw.title, options: opts };
|
|
69
|
+
if (raw.subtitle !== undefined) {
|
|
70
|
+
if (typeof raw.subtitle !== 'string')
|
|
71
|
+
throw new Error(`${where}.subtitle must be a string`);
|
|
72
|
+
interaction.subtitle = raw.subtitle;
|
|
61
73
|
}
|
|
62
|
-
|
|
63
|
-
if (typeof
|
|
64
|
-
throw new Error(`${where}.
|
|
65
|
-
|
|
66
|
-
throw new Error(`${where}.rationale must be a string`);
|
|
67
|
-
validated.push({ id: q.id, type: 'freetext', question: q.question, rationale: q.rationale });
|
|
74
|
+
if (raw.body !== undefined) {
|
|
75
|
+
if (typeof raw.body !== 'string')
|
|
76
|
+
throw new Error(`${where}.body must be a string`);
|
|
77
|
+
interaction.body = raw.body;
|
|
68
78
|
}
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
if (raw.bodyPath !== undefined) {
|
|
80
|
+
if (typeof raw.bodyPath !== 'string')
|
|
81
|
+
throw new Error(`${where}.bodyPath must be a string`);
|
|
82
|
+
interaction.bodyPath = raw.bodyPath;
|
|
71
83
|
}
|
|
84
|
+
if (raw.freetextLabel !== undefined) {
|
|
85
|
+
if (typeof raw.freetextLabel !== 'string')
|
|
86
|
+
throw new Error(`${where}.freetextLabel must be a string`);
|
|
87
|
+
interaction.freetextLabel = raw.freetextLabel;
|
|
88
|
+
}
|
|
89
|
+
if (raw.allowFreetext !== undefined) {
|
|
90
|
+
if (typeof raw.allowFreetext !== 'boolean')
|
|
91
|
+
throw new Error(`${where}.allowFreetext must be a boolean`);
|
|
92
|
+
interaction.allowFreetext = raw.allowFreetext;
|
|
93
|
+
}
|
|
94
|
+
if (raw.kind !== undefined) {
|
|
95
|
+
interaction.kind = raw.kind;
|
|
96
|
+
}
|
|
97
|
+
validated.push(interaction);
|
|
72
98
|
}
|
|
73
|
-
|
|
99
|
+
const deck = { interactions: validated };
|
|
100
|
+
if (obj.title !== undefined)
|
|
101
|
+
deck.title = obj.title;
|
|
102
|
+
return deck;
|
|
74
103
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
const raw = readFileSync(decisionsPath, 'utf8');
|
|
80
|
-
const parsed = JSON.parse(raw);
|
|
81
|
-
const input = validateInput(parsed);
|
|
82
|
-
const state = {
|
|
104
|
+
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
105
|
+
function buildInitialState(deck) {
|
|
106
|
+
return {
|
|
83
107
|
phase: 'overview',
|
|
84
108
|
currentIndex: 0,
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
interactions: deck.interactions,
|
|
110
|
+
responses: new Map(),
|
|
87
111
|
visuals: new Map(),
|
|
88
112
|
inputMode: null,
|
|
89
113
|
selectedAction: 0,
|
|
90
114
|
detailExpanded: false,
|
|
91
115
|
scrollOffset: 0,
|
|
92
116
|
};
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// best-effort — do not crash the TUI if the directory isn't writable
|
|
117
|
+
}
|
|
118
|
+
function collectResponses(state) {
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const interaction of state.interactions) {
|
|
121
|
+
const r = state.responses.get(interaction.id);
|
|
122
|
+
if (r !== undefined)
|
|
123
|
+
out.push(r);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function tryResume(state, progressPath, interactions) {
|
|
128
|
+
try {
|
|
129
|
+
const prior = JSON.parse(readFileSync(progressPath, 'utf8'));
|
|
130
|
+
if (!Array.isArray(prior.responses))
|
|
131
|
+
return;
|
|
132
|
+
const validIds = new Set(interactions.map((i) => i.id));
|
|
133
|
+
for (const r of prior.responses) {
|
|
134
|
+
if (validIds.has(r.id))
|
|
135
|
+
state.responses.set(r.id, r);
|
|
113
136
|
}
|
|
137
|
+
const firstUnanswered = interactions.findIndex((i) => !state.responses.has(i.id));
|
|
138
|
+
state.currentIndex = firstUnanswered >= 0 ? firstUnanswered : 0;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// corrupt or missing progress file — start fresh
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function atomicWriteProgress(progressPath, responses) {
|
|
145
|
+
const payload = JSON.stringify({ partial: true, responses, savedAt: new Date().toISOString() }, null, 2);
|
|
146
|
+
const tmp = `${progressPath}.tmp`;
|
|
147
|
+
try {
|
|
148
|
+
writeFileSync(tmp, payload);
|
|
149
|
+
renameSync(tmp, progressPath);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// best-effort
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function rebindPersist(internals) {
|
|
156
|
+
internals.state.persist = () => {
|
|
157
|
+
const responses = collectResponses(internals.state);
|
|
158
|
+
if (internals.progressPath !== undefined)
|
|
159
|
+
atomicWriteProgress(internals.progressPath, responses);
|
|
160
|
+
internals.callbacks.onProgress?.(responses);
|
|
114
161
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
162
|
+
}
|
|
163
|
+
function fireVisuals(internals, interactions) {
|
|
164
|
+
if (internals.generateVisual === undefined)
|
|
165
|
+
return;
|
|
166
|
+
const gen = internals.generateVisual;
|
|
167
|
+
for (const interaction of interactions) {
|
|
168
|
+
internals.state.visuals.set(interaction.id, { questionId: interaction.id, content: '', status: 'loading' });
|
|
169
|
+
gen(interaction).then((r) => {
|
|
170
|
+
if (!internals.mounted)
|
|
171
|
+
return;
|
|
172
|
+
if (!internals.state.interactions.some((x) => x.id === interaction.id))
|
|
173
|
+
return;
|
|
174
|
+
internals.state.visuals.set(interaction.id, r.ok
|
|
175
|
+
? { questionId: interaction.id, content: r.ansi, status: 'ready' }
|
|
176
|
+
: { questionId: interaction.id, content: '', status: 'error' });
|
|
177
|
+
}).catch(() => {
|
|
178
|
+
if (!internals.mounted)
|
|
179
|
+
return;
|
|
180
|
+
if (!internals.state.interactions.some((x) => x.id === interaction.id))
|
|
181
|
+
return;
|
|
182
|
+
internals.state.visuals.set(interaction.id, { questionId: interaction.id, content: '', status: 'error' });
|
|
183
|
+
});
|
|
129
184
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
185
|
+
}
|
|
186
|
+
export function mountPanel(opts) {
|
|
187
|
+
const internals = {
|
|
188
|
+
state: buildInitialState(opts.deck),
|
|
189
|
+
cols: opts.cols,
|
|
190
|
+
rows: opts.rows,
|
|
191
|
+
mounted: true,
|
|
192
|
+
generateVisual: opts.generateVisual,
|
|
193
|
+
progressPath: opts.progressPath,
|
|
194
|
+
callbacks: { onProgress: opts.onProgress, onComplete: opts.onComplete, onExit: opts.onExit },
|
|
195
|
+
};
|
|
196
|
+
assignShortcuts(internals.state.interactions);
|
|
197
|
+
rebindPersist(internals);
|
|
198
|
+
if (internals.progressPath !== undefined) {
|
|
199
|
+
tryResume(internals.state, internals.progressPath, opts.deck.interactions);
|
|
135
200
|
}
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
case '
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
case 'item-review':
|
|
144
|
-
lines = renderItemReview(state);
|
|
145
|
-
break;
|
|
146
|
-
case 'final':
|
|
147
|
-
lines = renderFinal(state);
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
flush(lines);
|
|
201
|
+
fireVisuals(internals, opts.deck.interactions);
|
|
202
|
+
const renderLines = () => {
|
|
203
|
+
switch (internals.state.phase) {
|
|
204
|
+
case 'overview': return renderOverview(internals.state, internals.cols, internals.rows);
|
|
205
|
+
case 'item-review': return renderItemReview(internals.state, internals.cols, internals.rows);
|
|
206
|
+
case 'final': return renderFinal(internals.state, internals.cols, internals.rows);
|
|
207
|
+
}
|
|
151
208
|
};
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
209
|
+
return {
|
|
210
|
+
handleKey(input, key) {
|
|
211
|
+
if (!internals.mounted)
|
|
212
|
+
return;
|
|
213
|
+
const onAutoComplete = () => {
|
|
214
|
+
const responses = collectResponses(internals.state);
|
|
215
|
+
if (internals.progressPath !== undefined) {
|
|
216
|
+
try {
|
|
217
|
+
unlinkSync(internals.progressPath);
|
|
218
|
+
}
|
|
219
|
+
catch { /* ignore */ }
|
|
220
|
+
}
|
|
221
|
+
internals.callbacks.onComplete?.(responses);
|
|
222
|
+
};
|
|
223
|
+
handleKeypress(input, key, internals.state, () => { }, () => {
|
|
224
|
+
const responses = collectResponses(internals.state);
|
|
225
|
+
if (responses.length >= internals.state.interactions.length) {
|
|
226
|
+
onAutoComplete();
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
internals.callbacks.onExit?.();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
render() {
|
|
234
|
+
if (!internals.mounted)
|
|
235
|
+
return [];
|
|
236
|
+
return renderLines();
|
|
237
|
+
},
|
|
238
|
+
handleResize(cols, rows) {
|
|
239
|
+
internals.cols = cols;
|
|
240
|
+
internals.rows = rows;
|
|
241
|
+
return renderLines();
|
|
242
|
+
},
|
|
243
|
+
unmount() {
|
|
244
|
+
internals.mounted = false;
|
|
245
|
+
internals.state.visuals.clear();
|
|
246
|
+
internals.state.persist = undefined;
|
|
247
|
+
},
|
|
248
|
+
loadDeck(deck, loadOpts) {
|
|
249
|
+
if (!internals.mounted)
|
|
250
|
+
return;
|
|
251
|
+
internals.state = buildInitialState(deck);
|
|
252
|
+
if (loadOpts !== undefined && loadOpts.progressPath !== undefined) {
|
|
253
|
+
internals.progressPath = loadOpts.progressPath;
|
|
167
254
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
255
|
+
assignShortcuts(internals.state.interactions);
|
|
256
|
+
rebindPersist(internals);
|
|
257
|
+
if (internals.progressPath !== undefined) {
|
|
258
|
+
tryResume(internals.state, internals.progressPath, deck.interactions);
|
|
172
259
|
}
|
|
260
|
+
fireVisuals(internals, deck.interactions);
|
|
261
|
+
},
|
|
262
|
+
canAcceptHostKeys() {
|
|
263
|
+
if (!internals.mounted)
|
|
264
|
+
return false;
|
|
265
|
+
return internals.state.inputMode === null;
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// ── launchTui shim ────────────────────────────────────────────────────────────
|
|
270
|
+
export async function launchTui(decisionsPath, sessionId) {
|
|
271
|
+
if (!existsSync(decisionsPath)) {
|
|
272
|
+
throw new Error(`Decisions file not found: ${decisionsPath}`);
|
|
273
|
+
}
|
|
274
|
+
const raw = readFileSync(decisionsPath, 'utf8');
|
|
275
|
+
const deck = validateInput(JSON.parse(raw));
|
|
276
|
+
let conversationContext = '';
|
|
277
|
+
if (sessionId !== undefined) {
|
|
278
|
+
try {
|
|
279
|
+
const conv = readConversation(sessionId);
|
|
280
|
+
conversationContext = conv.map((m) => `${m.role}: ${m.content}`).join('\n\n');
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// empty context — proceed without visuals context
|
|
173
284
|
}
|
|
174
285
|
}
|
|
286
|
+
setupTerminal();
|
|
287
|
+
const { cols, rows } = getTerminalSize();
|
|
175
288
|
return new Promise((resolve) => {
|
|
176
|
-
|
|
289
|
+
let panel = null;
|
|
290
|
+
let prevFrameLocal = [];
|
|
291
|
+
let lastResponses = [];
|
|
292
|
+
let onData;
|
|
293
|
+
const flushHost = (lines) => {
|
|
294
|
+
const { rows: currentRows } = getTerminalSize();
|
|
295
|
+
const { writes, nextPrevFrame } = diffFrame(prevFrameLocal, lines, currentRows);
|
|
296
|
+
process.stdout.write('\x1b[?2026h');
|
|
297
|
+
for (const w of writes)
|
|
298
|
+
process.stdout.write(w);
|
|
299
|
+
process.stdout.write('\x1b[?2026l');
|
|
300
|
+
prevFrameLocal = nextPrevFrame;
|
|
301
|
+
};
|
|
302
|
+
const onComplete = (responses) => {
|
|
177
303
|
restoreTerminal();
|
|
178
304
|
process.stdin.removeListener('data', onData);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const a = state.answers.get(q.id);
|
|
182
|
-
if (a)
|
|
183
|
-
answers.push(a);
|
|
184
|
-
}
|
|
185
|
-
if (answers.length >= input.questions.length) {
|
|
186
|
-
try {
|
|
187
|
-
unlinkSync(progressPath);
|
|
188
|
-
}
|
|
189
|
-
catch { /* ignore */ }
|
|
190
|
-
}
|
|
191
|
-
resolve({
|
|
192
|
-
answers,
|
|
193
|
-
completedAt: new Date().toISOString(),
|
|
194
|
-
});
|
|
305
|
+
panel?.unmount();
|
|
306
|
+
resolve({ responses, completedAt: new Date().toISOString() });
|
|
195
307
|
};
|
|
196
|
-
|
|
308
|
+
panel = mountPanel({
|
|
309
|
+
deck,
|
|
310
|
+
progressPath: `${decisionsPath}.progress.json`,
|
|
311
|
+
cols,
|
|
312
|
+
rows,
|
|
313
|
+
generateVisual: sessionId !== undefined
|
|
314
|
+
? (interaction) => defaultGenerateVisual(interaction, conversationContext)
|
|
315
|
+
: undefined,
|
|
316
|
+
onProgress: (responses) => {
|
|
317
|
+
lastResponses = responses;
|
|
318
|
+
if (panel !== null)
|
|
319
|
+
flushHost(panel.render());
|
|
320
|
+
},
|
|
321
|
+
onComplete,
|
|
322
|
+
onExit: () => {
|
|
323
|
+
onComplete(lastResponses);
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
flushHost(panel.render());
|
|
327
|
+
onData = (data) => {
|
|
197
328
|
const { input: inp, key } = parseKeypress(data);
|
|
198
|
-
|
|
329
|
+
panel.handleKey(inp, key);
|
|
330
|
+
flushHost(panel.render());
|
|
199
331
|
};
|
|
200
332
|
process.stdin.on('data', onData);
|
|
201
333
|
});
|
package/dist/tui/input.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { TuiState } from '../types.js';
|
|
1
|
+
import type { TuiState, Interaction } from '../types.js';
|
|
2
2
|
import type { Key } from './terminal.js';
|
|
3
3
|
export type RenderFn = () => void;
|
|
4
4
|
export type ExitFn = () => void;
|
|
5
|
+
export declare function assignShortcuts(interactions: Interaction[]): void;
|
|
5
6
|
export declare function handleKeypress(input: string, key: Key, state: TuiState, render: RenderFn, exit: ExitFn): void;
|