@towles/tool 0.0.106 → 0.0.108
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 +7 -1
- package/package.json +2 -1
- package/plugins/tt-agentboard/README.md +160 -0
- package/plugins/tt-agentboard/apps/server/package.json +20 -0
- package/plugins/tt-agentboard/apps/server/src/main.ts +60 -0
- package/plugins/tt-agentboard/apps/tui/build.ts +11 -0
- package/plugins/tt-agentboard/apps/tui/bunfig.toml +1 -0
- package/plugins/tt-agentboard/apps/tui/package.json +23 -0
- package/plugins/tt-agentboard/apps/tui/scripts/sessionizer.sh +36 -0
- package/plugins/tt-agentboard/apps/tui/src/components/DetailPanel.tsx +350 -0
- package/plugins/tt-agentboard/apps/tui/src/components/DiffStats.tsx +33 -0
- package/plugins/tt-agentboard/apps/tui/src/components/SessionCard.tsx +177 -0
- package/plugins/tt-agentboard/apps/tui/src/components/StatusBar.tsx +49 -0
- package/plugins/tt-agentboard/apps/tui/src/constants.ts +46 -0
- package/plugins/tt-agentboard/apps/tui/src/detail-panel-height.ts +21 -0
- package/plugins/tt-agentboard/apps/tui/src/index.tsx +880 -0
- package/plugins/tt-agentboard/apps/tui/src/mux-context.ts +61 -0
- package/plugins/tt-agentboard/apps/tui/tsconfig.json +15 -0
- package/plugins/tt-agentboard/bun.lock +444 -0
- package/plugins/tt-agentboard/package.json +26 -0
- package/plugins/tt-agentboard/packages/mux-tmux/package.json +14 -0
- package/plugins/tt-agentboard/packages/mux-tmux/src/client.ts +550 -0
- package/plugins/tt-agentboard/packages/mux-tmux/src/index.ts +18 -0
- package/plugins/tt-agentboard/packages/mux-tmux/src/provider.ts +259 -0
- package/plugins/tt-agentboard/packages/mux-tmux/tsconfig.json +13 -0
- package/plugins/tt-agentboard/packages/runtime/package.json +14 -0
- package/plugins/tt-agentboard/packages/runtime/src/agents/tracker.ts +233 -0
- package/plugins/tt-agentboard/packages/runtime/src/agents/watchers/amp.ts +316 -0
- package/plugins/tt-agentboard/packages/runtime/src/agents/watchers/claude-code.ts +374 -0
- package/plugins/tt-agentboard/packages/runtime/src/agents/watchers/codex.ts +364 -0
- package/plugins/tt-agentboard/packages/runtime/src/agents/watchers/opencode.ts +249 -0
- package/plugins/tt-agentboard/packages/runtime/src/config.ts +70 -0
- package/plugins/tt-agentboard/packages/runtime/src/contracts/agent-watcher.ts +38 -0
- package/plugins/tt-agentboard/packages/runtime/src/contracts/agent.ts +16 -0
- package/plugins/tt-agentboard/packages/runtime/src/contracts/index.ts +3 -0
- package/plugins/tt-agentboard/packages/runtime/src/contracts/mux.ts +148 -0
- package/plugins/tt-agentboard/packages/runtime/src/debug.ts +19 -0
- package/plugins/tt-agentboard/packages/runtime/src/index.ts +69 -0
- package/plugins/tt-agentboard/packages/runtime/src/mux/detect.ts +20 -0
- package/plugins/tt-agentboard/packages/runtime/src/mux/registry.ts +45 -0
- package/plugins/tt-agentboard/packages/runtime/src/plugins/loader.ts +152 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/context.ts +112 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/git-info.ts +164 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/index.ts +1753 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/launcher.ts +71 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/metadata-store.ts +86 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/pane-scanner.ts +327 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/port-scanner.ts +155 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/session-order.ts +127 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/sidebar-manager.ts +232 -0
- package/plugins/tt-agentboard/packages/runtime/src/server/sidebar-width-sync.ts +66 -0
- package/plugins/tt-agentboard/packages/runtime/src/shared.ts +179 -0
- package/plugins/tt-agentboard/packages/runtime/src/themes.ts +750 -0
- package/plugins/tt-agentboard/packages/runtime/test/config.test.ts +83 -0
- package/plugins/tt-agentboard/packages/runtime/test/tracker.test.ts +172 -0
- package/plugins/tt-agentboard/packages/runtime/tsconfig.json +13 -0
- package/plugins/tt-agentboard/tsconfig.json +19 -0
- package/plugins/tt-auto-claude/.claude-plugin/plugin.json +8 -0
- package/plugins/tt-auto-claude/commands/create-issue.md +20 -0
- package/plugins/tt-auto-claude/commands/list.md +21 -0
- package/plugins/tt-auto-claude/skills/auto-claude/SKILL.md +71 -0
- package/plugins/tt-core/.claude-plugin/plugin.json +8 -0
- package/plugins/tt-core/README.md +18 -0
- package/plugins/tt-core/commands/improve-architecture.md +66 -0
- package/plugins/tt-core/commands/interview-me.md +38 -0
- package/plugins/tt-core/commands/prd-to-issues.md +49 -0
- package/plugins/tt-core/commands/refine-text.md +30 -0
- package/plugins/tt-core/commands/task.md +37 -0
- package/plugins/tt-core/commands/tdd.md +69 -0
- package/plugins/tt-core/commands/write-prd.md +69 -0
- package/plugins/tt-core/promptfooconfig.interview-me.yaml +155 -0
- package/plugins/tt-core/promptfooconfig.refine-text.yaml +242 -0
- package/plugins/tt-core/promptfooconfig.tdd.yaml +144 -0
- package/plugins/tt-core/promptfooconfig.write-prd.yaml +145 -0
- package/plugins/tt-core/skills/towles-tool/SKILL.md +35 -0
- package/src/commands/agentboard.ts +19 -2
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
// --- Types ---
|
|
2
|
+
|
|
3
|
+
export interface TmuxClientOptions {
|
|
4
|
+
/** Path to tmux binary (default: "tmux") */
|
|
5
|
+
bin?: string;
|
|
6
|
+
/** tmux socket name (-L flag) */
|
|
7
|
+
socketName?: string;
|
|
8
|
+
/** tmux socket path (-S flag) */
|
|
9
|
+
socketPath?: string;
|
|
10
|
+
/** Whether run() throws on non-zero exit (default: false) */
|
|
11
|
+
throwOnError?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TmuxRunResult {
|
|
15
|
+
args: readonly string[];
|
|
16
|
+
exitCode: number;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
ok: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class TmuxError extends Error {
|
|
23
|
+
readonly args: readonly string[];
|
|
24
|
+
readonly exitCode: number;
|
|
25
|
+
readonly stdout: string;
|
|
26
|
+
readonly stderr: string;
|
|
27
|
+
|
|
28
|
+
constructor(result: TmuxRunResult) {
|
|
29
|
+
super(result.stderr || `tmux exited with code ${result.exitCode}`);
|
|
30
|
+
this.name = "TmuxError";
|
|
31
|
+
this.args = result.args;
|
|
32
|
+
this.exitCode = result.exitCode;
|
|
33
|
+
this.stdout = result.stdout;
|
|
34
|
+
this.stderr = result.stderr;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Typed result interfaces ---
|
|
39
|
+
|
|
40
|
+
export interface SessionInfo {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
createdAt: number;
|
|
44
|
+
attachedClients: number;
|
|
45
|
+
windowCount: number;
|
|
46
|
+
dir: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WindowInfo {
|
|
50
|
+
id: string;
|
|
51
|
+
sessionId: string;
|
|
52
|
+
sessionName: string;
|
|
53
|
+
index: number;
|
|
54
|
+
name: string;
|
|
55
|
+
active: boolean;
|
|
56
|
+
paneCount: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface PaneInfo {
|
|
60
|
+
id: string;
|
|
61
|
+
sessionName: string;
|
|
62
|
+
windowId: string;
|
|
63
|
+
windowIndex: number;
|
|
64
|
+
index: number;
|
|
65
|
+
active: boolean;
|
|
66
|
+
tty: string;
|
|
67
|
+
pid: number;
|
|
68
|
+
cwd: string;
|
|
69
|
+
command: string;
|
|
70
|
+
title: string;
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
left: number;
|
|
74
|
+
right: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ClientInfo {
|
|
78
|
+
name: string;
|
|
79
|
+
tty: string;
|
|
80
|
+
pid: number;
|
|
81
|
+
sessionName: string;
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Hook types ---
|
|
87
|
+
|
|
88
|
+
export const HOOK_NAMES = [
|
|
89
|
+
"client-session-changed",
|
|
90
|
+
"client-resized",
|
|
91
|
+
"client-attached",
|
|
92
|
+
"client-detached",
|
|
93
|
+
"client-focus-in",
|
|
94
|
+
"client-focus-out",
|
|
95
|
+
"session-created",
|
|
96
|
+
"session-closed",
|
|
97
|
+
"session-renamed",
|
|
98
|
+
"session-window-changed",
|
|
99
|
+
"window-linked",
|
|
100
|
+
"window-unlinked",
|
|
101
|
+
"window-renamed",
|
|
102
|
+
"window-layout-changed",
|
|
103
|
+
"after-select-window",
|
|
104
|
+
"after-new-window",
|
|
105
|
+
"after-resize-pane",
|
|
106
|
+
"pane-died",
|
|
107
|
+
"pane-exited",
|
|
108
|
+
"pane-focus-in",
|
|
109
|
+
"pane-focus-out",
|
|
110
|
+
] as const;
|
|
111
|
+
|
|
112
|
+
export type HookName = (typeof HOOK_NAMES)[number] | (string & {});
|
|
113
|
+
|
|
114
|
+
// --- Pane list scoping ---
|
|
115
|
+
|
|
116
|
+
export type PaneScope =
|
|
117
|
+
| { scope?: "all" }
|
|
118
|
+
| { scope: "session"; target: string }
|
|
119
|
+
| { scope: "window"; target: string };
|
|
120
|
+
|
|
121
|
+
// --- Split-window options ---
|
|
122
|
+
|
|
123
|
+
export interface SplitWindowOptions {
|
|
124
|
+
target: string;
|
|
125
|
+
direction?: "horizontal" | "vertical";
|
|
126
|
+
/** "before" = -b flag (split left/above), default is right/below */
|
|
127
|
+
before?: boolean;
|
|
128
|
+
/** Split against the entire window instead of only the target pane. */
|
|
129
|
+
fullWindow?: boolean;
|
|
130
|
+
size?: number;
|
|
131
|
+
command?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Internal parser helpers ---
|
|
135
|
+
|
|
136
|
+
/** Field delimiter — tab character, universally supported by tmux */
|
|
137
|
+
const SEP = "\t";
|
|
138
|
+
|
|
139
|
+
type Parser<T> = (raw: string) => T;
|
|
140
|
+
|
|
141
|
+
const str: Parser<string> = (s) => s;
|
|
142
|
+
const int: Parser<number> = (s) => {
|
|
143
|
+
const n = Number.parseInt(s, 10);
|
|
144
|
+
return Number.isNaN(n) ? 0 : n;
|
|
145
|
+
};
|
|
146
|
+
const bool: Parser<boolean> = (s) => s === "1";
|
|
147
|
+
|
|
148
|
+
type FieldSpec<T> = { [K in keyof T]: readonly [field: string, parse: Parser<T[K]>] };
|
|
149
|
+
|
|
150
|
+
function buildFormat<T>(spec: FieldSpec<T>): string {
|
|
151
|
+
const keys = Object.keys(spec) as (keyof T)[];
|
|
152
|
+
return keys.map((k) => `#{${spec[k][0]}}`).join(SEP);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseRows<T>(spec: FieldSpec<T>, raw: string): T[] {
|
|
156
|
+
if (!raw) return [];
|
|
157
|
+
const keys = Object.keys(spec) as (keyof T)[];
|
|
158
|
+
return raw.split("\n").reduce<T[]>((acc, line) => {
|
|
159
|
+
if (!line) return acc;
|
|
160
|
+
const parts = line.split(SEP);
|
|
161
|
+
const obj = {} as T;
|
|
162
|
+
for (let i = 0; i < keys.length; i++) {
|
|
163
|
+
const key = keys[i]!;
|
|
164
|
+
const [, parse] = spec[key];
|
|
165
|
+
obj[key] = parse(parts[i] ?? "");
|
|
166
|
+
}
|
|
167
|
+
acc.push(obj);
|
|
168
|
+
return acc;
|
|
169
|
+
}, []);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- Specs ---
|
|
173
|
+
|
|
174
|
+
const SESSION_SPEC: FieldSpec<SessionInfo> = {
|
|
175
|
+
id: ["session_id", str],
|
|
176
|
+
name: ["session_name", str],
|
|
177
|
+
createdAt: ["session_created", int],
|
|
178
|
+
attachedClients: ["session_attached", int],
|
|
179
|
+
windowCount: ["session_windows", int],
|
|
180
|
+
dir: ["session_path", str],
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const WINDOW_SPEC: FieldSpec<WindowInfo> = {
|
|
184
|
+
id: ["window_id", str],
|
|
185
|
+
sessionId: ["session_id", str],
|
|
186
|
+
sessionName: ["session_name", str],
|
|
187
|
+
index: ["window_index", int],
|
|
188
|
+
name: ["window_name", str],
|
|
189
|
+
active: ["window_active", bool],
|
|
190
|
+
paneCount: ["window_panes", int],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const PANE_SPEC: FieldSpec<PaneInfo> = {
|
|
194
|
+
id: ["pane_id", str],
|
|
195
|
+
sessionName: ["session_name", str],
|
|
196
|
+
windowId: ["window_id", str],
|
|
197
|
+
windowIndex: ["window_index", int],
|
|
198
|
+
index: ["pane_index", int],
|
|
199
|
+
active: ["pane_active", bool],
|
|
200
|
+
tty: ["pane_tty", str],
|
|
201
|
+
pid: ["pane_pid", int],
|
|
202
|
+
cwd: ["pane_current_path", str],
|
|
203
|
+
command: ["pane_current_command", str],
|
|
204
|
+
title: ["pane_title", str],
|
|
205
|
+
width: ["pane_width", int],
|
|
206
|
+
height: ["pane_height", int],
|
|
207
|
+
left: ["pane_left", int],
|
|
208
|
+
right: ["pane_right", int],
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const CLIENT_SPEC: FieldSpec<ClientInfo> = {
|
|
212
|
+
name: ["client_name", str],
|
|
213
|
+
tty: ["client_tty", str],
|
|
214
|
+
pid: ["client_pid", int],
|
|
215
|
+
sessionName: ["session_name", str],
|
|
216
|
+
width: ["client_width", int],
|
|
217
|
+
height: ["client_height", int],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// --- Format string constants (pre-built for performance) ---
|
|
221
|
+
|
|
222
|
+
const SESSION_FORMAT = buildFormat(SESSION_SPEC);
|
|
223
|
+
const WINDOW_FORMAT = buildFormat(WINDOW_SPEC);
|
|
224
|
+
const PANE_FORMAT = buildFormat(PANE_SPEC);
|
|
225
|
+
const CLIENT_FORMAT = buildFormat(CLIENT_SPEC);
|
|
226
|
+
|
|
227
|
+
// --- TmuxClient ---
|
|
228
|
+
|
|
229
|
+
export class TmuxClient {
|
|
230
|
+
private bin: string;
|
|
231
|
+
private globalArgs: string[];
|
|
232
|
+
private throwOnError: boolean;
|
|
233
|
+
|
|
234
|
+
constructor(options: TmuxClientOptions = {}) {
|
|
235
|
+
this.bin = options.bin ?? "tmux";
|
|
236
|
+
this.globalArgs = [];
|
|
237
|
+
if (options.socketName) this.globalArgs.push("-L", options.socketName);
|
|
238
|
+
if (options.socketPath) this.globalArgs.push("-S", options.socketPath);
|
|
239
|
+
this.throwOnError = options.throwOnError ?? false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Direct tmux call bypassing the SDK's format parsing.
|
|
244
|
+
* Use for operations where format string parsing causes issues
|
|
245
|
+
* (e.g. join-pane, resize-window on stash sessions).
|
|
246
|
+
*/
|
|
247
|
+
rawRun(args: readonly string[]): string {
|
|
248
|
+
try {
|
|
249
|
+
const result = Bun.spawnSync([this.bin, ...this.globalArgs, ...args], {
|
|
250
|
+
stdout: "pipe",
|
|
251
|
+
stderr: "pipe",
|
|
252
|
+
});
|
|
253
|
+
return result.stdout.toString().trim();
|
|
254
|
+
} catch {
|
|
255
|
+
return "";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Low-level escape hatch: run any tmux subcommand and get typed result.
|
|
261
|
+
* All other methods use this internally.
|
|
262
|
+
*/
|
|
263
|
+
run(args: readonly string[], options?: { throwOnError?: boolean }): TmuxRunResult {
|
|
264
|
+
const fullArgs = [this.bin, ...this.globalArgs, ...args];
|
|
265
|
+
const shouldThrow = options?.throwOnError ?? this.throwOnError;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const result = Bun.spawnSync(fullArgs, { stdout: "pipe", stderr: "pipe" });
|
|
269
|
+
const out: TmuxRunResult = {
|
|
270
|
+
args: fullArgs,
|
|
271
|
+
exitCode: result.exitCode,
|
|
272
|
+
stdout: result.stdout.toString().trim(),
|
|
273
|
+
stderr: result.stderr.toString().trim(),
|
|
274
|
+
ok: result.exitCode === 0,
|
|
275
|
+
};
|
|
276
|
+
if (!out.ok && shouldThrow) throw new TmuxError(out);
|
|
277
|
+
return out;
|
|
278
|
+
} catch (e) {
|
|
279
|
+
if (e instanceof TmuxError) throw e;
|
|
280
|
+
return {
|
|
281
|
+
args: fullArgs,
|
|
282
|
+
exitCode: -1,
|
|
283
|
+
stdout: "",
|
|
284
|
+
stderr: e instanceof Error ? e.message : String(e),
|
|
285
|
+
ok: false,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ─── Sessions ──────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
listSessions(): SessionInfo[] {
|
|
293
|
+
const { stdout } = this.run(["list-sessions", "-F", SESSION_FORMAT]);
|
|
294
|
+
return parseRows(SESSION_SPEC, stdout);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
newSession(options: { name?: string; cwd?: string; detached?: boolean } = {}): string {
|
|
298
|
+
const args = ["new-session"];
|
|
299
|
+
if (options.detached !== false) args.push("-d");
|
|
300
|
+
if (options.name) args.push("-s", options.name);
|
|
301
|
+
if (options.cwd) args.push("-c", options.cwd);
|
|
302
|
+
args.push("-P", "-F", "#{session_name}");
|
|
303
|
+
const { stdout } = this.run(args);
|
|
304
|
+
return stdout;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
killSession(target: string): void {
|
|
308
|
+
this.run(["kill-session", "-t", target]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ─── Windows ───────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
listWindows(options?: { scope?: "all" } | { scope: "session"; target: string }): WindowInfo[] {
|
|
314
|
+
const args = ["list-windows"];
|
|
315
|
+
if (!options || options.scope === "all" || !options.scope) {
|
|
316
|
+
args.push("-a");
|
|
317
|
+
} else if (options.scope === "session") {
|
|
318
|
+
args.push("-t", options.target);
|
|
319
|
+
}
|
|
320
|
+
args.push("-F", WINDOW_FORMAT);
|
|
321
|
+
const { stdout } = this.run(args);
|
|
322
|
+
return parseRows(WINDOW_SPEC, stdout);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
killWindow(target: string): void {
|
|
326
|
+
this.run(["kill-window", "-t", target]);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ─── Panes ─────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
listPanes(options?: PaneScope): PaneInfo[] {
|
|
332
|
+
const args = ["list-panes"];
|
|
333
|
+
if (!options || !options.scope || options.scope === "all") {
|
|
334
|
+
args.push("-a");
|
|
335
|
+
} else if (options.scope === "session") {
|
|
336
|
+
args.push("-s", "-t", options.target);
|
|
337
|
+
} else if (options.scope === "window") {
|
|
338
|
+
args.push("-t", options.target);
|
|
339
|
+
}
|
|
340
|
+
args.push("-F", PANE_FORMAT);
|
|
341
|
+
const { stdout } = this.run(args);
|
|
342
|
+
return parseRows(PANE_SPEC, stdout);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
splitWindow(options: SplitWindowOptions): PaneInfo | null {
|
|
346
|
+
const args = ["split-window"];
|
|
347
|
+
if (options.direction === "horizontal" || !options.direction) {
|
|
348
|
+
args.push(options.before ? "-hb" : "-h");
|
|
349
|
+
} else {
|
|
350
|
+
args.push(options.before ? "-vb" : "-v");
|
|
351
|
+
}
|
|
352
|
+
if (options.fullWindow) args.push("-f");
|
|
353
|
+
if (options.size != null) args.push("-l", String(options.size));
|
|
354
|
+
args.push("-t", options.target);
|
|
355
|
+
args.push("-P", "-F", PANE_FORMAT);
|
|
356
|
+
if (options.command) args.push(options.command);
|
|
357
|
+
const { stdout, ok } = this.run(args);
|
|
358
|
+
if (!ok || !stdout) return null;
|
|
359
|
+
const rows = parseRows(PANE_SPEC, stdout);
|
|
360
|
+
return rows[0] ?? null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
selectPane(target: string): void {
|
|
364
|
+
this.run(["select-pane", "-t", target]);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
setPaneStyle(target: string, style: string): void {
|
|
368
|
+
this.run(["select-pane", "-t", target, "-P", style]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
setPaneTitle(target: string, title: string): void {
|
|
372
|
+
this.run(["select-pane", "-t", target, "-T", title]);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
killPane(target: string): void {
|
|
376
|
+
this.run(["kill-pane", "-t", target]);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
resizePane(target: string, options: { width?: number; height?: number }): void {
|
|
380
|
+
const args = ["resize-pane", "-t", target];
|
|
381
|
+
if (options.width != null) args.push("-x", String(options.width));
|
|
382
|
+
if (options.height != null) args.push("-y", String(options.height));
|
|
383
|
+
this.run(args);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
breakPane(options: { source: string; name?: string; detached?: boolean }): void {
|
|
387
|
+
const args = ["break-pane"];
|
|
388
|
+
if (options.detached !== false) args.push("-d");
|
|
389
|
+
args.push("-s", options.source);
|
|
390
|
+
if (options.name) args.push("-n", options.name);
|
|
391
|
+
this.run(args);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ─── Clients ───────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
listClients(): ClientInfo[] {
|
|
397
|
+
const { stdout } = this.run(["list-clients", "-F", CLIENT_FORMAT]);
|
|
398
|
+
return parseRows(CLIENT_SPEC, stdout);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
switchClient(target: string, options?: { clientTty?: string }): void {
|
|
402
|
+
const args = ["switch-client"];
|
|
403
|
+
if (options?.clientTty) args.push("-c", options.clientTty);
|
|
404
|
+
args.push("-t", target);
|
|
405
|
+
this.run(args);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ─── Display / query ───────────────────────────────
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Run `display-message -p` to query a tmux format string.
|
|
412
|
+
* Returns the raw stdout string, trimmed.
|
|
413
|
+
*/
|
|
414
|
+
display(format: string, options?: { target?: string }): string {
|
|
415
|
+
const args = ["display-message"];
|
|
416
|
+
if (options?.target) args.push("-t", options.target);
|
|
417
|
+
args.push("-p", format);
|
|
418
|
+
return this.run(args).stdout;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get the current window ID (display-message -p '#{window_id}')
|
|
423
|
+
*/
|
|
424
|
+
getCurrentWindowId(target?: string): string {
|
|
425
|
+
return this.display("#{window_id}", target ? { target } : undefined);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get the current session name from the first attached client
|
|
430
|
+
*/
|
|
431
|
+
getCurrentSession(): string | null {
|
|
432
|
+
const clients = this.listClients();
|
|
433
|
+
if (clients.length === 0) return null;
|
|
434
|
+
return clients[0]!.sessionName || null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Get the client TTY for the current client
|
|
439
|
+
*/
|
|
440
|
+
getClientTty(): string {
|
|
441
|
+
return this.display("#{client_tty}");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get session directory (pane_current_path of the active pane)
|
|
446
|
+
*/
|
|
447
|
+
getSessionDir(target: string): string {
|
|
448
|
+
return this.display("#{pane_current_path}", { target });
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get pane count for a session by listing panes with -s flag
|
|
453
|
+
*/
|
|
454
|
+
getPaneCount(target: string): number {
|
|
455
|
+
const panes = this.listPanes({ scope: "session", target });
|
|
456
|
+
return panes.length;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get the active pane's cwd for every session in one `list-panes -a` call.
|
|
461
|
+
* Uses tmux's -f filter to get only non-sidebar panes in active windows.
|
|
462
|
+
* First hit per session wins (tmux lists active pane first).
|
|
463
|
+
*/
|
|
464
|
+
getActiveSessionDirs(): Map<string, string> {
|
|
465
|
+
const dirs = new Map<string, string>();
|
|
466
|
+
const { stdout } = this.run([
|
|
467
|
+
"list-panes",
|
|
468
|
+
"-a",
|
|
469
|
+
"-f",
|
|
470
|
+
"#{&&:#{window_active},#{!=:#{pane_title},agentboard-sidebar}}",
|
|
471
|
+
"-F",
|
|
472
|
+
`#{session_name}${SEP}#{pane_current_path}`,
|
|
473
|
+
]);
|
|
474
|
+
if (!stdout) return dirs;
|
|
475
|
+
for (const line of stdout.split("\n")) {
|
|
476
|
+
if (!line) continue;
|
|
477
|
+
const sep = line.indexOf(SEP);
|
|
478
|
+
if (sep < 0) continue;
|
|
479
|
+
const session = line.slice(0, sep);
|
|
480
|
+
const cwd = line.slice(sep + 1);
|
|
481
|
+
if (!dirs.has(session)) dirs.set(session, cwd);
|
|
482
|
+
}
|
|
483
|
+
return dirs;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Batch pane count retrieval for all sessions.
|
|
488
|
+
* Returns a Map of session name → pane count.
|
|
489
|
+
*/
|
|
490
|
+
getAllPaneCounts(): Map<string, number> {
|
|
491
|
+
const counts = new Map<string, number>();
|
|
492
|
+
const panes = this.listPanes({ scope: "all" });
|
|
493
|
+
for (const p of panes) {
|
|
494
|
+
counts.set(p.sessionName, (counts.get(p.sessionName) ?? 0) + 1);
|
|
495
|
+
}
|
|
496
|
+
return counts;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ─── Popups ────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
displayPopup(options: {
|
|
502
|
+
command: string;
|
|
503
|
+
title?: string;
|
|
504
|
+
width?: string | number;
|
|
505
|
+
height?: string | number;
|
|
506
|
+
style?: string;
|
|
507
|
+
borderStyle?: "rounded" | "sharp" | "double" | "heavy" | "simple" | "padded" | "none";
|
|
508
|
+
env?: Record<string, string>;
|
|
509
|
+
closeOnExit?: boolean;
|
|
510
|
+
}): void {
|
|
511
|
+
const args = ["display-popup"];
|
|
512
|
+
if (options.title) args.push("-T", options.title);
|
|
513
|
+
if (options.width) args.push("-w", String(options.width));
|
|
514
|
+
if (options.height) args.push("-h", String(options.height));
|
|
515
|
+
if (options.style) args.push("-s", options.style);
|
|
516
|
+
if (options.borderStyle) args.push("-b", options.borderStyle);
|
|
517
|
+
if (options.env) {
|
|
518
|
+
for (const [k, v] of Object.entries(options.env)) {
|
|
519
|
+
args.push("-e", `${k}=${v}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const closeOnExit = options.closeOnExit ?? true;
|
|
523
|
+
if (closeOnExit) args.push("-E");
|
|
524
|
+
args.push(options.command);
|
|
525
|
+
this.run(args);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ─── Hooks ─────────────────────────────────────────
|
|
529
|
+
|
|
530
|
+
setGlobalHook(name: HookName, command: string): void {
|
|
531
|
+
this.run(["set-hook", "-g", name, command]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
unsetGlobalHook(name: HookName): void {
|
|
535
|
+
this.run(["set-hook", "-gu", name]);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ─── Environment ───────────────────────────────────
|
|
539
|
+
|
|
540
|
+
setGlobalEnv(name: string, value: string): void {
|
|
541
|
+
this.run(["set-environment", "-g", name, value]);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
getGlobalEnv(name: string): string | null {
|
|
545
|
+
const { stdout, ok } = this.run(["show-environment", "-g", name]);
|
|
546
|
+
if (!ok || !stdout) return null;
|
|
547
|
+
const eqIdx = stdout.indexOf("=");
|
|
548
|
+
return eqIdx >= 0 ? stdout.slice(eqIdx + 1) : null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Re-export TmuxClient and types from client module
|
|
2
|
+
export {
|
|
3
|
+
TmuxClient,
|
|
4
|
+
TmuxError,
|
|
5
|
+
HOOK_NAMES,
|
|
6
|
+
type TmuxClientOptions,
|
|
7
|
+
type TmuxRunResult,
|
|
8
|
+
type SessionInfo,
|
|
9
|
+
type WindowInfo,
|
|
10
|
+
type PaneInfo,
|
|
11
|
+
type ClientInfo,
|
|
12
|
+
type HookName,
|
|
13
|
+
type PaneScope,
|
|
14
|
+
type SplitWindowOptions,
|
|
15
|
+
} from "./client";
|
|
16
|
+
|
|
17
|
+
// Re-export TmuxProvider from provider module
|
|
18
|
+
export { TmuxProvider, type TmuxProviderSettings } from "./provider";
|