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
|
@@ -16,15 +16,15 @@ _tmux-team() {
|
|
|
16
16
|
'add:Add a new agent'
|
|
17
17
|
'update:Update agent config'
|
|
18
18
|
'remove:Remove an agent'
|
|
19
|
-
'
|
|
19
|
+
'migrate:Copy legacy tmux-team.json registry into tmux metadata'
|
|
20
|
+
'team:Manage shared teams'
|
|
21
|
+
'init:Create empty legacy tmux-team.json'
|
|
20
22
|
'completion:Output shell completion script'
|
|
21
23
|
'help:Show help message'
|
|
22
24
|
)
|
|
23
25
|
|
|
24
26
|
_get_agents() {
|
|
25
|
-
|
|
26
|
-
agents=(\${(f)"$(node -e "console.log(Object.keys(JSON.parse(require('fs').readFileSync('./tmux-team.json'))).join('\\\\n'))" 2>/dev/null)"})
|
|
27
|
-
fi
|
|
27
|
+
agents=(\${(f)"$(tmux-team list --json 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{try{const j=JSON.parse(s); console.log(Object.keys(j.agents||{}).join("\\n"))}catch{}})')"})
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (( CURRENT == 2 )); then
|
|
@@ -64,16 +64,14 @@ const bashCompletion = `_tmux_team() {
|
|
|
64
64
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
65
65
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
66
66
|
|
|
67
|
-
commands="talk check list add update remove init completion help"
|
|
67
|
+
commands="talk check list add update remove migrate team init completion help"
|
|
68
68
|
|
|
69
69
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
70
70
|
COMPREPLY=( $(compgen -W "\${commands}" -- \${cur}) )
|
|
71
71
|
elif [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
72
72
|
case "\${prev}" in
|
|
73
73
|
talk|check|update|remove|rm)
|
|
74
|
-
|
|
75
|
-
agents=$(node -e "console.log(Object.keys(JSON.parse(require('fs').readFileSync('./tmux-team.json'))).join(' '))" 2>/dev/null)
|
|
76
|
-
fi
|
|
74
|
+
agents=$(tmux-team list --json 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{try{const j=JSON.parse(s); console.log(Object.keys(j.agents||{}).join(" "))}catch{}})')
|
|
77
75
|
if [[ "\${prev}" == "talk" ]]; then
|
|
78
76
|
agents="\${agents} all"
|
|
79
77
|
fi
|
|
@@ -51,6 +51,13 @@ function createCtx(
|
|
|
51
51
|
capture: vi.fn(),
|
|
52
52
|
listPanes: vi.fn(() => []),
|
|
53
53
|
getCurrentPaneId: vi.fn(() => null),
|
|
54
|
+
resolvePaneTarget: vi.fn((target: string) => target),
|
|
55
|
+
getAgentRegistry: vi.fn(() => ({ paneRegistry: {}, agents: {} })),
|
|
56
|
+
setAgentRegistration: vi.fn(),
|
|
57
|
+
clearAgentRegistration: vi.fn(() => false),
|
|
58
|
+
listTeams: vi.fn(() => ({})),
|
|
59
|
+
listTeamPanes: vi.fn(() => []),
|
|
60
|
+
removeTeam: vi.fn(() => ({ removed: 0, agents: [] })),
|
|
54
61
|
};
|
|
55
62
|
return {
|
|
56
63
|
argv: [],
|
|
@@ -167,10 +174,7 @@ describe('cmdConfig', () => {
|
|
|
167
174
|
|
|
168
175
|
it('shows sources in table mode with local settings', () => {
|
|
169
176
|
const ctx = createCtx(testDir);
|
|
170
|
-
fs.writeFileSync(
|
|
171
|
-
ctx.paths.localConfig,
|
|
172
|
-
JSON.stringify({ $config: { mode: 'wait' } })
|
|
173
|
-
);
|
|
177
|
+
fs.writeFileSync(ctx.paths.localConfig, JSON.stringify({ $config: { mode: 'wait' } }));
|
|
174
178
|
cmdConfig(ctx, ['show']);
|
|
175
179
|
expect(ctx.ui.table).toHaveBeenCalled();
|
|
176
180
|
// The table call should include (local) source
|
|
@@ -181,10 +185,7 @@ describe('cmdConfig', () => {
|
|
|
181
185
|
it('shows sources in table mode with global settings', () => {
|
|
182
186
|
const ctx = createCtx(testDir);
|
|
183
187
|
fs.mkdirSync(ctx.paths.globalDir, { recursive: true });
|
|
184
|
-
fs.writeFileSync(
|
|
185
|
-
ctx.paths.globalConfig,
|
|
186
|
-
JSON.stringify({ mode: 'wait' })
|
|
187
|
-
);
|
|
188
|
+
fs.writeFileSync(ctx.paths.globalConfig, JSON.stringify({ mode: 'wait' }));
|
|
188
189
|
cmdConfig(ctx, ['show']);
|
|
189
190
|
expect(ctx.ui.table).toHaveBeenCalled();
|
|
190
191
|
const tableCall = (ctx.ui.table as ReturnType<typeof vi.fn>).mock.calls[0];
|
package/src/commands/config.ts
CHANGED
|
@@ -114,11 +114,7 @@ function showConfig(ctx: Context): void {
|
|
|
114
114
|
['mode', ctx.config.mode, modeSource],
|
|
115
115
|
['preambleMode', ctx.config.preambleMode, preambleSource],
|
|
116
116
|
['preambleEvery', String(ctx.config.defaults.preambleEvery), preambleEverySource],
|
|
117
|
-
[
|
|
118
|
-
'pasteEnterDelayMs',
|
|
119
|
-
String(ctx.config.defaults.pasteEnterDelayMs),
|
|
120
|
-
pasteEnterDelaySource,
|
|
121
|
-
],
|
|
117
|
+
['pasteEnterDelayMs', String(ctx.config.defaults.pasteEnterDelayMs), pasteEnterDelaySource],
|
|
122
118
|
['defaults.timeout', String(ctx.config.defaults.timeout), '(global)'],
|
|
123
119
|
['defaults.pollInterval', String(ctx.config.defaults.pollInterval), '(global)'],
|
|
124
120
|
['defaults.captureLines', String(ctx.config.defaults.captureLines), '(global)'],
|
package/src/commands/help.ts
CHANGED
|
@@ -47,11 +47,13 @@ ${colors.yellow('USAGE')}
|
|
|
47
47
|
${colors.yellow('COMMANDS')}
|
|
48
48
|
${colors.green('talk')} <target> <message> Send message to an agent (or "all")
|
|
49
49
|
${colors.green('check')} <target> [lines] Capture output from agent's pane
|
|
50
|
-
${colors.green('list')}
|
|
50
|
+
${colors.green('list')} [team|pane] List workspace, team, or pane status
|
|
51
51
|
${colors.green('add')} <name> <pane> [remark] Add a new agent
|
|
52
52
|
${colors.green('this')} <name> [remark] Register current pane as an agent
|
|
53
53
|
${colors.green('update')} <name> [options] Update an agent's config
|
|
54
54
|
${colors.green('remove')} <name> Remove an agent
|
|
55
|
+
${colors.green('migrate')} [--dry-run] Copy legacy JSON registry to tmux metadata
|
|
56
|
+
${colors.green('team')} [ls|add|rm|panes] Manage shared teams
|
|
55
57
|
${colors.green('install')} [claude|codex] Install tmux-team for an AI agent
|
|
56
58
|
${colors.green('init')} Create empty tmux-team.json
|
|
57
59
|
${colors.green('config')} [show|set|clear] View/modify settings
|
|
@@ -86,12 +88,15 @@ ${colors.yellow('EXAMPLES')}${
|
|
|
86
88
|
tmux-team check codex ${colors.dim('← read response later')}`
|
|
87
89
|
}
|
|
88
90
|
tmux-team list --json
|
|
91
|
+
tmux-team list acme-app
|
|
92
|
+
tmux-team list main.1.0
|
|
89
93
|
tmux-team add codex 10.1 "Code review specialist"
|
|
90
94
|
|
|
91
95
|
${colors.yellow('CONFIG')}
|
|
92
|
-
|
|
96
|
+
Runtime: tmux pane metadata (agent registry)
|
|
97
|
+
Local: ./tmux-team.json (legacy registry + $config override)
|
|
93
98
|
Global: ~/.config/tmux-team/config.json (settings)
|
|
94
|
-
Teams:
|
|
99
|
+
Teams: tmux pane metadata; team panes shows cwd + workspace/team scopes
|
|
95
100
|
|
|
96
101
|
${colors.yellow('CHANGE MODE')}
|
|
97
102
|
tmux-team config set mode wait ${colors.dim('Enable wait mode (local)')}
|
|
@@ -26,7 +26,14 @@ function createCtx(testDir: string, overrides?: Partial<{ flags: Partial<Flags>
|
|
|
26
26
|
const config: ResolvedConfig = {
|
|
27
27
|
mode: 'polling',
|
|
28
28
|
preambleMode: 'always',
|
|
29
|
-
defaults: {
|
|
29
|
+
defaults: {
|
|
30
|
+
timeout: 180,
|
|
31
|
+
pollInterval: 1,
|
|
32
|
+
captureLines: 100,
|
|
33
|
+
maxCaptureLines: 2000,
|
|
34
|
+
preambleEvery: 3,
|
|
35
|
+
pasteEnterDelayMs: 500,
|
|
36
|
+
},
|
|
30
37
|
agents: {},
|
|
31
38
|
paneRegistry: {},
|
|
32
39
|
};
|
|
@@ -36,6 +43,13 @@ function createCtx(testDir: string, overrides?: Partial<{ flags: Partial<Flags>
|
|
|
36
43
|
capture: vi.fn(),
|
|
37
44
|
listPanes: vi.fn(() => []),
|
|
38
45
|
getCurrentPaneId: vi.fn(() => null),
|
|
46
|
+
resolvePaneTarget: vi.fn((target: string) => target),
|
|
47
|
+
getAgentRegistry: vi.fn(() => ({ paneRegistry: {}, agents: {} })),
|
|
48
|
+
setAgentRegistration: vi.fn(),
|
|
49
|
+
clearAgentRegistration: vi.fn(() => false),
|
|
50
|
+
listTeams: vi.fn(() => ({})),
|
|
51
|
+
listTeamPanes: vi.fn(() => []),
|
|
52
|
+
removeTeam: vi.fn(() => ({ removed: 0, agents: [] })),
|
|
39
53
|
};
|
|
40
54
|
return {
|
|
41
55
|
argv: [],
|
package/src/commands/list.ts
CHANGED
|
@@ -3,9 +3,22 @@
|
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
import type { Context } from '../types.js';
|
|
6
|
+
import { listPaneStatus, listTeamMembers } from './team.js';
|
|
6
7
|
|
|
7
|
-
export function cmdList(ctx: Context): void {
|
|
8
|
+
export function cmdList(ctx: Context, target?: string): void {
|
|
8
9
|
const { ui, config, flags } = ctx;
|
|
10
|
+
|
|
11
|
+
if (target) {
|
|
12
|
+
const teams = ctx.tmux.listTeams();
|
|
13
|
+
if (teams[target]) {
|
|
14
|
+
listTeamMembers(ctx, [target]);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
listPaneStatus(ctx, target);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
const agents = Object.entries(config.paneRegistry);
|
|
10
23
|
|
|
11
24
|
if (flags.json) {
|
|
@@ -15,7 +28,9 @@ export function cmdList(ctx: Context): void {
|
|
|
15
28
|
|
|
16
29
|
if (agents.length === 0) {
|
|
17
30
|
if (flags.team) {
|
|
18
|
-
ui.info(
|
|
31
|
+
ui.info(
|
|
32
|
+
`No agents in team "${flags.team}". Use 'tmt this <name> --team ${flags.team}' to add one.`
|
|
33
|
+
);
|
|
19
34
|
} else {
|
|
20
35
|
ui.info("No agents configured. Use 'tmux-team add <name> <pane>' to add one.");
|
|
21
36
|
}
|
|
@@ -26,6 +41,10 @@ export function cmdList(ctx: Context): void {
|
|
|
26
41
|
if (flags.team) {
|
|
27
42
|
console.log(`Team: ${flags.team}`);
|
|
28
43
|
console.log();
|
|
44
|
+
} else if (config.registrySource === 'legacy') {
|
|
45
|
+
ui.warn(
|
|
46
|
+
'Using legacy tmux-team.json registry. Run `tmt migrate` to store registrations in tmux.'
|
|
47
|
+
);
|
|
29
48
|
}
|
|
30
49
|
ui.table(
|
|
31
50
|
['NAME', 'PANE', 'REMARK'],
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// migrate command - copy legacy JSON registry into tmux metadata
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import type { Context, PaneEntry } from '../types.js';
|
|
6
|
+
import { ExitCodes } from '../exits.js';
|
|
7
|
+
import { loadLocalConfigFile, saveLocalConfigFile } from '../config.js';
|
|
8
|
+
import { getRegistryScope, registrationFromEntry, scopeLabel } from '../registry.js';
|
|
9
|
+
|
|
10
|
+
interface MigrationItem {
|
|
11
|
+
agent: string;
|
|
12
|
+
fromPane: string;
|
|
13
|
+
pane: string;
|
|
14
|
+
remark?: string;
|
|
15
|
+
status: 'ready' | 'migrated';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function cmdMigrate(ctx: Context, args: string[]): void {
|
|
19
|
+
const dryRun = args.includes('--dry-run');
|
|
20
|
+
const cleanup = args.includes('--cleanup');
|
|
21
|
+
const { ui, paths, flags, tmux, exit } = ctx;
|
|
22
|
+
const localConfig = loadLocalConfigFile(paths);
|
|
23
|
+
const scope = getRegistryScope(ctx);
|
|
24
|
+
const items: MigrationItem[] = [];
|
|
25
|
+
|
|
26
|
+
for (const [agentName, rawEntry] of Object.entries(localConfig)) {
|
|
27
|
+
if (agentName === '$config') continue;
|
|
28
|
+
const entry = rawEntry as PaneEntry | undefined;
|
|
29
|
+
if (!entry?.pane) continue;
|
|
30
|
+
|
|
31
|
+
const pane = tmux.resolvePaneTarget(entry.pane);
|
|
32
|
+
if (!pane) {
|
|
33
|
+
ui.error(`Pane '${entry.pane}' for agent '${agentName}' not found. Is tmux running?`);
|
|
34
|
+
exit(ExitCodes.PANE_NOT_FOUND);
|
|
35
|
+
}
|
|
36
|
+
const paneId = pane as string;
|
|
37
|
+
|
|
38
|
+
items.push({
|
|
39
|
+
agent: agentName,
|
|
40
|
+
fromPane: entry.pane,
|
|
41
|
+
pane: paneId,
|
|
42
|
+
...(entry.remark !== undefined && { remark: entry.remark }),
|
|
43
|
+
status: dryRun ? 'ready' : 'migrated',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!dryRun) {
|
|
47
|
+
tmux.setAgentRegistration(paneId, scope, registrationFromEntry(agentName, entry));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!dryRun && cleanup && items.length > 0) {
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
delete localConfig[item.agent];
|
|
54
|
+
}
|
|
55
|
+
saveLocalConfigFile(paths, localConfig);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (flags.json) {
|
|
59
|
+
ui.json({
|
|
60
|
+
dryRun,
|
|
61
|
+
cleanup,
|
|
62
|
+
scope,
|
|
63
|
+
migrated: dryRun ? 0 : items.length,
|
|
64
|
+
items,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (items.length === 0) {
|
|
70
|
+
ui.info(`No legacy agents found in ${paths.localConfig}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const action = dryRun ? 'Would migrate' : 'Migrated';
|
|
75
|
+
ui.success(`${action} ${items.length} agent(s) to ${scopeLabel(scope)}`);
|
|
76
|
+
ui.table(
|
|
77
|
+
['AGENT', 'FROM', 'PANE', 'REMARK'],
|
|
78
|
+
items.map((item) => [item.agent, item.fromPane, item.pane, item.remark ?? '-'])
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!dryRun && !cleanup) {
|
|
82
|
+
ui.info('Legacy JSON was left in place. Use --cleanup to remove migrated agent entries.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -14,7 +14,12 @@ import { cmdPreamble } from './preamble.js';
|
|
|
14
14
|
// Test utilities
|
|
15
15
|
// ─────────────────────────────────────────────────────────────
|
|
16
16
|
|
|
17
|
-
function createMockUI(): UI & {
|
|
17
|
+
function createMockUI(): UI & {
|
|
18
|
+
errors: string[];
|
|
19
|
+
warnings: string[];
|
|
20
|
+
infos: string[];
|
|
21
|
+
jsonOutput: unknown[];
|
|
22
|
+
} {
|
|
18
23
|
const mock = {
|
|
19
24
|
errors: [] as string[],
|
|
20
25
|
warnings: [] as string[],
|
|
@@ -48,7 +53,8 @@ function createDefaultConfig(): ResolvedConfig {
|
|
|
48
53
|
pollInterval: 1,
|
|
49
54
|
captureLines: 100,
|
|
50
55
|
maxCaptureLines: 2000,
|
|
51
|
-
preambleEvery: 3,
|
|
56
|
+
preambleEvery: 3,
|
|
57
|
+
pasteEnterDelayMs: 500,
|
|
52
58
|
},
|
|
53
59
|
agents: {},
|
|
54
60
|
paneRegistry: {
|
|
@@ -64,6 +70,13 @@ function createMockTmux(): Tmux {
|
|
|
64
70
|
capture: vi.fn(() => ''),
|
|
65
71
|
listPanes: vi.fn(() => []),
|
|
66
72
|
getCurrentPaneId: vi.fn(() => null),
|
|
73
|
+
resolvePaneTarget: vi.fn((target: string) => target),
|
|
74
|
+
getAgentRegistry: vi.fn(() => ({ paneRegistry: {}, agents: {} })),
|
|
75
|
+
setAgentRegistration: vi.fn(),
|
|
76
|
+
clearAgentRegistration: vi.fn(() => false),
|
|
77
|
+
listTeams: vi.fn(() => ({})),
|
|
78
|
+
listTeamPanes: vi.fn(() => []),
|
|
79
|
+
removeTeam: vi.fn(() => ({ removed: 0, agents: [] })),
|
|
67
80
|
};
|
|
68
81
|
}
|
|
69
82
|
|
package/src/commands/preamble.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { Context } from '../types.js';
|
|
6
6
|
import { ExitCodes } from '../context.js';
|
|
7
7
|
import { loadLocalConfigFile, saveLocalConfigFile } from '../config.js';
|
|
8
|
+
import { getRegistryScope, registrationFromEntry } from '../registry.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Show preamble(s) for agent(s).
|
|
@@ -60,7 +61,7 @@ function showPreamble(ctx: Context, agentName?: string): void {
|
|
|
60
61
|
* Set preamble for an agent (in local config).
|
|
61
62
|
*/
|
|
62
63
|
function setPreamble(ctx: Context, agentName: string, preamble: string): void {
|
|
63
|
-
const { ui, paths, flags, config } = ctx;
|
|
64
|
+
const { ui, paths, flags, config, tmux } = ctx;
|
|
64
65
|
|
|
65
66
|
// Check if agent exists in pane registry
|
|
66
67
|
if (!config.paneRegistry[agentName]) {
|
|
@@ -69,15 +70,19 @@ function setPreamble(ctx: Context, agentName: string, preamble: string): void {
|
|
|
69
70
|
ctx.exit(ExitCodes.ERROR);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (agentEntry) {
|
|
77
|
-
agentEntry.preamble = preamble;
|
|
73
|
+
const pane = tmux.resolvePaneTarget(config.paneRegistry[agentName].pane);
|
|
74
|
+
if (!pane) {
|
|
75
|
+
ui.error(`Pane '${config.paneRegistry[agentName].pane}' not found. Is tmux running?`);
|
|
76
|
+
ctx.exit(ExitCodes.PANE_NOT_FOUND);
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
const nextEntry = { ...config.paneRegistry[agentName], pane, preamble };
|
|
80
|
+
tmux.setAgentRegistration(
|
|
81
|
+
pane,
|
|
82
|
+
getRegistryScope(ctx),
|
|
83
|
+
registrationFromEntry(agentName, nextEntry)
|
|
84
|
+
);
|
|
85
|
+
updateLegacyPreambleIfPresent(paths, agentName, preamble);
|
|
81
86
|
|
|
82
87
|
if (flags.json) {
|
|
83
88
|
ui.json({ agent: agentName, preamble, status: 'set' });
|
|
@@ -90,14 +95,28 @@ function setPreamble(ctx: Context, agentName: string, preamble: string): void {
|
|
|
90
95
|
* Clear preamble for an agent (in local config).
|
|
91
96
|
*/
|
|
92
97
|
function clearPreamble(ctx: Context, agentName: string): void {
|
|
93
|
-
const { ui, paths, flags } = ctx;
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
const { ui, paths, flags, config, tmux } = ctx;
|
|
99
|
+
|
|
100
|
+
const entry = config.paneRegistry[agentName];
|
|
101
|
+
const hasPreamble =
|
|
102
|
+
entry?.preamble !== undefined ||
|
|
103
|
+
config.agents[agentName]?.preamble !== undefined ||
|
|
104
|
+
legacyHasPreamble(paths, agentName);
|
|
105
|
+
|
|
106
|
+
if (entry && hasPreamble) {
|
|
107
|
+
const pane = tmux.resolvePaneTarget(entry.pane);
|
|
108
|
+
if (!pane) {
|
|
109
|
+
ui.error(`Pane '${entry.pane}' not found. Is tmux running?`);
|
|
110
|
+
ctx.exit(ExitCodes.PANE_NOT_FOUND);
|
|
111
|
+
}
|
|
112
|
+
const nextEntry = { ...entry, pane };
|
|
113
|
+
delete nextEntry.preamble;
|
|
114
|
+
tmux.setAgentRegistration(
|
|
115
|
+
pane,
|
|
116
|
+
getRegistryScope(ctx),
|
|
117
|
+
registrationFromEntry(agentName, nextEntry)
|
|
118
|
+
);
|
|
119
|
+
clearLegacyPreambleIfPresent(paths, agentName);
|
|
101
120
|
|
|
102
121
|
if (flags.json) {
|
|
103
122
|
ui.json({ agent: agentName, status: 'cleared' });
|
|
@@ -113,6 +132,32 @@ function clearPreamble(ctx: Context, agentName: string): void {
|
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
134
|
|
|
135
|
+
function updateLegacyPreambleIfPresent(
|
|
136
|
+
paths: Context['paths'],
|
|
137
|
+
agentName: string,
|
|
138
|
+
preamble: string
|
|
139
|
+
): void {
|
|
140
|
+
const localConfig = loadLocalConfigFile(paths);
|
|
141
|
+
const agentEntry = localConfig[agentName] as { pane?: string; preamble?: string } | undefined;
|
|
142
|
+
if (!agentEntry) return;
|
|
143
|
+
agentEntry.preamble = preamble;
|
|
144
|
+
saveLocalConfigFile(paths, localConfig);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function clearLegacyPreambleIfPresent(paths: Context['paths'], agentName: string): void {
|
|
148
|
+
const localConfig = loadLocalConfigFile(paths);
|
|
149
|
+
const agentEntry = localConfig[agentName] as { pane?: string; preamble?: string } | undefined;
|
|
150
|
+
if (!agentEntry || !Object.prototype.hasOwnProperty.call(agentEntry, 'preamble')) return;
|
|
151
|
+
delete agentEntry.preamble;
|
|
152
|
+
saveLocalConfigFile(paths, localConfig);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function legacyHasPreamble(paths: Context['paths'], agentName: string): boolean {
|
|
156
|
+
const localConfig = loadLocalConfigFile(paths);
|
|
157
|
+
const agentEntry = localConfig[agentName] as { pane?: string; preamble?: string } | undefined;
|
|
158
|
+
return Boolean(agentEntry && Object.prototype.hasOwnProperty.call(agentEntry, 'preamble'));
|
|
159
|
+
}
|
|
160
|
+
|
|
116
161
|
/**
|
|
117
162
|
* Preamble command entry point.
|
|
118
163
|
*/
|
package/src/commands/remove.ts
CHANGED
|
@@ -5,22 +5,26 @@
|
|
|
5
5
|
import type { Context } from '../types.js';
|
|
6
6
|
import { ExitCodes } from '../exits.js';
|
|
7
7
|
import { loadLocalConfigFile, saveLocalConfigFile } from '../config.js';
|
|
8
|
+
import { getRegistryScope } from '../registry.js';
|
|
8
9
|
|
|
9
10
|
export function cmdRemove(ctx: Context, name: string): void {
|
|
10
|
-
const { ui, config, paths, flags, exit } = ctx;
|
|
11
|
+
const { ui, config, paths, flags, tmux, exit } = ctx;
|
|
11
12
|
|
|
12
13
|
if (!config.paneRegistry[name]) {
|
|
13
14
|
ui.error(`Agent '${name}' not found.`);
|
|
14
15
|
exit(ExitCodes.PANE_NOT_FOUND);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const removedFromTmux = tmux.clearAgentRegistration(name, getRegistryScope(ctx));
|
|
19
|
+
if (!removedFromTmux) {
|
|
20
|
+
// Legacy fallback: remove from tmux-team.json when this scope still uses it.
|
|
21
|
+
const localConfig = loadLocalConfigFile(paths);
|
|
22
|
+
delete localConfig[name];
|
|
23
|
+
saveLocalConfigFile(paths, localConfig);
|
|
24
|
+
}
|
|
21
25
|
|
|
22
26
|
if (flags.json) {
|
|
23
|
-
ui.json({ removed: name });
|
|
27
|
+
ui.json({ removed: name, source: removedFromTmux ? 'tmux' : 'legacy' });
|
|
24
28
|
} else {
|
|
25
29
|
ui.success(`Removed agent '${name}'`);
|
|
26
30
|
}
|