flatbread-cli 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,11 @@
2
2
 
3
3
  ## deja-vu
4
4
 
5
- `src/adapters/antigravity.ts` reads conversations the way deja-vu reads Windsurf trajectories
6
- (`internal/sources/windsurf.go`), and `test/fixtures/antigravity/*.pb` are its fixtures
7
- (`fixtures/registry/windsurf/cascade/`).
5
+ `src/adapters/antigravity.ts` reads `.pb` conversations the way deja-vu reads Windsurf trajectories
6
+ (`internal/sources/windsurf.go`) and transcripts the way it reads Antigravity's
7
+ (`internal/sources/antigravity.go`). `test/fixtures/antigravity/` holds its fixtures
8
+ (`fixtures/registry/windsurf/cascade/`, `fixtures/registry/antigravity/`) and transcripts from
9
+ `internal/sources/antigravity_toolcalls_test.go`.
8
10
 
9
11
  ```
10
12
  MIT License
@@ -4307,6 +4307,73 @@ function tallyFailures(loops) {
4307
4307
  };
4308
4308
  }
4309
4309
 
4310
+ // ../schema/src/harness.ts
4311
+ var HARNESS_INFO = {
4312
+ "claude-code": {
4313
+ id: "claude-code",
4314
+ label: "Claude Code",
4315
+ formatLabel: "session JSONL (~/.claude/projects)",
4316
+ confidence: "observed",
4317
+ implemented: true,
4318
+ humanTest: "A1",
4319
+ toolResults: "measured",
4320
+ note: "Built from real observed logs. Anthropic publishes no format guarantee, so watch the unknown-type counter."
4321
+ },
4322
+ cursor: {
4323
+ id: "cursor",
4324
+ label: "Cursor",
4325
+ formatLabel: "state.vscdb SQLite (cursorDiskKV + ItemTable)",
4326
+ confidence: "documented",
4327
+ implemented: true,
4328
+ humanTest: "A5",
4329
+ toolResults: "unavailable",
4330
+ note: "Reads composerData:/bubbleId: from cursorDiskKV (modern) and aiService.prompts from ItemTable (legacy). Cursor publishes no schema and has moved these keys between releases."
4331
+ },
4332
+ opencode: {
4333
+ id: "opencode",
4334
+ label: "OpenCode",
4335
+ formatLabel: "SQLite opencode.db (1.3+) or storage/ JSON tree (1.0.x)",
4336
+ confidence: "documented",
4337
+ implemented: true,
4338
+ humanTest: "A4",
4339
+ toolResults: "measured",
4340
+ note: "Two layouts. 1.3+ keeps sessions in an SQLite opencode.db (schema read off a real 1.3.17 install); 1.0.x kept a session/message/part JSON tree. OpenCode publishes no schema and its migrations have moved this once already, so a version bump can move it again."
4341
+ },
4342
+ antigravity: {
4343
+ id: "antigravity",
4344
+ label: "Google Antigravity",
4345
+ formatLabel: "app brain/<id>/.system_generated/logs/transcript.jsonl, or IDE conversations/<id>.pb",
4346
+ confidence: "documented",
4347
+ implemented: true,
4348
+ humanTest: "A6",
4349
+ toolResults: "unavailable",
4350
+ note: "Both stores measured on one machine. The app's transcript is read the way deja-vu reads it: prompts, replies and the planner's tool calls, not tool output. The IDE's .pb is read the way deja-vu reads Windsurf: prompts and replies only."
4351
+ },
4352
+ terminal: {
4353
+ id: "terminal",
4354
+ label: "Terminal / PTY recording",
4355
+ formatLabel: "asciinema cast v2, or a plain text transcript",
4356
+ confidence: "spec",
4357
+ implemented: true,
4358
+ humanTest: "A3",
4359
+ toolResults: "unavailable",
4360
+ note: "Cast format is specified. Segmenting a raw terminal stream into prompts and replies is heuristic and will misread unfamiliar prompts."
4361
+ },
4362
+ otel: {
4363
+ id: "otel",
4364
+ label: "OpenTelemetry",
4365
+ formatLabel: "OTLP/JSON spans with GenAI semantic conventions",
4366
+ confidence: "spec",
4367
+ implemented: true,
4368
+ humanTest: "A2",
4369
+ toolResults: "measured",
4370
+ note: "Both the wire format and the gen_ai.* attribute names are specified. Unproven: that real instrumentation populates them."
4371
+ }
4372
+ };
4373
+ function failuresRecorded(harness) {
4374
+ return HARNESS_INFO[harness].toolResults === "measured";
4375
+ }
4376
+
4310
4377
  // ../schema/src/brief.ts
4311
4378
  var MUTATING_TOOL_NAMES = /* @__PURE__ */ new Set([
4312
4379
  "write",
@@ -4642,14 +4709,15 @@ function runDiagnostics(trace) {
4642
4709
  if (hit) waste.push({ kind: "repeated-search", label, steps: hit, windowMinutes: WASTE_RULES.repeatedSearch.windowMinutes });
4643
4710
  }
4644
4711
  const stats = trace.stats;
4712
+ const recorded = failuresRecorded(trace.source.harness);
4645
4713
  return {
4646
4714
  counts: {
4647
4715
  steps: steps.length,
4648
4716
  agentSteps: steps.length - prompts.length,
4649
4717
  humanPrompts: prompts.length,
4650
4718
  toolCalls: stats.toolCallCount,
4651
- failures: steps.filter((step) => step.type === "error_recovery").length,
4652
- recoveries: loops.filter((loop) => loop.outcome === "recovered").length
4719
+ failures: recorded ? steps.filter((step) => step.type === "error_recovery").length : null,
4720
+ recoveries: recorded ? loops.filter((loop) => loop.outcome === "recovered").length : null
4653
4721
  },
4654
4722
  tokens: {
4655
4723
  input: stats.inputTokens,
@@ -4778,64 +4846,6 @@ function buildExecutionBrief(trace) {
4778
4846
  };
4779
4847
  }
4780
4848
 
4781
- // ../schema/src/harness.ts
4782
- var HARNESS_INFO = {
4783
- "claude-code": {
4784
- id: "claude-code",
4785
- label: "Claude Code",
4786
- formatLabel: "session JSONL (~/.claude/projects)",
4787
- confidence: "observed",
4788
- implemented: true,
4789
- humanTest: "A1",
4790
- note: "Built from real observed logs. Anthropic publishes no format guarantee, so watch the unknown-type counter."
4791
- },
4792
- cursor: {
4793
- id: "cursor",
4794
- label: "Cursor",
4795
- formatLabel: "state.vscdb SQLite (cursorDiskKV + ItemTable)",
4796
- confidence: "documented",
4797
- implemented: true,
4798
- humanTest: "A5",
4799
- note: "Reads composerData:/bubbleId: from cursorDiskKV (modern) and aiService.prompts from ItemTable (legacy). Cursor publishes no schema and has moved these keys between releases."
4800
- },
4801
- opencode: {
4802
- id: "opencode",
4803
- label: "OpenCode",
4804
- formatLabel: "SQLite opencode.db (1.3+) or storage/ JSON tree (1.0.x)",
4805
- confidence: "documented",
4806
- implemented: true,
4807
- humanTest: "A4",
4808
- note: "Two layouts. 1.3+ keeps sessions in an SQLite opencode.db (schema read off a real 1.3.17 install); 1.0.x kept a session/message/part JSON tree. OpenCode publishes no schema and its migrations have moved this once already, so a version bump can move it again."
4809
- },
4810
- antigravity: {
4811
- id: "antigravity",
4812
- label: "Google Antigravity",
4813
- formatLabel: "IDE conversations/<id>.pb (Windsurf's encrypted trajectory)",
4814
- confidence: "documented",
4815
- implemented: true,
4816
- humanTest: "A6",
4817
- note: "The IDE only, prompts and replies only. Read the way deja-vu reads Windsurf, which held on all nine IDE 1.23.2 conversations measured. Tool calls are undocumented and not read; the standalone Antigravity app is not read yet."
4818
- },
4819
- terminal: {
4820
- id: "terminal",
4821
- label: "Terminal / PTY recording",
4822
- formatLabel: "asciinema cast v2, or a plain text transcript",
4823
- confidence: "spec",
4824
- implemented: true,
4825
- humanTest: "A3",
4826
- note: "Cast format is specified. Segmenting a raw terminal stream into prompts and replies is heuristic and will misread unfamiliar prompts."
4827
- },
4828
- otel: {
4829
- id: "otel",
4830
- label: "OpenTelemetry",
4831
- formatLabel: "OTLP/JSON spans with GenAI semantic conventions",
4832
- confidence: "spec",
4833
- implemented: true,
4834
- humanTest: "A2",
4835
- note: "Both the wire format and the gen_ai.* attribute names are specified. Unproven: that real instrumentation populates them."
4836
- }
4837
- };
4838
-
4839
4849
  // ../schema/src/capsule.ts
4840
4850
  var CAPSULE_LIMITS = {
4841
4851
  /** Diff lines across every patch. Past it the rest is counted, never silently dropped. */
@@ -6000,7 +6010,7 @@ function buildTrace(input) {
6000
6010
  const title = titleFromPrompt(firstPrompt?.content ?? "");
6001
6011
  const filesChanged = input.capabilities.filesChanged === "measured" ? changedFiles.size : null;
6002
6012
  const filesPhrase = filesChanged === null ? "an unrecorded number of files changed" : `${plural2(filesChanged, "file")} changed`;
6003
- const summary = `${plural2(steps.length, "step")}, ${plural2(toolCallCount, "tool call")}, ${filesPhrase}, ${plural2(errorCount, "tool error")} over ${Math.round(durationSeconds)}s.`;
6013
+ const summary = `${plural2(steps.length, "step")}, ${plural2(toolCallCount, "tool call")}, ${filesPhrase}, ${failuresRecorded(input.harness) ? plural2(errorCount, "tool error") : "tool errors not recorded"} over ${Math.round(durationSeconds)}s.`;
6004
6014
  const tokensMeasured = input.capabilities.tokens === "measured";
6005
6015
  const provenance = {
6006
6016
  id: input.harness,
@@ -6052,11 +6062,12 @@ import fs3 from "node:fs";
6052
6062
  import os2 from "node:os";
6053
6063
  import path3 from "node:path";
6054
6064
  import { fileURLToPath } from "node:url";
6055
- var ANTIGRAVITY_ADAPTER_VERSION = 1;
6065
+ var ANTIGRAVITY_ADAPTER_VERSION = 2;
6056
6066
  var KEY = Buffer.from("safeCodeiumworldKeYsecretBalloon");
6057
6067
  var NONCE = 12;
6058
6068
  var TAG = 16;
6059
6069
  var CONVERSATION = /[\\/]\.gemini[\\/]antigravity[^\\/]*[\\/]conversations[\\/][^\\/]+\.pb$/i;
6070
+ var TRANSCRIPT = /[\\/]\.gemini[\\/]antigravity[^\\/]*[\\/]brain[\\/][^\\/]+[\\/]\.system_generated[\\/]logs[\\/]transcript\.jsonl$/i;
6060
6071
  var PB_VARINT = 0;
6061
6072
  var PB_FIXED64 = 1;
6062
6073
  var PB_BYTES = 2;
@@ -6214,6 +6225,82 @@ function decodeTrajectory2(plain) {
6214
6225
  function homeOf(env) {
6215
6226
  return (process.platform === "win32" ? env.USERPROFILE : env.HOME) || os2.homedir();
6216
6227
  }
6228
+ var USER_BLOCKS = [/<ADDITIONAL_METADATA>[\s\S]*?<\/ADDITIONAL_METADATA>/g, /<USER_SETTINGS_CHANGE>[\s\S]*?<\/USER_SETTINGS_CHANGE>/g];
6229
+ var ARTIFACT_COMMENT = /^Comments on artifact URI:.*$|^The user has (?:approved|rejected) this document\.$/gm;
6230
+ var REQUEST_TAG = /[ \t]*\n?\s*<\/?USER_REQUEST>\s*/g;
6231
+ function cleanUserContent(text2) {
6232
+ let out = text2;
6233
+ for (const block of USER_BLOCKS) out = out.replace(block, "");
6234
+ out = out.replace(ARTIFACT_COMMENT, "");
6235
+ return out.replace(REQUEST_TAG, "\n").trim();
6236
+ }
6237
+ function decodeArg(value) {
6238
+ if (typeof value !== "string" || !value.startsWith('"')) return value;
6239
+ try {
6240
+ return JSON.parse(value);
6241
+ } catch {
6242
+ return value;
6243
+ }
6244
+ }
6245
+ function isRecord(value) {
6246
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6247
+ }
6248
+ function decodeTranscript(text2) {
6249
+ const events = [];
6250
+ const unread = /* @__PURE__ */ new Map();
6251
+ let cwd;
6252
+ let steps = 0;
6253
+ let line = 0;
6254
+ for (const raw of text2.replace(/\r\n/g, "\n").split("\n")) {
6255
+ line++;
6256
+ if (raw.trim() === "") continue;
6257
+ let record;
6258
+ try {
6259
+ record = JSON.parse(raw);
6260
+ } catch {
6261
+ tally(unread, "malformed line");
6262
+ continue;
6263
+ }
6264
+ if (!isRecord(record)) {
6265
+ tally(unread, "malformed line");
6266
+ continue;
6267
+ }
6268
+ steps++;
6269
+ const source = typeof record["source"] === "string" ? record["source"] : "";
6270
+ const type = typeof record["type"] === "string" ? record["type"] : "";
6271
+ const content = typeof record["content"] === "string" ? record["content"] : "";
6272
+ const created = typeof record["created_at"] === "string" && !Number.isNaN(Date.parse(record["created_at"])) ? record["created_at"] : void 0;
6273
+ const at = created ? { timestamp: created } : {};
6274
+ if (source === "USER_EXPLICIT") {
6275
+ const prompt2 = cleanUserContent(content);
6276
+ if (prompt2) events.push({ kind: "prompt", ...at, text: prompt2 });
6277
+ continue;
6278
+ }
6279
+ if (source !== "MODEL") {
6280
+ tally(unread, type ? `${source || "no"} source, type ${type}` : `${source || "no"} source`);
6281
+ continue;
6282
+ }
6283
+ if (type !== "" && type !== "PLANNER_RESPONSE") {
6284
+ tally(unread, `tool step ${type}`);
6285
+ continue;
6286
+ }
6287
+ if (content.trim()) events.push({ kind: "text", ...at, text: content.trim() });
6288
+ const calls = Array.isArray(record["tool_calls"]) ? record["tool_calls"] : [];
6289
+ calls.forEach((call, k) => {
6290
+ if (!isRecord(call) || typeof call["name"] !== "string") return;
6291
+ const args = {};
6292
+ if (isRecord(call["args"])) for (const [key, value] of Object.entries(call["args"])) args[key] = decodeArg(value);
6293
+ if (cwd === void 0 && call["name"] === "run_command" && typeof args["Cwd"] === "string" && args["Cwd"]) cwd = args["Cwd"];
6294
+ const step = typeof record["step_index"] === "number" ? record["step_index"] : `line${line}`;
6295
+ events.push({ kind: "tool_call", ...at, id: `${step}.${k}`, name: call["name"], input: args });
6296
+ });
6297
+ }
6298
+ if (cwd !== void 0) events.unshift({ kind: "meta", cwd });
6299
+ return { events, steps, unread };
6300
+ }
6301
+ function transcriptId(file) {
6302
+ return path3.basename(path3.dirname(path3.dirname(path3.dirname(file))));
6303
+ }
6217
6304
  function listConversations(env = process.env) {
6218
6305
  const gemini = path3.join(homeOf(env), ".gemini");
6219
6306
  let roots;
@@ -6222,29 +6309,71 @@ function listConversations(env = process.env) {
6222
6309
  } catch {
6223
6310
  return [];
6224
6311
  }
6225
- const found = [];
6226
- for (const root of roots) {
6227
- const dir = path3.join(gemini, root, "conversations");
6228
- let names;
6312
+ const statFile = (file) => {
6229
6313
  try {
6230
- names = fs3.readdirSync(dir);
6314
+ const stat = fs3.statSync(file);
6315
+ return stat.isFile() ? stat : null;
6231
6316
  } catch {
6232
- continue;
6317
+ return null;
6233
6318
  }
6234
- for (const name of names) {
6235
- if (!name.toLowerCase().endsWith(".pb")) continue;
6236
- const file = path3.join(dir, name);
6237
- try {
6238
- const stat = fs3.statSync(file);
6239
- if (stat.isFile()) found.push({ file, mtime: stat.mtimeMs });
6240
- } catch {
6241
- continue;
6242
- }
6319
+ };
6320
+ const readNames = (dir) => {
6321
+ try {
6322
+ return fs3.readdirSync(dir);
6323
+ } catch {
6324
+ return [];
6243
6325
  }
6244
- }
6245
- return found.sort((a, b) => b.mtime - a.mtime).map((entry) => entry.file);
6326
+ };
6327
+ const pb = /* @__PURE__ */ new Map();
6328
+ const transcripts = /* @__PURE__ */ new Map();
6329
+ for (const root of roots) {
6330
+ const conversations = path3.join(gemini, root, "conversations");
6331
+ for (const name of readNames(conversations)) {
6332
+ if (!name.toLowerCase().endsWith(".pb")) continue;
6333
+ const file = path3.join(conversations, name);
6334
+ const stat = statFile(file);
6335
+ if (stat === null) continue;
6336
+ const id = name.slice(0, -".pb".length).toLowerCase();
6337
+ const kept = pb.get(id);
6338
+ if (kept === void 0 || stat.size > kept.size) pb.set(id, { file, mtime: stat.mtimeMs, size: stat.size });
6339
+ }
6340
+ const brain = path3.join(gemini, root, "brain");
6341
+ for (const name of readNames(brain)) {
6342
+ const file = path3.join(brain, name, ".system_generated", "logs", "transcript.jsonl");
6343
+ const stat = statFile(file);
6344
+ if (stat === null) continue;
6345
+ const id = name.toLowerCase();
6346
+ const kept = transcripts.get(id);
6347
+ if (kept === void 0 || stat.size > kept.size) transcripts.set(id, { file, mtime: stat.mtimeMs, size: stat.size });
6348
+ }
6349
+ }
6350
+ for (const id of transcripts.keys()) pb.delete(id);
6351
+ return [...pb.values(), ...transcripts.values()].sort((a, b) => b.mtime - a.mtime).map((entry) => entry.file);
6246
6352
  }
6247
6353
  var REFUSAL_TEST = "A6";
6354
+ async function readTranscript(file) {
6355
+ const trajectory = decodeTranscript(await fs3.promises.readFile(file, "utf8"));
6356
+ const id = transcriptId(file);
6357
+ const said = trajectory.events.filter((e) => e.kind === "prompt" || e.kind === "text").length;
6358
+ if (said === 0) {
6359
+ return {
6360
+ refused: true,
6361
+ reason: `${id}'s transcript has ${trajectory.steps} record(s) and no prompt or reply this reader knows.`,
6362
+ humanTest: REFUSAL_TEST
6363
+ };
6364
+ }
6365
+ return {
6366
+ events: trajectory.events,
6367
+ capabilities: { tokens: "unavailable", filesChanged: "unavailable" },
6368
+ sessionId: id,
6369
+ unreadFields: [...trajectory.unread].sort((a, b) => b[1] - a[1]).map(([field, count]) => `${field} x${count}`),
6370
+ notes: [
6371
+ `${trajectory.steps} record(s), ${said} prompt/reply event(s)`,
6372
+ "tool steps' bodies and checkpoints are not read; tool calls come from the planner's tool_calls",
6373
+ "CONFIDENCE: documented (deja-vu's transcript reader, measured on app 2.24.0). See HUMAN-TESTS.md A6."
6374
+ ]
6375
+ };
6376
+ }
6248
6377
  var antigravityAdapter = {
6249
6378
  id: "antigravity",
6250
6379
  version: ANTIGRAVITY_ADAPTER_VERSION,
@@ -6252,9 +6381,13 @@ var antigravityAdapter = {
6252
6381
  if (CONVERSATION.test(input.path)) {
6253
6382
  return { score: 85, reason: "a conversations/<id>.pb under ~/.gemini/antigravity" };
6254
6383
  }
6255
- return { score: 0, reason: "not an Antigravity conversations/<id>.pb" };
6384
+ if (TRANSCRIPT.test(input.path)) {
6385
+ return { score: 85, reason: "a brain/<id>/.system_generated/logs/transcript.jsonl under ~/.gemini/antigravity" };
6386
+ }
6387
+ return { score: 0, reason: "not an Antigravity conversations/<id>.pb or brain transcript" };
6256
6388
  },
6257
6389
  async read(input) {
6390
+ if (input.path.toLowerCase().endsWith(".jsonl")) return readTranscript(input.path);
6258
6391
  const plain = openConversation(await fs3.promises.readFile(input.path));
6259
6392
  if (plain === null) {
6260
6393
  return {
@@ -6305,6 +6438,18 @@ var antigravityAdapter = {
6305
6438
  const target = files[0];
6306
6439
  if (target === void 0) return lines2;
6307
6440
  lines2.push("", `inspecting ${target}`);
6441
+ if (target.toLowerCase().endsWith(".jsonl")) {
6442
+ try {
6443
+ const trajectory = decodeTranscript(await fs3.promises.readFile(target, "utf8"));
6444
+ lines2.push(` records: ${trajectory.steps}, events: ${trajectory.events.filter((e) => e.kind !== "meta").length}`);
6445
+ for (const [field, count] of [...trajectory.unread].sort((a, b) => b[1] - a[1]).slice(0, 15)) {
6446
+ lines2.push(` ${String(count).padStart(6)} ${field}`);
6447
+ }
6448
+ } catch (error) {
6449
+ lines2.push(` unreadable: ${error instanceof Error ? error.message : String(error)}`);
6450
+ }
6451
+ return lines2;
6452
+ }
6308
6453
  let plain;
6309
6454
  try {
6310
6455
  plain = openConversation(await fs3.promises.readFile(target));
@@ -10069,7 +10214,7 @@ function renderReviewPage(input) {
10069
10214
  <h1>${marked("title", trace.title)}</h1>
10070
10215
  <div class="meta">
10071
10216
  <b>${escapeHtml(trace.model)}</b> &middot; ${plural2(trace.steps.length, "step")} &middot; ${plural2(toolCalls, "tool call")} &middot;
10072
- ${trace.stats.filesChanged === null ? `${formatCount(null)} files` : plural2(trace.stats.filesChanged, "file")} changed &middot; ${plural2(trace.stats.errorCount, "error")} &middot;
10217
+ ${trace.stats.filesChanged === null ? `${formatCount(null)} files` : plural2(trace.stats.filesChanged, "file")} changed &middot; ${failuresRecorded(trace.source.harness) ? plural2(trace.stats.errorCount, "error") : `${formatCount(null)} errors`} &middot;
10073
10218
  ${Math.round(trace.stats.durationSeconds)}s &middot; <b>${matches.length}</b> redaction match${matches.length === 1 ? "" : "es"}
10074
10219
  </div>
10075
10220
  <div class="meta path">${escapeHtml(sessionPath)}</div>
@@ -10651,7 +10796,7 @@ function diagnosticsLines(diagnostics) {
10651
10796
  const { counts, tokens } = diagnostics;
10652
10797
  const out = [
10653
10798
  `${plural3(counts.steps, "step")}: ${counts.agentSteps.toLocaleString("en-US")} by the agent, ${plural3(counts.humanPrompts, "prompt")} from a person`,
10654
- `${plural3(counts.toolCalls, "tool call")}, ${plural3(counts.failures, "failure")}, ${plural3(counts.recoveries, "recovery", "recoveries")} by the agent`
10799
+ counts.failures === null || counts.recoveries === null ? `${plural3(counts.toolCalls, "tool call")}; failures not recorded by this harness` : `${plural3(counts.toolCalls, "tool call")}, ${plural3(counts.failures, "failure")}, ${plural3(counts.recoveries, "recovery", "recoveries")} by the agent`
10655
10800
  ];
10656
10801
  const recorded = [];
10657
10802
  if (tokens.input !== null) recorded.push(["in", tokens.input]);
@@ -10934,7 +11079,7 @@ function logCapture(dir, line, now = (/* @__PURE__ */ new Date()).toISOString())
10934
11079
 
10935
11080
  // src/mcp.ts
10936
11081
  var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
10937
- var MCP_SERVER_VERSION = "0.3.0";
11082
+ var MCP_SERVER_VERSION = "0.3.2";
10938
11083
  function defaultMcpDeps(storeDir2, bucketDir2) {
10939
11084
  return {
10940
11085
  storeDir: storeDir2,
@@ -12597,7 +12742,7 @@ function report(result, outputPath) {
12597
12742
  lines2.push(` tool calls ${formatInt(trace.stats.toolCallCount)}`);
12598
12743
  lines2.push(` tools used ${result.toolNames.length > 0 ? result.toolNames.join(", ") : "(none)"}`);
12599
12744
  lines2.push(` files changed ${formatCount(trace.stats.filesChanged)}`);
12600
- lines2.push(` tool errors ${formatInt(trace.stats.errorCount)}`);
12745
+ lines2.push(` tool errors ${failuresRecorded(trace.source.harness) ? formatInt(trace.stats.errorCount) : formatCount(null)}`);
12601
12746
  lines2.push("");
12602
12747
  lines2.push(
12603
12748
  ` tokens in ${formatCount(trace.stats.inputTokens)} / out ${formatCount(trace.stats.outputTokens)} / cache read ${formatCount(trace.stats.cacheReadTokens)} / cache write ${formatCount(trace.stats.cacheCreationTokens)}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flatbread-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Flatbread: read a coding agent's session off disk, redact it, share it, and pull a run's Context Capsule into another agent.",
5
5
  "type": "module",
6
6
  "bin": {