@blockrun/franklin 3.0.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.
Files changed (138) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +256 -0
  3. package/dist/agent/commands.d.ts +27 -0
  4. package/dist/agent/commands.js +659 -0
  5. package/dist/agent/compact.d.ts +31 -0
  6. package/dist/agent/compact.js +366 -0
  7. package/dist/agent/context.d.ts +11 -0
  8. package/dist/agent/context.js +184 -0
  9. package/dist/agent/error-classifier.d.ts +10 -0
  10. package/dist/agent/error-classifier.js +61 -0
  11. package/dist/agent/llm.d.ts +63 -0
  12. package/dist/agent/llm.js +448 -0
  13. package/dist/agent/loop.d.ts +12 -0
  14. package/dist/agent/loop.js +346 -0
  15. package/dist/agent/optimize.d.ts +53 -0
  16. package/dist/agent/optimize.js +262 -0
  17. package/dist/agent/permissions.d.ts +39 -0
  18. package/dist/agent/permissions.js +226 -0
  19. package/dist/agent/reduce.d.ts +49 -0
  20. package/dist/agent/reduce.js +317 -0
  21. package/dist/agent/streaming-executor.d.ts +36 -0
  22. package/dist/agent/streaming-executor.js +149 -0
  23. package/dist/agent/tokens.d.ts +53 -0
  24. package/dist/agent/tokens.js +185 -0
  25. package/dist/agent/types.d.ts +125 -0
  26. package/dist/agent/types.js +5 -0
  27. package/dist/banner.d.ts +1 -0
  28. package/dist/banner.js +27 -0
  29. package/dist/commands/balance.d.ts +1 -0
  30. package/dist/commands/balance.js +40 -0
  31. package/dist/commands/config.d.ts +14 -0
  32. package/dist/commands/config.js +107 -0
  33. package/dist/commands/daemon.d.ts +3 -0
  34. package/dist/commands/daemon.js +117 -0
  35. package/dist/commands/history.d.ts +5 -0
  36. package/dist/commands/history.js +31 -0
  37. package/dist/commands/init.d.ts +3 -0
  38. package/dist/commands/init.js +92 -0
  39. package/dist/commands/logs.d.ts +5 -0
  40. package/dist/commands/logs.js +89 -0
  41. package/dist/commands/models.d.ts +1 -0
  42. package/dist/commands/models.js +56 -0
  43. package/dist/commands/plugin.d.ts +14 -0
  44. package/dist/commands/plugin.js +176 -0
  45. package/dist/commands/proxy.d.ts +13 -0
  46. package/dist/commands/proxy.js +106 -0
  47. package/dist/commands/setup.d.ts +1 -0
  48. package/dist/commands/setup.js +49 -0
  49. package/dist/commands/start.d.ts +8 -0
  50. package/dist/commands/start.js +292 -0
  51. package/dist/commands/stats.d.ts +10 -0
  52. package/dist/commands/stats.js +94 -0
  53. package/dist/commands/uninit.d.ts +1 -0
  54. package/dist/commands/uninit.js +63 -0
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +41 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +179 -0
  59. package/dist/mcp/client.d.ts +44 -0
  60. package/dist/mcp/client.js +147 -0
  61. package/dist/mcp/config.d.ts +20 -0
  62. package/dist/mcp/config.js +138 -0
  63. package/dist/plugin-sdk/channel.d.ts +100 -0
  64. package/dist/plugin-sdk/channel.js +10 -0
  65. package/dist/plugin-sdk/index.d.ts +14 -0
  66. package/dist/plugin-sdk/index.js +9 -0
  67. package/dist/plugin-sdk/plugin.d.ts +87 -0
  68. package/dist/plugin-sdk/plugin.js +7 -0
  69. package/dist/plugin-sdk/search.d.ts +13 -0
  70. package/dist/plugin-sdk/search.js +4 -0
  71. package/dist/plugin-sdk/tracker.d.ts +27 -0
  72. package/dist/plugin-sdk/tracker.js +5 -0
  73. package/dist/plugin-sdk/workflow.d.ts +126 -0
  74. package/dist/plugin-sdk/workflow.js +11 -0
  75. package/dist/plugins/registry.d.ts +33 -0
  76. package/dist/plugins/registry.js +155 -0
  77. package/dist/plugins/runner.d.ts +21 -0
  78. package/dist/plugins/runner.js +453 -0
  79. package/dist/plugins-bundled/social/index.d.ts +10 -0
  80. package/dist/plugins-bundled/social/index.js +363 -0
  81. package/dist/plugins-bundled/social/plugin.json +14 -0
  82. package/dist/plugins-bundled/social/prompts.d.ts +19 -0
  83. package/dist/plugins-bundled/social/prompts.js +67 -0
  84. package/dist/plugins-bundled/social/types.d.ts +58 -0
  85. package/dist/plugins-bundled/social/types.js +16 -0
  86. package/dist/pricing.d.ts +21 -0
  87. package/dist/pricing.js +91 -0
  88. package/dist/proxy/fallback.d.ts +38 -0
  89. package/dist/proxy/fallback.js +144 -0
  90. package/dist/proxy/server.d.ts +18 -0
  91. package/dist/proxy/server.js +576 -0
  92. package/dist/proxy/sse-translator.d.ts +29 -0
  93. package/dist/proxy/sse-translator.js +270 -0
  94. package/dist/router/index.d.ts +22 -0
  95. package/dist/router/index.js +269 -0
  96. package/dist/session/search.d.ts +33 -0
  97. package/dist/session/search.js +229 -0
  98. package/dist/session/storage.d.ts +48 -0
  99. package/dist/session/storage.js +173 -0
  100. package/dist/stats/insights.d.ts +55 -0
  101. package/dist/stats/insights.js +195 -0
  102. package/dist/stats/tracker.d.ts +54 -0
  103. package/dist/stats/tracker.js +165 -0
  104. package/dist/tools/askuser.d.ts +6 -0
  105. package/dist/tools/askuser.js +76 -0
  106. package/dist/tools/bash.d.ts +5 -0
  107. package/dist/tools/bash.js +336 -0
  108. package/dist/tools/edit.d.ts +5 -0
  109. package/dist/tools/edit.js +148 -0
  110. package/dist/tools/glob.d.ts +5 -0
  111. package/dist/tools/glob.js +158 -0
  112. package/dist/tools/grep.d.ts +5 -0
  113. package/dist/tools/grep.js +194 -0
  114. package/dist/tools/imagegen.d.ts +6 -0
  115. package/dist/tools/imagegen.js +172 -0
  116. package/dist/tools/index.d.ts +17 -0
  117. package/dist/tools/index.js +30 -0
  118. package/dist/tools/read.d.ts +11 -0
  119. package/dist/tools/read.js +90 -0
  120. package/dist/tools/subagent.d.ts +5 -0
  121. package/dist/tools/subagent.js +116 -0
  122. package/dist/tools/task.d.ts +5 -0
  123. package/dist/tools/task.js +91 -0
  124. package/dist/tools/webfetch.d.ts +5 -0
  125. package/dist/tools/webfetch.js +166 -0
  126. package/dist/tools/websearch.d.ts +5 -0
  127. package/dist/tools/websearch.js +103 -0
  128. package/dist/tools/write.d.ts +5 -0
  129. package/dist/tools/write.js +114 -0
  130. package/dist/ui/app.d.ts +26 -0
  131. package/dist/ui/app.js +545 -0
  132. package/dist/ui/model-picker.d.ts +14 -0
  133. package/dist/ui/model-picker.js +161 -0
  134. package/dist/ui/terminal.d.ts +35 -0
  135. package/dist/ui/terminal.js +337 -0
  136. package/dist/wallet/manager.d.ts +10 -0
  137. package/dist/wallet/manager.js +23 -0
  138. package/package.json +79 -0
