flatbread-cli 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/THIRD-PARTY-NOTICES.md +33 -0
- package/dist/flatbread.mjs +777 -209
- package/package.json +3 -2
package/dist/flatbread.mjs
CHANGED
|
@@ -7,10 +7,10 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
9
|
import { spawn as spawn3 } from "node:child_process";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
+
import fs22 from "node:fs";
|
|
11
|
+
import os8 from "node:os";
|
|
12
|
+
import path21 from "node:path";
|
|
13
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14
14
|
|
|
15
15
|
// ../../node_modules/zod/v3/external.js
|
|
16
16
|
var external_exports = {};
|
|
@@ -490,8 +490,8 @@ function getErrorMap() {
|
|
|
490
490
|
|
|
491
491
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
492
492
|
var makeIssue = (params) => {
|
|
493
|
-
const { data, path:
|
|
494
|
-
const fullPath = [...
|
|
493
|
+
const { data, path: path22, errorMaps, issueData } = params;
|
|
494
|
+
const fullPath = [...path22, ...issueData.path || []];
|
|
495
495
|
const fullIssue = {
|
|
496
496
|
...issueData,
|
|
497
497
|
path: fullPath
|
|
@@ -607,11 +607,11 @@ var errorUtil;
|
|
|
607
607
|
|
|
608
608
|
// ../../node_modules/zod/v3/types.js
|
|
609
609
|
var ParseInputLazyPath = class {
|
|
610
|
-
constructor(parent, value,
|
|
610
|
+
constructor(parent, value, path22, key) {
|
|
611
611
|
this._cachedPath = [];
|
|
612
612
|
this.parent = parent;
|
|
613
613
|
this.data = value;
|
|
614
|
-
this._path =
|
|
614
|
+
this._path = path22;
|
|
615
615
|
this._key = key;
|
|
616
616
|
}
|
|
617
617
|
get path() {
|
|
@@ -4384,8 +4384,8 @@ function firstLine(text2) {
|
|
|
4384
4384
|
function labelOf(step, cwd) {
|
|
4385
4385
|
const tool = step.tool;
|
|
4386
4386
|
if (tool === void 0) return step.type;
|
|
4387
|
-
const
|
|
4388
|
-
if (
|
|
4387
|
+
const path22 = firstString(tool.input, FILE_PATH_KEYS);
|
|
4388
|
+
if (path22 !== void 0) return `${tool.name} ${repoRelative(path22, cwd)}`;
|
|
4389
4389
|
const other = firstString(tool.input, TARGET_KEYS);
|
|
4390
4390
|
if (other !== void 0) return cut(`${tool.name}: ${repoRelative(firstLine(other), cwd)}`, BRIEF_LIMITS.quote);
|
|
4391
4391
|
return tool.name;
|
|
@@ -4633,9 +4633,9 @@ function runDiagnostics(trace) {
|
|
|
4633
4633
|
searches.set(key, found);
|
|
4634
4634
|
}
|
|
4635
4635
|
}
|
|
4636
|
-
for (const [
|
|
4636
|
+
for (const [path22, entries] of reads) {
|
|
4637
4637
|
const hit = densest(entries, WASTE_RULES.repeatedRead.times, WASTE_RULES.repeatedRead.windowMinutes * 6e4);
|
|
4638
|
-
if (hit) waste.push({ kind: "repeated-read", path:
|
|
4638
|
+
if (hit) waste.push({ kind: "repeated-read", path: path22, steps: hit, windowMinutes: WASTE_RULES.repeatedRead.windowMinutes });
|
|
4639
4639
|
}
|
|
4640
4640
|
for (const { label, entries } of searches.values()) {
|
|
4641
4641
|
const hit = densest(entries, WASTE_RULES.repeatedSearch.times, WASTE_RULES.repeatedSearch.windowMinutes * 6e4);
|
|
@@ -4713,19 +4713,19 @@ function filesOf(steps, cwd) {
|
|
|
4713
4713
|
const patches = [];
|
|
4714
4714
|
steps.forEach((step, index) => {
|
|
4715
4715
|
if (!isSuccessfulEdit(step) || step.tool === void 0) return;
|
|
4716
|
-
const
|
|
4717
|
-
const file = files.get(
|
|
4716
|
+
const path22 = repoRelative(firstString(step.tool.input, FILE_PATH_KEYS), cwd);
|
|
4717
|
+
const file = files.get(path22) ?? { path: path22 };
|
|
4718
4718
|
if (step.diff !== void 0) {
|
|
4719
4719
|
const counted = countPatch(step.diff.patch);
|
|
4720
4720
|
file.added = (file.added ?? 0) + counted.added;
|
|
4721
4721
|
file.removed = (file.removed ?? 0) + counted.removed;
|
|
4722
|
-
patches.push({ path:
|
|
4722
|
+
patches.push({ path: path22, patch: repoRelative(step.diff.patch, cwd), step: step.stepNumber });
|
|
4723
4723
|
}
|
|
4724
4724
|
const result = resultOf(steps, index);
|
|
4725
4725
|
if (step.tool.name.toLowerCase() === "write" && result !== void 0 && /^File created successfully\b/.test(result.trim())) {
|
|
4726
4726
|
file.created = true;
|
|
4727
4727
|
}
|
|
4728
|
-
files.set(
|
|
4728
|
+
files.set(path22, file);
|
|
4729
4729
|
});
|
|
4730
4730
|
return { files: [...files.values()], patches };
|
|
4731
4731
|
}
|
|
@@ -4810,11 +4810,11 @@ var HARNESS_INFO = {
|
|
|
4810
4810
|
antigravity: {
|
|
4811
4811
|
id: "antigravity",
|
|
4812
4812
|
label: "Google Antigravity",
|
|
4813
|
-
formatLabel: "
|
|
4814
|
-
confidence: "
|
|
4815
|
-
implemented:
|
|
4813
|
+
formatLabel: "app brain/<id>/.system_generated/logs/transcript.jsonl, or IDE conversations/<id>.pb",
|
|
4814
|
+
confidence: "documented",
|
|
4815
|
+
implemented: true,
|
|
4816
4816
|
humanTest: "A6",
|
|
4817
|
-
note: "
|
|
4817
|
+
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."
|
|
4818
4818
|
},
|
|
4819
4819
|
terminal: {
|
|
4820
4820
|
id: "terminal",
|
|
@@ -5096,13 +5096,13 @@ function categorizeTrace(trace) {
|
|
|
5096
5096
|
};
|
|
5097
5097
|
}
|
|
5098
5098
|
if (paths.length > 0) {
|
|
5099
|
-
if (paths.every((
|
|
5099
|
+
if (paths.every((path22) => matchesAny(path22, DOC_PATTERNS))) {
|
|
5100
5100
|
return { category: "docs", evidence: [`all ${paths.length} touched path(s) are documentation`, ...paths.slice(0, 3)] };
|
|
5101
5101
|
}
|
|
5102
|
-
if (paths.every((
|
|
5102
|
+
if (paths.every((path22) => matchesAny(path22, TEST_PATTERNS))) {
|
|
5103
5103
|
return { category: "tests", evidence: [`all ${paths.length} touched path(s) are tests`, ...paths.slice(0, 3)] };
|
|
5104
5104
|
}
|
|
5105
|
-
if (paths.every((
|
|
5105
|
+
if (paths.every((path22) => matchesAny(path22, INFRA_PATTERNS))) {
|
|
5106
5106
|
return { category: "infra", evidence: [`all ${paths.length} touched path(s) are build/CI/deploy config`, ...paths.slice(0, 3)] };
|
|
5107
5107
|
}
|
|
5108
5108
|
}
|
|
@@ -6047,50 +6047,417 @@ function buildTrace(input) {
|
|
|
6047
6047
|
}
|
|
6048
6048
|
|
|
6049
6049
|
// src/adapters/antigravity.ts
|
|
6050
|
+
import crypto from "node:crypto";
|
|
6050
6051
|
import fs3 from "node:fs";
|
|
6051
6052
|
import os2 from "node:os";
|
|
6052
6053
|
import path3 from "node:path";
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6054
|
+
import { fileURLToPath } from "node:url";
|
|
6055
|
+
var ANTIGRAVITY_ADAPTER_VERSION = 2;
|
|
6056
|
+
var KEY = Buffer.from("safeCodeiumworldKeYsecretBalloon");
|
|
6057
|
+
var NONCE = 12;
|
|
6058
|
+
var TAG = 16;
|
|
6059
|
+
var CONVERSATION = /[\\/]\.gemini[\\/]antigravity[^\\/]*[\\/]conversations[\\/][^\\/]+\.pb$/i;
|
|
6060
|
+
var TRANSCRIPT = /[\\/]\.gemini[\\/]antigravity[^\\/]*[\\/]brain[\\/][^\\/]+[\\/]\.system_generated[\\/]logs[\\/]transcript\.jsonl$/i;
|
|
6061
|
+
var PB_VARINT = 0;
|
|
6062
|
+
var PB_FIXED64 = 1;
|
|
6063
|
+
var PB_BYTES = 2;
|
|
6064
|
+
var PB_FIXED32 = 5;
|
|
6065
|
+
var Malformed = class extends Error {
|
|
6066
|
+
};
|
|
6067
|
+
function varint(b, at) {
|
|
6068
|
+
let value = 0n;
|
|
6069
|
+
let shift = 0n;
|
|
6070
|
+
for (let i = at; i < b.length && shift <= 63n; i++) {
|
|
6071
|
+
const byte = b[i];
|
|
6072
|
+
value |= BigInt(byte & 127) << shift;
|
|
6073
|
+
shift += 7n;
|
|
6074
|
+
if ((byte & 128) === 0) return [value, i + 1];
|
|
6075
|
+
}
|
|
6076
|
+
throw new Malformed("malformed protobuf");
|
|
6077
|
+
}
|
|
6078
|
+
function pbFields(b) {
|
|
6079
|
+
const out = [];
|
|
6080
|
+
let i = 0;
|
|
6081
|
+
while (i < b.length) {
|
|
6082
|
+
const [key, afterKey] = varint(b, i);
|
|
6083
|
+
i = afterKey;
|
|
6084
|
+
const num2 = Number(key >> 3n);
|
|
6085
|
+
const wire = Number(key & 7n);
|
|
6086
|
+
if (num2 === 0) throw new Malformed("malformed protobuf");
|
|
6087
|
+
let value = 0n;
|
|
6088
|
+
let data = Buffer.alloc(0);
|
|
6089
|
+
if (wire === PB_VARINT) {
|
|
6090
|
+
[value, i] = varint(b, i);
|
|
6091
|
+
} else if (wire === PB_FIXED64) {
|
|
6092
|
+
if (i + 8 > b.length) throw new Malformed("malformed protobuf");
|
|
6093
|
+
value = b.readBigUInt64LE(i);
|
|
6094
|
+
i += 8;
|
|
6095
|
+
} else if (wire === PB_FIXED32) {
|
|
6096
|
+
if (i + 4 > b.length) throw new Malformed("malformed protobuf");
|
|
6097
|
+
value = BigInt(b.readUInt32LE(i));
|
|
6098
|
+
i += 4;
|
|
6099
|
+
} else if (wire === PB_BYTES) {
|
|
6100
|
+
const [length, afterLength] = varint(b, i);
|
|
6101
|
+
if (length > BigInt(b.length - afterLength)) throw new Malformed("malformed protobuf");
|
|
6102
|
+
data = b.subarray(afterLength, afterLength + Number(length));
|
|
6103
|
+
i = afterLength + Number(length);
|
|
6104
|
+
} else {
|
|
6105
|
+
throw new Malformed("malformed protobuf");
|
|
6106
|
+
}
|
|
6107
|
+
out.push({ num: num2, wire, value, data });
|
|
6108
|
+
}
|
|
6109
|
+
return out;
|
|
6110
|
+
}
|
|
6111
|
+
function bytesField(fields, num2) {
|
|
6112
|
+
return fields.find((f) => f.num === num2 && f.wire === PB_BYTES)?.data;
|
|
6113
|
+
}
|
|
6114
|
+
function pbString(b) {
|
|
6115
|
+
return b === void 0 ? "" : b.toString("utf8").replaceAll("\0", "");
|
|
6116
|
+
}
|
|
6117
|
+
function pbTimestamp(b) {
|
|
6118
|
+
if (b === void 0) return void 0;
|
|
6119
|
+
let seconds = 0n;
|
|
6120
|
+
let nanos = 0n;
|
|
6121
|
+
for (const f of pbFields(b)) {
|
|
6122
|
+
if (f.wire !== PB_VARINT) continue;
|
|
6123
|
+
if (f.num === 1) seconds = BigInt.asIntN(64, f.value);
|
|
6124
|
+
if (f.num === 2) nanos = BigInt.asIntN(32, f.value);
|
|
6125
|
+
}
|
|
6126
|
+
if (seconds === 0n && nanos === 0n) return void 0;
|
|
6127
|
+
const ms = Number(seconds) * 1e3 + Math.floor(Number(nanos) / 1e6);
|
|
6128
|
+
const date = new Date(ms);
|
|
6129
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
6130
|
+
}
|
|
6131
|
+
function openConversation(b) {
|
|
6132
|
+
if (b.length < NONCE + TAG) return null;
|
|
6133
|
+
try {
|
|
6134
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", KEY, b.subarray(0, NONCE));
|
|
6135
|
+
decipher.setAuthTag(b.subarray(b.length - TAG));
|
|
6136
|
+
return Buffer.concat([decipher.update(b.subarray(NONCE, b.length - TAG)), decipher.final()]);
|
|
6137
|
+
} catch {
|
|
6138
|
+
return null;
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
function folderPath(uri) {
|
|
6142
|
+
if (!uri.startsWith("file:")) return void 0;
|
|
6143
|
+
try {
|
|
6144
|
+
return fileURLToPath(uri);
|
|
6145
|
+
} catch {
|
|
6146
|
+
try {
|
|
6147
|
+
return decodeURIComponent(new URL(uri).pathname) || void 0;
|
|
6148
|
+
} catch {
|
|
6149
|
+
return void 0;
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
function workspaceOf(metadata) {
|
|
6154
|
+
if (metadata === void 0) return void 0;
|
|
6155
|
+
for (const f of pbFields(metadata)) {
|
|
6156
|
+
if (f.num !== 1 || f.wire !== PB_BYTES) continue;
|
|
6157
|
+
const folder = folderPath(pbString(bytesField(pbFields(f.data), 1)));
|
|
6158
|
+
if (folder !== void 0) return folder;
|
|
6159
|
+
}
|
|
6160
|
+
return void 0;
|
|
6161
|
+
}
|
|
6162
|
+
function tally(counts, key) {
|
|
6163
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
6164
|
+
}
|
|
6165
|
+
function decodeTrajectory2(plain) {
|
|
6166
|
+
const events = [];
|
|
6167
|
+
const unread = /* @__PURE__ */ new Map();
|
|
6168
|
+
let sessionId;
|
|
6169
|
+
let cwd;
|
|
6170
|
+
let steps = 0;
|
|
6171
|
+
let clock;
|
|
6172
|
+
for (const f of pbFields(plain)) {
|
|
6173
|
+
if (f.wire === PB_BYTES && f.num === 6) {
|
|
6174
|
+
sessionId = pbString(f.data) || sessionId;
|
|
6175
|
+
continue;
|
|
6176
|
+
}
|
|
6177
|
+
if (f.wire === PB_BYTES && f.num === 7) {
|
|
6178
|
+
cwd ??= workspaceOf(f.data);
|
|
6179
|
+
clock ??= pbTimestamp(bytesField(pbFields(f.data), 2));
|
|
6180
|
+
continue;
|
|
6181
|
+
}
|
|
6182
|
+
if (f.wire !== PB_BYTES || f.num !== 2) {
|
|
6183
|
+
tally(unread, `trajectory field ${f.num}`);
|
|
6184
|
+
continue;
|
|
6185
|
+
}
|
|
6186
|
+
steps++;
|
|
6187
|
+
const step = pbFields(f.data);
|
|
6188
|
+
const metadata = bytesField(step, 5);
|
|
6189
|
+
const metaFields = metadata === void 0 ? [] : pbFields(metadata);
|
|
6190
|
+
clock = pbTimestamp(bytesField(metaFields, 1)) ?? clock;
|
|
6191
|
+
const completed = pbTimestamp(bytesField(metaFields, 8));
|
|
6192
|
+
for (const s of step) {
|
|
6193
|
+
if (s.num !== 5 && s.num !== 19 && s.num !== 20) tally(unread, `step field ${s.num}`);
|
|
6194
|
+
}
|
|
6195
|
+
const input = bytesField(step, 19);
|
|
6196
|
+
if (input !== void 0) {
|
|
6197
|
+
const fields = pbFields(input);
|
|
6198
|
+
const text2 = pbString(bytesField(fields, 2)).trim() || pbString(bytesField(fields, 1)).trim();
|
|
6199
|
+
if (text2) events.push({ kind: "prompt", ...clock ? { timestamp: clock } : {}, text: text2 });
|
|
6200
|
+
}
|
|
6201
|
+
const planner = bytesField(step, 20);
|
|
6202
|
+
if (planner !== void 0) {
|
|
6203
|
+
const fields = pbFields(planner);
|
|
6204
|
+
const text2 = pbString(bytesField(fields, 1)).trim() || pbString(bytesField(fields, 8)).trim();
|
|
6205
|
+
if (bytesField(fields, 3) !== void 0) tally(unread, "planner thinking");
|
|
6206
|
+
const at = completed ?? clock;
|
|
6207
|
+
if (text2) events.push({ kind: "text", ...at ? { timestamp: at } : {}, text: text2 });
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
if (cwd !== void 0 || sessionId !== void 0) {
|
|
6211
|
+
events.unshift({ kind: "meta", ...sessionId ? { sessionId } : {}, ...cwd ? { cwd } : {} });
|
|
6212
|
+
}
|
|
6213
|
+
return { events, ...sessionId ? { sessionId } : {}, steps, unread };
|
|
6214
|
+
}
|
|
6215
|
+
function homeOf(env) {
|
|
6216
|
+
return (process.platform === "win32" ? env.USERPROFILE : env.HOME) || os2.homedir();
|
|
6217
|
+
}
|
|
6218
|
+
var USER_BLOCKS = [/<ADDITIONAL_METADATA>[\s\S]*?<\/ADDITIONAL_METADATA>/g, /<USER_SETTINGS_CHANGE>[\s\S]*?<\/USER_SETTINGS_CHANGE>/g];
|
|
6219
|
+
var ARTIFACT_COMMENT = /^Comments on artifact URI:.*$|^The user has (?:approved|rejected) this document\.$/gm;
|
|
6220
|
+
var REQUEST_TAG = /[ \t]*\n?\s*<\/?USER_REQUEST>\s*/g;
|
|
6221
|
+
function cleanUserContent(text2) {
|
|
6222
|
+
let out = text2;
|
|
6223
|
+
for (const block of USER_BLOCKS) out = out.replace(block, "");
|
|
6224
|
+
out = out.replace(ARTIFACT_COMMENT, "");
|
|
6225
|
+
return out.replace(REQUEST_TAG, "\n").trim();
|
|
6226
|
+
}
|
|
6227
|
+
function decodeArg(value) {
|
|
6228
|
+
if (typeof value !== "string" || !value.startsWith('"')) return value;
|
|
6229
|
+
try {
|
|
6230
|
+
return JSON.parse(value);
|
|
6231
|
+
} catch {
|
|
6232
|
+
return value;
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
function isRecord(value) {
|
|
6236
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6237
|
+
}
|
|
6238
|
+
function decodeTranscript(text2) {
|
|
6239
|
+
const events = [];
|
|
6240
|
+
const unread = /* @__PURE__ */ new Map();
|
|
6241
|
+
let cwd;
|
|
6242
|
+
let steps = 0;
|
|
6243
|
+
let line = 0;
|
|
6244
|
+
for (const raw of text2.replace(/\r\n/g, "\n").split("\n")) {
|
|
6245
|
+
line++;
|
|
6246
|
+
if (raw.trim() === "") continue;
|
|
6247
|
+
let record;
|
|
6248
|
+
try {
|
|
6249
|
+
record = JSON.parse(raw);
|
|
6250
|
+
} catch {
|
|
6251
|
+
tally(unread, "malformed line");
|
|
6252
|
+
continue;
|
|
6253
|
+
}
|
|
6254
|
+
if (!isRecord(record)) {
|
|
6255
|
+
tally(unread, "malformed line");
|
|
6256
|
+
continue;
|
|
6257
|
+
}
|
|
6258
|
+
steps++;
|
|
6259
|
+
const source = typeof record["source"] === "string" ? record["source"] : "";
|
|
6260
|
+
const type = typeof record["type"] === "string" ? record["type"] : "";
|
|
6261
|
+
const content = typeof record["content"] === "string" ? record["content"] : "";
|
|
6262
|
+
const created = typeof record["created_at"] === "string" && !Number.isNaN(Date.parse(record["created_at"])) ? record["created_at"] : void 0;
|
|
6263
|
+
const at = created ? { timestamp: created } : {};
|
|
6264
|
+
if (source === "USER_EXPLICIT") {
|
|
6265
|
+
const prompt2 = cleanUserContent(content);
|
|
6266
|
+
if (prompt2) events.push({ kind: "prompt", ...at, text: prompt2 });
|
|
6267
|
+
continue;
|
|
6268
|
+
}
|
|
6269
|
+
if (source !== "MODEL") {
|
|
6270
|
+
tally(unread, type ? `${source || "no"} source, type ${type}` : `${source || "no"} source`);
|
|
6271
|
+
continue;
|
|
6272
|
+
}
|
|
6273
|
+
if (type !== "" && type !== "PLANNER_RESPONSE") {
|
|
6274
|
+
tally(unread, `tool step ${type}`);
|
|
6275
|
+
continue;
|
|
6276
|
+
}
|
|
6277
|
+
if (content.trim()) events.push({ kind: "text", ...at, text: content.trim() });
|
|
6278
|
+
const calls = Array.isArray(record["tool_calls"]) ? record["tool_calls"] : [];
|
|
6279
|
+
calls.forEach((call, k) => {
|
|
6280
|
+
if (!isRecord(call) || typeof call["name"] !== "string") return;
|
|
6281
|
+
const args = {};
|
|
6282
|
+
if (isRecord(call["args"])) for (const [key, value] of Object.entries(call["args"])) args[key] = decodeArg(value);
|
|
6283
|
+
if (cwd === void 0 && call["name"] === "run_command" && typeof args["Cwd"] === "string" && args["Cwd"]) cwd = args["Cwd"];
|
|
6284
|
+
const step = typeof record["step_index"] === "number" ? record["step_index"] : `line${line}`;
|
|
6285
|
+
events.push({ kind: "tool_call", ...at, id: `${step}.${k}`, name: call["name"], input: args });
|
|
6286
|
+
});
|
|
6066
6287
|
}
|
|
6067
|
-
|
|
6288
|
+
if (cwd !== void 0) events.unshift({ kind: "meta", cwd });
|
|
6289
|
+
return { events, steps, unread };
|
|
6290
|
+
}
|
|
6291
|
+
function transcriptId(file) {
|
|
6292
|
+
return path3.basename(path3.dirname(path3.dirname(path3.dirname(file))));
|
|
6293
|
+
}
|
|
6294
|
+
function listConversations(env = process.env) {
|
|
6295
|
+
const gemini = path3.join(homeOf(env), ".gemini");
|
|
6296
|
+
let roots;
|
|
6297
|
+
try {
|
|
6298
|
+
roots = fs3.readdirSync(gemini).filter((name) => /^antigravity/i.test(name));
|
|
6299
|
+
} catch {
|
|
6300
|
+
return [];
|
|
6301
|
+
}
|
|
6302
|
+
const statFile = (file) => {
|
|
6303
|
+
try {
|
|
6304
|
+
const stat = fs3.statSync(file);
|
|
6305
|
+
return stat.isFile() ? stat : null;
|
|
6306
|
+
} catch {
|
|
6307
|
+
return null;
|
|
6308
|
+
}
|
|
6309
|
+
};
|
|
6310
|
+
const readNames = (dir) => {
|
|
6311
|
+
try {
|
|
6312
|
+
return fs3.readdirSync(dir);
|
|
6313
|
+
} catch {
|
|
6314
|
+
return [];
|
|
6315
|
+
}
|
|
6316
|
+
};
|
|
6317
|
+
const pb = /* @__PURE__ */ new Map();
|
|
6318
|
+
const transcripts = /* @__PURE__ */ new Map();
|
|
6319
|
+
for (const root of roots) {
|
|
6320
|
+
const conversations = path3.join(gemini, root, "conversations");
|
|
6321
|
+
for (const name of readNames(conversations)) {
|
|
6322
|
+
if (!name.toLowerCase().endsWith(".pb")) continue;
|
|
6323
|
+
const file = path3.join(conversations, name);
|
|
6324
|
+
const stat = statFile(file);
|
|
6325
|
+
if (stat === null) continue;
|
|
6326
|
+
const id = name.slice(0, -".pb".length).toLowerCase();
|
|
6327
|
+
const kept = pb.get(id);
|
|
6328
|
+
if (kept === void 0 || stat.size > kept.size) pb.set(id, { file, mtime: stat.mtimeMs, size: stat.size });
|
|
6329
|
+
}
|
|
6330
|
+
const brain = path3.join(gemini, root, "brain");
|
|
6331
|
+
for (const name of readNames(brain)) {
|
|
6332
|
+
const file = path3.join(brain, name, ".system_generated", "logs", "transcript.jsonl");
|
|
6333
|
+
const stat = statFile(file);
|
|
6334
|
+
if (stat === null) continue;
|
|
6335
|
+
const id = name.toLowerCase();
|
|
6336
|
+
const kept = transcripts.get(id);
|
|
6337
|
+
if (kept === void 0 || stat.size > kept.size) transcripts.set(id, { file, mtime: stat.mtimeMs, size: stat.size });
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
for (const id of transcripts.keys()) pb.delete(id);
|
|
6341
|
+
return [...pb.values(), ...transcripts.values()].sort((a, b) => b.mtime - a.mtime).map((entry) => entry.file);
|
|
6342
|
+
}
|
|
6343
|
+
var REFUSAL_TEST = "A6";
|
|
6344
|
+
async function readTranscript(file) {
|
|
6345
|
+
const trajectory = decodeTranscript(await fs3.promises.readFile(file, "utf8"));
|
|
6346
|
+
const id = transcriptId(file);
|
|
6347
|
+
const said = trajectory.events.filter((e) => e.kind === "prompt" || e.kind === "text").length;
|
|
6348
|
+
if (said === 0) {
|
|
6349
|
+
return {
|
|
6350
|
+
refused: true,
|
|
6351
|
+
reason: `${id}'s transcript has ${trajectory.steps} record(s) and no prompt or reply this reader knows.`,
|
|
6352
|
+
humanTest: REFUSAL_TEST
|
|
6353
|
+
};
|
|
6354
|
+
}
|
|
6355
|
+
return {
|
|
6356
|
+
events: trajectory.events,
|
|
6357
|
+
capabilities: { tokens: "unavailable", filesChanged: "unavailable" },
|
|
6358
|
+
sessionId: id,
|
|
6359
|
+
unreadFields: [...trajectory.unread].sort((a, b) => b[1] - a[1]).map(([field, count]) => `${field} x${count}`),
|
|
6360
|
+
notes: [
|
|
6361
|
+
`${trajectory.steps} record(s), ${said} prompt/reply event(s)`,
|
|
6362
|
+
"tool steps' bodies and checkpoints are not read; tool calls come from the planner's tool_calls",
|
|
6363
|
+
"CONFIDENCE: documented (deja-vu's transcript reader, measured on app 2.24.0). See HUMAN-TESTS.md A6."
|
|
6364
|
+
]
|
|
6365
|
+
};
|
|
6068
6366
|
}
|
|
6069
6367
|
var antigravityAdapter = {
|
|
6070
6368
|
id: "antigravity",
|
|
6071
|
-
version:
|
|
6369
|
+
version: ANTIGRAVITY_ADAPTER_VERSION,
|
|
6072
6370
|
async detect(input) {
|
|
6073
|
-
|
|
6074
|
-
|
|
6371
|
+
if (CONVERSATION.test(input.path)) {
|
|
6372
|
+
return { score: 85, reason: "a conversations/<id>.pb under ~/.gemini/antigravity" };
|
|
6373
|
+
}
|
|
6374
|
+
if (TRANSCRIPT.test(input.path)) {
|
|
6375
|
+
return { score: 85, reason: "a brain/<id>/.system_generated/logs/transcript.jsonl under ~/.gemini/antigravity" };
|
|
6376
|
+
}
|
|
6377
|
+
return { score: 0, reason: "not an Antigravity conversations/<id>.pb or brain transcript" };
|
|
6075
6378
|
},
|
|
6076
6379
|
async read(input) {
|
|
6077
|
-
|
|
6380
|
+
if (input.path.toLowerCase().endsWith(".jsonl")) return readTranscript(input.path);
|
|
6381
|
+
const plain = openConversation(await fs3.promises.readFile(input.path));
|
|
6382
|
+
if (plain === null) {
|
|
6383
|
+
return {
|
|
6384
|
+
refused: true,
|
|
6385
|
+
reason: `${path3.basename(input.path)} did not authenticate with the key Antigravity IDE 1.23.2 uses. A newer build may have changed the envelope; report its version via HUMAN-TESTS.md A6.`,
|
|
6386
|
+
humanTest: REFUSAL_TEST
|
|
6387
|
+
};
|
|
6388
|
+
}
|
|
6389
|
+
let trajectory;
|
|
6390
|
+
try {
|
|
6391
|
+
trajectory = decodeTrajectory2(plain);
|
|
6392
|
+
} catch (error) {
|
|
6393
|
+
if (!(error instanceof Malformed)) throw error;
|
|
6394
|
+
return {
|
|
6395
|
+
refused: true,
|
|
6396
|
+
reason: `${path3.basename(input.path)} authenticated but is not the trajectory protobuf this reader knows.`,
|
|
6397
|
+
humanTest: REFUSAL_TEST
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
const said = trajectory.events.filter((e) => e.kind === "prompt" || e.kind === "text").length;
|
|
6401
|
+
if (said === 0) {
|
|
6402
|
+
return {
|
|
6403
|
+
refused: true,
|
|
6404
|
+
reason: `${path3.basename(input.path)} has ${trajectory.steps} step(s) and no prompt or reply in the fields this reader knows.`,
|
|
6405
|
+
humanTest: REFUSAL_TEST
|
|
6406
|
+
};
|
|
6407
|
+
}
|
|
6408
|
+
const unreadFields = [...trajectory.unread].sort((a, b) => b[1] - a[1]).map(([field, count]) => `${field} x${count}`);
|
|
6078
6409
|
return {
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6410
|
+
events: trajectory.events,
|
|
6411
|
+
capabilities: { tokens: "unavailable", filesChanged: "unavailable" },
|
|
6412
|
+
sessionId: trajectory.sessionId ?? path3.basename(input.path, ".pb"),
|
|
6413
|
+
unreadFields,
|
|
6414
|
+
notes: [
|
|
6415
|
+
`${trajectory.steps} step(s), ${said} prompt/reply event(s)`,
|
|
6416
|
+
"tool calls, thinking and edits are not read: their step fields are undocumented",
|
|
6417
|
+
"CONFIDENCE: documented (Windsurf's schema, measured on IDE 1.23.2). See HUMAN-TESTS.md A6."
|
|
6418
|
+
]
|
|
6082
6419
|
};
|
|
6083
6420
|
},
|
|
6084
6421
|
discover(env) {
|
|
6085
|
-
return
|
|
6422
|
+
return listConversations(env);
|
|
6086
6423
|
},
|
|
6087
|
-
async describe() {
|
|
6088
|
-
const
|
|
6089
|
-
|
|
6090
|
-
|
|
6424
|
+
async describe(input) {
|
|
6425
|
+
const files = input ? [input.path] : listConversations();
|
|
6426
|
+
const lines2 = [`conversations found: ${files.length}`];
|
|
6427
|
+
for (const file of files.slice(0, 8)) lines2.push(` ${file}`);
|
|
6428
|
+
const target = files[0];
|
|
6429
|
+
if (target === void 0) return lines2;
|
|
6430
|
+
lines2.push("", `inspecting ${target}`);
|
|
6431
|
+
if (target.toLowerCase().endsWith(".jsonl")) {
|
|
6432
|
+
try {
|
|
6433
|
+
const trajectory = decodeTranscript(await fs3.promises.readFile(target, "utf8"));
|
|
6434
|
+
lines2.push(` records: ${trajectory.steps}, events: ${trajectory.events.filter((e) => e.kind !== "meta").length}`);
|
|
6435
|
+
for (const [field, count] of [...trajectory.unread].sort((a, b) => b[1] - a[1]).slice(0, 15)) {
|
|
6436
|
+
lines2.push(` ${String(count).padStart(6)} ${field}`);
|
|
6437
|
+
}
|
|
6438
|
+
} catch (error) {
|
|
6439
|
+
lines2.push(` unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
6440
|
+
}
|
|
6441
|
+
return lines2;
|
|
6442
|
+
}
|
|
6443
|
+
let plain;
|
|
6444
|
+
try {
|
|
6445
|
+
plain = openConversation(await fs3.promises.readFile(target));
|
|
6446
|
+
} catch (error) {
|
|
6447
|
+
lines2.push(` unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
6448
|
+
return lines2;
|
|
6449
|
+
}
|
|
6450
|
+
lines2.push(` authenticated: ${plain === null ? "no" : "yes"}`);
|
|
6451
|
+
if (plain === null) return lines2;
|
|
6452
|
+
try {
|
|
6453
|
+
const trajectory = decodeTrajectory2(plain);
|
|
6454
|
+
lines2.push(` steps: ${trajectory.steps}, prompt/reply events: ${trajectory.events.filter((e) => e.kind !== "meta").length}`);
|
|
6455
|
+
for (const [field, count] of [...trajectory.unread].sort((a, b) => b[1] - a[1]).slice(0, 15)) {
|
|
6456
|
+
lines2.push(` ${String(count).padStart(6)} ${field}`);
|
|
6457
|
+
}
|
|
6458
|
+
} catch {
|
|
6459
|
+
lines2.push(" not the trajectory protobuf this reader knows");
|
|
6091
6460
|
}
|
|
6092
|
-
lines2.push("");
|
|
6093
|
-
lines2.push("If one is FOUND, HUMAN-TESTS.md A6 lists exactly what to report back.");
|
|
6094
6461
|
return lines2;
|
|
6095
6462
|
}
|
|
6096
6463
|
};
|
|
@@ -8752,7 +9119,7 @@ function withLineage(trace, lineage, prUrl) {
|
|
|
8752
9119
|
}
|
|
8753
9120
|
|
|
8754
9121
|
// src/auth.ts
|
|
8755
|
-
import
|
|
9122
|
+
import crypto2 from "node:crypto";
|
|
8756
9123
|
import fs15 from "node:fs";
|
|
8757
9124
|
import os6 from "node:os";
|
|
8758
9125
|
import path14 from "node:path";
|
|
@@ -8782,7 +9149,7 @@ function isAuthFailure(value) {
|
|
|
8782
9149
|
return typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
8783
9150
|
}
|
|
8784
9151
|
function hashSecret(secret) {
|
|
8785
|
-
return
|
|
9152
|
+
return crypto2.createHash("sha256").update(secret).digest("hex");
|
|
8786
9153
|
}
|
|
8787
9154
|
function apiUrlFrom(env = process.env) {
|
|
8788
9155
|
const explicit = env["FLATBREAD_API_URL"]?.trim();
|
|
@@ -8795,7 +9162,7 @@ async function startCliLogin(options = {}) {
|
|
|
8795
9162
|
const apiUrl = options.apiUrl ?? apiUrlFrom(env);
|
|
8796
9163
|
const base = validateApiUrl(apiUrl);
|
|
8797
9164
|
if (isAuthFailure(base)) return base;
|
|
8798
|
-
const pollSecret =
|
|
9165
|
+
const pollSecret = crypto2.randomBytes(32).toString("hex");
|
|
8799
9166
|
const result = await postJson(
|
|
8800
9167
|
`${base}/api/cli-login/start`,
|
|
8801
9168
|
{ poll_secret_hash: hashSecret(pollSecret) },
|
|
@@ -9586,67 +9953,105 @@ function renderMarked(value, matches) {
|
|
|
9586
9953
|
return html;
|
|
9587
9954
|
}
|
|
9588
9955
|
var STYLE = `
|
|
9589
|
-
:root{
|
|
9956
|
+
:root{
|
|
9957
|
+
--canvas:#fafafa;--bg:#ffffff;--ivory:#fcfbf8;--surface:#f6f7f8;--inset:#f6f7f8;
|
|
9958
|
+
--line:rgba(226,232,240,.8);--line-soft:#eff0f2;--line-strong:#b9bec4;
|
|
9959
|
+
--fg:#23272c;--fg-2:rgba(35,39,44,.7);--fg-3:rgba(35,39,44,.56);--fg-lift:#2b3138;
|
|
9960
|
+
--fail-bg:#f6ece7;--fail-line:#c9755c;--fail-fg:#8a3b2a;
|
|
9961
|
+
--ok-bg:#eef1e8;--ok-line:#7f9a7a;--ok-fg:#3f5d43;
|
|
9962
|
+
--hold-bg:#f7f1e2;--hold-line:#c9a34a;--hold-fg:#7d601d;
|
|
9963
|
+
--intent-line:#d6d1c1;
|
|
9964
|
+
--shade:30,41,54;
|
|
9965
|
+
--shadow:0 1px 2px rgba(var(--shade),.04),0 10px 26px rgba(var(--shade),.08);
|
|
9966
|
+
--radius:10px;--radius-sm:6px;
|
|
9967
|
+
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
|
9968
|
+
--mono:ui-monospace,"SF Mono",SFMono-Regular,"Cascadia Mono","Segoe UI Mono","Roboto Mono",Menlo,Consolas,"Liberation Mono",monospace
|
|
9969
|
+
}
|
|
9590
9970
|
*{box-sizing:border-box}
|
|
9591
|
-
body{margin:0
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9971
|
+
html,body{margin:0}
|
|
9972
|
+
body{background:var(--canvas);color:var(--fg);font:14px/1.55 var(--sans);-webkit-font-smoothing:antialiased}
|
|
9973
|
+
code,pre,dd,.mono{font-family:var(--mono)}
|
|
9974
|
+
header{background:var(--bg);border-bottom:1px solid var(--line-soft)}
|
|
9975
|
+
.hin{max-width:1440px;margin:0 auto;padding:14px 28px 16px}
|
|
9976
|
+
.bar{display:flex;align-items:center;gap:10px;margin:0 0 12px}
|
|
9977
|
+
.wordmark{display:inline-flex;align-items:center;height:28px;padding:0 12px;border-radius:999px;background:var(--ivory);border:1px solid rgba(var(--shade),.08);box-shadow:inset 0 1px 0 #fff,0 1px 2px rgba(var(--shade),.05);font:600 13.5px/1 var(--mono);letter-spacing:-.01em;font-variant-ligatures:none}
|
|
9978
|
+
.crumb{color:var(--fg-3);font-size:12.5px}
|
|
9979
|
+
h1{margin:0 0 6px;font-size:20px;line-height:1.3;font-weight:600;letter-spacing:-.01em}
|
|
9980
|
+
.meta{color:var(--fg-3);font-size:12.5px;font-variant-numeric:tabular-nums}
|
|
9595
9981
|
.meta b{color:var(--fg);font-weight:600}
|
|
9596
|
-
.
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
.step
|
|
9601
|
-
.
|
|
9602
|
-
.badge
|
|
9603
|
-
.
|
|
9604
|
-
.
|
|
9605
|
-
.badge.
|
|
9606
|
-
|
|
9607
|
-
.
|
|
9608
|
-
|
|
9609
|
-
.sub{
|
|
9610
|
-
dl{margin:0;padding:
|
|
9611
|
-
dt{color:var(--
|
|
9612
|
-
dd{margin:2px 0 0;white-space:pre-wrap;word-break:break-word}
|
|
9613
|
-
|
|
9982
|
+
.meta.path{font-family:var(--mono);font-size:11.5px;margin-top:2px;word-break:break-all}
|
|
9983
|
+
.wrap{max-width:1440px;margin:0 auto;padding:20px 28px 120px;display:grid;grid-template-columns:minmax(0,1fr) 360px;gap:24px;align-items:start}
|
|
9984
|
+
main{min-width:0}
|
|
9985
|
+
aside{position:sticky;top:16px;max-height:calc(100vh - 32px);overflow:auto;background:var(--bg);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px}
|
|
9986
|
+
.step{background:var(--bg);border:1px solid var(--line);border-radius:var(--radius);box-shadow:0 1px 2px rgba(var(--shade),.04);margin:0 0 10px;overflow:hidden}
|
|
9987
|
+
.step>.head{display:flex;gap:10px;align-items:baseline;padding:9px 14px 0;font-size:12px;color:var(--fg-3);font-variant-numeric:tabular-nums}
|
|
9988
|
+
.badge{font-weight:600;color:var(--fg-2);font-size:12px}
|
|
9989
|
+
.step.user_prompt{border-left:3px solid var(--intent-line)}
|
|
9990
|
+
.step.error_recovery{border-color:color-mix(in srgb,var(--fail-line) 45%,transparent);border-left:3px solid var(--fail-line)}
|
|
9991
|
+
.badge.error_recovery,.sub.err{color:var(--fail-fg)}
|
|
9992
|
+
pre{margin:0;padding:8px 14px 12px;white-space:pre-wrap;word-break:break-word;font:12.5px/1.6 var(--mono);color:var(--fg)}
|
|
9993
|
+
.step.user_prompt>pre,.step.assistant_text>pre{font:14px/1.6 var(--sans)}
|
|
9994
|
+
.sub{padding:8px 14px 0;border-top:1px solid var(--line-soft);color:var(--fg-3);font-size:11px;text-transform:uppercase;letter-spacing:.06em}
|
|
9995
|
+
.sub+pre,.sub+dl{background:var(--inset);margin:8px 14px 12px;padding:10px 12px;border-radius:var(--radius-sm)}
|
|
9996
|
+
dl{margin:0;padding:4px 14px 12px}
|
|
9997
|
+
dt{color:var(--fg-3);font-size:11.5px;margin-top:8px}
|
|
9998
|
+
dd{margin:2px 0 0;white-space:pre-wrap;word-break:break-word;font-size:12.5px}
|
|
9999
|
+
#trace-meta dd{font-family:var(--sans);font-size:13.5px}
|
|
10000
|
+
mark.hit{background:rgba(var(--shade),.08);box-shadow:inset 0 1px 1px rgba(var(--shade),.08);color:var(--fg-2);border-radius:4px;padding:0 4px;font-family:var(--mono);font-size:.9em}
|
|
9614
10001
|
mark.hit .was{display:none}
|
|
9615
|
-
mark.hit.keep{background:
|
|
10002
|
+
mark.hit.keep{background:var(--hold-bg);box-shadow:none;color:var(--fg);border-bottom:1px dashed var(--hold-line);border-radius:2px;font-family:inherit;font-size:inherit}
|
|
9616
10003
|
mark.hit.keep .tok{display:none}
|
|
9617
10004
|
mark.hit.keep .was{display:inline}
|
|
9618
|
-
aside h2{margin:0 0 4px;font-size:
|
|
9619
|
-
aside .note{color:var(--
|
|
9620
|
-
.
|
|
9621
|
-
.row:hover{border-color:var(--acc)}
|
|
9622
|
-
.row input{margin:3px 0 0}
|
|
9623
|
-
.state{font-size:11px;letter-spacing:.04em}
|
|
9624
|
-
.s-keep{display:none;color:var(--warn)}
|
|
9625
|
-
.s-red{color:var(--ok)}
|
|
9626
|
-
.row input:checked ~ .state .s-red{display:none}
|
|
9627
|
-
.row input:checked ~ .state .s-keep{display:inline}
|
|
9628
|
-
.row .rule{font-size:12px}
|
|
9629
|
-
.row .prev{color:var(--dim);font-size:11px;word-break:break-all}
|
|
9630
|
-
.row .times{color:var(--warn)}
|
|
9631
|
-
.keys{color:var(--dim);font-size:11px;margin:0 0 10px}
|
|
9632
|
-
.keys b{color:var(--fg);font-weight:600}
|
|
9633
|
-
.row .path{color:#5c6474;font-size:10px;word-break:break-all}
|
|
9634
|
-
fieldset{border:1px solid var(--line);border-radius:6px;margin:0 0 12px;padding:8px 10px}
|
|
9635
|
-
input.title{box-sizing:border-box;width:100%;font:inherit;padding:6px 8px;margin:4px 0 6px;border:1px solid var(--line);border-radius:5px;background:var(--bg);color:var(--fg)}
|
|
9636
|
-
input.title:focus{outline:none;border-color:var(--acc)}
|
|
9637
|
-
legend{color:var(--dim);font-size:11px;text-transform:uppercase;letter-spacing:.06em}
|
|
9638
|
-
label.vis{display:block;padding:3px 0}
|
|
9639
|
-
.warn{color:var(--warn);font-size:11px;margin:4px 0 0}
|
|
9640
|
-
.actions{display:flex;gap:8px;margin-top:10px}
|
|
9641
|
-
button{font:inherit;padding:9px 14px;border-radius:5px;border:1px solid var(--line);background:#1d2430;color:var(--fg);cursor:pointer}
|
|
9642
|
-
button.primary{background:var(--ok);border-color:var(--ok);color:#04140b;font-weight:600}
|
|
9643
|
-
button:hover{border-color:var(--acc)}
|
|
10005
|
+
aside h2{margin:0 0 4px;font-size:15px;font-weight:600}
|
|
10006
|
+
aside .note{color:var(--fg-3);font-size:12.5px;margin:0 0 12px}
|
|
10007
|
+
aside .note code{font-size:11.5px;background:var(--inset);padding:1px 4px;border-radius:4px}
|
|
9644
10008
|
.tools{display:flex;gap:6px;margin:0 0 10px}
|
|
9645
|
-
.
|
|
9646
|
-
.
|
|
9647
|
-
|
|
9648
|
-
|
|
10009
|
+
.keys{color:var(--fg-3);font-size:12px;margin:0 0 14px}
|
|
10010
|
+
.keys b{display:inline-block;min-width:18px;padding:0 5px;border:1px solid rgba(var(--shade),.12);border-radius:999px;background:var(--bg);color:var(--fg);font:600 11px/16px var(--mono);text-align:center}
|
|
10011
|
+
fieldset{border:0;border-top:1px solid var(--line-soft);margin:0;padding:14px 0 12px}
|
|
10012
|
+
legend{float:left;width:100%;padding:0;margin:0 0 6px;color:var(--fg-3);font-size:11px;text-transform:uppercase;letter-spacing:.06em}
|
|
10013
|
+
legend+*{clear:left}
|
|
10014
|
+
input.title{width:100%;font:inherit;padding:8px 10px;margin:0 0 6px;border:1px solid rgba(var(--shade),.12);border-radius:var(--radius-sm);background:var(--ivory);color:var(--fg);box-shadow:inset 0 1px 0 #fff}
|
|
10015
|
+
input.title:focus{outline:none;background:#fff;border-color:var(--fg-3);box-shadow:0 0 0 3px rgba(var(--shade),.05)}
|
|
10016
|
+
label.vis{display:flex;align-items:center;gap:8px;padding:3px 0;cursor:pointer}
|
|
10017
|
+
input[type=radio],input[type=checkbox]{accent-color:var(--fg);margin:0}
|
|
10018
|
+
.warn{color:var(--fail-fg);font-size:12.5px;margin:6px 0 0}
|
|
10019
|
+
.actions{display:flex;gap:8px;padding:14px 0 16px;border-top:1px solid var(--line-soft)}
|
|
10020
|
+
button{display:inline-flex;align-items:center;font:500 12.5px/1 var(--sans);padding:9px 14px;border-radius:var(--radius-sm);border:1px solid var(--line-strong);background:var(--bg);color:var(--fg);cursor:pointer}
|
|
10021
|
+
button:hover{background:var(--surface)}
|
|
10022
|
+
button:focus-visible{outline:2px solid var(--fg);outline-offset:1px}
|
|
10023
|
+
button.primary{background:var(--fg);border-color:var(--fg);color:#fff}
|
|
10024
|
+
button.primary:hover{background:var(--fg-lift);border-color:var(--fg-lift)}
|
|
10025
|
+
.tools button{padding:5px 10px;font-size:12px}
|
|
10026
|
+
.row{display:grid;grid-template-columns:16px 52px minmax(0,1fr);gap:8px;align-items:start;padding:9px 10px;border:1px solid var(--line);border-radius:var(--radius-sm);margin-bottom:6px;cursor:pointer;background:var(--bg)}
|
|
10027
|
+
.row:hover{background:var(--surface)}
|
|
10028
|
+
.row:focus-within{border-color:var(--fg-3)}
|
|
10029
|
+
.row input{margin:3px 0 0}
|
|
10030
|
+
.state{font-size:11.5px;font-weight:600}
|
|
10031
|
+
.s-red{color:var(--fg-3)}
|
|
10032
|
+
.s-keep{display:none;color:var(--hold-fg)}
|
|
10033
|
+
.row input:checked~.state .s-red{display:none}
|
|
10034
|
+
.row input:checked~.state .s-keep{display:inline}
|
|
10035
|
+
.row:has(input:checked){background:var(--hold-bg);border-color:color-mix(in srgb,var(--hold-line) 50%,transparent)}
|
|
10036
|
+
.row .rule{font-size:13px;color:var(--fg)}
|
|
10037
|
+
.row .times{font-weight:600}
|
|
10038
|
+
.row .prev{color:var(--fg-2);font:11.5px/1.5 var(--mono);word-break:break-all}
|
|
10039
|
+
.row .path{color:var(--fg-3);font-size:11px;word-break:break-all}
|
|
10040
|
+
.done{max-width:620px;margin:14vh auto;padding:28px 30px;background:var(--bg);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
|
|
10041
|
+
.done h1{font-size:20px}
|
|
10042
|
+
.done .meta{font-size:13.5px;color:var(--fg-2);margin:10px 0 0}
|
|
10043
|
+
.done code{display:block;background:var(--inset);border-radius:var(--radius-sm);padding:12px 14px;margin:14px 0;font-size:12.5px;word-break:break-all}
|
|
10044
|
+
a{color:var(--fg);text-underline-offset:2px}
|
|
10045
|
+
@media (max-width:960px){.wrap{grid-template-columns:minmax(0,1fr);padding:16px}aside{position:static;max-height:none;order:-1}.hin{padding:12px 16px}}
|
|
9649
10046
|
`;
|
|
10047
|
+
var STEP_LABEL = {
|
|
10048
|
+
user_prompt: "prompt",
|
|
10049
|
+
assistant_text: "assistant",
|
|
10050
|
+
assistant_thinking: "thinking",
|
|
10051
|
+
tool_call: "tool call",
|
|
10052
|
+
tool_result: "tool result",
|
|
10053
|
+
error_recovery: "failure"
|
|
10054
|
+
};
|
|
9650
10055
|
var SCRIPT = `
|
|
9651
10056
|
(function(){
|
|
9652
10057
|
var boxes = Array.prototype.slice.call(document.querySelectorAll('input[name="keep"]'));
|
|
@@ -9753,14 +10158,14 @@ function renderReviewPage(input) {
|
|
|
9753
10158
|
const base = `steps[${index}]`;
|
|
9754
10159
|
const parts = [];
|
|
9755
10160
|
parts.push(
|
|
9756
|
-
`<div class="head"><span class="badge ${escapeHtml(step.type)}">${escapeHtml(step.type)}</span><span>#${step.stepNumber}</span><span>${escapeHtml(step.timestamp)}</span></div>`
|
|
10161
|
+
`<div class="head"><span class="badge ${escapeHtml(step.type)}">${escapeHtml(STEP_LABEL[step.type] ?? step.type)}</span><span>#${step.stepNumber}</span><span>${escapeHtml(step.timestamp)}</span></div>`
|
|
9757
10162
|
);
|
|
9758
10163
|
parts.push(`<pre>${marked(`${base}.content`, step.content)}</pre>`);
|
|
9759
10164
|
const tool = step.tool;
|
|
9760
10165
|
if (tool) {
|
|
9761
10166
|
const inputSlots = slots.filter((slot) => slot.fieldPath.startsWith(`${base}.tool.input`));
|
|
9762
10167
|
parts.push(
|
|
9763
|
-
`<div class="sub">tool ${escapeHtml(tool.name)}${tool.isError ? " · errored" : ""}</div>`
|
|
10168
|
+
`<div class="sub${tool.isError ? " err" : ""}">tool ${escapeHtml(tool.name)}${tool.isError ? " · errored" : ""}</div>`
|
|
9764
10169
|
);
|
|
9765
10170
|
if (inputSlots.length > 0) {
|
|
9766
10171
|
parts.push(
|
|
@@ -9780,7 +10185,7 @@ function renderReviewPage(input) {
|
|
|
9780
10185
|
`<div class="sub">diff ${marked(`${base}.diff.filePath`, diff.filePath)}</div><pre>${marked(`${base}.diff.patch`, diff.patch)}</pre>`
|
|
9781
10186
|
);
|
|
9782
10187
|
}
|
|
9783
|
-
return `<section class="step" id="${escapeHtml(base)}">${parts.join("")}</section>`;
|
|
10188
|
+
return `<section class="step ${escapeHtml(step.type)}" id="${escapeHtml(base)}">${parts.join("")}</section>`;
|
|
9784
10189
|
}).join("");
|
|
9785
10190
|
const groups = groupMatches(matches);
|
|
9786
10191
|
const rowsHtml = groups.length === 0 ? `<p class="note">No matches. Nothing will be rewritten.</p>` : groups.map((group) => {
|
|
@@ -9794,15 +10199,16 @@ function renderReviewPage(input) {
|
|
|
9794
10199
|
const publicWarning = input.publishTo === void 0 ? "Visibility public: this draft records that choice, and the run becomes readable by anyone the moment it is uploaded. Continue?" : `Visibility public: confirming uploads this run to ${input.publishTo} where anyone with the link can read it. Continue?`;
|
|
9795
10200
|
const destinationHtml = input.publishTo === void 0 ? `<p class="note">Local draft only. Confirm writes ${escapeHtml(input.draftPath ?? "a file in packages/cli/.tmp")} and nothing leaves this machine. The visibility above is stored in the draft. To upload a run, share it with <code>--publish</code> after <code>flatbread login</code>.</p>` : `<p class="warn">Confirm UPLOADS this run to ${escapeHtml(input.publishTo)} with the visibility selected above. Everything you KEEP below is uploaded as written.</p>`;
|
|
9796
10201
|
const body = `
|
|
9797
|
-
<header>
|
|
10202
|
+
<header><div class="hin">
|
|
10203
|
+
<div class="bar"><span class="wordmark">flatbread</span><span class="crumb">review before you share</span></div>
|
|
9798
10204
|
<h1>${marked("title", trace.title)}</h1>
|
|
9799
10205
|
<div class="meta">
|
|
9800
|
-
<b>${escapeHtml(trace.model)}</b> · ${trace.steps.length}
|
|
9801
|
-
${formatCount(trace.stats.filesChanged)}
|
|
9802
|
-
${Math.round(trace.stats.durationSeconds)}s · <b>${matches.length}</b> redaction matches
|
|
10206
|
+
<b>${escapeHtml(trace.model)}</b> · ${plural2(trace.steps.length, "step")} · ${plural2(toolCalls, "tool call")} ·
|
|
10207
|
+
${trace.stats.filesChanged === null ? `${formatCount(null)} files` : plural2(trace.stats.filesChanged, "file")} changed · ${plural2(trace.stats.errorCount, "error")} ·
|
|
10208
|
+
${Math.round(trace.stats.durationSeconds)}s · <b>${matches.length}</b> redaction match${matches.length === 1 ? "" : "es"}
|
|
9803
10209
|
</div>
|
|
9804
|
-
<div class="meta">${escapeHtml(sessionPath)}</div>
|
|
9805
|
-
</header>
|
|
10210
|
+
<div class="meta path">${escapeHtml(sessionPath)}</div>
|
|
10211
|
+
</div></header>
|
|
9806
10212
|
<form id="review" method="post" action="/confirm">
|
|
9807
10213
|
<div class="wrap">
|
|
9808
10214
|
<main>${metaHtml}${stepsHtml}</main>
|
|
@@ -9835,13 +10241,14 @@ function renderReviewPage(input) {
|
|
|
9835
10241
|
<script>var PUBLIC_WARNING=${JSON.stringify(publicWarning)};${SCRIPT}</script>`;
|
|
9836
10242
|
return page(`Flatbread review - ${trace.title}`, body);
|
|
9837
10243
|
}
|
|
9838
|
-
function renderDonePage(message, outputPath, publishTo) {
|
|
10244
|
+
function renderDonePage(message, outputPath, publishTo, keepsRun = false) {
|
|
9839
10245
|
const network = publishTo === void 0 ? `<p class="meta">Nothing was uploaded. This file is on your machine only.</p>` : `<p class="meta">Uploading this run to ${escapeHtml(publishTo)} now. Your terminal says whether it arrived, and prints its link.</p>`;
|
|
9840
10246
|
return page(
|
|
9841
10247
|
"Flatbread - share draft written",
|
|
9842
10248
|
`<div class="done"><h1>${escapeHtml(message)}</h1>
|
|
9843
10249
|
<code>${escapeHtml(outputPath)}</code>
|
|
9844
10250
|
${network}
|
|
10251
|
+
${keepsRun ? `<p class="meta">That file holds only your latest share, and the next one replaces it. This run is also kept under its own ID, printed in your terminal: <code>flatbread pull <id></code> prints it again, and <code>flatbread search --local</code> lists every run you have shared.</p>` : ""}
|
|
9845
10252
|
<p class="meta"><strong>Go back to your terminal now.</strong> The manage token for this
|
|
9846
10253
|
run is printed there, once, at the very bottom. It is the only way to open the run
|
|
9847
10254
|
while it is private, and only its hash is stored, so nothing can print it again.</p>
|
|
@@ -9882,12 +10289,11 @@ function parseDecisionBody(body) {
|
|
|
9882
10289
|
};
|
|
9883
10290
|
}
|
|
9884
10291
|
async function listenOnFirstFreePort(server, ports) {
|
|
9885
|
-
for (const port of ports) {
|
|
10292
|
+
for (const port of [...ports, 0]) {
|
|
9886
10293
|
const ok = await new Promise((resolve) => {
|
|
9887
|
-
const onError = (
|
|
10294
|
+
const onError = () => {
|
|
9888
10295
|
server.removeListener("listening", onListening);
|
|
9889
|
-
|
|
9890
|
-
else resolve(false);
|
|
10296
|
+
resolve(false);
|
|
9891
10297
|
};
|
|
9892
10298
|
const onListening = () => {
|
|
9893
10299
|
server.removeListener("error", onError);
|
|
@@ -9966,7 +10372,7 @@ async function startReviewServer(options) {
|
|
|
9966
10372
|
...changed ? { title } : {}
|
|
9967
10373
|
};
|
|
9968
10374
|
const message = options.onConfirm ? await options.onConfirm(confirmed) : "";
|
|
9969
|
-
send(200, renderDonePage("Share draft written", message, options.publishTo));
|
|
10375
|
+
send(200, renderDonePage("Share draft written", message, options.publishTo, options.keepsRun === true));
|
|
9970
10376
|
finish(confirmed);
|
|
9971
10377
|
} catch (error) {
|
|
9972
10378
|
send(500, page("Flatbread - error", `<div class="done"><h1>Could not write the share draft</h1><code>${escapeHtml(error instanceof Error ? error.message : String(error))}</code></div>`));
|
|
@@ -10120,6 +10526,7 @@ function reportPersist(outcome, options, log) {
|
|
|
10120
10526
|
log(` slug ${outcome.slug}`);
|
|
10121
10527
|
log(` visibility ${outcome.record.visibility}`);
|
|
10122
10528
|
if (outcome.recordPath) log(` record ${outcome.recordPath}`);
|
|
10529
|
+
log(` again flatbread pull ${outcome.slug} (the next share replaces ${path15.basename(options.outputPath)}, not this record)`);
|
|
10123
10530
|
log("");
|
|
10124
10531
|
log(`open ${outcome.publish?.ok ? outcome.publish.url : `${base}/r/${outcome.slug}`}`);
|
|
10125
10532
|
log("");
|
|
@@ -10228,6 +10635,7 @@ async function runShare(options) {
|
|
|
10228
10635
|
sessionPath: target,
|
|
10229
10636
|
draftPath: options.outputPath,
|
|
10230
10637
|
titleDraft: previewTitle,
|
|
10638
|
+
keepsRun: options.persist !== false && (options.storeDir ?? resolveStoreDir()) !== null,
|
|
10231
10639
|
...isPublisher(reviewPublisher) ? { publishTo: reviewPublisher.description } : {},
|
|
10232
10640
|
...options.ports ? { ports: options.ports } : {},
|
|
10233
10641
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
@@ -10241,9 +10649,9 @@ async function runShare(options) {
|
|
|
10241
10649
|
});
|
|
10242
10650
|
if (!session) {
|
|
10243
10651
|
console.error(
|
|
10244
|
-
`could not bind a review port (tried ${(options.ports ??
|
|
10652
|
+
`could not bind a review port on ${REVIEW_SERVER.host} (tried ${(options.ports ?? REVIEW_SERVER.ports).join(", ")}, then any port the OS would give)`
|
|
10245
10653
|
);
|
|
10246
|
-
console.error("
|
|
10654
|
+
console.error("Loopback itself refused, so this machine needs a look; --no-review redacts everything without the page.");
|
|
10247
10655
|
return { exitCode: 1, status: "port-unavailable" };
|
|
10248
10656
|
}
|
|
10249
10657
|
log(` review at ${session.url}`);
|
|
@@ -10661,7 +11069,7 @@ function logCapture(dir, line, now = (/* @__PURE__ */ new Date()).toISOString())
|
|
|
10661
11069
|
|
|
10662
11070
|
// src/mcp.ts
|
|
10663
11071
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
10664
|
-
var MCP_SERVER_VERSION = "0.
|
|
11072
|
+
var MCP_SERVER_VERSION = "0.3.1";
|
|
10665
11073
|
function defaultMcpDeps(storeDir2, bucketDir2) {
|
|
10666
11074
|
return {
|
|
10667
11075
|
storeDir: storeDir2,
|
|
@@ -10751,6 +11159,10 @@ async function searchEverywhere(query, deps, options = {}) {
|
|
|
10751
11159
|
if (local.unreadable > 0) out.push(`(${local.unreadable} local record file${local.unreadable === 1 ? "" : "s"} could not be read)`);
|
|
10752
11160
|
}
|
|
10753
11161
|
const seen = new Set(localHits.map((hit) => hit.slug));
|
|
11162
|
+
for (const record of local.records) {
|
|
11163
|
+
const { harness, sessionId } = record.trace.source;
|
|
11164
|
+
if (sessionId.length > 0 && localHits.some((hit) => hit.slug === record.slug)) seen.add(bucketSlug(harness, sessionId));
|
|
11165
|
+
}
|
|
10754
11166
|
if (deps.bucketDir !== null) {
|
|
10755
11167
|
const bucket = readBucket(deps.bucketDir);
|
|
10756
11168
|
const captured = bucket.rows.filter((row) => !seen.has(row.summary.slug) && matchesQuery(row.summary, query));
|
|
@@ -10784,18 +11196,24 @@ async function searchTool(args, deps) {
|
|
|
10784
11196
|
const { lines: lines2 } = await searchEverywhere(query, deps);
|
|
10785
11197
|
return text([...lines2, "", "Pass a run ID to flatbread_get_capsule for what that run learned."].join("\n"));
|
|
10786
11198
|
}
|
|
11199
|
+
async function readCapsule(ref, deps) {
|
|
11200
|
+
if (ref.site === void 0) {
|
|
11201
|
+
const stored = localRecords(deps).records.find((record) => record.slug === ref.slug);
|
|
11202
|
+
if (stored !== void 0) return { ok: true, capsule: buildCapsule(stored.trace), from: "store" };
|
|
11203
|
+
const captured = deps.bucketDir !== null ? readBucketRecord(deps.bucketDir, ref.slug) : null;
|
|
11204
|
+
if (captured !== null) return { ok: true, capsule: buildCapsule(captured.trace), from: "bucket" };
|
|
11205
|
+
}
|
|
11206
|
+
const remote = await deps.capsule(ref);
|
|
11207
|
+
return remote.ok ? { ok: true, capsule: remote.capsule, from: "site" } : remote;
|
|
11208
|
+
}
|
|
10787
11209
|
async function capsuleTool(args, deps) {
|
|
10788
11210
|
const raw = args["slug"];
|
|
10789
11211
|
if (typeof raw !== "string") return text("slug must be a run ID or a /r/<id> link", true);
|
|
10790
11212
|
const ref = parseRunRef(raw);
|
|
10791
11213
|
if (ref === null) return text(`"${raw}" is not a run ID or a /r/<id> link`, true);
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
}
|
|
10796
|
-
const remote = await deps.capsule(ref);
|
|
10797
|
-
if (remote.ok) return text(remote.capsule);
|
|
10798
|
-
return text(describeReadFailure(remote, `capsule ${ref.slug}`), true);
|
|
11214
|
+
const read2 = await readCapsule(ref, deps);
|
|
11215
|
+
if (read2.ok) return text(read2.capsule);
|
|
11216
|
+
return text(describeReadFailure(read2, `capsule ${ref.slug}`), true);
|
|
10799
11217
|
}
|
|
10800
11218
|
async function handleMcpMessage(message, deps) {
|
|
10801
11219
|
if (typeof message !== "object" || message === null || Array.isArray(message)) {
|
|
@@ -10844,7 +11262,7 @@ async function handleMcpMessage(message, deps) {
|
|
|
10844
11262
|
}
|
|
10845
11263
|
async function runMcpServer(deps, io) {
|
|
10846
11264
|
const input = io?.input ?? process.stdin;
|
|
10847
|
-
const
|
|
11265
|
+
const write2 = io?.write ?? ((line) => process.stdout.write(line));
|
|
10848
11266
|
const log = io?.log ?? ((line) => process.stderr.write(`${line}
|
|
10849
11267
|
`));
|
|
10850
11268
|
log(`flatbread mcp ${MCP_SERVER_VERSION} on stdio${deps.storeDir === null ? " (no local store)" : ` (local store ${deps.storeDir})`}`);
|
|
@@ -10857,12 +11275,12 @@ async function runMcpServer(deps, io) {
|
|
|
10857
11275
|
try {
|
|
10858
11276
|
message = JSON.parse(line);
|
|
10859
11277
|
} catch {
|
|
10860
|
-
|
|
11278
|
+
write2(`${JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } })}
|
|
10861
11279
|
`);
|
|
10862
11280
|
return;
|
|
10863
11281
|
}
|
|
10864
11282
|
const reply = await handleMcpMessage(message, deps);
|
|
10865
|
-
if (reply !== null)
|
|
11283
|
+
if (reply !== null) write2(`${JSON.stringify(reply)}
|
|
10866
11284
|
`);
|
|
10867
11285
|
});
|
|
10868
11286
|
}
|
|
@@ -10870,9 +11288,134 @@ async function runMcpServer(deps, io) {
|
|
|
10870
11288
|
return 0;
|
|
10871
11289
|
}
|
|
10872
11290
|
|
|
10873
|
-
// src/
|
|
11291
|
+
// src/claude-mcp.ts
|
|
10874
11292
|
import fs19 from "node:fs";
|
|
11293
|
+
import os7 from "node:os";
|
|
10875
11294
|
import path18 from "node:path";
|
|
11295
|
+
var MCP_SERVER_NAME = "flatbread";
|
|
11296
|
+
function claudeUserConfigPath(env = process.env) {
|
|
11297
|
+
const dir = env["CLAUDE_CONFIG_DIR"]?.trim();
|
|
11298
|
+
return path18.join(dir ? dir : os7.homedir(), ".claude.json");
|
|
11299
|
+
}
|
|
11300
|
+
function mcpEntryFor(execPath, entryPath) {
|
|
11301
|
+
return { type: "stdio", command: execPath, args: [entryPath, "mcp"] };
|
|
11302
|
+
}
|
|
11303
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11304
|
+
function runsFlatbreadMcp(entry) {
|
|
11305
|
+
if (!isObject(entry) || !Array.isArray(entry["args"])) return false;
|
|
11306
|
+
const words = [entry["command"], ...entry["args"]].filter((word) => typeof word === "string");
|
|
11307
|
+
return words[words.length - 1] === "mcp" && words.some((word) => /flatbread/i.test(word));
|
|
11308
|
+
}
|
|
11309
|
+
var BOM = "\uFEFF";
|
|
11310
|
+
function readConfig(file) {
|
|
11311
|
+
let raw;
|
|
11312
|
+
try {
|
|
11313
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
11314
|
+
} catch (error) {
|
|
11315
|
+
if (error.code === "ENOENT") return { root: {}, raw: null };
|
|
11316
|
+
return { problem: error.message };
|
|
11317
|
+
}
|
|
11318
|
+
const text2 = raw.startsWith(BOM) ? raw.slice(1) : raw;
|
|
11319
|
+
if (text2.trim() === "") return { root: {}, raw };
|
|
11320
|
+
let parsed;
|
|
11321
|
+
try {
|
|
11322
|
+
parsed = JSON.parse(text2);
|
|
11323
|
+
} catch (error) {
|
|
11324
|
+
return { problem: `not JSON (${error.message})` };
|
|
11325
|
+
}
|
|
11326
|
+
if (!isObject(parsed)) return { problem: "not a JSON object" };
|
|
11327
|
+
if (parsed["mcpServers"] !== void 0 && !isObject(parsed["mcpServers"])) return { problem: "its mcpServers is not an object" };
|
|
11328
|
+
return { root: parsed, raw };
|
|
11329
|
+
}
|
|
11330
|
+
function serializeLike(raw, root) {
|
|
11331
|
+
let body = JSON.stringify(root, null, 2);
|
|
11332
|
+
if (raw === null || raw.trimEnd() !== raw || raw.trim() === "") body += "\n";
|
|
11333
|
+
if (raw !== null && raw.includes("\r\n")) body = body.replace(/\n/g, "\r\n");
|
|
11334
|
+
if (raw !== null && raw.startsWith(BOM)) body = BOM + body;
|
|
11335
|
+
return body;
|
|
11336
|
+
}
|
|
11337
|
+
function write(file, read2, next) {
|
|
11338
|
+
if (read2.raw !== null) {
|
|
11339
|
+
try {
|
|
11340
|
+
fs19.accessSync(file, fs19.constants.W_OK);
|
|
11341
|
+
} catch {
|
|
11342
|
+
return `${file} is read-only, so it was left as it was`;
|
|
11343
|
+
}
|
|
11344
|
+
if (fs19.readFileSync(file, "utf8") !== read2.raw) return `${file} changed while setup ran (Claude Code writes it); run setup again`;
|
|
11345
|
+
const backup = `${file}.flatbread.bak`;
|
|
11346
|
+
if (!fs19.existsSync(backup)) fs19.copyFileSync(file, backup);
|
|
11347
|
+
}
|
|
11348
|
+
const mode = read2.raw !== null ? fs19.statSync(file).mode & 511 : 384;
|
|
11349
|
+
fs19.mkdirSync(path18.dirname(file), { recursive: true });
|
|
11350
|
+
const temp = `${file}.flatbread-${process.pid}.tmp`;
|
|
11351
|
+
fs19.writeFileSync(temp, next, { encoding: "utf8", mode });
|
|
11352
|
+
fs19.renameSync(temp, file);
|
|
11353
|
+
return null;
|
|
11354
|
+
}
|
|
11355
|
+
function registerClaudeMcp(options) {
|
|
11356
|
+
const { file, log } = options;
|
|
11357
|
+
const read2 = readConfig(file);
|
|
11358
|
+
if ("problem" in read2) {
|
|
11359
|
+
log(` mcp refused: ${file}: ${read2.problem}`);
|
|
11360
|
+
log(" Claude Code's own state lives there, so a file we cannot read is not one we rewrite.");
|
|
11361
|
+
if (options.uninstall !== true) log(` by hand: claude mcp add --scope user ${MCP_SERVER_NAME} -- "${options.execPath}" "${options.entryPath}" mcp`);
|
|
11362
|
+
return 1;
|
|
11363
|
+
}
|
|
11364
|
+
const servers = isObject(read2.root["mcpServers"]) ? read2.root["mcpServers"] : void 0;
|
|
11365
|
+
const current = servers?.[MCP_SERVER_NAME];
|
|
11366
|
+
if (options.uninstall === true) {
|
|
11367
|
+
if (current === void 0) {
|
|
11368
|
+
log(` mcp no ${MCP_SERVER_NAME} server in ${file}`);
|
|
11369
|
+
return 0;
|
|
11370
|
+
}
|
|
11371
|
+
if (!runsFlatbreadMcp(current)) {
|
|
11372
|
+
log(` mcp ${file}: the ${MCP_SERVER_NAME} server there does not run flatbread, left alone`);
|
|
11373
|
+
return 0;
|
|
11374
|
+
}
|
|
11375
|
+
if (options.dryRun === true) {
|
|
11376
|
+
log(` mcp ${file}: would remove the ${MCP_SERVER_NAME} server`);
|
|
11377
|
+
return 0;
|
|
11378
|
+
}
|
|
11379
|
+
const rest = { ...servers };
|
|
11380
|
+
delete rest[MCP_SERVER_NAME];
|
|
11381
|
+
const problem2 = write(file, read2, serializeLike(read2.raw, { ...read2.root, mcpServers: rest }));
|
|
11382
|
+
if (problem2 !== null) {
|
|
11383
|
+
log(` mcp refused: ${problem2}`);
|
|
11384
|
+
return 1;
|
|
11385
|
+
}
|
|
11386
|
+
log(` mcp ${file}: removed the ${MCP_SERVER_NAME} server`);
|
|
11387
|
+
const backup = `${file}.flatbread.bak`;
|
|
11388
|
+
if (fs19.existsSync(backup)) log(` ${backup} is the copy setup took before its first write; delete it when you like`);
|
|
11389
|
+
return 0;
|
|
11390
|
+
}
|
|
11391
|
+
const entry = mcpEntryFor(options.execPath, options.entryPath);
|
|
11392
|
+
if (current !== void 0 && !runsFlatbreadMcp(current)) {
|
|
11393
|
+
log(` mcp refused: ${file} already has a server named ${MCP_SERVER_NAME} that does not run flatbread, left alone`);
|
|
11394
|
+
return 1;
|
|
11395
|
+
}
|
|
11396
|
+
if (JSON.stringify(current) === JSON.stringify(entry)) {
|
|
11397
|
+
log(` mcp ${file}: registered for every project, unchanged`);
|
|
11398
|
+
return 0;
|
|
11399
|
+
}
|
|
11400
|
+
const verb = current === void 0 ? "register" : "update";
|
|
11401
|
+
if (options.dryRun === true) {
|
|
11402
|
+
log(` mcp ${file}: would ${verb} "${MCP_SERVER_NAME}": ${JSON.stringify(entry)}`);
|
|
11403
|
+
return 0;
|
|
11404
|
+
}
|
|
11405
|
+
const root = { ...read2.root, mcpServers: { ...servers ?? {}, [MCP_SERVER_NAME]: entry } };
|
|
11406
|
+
const problem = write(file, read2, serializeLike(read2.raw, root));
|
|
11407
|
+
if (problem !== null) {
|
|
11408
|
+
log(` mcp refused: ${problem}`);
|
|
11409
|
+
return 1;
|
|
11410
|
+
}
|
|
11411
|
+
log(` mcp ${file}: ${verb === "register" ? "registered" : "updated"} for every project (user scope)`);
|
|
11412
|
+
log(" restart Claude Code (or reload the VS Code window) so it starts the server");
|
|
11413
|
+
return 0;
|
|
11414
|
+
}
|
|
11415
|
+
|
|
11416
|
+
// src/git-hook.ts
|
|
11417
|
+
import fs20 from "node:fs";
|
|
11418
|
+
import path19 from "node:path";
|
|
10876
11419
|
var POST_COMMIT_MARKER = "# flatbread post-commit hook - remove with: flatbread setup --uninstall";
|
|
10877
11420
|
var CHAINED_HOOK = "post-commit.pre-flatbread";
|
|
10878
11421
|
var sq = (value) => `'${value.replace(/\\/g, "/").replace(/'/g, "'\\''")}'`;
|
|
@@ -10916,33 +11459,33 @@ function hooksDirFor(cwd, options = {}) {
|
|
|
10916
11459
|
}
|
|
10917
11460
|
const common = git(top, ["rev-parse", "--git-common-dir"], options)?.trim();
|
|
10918
11461
|
if (common === void 0 || common.length === 0) return { ok: false, reason: "unreadable", detail: `git could not name the git directory of ${top}` };
|
|
10919
|
-
return { ok: true, repo:
|
|
11462
|
+
return { ok: true, repo: path19.resolve(top), dir: path19.resolve(top, common, "hooks"), ...machineHooksPath !== void 0 ? { machineHooksPath } : {} };
|
|
10920
11463
|
}
|
|
10921
11464
|
var read = (file) => {
|
|
10922
11465
|
try {
|
|
10923
|
-
return
|
|
11466
|
+
return fs20.readFileSync(file, "utf8");
|
|
10924
11467
|
} catch {
|
|
10925
11468
|
return null;
|
|
10926
11469
|
}
|
|
10927
11470
|
};
|
|
10928
11471
|
function installGitHook(dir, script, options = {}) {
|
|
10929
|
-
const hook =
|
|
10930
|
-
const chained =
|
|
11472
|
+
const hook = path19.join(dir, "post-commit");
|
|
11473
|
+
const chained = path19.join(dir, CHAINED_HOOK);
|
|
10931
11474
|
const current = read(hook);
|
|
10932
|
-
const
|
|
11475
|
+
const write2 = (lines2) => {
|
|
10933
11476
|
if (options.dryRun === true) return { code: 0, lines: [...lines2, "--dry-run: nothing was written"] };
|
|
10934
|
-
|
|
10935
|
-
if (current !== null && !isOurs(current))
|
|
10936
|
-
|
|
10937
|
-
|
|
11477
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
11478
|
+
if (current !== null && !isOurs(current)) fs20.renameSync(hook, chained);
|
|
11479
|
+
fs20.writeFileSync(hook, script, { encoding: "utf8", mode: 493 });
|
|
11480
|
+
fs20.chmodSync(hook, 493);
|
|
10938
11481
|
return { code: 0, lines: lines2 };
|
|
10939
11482
|
};
|
|
10940
|
-
if (current === null) return
|
|
11483
|
+
if (current === null) return write2([`post-commit ${hook} (new)`]);
|
|
10941
11484
|
if (isOurs(current)) {
|
|
10942
11485
|
if (current === script) return { code: 0, lines: [`post-commit ${hook}`, "already installed, unchanged"] };
|
|
10943
|
-
return
|
|
11486
|
+
return write2([`post-commit ${hook}`, "updated the flatbread hook"]);
|
|
10944
11487
|
}
|
|
10945
|
-
if (
|
|
11488
|
+
if (fs20.existsSync(chained)) {
|
|
10946
11489
|
return {
|
|
10947
11490
|
code: 1,
|
|
10948
11491
|
lines: [
|
|
@@ -10952,16 +11495,16 @@ function installGitHook(dir, script, options = {}) {
|
|
|
10952
11495
|
]
|
|
10953
11496
|
};
|
|
10954
11497
|
}
|
|
10955
|
-
return
|
|
11498
|
+
return write2([
|
|
10956
11499
|
`post-commit ${hook}`,
|
|
10957
11500
|
`your post-commit was kept as ${CHAINED_HOOK} and runs first, unchanged, with its exit status`
|
|
10958
11501
|
]);
|
|
10959
11502
|
}
|
|
10960
11503
|
function uninstallGitHook(dir, options = {}) {
|
|
10961
|
-
const hook =
|
|
10962
|
-
const chained =
|
|
11504
|
+
const hook = path19.join(dir, "post-commit");
|
|
11505
|
+
const chained = path19.join(dir, CHAINED_HOOK);
|
|
10963
11506
|
const current = read(hook);
|
|
10964
|
-
const hasChained =
|
|
11507
|
+
const hasChained = fs20.existsSync(chained);
|
|
10965
11508
|
const done = (lines2, act) => {
|
|
10966
11509
|
if (options.dryRun === true) return { code: 0, lines: [...lines2, "--dry-run: nothing was written"] };
|
|
10967
11510
|
act();
|
|
@@ -10979,17 +11522,17 @@ function uninstallGitHook(dir, options = {}) {
|
|
|
10979
11522
|
if (current === null && !hasChained) return { code: 0, lines: [`no flatbread post-commit in ${dir}`] };
|
|
10980
11523
|
if (hasChained) {
|
|
10981
11524
|
return done([`post-commit ${hook}`, `restored your post-commit from ${CHAINED_HOOK}`], () => {
|
|
10982
|
-
if (current !== null)
|
|
10983
|
-
|
|
11525
|
+
if (current !== null) fs20.rmSync(hook);
|
|
11526
|
+
fs20.renameSync(chained, hook);
|
|
10984
11527
|
});
|
|
10985
11528
|
}
|
|
10986
|
-
return done([`post-commit ${hook}`, "removed the flatbread hook"], () =>
|
|
11529
|
+
return done([`post-commit ${hook}`, "removed the flatbread hook"], () => fs20.rmSync(hook));
|
|
10987
11530
|
}
|
|
10988
11531
|
|
|
10989
11532
|
// src/hook.ts
|
|
10990
11533
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
|
|
10991
|
-
import
|
|
10992
|
-
import
|
|
11534
|
+
import fs21 from "node:fs";
|
|
11535
|
+
import path20 from "node:path";
|
|
10993
11536
|
var HOOK_EVENTS = ["Stop", "SessionEnd"];
|
|
10994
11537
|
var OFFER_AFTER_STOPS = 2;
|
|
10995
11538
|
var MAX_TRACKED_SESSIONS = 50;
|
|
@@ -10999,14 +11542,14 @@ function emptyHookState() {
|
|
|
10999
11542
|
return { version: HOOK_STATE_VERSION, sessions: {} };
|
|
11000
11543
|
}
|
|
11001
11544
|
function hookStatePath(env = process.env) {
|
|
11002
|
-
return
|
|
11545
|
+
return path20.join(flatbreadHome(env), "hook-state.json");
|
|
11003
11546
|
}
|
|
11004
11547
|
function readHookState(env = process.env) {
|
|
11005
11548
|
const file = hookStatePath(env);
|
|
11006
|
-
if (!
|
|
11549
|
+
if (!fs21.existsSync(file)) return { ok: true, state: emptyHookState(), present: false };
|
|
11007
11550
|
let raw;
|
|
11008
11551
|
try {
|
|
11009
|
-
raw =
|
|
11552
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
11010
11553
|
} catch (error) {
|
|
11011
11554
|
return { ok: false, reason: "unreadable", detail: error.message };
|
|
11012
11555
|
}
|
|
@@ -11049,9 +11592,9 @@ function pruneHookState(state, keep = MAX_TRACKED_SESSIONS) {
|
|
|
11049
11592
|
}
|
|
11050
11593
|
function writeHookState(state, env = process.env) {
|
|
11051
11594
|
const dir = flatbreadHome(env);
|
|
11052
|
-
|
|
11595
|
+
fs21.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
11053
11596
|
const file = hookStatePath(env);
|
|
11054
|
-
|
|
11597
|
+
fs21.writeFileSync(file, JSON.stringify(pruneHookState(state), null, 2) + "\n", {
|
|
11055
11598
|
encoding: "utf8",
|
|
11056
11599
|
mode: 384
|
|
11057
11600
|
});
|
|
@@ -11197,7 +11740,7 @@ async function runHookNotify(io) {
|
|
|
11197
11740
|
const stdout = io.stdout ?? ((line) => process.stdout.write(line + "\n"));
|
|
11198
11741
|
const stderr = io.stderr ?? ((line) => process.stderr.write(line + "\n"));
|
|
11199
11742
|
const now = io.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11200
|
-
const exists = io.exists ?? ((filePath) =>
|
|
11743
|
+
const exists = io.exists ?? ((filePath) => fs21.existsSync(filePath));
|
|
11201
11744
|
const execPath = io.execPath ?? process.execPath;
|
|
11202
11745
|
const entryPath = io.entryPath ?? process.argv[1] ?? "";
|
|
11203
11746
|
try {
|
|
@@ -11354,10 +11897,10 @@ function removeHookConfig(settings) {
|
|
|
11354
11897
|
return { settings: root, removed };
|
|
11355
11898
|
}
|
|
11356
11899
|
function readSettingsFile(file) {
|
|
11357
|
-
if (!
|
|
11900
|
+
if (!fs21.existsSync(file)) return { present: false, file };
|
|
11358
11901
|
let raw;
|
|
11359
11902
|
try {
|
|
11360
|
-
raw =
|
|
11903
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
11361
11904
|
} catch (error) {
|
|
11362
11905
|
return { present: true, file, unreadable: error.message };
|
|
11363
11906
|
}
|
|
@@ -11369,10 +11912,10 @@ function readSettingsFile(file) {
|
|
|
11369
11912
|
}
|
|
11370
11913
|
}
|
|
11371
11914
|
function writeSettingsFile(file, settings) {
|
|
11372
|
-
|
|
11915
|
+
fs21.mkdirSync(path20.dirname(file), { recursive: true });
|
|
11373
11916
|
const temp = `${file}.flatbread-${process.pid}.tmp`;
|
|
11374
|
-
|
|
11375
|
-
|
|
11917
|
+
fs21.writeFileSync(temp, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
11918
|
+
fs21.renameSync(temp, file);
|
|
11376
11919
|
}
|
|
11377
11920
|
function handlersFor(execPath, entryPath, capture = false) {
|
|
11378
11921
|
return {
|
|
@@ -11530,8 +12073,8 @@ function runHookUninstall(options = {}) {
|
|
|
11530
12073
|
function settingsCandidates(env, cwd) {
|
|
11531
12074
|
return [
|
|
11532
12075
|
{ label: "user", file: claudeSettingsPath(env) },
|
|
11533
|
-
{ label: "project", file:
|
|
11534
|
-
{ label: "local", file:
|
|
12076
|
+
{ label: "project", file: path20.join(cwd, ".claude", "settings.json") },
|
|
12077
|
+
{ label: "local", file: path20.join(cwd, ".claude", "settings.local.json") }
|
|
11535
12078
|
];
|
|
11536
12079
|
}
|
|
11537
12080
|
function installedEventsIn(parsed, exists) {
|
|
@@ -11561,7 +12104,7 @@ function runHookStatus(options = {}) {
|
|
|
11561
12104
|
const env = options.env ?? process.env;
|
|
11562
12105
|
const log = options.log ?? ((line) => console.log(line));
|
|
11563
12106
|
const cwd = options.cwd ?? process.cwd();
|
|
11564
|
-
const exists = options.exists ?? ((filePath) =>
|
|
12107
|
+
const exists = options.exists ?? ((filePath) => fs21.existsSync(filePath));
|
|
11565
12108
|
const candidates = options.settingsPath === void 0 ? settingsCandidates(env, cwd) : [{ label: "settings", file: options.settingsPath }];
|
|
11566
12109
|
log("");
|
|
11567
12110
|
log("flatbread hook");
|
|
@@ -11711,6 +12254,22 @@ function runSetup(options = {}) {
|
|
|
11711
12254
|
...options.spawn !== void 0 ? { spawn: options.spawn } : {},
|
|
11712
12255
|
...settings
|
|
11713
12256
|
});
|
|
12257
|
+
let mcp = 0;
|
|
12258
|
+
if (options.noMcp === true) {
|
|
12259
|
+
log(" --no-mcp: Claude Code's MCP servers were not touched");
|
|
12260
|
+
} else if (claude === 0 || options.uninstall === true) {
|
|
12261
|
+
mcp = registerClaudeMcp({
|
|
12262
|
+
file: options.claudeConfigPath ?? claudeUserConfigPath(env),
|
|
12263
|
+
execPath,
|
|
12264
|
+
entryPath,
|
|
12265
|
+
uninstall: options.uninstall === true,
|
|
12266
|
+
dryRun,
|
|
12267
|
+
log
|
|
12268
|
+
});
|
|
12269
|
+
} else {
|
|
12270
|
+
log(" mcp not registered, because the hook step above was refused");
|
|
12271
|
+
}
|
|
12272
|
+
log("");
|
|
11714
12273
|
let git2 = 0;
|
|
11715
12274
|
log("git");
|
|
11716
12275
|
if (options.noGit === true) {
|
|
@@ -11746,11 +12305,11 @@ function runSetup(options = {}) {
|
|
|
11746
12305
|
log(" share one with: flatbread share --file <its session>, which opens the review");
|
|
11747
12306
|
}
|
|
11748
12307
|
log("");
|
|
11749
|
-
return claude !== 0 || git2 !== 0 ? 1 : 0;
|
|
12308
|
+
return claude !== 0 || mcp !== 0 || git2 !== 0 ? 1 : 0;
|
|
11750
12309
|
}
|
|
11751
12310
|
|
|
11752
12311
|
// src/login-server.ts
|
|
11753
|
-
import
|
|
12312
|
+
import crypto3 from "node:crypto";
|
|
11754
12313
|
import http2 from "node:http";
|
|
11755
12314
|
function callbackOrigin(port) {
|
|
11756
12315
|
return `http://127.0.0.1:${port}`;
|
|
@@ -11825,7 +12384,7 @@ function parseDelivery(raw, expectedState, nowSeconds) {
|
|
|
11825
12384
|
}
|
|
11826
12385
|
const given = Buffer.from(state, "utf8");
|
|
11827
12386
|
const wanted = Buffer.from(expectedState, "utf8");
|
|
11828
|
-
if (given.length !== wanted.length || !
|
|
12387
|
+
if (given.length !== wanted.length || !crypto3.timingSafeEqual(given, wanted)) {
|
|
11829
12388
|
return { ok: false, problem: "the callback state did not match" };
|
|
11830
12389
|
}
|
|
11831
12390
|
const sessionRaw = body["session"];
|
|
@@ -11873,7 +12432,7 @@ async function readBody2(req, limitBytes = 64 * 1024) {
|
|
|
11873
12432
|
async function startLoginServer(options = {}) {
|
|
11874
12433
|
const ports = options.ports ?? DEFAULT_LOGIN_PORTS;
|
|
11875
12434
|
const now = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
11876
|
-
const state = options.state ??
|
|
12435
|
+
const state = options.state ?? crypto3.randomBytes(32).toString("hex");
|
|
11877
12436
|
let settle;
|
|
11878
12437
|
const delivery = new Promise((resolve) => {
|
|
11879
12438
|
settle = resolve;
|
|
@@ -11959,13 +12518,13 @@ async function bindFirst(server, ports) {
|
|
|
11959
12518
|
}
|
|
11960
12519
|
|
|
11961
12520
|
// src/index.ts
|
|
11962
|
-
var PACKAGE_ROOT =
|
|
11963
|
-
var SOURCE_CHECKOUT =
|
|
11964
|
-
var TMP_DIR = process.env["FLATBREAD_TMP_DIR"]?.trim() || (SOURCE_CHECKOUT ?
|
|
11965
|
-
var OUTPUT_PATH =
|
|
11966
|
-
var SHARE_OUTPUT_PATH =
|
|
12521
|
+
var PACKAGE_ROOT = path21.resolve(path21.dirname(fileURLToPath2(import.meta.url)), "..");
|
|
12522
|
+
var SOURCE_CHECKOUT = fs22.existsSync(path21.join(PACKAGE_ROOT, "src", "index.ts"));
|
|
12523
|
+
var TMP_DIR = process.env["FLATBREAD_TMP_DIR"]?.trim() || (SOURCE_CHECKOUT ? path21.join(PACKAGE_ROOT, ".tmp") : path21.join(os8.homedir(), ".flatbread"));
|
|
12524
|
+
var OUTPUT_PATH = path21.join(TMP_DIR, "last-parse.json");
|
|
12525
|
+
var SHARE_OUTPUT_PATH = path21.join(TMP_DIR, "last-share.json");
|
|
11967
12526
|
function storeDir() {
|
|
11968
|
-
return resolveStoreDir() ?? (SOURCE_CHECKOUT ? null :
|
|
12527
|
+
return resolveStoreDir() ?? (SOURCE_CHECKOUT ? null : path21.join(TMP_DIR, DEFAULT_STORE_DIRNAME));
|
|
11969
12528
|
}
|
|
11970
12529
|
var USAGE = `flatbread - registry of AI agent runs
|
|
11971
12530
|
|
|
@@ -11975,14 +12534,16 @@ Usage:
|
|
|
11975
12534
|
flatbread share Review + sanitize the newest session
|
|
11976
12535
|
flatbread share --file <path> Review + sanitize a specific session
|
|
11977
12536
|
flatbread view Write the brief and capsule, print the viewer URL
|
|
11978
|
-
flatbread pull <id|url> Print a run's Context Capsule
|
|
12537
|
+
flatbread pull <id|url> Print a run's Context Capsule: a run this machine shared or
|
|
12538
|
+
captured is read here, anything else from the site
|
|
11979
12539
|
flatbread pull <id|url> --out <file> Write it to a file instead
|
|
11980
12540
|
flatbread mcp MCP server on stdio: flatbread_search_traces and
|
|
11981
12541
|
flatbread_get_capsule, for your coding agent
|
|
11982
12542
|
flatbread search <words> Find runs: this machine's shared and captured ones, then
|
|
11983
12543
|
the site (the same answer the MCP server gives)
|
|
11984
12544
|
flatbread setup Auto-capture: the Claude Code hook, plus a chained git
|
|
11985
|
-
post-commit in this repository
|
|
12545
|
+
post-commit in this repository, and the MCP server for
|
|
12546
|
+
every Claude Code project. Captures stay local
|
|
11986
12547
|
flatbread setup --uninstall Remove exactly what setup added
|
|
11987
12548
|
flatbread capture --file <path> Capture one session into the local bucket (setup's hooks
|
|
11988
12549
|
run this; redacted, private, never uploaded)
|
|
@@ -12024,9 +12585,10 @@ Options for search:
|
|
|
12024
12585
|
|
|
12025
12586
|
Options for setup:
|
|
12026
12587
|
--dry-run Say what would be written, write nothing
|
|
12027
|
-
--uninstall Remove the handlers and the post-commit;
|
|
12028
|
-
post-commit hook setup had chained
|
|
12588
|
+
--uninstall Remove the handlers, the MCP server and the post-commit;
|
|
12589
|
+
restore a post-commit hook setup had chained
|
|
12029
12590
|
--no-git Leave the repository's hooks alone
|
|
12591
|
+
--no-mcp Leave Claude Code's MCP servers alone
|
|
12030
12592
|
--settings <path> --no-probe As for hook install
|
|
12031
12593
|
|
|
12032
12594
|
Options for capture:
|
|
@@ -12049,14 +12611,16 @@ Options for adapters:
|
|
|
12049
12611
|
Notes:
|
|
12050
12612
|
parse and share record the git branch, HEAD commit and uncommitted file names when the
|
|
12051
12613
|
session ran inside a git repository. Nothing else from git is read.
|
|
12052
|
-
parse --save writes ${
|
|
12053
|
-
Share drafts are written to ${
|
|
12614
|
+
parse --save writes ${path21.relative(process.cwd(), OUTPUT_PATH) || OUTPUT_PATH}
|
|
12615
|
+
Share drafts are written to ${path21.relative(process.cwd(), SHARE_OUTPUT_PATH) || SHARE_OUTPUT_PATH}
|
|
12616
|
+
(the latest only; every shared run is also kept under its ID, for search and pull)
|
|
12054
12617
|
Every parsed run defaults to visibility "private".
|
|
12055
12618
|
Nothing is uploaded unless you pass --publish, or set FLATBREAD_API_URL and
|
|
12056
12619
|
FLATBREAD_API_TOKEN yourself. pull and mcp only read: public runs, your own when you
|
|
12057
12620
|
are signed in, and a private run through its share link. The review UI listens on
|
|
12058
|
-
127.0.0.1 only
|
|
12059
|
-
|
|
12621
|
+
127.0.0.1 only, on 4321-4325 or, when all five are busy, a port the OS picks.
|
|
12622
|
+
setup registers the MCP server in ~/.claude.json, for every project. By hand instead:
|
|
12623
|
+
claude mcp add --scope user flatbread -- flatbread mcp
|
|
12060
12624
|
The viewer is a separate dev server: npm run dev -w @flatbread/web
|
|
12061
12625
|
Captured runs live in ~/.flatbread/bucket (FLATBREAD_HOOK_DISABLED=1 stops every capture).
|
|
12062
12626
|
A capture is not a share: to publish one, run share --file on its session.
|
|
@@ -12085,10 +12649,10 @@ function parseArgv(argv) {
|
|
|
12085
12649
|
} else if (arg === "--out") {
|
|
12086
12650
|
const value = argv[i + 1];
|
|
12087
12651
|
if (value === void 0 || value.startsWith("--")) throw new Error("--out needs a file path");
|
|
12088
|
-
args.out =
|
|
12652
|
+
args.out = path21.resolve(value);
|
|
12089
12653
|
i += 1;
|
|
12090
12654
|
} else if (arg !== void 0 && arg.startsWith("--out=")) {
|
|
12091
|
-
args.out =
|
|
12655
|
+
args.out = path21.resolve(arg.slice("--out=".length));
|
|
12092
12656
|
} else if (arg === "--harness") {
|
|
12093
12657
|
const value = argv[i + 1];
|
|
12094
12658
|
if (value === void 0 || value.startsWith("--")) {
|
|
@@ -12209,12 +12773,12 @@ async function runParse(args) {
|
|
|
12209
12773
|
target = found[0];
|
|
12210
12774
|
console.log(`latest ${harness} session: ${target}`);
|
|
12211
12775
|
} else if (args.file) {
|
|
12212
|
-
target =
|
|
12213
|
-
if (!
|
|
12776
|
+
target = path21.resolve(args.file);
|
|
12777
|
+
if (!fs22.existsSync(target)) {
|
|
12214
12778
|
console.error(`file not found: ${target}`);
|
|
12215
12779
|
return 1;
|
|
12216
12780
|
}
|
|
12217
|
-
if (!
|
|
12781
|
+
if (!fs22.statSync(target).isFile()) {
|
|
12218
12782
|
console.error(`not a file: ${target}`);
|
|
12219
12783
|
return 1;
|
|
12220
12784
|
}
|
|
@@ -12245,8 +12809,8 @@ async function runParse(args) {
|
|
|
12245
12809
|
const trace = withLineage(result.trace, readGitLineage(result.trace.source.cwd));
|
|
12246
12810
|
FlatbreadTraceSchema.parse(trace);
|
|
12247
12811
|
if (args.out !== void 0) {
|
|
12248
|
-
|
|
12249
|
-
|
|
12812
|
+
fs22.mkdirSync(path21.dirname(args.out), { recursive: true });
|
|
12813
|
+
fs22.writeFileSync(args.out, JSON.stringify(trace, null, 2) + "\n", "utf8");
|
|
12250
12814
|
}
|
|
12251
12815
|
report({ ...result, trace }, args.out);
|
|
12252
12816
|
return 0;
|
|
@@ -12310,8 +12874,8 @@ async function runAdapters(args) {
|
|
|
12310
12874
|
}
|
|
12311
12875
|
let input = null;
|
|
12312
12876
|
if (args.file !== void 0) {
|
|
12313
|
-
const resolved =
|
|
12314
|
-
if (!
|
|
12877
|
+
const resolved = path21.resolve(args.file);
|
|
12878
|
+
if (!fs22.existsSync(resolved)) {
|
|
12315
12879
|
console.error(`file not found: ${resolved}`);
|
|
12316
12880
|
return 1;
|
|
12317
12881
|
}
|
|
@@ -12637,7 +13201,7 @@ async function prompt(label) {
|
|
|
12637
13201
|
});
|
|
12638
13202
|
}
|
|
12639
13203
|
var HOOK_SUBCOMMANDS = ["install", "status", "uninstall", "notify"];
|
|
12640
|
-
var CLI_ENTRY =
|
|
13204
|
+
var CLI_ENTRY = fileURLToPath2(import.meta.url);
|
|
12641
13205
|
function parseHookArgv(argv) {
|
|
12642
13206
|
const [sub, ...rest] = argv;
|
|
12643
13207
|
if (sub === void 0) {
|
|
@@ -12730,13 +13294,13 @@ function parsePullArgv(argv) {
|
|
|
12730
13294
|
if (ref === void 0) throw new Error("pull needs a run ID or a /r/<id> link, e.g. flatbread pull quiet-otter-4821");
|
|
12731
13295
|
return { ref, ...out !== void 0 ? { out } : {} };
|
|
12732
13296
|
}
|
|
12733
|
-
async function runPull(args) {
|
|
13297
|
+
async function runPull(args, deps = { ...defaultMcpDeps(storeDir(), bucketDir()), capsule: (ref) => fetchCapsule(ref) }) {
|
|
12734
13298
|
const ref = parseRunRef(args.ref);
|
|
12735
13299
|
if (ref === null) {
|
|
12736
13300
|
console.error(`"${args.ref}" is not a run ID or a /r/<id> link`);
|
|
12737
13301
|
return 1;
|
|
12738
13302
|
}
|
|
12739
|
-
const result = await
|
|
13303
|
+
const result = await readCapsule(ref, deps);
|
|
12740
13304
|
if (!result.ok) {
|
|
12741
13305
|
console.error(describeReadFailure(result, `could not pull ${ref.slug}`));
|
|
12742
13306
|
return 1;
|
|
@@ -12745,10 +13309,10 @@ async function runPull(args) {
|
|
|
12745
13309
|
process.stdout.write(result.capsule);
|
|
12746
13310
|
return 0;
|
|
12747
13311
|
}
|
|
12748
|
-
const target =
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
console.error(`wrote ${target}`);
|
|
13312
|
+
const target = path21.resolve(args.out);
|
|
13313
|
+
fs22.mkdirSync(path21.dirname(target), { recursive: true });
|
|
13314
|
+
fs22.writeFileSync(target, result.capsule, "utf8");
|
|
13315
|
+
console.error(`wrote ${target}${result.from === "site" ? "" : ` (from this machine's ${result.from === "store" ? "shared runs" : "captured runs"})`}`);
|
|
12752
13316
|
return 0;
|
|
12753
13317
|
}
|
|
12754
13318
|
function parseSearchArgv(argv) {
|
|
@@ -12860,6 +13424,7 @@ function parseSetupArgv(argv) {
|
|
|
12860
13424
|
dryRun: false,
|
|
12861
13425
|
uninstall: false,
|
|
12862
13426
|
noGit: false,
|
|
13427
|
+
noMcp: false,
|
|
12863
13428
|
noProbe: false
|
|
12864
13429
|
};
|
|
12865
13430
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -12867,6 +13432,7 @@ function parseSetupArgv(argv) {
|
|
|
12867
13432
|
if (arg === "--dry-run") args.dryRun = true;
|
|
12868
13433
|
else if (arg === "--uninstall") args.uninstall = true;
|
|
12869
13434
|
else if (arg === "--no-git") args.noGit = true;
|
|
13435
|
+
else if (arg === "--no-mcp") args.noMcp = true;
|
|
12870
13436
|
else if (arg === "--no-probe") args.noProbe = true;
|
|
12871
13437
|
else if (arg === "--settings") {
|
|
12872
13438
|
const value = argv[i + 1];
|
|
@@ -12913,6 +13479,7 @@ async function main(argv) {
|
|
|
12913
13479
|
dryRun: setup.dryRun,
|
|
12914
13480
|
uninstall: setup.uninstall,
|
|
12915
13481
|
noGit: setup.noGit,
|
|
13482
|
+
noMcp: setup.noMcp,
|
|
12916
13483
|
noProbe: setup.noProbe,
|
|
12917
13484
|
...setup.settings !== void 0 ? { settingsPath: setup.settings } : {}
|
|
12918
13485
|
});
|
|
@@ -12994,12 +13561,12 @@ var invokedDirectly = (() => {
|
|
|
12994
13561
|
if (entry === void 0) return false;
|
|
12995
13562
|
const real = (file) => {
|
|
12996
13563
|
try {
|
|
12997
|
-
return
|
|
13564
|
+
return fs22.realpathSync(file);
|
|
12998
13565
|
} catch {
|
|
12999
|
-
return
|
|
13566
|
+
return path21.resolve(file);
|
|
13000
13567
|
}
|
|
13001
13568
|
};
|
|
13002
|
-
return real(entry) === real(
|
|
13569
|
+
return real(entry) === real(fileURLToPath2(import.meta.url));
|
|
13003
13570
|
})();
|
|
13004
13571
|
if (invokedDirectly) {
|
|
13005
13572
|
main(process.argv.slice(2)).then((code) => {
|
|
@@ -13022,6 +13589,7 @@ export {
|
|
|
13022
13589
|
runCapture,
|
|
13023
13590
|
runLogin,
|
|
13024
13591
|
runLogout,
|
|
13592
|
+
runPull,
|
|
13025
13593
|
runSearch,
|
|
13026
13594
|
runWhoami
|
|
13027
13595
|
};
|