@hasna/logs 0.0.1 → 0.1.0

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/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
  import {
4
4
  runJob
5
- } from "../index-dh02dp7n.js";
5
+ } from "../index-4x090f69.js";
6
6
  import {
7
7
  createPage,
8
8
  createProject,
@@ -13,14 +13,16 @@ import {
13
13
  searchLogs,
14
14
  summarizeLogs,
15
15
  tailLogs
16
- } from "../index-zj6ymcv7.js";
16
+ } from "../index-qmsvtxax.js";
17
17
  import {
18
- __commonJS,
19
- __require,
20
- __toESM,
21
18
  createJob,
22
19
  listJobs
23
- } from "../index-4mnved04.js";
20
+ } from "../jobs-124e878j.js";
21
+ import {
22
+ __commonJS,
23
+ __require,
24
+ __toESM
25
+ } from "../index-g8dczzvv.js";
24
26
 
25
27
  // node_modules/commander/lib/error.js
26
28
  var require_error = __commonJS((exports) => {
@@ -2231,7 +2233,7 @@ program2.command("scan").description("Run an immediate scan for a job").option("
2231
2233
  process.exit(1);
2232
2234
  }
2233
2235
  const db = getDb();
2234
- const job = (await import("../jobs-zv49jh0y.js")).getJob(db, opts.job);
2236
+ const job = (await import("../jobs-124e878j.js")).getJob(db, opts.job);
2235
2237
  if (!job) {
2236
2238
  console.error("Job not found");
2237
2239
  process.exit(1);
@@ -2240,6 +2242,37 @@ program2.command("scan").description("Run an immediate scan for a job").option("
2240
2242
  await runJob(db, job.id, job.project_id, job.page_id ?? undefined);
2241
2243
  console.log("Scan complete.");
2242
2244
  });
2245
+ program2.command("export").description("Export logs to JSON or CSV").option("--project <id>").option("--since <time>", "Relative time or ISO").option("--level <level>").option("--service <name>").option("--format <fmt>", "json or csv", "json").option("--output <file>", "Output file (default: stdout)").option("--limit <n>", "Max rows", "100000").action(async (opts) => {
2246
+ const { exportToCsv, exportToJson } = await import("../export-yjaw2sr3.js");
2247
+ const { createWriteStream } = await import("fs");
2248
+ const db = getDb();
2249
+ const options = {
2250
+ project_id: opts.project,
2251
+ since: parseRelativeTime(opts.since),
2252
+ level: opts.level,
2253
+ service: opts.service,
2254
+ limit: Number(opts.limit)
2255
+ };
2256
+ let count = 0;
2257
+ if (opts.output) {
2258
+ const stream = createWriteStream(opts.output);
2259
+ const write = (s) => stream.write(s);
2260
+ count = opts.format === "csv" ? exportToCsv(db, options, write) : exportToJson(db, options, write);
2261
+ stream.end();
2262
+ console.error(`Exported ${count} log(s) to ${opts.output}`);
2263
+ } else {
2264
+ const write = (s) => process.stdout.write(s);
2265
+ count = opts.format === "csv" ? exportToCsv(db, options, write) : exportToJson(db, options, write);
2266
+ process.stderr.write(`
2267
+ Exported ${count} log(s)
2268
+ `);
2269
+ }
2270
+ });
2271
+ program2.command("health").description("Show server health and DB stats").action(async () => {
2272
+ const { getHealth } = await import("../health-f2qrebqc.js");
2273
+ const h = getHealth(getDb());
2274
+ console.log(JSON.stringify(h, null, 2));
2275
+ });
2243
2276
  program2.command("mcp").description("Start the MCP server").option("--claude", "Install into Claude Code").option("--codex", "Install into Codex").option("--gemini", "Install into Gemini").action(async (opts) => {
2244
2277
  if (opts.claude || opts.codex || opts.gemini) {
2245
2278
  const bin = process.execPath;
package/dist/mcp/index.js CHANGED
@@ -1,27 +1,37 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  import {
4
+ createAlertRule,
4
5
  createPage,
5
6
  createProject,
7
+ deleteAlertRule,
6
8
  getDb,
7
9
  getLatestSnapshot,
8
10
  getLogContext,
9
11
  getPerfTrend,
10
12
  ingestLog,
13
+ listAlertRules,
14
+ listIssues,
11
15
  listPages,
12
16
  listProjects,
13
17
  scoreLabel,
14
18
  searchLogs,
15
19
  summarizeLogs,
16
- tailLogs
17
- } from "../index-zj6ymcv7.js";
20
+ tailLogs,
21
+ updateIssueStatus
22
+ } from "../index-qmsvtxax.js";
18
23
  import {
19
- __commonJS,
20
- __export,
21
- __toESM,
22
24
  createJob,
23
25
  listJobs
24
- } from "../index-4mnved04.js";
26
+ } from "../jobs-124e878j.js";
27
+ import {
28
+ getHealth
29
+ } from "../health-f2qrebqc.js";
30
+ import {
31
+ __commonJS,
32
+ __export,
33
+ __toESM
34
+ } from "../index-g8dczzvv.js";
25
35
 
26
36
  // node_modules/ajv/dist/compile/codegen/code.js
27
37
  var require_code = __commonJS((exports) => {
@@ -6282,7 +6292,7 @@ var require_formats = __commonJS((exports) => {
6282
6292
  }
6283
6293
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
6284
6294
  function getTime(strictTimeZone) {
6285
- return function time3(str) {
6295
+ return function time(str) {
6286
6296
  const matches = TIME.exec(str);
6287
6297
  if (!matches)
6288
6298
  return false;
@@ -28368,25 +28378,155 @@ class StdioServerTransport {
28368
28378
  }
28369
28379
  }
28370
28380
 
28381
+ // src/lib/diagnose.ts
28382
+ function diagnose(db, projectId, since) {
28383
+ const window = since ?? new Date(Date.now() - 24 * 3600 * 1000).toISOString();
28384
+ const top_errors = db.prepare(`
28385
+ SELECT message, COUNT(*) as count, service, MAX(timestamp) as last_seen
28386
+ FROM logs
28387
+ WHERE project_id = $p AND level IN ('error','fatal') AND timestamp >= $since
28388
+ GROUP BY message, service
28389
+ ORDER BY count DESC
28390
+ LIMIT 10
28391
+ `).all({ $p: projectId, $since: window });
28392
+ const error_rate_by_service = db.prepare(`
28393
+ SELECT service,
28394
+ SUM(CASE WHEN level IN ('error','fatal') THEN 1 ELSE 0 END) as errors,
28395
+ SUM(CASE WHEN level = 'warn' THEN 1 ELSE 0 END) as warns,
28396
+ COUNT(*) as total
28397
+ FROM logs
28398
+ WHERE project_id = $p AND timestamp >= $since
28399
+ GROUP BY service
28400
+ ORDER BY errors DESC
28401
+ `).all({ $p: projectId, $since: window });
28402
+ const failing_pages = db.prepare(`
28403
+ SELECT l.page_id, p.url, COUNT(*) as error_count
28404
+ FROM logs l
28405
+ JOIN pages p ON p.id = l.page_id
28406
+ WHERE l.project_id = $p AND l.level IN ('error','fatal') AND l.timestamp >= $since AND l.page_id IS NOT NULL
28407
+ GROUP BY l.page_id, p.url
28408
+ ORDER BY error_count DESC
28409
+ LIMIT 10
28410
+ `).all({ $p: projectId, $since: window });
28411
+ const perf_regressions = db.prepare(`
28412
+ SELECT * FROM (
28413
+ SELECT
28414
+ cur.page_id,
28415
+ p.url,
28416
+ cur.score as score_now,
28417
+ prev.score as score_prev,
28418
+ (cur.score - prev.score) as delta
28419
+ FROM performance_snapshots cur
28420
+ JOIN pages p ON p.id = cur.page_id
28421
+ LEFT JOIN performance_snapshots prev ON prev.page_id = cur.page_id AND prev.id != cur.id
28422
+ WHERE cur.project_id = $p
28423
+ AND cur.timestamp = (SELECT MAX(timestamp) FROM performance_snapshots WHERE page_id = cur.page_id)
28424
+ AND (prev.timestamp = (SELECT MAX(timestamp) FROM performance_snapshots WHERE page_id = cur.page_id AND id != cur.id) OR prev.id IS NULL)
28425
+ ) WHERE delta < -5 OR delta IS NULL
28426
+ ORDER BY delta ASC
28427
+ LIMIT 10
28428
+ `).all({ $p: projectId });
28429
+ const totalErrors = top_errors.reduce((s, e) => s + e.count, 0);
28430
+ const topService = error_rate_by_service[0];
28431
+ const summary = totalErrors === 0 ? "No errors in this window. All looks good." : `${totalErrors} error(s) detected. Worst service: ${topService?.service ?? "unknown"} (${topService?.errors ?? 0} errors). ${failing_pages.length} page(s) with errors. ${perf_regressions.length} perf regression(s).`;
28432
+ return { project_id: projectId, window, top_errors, error_rate_by_service, failing_pages, perf_regressions, summary };
28433
+ }
28434
+
28435
+ // src/lib/compare.ts
28436
+ function getErrorsByMessage(db, projectId, since, until) {
28437
+ return db.prepare(`
28438
+ SELECT message, service, COUNT(*) as count
28439
+ FROM logs
28440
+ WHERE project_id = $p AND level IN ('error','fatal') AND timestamp >= $since AND timestamp <= $until
28441
+ GROUP BY message, service
28442
+ `).all({ $p: projectId, $since: since, $until: until });
28443
+ }
28444
+ function getErrorsByService(db, projectId, since, until) {
28445
+ return db.prepare(`
28446
+ SELECT service, COUNT(*) as errors
28447
+ FROM logs
28448
+ WHERE project_id = $p AND level IN ('error','fatal') AND timestamp >= $since AND timestamp <= $until
28449
+ GROUP BY service
28450
+ `).all({ $p: projectId, $since: since, $until: until });
28451
+ }
28452
+ function compare(db, projectId, aSince, aUntil, bSince, bUntil) {
28453
+ const errorsA = getErrorsByMessage(db, projectId, aSince, aUntil);
28454
+ const errorsB = getErrorsByMessage(db, projectId, bSince, bUntil);
28455
+ const keyA = new Set(errorsA.map((e) => `${e.service}|${e.message}`));
28456
+ const keyB = new Set(errorsB.map((e) => `${e.service}|${e.message}`));
28457
+ const new_errors = errorsB.filter((e) => !keyA.has(`${e.service}|${e.message}`));
28458
+ const resolved_errors = errorsA.filter((e) => !keyB.has(`${e.service}|${e.message}`));
28459
+ const svcA = getErrorsByService(db, projectId, aSince, aUntil);
28460
+ const svcB = getErrorsByService(db, projectId, bSince, bUntil);
28461
+ const svcMapA = new Map(svcA.map((s) => [s.service, s.errors]));
28462
+ const svcMapB = new Map(svcB.map((s) => [s.service, s.errors]));
28463
+ const allSvcs = new Set([...svcMapA.keys(), ...svcMapB.keys()]);
28464
+ const error_delta_by_service = [...allSvcs].map((svc) => ({
28465
+ service: svc,
28466
+ errors_a: svcMapA.get(svc) ?? 0,
28467
+ errors_b: svcMapB.get(svc) ?? 0,
28468
+ delta: (svcMapB.get(svc) ?? 0) - (svcMapA.get(svc) ?? 0)
28469
+ })).sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
28470
+ const perf_delta_by_page = db.prepare(`
28471
+ SELECT
28472
+ pa.page_id, pg.url,
28473
+ pa.score as score_a,
28474
+ pb.score as score_b,
28475
+ (pb.score - pa.score) as delta
28476
+ FROM
28477
+ (SELECT page_id, AVG(score) as score FROM performance_snapshots WHERE project_id = $p AND timestamp >= $as AND timestamp <= $au GROUP BY page_id) pa
28478
+ JOIN pages pg ON pg.id = pa.page_id
28479
+ LEFT JOIN (SELECT page_id, AVG(score) as score FROM performance_snapshots WHERE project_id = $p AND timestamp >= $bs AND timestamp <= $bu GROUP BY page_id) pb ON pb.page_id = pa.page_id
28480
+ ORDER BY delta ASC
28481
+ `).all({ $p: projectId, $as: aSince, $au: aUntil, $bs: bSince, $bu: bUntil });
28482
+ const summary = [
28483
+ `${new_errors.length} new error type(s), ${resolved_errors.length} resolved.`,
28484
+ error_delta_by_service.filter((s) => s.delta > 0).map((s) => `${s.service ?? "unknown"}: +${s.delta}`).join(", ") || "No error increases."
28485
+ ].join(" ");
28486
+ return {
28487
+ project_id: projectId,
28488
+ window_a: { since: aSince, until: aUntil },
28489
+ window_b: { since: bSince, until: bUntil },
28490
+ new_errors,
28491
+ resolved_errors,
28492
+ error_delta_by_service,
28493
+ perf_delta_by_page,
28494
+ summary
28495
+ };
28496
+ }
28497
+
28371
28498
  // src/mcp/index.ts
28372
28499
  var db = getDb();
28373
- var server = new McpServer({ name: "logs", version: "0.0.1" });
28500
+ var server = new McpServer({ name: "logs", version: "0.1.0" });
28501
+ function applyBrief(rows, brief = true) {
28502
+ if (!brief)
28503
+ return rows;
28504
+ return rows.map((r) => ({ id: r.id, timestamp: r.timestamp, level: r.level, message: r.message, service: r.service }));
28505
+ }
28374
28506
  var TOOLS = {
28375
28507
  register_project: "Register a project (name, github_repo?, base_url?, description?)",
28376
- register_page: "Register a page URL to a project (project_id, url, path?, name?)",
28377
- create_scan_job: "Schedule headless page scans (project_id, schedule, page_id?)",
28508
+ register_page: "Register a page URL (project_id, url, path?, name?)",
28509
+ create_scan_job: "Schedule page scans (project_id, schedule, page_id?)",
28378
28510
  log_push: "Push a log entry (level, message, project_id?, service?, trace_id?, metadata?)",
28379
- log_search: "Search logs (project_id?, page_id?, level?, since?, until?, text?, limit?)",
28380
- log_tail: "Get N most recent logs (project_id?, n?)",
28381
- log_summary: "Error/warn counts by service/page (project_id?, since?)",
28382
- log_context: "All logs for a trace_id",
28383
- perf_snapshot: "Latest performance snapshot for a project/page (project_id, page_id?)",
28384
- perf_trend: "Performance over time (project_id, page_id?, since?, limit?)",
28385
- scan_status: "Last scan runs per project (project_id?)",
28386
- list_projects: "List all registered projects",
28511
+ log_search: "Search logs (project_id?, level?, since?, text?, brief?=true, limit?)",
28512
+ log_tail: "Recent logs (project_id?, n?, brief?=true)",
28513
+ log_summary: "Error/warn counts by service (project_id?, since?)",
28514
+ log_context: "All logs for a trace_id (trace_id, brief?=true)",
28515
+ log_diagnose: "Full diagnosis: top errors, failing pages, perf regressions (project_id, since?)",
28516
+ log_compare: "Compare two time windows for new/resolved errors and perf delta",
28517
+ perf_snapshot: "Latest perf snapshot (project_id, page_id?)",
28518
+ perf_trend: "Perf over time (project_id, page_id?, since?, limit?)",
28519
+ scan_status: "Last scan jobs (project_id?)",
28520
+ list_projects: "List all projects",
28387
28521
  list_pages: "List pages for a project (project_id)",
28388
- search_tools: "Search available tools by keyword (query)",
28389
- describe_tools: "List all tools with descriptions"
28522
+ list_issues: "List grouped error issues (project_id?, status?, limit?)",
28523
+ resolve_issue: "Update issue status (id, status: open|resolved|ignored)",
28524
+ create_alert_rule: "Create alert rule (project_id, name, level, threshold_count, window_seconds, webhook_url?)",
28525
+ list_alert_rules: "List alert rules (project_id?)",
28526
+ delete_alert_rule: "Delete alert rule (id)",
28527
+ get_health: "Server health + DB stats",
28528
+ search_tools: "Search tools by keyword (query)",
28529
+ describe_tools: "List all tools"
28390
28530
  };
28391
28531
  server.tool("search_tools", { query: exports_external.string() }, ({ query }) => {
28392
28532
  const q = query.toLowerCase();
@@ -28394,37 +28534,27 @@ server.tool("search_tools", { query: exports_external.string() }, ({ query }) =>
28394
28534
  return { content: [{ type: "text", text: matches.map(([k, v]) => `${k}: ${v}`).join(`
28395
28535
  `) || "No matches" }] };
28396
28536
  });
28397
- server.tool("describe_tools", {}, () => {
28398
- const text = Object.entries(TOOLS).map(([k, v]) => `${k}: ${v}`).join(`
28399
- `);
28400
- return { content: [{ type: "text", text }] };
28401
- });
28537
+ server.tool("describe_tools", {}, () => ({
28538
+ content: [{ type: "text", text: Object.entries(TOOLS).map(([k, v]) => `${k}: ${v}`).join(`
28539
+ `) }]
28540
+ }));
28402
28541
  server.tool("register_project", {
28403
28542
  name: exports_external.string(),
28404
28543
  github_repo: exports_external.string().optional(),
28405
28544
  base_url: exports_external.string().optional(),
28406
28545
  description: exports_external.string().optional()
28407
- }, (args) => {
28408
- const project = createProject(db, args);
28409
- return { content: [{ type: "text", text: JSON.stringify(project) }] };
28410
- });
28546
+ }, (args) => ({ content: [{ type: "text", text: JSON.stringify(createProject(db, args)) }] }));
28411
28547
  server.tool("register_page", {
28412
28548
  project_id: exports_external.string(),
28413
28549
  url: exports_external.string(),
28414
28550
  path: exports_external.string().optional(),
28415
28551
  name: exports_external.string().optional()
28416
- }, (args) => {
28417
- const page = createPage(db, args);
28418
- return { content: [{ type: "text", text: JSON.stringify(page) }] };
28419
- });
28552
+ }, (args) => ({ content: [{ type: "text", text: JSON.stringify(createPage(db, args)) }] }));
28420
28553
  server.tool("create_scan_job", {
28421
28554
  project_id: exports_external.string(),
28422
28555
  schedule: exports_external.string(),
28423
28556
  page_id: exports_external.string().optional()
28424
- }, (args) => {
28425
- const job = createJob(db, args);
28426
- return { content: [{ type: "text", text: JSON.stringify(job) }] };
28427
- });
28557
+ }, (args) => ({ content: [{ type: "text", text: JSON.stringify(createJob(db, args)) }] }));
28428
28558
  server.tool("log_push", {
28429
28559
  level: exports_external.enum(["debug", "info", "warn", "error", "fatal"]),
28430
28560
  message: exports_external.string(),
@@ -28448,60 +28578,108 @@ server.tool("log_search", {
28448
28578
  until: exports_external.string().optional(),
28449
28579
  text: exports_external.string().optional(),
28450
28580
  trace_id: exports_external.string().optional(),
28451
- limit: exports_external.number().optional()
28581
+ limit: exports_external.number().optional(),
28582
+ brief: exports_external.boolean().optional()
28452
28583
  }, (args) => {
28453
- const rows = searchLogs(db, {
28454
- ...args,
28455
- level: args.level ? args.level.split(",") : undefined
28456
- });
28457
- return { content: [{ type: "text", text: JSON.stringify(rows) }] };
28584
+ const rows = searchLogs(db, { ...args, level: args.level ? args.level.split(",") : undefined });
28585
+ return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, args.brief !== false)) }] };
28458
28586
  });
