@blockrun/cc 0.2.0 → 0.3.0
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/README.md +2 -2
- package/dist/commands/balance.js +24 -8
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +36 -14
- package/dist/commands/start.js +41 -15
- package/dist/config.d.ts +6 -1
- package/dist/config.js +29 -1
- package/dist/index.js +3 -3
- package/dist/proxy/server.d.ts +2 -0
- package/dist/proxy/server.js +71 -18
- package/dist/wallet/manager.d.ts +4 -0
- package/dist/wallet/manager.js +11 -3
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Hitting usage limits? Account disabled? Can't verify your phone? brcc fixes all of that.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install -g
|
|
8
|
+
npm install -g @blockrun/cc
|
|
9
9
|
brcc start
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -35,7 +35,7 @@ Claude Code --> brcc (local proxy) --> BlockRun API --> Any model
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
# Install
|
|
38
|
-
npm install -g
|
|
38
|
+
npm install -g @blockrun/cc
|
|
39
39
|
|
|
40
40
|
# Create a wallet (one time)
|
|
41
41
|
brcc setup
|
package/dist/commands/balance.js
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { setupAgentWallet } from '@blockrun/llm';
|
|
2
|
+
import { setupAgentWallet, setupAgentSolanaWallet } from '@blockrun/llm';
|
|
3
|
+
import { loadChain } from '../config.js';
|
|
3
4
|
export async function balanceCommand() {
|
|
5
|
+
const chain = loadChain();
|
|
4
6
|
try {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
console.log(
|
|
7
|
+
if (chain === 'solana') {
|
|
8
|
+
const client = await setupAgentSolanaWallet({ silent: true });
|
|
9
|
+
const address = await client.getWalletAddress();
|
|
10
|
+
const balance = await client.getBalance();
|
|
11
|
+
console.log(`Chain: ${chalk.magenta('solana')}`);
|
|
12
|
+
console.log(`Wallet: ${chalk.cyan(address)}`);
|
|
13
|
+
console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
|
|
14
|
+
if (balance === 0) {
|
|
15
|
+
console.log(chalk.dim(`\nSend USDC on Solana to ${address} to get started.`));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const client = setupAgentWallet({ silent: true });
|
|
20
|
+
const address = client.getWalletAddress();
|
|
21
|
+
const balance = await client.getBalance();
|
|
22
|
+
console.log(`Chain: ${chalk.magenta('base')}`);
|
|
23
|
+
console.log(`Wallet: ${chalk.cyan(address)}`);
|
|
24
|
+
console.log(`USDC Balance: ${chalk.green(`$${balance.toFixed(2)}`)}`);
|
|
25
|
+
if (balance === 0) {
|
|
26
|
+
console.log(chalk.dim(`\nSend USDC on Base to ${address} to get started.`));
|
|
27
|
+
}
|
|
12
28
|
}
|
|
13
29
|
}
|
|
14
30
|
catch {
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function setupCommand(): Promise<void>;
|
|
1
|
+
export declare function setupCommand(chainArg?: string): Promise<void>;
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { getOrCreateWallet, scanWallets } from '@blockrun/llm';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { getOrCreateWallet, scanWallets, getOrCreateSolanaWallet, scanSolanaWallets, } from '@blockrun/llm';
|
|
3
|
+
import { saveChain } from '../config.js';
|
|
4
|
+
export async function setupCommand(chainArg) {
|
|
5
|
+
const chain = chainArg === 'solana' ? 'solana' : 'base';
|
|
6
|
+
if (chain === 'solana') {
|
|
7
|
+
const wallets = scanSolanaWallets();
|
|
8
|
+
if (wallets.length > 0) {
|
|
9
|
+
console.log(chalk.yellow('Solana wallet already exists.'));
|
|
10
|
+
console.log(`Address: ${chalk.cyan(wallets[0].publicKey)}`);
|
|
11
|
+
saveChain('solana');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log('Creating new Solana wallet...\n');
|
|
15
|
+
const { address, isNew } = await getOrCreateSolanaWallet();
|
|
16
|
+
if (isNew) {
|
|
17
|
+
console.log(chalk.green('Solana wallet created!\n'));
|
|
18
|
+
}
|
|
19
|
+
console.log(`Address: ${chalk.cyan(address)}`);
|
|
20
|
+
console.log(`\nSend USDC on Solana to this address to fund your account.`);
|
|
9
21
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
else {
|
|
23
|
+
const wallets = scanWallets();
|
|
24
|
+
if (wallets.length > 0) {
|
|
25
|
+
console.log(chalk.yellow('Wallet already exists.'));
|
|
26
|
+
console.log(`Address: ${chalk.cyan(wallets[0].address)}`);
|
|
27
|
+
saveChain('base');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log('Creating new wallet...\n');
|
|
31
|
+
const { address, isNew } = getOrCreateWallet();
|
|
32
|
+
if (isNew) {
|
|
33
|
+
console.log(chalk.green('Wallet created!\n'));
|
|
34
|
+
}
|
|
35
|
+
console.log(`Address: ${chalk.cyan(address)}`);
|
|
36
|
+
console.log(`\nSend USDC on Base to this address to fund your account.`);
|
|
14
37
|
}
|
|
15
|
-
|
|
16
|
-
console.log(`\nSend USDC on Base to this address to fund your account.`);
|
|
38
|
+
saveChain(chain);
|
|
17
39
|
console.log(`Then run ${chalk.bold('brcc start')} to launch Claude Code.\n`);
|
|
18
|
-
console.log(chalk.dim(
|
|
40
|
+
console.log(chalk.dim(`Chain: ${chain} — saved to ~/.blockrun/`));
|
|
19
41
|
}
|
package/dist/commands/start.js
CHANGED
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { getOrCreateWallet } from '@blockrun/llm';
|
|
3
|
+
import { getOrCreateWallet, getOrCreateSolanaWallet } from '@blockrun/llm';
|
|
4
4
|
import { createProxy } from '../proxy/server.js';
|
|
5
|
-
import {
|
|
5
|
+
import { loadChain, API_URLS, DEFAULT_PROXY_PORT } from '../config.js';
|
|
6
6
|
export async function startCommand(options) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const chain = loadChain();
|
|
8
|
+
const apiUrl = API_URLS[chain];
|
|
9
|
+
if (chain === 'solana') {
|
|
10
|
+
const wallet = await getOrCreateSolanaWallet();
|
|
11
|
+
if (wallet.isNew) {
|
|
12
|
+
console.log(chalk.yellow('No Solana wallet found — created a new one.'));
|
|
13
|
+
console.log(`Address: ${chalk.cyan(wallet.address)}`);
|
|
14
|
+
console.log(`\nSend USDC on Solana to this address, then run ${chalk.bold('brcc start')} again.\n`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
|
|
18
|
+
const shouldLaunch = options.launch !== false;
|
|
19
|
+
console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
|
|
20
|
+
console.log(`Chain: ${chalk.magenta('solana')}`);
|
|
21
|
+
console.log(`Wallet: ${chalk.cyan(wallet.address)}`);
|
|
22
|
+
console.log(`Proxy: ${chalk.cyan(`http://localhost:${port}`)}`);
|
|
23
|
+
console.log(`Backend: ${chalk.dim(apiUrl)}\n`);
|
|
24
|
+
const server = createProxy({ port, apiUrl, chain: 'solana' });
|
|
25
|
+
launchServer(server, port, shouldLaunch);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const wallet = getOrCreateWallet();
|
|
29
|
+
if (wallet.isNew) {
|
|
30
|
+
console.log(chalk.yellow('No wallet found — created a new one.'));
|
|
31
|
+
console.log(`Address: ${chalk.cyan(wallet.address)}`);
|
|
32
|
+
console.log(`\nSend USDC on Base to this address, then run ${chalk.bold('brcc start')} again.\n`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
|
|
36
|
+
const shouldLaunch = options.launch !== false;
|
|
37
|
+
console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
|
|
38
|
+
console.log(`Chain: ${chalk.magenta('base')}`);
|
|
39
|
+
console.log(`Wallet: ${chalk.cyan(wallet.address)}`);
|
|
40
|
+
console.log(`Proxy: ${chalk.cyan(`http://localhost:${port}`)}`);
|
|
41
|
+
console.log(`Backend: ${chalk.dim(apiUrl)}\n`);
|
|
42
|
+
const server = createProxy({ port, apiUrl, chain: 'base' });
|
|
43
|
+
launchServer(server, port, shouldLaunch);
|
|
13
44
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
console.log(chalk.bold('brcc — BlockRun Claude Code\n'));
|
|
17
|
-
console.log(`Wallet: ${chalk.cyan(wallet.address)}`);
|
|
18
|
-
console.log(`Proxy: ${chalk.cyan(`http://localhost:${port}`)}`);
|
|
19
|
-
console.log(`Backend: ${chalk.dim(DEFAULT_API_URL)}\n`);
|
|
20
|
-
const server = createProxy({ port, apiUrl: DEFAULT_API_URL });
|
|
45
|
+
}
|
|
46
|
+
function launchServer(server, port, shouldLaunch) {
|
|
21
47
|
server.listen(port, () => {
|
|
22
48
|
console.log(chalk.green(`Proxy running on port ${port}\n`));
|
|
23
49
|
if (shouldLaunch) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type Chain = 'base' | 'solana';
|
|
2
|
+
export declare const BLOCKRUN_DIR: string;
|
|
3
|
+
export declare const CHAIN_FILE: string;
|
|
4
|
+
export declare const API_URLS: Record<Chain, string>;
|
|
2
5
|
export declare const DEFAULT_PROXY_PORT = 8402;
|
|
6
|
+
export declare function saveChain(chain: Chain): void;
|
|
7
|
+
export declare function loadChain(): Chain;
|
package/dist/config.js
CHANGED
|
@@ -1,2 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
export const BLOCKRUN_DIR = path.join(os.homedir(), '.blockrun');
|
|
5
|
+
export const CHAIN_FILE = path.join(BLOCKRUN_DIR, 'payment-chain');
|
|
6
|
+
export const API_URLS = {
|
|
7
|
+
base: 'https://blockrun.ai/api',
|
|
8
|
+
solana: 'https://sol.blockrun.ai/api',
|
|
9
|
+
};
|
|
2
10
|
export const DEFAULT_PROXY_PORT = 8402;
|
|
11
|
+
export function saveChain(chain) {
|
|
12
|
+
fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
|
|
13
|
+
fs.writeFileSync(CHAIN_FILE, chain + '\n', { mode: 0o600 });
|
|
14
|
+
}
|
|
15
|
+
export function loadChain() {
|
|
16
|
+
const envChain = process.env.BRCC_CHAIN;
|
|
17
|
+
if (envChain === 'solana')
|
|
18
|
+
return 'solana';
|
|
19
|
+
if (envChain === 'base')
|
|
20
|
+
return 'base';
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(CHAIN_FILE, 'utf-8').trim();
|
|
23
|
+
if (content === 'solana')
|
|
24
|
+
return 'solana';
|
|
25
|
+
return 'base';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return 'base';
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -9,9 +9,9 @@ program
|
|
|
9
9
|
.description('BlockRun Claude Code — run Claude Code with any model, pay with USDC')
|
|
10
10
|
.version('0.1.0');
|
|
11
11
|
program
|
|
12
|
-
.command('setup')
|
|
13
|
-
.description('Create a new wallet for payments')
|
|
14
|
-
.action(setupCommand);
|
|
12
|
+
.command('setup [chain]')
|
|
13
|
+
.description('Create a new wallet for payments (base or solana)')
|
|
14
|
+
.action((chain) => setupCommand(chain));
|
|
15
15
|
program
|
|
16
16
|
.command('start')
|
|
17
17
|
.description('Start proxy and launch Claude Code')
|
package/dist/proxy/server.d.ts
CHANGED
package/dist/proxy/server.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
-
import { getOrCreateWallet, createPaymentPayload, parsePaymentRequired, extractPaymentDetails, } from '@blockrun/llm';
|
|
2
|
+
import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
|
|
3
3
|
export function createProxy(options) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const chain = options.chain || 'base';
|
|
5
|
+
let baseWallet = null;
|
|
6
|
+
let solanaWallet = null;
|
|
7
|
+
if (chain === 'base') {
|
|
8
|
+
const w = getOrCreateWallet();
|
|
9
|
+
baseWallet = { privateKey: w.privateKey, address: w.address };
|
|
10
|
+
}
|
|
11
|
+
const initSolana = async () => {
|
|
12
|
+
if (chain === 'solana' && !solanaWallet) {
|
|
13
|
+
const w = await getOrCreateSolanaWallet();
|
|
14
|
+
solanaWallet = { privateKey: w.privateKey, address: w.address };
|
|
15
|
+
}
|
|
16
|
+
};
|
|
7
17
|
const server = http.createServer(async (req, res) => {
|
|
8
18
|
if (req.method === 'OPTIONS') {
|
|
9
19
|
res.writeHead(200);
|
|
10
20
|
res.end();
|
|
11
21
|
return;
|
|
12
22
|
}
|
|
23
|
+
await initSolana();
|
|
13
24
|
const path = req.url?.replace(/^\/api/, '') || '';
|
|
14
25
|
const targetUrl = `${options.apiUrl}${path}`;
|
|
15
26
|
let body = '';
|
|
@@ -34,7 +45,12 @@ export function createProxy(options) {
|
|
|
34
45
|
body: body || undefined,
|
|
35
46
|
});
|
|
36
47
|
if (response.status === 402) {
|
|
37
|
-
|
|
48
|
+
if (chain === 'solana' && solanaWallet) {
|
|
49
|
+
response = await handleSolanaPayment(response, targetUrl, req.method || 'POST', headers, body, solanaWallet.privateKey, solanaWallet.address);
|
|
50
|
+
}
|
|
51
|
+
else if (baseWallet) {
|
|
52
|
+
response = await handleBasePayment(response, targetUrl, req.method || 'POST', headers, body, baseWallet.privateKey, baseWallet.address);
|
|
53
|
+
}
|
|
38
54
|
}
|
|
39
55
|
const responseHeaders = {};
|
|
40
56
|
response.headers.forEach((v, k) => {
|
|
@@ -71,19 +87,11 @@ export function createProxy(options) {
|
|
|
71
87
|
});
|
|
72
88
|
return server;
|
|
73
89
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (respBody.x402 || respBody.accepts) {
|
|
80
|
-
paymentHeader = btoa(JSON.stringify(respBody));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
// ignore parse errors
|
|
85
|
-
}
|
|
86
|
-
}
|
|
90
|
+
// ======================================================================
|
|
91
|
+
// Base (EIP-712) payment handler
|
|
92
|
+
// ======================================================================
|
|
93
|
+
async function handleBasePayment(response, url, method, headers, body, privateKey, fromAddress) {
|
|
94
|
+
const paymentHeader = await extractPaymentHeader(response);
|
|
87
95
|
if (!paymentHeader) {
|
|
88
96
|
throw new Error('402 response but no payment requirements found');
|
|
89
97
|
}
|
|
@@ -104,3 +112,48 @@ async function handlePayment(response, url, method, headers, body, privateKey, f
|
|
|
104
112
|
body: body || undefined,
|
|
105
113
|
});
|
|
106
114
|
}
|
|
115
|
+
// ======================================================================
|
|
116
|
+
// Solana payment handler
|
|
117
|
+
// ======================================================================
|
|
118
|
+
async function handleSolanaPayment(response, url, method, headers, body, privateKey, fromAddress) {
|
|
119
|
+
const paymentHeader = await extractPaymentHeader(response);
|
|
120
|
+
if (!paymentHeader) {
|
|
121
|
+
throw new Error('402 response but no payment requirements found');
|
|
122
|
+
}
|
|
123
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
124
|
+
const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
|
|
125
|
+
const secretKey = await solanaKeyToBytes(privateKey);
|
|
126
|
+
const feePayer = details.extra?.feePayer || details.recipient;
|
|
127
|
+
const paymentPayload = await createSolanaPaymentPayload(secretKey, fromAddress, details.recipient, details.amount, feePayer, {
|
|
128
|
+
resourceUrl: details.resource?.url || url,
|
|
129
|
+
resourceDescription: details.resource?.description || 'BlockRun AI API call',
|
|
130
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
131
|
+
extra: details.extra,
|
|
132
|
+
});
|
|
133
|
+
return fetch(url, {
|
|
134
|
+
method,
|
|
135
|
+
headers: {
|
|
136
|
+
...headers,
|
|
137
|
+
'PAYMENT-SIGNATURE': paymentPayload,
|
|
138
|
+
},
|
|
139
|
+
body: body || undefined,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// ======================================================================
|
|
143
|
+
// Shared helpers
|
|
144
|
+
// ======================================================================
|
|
145
|
+
async function extractPaymentHeader(response) {
|
|
146
|
+
let paymentHeader = response.headers.get('payment-required');
|
|
147
|
+
if (!paymentHeader) {
|
|
148
|
+
try {
|
|
149
|
+
const respBody = (await response.json());
|
|
150
|
+
if (respBody.x402 || respBody.accepts) {
|
|
151
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// ignore parse errors
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return paymentHeader;
|
|
159
|
+
}
|
package/dist/wallet/manager.d.ts
CHANGED
package/dist/wallet/manager.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { getOrCreateWallet, scanWallets, getWalletAddress, } from '@blockrun/llm';
|
|
1
|
+
import { getOrCreateWallet, scanWallets, getWalletAddress, getOrCreateSolanaWallet, scanSolanaWallets, } from '@blockrun/llm';
|
|
2
|
+
import { loadChain } from '../config.js';
|
|
2
3
|
export function walletExists() {
|
|
3
|
-
const
|
|
4
|
-
|
|
4
|
+
const chain = loadChain();
|
|
5
|
+
if (chain === 'solana') {
|
|
6
|
+
return scanSolanaWallets().length > 0;
|
|
7
|
+
}
|
|
8
|
+
return scanWallets().length > 0;
|
|
5
9
|
}
|
|
6
10
|
export function setupWallet() {
|
|
7
11
|
const { address, isNew } = getOrCreateWallet();
|
|
8
12
|
return { address, isNew };
|
|
9
13
|
}
|
|
14
|
+
export async function setupSolanaWallet() {
|
|
15
|
+
const { address, isNew } = await getOrCreateSolanaWallet();
|
|
16
|
+
return { address, isNew };
|
|
17
|
+
}
|
|
10
18
|
export function getAddress() {
|
|
11
19
|
const addr = getWalletAddress();
|
|
12
20
|
if (!addr)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/cc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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": {
|
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@blockrun/llm": "^1.4.2",
|
|
46
|
+
"@solana/spl-token": "^0.4.14",
|
|
47
|
+
"@solana/web3.js": "^1.98.4",
|
|
48
|
+
"bs58": "^6.0.0",
|
|
46
49
|
"chalk": "^5.4.0",
|
|
47
50
|
"commander": "^13.0.0"
|
|
48
51
|
},
|