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.
Files changed (2) hide show
  1. package/dist/cli.js +79 -4
  2. 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.5.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").action(async (options) => {
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
- print({ healthy: true, health, projects, ingestion });
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
- print({ release: options.release, project: options.project, uploaded, skipped });
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "error-mom",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Agent-first CLI and MCP tools for self-hosted Error Mom",
5
5
  "type": "module",
6
6
  "bin": {