@hasna/hooks 0.2.6 → 0.2.8

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.
Files changed (49) hide show
  1. package/bin/index.js +645 -43
  2. package/dist/index.js +89 -5
  3. package/hooks/hook-affected-tests/LICENSE +191 -0
  4. package/hooks/hook-affected-tests/README.md +37 -0
  5. package/hooks/hook-affected-tests/package.json +50 -0
  6. package/hooks/hook-affected-tests/src/hook.ts +148 -0
  7. package/hooks/hook-affected-tests/tsconfig.json +25 -0
  8. package/hooks/hook-agentmessages/bin/cli.ts +125 -0
  9. package/hooks/hook-announce-start/LICENSE +191 -0
  10. package/hooks/hook-announce-start/README.md +35 -0
  11. package/hooks/hook-announce-start/package.json +51 -0
  12. package/hooks/hook-announce-start/src/hook.ts +137 -0
  13. package/hooks/hook-announce-start/tsconfig.json +25 -0
  14. package/hooks/hook-announce-stop/LICENSE +191 -0
  15. package/hooks/hook-announce-stop/README.md +35 -0
  16. package/hooks/hook-announce-stop/package.json +51 -0
  17. package/hooks/hook-announce-stop/src/hook.ts +160 -0
  18. package/hooks/hook-announce-stop/tsconfig.json +25 -0
  19. package/hooks/hook-checkdocs/bun.lock +25 -0
  20. package/hooks/hook-commandlog/src/hook.ts +15 -38
  21. package/hooks/hook-conflict-detect/LICENSE +191 -0
  22. package/hooks/hook-conflict-detect/README.md +38 -0
  23. package/hooks/hook-conflict-detect/package.json +50 -0
  24. package/hooks/hook-conflict-detect/src/hook.ts +116 -0
  25. package/hooks/hook-conflict-detect/tsconfig.json +25 -0
  26. package/hooks/hook-costwatch/src/hook.ts +39 -42
  27. package/hooks/hook-dm-inject/LICENSE +191 -0
  28. package/hooks/hook-dm-inject/README.md +42 -0
  29. package/hooks/hook-dm-inject/package.json +51 -0
  30. package/hooks/hook-dm-inject/src/hook.ts +115 -0
  31. package/hooks/hook-dm-inject/tsconfig.json +25 -0
  32. package/hooks/hook-errornotify/src/hook.ts +20 -65
  33. package/hooks/hook-failure-to-task/LICENSE +191 -0
  34. package/hooks/hook-failure-to-task/README.md +40 -0
  35. package/hooks/hook-failure-to-task/package.json +51 -0
  36. package/hooks/hook-failure-to-task/src/hook.ts +171 -0
  37. package/hooks/hook-failure-to-task/tsconfig.json +25 -0
  38. package/hooks/hook-filelock/LICENSE +191 -0
  39. package/hooks/hook-filelock/README.md +44 -0
  40. package/hooks/hook-filelock/package.json +50 -0
  41. package/hooks/hook-filelock/src/hook.ts +147 -0
  42. package/hooks/hook-filelock/tsconfig.json +25 -0
  43. package/hooks/hook-sessionlog/src/hook.ts +11 -52
  44. package/hooks/hook-typecheck-gate/LICENSE +191 -0
  45. package/hooks/hook-typecheck-gate/README.md +46 -0
  46. package/hooks/hook-typecheck-gate/package.json +50 -0
  47. package/hooks/hook-typecheck-gate/src/hook.ts +152 -0
  48. package/hooks/hook-typecheck-gate/tsconfig.json +25 -0
  49. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -2147,7 +2165,7 @@ var init_registry = __esm(() => {
2147
2165
  {
2148
2166
  name: "sessionlog",
2149
2167
  displayName: "Session Log",
2150
- description: "Logs every tool call to .claude/session-log-<date>.jsonl",
2168
+ description: "Logs every tool call to SQLite (~/.hooks/hooks.db)",
2151
2169
  version: "0.1.0",
2152
2170
  category: "Observability",
2153
2171
  event: "PostToolUse",
@@ -2157,7 +2175,7 @@ var init_registry = __esm(() => {
2157
2175
  {
2158
2176
  name: "commandlog",
2159
2177
  displayName: "Command Log",
2160
- description: "Logs every bash command Claude runs to .claude/commands.log",
2178
+ description: "Logs every Bash command to SQLite (~/.hooks/hooks.db)",
2161
2179
  version: "0.1.0",
2162
2180
  category: "Observability",
2163
2181
  event: "PostToolUse",
@@ -2167,7 +2185,7 @@ var init_registry = __esm(() => {
2167
2185
  {
2168
2186
  name: "costwatch",
2169
2187
  displayName: "Cost Watch",
2170
- description: "Estimates session token usage and warns when budget threshold is exceeded",
2188
+ description: "Estimates session token usage, persists cost history to SQLite, and warns on budget overrun",
2171
2189
  version: "0.1.0",
2172
2190
  category: "Observability",
2173
2191
  event: "Stop",
@@ -2177,7 +2195,7 @@ var init_registry = __esm(() => {
2177
2195
  {
2178
2196
  name: "errornotify",
2179
2197
  displayName: "Error Notify",
2180
- description: "Detects tool failures and logs errors to .claude/errors.log",
2198
+ description: "Detects tool failures and logs errors to SQLite (~/.hooks/hooks.db)",
2181
2199
  version: "0.1.0",
2182
2200
  category: "Observability",
2183
2201
  event: "PostToolUse",
@@ -2203,6 +2221,86 @@ var init_registry = __esm(() => {
2203
2221
  event: "PostToolUse",
2204
2222
  matcher: "",
2205
2223
  tags: ["tasks", "completion", "gate", "quality", "agent-teams"]
2224
+ },
2225
+ {
2226
+ name: "typecheck-gate",
2227
+ displayName: "Typecheck Gate",
2228
+ description: "Runs TypeScript type checking on Stop and blocks if type errors are found",
2229
+ version: "0.1.0",
2230
+ category: "Code Quality",
2231
+ event: "Stop",
2232
+ matcher: "",
2233
+ tags: ["typescript", "typecheck", "types", "quality", "gate"]
2234
+ },
2235
+ {
2236
+ name: "affected-tests",
2237
+ displayName: "Affected Tests",
2238
+ description: "Maps edited files to their test files and runs them automatically after edits",
2239
+ version: "0.1.0",
2240
+ category: "Code Quality",
2241
+ event: "PostToolUse",
2242
+ matcher: "Edit|Write|NotebookEdit",
2243
+ tags: ["tests", "test-runner", "affected", "quality"]
2244
+ },
2245
+ {
2246
+ name: "conflict-detect",
2247
+ displayName: "Conflict Detect",
2248
+ description: "Blocks edits on files with unresolved git merge conflict markers",
2249
+ version: "0.1.0",
2250
+ category: "Git Safety",
2251
+ event: "PreToolUse",
2252
+ matcher: "Edit|Write",
2253
+ tags: ["git", "conflicts", "merge", "safety"]
2254
+ },
2255
+ {
2256
+ name: "failure-to-task",
2257
+ displayName: "Failure to Task",
2258
+ description: "Creates a todo task automatically when test or build commands fail",
2259
+ version: "0.1.0",
2260
+ category: "Workflow Automation",
2261
+ event: "PostToolUse",
2262
+ matcher: "Bash",
2263
+ tags: ["failures", "tasks", "tests", "build", "automation"]
2264
+ },
2265
+ {
2266
+ name: "filelock",
2267
+ displayName: "File Lock",
2268
+ description: "Auto-checks file locks before edits to prevent multi-agent conflicts",
2269
+ version: "0.1.0",
2270
+ category: "Agent Teams",
2271
+ event: "PreToolUse",
2272
+ matcher: "Edit|Write|NotebookEdit",
2273
+ tags: ["locking", "coordination", "multi-agent", "conflict"]
2274
+ },
2275
+ {
2276
+ name: "announce-start",
2277
+ displayName: "Announce Start",
2278
+ description: "On session start, auto-registers agent, reads messages, and announces to space",
2279
+ version: "0.1.0",
2280
+ category: "Agent Teams",
2281
+ event: "Notification",
2282
+ matcher: "",
2283
+ tags: ["announcement", "start", "register", "messages", "agent-teams"]
2284
+ },
2285
+ {
2286
+ name: "announce-stop",
2287
+ displayName: "Announce Stop",
2288
+ description: "On Stop, releases file locks, posts session summary, and updates task statuses",
2289
+ version: "0.1.0",
2290
+ category: "Agent Teams",
2291
+ event: "Stop",
2292
+ matcher: "",
2293
+ tags: ["announcement", "stop", "locks", "summary", "agent-teams"]
2294
+ },
2295
+ {
2296
+ name: "dm-inject",
2297
+ displayName: "DM Inject",
2298
+ description: "Injects unread direct messages into agent context on Notification events",
2299
+ version: "0.1.0",
2300
+ category: "Agent Teams",
2301
+ event: "Notification",
2302
+ matcher: "",
2303
+ tags: ["messages", "dm", "context", "inject", "agent-teams"]
2206
2304
  }
2207
2305
  ];
2208
2306
  });
@@ -4152,6 +4250,264 @@ var init_profiles = __esm(() => {
4152
4250
  PROFILES_DIR = join2(homedir2(), ".hooks", "profiles");
4153
4251
  });
4154
4252
 
4253
+ // src/db/schema.ts
4254
+ var CREATE_HOOK_EVENTS_TABLE = `
4255
+ CREATE TABLE IF NOT EXISTS hook_events (
4256
+ id TEXT PRIMARY KEY,
4257
+ timestamp TEXT NOT NULL,
4258
+ session_id TEXT NOT NULL,
4259
+ hook_name TEXT NOT NULL,
4260
+ event_type TEXT NOT NULL CHECK (event_type IN ('PreToolUse', 'PostToolUse', 'Stop', 'Notification')),
4261
+ tool_name TEXT,
4262
+ tool_input TEXT,
4263
+ result TEXT CHECK (result IN ('continue', 'block', NULL)),
4264
+ error TEXT,
4265
+ duration_ms INTEGER,
4266
+ project_dir TEXT,
4267
+ metadata TEXT
4268
+ )
4269
+ `, CREATE_INDEXES;
4270
+ var init_schema = __esm(() => {
4271
+ CREATE_INDEXES = [
4272
+ `CREATE INDEX IF NOT EXISTS idx_hook_events_timestamp ON hook_events (timestamp)`,
4273
+ `CREATE INDEX IF NOT EXISTS idx_hook_events_session_id ON hook_events (session_id)`,
4274
+ `CREATE INDEX IF NOT EXISTS idx_hook_events_hook_name ON hook_events (hook_name)`,
4275
+ `CREATE INDEX IF NOT EXISTS idx_hook_events_event_type ON hook_events (event_type)`,
4276
+ `CREATE INDEX IF NOT EXISTS idx_hook_events_errors ON hook_events (timestamp) WHERE error IS NOT NULL`
4277
+ ];
4278
+ });
4279
+
4280
+ // src/db/migrations/001_initial.ts
4281
+ function up(db) {
4282
+ db.exec(CREATE_HOOK_EVENTS_TABLE);
4283
+ for (const idx of CREATE_INDEXES) {
4284
+ db.exec(idx);
4285
+ }
4286
+ }
4287
+ var init_001_initial = __esm(() => {
4288
+ init_schema();
4289
+ });
4290
+
4291
+ // src/db/migrations/index.ts
4292
+ function ensureMigrationsTable(db) {
4293
+ db.exec(`
4294
+ CREATE TABLE IF NOT EXISTS schema_migrations (
4295
+ version TEXT PRIMARY KEY,
4296
+ applied_at TEXT NOT NULL
4297
+ )
4298
+ `);
4299
+ }
4300
+ function getApplied(db) {
4301
+ const rows = db.query("SELECT version FROM schema_migrations").all();
4302
+ return new Set(rows.map((r) => r.version));
4303
+ }
4304
+ function runMigrations(db) {
4305
+ ensureMigrationsTable(db);
4306
+ const applied = getApplied(db);
4307
+ for (const migration of MIGRATIONS) {
4308
+ if (applied.has(migration.version))
4309
+ continue;
4310
+ migration.up(db);
4311
+ db.run("INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)", [
4312
+ migration.version,
4313
+ new Date().toISOString()
4314
+ ]);
4315
+ }
4316
+ }
4317
+ var MIGRATIONS;
4318
+ var init_migrations = __esm(() => {
4319
+ init_001_initial();
4320
+ MIGRATIONS = [{ version: "001_initial", up }];
4321
+ });
4322
+
4323
+ // src/db/legacy-import.ts
4324
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
4325
+ import { join as join3 } from "path";
4326
+ import { homedir as homedir3 } from "os";
4327
+ function ensureMetaTable(db) {
4328
+ db.exec(`
4329
+ CREATE TABLE IF NOT EXISTS _meta (
4330
+ key TEXT PRIMARY KEY,
4331
+ value TEXT NOT NULL
4332
+ )
4333
+ `);
4334
+ }
4335
+ function isAlreadyDone(db) {
4336
+ ensureMetaTable(db);
4337
+ const row = db.query("SELECT value FROM _meta WHERE key = ?").get(META_KEY);
4338
+ return row?.value === "1";
4339
+ }
4340
+ function markDone(db) {
4341
+ db.run("INSERT OR REPLACE INTO _meta (key, value) VALUES (?, ?)", [META_KEY, "1"]);
4342
+ }
4343
+ function nanoid() {
4344
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 21);
4345
+ }
4346
+ function importJsonlFile(db, filePath) {
4347
+ let count = 0;
4348
+ try {
4349
+ const lines = readFileSync3(filePath, "utf-8").split(`
4350
+ `).filter(Boolean);
4351
+ for (const line of lines) {
4352
+ try {
4353
+ const entry = JSON.parse(line);
4354
+ db.run(`INSERT OR IGNORE INTO hook_events
4355
+ (id, timestamp, session_id, hook_name, event_type, tool_name, tool_input, project_dir)
4356
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
4357
+ nanoid(),
4358
+ entry.timestamp ?? new Date().toISOString(),
4359
+ entry.session_id ?? "legacy",
4360
+ "sessionlog",
4361
+ "PostToolUse",
4362
+ entry.tool_name ?? null,
4363
+ entry.tool_input ? String(entry.tool_input).slice(0, 500) : null,
4364
+ null
4365
+ ]);
4366
+ count++;
4367
+ } catch {}
4368
+ }
4369
+ } catch {}
4370
+ return count;
4371
+ }
4372
+ function importErrorsLog(db, filePath) {
4373
+ let count = 0;
4374
+ try {
4375
+ const lines = readFileSync3(filePath, "utf-8").split(`
4376
+ `).filter(Boolean);
4377
+ const linePattern = /^\[(.+?)\]\s+(?:\[session:(\S+)\]\s+)?(.+?)\s+\u2014\s+(.+)$/;
4378
+ for (const line of lines) {
4379
+ try {
4380
+ const m = line.match(linePattern);
4381
+ if (!m)
4382
+ continue;
4383
+ const [, timestamp, sessionPrefix, , errorMsg] = m;
4384
+ db.run(`INSERT OR IGNORE INTO hook_events
4385
+ (id, timestamp, session_id, hook_name, event_type, error)
4386
+ VALUES (?, ?, ?, ?, ?, ?)`, [
4387
+ nanoid(),
4388
+ timestamp,
4389
+ sessionPrefix ? `legacy-${sessionPrefix}` : "legacy",
4390
+ "errornotify",
4391
+ "PostToolUse",
4392
+ errorMsg.slice(0, 500)
4393
+ ]);
4394
+ count++;
4395
+ } catch {}
4396
+ }
4397
+ } catch {}
4398
+ return count;
4399
+ }
4400
+ function runLegacyImport(db) {
4401
+ try {
4402
+ if (isAlreadyDone(db))
4403
+ return;
4404
+ let total = 0;
4405
+ const claudeProjectsDir = join3(homedir3(), ".claude", "projects");
4406
+ if (existsSync3(claudeProjectsDir)) {
4407
+ try {
4408
+ const projectDirs = readdirSync2(claudeProjectsDir);
4409
+ for (const dir of projectDirs) {
4410
+ const projectDir = join3(claudeProjectsDir, dir);
4411
+ try {
4412
+ const files = readdirSync2(projectDir);
4413
+ for (const file of files) {
4414
+ if (file.match(/^session-log-\d{4}-\d{2}-\d{2}\.jsonl$/)) {
4415
+ total += importJsonlFile(db, join3(projectDir, file));
4416
+ }
4417
+ if (file === "errors.log") {
4418
+ total += importErrorsLog(db, join3(projectDir, file));
4419
+ }
4420
+ }
4421
+ } catch {}
4422
+ }
4423
+ } catch {}
4424
+ }
4425
+ markDone(db);
4426
+ if (total > 0) {
4427
+ process.stderr.write(`[hooks] Imported ${total} legacy log entries into SQLite.
4428
+ `);
4429
+ }
4430
+ } catch (err) {
4431
+ process.stderr.write(`[hooks] Legacy import failed (non-fatal): ${err}
4432
+ `);
4433
+ }
4434
+ }
4435
+ var META_KEY = "legacy_import_done";
4436
+ var init_legacy_import = () => {};
4437
+
4438
+ // src/db/retention.ts
4439
+ function runRetention(db, days) {
4440
+ const envDays = parseInt(process.env.HOOKS_RETENTION_DAYS ?? "30");
4441
+ const retentionDays = days ?? (isNaN(envDays) || envDays <= 0 ? 30 : envDays);
4442
+ const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();
4443
+ try {
4444
+ db.run("DELETE FROM hook_events WHERE timestamp < ?", [cutoff]);
4445
+ const changes = db.query("SELECT changes() as changes").get()?.changes ?? 0;
4446
+ return changes;
4447
+ } catch {
4448
+ return 0;
4449
+ }
4450
+ }
4451
+
4452
+ // src/db/index.ts
4453
+ var exports_db = {};
4454
+ __export(exports_db, {
4455
+ getDbPath: () => getDbPath,
4456
+ getDb: () => getDb,
4457
+ createTestDb: () => createTestDb,
4458
+ closeDb: () => closeDb
4459
+ });
4460
+ import { Database } from "bun:sqlite";
4461
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
4462
+ import { join as join4 } from "path";
4463
+ import { homedir as homedir4 } from "os";
4464
+ function getDbPath() {
4465
+ if (process.env.HOOKS_DB_PATH) {
4466
+ return process.env.HOOKS_DB_PATH;
4467
+ }
4468
+ const dataDir = process.env.HOOKS_DATA_DIR ?? join4(homedir4(), ".hooks");
4469
+ return join4(dataDir, "hooks.db");
4470
+ }
4471
+ function ensureDir(dbPath) {
4472
+ const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
4473
+ if (dir && !existsSync4(dir)) {
4474
+ mkdirSync3(dir, { recursive: true });
4475
+ }
4476
+ }
4477
+ function getDb() {
4478
+ if (instance)
4479
+ return instance;
4480
+ const dbPath = getDbPath();
4481
+ const isNew = dbPath === ":memory:" || !existsSync4(dbPath);
4482
+ ensureDir(dbPath);
4483
+ instance = new Database(dbPath);
4484
+ instance.exec("PRAGMA journal_mode=WAL");
4485
+ instance.exec("PRAGMA foreign_keys=ON");
4486
+ runMigrations(instance);
4487
+ runRetention(instance);
4488
+ if (isNew) {
4489
+ runLegacyImport(instance);
4490
+ }
4491
+ return instance;
4492
+ }
4493
+ function closeDb() {
4494
+ if (instance) {
4495
+ instance.close();
4496
+ instance = null;
4497
+ }
4498
+ }
4499
+ function createTestDb() {
4500
+ const db = new Database(":memory:");
4501
+ db.exec("PRAGMA journal_mode=WAL");
4502
+ db.exec("PRAGMA foreign_keys=ON");
4503
+ return db;
4504
+ }
4505
+ var instance = null;
4506
+ var init_db = __esm(() => {
4507
+ init_migrations();
4508
+ init_legacy_import();
4509
+ });
4510
+
4155
4511
  // src/mcp/server.ts
4156
4512
  var exports_server = {};
4157
4513
  __export(exports_server, {
@@ -4165,8 +4521,8 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4165
4521
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4166
4522
  import { z } from "zod";
4167
4523
  import { createServer } from "http";
4168
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4169
- import { join as join3, dirname as dirname2 } from "path";
4524
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4525
+ import { join as join5, dirname as dirname2 } from "path";
4170
4526
  import { fileURLToPath as fileURLToPath2 } from "url";
4171
4527
  function formatInstallResults(results, extra) {
4172
4528
  const installed = results.filter((r) => r.success).map((r) => r.hook);
@@ -4263,7 +4619,7 @@ function createHooksServer() {
4263
4619
  const settingsPath = getSettingsPath(scope);
4264
4620
  const issues = [];
4265
4621
  const healthy = [];
4266
- const settingsExist = existsSync3(settingsPath);
4622
+ const settingsExist = existsSync5(settingsPath);
4267
4623
  if (!settingsExist) {
4268
4624
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
4269
4625
  }
@@ -4276,13 +4632,13 @@ function createHooksServer() {
4276
4632
  continue;
4277
4633
  }
4278
4634
  const hookDir = getHookPath(name);
4279
- if (!existsSync3(join3(hookDir, "src", "hook.ts"))) {
4635
+ if (!existsSync5(join5(hookDir, "src", "hook.ts"))) {
4280
4636
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
4281
4637
  hookHealthy = false;
4282
4638
  }
4283
4639
  if (meta && settingsExist) {
4284
4640
  try {
4285
- const settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
4641
+ const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
4286
4642
  const eventHooks = settings.hooks?.[meta.event] || [];
4287
4643
  const found = eventHooks.some((entry) => entry.hooks?.some((h) => {
4288
4644
  const match = h.command?.match(/^hooks run (\w+)/);
@@ -4313,10 +4669,10 @@ function createHooksServer() {
4313
4669
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
4314
4670
  }
4315
4671
  const hookPath = getHookPath(name);
4316
- const readmePath = join3(hookPath, "README.md");
4672
+ const readmePath = join5(hookPath, "README.md");
4317
4673
  let readme = "";
4318
- if (existsSync3(readmePath)) {
4319
- readme = readFileSync3(readmePath, "utf-8");
4674
+ if (existsSync5(readmePath)) {
4675
+ readme = readFileSync4(readmePath, "utf-8");
4320
4676
  }
4321
4677
  return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
4322
4678
  }
@@ -4365,8 +4721,8 @@ function createHooksServer() {
4365
4721
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
4366
4722
  }
4367
4723
  const hookDir = getHookPath(name);
4368
- const hookScript = join3(hookDir, "src", "hook.ts");
4369
- if (!existsSync3(hookScript)) {
4724
+ const hookScript = join5(hookDir, "src", "hook.ts");
4725
+ if (!existsSync5(hookScript)) {
4370
4726
  return { content: [{ type: "text", text: JSON.stringify({ error: `Hook script not found: ${hookScript}` }) }] };
4371
4727
  }
4372
4728
  let hookInput = { ...input };
@@ -4457,7 +4813,7 @@ function createHooksServer() {
4457
4813
  const ctx = {
4458
4814
  scope,
4459
4815
  settings_path: settingsPath,
4460
- settings_exists: existsSync3(settingsPath),
4816
+ settings_exists: existsSync5(settingsPath),
4461
4817
  registered_hooks: hooks,
4462
4818
  hook_count: hooks.length,
4463
4819
  healthy,
@@ -4495,8 +4851,8 @@ function createHooksServer() {
4495
4851
  const input = { tool_name, tool_input };
4496
4852
  const results = await Promise.all(matchingHooks.map(async (name) => {
4497
4853
  const hookDir = getHookPath(name);
4498
- const hookScript = join3(hookDir, "src", "hook.ts");
4499
- if (!existsSync3(hookScript))
4854
+ const hookScript = join5(hookDir, "src", "hook.ts");
4855
+ if (!existsSync5(hookScript))
4500
4856
  return { name, decision: "approve", error: "script not found" };
4501
4857
  const proc = Bun.spawn(["bun", "run", hookScript], {
4502
4858
  stdin: new Response(JSON.stringify(input)),
@@ -4564,8 +4920,8 @@ function createHooksServer() {
4564
4920
  const meta = getHook(name);
4565
4921
  if (!meta)
4566
4922
  return { name, error: `Hook '${name}' not found` };
4567
- const hookScript = join3(getHookPath(name), "src", "hook.ts");
4568
- if (!existsSync3(hookScript))
4923
+ const hookScript = join5(getHookPath(name), "src", "hook.ts");
4924
+ if (!existsSync5(hookScript))
4569
4925
  return { name, error: "script not found" };
4570
4926
  const proc = Bun.spawn(["bun", "run", hookScript], {
4571
4927
  stdin: new Response(JSON.stringify(input)),
@@ -4598,8 +4954,8 @@ function createHooksServer() {
4598
4954
  const settingsPath = getSettingsPath(scope);
4599
4955
  let settings = {};
4600
4956
  try {
4601
- if (existsSync3(settingsPath))
4602
- settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
4957
+ if (existsSync5(settingsPath))
4958
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
4603
4959
  } catch {}
4604
4960
  if (!settings.hooks)
4605
4961
  settings.hooks = {};
@@ -4607,9 +4963,9 @@ function createHooksServer() {
4607
4963
  if (!disabled.includes(name))
4608
4964
  disabled.push(name);
4609
4965
  settings.hooks.__disabled = disabled;
4610
- const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3 } = await import("fs");
4966
+ const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync4 } = await import("fs");
4611
4967
  const { dirname: dirname3 } = await import("path");
4612
- mkdirSync3(dirname3(settingsPath), { recursive: true });
4968
+ mkdirSync4(dirname3(settingsPath), { recursive: true });
4613
4969
  writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + `
4614
4970
  `);
4615
4971
  return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
@@ -4621,8 +4977,8 @@ function createHooksServer() {
4621
4977
  const settingsPath = getSettingsPath(scope);
4622
4978
  let settings = {};
4623
4979
  try {
4624
- if (existsSync3(settingsPath))
4625
- settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
4980
+ if (existsSync5(settingsPath))
4981
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
4626
4982
  } catch {}
4627
4983
  if (settings.hooks?.__disabled) {
4628
4984
  settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
@@ -4645,6 +5001,104 @@ function createHooksServer() {
4645
5001
  const profiles = listProfiles();
4646
5002
  return { content: [{ type: "text", text: JSON.stringify(profiles) }] };
4647
5003
  });
5004
+ server.tool("hooks_log_list", "List hook events from SQLite (~/.hooks/hooks.db). Filter by hook name, session ID, or time range.", {
5005
+ hook_name: z.string().optional().describe("Filter by hook name (e.g. 'sessionlog', 'costwatch')"),
5006
+ session_id: z.string().optional().describe("Filter by session ID prefix"),
5007
+ limit: z.number().default(50).describe("Max number of events to return"),
5008
+ since: z.string().optional().describe("ISO timestamp or duration string (e.g. '1h', '30m', '7d') to filter from")
5009
+ }, async ({ hook_name, session_id, limit, since }) => {
5010
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
5011
+ const db = getDb2();
5012
+ function parseDuration(s) {
5013
+ const m = s.match(/^(\d+)(s|m|h|d)$/);
5014
+ if (!m)
5015
+ return null;
5016
+ const n = parseInt(m[1]);
5017
+ const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
5018
+ return new Date(Date.now() - n * ms).toISOString();
5019
+ }
5020
+ let sql = "SELECT * FROM hook_events WHERE 1=1";
5021
+ const params = [];
5022
+ if (hook_name) {
5023
+ sql += " AND hook_name = ?";
5024
+ params.push(hook_name);
5025
+ }
5026
+ if (session_id) {
5027
+ sql += " AND session_id LIKE ?";
5028
+ params.push(`${session_id}%`);
5029
+ }
5030
+ if (since) {
5031
+ const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
5032
+ if (ts) {
5033
+ sql += " AND timestamp >= ?";
5034
+ params.push(ts);
5035
+ }
5036
+ }
5037
+ sql += " ORDER BY timestamp DESC LIMIT ?";
5038
+ params.push(limit);
5039
+ const rows = db.query(sql).all(...params);
5040
+ return { content: [{ type: "text", text: JSON.stringify({ events: rows, count: rows.length }) }] };
5041
+ });
5042
+ server.tool("hooks_log_tail", "Show the most recent hook events from SQLite.", {
5043
+ n: z.number().default(20).describe("Number of most recent events to return")
5044
+ }, async ({ n }) => {
5045
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
5046
+ const db = getDb2();
5047
+ const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(n);
5048
+ return { content: [{ type: "text", text: JSON.stringify({ events: rows, count: rows.length }) }] };
5049
+ });
5050
+ server.tool("hooks_log_errors", "Show hook events that contain errors, optionally filtered by time range.", {
5051
+ since: z.string().default("24h").describe("Duration string (e.g. '1h', '30m', '7d') or ISO timestamp"),
5052
+ limit: z.number().default(50).describe("Max number of error events to return")
5053
+ }, async ({ since, limit }) => {
5054
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
5055
+ const db = getDb2();
5056
+ function parseDuration(s) {
5057
+ const m = s.match(/^(\d+)(s|m|h|d)$/);
5058
+ if (!m)
5059
+ return s;
5060
+ const n = parseInt(m[1]);
5061
+ const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
5062
+ return new Date(Date.now() - n * ms).toISOString();
5063
+ }
5064
+ const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
5065
+ const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts, limit);
5066
+ return { content: [{ type: "text", text: JSON.stringify({ events: rows, count: rows.length }) }] };
5067
+ });
5068
+ server.tool("hooks_log_summary", "Summarize hook execution: counts per hook, error rates, and recent activity.", {
5069
+ since: z.string().default("24h").describe("Duration string (e.g. '1h', '24h', '7d') or ISO timestamp")
5070
+ }, async ({ since }) => {
5071
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
5072
+ const db = getDb2();
5073
+ function parseDuration(s) {
5074
+ const m = s.match(/^(\d+)(s|m|h|d)$/);
5075
+ if (!m)
5076
+ return s;
5077
+ const n = parseInt(m[1]);
5078
+ const ms = { s: 1000, m: 60000, h: 3600000, d: 86400000 }[m[2]];
5079
+ return new Date(Date.now() - n * ms).toISOString();
5080
+ }
5081
+ const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
5082
+ const totals = db.query("SELECT hook_name, COUNT(*) as total, SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) as errors FROM hook_events WHERE timestamp >= ? GROUP BY hook_name ORDER BY total DESC").all(ts);
5083
+ const summary = totals.map((r) => ({
5084
+ hook_name: r.hook_name,
5085
+ total: r.total,
5086
+ errors: r.errors,
5087
+ error_rate: r.total > 0 ? (r.errors / r.total * 100).toFixed(1) + "%" : "0%"
5088
+ }));
5089
+ const grandTotal = totals.reduce((s, r) => s + r.total, 0);
5090
+ const grandErrors = totals.reduce((s, r) => s + r.errors, 0);
5091
+ return {
5092
+ content: [{
5093
+ type: "text",
5094
+ text: JSON.stringify({
5095
+ since: ts,
5096
+ hooks: summary,
5097
+ totals: { events: grandTotal, errors: grandErrors, hooks_active: totals.length }
5098
+ })
5099
+ }]
5100
+ };
5101
+ });
4648
5102
  return server;
4649
5103
  }
4650
5104
  async function startSSEServer(port = MCP_PORT) {
@@ -4690,7 +5144,7 @@ var init_server = __esm(() => {
4690
5144
  init_installer();
4691
5145
  init_profiles();
4692
5146
  __dirname3 = dirname2(fileURLToPath2(import.meta.url));
4693
- pkg = JSON.parse(readFileSync3(join3(__dirname3, "..", "..", "package.json"), "utf-8"));
5147
+ pkg = JSON.parse(readFileSync4(join5(__dirname3, "..", "..", "package.json"), "utf-8"));
4694
5148
  });
4695
5149
 
4696
5150
  // src/cli/index.tsx
@@ -4714,8 +5168,8 @@ var {
4714
5168
 
4715
5169
  // src/cli/index.tsx
4716
5170
  import chalk2 from "chalk";
4717
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
4718
- import { join as join4, dirname as dirname3 } from "path";
5171
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
5172
+ import { join as join6, dirname as dirname3 } from "path";
4719
5173
  import { fileURLToPath as fileURLToPath3 } from "url";
4720
5174
 
4721
5175
  // src/cli/components/App.tsx
@@ -5893,8 +6347,8 @@ init_installer();
5893
6347
  init_profiles();
5894
6348
  import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
5895
6349
  var __dirname4 = dirname3(fileURLToPath3(import.meta.url));
5896
- var pkgPath = existsSync4(join4(__dirname4, "..", "package.json")) ? join4(__dirname4, "..", "package.json") : join4(__dirname4, "..", "..", "package.json");
5897
- var pkg2 = JSON.parse(readFileSync4(pkgPath, "utf-8"));
6350
+ var pkgPath = existsSync6(join6(__dirname4, "..", "package.json")) ? join6(__dirname4, "..", "package.json") : join6(__dirname4, "..", "..", "package.json");
6351
+ var pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
5898
6352
  var program2 = new Command;
5899
6353
  function resolveScope(options) {
5900
6354
  if (options.project)
@@ -5964,8 +6418,8 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
5964
6418
  process.exit(1);
5965
6419
  }
5966
6420
  const hookDir = getHookPath(hook);
5967
- const hookScript = join4(hookDir, "src", "hook.ts");
5968
- if (!existsSync4(hookScript)) {
6421
+ const hookScript = join6(hookDir, "src", "hook.ts");
6422
+ if (!existsSync6(hookScript)) {
5969
6423
  console.error(JSON.stringify({ error: `Hook script not found: ${hookScript}` }));
5970
6424
  process.exit(1);
5971
6425
  }
@@ -6265,7 +6719,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
6265
6719
  const settingsPath = getSettingsPath(scope);
6266
6720
  const issues = [];
6267
6721
  const healthy = [];
6268
- const settingsExist = existsSync4(settingsPath);
6722
+ const settingsExist = existsSync6(settingsPath);
6269
6723
  if (!settingsExist) {
6270
6724
  issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
6271
6725
  }
@@ -6279,14 +6733,14 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
6279
6733
  continue;
6280
6734
  }
6281
6735
  const hookDir = getHookPath(name);
6282
- const hookScript = join4(hookDir, "src", "hook.ts");
6283
- if (!existsSync4(hookScript)) {
6736
+ const hookScript = join6(hookDir, "src", "hook.ts");
6737
+ if (!existsSync6(hookScript)) {
6284
6738
  issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
6285
6739
  hookHealthy = false;
6286
6740
  }
6287
6741
  if (meta && settingsExist) {
6288
6742
  try {
6289
- const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
6743
+ const settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
6290
6744
  const eventHooks = settings.hooks?.[meta.event] || [];
6291
6745
  const found = eventHooks.some((entry) => entry.hooks?.some((h) => {
6292
6746
  const match = h.command?.match(/^hooks run (\w+)/);
@@ -6384,10 +6838,10 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
6384
6838
  return;
6385
6839
  }
6386
6840
  const hookPath = getHookPath(hook);
6387
- const readmePath = join4(hookPath, "README.md");
6841
+ const readmePath = join6(hookPath, "README.md");
6388
6842
  let readme = "";
6389
- if (existsSync4(readmePath)) {
6390
- readme = readFileSync4(readmePath, "utf-8");
6843
+ if (existsSync6(readmePath)) {
6844
+ readme = readFileSync5(readmePath, "utf-8");
6391
6845
  }
6392
6846
  if (options.json) {
6393
6847
  console.log(JSON.stringify({ ...meta, readme }));
@@ -6569,9 +7023,9 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
6569
7023
  if (file === "-") {
6570
7024
  raw = await new Response(Bun.stdin.stream()).text();
6571
7025
  } else {
6572
- const { readFileSync: readFileSync5 } = await import("fs");
7026
+ const { readFileSync: readFileSync6 } = await import("fs");
6573
7027
  try {
6574
- raw = readFileSync5(file, "utf-8");
7028
+ raw = readFileSync6(file, "utf-8");
6575
7029
  } catch {
6576
7030
  if (options.json) {
6577
7031
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }));
@@ -6602,6 +7056,154 @@ program2.command("profile-import").argument("<file>", "JSON file to import profi
6602
7056
  console.log(chalk2.dim(` Skipped ${result.skipped} (already exist or invalid)`));
6603
7057
  }
6604
7058
  });
7059
+ var logCmd = program2.command("log").description("Query hook event logs from SQLite (~/.hooks/hooks.db)");
7060
+ logCmd.command("list").description("List hook events").option("--hook <name>", "Filter by hook name").option("--session <id>", "Filter by session ID").option("-n, --limit <n>", "Number of rows to show", "50").option("-j, --json", "Output as JSON", false).action(async (options) => {
7061
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
7062
+ const db = getDb2();
7063
+ const limit = parseInt(options.limit) || 50;
7064
+ let sql = "SELECT * FROM hook_events WHERE 1=1";
7065
+ const params = [];
7066
+ if (options.hook) {
7067
+ sql += " AND hook_name = ?";
7068
+ params.push(options.hook);
7069
+ }
7070
+ if (options.session) {
7071
+ sql += " AND session_id LIKE ?";
7072
+ params.push(`${options.session}%`);
7073
+ }
7074
+ sql += " ORDER BY timestamp DESC LIMIT ?";
7075
+ params.push(String(limit));
7076
+ const rows = db.query(sql).all(...params);
7077
+ if (options.json) {
7078
+ console.log(JSON.stringify(rows, null, 2));
7079
+ return;
7080
+ }
7081
+ if (rows.length === 0) {
7082
+ console.log(chalk2.dim("No events found."));
7083
+ return;
7084
+ }
7085
+ console.log(chalk2.bold(`
7086
+ Hook Events (${rows.length})
7087
+ `));
7088
+ for (const row of rows) {
7089
+ const ts = row.timestamp.slice(0, 19).replace("T", " ");
7090
+ const err = row.error ? chalk2.red(` ERR: ${row.error.slice(0, 60)}`) : "";
7091
+ const tool = row.tool_name ? chalk2.dim(` [${row.tool_name}]`) : "";
7092
+ console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))}${tool}${err}`);
7093
+ }
7094
+ console.log();
7095
+ });
7096
+ logCmd.command("search <text>").description("Search hook events by tool_input or error text").option("-n, --limit <n>", "Number of rows to show", "50").option("-j, --json", "Output as JSON", false).action(async (text, options) => {
7097
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
7098
+ const db = getDb2();
7099
+ const limit = parseInt(options.limit) || 50;
7100
+ const q = `%${text}%`;
7101
+ const rows = db.query("SELECT * FROM hook_events WHERE tool_input LIKE ? OR error LIKE ? ORDER BY timestamp DESC LIMIT ?").all(q, q, limit);
7102
+ if (options.json) {
7103
+ console.log(JSON.stringify(rows, null, 2));
7104
+ return;
7105
+ }
7106
+ if (rows.length === 0) {
7107
+ console.log(chalk2.dim(`No events matching "${text}".`));
7108
+ return;
7109
+ }
7110
+ console.log(chalk2.bold(`
7111
+ Search results for "${text}" (${rows.length})
7112
+ `));
7113
+ for (const row of rows) {
7114
+ const ts = row.timestamp.slice(0, 19).replace("T", " ");
7115
+ const snippet = (row.tool_input || row.error || "").slice(0, 80);
7116
+ console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))} ${chalk2.dim(snippet)}`);
7117
+ }
7118
+ console.log();
7119
+ });
7120
+ logCmd.command("tail").description("Show most recent hook events").option("-n <n>", "Number of rows", "20").option("-j, --json", "Output as JSON", false).action(async (options) => {
7121
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
7122
+ const db = getDb2();
7123
+ const limit = parseInt(options.n) || 20;
7124
+ const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(limit);
7125
+ if (options.json) {
7126
+ console.log(JSON.stringify(rows, null, 2));
7127
+ return;
7128
+ }
7129
+ if (rows.length === 0) {
7130
+ console.log(chalk2.dim("No events yet."));
7131
+ return;
7132
+ }
7133
+ console.log(chalk2.bold(`
7134
+ Last ${rows.length} events
7135
+ `));
7136
+ for (const row of rows) {
7137
+ const ts = row.timestamp.slice(0, 19).replace("T", " ");
7138
+ const err = row.error ? chalk2.red(` \u2717 ${row.error.slice(0, 60)}`) : "";
7139
+ const tool = row.tool_name ? chalk2.dim(` [${row.tool_name}]`) : "";
7140
+ console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))}${tool}${err}`);
7141
+ }
7142
+ console.log();
7143
+ });
7144
+ logCmd.command("errors").description("Show hook events that contain errors").option("--since <duration>", "Only show errors since this duration (e.g. 1h, 30m, 7d)", "24h").option("-n, --limit <n>", "Number of rows to show", "50").option("-j, --json", "Output as JSON", false).action(async (options) => {
7145
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
7146
+ const db = getDb2();
7147
+ const limit = parseInt(options.limit) || 50;
7148
+ function parseDuration(s) {
7149
+ const m = s.match(/^(\d+)(s|m|h|d)$/);
7150
+ if (!m)
7151
+ return 86400000;
7152
+ const n = parseInt(m[1]);
7153
+ switch (m[2]) {
7154
+ case "s":
7155
+ return n * 1000;
7156
+ case "m":
7157
+ return n * 60 * 1000;
7158
+ case "h":
7159
+ return n * 60 * 60 * 1000;
7160
+ case "d":
7161
+ return n * 24 * 60 * 60 * 1000;
7162
+ default:
7163
+ return 86400000;
7164
+ }
7165
+ }
7166
+ const since = new Date(Date.now() - parseDuration(options.since)).toISOString();
7167
+ const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(since, limit);
7168
+ if (options.json) {
7169
+ console.log(JSON.stringify(rows, null, 2));
7170
+ return;
7171
+ }
7172
+ if (rows.length === 0) {
7173
+ console.log(chalk2.dim(`No errors in the last ${options.since}.`));
7174
+ return;
7175
+ }
7176
+ console.log(chalk2.bold(`
7177
+ Errors (last ${options.since}, ${rows.length} found)
7178
+ `));
7179
+ for (const row of rows) {
7180
+ const ts = row.timestamp.slice(0, 19).replace("T", " ");
7181
+ console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))} ${chalk2.red(row.error.slice(0, 100))}`);
7182
+ }
7183
+ console.log();
7184
+ });
7185
+ logCmd.command("clear").description("Delete hook event logs").option("--hook <name>", "Only delete events for this hook").option("-y, --yes", "Skip confirmation prompt", false).action(async (options) => {
7186
+ const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
7187
+ const db = getDb2();
7188
+ const countRow = options.hook ? db.query("SELECT COUNT(*) as n FROM hook_events WHERE hook_name = ?").get(options.hook) : db.query("SELECT COUNT(*) as n FROM hook_events").get();
7189
+ const count = countRow?.n ?? 0;
7190
+ if (count === 0) {
7191
+ console.log(chalk2.dim("Nothing to clear."));
7192
+ return;
7193
+ }
7194
+ if (!options.yes) {
7195
+ const scope = options.hook ? `hook "${options.hook}"` : "all hooks";
7196
+ console.log(chalk2.yellow(`About to delete ${count} event(s) for ${scope}.`));
7197
+ console.log(chalk2.dim("Re-run with --yes to confirm."));
7198
+ return;
7199
+ }
7200
+ if (options.hook) {
7201
+ db.run("DELETE FROM hook_events WHERE hook_name = ?", [options.hook]);
7202
+ } else {
7203
+ db.run("DELETE FROM hook_events");
7204
+ }
7205
+ console.log(chalk2.green(`\u2713 Cleared ${count} event(s).`));
7206
+ });
6605
7207
  program2.command("mcp").option("-s, --stdio", "Use stdio transport (for agent MCP integration)", false).option("-p, --port <port>", "Port for SSE transport", "39427").description("Start MCP server for AI agent integration").action(async (options) => {
6606
7208
  if (options.stdio) {
6607
7209
  const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));