llm-trace 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -41
- package/dist/cli.cjs +18 -18
- package/dist/cli.js +18 -18
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/standalone.cjs +1 -1
- package/dist/standalone.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,74 +1,76 @@
|
|
|
1
1
|
# llm-trace
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
LLMs can read your code but they can't run it in their head. When something breaks, they end up asking you to paste errors and add `console.log` statements one at a time while they guess at what's going on.
|
|
4
4
|
|
|
5
|
-
llm-trace
|
|
5
|
+
llm-trace fixes this. It gives LLMs a way to instrument your code with structured traces, see actual runtime values, and figure out what went wrong without the back-and-forth.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Try It
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install llm-trace
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Add the [debugging skill](skills/debugging-with-llm-trace/SKILL.md) to
|
|
13
|
+
Add the [debugging skill](skills/debugging-with-llm-trace/SKILL.md) to Claude Code (or any LLM coding tool) and ask it to debug something. That's it — the skill teaches it the workflow.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
### What happens next
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
2. LLM instruments your code with `trace()`, `span()`, and `checkpoint()` calls
|
|
19
|
-
3. You run your code (or LLM runs it)
|
|
20
|
-
4. LLM queries traces with `npx llm-trace list`, `show`, `tail`
|
|
21
|
-
5. LLM reads structured runtime data, identifies root cause, fixes the bug
|
|
22
|
-
6. LLM runs `npx llm-trace stop` to clean up
|
|
17
|
+
You tell the LLM "this endpoint returns 500 sometimes" and it:
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
1. Runs `npx llm-trace start`
|
|
20
|
+
2. Wraps the endpoint in `trace()` with `span()` and `checkpoint()` calls
|
|
21
|
+
3. Triggers the bug
|
|
22
|
+
4. Reads the trace — sees actual values at each step, which span failed, the error
|
|
23
|
+
5. Fixes the root cause based on what it saw
|
|
24
|
+
6. Removes instrumentation, runs `npx llm-trace stop`
|
|
25
25
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
### `trace(name, fn)` — wrap a complete operation
|
|
26
|
+
## Three Primitives
|
|
29
27
|
|
|
30
28
|
```typescript
|
|
31
29
|
import { trace } from "llm-trace";
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
// handle.span() and handle.checkpoint() available here
|
|
35
|
-
return processRequest(req);
|
|
36
|
-
});
|
|
37
|
-
```
|
|
31
|
+
await trace("checkout", async (handle) => {
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
// span — time a step, nest arbitrarily
|
|
34
|
+
await handle.span("load-cart", async (h) => {
|
|
35
|
+
const cart = await db.getCart(userId);
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
// checkpoint — snapshot runtime values
|
|
38
|
+
h.checkpoint("cart", cart);
|
|
39
|
+
|
|
40
|
+
return cart;
|
|
41
|
+
});
|
|
46
42
|
});
|
|
47
43
|
```
|
|
48
44
|
|
|
49
|
-
|
|
45
|
+
**`trace(name, fn)`** — wraps a complete operation. Captures start, end, duration, errors.
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
handle.checkpoint("parsed-input", { tokens: 142 });
|
|
53
|
-
```
|
|
47
|
+
**`handle.span(name, fn)`** — a timed step within a trace. Nests to any depth.
|
|
54
48
|
|
|
55
|
-
|
|
49
|
+
**`handle.checkpoint(name, data?)`** — snapshots a value (truncated at 64KB).
|
|
50
|
+
|
|
51
|
+
Errors are captured automatically — if anything throws, the trace records the error and stack.
|
|
56
52
|
|
|
57
53
|
## CLI
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
```bash
|
|
56
|
+
llm-trace start # begin session
|
|
57
|
+
llm-trace list # all traces (JSON by default)
|
|
58
|
+
llm-trace list --errors # just failures
|
|
59
|
+
llm-trace show <id> # full trace tree
|
|
60
|
+
llm-trace tail # watch live
|
|
61
|
+
llm-trace stop # end session, delete traces
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Output is JSON by default (for LLM consumption). Add `--human` for readable output.
|
|
65
|
+
|
|
66
|
+
## How It Fits Together
|
|
67
|
+
|
|
68
|
+
The LLM writes `trace()` / `span()` / `checkpoint()` calls into your code. When the code runs, events stream over HTTP to a local server that writes `.ndjson` files. The LLM reads those files via the CLI. After debugging, everything is cleaned up — traces are ephemeral.
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
No dependencies. No config. Nothing persisted after `stop`.
|
|
69
71
|
|
|
70
72
|
## Configuration
|
|
71
73
|
|
|
72
74
|
| Variable | Default | Description |
|
|
73
75
|
|----------|---------|-------------|
|
|
74
|
-
| `
|
|
76
|
+
| `LLM_TRACE_PORT` | `13579` | HTTP server port |
|
package/dist/cli.cjs
CHANGED
|
@@ -168,7 +168,7 @@ async function getFormattedList(logDir, options) {
|
|
|
168
168
|
}), options.human === true);
|
|
169
169
|
}
|
|
170
170
|
async function runList(options) {
|
|
171
|
-
const logDir = (0, node_path.join)(process.cwd(), ".trace-
|
|
171
|
+
const logDir = (0, node_path.join)(process.cwd(), ".llm-trace-logs");
|
|
172
172
|
if (!(0, node_fs.existsSync)(logDir)) {
|
|
173
173
|
console.log("No active session.");
|
|
174
174
|
return;
|
|
@@ -179,7 +179,7 @@ async function runList(options) {
|
|
|
179
179
|
//#endregion
|
|
180
180
|
//#region src/cli/commands/show.ts
|
|
181
181
|
async function runShow(id, options) {
|
|
182
|
-
const logDir = (0, node_path.join)(process.cwd(), ".trace-
|
|
182
|
+
const logDir = (0, node_path.join)(process.cwd(), ".llm-trace-logs");
|
|
183
183
|
if (!(0, node_fs.existsSync)(logDir)) {
|
|
184
184
|
console.log("No active session.");
|
|
185
185
|
return;
|
|
@@ -191,7 +191,7 @@ async function runShow(id, options) {
|
|
|
191
191
|
//#endregion
|
|
192
192
|
//#region src/cli/commands/start.ts
|
|
193
193
|
async function startSession(projectDir, options = {}) {
|
|
194
|
-
const logDir = (0, node_path.join)(projectDir, ".trace-
|
|
194
|
+
const logDir = (0, node_path.join)(projectDir, ".llm-trace-logs");
|
|
195
195
|
if ((0, node_fs.existsSync)(logDir)) return {
|
|
196
196
|
created: false,
|
|
197
197
|
reason: "already_active"
|
|
@@ -199,17 +199,17 @@ async function startSession(projectDir, options = {}) {
|
|
|
199
199
|
(0, node_fs.mkdirSync)(logDir, { recursive: true });
|
|
200
200
|
const gitignorePath = (0, node_path.join)(projectDir, ".gitignore");
|
|
201
201
|
if ((0, node_fs.existsSync)(gitignorePath)) {
|
|
202
|
-
if (!(0, node_fs.readFileSync)(gitignorePath, "utf-8").includes(".trace-
|
|
203
|
-
} else (0, node_fs.writeFileSync)(gitignorePath, ".trace-
|
|
202
|
+
if (!(0, node_fs.readFileSync)(gitignorePath, "utf-8").includes(".llm-trace-logs/")) (0, node_fs.appendFileSync)(gitignorePath, "\n.llm-trace-logs/\n");
|
|
203
|
+
} else (0, node_fs.writeFileSync)(gitignorePath, ".llm-trace-logs/\n");
|
|
204
204
|
if (!options.skipServer) {
|
|
205
|
-
const port = parseInt(process.env.
|
|
205
|
+
const port = parseInt(process.env.LLM_TRACE_PORT || "", 10) || 13579;
|
|
206
206
|
const script = (0, node_path.join)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href), "..", "standalone.js");
|
|
207
207
|
const child = (0, node_child_process.spawn)(process.execPath, [script], {
|
|
208
208
|
detached: true,
|
|
209
209
|
stdio: "ignore",
|
|
210
210
|
env: {
|
|
211
211
|
...process.env,
|
|
212
|
-
|
|
212
|
+
LLM_TRACE_PORT: String(port),
|
|
213
213
|
TRACE_AI_DIR: logDir
|
|
214
214
|
}
|
|
215
215
|
});
|
|
@@ -234,7 +234,7 @@ async function runStart() {
|
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region src/cli/commands/status.ts
|
|
236
236
|
function getSessionStatus(projectDir) {
|
|
237
|
-
const logDir = (0, node_path.join)(projectDir, ".trace-
|
|
237
|
+
const logDir = (0, node_path.join)(projectDir, ".llm-trace-logs");
|
|
238
238
|
if (!(0, node_fs.existsSync)(logDir)) return { active: false };
|
|
239
239
|
const files = (0, node_fs.readdirSync)(logDir).filter((f) => f.endsWith(".ndjson"));
|
|
240
240
|
let errorCount = 0;
|
|
@@ -264,7 +264,7 @@ async function runStatus() {
|
|
|
264
264
|
//#endregion
|
|
265
265
|
//#region src/cli/commands/stop.ts
|
|
266
266
|
async function stopSession(projectDir) {
|
|
267
|
-
const logDir = (0, node_path.join)(projectDir, ".trace-
|
|
267
|
+
const logDir = (0, node_path.join)(projectDir, ".llm-trace-logs");
|
|
268
268
|
if (!(0, node_fs.existsSync)(logDir)) return {
|
|
269
269
|
stopped: false,
|
|
270
270
|
reason: "no_session"
|
|
@@ -294,7 +294,7 @@ function summarizeFile(filePath) {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
async function runTail(options) {
|
|
297
|
-
const logDir = (0, node_path.join)(process.cwd(), ".trace-
|
|
297
|
+
const logDir = (0, node_path.join)(process.cwd(), ".llm-trace-logs");
|
|
298
298
|
if (!(0, node_fs.existsSync)(logDir)) {
|
|
299
299
|
console.log("No active session.");
|
|
300
300
|
return;
|
|
@@ -343,15 +343,15 @@ function parseCliArgs(argv) {
|
|
|
343
343
|
|
|
344
344
|
//#endregion
|
|
345
345
|
//#region src/cli/index.ts
|
|
346
|
-
const HELP = `trace
|
|
346
|
+
const HELP = `llm-trace — Structured execution traces for LLM debugging
|
|
347
347
|
|
|
348
348
|
Usage:
|
|
349
|
-
trace
|
|
350
|
-
trace
|
|
351
|
-
trace
|
|
352
|
-
trace
|
|
353
|
-
trace
|
|
354
|
-
trace
|
|
349
|
+
llm-trace start Begin debugging session
|
|
350
|
+
llm-trace stop End session, delete traces
|
|
351
|
+
llm-trace status Show session info
|
|
352
|
+
llm-trace list [--errors] [--name <pattern>] [--last <n>] [--human]
|
|
353
|
+
llm-trace show <id> [--human]
|
|
354
|
+
llm-trace tail [--errors] [--name <pattern>]
|
|
355
355
|
`;
|
|
356
356
|
async function main() {
|
|
357
357
|
const { command, id, options } = parseCliArgs(process.argv.slice(2));
|
|
@@ -362,7 +362,7 @@ async function main() {
|
|
|
362
362
|
case "list": return runList(options);
|
|
363
363
|
case "show":
|
|
364
364
|
if (!id) {
|
|
365
|
-
console.error("Usage: trace
|
|
365
|
+
console.error("Usage: llm-trace show <id>");
|
|
366
366
|
process.exit(1);
|
|
367
367
|
}
|
|
368
368
|
return runShow(id, options);
|
package/dist/cli.js
CHANGED
|
@@ -168,7 +168,7 @@ async function getFormattedList(logDir, options) {
|
|
|
168
168
|
}), options.human === true);
|
|
169
169
|
}
|
|
170
170
|
async function runList(options) {
|
|
171
|
-
const logDir = join(process.cwd(), ".trace-
|
|
171
|
+
const logDir = join(process.cwd(), ".llm-trace-logs");
|
|
172
172
|
if (!existsSync(logDir)) {
|
|
173
173
|
console.log("No active session.");
|
|
174
174
|
return;
|
|
@@ -179,7 +179,7 @@ async function runList(options) {
|
|
|
179
179
|
//#endregion
|
|
180
180
|
//#region src/cli/commands/show.ts
|
|
181
181
|
async function runShow(id, options) {
|
|
182
|
-
const logDir = join(process.cwd(), ".trace-
|
|
182
|
+
const logDir = join(process.cwd(), ".llm-trace-logs");
|
|
183
183
|
if (!existsSync(logDir)) {
|
|
184
184
|
console.log("No active session.");
|
|
185
185
|
return;
|
|
@@ -191,7 +191,7 @@ async function runShow(id, options) {
|
|
|
191
191
|
//#endregion
|
|
192
192
|
//#region src/cli/commands/start.ts
|
|
193
193
|
async function startSession(projectDir, options = {}) {
|
|
194
|
-
const logDir = join(projectDir, ".trace-
|
|
194
|
+
const logDir = join(projectDir, ".llm-trace-logs");
|
|
195
195
|
if (existsSync(logDir)) return {
|
|
196
196
|
created: false,
|
|
197
197
|
reason: "already_active"
|
|
@@ -199,17 +199,17 @@ async function startSession(projectDir, options = {}) {
|
|
|
199
199
|
mkdirSync(logDir, { recursive: true });
|
|
200
200
|
const gitignorePath = join(projectDir, ".gitignore");
|
|
201
201
|
if (existsSync(gitignorePath)) {
|
|
202
|
-
if (!readFileSync(gitignorePath, "utf-8").includes(".trace-
|
|
203
|
-
} else writeFileSync(gitignorePath, ".trace-
|
|
202
|
+
if (!readFileSync(gitignorePath, "utf-8").includes(".llm-trace-logs/")) appendFileSync(gitignorePath, "\n.llm-trace-logs/\n");
|
|
203
|
+
} else writeFileSync(gitignorePath, ".llm-trace-logs/\n");
|
|
204
204
|
if (!options.skipServer) {
|
|
205
|
-
const port = parseInt(process.env.
|
|
205
|
+
const port = parseInt(process.env.LLM_TRACE_PORT || "", 10) || 13579;
|
|
206
206
|
const script = join(fileURLToPath(import.meta.url), "..", "standalone.js");
|
|
207
207
|
const child = spawn(process.execPath, [script], {
|
|
208
208
|
detached: true,
|
|
209
209
|
stdio: "ignore",
|
|
210
210
|
env: {
|
|
211
211
|
...process.env,
|
|
212
|
-
|
|
212
|
+
LLM_TRACE_PORT: String(port),
|
|
213
213
|
TRACE_AI_DIR: logDir
|
|
214
214
|
}
|
|
215
215
|
});
|
|
@@ -234,7 +234,7 @@ async function runStart() {
|
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region src/cli/commands/status.ts
|
|
236
236
|
function getSessionStatus(projectDir) {
|
|
237
|
-
const logDir = join(projectDir, ".trace-
|
|
237
|
+
const logDir = join(projectDir, ".llm-trace-logs");
|
|
238
238
|
if (!existsSync(logDir)) return { active: false };
|
|
239
239
|
const files = readdirSync(logDir).filter((f) => f.endsWith(".ndjson"));
|
|
240
240
|
let errorCount = 0;
|
|
@@ -264,7 +264,7 @@ async function runStatus() {
|
|
|
264
264
|
//#endregion
|
|
265
265
|
//#region src/cli/commands/stop.ts
|
|
266
266
|
async function stopSession(projectDir) {
|
|
267
|
-
const logDir = join(projectDir, ".trace-
|
|
267
|
+
const logDir = join(projectDir, ".llm-trace-logs");
|
|
268
268
|
if (!existsSync(logDir)) return {
|
|
269
269
|
stopped: false,
|
|
270
270
|
reason: "no_session"
|
|
@@ -294,7 +294,7 @@ function summarizeFile(filePath) {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
async function runTail(options) {
|
|
297
|
-
const logDir = join(process.cwd(), ".trace-
|
|
297
|
+
const logDir = join(process.cwd(), ".llm-trace-logs");
|
|
298
298
|
if (!existsSync(logDir)) {
|
|
299
299
|
console.log("No active session.");
|
|
300
300
|
return;
|
|
@@ -343,15 +343,15 @@ function parseCliArgs(argv) {
|
|
|
343
343
|
|
|
344
344
|
//#endregion
|
|
345
345
|
//#region src/cli/index.ts
|
|
346
|
-
const HELP = `trace
|
|
346
|
+
const HELP = `llm-trace — Structured execution traces for LLM debugging
|
|
347
347
|
|
|
348
348
|
Usage:
|
|
349
|
-
trace
|
|
350
|
-
trace
|
|
351
|
-
trace
|
|
352
|
-
trace
|
|
353
|
-
trace
|
|
354
|
-
trace
|
|
349
|
+
llm-trace start Begin debugging session
|
|
350
|
+
llm-trace stop End session, delete traces
|
|
351
|
+
llm-trace status Show session info
|
|
352
|
+
llm-trace list [--errors] [--name <pattern>] [--last <n>] [--human]
|
|
353
|
+
llm-trace show <id> [--human]
|
|
354
|
+
llm-trace tail [--errors] [--name <pattern>]
|
|
355
355
|
`;
|
|
356
356
|
async function main() {
|
|
357
357
|
const { command, id, options } = parseCliArgs(process.argv.slice(2));
|
|
@@ -362,7 +362,7 @@ async function main() {
|
|
|
362
362
|
case "list": return runList(options);
|
|
363
363
|
case "show":
|
|
364
364
|
if (!id) {
|
|
365
|
-
console.error("Usage: trace
|
|
365
|
+
console.error("Usage: llm-trace show <id>");
|
|
366
366
|
process.exit(1);
|
|
367
367
|
}
|
|
368
368
|
return runShow(id, options);
|
package/dist/index.cjs
CHANGED
|
@@ -157,7 +157,7 @@ function createHttpWriter(serverUrl) {
|
|
|
157
157
|
});
|
|
158
158
|
} catch {
|
|
159
159
|
if (!warned) {
|
|
160
|
-
console.warn("[trace
|
|
160
|
+
console.warn("[llm-trace] Server not running, traces disabled.");
|
|
161
161
|
warned = true;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -188,7 +188,7 @@ function createHttpWriter(serverUrl) {
|
|
|
188
188
|
//#endregion
|
|
189
189
|
//#region src/index.ts
|
|
190
190
|
const defaultTracer = createTracer({
|
|
191
|
-
writer: createHttpWriter(`http://127.0.0.1:${(typeof process !== "undefined" ? parseInt(process.env?.
|
|
191
|
+
writer: createHttpWriter(`http://127.0.0.1:${(typeof process !== "undefined" ? parseInt(process.env?.LLM_TRACE_PORT || "", 10) : NaN) || 13579}`),
|
|
192
192
|
idGenerator: createCryptoIdGenerator(),
|
|
193
193
|
clock: createSystemClock()
|
|
194
194
|
});
|
package/dist/index.js
CHANGED
|
@@ -156,7 +156,7 @@ function createHttpWriter(serverUrl) {
|
|
|
156
156
|
});
|
|
157
157
|
} catch {
|
|
158
158
|
if (!warned) {
|
|
159
|
-
console.warn("[trace
|
|
159
|
+
console.warn("[llm-trace] Server not running, traces disabled.");
|
|
160
160
|
warned = true;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
@@ -187,7 +187,7 @@ function createHttpWriter(serverUrl) {
|
|
|
187
187
|
//#endregion
|
|
188
188
|
//#region src/index.ts
|
|
189
189
|
const defaultTracer = createTracer({
|
|
190
|
-
writer: createHttpWriter(`http://127.0.0.1:${(typeof process !== "undefined" ? parseInt(process.env?.
|
|
190
|
+
writer: createHttpWriter(`http://127.0.0.1:${(typeof process !== "undefined" ? parseInt(process.env?.LLM_TRACE_PORT || "", 10) : NaN) || 13579}`),
|
|
191
191
|
idGenerator: createCryptoIdGenerator(),
|
|
192
192
|
clock: createSystemClock()
|
|
193
193
|
});
|
package/dist/standalone.cjs
CHANGED
|
@@ -65,7 +65,7 @@ function createTraceServer(logDir) {
|
|
|
65
65
|
|
|
66
66
|
//#endregion
|
|
67
67
|
//#region src/server/standalone.ts
|
|
68
|
-
const port = parseInt(process.env.
|
|
68
|
+
const port = parseInt(process.env.LLM_TRACE_PORT || "13579", 10);
|
|
69
69
|
const logDir = process.env.TRACE_AI_DIR || "";
|
|
70
70
|
if (!logDir) {
|
|
71
71
|
console.error("TRACE_AI_DIR required");
|
package/dist/standalone.js
CHANGED
|
@@ -65,7 +65,7 @@ function createTraceServer(logDir) {
|
|
|
65
65
|
|
|
66
66
|
//#endregion
|
|
67
67
|
//#region src/server/standalone.ts
|
|
68
|
-
const port = parseInt(process.env.
|
|
68
|
+
const port = parseInt(process.env.LLM_TRACE_PORT || "13579", 10);
|
|
69
69
|
const logDir = process.env.TRACE_AI_DIR || "";
|
|
70
70
|
if (!logDir) {
|
|
71
71
|
console.error("TRACE_AI_DIR required");
|