flatbread-cli 0.4.0 → 0.5.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 CHANGED
@@ -168,8 +168,16 @@ reload the VS Code window) afterwards.
168
168
  A connected tool is not a used one: in a real session the agent read the tool list and implemented
169
169
  the fix without searching. So setup also writes a skill that says when to search - before the first
170
170
  edit, after a failure - to `~/.claude/skills/flatbread/SKILL.md` (or under `$CLAUDE_CONFIG_DIR`) and
171
- `~/.agents/skills/flatbread/SKILL.md`, the shared skills folder Codex reads. A file there that
172
- flatbread did not write is left alone. `--no-mcp` skips the server and the skill.
171
+ `~/.agents/skills/flatbread/SKILL.md`, the shared skills folder Codex, Cursor and Gemini CLI read.
172
+ Where Antigravity is installed it also goes inside a plugin
173
+ (`~/.gemini/config/plugins/flatbread/`, with its `plugin.json`), and where OpenCode is installed to
174
+ `~/.config/opencode/skills/flatbread/SKILL.md`. A file there that flatbread did not write is left alone.
175
+
176
+ The other agents get the MCP server too, each only if it is installed (its folder exists): Codex as a
177
+ `[mcp_servers.flatbread]` table in `~/.codex/config.toml` (or `$CODEX_HOME`), Cursor in
178
+ `~/.cursor/mcp.json`, Antigravity in `~/.gemini/config/mcp_config.json`. Same rules as Claude Code's
179
+ file, and in the TOML every other line, comment and table stays where it was. `--no-mcp` skips every
180
+ server and every skill.
173
181
 
174
182
  `setup --dry-run` writes nothing. `setup --uninstall` removes the handlers, the MCP entry, the skill and
175
183
  the hook, and puts a chained hook back byte for byte. `FLATBREAD_HOOK_DISABLED=1` stops every capture.
