error-mom 0.4.0 → 0.5.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 (3) hide show
  1. package/dist/cli.js +58 -29
  2. package/package.json +8 -8
  3. package/LICENSE +0 -21
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { execFileSync } from "child_process";
5
- import { chmod, mkdir, readFile, writeFile, appendFile } from "fs/promises";
5
+ import { chmod, mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
6
6
  import { existsSync } from "fs";
7
7
  import { homedir } from "os";
8
8
  import { basename, join } from "path";
@@ -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.4.0";
13
+ var VERSION = "0.5.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);
@@ -121,22 +121,53 @@ program.command("init").description("Create/select a project, install the SDK, a
121
121
  const framework = detectFramework(packageJson);
122
122
  if (!options.skipInstall) installSdk(detectPackageManager(process.cwd()));
123
123
  const setupPath = await writeSetup(framework, config.server, project.ingestKey);
124
- await writeFile(
125
- join(process.cwd(), ".error-mom.json"),
126
- `${JSON.stringify({ server: config.server, projectId: project.id, projectName: project.name, framework: framework.id }, null, 2)}
127
- `
128
- );
129
- await appendEnvironment(framework, config.server, project.ingestKey);
130
124
  print({
131
125
  installed: !options.skipInstall,
132
126
  project: { id: project.id, name: project.name, slug: project.slug },
133
127
  framework: framework.id,
134
128
  setupFile: setupPath,
129
+ projectKey: project.ingestKey,
135
130
  verified: false,
136
131
  wiring: framework.wiring,
137
- nextAction: `Wire it up: ${framework.wiring} If the app routes caught errors through a central handler or error-broadcast function, call errorMom.captureError(err) inside it \u2014 that is where framework-caught and LLM errors surface. For handlers where a framework catches errors itself (queue/cron jobs, webhooks, MCP tools), wrap each with errorMom.wrap(fn, { culprit: "<name>" }). The setup file has the write-only project key baked in so production builds report without any CI configuration. Then run error-mom doctor --project-key <key>.`
132
+ nextAction: `Wire it up: ${framework.wiring} If the app routes caught errors through a central handler or error-broadcast function, call errorMom.captureError(err) inside it \u2014 that is where framework-caught and LLM errors surface. For handlers where a framework catches errors itself (queue/cron jobs, webhooks, MCP tools), wrap each with errorMom.wrap(fn, { culprit: "<name>" }). The setup file has the write-only project key baked in (safe to commit; ERROR_MOM_* env vars override when set), so production builds report without any configuration. Then run error-mom doctor --project-key ${project.ingestKey}.`
138
133
  });
139
134
  });
135
+ program.command("sourcemaps").description("Upload production source maps so minified stacks symbolicate on ingest").argument("<dir>", "Build output directory containing *.map files (e.g. dist)").requiredOption("--release <release>", "Release the maps belong to (must match the SDK release)").requiredOption("--project <id-or-slug>", "Project id or slug").action(async (dir, options) => {
136
+ const config = await loadConfig();
137
+ const mapFiles = await findMapFiles(dir);
138
+ if (mapFiles.length === 0) {
139
+ throw new Error(`No .map files found under ${dir}. Build with source maps enabled first.`);
140
+ }
141
+ const uploaded = [];
142
+ const skipped = [];
143
+ for (const mapFile of mapFiles) {
144
+ const info = await stat(mapFile);
145
+ if (info.size > 20 * 1024 * 1024) {
146
+ skipped.push({ file: mapFile, reason: "larger than 20 MB" });
147
+ continue;
148
+ }
149
+ let map;
150
+ try {
151
+ map = JSON.parse(await readFile(mapFile, "utf8"));
152
+ } catch {
153
+ skipped.push({ file: mapFile, reason: "not valid JSON" });
154
+ continue;
155
+ }
156
+ const fileName = basename(mapFile).replace(/\.map$/, "");
157
+ try {
158
+ await request(config.server, config.adminToken, "/api/v1/sourcemaps", {
159
+ body: { projectId: options.project, release: options.release, fileName, map }
160
+ });
161
+ uploaded.push(fileName);
162
+ } catch (error) {
163
+ skipped.push({
164
+ file: mapFile,
165
+ reason: error instanceof Error ? error.message : String(error)
166
+ });
167
+ }
168
+ }
169
+ print({ release: options.release, project: options.project, uploaded, skipped });
170
+ });
140
171
  program.command("mcp").description("Run Error Mom tools over MCP stdio for coding agents").action(async () => {
141
172
  await runMcpServer();
142
173
  });
