agentcassette 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/dist/cli.cjs +437 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +435 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-CgkW5WWa.d.cts +179 -0
- package/dist/client-CgkW5WWa.d.ts +179 -0
- package/dist/index.cjs +903 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +881 -0
- package/dist/index.js.map +1 -0
- package/dist/jest.cjs +500 -0
- package/dist/jest.cjs.map +1 -0
- package/dist/jest.d.cts +7 -0
- package/dist/jest.d.ts +7 -0
- package/dist/jest.js +498 -0
- package/dist/jest.js.map +1 -0
- package/dist/testing-Bj7F-tqU.d.ts +9 -0
- package/dist/testing-S6vBH_XM.d.cts +9 -0
- package/dist/vitest.cjs +500 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +7 -0
- package/dist/vitest.d.ts +7 -0
- package/dist/vitest.js +498 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +73 -0
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var promises = require('fs/promises');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var child_process = require('child_process');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
// src/core/canonical.ts
|
|
10
|
+
var ISO_TIMESTAMP = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})\b/giu;
|
|
11
|
+
var UNIX_TIMESTAMP = /\b1[5-9]\d{8}(?:\d{3})?\b/gu;
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function asJsonValue(value) {
|
|
16
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
17
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
18
|
+
if (Array.isArray(value)) return value.map(asJsonValue);
|
|
19
|
+
if (isRecord(value)) {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, item] of Object.entries(value)) {
|
|
22
|
+
if (item !== void 0) result[key] = asJsonValue(item);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
return String(value);
|
|
27
|
+
}
|
|
28
|
+
function stableStringify(value) {
|
|
29
|
+
return JSON.stringify(sortValue(asJsonValue(value)));
|
|
30
|
+
}
|
|
31
|
+
function sortValue(value) {
|
|
32
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
33
|
+
if (isRecord(value)) {
|
|
34
|
+
const result = {};
|
|
35
|
+
for (const key of Object.keys(value).sort()) {
|
|
36
|
+
const item = value[key];
|
|
37
|
+
if (item !== void 0) result[key] = sortValue(item);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function normalizeSemanticText(text) {
|
|
44
|
+
return text.normalize("NFKC").replace(ISO_TIMESTAMP, "<timestamp>").replace(UNIX_TIMESTAMP, "<timestamp>").replace(/\s+/gu, " ").trim();
|
|
45
|
+
}
|
|
46
|
+
function contentToText(content) {
|
|
47
|
+
return content.map((part) => {
|
|
48
|
+
if (part.type === "text") return part.text;
|
|
49
|
+
if (part.type === "json") return stableStringify(part.value);
|
|
50
|
+
return `[image:${part.source}]`;
|
|
51
|
+
}).join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/core/fingerprint.ts
|
|
55
|
+
var defaultFingerprint = (request) => {
|
|
56
|
+
const finalUser = [...request.messages].reverse().find((message) => message.role === "user");
|
|
57
|
+
const shape = request.messages.map((message) => ({
|
|
58
|
+
role: message.role,
|
|
59
|
+
content: message.content.map((part) => part.type),
|
|
60
|
+
...message.name === void 0 ? {} : { name: message.name },
|
|
61
|
+
...message.toolCallId === void 0 ? {} : { toolCallId: message.toolCallId }
|
|
62
|
+
}));
|
|
63
|
+
const tools = request.tools.map((tool) => ({
|
|
64
|
+
name: tool.name,
|
|
65
|
+
...tool.description === void 0 ? {} : { description: normalizeSemanticText(tool.description) },
|
|
66
|
+
inputSchema: tool.inputSchema
|
|
67
|
+
}));
|
|
68
|
+
const semanticFinalUserTurn = finalUser ? normalizeSemanticText(contentToText(finalUser.content)) : "";
|
|
69
|
+
const decisionHistory = request.messages.flatMap((message, index) => {
|
|
70
|
+
if (message.role === "system" || message === finalUser) return [];
|
|
71
|
+
return [{
|
|
72
|
+
index,
|
|
73
|
+
role: message.role,
|
|
74
|
+
content: normalizeSemanticText(contentToText(message.content)),
|
|
75
|
+
toolCalls: message.toolCalls ?? [],
|
|
76
|
+
...message.name === void 0 ? {} : { name: message.name },
|
|
77
|
+
...message.toolCallId === void 0 ? {} : { toolCallId: message.toolCallId }
|
|
78
|
+
}];
|
|
79
|
+
});
|
|
80
|
+
const canonical = stableStringify({ tools, shape, decisionHistory, semanticFinalUserTurn });
|
|
81
|
+
return `sha256:${crypto.createHash("sha256").update(canonical).digest("hex")}`;
|
|
82
|
+
};
|
|
83
|
+
var FileCassetteStore = class {
|
|
84
|
+
directory;
|
|
85
|
+
constructor(directory = ".agentcassette/cassettes") {
|
|
86
|
+
this.directory = path.resolve(directory);
|
|
87
|
+
}
|
|
88
|
+
async exists(name) {
|
|
89
|
+
try {
|
|
90
|
+
await promises.stat(this.pathFor(name));
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (hasCode(error, "ENOENT")) return false;
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async read(name) {
|
|
98
|
+
const source = await promises.readFile(this.pathFor(name), "utf8");
|
|
99
|
+
return parseCassette(source, name);
|
|
100
|
+
}
|
|
101
|
+
async write(cassette) {
|
|
102
|
+
const destination = this.pathFor(cassette.name);
|
|
103
|
+
await promises.mkdir(path.dirname(destination), { recursive: true });
|
|
104
|
+
const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
|
|
105
|
+
await promises.writeFile(temporary, `${JSON.stringify(cassette, null, 2)}
|
|
106
|
+
`, { encoding: "utf8", mode: 384 });
|
|
107
|
+
await promises.rename(temporary, destination);
|
|
108
|
+
}
|
|
109
|
+
async list() {
|
|
110
|
+
try {
|
|
111
|
+
const entries = await promises.readdir(this.directory, { withFileTypes: true });
|
|
112
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -5)).filter(isValidCassetteName).sort();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (hasCode(error, "ENOENT")) return [];
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async remove(name) {
|
|
119
|
+
await promises.rm(this.pathFor(name), { force: true });
|
|
120
|
+
}
|
|
121
|
+
pathFor(name) {
|
|
122
|
+
if (!isValidCassetteName(name)) {
|
|
123
|
+
throw new Error("Cassette names must start with a lowercase letter or digit and contain only lowercase letters, digits, dots, underscores, or hyphens.");
|
|
124
|
+
}
|
|
125
|
+
return path.join(this.directory, `${name}.json`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
function parseCassette(source, expectedName) {
|
|
129
|
+
const value = JSON.parse(source);
|
|
130
|
+
if (!isCassette(value)) throw new Error(`Invalid agentcassette file${expectedName ? `: ${expectedName}` : ""}.`);
|
|
131
|
+
if (expectedName !== void 0 && value.name !== expectedName) {
|
|
132
|
+
throw new Error(`Cassette identity mismatch: expected "${expectedName}", file contains "${value.name}".`);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
function isValidCassetteName(name) {
|
|
137
|
+
return /^[a-z0-9][a-z0-9._-]*$/u.test(name);
|
|
138
|
+
}
|
|
139
|
+
function isCassette(value) {
|
|
140
|
+
if (!isObject(value)) return false;
|
|
141
|
+
return value.schemaVersion === 1 && typeof value.name === "string" && isValidCassetteName(value.name) && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && (value.metadata === void 0 || isMetadata(value.metadata)) && Array.isArray(value.turns) && value.turns.every((turn, index) => isTurn(turn, index));
|
|
142
|
+
}
|
|
143
|
+
function isTurn(value, index) {
|
|
144
|
+
return isObject(value) && value.index === index && typeof value.fingerprint === "string" && isRequest(value.request) && Array.isArray(value.events) && value.events.every(isEvent) && isResponse(value.response);
|
|
145
|
+
}
|
|
146
|
+
function isMetadata(value) {
|
|
147
|
+
return isObject(value) && (value.provider === void 0 || typeof value.provider === "string") && (value.testFile === void 0 || typeof value.testFile === "string") && (value.tags === void 0 || Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === "string")) && (value.fingerprint === void 0 || value.fingerprint === "default" || value.fingerprint === "custom");
|
|
148
|
+
}
|
|
149
|
+
function isEvent(value) {
|
|
150
|
+
if (!isObject(value)) return false;
|
|
151
|
+
if (value.type === "messages.sent") return Array.isArray(value.messages) && value.messages.every(isMessage);
|
|
152
|
+
if (value.type === "tool.result.returned") return isMessage(value.message);
|
|
153
|
+
if (value.type === "tool.call.proposed") return isToolCall(value.call);
|
|
154
|
+
return value.type === "output.final" && Array.isArray(value.content) && value.content.every(isContentPart);
|
|
155
|
+
}
|
|
156
|
+
function isRequest(value) {
|
|
157
|
+
return isObject(value) && Array.isArray(value.messages) && value.messages.every(isMessage) && Array.isArray(value.tools) && value.tools.every((tool) => isObject(tool) && typeof tool.name === "string" && isJsonValue(tool.inputSchema)) && (value.model === void 0 || typeof value.model === "string");
|
|
158
|
+
}
|
|
159
|
+
function isResponse(value) {
|
|
160
|
+
return isObject(value) && Array.isArray(value.content) && value.content.every(isContentPart) && Array.isArray(value.toolCalls) && value.toolCalls.every(isToolCall) && (value.sequence === void 0 || Array.isArray(value.sequence) && value.sequence.every(isResponseItem)) && (value.id === void 0 || typeof value.id === "string") && (value.model === void 0 || typeof value.model === "string") && (value.finishReason === void 0 || typeof value.finishReason === "string") && (value.usage === void 0 || isObject(value.usage) && typeof value.usage.inputTokens === "number" && typeof value.usage.outputTokens === "number" && typeof value.usage.totalTokens === "number" && (value.usage.costUsd === void 0 || typeof value.usage.costUsd === "number"));
|
|
161
|
+
}
|
|
162
|
+
function isMessage(value) {
|
|
163
|
+
return isObject(value) && (value.role === "system" || value.role === "user" || value.role === "assistant" || value.role === "tool") && Array.isArray(value.content) && value.content.every(isContentPart) && (value.name === void 0 || typeof value.name === "string") && (value.toolCallId === void 0 || typeof value.toolCallId === "string") && (value.toolCalls === void 0 || Array.isArray(value.toolCalls) && value.toolCalls.every(isToolCall));
|
|
164
|
+
}
|
|
165
|
+
function isContentPart(value) {
|
|
166
|
+
if (!isObject(value)) return false;
|
|
167
|
+
if (value.type === "text") return typeof value.text === "string";
|
|
168
|
+
if (value.type === "image") return typeof value.source === "string";
|
|
169
|
+
return value.type === "json" && isJsonValue(value.value);
|
|
170
|
+
}
|
|
171
|
+
function isResponseItem(value) {
|
|
172
|
+
return isObject(value) && (value.type === "content" && isContentPart(value.part) || value.type === "toolCall" && isToolCall(value.call));
|
|
173
|
+
}
|
|
174
|
+
function isToolCall(value) {
|
|
175
|
+
return isObject(value) && typeof value.name === "string" && isJsonValue(value.arguments) && (value.id === void 0 || typeof value.id === "string");
|
|
176
|
+
}
|
|
177
|
+
function isJsonValue(value) {
|
|
178
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
179
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
180
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
181
|
+
return isObject(value) && Object.values(value).every(isJsonValue);
|
|
182
|
+
}
|
|
183
|
+
function isObject(value) {
|
|
184
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
185
|
+
}
|
|
186
|
+
function hasCode(error, code) {
|
|
187
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
188
|
+
}
|
|
189
|
+
async function readUsageEvents(file) {
|
|
190
|
+
let source;
|
|
191
|
+
try {
|
|
192
|
+
source = await promises.readFile(file, "utf8");
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return [];
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
return source.split("\n").flatMap((line) => {
|
|
198
|
+
if (!line.trim()) return [];
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(line);
|
|
201
|
+
return isUsageEvent(parsed) ? [parsed] : [];
|
|
202
|
+
} catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function isUsageEvent(value) {
|
|
208
|
+
if (typeof value !== "object" || value === null) return false;
|
|
209
|
+
const event = value;
|
|
210
|
+
return (event.type === "use" || event.type === "replay") && typeof event.cassette === "string" && typeof event.timestamp === "string";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/cli/program.ts
|
|
214
|
+
async function runCli(argv) {
|
|
215
|
+
const delimiter = argv.indexOf("--");
|
|
216
|
+
const command = delimiter >= 0 ? argv.slice(delimiter + 1) : [];
|
|
217
|
+
const ownArgs = delimiter >= 0 ? argv.slice(0, delimiter) : argv;
|
|
218
|
+
const directory = optionValue(ownArgs, "--dir") ?? process.env.AGENTCASSETTE_DIR ?? ".agentcassette/cassettes";
|
|
219
|
+
const context = {
|
|
220
|
+
directory,
|
|
221
|
+
usageFile: process.env.AGENTCASSETTE_USAGE_FILE ?? path.join(path.dirname(directory), "usage.ndjson"),
|
|
222
|
+
cwd: process.cwd()
|
|
223
|
+
};
|
|
224
|
+
const positional = ownArgs.filter((argument, index) => argument !== "--yes" && argument !== "--json" && argument !== "--dir" && ownArgs[index - 1] !== "--dir");
|
|
225
|
+
const action = positional[0] ?? "help";
|
|
226
|
+
if (action === "list") return listCassettes(context, ownArgs.includes("--json"));
|
|
227
|
+
if (action === "stats") return showStats(context, ownArgs.includes("--json"));
|
|
228
|
+
if (action === "prune") return pruneCassettes(context, ownArgs.includes("--yes"), optionValues(ownArgs, "--name"));
|
|
229
|
+
if (action === "verify") return verifyCassettes(context, command);
|
|
230
|
+
if (action === "help" || action === "--help" || action === "-h") {
|
|
231
|
+
printHelp();
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
console.error(`Unknown command: ${action}`);
|
|
235
|
+
printHelp();
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
async function listCassettes(context, json) {
|
|
239
|
+
const store = new FileCassetteStore(context.directory);
|
|
240
|
+
const cassettes = await readAll(store);
|
|
241
|
+
if (json) {
|
|
242
|
+
console.log(JSON.stringify(cassettes.map(summary), null, 2));
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
if (cassettes.length === 0) {
|
|
246
|
+
console.log("No cassettes found.");
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
console.log("NAME TURNS PROVIDER UPDATED");
|
|
250
|
+
for (const cassette of cassettes) {
|
|
251
|
+
console.log(`${cassette.name} ${cassette.turns.length} ${cassette.metadata?.provider ?? "unknown"} ${cassette.updatedAt}`);
|
|
252
|
+
}
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
async function showStats(context, json) {
|
|
256
|
+
const store = new FileCassetteStore(context.directory);
|
|
257
|
+
const cassettes = await readAll(store);
|
|
258
|
+
const events = await readUsageEvents(context.usageFile);
|
|
259
|
+
let turns = 0;
|
|
260
|
+
let inputTokens = 0;
|
|
261
|
+
let outputTokens = 0;
|
|
262
|
+
let recordedCostUsd = 0;
|
|
263
|
+
for (const cassette of cassettes) {
|
|
264
|
+
turns += cassette.turns.length;
|
|
265
|
+
for (const turn of cassette.turns) {
|
|
266
|
+
inputTokens += turn.response.usage?.inputTokens ?? 0;
|
|
267
|
+
outputTokens += turn.response.usage?.outputTokens ?? 0;
|
|
268
|
+
recordedCostUsd += turn.response.usage?.costUsd ?? 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const replayEvents = events.filter((event) => event.type === "replay");
|
|
272
|
+
const stats = {
|
|
273
|
+
cassettes: cassettes.length,
|
|
274
|
+
recordedTurns: turns,
|
|
275
|
+
recordedInputTokens: inputTokens,
|
|
276
|
+
recordedOutputTokens: outputTokens,
|
|
277
|
+
recordedCostUsd,
|
|
278
|
+
replayedTurns: replayEvents.length,
|
|
279
|
+
moneySavedUsd: replayEvents.reduce((total, event) => total + (event.costSavedUsd ?? 0), 0)
|
|
280
|
+
};
|
|
281
|
+
if (json) console.log(JSON.stringify(stats, null, 2));
|
|
282
|
+
else {
|
|
283
|
+
console.log(`Cassettes: ${stats.cassettes}`);
|
|
284
|
+
console.log(`Recorded turns: ${stats.recordedTurns}`);
|
|
285
|
+
console.log(`Recorded tokens: ${stats.recordedInputTokens + stats.recordedOutputTokens} (${stats.recordedInputTokens} input, ${stats.recordedOutputTokens} output)`);
|
|
286
|
+
console.log(`Replayed turns: ${stats.replayedTurns}`);
|
|
287
|
+
console.log(`Recorded API cost: $${stats.recordedCostUsd.toFixed(4)}`);
|
|
288
|
+
console.log(`Money saved by tracked replays: $${stats.moneySavedUsd.toFixed(4)}`);
|
|
289
|
+
}
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
async function pruneCassettes(context, confirmed, selected) {
|
|
293
|
+
const store = new FileCassetteStore(context.directory);
|
|
294
|
+
const names = await store.list();
|
|
295
|
+
const references = await sourceReferences(context.cwd, names);
|
|
296
|
+
const usage = await readUsageEvents(context.usageFile);
|
|
297
|
+
const observed = new Set(usage.filter((event) => event.type === "use").map((event) => event.cassette));
|
|
298
|
+
const unused = names.filter((name) => !references.has(name) && !observed.has(name));
|
|
299
|
+
if (unused.length === 0) {
|
|
300
|
+
console.log("No unused cassettes found.");
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
if (!confirmed) {
|
|
304
|
+
console.log(`Would prune ${unused.length} unused cassette(s): ${unused.join(", ")}`);
|
|
305
|
+
console.log("Delete reviewed candidates with --yes and one --name <cassette> per cassette.");
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
if (selected.length === 0) {
|
|
309
|
+
console.error("Refusing heuristic deletion. Add --name <cassette> for each reviewed candidate.");
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
const rejected = selected.filter((name) => !unused.includes(name));
|
|
313
|
+
if (rejected.length > 0) {
|
|
314
|
+
console.error(`Refusing to prune cassette(s) that are not unused candidates: ${rejected.join(", ")}`);
|
|
315
|
+
return 1;
|
|
316
|
+
}
|
|
317
|
+
for (const name of selected) await store.remove(name);
|
|
318
|
+
console.log(`Pruned ${selected.length} unused cassette(s): ${selected.join(", ")}`);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
async function verifyCassettes(context, command) {
|
|
322
|
+
const store = new FileCassetteStore(context.directory);
|
|
323
|
+
const cassettes = await readAll(store);
|
|
324
|
+
const failures = [];
|
|
325
|
+
for (const cassette of cassettes) {
|
|
326
|
+
for (const [index, turn] of cassette.turns.entries()) {
|
|
327
|
+
if (turn.index !== index) failures.push(`${cassette.name}: turn ${index + 1} has index ${turn.index}`);
|
|
328
|
+
if (cassette.metadata?.fingerprint !== "custom") {
|
|
329
|
+
const actual = defaultFingerprint(turn.request);
|
|
330
|
+
if (actual !== turn.fingerprint) failures.push(`${cassette.name}: turn ${index + 1} fingerprint is stale`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const references = await sourceReferences(context.cwd, cassettes.map((cassette) => cassette.name));
|
|
335
|
+
const usage = await readUsageEvents(context.usageFile);
|
|
336
|
+
const observed = new Set(usage.filter((event) => event.type === "use").map((event) => event.cassette));
|
|
337
|
+
for (const cassette of cassettes) {
|
|
338
|
+
if (!references.has(cassette.name) && !observed.has(cassette.name)) {
|
|
339
|
+
failures.push(`${cassette.name}: no test/source reference or runtime usage found`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (failures.length > 0) {
|
|
343
|
+
for (const failure of failures) console.error(`\u2717 ${failure}`);
|
|
344
|
+
return 1;
|
|
345
|
+
}
|
|
346
|
+
if (command.length > 0) {
|
|
347
|
+
const exitCode = await runCommand(command, context);
|
|
348
|
+
if (exitCode !== 0) return exitCode;
|
|
349
|
+
}
|
|
350
|
+
console.log(`Verified ${cassettes.length} cassette(s)${command.length > 0 ? " against the test command" : " structurally"}.`);
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
async function runCommand(command, context) {
|
|
354
|
+
const executable = command[0];
|
|
355
|
+
if (!executable) return 0;
|
|
356
|
+
return new Promise((resolveExit, reject) => {
|
|
357
|
+
const child = child_process.spawn(executable, command.slice(1), {
|
|
358
|
+
cwd: context.cwd,
|
|
359
|
+
stdio: "inherit",
|
|
360
|
+
env: {
|
|
361
|
+
...process.env,
|
|
362
|
+
AGENTCASSETTE_MODE: "replay",
|
|
363
|
+
AGENTCASSETTE_DIR: context.directory,
|
|
364
|
+
AGENTCASSETTE_USAGE_FILE: context.usageFile
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
child.once("error", reject);
|
|
368
|
+
child.once("exit", (code) => resolveExit(code ?? 1));
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async function readAll(store) {
|
|
372
|
+
const names = await store.list();
|
|
373
|
+
return Promise.all(names.map((name) => store.read(name)));
|
|
374
|
+
}
|
|
375
|
+
function summary(cassette) {
|
|
376
|
+
return {
|
|
377
|
+
name: cassette.name,
|
|
378
|
+
turns: cassette.turns.length,
|
|
379
|
+
provider: cassette.metadata?.provider ?? null,
|
|
380
|
+
updatedAt: cassette.updatedAt
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
async function sourceReferences(cwd, names) {
|
|
384
|
+
const found = /* @__PURE__ */ new Set();
|
|
385
|
+
if (names.length === 0) return found;
|
|
386
|
+
const files = await sourceFiles(cwd);
|
|
387
|
+
await Promise.all(files.map(async (file) => {
|
|
388
|
+
const source = await promises.readFile(file, "utf8");
|
|
389
|
+
for (const name of names) if (source.includes(name)) found.add(name);
|
|
390
|
+
}));
|
|
391
|
+
return found;
|
|
392
|
+
}
|
|
393
|
+
async function sourceFiles(directory) {
|
|
394
|
+
const result = [];
|
|
395
|
+
let entries;
|
|
396
|
+
try {
|
|
397
|
+
entries = await promises.readdir(directory, { withFileTypes: true });
|
|
398
|
+
} catch {
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
for (const entry of entries) {
|
|
402
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === ".agentcassette") continue;
|
|
403
|
+
const path$1 = path.join(directory, entry.name);
|
|
404
|
+
if (entry.isDirectory()) result.push(...await sourceFiles(path$1));
|
|
405
|
+
else if (/\.(?:[cm]?[jt]sx?)$/u.test(entry.name)) result.push(path$1);
|
|
406
|
+
}
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
function optionValue(args, name) {
|
|
410
|
+
const index = args.indexOf(name);
|
|
411
|
+
return index >= 0 ? args[index + 1] : void 0;
|
|
412
|
+
}
|
|
413
|
+
function optionValues(args, name) {
|
|
414
|
+
return args.flatMap((argument, index) => argument === name && args[index + 1] !== void 0 ? [args[index + 1]] : []);
|
|
415
|
+
}
|
|
416
|
+
function printHelp() {
|
|
417
|
+
console.log(`agentcassette <command> [options]
|
|
418
|
+
|
|
419
|
+
Commands:
|
|
420
|
+
list [--json] List cassettes
|
|
421
|
+
stats [--json] Show recorded usage and tracked savings
|
|
422
|
+
prune [--yes --name <id>] Find unused cassettes; delete reviewed names
|
|
423
|
+
verify [-- <test command>] Validate cassettes; optionally replay all tests
|
|
424
|
+
|
|
425
|
+
Options:
|
|
426
|
+
--dir <path> Cassette directory (default: .agentcassette/cassettes)`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/cli/index.ts
|
|
430
|
+
void runCli(process.argv.slice(2)).then((code) => {
|
|
431
|
+
process.exitCode = code;
|
|
432
|
+
}).catch((error) => {
|
|
433
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
434
|
+
process.exitCode = 1;
|
|
435
|
+
});
|
|
436
|
+
//# sourceMappingURL=cli.cjs.map
|
|
437
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/canonical.ts","../src/core/fingerprint.ts","../src/core/storage.ts","../src/adapters/usage-reader.ts","../src/cli/program.ts","../src/cli/index.ts"],"names":["createHash","resolve","stat","readFile","mkdir","dirname","writeFile","rename","readdir","rm","join","spawn","path"],"mappings":"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","file":"cli.cjs","sourcesContent":["import type { ContentPart, JsonValue, MessageRole, SemanticMessage } from \"./types.js\";\n\nconst ISO_TIMESTAMP = /\\b\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})\\b/giu;\nconst UNIX_TIMESTAMP = /\\b1[5-9]\\d{8}(?:\\d{3})?\\b/gu;\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function asJsonValue(value: unknown): JsonValue {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : String(value);\n if (Array.isArray(value)) return value.map(asJsonValue);\n if (isRecord(value)) {\n const result: { [key: string]: JsonValue } = {};\n for (const [key, item] of Object.entries(value)) {\n if (item !== undefined) result[key] = asJsonValue(item);\n }\n return result;\n }\n return String(value);\n}\n\nexport function stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(asJsonValue(value)));\n}\n\nfunction sortValue(value: JsonValue): JsonValue {\n if (Array.isArray(value)) return value.map(sortValue);\n if (isRecord(value)) {\n const result: { [key: string]: JsonValue } = {};\n for (const key of Object.keys(value).sort()) {\n const item = value[key];\n if (item !== undefined) result[key] = sortValue(item as JsonValue);\n }\n return result;\n }\n return value;\n}\n\nexport function normalizeSemanticText(text: string): string {\n return text\n .normalize(\"NFKC\")\n .replace(ISO_TIMESTAMP, \"<timestamp>\")\n .replace(UNIX_TIMESTAMP, \"<timestamp>\")\n .replace(/\\s+/gu, \" \")\n .trim();\n}\n\nexport function textPart(text: string): ContentPart {\n return { type: \"text\", text };\n}\n\nexport function normalizeRole(value: unknown): MessageRole {\n if (value === \"system\" || value === \"assistant\" || value === \"tool\") return value;\n if (value === \"model\") return \"assistant\";\n if (value === \"function\") return \"tool\";\n return \"user\";\n}\n\nexport function contentToText(content: ContentPart[]): string {\n return content\n .map((part) => {\n if (part.type === \"text\") return part.text;\n if (part.type === \"json\") return stableStringify(part.value);\n return `[image:${part.source}]`;\n })\n .join(\"\\n\");\n}\n\nexport function unknownToContent(value: unknown): ContentPart[] {\n if (typeof value === \"string\") return [textPart(value)];\n if (value === null || value === undefined) return [];\n if (Array.isArray(value)) {\n return value.flatMap((part) => {\n if (typeof part === \"string\") return [textPart(part)];\n if (!isRecord(part)) return [{ type: \"json\", value: asJsonValue(part) } satisfies ContentPart];\n if (typeof part.text === \"string\") return [textPart(part.text)];\n if (part.type === \"text\" && typeof part.text === \"string\") return [textPart(part.text)];\n if (part.type === \"image_url\" && isRecord(part.image_url) && typeof part.image_url.url === \"string\") {\n return [{ type: \"image\", source: part.image_url.url } satisfies ContentPart];\n }\n return [{ type: \"json\", value: asJsonValue(part) } satisfies ContentPart];\n });\n }\n return [{ type: \"json\", value: asJsonValue(value) }];\n}\n\nexport function messageFromUnknown(value: unknown): SemanticMessage {\n if (!isRecord(value)) return { role: \"user\", content: unknownToContent(value) };\n const message: SemanticMessage = {\n role: normalizeRole(value.role),\n content: unknownToContent(value.content),\n };\n if (typeof value.name === \"string\") message.name = value.name;\n if (typeof value.tool_call_id === \"string\") message.toolCallId = value.tool_call_id;\n if (typeof value.toolCallId === \"string\") message.toolCallId = value.toolCallId;\n return message;\n}\n","import { createHash } from \"node:crypto\";\nimport { contentToText, normalizeSemanticText, stableStringify } from \"./canonical.js\";\nimport type { FingerprintFunction, SemanticRequest } from \"./types.js\";\n\nexport const defaultFingerprint: FingerprintFunction = (request) => {\n const finalUser = [...request.messages].reverse().find((message) => message.role === \"user\");\n const shape = request.messages.map((message) => ({\n role: message.role,\n content: message.content.map((part) => part.type),\n ...(message.name === undefined ? {} : { name: message.name }),\n ...(message.toolCallId === undefined ? {} : { toolCallId: message.toolCallId }),\n }));\n const tools = request.tools.map((tool) => ({\n name: tool.name,\n ...(tool.description === undefined ? {} : { description: normalizeSemanticText(tool.description) }),\n inputSchema: tool.inputSchema,\n }));\n const semanticFinalUserTurn = finalUser\n ? normalizeSemanticText(contentToText(finalUser.content))\n : \"\";\n const decisionHistory = request.messages.flatMap((message, index) => {\n if (message.role === \"system\" || message === finalUser) return [];\n return [{\n index,\n role: message.role,\n content: normalizeSemanticText(contentToText(message.content)),\n toolCalls: message.toolCalls ?? [],\n ...(message.name === undefined ? {} : { name: message.name }),\n ...(message.toolCallId === undefined ? {} : { toolCallId: message.toolCallId }),\n }];\n });\n const canonical = stableStringify({ tools, shape, decisionHistory, semanticFinalUserTurn });\n return `sha256:${createHash(\"sha256\").update(canonical).digest(\"hex\")}`;\n};\n\nexport function explainDefaultFingerprint(request: SemanticRequest): {\n conversationRoles: string[];\n finalUserTurn: string;\n toolNames: string[];\n} {\n const finalUser = [...request.messages].reverse().find((message) => message.role === \"user\");\n return {\n conversationRoles: request.messages.map((message) => message.role),\n finalUserTurn: finalUser ? normalizeSemanticText(contentToText(finalUser.content)) : \"\",\n toolNames: request.tools.map((tool) => tool.name),\n };\n}\n","import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport type { Cassette, ContentPart, JsonValue, SemanticMessage, SemanticRequest, SemanticResponse, ToolCall } from \"./types.js\";\n\nexport interface CassetteStore {\n exists(name: string): Promise<boolean>;\n read(name: string): Promise<Cassette>;\n write(cassette: Cassette): Promise<void>;\n list(): Promise<string[]>;\n remove(name: string): Promise<void>;\n}\n\nexport class FileCassetteStore implements CassetteStore {\n readonly directory: string;\n\n constructor(directory = \".agentcassette/cassettes\") {\n this.directory = resolve(directory);\n }\n\n async exists(name: string): Promise<boolean> {\n try {\n await stat(this.pathFor(name));\n return true;\n } catch (error) {\n if (hasCode(error, \"ENOENT\")) return false;\n throw error;\n }\n }\n\n async read(name: string): Promise<Cassette> {\n const source = await readFile(this.pathFor(name), \"utf8\");\n return parseCassette(source, name);\n }\n\n async write(cassette: Cassette): Promise<void> {\n const destination = this.pathFor(cassette.name);\n await mkdir(dirname(destination), { recursive: true });\n const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;\n await writeFile(temporary, `${JSON.stringify(cassette, null, 2)}\\n`, { encoding: \"utf8\", mode: 0o600 });\n await rename(temporary, destination);\n }\n\n async list(): Promise<string[]> {\n try {\n const entries = await readdir(this.directory, { withFileTypes: true });\n return entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n .map((entry) => entry.name.slice(0, -5))\n .filter(isValidCassetteName)\n .sort();\n } catch (error) {\n if (hasCode(error, \"ENOENT\")) return [];\n throw error;\n }\n }\n\n async remove(name: string): Promise<void> {\n await rm(this.pathFor(name), { force: true });\n }\n\n pathFor(name: string): string {\n if (!isValidCassetteName(name)) {\n throw new Error(\"Cassette names must start with a lowercase letter or digit and contain only lowercase letters, digits, dots, underscores, or hyphens.\");\n }\n return join(this.directory, `${name}.json`);\n }\n}\n\nexport class MemoryCassetteStore implements CassetteStore {\n readonly cassettes = new Map<string, Cassette>();\n\n async exists(name: string): Promise<boolean> {\n return this.cassettes.has(name);\n }\n\n async read(name: string): Promise<Cassette> {\n const cassette = this.cassettes.get(name);\n if (!cassette) throw new Error(`Cassette \"${name}\" does not exist.`);\n return structuredClone(cassette);\n }\n\n async write(cassette: Cassette): Promise<void> {\n this.cassettes.set(cassette.name, structuredClone(cassette));\n }\n\n async list(): Promise<string[]> {\n return [...this.cassettes.keys()].sort();\n }\n\n async remove(name: string): Promise<void> {\n this.cassettes.delete(name);\n }\n}\n\nexport function parseCassette(source: string, expectedName?: string): Cassette {\n const value: unknown = JSON.parse(source);\n if (!isCassette(value)) throw new Error(`Invalid agentcassette file${expectedName ? `: ${expectedName}` : \"\"}.`);\n if (expectedName !== undefined && value.name !== expectedName) {\n throw new Error(`Cassette identity mismatch: expected \"${expectedName}\", file contains \"${value.name}\".`);\n }\n return value;\n}\n\nfunction isValidCassetteName(name: string): boolean {\n return /^[a-z0-9][a-z0-9._-]*$/u.test(name);\n}\n\nfunction isCassette(value: unknown): value is Cassette {\n if (!isObject(value)) return false;\n return value.schemaVersion === 1\n && typeof value.name === \"string\"\n && isValidCassetteName(value.name)\n && typeof value.createdAt === \"string\"\n && typeof value.updatedAt === \"string\"\n && (value.metadata === undefined || isMetadata(value.metadata))\n && Array.isArray(value.turns)\n && value.turns.every((turn, index) => isTurn(turn, index));\n}\n\nfunction isTurn(value: unknown, index: number): boolean {\n return isObject(value)\n && value.index === index\n && typeof value.fingerprint === \"string\"\n && isRequest(value.request)\n && Array.isArray(value.events)\n && value.events.every(isEvent)\n && isResponse(value.response);\n}\n\nfunction isMetadata(value: unknown): boolean {\n return isObject(value)\n && (value.provider === undefined || typeof value.provider === \"string\")\n && (value.testFile === undefined || typeof value.testFile === \"string\")\n && (value.tags === undefined || (Array.isArray(value.tags) && value.tags.every((tag) => typeof tag === \"string\")))\n && (value.fingerprint === undefined || value.fingerprint === \"default\" || value.fingerprint === \"custom\");\n}\n\nfunction isEvent(value: unknown): boolean {\n if (!isObject(value)) return false;\n if (value.type === \"messages.sent\") return Array.isArray(value.messages) && value.messages.every(isMessage);\n if (value.type === \"tool.result.returned\") return isMessage(value.message);\n if (value.type === \"tool.call.proposed\") return isToolCall(value.call);\n return value.type === \"output.final\" && Array.isArray(value.content) && value.content.every(isContentPart);\n}\n\nfunction isRequest(value: unknown): value is SemanticRequest {\n return isObject(value)\n && Array.isArray(value.messages)\n && value.messages.every(isMessage)\n && Array.isArray(value.tools)\n && value.tools.every((tool) => isObject(tool) && typeof tool.name === \"string\" && isJsonValue(tool.inputSchema))\n && (value.model === undefined || typeof value.model === \"string\");\n}\n\nfunction isResponse(value: unknown): value is SemanticResponse {\n return isObject(value)\n && Array.isArray(value.content)\n && value.content.every(isContentPart)\n && Array.isArray(value.toolCalls)\n && value.toolCalls.every(isToolCall)\n && (value.sequence === undefined || (Array.isArray(value.sequence) && value.sequence.every(isResponseItem)))\n && (value.id === undefined || typeof value.id === \"string\")\n && (value.model === undefined || typeof value.model === \"string\")\n && (value.finishReason === undefined || typeof value.finishReason === \"string\")\n && (value.usage === undefined || (isObject(value.usage)\n && typeof value.usage.inputTokens === \"number\"\n && typeof value.usage.outputTokens === \"number\"\n && typeof value.usage.totalTokens === \"number\"\n && (value.usage.costUsd === undefined || typeof value.usage.costUsd === \"number\")));\n}\n\nfunction isMessage(value: unknown): value is SemanticMessage {\n return isObject(value)\n && (value.role === \"system\" || value.role === \"user\" || value.role === \"assistant\" || value.role === \"tool\")\n && Array.isArray(value.content)\n && value.content.every(isContentPart)\n && (value.name === undefined || typeof value.name === \"string\")\n && (value.toolCallId === undefined || typeof value.toolCallId === \"string\")\n && (value.toolCalls === undefined || (Array.isArray(value.toolCalls) && value.toolCalls.every(isToolCall)));\n}\n\nfunction isContentPart(value: unknown): value is ContentPart {\n if (!isObject(value)) return false;\n if (value.type === \"text\") return typeof value.text === \"string\";\n if (value.type === \"image\") return typeof value.source === \"string\";\n return value.type === \"json\" && isJsonValue(value.value);\n}\n\nfunction isResponseItem(value: unknown): boolean {\n return isObject(value)\n && ((value.type === \"content\" && isContentPart(value.part))\n || (value.type === \"toolCall\" && isToolCall(value.call)));\n}\n\nfunction isToolCall(value: unknown): value is ToolCall {\n return isObject(value)\n && typeof value.name === \"string\"\n && isJsonValue(value.arguments)\n && (value.id === undefined || typeof value.id === \"string\");\n}\n\nfunction isJsonValue(value: unknown): value is JsonValue {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n if (typeof value === \"number\") return Number.isFinite(value);\n if (Array.isArray(value)) return value.every(isJsonValue);\n return isObject(value) && Object.values(value).every(isJsonValue);\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasCode(error: unknown, code: string): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error && (error as { code?: unknown }).code === code;\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { UsageEvent } from \"./usage.js\";\n\nexport async function readUsageEvents(file: string): Promise<UsageEvent[]> {\n let source: string;\n try {\n source = await readFile(file, \"utf8\");\n } catch (error) {\n if (typeof error === \"object\" && error !== null && \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\") return [];\n throw error;\n }\n return source.split(\"\\n\").flatMap((line) => {\n if (!line.trim()) return [];\n try {\n const parsed: unknown = JSON.parse(line);\n return isUsageEvent(parsed) ? [parsed] : [];\n } catch {\n return [];\n }\n });\n}\n\nfunction isUsageEvent(value: unknown): value is UsageEvent {\n if (typeof value !== \"object\" || value === null) return false;\n const event = value as Record<string, unknown>;\n return (event.type === \"use\" || event.type === \"replay\")\n && typeof event.cassette === \"string\"\n && typeof event.timestamp === \"string\";\n}\n","import { readFile, readdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { spawn } from \"node:child_process\";\nimport { defaultFingerprint } from \"../core/fingerprint.js\";\nimport { FileCassetteStore } from \"../core/storage.js\";\nimport type { Cassette } from \"../core/types.js\";\nimport { readUsageEvents } from \"../adapters/usage-reader.js\";\n\ninterface CliContext {\n directory: string;\n usageFile: string;\n cwd: string;\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n const delimiter = argv.indexOf(\"--\");\n const command = delimiter >= 0 ? argv.slice(delimiter + 1) : [];\n const ownArgs = delimiter >= 0 ? argv.slice(0, delimiter) : argv;\n const directory = optionValue(ownArgs, \"--dir\") ?? process.env.AGENTCASSETTE_DIR ?? \".agentcassette/cassettes\";\n const context: CliContext = {\n directory,\n usageFile: process.env.AGENTCASSETTE_USAGE_FILE ?? join(dirname(directory), \"usage.ndjson\"),\n cwd: process.cwd(),\n };\n const positional = ownArgs.filter((argument, index) => argument !== \"--yes\" && argument !== \"--json\" && argument !== \"--dir\" && ownArgs[index - 1] !== \"--dir\");\n const action = positional[0] ?? \"help\";\n\n if (action === \"list\") return listCassettes(context, ownArgs.includes(\"--json\"));\n if (action === \"stats\") return showStats(context, ownArgs.includes(\"--json\"));\n if (action === \"prune\") return pruneCassettes(context, ownArgs.includes(\"--yes\"), optionValues(ownArgs, \"--name\"));\n if (action === \"verify\") return verifyCassettes(context, command);\n if (action === \"help\" || action === \"--help\" || action === \"-h\") {\n printHelp();\n return 0;\n }\n console.error(`Unknown command: ${action}`);\n printHelp();\n return 1;\n}\n\nasync function listCassettes(context: CliContext, json: boolean): Promise<number> {\n const store = new FileCassetteStore(context.directory);\n const cassettes = await readAll(store);\n if (json) {\n console.log(JSON.stringify(cassettes.map(summary), null, 2));\n return 0;\n }\n if (cassettes.length === 0) {\n console.log(\"No cassettes found.\");\n return 0;\n }\n console.log(\"NAME\\tTURNS\\tPROVIDER\\tUPDATED\");\n for (const cassette of cassettes) {\n console.log(`${cassette.name}\\t${cassette.turns.length}\\t${cassette.metadata?.provider ?? \"unknown\"}\\t${cassette.updatedAt}`);\n }\n return 0;\n}\n\nasync function showStats(context: CliContext, json: boolean): Promise<number> {\n const store = new FileCassetteStore(context.directory);\n const cassettes = await readAll(store);\n const events = await readUsageEvents(context.usageFile);\n let turns = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let recordedCostUsd = 0;\n for (const cassette of cassettes) {\n turns += cassette.turns.length;\n for (const turn of cassette.turns) {\n inputTokens += turn.response.usage?.inputTokens ?? 0;\n outputTokens += turn.response.usage?.outputTokens ?? 0;\n recordedCostUsd += turn.response.usage?.costUsd ?? 0;\n }\n }\n const replayEvents = events.filter((event) => event.type === \"replay\");\n const stats = {\n cassettes: cassettes.length,\n recordedTurns: turns,\n recordedInputTokens: inputTokens,\n recordedOutputTokens: outputTokens,\n recordedCostUsd,\n replayedTurns: replayEvents.length,\n moneySavedUsd: replayEvents.reduce((total, event) => total + (event.costSavedUsd ?? 0), 0),\n };\n if (json) console.log(JSON.stringify(stats, null, 2));\n else {\n console.log(`Cassettes: ${stats.cassettes}`);\n console.log(`Recorded turns: ${stats.recordedTurns}`);\n console.log(`Recorded tokens: ${stats.recordedInputTokens + stats.recordedOutputTokens} (${stats.recordedInputTokens} input, ${stats.recordedOutputTokens} output)`);\n console.log(`Replayed turns: ${stats.replayedTurns}`);\n console.log(`Recorded API cost: $${stats.recordedCostUsd.toFixed(4)}`);\n console.log(`Money saved by tracked replays: $${stats.moneySavedUsd.toFixed(4)}`);\n }\n return 0;\n}\n\nasync function pruneCassettes(context: CliContext, confirmed: boolean, selected: string[]): Promise<number> {\n const store = new FileCassetteStore(context.directory);\n const names = await store.list();\n const references = await sourceReferences(context.cwd, names);\n const usage = await readUsageEvents(context.usageFile);\n const observed = new Set(usage.filter((event) => event.type === \"use\").map((event) => event.cassette));\n const unused = names.filter((name) => !references.has(name) && !observed.has(name));\n if (unused.length === 0) {\n console.log(\"No unused cassettes found.\");\n return 0;\n }\n if (!confirmed) {\n console.log(`Would prune ${unused.length} unused cassette(s): ${unused.join(\", \")}`);\n console.log(\"Delete reviewed candidates with --yes and one --name <cassette> per cassette.\");\n return 0;\n }\n if (selected.length === 0) {\n console.error(\"Refusing heuristic deletion. Add --name <cassette> for each reviewed candidate.\");\n return 1;\n }\n const rejected = selected.filter((name) => !unused.includes(name));\n if (rejected.length > 0) {\n console.error(`Refusing to prune cassette(s) that are not unused candidates: ${rejected.join(\", \")}`);\n return 1;\n }\n for (const name of selected) await store.remove(name);\n console.log(`Pruned ${selected.length} unused cassette(s): ${selected.join(\", \")}`);\n return 0;\n}\n\nasync function verifyCassettes(context: CliContext, command: string[]): Promise<number> {\n const store = new FileCassetteStore(context.directory);\n const cassettes = await readAll(store);\n const failures: string[] = [];\n for (const cassette of cassettes) {\n for (const [index, turn] of cassette.turns.entries()) {\n if (turn.index !== index) failures.push(`${cassette.name}: turn ${index + 1} has index ${turn.index}`);\n if (cassette.metadata?.fingerprint !== \"custom\") {\n const actual = defaultFingerprint(turn.request);\n if (actual !== turn.fingerprint) failures.push(`${cassette.name}: turn ${index + 1} fingerprint is stale`);\n }\n }\n }\n const references = await sourceReferences(context.cwd, cassettes.map((cassette) => cassette.name));\n const usage = await readUsageEvents(context.usageFile);\n const observed = new Set(usage.filter((event) => event.type === \"use\").map((event) => event.cassette));\n for (const cassette of cassettes) {\n if (!references.has(cassette.name) && !observed.has(cassette.name)) {\n failures.push(`${cassette.name}: no test/source reference or runtime usage found`);\n }\n }\n if (failures.length > 0) {\n for (const failure of failures) console.error(`✗ ${failure}`);\n return 1;\n }\n if (command.length > 0) {\n const exitCode = await runCommand(command, context);\n if (exitCode !== 0) return exitCode;\n }\n console.log(`Verified ${cassettes.length} cassette(s)${command.length > 0 ? \" against the test command\" : \" structurally\"}.`);\n return 0;\n}\n\nasync function runCommand(command: string[], context: CliContext): Promise<number> {\n const executable = command[0];\n if (!executable) return 0;\n return new Promise((resolveExit, reject) => {\n const child = spawn(executable, command.slice(1), {\n cwd: context.cwd,\n stdio: \"inherit\",\n env: {\n ...process.env,\n AGENTCASSETTE_MODE: \"replay\",\n AGENTCASSETTE_DIR: context.directory,\n AGENTCASSETTE_USAGE_FILE: context.usageFile,\n },\n });\n child.once(\"error\", reject);\n child.once(\"exit\", (code) => resolveExit(code ?? 1));\n });\n}\n\nasync function readAll(store: FileCassetteStore): Promise<Cassette[]> {\n const names = await store.list();\n return Promise.all(names.map((name) => store.read(name)));\n}\n\nfunction summary(cassette: Cassette): object {\n return {\n name: cassette.name,\n turns: cassette.turns.length,\n provider: cassette.metadata?.provider ?? null,\n updatedAt: cassette.updatedAt,\n };\n}\n\nasync function sourceReferences(cwd: string, names: string[]): Promise<Set<string>> {\n const found = new Set<string>();\n if (names.length === 0) return found;\n const files = await sourceFiles(cwd);\n await Promise.all(files.map(async (file) => {\n const source = await readFile(file, \"utf8\");\n for (const name of names) if (source.includes(name)) found.add(name);\n }));\n return found;\n}\n\nasync function sourceFiles(directory: string): Promise<string[]> {\n const result: string[] = [];\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n return result;\n }\n for (const entry of entries) {\n if (entry.name === \"node_modules\" || entry.name === \".git\" || entry.name === \"dist\" || entry.name === \".agentcassette\") continue;\n const path = join(directory, entry.name);\n if (entry.isDirectory()) result.push(...await sourceFiles(path));\n else if (/\\.(?:[cm]?[jt]sx?)$/u.test(entry.name)) result.push(path);\n }\n return result;\n}\n\nfunction optionValue(args: string[], name: string): string | undefined {\n const index = args.indexOf(name);\n return index >= 0 ? args[index + 1] : undefined;\n}\n\nfunction optionValues(args: string[], name: string): string[] {\n return args.flatMap((argument, index) => argument === name && args[index + 1] !== undefined ? [args[index + 1] as string] : []);\n}\n\nfunction printHelp(): void {\n console.log(`agentcassette <command> [options]\\n\\nCommands:\\n list [--json] List cassettes\\n stats [--json] Show recorded usage and tracked savings\\n prune [--yes --name <id>] Find unused cassettes; delete reviewed names\\n verify [-- <test command>] Validate cassettes; optionally replay all tests\\n\\nOptions:\\n --dir <path> Cassette directory (default: .agentcassette/cassettes)`);\n}\n","#!/usr/bin/env node\nimport { runCli } from \"./program.js\";\n\nvoid runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n}).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n"]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|