llmtap 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/dist/dashboard/assets/BarChart-N8X7TKRf.js +1 -0
- package/dist/dashboard/assets/Costs-BhWdS2kw.js +4 -0
- package/dist/dashboard/assets/Dashboard-DnHLwJsN.js +9 -0
- package/dist/dashboard/assets/Models-5jWcyfAk.js +1 -0
- package/dist/dashboard/assets/Sessions-IXJPYjKe.js +3 -0
- package/dist/dashboard/assets/Settings-kxBLuIHe.js +6 -0
- package/dist/dashboard/assets/TraceDetail-DdKFps7x.js +18 -0
- package/dist/dashboard/assets/Traces-Cv9L_xTe.js +2 -0
- package/dist/dashboard/assets/chevron-down-B0xosrpl.js +1 -0
- package/dist/dashboard/assets/clock-CSGov78l.js +1 -0
- package/dist/dashboard/assets/coins-RIVI5-1a.js +1 -0
- package/dist/dashboard/assets/download-PygxZ--5.js +1 -0
- package/dist/dashboard/assets/fira-code-400-CHoedHDv.woff2 +0 -0
- package/dist/dashboard/assets/fira-sans-300-B2LrTgQS.woff2 +0 -0
- package/dist/dashboard/assets/fira-sans-400-a0AnQzuD.woff2 +0 -0
- package/dist/dashboard/assets/fira-sans-500-Bvbxc8ch.woff2 +0 -0
- package/dist/dashboard/assets/fira-sans-600-CXKlxLG9.woff2 +0 -0
- package/dist/dashboard/assets/fira-sans-700-CRhwpWTq.woff2 +0 -0
- package/dist/dashboard/assets/format-BiBDo3KS.js +1 -0
- package/dist/dashboard/assets/gauge-BBNKtqFH.js +1 -0
- package/dist/dashboard/assets/generateCategoricalChart-CylgFInn.js +61 -0
- package/dist/dashboard/assets/index-BBr9_AHg.js +22 -0
- package/dist/dashboard/assets/index-CIGK6hiV.css +1 -0
- package/dist/dashboard/assets/layers--K2XRXAx.js +1 -0
- package/dist/dashboard/assets/message-square-DdcqfsnD.js +1 -0
- package/dist/dashboard/assets/orbit-2k6lSwt7.js +1 -0
- package/dist/dashboard/assets/search-BwbP84dP.js +1 -0
- package/dist/dashboard/assets/zap-Ccrxhk2Q.js +1 -0
- package/dist/dashboard/index.html +13 -0
- package/dist/index.js +487 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/start.ts
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import open from "open";
|
|
12
|
+
import { startServer, getOtlpEndpoint } from "@llmtap/collector";
|
|
13
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
async function startCommand(options) {
|
|
15
|
+
const port = parseInt(options.port, 10);
|
|
16
|
+
const retentionDays = options.retention ? parseInt(options.retention, 10) : void 0;
|
|
17
|
+
const bundledPath = path.resolve(__dirname, "..", "dashboard");
|
|
18
|
+
const monorepoPath = path.resolve(__dirname, "..", "..", "dashboard", "dist");
|
|
19
|
+
const dashboardPath = fs.existsSync(bundledPath) ? bundledPath : monorepoPath;
|
|
20
|
+
console.log("");
|
|
21
|
+
console.log(chalk.bold.hex("#6366f1")(" LLMTap") + chalk.gray(" - DevTools for AI Agents"));
|
|
22
|
+
console.log("");
|
|
23
|
+
try {
|
|
24
|
+
const host = options.host;
|
|
25
|
+
if (host === "0.0.0.0") {
|
|
26
|
+
console.log(chalk.yellow(" \u26A0 Binding to 0.0.0.0 \u2014 the collector will be accessible from your network"));
|
|
27
|
+
console.log("");
|
|
28
|
+
}
|
|
29
|
+
const address = await startServer({
|
|
30
|
+
port,
|
|
31
|
+
host,
|
|
32
|
+
dashboardPath,
|
|
33
|
+
quiet: options.quiet,
|
|
34
|
+
demo: options.demo,
|
|
35
|
+
retentionDays
|
|
36
|
+
});
|
|
37
|
+
const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
|
|
38
|
+
console.log(chalk.green(" Ready!"));
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(` ${chalk.gray("Dashboard:")} ${chalk.cyan(url)}`);
|
|
41
|
+
console.log(` ${chalk.gray("API:")} ${chalk.cyan(`${url}/v1`)}`);
|
|
42
|
+
console.log(` ${chalk.gray("Health:")} ${chalk.cyan(`${url}/health`)}`);
|
|
43
|
+
if (retentionDays && retentionDays > 0) {
|
|
44
|
+
console.log(` ${chalk.gray("Retention:")} ${chalk.yellow(`${retentionDays} days`)}`);
|
|
45
|
+
}
|
|
46
|
+
const otlpTarget = getOtlpEndpoint();
|
|
47
|
+
if (otlpTarget) {
|
|
48
|
+
console.log(` ${chalk.gray("OTLP:")} ${chalk.cyan(otlpTarget)} ${chalk.green("(auto-forwarding)")}`);
|
|
49
|
+
}
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log(chalk.gray(" Press Ctrl+C to stop"));
|
|
52
|
+
console.log("");
|
|
53
|
+
if (options.open !== false) {
|
|
54
|
+
try {
|
|
55
|
+
await open(url);
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const error = err;
|
|
61
|
+
if (error.message?.includes("EADDRINUSE")) {
|
|
62
|
+
console.error(chalk.red(` Port ${port} is already in use.`));
|
|
63
|
+
console.error(chalk.gray(` Try: npx llmtap --port ${port + 1}`));
|
|
64
|
+
} else {
|
|
65
|
+
console.error(chalk.red(` Failed to start: ${error.message}`));
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/commands/reset.ts
|
|
72
|
+
import chalk2 from "chalk";
|
|
73
|
+
import { resetDb } from "@llmtap/collector";
|
|
74
|
+
async function resetCommand() {
|
|
75
|
+
try {
|
|
76
|
+
resetDb();
|
|
77
|
+
console.log(chalk2.green(" Data cleared successfully."));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const error = err;
|
|
80
|
+
console.error(chalk2.red(` Failed to reset: ${error.message}`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/commands/export.ts
|
|
86
|
+
import fs2 from "fs";
|
|
87
|
+
import path2 from "path";
|
|
88
|
+
import chalk3 from "chalk";
|
|
89
|
+
import { getDb } from "@llmtap/collector";
|
|
90
|
+
import { spansToOtlp } from "@llmtap/shared";
|
|
91
|
+
function safeParse(v) {
|
|
92
|
+
if (!v || typeof v !== "string") return void 0;
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(v);
|
|
95
|
+
} catch {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function rowToSpan(row) {
|
|
100
|
+
return {
|
|
101
|
+
...row,
|
|
102
|
+
parentSpanId: row.parentSpanId ?? void 0,
|
|
103
|
+
endTime: row.endTime ?? void 0,
|
|
104
|
+
duration: row.duration ?? void 0,
|
|
105
|
+
responseModel: row.responseModel ?? void 0,
|
|
106
|
+
temperature: row.temperature ?? void 0,
|
|
107
|
+
maxTokens: row.maxTokens ?? void 0,
|
|
108
|
+
topP: row.topP ?? void 0,
|
|
109
|
+
inputMessages: safeParse(row.inputMessages),
|
|
110
|
+
outputMessages: safeParse(row.outputMessages),
|
|
111
|
+
toolCalls: safeParse(row.toolCalls),
|
|
112
|
+
tags: safeParse(row.tags),
|
|
113
|
+
errorType: row.errorType ?? void 0,
|
|
114
|
+
errorMessage: row.errorMessage ?? void 0,
|
|
115
|
+
sessionId: row.sessionId ?? void 0,
|
|
116
|
+
userId: row.userId ?? void 0
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function exportCommand(options) {
|
|
120
|
+
try {
|
|
121
|
+
const db = getDb();
|
|
122
|
+
const limit = parseInt(options.limit, 10);
|
|
123
|
+
const format = options.format ?? "json";
|
|
124
|
+
if (format === "otlp") {
|
|
125
|
+
return await exportOtlp(db, limit, options);
|
|
126
|
+
}
|
|
127
|
+
const traces = db.prepare(
|
|
128
|
+
`
|
|
129
|
+
SELECT
|
|
130
|
+
traceId,
|
|
131
|
+
MIN(name) as name,
|
|
132
|
+
MIN(startTime) as startTime,
|
|
133
|
+
MAX(endTime) as endTime,
|
|
134
|
+
CASE WHEN SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) > 0
|
|
135
|
+
THEN 'error' ELSE 'ok' END as status,
|
|
136
|
+
COUNT(*) as spanCount,
|
|
137
|
+
COALESCE(SUM(totalTokens), 0) as totalTokens,
|
|
138
|
+
COALESCE(SUM(totalCost), 0) as totalCost
|
|
139
|
+
FROM spans
|
|
140
|
+
GROUP BY traceId
|
|
141
|
+
ORDER BY startTime DESC
|
|
142
|
+
LIMIT ?
|
|
143
|
+
`
|
|
144
|
+
).all(limit);
|
|
145
|
+
const getSpans = db.prepare("SELECT * FROM spans WHERE traceId = ? ORDER BY startTime ASC");
|
|
146
|
+
const exportData = traces.map((trace) => ({
|
|
147
|
+
...trace,
|
|
148
|
+
spans: getSpans.all(trace.traceId).map((span) => ({
|
|
149
|
+
...span,
|
|
150
|
+
inputMessages: safeParse(span.inputMessages),
|
|
151
|
+
outputMessages: safeParse(span.outputMessages),
|
|
152
|
+
toolCalls: safeParse(span.toolCalls),
|
|
153
|
+
tags: safeParse(span.tags)
|
|
154
|
+
}))
|
|
155
|
+
}));
|
|
156
|
+
let output;
|
|
157
|
+
let ext;
|
|
158
|
+
if (format === "csv") {
|
|
159
|
+
ext = "csv";
|
|
160
|
+
const headers = [
|
|
161
|
+
"traceId",
|
|
162
|
+
"name",
|
|
163
|
+
"status",
|
|
164
|
+
"spanCount",
|
|
165
|
+
"totalTokens",
|
|
166
|
+
"totalCost",
|
|
167
|
+
"startTime",
|
|
168
|
+
"endTime"
|
|
169
|
+
];
|
|
170
|
+
const rows = traces.map(
|
|
171
|
+
(t) => headers.map((h) => {
|
|
172
|
+
const val = t[h];
|
|
173
|
+
const s = String(val ?? "");
|
|
174
|
+
return s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
|
|
175
|
+
}).join(",")
|
|
176
|
+
);
|
|
177
|
+
output = [headers.join(","), ...rows].join("\n");
|
|
178
|
+
} else {
|
|
179
|
+
ext = "json";
|
|
180
|
+
output = JSON.stringify(exportData, null, 2);
|
|
181
|
+
}
|
|
182
|
+
const defaultOutput = options.output === "llmtap-export.json" && format === "csv" ? "llmtap-export.csv" : options.output;
|
|
183
|
+
const outputPath = path2.resolve(defaultOutput);
|
|
184
|
+
fs2.writeFileSync(outputPath, output);
|
|
185
|
+
console.log(chalk3.green(` Exported ${traces.length} traces as ${ext.toUpperCase()} to ${outputPath}`));
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const error = err;
|
|
188
|
+
console.error(chalk3.red(` Export failed: ${error.message}`));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function exportOtlp(db, limit, options) {
|
|
193
|
+
const rows = db.prepare("SELECT * FROM spans ORDER BY startTime DESC LIMIT ?").all(limit);
|
|
194
|
+
const spans = rows.map(rowToSpan);
|
|
195
|
+
const otlp = spansToOtlp(spans, options.service ?? "llmtap");
|
|
196
|
+
if (options.endpoint) {
|
|
197
|
+
console.log(chalk3.blue(` Forwarding ${spans.length} spans to ${options.endpoint}...`));
|
|
198
|
+
try {
|
|
199
|
+
const res = await fetch(options.endpoint, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { "Content-Type": "application/json" },
|
|
202
|
+
body: JSON.stringify(otlp),
|
|
203
|
+
signal: AbortSignal.timeout(3e4)
|
|
204
|
+
});
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
const body = await res.text().catch(() => "");
|
|
207
|
+
console.error(chalk3.red(` OTLP endpoint returned ${res.status}: ${body.slice(0, 200)}`));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
console.log(chalk3.green(` Successfully forwarded ${spans.length} spans to OTLP endpoint`));
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error(chalk3.red(` Failed to reach OTLP endpoint: ${err instanceof Error ? err.message : String(err)}`));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const outputPath = path2.resolve(
|
|
218
|
+
options.output === "llmtap-export.json" ? "llmtap-export.otlp.json" : options.output
|
|
219
|
+
);
|
|
220
|
+
fs2.writeFileSync(outputPath, JSON.stringify(otlp, null, 2));
|
|
221
|
+
console.log(chalk3.green(` Exported ${spans.length} spans as OTLP JSON to ${outputPath}`));
|
|
222
|
+
console.log(chalk3.dim(` Import into Jaeger, Grafana Tempo, Datadog, or any OTLP-compatible backend`));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/commands/status.ts
|
|
226
|
+
import chalk4 from "chalk";
|
|
227
|
+
async function statusCommand() {
|
|
228
|
+
try {
|
|
229
|
+
const res = await fetch("http://localhost:4781/v1/db-info", {
|
|
230
|
+
signal: AbortSignal.timeout(3e3)
|
|
231
|
+
});
|
|
232
|
+
if (!res.ok) {
|
|
233
|
+
console.error(chalk4.red(" Collector responded with an error."));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const info = await res.json();
|
|
237
|
+
const formatBytes = (bytes) => {
|
|
238
|
+
if (bytes === 0) return "0 B";
|
|
239
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
240
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
241
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
242
|
+
};
|
|
243
|
+
console.log("");
|
|
244
|
+
console.log(chalk4.bold.hex("#6366f1")(" LLMTap") + chalk4.green(" \u2014 Running"));
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log(` ${chalk4.gray("Spans:")} ${chalk4.white(info.spanCount.toLocaleString())}`);
|
|
247
|
+
console.log(` ${chalk4.gray("Traces:")} ${chalk4.white(info.traceCount.toLocaleString())}`);
|
|
248
|
+
console.log(` ${chalk4.gray("DB size:")} ${chalk4.white(formatBytes(info.sizeBytes))}`);
|
|
249
|
+
console.log(` ${chalk4.gray("WAL mode:")} ${chalk4.white(info.walMode.toUpperCase())}`);
|
|
250
|
+
console.log(` ${chalk4.gray("DB path:")} ${chalk4.white(info.path)}`);
|
|
251
|
+
if (info.oldestSpan && info.newestSpan) {
|
|
252
|
+
const oldest = new Date(info.oldestSpan).toLocaleString();
|
|
253
|
+
const newest = new Date(info.newestSpan).toLocaleString();
|
|
254
|
+
console.log(` ${chalk4.gray("Data range:")} ${chalk4.white(`${oldest} \u2014 ${newest}`)}`);
|
|
255
|
+
}
|
|
256
|
+
console.log("");
|
|
257
|
+
} catch {
|
|
258
|
+
console.log("");
|
|
259
|
+
console.log(chalk4.bold.hex("#6366f1")(" LLMTap") + chalk4.red(" \u2014 Not running"));
|
|
260
|
+
console.log("");
|
|
261
|
+
console.log(chalk4.gray(" Start the collector with: ") + chalk4.cyan("npx llmtap"));
|
|
262
|
+
console.log("");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/commands/tail.ts
|
|
267
|
+
import chalk5 from "chalk";
|
|
268
|
+
async function tailCommand(options) {
|
|
269
|
+
const format = options.format ?? "pretty";
|
|
270
|
+
const url = "http://localhost:4781/v1/stream";
|
|
271
|
+
console.log("");
|
|
272
|
+
console.log(
|
|
273
|
+
chalk5.bold.hex("#6366f1")(" LLMTap") + chalk5.gray(" \u2014 Streaming traces in real-time")
|
|
274
|
+
);
|
|
275
|
+
console.log(chalk5.gray(" Press Ctrl+C to stop"));
|
|
276
|
+
console.log("");
|
|
277
|
+
try {
|
|
278
|
+
const res = await fetch(url, {
|
|
279
|
+
headers: { Accept: "text/event-stream" }
|
|
280
|
+
});
|
|
281
|
+
if (!res.ok || !res.body) {
|
|
282
|
+
console.error(chalk5.red(" Could not connect to collector."));
|
|
283
|
+
console.error(chalk5.gray(" Make sure the collector is running: npx llmtap"));
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
const decoder = new TextDecoder();
|
|
287
|
+
const reader = res.body.getReader();
|
|
288
|
+
let buffer = "";
|
|
289
|
+
while (true) {
|
|
290
|
+
const { done, value } = await reader.read();
|
|
291
|
+
if (done) break;
|
|
292
|
+
buffer += decoder.decode(value, { stream: true });
|
|
293
|
+
const lines = buffer.split("\n");
|
|
294
|
+
buffer = lines.pop() ?? "";
|
|
295
|
+
for (const line of lines) {
|
|
296
|
+
if (!line.startsWith("data: ")) continue;
|
|
297
|
+
const json = line.slice(6).trim();
|
|
298
|
+
if (!json) continue;
|
|
299
|
+
try {
|
|
300
|
+
const span = JSON.parse(json);
|
|
301
|
+
if (format === "json") {
|
|
302
|
+
console.log(json);
|
|
303
|
+
} else {
|
|
304
|
+
const dur = span.duration ? `${span.duration}ms` : "...";
|
|
305
|
+
const cost = span.totalCost > 0 ? `$${span.totalCost.toFixed(4)}` : "$0";
|
|
306
|
+
const statusIcon = span.status === "error" ? chalk5.red("ERR") : chalk5.green("OK ");
|
|
307
|
+
console.log(
|
|
308
|
+
` ${statusIcon} ${chalk5.gray(dur.padStart(7))} ${chalk5.cyan(span.providerName.padEnd(10))} ${chalk5.white(span.requestModel.padEnd(24))} ${chalk5.yellow(String(span.totalTokens).padStart(6) + " tok")} ${chalk5.green(cost.padStart(8))} ${chalk5.gray(span.name)}`
|
|
309
|
+
);
|
|
310
|
+
if (span.errorMessage) {
|
|
311
|
+
console.log(
|
|
312
|
+
` ${chalk5.red("\u2192 " + span.errorMessage.slice(0, 120))}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
console.error(chalk5.red(" Could not connect to collector."));
|
|
322
|
+
console.error(chalk5.gray(" Make sure the collector is running: npx llmtap"));
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/commands/doctor.ts
|
|
328
|
+
import chalk6 from "chalk";
|
|
329
|
+
async function doctorCommand() {
|
|
330
|
+
console.log("");
|
|
331
|
+
console.log(chalk6.bold.hex("#6366f1")(" LLMTap Doctor"));
|
|
332
|
+
console.log(chalk6.gray(" Checking your setup..."));
|
|
333
|
+
console.log("");
|
|
334
|
+
const checks = [];
|
|
335
|
+
const nodeVersion = process.version;
|
|
336
|
+
const major = parseInt(nodeVersion.slice(1), 10);
|
|
337
|
+
if (major >= 18) {
|
|
338
|
+
checks.push({ label: "Node.js version", status: "ok", detail: nodeVersion });
|
|
339
|
+
} else {
|
|
340
|
+
checks.push({ label: "Node.js version", status: "fail", detail: `${nodeVersion} (requires >= 18)` });
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const res = await fetch("http://localhost:4781/health", {
|
|
344
|
+
signal: AbortSignal.timeout(3e3)
|
|
345
|
+
});
|
|
346
|
+
if (res.ok) {
|
|
347
|
+
checks.push({ label: "Collector", status: "ok", detail: "Running on port 4781" });
|
|
348
|
+
} else {
|
|
349
|
+
checks.push({ label: "Collector", status: "fail", detail: `Responded with HTTP ${res.status}` });
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
checks.push({ label: "Collector", status: "warn", detail: "Not running (start with: npx llmtap)" });
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const res = await fetch("http://localhost:4781/v1/db-info", {
|
|
356
|
+
signal: AbortSignal.timeout(3e3)
|
|
357
|
+
});
|
|
358
|
+
if (res.ok) {
|
|
359
|
+
const info = await res.json();
|
|
360
|
+
checks.push({
|
|
361
|
+
label: "Database",
|
|
362
|
+
status: info.walMode === "wal" ? "ok" : "warn",
|
|
363
|
+
detail: `${info.spanCount} spans, WAL=${info.walMode.toUpperCase()}`
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
} catch {
|
|
367
|
+
checks.push({ label: "Database", status: "warn", detail: "Cannot check (collector not running)" });
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
await import("@llmtap/sdk");
|
|
371
|
+
checks.push({ label: "@llmtap/sdk", status: "ok", detail: "Installed" });
|
|
372
|
+
} catch {
|
|
373
|
+
checks.push({ label: "@llmtap/sdk", status: "warn", detail: "Not found in current project (install with: npm i @llmtap/sdk)" });
|
|
374
|
+
}
|
|
375
|
+
if (checks.find((c) => c.label === "Collector")?.status !== "ok") {
|
|
376
|
+
try {
|
|
377
|
+
const res = await fetch("http://localhost:4781", {
|
|
378
|
+
signal: AbortSignal.timeout(1e3)
|
|
379
|
+
});
|
|
380
|
+
if (!res.ok) {
|
|
381
|
+
checks.push({ label: "Port 4781", status: "warn", detail: "Something is running but not LLMTap" });
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
checks.push({ label: "Port 4781", status: "ok", detail: "Available" });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (const check of checks) {
|
|
388
|
+
const icon = check.status === "ok" ? chalk6.green(" \u2713") : check.status === "warn" ? chalk6.yellow(" !") : chalk6.red(" \u2717");
|
|
389
|
+
const label = chalk6.white(check.label.padEnd(20));
|
|
390
|
+
const detail = check.status === "ok" ? chalk6.gray(check.detail) : check.status === "warn" ? chalk6.yellow(check.detail) : chalk6.red(check.detail);
|
|
391
|
+
console.log(`${icon} ${label} ${detail}`);
|
|
392
|
+
}
|
|
393
|
+
const failCount = checks.filter((c) => c.status === "fail").length;
|
|
394
|
+
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
395
|
+
console.log("");
|
|
396
|
+
if (failCount > 0) {
|
|
397
|
+
console.log(chalk6.red(` ${failCount} issue(s) found. Fix them to use LLMTap.`));
|
|
398
|
+
} else if (warnCount > 0) {
|
|
399
|
+
console.log(chalk6.yellow(` ${warnCount} warning(s). Everything should still work.`));
|
|
400
|
+
} else {
|
|
401
|
+
console.log(chalk6.green(" All checks passed!"));
|
|
402
|
+
}
|
|
403
|
+
console.log("");
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/commands/stats.ts
|
|
407
|
+
import chalk7 from "chalk";
|
|
408
|
+
async function statsCommand(options) {
|
|
409
|
+
const period = Number(options.period ?? "24");
|
|
410
|
+
const host = options.host ?? "http://localhost:4781";
|
|
411
|
+
try {
|
|
412
|
+
const res = await fetch(`${host}/v1/stats?period=${period}`);
|
|
413
|
+
if (!res.ok) {
|
|
414
|
+
console.error(chalk7.red(`Error: Collector returned HTTP ${res.status}`));
|
|
415
|
+
console.error(chalk7.dim("Is the collector running? Try: npx llmtap start"));
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
const stats = await res.json();
|
|
419
|
+
console.log("");
|
|
420
|
+
console.log(chalk7.bold.white(` LLMTap Stats \u2014 Last ${period}h`));
|
|
421
|
+
console.log(chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
422
|
+
console.log("");
|
|
423
|
+
const errorPct = (stats.errorRate * 100).toFixed(1);
|
|
424
|
+
console.log(` ${chalk7.dim("Traces")} ${chalk7.bold.white(String(stats.totalTraces))}`);
|
|
425
|
+
console.log(` ${chalk7.dim("Spans")} ${chalk7.bold.white(String(stats.totalSpans))}`);
|
|
426
|
+
console.log(` ${chalk7.dim("Tokens")} ${chalk7.bold.white(stats.totalTokens.toLocaleString())}`);
|
|
427
|
+
console.log(` ${chalk7.dim("Total Cost")} ${chalk7.bold.green("$" + stats.totalCost.toFixed(4))}`);
|
|
428
|
+
console.log(` ${chalk7.dim("Avg Latency")} ${chalk7.white(formatMs(stats.avgDuration))}`);
|
|
429
|
+
console.log(
|
|
430
|
+
` ${chalk7.dim("Error Rate")} ${stats.errorRate > 0.05 ? chalk7.bold.red(errorPct + "%") : chalk7.green(errorPct + "%")} ${chalk7.dim(`(${stats.errorCount} errors)`)}`
|
|
431
|
+
);
|
|
432
|
+
if (stats.byProvider.length > 0) {
|
|
433
|
+
console.log("");
|
|
434
|
+
console.log(chalk7.bold.white(" Top Providers"));
|
|
435
|
+
console.log(chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
436
|
+
for (const p of stats.byProvider.slice(0, 5)) {
|
|
437
|
+
const bar = makeBar(p.totalCost, stats.totalCost, 20);
|
|
438
|
+
console.log(
|
|
439
|
+
` ${chalk7.cyan(p.provider.padEnd(12))} ${chalk7.dim(String(p.spanCount).padStart(5) + " calls")} ${chalk7.green("$" + p.totalCost.toFixed(4).padStart(8))} ${chalk7.dim(bar)}`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (stats.byModel.length > 0) {
|
|
444
|
+
console.log("");
|
|
445
|
+
console.log(chalk7.bold.white(" Top Models"));
|
|
446
|
+
console.log(chalk7.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
447
|
+
for (const m of stats.byModel.slice(0, 8)) {
|
|
448
|
+
const bar = makeBar(m.totalCost, stats.totalCost, 20);
|
|
449
|
+
console.log(
|
|
450
|
+
` ${chalk7.white(m.model.padEnd(28).slice(0, 28))} ${chalk7.dim(String(m.spanCount).padStart(5) + " calls")} ${chalk7.green("$" + m.totalCost.toFixed(4).padStart(8))} ${chalk7.dim(bar)}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
console.log("");
|
|
455
|
+
} catch (err) {
|
|
456
|
+
if (err instanceof TypeError && err.cause) {
|
|
457
|
+
console.error(chalk7.red("Error: Cannot connect to collector"));
|
|
458
|
+
console.error(chalk7.dim("Is the collector running? Try: npx llmtap start"));
|
|
459
|
+
} else {
|
|
460
|
+
console.error(chalk7.red("Error:"), err instanceof Error ? err.message : err);
|
|
461
|
+
}
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function formatMs(ms) {
|
|
466
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
467
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
468
|
+
}
|
|
469
|
+
function makeBar(value, total, width) {
|
|
470
|
+
if (total <= 0) return "";
|
|
471
|
+
const filled = Math.max(Math.round(value / total * width), 1);
|
|
472
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/index.ts
|
|
476
|
+
import { VERSION } from "@llmtap/shared";
|
|
477
|
+
var program = new Command();
|
|
478
|
+
program.name("llmtap").description("DevTools for AI Agents - See every LLM call, trace agent workflows, track costs").version(VERSION);
|
|
479
|
+
program.command("start", { isDefault: true }).description("Start the LLMTap collector and dashboard").option("-p, --port <port>", "Port number", "4781").option("-H, --host <host>", "Host to bind to (use 0.0.0.0 to expose to network)", "127.0.0.1").option("-q, --quiet", "Suppress server logs").option("--demo", "Seed demo data on startup").option("--no-open", "Don't open browser automatically").option("-r, --retention <days>", "Auto-delete data older than N days (0 = keep forever)").action(startCommand);
|
|
480
|
+
program.command("status").description("Show collector status, database info, and span count").action(statusCommand);
|
|
481
|
+
program.command("reset").description("Clear all stored data").action(resetCommand);
|
|
482
|
+
program.command("export").description("Export traces as JSON, CSV, or OTLP").option("-o, --output <path>", "Output file path", "llmtap-export.json").option("-f, --format <format>", "Output format (json, csv, or otlp)", "json").option("-l, --limit <count>", "Number of traces/spans to export", "100").option("-e, --endpoint <url>", "OTLP endpoint to forward spans to (e.g. http://localhost:4318/v1/traces)").option("-s, --service <name>", "service.name for OTLP export", "llmtap").action(exportCommand);
|
|
483
|
+
program.command("tail").description("Stream traces to terminal in real-time").option("-f, --format <format>", "Output format (pretty or json)", "pretty").action(tailCommand);
|
|
484
|
+
program.command("doctor").description("Diagnose common setup issues").action(doctorCommand);
|
|
485
|
+
program.command("stats").description("Show quick terminal stats (cost, models, errors)").option("-p, --period <hours>", "Time period in hours", "24").option("--host <url>", "Collector URL", "http://localhost:4781").action(statsCommand);
|
|
486
|
+
program.parse();
|
|
487
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/start.ts","../src/commands/reset.ts","../src/commands/export.ts","../src/commands/status.ts","../src/commands/tail.ts","../src/commands/doctor.ts","../src/commands/stats.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { startCommand } from \"./commands/start.js\";\nimport { resetCommand } from \"./commands/reset.js\";\nimport { exportCommand } from \"./commands/export.js\";\nimport { statusCommand } from \"./commands/status.js\";\nimport { tailCommand } from \"./commands/tail.js\";\nimport { doctorCommand } from \"./commands/doctor.js\";\nimport { statsCommand } from \"./commands/stats.js\";\nimport { VERSION } from \"@llmtap/shared\";\n\nconst program = new Command();\n\nprogram\n .name(\"llmtap\")\n .description(\"DevTools for AI Agents - See every LLM call, trace agent workflows, track costs\")\n .version(VERSION);\n\nprogram\n .command(\"start\", { isDefault: true })\n .description(\"Start the LLMTap collector and dashboard\")\n .option(\"-p, --port <port>\", \"Port number\", \"4781\")\n .option(\"-H, --host <host>\", \"Host to bind to (use 0.0.0.0 to expose to network)\", \"127.0.0.1\")\n .option(\"-q, --quiet\", \"Suppress server logs\")\n .option(\"--demo\", \"Seed demo data on startup\")\n .option(\"--no-open\", \"Don't open browser automatically\")\n .option(\"-r, --retention <days>\", \"Auto-delete data older than N days (0 = keep forever)\")\n .action(startCommand);\n\nprogram\n .command(\"status\")\n .description(\"Show collector status, database info, and span count\")\n .action(statusCommand);\n\nprogram\n .command(\"reset\")\n .description(\"Clear all stored data\")\n .action(resetCommand);\n\nprogram\n .command(\"export\")\n .description(\"Export traces as JSON, CSV, or OTLP\")\n .option(\"-o, --output <path>\", \"Output file path\", \"llmtap-export.json\")\n .option(\"-f, --format <format>\", \"Output format (json, csv, or otlp)\", \"json\")\n .option(\"-l, --limit <count>\", \"Number of traces/spans to export\", \"100\")\n .option(\"-e, --endpoint <url>\", \"OTLP endpoint to forward spans to (e.g. http://localhost:4318/v1/traces)\")\n .option(\"-s, --service <name>\", \"service.name for OTLP export\", \"llmtap\")\n .action(exportCommand);\n\nprogram\n .command(\"tail\")\n .description(\"Stream traces to terminal in real-time\")\n .option(\"-f, --format <format>\", \"Output format (pretty or json)\", \"pretty\")\n .action(tailCommand);\n\nprogram\n .command(\"doctor\")\n .description(\"Diagnose common setup issues\")\n .action(doctorCommand);\n\nprogram\n .command(\"stats\")\n .description(\"Show quick terminal stats (cost, models, errors)\")\n .option(\"-p, --period <hours>\", \"Time period in hours\", \"24\")\n .option(\"--host <url>\", \"Collector URL\", \"http://localhost:4781\")\n .action(statsCommand);\n\nprogram.parse();\n","import path from \"node:path\";\nimport fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport open from \"open\";\nimport { startServer, getOtlpEndpoint } from \"@llmtap/collector\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\ninterface StartOptions {\n port: string;\n host: string;\n quiet?: boolean;\n open?: boolean;\n demo?: boolean;\n retention?: string;\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n const port = parseInt(options.port, 10);\n const retentionDays = options.retention\n ? parseInt(options.retention, 10)\n : undefined;\n\n // Resolve dashboard dist path\n // 1. Bundled inside CLI dist (for npm publish): dist/dashboard/\n // 2. Monorepo sibling (for local dev): ../dashboard/dist/\n const bundledPath = path.resolve(__dirname, \"..\", \"dashboard\");\n const monorepoPath = path.resolve(__dirname, \"..\", \"..\", \"dashboard\", \"dist\");\n const dashboardPath = fs.existsSync(bundledPath) ? bundledPath : monorepoPath;\n\n console.log(\"\");\n console.log(chalk.bold.hex(\"#6366f1\")(\" LLMTap\") + chalk.gray(\" - DevTools for AI Agents\"));\n console.log(\"\");\n\n try {\n const host = options.host;\n\n if (host === \"0.0.0.0\") {\n console.log(chalk.yellow(\" ⚠ Binding to 0.0.0.0 — the collector will be accessible from your network\"));\n console.log(\"\");\n }\n\n const address = await startServer({\n port,\n host,\n dashboardPath,\n quiet: options.quiet,\n demo: options.demo,\n retentionDays,\n });\n\n const url = `http://${host === \"0.0.0.0\" ? \"localhost\" : host}:${port}`;\n\n console.log(chalk.green(\" Ready!\"));\n console.log(\"\");\n console.log(` ${chalk.gray(\"Dashboard:\")} ${chalk.cyan(url)}`);\n console.log(` ${chalk.gray(\"API:\")} ${chalk.cyan(`${url}/v1`)}`);\n console.log(` ${chalk.gray(\"Health:\")} ${chalk.cyan(`${url}/health`)}`);\n if (retentionDays && retentionDays > 0) {\n console.log(` ${chalk.gray(\"Retention:\")} ${chalk.yellow(`${retentionDays} days`)}`);\n }\n const otlpTarget = getOtlpEndpoint();\n if (otlpTarget) {\n console.log(` ${chalk.gray(\"OTLP:\")} ${chalk.cyan(otlpTarget)} ${chalk.green(\"(auto-forwarding)\")}`);\n }\n console.log(\"\");\n console.log(chalk.gray(\" Press Ctrl+C to stop\"));\n console.log(\"\");\n\n // Open browser\n if (options.open !== false) {\n try {\n await open(url);\n } catch {\n // Ignore if browser can't be opened\n }\n }\n } catch (err: unknown) {\n const error = err as Error;\n if (error.message?.includes(\"EADDRINUSE\")) {\n console.error(chalk.red(` Port ${port} is already in use.`));\n console.error(chalk.gray(` Try: npx llmtap --port ${port + 1}`));\n } else {\n console.error(chalk.red(` Failed to start: ${error.message}`));\n }\n process.exit(1);\n }\n}\n","import chalk from \"chalk\";\nimport { resetDb } from \"@llmtap/collector\";\n\nexport async function resetCommand(): Promise<void> {\n try {\n resetDb();\n console.log(chalk.green(\" Data cleared successfully.\"));\n } catch (err: unknown) {\n const error = err as Error;\n console.error(chalk.red(` Failed to reset: ${error.message}`));\n process.exit(1);\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport chalk from \"chalk\";\nimport { getDb } from \"@llmtap/collector\";\nimport { spansToOtlp } from \"@llmtap/shared\";\nimport type { Span } from \"@llmtap/shared\";\n\ninterface ExportOptions {\n output: string;\n limit: string;\n format: string;\n endpoint?: string;\n service?: string;\n}\n\nfunction safeParse(v: unknown): unknown {\n if (!v || typeof v !== \"string\") return undefined;\n try { return JSON.parse(v); } catch { return undefined; }\n}\n\nfunction rowToSpan(row: Record<string, unknown>): Span {\n return {\n ...row,\n parentSpanId: (row.parentSpanId as string) ?? undefined,\n endTime: (row.endTime as number) ?? undefined,\n duration: (row.duration as number) ?? undefined,\n responseModel: (row.responseModel as string) ?? undefined,\n temperature: (row.temperature as number) ?? undefined,\n maxTokens: (row.maxTokens as number) ?? undefined,\n topP: (row.topP as number) ?? undefined,\n inputMessages: safeParse(row.inputMessages) as Span[\"inputMessages\"],\n outputMessages: safeParse(row.outputMessages) as Span[\"outputMessages\"],\n toolCalls: safeParse(row.toolCalls) as Span[\"toolCalls\"],\n tags: safeParse(row.tags) as Record<string, string> | undefined,\n errorType: (row.errorType as string) ?? undefined,\n errorMessage: (row.errorMessage as string) ?? undefined,\n sessionId: (row.sessionId as string) ?? undefined,\n userId: (row.userId as string) ?? undefined,\n } as Span;\n}\n\nexport async function exportCommand(options: ExportOptions): Promise<void> {\n try {\n const db = getDb();\n const limit = parseInt(options.limit, 10);\n const format = options.format ?? \"json\";\n\n if (format === \"otlp\") {\n return await exportOtlp(db, limit, options);\n }\n\n // Get traces\n const traces = db\n .prepare(\n `\n SELECT\n traceId,\n MIN(name) as name,\n MIN(startTime) as startTime,\n MAX(endTime) as endTime,\n CASE WHEN SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) > 0\n THEN 'error' ELSE 'ok' END as status,\n COUNT(*) as spanCount,\n COALESCE(SUM(totalTokens), 0) as totalTokens,\n COALESCE(SUM(totalCost), 0) as totalCost\n FROM spans\n GROUP BY traceId\n ORDER BY startTime DESC\n LIMIT ?\n `\n )\n .all(limit) as Array<Record<string, unknown>>;\n\n // Get spans for each trace\n const getSpans = db.prepare(\"SELECT * FROM spans WHERE traceId = ? ORDER BY startTime ASC\");\n const exportData = traces.map((trace) => ({\n ...trace,\n spans: (getSpans.all(trace.traceId) as Array<Record<string, unknown>>).map((span) => ({\n ...span,\n inputMessages: safeParse(span.inputMessages),\n outputMessages: safeParse(span.outputMessages),\n toolCalls: safeParse(span.toolCalls),\n tags: safeParse(span.tags),\n })),\n }));\n\n let output: string;\n let ext: string;\n\n if (format === \"csv\") {\n ext = \"csv\";\n const headers = [\n \"traceId\", \"name\", \"status\", \"spanCount\", \"totalTokens\", \"totalCost\",\n \"startTime\", \"endTime\",\n ];\n const rows = traces.map((t) =>\n headers.map((h) => {\n const val = t[h];\n const s = String(val ?? \"\");\n return s.includes(\",\") || s.includes('\"') || s.includes(\"\\n\") ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n }).join(\",\")\n );\n output = [headers.join(\",\"), ...rows].join(\"\\n\");\n } else {\n ext = \"json\";\n output = JSON.stringify(exportData, null, 2);\n }\n\n const defaultOutput = options.output === \"llmtap-export.json\" && format === \"csv\"\n ? \"llmtap-export.csv\"\n : options.output;\n const outputPath = path.resolve(defaultOutput);\n fs.writeFileSync(outputPath, output);\n\n console.log(chalk.green(` Exported ${traces.length} traces as ${ext.toUpperCase()} to ${outputPath}`));\n } catch (err: unknown) {\n const error = err as Error;\n console.error(chalk.red(` Export failed: ${error.message}`));\n process.exit(1);\n }\n}\n\nasync function exportOtlp(\n db: ReturnType<typeof getDb>,\n limit: number,\n options: ExportOptions\n): Promise<void> {\n const rows = db\n .prepare(\"SELECT * FROM spans ORDER BY startTime DESC LIMIT ?\")\n .all(limit) as Array<Record<string, unknown>>;\n\n const spans = rows.map(rowToSpan);\n const otlp = spansToOtlp(spans, options.service ?? \"llmtap\");\n\n // If --endpoint is provided, forward to OTLP collector\n if (options.endpoint) {\n console.log(chalk.blue(` Forwarding ${spans.length} spans to ${options.endpoint}...`));\n try {\n const res = await fetch(options.endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(otlp),\n signal: AbortSignal.timeout(30000),\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n console.error(chalk.red(` OTLP endpoint returned ${res.status}: ${body.slice(0, 200)}`));\n process.exit(1);\n }\n console.log(chalk.green(` Successfully forwarded ${spans.length} spans to OTLP endpoint`));\n } catch (err) {\n console.error(chalk.red(` Failed to reach OTLP endpoint: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(1);\n }\n return;\n }\n\n // Otherwise write to file\n const outputPath = path.resolve(\n options.output === \"llmtap-export.json\" ? \"llmtap-export.otlp.json\" : options.output\n );\n fs.writeFileSync(outputPath, JSON.stringify(otlp, null, 2));\n console.log(chalk.green(` Exported ${spans.length} spans as OTLP JSON to ${outputPath}`));\n console.log(chalk.dim(` Import into Jaeger, Grafana Tempo, Datadog, or any OTLP-compatible backend`));\n}\n","import chalk from \"chalk\";\r\n\r\nexport async function statusCommand(): Promise<void> {\r\n try {\r\n const res = await fetch(\"http://localhost:4781/v1/db-info\", {\r\n signal: AbortSignal.timeout(3000),\r\n });\r\n\r\n if (!res.ok) {\r\n console.error(chalk.red(\" Collector responded with an error.\"));\r\n process.exit(1);\r\n }\r\n\r\n const info = (await res.json()) as {\r\n path: string;\r\n sizeBytes: number;\r\n spanCount: number;\r\n traceCount: number;\r\n oldestSpan: number | null;\r\n newestSpan: number | null;\r\n walMode: string;\r\n };\r\n\r\n const formatBytes = (bytes: number) => {\r\n if (bytes === 0) return \"0 B\";\r\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\r\n const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\r\n return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;\r\n };\r\n\r\n console.log(\"\");\r\n console.log(chalk.bold.hex(\"#6366f1\")(\" LLMTap\") + chalk.green(\" — Running\"));\r\n console.log(\"\");\r\n console.log(` ${chalk.gray(\"Spans:\")} ${chalk.white(info.spanCount.toLocaleString())}`);\r\n console.log(` ${chalk.gray(\"Traces:\")} ${chalk.white(info.traceCount.toLocaleString())}`);\r\n console.log(` ${chalk.gray(\"DB size:\")} ${chalk.white(formatBytes(info.sizeBytes))}`);\r\n console.log(` ${chalk.gray(\"WAL mode:\")} ${chalk.white(info.walMode.toUpperCase())}`);\r\n console.log(` ${chalk.gray(\"DB path:\")} ${chalk.white(info.path)}`);\r\n\r\n if (info.oldestSpan && info.newestSpan) {\r\n const oldest = new Date(info.oldestSpan).toLocaleString();\r\n const newest = new Date(info.newestSpan).toLocaleString();\r\n console.log(` ${chalk.gray(\"Data range:\")} ${chalk.white(`${oldest} — ${newest}`)}`);\r\n }\r\n console.log(\"\");\r\n } catch {\r\n console.log(\"\");\r\n console.log(chalk.bold.hex(\"#6366f1\")(\" LLMTap\") + chalk.red(\" — Not running\"));\r\n console.log(\"\");\r\n console.log(chalk.gray(\" Start the collector with: \") + chalk.cyan(\"npx llmtap\"));\r\n console.log(\"\");\r\n }\r\n}\r\n","import chalk from \"chalk\";\r\n\r\ninterface TailOptions {\r\n format: string;\r\n}\r\n\r\nexport async function tailCommand(options: TailOptions): Promise<void> {\r\n const format = options.format ?? \"pretty\";\r\n const url = \"http://localhost:4781/v1/stream\";\r\n\r\n console.log(\"\");\r\n console.log(\r\n chalk.bold.hex(\"#6366f1\")(\" LLMTap\") +\r\n chalk.gray(\" — Streaming traces in real-time\")\r\n );\r\n console.log(chalk.gray(\" Press Ctrl+C to stop\"));\r\n console.log(\"\");\r\n\r\n try {\r\n const res = await fetch(url, {\r\n headers: { Accept: \"text/event-stream\" },\r\n });\r\n\r\n if (!res.ok || !res.body) {\r\n console.error(chalk.red(\" Could not connect to collector.\"));\r\n console.error(chalk.gray(\" Make sure the collector is running: npx llmtap\"));\r\n process.exit(1);\r\n }\r\n\r\n const decoder = new TextDecoder();\r\n const reader = res.body.getReader();\r\n let buffer = \"\";\r\n\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n\r\n buffer += decoder.decode(value, { stream: true });\r\n const lines = buffer.split(\"\\n\");\r\n buffer = lines.pop() ?? \"\";\r\n\r\n for (const line of lines) {\r\n if (!line.startsWith(\"data: \")) continue;\r\n const json = line.slice(6).trim();\r\n if (!json) continue;\r\n\r\n try {\r\n const span = JSON.parse(json) as {\r\n spanId: string;\r\n traceId: string;\r\n name: string;\r\n providerName: string;\r\n requestModel: string;\r\n duration?: number;\r\n totalTokens: number;\r\n totalCost: number;\r\n status: string;\r\n errorMessage?: string;\r\n };\r\n\r\n if (format === \"json\") {\r\n console.log(json);\r\n } else {\r\n const dur = span.duration ? `${span.duration}ms` : \"...\";\r\n const cost =\r\n span.totalCost > 0 ? `$${span.totalCost.toFixed(4)}` : \"$0\";\r\n const statusIcon =\r\n span.status === \"error\" ? chalk.red(\"ERR\") : chalk.green(\"OK \");\r\n\r\n console.log(\r\n ` ${statusIcon} ${chalk.gray(dur.padStart(7))} ${chalk.cyan(span.providerName.padEnd(10))} ${chalk.white(span.requestModel.padEnd(24))} ${chalk.yellow(String(span.totalTokens).padStart(6) + \" tok\")} ${chalk.green(cost.padStart(8))} ${chalk.gray(span.name)}`\r\n );\r\n if (span.errorMessage) {\r\n console.log(\r\n ` ${chalk.red(\"→ \" + span.errorMessage.slice(0, 120))}`\r\n );\r\n }\r\n }\r\n } catch {\r\n // Skip malformed events\r\n }\r\n }\r\n }\r\n } catch {\r\n console.error(chalk.red(\" Could not connect to collector.\"));\r\n console.error(chalk.gray(\" Make sure the collector is running: npx llmtap\"));\r\n process.exit(1);\r\n }\r\n}\r\n","import chalk from \"chalk\";\r\n\r\nexport async function doctorCommand(): Promise<void> {\r\n console.log(\"\");\r\n console.log(chalk.bold.hex(\"#6366f1\")(\" LLMTap Doctor\"));\r\n console.log(chalk.gray(\" Checking your setup...\"));\r\n console.log(\"\");\r\n\r\n const checks: { label: string; status: \"ok\" | \"warn\" | \"fail\"; detail: string }[] = [];\r\n\r\n // Check 1: Node.js version\r\n const nodeVersion = process.version;\r\n const major = parseInt(nodeVersion.slice(1), 10);\r\n if (major >= 18) {\r\n checks.push({ label: \"Node.js version\", status: \"ok\", detail: nodeVersion });\r\n } else {\r\n checks.push({ label: \"Node.js version\", status: \"fail\", detail: `${nodeVersion} (requires >= 18)` });\r\n }\r\n\r\n // Check 2: Collector running\r\n try {\r\n const res = await fetch(\"http://localhost:4781/health\", {\r\n signal: AbortSignal.timeout(3000),\r\n });\r\n if (res.ok) {\r\n checks.push({ label: \"Collector\", status: \"ok\", detail: \"Running on port 4781\" });\r\n } else {\r\n checks.push({ label: \"Collector\", status: \"fail\", detail: `Responded with HTTP ${res.status}` });\r\n }\r\n } catch {\r\n checks.push({ label: \"Collector\", status: \"warn\", detail: \"Not running (start with: npx llmtap)\" });\r\n }\r\n\r\n // Check 3: Database accessible\r\n try {\r\n const res = await fetch(\"http://localhost:4781/v1/db-info\", {\r\n signal: AbortSignal.timeout(3000),\r\n });\r\n if (res.ok) {\r\n const info = (await res.json()) as { sizeBytes: number; spanCount: number; walMode: string };\r\n checks.push({\r\n label: \"Database\",\r\n status: info.walMode === \"wal\" ? \"ok\" : \"warn\",\r\n detail: `${info.spanCount} spans, WAL=${info.walMode.toUpperCase()}`,\r\n });\r\n }\r\n } catch {\r\n checks.push({ label: \"Database\", status: \"warn\", detail: \"Cannot check (collector not running)\" });\r\n }\r\n\r\n // Check 4: SDK installed\r\n try {\r\n await import(\"@llmtap/sdk\");\r\n checks.push({ label: \"@llmtap/sdk\", status: \"ok\", detail: \"Installed\" });\r\n } catch {\r\n checks.push({ label: \"@llmtap/sdk\", status: \"warn\", detail: \"Not found in current project (install with: npm i @llmtap/sdk)\" });\r\n }\r\n\r\n // Check 5: Port available\r\n if (checks.find((c) => c.label === \"Collector\")?.status !== \"ok\") {\r\n try {\r\n // Try connecting to port 4781 to see if something else is using it\r\n const res = await fetch(\"http://localhost:4781\", {\r\n signal: AbortSignal.timeout(1000),\r\n });\r\n if (!res.ok) {\r\n checks.push({ label: \"Port 4781\", status: \"warn\", detail: \"Something is running but not LLMTap\" });\r\n }\r\n } catch {\r\n checks.push({ label: \"Port 4781\", status: \"ok\", detail: \"Available\" });\r\n }\r\n }\r\n\r\n // Display results\r\n for (const check of checks) {\r\n const icon =\r\n check.status === \"ok\"\r\n ? chalk.green(\" ✓\")\r\n : check.status === \"warn\"\r\n ? chalk.yellow(\" !\")\r\n : chalk.red(\" ✗\");\r\n const label = chalk.white(check.label.padEnd(20));\r\n const detail =\r\n check.status === \"ok\"\r\n ? chalk.gray(check.detail)\r\n : check.status === \"warn\"\r\n ? chalk.yellow(check.detail)\r\n : chalk.red(check.detail);\r\n console.log(`${icon} ${label} ${detail}`);\r\n }\r\n\r\n const failCount = checks.filter((c) => c.status === \"fail\").length;\r\n const warnCount = checks.filter((c) => c.status === \"warn\").length;\r\n console.log(\"\");\r\n if (failCount > 0) {\r\n console.log(chalk.red(` ${failCount} issue(s) found. Fix them to use LLMTap.`));\r\n } else if (warnCount > 0) {\r\n console.log(chalk.yellow(` ${warnCount} warning(s). Everything should still work.`));\r\n } else {\r\n console.log(chalk.green(\" All checks passed!\"));\r\n }\r\n console.log(\"\");\r\n}\r\n","import chalk from \"chalk\";\r\n\r\ninterface StatsResponse {\r\n totalTraces: number;\r\n totalSpans: number;\r\n totalTokens: number;\r\n totalCost: number;\r\n avgDuration: number;\r\n errorCount: number;\r\n errorRate: number;\r\n byProvider: { provider: string; spanCount: number; totalTokens: number; totalCost: number; avgDuration: number }[];\r\n byModel: { model: string; provider: string; spanCount: number; totalTokens: number; totalCost: number; avgDuration: number }[];\r\n}\r\n\r\nexport async function statsCommand(options: { period?: string; host?: string }): Promise<void> {\r\n const period = Number(options.period ?? \"24\");\r\n const host = options.host ?? \"http://localhost:4781\";\r\n\r\n try {\r\n const res = await fetch(`${host}/v1/stats?period=${period}`);\r\n if (!res.ok) {\r\n console.error(chalk.red(`Error: Collector returned HTTP ${res.status}`));\r\n console.error(chalk.dim(\"Is the collector running? Try: npx llmtap start\"));\r\n process.exit(1);\r\n }\r\n\r\n const stats = (await res.json()) as StatsResponse;\r\n\r\n console.log(\"\");\r\n console.log(chalk.bold.white(` LLMTap Stats — Last ${period}h`));\r\n console.log(chalk.dim(\" ─────────────────────────────────\"));\r\n console.log(\"\");\r\n\r\n // Summary\r\n const errorPct = (stats.errorRate * 100).toFixed(1);\r\n console.log(` ${chalk.dim(\"Traces\")} ${chalk.bold.white(String(stats.totalTraces))}`);\r\n console.log(` ${chalk.dim(\"Spans\")} ${chalk.bold.white(String(stats.totalSpans))}`);\r\n console.log(` ${chalk.dim(\"Tokens\")} ${chalk.bold.white(stats.totalTokens.toLocaleString())}`);\r\n console.log(` ${chalk.dim(\"Total Cost\")} ${chalk.bold.green(\"$\" + stats.totalCost.toFixed(4))}`);\r\n console.log(` ${chalk.dim(\"Avg Latency\")} ${chalk.white(formatMs(stats.avgDuration))}`);\r\n console.log(\r\n ` ${chalk.dim(\"Error Rate\")} ${\r\n stats.errorRate > 0.05\r\n ? chalk.bold.red(errorPct + \"%\")\r\n : chalk.green(errorPct + \"%\")\r\n } ${chalk.dim(`(${stats.errorCount} errors)`)}`\r\n );\r\n\r\n // Top providers\r\n if (stats.byProvider.length > 0) {\r\n console.log(\"\");\r\n console.log(chalk.bold.white(\" Top Providers\"));\r\n console.log(chalk.dim(\" ─────────────────────────────────\"));\r\n for (const p of stats.byProvider.slice(0, 5)) {\r\n const bar = makeBar(p.totalCost, stats.totalCost, 20);\r\n console.log(\r\n ` ${chalk.cyan(p.provider.padEnd(12))} ${chalk.dim(String(p.spanCount).padStart(5) + \" calls\")} ${chalk.green(\"$\" + p.totalCost.toFixed(4).padStart(8))} ${chalk.dim(bar)}`\r\n );\r\n }\r\n }\r\n\r\n // Top models\r\n if (stats.byModel.length > 0) {\r\n console.log(\"\");\r\n console.log(chalk.bold.white(\" Top Models\"));\r\n console.log(chalk.dim(\" ─────────────────────────────────\"));\r\n for (const m of stats.byModel.slice(0, 8)) {\r\n const bar = makeBar(m.totalCost, stats.totalCost, 20);\r\n console.log(\r\n ` ${chalk.white(m.model.padEnd(28).slice(0, 28))} ${chalk.dim(String(m.spanCount).padStart(5) + \" calls\")} ${chalk.green(\"$\" + m.totalCost.toFixed(4).padStart(8))} ${chalk.dim(bar)}`\r\n );\r\n }\r\n }\r\n\r\n console.log(\"\");\r\n } catch (err) {\r\n if (err instanceof TypeError && (err as NodeJS.ErrnoException).cause) {\r\n console.error(chalk.red(\"Error: Cannot connect to collector\"));\r\n console.error(chalk.dim(\"Is the collector running? Try: npx llmtap start\"));\r\n } else {\r\n console.error(chalk.red(\"Error:\"), err instanceof Error ? err.message : err);\r\n }\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction formatMs(ms: number): string {\r\n if (ms < 1000) return `${Math.round(ms)}ms`;\r\n return `${(ms / 1000).toFixed(2)}s`;\r\n}\r\n\r\nfunction makeBar(value: number, total: number, width: number): string {\r\n if (total <= 0) return \"\";\r\n const filled = Math.max(Math.round((value / total) * width), 1);\r\n return \"█\".repeat(filled) + \"░\".repeat(width - filled);\r\n}\r\n"],"mappings":"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","names":["chalk","fs","path","chalk","chalk","chalk","chalk","chalk"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llmtap",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DevTools for AI Agents - See every LLM call, trace agent workflows, track costs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"llmtap": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"chalk": "^5.4.0",
|
|
14
|
+
"commander": "^13.0.0",
|
|
15
|
+
"open": "^10.1.0",
|
|
16
|
+
"@llmtap/collector": "0.1.0",
|
|
17
|
+
"@llmtap/shared": "0.1.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^25.4.0",
|
|
21
|
+
"rimraf": "^6.0.0",
|
|
22
|
+
"tsup": "^8.4.0",
|
|
23
|
+
"typescript": "^5.7.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"llm",
|
|
27
|
+
"ai",
|
|
28
|
+
"agent",
|
|
29
|
+
"observability",
|
|
30
|
+
"devtools",
|
|
31
|
+
"openai",
|
|
32
|
+
"anthropic",
|
|
33
|
+
"gemini",
|
|
34
|
+
"groq",
|
|
35
|
+
"tracing",
|
|
36
|
+
"debugging"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/llmtap/llmtap",
|
|
42
|
+
"directory": "packages/cli"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"prebuild": "node -e \"\"",
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"postbuild": "node -e \"const fs=require('fs');const path=require('path');const src=path.resolve(__dirname,'..','dashboard','dist');const dst=path.resolve(__dirname,'dist','dashboard');if(fs.existsSync(src)){fs.cpSync(src,dst,{recursive:true});console.log('Dashboard bundled into CLI dist')}else{console.log('Dashboard dist not found at',src)}\"",
|
|
48
|
+
"clean": "rimraf dist"
|
|
49
|
+
}
|
|
50
|
+
}
|