@towles/tool 0.0.103 → 0.0.104
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 +2 -2
- package/package.json +3 -1
- package/src/commands/agentboard.ts +129 -4
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towles/tool",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.104",
|
|
4
4
|
"description": "One off quality of life scripts that I use on a daily basis.",
|
|
5
5
|
"homepage": "https://github.com/ChrisTowles/towles-tool#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"test": "vitest run",
|
|
40
40
|
"test:watch": "CI=DisableCallingClaude vitest watch",
|
|
41
41
|
"typecheck": "tsgo --noEmit --incremental",
|
|
42
|
+
"link": "bun link",
|
|
43
|
+
"link:show": "readlink -f $(which towles-tool)",
|
|
42
44
|
"prepare": "simple-git-hooks"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
@@ -14,12 +14,11 @@ const PLUGIN_DIR = resolve(import.meta.dirname, "../../plugins/tt-agentboard");
|
|
|
14
14
|
// Keybinding defaults
|
|
15
15
|
const DEFAULT_KEY = "a";
|
|
16
16
|
const TMUX_BINDINGS = { toggle: "t", focus: "s" } as const;
|
|
17
|
-
const RUN_SHELL_LINE =
|
|
17
|
+
const RUN_SHELL_LINE = "run-shell 'tt agentboard init'";
|
|
18
18
|
const MARKER = "# agentboard";
|
|
19
19
|
|
|
20
20
|
function findTmuxConf(): string | null {
|
|
21
21
|
const candidates = [
|
|
22
|
-
resolve(process.env.HOME ?? "~", ".tmux.conf"),
|
|
23
22
|
resolve(process.env.HOME ?? "~", ".config/tmux/tmux.conf"),
|
|
24
23
|
];
|
|
25
24
|
for (const path of candidates) {
|
|
@@ -123,7 +122,7 @@ function setup(): void {
|
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
const content = readFileSync(editPath, "utf8");
|
|
126
|
-
if (content.includes(
|
|
125
|
+
if (content.includes(MARKER)) {
|
|
127
126
|
consola.success("Already installed in tmux.conf");
|
|
128
127
|
reloadTmux();
|
|
129
128
|
return;
|
|
@@ -227,6 +226,122 @@ async function ensureServerUp(): Promise<boolean> {
|
|
|
227
226
|
return false;
|
|
228
227
|
}
|
|
229
228
|
|
|
229
|
+
function tmuxDisplay(fmt: string): string {
|
|
230
|
+
try {
|
|
231
|
+
const r = spawnSync("tmux", ["display-message", "-p", fmt], {
|
|
232
|
+
encoding: "utf8",
|
|
233
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
234
|
+
});
|
|
235
|
+
return (r.stdout ?? "").trim();
|
|
236
|
+
} catch {
|
|
237
|
+
return "";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function tmuxContext(): string {
|
|
242
|
+
return tmuxDisplay("#{client_tty}|#{session_name}|#{window_id}");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function resetTmuxKeys(): void {
|
|
246
|
+
spawnSync("tmux", ["switch-client", "-T", "root"], { stdio: "pipe" });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function findSidebarPane(windowId: string): string | null {
|
|
250
|
+
try {
|
|
251
|
+
const r = spawnSync("tmux", ["list-panes", "-t", windowId, "-F", "#{pane_id} #{pane_title}"], {
|
|
252
|
+
encoding: "utf8",
|
|
253
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
254
|
+
});
|
|
255
|
+
for (const line of (r.stdout ?? "").trim().split("\n")) {
|
|
256
|
+
const [paneId, title] = line.split(" ", 2);
|
|
257
|
+
if (title === "agentboard-sidebar" && paneId) return paneId;
|
|
258
|
+
}
|
|
259
|
+
} catch {}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function tmux(...args: string[]): void {
|
|
264
|
+
spawnSync("tmux", args, { stdio: "pipe" });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function init(): void {
|
|
268
|
+
const port = process.env.TT_AGENTBOARD_PORT ?? "4201";
|
|
269
|
+
const host = process.env.TT_AGENTBOARD_HOST ?? "127.0.0.1";
|
|
270
|
+
|
|
271
|
+
// Read tmux options with defaults
|
|
272
|
+
const keyResult = spawnSync("tmux", ["show-option", "-gqv", "@agentboard-key"], {
|
|
273
|
+
encoding: "utf8",
|
|
274
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
275
|
+
});
|
|
276
|
+
const key = (keyResult.stdout ?? "").trim() || DEFAULT_KEY;
|
|
277
|
+
|
|
278
|
+
// Export to tmux environment
|
|
279
|
+
tmux("set-environment", "-g", "TT_AGENTBOARD_PORT", port);
|
|
280
|
+
tmux("set-environment", "-g", "TT_AGENTBOARD_HOST", host);
|
|
281
|
+
|
|
282
|
+
// Bind keybindings via command table "agentboard"
|
|
283
|
+
tmux("bind-key", "-T", "prefix", key, "switch-client", "-T", "agentboard");
|
|
284
|
+
tmux("bind-key", "-T", "agentboard", TMUX_BINDINGS.toggle, "run-shell", "tt agentboard run --toggle");
|
|
285
|
+
tmux("bind-key", "-T", "agentboard", TMUX_BINDINGS.focus, "run-shell", "tt agentboard run --focus");
|
|
286
|
+
|
|
287
|
+
// Number keys 1-9 switch to session by index
|
|
288
|
+
for (let i = 1; i <= 9; i++) {
|
|
289
|
+
tmux(
|
|
290
|
+
"bind-key", "-T", "agentboard", String(i), "run-shell",
|
|
291
|
+
`curl -s -X POST 'http://${host}:${port}/switch-index?index=${i}' -d "$(tmux display-message -p '#{q:client_tty}|#{q:session_name}|#{q:window_id}')" >/dev/null 2>&1 || true`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Hooks (fallback for when server isn't running yet)
|
|
296
|
+
const hookPost = (path: string, body?: string) => {
|
|
297
|
+
const bodyArg = body ? ` -d \\"${body}\\"` : "";
|
|
298
|
+
return `run-shell -b "curl -s -X POST http://${host}:${port}${path}${bodyArg} >/dev/null 2>&1 || true"`;
|
|
299
|
+
};
|
|
300
|
+
const focusBody = "#{q:client_tty}|#{q:session_name}|#{q:window_id}";
|
|
301
|
+
const resizeBody = "#{q:pane_id}|#{q:session_name}|#{q:window_id}|#{q:pane_width}|#{q:window_width}";
|
|
302
|
+
|
|
303
|
+
tmux("set-hook", "-g", "client-session-changed", hookPost("/focus", focusBody));
|
|
304
|
+
tmux("set-hook", "-g", "after-select-window", hookPost("/ensure-sidebar", focusBody));
|
|
305
|
+
tmux("set-hook", "-g", "after-resize-pane", hookPost("/resize-sidebars", resizeBody));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function runToggle(): Promise<void> {
|
|
309
|
+
if (!(await ensureServerUp())) process.exit(0);
|
|
310
|
+
const ctx = tmuxContext();
|
|
311
|
+
await fetch(`http://${SERVER_HOST}:${SERVER_PORT}/toggle`, { method: "POST", body: ctx }).catch(() => {});
|
|
312
|
+
resetTmuxKeys();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function runFocus(): Promise<void> {
|
|
316
|
+
const windowId = tmuxDisplay("#{window_id}");
|
|
317
|
+
if (!windowId) process.exit(0);
|
|
318
|
+
|
|
319
|
+
// If sidebar already exists, just focus it
|
|
320
|
+
const existing = findSidebarPane(windowId);
|
|
321
|
+
if (existing) {
|
|
322
|
+
spawnSync("tmux", ["select-pane", "-t", existing], { stdio: "pipe" });
|
|
323
|
+
resetTmuxKeys();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Otherwise, ensure server + toggle sidebar on
|
|
328
|
+
if (!(await ensureServerUp())) process.exit(0);
|
|
329
|
+
const ctx = tmuxContext();
|
|
330
|
+
await fetch(`http://${SERVER_HOST}:${SERVER_PORT}/toggle`, { method: "POST", body: ctx }).catch(() => {});
|
|
331
|
+
|
|
332
|
+
// Wait for sidebar pane to appear
|
|
333
|
+
for (let i = 0; i < 20; i++) {
|
|
334
|
+
const paneId = findSidebarPane(windowId);
|
|
335
|
+
if (paneId) {
|
|
336
|
+
spawnSync("tmux", ["select-pane", "-t", paneId], { stdio: "pipe" });
|
|
337
|
+
resetTmuxKeys();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
341
|
+
}
|
|
342
|
+
resetTmuxKeys();
|
|
343
|
+
}
|
|
344
|
+
|
|
230
345
|
async function restart(): Promise<void> {
|
|
231
346
|
ensureDeps();
|
|
232
347
|
|
|
@@ -293,8 +408,10 @@ export default defineCommand({
|
|
|
293
408
|
subcommand: {
|
|
294
409
|
type: "positional",
|
|
295
410
|
required: false,
|
|
296
|
-
description: "Subcommand: setup, uninstall, server, tui, start, restart, keys",
|
|
411
|
+
description: "Subcommand: setup, uninstall, server, tui, start, restart, run, keys",
|
|
297
412
|
},
|
|
413
|
+
toggle: { type: "boolean", description: "Toggle sidebar (used with 'run')" },
|
|
414
|
+
focus: { type: "boolean", description: "Focus sidebar (used with 'run')" },
|
|
298
415
|
},
|
|
299
416
|
async run({ args }) {
|
|
300
417
|
switch (args.subcommand) {
|
|
@@ -316,6 +433,14 @@ export default defineCommand({
|
|
|
316
433
|
case "restart":
|
|
317
434
|
await restart();
|
|
318
435
|
break;
|
|
436
|
+
case "init":
|
|
437
|
+
init();
|
|
438
|
+
break;
|
|
439
|
+
case "run":
|
|
440
|
+
if (args.toggle) await runToggle();
|
|
441
|
+
else if (args.focus) await runFocus();
|
|
442
|
+
else consola.error("Usage: tt agentboard run --toggle | --focus");
|
|
443
|
+
break;
|
|
319
444
|
case "keys":
|
|
320
445
|
showKeys();
|
|
321
446
|
break;
|