mergen-server 1.4.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/LICENSE +57 -0
- package/README.md +96 -0
- package/dist/__stubs__/calibration.js +471 -0
- package/dist/__stubs__/causal.js +601 -0
- package/dist/__stubs__/closed-source.js +150 -0
- package/dist/__stubs__/license.js +35 -0
- package/dist/__stubs__/plans.js +197 -0
- package/dist/app.js +467 -0
- package/dist/cli.js +250 -0
- package/dist/commands/agent-identity.js +79 -0
- package/dist/commands/gate.js +107 -0
- package/dist/commands/github.js +632 -0
- package/dist/commands/incident.js +644 -0
- package/dist/commands/policy.js +710 -0
- package/dist/commands/scan.js +243 -0
- package/dist/commands/setup.js +1218 -0
- package/dist/commands/shared.js +109 -0
- package/dist/commands/team.js +809 -0
- package/dist/datadog/blame-attribution.js +228 -0
- package/dist/datadog/client.js +159 -0
- package/dist/datadog/compactor.js +169 -0
- package/dist/datadog/fingerprinter.js +34 -0
- package/dist/datadog/incident-state.js +21 -0
- package/dist/datadog/line-matcher.js +56 -0
- package/dist/datadog/memory-store.js +399 -0
- package/dist/datadog/otel-trace.js +67 -0
- package/dist/index.js +505 -0
- package/dist/intelligence/action-risk.js +132 -0
- package/dist/intelligence/activity-feed.js +40 -0
- package/dist/intelligence/agent-identity.js +115 -0
- package/dist/intelligence/agent-pipeline.js +329 -0
- package/dist/intelligence/agent-profiles.js +81 -0
- package/dist/intelligence/ai-commit.js +16 -0
- package/dist/intelligence/approval-analytics.js +42 -0
- package/dist/intelligence/approval-events.js +5 -0
- package/dist/intelligence/arch-boundaries.js +94 -0
- package/dist/intelligence/arch-graph.js +117 -0
- package/dist/intelligence/autonomy.js +397 -0
- package/dist/intelligence/azure-broker.js +57 -0
- package/dist/intelligence/azure-proxy-sessions.js +49 -0
- package/dist/intelligence/baseline.js +1 -0
- package/dist/intelligence/behavior-baseline.js +134 -0
- package/dist/intelligence/billing.js +1 -0
- package/dist/intelligence/blast-radius.js +224 -0
- package/dist/intelligence/bypass.js +157 -0
- package/dist/intelligence/calibration-classifier.js +77 -0
- package/dist/intelligence/calibration-git-sync.js +74 -0
- package/dist/intelligence/calibration.js +1 -0
- package/dist/intelligence/cascade-detector.js +58 -0
- package/dist/intelligence/case-study-generator.js +126 -0
- package/dist/intelligence/causal-graph.js +126 -0
- package/dist/intelligence/causal.js +1 -0
- package/dist/intelligence/change-risk.js +102 -0
- package/dist/intelligence/compliance-report.js +134 -0
- package/dist/intelligence/confidence-report.js +85 -0
- package/dist/intelligence/corpus-to-policy.js +140 -0
- package/dist/intelligence/credential-broker.js +112 -0
- package/dist/intelligence/credential-misuse.js +39 -0
- package/dist/intelligence/debug-sessions.js +108 -0
- package/dist/intelligence/decision-escalation.js +36 -0
- package/dist/intelligence/decision-memory.js +47 -0
- package/dist/intelligence/decision-similarity.js +85 -0
- package/dist/intelligence/default-safety-tests.js +53 -0
- package/dist/intelligence/degradation-watcher.js +108 -0
- package/dist/intelligence/detector-plugins.js +70 -0
- package/dist/intelligence/detectors.js +1 -0
- package/dist/intelligence/device-auth.js +138 -0
- package/dist/intelligence/diff-size.js +42 -0
- package/dist/intelligence/ebpf-verify.js +45 -0
- package/dist/intelligence/enterprise-policy-engine.js +1122 -0
- package/dist/intelligence/error-fingerprint.js +1 -0
- package/dist/intelligence/execution-gate.js +100 -0
- package/dist/intelligence/execution-mode.js +22 -0
- package/dist/intelligence/gate-analytics.js +256 -0
- package/dist/intelligence/gate-decision.js +64 -0
- package/dist/intelligence/gcp-broker.js +50 -0
- package/dist/intelligence/git-adr-sync.js +207 -0
- package/dist/intelligence/hitl-hold.js +460 -0
- package/dist/intelligence/hypothesis-history.js +93 -0
- package/dist/intelligence/idp-identity.js +83 -0
- package/dist/intelligence/impl-critic.js +216 -0
- package/dist/intelligence/incident-autopilot.js +622 -0
- package/dist/intelligence/incident-replay.js +162 -0
- package/dist/intelligence/incident-result-cache.js +33 -0
- package/dist/intelligence/incident-similarity.js +58 -0
- package/dist/intelligence/infra-detectors.js +312 -0
- package/dist/intelligence/license.js +185 -0
- package/dist/intelligence/llm-spokesperson.js +62 -0
- package/dist/intelligence/mcp-prompts.js +320 -0
- package/dist/intelligence/mcp-resources.js +171 -0
- package/dist/intelligence/normalize.js +108 -0
- package/dist/intelligence/notifications.js +83 -0
- package/dist/intelligence/oidc-issuer.js +87 -0
- package/dist/intelligence/override-corpus.js +597 -0
- package/dist/intelligence/planning-gate.js +84 -0
- package/dist/intelligence/plans.js +223 -0
- package/dist/intelligence/platt-scaling.js +164 -0
- package/dist/intelligence/policy-proposals.js +84 -0
- package/dist/intelligence/policy-suggester.js +161 -0
- package/dist/intelligence/policy-sync.js +103 -0
- package/dist/intelligence/policy-test-runner.js +35 -0
- package/dist/intelligence/postmortem-parser.js +181 -0
- package/dist/intelligence/postmortem-retrieval.js +224 -0
- package/dist/intelligence/postmortem-store.js +596 -0
- package/dist/intelligence/pr-commenter.js +81 -0
- package/dist/intelligence/pr-policy-gate.js +38 -0
- package/dist/intelligence/pr-shadow-analyzer.js +229 -0
- package/dist/intelligence/process-tree.js +108 -0
- package/dist/intelligence/prompts.js +1 -0
- package/dist/intelligence/replay.js +38 -0
- package/dist/intelligence/repro-steps.js +1 -0
- package/dist/intelligence/resource-extractors.js +0 -0
- package/dist/intelligence/resource-file-context.js +138 -0
- package/dist/intelligence/risk-score.js +22 -0
- package/dist/intelligence/rollback.js +137 -0
- package/dist/intelligence/runbook-updater.js +143 -0
- package/dist/intelligence/sandbox.js +194 -0
- package/dist/intelligence/script-trust.js +104 -0
- package/dist/intelligence/session-metrics.js +67 -0
- package/dist/intelligence/session-threat-tracker.js +231 -0
- package/dist/intelligence/shadow-digest-cron.js +81 -0
- package/dist/intelligence/shadow-log.js +284 -0
- package/dist/intelligence/shell-ast.js +145 -0
- package/dist/intelligence/siem-forward.js +104 -0
- package/dist/intelligence/slack-digest.js +151 -0
- package/dist/intelligence/slack-override-loop.js +217 -0
- package/dist/intelligence/slack-routing.js +67 -0
- package/dist/intelligence/slack.js +900 -0
- package/dist/intelligence/sql-ast.js +31 -0
- package/dist/intelligence/team.js +1 -0
- package/dist/intelligence/telemetry.js +76 -0
- package/dist/intelligence/threshold-optimizer.js +75 -0
- package/dist/intelligence/token-budget.js +66 -0
- package/dist/intelligence/tool-guard.js +708 -0
- package/dist/intelligence/tool-manifest 2.js +74 -0
- package/dist/intelligence/tool-manifest.js +125 -0
- package/dist/intelligence/tools-analysis.js +857 -0
- package/dist/intelligence/tools-arch.js +152 -0
- package/dist/intelligence/tools-autonomy.js +495 -0
- package/dist/intelligence/tools-blast-radius.js +140 -0
- package/dist/intelligence/tools-browser.js +431 -0
- package/dist/intelligence/tools-change-timeline.js +154 -0
- package/dist/intelligence/tools-credentials.js +117 -0
- package/dist/intelligence/tools-datadog.js +233 -0
- package/dist/intelligence/tools-debug-sessions.js +209 -0
- package/dist/intelligence/tools-discovery.js +284 -0
- package/dist/intelligence/tools-infra.js +612 -0
- package/dist/intelligence/tools-intent.js +128 -0
- package/dist/intelligence/tools-memory.js +514 -0
- package/dist/intelligence/tools-runbook.js +951 -0
- package/dist/intelligence/tools-sessions.js +107 -0
- package/dist/intelligence/tools-state.js +232 -0
- package/dist/intelligence/tools-utility.js +430 -0
- package/dist/intelligence/tools-validate.js +215 -0
- package/dist/intelligence/tools.js +1 -0
- package/dist/intelligence/trace-context.js +11 -0
- package/dist/intelligence/unclassified-clusters.js +87 -0
- package/dist/intelligence/usage.js +195 -0
- package/dist/routes/active-authors.js +63 -0
- package/dist/routes/activity-feed.js +21 -0
- package/dist/routes/adr.js +117 -0
- package/dist/routes/agent-activity.js +184 -0
- package/dist/routes/agent-blunders.js +163 -0
- package/dist/routes/agents.js +175 -0
- package/dist/routes/api-keys.js +65 -0
- package/dist/routes/arch.js +75 -0
- package/dist/routes/audit-export.js +93 -0
- package/dist/routes/billing-dashboard.js +158 -0
- package/dist/routes/billing-outcome.js +77 -0
- package/dist/routes/calibration.js +305 -0
- package/dist/routes/ci-gate-history.js +20 -0
- package/dist/routes/ci-gate.js +232 -0
- package/dist/routes/ci.js +293 -0
- package/dist/routes/compliance-report.js +24 -0
- package/dist/routes/confidence.js +30 -0
- package/dist/routes/credentials.js +119 -0
- package/dist/routes/dashboard.js +1407 -0
- package/dist/routes/decision-memory.js +42 -0
- package/dist/routes/demo.js +844 -0
- package/dist/routes/device-auth-stub.js +173 -0
- package/dist/routes/explain-why.js +39 -0
- package/dist/routes/gate-analytics.js +213 -0
- package/dist/routes/gate.js +28 -0
- package/dist/routes/github-webhook.js +364 -0
- package/dist/routes/habituation.js +30 -0
- package/dist/routes/health-integrations.js +243 -0
- package/dist/routes/heartbeats.js +45 -0
- package/dist/routes/hitl.js +364 -0
- package/dist/routes/impact-report.js +645 -0
- package/dist/routes/incident-webhook.js +63 -0
- package/dist/routes/incidents.js +366 -0
- package/dist/routes/layers.js +62 -0
- package/dist/routes/license.js +187 -0
- package/dist/routes/oidc-issuer.js +30 -0
- package/dist/routes/onboarding.js +170 -0
- package/dist/routes/otel.js +67 -0
- package/dist/routes/otlp-receiver.js +179 -0
- package/dist/routes/overrides.js +241 -0
- package/dist/routes/pagerduty.js +258 -0
- package/dist/routes/policies.js +809 -0
- package/dist/routes/policy-nl.js +169 -0
- package/dist/routes/postmortem.js +102 -0
- package/dist/routes/pr-shadow.js +47 -0
- package/dist/routes/rbac.js +61 -0
- package/dist/routes/replay.js +17 -0
- package/dist/routes/risk-report.js +164 -0
- package/dist/routes/runbooks.js +125 -0
- package/dist/routes/safety-test.js +174 -0
- package/dist/routes/sdk.js +222 -0
- package/dist/routes/sensor.js +819 -0
- package/dist/routes/sentry.js +140 -0
- package/dist/routes/sessions.js +43 -0
- package/dist/routes/setup-ui.js +594 -0
- package/dist/routes/shadow-report.js +107 -0
- package/dist/routes/slack-routing.js +171 -0
- package/dist/routes/team-usage.js +71 -0
- package/dist/routes/telemetry.js +28 -0
- package/dist/routes/tenants.js +199 -0
- package/dist/routes/tickets.js +167 -0
- package/dist/routes/validate.js +13 -0
- package/dist/routes/war-room.js +113 -0
- package/dist/scripts/check-arch.js +22 -0
- package/dist/seeds/community-corpus.js +176 -0
- package/dist/seeds/corpus.js +1224 -0
- package/dist/sensor/action-ledger.js +327 -0
- package/dist/sensor/adr-store.js +140 -0
- package/dist/sensor/agent-blunder-store.js +370 -0
- package/dist/sensor/agent-context-store.js +225 -0
- package/dist/sensor/agent-memory-store.js +231 -0
- package/dist/sensor/audit-fetch.js +23 -0
- package/dist/sensor/audit-log.js +273 -0
- package/dist/sensor/buffer-schemas.js +225 -0
- package/dist/sensor/buffer.js +599 -0
- package/dist/sensor/bypass-tracker.js +93 -0
- package/dist/sensor/ci-gate-history.js +70 -0
- package/dist/sensor/cloud-auth.js +183 -0
- package/dist/sensor/commit-context-store.js +235 -0
- package/dist/sensor/docker-log-stream.js +164 -0
- package/dist/sensor/docker-monitor.js +122 -0
- package/dist/sensor/extended-buffer.js +46 -0
- package/dist/sensor/feedback-token.js +41 -0
- package/dist/sensor/file-lock.js +6 -0
- package/dist/sensor/fs-watcher.js +106 -0
- package/dist/sensor/gate-heartbeat.js +122 -0
- package/dist/sensor/git-suspect.js +136 -0
- package/dist/sensor/habituation-store.js +77 -0
- package/dist/sensor/heartbeat-monitor.js +133 -0
- package/dist/sensor/incident-store.js +340 -0
- package/dist/sensor/infra-normalizer.js +513 -0
- package/dist/sensor/ingest.js +339 -0
- package/dist/sensor/jest-reporter.js +52 -0
- package/dist/sensor/k8s-events.js +132 -0
- package/dist/sensor/layer2-store.js +111 -0
- package/dist/sensor/layer3-store.js +230 -0
- package/dist/sensor/layer4-store.js +147 -0
- package/dist/sensor/logger.js +13 -0
- package/dist/sensor/otel-exporter.js +161 -0
- package/dist/sensor/paths.js +71 -0
- package/dist/sensor/policy-history.js +147 -0
- package/dist/sensor/pr-shadow-store.js +83 -0
- package/dist/sensor/process-watcher.js +162 -0
- package/dist/sensor/rbac.js +92 -0
- package/dist/sensor/redact.js +114 -0
- package/dist/sensor/redis-store.js +191 -0
- package/dist/sensor/route-reachability.js +64 -0
- package/dist/sensor/security-utils.js +18 -0
- package/dist/sensor/service-graph.js +170 -0
- package/dist/sensor/service-topology.js +227 -0
- package/dist/sensor/session-history.js +74 -0
- package/dist/sensor/session-persist.js +38 -0
- package/dist/sensor/shadow-promote.js +122 -0
- package/dist/sensor/sourcemap.js +281 -0
- package/dist/sensor/sqlite-store.js +205 -0
- package/dist/sensor/sso.js +164 -0
- package/dist/sensor/vitest-reporter.js +65 -0
- package/dist/sensor/watcher.js +48 -0
- package/dist/storage/interfaces.js +0 -0
- package/dist/storage/pg/pg-action-ledger.js +138 -0
- package/dist/storage/pg/pg-approval-store.js +107 -0
- package/dist/storage/pg/pg-blunder-store.js +193 -0
- package/dist/storage/pg/pg-ci-gate-history.js +83 -0
- package/dist/storage/pg/pg-client.js +24 -0
- package/dist/storage/pg/pg-event-store.js +63 -0
- package/dist/storage/pg/pg-incident-store.js +190 -0
- package/dist/storage/pg/pg-migrations.js +26 -0
- package/dist/storage/pg/pg-override-corpus.js +447 -0
- package/dist/storage/pg/pg-shadow-log.js +129 -0
- package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
- package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
- package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
- package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
- package/dist/storage/sqlite/sqlite-event-store.js +29 -0
- package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
- package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
- package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
- package/dist/storage/store-factory.js +55 -0
- package/dist/storage/store-registry.js +31 -0
- package/dist/update-checker.js +107 -0
- package/dist/workers/autopilot-worker.js +28 -0
- package/dist/workers/notification-worker.js +28 -0
- package/dist/workers/queues.js +58 -0
- package/dist/workers/validation-worker.js +24 -0
- package/dist/workers/worker-registry.js +18 -0
- package/package.json +123 -0
- package/sdk/mergen-inject.js +260 -0
- package/sdk/node.js +313 -0
- package/sdk/vite-plugin.ts +57 -0
- package/sdk/webpack-plugin.js +106 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { resolve, dirname } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import logger from "../sensor/logger.js";
|
|
7
|
+
function createSetupRouter() {
|
|
8
|
+
const router = Router();
|
|
9
|
+
router.use((req, res, next) => {
|
|
10
|
+
const remoteIp = (req.socket.remoteAddress ?? "").replace(/^::ffff:/, "");
|
|
11
|
+
const isLocalhost = remoteIp === "127.0.0.1" || remoteIp === "::1" || remoteIp === "";
|
|
12
|
+
if (!isLocalhost && process.env.NODE_ENV !== "test") {
|
|
13
|
+
logger.warn({ remoteIp, path: req.path }, "setup-ui: blocked remote attempt to access setup wizard");
|
|
14
|
+
res.status(403).send("Forbidden \u2014 setup wizard is only accessible via localhost loopback interface (127.0.0.1) for security.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
next();
|
|
18
|
+
});
|
|
19
|
+
router.get("/setup", (req, res) => {
|
|
20
|
+
const preServer = typeof req.query.server === "string" ? req.query.server : "";
|
|
21
|
+
const preToken = typeof req.query.token === "string" ? req.query.token : "";
|
|
22
|
+
const html = preServer ? SETUP_HTML.replace("</head>", `<script>window.__MERGEN_PREFILL__={server:${JSON.stringify(preServer)},token:${JSON.stringify(preToken)}};</script></head>`) : SETUP_HTML;
|
|
23
|
+
res.send(html);
|
|
24
|
+
});
|
|
25
|
+
router.post("/api/setup/ide", (req, res) => {
|
|
26
|
+
const { ide } = req.body;
|
|
27
|
+
if (!ide || !["cursor", "claude-code", "vscode", "windsurf"].includes(ide)) {
|
|
28
|
+
res.status(400).json({ error: "Invalid IDE" });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const serverPath = resolve(__dirname, "../index.js");
|
|
33
|
+
configureIDE(ide, serverPath);
|
|
34
|
+
res.json({ success: true, message: `${ide} configured successfully` });
|
|
35
|
+
} catch (err) {
|
|
36
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Configuration failed" });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
router.get("/api/setup/test", async (_req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch("http://127.0.0.1:3000/ingest", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
type: "console",
|
|
46
|
+
level: "log",
|
|
47
|
+
args: ["\u2713 Mergen setup test"],
|
|
48
|
+
url: "http://127.0.0.1:3000/setup",
|
|
49
|
+
timestamp: Date.now()
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
if (response.ok) {
|
|
53
|
+
res.json({ success: true, message: "Pipeline working! Test event ingested successfully." });
|
|
54
|
+
} else {
|
|
55
|
+
res.json({ success: false, message: `Server returned ${response.status}` });
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
res.json({ success: false, message: err instanceof Error ? err.message : "Test failed" });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
router.get("/api/setup/status", (_req, res) => {
|
|
62
|
+
res.json({
|
|
63
|
+
server: true,
|
|
64
|
+
ide: checkIDEConfigured(),
|
|
65
|
+
slack: !!process.env.MERGEN_SLACK_BOT_TOKEN,
|
|
66
|
+
slackChannel: process.env.MERGEN_SLACK_CHANNEL ?? "",
|
|
67
|
+
autopilot: process.env.MERGEN_AUTOPILOT === "true",
|
|
68
|
+
pagerdutyWebhookUrl: `${process.env.MERGEN_DASHBOARD_URL ?? "http://your-server:3000"}/webhooks/pagerduty`,
|
|
69
|
+
slackEventsWebhookUrl: `${process.env.MERGEN_DASHBOARD_URL ?? "http://your-server:3000"}/webhooks/slack/events`,
|
|
70
|
+
adrWebhookUrl: `${process.env.MERGEN_DASHBOARD_URL ?? "http://your-server:3000"}/ci/adr`
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
router.post("/api/setup/test-slack", async (req, res) => {
|
|
74
|
+
const { token, channel } = req.body ?? {};
|
|
75
|
+
const tok = token?.trim() || process.env.MERGEN_SLACK_BOT_TOKEN;
|
|
76
|
+
if (!tok) {
|
|
77
|
+
res.json({ ok: false, error: "No token provided" });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const r = await fetch("https://slack.com/api/auth.test", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json" },
|
|
84
|
+
signal: AbortSignal.timeout(5e3)
|
|
85
|
+
});
|
|
86
|
+
const d = await r.json();
|
|
87
|
+
if (!d.ok) {
|
|
88
|
+
res.json({ ok: false, error: d.error });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (channel?.trim()) {
|
|
92
|
+
const ch = await fetch("https://slack.com/api/chat.postMessage", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { Authorization: `Bearer ${tok}`, "Content-Type": "application/json" },
|
|
95
|
+
body: JSON.stringify({ channel: channel.trim(), text: "\u2705 Mergen connected \u2014 autonomous incident triage is active." }),
|
|
96
|
+
signal: AbortSignal.timeout(5e3)
|
|
97
|
+
});
|
|
98
|
+
const cd = await ch.json();
|
|
99
|
+
if (!cd.ok) {
|
|
100
|
+
res.json({ ok: false, error: `Token valid but channel post failed: ${cd.error}` });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
res.json({ ok: true, team: d.team, user: d.user });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
res.json({ ok: false, error: err instanceof Error ? err.message : "Network error" });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
router.get("/api/setup/test-otlp", async (_req, res) => {
|
|
110
|
+
try {
|
|
111
|
+
const r = await fetch("http://127.0.0.1:3000/health", { signal: AbortSignal.timeout(1e3) });
|
|
112
|
+
if (!r.ok) {
|
|
113
|
+
res.json({ ok: false, receiving: false });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const d = await r.json();
|
|
117
|
+
const hasRecent = d.lastEventAt ? Date.now() - d.lastEventAt < 5 * 60 * 1e3 : false;
|
|
118
|
+
res.json({ ok: true, receiving: hasRecent, buffered: d.buffered ?? 0, lastEventAt: d.lastEventAt });
|
|
119
|
+
} catch {
|
|
120
|
+
res.json({ ok: false, receiving: false });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return router;
|
|
124
|
+
}
|
|
125
|
+
function configureIDE(ide, serverPath) {
|
|
126
|
+
switch (ide) {
|
|
127
|
+
case "claude-code":
|
|
128
|
+
try {
|
|
129
|
+
execSync(`claude mcp add mergen --transport stdio -- node "${serverPath}"`, {
|
|
130
|
+
stdio: "ignore"
|
|
131
|
+
});
|
|
132
|
+
} catch {
|
|
133
|
+
throw new Error("Claude Code CLI not found. Install from: https://claude.ai/download");
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "cursor": {
|
|
137
|
+
const configPath = resolve(homedir(), ".cursor", "mcp.json");
|
|
138
|
+
const config = {
|
|
139
|
+
mcpServers: {
|
|
140
|
+
mergen: {
|
|
141
|
+
command: "node",
|
|
142
|
+
args: [serverPath]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
147
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "vscode": {
|
|
151
|
+
const configPath = resolve(homedir(), ".vscode", "mcp.json");
|
|
152
|
+
const config = {
|
|
153
|
+
mcpServers: {
|
|
154
|
+
mergen: {
|
|
155
|
+
command: "node",
|
|
156
|
+
args: [serverPath]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
161
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "windsurf": {
|
|
165
|
+
const configPath = resolve(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
166
|
+
const config = {
|
|
167
|
+
mcpServers: {
|
|
168
|
+
mergen: {
|
|
169
|
+
command: "node",
|
|
170
|
+
args: [serverPath]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
175
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`Unknown IDE: ${ide}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function checkIDEConfigured() {
|
|
183
|
+
const paths = [
|
|
184
|
+
resolve(homedir(), ".cursor", "mcp.json"),
|
|
185
|
+
resolve(homedir(), ".vscode", "mcp.json"),
|
|
186
|
+
resolve(homedir(), ".codeium", "windsurf", "mcp_config.json")
|
|
187
|
+
];
|
|
188
|
+
for (const path of paths) {
|
|
189
|
+
if (existsSync(path)) {
|
|
190
|
+
try {
|
|
191
|
+
const content = require("fs").readFileSync(path, "utf8");
|
|
192
|
+
if (content.includes("mergen")) return true;
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const output = execSync("claude mcp list", { encoding: "utf8", stdio: "pipe" });
|
|
199
|
+
if (output.includes("mergen")) return true;
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const SETUP_HTML = `<!DOCTYPE html>
|
|
205
|
+
<html lang="en">
|
|
206
|
+
<head>
|
|
207
|
+
<meta charset="UTF-8">
|
|
208
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
209
|
+
<title>Mergen Setup Wizard</title>
|
|
210
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
211
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
212
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
213
|
+
<style>
|
|
214
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
215
|
+
body {
|
|
216
|
+
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
217
|
+
background: radial-gradient(circle at 50% 50%, #0f172a 0%, #020617 100%);
|
|
218
|
+
color: #f1f5f9;
|
|
219
|
+
min-height: 100vh;
|
|
220
|
+
padding: 40px 20px;
|
|
221
|
+
-webkit-font-smoothing: antialiased;
|
|
222
|
+
}
|
|
223
|
+
.container {
|
|
224
|
+
max-width: 800px;
|
|
225
|
+
margin: 0 auto;
|
|
226
|
+
background: rgba(30, 41, 59, 0.45);
|
|
227
|
+
backdrop-filter: blur(16px);
|
|
228
|
+
-webkit-backdrop-filter: blur(16px);
|
|
229
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
230
|
+
border-radius: 24px;
|
|
231
|
+
overflow: hidden;
|
|
232
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
233
|
+
}
|
|
234
|
+
header {
|
|
235
|
+
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%);
|
|
236
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
237
|
+
color: white;
|
|
238
|
+
padding: 50px 40px;
|
|
239
|
+
text-align: center;
|
|
240
|
+
position: relative;
|
|
241
|
+
}
|
|
242
|
+
header::after {
|
|
243
|
+
content: '';
|
|
244
|
+
position: absolute;
|
|
245
|
+
bottom: 0;
|
|
246
|
+
left: 10%;
|
|
247
|
+
right: 10%;
|
|
248
|
+
height: 1px;
|
|
249
|
+
background: linear-gradient(to right, transparent, rgba(99, 102, 241, 0.4), transparent);
|
|
250
|
+
}
|
|
251
|
+
header h1 {
|
|
252
|
+
font-size: 38px;
|
|
253
|
+
font-weight: 800;
|
|
254
|
+
margin-bottom: 10px;
|
|
255
|
+
letter-spacing: -0.03em;
|
|
256
|
+
background: linear-gradient(135deg, #a5b4fc 0%, #c084fc 100%);
|
|
257
|
+
-webkit-background-clip: text;
|
|
258
|
+
-webkit-text-fill-color: transparent;
|
|
259
|
+
}
|
|
260
|
+
header p {
|
|
261
|
+
font-size: 16px;
|
|
262
|
+
color: #94a3b8;
|
|
263
|
+
font-weight: 400;
|
|
264
|
+
letter-spacing: 0.01em;
|
|
265
|
+
}
|
|
266
|
+
.content { padding: 40px; }
|
|
267
|
+
.step {
|
|
268
|
+
padding: 30px;
|
|
269
|
+
background: rgba(30, 41, 59, 0.2);
|
|
270
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
271
|
+
border-radius: 16px;
|
|
272
|
+
margin: 24px 0;
|
|
273
|
+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
274
|
+
}
|
|
275
|
+
.step:hover {
|
|
276
|
+
border-color: rgba(99, 102, 241, 0.3);
|
|
277
|
+
box-shadow: 0 8px 30px rgba(99, 102, 241, 0.1);
|
|
278
|
+
transform: translateY(-2px);
|
|
279
|
+
}
|
|
280
|
+
.step h2 {
|
|
281
|
+
font-size: 20px;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
margin-bottom: 15px;
|
|
284
|
+
display: flex;
|
|
285
|
+
align-items: center;
|
|
286
|
+
gap: 12px;
|
|
287
|
+
letter-spacing: -0.02em;
|
|
288
|
+
}
|
|
289
|
+
.step p { color: #94a3b8; margin-bottom: 15px; line-height: 1.6; font-size: 14px; }
|
|
290
|
+
.step.complete {
|
|
291
|
+
background: rgba(16, 185, 129, 0.05);
|
|
292
|
+
border-color: rgba(16, 185, 129, 0.2);
|
|
293
|
+
}
|
|
294
|
+
.step.complete h2 { color: #34d399; }
|
|
295
|
+
.step.pending {
|
|
296
|
+
background: rgba(245, 158, 11, 0.03);
|
|
297
|
+
border-color: rgba(245, 158, 11, 0.15);
|
|
298
|
+
}
|
|
299
|
+
.step.pending h2 { color: #fbbf24; }
|
|
300
|
+
.step.error {
|
|
301
|
+
background: rgba(239, 68, 68, 0.05);
|
|
302
|
+
border-color: rgba(239, 68, 68, 0.2);
|
|
303
|
+
}
|
|
304
|
+
.step.error h2 { color: #f87171; }
|
|
305
|
+
.icon { font-size: 22px; display: inline-flex; align-items: center; justify-content: center; }
|
|
306
|
+
.btn-group { display: flex; gap: 12px; flex-wrap: wrap; }
|
|
307
|
+
button {
|
|
308
|
+
padding: 12px 24px;
|
|
309
|
+
font-size: 14px;
|
|
310
|
+
font-weight: 600;
|
|
311
|
+
font-family: 'Outfit', sans-serif;
|
|
312
|
+
border: 1px solid transparent;
|
|
313
|
+
border-radius: 10px;
|
|
314
|
+
cursor: pointer;
|
|
315
|
+
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
316
|
+
flex: 1;
|
|
317
|
+
min-width: 120px;
|
|
318
|
+
display: inline-flex;
|
|
319
|
+
align-items: center;
|
|
320
|
+
justify-content: center;
|
|
321
|
+
gap: 8px;
|
|
322
|
+
}
|
|
323
|
+
button:active { transform: scale(0.97); }
|
|
324
|
+
button.primary {
|
|
325
|
+
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
|
326
|
+
color: white;
|
|
327
|
+
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.3);
|
|
328
|
+
}
|
|
329
|
+
button.primary:hover {
|
|
330
|
+
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
|
|
331
|
+
filter: brightness(1.1);
|
|
332
|
+
}
|
|
333
|
+
button.secondary {
|
|
334
|
+
background: rgba(255, 255, 255, 0.05);
|
|
335
|
+
color: #f1f5f9;
|
|
336
|
+
border-color: rgba(255, 255, 255, 0.08);
|
|
337
|
+
}
|
|
338
|
+
button.secondary:hover {
|
|
339
|
+
background: rgba(255, 255, 255, 0.1);
|
|
340
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
341
|
+
}
|
|
342
|
+
button.success {
|
|
343
|
+
background: linear-gradient(135deg, #059669 0%, #10b981 100%);
|
|
344
|
+
color: white;
|
|
345
|
+
box-shadow: 0 4px 14px rgba(16, 185, 129, 0.25);
|
|
346
|
+
}
|
|
347
|
+
button.success:hover {
|
|
348
|
+
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.35);
|
|
349
|
+
filter: brightness(1.1);
|
|
350
|
+
}
|
|
351
|
+
button:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none !important; }
|
|
352
|
+
|
|
353
|
+
input {
|
|
354
|
+
flex: 1;
|
|
355
|
+
padding: 12px 16px;
|
|
356
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
357
|
+
border-radius: 10px;
|
|
358
|
+
font-size: 14px;
|
|
359
|
+
min-width: 200px;
|
|
360
|
+
background: rgba(15, 23, 42, 0.5);
|
|
361
|
+
color: #f1f5f9;
|
|
362
|
+
font-family: inherit;
|
|
363
|
+
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
364
|
+
}
|
|
365
|
+
input:focus {
|
|
366
|
+
outline: none;
|
|
367
|
+
border-color: rgba(99, 102, 241, 0.5);
|
|
368
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
|
|
369
|
+
background: rgba(15, 23, 42, 0.85);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
pre {
|
|
373
|
+
background: #0b0f19;
|
|
374
|
+
color: #cbd5e1;
|
|
375
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
376
|
+
padding: 20px;
|
|
377
|
+
border-radius: 12px;
|
|
378
|
+
overflow-x: auto;
|
|
379
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
380
|
+
font-size: 13px;
|
|
381
|
+
line-height: 1.6;
|
|
382
|
+
margin: 15px 0;
|
|
383
|
+
}
|
|
384
|
+
.alert {
|
|
385
|
+
padding: 16px;
|
|
386
|
+
border-radius: 10px;
|
|
387
|
+
margin: 15px 0;
|
|
388
|
+
font-size: 13px;
|
|
389
|
+
line-height: 1.5;
|
|
390
|
+
display: flex;
|
|
391
|
+
align-items: center;
|
|
392
|
+
gap: 10px;
|
|
393
|
+
border: 1px solid transparent;
|
|
394
|
+
}
|
|
395
|
+
.alert-success {
|
|
396
|
+
background: rgba(16, 185, 129, 0.08);
|
|
397
|
+
color: #a7f3d0;
|
|
398
|
+
border-color: rgba(16, 185, 129, 0.25);
|
|
399
|
+
}
|
|
400
|
+
.alert-error {
|
|
401
|
+
background: rgba(239, 68, 68, 0.08);
|
|
402
|
+
color: #fca5a5;
|
|
403
|
+
border-color: rgba(239, 68, 68, 0.25);
|
|
404
|
+
}
|
|
405
|
+
.alert-info {
|
|
406
|
+
background: rgba(59, 130, 246, 0.08);
|
|
407
|
+
color: #bfdbfe;
|
|
408
|
+
border-color: rgba(59, 130, 246, 0.25);
|
|
409
|
+
}
|
|
410
|
+
a { color: #818cf8; text-decoration: none; font-weight: 600; transition: color 0.15s; }
|
|
411
|
+
a:hover { color: #a5b4fc; text-decoration: underline; }
|
|
412
|
+
footer { text-align: center; padding: 40px 30px; color: #64748b; font-size: 13px; border-top: 1px solid rgba(255, 255, 255, 0.04); }
|
|
413
|
+
</style>
|
|
414
|
+
</head>
|
|
415
|
+
<body>
|
|
416
|
+
<div class="container">
|
|
417
|
+
<header>
|
|
418
|
+
<h1>\u2B21 Mergen Setup</h1>
|
|
419
|
+
<p>AI operations layer for backend & infrastructure</p>
|
|
420
|
+
</header>
|
|
421
|
+
|
|
422
|
+
<div class="content">
|
|
423
|
+
<div class="step complete" id="step-1">
|
|
424
|
+
<h2><span class="icon">\u2705</span> Step 1: Server Running</h2>
|
|
425
|
+
<p>Mergen is running at <code>http://127.0.0.1:3000</code> \u2014 ready to receive OpenTelemetry, PagerDuty webhooks, and Docker logs.</p>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<div class="step pending" id="step-2">
|
|
429
|
+
<h2><span class="icon">\u23F3</span> Step 2: Connect Slack</h2>
|
|
430
|
+
<p>Mergen needs a Slack bot token to own incident threads and post autonomous resolution updates.</p>
|
|
431
|
+
<p style="font-size:13px;color:#64748b;margin-bottom:12px">
|
|
432
|
+
1. Create app at <a href="https://api.slack.com/apps" target="_blank">api.slack.com/apps</a> \u2192
|
|
433
|
+
OAuth & Permissions \u2192 add scope <code>chat:write</code> \u2192 Install \u2192 copy Bot Token
|
|
434
|
+
</p>
|
|
435
|
+
<div style="display:flex;gap:10px;margin-bottom:10px;flex-wrap:wrap">
|
|
436
|
+
<input id="slack-token" type="password" placeholder="xoxb-..." style="flex:1;padding:12px 16px;border:1px solid rgba(255,255,255,0.1);border-radius:10px;font-size:14px;min-width:200px;background:rgba(15,23,42,0.5);color:#f1f5f9;outline:none">
|
|
437
|
+
<input id="slack-channel" type="text" placeholder="#incidents" style="width:140px;padding:12px 16px;border:1px solid rgba(255,255,255,0.1);border-radius:10px;font-size:14px;background:rgba(15,23,42,0.5);color:#f1f5f9;outline:none">
|
|
438
|
+
<button class="primary" onclick="testSlack()" style="flex:0;min-width:120px">Test & Connect</button>
|
|
439
|
+
</div>
|
|
440
|
+
<div id="slack-result"></div>
|
|
441
|
+
<p style="font-size:12px;color:#64748b;margin-top:8px">Token is sent only to Slack's API and your own server \u2014 never stored in plaintext.</p>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<div class="step pending" id="step-3">
|
|
445
|
+
<h2><span class="icon">\u23F3</span> Step 3: PagerDuty Webhook</h2>
|
|
446
|
+
<p>Point PagerDuty at Mergen so it can start the autonomous triage loop when an incident fires.</p>
|
|
447
|
+
<div id="pd-url-box" style="background:#0b0f19;color:#cbd5e1;padding:14px 18px;border:1px solid rgba(255,255,255,0.06);border-radius:10px;font-family:'JetBrains Mono',monospace;font-size:13px;margin-bottom:12px;word-break:break-all">
|
|
448
|
+
Loading webhook URL\u2026
|
|
449
|
+
</div>
|
|
450
|
+
<p style="font-size:13px;color:#64748b;margin-bottom:12px">
|
|
451
|
+
In PagerDuty: <strong>Services \u2192 [your service] \u2192 Integrations \u2192 Add Webhook \u2192 paste the URL above</strong>
|
|
452
|
+
</p>
|
|
453
|
+
<button class="secondary" onclick="copyWebhookUrl()">Copy URL</button>
|
|
454
|
+
<div id="pd-result"></div>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<div class="step pending" id="step-4">
|
|
458
|
+
<h2><span class="icon">\u23F3</span> Step 4: Send Telemetry</h2>
|
|
459
|
+
<p>Point your services at Mergen via OpenTelemetry \u2014 no code changes needed.</p>
|
|
460
|
+
<pre>OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:3000 \\
|
|
461
|
+
OTEL_SERVICE_NAME=api \\
|
|
462
|
+
node app.js</pre>
|
|
463
|
+
<p style="margin-top:12px;font-size:13px;color:#64748b">Or run the demo to see it working immediately:</p>
|
|
464
|
+
<button class="success" onclick="runDemo()">Run Demo Incident</button>
|
|
465
|
+
<div id="otlp-result"></div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<div class="step pending" id="step-5">
|
|
469
|
+
<h2><span class="icon">\u23F3</span> Step 5: Configure AI IDE</h2>
|
|
470
|
+
<p>Let your AI assistant call Mergen's MCP tools directly \u2014 <em>"triage the latest incident"</em></p>
|
|
471
|
+
<div class="btn-group">
|
|
472
|
+
<button class="primary" onclick="configureIDE('claude-code')">Claude Code</button>
|
|
473
|
+
<button class="primary" onclick="configureIDE('cursor')">Cursor</button>
|
|
474
|
+
<button class="primary" onclick="configureIDE('vscode')">VS Code</button>
|
|
475
|
+
<button class="primary" onclick="configureIDE('windsurf')">Windsurf</button>
|
|
476
|
+
</div>
|
|
477
|
+
<div id="ide-result"></div>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div class="step complete" id="step-6">
|
|
481
|
+
<h2><span class="icon">\u2705</span> Step 6: Continuous Flywheel (Compounding Memory)</h2>
|
|
482
|
+
<p>Enable Phase 4 Continuous Flywheel to automatically extract override policies from Slack threads and git decisions.</p>
|
|
483
|
+
<p style="margin-bottom:8px"><strong>Slack Postmortems Webhook:</strong> Point your Slack App's <em>Event Subscriptions</em> to:</p>
|
|
484
|
+
<div id="slack-events-url-box" style="background:#0b0f19;color:#cbd5e1;padding:14px 18px;border:1px solid rgba(255,255,255,0.06);border-radius:10px;font-family:'JetBrains Mono',monospace;font-size:13px;margin-bottom:12px;word-break:break-all">
|
|
485
|
+
Loading slack events URL\u2026
|
|
486
|
+
</div>
|
|
487
|
+
<p style="font-size:13px;color:#64748b;margin-bottom:12px">Subscribe to <code>message.channels</code> events (ensure your bot is added to postmortem channels).</p>
|
|
488
|
+
|
|
489
|
+
<p style="margin-bottom:8px"><strong>Git ADR Webhook:</strong> Point your repository's commit or ADR webhook to:</p>
|
|
490
|
+
<div id="adr-events-url-box" style="background:#0b0f19;color:#cbd5e1;padding:14px 18px;border:1px solid rgba(255,255,255,0.06);border-radius:10px;font-family:'JetBrains Mono',monospace;font-size:13px;margin-bottom:12px;word-break:break-all">
|
|
491
|
+
Loading ADR webhook URL\u2026
|
|
492
|
+
</div>
|
|
493
|
+
<p style="font-size:13px;color:#64748b;margin-bottom:12px">Submit architectural decisions via JSON to compile override corpus rules automatically.</p>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<footer>
|
|
498
|
+
<p>Need help? <a href="https://github.com/omertt27/Mergen" target="_blank">GitHub</a> \xB7 <a href="/dashboard">Dashboard \u2192</a></p>
|
|
499
|
+
</footer>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<script>
|
|
503
|
+
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
504
|
+
|
|
505
|
+
let _webhookUrl = '';
|
|
506
|
+
|
|
507
|
+
async function testSlack() {
|
|
508
|
+
const token = document.getElementById('slack-token').value.trim();
|
|
509
|
+
const channel = document.getElementById('slack-channel').value.trim();
|
|
510
|
+
const result = document.getElementById('slack-result');
|
|
511
|
+
if (!token) { result.innerHTML = '<div class="alert alert-error">Paste your bot token first.</div>'; return; }
|
|
512
|
+
result.innerHTML = '<div class="alert alert-info">Testing Slack connection\u2026</div>';
|
|
513
|
+
try {
|
|
514
|
+
const r = await fetch('/api/setup/test-slack', {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
headers: { 'Content-Type': 'application/json' },
|
|
517
|
+
body: JSON.stringify({ token, channel }),
|
|
518
|
+
});
|
|
519
|
+
const d = await r.json();
|
|
520
|
+
if (d.ok) {
|
|
521
|
+
result.innerHTML = '<div class="alert alert-success">\u2713 Connected to workspace <strong>' + escHtml(d.team||'') + '</strong>' + (channel ? ' \u2014 test message posted to ' + escHtml(channel) : '') + '</div>';
|
|
522
|
+
document.getElementById('step-2').className = 'step complete';
|
|
523
|
+
document.getElementById('step-2').querySelector('.icon').textContent = '\u2705';
|
|
524
|
+
} else {
|
|
525
|
+
result.innerHTML = '<div class="alert alert-error">\u2717 ' + escHtml(d.error) + ' \u2014 check your token has <code>chat:write</code> scope.</div>';
|
|
526
|
+
}
|
|
527
|
+
} catch(e) { result.innerHTML = '<div class="alert alert-error">\u2717 ' + escHtml(e.message) + '</div>'; }
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function copyWebhookUrl() {
|
|
531
|
+
if (!_webhookUrl) return;
|
|
532
|
+
navigator.clipboard.writeText(_webhookUrl).then(() => {
|
|
533
|
+
document.getElementById('pd-result').innerHTML = '<div class="alert alert-success">\u2713 Copied to clipboard</div>';
|
|
534
|
+
document.getElementById('step-3').className = 'step complete';
|
|
535
|
+
document.getElementById('step-3').querySelector('.icon').textContent = '\u2705';
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function runDemo() {
|
|
540
|
+
const result = document.getElementById('otlp-result');
|
|
541
|
+
result.innerHTML = '<div class="alert alert-info">Injecting demo incident\u2026</div>';
|
|
542
|
+
try {
|
|
543
|
+
const ts = Date.now();
|
|
544
|
+
await fetch('/ingest', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
545
|
+
body: JSON.stringify({ type: 'console', level: 'error', args: ['[api] database connection pool exhausted \u2014 0 connections available after 30s timeout'], url: 'http://api:8080/health', timestamp: ts }) });
|
|
546
|
+
await fetch('/ingest', { method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
547
|
+
body: JSON.stringify({ type: 'network', method: 'GET', url: 'http://api:8080/api/users', status: 503, duration: 30421, error: 'upstream connect error', timestamp: ts + 100 }) });
|
|
548
|
+
await new Promise(r => setTimeout(r, 600));
|
|
549
|
+
const h = await fetch('/health').then(r => r.json());
|
|
550
|
+
result.innerHTML = '<div class="alert alert-success">\u2713 Demo incident injected \u2014 ' + escHtml(h.buffered||0) + ' events in buffer.<br><strong>In your AI IDE:</strong> "triage the latest incident"<br>Or check the <a href="/dashboard">dashboard \u2192</a></div>';
|
|
551
|
+
document.getElementById('step-4').className = 'step complete';
|
|
552
|
+
document.getElementById('step-4').querySelector('.icon').textContent = '\u2705';
|
|
553
|
+
} catch(e) { result.innerHTML = '<div class="alert alert-error">\u2717 ' + escHtml(e.message) + '</div>'; }
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function configureIDE(ide) {
|
|
557
|
+
const result = document.getElementById('ide-result');
|
|
558
|
+
result.innerHTML = '<div class="alert alert-info">Configuring\u2026</div>';
|
|
559
|
+
try {
|
|
560
|
+
const r = await fetch('/api/setup/ide', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ide }) });
|
|
561
|
+
const d = await r.json();
|
|
562
|
+
if (r.ok) {
|
|
563
|
+
result.innerHTML = '<div class="alert alert-success">\u2713 ' + escHtml(d.message) + ' \u2014 restart your IDE, then ask: <strong>"Triage the latest incident"</strong></div>';
|
|
564
|
+
document.getElementById('step-5').className = 'step complete';
|
|
565
|
+
document.getElementById('step-5').querySelector('.icon').textContent = '\u2705';
|
|
566
|
+
} else {
|
|
567
|
+
result.innerHTML = '<div class="alert alert-error">\u2717 ' + escHtml(d.error) + '</div>';
|
|
568
|
+
}
|
|
569
|
+
} catch(e) { result.innerHTML = '<div class="alert alert-error">\u2717 ' + escHtml(e.message) + '</div>'; }
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
window.addEventListener('load', async () => {
|
|
573
|
+
try {
|
|
574
|
+
const s = await fetch('/api/setup/status').then(r => r.json());
|
|
575
|
+
if (s.ide) { document.getElementById('step-5').className = 'step complete'; document.getElementById('step-5').querySelector('.icon').textContent = '\u2705'; }
|
|
576
|
+
if (s.slack) { document.getElementById('step-2').className = 'step complete'; document.getElementById('step-2').querySelector('.icon').textContent = '\u2705'; }
|
|
577
|
+
if (s.pagerdutyWebhookUrl) {
|
|
578
|
+
_webhookUrl = s.pagerdutyWebhookUrl;
|
|
579
|
+
document.getElementById('pd-url-box').textContent = s.pagerdutyWebhookUrl;
|
|
580
|
+
}
|
|
581
|
+
if (s.slackEventsWebhookUrl) {
|
|
582
|
+
document.getElementById('slack-events-url-box').textContent = s.slackEventsWebhookUrl;
|
|
583
|
+
}
|
|
584
|
+
if (s.adrWebhookUrl) {
|
|
585
|
+
document.getElementById('adr-events-url-box').textContent = s.adrWebhookUrl;
|
|
586
|
+
}
|
|
587
|
+
} catch(e) { console.error('Status load failed:', e); }
|
|
588
|
+
});
|
|
589
|
+
</script>
|
|
590
|
+
</body>
|
|
591
|
+
</html>`;
|
|
592
|
+
export {
|
|
593
|
+
createSetupRouter
|
|
594
|
+
};
|