@hasna/logs 0.1.0 → 0.3.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/dashboard/README.md +73 -0
- package/dashboard/bun.lock +526 -0
- package/dashboard/eslint.config.js +23 -0
- package/dashboard/index.html +13 -0
- package/dashboard/package.json +32 -0
- package/dashboard/public/favicon.svg +1 -0
- package/dashboard/public/icons.svg +24 -0
- package/dashboard/src/App.css +184 -0
- package/dashboard/src/App.tsx +49 -0
- package/dashboard/src/api.ts +33 -0
- package/dashboard/src/assets/hero.png +0 -0
- package/dashboard/src/assets/react.svg +1 -0
- package/dashboard/src/assets/vite.svg +1 -0
- package/dashboard/src/index.css +111 -0
- package/dashboard/src/main.tsx +10 -0
- package/dashboard/src/pages/Alerts.tsx +69 -0
- package/dashboard/src/pages/Issues.tsx +50 -0
- package/dashboard/src/pages/Perf.tsx +75 -0
- package/dashboard/src/pages/Projects.tsx +67 -0
- package/dashboard/src/pages/Summary.tsx +67 -0
- package/dashboard/src/pages/Tail.tsx +65 -0
- package/dashboard/tsconfig.app.json +28 -0
- package/dashboard/tsconfig.json +7 -0
- package/dashboard/tsconfig.node.json +26 -0
- package/dashboard/vite.config.ts +14 -0
- package/dist/cli/index.js +80 -9
- package/dist/mcp/index.js +217 -96
- package/dist/server/index.js +307 -7
- package/package.json +3 -1
- package/sdk/package.json +3 -2
- package/sdk/src/index.ts +1 -1
- package/sdk/src/types.ts +56 -0
- package/src/cli/index.ts +70 -4
- package/src/lib/count.test.ts +44 -0
- package/src/lib/count.ts +45 -0
- package/src/lib/diagnose.ts +26 -11
- package/src/lib/parse-time.test.ts +37 -0
- package/src/lib/parse-time.ts +14 -0
- package/src/lib/projects.ts +10 -0
- package/src/lib/query.ts +10 -2
- package/src/lib/session-context.ts +28 -0
- package/src/lib/summarize.ts +2 -1
- package/src/mcp/index.ts +138 -59
- package/src/server/index.ts +4 -1
- package/src/server/routes/logs.ts +28 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
import {
|
|
4
|
+
countLogs
|
|
5
|
+
} from "../index-n8qd55mt.js";
|
|
3
6
|
import {
|
|
4
7
|
createAlertRule,
|
|
5
8
|
createPage,
|
|
@@ -7,23 +10,29 @@ import {
|
|
|
7
10
|
deleteAlertRule,
|
|
8
11
|
getDb,
|
|
9
12
|
getLatestSnapshot,
|
|
10
|
-
getLogContext,
|
|
11
13
|
getPerfTrend,
|
|
14
|
+
ingestBatch,
|
|
12
15
|
ingestLog,
|
|
13
16
|
listAlertRules,
|
|
14
17
|
listIssues,
|
|
15
18
|
listPages,
|
|
16
19
|
listProjects,
|
|
20
|
+
resolveProjectId,
|
|
17
21
|
scoreLabel,
|
|
18
|
-
searchLogs,
|
|
19
22
|
summarizeLogs,
|
|
20
|
-
tailLogs,
|
|
21
23
|
updateIssueStatus
|
|
22
|
-
} from "../index-
|
|
24
|
+
} from "../index-34xx795x.js";
|
|
23
25
|
import {
|
|
24
26
|
createJob,
|
|
25
27
|
listJobs
|
|
26
28
|
} from "../jobs-124e878j.js";
|
|
29
|
+
import {
|
|
30
|
+
getLogContext,
|
|
31
|
+
getLogContextFromId,
|
|
32
|
+
parseTime,
|
|
33
|
+
searchLogs,
|
|
34
|
+
tailLogs
|
|
35
|
+
} from "../query-d5b0chp4.js";
|
|
27
36
|
import {
|
|
28
37
|
getHealth
|
|
29
38
|
} from "../health-f2qrebqc.js";
|
|
@@ -10332,7 +10341,7 @@ __export(exports_core2, {
|
|
|
10332
10341
|
safeDecode: () => safeDecode,
|
|
10333
10342
|
registry: () => registry,
|
|
10334
10343
|
regexes: () => exports_regexes,
|
|
10335
|
-
process: () =>
|
|
10344
|
+
process: () => process2,
|
|
10336
10345
|
prettifyError: () => prettifyError,
|
|
10337
10346
|
parseAsync: () => parseAsync,
|
|
10338
10347
|
parse: () => parse,
|
|
@@ -20796,7 +20805,7 @@ function initializeContext(params) {
|
|
|
20796
20805
|
external: params?.external ?? undefined
|
|
20797
20806
|
};
|
|
20798
20807
|
}
|
|
20799
|
-
function
|
|
20808
|
+
function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
|
|
20800
20809
|
var _a2;
|
|
20801
20810
|
const def = schema._zod.def;
|
|
20802
20811
|
const seen = ctx.seen.get(schema);
|
|
@@ -20833,7 +20842,7 @@ function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
|
|
|
20833
20842
|
if (parent) {
|
|
20834
20843
|
if (!result.ref)
|
|
20835
20844
|
result.ref = parent;
|
|
20836
|
-
|
|
20845
|
+
process2(parent, ctx, params);
|
|
20837
20846
|
ctx.seen.get(parent).isParent = true;
|
|
20838
20847
|
}
|
|
20839
20848
|
}
|
|
@@ -21109,14 +21118,14 @@ function isTransforming(_schema, _ctx) {
|
|
|
21109
21118
|
}
|
|
21110
21119
|
var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
|
|
21111
21120
|
const ctx = initializeContext({ ...params, processors });
|
|
21112
|
-
|
|
21121
|
+
process2(schema, ctx);
|
|
21113
21122
|
extractDefs(ctx, schema);
|
|
21114
21123
|
return finalize(ctx, schema);
|
|
21115
21124
|
};
|
|
21116
21125
|
var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
|
|
21117
21126
|
const { libraryOptions, target } = params ?? {};
|
|
21118
21127
|
const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
|
|
21119
|
-
|
|
21128
|
+
process2(schema, ctx);
|
|
21120
21129
|
extractDefs(ctx, schema);
|
|
21121
21130
|
return finalize(ctx, schema);
|
|
21122
21131
|
};
|
|
@@ -21367,7 +21376,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
|
|
|
21367
21376
|
if (typeof maximum === "number")
|
|
21368
21377
|
json.maxItems = maximum;
|
|
21369
21378
|
json.type = "array";
|
|
21370
|
-
json.items =
|
|
21379
|
+
json.items = process2(def.element, ctx, { ...params, path: [...params.path, "items"] });
|
|
21371
21380
|
};
|
|
21372
21381
|
var objectProcessor = (schema, ctx, _json, params) => {
|
|
21373
21382
|
const json = _json;
|
|
@@ -21376,7 +21385,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
21376
21385
|
json.properties = {};
|
|
21377
21386
|
const shape = def.shape;
|
|
21378
21387
|
for (const key in shape) {
|
|
21379
|
-
json.properties[key] =
|
|
21388
|
+
json.properties[key] = process2(shape[key], ctx, {
|
|
21380
21389
|
...params,
|
|
21381
21390
|
path: [...params.path, "properties", key]
|
|
21382
21391
|
});
|
|
@@ -21399,7 +21408,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
21399
21408
|
if (ctx.io === "output")
|
|
21400
21409
|
json.additionalProperties = false;
|
|
21401
21410
|
} else if (def.catchall) {
|
|
21402
|
-
json.additionalProperties =
|
|
21411
|
+
json.additionalProperties = process2(def.catchall, ctx, {
|
|
21403
21412
|
...params,
|
|
21404
21413
|
path: [...params.path, "additionalProperties"]
|
|
21405
21414
|
});
|
|
@@ -21408,7 +21417,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
21408
21417
|
var unionProcessor = (schema, ctx, json, params) => {
|
|
21409
21418
|
const def = schema._zod.def;
|
|
21410
21419
|
const isExclusive = def.inclusive === false;
|
|
21411
|
-
const options = def.options.map((x, i) =>
|
|
21420
|
+
const options = def.options.map((x, i) => process2(x, ctx, {
|
|
21412
21421
|
...params,
|
|
21413
21422
|
path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
|
|
21414
21423
|
}));
|
|
@@ -21420,11 +21429,11 @@ var unionProcessor = (schema, ctx, json, params) => {
|
|
|
21420
21429
|
};
|
|
21421
21430
|
var intersectionProcessor = (schema, ctx, json, params) => {
|
|
21422
21431
|
const def = schema._zod.def;
|
|
21423
|
-
const a =
|
|
21432
|
+
const a = process2(def.left, ctx, {
|
|
21424
21433
|
...params,
|
|
21425
21434
|
path: [...params.path, "allOf", 0]
|
|
21426
21435
|
});
|
|
21427
|
-
const b =
|
|
21436
|
+
const b = process2(def.right, ctx, {
|
|
21428
21437
|
...params,
|
|
21429
21438
|
path: [...params.path, "allOf", 1]
|
|
21430
21439
|
});
|
|
@@ -21441,11 +21450,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
21441
21450
|
json.type = "array";
|
|
21442
21451
|
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
21443
21452
|
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
21444
|
-
const prefixItems = def.items.map((x, i) =>
|
|
21453
|
+
const prefixItems = def.items.map((x, i) => process2(x, ctx, {
|
|
21445
21454
|
...params,
|
|
21446
21455
|
path: [...params.path, prefixPath, i]
|
|
21447
21456
|
}));
|
|
21448
|
-
const rest = def.rest ?
|
|
21457
|
+
const rest = def.rest ? process2(def.rest, ctx, {
|
|
21449
21458
|
...params,
|
|
21450
21459
|
path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
|
|
21451
21460
|
}) : null;
|
|
@@ -21485,7 +21494,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
|
|
|
21485
21494
|
const keyBag = keyType._zod.bag;
|
|
21486
21495
|
const patterns = keyBag?.patterns;
|
|
21487
21496
|
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
21488
|
-
const valueSchema =
|
|
21497
|
+
const valueSchema = process2(def.valueType, ctx, {
|
|
21489
21498
|
...params,
|
|
21490
21499
|
path: [...params.path, "patternProperties", "*"]
|
|
21491
21500
|
});
|
|
@@ -21495,12 +21504,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
|
|
|
21495
21504
|
}
|
|
21496
21505
|
} else {
|
|
21497
21506
|
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
|
|
21498
|
-
json.propertyNames =
|
|
21507
|
+
json.propertyNames = process2(def.keyType, ctx, {
|
|
21499
21508
|
...params,
|
|
21500
21509
|
path: [...params.path, "propertyNames"]
|
|
21501
21510
|
});
|
|
21502
21511
|
}
|
|
21503
|
-
json.additionalProperties =
|
|
21512
|
+
json.additionalProperties = process2(def.valueType, ctx, {
|
|
21504
21513
|
...params,
|
|
21505
21514
|
path: [...params.path, "additionalProperties"]
|
|
21506
21515
|
});
|
|
@@ -21515,7 +21524,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
|
|
|
21515
21524
|
};
|
|
21516
21525
|
var nullableProcessor = (schema, ctx, json, params) => {
|
|
21517
21526
|
const def = schema._zod.def;
|
|
21518
|
-
const inner =
|
|
21527
|
+
const inner = process2(def.innerType, ctx, params);
|
|
21519
21528
|
const seen = ctx.seen.get(schema);
|
|
21520
21529
|
if (ctx.target === "openapi-3.0") {
|
|
21521
21530
|
seen.ref = def.innerType;
|
|
@@ -21526,20 +21535,20 @@ var nullableProcessor = (schema, ctx, json, params) => {
|
|
|
21526
21535
|
};
|
|
21527
21536
|
var nonoptionalProcessor = (schema, ctx, _json, params) => {
|
|
21528
21537
|
const def = schema._zod.def;
|
|
21529
|
-
|
|
21538
|
+
process2(def.innerType, ctx, params);
|
|
21530
21539
|
const seen = ctx.seen.get(schema);
|
|
21531
21540
|
seen.ref = def.innerType;
|
|
21532
21541
|
};
|
|
21533
21542
|
var defaultProcessor = (schema, ctx, json, params) => {
|
|
21534
21543
|
const def = schema._zod.def;
|
|
21535
|
-
|
|
21544
|
+
process2(def.innerType, ctx, params);
|
|
21536
21545
|
const seen = ctx.seen.get(schema);
|
|
21537
21546
|
seen.ref = def.innerType;
|
|
21538
21547
|
json.default = JSON.parse(JSON.stringify(def.defaultValue));
|
|
21539
21548
|
};
|
|
21540
21549
|
var prefaultProcessor = (schema, ctx, json, params) => {
|
|
21541
21550
|
const def = schema._zod.def;
|
|
21542
|
-
|
|
21551
|
+
process2(def.innerType, ctx, params);
|
|
21543
21552
|
const seen = ctx.seen.get(schema);
|
|
21544
21553
|
seen.ref = def.innerType;
|
|
21545
21554
|
if (ctx.io === "input")
|
|
@@ -21547,7 +21556,7 @@ var prefaultProcessor = (schema, ctx, json, params) => {
|
|
|
21547
21556
|
};
|
|
21548
21557
|
var catchProcessor = (schema, ctx, json, params) => {
|
|
21549
21558
|
const def = schema._zod.def;
|
|
21550
|
-
|
|
21559
|
+
process2(def.innerType, ctx, params);
|
|
21551
21560
|
const seen = ctx.seen.get(schema);
|
|
21552
21561
|
seen.ref = def.innerType;
|
|
21553
21562
|
let catchValue;
|
|
@@ -21561,32 +21570,32 @@ var catchProcessor = (schema, ctx, json, params) => {
|
|
|
21561
21570
|
var pipeProcessor = (schema, ctx, _json, params) => {
|
|
21562
21571
|
const def = schema._zod.def;
|
|
21563
21572
|
const innerType = ctx.io === "input" ? def.in._zod.def.type === "transform" ? def.out : def.in : def.out;
|
|
21564
|
-
|
|
21573
|
+
process2(innerType, ctx, params);
|
|
21565
21574
|
const seen = ctx.seen.get(schema);
|
|
21566
21575
|
seen.ref = innerType;
|
|
21567
21576
|
};
|
|
21568
21577
|
var readonlyProcessor = (schema, ctx, json, params) => {
|
|
21569
21578
|
const def = schema._zod.def;
|
|
21570
|
-
|
|
21579
|
+
process2(def.innerType, ctx, params);
|
|
21571
21580
|
const seen = ctx.seen.get(schema);
|
|
21572
21581
|
seen.ref = def.innerType;
|
|
21573
21582
|
json.readOnly = true;
|
|
21574
21583
|
};
|
|
21575
21584
|
var promiseProcessor = (schema, ctx, _json, params) => {
|
|
21576
21585
|
const def = schema._zod.def;
|
|
21577
|
-
|
|
21586
|
+
process2(def.innerType, ctx, params);
|
|
21578
21587
|
const seen = ctx.seen.get(schema);
|
|
21579
21588
|
seen.ref = def.innerType;
|
|
21580
21589
|
};
|
|
21581
21590
|
var optionalProcessor = (schema, ctx, _json, params) => {
|
|
21582
21591
|
const def = schema._zod.def;
|
|
21583
|
-
|
|
21592
|
+
process2(def.innerType, ctx, params);
|
|
21584
21593
|
const seen = ctx.seen.get(schema);
|
|
21585
21594
|
seen.ref = def.innerType;
|
|
21586
21595
|
};
|
|
21587
21596
|
var lazyProcessor = (schema, ctx, _json, params) => {
|
|
21588
21597
|
const innerType = schema._zod.innerType;
|
|
21589
|
-
|
|
21598
|
+
process2(innerType, ctx, params);
|
|
21590
21599
|
const seen = ctx.seen.get(schema);
|
|
21591
21600
|
seen.ref = innerType;
|
|
21592
21601
|
};
|
|
@@ -21638,7 +21647,7 @@ function toJSONSchema(input, params) {
|
|
|
21638
21647
|
const defs = {};
|
|
21639
21648
|
for (const entry of registry2._idmap.entries()) {
|
|
21640
21649
|
const [_, schema] = entry;
|
|
21641
|
-
|
|
21650
|
+
process2(schema, ctx2);
|
|
21642
21651
|
}
|
|
21643
21652
|
const schemas = {};
|
|
21644
21653
|
const external = {
|
|
@@ -21661,7 +21670,7 @@ function toJSONSchema(input, params) {
|
|
|
21661
21670
|
return { schemas };
|
|
21662
21671
|
}
|
|
21663
21672
|
const ctx = initializeContext({ ...params, processors: allProcessors });
|
|
21664
|
-
|
|
21673
|
+
process2(input, ctx);
|
|
21665
21674
|
extractDefs(ctx, input);
|
|
21666
21675
|
return finalize(ctx, input);
|
|
21667
21676
|
}
|
|
@@ -21707,7 +21716,7 @@ class JSONSchemaGenerator {
|
|
|
21707
21716
|
});
|
|
21708
21717
|
}
|
|
21709
21718
|
process(schema, _params = { path: [], schemaPath: [] }) {
|
|
21710
|
-
return
|
|
21719
|
+
return process2(schema, this.ctx, _params);
|
|
21711
21720
|
}
|
|
21712
21721
|
emit(schema, _params) {
|
|
21713
21722
|
if (_params) {
|
|
@@ -28288,7 +28297,7 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
28288
28297
|
};
|
|
28289
28298
|
|
|
28290
28299
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
28291
|
-
import
|
|
28300
|
+
import process3 from "process";
|
|
28292
28301
|
|
|
28293
28302
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
28294
28303
|
class ReadBuffer {
|
|
@@ -28322,7 +28331,7 @@ function serializeMessage(message) {
|
|
|
28322
28331
|
|
|
28323
28332
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
28324
28333
|
class StdioServerTransport {
|
|
28325
|
-
constructor(_stdin =
|
|
28334
|
+
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
28326
28335
|
this._stdin = _stdin;
|
|
28327
28336
|
this._stdout = _stdout;
|
|
28328
28337
|
this._readBuffer = new ReadBuffer;
|
|
@@ -28379,17 +28388,19 @@ class StdioServerTransport {
|
|
|
28379
28388
|
}
|
|
28380
28389
|
|
|
28381
28390
|
// src/lib/diagnose.ts
|
|
28382
|
-
function diagnose(db, projectId, since) {
|
|
28383
|
-
const window = since ?? new Date(Date.now() - 24 * 3600 * 1000).toISOString();
|
|
28384
|
-
const
|
|
28391
|
+
function diagnose(db, projectId, since, include) {
|
|
28392
|
+
const window = parseTime(since) ?? since ?? new Date(Date.now() - 24 * 3600 * 1000).toISOString();
|
|
28393
|
+
const all = !include || include.length === 0;
|
|
28394
|
+
const want = (k) => all || include.includes(k);
|
|
28395
|
+
const top_errors = want("top_errors") ? db.prepare(`
|
|
28385
28396
|
SELECT message, COUNT(*) as count, service, MAX(timestamp) as last_seen
|
|
28386
28397
|
FROM logs
|
|
28387
28398
|
WHERE project_id = $p AND level IN ('error','fatal') AND timestamp >= $since
|
|
28388
28399
|
GROUP BY message, service
|
|
28389
28400
|
ORDER BY count DESC
|
|
28390
28401
|
LIMIT 10
|
|
28391
|
-
`).all({ $p: projectId, $since: window });
|
|
28392
|
-
const error_rate_by_service = db.prepare(`
|
|
28402
|
+
`).all({ $p: projectId, $since: window }) : [];
|
|
28403
|
+
const error_rate_by_service = want("error_rate") ? db.prepare(`
|
|
28393
28404
|
SELECT service,
|
|
28394
28405
|
SUM(CASE WHEN level IN ('error','fatal') THEN 1 ELSE 0 END) as errors,
|
|
28395
28406
|
SUM(CASE WHEN level = 'warn' THEN 1 ELSE 0 END) as warns,
|
|
@@ -28398,8 +28409,8 @@ function diagnose(db, projectId, since) {
|
|
|
28398
28409
|
WHERE project_id = $p AND timestamp >= $since
|
|
28399
28410
|
GROUP BY service
|
|
28400
28411
|
ORDER BY errors DESC
|
|
28401
|
-
`).all({ $p: projectId, $since: window });
|
|
28402
|
-
const failing_pages = db.prepare(`
|
|
28412
|
+
`).all({ $p: projectId, $since: window }) : [];
|
|
28413
|
+
const failing_pages = want("failing_pages") ? db.prepare(`
|
|
28403
28414
|
SELECT l.page_id, p.url, COUNT(*) as error_count
|
|
28404
28415
|
FROM logs l
|
|
28405
28416
|
JOIN pages p ON p.id = l.page_id
|
|
@@ -28407,8 +28418,8 @@ function diagnose(db, projectId, since) {
|
|
|
28407
28418
|
GROUP BY l.page_id, p.url
|
|
28408
28419
|
ORDER BY error_count DESC
|
|
28409
28420
|
LIMIT 10
|
|
28410
|
-
`).all({ $p: projectId, $since: window });
|
|
28411
|
-
const perf_regressions = db.prepare(`
|
|
28421
|
+
`).all({ $p: projectId, $since: window }) : [];
|
|
28422
|
+
const perf_regressions = want("perf") ? db.prepare(`
|
|
28412
28423
|
SELECT * FROM (
|
|
28413
28424
|
SELECT
|
|
28414
28425
|
cur.page_id,
|
|
@@ -28425,11 +28436,25 @@ function diagnose(db, projectId, since) {
|
|
|
28425
28436
|
) WHERE delta < -5 OR delta IS NULL
|
|
28426
28437
|
ORDER BY delta ASC
|
|
28427
28438
|
LIMIT 10
|
|
28428
|
-
`).all({ $p: projectId });
|
|
28439
|
+
`).all({ $p: projectId }) : [];
|
|
28429
28440
|
const totalErrors = top_errors.reduce((s, e) => s + e.count, 0);
|
|
28441
|
+
const totalWarns = error_rate_by_service.reduce((s, r) => s + r.warns, 0);
|
|
28430
28442
|
const topService = error_rate_by_service[0];
|
|
28443
|
+
const score = totalErrors === 0 ? "green" : totalErrors <= 10 ? "yellow" : "red";
|
|
28431
28444
|
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 {
|
|
28445
|
+
return {
|
|
28446
|
+
project_id: projectId,
|
|
28447
|
+
window,
|
|
28448
|
+
score,
|
|
28449
|
+
error_count: totalErrors,
|
|
28450
|
+
warn_count: totalWarns,
|
|
28451
|
+
has_perf_regression: perf_regressions.length > 0,
|
|
28452
|
+
top_errors,
|
|
28453
|
+
error_rate_by_service,
|
|
28454
|
+
failing_pages,
|
|
28455
|
+
perf_regressions,
|
|
28456
|
+
summary
|
|
28457
|
+
};
|
|
28433
28458
|
}
|
|
28434
28459
|
|
|
28435
28460
|
// src/lib/compare.ts
|
|
@@ -28495,49 +28520,91 @@ function compare(db, projectId, aSince, aUntil, bSince, bUntil) {
|
|
|
28495
28520
|
};
|
|
28496
28521
|
}
|
|
28497
28522
|
|
|
28523
|
+
// src/lib/session-context.ts
|
|
28524
|
+
async function getSessionContext(db, sessionId) {
|
|
28525
|
+
const logs = db.prepare("SELECT * FROM logs WHERE session_id = $s ORDER BY timestamp ASC").all({ $s: sessionId });
|
|
28526
|
+
const sessionsUrl = process.env.SESSIONS_URL;
|
|
28527
|
+
if (!sessionsUrl) {
|
|
28528
|
+
return { session_id: sessionId, logs };
|
|
28529
|
+
}
|
|
28530
|
+
try {
|
|
28531
|
+
const res = await fetch(`${sessionsUrl.replace(/\/$/, "")}/api/sessions/${sessionId}`);
|
|
28532
|
+
if (!res.ok)
|
|
28533
|
+
return { session_id: sessionId, logs };
|
|
28534
|
+
const session = await res.json();
|
|
28535
|
+
return { session_id: sessionId, logs, session };
|
|
28536
|
+
} catch (err) {
|
|
28537
|
+
return { session_id: sessionId, logs, error: String(err) };
|
|
28538
|
+
}
|
|
28539
|
+
}
|
|
28540
|
+
|
|
28498
28541
|
// src/mcp/index.ts
|
|
28499
28542
|
var db = getDb();
|
|
28500
|
-
var server = new McpServer({ name: "logs", version: "0.
|
|
28543
|
+
var server = new McpServer({ name: "logs", version: "0.3.0" });
|
|
28501
28544
|
function applyBrief(rows, brief = true) {
|
|
28502
28545
|
if (!brief)
|
|
28503
28546
|
return rows;
|
|
28504
|
-
return rows.map((r) => ({
|
|
28547
|
+
return rows.map((r) => ({
|
|
28548
|
+
id: r.id,
|
|
28549
|
+
timestamp: r.timestamp,
|
|
28550
|
+
level: r.level,
|
|
28551
|
+
message: r.message,
|
|
28552
|
+
service: r.service,
|
|
28553
|
+
age_seconds: Math.floor((Date.now() - new Date(r.timestamp).getTime()) / 1000)
|
|
28554
|
+
}));
|
|
28555
|
+
}
|
|
28556
|
+
function rp(idOrName) {
|
|
28557
|
+
if (!idOrName)
|
|
28558
|
+
return;
|
|
28559
|
+
return resolveProjectId(db, idOrName) ?? idOrName;
|
|
28505
28560
|
}
|
|
28506
28561
|
var TOOLS = {
|
|
28507
|
-
register_project: "Register a project (name, github_repo?, base_url?, description?)",
|
|
28508
|
-
register_page: "Register a page URL (project_id, url, path?, name?)",
|
|
28509
|
-
create_scan_job: "Schedule page scans (project_id, schedule, page_id?)",
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
|
|
28518
|
-
|
|
28519
|
-
|
|
28520
|
-
|
|
28521
|
-
|
|
28522
|
-
|
|
28523
|
-
|
|
28524
|
-
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
|
|
28529
|
-
|
|
28562
|
+
register_project: { desc: "Register a project", params: "(name, github_repo?, base_url?, description?)" },
|
|
28563
|
+
register_page: { desc: "Register a page URL to a project", params: "(project_id, url, path?, name?)" },
|
|
28564
|
+
create_scan_job: { desc: "Schedule headless page scans", params: "(project_id, schedule, page_id?)" },
|
|
28565
|
+
resolve_project: { desc: "Resolve project name to ID", params: "(name)" },
|
|
28566
|
+
log_push: { desc: "Push a single log entry", params: "(level, message, project_id?, service?, trace_id?, metadata?)" },
|
|
28567
|
+
log_push_batch: { desc: "Push multiple log entries in one call", params: "(entries: Array<{level, message, project_id?, service?, trace_id?}>)" },
|
|
28568
|
+
log_search: { desc: "Search logs", params: "(project_id?, level?, since?, until?, text?, service?, limit?=100, brief?=true)" },
|
|
28569
|
+
log_tail: { desc: "Get N most recent logs", params: "(project_id?, n?=50, brief?=true)" },
|
|
28570
|
+
log_count: { desc: "Count logs \u2014 zero token cost, pure signal", params: "(project_id?, service?, level?, since?, until?)" },
|
|
28571
|
+
log_recent_errors: { desc: "Shortcut: recent errors + fatals", params: "(project_id?, since?='1h', limit?=20)" },
|
|
28572
|
+
log_summary: { desc: "Error/warn counts by service", params: "(project_id?, since?)" },
|
|
28573
|
+
log_context: { desc: "All logs for a trace_id", params: "(trace_id, brief?=true)" },
|
|
28574
|
+
log_context_from_id: { desc: "Trace context from a log ID (no trace_id needed)", params: "(log_id, brief?=true)" },
|
|
28575
|
+
log_diagnose: { desc: "Full diagnosis: score, top errors, failing pages, perf regressions", params: "(project_id, since?='24h', include?=['top_errors','error_rate','failing_pages','perf'])" },
|
|
28576
|
+
log_compare: { desc: "Diff two time windows for new/resolved errors", params: "(project_id, a_since, a_until, b_since, b_until)" },
|
|
28577
|
+
log_session_context: { desc: "Logs + session metadata for a session_id", params: "(session_id, brief?=true)" },
|
|
28578
|
+
perf_snapshot: { desc: "Latest performance snapshot", params: "(project_id, page_id?)" },
|
|
28579
|
+
perf_trend: { desc: "Performance over time", params: "(project_id, page_id?, since?, limit?=50)" },
|
|
28580
|
+
scan_status: { desc: "Last scan jobs", params: "(project_id?)" },
|
|
28581
|
+
list_projects: { desc: "List all projects", params: "()" },
|
|
28582
|
+
list_pages: { desc: "List pages for a project", params: "(project_id)" },
|
|
28583
|
+
list_issues: { desc: "List grouped error issues", params: "(project_id?, status?, limit?=50)" },
|
|
28584
|
+
resolve_issue: { desc: "Update issue status", params: "(id, status: open|resolved|ignored)" },
|
|
28585
|
+
create_alert_rule: { desc: "Create alert rule", params: "(project_id, name, level?, threshold_count?, window_seconds?, webhook_url?)" },
|
|
28586
|
+
list_alert_rules: { desc: "List alert rules", params: "(project_id?)" },
|
|
28587
|
+
delete_alert_rule: { desc: "Delete alert rule", params: "(id)" },
|
|
28588
|
+
get_health: { desc: "Server health + DB stats", params: "()" },
|
|
28589
|
+
search_tools: { desc: "Search tools by keyword \u2014 returns names, descriptions, param signatures", params: "(query)" },
|
|
28590
|
+
describe_tools: { desc: "List all tools with descriptions and param signatures", params: "()" }
|
|
28530
28591
|
};
|
|
28531
28592
|
server.tool("search_tools", { query: exports_external.string() }, ({ query }) => {
|
|
28532
28593
|
const q = query.toLowerCase();
|
|
28533
|
-
const matches = Object.entries(TOOLS).filter(([k, v]) => k.includes(q) || v.toLowerCase().includes(q));
|
|
28534
|
-
|
|
28535
|
-
`) || "No matches"
|
|
28594
|
+
const matches = Object.entries(TOOLS).filter(([k, v]) => k.includes(q) || v.desc.toLowerCase().includes(q));
|
|
28595
|
+
const text = matches.map(([k, v]) => `${k}${v.params} \u2014 ${v.desc}`).join(`
|
|
28596
|
+
`) || "No matches";
|
|
28597
|
+
return { content: [{ type: "text", text }] };
|
|
28536
28598
|
});
|
|
28537
28599
|
server.tool("describe_tools", {}, () => ({
|
|
28538
|
-
content: [{ type: "text", text: Object.entries(TOOLS).map(([k, v]) => `${k}
|
|
28600
|
+
content: [{ type: "text", text: Object.entries(TOOLS).map(([k, v]) => `${k}${v.params} \u2014 ${v.desc}`).join(`
|
|
28539
28601
|
`) }]
|
|
28540
28602
|
}));
|
|
28603
|
+
server.tool("resolve_project", { name: exports_external.string() }, ({ name }) => {
|
|
28604
|
+
const id = resolveProjectId(db, name);
|
|
28605
|
+
const project = id ? db.prepare("SELECT * FROM projects WHERE id = $id").get({ $id: id }) : null;
|
|
28606
|
+
return { content: [{ type: "text", text: JSON.stringify(project ?? { error: `Project '${name}' not found` }) }] };
|
|
28607
|
+
});
|
|
28541
28608
|
server.tool("register_project", {
|
|
28542
28609
|
name: exports_external.string(),
|
|
28543
28610
|
github_repo: exports_external.string().optional(),
|
|
@@ -28549,12 +28616,12 @@ server.tool("register_page", {
|
|
|
28549
28616
|
url: exports_external.string(),
|
|
28550
28617
|
path: exports_external.string().optional(),
|
|
28551
28618
|
name: exports_external.string().optional()
|
|
28552
|
-
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createPage(db, args)) }] }));
|
|
28619
|
+
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createPage(db, { ...args, project_id: rp(args.project_id) ?? args.project_id })) }] }));
|
|
28553
28620
|
server.tool("create_scan_job", {
|
|
28554
28621
|
project_id: exports_external.string(),
|
|
28555
28622
|
schedule: exports_external.string(),
|
|
28556
28623
|
page_id: exports_external.string().optional()
|
|
28557
|
-
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createJob(db, args)) }] }));
|
|
28624
|
+
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createJob(db, { ...args, project_id: rp(args.project_id) ?? args.project_id })) }] }));
|
|
28558
28625
|
server.tool("log_push", {
|
|
28559
28626
|
level: exports_external.enum(["debug", "info", "warn", "error", "fatal"]),
|
|
28560
28627
|
message: exports_external.string(),
|
|
@@ -28566,9 +28633,22 @@ server.tool("log_push", {
|
|
|
28566
28633
|
url: exports_external.string().optional(),
|
|
28567
28634
|
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
28568
28635
|
}, (args) => {
|
|
28569
|
-
const row = ingestLog(db, args);
|
|
28636
|
+
const row = ingestLog(db, { ...args, project_id: rp(args.project_id) });
|
|
28570
28637
|
return { content: [{ type: "text", text: `Logged: ${row.id}` }] };
|
|
28571
28638
|
});
|
|
28639
|
+
server.tool("log_push_batch", {
|
|
28640
|
+
entries: exports_external.array(exports_external.object({
|
|
28641
|
+
level: exports_external.enum(["debug", "info", "warn", "error", "fatal"]),
|
|
28642
|
+
message: exports_external.string(),
|
|
28643
|
+
project_id: exports_external.string().optional(),
|
|
28644
|
+
service: exports_external.string().optional(),
|
|
28645
|
+
trace_id: exports_external.string().optional(),
|
|
28646
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
28647
|
+
}))
|
|
28648
|
+
}, ({ entries }) => {
|
|
28649
|
+
const rows = ingestBatch(db, entries.map((e) => ({ ...e, project_id: rp(e.project_id) })));
|
|
28650
|
+
return { content: [{ type: "text", text: `Logged ${rows.length} entries` }] };
|
|
28651
|
+
});
|
|
28572
28652
|
server.tool("log_search", {
|
|
28573
28653
|
project_id: exports_external.string().optional(),
|
|
28574
28654
|
page_id: exports_external.string().optional(),
|
|
@@ -28581,7 +28661,13 @@ server.tool("log_search", {
|
|
|
28581
28661
|
limit: exports_external.number().optional(),
|
|
28582
28662
|
brief: exports_external.boolean().optional()
|
|
28583
28663
|
}, (args) => {
|
|
28584
|
-
const rows = searchLogs(db, {
|
|
28664
|
+
const rows = searchLogs(db, {
|
|
28665
|
+
...args,
|
|
28666
|
+
project_id: rp(args.project_id),
|
|
28667
|
+
level: args.level ? args.level.split(",") : undefined,
|
|
28668
|
+
since: parseTime(args.since) ?? args.since,
|
|
28669
|
+
until: parseTime(args.until) ?? args.until
|
|
28670
|
+
});
|
|
28585
28671
|
return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, args.brief !== false)) }] };
|
|
28586
28672
|
});
|
|
28587
28673
|
server.tool("log_tail", {
|
|
@@ -28589,27 +28675,55 @@ server.tool("log_tail", {
|
|
|
28589
28675
|
n: exports_external.number().optional(),
|
|
28590
28676
|
brief: exports_external.boolean().optional()
|
|
28591
28677
|
}, ({ project_id, n, brief }) => {
|
|
28592
|
-
const rows = tailLogs(db, project_id, n ?? 50);
|
|
28678
|
+
const rows = tailLogs(db, rp(project_id), n ?? 50);
|
|
28593
28679
|
return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, brief !== false)) }] };
|
|
28594
28680
|
});
|
|
28681
|
+
server.tool("log_count", {
|
|
28682
|
+
project_id: exports_external.string().optional(),
|
|
28683
|
+
service: exports_external.string().optional(),
|
|
28684
|
+
level: exports_external.string().optional(),
|
|
28685
|
+
since: exports_external.string().optional(),
|
|
28686
|
+
until: exports_external.string().optional()
|
|
28687
|
+
}, (args) => ({
|
|
28688
|
+
content: [{ type: "text", text: JSON.stringify(countLogs(db, { ...args, project_id: rp(args.project_id) })) }]
|
|
28689
|
+
}));
|
|
28690
|
+
server.tool("log_recent_errors", {
|
|
28691
|
+
project_id: exports_external.string().optional(),
|
|
28692
|
+
since: exports_external.string().optional(),
|
|
28693
|
+
limit: exports_external.number().optional()
|
|
28694
|
+
}, ({ project_id, since, limit }) => {
|
|
28695
|
+
const rows = searchLogs(db, {
|
|
28696
|
+
project_id: rp(project_id),
|
|
28697
|
+
level: ["error", "fatal"],
|
|
28698
|
+
since: parseTime(since ?? "1h"),
|
|
28699
|
+
limit: limit ?? 20
|
|
28700
|
+
});
|
|
28701
|
+
return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, true)) }] };
|
|
28702
|
+
});
|
|
28595
28703
|
server.tool("log_summary", {
|
|
28596
28704
|
project_id: exports_external.string().optional(),
|
|
28597
28705
|
since: exports_external.string().optional()
|
|
28598
28706
|
}, ({ project_id, since }) => ({
|
|
28599
|
-
content: [{ type: "text", text: JSON.stringify(summarizeLogs(db, project_id, since)) }]
|
|
28707
|
+
content: [{ type: "text", text: JSON.stringify(summarizeLogs(db, rp(project_id), parseTime(since) ?? since)) }]
|
|
28600
28708
|
}));
|
|
28601
28709
|
server.tool("log_context", {
|
|
28602
28710
|
trace_id: exports_external.string(),
|
|
28603
28711
|
brief: exports_external.boolean().optional()
|
|
28604
|
-
}, ({ trace_id, brief }) => {
|
|
28605
|
-
|
|
28606
|
-
|
|
28607
|
-
|
|
28712
|
+
}, ({ trace_id, brief }) => ({
|
|
28713
|
+
content: [{ type: "text", text: JSON.stringify(applyBrief(getLogContext(db, trace_id), brief !== false)) }]
|
|
28714
|
+
}));
|
|
28715
|
+
server.tool("log_context_from_id", {
|
|
28716
|
+
log_id: exports_external.string(),
|
|
28717
|
+
brief: exports_external.boolean().optional()
|
|
28718
|
+
}, ({ log_id, brief }) => ({
|
|
28719
|
+
content: [{ type: "text", text: JSON.stringify(applyBrief(getLogContextFromId(db, log_id), brief !== false)) }]
|
|
28720
|
+
}));
|
|
28608
28721
|
server.tool("log_diagnose", {
|
|
28609
28722
|
project_id: exports_external.string(),
|
|
28610
|
-
since: exports_external.string().optional()
|
|
28611
|
-
|
|
28612
|
-
|
|
28723
|
+
since: exports_external.string().optional(),
|
|
28724
|
+
include: exports_external.array(exports_external.enum(["top_errors", "error_rate", "failing_pages", "perf"])).optional()
|
|
28725
|
+
}, ({ project_id, since, include }) => ({
|
|
28726
|
+
content: [{ type: "text", text: JSON.stringify(diagnose(db, rp(project_id) ?? project_id, since, include)) }]
|
|
28613
28727
|
}));
|
|
28614
28728
|
server.tool("log_compare", {
|
|
28615
28729
|
project_id: exports_external.string(),
|
|
@@ -28618,13 +28732,20 @@ server.tool("log_compare", {
|
|
|
28618
28732
|
b_since: exports_external.string(),
|
|
28619
28733
|
b_until: exports_external.string()
|
|
28620
28734
|
}, ({ 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)) }]
|
|
28735
|
+
content: [{ type: "text", text: JSON.stringify(compare(db, rp(project_id) ?? project_id, parseTime(a_since) ?? a_since, parseTime(a_until) ?? a_until, parseTime(b_since) ?? b_since, parseTime(b_until) ?? b_until)) }]
|
|
28622
28736
|
}));
|
|
28737
|
+
server.tool("log_session_context", {
|
|
28738
|
+
session_id: exports_external.string(),
|
|
28739
|
+
brief: exports_external.boolean().optional()
|
|
28740
|
+
}, async ({ session_id, brief }) => {
|
|
28741
|
+
const ctx = await getSessionContext(db, session_id);
|
|
28742
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...ctx, logs: applyBrief(ctx.logs, brief !== false) }) }] };
|
|
28743
|
+
});
|
|
28623
28744
|
server.tool("perf_snapshot", {
|
|
28624
28745
|
project_id: exports_external.string(),
|
|
28625
28746
|
page_id: exports_external.string().optional()
|
|
28626
28747
|
}, ({ project_id, page_id }) => {
|
|
28627
|
-
const snap = getLatestSnapshot(db, project_id, page_id);
|
|
28748
|
+
const snap = getLatestSnapshot(db, rp(project_id) ?? project_id, page_id);
|
|
28628
28749
|
return { content: [{ type: "text", text: JSON.stringify(snap ? { ...snap, label: scoreLabel(snap.score) } : null) }] };
|
|
28629
28750
|
});
|
|
28630
28751
|
server.tool("perf_trend", {
|
|
@@ -28633,25 +28754,25 @@ server.tool("perf_trend", {
|
|
|
28633
28754
|
since: exports_external.string().optional(),
|
|
28634
28755
|
limit: exports_external.number().optional()
|
|
28635
28756
|
}, ({ project_id, page_id, since, limit }) => ({
|
|
28636
|
-
content: [{ type: "text", text: JSON.stringify(getPerfTrend(db, project_id, page_id, since, limit ?? 50)) }]
|
|
28757
|
+
content: [{ type: "text", text: JSON.stringify(getPerfTrend(db, rp(project_id) ?? project_id, page_id, parseTime(since) ?? since, limit ?? 50)) }]
|
|
28637
28758
|
}));
|
|
28638
28759
|
server.tool("scan_status", {
|
|
28639
28760
|
project_id: exports_external.string().optional()
|
|
28640
28761
|
}, ({ project_id }) => ({
|
|
28641
|
-
content: [{ type: "text", text: JSON.stringify(listJobs(db, project_id)) }]
|
|
28762
|
+
content: [{ type: "text", text: JSON.stringify(listJobs(db, rp(project_id))) }]
|
|
28642
28763
|
}));
|
|
28643
28764
|
server.tool("list_projects", {}, () => ({
|
|
28644
28765
|
content: [{ type: "text", text: JSON.stringify(listProjects(db)) }]
|
|
28645
28766
|
}));
|
|
28646
28767
|
server.tool("list_pages", { project_id: exports_external.string() }, ({ project_id }) => ({
|
|
28647
|
-
content: [{ type: "text", text: JSON.stringify(listPages(db, project_id)) }]
|
|
28768
|
+
content: [{ type: "text", text: JSON.stringify(listPages(db, rp(project_id) ?? project_id)) }]
|
|
28648
28769
|
}));
|
|
28649
28770
|
server.tool("list_issues", {
|
|
28650
28771
|
project_id: exports_external.string().optional(),
|
|
28651
28772
|
status: exports_external.string().optional(),
|
|
28652
28773
|
limit: exports_external.number().optional()
|
|
28653
28774
|
}, ({ project_id, status, limit }) => ({
|
|
28654
|
-
content: [{ type: "text", text: JSON.stringify(listIssues(db, project_id, status, limit ?? 50)) }]
|
|
28775
|
+
content: [{ type: "text", text: JSON.stringify(listIssues(db, rp(project_id), status, limit ?? 50)) }]
|
|
28655
28776
|
}));
|
|
28656
28777
|
server.tool("resolve_issue", {
|
|
28657
28778
|
id: exports_external.string(),
|
|
@@ -28668,11 +28789,11 @@ server.tool("create_alert_rule", {
|
|
|
28668
28789
|
window_seconds: exports_external.number().optional(),
|
|
28669
28790
|
action: exports_external.enum(["webhook", "log"]).optional(),
|
|
28670
28791
|
webhook_url: exports_external.string().optional()
|
|
28671
|
-
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createAlertRule(db, args)) }] }));
|
|
28792
|
+
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createAlertRule(db, { ...args, project_id: rp(args.project_id) ?? args.project_id })) }] }));
|
|
28672
28793
|
server.tool("list_alert_rules", {
|
|
28673
28794
|
project_id: exports_external.string().optional()
|
|
28674
28795
|
}, ({ project_id }) => ({
|
|
28675
|
-
content: [{ type: "text", text: JSON.stringify(listAlertRules(db, project_id)) }]
|
|
28796
|
+
content: [{ type: "text", text: JSON.stringify(listAlertRules(db, rp(project_id))) }]
|
|
28676
28797
|
}));
|
|
28677
28798
|
server.tool("delete_alert_rule", { id: exports_external.string() }, ({ id }) => {
|
|
28678
28799
|
deleteAlertRule(db, id);
|