@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 CHANGED
@@ -1,5 +1,6 @@
1
- #!/usr/bin/env tsx
1
+ #!/usr/bin/env bun
2
2
 
3
- import { execute } from "@oclif/core";
3
+ import { runMain } from "citty";
4
+ import { main } from "../src/cli.js";
4
5
 
5
- await execute({ dir: import.meta.url });
6
+ runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towles/tool",
3
- "version": "0.0.96",
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": "pnpm tsx scripts/sync-versions.ts",
33
- "prepublishOnly": "pnpm run version:sync",
34
- "dev": "tsx bin/run.ts",
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
- "@oclif/core": "^4.3.2",
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": "pnpm format && pnpm lint:fix && pnpm typecheck && claude plugin validate ."
73
+ "pre-commit": "bun run format && bun run lint:fix && bun run typecheck && claude plugin validate ."
82
74
  },
83
- "oclif": {
84
- "bin": "tt",
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 { Args } from "@oclif/core";
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 { BaseCommand } from "./base.js";
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
- export default class Agentboard2 extends BaseCommand {
34
- static override aliases = ["ag2"];
35
- static override description = "AgentBoard2 — opensessions-style tmux TUI sidebar";
36
-
37
- static override examples = [
38
- {
39
- description: "Install agentboard into tmux",
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
- switch (args.subcommand) {
68
- case "setup":
69
- this.setup();
70
- break;
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
- private ensureDeps(): void {
94
- // Check bun is installed
95
- try {
96
- execSync("bun --version", { stdio: "pipe" });
97
- } catch {
98
- this.error("bun is required but not found. Install: https://bun.sh");
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
- // Install deps if needed for runtime package
102
- const runtimeNodeModules = resolve(PLUGIN_DIR, "packages/runtime/node_modules");
103
- if (!existsSync(runtimeNodeModules)) {
104
- consola.info("Installing agentboard dependencies...");
105
- execSync("pnpm install", { cwd: PLUGIN_DIR, stdio: "inherit" });
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
- private setup(): void {
110
- this.ensureDeps();
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
- // Find tmux.conf
113
- const confPath = findTmuxConf();
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
- // If it's a symlink, resolve to the real file for editing
121
- let editPath = confPath;
122
- try {
123
- editPath = realpathSync(confPath);
124
- } catch {
125
- // keep confPath
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
- // Check if already installed
129
- const content = readFileSync(editPath, "utf8");
130
- if (content.includes("agentboard.tmux")) {
131
- consola.success("Already installed in tmux.conf");
132
- this.reloadTmux();
133
- return;
134
- }
118
+ let editPath = confPath;
119
+ try {
120
+ editPath = realpathSync(confPath);
121
+ } catch {
122
+ // keep confPath
123
+ }
135
124
 
136
- // Add run-shell line before TPM init
137
- const tpmLine = "run '~/.config/tmux/plugins/tpm/tpm'";
138
- const altTpmLine = "run-shell '~/.tmux/plugins/tpm/tpm'";
139
- const insertLines = `\n${MARKER}\n${RUN_SHELL_LINE}\n`;
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
- let newContent: string;
142
- if (content.includes(tpmLine)) {
143
- newContent = content.replace(tpmLine, `${insertLines}\n${tpmLine}`);
144
- } else if (content.includes(altTpmLine)) {
145
- newContent = content.replace(altTpmLine, `${insertLines}\n${altTpmLine}`);
146
- } else {
147
- // No TPM found, append to end
148
- newContent = content + insertLines;
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
- writeFileSync(editPath, newContent);
152
- consola.success(`Added agentboard to ${editPath}`);
145
+ writeFileSync(editPath, newContent);
146
+ consola.success(`Added agentboard to ${editPath}`);
153
147
 
154
- this.reloadTmux();
155
- this.showKeys();
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
- private uninstall(): void {
159
- const confPath = findTmuxConf();
160
- if (!confPath) {
161
- consola.info("No tmux.conf found.");
162
- return;
163
- }
159
+ let editPath = confPath;
160
+ try {
161
+ editPath = realpathSync(confPath);
162
+ } catch {
163
+ // keep confPath
164
+ }
164
165
 
165
- let editPath = confPath;
166
- try {
167
- editPath = realpathSync(confPath);
168
- } catch {
169
- // keep confPath
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
- const content = readFileSync(editPath, "utf8");
173
- if (!content.includes("agentboard")) {
174
- consola.info("agentboard not found in tmux.conf");
175
- return;
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
- // Remove the marker line and run-shell line
179
- const newContent = content
180
- .split("\n")
181
- .filter((line) => !line.includes("agentboard"))
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
- writeFileSync(editPath, newContent);
186
- consola.success("Removed agentboard from tmux.conf");
187
- this.reloadTmux();
188
- }
183
+ function startServer(): void {
184
+ ensureDeps();
189
185
 
190
- // Foreground command blocks until server exits (Ctrl+C to stop)
191
- private startServer(): void {
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
- const serverEntry = resolve(PLUGIN_DIR, "apps/server/src/main.ts");
195
- consola.info("Starting agentboard server (foreground, Ctrl+C to stop)...");
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
- execSync(`bun run ${serverEntry}`, {
198
- stdio: "inherit",
199
- cwd: PLUGIN_DIR,
200
- env: {
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
- // Foreground command blocks until TUI exits
208
- private startTui(): void {
209
- this.ensureDeps();
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
- const tuiEntry = resolve(PLUGIN_DIR, "apps/tui/src/index.tsx");
230
+ async function restart(): Promise<void> {
231
+ ensureDeps();
212
232
 
213
- execSync(`bun run ${tuiEntry}`, {
214
- stdio: "inherit",
215
- cwd: resolve(PLUGIN_DIR, "apps/tui"),
216
- env: {
217
- ...process.env,
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
- private showKeys(): void {
224
- // Get tmux prefix and agentboard key from tmux
225
- let prefix = "C-a";
226
- let key = DEFAULT_KEY;
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
- const { toggle, focus } = TMUX_BINDINGS;
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
- private reloadTmux(): void {
268
- try {
269
- execSync(
270
- "tmux source-file ~/.config/tmux/tmux.conf 2>/dev/null || tmux source-file ~/.tmux.conf 2>/dev/null",
271
- {
272
- stdio: "pipe",
273
- },
274
- );
275
- consola.success("tmux config reloaded");
276
- } catch {
277
- consola.info("Reload tmux manually: tmux source-file ~/.config/tmux/tmux.conf");
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
+ });