error-mom 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +79 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Command } from "commander";
|
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
var VERSION = "0.
|
|
13
|
+
var VERSION = "0.6.0";
|
|
14
14
|
var CONFIG_DIR = join(homedir(), ".error-mom");
|
|
15
15
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
16
16
|
var program = new Command().name("error-mom").description("Query and operate a self-hosted Error Mom incident desk").version(VERSION);
|
|
@@ -75,7 +75,10 @@ program.command("delete-project").description("Permanently delete one project an
|
|
|
75
75
|
)
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
|
-
program.command("doctor").description("Verify collector health, credentials, and optional project ingestion").option("--project-key <key>", "Send and verify a synthetic event with this write-only key").
|
|
78
|
+
program.command("doctor").description("Verify collector health, credentials, and optional project ingestion").option("--project-key <key>", "Send and verify a synthetic event with this write-only key").option(
|
|
79
|
+
"--symbolication",
|
|
80
|
+
"Round-trip a synthetic minified stack through the server's symbolication engine"
|
|
81
|
+
).action(async (options) => {
|
|
79
82
|
const config = await loadConfig();
|
|
80
83
|
const health = await request(config.server, void 0, "/api/health");
|
|
81
84
|
const projects = await request(config.server, config.adminToken, "/api/v1/projects");
|
|
@@ -89,7 +92,30 @@ program.command("doctor").description("Verify collector health, credentials, and
|
|
|
89
92
|
}
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
let symbolication = "not tested";
|
|
96
|
+
if (options.symbolication) {
|
|
97
|
+
const result = await request(config.server, config.adminToken, "/api/v1/sourcemaps/check", {
|
|
98
|
+
body: {
|
|
99
|
+
stack: "ErrorMomDoctor: synthetic minified stack\n at t.xyz (https://app.example/assets/doctor-abc.js:1:11)",
|
|
100
|
+
fileName: "doctor-abc.js",
|
|
101
|
+
map: {
|
|
102
|
+
version: 3,
|
|
103
|
+
file: "doctor-abc.js",
|
|
104
|
+
sources: ["src/doctor-fixture.ts"],
|
|
105
|
+
names: ["doctorBoom"],
|
|
106
|
+
mappings: "UAIEA"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const roundTripped = result.symbolicated === true && typeof result.stack === "string" && result.stack.includes("doctorBoom (src/doctor-fixture.ts:5:3)");
|
|
111
|
+
if (!roundTripped) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Symbolication round-trip failed: expected the synthetic frame to rewrite to src/doctor-fixture.ts:5:3, got ${JSON.stringify(result)}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
symbolication = { verified: true, rewrittenFrame: "doctorBoom (src/doctor-fixture.ts:5:3)" };
|
|
117
|
+
}
|
|
118
|
+
print({ healthy: true, health, projects, ingestion, symbolication });
|
|
93
119
|
});
|
|
94
120
|
program.command("init").description("Create/select a project, install the SDK, and generate framework-aware setup").option("--name <name>", "Project name").option("--project <slug>", "Use an existing project slug").option("--skip-install", "Generate setup without invoking the package manager").action(async (options) => {
|
|
95
121
|
const config = await loadConfig();
|
|
@@ -140,6 +166,7 @@ program.command("sourcemaps").description("Upload production source maps so mini
|
|
|
140
166
|
}
|
|
141
167
|
const uploaded = [];
|
|
142
168
|
const skipped = [];
|
|
169
|
+
const warnings = [];
|
|
143
170
|
for (const mapFile of mapFiles) {
|
|
144
171
|
const info = await stat(mapFile);
|
|
145
172
|
if (info.size > 20 * 1024 * 1024) {
|
|
@@ -154,6 +181,12 @@ program.command("sourcemaps").description("Upload production source maps so mini
|
|
|
154
181
|
continue;
|
|
155
182
|
}
|
|
156
183
|
const fileName = basename(mapFile).replace(/\.map$/, "");
|
|
184
|
+
const sourcesContent = map.sourcesContent;
|
|
185
|
+
if (!Array.isArray(sourcesContent) || sourcesContent.every((entry) => entry == null)) {
|
|
186
|
+
warnings.push(
|
|
187
|
+
`${fileName}: no sourcesContent \u2014 frames still symbolicate to file:line, but agents cannot read the original code from the map. Enable it in the bundler if you want richer context.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
157
190
|
try {
|
|
158
191
|
await request(config.server, config.adminToken, "/api/v1/sourcemaps", {
|
|
159
192
|
body: { projectId: options.project, release: options.release, fileName, map }
|
|
@@ -166,7 +199,8 @@ program.command("sourcemaps").description("Upload production source maps so mini
|
|
|
166
199
|
});
|
|
167
200
|
}
|
|
168
201
|
}
|
|
169
|
-
|
|
202
|
+
warnings.push(...await releaseMismatchWarning(config, options.project, options.release));
|
|
203
|
+
print({ release: options.release, project: options.project, uploaded, skipped, warnings });
|
|
170
204
|
});
|
|
171
205
|
program.command("mcp").description("Run Error Mom tools over MCP stdio for coding agents").action(async () => {
|
|
172
206
|
await runMcpServer();
|
|
@@ -236,6 +270,22 @@ async function runMcpServer() {
|
|
|
236
270
|
)
|
|
237
271
|
)
|
|
238
272
|
);
|
|
273
|
+
server.registerTool(
|
|
274
|
+
"check_symbolication",
|
|
275
|
+
{
|
|
276
|
+
description: "Dry-run a minified stack against a project's uploaded source maps to verify frames rewrite to original file:line. Nothing is stored.",
|
|
277
|
+
inputSchema: {
|
|
278
|
+
projectId: z.string().min(1).describe("Project id or slug"),
|
|
279
|
+
release: z.string().min(1),
|
|
280
|
+
stack: z.string().min(1).max(5e4)
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
async ({ projectId, release, stack }) => toolResult(
|
|
284
|
+
await request(config.server, config.adminToken, "/api/v1/sourcemaps/check", {
|
|
285
|
+
body: { projectId, release, stack }
|
|
286
|
+
})
|
|
287
|
+
)
|
|
288
|
+
);
|
|
239
289
|
server.registerTool(
|
|
240
290
|
"delete_project",
|
|
241
291
|
{
|
|
@@ -510,6 +560,31 @@ function syntheticEvent() {
|
|
|
510
560
|
context: {}
|
|
511
561
|
};
|
|
512
562
|
}
|
|
563
|
+
async function releaseMismatchWarning(config, projectIdOrSlug, release) {
|
|
564
|
+
try {
|
|
565
|
+
const { projects } = await request(config.server, config.adminToken, "/api/v1/projects");
|
|
566
|
+
const project = projects.find(
|
|
567
|
+
(candidate) => candidate.id === projectIdOrSlug || candidate.slug === projectIdOrSlug
|
|
568
|
+
);
|
|
569
|
+
if (!project) return [];
|
|
570
|
+
const { issues } = await request(
|
|
571
|
+
config.server,
|
|
572
|
+
config.adminToken,
|
|
573
|
+
`/api/v1/issues?${new URLSearchParams({ projectId: project.id, status: "all" })}`
|
|
574
|
+
);
|
|
575
|
+
const seenReleases = [
|
|
576
|
+
...new Set(issues.map((issue) => issue.latestRelease).filter((value) => value !== null))
|
|
577
|
+
];
|
|
578
|
+
if (seenReleases.length > 0 && !seenReleases.includes(release)) {
|
|
579
|
+
return [
|
|
580
|
+
`release "${release}" has not been reported by any event yet (recent releases: ${seenReleases.slice(0, 5).join(", ")}). Maps only apply when the SDK's release matches --release exactly.`
|
|
581
|
+
];
|
|
582
|
+
}
|
|
583
|
+
return [];
|
|
584
|
+
} catch {
|
|
585
|
+
return [];
|
|
586
|
+
}
|
|
587
|
+
}
|
|
513
588
|
async function findMapFiles(dir) {
|
|
514
589
|
const found = [];
|
|
515
590
|
let entries;
|