@automagik/genie 0.260202.1833 → 0.260203.43

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 (108) hide show
  1. package/.beads/README.md +81 -0
  2. package/.beads/config.yaml +67 -0
  3. package/.beads/interactions.jsonl +0 -0
  4. package/.beads/issues.jsonl +9 -0
  5. package/.beads/metadata.json +4 -0
  6. package/.claude/skills/brainstorm/SKILL.md +53 -0
  7. package/.claude/skills/genie-base/SKILL.md +66 -0
  8. package/.claude/skills/genie-base/assets/workspace/AGENTS.md +191 -0
  9. package/.claude/skills/genie-base/assets/workspace/ENVIRONMENT.md +18 -0
  10. package/.claude/skills/genie-base/assets/workspace/HEARTBEAT.md +4 -0
  11. package/.claude/skills/genie-base/assets/workspace/IDENTITY.md +17 -0
  12. package/.claude/skills/genie-base/assets/workspace/MEMORY.md +16 -0
  13. package/.claude/skills/genie-base/assets/workspace/ROLE.md +14 -0
  14. package/.claude/skills/genie-base/assets/workspace/SOUL.md +36 -0
  15. package/.claude/skills/genie-base/assets/workspace/TOOLS.md +25 -0
  16. package/.claude/skills/genie-base/assets/workspace/USER.md +13 -0
  17. package/.claude/skills/genie-base/assets/workspace/memory/2026-01-30.md +6 -0
  18. package/.claude/skills/genie-base/assets/workspace/memory/2026-01-31.md +16 -0
  19. package/.claude/skills/genie-base/assets/workspace/memory/882c22be-9710-41c1-91f8-ed82947ef6ce.txt +1 -0
  20. package/.claude/skills/genie-base/scripts/install-workspace.sh +107 -0
  21. package/.claude/skills/genie-base/scripts/sanity-sweep.sh +60 -0
  22. package/.claude/skills/genie-blank-init/SKILL.md +37 -0
  23. package/.claude/skills/genie-blank-init/assets/BOOTSTRAP.md +44 -0
  24. package/.claude/skills/genie-blank-init/assets/IDENTITY.md +9 -0
  25. package/.claude/skills/genie-blank-init/assets/SOUL.md +10 -0
  26. package/.claude/skills/genie-blank-init/assets/USER.md +9 -0
  27. package/.claude/skills/genie-blank-init/scripts/apply-blank-init.sh +117 -0
  28. package/.claude/skills/genie-forge/SKILL.md +171 -0
  29. package/.claude/skills/genie-plan-review/CLAUDE.md +11 -0
  30. package/.claude/skills/genie-plan-review/SKILL.md +53 -0
  31. package/.claude/skills/genie-review/SKILL.md +171 -0
  32. package/.claude/skills/genie-wish/SKILL.md +141 -0
  33. package/.claude-plugin/marketplace.json +18 -0
  34. package/.genie/.gitkeep +3 -0
  35. package/.genie/backlog/hooks-v2.md +82 -0
  36. package/.genie/wishes/upgrade-brainstorm-handoff/wish.md +124 -0
  37. package/.gitattributes +3 -0
  38. package/AGENTS.md +75 -0
  39. package/bun.lock +55 -0
  40. package/dist/claudio.js +1 -1
  41. package/dist/genie.js +1 -1
  42. package/dist/term.js +123 -99
  43. package/docs/CO-ORCHESTRATION-GUIDE.md +368 -0
  44. package/package.json +5 -1
  45. package/plugin/.claude-plugin/plugin.json +18 -0
  46. package/plugin/README.md +120 -0
  47. package/plugin/agents/implementor.md +92 -0
  48. package/plugin/agents/quality-reviewer.md +113 -0
  49. package/plugin/agents/spec-reviewer.md +90 -0
  50. package/plugin/hooks/hooks.json +3 -0
  51. package/plugin/references/review-criteria.md +72 -0
  52. package/plugin/references/wish-template.md +92 -0
  53. package/plugin/scripts/genie.cjs +141 -0
  54. package/plugin/scripts/smart-install.js +308 -0
  55. package/plugin/scripts/src/install-genie-cli.sh +120 -0
  56. package/plugin/scripts/src/validate-completion.ts +142 -0
  57. package/plugin/scripts/src/validate-wish.ts +137 -0
  58. package/plugin/scripts/term.cjs +229 -0
  59. package/plugin/scripts/validate-completion.cjs +16 -0
  60. package/plugin/scripts/validate-wish.cjs +17 -0
  61. package/plugin/scripts/worker-service.cjs +28 -0
  62. package/plugin/skills/brainstorm/SKILL.md +106 -0
  63. package/plugin/skills/forge/SKILL.md +171 -0
  64. package/plugin/skills/genie-base/SKILL.md +99 -0
  65. package/plugin/skills/genie-base/assets/workspace/AGENTS.md +191 -0
  66. package/plugin/skills/genie-base/assets/workspace/ENVIRONMENT.md +18 -0
  67. package/plugin/skills/genie-base/assets/workspace/HEARTBEAT.md +4 -0
  68. package/plugin/skills/genie-base/assets/workspace/IDENTITY.md +17 -0
  69. package/plugin/skills/genie-base/assets/workspace/MEMORY.md +16 -0
  70. package/plugin/skills/genie-base/assets/workspace/ROLE.md +14 -0
  71. package/plugin/skills/genie-base/assets/workspace/SOUL.md +36 -0
  72. package/plugin/skills/genie-base/assets/workspace/TOOLS.md +25 -0
  73. package/plugin/skills/genie-base/assets/workspace/USER.md +13 -0
  74. package/plugin/skills/genie-base/scripts/install-workspace.sh +107 -0
  75. package/plugin/skills/genie-base/scripts/sanity-sweep.sh +60 -0
  76. package/plugin/skills/genie-blank-init/SKILL.md +73 -0
  77. package/plugin/skills/genie-blank-init/assets/BOOTSTRAP.md +44 -0
  78. package/plugin/skills/genie-blank-init/assets/IDENTITY.md +9 -0
  79. package/plugin/skills/genie-blank-init/assets/SOUL.md +10 -0
  80. package/plugin/skills/genie-blank-init/assets/USER.md +9 -0
  81. package/plugin/skills/genie-blank-init/scripts/apply-blank-init.sh +117 -0
  82. package/plugin/skills/genie-cli-dev/CLAUDE.md +19 -0
  83. package/plugin/skills/genie-cli-dev/SKILL.md +292 -0
  84. package/plugin/skills/plan-review/SKILL.md +101 -0
  85. package/plugin/skills/review/SKILL.md +221 -0
  86. package/plugin/skills/wish/SKILL.md +110 -0
  87. package/plugin/skills/work-orchestration/SKILL.md +110 -0
  88. package/scripts/build.js +132 -0
  89. package/scripts/smart-install.js +308 -0
  90. package/scripts/sync.js +134 -0
  91. package/src/lib/beads-registry.ts +595 -0
  92. package/src/lib/orchestrator/event-monitor.ts +2 -0
  93. package/src/lib/skill-loader.ts +215 -0
  94. package/src/lib/tmux.ts +30 -11
  95. package/src/lib/version.ts +1 -1
  96. package/src/lib/worker-registry.ts +10 -0
  97. package/src/services/worker-service.ts +351 -0
  98. package/src/term-commands/close.ts +48 -3
  99. package/src/term-commands/create.ts +95 -0
  100. package/src/term-commands/daemon.ts +176 -0
  101. package/src/term-commands/kill.ts +56 -2
  102. package/src/term-commands/orchestrate.ts +3 -2
  103. package/src/term-commands/send.ts +43 -15
  104. package/src/term-commands/spawn.ts +446 -0
  105. package/src/term-commands/split.ts +20 -8
  106. package/src/term-commands/work.ts +279 -37
  107. package/src/term-commands/workers.ts +36 -2
  108. package/src/term.ts +120 -7
