flatbread-cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/THIRD-PARTY-NOTICES.md +31 -0
- package/dist/flatbread.mjs +642 -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: "IDE conversations/<id>.pb (Windsurf's encrypted trajectory)",
|
|
4814
|
+
confidence: "documented",
|
|
4815
|
+
implemented: true,
|
|
4816
4816
|
humanTest: "A6",
|
|
4817
|
-
note: "
|
|
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
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,282 @@ 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 = 1;
|
|
6056
|
+
var KEY = Buffer.from("safeCodeiumworldKeYsecretBalloon");
|
|
6057
|
+
var NONCE = 12;
|
|
6058
|
+
var TAG = 16;
|
|
6059
|
+
var CONVERSATION = /[\\/]\.gemini[\\/]antigravity[^\\/]*[\\/]conversations[\\/][^\\/]+\.pb$/i;
|
|
6060
|
+
var PB_VARINT = 0;
|
|
6061
|
+
var PB_FIXED64 = 1;
|
|
6062
|
+
var PB_BYTES = 2;
|
|
6063
|
+
var PB_FIXED32 = 5;
|
|
6064
|
+
var Malformed = class extends Error {
|
|
6065
|
+
};
|
|
6066
|
+
function varint(b, at) {
|
|
6067
|
+
let value = 0n;
|
|
6068
|
+
let shift = 0n;
|
|
6069
|
+
for (let i = at; i < b.length && shift <= 63n; i++) {
|
|
6070
|
+
const byte = b[i];
|
|
6071
|
+
value |= BigInt(byte & 127) << shift;
|
|
6072
|
+
shift += 7n;
|
|
6073
|
+
if ((byte & 128) === 0) return [value, i + 1];
|
|
6074
|
+
}
|
|
6075
|
+
throw new Malformed("malformed protobuf");
|
|
6076
|
+
}
|
|
6077
|
+
function pbFields(b) {
|
|
6078
|
+
const out = [];
|
|
6079
|
+
let i = 0;
|
|
6080
|
+
while (i < b.length) {
|
|
6081
|
+
const [key, afterKey] = varint(b, i);
|
|
6082
|
+
i = afterKey;
|
|
6083
|
+
const num2 = Number(key >> 3n);
|
|
6084
|
+
const wire = Number(key & 7n);
|
|
6085
|
+
if (num2 === 0) throw new Malformed("malformed protobuf");
|
|
6086
|
+
let value = 0n;
|
|
6087
|
+
let data = Buffer.alloc(0);
|
|
6088
|
+
if (wire === PB_VARINT) {
|
|
6089
|
+
[value, i] = varint(b, i);
|
|
6090
|
+
} else if (wire === PB_FIXED64) {
|
|
6091
|
+
if (i + 8 > b.length) throw new Malformed("malformed protobuf");
|
|
6092
|
+
value = b.readBigUInt64LE(i);
|
|
6093
|
+
i += 8;
|
|
6094
|
+
} else if (wire === PB_FIXED32) {
|
|
6095
|
+
if (i + 4 > b.length) throw new Malformed("malformed protobuf");
|
|
6096
|
+
value = BigInt(b.readUInt32LE(i));
|
|
6097
|
+
i += 4;
|
|
6098
|
+
} else if (wire === PB_BYTES) {
|
|
6099
|
+
const [length, afterLength] = varint(b, i);
|
|
6100
|
+
if (length > BigInt(b.length - afterLength)) throw new Malformed("malformed protobuf");
|
|
6101
|
+
data = b.subarray(afterLength, afterLength + Number(length));
|
|
6102
|
+
i = afterLength + Number(length);
|
|
6103
|
+
} else {
|
|
6104
|
+
throw new Malformed("malformed protobuf");
|
|
6105
|
+
}
|
|
6106
|
+
out.push({ num: num2, wire, value, data });
|
|
6107
|
+
}
|
|
6108
|
+
return out;
|
|
6109
|
+
}
|
|
6110
|
+
function bytesField(fields, num2) {
|
|
6111
|
+
return fields.find((f) => f.num === num2 && f.wire === PB_BYTES)?.data;
|
|
6112
|
+
}
|
|
6113
|
+
function pbString(b) {
|
|
6114
|
+
return b === void 0 ? "" : b.toString("utf8").replaceAll("\0", "");
|
|
6115
|
+
}
|
|
6116
|
+
function pbTimestamp(b) {
|
|
6117
|
+
if (b === void 0) return void 0;
|
|
6118
|
+
let seconds = 0n;
|
|
6119
|
+
let nanos = 0n;
|
|
6120
|
+
for (const f of pbFields(b)) {
|
|
6121
|
+
if (f.wire !== PB_VARINT) continue;
|
|
6122
|
+
if (f.num === 1) seconds = BigInt.asIntN(64, f.value);
|
|
6123
|
+
if (f.num === 2) nanos = BigInt.asIntN(32, f.value);
|
|
6124
|
+
}
|
|
6125
|
+
if (seconds === 0n && nanos === 0n) return void 0;
|
|
6126
|
+
const ms = Number(seconds) * 1e3 + Math.floor(Number(nanos) / 1e6);
|
|
6127
|
+
const date = new Date(ms);
|
|
6128
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
6129
|
+
}
|
|
6130
|
+
function openConversation(b) {
|
|
6131
|
+
if (b.length < NONCE + TAG) return null;
|
|
6132
|
+
try {
|
|
6133
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", KEY, b.subarray(0, NONCE));
|
|
6134
|
+
decipher.setAuthTag(b.subarray(b.length - TAG));
|
|
6135
|
+
return Buffer.concat([decipher.update(b.subarray(NONCE, b.length - TAG)), decipher.final()]);
|
|
6136
|
+
} catch {
|
|
6137
|
+
return null;
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
function folderPath(uri) {
|
|
6141
|
+
if (!uri.startsWith("file:")) return void 0;
|
|
6142
|
+
try {
|
|
6143
|
+
return fileURLToPath(uri);
|
|
6144
|
+
} catch {
|
|
6145
|
+
try {
|
|
6146
|
+
return decodeURIComponent(new URL(uri).pathname) || void 0;
|
|
6147
|
+
} catch {
|
|
6148
|
+
return void 0;
|
|
6149
|
+
}
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
function workspaceOf(metadata) {
|
|
6153
|
+
if (metadata === void 0) return void 0;
|
|
6154
|
+
for (const f of pbFields(metadata)) {
|
|
6155
|
+
if (f.num !== 1 || f.wire !== PB_BYTES) continue;
|
|
6156
|
+
const folder = folderPath(pbString(bytesField(pbFields(f.data), 1)));
|
|
6157
|
+
if (folder !== void 0) return folder;
|
|
6158
|
+
}
|
|
6159
|
+
return void 0;
|
|
6160
|
+
}
|
|
6161
|
+
function tally(counts, key) {
|
|
6162
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
6163
|
+
}
|
|
6164
|
+
function decodeTrajectory2(plain) {
|
|
6165
|
+
const events = [];
|
|
6166
|
+
const unread = /* @__PURE__ */ new Map();
|
|
6167
|
+
let sessionId;
|
|
6168
|
+
let cwd;
|
|
6169
|
+
let steps = 0;
|
|
6170
|
+
let clock;
|
|
6171
|
+
for (const f of pbFields(plain)) {
|
|
6172
|
+
if (f.wire === PB_BYTES && f.num === 6) {
|
|
6173
|
+
sessionId = pbString(f.data) || sessionId;
|
|
6174
|
+
continue;
|
|
6175
|
+
}
|
|
6176
|
+
if (f.wire === PB_BYTES && f.num === 7) {
|
|
6177
|
+
cwd ??= workspaceOf(f.data);
|
|
6178
|
+
clock ??= pbTimestamp(bytesField(pbFields(f.data), 2));
|
|
6179
|
+
continue;
|
|
6180
|
+
}
|
|
6181
|
+
if (f.wire !== PB_BYTES || f.num !== 2) {
|
|
6182
|
+
tally(unread, `trajectory field ${f.num}`);
|
|
6183
|
+
continue;
|
|
6184
|
+
}
|
|
6185
|
+
steps++;
|
|
6186
|
+
const step = pbFields(f.data);
|
|
6187
|
+
const metadata = bytesField(step, 5);
|
|
6188
|
+
const metaFields = metadata === void 0 ? [] : pbFields(metadata);
|
|
6189
|
+
clock = pbTimestamp(bytesField(metaFields, 1)) ?? clock;
|
|
6190
|
+
const completed = pbTimestamp(bytesField(metaFields, 8));
|
|
6191
|
+
for (const s of step) {
|
|
6192
|
+
if (s.num !== 5 && s.num !== 19 && s.num !== 20) tally(unread, `step field ${s.num}`);
|
|
6193
|
+
}
|
|
6194
|
+
const input = bytesField(step, 19);
|
|
6195
|
+
if (input !== void 0) {
|
|
6196
|
+
const fields = pbFields(input);
|
|
6197
|
+
const text2 = pbString(bytesField(fields, 2)).trim() || pbString(bytesField(fields, 1)).trim();
|
|
6198
|
+
if (text2) events.push({ kind: "prompt", ...clock ? { timestamp: clock } : {}, text: text2 });
|
|
6199
|
+
}
|
|
6200
|
+
const planner = bytesField(step, 20);
|
|
6201
|
+
if (planner !== void 0) {
|
|
6202
|
+
const fields = pbFields(planner);
|
|
6203
|
+
const text2 = pbString(bytesField(fields, 1)).trim() || pbString(bytesField(fields, 8)).trim();
|
|
6204
|
+
if (bytesField(fields, 3) !== void 0) tally(unread, "planner thinking");
|
|
6205
|
+
const at = completed ?? clock;
|
|
6206
|
+
if (text2) events.push({ kind: "text", ...at ? { timestamp: at } : {}, text: text2 });
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
if (cwd !== void 0 || sessionId !== void 0) {
|
|
6210
|
+
events.unshift({ kind: "meta", ...sessionId ? { sessionId } : {}, ...cwd ? { cwd } : {} });
|
|
6211
|
+
}
|
|
6212
|
+
return { events, ...sessionId ? { sessionId } : {}, steps, unread };
|
|
6213
|
+
}
|
|
6214
|
+
function homeOf(env) {
|
|
6215
|
+
return (process.platform === "win32" ? env.USERPROFILE : env.HOME) || os2.homedir();
|
|
6216
|
+
}
|
|
6217
|
+
function listConversations(env = process.env) {
|
|
6218
|
+
const gemini = path3.join(homeOf(env), ".gemini");
|
|
6219
|
+
let roots;
|
|
6220
|
+
try {
|
|
6221
|
+
roots = fs3.readdirSync(gemini).filter((name) => /^antigravity/i.test(name));
|
|
6222
|
+
} catch {
|
|
6223
|
+
return [];
|
|
6066
6224
|
}
|
|
6067
|
-
|
|
6225
|
+
const found = [];
|
|
6226
|
+
for (const root of roots) {
|
|
6227
|
+
const dir = path3.join(gemini, root, "conversations");
|
|
6228
|
+
let names;
|
|
6229
|
+
try {
|
|
6230
|
+
names = fs3.readdirSync(dir);
|
|
6231
|
+
} catch {
|
|
6232
|
+
continue;
|
|
6233
|
+
}
|
|
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
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
return found.sort((a, b) => b.mtime - a.mtime).map((entry) => entry.file);
|
|
6068
6246
|
}
|
|
6247
|
+
var REFUSAL_TEST = "A6";
|
|
6069
6248
|
var antigravityAdapter = {
|
|
6070
6249
|
id: "antigravity",
|
|
6071
|
-
version:
|
|
6250
|
+
version: ANTIGRAVITY_ADAPTER_VERSION,
|
|
6072
6251
|
async detect(input) {
|
|
6073
|
-
|
|
6074
|
-
|
|
6252
|
+
if (CONVERSATION.test(input.path)) {
|
|
6253
|
+
return { score: 85, reason: "a conversations/<id>.pb under ~/.gemini/antigravity" };
|
|
6254
|
+
}
|
|
6255
|
+
return { score: 0, reason: "not an Antigravity conversations/<id>.pb" };
|
|
6075
6256
|
},
|
|
6076
6257
|
async read(input) {
|
|
6077
|
-
|
|
6258
|
+
const plain = openConversation(await fs3.promises.readFile(input.path));
|
|
6259
|
+
if (plain === null) {
|
|
6260
|
+
return {
|
|
6261
|
+
refused: true,
|
|
6262
|
+
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.`,
|
|
6263
|
+
humanTest: REFUSAL_TEST
|
|
6264
|
+
};
|
|
6265
|
+
}
|
|
6266
|
+
let trajectory;
|
|
6267
|
+
try {
|
|
6268
|
+
trajectory = decodeTrajectory2(plain);
|
|
6269
|
+
} catch (error) {
|
|
6270
|
+
if (!(error instanceof Malformed)) throw error;
|
|
6271
|
+
return {
|
|
6272
|
+
refused: true,
|
|
6273
|
+
reason: `${path3.basename(input.path)} authenticated but is not the trajectory protobuf this reader knows.`,
|
|
6274
|
+
humanTest: REFUSAL_TEST
|
|
6275
|
+
};
|
|
6276
|
+
}
|
|
6277
|
+
const said = trajectory.events.filter((e) => e.kind === "prompt" || e.kind === "text").length;
|
|
6278
|
+
if (said === 0) {
|
|
6279
|
+
return {
|
|
6280
|
+
refused: true,
|
|
6281
|
+
reason: `${path3.basename(input.path)} has ${trajectory.steps} step(s) and no prompt or reply in the fields this reader knows.`,
|
|
6282
|
+
humanTest: REFUSAL_TEST
|
|
6283
|
+
};
|
|
6284
|
+
}
|
|
6285
|
+
const unreadFields = [...trajectory.unread].sort((a, b) => b[1] - a[1]).map(([field, count]) => `${field} x${count}`);
|
|
6078
6286
|
return {
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6287
|
+
events: trajectory.events,
|
|
6288
|
+
capabilities: { tokens: "unavailable", filesChanged: "unavailable" },
|
|
6289
|
+
sessionId: trajectory.sessionId ?? path3.basename(input.path, ".pb"),
|
|
6290
|
+
unreadFields,
|
|
6291
|
+
notes: [
|
|
6292
|
+
`${trajectory.steps} step(s), ${said} prompt/reply event(s)`,
|
|
6293
|
+
"tool calls, thinking and edits are not read: their step fields are undocumented",
|
|
6294
|
+
"CONFIDENCE: documented (Windsurf's schema, measured on IDE 1.23.2). See HUMAN-TESTS.md A6."
|
|
6295
|
+
]
|
|
6082
6296
|
};
|
|
6083
6297
|
},
|
|
6084
6298
|
discover(env) {
|
|
6085
|
-
return
|
|
6299
|
+
return listConversations(env);
|
|
6086
6300
|
},
|
|
6087
|
-
async describe() {
|
|
6088
|
-
const
|
|
6089
|
-
|
|
6090
|
-
|
|
6301
|
+
async describe(input) {
|
|
6302
|
+
const files = input ? [input.path] : listConversations();
|
|
6303
|
+
const lines2 = [`conversations found: ${files.length}`];
|
|
6304
|
+
for (const file of files.slice(0, 8)) lines2.push(` ${file}`);
|
|
6305
|
+
const target = files[0];
|
|
6306
|
+
if (target === void 0) return lines2;
|
|
6307
|
+
lines2.push("", `inspecting ${target}`);
|
|
6308
|
+
let plain;
|
|
6309
|
+
try {
|
|
6310
|
+
plain = openConversation(await fs3.promises.readFile(target));
|
|
6311
|
+
} catch (error) {
|
|
6312
|
+
lines2.push(` unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
6313
|
+
return lines2;
|
|
6314
|
+
}
|
|
6315
|
+
lines2.push(` authenticated: ${plain === null ? "no" : "yes"}`);
|
|
6316
|
+
if (plain === null) return lines2;
|
|
6317
|
+
try {
|
|
6318
|
+
const trajectory = decodeTrajectory2(plain);
|
|
6319
|
+
lines2.push(` steps: ${trajectory.steps}, prompt/reply events: ${trajectory.events.filter((e) => e.kind !== "meta").length}`);
|
|
6320
|
+
for (const [field, count] of [...trajectory.unread].sort((a, b) => b[1] - a[1]).slice(0, 15)) {
|
|
6321
|
+
lines2.push(` ${String(count).padStart(6)} ${field}`);
|
|
6322
|
+
}
|
|
6323
|
+
} catch {
|
|
6324
|
+
lines2.push(" not the trajectory protobuf this reader knows");
|
|
6091
6325
|
}
|
|
6092
|
-
lines2.push("");
|
|
6093
|
-
lines2.push("If one is FOUND, HUMAN-TESTS.md A6 lists exactly what to report back.");
|
|
6094
6326
|
return lines2;
|
|
6095
6327
|
}
|
|
6096
6328
|
};
|
|
@@ -8752,7 +8984,7 @@ function withLineage(trace, lineage, prUrl) {
|
|
|
8752
8984
|
}
|
|
8753
8985
|
|
|
8754
8986
|
// src/auth.ts
|
|
8755
|
-
import
|
|
8987
|
+
import crypto2 from "node:crypto";
|
|
8756
8988
|
import fs15 from "node:fs";
|
|
8757
8989
|
import os6 from "node:os";
|
|
8758
8990
|
import path14 from "node:path";
|
|
@@ -8782,7 +9014,7 @@ function isAuthFailure(value) {
|
|
|
8782
9014
|
return typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
8783
9015
|
}
|
|
8784
9016
|
function hashSecret(secret) {
|
|
8785
|
-
return
|
|
9017
|
+
return crypto2.createHash("sha256").update(secret).digest("hex");
|
|
8786
9018
|
}
|
|
8787
9019
|
function apiUrlFrom(env = process.env) {
|
|
8788
9020
|
const explicit = env["FLATBREAD_API_URL"]?.trim();
|
|
@@ -8795,7 +9027,7 @@ async function startCliLogin(options = {}) {
|
|
|
8795
9027
|
const apiUrl = options.apiUrl ?? apiUrlFrom(env);
|
|
8796
9028
|
const base = validateApiUrl(apiUrl);
|
|
8797
9029
|
if (isAuthFailure(base)) return base;
|
|
8798
|
-
const pollSecret =
|
|
9030
|
+
const pollSecret = crypto2.randomBytes(32).toString("hex");
|
|
8799
9031
|
const result = await postJson(
|
|
8800
9032
|
`${base}/api/cli-login/start`,
|
|
8801
9033
|
{ poll_secret_hash: hashSecret(pollSecret) },
|
|
@@ -9586,67 +9818,105 @@ function renderMarked(value, matches) {
|
|
|
9586
9818
|
return html;
|
|
9587
9819
|
}
|
|
9588
9820
|
var STYLE = `
|
|
9589
|
-
:root{
|
|
9821
|
+
:root{
|
|
9822
|
+
--canvas:#fafafa;--bg:#ffffff;--ivory:#fcfbf8;--surface:#f6f7f8;--inset:#f6f7f8;
|
|
9823
|
+
--line:rgba(226,232,240,.8);--line-soft:#eff0f2;--line-strong:#b9bec4;
|
|
9824
|
+
--fg:#23272c;--fg-2:rgba(35,39,44,.7);--fg-3:rgba(35,39,44,.56);--fg-lift:#2b3138;
|
|
9825
|
+
--fail-bg:#f6ece7;--fail-line:#c9755c;--fail-fg:#8a3b2a;
|
|
9826
|
+
--ok-bg:#eef1e8;--ok-line:#7f9a7a;--ok-fg:#3f5d43;
|
|
9827
|
+
--hold-bg:#f7f1e2;--hold-line:#c9a34a;--hold-fg:#7d601d;
|
|
9828
|
+
--intent-line:#d6d1c1;
|
|
9829
|
+
--shade:30,41,54;
|
|
9830
|
+
--shadow:0 1px 2px rgba(var(--shade),.04),0 10px 26px rgba(var(--shade),.08);
|
|
9831
|
+
--radius:10px;--radius-sm:6px;
|
|
9832
|
+
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
|
9833
|
+
--mono:ui-monospace,"SF Mono",SFMono-Regular,"Cascadia Mono","Segoe UI Mono","Roboto Mono",Menlo,Consolas,"Liberation Mono",monospace
|
|
9834
|
+
}
|
|
9590
9835
|
*{box-sizing:border-box}
|
|
9591
|
-
body{margin:0
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9836
|
+
html,body{margin:0}
|
|
9837
|
+
body{background:var(--canvas);color:var(--fg);font:14px/1.55 var(--sans);-webkit-font-smoothing:antialiased}
|
|
9838
|
+
code,pre,dd,.mono{font-family:var(--mono)}
|
|
9839
|
+
header{background:var(--bg);border-bottom:1px solid var(--line-soft)}
|
|
9840
|
+
.hin{max-width:1440px;margin:0 auto;padding:14px 28px 16px}
|
|
9841
|
+
.bar{display:flex;align-items:center;gap:10px;margin:0 0 12px}
|
|
9842
|
+
.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}
|
|
9843
|
+
.crumb{color:var(--fg-3);font-size:12.5px}
|
|
9844
|
+
h1{margin:0 0 6px;font-size:20px;line-height:1.3;font-weight:600;letter-spacing:-.01em}
|
|
9845
|
+
.meta{color:var(--fg-3);font-size:12.5px;font-variant-numeric:tabular-nums}
|
|
9595
9846
|
.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
|
-
|
|
9847
|
+
.meta.path{font-family:var(--mono);font-size:11.5px;margin-top:2px;word-break:break-all}
|
|
9848
|
+
.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}
|
|
9849
|
+
main{min-width:0}
|
|
9850
|
+
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}
|
|
9851
|
+
.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}
|
|
9852
|
+
.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}
|
|
9853
|
+
.badge{font-weight:600;color:var(--fg-2);font-size:12px}
|
|
9854
|
+
.step.user_prompt{border-left:3px solid var(--intent-line)}
|
|
9855
|
+
.step.error_recovery{border-color:color-mix(in srgb,var(--fail-line) 45%,transparent);border-left:3px solid var(--fail-line)}
|
|
9856
|
+
.badge.error_recovery,.sub.err{color:var(--fail-fg)}
|
|
9857
|
+
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)}
|
|
9858
|
+
.step.user_prompt>pre,.step.assistant_text>pre{font:14px/1.6 var(--sans)}
|
|
9859
|
+
.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}
|
|
9860
|
+
.sub+pre,.sub+dl{background:var(--inset);margin:8px 14px 12px;padding:10px 12px;border-radius:var(--radius-sm)}
|
|
9861
|
+
dl{margin:0;padding:4px 14px 12px}
|
|
9862
|
+
dt{color:var(--fg-3);font-size:11.5px;margin-top:8px}
|
|
9863
|
+
dd{margin:2px 0 0;white-space:pre-wrap;word-break:break-word;font-size:12.5px}
|
|
9864
|
+
#trace-meta dd{font-family:var(--sans);font-size:13.5px}
|
|
9865
|
+
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
9866
|
mark.hit .was{display:none}
|
|
9615
|
-
mark.hit.keep{background:
|
|
9867
|
+
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
9868
|
mark.hit.keep .tok{display:none}
|
|
9617
9869
|
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)}
|
|
9870
|
+
aside h2{margin:0 0 4px;font-size:15px;font-weight:600}
|
|
9871
|
+
aside .note{color:var(--fg-3);font-size:12.5px;margin:0 0 12px}
|
|
9872
|
+
aside .note code{font-size:11.5px;background:var(--inset);padding:1px 4px;border-radius:4px}
|
|
9644
9873
|
.tools{display:flex;gap:6px;margin:0 0 10px}
|
|
9645
|
-
.
|
|
9646
|
-
.
|
|
9647
|
-
|
|
9648
|
-
|
|
9874
|
+
.keys{color:var(--fg-3);font-size:12px;margin:0 0 14px}
|
|
9875
|
+
.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}
|
|
9876
|
+
fieldset{border:0;border-top:1px solid var(--line-soft);margin:0;padding:14px 0 12px}
|
|
9877
|
+
legend{float:left;width:100%;padding:0;margin:0 0 6px;color:var(--fg-3);font-size:11px;text-transform:uppercase;letter-spacing:.06em}
|
|
9878
|
+
legend+*{clear:left}
|
|
9879
|
+
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}
|
|
9880
|
+
input.title:focus{outline:none;background:#fff;border-color:var(--fg-3);box-shadow:0 0 0 3px rgba(var(--shade),.05)}
|
|
9881
|
+
label.vis{display:flex;align-items:center;gap:8px;padding:3px 0;cursor:pointer}
|
|
9882
|
+
input[type=radio],input[type=checkbox]{accent-color:var(--fg);margin:0}
|
|
9883
|
+
.warn{color:var(--fail-fg);font-size:12.5px;margin:6px 0 0}
|
|
9884
|
+
.actions{display:flex;gap:8px;padding:14px 0 16px;border-top:1px solid var(--line-soft)}
|
|
9885
|
+
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}
|
|
9886
|
+
button:hover{background:var(--surface)}
|
|
9887
|
+
button:focus-visible{outline:2px solid var(--fg);outline-offset:1px}
|
|
9888
|
+
button.primary{background:var(--fg);border-color:var(--fg);color:#fff}
|
|
9889
|
+
button.primary:hover{background:var(--fg-lift);border-color:var(--fg-lift)}
|
|
9890
|
+
.tools button{padding:5px 10px;font-size:12px}
|
|
9891
|
+
.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)}
|
|
9892
|
+
.row:hover{background:var(--surface)}
|
|
9893
|
+
.row:focus-within{border-color:var(--fg-3)}
|
|
9894
|
+
.row input{margin:3px 0 0}
|
|
9895
|
+
.state{font-size:11.5px;font-weight:600}
|
|
9896
|
+
.s-red{color:var(--fg-3)}
|
|
9897
|
+
.s-keep{display:none;color:var(--hold-fg)}
|
|
9898
|
+
.row input:checked~.state .s-red{display:none}
|
|
9899
|
+
.row input:checked~.state .s-keep{display:inline}
|
|
9900
|
+
.row:has(input:checked){background:var(--hold-bg);border-color:color-mix(in srgb,var(--hold-line) 50%,transparent)}
|
|
9901
|
+
.row .rule{font-size:13px;color:var(--fg)}
|
|
9902
|
+
.row .times{font-weight:600}
|
|
9903
|
+
.row .prev{color:var(--fg-2);font:11.5px/1.5 var(--mono);word-break:break-all}
|
|
9904
|
+
.row .path{color:var(--fg-3);font-size:11px;word-break:break-all}
|
|
9905
|
+
.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)}
|
|
9906
|
+
.done h1{font-size:20px}
|
|
9907
|
+
.done .meta{font-size:13.5px;color:var(--fg-2);margin:10px 0 0}
|
|
9908
|
+
.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}
|
|
9909
|
+
a{color:var(--fg);text-underline-offset:2px}
|
|
9910
|
+
@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
9911
|
`;
|
|
9912
|
+
var STEP_LABEL = {
|
|
9913
|
+
user_prompt: "prompt",
|
|
9914
|
+
assistant_text: "assistant",
|
|
9915
|
+
assistant_thinking: "thinking",
|
|
9916
|
+
tool_call: "tool call",
|
|
9917
|
+
tool_result: "tool result",
|
|
9918
|
+
error_recovery: "failure"
|
|
9919
|
+
};
|
|
9650
9920
|
var SCRIPT = `
|
|
9651
9921
|
(function(){
|
|
9652
9922
|
var boxes = Array.prototype.slice.call(document.querySelectorAll('input[name="keep"]'));
|
|
@@ -9753,14 +10023,14 @@ function renderReviewPage(input) {
|
|
|
9753
10023
|
const base = `steps[${index}]`;
|
|
9754
10024
|
const parts = [];
|
|
9755
10025
|
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>`
|
|
10026
|
+
`<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
10027
|
);
|
|
9758
10028
|
parts.push(`<pre>${marked(`${base}.content`, step.content)}</pre>`);
|
|
9759
10029
|
const tool = step.tool;
|
|
9760
10030
|
if (tool) {
|
|
9761
10031
|
const inputSlots = slots.filter((slot) => slot.fieldPath.startsWith(`${base}.tool.input`));
|
|
9762
10032
|
parts.push(
|
|
9763
|
-
`<div class="sub">tool ${escapeHtml(tool.name)}${tool.isError ? " · errored" : ""}</div>`
|
|
10033
|
+
`<div class="sub${tool.isError ? " err" : ""}">tool ${escapeHtml(tool.name)}${tool.isError ? " · errored" : ""}</div>`
|
|
9764
10034
|
);
|
|
9765
10035
|
if (inputSlots.length > 0) {
|
|
9766
10036
|
parts.push(
|
|
@@ -9780,7 +10050,7 @@ function renderReviewPage(input) {
|
|
|
9780
10050
|
`<div class="sub">diff ${marked(`${base}.diff.filePath`, diff.filePath)}</div><pre>${marked(`${base}.diff.patch`, diff.patch)}</pre>`
|
|
9781
10051
|
);
|
|
9782
10052
|
}
|
|
9783
|
-
return `<section class="step" id="${escapeHtml(base)}">${parts.join("")}</section>`;
|
|
10053
|
+
return `<section class="step ${escapeHtml(step.type)}" id="${escapeHtml(base)}">${parts.join("")}</section>`;
|
|
9784
10054
|
}).join("");
|
|
9785
10055
|
const groups = groupMatches(matches);
|
|
9786
10056
|
const rowsHtml = groups.length === 0 ? `<p class="note">No matches. Nothing will be rewritten.</p>` : groups.map((group) => {
|
|
@@ -9794,15 +10064,16 @@ function renderReviewPage(input) {
|
|
|
9794
10064
|
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
10065
|
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
10066
|
const body = `
|
|
9797
|
-
<header>
|
|
10067
|
+
<header><div class="hin">
|
|
10068
|
+
<div class="bar"><span class="wordmark">flatbread</span><span class="crumb">review before you share</span></div>
|
|
9798
10069
|
<h1>${marked("title", trace.title)}</h1>
|
|
9799
10070
|
<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
|
|
10071
|
+
<b>${escapeHtml(trace.model)}</b> · ${plural2(trace.steps.length, "step")} · ${plural2(toolCalls, "tool call")} ·
|
|
10072
|
+
${trace.stats.filesChanged === null ? `${formatCount(null)} files` : plural2(trace.stats.filesChanged, "file")} changed · ${plural2(trace.stats.errorCount, "error")} ·
|
|
10073
|
+
${Math.round(trace.stats.durationSeconds)}s · <b>${matches.length}</b> redaction match${matches.length === 1 ? "" : "es"}
|
|
9803
10074
|
</div>
|
|
9804
|
-
<div class="meta">${escapeHtml(sessionPath)}</div>
|
|
9805
|
-
</header>
|
|
10075
|
+
<div class="meta path">${escapeHtml(sessionPath)}</div>
|
|
10076
|
+
</div></header>
|
|
9806
10077
|
<form id="review" method="post" action="/confirm">
|
|
9807
10078
|
<div class="wrap">
|
|
9808
10079
|
<main>${metaHtml}${stepsHtml}</main>
|
|
@@ -9835,13 +10106,14 @@ function renderReviewPage(input) {
|
|
|
9835
10106
|
<script>var PUBLIC_WARNING=${JSON.stringify(publicWarning)};${SCRIPT}</script>`;
|
|
9836
10107
|
return page(`Flatbread review - ${trace.title}`, body);
|
|
9837
10108
|
}
|
|
9838
|
-
function renderDonePage(message, outputPath, publishTo) {
|
|
10109
|
+
function renderDonePage(message, outputPath, publishTo, keepsRun = false) {
|
|
9839
10110
|
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
10111
|
return page(
|
|
9841
10112
|
"Flatbread - share draft written",
|
|
9842
10113
|
`<div class="done"><h1>${escapeHtml(message)}</h1>
|
|
9843
10114
|
<code>${escapeHtml(outputPath)}</code>
|
|
9844
10115
|
${network}
|
|
10116
|
+
${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
10117
|
<p class="meta"><strong>Go back to your terminal now.</strong> The manage token for this
|
|
9846
10118
|
run is printed there, once, at the very bottom. It is the only way to open the run
|
|
9847
10119
|
while it is private, and only its hash is stored, so nothing can print it again.</p>
|
|
@@ -9882,12 +10154,11 @@ function parseDecisionBody(body) {
|
|
|
9882
10154
|
};
|
|
9883
10155
|
}
|
|
9884
10156
|
async function listenOnFirstFreePort(server, ports) {
|
|
9885
|
-
for (const port of ports) {
|
|
10157
|
+
for (const port of [...ports, 0]) {
|
|
9886
10158
|
const ok = await new Promise((resolve) => {
|
|
9887
|
-
const onError = (
|
|
10159
|
+
const onError = () => {
|
|
9888
10160
|
server.removeListener("listening", onListening);
|
|
9889
|
-
|
|
9890
|
-
else resolve(false);
|
|
10161
|
+
resolve(false);
|
|
9891
10162
|
};
|
|
9892
10163
|
const onListening = () => {
|
|
9893
10164
|
server.removeListener("error", onError);
|
|
@@ -9966,7 +10237,7 @@ async function startReviewServer(options) {
|
|
|
9966
10237
|
...changed ? { title } : {}
|
|
9967
10238
|
};
|
|
9968
10239
|
const message = options.onConfirm ? await options.onConfirm(confirmed) : "";
|
|
9969
|
-
send(200, renderDonePage("Share draft written", message, options.publishTo));
|
|
10240
|
+
send(200, renderDonePage("Share draft written", message, options.publishTo, options.keepsRun === true));
|
|
9970
10241
|
finish(confirmed);
|
|
9971
10242
|
} catch (error) {
|
|
9972
10243
|
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 +10391,7 @@ function reportPersist(outcome, options, log) {
|
|
|
10120
10391
|
log(` slug ${outcome.slug}`);
|
|
10121
10392
|
log(` visibility ${outcome.record.visibility}`);
|
|
10122
10393
|
if (outcome.recordPath) log(` record ${outcome.recordPath}`);
|
|
10394
|
+
log(` again flatbread pull ${outcome.slug} (the next share replaces ${path15.basename(options.outputPath)}, not this record)`);
|
|
10123
10395
|
log("");
|
|
10124
10396
|
log(`open ${outcome.publish?.ok ? outcome.publish.url : `${base}/r/${outcome.slug}`}`);
|
|
10125
10397
|
log("");
|
|
@@ -10228,6 +10500,7 @@ async function runShare(options) {
|
|
|
10228
10500
|
sessionPath: target,
|
|
10229
10501
|
draftPath: options.outputPath,
|
|
10230
10502
|
titleDraft: previewTitle,
|
|
10503
|
+
keepsRun: options.persist !== false && (options.storeDir ?? resolveStoreDir()) !== null,
|
|
10231
10504
|
...isPublisher(reviewPublisher) ? { publishTo: reviewPublisher.description } : {},
|
|
10232
10505
|
...options.ports ? { ports: options.ports } : {},
|
|
10233
10506
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
@@ -10241,9 +10514,9 @@ async function runShare(options) {
|
|
|
10241
10514
|
});
|
|
10242
10515
|
if (!session) {
|
|
10243
10516
|
console.error(
|
|
10244
|
-
`could not bind a review port (tried ${(options.ports ??
|
|
10517
|
+
`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
10518
|
);
|
|
10246
|
-
console.error("
|
|
10519
|
+
console.error("Loopback itself refused, so this machine needs a look; --no-review redacts everything without the page.");
|
|
10247
10520
|
return { exitCode: 1, status: "port-unavailable" };
|
|
10248
10521
|
}
|
|
10249
10522
|
log(` review at ${session.url}`);
|
|
@@ -10661,7 +10934,7 @@ function logCapture(dir, line, now = (/* @__PURE__ */ new Date()).toISOString())
|
|
|
10661
10934
|
|
|
10662
10935
|
// src/mcp.ts
|
|
10663
10936
|
var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
10664
|
-
var MCP_SERVER_VERSION = "0.
|
|
10937
|
+
var MCP_SERVER_VERSION = "0.3.0";
|
|
10665
10938
|
function defaultMcpDeps(storeDir2, bucketDir2) {
|
|
10666
10939
|
return {
|
|
10667
10940
|
storeDir: storeDir2,
|
|
@@ -10751,6 +11024,10 @@ async function searchEverywhere(query, deps, options = {}) {
|
|
|
10751
11024
|
if (local.unreadable > 0) out.push(`(${local.unreadable} local record file${local.unreadable === 1 ? "" : "s"} could not be read)`);
|
|
10752
11025
|
}
|
|
10753
11026
|
const seen = new Set(localHits.map((hit) => hit.slug));
|
|
11027
|
+
for (const record of local.records) {
|
|
11028
|
+
const { harness, sessionId } = record.trace.source;
|
|
11029
|
+
if (sessionId.length > 0 && localHits.some((hit) => hit.slug === record.slug)) seen.add(bucketSlug(harness, sessionId));
|
|
11030
|
+
}
|
|
10754
11031
|
if (deps.bucketDir !== null) {
|
|
10755
11032
|
const bucket = readBucket(deps.bucketDir);
|
|
10756
11033
|
const captured = bucket.rows.filter((row) => !seen.has(row.summary.slug) && matchesQuery(row.summary, query));
|
|
@@ -10784,18 +11061,24 @@ async function searchTool(args, deps) {
|
|
|
10784
11061
|
const { lines: lines2 } = await searchEverywhere(query, deps);
|
|
10785
11062
|
return text([...lines2, "", "Pass a run ID to flatbread_get_capsule for what that run learned."].join("\n"));
|
|
10786
11063
|
}
|
|
11064
|
+
async function readCapsule(ref, deps) {
|
|
11065
|
+
if (ref.site === void 0) {
|
|
11066
|
+
const stored = localRecords(deps).records.find((record) => record.slug === ref.slug);
|
|
11067
|
+
if (stored !== void 0) return { ok: true, capsule: buildCapsule(stored.trace), from: "store" };
|
|
11068
|
+
const captured = deps.bucketDir !== null ? readBucketRecord(deps.bucketDir, ref.slug) : null;
|
|
11069
|
+
if (captured !== null) return { ok: true, capsule: buildCapsule(captured.trace), from: "bucket" };
|
|
11070
|
+
}
|
|
11071
|
+
const remote = await deps.capsule(ref);
|
|
11072
|
+
return remote.ok ? { ok: true, capsule: remote.capsule, from: "site" } : remote;
|
|
11073
|
+
}
|
|
10787
11074
|
async function capsuleTool(args, deps) {
|
|
10788
11075
|
const raw = args["slug"];
|
|
10789
11076
|
if (typeof raw !== "string") return text("slug must be a run ID or a /r/<id> link", true);
|
|
10790
11077
|
const ref = parseRunRef(raw);
|
|
10791
11078
|
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);
|
|
11079
|
+
const read2 = await readCapsule(ref, deps);
|
|
11080
|
+
if (read2.ok) return text(read2.capsule);
|
|
11081
|
+
return text(describeReadFailure(read2, `capsule ${ref.slug}`), true);
|
|
10799
11082
|
}
|
|
10800
11083
|
async function handleMcpMessage(message, deps) {
|
|
10801
11084
|
if (typeof message !== "object" || message === null || Array.isArray(message)) {
|
|
@@ -10844,7 +11127,7 @@ async function handleMcpMessage(message, deps) {
|
|
|
10844
11127
|
}
|
|
10845
11128
|
async function runMcpServer(deps, io) {
|
|
10846
11129
|
const input = io?.input ?? process.stdin;
|
|
10847
|
-
const
|
|
11130
|
+
const write2 = io?.write ?? ((line) => process.stdout.write(line));
|
|
10848
11131
|
const log = io?.log ?? ((line) => process.stderr.write(`${line}
|
|
10849
11132
|
`));
|
|
10850
11133
|
log(`flatbread mcp ${MCP_SERVER_VERSION} on stdio${deps.storeDir === null ? " (no local store)" : ` (local store ${deps.storeDir})`}`);
|
|
@@ -10857,12 +11140,12 @@ async function runMcpServer(deps, io) {
|
|
|
10857
11140
|
try {
|
|
10858
11141
|
message = JSON.parse(line);
|
|
10859
11142
|
} catch {
|
|
10860
|
-
|
|
11143
|
+
write2(`${JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } })}
|
|
10861
11144
|
`);
|
|
10862
11145
|
return;
|
|
10863
11146
|
}
|
|
10864
11147
|
const reply = await handleMcpMessage(message, deps);
|
|
10865
|
-
if (reply !== null)
|
|
11148
|
+
if (reply !== null) write2(`${JSON.stringify(reply)}
|
|
10866
11149
|
`);
|
|
10867
11150
|
});
|
|
10868
11151
|
}
|
|
@@ -10870,9 +11153,134 @@ async function runMcpServer(deps, io) {
|
|
|
10870
11153
|
return 0;
|
|
10871
11154
|
}
|
|
10872
11155
|
|
|
10873
|
-
// src/
|
|
11156
|
+
// src/claude-mcp.ts
|
|
10874
11157
|
import fs19 from "node:fs";
|
|
11158
|
+
import os7 from "node:os";
|
|
10875
11159
|
import path18 from "node:path";
|
|
11160
|
+
var MCP_SERVER_NAME = "flatbread";
|
|
11161
|
+
function claudeUserConfigPath(env = process.env) {
|
|
11162
|
+
const dir = env["CLAUDE_CONFIG_DIR"]?.trim();
|
|
11163
|
+
return path18.join(dir ? dir : os7.homedir(), ".claude.json");
|
|
11164
|
+
}
|
|
11165
|
+
function mcpEntryFor(execPath, entryPath) {
|
|
11166
|
+
return { type: "stdio", command: execPath, args: [entryPath, "mcp"] };
|
|
11167
|
+
}
|
|
11168
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11169
|
+
function runsFlatbreadMcp(entry) {
|
|
11170
|
+
if (!isObject(entry) || !Array.isArray(entry["args"])) return false;
|
|
11171
|
+
const words = [entry["command"], ...entry["args"]].filter((word) => typeof word === "string");
|
|
11172
|
+
return words[words.length - 1] === "mcp" && words.some((word) => /flatbread/i.test(word));
|
|
11173
|
+
}
|
|
11174
|
+
var BOM = "\uFEFF";
|
|
11175
|
+
function readConfig(file) {
|
|
11176
|
+
let raw;
|
|
11177
|
+
try {
|
|
11178
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
11179
|
+
} catch (error) {
|
|
11180
|
+
if (error.code === "ENOENT") return { root: {}, raw: null };
|
|
11181
|
+
return { problem: error.message };
|
|
11182
|
+
}
|
|
11183
|
+
const text2 = raw.startsWith(BOM) ? raw.slice(1) : raw;
|
|
11184
|
+
if (text2.trim() === "") return { root: {}, raw };
|
|
11185
|
+
let parsed;
|
|
11186
|
+
try {
|
|
11187
|
+
parsed = JSON.parse(text2);
|
|
11188
|
+
} catch (error) {
|
|
11189
|
+
return { problem: `not JSON (${error.message})` };
|
|
11190
|
+
}
|
|
11191
|
+
if (!isObject(parsed)) return { problem: "not a JSON object" };
|
|
11192
|
+
if (parsed["mcpServers"] !== void 0 && !isObject(parsed["mcpServers"])) return { problem: "its mcpServers is not an object" };
|
|
11193
|
+
return { root: parsed, raw };
|
|
11194
|
+
}
|
|
11195
|
+
function serializeLike(raw, root) {
|
|
11196
|
+
let body = JSON.stringify(root, null, 2);
|
|
11197
|
+
if (raw === null || raw.trimEnd() !== raw || raw.trim() === "") body += "\n";
|
|
11198
|
+
if (raw !== null && raw.includes("\r\n")) body = body.replace(/\n/g, "\r\n");
|
|
11199
|
+
if (raw !== null && raw.startsWith(BOM)) body = BOM + body;
|
|
11200
|
+
return body;
|
|
11201
|
+
}
|
|
11202
|
+
function write(file, read2, next) {
|
|
11203
|
+
if (read2.raw !== null) {
|
|
11204
|
+
try {
|
|
11205
|
+
fs19.accessSync(file, fs19.constants.W_OK);
|
|
11206
|
+
} catch {
|
|
11207
|
+
return `${file} is read-only, so it was left as it was`;
|
|
11208
|
+
}
|
|
11209
|
+
if (fs19.readFileSync(file, "utf8") !== read2.raw) return `${file} changed while setup ran (Claude Code writes it); run setup again`;
|
|
11210
|
+
const backup = `${file}.flatbread.bak`;
|
|
11211
|
+
if (!fs19.existsSync(backup)) fs19.copyFileSync(file, backup);
|
|
11212
|
+
}
|
|
11213
|
+
const mode = read2.raw !== null ? fs19.statSync(file).mode & 511 : 384;
|
|
11214
|
+
fs19.mkdirSync(path18.dirname(file), { recursive: true });
|
|
11215
|
+
const temp = `${file}.flatbread-${process.pid}.tmp`;
|
|
11216
|
+
fs19.writeFileSync(temp, next, { encoding: "utf8", mode });
|
|
11217
|
+
fs19.renameSync(temp, file);
|
|
11218
|
+
return null;
|
|
11219
|
+
}
|
|
11220
|
+
function registerClaudeMcp(options) {
|
|
11221
|
+
const { file, log } = options;
|
|
11222
|
+
const read2 = readConfig(file);
|
|
11223
|
+
if ("problem" in read2) {
|
|
11224
|
+
log(` mcp refused: ${file}: ${read2.problem}`);
|
|
11225
|
+
log(" Claude Code's own state lives there, so a file we cannot read is not one we rewrite.");
|
|
11226
|
+
if (options.uninstall !== true) log(` by hand: claude mcp add --scope user ${MCP_SERVER_NAME} -- "${options.execPath}" "${options.entryPath}" mcp`);
|
|
11227
|
+
return 1;
|
|
11228
|
+
}
|
|
11229
|
+
const servers = isObject(read2.root["mcpServers"]) ? read2.root["mcpServers"] : void 0;
|
|
11230
|
+
const current = servers?.[MCP_SERVER_NAME];
|
|
11231
|
+
if (options.uninstall === true) {
|
|
11232
|
+
if (current === void 0) {
|
|
11233
|
+
log(` mcp no ${MCP_SERVER_NAME} server in ${file}`);
|
|
11234
|
+
return 0;
|
|
11235
|
+
}
|
|
11236
|
+
if (!runsFlatbreadMcp(current)) {
|
|
11237
|
+
log(` mcp ${file}: the ${MCP_SERVER_NAME} server there does not run flatbread, left alone`);
|
|
11238
|
+
return 0;
|
|
11239
|
+
}
|
|
11240
|
+
if (options.dryRun === true) {
|
|
11241
|
+
log(` mcp ${file}: would remove the ${MCP_SERVER_NAME} server`);
|
|
11242
|
+
return 0;
|
|
11243
|
+
}
|
|
11244
|
+
const rest = { ...servers };
|
|
11245
|
+
delete rest[MCP_SERVER_NAME];
|
|
11246
|
+
const problem2 = write(file, read2, serializeLike(read2.raw, { ...read2.root, mcpServers: rest }));
|
|
11247
|
+
if (problem2 !== null) {
|
|
11248
|
+
log(` mcp refused: ${problem2}`);
|
|
11249
|
+
return 1;
|
|
11250
|
+
}
|
|
11251
|
+
log(` mcp ${file}: removed the ${MCP_SERVER_NAME} server`);
|
|
11252
|
+
const backup = `${file}.flatbread.bak`;
|
|
11253
|
+
if (fs19.existsSync(backup)) log(` ${backup} is the copy setup took before its first write; delete it when you like`);
|
|
11254
|
+
return 0;
|
|
11255
|
+
}
|
|
11256
|
+
const entry = mcpEntryFor(options.execPath, options.entryPath);
|
|
11257
|
+
if (current !== void 0 && !runsFlatbreadMcp(current)) {
|
|
11258
|
+
log(` mcp refused: ${file} already has a server named ${MCP_SERVER_NAME} that does not run flatbread, left alone`);
|
|
11259
|
+
return 1;
|
|
11260
|
+
}
|
|
11261
|
+
if (JSON.stringify(current) === JSON.stringify(entry)) {
|
|
11262
|
+
log(` mcp ${file}: registered for every project, unchanged`);
|
|
11263
|
+
return 0;
|
|
11264
|
+
}
|
|
11265
|
+
const verb = current === void 0 ? "register" : "update";
|
|
11266
|
+
if (options.dryRun === true) {
|
|
11267
|
+
log(` mcp ${file}: would ${verb} "${MCP_SERVER_NAME}": ${JSON.stringify(entry)}`);
|
|
11268
|
+
return 0;
|
|
11269
|
+
}
|
|
11270
|
+
const root = { ...read2.root, mcpServers: { ...servers ?? {}, [MCP_SERVER_NAME]: entry } };
|
|
11271
|
+
const problem = write(file, read2, serializeLike(read2.raw, root));
|
|
11272
|
+
if (problem !== null) {
|
|
11273
|
+
log(` mcp refused: ${problem}`);
|
|
11274
|
+
return 1;
|
|
11275
|
+
}
|
|
11276
|
+
log(` mcp ${file}: ${verb === "register" ? "registered" : "updated"} for every project (user scope)`);
|
|
11277
|
+
log(" restart Claude Code (or reload the VS Code window) so it starts the server");
|
|
11278
|
+
return 0;
|
|
11279
|
+
}
|
|
11280
|
+
|
|
11281
|
+
// src/git-hook.ts
|
|
11282
|
+
import fs20 from "node:fs";
|
|
11283
|
+
import path19 from "node:path";
|
|
10876
11284
|
var POST_COMMIT_MARKER = "# flatbread post-commit hook - remove with: flatbread setup --uninstall";
|
|
10877
11285
|
var CHAINED_HOOK = "post-commit.pre-flatbread";
|
|
10878
11286
|
var sq = (value) => `'${value.replace(/\\/g, "/").replace(/'/g, "'\\''")}'`;
|
|
@@ -10916,33 +11324,33 @@ function hooksDirFor(cwd, options = {}) {
|
|
|
10916
11324
|
}
|
|
10917
11325
|
const common = git(top, ["rev-parse", "--git-common-dir"], options)?.trim();
|
|
10918
11326
|
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:
|
|
11327
|
+
return { ok: true, repo: path19.resolve(top), dir: path19.resolve(top, common, "hooks"), ...machineHooksPath !== void 0 ? { machineHooksPath } : {} };
|
|
10920
11328
|
}
|
|
10921
11329
|
var read = (file) => {
|
|
10922
11330
|
try {
|
|
10923
|
-
return
|
|
11331
|
+
return fs20.readFileSync(file, "utf8");
|
|
10924
11332
|
} catch {
|
|
10925
11333
|
return null;
|
|
10926
11334
|
}
|
|
10927
11335
|
};
|
|
10928
11336
|
function installGitHook(dir, script, options = {}) {
|
|
10929
|
-
const hook =
|
|
10930
|
-
const chained =
|
|
11337
|
+
const hook = path19.join(dir, "post-commit");
|
|
11338
|
+
const chained = path19.join(dir, CHAINED_HOOK);
|
|
10931
11339
|
const current = read(hook);
|
|
10932
|
-
const
|
|
11340
|
+
const write2 = (lines2) => {
|
|
10933
11341
|
if (options.dryRun === true) return { code: 0, lines: [...lines2, "--dry-run: nothing was written"] };
|
|
10934
|
-
|
|
10935
|
-
if (current !== null && !isOurs(current))
|
|
10936
|
-
|
|
10937
|
-
|
|
11342
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
11343
|
+
if (current !== null && !isOurs(current)) fs20.renameSync(hook, chained);
|
|
11344
|
+
fs20.writeFileSync(hook, script, { encoding: "utf8", mode: 493 });
|
|
11345
|
+
fs20.chmodSync(hook, 493);
|
|
10938
11346
|
return { code: 0, lines: lines2 };
|
|
10939
11347
|
};
|
|
10940
|
-
if (current === null) return
|
|
11348
|
+
if (current === null) return write2([`post-commit ${hook} (new)`]);
|
|
10941
11349
|
if (isOurs(current)) {
|
|
10942
11350
|
if (current === script) return { code: 0, lines: [`post-commit ${hook}`, "already installed, unchanged"] };
|
|
10943
|
-
return
|
|
11351
|
+
return write2([`post-commit ${hook}`, "updated the flatbread hook"]);
|
|
10944
11352
|
}
|
|
10945
|
-
if (
|
|
11353
|
+
if (fs20.existsSync(chained)) {
|
|
10946
11354
|
return {
|
|
10947
11355
|
code: 1,
|
|
10948
11356
|
lines: [
|
|
@@ -10952,16 +11360,16 @@ function installGitHook(dir, script, options = {}) {
|
|
|
10952
11360
|
]
|
|
10953
11361
|
};
|
|
10954
11362
|
}
|
|
10955
|
-
return
|
|
11363
|
+
return write2([
|
|
10956
11364
|
`post-commit ${hook}`,
|
|
10957
11365
|
`your post-commit was kept as ${CHAINED_HOOK} and runs first, unchanged, with its exit status`
|
|
10958
11366
|
]);
|
|
10959
11367
|
}
|
|
10960
11368
|
function uninstallGitHook(dir, options = {}) {
|
|
10961
|
-
const hook =
|
|
10962
|
-
const chained =
|
|
11369
|
+
const hook = path19.join(dir, "post-commit");
|
|
11370
|
+
const chained = path19.join(dir, CHAINED_HOOK);
|
|
10963
11371
|
const current = read(hook);
|
|
10964
|
-
const hasChained =
|
|
11372
|
+
const hasChained = fs20.existsSync(chained);
|
|
10965
11373
|
const done = (lines2, act) => {
|
|
10966
11374
|
if (options.dryRun === true) return { code: 0, lines: [...lines2, "--dry-run: nothing was written"] };
|
|
10967
11375
|
act();
|
|
@@ -10979,17 +11387,17 @@ function uninstallGitHook(dir, options = {}) {
|
|
|
10979
11387
|
if (current === null && !hasChained) return { code: 0, lines: [`no flatbread post-commit in ${dir}`] };
|
|
10980
11388
|
if (hasChained) {
|
|
10981
11389
|
return done([`post-commit ${hook}`, `restored your post-commit from ${CHAINED_HOOK}`], () => {
|
|
10982
|
-
if (current !== null)
|
|
10983
|
-
|
|
11390
|
+
if (current !== null) fs20.rmSync(hook);
|
|
11391
|
+
fs20.renameSync(chained, hook);
|
|
10984
11392
|
});
|
|
10985
11393
|
}
|
|
10986
|
-
return done([`post-commit ${hook}`, "removed the flatbread hook"], () =>
|
|
11394
|
+
return done([`post-commit ${hook}`, "removed the flatbread hook"], () => fs20.rmSync(hook));
|
|
10987
11395
|
}
|
|
10988
11396
|
|
|
10989
11397
|
// src/hook.ts
|
|
10990
11398
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
|
|
10991
|
-
import
|
|
10992
|
-
import
|
|
11399
|
+
import fs21 from "node:fs";
|
|
11400
|
+
import path20 from "node:path";
|
|
10993
11401
|
var HOOK_EVENTS = ["Stop", "SessionEnd"];
|
|
10994
11402
|
var OFFER_AFTER_STOPS = 2;
|
|
10995
11403
|
var MAX_TRACKED_SESSIONS = 50;
|
|
@@ -10999,14 +11407,14 @@ function emptyHookState() {
|
|
|
10999
11407
|
return { version: HOOK_STATE_VERSION, sessions: {} };
|
|
11000
11408
|
}
|
|
11001
11409
|
function hookStatePath(env = process.env) {
|
|
11002
|
-
return
|
|
11410
|
+
return path20.join(flatbreadHome(env), "hook-state.json");
|
|
11003
11411
|
}
|
|
11004
11412
|
function readHookState(env = process.env) {
|
|
11005
11413
|
const file = hookStatePath(env);
|
|
11006
|
-
if (!
|
|
11414
|
+
if (!fs21.existsSync(file)) return { ok: true, state: emptyHookState(), present: false };
|
|
11007
11415
|
let raw;
|
|
11008
11416
|
try {
|
|
11009
|
-
raw =
|
|
11417
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
11010
11418
|
} catch (error) {
|
|
11011
11419
|
return { ok: false, reason: "unreadable", detail: error.message };
|
|
11012
11420
|
}
|
|
@@ -11049,9 +11457,9 @@ function pruneHookState(state, keep = MAX_TRACKED_SESSIONS) {
|
|
|
11049
11457
|
}
|
|
11050
11458
|
function writeHookState(state, env = process.env) {
|
|
11051
11459
|
const dir = flatbreadHome(env);
|
|
11052
|
-
|
|
11460
|
+
fs21.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
11053
11461
|
const file = hookStatePath(env);
|
|
11054
|
-
|
|
11462
|
+
fs21.writeFileSync(file, JSON.stringify(pruneHookState(state), null, 2) + "\n", {
|
|
11055
11463
|
encoding: "utf8",
|
|
11056
11464
|
mode: 384
|
|
11057
11465
|
});
|
|
@@ -11197,7 +11605,7 @@ async function runHookNotify(io) {
|
|
|
11197
11605
|
const stdout = io.stdout ?? ((line) => process.stdout.write(line + "\n"));
|
|
11198
11606
|
const stderr = io.stderr ?? ((line) => process.stderr.write(line + "\n"));
|
|
11199
11607
|
const now = io.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11200
|
-
const exists = io.exists ?? ((filePath) =>
|
|
11608
|
+
const exists = io.exists ?? ((filePath) => fs21.existsSync(filePath));
|
|
11201
11609
|
const execPath = io.execPath ?? process.execPath;
|
|
11202
11610
|
const entryPath = io.entryPath ?? process.argv[1] ?? "";
|
|
11203
11611
|
try {
|
|
@@ -11354,10 +11762,10 @@ function removeHookConfig(settings) {
|
|
|
11354
11762
|
return { settings: root, removed };
|
|
11355
11763
|
}
|
|
11356
11764
|
function readSettingsFile(file) {
|
|
11357
|
-
if (!
|
|
11765
|
+
if (!fs21.existsSync(file)) return { present: false, file };
|
|
11358
11766
|
let raw;
|
|
11359
11767
|
try {
|
|
11360
|
-
raw =
|
|
11768
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
11361
11769
|
} catch (error) {
|
|
11362
11770
|
return { present: true, file, unreadable: error.message };
|
|
11363
11771
|
}
|
|
@@ -11369,10 +11777,10 @@ function readSettingsFile(file) {
|
|
|
11369
11777
|
}
|
|
11370
11778
|
}
|
|
11371
11779
|
function writeSettingsFile(file, settings) {
|
|
11372
|
-
|
|
11780
|
+
fs21.mkdirSync(path20.dirname(file), { recursive: true });
|
|
11373
11781
|
const temp = `${file}.flatbread-${process.pid}.tmp`;
|
|
11374
|
-
|
|
11375
|
-
|
|
11782
|
+
fs21.writeFileSync(temp, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
11783
|
+
fs21.renameSync(temp, file);
|
|
11376
11784
|
}
|
|
11377
11785
|
function handlersFor(execPath, entryPath, capture = false) {
|
|
11378
11786
|
return {
|
|
@@ -11530,8 +11938,8 @@ function runHookUninstall(options = {}) {
|
|
|
11530
11938
|
function settingsCandidates(env, cwd) {
|
|
11531
11939
|
return [
|
|
11532
11940
|
{ label: "user", file: claudeSettingsPath(env) },
|
|
11533
|
-
{ label: "project", file:
|
|
11534
|
-
{ label: "local", file:
|
|
11941
|
+
{ label: "project", file: path20.join(cwd, ".claude", "settings.json") },
|
|
11942
|
+
{ label: "local", file: path20.join(cwd, ".claude", "settings.local.json") }
|
|
11535
11943
|
];
|
|
11536
11944
|
}
|
|
11537
11945
|
function installedEventsIn(parsed, exists) {
|
|
@@ -11561,7 +11969,7 @@ function runHookStatus(options = {}) {
|
|
|
11561
11969
|
const env = options.env ?? process.env;
|
|
11562
11970
|
const log = options.log ?? ((line) => console.log(line));
|
|
11563
11971
|
const cwd = options.cwd ?? process.cwd();
|
|
11564
|
-
const exists = options.exists ?? ((filePath) =>
|
|
11972
|
+
const exists = options.exists ?? ((filePath) => fs21.existsSync(filePath));
|
|
11565
11973
|
const candidates = options.settingsPath === void 0 ? settingsCandidates(env, cwd) : [{ label: "settings", file: options.settingsPath }];
|
|
11566
11974
|
log("");
|
|
11567
11975
|
log("flatbread hook");
|
|
@@ -11711,6 +12119,22 @@ function runSetup(options = {}) {
|
|
|
11711
12119
|
...options.spawn !== void 0 ? { spawn: options.spawn } : {},
|
|
11712
12120
|
...settings
|
|
11713
12121
|
});
|
|
12122
|
+
let mcp = 0;
|
|
12123
|
+
if (options.noMcp === true) {
|
|
12124
|
+
log(" --no-mcp: Claude Code's MCP servers were not touched");
|
|
12125
|
+
} else if (claude === 0 || options.uninstall === true) {
|
|
12126
|
+
mcp = registerClaudeMcp({
|
|
12127
|
+
file: options.claudeConfigPath ?? claudeUserConfigPath(env),
|
|
12128
|
+
execPath,
|
|
12129
|
+
entryPath,
|
|
12130
|
+
uninstall: options.uninstall === true,
|
|
12131
|
+
dryRun,
|
|
12132
|
+
log
|
|
12133
|
+
});
|
|
12134
|
+
} else {
|
|
12135
|
+
log(" mcp not registered, because the hook step above was refused");
|
|
12136
|
+
}
|
|
12137
|
+
log("");
|
|
11714
12138
|
let git2 = 0;
|
|
11715
12139
|
log("git");
|
|
11716
12140
|
if (options.noGit === true) {
|
|
@@ -11746,11 +12170,11 @@ function runSetup(options = {}) {
|
|
|
11746
12170
|
log(" share one with: flatbread share --file <its session>, which opens the review");
|
|
11747
12171
|
}
|
|
11748
12172
|
log("");
|
|
11749
|
-
return claude !== 0 || git2 !== 0 ? 1 : 0;
|
|
12173
|
+
return claude !== 0 || mcp !== 0 || git2 !== 0 ? 1 : 0;
|
|
11750
12174
|
}
|
|
11751
12175
|
|
|
11752
12176
|
// src/login-server.ts
|
|
11753
|
-
import
|
|
12177
|
+
import crypto3 from "node:crypto";
|
|
11754
12178
|
import http2 from "node:http";
|
|
11755
12179
|
function callbackOrigin(port) {
|
|
11756
12180
|
return `http://127.0.0.1:${port}`;
|
|
@@ -11825,7 +12249,7 @@ function parseDelivery(raw, expectedState, nowSeconds) {
|
|
|
11825
12249
|
}
|
|
11826
12250
|
const given = Buffer.from(state, "utf8");
|
|
11827
12251
|
const wanted = Buffer.from(expectedState, "utf8");
|
|
11828
|
-
if (given.length !== wanted.length || !
|
|
12252
|
+
if (given.length !== wanted.length || !crypto3.timingSafeEqual(given, wanted)) {
|
|
11829
12253
|
return { ok: false, problem: "the callback state did not match" };
|
|
11830
12254
|
}
|
|
11831
12255
|
const sessionRaw = body["session"];
|
|
@@ -11873,7 +12297,7 @@ async function readBody2(req, limitBytes = 64 * 1024) {
|
|
|
11873
12297
|
async function startLoginServer(options = {}) {
|
|
11874
12298
|
const ports = options.ports ?? DEFAULT_LOGIN_PORTS;
|
|
11875
12299
|
const now = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
11876
|
-
const state = options.state ??
|
|
12300
|
+
const state = options.state ?? crypto3.randomBytes(32).toString("hex");
|
|
11877
12301
|
let settle;
|
|
11878
12302
|
const delivery = new Promise((resolve) => {
|
|
11879
12303
|
settle = resolve;
|
|
@@ -11959,13 +12383,13 @@ async function bindFirst(server, ports) {
|
|
|
11959
12383
|
}
|
|
11960
12384
|
|
|
11961
12385
|
// 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 =
|
|
12386
|
+
var PACKAGE_ROOT = path21.resolve(path21.dirname(fileURLToPath2(import.meta.url)), "..");
|
|
12387
|
+
var SOURCE_CHECKOUT = fs22.existsSync(path21.join(PACKAGE_ROOT, "src", "index.ts"));
|
|
12388
|
+
var TMP_DIR = process.env["FLATBREAD_TMP_DIR"]?.trim() || (SOURCE_CHECKOUT ? path21.join(PACKAGE_ROOT, ".tmp") : path21.join(os8.homedir(), ".flatbread"));
|
|
12389
|
+
var OUTPUT_PATH = path21.join(TMP_DIR, "last-parse.json");
|
|
12390
|
+
var SHARE_OUTPUT_PATH = path21.join(TMP_DIR, "last-share.json");
|
|
11967
12391
|
function storeDir() {
|
|
11968
|
-
return resolveStoreDir() ?? (SOURCE_CHECKOUT ? null :
|
|
12392
|
+
return resolveStoreDir() ?? (SOURCE_CHECKOUT ? null : path21.join(TMP_DIR, DEFAULT_STORE_DIRNAME));
|
|
11969
12393
|
}
|
|
11970
12394
|
var USAGE = `flatbread - registry of AI agent runs
|
|
11971
12395
|
|
|
@@ -11975,14 +12399,16 @@ Usage:
|
|
|
11975
12399
|
flatbread share Review + sanitize the newest session
|
|
11976
12400
|
flatbread share --file <path> Review + sanitize a specific session
|
|
11977
12401
|
flatbread view Write the brief and capsule, print the viewer URL
|
|
11978
|
-
flatbread pull <id|url> Print a run's Context Capsule
|
|
12402
|
+
flatbread pull <id|url> Print a run's Context Capsule: a run this machine shared or
|
|
12403
|
+
captured is read here, anything else from the site
|
|
11979
12404
|
flatbread pull <id|url> --out <file> Write it to a file instead
|
|
11980
12405
|
flatbread mcp MCP server on stdio: flatbread_search_traces and
|
|
11981
12406
|
flatbread_get_capsule, for your coding agent
|
|
11982
12407
|
flatbread search <words> Find runs: this machine's shared and captured ones, then
|
|
11983
12408
|
the site (the same answer the MCP server gives)
|
|
11984
12409
|
flatbread setup Auto-capture: the Claude Code hook, plus a chained git
|
|
11985
|
-
post-commit in this repository
|
|
12410
|
+
post-commit in this repository, and the MCP server for
|
|
12411
|
+
every Claude Code project. Captures stay local
|
|
11986
12412
|
flatbread setup --uninstall Remove exactly what setup added
|
|
11987
12413
|
flatbread capture --file <path> Capture one session into the local bucket (setup's hooks
|
|
11988
12414
|
run this; redacted, private, never uploaded)
|
|
@@ -12024,9 +12450,10 @@ Options for search:
|
|
|
12024
12450
|
|
|
12025
12451
|
Options for setup:
|
|
12026
12452
|
--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
|
|
12453
|
+
--uninstall Remove the handlers, the MCP server and the post-commit;
|
|
12454
|
+
restore a post-commit hook setup had chained
|
|
12029
12455
|
--no-git Leave the repository's hooks alone
|
|
12456
|
+
--no-mcp Leave Claude Code's MCP servers alone
|
|
12030
12457
|
--settings <path> --no-probe As for hook install
|
|
12031
12458
|
|
|
12032
12459
|
Options for capture:
|
|
@@ -12049,14 +12476,16 @@ Options for adapters:
|
|
|
12049
12476
|
Notes:
|
|
12050
12477
|
parse and share record the git branch, HEAD commit and uncommitted file names when the
|
|
12051
12478
|
session ran inside a git repository. Nothing else from git is read.
|
|
12052
|
-
parse --save writes ${
|
|
12053
|
-
Share drafts are written to ${
|
|
12479
|
+
parse --save writes ${path21.relative(process.cwd(), OUTPUT_PATH) || OUTPUT_PATH}
|
|
12480
|
+
Share drafts are written to ${path21.relative(process.cwd(), SHARE_OUTPUT_PATH) || SHARE_OUTPUT_PATH}
|
|
12481
|
+
(the latest only; every shared run is also kept under its ID, for search and pull)
|
|
12054
12482
|
Every parsed run defaults to visibility "private".
|
|
12055
12483
|
Nothing is uploaded unless you pass --publish, or set FLATBREAD_API_URL and
|
|
12056
12484
|
FLATBREAD_API_TOKEN yourself. pull and mcp only read: public runs, your own when you
|
|
12057
12485
|
are signed in, and a private run through its share link. The review UI listens on
|
|
12058
|
-
127.0.0.1 only
|
|
12059
|
-
|
|
12486
|
+
127.0.0.1 only, on 4321-4325 or, when all five are busy, a port the OS picks.
|
|
12487
|
+
setup registers the MCP server in ~/.claude.json, for every project. By hand instead:
|
|
12488
|
+
claude mcp add --scope user flatbread -- flatbread mcp
|
|
12060
12489
|
The viewer is a separate dev server: npm run dev -w @flatbread/web
|
|
12061
12490
|
Captured runs live in ~/.flatbread/bucket (FLATBREAD_HOOK_DISABLED=1 stops every capture).
|
|
12062
12491
|
A capture is not a share: to publish one, run share --file on its session.
|
|
@@ -12085,10 +12514,10 @@ function parseArgv(argv) {
|
|
|
12085
12514
|
} else if (arg === "--out") {
|
|
12086
12515
|
const value = argv[i + 1];
|
|
12087
12516
|
if (value === void 0 || value.startsWith("--")) throw new Error("--out needs a file path");
|
|
12088
|
-
args.out =
|
|
12517
|
+
args.out = path21.resolve(value);
|
|
12089
12518
|
i += 1;
|
|
12090
12519
|
} else if (arg !== void 0 && arg.startsWith("--out=")) {
|
|
12091
|
-
args.out =
|
|
12520
|
+
args.out = path21.resolve(arg.slice("--out=".length));
|
|
12092
12521
|
} else if (arg === "--harness") {
|
|
12093
12522
|
const value = argv[i + 1];
|
|
12094
12523
|
if (value === void 0 || value.startsWith("--")) {
|
|
@@ -12209,12 +12638,12 @@ async function runParse(args) {
|
|
|
12209
12638
|
target = found[0];
|
|
12210
12639
|
console.log(`latest ${harness} session: ${target}`);
|
|
12211
12640
|
} else if (args.file) {
|
|
12212
|
-
target =
|
|
12213
|
-
if (!
|
|
12641
|
+
target = path21.resolve(args.file);
|
|
12642
|
+
if (!fs22.existsSync(target)) {
|
|
12214
12643
|
console.error(`file not found: ${target}`);
|
|
12215
12644
|
return 1;
|
|
12216
12645
|
}
|
|
12217
|
-
if (!
|
|
12646
|
+
if (!fs22.statSync(target).isFile()) {
|
|
12218
12647
|
console.error(`not a file: ${target}`);
|
|
12219
12648
|
return 1;
|
|
12220
12649
|
}
|
|
@@ -12245,8 +12674,8 @@ async function runParse(args) {
|
|
|
12245
12674
|
const trace = withLineage(result.trace, readGitLineage(result.trace.source.cwd));
|
|
12246
12675
|
FlatbreadTraceSchema.parse(trace);
|
|
12247
12676
|
if (args.out !== void 0) {
|
|
12248
|
-
|
|
12249
|
-
|
|
12677
|
+
fs22.mkdirSync(path21.dirname(args.out), { recursive: true });
|
|
12678
|
+
fs22.writeFileSync(args.out, JSON.stringify(trace, null, 2) + "\n", "utf8");
|
|
12250
12679
|
}
|
|
12251
12680
|
report({ ...result, trace }, args.out);
|
|
12252
12681
|
return 0;
|
|
@@ -12310,8 +12739,8 @@ async function runAdapters(args) {
|
|
|
12310
12739
|
}
|
|
12311
12740
|
let input = null;
|
|
12312
12741
|
if (args.file !== void 0) {
|
|
12313
|
-
const resolved =
|
|
12314
|
-
if (!
|
|
12742
|
+
const resolved = path21.resolve(args.file);
|
|
12743
|
+
if (!fs22.existsSync(resolved)) {
|
|
12315
12744
|
console.error(`file not found: ${resolved}`);
|
|
12316
12745
|
return 1;
|
|
12317
12746
|
}
|
|
@@ -12637,7 +13066,7 @@ async function prompt(label) {
|
|
|
12637
13066
|
});
|
|
12638
13067
|
}
|
|
12639
13068
|
var HOOK_SUBCOMMANDS = ["install", "status", "uninstall", "notify"];
|
|
12640
|
-
var CLI_ENTRY =
|
|
13069
|
+
var CLI_ENTRY = fileURLToPath2(import.meta.url);
|
|
12641
13070
|
function parseHookArgv(argv) {
|
|
12642
13071
|
const [sub, ...rest] = argv;
|
|
12643
13072
|
if (sub === void 0) {
|
|
@@ -12730,13 +13159,13 @@ function parsePullArgv(argv) {
|
|
|
12730
13159
|
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
13160
|
return { ref, ...out !== void 0 ? { out } : {} };
|
|
12732
13161
|
}
|
|
12733
|
-
async function runPull(args) {
|
|
13162
|
+
async function runPull(args, deps = { ...defaultMcpDeps(storeDir(), bucketDir()), capsule: (ref) => fetchCapsule(ref) }) {
|
|
12734
13163
|
const ref = parseRunRef(args.ref);
|
|
12735
13164
|
if (ref === null) {
|
|
12736
13165
|
console.error(`"${args.ref}" is not a run ID or a /r/<id> link`);
|
|
12737
13166
|
return 1;
|
|
12738
13167
|
}
|
|
12739
|
-
const result = await
|
|
13168
|
+
const result = await readCapsule(ref, deps);
|
|
12740
13169
|
if (!result.ok) {
|
|
12741
13170
|
console.error(describeReadFailure(result, `could not pull ${ref.slug}`));
|
|
12742
13171
|
return 1;
|
|
@@ -12745,10 +13174,10 @@ async function runPull(args) {
|
|
|
12745
13174
|
process.stdout.write(result.capsule);
|
|
12746
13175
|
return 0;
|
|
12747
13176
|
}
|
|
12748
|
-
const target =
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
console.error(`wrote ${target}`);
|
|
13177
|
+
const target = path21.resolve(args.out);
|
|
13178
|
+
fs22.mkdirSync(path21.dirname(target), { recursive: true });
|
|
13179
|
+
fs22.writeFileSync(target, result.capsule, "utf8");
|
|
13180
|
+
console.error(`wrote ${target}${result.from === "site" ? "" : ` (from this machine's ${result.from === "store" ? "shared runs" : "captured runs"})`}`);
|
|
12752
13181
|
return 0;
|
|
12753
13182
|
}
|
|
12754
13183
|
function parseSearchArgv(argv) {
|
|
@@ -12860,6 +13289,7 @@ function parseSetupArgv(argv) {
|
|
|
12860
13289
|
dryRun: false,
|
|
12861
13290
|
uninstall: false,
|
|
12862
13291
|
noGit: false,
|
|
13292
|
+
noMcp: false,
|
|
12863
13293
|
noProbe: false
|
|
12864
13294
|
};
|
|
12865
13295
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -12867,6 +13297,7 @@ function parseSetupArgv(argv) {
|
|
|
12867
13297
|
if (arg === "--dry-run") args.dryRun = true;
|
|
12868
13298
|
else if (arg === "--uninstall") args.uninstall = true;
|
|
12869
13299
|
else if (arg === "--no-git") args.noGit = true;
|
|
13300
|
+
else if (arg === "--no-mcp") args.noMcp = true;
|
|
12870
13301
|
else if (arg === "--no-probe") args.noProbe = true;
|
|
12871
13302
|
else if (arg === "--settings") {
|
|
12872
13303
|
const value = argv[i + 1];
|
|
@@ -12913,6 +13344,7 @@ async function main(argv) {
|
|
|
12913
13344
|
dryRun: setup.dryRun,
|
|
12914
13345
|
uninstall: setup.uninstall,
|
|
12915
13346
|
noGit: setup.noGit,
|
|
13347
|
+
noMcp: setup.noMcp,
|
|
12916
13348
|
noProbe: setup.noProbe,
|
|
12917
13349
|
...setup.settings !== void 0 ? { settingsPath: setup.settings } : {}
|
|
12918
13350
|
});
|
|
@@ -12994,12 +13426,12 @@ var invokedDirectly = (() => {
|
|
|
12994
13426
|
if (entry === void 0) return false;
|
|
12995
13427
|
const real = (file) => {
|
|
12996
13428
|
try {
|
|
12997
|
-
return
|
|
13429
|
+
return fs22.realpathSync(file);
|
|
12998
13430
|
} catch {
|
|
12999
|
-
return
|
|
13431
|
+
return path21.resolve(file);
|
|
13000
13432
|
}
|
|
13001
13433
|
};
|
|
13002
|
-
return real(entry) === real(
|
|
13434
|
+
return real(entry) === real(fileURLToPath2(import.meta.url));
|
|
13003
13435
|
})();
|
|
13004
13436
|
if (invokedDirectly) {
|
|
13005
13437
|
main(process.argv.slice(2)).then((code) => {
|
|
@@ -13022,6 +13454,7 @@ export {
|
|
|
13022
13454
|
runCapture,
|
|
13023
13455
|
runLogin,
|
|
13024
13456
|
runLogout,
|
|
13457
|
+
runPull,
|
|
13025
13458
|
runSearch,
|
|
13026
13459
|
runWhoami
|
|
13027
13460
|
};
|