tmux-team 3.0.1 β 3.2.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 +55 -259
- package/package.json +16 -14
- package/skills/README.md +9 -10
- package/src/cli.test.ts +163 -0
- package/src/cli.ts +20 -17
- package/src/commands/basic-commands.test.ts +252 -0
- package/src/commands/config-command.test.ts +116 -0
- package/src/commands/help.ts +4 -1
- package/src/commands/install.test.ts +205 -0
- package/src/commands/install.ts +207 -0
- package/src/commands/setup.test.ts +175 -0
- package/src/commands/setup.ts +163 -0
- package/src/commands/talk.test.ts +144 -111
- package/src/commands/talk.ts +185 -100
- 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/commands/install-skill.ts +0 -148
package/README.md
CHANGED
|
@@ -1,310 +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
|
-
**Alias:** `tmt` is available as a shorthand for `tmux-team`
|
|
67
|
-
|
|
68
|
-
### Shell Completion
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
# Zsh (add to ~/.zshrc)
|
|
72
|
-
eval "$(tmux-team completion zsh)"
|
|
73
|
-
|
|
74
|
-
# Bash (add to ~/.bashrc)
|
|
75
|
-
eval "$(tmux-team completion bash)"
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Claude Code Plugin
|
|
11
|
+
**Requirements:** Node.js >= 18, tmux
|
|
79
12
|
|
|
80
|
-
|
|
81
|
-
/plugin marketplace add wkh237/tmux-team
|
|
82
|
-
/plugin install tmux-team@tmux-team
|
|
83
|
-
```
|
|
13
|
+
**Alias:** `tmt` (shorthand for `tmux-team`)
|
|
84
14
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Install tmux-team as a native skill for your AI coding agent:
|
|
15
|
+
## Quick Start
|
|
88
16
|
|
|
89
17
|
```bash
|
|
90
|
-
# Install for
|
|
91
|
-
tmux-team install-
|
|
92
|
-
|
|
93
|
-
# Install for OpenAI Codex (user-wide)
|
|
94
|
-
tmux-team install-skill codex
|
|
18
|
+
# 1. Install for your AI agent
|
|
19
|
+
tmux-team install claude # or: tmux-team install codex
|
|
95
20
|
|
|
96
|
-
#
|
|
97
|
-
tmux-team
|
|
98
|
-
tmux-team install-skill codex --local
|
|
99
|
-
```
|
|
21
|
+
# 2. Run the setup wizard (auto-detects panes)
|
|
22
|
+
tmux-team setup
|
|
100
23
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## β¨οΈ Quick Start
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
# Initialize config
|
|
109
|
-
tmux-team init
|
|
110
|
-
|
|
111
|
-
# Register your agents (name + tmux pane ID)
|
|
112
|
-
tmux-team add claude 10.0 "Frontend specialist"
|
|
113
|
-
tmux-team add codex 10.1 "Backend engineer"
|
|
114
|
-
tmux-team add gemini 10.2 "Code reviewer"
|
|
115
|
-
|
|
116
|
-
# Send messages and wait for response (recommended for better token utilization)
|
|
117
|
-
tmux-team talk codex "Review the auth module" --wait
|
|
118
|
-
tmux-team talk all "Starting the refactor now" --wait
|
|
119
|
-
|
|
120
|
-
# Or use the shorthand alias
|
|
121
|
-
tmt talk codex "Quick question" --wait
|
|
122
|
-
|
|
123
|
-
# Manage agents
|
|
124
|
-
tmux-team list
|
|
125
|
-
tmux-team update codex --remark "Now handling tests"
|
|
126
|
-
tmux-team remove gemini
|
|
127
|
-
|
|
128
|
-
# Learn more
|
|
129
|
-
tmux-team learn
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### From Claude Code
|
|
133
|
-
|
|
134
|
-
Once the plugin is installed, coordinate directly from your Claude Code session:
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
/tmux-team:team codex "Can you review my changes?" --wait
|
|
138
|
-
/tmux-team:team all "I'm starting the database migration" --wait
|
|
24
|
+
# 3. Talk to agents
|
|
25
|
+
tmux-team talk codex "Review this code" --wait
|
|
139
26
|
```
|
|
140
27
|
|
|
141
|
-
|
|
28
|
+
The `--wait` flag blocks until the agent responds, returning the response directly.
|
|
142
29
|
|
|
143
|
-
##
|
|
30
|
+
## Commands
|
|
144
31
|
|
|
145
32
|
| Command | Description |
|
|
146
33
|
|---------|-------------|
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
| `talk
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `update <name> --pane/--remark` | Update agent configuration |
|
|
154
|
-
| `remove <name>` | Unregister an agent |
|
|
155
|
-
| `init` | Create `tmux-team.json` in current directory |
|
|
156
|
-
| `config [show/set/clear]` | View/modify settings |
|
|
157
|
-
| `preamble [show/set/clear]` | Manage agent preambles |
|
|
158
|
-
| `install-skill <agent>` | Install skill for Claude/Codex (--local/--user) |
|
|
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 |
|
|
159
40
|
| `learn` | Show educational guide |
|
|
160
|
-
| `completion [zsh\|bash]` | Output shell completion script |
|
|
161
41
|
|
|
162
|
-
|
|
42
|
+
**Options for `talk --wait`:**
|
|
43
|
+
- `--timeout <seconds>` - Max wait time (default: 180s)
|
|
44
|
+
- `--lines <number>` - Lines to capture from response (default: 100)
|
|
163
45
|
|
|
164
|
-
|
|
46
|
+
Run `tmux-team help` for all commands and options.
|
|
165
47
|
|
|
166
|
-
|
|
48
|
+
## Managing Your Team
|
|
167
49
|
|
|
168
|
-
|
|
50
|
+
Configuration lives in `tmux-team.json` in your project root.
|
|
169
51
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"pane": "10.0",
|
|
174
|
-
"remark": "Frontend specialist",
|
|
175
|
-
"preamble": "Focus on UI components. Ask for review before merging."
|
|
176
|
-
},
|
|
177
|
-
"codex": {
|
|
178
|
-
"pane": "10.1",
|
|
179
|
-
"remark": "Code reviewer",
|
|
180
|
-
"preamble": "You are the code quality guard. Review changes thoroughly."
|
|
181
|
-
}
|
|
182
|
-
}
|
|
52
|
+
**Create** - Run the setup wizard to auto-detect agents:
|
|
53
|
+
```bash
|
|
54
|
+
tmux-team setup
|
|
183
55
|
```
|
|
184
56
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
| `preamble` | Hidden instructions prepended to every message |
|
|
190
|
-
|
|
191
|
-
### Global Config (`~/.config/tmux-team/config.json`)
|
|
192
|
-
|
|
193
|
-
Global settings that apply to all projects:
|
|
57
|
+
**Read** - List configured agents:
|
|
58
|
+
```bash
|
|
59
|
+
tmux-team list
|
|
60
|
+
```
|
|
194
61
|
|
|
62
|
+
**Update** - Edit `tmux-team.json` directly or re-run setup:
|
|
195
63
|
```json
|
|
196
64
|
{
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
"defaults": {
|
|
200
|
-
"timeout": 180,
|
|
201
|
-
"pollInterval": 1,
|
|
202
|
-
"captureLines": 100,
|
|
203
|
-
"preambleEvery": 3
|
|
204
|
-
}
|
|
65
|
+
"codex": { "pane": "%1", "remark": "Code reviewer" },
|
|
66
|
+
"gemini": { "pane": "%2", "remark": "Documentation" }
|
|
205
67
|
}
|
|
206
68
|
```
|
|
207
69
|
|
|
208
|
-
|
|
209
|
-
|-------|-------------|
|
|
210
|
-
| `mode` | Default mode: `polling` (manual check) or `wait` (auto-wait) |
|
|
211
|
-
| `preambleMode` | `always` (inject preambles) or `disabled` |
|
|
212
|
-
| `defaults.timeout` | Default --wait timeout in seconds |
|
|
213
|
-
| `defaults.pollInterval` | Polling interval in seconds |
|
|
214
|
-
| `defaults.captureLines` | Default lines for `check` command |
|
|
215
|
-
| `defaults.preambleEvery` | Inject preamble every N messages (default: 3) |
|
|
216
|
-
|
|
217
|
-
---
|
|
70
|
+
**Delete** - Remove an agent entry from `tmux-team.json` or delete the file entirely.
|
|
218
71
|
|
|
219
|
-
|
|
72
|
+
Find pane IDs: `tmux display-message -p "#{pane_id}"`
|
|
220
73
|
|
|
221
|
-
|
|
74
|
+
## Claude Code Plugin
|
|
222
75
|
|
|
223
|
-
The `--wait` flag is recommended for better token utilization:
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
# Wait for response with nonce-based completion detection
|
|
227
|
-
tmux-team talk codex "Review this code" --wait
|
|
228
|
-
|
|
229
|
-
# With custom timeout for complex tasks
|
|
230
|
-
tmux-team talk codex "Implement the feature" --wait --timeout 300
|
|
231
|
-
|
|
232
|
-
# Delay before sending (safe alternative to sleep)
|
|
233
|
-
tmux-team talk codex "message" --wait --delay 5
|
|
234
76
|
```
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
### π Agent Preambles
|
|
239
|
-
|
|
240
|
-
Inject hidden instructions into every message via local `tmux-team.json`:
|
|
241
|
-
|
|
242
|
-
```json
|
|
243
|
-
{
|
|
244
|
-
"gemini": {
|
|
245
|
-
"pane": "10.2",
|
|
246
|
-
"preamble": "Always explain your reasoning. Do not edit files directly."
|
|
247
|
-
}
|
|
248
|
-
}
|
|
77
|
+
/plugin marketplace add wkh237/tmux-team
|
|
78
|
+
/plugin install tmux-team
|
|
249
79
|
```
|
|
250
80
|
|
|
251
|
-
|
|
81
|
+
Gives you two slash commands:
|
|
252
82
|
|
|
253
|
-
|
|
254
|
-
tmux-team preamble show gemini # View current preamble
|
|
255
|
-
tmux-team preamble set gemini "Be concise" # Set preamble
|
|
256
|
-
tmux-team preamble clear gemini # Remove preamble
|
|
83
|
+
**`/learn`** - Teach Claude how to use tmux-team
|
|
257
84
|
```
|
|
85
|
+
/learn
|
|
86
|
+
```
|
|
87
|
+
Run this once when starting a session. Claude will understand how to coordinate with other agents.
|
|
258
88
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
- **Not an orchestrator** β No automatic agent selection or routing
|
|
266
|
-
- **Not a session manager** β Doesn't create/manage tmux sessions
|
|
267
|
-
- **Not an LLM wrapper** β Doesn't process or route messages through AI
|
|
268
|
-
|
|
269
|
-
It's the plumbing layer that lets humans and AI agents coordinate via tmux, nothing more.
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## π Command Reference
|
|
274
|
-
|
|
275
|
-
### talk Options
|
|
276
|
-
|
|
277
|
-
| Option | Description |
|
|
278
|
-
|--------|-------------|
|
|
279
|
-
| `--delay <seconds>` | Wait before sending (whitelist-friendly alternative to `sleep`) |
|
|
280
|
-
| `--wait` | Block until agent responds (nonce-based completion detection) |
|
|
281
|
-
| `--timeout <seconds>` | Max wait time (default: 180s) |
|
|
282
|
-
| `--no-preamble` | Skip agent preamble for this message |
|
|
283
|
-
|
|
284
|
-
### config Command
|
|
285
|
-
|
|
286
|
-
```bash
|
|
287
|
-
tmux-team config show # Show current config
|
|
288
|
-
tmux-team config set mode wait # Enable wait mode
|
|
289
|
-
tmux-team config set preambleMode disabled # Disable preambles
|
|
290
|
-
tmux-team config set preambleEvery 5 # Inject preamble every 5 messages
|
|
291
|
-
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
|
|
292
94
|
```
|
|
95
|
+
Use this to delegate tasks, ask for reviews, or broadcast updates.
|
|
293
96
|
|
|
294
|
-
|
|
97
|
+
## Learn More
|
|
295
98
|
|
|
296
99
|
```bash
|
|
297
|
-
tmux-team
|
|
298
|
-
tmux-team
|
|
299
|
-
tmux-team preamble clear <agent> # Clear agent's preamble
|
|
100
|
+
tmux-team learn # Comprehensive guide
|
|
101
|
+
tmux-team help # All commands and options
|
|
300
102
|
```
|
|
301
103
|
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
*Built for developers who live in the terminal and want their AIs to do the same.*
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
104
|
## License
|
|
309
105
|
|
|
310
106
|
MIT
|
package/package.json
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-team",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "CLI tool for AI agent collaboration in tmux - manage cross-pane communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tmux-team": "./bin/tmux-team",
|
|
8
8
|
"tmt": "./bin/tmux-team"
|
|
9
9
|
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsx src/cli.ts",
|
|
12
|
+
"tmt": "./bin/tmux-team",
|
|
13
|
+
"test": "npm run test:run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:run": "vitest run --coverage && node scripts/check-coverage.mjs --threshold 95",
|
|
16
|
+
"lint": "oxlint src/",
|
|
17
|
+
"lint:fix": "oxlint src/ --fix",
|
|
18
|
+
"format": "prettier --write src/",
|
|
19
|
+
"format:check": "prettier --check src/",
|
|
20
|
+
"type:check": "tsc --noEmit",
|
|
21
|
+
"check": "npm run type:check && npm run lint && npm run format:check"
|
|
22
|
+
},
|
|
10
23
|
"keywords": [
|
|
11
24
|
"tmux",
|
|
12
25
|
"cli",
|
|
@@ -38,21 +51,10 @@
|
|
|
38
51
|
},
|
|
39
52
|
"devDependencies": {
|
|
40
53
|
"@types/node": "^25.0.3",
|
|
54
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
41
55
|
"oxlint": "^1.34.0",
|
|
42
56
|
"prettier": "^3.7.4",
|
|
43
57
|
"typescript": "^5.3.0",
|
|
44
58
|
"vitest": "^1.2.0"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"dev": "tsx src/cli.ts",
|
|
48
|
-
"tmt": "./bin/tmux-team",
|
|
49
|
-
"test": "vitest",
|
|
50
|
-
"test:run": "vitest run",
|
|
51
|
-
"lint": "oxlint src/",
|
|
52
|
-
"lint:fix": "oxlint src/ --fix",
|
|
53
|
-
"format": "prettier --write src/",
|
|
54
|
-
"format:check": "prettier --check src/",
|
|
55
|
-
"type:check": "tsc --noEmit",
|
|
56
|
-
"check": "npm run type:check && npm run lint && npm run format:check"
|
|
57
59
|
}
|
|
58
|
-
}
|
|
60
|
+
}
|
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
|
+
});
|