28459
28587
  server.tool("log_tail", {
28460
28588
  project_id: exports_external.string().optional(),
28461
- n: exports_external.number().optional()
28462
- }, ({ project_id, n }) => {
28589
+ n: exports_external.number().optional(),
28590
+ brief: exports_external.boolean().optional()
28591
+ }, ({ project_id, n, brief }) => {
28463
28592
  const rows = tailLogs(db, project_id, n ?? 50);
28464
- return { content: [{ type: "text", text: JSON.stringify(rows) }] };
28593
+ return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, brief !== false)) }] };
28465
28594
  });
28466
28595
  server.tool("log_summary", {
28467
28596
  project_id: exports_external.string().optional(),
28468
28597
  since: exports_external.string().optional()
28469
- }, ({ project_id, since }) => {
28470
- const summary = summarizeLogs(db, project_id, since);
28471
- return { content: [{ type: "text", text: JSON.stringify(summary) }] };
28472
- });
28473
- server.tool("log_context", { trace_id: exports_external.string() }, ({ trace_id }) => {
28598
+ }, ({ project_id, since }) => ({
28599
+ content: [{ type: "text", text: JSON.stringify(summarizeLogs(db, project_id, since)) }]
28600
+ }));
28601
+ server.tool("log_context", {
28602
+ trace_id: exports_external.string(),
28603
+ brief: exports_external.boolean().optional()
28604
+ }, ({ trace_id, brief }) => {
28474
28605
  const rows = getLogContext(db, trace_id);
28475
- return { content: [{ type: "text", text: JSON.stringify(rows) }] };
28606
+ return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, brief !== false)) }] };
28476
28607
  });
