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 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 initialize
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
- # 4. Talk to agents
25
+ # 3. Talk to agents
29
26
  tmt talk codex "Review this code" # waits for response by default
30
27
 
31
- # 5. Update or remove an agent
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. You can add an agent from one project to another:
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 configured agents |
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
- Configuration lives in `tmux-team.json` in your project root.
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 ls
145
+ tmt team panes # grouped pane inventory
146
+ tmt team panes --json # { teams, panes } incl. each pane's registrations
84
147
  ```
85
148
 
86
- **Edit** - Modify `tmux-team.json` directly:
87
- ```json
88
- {
89
- "$config": { "pasteEnterDelayMs": 500 },
90
- "codex": { "pane": "1.1", "remark": "Code reviewer" },
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** - Delete an agent:
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, `tmux-team.json` is local to each folder. The `--team` flag lets agents across different folders share a team:
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.0.0",
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: { timeout: 180, pollInterval: 1, captureLines: 100, maxCaptureLines: 2000, preambleEvery: 3, pasteEnterDelayMs: 500 },
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
- cmdList(ctx);
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]');
@@ -2,54 +2,39 @@
2
2
  // add command - register a new agent
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
- import fs from 'fs';
6
- import type { Context, PaneEntry } from '../types.js';
5
+ import type { Context } from '../types.js';
7
6
  import { ExitCodes } from '../exits.js';
8
- import { loadLocalConfigFile, saveLocalConfigFile, ensureTeamsDir } from '../config.js';
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, paths, flags, exit } = ctx;
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
- // Load existing config to preserve all fields (preamble, deny, etc.)
36
- const localConfig = loadLocalConfigFile(paths);
37
-
38
- const newEntry: PaneEntry = { pane };
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
- localConfig[name] = newEntry;
22
+ const paneId = resolvedPane as string;
43
23
 
44
- saveLocalConfigFile(paths, localConfig);
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 ${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 ${pane}`);
37
+ ui.success(`Added agent '${name}' at pane ${paneId}`);
53
38
  }
54
39
  }
55
40
  }