@agimon-ai/log-sink-mcp 0.18.4 → 0.19.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/README.md CHANGED
@@ -7,7 +7,8 @@ AI-powered log analysis MCP server with HTTP ingestion and SQLite storage.
7
7
  `log-sink-mcp` provides a complete log analysis solution for AI assistants like Claude. It combines:
8
8
 
9
9
  - **HTTP Server**: REST endpoint for log ingestion from any application
10
- - **MCP Server**: 7 AI-optimized tools for querying and analyzing logs
10
+ - **MCP Server**: 4 application-focused tools for searching/filtering, tracing, and inspecting logs
11
+ - **CLI Analysis Commands**: Local shell commands for deeper error and agent-issue analysis
11
12
  - **SQLite Database**: Fast local storage with FTS5 full-text search plus cached local embeddings
12
13
  - **Real-time Analysis**: Query logs, trace distributed requests, analyze error patterns
13
14
 
@@ -16,7 +17,7 @@ Perfect for debugging, monitoring, and understanding application behavior throug
16
17
  ## Features
17
18
 
18
19
  - ✅ **HTTP Log Ingestion**: POST logs from any application via REST API
19
- - ✅ **7 MCP Tools**: Query, search, trace, analyze, and manage logs
20
+ - ✅ **4 MCP Tools**: Search/filter, trace, and inspect stats/services
20
21
  - ✅ **Hybrid Search**: FTS5-powered search plus optional local semantic retrieval across messages and errors
21
22
  - ✅ **Distributed Tracing**: Timeline view for trace IDs and span relationships
22
23
  - ✅ **Error Analysis**: Pattern detection and error categorization
@@ -42,7 +43,7 @@ Perfect for debugging, monitoring, and understanding application behavior throug
42
43
 
43
44
  ┌──────────────┐ MCP Protocol ┌─────────────────┐
44
45
  │ Claude / │ <─────────────────────> │ MCP Server │
45
- │ AI Agent │ 7 Analysis Tools │ (stdio) │
46
+ │ AI Agent │ 4 App Log Tools │ (stdio) │
46
47
  └──────────────┘ └────────┬────────┘
47
48
 
48
49
 
@@ -132,6 +133,42 @@ bun run src/cli.ts start --port 3100 --db-path ./logs/session.db
132
133
 
133
134
  The `start` command uses singleton pattern - multiple MCP servers will share a single HTTP server automatically.
134
135
 
136
+ ## Local vs Global Log Sink
137
+
138
+ By default, log-sink resolves to a repo-local instance. Add a `.logsink.yaml` file when selected MCP/tooling packages should send telemetry to a shared global instance instead:
139
+
140
+ ```yaml
141
+ global:
142
+ port: 3100
143
+ services:
144
+ - "@agimon-ai/log-sink-mcp"
145
+ - "@agimon-ai/mcp-proxy"
146
+ - "@agimon-ai/browse-tool"
147
+ - "@agimon-ai/workflow-mcp"
148
+ ```
149
+
150
+ Only exact package/service names listed in `global.services` use the global instance. Unlisted names stay local. If `global.port` is omitted, log-sink asks the port registry to allocate and register an available global port dynamically.
151
+
152
+ Config lookup uses `LOG_SINK_CONFIG` or `--config <path>` first, then the nearest `.logsink.yaml` from the current directory upward, then `~/.logsink.yaml`. The global instance stores its registry identity and default database under `~/.log-sink-mcp/global/`.
153
+
154
+ Useful overrides:
155
+
156
+ ```bash
157
+ # Show currently registered ports
158
+ log-sink-mcp port --all --no-health-check
159
+ log-sink-mcp port --instance global
160
+ log-sink-mcp port --global
161
+
162
+ # Force one command to manage/query the local instance
163
+ log-sink-mcp status --instance local
164
+
165
+ # Query logs from the global instance
166
+ log-sink-mcp logs query --global --limit 20
167
+
168
+ # Send telemetry to an explicit running log-sink endpoint
169
+ LOG_SINK_ENDPOINT=http://127.0.0.1:3100 node ./dist/cli.mjs mcp-serve
170
+ ```
171
+
135
172
  ### 2. Configure MCP Server
136
173
 
137
174
  Add to your `mcp-config.yaml`:
@@ -267,51 +304,39 @@ Health check endpoint.
267
304
 
268
305
  ## MCP Tools Reference
269
306
 
270
- ### 1. `query_logs`
271
-
272
- Filter logs by level, service, or trace ID.
273
-
274
- **Parameters:**
275
- - `level?`: string - Filter by log level (debug, info, warn, error)
276
- - `service?`: string - Filter by service name
277
- - `traceId?`: string - Filter by trace ID
278
- - `limit?`: number - Maximum results (default: 100)
279
-
280
- **Example:**
281
- ```
282
- Query all error logs from user-service
283
- ```
284
-
285
- **Returns:** JSON array of matching log entries with timestamps, messages, metadata.
286
-
287
- ---
307
+ ### 1. `search_logs`
288
308
 
289
- ### 2. `search_logs`
290
-
291
- Hybrid search across log messages and error messages. By default it combines:
309
+ Search and filter log entries. Without `query`/`searchQuery`, it behaves as a structured filter tool. With `query` or `searchQuery`, it searches across log messages and error messages. By default text search combines:
292
310
 
293
311
  - FTS5 exact / keyword matching
294
312
  - Local semantic similarity from cached embeddings
295
313
 
296
314
  **Parameters:**
297
- - `searchQuery`: string (required) - Text to search for. Common punctuation such as hyphens is escaped safely; quoted phrases, prefix matching with `*`, and boolean operators `AND`/`OR`/`NOT` are supported.
315
+ - `query?` / `searchQuery?`: string - Optional text to search for. Common punctuation such as hyphens is escaped safely; quoted phrases, prefix matching with `*`, and boolean operators `AND`/`OR`/`NOT` are supported.
298
316
  - `mode?`: string - `hybrid` (default), `fts`, or `semantic`
299
317
  - `service?`: string or string[] - Filter by service name
300
318
  - `level?`: string or string[] - Filter by log level
319
+ - `traceId?`: string - Filter by trace ID
320
+ - `spanId?`: string - Filter by span ID
321
+ - `errorType?`: string - Filter by error type
322
+ - `sessionId?`: string - Filter by agent session ID
323
+ - `workflowRunId?`: string - Filter by workflow run ID
324
+ - `workflowName?`: string - Filter by workflow name
325
+ - `jobName?`: string - Filter by workflow job name
301
326
  - `startTime?`: string - ISO 8601 start time filter
302
327
  - `endTime?`: string - ISO 8601 end time filter
303
328
  - `limit?`: number - Maximum results (default: 100)
304
329
 
305
330
  **Example:**
306
331
  ```
307
- Search logs for "database connection timeout"
332
+ Find error logs from user-service that mention "database timeout"
308
333
  ```
309
334
 
310
- **Returns:** Matching logs with search relevance, count of results.
335
+ **Returns:** Matching logs with count and filters. Text searches also include search relevance metadata.
311
336
 
312
337
  ---
313
338
 
314
- ### 3. `get_trace_timeline`
339
+ ### 2. `get_trace_timeline`
315
340
 
316
341
  Get chronological timeline for a distributed trace.
317
342
 
@@ -327,23 +352,7 @@ Show me the trace timeline for trace ID a1b2c3d4e5f6789012345678901234ab
327
352
 
328
353
  ---
329
354
 
330
- ### 4. `analyze_errors`
331
-
332
- Analyze error patterns and categorize by error type.
333
-
334
- **Parameters:**
335
- - `limit?`: number - Maximum errors to analyze (default: 100)
336
-
337
- **Example:**
338
- ```
339
- Analyze error patterns in the logs
340
- ```
341
-
342
- **Returns:** Error categories, frequencies, example stack traces, common patterns.
343
-
344
- ---
345
-
346
- ### 5. `get_log_stats`
355
+ ### 3. `get_log_stats`
347
356
 
348
357
  Get statistics grouped by log level.
349
358
 
@@ -358,7 +367,7 @@ Show me log statistics
358
367
 
359
368
  ---
360
369
 
361
- ### 6. `get_services`
370
+ ### 4. `get_services`
362
371
 
363
372
  List all unique services that have logged.
364
373
 
@@ -371,23 +380,6 @@ What services are logging to the database?
371
380
 
372
381
  **Returns:** Array of service names with log counts.
373
382
 
374
- ---
375
-
376
- ### 7. `clear_logs`
377
-
378
- Delete all logs from the database.
379
-
380
- **Parameters:** None
381
-
382
- **Example:**
383
- ```
384
- Clear all logs from the database
385
- ```
386
-
387
- **Returns:** Confirmation with count of deleted entries.
388
-
389
- ⚠️ **Warning:** This operation cannot be undone.
390
-
391
383
  ## Integration Examples
392
384
 
393
385
  ### Pino Logger
@@ -556,21 +548,21 @@ bun run src/cli.ts mcp-serve --type sse --port 3002
556
548
 
557
549
  #### `logs` command
558
550
 
559
- Run the same analysis capabilities exposed by the package MCP tools directly from the shell.
551
+ Run log analysis capabilities directly from the shell. The deeper `analyze-errors` and `agent-issues` commands are CLI-only so the MCP server stays focused on application log inspection.
560
552
 
561
553
  ```bash
562
554
  bun run src/cli.ts logs <subcommand> [options]
563
555
  ```
564
556
 
565
557
  Subcommands:
566
- - `query`: Equivalent to `query_logs`
567
- - `search`: Equivalent to `search_logs`
558
+ - `query`: Compatibility alias for filter-only `search_logs`
559
+ - `search`: Unified filter/search command; the text query is optional
568
560
  - `search --mode hybrid`: Default hybrid search across FTS5 and semantic embeddings
569
561
  - `trace`: Equivalent to `get_trace_timeline`
570
- - `analyze-errors`: Equivalent to `analyze_errors`
562
+ - `analyze-errors`: CLI-only grouped error analysis
571
563
  - `stats`: Equivalent to `get_log_stats`
572
564
  - `services`: Equivalent to `get_services`
573
- - `clear`: Equivalent to `clear_logs`
565
+ - `clear`: CLI-only command to delete all logs from the selected database
574
566
 
575
567
  All subcommands support:
576
568
  - `--db-path <path>`: SQLite database path (default: `./logs/session.db`)
@@ -636,9 +628,9 @@ log-sink-mcp/
636
628
  │ │ ├── LogQueryService.ts
637
629
  │ │ ├── LogSearchService.ts
638
630
  │ │ └── LogRetentionService.ts
639
- │ ├── tools/ # MCP tools
640
- │ │ ├── QueryLogsTool.ts
631
+ │ ├── tools/ # MCP and CLI tool implementations
641
632
  │ │ ├── SearchLogsTool.ts
633
+ │ │ ├── QueryLogsTool.ts # Compatibility wrapper for filter-only queries
642
634
  │ │ ├── GetTraceTimelineTool.ts
643
635
  │ │ ├── AnalyzeErrorsTool.ts
644
636
  │ │ ├── GetLogStatsTool.ts
@@ -669,7 +661,7 @@ pnpm test --coverage
669
661
 
670
662
  **Test Coverage:**
671
663
  - HTTP server integration tests (8 tests)
672
- - MCP tools integration tests (17 tests)
664
+ - MCP/tool integration tests
673
665
  - Command tests (5 tests)
674
666
 
675
667
  ### Adding New Tools
