tmux-team 2.1.0 → 2.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
@@ -24,7 +24,7 @@ Unlike heavyweight frameworks that require specific SDKs or cloud infrastructure
24
24
  - **Model Agnostic** — Works with Claude Code, Gemini CLI, Codex, Aider, or any CLI tool
25
25
  - **Zero Infrastructure** — No servers, no MCP setup, no complex configuration. If it runs in tmux, tmux-team can talk to it
26
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 `~/.tmux-team/` (v2)
27
+ - **Local-First** — Per-project `tmux-team.json` lives with your repo; global config in `~/.config/tmux-team/`
28
28
 
29
29
  ---
30
30
 
@@ -140,7 +140,7 @@ Once the plugin is installed, coordinate directly from your Claude Code session:
140
140
  | `remove <name>` | Unregister an agent |
141
141
  | `init` | Create `tmux-team.json` in current directory |
142
142
  | `pm init --name "Project"` | Initialize project management |
143
- | `pm m add/list/done` | Manage milestones |
143
+ | `pm m add/list/done/delete` | Manage milestones |
144
144
  | `pm t add/list/update/done` | Manage tasks |
145
145
  | `pm log` | View audit event log |
146
146
  | `completion [zsh\|bash]` | Output shell completion script |
@@ -151,33 +151,59 @@ Once the plugin is installed, coordinate directly from your Claude Code session:
151
151
 
152
152
  ### Local Config (`./tmux-team.json`)
153
153
 
154
- Per-project agent registry:
154
+ Per-project agent registry with optional preambles and permissions:
155
155
 
156
156
  ```json
157
157
  {
158
- "claude": { "pane": "10.0", "remark": "Frontend specialist" },
159
- "codex": { "pane": "10.1", "remark": "Backend engineer" }
158
+ "claude": {
159
+ "pane": "10.0",
160
+ "remark": "Frontend specialist",
161
+ "preamble": "Focus on UI components. Ask for review before merging.",
162
+ "deny": ["pm:task:update(status)"]
163
+ },
164
+ "codex": {
165
+ "pane": "10.1",
166
+ "remark": "Code reviewer",
167
+ "preamble": "You are the code quality guard. Review changes thoroughly."
168
+ }
160
169
  }
161
170
  ```
162
171
 
172
+ | Field | Description |
173
+ |-------|-------------|
174
+ | `pane` | tmux pane ID (required) |
175
+ | `remark` | Description shown in `list` |
176
+ | `preamble` | Hidden instructions prepended to every message |
177
+ | `deny` | Permission patterns to block (e.g., `pm:task:update(status)`) |
178
+
163
179
  ### Global Config (`~/.config/tmux-team/config.json`)
164
180
 
181
+ Global settings that apply to all projects:
182
+
165
183
  ```json
166
184
  {
167
185
  "mode": "polling",
168
186
  "preambleMode": "always",
169
187
  "defaults": {
170
- "timeout": 60000,
171
- "pollInterval": 1000
172
- },
173
- "agents": {
174
- "gemini": {
175
- "preamble": "Do not edit files until explicitly asked."
176
- }
188
+ "timeout": 180,
189
+ "pollInterval": 1,
190
+ "captureLines": 100,
191
+ "hideOrphanTasks": false
177
192
  }
178
193
  }
179
194
  ```
180
195
 
196
+ | Field | Description |
197
+ |-------|-------------|
198
+ | `mode` | Default mode: `polling` (manual check) or `wait` (auto-wait) |
199
+ | `preambleMode` | `always` (inject preambles) or `disabled` |
200
+ | `defaults.timeout` | Default --wait timeout in seconds |
201
+ | `defaults.pollInterval` | Polling interval in seconds |
202
+ | `defaults.captureLines` | Default lines for `check` command |
203
+ | `defaults.hideOrphanTasks` | Hide tasks without milestone in `pm t list` |
204
+
205
+ > **Note:** Agent-specific config (preamble, deny) lives in local `tmux-team.json` only.
206
+
181
207
  ---
182
208
 
183
209
  ## ✨ v2 Features
@@ -194,18 +220,48 @@ tmux-team talk codex "message" --wait --timeout 60
194
220
 
195
221
  ### 📜 Agent Preambles
