tmux-team 2.0.0-alpha.4 → 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 +223 -17
- package/package.json +1 -1
- package/src/commands/add.ts +9 -5
- package/src/commands/config.ts +104 -9
- package/src/commands/help.ts +3 -1
- package/src/commands/preamble.ts +22 -25
- package/src/commands/remove.ts +5 -3
- package/src/commands/talk.test.ts +199 -14
- package/src/commands/talk.ts +26 -2
- package/src/commands/update.ts +16 -5
- package/src/config.test.ts +183 -9
- package/src/config.ts +29 -16
- package/src/pm/commands.test.ts +389 -55
- package/src/pm/commands.ts +312 -24
- package/src/pm/permissions.test.ts +113 -1
- package/src/pm/permissions.ts +18 -4
- package/src/pm/storage/adapter.ts +2 -0
- package/src/pm/storage/fs.test.ts +129 -1
- package/src/pm/storage/fs.ts +38 -4
- package/src/pm/storage/github.ts +96 -17
- package/src/pm/types.ts +6 -0
- package/src/state.test.ts +20 -10
- package/src/state.ts +28 -1
- package/src/types.ts +5 -1
- package/src/ui.ts +2 -1
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/`
|
|
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": {
|
|
159
|
-
|
|
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":
|
|
171
|
-
"pollInterval":
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
"
|
|
202
|
-
"
|
|
203
|
-
|
|
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
package/src/commands/add.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
31
|
+
newEntry.remark = remark;
|
|
29
32
|
}
|
|
33
|
+
localConfig[name] = newEntry;
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
saveLocalConfigFile(paths, localConfig);
|
|
32
36
|
|
|
33
37
|
if (flags.json) {
|
|
34
38
|
ui.json({ added: name, pane, remark });
|
package/src/commands/config.ts
CHANGED
|
@@ -13,11 +13,17 @@ import {
|
|
|
13
13
|
clearLocalSettings,
|
|
14
14
|
} from '../config.js';
|
|
15
15
|
|
|
16
|
-
type
|
|
16
|
+
type EnumConfigKey = 'mode' | 'preambleMode';
|
|
17
|
+
type NumericConfigKey = 'preambleEvery';
|
|
18
|
+
type BoolConfigKey = 'hideOrphanTasks';
|
|
19
|
+
type ConfigKey = EnumConfigKey | NumericConfigKey | BoolConfigKey;
|
|
17
20
|
|
|
18
|
-
const
|
|
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<
|
|
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
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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);
|
package/src/commands/help.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/commands/preamble.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
//
|
|
68
|
-
if (!
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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' });
|
package/src/commands/remove.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Context } from '../types.js';
|
|
6
6
|
import { ExitCodes } from '../exits.js';
|
|
7
|
-
import {
|
|
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
|
-
|
|
18
|
-
|
|
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 });
|