28608
+ server.tool("log_diagnose", {
28609
+ project_id: exports_external.string(),
28610
+ since: exports_external.string().optional()
28611
+ }, ({ project_id, since }) => ({
28612
+ content: [{ type: "text", text: JSON.stringify(diagnose(db, project_id, since)) }]
28613
+ }));
28614
+ server.tool("log_compare", {
28615
+ project_id: exports_external.string(),
28616
+ a_since: exports_external.string(),
28617
+ a_until: exports_external.string(),
28618
+ b_since: exports_external.string(),
28619
+ b_until: exports_external.string()
28620
+ }, ({ project_id, a_since, a_until, b_since, b_until }) => ({
28621
+ content: [{ type: "text", text: JSON.stringify(compare(db, project_id, a_since, a_until, b_since, b_until)) }]
28622
+ }));
28477
28623
  server.tool("perf_snapshot", {
28478
28624
  project_id: exports_external.string(),
28479
28625
  page_id: exports_external.string().optional()
28480
28626
  }, ({ project_id, page_id }) => {
28481
28627
  const snap = getLatestSnapshot(db, project_id, page_id);
28482
- const label = snap ? scoreLabel(snap.score) : "unknown";
28483
- return { content: [{ type: "text", text: JSON.stringify({ ...snap, label }) }] };
28628
+ return { content: [{ type: "text", text: JSON.stringify(snap ? { ...snap, label: scoreLabel(snap.score) } : null) }] };
28484
28629
  });
