@towles/tool 0.0.103 → 0.0.105
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 +153 -7
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.105",
|
|
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,14 +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
|
-
const candidates = [
|
|
22
|
-
resolve(process.env.HOME ?? "~", ".tmux.conf"),
|
|
23
|
-
resolve(process.env.HOME ?? "~", ".config/tmux/tmux.conf"),
|
|
24
|
-
];
|
|
21
|
+
const candidates = [resolve(process.env.HOME ?? "~", ".config/tmux/tmux.conf")];
|
|
25
22
|
for (const path of candidates) {
|
|
26
23
|
try {
|
|
27
24
|
const real = existsSync(path) ? path : null;
|
|
@@ -123,7 +120,7 @@ function setup(): void {
|
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
const content = readFileSync(editPath, "utf8");
|
|
126
|
-
if (content.includes(
|
|
123
|
+
if (content.includes(MARKER)) {
|
|
127
124
|
consola.success("Already installed in tmux.conf");
|
|
128
125
|
reloadTmux();
|
|
129
126
|
return;
|
|
@@ -227,6 +224,145 @@ async function ensureServerUp(): Promise<boolean> {
|
|
|
227
224
|
return false;
|
|
228
225
|
}
|
|
229
226
|
|
|
227
|
+
function tmuxDisplay(fmt: string): string {
|
|
228
|
+
try {
|
|
229
|
+
const r = spawnSync("tmux", ["display-message", "-p", fmt], {
|
|
230
|
+
encoding: "utf8",
|
|
231
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
232
|
+
});
|
|
233
|
+
return (r.stdout ?? "").trim();
|
|
234
|
+
} catch {
|
|
235
|
+
return "";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function tmuxContext(): string {
|
|
240
|
+
return tmuxDisplay("#{client_tty}|#{session_name}|#{window_id}");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function resetTmuxKeys(): void {
|
|
244
|
+
spawnSync("tmux", ["switch-client", "-T", "root"], { stdio: "pipe" });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function findSidebarPane(windowId: string): string | null {
|
|
248
|
+
try {
|
|
249
|
+
const r = spawnSync("tmux", ["list-panes", "-t", windowId, "-F", "#{pane_id} #{pane_title}"], {
|
|
250
|
+
encoding: "utf8",
|
|
251
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
252
|
+
});
|
|
253
|
+
for (const line of (r.stdout ?? "").trim().split("\n")) {
|
|
254
|
+
const [paneId, title] = line.split(" ", 2);
|
|
255
|
+
if (title === "agentboard-sidebar" && paneId) return paneId;
|
|
256
|
+
}
|
|
257
|
+
} catch {}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function tmux(...args: string[]): void {
|
|
262
|
+
spawnSync("tmux", args, { stdio: "pipe" });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function init(): void {
|
|
266
|
+
const port = process.env.TT_AGENTBOARD_PORT ?? "4201";
|
|
267
|
+
const host = process.env.TT_AGENTBOARD_HOST ?? "127.0.0.1";
|
|
268
|
+
|
|
269
|
+
// Read tmux options with defaults
|
|
270
|
+
const keyResult = spawnSync("tmux", ["show-option", "-gqv", "@agentboard-key"], {
|
|
271
|
+
encoding: "utf8",
|
|
272
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
273
|
+
});
|
|
274
|
+
const key = (keyResult.stdout ?? "").trim() || DEFAULT_KEY;
|
|
275
|
+
|
|
276
|
+
// Export to tmux environment
|
|
277
|
+
tmux("set-environment", "-g", "TT_AGENTBOARD_PORT", port);
|
|
278
|
+
tmux("set-environment", "-g", "TT_AGENTBOARD_HOST", host);
|
|
279
|
+
|
|
280
|
+
// Bind keybindings via command table "agentboard"
|
|
281
|
+
tmux("bind-key", "-T", "prefix", key, "switch-client", "-T", "agentboard");
|
|
282
|
+
tmux(
|
|
283
|
+
"bind-key",
|
|
284
|
+
"-T",
|
|
285
|
+
"agentboard",
|
|
286
|
+
TMUX_BINDINGS.toggle,
|
|
287
|
+
"run-shell",
|
|
288
|
+
"tt agentboard run --toggle",
|
|
289
|
+
);
|
|
290
|
+
tmux(
|
|
291
|
+
"bind-key",
|
|
292
|
+
"-T",
|
|
293
|
+
"agentboard",
|
|
294
|
+
TMUX_BINDINGS.focus,
|
|
295
|
+
"run-shell",
|
|
296
|
+
"tt agentboard run --focus",
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Number keys 1-9 switch to session by index
|
|
300
|
+
for (let i = 1; i <= 9; i++) {
|
|
301
|
+
tmux(
|
|
302
|
+
"bind-key",
|
|
303
|
+
"-T",
|
|
304
|
+
"agentboard",
|
|
305
|
+
String(i),
|
|
306
|
+
"run-shell",
|
|
307
|
+
`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`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Hooks (fallback for when server isn't running yet)
|
|
312
|
+
const hookPost = (path: string, body?: string) => {
|
|
313
|
+
const bodyArg = body ? ` -d \\"${body}\\"` : "";
|
|
314
|
+
return `run-shell -b "curl -s -X POST http://${host}:${port}${path}${bodyArg} >/dev/null 2>&1 || true"`;
|
|
315
|
+
};
|
|
316
|
+
const focusBody = "#{q:client_tty}|#{q:session_name}|#{q:window_id}";
|
|
317
|
+
const resizeBody =
|
|
318
|
+
"#{q:pane_id}|#{q:session_name}|#{q:window_id}|#{q:pane_width}|#{q:window_width}";
|
|
319
|
+
|
|
320
|
+
tmux("set-hook", "-g", "client-session-changed", hookPost("/focus", focusBody));
|
|
321
|
+
tmux("set-hook", "-g", "after-select-window", hookPost("/ensure-sidebar", focusBody));
|
|
322
|
+
tmux("set-hook", "-g", "after-resize-pane", hookPost("/resize-sidebars", resizeBody));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function runToggle(): Promise<void> {
|
|
326
|
+
if (!(await ensureServerUp())) process.exit(0);
|
|
327
|
+
const ctx = tmuxContext();
|
|
328
|
+
await fetch(`http://${SERVER_HOST}:${SERVER_PORT}/toggle`, { method: "POST", body: ctx }).catch(
|
|
329
|
+
() => {},
|
|
330
|
+
);
|
|
331
|
+
resetTmuxKeys();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function runFocus(): Promise<void> {
|
|
335
|
+
const windowId = tmuxDisplay("#{window_id}");
|
|
336
|
+
if (!windowId) process.exit(0);
|
|
337
|
+
|
|
338
|
+
// If sidebar already exists, just focus it
|
|
339
|
+
const existing = findSidebarPane(windowId);
|
|
340
|
+
if (existing) {
|
|
341
|
+
spawnSync("tmux", ["select-pane", "-t", existing], { stdio: "pipe" });
|
|
342
|
+
resetTmuxKeys();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Otherwise, ensure server + toggle sidebar on
|
|
347
|
+
if (!(await ensureServerUp())) process.exit(0);
|
|
348
|
+
const ctx = tmuxContext();
|
|
349
|
+
await fetch(`http://${SERVER_HOST}:${SERVER_PORT}/toggle`, { method: "POST", body: ctx }).catch(
|
|
350
|
+
() => {},
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Wait for sidebar pane to appear
|
|
354
|
+
for (let i = 0; i < 20; i++) {
|
|
355
|
+
const paneId = findSidebarPane(windowId);
|
|
356
|
+
if (paneId) {
|
|
357
|
+
spawnSync("tmux", ["select-pane", "-t", paneId], { stdio: "pipe" });
|
|
358
|
+
resetTmuxKeys();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
362
|
+
}
|
|
363
|
+
resetTmuxKeys();
|
|
364
|
+
}
|
|
365
|
+
|
|
230
366
|
async function restart(): Promise<void> {
|
|
231
367
|
ensureDeps();
|
|
232
368
|
|
|
@@ -293,8 +429,10 @@ export default defineCommand({
|
|
|
293
429
|
subcommand: {
|
|
294
430
|
type: "positional",
|
|
295
431
|
required: false,
|
|
296
|
-
description: "Subcommand: setup, uninstall, server, tui, start, restart, keys",
|
|
432
|
+
description: "Subcommand: setup, uninstall, server, tui, start, restart, run, keys",
|
|
297
433
|
},
|
|
434
|
+
toggle: { type: "boolean", description: "Toggle sidebar (used with 'run')" },
|
|
435
|
+
focus: { type: "boolean", description: "Focus sidebar (used with 'run')" },
|
|
298
436
|
},
|
|
299
437
|
async run({ args }) {
|
|
300
438
|
switch (args.subcommand) {
|
|
@@ -316,6 +454,14 @@ export default defineCommand({
|
|
|
316
454
|
case "restart":
|
|
317
455
|
await restart();
|
|
318
456
|
break;
|
|
457
|
+
case "init":
|
|
458
|
+
init();
|
|
459
|
+
break;
|
|
460
|
+
case "run":
|
|
461
|
+
if (args.toggle) await runToggle();
|
|
462
|
+
else if (args.focus) await runFocus();
|
|
463
|
+
else consola.error("Usage: tt agentboard run --toggle | --focus");
|
|
464
|
+
break;
|
|
319
465
|
case "keys":
|
|
320
466
|
showKeys();
|
|
321
467
|
break;
|