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.
- package/dist/presentation/cli/repl.js +321 -394
- package/package.json +1 -1
|
@@ -1,370 +1,369 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Interactive REPL
|
|
2
|
+
* Interactive REPL · faithful port of v0.2's session.ts (commit 796d4b7).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
30
|
-
|
|
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.
|
|
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('
|
|
105
|
-
|
|
106
|
-
console.log(colors.dim('
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
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('
|
|
71
|
+
console.log(colors.dim('Session ended.'));
|
|
139
72
|
process.exit(0);
|
|
140
73
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
297
|
-
|
|
298
|
-
|
|
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.
|
|
304
|
-
console.log(colors.
|
|
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('
|
|
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.
|
|
311
|
-
|
|
312
|
-
|
|
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.
|
|
315
|
-
console.log(colors.dim('
|
|
316
|
-
console.log(colors.dim('
|
|
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
|
|
288
|
+
function showKeyboardShortcuts() {
|
|
289
|
+
console.log(colors.bold('Keyboard Shortcuts:'));
|
|
320
290
|
newline();
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
366
|
-
const verdict =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|