codehere 0.3.0-rc.10 → 0.3.0-rc.11

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.
@@ -1,370 +1,369 @@
1
1
  /**
2
- * Interactive REPL for codehere.
2
+ * Interactive REPL · faithful port of v0.2's session.ts (commit 796d4b7).
3
3
  *
4
- * Bare `codehere` (no args) drops here — the CLI surface, parallel to the
5
- * web UI at `codehere start`. Type a task in plain English; the Conductor
6
- * routes to the right specialist; events stream to the terminal; you get
7
- * the next prompt when the task completes.
4
+ * Same banner, prompt, slash dispatcher, /help formatter, legacy non-slash
5
+ * syntax, and keyboard-shortcut hints as the v0.2 REPL. Backends adapted
6
+ * to v0.3 reality:
7
+ * - chat/edit/review/planner functions (removed in D7) → Conductor.spawn
8
+ * - /cost (no v0.3 cost tracking) → alias to /status
9
+ * - /trust → runs `codehere trust` inline
10
+ * - /undo (no v0.3 undo layer) → queued for v0.5 (F3 memory)
8
11
  *
9
- * Why this exists:
10
- * - The v0.2 REPL was removed in D7 along with the rest of the v0.2 CLI
11
- * surface. That trim went too far — it removed a CLI entry point users
12
- * relied on. rc.6 brings it back, this time backed by the v0.3
13
- * Conductor (rule-based routing) instead of v0.2's smart-route.
14
- * - codehere is "PM you talk to" — terminal users should be able to talk
15
- * to it in the terminal, not be forced to a web UI. Both surfaces, both
16
- * first-class.
17
- *
18
- * What's here:
19
- * - Read a line → route via Conductor → spawn specialist → stream events
20
- * to stdout → await completion → next prompt
21
- * - `/agents` lists installed specialists and the routing rules
22
- * - `/status` shows recent audit activity
23
- * - `/quit` (or Ctrl+C / Ctrl+D) exits cleanly
12
+ * Citing 796d4b7 for lineage. Do not redesign without searching git first.
24
13
  */
25
- import readline from 'readline';
26
- import { colors, newline } from '../../ui.js';
27
- import { getVersion } from '../../utils/version.js';
14
+ import * as readline from 'readline';
15
+ import { colors, newline, success, error, warning, info, createWelcomeMessage } from '../../ui.js';
28
16
  import { SessionManager, availableAgents } from '../../infrastructure/orchestration/session-manager.js';
