@automagik/genie 0.260202.530 → 0.260202.1833
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/claudio.js +44 -45
- package/dist/genie.js +58 -135
- package/dist/term.js +134 -67
- package/install.sh +43 -7
- package/package.json +1 -1
- package/src/claudio.ts +31 -21
- package/src/commands/launch.ts +12 -68
- package/src/genie-commands/doctor.ts +327 -0
- package/src/genie-commands/setup.ts +317 -199
- package/src/genie-commands/uninstall.ts +176 -0
- package/src/genie.ts +24 -44
- package/src/lib/claude-settings.ts +22 -64
- package/src/lib/genie-config.ts +169 -57
- 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/version.ts +1 -1
- package/src/lib/worker-registry.ts +229 -0
- package/src/term-commands/close.ts +221 -0
- package/src/term-commands/exec.ts +28 -6
- package/src/term-commands/kill.ts +143 -0
- package/src/term-commands/orchestrate.ts +844 -0
- package/src/term-commands/read.ts +6 -1
- package/src/term-commands/shortcuts.ts +14 -14
- package/src/term-commands/work.ts +415 -0
- package/src/term-commands/workers.ts +264 -0
- package/src/term.ts +201 -3
- package/src/types/genie-config.ts +49 -81
- package/src/genie-commands/hooks.ts +0 -317
- package/src/lib/hook-script.ts +0 -263
- package/src/lib/hooks/compose.ts +0 -72
- package/src/lib/hooks/index.ts +0 -163
- package/src/lib/hooks/presets/audited.ts +0 -191
- package/src/lib/hooks/presets/collaborative.ts +0 -143
- package/src/lib/hooks/presets/sandboxed.ts +0 -153
- package/src/lib/hooks/presets/supervised.ts +0 -66
- package/src/lib/hooks/utils/escape.ts +0 -46
|
@@ -0,0 +1,264 @@
|
|
|
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 { detectState, stripAnsi } from '../lib/orchestrator/index.js';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface WorkersOptions {
|
|
20
|
+
json?: boolean;
|
|
21
|
+
watch?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface WorkerDisplay {
|
|
25
|
+
name: string;
|
|
26
|
+
pane: string;
|
|
27
|
+
task: string;
|
|
28
|
+
state: string;
|
|
29
|
+
time: string;
|
|
30
|
+
alive: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Helper Functions
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if a pane still exists
|
|
39
|
+
*/
|
|
40
|
+
async function isPaneAlive(paneId: string): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
await tmux.capturePaneContent(paneId, 1);
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get current state from pane output
|
|
51
|
+
*/
|
|
52
|
+
async function getCurrentState(paneId: string): Promise<string> {
|
|
53
|
+
try {
|
|
54
|
+
const output = await tmux.capturePaneContent(paneId, 30);
|
|
55
|
+
const state = detectState(output);
|
|
56
|
+
|
|
57
|
+
// Map to display format
|
|
58
|
+
switch (state.type) {
|
|
59
|
+
case 'working':
|
|
60
|
+
case 'tool_use':
|
|
61
|
+
return 'working';
|
|
62
|
+
case 'idle':
|
|
63
|
+
return 'idle';
|
|
64
|
+
case 'permission':
|
|
65
|
+
return '⚠️ perm';
|
|
66
|
+
case 'question':
|
|
67
|
+
return '⚠️ question';
|
|
68
|
+
case 'error':
|
|
69
|
+
return '❌ error';
|
|
70
|
+
case 'complete':
|
|
71
|
+
return '✅ done';
|
|
72
|
+
default:
|
|
73
|
+
return state.type;
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
return 'unknown';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get beads queue status
|
|
82
|
+
*/
|
|
83
|
+
async function getQueueStatus(): Promise<{ ready: string[]; blocked: string[] }> {
|
|
84
|
+
const ready: string[] = [];
|
|
85
|
+
const blocked: string[] = [];
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Get ready issues
|
|
89
|
+
const readyResult = await $`bd ready --json`.quiet();
|
|
90
|
+
const readyOutput = readyResult.stdout.toString().trim();
|
|
91
|
+
if (readyOutput) {
|
|
92
|
+
try {
|
|
93
|
+
const issues = JSON.parse(readyOutput);
|
|
94
|
+
for (const issue of issues) {
|
|
95
|
+
ready.push(`${issue.id}`);
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Parse line-based format
|
|
99
|
+
const lines = readyOutput.split('\n').filter(l => l.trim());
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
const match = line.match(/^(bd-\d+)/);
|
|
102
|
+
if (match) ready.push(match[1]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore bd errors
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Get blocked issues
|
|
112
|
+
const listResult = await $`bd list --json`.quiet();
|
|
113
|
+
const listOutput = listResult.stdout.toString().trim();
|
|
114
|
+
if (listOutput) {
|
|
115
|
+
try {
|
|
116
|
+
const issues = JSON.parse(listOutput);
|
|
117
|
+
for (const issue of issues) {
|
|
118
|
+
if (issue.blockedBy && issue.blockedBy.length > 0) {
|
|
119
|
+
blocked.push(`${issue.id} (blocked by ${issue.blockedBy.join(', ')})`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Ignore parse errors
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Ignore bd errors
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { ready, blocked };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format time elapsed
|
|
135
|
+
*/
|
|
136
|
+
function formatElapsed(startedAt: string): string {
|
|
137
|
+
const startTime = new Date(startedAt).getTime();
|
|
138
|
+
const ms = Date.now() - startTime;
|
|
139
|
+
const minutes = Math.floor(ms / 60000);
|
|
140
|
+
const hours = Math.floor(minutes / 60);
|
|
141
|
+
|
|
142
|
+
if (hours > 0) {
|
|
143
|
+
return `${hours}h ${minutes % 60}m`;
|
|
144
|
+
} else if (minutes > 0) {
|
|
145
|
+
return `${minutes}m`;
|
|
146
|
+
}
|
|
147
|
+
return '<1m';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Main Command
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
export async function workersCommand(options: WorkersOptions = {}): Promise<void> {
|
|
155
|
+
try {
|
|
156
|
+
const workers = await registry.list();
|
|
157
|
+
|
|
158
|
+
// Gather display data for each worker
|
|
159
|
+
const displayData: WorkerDisplay[] = [];
|
|
160
|
+
|
|
161
|
+
for (const worker of workers) {
|
|
162
|
+
const alive = await isPaneAlive(worker.paneId);
|
|
163
|
+
let currentState = worker.state;
|
|
164
|
+
|
|
165
|
+
if (alive) {
|
|
166
|
+
// Get live state from pane
|
|
167
|
+
currentState = await getCurrentState(worker.paneId);
|
|
168
|
+
|
|
169
|
+
// Update registry if state differs
|
|
170
|
+
const mappedState = mapDisplayStateToRegistry(currentState);
|
|
171
|
+
if (mappedState && mappedState !== worker.state) {
|
|
172
|
+
await registry.updateState(worker.id, mappedState);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
currentState = '💀 dead';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
displayData.push({
|
|
179
|
+
name: worker.id,
|
|
180
|
+
pane: worker.paneId,
|
|
181
|
+
task: worker.taskTitle
|
|
182
|
+
? `"${worker.taskTitle.substring(0, 25)}${worker.taskTitle.length > 25 ? '...' : ''}"`
|
|
183
|
+
: worker.taskId,
|
|
184
|
+
state: currentState,
|
|
185
|
+
time: formatElapsed(worker.startedAt),
|
|
186
|
+
alive,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Get queue status
|
|
191
|
+
const queue = await getQueueStatus();
|
|
192
|
+
|
|
193
|
+
// Filter out dead workers from ready count
|
|
194
|
+
const activeTaskIds = workers.filter(w => displayData.find(d => d.name === w.id && d.alive)).map(w => w.taskId);
|
|
195
|
+
const actuallyReady = queue.ready.filter(id => !activeTaskIds.includes(id));
|
|
196
|
+
|
|
197
|
+
if (options.json) {
|
|
198
|
+
console.log(JSON.stringify({
|
|
199
|
+
workers: displayData,
|
|
200
|
+
queue: {
|
|
201
|
+
ready: actuallyReady,
|
|
202
|
+
blocked: queue.blocked,
|
|
203
|
+
},
|
|
204
|
+
}, null, 2));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Display workers table
|
|
209
|
+
console.log('┌─────────────────────────────────────────────────────────────────┐');
|
|
210
|
+
console.log('│ WORKERS │');
|
|
211
|
+
console.log('├──────────┬──────────┬───────────────────────────┬──────────┬────┤');
|
|
212
|
+
console.log('│ Name │ Pane │ Task │ State │Time│');
|
|
213
|
+
console.log('├──────────┼──────────┼───────────────────────────┼──────────┼────┤');
|
|
214
|
+
|
|
215
|
+
if (displayData.length === 0) {
|
|
216
|
+
console.log('│ (no workers) │');
|
|
217
|
+
} else {
|
|
218
|
+
for (const w of displayData) {
|
|
219
|
+
const name = w.name.padEnd(8).substring(0, 8);
|
|
220
|
+
const pane = w.pane.padEnd(8).substring(0, 8);
|
|
221
|
+
const task = w.task.padEnd(25).substring(0, 25);
|
|
222
|
+
const state = w.state.padEnd(8).substring(0, 8);
|
|
223
|
+
const time = w.time.padStart(4).substring(0, 4);
|
|
224
|
+
console.log(`│ ${name} │ ${pane} │ ${task} │ ${state} │${time}│`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log('└──────────┴──────────┴───────────────────────────┴──────────┴────┘');
|
|
229
|
+
|
|
230
|
+
// Display queue
|
|
231
|
+
if (queue.blocked.length > 0) {
|
|
232
|
+
console.log(`\nBlocked: ${queue.blocked.slice(0, 5).join(', ')}${queue.blocked.length > 5 ? '...' : ''}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (actuallyReady.length > 0) {
|
|
236
|
+
console.log(`Ready: ${actuallyReady.slice(0, 5).join(', ')}${actuallyReady.length > 5 ? '...' : ''}`);
|
|
237
|
+
} else if (displayData.length > 0) {
|
|
238
|
+
console.log(`\nReady: (none - all assigned or blocked)`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Cleanup dead workers (optional - could prompt)
|
|
242
|
+
const deadWorkers = displayData.filter(w => !w.alive);
|
|
243
|
+
if (deadWorkers.length > 0) {
|
|
244
|
+
console.log(`\n⚠️ ${deadWorkers.length} dead worker(s) detected. Run \`term kill <name>\` to clean up.`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
} catch (error: any) {
|
|
248
|
+
console.error(`❌ Error: ${error.message}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Map display state to registry state
|
|
255
|
+
*/
|
|
256
|
+
function mapDisplayStateToRegistry(displayState: string): registry.WorkerState | null {
|
|
257
|
+
if (displayState === 'working') return 'working';
|
|
258
|
+
if (displayState === 'idle') return 'idle';
|
|
259
|
+
if (displayState === '⚠️ perm') return 'permission';
|
|
260
|
+
if (displayState === '⚠️ question') return 'question';
|
|
261
|
+
if (displayState === '❌ error') return 'error';
|
|
262
|
+
if (displayState === '✅ done') return 'done';
|
|
263
|
+
return null;
|
|
264
|
+
}
|
package/src/term.ts
CHANGED
|
@@ -15,6 +15,11 @@ 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';
|
|
18
23
|
|
|
19
24
|
const program = new Command();
|
|
20
25
|
|
|
@@ -22,8 +27,20 @@ program
|
|
|
22
27
|
.name('term')
|
|
23
28
|
.description(`AI-friendly terminal orchestration (tmux wrapper)
|
|
24
29
|
|
|
30
|
+
Collaborative Usage:
|
|
31
|
+
AI runs: term exec genie:shell '<command>'
|
|
32
|
+
Human watches: tmux attach -t genie
|
|
33
|
+
AI reads: term read genie
|
|
34
|
+
|
|
25
35
|
Workflow: new → exec → read → rm
|
|
26
|
-
Full control: window new/ls/rm, pane ls/rm, split, status
|
|
36
|
+
Full control: window new/ls/rm, pane ls/rm, split, status
|
|
37
|
+
|
|
38
|
+
Worker Orchestration:
|
|
39
|
+
term work <bd-id> - Spawn worker bound to beads issue
|
|
40
|
+
term work next - Work on next ready issue
|
|
41
|
+
term workers - List all workers and states
|
|
42
|
+
term close <bd-id> - Close issue, cleanup worker
|
|
43
|
+
term kill <worker> - Force kill a stuck worker`)
|
|
27
44
|
.version(VERSION);
|
|
28
45
|
|
|
29
46
|
// Session management
|
|
@@ -81,8 +98,13 @@ program
|
|
|
81
98
|
program
|
|
82
99
|
.command('exec <session> <command...>')
|
|
83
100
|
.description('Execute command in a tmux session')
|
|
84
|
-
.
|
|
85
|
-
|
|
101
|
+
.option('-q, --quiet', 'Suppress stdout output')
|
|
102
|
+
.option('-t, --timeout <ms>', 'Timeout in milliseconds (default: 120000)')
|
|
103
|
+
.action(async (session: string, command: string[], options: { quiet?: boolean; timeout?: string }) => {
|
|
104
|
+
await execCmd.executeInSession(session, command.join(' '), {
|
|
105
|
+
quiet: options.quiet,
|
|
106
|
+
timeout: options.timeout ? parseInt(options.timeout, 10) : undefined,
|
|
107
|
+
});
|
|
86
108
|
});
|
|
87
109
|
|
|
88
110
|
program
|
|
@@ -190,4 +212,180 @@ program
|
|
|
190
212
|
await shortcutsCmd.handleShortcuts(options);
|
|
191
213
|
});
|
|
192
214
|
|
|
215
|
+
// Worker management commands (beads + Claude orchestration)
|
|
216
|
+
program
|
|
217
|
+
.command('work <target>')
|
|
218
|
+
.description('Spawn worker bound to beads issue (target: bd-id, "next", or "wish")')
|
|
219
|
+
.option('--no-worktree', 'Use shared repo instead of worktree')
|
|
220
|
+
.option('-s, --session <name>', 'Target tmux session')
|
|
221
|
+
.option('--no-focus', 'Don\'t focus the worker pane')
|
|
222
|
+
.option('-p, --prompt <message>', 'Custom initial prompt')
|
|
223
|
+
.action(async (target: string, options: workCmd.WorkOptions) => {
|
|
224
|
+
await workCmd.workCommand(target, options);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
program
|
|
228
|
+
.command('workers')
|
|
229
|
+
.description('List all workers and their states')
|
|
230
|
+
.option('--json', 'Output as JSON')
|
|
231
|
+
.option('-w, --watch', 'Live updates (coming soon)')
|
|
232
|
+
.action(async (options: workersCmd.WorkersOptions) => {
|
|
233
|
+
if (options.watch) {
|
|
234
|
+
console.log('ℹ️ --watch mode coming in Phase 1.5');
|
|
235
|
+
}
|
|
236
|
+
await workersCmd.workersCommand(options);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
program
|
|
240
|
+
.command('close <task-id>')
|
|
241
|
+
.description('Close beads issue and cleanup worker')
|
|
242
|
+
.option('--no-sync', 'Skip bd sync')
|
|
243
|
+
.option('--keep-worktree', 'Don\'t remove the worktree')
|
|
244
|
+
.option('--merge', 'Merge worktree changes to main branch')
|
|
245
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
246
|
+
.action(async (taskId: string, options: closeCmd.CloseOptions) => {
|
|
247
|
+
await closeCmd.closeCommand(taskId, options);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
program
|
|
251
|
+
.command('kill <worker>')
|
|
252
|
+
.description('Force kill a worker')
|
|
253
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
254
|
+
.option('--keep-worktree', 'Don\'t remove the worktree')
|
|
255
|
+
.action(async (worker: string, options: killCmd.KillOptions) => {
|
|
256
|
+
await killCmd.killCommand(worker, options);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Approve command (shortcut to orc approve for worker use)
|
|
260
|
+
program
|
|
261
|
+
.command('approve <worker>')
|
|
262
|
+
.description('Approve pending permission request for a worker')
|
|
263
|
+
.option('--deny', 'Deny instead of approve')
|
|
264
|
+
.action(async (worker: string, options: { deny?: boolean }) => {
|
|
265
|
+
// Find worker to get pane
|
|
266
|
+
const registry = await import('./lib/worker-registry.js');
|
|
267
|
+
let workerInfo = await registry.get(worker);
|
|
268
|
+
if (!workerInfo) {
|
|
269
|
+
workerInfo = await registry.findByTask(worker);
|
|
270
|
+
}
|
|
271
|
+
if (!workerInfo) {
|
|
272
|
+
console.error(`❌ Worker "${worker}" not found. Run \`term workers\` to see workers.`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
await orchestrateCmd.approvePermission(workerInfo.session, {
|
|
276
|
+
pane: workerInfo.paneId,
|
|
277
|
+
deny: options.deny,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Answer command (shortcut to orc answer for worker use)
|
|
282
|
+
program
|
|
283
|
+
.command('answer <worker> <choice>')
|
|
284
|
+
.description('Answer a question for a worker (use "text:..." for text input)')
|
|
285
|
+
.action(async (worker: string, choice: string) => {
|
|
286
|
+
const registry = await import('./lib/worker-registry.js');
|
|
287
|
+
let workerInfo = await registry.get(worker);
|
|
288
|
+
if (!workerInfo) {
|
|
289
|
+
workerInfo = await registry.findByTask(worker);
|
|
290
|
+
}
|
|
291
|
+
if (!workerInfo) {
|
|
292
|
+
console.error(`❌ Worker "${worker}" not found. Run \`term workers\` to see workers.`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
await orchestrateCmd.answerQuestion(workerInfo.session, choice, {
|
|
296
|
+
pane: workerInfo.paneId,
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Orchestration commands (Claude Code automation)
|
|
301
|
+
const orcProgram = program.command('orc').description('Orchestrate Claude Code sessions');
|
|
302
|
+
|
|
303
|
+
orcProgram
|
|
304
|
+
.command('start <session>')
|
|
305
|
+
.description('Start Claude Code in a session with optional monitoring')
|
|
306
|
+
.option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
307
|
+
.option('-m, --monitor', 'Enable real-time event monitoring')
|
|
308
|
+
.option('-c, --command <cmd>', 'Command to run instead of claude')
|
|
309
|
+
.option('--json', 'Output events as JSON')
|
|
310
|
+
.action(async (session: string, options: orchestrateCmd.StartOptions) => {
|
|
311
|
+
await orchestrateCmd.startSession(session, options);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
orcProgram
|
|
315
|
+
.command('send <session> <message>')
|
|
316
|
+
.description('Send message to Claude and track completion')
|
|
317
|
+
.option('--pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
318
|
+
.option('--method <name>', 'Completion detection method')
|
|
319
|
+
.option('-t, --timeout <ms>', 'Timeout in milliseconds')
|
|
320
|
+
.option('--no-wait', 'Send without waiting for completion')
|
|
321
|
+
.option('--json', 'Output as JSON')
|
|
322
|
+
.action(async (session: string, message: string, options: orchestrateCmd.SendOptions) => {
|
|
323
|
+
await orchestrateCmd.sendMessage(session, message, options);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
orcProgram
|
|
327
|
+
.command('status <session>')
|
|
328
|
+
.description('Show current Claude state and details')
|
|
329
|
+
.option('--pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
330
|
+
.option('--json', 'Output as JSON')
|
|
331
|
+
.action(async (session: string, options: orchestrateCmd.StatusOptions) => {
|
|
332
|
+
await orchestrateCmd.showStatus(session, options);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
orcProgram
|
|
336
|
+
.command('watch <session>')
|
|
337
|
+
.description('Watch session events in real-time')
|
|
338
|
+
.option('--pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
339
|
+
.option('--json', 'Output events as JSON')
|
|
340
|
+
.option('-p, --poll <ms>', 'Poll interval in milliseconds')
|
|
341
|
+
.action(async (session: string, options: orchestrateCmd.WatchOptions) => {
|
|
342
|
+
await orchestrateCmd.watchSession(session, options);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
orcProgram
|
|
346
|
+
.command('approve <session>')
|
|
347
|
+
.description('Approve pending permission request')
|
|
348
|
+
.option('-p, --pane <id>', 'Specific pane ID to target')
|
|
349
|
+
.option('--auto', 'Auto-approve all future permissions (dangerous!)')
|
|
350
|
+
.option('--deny', 'Deny instead of approve')
|
|
351
|
+
.action(async (session: string, options: orchestrateCmd.ApproveOptions) => {
|
|
352
|
+
await orchestrateCmd.approvePermission(session, options);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
orcProgram
|
|
356
|
+
.command('answer <session> <choice>')
|
|
357
|
+
.description('Answer a question with the given choice (use "text:..." to send feedback)')
|
|
358
|
+
.option('-p, --pane <id>', 'Specific pane ID to target')
|
|
359
|
+
.action(async (session: string, choice: string, options: { pane?: string }) => {
|
|
360
|
+
await orchestrateCmd.answerQuestion(session, choice, options);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
orcProgram
|
|
364
|
+
.command('experiment <method>')
|
|
365
|
+
.description('Test a completion detection method')
|
|
366
|
+
.option('-n, --runs <number>', 'Number of test runs')
|
|
367
|
+
.option('--task <command>', 'Test command to run')
|
|
368
|
+
.option('--json', 'Output as JSON')
|
|
369
|
+
.action(async (method: string, options: orchestrateCmd.ExperimentOptions) => {
|
|
370
|
+
await orchestrateCmd.runExperiment(method, options);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
orcProgram
|
|
374
|
+
.command('methods')
|
|
375
|
+
.description('List available completion detection methods')
|
|
376
|
+
.action(async () => {
|
|
377
|
+
await orchestrateCmd.listMethods();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
orcProgram
|
|
381
|
+
.command('run <session> <message>')
|
|
382
|
+
.description('Send task and auto-approve until idle (fire-and-forget)')
|
|
383
|
+
.option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
|
|
384
|
+
.option('-a, --auto-approve', 'Auto-approve permissions and plans')
|
|
385
|
+
.option('-t, --timeout <ms>', 'Timeout in milliseconds (default: 300000)')
|
|
386
|
+
.option('--json', 'Output final state as JSON')
|
|
387
|
+
.action(async (session: string, message: string, options: orchestrateCmd.RunOptions) => {
|
|
388
|
+
await orchestrateCmd.runTask(session, message, options);
|
|
389
|
+
});
|
|
390
|
+
|
|
193
391
|
program.parse();
|
|
@@ -1,112 +1,80 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Genie Configuration Schema
|
|
4
|
+
* Genie Configuration Schema v2
|
|
5
5
|
*
|
|
6
6
|
* Stored at ~/.genie/config.json
|
|
7
|
-
* Manages
|
|
7
|
+
* Manages session configuration, terminal defaults, and shortcuts for the genie CLI.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
export const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Audited preset configuration
|
|
16
|
-
export const AuditedConfigSchema = z.object({
|
|
17
|
-
logPath: z.string().default('~/.genie/audit.log'),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Supervised preset configuration
|
|
21
|
-
export const SupervisedConfigSchema = z.object({
|
|
22
|
-
alwaysAsk: z.array(z.string()).default(['Write', 'Edit']),
|
|
10
|
+
// Session configuration
|
|
11
|
+
export const SessionConfigSchema = z.object({
|
|
12
|
+
name: z.string().default('genie'),
|
|
13
|
+
defaultWindow: z.string().default('shell'),
|
|
14
|
+
autoCreate: z.boolean().default(true),
|
|
23
15
|
});
|
|
24
16
|
|
|
25
|
-
//
|
|
26
|
-
export const
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
// Terminal configuration
|
|
18
|
+
export const TerminalConfigSchema = z.object({
|
|
19
|
+
execTimeout: z.number().default(120000),
|
|
20
|
+
readLines: z.number().default(100),
|
|
21
|
+
worktreeBase: z.string().default('.worktrees'),
|
|
29
22
|
});
|
|
30
23
|
|
|
31
24
|
// Logging configuration
|
|
32
25
|
export const LoggingConfigSchema = z.object({
|
|
33
26
|
tmuxDebug: z.boolean().default(false),
|
|
27
|
+
verbose: z.boolean().default(false),
|
|
34
28
|
});
|
|
35
29
|
|
|
36
|
-
//
|
|
37
|
-
export const
|
|
38
|
-
|
|
39
|
-
collaborative: CollaborativeConfigSchema.optional(),
|
|
40
|
-
supervised: SupervisedConfigSchema.optional(),
|
|
41
|
-
sandboxed: SandboxedConfigSchema.optional(),
|
|
42
|
-
audited: AuditedConfigSchema.optional(),
|
|
30
|
+
// Shell configuration
|
|
31
|
+
export const ShellConfigSchema = z.object({
|
|
32
|
+
preference: z.enum(['auto', 'zsh', 'bash', 'fish']).default('auto'),
|
|
43
33
|
});
|
|
44
34
|
|
|
45
|
-
//
|
|
46
|
-
export const
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
// Shortcuts configuration
|
|
36
|
+
export const ShortcutsConfigSchema = z.object({
|
|
37
|
+
tmuxInstalled: z.boolean().default(false),
|
|
38
|
+
shellInstalled: z.boolean().default(false),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Claudio integration configuration
|
|
42
|
+
export const ClaudioConfigSchema = z.object({
|
|
43
|
+
enabled: z.boolean().default(false),
|
|
49
44
|
});
|
|
50
45
|
|
|
51
46
|
// Full genie configuration
|
|
52
47
|
export const GenieConfigSchema = z.object({
|
|
53
|
-
|
|
48
|
+
version: z.number().default(2),
|
|
54
49
|
session: SessionConfigSchema.default({}),
|
|
50
|
+
terminal: TerminalConfigSchema.default({}),
|
|
55
51
|
logging: LoggingConfigSchema.default({}),
|
|
52
|
+
shell: ShellConfigSchema.default({}),
|
|
53
|
+
shortcuts: ShortcutsConfigSchema.default({}),
|
|
54
|
+
claudio: ClaudioConfigSchema.optional(),
|
|
55
|
+
installMethod: z.enum(['source', 'npm', 'bun']).optional(),
|
|
56
|
+
setupComplete: z.boolean().default(false),
|
|
57
|
+
lastSetupAt: z.string().optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Legacy v1 config schema (for migration)
|
|
61
|
+
export const GenieConfigV1Schema = z.object({
|
|
62
|
+
session: z.object({
|
|
63
|
+
name: z.string().default('genie'),
|
|
64
|
+
defaultWindow: z.string().default('shell'),
|
|
65
|
+
}).default({}),
|
|
66
|
+
logging: z.object({
|
|
67
|
+
tmuxDebug: z.boolean().default(false),
|
|
68
|
+
}).default({}),
|
|
56
69
|
installMethod: z.enum(['source', 'npm', 'bun']).optional(),
|
|
57
70
|
});
|
|
58
71
|
|
|
59
72
|
// Inferred types
|
|
60
|
-
export type SandboxedConfig = z.infer<typeof SandboxedConfigSchema>;
|
|
61
|
-
export type AuditedConfig = z.infer<typeof AuditedConfigSchema>;
|
|
62
|
-
export type SupervisedConfig = z.infer<typeof SupervisedConfigSchema>;
|
|
63
|
-
export type CollaborativeConfig = z.infer<typeof CollaborativeConfigSchema>;
|
|
64
|
-
export type HooksConfig = z.infer<typeof HooksConfigSchema>;
|
|
65
73
|
export type SessionConfig = z.infer<typeof SessionConfigSchema>;
|
|
74
|
+
export type TerminalConfig = z.infer<typeof TerminalConfigSchema>;
|
|
66
75
|
export type LoggingConfig = z.infer<typeof LoggingConfigSchema>;
|
|
76
|
+
export type ShellConfig = z.infer<typeof ShellConfigSchema>;
|
|
77
|
+
export type ShortcutsConfig = z.infer<typeof ShortcutsConfigSchema>;
|
|
78
|
+
export type ClaudioConfig = z.infer<typeof ClaudioConfigSchema>;
|
|
67
79
|
export type GenieConfig = z.infer<typeof GenieConfigSchema>;
|
|
68
|
-
|
|
69
|
-
// Preset names type
|
|
70
|
-
export type PresetName = 'collaborative' | 'supervised' | 'sandboxed' | 'audited';
|
|
71
|
-
|
|
72
|
-
// Preset description for UI
|
|
73
|
-
export interface PresetDescription {
|
|
74
|
-
name: PresetName;
|
|
75
|
-
title: string;
|
|
76
|
-
what: string;
|
|
77
|
-
why: string;
|
|
78
|
-
how: string;
|
|
79
|
-
recommended?: boolean;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export const PRESET_DESCRIPTIONS: PresetDescription[] = [
|
|
83
|
-
{
|
|
84
|
-
name: 'collaborative',
|
|
85
|
-
title: 'Collaborative',
|
|
86
|
-
what: 'All terminal commands run through tmux',
|
|
87
|
-
why: 'You can watch AI work in real-time',
|
|
88
|
-
how: 'Bash commands → term exec genie:shell',
|
|
89
|
-
recommended: true,
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: 'supervised',
|
|
93
|
-
title: 'Supervised',
|
|
94
|
-
what: 'File changes require your approval',
|
|
95
|
-
why: 'Prevents accidental overwrites',
|
|
96
|
-
how: 'Write/Edit tools always ask permission',
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'sandboxed',
|
|
100
|
-
title: 'Sandboxed',
|
|
101
|
-
what: 'Restrict file access to specific directories',
|
|
102
|
-
why: 'Protects sensitive areas of your system',
|
|
103
|
-
how: 'Operations outside sandbox are blocked',
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: 'audited',
|
|
107
|
-
title: 'Audited',
|
|
108
|
-
what: 'Log all AI tool usage to a file',
|
|
109
|
-
why: 'Review what the AI did after a session',
|
|
110
|
-
how: 'Every tool call → ~/.genie/audit.log',
|
|
111
|
-
},
|
|
112
|
-
];
|
|
80
|
+
export type GenieConfigV1 = z.infer<typeof GenieConfigV1Schema>;
|