196
222
 
197
- Inject hidden instructions into every message:
223
+ Inject hidden instructions into every message via local `tmux-team.json`:
198
224
 
199
225
  ```json
200
226
  {
201
- "agents": {
202
- "gemini": {
203
- "preamble": "Always explain your reasoning. Do not edit files directly."
204
- }
227
+ "gemini": {
228
+ "pane": "10.2",
229
+ "preamble": "Always explain your reasoning. Do not edit files directly."
205
230
  }
206
231
  }
207
232
  ```
208
233
 
234
+ Use the CLI to manage preambles:
235
+
236
+ ```bash
237
+ tmux-team preamble show gemini # View current preamble
238
+ tmux-team preamble set gemini "..." # Set preamble
239
+ tmux-team preamble clear gemini # Remove preamble
240
+ ```
241
+
242
+ ### 🔐 Agent Permissions
243
+
244
+ Control what agents can do with `deny` patterns in `tmux-team.json`:
245
+
246
+ ```json
247
+ {
248
+ "claude": {
249
+ "pane": "10.0",
250
+ "deny": ["pm:task:update(status)", "pm:milestone:update(status)"]
251
+ }
252
+ }
253
+ ```
254
+
255
+ Pattern format: `pm:<resource>:<action>(<fields>)`
256
+
257
+ | Pattern | Effect |
258
+ |---------|--------|
259
+ | `pm:task:update(status)` | Block status changes only |
260
+ | `pm:task:update(*)` | Block all task updates |
261
+ | `pm:task:update` | Block entire update action |
262
+
263
+ Permissions are enforced via pane identity—agents are identified by which tmux pane they run in, not environment variables.
264
+
209
265
  ### 🎯 Project Management
210
266
 
