@blockrun/cc 0.9.1 → 0.9.2

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.
@@ -27,8 +27,14 @@ export async function balanceCommand() {
27
27
  }
28
28
  }
29
29
  }
30
- catch {
31
- console.log(chalk.red('No wallet found. Run `brcc setup` first.'));
30
+ catch (err) {
31
+ const msg = err instanceof Error ? err.message : '';
32
+ if (msg.includes('ENOENT') || msg.includes('wallet') || msg.includes('key')) {
33
+ console.log(chalk.red('No wallet found. Run `brcc setup` first.'));
34
+ }
35
+ else {
36
+ console.log(chalk.red(`Error checking balance: ${msg || 'unknown error'}`));
37
+ }
32
38
  process.exit(1);
33
39
  }
34
40
  }
@@ -37,6 +37,13 @@ export async function modelsCommand() {
37
37
  console.log(`\n${chalk.dim(`${models.length} models available. Use:`)} ${chalk.bold('brcc start --model <model-id>')}`);
38
38
  }
39
39
  catch (err) {
40
- console.log(chalk.red(`Error: ${err instanceof Error ? err.message : 'Failed to fetch models'}`));
40
+ const msg = err instanceof Error ? err.message : 'unknown error';
41
+ if (msg.includes('fetch') || msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
42
+ console.log(chalk.red(`Cannot reach BlockRun API at ${apiUrl}`));
43
+ console.log(chalk.dim('Check your internet connection or try again later.'));
44
+ }
45
+ else {
46
+ console.log(chalk.red(`Error: ${msg}`));
47
+ }
41
48
  }
42
49
  }
@@ -8,6 +8,11 @@ export async function startCommand(options) {
8
8
  const chain = loadChain();
9
9
  const apiUrl = API_URLS[chain];
10
10
  const fallbackEnabled = options.fallback !== false; // Default true
11
+ const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
12
+ if (isNaN(port) || port < 1 || port > 65535) {
13
+ console.log(chalk.red(`Invalid port: ${options.port}. Must be 1-65535.`));
14
+ process.exit(1);
15
+ }
11
16
  if (chain === 'solana') {
12
17
  const wallet = await getOrCreateSolanaWallet();
13
18
  if (wallet.isNew) {
@@ -16,7 +21,6 @@ export async function startCommand(options) {
16
21
  console.log(`\nSend USDC on Solana to this address, then run ${chalk.bold('brcc start')} again.\n`);
17
22
  return;
18
23
  }
19
- const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
20
24
  const shouldLaunch = options.launch !== false;
21
25
  const model = options.model;
22
26
  console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
@@ -35,7 +39,7 @@ export async function startCommand(options) {
35
39
  debug: options.debug,
36
40
  fallbackEnabled,
37
41
  });
38
- launchServer(server, port, shouldLaunch, model);
42
+ launchServer(server, port, shouldLaunch, model, options.debug);
39
43
  }
40
44
  else {
41
45
  const wallet = getOrCreateWallet();
@@ -45,7 +49,6 @@ export async function startCommand(options) {
45
49
  console.log(`\nSend USDC on Base to this address, then run ${chalk.bold('brcc start')} again.\n`);
46
50
  return;
47
51
  }
48
- const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
49
52
  const shouldLaunch = options.launch !== false;
50
53
  const model = options.model;
51
54
  console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
@@ -64,13 +67,15 @@ export async function startCommand(options) {
64
67
  debug: options.debug,
65
68
  fallbackEnabled,
66
69
  });
67
- launchServer(server, port, shouldLaunch, model);
70
+ launchServer(server, port, shouldLaunch, model, options.debug);
68
71
  }
69
72
  }