@@ -7,9 +7,9 @@ var __export = (target, all) => {
7
7
 
8
8
  // src/index.ts
9
9
  import { spawn as spawn3 } from "node:child_process";
10
- import fs24 from "node:fs";
10
+ import fs26 from "node:fs";
11
11
  import os9 from "node:os";
12
- import path23 from "node:path";
12
+ import path25 from "node:path";
13
13
  import { fileURLToPath as fileURLToPath2 } from "node:url";
14
14
 
15
15
  // ../../node_modules/zod/v3/external.js
@@ -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: path24, errorMaps, issueData } = params;
494
- const fullPath = [...path24, ...issueData.path || []];
493
+ const { data, path: path26, errorMaps, issueData } = params;
494
+ const fullPath = [...path26, ...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, path24, key) {
610
+ constructor(parent, value, path26, key) {
611
611
  this._cachedPath = [];
612
612
  this.parent = parent;
613
613
  this.data = value;
614
- this._path = path24;
614
+ this._path = path26;
615
615
  this._key = key;
616
616
  }
617
617
  get path() {
@@ -4461,8 +4461,8 @@ function firstLine(text2) {
4461
4461
  function labelOf(step, cwd) {
4462
4462
  const tool = step.tool;
4463
4463
  if (tool === void 0) return step.type;
4464
- const path24 = firstString(tool.input, FILE_PATH_KEYS);
4465
- if (path24 !== void 0) return `${tool.name} ${repoRelative(path24, cwd)}`;
4464
+ const path26 = firstString(tool.input, FILE_PATH_KEYS);
4465
+ if (path26 !== void 0) return `${tool.name} ${repoRelative(path26, cwd)}`;
4466
4466
  const other = firstString(tool.input, TARGET_KEYS);
4467
4467
  if (other !== void 0) return cut(`${tool.name}: ${repoRelative(firstLine(other), cwd)}`, BRIEF_LIMITS.quote);
4468
4468
  return tool.name;
@@ -4714,9 +4714,9 @@ function runDiagnostics(trace) {
4714
4714
  searches.set(key, found);
4715
4715
  }
4716
4716
  }
4717
- for (const [path24, entries] of reads) {
4717
+ for (const [path26, entries] of reads) {
4718
4718
  const hit = densest(entries, WASTE_RULES.repeatedRead.times, WASTE_RULES.repeatedRead.windowMinutes * 6e4);
4719
- if (hit) waste.push({ kind: "repeated-read", path: path24, steps: hit, windowMinutes: WASTE_RULES.repeatedRead.windowMinutes });
4719
+ if (hit) waste.push({ kind: "repeated-read", path: path26, steps: hit, windowMinutes: WASTE_RULES.repeatedRead.windowMinutes });
4720
4720
  }
4721
4721
  for (const { label, entries } of searches.values()) {
4722
4722
  const hit = densest(entries, WASTE_RULES.repeatedSearch.times, WASTE_RULES.repeatedSearch.windowMinutes * 6e4);
@@ -4795,19 +4795,19 @@ function filesOf(steps, cwd) {
4795
4795
  const patches = [];
4796
4796
  steps.forEach((step, index) => {
4797
4797
  if (!isSuccessfulEdit(step) || step.tool === void 0) return;
4798
- const path24 = repoRelative(firstString(step.tool.input, FILE_PATH_KEYS), cwd);
4799
- const file = files.get(path24) ?? { path: path24 };
4798
+ const path26 = repoRelative(firstString(step.tool.input, FILE_PATH_KEYS), cwd);
4799
+ const file = files.get(path26) ?? { path: path26 };
4800
4800
  if (step.diff !== void 0) {
4801
4801
  const counted = countPatch(step.diff.patch);
4802
4802
  file.added = (file.added ?? 0) + counted.added;
4803
4803
  file.removed = (file.removed ?? 0) + counted.removed;
4804
- patches.push({ path: path24, patch: repoRelative(step.diff.patch, cwd), step: step.stepNumber });
4804
+ patches.push({ path: path26, patch: repoRelative(step.diff.patch, cwd), step: step.stepNumber });
4805
4805
  }
4806
4806
  const result = resultOf(steps, index);
4807
4807
  if (step.tool.name.toLowerCase() === "write" && result !== void 0 && /^File created successfully\b/.test(result.trim())) {
4808
4808
  file.created = true;
4809
4809
  }
4810
- files.set(path24, file);
4810
+ files.set(path26, file);
4811
4811
  });
4812
4812
  return { files: [...files.values()], patches };
4813
4813
  }
@@ -5120,13 +5120,13 @@ function categorizeTrace(trace) {
5120
5120
  };
5121
5121
  }
5122
5122
  if (paths.length > 0) {
5123
- if (paths.every((path24) => matchesAny(path24, DOC_PATTERNS))) {
5123
+ if (paths.every((path26) => matchesAny(path26, DOC_PATTERNS))) {
5124
5124
  return { category: "docs", evidence: [`all ${paths.length} touched path(s) are documentation`, ...paths.slice(0, 3)] };
5125
5125
  }
5126
- if (paths.every((path24) => matchesAny(path24, TEST_PATTERNS))) {
5126
+ if (paths.every((path26) => matchesAny(path26, TEST_PATTERNS))) {
5127
5127
  return { category: "tests", evidence: [`all ${paths.length} touched path(s) are tests`, ...paths.slice(0, 3)] };
5128
5128
  }
5129
- if (paths.every((path24) => matchesAny(path24, INFRA_PATTERNS))) {
5129
+ if (paths.every((path26) => matchesAny(path26, INFRA_PATTERNS))) {
5130
5130
  return { category: "infra", evidence: [`all ${paths.length} touched path(s) are build/CI/deploy config`, ...paths.slice(0, 3)] };
5131
5131
  }
5132
5132
  }
@@ -11236,7 +11236,6 @@ async function searchRemote(query, options = {}) {
11236
11236
  if (query.query) params.set("q", query.query);
11237
11237
  if (query.harness) params.set("harness", query.harness);
11238
11238
  if (query.category) params.set("category", query.category);
11239
- if (query.onlyWithRecoveries) params.set("recoveries", "1");
11240
11239
  const answer = await get(`${apiUrl}/api/search?${params.toString()}`, headersFor(apiUrl, options, "application/json"), options);
11241
11240
  if (!answer.ok) return answer;
11242
11241
  if (answer.status !== 200) return failureOf(answer.status, answer.body);
@@ -11357,7 +11356,6 @@ function readBucketRecord(dir, slug) {
11357
11356
  function matchesQuery(summary, query) {
11358
11357
  if (query.harness !== void 0 && summary.harness !== query.harness) return false;
11359
11358
  if (query.category !== void 0 && summary.category !== query.category) return false;
11360
- if (query.onlyWithRecoveries === true && summary.failuresRecovered === 0) return false;
11361
11359
  return matchesSearch(summary, query.query ?? "");
11362
11360
  }
11363
11361
  async function captureToBucket(options) {
@@ -11422,7 +11420,7 @@ function logCapture(dir, line, now = (/* @__PURE__ */ new Date()).toISOString())
11422
11420
 
11423
11421
  // src/mcp.ts
11424
11422
  var MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
11425
- var MCP_SERVER_VERSION = "0.4.0";
11423
+ var MCP_SERVER_VERSION = "0.5.0";
11426
11424
  function defaultMcpDeps(storeDir2, bucketDir2) {
11427
11425
  return {
11428
11426
  storeDir: storeDir2,
@@ -11441,8 +11439,7 @@ var MCP_TOOLS = [
11441
11439
  properties: {
11442
11440
  query: { type: "string", description: "Words that must all appear in the run's title or ID" },
11443
11441
  harness: { type: "string", enum: [...HARNESS_IDS], description: "Only runs from this agent harness" },
11444
- category: { type: "string", enum: [...TASK_CATEGORIES], description: "Only runs of this kind of job" },
11445
- onlyWithRecoveries: { type: "boolean", description: "Only runs where the agent recovered from a failure" }
11442
+ category: { type: "string", enum: [...TASK_CATEGORIES], description: "Only runs of this kind of job" }
11446
11443
  },
11447
11444
  additionalProperties: false
11448
11445
  }
@@ -11489,10 +11486,7 @@ function readQuery(args) {
11489
11486
  }
11490
11487
  query.category = args["category"];
11491
11488
  }
11492
- if (args["onlyWithRecoveries"] !== void 0) {
11493
- if (typeof args["onlyWithRecoveries"] !== "boolean") return "onlyWithRecoveries must be true or false";
11494
- query.onlyWithRecoveries = args["onlyWithRecoveries"];
11495
- }
11489
+ if (args["onlyWithRecoveries"] !== void 0) return "onlyWithRecoveries was removed: search by words, harness and category";
11496
11490
  return query;
11497
11491
  }
11498
11492
  function localRecords(deps) {
@@ -11641,6 +11635,10 @@ async function runMcpServer(deps, io) {
11641
11635
  return 0;
11642
11636
  }
11643
11637
 
11638
+ // src/setup.ts
11639
+ import fs25 from "node:fs";
11640
+ import path24 from "node:path";
11641
+
11644
11642
  // src/claude-mcp.ts
11645
11643
  import fs20 from "node:fs";
11646
11644
  import os8 from "node:os";
@@ -11694,7 +11692,7 @@ function write(file, read2, next) {
11694
11692
  } catch {
11695
11693
  return `${file} is read-only, so it was left as it was`;
11696
11694
  }
11697
- if (fs20.readFileSync(file, "utf8") !== read2.raw) return `${file} changed while setup ran (Claude Code writes it); run setup again`;
11695
+ if (fs20.readFileSync(file, "utf8") !== read2.raw) return `${file} changed while setup ran; run setup again`;
11698
11696
  const backup = `${file}.flatbread.bak`;
11699
11697
  if (!fs20.existsSync(backup)) fs20.copyFileSync(file, backup);
11700
11698
  }
@@ -11706,12 +11704,22 @@ function write(file, read2, next) {
11706
11704
  return null;
11707
11705
  }
11708
11706
  function registerClaudeMcp(options) {
11707
+ return registerMcpJson({
11708
+ ...options,
11709
+ entry: { ...mcpEntryFor(options.execPath, options.entryPath) },
11710
+ app: "Claude Code",
11711
+ scope: "for every project (user scope)",
11712
+ restart: "restart Claude Code (or reload the VS Code window) so it starts the server",
11713
+ byHand: `claude mcp add --scope user ${MCP_SERVER_NAME} -- "${options.execPath}" "${options.entryPath}" mcp`
11714
+ });
11715
+ }
11716
+ function registerMcpJson(options) {
11709
11717
  const { file, log } = options;
11710
11718
  const read2 = readConfig(file);
11711
11719
  if ("problem" in read2) {
11712
11720
  log(` mcp refused: ${file}: ${read2.problem}`);
11713
- log(" Claude Code's own state lives there, so a file we cannot read is not one we rewrite.");
11714
- if (options.uninstall !== true) log(` by hand: claude mcp add --scope user ${MCP_SERVER_NAME} -- "${options.execPath}" "${options.entryPath}" mcp`);
11721
+ log(` ${options.app}'s own config lives there, so a file we cannot read is not one we rewrite.`);
11722
+ if (options.uninstall !== true && options.byHand !== void 0) log(` by hand: ${options.byHand}`);
11715
11723
  return 1;
11716
11724
  }
11717
11725
  const servers = isObject(read2.root["mcpServers"]) ? read2.root["mcpServers"] : void 0;
@@ -11741,13 +11749,13 @@ function registerClaudeMcp(options) {
11741
11749
  if (fs20.existsSync(backup)) log(` ${backup} is the copy setup took before its first write; delete it when you like`);
11742
11750
  return 0;
11743
11751
  }
11744
- const entry = mcpEntryFor(options.execPath, options.entryPath);
11752
+ const entry = options.entry;
11745
11753
  if (current !== void 0 && !runsFlatbreadMcp(current)) {
11746
11754
  log(` mcp refused: ${file} already has a server named ${MCP_SERVER_NAME} that does not run flatbread, left alone`);
11747
11755
  return 1;
11748
11756
  }
11749
11757
  if (JSON.stringify(current) === JSON.stringify(entry)) {
11750
- log(` mcp ${file}: registered for every project, unchanged`);
11758
+ log(` mcp ${file}: registered ${options.scope}, unchanged`);
11751
11759
  return 0;
11752
11760
  }
11753
11761
  const verb = current === void 0 ? "register" : "update";
@@ -11761,14 +11769,367 @@ function registerClaudeMcp(options) {
11761
11769
  log(` mcp refused: ${problem}`);
11762
11770
  return 1;
11763
11771
  }
11764
- log(` mcp ${file}: ${verb === "register" ? "registered" : "updated"} for every project (user scope)`);
11765
- log(" restart Claude Code (or reload the VS Code window) so it starts the server");
11772
+ log(` mcp ${file}: ${verb === "register" ? "registered" : "updated"} ${options.scope}`);
11773
+ log(` ${options.restart}`);
11766
11774
  return 0;
11767
11775
  }
11768
11776
 
11769
- // src/git-hook.ts
11777
+ // src/codex-mcp.ts
11770
11778
  import fs21 from "node:fs";
11771
11779
  import path20 from "node:path";
11780
+ var BOM2 = "\uFEFF";
11781
+ var OWNED_KEYS = /* @__PURE__ */ new Set(["type", "command", "args"]);
11782
+ function codexConfigPath(codexHome2) {
11783
+ return path20.join(codexHome2, "config.toml");
11784
+ }
11785
+ function quote(value) {
11786
+ return JSON.stringify(value);
11787
+ }
11788
+ function codexBlock(execPath, entryPath) {
11789
+ return `[mcp_servers.${MCP_SERVER_NAME}]
11790
+ type = "stdio"
11791
+ command = ${quote(execPath)}
11792
+ args = [${[entryPath, "mcp"].map(quote).join(", ")}]
11793
+ `;
11794
+ }
11795
+ function tomlCode(line) {
11796
+ let out = "";
11797
+ let inQuote = "";
11798
+ let escaped = false;
11799
+ for (const c of line) {
11800
+ if (inQuote !== "") {
11801
+ out += c;
11802
+ if (inQuote === '"' && escaped) escaped = false;
11803
+ else if (inQuote === '"' && c === "\\") escaped = true;
11804
+ else if (c === inQuote) inQuote = "";
11805
+ continue;
11806
+ }
11807
+ if (c === "#") break;
11808
+ if (c === '"' || c === "'") inQuote = c;
11809
+ out += c;
11810
+ }
11811
+ return out.trim();
11812
+ }
11813
+ function openAfter(line, open) {
11814
+ let i = 0;
11815
+ while (i < line.length) {
11816
+ if (open === '"""') {
11817
+ if (line[i] === "\\") {
11818
+ i += 2;
11819
+ continue;
11820
+ }
11821
+ if (line.startsWith('"""', i)) {
11822
+ i += 3;
11823
+ while (line[i] === '"') i++;
11824
+ open = "";
11825
+ continue;
11826
+ }
11827
+ i++;
11828
+ } else if (open === "'''") {
11829
+ const j = line.indexOf("'''", i);
11830
+ if (j < 0) return open;
11831
+ i = j + 3;
11832
+ while (line[i] === "'") i++;
11833
+ open = "";
11834
+ } else if (line.startsWith('"""', i)) {
11835
+ open = '"""';
11836
+ i += 3;
11837
+ } else if (line.startsWith("'''", i)) {
11838
+ open = "'''";
11839
+ i += 3;
11840
+ } else if (line[i] === "#") {
11841
+ return "";
11842
+ } else if (line[i] === '"') {
11843
+ i++;
11844
+ while (i < line.length && line[i] !== '"') i += line[i] === "\\" ? 2 : 1;
11845
+ i++;
11846
+ } else if (line[i] === "'") {
11847
+ const j = line.indexOf("'", i + 1);
11848
+ if (j < 0) return "";
11849
+ i = j + 1;
11850
+ } else {
11851
+ i++;
11852
+ }
11853
+ }
11854
+ return open;
11855
+ }
11856
+ function tomlCodeLines(text2) {
11857
+ let open = "";
11858
+ return text2.split("\n").map((line) => {
11859
+ const was = open;
11860
+ open = openAfter(line, open);
11861
+ return was !== "" ? "" : line;
11862
+ });
11863
+ }
11864
+ function tomlKeyPath(key) {
11865
+ const parts = [];
11866
+ let part = "";
11867
+ let inQuote = "";
11868
+ for (const c of key) {
11869
+ if (inQuote !== "") {
11870
+ part += c;
11871
+ if (c === inQuote) inQuote = "";
11872
+ continue;
11873
+ }
11874
+ if (c === '"' || c === "'") {
11875
+ inQuote = c;
11876
+ part += c;
11877
+ } else if (c === ".") {
11878
+ parts.push(part);
11879
+ part = "";
11880
+ } else {
11881
+ part += c;
11882
+ }
11883
+ }
11884
+ parts.push(part);
11885
+ return parts.map((p) => {
11886
+ const t = p.trim();
11887
+ if (t.length >= 2 && (t[0] === '"' || t[0] === "'") && t[t.length - 1] === t[0]) {
11888
+ if (t[0] === "'") return t.slice(1, -1);
11889
+ try {
11890
+ return JSON.parse(t);
11891
+ } catch {
11892
+ return t;
11893
+ }
11894
+ }
11895
+ return t;
11896
+ });
11897
+ }
11898
+ function startsWithPath(full, prefix) {
11899
+ return prefix.length <= full.length && prefix.every((part, i) => full[i] === part);
11900
+ }
11901
+ function headerPath(line) {
11902
+ const code = tomlCode(line);
11903
+ if (!code.startsWith("[") || code.startsWith("[[") || !code.endsWith("]")) return null;
11904
+ return tomlKeyPath(code.slice(1, -1));
11905
+ }
11906
+ function arrayDelta(text2) {
11907
+ let depth = 0;
11908
+ let inQuote = "";
11909
+ let escaped = false;
11910
+ for (const c of text2) {
11911
+ if (inQuote !== "") {
11912
+ if (inQuote === '"' && escaped) escaped = false;
11913
+ else if (inQuote === '"' && c === "\\") escaped = true;
11914
+ else if (c === inQuote) inQuote = "";
11915
+ continue;
11916
+ }
11917
+ if (c === '"' || c === "'") inQuote = c;
11918
+ else if (c === "[") depth++;
11919
+ else if (c === "]") depth--;
11920
+ }
11921
+ return depth;
11922
+ }
11923
+ function stringValues(text2) {
11924
+ const out = [];
11925
+ const re = /"((?:[^"\\]|\\.)*)"|'([^']*)'/g;
11926
+ for (const match of text2.matchAll(re)) {
11927
+ if (match[2] !== void 0) out.push(match[2]);
11928
+ else {
11929
+ try {
11930
+ out.push(JSON.parse(`"${match[1] ?? ""}"`));
11931
+ } catch {
11932
+ }
11933
+ }
11934
+ }
11935
+ return out;
11936
+ }
11937
+ var OURS = ["mcp_servers", MCP_SERVER_NAME];
11938
+ function ourBlocks(code) {
11939
+ const all = [];
11940
+ let main2 = null;
11941
+ code.forEach((line, i) => {
11942
+ const header = headerPath(line);
11943
+ if (header === null || !startsWithPath(header, OURS)) return;
11944
+ let end = code.length;
11945
+ for (let j = i + 1; j < code.length; j++) {
11946
+ if (tomlCode(code[j] ?? "").startsWith("[")) {
11947
+ end = j;
11948
+ break;
11949
+ }
11950
+ }
11951
+ const block = { start: i, end };
11952
+ all.push(block);
11953
+ if (header.length === OURS.length && main2 === null) main2 = block;
11954
+ });
11955
+ return { main: main2, all };
11956
+ }
11957
+ function blockRunsFlatbread(code, block) {
11958
+ const words = [];
11959
+ for (let i = block.start + 1; i < block.end; i++) {
11960
+ const line = tomlCode(code[i] ?? "");
11961
+ const at = line.indexOf("=");
11962
+ if (at < 0) continue;
11963
+ const key = line.slice(0, at).trim();
11964
+ let value = line.slice(at + 1).trim();
11965
+ if (key === "command") words.unshift(...stringValues(value));
11966
+ if (key === "args") {
11967
+ let depth = arrayDelta(value);
11968
+ while (depth > 0 && i + 1 < block.end) {
11969
+ i++;
11970
+ const next = tomlCode(code[i] ?? "");
11971
+ value += `
11972
+ ${next}`;
11973
+ depth += arrayDelta(next);
11974
+ }
11975
+ words.push(...stringValues(value));
11976
+ }
11977
+ }
11978
+ return words[words.length - 1] === "mcp" && words.some((word) => /flatbread/i.test(word));
11979
+ }
11980
+ function tomlProblem(code) {
11981
+ let table = [];
11982
+ for (const [i, line] of code.entries()) {
11983
+ const text2 = tomlCode(line);
11984
+ if (text2.startsWith("[")) {
11985
+ if (!text2.endsWith("]")) return `line ${i + 1} is a table header that never closes: ${line.trim()}`;
11986
+ table = text2.startsWith("[[") ? tomlKeyPath(text2.slice(2, -2)) : tomlKeyPath(text2.slice(1, -1));
11987
+ continue;
11988
+ }
11989
+ const at = text2.indexOf("=");
11990
+ if (at < 0 || startsWithPath(table, OURS)) continue;
11991
+ const full = [...table, ...tomlKeyPath(text2.slice(0, at))];
11992
+ if (startsWithPath(OURS, full) || startsWithPath(full, OURS)) {
11993
+ return `line ${i + 1} sets ${full.join(".")} inline, and the [mcp_servers.${MCP_SERVER_NAME}] table would define it twice`;
11994
+ }
11995
+ }
11996
+ return null;
11997
+ }
11998
+ function writeLike(file, raw, lf) {
11999
+ let next = lf;
12000
+ if (raw !== null && raw.includes("\r\n")) next = next.replace(/\n/g, "\r\n");
12001
+ if (raw !== null && raw.startsWith(BOM2)) next = BOM2 + next;
12002
+ if (raw !== null) {
12003
+ try {
12004
+ fs21.accessSync(file, fs21.constants.W_OK);
12005
+ } catch {
12006
+ return `${file} is read-only, so it was left as it was`;
12007
+ }
12008
+ if (fs21.readFileSync(file, "utf8") !== raw) return `${file} changed while setup ran; run setup again`;
12009
+ const backup = `${file}.flatbread.bak`;
12010
+ if (!fs21.existsSync(backup)) fs21.copyFileSync(file, backup);
12011
+ }
12012
+ const mode = raw !== null ? fs21.statSync(file).mode & 511 : 384;
12013
+ fs21.mkdirSync(path20.dirname(file), { recursive: true });
12014
+ const temp = `${file}.flatbread-${process.pid}.tmp`;
12015
+ fs21.writeFileSync(temp, next, { encoding: "utf8", mode });
12016
+ fs21.renameSync(temp, file);
12017
+ return null;
12018
+ }
12019
+ function removeBlocks(lines2, blocks) {
12020
+ const drop = /* @__PURE__ */ new Set();
12021
+ for (const block of blocks) for (let i = block.start; i < block.end; i++) drop.add(i);
12022
+ return lines2.filter((_, i) => !drop.has(i));
12023
+ }
12024
+ function tidy(lines2) {
12025
+ const text2 = lines2.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "");
12026
+ return text2 === "" ? "" : `${text2}
12027
+ `;
12028
+ }
12029
+ function registerCodexMcp(options) {
12030
+ const { file, log } = options;
12031
+ let raw;
12032
+ try {
12033
+ raw = fs21.readFileSync(file, "utf8");
12034
+ } catch (error) {
12035
+ if (error.code !== "ENOENT") {
12036
+ log(` mcp refused: ${file}: ${error.message}`);
12037
+ return 1;
12038
+ }
12039
+ raw = null;
12040
+ }
12041
+ const text2 = (raw ?? "").replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
12042
+ const lines2 = text2.split("\n");
12043
+ const code = tomlCodeLines(text2);
12044
+ const { main: main2, all } = ourBlocks(code);
12045
+ if (options.uninstall === true) {
12046
+ if (main2 === null) {
12047
+ log(` mcp no ${MCP_SERVER_NAME} server in ${file}`);
12048
+ return 0;
12049
+ }
12050
+ if (!blockRunsFlatbread(code, main2)) {
12051
+ log(` mcp ${file}: the ${MCP_SERVER_NAME} server there does not run flatbread, left alone`);
12052
+ return 0;
12053
+ }
12054
+ if (options.dryRun === true) {
12055
+ log(` mcp ${file}: would remove the ${MCP_SERVER_NAME} server`);
12056
+ return 0;
12057
+ }
12058
+ const problem2 = writeLike(file, raw, tidy(removeBlocks(lines2, all)));
12059
+ if (problem2 !== null) {
12060
+ log(` mcp refused: ${problem2}`);
12061
+ return 1;
12062
+ }
12063
+ log(` mcp ${file}: removed the ${MCP_SERVER_NAME} server`);
12064
+ return 0;
12065
+ }
12066
+ const problem = tomlProblem(code);
12067
+ if (problem !== null) {
12068
+ log(` mcp refused: ${file}: ${problem}`);
12069
+ log(" fix that line, or add this table by hand:");
12070
+ for (const line of codexBlock(options.execPath, options.entryPath).trimEnd().split("\n")) log(` ${line}`);
12071
+ return 1;
12072
+ }
12073
+ if (main2 !== null && !blockRunsFlatbread(code, main2)) {
12074
+ log(` mcp refused: ${file} already has a server named ${MCP_SERVER_NAME} that does not run flatbread, left alone`);
12075
+ return 1;
12076
+ }
12077
+ const block = codexBlock(options.execPath, options.entryPath);
12078
+ const ownLines = block.trimEnd().split("\n").slice(1);
12079
+ let next;
12080
+ if (main2 === null) {
12081
+ const kept = text2.replace(/\n+$/, "");
12082
+ next = `${kept}${kept === "" ? "" : "\n\n"}${block}`;
12083
+ } else {
12084
+ const body = [];
12085
+ let inserted = false;
12086
+ for (let i = main2.start + 1; i < main2.end; i++) {
12087
+ const lineCode = tomlCode(code[i] ?? "");
12088
+ const at = lineCode.indexOf("=");
12089
+ const key = at >= 0 ? lineCode.slice(0, at).trim() : "";
12090
+ if (!OWNED_KEYS.has(key)) {
12091
+ body.push(lines2[i] ?? "");
12092
+ continue;
12093
+ }
12094
+ if (!inserted) {
12095
+ body.push(...ownLines);
12096
+ inserted = true;
12097
+ }
12098
+ if (key === "args") {
12099
+ let depth = arrayDelta(lineCode.slice(at + 1));
12100
+ while (depth > 0 && i + 1 < main2.end) {
12101
+ i++;
12102
+ depth += arrayDelta(tomlCode(code[i] ?? ""));
12103
+ }
12104
+ }
12105
+ }
12106
+ if (!inserted) body.unshift(...ownLines);
12107
+ const out = [...lines2.slice(0, main2.start + 1), ...body, ...lines2.slice(main2.end)];
12108
+ next = out.join("\n");
12109
+ if (!next.endsWith("\n")) next += "\n";
12110
+ }
12111
+ if (next === text2) {
12112
+ log(` mcp ${file}: registered for Codex, unchanged`);
12113
+ return 0;
12114
+ }
12115
+ const verb = main2 === null ? "register" : "update";
12116
+ if (options.dryRun === true) {
12117
+ log(` mcp ${file}: would ${verb} [mcp_servers.${MCP_SERVER_NAME}]`);
12118
+ return 0;
12119
+ }
12120
+ const failed = writeLike(file, raw, next);
12121
+ if (failed !== null) {
12122
+ log(` mcp refused: ${failed}`);
12123
+ return 1;
12124
+ }
12125
+ log(` mcp ${file}: ${verb === "register" ? "registered" : "updated"} for Codex`);
12126
+ log(" start a new Codex session so it starts the server");
12127
+ return 0;
12128
+ }
12129
+
12130
+ // src/git-hook.ts
12131
+ import fs22 from "node:fs";
12132
+ import path21 from "node:path";
11772
12133
  var POST_COMMIT_MARKER = "# flatbread post-commit hook - remove with: flatbread setup --uninstall";
11773
12134
  var CHAINED_HOOK = "post-commit.pre-flatbread";
11774
12135
  var sq = (value) => `'${value.replace(/\\/g, "/").replace(/'/g, "'\\''")}'`;
@@ -11812,25 +12173,25 @@ function hooksDirFor(cwd, options = {}) {
11812
12173
  }
11813
12174
  const common = git(top, ["rev-parse", "--git-common-dir"], options)?.trim();
11814
12175
  if (common === void 0 || common.length === 0) return { ok: false, reason: "unreadable", detail: `git could not name the git directory of ${top}` };
11815
- return { ok: true, repo: path20.resolve(top), dir: path20.resolve(top, common, "hooks"), ...machineHooksPath !== void 0 ? { machineHooksPath } : {} };
12176
+ return { ok: true, repo: path21.resolve(top), dir: path21.resolve(top, common, "hooks"), ...machineHooksPath !== void 0 ? { machineHooksPath } : {} };
11816
12177
  }
11817
12178
  var read = (file) => {
11818
12179
  try {
11819
- return fs21.readFileSync(file, "utf8");
12180
+ return fs22.readFileSync(file, "utf8");
11820
12181
  } catch {
11821
12182
  return null;
11822
12183
  }
11823
12184
  };
11824
12185
  function installGitHook(dir, script, options = {}) {
11825
- const hook = path20.join(dir, "post-commit");
11826
- const chained = path20.join(dir, CHAINED_HOOK);
12186
+ const hook = path21.join(dir, "post-commit");
12187
+ const chained = path21.join(dir, CHAINED_HOOK);
11827
12188
  const current = read(hook);
11828
12189
  const write2 = (lines2) => {
11829
12190
  if (options.dryRun === true) return { code: 0, lines: [...lines2, "--dry-run: nothing was written"] };
11830
- fs21.mkdirSync(dir, { recursive: true });
11831
- if (current !== null && !isOurs(current)) fs21.renameSync(hook, chained);
11832
- fs21.writeFileSync(hook, script, { encoding: "utf8", mode: 493 });
11833
- fs21.chmodSync(hook, 493);
12191
+ fs22.mkdirSync(dir, { recursive: true });
12192
+ if (current !== null && !isOurs(current)) fs22.renameSync(hook, chained);
12193
+ fs22.writeFileSync(hook, script, { encoding: "utf8", mode: 493 });
12194
+ fs22.chmodSync(hook, 493);
11834
12195
  return { code: 0, lines: lines2 };
11835
12196
  };
11836
12197
  if (current === null) return write2([`post-commit ${hook} (new)`]);
@@ -11838,7 +12199,7 @@ function installGitHook(dir, script, options = {}) {
11838
12199
  if (current === script) return { code: 0, lines: [`post-commit ${hook}`, "already installed, unchanged"] };
11839
12200
  return write2([`post-commit ${hook}`, "updated the flatbread hook"]);
11840
12201
  }
11841
- if (fs21.existsSync(chained)) {
12202
+ if (fs22.existsSync(chained)) {
11842
12203
  return {
11843
12204
  code: 1,
11844
12205
  lines: [
@@ -11854,10 +12215,10 @@ function installGitHook(dir, script, options = {}) {
11854
12215
  ]);
11855
12216
  }
11856
12217
  function uninstallGitHook(dir, options = {}) {
11857
- const hook = path20.join(dir, "post-commit");
11858
- const chained = path20.join(dir, CHAINED_HOOK);
12218
+ const hook = path21.join(dir, "post-commit");
12219
+ const chained = path21.join(dir, CHAINED_HOOK);
11859
12220
  const current = read(hook);
11860
- const hasChained = fs21.existsSync(chained);
12221
+ const hasChained = fs22.existsSync(chained);
11861
12222
  const done = (lines2, act) => {
11862
12223
  if (options.dryRun === true) return { code: 0, lines: [...lines2, "--dry-run: nothing was written"] };
11863
12224
  act();
@@ -11875,17 +12236,17 @@ function uninstallGitHook(dir, options = {}) {
11875
12236
  if (current === null && !hasChained) return { code: 0, lines: [`no flatbread post-commit in ${dir}`] };
11876
12237
  if (hasChained) {
11877
12238
  return done([`post-commit ${hook}`, `restored your post-commit from ${CHAINED_HOOK}`], () => {
11878
- if (current !== null) fs21.rmSync(hook);
11879
- fs21.renameSync(chained, hook);
12239
+ if (current !== null) fs22.rmSync(hook);
12240
+ fs22.renameSync(chained, hook);
11880
12241
  });
11881
12242
  }
11882
- return done([`post-commit ${hook}`, "removed the flatbread hook"], () => fs21.rmSync(hook));
12243
+ return done([`post-commit ${hook}`, "removed the flatbread hook"], () => fs22.rmSync(hook));
11883
12244
  }
11884
12245
 
11885
12246
  // src/hook.ts
11886
12247
  import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
11887
- import fs22 from "node:fs";
11888
- import path21 from "node:path";
12248
+ import fs23 from "node:fs";
12249
+ import path22 from "node:path";
11889
12250
  var HOOK_EVENTS = ["Stop", "SessionEnd"];
11890
12251
  var OFFER_AFTER_STOPS = 2;
11891
12252
  var MAX_TRACKED_SESSIONS = 50;
@@ -11895,14 +12256,14 @@ function emptyHookState() {
11895
12256
  return { version: HOOK_STATE_VERSION, sessions: {} };
11896
12257
  }
11897
12258
  function hookStatePath(env = process.env) {
11898
- return path21.join(flatbreadHome(env), "hook-state.json");
12259
+ return path22.join(flatbreadHome(env), "hook-state.json");
11899
12260
  }
11900
12261
  function readHookState(env = process.env) {
11901
12262
  const file = hookStatePath(env);
11902
- if (!fs22.existsSync(file)) return { ok: true, state: emptyHookState(), present: false };
12263
+ if (!fs23.existsSync(file)) return { ok: true, state: emptyHookState(), present: false };
11903
12264
  let raw;
11904
12265
  try {
11905
- raw = fs22.readFileSync(file, "utf8");
12266
+ raw = fs23.readFileSync(file, "utf8");
11906
12267
  } catch (error) {
11907
12268
  return { ok: false, reason: "unreadable", detail: error.message };
11908
12269
  }
@@ -11945,9 +12306,9 @@ function pruneHookState(state, keep = MAX_TRACKED_SESSIONS) {
11945
12306
  }
11946
12307
  function writeHookState(state, env = process.env) {
11947
12308
  const dir = flatbreadHome(env);
11948
- fs22.mkdirSync(dir, { recursive: true, mode: 448 });
12309
+ fs23.mkdirSync(dir, { recursive: true, mode: 448 });
11949
12310
  const file = hookStatePath(env);
11950
- fs22.writeFileSync(file, JSON.stringify(pruneHookState(state), null, 2) + "\n", {
12311
+ fs23.writeFileSync(file, JSON.stringify(pruneHookState(state), null, 2) + "\n", {
11951
12312
  encoding: "utf8",
11952
12313
  mode: 384
11953
12314
  });
@@ -12093,7 +12454,7 @@ async function runHookNotify(io) {
12093
12454
  const stdout = io.stdout ?? ((line) => process.stdout.write(line + "\n"));
12094
12455
  const stderr = io.stderr ?? ((line) => process.stderr.write(line + "\n"));
12095
12456
  const now = io.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
12096
- const exists = io.exists ?? ((filePath) => fs22.existsSync(filePath));
12457
+ const exists = io.exists ?? ((filePath) => fs23.existsSync(filePath));
12097
12458
  const execPath = io.execPath ?? process.execPath;
12098
12459
  const entryPath = io.entryPath ?? process.argv[1] ?? "";
12099
12460
  try {
@@ -12250,10 +12611,10 @@ function removeHookConfig(settings) {
12250
12611
  return { settings: root, removed };
12251
12612
  }
12252
12613
  function readSettingsFile(file) {
12253
- if (!fs22.existsSync(file)) return { present: false, file };
12614
+ if (!fs23.existsSync(file)) return { present: false, file };
12254
12615
  let raw;
12255
12616
  try {
12256
- raw = fs22.readFileSync(file, "utf8");
12617
+ raw = fs23.readFileSync(file, "utf8");
12257
12618
  } catch (error) {
12258
12619
  return { present: true, file, unreadable: error.message };
12259
12620
  }
@@ -12265,10 +12626,10 @@ function readSettingsFile(file) {
12265
12626
  }
12266
12627
  }
12267
12628
  function writeSettingsFile(file, settings) {
12268
- fs22.mkdirSync(path21.dirname(file), { recursive: true });
12629
+ fs23.mkdirSync(path22.dirname(file), { recursive: true });
12269
12630
  const temp = `${file}.flatbread-${process.pid}.tmp`;
12270
- fs22.writeFileSync(temp, JSON.stringify(settings, null, 2) + "\n", "utf8");
12271
- fs22.renameSync(temp, file);
12631
+ fs23.writeFileSync(temp, JSON.stringify(settings, null, 2) + "\n", "utf8");
12632
+ fs23.renameSync(temp, file);
12272
12633
  }
12273
12634
  function handlersFor(execPath, entryPath, capture = false) {
12274
12635
  return {
@@ -12426,8 +12787,8 @@ function runHookUninstall(options = {}) {
12426
12787
  function settingsCandidates(env, cwd) {
12427
12788
  return [
12428
12789
  { label: "user", file: claudeSettingsPath(env) },
12429
- { label: "project", file: path21.join(cwd, ".claude", "settings.json") },
12430
- { label: "local", file: path21.join(cwd, ".claude", "settings.local.json") }
12790
+ { label: "project", file: path22.join(cwd, ".claude", "settings.json") },
12791
+ { label: "local", file: path22.join(cwd, ".claude", "settings.local.json") }
12431
12792
  ];
12432
12793
  }
12433
12794
  function installedEventsIn(parsed, exists) {
@@ -12457,7 +12818,7 @@ function runHookStatus(options = {}) {
12457
12818
  const env = options.env ?? process.env;
12458
12819
  const log = options.log ?? ((line) => console.log(line));
12459
12820
  const cwd = options.cwd ?? process.cwd();
12460
- const exists = options.exists ?? ((filePath) => fs22.existsSync(filePath));
12821
+ const exists = options.exists ?? ((filePath) => fs23.existsSync(filePath));
12461
12822
  const candidates = options.settingsPath === void 0 ? settingsCandidates(env, cwd) : [{ label: "settings", file: options.settingsPath }];
12462
12823
  log("");
12463
12824
  log("flatbread hook");
@@ -12578,8 +12939,8 @@ function probeHandler(handler, run = spawnSync2) {
12578
12939
  }
12579
12940
 
12580
12941
  // src/skill.ts
12581
- import fs23 from "node:fs";
12582
- import path22 from "node:path";
12942
+ import fs24 from "node:fs";
12943
+ import path23 from "node:path";
12583
12944
  var SKILL_NAME = "flatbread";
12584
12945
  var MARKER = "<!-- written by flatbread setup; flatbread setup --uninstall removes it -->";
12585
12946
  var DESCRIPTION = "Search recorded AI coding-agent runs - this machine's own and public ones on flatbread.io - for one that already did this task or hit this error. Use BEFORE your first edit in any coding task, however small it looks; when a command, test or build fails; when the user says it was done or fixed before; and before you call a change done.";
@@ -12617,28 +12978,57 @@ function homeOf3(env) {
12617
12978
  const home = (process.platform === "win32" ? env["USERPROFILE"] : env["HOME"])?.trim();
12618
12979
  return home ? home : null;
12619
12980
  }
12620
- function skillPaths(env = process.env) {
12981
+ var ANTIGRAVITY_PLUGIN_MANIFEST = `${JSON.stringify({ name: SKILL_NAME }, null, 2)}
12982
+ `;
12983
+ function isDir(dir) {
12984
+ try {
12985
+ return fs24.statSync(dir).isDirectory();
12986
+ } catch {
12987
+ return false;
12988
+ }
12989
+ }
12990
+ function skillTargets(env) {
12621
12991
  const home = homeOf3(env);
12622
12992
  if (home === null) return [];
12623
- const claudeDir = env["CLAUDE_CONFIG_DIR"]?.trim() || path22.join(home, ".claude");
12624
- return [path22.join(claudeDir, "skills", SKILL_NAME, "SKILL.md"), path22.join(home, ".agents", "skills", SKILL_NAME, "SKILL.md")];
12993
+ const claudeDir = env["CLAUDE_CONFIG_DIR"]?.trim() || path23.join(home, ".claude");
12994
+ const targets = [
12995
+ { file: path23.join(claudeDir, "skills", SKILL_NAME, "SKILL.md") },
12996
+ { file: path23.join(home, ".agents", "skills", SKILL_NAME, "SKILL.md") }
12997
+ ];
12998
+ const gemini = path23.join(home, ".gemini");
12999
+ if (["antigravity", "antigravity-cli", "config"].some((dir) => isDir(path23.join(gemini, dir)))) {
13000
+ const plugin = path23.join(gemini, "config", "plugins", SKILL_NAME);
13001
+ targets.push({
13002
+ file: path23.join(plugin, "skills", SKILL_NAME, "SKILL.md"),
13003
+ companion: { file: path23.join(plugin, "plugin.json"), text: ANTIGRAVITY_PLUGIN_MANIFEST }
13004
+ });
13005
+ }
13006
+ const opencode = path23.join(env["XDG_CONFIG_HOME"]?.trim() || path23.join(home, ".config"), "opencode");
13007
+ if (isDir(opencode)) targets.push({ file: path23.join(opencode, "skills", SKILL_NAME, "SKILL.md") });
13008
+ return targets;
13009
+ }
13010
+ function removeIfEmpty(dir) {
13011
+ try {
13012
+ fs24.rmdirSync(dir);
13013
+ } catch {
13014
+ }
12625
13015
  }
12626
13016
  function readOrNull(file) {
12627
13017
  try {
12628
- return fs23.readFileSync(file, "utf8");
13018
+ return fs24.readFileSync(file, "utf8");
12629
13019
  } catch {
12630
13020
  return null;
12631
13021
  }
12632
13022
  }
12633
13023
  function installSkill(options) {
12634
13024
  const text2 = skillText();
12635
- const files = skillPaths(options.env);
12636
- if (files.length === 0) {
13025
+ const targets = skillTargets(options.env ?? process.env);
13026
+ if (targets.length === 0) {
12637
13027
  options.log(` skill skipped: no ${process.platform === "win32" ? "USERPROFILE" : "HOME"} in the environment`);
12638
13028
  return 1;
12639
13029
  }
12640
13030
  let code = 0;
12641
- for (const file of files) {
13031
+ for (const { file, companion } of targets) {
12642
13032
  const current = readOrNull(file);
12643
13033
  const ours = current !== null && current.includes(MARKER);
12644
13034
  if (current !== null && !ours) {
@@ -12655,10 +13045,12 @@ function installSkill(options) {
12655
13045
  options.log(` skill would remove ${file}`);
12656
13046
  continue;
12657
13047
  }
12658
- fs23.rmSync(file);
12659
- try {
12660
- fs23.rmdirSync(path22.dirname(file));
12661
- } catch {
13048
+ fs24.rmSync(file);
13049
+ removeIfEmpty(path23.dirname(file));
13050
+ if (companion !== void 0) {
13051
+ removeIfEmpty(path23.dirname(path23.dirname(file)));
13052
+ if (readOrNull(companion.file) === companion.text) fs24.rmSync(companion.file);
13053
+ removeIfEmpty(path23.dirname(companion.file));
12662
13054
  }
12663
13055
  options.log(` skill removed ${file}`);
12664
13056
  continue;
@@ -12671,14 +13063,68 @@ function installSkill(options) {
12671
13063
  options.log(` skill would ${current === null ? "write" : "update"} ${file}`);
12672
13064
  continue;
12673
13065
  }
12674
- fs23.mkdirSync(path22.dirname(file), { recursive: true });
12675
- fs23.writeFileSync(file, text2);
13066
+ fs24.mkdirSync(path23.dirname(file), { recursive: true });
13067
+ if (companion !== void 0 && readOrNull(companion.file) === null) fs24.writeFileSync(companion.file, companion.text);
13068
+ fs24.writeFileSync(file, text2);
12676
13069
  options.log(` skill ${current === null ? "wrote" : "updated"} ${file}`);
12677
13070
  }
12678
13071
  return code;
12679
13072
  }
12680
13073
 
12681
13074
  // src/setup.ts
13075
+ function isDir2(dir) {
13076
+ try {
13077
+ return fs25.statSync(dir).isDirectory();
13078
+ } catch {
13079
+ return false;
13080
+ }
13081
+ }
13082
+ function otherAgents(env, step) {
13083
+ const { log } = step;
13084
+ const home = homeOf3(env);
13085
+ if (home === null) {
13086
+ log("Other agents");
13087
+ log(" skipped no home directory in the environment");
13088
+ return 1;
13089
+ }
13090
+ const entry = { command: step.execPath, args: [step.entryPath, "mcp"] };
13091
+ let code = 0;
13092
+ log("Codex");
13093
+ const codexHome2 = env["CODEX_HOME"]?.trim() || path24.join(home, ".codex");
13094
+ if (!isDir2(codexHome2)) log(` skipped not installed here (no ${codexHome2})`);
13095
+ else code = Math.max(code, registerCodexMcp({ file: codexConfigPath(codexHome2), ...step }));
13096
+ if (isDir2(codexHome2) && !step.uninstall) log(" skill Codex reads the shared ~/.agents/skills copy above");
13097
+ log("");
13098
+ log("Cursor");
13099
+ const cursorHome = path24.join(home, ".cursor");
13100
+ if (!isDir2(cursorHome)) log(` skipped not installed here (no ${cursorHome})`);
13101
+ else {
13102
+ code = Math.max(code, registerMcpJson({
13103
+ file: path24.join(cursorHome, "mcp.json"),
13104
+ entry,
13105
+ app: "Cursor",
13106
+ scope: "for every project",
13107
+ restart: "restart Cursor so it starts the server",
13108
+ ...step
13109
+ }));
13110
+ }
13111
+ log("");
13112
+ log("Antigravity");
13113
+ const gemini = path24.join(home, ".gemini");
13114
+ if (!["antigravity", "antigravity-cli", "config"].some((dir) => isDir2(path24.join(gemini, dir)))) {
13115
+ log(` skipped not installed here (no ${path24.join(gemini, "antigravity")})`);
13116
+ } else {
13117
+ code = Math.max(code, registerMcpJson({
13118
+ file: path24.join(gemini, "config", "mcp_config.json"),
13119
+ entry,
13120
+ app: "Antigravity",
13121
+ scope: "for Antigravity",
13122
+ restart: "restart Antigravity so it starts the server",
13123
+ ...step
13124
+ }));
13125
+ }
13126
+ return code;
13127
+ }
12682
13128
  function runSetup(options = {}) {
12683
13129
  const env = options.env ?? process.env;
12684
13130
  const log = options.log ?? ((line) => console.log(line));
@@ -12710,20 +13156,18 @@ function runSetup(options = {}) {
12710
13156
  });
12711
13157
  let mcp = 0;
12712
13158
  let skill = 0;
13159
+ const uninstall = options.uninstall === true;
12713
13160
  if (options.noMcp === true) {
12714
- log(" --no-mcp: Claude Code's MCP servers and the flatbread skill were not touched");
12715
- } else if (claude === 0 || options.uninstall === true) {
12716
- mcp = registerClaudeMcp({
12717
- file: options.claudeConfigPath ?? claudeUserConfigPath(env),
12718
- execPath,
12719
- entryPath,
12720
- uninstall: options.uninstall === true,
12721
- dryRun,
12722
- log
12723
- });
12724
- skill = installSkill({ env, dryRun, uninstall: options.uninstall === true, log });
13161
+ log(" --no-mcp: no agent's MCP servers and no flatbread skill were touched");
12725
13162
  } else {
12726
- log(" mcp not registered, because the hook step above was refused");
13163
+ if (claude === 0 || uninstall) {
13164
+ mcp = registerClaudeMcp({ file: options.claudeConfigPath ?? claudeUserConfigPath(env), execPath, entryPath, uninstall, dryRun, log });
13165
+ } else {
13166
+ log(" mcp not registered, because the hook step above was refused");
13167
+ }
13168
+ skill = installSkill({ env, dryRun, uninstall, log });
13169
+ log("");
13170
+ mcp = Math.max(mcp, otherAgents(env, { execPath, entryPath, uninstall, dryRun, log }));
12727
13171
  }
12728
13172
  log("");
12729
13173
  let git2 = 0;
@@ -12974,13 +13418,13 @@ async function bindFirst(server, ports) {
12974
13418
  }
12975
13419
 
12976
13420
  // src/index.ts
12977
- var PACKAGE_ROOT = path23.resolve(path23.dirname(fileURLToPath2(import.meta.url)), "..");
12978
- var SOURCE_CHECKOUT = fs24.existsSync(path23.join(PACKAGE_ROOT, "src", "index.ts"));
12979
- var TMP_DIR = process.env["FLATBREAD_TMP_DIR"]?.trim() || (SOURCE_CHECKOUT ? path23.join(PACKAGE_ROOT, ".tmp") : path23.join(os9.homedir(), ".flatbread"));
12980
- var OUTPUT_PATH = path23.join(TMP_DIR, "last-parse.json");
12981
- var SHARE_OUTPUT_PATH = path23.join(TMP_DIR, "last-share.json");
13421
+ var PACKAGE_ROOT = path25.resolve(path25.dirname(fileURLToPath2(import.meta.url)), "..");
13422
+ var SOURCE_CHECKOUT = fs26.existsSync(path25.join(PACKAGE_ROOT, "src", "index.ts"));
13423
+ var TMP_DIR = process.env["FLATBREAD_TMP_DIR"]?.trim() || (SOURCE_CHECKOUT ? path25.join(PACKAGE_ROOT, ".tmp") : path25.join(os9.homedir(), ".flatbread"));
13424
+ var OUTPUT_PATH = path25.join(TMP_DIR, "last-parse.json");
13425
+ var SHARE_OUTPUT_PATH = path25.join(TMP_DIR, "last-share.json");
12982
13426
  function storeDir() {
12983
- return resolveStoreDir() ?? (SOURCE_CHECKOUT ? null : path23.join(TMP_DIR, DEFAULT_STORE_DIRNAME));
13427
+ return resolveStoreDir() ?? (SOURCE_CHECKOUT ? null : path25.join(TMP_DIR, DEFAULT_STORE_DIRNAME));
12984
13428
  }
12985
13429
  var USAGE = `flatbread - registry of AI agent runs
12986
13430
 
@@ -13036,7 +13480,6 @@ Options for share:
13036
13480
 
13037
13481
  Options for search:
13038
13482
  --harness <id> --category <kind> Only runs from that harness, or of that kind of job
13039
- --recovered Only runs where the agent recovered from a failure
13040
13483
  --local This machine only; the site is not asked
13041
13484
 
13042
13485
  Options for setup:
@@ -13044,7 +13487,7 @@ Options for setup:
13044
13487
  --uninstall Remove the handlers, the MCP server and the post-commit;
13045
13488
  restore a post-commit hook setup had chained
13046
13489
  --no-git Leave the repository's hooks alone
13047
- --no-mcp Leave Claude Code's MCP servers and the flatbread skill alone
13490
+ --no-mcp Leave every agent's MCP servers and the flatbread skill alone
13048
13491
  --settings <path> --no-probe As for hook install
13049
13492
 
13050
13493
  Options for capture:
@@ -13067,8 +13510,8 @@ Options for adapters:
13067
13510
  Notes:
13068
13511
  parse and share record the git branch, HEAD commit and uncommitted file names when the
13069
13512
  session ran inside a git repository. Nothing else from git is read.
13070
- parse --save writes ${path23.relative(process.cwd(), OUTPUT_PATH) || OUTPUT_PATH}
13071
- Share drafts are written to ${path23.relative(process.cwd(), SHARE_OUTPUT_PATH) || SHARE_OUTPUT_PATH}
13513
+ parse --save writes ${path25.relative(process.cwd(), OUTPUT_PATH) || OUTPUT_PATH}
13514
+ Share drafts are written to ${path25.relative(process.cwd(), SHARE_OUTPUT_PATH) || SHARE_OUTPUT_PATH}
13072
13515
  (the latest only; every shared run is also kept under its ID, for search and pull)
13073
13516
  Every parsed run defaults to visibility "private".
13074
13517
  Nothing is uploaded unless you pass --publish, or set FLATBREAD_API_URL and
@@ -13105,10 +13548,10 @@ function parseArgv(argv) {
13105
13548
  } else if (arg === "--out") {
13106
13549
  const value = argv[i + 1];
13107
13550
  if (value === void 0 || value.startsWith("--")) throw new Error("--out needs a file path");
13108
- args.out = path23.resolve(value);
13551
+ args.out = path25.resolve(value);
13109
13552
  i += 1;
13110
13553
  } else if (arg !== void 0 && arg.startsWith("--out=")) {
13111
- args.out = path23.resolve(arg.slice("--out=".length));
13554
+ args.out = path25.resolve(arg.slice("--out=".length));
13112
13555
  } else if (arg === "--harness") {
13113
13556
  const value = argv[i + 1];
13114
13557
  if (value === void 0 || value.startsWith("--")) {
@@ -13229,12 +13672,12 @@ async function runParse(args) {
13229
13672
  target = found[0];
13230
13673
  console.log(`latest ${harness} session: ${target}`);
13231
13674
  } else if (args.file) {
13232
- target = path23.resolve(args.file);
13233
- if (!fs24.existsSync(target)) {
13675
+ target = path25.resolve(args.file);
13676
+ if (!fs26.existsSync(target)) {
13234
13677
  console.error(`file not found: ${target}`);
13235
13678
  return 1;
13236
13679
  }
13237
- if (!fs24.statSync(target).isFile()) {
13680
+ if (!fs26.statSync(target).isFile()) {
13238
13681
  console.error(`not a file: ${target}`);
13239
13682
  return 1;
13240
13683
  }
@@ -13265,8 +13708,8 @@ async function runParse(args) {
13265
13708
  const trace = withLineage(result.trace, readGitLineage(result.trace.source.cwd));
13266
13709
  FlatbreadTraceSchema.parse(trace);
13267
13710
  if (args.out !== void 0) {
13268
- fs24.mkdirSync(path23.dirname(args.out), { recursive: true });
13269
- fs24.writeFileSync(args.out, JSON.stringify(trace, null, 2) + "\n", "utf8");
13711
+ fs26.mkdirSync(path25.dirname(args.out), { recursive: true });
13712
+ fs26.writeFileSync(args.out, JSON.stringify(trace, null, 2) + "\n", "utf8");
13270
13713
  }
13271
13714
  report({ ...result, trace }, args.out);
13272
13715
  return 0;
@@ -13330,8 +13773,8 @@ async function runAdapters(args) {
13330
13773
  }
13331
13774
  let input = null;
13332
13775
  if (args.file !== void 0) {
13333
- const resolved = path23.resolve(args.file);
13334
- if (!fs24.existsSync(resolved)) {
13776
+ const resolved = path25.resolve(args.file);
13777
+ if (!fs26.existsSync(resolved)) {
13335
13778
  console.error(`file not found: ${resolved}`);
13336
13779
  return 1;
13337
13780
  }
@@ -13765,9 +14208,9 @@ async function runPull(args, deps = { ...defaultMcpDeps(storeDir(), bucketDir())
13765
14208
  process.stdout.write(result.capsule);
13766
14209
  return 0;
13767
14210
  }
13768
- const target = path23.resolve(args.out);
13769
- fs24.mkdirSync(path23.dirname(target), { recursive: true });
13770
- fs24.writeFileSync(target, result.capsule, "utf8");
14211
+ const target = path25.resolve(args.out);
14212
+ fs26.mkdirSync(path25.dirname(target), { recursive: true });
14213
+ fs26.writeFileSync(target, result.capsule, "utf8");
13771
14214
  console.error(`wrote ${target}${result.from === "site" ? "" : ` (from this machine's ${result.from === "store" ? "shared runs" : "captured runs"})`}`);
13772
14215
  return 0;
13773
14216
  }
@@ -13790,8 +14233,6 @@ function parseSearchArgv(argv) {
13790
14233
  const value = valueOf("--category");
13791
14234
  if (!TASK_CATEGORIES.includes(value)) throw new Error(`unknown --category "${value}". Known: ${TASK_CATEGORIES.join(", ")}`);
13792
14235
  query.category = value;
13793
- } else if (arg === "--recovered") {
13794
- query.onlyWithRecoveries = true;
13795
14236
  } else if (arg === "--local") {
13796
14237
  local = true;
13797
14238
  } else if (arg.startsWith("--")) {
@@ -14017,9 +14458,9 @@ var invokedDirectly = (() => {
14017
14458
  if (entry === void 0) return false;
14018
14459
  const real = (file) => {
14019
14460
  try {
14020
- return fs24.realpathSync(file);
14461
+ return fs26.realpathSync(file);
14021
14462
  } catch {
14022
- return path23.resolve(file);
14463
+ return path25.resolve(file);
14023
14464
  }
14024
14465
  };
14025
14466
  return real(entry) === real(fileURLToPath2(import.meta.url));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flatbread-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Flatbread: read a coding agent's session off disk, redact it, share it, and pull a run's Context Capsule into another agent.",
5
5
  "type": "module",
6
6
  "bin": {