28485
28630
  server.tool("perf_trend", {
28486
28631
  project_id: exports_external.string(),
28487
28632
  page_id: exports_external.string().optional(),
28488
28633
  since: exports_external.string().optional(),
28489
28634
  limit: exports_external.number().optional()
28490
- }, ({ project_id, page_id, since, limit }) => {
28491
- const trend = getPerfTrend(db, project_id, page_id, since, limit ?? 50);
28492
- return { content: [{ type: "text", text: JSON.stringify(trend) }] };
28493
- });
28635
+ }, ({ project_id, page_id, since, limit }) => ({
28636
+ content: [{ type: "text", text: JSON.stringify(getPerfTrend(db, project_id, page_id, since, limit ?? 50)) }]
28637
+ }));
28494
28638
  server.tool("scan_status", {
28495
28639
  project_id: exports_external.string().optional()
28496
- }, ({ project_id }) => {
28497
- const jobs = listJobs(db, project_id);
28498
- return { content: [{ type: "text", text: JSON.stringify(jobs) }] };
28499
- });
28500
- server.tool("list_projects", {}, () => {
28501
- return { content: [{ type: "text", text: JSON.stringify(listProjects(db)) }] };
28502
- });
28503
- server.tool("list_pages", { project_id: exports_external.string() }, ({ project_id }) => {
28504
- return { content: [{ type: "text", text: JSON.stringify(listPages(db, project_id)) }] };
28640
+ }, ({ project_id }) => ({
28641
+ content: [{ type: "text", text: JSON.stringify(listJobs(db, project_id)) }]
28642
+ }));
28643
+ server.tool("list_projects", {}, () => ({
28644
+ content: [{ type: "text", text: JSON.stringify(listProjects(db)) }]
28645
+ }));
28646
+ server.tool("list_pages", { project_id: exports_external.string() }, ({ project_id }) => ({
28647
+ content: [{ type: "text", text: JSON.stringify(listPages(db, project_id)) }]
28648
+ }));
28649
+ server.tool("list_issues", {
28650
+ project_id: exports_external.string().optional(),
28651
+ status: exports_external.string().optional(),
28652
+ limit: exports_external.number().optional()
28653
+ }, ({ project_id, status, limit }) => ({
28654
+ content: [{ type: "text", text: JSON.stringify(listIssues(db, project_id, status, limit ?? 50)) }]
28655
+ }));
28656
+ server.tool("resolve_issue", {
28657
+ id: exports_external.string(),
28658
+ status: exports_external.enum(["open", "resolved", "ignored"])
28659
+ }, ({ id, status }) => ({
28660
+ content: [{ type: "text", text: JSON.stringify(updateIssueStatus(db, id, status)) }]
28661
+ }));
28662
+ server.tool("create_alert_rule", {
28663
+ project_id: exports_external.string(),
28664
+ name: exports_external.string(),
28665
+ level: exports_external.string().optional(),
28666
+ service: exports_external.string().optional(),
28667
+ threshold_count: exports_external.number().optional(),
28668
+ window_seconds: exports_external.number().optional(),
28669
+ action: exports_external.enum(["webhook", "log"]).optional(),
28670
+ webhook_url: exports_external.string().optional()
28671
+ }, (args) => ({ content: [{ type: "text", text: JSON.stringify(createAlertRule(db, args)) }] }));
28672
+ server.tool("list_alert_rules", {
28673
+ project_id: exports_external.string().optional()
28674
+ }, ({ project_id }) => ({
28675
+ content: [{ type: "text", text: JSON.stringify(listAlertRules(db, project_id)) }]
28676
+ }));
28677
+ server.tool("delete_alert_rule", { id: exports_external.string() }, ({ id }) => {
28678
+ deleteAlertRule(db, id);
28679
+ return { content: [{ type: "text", text: "deleted" }] };
28505
28680
  });
28681
+ server.tool("get_health", {}, () => ({
28682
+ content: [{ type: "text", text: JSON.stringify(getHealth(db)) }]
28683
+ }));
28506
28684
  var transport = new StdioServerTransport;
28507
28685
  await server.connect(transport);