package/dist/cli.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./node-8WCVGqP8.cjs`),t=require(`./stdio-_D27bJGJ.cjs`);let n=require(`zod`),r=require(`node:fs`),i=require(`node:fs/promises`);i=e.o(i,1);let a=require(`node:path`),o=require(`node:url`),s=require(`@agimon-ai/foundation-port-registry`),c=require(`@agimon-ai/foundation-process-registry`),l=require(`commander`),u=require(`@hono/node-server`),d=require(`hono`),f=require(`hono-rate-limiter`),p=require(`hono/cors`),m=require(`hono/html`),h=require(`hono/jsx/jsx-runtime`);const g=[`error`,`fatal`];function _(e){let t=e.trim();if(!t)return null;try{return JSON.parse(t)}catch{return null}}function v(e){if(e==null)return`unknown`;if(typeof e==`string`)return e;try{return JSON.stringify(e,null,2)}catch{return String(e)}}function y(e){return e.hook_event_name===`PostToolUseFailure`?`Current tool failure:\n- Tool: ${e.tool_name??`unknown tool`}\n- Error: ${v(e.error)}`:null}function b(e,t,n){let r=new Map;for(let t of e){let e=`${t.service}::${t.level}::${t.error_type??``}::${t.error_message??t.message}`,i=new Date(Number(t.created_at)*1e3).toISOString();r.has(e)||r.set(e,{key:e,count:0,service:t.service,level:t.level,errorType:t.error_type,errorMessage:t.error_message,message:t.message,firstSeen:i,lastSeen:i,traceIds:new Set,examples:[]});let a=r.get(e);a.count+=1,a.lastSeen=i,t.trace_id&&a.traceIds.add(t.trace_id),a.examples.length<n&&a.examples.push({id:t.id,timestamp:i,traceId:t.trace_id})}return Array.from(r.values()).sort((e,t)=>t.count-e.count||t.lastSeen.localeCompare(e.lastSeen)).slice(0,t)}function x(e,t,n,r){let i=b(e,n,r),a=[];a.push(`Recent log errors (${e.length} new):`),t.tool_name&&a.push(`- Triggered after tool: ${t.tool_name}`);for(let[e,t]of i.entries()){a.push(`${e+1}. ${t.service} [${t.level}] x${t.count} ${t.errorType?`(${t.errorType}) `:``}${t.errorMessage??t.message}`),a.push(` First seen: ${t.firstSeen}`),a.push(` Last seen: ${t.lastSeen}`),t.traceIds.size>0&&a.push(` Trace IDs: ${Array.from(t.traceIds).slice(0,3).join(`, `)}`);for(let e of t.examples)a.push(` Example: ${e.timestamp} id=${e.id}${e.traceId?` trace=${e.traceId}`:``}`)}if(t.hook_event_name===`PostToolUseFailure`){let e=y(t);e&&a.push(``,e)}return a.join(`
3
- `)}function S(e){if(e.session_id?.trim())return(0,s.normalizeRepositoryPath)(t.h(e.cwd?.trim()||process.cwd()))}async function ee(e,n){if(n.dbPath)return(0,r.existsSync)(n.dbPath)?n.dbPath:void 0;let i=process.env,a=S(e);if(!a)return;let o=new s.PortRegistryService(i.PORT_REGISTRY_PATH??s.DEFAULT_REGISTRY_PATH);try{let e=await o.getPort({repositoryPath:a,serviceName:`log-sink-mcp-http`,serviceType:`tool`,environment:i.NODE_ENV||`development`}),t=e.record?.metadata?.dbPath;if(e.success&&typeof t==`string`&&t&&(0,r.existsSync)(t))return t}catch{}let c=t.m(e.cwd??process.cwd());return(0,r.existsSync)(c)?c:void 0}async function te(e,t,n,r,i){let a=e.getSqliteClient(),o=Math.floor((Date.now()-n*60*1e3)/1e3),s=``;if(t){let e=a.prepare(`
2
+ const e=require(`./node-BdH18K8G.cjs`),t=require(`./stdio-BnKJGxUE.cjs`);let n=require(`zod`),r=require(`node:fs`),i=require(`node:fs/promises`);i=e.l(i,1);let a=require(`node:path`),o=require(`node:url`),s=require(`@agimon-ai/foundation-port-registry`),c=require(`@agimon-ai/foundation-process-registry`),l=require(`commander`),u=require(`@hono/node-server`),d=require(`hono`),f=require(`hono-rate-limiter`),p=require(`hono/cors`),m=require(`hono/html`),h=require(`hono/jsx/jsx-runtime`);function g(e){return e.option(`--config <path>`,`Path to .logsink.yaml`).option(`--global`,`Use the global log-sink instance`).option(`--instance <scope>`,`Force log-sink instance: local or global`)}function _(e){let t=e.instance?.trim();if(e.global&&t&&t.toLowerCase()!==`global`)throw Error(`Use either --global or --instance local, not both`);return e.global?`global`:t}function v(t,n=e.o){t.config&&(process.env.LOG_SINK_CONFIG=t.config);let r=_(t);return r&&(process.env.LOG_SINK_INSTANCE=r),e.s({configPath:t.config,packageName:n})}const ee=[`error`,`fatal`];function y(e){let t=e.trim();if(!t)return null;try{return JSON.parse(t)}catch{return null}}function b(e){if(e==null)return`unknown`;if(typeof e==`string`)return e;try{return JSON.stringify(e,null,2)}catch{return String(e)}}function x(e){return e.hook_event_name===`PostToolUseFailure`?`Current tool failure:\n- Tool: ${e.tool_name??`unknown tool`}\n- Error: ${b(e.error)}`:null}function S(e,t,n){let r=new Map;for(let t of e){let e=`${t.service}::${t.level}::${t.error_type??``}::${t.error_message??t.message}`,i=new Date(Number(t.created_at)*1e3).toISOString();r.has(e)||r.set(e,{key:e,count:0,service:t.service,level:t.level,errorType:t.error_type,errorMessage:t.error_message,message:t.message,firstSeen:i,lastSeen:i,traceIds:new Set,examples:[]});let a=r.get(e);a.count+=1,a.lastSeen=i,t.trace_id&&a.traceIds.add(t.trace_id),a.examples.length<n&&a.examples.push({id:t.id,timestamp:i,traceId:t.trace_id})}return Array.from(r.values()).sort((e,t)=>t.count-e.count||t.lastSeen.localeCompare(e.lastSeen)).slice(0,t)}function C(e,t,n,r){let i=S(e,n,r),a=[];a.push(`Recent log errors (${e.length} new):`),t.tool_name&&a.push(`- Triggered after tool: ${t.tool_name}`);for(let[e,t]of i.entries()){a.push(`${e+1}. ${t.service} [${t.level}] x${t.count} ${t.errorType?`(${t.errorType}) `:``}${t.errorMessage??t.message}`),a.push(` First seen: ${t.firstSeen}`),a.push(` Last seen: ${t.lastSeen}`),t.traceIds.size>0&&a.push(` Trace IDs: ${Array.from(t.traceIds).slice(0,3).join(`, `)}`);for(let e of t.examples)a.push(` Example: ${e.timestamp} id=${e.id}${e.traceId?` trace=${e.traceId}`:``}`)}if(t.hook_event_name===`PostToolUseFailure`){let e=x(t);e&&a.push(``,e)}return a.join(`
3
+ `)}function te(t){if(t.session_id?.trim())return(0,s.normalizeRepositoryPath)(e.c(t.cwd?.trim()||process.cwd()))}async function ne(t,n){let i=_(n);if(n.dbPath){let e=i===`global`?`global`:`local`;return(0,r.existsSync)(n.dbPath)?{dbPath:n.dbPath,scope:e}:void 0}let a={...process.env};n.config&&(a.LOG_SINK_CONFIG=n.config),i&&(a.LOG_SINK_INSTANCE=i);let o=e.s({configPath:n.config,cwd:t.cwd??process.cwd(),env:a,packageName:e.o}),c=new s.PortRegistryService(a.PORT_REGISTRY_PATH??s.DEFAULT_REGISTRY_PATH);try{let e=await c.getPort({repositoryPath:o.repositoryPath,serviceName:`log-sink-mcp-http`,serviceType:`tool`,environment:a.NODE_ENV||`development`}),t=e.record?.metadata?.dbPath;if(e.success&&typeof t==`string`&&t&&(0,r.existsSync)(t))return{dbPath:t,scope:o.scope}}catch{}return(0,r.existsSync)(o.dbPath)?{dbPath:o.dbPath,scope:o.scope}:void 0}async function re(e,t,n,r,i,a){let o=e.getSqliteClient(),s=Math.floor((Date.now()-n*60*1e3)/1e3),c=``;if(t){let e=o.prepare(`
4
4
  SELECT id, created_at
5
5
  FROM logs
6
6
  WHERE id = ?
7
7
  LIMIT 1
8
- `).all(t)[0];e&&(o=Number(e.created_at),s=e.id)}let c=a.prepare(`
8
+ `).all(t)[0];e&&(s=Number(e.created_at),c=e.id)}let l=a?`AND session_id = ?`:``,u=[...ee,s,s,c];a&&u.push(a),u.push(Math.max(r*i*2,r*3));let d=o.prepare(`
9
9
  SELECT
10
10
  id,
11
11
  created_at,
@@ -23,9 +23,10 @@ const e=require(`./node-8WCVGqP8.cjs`),t=require(`./stdio-_D27bJGJ.cjs`);let n=r
23
23
  created_at > ?
24
24
  OR (created_at = ? AND id > ?)
25
25
  )
26
+ ${l}
26
27
  ORDER BY created_at ASC, id ASC
27
28
  LIMIT ?
28
- `).all(...g,o,o,s,Math.max(r*i*2,r*3));return c.length===0?{rows:[]}:{rows:c,nextCheckpoint:c[c.length-1]}}async function ne(e,n={}){let r=S(e),i=e.session_id?.trim();if(!r||!i)return null;let a=await ee(e,n);if(!a)return null;let o=new t.p;try{await o.initializeDatabase(a);let{rows:t,nextCheckpoint:s}=await te(o,await o.getHookCheckpoint(i,r),n.lookbackMinutes??10,n.maxGroups??5,n.maxExamplesPerGroup??3),c=y(e);if(t.length===0)return c?{hookSpecificOutput:{hookEventName:e.hook_event_name??`PostToolUse`,additionalContext:c}}:null;let l=x(t,e,n.maxGroups??5,n.maxExamplesPerGroup??3);return await o.setHookCheckpoint(i,r,s?.id??t[t.length-1].id),{hookSpecificOutput:{hookEventName:e.hook_event_name??`PostToolUse`,additionalContext:c?`${l}\n\n${c}`:l}}}finally{await o.disconnect().catch(()=>void 0)}}const re=new l.Command(`claude-hook`).alias(`hook`).description(`Emit additional Claude Code context from recent log errors`).option(`--db-path <path>`,`Path to the SQLite database file`).option(`--lookback-minutes <minutes>`,`Initial monitoring window in minutes`,`10`).option(`--max-groups <count>`,`Maximum number of grouped errors to include`,`5`).option(`--max-examples-per-group <count>`,`Maximum number of example errors to include per group`,`3`).action(async e=>{try{let t=_((0,r.readFileSync)(0,`utf-8`));t||process.exit(0);let n=await ne(t,{dbPath:e.dbPath,lookbackMinutes:e.lookbackMinutes?Number.parseInt(e.lookbackMinutes,10):10,maxGroups:e.maxGroups?Number.parseInt(e.maxGroups,10):5,maxExamplesPerGroup:e.maxExamplesPerGroup?Number.parseInt(e.maxExamplesPerGroup,10):3});n||process.exit(0),process.stdout.write(`${JSON.stringify(n)}\n`),process.exit(0)}catch(e){process.stderr.write(`Error running Claude hook: ${e instanceof Error?e.message:String(e)}\n`),process.exit(1)}}),ie=n.z.object({service:n.z.string().optional(),level:n.z.string().optional(),traceId:n.z.string().optional(),search:n.z.string().optional(),limit:n.z.coerce.number().min(1).max(1e3).default(25),offset:n.z.coerce.number().min(0).default(0)});function ae(e){let n=new d.Hono,r=e.get(t.g.LogQueryService),i=e.get(t.g.LogSearchService);return n.get(`/logs`,async e=>{try{let{service:t,level:n,traceId:a,search:o,limit:s,offset:c}=ie.parse({service:e.req.query(`service`),level:e.req.query(`level`),traceId:e.req.query(`traceId`),search:e.req.query(`search`),limit:e.req.query(`limit`),offset:e.req.query(`offset`)}),l,u=0;if(o){let e={};t&&(e.service=t),n&&(e.level=n);let r=await i.searchLogsPaginated(o,{...e,offset:c},s);l=r.results,u=r.total}else{let e=n?n.split(`,`):[`info`,`warn`,`error`,`fatal`],i=await r.queryLogs({level:e,service:t||void 0,traceId:a||void 0,limit:s,offset:c});l=i.logs,u=i.total}return e.json({logs:l,total:u,hasMore:c+s<u})}catch(t){return console.error(`Failed to query logs:`,t),e.json({error:`Failed to query logs`,message:t instanceof Error?t.message:String(t)},500)}}),n.get(`/services`,async e=>{try{let t=await r.getActiveServices();return e.json({services:t,total:t.length})}catch(t){return console.error(`Failed to get services:`,t),e.json({error:`Failed to get services`,message:t instanceof Error?t.message:String(t)},500)}}),n.get(`/stats`,async e=>{try{let t=await r.getStatistics(),n=t.reduce((e,t)=>e+t.count,0),i=t.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),a=new Set(t.map(e=>e.service)).size;return e.json({totalLogs:n,errors:i,services:a,breakdown:t})}catch(t){return console.error(`Failed to get statistics:`,t),e.json({error:`Failed to get statistics`,message:t instanceof Error?t.message:String(t)},500)}}),n}function oe(e){return e.toLocaleString(`en-US`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}const C=`log-table`,w=`level-trace`,T=`level-debug`,E=`level-info`,se=`level-warn`,ce=`level-error`,le=`level-fatal`,D=`stat-card`,O=`trace-link`,k=`timestamp-cell`,A=`service-cell`,j=`pagination-btn`,M=`log-entry-group`,N=`metadata-row`,P=`message-row`,F=`message-full-cell`;function ue(e){let t=e.toLowerCase();return t===`trace`?w:t===`debug`?T:t===`info`?E:t===`warn`?se:t===`error`?ce:t===`fatal`?le:``}function de(e){if(!e||typeof e!=`object`)return null;let t=e,n={};for(let[e,r]of Object.entries(t))e===`trace.id`||e===`span.id`||(n[e]=r);return Object.keys(n).length>0?n:null}function fe({logs:e}){return(0,h.jsx)(`div`,{class:`table-container`,children:(0,h.jsxs)(`table`,{class:C,children:[(0,h.jsx)(`thead`,{children:(0,h.jsxs)(`tr`,{children:[(0,h.jsx)(`th`,{children:`Timestamp`}),(0,h.jsx)(`th`,{children:`Level`}),(0,h.jsx)(`th`,{children:`Service`}),(0,h.jsx)(`th`,{children:`Trace ID`})]})}),e.map(e=>{let t=de(e.metadata);return(0,h.jsxs)(`tbody`,{class:M,children:[(0,h.jsxs)(`tr`,{class:N,children:[(0,h.jsx)(`td`,{class:k,children:oe(e.timestamp)}),(0,h.jsx)(`td`,{class:ue(e.level),children:e.level.toUpperCase()}),(0,h.jsx)(`td`,{class:A,children:e.service}),(0,h.jsx)(`td`,{children:e.traceId?(0,h.jsxs)(`span`,{class:O,onclick:`dashboard.filterByTrace('${e.traceId}')`,children:[e.traceId.slice(0,8),`...`]}):(0,h.jsx)(`span`,{children:`-`})})]}),(0,h.jsx)(`tr`,{class:P,children:(0,h.jsxs)(`td`,{colspan:4,class:F,children:[(0,h.jsx)(`div`,{children:e.message}),t&&(0,h.jsx)(`pre`,{class:`attributes-cell`,children:JSON.stringify(t,null,2)})]})})]},e.id)})]})})}function pe(){return(0,h.jsx)(`input`,{id:`search-input`,type:`text`,class:`input-field`,placeholder:`Search logs...`,oninput:`dashboard.handleSearchInput(event)`})}function me({services:e}){return(0,h.jsxs)(`select`,{id:`service-select`,class:`select-field`,onchange:`dashboard.handleServiceChange(event)`,children:[(0,h.jsx)(`option`,{value:``,children:`All Services`}),e.map(e=>(0,h.jsx)(`option`,{value:e,children:e},e))]})}function he({stats:e}){return(0,h.jsxs)(`div`,{class:`stats-container`,children:[(0,h.jsxs)(`div`,{class:D,children:[(0,h.jsx)(`h3`,{children:`Total Logs`}),(0,h.jsx)(`div`,{class:`value`,children:e.totalLogs.toLocaleString()})]}),(0,h.jsxs)(`div`,{class:D,children:[(0,h.jsx)(`h3`,{children:`Errors`}),(0,h.jsx)(`div`,{class:`value`,children:e.errors.toLocaleString()})]}),(0,h.jsxs)(`div`,{class:D,children:[(0,h.jsx)(`h3`,{children:`Services`}),(0,h.jsx)(`div`,{class:`value`,children:e.services})]})]})}function ge({initialLogs:e,services:t,stats:n}){return(0,h.jsxs)(`div`,{class:`dashboard-container`,children:[(0,h.jsxs)(`div`,{class:`dashboard-header`,children:[(0,h.jsx)(`h1`,{children:`Log Dashboard`}),(0,h.jsx)(`p`,{children:`Real-time log streaming with auto-refresh (3 seconds)`})]}),(0,h.jsx)(he,{stats:n}),(0,h.jsxs)(`div`,{class:`controls-section`,children:[(0,h.jsx)(pe,{}),(0,h.jsx)(me,{services:t}),(0,h.jsx)(`button`,{type:`button`,id:`refresh-btn`,class:`btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`})]}),(0,h.jsx)(fe,{logs:e}),(0,h.jsxs)(`div`,{id:`pagination-controls`,class:`pagination-container`,children:[(0,h.jsx)(`button`,{type:`button`,id:`prev-btn`,class:j,onclick:`dashboard.prevPage()`,disabled:!0,children:`Previous`}),(0,h.jsx)(`span`,{id:`page-info`,class:`page-info`,children:`Page 1`}),(0,h.jsx)(`button`,{type:`button`,id:`next-btn`,class:j,onclick:`dashboard.nextPage()`,children:`Next`})]}),(0,h.jsx)(`script`,{children:(0,m.raw)(`
29
+ `).all(...u);return d.length===0?{rows:[]}:{rows:d,nextCheckpoint:d[d.length-1]}}async function ie(e,n={}){let r=te(e),i=e.session_id?.trim();if(!r||!i)return null;let a=await ne(e,n);if(!a)return null;let o=new t.l;try{await o.initializeDatabase(a.dbPath);let{rows:t,nextCheckpoint:s}=await re(o,await o.getHookCheckpoint(i,r),n.lookbackMinutes??10,n.maxGroups??5,n.maxExamplesPerGroup??3,a.scope===`global`?i:void 0),c=x(e);if(t.length===0)return c?{hookSpecificOutput:{hookEventName:e.hook_event_name??`PostToolUse`,additionalContext:c}}:null;let l=C(t,e,n.maxGroups??5,n.maxExamplesPerGroup??3);return await o.setHookCheckpoint(i,r,s?.id??t[t.length-1].id),{hookSpecificOutput:{hookEventName:e.hook_event_name??`PostToolUse`,additionalContext:c?`${l}\n\n${c}`:l}}}finally{await o.disconnect().catch(()=>void 0)}}const ae=g(new l.Command(`claude-hook`)).alias(`hook`).description(`Emit additional Claude Code context from recent log errors`).option(`--db-path <path>`,`Path to the SQLite database file`).option(`--lookback-minutes <minutes>`,`Initial monitoring window in minutes`,`10`).option(`--max-groups <count>`,`Maximum number of grouped errors to include`,`5`).option(`--max-examples-per-group <count>`,`Maximum number of example errors to include per group`,`3`).action(async e=>{try{let t=y((0,r.readFileSync)(0,`utf-8`));t||process.exit(0);let n=await ie(t,{dbPath:e.dbPath,config:e.config,global:e.global,instance:e.instance,lookbackMinutes:e.lookbackMinutes?Number.parseInt(e.lookbackMinutes,10):10,maxGroups:e.maxGroups?Number.parseInt(e.maxGroups,10):5,maxExamplesPerGroup:e.maxExamplesPerGroup?Number.parseInt(e.maxExamplesPerGroup,10):3});n||process.exit(0),process.stdout.write(`${JSON.stringify(n)}\n`),process.exit(0)}catch(e){process.stderr.write(`Error running Claude hook: ${e instanceof Error?e.message:String(e)}\n`),process.exit(1)}}),oe=n.z.object({service:n.z.string().optional(),level:n.z.string().optional(),traceId:n.z.string().optional(),search:n.z.string().optional(),limit:n.z.coerce.number().min(1).max(1e3).default(25),offset:n.z.coerce.number().min(0).default(0)});function se(e){let n=new d.Hono,r=e.get(t.u.LogQueryService),i=e.get(t.u.LogSearchService);return n.get(`/logs`,async e=>{try{let{service:t,level:n,traceId:a,search:o,limit:s,offset:c}=oe.parse({service:e.req.query(`service`),level:e.req.query(`level`),traceId:e.req.query(`traceId`),search:e.req.query(`search`),limit:e.req.query(`limit`),offset:e.req.query(`offset`)}),l,u=0;if(o){let e={};t&&(e.service=t),n&&(e.level=n);let r=await i.searchLogsPaginated(o,{...e,offset:c},s);l=r.results,u=r.total}else{let e=n?n.split(`,`):[`info`,`warn`,`error`,`fatal`],i=await r.queryLogs({level:e,service:t||void 0,traceId:a||void 0,limit:s,offset:c});l=i.logs,u=i.total}return e.json({logs:l,total:u,hasMore:c+s<u})}catch(t){return console.error(`Failed to query logs:`,t),e.json({error:`Failed to query logs`,message:t instanceof Error?t.message:String(t)},500)}}),n.get(`/services`,async e=>{try{let t=await r.getActiveServices();return e.json({services:t,total:t.length})}catch(t){return console.error(`Failed to get services:`,t),e.json({error:`Failed to get services`,message:t instanceof Error?t.message:String(t)},500)}}),n.get(`/stats`,async e=>{try{let t=await r.getStatistics(),n=t.reduce((e,t)=>e+t.count,0),i=t.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),a=new Set(t.map(e=>e.service)).size;return e.json({totalLogs:n,errors:i,services:a,breakdown:t})}catch(t){return console.error(`Failed to get statistics:`,t),e.json({error:`Failed to get statistics`,message:t instanceof Error?t.message:String(t)},500)}}),n}function ce(e){return e.toLocaleString(`en-US`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}const w=`log-table`,T=`level-trace`,E=`level-debug`,D=`level-info`,le=`level-warn`,ue=`level-error`,de=`level-fatal`,O=`stat-card`,fe=`trace-link`,pe=`timestamp-cell`,k=`service-cell`,A=`pagination-btn`,j=`log-entry-group`,M=`metadata-row`,N=`message-row`,P=`message-full-cell`;function me(e){let t=e.toLowerCase();return t===`trace`?T:t===`debug`?E:t===`info`?D:t===`warn`?le:t===`error`?ue:t===`fatal`?de:``}function he(e){if(!e||typeof e!=`object`)return null;let t=e,n={};for(let[e,r]of Object.entries(t))e===`trace.id`||e===`span.id`||(n[e]=r);return Object.keys(n).length>0?n:null}function ge({logs:e}){return(0,h.jsx)(`div`,{class:`table-container`,children:(0,h.jsxs)(`table`,{class:w,children:[(0,h.jsx)(`thead`,{children:(0,h.jsxs)(`tr`,{children:[(0,h.jsx)(`th`,{children:`Timestamp`}),(0,h.jsx)(`th`,{children:`Level`}),(0,h.jsx)(`th`,{children:`Service`}),(0,h.jsx)(`th`,{children:`Trace ID`})]})}),e.map(e=>{let t=he(e.metadata);return(0,h.jsxs)(`tbody`,{class:j,children:[(0,h.jsxs)(`tr`,{class:M,children:[(0,h.jsx)(`td`,{class:pe,children:ce(e.timestamp)}),(0,h.jsx)(`td`,{class:me(e.level),children:e.level.toUpperCase()}),(0,h.jsx)(`td`,{class:k,children:e.service}),(0,h.jsx)(`td`,{children:e.traceId?(0,h.jsxs)(`span`,{class:fe,onclick:`dashboard.filterByTrace('${e.traceId}')`,children:[e.traceId.slice(0,8),`...`]}):(0,h.jsx)(`span`,{children:`-`})})]}),(0,h.jsx)(`tr`,{class:N,children:(0,h.jsxs)(`td`,{colspan:4,class:P,children:[(0,h.jsx)(`div`,{children:e.message}),t&&(0,h.jsx)(`pre`,{class:`attributes-cell`,children:JSON.stringify(t,null,2)})]})})]},e.id)})]})})}function _e(){return(0,h.jsx)(`input`,{id:`search-input`,type:`text`,class:`input-field`,placeholder:`Search logs...`,oninput:`dashboard.handleSearchInput(event)`})}function ve({services:e}){return(0,h.jsxs)(`select`,{id:`service-select`,class:`select-field`,onchange:`dashboard.handleServiceChange(event)`,children:[(0,h.jsx)(`option`,{value:``,children:`All Services`}),e.map(e=>(0,h.jsx)(`option`,{value:e,children:e},e))]})}function ye({stats:e}){return(0,h.jsxs)(`div`,{class:`stats-container`,children:[(0,h.jsxs)(`div`,{class:O,children:[(0,h.jsx)(`h3`,{children:`Total Logs`}),(0,h.jsx)(`div`,{class:`value`,children:e.totalLogs.toLocaleString()})]}),(0,h.jsxs)(`div`,{class:O,children:[(0,h.jsx)(`h3`,{children:`Errors`}),(0,h.jsx)(`div`,{class:`value`,children:e.errors.toLocaleString()})]}),(0,h.jsxs)(`div`,{class:O,children:[(0,h.jsx)(`h3`,{children:`Services`}),(0,h.jsx)(`div`,{class:`value`,children:e.services})]})]})}function be({initialLogs:e,services:t,stats:n}){return(0,h.jsxs)(`div`,{class:`dashboard-container`,children:[(0,h.jsxs)(`div`,{class:`dashboard-header`,children:[(0,h.jsx)(`h1`,{children:`Log Dashboard`}),(0,h.jsx)(`p`,{children:`Real-time log streaming with auto-refresh (3 seconds)`})]}),(0,h.jsx)(ye,{stats:n}),(0,h.jsxs)(`div`,{class:`controls-section`,children:[(0,h.jsx)(_e,{}),(0,h.jsx)(ve,{services:t}),(0,h.jsx)(`button`,{type:`button`,id:`refresh-btn`,class:`btn`,onclick:`dashboard.manualRefresh()`,children:`Refresh Now`})]}),(0,h.jsx)(ge,{logs:e}),(0,h.jsxs)(`div`,{id:`pagination-controls`,class:`pagination-container`,children:[(0,h.jsx)(`button`,{type:`button`,id:`prev-btn`,class:A,onclick:`dashboard.prevPage()`,disabled:!0,children:`Previous`}),(0,h.jsx)(`span`,{id:`page-info`,class:`page-info`,children:`Page 1`}),(0,h.jsx)(`button`,{type:`button`,id:`next-btn`,class:A,onclick:`dashboard.nextPage()`,children:`Next`})]}),(0,h.jsx)(`script`,{children:(0,m.raw)(`
29
30
  class DashboardManager {
30
31
  constructor() {
31
32
  this.filters = {
@@ -95,7 +96,7 @@ class DashboardManager {
95
96
  }
96
97
 
97
98
  updateLogTable(logs) {
98
- const table = document.querySelector('.${C}');
99
+ const table = document.querySelector('.${w}');
99
100
  if (!table) return;
100
101
 
101
102
  // Remove existing tbody elements (except thead)
@@ -107,7 +108,7 @@ class DashboardManager {
107
108
  const levelClass = this.getLevelClass(log.level);
108
109
  const timestamp = this.formatTimestamp(log.timestamp);
109
110
  const traceIdHtml = log.traceId
110
- ? \`<span class="${O}" onclick="dashboard.filterByTrace('\${log.traceId}')">\${log.traceId.slice(0, 8)}...</span>\`
111
+ ? \`<span class="${fe}" onclick="dashboard.filterByTrace('\${log.traceId}')">\${log.traceId.slice(0, 8)}...</span>\`
111
112
  : '<span>-</span>';
112
113
 
113
114
  // Get display attributes (filter out trace/span IDs)
@@ -117,16 +118,16 @@ class DashboardManager {
117
118
  : '';
118
119
 
119
120
  const tbody = document.createElement('tbody');
120
- tbody.className = '${M}';
121
+ tbody.className = '${j}';
121
122
  tbody.innerHTML = \`
122
- <tr class="${N}">
123
- <td class="${k}">\${timestamp}</td>
123
+ <tr class="${M}">
124
+ <td class="${pe}">\${timestamp}</td>
124
125
  <td class="\${levelClass}">\${log.level.toUpperCase()}</td>
125
- <td class="${A}">\${log.service}</td>
126
+ <td class="${k}">\${log.service}</td>
126
127
  <td>\${traceIdHtml}</td>
127
128
  </tr>
128
- <tr class="${P}">
129
- <td colspan="4" class="${F}">
129
+ <tr class="${N}">
130
+ <td colspan="4" class="${P}">
130
131
  <div>\${this.escapeHtml(log.message)}</div>
131
132
  \${attrsHtml}
132
133
  </td>
@@ -159,12 +160,12 @@ class DashboardManager {
159
160
 
160
161
  getLevelClass(level) {
161
162
  const classes = {
162
- trace: '${w}',
163
- debug: '${T}',
164
- info: '${E}',
165
- warn: '${se}',
166
- error: '${ce}',
167
- fatal: '${le}',
163
+ trace: '${T}',
164
+ debug: '${E}',
165
+ info: '${D}',
166
+ warn: '${le}',
167
+ error: '${ue}',
168
+ fatal: '${de}',
168
169
  };
169
170
  return classes[level.toLowerCase()] || '';
170
171
  }
@@ -267,7 +268,7 @@ const dashboard = new DashboardManager();
267
268
  window.addEventListener('beforeunload', () => {
268
269
  dashboard.stopAutoRefresh();
269
270
  });
270
- `)})]})}function I({title:e,children:t}){return(0,h.jsxs)(`html`,{lang:`en`,children:[(0,h.jsxs)(`head`,{children:[(0,h.jsx)(`meta`,{charset:`UTF-8`}),(0,h.jsx)(`meta`,{name:`viewport`,content:`width=device-width, initial-scale=1.0`}),(0,h.jsx)(`title`,{children:e}),(0,h.jsx)(`style`,{children:(0,m.raw)(`
271
+ `)})]})}function F({title:e,children:t}){return(0,h.jsxs)(`html`,{lang:`en`,children:[(0,h.jsxs)(`head`,{children:[(0,h.jsx)(`meta`,{charset:`UTF-8`}),(0,h.jsx)(`meta`,{name:`viewport`,content:`width=device-width, initial-scale=1.0`}),(0,h.jsx)(`title`,{children:e}),(0,h.jsx)(`style`,{children:(0,m.raw)(`
271
272
  * {
272
273
  margin: 0;
273
274
  padding: 0;
@@ -582,9 +583,10 @@ window.addEventListener('beforeunload', () => {
582
583
  color: #555;
583
584
  margin-top: 0.25rem;
584
585
  }
585
- `)})]}),(0,h.jsx)(`body`,{children:t})]})}function _e(e){let n=new d.Hono,r=e.get(t.g.LogQueryService);return n.get(`/`,async e=>{try{let t=await r.getActiveServices(),n=await r.getStatistics(),i=await r.filterByLevel([`info`,`warn`,`error`,`fatal`],100),a={totalLogs:n.reduce((e,t)=>e+t.count,0),errors:n.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),services:new Set(n.map(e=>e.service)).size};return e.html((0,h.jsx)(I,{title:`Log Dashboard`,children:(0,h.jsx)(ge,{initialLogs:i,services:t,stats:a})}))}catch(t){return console.error(`Failed to render dashboard:`,t),e.html((0,h.jsx)(I,{title:`Log Dashboard - Error`,children:(0,h.jsxs)(`div`,{style:`padding: 2rem; text-align: center;`,children:[(0,h.jsx)(`h1`,{children:`Failed to load dashboard`}),(0,h.jsx)(`p`,{children:t instanceof Error?t.message:String(t)}),(0,h.jsx)(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),n}const L=5*1024*1024,R=2e3,z=8e3,ve=n.z.object({timestamp:n.z.string().datetime().optional(),level:n.z.enum([`trace`,`debug`,`info`,`warn`,`error`,`fatal`]),message:n.z.string().min(1).max(z),traceId:n.z.string().regex(/^[0-9a-f]{32}$/i,`traceId must be 32 hex characters`).optional(),spanId:n.z.string().regex(/^[0-9a-f]{16}$/i,`spanId must be 16 hex characters`).optional(),parentSpanId:n.z.string().regex(/^[0-9a-f]{16}$/i,`parentSpanId must be 16 hex characters`).optional(),service:n.z.string().min(1).max(200),hostname:n.z.string().max(255).optional(),pid:n.z.number().int().nonnegative().optional(),metadata:n.z.record(n.z.string(),n.z.unknown()).optional(),errorType:n.z.string().max(255).optional(),errorMessage:n.z.string().max(8e3).optional(),errorStack:n.z.string().max(32e3).optional()}),ye=n.z.object({logs:n.z.array(ve).min(1).max(1e3)});function B(e){if(!e||!/^\d+$/.test(e))throw Error(`OTLP timestamp must be a numeric unix-nanoseconds string`);let t=Number(BigInt(e)/BigInt(1e6));return new Date(t)}function V(e,t){return typeof e==`string`&&RegExp(`^[0-9a-f]{${t}}$`,`i`).test(e)}function H(e){if(!e)return null;let t=Number(e);return!Number.isFinite(t)||t<=0?null:t>L?`Request body exceeds ${L} bytes`:null}async function U(e){try{return await e.req.json()}catch(e){throw Error(`Invalid JSON body: ${e instanceof Error?e.message:String(e)}`)}}function be(e){if(!Array.isArray(e.resourceSpans)||e.resourceSpans.length===0)throw Error(`Invalid OTLP trace request: missing resourceSpans`);let t=0;for(let n of e.resourceSpans){if(!Array.isArray(n.scopeSpans))throw Error(`Invalid OTLP trace request: scopeSpans must be an array`);for(let e of n.scopeSpans){if(!Array.isArray(e.spans))throw Error(`Invalid OTLP trace request: spans must be an array`);for(let n of e.spans){if(t+=1,t>R)throw Error(`OTLP trace request exceeds ${R} spans`);if(!V(n.traceId,32))throw Error(`Invalid OTLP trace request: traceId must be 32 hex characters`);if(!V(n.spanId,16))throw Error(`Invalid OTLP trace request: spanId must be 16 hex characters`);if(n.parentSpanId&&!V(n.parentSpanId,16))throw Error(`Invalid OTLP trace request: parentSpanId must be 16 hex characters`);if(!n.name||n.name.length>z)throw Error(`Invalid OTLP trace request: span name is missing or too long`);B(n.startTimeUnixNano)}}}}function xe(e){if(!Array.isArray(e.resourceLogs)||e.resourceLogs.length===0)throw Error(`Invalid OTLP logs request: missing resourceLogs`);let t=0;for(let n of e.resourceLogs){if(!Array.isArray(n.scopeLogs))throw Error(`Invalid OTLP logs request: scopeLogs must be an array`);for(let e of n.scopeLogs){if(!Array.isArray(e.logRecords))throw Error(`Invalid OTLP logs request: logRecords must be an array`);for(let n of e.logRecords){if(t+=1,t>R)throw Error(`OTLP logs request exceeds ${R} log records`);if(n.traceId&&!V(n.traceId,32))throw Error(`Invalid OTLP logs request: traceId must be 32 hex characters`);if(n.spanId&&!V(n.spanId,16))throw Error(`Invalid OTLP logs request: spanId must be 16 hex characters`);if(n.severityText&&n.severityText.length>32)throw Error(`Invalid OTLP logs request: severityText is too long`);B(n.timeUnixNano)}}}}function Se(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function W(e,t){let n=e?.find(e=>e.key===t);if(!n)return;let{value:r}=n;if(r.stringValue!==void 0)return r.stringValue;if(r.intValue!==void 0)return r.intValue;if(r.doubleValue!==void 0)return String(r.doubleValue);if(r.boolValue!==void 0)return String(r.boolValue)}function Ce(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function we(e){return e===void 0?`info`:e<=4?`trace`:e<=8?`debug`:e<=12?`info`:e<=16?`warn`:e<=20?`error`:`fatal`}function G(e){let t=t=>{let n=e.req.header(t);return n&&n.length>0?n.slice(0,200):null};return{sessionId:t(`x-agent-session-id`),workflowRunId:t(`x-workflow-run-id`),workflowName:t(`x-workflow-name`),jobName:t(`x-workflow-job-name`),jobId:t(`x-workflow-job-id`),agent:t(`x-agent`)}}function K(e,t){return{...e,service:e.service===`unknown-service`&&t.agent?t.agent:e.service,sessionId:t.sessionId,workflowRunId:t.workflowRunId,workflowName:t.workflowName,jobName:t.jobName,jobId:t.jobId}}function Te(e,t){let n=B(e.timeUnixNano),r=e.severityText?.toLowerCase()||we(e.severityNumber),i=``;if(e.body?.stringValue)i=e.body.stringValue;else if(e.body?.kvlistValue){let t={};for(let n of e.body.kvlistValue.values){let e=n.value.stringValue??n.value.intValue??n.value.doubleValue??n.value.boolValue;t[n.key]=e}i=JSON.stringify(t)}let a={},o=null,s=null,c=null;if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;t.key===`exception.type`?o=String(e):t.key===`exception.message`?s=String(e):t.key===`exception.stacktrace`?c=String(e):a[t.key]=e}return{timestamp:n,level:r,message:i||`[LOG] ${t}`,traceId:e.traceId??null,spanId:e.spanId??null,parentSpanId:null,service:t,hostname:null,pid:null,metadata:Object.keys(a).length>0?a:null,errorType:o,errorMessage:s,errorStack:c}}function Ee(e,t){let n=B(e.startTimeUnixNano),r=e.status?.code,i=`info`;r===2?i=`error`:r===1&&(i=`info`);let a=W(e.attributes,`exception.type`),o=W(e.attributes,`exception.message`),s=W(e.attributes,`exception.stacktrace`),c={};if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;c[t.key]=e}return c.spanName=e.name,c.spanKind=e.kind,e.status?.message&&(c.statusMessage=e.status.message),{timestamp:n,level:i,message:`[SPAN] ${e.name}`,traceId:e.traceId,spanId:e.spanId,parentSpanId:e.parentSpanId??null,service:t,hostname:null,pid:null,metadata:c,errorType:a??null,errorMessage:o??null,errorStack:s??null}}function De(e){let n=new d.Hono,r=e.get(t.g.LogStorageService);n.use(`*`,(0,p.cors)({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),n.get(`/health`,async e=>{try{return e.json({status:`healthy`,service:`log-sink-mcp-http`,serviceName:`log-sink-mcp-http`,timestamp:new Date().toISOString()})}catch(t){return e.json({status:`unhealthy`,error:t instanceof Error?t.message:String(t)},503)}});let i=(0,f.rateLimiter)({windowMs:60*1e3,limit:100,standardHeaders:`draft-6`,keyGenerator:e=>e.req.header(`x-forwarded-for`)||e.req.header(`x-real-ip`)||`unknown`});n.post(`/logs`,i,async e=>{try{let t=H(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let n=await U(e),i=ye.safeParse(n);if(!i.success)return e.json({success:!1,error:`Validation failed`,details:i.error.issues},400);let{logs:a}=i.data,o=G(e),s=a.map(e=>K({...e,timestamp:e.timestamp?new Date(e.timestamp):new Date},o)),c=await r.insertBatch(s);return e.json({success:!0,count:c.length,message:`Successfully inserted ${c.length} logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert logs:`,t),e.json({success:!1,error:`Failed to insert logs`},500))}}),n.post(`/v1/traces`,i,async e=>{try{let t=H(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let n=await U(e);be(n);let i=G(e),a=[];for(let e of n.resourceSpans){let t=Se(e);for(let n of e.scopeSpans)for(let e of n.spans){let n=K(Ee(e,t),i);a.push(n)}}let o=await r.insertBatch(a);return e.json({success:!0,count:o.length,message:`Successfully inserted ${o.length} trace spans as logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP traces:`,t),e.json({success:!1,error:`Failed to insert OTLP traces`},500))}}),n.post(`/v1/logs`,i,async e=>{try{let t=H(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let n=await U(e);xe(n);let i=G(e),a=[];for(let e of n.resourceLogs){let t=Ce(e);for(let n of e.scopeLogs)for(let e of n.logRecords){let n=K(Te(e,t),i);a.push(n)}}let o=await r.insertBatch(a);return e.json({success:!0,count:o.length,message:`Successfully inserted ${o.length} OTLP logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP logs:`,t),e.json({success:!1,error:`Failed to insert OTLP logs`},500))}});let a=ae(e);n.route(`/api`,a);let o=_e(e);return n.route(`/`,o),n}function q(e,t){if(e===void 0||e===``)return t;let n=Number.parseInt(e,10);if(!Number.isFinite(n)||n<=0)throw Error(`Invalid database retention value: ${e}`);return n}function J(e={}){let t=q(e.dbMaxBytes??process.env.LOG_SINK_DB_MAX_BYTES,268435456),n=q(e.dbTargetBytes??process.env.LOG_SINK_DB_TARGET_BYTES,Math.floor(t*.9));return{maxBytes:t,targetBytes:Math.min(n,t),intervalMs:q(e.dbRetentionIntervalMs??process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS,3e5),batchSize:q(e.dbRetentionBatchSize??process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE,1e3)}}const Oe=String(s.DEFAULT_PORT_RANGE.min),ke=t.m(),Ae=new l.Command(`http-serve`).description(`Start HTTP server for log ingestion with configurable port, database path, and in-memory mode`).option(`-p, --port <port>`,`Port to listen on`,Oe).option(`--db-path <path>`,`Path to SQLite database file`,ke).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).action(async e=>{let n;try{let r=Number.parseInt(e.port,10),i={min:s.DEFAULT_PORT_RANGE.min,max:Math.max(s.DEFAULT_PORT_RANGE.max??4999,r)},a=process.env.NODE_ENV||`development`,o=t.h(process.cwd()),l=`log-sink-mcp-http`;e.registryPath&&(process.env.PORT_REGISTRY_PATH=e.registryPath,process.env.PROCESS_REGISTRY_PATH=(0,c.resolveSiblingRegistryPath)(e.registryPath,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let d=new s.PortRegistryService(process.env.PORT_REGISTRY_PATH),f=t.f(),p=f.get(t.g.LogStorageService),m=f.get(t.g.LogRetentionService);await p.initializeDatabase(e.dbPath,e.inMemory);let h=m.startSizeRetentionMonitor(J({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})),g=De(f),_=await d.reservePort({repositoryPath:o,serviceName:l,serviceType:`tool`,environment:a,preferredPort:r,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:i,metadata:{healthCheckUrl:`http://localhost:${r}/health`,dbPath:e.dbPath}});if(!_.success||!_.record)throw Error(_.error||`Failed to reserve service in global registry`);let v=_.record.port;if(v!==r){let t=await d.reservePort({repositoryPath:o,serviceName:l,serviceType:`tool`,environment:a,preferredPort:v,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:i,metadata:{healthCheckUrl:`http://localhost:${v}/health`,dbPath:e.dbPath}});if(!t.success||!t.record)throw Error(t.error||`Failed to update service metadata in registry`);_=t}if(!_.record)throw Error(`Port reservation lost before startup`);let y=_.record.port;n=await(0,c.createProcessLease)({repositoryPath:o,serviceName:l,serviceType:`tool`,environment:a,pid:process.pid,port:y,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{dbPath:e.dbPath,transport:`http`}});let b;try{b=(0,u.serve)({fetch:g.fetch,port:y})}catch(e){try{await n.release({kill:!1})}catch{}throw e}let x=async()=>{try{b.close();try{await n?.release({kill:!1})}catch{}h(),await p.disconnect(),process.exit(0)}catch{process.exit(1)}};process.on(`SIGINT`,()=>x()),process.on(`SIGTERM`,()=>x())}catch{try{await n?.release({kill:!1})}catch{}process.exit(1)}}),je=t.m(),Y=new Set(Object.values(t.a)),Me=new Set([`level`,`service`,`both`]),Ne={stdout:e=>console.log(e),stderr:e=>console.error(e)};function X(e){let t=Number.parseInt(e,10);if(!Number.isInteger(t)||t<=0)throw new l.InvalidArgumentError(`Expected a positive integer.`);return t}function Pe(e){if(!Me.has(e))throw new l.InvalidArgumentError(`Expected one of: 'level', 'service', 'both'.`);return e}function Z(e,t=[]){if(!Y.has(e))throw new l.InvalidArgumentError(`Invalid log level '${e}'. Expected one of: ${Array.from(Y).join(`, `)}`);return[...t,e]}function Fe(e){return e.content.map(e=>e.type===`text`?e.text:JSON.stringify(e)).join(`
586
- `)}function Q(e){return e.option(`--db-path <path>`,`Path to SQLite database file`,je).option(`--in-memory`,`Use in-memory database`,!1)}async function Ie(e,n){let r=t.f(),i=r.get(t.g.LogStorageService);await i.initializeDatabase(e.dbPath,e.inMemory);try{return await n(r)}finally{await i.disconnect()}}async function Le(e,t,n,r=Ne){try{let i=await Ie(e,async e=>t(e).execute(n)),a=Fe(i);return i.isError?(r.stderr(a),1):(r.stdout(a),0)}catch(e){return r.stderr(`Error executing logs command: ${e instanceof Error?e.message:String(e)}`),1}}async function $(e,t,n){let r=await Le(e,t,n);r!==0&&process.exit(r)}function Re(e,t){if(!(!e&&!t)){if(!e||!t)throw new l.InvalidArgumentError(`Provide both --start-time and --end-time together.`);return{start:e,end:t}}}function ze(){let e=new l.Command(`logs`).description(`Run log analysis commands that mirror MCP tool capabilities`),n=Q(new l.Command(`query`).alias(`query-logs`).description(`Query and filter log entries by level, trace ID, service, or time range`).option(`--level <level...>`,`Log level(s) to filter by`,Z).option(`--trace-id <traceId>`,`Trace ID to filter by`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--session-id <sessionId>`,`Agent session id to filter by`).option(`--workflow-run-id <workflowRunId>`,`Workflow run id to filter by`).option(`--workflow-name <workflowName>`,`Workflow name to filter by (all runs of a workflow type)`).option(`--job-name <jobName>`,`Workflow job name to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of log entries to return`,X).action(async e=>{await $(e,e=>new t.i(e),{level:e.level,traceId:e.traceId,service:e.service,sessionId:e.sessionId,workflowRunId:e.workflowRunId,workflowName:e.workflowName,jobName:e.jobName,startTime:e.startTime,endTime:e.endTime,limit:e.limit})})),r=Q(new l.Command(`search`).alias(`search-logs`).description(`Search log entries with full-text search and optional filters`).argument(`<search-query>`,`FTS5 search query`).option(`--mode <mode>`,`Search strategy: fts, semantic, or hybrid`,`hybrid`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--level <level...>`,`Log level(s) to filter by`,Z).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of search results to return`,X).action(async(e,n)=>{await $(n,e=>new t.r(e),{searchQuery:e,mode:n.mode,service:n.service,level:n.level,startTime:n.startTime,endTime:n.endTime,limit:n.limit})})),i=Q(new l.Command(`trace`).alias(`get-trace-timeline`).description(`Get the complete trace timeline for a trace ID`).argument(`<trace-id>`,`Trace ID to inspect`).action(async(e,n)=>{await $(n,e=>new t.o(e),{traceId:e})})),a=Q(new l.Command(`analyze-errors`).alias(`errors`).description(`Analyze error patterns and group similar errors`).option(`--trace-id <traceId>`,`Optional trace ID to scope the analysis`).option(`--start-time <iso8601>`,`Start time for error analysis (ISO 8601)`).option(`--end-time <iso8601>`,`End time for error analysis (ISO 8601)`).option(`--limit <number>`,`Maximum number of error entries to analyze`,X).action(async function(e){let n;try{n=Re(e.startTime,e.endTime)}catch(e){this.error(e instanceof Error?e.message:String(e))}await $(e,e=>new t.u(e),{traceId:e.traceId,timeRange:n,limit:e.limit})})),o=Q(new l.Command(`agent-issues`).alias(`agent-problems`).description(`Detect and aggregate agent-run problems (tool failures, API errors/refusals, retries, rejections)`).option(`--session-id <sessionId>`,`Agent session id to scope by`).option(`--workflow-run-id <workflowRunId>`,`Workflow run id to scope by`).option(`--workflow-name <workflowName>`,`Workflow name to scope by`).option(`--job-name <jobName>`,`Workflow job name to scope by`).option(`--service <service...>`,`Agent/service name(s) to scope by`).option(`--start-time <iso8601>`,`Start time for the analysis window (ISO 8601)`).option(`--end-time <iso8601>`,`End time for the analysis window (ISO 8601)`).option(`--limit <number>`,`Maximum number of issue samples to return`,X).action(async e=>{await $(e,e=>new t.d(e),{sessionId:e.sessionId,workflowRunId:e.workflowRunId,workflowName:e.workflowName,jobName:e.jobName,service:e.service,startTime:e.startTime,endTime:e.endTime,limit:e.limit})})),s=Q(new l.Command(`stats`).alias(`get-log-stats`).description(`Get aggregated log statistics grouped by level, service, or both`).option(`--start-time <iso8601>`,`Start time for statistics (ISO 8601)`).option(`--end-time <iso8601>`,`End time for statistics (ISO 8601)`).option(`--group-by <groupBy>`,`Group statistics by level, service, or both`,Pe).action(async e=>{await $(e,e=>new t.c(e),{startTime:e.startTime,endTime:e.endTime,groupBy:e.groupBy})})),c=Q(new l.Command(`services`).alias(`get-services`).description(`Get the list of unique services present in the log database`).action(async e=>{await $(e,e=>new t.s(e),{})})),u=Q(new l.Command(`clear`).alias(`clear-logs`).description(`Clear all logs from the database`).action(async e=>{await $(e,e=>new t.l(e),{})}));return e.addCommand(n).addCommand(r).addCommand(i).addCommand(a).addCommand(o).addCommand(s).addCommand(c).addCommand(u)}const Be=ze();function Ve(){return t.m()}const He=new l.Command(`mcp-serve`).description(`Start MCP server with stdio transport`).option(`--cleanup`,`Stop HTTP server on MCP server shutdown`,!1).option(`--db-path <path>`,`Path to SQLite database file`,Ve()).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let n;try{let r=e.registryPath||e.registryDir;r&&(process.env.PORT_REGISTRY_PATH=r,process.env.PROCESS_REGISTRY_PATH=(0,c.resolveSiblingRegistryPath)(r,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let i=t.f(),a=i.get(t.g.LogStorageService),o=i.get(t.g.LogRetentionService),s=i.get(t.g.HttpServerManager);await a.initializeDatabase(e.dbPath,e.inMemory);let l=o.startSizeRetentionMonitor(J({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})),u=new t.t(t.n(i));n=await(0,c.createProcessLease)({repositoryPath:process.cwd(),serviceName:`log-sink-mcp-mcp-serve`,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`mcp-serve`}});let d=s.ensureRunning(void 0,e.dbPath),f;d.then(t=>{t.running&&(f=s.startHealthMonitor(e.dbPath))}).catch(()=>void 0);let p=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`);try{f?.(),await u.stop(),e.cleanup&&(await d.catch(()=>({running:!1}))).running&&(await s.stop(),console.error(`HTTP server stopped`)),l(),await a.disconnect(),await n?.release(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>p(`SIGINT`)),process.on(`SIGTERM`,()=>p(`SIGTERM`)),await u.start()}catch(e){await n?.release().catch(()=>void 0),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),Ue=new l.Command(`start`).description(`Start HTTP and/or MCP servers with singleton coordination`).option(`--mcp-only`,`Start only the MCP server (stdio transport)`,!1).option(`--http-only`,`Start only the HTTP server`,!1).option(`-p, --port <port>`,`Port for HTTP server`,String(s.DEFAULT_PORT_RANGE.min)).option(`--db-path <path>`,`Path to SQLite database file`,t.m()).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let n;try{let r=e.registryPath||e.registryDir;r&&(process.env.PORT_REGISTRY_PATH=r,process.env.PROCESS_REGISTRY_PATH=(0,c.resolveSiblingRegistryPath)(r,`processes.json`)??process.env.PROCESS_REGISTRY_PATH),e.dbMaxBytes&&(process.env.LOG_SINK_DB_MAX_BYTES=e.dbMaxBytes),e.dbTargetBytes&&(process.env.LOG_SINK_DB_TARGET_BYTES=e.dbTargetBytes),e.dbRetentionIntervalMs&&(process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS=e.dbRetentionIntervalMs),e.dbRetentionBatchSize&&(process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE=e.dbRetentionBatchSize);let i=t.f(),a=i.get(t.g.LogStorageService);await a.initializeDatabase(e.dbPath,e.inMemory);let o=Number.parseInt(e.port,10),s=!e.mcpOnly,l=!e.httpOnly,u=i.get(t.g.LogRetentionService),d=l?u.startSizeRetentionMonitor(J({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})):()=>void 0;if(console.log(`🚀 Starting log-sink-mcp services...`),console.log(` Database: ${e.inMemory?`In-Memory`:e.dbPath}`),s){let n=await i.get(t.g.HttpServerManager).ensureRunning(o,e.dbPath);n.running?(console.log(`✓ HTTP Server: Running on http://localhost:${n.port} (PID: ${n.pid})`),console.log(` Health check: http://localhost:${n.port}/health`),console.log(` Log ingestion: POST http://localhost:${n.port}/logs`)):(console.error(`✗ HTTP Server failed to start: ${n.error}`),l||process.exit(1))}if(l){n=await(0,c.createProcessLease)({repositoryPath:process.cwd(),serviceName:`log-sink-mcp-start`,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`start`}}),console.log(`✓ MCP Server: Starting with stdio transport...`);let e=new t.t(t.n(i));await e.start(),console.log(`✓ MCP Server: Ready for connections`);let r=async r=>{console.log(`\n\n${r} received. Shutting down gracefully...`);try{await e.stop(),console.log(`✓ MCP server stopped`),s&&(await i.get(t.g.HttpServerManager).stop(),console.log(`✓ HTTP server stopped`)),d(),await a.disconnect(),console.log(`✓ Database disconnected`),await n?.release(),console.log(`✓ Process registry entry released`),console.log(`Goodbye! 👋`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>r(`SIGINT`)),process.on(`SIGTERM`,()=>r(`SIGTERM`)),console.log(`
586
+ `)})]}),(0,h.jsx)(`body`,{children:t})]})}function xe(e){let n=new d.Hono,r=e.get(t.u.LogQueryService);return n.get(`/`,async e=>{try{let t=await r.getActiveServices(),n=await r.getStatistics(),i=await r.filterByLevel([`info`,`warn`,`error`,`fatal`],100),a={totalLogs:n.reduce((e,t)=>e+t.count,0),errors:n.filter(e=>e.level===`error`||e.level===`fatal`).reduce((e,t)=>e+t.count,0),services:new Set(n.map(e=>e.service)).size};return e.html((0,h.jsx)(F,{title:`Log Dashboard`,children:(0,h.jsx)(be,{initialLogs:i,services:t,stats:a})}))}catch(t){return console.error(`Failed to render dashboard:`,t),e.html((0,h.jsx)(F,{title:`Log Dashboard - Error`,children:(0,h.jsxs)(`div`,{style:`padding: 2rem; text-align: center;`,children:[(0,h.jsx)(`h1`,{children:`Failed to load dashboard`}),(0,h.jsx)(`p`,{children:t instanceof Error?t.message:String(t)}),(0,h.jsx)(`a`,{href:`/`,style:`color: #2196f3; text-decoration: underline;`,children:`Retry`})]})}),500)}}),n}const I=5*1024*1024,L=2e3,R=8e3,Se=n.z.object({timestamp:n.z.string().datetime().optional(),level:n.z.enum([`trace`,`debug`,`info`,`warn`,`error`,`fatal`]),message:n.z.string().min(1).max(R),traceId:n.z.string().regex(/^[0-9a-f]{32}$/i,`traceId must be 32 hex characters`).optional(),spanId:n.z.string().regex(/^[0-9a-f]{16}$/i,`spanId must be 16 hex characters`).optional(),parentSpanId:n.z.string().regex(/^[0-9a-f]{16}$/i,`parentSpanId must be 16 hex characters`).optional(),service:n.z.string().min(1).max(200),hostname:n.z.string().max(255).optional(),pid:n.z.number().int().nonnegative().optional(),metadata:n.z.record(n.z.string(),n.z.unknown()).optional(),errorType:n.z.string().max(255).optional(),errorMessage:n.z.string().max(8e3).optional(),errorStack:n.z.string().max(32e3).optional()}),Ce=n.z.object({logs:n.z.array(Se).min(1).max(1e3)});function z(e){if(!e||!/^\d+$/.test(e))throw Error(`OTLP timestamp must be a numeric unix-nanoseconds string`);let t=Number(BigInt(e)/BigInt(1e6));return new Date(t)}function B(e,t){return typeof e==`string`&&RegExp(`^[0-9a-f]{${t}}$`,`i`).test(e)}function V(e){if(!e)return null;let t=Number(e);return!Number.isFinite(t)||t<=0?null:t>I?`Request body exceeds ${I} bytes`:null}async function H(e){try{return await e.req.json()}catch(e){throw Error(`Invalid JSON body: ${e instanceof Error?e.message:String(e)}`)}}function we(e){if(!Array.isArray(e.resourceSpans)||e.resourceSpans.length===0)throw Error(`Invalid OTLP trace request: missing resourceSpans`);let t=0;for(let n of e.resourceSpans){if(!Array.isArray(n.scopeSpans))throw Error(`Invalid OTLP trace request: scopeSpans must be an array`);for(let e of n.scopeSpans){if(!Array.isArray(e.spans))throw Error(`Invalid OTLP trace request: spans must be an array`);for(let n of e.spans){if(t+=1,t>L)throw Error(`OTLP trace request exceeds ${L} spans`);if(!B(n.traceId,32))throw Error(`Invalid OTLP trace request: traceId must be 32 hex characters`);if(!B(n.spanId,16))throw Error(`Invalid OTLP trace request: spanId must be 16 hex characters`);if(n.parentSpanId&&!B(n.parentSpanId,16))throw Error(`Invalid OTLP trace request: parentSpanId must be 16 hex characters`);if(!n.name||n.name.length>R)throw Error(`Invalid OTLP trace request: span name is missing or too long`);z(n.startTimeUnixNano)}}}}function Te(e){if(!Array.isArray(e.resourceLogs)||e.resourceLogs.length===0)throw Error(`Invalid OTLP logs request: missing resourceLogs`);let t=0;for(let n of e.resourceLogs){if(!Array.isArray(n.scopeLogs))throw Error(`Invalid OTLP logs request: scopeLogs must be an array`);for(let e of n.scopeLogs){if(!Array.isArray(e.logRecords))throw Error(`Invalid OTLP logs request: logRecords must be an array`);for(let n of e.logRecords){if(t+=1,t>L)throw Error(`OTLP logs request exceeds ${L} log records`);if(n.traceId&&!B(n.traceId,32))throw Error(`Invalid OTLP logs request: traceId must be 32 hex characters`);if(n.spanId&&!B(n.spanId,16))throw Error(`Invalid OTLP logs request: spanId must be 16 hex characters`);if(n.severityText&&n.severityText.length>32)throw Error(`Invalid OTLP logs request: severityText is too long`);z(n.timeUnixNano)}}}}function Ee(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function U(e,t){let n=e?.find(e=>e.key===t);if(!n)return;let{value:r}=n;if(r.stringValue!==void 0)return r.stringValue;if(r.intValue!==void 0)return r.intValue;if(r.doubleValue!==void 0)return String(r.doubleValue);if(r.boolValue!==void 0)return String(r.boolValue)}function De(e){return(e.resource?.attributes?.find(e=>e.key===`service.name`))?.value?.stringValue||`unknown-service`}function Oe(e){return e===void 0?`info`:e<=4?`trace`:e<=8?`debug`:e<=12?`info`:e<=16?`warn`:e<=20?`error`:`fatal`}function W(e){let t=t=>{let n=e.req.header(t);return n&&n.length>0?n.slice(0,200):null};return{sessionId:t(`x-agent-session-id`),workflowRunId:t(`x-workflow-run-id`),workflowName:t(`x-workflow-name`),jobName:t(`x-workflow-job-name`),jobId:t(`x-workflow-job-id`),agent:t(`x-agent`)}}function G(e,t){return{...e,service:e.service===`unknown-service`&&t.agent?t.agent:e.service,sessionId:t.sessionId,workflowRunId:t.workflowRunId,workflowName:t.workflowName,jobName:t.jobName,jobId:t.jobId}}function ke(e,t){let n=z(e.timeUnixNano),r=e.severityText?.toLowerCase()||Oe(e.severityNumber),i=``;if(e.body?.stringValue)i=e.body.stringValue;else if(e.body?.kvlistValue){let t={};for(let n of e.body.kvlistValue.values){let e=n.value.stringValue??n.value.intValue??n.value.doubleValue??n.value.boolValue;t[n.key]=e}i=JSON.stringify(t)}let a={},o=null,s=null,c=null;if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;t.key===`exception.type`?o=String(e):t.key===`exception.message`?s=String(e):t.key===`exception.stacktrace`?c=String(e):a[t.key]=e}return{timestamp:n,level:r,message:i||`[LOG] ${t}`,traceId:e.traceId??null,spanId:e.spanId??null,parentSpanId:null,service:t,hostname:null,pid:null,metadata:Object.keys(a).length>0?a:null,errorType:o,errorMessage:s,errorStack:c}}function Ae(e,t){let n=z(e.startTimeUnixNano),r=e.status?.code,i=`info`;r===2?i=`error`:r===1&&(i=`info`);let a=U(e.attributes,`exception.type`),o=U(e.attributes,`exception.message`),s=U(e.attributes,`exception.stacktrace`),c={};if(e.attributes)for(let t of e.attributes){let e=t.value.stringValue??t.value.intValue??t.value.doubleValue??t.value.boolValue;c[t.key]=e}return c.spanName=e.name,c.spanKind=e.kind,e.status?.message&&(c.statusMessage=e.status.message),{timestamp:n,level:i,message:`[SPAN] ${e.name}`,traceId:e.traceId,spanId:e.spanId,parentSpanId:e.parentSpanId??null,service:t,hostname:null,pid:null,metadata:c,errorType:a??null,errorMessage:o??null,errorStack:s??null}}function je(e){let n=new d.Hono,r=e.get(t.u.LogStorageService);n.use(`*`,(0,p.cors)({origin:e=>e??null,credentials:!0,allowMethods:[`GET`,`POST`,`OPTIONS`],allowHeaders:[`Content-Type`,`Accept`,`Authorization`]})),n.get(`/health`,async e=>{try{return e.json({status:`healthy`,service:`log-sink-mcp-http`,serviceName:`log-sink-mcp-http`,timestamp:new Date().toISOString()})}catch(t){return e.json({status:`unhealthy`,error:t instanceof Error?t.message:String(t)},503)}});let i=(0,f.rateLimiter)({windowMs:60*1e3,limit:100,standardHeaders:`draft-6`,keyGenerator:e=>e.req.header(`x-forwarded-for`)||e.req.header(`x-real-ip`)||`unknown`});n.post(`/logs`,i,async e=>{try{let t=V(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let n=await H(e),i=Ce.safeParse(n);if(!i.success)return e.json({success:!1,error:`Validation failed`,details:i.error.issues},400);let{logs:a}=i.data,o=W(e),s=a.map(e=>G({...e,timestamp:e.timestamp?new Date(e.timestamp):new Date},o)),c=await r.insertBatch(s);return e.json({success:!0,count:c.length,message:`Successfully inserted ${c.length} logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert logs:`,t),e.json({success:!1,error:`Failed to insert logs`},500))}}),n.post(`/v1/traces`,i,async e=>{try{let t=V(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let n=await H(e);we(n);let i=W(e),a=[];for(let e of n.resourceSpans){let t=Ee(e);for(let n of e.scopeSpans)for(let e of n.spans){let n=G(Ae(e,t),i);a.push(n)}}let o=await r.insertBatch(a);return e.json({success:!0,count:o.length,message:`Successfully inserted ${o.length} trace spans as logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP traces:`,t),e.json({success:!1,error:`Failed to insert OTLP traces`},500))}}),n.post(`/v1/logs`,i,async e=>{try{let t=V(e.req.header(`content-length`));if(t)return e.json({success:!1,error:t},413);let n=await H(e);Te(n);let i=W(e),a=[];for(let e of n.resourceLogs){let t=De(e);for(let n of e.scopeLogs)for(let e of n.logRecords){let n=G(ke(e,t),i);a.push(n)}}let o=await r.insertBatch(a);return e.json({success:!0,count:o.length,message:`Successfully inserted ${o.length} OTLP logs`},201)}catch(t){let n=t instanceof Error?t.message:String(t);return n.startsWith(`Invalid JSON body`)||n.startsWith(`Invalid OTLP`)||n.startsWith(`OTLP`)?e.json({success:!1,error:n},400):(console.error(`Failed to insert OTLP logs:`,t),e.json({success:!1,error:`Failed to insert OTLP logs`},500))}});let a=se(e);n.route(`/api`,a);let o=xe(e);return n.route(`/`,o),n}function K(e,t){if(e===void 0||e===``)return t;let n=Number.parseInt(e,10);if(!Number.isFinite(n)||n<=0)throw Error(`Invalid database retention value: ${e}`);return n}function q(e={}){let t=K(e.dbMaxBytes??process.env.LOG_SINK_DB_MAX_BYTES,268435456),n=K(e.dbTargetBytes??process.env.LOG_SINK_DB_TARGET_BYTES,Math.floor(t*.9));return{maxBytes:t,targetBytes:Math.min(n,t),intervalMs:K(e.dbRetentionIntervalMs??process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS,3e5),batchSize:K(e.dbRetentionBatchSize??process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE,1e3)}}const Me=g(new l.Command(`http-serve`)).description(`Start HTTP server for log ingestion with configurable port, database path, and in-memory mode`).option(`-p, --port <port>`,`Port to listen on`).option(`--db-path <path>`,`Path to SQLite database file`).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).action(async e=>{let n;try{let r=v(e),i=e.dbPath??r.dbPath,a=e.port?Number.parseInt(e.port,10):r.port,o={min:s.DEFAULT_PORT_RANGE.min,max:a===void 0?s.DEFAULT_PORT_RANGE.max??4999:Math.max(s.DEFAULT_PORT_RANGE.max??4999,a)},l=process.env.NODE_ENV||`development`,d=r.repositoryPath,f=`log-sink-mcp-http`;e.registryPath&&(process.env.PORT_REGISTRY_PATH=e.registryPath,process.env.PROCESS_REGISTRY_PATH=(0,c.resolveSiblingRegistryPath)(e.registryPath,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let p=new s.PortRegistryService(process.env.PORT_REGISTRY_PATH),m=t.c(),h=m.get(t.u.LogStorageService),g=m.get(t.u.LogRetentionService);await h.initializeDatabase(i,e.inMemory);let _=g.startSizeRetentionMonitor(q({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})),ee=je(m),y=await p.reservePort({repositoryPath:d,serviceName:f,serviceType:`tool`,environment:l,...a===void 0?{}:{preferredPort:a},pid:process.pid,host:`127.0.0.1`,force:!0,portRange:o,metadata:{dbPath:i,scope:r.scope}});if(!y.success||!y.record)throw Error(y.error||`Failed to reserve service in global registry`);let b=y.record.port;if(b!==a||!y.record.metadata?.healthCheckUrl){let e=await p.reservePort({repositoryPath:d,serviceName:f,serviceType:`tool`,environment:l,preferredPort:b,pid:process.pid,host:`127.0.0.1`,force:!0,portRange:o,metadata:{healthCheckUrl:`http://localhost:${b}/health`,dbPath:i,scope:r.scope}});if(!e.success||!e.record)throw Error(e.error||`Failed to update service metadata in registry`);y=e}if(!y.record)throw Error(`Port reservation lost before startup`);let x=y.record.port;n=await(0,c.createProcessLease)({repositoryPath:d,serviceName:f,serviceType:`tool`,environment:l,pid:process.pid,port:x,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{dbPath:i,scope:r.scope,transport:`http`}});let S;try{S=(0,u.serve)({fetch:ee.fetch,port:x})}catch(e){try{await n.release({kill:!1})}catch{}throw e}let C=async()=>{try{S.close();try{await n?.release({kill:!1})}catch{}_(),await h.disconnect(),process.exit(0)}catch{process.exit(1)}};process.on(`SIGINT`,()=>C()),process.on(`SIGTERM`,()=>C())}catch{try{await n?.release({kill:!1})}catch{}process.exit(1)}}),J=n.z.object({sessionId:n.z.string().optional().describe(`Agent session id to scope the analysis to one agent launch`),workflowRunId:n.z.string().optional().describe(`Workflow run id to scope to all jobs of one workflow run`),workflowName:n.z.string().optional().describe(`Workflow name to scope to all runs of a workflow type`),jobName:n.z.string().optional().describe(`Workflow job name to scope to a single job`),service:n.z.union([n.z.string(),n.z.array(n.z.string())]).optional().describe(`Agent/service name(s) to scope by (e.g. claude-code, codex)`),startTime:n.z.string().optional().describe(`Start time for the analysis window (ISO 8601 format)`),endTime:n.z.string().optional().describe(`End time for the analysis window (ISO 8601 format)`),limit:n.z.number().min(1).max(1e3).optional().default(100).describe(`Maximum number of issue samples to return`)});var Ne=class e{static TOOL_NAME=`analyze_agent_issues`;queryService;constructor(e){this.queryService=e.get(t.u.LogQueryService)}getInputSchema(){return J}getDefinition(){return{name:e.TOOL_NAME,description:`Detect and aggregate agent-run problems (failed tool calls, API errors, refusals, retries exhausted, rejected tools, error logs) for reviewing agent performance and improving the repo harness. Filter by session id, workflow run id, workflow name, job name, or agent/service. Returns counts by category/tool/error-type plus sample issues with the tool, input, and error context needed to diagnose them.`,inputSchema:n.z.toJSONSchema(J,{reused:`inline`})}}async execute(e){try{let t=J.parse(e),n=await this.queryService.analyzeAgentIssues({sessionId:t.sessionId,workflowRunId:t.workflowRunId,workflowName:t.workflowName,jobName:t.jobName,service:t.service,startTime:t.startTime?new Date(t.startTime):void 0,endTime:t.endTime?new Date(t.endTime):void 0},{sampleLimit:t.limit});return{content:[{type:`text`,text:JSON.stringify({...n,issues:n.issues.map(e=>({...e,timestamp:e.timestamp.toISOString()}))},null,2)}]}}catch(e){return{content:[{type:`text`,text:`Error analyzing agent issues: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}};const Y=n.z.object({timeRange:n.z.object({start:n.z.string().describe(`Start time for error analysis (ISO 8601 format)`),end:n.z.string().describe(`End time for error analysis (ISO 8601 format)`)}).optional().describe(`Optional time range to filter errors`),traceId:n.z.string().optional().describe(`Optional trace ID to analyze errors within a specific trace`),limit:n.z.number().min(1).max(1e3).optional().default(100).describe(`Maximum number of error entries to analyze`)});var Pe=class e{static TOOL_NAME=`analyze_errors`;queryService;constructor(e){this.queryService=e.get(t.u.LogQueryService)}getInputSchema(){return Y}getDefinition(){return{name:e.TOOL_NAME,description:`Analyze error patterns and group similar errors by type and message. Calculates error frequencies, suggests root causes, and provides debugging insights.`,inputSchema:n.z.toJSONSchema(Y,{reused:`inline`})}}async execute(e){try{let t=Y.parse(e),n=t.limit,r;if(t.traceId)r=(await this.queryService.filterByTrace(t.traceId)).filter(e=>e.level===`error`||e.level===`fatal`).slice(0,n);else if(t.timeRange){let e=new Date(t.timeRange.start),i=new Date(t.timeRange.end);r=(await this.queryService.filterByTimeRange(e,i)).filter(e=>e.level===`error`||e.level===`fatal`).slice(0,n)}else r=await this.queryService.filterByLevel([`error`,`fatal`],n);let i=new Map;for(let e of r){let t=`${e.errorType}::${e.errorMessage}`;i.has(t)||i.set(t,{errorType:e.errorType,errorMessage:e.errorMessage,frequency:0,services:new Set,examples:[]});let n=i.get(t);n.frequency+=1,n.services.add(e.service),n.examples.length<3&&n.examples.push({id:e.id,timestamp:e.timestamp.toISOString(),service:e.service})}let a=Array.from(i.values()).sort((e,t)=>t.frequency-e.frequency).map(e=>({errorType:e.errorType,errorMessage:e.errorMessage,frequency:e.frequency,affectedServices:Array.from(e.services),examples:e.examples}));return{content:[{type:`text`,text:JSON.stringify({totalErrorsAnalyzed:r.length,uniqueErrorPatterns:a.length,analysis:a},null,2)}]}}catch(e){return{content:[{type:`text`,text:`Error analyzing logs: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}};const Fe=n.z.object({});var Ie=class e{static TOOL_NAME=`clear_logs`;retentionService;constructor(e){this.retentionService=e.get(t.u.LogRetentionService)}getInputSchema(){return Fe}getDefinition(){return{name:e.TOOL_NAME,description:`Clear all stored log entries from the database. Useful for resetting state in development.`,inputSchema:n.z.toJSONSchema(Fe,{reused:`inline`})}}async execute(e){try{let e=await this.retentionService.clearAllLogs();return{content:[{type:`text`,text:JSON.stringify({success:!0,message:`All logs have been cleared`,deletedEntries:e},null,2)}]}}catch(e){return{content:[{type:`text`,text:`Error clearing logs: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}};const Le=new Set(Object.values(t.i)),Re=new Set([`level`,`service`,`both`]),ze={stdout:e=>console.log(e),stderr:e=>console.error(e)};function X(e){let t=Number.parseInt(e,10);if(!Number.isInteger(t)||t<=0)throw new l.InvalidArgumentError(`Expected a positive integer.`);return t}function Be(e){if(!Re.has(e))throw new l.InvalidArgumentError(`Expected one of: 'level', 'service', 'both'.`);return e}function Ve(e,t=[]){if(!Le.has(e))throw new l.InvalidArgumentError(`Invalid log level '${e}'. Expected one of: ${Array.from(Le).join(`, `)}`);return[...t,e]}function He(e){return e.content.map(e=>e.type===`text`?e.text:JSON.stringify(e)).join(`
587
+ `)}function Z(e){return g(e).option(`--db-path <path>`,`Path to SQLite database file`).option(`--in-memory`,`Use in-memory database`,!1)}async function Ue(e,n){let r=v(e),i=e.dbPath??r.dbPath,a=t.c(),o=a.get(t.u.LogStorageService);await o.initializeDatabase(i,e.inMemory);try{return await n(a)}finally{await o.disconnect()}}async function We(e,t,n,r=ze){try{let i=await Ue(e,async e=>t(e).execute(n)),a=He(i);return i.isError?(r.stderr(a),1):(r.stdout(a),0)}catch(e){return r.stderr(`Error executing logs command: ${e instanceof Error?e.message:String(e)}`),1}}async function Q(e,t,n){let r=await We(e,t,n);r!==0&&process.exit(r)}function Ge(e,t){if(!(!e&&!t)){if(!e||!t)throw new l.InvalidArgumentError(`Provide both --start-time and --end-time together.`);return{start:e,end:t}}}function Ke(){let e=new l.Command(`logs`).description(`Run log analysis commands that mirror MCP tool capabilities`),n=Z(new l.Command(`query`).alias(`query-logs`).description(`Query and filter log entries by level, trace ID, service, or time range`).option(`--level <level...>`,`Log level(s) to filter by`,Ve).option(`--trace-id <traceId>`,`Trace ID to filter by`).option(`--span-id <spanId>`,`Span ID to filter by`).option(`--error-type <errorType>`,`Error type to filter by`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--session-id <sessionId>`,`Agent session id to filter by`).option(`--workflow-run-id <workflowRunId>`,`Workflow run id to filter by`).option(`--workflow-name <workflowName>`,`Workflow name to filter by (all runs of a workflow type)`).option(`--job-name <jobName>`,`Workflow job name to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of log entries to return`,X).action(async e=>{await Q(e,e=>new t.r(e),{level:e.level,traceId:e.traceId,spanId:e.spanId,errorType:e.errorType,service:e.service,sessionId:e.sessionId,workflowRunId:e.workflowRunId,workflowName:e.workflowName,jobName:e.jobName,startTime:e.startTime,endTime:e.endTime,limit:e.limit})})),r=Z(new l.Command(`search`).alias(`search-logs`).description(`Search or filter log entries with optional full-text search`).argument(`[search-query]`,`Optional FTS5 search query`).option(`--mode <mode>`,`Search strategy: fts, semantic, or hybrid`,`hybrid`).option(`--service <service...>`,`Service name(s) to filter by`).option(`--level <level...>`,`Log level(s) to filter by`,Ve).option(`--trace-id <traceId>`,`Trace ID to filter by`).option(`--span-id <spanId>`,`Span ID to filter by`).option(`--error-type <errorType>`,`Error type to filter by`).option(`--session-id <sessionId>`,`Agent session id to filter by`).option(`--workflow-run-id <workflowRunId>`,`Workflow run id to filter by`).option(`--workflow-name <workflowName>`,`Workflow name to filter by (all runs of a workflow type)`).option(`--job-name <jobName>`,`Workflow job name to filter by`).option(`--start-time <iso8601>`,`Start time for log range (ISO 8601)`).option(`--end-time <iso8601>`,`End time for log range (ISO 8601)`).option(`--limit <number>`,`Maximum number of search results to return`,X).action(async(e,n)=>{await Q(n,e=>new t.r(e),{searchQuery:e,mode:n.mode,service:n.service,level:n.level,traceId:n.traceId,spanId:n.spanId,errorType:n.errorType,sessionId:n.sessionId,workflowRunId:n.workflowRunId,workflowName:n.workflowName,jobName:n.jobName,startTime:n.startTime,endTime:n.endTime,limit:n.limit})})),i=Z(new l.Command(`trace`).alias(`get-trace-timeline`).description(`Get the complete trace timeline for a trace ID`).argument(`<trace-id>`,`Trace ID to inspect`).action(async(e,n)=>{await Q(n,e=>new t.a(e),{traceId:e})})),a=Z(new l.Command(`analyze-errors`).alias(`errors`).description(`Analyze error patterns and group similar errors`).option(`--trace-id <traceId>`,`Optional trace ID to scope the analysis`).option(`--start-time <iso8601>`,`Start time for error analysis (ISO 8601)`).option(`--end-time <iso8601>`,`End time for error analysis (ISO 8601)`).option(`--limit <number>`,`Maximum number of error entries to analyze`,X).action(async function(e){let t;try{t=Ge(e.startTime,e.endTime)}catch(e){this.error(e instanceof Error?e.message:String(e))}await Q(e,e=>new Pe(e),{traceId:e.traceId,timeRange:t,limit:e.limit})})),o=Z(new l.Command(`agent-issues`).alias(`agent-problems`).description(`Detect and aggregate agent-run problems (tool failures, API errors/refusals, retries, rejections)`).option(`--session-id <sessionId>`,`Agent session id to scope by`).option(`--workflow-run-id <workflowRunId>`,`Workflow run id to scope by`).option(`--workflow-name <workflowName>`,`Workflow name to scope by`).option(`--job-name <jobName>`,`Workflow job name to scope by`).option(`--service <service...>`,`Agent/service name(s) to scope by`).option(`--start-time <iso8601>`,`Start time for the analysis window (ISO 8601)`).option(`--end-time <iso8601>`,`End time for the analysis window (ISO 8601)`).option(`--limit <number>`,`Maximum number of issue samples to return`,X).action(async e=>{await Q(e,e=>new Ne(e),{sessionId:e.sessionId,workflowRunId:e.workflowRunId,workflowName:e.workflowName,jobName:e.jobName,service:e.service,startTime:e.startTime,endTime:e.endTime,limit:e.limit})})),s=Z(new l.Command(`stats`).alias(`get-log-stats`).description(`Get aggregated log statistics grouped by level, service, or both`).option(`--start-time <iso8601>`,`Start time for statistics (ISO 8601)`).option(`--end-time <iso8601>`,`End time for statistics (ISO 8601)`).option(`--group-by <groupBy>`,`Group statistics by level, service, or both`,Be).action(async e=>{await Q(e,e=>new t.s(e),{startTime:e.startTime,endTime:e.endTime,groupBy:e.groupBy})})),c=Z(new l.Command(`services`).alias(`get-services`).description(`Get the list of unique services present in the log database`).action(async e=>{await Q(e,e=>new t.o(e),{})})),u=Z(new l.Command(`clear`).alias(`clear-logs`).description(`Clear all logs from the database`).action(async e=>{await Q(e,e=>new Ie(e),{})}));return e.addCommand(n).addCommand(r).addCommand(i).addCommand(a).addCommand(o).addCommand(s).addCommand(c).addCommand(u)}const qe=Ke(),Je=g(new l.Command(`mcp-serve`)).description(`Start MCP server with stdio transport`).option(`--cleanup`,`Stop HTTP server on MCP server shutdown`,!1).option(`--db-path <path>`,`Path to SQLite database file`).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let n;try{let r=e.registryPath||e.registryDir;r&&(process.env.PORT_REGISTRY_PATH=r,process.env.PROCESS_REGISTRY_PATH=(0,c.resolveSiblingRegistryPath)(r,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let i=v(e),a=e.dbPath??i.dbPath,o=t.c(),s=o.get(t.u.LogStorageService),l=o.get(t.u.LogRetentionService),u=o.get(t.u.HttpServerManager);await s.initializeDatabase(a,e.inMemory);let d=l.startSizeRetentionMonitor(q({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})),f=new t.t(t.n(o));n=await(0,c.createProcessLease)({repositoryPath:i.repositoryPath,serviceName:`log-sink-mcp-mcp-serve`,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`mcp-serve`,scope:i.scope}});let p=u.ensureRunning(void 0,a),m;p.then(e=>{e.running&&(m=u.startHealthMonitor(a))}).catch(()=>void 0);let h=async t=>{console.error(`\nReceived ${t}, shutting down gracefully...`);try{m?.(),await f.stop(),e.cleanup&&(await p.catch(()=>({running:!1}))).running&&(await u.stop(),console.error(`HTTP server stopped`)),d(),await s.disconnect(),await n?.release(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>h(`SIGINT`)),process.on(`SIGTERM`,()=>h(`SIGTERM`)),await f.start()}catch(e){await n?.release().catch(()=>void 0),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),Ye={stdout:e=>console.log(e),stderr:e=>console.error(e)};function Xe(e,t){let n={...process.env};e.config&&(n.LOG_SINK_CONFIG=e.config);let r=t??_(e);return r&&(n.LOG_SINK_INSTANCE=r),n}async function $(t,n){let r=Xe(t,n),i=e.s({configPath:t.config,env:r,packageName:e.o}),a=await e.a({configPath:t.config,env:r,packageName:e.o,healthCheck:t.healthCheck});return{scope:i.scope,registered:!!a,port:a?.port,host:a?.host,endpoint:a?.endpoint,configPath:i.configPath,dbPath:i.dbPath,repositoryPath:i.repositoryPath,registeredName:i.registeredName}}function Ze(e,t){return e.registered?`${e.scope}: ${e.endpoint} (port ${e.port})`:`${e.scope}: not registered${t===!1?``:` or unhealthy`}`}async function Qe(e,t=Ye){try{let n=e.all?await Promise.all([$(e,`local`),$(e,`global`)]):[await $(e)];return e.json?t.stdout(JSON.stringify(e.all?n:n[0],null,2)):t.stdout(n.map(t=>Ze(t,e.healthCheck)).join(`
588
+ `)),+!n.some(e=>e.registered)}catch(e){return t.stderr(`Error resolving log-sink port: ${e instanceof Error?e.message:String(e)}`),1}}const $e=g(new l.Command(`port`)).description(`Show the registered log-sink HTTP port for the selected instance`).option(`--all`,`Show both local and global log-sink ports`,!1).option(`--json`,`Print machine-readable JSON output`,!1).option(`--no-health-check`,`Read the registry without requiring a healthy HTTP server`).action(async e=>{let t=await Qe(e);t!==0&&process.exit(t)}),et=g(new l.Command(`start`)).description(`Start HTTP and/or MCP servers with singleton coordination`).option(`--mcp-only`,`Start only the MCP server (stdio transport)`,!1).option(`--http-only`,`Start only the HTTP server`,!1).option(`-p, --port <port>`,`Port for HTTP server`).option(`--db-path <path>`,`Path to SQLite database file`).option(`--in-memory`,`Use in-memory database (for testing)`,!1).option(`--db-max-bytes <bytes>`,`Maximum SQLite database size before trimming`).option(`--db-target-bytes <bytes>`,`Target SQLite database size after trimming`).option(`--db-retention-interval-ms <ms>`,`How often to check database size`,`300000`).option(`--db-retention-batch-size <count>`,`Number of oldest log rows to delete per trim batch`,`1000`).option(`--registry-path <path>`,`Custom registry path or directory for service discovery`).option(`--registry-dir <path>`,`Custom registry directory for service discovery`).action(async e=>{let n;try{let r=e.registryPath||e.registryDir;r&&(process.env.PORT_REGISTRY_PATH=r,process.env.PROCESS_REGISTRY_PATH=(0,c.resolveSiblingRegistryPath)(r,`processes.json`)??process.env.PROCESS_REGISTRY_PATH);let i=v(e),a=e.dbPath??i.dbPath;e.dbMaxBytes&&(process.env.LOG_SINK_DB_MAX_BYTES=e.dbMaxBytes),e.dbTargetBytes&&(process.env.LOG_SINK_DB_TARGET_BYTES=e.dbTargetBytes),e.dbRetentionIntervalMs&&(process.env.LOG_SINK_DB_RETENTION_INTERVAL_MS=e.dbRetentionIntervalMs),e.dbRetentionBatchSize&&(process.env.LOG_SINK_DB_RETENTION_BATCH_SIZE=e.dbRetentionBatchSize);let o=t.c(),s=o.get(t.u.LogStorageService);await s.initializeDatabase(a,e.inMemory);let l=e.port?Number.parseInt(e.port,10):i.port,u=!e.mcpOnly,d=!e.httpOnly,f=o.get(t.u.LogRetentionService),p=d?f.startSizeRetentionMonitor(q({dbMaxBytes:e.dbMaxBytes,dbTargetBytes:e.dbTargetBytes,dbRetentionIntervalMs:e.dbRetentionIntervalMs,dbRetentionBatchSize:e.dbRetentionBatchSize})):()=>void 0;if(console.log(`🚀 Starting log-sink-mcp services...`),console.log(` Instance: ${i.scope}`),console.log(` Database: ${e.inMemory?`In-Memory`:a}`),u){let e=await o.get(t.u.HttpServerManager).ensureRunning(l,a);e.running?(console.log(`✓ HTTP Server: Running on http://localhost:${e.port} (PID: ${e.pid})`),console.log(` Health check: http://localhost:${e.port}/health`),console.log(` Log ingestion: POST http://localhost:${e.port}/logs`)):(console.error(`✗ HTTP Server failed to start: ${e.error}`),d||process.exit(1))}if(d){n=await(0,c.createProcessLease)({repositoryPath:i.repositoryPath,serviceName:`log-sink-mcp-start`,serviceType:`tool`,environment:process.env.NODE_ENV||`development`,pid:process.pid,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2),metadata:{transport:`stdio`,command:`start`,scope:i.scope}}),console.log(`✓ MCP Server: Starting with stdio transport...`);let e=new t.t(t.n(o));await e.start(),console.log(`✓ MCP Server: Ready for connections`);let r=async r=>{console.log(`\n\n${r} received. Shutting down gracefully...`);try{await e.stop(),console.log(`✓ MCP server stopped`),u&&(await o.get(t.u.HttpServerManager).stop(),console.log(`✓ HTTP server stopped`)),p(),await s.disconnect(),console.log(`✓ Database disconnected`),await n?.release(),console.log(`✓ Process registry entry released`),console.log(`Goodbye! 👋`),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>r(`SIGINT`)),process.on(`SIGTERM`,()=>r(`SIGTERM`)),console.log(`
587
589
  Press Ctrl+C to stop the server`)}else console.log(`
588
- ✓ HTTP server started in background`),console.log(`Use "log-sink-mcp stop" to stop the HTTP server`),n&&await n.release().catch(()=>void 0),process.exit(0)}catch(e){n&&await n.release().catch(()=>void 0),console.error(`Error starting services:`,e),process.exit(1)}}),We=new l.Command(`status`).description(`Show status of HTTP server and diagnostics`).action(async()=>{try{console.log(`📊 log-sink-mcp Status
589
- `),console.log(`─`.repeat(50));let e=await t.f().get(t.g.HttpServerManager).getStatus();e.running?(console.log(`HTTP Server: 🟢 Running`),console.log(` PID: ${e.pid}`),console.log(` Port: ${e.port}`),console.log(` Health: http://localhost:${e.port}/health`)):(console.log(`HTTP Server: 🔴 Not Running`),e.error&&console.log(` Error: ${e.error}`)),console.log(``);try{let e=t.m(),n=((await i.stat(e)).size/1024/1024).toFixed(2);console.log(`Database: ${e} (${n} MB)`)}catch{console.log(`Database: Not found or not accessible`)}console.log(`─`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),Ge=new l.Command(`stop`).description(`Stop HTTP server and clean up registry/PID files`).action(async()=>{try{console.log(`🛑 Stopping log-sink-mcp services...`),await t.f().get(t.g.HttpServerManager).stop()?(console.log(`✓ HTTP server stopped`),console.log(`✓ Registry and PID files cleaned up`)):console.log(`ℹ No HTTP server was running`),console.log(`Done! 👋`),process.exit(0)}catch(e){console.error(`Error stopping services:`,e),process.exit(1)}}),Ke=(0,a.dirname)((0,o.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),qe=JSON.parse((0,r.readFileSync)((0,a.join)(Ke,`../package.json`),`utf-8`));async function Je(){let e=new l.Command;e.name(`log-sink-mcp`).description(`Log sink MCP server with HTTP ingestion and AI analysis`).version(qe.version),e.addCommand(re),e.addCommand(Ue),e.addCommand(Ge),e.addCommand(We),e.addCommand(Ae),e.addCommand(He),e.addCommand(Be),await e.parseAsync(process.argv)}Je();
590
+ ✓ HTTP server started in background`),console.log(`Use "log-sink-mcp stop" to stop the HTTP server`),n&&await n.release().catch(()=>void 0),process.exit(0)}catch(e){n&&await n.release().catch(()=>void 0),console.error(`Error starting services:`,e),process.exit(1)}}),tt=g(new l.Command(`status`)).description(`Show status of HTTP server and diagnostics`).action(async e=>{try{console.log(`📊 log-sink-mcp Status
591
+ `),console.log(`─`.repeat(50));let n=v(e);console.log(`Instance: ${n.scope}`);let r=await t.c().get(t.u.HttpServerManager).getStatus();r.running?(console.log(`HTTP Server: 🟢 Running`),console.log(` PID: ${r.pid}`),console.log(` Port: ${r.port}`),console.log(` Health: http://localhost:${r.port}/health`)):(console.log(`HTTP Server: 🔴 Not Running`),r.error&&console.log(` Error: ${r.error}`)),console.log(``);try{let e=n.dbPath,t=((await i.stat(e)).size/1024/1024).toFixed(2);console.log(`Database: ${e} (${t} MB)`)}catch{console.log(`Database: Not found or not accessible`)}console.log(`─`.repeat(50)),process.exit(0)}catch(e){console.error(`Error getting status:`,e),process.exit(1)}}),nt=g(new l.Command(`stop`)).description(`Stop HTTP server and clean up registry/PID files`).action(async e=>{try{console.log(`🛑 Stopping log-sink-mcp services...`);let n=v(e);console.log(`Instance: ${n.scope}`),await t.c().get(t.u.HttpServerManager).stop()?(console.log(`✓ HTTP server stopped`),console.log(`✓ Registry and PID files cleaned up`)):console.log(`ℹ No HTTP server was running`),console.log(`Done! 👋`),process.exit(0)}catch(e){console.error(`Error stopping services:`,e),process.exit(1)}}),rt=(0,a.dirname)((0,o.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),it=JSON.parse((0,r.readFileSync)((0,a.join)(rt,`../package.json`),`utf-8`));async function at(){let e=new l.Command;e.name(`log-sink-mcp`).description(`Log sink MCP server with HTTP ingestion and AI analysis`).version(it.version),e.addCommand(ae),e.addCommand(et),e.addCommand(nt),e.addCommand(tt),e.addCommand($e),e.addCommand(Me),e.addCommand(Je),e.addCommand(qe),await e.parseAsync(process.argv)}at();
590
592
  //# sourceMappingURL=cli.cjs.map