logifai 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tomoya Fujita
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # logifai
2
+
3
+ **Auto-capture development logs for Claude Code — stop copy-pasting terminal output.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/logifai)](https://www.npmjs.com/package/logifai)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
8
+
9
+ ## The Problem
10
+
11
+ When debugging with Claude Code, you're constantly doing this:
12
+
13
+ ```
14
+ 1. Run your dev server
15
+ 2. Hit an error
16
+ 3. Scroll through terminal output
17
+ 4. Copy the error message
18
+ 5. Paste it into Claude Code
19
+ 6. Ask "what's this?"
20
+ ```
21
+
22
+ **logifai eliminates steps 3-5.** Your logs are always there — just ask Claude.
23
+
24
+ ```
25
+ 1. npm run dev 2>&1 | logifai capture
26
+ 2. Hit an error
27
+ 3. Ask Claude Code "what went wrong?"
28
+ 4. Claude automatically searches your logs and answers
29
+ ```
30
+
31
+ ## Features
32
+
33
+ - **Pipe & Capture** — `command 2>&1 | logifai capture` records everything
34
+ - **Smart Normalization** — auto-detects JSON, infers log levels (ERROR/WARN/INFO/DEBUG), groups stack traces
35
+ - **Automatic Redaction** — API keys, tokens, passwords, and connection strings are masked before storage
36
+ - **Claude Code Skill** — Claude searches your logs automatically when you ask about errors
37
+ - **NDJSON Storage** — structured, greppable, `jq`-friendly format
38
+ - **Zero Dependencies** — built on Node.js standard library only
39
+
40
+ ## Quick Start
41
+
42
+ ### 1. Install
43
+
44
+ ```bash
45
+ npm install -g logifai
46
+ ```
47
+
48
+ ### 2. Capture logs
49
+
50
+ ```bash
51
+ npm run dev 2>&1 | logifai capture
52
+ ```
53
+
54
+ Output passes through to your terminal as normal — logifai records it in the background.
55
+
56
+ ### 3. Install the Claude Code Skill
57
+
58
+ ```bash
59
+ cp -r "$(npm root -g)/logifai/skills/logifai" ~/.claude/skills/logifai
60
+ ```
61
+
62
+ ### 4. Ask Claude
63
+
64
+ Just ask Claude Code naturally:
65
+
66
+ - "What errors happened recently?"
67
+ - "Show me the stack trace from the last failure"
68
+ - "What went wrong with the API call?"
69
+
70
+ Claude automatically searches your captured logs and responds with context.
71
+
72
+ ## How It Works
73
+
74
+ ```
75
+ stdin ──→ Normalizer ──→ Redactor ──→ NDJSON file
76
+ │ │
77
+ ├─ JSON auto-detect └─ ~/.local/state/logifai/logs/
78
+ ├─ Level inference ├─ session-*.ndjson
79
+ ├─ Stack trace grouping └─ current.ndjson (symlink)
80
+ └─ CLF/Syslog parsing
81
+ ```
82
+
83
+ Each log line becomes a structured JSON entry:
84
+
85
+ ```json
86
+ {
87
+ "timestamp": "2026-02-08T10:30:45.123Z",
88
+ "level": "ERROR",
89
+ "message": "Module not found: @/components/Button",
90
+ "source": "npm-run-dev",
91
+ "project": "/home/user/my-app",
92
+ "session_id": "a1b2c3d4",
93
+ "git_branch": "feature/auth",
94
+ "stack": "Error: Module not found\n at Object.<anonymous> ..."
95
+ }
96
+ ```
97
+
98
+ The Claude Code Skill (`~/.claude/skills/logifai/SKILL.md`) gives Claude the knowledge to search these files using `grep`, `jq`, and standard tools — no MCP server needed.
99
+
100
+ ## CLI Reference
101
+
102
+ ```
103
+ logifai capture [options]
104
+
105
+ Options:
106
+ --source <name> Source label for log entries (default: "unknown")
107
+ --project <path> Project path (default: current directory)
108
+ --no-passthrough Don't echo stdin to stdout
109
+ --help Show help
110
+ --version Show version
111
+ ```
112
+
113
+ ### Examples
114
+
115
+ ```bash
116
+ # Basic capture
117
+ npm run dev 2>&1 | logifai capture
118
+
119
+ # Label the source
120
+ npm run build 2>&1 | logifai capture --source build
121
+
122
+ # Capture without terminal echo
123
+ npm test 2>&1 | logifai capture --no-passthrough
124
+
125
+ # Capture any command
126
+ docker compose up 2>&1 | logifai capture --source docker
127
+ ```
128
+
129
+ ## Storage
130
+
131
+ Logs are stored following the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir/latest/):
132
+
133
+ ```
134
+ ~/.local/state/logifai/logs/
135
+ ├── session-20260208-103045-a1b2c3d4.ndjson # Session files
136
+ ├── session-20260208-140522-e5f6g7h8.ndjson
137
+ └── current.ndjson -> session-...ndjson # Symlink to latest
138
+ ```
139
+
140
+ Respects `$XDG_STATE_HOME` if set.
141
+
142
+ ### Manual Queries
143
+
144
+ ```bash
145
+ # Recent errors
146
+ grep '"level":"ERROR"' ~/.local/state/logifai/logs/current.ndjson
147
+
148
+ # Errors with context
149
+ grep -B 5 -A 5 "Module not found" ~/.local/state/logifai/logs/current.ndjson
150
+
151
+ # With jq
152
+ jq 'select(.level == "ERROR" and .stack != null)' ~/.local/state/logifai/logs/current.ndjson
153
+ ```
154
+
155
+ ## Security
156
+
157
+ ### Automatic Redaction
158
+
159
+ Sensitive data is automatically masked before being written to disk:
160
+
161
+ | Pattern | Example |
162
+ |---------|---------|
163
+ | Bearer tokens | `Bearer [REDACTED]` |
164
+ | GitHub PATs | `[REDACTED]` |
165
+ | OpenAI/Anthropic API keys | `[REDACTED]` |
166
+ | AWS Access Key IDs | `[REDACTED]` |
167
+ | Database connection strings | `postgres://[REDACTED]:[REDACTED]@...` |
168
+ | JWT tokens | `[REDACTED]` |
169
+ | Generic secrets (`api_key=...`, `token=...`) | `api_key=[REDACTED]` |
170
+ | Private key blocks | `[REDACTED]` |
171
+
172
+ ### File Permissions
173
+
174
+ - Log directory: `700` (owner-only access)
175
+ - Log files: `600` (owner read/write only)
176
+
177
+ ### Local Only
178
+
179
+ All data stays on your machine. No external services, no telemetry, no network calls.
180
+
181
+ ## Roadmap
182
+
183
+ | Phase | Status | Description |
184
+ |-------|--------|-------------|
185
+ | **Phase 1** | Done | Pipe capture, NDJSON storage, normalizer, redactor, Claude Code Skill |
186
+ | **Phase 2** | Planned | `logifai exec` — child process mode with TTY propagation and signal forwarding |
187
+ | **Phase 3** | Planned | SQLite FTS5 index, `.logifai.toml` config file, `logifai start` |
188
+ | **Phase 4** | Planned | MCP server, semantic search, anomaly detection |
189
+
190
+ See [doc.md](doc.md) for the full technical specification.
191
+
192
+ ## Contributing
193
+
194
+ Contributions are welcome! Please:
195
+
196
+ 1. Fork the repository
197
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
198
+ 3. Make your changes
199
+ 4. Run tests: `npm test`
200
+ 5. Commit and push
201
+ 6. Open a Pull Request
202
+
203
+ ### Development
204
+
205
+ ```bash
206
+ git clone https://github.com/user/logifai.git
207
+ cd logifai
208
+ npm install
209
+ npm run build
210
+ npm test
211
+ ```
212
+
213
+ ## License
214
+
215
+ [MIT](LICENSE)
@@ -0,0 +1,3 @@
1
+ import type { CaptureOptions } from "./types.js";
2
+ export declare function capture(input: NodeJS.ReadableStream, options: CaptureOptions): Promise<void>;
3
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,YAAY,CAAC;AAM3D,wBAAsB,OAAO,CAC3B,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAmEf"}
@@ -0,0 +1,65 @@
1
+ import { createInterface } from "node:readline";
2
+ import { join } from "node:path";
3
+ import { createSession } from "./session.js";
4
+ import { normalizeLine, isStackTraceLine, stripAnsi } from "./normalizer.js";
5
+ import { redactLogEntry } from "./redactor.js";
6
+ import { ensureLogsDir, NdjsonWriter, updateCurrentSymlink } from "./storage.js";
7
+ export async function capture(input, options) {
8
+ const session = await createSession();
9
+ const dir = await ensureLogsDir();
10
+ const filePath = join(dir, session.filename);
11
+ const writer = new NdjsonWriter(filePath);
12
+ await updateCurrentSymlink(dir, session.filename);
13
+ // Ignore SIGPIPE (e.g. downstream consumer closes pipe)
14
+ process.on("SIGPIPE", () => { });
15
+ const rl = createInterface({ input, crlfDelay: Infinity });
16
+ let pendingEntry = null;
17
+ let stackLines = [];
18
+ function flushPending() {
19
+ if (pendingEntry) {
20
+ if (stackLines.length > 0) {
21
+ pendingEntry.stack = stackLines.join("\n");
22
+ }
23
+ writer.write(redactLogEntry(pendingEntry));
24
+ pendingEntry = null;
25
+ stackLines = [];
26
+ }
27
+ }
28
+ for await (const rawLine of rl) {
29
+ // Passthrough: echo to stdout
30
+ if (options.passthrough) {
31
+ process.stdout.write(rawLine + "\n");
32
+ }
33
+ // Skip empty lines
34
+ if (rawLine.trim() === "")
35
+ continue;
36
+ const stripped = stripAnsi(rawLine);
37
+ // Check if this is a stack trace line
38
+ if (isStackTraceLine(stripped)) {
39
+ if (pendingEntry) {
40
+ stackLines.push(stripped);
41
+ }
42
+ else {
43
+ // Orphaned stack trace line — emit as its own entry
44
+ const entry = normalizeLine(rawLine, session, options.source, options.project);
45
+ writer.write(redactLogEntry(entry));
46
+ }
47
+ continue;
48
+ }
49
+ // Not a stack trace line — flush any pending entry first
50
+ flushPending();
51
+ // Normalize new line
52
+ const entry = normalizeLine(rawLine, session, options.source, options.project);
53
+ // If ERROR level, defer writing to accumulate stack traces
54
+ if (entry.level === "ERROR") {
55
+ pendingEntry = entry;
56
+ }
57
+ else {
58
+ writer.write(redactLogEntry(entry));
59
+ }
60
+ }
61
+ // Flush remaining pending entry
62
+ flushPending();
63
+ await writer.close();
64
+ }
65
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { capture } from "./capture.js";
3
+ const VERSION = "0.1.0";
4
+ const HELP = `logifai - Auto-capture development command output
5
+
6
+ Usage:
7
+ command 2>&1 | logifai capture [options]
8
+
9
+ Commands:
10
+ capture Read stdin and save as NDJSON log
11
+
12
+ Options:
13
+ --source <name> Source label (default: "unknown")
14
+ --project <path> Project path (default: cwd)
15
+ --no-passthrough Don't echo stdin to stdout
16
+ --help Show this help
17
+ --version Show version
18
+ `;
19
+ function parseArgs(args) {
20
+ let command = null;
21
+ let source = "unknown";
22
+ let project = process.cwd();
23
+ let passthrough = true;
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (arg === "capture") {
27
+ command = "capture";
28
+ }
29
+ else if (arg === "--source" && i + 1 < args.length) {
30
+ source = args[++i];
31
+ }
32
+ else if (arg === "--project" && i + 1 < args.length) {
33
+ project = args[++i];
34
+ }
35
+ else if (arg === "--no-passthrough") {
36
+ passthrough = false;
37
+ }
38
+ else if (arg === "--help" || arg === "-h") {
39
+ process.stdout.write(HELP);
40
+ process.exit(0);
41
+ }
42
+ else if (arg === "--version" || arg === "-v") {
43
+ process.stdout.write(VERSION + "\n");
44
+ process.exit(0);
45
+ }
46
+ }
47
+ return { command, source, project, passthrough };
48
+ }
49
+ async function main() {
50
+ const args = process.argv.slice(2);
51
+ const parsed = parseArgs(args);
52
+ if (!parsed.command) {
53
+ process.stderr.write("Error: No command specified. Use 'logifai capture'.\n");
54
+ process.stderr.write("Run 'logifai --help' for usage.\n");
55
+ process.exit(1);
56
+ }
57
+ if (parsed.command === "capture") {
58
+ if (process.stdin.isTTY) {
59
+ process.stderr.write("Error: No piped input detected.\n");
60
+ process.stderr.write("Usage: command 2>&1 | logifai capture\n");
61
+ process.exit(1);
62
+ }
63
+ await capture(process.stdin, {
64
+ source: parsed.source,
65
+ project: parsed.project,
66
+ passthrough: parsed.passthrough,
67
+ });
68
+ }
69
+ }
70
+ main().catch((err) => {
71
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
72
+ process.exit(1);
73
+ });
74
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type { LogEntry, LogLevel, SessionInfo, CaptureOptions } from "./types.js";
2
+ export { capture } from "./capture.js";
3
+ export { createSession, generateSessionId, getGitBranch } from "./session.js";
4
+ export { normalizeLine, detectLevel, isStackTraceLine, stripAnsi } from "./normalizer.js";
5
+ export { redact, redactLogEntry } from "./redactor.js";
6
+ export { logsDir, ensureLogsDir, NdjsonWriter, updateCurrentSymlink } from "./storage.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { capture } from "./capture.js";
2
+ export { createSession, generateSessionId, getGitBranch } from "./session.js";
3
+ export { normalizeLine, detectLevel, isStackTraceLine, stripAnsi } from "./normalizer.js";
4
+ export { redact, redactLogEntry } from "./redactor.js";
5
+ export { logsDir, ensureLogsDir, NdjsonWriter, updateCurrentSymlink } from "./storage.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { LogEntry, LogLevel, SessionInfo } from "./types.js";
2
+ export declare function stripAnsi(text: string): string;
3
+ export declare function detectLevel(line: string): LogLevel;
4
+ export declare function isStackTraceLine(line: string): boolean;
5
+ export declare function tryParseJson(line: string): Record<string, unknown> | null;
6
+ interface ParsedFormat {
7
+ timestamp?: string;
8
+ level?: LogLevel;
9
+ message?: string;
10
+ extra?: Record<string, unknown>;
11
+ }
12
+ export declare function parseCommonFormats(line: string): ParsedFormat | null;
13
+ export declare function normalizeLine(line: string, session: SessionInfo, source: string, project: string): LogEntry;
14
+ export {};
15
+ //# sourceMappingURL=normalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAKlE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAMlD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAatD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAYzE;AAcD,UAAU,YAAY;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA+CpE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,QAAQ,CA2DV"}
@@ -0,0 +1,160 @@
1
+ // eslint-disable-next-line no-control-regex
2
+ const ANSI_RE = /[\u001b\u009b]\[[0-9;]*[a-zA-Z]/g;
3
+ export function stripAnsi(text) {
4
+ return text.replace(ANSI_RE, "");
5
+ }
6
+ export function detectLevel(line) {
7
+ const stripped = stripAnsi(line);
8
+ if (/error|exception|fatal|ERR!|\u2717|\u274c/i.test(stripped))
9
+ return "ERROR";
10
+ if (/\b(warn|warning)\b|WRN|\u26a0/i.test(stripped))
11
+ return "WARN";
12
+ if (/\b(debug)\b|DBG|\ud83d\udd0d/i.test(stripped))
13
+ return "DEBUG";
14
+ return "INFO";
15
+ }
16
+ export function isStackTraceLine(line) {
17
+ const stripped = stripAnsi(line).trimStart();
18
+ // V8 / Node.js: " at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1077:15)"
19
+ if (/^at\s+.+/.test(stripped))
20
+ return true;
21
+ // Python: " File "/app/main.py", line 10, in <module>"
22
+ if (/^File\s+"[^"]+",\s+line\s+\d+/.test(stripped))
23
+ return true;
24
+ // Java: " at com.example.Main.method(Main.java:42)"
25
+ if (/^at\s+[\w$.]+\([\w.]+:\d+\)/.test(stripped))
26
+ return true;
27
+ // Go: " /app/main.go:42 +0x1a"
28
+ if (/^\/.+\.go:\d+/.test(stripped))
29
+ return true;
30
+ // Rust: " 0: rust_begin_unwind"
31
+ if (/^\d+:\s+\w+/.test(stripped))
32
+ return false; // too generic, skip
33
+ return false;
34
+ }
35
+ export function tryParseJson(line) {
36
+ const trimmed = line.trim();
37
+ if (!trimmed.startsWith("{"))
38
+ return null;
39
+ try {
40
+ const parsed = JSON.parse(trimmed);
41
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
42
+ return parsed;
43
+ }
44
+ }
45
+ catch {
46
+ // Not valid JSON
47
+ }
48
+ return null;
49
+ }
50
+ // Apache/Nginx Combined Log Format
51
+ const CLF_RE = /^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d+)/;
52
+ // Syslog format
53
+ const SYSLOG_RE = /^(\w+\s+\d+\s+\d+:\d+:\d+) (\S+) ([^:[\]]+?)(?:\[(\d+)\])?: (.+)$/;
54
+ // ISO timestamp prefix: "2026-02-08T10:30:45.123Z some message"
55
+ const ISO_TS_RE = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2}))\s+(.+)$/;
56
+ export function parseCommonFormats(line) {
57
+ const stripped = stripAnsi(line);
58
+ // ISO timestamp prefix
59
+ const isoMatch = stripped.match(ISO_TS_RE);
60
+ if (isoMatch) {
61
+ return {
62
+ timestamp: isoMatch[1],
63
+ message: isoMatch[2],
64
+ level: detectLevel(isoMatch[2]),
65
+ };
66
+ }
67
+ // Apache/Nginx CLF
68
+ const clfMatch = stripped.match(CLF_RE);
69
+ if (clfMatch) {
70
+ const status = parseInt(clfMatch[6], 10);
71
+ let level = "INFO";
72
+ if (status >= 500)
73
+ level = "ERROR";
74
+ else if (status >= 400)
75
+ level = "WARN";
76
+ return {
77
+ message: `${clfMatch[3]} ${clfMatch[4]} ${clfMatch[5]} ${clfMatch[6]}`,
78
+ level,
79
+ extra: {
80
+ ip: clfMatch[1],
81
+ method: clfMatch[3],
82
+ path: clfMatch[4],
83
+ status,
84
+ },
85
+ };
86
+ }
87
+ // Syslog
88
+ const syslogMatch = stripped.match(SYSLOG_RE);
89
+ if (syslogMatch) {
90
+ return {
91
+ message: syslogMatch[5],
92
+ level: detectLevel(syslogMatch[5]),
93
+ extra: {
94
+ hostname: syslogMatch[2],
95
+ process: syslogMatch[3],
96
+ syslog_pid: syslogMatch[4] ? parseInt(syslogMatch[4], 10) : undefined,
97
+ },
98
+ };
99
+ }
100
+ return null;
101
+ }
102
+ export function normalizeLine(line, session, source, project) {
103
+ const stripped = stripAnsi(line);
104
+ const now = new Date().toISOString();
105
+ // Try JSON first
106
+ const json = tryParseJson(stripped);
107
+ if (json) {
108
+ const level = typeof json.level === "string" && isValidLevel(json.level)
109
+ ? json.level.toUpperCase()
110
+ : detectLevel(stripped);
111
+ return {
112
+ timestamp: typeof json.timestamp === "string" ? json.timestamp : now,
113
+ level,
114
+ message: typeof json.message === "string" ? json.message : stripped,
115
+ source,
116
+ project,
117
+ session_id: session.id,
118
+ git_branch: session.gitBranch,
119
+ pid: process.pid,
120
+ raw: false,
121
+ stack: null,
122
+ _original: json,
123
+ };
124
+ }
125
+ // Try common formats
126
+ const parsed = parseCommonFormats(stripped);
127
+ if (parsed) {
128
+ return {
129
+ timestamp: parsed.timestamp ?? now,
130
+ level: parsed.level ?? detectLevel(stripped),
131
+ message: parsed.message ?? stripped,
132
+ source,
133
+ project,
134
+ session_id: session.id,
135
+ git_branch: session.gitBranch,
136
+ pid: process.pid,
137
+ raw: false,
138
+ stack: null,
139
+ _original: parsed.extra ?? null,
140
+ };
141
+ }
142
+ // Raw unstructured log
143
+ return {
144
+ timestamp: now,
145
+ level: detectLevel(stripped),
146
+ message: stripped,
147
+ source,
148
+ project,
149
+ session_id: session.id,
150
+ git_branch: session.gitBranch,
151
+ pid: process.pid,
152
+ raw: true,
153
+ stack: null,
154
+ _original: null,
155
+ };
156
+ }
157
+ function isValidLevel(s) {
158
+ return ["error", "warn", "info", "debug"].includes(s.toLowerCase());
159
+ }
160
+ //# sourceMappingURL=normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.js","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { LogEntry } from "./types.js";
2
+ export declare function redact(text: string): string;
3
+ export declare function redactLogEntry(entry: LogEntry): LogEntry;
4
+ //# sourceMappingURL=redactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactor.d.ts","sourceRoot":"","sources":["../src/redactor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA4C3C,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY3C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAOxD"}
@@ -0,0 +1,78 @@
1
+ const REDACTED = "[REDACTED]";
2
+ const SENSITIVE_PATTERNS = [
3
+ // Bearer tokens
4
+ { pattern: /Bearer\s+[A-Za-z0-9\-_.]+/gi, replacement: `Bearer ${REDACTED}` },
5
+ // GitHub PAT (classic & fine-grained)
6
+ { pattern: /gh[ps]_[A-Za-z0-9]{36,}/g, replacement: REDACTED },
7
+ // OpenAI API Key
8
+ { pattern: /sk-[A-Za-z0-9]{20,}/g, replacement: REDACTED },
9
+ // Anthropic API Key
10
+ { pattern: /sk-ant-[A-Za-z0-9\-_]{20,}/g, replacement: REDACTED },
11
+ // AWS Access Key ID
12
+ { pattern: /AKIA[0-9A-Z]{16}/g, replacement: REDACTED },
13
+ // Database connection strings (postgres, mysql, mongodb)
14
+ {
15
+ pattern: /(postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@/gi,
16
+ replacement: (match) => {
17
+ const proto = match.split("://")[0];
18
+ return `${proto}://${REDACTED}:${REDACTED}@`;
19
+ },
20
+ },
21
+ // JWT tokens
22
+ {
23
+ pattern: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+/g,
24
+ replacement: REDACTED,
25
+ },
26
+ // Generic API key patterns (key=..., api_key=..., apikey=..., token=...)
27
+ {
28
+ pattern: /(api[_-]?key|token|secret|password|credential)[\s]*[=:]\s*["']?[A-Za-z0-9\-_.]{16,}["']?/gi,
29
+ replacement: (match) => {
30
+ const sep = match.includes("=") ? "=" : ":";
31
+ const key = match.split(/[=:]/)[0];
32
+ return `${key}${sep}${REDACTED}`;
33
+ },
34
+ },
35
+ // Private key blocks
36
+ {
37
+ pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(RSA\s+)?PRIVATE\s+KEY-----/g,
38
+ replacement: REDACTED,
39
+ },
40
+ ];
41
+ export function redact(text) {
42
+ let result = text;
43
+ for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
44
+ // Reset lastIndex for global regexes
45
+ pattern.lastIndex = 0;
46
+ if (typeof replacement === "string") {
47
+ result = result.replace(pattern, replacement);
48
+ }
49
+ else {
50
+ result = result.replace(pattern, replacement);
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+ export function redactLogEntry(entry) {
56
+ return {
57
+ ...entry,
58
+ message: redact(entry.message),
59
+ stack: entry.stack ? redact(entry.stack) : null,
60
+ _original: entry._original ? redactObject(entry._original) : null,
61
+ };
62
+ }
63
+ function redactObject(obj) {
64
+ const result = {};
65
+ for (const [key, value] of Object.entries(obj)) {
66
+ if (typeof value === "string") {
67
+ result[key] = redact(value);
68
+ }
69
+ else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
70
+ result[key] = redactObject(value);
71
+ }
72
+ else {
73
+ result[key] = value;
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+ //# sourceMappingURL=redactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactor.js","sourceRoot":"","sources":["../src/redactor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SessionInfo } from "./types.js";
2
+ export declare function generateSessionId(): string;
3
+ export declare function getGitBranch(): Promise<string | null>;
4
+ export declare function formatSessionFilename(date: Date, id: string): string;
5
+ export declare function createSession(): Promise<SessionInfo>;
6
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWrD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,CAM1D"}
@@ -0,0 +1,34 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ export function generateSessionId() {
4
+ return randomUUID().slice(0, 8);
5
+ }
6
+ export function getGitBranch() {
7
+ return new Promise((resolve) => {
8
+ execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"], (err, stdout) => {
9
+ if (err) {
10
+ resolve(null);
11
+ return;
12
+ }
13
+ const branch = stdout.trim();
14
+ resolve(branch || null);
15
+ });
16
+ });
17
+ }
18
+ export function formatSessionFilename(date, id) {
19
+ const y = date.getFullYear();
20
+ const mo = String(date.getMonth() + 1).padStart(2, "0");
21
+ const d = String(date.getDate()).padStart(2, "0");
22
+ const h = String(date.getHours()).padStart(2, "0");
23
+ const mi = String(date.getMinutes()).padStart(2, "0");
24
+ const s = String(date.getSeconds()).padStart(2, "0");
25
+ return `session-${y}${mo}${d}-${h}${mi}${s}-${id}.ndjson`;
26
+ }
27
+ export async function createSession() {
28
+ const id = generateSessionId();
29
+ const startedAt = new Date();
30
+ const gitBranch = await getGitBranch();
31
+ const filename = formatSessionFilename(startedAt, id);
32
+ return { id, startedAt, filename, gitBranch };
33
+ }
34
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACrE,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;YAC7B,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAU,EAAE,EAAU;IAC1D,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,MAAM,YAAY,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACtD,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { LogEntry } from "./types.js";
2
+ export declare function logsDir(): string;
3
+ export declare function ensureLogsDir(): Promise<string>;
4
+ export declare class NdjsonWriter {
5
+ private stream;
6
+ readonly filePath: string;
7
+ constructor(filePath: string);
8
+ write(entry: LogEntry): void;
9
+ close(): Promise<void>;
10
+ }
11
+ export declare function updateCurrentSymlink(dir: string, filename: string): Promise<void>;
12
+ export declare function fileExists(path: string): Promise<boolean>;
13
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,OAAO,IAAI,MAAM,CAGhC;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAIrD;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAc;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM;IAQ5B,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQvB;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D"}
@@ -0,0 +1,59 @@
1
+ import { mkdir, symlink, unlink, stat } from "node:fs/promises";
2
+ import { createWriteStream } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ export function logsDir() {
6
+ const xdgState = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
7
+ return join(xdgState, "logifai", "logs");
8
+ }
9
+ export async function ensureLogsDir() {
10
+ const dir = logsDir();
11
+ await mkdir(dir, { recursive: true, mode: 0o700 });
12
+ return dir;
13
+ }
14
+ export class NdjsonWriter {
15
+ stream;
16
+ filePath;
17
+ constructor(filePath) {
18
+ this.filePath = filePath;
19
+ this.stream = createWriteStream(filePath, {
20
+ flags: "a",
21
+ mode: 0o600,
22
+ });
23
+ }
24
+ write(entry) {
25
+ this.stream.write(JSON.stringify(entry) + "\n");
26
+ }
27
+ close() {
28
+ return new Promise((resolve, reject) => {
29
+ this.stream.end((err) => {
30
+ if (err)
31
+ reject(err);
32
+ else
33
+ resolve();
34
+ });
35
+ });
36
+ }
37
+ }
38
+ export async function updateCurrentSymlink(dir, filename) {
39
+ const linkPath = join(dir, "current.ndjson");
40
+ try {
41
+ await unlink(linkPath);
42
+ }
43
+ catch (err) {
44
+ // Ignore if symlink doesn't exist
45
+ if (err.code !== "ENOENT")
46
+ throw err;
47
+ }
48
+ await symlink(filename, linkPath);
49
+ }
50
+ export async function fileExists(path) {
51
+ try {
52
+ await stat(path);
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,OAAO,YAAY;IACf,MAAM,CAAc;IACnB,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,QAAQ,EAAE;YACxC,KAAK,EAAE,GAAG;YACV,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAe;QACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,KAAK;QACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAW,EAAE,EAAE;gBAC9B,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAW,EACX,QAAgB;IAEhB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,kCAAkC;QAClC,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;IACD,MAAM,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ export type LogLevel = "ERROR" | "WARN" | "INFO" | "DEBUG";
2
+ export interface LogEntry {
3
+ timestamp: string;
4
+ level: LogLevel;
5
+ message: string;
6
+ source: string;
7
+ project: string;
8
+ session_id: string;
9
+ git_branch: string | null;
10
+ pid: number;
11
+ raw: boolean;
12
+ stack: string | null;
13
+ _original: Record<string, unknown> | null;
14
+ }
15
+ export interface SessionInfo {
16
+ id: string;
17
+ startedAt: Date;
18
+ filename: string;
19
+ gitBranch: string | null;
20
+ }
21
+ export interface CaptureOptions {
22
+ source: string;
23
+ project: string;
24
+ passthrough: boolean;
25
+ }
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "logifai",
3
+ "version": "0.1.0",
4
+ "description": "Auto-capture development command output and search/analyze with Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "logifai": "dist/cli.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "author": "Tomoya Fujita",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/tomoyaf/logifai.git"
15
+ },
16
+ "homepage": "https://github.com/tomoyaf/logifai#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/tomoyaf/logifai/issues"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "!dist/__tests__",
23
+ "skills",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "test": "node --test dist/__tests__/*.test.js",
30
+ "pretest": "tsc",
31
+ "prepublishOnly": "npm run build && npm test"
32
+ },
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "keywords": [
37
+ "logging",
38
+ "capture",
39
+ "cli",
40
+ "claude-code",
41
+ "developer-tools"
42
+ ],
43
+ "license": "MIT",
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "typescript": "^5.7.0"
47
+ }
48
+ }
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: logifai-logs
3
+ description: Search and analyze development logs captured by logifai. Use when investigating errors, debugging runtime issues, or reviewing recent application activity. Automatically triggered when user mentions "logs", "errors", "what went wrong", or references recent failures.
4
+ allowed-tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ # logifai Development Logs
8
+
9
+ logifai automatically captures output from development commands (e.g. `npm run dev 2>&1 | logifai capture`) and saves them as NDJSON files.
10
+
11
+ ## Log Location
12
+
13
+ ```
14
+ ~/.local/state/logifai/logs/
15
+ ├── session-YYYYMMDD-HHmmss-{id}.ndjson # Session files
16
+ └── current.ndjson -> session-...ndjson # Symlink to latest session
17
+ ```
18
+
19
+ ## NDJSON Schema
20
+
21
+ Each line is a JSON object:
22
+
23
+ ```json
24
+ {
25
+ "timestamp": "2026-02-08T10:30:45.123Z",
26
+ "level": "ERROR | WARN | INFO | DEBUG",
27
+ "message": "Log message text",
28
+ "source": "npm-run-dev",
29
+ "project": "/home/user/my-app",
30
+ "session_id": "a1b2c3d4",
31
+ "git_branch": "feature/auth",
32
+ "pid": 12345,
33
+ "raw": true,
34
+ "stack": "Error stack trace if detected",
35
+ "_original": {}
36
+ }
37
+ ```
38
+
39
+ ## Quick Search Commands
40
+
41
+ ### List recent sessions
42
+ ```bash
43
+ ls -lt ~/.local/state/logifai/logs/session-*.ndjson | head -10
44
+ ```
45
+
46
+ ### Search current session for errors
47
+ ```bash
48
+ grep '"level":"ERROR"' ~/.local/state/logifai/logs/current.ndjson
49
+ ```
50
+
51
+ ### Search all sessions for a keyword
52
+ ```bash
53
+ grep -l "keyword" ~/.local/state/logifai/logs/*.ndjson
54
+ ```
55
+
56
+ ### Get context around an error
57
+ ```bash
58
+ grep -B 5 -A 5 "Module not found" ~/.local/state/logifai/logs/current.ndjson
59
+ ```
60
+
61
+ ### Find errors with stack traces
62
+ ```bash
63
+ grep '"stack"' ~/.local/state/logifai/logs/current.ndjson | grep -v '"stack":null'
64
+ ```
65
+
66
+ ## Advanced Queries (with jq)
67
+
68
+ ```bash
69
+ # Last 50 ERROR entries
70
+ tail -n 500 ~/.local/state/logifai/logs/current.ndjson | jq 'select(.level == "ERROR")' | tail -50
71
+
72
+ # Errors after a specific time
73
+ jq 'select(.level == "ERROR" and .timestamp >= "2026-02-08T10:00:00")' ~/.local/state/logifai/logs/current.ndjson
74
+
75
+ # Errors with stack traces
76
+ jq 'select(.level == "ERROR" and .stack != null)' ~/.local/state/logifai/logs/current.ndjson
77
+
78
+ # Group by level
79
+ jq -s 'group_by(.level) | map({level: .[0].level, count: length})' ~/.local/state/logifai/logs/current.ndjson
80
+ ```
81
+
82
+ ## Investigation Workflow
83
+
84
+ When user asks about errors or issues:
85
+ 1. Check `current.ndjson` for recent ERROR entries
86
+ 2. Look for stack traces (`.stack != null`)
87
+ 3. Check surrounding WARN/INFO entries for context
88
+ 4. Search across sessions if not found in current
89
+
90
+ When user asks "what went wrong":
91
+ 1. Search for ERROR level logs in current session
92
+ 2. Check for stack traces
93
+ 3. Look for related WARN logs nearby
94
+ 4. Summarize findings with timestamps and context