tmux-team 3.0.0 β 3.1.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 +56 -248
- package/package.json +5 -3
- package/skills/README.md +9 -10
- package/src/cli.test.ts +163 -0
- package/src/cli.ts +29 -18
- package/src/commands/basic-commands.test.ts +252 -0
- package/src/commands/config-command.test.ts +116 -0
- package/src/commands/help.ts +19 -3
- package/src/commands/install.test.ts +205 -0
- package/src/commands/install.ts +207 -0
- package/src/commands/learn.ts +80 -0
- package/src/commands/setup.test.ts +175 -0
- package/src/commands/setup.ts +163 -0
- package/src/commands/talk.test.ts +169 -101
- package/src/commands/talk.ts +186 -98
- package/src/context.test.ts +68 -0
- package/src/identity.test.ts +70 -0
- package/src/state.test.ts +14 -0
- package/src/tmux.test.ts +50 -0
- package/src/tmux.ts +66 -2
- package/src/types.ts +10 -1
- package/src/ui.test.ts +63 -0
- package/src/version.test.ts +31 -0
- package/src/version.ts +1 -1
- package/src/commands/install-skill.ts +0 -148
package/README.md
CHANGED
|
@@ -1,298 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tmux-team
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Coordinate AI agents (Claude, Codex, Gemini) running in tmux panes. Send messages, wait for responses, broadcast to all.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## π The Problem
|
|
10
|
-
|
|
11
|
-
As we move from "Chat with an AI" to "Orchestrating a Team," we face major friction points:
|
|
12
|
-
|
|
13
|
-
1. **Isolation** β Agents in different panes (Claude, Gemini, local LLMs) have no way to talk to each other
|
|
14
|
-
2. **Synchronization** β Humans are stuck in a "manual polling" loopβwaiting for an agent to finish before copying its output to the next pane
|
|
15
|
-
3. **Tool Restrictions** β AI agents operate under tool whitelists; using `sleep` or arbitrary shell commands is dangerous or blocked
|
|
16
|
-
4. **Token Waste** β Repeated polling instructions burn context tokens unnecessarily
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## π Our Niche: The Universal Transport Layer
|
|
21
|
-
|
|
22
|
-
Unlike heavyweight frameworks that require specific SDKs or cloud infrastructure, tmux-team treats the **terminal pane as the universal interface**.
|
|
23
|
-
|
|
24
|
-
- **Model Agnostic** β Works with Claude Code, Gemini CLI, Codex, Aider, or any CLI tool
|
|
25
|
-
- **Zero Infrastructure** β No servers, no MCP setup, no complex configuration. If it runs in tmux, tmux-team can talk to it
|
|
26
|
-
- **Whitelist-Friendly** β A single `tmux-team talk:*` prefix covers all operations, keeping AI tool permissions simple and safe
|
|
27
|
-
- **Local-First** β Per-project `tmux-team.json` lives with your repo; global config in `~/.config/tmux-team/`
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## π§ Design Philosophy
|
|
32
|
-
|
|
33
|
-
> *These principles guide our design decisions.*
|
|
34
|
-
|
|
35
|
-
### 1. Deterministic Transport (`--delay` vs. `sleep`)
|
|
36
|
-
|
|
37
|
-
**The Problem**: Tool allowlists typically approve one safe command (`tmux-team talk ...`) but not arbitrary shell commands. Using `sleep` is often blocked by security policies.
|
|
38
|
-
|
|
39
|
-
**The Why**: Internal delay keeps the workflow as a single tool call. No shell dependency, no policy friction.
|
|
40
|
-
|
|
41
|
-
### 2. Stateless Handshakes (The "Nonce" Strategy)
|
|
42
|
-
|
|
43
|
-
**The Problem**: Terminal panes are streams, not RPC channels. A simple `[DONE]` string could already be in scrollback.
|
|
44
|
-
|
|
45
|
-
**The Why**: We use a unique **Nonce** for every request: `{tmux-team-end:8f3a}`.
|
|
46
|
-
- **Collision Avoidance** β Prevents matching markers from previous turns
|
|
47
|
-
- **Completion Safety** β Ensures the agent has truly finished
|
|
48
|
-
- **Zero-API RPC** β Creates request/response semantics over a standard TTY
|
|
49
|
-
|
|
50
|
-
### 3. Context Injection (Preambles)
|
|
51
|
-
|
|
52
|
-
**The Problem**: AI agents are prone to "instruction drift." Over a long session, they might forget constraints.
|
|
53
|
-
|
|
54
|
-
**The Why**: Preambles act as a forced system prompt for CLI environments. By injecting these "hidden instructions" at the transport level, we ensure the agent remains in character.
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## π¦ Installation
|
|
5
|
+
## Install
|
|
59
6
|
|
|
60
7
|
```bash
|
|
61
8
|
npm install -g tmux-team
|
|
62
9
|
```
|
|
63
10
|
|
|
64
|
-
**Requirements:** Node.js >= 18, tmux
|
|
65
|
-
|
|
66
|
-
### Shell Completion
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
# Zsh (add to ~/.zshrc)
|
|
70
|
-
eval "$(tmux-team completion zsh)"
|
|
71
|
-
|
|
72
|
-
# Bash (add to ~/.bashrc)
|
|
73
|
-
eval "$(tmux-team completion bash)"
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Claude Code Plugin
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
/plugin marketplace add wkh237/tmux-team
|
|
80
|
-
/plugin install tmux-team@tmux-team
|
|
81
|
-
```
|
|
11
|
+
**Requirements:** Node.js >= 18, tmux
|
|
82
12
|
|
|
83
|
-
|
|
13
|
+
**Alias:** `tmt` (shorthand for `tmux-team`)
|
|
84
14
|
|
|
85
|
-
|
|
15
|
+
## Quick Start
|
|
86
16
|
|
|
87
17
|
```bash
|
|
88
|
-
# Install for
|
|
89
|
-
tmux-team install-
|
|
90
|
-
|
|
91
|
-
# Install for OpenAI Codex (user-wide)
|
|
92
|
-
tmux-team install-skill codex
|
|
93
|
-
|
|
94
|
-
# Install to project directory instead
|
|
95
|
-
tmux-team install-skill claude --local
|
|
96
|
-
tmux-team install-skill codex --local
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
See [skills/README.md](./skills/README.md) for detailed instructions.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## β¨οΈ Quick Start
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
# Initialize config
|
|
107
|
-
tmux-team init
|
|
108
|
-
|
|
109
|
-
# Register your agents (name + tmux pane ID)
|
|
110
|
-
tmux-team add claude 10.0 "Frontend specialist"
|
|
111
|
-
tmux-team add codex 10.1 "Backend engineer"
|
|
112
|
-
tmux-team add gemini 10.2 "Code reviewer"
|
|
113
|
-
|
|
114
|
-
# Send messages
|
|
115
|
-
tmux-team talk codex "Review the auth module and suggest improvements"
|
|
116
|
-
tmux-team talk all "Starting the refactor now"
|
|
18
|
+
# 1. Install for your AI agent
|
|
19
|
+
tmux-team install claude # or: tmux-team install codex
|
|
117
20
|
|
|
118
|
-
#
|
|
119
|
-
tmux-team
|
|
120
|
-
tmux-team check codex 200 # More lines if needed
|
|
21
|
+
# 2. Run the setup wizard (auto-detects panes)
|
|
22
|
+
tmux-team setup
|
|
121
23
|
|
|
122
|
-
#
|
|
123
|
-
tmux-team
|
|
124
|
-
tmux-team update codex --remark "Now handling tests"
|
|
125
|
-
tmux-team remove gemini
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### From Claude Code
|
|
129
|
-
|
|
130
|
-
Once the plugin is installed, coordinate directly from your Claude Code session:
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
/tmux-team:team codex "Can you review my changes?"
|
|
134
|
-
/tmux-team:team all "I'm starting the database migration"
|
|
24
|
+
# 3. Talk to agents
|
|
25
|
+
tmux-team talk codex "Review this code" --wait
|
|
135
26
|
```
|
|
136
27
|
|
|
137
|
-
|
|
28
|
+
The `--wait` flag blocks until the agent responds, returning the response directly.
|
|
138
29
|
|
|
139
|
-
##
|
|
30
|
+
## Commands
|
|
140
31
|
|
|
141
32
|
| Command | Description |
|
|
142
33
|
|---------|-------------|
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `talk
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
| `remove <name>` | Unregister an agent |
|
|
151
|
-
| `init` | Create `tmux-team.json` in current directory |
|
|
152
|
-
| `config [show/set/clear]` | View/modify settings |
|
|
153
|
-
| `preamble [show/set/clear]` | Manage agent preambles |
|
|
154
|
-
| `install-skill <agent>` | Install skill for Claude/Codex (--local/--user) |
|
|
155
|
-
| `completion [zsh\|bash]` | Output shell completion script |
|
|
34
|
+
| `install [claude\|codex]` | Install tmux-team for an AI agent |
|
|
35
|
+
| `setup` | Interactive wizard to configure agents |
|
|
36
|
+
| `talk <agent> "msg" --wait` | Send message and wait for response |
|
|
37
|
+
| `talk all "msg" --wait` | Broadcast to all agents |
|
|
38
|
+
| `check <agent> [lines]` | Read agent's pane output (fallback if --wait times out) |
|
|
39
|
+
| `list` | Show configured agents |
|
|
40
|
+
| `learn` | Show educational guide |
|
|
156
41
|
|
|
157
|
-
|
|
42
|
+
**Options for `talk --wait`:**
|
|
43
|
+
- `--timeout <seconds>` - Max wait time (default: 180s)
|
|
44
|
+
- `--lines <number>` - Lines to capture from response (default: 100)
|
|
158
45
|
|
|
159
|
-
|
|
46
|
+
Run `tmux-team help` for all commands and options.
|
|
160
47
|
|
|
161
|
-
|
|
48
|
+
## Managing Your Team
|
|
162
49
|
|
|
163
|
-
|
|
50
|
+
Configuration lives in `tmux-team.json` in your project root.
|
|
164
51
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"pane": "10.0",
|
|
169
|
-
"remark": "Frontend specialist",
|
|
170
|
-
"preamble": "Focus on UI components. Ask for review before merging."
|
|
171
|
-
},
|
|
172
|
-
"codex": {
|
|
173
|
-
"pane": "10.1",
|
|
174
|
-
"remark": "Code reviewer",
|
|
175
|
-
"preamble": "You are the code quality guard. Review changes thoroughly."
|
|
176
|
-
}
|
|
177
|
-
}
|
|
52
|
+
**Create** - Run the setup wizard to auto-detect agents:
|
|
53
|
+
```bash
|
|
54
|
+
tmux-team setup
|
|
178
55
|
```
|
|
179
56
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
| `preamble` | Hidden instructions prepended to every message |
|
|
185
|
-
|
|
186
|
-
### Global Config (`~/.config/tmux-team/config.json`)
|
|
187
|
-
|
|
188
|
-
Global settings that apply to all projects:
|
|
57
|
+
**Read** - List configured agents:
|
|
58
|
+
```bash
|
|
59
|
+
tmux-team list
|
|
60
|
+
```
|
|
189
61
|
|
|
62
|
+
**Update** - Edit `tmux-team.json` directly or re-run setup:
|
|
190
63
|
```json
|
|
191
64
|
{
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"defaults": {
|
|
195
|
-
"timeout": 180,
|
|
196
|
-
"pollInterval": 1,
|
|
197
|
-
"captureLines": 100,
|
|
198
|
-
"preambleEvery": 3
|
|
199
|
-
}
|
|
65
|
+
"codex": { "pane": "%1", "remark": "Code reviewer" },
|
|
66
|
+
"gemini": { "pane": "%2", "remark": "Documentation" }
|
|
200
67
|
}
|
|
201
68
|
```
|
|
202
69
|
|
|
203
|
-
|
|
204
|
-
|-------|-------------|
|
|
205
|
-
| `mode` | Default mode: `polling` (manual check) or `wait` (auto-wait) |
|
|
206
|
-
| `preambleMode` | `always` (inject preambles) or `disabled` |
|
|
207
|
-
| `defaults.timeout` | Default --wait timeout in seconds |
|
|
208
|
-
| `defaults.pollInterval` | Polling interval in seconds |
|
|
209
|
-
| `defaults.captureLines` | Default lines for `check` command |
|
|
210
|
-
| `defaults.preambleEvery` | Inject preamble every N messages (default: 3) |
|
|
211
|
-
|
|
212
|
-
---
|
|
70
|
+
**Delete** - Remove an agent entry from `tmux-team.json` or delete the file entirely.
|
|
213
71
|
|
|
214
|
-
|
|
72
|
+
Find pane IDs: `tmux display-message -p "#{pane_id}"`
|
|
215
73
|
|
|
216
|
-
|
|
74
|
+
## Claude Code Plugin
|
|
217
75
|
|
|
218
|
-
```bash
|
|
219
|
-
# Delay before sending (safe alternative to sleep)
|
|
220
|
-
tmux-team talk codex "message" --delay 5
|
|
221
|
-
|
|
222
|
-
# Wait for response with nonce-based completion detection
|
|
223
|
-
tmux-team talk codex "message" --wait --timeout 60
|
|
224
76
|
```
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Inject hidden instructions into every message via local `tmux-team.json`:
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{
|
|
232
|
-
"gemini": {
|
|
233
|
-
"pane": "10.2",
|
|
234
|
-
"preamble": "Always explain your reasoning. Do not edit files directly."
|
|
235
|
-
}
|
|
236
|
-
}
|
|
77
|
+
/plugin marketplace add wkh237/tmux-team
|
|
78
|
+
/plugin install tmux-team
|
|
237
79
|
```
|
|
238
80
|
|
|
239
|
-
|
|
81
|
+
Gives you two slash commands:
|
|
240
82
|
|
|
241
|
-
|
|
242
|
-
tmux-team preamble show gemini # View current preamble
|
|
243
|
-
tmux-team preamble set gemini "Be concise" # Set preamble
|
|
244
|
-
tmux-team preamble clear gemini # Remove preamble
|
|
83
|
+
**`/learn`** - Teach Claude how to use tmux-team
|
|
245
84
|
```
|
|
85
|
+
/learn
|
|
86
|
+
```
|
|
87
|
+
Run this once when starting a session. Claude will understand how to coordinate with other agents.
|
|
246
88
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
- **Not an orchestrator** β No automatic agent selection or routing
|
|
254
|
-
- **Not a session manager** β Doesn't create/manage tmux sessions
|
|
255
|
-
- **Not an LLM wrapper** β Doesn't process or route messages through AI
|
|
256
|
-
|
|
257
|
-
It's the plumbing layer that lets humans and AI agents coordinate via tmux, nothing more.
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
## π Command Reference
|
|
262
|
-
|
|
263
|
-
### talk Options
|
|
264
|
-
|
|
265
|
-
| Option | Description |
|
|
266
|
-
|--------|-------------|
|
|
267
|
-
| `--delay <seconds>` | Wait before sending (whitelist-friendly alternative to `sleep`) |
|
|
268
|
-
| `--wait` | Block until agent responds (nonce-based completion detection) |
|
|
269
|
-
| `--timeout <seconds>` | Max wait time (default: 180s) |
|
|
270
|
-
| `--no-preamble` | Skip agent preamble for this message |
|
|
271
|
-
|
|
272
|
-
### config Command
|
|
273
|
-
|
|
274
|
-
```bash
|
|
275
|
-
tmux-team config show # Show current config
|
|
276
|
-
tmux-team config set mode wait # Enable wait mode
|
|
277
|
-
tmux-team config set preambleMode disabled # Disable preambles
|
|
278
|
-
tmux-team config set preambleEvery 5 # Inject preamble every 5 messages
|
|
279
|
-
tmux-team config clear <key> # Clear a config value
|
|
89
|
+
**`/team`** - Talk to other agents
|
|
90
|
+
```
|
|
91
|
+
/team talk codex "Review my authentication changes" --wait
|
|
92
|
+
/team talk all "I'm starting the database migration" --wait
|
|
93
|
+
/team list
|
|
280
94
|
```
|
|
95
|
+
Use this to delegate tasks, ask for reviews, or broadcast updates.
|
|
281
96
|
|
|
282
|
-
|
|
97
|
+
## Learn More
|
|
283
98
|
|
|
284
99
|
```bash
|
|
285
|
-
tmux-team
|
|
286
|
-
tmux-team
|
|
287
|
-
tmux-team preamble clear <agent> # Clear agent's preamble
|
|
100
|
+
tmux-team learn # Comprehensive guide
|
|
101
|
+
tmux-team help # All commands and options
|
|
288
102
|
```
|
|
289
103
|
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
*Built for developers who live in the terminal and want their AIs to do the same.*
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
104
|
## License
|
|
297
105
|
|
|
298
106
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-team",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "CLI tool for AI agent collaboration in tmux - manage cross-pane communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "tsx src/cli.ts",
|
|
12
12
|
"tmt": "./bin/tmux-team",
|
|
13
|
-
"test": "
|
|
14
|
-
"test:
|
|
13
|
+
"test": "npm run test:run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:run": "vitest run --coverage && node scripts/check-coverage.mjs --threshold 95",
|
|
15
16
|
"lint": "oxlint src/",
|
|
16
17
|
"lint:fix": "oxlint src/ --fix",
|
|
17
18
|
"format": "prettier --write src/",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^25.0.3",
|
|
54
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
53
55
|
"oxlint": "^1.34.0",
|
|
54
56
|
"prettier": "^3.7.4",
|
|
55
57
|
"typescript": "^5.3.0",
|
package/skills/README.md
CHANGED
|
@@ -16,22 +16,21 @@ The easiest way to add tmux-team to Claude Code is via the plugin system:
|
|
|
16
16
|
|
|
17
17
|
This gives you `/team` and `/learn` slash commands automatically.
|
|
18
18
|
|
|
19
|
-
## Quick Install
|
|
19
|
+
## Quick Install
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Use the interactive install command:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
#
|
|
25
|
-
tmux-team install
|
|
24
|
+
# Auto-detect environment and install
|
|
25
|
+
tmux-team install
|
|
26
26
|
|
|
27
|
-
#
|
|
28
|
-
tmux-team install
|
|
29
|
-
|
|
30
|
-
# Install to project directory (local scope)
|
|
31
|
-
tmux-team install-skill claude --local
|
|
32
|
-
tmux-team install-skill codex --local
|
|
27
|
+
# Or specify agent directly
|
|
28
|
+
tmux-team install claude
|
|
29
|
+
tmux-team install codex
|
|
33
30
|
```
|
|
34
31
|
|
|
32
|
+
After installation, run `tmux-team setup` to configure your agents interactively.
|
|
33
|
+
|
|
35
34
|
## Claude Code
|
|
36
35
|
|
|
37
36
|
Claude Code uses slash commands stored in `~/.claude/commands/` (user) or `.claude/commands/` (local).
|
package/src/cli.test.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import type { Context } from './types.js';
|
|
3
|
+
|
|
4
|
+
function makeStubContext(): Context {
|
|
5
|
+
return {
|
|
6
|
+
argv: [],
|
|
7
|
+
flags: { json: false, verbose: false },
|
|
8
|
+
ui: {
|
|
9
|
+
info: vi.fn(),
|
|
10
|
+
success: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
error: vi.fn(),
|
|
13
|
+
table: vi.fn(),
|
|
14
|
+
json: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
config: {
|
|
17
|
+
mode: 'polling',
|
|
18
|
+
preambleMode: 'always',
|
|
19
|
+
defaults: { timeout: 180, pollInterval: 1, captureLines: 100, preambleEvery: 3 },
|
|
20
|
+
agents: {},
|
|
21
|
+
paneRegistry: {},
|
|
22
|
+
},
|
|
23
|
+
tmux: {
|
|
24
|
+
send: vi.fn(),
|
|
25
|
+
capture: vi.fn(),
|
|
26
|
+
listPanes: vi.fn(() => []),
|
|
27
|
+
getCurrentPaneId: vi.fn(() => null),
|
|
28
|
+
},
|
|
29
|
+
paths: {
|
|
30
|
+
globalDir: '/g',
|
|
31
|
+
globalConfig: '/g/c.json',
|
|
32
|
+
localConfig: '/p/t.json',
|
|
33
|
+
stateFile: '/g/s.json',
|
|
34
|
+
},
|
|
35
|
+
exit: ((code: number) => {
|
|
36
|
+
const err = new Error(`exit(${code})`);
|
|
37
|
+
(err as Error & { exitCode: number }).exitCode = code;
|
|
38
|
+
throw err;
|
|
39
|
+
}) as any,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('cli', () => {
|
|
44
|
+
const originalArgv = process.argv;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
process.argv = originalArgv;
|
|
52
|
+
vi.restoreAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('prints completion for bash', async () => {
|
|
56
|
+
vi.resetModules();
|
|
57
|
+
process.argv = ['node', 'cli', 'completion', 'bash'];
|
|
58
|
+
|
|
59
|
+
vi.doMock('./context.js', () => ({
|
|
60
|
+
createContext: () => makeStubContext(),
|
|
61
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
62
|
+
}));
|
|
63
|
+
vi.doMock('./commands/completion.js', () => ({
|
|
64
|
+
cmdCompletion: (shell?: string) => {
|
|
65
|
+
console.log(`completion:${shell}`);
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
70
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
71
|
+
|
|
72
|
+
await import('./cli.js');
|
|
73
|
+
expect(logSpy).toHaveBeenCalledWith('completion:bash');
|
|
74
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('errors on invalid time format', async () => {
|
|
78
|
+
vi.resetModules();
|
|
79
|
+
process.argv = ['node', 'cli', 'talk', 'codex', 'hi', '--delay', 'abc'];
|
|
80
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code?: number) => {
|
|
81
|
+
throw new Error(`exit(${code})`);
|
|
82
|
+
}) as any);
|
|
83
|
+
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
84
|
+
|
|
85
|
+
await expect(import('./cli.js')).rejects.toThrow('exit(1)');
|
|
86
|
+
expect(errSpy).toHaveBeenCalled();
|
|
87
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('routes unknown command to ctx.ui.error and exits', async () => {
|
|
91
|
+
vi.resetModules();
|
|
92
|
+
process.argv = ['node', 'cli', 'nope'];
|
|
93
|
+
|
|
94
|
+
const ctx = makeStubContext();
|
|
95
|
+
vi.doMock('./context.js', () => ({
|
|
96
|
+
createContext: () => ctx,
|
|
97
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
101
|
+
await import('./cli.js');
|
|
102
|
+
expect(ctx.ui.error).toHaveBeenCalled();
|
|
103
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('handles --version by printing VERSION', async () => {
|
|
107
|
+
vi.resetModules();
|
|
108
|
+
process.argv = ['node', 'cli', '--version'];
|
|
109
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
110
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
111
|
+
|
|
112
|
+
await import('./cli.js');
|
|
113
|
+
// allow the dynamic import to resolve
|
|
114
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
115
|
+
expect(logSpy).toHaveBeenCalled();
|
|
116
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('routes learn command and does not exit', async () => {
|
|
120
|
+
vi.resetModules();
|
|
121
|
+
process.argv = ['node', 'cli', 'learn'];
|
|
122
|
+
|
|
123
|
+
const ctx = makeStubContext();
|
|
124
|
+
const learnSpy = vi.fn();
|
|
125
|
+
vi.doMock('./context.js', () => ({
|
|
126
|
+
createContext: () => ctx,
|
|
127
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
128
|
+
}));
|
|
129
|
+
vi.doMock('./commands/learn.js', () => ({ cmdLearn: learnSpy }));
|
|
130
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
131
|
+
|
|
132
|
+
await import('./cli.js');
|
|
133
|
+
expect(learnSpy).toHaveBeenCalled();
|
|
134
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('prints JSON error when --json and a command throws', async () => {
|
|
138
|
+
vi.resetModules();
|
|
139
|
+
process.argv = ['node', 'cli', 'remove', 'some-agent', '--json']; // will throw in our mocked cmdRemove
|
|
140
|
+
|
|
141
|
+
const ctx = makeStubContext();
|
|
142
|
+
ctx.flags.json = true;
|
|
143
|
+
vi.doMock('./context.js', () => ({
|
|
144
|
+
createContext: () => ctx,
|
|
145
|
+
ExitCodes: { SUCCESS: 0, ERROR: 1 },
|
|
146
|
+
}));
|
|
147
|
+
vi.doMock('./commands/remove.js', () => ({
|
|
148
|
+
cmdRemove: () => {
|
|
149
|
+
throw new Error('boom');
|
|
150
|
+
},
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
154
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
155
|
+
|
|
156
|
+
await import('./cli.js');
|
|
157
|
+
// allow the run().catch handler to run
|
|
158
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
159
|
+
|
|
160
|
+
expect(errSpy).toHaveBeenCalledWith(JSON.stringify({ error: 'boom' }));
|
|
161
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
162
|
+
});
|
|
163
|
+
});
|
package/src/cli.ts
CHANGED
|
@@ -19,7 +19,9 @@ import { cmdCheck } from './commands/check.js';
|
|
|
19
19
|
import { cmdCompletion } from './commands/completion.js';
|
|
20
20
|
import { cmdConfig } from './commands/config.js';
|
|
21
21
|
import { cmdPreamble } from './commands/preamble.js';
|
|
22
|
-
import {
|
|
22
|
+
import { cmdInstall } from './commands/install.js';
|
|
23
|
+
import { cmdSetup } from './commands/setup.js';
|
|
24
|
+
import { cmdLearn } from './commands/learn.js';
|
|
23
25
|
|
|
24
26
|
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
25
27
|
// Argument parsing
|
|
@@ -41,6 +43,8 @@ function parseArgs(argv: string[]): { command: string; args: string[]; flags: Fl
|
|
|
41
43
|
flags.json = true;
|
|
42
44
|
} else if (arg === '--verbose' || arg === '-v') {
|
|
43
45
|
flags.verbose = true;
|
|
46
|
+
} else if (arg === '--debug') {
|
|
47
|
+
flags.debug = true;
|
|
44
48
|
} else if (arg === '--force' || arg === '-f') {
|
|
45
49
|
flags.force = true;
|
|
46
50
|
} else if (arg === '--config') {
|
|
@@ -51,6 +55,8 @@ function parseArgs(argv: string[]): { command: string; args: string[]; flags: Fl
|
|
|
51
55
|
flags.wait = true;
|
|
52
56
|
} else if (arg === '--timeout') {
|
|
53
57
|
flags.timeout = parseTime(argv[++i]);
|
|
58
|
+
} else if (arg === '--lines') {
|
|
59
|
+
flags.lines = parseInt(argv[++i], 10) || 100;
|
|
54
60
|
} else if (arg === '--no-preamble') {
|
|
55
61
|
flags.noPreamble = true;
|
|
56
62
|
} else if (arg.startsWith('--pane=')) {
|
|
@@ -106,19 +112,23 @@ function main(): void {
|
|
|
106
112
|
|
|
107
113
|
// Help - load config to show current mode/timeout
|
|
108
114
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
115
|
+
// Show intro highlight when running just `tmux-team` with no args
|
|
116
|
+
const showIntro = !command || argv.length === 0;
|
|
109
117
|
try {
|
|
110
118
|
const paths = resolvePaths();
|
|
111
119
|
const config = loadConfig(paths);
|
|
112
120
|
const helpConfig: HelpConfig = {
|
|
113
121
|
mode: config.mode,
|
|
114
122
|
timeout: config.defaults.timeout,
|
|
123
|
+
showIntro,
|
|
115
124
|
};
|
|
116
125
|
cmdHelp(helpConfig);
|
|
117
126
|
} catch {
|
|
118
127
|
// Fallback if config can't be loaded
|
|
119
|
-
cmdHelp();
|
|
128
|
+
cmdHelp({ showIntro });
|
|
120
129
|
}
|
|
121
130
|
process.exit(ExitCodes.SUCCESS);
|
|
131
|
+
return;
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
if (command === '--version' || command === '-V') {
|
|
@@ -130,11 +140,18 @@ function main(): void {
|
|
|
130
140
|
if (command === 'completion') {
|
|
131
141
|
cmdCompletion(args[0]);
|
|
132
142
|
process.exit(ExitCodes.SUCCESS);
|
|
143
|
+
return;
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
// Create context for all other commands
|
|
136
147
|
const ctx = createContext({ argv, flags });
|
|
137
148
|
|
|
149
|
+
// Warn if not in tmux for commands that require it
|
|
150
|
+
const TMUX_REQUIRED_COMMANDS = ['talk', 'send', 'check', 'read', 'setup'];
|
|
151
|
+
if (!process.env.TMUX && TMUX_REQUIRED_COMMANDS.includes(command)) {
|
|
152
|
+
ctx.ui.warn('Not running inside tmux. Some features may not work.');
|
|
153
|
+
}
|
|
154
|
+
|
|
138
155
|
const run = async (): Promise<void> => {
|
|
139
156
|
switch (command) {
|
|
140
157
|
case 'init':
|
|
@@ -211,22 +228,16 @@ function main(): void {
|
|
|
211
228
|
cmdPreamble(ctx, args);
|
|
212
229
|
break;
|
|
213
230
|
|
|
214
|
-
case 'install
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
} else {
|
|
225
|
-
filteredArgs.push(arg);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
cmdInstallSkill(ctx, filteredArgs[0], scope);
|
|
229
|
-
}
|
|
231
|
+
case 'install':
|
|
232
|
+
await cmdInstall(ctx, args[0]);
|
|
233
|
+
break;
|
|
234
|
+
|
|
235
|
+
case 'setup':
|
|
236
|
+
await cmdSetup(ctx);
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case 'learn':
|
|
240
|
+
cmdLearn();
|
|
230
241
|
break;
|
|
231
242
|
|
|
232
243
|
default:
|