@@ -0,0 +1,107 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { BLOCKRUN_DIR } from '../config.js';
5
+ const CONFIG_FILE = path.join(BLOCKRUN_DIR, 'runcode-config.json');
6
+ const VALID_KEYS = [
7
+ 'default-model',
8
+ 'sonnet-model',
9
+ 'opus-model',
10
+ 'haiku-model',
11
+ 'smart-routing',
12
+ 'permission-mode',
13
+ 'max-turns',
14
+ 'auto-compact',
15
+ 'session-save',
16
+ 'debug',
17
+ ];
18
+ export function loadConfig() {
19
+ try {
20
+ const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
21
+ return JSON.parse(content);
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ function saveConfig(config) {
28
+ try {
29
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
30
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', {
31
+ mode: 0o600,
32
+ });
33
+ }
34
+ catch (err) {
35
+ console.error(chalk.red(`Failed to save config: ${err.message}`));
36
+ }
37
+ }
38
+ function isValidKey(key) {
39
+ return VALID_KEYS.includes(key);
40
+ }
41
+ export function configCommand(action, keyOrUndefined, value) {
42
+ if (action === 'list') {
43
+ const config = loadConfig();
44
+ const entries = Object.entries(config);
45
+ if (entries.length === 0) {
46
+ console.log(chalk.dim('No config set. Defaults will be used.'));
47
+ console.log(chalk.dim(`\nConfig file: ${CONFIG_FILE}`));
48
+ return;
49
+ }
50
+ console.log(chalk.bold('runcode config\n'));
51
+ for (const [k, v] of entries) {
52
+ console.log(` ${chalk.cyan(k)} = ${chalk.green(v)}`);
53
+ }
54
+ console.log(chalk.dim(`\nConfig file: ${CONFIG_FILE}`));
55
+ return;
56
+ }
57
+ if (action === 'get') {
58
+ if (!keyOrUndefined) {
59
+ console.log(chalk.red('Usage: runcode config get <key>'));
60
+ process.exit(1);
61
+ }
62
+ const config = loadConfig();
63
+ const val = config[keyOrUndefined];
64
+ if (val !== undefined) {
65
+ console.log(val);
66
+ }
67
+ else {
68
+ console.log(chalk.dim('(not set)'));
69
+ }
70
+ return;
71
+ }
72
+ if (action === 'set') {
73
+ if (!keyOrUndefined || value === undefined) {
74
+ console.log(chalk.red('Usage: runcode config set <key> <value>'));
75
+ process.exit(1);
76
+ }
77
+ if (!isValidKey(keyOrUndefined)) {
78
+ console.log(chalk.red(`Unknown config key: ${keyOrUndefined}`));
79
+ console.log(`Valid keys: ${VALID_KEYS.map((k) => chalk.cyan(k)).join(', ')}`);
80
+ process.exit(1);
81
+ }
82
+ const config = loadConfig();
83
+ config[keyOrUndefined] = value;
84
+ saveConfig(config);
85
+ console.log(`${chalk.cyan(keyOrUndefined)} = ${chalk.green(value)}`);
86
+ return;
87
+ }
88
+ if (action === 'unset') {
89
+ if (!keyOrUndefined) {
90
+ console.log(chalk.red('Usage: runcode config unset <key>'));
91
+ process.exit(1);
92
+ }
93
+ if (!isValidKey(keyOrUndefined)) {
94
+ console.log(chalk.red(`Unknown config key: ${keyOrUndefined}`));
95
+ console.log(`Valid keys: ${VALID_KEYS.map((k) => chalk.cyan(k)).join(', ')}`);
96
+ process.exit(1);
97
+ }
98
+ const config = loadConfig();
99
+ delete config[keyOrUndefined];
100
+ saveConfig(config);
101
+ console.log(chalk.dim(`Unset ${keyOrUndefined}`));
102
+ return;
103
+ }
104
+ console.log(chalk.red(`Unknown action: ${action}`));
105
+ console.log('Usage: runcode config <set|get|unset|list> [key] [value]');
106
+ process.exit(1);
107
+ }
@@ -0,0 +1,3 @@
1
+ export declare function daemonCommand(action: string, options: {
2
+ port?: string;
3
+ }): Promise<void>;
@@ -0,0 +1,117 @@
1
+ import { spawn, execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import chalk from 'chalk';
5
+ import { BLOCKRUN_DIR, DEFAULT_PROXY_PORT } from '../config.js';
6
+ const PID_FILE = path.join(BLOCKRUN_DIR, 'runcode.pid');
7
+ const LOG_FILE = path.join(BLOCKRUN_DIR, 'runcode-debug.log');
8
+ function readPid() {
9
+ try {
10
+ const raw = fs.readFileSync(PID_FILE, 'utf-8').trim();
11
+ const pid = parseInt(raw, 10);
12
+ return isNaN(pid) ? null : pid;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ function isRunning(pid) {
19
+ try {
20
+ process.kill(pid, 0);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ export async function daemonCommand(action, options) {
28
+ const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
29
+ if (isNaN(port) || port < 1 || port > 65535) {
30
+ console.log(chalk.red(`Invalid port "${options.port}". Must be 1-65535. Default: ${DEFAULT_PROXY_PORT}`));
31
+ return;
32
+ }
33
+ switch (action) {
34
+ case 'start': {
35
+ const existing = readPid();
36
+ if (existing && isRunning(existing)) {
37
+ console.log(chalk.yellow(`runcode daemon already running (PID ${existing})`));
38
+ console.log(chalk.dim(` Proxy: http://localhost:${port}/api`));
39
+ return;
40
+ }
41
+ // Find runcode binary
42
+ let runcodeBin;
43
+ try {
44
+ runcodeBin = execSync('which runcode', { encoding: 'utf-8' }).trim();
45
+ }
46
+ catch {
47
+ console.log(chalk.red('runcode binary not found in PATH.'));
48
+ return;
49
+ }
50
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
51
+ const child = spawn(runcodeBin, ['proxy', '--port', String(port)], {
52
+ detached: true,
53
+ stdio: ['ignore', fs.openSync(LOG_FILE, 'a'), fs.openSync(LOG_FILE, 'a')],
54
+ });
55
+ child.unref();
56
+ fs.writeFileSync(PID_FILE, String(child.pid));
57
+ console.log(chalk.green(`✓ runcode daemon started (PID ${child.pid})`));
58
+ console.log(chalk.dim(` Proxy: http://localhost:${port}/api`));
59
+ console.log(chalk.dim(` Logs: ${LOG_FILE}`));
60
+ break;
61
+ }
62
+ case 'stop': {
63
+ const pid = readPid();
64
+ if (!pid) {
65
+ console.log(chalk.yellow('No runcode daemon found.'));
66
+ return;
67
+ }
68
+ if (!isRunning(pid)) {
69
+ fs.unlinkSync(PID_FILE);
70
+ console.log(chalk.yellow(`Daemon PID ${pid} not running — cleaned up.`));
71
+ return;
72
+ }
73
+ try {
74
+ process.kill(pid, 'SIGTERM');
75
+ // Wait for process to exit (up to 5s)
76
+ for (let i = 0; i < 50; i++) {
77
+ if (!isRunning(pid))
78
+ break;
79
+ await new Promise(r => setTimeout(r, 100));
80
+ }
81
+ if (isRunning(pid)) {
82
+ process.kill(pid, 'SIGKILL');
83
+ }
84
+ try {
85
+ fs.unlinkSync(PID_FILE);
86
+ }
87
+ catch { /* already gone */ }
88
+ console.log(chalk.green(`✓ runcode daemon stopped (PID ${pid})`));
89
+ }
90
+ catch (e) {
91
+ console.log(chalk.red(`Failed to stop daemon: ${e.message}`));
92
+ }
93
+ break;
94
+ }
95
+ case 'status': {
96
+ const pid = readPid();
97
+ if (!pid) {
98
+ console.log(chalk.dim('runcode daemon: not running'));
99
+ return;
100
+ }
101
+ if (isRunning(pid)) {
102
+ console.log(chalk.green(`✓ runcode daemon running`));
103
+ console.log(` PID: ${chalk.bold(pid)}`);
104
+ console.log(` Proxy: ${chalk.cyan(`http://localhost:${port}/api`)}`);
105
+ console.log(chalk.dim(` Logs: ${LOG_FILE}`));
106
+ }
107
+ else {
108
+ fs.unlinkSync(PID_FILE);
109
+ console.log(chalk.yellow('runcode daemon: not running (stale PID cleaned up)'));
110
+ }
111
+ break;
112
+ }
113
+ default:
114
+ console.log(chalk.red(`Unknown daemon action: ${action}`));
115
+ console.log('Usage: runcode daemon <start|stop|status>');
116
+ }
117
+ }
@@ -0,0 +1,5 @@
1
+ interface HistoryOptions {
2
+ n?: string;
3
+ }
4
+ export declare function historyCommand(options: HistoryOptions): void;
5
+ export {};
@@ -0,0 +1,31 @@
1
+ import chalk from 'chalk';
2
+ import { loadStats } from '../stats/tracker.js';
3
+ export function historyCommand(options) {
4
+ const { history } = loadStats();
5
+ const limit = Math.min(parseInt(options.n || '20', 10), history.length);
6
+ console.log(chalk.bold(`
7
+ 📜 Last ${limit} Requests\n`));
8
+ console.log('─'.repeat(55));
9
+ if (history.length === 0) {
10
+ console.log(chalk.gray('\n No history recorded yet.\n'));
11
+ console.log('─'.repeat(55) + '\n');
12
+ return;
13
+ }
14
+ const recent = history.slice(-limit).reverse();
15
+ for (const record of recent) {
16
+ const time = new Date(record.timestamp).toLocaleString();
17
+ const model = record.model.split('/').pop() || record.model;
18
+ const cost = '$' + record.costUsd.toFixed(5);
19
+ const tokens = `${record.inputTokens}+${record.outputTokens}`.padEnd(10);
20
+ const latency = `${record.latencyMs}ms`.padEnd(8);
21
+ const fallbackMark = record.fallback ? chalk.yellow(' ↺') : '';
22
+ console.log(chalk.gray(`[${time}]`) +
23
+ ` ${model.padEnd(20)}${fallbackMark} ` +
24
+ chalk.cyan(tokens) +
25
+ chalk.magenta(latency) +
26
+ chalk.green(cost));
27
+ }
28
+ console.log('\n' + '─'.repeat(55));
29
+ console.log(chalk.gray(` Showing ${limit} of ${history.length} total records.`));
30
+ console.log(chalk.gray(' Run `runcode stats` for more detailed statistics.\n'));
31
+ }
@@ -0,0 +1,3 @@
1
+ export declare function initCommand(options: {
2
+ port?: string;
3
+ }): Promise<void>;
@@ -0,0 +1,92 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import chalk from 'chalk';
5
+ import { DEFAULT_PROXY_PORT } from '../config.js';
6
+ const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json');
7
+ const LAUNCH_AGENT_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents');
8
+ const LAUNCH_AGENT_PLIST = path.join(LAUNCH_AGENT_DIR, 'ai.blockrun.runcode.plist');
9
+ export async function initCommand(options) {
10
+ const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
11
+ if (isNaN(port) || port < 1 || port > 65535) {
12
+ console.error(chalk.red(`Error: invalid port "${options.port}". Must be 1-65535. Default: ${DEFAULT_PROXY_PORT}`));
13
+ process.exit(1);
14
+ }
15
+ // ── 1. Write ~/.claude/settings.json ────────────────────────────────────
16
+ let settings = {};
17
+ try {
18
+ if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
19
+ settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
20
+ }
21
+ }
22
+ catch {
23
+ console.log(chalk.yellow(` Warning: could not parse ${CLAUDE_SETTINGS_FILE}, starting fresh.`));
24
+ }
25
+ settings.env = {
26
+ ...(settings.env ?? {}),
27
+ ANTHROPIC_BASE_URL: `http://localhost:${port}/api`,
28
+ ANTHROPIC_AUTH_TOKEN: 'x402-proxy-handles-auth',
29
+ ANTHROPIC_MODEL: 'blockrun/auto',
30
+ ANTHROPIC_DEFAULT_SONNET_MODEL: 'anthropic/claude-sonnet-4.6',
31
+ ANTHROPIC_DEFAULT_OPUS_MODEL: 'anthropic/claude-opus-4.6',
32
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'anthropic/claude-haiku-4.5-20251001',
33
+ };
34
+ fs.mkdirSync(path.dirname(CLAUDE_SETTINGS_FILE), { recursive: true });
35
+ fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
36
+ console.log(chalk.green(`✓ Configured ${CLAUDE_SETTINGS_FILE}`));
37
+ // ── 2. Install macOS LaunchAgent (auto-start on login) ─────────────────
38
+ if (process.platform === 'darwin') {
39
+ let runcodeBin = '';
40
+ try {
41
+ const { execSync } = await import('node:child_process');
42
+ runcodeBin = execSync('which runcode', { encoding: 'utf-8' }).trim();
43
+ }
44
+ catch {
45
+ console.log(chalk.yellow(' Warning: runcode not found in PATH — LaunchAgent not installed.'));
46
+ }
47
+ if (runcodeBin) {
48
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
49
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
50
+ <plist version="1.0">
51
+ <dict>
52
+ <key>Label</key>
53
+ <string>ai.blockrun.runcode</string>
54
+ <key>ProgramArguments</key>
55
+ <array>
56
+ <string>${runcodeBin}</string>
57
+ <string>proxy</string>
58
+ <string>--port</string>
59
+ <string>${port}</string>
60
+ </array>
61
+ <key>RunAtLoad</key>
62
+ <true/>
63
+ <key>KeepAlive</key>
64
+ <false/>
65
+ <key>StandardOutPath</key>
66
+ <string>${os.homedir()}/.blockrun/runcode-debug.log</string>
67
+ <key>StandardErrorPath</key>
68
+ <string>${os.homedir()}/.blockrun/runcode-debug.log</string>
69
+ </dict>
70
+ </plist>`;
71
+ fs.mkdirSync(LAUNCH_AGENT_DIR, { recursive: true });
72
+ fs.writeFileSync(LAUNCH_AGENT_PLIST, plist);
73
+ try {
74
+ const { execSync } = await import('node:child_process');
75
+ execSync(`launchctl load -w "${LAUNCH_AGENT_PLIST}"`, { stdio: 'pipe' });
76
+ console.log(chalk.green(`✓ LaunchAgent installed — runcode proxy starts automatically on login`));
77
+ }
78
+ catch {
79
+ console.log(chalk.dim(` LaunchAgent written to ${LAUNCH_AGENT_PLIST}`));
80
+ console.log(chalk.dim(` Load manually: launchctl load -w "${LAUNCH_AGENT_PLIST}"`));
81
+ }
82
+ }
83
+ }
84
+ // ── 3. Start daemon now ──────────────────────────────────────────────────
85
+ console.log('');
86
+ console.log(chalk.bold('runcode initialized (proxy mode for Claude Code).'));
87
+ console.log(`Run ${chalk.bold('runcode daemon start')} to start the background proxy now.`);
88
+ console.log(`Then just run ${chalk.bold('claude')} — runcode proxy handles payments automatically.`);
89
+ console.log('');
90
+ console.log(chalk.dim('Or use runcode directly: runcode start'));
91
+ console.log(chalk.dim('Note: Claude Code will ask you to trust the proxy URL once.'));
92
+ }
@@ -0,0 +1,5 @@
1
+ export declare function logsCommand(options: {
2
+ follow?: boolean;
3
+ lines?: string;
4
+ clear?: boolean;
5
+ }): void;
@@ -0,0 +1,89 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { BLOCKRUN_DIR } from '../config.js';
5
+ const LOG_FILE = path.join(BLOCKRUN_DIR, 'runcode-debug.log');
6
+ const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB auto-rotate threshold
7
+ export function logsCommand(options) {
8
+ if (options.clear) {
9
+ try {
10
+ fs.unlinkSync(LOG_FILE);
11
+ console.log(chalk.green('Logs cleared.'));
12
+ }
13
+ catch {
14
+ console.log(chalk.dim('No log file to clear.'));
15
+ }
16
+ return;
17
+ }
18
+ if (!fs.existsSync(LOG_FILE)) {
19
+ console.log(chalk.dim('No logs yet. Start runcode with --debug to enable logging:'));
20
+ console.log(chalk.bold(' runcode start --debug'));
21
+ return;
22
+ }
23
+ // Auto-rotate: if file is over threshold, keep only last half
24
+ try {
25
+ const stat = fs.statSync(LOG_FILE);
26
+ if (stat.size > MAX_LOG_SIZE) {
27
+ const content = fs.readFileSync(LOG_FILE, 'utf-8');
28
+ const lines = content.split('\n');
29
+ const half = lines.slice(Math.floor(lines.length / 2));
30
+ fs.writeFileSync(LOG_FILE, half.join('\n'));
31
+ console.log(chalk.dim(`(Rotated log — was ${(stat.size / 1024 / 1024).toFixed(1)}MB)`));
32
+ }
33
+ }
34
+ catch { /* ignore rotation errors */ }
35
+ const parsed = parseInt(options.lines || '50', 10);
36
+ const tailLines = isNaN(parsed) ? 50 : Math.max(1, Math.min(10000, parsed));
37
+ if (options.follow) {
38
+ // Tail -f mode: print last N lines then watch for changes
39
+ printLastLines(tailLines);
40
+ console.log(chalk.dim('--- watching for new entries (ctrl+c to stop) ---'));
41
+ let lastSize = fs.statSync(LOG_FILE).size;
42
+ const watcher = setInterval(() => {
43
+ try {
44
+ const stat = fs.statSync(LOG_FILE);
45
+ if (stat.size > lastSize) {
46
+ const fd = fs.openSync(LOG_FILE, 'r');
47
+ const buf = Buffer.alloc(stat.size - lastSize);
48
+ fs.readSync(fd, buf, 0, buf.length, lastSize);
49
+ fs.closeSync(fd);
50
+ process.stdout.write(buf.toString('utf-8'));
51
+ lastSize = stat.size;
52
+ }
53
+ else if (stat.size < lastSize) {
54
+ // File was rotated/cleared
55
+ lastSize = 0;
56
+ }
57
+ }
58
+ catch {
59
+ /* file may have been deleted */
60
+ }
61
+ }, 500);
62
+ process.on('SIGINT', () => {
63
+ clearInterval(watcher);
64
+ process.exit(0);
65
+ });
66
+ }
67
+ else {
68
+ printLastLines(tailLines);
69
+ }
70
+ }
71
+ function printLastLines(n) {
72
+ try {
73
+ const content = fs.readFileSync(LOG_FILE, 'utf-8');
74
+ const lines = content.split('\n').filter(Boolean);
75
+ const start = Math.max(0, lines.length - n);
76
+ const slice = lines.slice(start);
77
+ if (start > 0) {
78
+ console.log(chalk.dim(`... (${start} earlier entries, use --lines to see more)`));
79
+ }
80
+ for (const line of slice) {
81
+ // Colorize timestamps
82
+ const colored = line.replace(/^\[(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\]/, chalk.dim('[$1]'));
83
+ console.log(colored);
84
+ }
85
+ }
86
+ catch {
87
+ console.log(chalk.dim('Could not read log file.'));
88
+ }
89
+ }
@@ -0,0 +1 @@
1
+ export declare function modelsCommand(): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import chalk from 'chalk';
2
+ import { loadChain, API_URLS } from '../config.js';
3
+ export async function modelsCommand() {
4
+ const chain = loadChain();
5
+ const apiUrl = API_URLS[chain];
6
+ console.log(chalk.bold('Available Models\n'));
7
+ console.log(`Chain: ${chalk.magenta(chain)} — ${chalk.dim(apiUrl)}\n`);
8
+ try {
9
+ const controller = new AbortController();
10
+ const timeout = setTimeout(() => controller.abort(), 15_000);
11
+ const response = await fetch(`${apiUrl}/v1/models`, { signal: controller.signal });
12
+ clearTimeout(timeout);
13
+ if (!response.ok) {
14
+ console.log(chalk.red(`Failed to fetch models: ${response.status}`));
15
+ return;
16
+ }
17
+ const data = (await response.json());
18
+ if (!data.data || data.data.length === 0) {
19
+ console.log(chalk.yellow('No models returned from API.'));
20
+ return;
21
+ }
22
+ const models = data.data
23
+ .sort((a, b) => (a.pricing?.input ?? 0) - (b.pricing?.input ?? 0));
24
+ const free = models.filter((m) => m.billing_mode === 'free');
25
+ const paid = models.filter((m) => m.billing_mode !== 'free');
26
+ if (free.length > 0) {
27
+ console.log(chalk.green.bold('Free Models (no USDC needed)'));
28
+ console.log(chalk.dim('─'.repeat(70)));
29
+ for (const m of free) {
30
+ console.log(` ${chalk.cyan(m.id)}`);
31
+ }
32
+ console.log('');
33
+ }
34
+ console.log(chalk.yellow.bold('Paid Models'));
35
+ console.log(chalk.dim('─'.repeat(70)));
36
+ console.log(chalk.dim(` ${'Model'.padEnd(35)} ${'Input'.padEnd(12)} ${'Output'.padEnd(12)} Context`));
37
+ console.log(chalk.dim('─'.repeat(70)));
38
+ for (const m of paid) {
39
+ const input = `$${(m.pricing?.input ?? 0).toFixed(2)}/M`;
40
+ const output = `$${(m.pricing?.output ?? 0).toFixed(2)}/M`;
41
+ const ctx = '';
42
+ console.log(` ${chalk.cyan(m.id.padEnd(35))} ${input.padEnd(12)} ${output.padEnd(12)} ${ctx}`);
43
+ }
44
+ console.log(`\n${chalk.dim(`${models.length} models available. Use:`)} ${chalk.bold('runcode start --model <model-id>')}`);
45
+ }
46
+ catch (err) {
47
+ const msg = err instanceof Error ? err.message : 'unknown error';
48
+ if (msg.includes('fetch') || msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
49
+ console.log(chalk.red(`Cannot reach BlockRun API at ${apiUrl}`));
50
+ console.log(chalk.dim('Check your internet connection or try again later.'));
51
+ }
52
+ else {
53
+ console.log(chalk.red(`Error: ${msg}`));
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Generic plugin command dispatcher.
3
+ *
4
+ * `runcode <plugin-id> <action>` works for ANY plugin that registers a workflow.
5
+ * Core stays plugin-agnostic — adding a new plugin requires zero changes here.
6
+ */
7
+ export interface PluginCommandOptions {
8
+ dryRun?: boolean;
9
+ debug?: boolean;
10
+ }
11
+ /** Run a plugin command. Plugin id is the first arg. */
12
+ export declare function pluginCommand(pluginId: string, action: string | undefined, options: PluginCommandOptions): Promise<void>;
13
+ /** List all installed plugins */
14
+ export declare function listAvailablePlugins(): void;