@automagik/genie 0.260202.1901 → 0.260203.135

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.
Files changed (104) hide show
  1. package/.beads/issues.jsonl +9 -0
  2. package/.claude/skills/brainstorm/SKILL.md +53 -0
  3. package/.claude/skills/genie-base/SKILL.md +66 -0
  4. package/.claude/skills/genie-base/assets/workspace/AGENTS.md +191 -0
  5. package/.claude/skills/genie-base/assets/workspace/ENVIRONMENT.md +18 -0
  6. package/.claude/skills/genie-base/assets/workspace/HEARTBEAT.md +4 -0
  7. package/.claude/skills/genie-base/assets/workspace/IDENTITY.md +17 -0
  8. package/.claude/skills/genie-base/assets/workspace/MEMORY.md +16 -0
  9. package/.claude/skills/genie-base/assets/workspace/ROLE.md +14 -0
  10. package/.claude/skills/genie-base/assets/workspace/SOUL.md +36 -0
  11. package/.claude/skills/genie-base/assets/workspace/TOOLS.md +25 -0
  12. package/.claude/skills/genie-base/assets/workspace/USER.md +13 -0
  13. package/.claude/skills/genie-base/assets/workspace/memory/2026-01-30.md +6 -0
  14. package/.claude/skills/genie-base/assets/workspace/memory/2026-01-31.md +16 -0
  15. package/.claude/skills/genie-base/assets/workspace/memory/882c22be-9710-41c1-91f8-ed82947ef6ce.txt +1 -0
  16. package/.claude/skills/genie-base/scripts/install-workspace.sh +107 -0
  17. package/.claude/skills/genie-base/scripts/sanity-sweep.sh +60 -0
  18. package/.claude/skills/genie-blank-init/SKILL.md +37 -0
  19. package/.claude/skills/genie-blank-init/assets/BOOTSTRAP.md +44 -0
  20. package/.claude/skills/genie-blank-init/assets/IDENTITY.md +9 -0
  21. package/.claude/skills/genie-blank-init/assets/SOUL.md +10 -0
  22. package/.claude/skills/genie-blank-init/assets/USER.md +9 -0
  23. package/.claude/skills/genie-blank-init/scripts/apply-blank-init.sh +117 -0
  24. package/.claude/skills/genie-forge/SKILL.md +171 -0
  25. package/.claude/skills/genie-plan-review/CLAUDE.md +11 -0
  26. package/.claude/skills/genie-plan-review/SKILL.md +53 -0
  27. package/.claude/skills/genie-review/SKILL.md +171 -0
  28. package/.claude/skills/genie-wish/SKILL.md +141 -0
  29. package/.claude-plugin/marketplace.json +18 -0
  30. package/.genie/.gitkeep +3 -0
  31. package/.genie/backlog/hooks-v2.md +82 -0
  32. package/.genie/wishes/upgrade-brainstorm-handoff/wish.md +124 -0
  33. package/.gitattributes +1 -1
  34. package/AGENTS.md +35 -0
  35. package/README.md +10 -5
  36. package/bun.lock +55 -0
  37. package/dist/claudio.js +1 -1
  38. package/dist/genie.js +1 -1
  39. package/dist/term.js +108 -85
  40. package/docs/CO-ORCHESTRATION-GUIDE.md +375 -0
  41. package/package.json +5 -1
  42. package/plugin/.claude-plugin/plugin.json +18 -0
  43. package/plugin/README.md +120 -0
  44. package/plugin/agents/implementor.md +92 -0
  45. package/plugin/agents/quality-reviewer.md +113 -0
  46. package/plugin/agents/spec-reviewer.md +90 -0
  47. package/plugin/hooks/hooks.json +3 -0
  48. package/plugin/hooks/postInstall.sh +10 -0
  49. package/plugin/references/review-criteria.md +72 -0
  50. package/plugin/references/wish-template.md +92 -0
  51. package/plugin/scripts/genie.cjs +141 -0
  52. package/plugin/scripts/smart-install.js +308 -0
  53. package/plugin/scripts/src/install-genie-cli.sh +120 -0
  54. package/plugin/scripts/src/validate-completion.ts +142 -0
  55. package/plugin/scripts/src/validate-wish.ts +137 -0
  56. package/plugin/scripts/term.cjs +231 -0
  57. package/plugin/scripts/validate-completion.cjs +16 -0
  58. package/plugin/scripts/validate-wish.cjs +17 -0
  59. package/plugin/scripts/worker-service.cjs +28 -0
  60. package/plugin/skills/brainstorm/SKILL.md +106 -0
  61. package/plugin/skills/forge/SKILL.md +171 -0
  62. package/plugin/skills/genie-base/SKILL.md +99 -0
  63. package/plugin/skills/genie-base/assets/workspace/AGENTS.md +191 -0
  64. package/plugin/skills/genie-base/assets/workspace/ENVIRONMENT.md +18 -0
  65. package/plugin/skills/genie-base/assets/workspace/HEARTBEAT.md +4 -0
  66. package/plugin/skills/genie-base/assets/workspace/IDENTITY.md +17 -0
  67. package/plugin/skills/genie-base/assets/workspace/MEMORY.md +16 -0
  68. package/plugin/skills/genie-base/assets/workspace/ROLE.md +14 -0
  69. package/plugin/skills/genie-base/assets/workspace/SOUL.md +36 -0
  70. package/plugin/skills/genie-base/assets/workspace/TOOLS.md +25 -0
  71. package/plugin/skills/genie-base/assets/workspace/USER.md +13 -0
  72. package/plugin/skills/genie-base/scripts/install-workspace.sh +107 -0
  73. package/plugin/skills/genie-base/scripts/sanity-sweep.sh +60 -0
  74. package/plugin/skills/genie-blank-init/SKILL.md +73 -0
  75. package/plugin/skills/genie-blank-init/assets/BOOTSTRAP.md +44 -0
  76. package/plugin/skills/genie-blank-init/assets/IDENTITY.md +9 -0
  77. package/plugin/skills/genie-blank-init/assets/SOUL.md +10 -0
  78. package/plugin/skills/genie-blank-init/assets/USER.md +9 -0
  79. package/plugin/skills/genie-blank-init/scripts/apply-blank-init.sh +117 -0
  80. package/plugin/skills/genie-cli-dev/CLAUDE.md +19 -0
  81. package/plugin/skills/genie-cli-dev/SKILL.md +295 -0
  82. package/plugin/skills/plan-review/SKILL.md +101 -0
  83. package/plugin/skills/review/SKILL.md +221 -0
  84. package/plugin/skills/wish/SKILL.md +110 -0
  85. package/plugin/skills/work-orchestration/SKILL.md +116 -0
  86. package/scripts/build.js +132 -0
  87. package/scripts/smart-install.js +308 -0
  88. package/scripts/sync.js +134 -0
  89. package/src/lib/beads-registry.ts +49 -0
  90. package/src/lib/orchestrator/event-monitor.ts +2 -0
  91. package/src/lib/skill-loader.ts +215 -0
  92. package/src/lib/tmux.ts +19 -14
  93. package/src/lib/version.ts +1 -1
  94. package/src/lib/worker-registry.ts +10 -0
  95. package/src/services/worker-service.ts +351 -0
  96. package/src/term-commands/close.ts +14 -4
  97. package/src/term-commands/create.ts +95 -0
  98. package/src/term-commands/kill.ts +15 -4
  99. package/src/term-commands/orchestrate.ts +3 -2
  100. package/src/term-commands/send.ts +43 -15
  101. package/src/term-commands/spawn.ts +446 -0
  102. package/src/term-commands/split.ts +14 -3
  103. package/src/term-commands/work.ts +217 -57
  104. package/src/term.ts +81 -6
