error-mom 0.2.0 → 0.3.1

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 +159 -22
  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.2.0";
13
+ var VERSION = "0.3.1";
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);
@@ -64,6 +64,17 @@ program.command("resolve").description("Resolve one issue and record the release
64
64
  )
65
65
  );
66
66
  });
67
+ program.command("delete-project").description("Permanently delete one project and all of its issues and history").argument("<project-id>").action(async (projectId) => {
68
+ const config = await loadConfig();
69
+ print(
70
+ await request(
71
+ config.server,
72
+ config.adminToken,
73
+ `/api/v1/projects/${encodeURIComponent(projectId)}`,
74
+ { method: "DELETE" }
75
+ )
76
+ );
77
+ });
67
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) => {
68
79
  const config = await loadConfig();
69
80
  const health = await request(config.server, void 0, "/api/health");
@@ -112,17 +123,18 @@ program.command("init").description("Create/select a project, install the SDK, a
112
123
  const setupPath = await writeSetup(framework);
113
124
  await writeFile(
114
125
  join(process.cwd(), ".error-mom.json"),
115
- `${JSON.stringify({ server: config.server, projectId: project.id, projectName: project.name, framework }, null, 2)}
126
+ `${JSON.stringify({ server: config.server, projectId: project.id, projectName: project.name, framework: framework.id }, null, 2)}
116
127
  `
117
128
  );
118
129
  await appendEnvironment(framework, config.server, project.ingestKey);
119
130
  print({
120
131
  installed: !options.skipInstall,
121
132
  project: { id: project.id, name: project.name, slug: project.slug },
122
- framework,
133
+ framework: framework.id,
123
134
  setupFile: setupPath,
124
135
  verified: false,
125
- nextAction: framework === "next" ? `Import ${setupPath} from the root layout for client errors; instrumentation.ts already reports server errors. For jobs/handlers that catch errors themselves (queues, webhooks, MCP tools), wrap them with errorMom.wrap(fn). Then run error-mom doctor --project-key <key>.` : `Import ${setupPath} from the earliest ${framework} entry point. For code where a framework catches errors itself (job runners, webhooks, MCP tools), wrap handlers with errorMom.wrap(fn). Then run error-mom doctor --project-key <key>.`
136
+ wiring: framework.wiring,
137
+ 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
138
  });
127
139
  });
128
140
  program.command("mcp").description("Run Error Mom tools over MCP stdio for coding agents").action(async () => {
@@ -193,6 +205,21 @@ async function runMcpServer() {
193
205
  )
194
206
  )
195
207
  );
208
+ server.registerTool(
209
+ "delete_project",
210
+ {
211
+ description: "Permanently delete a project and all of its issues, samples, and history. Irreversible.",
212
+ inputSchema: { projectId: z.string().min(1) }
213
+ },
214
+ async ({ projectId }) => toolResult(
215
+ await request(
216
+ config.server,
217
+ config.adminToken,
218
+ `/api/v1/projects/${encodeURIComponent(projectId)}`,
219
+ { method: "DELETE" }
220
+ )
221
+ )
222
+ );
196
223
  await server.connect(new StdioServerTransport());
197
224
  }
198
225
  function toolResult(value) {
@@ -243,14 +270,125 @@ async function readPackageJson(cwd) {
243
270
  );
244
271
  }
245
272
  }
273
+ var FRAMEWORKS = [
274
+ [
275
+ "next",
276
+ {
277
+ id: "next",
278
+ envStyle: "next",
279
+ 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)."
280
+ }
281
+ ],
282
+ [
283
+ "@tauri-apps/api",
284
+ {
285
+ id: "tauri",
286
+ envStyle: "vite",
287
+ 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."
288
+ }
289
+ ],
290
+ [
291
+ "electron",
292
+ {
293
+ id: "electron",
294
+ envStyle: "node",
295
+ 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."
296
+ }
297
+ ],
298
+ [
299
+ "astro",
300
+ {
301
+ id: "astro",
302
+ envStyle: "vite",
303
+ 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."
304
+ }
305
+ ],
306
+ [
307
+ "@sveltejs/kit",
308
+ {
309
+ id: "sveltekit",
310
+ envStyle: "vite",
311
+ 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."
312
+ }
313
+ ],
314
+ [
315
+ "nuxt",
316
+ {
317
+ id: "nuxt",
318
+ envStyle: "vite",
319
+ 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."
320
+ }
321
+ ],
322
+ [
323
+ "@remix-run/react",
324
+ {
325
+ id: "remix",
326
+ envStyle: "node",
327
+ 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."
328
+ }
329
+ ],
330
+ [
331
+ "@angular/core",
332
+ {
333
+ id: "angular",
334
+ envStyle: "node",
335
+ 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."
336
+ }
337
+ ],
338
+ [
339
+ "express",
340
+ {
341
+ id: "express",
342
+ envStyle: "node",
343
+ 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."
344
+ }
345
+ ],
346
+ [
347
+ "fastify",
348
+ {
349
+ id: "fastify",
350
+ envStyle: "node",
351
+ 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."
352
+ }
353
+ ],
354
+ [
355
+ "hono",
356
+ {
357
+ id: "hono",
358
+ envStyle: "node",
359
+ 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."
360
+ }
361
+ ],
362
+ [
363
+ "@nestjs/core",
364
+ {
365
+ id: "nestjs",
366
+ envStyle: "node",
367
+ 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."
368
+ }
369
+ ],
370
+ [
371
+ "vite",
372
+ {
373
+ id: "vite",
374
+ envStyle: "vite",
375
+ wiring: "Import the setup file first in the app entry (main.tsx or equivalent)."
376
+ }
377
+ ]
378
+ ];
246
379
  function detectFramework(packageJson) {
247
380
  const dependencies = {
248
381
  ...packageJson.dependencies ?? {},
249
382
  ...packageJson.devDependencies ?? {}
250
383
  };
251
- if (dependencies.next) return "next";
252
- if (dependencies.vite) return "vite";
253
- return "node";
384
+ for (const [dependency, info] of FRAMEWORKS) {
385
+ if (dependencies[dependency]) return info;
386
+ }
387
+ return {
388
+ id: "node",
389
+ envStyle: "node",
390
+ wiring: "Import the setup file at the TOP of the main entry point, before anything else."
391
+ };
254
392
  }
255
393
  function detectPackageManager(cwd) {
256
394
  if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
@@ -271,25 +409,24 @@ function installSdk(packageManager) {
271
409
  async function writeSetup(framework) {
272
410
  const sourceDirectory = existsSync(join(process.cwd(), "src")) ? "src" : ".";
273
411
  const relativePath = join(sourceDirectory, "error-mom.ts");
274
- if (framework === "next") await writeNextInstrumentation(sourceDirectory);
275
- const environment = framework === "next" ? "process.env.NEXT_PUBLIC_" : framework === "vite" ? "import.meta.env.VITE_" : "process.env.";
276
- const moduleName = framework === "node" ? "@kenkaiiii/error-mom/node" : "@kenkaiiii/error-mom";
277
- const contents = `${framework === "next" ? '"use client";\n\n' : ""}import { initErrorMom } from "${moduleName}";
412
+ if (framework.id === "next") await writeNextInstrumentation(sourceDirectory);
413
+ const environment = framework.envStyle === "next" ? "process.env.NEXT_PUBLIC_" : framework.envStyle === "vite" ? "import.meta.env.VITE_" : "process.env.";
414
+ const moduleName = framework.envStyle === "node" ? "@kenkaiiii/error-mom/node" : "@kenkaiiii/error-mom";
415
+ const contents = `${framework.id === "next" ? '"use client";\n\n' : ""}import { initErrorMom } from "${moduleName}";
278
416
 
279
417
  const server = ${environment}ERROR_MOM_SERVER;
280
418
  const projectKey = ${environment}ERROR_MOM_PROJECT_KEY;
281
419
  const release = ${environment}ERROR_MOM_RELEASE;
282
420
 
283
- if (!server || !projectKey) {
284
- throw new Error("ERROR_MOM_SERVER and ERROR_MOM_PROJECT_KEY are required");
285
- }
286
-
287
- initErrorMom({
288
- server,
289
- projectKey,
290
- environment: ${environment}ERROR_MOM_ENVIRONMENT ?? "production",
291
- ...(release ? { release } : {}),
292
- });
421
+ export const errorMom =
422
+ server && projectKey
423
+ ? initErrorMom({
424
+ server,
425
+ projectKey,
426
+ environment: ${environment}ERROR_MOM_ENVIRONMENT ?? "production",
427
+ ...(release ? { release } : {}),
428
+ })
429
+ : undefined;
293
430
  `;
294
431
  await writeFile(join(process.cwd(), relativePath), contents);
295
432
  return relativePath;
@@ -324,7 +461,7 @@ export const onRequestError: Instrumentation.onRequestError = async (
324
461
  await writeFile(file, contents);
325
462
  }
326
463
  async function appendEnvironment(framework, server, key) {
327
- const prefix = framework === "next" ? "NEXT_PUBLIC_" : framework === "vite" ? "VITE_" : "";
464
+ const prefix = framework.envStyle === "next" ? "NEXT_PUBLIC_" : framework.envStyle === "vite" ? "VITE_" : "";
328
465
  const file = join(process.cwd(), ".env.local");
329
466
  const existing = existsSync(file) ? await readFile(file, "utf8") : "";
330
467
  const lines = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "error-mom",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Agent-first CLI and MCP tools for self-hosted Error Mom",
5
5
  "type": "module",
6
6
  "bin": {