error-mom 0.1.1 → 0.3.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/LICENSE +21 -0
  2. package/dist/cli.js +162 -21
  3. package/package.json +8 -8
package/LICENSE ADDED
@@ -0,0 +1,21 @@
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.
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.1.1";
13
+ var VERSION = "0.3.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);
@@ -112,17 +112,18 @@ program.command("init").description("Create/select a project, install the SDK, a
112
112
  const setupPath = await writeSetup(framework);
113
113
  await writeFile(
114
114
  join(process.cwd(), ".error-mom.json"),
115
- `${JSON.stringify({ server: config.server, projectId: project.id, projectName: project.name, framework }, null, 2)}
115
+ `${JSON.stringify({ server: config.server, projectId: project.id, projectName: project.name, framework: framework.id }, null, 2)}
116
116
  `
117
117
  );
118
118
  await appendEnvironment(framework, config.server, project.ingestKey);
119
119
  print({
120
120
  installed: !options.skipInstall,
121
121
  project: { id: project.id, name: project.name, slug: project.slug },
122
- framework,
122
+ framework: framework.id,
123
123
  setupFile: setupPath,
124
124
  verified: false,
125
- nextAction: `Import ${setupPath} from the earliest ${framework} entry point, then run error-mom doctor --project-key <key>.`
125
+ wiring: framework.wiring,
126
+ nextAction: `Wire it up: ${framework.wiring} For handlers where a framework catches errors itself (queue/cron jobs, webhooks, MCP tools), wrap each with errorMom.wrap(fn, { culprit: "<name>" }). Then run error-mom doctor --project-key <key>.`
126
127
  });
127
128
  });
128
129
  program.command("mcp").description("Run Error Mom tools over MCP stdio for coding agents").action(async () => {
@@ -243,14 +244,125 @@ async function readPackageJson(cwd) {
243
244
  );
244
245
  }
245
246
  }
247
+ var FRAMEWORKS = [
248
+ [
249
+ "next",
250
+ {
251
+ id: "next",
252
+ envStyle: "next",
253
+ wiring: "Import the setup file from the root layout for client errors; the generated instrumentation.ts already reports server errors (API routes, SSR, server actions)."
254
+ }
255
+ ],
256
+ [
257
+ "@tauri-apps/api",
258
+ {
259
+ id: "tauri",
260
+ envStyle: "vite",
261
+ wiring: "Import the setup file first in the webview entry (main.tsx or equivalent). If the app spawns Node sidecar processes, also call initErrorMom from @kenkaiiii/error-mom/node at each sidecar entry so backend/LLM errors are reported too. Rust-side panics are not captured."
262
+ }
263
+ ],
264
+ [
265
+ "electron",
266
+ {
267
+ id: "electron",
268
+ envStyle: "node",
269
+ wiring: "Call initErrorMom from @kenkaiiii/error-mom/node at the TOP of the main process entry, and import @kenkaiiii/error-mom (browser build) first in each renderer entry. Both report to the same project."
270
+ }
271
+ ],
272
+ [
273
+ "astro",
274
+ {
275
+ id: "astro",
276
+ envStyle: "vite",
277
+ wiring: "Load the setup file as a client script in the base layout so it runs on every page. For SSR/API routes, add src/middleware.ts that wraps next() with errorMom.wrap using @kenkaiiii/error-mom/node."
278
+ }
279
+ ],
280
+ [
281
+ "@sveltejs/kit",
282
+ {
283
+ id: "sveltekit",
284
+ envStyle: "vite",
285
+ wiring: "Import the setup file in hooks.client.ts and export handleError from BOTH hooks.client.ts and hooks.server.ts calling errorMom.captureError(error); the server hook uses @kenkaiiii/error-mom/node. SvelteKit catches load/endpoint errors, so handleError is its official reporting hook."
286
+ }
287
+ ],
288
+ [
289
+ "nuxt",
290
+ {
291
+ id: "nuxt",
292
+ envStyle: "vite",
293
+ wiring: "Create a client plugin (plugins/error-mom.client.ts) importing the setup file, and a nitro plugin hooking 'error' with @kenkaiiii/error-mom/node for server-side errors."
294
+ }
295
+ ],
296
+ [
297
+ "@remix-run/react",
298
+ {
299
+ id: "remix",
300
+ envStyle: "node",
301
+ wiring: "Import the setup file in entry.client.tsx, and export handleError from entry.server.tsx calling errorMom.captureError(error). Remix catches loader/action errors and handleError is its official reporting hook."
302
+ }
303
+ ],
304
+ [
305
+ "@angular/core",
306
+ {
307
+ id: "angular",
308
+ envStyle: "node",
309
+ wiring: "Import the setup file in main.ts and provide a custom ErrorHandler that calls errorMom.captureError(error). Angular catches component errors in its own handler."
310
+ }
311
+ ],
312
+ [
313
+ "express",
314
+ {
315
+ id: "express",
316
+ envStyle: "node",
317
+ wiring: "Import the setup file at the TOP of the server entry, and add a final error middleware: (err, req, res, next) => { errorMom.captureError(err, { culprit: `${req.method} ${req.path}` }); next(err); }. Express catches route errors, so the middleware is where they surface."
318
+ }
319
+ ],
320
+ [
321
+ "fastify",
322
+ {
323
+ id: "fastify",
324
+ envStyle: "node",
325
+ wiring: "Import the setup file at the TOP of the server entry, and call errorMom.captureError(error) inside setErrorHandler before replying. Fastify catches handler errors there."
326
+ }
327
+ ],
328
+ [
329
+ "hono",
330
+ {
331
+ id: "hono",
332
+ envStyle: "node",
333
+ wiring: "Import the setup file at the TOP of the server entry, and call errorMom.captureError(err) inside app.onError. Hono catches handler errors there."
334
+ }
335
+ ],
336
+ [
337
+ "@nestjs/core",
338
+ {
339
+ id: "nestjs",
340
+ envStyle: "node",
341
+ wiring: "Import the setup file at the TOP of main.ts, and add a global exception filter that calls errorMom.captureError(exception) before delegating to the base filter."
342
+ }
343
+ ],
344
+ [
345
+ "vite",
346
+ {
347
+ id: "vite",
348
+ envStyle: "vite",
349
+ wiring: "Import the setup file first in the app entry (main.tsx or equivalent)."
350
+ }
351
+ ]
352
+ ];
246
353
  function detectFramework(packageJson) {
247
354
  const dependencies = {
248
355
  ...packageJson.dependencies ?? {},
249
356
  ...packageJson.devDependencies ?? {}
250
357
  };
251
- if (dependencies.next) return "next";
252
- if (dependencies.vite) return "vite";
253
- return "node";
358
+ for (const [dependency, info] of FRAMEWORKS) {
359
+ if (dependencies[dependency]) return info;
360
+ }
361
+ return {
362
+ id: "node",
363
+ envStyle: "node",
364
+ wiring: "Import the setup file at the TOP of the main entry point, before anything else."
365
+ };
254
366
  }
255
367
  function detectPackageManager(cwd) {
256
368
  if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
@@ -271,30 +383,59 @@ function installSdk(packageManager) {
271
383
  async function writeSetup(framework) {
272
384
  const sourceDirectory = existsSync(join(process.cwd(), "src")) ? "src" : ".";
273
385
  const relativePath = join(sourceDirectory, "error-mom.ts");
274
- const environment = framework === "next" ? "process.env.NEXT_PUBLIC_" : framework === "vite" ? "import.meta.env.VITE_" : "process.env.";
275
- const moduleName = framework === "node" ? "@kenkaiiii/error-mom/node" : "@kenkaiiii/error-mom";
276
- const contents = `${framework === "next" ? '"use client";\n\n' : ""}import { initErrorMom } from "${moduleName}";
386
+ if (framework.id === "next") await writeNextInstrumentation(sourceDirectory);
387
+ const environment = framework.envStyle === "next" ? "process.env.NEXT_PUBLIC_" : framework.envStyle === "vite" ? "import.meta.env.VITE_" : "process.env.";
388
+ const moduleName = framework.envStyle === "node" ? "@kenkaiiii/error-mom/node" : "@kenkaiiii/error-mom";
389
+ const contents = `${framework.id === "next" ? '"use client";\n\n' : ""}import { initErrorMom } from "${moduleName}";
277
390
 
278
391
  const server = ${environment}ERROR_MOM_SERVER;
279
392
  const projectKey = ${environment}ERROR_MOM_PROJECT_KEY;
280
393
  const release = ${environment}ERROR_MOM_RELEASE;
281
394
 
282
- if (!server || !projectKey) {
283
- throw new Error("ERROR_MOM_SERVER and ERROR_MOM_PROJECT_KEY are required");
284
- }
285
-
286
- initErrorMom({
287
- server,
288
- projectKey,
289
- environment: ${environment}ERROR_MOM_ENVIRONMENT ?? "production",
290
- ...(release ? { release } : {}),
291
- });
395
+ export const errorMom =
396
+ server && projectKey
397
+ ? initErrorMom({
398
+ server,
399
+ projectKey,
400
+ environment: ${environment}ERROR_MOM_ENVIRONMENT ?? "production",
401
+ ...(release ? { release } : {}),
402
+ })
403
+ : undefined;
292
404
  `;
293
405
  await writeFile(join(process.cwd(), relativePath), contents);
294
406
  return relativePath;
295
407
  }
408
+ async function writeNextInstrumentation(sourceDirectory) {
409
+ const file = join(process.cwd(), sourceDirectory, "instrumentation.ts");
410
+ if (existsSync(file)) return;
411
+ const contents = `import type { Instrumentation } from "next";
412
+
413
+ // Reports server-side errors (API routes, SSR, server actions, middleware)
414
+ // that Next.js catches before they can become uncaught exceptions.
415
+ export const onRequestError: Instrumentation.onRequestError = async (
416
+ error,
417
+ request,
418
+ context,
419
+ ) => {
420
+ if (process.env.NEXT_RUNTIME !== "nodejs") return;
421
+ const server = process.env.NEXT_PUBLIC_ERROR_MOM_SERVER;
422
+ const projectKey = process.env.NEXT_PUBLIC_ERROR_MOM_PROJECT_KEY;
423
+ if (!server || !projectKey) return;
424
+ const { initErrorMom } = await import("@kenkaiiii/error-mom/node");
425
+ initErrorMom({
426
+ server,
427
+ projectKey,
428
+ environment: process.env.NEXT_PUBLIC_ERROR_MOM_ENVIRONMENT ?? "production",
429
+ }).captureError(error, {
430
+ culprit: \`\${request.method} \${request.path}\`,
431
+ tags: { routerKind: context.routerKind, routeType: context.routeType, runtime: "server" },
432
+ });
433
+ };
434
+ `;
435
+ await writeFile(file, contents);
436
+ }
296
437
  async function appendEnvironment(framework, server, key) {
297
- const prefix = framework === "next" ? "NEXT_PUBLIC_" : framework === "vite" ? "VITE_" : "";
438
+ const prefix = framework.envStyle === "next" ? "NEXT_PUBLIC_" : framework.envStyle === "vite" ? "VITE_" : "";
298
439
  const file = join(process.cwd(), ".env.local");
299
440
  const existing = existsSync(file) ? await readFile(file, "utf8") : "";
300
441
  const lines = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "error-mom",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Agent-first CLI and MCP tools for self-hosted Error Mom",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,11 +11,6 @@
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
- },
19
14
  "dependencies": {
20
15
  "@modelcontextprotocol/sdk": "^1.29.0",
21
16
  "commander": "^15.0.0",
@@ -28,5 +23,10 @@
28
23
  "publishConfig": {
29
24
  "access": "public"
30
25
  },
31
- "license": "MIT"
32
- }
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
+ }