tmux-team 4.0.0 → 4.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 +123 -25
- package/package.json +15 -16
- package/src/cli.test.ts +15 -1
- package/src/cli.ts +15 -1
- package/src/commands/add.ts +17 -32
- package/src/commands/basic-commands.test.ts +534 -17
- package/src/commands/check.ts +20 -0
- package/src/commands/completion.ts +6 -8
- package/src/commands/config-command.test.ts +9 -8
- package/src/commands/config.ts +1 -5
- package/src/commands/help.ts +8 -3
- package/src/commands/install.test.ts +15 -1
- package/src/commands/list.ts +21 -2
- package/src/commands/migrate.ts +84 -0
- package/src/commands/preamble.test.ts +15 -2
- package/src/commands/preamble.ts +61 -16
- package/src/commands/remove.ts +10 -6
- package/src/commands/talk.test.ts +132 -22
- package/src/commands/talk.ts +28 -3
- package/src/commands/team.ts +361 -0
- package/src/commands/update.ts +45 -14
- package/src/config.test.ts +24 -0
- package/src/config.ts +37 -3
- package/src/context.test.ts +76 -1
- package/src/context.ts +8 -1
- package/src/identity.test.ts +3 -9
- package/src/identity.ts +7 -9
- package/src/registry.test.ts +61 -0
- package/src/registry.ts +29 -0
- package/src/tmux.test.ts +190 -1
- package/src/tmux.ts +289 -9
- package/src/types.ts +55 -0
- package/src/ui.test.ts +7 -1
package/README.md
CHANGED
|
@@ -18,26 +18,35 @@ npm install -g tmux-team
|
|
|
18
18
|
# 1. Install for your AI agent
|
|
19
19
|
tmt install claude # or: tmt install codex
|
|
20
20
|
|
|
21
|
-
# 2. Go to working folder and
|
|
22
|
-
tmt init
|
|
23
|
-
|
|
24
|
-
# 3. Register agents (run inside each agent's pane)
|
|
21
|
+
# 2. Go to working folder and register agents (run inside each agent's pane)
|
|
25
22
|
tmt this claude # registers current pane as "claude"
|
|
26
23
|
tmt this codex # registers current pane as "codex"
|
|
27
24
|
|
|
28
|
-
#
|
|
25
|
+
# 3. Talk to agents
|
|
29
26
|
tmt talk codex "Review this code" # waits for response by default
|
|
30
27
|
|
|
31
|
-
#
|
|
28
|
+
# 4. Update or remove an agent
|
|
32
29
|
tmt update codex --pane 2.3
|
|
33
30
|
tmt rm codex
|
|
34
31
|
```
|
|
35
32
|
|
|
36
33
|
> **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
34
|
|
|
35
|
+
### How scopes work
|
|
36
|
+
|
|
37
|
+
Registrations live in tmux pane metadata, not in a JSON file you have to track.
|
|
38
|
+
By default they are scoped to the current **workspace** — the nearest Git root,
|
|
39
|
+
or the current folder when you are not inside a Git repo. So `tmt this`,
|
|
40
|
+
`tmt add`, `tmt rm`, `tmt update`, `tmt preamble`, and `tmt list` all act on
|
|
41
|
+
the workspace you are currently in.
|
|
42
|
+
|
|
43
|
+
Reach for `--team <name>` only when you want an explicit shared team that spans
|
|
44
|
+
folders (see [Shared Teams](#shared-teams)).
|
|
45
|
+
|
|
38
46
|
## Cross-Folder Collaboration
|
|
39
47
|
|
|
40
|
-
Agents don't need to be in the same folder to collaborate.
|
|
48
|
+
Agents don't need to be in the same folder to collaborate. From your current
|
|
49
|
+
workspace you can add an agent whose pane lives in another project:
|
|
41
50
|
|
|
42
51
|
```bash
|
|
43
52
|
# In project-a folder, add an agent that's running in project-b
|
|
@@ -46,6 +55,10 @@ tmt add codex-reviewer 5.1 # Use the pane ID from the other project
|
|
|
46
55
|
|
|
47
56
|
Find pane IDs with: `tmux display-message -p "#{pane_id}"`
|
|
48
57
|
|
|
58
|
+
This still uses the default workspace scope: the registration is visible from
|
|
59
|
+
project-a, not from project-b. For long-running collaboration that should be
|
|
60
|
+
visible on both sides, use a [shared team](#shared-teams).
|
|
61
|
+
|
|
49
62
|
## Commands
|
|
50
63
|
|
|
51
64
|
| Command | Description |
|
|
@@ -55,7 +68,13 @@ Find pane IDs with: `tmux display-message -p "#{pane_id}"`
|
|
|
55
68
|
| `talk <agent> "msg"` | Send message and wait for response |
|
|
56
69
|
| `talk all "msg"` | Broadcast to all agents |
|
|
57
70
|
| `check <agent> [lines]` | Read agent's pane output |
|
|
58
|
-
| `list` | Show
|
|
71
|
+
| `list [team\|pane]` | Show current workspace agents, one shared team, or one pane's registrations |
|
|
72
|
+
| `migrate [--dry-run] [--cleanup]` | Move legacy `tmux-team.json` entries into tmux pane metadata |
|
|
73
|
+
| `team` | List shared team names |
|
|
74
|
+
| `team ls <team>` | List members of a shared team |
|
|
75
|
+
| `team add <team> <name> [pane]` | Add current or specified pane to a shared team |
|
|
76
|
+
| `team panes [--json]` | Inspect tmux panes grouped by scope |
|
|
77
|
+
| `team rm <team> --force` | Remove a shared team registration from every pane |
|
|
59
78
|
| `learn` | Show educational guide |
|
|
60
79
|
|
|
61
80
|
**Options for `talk`:**
|
|
@@ -76,39 +95,117 @@ tmt config set pasteEnterDelayMs 500
|
|
|
76
95
|
|
|
77
96
|
## Managing Your Team
|
|
78
97
|
|
|
79
|
-
|
|
98
|
+
Agent registrations live in tmux pane metadata, scoped per workspace by
|
|
99
|
+
default. The same-folder workflow never needs `--team`.
|
|
100
|
+
|
|
101
|
+
**List agents and status:**
|
|
102
|
+
```bash
|
|
103
|
+
tmt ls # agents in this workspace
|
|
104
|
+
tmt ls myproject # members of a shared team
|
|
105
|
+
tmt ls 10.1 # registrations on a pane
|
|
106
|
+
tmt ls main.10.1 # shorthand for main:10.1
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Manage shared teams** with the `team` namespace:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
tmt team # list shared team names
|
|
113
|
+
tmt team ls myproject # list team members
|
|
114
|
+
tmt team add myproject claude # add current pane to a team
|
|
115
|
+
tmt team add myproject codex 1.1 # add a specific pane to a team
|
|
116
|
+
tmt team rm myproject --force # remove a team from every pane
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A single pane can belong to multiple teams. Commands never guess across teams:
|
|
120
|
+
`tmt talk codex` uses the current workspace, while `tmt talk codex --team
|
|
121
|
+
myproject` uses only that shared team. If an agent name appears in multiple
|
|
122
|
+
shared teams and is not in the current workspace, tmux-team asks you to specify
|
|
123
|
+
the team.
|
|
124
|
+
|
|
125
|
+
**Inspect every tmux pane** with `tmt team panes`. Output is grouped by scope —
|
|
126
|
+
shared teams first, then workspaces, then unregistered panes — and each
|
|
127
|
+
section's title lists the agents living there:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Team: acme-app (codex, gemini)
|
|
131
|
+
PANE TARGET CWD CMD
|
|
132
|
+
%12 main:1.0 ~/acme/frontend node
|
|
133
|
+
%17 main:2.0 ~/acme/backend python
|
|
134
|
+
|
|
135
|
+
Workspace: ~/dev/tmux-team (claude)
|
|
136
|
+
PANE TARGET CWD CMD
|
|
137
|
+
%3 work:0.1 ~/dev/tmux-team node
|
|
138
|
+
|
|
139
|
+
Unregistered panes
|
|
140
|
+
PANE TARGET CWD CMD
|
|
141
|
+
%9 misc:0.0 ~/scratch zsh
|
|
142
|
+
```
|
|
80
143
|
|
|
81
|
-
**List** - Show configured agents:
|
|
82
144
|
```bash
|
|
83
|
-
tmt
|
|
145
|
+
tmt team panes # grouped pane inventory
|
|
146
|
+
tmt team panes --json # { teams, panes } incl. each pane's registrations
|
|
84
147
|
```
|
|
85
148
|
|
|
86
|
-
**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"gemini": { "pane": "1.2", "remark": "Documentation" }
|
|
92
|
-
}
|
|
149
|
+
**Add an agent from any pane.** Targets can be `%pane_id`, `window.pane`, or
|
|
150
|
+
`session:window.pane`; tmux-team stores the canonical `%pane_id`.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
tmt add codex 1.1 "Code reviewer"
|
|
93
154
|
```
|
|
94
155
|
|
|
95
|
-
**Remove**
|
|
156
|
+
**Remove an agent** from the current scope:
|
|
96
157
|
```bash
|
|
97
158
|
tmt rm codex
|
|
98
159
|
```
|
|
99
160
|
|
|
161
|
+
**Migrate from legacy `tmux-team.json`.** Versions before v4 stored agents in
|
|
162
|
+
a JSON file. `tmt migrate` copies those entries into tmux pane metadata so the
|
|
163
|
+
new commands can see them. Run it once per project that still has the file:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
tmt migrate --dry-run # preview what would move
|
|
167
|
+
tmt migrate # move entries into tmux metadata
|
|
168
|
+
tmt migrate --cleanup # also delete the migrated entries from the JSON file
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`tmux-team.json` is still loaded as a fallback when no tmux metadata exists,
|
|
172
|
+
and it remains the home for local `$config` overrides. If you don't use it,
|
|
173
|
+
you can ignore it.
|
|
174
|
+
|
|
100
175
|
---
|
|
101
176
|
|
|
177
|
+
## Agent Preambles
|
|
178
|
+
|
|
179
|
+
Set a per-agent preamble to steer behavior (stored with the pane registration):
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
tmt preamble set codex "You are the code quality guard. Be strict."
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### What Happens When a Preamble Is Set
|
|
186
|
+
|
|
187
|
+
When you send a message, tmux-team injects the preamble like this:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
[SYSTEM: You are the code quality guard. Be strict.]
|
|
191
|
+
|
|
192
|
+
Review the login flow changes.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Control how often it’s injected with `preambleEvery`:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
tmt config set preambleEvery 3
|
|
199
|
+
```
|
|
200
|
+
|
|
102
201
|
## Shared Teams
|
|
103
202
|
|
|
104
203
|
> *Work on different folders but talk to the same team of agents.*
|
|
105
204
|
|
|
106
|
-
By default,
|
|
205
|
+
By default, registrations are scoped to the current workspace. The `--team` flag
|
|
206
|
+
creates an explicit shared team that works across folders:
|
|
107
207
|
|
|
108
208
|
```bash
|
|
109
|
-
# Initialize a shared team
|
|
110
|
-
tmt init --team myproject
|
|
111
|
-
|
|
112
209
|
# Register agents from ANY folder
|
|
113
210
|
cd ~/code/frontend && tmt this claude --team myproject
|
|
114
211
|
cd ~/code/backend && tmt this codex --team myproject
|
|
@@ -128,16 +225,17 @@ tmt talk all "Starting deploy - heads up" --team myproject
|
|
|
128
225
|
|
|
129
226
|
**Single project** (default) — agents work in the same folder:
|
|
130
227
|
```bash
|
|
131
|
-
tmt init
|
|
132
228
|
tmt this claude
|
|
133
229
|
tmt add codex 1.1
|
|
134
230
|
```
|
|
135
231
|
|
|
136
232
|
**Shared team** — agents work across folders but collaborate:
|
|
137
233
|
```bash
|
|
138
|
-
tmt init --team acme-app
|
|
139
234
|
tmt this frontend-claude --team acme-app # from ~/acme/frontend
|
|
140
235
|
tmt this backend-codex --team acme-app # from ~/acme/backend
|
|
236
|
+
tmt team # list shared teams
|
|
237
|
+
tmt team ls acme-app # list members
|
|
238
|
+
tmt team rm acme-app --force # remove the team from every pane
|
|
141
239
|
```
|
|
142
240
|
|
|
143
241
|
### Multi-team coordination
|
package/package.json
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-team",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.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": "pnpm test:run",
|
|
14
|
-
"test:watch": "vitest",
|
|
15
|
-
"test:run": "vitest run --coverage && node scripts/check-coverage.mjs --threshold 90 --branches 85",
|
|
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": "pnpm type:check && pnpm lint && pnpm format:check"
|
|
22
|
-
},
|
|
23
10
|
"keywords": [
|
|
24
11
|
"tmux",
|
|
25
12
|
"cli",
|
|
@@ -37,7 +24,6 @@
|
|
|
37
24
|
"engines": {
|
|
38
25
|
"node": ">=18"
|
|
39
26
|
},
|
|
40
|
-
"packageManager": "pnpm@9.15.4",
|
|
41
27
|
"os": [
|
|
42
28
|
"darwin",
|
|
43
29
|
"linux"
|
|
@@ -57,5 +43,18 @@
|
|
|
57
43
|
"prettier": "^3.7.4",
|
|
58
44
|
"typescript": "^5.3.0",
|
|
59
45
|
"vitest": "^1.2.0"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"dev": "tsx src/cli.ts",
|
|
49
|
+
"tmt": "./bin/tmux-team",
|
|
50
|
+
"test": "pnpm test:run",
|
|
51
|
+
"test:watch": "vitest",
|
|
52
|
+
"test:run": "vitest run --coverage && node scripts/check-coverage.mjs --threshold 90 --branches 85",
|
|
53
|
+
"lint": "oxlint src/",
|
|
54
|
+
"lint:fix": "oxlint src/ --fix",
|
|
55
|
+
"format": "prettier --write src/",
|
|
56
|
+
"format:check": "prettier --check src/",
|
|
57
|
+
"type:check": "tsc --noEmit",
|
|
58
|
+
"check": "pnpm type:check && pnpm lint && pnpm format:check"
|
|
60
59
|
}
|
|
61
|
-
}
|
|
60
|
+
}
|
package/src/cli.test.ts
CHANGED
|
@@ -16,7 +16,14 @@ function makeStubContext(): Context {
|
|
|
16
16
|
config: {
|
|
17
17
|
mode: 'polling',
|
|
18
18
|
preambleMode: 'always',
|
|
19
|
-
defaults: {
|
|
19
|
+
defaults: {
|
|
20
|
+
timeout: 180,
|
|
21
|
+
pollInterval: 1,
|
|
22
|
+
captureLines: 100,
|
|
23
|
+
maxCaptureLines: 2000,
|
|
24
|
+
preambleEvery: 3,
|
|
25
|
+
pasteEnterDelayMs: 500,
|
|
26
|
+
},
|
|
20
27
|
agents: {},
|
|
21
28
|
paneRegistry: {},
|
|
22
29
|
},
|
|
@@ -25,6 +32,13 @@ function makeStubContext(): Context {
|
|
|
25
32
|
capture: vi.fn(),
|
|
26
33
|
listPanes: vi.fn(() => []),
|
|
27
34
|
getCurrentPaneId: vi.fn(() => null),
|
|
35
|
+
resolvePaneTarget: vi.fn((target: string) => target),
|
|
36
|
+
getAgentRegistry: vi.fn(() => ({ paneRegistry: {}, agents: {} })),
|
|
37
|
+
setAgentRegistration: vi.fn(),
|
|
38
|
+
clearAgentRegistration: vi.fn(() => false),
|
|
39
|
+
listTeams: vi.fn(() => ({})),
|
|
40
|
+
listTeamPanes: vi.fn(() => []),
|
|
41
|
+
removeTeam: vi.fn(() => ({ removed: 0, agents: [] })),
|
|
28
42
|
},
|
|
29
43
|
paths: {
|
|
30
44
|
globalDir: '/g',
|
package/src/cli.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { cmdPreamble } from './commands/preamble.js';
|
|
|
22
22
|
import { cmdInstall } from './commands/install.js';
|
|
23
23
|
import { cmdLearn } from './commands/learn.js';
|
|
24
24
|
import { cmdThis } from './commands/this.js';
|
|
25
|
+
import { cmdMigrate } from './commands/migrate.js';
|
|
26
|
+
import { cmdTeam } from './commands/team.js';
|
|
25
27
|
|
|
26
28
|
// ─────────────────────────────────────────────────────────────
|
|
27
29
|
// Argument parsing
|
|
@@ -164,7 +166,11 @@ function main(): void {
|
|
|
164
166
|
|
|
165
167
|
case 'list':
|
|
166
168
|
case 'ls':
|
|
167
|
-
|
|
169
|
+
if (args[0] === undefined) {
|
|
170
|
+
cmdList(ctx);
|
|
171
|
+
} else {
|
|
172
|
+
cmdList(ctx, args[0]);
|
|
173
|
+
}
|
|
168
174
|
break;
|
|
169
175
|
|
|
170
176
|
case 'add':
|
|
@@ -206,6 +212,14 @@ function main(): void {
|
|
|
206
212
|
cmdRemove(ctx, args[0]);
|
|
207
213
|
break;
|
|
208
214
|
|
|
215
|
+
case 'migrate':
|
|
216
|
+
cmdMigrate(ctx, args);
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case 'team':
|
|
220
|
+
cmdTeam(ctx, args);
|
|
221
|
+
break;
|
|
222
|
+
|
|
209
223
|
case 'this':
|
|
210
224
|
if (args.length < 1) {
|
|
211
225
|
ctx.ui.error('Usage: tmux-team this <name> [remark]');
|
package/src/commands/add.ts
CHANGED
|
@@ -2,54 +2,39 @@
|
|
|
2
2
|
// add command - register a new agent
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import type { Context, PaneEntry } from '../types.js';
|
|
5
|
+
import type { Context } from '../types.js';
|
|
7
6
|
import { ExitCodes } from '../exits.js';
|
|
8
|
-
import {
|
|
7
|
+
import { getRegistryScope, registrationFromEntry } from '../registry.js';
|
|
9
8
|
|
|
10
9
|
export function cmdAdd(ctx: Context, name: string, pane: string, remark?: string): void {
|
|
11
|
-
const { ui, config,
|
|
12
|
-
|
|
13
|
-
// Ensure teams directory exists if using --team
|
|
14
|
-
if (flags.team) {
|
|
15
|
-
ensureTeamsDir(paths.globalDir);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Create config file if it doesn't exist
|
|
19
|
-
if (!fs.existsSync(paths.localConfig)) {
|
|
20
|
-
fs.writeFileSync(paths.localConfig, '{}\n');
|
|
21
|
-
if (!flags.json) {
|
|
22
|
-
if (flags.team) {
|
|
23
|
-
ui.info(`Created shared team "${flags.team}"`);
|
|
24
|
-
} else {
|
|
25
|
-
ui.info(`Created ${paths.localConfig}`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
10
|
+
const { ui, config, tmux, flags, exit } = ctx;
|
|
29
11
|
|
|
30
12
|
if (config.paneRegistry[name]) {
|
|
31
13
|
ui.error(`Agent '${name}' already exists. Use 'tmux-team update' to modify.`);
|
|
32
14
|
exit(ExitCodes.ERROR);
|
|
33
15
|
}
|
|
34
16
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (remark) {
|
|
40
|
-
newEntry.remark = remark;
|
|
17
|
+
const resolvedPane = tmux.resolvePaneTarget(pane);
|
|
18
|
+
if (!resolvedPane) {
|
|
19
|
+
ui.error(`Pane '${pane}' not found. Is tmux running?`);
|
|
20
|
+
exit(ExitCodes.PANE_NOT_FOUND);
|
|
41
21
|
}
|
|
42
|
-
|
|
22
|
+
const paneId = resolvedPane as string;
|
|
43
23
|
|
|
44
|
-
|
|
24
|
+
const scope = getRegistryScope(ctx);
|
|
25
|
+
const registration = registrationFromEntry(name, {
|
|
26
|
+
pane: paneId,
|
|
27
|
+
...(remark !== undefined && { remark }),
|
|
28
|
+
});
|
|
29
|
+
tmux.setAgentRegistration(paneId, scope, registration);
|
|
45
30
|
|
|
46
31
|
if (flags.json) {
|
|
47
|
-
ui.json({ added: name, pane, remark, team: flags.team });
|
|
32
|
+
ui.json({ added: name, pane: paneId, remark, team: flags.team });
|
|
48
33
|
} else {
|
|
49
34
|
if (flags.team) {
|
|
50
|
-
ui.success(`Added agent '${name}' to team "${flags.team}" at pane ${
|
|
35
|
+
ui.success(`Added agent '${name}' to team "${flags.team}" at pane ${paneId}`);
|
|
51
36
|
} else {
|
|
52
|
-
ui.success(`Added agent '${name}' at pane ${
|
|
37
|
+
ui.success(`Added agent '${name}' at pane ${paneId}`);
|
|
53
38
|
}
|
|
54
39
|
}
|
|
55
40
|
}
|