@blockrun/runcode 2.5.3 → 2.5.5
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/agent/commands.js +14 -9
- package/dist/agent/compact.js +17 -0
- package/dist/tools/askuser.js +10 -2
- package/dist/tools/bash.js +17 -2
- package/dist/tools/glob.js +19 -2
- package/dist/tools/grep.js +8 -0
- package/dist/ui/app.js +2 -2
- package/dist/ui/terminal.d.ts +1 -0
- package/dist/ui/terminal.js +11 -2
- package/package.json +1 -1
package/dist/agent/commands.js
CHANGED
|
@@ -295,17 +295,18 @@ const DIRECT_COMMANDS = {
|
|
|
295
295
|
try {
|
|
296
296
|
let address;
|
|
297
297
|
let balance;
|
|
298
|
+
const fetchTimeout = (ms) => new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms));
|
|
298
299
|
if (chain === 'solana') {
|
|
299
300
|
const { getOrCreateSolanaWallet, setupAgentSolanaWallet } = await import('@blockrun/llm');
|
|
300
301
|
const w = await getOrCreateSolanaWallet();
|
|
301
302
|
address = w.address;
|
|
302
303
|
try {
|
|
303
304
|
const client = await setupAgentSolanaWallet({ silent: true });
|
|
304
|
-
const bal = await client.getBalance();
|
|
305
|
+
const bal = await Promise.race([client.getBalance(), fetchTimeout(5000)]);
|
|
305
306
|
balance = `$${bal.toFixed(2)} USDC`;
|
|
306
307
|
}
|
|
307
308
|
catch {
|
|
308
|
-
balance = '(
|
|
309
|
+
balance = '(unavailable)';
|
|
309
310
|
}
|
|
310
311
|
}
|
|
311
312
|
else {
|
|
@@ -314,11 +315,11 @@ const DIRECT_COMMANDS = {
|
|
|
314
315
|
address = w.address;
|
|
315
316
|
try {
|
|
316
317
|
const client = setupAgentWallet({ silent: true });
|
|
317
|
-
const bal = await client.getBalance();
|
|
318
|
+
const bal = await Promise.race([client.getBalance(), fetchTimeout(5000)]);
|
|
318
319
|
balance = `$${bal.toFixed(2)} USDC`;
|
|
319
320
|
}
|
|
320
321
|
catch {
|
|
321
|
-
balance = '(
|
|
322
|
+
balance = '(unavailable)';
|
|
322
323
|
}
|
|
323
324
|
}
|
|
324
325
|
ctx.onEvent({ kind: 'text_delta', text: `**Wallet**\n` +
|
|
@@ -345,12 +346,16 @@ const DIRECT_COMMANDS = {
|
|
|
345
346
|
ctx.history.length = 0;
|
|
346
347
|
ctx.history.push(...compacted);
|
|
347
348
|
resetTokenAnchor();
|
|
349
|
+
const afterTokens = estimateHistoryTokens(ctx.history);
|
|
350
|
+
const saved = beforeTokens - afterTokens;
|
|
351
|
+
const pct = Math.round((saved / beforeTokens) * 100);
|
|
352
|
+
ctx.onEvent({ kind: 'text_delta', text: `Compacted: ~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens (saved ${pct}%)\n`
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
ctx.onEvent({ kind: 'text_delta', text: `Nothing to compact — history is already minimal (${beforeTokens.toLocaleString()} tokens, ${ctx.history.length} messages).\n`
|
|
357
|
+
});
|
|
348
358
|
}
|
|
349
|
-
const afterTokens = estimateHistoryTokens(ctx.history);
|
|
350
|
-
ctx.onEvent({ kind: 'text_delta', text: didCompact
|
|
351
|
-
? `Compacted: ~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens\n`
|
|
352
|
-
: `History too short to compact (${beforeTokens.toLocaleString()} tokens, ${ctx.history.length} messages).\n`
|
|
353
|
-
});
|
|
354
359
|
emitDone(ctx);
|
|
355
360
|
},
|
|
356
361
|
};
|
package/dist/agent/compact.js
CHANGED
|
@@ -38,8 +38,16 @@ export async function autoCompactIfNeeded(history, model, client, debug) {
|
|
|
38
38
|
if (debug) {
|
|
39
39
|
console.error(`[runcode] Auto-compacting: ~${currentTokens} tokens, threshold=${threshold}`);
|
|
40
40
|
}
|
|
41
|
+
const beforeTokens = estimateHistoryTokens(history);
|
|
41
42
|
try {
|
|
42
43
|
const compacted = await compactHistory(history, model, client, debug);
|
|
44
|
+
const afterTokens = estimateHistoryTokens(compacted);
|
|
45
|
+
if (afterTokens >= beforeTokens) {
|
|
46
|
+
if (debug) {
|
|
47
|
+
console.error(`[runcode] Auto-compaction grew history (${beforeTokens} → ${afterTokens}) — skipping`);
|
|
48
|
+
}
|
|
49
|
+
return { history, compacted: false };
|
|
50
|
+
}
|
|
43
51
|
return { history: compacted, compacted: true };
|
|
44
52
|
}
|
|
45
53
|
catch (err) {
|
|
@@ -58,8 +66,17 @@ export async function forceCompact(history, model, client, debug) {
|
|
|
58
66
|
if (history.length <= 4) {
|
|
59
67
|
return { history, compacted: false };
|
|
60
68
|
}
|
|
69
|
+
const beforeTokens = estimateHistoryTokens(history);
|
|
61
70
|
try {
|
|
62
71
|
const compacted = await compactHistory(history, model, client, debug);
|
|
72
|
+
const afterTokens = estimateHistoryTokens(compacted);
|
|
73
|
+
// Only accept compaction if it actually reduces tokens
|
|
74
|
+
if (afterTokens >= beforeTokens) {
|
|
75
|
+
if (debug) {
|
|
76
|
+
console.error(`[runcode] Compaction produced larger history (${beforeTokens} → ${afterTokens}) — reverting`);
|
|
77
|
+
}
|
|
78
|
+
return { history, compacted: false };
|
|
79
|
+
}
|
|
63
80
|
return { history: compacted, compacted: true };
|
|
64
81
|
}
|
|
65
82
|
catch (err) {
|
package/dist/tools/askuser.js
CHANGED
|
@@ -9,6 +9,14 @@ async function execute(input, _ctx) {
|
|
|
9
9
|
if (!question) {
|
|
10
10
|
return { output: 'Error: question is required', isError: true };
|
|
11
11
|
}
|
|
12
|
+
// In non-TTY (piped/scripted) mode, creating a new readline would conflict with
|
|
13
|
+
// the TerminalUI's existing readline. Return a hint for the model to proceed.
|
|
14
|
+
if (!process.stdin.isTTY) {
|
|
15
|
+
return {
|
|
16
|
+
output: `[Non-interactive mode] Cannot prompt user. Proceed with a reasonable assumption. Question was: ${question}`,
|
|
17
|
+
isError: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
12
20
|
console.error('');
|
|
13
21
|
console.error(chalk.yellow(' ╭─ Question ────────────────────────────'));
|
|
14
22
|
console.error(chalk.yellow(` │ ${question}`));
|
|
@@ -21,7 +29,7 @@ async function execute(input, _ctx) {
|
|
|
21
29
|
const rl = readline.createInterface({
|
|
22
30
|
input: process.stdin,
|
|
23
31
|
output: process.stderr,
|
|
24
|
-
terminal:
|
|
32
|
+
terminal: true,
|
|
25
33
|
});
|
|
26
34
|
return new Promise((resolve) => {
|
|
27
35
|
let answered = false;
|
|
@@ -32,7 +40,7 @@ async function execute(input, _ctx) {
|
|
|
32
40
|
});
|
|
33
41
|
rl.on('close', () => {
|
|
34
42
|
if (!answered)
|
|
35
|
-
resolve({ output: 'User
|
|
43
|
+
resolve({ output: 'User closed input without responding.', isError: false });
|
|
36
44
|
});
|
|
37
45
|
});
|
|
38
46
|
}
|
package/dist/tools/bash.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Bash capability — execute shell commands with timeout and output capture.
|
|
3
3
|
*/
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
const MAX_OUTPUT_BYTES = 512 * 1024; // 512KB
|
|
5
|
+
const MAX_OUTPUT_BYTES = 512 * 1024; // 512KB capture buffer (prevents OOM)
|
|
6
|
+
const MAX_RETURN_CHARS = 32_000; // 32KB return cap (~8,000 tokens) — prevents context bloat
|
|
6
7
|
const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes
|
|
7
8
|
async function execute(input, ctx) {
|
|
8
9
|
const { command, timeout } = input;
|
|
@@ -99,7 +100,21 @@ async function execute(input, ctx) {
|
|
|
99
100
|
result += stderr;
|
|
100
101
|
}
|
|
101
102
|
if (truncated) {
|
|
102
|
-
result += '\n\n... (output truncated
|
|
103
|
+
result += '\n\n... (output truncated — command produced >512KB)';
|
|
104
|
+
}
|
|
105
|
+
// Cap returned output to prevent context bloat.
|
|
106
|
+
// Keep the LAST part (most relevant for errors/test failures/build output).
|
|
107
|
+
if (result.length > MAX_RETURN_CHARS) {
|
|
108
|
+
const lines = result.split('\n');
|
|
109
|
+
let trimmed = '';
|
|
110
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
111
|
+
const candidate = lines[i] + '\n' + trimmed;
|
|
112
|
+
if (candidate.length > MAX_RETURN_CHARS)
|
|
113
|
+
break;
|
|
114
|
+
trimmed = candidate;
|
|
115
|
+
}
|
|
116
|
+
const omitted = result.length - trimmed.length;
|
|
117
|
+
result = `... (${omitted.toLocaleString()} chars omitted from start)\n${trimmed}`;
|
|
103
118
|
}
|
|
104
119
|
if (killed) {
|
|
105
120
|
resolve({
|
package/dist/tools/glob.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
const MAX_RESULTS =
|
|
6
|
+
const MAX_RESULTS = 200;
|
|
7
|
+
const MAX_OUTPUT_CHARS = 12_000; // ~3,000 tokens — prevents huge glob results from blowing up context
|
|
7
8
|
/**
|
|
8
9
|
* Simple glob matcher supporting *, **, and ? wildcards.
|
|
9
10
|
* No external dependencies.
|
|
@@ -115,7 +116,23 @@ async function execute(input, ctx) {
|
|
|
115
116
|
}
|
|
116
117
|
let output = sorted.join('\n');
|
|
117
118
|
if (sorted.length >= MAX_RESULTS) {
|
|
118
|
-
output += `\n\n... (limited to ${MAX_RESULTS} results)`;
|
|
119
|
+
output += `\n\n... (limited to ${MAX_RESULTS} results. Use a more specific pattern to narrow results.)`;
|
|
120
|
+
}
|
|
121
|
+
// Cap total output length to prevent context bloat
|
|
122
|
+
if (output.length > MAX_OUTPUT_CHARS) {
|
|
123
|
+
const lines = output.split('\n');
|
|
124
|
+
let trimmed = '';
|
|
125
|
+
let count = 0;
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
if ((trimmed + line).length > MAX_OUTPUT_CHARS)
|
|
128
|
+
break;
|
|
129
|
+
trimmed += (trimmed ? '\n' : '') + line;
|
|
130
|
+
count++;
|
|
131
|
+
}
|
|
132
|
+
const remaining = lines.length - count;
|
|
133
|
+
if (remaining > 0) {
|
|
134
|
+
output = `${trimmed}\n... (${remaining} more paths not shown — use a more specific pattern)`;
|
|
135
|
+
}
|
|
119
136
|
}
|
|
120
137
|
return { output };
|
|
121
138
|
}
|
package/dist/tools/grep.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { execSync, execFileSync } from 'node:child_process';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
const MAX_GREP_OUTPUT_CHARS = 16_000; // ~4,000 tokens — prevents huge grep results
|
|
7
8
|
let _hasRipgrep = null;
|
|
8
9
|
function hasRipgrep() {
|
|
9
10
|
if (_hasRipgrep !== null)
|
|
@@ -79,6 +80,10 @@ function runRipgrep(opts, searchPath, mode, limit) {
|
|
|
79
80
|
if (lines.length > limited.length) {
|
|
80
81
|
output += `\n\n... (${lines.length - limited.length} more results, use head_limit to see more)`;
|
|
81
82
|
}
|
|
83
|
+
// Cap total output to prevent context bloat
|
|
84
|
+
if (output.length > MAX_GREP_OUTPUT_CHARS) {
|
|
85
|
+
output = output.slice(0, MAX_GREP_OUTPUT_CHARS) + `\n... (output capped at ${MAX_GREP_OUTPUT_CHARS / 1000}KB — use more specific pattern or head_limit)`;
|
|
86
|
+
}
|
|
82
87
|
return { output: output || 'No matches found' };
|
|
83
88
|
}
|
|
84
89
|
catch (err) {
|
|
@@ -127,6 +132,9 @@ function runNativeGrep(opts, searchPath, mode, limit) {
|
|
|
127
132
|
if (lines.length > limited.length) {
|
|
128
133
|
output += `\n\n... (${lines.length - limited.length} more results)`;
|
|
129
134
|
}
|
|
135
|
+
if (output.length > MAX_GREP_OUTPUT_CHARS) {
|
|
136
|
+
output = output.slice(0, MAX_GREP_OUTPUT_CHARS) + `\n... (output capped at ${MAX_GREP_OUTPUT_CHARS / 1000}KB)`;
|
|
137
|
+
}
|
|
130
138
|
return { output: output || 'No matches found' };
|
|
131
139
|
}
|
|
132
140
|
catch (err) {
|
package/dist/ui/app.js
CHANGED
|
@@ -295,9 +295,9 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
295
295
|
}), _jsx(Text, { children: " " })] }));
|
|
296
296
|
}
|
|
297
297
|
// ── Normal Mode ──
|
|
298
|
-
return (_jsxs(Box, { flexDirection: "column", children: [statusMsg && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "green", children: statusMsg }) })), showHelp && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Commands" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/model" }), " [name] Switch model (picker if no name)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/wallet" }), " Show wallet address & balance"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/cost" }), " Session cost & savings"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/retry" }), " Retry the last prompt"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/compact" }), " Compress conversation history"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Coding \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/test" }), " Run tests"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/fix" }), " Fix last error"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/review" }), " Code review"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/explain" }), " file Explain code"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/search" }), " query Search codebase"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/refactor" }), " desc Refactor code"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/scaffold" }), " desc Generate boilerplate"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Git \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/commit" }), " Commit changes"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/push" }), " Push to remote"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/pr" }), " Create pull request"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/status" }), " Git status"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/diff" }), " Git diff"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/log" }), " Git log"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/branch" }), " [name] Branches"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/stash" }), " Stash changes"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/undo" }), " Undo last commit"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Analysis \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/security" }), " Security audit"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/lint" }), " Quality check"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/optimize" }), " Performance check"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/todo" }), " Find TODOs"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/deps" }), " Dependencies"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/clean" }), " Dead code removal"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/context" }), " Session info (model, tokens, mode)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/plan" }), " Enter plan mode (read-only tools)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/execute" }), " Exit plan mode (enable all tools)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/sessions" }), " List saved sessions"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/resume" }), " id Resume a saved session"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/clear" }), " Clear conversation display"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/doctor" }), " Diagnose setup issues"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/help" }), " This help"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/exit" }), " Quit"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Shortcuts: sonnet, opus, gpt, gemini, deepseek, flash, free, r1, o4, nano, mini, haiku" })] })), showWallet && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Wallet" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" Chain: ", _jsx(Text, { color: "magenta", children: chain })] }), _jsxs(Text, { children: [" Address: ", _jsx(Text, { color: "cyan", children: walletAddress })] }), _jsxs(Text, { children: [" Balance: ", _jsx(Text, { color: "green", children: balance })] })] })), Array.from(tools.
|
|
298
|
+
return (_jsxs(Box, { flexDirection: "column", children: [statusMsg && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "green", children: statusMsg }) })), showHelp && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Commands" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/model" }), " [name] Switch model (picker if no name)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/wallet" }), " Show wallet address & balance"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/cost" }), " Session cost & savings"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/retry" }), " Retry the last prompt"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/compact" }), " Compress conversation history"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Coding \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/test" }), " Run tests"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/fix" }), " Fix last error"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/review" }), " Code review"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/explain" }), " file Explain code"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/search" }), " query Search codebase"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/refactor" }), " desc Refactor code"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/scaffold" }), " desc Generate boilerplate"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Git \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/commit" }), " Commit changes"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/push" }), " Push to remote"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/pr" }), " Create pull request"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/status" }), " Git status"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/diff" }), " Git diff"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/log" }), " Git log"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/branch" }), " [name] Branches"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/stash" }), " Stash changes"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/undo" }), " Undo last commit"] }), _jsx(Text, { dimColor: true, children: " \u2500\u2500 Analysis \u2500\u2500" }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/security" }), " Security audit"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/lint" }), " Quality check"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/optimize" }), " Performance check"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/todo" }), " Find TODOs"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/deps" }), " Dependencies"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/clean" }), " Dead code removal"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/context" }), " Session info (model, tokens, mode)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/plan" }), " Enter plan mode (read-only tools)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/execute" }), " Exit plan mode (enable all tools)"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/sessions" }), " List saved sessions"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/resume" }), " id Resume a saved session"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/clear" }), " Clear conversation display"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/doctor" }), " Diagnose setup issues"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/help" }), " This help"] }), _jsxs(Text, { children: [" ", _jsx(Text, { color: "cyan", children: "/exit" }), " Quit"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Shortcuts: sonnet, opus, gpt, gemini, deepseek, flash, free, r1, o4, nano, mini, haiku" })] })), showWallet && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Wallet" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [" Chain: ", _jsx(Text, { color: "magenta", children: chain })] }), _jsxs(Text, { children: [" Address: ", _jsx(Text, { color: "cyan", children: walletAddress })] }), _jsxs(Text, { children: [" Balance: ", _jsx(Text, { color: "green", children: balance })] })] })), Array.from(tools.entries()).map(([id, tool]) => (_jsx(Box, { marginLeft: 1, children: tool.done ? (tool.error
|
|
299
299
|
? _jsxs(Text, { color: "red", children: [" \u2717 ", tool.name, " ", _jsxs(Text, { dimColor: true, children: [tool.elapsed, "ms"] })] })
|
|
300
|
-
: _jsxs(Text, { color: "green", children: [" \u2713 ", tool.name, " ", _jsxs(Text, { dimColor: true, children: [tool.elapsed, "ms \u2014 ", tool.preview.slice(0, 200), tool.preview.length > 200 ? '...' : ''] })] })) : (_jsxs(Text, { color: "cyan", children: [" ", _jsx(Spinner, { type: "dots" }), " ", tool.name, "... ", _jsx(Text, { dimColor: true, children: (() => { const s = Math.round((Date.now() - tool.startTime) / 1000); return s > 0 ? `${s}s` : ''; })() })] })) },
|
|
300
|
+
: _jsxs(Text, { color: "green", children: [" \u2713 ", tool.name, " ", _jsxs(Text, { dimColor: true, children: [tool.elapsed, "ms \u2014 ", tool.preview.slice(0, 200), tool.preview.length > 200 ? '...' : ''] })] })) : (_jsxs(Text, { color: "cyan", children: [" ", _jsx(Spinner, { type: "dots" }), " ", tool.name, "... ", _jsx(Text, { dimColor: true, children: (() => { const s = Math.round((Date.now() - tool.startTime) / 1000); return s > 0 ? `${s}s` : ''; })() })] })) }, id))), thinking && (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { color: "magenta", children: [" ", _jsx(Spinner, { type: "dots" }), " thinking..."] }), thinkingText && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [" ", thinkingText.split('\n').pop()?.slice(0, 80)] }))] })), waiting && !thinking && tools.size === 0 && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: "yellow", children: [" ", _jsx(Spinner, { type: "dots" }), " ", _jsx(Text, { dimColor: true, children: currentModel })] }) })), streamText && (_jsx(Box, { marginTop: 0, marginBottom: 0, children: _jsx(Text, { children: streamText }) })), ready && (turnTokens.input > 0 || turnTokens.output > 0) && streamText && (_jsx(Box, { marginLeft: 1, marginTop: 0, children: _jsxs(Text, { dimColor: true, children: [turnTokens.input.toLocaleString(), " in / ", turnTokens.output.toLocaleString(), " out", totalCost > 0 ? ` · $${totalCost.toFixed(4)} session` : ''] }) })), ready && (_jsx(InputBox, { input: input, setInput: setInput, onSubmit: handleSubmit, model: currentModel, balance: balance, focused: mode === 'input' }))] }));
|
|
301
301
|
}
|
|
302
302
|
export function launchInkUI(opts) {
|
|
303
303
|
let resolveInput = null;
|
package/dist/ui/terminal.d.ts
CHANGED
package/dist/ui/terminal.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import readline from 'node:readline';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
|
+
import { estimateCost } from '../pricing.js';
|
|
8
9
|
// ─── Spinner ───────────────────────────────────────────────────────────────
|
|
9
10
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
10
11
|
class Spinner {
|
|
@@ -130,6 +131,7 @@ export class TerminalUI {
|
|
|
130
131
|
activeCapabilities = new Map();
|
|
131
132
|
totalInputTokens = 0;
|
|
132
133
|
totalOutputTokens = 0;
|
|
134
|
+
sessionModel = '';
|
|
133
135
|
mdRenderer = new MarkdownRenderer();
|
|
134
136
|
// Line queue for piped (non-TTY) input — buffers all stdin lines eagerly
|
|
135
137
|
lineQueue = [];
|
|
@@ -269,6 +271,8 @@ export class TerminalUI {
|
|
|
269
271
|
case 'usage':
|
|
270
272
|
this.totalInputTokens += event.inputTokens;
|
|
271
273
|
this.totalOutputTokens += event.outputTokens;
|
|
274
|
+
if (event.model)
|
|
275
|
+
this.sessionModel = event.model;
|
|
272
276
|
break;
|
|
273
277
|
case 'turn_done': {
|
|
274
278
|
this.spinner.stop();
|
|
@@ -295,9 +299,14 @@ export class TerminalUI {
|
|
|
295
299
|
const cmd = parts[0].toLowerCase();
|
|
296
300
|
switch (cmd) {
|
|
297
301
|
case '/cost':
|
|
298
|
-
case '/usage':
|
|
299
|
-
|
|
302
|
+
case '/usage': {
|
|
303
|
+
const cost = this.sessionModel
|
|
304
|
+
? estimateCost(this.sessionModel, this.totalInputTokens, this.totalOutputTokens)
|
|
305
|
+
: 0;
|
|
306
|
+
const costStr = cost > 0 ? ` · $${cost.toFixed(4)} USDC` : '';
|
|
307
|
+
console.error(chalk.dim(`\n Tokens: ${this.totalInputTokens.toLocaleString()} in / ${this.totalOutputTokens.toLocaleString()} out${costStr}\n`));
|
|
300
308
|
return true;
|
|
309
|
+
}
|
|
301
310
|
default:
|
|
302
311
|
// All other slash commands pass through to the agent loop (commands.ts handles them)
|
|
303
312
|
return false;
|