@hasna/uptime 0.1.0 → 0.1.2
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/CHANGELOG.md +34 -0
- package/README.md +25 -4
- package/dist/api.d.ts +9 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +422 -12
- package/dist/cli/index.js +499 -22
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +424 -12
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +424 -8
- package/dist/report.d.ts +49 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +274 -0
- package/dist/service.d.ts +7 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +392 -9
- package/dist/store.d.ts +9 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +93 -7
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { createUptimeClient, UptimeService } from "./service.js";
|
|
|
2
2
|
export { UptimeStore } from "./store.js";
|
|
3
3
|
export { runMonitorCheck, runHttpCheck, runTcpCheck } from "./checks.js";
|
|
4
4
|
export { createApiHandler, serveUptime } from "./api.js";
|
|
5
|
+
export { buildUptimeReport, sendUptimeReport } from "./report.js";
|
|
5
6
|
export { uptimeHome, uptimeDbPath, ensureUptimeHome } from "./paths.js";
|
|
6
7
|
export type { CheckAttemptResult, CheckResult, CheckStatus, CreateMonitorInput, Incident, IncidentStatus, ListResultsOptions, Monitor, MonitorKind, MonitorStatus, MonitorSummary, SchedulerHandle, UpdateMonitorInput, UptimeSummary, } from "./types.js";
|
|
8
|
+
export type { BuildUptimeReportOptions, SendUptimeReportOptions, UptimeEmailReportTarget, UptimeLogsReportTarget, UptimeReport, UptimeReportDelivery, UptimeSmsReportTarget, } from "./report.js";
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxE,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,OAAO,EACP,WAAW,EACX,aAAa,EACb,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxE,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,OAAO,EACP,WAAW,EACX,aAAa,EACb,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -98,6 +98,13 @@ var MAX_RETRY_COUNT = 10;
|
|
|
98
98
|
var MAX_RESULT_LIMIT = 1000;
|
|
99
99
|
|
|
100
100
|
// src/store.ts
|
|
101
|
+
class StaleCheckResultError extends Error {
|
|
102
|
+
constructor(message) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.name = "StaleCheckResultError";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
class UptimeStore {
|
|
102
109
|
dbPath;
|
|
103
110
|
db;
|
|
@@ -131,10 +138,12 @@ class UptimeStore {
|
|
|
131
138
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
132
139
|
status TEXT NOT NULL DEFAULT 'unknown',
|
|
133
140
|
last_checked_at TEXT,
|
|
141
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
134
142
|
created_at TEXT NOT NULL,
|
|
135
143
|
updated_at TEXT NOT NULL
|
|
136
144
|
)
|
|
137
145
|
`);
|
|
146
|
+
this.ensureColumn("monitors", "revision", "INTEGER NOT NULL DEFAULT 1");
|
|
138
147
|
this.db.run(`
|
|
139
148
|
CREATE TABLE IF NOT EXISTS check_results (
|
|
140
149
|
id TEXT PRIMARY KEY,
|
|
@@ -160,8 +169,17 @@ class UptimeStore {
|
|
|
160
169
|
reason TEXT
|
|
161
170
|
)
|
|
162
171
|
`);
|
|
172
|
+
this.db.run(`
|
|
173
|
+
CREATE TABLE IF NOT EXISTS check_leases (
|
|
174
|
+
monitor_id TEXT PRIMARY KEY REFERENCES monitors(id) ON DELETE CASCADE,
|
|
175
|
+
owner TEXT NOT NULL,
|
|
176
|
+
leased_until TEXT NOT NULL,
|
|
177
|
+
acquired_at TEXT NOT NULL
|
|
178
|
+
)
|
|
179
|
+
`);
|
|
163
180
|
this.db.run("CREATE INDEX IF NOT EXISTS idx_results_monitor_time ON check_results(monitor_id, checked_at DESC)");
|
|
164
181
|
this.db.run("CREATE INDEX IF NOT EXISTS idx_incidents_monitor_status ON incidents(monitor_id, status)");
|
|
182
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_check_leases_until ON check_leases(leased_until)");
|
|
165
183
|
}
|
|
166
184
|
createMonitor(input) {
|
|
167
185
|
const normalized = normalizeCreateMonitor(input);
|
|
@@ -181,14 +199,15 @@ class UptimeStore {
|
|
|
181
199
|
enabled: normalized.enabled ?? true,
|
|
182
200
|
status: normalized.enabled === false ? "paused" : "unknown",
|
|
183
201
|
lastCheckedAt: null,
|
|
202
|
+
revision: 1,
|
|
184
203
|
createdAt: now,
|
|
185
204
|
updatedAt: now
|
|
186
205
|
};
|
|
187
206
|
this.db.query(`INSERT INTO monitors (
|
|
188
207
|
id, name, kind, url, host, port, method, expected_status,
|
|
189
208
|
interval_seconds, timeout_ms, retry_count, enabled, status,
|
|
190
|
-
last_checked_at, created_at, updated_at
|
|
191
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(monitor.id, monitor.name, monitor.kind, monitor.url, monitor.host, monitor.port, monitor.method, monitor.expectedStatus, monitor.intervalSeconds, monitor.timeoutMs, monitor.retryCount, monitor.enabled ? 1 : 0, monitor.status, monitor.lastCheckedAt, monitor.createdAt, monitor.updatedAt);
|
|
209
|
+
last_checked_at, revision, created_at, updated_at
|
|
210
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(monitor.id, monitor.name, monitor.kind, monitor.url, monitor.host, monitor.port, monitor.method, monitor.expectedStatus, monitor.intervalSeconds, monitor.timeoutMs, monitor.retryCount, monitor.enabled ? 1 : 0, monitor.status, monitor.lastCheckedAt, monitor.revision, monitor.createdAt, monitor.updatedAt);
|
|
192
211
|
return monitor;
|
|
193
212
|
}
|
|
194
213
|
listMonitors(options = {}) {
|
|
@@ -208,8 +227,12 @@ class UptimeStore {
|
|
|
208
227
|
this.db.query(`UPDATE monitors SET
|
|
209
228
|
name = ?, kind = ?, url = ?, host = ?, port = ?, method = ?,
|
|
210
229
|
expected_status = ?, interval_seconds = ?, timeout_ms = ?,
|
|
211
|
-
retry_count = ?, enabled = ?, status = ?, last_checked_at = ?,
|
|
230
|
+
retry_count = ?, enabled = ?, status = ?, last_checked_at = ?,
|
|
231
|
+
revision = revision + 1, updated_at = ?
|
|
212
232
|
WHERE id = ?`).run(next.name, next.kind, next.url, next.host, next.port, next.method, next.expectedStatus, next.intervalSeconds, next.timeoutMs, next.retryCount, next.enabled ? 1 : 0, next.status, next.lastCheckedAt, updatedAt, current.id);
|
|
233
|
+
if (definitionChanged(current, next)) {
|
|
234
|
+
this.closeOpenIncident(current.id, updatedAt);
|
|
235
|
+
}
|
|
213
236
|
return this.getMonitor(current.id);
|
|
214
237
|
}
|
|
215
238
|
deleteMonitor(idOrName) {
|
|
@@ -219,10 +242,31 @@ class UptimeStore {
|
|
|
219
242
|
this.db.query("DELETE FROM monitors WHERE id = ?").run(current.id);
|
|
220
243
|
return true;
|
|
221
244
|
}
|
|
245
|
+
acquireCheckLease(monitorId, owner, ttlMs) {
|
|
246
|
+
const now = new Date;
|
|
247
|
+
const nowIso = now.toISOString();
|
|
248
|
+
const leasedUntil = new Date(now.getTime() + Math.max(1000, ttlMs)).toISOString();
|
|
249
|
+
const tx = this.db.transaction(() => {
|
|
250
|
+
this.db.query("DELETE FROM check_leases WHERE monitor_id = ? AND leased_until <= ?").run(monitorId, nowIso);
|
|
251
|
+
this.db.query("INSERT OR IGNORE INTO check_leases (monitor_id, owner, leased_until, acquired_at) VALUES (?, ?, ?, ?)").run(monitorId, owner, leasedUntil, nowIso);
|
|
252
|
+
const row = this.db.query("SELECT * FROM check_leases WHERE monitor_id = ?").get(monitorId);
|
|
253
|
+
return row?.owner === owner;
|
|
254
|
+
});
|
|
255
|
+
return tx();
|
|
256
|
+
}
|
|
257
|
+
releaseCheckLease(monitorId, owner) {
|
|
258
|
+
this.db.query("DELETE FROM check_leases WHERE monitor_id = ? AND owner = ?").run(monitorId, owner);
|
|
259
|
+
}
|
|
222
260
|
recordCheckResult(input) {
|
|
223
261
|
const monitor = this.getMonitor(input.monitorId);
|
|
224
262
|
if (!monitor)
|
|
225
263
|
throw new Error(`Monitor not found: ${input.monitorId}`);
|
|
264
|
+
if (input.expectedMonitorRevision !== undefined && monitor.revision !== input.expectedMonitorRevision) {
|
|
265
|
+
throw new StaleCheckResultError(`Monitor changed while check was in progress: ${monitor.name}`);
|
|
266
|
+
}
|
|
267
|
+
if (!monitor.enabled) {
|
|
268
|
+
throw new StaleCheckResultError(`Monitor was disabled while check was in progress: ${monitor.name}`);
|
|
269
|
+
}
|
|
226
270
|
const checkedAt = input.checkedAt ?? new Date().toISOString();
|
|
227
271
|
const result = {
|
|
228
272
|
id: newId("chk"),
|
|
@@ -235,6 +279,15 @@ class UptimeStore {
|
|
|
235
279
|
attemptCount: Math.max(1, input.attemptCount)
|
|
236
280
|
};
|
|
237
281
|
const tx = this.db.transaction(() => {
|
|
282
|
+
const current = this.db.query("SELECT * FROM monitors WHERE id = ?").get(result.monitorId);
|
|
283
|
+
if (!current)
|
|
284
|
+
throw new Error(`Monitor not found: ${result.monitorId}`);
|
|
285
|
+
if (input.expectedMonitorRevision !== undefined && current.revision !== input.expectedMonitorRevision) {
|
|
286
|
+
throw new StaleCheckResultError(`Monitor changed while check was in progress: ${current.name}`);
|
|
287
|
+
}
|
|
288
|
+
if (!current.enabled) {
|
|
289
|
+
throw new StaleCheckResultError(`Monitor was disabled while check was in progress: ${current.name}`);
|
|
290
|
+
}
|
|
238
291
|
this.db.query(`INSERT INTO check_results (
|
|
239
292
|
id, monitor_id, checked_at, status, latency_ms, status_code, error, attempt_count
|
|
240
293
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(result.id, result.monitorId, result.checkedAt, result.status, result.latencyMs, result.statusCode, result.error, result.attemptCount);
|
|
@@ -282,10 +335,14 @@ class UptimeStore {
|
|
|
282
335
|
down: monitors.filter((m) => m.status === "down").length,
|
|
283
336
|
paused: monitors.filter((m) => !m.enabled || m.status === "paused").length,
|
|
284
337
|
unknown: monitors.filter((m) => m.status === "unknown").length,
|
|
285
|
-
openIncidents: this.
|
|
338
|
+
openIncidents: this.countOpenIncidents()
|
|
286
339
|
}
|
|
287
340
|
};
|
|
288
341
|
}
|
|
342
|
+
countOpenIncidents() {
|
|
343
|
+
const row = this.db.query("SELECT COUNT(*) AS count FROM incidents WHERE status = 'open'").get();
|
|
344
|
+
return Number(row?.count ?? 0);
|
|
345
|
+
}
|
|
289
346
|
monitorSummary(monitor) {
|
|
290
347
|
const row = this.db.query(`SELECT
|
|
291
348
|
COUNT(*) as total,
|
|
@@ -323,13 +380,24 @@ class UptimeStore {
|
|
|
323
380
|
this.db.query("UPDATE incidents SET status = 'closed', closed_at = ?, recovery_check_id = ? WHERE id = ?").run(result.checkedAt, result.id, open.id);
|
|
324
381
|
}
|
|
325
382
|
}
|
|
383
|
+
closeOpenIncident(monitorId, closedAt) {
|
|
384
|
+
this.db.query("UPDATE incidents SET status = 'closed', closed_at = ? WHERE monitor_id = ? AND status = 'open'").run(closedAt, monitorId);
|
|
385
|
+
}
|
|
386
|
+
ensureColumn(table, name, definition) {
|
|
387
|
+
const columns = this.db.query(`PRAGMA table_info(${table})`).all();
|
|
388
|
+
if (!columns.some((column) => column.name === name)) {
|
|
389
|
+
this.db.run(`ALTER TABLE ${table} ADD COLUMN ${name} ${definition}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
326
392
|
}
|
|
327
393
|
function normalizeCreateMonitor(input) {
|
|
328
394
|
const name = input.name?.trim();
|
|
329
395
|
if (!name)
|
|
330
396
|
throw new Error("Monitor name is required");
|
|
397
|
+
rejectControlCharacters(name, "Monitor name");
|
|
331
398
|
const method = normalizeMethod(input.method ?? "GET");
|
|
332
399
|
const expectedStatus = normalizeExpectedStatus(input.expectedStatus);
|
|
400
|
+
const enabled = normalizeEnabled(input.enabled);
|
|
333
401
|
if (input.kind === "http") {
|
|
334
402
|
const url = normalizeHttpUrl(input.url);
|
|
335
403
|
return {
|
|
@@ -341,12 +409,13 @@ function normalizeCreateMonitor(input) {
|
|
|
341
409
|
intervalSeconds: boundedInteger(input.intervalSeconds ?? 60, "intervalSeconds", MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS),
|
|
342
410
|
timeoutMs: boundedInteger(input.timeoutMs ?? 5000, "timeoutMs", MIN_TIMEOUT_MS, MAX_TIMEOUT_MS),
|
|
343
411
|
retryCount: boundedInteger(input.retryCount ?? 0, "retryCount", MIN_RETRY_COUNT, MAX_RETRY_COUNT),
|
|
344
|
-
enabled
|
|
412
|
+
enabled
|
|
345
413
|
};
|
|
346
414
|
} else if (input.kind === "tcp") {
|
|
347
415
|
const host = input.host?.trim();
|
|
348
416
|
if (!host)
|
|
349
417
|
throw new Error("TCP monitors require host");
|
|
418
|
+
rejectControlCharacters(host, "TCP host");
|
|
350
419
|
if (!Number.isInteger(input.port) || input.port <= 0 || input.port > 65535) {
|
|
351
420
|
throw new Error("TCP monitors require a port from 1 to 65535");
|
|
352
421
|
}
|
|
@@ -360,12 +429,15 @@ function normalizeCreateMonitor(input) {
|
|
|
360
429
|
intervalSeconds: boundedInteger(input.intervalSeconds ?? 60, "intervalSeconds", MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS),
|
|
361
430
|
timeoutMs: boundedInteger(input.timeoutMs ?? 5000, "timeoutMs", MIN_TIMEOUT_MS, MAX_TIMEOUT_MS),
|
|
362
431
|
retryCount: boundedInteger(input.retryCount ?? 0, "retryCount", MIN_RETRY_COUNT, MAX_RETRY_COUNT),
|
|
363
|
-
enabled
|
|
432
|
+
enabled
|
|
364
433
|
};
|
|
365
434
|
} else {
|
|
366
435
|
throw new Error("Monitor kind must be http or tcp");
|
|
367
436
|
}
|
|
368
437
|
}
|
|
438
|
+
function definitionChanged(current, next) {
|
|
439
|
+
return next.kind !== current.kind || next.url !== current.url || next.host !== current.host || next.port !== current.port || next.method !== current.method || next.expectedStatus !== current.expectedStatus;
|
|
440
|
+
}
|
|
369
441
|
function normalizeUpdateMonitor(current, input, updatedAt) {
|
|
370
442
|
const merged = {
|
|
371
443
|
...current,
|
|
@@ -430,6 +502,18 @@ function normalizeExpectedStatus(value) {
|
|
|
430
502
|
}
|
|
431
503
|
return value;
|
|
432
504
|
}
|
|
505
|
+
function normalizeEnabled(value) {
|
|
506
|
+
if (value === undefined)
|
|
507
|
+
return true;
|
|
508
|
+
if (typeof value !== "boolean")
|
|
509
|
+
throw new Error("enabled must be a boolean");
|
|
510
|
+
return value;
|
|
511
|
+
}
|
|
512
|
+
function rejectControlCharacters(value, label) {
|
|
513
|
+
if (/[\x00-\x1f\x7f-\x9f]/.test(value)) {
|
|
514
|
+
throw new Error(`${label} must not contain control characters`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
433
517
|
function monitorFromRow(row) {
|
|
434
518
|
return {
|
|
435
519
|
id: row.id,
|
|
@@ -446,6 +530,7 @@ function monitorFromRow(row) {
|
|
|
446
530
|
enabled: Boolean(row.enabled),
|
|
447
531
|
status: row.status,
|
|
448
532
|
lastCheckedAt: row.last_checked_at,
|
|
533
|
+
revision: row.revision ?? 1,
|
|
449
534
|
createdAt: row.created_at,
|
|
450
535
|
updatedAt: row.updated_at
|
|
451
536
|
};
|
|
@@ -494,10 +579,282 @@ function round(value, places) {
|
|
|
494
579
|
return Math.round(value * factor) / factor;
|
|
495
580
|
}
|
|
496
581
|
|
|
582
|
+
// src/report.ts
|
|
583
|
+
var DEFAULT_MAILERY_API_URL = "http://localhost:3900";
|
|
584
|
+
var DEFAULT_TELEPHONY_API_URL = "http://localhost:19451";
|
|
585
|
+
var DEFAULT_LOGS_API_URL = "http://localhost:3460";
|
|
586
|
+
var DEFAULT_TIMEOUT_MS = 15000;
|
|
587
|
+
function buildUptimeReport(summary, options = {}) {
|
|
588
|
+
const subject = options.subject ?? defaultSubject(summary);
|
|
589
|
+
const lines = [
|
|
590
|
+
subject,
|
|
591
|
+
`Generated: ${summary.generatedAt}`,
|
|
592
|
+
`Monitors: ${summary.totals.monitors} total, ${summary.totals.enabled} enabled, ${summary.totals.up} up, ${summary.totals.down} down, ${summary.totals.openIncidents} open incidents`,
|
|
593
|
+
"",
|
|
594
|
+
...summary.monitors.map(renderMonitorLine)
|
|
595
|
+
];
|
|
596
|
+
const text = lines.join(`
|
|
597
|
+
`).trimEnd();
|
|
598
|
+
const json = {
|
|
599
|
+
kind: "open-uptime.report",
|
|
600
|
+
generated_at: summary.generatedAt,
|
|
601
|
+
subject,
|
|
602
|
+
totals: summary.totals,
|
|
603
|
+
monitors: summary.monitors
|
|
604
|
+
};
|
|
605
|
+
return {
|
|
606
|
+
subject,
|
|
607
|
+
generatedAt: summary.generatedAt,
|
|
608
|
+
summary,
|
|
609
|
+
text,
|
|
610
|
+
html: `<pre>${escapeHtml(text)}</pre>`,
|
|
611
|
+
json
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async function sendUptimeReport(summary, options = {}) {
|
|
615
|
+
const report = buildUptimeReport(summary, options);
|
|
616
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
617
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
618
|
+
const deliveries = [];
|
|
619
|
+
if (options.email) {
|
|
620
|
+
deliveries.push(await sendEmailReport(report, resolveEmailTarget(options.email), fetchImpl, timeoutMs));
|
|
621
|
+
}
|
|
622
|
+
if (options.sms) {
|
|
623
|
+
const smsTarget = resolveSmsTarget(options.sms);
|
|
624
|
+
const recipients = splitTargets(smsTarget.to);
|
|
625
|
+
if (recipients.length === 0) {
|
|
626
|
+
deliveries.push(await sendSmsReport(report, smsTarget, fetchImpl, timeoutMs));
|
|
627
|
+
} else {
|
|
628
|
+
for (const target of recipients) {
|
|
629
|
+
deliveries.push(await sendSmsReport(report, { ...smsTarget, to: target }, fetchImpl, timeoutMs));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (options.logs) {
|
|
634
|
+
deliveries.push(await sendLogsReport(report, resolveLogsTarget(options.logs), fetchImpl, timeoutMs));
|
|
635
|
+
}
|
|
636
|
+
return deliveries;
|
|
637
|
+
}
|
|
638
|
+
function defaultSubject(summary) {
|
|
639
|
+
if (summary.totals.openIncidents > 0 || summary.totals.down > 0) {
|
|
640
|
+
return `Open Uptime alert: ${summary.totals.down} down, ${summary.totals.openIncidents} open incidents`;
|
|
641
|
+
}
|
|
642
|
+
return `Open Uptime report: ${summary.totals.up}/${summary.totals.enabled} enabled monitors up`;
|
|
643
|
+
}
|
|
644
|
+
function renderMonitorLine(item) {
|
|
645
|
+
const uptime = item.uptimePercent == null ? "-" : `${item.uptimePercent.toFixed(2)}%`;
|
|
646
|
+
const latency = item.averageLatencyMs == null ? "-" : `${item.averageLatencyMs}ms`;
|
|
647
|
+
const incident = item.openIncident ? ` open incident: ${item.openIncident.reason ?? "down"}` : "";
|
|
648
|
+
return `- ${item.monitor.status.toUpperCase()} ${item.monitor.name} (${targetLabel(item)}): uptime ${uptime}, latency ${latency}${incident}`;
|
|
649
|
+
}
|
|
650
|
+
function targetLabel(item) {
|
|
651
|
+
return item.monitor.kind === "http" ? item.monitor.url ?? "" : `${item.monitor.host}:${item.monitor.port}`;
|
|
652
|
+
}
|
|
653
|
+
function resolveEmailTarget(value) {
|
|
654
|
+
const target = typeof value === "boolean" ? {} : value;
|
|
655
|
+
return {
|
|
656
|
+
apiUrl: target.apiUrl ?? env("HASNA_MAILERY_API_URL", "MAILERY_API_URL") ?? DEFAULT_MAILERY_API_URL,
|
|
657
|
+
sendKey: target.sendKey ?? env("HASNA_MAILERY_SEND_KEY", "MAILERY_SEND_KEY", "ESK"),
|
|
658
|
+
from: target.from ?? env("HASNA_UPTIME_REPORT_EMAIL_FROM", "UPTIME_REPORT_EMAIL_FROM"),
|
|
659
|
+
to: target.to ?? env("HASNA_UPTIME_REPORT_EMAIL_TO", "UPTIME_REPORT_EMAIL_TO"),
|
|
660
|
+
subject: target.subject,
|
|
661
|
+
providerId: target.providerId ?? env("HASNA_MAILERY_PROVIDER_ID", "MAILERY_PROVIDER_ID")
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function resolveSmsTarget(value) {
|
|
665
|
+
const target = typeof value === "boolean" ? {} : value;
|
|
666
|
+
return {
|
|
667
|
+
apiUrl: target.apiUrl ?? env("HASNA_TELEPHONY_API_URL", "TELEPHONY_API_URL") ?? DEFAULT_TELEPHONY_API_URL,
|
|
668
|
+
from: target.from ?? env("HASNA_UPTIME_REPORT_SMS_FROM", "UPTIME_REPORT_SMS_FROM"),
|
|
669
|
+
to: target.to ?? env("HASNA_UPTIME_REPORT_PHONE_TO", "UPTIME_REPORT_PHONE_TO")
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function resolveLogsTarget(value) {
|
|
673
|
+
const target = typeof value === "boolean" ? {} : value;
|
|
674
|
+
return {
|
|
675
|
+
apiUrl: target.apiUrl ?? env("HASNA_LOGS_URL", "LOGS_URL") ?? DEFAULT_LOGS_API_URL,
|
|
676
|
+
apiKey: target.apiKey ?? env("HASNA_LOGS_API_TOKEN", "LOGS_API_TOKEN", "HASNA_LOGS_API_KEY", "LOGS_API_KEY"),
|
|
677
|
+
projectId: target.projectId ?? env("HASNA_LOGS_PROJECT_ID", "LOGS_PROJECT_ID") ?? "open-uptime",
|
|
678
|
+
environment: target.environment ?? env("HASNA_ENV", "NODE_ENV"),
|
|
679
|
+
service: target.service ?? "open-uptime"
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
async function sendEmailReport(report, target, fetchImpl, timeoutMs) {
|
|
683
|
+
if (!target.sendKey)
|
|
684
|
+
return { channel: "email", ok: false, error: "Mailery send key is required" };
|
|
685
|
+
if (!target.from)
|
|
686
|
+
return { channel: "email", ok: false, error: "Email from address is required" };
|
|
687
|
+
if (!hasTargets(target.to))
|
|
688
|
+
return { channel: "email", ok: false, error: "Email recipient is required" };
|
|
689
|
+
const body = {
|
|
690
|
+
from: target.from,
|
|
691
|
+
to: splitTargets(target.to),
|
|
692
|
+
subject: target.subject ?? report.subject,
|
|
693
|
+
text: report.text,
|
|
694
|
+
html: report.html,
|
|
695
|
+
provider_id: target.providerId
|
|
696
|
+
};
|
|
697
|
+
return requestJson("email", `${normalizeUrl(target.apiUrl ?? DEFAULT_MAILERY_API_URL)}/api/v1/send`, {
|
|
698
|
+
method: "POST",
|
|
699
|
+
headers: { authorization: `Bearer ${target.sendKey}` },
|
|
700
|
+
body
|
|
701
|
+
}, fetchImpl, timeoutMs, secretsForTarget(target));
|
|
702
|
+
}
|
|
703
|
+
async function sendSmsReport(report, target, fetchImpl, timeoutMs) {
|
|
704
|
+
if (!hasTargets(target.to))
|
|
705
|
+
return { channel: "sms", ok: false, error: "SMS recipient phone number is required" };
|
|
706
|
+
return requestJson("sms", `${normalizeUrl(target.apiUrl ?? DEFAULT_TELEPHONY_API_URL)}/api/sms/send`, {
|
|
707
|
+
method: "POST",
|
|
708
|
+
body: {
|
|
709
|
+
to: Array.isArray(target.to) ? target.to[0] : target.to,
|
|
710
|
+
from: target.from,
|
|
711
|
+
body: truncateSms(report.text)
|
|
712
|
+
}
|
|
713
|
+
}, fetchImpl, timeoutMs, secretsForTarget(target));
|
|
714
|
+
}
|
|
715
|
+
async function sendLogsReport(report, target, fetchImpl, timeoutMs) {
|
|
716
|
+
const params = new URLSearchParams({
|
|
717
|
+
format: "json",
|
|
718
|
+
source: "structured",
|
|
719
|
+
service: target.service ?? "open-uptime",
|
|
720
|
+
project_id: target.projectId ?? "open-uptime"
|
|
721
|
+
});
|
|
722
|
+
if (target.environment)
|
|
723
|
+
params.set("environment", target.environment);
|
|
724
|
+
return requestJson("logs", `${normalizeUrl(target.apiUrl ?? DEFAULT_LOGS_API_URL)}/api/logs/structured?${params}`, {
|
|
725
|
+
method: "POST",
|
|
726
|
+
headers: target.apiKey ? { authorization: `Bearer ${target.apiKey}` } : undefined,
|
|
727
|
+
body: {
|
|
728
|
+
timestamp: report.generatedAt,
|
|
729
|
+
level: report.summary.totals.down > 0 || report.summary.totals.openIncidents > 0 ? "warn" : "info",
|
|
730
|
+
message: report.subject,
|
|
731
|
+
report: report.json
|
|
732
|
+
}
|
|
733
|
+
}, fetchImpl, timeoutMs, secretsForTarget(target));
|
|
734
|
+
}
|
|
735
|
+
async function requestJson(channel, url, options, fetchImpl, timeoutMs, secrets = []) {
|
|
736
|
+
const controller = new AbortController;
|
|
737
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
738
|
+
try {
|
|
739
|
+
const response = await fetchImpl(url, {
|
|
740
|
+
method: options.method,
|
|
741
|
+
signal: controller.signal,
|
|
742
|
+
headers: {
|
|
743
|
+
"content-type": "application/json",
|
|
744
|
+
accept: "application/json",
|
|
745
|
+
...options.headers
|
|
746
|
+
},
|
|
747
|
+
body: JSON.stringify(options.body)
|
|
748
|
+
});
|
|
749
|
+
const text = await response.text();
|
|
750
|
+
const data = parseMaybeJson(text);
|
|
751
|
+
if (!response.ok) {
|
|
752
|
+
return { channel, ok: false, status: response.status, error: errorFromResponse(data, response.statusText, secrets) };
|
|
753
|
+
}
|
|
754
|
+
return { channel, ok: true, status: response.status, id: redactOptional(idFromResponse(data), secrets) };
|
|
755
|
+
} catch (error) {
|
|
756
|
+
const message = error instanceof Error && error.name === "AbortError" ? "request timed out" : error instanceof Error ? error.message : String(error);
|
|
757
|
+
return { channel, ok: false, error: redactSecrets(message, secrets) };
|
|
758
|
+
} finally {
|
|
759
|
+
clearTimeout(timer);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function hasTargets(value) {
|
|
763
|
+
return splitTargets(value).length > 0;
|
|
764
|
+
}
|
|
765
|
+
function splitTargets(value) {
|
|
766
|
+
if (!value)
|
|
767
|
+
return [];
|
|
768
|
+
const values = Array.isArray(value) ? value : value.split(",");
|
|
769
|
+
return values.map((item) => item.trim()).filter(Boolean);
|
|
770
|
+
}
|
|
771
|
+
function normalizeUrl(value) {
|
|
772
|
+
const parsed = new URL(value.trim());
|
|
773
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
774
|
+
throw new Error("Integration API URL must use http or https");
|
|
775
|
+
}
|
|
776
|
+
return parsed.toString().replace(/\/$/, "");
|
|
777
|
+
}
|
|
778
|
+
function truncateSms(value) {
|
|
779
|
+
return value.length > 1400 ? `${value.slice(0, 1397)}...` : value;
|
|
780
|
+
}
|
|
781
|
+
function parseMaybeJson(text) {
|
|
782
|
+
if (!text.trim())
|
|
783
|
+
return {};
|
|
784
|
+
try {
|
|
785
|
+
return JSON.parse(text);
|
|
786
|
+
} catch {
|
|
787
|
+
return { message: text };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function idFromResponse(data) {
|
|
791
|
+
if (!data || typeof data !== "object")
|
|
792
|
+
return;
|
|
793
|
+
const record = data;
|
|
794
|
+
for (const key of ["id", "message_id", "event_id"]) {
|
|
795
|
+
if (typeof record[key] === "string")
|
|
796
|
+
return record[key];
|
|
797
|
+
}
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
function errorFromResponse(data, fallback, secrets = []) {
|
|
801
|
+
if (data && typeof data === "object") {
|
|
802
|
+
const record = data;
|
|
803
|
+
if (typeof record.error === "string")
|
|
804
|
+
return redactSecrets(record.error, secrets);
|
|
805
|
+
if (typeof record.message === "string")
|
|
806
|
+
return redactSecrets(record.message, secrets);
|
|
807
|
+
}
|
|
808
|
+
return redactSecrets(fallback, secrets);
|
|
809
|
+
}
|
|
810
|
+
function env(...keys) {
|
|
811
|
+
for (const key of keys) {
|
|
812
|
+
const value = process.env[key]?.trim();
|
|
813
|
+
if (value)
|
|
814
|
+
return value;
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
function escapeHtml(value) {
|
|
819
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
820
|
+
}
|
|
821
|
+
function secretsForTarget(target) {
|
|
822
|
+
const values = new Set;
|
|
823
|
+
for (const key of ["sendKey", "apiKey"]) {
|
|
824
|
+
const value = target[key];
|
|
825
|
+
if (typeof value === "string" && value.trim())
|
|
826
|
+
values.add(value.trim());
|
|
827
|
+
}
|
|
828
|
+
const apiUrl = target.apiUrl;
|
|
829
|
+
if (apiUrl) {
|
|
830
|
+
try {
|
|
831
|
+
const parsed = new URL(apiUrl);
|
|
832
|
+
if (parsed.username)
|
|
833
|
+
values.add(decodeURIComponent(parsed.username));
|
|
834
|
+
if (parsed.password)
|
|
835
|
+
values.add(decodeURIComponent(parsed.password));
|
|
836
|
+
} catch {}
|
|
837
|
+
}
|
|
838
|
+
return [...values];
|
|
839
|
+
}
|
|
840
|
+
function redactSecrets(value, secrets = []) {
|
|
841
|
+
let redacted = value;
|
|
842
|
+
for (const secret of secrets) {
|
|
843
|
+
if (secret.length >= 3)
|
|
844
|
+
redacted = redacted.split(secret).join("[REDACTED]");
|
|
845
|
+
}
|
|
846
|
+
return redacted.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/\besk_[A-Za-z0-9._~+/=-]+/g, "esk_[REDACTED]");
|
|
847
|
+
}
|
|
848
|
+
function redactOptional(value, secrets) {
|
|
849
|
+
return value === undefined ? undefined : redactSecrets(value, secrets);
|
|
850
|
+
}
|
|
851
|
+
|
|
497
852
|
// src/service.ts
|
|
853
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
498
854
|
class UptimeService {
|
|
499
855
|
store;
|
|
500
856
|
checkRunner;
|
|
857
|
+
leaseOwner = `svc_${randomUUID2().replace(/-/g, "").slice(0, 18)}`;
|
|
501
858
|
inFlightChecks = new Set;
|
|
502
859
|
constructor(options = {}) {
|
|
503
860
|
this.store = options.store ?? new UptimeStore(options);
|
|
@@ -530,6 +887,12 @@ class UptimeService {
|
|
|
530
887
|
summary() {
|
|
531
888
|
return this.store.summary();
|
|
532
889
|
}
|
|
890
|
+
buildReport(options = {}) {
|
|
891
|
+
return buildUptimeReport(this.summary(), options);
|
|
892
|
+
}
|
|
893
|
+
async sendReport(options = {}) {
|
|
894
|
+
return sendUptimeReport(this.summary(), options);
|
|
895
|
+
}
|
|
533
896
|
async checkMonitor(idOrName) {
|
|
534
897
|
const monitor = this.store.getMonitor(idOrName);
|
|
535
898
|
if (!monitor)
|
|
@@ -538,6 +901,10 @@ class UptimeService {
|
|
|
538
901
|
throw new Error(`Monitor is disabled: ${monitor.name}`);
|
|
539
902
|
if (this.inFlightChecks.has(monitor.id))
|
|
540
903
|
throw new Error(`Monitor check already in progress: ${monitor.name}`);
|
|
904
|
+
const leaseTtlMs = Math.max(60000, (monitor.retryCount + 1) * monitor.timeoutMs + 1e4);
|
|
905
|
+
if (!this.store.acquireCheckLease(monitor.id, this.leaseOwner, leaseTtlMs)) {
|
|
906
|
+
throw new MonitorCheckBusyError(`Monitor check already in progress: ${monitor.name}`);
|
|
907
|
+
}
|
|
541
908
|
this.inFlightChecks.add(monitor.id);
|
|
542
909
|
try {
|
|
543
910
|
let attemptCount = 0;
|
|
@@ -555,10 +922,12 @@ class UptimeService {
|
|
|
555
922
|
latencyMs: last.latencyMs,
|
|
556
923
|
statusCode: last.statusCode ?? null,
|
|
557
924
|
error: last.error ?? null,
|
|
558
|
-
attemptCount
|
|
925
|
+
attemptCount,
|
|
926
|
+
expectedMonitorRevision: monitor.revision
|
|
559
927
|
});
|
|
560
928
|
} finally {
|
|
561
929
|
this.inFlightChecks.delete(monitor.id);
|
|
930
|
+
this.store.releaseCheckLease(monitor.id, this.leaseOwner);
|
|
562
931
|
}
|
|
563
932
|
}
|
|
564
933
|
async checkAll() {
|
|
@@ -587,7 +956,13 @@ class UptimeService {
|
|
|
587
956
|
const current = this.store.getMonitor(monitor.id);
|
|
588
957
|
if (!current || !this.isDue(current, now))
|
|
589
958
|
continue;
|
|
590
|
-
|
|
959
|
+
try {
|
|
960
|
+
results.push(await this.checkMonitor(current.id));
|
|
961
|
+
} catch (error) {
|
|
962
|
+
if (error instanceof MonitorCheckBusyError || error instanceof StaleCheckResultError)
|
|
963
|
+
continue;
|
|
964
|
+
throw error;
|
|
965
|
+
}
|
|
591
966
|
}
|
|
592
967
|
return results;
|
|
593
968
|
}
|
|
@@ -606,6 +981,13 @@ function createUptimeClient(options = {}) {
|
|
|
606
981
|
return new UptimeService(options);
|
|
607
982
|
}
|
|
608
983
|
|
|
984
|
+
class MonitorCheckBusyError extends Error {
|
|
985
|
+
constructor(message) {
|
|
986
|
+
super(message);
|
|
987
|
+
this.name = "MonitorCheckBusyError";
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
609
991
|
// src/dashboard.ts
|
|
610
992
|
function dashboardHtml() {
|
|
611
993
|
return `<!doctype html>
|
|
@@ -957,11 +1339,11 @@ function dashboardHtml() {
|
|
|
957
1339
|
}
|
|
958
1340
|
|
|
959
1341
|
// src/api.ts
|
|
960
|
-
function createApiHandler(service) {
|
|
1342
|
+
function createApiHandler(service, options = {}) {
|
|
961
1343
|
return async (request) => {
|
|
962
1344
|
const url = new URL(request.url);
|
|
963
1345
|
try {
|
|
964
|
-
validateLocalMutationRequest(request, url);
|
|
1346
|
+
validateLocalMutationRequest(request, url, options);
|
|
965
1347
|
if (request.method === "GET" && url.pathname === "/") {
|
|
966
1348
|
return html(dashboardHtml());
|
|
967
1349
|
}
|
|
@@ -971,6 +1353,13 @@ function createApiHandler(service) {
|
|
|
971
1353
|
if (request.method === "GET" && url.pathname === "/api/summary") {
|
|
972
1354
|
return json(service.summary());
|
|
973
1355
|
}
|
|
1356
|
+
if (request.method === "GET" && url.pathname === "/api/report") {
|
|
1357
|
+
return json(service.buildReport());
|
|
1358
|
+
}
|
|
1359
|
+
if (request.method === "POST" && url.pathname === "/api/report") {
|
|
1360
|
+
const input = await jsonBody(request);
|
|
1361
|
+
return json(await service.sendReport({ ...input, fetchImpl: options.fetchImpl }));
|
|
1362
|
+
}
|
|
974
1363
|
if (request.method === "GET" && url.pathname === "/api/monitors") {
|
|
975
1364
|
return json(service.listMonitors({ includeDisabled: url.searchParams.get("includeDisabled") === "true" }));
|
|
976
1365
|
}
|
|
@@ -1023,7 +1412,11 @@ function serveUptime(options = {}) {
|
|
|
1023
1412
|
const server = Bun.serve({
|
|
1024
1413
|
hostname: options.host ?? "127.0.0.1",
|
|
1025
1414
|
port: options.port ?? 3899,
|
|
1026
|
-
fetch: createApiHandler(service
|
|
1415
|
+
fetch: createApiHandler(service, {
|
|
1416
|
+
apiToken: options.apiToken,
|
|
1417
|
+
allowUnsafeRemoteMutations: options.allowUnsafeRemoteMutations,
|
|
1418
|
+
trustedLoopback: isLoopbackHost(options.host ?? "127.0.0.1")
|
|
1419
|
+
})
|
|
1027
1420
|
});
|
|
1028
1421
|
return { server, service, scheduler };
|
|
1029
1422
|
}
|
|
@@ -1051,14 +1444,31 @@ function numericParam(url, name, fallback) {
|
|
|
1051
1444
|
const parsed = Number(raw);
|
|
1052
1445
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1053
1446
|
}
|
|
1054
|
-
function validateLocalMutationRequest(request, url) {
|
|
1447
|
+
function validateLocalMutationRequest(request, url, options) {
|
|
1055
1448
|
if (!["POST", "PATCH", "DELETE"].includes(request.method))
|
|
1056
1449
|
return;
|
|
1450
|
+
const apiToken = options.apiToken ?? process.env.HASNA_UPTIME_API_TOKEN;
|
|
1451
|
+
const hasToken = apiToken ? hasValidApiToken(request, apiToken) : false;
|
|
1452
|
+
const allowUnsafeRemote = options.allowUnsafeRemoteMutations || process.env.HASNA_UPTIME_ALLOW_REMOTE_MUTATIONS === "1";
|
|
1453
|
+
const trustedLoopback = options.trustedLoopback ?? isLoopbackHost(url.hostname);
|
|
1454
|
+
if (!allowUnsafeRemote && !hasToken && (!trustedLoopback || !isLoopbackHost(url.hostname))) {
|
|
1455
|
+
throw new ApiError("non-loopback host rejected for local mutation", 403);
|
|
1456
|
+
}
|
|
1057
1457
|
const origin = request.headers.get("origin");
|
|
1058
1458
|
if (origin && origin !== `${url.protocol}//${url.host}`) {
|
|
1059
1459
|
throw new ApiError("cross-origin mutation rejected", 403);
|
|
1060
1460
|
}
|
|
1061
1461
|
}
|
|
1462
|
+
function isLoopbackHost(hostname) {
|
|
1463
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1464
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
1465
|
+
}
|
|
1466
|
+
function hasValidApiToken(request, token) {
|
|
1467
|
+
const authorization = request.headers.get("authorization") ?? "";
|
|
1468
|
+
const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
|
|
1469
|
+
const headerToken = request.headers.get("x-uptime-token")?.trim();
|
|
1470
|
+
return bearer === token || headerToken === token;
|
|
1471
|
+
}
|
|
1062
1472
|
async function jsonBody(request) {
|
|
1063
1473
|
const contentType = request.headers.get("content-type") ?? "";
|
|
1064
1474
|
const mediaType = contentType.split(";")[0]?.trim().toLowerCase();
|
|
@@ -1079,12 +1489,14 @@ export {
|
|
|
1079
1489
|
uptimeHome,
|
|
1080
1490
|
uptimeDbPath,
|
|
1081
1491
|
serveUptime,
|
|
1492
|
+
sendUptimeReport,
|
|
1082
1493
|
runTcpCheck,
|
|
1083
1494
|
runMonitorCheck,
|
|
1084
1495
|
runHttpCheck,
|
|
1085
1496
|
ensureUptimeHome,
|
|
1086
1497
|
createUptimeClient,
|
|
1087
1498
|
createApiHandler,
|
|
1499
|
+
buildUptimeReport,
|
|
1088
1500
|
UptimeStore,
|
|
1089
1501
|
UptimeService
|
|
1090
1502
|
};
|
package/dist/mcp/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAY9C,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAY9C,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,SAAS,CAwN/E"}
|