@ruvnet/open-claude-code 2.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.
- package/package.json +27 -0
- package/src/agents/loader.mjs +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/config/cli-args.mjs +139 -0
- package/src/config/env.mjs +261 -0
- package/src/config/settings.mjs +119 -0
- package/src/core/agent-loop.mjs +462 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/checkpoints.mjs +141 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session.mjs +143 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +220 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/tools/agent.mjs +127 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +127 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +114 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/ink-app.mjs +231 -0
- package/src/ui/repl.mjs +144 -0
- package/test/test.mjs +1857 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ink-like Terminal UI — rich terminal output without heavy dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Spinner during API calls
|
|
6
|
+
* - Color tool names in yellow, errors in red, thinking in dim
|
|
7
|
+
* - Code blocks with ``` detection and basic syntax coloring
|
|
8
|
+
* - Markdown bold, italic, inline code rendering
|
|
9
|
+
* - Status line showing model, tokens used, cost estimate
|
|
10
|
+
* - Progress for tool execution (show tool name + "running...")
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ANSI color codes
|
|
14
|
+
const COLORS = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
dim: '\x1b[2m',
|
|
18
|
+
italic: '\x1b[3m',
|
|
19
|
+
underline: '\x1b[4m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
yellow: '\x1b[33m',
|
|
23
|
+
blue: '\x1b[34m',
|
|
24
|
+
magenta: '\x1b[35m',
|
|
25
|
+
cyan: '\x1b[36m',
|
|
26
|
+
white: '\x1b[37m',
|
|
27
|
+
gray: '\x1b[90m',
|
|
28
|
+
bgRed: '\x1b[41m',
|
|
29
|
+
bgGreen: '\x1b[42m',
|
|
30
|
+
bgYellow: '\x1b[43m',
|
|
31
|
+
bgBlue: '\x1b[44m',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const noColor = process.env.NO_COLOR === '1';
|
|
35
|
+
|
|
36
|
+
function c(color, text) {
|
|
37
|
+
if (noColor) return text;
|
|
38
|
+
return `${COLORS[color] || ''}${text}${COLORS.reset}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Spinner for async operations.
|
|
43
|
+
*/
|
|
44
|
+
export class Spinner {
|
|
45
|
+
constructor(message = 'Working...') {
|
|
46
|
+
this.message = message;
|
|
47
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
48
|
+
this.frameIdx = 0;
|
|
49
|
+
this.timer = null;
|
|
50
|
+
this.active = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
start() {
|
|
54
|
+
if (noColor || !process.stderr.isTTY) return;
|
|
55
|
+
this.active = true;
|
|
56
|
+
this.timer = setInterval(() => {
|
|
57
|
+
const frame = this.frames[this.frameIdx % this.frames.length];
|
|
58
|
+
process.stderr.write(`\r${c('cyan', frame)} ${c('dim', this.message)}`);
|
|
59
|
+
this.frameIdx++;
|
|
60
|
+
}, 80);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
update(message) {
|
|
64
|
+
this.message = message;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
stop() {
|
|
68
|
+
if (this.timer) {
|
|
69
|
+
clearInterval(this.timer);
|
|
70
|
+
this.timer = null;
|
|
71
|
+
}
|
|
72
|
+
if (this.active) {
|
|
73
|
+
process.stderr.write('\r\x1b[K'); // Clear line
|
|
74
|
+
this.active = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Highlight code blocks in markdown text.
|
|
81
|
+
* @param {string} text
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
export function highlightCode(text) {
|
|
85
|
+
if (noColor) return text;
|
|
86
|
+
|
|
87
|
+
return text.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => {
|
|
88
|
+
const header = lang ? c('dim', ` ${lang}`) : '';
|
|
89
|
+
const highlighted = code
|
|
90
|
+
.split('\n')
|
|
91
|
+
.map(line => ` ${highlightLine(line, lang)}`)
|
|
92
|
+
.join('\n');
|
|
93
|
+
return `${c('dim', '```')}${header}\n${highlighted}\n${c('dim', '```')}`;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function highlightLine(line, lang) {
|
|
98
|
+
if (noColor) return line;
|
|
99
|
+
|
|
100
|
+
let result = line;
|
|
101
|
+
|
|
102
|
+
// Strings
|
|
103
|
+
result = result.replace(/(["'`])(.*?)\1/g, `${COLORS.green}$1$2$1${COLORS.reset}`);
|
|
104
|
+
|
|
105
|
+
// Keywords (common across languages)
|
|
106
|
+
const keywords = /\b(const|let|var|function|return|if|else|for|while|class|import|export|from|async|await|try|catch|throw|new|this|def|fn|pub|use|mod)\b/g;
|
|
107
|
+
result = result.replace(keywords, `${COLORS.magenta}$1${COLORS.reset}`);
|
|
108
|
+
|
|
109
|
+
// Numbers
|
|
110
|
+
result = result.replace(/\b(\d+\.?\d*)\b/g, `${COLORS.yellow}$1${COLORS.reset}`);
|
|
111
|
+
|
|
112
|
+
// Comments
|
|
113
|
+
result = result.replace(/(\/\/.*|#.*)$/, `${COLORS.gray}$1${COLORS.reset}`);
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Render markdown formatting: bold, italic, inline code.
|
|
120
|
+
* @param {string} text
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
export function renderMarkdown(text) {
|
|
124
|
+
if (noColor) return text;
|
|
125
|
+
let result = text;
|
|
126
|
+
|
|
127
|
+
// Bold: **text**
|
|
128
|
+
result = result.replace(/\*\*(.+?)\*\*/g, `${COLORS.bold}$1${COLORS.reset}`);
|
|
129
|
+
|
|
130
|
+
// Italic: *text*
|
|
131
|
+
result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, `${COLORS.italic}$1${COLORS.reset}`);
|
|
132
|
+
|
|
133
|
+
// Inline code: `text`
|
|
134
|
+
result = result.replace(/`([^`]+)`/g, `${COLORS.cyan}$1${COLORS.reset}`);
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Render a tool progress indicator.
|
|
141
|
+
* @param {string} toolName
|
|
142
|
+
* @param {string} status
|
|
143
|
+
*/
|
|
144
|
+
export function renderToolProgress(toolName, status) {
|
|
145
|
+
const icon = status === 'running' ? c('yellow', '>>') : c('green', '>>');
|
|
146
|
+
return `${icon} ${c('yellow', toolName)} ${c('dim', status)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Render a status bar with token counts and cost estimate.
|
|
151
|
+
* @param {object} state
|
|
152
|
+
*/
|
|
153
|
+
export function renderStatusBar(state) {
|
|
154
|
+
if (noColor || !process.stderr.isTTY) return '';
|
|
155
|
+
const cols = process.stdout.columns || 80;
|
|
156
|
+
const model = state.model || 'default';
|
|
157
|
+
const inp = state.tokenUsage?.input || 0;
|
|
158
|
+
const out = state.tokenUsage?.output || 0;
|
|
159
|
+
const tokens = `in:${inp} out:${out}`;
|
|
160
|
+
const turns = `turn:${state.turnCount || 0}`;
|
|
161
|
+
|
|
162
|
+
// Cost estimate (Sonnet pricing by default)
|
|
163
|
+
const costIn = (inp / 1_000_000) * 3;
|
|
164
|
+
const costOut = (out / 1_000_000) * 15;
|
|
165
|
+
const cost = `$${(costIn + costOut).toFixed(4)}`;
|
|
166
|
+
|
|
167
|
+
const right = `${model} | ${tokens} | ${cost} | ${turns}`;
|
|
168
|
+
const padding = Math.max(0, cols - right.length - 1);
|
|
169
|
+
return c('dim', `${' '.repeat(padding)}${right}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Render an error message.
|
|
174
|
+
* @param {string} message
|
|
175
|
+
*/
|
|
176
|
+
export function renderError(message) {
|
|
177
|
+
return `${c('red', 'Error:')} ${message}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Render a warning message.
|
|
182
|
+
* @param {string} message
|
|
183
|
+
*/
|
|
184
|
+
export function renderWarning(message) {
|
|
185
|
+
return `${c('yellow', 'Warning:')} ${message}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Render a success message.
|
|
190
|
+
* @param {string} message
|
|
191
|
+
*/
|
|
192
|
+
export function renderSuccess(message) {
|
|
193
|
+
return `${c('green', 'OK:')} ${message}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Render thinking output (dim).
|
|
198
|
+
* @param {string} text
|
|
199
|
+
*/
|
|
200
|
+
export function renderThinking(text) {
|
|
201
|
+
return c('dim', text);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Format a tool result for display.
|
|
206
|
+
* @param {string} toolName
|
|
207
|
+
* @param {string} result
|
|
208
|
+
* @param {boolean} [truncate=true]
|
|
209
|
+
*/
|
|
210
|
+
export function formatToolResult(toolName, result, truncate = true) {
|
|
211
|
+
const max = 500;
|
|
212
|
+
const display = truncate && result.length > max
|
|
213
|
+
? result.slice(0, max) + c('dim', `\n...[${result.length - max} more chars]`)
|
|
214
|
+
: result;
|
|
215
|
+
return `${c('cyan', `[${toolName}]`)} ${display}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Clear the screen.
|
|
220
|
+
*/
|
|
221
|
+
export function clearScreen() {
|
|
222
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Print a horizontal rule.
|
|
227
|
+
*/
|
|
228
|
+
export function hr() {
|
|
229
|
+
const cols = process.stdout.columns || 80;
|
|
230
|
+
return c('dim', '-'.repeat(cols));
|
|
231
|
+
}
|
package/src/ui/repl.mjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL — interactive read-eval-print loop for open-claude-code.
|
|
3
|
+
*
|
|
4
|
+
* Supports all 39 slash commands, skill invocation, enhanced UI,
|
|
5
|
+
* and streaming output with spinner and token display.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
import { executeCommand, getCompletions, COMMANDS } from './commands.mjs';
|
|
10
|
+
import { Spinner, highlightCode, renderStatusBar, renderToolProgress } from './ink-app.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Start the interactive REPL.
|
|
14
|
+
* @param {object} loop - agent loop instance (from createAgentLoop)
|
|
15
|
+
* @param {object} settings - loaded settings
|
|
16
|
+
*/
|
|
17
|
+
export async function startRepl(loop, settings) {
|
|
18
|
+
const rl = readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout,
|
|
21
|
+
terminal: true,
|
|
22
|
+
completer: (line) => {
|
|
23
|
+
if (line.startsWith('/')) {
|
|
24
|
+
const completions = getCompletions(line);
|
|
25
|
+
return [completions, line];
|
|
26
|
+
}
|
|
27
|
+
return [[], line];
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log('\x1b[1mopen-claude-code v2\x1b[0m — type your prompt or /help');
|
|
32
|
+
console.log('\x1b[2mModel: %s | Tools: %d\x1b[0m', loop.state.model || 'default', loop.state.tools?.list?.()?.length || 0);
|
|
33
|
+
console.log('');
|
|
34
|
+
|
|
35
|
+
const askPrompt = () => {
|
|
36
|
+
rl.question('\x1b[36m>\x1b[0m ', async (input) => {
|
|
37
|
+
const trimmed = input.trim();
|
|
38
|
+
if (!trimmed) { askPrompt(); return; }
|
|
39
|
+
|
|
40
|
+
// Handle slash commands
|
|
41
|
+
if (trimmed.startsWith('/')) {
|
|
42
|
+
// Check if it is a skill invocation
|
|
43
|
+
const cmdName = trimmed.split(/\s+/)[0].toLowerCase();
|
|
44
|
+
if (!COMMANDS[cmdName] && loop.state._skillsLoader) {
|
|
45
|
+
const skill = loop.state._skillsLoader.get(cmdName.slice(1));
|
|
46
|
+
if (skill) {
|
|
47
|
+
const args = trimmed.slice(cmdName.length).trim();
|
|
48
|
+
try {
|
|
49
|
+
for await (const event of loop.run(`[Skill: ${skill.name}]\n${skill.prompt}${args ? `\nArguments: ${args}` : ''}`)) {
|
|
50
|
+
renderEvent(event);
|
|
51
|
+
}
|
|
52
|
+
console.log('');
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(`\x1b[31mSkill error: ${err.message}\x1b[0m`);
|
|
55
|
+
}
|
|
56
|
+
askPrompt();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { response, exit } = executeCommand(trimmed, loop.state);
|
|
62
|
+
if (exit) { rl.close(); return; }
|
|
63
|
+
console.log(response);
|
|
64
|
+
askPrompt();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Run through agent loop
|
|
69
|
+
const spinner = new Spinner('Thinking...');
|
|
70
|
+
spinner.start();
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
let firstEvent = true;
|
|
74
|
+
for await (const event of loop.run(trimmed)) {
|
|
75
|
+
if (firstEvent) {
|
|
76
|
+
spinner.stop();
|
|
77
|
+
firstEvent = false;
|
|
78
|
+
}
|
|
79
|
+
renderEvent(event);
|
|
80
|
+
}
|
|
81
|
+
console.log('');
|
|
82
|
+
|
|
83
|
+
// Show status bar if enabled
|
|
84
|
+
if (settings.showTokenUsage !== false) {
|
|
85
|
+
process.stderr.write(renderStatusBar(loop.state) + '\n');
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
spinner.stop();
|
|
89
|
+
console.error(`\x1b[31mError: ${err.message}\x1b[0m`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
askPrompt();
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
askPrompt();
|
|
97
|
+
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
rl.on('close', resolve);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Render a single agent loop event to the terminal.
|
|
105
|
+
*/
|
|
106
|
+
function renderEvent(event) {
|
|
107
|
+
switch (event.type) {
|
|
108
|
+
case 'stream_event':
|
|
109
|
+
process.stdout.write(event.text || '');
|
|
110
|
+
break;
|
|
111
|
+
case 'thinking':
|
|
112
|
+
if (process.env.SHOW_THINKING) {
|
|
113
|
+
process.stdout.write(`\x1b[2m${event.text}\x1b[0m`);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
case 'tool_progress':
|
|
117
|
+
process.stderr.write(`${renderToolProgress(event.tool, event.status || 'running')}\n`);
|
|
118
|
+
break;
|
|
119
|
+
case 'result':
|
|
120
|
+
if (process.env.SHOW_TOOL_RESULTS) {
|
|
121
|
+
const display = String(event.result).slice(0, 300);
|
|
122
|
+
console.log(`\x1b[36m[${event.tool}]\x1b[0m ${display}`);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case 'assistant':
|
|
126
|
+
if (event.content) {
|
|
127
|
+
process.stdout.write(highlightCode(event.content));
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case 'compaction':
|
|
131
|
+
process.stderr.write(`\x1b[2m[compaction #${event.count}]\x1b[0m\n`);
|
|
132
|
+
break;
|
|
133
|
+
case 'hookPermissionResult':
|
|
134
|
+
if (!event.allowed) {
|
|
135
|
+
process.stderr.write(`\x1b[31m[blocked: ${event.tool}]\x1b[0m\n`);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'stop':
|
|
139
|
+
break;
|
|
140
|
+
case 'error':
|
|
141
|
+
console.error(`\x1b[31mError: ${event.message}\x1b[0m`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|