luckerr 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +267 -0
- package/README.zh-CN.md +237 -0
- package/dashboard/app.css +3022 -0
- package/dashboard/dist/app.js +30137 -0
- package/dashboard/dist/app.js.map +1 -0
- package/dashboard/dist/vendor-hljs.css +10 -0
- package/dashboard/dist/vendor-uplot.css +1 -0
- package/dashboard/index.html +19 -0
- package/data/deepseek-tokenizer.json.gz +0 -0
- package/dist/cli/acp-EOOAI4F5.js +712 -0
- package/dist/cli/acp-EOOAI4F5.js.map +1 -0
- package/dist/cli/chat-7J6GJXL2.js +51 -0
- package/dist/cli/chat-7J6GJXL2.js.map +1 -0
- package/dist/cli/chunk-2425HK6U.js +54 -0
- package/dist/cli/chunk-2425HK6U.js.map +1 -0
- package/dist/cli/chunk-25T6CVUP.js +172 -0
- package/dist/cli/chunk-25T6CVUP.js.map +1 -0
- package/dist/cli/chunk-2UQP6H6T.js +31 -0
- package/dist/cli/chunk-2UQP6H6T.js.map +1 -0
- package/dist/cli/chunk-56OAJILV.js +47 -0
- package/dist/cli/chunk-56OAJILV.js.map +1 -0
- package/dist/cli/chunk-5FTI4KXH.js +150 -0
- package/dist/cli/chunk-5FTI4KXH.js.map +1 -0
- package/dist/cli/chunk-5TWQD73O.js +2846 -0
- package/dist/cli/chunk-5TWQD73O.js.map +1 -0
- package/dist/cli/chunk-653BOCMK.js +40 -0
- package/dist/cli/chunk-653BOCMK.js.map +1 -0
- package/dist/cli/chunk-6ALJTWWQ.js +2663 -0
- package/dist/cli/chunk-6ALJTWWQ.js.map +1 -0
- package/dist/cli/chunk-6DRKA2IL.js +341 -0
- package/dist/cli/chunk-6DRKA2IL.js.map +1 -0
- package/dist/cli/chunk-6LV63NJV.js +634 -0
- package/dist/cli/chunk-6LV63NJV.js.map +1 -0
- package/dist/cli/chunk-74EX7SUH.js +25293 -0
- package/dist/cli/chunk-74EX7SUH.js.map +1 -0
- package/dist/cli/chunk-74U5RKTX.js +60611 -0
- package/dist/cli/chunk-74U5RKTX.js.map +1 -0
- package/dist/cli/chunk-ANJSUESV.js +143 -0
- package/dist/cli/chunk-ANJSUESV.js.map +1 -0
- package/dist/cli/chunk-DB2Z3DKZ.js +54 -0
- package/dist/cli/chunk-DB2Z3DKZ.js.map +1 -0
- package/dist/cli/chunk-DDIH3ZAA.js +400 -0
- package/dist/cli/chunk-DDIH3ZAA.js.map +1 -0
- package/dist/cli/chunk-ELN3Z3B2.js +621 -0
- package/dist/cli/chunk-ELN3Z3B2.js.map +1 -0
- package/dist/cli/chunk-F6BSQJGV.js +200 -0
- package/dist/cli/chunk-F6BSQJGV.js.map +1 -0
- package/dist/cli/chunk-FET2UAG5.js +246 -0
- package/dist/cli/chunk-FET2UAG5.js.map +1 -0
- package/dist/cli/chunk-FFJ342IJ.js +190 -0
- package/dist/cli/chunk-FFJ342IJ.js.map +1 -0
- package/dist/cli/chunk-GB3247B6.js +130 -0
- package/dist/cli/chunk-GB3247B6.js.map +1 -0
- package/dist/cli/chunk-HC2J4U3G.js +373 -0
- package/dist/cli/chunk-HC2J4U3G.js.map +1 -0
- package/dist/cli/chunk-HRUZAIHQ.js +42 -0
- package/dist/cli/chunk-HRUZAIHQ.js.map +1 -0
- package/dist/cli/chunk-J3ZJFUDL.js +308 -0
- package/dist/cli/chunk-J3ZJFUDL.js.map +1 -0
- package/dist/cli/chunk-J5XJHLWM.js +55 -0
- package/dist/cli/chunk-J5XJHLWM.js.map +1 -0
- package/dist/cli/chunk-JFGLMRZ6.js +160 -0
- package/dist/cli/chunk-JFGLMRZ6.js.map +1 -0
- package/dist/cli/chunk-JMBMLOBP.js +26 -0
- package/dist/cli/chunk-JMBMLOBP.js.map +1 -0
- package/dist/cli/chunk-JMWHXZEL.js +551 -0
- package/dist/cli/chunk-JMWHXZEL.js.map +1 -0
- package/dist/cli/chunk-KEQGPJBO.js +209 -0
- package/dist/cli/chunk-KEQGPJBO.js.map +1 -0
- package/dist/cli/chunk-M4K6U37F.js +232 -0
- package/dist/cli/chunk-M4K6U37F.js.map +1 -0
- package/dist/cli/chunk-MIJI2WMN.js +95 -0
- package/dist/cli/chunk-MIJI2WMN.js.map +1 -0
- package/dist/cli/chunk-MPAO3JNR.js +128 -0
- package/dist/cli/chunk-MPAO3JNR.js.map +1 -0
- package/dist/cli/chunk-PZOFBEDC.js +873 -0
- package/dist/cli/chunk-PZOFBEDC.js.map +1 -0
- package/dist/cli/chunk-RAILYQLN.js +46 -0
- package/dist/cli/chunk-RAILYQLN.js.map +1 -0
- package/dist/cli/chunk-RR35VQVT.js +90 -0
- package/dist/cli/chunk-RR35VQVT.js.map +1 -0
- package/dist/cli/chunk-RRA7VPW4.js +417 -0
- package/dist/cli/chunk-RRA7VPW4.js.map +1 -0
- package/dist/cli/chunk-RU36QVN3.js +452 -0
- package/dist/cli/chunk-RU36QVN3.js.map +1 -0
- package/dist/cli/chunk-RUBIINXR.js +1819 -0
- package/dist/cli/chunk-RUBIINXR.js.map +1 -0
- package/dist/cli/chunk-S4XVGLRW.js +499 -0
- package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
- package/dist/cli/chunk-TUK7OWJA.js +51 -0
- package/dist/cli/chunk-TUK7OWJA.js.map +1 -0
- package/dist/cli/chunk-VALDDV76.js +580 -0
- package/dist/cli/chunk-VALDDV76.js.map +1 -0
- package/dist/cli/chunk-WQOGPYGN.js +11390 -0
- package/dist/cli/chunk-WQOGPYGN.js.map +1 -0
- package/dist/cli/chunk-WREKDFXT.js +34320 -0
- package/dist/cli/chunk-WREKDFXT.js.map +1 -0
- package/dist/cli/chunk-Y7XQU2EL.js +270 -0
- package/dist/cli/chunk-Y7XQU2EL.js.map +1 -0
- package/dist/cli/chunk-YBVCZJU4.js +54 -0
- package/dist/cli/chunk-YBVCZJU4.js.map +1 -0
- package/dist/cli/chunk-YLIHDXUQ.js +749 -0
- package/dist/cli/chunk-YLIHDXUQ.js.map +1 -0
- package/dist/cli/chunk-YV5XXFD7.js +767 -0
- package/dist/cli/chunk-YV5XXFD7.js.map +1 -0
- package/dist/cli/chunk-ZRCNIYRQ.js +101 -0
- package/dist/cli/chunk-ZRCNIYRQ.js.map +1 -0
- package/dist/cli/code-CRKVCMFZ.js +155 -0
- package/dist/cli/code-CRKVCMFZ.js.map +1 -0
- package/dist/cli/commands-QLMD3T7B.js +356 -0
- package/dist/cli/commands-QLMD3T7B.js.map +1 -0
- package/dist/cli/commit-53PP32NC.js +293 -0
- package/dist/cli/commit-53PP32NC.js.map +1 -0
- package/dist/cli/desktop-R6W5CLJ5.js +1046 -0
- package/dist/cli/desktop-R6W5CLJ5.js.map +1 -0
- package/dist/cli/devtools-YECO25QO.js +3719 -0
- package/dist/cli/devtools-YECO25QO.js.map +1 -0
- package/dist/cli/diff-LYNRCJZE.js +166 -0
- package/dist/cli/diff-LYNRCJZE.js.map +1 -0
- package/dist/cli/doctor-5IBP4R5J.js +28 -0
- package/dist/cli/doctor-5IBP4R5J.js.map +1 -0
- package/dist/cli/events-QN6KLN2V.js +340 -0
- package/dist/cli/events-QN6KLN2V.js.map +1 -0
- package/dist/cli/index.js +3500 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mcp-FGKEH7RG.js +277 -0
- package/dist/cli/mcp-FGKEH7RG.js.map +1 -0
- package/dist/cli/mcp-browse-YCND4NWT.js +178 -0
- package/dist/cli/mcp-browse-YCND4NWT.js.map +1 -0
- package/dist/cli/mcp-inspect-V34J3VX5.js +143 -0
- package/dist/cli/mcp-inspect-V34J3VX5.js.map +1 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/prompt-I775PNKT.js +16 -0
- package/dist/cli/prompt-I775PNKT.js.map +1 -0
- package/dist/cli/prune-sessions-KGIIYD3P.js +44 -0
- package/dist/cli/prune-sessions-KGIIYD3P.js.map +1 -0
- package/dist/cli/replay-RDXLUAOE.js +292 -0
- package/dist/cli/replay-RDXLUAOE.js.map +1 -0
- package/dist/cli/run-RCAC2RYW.js +223 -0
- package/dist/cli/run-RCAC2RYW.js.map +1 -0
- package/dist/cli/server-FFU6TLYJ.js +3658 -0
- package/dist/cli/server-FFU6TLYJ.js.map +1 -0
- package/dist/cli/sessions-QT26MQAE.js +107 -0
- package/dist/cli/sessions-QT26MQAE.js.map +1 -0
- package/dist/cli/setup-VV4WKXHV.js +767 -0
- package/dist/cli/setup-VV4WKXHV.js.map +1 -0
- package/dist/cli/stats-JVZPQWAN.js +15 -0
- package/dist/cli/stats-JVZPQWAN.js.map +1 -0
- package/dist/cli/update-KYI3OVJP.js +15 -0
- package/dist/cli/update-KYI3OVJP.js.map +1 -0
- package/dist/cli/version-ANYORXTI.js +34 -0
- package/dist/cli/version-ANYORXTI.js.map +1 -0
- package/dist/index.d.ts +2557 -0
- package/dist/index.js +15000 -0
- package/dist/index.js.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
|
|
3
|
+
import {
|
|
4
|
+
Usage
|
|
5
|
+
} from "./chunk-DDIH3ZAA.js";
|
|
6
|
+
import {
|
|
7
|
+
claudeEquivalentCost,
|
|
8
|
+
costUsd,
|
|
9
|
+
inputCostUsd,
|
|
10
|
+
outputCostUsd
|
|
11
|
+
} from "./chunk-ANJSUESV.js";
|
|
12
|
+
|
|
13
|
+
// src/transcript/log.ts
|
|
14
|
+
import { createWriteStream, readFileSync } from "fs";
|
|
15
|
+
function recordFromLoopEvent(ev, extra) {
|
|
16
|
+
const rec = {
|
|
17
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18
|
+
turn: ev.turn,
|
|
19
|
+
role: ev.role,
|
|
20
|
+
content: ev.content
|
|
21
|
+
};
|
|
22
|
+
if (ev.toolName !== void 0) rec.tool = ev.toolName;
|
|
23
|
+
if (ev.toolArgs !== void 0) rec.args = ev.toolArgs;
|
|
24
|
+
if (ev.error !== void 0) rec.error = ev.error;
|
|
25
|
+
if (ev.stats) {
|
|
26
|
+
rec.usage = {
|
|
27
|
+
prompt_tokens: ev.stats.usage.promptTokens,
|
|
28
|
+
completion_tokens: ev.stats.usage.completionTokens,
|
|
29
|
+
total_tokens: ev.stats.usage.totalTokens,
|
|
30
|
+
prompt_cache_hit_tokens: ev.stats.usage.promptCacheHitTokens,
|
|
31
|
+
prompt_cache_miss_tokens: ev.stats.usage.promptCacheMissTokens
|
|
32
|
+
};
|
|
33
|
+
rec.cost = ev.stats.cost;
|
|
34
|
+
rec.model = ev.stats.model;
|
|
35
|
+
rec.prefixHash = extra.prefixHash;
|
|
36
|
+
} else if (ev.role === "assistant_final") {
|
|
37
|
+
rec.model = extra.model;
|
|
38
|
+
rec.prefixHash = extra.prefixHash;
|
|
39
|
+
}
|
|
40
|
+
return rec;
|
|
41
|
+
}
|
|
42
|
+
function writeRecord(stream, record) {
|
|
43
|
+
stream.write(`${JSON.stringify(record)}
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
function writeMeta(stream, meta) {
|
|
47
|
+
const line = { role: "_meta", meta };
|
|
48
|
+
stream.write(`${JSON.stringify(line)}
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
function openTranscriptFile(path, meta) {
|
|
52
|
+
const stream = createWriteStream(path, { flags: "a" });
|
|
53
|
+
writeMeta(stream, meta);
|
|
54
|
+
return stream;
|
|
55
|
+
}
|
|
56
|
+
function readTranscript(path) {
|
|
57
|
+
const raw = readFileSync(path, "utf8");
|
|
58
|
+
return parseTranscript(raw);
|
|
59
|
+
}
|
|
60
|
+
function parseTranscript(raw) {
|
|
61
|
+
const out = { meta: null, records: [] };
|
|
62
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed) continue;
|
|
65
|
+
let obj;
|
|
66
|
+
try {
|
|
67
|
+
obj = JSON.parse(trimmed);
|
|
68
|
+
} catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!obj || typeof obj !== "object") continue;
|
|
72
|
+
const rec = obj;
|
|
73
|
+
if (rec.role === "_meta" && rec.meta && typeof rec.meta === "object") {
|
|
74
|
+
out.meta = rec.meta;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (typeof rec.ts === "string" && typeof rec.turn === "number" && typeof rec.role === "string" && typeof rec.content === "string") {
|
|
78
|
+
out.records.push(rec);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/transcript/replay.ts
|
|
85
|
+
function groupRecordsByTurn(records) {
|
|
86
|
+
const byTurn = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const rec of records) {
|
|
88
|
+
const list = byTurn.get(rec.turn);
|
|
89
|
+
if (list) list.push(rec);
|
|
90
|
+
else byTurn.set(rec.turn, [rec]);
|
|
91
|
+
}
|
|
92
|
+
return [...byTurn.entries()].sort(([a], [b]) => a - b).map(([turn, records2]) => ({ turn, records: records2 }));
|
|
93
|
+
}
|
|
94
|
+
function computeCumulativeStats(pages, upToIdx) {
|
|
95
|
+
if (upToIdx < 0) return computeReplayStats([]);
|
|
96
|
+
const flat = [];
|
|
97
|
+
for (let i = 0; i <= upToIdx && i < pages.length; i++) {
|
|
98
|
+
const records = pages[i]?.records;
|
|
99
|
+
if (records) flat.push(...records);
|
|
100
|
+
}
|
|
101
|
+
return computeReplayStats(flat);
|
|
102
|
+
}
|
|
103
|
+
function replayFromFile(path) {
|
|
104
|
+
const parsed = readTranscript(path);
|
|
105
|
+
return { parsed, stats: computeReplayStats(parsed.records) };
|
|
106
|
+
}
|
|
107
|
+
function computeReplayStats(records) {
|
|
108
|
+
const turns = [];
|
|
109
|
+
const models = /* @__PURE__ */ new Set();
|
|
110
|
+
const prefixHashes = /* @__PURE__ */ new Set();
|
|
111
|
+
let userTurns = 0;
|
|
112
|
+
let toolCalls = 0;
|
|
113
|
+
for (const rec of records) {
|
|
114
|
+
if (rec.role === "user") userTurns++;
|
|
115
|
+
else if (rec.role === "tool") toolCalls++;
|
|
116
|
+
else if (rec.role === "assistant_final") {
|
|
117
|
+
if (rec.model) models.add(rec.model);
|
|
118
|
+
if (rec.prefixHash) prefixHashes.add(rec.prefixHash);
|
|
119
|
+
if (rec.usage && rec.model) {
|
|
120
|
+
const u = new Usage(
|
|
121
|
+
rec.usage.prompt_tokens ?? 0,
|
|
122
|
+
rec.usage.completion_tokens ?? 0,
|
|
123
|
+
rec.usage.total_tokens ?? 0,
|
|
124
|
+
rec.usage.prompt_cache_hit_tokens ?? 0,
|
|
125
|
+
rec.usage.prompt_cache_miss_tokens ?? 0
|
|
126
|
+
);
|
|
127
|
+
turns.push({
|
|
128
|
+
turn: rec.turn,
|
|
129
|
+
model: rec.model,
|
|
130
|
+
usage: u,
|
|
131
|
+
// `rec.cost` wins when present — honors whatever the writer computed
|
|
132
|
+
// even if pricing tables have since changed. Only recompute when
|
|
133
|
+
// the transcript didn't record it (old format).
|
|
134
|
+
cost: rec.cost ?? costUsd(rec.model, u),
|
|
135
|
+
cacheHitRatio: u.cacheHitRatio
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
perTurn: turns,
|
|
142
|
+
models: [...models],
|
|
143
|
+
prefixHashes: [...prefixHashes],
|
|
144
|
+
userTurns,
|
|
145
|
+
toolCalls,
|
|
146
|
+
...summarizeTurns(turns)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function summarizeTurns(turns) {
|
|
150
|
+
const totalCost = turns.reduce((s, t) => s + t.cost, 0);
|
|
151
|
+
const totalInput = turns.reduce((s, t) => s + inputCostUsd(t.model, t.usage), 0);
|
|
152
|
+
const totalOutput = turns.reduce((s, t) => s + outputCostUsd(t.model, t.usage), 0);
|
|
153
|
+
const totalClaude = turns.reduce((s, t) => s + claudeEquivalentCost(t.usage), 0);
|
|
154
|
+
let hit = 0;
|
|
155
|
+
let miss = 0;
|
|
156
|
+
for (const t of turns) {
|
|
157
|
+
hit += t.usage.promptCacheHitTokens;
|
|
158
|
+
miss += t.usage.promptCacheMissTokens;
|
|
159
|
+
}
|
|
160
|
+
const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;
|
|
161
|
+
const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;
|
|
162
|
+
const lastTurn = turns[turns.length - 1];
|
|
163
|
+
return {
|
|
164
|
+
turns: turns.length,
|
|
165
|
+
totalCostUsd: round(totalCost, 6),
|
|
166
|
+
totalInputCostUsd: round(totalInput, 6),
|
|
167
|
+
totalOutputCostUsd: round(totalOutput, 6),
|
|
168
|
+
claudeEquivalentUsd: round(totalClaude, 6),
|
|
169
|
+
savingsVsClaudePct: round(savingsVsClaude * 100, 2),
|
|
170
|
+
cacheHitRatio: round(cacheHitRatio, 4),
|
|
171
|
+
lastPromptTokens: lastTurn?.usage.promptTokens ?? 0,
|
|
172
|
+
lastTurnCostUsd: round(lastTurn?.cost ?? 0, 6)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function round(n, digits) {
|
|
176
|
+
const f = 10 ** digits;
|
|
177
|
+
return Math.round(n * f) / f;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
recordFromLoopEvent,
|
|
182
|
+
writeRecord,
|
|
183
|
+
openTranscriptFile,
|
|
184
|
+
readTranscript,
|
|
185
|
+
groupRecordsByTurn,
|
|
186
|
+
computeCumulativeStats,
|
|
187
|
+
replayFromFile,
|
|
188
|
+
computeReplayStats
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=chunk-FFJ342IJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/transcript/log.ts","../../src/transcript/replay.ts"],"sourcesContent":["/** Transcripts are receipts (cost/usage/prefix); sessions are memory (ChatMessages). Don't conflate. */\n\nimport { type WriteStream, createWriteStream, readFileSync } from \"node:fs\";\nimport type { LoopEvent } from \"../loop.js\";\nimport type { RawUsage } from \"../types.js\";\n\nexport interface TranscriptRecord {\n /** ISO-8601 timestamp at emit time. */\n ts: string;\n /** 1-based turn number within the session. */\n turn: number;\n /** LoopEvent role — \"assistant_delta\" | \"assistant_final\" | \"tool\" | \"done\" | ... */\n role: string;\n /** For assistant events, the final (or delta) text; for tool events, the tool result. */\n content: string;\n /** Tool name (role === \"tool\"). */\n tool?: string;\n /** JSON-string args the model sent for a tool call (role === \"tool\"). Persisted so diff can explain *why* two runs made different calls. */\n args?: string;\n /** DeepSeek token-usage snapshot (role === \"assistant_final\"). */\n usage?: RawUsage;\n /** USD cost of this turn (role === \"assistant_final\"). */\n cost?: number;\n /** Model id that produced this turn. */\n model?: string;\n /** Lets diff attribute cache-hit delta to log stability vs prompt change. */\n prefixHash?: string;\n /** Optional error message (role === \"error\"). */\n error?: string;\n}\n\nexport interface TranscriptMeta {\n version: 1;\n source: string; // e.g. \"luckerr chat\", \"bench/baseline\", \"bench/luckerr\"\n model?: string;\n task?: string;\n mode?: string;\n repeat?: number;\n startedAt: string;\n}\n\ninterface MetaLine {\n role: \"_meta\";\n meta: TranscriptMeta;\n}\n\nexport interface ReadTranscriptResult {\n meta: TranscriptMeta | null;\n records: TranscriptRecord[];\n}\n\nexport function recordFromLoopEvent(\n ev: LoopEvent,\n extra: { model: string; prefixHash: string },\n): TranscriptRecord {\n const rec: TranscriptRecord = {\n ts: new Date().toISOString(),\n turn: ev.turn,\n role: ev.role,\n content: ev.content,\n };\n if (ev.toolName !== undefined) rec.tool = ev.toolName;\n if (ev.toolArgs !== undefined) rec.args = ev.toolArgs;\n if (ev.error !== undefined) rec.error = ev.error;\n if (ev.stats) {\n rec.usage = {\n prompt_tokens: ev.stats.usage.promptTokens,\n completion_tokens: ev.stats.usage.completionTokens,\n total_tokens: ev.stats.usage.totalTokens,\n prompt_cache_hit_tokens: ev.stats.usage.promptCacheHitTokens,\n prompt_cache_miss_tokens: ev.stats.usage.promptCacheMissTokens,\n };\n rec.cost = ev.stats.cost;\n rec.model = ev.stats.model;\n rec.prefixHash = extra.prefixHash;\n } else if (ev.role === \"assistant_final\") {\n // assistant_final without stats (shouldn't happen in the live loop but\n // might in test fixtures) — still persist model + prefix for continuity.\n rec.model = extra.model;\n rec.prefixHash = extra.prefixHash;\n }\n return rec;\n}\n\n/**\n * Append a record to an open write stream. Caller owns the stream lifecycle.\n */\nexport function writeRecord(stream: WriteStream, record: TranscriptRecord): void {\n stream.write(`${JSON.stringify(record)}\\n`);\n}\n\n/**\n * Write a _meta line to an open write stream. Call exactly once, at the top.\n */\nexport function writeMeta(stream: WriteStream, meta: TranscriptMeta): void {\n const line: MetaLine = { role: \"_meta\", meta };\n stream.write(`${JSON.stringify(line)}\\n`);\n}\n\n/**\n * Convenience: open a stream, write meta, return stream.\n */\nexport function openTranscriptFile(path: string, meta: TranscriptMeta): WriteStream {\n const stream = createWriteStream(path, { flags: \"a\" });\n writeMeta(stream, meta);\n return stream;\n}\n\n/** Tolerant: empty / malformed lines skipped, missing optionals OK — live chats may be mid-write. */\nexport function readTranscript(path: string): ReadTranscriptResult {\n const raw = readFileSync(path, \"utf8\");\n return parseTranscript(raw);\n}\n\nexport function parseTranscript(raw: string): ReadTranscriptResult {\n const out: ReadTranscriptResult = { meta: null, records: [] };\n for (const line of raw.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n let obj: unknown;\n try {\n obj = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (!obj || typeof obj !== \"object\") continue;\n const rec = obj as Record<string, unknown>;\n if (rec.role === \"_meta\" && rec.meta && typeof rec.meta === \"object\") {\n out.meta = rec.meta as TranscriptMeta;\n continue;\n }\n if (\n typeof rec.ts === \"string\" &&\n typeof rec.turn === \"number\" &&\n typeof rec.role === \"string\" &&\n typeof rec.content === \"string\"\n ) {\n out.records.push(rec as unknown as TranscriptRecord);\n }\n }\n return out;\n}\n","/** Reconstruct session economics from a transcript alone — offline audit, no API key. */\n\nimport { Usage } from \"../client.js\";\nimport {\n type SessionSummary,\n type TurnStats,\n claudeEquivalentCost,\n costUsd,\n inputCostUsd,\n outputCostUsd,\n} from \"../telemetry/stats.js\";\nimport { type ReadTranscriptResult, type TranscriptRecord, readTranscript } from \"./log.js\";\n\nexport interface TurnPage {\n turn: number;\n records: TranscriptRecord[];\n}\n\nexport function groupRecordsByTurn(records: TranscriptRecord[]): TurnPage[] {\n const byTurn = new Map<number, TranscriptRecord[]>();\n for (const rec of records) {\n const list = byTurn.get(rec.turn);\n if (list) list.push(rec);\n else byTurn.set(rec.turn, [rec]);\n }\n return [...byTurn.entries()]\n .sort(([a], [b]) => a - b)\n .map(([turn, records]) => ({ turn, records }));\n}\n\nexport function computeCumulativeStats(pages: TurnPage[], upToIdx: number): ReplayStats {\n if (upToIdx < 0) return computeReplayStats([]);\n const flat: TranscriptRecord[] = [];\n for (let i = 0; i <= upToIdx && i < pages.length; i++) {\n const records = pages[i]?.records;\n if (records) flat.push(...records);\n }\n return computeReplayStats(flat);\n}\n\nexport interface ReplayStats extends SessionSummary {\n /** Per-turn stats, in turn order. Only assistant_final records contribute. */\n perTurn: TurnStats[];\n /** Unique models that appeared in the transcript's assistant_final records. */\n models: string[];\n /** Unique prefix hashes that appeared. Length > 1 means the prefix churned (cache-hostile). */\n prefixHashes: string[];\n /** Count of user-role records (user turns issued). */\n userTurns: number;\n /** Count of tool-role records (tool calls executed). */\n toolCalls: number;\n}\n\nexport function replayFromFile(path: string): { parsed: ReadTranscriptResult; stats: ReplayStats } {\n const parsed = readTranscript(path);\n return { parsed, stats: computeReplayStats(parsed.records) };\n}\n\nexport function computeReplayStats(records: TranscriptRecord[]): ReplayStats {\n const turns: TurnStats[] = [];\n const models = new Set<string>();\n const prefixHashes = new Set<string>();\n let userTurns = 0;\n let toolCalls = 0;\n\n for (const rec of records) {\n if (rec.role === \"user\") userTurns++;\n else if (rec.role === \"tool\") toolCalls++;\n else if (rec.role === \"assistant_final\") {\n if (rec.model) models.add(rec.model);\n if (rec.prefixHash) prefixHashes.add(rec.prefixHash);\n if (rec.usage && rec.model) {\n const u = new Usage(\n rec.usage.prompt_tokens ?? 0,\n rec.usage.completion_tokens ?? 0,\n rec.usage.total_tokens ?? 0,\n rec.usage.prompt_cache_hit_tokens ?? 0,\n rec.usage.prompt_cache_miss_tokens ?? 0,\n );\n turns.push({\n turn: rec.turn,\n model: rec.model,\n usage: u,\n // `rec.cost` wins when present — honors whatever the writer computed\n // even if pricing tables have since changed. Only recompute when\n // the transcript didn't record it (old format).\n cost: rec.cost ?? costUsd(rec.model, u),\n cacheHitRatio: u.cacheHitRatio,\n });\n }\n }\n }\n\n return {\n perTurn: turns,\n models: [...models],\n prefixHashes: [...prefixHashes],\n userTurns,\n toolCalls,\n ...summarizeTurns(turns),\n };\n}\n\nfunction summarizeTurns(turns: TurnStats[]): SessionSummary {\n const totalCost = turns.reduce((s, t) => s + t.cost, 0);\n const totalInput = turns.reduce((s, t) => s + inputCostUsd(t.model, t.usage), 0);\n const totalOutput = turns.reduce((s, t) => s + outputCostUsd(t.model, t.usage), 0);\n const totalClaude = turns.reduce((s, t) => s + claudeEquivalentCost(t.usage), 0);\n let hit = 0;\n let miss = 0;\n for (const t of turns) {\n hit += t.usage.promptCacheHitTokens;\n miss += t.usage.promptCacheMissTokens;\n }\n const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;\n const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;\n const lastTurn = turns[turns.length - 1];\n return {\n turns: turns.length,\n totalCostUsd: round(totalCost, 6),\n totalInputCostUsd: round(totalInput, 6),\n totalOutputCostUsd: round(totalOutput, 6),\n claudeEquivalentUsd: round(totalClaude, 6),\n savingsVsClaudePct: round(savingsVsClaude * 100, 2),\n cacheHitRatio: round(cacheHitRatio, 4),\n lastPromptTokens: lastTurn?.usage.promptTokens ?? 0,\n lastTurnCostUsd: round(lastTurn?.cost ?? 0, 6),\n };\n}\n\nfunction round(n: number, digits: number): number {\n const f = 10 ** digits;\n return Math.round(n * f) / f;\n}\n"],"mappings":"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","names":["records"]}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
|
|
3
|
+
import {
|
|
4
|
+
VERSION,
|
|
5
|
+
compareVersions,
|
|
6
|
+
detectInstallSource,
|
|
7
|
+
detectNpmInstallPrefix,
|
|
8
|
+
getLatestVersion
|
|
9
|
+
} from "./chunk-MPAO3JNR.js";
|
|
10
|
+
|
|
11
|
+
// src/cli/commands/update.ts
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
var MANUAL_UPDATE_COMMANDS = [
|
|
14
|
+
"npm install -g luckerr@latest",
|
|
15
|
+
"bun add -g luckerr",
|
|
16
|
+
"pnpm add -g luckerr@latest",
|
|
17
|
+
"yarn global add luckerr@latest"
|
|
18
|
+
];
|
|
19
|
+
function planUpdate(input) {
|
|
20
|
+
const diff = compareVersions(input.current, input.latest);
|
|
21
|
+
if (diff > 0) {
|
|
22
|
+
return {
|
|
23
|
+
action: "newer-local",
|
|
24
|
+
message: `current (${input.current}) is newer than the published ${input.latest} \u2014 nothing to do.`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (diff === 0) {
|
|
28
|
+
return { action: "up-to-date", message: `luckerr ${input.current} is up to date.` };
|
|
29
|
+
}
|
|
30
|
+
if (input.installSource === "npx") {
|
|
31
|
+
return {
|
|
32
|
+
action: "npx-hint",
|
|
33
|
+
message: [
|
|
34
|
+
`luckerr ${input.latest} is available.`,
|
|
35
|
+
"you're running via npx \u2014 the next `npx luckerr ...` launch will auto-fetch",
|
|
36
|
+
"the latest (npx caches packages for a short window). to force a refresh",
|
|
37
|
+
"sooner, clear the cache: `npm cache clean --force`."
|
|
38
|
+
].join("\n")
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (input.installSource === "unknown") {
|
|
42
|
+
return {
|
|
43
|
+
action: "manual-hint",
|
|
44
|
+
message: [
|
|
45
|
+
`luckerr ${input.latest} is available, but the install source could not be determined automatically.`,
|
|
46
|
+
"run one of these manually based on how you installed luckerr:",
|
|
47
|
+
...MANUAL_UPDATE_COMMANDS.map((c) => ` ${c}`)
|
|
48
|
+
].join("\n")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const command = buildUpdateCommand(input.installSource, input.npmPrefix ?? null);
|
|
52
|
+
return {
|
|
53
|
+
action: "run-install",
|
|
54
|
+
message: `upgrading luckerr ${input.current} \u2192 ${input.latest} (via ${input.installSource})`,
|
|
55
|
+
command
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildUpdateCommand(source, npmPrefix) {
|
|
59
|
+
switch (source) {
|
|
60
|
+
case "npm":
|
|
61
|
+
return npmPrefix ? ["npm", "--prefix", npmPrefix, "install", "-g", "luckerr@latest"] : ["npm", "install", "-g", "luckerr@latest"];
|
|
62
|
+
case "bun":
|
|
63
|
+
return ["bun", "add", "-g", "luckerr"];
|
|
64
|
+
case "pnpm":
|
|
65
|
+
return ["pnpm", "add", "-g", "luckerr@latest"];
|
|
66
|
+
case "yarn":
|
|
67
|
+
return ["yarn", "global", "add", "luckerr@latest"];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function defaultSpawn(argv) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
shell: process.platform === "win32"
|
|
75
|
+
});
|
|
76
|
+
child.once("error", reject);
|
|
77
|
+
child.once("exit", (code) => resolve(code ?? 1));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function updateCommand(opts = {}) {
|
|
81
|
+
const write = opts.write ?? ((m) => process.stdout.write(m));
|
|
82
|
+
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
83
|
+
const fetchLatest = opts.fetchLatest ?? (() => getLatestVersion({ force: true }));
|
|
84
|
+
const detectSource = opts.detectSource ?? (() => detectInstallSource());
|
|
85
|
+
const detectPrefix = opts.detectPrefix ?? (() => detectNpmInstallPrefix());
|
|
86
|
+
const doSpawn = opts.spawnInstall ?? defaultSpawn;
|
|
87
|
+
write(`current: luckerr ${VERSION}
|
|
88
|
+
`);
|
|
89
|
+
const latest = await fetchLatest();
|
|
90
|
+
if (!latest) {
|
|
91
|
+
write("could not reach registry.npmjs.org \u2014 check your network.\n");
|
|
92
|
+
exit(1);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
write(`latest: luckerr ${latest}
|
|
96
|
+
`);
|
|
97
|
+
const installSource = detectSource();
|
|
98
|
+
const npmPrefix = installSource === "npm" ? detectPrefix() : null;
|
|
99
|
+
const plan = planUpdate({ current: VERSION, latest, installSource, npmPrefix });
|
|
100
|
+
write(`
|
|
101
|
+
${plan.message}
|
|
102
|
+
`);
|
|
103
|
+
if (plan.action === "manual-hint") {
|
|
104
|
+
exit(1);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (plan.action !== "run-install" || !plan.command) return;
|
|
108
|
+
if (opts.dryRun) {
|
|
109
|
+
write(`(dry run) would run: ${plan.command.join(" ")}
|
|
110
|
+
`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
write(`
|
|
114
|
+
running: ${plan.command.join(" ")}
|
|
115
|
+
`);
|
|
116
|
+
const code = await doSpawn(plan.command);
|
|
117
|
+
if (code !== 0) {
|
|
118
|
+
write(`
|
|
119
|
+
${plan.command[0]} exited with code ${code}. upgrade did not complete.
|
|
120
|
+
`);
|
|
121
|
+
exit(code);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
MANUAL_UPDATE_COMMANDS,
|
|
127
|
+
planUpdate,
|
|
128
|
+
updateCommand
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=chunk-GB3247B6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/commands/update.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport {\n type InstallSource,\n VERSION,\n compareVersions,\n detectInstallSource,\n detectNpmInstallPrefix,\n getLatestVersion,\n} from \"../../version.js\";\n\nexport type UpdateAction =\n | \"up-to-date\"\n | \"newer-local\"\n | \"npx-hint\"\n | \"manual-hint\"\n | \"run-install\";\n\nexport interface UpdatePlan {\n action: UpdateAction;\n /** Human-readable summary; the CLI prints this verbatim. */\n message: string;\n command?: string[];\n}\n\nexport interface PlanUpdateInput {\n current: string;\n latest: string;\n installSource: InstallSource;\n /** Pin npm to this prefix so nvm/fnm can't redirect the install. */\n npmPrefix?: string | null;\n}\n\nexport const MANUAL_UPDATE_COMMANDS: readonly string[] = [\n \"npm install -g luckerr@latest\",\n \"bun add -g luckerr\",\n \"pnpm add -g luckerr@latest\",\n \"yarn global add luckerr@latest\",\n];\n\n/** Pure decision — split out so tests don't need to spawn child processes or hit the network. */\nexport function planUpdate(input: PlanUpdateInput): UpdatePlan {\n const diff = compareVersions(input.current, input.latest);\n if (diff > 0) {\n return {\n action: \"newer-local\",\n message: `current (${input.current}) is newer than the published ${input.latest} — nothing to do.`,\n };\n }\n if (diff === 0) {\n return { action: \"up-to-date\", message: `luckerr ${input.current} is up to date.` };\n }\n if (input.installSource === \"npx\") {\n return {\n action: \"npx-hint\",\n message: [\n `luckerr ${input.latest} is available.`,\n \"you're running via npx — the next `npx luckerr ...` launch will auto-fetch\",\n \"the latest (npx caches packages for a short window). to force a refresh\",\n \"sooner, clear the cache: `npm cache clean --force`.\",\n ].join(\"\\n\"),\n };\n }\n if (input.installSource === \"unknown\") {\n return {\n action: \"manual-hint\",\n message: [\n `luckerr ${input.latest} is available, but the install source could not be determined automatically.`,\n \"run one of these manually based on how you installed luckerr:\",\n ...MANUAL_UPDATE_COMMANDS.map((c) => ` ${c}`),\n ].join(\"\\n\"),\n };\n }\n const command = buildUpdateCommand(input.installSource, input.npmPrefix ?? null);\n return {\n action: \"run-install\",\n message: `upgrading luckerr ${input.current} → ${input.latest} (via ${input.installSource})`,\n command,\n };\n}\n\nfunction buildUpdateCommand(\n source: Exclude<InstallSource, \"npx\" | \"unknown\">,\n npmPrefix: string | null,\n): string[] {\n switch (source) {\n case \"npm\":\n return npmPrefix\n ? [\"npm\", \"--prefix\", npmPrefix, \"install\", \"-g\", \"luckerr@latest\"]\n : [\"npm\", \"install\", \"-g\", \"luckerr@latest\"];\n case \"bun\":\n return [\"bun\", \"add\", \"-g\", \"luckerr\"];\n case \"pnpm\":\n return [\"pnpm\", \"add\", \"-g\", \"luckerr@latest\"];\n case \"yarn\":\n return [\"yarn\", \"global\", \"add\", \"luckerr@latest\"];\n }\n}\n\nexport interface UpdateCommandOptions {\n /** Skip spawning the package manager; print the decision only. */\n dryRun?: boolean;\n /** Test seam: override the registry lookup. Returns null = offline. */\n fetchLatest?: () => Promise<string | null>;\n /** Test seam: override the install-source detector. */\n detectSource?: () => InstallSource;\n /** Test seam: override the npm prefix detector. */\n detectPrefix?: () => string | null;\n /** Test seam: override the spawner. Must return exit code. */\n spawnInstall?: (argv: string[]) => Promise<number>;\n /** Test seam: stdout writer. */\n write?: (msg: string) => void;\n /** Test seam: process exit — tests don't want to tear down vitest. */\n exit?: (code: number) => void;\n}\n\nfunction defaultSpawn(argv: string[]): Promise<number> {\n return new Promise((resolve, reject) => {\n // `shell: true` on Windows is what lets `npm` resolve to `npm.cmd`\n // without routing through our `prepareSpawn` helper. The args here\n // are literal strings under our control — no user input flows in,\n // so injection is not a concern. Avoiding `prepareSpawn` keeps\n // this command free of a dep on the shell tools module.\n const child = spawn(argv[0]!, argv.slice(1), {\n stdio: \"inherit\",\n shell: process.platform === \"win32\",\n });\n child.once(\"error\", reject);\n child.once(\"exit\", (code) => resolve(code ?? 1));\n });\n}\n\nexport async function updateCommand(opts: UpdateCommandOptions = {}): Promise<void> {\n const write = opts.write ?? ((m: string) => process.stdout.write(m));\n const exit = opts.exit ?? ((c: number) => process.exit(c));\n const fetchLatest = opts.fetchLatest ?? (() => getLatestVersion({ force: true }));\n const detectSource = opts.detectSource ?? (() => detectInstallSource());\n const detectPrefix = opts.detectPrefix ?? (() => detectNpmInstallPrefix());\n const doSpawn = opts.spawnInstall ?? defaultSpawn;\n\n write(`current: luckerr ${VERSION}\\n`);\n const latest = await fetchLatest();\n if (!latest) {\n write(\"could not reach registry.npmjs.org — check your network.\\n\");\n exit(1);\n return;\n }\n write(`latest: luckerr ${latest}\\n`);\n\n const installSource = detectSource();\n const npmPrefix = installSource === \"npm\" ? detectPrefix() : null;\n const plan = planUpdate({ current: VERSION, latest, installSource, npmPrefix });\n write(`\\n${plan.message}\\n`);\n\n if (plan.action === \"manual-hint\") {\n exit(1);\n return;\n }\n if (plan.action !== \"run-install\" || !plan.command) return;\n if (opts.dryRun) {\n write(`(dry run) would run: ${plan.command.join(\" \")}\\n`);\n return;\n }\n write(`\\nrunning: ${plan.command.join(\" \")}\\n`);\n const code = await doSpawn(plan.command);\n if (code !== 0) {\n write(`\\n${plan.command[0]} exited with code ${code}. upgrade did not complete.\\n`);\n exit(code);\n }\n}\n"],"mappings":"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","names":[]}
|