29
- const SLASH_COMMANDS = [
30
- // Task commands · describe the work, the Conductor routes
31
- {
32
- name: 'fix',
33
- aliases: ['edit'],
34
- args: '<file> <instruction>',
35
- description: 'fix or edit a file (Conductor routes to Aider by default)',
36
- action: 'task',
37
- promptTemplate: (args) => {
38
- const parts = args.split(/\s+/);
39
- const file = parts[0];
40
- const instr = parts.slice(1).join(' ');
41
- return `fix ${file}: ${instr}`;
42
- },
43
- },
44
- {
45
- name: 'explain',
46
- aliases: ['e'],
47
- args: '<file>',
48
- description: 'explain a file (Conductor routes to Claude Code by default)',
49
- action: 'task',
50
- promptTemplate: (args) => `explain this file in plain English: ${args}`,
51
- },
52
- {
53
- name: 'review',
54
- aliases: ['r'],
55
- args: '<file>',
56
- description: 'review a file for bugs and risks (routes to Claude Code)',
57
- action: 'task',
58
- promptTemplate: (args) => `review ${args} for bugs, security issues, and risks. List concrete findings.`,
59
- },
60
- {
61
- name: 'plan',
62
- aliases: ['p'],
63
- args: '<instruction>',
64
- description: 'plan a complex task into steps (routes to Codex/Claude Code)',
65
- action: 'task',
66
- promptTemplate: (args) => `plan: ${args}. Break down into concrete steps before any execution.`,
67
- },
68
- {
69
- name: 'test',
70
- args: '<file or area>',
71
- description: 'add or write tests (routes to Codex)',
72
- action: 'task',
73
- promptTemplate: (args) => `add tests for ${args}. Cover edge cases and likely failure modes.`,
74
- },
75
- // Meta · don't spawn agents · run locally
76
- { name: 'agents', aliases: ['a'], description: 'list installed specialists + routing rules', action: 'meta' },
77
- { name: 'status', aliases: ['s'], description: 'last 24h activity from the audit log', action: 'meta' },
78
- { name: 'trust', args: '[path]', description: 'scan a path for security issues (default: current dir)', action: 'meta' },
79
- { name: 'history', aliases: ['hist'], description: 'recent prompts in this session', action: 'meta' },
80
- { name: 'web', description: 'launch the web UI at :3773 (in another terminal)', action: 'meta' },
81
- { name: 'undo', description: 'undo the last edit (queued for v0.5 · F3 memory layer)', action: 'meta' },
82
- { name: 'cost', description: 'usage / cost (alias for /status in v0.3)', action: 'meta' },
83
- { name: 'clear', description: 'clear the screen', action: 'meta' },
84
- { name: 'help', aliases: ['?'], description: 'show this help', action: 'meta' },
85
- { name: 'quit', aliases: ['exit', 'q'], description: 'exit (Ctrl+C / Ctrl+D also work)', action: 'meta' },
86
- ];
87
- function findSlashCommand(input) {
88
- const parts = input.trim().split(/\s+/);
89
- const head = parts[0].toLowerCase();
90
- for (const cmd of SLASH_COMMANDS) {
91
- if (cmd.name === head || (cmd.aliases ?? []).includes(head)) {
92
- return { command: cmd, args: parts.slice(1).join(' ').trim() };
93
- }
94
- }
95
- return null;
96
- }
97
- export async function runInteractiveRepl(options = {}) {
98
- const cwd = options.cwd ?? process.cwd();
99
- const history = [];
17
+ export async function runInteractiveRepl() {
18
+ console.clear();
19
+ console.log(createWelcomeMessage());
100
20
  newline();
101
- console.log(colors.bold('codehere ') + colors.dim('v' + getVersion()));
102
- console.log(colors.dim('Talk to your AI coding agents. Type a task to delegate; or use a /command.'));
21
+ console.log(colors.dim('Type your questions naturally, or use /commands. Type /help for all commands, exit to quit.'));
103
22
  newline();
104
- console.log(colors.dim(' /fix /explain /review /plan delegate a task; Conductor picks the specialist'));
105
- console.log(colors.dim(' /agents /status /trust /history show state, no agent spawn'));
106
- console.log(colors.dim(' /help full command list'));
107
- console.log(colors.dim(' ↑/↓ history · Ctrl+L clear · Ctrl+C / Ctrl+D exit'));
23
+ console.log(colors.dim('Keyboard Shortcuts: ↑/↓ for history, Ctrl+C to cancel, Ctrl+D to exit, Ctrl+L to clear'));
24
+ newline();
25
+ console.log(colors.dim('────────────────────────────────────────────────────'));
108
26
  newline();
109
- const installed = availableAgents().filter((a) => a.installed);
110
- if (installed.length === 0) {
111
- console.log(colors.yellow(' No specialist agents installed. Install at least one to delegate tasks:'));
112
- console.log(colors.dim(' npm install -g @anthropic-ai/claude-code'));
113
- console.log(colors.dim(' npm install -g @openai/codex'));
114
- console.log(colors.dim(' brew install aider'));
115
- console.log(colors.dim(' curl -fsSL https://opencode.ai/install | bash'));
116
- newline();
117
- console.log(colors.dim('You can still use ') +
118
- colors.cyan('/status') +
119
- colors.dim(', ') +
120
- colors.cyan('codehere trust .') +
121
- colors.dim(', and the safety hook here.'));
122
- newline();
123
- }
124
27
  const rl = readline.createInterface({
125
28
  input: process.stdin,
126
29
  output: process.stdout,
127
- prompt: colors.cyan('› '),
30
+ prompt: colors.cyan('codehere> '),
31
+ historySize: 100,
128
32
  terminal: true,
129
- historySize: 200,
130
33
  });
131
- const manager = SessionManager.getInstance();
132
- const promptNext = () => {
133
- rl.setPrompt(colors.cyan('› '));
34
+ const state = { history: [] };
35
+ rl.prompt();
36
+ rl.on('line', async (input) => {
37
+ const trimmed = input.trim();
38
+ if (!trimmed) {
39
+ rl.prompt();
40
+ return;
41
+ }
42
+ const lower = trimmed.toLowerCase();
43
+ if (lower === 'exit' || lower === 'quit' || trimmed === 'q') {
44
+ console.log(info('Goodbye! 👋'));
45
+ rl.close();
46
+ return;
47
+ }
48
+ if (lower === 'help' || trimmed === '?') {
49
+ showHelp();
50
+ showKeyboardShortcuts();
51
+ rl.prompt();
52
+ return;
53
+ }
54
+ if (lower === 'clear') {
55
+ console.clear();
56
+ console.log(createWelcomeMessage());
57
+ newline();
58
+ rl.prompt();
59
+ return;
60
+ }
61
+ try {
62
+ await executeCommand(trimmed, state);
63
+ }
64
+ catch (err) {
65
+ console.error(error('Error:'), err instanceof Error ? err.message : String(err));
66
+ }
134
67
  rl.prompt();
135
- };
68
+ });
136
69
  rl.on('close', () => {
137
70
  newline();
138
- console.log(colors.dim('Goodbye.'));
71
+ console.log(colors.dim('Session ended.'));
139
72
  process.exit(0);
140
73
  });
141
- rl.on('SIGINT', () => {
142
- rl.close();
143
- });
144
- promptNext();
145
- for await (const line of rl) {
146
- const trimmed = line.trim();
147
- if (!trimmed) {
148
- promptNext();
149
- continue;
74
+ }
75
+ /** One-shot · `codehere "review src/auth.ts"` runs that task and exits. */
76
+ export async function runOneShot(prompt) {
77
+ const installed = availableAgents().filter((a) => a.installed);
78
+ if (installed.length === 0) {
79
+ console.log(warning('No specialist installed. Run `codehere setup` and try again.'));
80
+ process.exit(1);
81
+ }
82
+ try {
83
+ await spawnViaConductor(prompt);
84
+ }
85
+ catch (err) {
86
+ console.error(error('Error:'), err instanceof Error ? err.message : String(err));
87
+ process.exit(1);
88
+ }
89
+ }
90
+ async function executeCommand(input, state) {
91
+ const lower = input.toLowerCase().trim();
92
+ if (lower.startsWith('/')) {
93
+ await handleSlashCommand(input.substring(1), state);
94
+ return;
95
+ }
96
+ // Legacy non-slash syntax (v0.2 backwards compat)
97
+ if (lower.startsWith('explain ') || lower.startsWith('e ')) {
98
+ const file = input.substring(lower.startsWith('explain ') ? 8 : 2).trim();
99
+ await handleExplain(file);
100
+ }
101
+ else if (lower.startsWith('fix ') || lower.startsWith('edit ')) {
102
+ const rest = input.substring(lower.startsWith('fix ') ? 4 : 5).trim();
103
+ const parts = rest.split(' ');
104
+ const file = parts[0];
105
+ const instruction = parts.slice(1).join(' ');
106
+ await handleFix(file, instruction);
107
+ }
108
+ else if (lower.startsWith('review ')) {
109
+ await handleReview(input.substring(7).trim());
110
+ }
111
+ else if (lower.startsWith('plan ')) {
112
+ await handlePlan(input.substring(5).trim());
113
+ }
114
+ else {
115
+ // Default: delegate via Conductor
116
+ await handleAsk(input, state);
117
+ }
118
+ }
119
+ async function handleSlashCommand(input, state) {
120
+ const parts = input.trim().split(/\s+/);
121
+ const command = parts[0].toLowerCase();
122
+ const args = parts.slice(1).join(' ');
123
+ switch (command) {
124
+ case 'fix':
125
+ case 'edit': {
126
+ const fixParts = args.split(/\s+/);
127
+ if (fixParts.length < 2) {
128
+ console.log(warning('Usage: /fix <file> <instruction>'));
129
+ console.log(colors.dim('Example: /fix src/utils.ts "add error handling"'));
130
+ return;
131
+ }
132
+ await handleFix(fixParts[0], fixParts.slice(1).join(' '));
133
+ break;
150
134
  }
151
- history.push(trimmed);
152
- // Slash commands first
153
- if (trimmed.startsWith('/')) {
154
- const found = findSlashCommand(trimmed.slice(1));
155
- if (!found) {
156
- console.log(colors.yellow(` Unknown command: ${trimmed.split(/\s+/)[0]}`));
157
- console.log(colors.dim(' Run /help to see all commands.'));
158
- promptNext();
159
- continue;
135
+ case 'explain':
136
+ case 'e':
137
+ if (!args) {
138
+ console.log(warning('Usage: /explain <file>'));
139
+ console.log(colors.dim('Example: /explain src/auth.ts'));
140
+ return;
160
141
  }
161
- const { command, args } = found;
162
- switch (command.name) {
163
- case 'quit':
164
- rl.close();
165
- return;
166
- case 'help':
167
- printSlashCommandHelp();
168
- break;
169
- case 'clear':
170
- console.clear();
171
- newline();
172
- console.log(colors.bold('codehere ') + colors.dim('v' + getVersion()));
173
- newline();
174
- break;
175
- case 'agents':
176
- printAgentsAndRoutingRules();
177
- break;
178
- case 'status':
179
- await printStatusSummary();
180
- break;
181
- case 'history':
182
- printHistory(history);
183
- break;
184
- case 'web':
185
- console.log(colors.dim(' Tip: run ') +
186
- colors.cyan('codehere start') +
187
- colors.dim(' in another terminal to launch the web UI at http://127.0.0.1:3773'));
188
- break;
189
- case 'trust':
190
- await runTrustScan(args || '.');
191
- break;
192
- case 'cost':
193
- // v0.3 doesn't separate cost tracking — show the same dashboard as /status
194
- await printStatusSummary();
195
- break;
196
- case 'undo':
197
- console.log(colors.dim(' /undo is queued for v0.5 (F3 memory layer).'));
198
- console.log(colors.dim(' For now, use git: ') + colors.cyan('git diff') + colors.dim(' / ') + colors.cyan('git checkout -- <file>'));
199
- break;
200
- default: {
201
- // Task slash command (/fix, /explain, /review, /plan, /test) → Conductor
202
- if (command.action !== 'task' || !command.promptTemplate)
203
- break;
204
- if (!args) {
205
- console.log(colors.yellow(` Usage: /${command.name}${command.args ? ' ' + command.args : ''}`));
206
- break;
207
- }
208
- if (installed.length === 0) {
209
- console.log(colors.yellow(' No specialist installed — install one and try again.'));
210
- break;
211
- }
212
- const promptText = command.promptTemplate(args);
213
- try {
214
- await runTask(promptText, cwd, manager);
215
- }
216
- catch (err) {
217
- console.log(colors.red(' ✗ ') + (err instanceof Error ? err.message : String(err)));
218
- }
219
- newline();
220
- }
142
+ await handleExplain(args.split(/\s+/)[0]);
143
+ break;
144
+ case 'review':
145
+ case 'r':
146
+ if (!args) {
147
+ console.log(warning('Usage: /review <file>'));
148
+ console.log(colors.dim('Example: /review src/auth.ts'));
149
+ return;
221
150
  }
222
- promptNext();
223
- continue;
224
- }
225
- // Legacy non-slash syntax for backwards-compat with v0.2 muscle memory
226
- // (commit 796d4b7's REPL accepted these forms without /).
227
- const lower = trimmed.toLowerCase();
228
- const legacyMatch = lower.startsWith('explain ') || lower.startsWith('e ')
229
- ? { name: 'explain', argsAt: lower.startsWith('explain ') ? 8 : 2 }
230
- : lower.startsWith('fix ') || lower.startsWith('edit ')
231
- ? { name: 'fix', argsAt: lower.startsWith('fix ') ? 4 : 5 }
232
- : lower.startsWith('review ')
233
- ? { name: 'review', argsAt: 7 }
234
- : lower.startsWith('plan ')
235
- ? { name: 'plan', argsAt: 5 }
236
- : null;
237
- if (legacyMatch) {
238
- const cmd = SLASH_COMMANDS.find((c) => c.name === legacyMatch.name);
239
- if (cmd?.promptTemplate) {
240
- const args = trimmed.substring(legacyMatch.argsAt).trim();
241
- if (!args) {
242
- console.log(colors.yellow(` Usage: /${cmd.name}${cmd.args ? ' ' + cmd.args : ''}`));
243
- }
244
- else if (installed.length === 0) {
245
- console.log(colors.yellow(' No specialist installed — install one and try again.'));
246
- }
247
- else {
248
- try {
249
- await runTask(cmd.promptTemplate(args), cwd, manager);
250
- }
251
- catch (err) {
252
- console.log(colors.red(' ✗ ') + (err instanceof Error ? err.message : String(err)));
253
- }
254
- }
255
- newline();
256
- promptNext();
257
- continue;
151
+ await handleReview(args.split(/\s+/)[0]);
152
+ break;
153
+ case 'plan':
154
+ case 'p':
155
+ if (!args) {
156
+ console.log(warning('Usage: /plan <instruction>'));
157
+ console.log(colors.dim('Example: /plan "add user authentication"'));
158
+ return;
258
159
  }
160
+ await handlePlan(args);
161
+ break;
162
+ case 'cost':
163
+ case 'status': {
164
+ // v0.3: no separate cost tracking; both surface the audit-log dashboard
165
+ try {
166
+ const { executeStatusCommand } = await import('./commands/status-command.js');
167
+ await executeStatusCommand({});
168
+ }
169
+ catch {
170
+ console.log(error('Failed to get status'));
171
+ }
172
+ break;
259
173
  }
260
- // Plain text → delegate via Conductor
261
- if (installed.length === 0) {
262
- console.log(colors.yellow(' No specialist installed — install one and try again.'));
263
- promptNext();
264
- continue;
265
- }
266
- try {
267
- await runTask(trimmed, cwd, manager);
174
+ case 'trust': {
175
+ // v0.3: trust scan inline
176
+ const target = args || '.';
177
+ const { spawn } = await import('child_process');
178
+ const { fileURLToPath } = await import('url');
179
+ const { join, dirname } = await import('path');
180
+ const here = fileURLToPath(import.meta.url);
181
+ const cliPath = join(dirname(here), '..', '..', 'index.js');
182
+ await new Promise((resolve) => {
183
+ const child = spawn('node', [cliPath, 'trust', target], { stdio: 'inherit' });
184
+ child.on('close', () => resolve());
185
+ child.on('error', (err) => {
186
+ console.log(error('Trust scan failed: ' + err.message));
187
+ resolve();
188
+ });
189
+ });
190
+ break;
268
191
  }
269
- catch (err) {
270
- console.log(colors.red(' ✗ ') + (err instanceof Error ? err.message : String(err)));
192
+ case 'agents': {
193
+ const all = availableAgents();
194
+ newline();
195
+ console.log(colors.bold(' Installed specialists:'));
196
+ for (const a of all) {
197
+ const tag = a.installed ? colors.green(' ✓ ') : colors.dim(' · ');
198
+ const status = a.installed ? '' : colors.dim(' (not installed)');
199
+ console.log(tag + colors.cyan(a.id.padEnd(14)) + colors.dim(a.label) + status);
200
+ }
201
+ newline();
202
+ break;
271
203
  }
272
- newline();
273
- promptNext();
204
+ case 'undo':
205
+ console.log(colors.dim(' /undo is queued for v0.5 (F3 memory layer).'));
206
+ console.log(colors.dim(' For now: ') + colors.cyan('git diff') + colors.dim(' / ') + colors.cyan('git checkout -- <file>'));
207
+ break;
208
+ case 'history':
209
+ case 'h':
210
+ if (state.history.length === 0) {
211
+ console.log(colors.dim('No command history yet.'));
212
+ }
213
+ else {
214
+ console.log(colors.bold('Recent Commands:'));
215
+ state.history.slice(-10).forEach((h, i) => {
216
+ const preview = h.command.length > 60 ? h.command.slice(0, 57) + '...' : h.command;
217
+ console.log(colors.dim(` ${i + 1}. ${preview}`));
218
+ });
219
+ }
220
+ break;
221
+ case 'clear':
222
+ console.clear();
223
+ console.log(createWelcomeMessage());
224
+ newline();
225
+ break;
226
+ case 'help':
227
+ case '?':
228
+ showSlashCommandHelp();
229
+ break;
230
+ default:
231
+ console.log(warning(`Unknown command: /${command}`));
232
+ console.log(colors.dim('Type /help to see available commands'));
274
233
  }
275
234
  }
276
- /**
277
- * Run `codehere trust <path>` inline. Spawns the trust command as a child
278
- * process so the existing rendering / scoring logic is reused as-is.
279
- */
280
- async function runTrustScan(target) {
281
- const { spawn } = await import('child_process');
282
- const { fileURLToPath } = await import('url');
283
- const { join, dirname } = await import('path');
284
- // dist/presentation/cli/repl.js → walk up to dist/index.js
285
- const here = fileURLToPath(import.meta.url);
286
- const cliPath = join(dirname(here), '..', '..', 'index.js');
287
- await new Promise((resolve) => {
288
- const child = spawn('node', [cliPath, 'trust', target], { stdio: 'inherit' });
289
- child.on('close', () => resolve());
290
- child.on('error', (err) => {
291
- console.log(colors.red(' ✗ ') + err.message);
292
- resolve();
293
- });
235
+ function showSlashCommandHelp() {
236
+ newline();
237
+ console.log(colors.bold('Slash Commands'));
238
+ console.log(colors.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
239
+ newline();
240
+ const commands = [
241
+ ['/fix <file> <instruction>', 'Apply AI-generated edit to a file'],
242
+ ['/explain <file>', 'Get AI explanation of a file'],
243
+ ['/review <file>', 'Review code quality and get suggestions'],
244
+ ['/plan <instruction>', 'Create an implementation plan'],
245
+ ['/agents', 'List installed specialist agents'],
246
+ ['/trust [path]', 'Scan path for security issues (default: cwd)'],
247
+ ['/status', 'Show project / audit dashboard'],
248
+ ['/cost', 'Alias for /status (no separate cost layer in v0.3)'],
249
+ ['/undo', 'Undo last edit (queued for v0.5)'],
250
+ ['/history', 'Show command history this session'],
251
+ ['/clear', 'Clear the screen'],
252
+ ['/help', 'Show this help message'],
253
+ ];
254
+ commands.forEach(([cmd, desc]) => {
255
+ console.log(` ${colors.cyan(cmd.padEnd(30))} ${colors.dim(desc)}`);
294
256
  });
257
+ newline();
258
+ console.log(colors.dim('Tip: You can also just type your question naturally!'));
259
+ console.log(colors.dim('Example: "how does authentication work?"'));
260
+ newline();
295
261
  }
296
- function printSlashCommandHelp() {
297
- const formatRow = (cmd) => {
298
- const aliases = cmd.aliases?.length ? colors.dim(' (/' + cmd.aliases.join(', /') + ')') : '';
299
- const cmdLabel = '/' + cmd.name + (cmd.args ? ' ' + cmd.args : '');
300
- console.log(' ' + colors.cyan(cmdLabel.padEnd(34)) + colors.dim(cmd.description) + aliases);
301
- };
262
+ function showHelp() {
263
+ newline();
264
+ console.log(colors.bold('Slash Commands (recommended):'));
302
265
  newline();
303
- console.log(colors.bold('Slash commands'));
304
- console.log(colors.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
266
+ console.log(colors.cyan(' /fix <file> <instruction>') + ' Apply AI-generated edit');
267
+ console.log(colors.cyan(' /explain <file>') + ' Get AI explanation');
268
+ console.log(colors.cyan(' /review <file>') + ' Review code quality');
269
+ console.log(colors.cyan(' /plan <instruction>') + ' Create implementation plan');
270
+ console.log(colors.cyan(' /agents') + ' List installed specialists');
271
+ console.log(colors.cyan(' /trust [path]') + ' Scan for security issues');
272
+ console.log(colors.cyan(' /status') + ' Show project status');
273
+ console.log(colors.cyan(' /history') + ' Show command history');
274
+ console.log(colors.cyan(' /help') + ' Show all commands');
305
275
  newline();
306
- console.log(colors.bold(' Tasks') + colors.dim(' — describe the work; Conductor routes to the right specialist'));
307
- for (const cmd of SLASH_COMMANDS.filter((c) => c.action === 'task'))
308
- formatRow(cmd);
276
+ console.log(colors.bold('Quick Commands:'));
309
277
  newline();
310
- console.log(colors.bold(' Meta') + colors.dim(' — run locally, no agent spawn'));
311
- for (const cmd of SLASH_COMMANDS.filter((c) => c.action === 'meta'))
312
- formatRow(cmd);
278
+ console.log(colors.cyan(' <question>') + ' Ask anything · Conductor routes to the right specialist');
279
+ console.log(colors.cyan(' clear') + ' Clear screen');
280
+ console.log(colors.cyan(' exit') + ' Exit session (or: quit, q)');
313
281
  newline();
314
- console.log(colors.bold(' Or just type'));
315
- console.log(colors.dim(' Plain text without / → Conductor routes (e.g. "review src/auth.ts" → claude-code)'));
316
- console.log(colors.dim(' Legacy v0.2 syntax: ') + colors.cyan('explain src/auth.ts') + colors.dim(' / ') + colors.cyan('fix file "instr"') + colors.dim(' also work without /'));
282
+ console.log(colors.dim('Examples:'));
283
+ console.log(colors.dim(' How does authentication work?'));
284
+ console.log(colors.dim(' /explain src/auth.ts'));
285
+ console.log(colors.dim(' /fix src/utils.js "Add error handling"'));
317
286
  newline();
318
287
  }
319
- function printHistory(history) {
288
+ function showKeyboardShortcuts() {
289
+ console.log(colors.bold('Keyboard Shortcuts:'));
320
290
  newline();
321
- if (history.length === 0) {
322
- console.log(colors.dim(' No prompts yet in this session.'));
323
- newline();
291
+ console.log(colors.cyan(' ↑/↓') + ' Navigate command history');
292
+ console.log(colors.cyan(' Ctrl+C') + ' Cancel current command');
293
+ console.log(colors.cyan(' Ctrl+D') + ' Exit session');
294
+ console.log(colors.cyan(' Ctrl+L') + ' Clear screen');
295
+ newline();
296
+ }
297
+ // ── Handlers · all delegate via Conductor in v0.3 ────────────────────────
298
+ async function handleAsk(question, state) {
299
+ await spawnViaConductor(question, state);
300
+ }
301
+ async function handleExplain(file) {
302
+ if (!file) {
303
+ console.log(warning('Usage: explain <file>'));
324
304
  return;
325
305
  }
326
- console.log(colors.bold(' Recent prompts:'));
327
- history.slice(-10).forEach((h, i) => {
328
- const preview = h.length > 70 ? h.slice(0, 67) + '…' : h;
329
- console.log(colors.dim(` ${(i + 1).toString().padStart(2, ' ')}. `) + preview);
330
- });
331
- newline();
306
+ await spawnViaConductor(`Explain this file in plain English: ${file}`);
332
307
  }
333
- /**
334
- * Run a single task and exit — bypass the REPL prompt loop.
335
- * Use case: `codehere "review src/auth.ts"` or scripts piping a task in.
336
- */
337
- export async function runOneShot(prompt, cwd = process.cwd()) {
338
- const installed = availableAgents().filter((a) => a.installed);
339
- if (installed.length === 0) {
340
- console.log(colors.yellow(' No specialist installed. Install one (see `codehere setup`) and try again.'));
341
- process.exit(1);
308
+ async function handleFix(file, instruction) {
309
+ if (!file || !instruction) {
310
+ console.log(warning('Usage: fix <file> <instruction>'));
311
+ console.log(colors.dim('Example: fix src/utils.ts "add error handling"'));
312
+ return;
342
313
  }
343
- const manager = SessionManager.getInstance();
344
- try {
345
- await runTask(prompt, cwd, manager);
314
+ await spawnViaConductor(`fix ${file}: ${instruction}`);
315
+ }
316
+ async function handleReview(file) {
317
+ if (!file) {
318
+ console.log(warning('Usage: review <file>'));
319
+ return;
346
320
  }
347
- catch (err) {
348
- console.log(colors.red(' ✗ ') + (err instanceof Error ? err.message : String(err)));
349
- process.exit(1);
321
+ await spawnViaConductor(`Review ${file} for bugs, security issues, and risks. Concrete findings only.`);
322
+ }
323
+ async function handlePlan(instruction) {
324
+ if (!instruction) {
325
+ console.log(warning('Usage: plan <instruction>'));
326
+ return;
350
327
  }
328
+ await spawnViaConductor(`Plan: ${instruction}. Break into concrete steps before any execution.`);
351
329
  }
352
- async function runTask(prompt, cwd, manager) {
353
- const session = manager.spawn(prompt, 'conductor', { cwd });
354
- const startedLabel = colors.dim(' ┌─ ') + colors.bold(session.sessionId.slice(0, 8)) + colors.dim(' · ' + new Date(session.startedAt).toLocaleTimeString());
355
- console.log(startedLabel);
330
+ async function spawnViaConductor(prompt, state) {
331
+ const installed = availableAgents().filter((a) => a.installed);
332
+ if (installed.length === 0) {
333
+ console.log(warning('No specialist installed — install one and try again.'));
334
+ return;
335
+ }
336
+ const manager = SessionManager.getInstance();
337
+ const session = manager.spawn(prompt, 'conductor', { cwd: process.cwd() });
338
+ console.log(colors.dim(' ┌─ ') +
339
+ colors.bold(session.sessionId.slice(0, 8)) +
340
+ colors.dim(' · ' + new Date(session.startedAt).toLocaleTimeString()));
341
+ let captured = '';
356
342
  await new Promise((resolve) => {
357
343
  const unsubscribe = session.subscribe((event) => {
358
- renderEvent(event);
344
+ const text = renderEvent(event);
345
+ if (text)
346
+ captured += text + '\n';
359
347
  if (event.type === 'codehere:complete' || event.type === 'codehere:error' || event.type === 'exit') {
360
348
  unsubscribe();
361
349
  resolve();
362
350
  }
363
351
  });
364
352
  });
365
- const finalStatus = session.status;
366
- const verdict = finalStatus === 'completed' ? colors.green(' └─ done') : finalStatus === 'killed' ? colors.yellow(' └─ killed') : colors.red(' └─ ' + finalStatus);
353
+ const status = session.status;
354
+ const verdict = status === 'completed'
355
+ ? success(' └─ done')
356
+ : status === 'killed'
357
+ ? warning(' └─ killed')
358
+ : error(' └─ ' + status);
367
359
  console.log(verdict);
360
+ if (state) {
361
+ state.history.push({
362
+ command: prompt,
363
+ response: captured,
364
+ timestamp: new Date().toISOString(),
365
+ });
366
+ }
368
367
  }
369
368
  function renderEvent(event) {
370
369
  switch (event.type) {
@@ -374,11 +373,7 @@ function renderEvent(event) {
374
373
  colors.bold('routing → ') +
375
374
  colors.cyan(data.delegatedTo ?? '?') +
376
375
  colors.dim(' · ' + (data.reason ?? '')));
377
- break;
378
- }
379
- case 'codehere:steps': {
380
- // Step snapshots replay frequently — skip the noisy ones; the UI handles them
381
- break;
376
+ return null;
382
377
  }
383
378
  case 'stdout':
384
379
  case 'stderr': {
@@ -386,89 +381,21 @@ function renderEvent(event) {
386
381
  const line = data?.line;
387
382
  if (line) {
388
383
  const text = String(line).trimEnd();
389
- if (text)
384
+ if (text) {
390
385
  console.log(colors.dim(' │ ') + text);
386
+ return text;
387
+ }
391
388
  }
392
- break;
389
+ return null;
393
390
  }
394
391
  case 'error': {
395
392
  const data = event.data;
396
393
  if (data?.message)
397
394
  console.log(colors.red(' │ ✗ ') + data.message);
398
- break;
399
- }
400
- case 'codehere:complete':
401
- case 'exit': {
402
- // handled by runTask's verdict line
403
- break;
395
+ return null;
404
396
  }
405
397
  default:
406
- // Other event types — quiet by default to keep the REPL readable
407
- break;
408
- }
409
- }
410
- function printAgentsAndRoutingRules() {
411
- newline();
412
- console.log(colors.bold(' Installed specialists:'));
413
- const all = availableAgents();
414
- for (const a of all) {
415
- const tag = a.installed ? colors.green(' ✓ ') : colors.dim(' · ');
416
- const status = a.installed ? '' : colors.dim(' (not installed)');
417
- console.log(tag + colors.cyan(a.id.padEnd(14)) + colors.dim(a.label) + status);
398
+ return null;
418
399
  }
419
- newline();
420
- console.log(colors.bold(' Routing (rule-based; LLM classifier in v0.4):'));
421
- console.log(colors.dim(' review · audit · explain → claude-code'));
422
- console.log(colors.dim(' refactor · restructure → codex'));
423
- console.log(colors.dim(' fix · patch · bugfix → aider'));
424
- console.log(colors.dim(' scaffold · generate · bootstrap → opencode'));
425
- console.log(colors.dim(' add tests · write tests → codex'));
426
- console.log(colors.dim(' (anything else) → claude-code'));
427
- newline();
428
- }
429
- async function printStatusSummary() {
430
- newline();
431
- try {
432
- const { existsSync, readFileSync } = await import('fs');
433
- const { join } = await import('path');
434
- const { homedir } = await import('os');
435
- const auditPath = process.env.CODEHERE_AUDIT_FILE || join(homedir(), '.codehere', 'audit', 'hook-events.jsonl');
436
- if (!existsSync(auditPath)) {
437
- console.log(colors.dim(' No audit log yet. Run a task or `codehere hook install`.'));
438
- newline();
439
- return;
440
- }
441
- const lines = readFileSync(auditPath, 'utf-8').split('\n').filter(Boolean).slice(-10);
442
- const cutoff = Date.now() - 24 * 60 * 60 * 1000;
443
- let actions = 0;
444
- let blocked = 0;
445
- let trustSum = 0;
446
- let trustN = 0;
447
- for (const line of lines) {
448
- try {
449
- const entry = JSON.parse(line);
450
- const ts = entry.timestamp ? Date.parse(entry.timestamp) : 0;
451
- if (ts < cutoff)
452
- continue;
453
- actions++;
454
- if (entry.blocked)
455
- blocked++;
456
- if (typeof entry.riskScore === 'number') {
457
- trustSum += 100 - entry.riskScore;
458
- trustN++;
459
- }
460
- }
461
- catch {
462
- /* skip malformed line */
463
- }
464
- }
465
- const avgTrust = trustN > 0 ? Math.round(trustSum / trustN) : null;
466
- console.log(colors.bold(' Last 24h:'));
467
- console.log(` ${actions} actions · ${blocked} blocked${avgTrust !== null ? ` · avg trust ${avgTrust}` : ''}`);
468
- }
469
- catch (err) {
470
- console.log(colors.dim(' Could not read audit log: ' + (err instanceof Error ? err.message : String(err))));
471
- }
472
- newline();
473
400
  }
474
401
  //# sourceMappingURL=repl.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehere",
3
- "version": "0.3.0-rc.10",
3
+ "version": "0.3.0-rc.11",
4
4
  "description": "A PM for your AI coding agents. Delegate, orchestrate, and audit Claude Code, Codex, Aider, and OpenCode from one local web UI — every AI action traced, every file scored.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",