@@ -9,17 +9,18 @@
9
9
  * Options:
10
10
  * --no-worktree - Use shared repo instead of worktree
11
11
  * -s, --session <name> - Target tmux session
12
- * --focus - Focus the worker pane (default: true)
12
+ * --focus - Focus the worker pane (default: false)
13
+ * --resume - Resume previous Claude session if available (default: true)
14
+ * --no-resume - Start fresh session even if previous exists
13
15
  */
14
16
 
15
17
  import { $ } from 'bun';
18
+ import { randomUUID } from 'crypto';
16
19
  import * as tmux from '../lib/tmux.js';
17
20
  import * as registry from '../lib/worker-registry.js';
18
21
  import * as beadsRegistry from '../lib/beads-registry.js';
19
- import { WorktreeManager } from '../lib/worktree.js';
20
- import { EventMonitor, detectState } from '../lib/orchestrator/index.js';
22
+ import { EventMonitor } from '../lib/orchestrator/index.js';
21
23
  import { join } from 'path';
22
- import { homedir } from 'os';
23
24
 
24
25
  // Use beads registry when enabled
25
26
  const useBeads = beadsRegistry.isBeadsRegistryEnabled();
@@ -33,12 +34,15 @@ export interface WorkOptions {
33
34
  session?: string;
34
35
  focus?: boolean;
35
36
  prompt?: string;
37
+ /** Resume previous Claude session if available */
38
+ resume?: boolean;
36
39
  }