70
- function launchServer(server, port, shouldLaunch, model) {
73
+ function launchServer(server, port, shouldLaunch, model, debug) {
71
74
  server.listen(port, () => {
72
75
  console.log(chalk.green(`✓ Proxy running on port ${port}`));
73
76
  console.log(chalk.dim(` Usage tracking: ~/.blockrun/brcc-stats.json`));
77
+ if (debug)
78
+ console.log(chalk.dim(` Debug log: ~/.blockrun/brcc-debug.log`));
74
79
  console.log(chalk.dim(` Run 'brcc stats' to view statistics\n`));
75
80
  if (shouldLaunch) {
76
81
  console.log('Starting Claude Code...\n');
package/dist/index.js CHANGED
@@ -6,12 +6,22 @@ import { balanceCommand } from './commands/balance.js';
6
6
  import { modelsCommand } from './commands/models.js';
7
7
  import { configCommand } from './commands/config.js';
8
8
  import { statsCommand } from './commands/stats.js';
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ let version = '0.9.0';
14
+ try {
15
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'));
16
+ version = pkg.version || version;
17
+ }
18
+ catch { /* use default */ }
9
19
  const program = new Command();
10
20
  program
11
21
  .name('brcc')
12
22
  .description('BlockRun Claude Code — run Claude Code with any model, pay with USDC.\n\n' +
13
23
  'Use /model inside Claude Code to switch between models on the fly.')
14
- .version('0.9.0');
24
+ .version(version);
15
25
  program
16
26
  .command('setup [chain]')
17
27
  .description('Create a new wallet for payments (base or solana)')
@@ -75,11 +75,18 @@ const MODEL_PRICING = {
75
75
  'google/gemini-2.5-pro': { input: 1.25, output: 10.0 },
76
76
  'google/gemini-2.5-flash': { input: 0.3, output: 2.5 },
77
77
  'deepseek/deepseek-chat': { input: 0.28, output: 0.42 },
78
+ 'deepseek/deepseek-reasoner': { input: 0.55, output: 2.19 },
78
79
  'xai/grok-3': { input: 3.0, output: 15.0 },
79
80
  'xai/grok-4-fast': { input: 0.2, output: 0.5 },
81
+ 'xai/grok-4-1-fast-reasoning': { input: 0.2, output: 0.5 },
80
82
  'nvidia/gpt-oss-120b': { input: 0, output: 0 },
81
83
  'zai/glm-5': { input: 1.0, output: 3.2 },
82
84
  'moonshot/kimi-k2.5': { input: 0.6, output: 3.0 },
85
+ 'openai/gpt-5.3-codex': { input: 2.5, output: 10.0 },
86
+ 'openai/o3': { input: 2.0, output: 8.0 },
87
+ 'openai/o4-mini': { input: 1.1, output: 4.4 },
88
+ 'google/gemini-2.5-flash-lite': { input: 0.08, output: 0.3 },
89
+ 'google/gemini-3.1-pro': { input: 1.25, output: 10.0 },
83
90
  };
84
91
  function estimateCost(model, inputTokens, outputTokens) {
85
92
  const pricing = MODEL_PRICING[model] || { input: 2.0, output: 10.0 };
@@ -301,23 +308,63 @@ export function createProxy(options) {
301
308
  response.headers.forEach((v, k) => {
302
309
  responseHeaders[k] = v;
303
310
  });
311
+ // Intercept error responses and ensure Anthropic-format errors
312
+ // so Claude Code doesn't fall back to showing a login page
313
+ if (response.status >= 400 && !responseHeaders['content-type']?.includes('text/event-stream')) {
314
+ let errorBody;
315
+ try {
316
+ const rawText = await response.text();
317
+ const parsed = JSON.parse(rawText);
318
+ // Already has Anthropic error shape? Pass through
319
+ if (parsed.type === 'error' && parsed.error) {
320
+ errorBody = rawText;
321
+ }
322
+ else {
323
+ // Wrap in Anthropic error format
324
+ const errorMsg = parsed.error?.message || parsed.message || rawText.slice(0, 500);
325
+ errorBody = JSON.stringify({
326
+ type: 'error',
327
+ error: {
328
+ type: response.status === 401 ? 'authentication_error'
329
+ : response.status === 402 ? 'invalid_request_error'
330
+ : response.status === 429 ? 'rate_limit_error'
331
+ : response.status === 400 ? 'invalid_request_error'
332
+ : 'api_error',
333
+ message: `[${finalModel}] ${errorMsg}`,
334
+ },
335
+ });
336
+ }
337
+ }
338
+ catch {
339
+ errorBody = JSON.stringify({
340
+ type: 'error',
341
+ error: { type: 'api_error', message: `Backend returned ${response.status}` },
342
+ });
343
+ }
344
+ res.writeHead(response.status, { 'Content-Type': 'application/json' });
345
+ res.end(errorBody);
346
+ log(`⚠️ ${response.status} from backend for ${finalModel}`);
347
+ return;
348
+ }
304
349
  res.writeHead(response.status, responseHeaders);
305
350
  const isStreaming = responseHeaders['content-type']?.includes('text/event-stream');
306
351
  if (response.body) {
307
352
  const reader = response.body.getReader();
308
353
  const decoder = new TextDecoder();
309
- let lastChunkText = '';
310
354
  let fullResponse = '';
355
+ const STREAM_CAP = 5_000_000; // 5MB cap on accumulated stream
311
356
  const pump = async () => {
312
357
  while (true) {
313
358
  const { done, value } = await reader.read();
314
359
  if (done) {
315
360
  // Record stats from streaming response
316
- if (isStreaming && lastChunkText) {
317
- const outputMatch = lastChunkText.match(/"output_tokens"\s*:\s*(\d+)/);
361
+ if (isStreaming && fullResponse) {
362
+ // Search full response for the last output_tokens value
363
+ const allOutputMatches = [...fullResponse.matchAll(/"output_tokens"\s*:\s*(\d+)/g)];
364
+ const lastOutputMatch = allOutputMatches[allOutputMatches.length - 1];
318
365
  const inputMatch = fullResponse.match(/"input_tokens"\s*:\s*(\d+)/);
319
- if (outputMatch) {
320
- lastOutputTokens = parseInt(outputMatch[1], 10);
366
+ if (lastOutputMatch) {
367
+ lastOutputTokens = parseInt(lastOutputMatch[1], 10);
321
368
  const inputTokens = inputMatch
322
369
  ? parseInt(inputMatch[1], 10)
323
370
  : 0;
@@ -330,9 +377,8 @@ export function createProxy(options) {
330
377
  res.end();
331
378
  break;
332
379
  }
333
- if (isStreaming) {
380
+ if (isStreaming && fullResponse.length < STREAM_CAP) {
334
381
  const chunk = decoder.decode(value, { stream: true });
335
- lastChunkText = chunk;
336
382
  fullResponse += chunk;
337
383
  }
338
384
  res.write(value);
@@ -221,10 +221,17 @@ export function routeRequest(prompt, profile = 'auto') {
221
221
  'google/gemini-2.5-flash': 0.001,
222
222
  'google/gemini-2.5-flash-lite': 0.0003,
223
223
  'deepseek/deepseek-chat': 0.0004,
224
+ 'deepseek/deepseek-reasoner': 0.003,
224
225
  'moonshot/kimi-k2.5': 0.002,
226
+ 'google/gemini-2.5-pro': 0.006,
225
227
  'google/gemini-3.1-pro': 0.007,
228
+ 'anthropic/claude-haiku-4.5': 0.003,
226
229
  'anthropic/claude-sonnet-4.6': 0.009,
227
230
  'anthropic/claude-opus-4.6': 0.015,
231
+ 'openai/gpt-5.3-codex': 0.008,
232
+ 'openai/gpt-5.4': 0.009,
233
+ 'openai/o3': 0.012,
234
+ 'openai/o4-mini': 0.006,
228
235
  'xai/grok-4-1-fast-reasoning': 0.0004,
229
236
  };
230
237
  const modelCost = modelCosts[model] ?? 0.005;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/cc",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Run Claude Code with any model — no rate limits, no account locks, no phone verification. Pay per use with USDC.",
5
5
  "type": "module",
6
6
  "bin": {