codehere 0.3.0-rc.8 → 0.3.0-rc.9

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.
@@ -26,16 +26,42 @@ import readline from 'readline';
26
26
  import { colors, newline } from '../../ui.js';
27
27
  import { getVersion } from '../../utils/version.js';
28
28
  import { SessionManager, availableAgents } from '../../infrastructure/orchestration/session-manager.js';
29
+ const SLASH_COMMANDS = [
30
+ // Meta · don't spawn agents · run locally
31
+ { name: 'agents', aliases: ['a'], description: 'list installed specialists + routing rules', action: 'meta' },
32
+ { name: 'status', aliases: ['s'], description: 'last 24h activity from the audit log', action: 'meta' },
33
+ { name: 'history', aliases: ['hist'], description: 'recent prompts in this session', action: 'meta' },
34
+ { name: 'web', description: 'launch the web UI at :3773 (in another terminal)', action: 'meta' },
35
+ { name: 'clear', description: 'clear the screen', action: 'meta' },
36
+ { name: 'help', aliases: ['?'], description: 'show this help', action: 'meta' },
37
+ { name: 'quit', aliases: ['exit', 'q'], description: 'exit (Ctrl+C / Ctrl+D also work)', action: 'meta' },
38
+ // Direct-route · skip the Conductor and send the prompt straight to a specific specialist
39
+ { name: 'codex', args: '<prompt>', description: 'send to Codex specifically (skip Conductor)', action: 'route', targetAgent: 'codex' },
40
+ { name: 'claude', args: '<prompt>', aliases: ['cc'], description: 'send to Claude Code (skip Conductor)', action: 'route', targetAgent: 'claude-code' },
41
+ { name: 'aider', args: '<prompt>', description: 'send to Aider (skip Conductor)', action: 'route', targetAgent: 'aider' },
42
+ { name: 'opencode', args: '<prompt>', aliases: ['oc'], description: 'send to OpenCode (skip Conductor)', action: 'route', targetAgent: 'opencode' },
43
+ ];
44
+ function findSlashCommand(input) {
45
+ const parts = input.trim().split(/\s+/);
46
+ const head = parts[0].toLowerCase();
47
+ for (const cmd of SLASH_COMMANDS) {
48
+ if (cmd.name === head || (cmd.aliases ?? []).includes(head)) {
49
+ return { command: cmd, args: parts.slice(1).join(' ').trim() };
50
+ }
51
+ }
52
+ return null;
53
+ }
29
54
  export async function runInteractiveRepl(options = {}) {
30
55
  const cwd = options.cwd ?? process.cwd();
56
+ const history = [];
31
57
  newline();
32
58
  console.log(colors.bold('codehere ') + colors.dim('v' + getVersion()));
33
- console.log(colors.dim('Talk to your AI coding agents. Type a task; codehere routes to the right specialist.'));
59
+ console.log(colors.dim('Talk to your AI coding agents. Type a task to delegate; or use a /command.'));
34
60
  newline();
35
- console.log(colors.dim(' /agents list installed specialists + routing rules'));
36
- console.log(colors.dim(' /status recent activity from the audit log'));
37
- console.log(colors.dim(' /web open the web UI at http://127.0.0.1:3773'));
38
- console.log(colors.dim(' /quit exit (or Ctrl+C / Ctrl+D)'));
61
+ console.log(colors.dim(' /help show all slash commands'));
62
+ console.log(colors.dim(' /agents installed specialists + routing rules'));
63
+ console.log(colors.dim(' /codex <…> skip the Conductor — send straight to a specific specialist'));
64
+ console.log(colors.dim(' ↑/↓ history · Ctrl+L to clear · Ctrl+C / Ctrl+D to exit'));
39
65
  newline();
40
66
  const installed = availableAgents().filter((a) => a.installed);
41
67
  if (installed.length === 0) {
@@ -57,9 +83,10 @@ export async function runInteractiveRepl(options = {}) {
57
83
  output: process.stdout,
58
84
  prompt: colors.cyan('› '),
59
85
  terminal: true,
86
+ historySize: 200,
60
87
  });
61
88
  const manager = SessionManager.getInstance();
62
- const prompt = () => {
89
+ const promptNext = () => {
63
90
  rl.setPrompt(colors.cyan('› '));
64
91
  rl.prompt();
65
92
  };
@@ -69,46 +96,82 @@ export async function runInteractiveRepl(options = {}) {
69
96
  process.exit(0);
70
97
  });
71
98
  rl.on('SIGINT', () => {
72
- // Ctrl+C: if we're idle at the prompt, exit; if a task is mid-flight the
73
- // task's own kill hook handles it via session.kill() (TODO: wire up).
74
99
  rl.close();
75
100
  });
76
- prompt();
101
+ promptNext();
77
102
  for await (const line of rl) {
78
103
  const trimmed = line.trim();
79
104
  if (!trimmed) {
80
- prompt();
81
- continue;
82
- }
83
- if (trimmed === '/quit' || trimmed === '/exit') {
84
- rl.close();
85
- return;
86
- }
87
- if (trimmed === '/agents') {
88
- printAgentsAndRoutingRules();
89
- prompt();
90
- continue;
91
- }
92
- if (trimmed === '/status') {
93
- await printStatusSummary();
94
- prompt();
95
- continue;
96
- }
97
- if (trimmed === '/web' || trimmed === '/start') {
98
- console.log(colors.dim(' Tip: run ') + colors.cyan('codehere start') + colors.dim(' in another terminal to launch the web UI.'));
99
- prompt();
105
+ promptNext();
100
106
  continue;
101
107
  }
108
+ history.push(trimmed);
109
+ // Slash commands first
102
110
  if (trimmed.startsWith('/')) {
103
- console.log(colors.yellow(` Unknown REPL command: ${trimmed}`));
104
- console.log(colors.dim(' Try /agents, /status, /web, or /quit.'));
105
- prompt();
111
+ const found = findSlashCommand(trimmed.slice(1));
112
+ if (!found) {
113
+ console.log(colors.yellow(` Unknown command: ${trimmed.split(/\s+/)[0]}`));
114
+ console.log(colors.dim(' Run /help to see all commands.'));
115
+ promptNext();
116
+ continue;
117
+ }
118
+ const { command, args } = found;
119
+ switch (command.name) {
120
+ case 'quit':
121
+ rl.close();
122
+ return;
123
+ case 'help':
124
+ printSlashCommandHelp();
125
+ break;
126
+ case 'clear':
127
+ console.clear();
128
+ newline();
129
+ console.log(colors.bold('codehere ') + colors.dim('v' + getVersion()));
130
+ newline();
131
+ break;
132
+ case 'agents':
133
+ printAgentsAndRoutingRules();
134
+ break;
135
+ case 'status':
136
+ await printStatusSummary();
137
+ break;
138
+ case 'history':
139
+ printHistory(history);
140
+ break;
141
+ case 'web':
142
+ console.log(colors.dim(' Tip: run ') +
143
+ colors.cyan('codehere start') +
144
+ colors.dim(' in another terminal to launch the web UI at http://127.0.0.1:3773'));
145
+ break;
146
+ default: {
147
+ // Direct-route slash command (/codex, /claude, /aider, /opencode)
148
+ if (command.action !== 'route' || !command.targetAgent)
149
+ break;
150
+ if (!args) {
151
+ console.log(colors.yellow(` Usage: /${command.name} ${command.args ?? '<prompt>'}`));
152
+ console.log(colors.dim(` Example: /${command.name} review src/auth.ts`));
153
+ break;
154
+ }
155
+ if (!installed.some((a) => a.id === command.targetAgent)) {
156
+ console.log(colors.yellow(` ${command.targetAgent} is not installed. Install it or use a different /command.`));
157
+ break;
158
+ }
159
+ try {
160
+ await runTaskWithAgent(args, cwd, manager, command.targetAgent);
161
+ }
162
+ catch (err) {
163
+ console.log(colors.red(' ✗ ') + (err instanceof Error ? err.message : String(err)));
164
+ }
165
+ newline();
166
+ }
167
+ }
168
+ promptNext();
106
169
  continue;
107
170
  }
108
171
  // Plain text → delegate via Conductor
109
172
  if (installed.length === 0) {
110
173
  console.log(colors.yellow(' No specialist installed — install one and try again.'));
111
- prompt();
174
+ promptNext();
112
175
  continue;
113
176
  }
114
177
  try {
@@ -118,8 +181,73 @@ export async function runInteractiveRepl(options = {}) {
118
181
  console.log(colors.red(' ✗ ') + (err instanceof Error ? err.message : String(err)));
119
182
  }
120
183
  newline();
121
- prompt();
184
+ promptNext();
185
+ }
186
+ }
187
+ /**
188
+ * Run a task against a specific specialist (skipping the Conductor).
189
+ * Used by /codex, /claude, /aider, /opencode slash commands.
190
+ */
191
+ async function runTaskWithAgent(prompt, cwd, manager, agentId) {
192
+ const session = manager.spawn(prompt, agentId, { cwd });
193
+ const started = colors.dim(' ┌─ ') +
194
+ colors.bold(session.sessionId.slice(0, 8)) +
195
+ colors.dim(' · ' + agentId + ' · ' + new Date(session.startedAt).toLocaleTimeString());
196
+ console.log(started);
197
+ await new Promise((resolve) => {
198
+ const unsubscribe = session.subscribe((event) => {
199
+ renderEvent(event);
200
+ if (event.type === 'codehere:complete' || event.type === 'codehere:error' || event.type === 'exit') {
201
+ unsubscribe();
202
+ resolve();
203
+ }
204
+ });
205
+ });
206
+ const finalStatus = session.status;
207
+ const verdict = finalStatus === 'completed'
208
+ ? colors.green(' └─ done')
209
+ : finalStatus === 'killed'
210
+ ? colors.yellow(' └─ killed')
211
+ : colors.red(' └─ ' + finalStatus);
212
+ console.log(verdict);
213
+ }
214
+ function printSlashCommandHelp() {
215
+ newline();
216
+ console.log(colors.bold('Slash commands'));
217
+ console.log(colors.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
218
+ newline();
219
+ console.log(colors.bold(' Meta'));
220
+ for (const cmd of SLASH_COMMANDS.filter((c) => c.action === 'meta')) {
221
+ const aliases = cmd.aliases?.length ? colors.dim(' (' + cmd.aliases.map((a) => '/' + a).join(', ') + ')') : '';
222
+ const cmdLabel = '/' + cmd.name + (cmd.args ? ' ' + cmd.args : '');
223
+ console.log(' ' + colors.cyan(cmdLabel.padEnd(28)) + colors.dim(cmd.description) + aliases);
224
+ }
225
+ newline();
226
+ console.log(colors.bold(' Direct route') + colors.dim(' — skip the Conductor, send straight to a specialist'));
227
+ for (const cmd of SLASH_COMMANDS.filter((c) => c.action === 'route')) {
228
+ const aliases = cmd.aliases?.length ? colors.dim(' (' + cmd.aliases.map((a) => '/' + a).join(', ') + ')') : '';
229
+ const cmdLabel = '/' + cmd.name + (cmd.args ? ' ' + cmd.args : '');
230
+ console.log(' ' + colors.cyan(cmdLabel.padEnd(28)) + colors.dim(cmd.description) + aliases);
231
+ }
232
+ newline();
233
+ console.log(colors.bold(' Or just type'));
234
+ console.log(colors.dim(' Plain text in any other shape → Conductor routes to the right specialist'));
235
+ console.log(colors.dim(' Examples: "review src/auth.ts" → claude-code · "fix the race in payments" → aider'));
236
+ newline();
237
+ }
238
+ function printHistory(history) {
239
+ newline();
240
+ if (history.length === 0) {
241
+ console.log(colors.dim(' No prompts yet in this session.'));
242
+ newline();
243
+ return;
122
244
  }
245
+ console.log(colors.bold(' Recent prompts:'));
246
+ history.slice(-10).forEach((h, i) => {
247
+ const preview = h.length > 70 ? h.slice(0, 67) + '…' : h;
248
+ console.log(colors.dim(` ${(i + 1).toString().padStart(2, ' ')}. `) + preview);
249
+ });
250
+ newline();
123
251
  }
124
252
  /**
125
253
  * Run a single task and exit — bypass the REPL prompt loop.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehere",
3
- "version": "0.3.0-rc.8",
3
+ "version": "0.3.0-rc.9",
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",