@@ -459,26 +490,6 @@ export const onRequestError: Instrumentation.onRequestError = async (
459
490
  `;
460
491
  await writeFile(file, contents);
461
492
  }
462
- async function appendEnvironment(framework, server, key) {
463
- const prefix = framework.envStyle === "next" ? "NEXT_PUBLIC_" : framework.envStyle === "vite" ? "VITE_" : "";
464
- const file = join(process.cwd(), ".env.local");
465
- const existing = existsSync(file) ? await readFile(file, "utf8") : "";
466
- const lines = [
467
- [`${prefix}ERROR_MOM_SERVER`, server],
468
- [`${prefix}ERROR_MOM_PROJECT_KEY`, key],
469
- [`${prefix}ERROR_MOM_ENVIRONMENT`, "production"]
470
- ].filter(([name]) => !existing.includes(`${name}=`));
471
- if (lines.length) {
472
- await appendFile(
473
- file,
474
- `${existing && !existing.endsWith("\n") ? "\n" : ""}${lines.map(([name, value]) => `${name}=${value}`).join("\n")}
475
- `,
476
- {
477
- mode: 384
478
- }
479
- );
480
- }
481
- }
482
493
  function syntheticEvent() {
483
494
  return {
484
495
  eventId: crypto.randomUUID(),
@@ -499,6 +510,24 @@ function syntheticEvent() {
499
510
  context: {}
500
511
  };
501
512
  }
513
+ async function findMapFiles(dir) {
514
+ const found = [];
515
+ let entries;
516
+ try {
517
+ entries = await readdir(dir, { withFileTypes: true });
518
+ } catch {
519
+ throw new Error(`Cannot read directory ${dir}.`);
520
+ }
521
+ for (const entry of entries) {
522
+ const fullPath = join(dir, entry.name);
523
+ if (entry.isDirectory() && entry.name !== "node_modules") {
524
+ found.push(...await findMapFiles(fullPath));
525
+ } else if (entry.isFile() && entry.name.endsWith(".map")) {
526
+ found.push(fullPath);
527
+ }
528
+ }
529
+ return found.sort();
530
+ }
502
531
  function normalizeServer(server) {
503
532
  return server.replace(/\/$/, "");
504
533
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "error-mom",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Agent-first CLI and MCP tools for self-hosted Error Mom",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,11 @@
11
11
  "dist",
12
12
  "README.md"
13
13
  ],
14
+ "scripts": {
15
+ "build": "tsup src/cli.ts --format esm --dts --clean",
16
+ "check": "tsc --noEmit",
17
+ "test": "vitest run --passWithNoTests"
18
+ },
14
19
  "dependencies": {
15
20
  "@modelcontextprotocol/sdk": "^1.29.0",
16
21
  "commander": "^15.0.0",
@@ -23,10 +28,5 @@
23
28
  "publishConfig": {
24
29
  "access": "public"
25
30
  },
26
- "license": "MIT",
27
- "scripts": {
28
- "build": "tsup src/cli.ts --format esm --dts --clean",
29
- "check": "tsc --noEmit",
30
- "test": "vitest run --passWithNoTests"
31
- }
32
- }
31
+ "license": "MIT"
32
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Ken Kai
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.