@towles/tool 0.0.96 → 0.0.103
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/bin/run.ts +4 -3
- package/package.json +8 -37
- package/src/cli.ts +19 -0
- package/src/commands/agentboard.ts +264 -217
- package/src/commands/auto-claude/index.ts +74 -91
- package/src/commands/auto-claude/list.ts +33 -43
- package/src/commands/auto-claude/retry.test.ts +10 -6
- package/src/commands/auto-claude/retry.ts +26 -39
- package/src/commands/auto-claude/status.ts +10 -17
- package/src/commands/config.test.ts +4 -10
- package/src/commands/config.ts +14 -28
- package/src/commands/doctor.ts +156 -178
- package/src/commands/gh/branch-clean.ts +28 -43
- package/src/commands/gh/branch.ts +22 -37
- package/src/commands/gh/index.ts +10 -0
- package/src/commands/gh/pr.ts +82 -100
- package/src/commands/graph/index.ts +59 -70
- package/src/commands/install.ts +91 -115
- package/src/commands/journal/daily-notes.ts +16 -24
- package/src/commands/journal/index.ts +10 -0
- package/src/commands/journal/meeting.ts +16 -34
- package/src/commands/journal/note.ts +16 -34
- package/src/commands/shared.ts +21 -0
- package/src/lib/auto-claude/templates.test.ts +16 -11
- package/src/lib/graph/parser.test.ts +11 -10
- package/src/utils/git/gh-cli-wrapper.test.ts +6 -5
- package/src/commands/base.ts +0 -32
package/bin/run.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towles/tool",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.103",
|
|
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": {
|
|
@@ -29,19 +29,14 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"version:sync": "
|
|
33
|
-
"prepublishOnly": "
|
|
34
|
-
"dev": "
|
|
32
|
+
"version:sync": "bun run scripts/sync-versions.ts",
|
|
33
|
+
"prepublishOnly": "bun run version:sync",
|
|
34
|
+
"dev": "bun run bin/run.ts",
|
|
35
35
|
"format": "oxfmt --write .",
|
|
36
36
|
"format:check": "oxfmt --check .",
|
|
37
37
|
"lint": "oxlint",
|
|
38
38
|
"lint:fix": "oxlint --fix",
|
|
39
39
|
"test": "vitest run",
|
|
40
|
-
"test:prompts": "promptfoo eval && promptfoo eval -c plugins/tt-core/promptfooconfig.yaml && promptfoo eval -c plugins/tt-auto-claude/promptfooconfig.yaml",
|
|
41
|
-
"test:prompts:root": "promptfoo eval",
|
|
42
|
-
"test:prompts:tt-core": "promptfoo eval -c plugins/tt-core/promptfooconfig.yaml",
|
|
43
|
-
"test:prompts:tt-core:llm": "promptfoo eval -c plugins/tt-core/promptfooconfig.llm.yaml",
|
|
44
|
-
"test:prompts:tt-auto-claude": "promptfoo eval -c plugins/tt-auto-claude/promptfooconfig.yaml",
|
|
45
40
|
"test:watch": "CI=DisableCallingClaude vitest watch",
|
|
46
41
|
"typecheck": "tsgo --noEmit --incremental",
|
|
47
42
|
"prepare": "simple-git-hooks"
|
|
@@ -49,7 +44,7 @@
|
|
|
49
44
|
"dependencies": {
|
|
50
45
|
"@anthropic-ai/claude-code": "^2.1.4",
|
|
51
46
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
52
|
-
"
|
|
47
|
+
"citty": "^0.1.6",
|
|
53
48
|
"consola": "^3.4.2",
|
|
54
49
|
"d3-hierarchy": "^3.1.2",
|
|
55
50
|
"fzf": "^0.5.2",
|
|
@@ -62,7 +57,6 @@
|
|
|
62
57
|
"zod": "^4.0.5"
|
|
63
58
|
},
|
|
64
59
|
"devDependencies": {
|
|
65
|
-
"@oclif/test": "^4.1.10",
|
|
66
60
|
"@types/d3-hierarchy": "^3.1.7",
|
|
67
61
|
"@types/luxon": "^3.6.2",
|
|
68
62
|
"@types/node": "^22.16.3",
|
|
@@ -71,38 +65,15 @@
|
|
|
71
65
|
"bumpp": "^10.4.0",
|
|
72
66
|
"oxfmt": "^0.24.0",
|
|
73
67
|
"oxlint": "^1.7.0",
|
|
74
|
-
"promptfoo": "^0.121.2",
|
|
75
68
|
"simple-git-hooks": "^2.13.0",
|
|
76
|
-
"tsx": "^4.19.4",
|
|
77
69
|
"typescript": "^5.8.3",
|
|
78
70
|
"vitest": "^4.0.17"
|
|
79
71
|
},
|
|
80
72
|
"simple-git-hooks": {
|
|
81
|
-
"pre-commit": "
|
|
73
|
+
"pre-commit": "bun run format && bun run lint:fix && bun run typecheck && claude plugin validate ."
|
|
82
74
|
},
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"commands": "./src/commands",
|
|
86
|
-
"dirname": "towles-tool",
|
|
87
|
-
"topicSeparator": " "
|
|
88
|
-
},
|
|
89
|
-
"packageManager": "pnpm@10.27.0",
|
|
90
|
-
"pnpm": {
|
|
91
|
-
"patchedDependencies": {
|
|
92
|
-
"prompts@2.4.2": "patches/prompts.patch"
|
|
93
|
-
},
|
|
94
|
-
"onlyBuiltDependencies": [
|
|
95
|
-
"@anthropic-ai/claude-code",
|
|
96
|
-
"@parcel/watcher",
|
|
97
|
-
"@playwright/browser-chromium",
|
|
98
|
-
"@swc/core",
|
|
99
|
-
"better-sqlite3",
|
|
100
|
-
"esbuild",
|
|
101
|
-
"onnxruntime-node",
|
|
102
|
-
"protobufjs",
|
|
103
|
-
"sharp",
|
|
104
|
-
"simple-git-hooks"
|
|
105
|
-
]
|
|
75
|
+
"patchedDependencies": {
|
|
76
|
+
"prompts@2.4.2": "patches/prompts.patch"
|
|
106
77
|
},
|
|
107
78
|
"trustedDependencies": [
|
|
108
79
|
"@anthropic-ai/claude-code"
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
|
|
3
|
+
export const main = defineCommand({
|
|
4
|
+
meta: { name: "tt", description: "towles-tool — personal CLI utilities" },
|
|
5
|
+
subCommands: {
|
|
6
|
+
config: () => import("./commands/config.js").then((m) => m.default),
|
|
7
|
+
doctor: () => import("./commands/doctor.js").then((m) => m.default),
|
|
8
|
+
install: () => import("./commands/install.js").then((m) => m.default),
|
|
9
|
+
agentboard: () => import("./commands/agentboard.js").then((m) => m.default),
|
|
10
|
+
ag: () => import("./commands/agentboard.js").then((m) => m.default),
|
|
11
|
+
gh: () => import("./commands/gh/index.js").then((m) => m.default),
|
|
12
|
+
pr: () => import("./commands/gh/pr.js").then((m) => m.default),
|
|
13
|
+
journal: () => import("./commands/journal/index.js").then((m) => m.default),
|
|
14
|
+
today: () => import("./commands/journal/daily-notes.js").then((m) => m.default),
|
|
15
|
+
"auto-claude": () => import("./commands/auto-claude/index.js").then((m) => m.default),
|
|
16
|
+
ac: () => import("./commands/auto-claude/index.js").then((m) => m.default),
|
|
17
|
+
graph: () => import("./commands/graph/index.js").then((m) => m.default),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync, realpathSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import { colors } from "consola/utils";
|
|
7
|
-
import {
|
|
7
|
+
import { debugArg } from "./shared.js";
|
|
8
|
+
|
|
9
|
+
const SERVER_HOST = "127.0.0.1";
|
|
10
|
+
const SERVER_PORT = 4201;
|
|
8
11
|
|
|
9
12
|
const PLUGIN_DIR = resolve(import.meta.dirname, "../../plugins/tt-agentboard");
|
|
10
13
|
|
|
@@ -30,251 +33,295 @@ function findTmuxConf(): string | null {
|
|
|
30
33
|
return null;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
command: "<%= config.bin %> agentboard setup",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
description: "Uninstall from tmux",
|
|
44
|
-
command: "<%= config.bin %> agentboard uninstall",
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
description: "Launch the server",
|
|
48
|
-
command: "<%= config.bin %> agentboard server",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
description: "Launch the TUI directly",
|
|
52
|
-
command: "<%= config.bin %> agentboard tui",
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
static override args = {
|
|
57
|
-
subcommand: Args.string({
|
|
58
|
-
description: "Subcommand: setup, uninstall, server, tui, keys",
|
|
59
|
-
required: false,
|
|
60
|
-
options: ["setup", "uninstall", "server", "tui", "start", "keys"],
|
|
61
|
-
}),
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
async run(): Promise<void> {
|
|
65
|
-
const { args } = await this.parse(Agentboard2);
|
|
36
|
+
function ensureDeps(): void {
|
|
37
|
+
try {
|
|
38
|
+
execSync("bun --version", { stdio: "pipe" });
|
|
39
|
+
} catch {
|
|
40
|
+
consola.error("bun is required but not found. Install: https://bun.sh");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
66
43
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
case "uninstall":
|
|
72
|
-
this.uninstall();
|
|
73
|
-
break;
|
|
74
|
-
case "server":
|
|
75
|
-
this.startServer();
|
|
76
|
-
break;
|
|
77
|
-
case "tui":
|
|
78
|
-
this.startTui();
|
|
79
|
-
break;
|
|
80
|
-
case "start":
|
|
81
|
-
// For backwards compat, start = tui
|
|
82
|
-
this.startTui();
|
|
83
|
-
break;
|
|
84
|
-
case "keys":
|
|
85
|
-
this.showKeys();
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
this.showKeys();
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
44
|
+
const runtimeNodeModules = resolve(PLUGIN_DIR, "packages/runtime/node_modules");
|
|
45
|
+
if (!existsSync(runtimeNodeModules)) {
|
|
46
|
+
consola.info("Installing agentboard dependencies...");
|
|
47
|
+
execSync("bun install", { cwd: PLUGIN_DIR, stdio: "inherit" });
|
|
91
48
|
}
|
|
49
|
+
}
|
|
92
50
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
51
|
+
function reloadTmux(): void {
|
|
52
|
+
try {
|
|
53
|
+
execSync(
|
|
54
|
+
"tmux source-file ~/.config/tmux/tmux.conf 2>/dev/null || tmux source-file ~/.tmux.conf 2>/dev/null",
|
|
55
|
+
{
|
|
56
|
+
stdio: "pipe",
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
consola.success("tmux config reloaded");
|
|
60
|
+
} catch {
|
|
61
|
+
consola.info("Reload tmux manually: tmux source-file ~/.config/tmux/tmux.conf");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
100
64
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
65
|
+
function showKeys(): void {
|
|
66
|
+
let prefix = "C-a";
|
|
67
|
+
let key = DEFAULT_KEY;
|
|
68
|
+
try {
|
|
69
|
+
prefix = execSync("tmux show-option -gv prefix", {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
+
}).trim();
|
|
73
|
+
const abKey = execSync(
|
|
74
|
+
`tmux show-option -gv @agentboard-key 2>/dev/null || echo ${DEFAULT_KEY}`,
|
|
75
|
+
{
|
|
76
|
+
encoding: "utf8",
|
|
77
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
78
|
+
},
|
|
79
|
+
).trim();
|
|
80
|
+
if (abKey) key = abKey;
|
|
81
|
+
} catch {
|
|
82
|
+
// use defaults
|
|
107
83
|
}
|
|
108
84
|
|
|
109
|
-
|
|
110
|
-
|
|
85
|
+
const { toggle, focus } = TMUX_BINDINGS;
|
|
86
|
+
consola.box(
|
|
87
|
+
[
|
|
88
|
+
`${colors.bold("AgentBoard Keybindings")}\n`,
|
|
89
|
+
`${colors.cyan(`tmux (prefix = ${prefix}, C = Ctrl):`)}`,
|
|
90
|
+
` ${prefix} ${key} ${toggle} toggle sidebar`,
|
|
91
|
+
` ${prefix} ${key} ${focus} focus sidebar`,
|
|
92
|
+
` ${prefix} ${key} 1-9 jump to session\n`,
|
|
93
|
+
`${colors.cyan("In sidebar:")}`,
|
|
94
|
+
` Tab cycle sessions`,
|
|
95
|
+
` j / ↓ move down`,
|
|
96
|
+
` k / ↑ move up`,
|
|
97
|
+
` Enter / l switch to selected session`,
|
|
98
|
+
` 1-9 jump to session`,
|
|
99
|
+
` d hide session`,
|
|
100
|
+
` x kill session`,
|
|
101
|
+
` r refresh`,
|
|
102
|
+
` ? help`,
|
|
103
|
+
` q quit`,
|
|
104
|
+
].join("\n"),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
111
107
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!confPath) {
|
|
115
|
-
consola.warn("No tmux.conf found. Add this line manually:");
|
|
116
|
-
consola.info(colors.cyan(` ${RUN_SHELL_LINE}`));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
108
|
+
function setup(): void {
|
|
109
|
+
ensureDeps();
|
|
119
110
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
111
|
+
const confPath = findTmuxConf();
|
|
112
|
+
if (!confPath) {
|
|
113
|
+
consola.warn("No tmux.conf found. Add this line manually:");
|
|
114
|
+
consola.info(colors.cyan(` ${RUN_SHELL_LINE}`));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
118
|
+
let editPath = confPath;
|
|
119
|
+
try {
|
|
120
|
+
editPath = realpathSync(confPath);
|
|
121
|
+
} catch {
|
|
122
|
+
// keep confPath
|
|
123
|
+
}
|
|
135
124
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
125
|
+
const content = readFileSync(editPath, "utf8");
|
|
126
|
+
if (content.includes("agentboard.tmux")) {
|
|
127
|
+
consola.success("Already installed in tmux.conf");
|
|
128
|
+
reloadTmux();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
132
|
+
const tpmLine = "run '~/.config/tmux/plugins/tpm/tpm'";
|
|
133
|
+
const altTpmLine = "run-shell '~/.tmux/plugins/tpm/tpm'";
|
|
134
|
+
const insertLines = `\n${MARKER}\n${RUN_SHELL_LINE}\n`;
|
|
135
|
+
|
|
136
|
+
let newContent: string;
|
|
137
|
+
if (content.includes(tpmLine)) {
|
|
138
|
+
newContent = content.replace(tpmLine, `${insertLines}\n${tpmLine}`);
|
|
139
|
+
} else if (content.includes(altTpmLine)) {
|
|
140
|
+
newContent = content.replace(altTpmLine, `${insertLines}\n${altTpmLine}`);
|
|
141
|
+
} else {
|
|
142
|
+
newContent = content + insertLines;
|
|
143
|
+
}
|
|
150
144
|
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
writeFileSync(editPath, newContent);
|
|
146
|
+
consola.success(`Added agentboard to ${editPath}`);
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
reloadTmux();
|
|
149
|
+
showKeys();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function uninstall(): void {
|
|
153
|
+
const confPath = findTmuxConf();
|
|
154
|
+
if (!confPath) {
|
|
155
|
+
consola.info("No tmux.conf found.");
|
|
156
|
+
return;
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
let editPath = confPath;
|
|
160
|
+
try {
|
|
161
|
+
editPath = realpathSync(confPath);
|
|
162
|
+
} catch {
|
|
163
|
+
// keep confPath
|
|
164
|
+
}
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
166
|
+
const content = readFileSync(editPath, "utf8");
|
|
167
|
+
if (!content.includes("agentboard")) {
|
|
168
|
+
consola.info("agentboard not found in tmux.conf");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
172
|
+
const newContent = content
|
|
173
|
+
.split("\n")
|
|
174
|
+
.filter((line) => !line.includes("agentboard"))
|
|
175
|
+
.join("\n")
|
|
176
|
+
.replace(/\n{3,}/g, "\n\n");
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.join("\n")
|
|
183
|
-
.replace(/\n{3,}/g, "\n\n");
|
|
178
|
+
writeFileSync(editPath, newContent);
|
|
179
|
+
consola.success("Removed agentboard from tmux.conf");
|
|
180
|
+
reloadTmux();
|
|
181
|
+
}
|
|
184
182
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this.reloadTmux();
|
|
188
|
-
}
|
|
183
|
+
function startServer(): void {
|
|
184
|
+
ensureDeps();
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.ensureDeps();
|
|
186
|
+
const serverEntry = resolve(PLUGIN_DIR, "apps/server/src/main.ts");
|
|
187
|
+
consola.info("Starting agentboard server (foreground, Ctrl+C to stop)...");
|
|
193
188
|
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
execSync(`bun run ${serverEntry}`, {
|
|
190
|
+
stdio: "inherit",
|
|
191
|
+
cwd: PLUGIN_DIR,
|
|
192
|
+
env: {
|
|
193
|
+
...process.env,
|
|
194
|
+
AGENTBOARD_DIR: PLUGIN_DIR,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
196
198
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
...process.env,
|
|
202
|
-
AGENTBOARD2_DIR: PLUGIN_DIR,
|
|
203
|
-
},
|
|
199
|
+
async function serverAlive(): Promise<boolean> {
|
|
200
|
+
try {
|
|
201
|
+
const res = await fetch(`http://${SERVER_HOST}:${SERVER_PORT}/`, {
|
|
202
|
+
signal: AbortSignal.timeout(500),
|
|
204
203
|
});
|
|
204
|
+
return res.ok;
|
|
205
|
+
} catch {
|
|
206
|
+
return false;
|
|
205
207
|
}
|
|
208
|
+
}
|
|
206
209
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
async function ensureServerUp(): Promise<boolean> {
|
|
211
|
+
if (await serverAlive()) return true;
|
|
212
|
+
|
|
213
|
+
const serverEntry = resolve(PLUGIN_DIR, "apps/server/src/main.ts");
|
|
214
|
+
consola.info("Starting agentboard server...");
|
|
215
|
+
const child = spawn("bun", ["run", serverEntry], {
|
|
216
|
+
stdio: "ignore",
|
|
217
|
+
cwd: PLUGIN_DIR,
|
|
218
|
+
detached: true,
|
|
219
|
+
env: { ...process.env, AGENTBOARD_DIR: PLUGIN_DIR },
|
|
220
|
+
});
|
|
221
|
+
child.unref();
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < 30; i++) {
|
|
224
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
225
|
+
if (await serverAlive()) return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
210
229
|
|
|
211
|
-
|
|
230
|
+
async function restart(): Promise<void> {
|
|
231
|
+
ensureDeps();
|
|
212
232
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
AGENTBOARD2_DIR: PLUGIN_DIR,
|
|
219
|
-
},
|
|
233
|
+
// 1. Kill stash sessions left over from hidden sidebars
|
|
234
|
+
try {
|
|
235
|
+
const result = spawnSync("tmux", ["list-sessions", "-F", "#{session_name}"], {
|
|
236
|
+
encoding: "utf8",
|
|
237
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
220
238
|
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
try {
|
|
228
|
-
prefix = execSync("tmux show-option -gv prefix", {
|
|
229
|
-
encoding: "utf8",
|
|
230
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
231
|
-
}).trim();
|
|
232
|
-
const ab2Key = execSync(
|
|
233
|
-
`tmux show-option -gv @agentboard-key 2>/dev/null || echo ${DEFAULT_KEY}`,
|
|
234
|
-
{
|
|
235
|
-
encoding: "utf8",
|
|
236
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
237
|
-
},
|
|
238
|
-
).trim();
|
|
239
|
-
if (ab2Key) key = ab2Key;
|
|
240
|
-
} catch {
|
|
241
|
-
// use defaults
|
|
239
|
+
const sessions = (result.stdout ?? "").trim().split("\n").filter(Boolean);
|
|
240
|
+
for (const name of sessions) {
|
|
241
|
+
if (name.startsWith("_ab_stash")) {
|
|
242
|
+
spawnSync("tmux", ["kill-session", "-t", name], { stdio: "pipe" });
|
|
243
|
+
consola.info(`Killed stash session: ${name}`);
|
|
244
|
+
}
|
|
242
245
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
consola.box(
|
|
246
|
-
[
|
|
247
|
-
`${colors.bold("AgentBoard2 Keybindings")}\n`,
|
|
248
|
-
`${colors.cyan(`tmux (prefix = ${prefix}, C = Ctrl):`)}`,
|
|
249
|
-
` ${prefix} ${key} ${toggle} toggle sidebar`,
|
|
250
|
-
` ${prefix} ${key} ${focus} focus sidebar`,
|
|
251
|
-
` ${prefix} ${key} 1-9 jump to session\n`,
|
|
252
|
-
`${colors.cyan("In sidebar:")}`,
|
|
253
|
-
` Tab cycle sessions`,
|
|
254
|
-
` j / ↓ move down`,
|
|
255
|
-
` k / ↑ move up`,
|
|
256
|
-
` Enter / l switch to selected session`,
|
|
257
|
-
` 1-9 jump to session`,
|
|
258
|
-
` d hide session`,
|
|
259
|
-
` x kill session`,
|
|
260
|
-
` t theme picker`,
|
|
261
|
-
` r refresh`,
|
|
262
|
-
` q quit`,
|
|
263
|
-
].join("\n"),
|
|
264
|
-
);
|
|
246
|
+
} catch {
|
|
247
|
+
// no tmux or no sessions
|
|
265
248
|
}
|
|
266
249
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
250
|
+
// 2. Ensure server is running
|
|
251
|
+
if (!(await ensureServerUp())) {
|
|
252
|
+
consola.error("Failed to start agentboard server");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
consola.success("Server is running");
|
|
256
|
+
|
|
257
|
+
// 3. Toggle sidebar on (the server spawns sidebars in all active windows)
|
|
258
|
+
try {
|
|
259
|
+
const res = await fetch(`http://${SERVER_HOST}:${SERVER_PORT}/toggle`, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
body: "",
|
|
262
|
+
signal: AbortSignal.timeout(2000),
|
|
263
|
+
});
|
|
264
|
+
if (res.ok) {
|
|
265
|
+
consola.success("Sidebar toggled on for all sessions");
|
|
266
|
+
} else {
|
|
267
|
+
consola.warn(`Toggle returned: ${res.status}`);
|
|
278
268
|
}
|
|
269
|
+
} catch (err) {
|
|
270
|
+
consola.error("Failed to toggle sidebar:", err);
|
|
279
271
|
}
|
|
280
272
|
}
|
|
273
|
+
|
|
274
|
+
function startTui(): void {
|
|
275
|
+
ensureDeps();
|
|
276
|
+
|
|
277
|
+
const tuiEntry = resolve(PLUGIN_DIR, "apps/tui/src/index.tsx");
|
|
278
|
+
|
|
279
|
+
execSync(`bun run ${tuiEntry}`, {
|
|
280
|
+
stdio: "inherit",
|
|
281
|
+
cwd: resolve(PLUGIN_DIR, "apps/tui"),
|
|
282
|
+
env: {
|
|
283
|
+
...process.env,
|
|
284
|
+
AGENTBOARD_DIR: PLUGIN_DIR,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export default defineCommand({
|
|
290
|
+
meta: { name: "agentboard", description: "AgentBoard — tmux TUI sidebar" },
|
|
291
|
+
args: {
|
|
292
|
+
debug: debugArg,
|
|
293
|
+
subcommand: {
|
|
294
|
+
type: "positional",
|
|
295
|
+
required: false,
|
|
296
|
+
description: "Subcommand: setup, uninstall, server, tui, start, restart, keys",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
async run({ args }) {
|
|
300
|
+
switch (args.subcommand) {
|
|
301
|
+
case "setup":
|
|
302
|
+
setup();
|
|
303
|
+
break;
|
|
304
|
+
case "uninstall":
|
|
305
|
+
uninstall();
|
|
306
|
+
break;
|
|
307
|
+
case "server":
|
|
308
|
+
startServer();
|
|
309
|
+
break;
|
|
310
|
+
case "tui":
|
|
311
|
+
startTui();
|
|
312
|
+
break;
|
|
313
|
+
case "start":
|
|
314
|
+
startTui();
|
|
315
|
+
break;
|
|
316
|
+
case "restart":
|
|
317
|
+
await restart();
|
|
318
|
+
break;
|
|
319
|
+
case "keys":
|
|
320
|
+
showKeys();
|
|
321
|
+
break;
|
|
322
|
+
default:
|
|
323
|
+
showKeys();
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
});
|