@hasna/logs 0.3.17 → 0.3.19
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/bun.lock +2 -2
- package/dist/cli/index.js +4 -3
- package/dist/{index-5qwba140.js → index-8pwbytc6.js} +2 -2
- package/dist/{index-6zrkek5y.js → index-9n6bpjxf.js} +4448 -3115
- package/dist/{index-vmr85wa1.js → index-hwabsrfh.js} +4303 -3140
- package/dist/{index-2sbhn1ye.js → index-sgg59p1t.js} +2 -2
- package/dist/mcp/index.js +11 -67
- package/dist/server/index.js +11 -3
- package/package.json +3 -3
- package/src/cli/entrypoints.test.ts +63 -0
- package/src/cli/index.ts +2 -1
- package/src/lib/ingest.ts +6 -6
- package/src/lib/package-meta.ts +62 -0
- package/src/mcp/index.ts +10 -3
- package/src/server/index.ts +9 -1
- package/dist/export-yjaar93b.js +0 -10
- package/dist/health-9792c1rc.js +0 -8
- package/dist/health-egdb00st.js +0 -8
- package/dist/index-14dvwcf1.js +0 -45
- package/dist/index-1f2ghyhm.js +0 -540
- package/dist/index-4ba0sabv.js +0 -1241
- package/dist/index-4hj4sakk.js +0 -1241
- package/dist/index-5tvnhvgr.js +0 -536
- package/dist/index-6y8pmes4.js +0 -45
- package/dist/index-7qhh666n.js +0 -1241
- package/dist/index-86j0hn03.js +0 -540
- package/dist/index-exeq2gs6.js +0 -79
- package/dist/index-fzmz9aqs.js +0 -1241
- package/dist/index-g8dczzvv.js +0 -30
- package/dist/index-rbrsvsyh.js +0 -88
- package/dist/index-t97ttm0a.js +0 -543
- package/dist/index-wbsq8qjd.js +0 -1241
- package/dist/index-xjn8gam3.js +0 -39
- package/dist/index-yb8yd4j6.js +0 -39
- package/dist/jobs-02z4fzsn.js +0 -22
- package/dist/query-6s5gqkck.js +0 -15
- package/dist/query-shjjj67k.js +0 -14
- package/dist/query-tcg3bm9s.js +0 -14
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
listPages,
|
|
6
6
|
saveSnapshot,
|
|
7
7
|
touchPage
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-9n6bpjxf.js";
|
|
9
9
|
import {
|
|
10
10
|
createScanRun,
|
|
11
11
|
finishScanRun,
|
|
@@ -894,7 +894,7 @@ var require_scheduled_task = __commonJS((exports, module) => {
|
|
|
894
894
|
|
|
895
895
|
// node_modules/node-cron/src/background-scheduled-task/index.js
|
|
896
896
|
var require_background_scheduled_task = __commonJS((exports, module) => {
|
|
897
|
-
var __dirname = "/
|
|
897
|
+
var __dirname = "/home/hasna/workspace/hasna/opensource/opensourcedev/open-logs/node_modules/node-cron/src/background-scheduled-task";
|
|
898
898
|
var EventEmitter = __require("events");
|
|
899
899
|
var path = __require("path");
|
|
900
900
|
var { fork } = __require("child_process");
|
package/dist/mcp/index.js
CHANGED
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
getHealth
|
|
5
5
|
} from "../index-cpvq9np9.js";
|
|
6
6
|
import {
|
|
7
|
+
PACKAGE_VERSION,
|
|
7
8
|
createAlertRule,
|
|
8
9
|
createPage,
|
|
9
10
|
createProject,
|
|
10
11
|
deleteAlertRule,
|
|
12
|
+
exitIfMetadataRequest,
|
|
11
13
|
getDb,
|
|
12
14
|
getLatestSnapshot,
|
|
13
15
|
getPerfTrend,
|
|
@@ -17,11 +19,12 @@ import {
|
|
|
17
19
|
listIssues,
|
|
18
20
|
listPages,
|
|
19
21
|
listProjects,
|
|
22
|
+
registerCloudTools,
|
|
20
23
|
resolveProjectId,
|
|
21
24
|
scoreLabel,
|
|
22
25
|
summarizeLogs,
|
|
23
26
|
updateIssueStatus
|
|
24
|
-
} from "../index-
|
|
27
|
+
} from "../index-9n6bpjxf.js";
|
|
25
28
|
import {
|
|
26
29
|
createJob,
|
|
27
30
|
listJobs
|
|
@@ -6523,69 +6526,6 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
6523
6526
|
exports.default = formatsPlugin;
|
|
6524
6527
|
});
|
|
6525
6528
|
|
|
6526
|
-
// package.json
|
|
6527
|
-
var require_package = __commonJS((exports, module) => {
|
|
6528
|
-
module.exports = {
|
|
6529
|
-
name: "@hasna/logs",
|
|
6530
|
-
version: "0.3.16",
|
|
6531
|
-
description: "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
|
|
6532
|
-
type: "module",
|
|
6533
|
-
main: "./dist/index.js",
|
|
6534
|
-
types: "./dist/index.d.ts",
|
|
6535
|
-
bin: {
|
|
6536
|
-
logs: "./dist/cli/index.js",
|
|
6537
|
-
"logs-mcp": "./dist/mcp/index.js",
|
|
6538
|
-
"logs-serve": "./dist/server/index.js"
|
|
6539
|
-
},
|
|
6540
|
-
scripts: {
|
|
6541
|
-
build: "bun build src/cli/index.ts src/mcp/index.ts src/server/index.ts --outdir dist --target bun --splitting --external playwright --external playwright-core --external electron --external chromium-bidi --external lighthouse",
|
|
6542
|
-
"build:dashboard": "cd dashboard && bun run build",
|
|
6543
|
-
"build:all": "bun run build:dashboard && bun run build",
|
|
6544
|
-
dev: "bun run src/server/index.ts",
|
|
6545
|
-
test: "bun test",
|
|
6546
|
-
"test:coverage": "bun test --coverage",
|
|
6547
|
-
lint: "biome check src/",
|
|
6548
|
-
postinstall: "mkdir -p $HOME/.hasna/logs 2>/dev/null || true"
|
|
6549
|
-
},
|
|
6550
|
-
repository: {
|
|
6551
|
-
type: "git",
|
|
6552
|
-
url: "https://github.com/hasna/logs.git"
|
|
6553
|
-
},
|
|
6554
|
-
publishConfig: {
|
|
6555
|
-
registry: "https://registry.npmjs.org",
|
|
6556
|
-
access: "public"
|
|
6557
|
-
},
|
|
6558
|
-
keywords: [
|
|
6559
|
-
"logs",
|
|
6560
|
-
"monitoring",
|
|
6561
|
-
"mcp",
|
|
6562
|
-
"ai-agents",
|
|
6563
|
-
"sentry",
|
|
6564
|
-
"performance",
|
|
6565
|
-
"lighthouse"
|
|
6566
|
-
],
|
|
6567
|
-
author: "Andrei Hasna <andrei@hasna.com>",
|
|
6568
|
-
license: "Apache-2.0",
|
|
6569
|
-
dependencies: {
|
|
6570
|
-
"@hasna/cloud": "0.1.5",
|
|
6571
|
-
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
6572
|
-
commander: "^14.0.0",
|
|
6573
|
-
hono: "^4.7.11",
|
|
6574
|
-
ink: "^5.1.0",
|
|
6575
|
-
"node-cron": "^3.0.3",
|
|
6576
|
-
playwright: "^1.52.0",
|
|
6577
|
-
react: "^19.1.0"
|
|
6578
|
-
},
|
|
6579
|
-
devDependencies: {
|
|
6580
|
-
"@biomejs/biome": "^1.9.4",
|
|
6581
|
-
"@types/bun": "latest",
|
|
6582
|
-
"@types/node-cron": "^3.0.11",
|
|
6583
|
-
"@types/react": "^19.1.4",
|
|
6584
|
-
typescript: "^5.9.3"
|
|
6585
|
-
}
|
|
6586
|
-
};
|
|
6587
|
-
});
|
|
6588
|
-
|
|
6589
6529
|
// node_modules/@modelcontextprotocol/sdk/node_modules/zod/v3/helpers/util.js
|
|
6590
6530
|
var util;
|
|
6591
6531
|
(function(util2) {
|
|
@@ -23995,8 +23935,12 @@ async function getSessionContext(db, sessionId) {
|
|
|
23995
23935
|
}
|
|
23996
23936
|
|
|
23997
23937
|
// src/mcp/index.ts
|
|
23938
|
+
exitIfMetadataRequest({
|
|
23939
|
+
name: "logs-mcp",
|
|
23940
|
+
description: "Start the @hasna/logs MCP server over stdio."
|
|
23941
|
+
});
|
|
23998
23942
|
var db = getDb();
|
|
23999
|
-
var server = new McpServer({ name: "logs", version:
|
|
23943
|
+
var server = new McpServer({ name: "logs", version: PACKAGE_VERSION });
|
|
24000
23944
|
var _logsAgents = new Map;
|
|
24001
23945
|
function applyBrief(rows, brief = true) {
|
|
24002
23946
|
if (!brief)
|
|
@@ -24325,12 +24269,11 @@ server.tool("send_feedback", "Send feedback about this service", {
|
|
|
24325
24269
|
category: exports_external.enum(["bug", "feature", "general"]).optional()
|
|
24326
24270
|
}, async (params) => {
|
|
24327
24271
|
try {
|
|
24328
|
-
const pkg = require_package();
|
|
24329
24272
|
db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [
|
|
24330
24273
|
params.message,
|
|
24331
24274
|
params.email || null,
|
|
24332
24275
|
params.category || "general",
|
|
24333
|
-
|
|
24276
|
+
PACKAGE_VERSION
|
|
24334
24277
|
]);
|
|
24335
24278
|
return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
|
|
24336
24279
|
} catch (e) {
|
|
@@ -24376,4 +24319,5 @@ server.tool("list_agents", "List all registered agents.", {}, async () => {
|
|
|
24376
24319
|
return { content: [{ type: "text", text: JSON.stringify([..._logsAgents.values()]) }] };
|
|
24377
24320
|
});
|
|
24378
24321
|
var transport = new StdioServerTransport;
|
|
24322
|
+
registerCloudTools(server, "logs");
|
|
24379
24323
|
await server.connect(transport);
|
package/dist/server/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
setPageAuth,
|
|
7
7
|
setRetentionPolicy,
|
|
8
8
|
startScheduler
|
|
9
|
-
} from "../index-
|
|
9
|
+
} from "../index-sgg59p1t.js";
|
|
10
10
|
import {
|
|
11
11
|
getHealth
|
|
12
12
|
} from "../index-cpvq9np9.js";
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
createPage,
|
|
16
16
|
createProject,
|
|
17
17
|
deleteAlertRule,
|
|
18
|
+
exitIfMetadataRequest,
|
|
18
19
|
getDb,
|
|
19
20
|
getIssue,
|
|
20
21
|
getLatestSnapshot,
|
|
@@ -26,12 +27,13 @@ import {
|
|
|
26
27
|
listIssues,
|
|
27
28
|
listPages,
|
|
28
29
|
listProjects,
|
|
30
|
+
readOptionValue,
|
|
29
31
|
resolveProjectId,
|
|
30
32
|
summarizeLogs,
|
|
31
33
|
updateAlertRule,
|
|
32
34
|
updateIssueStatus,
|
|
33
35
|
updateProject
|
|
34
|
-
} from "../index-
|
|
36
|
+
} from "../index-9n6bpjxf.js";
|
|
35
37
|
import {
|
|
36
38
|
createJob,
|
|
37
39
|
deleteJob,
|
|
@@ -2442,7 +2444,13 @@ function streamRoutes(db) {
|
|
|
2442
2444
|
}
|
|
2443
2445
|
|
|
2444
2446
|
// src/server/index.ts
|
|
2445
|
-
|
|
2447
|
+
exitIfMetadataRequest({
|
|
2448
|
+
name: "logs-serve",
|
|
2449
|
+
description: "Start the @hasna/logs REST API server.",
|
|
2450
|
+
options: [" -p, --port <n> Port to listen on (default: LOGS_PORT or 3460)"]
|
|
2451
|
+
});
|
|
2452
|
+
var portArg = readOptionValue(["--port", "-p"]);
|
|
2453
|
+
var PORT = Number(portArg ?? process.env.LOGS_PORT ?? 3460);
|
|
2446
2454
|
var db = getDb();
|
|
2447
2455
|
var app = new Hono2;
|
|
2448
2456
|
app.use("*", cors());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/logs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"author": "Andrei Hasna <andrei@hasna.com>",
|
|
41
41
|
"license": "Apache-2.0",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@hasna/cloud": "0.1.
|
|
43
|
+
"@hasna/cloud": "^0.1.24",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
45
45
|
"commander": "^14.0.0",
|
|
46
46
|
"hono": "^4.7.11",
|
|
@@ -56,4 +56,4 @@
|
|
|
56
56
|
"@types/react": "^19.1.4",
|
|
57
57
|
"typescript": "^5.9.3"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { readFileSync } from "node:fs"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
|
|
5
|
+
const packageJson = JSON.parse(
|
|
6
|
+
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
7
|
+
) as { version: string }
|
|
8
|
+
|
|
9
|
+
async function runEntrypoint(entryRelativePath: string, args: string[]) {
|
|
10
|
+
const entry = fileURLToPath(new URL(entryRelativePath, import.meta.url))
|
|
11
|
+
const proc = Bun.spawn(["bun", entry, ...args], {
|
|
12
|
+
env: {
|
|
13
|
+
...process.env,
|
|
14
|
+
LOGS_PORT: "0",
|
|
15
|
+
},
|
|
16
|
+
stdout: "pipe",
|
|
17
|
+
stderr: "pipe",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
21
|
+
const timer = setTimeout(() => {
|
|
22
|
+
proc.kill()
|
|
23
|
+
reject(new Error(`Timed out running ${entryRelativePath} ${args.join(" ")}`.trim()))
|
|
24
|
+
}, 2000)
|
|
25
|
+
|
|
26
|
+
proc.exited.finally(() => clearTimeout(timer))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const exitCode = await Promise.race([proc.exited, timeout])
|
|
30
|
+
const stdout = await new Response(proc.stdout).text()
|
|
31
|
+
const stderr = await new Response(proc.stderr).text()
|
|
32
|
+
|
|
33
|
+
return { exitCode, stdout, stderr }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test("logs --version matches package.json", async () => {
|
|
37
|
+
const result = await runEntrypoint("./index.ts", ["--version"])
|
|
38
|
+
|
|
39
|
+
expect(result.exitCode).toBe(0)
|
|
40
|
+
expect(result.stdout.trim()).toBe(packageJson.version)
|
|
41
|
+
expect(result.stderr.trim()).toBe("")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("logs-mcp --help prints usage and exits without starting stdio transport", async () => {
|
|
45
|
+
const result = await runEntrypoint("../mcp/index.ts", ["--help"])
|
|
46
|
+
|
|
47
|
+
expect(result.exitCode).toBe(0)
|
|
48
|
+
expect(result.stdout).toContain("Usage: logs-mcp [options]")
|
|
49
|
+
expect(result.stdout).toContain("Start the @hasna/logs MCP server over stdio.")
|
|
50
|
+
expect(result.stdout).not.toContain("Listening")
|
|
51
|
+
expect(result.stderr.trim()).toBe("")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("logs-serve --help prints usage and exits without starting the server", async () => {
|
|
55
|
+
const result = await runEntrypoint("../server/index.ts", ["--help"])
|
|
56
|
+
|
|
57
|
+
expect(result.exitCode).toBe(0)
|
|
58
|
+
expect(result.stdout).toContain("Usage: logs-serve [options]")
|
|
59
|
+
expect(result.stdout).toContain("Start the @hasna/logs REST API server.")
|
|
60
|
+
expect(result.stdout).not.toContain("server running")
|
|
61
|
+
expect(result.stdout).not.toContain("Scheduler started")
|
|
62
|
+
expect(result.stderr.trim()).toBe("")
|
|
63
|
+
})
|
package/src/cli/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander"
|
|
3
3
|
import { getDb } from "../db/index.ts"
|
|
4
4
|
import { ingestLog } from "../lib/ingest.ts"
|
|
5
|
+
import { PACKAGE_VERSION } from "../lib/package-meta.ts"
|
|
5
6
|
import { searchLogs, tailLogs } from "../lib/query.ts"
|
|
6
7
|
import { summarizeLogs } from "../lib/summarize.ts"
|
|
7
8
|
import { createJob, listJobs } from "../lib/jobs.ts"
|
|
@@ -39,7 +40,7 @@ function resolveProject(nameOrId: string | undefined): string | undefined {
|
|
|
39
40
|
const program = new Command()
|
|
40
41
|
.name("logs")
|
|
41
42
|
.description("@hasna/logs — log aggregation and monitoring")
|
|
42
|
-
.version(
|
|
43
|
+
.version(PACKAGE_VERSION)
|
|
43
44
|
|
|
44
45
|
// ── logs list ──────────────────────────────────────────────
|
|
45
46
|
program.command("list")
|
package/src/lib/ingest.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DbAdapter } from "@hasna/cloud"
|
|
2
2
|
import type { LogEntry, LogRow } from "../types/index.ts"
|
|
3
3
|
import { upsertIssue } from "./issues.ts"
|
|
4
4
|
import { evaluateAlerts } from "./alerts.ts"
|
|
5
5
|
|
|
6
6
|
const ERROR_LEVELS = new Set(["warn", "error", "fatal"])
|
|
7
7
|
|
|
8
|
-
export function ingestLog(db:
|
|
8
|
+
export function ingestLog(db: DbAdapter, entry: LogEntry): LogRow {
|
|
9
9
|
const stmt = db.prepare(`
|
|
10
10
|
INSERT INTO logs (project_id, page_id, level, source, service, message, trace_id, session_id, agent, url, stack_trace, metadata)
|
|
11
11
|
VALUES ($project_id, $page_id, $level, $source, $service, $message, $trace_id, $session_id, $agent, $url, $stack_trace, $metadata)
|
|
@@ -37,7 +37,7 @@ export function ingestLog(db: Database, entry: LogEntry): LogRow {
|
|
|
37
37
|
return row
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function ingestBatch(db:
|
|
40
|
+
export function ingestBatch(db: DbAdapter, entries: LogEntry[], sharedTraceId?: string | null): LogRow[] {
|
|
41
41
|
// Apply shared trace_id to entries that don't have their own
|
|
42
42
|
if (sharedTraceId) {
|
|
43
43
|
entries = entries.map(e => e.trace_id ? e : { ...e, trace_id: sharedTraceId })
|
|
@@ -47,8 +47,9 @@ export function ingestBatch(db: Database, entries: LogEntry[], sharedTraceId?: s
|
|
|
47
47
|
VALUES ($project_id, $page_id, $level, $source, $service, $message, $trace_id, $session_id, $agent, $url, $stack_trace, $metadata)
|
|
48
48
|
RETURNING *
|
|
49
49
|
`)
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// @hasna/cloud executes the callback inside the transaction immediately.
|
|
51
|
+
const rows = db.transaction(() =>
|
|
52
|
+
entries.map(entry =>
|
|
52
53
|
insert.get({
|
|
53
54
|
$project_id: entry.project_id ?? null,
|
|
54
55
|
$page_id: entry.page_id ?? null,
|
|
@@ -65,7 +66,6 @@ export function ingestBatch(db: Database, entries: LogEntry[], sharedTraceId?: s
|
|
|
65
66
|
}) as LogRow
|
|
66
67
|
)
|
|
67
68
|
)
|
|
68
|
-
const rows = tx(entries)
|
|
69
69
|
|
|
70
70
|
// Issue grouping for error-level entries (outside transaction for perf)
|
|
71
71
|
for (const entry of entries) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
|
|
3
|
+
type StandaloneCliSpec = {
|
|
4
|
+
name: string
|
|
5
|
+
description: string
|
|
6
|
+
options?: string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type PackageJson = {
|
|
10
|
+
version?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const packageJson = JSON.parse(
|
|
14
|
+
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
15
|
+
) as PackageJson
|
|
16
|
+
|
|
17
|
+
export const PACKAGE_VERSION = packageJson.version ?? "0.0.0"
|
|
18
|
+
|
|
19
|
+
export function exitIfMetadataRequest(spec: StandaloneCliSpec, argv = process.argv.slice(2)): void {
|
|
20
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
21
|
+
console.log(PACKAGE_VERSION)
|
|
22
|
+
process.exit(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
26
|
+
const options = spec.options ?? []
|
|
27
|
+
const renderedOptions = [
|
|
28
|
+
" -V, --version output the version number",
|
|
29
|
+
" -h, --help display help for command",
|
|
30
|
+
...options,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
console.log(
|
|
34
|
+
[
|
|
35
|
+
`Usage: ${spec.name} [options]`,
|
|
36
|
+
"",
|
|
37
|
+
spec.description,
|
|
38
|
+
"",
|
|
39
|
+
"Options:",
|
|
40
|
+
...renderedOptions,
|
|
41
|
+
].join("\n"),
|
|
42
|
+
)
|
|
43
|
+
process.exit(0)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readOptionValue(names: string[], argv = process.argv.slice(2)): string | undefined {
|
|
48
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
49
|
+
const arg = argv[index]
|
|
50
|
+
if (!arg) continue
|
|
51
|
+
|
|
52
|
+
const inline = names.find((name) => arg.startsWith(`${name}=`))
|
|
53
|
+
if (inline) return arg.slice(inline.length + 1)
|
|
54
|
+
|
|
55
|
+
if (names.includes(arg)) {
|
|
56
|
+
const next = argv[index + 1]
|
|
57
|
+
if (next && !next.startsWith("-")) return next
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
package/src/mcp/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
4
|
+
import { registerCloudTools } from "@hasna/cloud"
|
|
4
5
|
import { z } from "zod"
|
|
5
6
|
import { getDb } from "../db/index.ts"
|
|
7
|
+
import { exitIfMetadataRequest, PACKAGE_VERSION } from "../lib/package-meta.ts"
|
|
6
8
|
import { ingestBatch, ingestLog } from "../lib/ingest.ts"
|
|
7
9
|
import { getLogContext, getLogContextFromId, searchLogs, tailLogs } from "../lib/query.ts"
|
|
8
10
|
import { summarizeLogs } from "../lib/summarize.ts"
|
|
@@ -20,8 +22,13 @@ import { getSessionContext } from "../lib/session-context.ts"
|
|
|
20
22
|
import { parseTime } from "../lib/parse-time.ts"
|
|
21
23
|
import type { LogLevel, LogRow } from "../types/index.ts"
|
|
22
24
|
|
|
25
|
+
exitIfMetadataRequest({
|
|
26
|
+
name: "logs-mcp",
|
|
27
|
+
description: "Start the @hasna/logs MCP server over stdio.",
|
|
28
|
+
})
|
|
29
|
+
|
|
23
30
|
const db = getDb()
|
|
24
|
-
const server = new McpServer({ name: "logs", version:
|
|
31
|
+
const server = new McpServer({ name: "logs", version: PACKAGE_VERSION })
|
|
25
32
|
|
|
26
33
|
// --- in-memory agent registry ---
|
|
27
34
|
interface _LogsAgent { id: string; name: string; session_id?: string; last_seen_at: string; project_id?: string }
|
|
@@ -354,9 +361,8 @@ server.tool(
|
|
|
354
361
|
},
|
|
355
362
|
async (params) => {
|
|
356
363
|
try {
|
|
357
|
-
const pkg = require("../../package.json")
|
|
358
364
|
db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [
|
|
359
|
-
params.message, params.email || null, params.category || "general",
|
|
365
|
+
params.message, params.email || null, params.category || "general", PACKAGE_VERSION,
|
|
360
366
|
])
|
|
361
367
|
return { content: [{ type: "text" as const, text: "Feedback saved. Thank you!" }] }
|
|
362
368
|
} catch (e) {
|
|
@@ -403,4 +409,5 @@ server.tool("list_agents", "List all registered agents.", {}, async () => {
|
|
|
403
409
|
})
|
|
404
410
|
|
|
405
411
|
const transport = new StdioServerTransport()
|
|
412
|
+
registerCloudTools(server, "logs")
|
|
406
413
|
await server.connect(transport)
|
package/src/server/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { serveStatic } from "hono/bun"
|
|
|
5
5
|
import { getDb } from "../db/index.ts"
|
|
6
6
|
import { getBrowserScript } from "../lib/browser-script.ts"
|
|
7
7
|
import { getHealth } from "../lib/health.ts"
|
|
8
|
+
import { exitIfMetadataRequest, readOptionValue } from "../lib/package-meta.ts"
|
|
8
9
|
import { startScheduler } from "../lib/scheduler.ts"
|
|
9
10
|
import { alertsRoutes } from "./routes/alerts.ts"
|
|
10
11
|
import { issuesRoutes } from "./routes/issues.ts"
|
|
@@ -14,7 +15,14 @@ import { perfRoutes } from "./routes/perf.ts"
|
|
|
14
15
|
import { projectsRoutes } from "./routes/projects.ts"
|
|
15
16
|
import { streamRoutes } from "./routes/stream.ts"
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
exitIfMetadataRequest({
|
|
19
|
+
name: "logs-serve",
|
|
20
|
+
description: "Start the @hasna/logs REST API server.",
|
|
21
|
+
options: [" -p, --port <n> Port to listen on (default: LOGS_PORT or 3460)"],
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const portArg = readOptionValue(["--port", "-p"])
|
|
25
|
+
const PORT = Number(portArg ?? process.env.LOGS_PORT ?? 3460)
|
|
18
26
|
const db = getDb()
|
|
19
27
|
const app = new Hono()
|
|
20
28
|
|
package/dist/export-yjaar93b.js
DELETED
package/dist/health-9792c1rc.js
DELETED
package/dist/health-egdb00st.js
DELETED
package/dist/index-14dvwcf1.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
parseTime
|
|
4
|
-
} from "./index-997bkzr2.js";
|
|
5
|
-
|
|
6
|
-
// src/lib/count.ts
|
|
7
|
-
function countLogs(db, opts) {
|
|
8
|
-
const conditions = [];
|
|
9
|
-
const params = {};
|
|
10
|
-
if (opts.project_id) {
|
|
11
|
-
conditions.push("project_id = $p");
|
|
12
|
-
params.$p = opts.project_id;
|
|
13
|
-
}
|
|
14
|
-
if (opts.service) {
|
|
15
|
-
conditions.push("service = $service");
|
|
16
|
-
params.$service = opts.service;
|
|
17
|
-
}
|
|
18
|
-
if (opts.level) {
|
|
19
|
-
conditions.push("level = $level");
|
|
20
|
-
params.$level = opts.level;
|
|
21
|
-
}
|
|
22
|
-
const since = parseTime(opts.since);
|
|
23
|
-
const until = parseTime(opts.until);
|
|
24
|
-
if (since) {
|
|
25
|
-
conditions.push("timestamp >= $since");
|
|
26
|
-
params.$since = since;
|
|
27
|
-
}
|
|
28
|
-
if (until) {
|
|
29
|
-
conditions.push("timestamp <= $until");
|
|
30
|
-
params.$until = until;
|
|
31
|
-
}
|
|
32
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
33
|
-
const byLevel = db.prepare(`SELECT level, COUNT(*) as c FROM logs ${where} GROUP BY level`).all(params);
|
|
34
|
-
const by_level = Object.fromEntries(byLevel.map((r) => [r.level, r.c]));
|
|
35
|
-
const total = byLevel.reduce((s, r) => s + r.c, 0);
|
|
36
|
-
return {
|
|
37
|
-
total,
|
|
38
|
-
errors: by_level["error"] ?? 0,
|
|
39
|
-
warns: by_level["warn"] ?? 0,
|
|
40
|
-
fatals: by_level["fatal"] ?? 0,
|
|
41
|
-
by_level
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export { countLogs };
|