cvc-tui 0.1.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry.js +71145 -34
- package/package.json +3 -2
- package/dist/app/completion.js +0 -98
- package/dist/app/historyStore.js +0 -119
- package/dist/app/inputBuffer.js +0 -116
- package/dist/app/inputStore.js +0 -24
- package/dist/app/promptStore.js +0 -40
- package/dist/app/queueStore.js +0 -21
- package/dist/app/slash/commands/core.js +0 -292
- package/dist/app/slash/commands/debug.js +0 -11
- package/dist/app/slash/commands/ops.js +0 -163
- package/dist/app/slash/commands/session.js +0 -91
- package/dist/app/slash/commands/setup.js +0 -47
- package/dist/app/slash/commands/toggles.js +0 -36
- package/dist/app/slash/registry.js +0 -79
- package/dist/app/slash/types.js +0 -16
- package/dist/app/turnStore.js +0 -60
- package/dist/app/uiStore.js +0 -31
- package/dist/app.js +0 -219
- package/dist/banner.js +0 -20
- package/dist/components/appLayout.js +0 -22
- package/dist/components/branding.js +0 -6
- package/dist/components/overlays/confirmPrompt.js +0 -25
- package/dist/components/overlays/helpOverlay.js +0 -75
- package/dist/components/overlays/historySearch.js +0 -48
- package/dist/components/overlays/modelPicker.js +0 -59
- package/dist/components/overlays/overlayUtils.js +0 -18
- package/dist/components/overlays/secretPrompt.js +0 -35
- package/dist/components/overlays/sessionPicker.js +0 -92
- package/dist/components/overlays/skillsHub.js +0 -70
- package/dist/components/streamingMarkdown.js +0 -220
- package/dist/components/textInput.js +0 -264
- package/dist/components/thinking.js +0 -39
- package/dist/components/transcript.js +0 -22
- package/dist/config/timing.js +0 -14
- package/dist/gateway/client.js +0 -312
- package/dist/types.js +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cvc-tui",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "CVC — Cognitive Version Control terminal UI (Ink + React 19). Sidecar binary embedded in the cvc Python wheel.",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"dev": "bun run src/entry.tsx 2>/dev/null || tsx --watch src/entry.tsx",
|
|
40
40
|
"start": "bun run src/entry.tsx 2>/dev/null || tsx src/entry.tsx",
|
|
41
|
-
"build": "bun build src/entry.tsx --target=node --outfile=dist/entry.js 2>/dev/null ||
|
|
41
|
+
"build": "rm -rf dist && (bun build src/entry.tsx --target=node --outfile=dist/entry.js 2>/dev/null || esbuild src/entry.tsx --bundle --platform=node --format=esm --target=node20 --packages=bundle --alias:react-devtools-core=./src/lib/react-devtools-stub.ts --outfile=dist/entry.js) && chmod +x dist/entry.js",
|
|
42
42
|
"build:compile": "bun build src/entry.tsx --compile --outfile=dist/cvc-tui",
|
|
43
43
|
"type-check": "tsc --noEmit -p tsconfig.json",
|
|
44
44
|
"test": "vitest run",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"fix": "npm run lint:fix && npm run fmt"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"@cvc/ink": "file:./packages/cvc-ink",
|
|
52
53
|
"@nanostores/react": "^1.1.0",
|
|
53
54
|
"cli-highlight": "^2.1.11",
|
|
54
55
|
"ink": "^6.8.0",
|
package/dist/app/completion.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// Tab-completion logic. Pure functions where possible, with one
|
|
2
|
-
// fs-touching helper for path expansion.
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import * as os from 'node:os';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
import { listCommandNames } from './slash/registry.js';
|
|
7
|
-
/**
|
|
8
|
-
* Find the token under the cursor: a maximal run of non-whitespace ending at
|
|
9
|
-
* `cursor`. Returns null when no token (cursor on whitespace at boundary).
|
|
10
|
-
*/
|
|
11
|
-
export function tokenAtCursor(text, cursor) {
|
|
12
|
-
if (cursor < 0)
|
|
13
|
-
cursor = 0;
|
|
14
|
-
if (cursor > text.length)
|
|
15
|
-
cursor = text.length;
|
|
16
|
-
let s = cursor;
|
|
17
|
-
while (s > 0 && !/\s/.test(text[s - 1]))
|
|
18
|
-
s--;
|
|
19
|
-
const tok = text.slice(s, cursor);
|
|
20
|
-
if (!tok)
|
|
21
|
-
return null;
|
|
22
|
-
const isSlash = tok.startsWith('/') && s === firstNonSpaceOnLine(text, s);
|
|
23
|
-
const isPath = !isSlash && (tok.startsWith('~/') || tok.startsWith('./') || tok.startsWith('../') || tok.startsWith('/'));
|
|
24
|
-
return { start: s, token: tok, isSlash, isPath };
|
|
25
|
-
}
|
|
26
|
-
function firstNonSpaceOnLine(text, off) {
|
|
27
|
-
// Walk back to start of logical line, then forward over spaces/tabs.
|
|
28
|
-
const lineStart = text.lastIndexOf('\n', off - 1) + 1;
|
|
29
|
-
let i = lineStart;
|
|
30
|
-
while (i < text.length && (text[i] === ' ' || text[i] === '\t'))
|
|
31
|
-
i++;
|
|
32
|
-
return i;
|
|
33
|
-
}
|
|
34
|
-
/** Slash-command candidates whose name starts with `prefix`. */
|
|
35
|
-
export function slashCandidates(prefix) {
|
|
36
|
-
return listCommandNames()
|
|
37
|
-
.filter((n) => n.startsWith(prefix))
|
|
38
|
-
.sort();
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Path candidates for `prefix`. Reads the parent directory and returns
|
|
42
|
-
* matching entries (files + dirs, dirs suffixed with `/`).
|
|
43
|
-
*
|
|
44
|
-
* Side-effect: touches the filesystem (fs.readdirSync).
|
|
45
|
-
*/
|
|
46
|
-
export function pathCandidates(prefix) {
|
|
47
|
-
let abs = prefix;
|
|
48
|
-
let displayPrefix = prefix;
|
|
49
|
-
if (prefix.startsWith('~/')) {
|
|
50
|
-
abs = path.join(os.homedir(), prefix.slice(2));
|
|
51
|
-
}
|
|
52
|
-
// Decide directory + leaf to filter by.
|
|
53
|
-
const lastSlash = abs.lastIndexOf('/');
|
|
54
|
-
let dirAbs;
|
|
55
|
-
let dirDisplay;
|
|
56
|
-
let leaf;
|
|
57
|
-
if (lastSlash < 0) {
|
|
58
|
-
dirAbs = '.';
|
|
59
|
-
dirDisplay = '';
|
|
60
|
-
leaf = abs;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
dirAbs = abs.slice(0, lastSlash + 1) || '/';
|
|
64
|
-
dirDisplay = displayPrefix.slice(0, lastSlash + 1);
|
|
65
|
-
leaf = abs.slice(lastSlash + 1);
|
|
66
|
-
}
|
|
67
|
-
let entries;
|
|
68
|
-
try {
|
|
69
|
-
entries = fs.readdirSync(dirAbs, { withFileTypes: true });
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
const out = [];
|
|
75
|
-
for (const e of entries) {
|
|
76
|
-
if (!e.name.startsWith(leaf))
|
|
77
|
-
continue;
|
|
78
|
-
if (leaf === '' && e.name.startsWith('.'))
|
|
79
|
-
continue; // skip hidden when no leaf
|
|
80
|
-
out.push(dirDisplay + e.name + (e.isDirectory() ? '/' : ''));
|
|
81
|
-
}
|
|
82
|
-
return out.sort();
|
|
83
|
-
}
|
|
84
|
-
/** Compute candidates for the given context. */
|
|
85
|
-
export function candidatesFor(ctx) {
|
|
86
|
-
if (ctx.isSlash)
|
|
87
|
-
return slashCandidates(ctx.token);
|
|
88
|
-
if (ctx.isPath)
|
|
89
|
-
return pathCandidates(ctx.token);
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
/** Apply a candidate to the buffer at the given start offset (replacing token). */
|
|
93
|
-
export function applyCandidate(text, start, tokenLen, candidate) {
|
|
94
|
-
const before = text.slice(0, start);
|
|
95
|
-
const after = text.slice(start + tokenLen);
|
|
96
|
-
const next = before + candidate + after;
|
|
97
|
-
return { text: next, cursor: start + candidate.length };
|
|
98
|
-
}
|
package/dist/app/historyStore.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// History store — persists user input to ~/.cvc/agent_history.
|
|
2
|
-
// One entry per line, dedup against immediate previous, max 10000.
|
|
3
|
-
import { atom } from 'nanostores';
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
import * as os from 'node:os';
|
|
7
|
-
export const HISTORY_MAX = 10000;
|
|
8
|
-
function defaultHistoryPath() {
|
|
9
|
-
return path.join(os.homedir(), '.cvc', 'agent_history');
|
|
10
|
-
}
|
|
11
|
-
let historyPath = defaultHistoryPath();
|
|
12
|
-
/** Used by tests to redirect persistence. */
|
|
13
|
-
export function setHistoryPath(p) {
|
|
14
|
-
historyPath = p;
|
|
15
|
-
}
|
|
16
|
-
export function getHistoryPath() {
|
|
17
|
-
return historyPath;
|
|
18
|
-
}
|
|
19
|
-
export const $history = atom([]);
|
|
20
|
-
/**
|
|
21
|
-
* Cursor pointing into history while user is browsing with Up/Down.
|
|
22
|
-
* `null` means "not browsing" (i.e. at the live edit buffer).
|
|
23
|
-
*/
|
|
24
|
-
export const $historyCursor = atom(null);
|
|
25
|
-
/** Snapshot of the live buffer so we can restore it after browsing. */
|
|
26
|
-
export const $historyDraft = atom('');
|
|
27
|
-
export function loadHistory() {
|
|
28
|
-
try {
|
|
29
|
-
if (!fs.existsSync(historyPath)) {
|
|
30
|
-
$history.set([]);
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
const raw = fs.readFileSync(historyPath, 'utf8');
|
|
34
|
-
// Drop empty trailing line, ignore blank lines.
|
|
35
|
-
const lines = raw.split('\n').filter((l) => l.length > 0);
|
|
36
|
-
$history.set(lines);
|
|
37
|
-
return lines;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
$history.set([]);
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/** Append an entry, dedup-against-previous, cap at HISTORY_MAX, persist. */
|
|
45
|
-
export function pushHistory(entry) {
|
|
46
|
-
const trimmed = entry.replace(/\s+$/g, '');
|
|
47
|
-
if (!trimmed)
|
|
48
|
-
return;
|
|
49
|
-
const cur = $history.get();
|
|
50
|
-
if (cur.length > 0 && cur[cur.length - 1] === trimmed) {
|
|
51
|
-
// Dedup consecutive duplicates without rewriting the file.
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
let next = [...cur, trimmed];
|
|
55
|
-
if (next.length > HISTORY_MAX) {
|
|
56
|
-
next = next.slice(next.length - HISTORY_MAX);
|
|
57
|
-
}
|
|
58
|
-
$history.set(next);
|
|
59
|
-
persist();
|
|
60
|
-
}
|
|
61
|
-
export function persist() {
|
|
62
|
-
try {
|
|
63
|
-
fs.mkdirSync(path.dirname(historyPath), { recursive: true });
|
|
64
|
-
fs.writeFileSync(historyPath, $history.get().join('\n') + '\n', 'utf8');
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// Best-effort — never crash the UI on history I/O failure.
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Reverse-incremental search. Returns up to `limit` matches, most-recent-first.
|
|
72
|
-
*/
|
|
73
|
-
export function searchHistory(query, limit = 50) {
|
|
74
|
-
if (!query)
|
|
75
|
-
return [];
|
|
76
|
-
const q = query.toLowerCase();
|
|
77
|
-
const out = [];
|
|
78
|
-
const cur = $history.get();
|
|
79
|
-
for (let i = cur.length - 1; i >= 0 && out.length < limit; i--) {
|
|
80
|
-
if (cur[i].toLowerCase().includes(q))
|
|
81
|
-
out.push(cur[i]);
|
|
82
|
-
}
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
/** Move history cursor toward older entries; returns the entry text or null. */
|
|
86
|
-
export function historyPrev(currentDraft) {
|
|
87
|
-
const h = $history.get();
|
|
88
|
-
if (h.length === 0)
|
|
89
|
-
return null;
|
|
90
|
-
let idx = $historyCursor.get();
|
|
91
|
-
if (idx === null) {
|
|
92
|
-
$historyDraft.set(currentDraft);
|
|
93
|
-
idx = h.length - 1;
|
|
94
|
-
}
|
|
95
|
-
else if (idx > 0) {
|
|
96
|
-
idx -= 1;
|
|
97
|
-
}
|
|
98
|
-
$historyCursor.set(idx);
|
|
99
|
-
return h[idx];
|
|
100
|
-
}
|
|
101
|
-
/** Move history cursor toward newer entries; returns the entry text or null on fall-through. */
|
|
102
|
-
export function historyNext() {
|
|
103
|
-
const h = $history.get();
|
|
104
|
-
const idx = $historyCursor.get();
|
|
105
|
-
if (idx === null)
|
|
106
|
-
return null;
|
|
107
|
-
if (idx >= h.length - 1) {
|
|
108
|
-
// Fell off the end: restore draft.
|
|
109
|
-
$historyCursor.set(null);
|
|
110
|
-
return $historyDraft.get();
|
|
111
|
-
}
|
|
112
|
-
const next = idx + 1;
|
|
113
|
-
$historyCursor.set(next);
|
|
114
|
-
return h[next];
|
|
115
|
-
}
|
|
116
|
-
export function resetHistoryBrowse() {
|
|
117
|
-
$historyCursor.set(null);
|
|
118
|
-
$historyDraft.set('');
|
|
119
|
-
}
|
package/dist/app/inputBuffer.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// Pure text-buffer helpers for the multi-line composer.
|
|
2
|
-
// Kept side-effect-free so they can be unit-tested without Ink/React.
|
|
3
|
-
export function emptyBuffer() {
|
|
4
|
-
return { text: '', cursor: 0 };
|
|
5
|
-
}
|
|
6
|
-
export function insert(buf, s) {
|
|
7
|
-
const text = buf.text.slice(0, buf.cursor) + s + buf.text.slice(buf.cursor);
|
|
8
|
-
return { text, cursor: buf.cursor + s.length };
|
|
9
|
-
}
|
|
10
|
-
export function backspace(buf) {
|
|
11
|
-
if (buf.cursor === 0)
|
|
12
|
-
return buf;
|
|
13
|
-
const text = buf.text.slice(0, buf.cursor - 1) + buf.text.slice(buf.cursor);
|
|
14
|
-
return { text, cursor: buf.cursor - 1 };
|
|
15
|
-
}
|
|
16
|
-
export function del(buf) {
|
|
17
|
-
if (buf.cursor >= buf.text.length)
|
|
18
|
-
return buf;
|
|
19
|
-
const text = buf.text.slice(0, buf.cursor) + buf.text.slice(buf.cursor + 1);
|
|
20
|
-
return { text, cursor: buf.cursor };
|
|
21
|
-
}
|
|
22
|
-
export function moveLeft(buf) {
|
|
23
|
-
return { ...buf, cursor: Math.max(0, buf.cursor - 1) };
|
|
24
|
-
}
|
|
25
|
-
export function moveRight(buf) {
|
|
26
|
-
return { ...buf, cursor: Math.min(buf.text.length, buf.cursor + 1) };
|
|
27
|
-
}
|
|
28
|
-
export function moveHome(buf) {
|
|
29
|
-
// Move to start of current logical line.
|
|
30
|
-
const before = buf.text.slice(0, buf.cursor);
|
|
31
|
-
const nl = before.lastIndexOf('\n');
|
|
32
|
-
return { ...buf, cursor: nl < 0 ? 0 : nl + 1 };
|
|
33
|
-
}
|
|
34
|
-
export function moveEnd(buf) {
|
|
35
|
-
const after = buf.text.slice(buf.cursor);
|
|
36
|
-
const nl = after.indexOf('\n');
|
|
37
|
-
return { ...buf, cursor: nl < 0 ? buf.text.length : buf.cursor + nl };
|
|
38
|
-
}
|
|
39
|
-
/** Lines split on \n (keeps empty trailing line). */
|
|
40
|
-
export function lines(text) {
|
|
41
|
-
return text.split('\n');
|
|
42
|
-
}
|
|
43
|
-
/** Convert cursor offset to {row, col} on logical lines. */
|
|
44
|
-
export function cursorRowCol(buf) {
|
|
45
|
-
const before = buf.text.slice(0, buf.cursor);
|
|
46
|
-
const ls = before.split('\n');
|
|
47
|
-
return { row: ls.length - 1, col: ls[ls.length - 1].length };
|
|
48
|
-
}
|
|
49
|
-
/** Move cursor up one logical line, preserving column when possible. */
|
|
50
|
-
export function moveUp(buf) {
|
|
51
|
-
const { row, col } = cursorRowCol(buf);
|
|
52
|
-
if (row === 0)
|
|
53
|
-
return buf;
|
|
54
|
-
const ls = lines(buf.text);
|
|
55
|
-
const targetCol = Math.min(col, ls[row - 1].length);
|
|
56
|
-
// Compute new offset: sum of lines 0..row-2 + their newlines + targetCol.
|
|
57
|
-
let off = 0;
|
|
58
|
-
for (let i = 0; i < row - 1; i++)
|
|
59
|
-
off += ls[i].length + 1;
|
|
60
|
-
off += targetCol;
|
|
61
|
-
return { ...buf, cursor: off };
|
|
62
|
-
}
|
|
63
|
-
export function moveDown(buf) {
|
|
64
|
-
const { row, col } = cursorRowCol(buf);
|
|
65
|
-
const ls = lines(buf.text);
|
|
66
|
-
if (row >= ls.length - 1)
|
|
67
|
-
return buf;
|
|
68
|
-
const targetCol = Math.min(col, ls[row + 1].length);
|
|
69
|
-
let off = 0;
|
|
70
|
-
for (let i = 0; i < row + 1; i++)
|
|
71
|
-
off += ls[i].length + 1;
|
|
72
|
-
off += targetCol;
|
|
73
|
-
return { ...buf, cursor: off };
|
|
74
|
-
}
|
|
75
|
-
/** True when the cursor is on the first logical line. */
|
|
76
|
-
export function onFirstLine(buf) {
|
|
77
|
-
return cursorRowCol(buf).row === 0;
|
|
78
|
-
}
|
|
79
|
-
/** True when the cursor is on the last logical line. */
|
|
80
|
-
export function onLastLine(buf) {
|
|
81
|
-
return cursorRowCol(buf).row === lines(buf.text).length - 1;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Word-wrap a logical line into visual rows of width `cols`.
|
|
85
|
-
* Returns array of strings (each ≤ cols chars). Tries to break at spaces.
|
|
86
|
-
*/
|
|
87
|
-
export function wrapLine(line, cols) {
|
|
88
|
-
if (cols <= 0)
|
|
89
|
-
return [line];
|
|
90
|
-
if (line.length <= cols)
|
|
91
|
-
return [line];
|
|
92
|
-
const out = [];
|
|
93
|
-
let rest = line;
|
|
94
|
-
while (rest.length > cols) {
|
|
95
|
-
let cut = cols;
|
|
96
|
-
// Look for a space to break at, within a small window.
|
|
97
|
-
const window = rest.slice(0, cols);
|
|
98
|
-
const lastSpace = window.lastIndexOf(' ');
|
|
99
|
-
if (lastSpace > Math.floor(cols * 0.5)) {
|
|
100
|
-
cut = lastSpace + 1;
|
|
101
|
-
}
|
|
102
|
-
out.push(rest.slice(0, cut));
|
|
103
|
-
rest = rest.slice(cut);
|
|
104
|
-
}
|
|
105
|
-
if (rest.length)
|
|
106
|
-
out.push(rest);
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
/** Wrap the full text, returning visual rows. */
|
|
110
|
-
export function wrapText(text, cols) {
|
|
111
|
-
return text.split('\n').flatMap((l) => (l.length === 0 ? [''] : wrapLine(l, cols)));
|
|
112
|
-
}
|
|
113
|
-
/** Stats for the dim status footer. */
|
|
114
|
-
export function bufferStats(text) {
|
|
115
|
-
return { chars: text.length, lineCount: text.split('\n').length };
|
|
116
|
-
}
|
package/dist/app/inputStore.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Composer state — kept in a nanostore so any subsystem can read/write
|
|
2
|
-
// without prop-drilling. Holds the active text buffer + completion state.
|
|
3
|
-
import { atom } from 'nanostores';
|
|
4
|
-
import { emptyBuffer } from './inputBuffer.js';
|
|
5
|
-
export const $buffer = atom(emptyBuffer());
|
|
6
|
-
export const $completion = atom({
|
|
7
|
-
active: false,
|
|
8
|
-
prefix: '',
|
|
9
|
-
candidates: [],
|
|
10
|
-
index: 0,
|
|
11
|
-
start: 0,
|
|
12
|
-
});
|
|
13
|
-
export function resetCompletion() {
|
|
14
|
-
$completion.set({ active: false, prefix: '', candidates: [], index: 0, start: 0 });
|
|
15
|
-
}
|
|
16
|
-
export function setBuffer(b) {
|
|
17
|
-
$buffer.set(b);
|
|
18
|
-
// Any non-Tab edit should reset the completion cycle.
|
|
19
|
-
resetCompletion();
|
|
20
|
-
}
|
|
21
|
-
/** Direct setter that does not reset completion (used by the cycle itself). */
|
|
22
|
-
export function setBufferRaw(b) {
|
|
23
|
-
$buffer.set(b);
|
|
24
|
-
}
|
package/dist/app/promptStore.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// Prompt store — confirmation + secret prompts requested by any subsystem.
|
|
2
|
-
// The composer hides while a prompt is active; the prompt overlay owns input.
|
|
3
|
-
import { atom } from 'nanostores';
|
|
4
|
-
export const $prompt = atom(null);
|
|
5
|
-
let counter = 0;
|
|
6
|
-
function nextId() {
|
|
7
|
-
counter += 1;
|
|
8
|
-
return `prompt_${Date.now()}_${counter}`;
|
|
9
|
-
}
|
|
10
|
-
/** Request a yes/no confirmation. Resolves with `true`/`false`, or `null` on Esc. */
|
|
11
|
-
export function requestConfirm(prompt, opts = {}) {
|
|
12
|
-
return new Promise((resolve) => {
|
|
13
|
-
$prompt.set({
|
|
14
|
-
id: nextId(),
|
|
15
|
-
kind: 'confirm',
|
|
16
|
-
prompt,
|
|
17
|
-
destructive: opts.destructive,
|
|
18
|
-
resolve: (v) => resolve(v),
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
/** Request a secret. Resolves with the entered string, or `null` on Esc. */
|
|
23
|
-
export function requestSecret(prompt, opts = {}) {
|
|
24
|
-
return new Promise((resolve) => {
|
|
25
|
-
$prompt.set({
|
|
26
|
-
id: nextId(),
|
|
27
|
-
kind: 'secret',
|
|
28
|
-
prompt,
|
|
29
|
-
mask: opts.mask !== false,
|
|
30
|
-
resolve: (v) => resolve(v),
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
export function resolveActivePrompt(value) {
|
|
35
|
-
const cur = $prompt.get();
|
|
36
|
-
if (!cur)
|
|
37
|
-
return;
|
|
38
|
-
cur.resolve(value);
|
|
39
|
-
$prompt.set(null);
|
|
40
|
-
}
|
package/dist/app/queueStore.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Queue store — submissions made while a turn is streaming are queued
|
|
2
|
-
// and flushed when the active turn ends. Composer shows `[queued: N]`.
|
|
3
|
-
import { atom } from 'nanostores';
|
|
4
|
-
export const $queue = atom([]);
|
|
5
|
-
export function enqueue(text) {
|
|
6
|
-
const t = text.replace(/\s+$/g, '');
|
|
7
|
-
if (!t)
|
|
8
|
-
return;
|
|
9
|
-
$queue.set([...$queue.get(), t]);
|
|
10
|
-
}
|
|
11
|
-
export function dequeueAll() {
|
|
12
|
-
const cur = $queue.get();
|
|
13
|
-
$queue.set([]);
|
|
14
|
-
return cur;
|
|
15
|
-
}
|
|
16
|
-
export function queueLength() {
|
|
17
|
-
return $queue.get().length;
|
|
18
|
-
}
|
|
19
|
-
export function clearQueue() {
|
|
20
|
-
$queue.set([]);
|
|
21
|
-
}
|