tmux-team 1.0.0

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 (3) hide show
  1. package/README.md +70 -0
  2. package/bin/tmux-team +432 -0
  3. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # tmux-team
2
+
3
+ CLI for multi-agent collaboration in tmux. Coordinate AI agents (Claude, Codex, Gemini) working in different panes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g tmux-team
9
+ ```
10
+
11
+ ### Shell Completion
12
+
13
+ ```bash
14
+ # Zsh (add to ~/.zshrc)
15
+ eval "$(tmux-team completion zsh)"
16
+
17
+ # Bash (add to ~/.bashrc)
18
+ eval "$(tmux-team completion bash)"
19
+ ```
20
+
21
+ ### Claude Code Plugin
22
+
23
+ ```
24
+ /plugin marketplace add anthropics/tmux-team
25
+ /plugin install tmux-team@tmux-team
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # Setup agents
32
+ tmux-team add claude 10.0 "Frontend"
33
+ tmux-team add codex 10.1 "Backend"
34
+ tmux-team add gemini 10.2 "Testing"
35
+
36
+ # Talk to agents
37
+ tmux-team talk codex "Review the auth module"
38
+ tmux-team talk all "Starting the refactor"
39
+
40
+ # Read responses
41
+ tmux-team check codex
42
+
43
+ # Manage agents
44
+ tmux-team list
45
+ tmux-team remove gemini
46
+ ```
47
+
48
+ ## From Claude Code
49
+
50
+ ```
51
+ /team codex "Can you review my changes?"
52
+ /team all "I'm refactoring the database schema"
53
+ ```
54
+
55
+ ## Commands
56
+
57
+ | Command | Description |
58
+ |---------|-------------|
59
+ | `talk <agent> <msg>` | Send message to agent (or "all") |
60
+ | `check <agent> [lines]` | Read agent's output |
61
+ | `list` | List configured agents |
62
+ | `add <name> <pane> [remark]` | Add agent |
63
+ | `update <name> --pane/--remark` | Update agent |
64
+ | `remove <name>` | Remove agent |
65
+ | `init` | Create tmux-team.json |
66
+ | `completion [zsh\|bash]` | Output completion script |
67
+
68
+ ## License
69
+
70
+ MIT
package/bin/tmux-team ADDED
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync, spawn } = require('child_process');
6
+
7
+ const CONFIG_FILE = './tmux-team.json';
8
+ const VERSION = '1.0.0';
9
+
10
+ // ─────────────────────────────────────────────────────────────
11
+ // Colors (only when stdout is a TTY)
12
+ // ─────────────────────────────────────────────────────────────
13
+ const isTTY = process.stdout.isTTY;
14
+ const colors = {
15
+ red: (s) => isTTY ? `\x1b[31m${s}\x1b[0m` : s,
16
+ green: (s) => isTTY ? `\x1b[32m${s}\x1b[0m` : s,
17
+ yellow: (s) => isTTY ? `\x1b[33m${s}\x1b[0m` : s,
18
+ blue: (s) => isTTY ? `\x1b[34m${s}\x1b[0m` : s,
19
+ cyan: (s) => isTTY ? `\x1b[36m${s}\x1b[0m` : s,
20
+ };
21
+
22
+ // ─────────────────────────────────────────────────────────────
23
+ // Helpers
24
+ // ─────────────────────────────────────────────────────────────
25
+ function error(msg) {
26
+ console.error(`${colors.red('❌ Error:')} ${msg}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ function success(msg) {
31
+ console.log(`${colors.green('✓')} ${msg}`);
32
+ }
33
+
34
+ function info(msg) {
35
+ console.log(`${colors.blue('ℹ')} ${msg}`);
36
+ }
37
+
38
+ function loadConfig() {
39
+ if (!fs.existsSync(CONFIG_FILE)) {
40
+ error(`${CONFIG_FILE} not found. Run 'tmux-team init' to create one.`);
41
+ }
42
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
43
+ }
44
+
45
+ function saveConfig(config) {
46
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
47
+ }
48
+
49
+ // ─────────────────────────────────────────────────────────────
50
+ // Commands
51
+ // ─────────────────────────────────────────────────────────────
52
+ function showHelp() {
53
+ console.log(`
54
+ ${colors.cyan('tmux-team')} v${VERSION} - AI agent collaboration in tmux
55
+
56
+ ${colors.yellow('USAGE')}
57
+ tmux-team <command> [arguments]
58
+
59
+ ${colors.yellow('COMMANDS')}
60
+ ${colors.green('talk')} <target> <message> Send message to an agent (or "all")
61
+ ${colors.green('check')} <target> [lines] Capture output from agent's pane
62
+ ${colors.green('list')} List all configured agents
63
+ ${colors.green('add')} <name> <pane> [remark] Add a new agent
64
+ ${colors.green('update')} <name> [options] Update an agent's config
65
+ ${colors.green('remove')} <name> Remove an agent
66
+ ${colors.green('init')} Create empty tmux-team.json
67
+ ${colors.green('init-claude')} Show Claude Code plugin install instructions
68
+ ${colors.green('completion')} Output shell completion script
69
+ ${colors.green('help')} Show this help message
70
+
71
+ ${colors.yellow('EXAMPLES')}
72
+ tmux-team talk codex "Please review the PR"
73
+ tmux-team talk all "Sync meeting in 5 minutes"
74
+ tmux-team check gemini 50
75
+ tmux-team list
76
+ tmux-team add codex 10.1 "Code review specialist"
77
+ tmux-team update codex --pane 10.2
78
+ tmux-team update codex --remark "New description"
79
+ tmux-team remove codex
80
+
81
+ ${colors.yellow('CONFIG FILE')}
82
+ Uses ./tmux-team.json in current directory:
83
+ {
84
+ "codex": { "pane": "10.1", "remark": "Code reviewer" },
85
+ "gemini": { "pane": "10.2", "remark": "Implementation" }
86
+ }
87
+ `);
88
+ }
89
+
90
+ function cmdInit() {
91
+ if (fs.existsSync(CONFIG_FILE)) {
92
+ error(`${CONFIG_FILE} already exists. Remove it first if you want to reinitialize.`);
93
+ }
94
+ fs.writeFileSync(CONFIG_FILE, '{}\n');
95
+ success(`Created ${CONFIG_FILE}`);
96
+ }
97
+
98
+ function cmdCompletion(shell) {
99
+ const zshCompletion = `#compdef tmux-team
100
+
101
+ _tmux-team() {
102
+ local -a commands agents
103
+
104
+ commands=(
105
+ 'talk:Send message to an agent'
106
+ 'check:Capture output from agent pane'
107
+ 'list:List all configured agents'
108
+ 'add:Add a new agent'
109
+ 'update:Update agent config'
110
+ 'remove:Remove an agent'
111
+ 'init:Create empty tmux-team.json'
112
+ 'init-claude:Show Claude Code plugin install instructions'
113
+ 'completion:Output shell completion script'
114
+ 'help:Show help message'
115
+ )
116
+
117
+ _get_agents() {
118
+ if [[ -f ./tmux-team.json ]]; then
119
+ agents=(\${(f)"$(node -e "console.log(Object.keys(JSON.parse(require('fs').readFileSync('./tmux-team.json'))).join('\\\\n'))" 2>/dev/null)"})
120
+ fi
121
+ }
122
+
123
+ if (( CURRENT == 2 )); then
124
+ _describe -t commands 'tmux-team commands' commands
125
+ elif (( CURRENT == 3 )); then
126
+ case \${words[2]} in
127
+ talk|check|update|remove|rm)
128
+ _get_agents
129
+ if [[ -n "\$agents" ]]; then
130
+ _describe -t agents 'agents' agents
131
+ fi
132
+ if [[ "\${words[2]}" == "talk" ]]; then
133
+ compadd "all"
134
+ fi
135
+ ;;
136
+ completion)
137
+ compadd "zsh" "bash"
138
+ ;;
139
+ esac
140
+ elif (( CURRENT == 4 )); then
141
+ case \${words[2]} in
142
+ update)
143
+ compadd -- "--pane" "--remark"
144
+ ;;
145
+ esac
146
+ fi
147
+ }
148
+
149
+ _tmux-team "\$@"`;
150
+
151
+ const bashCompletion = `_tmux_team() {
152
+ local cur prev commands agents
153
+ COMPREPLY=()
154
+ cur="\${COMP_WORDS[COMP_CWORD]}"
155
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
156
+
157
+ commands="talk check list add update remove init init-claude completion help"
158
+
159
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
160
+ COMPREPLY=( $(compgen -W "\${commands}" -- \${cur}) )
161
+ elif [[ \${COMP_CWORD} -eq 2 ]]; then
162
+ case "\${prev}" in
163
+ talk|check|update|remove|rm)
164
+ if [[ -f ./tmux-team.json ]]; then
165
+ agents=$(node -e "console.log(Object.keys(JSON.parse(require('fs').readFileSync('./tmux-team.json'))).join(' '))" 2>/dev/null)
166
+ fi
167
+ if [[ "\${prev}" == "talk" ]]; then
168
+ agents="\${agents} all"
169
+ fi
170
+ COMPREPLY=( $(compgen -W "\${agents}" -- \${cur}) )
171
+ ;;
172
+ completion)
173
+ COMPREPLY=( $(compgen -W "zsh bash" -- \${cur}) )
174
+ ;;
175
+ esac
176
+ elif [[ \${COMP_CWORD} -eq 3 ]]; then
177
+ case "\${COMP_WORDS[1]}" in
178
+ update)
179
+ COMPREPLY=( $(compgen -W "--pane --remark" -- \${cur}) )
180
+ ;;
181
+ esac
182
+ fi
183
+ }
184
+
185
+ complete -F _tmux_team tmux-team`;
186
+
187
+ if (shell === 'bash') {
188
+ console.log(bashCompletion);
189
+ } else if (shell === 'zsh') {
190
+ console.log(zshCompletion);
191
+ } else {
192
+ console.log(`
193
+ ${colors.cyan('Shell Completion Setup')}
194
+
195
+ ${colors.yellow('Zsh')} (add to ~/.zshrc):
196
+ eval "$(tmux-team completion zsh)"
197
+
198
+ ${colors.yellow('Bash')} (add to ~/.bashrc):
199
+ eval "$(tmux-team completion bash)"
200
+
201
+ Then restart your shell or run: source ~/.zshrc (or ~/.bashrc)
202
+ `);
203
+ }
204
+ }
205
+
206
+ function cmdInitClaude() {
207
+ console.log(`
208
+ ${colors.cyan('To install the tmux-team plugin in Claude Code:')}
209
+
210
+ ${colors.green('1.')} Open Claude Code
211
+ ${colors.green('2.')} Run these commands:
212
+
213
+ ${colors.yellow('/plugin marketplace add anthropics/tmux-team')}
214
+ ${colors.yellow('/plugin install tmux-team@tmux-team')}
215
+
216
+ ${colors.green('3.')} Use the slash command:
217
+
218
+ ${colors.yellow('/team codex "your message"')}
219
+ ${colors.yellow('/team gemini "please review this PR"')}
220
+ `);
221
+ }
222
+
223
+ function cmdList() {
224
+ const config = loadConfig();
225
+ const agents = Object.keys(config);
226
+
227
+ if (agents.length === 0) {
228
+ info("No agents configured. Use 'tmux-team add <name> <pane>' to add one.");
229
+ return;
230
+ }
231
+
232
+ console.log(`\n${colors.cyan('Configured agents:')}\n`);
233
+ console.log(` ${colors.yellow('NAME'.padEnd(12))} ${colors.yellow('PANE'.padEnd(10))} REMARK`);
234
+ console.log(` ${'─'.repeat(12)} ${'─'.repeat(10)} ${'─'.repeat(32)}`);
235
+
236
+ for (const [name, data] of Object.entries(config)) {
237
+ console.log(` ${name.padEnd(12)} ${data.pane.padEnd(10)} ${data.remark || '-'}`);
238
+ }
239
+ console.log();
240
+ }
241
+
242
+ function cmdAdd(name, pane, remark) {
243
+ let config = {};
244
+
245
+ if (fs.existsSync(CONFIG_FILE)) {
246
+ config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
247
+ } else {
248
+ info(`Created ${CONFIG_FILE}`);
249
+ }
250
+
251
+ if (config[name]) {
252
+ error(`Agent '${name}' already exists. Use 'tmux-team update' to modify.`);
253
+ }
254
+
255
+ config[name] = { pane };
256
+ if (remark) {
257
+ config[name].remark = remark;
258
+ }
259
+
260
+ saveConfig(config);
261
+ success(`Added agent '${name}' at pane ${pane}`);
262
+ }
263
+
264
+ function cmdUpdate(name, args) {
265
+ const config = loadConfig();
266
+
267
+ if (!config[name]) {
268
+ error(`Agent '${name}' not found. Use 'tmux-team add' to create.`);
269
+ }
270
+
271
+ let newPane = null;
272
+ let newRemark = null;
273
+
274
+ for (let i = 0; i < args.length; i++) {
275
+ if (args[i] === '--pane' && args[i + 1]) {
276
+ newPane = args[++i];
277
+ } else if (args[i] === '--remark' && args[i + 1]) {
278
+ newRemark = args[++i];
279
+ }
280
+ }
281
+
282
+ if (!newPane && !newRemark) {
283
+ error('No updates specified. Use --pane or --remark.');
284
+ }
285
+
286
+ if (newPane) {
287
+ config[name].pane = newPane;
288
+ success(`Updated '${name}' pane to ${newPane}`);
289
+ }
290
+
291
+ if (newRemark) {
292
+ config[name].remark = newRemark;
293
+ success(`Updated '${name}' remark`);
294
+ }
295
+
296
+ saveConfig(config);
297
+ }
298
+
299
+ function cmdRemove(name) {
300
+ const config = loadConfig();
301
+
302
+ if (!config[name]) {
303
+ error(`Agent '${name}' not found.`);
304
+ }
305
+
306
+ delete config[name];
307
+ saveConfig(config);
308
+ success(`Removed agent '${name}'`);
309
+ }
310
+
311
+ function sendToPane(paneId, message, agentName) {
312
+ // Special handling: Gemini doesn't like exclamation marks
313
+ if (agentName === 'gemini') {
314
+ message = message.replace(/!/g, '');
315
+ }
316
+
317
+ console.log(`${colors.green('🚀')} Sending to ${colors.cyan(agentName)} (${paneId})`);
318
+
319
+ try {
320
+ execSync(`tmux send-keys -t "${paneId}" ${JSON.stringify(message)}`, { stdio: 'inherit' });
321
+ execSync(`tmux send-keys -t "${paneId}" Enter`, { stdio: 'inherit' });
322
+ } catch (e) {
323
+ error(`Failed to send to pane ${paneId}. Is tmux running?`);
324
+ }
325
+ }
326
+
327
+ function cmdTalk(target, message) {
328
+ const config = loadConfig();
329
+
330
+ if (target === 'all') {
331
+ for (const [name, data] of Object.entries(config)) {
332
+ sendToPane(data.pane, message, name);
333
+ }
334
+ return;
335
+ }
336
+
337
+ if (!config[target]) {
338
+ const available = Object.keys(config).join(', ');
339
+ error(`Agent '${target}' not found. Available: ${available}`);
340
+ }
341
+
342
+ sendToPane(config[target].pane, message, target);
343
+ }
344
+
345
+ function cmdCheck(target, lines = 100) {
346
+ const config = loadConfig();
347
+
348
+ if (!config[target]) {
349
+ const available = Object.keys(config).join(', ');
350
+ error(`Agent '${target}' not found. Available: ${available}`);
351
+ }
352
+
353
+ const pane = config[target].pane;
354
+ console.log(`${colors.cyan(`─── Output from ${target} (${pane}) ───`)}`);
355
+
356
+ try {
357
+ execSync(`tmux capture-pane -t "${pane}" -p -S -${lines}`, { stdio: 'inherit' });
358
+ } catch (e) {
359
+ error(`Failed to capture pane ${pane}. Is tmux running?`);
360
+ }
361
+ }
362
+
363
+ // ─────────────────────────────────────────────────────────────
364
+ // Main
365
+ // ─────────────────────────────────────────────────────────────
366
+ const args = process.argv.slice(2);
367
+ const command = args[0];
368
+
369
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
370
+ showHelp();
371
+ process.exit(0);
372
+ }
373
+
374
+ switch (command) {
375
+ case 'init':
376
+ cmdInit();
377
+ break;
378
+
379
+ case 'init-claude':
380
+ cmdInitClaude();
381
+ break;
382
+
383
+ case 'completion':
384
+ cmdCompletion(args[1]);
385
+ break;
386
+
387
+ case 'list':
388
+ case 'ls':
389
+ cmdList();
390
+ break;
391
+
392
+ case 'add':
393
+ if (args.length < 3) {
394
+ error('Usage: tmux-team add <name> <pane> [remark]');
395
+ }
396
+ cmdAdd(args[1], args[2], args[3]);
397
+ break;
398
+
399
+ case 'update':
400
+ if (args.length < 2) {
401
+ error('Usage: tmux-team update <name> --pane <pane> | --remark <remark>');
402
+ }
403
+ cmdUpdate(args[1], args.slice(2));
404
+ break;
405
+
406
+ case 'remove':
407
+ case 'rm':
408
+ if (args.length < 2) {
409
+ error('Usage: tmux-team remove <name>');
410
+ }
411
+ cmdRemove(args[1]);
412
+ break;
413
+
414
+ case 'talk':
415
+ case 'send':
416
+ if (args.length < 3) {
417
+ error('Usage: tmux-team talk <target> <message>');
418
+ }
419
+ cmdTalk(args[1], args[2]);
420
+ break;
421
+
422
+ case 'check':
423
+ case 'read':
424
+ if (args.length < 2) {
425
+ error('Usage: tmux-team check <target> [lines]');
426
+ }
427
+ cmdCheck(args[1], parseInt(args[2]) || 100);
428
+ break;
429
+
430
+ default:
431
+ error(`Unknown command: ${command}. Run 'tmux-team help' for usage.`);
432
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "tmux-team",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for AI agent collaboration in tmux - manage cross-pane communication",
5
+ "bin": {
6
+ "tmux-team": "./bin/tmux-team"
7
+ },
8
+ "scripts": {
9
+ "test": "echo \"No tests yet\" && exit 0"
10
+ },
11
+ "keywords": [
12
+ "tmux",
13
+ "cli",
14
+ "ai",
15
+ "agent",
16
+ "collaboration",
17
+ "multi-agent"
18
+ ],
19
+ "author": "Ben Hsieh",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/anthropics/tmux-team.git"
24
+ },
25
+ "engines": {
26
+ "node": ">=16"
27
+ },
28
+ "os": [
29
+ "darwin",
30
+ "linux"
31
+ ],
32
+ "files": [
33
+ "bin"
34
+ ]
35
+ }