211
267
  ```bash
@@ -238,6 +294,156 @@ It's the plumbing layer that lets humans and AI agents coordinate via tmux, noth
238
294
 
239
295
  *Built for developers who live in the terminal and want their AIs to do the same.*
240
296
 
297
+ ---
298
+
299
+ ## 📖 Command Reference
300
+
301
+ ### Core Commands
302
+
303
+ ```
304
+ tmux-team <command> [arguments]
305
+ ```
306
+
307
+ | Command | Description |
308
+ |---------|-------------|
309
+ | `talk <target> <message>` | Send message to an agent (or `all` for broadcast) |
310
+ | `check <target> [lines]` | Capture output from agent's pane (default: 100 lines) |
311
+ | `list` | List all configured agents |
312
+ | `add <name> <pane> [remark]` | Register a new agent |
313
+ | `update <name> [options]` | Update an agent's config |
314
+ | `remove <name>` | Unregister an agent |
315
+ | `init` | Create empty `tmux-team.json` in current directory |
316
+ | `config [show\|set\|clear]` | View/modify configuration settings |
317
+ | `preamble [show\|set\|clear]` | Manage agent preambles |
318
+ | `pm <subcommand>` | Project management commands |
319
+ | `completion [zsh\|bash]` | Output shell completion script |
320
+ | `help` | Show help message |
321
+
322
+ **Aliases:** `send` = talk, `read` = check, `ls` = list, `rm` = remove
323
+
324
+ ### Global Options
325
+
326
+ | Option | Description |
327
+ |--------|-------------|
328
+ | `--json` | Output in JSON format |
329
+ | `--verbose` | Show detailed output |
330
+ | `--force` | Skip warnings |
331
+
332
+ ### talk Options
333
+
334
+ | Option | Description |
335
+ |--------|-------------|
336
+ | `--delay <seconds>` | Wait before sending (whitelist-friendly alternative to `sleep`) |
337
+ | `--wait` | Block until agent responds (nonce-based completion detection) |
338
+ | `--timeout <seconds>` | Max wait time (default: 180s) |
339
+ | `--no-preamble` | Skip agent preamble for this message |
340
+
341
+ ### config Command
342
+
343
+ ```bash
344
+ tmux-team config show # Show current config
345
+ tmux-team config set <key> <value> # Set a config value
346
+ tmux-team config set mode wait # Enable wait mode
347
+ tmux-team config set preambleMode disabled # Disable preambles
348
+ tmux-team config set hideOrphanTasks true # Hide tasks without milestone
349
+ tmux-team config clear <key> # Clear a config value
350
+ tmux-team config --global set ... # Modify global config
351
+ ```
352
+
353
+ ### preamble Command
354
+
355
+ ```bash
356
+ tmux-team preamble show <agent> # Show agent's preamble
357
+ tmux-team preamble set <agent> "..." # Set agent's preamble
358
+ tmux-team preamble clear <agent> # Clear agent's preamble
359
+ ```
360
+
361
+ ---
362
+
363
+ ### Project Management (`pm`)
364
+
365
+ ```
366
+ tmux-team pm <subcommand>
367
+ ```
368
+
369
+ **Shorthands:** `pm m` = milestone, `pm t` = task, `pm ls` = list
370
+
371
+ #### pm init
372
+
373
+ ```bash
374
+ tmux-team pm init --name "Project Name"
375
+ tmux-team pm init --name "Sprint 1" --backend github --repo owner/repo
376
+ ```
377
+
378
+ | Option | Description |
379
+ |--------|-------------|
380
+ | `--name <name>` | Project name (required) |
381
+ | `--backend <fs\|github>` | Storage backend (default: `fs`) |
382
+ | `--repo <owner/repo>` | GitHub repo (required for github backend) |
383
+
384
+ #### pm list
385
+
386
+ ```bash
387
+ tmux-team pm list # List all teams/projects
388
+ tmux-team pm ls # Shorthand
389
+ ```
390
+
391
+ #### pm milestone (shorthand: `pm m`)
392
+
393
+ ```bash
394
+ tmux-team pm m # List all milestones
395
+ tmux-team pm m add "Phase 1" # Add milestone
396
+ tmux-team pm m add "Phase 1" -d "..." # Add with description
397
+ tmux-team pm m list # List milestones
398
+ tmux-team pm m done <id> # Mark milestone complete
399
+ tmux-team pm m delete <id> # Delete milestone (or: rm)
400
+ tmux-team pm m doc <id> # Print milestone documentation
401
+ tmux-team pm m doc <id> --edit # Edit doc in $EDITOR
402
+ tmux-team pm m doc <id> ref # Print doc path/reference
403
+ tmux-team pm m doc <id> --body "..." # Set doc content directly
404
+ tmux-team pm m doc <id> --body-file x # Set doc content from file
405
+ ```
406
+
407
+ #### pm task (shorthand: `pm t`)
408
+
409
+ ```bash
410
+ tmux-team pm t # List all tasks
411
+ tmux-team pm t add "Task title" # Add task
412
+ tmux-team pm t add "..." --milestone 1 # Add task to milestone
413
+ tmux-team pm t add "..." --body "..." # Add task with body
414
+ tmux-team pm t add "..." -a @user # Add task with assignee
415
+ tmux-team pm t list # List tasks
416
+ tmux-team pm t list --status pending # Filter by status
417
+ tmux-team pm t list --milestone 1 # Filter by milestone
418
+ tmux-team pm t show <id> # Show task details
419
+ tmux-team pm t update <id> --status in_progress
420
+ tmux-team pm t update <id> -a @user # Assign task
421
+ tmux-team pm t done <id> # Mark task complete
422
+ tmux-team pm t doc <id> # Print task documentation
423
+ tmux-team pm t doc <id> --edit # Edit doc in $EDITOR
424
+ tmux-team pm t doc <id> ref # Print doc path/reference
425
+ tmux-team pm t doc <id> --body "..." # Set doc content directly
426
+ tmux-team pm t doc <id> --body-file x # Set doc content from file
427
+ ```
428
+
429
+ #### pm log
430
+
431
+ ```bash
432
+ tmux-team pm log # Show audit event log
433
+ tmux-team pm log --limit 10 # Limit to 10 events
434
+ ```
435
+
436
+ ---
437
+
438
+ ### Storage Backends
439
+
440
+ | Backend | Description |
441
+ |---------|-------------|
442
+ | `fs` | Local filesystem (default). Tasks stored in `~/.config/tmux-team/teams/` |
443
+ | `github` | GitHub Issues. Tasks become issues, milestones sync with GitHub |
444
+
445
+ ---
446
+
241
447
  ## License
242
448
 
243
449
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-team",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI tool for AI agent collaboration in tmux - manage cross-pane communication",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,9 +3,9 @@
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
5
  import fs from 'fs';
6
- import type { Context } from '../types.js';
6
+ import type { Context, PaneEntry } from '../types.js';
7
7
  import { ExitCodes } from '../exits.js';
8
- import { saveLocalConfig } from '../config.js';
8
+ import { loadLocalConfigFile, saveLocalConfigFile } from '../config.js';
9
9
 
10
10
  export function cmdAdd(ctx: Context, name: string, pane: string, remark?: string): void {
11
11
  const { ui, config, paths, flags, exit } = ctx;
@@ -23,12 +23,16 @@ export function cmdAdd(ctx: Context, name: string, pane: string, remark?: string
23
23
  exit(ExitCodes.ERROR);
24
24
  }
25
25
 
26
- config.paneRegistry[name] = { pane };
26
+ // Load existing config to preserve all fields (preamble, deny, etc.)
27
+ const localConfig = loadLocalConfigFile(paths);
28
+
29
+ const newEntry: PaneEntry = { pane };
27
30
  if (remark) {
28
- config.paneRegistry[name].remark = remark;
31
+ newEntry.remark = remark;
29
32
  }
33
+ localConfig[name] = newEntry;
30
34
 
31
- saveLocalConfig(paths, config.paneRegistry);
35
+ saveLocalConfigFile(paths, localConfig);
32
36
 
33
37
  if (flags.json) {
34
38
  ui.json({ added: name, pane, remark });
@@ -13,11 +13,17 @@ import {
13
13
  clearLocalSettings,
14
14
  } from '../config.js';
15
15
 
16
- type ConfigKey = 'mode' | 'preambleMode';
16
+ type EnumConfigKey = 'mode' | 'preambleMode';
17
+ type NumericConfigKey = 'preambleEvery';
18
+ type BoolConfigKey = 'hideOrphanTasks';
19
+ type ConfigKey = EnumConfigKey | NumericConfigKey | BoolConfigKey;
17
20
 
18
- const VALID_KEYS: ConfigKey[] = ['mode', 'preambleMode'];
21
+ const ENUM_KEYS: EnumConfigKey[] = ['mode', 'preambleMode'];
22
+ const NUMERIC_KEYS: NumericConfigKey[] = ['preambleEvery'];
23
+ const BOOL_KEYS: BoolConfigKey[] = ['hideOrphanTasks'];
24
+ const VALID_KEYS: ConfigKey[] = [...ENUM_KEYS, ...NUMERIC_KEYS, ...BOOL_KEYS];
19
25
 
20
- const VALID_VALUES: Record<ConfigKey, string[]> = {
26
+ const VALID_VALUES: Record<EnumConfigKey, string[]> = {
21
27
  mode: ['polling', 'wait'],
22
28
  preambleMode: ['always', 'disabled'],
23
29
  };
@@ -26,7 +32,19 @@ function isValidKey(key: string): key is ConfigKey {
26
32
  return VALID_KEYS.includes(key as ConfigKey);
27
33
  }
28
34
 
29
- function isValidValue(key: ConfigKey, value: string): boolean {
35
+ function isEnumKey(key: ConfigKey): key is EnumConfigKey {
36
+ return ENUM_KEYS.includes(key as EnumConfigKey);
37
+ }
38
+
39
+ function isNumericKey(key: ConfigKey): key is NumericConfigKey {
40
+ return NUMERIC_KEYS.includes(key as NumericConfigKey);
41
+ }
42
+
43
+ function isBoolKey(key: ConfigKey): key is BoolConfigKey {
44
+ return BOOL_KEYS.includes(key as BoolConfigKey);
45
+ }
46
+
47
+ function isValidValue(key: EnumConfigKey, value: string): boolean {
30
48
  return VALID_VALUES[key].includes(value);
31
49
  }
32
50
 
@@ -43,6 +61,8 @@ function showConfig(ctx: Context): void {
43
61
  resolved: {
44
62
  mode: ctx.config.mode,
45
63
  preambleMode: ctx.config.preambleMode,
64
+ preambleEvery: ctx.config.defaults.preambleEvery,
65
+ hideOrphanTasks: ctx.config.defaults.hideOrphanTasks,
46
66
  defaults: ctx.config.defaults,
47
67
  },
48
68
  sources: {
@@ -52,6 +72,14 @@ function showConfig(ctx: Context): void {
52
72
  : globalConfig.preambleMode
53
73
  ? 'global'
54
74
  : 'default',
75
+ preambleEvery:
76
+ localSettings?.preambleEvery !== undefined
77
+ ? 'local'
78
+ : globalConfig.defaults?.preambleEvery !== undefined
79
+ ? 'global'
80
+ : 'default',
81
+ hideOrphanTasks:
82
+ globalConfig.defaults?.hideOrphanTasks !== undefined ? 'global' : 'default',
55
83
  },
56
84
  paths: {
57
85
  global: ctx.paths.globalConfig,
@@ -68,6 +96,14 @@ function showConfig(ctx: Context): void {
68
96
  : globalConfig.preambleMode
69
97
  ? '(global)'
70
98
  : '(default)';
99
+ const preambleEverySource =
100
+ localSettings?.preambleEvery !== undefined
101
+ ? '(local)'
102
+ : globalConfig.defaults?.preambleEvery !== undefined
103
+ ? '(global)'
104
+ : '(default)';
105
+ const hideOrphanSource =
106
+ globalConfig.defaults?.hideOrphanTasks !== undefined ? '(global)' : '(default)';
71
107
 
72
108
  ctx.ui.info('Current configuration:\n');
73
109
  ctx.ui.table(
@@ -75,6 +111,8 @@ function showConfig(ctx: Context): void {
75
111
  [
76
112
  ['mode', ctx.config.mode, modeSource],
77
113
  ['preambleMode', ctx.config.preambleMode, preambleSource],
114
+ ['preambleEvery', String(ctx.config.defaults.preambleEvery), preambleEverySource],
115
+ ['hideOrphanTasks', String(ctx.config.defaults.hideOrphanTasks), hideOrphanSource],
78
116
  ['defaults.timeout', String(ctx.config.defaults.timeout), '(global)'],
79
117
  ['defaults.pollInterval', String(ctx.config.defaults.pollInterval), '(global)'],
80
118
  ['defaults.captureLines', String(ctx.config.defaults.captureLines), '(global)'],
@@ -95,11 +133,50 @@ function setConfig(ctx: Context, key: string, value: string, global: boolean): v
95
133
  ctx.exit(ExitCodes.ERROR);
96
134
  }
97
135
 
98
- if (!isValidValue(key, value)) {
99
- ctx.ui.error(
100
- `Invalid value for ${key}: ${value}. Valid values: ${VALID_VALUES[key].join(', ')}`
101
- );
102
- ctx.exit(ExitCodes.ERROR);
136
+ const validKey = key as ConfigKey;
137
+
138
+ // Validate enum keys
139
+ if (isEnumKey(validKey)) {
140
+ if (!isValidValue(validKey, value)) {
141
+ ctx.ui.error(
142
+ `Invalid value for ${key}: ${value}. Valid values: ${VALID_VALUES[validKey].join(', ')}`
143
+ );
144
+ ctx.exit(ExitCodes.ERROR);
145
+ }
146
+ }
147
+
148
+ // Validate numeric keys
149
+ if (isNumericKey(validKey)) {
150
+ const numValue = parseInt(value, 10);
151
+ if (isNaN(numValue) || numValue < 0) {
152
+ ctx.ui.error(`Invalid value for ${key}: ${value}. Must be a non-negative integer.`);
153
+ ctx.exit(ExitCodes.ERROR);
154
+ }
155
+ }
156
+
157
+ if (isBoolKey(validKey)) {
158
+ if (value !== 'true' && value !== 'false') {
159
+ ctx.ui.error(`Invalid value for ${key}: ${value}. Use true or false.`);
160
+ ctx.exit(ExitCodes.ERROR);
161
+ }
162
+ }
163
+
164
+ if (key === 'hideOrphanTasks') {
165
+ const globalConfig = loadGlobalConfig(ctx.paths);
166
+ if (!globalConfig.defaults) {
167
+ globalConfig.defaults = {
168
+ timeout: 180,
169
+ pollInterval: 1,
170
+ captureLines: 100,
171
+ preambleEvery: ctx.config.defaults.preambleEvery,
172
+ hideOrphanTasks: value === 'true',
173
+ };
174
+ } else {
175
+ globalConfig.defaults.hideOrphanTasks = value === 'true';
176
+ }
177
+ saveGlobalConfig(ctx.paths, globalConfig);
178
+ ctx.ui.success(`Set ${key}=${value} in global config`);
179
+ return;
103
180
  }
104
181
 
105
182
  if (global) {
@@ -109,6 +186,18 @@ function setConfig(ctx: Context, key: string, value: string, global: boolean): v
109
186
  globalConfig.mode = value as 'polling' | 'wait';
110
187
  } else if (key === 'preambleMode') {
111
188
  globalConfig.preambleMode = value as 'always' | 'disabled';
189
+ } else if (key === 'preambleEvery') {
190
+ if (!globalConfig.defaults) {
191
+ globalConfig.defaults = {
192
+ timeout: 180,
193
+ pollInterval: 1,
194
+ captureLines: 100,
195
+ preambleEvery: parseInt(value, 10),
196
+ hideOrphanTasks: ctx.config.defaults.hideOrphanTasks,
197
+ };
198
+ } else {
199
+ globalConfig.defaults.preambleEvery = parseInt(value, 10);
200
+ }
112
201
  }
113
202
  saveGlobalConfig(ctx.paths, globalConfig);
114
203
  ctx.ui.success(`Set ${key}=${value} in global config`);
@@ -118,6 +207,8 @@ function setConfig(ctx: Context, key: string, value: string, global: boolean): v
118
207
  updateLocalSettings(ctx.paths, { mode: value as 'polling' | 'wait' });
119
208
  } else if (key === 'preambleMode') {
120
209
  updateLocalSettings(ctx.paths, { preambleMode: value as 'always' | 'disabled' });
210
+ } else if (key === 'preambleEvery') {
211
+ updateLocalSettings(ctx.paths, { preambleEvery: parseInt(value, 10) });
121
212
  }
122
213
  ctx.ui.success(`Set ${key}=${value} in local config (repo override)`);
123
214
  }
@@ -132,6 +223,10 @@ function clearConfig(ctx: Context, key?: string): void {
132
223
  ctx.ui.error(`Invalid key: ${key}. Valid keys: ${VALID_KEYS.join(', ')}`);
133
224
  ctx.exit(ExitCodes.ERROR);
134
225
  }
226
+ if (key === 'hideOrphanTasks') {
227
+ ctx.ui.error(`Cannot clear global-only key: ${key}. Edit global config instead.`);
228
+ ctx.exit(ExitCodes.ERROR);
229
+ }
135
230
 
136
231
  // Clear specific key from local settings
137
232
  const localConfigFile = loadLocalConfigFile(ctx.paths);
@@ -78,6 +78,8 @@ ${colors.yellow('CONFIG')}
78
78
  ${colors.yellow('CHANGE MODE')}
79
79
  tmux-team config set mode wait ${colors.dim('Enable wait mode (local)')}
80
80
  tmux-team config set mode polling ${colors.dim('Enable polling mode (local)')}
81
- tmux-team config set timeout 120 ${colors.dim('Set timeout to 120s')}
81
+ tmux-team config set preambleMode disabled ${colors.dim('Disable preambles (local)')}
82
+ tmux-team config set preambleEvery 5 ${colors.dim('Inject preamble every 5 messages')}
83
+ tmux-team config set hideOrphanTasks true ${colors.dim('Hide tasks without milestones (global)')}
82
84
  `);
