@rikalabs/logpoint 0.0.2

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 ADDED
@@ -0,0 +1,104 @@
1
+ # @rikalabs/logpoint
2
+
3
+ `@rikalabs/logpoint` is a Bun + Effect CLI for runtime debugging with non-breaking HTTP logpoints.
4
+
5
+ It injects temporary logpoints into source files, collects snapshots over HTTP, summarizes anomalies, and removes instrumentation cleanly.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add -g @rikalabs/logpoint
11
+ ```
12
+
13
+ If you do not install globally, use `bunx @rikalabs/logpoint <command>`.
14
+
15
+ ### One-Line Skill Install (Claude, Codex, OpenCode, Amp)
16
+
17
+ ```bash
18
+ curl -fsSL https://raw.githubusercontent.com/Rika-Labs/logpoint/main/scripts/install-skill.sh | bash
19
+ ```
20
+
21
+ This installs `SKILL.md` and `agents/debugger.md` into:
22
+
23
+ - `~/.claude/skills/debug-logpoints/`
24
+ - `~/.agents/skills/debug-logpoints/`
25
+ - `~/.config/opencode/skills/debug-logpoints/`
26
+ - `~/.config/agents/skills/debug-logpoints/`
27
+
28
+ ## Commands
29
+
30
+ ```bash
31
+ logpoint collector [--port 9111] [--timeout 300] [--output /tmp/debug-logpoints.jsonl] [--cors-origin *]
32
+ logpoint inject --manifest /tmp/logpoints.json [--project-root .] [--language <lang>] [--dry-run]
33
+ logpoint analyze [--input /tmp/debug-logpoints.jsonl] [--format markdown|json]
34
+ logpoint cleanup [--dir .] [--ids hp1,hp2] [--dry-run] [--verify]
35
+ logpoint validate --manifest /tmp/logpoints.json
36
+ logpoint doctor
37
+ ```
38
+
39
+ ## Supported Languages
40
+
41
+ - JavaScript
42
+ - TypeScript
43
+ - Python
44
+ - Go
45
+ - Ruby
46
+ - Shell (sh/bash/zsh/ksh)
47
+ - Java
48
+ - C#
49
+ - PHP
50
+ - Rust
51
+ - Kotlin
52
+
53
+ ## Manifest Example
54
+
55
+ ```json
56
+ {
57
+ "port": 9111,
58
+ "projectRoot": ".",
59
+ "language": "typescript",
60
+ "logpoints": [
61
+ {
62
+ "id": "hp1",
63
+ "file": "src/cart.ts",
64
+ "line": 42,
65
+ "label": "cart total",
66
+ "hypothesis": "tax applied twice",
67
+ "capture": ["total", "tax", "items"],
68
+ "maxHits": 100
69
+ }
70
+ ]
71
+ }
72
+ ```
73
+
74
+ ## Safety Guarantees
75
+
76
+ - Logpoint snippets are wrapped in language-specific error guards so app flow is not interrupted.
77
+ - Sensitive variable names are blocked before injection.
78
+ - Each logpoint enforces `maxHits` locally, and the collector enforces hit limits server-side.
79
+ - Cleanup supports verification to ensure markers are fully removed.
80
+
81
+ ## Architecture Notes
82
+
83
+ - Runtime: Bun CLI and HTTP server.
84
+ - Core framework: Effect (typed errors, services, schema decoding, composable effects).
85
+ - Architecture guardrails: `repo-lint` in strict mode.
86
+ - Type safety: `tsgo` + `tsc` in strict mode.
87
+ - Linting/formatting: `oxlint`.
88
+
89
+ ## Testing and Coverage
90
+
91
+ ```bash
92
+ bun run test
93
+ bun run coverage
94
+ bun run coverage:check
95
+ ```
96
+
97
+ `coverage:check` enforces a minimum of `95%` line coverage.
98
+
99
+ ## Skill Packaging
100
+
101
+ The repo includes a portable `SKILL.md` workflow and `agents/debugger.md` persona.
102
+
103
+ See `docs/cli-compatibility.md` for installation paths and behavior across Claude Code, OpenAI Codex, OpenCode, and Amp.
104
+ See `docs/testing-and-quality.md` for the quality gate and coverage workflow.
package/SKILL.md ADDED
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: debug-logpoints
3
+ description: >
4
+ Debug runtime bugs using HTTP logpoints that capture variable state
5
+ without breakpoints. Use when the user describes a bug involving wrong
6
+ values, race conditions, unexpected state, timing issues, or says
7
+ "debug this", "why is this value wrong", "trace this", or "instrument
8
+ this code". Works with JavaScript, TypeScript, Python, Go, Ruby, Shell,
9
+ Java, C#, PHP, Rust, and Kotlin.
10
+ disable-model-invocation: true
11
+ compatibility:
12
+ - claude-code
13
+ - codex
14
+ - opencode
15
+ - amp
16
+ allowed-tools:
17
+ - Bash
18
+ - Read
19
+ - Edit
20
+ - Write
21
+ ---
22
+
23
+ ## Phase 1 - Hypothesize
24
+ 1. Read files mentioned by the user.
25
+ 2. Compare expected behavior with observed behavior.
26
+ 3. Produce 3 to 5 ranked hypotheses.
27
+ 4. For each hypothesis identify file, line, and variable captures.
28
+ 5. Confirm the proposed manifest with the user.
29
+
30
+ ## Phase 2 - Instrument
31
+ 1. Write `/tmp/logpoints.json` manifest from hypotheses.
32
+ 2. Start collector: `logpoint collector --port 9111 --timeout 300 --output /tmp/debug-logpoints.jsonl`.
33
+ 3. Inject logpoints: `logpoint inject --manifest /tmp/logpoints.json`.
34
+ 4. Show an injection summary and touched files.
35
+ 5. Ask the user to reproduce and confirm when complete.
36
+
37
+ ## Phase 3 - Analyze
38
+ 1. Run: `logpoint analyze --input /tmp/debug-logpoints.jsonl --format markdown`.
39
+ 2. Read markdown results.
40
+ 3. Confirm or refute each hypothesis with direct evidence.
41
+ 4. Present findings with hit-by-hit data.
42
+
43
+ ## Phase 4 - Fix
44
+ 1. Propose the smallest fix supported by evidence.
45
+ 2. Show diff and apply on confirmation.
46
+
47
+ ## Phase 5 - Verify
48
+ 1. Reproduce again with collector running.
49
+ 2. Confirm the anomaly no longer appears.
50
+ 3. If unresolved, return to Phase 2 with updated hypotheses.
51
+
52
+ ## Phase 6 - Cleanup
53
+ 1. Run: `logpoint cleanup --dir . --verify`.
54
+ 2. Stop collector with PID file or by port.
55
+ 3. Remove `/tmp/debug-logpoints.jsonl`, `/tmp/logpoints.json`, `/tmp/debug-logpoints.port`, `/tmp/debug-logpoints.pid`.
56
+ 4. Verify no `LOGPOINT_START` or `LOGPOINT_END` markers remain.
57
+ 5. Summarize root cause, fix, and validating evidence.
58
+
59
+ ## Error Playbooks
60
+ - `PortInUse`: increment port and retry up to 10 times.
61
+ - `SecretVarBlocked`: warn, drop that variable, continue.
62
+ - `FileNotFound`: ask user to verify path.
63
+ - `InjectionError`: show file, line, and reason; skip failed item and continue.
64
+ - `CollectorTimeout`: restart collector with higher timeout if needed.
65
+
66
+ ## Runtime Fallback
67
+ - Preferred runtime is Bun.
68
+ - If `logpoint` is not on PATH, run `bunx @rikalabs/logpoint <command>`.
@@ -0,0 +1,13 @@
1
+ You are the debugger subagent for logpoint workflows.
2
+
3
+ Responsibilities:
4
+ 1. Build ranked hypotheses from observed behavior.
5
+ 2. Produce precise logpoint manifests with low-noise variable captures.
6
+ 3. Interpret analysis results and tie evidence to hypotheses.
7
+ 4. Keep instrumentation minimal and reversible.
8
+
9
+ Rules:
10
+ 1. Do not propose fixes before evidence is collected.
11
+ 2. Avoid capturing known sensitive variables.
12
+ 3. Favor a small number of high-signal logpoints.
13
+ 4. Keep line-level targeting explicit and verifiable.
@@ -0,0 +1,65 @@
1
+ # Skill Compatibility Across Claude Code, Codex, OpenCode, and Amp
2
+
3
+ This package is designed so one `SKILL.md` workflow can be reused across major agent CLIs.
4
+
5
+ The workflow calls the global `logpoint` command, so the Skill file does not need local script paths or symlinks.
6
+
7
+ ## Compatibility Matrix
8
+
9
+ | CLI | `SKILL.md` support | Skill directories | `AGENTS.md` support | Notes |
10
+ | --- | --- | --- | --- | --- |
11
+ | Claude Code | Yes | `.claude/skills/<name>/SKILL.md`, `~/.claude/skills/<name>/SKILL.md` | Project guidance uses Claude docs conventions; Skill system is separate | Model-invoked skills; Claude decides when to load skill instructions. |
12
+ | OpenAI Codex | Yes | Repo and user skill scan includes `.agents/skills` and `$HOME/.agents/skills` | Yes (`AGENTS.md` layering from global and project scopes) | Skills are available in CLI, IDE extension, and Codex app. |
13
+ | OpenCode | Yes | `.opencode/skill`, `~/.config/opencode/skills`, plus `.claude/skills` and `.agents/skills` compatibility paths | Yes (`AGENTS.md` in repo and home) | OpenCode loads skills via its native `skill` tool. |
14
+ | Amp | Yes | `.agents/skills`, `~/.config/agents/skills`, and compatible `.claude/skills` / `.opencode/skill` | Yes | Amp documents both AGENTS and SKILL.md compatibility modes. |
15
+
16
+ ## Installation Without File Linking
17
+
18
+ Copy, do not symlink:
19
+
20
+ ```bash
21
+ # from this package root
22
+ mkdir -p ~/.claude/skills/debug-logpoints
23
+ cp SKILL.md ~/.claude/skills/debug-logpoints/SKILL.md
24
+
25
+ mkdir -p ~/.agents/skills/debug-logpoints
26
+ cp SKILL.md ~/.agents/skills/debug-logpoints/SKILL.md
27
+
28
+ mkdir -p ~/.config/opencode/skills/debug-logpoints
29
+ cp SKILL.md ~/.config/opencode/skills/debug-logpoints/SKILL.md
30
+
31
+ mkdir -p ~/.config/agents/skills/debug-logpoints
32
+ cp SKILL.md ~/.config/agents/skills/debug-logpoints/SKILL.md
33
+ ```
34
+
35
+ Optional:
36
+
37
+ ```bash
38
+ cp -R agents ~/.claude/skills/debug-logpoints/
39
+ cp -R agents ~/.agents/skills/debug-logpoints/
40
+ cp -R agents ~/.config/opencode/skills/debug-logpoints/
41
+ cp -R agents ~/.config/agents/skills/debug-logpoints/
42
+ ```
43
+
44
+ One-line installer:
45
+
46
+ ```bash
47
+ curl -fsSL https://raw.githubusercontent.com/Rika-Labs/logpoint/main/scripts/install-skill.sh | bash
48
+ ```
49
+
50
+ ## Recommended Project Layout
51
+
52
+ - Keep one canonical `SKILL.md` in this package.
53
+ - Keep CLI behavior in the `logpoint` binary (`@rikalabs/logpoint`) instead of hardcoded filesystem paths.
54
+ - Use project `AGENTS.md` only for repository-specific policy and constraints, not for duplicating the full debugging workflow.
55
+
56
+ ## Primary References
57
+
58
+ - Claude Code Skill docs: https://docs.claude.com/en/docs/claude-code/skills
59
+ - OpenAI Codex Skills docs: https://developers.openai.com/codex/skills
60
+ - OpenAI Codex AGENTS docs: https://developers.openai.com/codex/guides/agents-md
61
+ - OpenAI skills catalog (`openai/skills`): https://github.com/openai/skills
62
+ - OpenCode Skills docs: https://opencode.ai/docs/skills/
63
+ - OpenCode Agents docs: https://opencode.ai/docs/agents/
64
+ - Amp AGENTS docs: https://ampcode.com/manual#agentsmd
65
+ - Amp Skills docs: https://ampcode.com/reference/skills
@@ -0,0 +1,40 @@
1
+ # Testing and Quality Gates
2
+
3
+ ## Local Quality Pipeline
4
+
5
+ Run the full project gate:
6
+
7
+ ```bash
8
+ bun run check
9
+ ```
10
+
11
+ This runs:
12
+
13
+ 1. `oxlint` for lint + formatting policy.
14
+ 2. `tsgo` and `tsc` for strict type checks.
15
+ 3. Coverage gate with minimum line coverage of `95%`.
16
+ 4. `repo-lint` strict architecture validation.
17
+
18
+ ## Coverage Commands
19
+
20
+ ```bash
21
+ bun run coverage
22
+ bun run coverage:check
23
+ ```
24
+
25
+ `coverage:check` generates LCOV and fails if combined line coverage is below `95%`.
26
+
27
+ ## Test Scope
28
+
29
+ Current tests cover:
30
+
31
+ - schema decoding and defaulting behavior
32
+ - CLI argument parsing edge cases
33
+ - JSONL append/read failure paths
34
+ - injection and cleanup logic (including Go import mutation paths)
35
+ - anomaly detection and markdown/json rendering
36
+ - service layer behavior (`FileSystem`, `Logger`, `Clock`, `RuntimeEnv`)
37
+
38
+ ## CI Recommendation
39
+
40
+ Use `bun run check` as the required CI status for pull requests.
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@rikalabs/logpoint",
3
+ "version": "0.0.2",
4
+ "description": "HTTP logpoint debugger CLI built with Bun and Effect",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Rika-Labs/logpoint"
8
+ },
9
+ "homepage": "https://github.com/Rika-Labs/logpoint#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/Rika-Labs/logpoint/issues"
12
+ },
13
+ "license": "MIT",
14
+ "author": "Rika Labs",
15
+ "type": "module",
16
+ "bin": {
17
+ "logpoint": "src/cli.ts"
18
+ },
19
+ "files": [
20
+ "src",
21
+ "SKILL.md",
22
+ "agents",
23
+ "README.md",
24
+ "docs"
25
+ ],
26
+ "scripts": {
27
+ "dev": "bun run src/cli.ts",
28
+ "collector": "bun run src/collector.ts",
29
+ "inject": "bun run src/inject.ts",
30
+ "cleanup": "bun run src/cleanup.ts",
31
+ "analyze": "bun run src/analyze.ts",
32
+ "validate": "bun run src/validate.ts",
33
+ "doctor": "bun run src/doctor.ts",
34
+ "lint": "oxlint -c oxlint.json src test --deny-warnings",
35
+ "lint:fix": "oxlint -c oxlint.json src test --fix",
36
+ "typecheck": "tsgo --noEmit && tsc --noEmit",
37
+ "test": "bun test",
38
+ "coverage": "bun test --coverage",
39
+ "coverage:check": "bun test --coverage --coverage-reporter=lcov > /dev/null && bun -e \"const fs=require('node:fs');const lines=fs.readFileSync('coverage/lcov.info','utf8').split('\\\\n');let lf=0;let lh=0;for(const line of lines){if(line.startsWith('LF:'))lf+=Number(line.slice(3));if(line.startsWith('LH:'))lh+=Number(line.slice(3));}const pct=lf===0?0:lh/lf*100;console.log('line coverage: '+pct.toFixed(2)+'% ('+lh+'/'+lf+')');if(pct<95){console.error('Expected at least 95% line coverage.');process.exit(1);}\"",
40
+ "check:arch": "repo-lint check --config repo-lint.config.yaml",
41
+ "check": "bun run lint && bun run typecheck && bun run coverage:check && bun run check:arch"
42
+ },
43
+ "dependencies": {
44
+ "@effect/schema": "^0.75.5",
45
+ "effect": "^3.19.14"
46
+ },
47
+ "devDependencies": {
48
+ "@rikalabs/repo-lint": "^2.1.3",
49
+ "@types/bun": "latest",
50
+ "@typescript/native-preview": "^7.0.0-dev.20260207.1",
51
+ "oxlint": "^1.43.0",
52
+ "typescript": "^5.9.3"
53
+ }
54
+ }
package/src/analyze.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { Chunk, Effect, Stream } from "effect";
2
+ import {
3
+ optionalStringOption,
4
+ parseArgs,
5
+ } from "./lib/cli-args.js";
6
+ import {
7
+ ManifestError,
8
+ ValidationError,
9
+ type FileReadError,
10
+ type ParseError,
11
+ } from "./lib/errors.js";
12
+ import { readJsonLines } from "./lib/jsonl.js";
13
+ import { detectAnomalies, groupSnapshots, renderJson, renderMarkdown } from "./lib/render.js";
14
+ import { decodeAnalyzeConfigUnknown, decodeSnapshotUnknown, type AnalyzeConfig } from "./lib/schema.js";
15
+ import { AppLive, Logger } from "./lib/services.js";
16
+
17
+ const parseAnalyzeConfig = (argv: readonly string[]): Effect.Effect<AnalyzeConfig, ManifestError | ValidationError, never> =>
18
+ Effect.gen(function* () {
19
+ const parsed = parseArgs(argv);
20
+ const positionalInput = parsed.positionals[0];
21
+ const input = (yield* optionalStringOption(parsed, "input")) ?? positionalInput;
22
+ const format = yield* optionalStringOption(parsed, "format");
23
+
24
+ if (format !== undefined && format !== "markdown" && format !== "json") {
25
+ return yield* Effect.fail(new ValidationError({ message: `Unsupported format: ${format}` }));
26
+ }
27
+
28
+ return yield* decodeAnalyzeConfigUnknown({
29
+ input,
30
+ format,
31
+ });
32
+ });
33
+
34
+ export type AnalyzeRunError = ManifestError | ValidationError | FileReadError | ParseError;
35
+
36
+ export const runAnalyze = (
37
+ config: AnalyzeConfig,
38
+ ): Effect.Effect<void, AnalyzeRunError | ValidationError, Logger> =>
39
+ Effect.gen(function* () {
40
+ const logger = yield* Logger;
41
+ const lines = yield* readJsonLines(config.input);
42
+
43
+ const snapshots = yield* Stream.fromIterable(lines).pipe(
44
+ Stream.mapEffect((line) => decodeSnapshotUnknown(line)),
45
+ Stream.runCollect,
46
+ Effect.map((chunk) => Chunk.toReadonlyArray(chunk)),
47
+ );
48
+
49
+ const grouped = groupSnapshots(snapshots);
50
+ const anomalies = detectAnomalies(grouped);
51
+
52
+ const output = config.format === "json" ? renderJson(grouped, anomalies) : renderMarkdown(grouped, anomalies);
53
+ yield* logger.info(output);
54
+ });
55
+
56
+ export const runAnalyzeFromArgs = (
57
+ argv: readonly string[],
58
+ ): Effect.Effect<void, AnalyzeRunError | ValidationError, Logger> =>
59
+ parseAnalyzeConfig(argv).pipe(Effect.flatMap((config) => runAnalyze(config)));
60
+
61
+ const execute = (argv: readonly string[]): Promise<void> =>
62
+ Effect.runPromise(runAnalyzeFromArgs(argv).pipe(Effect.provide(AppLive)));
63
+
64
+ if (import.meta.main) {
65
+ execute(Bun.argv.slice(2)).catch((error) => {
66
+ console.error(error);
67
+ process.exitCode = 1;
68
+ });
69
+ }
package/src/cleanup.ts ADDED
@@ -0,0 +1,150 @@
1
+ import { Effect } from "effect";
2
+ import {
3
+ optionalBooleanOption,
4
+ optionalCsvOption,
5
+ optionalStringOption,
6
+ parseArgs,
7
+ } from "./lib/cli-args.js";
8
+ import {
9
+ CleanupError,
10
+ ManifestError,
11
+ ValidationError,
12
+ type FileNotFound,
13
+ type FileReadError,
14
+ type FileWriteError,
15
+ } from "./lib/errors.js";
16
+ import { cleanupContent, countMarkers } from "./lib/injection-engine.js";
17
+ import { decodeCleanupConfigUnknown, type CleanupConfig } from "./lib/schema.js";
18
+ import { AppLive, FileSystem, Logger } from "./lib/services.js";
19
+
20
+ const sourcePattern = "**/*.{js,jsx,ts,tsx,mjs,cjs,py,go,rb,sh,bash,zsh,ksh,java,cs,php,rs,kt,kts}";
21
+
22
+ const ignoredSegments = new Set(["node_modules", ".git", "dist", "coverage", ".next", "target"]);
23
+
24
+ const shouldSkipPath = (path: string): boolean => {
25
+ const parts = path.split("/");
26
+ return parts.some((part) => ignoredSegments.has(part));
27
+ };
28
+
29
+ const parseCleanupConfig = (argv: readonly string[]): Effect.Effect<CleanupConfig, ManifestError | ValidationError, never> =>
30
+ Effect.gen(function* () {
31
+ const parsed = parseArgs(argv);
32
+ const positionalDir = parsed.positionals[0];
33
+ const dir = (yield* optionalStringOption(parsed, "dir")) ?? positionalDir;
34
+ const ids = yield* optionalCsvOption(parsed, "ids");
35
+ const dryRun = yield* optionalBooleanOption(parsed, "dry-run");
36
+ const verify = yield* optionalBooleanOption(parsed, "verify");
37
+
38
+ return yield* decodeCleanupConfigUnknown({
39
+ dir,
40
+ ids,
41
+ dryRun,
42
+ verify,
43
+ });
44
+ });
45
+
46
+ const hasTargetMarkers = (content: string, ids?: readonly string[]): boolean => {
47
+ if (ids === undefined) {
48
+ return countMarkers(content) > 0;
49
+ }
50
+
51
+ for (const id of ids) {
52
+ if (content.includes(`LOGPOINT_START [${id}]`)) {
53
+ return true;
54
+ }
55
+ }
56
+
57
+ return false;
58
+ };
59
+
60
+ export type CleanupRunError =
61
+ | ManifestError
62
+ | ValidationError
63
+ | CleanupError
64
+ | FileNotFound
65
+ | FileReadError
66
+ | FileWriteError;
67
+
68
+ export const runCleanup = (
69
+ config: CleanupConfig,
70
+ ): Effect.Effect<void, CleanupRunError, FileSystem | Logger> =>
71
+ Effect.gen(function* () {
72
+ const fs = yield* FileSystem;
73
+ const logger = yield* Logger;
74
+
75
+ const allFiles = yield* fs.glob(sourcePattern, config.dir);
76
+ const files = allFiles.filter((path) => !shouldSkipPath(path));
77
+
78
+ const cleaned = yield* Effect.forEach(
79
+ files,
80
+ (filePath) =>
81
+ Effect.gen(function* () {
82
+ const content = yield* fs.readFile(filePath);
83
+ if (!content.includes("LOGPOINT_START")) {
84
+ return { filePath, removed: 0 };
85
+ }
86
+
87
+ const result = cleanupContent(content, config.ids);
88
+
89
+ if (!config.dryRun && result.removed > 0) {
90
+ yield* fs.writeFile(filePath, result.cleaned);
91
+ }
92
+
93
+ return { filePath, removed: result.removed };
94
+ }),
95
+ { concurrency: 16 },
96
+ );
97
+
98
+ const touched = cleaned.filter((item) => item.removed > 0);
99
+ const totalRemoved = touched.reduce((total, item) => total + item.removed, 0);
100
+
101
+ if (config.verify && !config.dryRun) {
102
+ const verification = yield* Effect.forEach(
103
+ touched,
104
+ (entry) =>
105
+ Effect.gen(function* () {
106
+ const content = yield* fs.readFile(entry.filePath);
107
+ return { filePath: entry.filePath, hasMarkers: hasTargetMarkers(content, config.ids) };
108
+ }),
109
+ );
110
+
111
+ const failed = verification.find((entry) => entry.hasMarkers);
112
+ if (failed !== undefined) {
113
+ return yield* Effect.fail(
114
+ new CleanupError({
115
+ file: failed.filePath,
116
+ reason: "Verification failed: marker still present",
117
+ }),
118
+ );
119
+ }
120
+ }
121
+
122
+ yield* logger.json({
123
+ filesScanned: files.length,
124
+ filesTouched: touched.length,
125
+ removed: totalRemoved,
126
+ dryRun: config.dryRun,
127
+ ids: config.ids,
128
+ });
129
+
130
+ for (const item of touched) {
131
+ if (config.dryRun) {
132
+ yield* logger.info(`dry-run would remove ${item.removed} logpoints from ${item.filePath}`);
133
+ }
134
+ }
135
+ });
136
+
137
+ export const runCleanupFromArgs = (
138
+ argv: readonly string[],
139
+ ): Effect.Effect<void, CleanupRunError, FileSystem | Logger> =>
140
+ parseCleanupConfig(argv).pipe(Effect.flatMap((config) => runCleanup(config)));
141
+
142
+ const execute = (argv: readonly string[]): Promise<void> =>
143
+ Effect.runPromise(runCleanupFromArgs(argv).pipe(Effect.provide(AppLive)));
144
+
145
+ if (import.meta.main) {
146
+ execute(Bun.argv.slice(2)).catch((error) => {
147
+ console.error(error);
148
+ process.exitCode = 1;
149
+ });
150
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Effect } from "effect";
4
+ import { runAnalyzeFromArgs } from "./analyze.js";
5
+ import { runCleanupFromArgs } from "./cleanup.js";
6
+ import { runCollectorFromArgs } from "./collector.js";
7
+ import { runDoctorFromArgs } from "./doctor.js";
8
+ import { CliUsageError } from "./lib/errors.js";
9
+ import { AppLive, FileSystem, Logger, RuntimeEnv } from "./lib/services.js";
10
+ import { runInjectFromArgs } from "./inject.js";
11
+ import { runValidateFromArgs } from "./validate.js";
12
+
13
+ const helpText = `logpoint - HTTP logpoint debugger CLI
14
+
15
+ Usage:
16
+ logpoint collector [--port 9111] [--timeout 300] [--output /tmp/debug-logpoints.jsonl] [--cors-origin *]
17
+ logpoint inject --manifest /tmp/logpoints.json [--project-root .] [--language javascript|typescript|python|go|ruby|shell|java|csharp|php|rust|kotlin] [--dry-run]
18
+ logpoint cleanup [--dir .] [--ids hp1,hp2] [--dry-run] [--verify]
19
+ logpoint analyze [--input /tmp/debug-logpoints.jsonl] [--format markdown|json]
20
+ logpoint validate --manifest /tmp/logpoints.json
21
+ logpoint doctor
22
+ `;
23
+
24
+ const dispatch = (
25
+ command: string,
26
+ argv: readonly string[],
27
+ ): Effect.Effect<void, unknown, FileSystem | Logger | RuntimeEnv> => {
28
+ switch (command) {
29
+ case "collector":
30
+ return runCollectorFromArgs(argv);
31
+ case "inject":
32
+ return runInjectFromArgs(argv);
33
+ case "cleanup":
34
+ return runCleanupFromArgs(argv);
35
+ case "analyze":
36
+ return runAnalyzeFromArgs(argv);
37
+ case "validate":
38
+ return runValidateFromArgs(argv);
39
+ case "doctor":
40
+ return runDoctorFromArgs();
41
+ case "help":
42
+ case "--help":
43
+ case "-h":
44
+ return Effect.sync(() => {
45
+ console.log(helpText);
46
+ });
47
+ default:
48
+ return Effect.fail(new CliUsageError({ message: `Unknown command: ${command}` }));
49
+ }
50
+ };
51
+
52
+ const runCli = (argv: readonly string[]) =>
53
+ Effect.gen(function* () {
54
+ const [command = "help", ...rest] = argv;
55
+ yield* dispatch(command, rest).pipe(
56
+ Effect.catchAll((error) => {
57
+ if (typeof error === "object" && error !== null && "_tag" in error) {
58
+ const tagged = error as { readonly _tag: string; readonly message?: string };
59
+ if (tagged._tag === "CollectorTimeout") {
60
+ return Effect.sync(() => {
61
+ console.error(tagged.message ?? "Collector timeout reached");
62
+ });
63
+ }
64
+ }
65
+ return Effect.fail(error);
66
+ }),
67
+ );
68
+ });
69
+
70
+ if (import.meta.main) {
71
+ Effect.runPromise(runCli(Bun.argv.slice(2)).pipe(Effect.provide(AppLive))).catch((error) => {
72
+ if (typeof error === "object" && error !== null && "_tag" in error) {
73
+ const tagged = error as { readonly _tag: string; readonly message?: string };
74
+ if (tagged._tag === "CliUsageError") {
75
+ console.error(`${tagged.message ?? "Invalid command"}\n\n${helpText}`);
76
+ } else {
77
+ console.error(error);
78
+ }
79
+ } else {
80
+ console.error(error);
81
+ }
82
+ process.exitCode = 1;
83
+ });
84
+ }