tmux-team 3.2.4 → 3.3.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.
- package/README.md +44 -29
- package/package.json +1 -1
- package/skills/README.md +1 -1
- package/src/cli.test.ts +426 -0
- package/src/cli.ts +10 -6
- package/src/commands/basic-commands.test.ts +130 -0
- package/src/commands/config-command.test.ts +79 -0
- package/src/commands/help.ts +2 -2
- package/src/commands/install.ts +2 -14
- package/src/commands/preamble.test.ts +378 -0
- package/src/commands/talk.test.ts +116 -63
- package/src/commands/talk.ts +41 -28
- package/src/commands/this.ts +20 -0
- package/src/config.test.ts +1 -1
- package/src/config.ts +1 -1
- package/src/tmux.test.ts +20 -0
- package/src/ui.test.ts +46 -0
- package/src/commands/setup.test.ts +0 -175
- package/src/commands/setup.ts +0 -163
package/README.md
CHANGED
|
@@ -16,60 +16,75 @@ npm install -g tmux-team
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
# 1. Install for your AI agent
|
|
19
|
-
|
|
19
|
+
tmt install claude # or: tmt install codex
|
|
20
20
|
|
|
21
|
-
# 2.
|
|
22
|
-
|
|
21
|
+
# 2. Go to working folder and initialize
|
|
22
|
+
tmt init
|
|
23
23
|
|
|
24
|
-
# 3.
|
|
25
|
-
|
|
24
|
+
# 3. Register agents (run inside each agent's pane)
|
|
25
|
+
tmt this claude # registers current pane as "claude"
|
|
26
|
+
tmt this codex # registers current pane as "codex"
|
|
27
|
+
|
|
28
|
+
# 4. Talk to agents
|
|
29
|
+
tmt talk codex "Review this code" # waits for response by default
|
|
30
|
+
|
|
31
|
+
# 5. Update or remove an agent
|
|
32
|
+
tmt update codex --pane 2.3
|
|
33
|
+
tmt rm codex
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> **Tip:** Most AI agents support `!` to run bash commands. From inside Claude Code, Codex, or Gemini CLI, you can run `!tmt this myname` to quickly register that pane.
|
|
37
|
+
|
|
38
|
+
## Cross-Folder Collaboration
|
|
39
|
+
|
|
40
|
+
Agents don't need to be in the same folder to collaborate. You can add an agent from one project to another:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# In project-a folder, add an agent that's running in project-b
|
|
44
|
+
tmt add codex-reviewer 5.1 # Use the pane ID from the other project
|
|
26
45
|
```
|
|
27
46
|
|
|
28
|
-
|
|
47
|
+
Find pane IDs with: `tmux display-message -p "#{pane_id}"`
|
|
29
48
|
|
|
30
49
|
## Commands
|
|
31
50
|
|
|
32
51
|
| Command | Description |
|
|
33
52
|
|---------|-------------|
|
|
34
53
|
| `install [claude\|codex]` | Install tmux-team for an AI agent |
|
|
35
|
-
| `
|
|
36
|
-
| `talk <agent> "msg"
|
|
37
|
-
| `talk all "msg"
|
|
38
|
-
| `check <agent> [lines]` | Read agent's pane output
|
|
54
|
+
| `this <name> [remark]` | Register current pane as an agent |
|
|
55
|
+
| `talk <agent> "msg"` | Send message and wait for response |
|
|
56
|
+
| `talk all "msg"` | Broadcast to all agents |
|
|
57
|
+
| `check <agent> [lines]` | Read agent's pane output |
|
|
39
58
|
| `list` | Show configured agents |
|
|
40
59
|
| `learn` | Show educational guide |
|
|
41
60
|
|
|
42
|
-
**Options for `talk
|
|
61
|
+
**Options for `talk`:**
|
|
43
62
|
- `--timeout <seconds>` - Max wait time (default: 180s)
|
|
44
63
|
- `--lines <number>` - Lines to capture from response (default: 100)
|
|
45
64
|
|
|
46
|
-
Run `
|
|
65
|
+
Run `tmt help` for all commands and options.
|
|
47
66
|
|
|
48
67
|
## Managing Your Team
|
|
49
68
|
|
|
50
69
|
Configuration lives in `tmux-team.json` in your project root.
|
|
51
70
|
|
|
52
|
-
**
|
|
53
|
-
```bash
|
|
54
|
-
tmux-team setup
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Read** - List configured agents:
|
|
71
|
+
**List** - Show configured agents:
|
|
58
72
|
```bash
|
|
59
|
-
|
|
73
|
+
tmt ls
|
|
60
74
|
```
|
|
61
75
|
|
|
62
|
-
**
|
|
76
|
+
**Edit** - Modify `tmux-team.json` directly:
|
|
63
77
|
```json
|
|
64
78
|
{
|
|
65
|
-
"codex": { "pane": "
|
|
66
|
-
"gemini": { "pane": "
|
|
79
|
+
"codex": { "pane": "1.1", "remark": "Code reviewer" },
|
|
80
|
+
"gemini": { "pane": "1.2", "remark": "Documentation" }
|
|
67
81
|
}
|
|
68
82
|
```
|
|
69
83
|
|
|
70
|
-
**
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
**Remove** - Delete an agent:
|
|
85
|
+
```bash
|
|
86
|
+
tmt rm codex
|
|
87
|
+
```
|
|
73
88
|
|
|
74
89
|
## Claude Code Plugin
|
|
75
90
|
|
|
@@ -88,8 +103,8 @@ Run this once when starting a session. Claude will understand how to coordinate
|
|
|
88
103
|
|
|
89
104
|
**`/team`** - Talk to other agents
|
|
90
105
|
```
|
|
91
|
-
/team talk codex "Review my authentication changes"
|
|
92
|
-
/team talk all "I'm starting the database migration"
|
|
106
|
+
/team talk codex "Review my authentication changes"
|
|
107
|
+
/team talk all "I'm starting the database migration"
|
|
93
108
|
/team list
|
|
94
109
|
```
|
|
95
110
|
Use this to delegate tasks, ask for reviews, or broadcast updates.
|
|
@@ -97,8 +112,8 @@ Use this to delegate tasks, ask for reviews, or broadcast updates.
|
|
|
97
112
|
## Learn More
|
|
98
113
|
|
|
99
114
|
```bash
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
tmt learn # Comprehensive guide
|
|
116
|
+
tmt help # All commands and options
|
|
102
117
|
```
|
|
103
118
|
|
|
104
119
|
## License
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -29,7 +29,7 @@ tmux-team install claude
|
|
|
29
29
|
tmux-team install codex
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
After installation, run `tmux-team
|
|
32
|
+
After installation, run `tmux-team add <name> <pane>` to register your agents, or use `tmux-team this <name>` inside each agent's tmux pane.
|
|
33
33
|
|
|
34
34
|
## Claude Code
|
|
35
35
|
|
package/src/cli.test.ts
CHANGED
|
@@ -160,4 +160,430 @@ describe('cli', () => {
|
|
|
160
160
|
expect(errSpy).toHaveBeenCalledWith(JSON.stringify({ error: 'boom' }));
|
|
161
161
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
it('routes install command', async () => {
|
|
165
|
+
vi.resetModules();
|
|
166
|
+
process.argv = ['node', 'cli', 'install', 'claude'];
|
|
167
|
+
|
|
168
|
+
const ctx = makeStubContext();
|
|
169
|
+
const installSpy = vi.fn();
|
|
170
|
+
vi.doMock('./context.js', () => ({
|
|
171
|
+
createContext: () => ctx,
|
|
172
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
173
|
+
}));
|
|
174
|
+
vi.doMock('./commands/install.js', () => ({ cmdInstall: installSpy }));
|
|
175
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
176
|
+
|
|
177
|
+
await import('./cli.js');
|
|
178
|
+
// allow async to resolve
|
|
179
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
180
|
+
|
|
181
|
+
expect(installSpy).toHaveBeenCalledWith(ctx, 'claude');
|
|
182
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('routes preamble command', async () => {
|
|
186
|
+
vi.resetModules();
|
|
187
|
+
process.argv = ['node', 'cli', 'preamble', 'show'];
|
|
188
|
+
|
|
189
|
+
const ctx = makeStubContext();
|
|
190
|
+
const preambleSpy = vi.fn();
|
|
191
|
+
vi.doMock('./context.js', () => ({
|
|
192
|
+
createContext: () => ctx,
|
|
193
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
194
|
+
}));
|
|
195
|
+
vi.doMock('./commands/preamble.js', () => ({ cmdPreamble: preambleSpy }));
|
|
196
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
197
|
+
|
|
198
|
+
await import('./cli.js');
|
|
199
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
200
|
+
|
|
201
|
+
expect(preambleSpy).toHaveBeenCalledWith(ctx, ['show']);
|
|
202
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('routes this command', async () => {
|
|
206
|
+
vi.resetModules();
|
|
207
|
+
process.argv = ['node', 'cli', 'this', 'myagent', 'remark'];
|
|
208
|
+
|
|
209
|
+
const ctx = makeStubContext();
|
|
210
|
+
const thisSpy = vi.fn();
|
|
211
|
+
vi.doMock('./context.js', () => ({
|
|
212
|
+
createContext: () => ctx,
|
|
213
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
214
|
+
}));
|
|
215
|
+
vi.doMock('./commands/this.js', () => ({ cmdThis: thisSpy }));
|
|
216
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
217
|
+
|
|
218
|
+
await import('./cli.js');
|
|
219
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
220
|
+
|
|
221
|
+
expect(thisSpy).toHaveBeenCalledWith(ctx, 'myagent', 'remark');
|
|
222
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('errors when this command is missing name', async () => {
|
|
226
|
+
vi.resetModules();
|
|
227
|
+
process.argv = ['node', 'cli', 'this'];
|
|
228
|
+
|
|
229
|
+
const ctx = makeStubContext();
|
|
230
|
+
const exitSpy = vi.fn();
|
|
231
|
+
ctx.exit = exitSpy as any;
|
|
232
|
+
vi.doMock('./context.js', () => ({
|
|
233
|
+
createContext: () => ctx,
|
|
234
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
235
|
+
}));
|
|
236
|
+
vi.doMock('./commands/this.js', () => ({ cmdThis: vi.fn() }));
|
|
237
|
+
|
|
238
|
+
await import('./cli.js');
|
|
239
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
240
|
+
|
|
241
|
+
expect(ctx.ui.error).toHaveBeenCalledWith('Usage: tmux-team this <name> [remark]');
|
|
242
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('routes update command with --pane and --remark flags', async () => {
|
|
246
|
+
vi.resetModules();
|
|
247
|
+
process.argv = ['node', 'cli', 'update', 'codex', '--pane', '2.0', '--remark', 'updated'];
|
|
248
|
+
|
|
249
|
+
const ctx = makeStubContext();
|
|
250
|
+
const updateSpy = vi.fn();
|
|
251
|
+
vi.doMock('./context.js', () => ({
|
|
252
|
+
createContext: () => ctx,
|
|
253
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
254
|
+
}));
|
|
255
|
+
vi.doMock('./commands/update.js', () => ({ cmdUpdate: updateSpy }));
|
|
256
|
+
|
|
257
|
+
await import('./cli.js');
|
|
258
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
259
|
+
|
|
260
|
+
expect(updateSpy).toHaveBeenCalledWith(ctx, 'codex', { pane: '2.0', remark: 'updated' });
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('routes init command', async () => {
|
|
264
|
+
vi.resetModules();
|
|
265
|
+
process.argv = ['node', 'cli', 'init'];
|
|
266
|
+
|
|
267
|
+
const ctx = makeStubContext();
|
|
268
|
+
const initSpy = vi.fn();
|
|
269
|
+
vi.doMock('./context.js', () => ({
|
|
270
|
+
createContext: () => ctx,
|
|
271
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
272
|
+
}));
|
|
273
|
+
vi.doMock('./commands/init.js', () => ({ cmdInit: initSpy }));
|
|
274
|
+
|
|
275
|
+
await import('./cli.js');
|
|
276
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
277
|
+
|
|
278
|
+
expect(initSpy).toHaveBeenCalledWith(ctx);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('routes list command', async () => {
|
|
282
|
+
vi.resetModules();
|
|
283
|
+
process.argv = ['node', 'cli', 'list'];
|
|
284
|
+
|
|
285
|
+
const ctx = makeStubContext();
|
|
286
|
+
const listSpy = vi.fn();
|
|
287
|
+
vi.doMock('./context.js', () => ({
|
|
288
|
+
createContext: () => ctx,
|
|
289
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
290
|
+
}));
|
|
291
|
+
vi.doMock('./commands/list.js', () => ({ cmdList: listSpy }));
|
|
292
|
+
|
|
293
|
+
await import('./cli.js');
|
|
294
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
295
|
+
|
|
296
|
+
expect(listSpy).toHaveBeenCalledWith(ctx);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('routes ls alias to list command', async () => {
|
|
300
|
+
vi.resetModules();
|
|
301
|
+
process.argv = ['node', 'cli', 'ls'];
|
|
302
|
+
|
|
303
|
+
const ctx = makeStubContext();
|
|
304
|
+
const listSpy = vi.fn();
|
|
305
|
+
vi.doMock('./context.js', () => ({
|
|
306
|
+
createContext: () => ctx,
|
|
307
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
308
|
+
}));
|
|
309
|
+
vi.doMock('./commands/list.js', () => ({ cmdList: listSpy }));
|
|
310
|
+
|
|
311
|
+
await import('./cli.js');
|
|
312
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
313
|
+
|
|
314
|
+
expect(listSpy).toHaveBeenCalledWith(ctx);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('routes add command', async () => {
|
|
318
|
+
vi.resetModules();
|
|
319
|
+
process.argv = ['node', 'cli', 'add', 'myagent', '1.0', 'remark'];
|
|
320
|
+
|
|
321
|
+
const ctx = makeStubContext();
|
|
322
|
+
const addSpy = vi.fn();
|
|
323
|
+
vi.doMock('./context.js', () => ({
|
|
324
|
+
createContext: () => ctx,
|
|
325
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
326
|
+
}));
|
|
327
|
+
vi.doMock('./commands/add.js', () => ({ cmdAdd: addSpy }));
|
|
328
|
+
|
|
329
|
+
await import('./cli.js');
|
|
330
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
331
|
+
|
|
332
|
+
expect(addSpy).toHaveBeenCalledWith(ctx, 'myagent', '1.0', 'remark');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('routes config command', async () => {
|
|
336
|
+
vi.resetModules();
|
|
337
|
+
process.argv = ['node', 'cli', 'config', 'get', 'mode'];
|
|
338
|
+
|
|
339
|
+
const ctx = makeStubContext();
|
|
340
|
+
const configSpy = vi.fn();
|
|
341
|
+
vi.doMock('./context.js', () => ({
|
|
342
|
+
createContext: () => ctx,
|
|
343
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
344
|
+
}));
|
|
345
|
+
vi.doMock('./commands/config.js', () => ({ cmdConfig: configSpy }));
|
|
346
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
347
|
+
|
|
348
|
+
await import('./cli.js');
|
|
349
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
350
|
+
|
|
351
|
+
expect(configSpy).toHaveBeenCalledWith(ctx, ['get', 'mode']);
|
|
352
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('parses --timeout flag with seconds', async () => {
|
|
356
|
+
vi.resetModules();
|
|
357
|
+
process.argv = ['node', 'cli', 'talk', 'claude', 'hi', '--timeout', '30'];
|
|
358
|
+
|
|
359
|
+
const ctx = makeStubContext();
|
|
360
|
+
const talkSpy = vi.fn();
|
|
361
|
+
vi.doMock('./context.js', () => ({
|
|
362
|
+
createContext: (opts: any) => {
|
|
363
|
+
ctx.flags = opts.flags;
|
|
364
|
+
return ctx;
|
|
365
|
+
},
|
|
366
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
367
|
+
}));
|
|
368
|
+
vi.doMock('./commands/talk.js', () => ({ cmdTalk: talkSpy }));
|
|
369
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
370
|
+
|
|
371
|
+
await import('./cli.js');
|
|
372
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
373
|
+
|
|
374
|
+
expect(ctx.flags.timeout).toBe(30);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('parses --timeout flag with ms suffix', async () => {
|
|
378
|
+
vi.resetModules();
|
|
379
|
+
process.argv = ['node', 'cli', 'talk', 'claude', 'hi', '--timeout', '500ms'];
|
|
380
|
+
|
|
381
|
+
const ctx = makeStubContext();
|
|
382
|
+
const talkSpy = vi.fn();
|
|
383
|
+
vi.doMock('./context.js', () => ({
|
|
384
|
+
createContext: (opts: any) => {
|
|
385
|
+
ctx.flags = opts.flags;
|
|
386
|
+
return ctx;
|
|
387
|
+
},
|
|
388
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
389
|
+
}));
|
|
390
|
+
vi.doMock('./commands/talk.js', () => ({ cmdTalk: talkSpy }));
|
|
391
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
392
|
+
|
|
393
|
+
await import('./cli.js');
|
|
394
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
395
|
+
|
|
396
|
+
expect(ctx.flags.timeout).toBe(0.5);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('parses --lines flag', async () => {
|
|
400
|
+
vi.resetModules();
|
|
401
|
+
process.argv = ['node', 'cli', 'talk', 'claude', 'hi', '--wait', '--lines', '50'];
|
|
402
|
+
|
|
403
|
+
const ctx = makeStubContext();
|
|
404
|
+
const talkSpy = vi.fn();
|
|
405
|
+
vi.doMock('./context.js', () => ({
|
|
406
|
+
createContext: (opts: any) => {
|
|
407
|
+
ctx.flags = opts.flags;
|
|
408
|
+
return ctx;
|
|
409
|
+
},
|
|
410
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
411
|
+
}));
|
|
412
|
+
vi.doMock('./commands/talk.js', () => ({ cmdTalk: talkSpy }));
|
|
413
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
414
|
+
|
|
415
|
+
await import('./cli.js');
|
|
416
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
417
|
+
|
|
418
|
+
expect(ctx.flags.lines).toBe(50);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('parses --no-preamble flag', async () => {
|
|
422
|
+
vi.resetModules();
|
|
423
|
+
process.argv = ['node', 'cli', 'talk', 'claude', 'hi', '--no-preamble'];
|
|
424
|
+
|
|
425
|
+
const ctx = makeStubContext();
|
|
426
|
+
const talkSpy = vi.fn();
|
|
427
|
+
vi.doMock('./context.js', () => ({
|
|
428
|
+
createContext: (opts: any) => {
|
|
429
|
+
ctx.flags = opts.flags;
|
|
430
|
+
return ctx;
|
|
431
|
+
},
|
|
432
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
433
|
+
}));
|
|
434
|
+
vi.doMock('./commands/talk.js', () => ({ cmdTalk: talkSpy }));
|
|
435
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
436
|
+
|
|
437
|
+
await import('./cli.js');
|
|
438
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
439
|
+
|
|
440
|
+
expect(ctx.flags.noPreamble).toBe(true);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('routes check command with lines argument', async () => {
|
|
444
|
+
vi.resetModules();
|
|
445
|
+
process.argv = ['node', 'cli', 'check', 'claude', '50'];
|
|
446
|
+
|
|
447
|
+
const ctx = makeStubContext();
|
|
448
|
+
const checkSpy = vi.fn();
|
|
449
|
+
vi.doMock('./context.js', () => ({
|
|
450
|
+
createContext: () => ctx,
|
|
451
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
452
|
+
}));
|
|
453
|
+
vi.doMock('./commands/check.js', () => ({ cmdCheck: checkSpy }));
|
|
454
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
455
|
+
|
|
456
|
+
await import('./cli.js');
|
|
457
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
458
|
+
|
|
459
|
+
expect(checkSpy).toHaveBeenCalledWith(ctx, 'claude', 50);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('routes update command with --pane= syntax', async () => {
|
|
463
|
+
vi.resetModules();
|
|
464
|
+
process.argv = ['node', 'cli', 'update', 'claude', '--pane=2.0'];
|
|
465
|
+
|
|
466
|
+
const ctx = makeStubContext();
|
|
467
|
+
const updateSpy = vi.fn();
|
|
468
|
+
vi.doMock('./context.js', () => ({
|
|
469
|
+
createContext: () => ctx,
|
|
470
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
471
|
+
}));
|
|
472
|
+
vi.doMock('./commands/update.js', () => ({ cmdUpdate: updateSpy }));
|
|
473
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
474
|
+
|
|
475
|
+
await import('./cli.js');
|
|
476
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
477
|
+
|
|
478
|
+
expect(updateSpy).toHaveBeenCalledWith(ctx, 'claude', { pane: '2.0' });
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('routes update command with --remark= syntax', async () => {
|
|
482
|
+
vi.resetModules();
|
|
483
|
+
process.argv = ['node', 'cli', 'update', 'claude', '--remark=new remark'];
|
|
484
|
+
|
|
485
|
+
const ctx = makeStubContext();
|
|
486
|
+
const updateSpy = vi.fn();
|
|
487
|
+
vi.doMock('./context.js', () => ({
|
|
488
|
+
createContext: () => ctx,
|
|
489
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
490
|
+
}));
|
|
491
|
+
vi.doMock('./commands/update.js', () => ({ cmdUpdate: updateSpy }));
|
|
492
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
493
|
+
|
|
494
|
+
await import('./cli.js');
|
|
495
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
496
|
+
|
|
497
|
+
expect(updateSpy).toHaveBeenCalledWith(ctx, 'claude', { remark: 'new remark' });
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('errors on talk with missing arguments', async () => {
|
|
501
|
+
vi.resetModules();
|
|
502
|
+
process.argv = ['node', 'cli', 'talk', 'claude']; // missing message
|
|
503
|
+
|
|
504
|
+
const ctx = makeStubContext();
|
|
505
|
+
vi.doMock('./context.js', () => ({
|
|
506
|
+
createContext: () => ctx,
|
|
507
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
508
|
+
}));
|
|
509
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
510
|
+
|
|
511
|
+
await import('./cli.js');
|
|
512
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
513
|
+
|
|
514
|
+
expect(ctx.ui.error).toHaveBeenCalled();
|
|
515
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('errors on add with missing arguments', async () => {
|
|
519
|
+
vi.resetModules();
|
|
520
|
+
process.argv = ['node', 'cli', 'add', 'claude']; // missing pane
|
|
521
|
+
|
|
522
|
+
const ctx = makeStubContext();
|
|
523
|
+
vi.doMock('./context.js', () => ({
|
|
524
|
+
createContext: () => ctx,
|
|
525
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
526
|
+
}));
|
|
527
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
528
|
+
|
|
529
|
+
await import('./cli.js');
|
|
530
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
531
|
+
|
|
532
|
+
expect(ctx.ui.error).toHaveBeenCalled();
|
|
533
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('errors on update with missing arguments', async () => {
|
|
537
|
+
vi.resetModules();
|
|
538
|
+
process.argv = ['node', 'cli', 'update']; // missing name
|
|
539
|
+
|
|
540
|
+
const ctx = makeStubContext();
|
|
541
|
+
vi.doMock('./context.js', () => ({
|
|
542
|
+
createContext: () => ctx,
|
|
543
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
544
|
+
}));
|
|
545
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
546
|
+
|
|
547
|
+
await import('./cli.js');
|
|
548
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
549
|
+
|
|
550
|
+
expect(ctx.ui.error).toHaveBeenCalled();
|
|
551
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('errors on remove with missing arguments', async () => {
|
|
555
|
+
vi.resetModules();
|
|
556
|
+
process.argv = ['node', 'cli', 'remove']; // missing name
|
|
557
|
+
|
|
558
|
+
const ctx = makeStubContext();
|
|
559
|
+
vi.doMock('./context.js', () => ({
|
|
560
|
+
createContext: () => ctx,
|
|
561
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
562
|
+
}));
|
|
563
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
564
|
+
|
|
565
|
+
await import('./cli.js');
|
|
566
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
567
|
+
|
|
568
|
+
expect(ctx.ui.error).toHaveBeenCalled();
|
|
569
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('errors on check with missing arguments', async () => {
|
|
573
|
+
vi.resetModules();
|
|
574
|
+
process.argv = ['node', 'cli', 'check']; // missing target
|
|
575
|
+
|
|
576
|
+
const ctx = makeStubContext();
|
|
577
|
+
vi.doMock('./context.js', () => ({
|
|
578
|
+
createContext: () => ctx,
|
|
579
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
580
|
+
}));
|
|
581
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
582
|
+
|
|
583
|
+
await import('./cli.js');
|
|
584
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
585
|
+
|
|
586
|
+
expect(ctx.ui.error).toHaveBeenCalled();
|
|
587
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
588
|
+
});
|
|
163
589
|
});
|
package/src/cli.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { cmdCompletion } from './commands/completion.js';
|
|
|
20
20
|
import { cmdConfig } from './commands/config.js';
|
|
21
21
|
import { cmdPreamble } from './commands/preamble.js';
|
|
22
22
|
import { cmdInstall } from './commands/install.js';
|
|
23
|
-
import { cmdSetup } from './commands/setup.js';
|
|
24
23
|
import { cmdLearn } from './commands/learn.js';
|
|
24
|
+
import { cmdThis } from './commands/this.js';
|
|
25
25
|
|
|
26
26
|
// ─────────────────────────────────────────────────────────────
|
|
27
27
|
// Argument parsing
|
|
@@ -147,7 +147,7 @@ function main(): void {
|
|
|
147
147
|
const ctx = createContext({ argv, flags });
|
|
148
148
|
|
|
149
149
|
// Warn if not in tmux for commands that require it
|
|
150
|
-
const TMUX_REQUIRED_COMMANDS = ['talk', 'send', 'check', 'read', '
|
|
150
|
+
const TMUX_REQUIRED_COMMANDS = ['talk', 'send', 'check', 'read', 'this'];
|
|
151
151
|
if (!process.env.TMUX && TMUX_REQUIRED_COMMANDS.includes(command)) {
|
|
152
152
|
ctx.ui.warn('Not running inside tmux. Some features may not work.');
|
|
153
153
|
}
|
|
@@ -202,6 +202,14 @@ function main(): void {
|
|
|
202
202
|
cmdRemove(ctx, args[0]);
|
|
203
203
|
break;
|
|
204
204
|
|
|
205
|
+
case 'this':
|
|
206
|
+
if (args.length < 1) {
|
|
207
|
+
ctx.ui.error('Usage: tmux-team this <name> [remark]');
|
|
208
|
+
ctx.exit(ExitCodes.ERROR);
|
|
209
|
+
}
|
|
210
|
+
cmdThis(ctx, args[0], args[1]);
|
|
211
|
+
break;
|
|
212
|
+
|
|
205
213
|
case 'talk':
|
|
206
214
|
case 'send':
|
|
207
215
|
if (args.length < 2) {
|
|
@@ -232,10 +240,6 @@ function main(): void {
|
|
|
232
240
|
await cmdInstall(ctx, args[0]);
|
|
233
241
|
break;
|
|
234
242
|
|
|
235
|
-
case 'setup':
|
|
236
|
-
await cmdSetup(ctx);
|
|
237
|
-
break;
|
|
238
|
-
|
|
239
243
|
case 'learn':
|
|
240
244
|
cmdLearn();
|
|
241
245
|
break;
|