koneck 2.25.19 → 2.25.21
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/engine.d.ts +20 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +64 -5
- package/dist/engine.js.map +1 -1
- package/dist/glyphs.d.ts +40 -0
- package/dist/glyphs.d.ts.map +1 -0
- package/dist/glyphs.js +53 -0
- package/dist/glyphs.js.map +1 -0
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +59 -18
- package/dist/ink-chat.js.map +1 -1
- package/dist/session.d.ts +27 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +59 -9
- package/dist/session.js.map +1 -1
- package/dist/setup.d.ts +8 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +45 -4
- package/dist/setup.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -2,9 +2,9 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Static, Text, render, useApp, useInput, useStdin } from 'ink';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
|
-
import { createAgentSession, buildClient, resolveAgentConcurrency } from './engine.js';
|
|
5
|
+
import { createAgentSession, buildClient, resolveAgentConcurrency, toolsUnsupportedNotice } from './engine.js';
|
|
6
6
|
import { estimateCost, formatCost } from './pricing.js';
|
|
7
|
-
import { generateSessionId, saveSession, listSessions, loadSession } from './session.js';
|
|
7
|
+
import { generateSessionId, saveSession, listSessions, loadSession, relativeAge } from './session.js';
|
|
8
8
|
import { loadMemory } from './memory.js';
|
|
9
9
|
import { loadKoneckConfig, saveKoneckConfig, setConfigKey, configKeyDescriptions, CONFIG_FILE, CONFIG_SCHEMA, stepValue, displayValue } from './config-store.js';
|
|
10
10
|
import { PROVIDERS, resolveProvider, getApiKey, addUserProvider, userProvidersPath } from './providers.js';
|
|
@@ -14,6 +14,7 @@ import { listWorkspaceEntries, filterEntries, activeReference } from './workspac
|
|
|
14
14
|
import { isSlashCommand, findMentions, shortenMentions } from './mentions.js';
|
|
15
15
|
import { bar, humanTokens, humanDuration, diffStatSince, modelInfoFrom, contextWindowFor, recordStatus, classifyResponse, stateLabel, readModelStatus, computeUiWidth, } from './usage.js';
|
|
16
16
|
import { Markdown, Panel, panelWidth, safeCommitPoint, displayWidth, sliceToWidth } from './markdown-ink.js';
|
|
17
|
+
import { G } from './glyphs.js';
|
|
17
18
|
import { fetchUpdateStatus, updateCommand } from './update-check.js';
|
|
18
19
|
import { readFileSync, statSync } from 'fs';
|
|
19
20
|
import { fileURLToPath } from 'url';
|
|
@@ -179,7 +180,7 @@ export function liveReplyLines(termRows) {
|
|
|
179
180
|
const rows = termRows && termRows > 0 ? termRows : 24;
|
|
180
181
|
return rows < 12 ? 1 : 2;
|
|
181
182
|
}
|
|
182
|
-
const SPINNER =
|
|
183
|
+
const SPINNER = G.spinner;
|
|
183
184
|
/**
|
|
184
185
|
* A highlight that sweeps left to right through the word, so the label itself looks like it is
|
|
185
186
|
* working rather than sitting still. The gap after the word's end pauses briefly between passes;
|
|
@@ -510,6 +511,15 @@ function App({ config: initialConfig }) {
|
|
|
510
511
|
const thinkingRef = useRef({ chars: 0, at: 0 });
|
|
511
512
|
/** Why the current turn stopped short, when it did. Cleared at the start of each turn. */
|
|
512
513
|
const incompleteRef = useRef(null);
|
|
514
|
+
/** The model already reported as unable to call tools, so the notice is not repeated. */
|
|
515
|
+
const toolsWarnedRef = useRef(null);
|
|
516
|
+
/**
|
|
517
|
+
* The id to save under, mirrored in a ref.
|
|
518
|
+
*
|
|
519
|
+
* `sessionId` is state, so a save fired from inside a turn closure would use the value captured
|
|
520
|
+
* when the turn began — which is the pre-resume id if the two happen close together.
|
|
521
|
+
*/
|
|
522
|
+
const resumedIdRef = useRef(null);
|
|
513
523
|
/**
|
|
514
524
|
* Text pasted into the composer, held aside so the prompt stays readable.
|
|
515
525
|
*
|
|
@@ -668,6 +678,13 @@ function App({ config: initialConfig }) {
|
|
|
668
678
|
},
|
|
669
679
|
onStreamActivity: (frames) => setProviderFrames(frames),
|
|
670
680
|
onIncomplete: (reason) => { incompleteRef.current = reason; },
|
|
681
|
+
onToolsUnsupported: (model) => {
|
|
682
|
+
// Said once per session, not once per turn — it is a property of the model, not an event.
|
|
683
|
+
if (toolsWarnedRef.current === model)
|
|
684
|
+
return;
|
|
685
|
+
toolsWarnedRef.current = model;
|
|
686
|
+
addSystem(toolsUnsupportedNotice(model));
|
|
687
|
+
},
|
|
671
688
|
onThinking: (_text, totalChars) => {
|
|
672
689
|
thinkingRef.current = { chars: totalChars, at: Date.now() };
|
|
673
690
|
lastActivityRef.current = Date.now();
|
|
@@ -692,7 +709,10 @@ function App({ config: initialConfig }) {
|
|
|
692
709
|
const [updatePane, setUpdatePane] = useState(null);
|
|
693
710
|
const [updating, setUpdating] = useState('idle');
|
|
694
711
|
const [updateLog, setUpdateLog] = useState('');
|
|
695
|
-
|
|
712
|
+
// Settable, because resuming has to adopt the restored session's id. Without that, every save
|
|
713
|
+
// after a resume wrote a new file and left the session you resumed frozen at its old state —
|
|
714
|
+
// which is exactly what made /resume look like it always returned to the old one.
|
|
715
|
+
const [sessionId, setSessionId] = useState(() => generateSessionId());
|
|
696
716
|
/** Messages that arrived while a turn was running; drained when the agent goes idle. */
|
|
697
717
|
const inboxRef = useRef([]);
|
|
698
718
|
const busRef = useRef(null);
|
|
@@ -935,10 +955,15 @@ function App({ config: initialConfig }) {
|
|
|
935
955
|
const firstUser = s?.messages.find((m) => m.role === 'user');
|
|
936
956
|
const task = firstUser && typeof firstUser.content === 'string'
|
|
937
957
|
? firstUser.content.slice(0, 120) : '(chat session)';
|
|
938
|
-
|
|
939
|
-
|
|
958
|
+
// Both of these come from the live session rather than from React state: a save fired at the
|
|
959
|
+
// end of a turn runs inside a closure that captured state as it was when the turn began, so
|
|
960
|
+
// reading `stats` here recorded turn counts one behind and, after a resume, the wrong id.
|
|
961
|
+
const id = resumedIdRef.current ?? sessionId;
|
|
962
|
+
const live = s?.stats ?? { turns: stats.turns, totalTokens: stats.totalTokens };
|
|
963
|
+
return saveSession(cfg.cwd, id, {
|
|
964
|
+
id, task, provider: cfg.provider, model: cfg.model,
|
|
940
965
|
cwd: cfg.cwd, startedAt: new Date().toISOString(),
|
|
941
|
-
turns:
|
|
966
|
+
turns: live.turns, totalTokens: live.totalTokens,
|
|
942
967
|
}, s?.messages ?? []);
|
|
943
968
|
}
|
|
944
969
|
/**
|
|
@@ -990,7 +1015,9 @@ function App({ config: initialConfig }) {
|
|
|
990
1015
|
async function resumeSession(id) {
|
|
991
1016
|
try {
|
|
992
1017
|
const { meta, messages } = await loadSession(cfg.cwd, id);
|
|
993
|
-
activeSession.p = createAgentSession({ ...cfg, silent: true, ...sessionOpts }, messages);
|
|
1018
|
+
activeSession.p = createAgentSession({ ...cfg, silent: true, ...sessionOpts }, messages, { turns: meta.turns, totalTokens: meta.totalTokens });
|
|
1019
|
+
setSessionId(id); // continue this session rather than forking a new one
|
|
1020
|
+
resumedIdRef.current = id;
|
|
994
1021
|
const restored = [{ role: 'header' }];
|
|
995
1022
|
for (const m of messages) {
|
|
996
1023
|
if (m.role === 'user' && typeof m.content === 'string' && !m.content.startsWith('[')) {
|
|
@@ -1317,12 +1344,18 @@ function App({ config: initialConfig }) {
|
|
|
1317
1344
|
}
|
|
1318
1345
|
case '/clear':
|
|
1319
1346
|
case '/reset':
|
|
1320
|
-
case '/new':
|
|
1347
|
+
case '/new': {
|
|
1348
|
+
// A new id, or the autosave that now runs every turn would overwrite the session just
|
|
1349
|
+
// cleared with the empty conversation replacing it — turning a clear into a deletion.
|
|
1321
1350
|
await resetSession();
|
|
1351
|
+
const fresh = generateSessionId();
|
|
1352
|
+
setSessionId(fresh);
|
|
1353
|
+
resumedIdRef.current = fresh;
|
|
1322
1354
|
setRows([{ role: 'header' }]);
|
|
1323
1355
|
setStats({ turns: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 });
|
|
1324
|
-
addSystem('Conversation cleared.');
|
|
1356
|
+
addSystem('Conversation cleared. The previous session is kept; /resume lists it.');
|
|
1325
1357
|
return;
|
|
1358
|
+
}
|
|
1326
1359
|
case '/model': {
|
|
1327
1360
|
if (!arg) {
|
|
1328
1361
|
addSystem(`Current model: ${cfg.model}\nUsage: /model <name>`);
|
|
@@ -1481,10 +1514,13 @@ function App({ config: initialConfig }) {
|
|
|
1481
1514
|
// With an id, restore straight away; without one, offer a picker.
|
|
1482
1515
|
const pick = arg.trim();
|
|
1483
1516
|
if (pick === '') {
|
|
1484
|
-
openPicker('resume', saved.slice(0, 20).map(x => ({
|
|
1517
|
+
openPicker('resume', saved.slice(0, 20).map((x, i) => ({
|
|
1485
1518
|
value: x.id,
|
|
1486
1519
|
label: x.id,
|
|
1487
|
-
|
|
1520
|
+
// Ordered by last write, so the first entry is the one you were last in — said out
|
|
1521
|
+
// loud rather than left to be inferred from a timestamp buried in the id.
|
|
1522
|
+
desc: `${i === 0 ? 'latest · ' : ''}${relativeAge(x.updatedAt)} ` +
|
|
1523
|
+
`${x.turns} turns ${x.totalTokens.toLocaleString()} tok ${x.task.slice(0, 36)}`,
|
|
1488
1524
|
})));
|
|
1489
1525
|
return;
|
|
1490
1526
|
}
|
|
@@ -2343,6 +2379,10 @@ function App({ config: initialConfig }) {
|
|
|
2343
2379
|
recovered: turnRecoveredRef.current,
|
|
2344
2380
|
}, turnWordRef.current)[1] },
|
|
2345
2381
|
]);
|
|
2382
|
+
// Persisted on every turn, not just on /save, /clear and ctrl+c. Resuming restored
|
|
2383
|
+
// whichever of those three happened last, which for anyone who closes the terminal
|
|
2384
|
+
// normally was an old snapshot — the reported "always resumes the old one".
|
|
2385
|
+
void doSave().catch(() => { });
|
|
2346
2386
|
// A run that stopped short says so, rather than letting the footer imply it finished.
|
|
2347
2387
|
if (incompleteRef.current) {
|
|
2348
2388
|
addSystem(incompleteRef.current);
|
|
@@ -2367,6 +2407,7 @@ function App({ config: initialConfig }) {
|
|
|
2367
2407
|
sayRef.current = '';
|
|
2368
2408
|
liveToolRef.current = null;
|
|
2369
2409
|
flushAgents();
|
|
2410
|
+
void doSave().catch(() => { });
|
|
2370
2411
|
setRows(prev => [
|
|
2371
2412
|
...prev,
|
|
2372
2413
|
...(trailing ? [{ role: 'steps', steps: [{ kind: 'say', text: trailing }] }] : []),
|
|
@@ -2446,14 +2487,14 @@ function App({ config: initialConfig }) {
|
|
|
2446
2487
|
}
|
|
2447
2488
|
const running = step.endedAt == null;
|
|
2448
2489
|
const ms = (step.endedAt ?? Date.now()) - step.startedAt;
|
|
2449
|
-
const glyph = running ? SPINNER[spinFrame] : step.ok === false ?
|
|
2490
|
+
const glyph = running ? SPINNER[spinFrame] : step.ok === false ? G.fail : G.ok;
|
|
2450
2491
|
const color = running ? AMBER : step.ok === false ? CRIMSON : GREEN;
|
|
2451
2492
|
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { color: AMBER, bold: true, children: step.name }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(ms) })] }), step.detail !== '' && (step.diffs ?? []).length === 0 && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: MUTED, children: step.detail })] })), running && step.output && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: DIM, children: fitCells(step.output, Math.max(20, barWidth - 6)) }) })), (step.diffs ?? []).map((d, i) => renderDiff(d, i))] }, key));
|
|
2452
2493
|
};
|
|
2453
2494
|
return (_jsxs(Box, { flexDirection: "column", width: uiWidth, paddingX: 2, children: [showAnalytics && (_jsxs(Box, { borderStyle: "round", borderColor: AMBER, paddingX: 1, marginBottom: 1, flexDirection: "column", width: barWidth, children: [_jsx(Text, { color: AMBER, bold: true, children: "\u25C6 Session Analytics" }), _jsxs(Text, { color: MUTED, children: ["Turns : ", _jsx(Text, { color: INK, children: stats.turns })] }), _jsxs(Text, { color: MUTED, children: ["Prompt tok : ", _jsx(Text, { color: INK, children: stats.promptTokens.toLocaleString() })] }), _jsxs(Text, { color: MUTED, children: ["Output tok : ", _jsx(Text, { color: INK, children: stats.completionTokens.toLocaleString() })] }), _jsxs(Text, { color: MUTED, children: ["Total tok : ", _jsx(Text, { color: INK, children: stats.totalTokens.toLocaleString() })] }), _jsxs(Text, { color: MUTED, children: ["Model : ", _jsx(Text, { color: INK, children: modelShort }), " Provider: ", _jsx(Text, { color: INK, children: cfg.provider })] }), _jsx(Text, { color: DIM, children: "Tab to close" })] })), _jsx(Static, { items: rows, children: (row, index) => row.role === 'header' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: "KONECK / SOFTWARE DELIVERY SYSTEM" }), _jsx(Text, { color: MUTED, children: "Deliberate engineering, with control at every change." })] }), _jsx(Panel, { width: barWidth, color: CYAN, title: "KONECK", children: [`MODEL: \`${modelShort}\` PROVIDER: \`${cfg.provider}\` WORKSPACE: \`${workspace}\``] })] }, index)) : row.role === 'user' ? (
|
|
2454
2495
|
// Painted as a filled bar so the user's own words are the most findable thing
|
|
2455
2496
|
// on screen when scrolling back through a long session.
|
|
2456
|
-
_jsx(Box, { marginTop: 1, flexDirection: "column", children: wrapToWidth(row.text ?? '', barWidth - 2).map((line, li) => (_jsx(Text, { backgroundColor: USER_BG, color: USER_FG, bold: li === 0, children: (li === 0 ?
|
|
2497
|
+
_jsx(Box, { marginTop: 1, flexDirection: "column", children: wrapToWidth(row.text ?? '', barWidth - 2).map((line, li) => (_jsx(Text, { backgroundColor: USER_BG, color: USER_FG, bold: li === 0, children: (li === 0 ? `${G.caret} ` : ' ') + fitCells(line, barWidth - 2) }, li))) }, index)) : row.role === 'error' ? (() => {
|
|
2457
2498
|
const lines = [row.text ?? '', '', 'Fix the above, then retry. /status shows the active provider and model.'];
|
|
2458
2499
|
return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 40), color: CRIMSON, title: "[!] ERROR", children: lines }) }, index));
|
|
2459
2500
|
})() : row.role === 'system' ? (() => {
|
|
@@ -2464,7 +2505,7 @@ function App({ config: initialConfig }) {
|
|
|
2464
2505
|
// content, so the right edge stepped in and out; and a box inside a box left
|
|
2465
2506
|
// markdown tables needlessly narrow. A marker plus the content reads better and
|
|
2466
2507
|
// cannot misalign, which is how Claude Code presents its own answers.
|
|
2467
|
-
_jsxs(Box, { flexDirection: "column", marginTop: row.continued ? 0 : 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: row.continued ? ' ' :
|
|
2508
|
+
_jsxs(Box, { flexDirection: "column", marginTop: row.continued ? 0 : 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: row.continued ? ' ' : G.brand }), _jsx(Box, { flexDirection: "column", children: _jsx(Markdown, { text: row.text ?? '', width: barWidth - 3 }) })] }), row.tokens != null && (_jsxs(Text, { color: MUTED, children: [_jsx(Text, { color: GREEN, children: "* " }), row.done ? `${row.done} in ${fmtElapsed(row.elapsed ?? 0)}` : fmtElapsed(row.elapsed ?? 0), ' | ', row.tokens, " tokens", ' | ', cfg.provider, " ", modelShort] }))] }, index)) }), _jsx(Box, { flexDirection: "column", children: busy && (agentState === 'processing' || agentState === 'syncing') && (_jsxs(Box, { flexDirection: "column", children: [sayRef.current.trim() !== '' && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: replyStartedRef.current ? ' ' : G.brand }), _jsx(Box, { flexDirection: "column", children: tailLines(sayRef.current, liveReplyLines(termRows), replyWidth - 3)
|
|
2468
2509
|
.split('\n').map((line, i) => _jsx(Text, { color: INK, children: line }, i)) })] })), liveToolRef.current && renderStep(liveToolRef.current, 1), queued.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: AMBER, children: "\u25AA" }), _jsxs(Text, { color: MUTED, children: [queued.length, " queued, will run when this finishes \u00B7 /aside to ask without waiting"] })] }), queued.map((q, i) => (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: fitCells(q.split('\n')[0] ?? '', Math.max(20, barWidth - 6), true) }) }, i)))] })), agents.length > 0 && (() => {
|
|
2469
2510
|
const done = agents.filter(a => a.status !== 'running').length;
|
|
2470
2511
|
const failed = agents.filter(a => a.status === 'failed').length;
|
|
@@ -2475,7 +2516,7 @@ function App({ config: initialConfig }) {
|
|
|
2475
2516
|
const taskWidth = Math.max(16, barWidth - 34);
|
|
2476
2517
|
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: CYAN, children: "\u25C6" }), _jsxs(Text, { color: INK, bold: true, children: [agents.length, " agents"] }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsxs(Text, { color: done === agents.length ? GREEN : AMBER, children: [done, "/", agents.length, " complete"] }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsxs(Text, { color: MUTED, children: [humanTokens(tokens), " tok"] }), _jsx(Text, { color: DIM, children: "\u00B7" }), _jsx(Text, { color: MUTED, children: fmtElapsed(Date.now() - started) }), failed > 0 && _jsxs(Text, { color: CRIMSON, children: ["\u00B7 ", failed, " failed"] })] }), agents.map(a => {
|
|
2477
2518
|
const glyph = a.status === 'running' ? SPINNER[spinFrame]
|
|
2478
|
-
: a.status === 'failed' ?
|
|
2519
|
+
: a.status === 'failed' ? G.fail : G.ok;
|
|
2479
2520
|
const color = a.status === 'running' ? AMBER
|
|
2480
2521
|
: a.status === 'failed' ? CRIMSON : GREEN;
|
|
2481
2522
|
const took = (a.endedAt ?? Date.now()) - a.startedAt;
|
|
@@ -2648,7 +2689,7 @@ function App({ config: initialConfig }) {
|
|
|
2648
2689
|
lines.push('show a real bar here with no further work.');
|
|
2649
2690
|
}
|
|
2650
2691
|
lines.push('---');
|
|
2651
|
-
lines.push('
|
|
2692
|
+
lines.push('**{left}/{right}** switch tabs · **esc** to close');
|
|
2652
2693
|
return (_jsx(Box, { marginTop: 1, justifyContent: "flex-end", children: _jsx(Panel, { width: width, color: CYAN, title: "KONECK \u00B7 usage", children: lines }) }));
|
|
2653
2694
|
})(), updatePane && (() => {
|
|
2654
2695
|
const u = updatePane;
|
|
@@ -2716,7 +2757,7 @@ function App({ config: initialConfig }) {
|
|
|
2716
2757
|
const header = item.group && item.group !== lastGroup ? item.group : null;
|
|
2717
2758
|
lastGroup = item.group;
|
|
2718
2759
|
const label = item.label.length > 34 ? item.label.slice(0, 31) + '...' : item.label;
|
|
2719
|
-
return (_jsxs(Box, { flexDirection: "column", children: [header && _jsx(Text, { color: DIM, children: header }), _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? '
|
|
2760
|
+
return (_jsxs(Box, { flexDirection: "column", children: [header && _jsx(Text, { color: DIM, children: header }), _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? G.caret + ' ' : ' '}${fitCells(label, 36)}${item.current ? '(current) ' : ''}${item.desc}`, width) })] }, item.value + absolute));
|
|
2720
2761
|
}), list.length > PICKER_ROWS && (_jsxs(Text, { color: DIM, children: ["\u2191\u2193 navigate \u00B7 enter select \u00B7 showing ", Math.max(0, start) + 1, "\u2013", Math.max(0, start) + shown.length, " of ", list.length] }))] }));
|
|
2721
2762
|
})(), keyPrompt && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AMBER, paddingX: 1, marginTop: 1, width: barWidth, children: [_jsxs(Text, { color: AMBER, bold: true, children: ["API key for ", keyPrompt.provider] }), _jsx(Text, { color: MUTED, children: "Paste it and press enter. Held in memory for this session only; esc to cancel." }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: MUTED, children: [keyPrompt.env, ": "] }), _jsx(Text, { color: INK, children: '*'.repeat(Math.min(keyPrompt.value.length, 48)) }), _jsx(Text, { color: CYAN, children: "\u2588" })] })] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: CYAN, bold: true, children: "\u276F " }), _jsx(Text, { color: INK, children: draft.slice(0, caret) }), busy
|
|
2722
2763
|
? _jsx(Text, { color: INK, children: draft.slice(caret) })
|