37
40
 
38
41
  interface BeadsIssue {
39
42
  id: string;
40
43
  title: string;
41
44
  status: string;
45
+ description?: string;
42
46
  blockedBy?: string[];
43
47
  }
44
48
 
@@ -46,7 +50,8 @@ interface BeadsIssue {
46
50
  // Configuration
47
51
  // ============================================================================
48
52
 
49
- const WORKTREE_BASE = join(homedir(), '.local', 'share', 'term', 'worktrees');
53
+ // Worktrees are created inside the project at .genie/worktrees/<taskId>
54
+ const WORKTREE_DIR_NAME = '.genie/worktrees';
50
55
 
51
56
  // ============================================================================
52
57
  // Helper Functions
@@ -72,11 +77,15 @@ async function getBeadsIssue(id: string): Promise<BeadsIssue | null> {
72
77
  if (exitCode !== 0 || !stdout) return null;
73
78
 
74
79
  try {
75
- const issue = JSON.parse(stdout);
80
+ const parsed = JSON.parse(stdout);
81
+ // bd show --json returns an array with single element
82
+ const issue = Array.isArray(parsed) ? parsed[0] : parsed;
83
+ if (!issue) return null;
76
84
  return {
77
85
  id: issue.id,
78
86
  title: issue.title || issue.description?.substring(0, 50) || 'Untitled',
79
87
  status: issue.status,
88
+ description: issue.description,
80
89
  blockedBy: issue.blockedBy || [],
81
90
  };
82
91
  } catch {
@@ -99,6 +108,7 @@ async function getNextReadyIssue(): Promise<BeadsIssue | null> {
99
108
  id: issue.id,
100
109
  title: issue.title || issue.description?.substring(0, 50) || 'Untitled',
101
110
  status: issue.status,
111
+ description: issue.description,
102
112
  blockedBy: issue.blockedBy || [],
103
113
  };
104
114
  }
@@ -138,52 +148,54 @@ async function getCurrentSession(): Promise<string | null> {
138
148
  }
139
149
 
140
150
  /**
141
- * Create worktree for worker
142
- * Uses bd worktree when beads registry is enabled (auto .beads redirect)
143
- * Falls back to WorktreeManager otherwise
151
+ * Create worktree for worker in .genie/worktrees/<taskId>
152
+ * Creates a .genie redirect file so bd commands work in the worktree
144
153
  */
145
154
  async function createWorktree(
146
155
  taskId: string,
147
156
  repoPath: string
148
157
  ): Promise<string | null> {
149
- // Try bd worktree first when beads is enabled
150
- if (useBeads) {
151
- try {
152
- // Check if worktree exists via beads
153
- const existing = await beadsRegistry.getWorktree(taskId);
154
- if (existing) {
155
- console.log(`ℹ️ Worktree for ${taskId} already exists`);
156
- return existing.path;
157
- }
158
+ const fs = await import('fs/promises');
159
+ const worktreeDir = join(repoPath, WORKTREE_DIR_NAME);
160
+ const worktreePath = join(worktreeDir, taskId);
158
161
 
159
- // Create via bd worktree (includes .beads redirect)
160
- const info = await beadsRegistry.createWorktree(taskId);
161
- if (info) {
162
- return info.path;
163
- }
164
- // Fall through to WorktreeManager if bd worktree fails
165
- console.log(`⚠️ bd worktree failed, falling back to git worktree`);
166
- } catch (error: any) {
167
- console.log(`⚠️ bd worktree error: ${error.message}, falling back`);
168
- }
162
+ // Ensure .genie/worktrees exists
163
+ try {
164
+ await fs.mkdir(worktreeDir, { recursive: true });
165
+ } catch {
166
+ // Directory may already exist
169
167
  }
170
168
 
171
- // Fallback to WorktreeManager
169
+ // Check if worktree already exists
172
170
  try {
173
- const manager = new WorktreeManager({
174
- baseDir: WORKTREE_BASE,
175
- repoPath,
176
- });
177
-
178
- // Check if worktree already exists
179
- if (await manager.worktreeExists(taskId)) {
171
+ const stat = await fs.stat(worktreePath);
172
+ if (stat.isDirectory()) {
180
173
  console.log(`ℹ️ Worktree for ${taskId} already exists`);
181
- return manager.getWorktreePath(taskId);
174
+ return worktreePath;
182
175
  }
176
+ } catch {
177
+ // Doesn't exist, will create
178
+ }
179
+
180
+ // Create worktree using git directly (with branch)
181
+ const branchName = `work/${taskId}`;
182
+ try {
183
+ // Create branch if it doesn't exist (ignore error if it already exists)
184
+ try {
185
+ await $`git -C ${repoPath} branch ${branchName}`.quiet();
186
+ } catch {
187
+ // Branch may already exist, that's ok
188
+ }
189
+
190
+ // Create worktree
191
+ await $`git -C ${repoPath} worktree add ${worktreePath} ${branchName}`.quiet();
192
+
193
+ // Set up .genie redirect so bd commands work in the worktree
194
+ const genieRedirect = join(worktreePath, '.genie');
195
+ await fs.mkdir(genieRedirect, { recursive: true });
196
+ await fs.writeFile(join(genieRedirect, 'redirect'), join(repoPath, '.genie'));
183
197
 
184
- // Create new worktree with branch
185
- const info = await manager.createWorktree(taskId, true);
186
- return info.path;
198
+ return worktreePath;
187
199
  } catch (error: any) {
188
200
  console.error(`⚠️ Failed to create worktree: ${error.message}`);
189
201
  return null;
@@ -191,14 +203,71 @@ async function createWorktree(
191
203
  }
192
204
 
193
205
  /**
194
- * Spawn Claude worker in new pane
206
+ * Remove worktree for a worker
207
+ */
208
+ async function removeWorktree(taskId: string, repoPath: string): Promise<void> {
209
+ const worktreePath = join(repoPath, WORKTREE_DIR_NAME, taskId);
210
+
211
+ try {
212
+ // Remove worktree
213
+ await $`git -C ${repoPath} worktree remove ${worktreePath} --force`.quiet();
214
+ } catch {
215
+ // Ignore errors - worktree may already be removed
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Wait for Claude CLI to be ready to accept input
221
+ * Polls pane content looking for Claude's input prompt indicator
222
+ */
223
+ async function waitForClaudeReady(
224
+ paneId: string,
225
+ timeoutMs: number = 30000,
226
+ pollIntervalMs: number = 500
227
+ ): Promise<boolean> {
228
+ const startTime = Date.now();
229
+
230
+ while (Date.now() - startTime < timeoutMs) {
231
+ try {
232
+ const content = await tmux.capturePaneContent(paneId, 50);
233
+
234
+ // Claude CLI shows ">" prompt when ready for input
235
+ // Also check for the input area indicator
236
+ // The prompt appears at the end of output when Claude is waiting for input
237
+ const lines = content.split('\n').filter(l => l.trim());
238
+ if (lines.length > 0) {
239
+ const lastFewLines = lines.slice(-5).join('\n');
240
+ // Claude shows "❯" prompt when ready for input
241
+ // Also detect welcome messages or input hints
242
+ if (
243
+ lastFewLines.includes('❯') ||
244
+ lastFewLines.includes('? for shortcuts') ||
245
+ lastFewLines.includes('What would you like') ||
246
+ lastFewLines.includes('How can I help')
247
+ ) {
248
+ return true;
249
+ }
250
+ }
251
+ } catch {
252
+ // Pane may not exist yet, continue polling
253
+ }
254
+
255
+ await new Promise(r => setTimeout(r, pollIntervalMs));
256
+ }
257
+
258
+ // Timeout - return false but don't fail (caller can decide)
259
+ return false;
260
+ }
261
+
262
+ /**
263
+ * Spawn Claude worker in new pane (splits the CURRENT active pane)
195
264
  */
196
265
  async function spawnWorkerPane(
197
266
  session: string,
198
267
  workingDir: string
199
268
  ): Promise<{ paneId: string } | null> {
200
269
  try {
201
- // Find current window
270
+ // Find session
202
271
  const sessionObj = await tmux.findSessionByName(session);
203
272
  if (!sessionObj) {
204
273
  console.error(`❌ Session "${session}" not found`);
@@ -211,15 +280,21 @@ async function spawnWorkerPane(
211
280
  return null;
212
281
  }
213
282
 
214
- const panes = await tmux.listPanes(windows[0].id);
283
+ // Find the ACTIVE window (not first window)
284
+ const activeWindow = windows.find(w => w.active) || windows[0];
285
+
286
+ const panes = await tmux.listPanes(activeWindow.id);
215
287
  if (!panes || panes.length === 0) {
216
- console.error(`❌ No panes in session "${session}"`);
288
+ console.error(`❌ No panes in window "${activeWindow.name}"`);
217
289
  return null;
218
290
  }
219
291
 
292
+ // Find the ACTIVE pane (not first pane)
293
+ const activePane = panes.find(p => p.active) || panes[0];
294
+
220
295
  // Split current pane horizontally (side by side)
221
296
  const newPane = await tmux.splitPane(
222
- panes[0].id,
297
+ activePane.id,
223
298
  'horizontal',
224
299
  50,
225
300
  workingDir
@@ -366,6 +441,78 @@ export async function workCommand(
366
441
  existingWorker = await registry.findByTask(taskId);
367
442
  }
368
443
  if (existingWorker) {
444
+ // If worker exists and has a session ID, offer to resume
445
+ if (existingWorker.claudeSessionId && options.resume !== false) {
446
+ console.log(`📋 Found existing worker for ${taskId} with resumable session`);
447
+ console.log(` Session ID: ${existingWorker.claudeSessionId}`);
448
+ console.log(` Resuming previous Claude session...`);
449
+
450
+ // Get session
451
+ const session = options.session || await getCurrentSession();
452
+ if (!session) {
453
+ console.error('❌ Not in a tmux session. Attach to a session first or use --session.');
454
+ process.exit(1);
455
+ }
456
+
457
+ // Spawn a new pane for the resumed session
458
+ const workingDir = existingWorker.worktree || existingWorker.repoPath;
459
+ console.log(`🚀 Spawning worker pane...`);
460
+ const paneResult = await spawnWorkerPane(session, workingDir);
461
+ if (!paneResult) {
462
+ process.exit(1);
463
+ }
464
+
465
+ const { paneId } = paneResult;
466
+
467
+ // Update worker with new pane ID
468
+ await registry.update(existingWorker.id, {
469
+ paneId,
470
+ session,
471
+ state: 'spawning',
472
+ lastStateChange: new Date().toISOString(),
473
+ });
474
+ if (useBeads) {
475
+ await beadsRegistry.setAgentState(existingWorker.id, 'spawning').catch(() => {});
476
+ }
477
+
478
+ // Set BEADS_DIR so bd commands work in the worktree
479
+ const beadsDir = join(existingWorker.repoPath, '.genie');
480
+ const escapedWorkingDir = workingDir.replace(/'/g, "'\\''");
481
+
482
+ // Resume Claude with the stored session ID
483
+ await tmux.executeCommand(
484
+ paneId,
485
+ `cd '${escapedWorkingDir}' && BEADS_DIR='${beadsDir}' claude --resume '${existingWorker.claudeSessionId}'`,
486
+ true,
487
+ false
488
+ );
489
+
490
+ // Update state to working
491
+ if (useBeads) {
492
+ await beadsRegistry.setAgentState(existingWorker.id, 'working').catch(() => {});
493
+ }
494
+ await registry.updateState(existingWorker.id, 'working');
495
+
496
+ // Start monitoring
497
+ startWorkerMonitoring(existingWorker.id, session, paneId);
498
+
499
+ // Focus pane (only if explicitly requested)
500
+ if (options.focus === true) {
501
+ await tmux.executeTmux(`select-pane -t '${paneId}'`);
502
+ }
503
+
504
+ console.log(`\n✅ Resumed worker for ${taskId}`);
505
+ console.log(` Pane: ${paneId}`);
506
+ console.log(` Session: ${session}`);
507
+ console.log(` Claude Session: ${existingWorker.claudeSessionId}`);
508
+ console.log(`\nCommands:`);
509
+ console.log(` term workers - Check worker status`);
510
+ console.log(` term approve ${taskId} - Approve permissions`);
511
+ console.log(` term close ${taskId} - Close issue when done`);
512
+ console.log(` term kill ${taskId} - Force kill worker`);
513
+ return;
514
+ }
515
+
369
516
  console.error(`❌ ${taskId} already has a worker (pane ${existingWorker.paneId})`);
370
517
  console.log(` Run \`term kill ${existingWorker.id}\` first, or work on a different issue.`);
371
518
  process.exit(1);
@@ -410,7 +557,10 @@ export async function workCommand(
410
557
 
411
558
  const { paneId } = paneResult;
412
559
 
413
- // 7. Register worker (write to both registries during transition)
560
+ // 7. Generate Claude session ID for resume capability
561
+ const claudeSessionId = randomUUID();
562
+
563
+ // 8. Register worker (write to both registries during transition)
414
564
  const worker: registry.Worker = {
415
565
  id: taskId,
416
566
  paneId,
@@ -422,6 +572,7 @@ export async function workCommand(
422
572
  state: 'spawning',
423
573
  lastStateChange: new Date().toISOString(),
424
574
  repoPath,
575
+ claudeSessionId,
425
576
  };
426
577
 
427
578
  // Register in beads (creates agent bead)
@@ -434,6 +585,7 @@ export async function workCommand(
434
585
  repoPath,
435
586
  taskId,
436
587
  taskTitle: issue.title,
588
+ claudeSessionId,
437
589
  });
438
590
 
439
591
  // Bind work to agent
@@ -449,20 +601,28 @@ export async function workCommand(
449
601
  // Also register in JSON registry (parallel operation during transition)
450
602
  await registry.register(worker);
451
603
 
452
- // 8. Start Claude in pane
453
- await tmux.executeCommand(paneId, 'claude', false, false);
454
-
455
- // Wait a bit for Claude to start
456
- await new Promise(r => setTimeout(r, 2000));
457
-
458
- // 9. Send initial prompt
604
+ // 9. Build prompt and start Claude with it as argument
459
605
  const prompt = options.prompt || `Work on beads issue ${taskId}: "${issue.title}"
460
606
 
461
- Read the issue details with: bd show ${taskId}
607
+ ## Description
608
+ ${issue.description || 'No description provided.'}
462
609
 
463
610
  When you're done, commit your changes and let me know.`;
464
611
 
465
- await tmux.executeCommand(paneId, prompt, false, false);
612
+ // Escape the prompt for shell (single quotes)
613
+ const escapedPrompt = prompt.replace(/'/g, "'\\''");
614
+
615
+ // Set BEADS_DIR so bd commands work in the worktree
616
+ const beadsDir = join(repoPath, '.genie');
617
+
618
+ // Escape workingDir for shell
619
+ const escapedWorkingDir = workingDir.replace(/'/g, "'\\''");
620
+
621
+ // Start Claude with session ID for resume capability
622
+ // First cd to correct directory (shell rc files may have overridden tmux -c)
623
+ await tmux.executeCommand(paneId, `cd '${escapedWorkingDir}' && BEADS_DIR='${beadsDir}' claude --session-id '${claudeSessionId}' '${escapedPrompt}'`, true, false);
624
+
625
+ console.log(` Session ID: ${claudeSessionId}`);
466
626
 
467
627
  // 10. Update state to working (both registries)
468
628
  if (useBeads) {
@@ -473,8 +633,8 @@ When you're done, commit your changes and let me know.`;
473
633
  // 11. Start monitoring
474
634
  startWorkerMonitoring(taskId, session, paneId);
475
635
 
476
- // 12. Focus pane (unless disabled)
477
- if (options.focus !== false) {
636
+ // 12. Focus pane (only if explicitly requested)
637
+ if (options.focus === true) {
478
638
  await tmux.executeTmux(`select-pane -t '${paneId}'`);
479
639
  }
480
640
 
package/src/term.ts CHANGED
@@ -21,6 +21,8 @@ import * as workersCmd from './term-commands/workers.js';
21
21
  import * as closeCmd from './term-commands/close.js';
22
22
  import * as killCmd from './term-commands/kill.js';
23
23
  import * as daemonCmd from './term-commands/daemon.js';
24
+ import * as spawnCmd from './term-commands/spawn.js';
25
+ import * as createCmd from './term-commands/create.js';
24
26
 
25
27
  const program = new Command();
26
28
 
@@ -36,6 +38,12 @@ Collaborative Usage:
36
38
  Workflow: new → exec → read → rm
37
39
  Full control: window new/ls/rm, pane ls/rm, split, status
38
40
 
41
+ Skill-Based Spawning:
42
+ term spawn - Pick skill interactively
43
+ term spawn <skill> - Spawn Claude with skill loaded
44
+ term skills - List available skills
45
+ term create <title> - Create beads issue
46
+
39
47
  Worker Orchestration:
40
48
  term work <bd-id> - Spawn worker bound to beads issue
41
49
  term work next - Work on next ready issue
@@ -111,9 +119,11 @@ program
111
119
 
112
120
  program
113
121
  .command('send <session> <keys>')
114
- .description('Send raw keys to a tmux session')
115
- .action(async (session: string, keys: string) => {
116
- await sendCmd.sendKeysToSession(session, keys);
122
+ .description('Send keys to a tmux session (appends Enter by default)')
123
+ .option('--no-enter', 'Send raw keys without appending Enter')
124
+ .option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
125
+ .action(async (session: string, keys: string, options: { enter?: boolean; pane?: string }) => {
126
+ await sendCmd.sendKeysToSession(session, keys, options);
117
127
  });
118
128
 
119
129
  // Pane splitting
@@ -126,9 +136,9 @@ program
126
136
  await splitCmd.splitSessionPane(session, direction, options);
127
137
  });
128
138
 
129
- // Status command
139
+ // Info command (renamed from status)
130
140
  program
131
- .command('status <session>')
141
+ .command('info <session>')
132
142
  .description('Check session state (idle/busy, pane count)')
133
143
  .option('--command <id>', 'Check specific command status')
134
144
  .option('--json', 'Output as JSON')
@@ -214,14 +224,79 @@ program
214
224
  await shortcutsCmd.handleShortcuts(options);
215
225
  });
216
226
 
227
+ // Skill-based spawning
228
+ program
229
+ .command('spawn [skill]')
230
+ .description('Spawn Claude with a skill (interactive picker if no skill specified)')
231
+ .option('-s, --session <name>', 'Target tmux session')
232
+ .option('--no-worktree', 'Skip worktree creation when taskId provided')
233
+ .option('--no-focus', 'Don\'t focus the new pane')
234
+ .option('-p, --prompt <message>', 'Additional context for the skill')
235
+ .option('-t, --task-id <id>', 'Bind to beads issue')
236
+ .action(async (skill: string | undefined, options: spawnCmd.SpawnOptions) => {
237
+ await spawnCmd.spawnCommand(skill, options);
238
+ });
239
+
240
+ program
241
+ .command('skills')
242
+ .description('List available skills')
243
+ .action(async () => {
244
+ await spawnCmd.listSkillsCommand();
245
+ });
246
+
247
+ program
248
+ .command('brainstorm')
249
+ .description('Spawn Claude with brainstorm skill (idea → design → spec)')
250
+ .option('-s, --session <name>', 'Target tmux session')
251
+ .option('--no-focus', 'Don\'t focus the new pane')
252
+ .option('-p, --prompt <message>', 'Additional context')
253
+ .action(async (options: spawnCmd.SpawnOptions) => {
254
+ await spawnCmd.spawnCommand('brainstorm', options);
255
+ });
256
+
257
+ // Watch session events (promoted from orc watch)
258
+ program
259
+ .command('watch <session>')
260
+ .description('Watch session events in real-time')
261
+ .option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
262
+ .option('--json', 'Output events as JSON')
263
+ .option('--poll <ms>', 'Poll interval in milliseconds')
264
+ .action(async (session: string, options: orchestrateCmd.WatchOptions) => {
265
+ await orchestrateCmd.watchSession(session, options);
266
+ });
267
+
268
+ // Run task with monitoring (promoted from orc run)
269
+ program
270
+ .command('run <session> <message>')
271
+ .description('Send task and auto-approve until idle (fire-and-forget)')
272
+ .option('-p, --pane <id>', 'Target specific pane ID (e.g., %16)')
273
+ .option('-a, --auto-approve', 'Auto-approve permissions and plans')
274
+ .option('-t, --timeout <ms>', 'Timeout in milliseconds (default: 300000)')
275
+ .option('--json', 'Output final state as JSON')
276
+ .action(async (session: string, message: string, options: orchestrateCmd.RunOptions) => {
277
+ await orchestrateCmd.runTask(session, message, options);
278
+ });
279
+
280
+ // Create beads issue command
281
+ program
282
+ .command('create <title>')
283
+ .description('Create a new beads issue')
284
+ .option('-d, --description <text>', 'Issue description')
285
+ .option('-p, --parent <id>', 'Parent issue ID (creates dependency)')
286
+ .option('--json', 'Output as JSON')
287
+ .action(async (title: string, options: createCmd.CreateOptions) => {
288
+ await createCmd.createCommand(title, options);
289
+ });
290
+
217
291
  // Worker management commands (beads + Claude orchestration)
218
292
  program
219
293
  .command('work <target>')
220
294
  .description('Spawn worker bound to beads issue (target: bd-id, "next", or "wish")')
221
295
  .option('--no-worktree', 'Use shared repo instead of worktree')
222
296
  .option('-s, --session <name>', 'Target tmux session')
223
- .option('--no-focus', 'Don\'t focus the worker pane')
297
+ .option('--focus', 'Focus the worker pane after spawning')
224
298
  .option('-p, --prompt <message>', 'Custom initial prompt')
299
+ .option('--no-resume', 'Start fresh session even if previous exists')
225
300
  .action(async (target: string, options: workCmd.WorkOptions) => {
226
301
  await workCmd.workCommand(target, options);
227
302
  });