@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.
Files changed (63) hide show
  1. package/package.json +27 -0
  2. package/src/agents/loader.mjs +89 -0
  3. package/src/agents/parser.mjs +98 -0
  4. package/src/agents/teams.mjs +123 -0
  5. package/src/auth/oauth.mjs +220 -0
  6. package/src/config/cli-args.mjs +139 -0
  7. package/src/config/env.mjs +261 -0
  8. package/src/config/settings.mjs +119 -0
  9. package/src/core/agent-loop.mjs +462 -0
  10. package/src/core/cache.mjs +105 -0
  11. package/src/core/checkpoints.mjs +141 -0
  12. package/src/core/context-manager.mjs +198 -0
  13. package/src/core/providers.mjs +219 -0
  14. package/src/core/rate-limiter.mjs +119 -0
  15. package/src/core/scheduler.mjs +173 -0
  16. package/src/core/session.mjs +143 -0
  17. package/src/core/streaming.mjs +182 -0
  18. package/src/core/system-prompt.mjs +135 -0
  19. package/src/hooks/engine.mjs +162 -0
  20. package/src/index.mjs +220 -0
  21. package/src/mcp/client.mjs +253 -0
  22. package/src/mcp/transport-shttp.mjs +130 -0
  23. package/src/mcp/transport-sse.mjs +131 -0
  24. package/src/mcp/transport-ws.mjs +134 -0
  25. package/src/permissions/checker.mjs +57 -0
  26. package/src/permissions/injection-check.mjs +60 -0
  27. package/src/permissions/path-check.mjs +102 -0
  28. package/src/permissions/prompt.mjs +73 -0
  29. package/src/permissions/sandbox.mjs +112 -0
  30. package/src/plugins/loader.mjs +138 -0
  31. package/src/skills/loader.mjs +147 -0
  32. package/src/skills/runner.mjs +55 -0
  33. package/src/telemetry/index.mjs +96 -0
  34. package/src/tools/agent.mjs +127 -0
  35. package/src/tools/ask-user.mjs +61 -0
  36. package/src/tools/bash.mjs +148 -0
  37. package/src/tools/cron-create.mjs +120 -0
  38. package/src/tools/cron-delete.mjs +49 -0
  39. package/src/tools/cron-list.mjs +37 -0
  40. package/src/tools/edit.mjs +82 -0
  41. package/src/tools/enter-worktree.mjs +69 -0
  42. package/src/tools/exit-worktree.mjs +57 -0
  43. package/src/tools/glob.mjs +117 -0
  44. package/src/tools/grep.mjs +127 -0
  45. package/src/tools/ls.mjs +58 -0
  46. package/src/tools/lsp.mjs +115 -0
  47. package/src/tools/multi-edit.mjs +94 -0
  48. package/src/tools/notebook-edit.mjs +96 -0
  49. package/src/tools/read-mcp-resource.mjs +57 -0
  50. package/src/tools/read.mjs +138 -0
  51. package/src/tools/registry.mjs +114 -0
  52. package/src/tools/remote-trigger.mjs +84 -0
  53. package/src/tools/send-message.mjs +64 -0
  54. package/src/tools/skill.mjs +52 -0
  55. package/src/tools/todo-write.mjs +68 -0
  56. package/src/tools/tool-search.mjs +77 -0
  57. package/src/tools/web-fetch.mjs +65 -0
  58. package/src/tools/web-search.mjs +89 -0
  59. package/src/tools/write.mjs +55 -0
  60. package/src/ui/commands.mjs +499 -0
  61. package/src/ui/ink-app.mjs +231 -0
  62. package/src/ui/repl.mjs +144 -0
  63. 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
+ }
@@ -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
+ }