@blockrun/runcode 2.5.3 → 2.5.4

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.
@@ -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 = '(fetch failed)';
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 = '(fetch failed)';
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
  };
@@ -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) {
@@ -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: process.stdin.isTTY ?? false,
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 did not respond (EOF/piped input).', isError: true });
43
+ resolve({ output: 'User closed input without responding.', isError: false });
36
44
  });
37
45
  });
38
46
  }
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.values()).map((tool, i) => (_jsx(Box, { marginLeft: 1, children: tool.done ? (tool.error
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` : ''; })() })] })) }, i))), 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' }))] }));
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;
@@ -9,6 +9,7 @@ export declare class TerminalUI {
9
9
  private activeCapabilities;
10
10
  private totalInputTokens;
11
11
  private totalOutputTokens;
12
+ private sessionModel;
12
13
  private mdRenderer;
13
14
  private lineQueue;
14
15
  private lineWaiters;
@@ -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
- console.error(chalk.dim(`\n Tokens: ${this.totalInputTokens.toLocaleString()} in / ${this.totalOutputTokens.toLocaleString()} out\n`));
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/runcode",
3
- "version": "2.5.3",
3
+ "version": "2.5.4",
4
4
  "description": "RunCode — AI coding agent powered by 41+ models. Pay per use with USDC.",
5
5
  "type": "module",
6
6
  "bin": {