@@ -15,10 +15,14 @@ import { $ } from 'bun';
15
15
  import { confirm } from '@inquirer/prompts';
16
16
  import * as tmux from '../lib/tmux.js';
17
17
  import * as registry from '../lib/worker-registry.js';
18
+ import * as beadsRegistry from '../lib/beads-registry.js';
18
19
  import { WorktreeManager } from '../lib/worktree.js';
19
20
  import { join } from 'path';
20
21
  import { homedir } from 'os';
21
22
 
23
+ // Use beads registry when enabled
24
+ const useBeads = beadsRegistry.isBeadsRegistryEnabled();
25
+
22
26
  // ============================================================================
23
27
  // Types
24
28
  // ============================================================================
@@ -35,6 +39,8 @@ export interface CloseOptions {
35
39
  // ============================================================================
36
40
 
37
41
  const WORKTREE_BASE = join(homedir(), '.local', 'share', 'term', 'worktrees');
42
+ // Worktrees are created inside the project at .genie/worktrees/<taskId>
43
+ const WORKTREE_DIR_NAME = '.genie/worktrees';
38
44
 
39
45
  // ============================================================================
40
46
  // Helper Functions
@@ -101,8 +107,30 @@ async function mergeToMain(
101
107
 
102
108
  /**
103
109
  * Remove worktree
110
+ * Checks .genie/worktrees first, then bd worktree, then WorktreeManager
104
111
  */
105
112
  async function removeWorktree(taskId: string, repoPath: string): Promise<boolean> {
113
+ // First, check .genie/worktrees location (new location)
114
+ const inProjectWorktree = join(repoPath, WORKTREE_DIR_NAME, taskId);
115
+ try {
116
+ await $`git -C ${repoPath} worktree remove ${inProjectWorktree} --force`.quiet();
117
+ return true;
118
+ } catch {
119
+ // Worktree may not exist at this location, continue checking
120
+ }
121
+
122
+ // Try bd worktree when beads is enabled
123
+ if (useBeads) {
124
+ try {
125
+ const removed = await beadsRegistry.removeWorktree(taskId);
126
+ if (removed) return true;
127
+ // Fall through to WorktreeManager if bd worktree fails
128
+ } catch {
129
+ // Fall through
130
+ }
131
+ }
132
+
133
+ // Fallback to WorktreeManager (legacy location)
106
134
  try {
107
135
  const manager = new WorktreeManager({
108
136
  baseDir: WORKTREE_BASE,
@@ -141,8 +169,13 @@ export async function closeCommand(
141
169
  options: CloseOptions = {}
142
170
  ): Promise<void> {
143
171
  try {
144
- // Find worker in registry
145
- const worker = await registry.findByTask(taskId);
172
+ // Find worker in registry (check both during transition)
173
+ let worker = useBeads
174
+ ? await beadsRegistry.findByTask(taskId)
175
+ : null;
176
+ if (!worker) {
177
+ worker = await registry.findByTask(taskId);
178
+ }
146
179
 
147
180
  if (!worker) {
148
181
  console.log(`ℹ️ No active worker for ${taskId}. Closing issue only.`);
@@ -207,7 +240,19 @@ export async function closeCommand(
207
240
  await killWorkerPane(worker.paneId);
208
241
  console.log(` ✅ Pane killed`);
209
242
 
210
- // 5. Unregister worker
243
+ // 5. Unregister worker from both registries
244
+ if (useBeads) {
245
+ try {
246
+ // Unbind work from agent
247
+ await beadsRegistry.unbindWork(worker.id);
248
+ // Set agent state to done
249
+ await beadsRegistry.setAgentState(worker.id, 'done');
250
+ // Delete agent bead
251
+ await beadsRegistry.deleteAgent(worker.id);
252
+ } catch {
253
+ // Non-fatal if beads cleanup fails
254
+ }
255
+ }
211
256
  await registry.unregister(worker.id);
212
257
  console.log(` ✅ Worker unregistered`);
213
258
  }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Create command - Simple bd create wrapper
3
+ *
4
+ * Usage:
5
+ * term create "Task title" - Create beads issue
6
+ * term create "Task title" -d "Description" - With description
7
+ * term create "Task title" -p bd-1 - With parent/dependency
8
+ *
9
+ * Options:
10
+ * -d, --description <text> - Issue description
11
+ * -p, --parent <id> - Parent issue ID (creates dependency)
12
+ * --json - Output as JSON
13
+ */
14
+
15
+ import { $ } from 'bun';
16
+
17
+ export interface CreateOptions {
18
+ description?: string;
19
+ parent?: string;
20
+ json?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Run bd command and return result
25
+ */
26
+ async function runBd(args: string[]): Promise<{ stdout: string; exitCode: number }> {
27
+ try {
28
+ const result = await $`bd ${args}`.quiet();
29
+ return { stdout: result.stdout.toString().trim(), exitCode: 0 };
30
+ } catch (error: any) {
31
+ return {
32
+ stdout: error.stdout?.toString().trim() || error.message,
33
+ exitCode: error.exitCode || 1
34
+ };
35
+ }
36
+ }
37
+
38
+ export async function createCommand(
39
+ title: string,
40
+ options: CreateOptions = {}
41
+ ): Promise<void> {
42
+ // Build bd create command
43
+ const args = ['create', title];
44
+
45
+ if (options.description) {
46
+ args.push('--description', options.description);
47
+ }
48
+
49
+ // Run bd create
50
+ const { stdout, exitCode } = await runBd(args);
51
+
52
+ if (exitCode !== 0) {
53
+ console.error(`Failed to create issue: ${stdout}`);
54
+ process.exit(1);
55
+ }
56
+
57
+ // Extract issue ID from output
58
+ // bd create typically outputs something like "Created bd-123" or just the ID
59
+ const idMatch = stdout.match(/bd-\d+/);
60
+ const issueId = idMatch ? idMatch[0] : null;
61
+
62
+ if (!issueId) {
63
+ console.log(stdout);
64
+ return;
65
+ }
66
+
67
+ // If parent specified, add blockedBy relationship
68
+ if (options.parent) {
69
+ const { exitCode: updateExit } = await runBd([
70
+ 'update',
71
+ issueId,
72
+ '--blocked-by',
73
+ options.parent,
74
+ ]);
75
+
76
+ if (updateExit !== 0) {
77
+ console.log(`Created ${issueId} (failed to set parent dependency)`);
78
+ }
79
+ }
80
+
81
+ if (options.json) {
82
+ // Fetch full issue details
83
+ const { stdout: showOutput } = await runBd(['show', issueId, '--json']);
84
+ console.log(showOutput);
85
+ } else {
86
+ console.log(`Created: ${issueId} - "${title}"`);
87
+ if (options.parent) {
88
+ console.log(` Blocked by: ${options.parent}`);
89
+ }
90
+ console.log(`\nNext steps:`);
91
+ console.log(` term work ${issueId} - Start working on it`);
92
+ console.log(` term spawn genie:wish -t ${issueId} - Plan with wish skill`);
93
+ console.log(` bd show ${issueId} - View details`);
94
+ }
95
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Daemon command - Manage beads daemon
3
+ *
4
+ * Usage:
5
+ * term daemon start - Start beads daemon (auto-commit, auto-sync)
6
+ * term daemon stop - Stop beads daemon
7
+ * term daemon status - Show daemon status
8
+ * term daemon restart - Restart daemon
9
+ *
10
+ * Options:
11
+ * --auto-commit - Enable auto-commit (default: true for start)
12
+ * --auto-push - Enable auto-push to remote
13
+ * --json - Output as JSON
14
+ */
15
+
16
+ import * as beadsRegistry from '../lib/beads-registry.js';
17
+
18
+ // ============================================================================
19
+ // Types
20
+ // ============================================================================
21
+
22
+ export interface DaemonStartOptions {
23
+ autoCommit?: boolean;
24
+ autoPush?: boolean;
25
+ }
26
+
27
+ export interface DaemonStatusOptions {
28
+ json?: boolean;
29
+ }
30
+
31
+ // ============================================================================
32
+ // Commands
33
+ // ============================================================================
34
+
35
+ /**
36
+ * Start the beads daemon
37
+ */
38
+ export async function startCommand(options: DaemonStartOptions = {}): Promise<void> {
39
+ try {
40
+ // Check if already running
41
+ const status = await beadsRegistry.checkDaemonStatus();
42
+ if (status.running) {
43
+ console.log('ℹ️ Daemon is already running');
44
+ if (status.pid) {
45
+ console.log(` PID: ${status.pid}`);
46
+ }
47
+ return;
48
+ }
49
+
50
+ console.log('🚀 Starting beads daemon...');
51
+ const started = await beadsRegistry.startDaemon({
52
+ autoCommit: options.autoCommit !== false, // Default to true
53
+ autoPush: options.autoPush,
54
+ });
55
+
56
+ if (started) {
57
+ console.log(' ✅ Daemon started');
58
+
59
+ // Show updated status
60
+ const newStatus = await beadsRegistry.checkDaemonStatus();
61
+ if (newStatus.pid) {
62
+ console.log(` PID: ${newStatus.pid}`);
63
+ }
64
+ if (newStatus.autoCommit) {
65
+ console.log(' Auto-commit: enabled');
66
+ }
67
+ if (newStatus.autoPush) {
68
+ console.log(' Auto-push: enabled');
69
+ }
70
+ } else {
71
+ console.error('❌ Failed to start daemon');
72
+ console.log(' Check `bd daemon status` for details');
73
+ process.exit(1);
74
+ }
75
+ } catch (error: any) {
76
+ console.error(`❌ Error: ${error.message}`);
77
+ process.exit(1);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Stop the beads daemon
83
+ */
84
+ export async function stopCommand(): Promise<void> {
85
+ try {
86
+ // Check if running
87
+ const status = await beadsRegistry.checkDaemonStatus();
88
+ if (!status.running) {
89
+ console.log('ℹ️ Daemon is not running');
90
+ return;
91
+ }
92
+
93
+ console.log('🛑 Stopping beads daemon...');
94
+ const stopped = await beadsRegistry.stopDaemon();
95
+
96
+ if (stopped) {
97
+ console.log(' ✅ Daemon stopped');
98
+ } else {
99
+ console.error('❌ Failed to stop daemon');
100
+ process.exit(1);
101
+ }
102
+ } catch (error: any) {
103
+ console.error(`❌ Error: ${error.message}`);
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Show daemon status
110
+ */
111
+ export async function statusCommand(options: DaemonStatusOptions = {}): Promise<void> {
112
+ try {
113
+ const status = await beadsRegistry.checkDaemonStatus();
114
+
115
+ if (options.json) {
116
+ console.log(JSON.stringify(status, null, 2));
117
+ return;
118
+ }
119
+
120
+ console.log('Beads Daemon Status');
121
+ console.log('───────────────────');
122
+ console.log(`Running: ${status.running ? '✅ yes' : '❌ no'}`);
123
+
124
+ if (status.running) {
125
+ if (status.pid) {
126
+ console.log(`PID: ${status.pid}`);
127
+ }
128
+ if (status.lastSync) {
129
+ console.log(`Last sync: ${status.lastSync}`);
130
+ }
131
+ if (status.autoCommit !== undefined) {
132
+ console.log(`Auto-commit: ${status.autoCommit ? 'enabled' : 'disabled'}`);
133
+ }
134
+ if (status.autoPush !== undefined) {
135
+ console.log(`Auto-push: ${status.autoPush ? 'enabled' : 'disabled'}`);
136
+ }
137
+ } else {
138
+ console.log('\nRun `term daemon start` to start the daemon');
139
+ }
140
+ } catch (error: any) {
141
+ console.error(`❌ Error: ${error.message}`);
142
+ process.exit(1);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Restart the beads daemon
148
+ */
149
+ export async function restartCommand(options: DaemonStartOptions = {}): Promise<void> {
150
+ try {
151
+ // Check if running and stop
152
+ const status = await beadsRegistry.checkDaemonStatus();
153
+ if (status.running) {
154
+ console.log('🛑 Stopping beads daemon...');
155
+ await beadsRegistry.stopDaemon();
156
+ console.log(' ✅ Stopped');
157
+ }
158
+
159
+ // Start with new options
160
+ console.log('🚀 Starting beads daemon...');
161
+ const started = await beadsRegistry.startDaemon({
162
+ autoCommit: options.autoCommit !== false,
163
+ autoPush: options.autoPush,
164
+ });
165
+
166
+ if (started) {
167
+ console.log(' ✅ Daemon restarted');
168
+ } else {
169
+ console.error('❌ Failed to restart daemon');
170
+ process.exit(1);
171
+ }
172
+ } catch (error: any) {
173
+ console.error(`❌ Error: ${error.message}`);
174
+ process.exit(1);
175
+ }
176
+ }
@@ -9,13 +9,18 @@
9
9
  * --keep-worktree - Don't remove the worktree
10
10
  */
11
11
 
12
+ import { $ } from 'bun';
12
13
  import { confirm } from '@inquirer/prompts';
13
14
  import * as tmux from '../lib/tmux.js';
14
15
  import * as registry from '../lib/worker-registry.js';
16
+ import * as beadsRegistry from '../lib/beads-registry.js';
15
17
  import { WorktreeManager } from '../lib/worktree.js';
16
18
  import { join } from 'path';
17
19
  import { homedir } from 'os';
18
20
 
21
+ // Use beads registry when enabled
22
+ const useBeads = beadsRegistry.isBeadsRegistryEnabled();
23
+
19
24
  // ============================================================================
20
25
  // Types
21
26
  // ============================================================================
@@ -30,6 +35,8 @@ export interface KillOptions {
30
35
  // ============================================================================
31
36
 
32
37
  const WORKTREE_BASE = join(homedir(), '.local', 'share', 'term', 'worktrees');
38
+ // Worktrees are created inside the project at .genie/worktrees/<taskId>
39
+ const WORKTREE_DIR_NAME = '.genie/worktrees';
33
40
 
34
41
  // ============================================================================
35
42
  // Helper Functions
@@ -49,8 +56,30 @@ async function killWorkerPane(paneId: string): Promise<boolean> {
49
56
 
50
57
  /**
51
58
  * Remove worktree
59
+ * Checks .genie/worktrees first, then bd worktree, then WorktreeManager
52
60
  */
53
61
  async function removeWorktree(taskId: string, repoPath: string): Promise<boolean> {
62
+ // First, check .genie/worktrees location (new location)
63
+ const inProjectWorktree = join(repoPath, WORKTREE_DIR_NAME, taskId);
64
+ try {
65
+ await $`git -C ${repoPath} worktree remove ${inProjectWorktree} --force`.quiet();
66
+ return true;
67
+ } catch {
68
+ // Worktree may not exist at this location, continue checking
69
+ }
70
+
71
+ // Try bd worktree when beads is enabled
72
+ if (useBeads) {
73
+ try {
74
+ const removed = await beadsRegistry.removeWorktree(taskId);
75
+ if (removed) return true;
76
+ // Fall through to WorktreeManager if bd worktree fails
77
+ } catch {
78
+ // Fall through
79
+ }
80
+ }
81
+
82
+ // Fallback to WorktreeManager (legacy location)
54
83
  try {
55
84
  const manager = new WorktreeManager({
56
85
  baseDir: WORKTREE_BASE,
@@ -77,19 +106,32 @@ export async function killCommand(
77
106
  options: KillOptions = {}
78
107
  ): Promise<void> {
79
108
  try {
80
- // Find worker by ID or pane
109
+ // Find worker by ID or pane (check both registries during transition)
81
110
  let worker = await registry.get(target);
82
111
 
112
+ if (!worker && useBeads) {
113
+ // Try beads registry
114
+ worker = await beadsRegistry.getWorker(target);
115
+ }
116
+
83
117
  if (!worker) {
84
118
  // Try finding by pane ID
85
119
  worker = await registry.findByPane(target);
86
120
  }
87
121
 
122
+ if (!worker && useBeads) {
123
+ worker = await beadsRegistry.findByPane(target);
124
+ }
125
+
88
126
  if (!worker) {
89
127
  // Try finding by task ID
90
128
  worker = await registry.findByTask(target);
91
129
  }
92
130
 
131
+ if (!worker && useBeads) {
132
+ worker = await beadsRegistry.findByTask(target);
133
+ }
134
+
93
135
  if (!worker) {
94
136
  console.error(`❌ Worker "${target}" not found.`);
95
137
  console.log(` Run \`term workers\` to see active workers.`);
@@ -127,7 +169,19 @@ export async function killCommand(
127
169
  }
128
170
  }
129
171
 
130
- // 3. Unregister worker
172
+ // 3. Unregister worker from both registries
173
+ if (useBeads) {
174
+ try {
175
+ // Unbind work from agent
176
+ await beadsRegistry.unbindWork(worker.id);
177
+ // Set agent state to error (killed, not done)
178
+ await beadsRegistry.setAgentState(worker.id, 'error');
179
+ // Delete agent bead
180
+ await beadsRegistry.deleteAgent(worker.id);
181
+ } catch {
182
+ // Non-fatal if beads cleanup fails
183
+ }
184
+ }
131
185
  await registry.unregister(worker.id);
132
186
  console.log(` ✅ Worker unregistered`);
133
187
 
@@ -243,8 +243,9 @@ export async function sendMessage(
243
243
  try {
244
244
  const { paneId } = await getSessionPane(sessionName, options.pane);
245
245
 
246
- // Send the message
247
- await tmux.executeCommand(paneId, message, false, false);
246
+ // Send the message cleanly (no TMUX_MCP markers)
247
+ const escapedMessage = message.replace(/'/g, "'\\''");
248
+ await tmux.executeTmux(`send-keys -t '${paneId}' '${escapedMessage}' Enter`);
248
249
 
249
250
  if (options.noWait) {
250
251
  console.log(`✅ Message sent to session "${sessionName}"`);
@@ -1,6 +1,15 @@
1
1
  import * as tmux from '../lib/tmux.js';
2
2
 
3
- export async function sendKeysToSession(sessionName: string, keys: string): Promise<void> {
3
+ export interface SendOptions {
4
+ enter?: boolean;
5
+ pane?: string;
6
+ }
7
+
8
+ export async function sendKeysToSession(
9
+ sessionName: string,
10
+ keys: string,
11
+ options: SendOptions = {}
12
+ ): Promise<void> {
4
13
  try {
5
14
  // Find session
6
15
  const session = await tmux.findSessionByName(sessionName);
@@ -9,24 +18,43 @@ export async function sendKeysToSession(sessionName: string, keys: string): Prom
9
18
  process.exit(1);
10
19
  }
11
20
 
12
- // Get first window and pane
13
- const windows = await tmux.listWindows(session.id);
14
- if (!windows || windows.length === 0) {
15
- console.error(`❌ No windows found in session "${sessionName}"`);
16
- process.exit(1);
17
- }
21
+ let paneId: string;
18
22
 
19
- const panes = await tmux.listPanes(windows[0].id);
20
- if (!panes || panes.length === 0) {
21
- console.error(`❌ No panes found in session "${sessionName}"`);
22
- process.exit(1);
23
+ // Use specified pane or find first pane
24
+ if (options.pane) {
25
+ paneId = options.pane.startsWith('%') ? options.pane : `%${options.pane}`;
26
+ } else {
27
+ // Get first window and pane
28
+ const windows = await tmux.listWindows(session.id);
29
+ if (!windows || windows.length === 0) {
30
+ console.error(`❌ No windows found in session "${sessionName}"`);
31
+ process.exit(1);
32
+ }
33
+
34
+ const panes = await tmux.listPanes(windows[0].id);
35
+ if (!panes || panes.length === 0) {
36
+ console.error(`❌ No panes found in session "${sessionName}"`);
37
+ process.exit(1);
38
+ }
39
+
40
+ paneId = panes[0].id;
23
41
  }
24
42
 
25
- const paneId = panes[0].id;
43
+ // Default: enter is true (append Enter key)
44
+ const withEnter = options.enter !== false;
45
+
46
+ // Escape single quotes for shell
47
+ const escapedKeys = keys.replace(/'/g, "'\\''");
48
+
49
+ if (withEnter) {
50
+ // Send keys with Enter appended
51
+ await tmux.executeTmux(`send-keys -t '${paneId}' '${escapedKeys}' Enter`);
52
+ } else {
53
+ // Send raw keys without Enter
54
+ await tmux.executeTmux(`send-keys -t '${paneId}' '${escapedKeys}'`);
55
+ }
26
56
 
27
- // Send keys without Enter
28
- await tmux.executeCommand(paneId, keys, false, true);
29
- console.log(`✅ Keys sent to session "${sessionName}"`);
57
+ console.log(`✅ Keys sent to session "${sessionName}"${withEnter ? ' (with Enter)' : ''}`);
30
58
  } catch (error: any) {
31
59
  console.error(`❌ Error sending keys: ${error.message}`);
32
60
  process.exit(1);