@towles/tool 0.0.70 → 0.0.72
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/package.json +1 -1
- package/src/commands/agentboard.ts +166 -9
package/package.json
CHANGED
|
@@ -4,6 +4,8 @@ import { existsSync } from "node:fs";
|
|
|
4
4
|
import { resolve, join } from "node:path";
|
|
5
5
|
import { networkInterfaces } from "node:os";
|
|
6
6
|
import consola from "consola";
|
|
7
|
+
import { colors } from "consola/utils";
|
|
8
|
+
import prompts from "prompts";
|
|
7
9
|
import { BaseCommand } from "./base.js";
|
|
8
10
|
|
|
9
11
|
function getLocalIp(): string {
|
|
@@ -40,6 +42,14 @@ export default class Agentboard extends BaseCommand {
|
|
|
40
42
|
description: "Attach to a running card tmux session",
|
|
41
43
|
command: "<%= config.bin %> ag attach 42",
|
|
42
44
|
},
|
|
45
|
+
{
|
|
46
|
+
description: "Selectively clear database (interactive)",
|
|
47
|
+
command: "<%= config.bin %> ag reset",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
description: "Delete entire database without prompting",
|
|
51
|
+
command: "<%= config.bin %> ag reset --all",
|
|
52
|
+
},
|
|
43
53
|
];
|
|
44
54
|
|
|
45
55
|
static override flags = {
|
|
@@ -62,6 +72,10 @@ export default class Agentboard extends BaseCommand {
|
|
|
62
72
|
description: "Listen on all interfaces (0.0.0.0) for LAN access. Default: localhost only.",
|
|
63
73
|
default: false,
|
|
64
74
|
}),
|
|
75
|
+
all: Flags.boolean({
|
|
76
|
+
description: "Reset entire database without prompting (for tt ag reset --all)",
|
|
77
|
+
default: false,
|
|
78
|
+
}),
|
|
65
79
|
};
|
|
66
80
|
|
|
67
81
|
static override args = {
|
|
@@ -96,22 +110,18 @@ export default class Agentboard extends BaseCommand {
|
|
|
96
110
|
);
|
|
97
111
|
const dataDir = flags["data-dir"] ? resolve(flags["data-dir"]) : defaultDataDir;
|
|
98
112
|
const dbPath = join(dataDir, "agentboard.db");
|
|
99
|
-
const walPath = `${dbPath}-wal`;
|
|
100
|
-
const shmPath = `${dbPath}-shm`;
|
|
101
113
|
|
|
102
114
|
if (!existsSync(dbPath)) {
|
|
103
115
|
consola.info("No database found — nothing to reset.");
|
|
104
116
|
return;
|
|
105
117
|
}
|
|
106
118
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const { unlinkSync } = await import("node:fs");
|
|
111
|
-
unlinkSync(f);
|
|
112
|
-
}
|
|
119
|
+
if (flags.all) {
|
|
120
|
+
await this.resetEntireDatabase(dbPath);
|
|
121
|
+
return;
|
|
113
122
|
}
|
|
114
|
-
|
|
123
|
+
|
|
124
|
+
await this.selectiveClear(dbPath);
|
|
115
125
|
return;
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -173,4 +183,151 @@ export default class Agentboard extends BaseCommand {
|
|
|
173
183
|
|
|
174
184
|
proc.on("exit", (code) => process.exit(code ?? 0));
|
|
175
185
|
}
|
|
186
|
+
|
|
187
|
+
private async resetEntireDatabase(dbPath: string): Promise<void> {
|
|
188
|
+
const walPath = `${dbPath}-wal`;
|
|
189
|
+
const shmPath = `${dbPath}-shm`;
|
|
190
|
+
consola.warn(`This will delete: ${dbPath}`);
|
|
191
|
+
const { unlinkSync } = await import("node:fs");
|
|
192
|
+
for (const f of [dbPath, walPath, shmPath]) {
|
|
193
|
+
if (existsSync(f)) {
|
|
194
|
+
unlinkSync(f);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
consola.success("Database reset. Start AgentBoard to create a fresh DB.");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async selectiveClear(dbPath: string): Promise<void> {
|
|
201
|
+
const { createRequire } = await import("node:module");
|
|
202
|
+
const require = createRequire(import.meta.url);
|
|
203
|
+
const Database = require("better-sqlite3");
|
|
204
|
+
const sqlite = new Database(dbPath) as {
|
|
205
|
+
pragma(stmt: string): void;
|
|
206
|
+
prepare(sql: string): {
|
|
207
|
+
get(): unknown;
|
|
208
|
+
run(): { changes: number };
|
|
209
|
+
};
|
|
210
|
+
close(): void;
|
|
211
|
+
};
|
|
212
|
+
sqlite.pragma("foreign_keys = ON");
|
|
213
|
+
|
|
214
|
+
const counts = {
|
|
215
|
+
doneCards: sqlite.prepare("SELECT COUNT(*) as c FROM cards WHERE column = 'done'").get() as {
|
|
216
|
+
c: number;
|
|
217
|
+
},
|
|
218
|
+
failedCards: sqlite
|
|
219
|
+
.prepare("SELECT COUNT(*) as c FROM cards WHERE status = 'failed'")
|
|
220
|
+
.get() as { c: number },
|
|
221
|
+
allCards: sqlite.prepare("SELECT COUNT(*) as c FROM cards").get() as { c: number },
|
|
222
|
+
workflowRuns: sqlite.prepare("SELECT COUNT(*) as c FROM workflow_runs").get() as {
|
|
223
|
+
c: number;
|
|
224
|
+
},
|
|
225
|
+
cardEvents: sqlite.prepare("SELECT COUNT(*) as c FROM card_events").get() as { c: number },
|
|
226
|
+
agentLogs: sqlite.prepare("SELECT COUNT(*) as c FROM agent_logs").get() as { c: number },
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const choices = [
|
|
230
|
+
{
|
|
231
|
+
title: `Completed cards ${colors.dim(`(${counts.doneCards.c} cards in "done" column + events/runs)`)}`,
|
|
232
|
+
value: "done_cards",
|
|
233
|
+
disabled: counts.doneCards.c === 0,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
title: `Failed cards ${colors.dim(`(${counts.failedCards.c} cards with "failed" status + events/runs)`)}`,
|
|
237
|
+
value: "failed_cards",
|
|
238
|
+
disabled: counts.failedCards.c === 0,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
title: `Execution history ${colors.dim(`(${counts.workflowRuns.c} workflow runs, step runs, ${counts.agentLogs.c} agent logs)`)}`,
|
|
242
|
+
value: "execution_history",
|
|
243
|
+
disabled: counts.workflowRuns.c === 0,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
title: `Event logs ${colors.dim(`(${counts.cardEvents.c} card events)`)}`,
|
|
247
|
+
value: "event_logs",
|
|
248
|
+
disabled: counts.cardEvents.c === 0,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
title: `All cards ${colors.dim(`(${counts.allCards.c} cards — keeps repos, boards, slots)`)}`,
|
|
252
|
+
value: "all_cards",
|
|
253
|
+
disabled: counts.allCards.c === 0,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
title: colors.red(`Everything (delete entire database)`),
|
|
257
|
+
value: "everything",
|
|
258
|
+
},
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
const result = await prompts(
|
|
262
|
+
{
|
|
263
|
+
name: "selected",
|
|
264
|
+
message: "What would you like to clear?",
|
|
265
|
+
type: "multiselect",
|
|
266
|
+
choices,
|
|
267
|
+
instructions: false,
|
|
268
|
+
hint: "- Space to select, Enter to confirm",
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
onCancel: () => {
|
|
272
|
+
consola.info(colors.dim("Canceled"));
|
|
273
|
+
process.exit(0);
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const selected: string[] = result.selected;
|
|
279
|
+
if (!selected || selected.length === 0) {
|
|
280
|
+
consola.info("Nothing selected.");
|
|
281
|
+
sqlite.close();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (selected.includes("everything")) {
|
|
286
|
+
sqlite.close();
|
|
287
|
+
await this.resetEntireDatabase(dbPath);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let totalDeleted = 0;
|
|
292
|
+
|
|
293
|
+
if (selected.includes("done_cards")) {
|
|
294
|
+
// Cascade deletes handle events, dependencies, workflow_runs, step_runs, agent_logs
|
|
295
|
+
const deleted = sqlite.prepare("DELETE FROM cards WHERE column = 'done'").run();
|
|
296
|
+
consola.success(`Cleared ${deleted.changes} completed card(s)`);
|
|
297
|
+
totalDeleted += deleted.changes;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (selected.includes("failed_cards")) {
|
|
301
|
+
const deleted = sqlite.prepare("DELETE FROM cards WHERE status = 'failed'").run();
|
|
302
|
+
consola.success(`Cleared ${deleted.changes} failed card(s)`);
|
|
303
|
+
totalDeleted += deleted.changes;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (selected.includes("execution_history")) {
|
|
307
|
+
// agent_logs and step_runs cascade from workflow_runs
|
|
308
|
+
const deleted = sqlite.prepare("DELETE FROM workflow_runs").run();
|
|
309
|
+
consola.success(`Cleared ${deleted.changes} workflow run(s) and associated logs`);
|
|
310
|
+
totalDeleted += deleted.changes;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (selected.includes("event_logs")) {
|
|
314
|
+
const deleted = sqlite.prepare("DELETE FROM card_events").run();
|
|
315
|
+
consola.success(`Cleared ${deleted.changes} event log(s)`);
|
|
316
|
+
totalDeleted += deleted.changes;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (selected.includes("all_cards")) {
|
|
320
|
+
const deleted = sqlite.prepare("DELETE FROM cards").run();
|
|
321
|
+
consola.success(`Cleared ${deleted.changes} card(s)`);
|
|
322
|
+
totalDeleted += deleted.changes;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
sqlite.close();
|
|
326
|
+
|
|
327
|
+
if (totalDeleted === 0) {
|
|
328
|
+
consola.info("Nothing to clear.");
|
|
329
|
+
} else {
|
|
330
|
+
consola.success("Done.");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
176
333
|
}
|