tmux-team 4.0.0 → 4.1.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 +102 -24
- package/package.json +1 -1
- package/src/cli.test.ts +15 -1
- package/src/cli.ts +10 -0
- package/src/commands/add.ts +17 -32
- package/src/commands/basic-commands.test.ts +293 -17
- 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 +5 -2
- package/src/commands/install.test.ts +15 -1
- package/src/commands/list.ts +7 -1
- 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 +111 -22
- package/src/commands/talk.ts +8 -3
- package/src/commands/team.ts +172 -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
|
@@ -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
|
}
|
|
@@ -43,6 +43,25 @@ function createMockTmux(): Tmux & {
|
|
|
43
43
|
getCurrentPaneId() {
|
|
44
44
|
return null;
|
|
45
45
|
},
|
|
46
|
+
resolvePaneTarget(target: string) {
|
|
47
|
+
return target;
|
|
48
|
+
},
|
|
49
|
+
getAgentRegistry() {
|
|
50
|
+
return { paneRegistry: {}, agents: {} };
|
|
51
|
+
},
|
|
52
|
+
setAgentRegistration() {},
|
|
53
|
+
clearAgentRegistration() {
|
|
54
|
+
return false;
|
|
55
|
+
},
|
|
56
|
+
listTeams() {
|
|
57
|
+
return {};
|
|
58
|
+
},
|
|
59
|
+
listTeamPanes() {
|
|
60
|
+
return [];
|
|
61
|
+
},
|
|
62
|
+
removeTeam() {
|
|
63
|
+
return { removed: 0, agents: [] };
|
|
64
|
+
},
|
|
46
65
|
};
|
|
47
66
|
return mock;
|
|
48
67
|
}
|
|
@@ -239,7 +258,8 @@ describe('buildMessage (via cmdTalk)', () => {
|
|
|
239
258
|
pollInterval: 0.1,
|
|
240
259
|
captureLines: 100,
|
|
241
260
|
maxCaptureLines: 2000,
|
|
242
|
-
preambleEvery: 3,
|
|
261
|
+
preambleEvery: 3,
|
|
262
|
+
pasteEnterDelayMs: 500,
|
|
243
263
|
},
|
|
244
264
|
};
|
|
245
265
|
|
|
@@ -551,7 +571,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
551
571
|
pollInterval: 0.01,
|
|
552
572
|
captureLines: 100,
|
|
553
573
|
maxCaptureLines: 2000,
|
|
554
|
-
preambleEvery: 3,
|
|
574
|
+
preambleEvery: 3,
|
|
575
|
+
pasteEnterDelayMs: 500,
|
|
555
576
|
},
|
|
556
577
|
},
|
|
557
578
|
});
|
|
@@ -595,7 +616,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
595
616
|
pollInterval: 0.01,
|
|
596
617
|
captureLines: 100,
|
|
597
618
|
maxCaptureLines: 2000,
|
|
598
|
-
preambleEvery: 3,
|
|
619
|
+
preambleEvery: 3,
|
|
620
|
+
pasteEnterDelayMs: 500,
|
|
599
621
|
},
|
|
600
622
|
},
|
|
601
623
|
});
|
|
@@ -626,7 +648,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
626
648
|
pollInterval: 0.02,
|
|
627
649
|
captureLines: 100,
|
|
628
650
|
maxCaptureLines: 2000,
|
|
629
|
-
preambleEvery: 3,
|
|
651
|
+
preambleEvery: 3,
|
|
652
|
+
pasteEnterDelayMs: 500,
|
|
630
653
|
},
|
|
631
654
|
},
|
|
632
655
|
});
|
|
@@ -674,7 +697,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
674
697
|
pollInterval: 0.01,
|
|
675
698
|
captureLines: 100,
|
|
676
699
|
maxCaptureLines: 2000,
|
|
677
|
-
preambleEvery: 3,
|
|
700
|
+
preambleEvery: 3,
|
|
701
|
+
pasteEnterDelayMs: 500,
|
|
678
702
|
},
|
|
679
703
|
},
|
|
680
704
|
});
|
|
@@ -710,7 +734,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
710
734
|
pollInterval: 0.01,
|
|
711
735
|
captureLines: 100,
|
|
712
736
|
maxCaptureLines: 2000,
|
|
713
|
-
preambleEvery: 3,
|
|
737
|
+
preambleEvery: 3,
|
|
738
|
+
pasteEnterDelayMs: 500,
|
|
714
739
|
},
|
|
715
740
|
},
|
|
716
741
|
});
|
|
@@ -739,7 +764,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
739
764
|
pollInterval: 0.01,
|
|
740
765
|
captureLines: 100,
|
|
741
766
|
maxCaptureLines: 2000,
|
|
742
|
-
preambleEvery: 3,
|
|
767
|
+
preambleEvery: 3,
|
|
768
|
+
pasteEnterDelayMs: 500,
|
|
743
769
|
},
|
|
744
770
|
},
|
|
745
771
|
});
|
|
@@ -791,7 +817,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
791
817
|
pollInterval: 0.05,
|
|
792
818
|
captureLines: 100,
|
|
793
819
|
maxCaptureLines: 2000,
|
|
794
|
-
preambleEvery: 3,
|
|
820
|
+
preambleEvery: 3,
|
|
821
|
+
pasteEnterDelayMs: 500,
|
|
795
822
|
},
|
|
796
823
|
paneRegistry: {
|
|
797
824
|
codex: { pane: '10.1' },
|
|
@@ -837,7 +864,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
837
864
|
pollInterval: 0.02,
|
|
838
865
|
captureLines: 100,
|
|
839
866
|
maxCaptureLines: 2000,
|
|
840
|
-
preambleEvery: 3,
|
|
867
|
+
preambleEvery: 3,
|
|
868
|
+
pasteEnterDelayMs: 500,
|
|
841
869
|
},
|
|
842
870
|
paneRegistry: {
|
|
843
871
|
codex: { pane: '10.1' },
|
|
@@ -892,7 +920,8 @@ describe('cmdTalk - --wait mode', () => {
|
|
|
892
920
|
pollInterval: 0.02,
|
|
893
921
|
captureLines: 100,
|
|
894
922
|
maxCaptureLines: 2000,
|
|
895
|
-
preambleEvery: 3,
|
|
923
|
+
preambleEvery: 3,
|
|
924
|
+
pasteEnterDelayMs: 500,
|
|
896
925
|
},
|
|
897
926
|
paneRegistry: {
|
|
898
927
|
codex: { pane: '10.1' },
|
|
@@ -1003,7 +1032,8 @@ describe('cmdTalk - nonce collision handling', () => {
|
|
|
1003
1032
|
pollInterval: 0.01,
|
|
1004
1033
|
captureLines: 100,
|
|
1005
1034
|
maxCaptureLines: 2000,
|
|
1006
|
-
preambleEvery: 3,
|
|
1035
|
+
preambleEvery: 3,
|
|
1036
|
+
pasteEnterDelayMs: 500,
|
|
1007
1037
|
},
|
|
1008
1038
|
},
|
|
1009
1039
|
});
|
|
@@ -1058,7 +1088,8 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1058
1088
|
pollInterval: 0.01,
|
|
1059
1089
|
captureLines: 100,
|
|
1060
1090
|
maxCaptureLines: 2000,
|
|
1061
|
-
preambleEvery: 3,
|
|
1091
|
+
preambleEvery: 3,
|
|
1092
|
+
pasteEnterDelayMs: 500,
|
|
1062
1093
|
},
|
|
1063
1094
|
},
|
|
1064
1095
|
});
|
|
@@ -1099,7 +1130,8 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1099
1130
|
pollInterval: 0.01,
|
|
1100
1131
|
captureLines: 100,
|
|
1101
1132
|
maxCaptureLines: 2000,
|
|
1102
|
-
preambleEvery: 3,
|
|
1133
|
+
preambleEvery: 3,
|
|
1134
|
+
pasteEnterDelayMs: 500,
|
|
1103
1135
|
},
|
|
1104
1136
|
},
|
|
1105
1137
|
});
|
|
@@ -1141,7 +1173,8 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1141
1173
|
pollInterval: 0.01,
|
|
1142
1174
|
captureLines: 100,
|
|
1143
1175
|
maxCaptureLines: 2000,
|
|
1144
|
-
preambleEvery: 3,
|
|
1176
|
+
preambleEvery: 3,
|
|
1177
|
+
pasteEnterDelayMs: 500,
|
|
1145
1178
|
},
|
|
1146
1179
|
},
|
|
1147
1180
|
});
|
|
@@ -1178,7 +1211,8 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1178
1211
|
pollInterval: 0.01,
|
|
1179
1212
|
captureLines: 100,
|
|
1180
1213
|
maxCaptureLines: 2000,
|
|
1181
|
-
preambleEvery: 3,
|
|
1214
|
+
preambleEvery: 3,
|
|
1215
|
+
pasteEnterDelayMs: 500,
|
|
1182
1216
|
},
|
|
1183
1217
|
},
|
|
1184
1218
|
});
|
|
@@ -1228,7 +1262,8 @@ describe('cmdTalk - JSON output contract', () => {
|
|
|
1228
1262
|
pollInterval: 0.02,
|
|
1229
1263
|
captureLines: 100,
|
|
1230
1264
|
maxCaptureLines: 2000,
|
|
1231
|
-
preambleEvery: 3,
|
|
1265
|
+
preambleEvery: 3,
|
|
1266
|
+
pasteEnterDelayMs: 500,
|
|
1232
1267
|
},
|
|
1233
1268
|
paneRegistry: {
|
|
1234
1269
|
codex: { pane: '10.1' },
|
|
@@ -1310,7 +1345,16 @@ describe('cmdTalk - end marker detection', () => {
|
|
|
1310
1345
|
ui,
|
|
1311
1346
|
paths: createTestPaths(testDir),
|
|
1312
1347
|
flags: { wait: true, json: true, timeout: 0.5 },
|
|
1313
|
-
config: {
|
|
1348
|
+
config: {
|
|
1349
|
+
defaults: {
|
|
1350
|
+
timeout: 0.5,
|
|
1351
|
+
pollInterval: 0.01,
|
|
1352
|
+
captureLines: 100,
|
|
1353
|
+
maxCaptureLines: 2000,
|
|
1354
|
+
preambleEvery: 3,
|
|
1355
|
+
pasteEnterDelayMs: 500,
|
|
1356
|
+
},
|
|
1357
|
+
},
|
|
1314
1358
|
});
|
|
1315
1359
|
|
|
1316
1360
|
await cmdTalk(ctx, 'claude', 'Test message');
|
|
@@ -1344,7 +1388,16 @@ describe('cmdTalk - end marker detection', () => {
|
|
|
1344
1388
|
ui,
|
|
1345
1389
|
paths: createTestPaths(testDir),
|
|
1346
1390
|
flags: { wait: true, json: true, timeout: 0.5 },
|
|
1347
|
-
config: {
|
|
1391
|
+
config: {
|
|
1392
|
+
defaults: {
|
|
1393
|
+
timeout: 0.5,
|
|
1394
|
+
pollInterval: 0.01,
|
|
1395
|
+
captureLines: 100,
|
|
1396
|
+
maxCaptureLines: 2000,
|
|
1397
|
+
preambleEvery: 3,
|
|
1398
|
+
pasteEnterDelayMs: 500,
|
|
1399
|
+
},
|
|
1400
|
+
},
|
|
1348
1401
|
});
|
|
1349
1402
|
|
|
1350
1403
|
await cmdTalk(ctx, 'claude', 'Test');
|
|
@@ -1378,7 +1431,16 @@ Line 4 final`;
|
|
|
1378
1431
|
ui,
|
|
1379
1432
|
paths: createTestPaths(testDir),
|
|
1380
1433
|
flags: { wait: true, json: true, timeout: 0.5 },
|
|
1381
|
-
config: {
|
|
1434
|
+
config: {
|
|
1435
|
+
defaults: {
|
|
1436
|
+
timeout: 0.5,
|
|
1437
|
+
pollInterval: 0.01,
|
|
1438
|
+
captureLines: 100,
|
|
1439
|
+
maxCaptureLines: 2000,
|
|
1440
|
+
preambleEvery: 3,
|
|
1441
|
+
pasteEnterDelayMs: 500,
|
|
1442
|
+
},
|
|
1443
|
+
},
|
|
1382
1444
|
});
|
|
1383
1445
|
|
|
1384
1446
|
await cmdTalk(ctx, 'claude', 'Test');
|
|
@@ -1409,7 +1471,16 @@ Line 4 final`;
|
|
|
1409
1471
|
ui,
|
|
1410
1472
|
paths: createTestPaths(testDir),
|
|
1411
1473
|
flags: { wait: true, json: true, timeout: 0.5 },
|
|
1412
|
-
config: {
|
|
1474
|
+
config: {
|
|
1475
|
+
defaults: {
|
|
1476
|
+
timeout: 0.5,
|
|
1477
|
+
pollInterval: 0.01,
|
|
1478
|
+
captureLines: 100,
|
|
1479
|
+
maxCaptureLines: 2000,
|
|
1480
|
+
preambleEvery: 3,
|
|
1481
|
+
pasteEnterDelayMs: 500,
|
|
1482
|
+
},
|
|
1483
|
+
},
|
|
1413
1484
|
});
|
|
1414
1485
|
|
|
1415
1486
|
await cmdTalk(ctx, 'claude', 'Test');
|
|
@@ -1446,7 +1517,16 @@ Line 4 final`;
|
|
|
1446
1517
|
ui,
|
|
1447
1518
|
paths: createTestPaths(testDir),
|
|
1448
1519
|
flags: { wait: true, json: true, timeout: 0.5 },
|
|
1449
|
-
config: {
|
|
1520
|
+
config: {
|
|
1521
|
+
defaults: {
|
|
1522
|
+
timeout: 0.5,
|
|
1523
|
+
pollInterval: 0.01,
|
|
1524
|
+
captureLines: 100,
|
|
1525
|
+
maxCaptureLines: 2000,
|
|
1526
|
+
preambleEvery: 3,
|
|
1527
|
+
pasteEnterDelayMs: 500,
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1450
1530
|
});
|
|
1451
1531
|
|
|
1452
1532
|
await cmdTalk(ctx, 'claude', 'Test');
|
|
@@ -1482,7 +1562,16 @@ Line 4 final`;
|
|
|
1482
1562
|
ui,
|
|
1483
1563
|
paths: createTestPaths(testDir),
|
|
1484
1564
|
flags: { wait: true, json: true, timeout: 0.5 },
|
|
1485
|
-
config: {
|
|
1565
|
+
config: {
|
|
1566
|
+
defaults: {
|
|
1567
|
+
timeout: 0.5,
|
|
1568
|
+
pollInterval: 0.01,
|
|
1569
|
+
captureLines: 200,
|
|
1570
|
+
maxCaptureLines: 2000,
|
|
1571
|
+
preambleEvery: 3,
|
|
1572
|
+
pasteEnterDelayMs: 500,
|
|
1573
|
+
},
|
|
1574
|
+
},
|
|
1486
1575
|
});
|
|
1487
1576
|
|
|
1488
1577
|
await cmdTalk(ctx, 'claude', 'Test');
|
package/src/commands/talk.ts
CHANGED
|
@@ -186,7 +186,10 @@ function extractWithExpandableCapture(
|
|
|
186
186
|
|
|
187
187
|
if (instructionLineIndex !== -1 && instructionLineIndex < endMarkerLineIndex) {
|
|
188
188
|
// Instruction visible: extract from after instruction to marker
|
|
189
|
-
const response = lines
|
|
189
|
+
const response = lines
|
|
190
|
+
.slice(instructionLineIndex + 1, endMarkerLineIndex)
|
|
191
|
+
.join('\n')
|
|
192
|
+
.trim();
|
|
190
193
|
return { response, truncated: false };
|
|
191
194
|
}
|
|
192
195
|
|
|
@@ -619,7 +622,8 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
619
622
|
);
|
|
620
623
|
|
|
621
624
|
// Clean Gemini CLI UI artifacts
|
|
622
|
-
const response =
|
|
625
|
+
const response =
|
|
626
|
+
target === 'gemini' ? cleanGeminiResponse(extractedResponse) : extractedResponse;
|
|
623
627
|
|
|
624
628
|
if (!flags.json && isTTY) {
|
|
625
629
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
@@ -883,7 +887,8 @@ async function cmdTalkAllWait(
|
|
|
883
887
|
);
|
|
884
888
|
|
|
885
889
|
// Clean Gemini CLI UI artifacts
|
|
886
|
-
const response =
|
|
890
|
+
const response =
|
|
891
|
+
state.agent === 'gemini' ? cleanGeminiResponse(extractedResponse) : extractedResponse;
|
|
887
892
|
state.response = response;
|
|
888
893
|
state.truncated = truncated;
|
|
889
894
|
state.status = 'completed';
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// team command - inspect pane team/workspace scope and manage explicit shared teams
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import type { Context, TeamPaneInfo, TeamPaneRegistration } from '../types.js';
|
|
6
|
+
import { ExitCodes } from '../exits.js';
|
|
7
|
+
|
|
8
|
+
export function cmdTeam(ctx: Context, args: string[]): void {
|
|
9
|
+
const subcommand = args[0] ?? 'ls';
|
|
10
|
+
|
|
11
|
+
switch (subcommand) {
|
|
12
|
+
case 'ls':
|
|
13
|
+
case 'list':
|
|
14
|
+
listTeams(ctx, args.slice(1));
|
|
15
|
+
break;
|
|
16
|
+
case 'rm':
|
|
17
|
+
case 'remove':
|
|
18
|
+
removeTeam(ctx, args.slice(1));
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
ctx.ui.error(`Unknown team subcommand: ${subcommand}`);
|
|
22
|
+
ctx.ui.error('Usage: tmux-team team [ls [--summary]|rm <team> --force]');
|
|
23
|
+
ctx.exit(ExitCodes.ERROR);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function listTeams(ctx: Context, args: string[]): void {
|
|
28
|
+
const teams = ctx.tmux.listTeams();
|
|
29
|
+
const panes = ctx.tmux.listTeamPanes();
|
|
30
|
+
const summaryOnly = args.includes('--summary');
|
|
31
|
+
|
|
32
|
+
if (ctx.flags.json) {
|
|
33
|
+
ctx.ui.json({ teams, panes });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (summaryOnly) {
|
|
38
|
+
const rows = Object.entries(teams).sort(([a], [b]) => a.localeCompare(b));
|
|
39
|
+
if (rows.length === 0) {
|
|
40
|
+
ctx.ui.info('No shared teams found.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
ctx.ui.table(
|
|
44
|
+
['TEAM', 'AGENTS'],
|
|
45
|
+
rows.map(([teamName, agents]) => [teamName, agents.join(', ') || '-'])
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const groups = panesToGroups(panes);
|
|
51
|
+
if (groups.length === 0) {
|
|
52
|
+
ctx.ui.info('No tmux panes found.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const [index, group] of groups.entries()) {
|
|
57
|
+
if (index > 0) console.log('');
|
|
58
|
+
console.log(group.title);
|
|
59
|
+
ctx.ui.table(['PANE', 'TARGET', 'CWD', 'CMD'], group.rows);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface PaneGroup {
|
|
64
|
+
key: string;
|
|
65
|
+
title: string;
|
|
66
|
+
agents: Set<string>;
|
|
67
|
+
rows: string[][];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function panesToGroups(panes: TeamPaneInfo[]): PaneGroup[] {
|
|
71
|
+
const groups = new Map<string, PaneGroup>();
|
|
72
|
+
|
|
73
|
+
for (const pane of panes) {
|
|
74
|
+
if (pane.registrations.length === 0) {
|
|
75
|
+
addPaneToGroup(groups, '2:', 'Unregistered panes', pane);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const registration of pane.registrations) {
|
|
80
|
+
addPaneToGroup(
|
|
81
|
+
groups,
|
|
82
|
+
scopeSortKey(registration),
|
|
83
|
+
groupTitle(registration),
|
|
84
|
+
pane,
|
|
85
|
+
registration
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return [...groups.values()]
|
|
91
|
+
.sort((a, b) => a.key.localeCompare(b.key))
|
|
92
|
+
.map((group) => ({
|
|
93
|
+
...group,
|
|
94
|
+
title:
|
|
95
|
+
group.agents.size > 0
|
|
96
|
+
? `${group.title} (${[...group.agents].sort().join(', ')})`
|
|
97
|
+
: group.title,
|
|
98
|
+
rows: group.rows.sort((a, b) => a[1].localeCompare(b[1])),
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function addPaneToGroup(
|
|
103
|
+
groups: Map<string, PaneGroup>,
|
|
104
|
+
key: string,
|
|
105
|
+
title: string,
|
|
106
|
+
pane: TeamPaneInfo,
|
|
107
|
+
registration?: TeamPaneRegistration
|
|
108
|
+
): void {
|
|
109
|
+
const group = groups.get(key) ?? { key, title, agents: new Set<string>(), rows: [] };
|
|
110
|
+
if (registration) group.agents.add(formatAgent(registration));
|
|
111
|
+
group.rows.push([pane.pane, pane.target ?? '-', pane.cwd ?? '-', pane.command || '-']);
|
|
112
|
+
groups.set(key, group);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function scopeSortKey(registration: TeamPaneRegistration): string {
|
|
116
|
+
if (registration.scopeType === 'team') return `0:${registration.scope}`;
|
|
117
|
+
if (registration.scopeType === 'workspace') return `1:${registration.scope}`;
|
|
118
|
+
return '2:';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function groupTitle(registration: TeamPaneRegistration): string {
|
|
122
|
+
if (registration.scopeType === 'team') return `Team: ${registration.scope}`;
|
|
123
|
+
return `Workspace: ${registration.scope}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function formatAgent(registration: TeamPaneRegistration): string {
|
|
127
|
+
return registration.remark
|
|
128
|
+
? `${registration.agent} (${registration.remark})`
|
|
129
|
+
: registration.agent;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function removeTeam(ctx: Context, args: string[]): void {
|
|
133
|
+
const teamName = args.find((arg) => !arg.startsWith('-'));
|
|
134
|
+
const dryRun = args.includes('--dry-run');
|
|
135
|
+
const force = ctx.flags.force || args.includes('--force') || args.includes('-f');
|
|
136
|
+
|
|
137
|
+
if (!teamName) {
|
|
138
|
+
ctx.ui.error('Usage: tmux-team team rm <team> --force');
|
|
139
|
+
ctx.exit(ExitCodes.ERROR);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const teams = ctx.tmux.listTeams();
|
|
143
|
+
const agents = teams[teamName] ?? [];
|
|
144
|
+
if (agents.length === 0) {
|
|
145
|
+
ctx.ui.error(`Team '${teamName}' not found.`);
|
|
146
|
+
ctx.exit(ExitCodes.PANE_NOT_FOUND);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (ctx.flags.json && dryRun) {
|
|
150
|
+
ctx.ui.json({ team: teamName, dryRun: true, agents, removed: 0 });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (dryRun) {
|
|
155
|
+
ctx.ui.info(`Would remove team '${teamName}' from ${agents.length} agent(s).`);
|
|
156
|
+
ctx.ui.table(['TEAM', 'AGENTS'], [[teamName, agents.join(', ')]]);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!force) {
|
|
161
|
+
ctx.ui.error(`Refusing to remove team '${teamName}' without --force.`);
|
|
162
|
+
ctx.exit(ExitCodes.ERROR);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const result = ctx.tmux.removeTeam(teamName);
|
|
166
|
+
if (ctx.flags.json) {
|
|
167
|
+
ctx.ui.json({ team: teamName, removed: result.removed, agents: result.agents });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
ctx.ui.success(`Removed team '${teamName}' from ${result.removed} pane(s).`);
|
|
172
|
+
}
|