@automagik/genie 0.260202.1607 → 0.260202.1901
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/.beads/README.md +81 -0
- package/.beads/config.yaml +67 -0
- package/.beads/interactions.jsonl +0 -0
- package/.beads/issues.jsonl +0 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/AGENTS.md +40 -0
- package/dist/claudio.js +5 -5
- package/dist/genie.js +6 -6
- package/dist/term.js +116 -53
- package/package.json +1 -1
- package/src/lib/beads-registry.ts +546 -0
- package/src/lib/orchestrator/completion.ts +392 -0
- package/src/lib/orchestrator/event-monitor.ts +442 -0
- package/src/lib/orchestrator/index.ts +12 -0
- package/src/lib/orchestrator/patterns.ts +277 -0
- package/src/lib/orchestrator/state-detector.ts +339 -0
- package/src/lib/tmux.ts +15 -1
- package/src/lib/version.ts +1 -1
- package/src/lib/worker-registry.ts +229 -0
- package/src/term-commands/close.ts +256 -0
- package/src/term-commands/daemon.ts +176 -0
- package/src/term-commands/kill.ts +186 -0
- package/src/term-commands/orchestrate.ts +844 -0
- package/src/term-commands/split.ts +8 -7
- package/src/term-commands/work.ts +497 -0
- package/src/term-commands/workers.ts +298 -0
- package/src/term.ts +227 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workers command - Show worker status
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* term workers - List all workers and their states
|
|
6
|
+
* term workers --json - Output as JSON
|
|
7
|
+
* term workers --watch - Live updates (TODO: Phase 1.5)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { $ } from 'bun';
|
|
11
|
+
import * as tmux from '../lib/tmux.js';
|
|
12
|
+
import * as registry from '../lib/worker-registry.js';
|
|
13
|
+
import * as beadsRegistry from '../lib/beads-registry.js';
|
|
14
|
+
import { detectState, stripAnsi } from '../lib/orchestrator/index.js';
|
|
15
|
+
|
|
16
|
+
// Use beads registry when enabled
|
|
17
|
+
const useBeads = beadsRegistry.isBeadsRegistryEnabled();
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export interface WorkersOptions {
|
|
24
|
+
json?: boolean;
|
|
25
|
+
watch?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface WorkerDisplay {
|
|
29
|
+
name: string;
|
|
30
|
+
pane: string;
|
|
31
|
+
task: string;
|
|
32
|
+
state: string;
|
|
33
|
+
time: string;
|
|
34
|
+
alive: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Helper Functions
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if a pane still exists
|
|
43
|
+
*/
|
|
44
|
+
async function isPaneAlive(paneId: string): Promise<boolean> {
|
|
45
|
+
try {
|
|
46
|
+
await tmux.capturePaneContent(paneId, 1);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get current state from pane output
|
|
55
|
+
*/
|
|
56
|
+
async function getCurrentState(paneId: string): Promise<string> {
|
|
57
|
+
try {
|
|
58
|
+
const output = await tmux.capturePaneContent(paneId, 30);
|
|
59
|
+
const state = detectState(output);
|
|
60
|
+
|
|
61
|
+
// Map to display format
|
|
62
|
+
switch (state.type) {
|
|
63
|
+
case 'working':
|
|
64
|
+
case 'tool_use':
|
|
65
|
+
return 'working';
|
|
66
|
+
case 'idle':
|
|
67
|
+
return 'idle';
|
|
68
|
+
case 'permission':
|
|
69
|
+
return '⚠️ perm';
|
|
70
|
+
case 'question':
|
|
71
|
+
return '⚠️ question';
|
|
72
|
+
case 'error':
|
|
73
|
+
return '❌ error';
|
|
74
|
+
case 'complete':
|
|
75
|
+
return '✅ done';
|
|
76
|
+
default:
|
|
77
|
+
return state.type;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
return 'unknown';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get beads queue status
|
|
86
|
+
*/
|
|
87
|
+
async function getQueueStatus(): Promise<{ ready: string[]; blocked: string[] }> {
|
|
88
|
+
const ready: string[] = [];
|
|
89
|
+
const blocked: string[] = [];
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Get ready issues
|
|
93
|
+
const readyResult = await $`bd ready --json`.quiet();
|
|
94
|
+
const readyOutput = readyResult.stdout.toString().trim();
|
|
95
|
+
if (readyOutput) {
|
|
96
|
+
try {
|
|
97
|
+
const issues = JSON.parse(readyOutput);
|
|
98
|
+
for (const issue of issues) {
|
|
99
|
+
ready.push(`${issue.id}`);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Parse line-based format
|
|
103
|
+
const lines = readyOutput.split('\n').filter(l => l.trim());
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
const match = line.match(/^(bd-\d+)/);
|
|
106
|
+
if (match) ready.push(match[1]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// Ignore bd errors
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Get blocked issues
|
|
116
|
+
const listResult = await $`bd list --json`.quiet();
|
|
117
|
+
const listOutput = listResult.stdout.toString().trim();
|
|
118
|
+
if (listOutput) {
|
|
119
|
+
try {
|
|
120
|
+
const issues = JSON.parse(listOutput);
|
|
121
|
+
for (const issue of issues) {
|
|
122
|
+
if (issue.blockedBy && issue.blockedBy.length > 0) {
|
|
123
|
+
blocked.push(`${issue.id} (blocked by ${issue.blockedBy.join(', ')})`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Ignore parse errors
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Ignore bd errors
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { ready, blocked };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Format time elapsed
|
|
139
|
+
*/
|
|
140
|
+
function formatElapsed(startedAt: string): string {
|
|
141
|
+
const startTime = new Date(startedAt).getTime();
|
|
142
|
+
const ms = Date.now() - startTime;
|
|
143
|
+
const minutes = Math.floor(ms / 60000);
|
|
144
|
+
const hours = Math.floor(minutes / 60);
|
|
145
|
+
|
|
146
|
+
if (hours > 0) {
|
|
147
|
+
return `${hours}h ${minutes % 60}m`;
|
|
148
|
+
} else if (minutes > 0) {
|
|
149
|
+
return `${minutes}m`;
|
|
150
|
+
}
|
|
151
|
+
return '<1m';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// Main Command
|
|
156
|
+
// ============================================================================
|
|
157
|
+
|
|
158
|
+
export async function workersCommand(options: WorkersOptions = {}): Promise<void> {
|
|
159
|
+
try {
|
|
160
|
+
// Get workers from beads or JSON registry
|
|
161
|
+
// During transition, merge results from both
|
|
162
|
+
let workers: registry.Worker[] = [];
|
|
163
|
+
|
|
164
|
+
if (useBeads) {
|
|
165
|
+
try {
|
|
166
|
+
const beadsWorkers = await beadsRegistry.listWorkers();
|
|
167
|
+
workers = beadsWorkers;
|
|
168
|
+
} catch {
|
|
169
|
+
// Fallback to JSON registry
|
|
170
|
+
workers = await registry.list();
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
workers = await registry.list();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Also check JSON registry for any workers not in beads
|
|
177
|
+
const jsonWorkers = await registry.list();
|
|
178
|
+
const beadsIds = new Set(workers.map(w => w.id));
|
|
179
|
+
for (const jw of jsonWorkers) {
|
|
180
|
+
if (!beadsIds.has(jw.id)) {
|
|
181
|
+
workers.push(jw);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Gather display data for each worker
|
|
186
|
+
const displayData: WorkerDisplay[] = [];
|
|
187
|
+
|
|
188
|
+
for (const worker of workers) {
|
|
189
|
+
const alive = await isPaneAlive(worker.paneId);
|
|
190
|
+
let currentState = worker.state;
|
|
191
|
+
|
|
192
|
+
if (alive) {
|
|
193
|
+
// Get live state from pane
|
|
194
|
+
currentState = await getCurrentState(worker.paneId);
|
|
195
|
+
|
|
196
|
+
// Update both registries if state differs
|
|
197
|
+
const mappedState = mapDisplayStateToRegistry(currentState);
|
|
198
|
+
if (mappedState && mappedState !== worker.state) {
|
|
199
|
+
if (useBeads) {
|
|
200
|
+
// Update beads and send heartbeat
|
|
201
|
+
await beadsRegistry.updateState(worker.id, mappedState).catch(() => {});
|
|
202
|
+
}
|
|
203
|
+
await registry.updateState(worker.id, mappedState);
|
|
204
|
+
} else if (useBeads) {
|
|
205
|
+
// Just send heartbeat even if state unchanged
|
|
206
|
+
await beadsRegistry.heartbeat(worker.id).catch(() => {});
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
currentState = '💀 dead';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
displayData.push({
|
|
213
|
+
name: worker.id,
|
|
214
|
+
pane: worker.paneId,
|
|
215
|
+
task: worker.taskTitle
|
|
216
|
+
? `"${worker.taskTitle.substring(0, 25)}${worker.taskTitle.length > 25 ? '...' : ''}"`
|
|
217
|
+
: worker.taskId,
|
|
218
|
+
state: currentState,
|
|
219
|
+
time: formatElapsed(worker.startedAt),
|
|
220
|
+
alive,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Get queue status
|
|
225
|
+
const queue = await getQueueStatus();
|
|
226
|
+
|
|
227
|
+
// Filter out dead workers from ready count
|
|
228
|
+
const activeTaskIds = workers.filter(w => displayData.find(d => d.name === w.id && d.alive)).map(w => w.taskId);
|
|
229
|
+
const actuallyReady = queue.ready.filter(id => !activeTaskIds.includes(id));
|
|
230
|
+
|
|
231
|
+
if (options.json) {
|
|
232
|
+
console.log(JSON.stringify({
|
|
233
|
+
workers: displayData,
|
|
234
|
+
queue: {
|
|
235
|
+
ready: actuallyReady,
|
|
236
|
+
blocked: queue.blocked,
|
|
237
|
+
},
|
|
238
|
+
}, null, 2));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Display workers table
|
|
243
|
+
console.log('┌─────────────────────────────────────────────────────────────────┐');
|
|
244
|
+
console.log('│ WORKERS │');
|
|
245
|
+
console.log('├──────────┬──────────┬───────────────────────────┬──────────┬────┤');
|
|
246
|
+
console.log('│ Name │ Pane │ Task │ State │Time│');
|
|
247
|
+
console.log('├──────────┼──────────┼───────────────────────────┼──────────┼────┤');
|
|
248
|
+
|
|
249
|
+
if (displayData.length === 0) {
|
|
250
|
+
console.log('│ (no workers) │');
|
|
251
|
+
} else {
|
|
252
|
+
for (const w of displayData) {
|
|
253
|
+
const name = w.name.padEnd(8).substring(0, 8);
|
|
254
|
+
const pane = w.pane.padEnd(8).substring(0, 8);
|
|
255
|
+
const task = w.task.padEnd(25).substring(0, 25);
|
|
256
|
+
const state = w.state.padEnd(8).substring(0, 8);
|
|
257
|
+
const time = w.time.padStart(4).substring(0, 4);
|
|
258
|
+
console.log(`│ ${name} │ ${pane} │ ${task} │ ${state} │${time}│`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log('└──────────┴──────────┴───────────────────────────┴──────────┴────┘');
|
|
263
|
+
|
|
264
|
+
// Display queue
|
|
265
|
+
if (queue.blocked.length > 0) {
|
|
266
|
+
console.log(`\nBlocked: ${queue.blocked.slice(0, 5).join(', ')}${queue.blocked.length > 5 ? '...' : ''}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (actuallyReady.length > 0) {
|
|
270
|
+
console.log(`Ready: ${actuallyReady.slice(0, 5).join(', ')}${actuallyReady.length > 5 ? '...' : ''}`);
|
|
271
|
+
} else if (displayData.length > 0) {
|
|
272
|
+
console.log(`\nReady: (none - all assigned or blocked)`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Cleanup dead workers (optional - could prompt)
|
|
276
|
+
const deadWorkers = displayData.filter(w => !w.alive);
|
|
277
|
+
if (deadWorkers.length > 0) {
|
|
278
|
+
console.log(`\n⚠️ ${deadWorkers.length} dead worker(s) detected. Run \`term kill <name>\` to clean up.`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
} catch (error: any) {
|
|
282
|
+
console.error(`❌ Error: ${error.message}`);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Map display state to registry state
|
|
289
|
+
*/
|
|
290
|
+
function mapDisplayStateToRegistry(displayState: string): registry.WorkerState | null {
|
|
291
|
+
if (displayState === 'working') return 'working';
|
|
292
|
+
if (displayState === 'idle') return 'idle';
|
|
293
|
+
if (displayState === '⚠️ perm') return 'permission';
|
|
294
|
+
if (displayState === '⚠️ question') return 'question';
|
|
295
|
+
if (displayState === '❌ error') return 'error';
|
|
296
|
+
if (displayState === '✅ done') return 'done';
|
|
297
|
+
return null;
|
|
298
|
+
}
|
package/src/term.ts
CHANGED
|
@@ -15,6 +15,12 @@ import * as windowCmd from './term-commands/window.js';
|
|
|
15
15
|
import * as paneCmd from './term-commands/pane.js';
|
|
16
16
|
import * as statusCmd from './term-commands/status.js';
|
|
17
17
|
import * as shortcutsCmd from './term-commands/shortcuts.js';
|
|
18
|
+
import * as orchestrateCmd from './term-commands/orchestrate.js';
|
|
19
|
+
import * as workCmd from './term-commands/work.js';
|
|
20
|
+
import * as workersCmd from './term-commands/workers.js';
|
|
21
|
+
import * as closeCmd from './term-commands/close.js';
|
|
22
|
+
import * as killCmd from './term-commands/kill.js';
|
|
23
|
+
import * as daemonCmd from './term-commands/daemon.js';
|
|
18
24
|
|
|
19
25
|
const program = new Command();
|
|
20
26
|
|
|
@@ -28,7 +34,15 @@ Collaborative Usage:
|
|
|
28
34
|
AI reads: term read genie
|
|
29
35
|
|
|
30
36
|
Workflow: new → exec → read → rm
|
|
31
|
-
Full control: window new/ls/rm, pane ls/rm, split, status
|
|
37
|
+
Full control: window new/ls/rm, pane ls/rm, split, status
|
|
38
|
+
|
|
39
|
+
Worker Orchestration:
|
|
40
|
+
term work <bd-id> - Spawn worker bound to beads issue
|
|
41
|
+
term work next - Work on next ready issue
|
|
42
|
+
term workers - List all workers and states
|
|
43
|
+
term close <bd-id> - Close issue, cleanup worker
|
|
44
|
+
term kill <worker> - Force kill a stuck worker
|
|
45
|
+
term daemon start - Start beads daemon for auto-sync`)
|
|
32
46
|
.version(VERSION);
|
|
33
47
|
|
|
34
48
|
// Session management
|
|
@@ -200,4 +214,216 @@ program
|
|
|
200
214
|
await shortcutsCmd.handleShortcuts(options);
|
|
201
215
|
});
|
|
202
216
|
|
|
217
|
+
// Worker management commands (beads + Claude orchestration)
|
|
218
|
+
program
|
|
219
|
+
.command('work <target>')
|
|
220
|
+
.description('Spawn worker bound to beads issue (target: bd-id, "next", or "wish")')
|
|
221
|
+
.option('--no-worktree', 'Use shared repo instead of worktree')
|
|
222
|
+
.option('-s, --session <name>', 'Target tmux session')
|
|
223
|
+
.option('--no-focus', 'Don\'t focus the worker pane')
|
|
224
|
+
.option('-p, --prompt <message>', 'Custom initial prompt')
|
|
225
|
+
.action(async (target: string, options: workCmd.WorkOptions) => {
|
|
226
|
+
await workCmd.workCommand(target, options);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
program
|
|
230
|
+
.command('workers')
|
|
231
|
+
.description('List all workers and their states')
|
|
232
|
+
.option('--json', 'Output as JSON')
|
|
233
|
+
.option('-w, --watch', 'Live updates (coming soon)')
|
|
234
|
+
.action(async (options: workersCmd.WorkersOptions) => {
|
|
235
|
+
if (options.watch) {
|
|
236
|
+
console.log('ℹ️ --watch mode coming in Phase 1.5');
|
|
237
|
+
}
|
|
238
|
+
await workersCmd.workersCommand(options);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
program
|
|
242
|
+
.command('close <task-id>')
|
|
243
|
+
.description('Close beads issue and cleanup worker')
|
|
244
|
+
.option('--no-sync', 'Skip bd sync')
|
|
245
|
+
.option('--keep-worktree', 'Don\'t remove the worktree')
|
|
246
|
+
.option('--merge', 'Merge worktree changes to main branch')
|
|
247
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
248
|
+
.action(async (taskId: string, options: closeCmd.CloseOptions) => {
|
|
249
|
+
await closeCmd.closeCommand(taskId, options);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
program
|
|
253
|
+
.command('kill <worker>')
|
|
254
|
+
.description('Force kill a worker')
|
|
255
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
256
|
+
.option('--keep-worktree', 'Don\'t remove the worktree')
|
|
257
|
+
.action(async (worker: string, options: killCmd.KillOptions) => {
|
|
258
|
+
await killCmd.killCommand(worker, options);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Approve command (shortcut to orc approve for worker use)
|
|
262
|
+
program
|
|
263
|
+
.command('approve <worker>')
|
|
264
|
+
.description('Approve pending permission request for a worker')
|
|
265
|
+
.option('--deny', 'Deny instead of approve')
|
|
266
|
+
.action(async (worker: string, options: { deny?: boolean }) => {
|
|
267
|
+
// Find worker to get pane
|
|
268
|
+
const registry = await import('./lib/worker-registry.js');
|
|
269
|
+
let workerInfo = await registry.get(worker);
|
|
270
|
+
if (!workerInfo) {
|
|
271
|
+
workerInfo = await registry.findByTask(worker);
|
|
272
|
+
}
|
|
273
|
+
if (!workerInfo) {
|
|
274
|
+
console.error(`❌ Worker "${worker}" not found. Run \`term workers\` to see workers.`);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
await orchestrateCmd.approvePermission(workerInfo.session, {
|
|
278
|
+
pane: workerInfo.paneId,
|
|
279
|
+
deny: options.deny,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Answer command (shortcut to orc answer for worker use)
|
|
284
|
+
program
|
|
285
|
+
.command('answer <worker> <choice>')
|
|
286
|
+
.description('Answer a question for a worker (use "text:..." for text input)')
|
|
287
|
+
.action(async (worker: string, choice: string) => {
|
|
288
|
+
const registry = await import('./lib/worker-registry.js');
|
|
289
|
+
let workerInfo = await registry.get(worker);
|
|
290
|
+
if (!workerInfo) {
|
|
291
|
+
workerInfo = await registry.findByTask(worker);
|
|
292
|
+
}
|
|
293
|
+
if (!workerInfo) {
|
|
294
|
+
console.error(`❌ Worker "${worker}" not found. Run \`term workers\` to see workers.`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
await orchestrateCmd.answerQuestion(workerInfo.session, choice, {
|
|
298
|
+
pane: workerInfo.paneId,
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Daemon management (beads auto-sync)
|
|
303
|
+
const daemonProgram = program.command('daemon').description('Manage beads daemon for auto-sync');
|
|
304
|
+
|
|
305
|
+
daemonProgram
|
|
306
|
+
.command('start')
|
|
307
|
+
.description('Start beads daemon (auto-commit, auto-sync)')
|
|
308
|
+
.option('--no-auto-commit', 'Disable auto-commit')
|
|
309
|
+
.option('--auto-push', 'Enable auto-push to remote')
|
|
310
|
+
.action(async (options: daemonCmd.DaemonStartOptions) => {
|
|
311
|
+
await daemonCmd.startCommand(options);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
daemonProgram
|
|
315
|
+
.command('stop')
|
|
316
|
+
.description('Stop beads daemon')
|
|
317
|
+
.action(async () => {
|
|
318
|
+
await daemonCmd.stopCommand();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
daemonProgram
|
|
322
|
+
.command('status')
|
|
323
|
+
.description('Show daemon status')
|
|
324
|
+
.option('--json', 'Output as JSON')
|
|
325
|
+
.action(async (options: daemonCmd.DaemonStatusOptions) => {
|
|
326
|
+
await daemonCmd.statusCommand(options);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
daemonProgram
|
|
330
|
+
.command('restart')
|
|
331
|
+
.description('Restart beads daemon')
|
|
332
|
+
.option('--no-auto-commit', 'Disable auto-commit')
|
|
333
|
+
.option('--auto-push', 'Enable auto-push to remote')
|
|
334
|
+
.action(async (options: daemonCmd.DaemonStartOptions) => {
|
|
335
|
+
await daemonCmd.restartCommand(options);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Orchestration commands (Claude Code automation)
|
|
339
|
+
const orcProgram = program.command('orc').description('Orchestrate Claude Code sessions');
|
|
340
|
+
|
|
341
|
+
orcProgram
|
|
342
|
+
.command('start <session>')
|
|
343
|
+
.description('Start Claude Code in a session with optional monitoring')
|
|
344
|
+
.option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
345
|
+
.option('-m, --monitor', 'Enable real-time event monitoring')
|
|
346
|
+
.option('-c, --command <cmd>', 'Command to run instead of claude')
|
|
347
|
+
.option('--json', 'Output events as JSON')
|
|
348
|
+
.action(async (session: string, options: orchestrateCmd.StartOptions) => {
|
|
349
|
+
await orchestrateCmd.startSession(session, options);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
orcProgram
|
|
353
|
+
.command('send <session> <message>')
|
|
354
|
+
.description('Send message to Claude and track completion')
|
|
355
|
+
.option('--pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
356
|
+
.option('--method <name>', 'Completion detection method')
|
|
357
|
+
.option('-t, --timeout <ms>', 'Timeout in milliseconds')
|
|
358
|
+
.option('--no-wait', 'Send without waiting for completion')
|
|
359
|
+
.option('--json', 'Output as JSON')
|
|
360
|
+
.action(async (session: string, message: string, options: orchestrateCmd.SendOptions) => {
|
|
361
|
+
await orchestrateCmd.sendMessage(session, message, options);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
orcProgram
|
|
365
|
+
.command('status <session>')
|
|
366
|
+
.description('Show current Claude state and details')
|
|
367
|
+
.option('--pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
368
|
+
.option('--json', 'Output as JSON')
|
|
369
|
+
.action(async (session: string, options: orchestrateCmd.StatusOptions) => {
|
|
370
|
+
await orchestrateCmd.showStatus(session, options);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
orcProgram
|
|
374
|
+
.command('watch <session>')
|
|
375
|
+
.description('Watch session events in real-time')
|
|
376
|
+
.option('--pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
377
|
+
.option('--json', 'Output events as JSON')
|
|
378
|
+
.option('-p, --poll <ms>', 'Poll interval in milliseconds')
|
|
379
|
+
.action(async (session: string, options: orchestrateCmd.WatchOptions) => {
|
|
380
|
+
await orchestrateCmd.watchSession(session, options);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
orcProgram
|
|
384
|
+
.command('approve <session>')
|
|
385
|
+
.description('Approve pending permission request')
|
|
386
|
+
.option('-p, --pane <id>', 'Specific pane ID to target')
|
|
387
|
+
.option('--auto', 'Auto-approve all future permissions (dangerous!)')
|
|
388
|
+
.option('--deny', 'Deny instead of approve')
|
|
389
|
+
.action(async (session: string, options: orchestrateCmd.ApproveOptions) => {
|
|
390
|
+
await orchestrateCmd.approvePermission(session, options);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
orcProgram
|
|
394
|
+
.command('answer <session> <choice>')
|
|
395
|
+
.description('Answer a question with the given choice (use "text:..." to send feedback)')
|
|
396
|
+
.option('-p, --pane <id>', 'Specific pane ID to target')
|
|
397
|
+
.action(async (session: string, choice: string, options: { pane?: string }) => {
|
|
398
|
+
await orchestrateCmd.answerQuestion(session, choice, options);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
orcProgram
|
|
402
|
+
.command('experiment <method>')
|
|
403
|
+
.description('Test a completion detection method')
|
|
404
|
+
.option('-n, --runs <number>', 'Number of test runs')
|
|
405
|
+
.option('--task <command>', 'Test command to run')
|
|
406
|
+
.option('--json', 'Output as JSON')
|
|
407
|
+
.action(async (method: string, options: orchestrateCmd.ExperimentOptions) => {
|
|
408
|
+
await orchestrateCmd.runExperiment(method, options);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
orcProgram
|
|
412
|
+
.command('methods')
|
|
413
|
+
.description('List available completion detection methods')
|
|
414
|
+
.action(async () => {
|
|
415
|
+
await orchestrateCmd.listMethods();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
orcProgram
|
|
419
|
+
.command('run <session> <message>')
|
|
420
|
+
.description('Send task and auto-approve until idle (fire-and-forget)')
|
|
421
|
+
.option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
422
|
+
.option('-a, --auto-approve', 'Auto-approve permissions and plans')
|
|
423
|
+
.option('-t, --timeout <ms>', 'Timeout in milliseconds (default: 300000)')
|
|
424
|
+
.option('--json', 'Output final state as JSON')
|
|
425
|
+
.action(async (session: string, message: string, options: orchestrateCmd.RunOptions) => {
|
|
426
|
+
await orchestrateCmd.runTask(session, message, options);
|
|
427
|
+
});
|
|
428
|
+
|
|
203
429
|
program.parse();
|