83
85
  }
@@ -1,10 +1,10 @@
1
1
  // ─────────────────────────────────────────────────────────────
2
- // Preamble command - manage agent preambles
2
+ // Preamble command - manage agent preambles (local config only)
3
3
  // ─────────────────────────────────────────────────────────────
4
4
 
5
5
  import type { Context } from '../types.js';
6
6
  import { ExitCodes } from '../context.js';
7
- import { loadGlobalConfig, saveGlobalConfig } from '../config.js';
7
+ import { loadLocalConfigFile, saveLocalConfigFile } from '../config.js';
8
8
 
9
9
  /**
10
10
  * Show preamble(s) for agent(s).
@@ -57,25 +57,27 @@ function showPreamble(ctx: Context, agentName?: string): void {
57
57
  }
58
58
 
59
59
  /**
60
- * Set preamble for an agent.
60
+ * Set preamble for an agent (in local config).
61
61
  */
62
62
  function setPreamble(ctx: Context, agentName: string, preamble: string): void {
63
- const { ui, paths, flags } = ctx;
64
-
65
- const globalConfig = loadGlobalConfig(paths);
63
+ const { ui, paths, flags, config } = ctx;
66
64
 
67
- // Ensure agents object exists
68
- if (!globalConfig.agents) {
69
- globalConfig.agents = {};
65
+ // Check if agent exists in pane registry
66
+ if (!config.paneRegistry[agentName]) {
67
+ ui.error(`Agent '${agentName}' not found in local config`);
68
+ ui.error('Add the agent with: tmux-team add <agent> <pane>');
69
+ ctx.exit(ExitCodes.ERROR);
70
70
  }
71
71
 
72
- // Ensure agent config exists
73
- if (!globalConfig.agents[agentName]) {
74
- globalConfig.agents[agentName] = {};
72
+ const localConfig = loadLocalConfigFile(paths);
73
+
74
+ // Update preamble in local config
75
+ const agentEntry = localConfig[agentName] as { pane: string; preamble?: string } | undefined;
76
+ if (agentEntry) {
77
+ agentEntry.preamble = preamble;
75
78
  }
76
79
 
77
- globalConfig.agents[agentName].preamble = preamble;
78
- saveGlobalConfig(paths, globalConfig);
80
+ saveLocalConfigFile(paths, localConfig);
79
81
 
80
82
  if (flags.json) {
81
83
  ui.json({ agent: agentName, preamble, status: 'set' });
@@ -85,22 +87,17 @@ function setPreamble(ctx: Context, agentName: string, preamble: string): void {
85
87
  }
86
88
 
87
89
  /**
88
- * Clear preamble for an agent.
90
+ * Clear preamble for an agent (in local config).
89
91
  */
90
92
  function clearPreamble(ctx: Context, agentName: string): void {
91
93
  const { ui, paths, flags } = ctx;
92
94
 
93
- const globalConfig = loadGlobalConfig(paths);
94
-
95
- if (globalConfig.agents?.[agentName]?.preamble) {
96
- delete globalConfig.agents[agentName].preamble;
97
-
98
- // Clean up empty agent config
99
- if (Object.keys(globalConfig.agents[agentName]).length === 0) {
100
- delete globalConfig.agents[agentName];
101
- }
95
+ const localConfig = loadLocalConfigFile(paths);
96
+ const agentEntry = localConfig[agentName] as { pane?: string; preamble?: string } | undefined;
102
97
 
103
- saveGlobalConfig(paths, globalConfig);
98
+ if (agentEntry?.preamble) {
99
+ delete agentEntry.preamble;
100
+ saveLocalConfigFile(paths, localConfig);
104
101
 
105
102
  if (flags.json) {
106
103
  ui.json({ agent: agentName, status: 'cleared' });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { Context } from '../types.js';
6
6
  import { ExitCodes } from '../exits.js';
7
- import { saveLocalConfig } from '../config.js';
7
+ import { loadLocalConfigFile, saveLocalConfigFile } from '../config.js';
8
8
 
9
9
  export function cmdRemove(ctx: Context, name: string): void {
10
10
  const { ui, config, paths, flags, exit } = ctx;
@@ -14,8 +14,10 @@ export function cmdRemove(ctx: Context, name: string): void {
14
14
  exit(ExitCodes.PANE_NOT_FOUND);
15
15
  }
16
16
 
17
- delete config.paneRegistry[name];
18
- saveLocalConfig(paths, config.paneRegistry);
17
+ // Load existing config to preserve other agents' fields (preamble, deny, etc.)
18
+ const localConfig = loadLocalConfigFile(paths);
19
+ delete localConfig[name];
20
+ saveLocalConfigFile(paths, localConfig);
19
21
 
20
22
  if (flags.json) {
21
23
  ui.json({ removed: name });