@onebrain-ai/cli 2.1.0 → 2.1.4

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/dist/onebrain CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import { createRequire } from "node:module";
4
3
  var __create = Object.create;
5
4
  var __getProtoOf = Object.getPrototypeOf;
6
5
  var __defProp = Object.defineProperty;
@@ -28,7 +27,7 @@ var __export = (target, all) => {
28
27
  });
29
28
  };
30
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
31
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
+ var __require = import.meta.require;
32
31
 
33
32
  // node_modules/commander/lib/error.js
34
33
  var require_error = __commonJS((exports) => {
@@ -353,7 +352,7 @@ var require_help = __commonJS((exports) => {
353
352
  return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
354
353
  }
355
354
  wrap(str, width, indent, minColumnWidth = 40) {
356
- const indents = " \\f\\t\\v   -    \uFEFF";
355
+ const indents = " \\f\\t\\v\xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF";
357
356
  const manualIndent = new RegExp(`[\\n][${indents}]+`);
358
357
  if (str.match(manualIndent))
359
358
  return str;
@@ -365,7 +364,7 @@ var require_help = __commonJS((exports) => {
365
364
  `, `
366
365
  `);
367
366
  const indentString = " ".repeat(indent);
368
- const zeroWidthSpace = "";
367
+ const zeroWidthSpace = "\u200B";
369
368
  const breaks = `\\s${zeroWidthSpace}`;
370
369
  const regex = new RegExp(`
371
370
  |.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, "g");
@@ -608,11 +607,11 @@ var require_suggestSimilar = __commonJS((exports) => {
608
607
 
609
608
  // node_modules/commander/lib/command.js
610
609
  var require_command = __commonJS((exports) => {
611
- var EventEmitter = __require("node:events").EventEmitter;
612
- var childProcess = __require("node:child_process");
613
- var path = __require("node:path");
614
- var fs = __require("node:fs");
615
- var process2 = __require("node:process");
610
+ var EventEmitter = __require("events").EventEmitter;
611
+ var childProcess = __require("child_process");
612
+ var path = __require("path");
613
+ var fs = __require("fs");
614
+ var process2 = __require("process");
616
615
  var { Argument, humanReadableArgName } = require_argument();
617
616
  var { CommanderError } = require_error();
618
617
  var { Help } = require_help();
@@ -5365,15 +5364,15 @@ var require_errors = __commonJS((exports) => {
5365
5364
  let lineStr = src.substring(lc.lineStarts[line - 1], lc.lineStarts[line]).replace(/[\n\r]+$/, "");
5366
5365
  if (ci >= 60 && lineStr.length > 80) {
5367
5366
  const trimStart = Math.min(ci - 39, lineStr.length - 79);
5368
- lineStr = "" + lineStr.substring(trimStart);
5367
+ lineStr = "\u2026" + lineStr.substring(trimStart);
5369
5368
  ci -= trimStart - 1;
5370
5369
  }
5371
5370
  if (lineStr.length > 80)
5372
- lineStr = lineStr.substring(0, 79) + "";
5371
+ lineStr = lineStr.substring(0, 79) + "\u2026";
5373
5372
  if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) {
5374
5373
  let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]);
5375
5374
  if (prev.length > 80)
5376
- prev = prev.substring(0, 79) + `…
5375
+ prev = prev.substring(0, 79) + `\u2026
5377
5376
  `;
5378
5377
  lineStr = prev + lineStr;
5379
5378
  }
@@ -6428,8 +6427,8 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
6428
6427
  r: "\r",
6429
6428
  t: "\t",
6430
6429
  v: "\v",
6431
- N: "…",
6432
- _: " ",
6430
+ N: "\x85",
6431
+ _: "\xA0",
6433
6432
  L: "\u2028",
6434
6433
  P: "\u2029",
6435
6434
  " ": " ",
@@ -8904,7 +8903,7 @@ var require_dist = __commonJS((exports) => {
8904
8903
  });
8905
8904
 
8906
8905
  // src/lib/parser.ts
8907
- import { join } from "node:path";
8906
+ import { join } from "path";
8908
8907
  async function loadVaultConfig(vaultRoot) {
8909
8908
  const vaultYmlPath = join(vaultRoot, "vault.yml");
8910
8909
  const file = Bun.file(vaultYmlPath);
@@ -8973,8 +8972,8 @@ var init_parser = __esm(() => {
8973
8972
  });
8974
8973
 
8975
8974
  // src/lib/validator.ts
8976
- import { stat } from "node:fs/promises";
8977
- import { join as join2 } from "node:path";
8975
+ import { stat } from "fs/promises";
8976
+ import { join as join2 } from "path";
8978
8977
  async function checkVaultYml(vaultRoot) {
8979
8978
  const vaultYmlPath = join2(vaultRoot, "vault.yml");
8980
8979
  const file = Bun.file(vaultYmlPath);
@@ -9015,7 +9014,7 @@ async function checkVaultYml(vaultRoot) {
9015
9014
  check: "vault.yml",
9016
9015
  status: "ok",
9017
9016
  message: "valid",
9018
- details: details.length > 0 ? details : undefined
9017
+ ...details.length > 0 ? { details } : {}
9019
9018
  };
9020
9019
  }
9021
9020
  async function checkFolders(vaultRoot, config) {
@@ -9100,7 +9099,7 @@ async function checkQmdEmbeddings(config) {
9100
9099
  if (total === null) {
9101
9100
  return { check: "qmd-embeddings", status: "ok", message: "qmd status unavailable" };
9102
9101
  }
9103
- const summary = `${total} indexed · ${pending} unembedded`;
9102
+ const summary = `${total} indexed \xB7 ${pending} unembedded`;
9104
9103
  if (pending > 0) {
9105
9104
  return {
9106
9105
  check: "qmd-embeddings",
@@ -9267,7 +9266,7 @@ async function checkPluginFiles(vaultRoot) {
9267
9266
  check: "plugin-files",
9268
9267
  status: "ok",
9269
9268
  message: "all required files present",
9270
- details: [`${skillCount} skills · ${agentCount} agents · INSTRUCTIONS.md ✓`]
9269
+ details: [`${skillCount} skills \xB7 ${agentCount} agents \xB7 INSTRUCTIONS.md \u2713`]
9271
9270
  };
9272
9271
  }
9273
9272
  async function checkVaultYmlKeys(vaultRoot) {
@@ -9338,7 +9337,7 @@ async function checkVaultYmlKeys(vaultRoot) {
9338
9337
  check: "vault.yml-keys",
9339
9338
  status: "error",
9340
9339
  message: `${errors.length} error(s)`,
9341
- hint,
9340
+ ...hint !== undefined ? { hint } : {},
9342
9341
  details: hint ? [...errors, hint] : errors
9343
9342
  };
9344
9343
  }
@@ -9349,7 +9348,7 @@ async function checkVaultYmlKeys(vaultRoot) {
9349
9348
  check: "vault.yml-keys",
9350
9349
  status: "warn",
9351
9350
  message: `${warnings.length} issue(s)`,
9352
- hint,
9351
+ ...hint !== undefined ? { hint } : {},
9353
9352
  details: hint ? [...warnings, hint] : warnings
9354
9353
  };
9355
9354
  }
@@ -9392,14 +9391,14 @@ async function checkSettingsHooks(vaultRoot, config) {
9392
9391
  if (!hookPresent(settings, event, cmdSubstring)) {
9393
9392
  warnings.push(`${event} hook missing`);
9394
9393
  } else {
9395
- confirmedHooks.push(`${event} ✓`);
9394
+ confirmedHooks.push(`${event} \u2713`);
9396
9395
  }
9397
9396
  }
9398
9397
  if (config.qmd_collection) {
9399
9398
  if (!hookPresent(settings, "PostToolUse", QMD_HOOK_SUBSTRING)) {
9400
9399
  warnings.push("PostToolUse (qmd) hook missing");
9401
9400
  } else {
9402
- confirmedHooks.push("PostToolUse ");
9401
+ confirmedHooks.push("PostToolUse \u2713");
9403
9402
  }
9404
9403
  }
9405
9404
  const precompactGroups = settings.hooks?.["PreCompact"] ?? [];
@@ -9438,12 +9437,12 @@ async function checkSettingsHooks(vaultRoot, config) {
9438
9437
  if (confirmedHooks.length > 0)
9439
9438
  okDetails.push(`hooks: ${confirmedHooks.join(" ")}`);
9440
9439
  if (permissionOk)
9441
- okDetails.push("permissions: Bash(onebrain *) ");
9440
+ okDetails.push("permissions: Bash(onebrain *) \u2713");
9442
9441
  return {
9443
9442
  check: "settings-hooks",
9444
9443
  status: "ok",
9445
9444
  message: "hooks ok",
9446
- details: okDetails.length > 0 ? okDetails : undefined
9445
+ ...okDetails.length > 0 ? { details: okDetails } : {}
9447
9446
  };
9448
9447
  }
9449
9448
  var import_yaml2, STANDARD_FOLDER_KEYS, REQUIRED_PLUGIN_FILES, REQUIRED_PLUGIN_DIRS, STALE_BASH_FILES, REQUIRED_VAULT_YML_KEYS, REQUIRED_FOLDER_KEYS, REQUIRED_HOOKS, QMD_HOOK_SUBSTRING = "onebrain qmd-reindex", PRECOMPACT_ONEBRAIN_SUBSTRING = "onebrain", REQUIRED_PERMISSION = "Bash(onebrain *)", STALE_HOOK_SUBSTRINGS;
@@ -9498,8 +9497,8 @@ var init_lib = __esm(() => {
9498
9497
  var require_package = __commonJS((exports, module) => {
9499
9498
  module.exports = {
9500
9499
  name: "@onebrain-ai/cli",
9501
- version: "2.1.0",
9502
- description: "CLI for OneBrain personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9500
+ version: "2.1.4",
9501
+ description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9503
9502
  keywords: [
9504
9503
  "onebrain",
9505
9504
  "obsidian",
@@ -9524,9 +9523,11 @@ var require_package = __commonJS((exports, module) => {
9524
9523
  bin: {
9525
9524
  onebrain: "dist/onebrain"
9526
9525
  },
9527
- files: ["dist/onebrain"],
9526
+ files: ["dist/onebrain", "dist/postinstall.js"],
9528
9527
  scripts: {
9529
- build: "bun build src/index.ts --outfile dist/onebrain --target node",
9528
+ build: "bun build src/index.ts --outfile dist/onebrain --target bun",
9529
+ "build:postinstall": "bun build src/scripts/postinstall.ts --outfile dist/postinstall.js --target node",
9530
+ postinstall: "node dist/postinstall.js",
9530
9531
  test: "bun test --pass-with-no-tests src/",
9531
9532
  typecheck: "tsc --noEmit"
9532
9533
  },
@@ -9545,6 +9546,129 @@ var require_package = __commonJS((exports, module) => {
9545
9546
  };
9546
9547
  });
9547
9548
 
9549
+ // src/commands/internal/cli-ui.ts
9550
+ function out(str) {
9551
+ process.stdout.write(Buffer.from(str, "utf8"));
9552
+ }
9553
+ function writeLine(msg) {
9554
+ out(`${msg}
9555
+ `);
9556
+ }
9557
+ function barLine(msg) {
9558
+ out(`${bar} ${msg}
9559
+ `);
9560
+ }
9561
+ function barBlank() {
9562
+ out(`${bar}
9563
+ `);
9564
+ }
9565
+ function close(msg, isError = false, isWarning = false) {
9566
+ if (isError) {
9567
+ out(`${import_picocolors2.default.cyan("\u2514")} ${import_picocolors2.default.bold(import_picocolors2.default.red(msg))}
9568
+ `);
9569
+ } else if (isWarning) {
9570
+ out(`${import_picocolors2.default.cyan("\u2514")} ${import_picocolors2.default.yellow(msg)}
9571
+ `);
9572
+ } else {
9573
+ out(`${import_picocolors2.default.cyan("\u2514")} ${msg}
9574
+ `);
9575
+ }
9576
+ }
9577
+ function dotLine(emoji, label) {
9578
+ out(`${dot} ${emoji} ${label}
9579
+ `);
9580
+ }
9581
+ function makeStepFn(isTTY) {
9582
+ return function createStep(emoji, label) {
9583
+ if (!isTTY)
9584
+ return null;
9585
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9586
+ let i = 0;
9587
+ out(`${import_picocolors2.default.green(frames[0])} ${emoji} ${label}\u2026
9588
+ `);
9589
+ const timer = setInterval(() => {
9590
+ i = (i + 1) % frames.length;
9591
+ out(`\x1B[1A\x1B[2K${import_picocolors2.default.green(frames[i])} ${emoji} ${label}\u2026
9592
+ `);
9593
+ }, 80);
9594
+ return {
9595
+ stop(result, details) {
9596
+ clearInterval(timer);
9597
+ process.stdout.write(Buffer.from("\x1B[1A\x1B[2K", "utf8"));
9598
+ out(`${dot} ${emoji} ${label}
9599
+ `);
9600
+ if (result !== undefined)
9601
+ barLine(result);
9602
+ if (details)
9603
+ for (const d of details)
9604
+ barLine(` \xB7 ${d}`);
9605
+ barBlank();
9606
+ }
9607
+ };
9608
+ };
9609
+ }
9610
+ async function askYesNo(question) {
9611
+ out(`${import_picocolors2.default.cyan("\u25C6")} ${question}
9612
+ `);
9613
+ process.stdout.write(Buffer.from("\x1B[?25l", "utf8"));
9614
+ function renderOptions(yes) {
9615
+ const yesLabel = yes ? `${import_picocolors2.default.bold(import_picocolors2.default.green("\u25CF"))} Yes` : `${import_picocolors2.default.dim("\u25CB")} Yes`;
9616
+ const noLabel = yes ? `${import_picocolors2.default.dim("\u25CB")} No` : `${import_picocolors2.default.bold(import_picocolors2.default.green("\u25CF"))} No`;
9617
+ out(`\x1B[2K${bar} ${yesLabel} / ${noLabel}\r`);
9618
+ }
9619
+ const answer = await new Promise((resolve) => {
9620
+ let selected = true;
9621
+ renderOptions(selected);
9622
+ const { stdin } = process;
9623
+ const wasRaw = stdin.isTTY ? stdin.isRaw : false;
9624
+ if (stdin.isTTY)
9625
+ stdin.setRawMode(true);
9626
+ stdin.resume();
9627
+ function onData(buf) {
9628
+ const key = buf.toString();
9629
+ if (key === "\x03") {
9630
+ stdin.removeListener("data", onData);
9631
+ if (stdin.isTTY)
9632
+ stdin.setRawMode(wasRaw);
9633
+ stdin.pause();
9634
+ resolve(null);
9635
+ } else if (key === "\r" || key === `
9636
+ `) {
9637
+ stdin.removeListener("data", onData);
9638
+ if (stdin.isTTY)
9639
+ stdin.setRawMode(wasRaw);
9640
+ stdin.pause();
9641
+ resolve(selected);
9642
+ } else if (key === "y" || key === "Y") {
9643
+ stdin.removeListener("data", onData);
9644
+ if (stdin.isTTY)
9645
+ stdin.setRawMode(wasRaw);
9646
+ stdin.pause();
9647
+ resolve(true);
9648
+ } else if (key === "n" || key === "N") {
9649
+ stdin.removeListener("data", onData);
9650
+ if (stdin.isTTY)
9651
+ stdin.setRawMode(wasRaw);
9652
+ stdin.pause();
9653
+ resolve(false);
9654
+ } else if (key === "\x1B[C" || key === "\x1B[D" || key === "\t") {
9655
+ selected = !selected;
9656
+ renderOptions(selected);
9657
+ }
9658
+ }
9659
+ stdin.on("data", onData);
9660
+ });
9661
+ process.stdout.write(Buffer.from(`
9662
+ \x1B[?25h\x1B[1A\x1B[2K`, "utf8"));
9663
+ return answer;
9664
+ }
9665
+ var import_picocolors2, bar, dot;
9666
+ var init_cli_ui = __esm(() => {
9667
+ import_picocolors2 = __toESM(require_picocolors(), 1);
9668
+ bar = import_picocolors2.default.cyan("\u2502");
9669
+ dot = import_picocolors2.default.green("\u25CF");
9670
+ });
9671
+
9548
9672
  // node_modules/sisteransi/src/index.js
9549
9673
  var require_src = __commonJS((exports, module) => {
9550
9674
  var ESC = "\x1B";
@@ -9604,8 +9728,8 @@ var require_src = __commonJS((exports, module) => {
9604
9728
  });
9605
9729
 
9606
9730
  // node_modules/@clack/core/dist/index.mjs
9607
- import { stdin as $, stdout as j } from "node:process";
9608
- import * as f from "node:readline";
9731
+ import { stdin as $, stdout as j } from "process";
9732
+ import * as f from "readline";
9609
9733
  function J({ onlyFirst: t = false } = {}) {
9610
9734
  const F = ["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"].join("|");
9611
9735
  return new RegExp(F, t ? undefined : "g");
@@ -9724,7 +9848,7 @@ var init_dist = __esm(() => {
9724
9848
  eD = Object.keys(r.bgColor);
9725
9849
  [...tD, ...eD];
9726
9850
  iD = sD();
9727
- v = new Set(["\x1B", "›"]);
9851
+ v = new Set(["\x1B", "\x9B"]);
9728
9852
  y = `${rD}8;;`;
9729
9853
  aD = ["up", "down", "left", "right", "space", "enter", "cancel"];
9730
9854
  c = { actions: new Set(aD), aliases: new Map([["k", "up"], ["j", "down"], ["h", "left"], ["l", "right"], ["\x03", "cancel"], ["escape", "cancel"]]) };
@@ -9733,7 +9857,7 @@ var init_dist = __esm(() => {
9733
9857
  });
9734
9858
 
9735
9859
  // node_modules/@clack/prompts/dist/index.mjs
9736
- import p from "node:process";
9860
+ import p from "process";
9737
9861
  function X2() {
9738
9862
  return p.platform !== "win32" ? p.env.TERM !== "linux" : !!p.env.CI || !!p.env.WT_SESSION || !!p.env.TERMINUS_SUBLIME || p.env.ConEmuTask === "{cmd::Cmder}" || p.env.TERM_PROGRAM === "Terminus-Sublime" || p.env.TERM_PROGRAM === "vscode" || p.env.TERM === "xterm-256color" || p.env.TERM === "alacritty" || p.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
9739
9863
  }
@@ -9746,7 +9870,7 @@ ${import_picocolors3.default.gray(m2)} ${s}
9746
9870
 
9747
9871
  `);
9748
9872
  }, L2 = () => {
9749
- const s = E ? ["", "", "", ""] : ["", "o", "O", "0"], n = E ? 80 : 120, t = process.env.CI === "true";
9873
+ const s = E ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], n = E ? 80 : 120, t = process.env.CI === "true";
9750
9874
  let i, r2, c2 = false, o = "", l2;
9751
9875
  const $2 = (h) => {
9752
9876
  const g = h > 1 ? "Something went wrong" : "Canceled";
@@ -9790,32 +9914,32 @@ var init_dist2 = __esm(() => {
9790
9914
  import_picocolors3 = __toESM(require_picocolors(), 1);
9791
9915
  import_sisteransi2 = __toESM(require_src(), 1);
9792
9916
  E = X2();
9793
- ee = u("", "*");
9794
- A = u("", "x");
9795
- B = u("", "x");
9796
- S2 = u("", "o");
9797
- te = u("", "T");
9798
- a = u("", "|");
9799
- m2 = u("", "");
9800
- j2 = u("", ">");
9801
- R = u("", " ");
9802
- V = u("", "[]");
9803
- M = u("", "[+]");
9804
- G = u("", "[ ]");
9805
- se = u("", "");
9806
- N2 = u("", "-");
9807
- re = u("", "+");
9808
- ie = u("", "+");
9809
- ne = u("", "+");
9810
- ae = u("", "");
9811
- oe = u("", "*");
9812
- ce = u("", "!");
9813
- le = u("", "x");
9917
+ ee = u("\u25C6", "*");
9918
+ A = u("\u25A0", "x");
9919
+ B = u("\u25B2", "x");
9920
+ S2 = u("\u25C7", "o");
9921
+ te = u("\u250C", "T");
9922
+ a = u("\u2502", "|");
9923
+ m2 = u("\u2514", "\u2014");
9924
+ j2 = u("\u25CF", ">");
9925
+ R = u("\u25CB", " ");
9926
+ V = u("\u25FB", "[\u2022]");
9927
+ M = u("\u25FC", "[+]");
9928
+ G = u("\u25FB", "[ ]");
9929
+ se = u("\u25AA", "\u2022");
9930
+ N2 = u("\u2500", "-");
9931
+ re = u("\u256E", "+");
9932
+ ie = u("\u251C", "+");
9933
+ ne = u("\u256F", "+");
9934
+ ae = u("\u25CF", "\u2022");
9935
+ oe = u("\u25C6", "*");
9936
+ ce = u("\u25B2", "!");
9937
+ le = u("\u25A0", "x");
9814
9938
  });
9815
9939
 
9816
9940
  // src/commands/internal/harness.ts
9817
- import { stat as stat2 } from "node:fs/promises";
9818
- import { join as join3 } from "node:path";
9941
+ import { stat as stat2 } from "fs/promises";
9942
+ import { join as join3 } from "path";
9819
9943
  async function pathExists(p2) {
9820
9944
  try {
9821
9945
  await stat2(p2);
@@ -9833,7 +9957,7 @@ async function detectHarness(vaultRoot) {
9833
9957
  return "gemini";
9834
9958
  if (env === "direct")
9835
9959
  return "direct";
9836
- process.stderr.write(`harness: unknown ONEBRAIN_HARNESS value "${env}" ignoring, falling back to directory detection
9960
+ process.stderr.write(`harness: unknown ONEBRAIN_HARNESS value "${env}" \u2014 ignoring, falling back to directory detection
9837
9961
  `);
9838
9962
  }
9839
9963
  if (await pathExists(join3(vaultRoot, ".gemini")))
@@ -9850,9 +9974,9 @@ __export(exports_register_hooks, {
9850
9974
  runRegisterHooks: () => runRegisterHooks,
9851
9975
  registerHooksCommand: () => registerHooksCommand
9852
9976
  });
9853
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
9854
- import { homedir } from "node:os";
9855
- import { dirname, join as join4 } from "node:path";
9977
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
9978
+ import { homedir } from "os";
9979
+ import { dirname, join as join4 } from "path";
9856
9980
  async function readSettings(settingsPath) {
9857
9981
  try {
9858
9982
  const text = await readFile(settingsPath, "utf8");
@@ -10029,11 +10153,11 @@ async function runRegisterHooks(opts = {}) {
10029
10153
  if (isTTY) {
10030
10154
  const parts = HOOK_EVENTS.map((e2) => {
10031
10155
  const status = result.hooks[e2];
10032
- const icon = import_picocolors4.default.green(status === "ok" ? "" : status === "migrated" ? "" : "+");
10156
+ const icon = import_picocolors4.default.green(status === "ok" ? "\u2713" : status === "migrated" ? "\u2191" : "+");
10033
10157
  return `${import_picocolors4.default.dim(e2)} ${icon}`;
10034
10158
  });
10035
10159
  if (qmdStatus)
10036
- parts.push(`${import_picocolors4.default.dim("PostToolUse")} ${import_picocolors4.default.green(qmdStatus === "ok" ? "" : "+")}`);
10160
+ parts.push(`${import_picocolors4.default.dim("PostToolUse")} ${import_picocolors4.default.green(qmdStatus === "ok" ? "\u2713" : "+")}`);
10037
10161
  hooksSpinner?.stop(`Hooks ${parts.join(" ")}`);
10038
10162
  } else {
10039
10163
  const hookLine = HOOK_EVENTS.map((e2) => {
@@ -10129,9 +10253,9 @@ import {
10129
10253
  stat as stat3,
10130
10254
  unlink,
10131
10255
  writeFile as writeFile2
10132
- } from "node:fs/promises";
10133
- import { homedir as homedir2, tmpdir } from "node:os";
10134
- import { dirname as dirname2, join as join5, relative } from "node:path";
10256
+ } from "fs/promises";
10257
+ import { homedir as homedir2, tmpdir } from "os";
10258
+ import { dirname as dirname2, join as join5, relative } from "path";
10135
10259
  function resolveBranch(updateChannel) {
10136
10260
  return updateChannel === "stable" ? "main" : "next";
10137
10261
  }
@@ -10140,9 +10264,9 @@ async function downloadTarball(branch, fetchFn) {
10140
10264
  const response = await fetchFn(url);
10141
10265
  if (!response.ok) {
10142
10266
  const hints = {
10143
- 403: " check repo permissions or GITHUB_TOKEN",
10144
- 404: " repo or branch not found",
10145
- 429: " rate limited, wait and retry"
10267
+ 403: " \u2014 check repo permissions or GITHUB_TOKEN",
10268
+ 404: " \u2014 repo or branch not found",
10269
+ 429: " \u2014 rate limited, wait and retry"
10146
10270
  };
10147
10271
  const hint = hints[response.status] ?? "";
10148
10272
  throw new Error(`HTTP ${response.status} downloading tarball from ${url}${hint}`);
@@ -10461,35 +10585,47 @@ async function runVaultSync(vaultRoot, opts = {}) {
10461
10585
  pinSkipped: true,
10462
10586
  cacheRemoved: 0
10463
10587
  };
10588
+ const embedded = opts.embedded ?? false;
10589
+ const createEmbeddedStep = embedded ? makeStepFn(true) : null;
10464
10590
  let s = null;
10465
- function startSpinner(msg) {
10591
+ let currentStep = null;
10592
+ function startSpinner(emoji, label) {
10466
10593
  if (isTTY) {
10467
- s = L2();
10468
- s.start(msg);
10594
+ if (embedded) {
10595
+ currentStep = createEmbeddedStep(emoji, label);
10596
+ } else {
10597
+ s = L2();
10598
+ s.start(label);
10599
+ }
10469
10600
  } else {
10470
- process.stdout.write(`vault-sync: ${msg}
10601
+ process.stdout.write(`vault-sync: ${label}
10471
10602
  `);
10472
10603
  }
10473
10604
  }
10474
- function stopSpinner(msg) {
10475
- if (isTTY && s) {
10476
- s.stop(msg);
10477
- s = null;
10605
+ function stopSpinner(result2, details) {
10606
+ if (isTTY) {
10607
+ if (embedded) {
10608
+ currentStep?.stop(import_picocolors6.default.dim(result2), details);
10609
+ currentStep = null;
10610
+ } else if (s) {
10611
+ s.stop(result2);
10612
+ s = null;
10613
+ }
10478
10614
  }
10479
10615
  }
10480
- if (isTTY) {
10616
+ if (isTTY && !embedded) {
10481
10617
  we("OneBrain Vault Sync");
10482
10618
  }
10483
10619
  let tmpDir = null;
10484
10620
  try {
10485
- startSpinner("Downloading tarball...");
10621
+ startSpinner("\uD83D\uDCE5", "Downloading");
10486
10622
  let extractedDir;
10487
10623
  try {
10488
10624
  const dl = await downloadTarball(branch, fetchFn);
10489
10625
  tmpDir = dl.tmpDir;
10490
10626
  extractedDir = await extractTarball(dl.tarball, tmpDir);
10491
10627
  } catch (err) {
10492
- stopSpinner("Download failed");
10628
+ stopSpinner("download failed");
10493
10629
  const msg = err instanceof Error ? err.message : String(err);
10494
10630
  result.error = msg;
10495
10631
  process.stderr.write(`vault-sync: download failed: ${msg}
@@ -10504,13 +10640,13 @@ async function runVaultSync(vaultRoot, opts = {}) {
10504
10640
  }
10505
10641
  } catch {}
10506
10642
  stopSpinner(`kengio/onebrain@${branch} (v${result.version})`);
10507
- startSpinner("Syncing plugin files...");
10643
+ startSpinner("\uD83D\uDCC2", "Syncing files");
10508
10644
  try {
10509
10645
  const { filesAdded, filesRemoved } = await syncPluginFiles(extractedDir, vaultRoot, unlinkFn);
10510
10646
  result.filesAdded = filesAdded;
10511
10647
  result.filesRemoved = filesRemoved;
10512
10648
  } catch (err) {
10513
- stopSpinner("Plugin sync failed");
10649
+ stopSpinner("plugin sync failed");
10514
10650
  const msg = err instanceof Error ? err.message : String(err);
10515
10651
  result.error = msg;
10516
10652
  process.stderr.write(`vault-sync: plugin sync failed: ${msg}
@@ -10533,13 +10669,13 @@ async function runVaultSync(vaultRoot, opts = {}) {
10533
10669
  } catch {}
10534
10670
  }
10535
10671
  await copyRootDocs(extractedDir, vaultRoot);
10536
- startSpinner("Updating harness files...");
10672
+ startSpinner("\uD83D\uDD27", "Updating harness");
10537
10673
  let importsAdded = 0;
10538
10674
  try {
10539
10675
  importsAdded = await mergeHarnessFiles(extractedDir, vaultRoot);
10540
10676
  result.importsAdded = importsAdded;
10541
10677
  } catch (err) {
10542
- stopSpinner("Harness merge failed");
10678
+ stopSpinner("harness merge failed");
10543
10679
  const msg = err instanceof Error ? err.message : String(err);
10544
10680
  result.error = msg;
10545
10681
  process.stderr.write(`vault-sync: harness merge failed: ${msg}
@@ -10561,23 +10697,23 @@ async function runVaultSync(vaultRoot, opts = {}) {
10561
10697
  return result;
10562
10698
  }
10563
10699
  if (harness === "claude") {
10564
- startSpinner("Pinning to vault...");
10700
+ startSpinner("\uD83D\uDCCC", "Pinning to vault");
10565
10701
  try {
10566
10702
  const pinResult = await pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir);
10567
10703
  result.pinSkipped = pinResult.skipped;
10568
10704
  if (pinResult.skipped) {
10569
10705
  stopSpinner("pin skipped (not found or marketplace)");
10570
10706
  } else {
10571
- stopSpinner("installPath .claude/plugins/onebrain");
10707
+ stopSpinner("installPath \u2192 .claude/plugins/onebrain");
10572
10708
  }
10573
10709
  } catch (err) {
10574
10710
  const msg = err instanceof Error ? err.message : String(err);
10575
10711
  process.stderr.write(`vault-sync: pin warning: ${msg}
10576
10712
  `);
10577
10713
  result.pinSkipped = true;
10578
- stopSpinner("pin skipped (error non-fatal)");
10714
+ stopSpinner("pin skipped (error \u2014 non-fatal)");
10579
10715
  }
10580
- startSpinner("Cleaning cache...");
10716
+ startSpinner("\uD83E\uDDF9", "Cleaning cache");
10581
10717
  try {
10582
10718
  const cacheRemoved = await cleanPluginCache(installedPluginsPath, installedPluginsCacheDir);
10583
10719
  result.cacheRemoved = cacheRemoved;
@@ -10590,12 +10726,14 @@ async function runVaultSync(vaultRoot, opts = {}) {
10590
10726
  const msg = err instanceof Error ? err.message : String(err);
10591
10727
  process.stderr.write(`vault-sync: cache clean warning: ${msg}
10592
10728
  `);
10593
- stopSpinner("cache clean skipped (error non-fatal)");
10729
+ stopSpinner("cache clean skipped (error \u2014 non-fatal)");
10594
10730
  }
10595
10731
  }
10596
10732
  result.ok = true;
10597
10733
  if (isTTY) {
10598
- fe(`Done — v${result.version} synced`);
10734
+ if (!embedded) {
10735
+ fe(`Done \u2014 v${result.version} synced`);
10736
+ }
10599
10737
  } else {
10600
10738
  process.stdout.write(`vault-sync: done
10601
10739
  `);
@@ -10613,16 +10751,18 @@ async function vaultSyncCommand(vaultRoot, opts = {}) {
10613
10751
  process.exit(1);
10614
10752
  }
10615
10753
  }
10616
- var import_yaml3;
10754
+ var import_picocolors6, import_yaml3;
10617
10755
  var init_vault_sync = __esm(() => {
10618
10756
  init_dist2();
10757
+ init_cli_ui();
10619
10758
  init_harness();
10759
+ import_picocolors6 = __toESM(require_picocolors(), 1);
10620
10760
  import_yaml3 = __toESM(require_dist(), 1);
10621
10761
  });
10622
10762
 
10623
10763
  // src/index.ts
10624
10764
  import { existsSync } from "fs";
10625
- import { dirname as dirname6, join as join14 } from "path";
10765
+ import { dirname as dirname4, join as join11 } from "path";
10626
10766
 
10627
10767
  // node_modules/commander/esm.mjs
10628
10768
  var import__ = __toESM(require_commander(), 1);
@@ -10648,7 +10788,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
10648
10788
  var import_picocolors = __toESM(require_picocolors(), 1);
10649
10789
  function resolveBinaryVersion() {
10650
10790
  if (true)
10651
- return "2.1.0";
10791
+ return "2.1.4";
10652
10792
  try {
10653
10793
  const pkg = require_package();
10654
10794
  return pkg.version ?? "dev";
@@ -10657,11 +10797,11 @@ function resolveBinaryVersion() {
10657
10797
  }
10658
10798
  }
10659
10799
  var ART_LINES = [
10660
- ` ◆${"".repeat(25)}◆`,
10661
- " ┌─┐┌┐╷┌─╴┌┐ ┌─┐┌─┐╷┌┐╷",
10662
- " ││└┤├╴ ├┴┐├┬┘├─┤││└┤",
10663
- " └─┘╵ ╵└─╴└─┘╵└╴╵ ╵╵╵ ",
10664
- ` ◆${"".repeat(25)}◆`
10800
+ ` \u25C6${"\u2500".repeat(25)}\u25C6`,
10801
+ " \u250C\u2500\u2510\u250C\u2510\u2577\u250C\u2500\u2574\u250C\u2510 \u250C\u2500\u2510\u250C\u2500\u2510\u2577\u250C\u2510\u2577",
10802
+ " \u2502 \u2502\u2502\u2514\u2524\u251C\u2574 \u251C\u2534\u2510\u251C\u252C\u2518\u251C\u2500\u2524\u2502\u2502\u2514\u2524",
10803
+ " \u2514\u2500\u2518\u2575 \u2575\u2514\u2500\u2574\u2514\u2500\u2518\u2575\u2514\u2574\u2575 \u2575\u2575\u2575 \u2575",
10804
+ ` \u25C6${"\u2500".repeat(25)}\u25C6`
10665
10805
  ];
10666
10806
  var TAGLINE = "Your AI Thinking Partner";
10667
10807
  var BANNER_LINE_COUNT = 1 + ART_LINES.length + 3;
@@ -10713,39 +10853,42 @@ function scanLine(line) {
10713
10853
  function dimLine(line) {
10714
10854
  return line.split("").map((ch) => ch === " " ? ch : `\x1B[2;38;2;50;50;70m${ch}\x1B[0m`).join("");
10715
10855
  }
10856
+ function outb(str) {
10857
+ process.stdout.write(Buffer.from(str, "utf8"));
10858
+ }
10716
10859
  function printFrame(artLines, tagline) {
10717
- process.stdout.write(`
10860
+ outb(`
10718
10861
  `);
10719
10862
  for (const l of artLines)
10720
- process.stdout.write(`${l}
10863
+ outb(`${l}
10721
10864
  `);
10722
- process.stdout.write(`
10865
+ outb(`
10723
10866
  `);
10724
- process.stdout.write(`${tagline}
10867
+ outb(`${tagline}
10725
10868
  `);
10726
- process.stdout.write(`
10869
+ outb(`
10727
10870
  `);
10728
10871
  }
10729
10872
  async function printBanner() {
10730
10873
  if (!process.stdout.isTTY)
10731
10874
  return;
10732
10875
  const delay = (ms) => new Promise((r) => setTimeout(r, ms));
10733
- const up = (n) => process.stdout.write(`\x1B[${n}F`);
10876
+ const up = (n) => outb(`\x1B[${n}F`);
10734
10877
  if (!supportsRgb()) {
10735
- process.stdout.write(`
10878
+ outb(`
10736
10879
  `);
10737
10880
  for (const l of ART_LINES)
10738
- process.stdout.write(`${import_picocolors.default.bold(import_picocolors.default.cyan(l))}
10881
+ outb(`${import_picocolors.default.bold(import_picocolors.default.cyan(l))}
10739
10882
  `);
10740
- process.stdout.write(`
10883
+ outb(`
10741
10884
  `);
10742
- process.stdout.write(` ${import_picocolors.default.bold(import_picocolors.default.magenta(TAGLINE))}
10885
+ outb(` ${import_picocolors.default.bold(import_picocolors.default.magenta(TAGLINE))}
10743
10886
  `);
10744
- process.stdout.write(`
10887
+ outb(`
10745
10888
  `);
10746
10889
  return;
10747
10890
  }
10748
- process.stdout.write("\x1B[?25l");
10891
+ outb("\x1B[?25l");
10749
10892
  try {
10750
10893
  let diagFrame = function(highlight) {
10751
10894
  return ART_LINES.map((line, row) => line.split("").map((ch, col) => {
@@ -10797,7 +10940,7 @@ async function printBanner() {
10797
10940
  up(BANNER_LINE_COUNT);
10798
10941
  printFrame(ART_LINES.map((l, i) => neonLine(l, i)), ` ${" ".repeat(TAGLINE.length)}`);
10799
10942
  await delay(200);
10800
- const cursor = `\x1B[1;38;2;140;255;255m▌\x1B[0m`;
10943
+ const cursor = "\x1B[1;38;2;140;255;255m\u258C\x1B[0m";
10801
10944
  for (let len = 1;len <= TAGLINE.length; len++) {
10802
10945
  await delay(32);
10803
10946
  up(BANNER_LINE_COUNT);
@@ -10818,126 +10961,12 @@ async function printBanner() {
10818
10961
  up(BANNER_LINE_COUNT);
10819
10962
  printFrame(ART_LINES.map((l, i) => neonLine(l, i)), ` ${import_picocolors.default.bold(import_picocolors.default.magenta(TAGLINE))}\x1B[K`);
10820
10963
  } finally {
10821
- process.stdout.write("\x1B[?25h");
10822
- }
10823
- }
10824
-
10825
- // src/commands/internal/cli-ui.ts
10826
- var import_picocolors2 = __toESM(require_picocolors(), 1);
10827
- var bar = import_picocolors2.default.cyan("│");
10828
- var dot = import_picocolors2.default.green("●");
10829
- function writeLine(msg) {
10830
- process.stdout.write(`${msg}
10831
- `);
10832
- }
10833
- function barLine(msg) {
10834
- process.stdout.write(`${bar} ${msg}
10835
- `);
10836
- }
10837
- function barBlank() {
10838
- process.stdout.write(`${bar}
10839
- `);
10840
- }
10841
- function close(msg, isError = false, isWarning = false) {
10842
- if (isError) {
10843
- process.stdout.write(`${import_picocolors2.default.cyan("└")} ${import_picocolors2.default.bold(import_picocolors2.default.red(msg))}
10844
- `);
10845
- } else if (isWarning) {
10846
- process.stdout.write(`${import_picocolors2.default.cyan("└")} ${import_picocolors2.default.yellow(msg)}
10847
- `);
10848
- } else {
10849
- process.stdout.write(`${import_picocolors2.default.cyan("└")} ${msg}
10850
- `);
10851
- }
10852
- }
10853
- function makeStepFn(isTTY) {
10854
- return function createStep(emoji, label) {
10855
- if (!isTTY)
10856
- return null;
10857
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
10858
- let i = 0;
10859
- process.stdout.write(`${import_picocolors2.default.green(frames[0])} ${emoji} ${label}…
10860
- `);
10861
- const timer = setInterval(() => {
10862
- i = (i + 1) % frames.length;
10863
- process.stdout.write(`\x1B[1A\x1B[2K${import_picocolors2.default.green(frames[i])} ${emoji} ${label}…
10864
- `);
10865
- }, 80);
10866
- return {
10867
- stop(result, details) {
10868
- clearInterval(timer);
10869
- process.stdout.write("\x1B[1A\x1B[2K");
10870
- process.stdout.write(`${dot} ${emoji} ${label}
10871
- `);
10872
- if (result !== undefined)
10873
- barLine(result);
10874
- if (details)
10875
- for (const d of details)
10876
- barLine(` · ${d}`);
10877
- barBlank();
10878
- }
10879
- };
10880
- };
10881
- }
10882
- async function askYesNo(question) {
10883
- process.stdout.write(`${import_picocolors2.default.cyan("◆")} ${question}
10884
- `);
10885
- process.stdout.write("\x1B[?25l");
10886
- function renderOptions(yes) {
10887
- const yesLabel = yes ? `${import_picocolors2.default.bold(import_picocolors2.default.green("●"))} Yes` : `${import_picocolors2.default.dim("○")} Yes`;
10888
- const noLabel = yes ? `${import_picocolors2.default.dim("○")} No` : `${import_picocolors2.default.bold(import_picocolors2.default.green("●"))} No`;
10889
- process.stdout.write(`\x1B[2K${bar} ${yesLabel} / ${noLabel}\r`);
10964
+ outb("\x1B[?25h");
10890
10965
  }
10891
- const answer = await new Promise((resolve) => {
10892
- let selected = true;
10893
- renderOptions(selected);
10894
- const { stdin } = process;
10895
- const wasRaw = stdin.isTTY ? stdin.isRaw : false;
10896
- if (stdin.isTTY)
10897
- stdin.setRawMode(true);
10898
- stdin.resume();
10899
- function onData(buf) {
10900
- const key = buf.toString();
10901
- if (key === "\x03") {
10902
- stdin.removeListener("data", onData);
10903
- if (stdin.isTTY)
10904
- stdin.setRawMode(wasRaw);
10905
- stdin.pause();
10906
- resolve(null);
10907
- } else if (key === "\r" || key === `
10908
- `) {
10909
- stdin.removeListener("data", onData);
10910
- if (stdin.isTTY)
10911
- stdin.setRawMode(wasRaw);
10912
- stdin.pause();
10913
- resolve(selected);
10914
- } else if (key === "y" || key === "Y") {
10915
- stdin.removeListener("data", onData);
10916
- if (stdin.isTTY)
10917
- stdin.setRawMode(wasRaw);
10918
- stdin.pause();
10919
- resolve(true);
10920
- } else if (key === "n" || key === "N") {
10921
- stdin.removeListener("data", onData);
10922
- if (stdin.isTTY)
10923
- stdin.setRawMode(wasRaw);
10924
- stdin.pause();
10925
- resolve(false);
10926
- } else if (key === "\x1B[C" || key === "\x1B[D" || key === "\t") {
10927
- selected = !selected;
10928
- renderOptions(selected);
10929
- }
10930
- }
10931
- stdin.on("data", onData);
10932
- });
10933
- process.stdout.write(`
10934
- `);
10935
- process.stdout.write("\x1B[?25h");
10936
- process.stdout.write("\x1B[1A\x1B[2K");
10937
- return answer;
10938
10966
  }
10939
10967
 
10940
10968
  // src/commands/doctor.ts
10969
+ init_cli_ui();
10941
10970
  async function runDoctor(opts = {}) {
10942
10971
  const vaultDir = opts.vaultDir ?? process.cwd();
10943
10972
  const isTTY = opts.isTTY ?? process.stdout.isTTY ?? false;
@@ -10990,7 +11019,7 @@ async function runDoctor(opts = {}) {
10990
11019
  let vaultYmlKeysResult;
10991
11020
  let settingsHooksResult;
10992
11021
  if (isTTY) {
10993
- const sp2 = createStep("⚙️", "Config schema");
11022
+ const sp2 = createStep("\u2699\uFE0F", "Config schema");
10994
11023
  vaultYmlKeysResult = await checkVaultYmlKeysFn(vaultDir);
10995
11024
  await randDelay();
10996
11025
  sp2.stop(fmtResult(vaultYmlKeysResult), vaultYmlKeysResult.details);
@@ -11054,15 +11083,15 @@ async function runDoctor(opts = {}) {
11054
11083
  printNonTtyOutput(results, totalChecks, errorCount, warningCount, showFixHint, fixableCount);
11055
11084
  } else {
11056
11085
  if (errorCount > 0) {
11057
- close(`${summaryParts.join(" · ")} fix before using`, true);
11086
+ close(`${summaryParts.join(" \xB7 ")} \u2014 fix before using`, true);
11058
11087
  } else if (warningCount > 0) {
11059
- close(`${summaryParts.join(" · ")} advisory only, safe to run`, false, true);
11088
+ close(`${summaryParts.join(" \xB7 ")} \u2014 advisory only, safe to run`, false, true);
11060
11089
  } else {
11061
- close(import_picocolors5.default.green(`${summaryParts.join(" · ")} all passed`));
11090
+ close(import_picocolors5.default.green(`${summaryParts.join(" \xB7 ")} \u2014 all passed`));
11062
11091
  }
11063
11092
  if (showFixHint) {
11064
11093
  process.stdout.write(`
11065
- Run ${import_picocolors5.default.cyan("onebrain doctor --fix")} to auto-fix ${fixableCount} issue(s)
11094
+ \u2192 Run ${import_picocolors5.default.cyan("onebrain doctor --fix")} to auto-fix ${fixableCount} issue(s)
11066
11095
  `);
11067
11096
  }
11068
11097
  }
@@ -11083,21 +11112,21 @@ async function doctorCommand(opts = {}) {
11083
11112
  function printNonTtyOutput(results, totalChecks, errorCount, warningCount, showFixHint, fixableCount) {
11084
11113
  const lines = ["OneBrain Doctor", ""];
11085
11114
  for (const result of results) {
11086
- const icon = result.status === "ok" ? "[]" : result.status === "warn" ? "[!]" : "[]";
11115
+ const icon = result.status === "ok" ? "[\u2713]" : result.status === "warn" ? "[!]" : "[\u2717]";
11087
11116
  lines.push(` ${icon} ${result.check.padEnd(20)} ${result.message}`);
11088
11117
  if (result.hint)
11089
- lines.push(` ${result.hint}`);
11118
+ lines.push(` \u2192 ${result.hint}`);
11090
11119
  if (result.details)
11091
11120
  for (const d of result.details)
11092
- lines.push(` · ${d}`);
11121
+ lines.push(` \xB7 ${d}`);
11093
11122
  }
11094
11123
  lines.push("");
11095
11124
  if (errorCount > 0) {
11096
- lines.push(`Summary: ${totalChecks} checks · ${errorCount} error(s) · ${warningCount} warning(s) fix before using`);
11125
+ lines.push(`Summary: ${totalChecks} checks \xB7 ${errorCount} error(s) \xB7 ${warningCount} warning(s) \u2014 fix before using`);
11097
11126
  } else if (warningCount > 0) {
11098
- lines.push(`Summary: ${totalChecks} checks · ${warningCount} warning(s) ok to run`);
11127
+ lines.push(`Summary: ${totalChecks} checks \xB7 ${warningCount} warning(s) \u2014 ok to run`);
11099
11128
  } else {
11100
- lines.push(`Summary: ${totalChecks} checks all passed`);
11129
+ lines.push(`Summary: ${totalChecks} checks \u2014 all passed`);
11101
11130
  }
11102
11131
  if (showFixHint)
11103
11132
  lines.push(`hint: run onebrain doctor --fix to auto-fix ${fixableCount} issue(s)`);
@@ -11126,8 +11155,8 @@ function getFix(r2) {
11126
11155
  const description = deprecated.length > 0 ? `Remove deprecated keys from vault.yml: ${deprecated.join(", ")}` : "Remove deprecated keys from vault.yml";
11127
11156
  return {
11128
11157
  fn: async (vaultDir) => {
11129
- const { readFile: readFile2, writeFile: writeFile2, rename: rename2 } = await import("node:fs/promises");
11130
- const { join: join5 } = await import("node:path");
11158
+ const { readFile: readFile2, writeFile: writeFile2, rename: rename2 } = await import("fs/promises");
11159
+ const { join: join5 } = await import("path");
11131
11160
  const { parse: parse3, stringify } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
11132
11161
  const vaultYmlPath = join5(vaultDir, "vault.yml");
11133
11162
  const text = await readFile2(vaultYmlPath, "utf8");
@@ -11159,9 +11188,9 @@ function getFix(r2) {
11159
11188
  const count = pendingMatch?.[1] ?? "some";
11160
11189
  return {
11161
11190
  fn: async (vaultDir) => {
11162
- const { join: join5 } = await import("node:path");
11191
+ const { join: join5 } = await import("path");
11163
11192
  const { parse: parseYaml } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
11164
- const { readFile: readFile2 } = await import("node:fs/promises");
11193
+ const { readFile: readFile2 } = await import("fs/promises");
11165
11194
  const raw = parseYaml(await readFile2(join5(vaultDir, "vault.yml"), "utf8"));
11166
11195
  const collection = raw["qmd_collection"];
11167
11196
  if (!collection)
@@ -11187,8 +11216,8 @@ function getFix(r2) {
11187
11216
  const missingStr = r2.hint.replace("Missing: ", "");
11188
11217
  return {
11189
11218
  fn: async (vaultDir) => {
11190
- const { mkdir: mkdir2 } = await import("node:fs/promises");
11191
- const { join: join5 } = await import("node:path");
11219
+ const { mkdir: mkdir2 } = await import("fs/promises");
11220
+ const { join: join5 } = await import("path");
11192
11221
  const missing = missingStr.split(", ").map((f2) => f2.trim()).filter(Boolean);
11193
11222
  for (const folder of missing) {
11194
11223
  await mkdir2(join5(vaultDir, folder), { recursive: true });
@@ -11203,7 +11232,7 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11203
11232
  const fixable = results.filter((r2) => r2.status !== "ok" && getFix(r2) !== null);
11204
11233
  if (fixable.length === 0) {
11205
11234
  if (isTTY)
11206
- barLine(`${import_picocolors5.default.green("")} Nothing to fix`);
11235
+ barLine(`${import_picocolors5.default.green("\u25C6")} Nothing to fix`);
11207
11236
  else
11208
11237
  writeLine("nothing to fix");
11209
11238
  return;
@@ -11213,14 +11242,14 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11213
11242
  barLine(import_picocolors5.default.bold(`${fixable.length} fix(es) to apply:`));
11214
11243
  barBlank();
11215
11244
  for (const r2 of fixable) {
11216
- barLine(` ${import_picocolors5.default.cyan("")} ${getFix(r2).description}`);
11245
+ barLine(` ${import_picocolors5.default.cyan("\u25C6")} ${getFix(r2).description}`);
11217
11246
  }
11218
11247
  barBlank();
11219
11248
  const answer = await askYesNo("Apply all?");
11220
11249
  if (answer === null || answer === false) {
11221
11250
  barLine(import_picocolors5.default.dim("No"));
11222
11251
  barBlank();
11223
- close(`No changes made run ${import_picocolors5.default.cyan("onebrain doctor --fix")} to apply`);
11252
+ close(`No changes made \u2014 run ${import_picocolors5.default.cyan("onebrain doctor --fix")} to apply`);
11224
11253
  return;
11225
11254
  }
11226
11255
  barLine("Yes");
@@ -11240,11 +11269,11 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11240
11269
  await fix.fn(vaultDir, registerHooksFn);
11241
11270
  fixed++;
11242
11271
  if (isTTY)
11243
- barLine(`${import_picocolors5.default.green("")} ${fix.description}`);
11272
+ barLine(`${import_picocolors5.default.green("\u25C6")} ${fix.description}`);
11244
11273
  } catch (err) {
11245
11274
  const errMsg = err instanceof Error ? err.message : String(err);
11246
11275
  if (isTTY) {
11247
- barLine(`${import_picocolors5.default.yellow("")} Could not fix ${r2.check}: ${errMsg}`);
11276
+ barLine(`${import_picocolors5.default.yellow("\u25B2")} Could not fix ${r2.check}: ${errMsg}`);
11248
11277
  } else {
11249
11278
  process.stderr.write(`doctor: fix failed for ${r2.check}: ${errMsg}
11250
11279
  `);
@@ -11254,9 +11283,9 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11254
11283
  if (isTTY) {
11255
11284
  barBlank();
11256
11285
  if (fixed > 0)
11257
- barLine(`${import_picocolors5.default.green("")} Fixed ${fixed} issue(s)`);
11286
+ barLine(`${import_picocolors5.default.green("\u25C6")} Fixed ${fixed} issue(s)`);
11258
11287
  if (unfixable.length > 0) {
11259
- barLine(`${import_picocolors5.default.yellow("")} ${unfixable.length} issue(s) require manual action:`);
11288
+ barLine(`${import_picocolors5.default.yellow("\u25B2")} ${unfixable.length} issue(s) require manual action:`);
11260
11289
  for (const r2 of unfixable) {
11261
11290
  barLine(` ${r2.check}: ${r2.hint ?? "no auto-fix available"}`);
11262
11291
  }
@@ -11274,11 +11303,12 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11274
11303
  }
11275
11304
 
11276
11305
  // src/commands/init.ts
11277
- var import_picocolors6 = __toESM(require_picocolors(), 1);
11306
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
11278
11307
  var import_yaml4 = __toESM(require_dist(), 1);
11279
- import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
11280
- import { homedir as homedir3 } from "node:os";
11281
- import { dirname as dirname3, join as join6 } from "node:path";
11308
+ import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as writeFile3 } from "fs/promises";
11309
+ import { homedir as homedir3 } from "os";
11310
+ import { dirname as dirname3, join as join6 } from "path";
11311
+ init_cli_ui();
11282
11312
  var binaryVersion = resolveBinaryVersion();
11283
11313
  var STANDARD_FOLDERS = [
11284
11314
  "00-inbox",
@@ -11343,12 +11373,12 @@ async function downloadPluginFiles(vaultDir, vaultSyncFn) {
11343
11373
  } catch {}
11344
11374
  let driftWarning;
11345
11375
  if (pluginVersion && binaryVersion !== "dev" && pluginVersion !== binaryVersion) {
11346
- driftWarning = `Plugin files v${pluginVersion}, binary v${binaryVersion} run onebrain update to sync.`;
11376
+ driftWarning = `Plugin files v${pluginVersion}, binary v${binaryVersion} \u2014 run onebrain update to sync.`;
11347
11377
  }
11348
11378
  return driftWarning !== undefined ? { skipped: true, driftWarning } : { skipped: true };
11349
11379
  }
11350
11380
  try {
11351
- await vaultSyncFn(vaultDir, { includeObsidian: true });
11381
+ await vaultSyncFn(vaultDir, { includeObsidian: true, embedded: true });
11352
11382
  } catch (err) {
11353
11383
  const msg = err instanceof Error ? err.message : String(err);
11354
11384
  process.stderr.write(`init: vault-sync warning: ${msg}
@@ -11544,7 +11574,7 @@ async function installObsidianPlugins(vaultDir, opts) {
11544
11574
  }
11545
11575
  if (pluginFailed) {
11546
11576
  try {
11547
- const { rm: rm2 } = await import("node:fs/promises");
11577
+ const { rm: rm2 } = await import("fs/promises");
11548
11578
  await rm2(pluginDir, { recursive: true, force: true });
11549
11579
  } catch {}
11550
11580
  } else {
@@ -11578,22 +11608,17 @@ async function runInit(opts = {}) {
11578
11608
  const createStep = makeStepFn(isTTY);
11579
11609
  const delay = opts.delayFn ?? ((ms) => new Promise((r2) => setTimeout(r2, ms)));
11580
11610
  const randDelay = () => isTTY ? delay(Math.floor(Math.random() * 1000) + 1000) : Promise.resolve();
11611
+ const _confirmFn = opts.confirmFn ?? askYesNo;
11581
11612
  const vaultYmlPath = join6(vaultDir, "vault.yml");
11582
11613
  const vaultYmlExists = await pathExists2(vaultYmlPath);
11583
- if (vaultYmlExists && !force) {
11584
- if (!isTTY) {
11585
- const msg = "vault.yml exists. Re-run with --force to overwrite.";
11586
- process.stdout.write(`${msg}
11587
- `);
11588
- result.message = msg;
11589
- result.exitCode = 1;
11590
- return result;
11591
- }
11592
- if (isTTY) {
11593
- await printBanner();
11594
- const answer = await askYesNo("vault.yml already exists. Overwrite?");
11595
- if (answer === null || answer === false) {
11596
- barLine(import_picocolors6.default.dim("No"));
11614
+ if (isTTY) {
11615
+ await printBanner();
11616
+ if (!force) {
11617
+ barLine(`${import_picocolors7.default.dim("vault root")} ${import_picocolors7.default.cyan(vaultDir)}`);
11618
+ barBlank();
11619
+ const proceed = await _confirmFn("Initialize OneBrain vault here?");
11620
+ if (proceed === null || proceed === false) {
11621
+ barLine(import_picocolors7.default.dim("No"));
11597
11622
  barBlank();
11598
11623
  close("Aborted");
11599
11624
  result.ok = true;
@@ -11602,18 +11627,36 @@ async function runInit(opts = {}) {
11602
11627
  }
11603
11628
  barLine("Yes");
11604
11629
  barBlank();
11630
+ if (vaultYmlExists) {
11631
+ const overwrite = await _confirmFn("vault.yml already exists. Overwrite?");
11632
+ if (overwrite === null || overwrite === false) {
11633
+ barLine(import_picocolors7.default.dim("No"));
11634
+ barBlank();
11635
+ close("Aborted");
11636
+ result.ok = true;
11637
+ result.exitCode = 0;
11638
+ return result;
11639
+ }
11640
+ barLine("Yes");
11641
+ barBlank();
11642
+ }
11643
+ }
11644
+ } else {
11645
+ if (vaultYmlExists && !force) {
11646
+ const msg = "vault.yml exists. Re-run with --force to overwrite.";
11647
+ process.stdout.write(`${msg}
11648
+ `);
11649
+ result.message = msg;
11650
+ result.exitCode = 1;
11651
+ return result;
11605
11652
  }
11606
- } else if (isTTY) {
11607
- await printBanner();
11608
- }
11609
- if (!isTTY) {
11610
11653
  writeLine("OneBrain Init");
11611
11654
  }
11612
11655
  const sp2 = createStep("\uD83D\uDCCB", "vault.yml");
11613
11656
  await writeVaultYml(vaultDir);
11614
11657
  if (sp2) {
11615
11658
  await randDelay();
11616
- sp2.stop(import_picocolors6.default.dim("written"), ["update_channel: stable", "checkpoint: 15 msgs · 30 min"]);
11659
+ sp2.stop(import_picocolors7.default.dim("written"), ["update_channel: stable", "checkpoint: 15 msgs \xB7 30 min"]);
11617
11660
  } else {
11618
11661
  writeLine("vault.yml: written");
11619
11662
  }
@@ -11626,18 +11669,25 @@ async function runInit(opts = {}) {
11626
11669
  } else {
11627
11670
  writeLine(`folders: ${foldersCreated} created`);
11628
11671
  }
11629
- const sp4 = createStep("\uD83D\uDCE6", "Plugin files");
11630
- const {
11631
- skipped: pluginSkipped,
11632
- failed: pluginDownloadFailed
11633
- } = await downloadPluginFiles(vaultDir, vaultSyncFn);
11672
+ const pluginJsonPath = join6(vaultDir, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json");
11673
+ const pluginFilesExist = await pathExists2(pluginJsonPath);
11674
+ const sp4 = pluginFilesExist ? createStep("\uD83D\uDCE6", "Plugin files") : null;
11675
+ const { skipped: pluginSkipped, failed: pluginDownloadFailed } = await downloadPluginFiles(vaultDir, vaultSyncFn);
11634
11676
  result.pluginSkipped = pluginSkipped;
11635
11677
  if (sp4) {
11636
11678
  if (pluginDownloadFailed) {
11637
11679
  sp4.stop("download failed");
11638
11680
  } else {
11639
11681
  const { skills, agents } = await countPluginContents(vaultDir);
11640
- sp4.stop(import_picocolors6.default.dim(pluginSkipped ? "already installed" : "downloaded"), [`${skills} skills · ${agents} agents`]);
11682
+ sp4.stop(import_picocolors7.default.dim("already installed"), [`${skills} skills \xB7 ${agents} agents`]);
11683
+ }
11684
+ } else if (isTTY) {
11685
+ if (!pluginDownloadFailed) {
11686
+ const { skills, agents } = await countPluginContents(vaultDir);
11687
+ dotLine("\uD83D\uDCE6", "Plugin files");
11688
+ barLine(import_picocolors7.default.dim("downloaded"));
11689
+ barLine(` \xB7 ${skills} skills \xB7 ${agents} agents`);
11690
+ barBlank();
11641
11691
  }
11642
11692
  } else {
11643
11693
  if (pluginSkipped)
@@ -11650,7 +11700,7 @@ async function runInit(opts = {}) {
11650
11700
  if (isTTY) {
11651
11701
  close("Could not download plugin files. Check your internet connection and try again.", true);
11652
11702
  } else {
11653
- writeLine("error: vault-sync failed run onebrain update to download plugin files");
11703
+ writeLine("error: vault-sync failed \u2014 run onebrain update to download plugin files");
11654
11704
  }
11655
11705
  return result;
11656
11706
  }
@@ -11669,7 +11719,7 @@ async function runInit(opts = {}) {
11669
11719
  ...pluginResult.installed,
11670
11720
  ...pluginResult.failed.map((f2) => `${f2.id} (skipped)`)
11671
11721
  ];
11672
- sp4b.stop(import_picocolors6.default.dim(n > 0 ? `${n} installed` : "none"), details.length > 0 ? details : undefined);
11722
+ sp4b.stop(import_picocolors7.default.dim(n > 0 ? `${n} installed` : "none"), details.length > 0 ? details : undefined);
11673
11723
  } else {
11674
11724
  if (pluginResult.installed.length > 0)
11675
11725
  writeLine(`plugins: ${pluginResult.installed.join(", ")} installed`);
@@ -11681,7 +11731,9 @@ async function runInit(opts = {}) {
11681
11731
  result.pluginRegistrationSkipped = pluginRegistrationSkipped;
11682
11732
  if (sp5) {
11683
11733
  await randDelay();
11684
- sp5.stop(import_picocolors6.default.dim(pluginRegistrationSkipped ? "skipped" : "registered"), [`source: ${pluginRegistrationSkipped ? "marketplace" : "local"}`]);
11734
+ sp5.stop(import_picocolors7.default.dim(pluginRegistrationSkipped ? "skipped" : "registered"), [
11735
+ `source: ${pluginRegistrationSkipped ? "marketplace" : "local"}`
11736
+ ]);
11685
11737
  } else {
11686
11738
  writeLine(`plugin: ${pluginRegistrationSkipped ? "skipped (marketplace)" : "registered"}`);
11687
11739
  }
@@ -11697,20 +11749,20 @@ async function runInit(opts = {}) {
11697
11749
  }
11698
11750
  if (sp6) {
11699
11751
  await randDelay();
11700
- sp6.stop(hooksOk ? undefined : "not registered run onebrain update", hooksOk ? ["Stop PostCompact ", "Bash(onebrain *) "] : undefined);
11752
+ sp6.stop(hooksOk ? undefined : "not registered \u2014 run onebrain update", hooksOk ? ["Stop \u2713 PostCompact \u2713", "Bash(onebrain *) \u2713"] : undefined);
11701
11753
  } else {
11702
- writeLine(`hooks: ${hooksOk ? "ok" : "warning hooks not registered; run onebrain update"}`);
11754
+ writeLine(`hooks: ${hooksOk ? "ok" : "warning \u2014 hooks not registered; run onebrain update"}`);
11703
11755
  }
11704
11756
  result.ok = true;
11705
11757
  result.exitCode = 0;
11706
11758
  if (isTTY) {
11707
- barLine(import_picocolors6.default.dim(`─── Next steps ${"".repeat(25)}`));
11759
+ barLine(import_picocolors7.default.dim(`\u2500\u2500\u2500 Next steps ${"\u2500".repeat(25)}`));
11708
11760
  barBlank();
11709
- barLine(` ${import_picocolors6.default.bold(import_picocolors6.default.cyan("1"))} \uD83D\uDCC1 Open Obsidian open this folder as vault`);
11710
- barLine(` ${import_picocolors6.default.bold(import_picocolors6.default.cyan("2"))} \uD83E\uDD16 Run ${import_picocolors6.default.cyan("claude")}`);
11711
- barLine(` ${import_picocolors6.default.bold(import_picocolors6.default.cyan("3"))} \uD83E\uDDE0 Type ${import_picocolors6.default.cyan("/onboarding")} to personalize`);
11761
+ barLine(` ${import_picocolors7.default.bold(import_picocolors7.default.cyan("1"))} \uD83D\uDCC1 Open Obsidian \u2192 open this folder as vault`);
11762
+ barLine(` ${import_picocolors7.default.bold(import_picocolors7.default.cyan("2"))} \uD83E\uDD16 Run ${import_picocolors7.default.cyan("claude")}`);
11763
+ barLine(` ${import_picocolors7.default.bold(import_picocolors7.default.cyan("3"))} \uD83E\uDDE0 Type ${import_picocolors7.default.cyan("/onboarding")} to personalize`);
11712
11764
  barBlank();
11713
- close(`✨ ${import_picocolors6.default.bold("Ready")} ${import_picocolors6.default.cyan("/onboarding")}`);
11765
+ close(`\u2728 ${import_picocolors7.default.bold("Ready")} \u2014 ${import_picocolors7.default.cyan("/onboarding")}`);
11714
11766
  } else {
11715
11767
  writeLine("done: run /onboarding in Claude to finish setup");
11716
11768
  }
@@ -11724,9 +11776,9 @@ async function initCommand(opts = {}) {
11724
11776
  }
11725
11777
 
11726
11778
  // src/commands/internal/checkpoint.ts
11727
- import { readFileSync, readdirSync, writeFileSync } from "node:fs";
11728
- import { tmpdir as osTmpdir } from "node:os";
11729
- import { join as join7 } from "node:path";
11779
+ import { readFileSync, readdirSync, writeFileSync } from "fs";
11780
+ import { tmpdir as osTmpdir } from "os";
11781
+ import { join as join7 } from "path";
11730
11782
  var SKIP_WINDOW = 60;
11731
11783
  var MIN_ACTIVITY = 2;
11732
11784
  var PRECOMPACT_RECENCY = 300;
@@ -11902,8 +11954,8 @@ async function checkpointCommand(mode, token, vaultRoot) {
11902
11954
  // src/commands/internal/migrate.ts
11903
11955
  init_lib();
11904
11956
  var import_yaml5 = __toESM(require_dist(), 1);
11905
- import { readFile as readFile4, readdir as readdir3, writeFile as writeFile4 } from "node:fs/promises";
11906
- import { join as join8 } from "node:path";
11957
+ import { readFile as readFile4, readdir as readdir3, writeFile as writeFile4 } from "fs/promises";
11958
+ import { join as join8 } from "path";
11907
11959
  function parseFrontmatterWithRest(rawText) {
11908
11960
  const text = rawText.replace(/\r\n/g, `
11909
11961
  `);
@@ -11972,7 +12024,7 @@ async function runBackfillRecapped(logsFolder, cutoffDate) {
11972
12024
  const content = await readFile4(fpath, "utf8");
11973
12025
  const parsed = parseFrontmatterWithRest(content);
11974
12026
  if (!parsed) {
11975
- process.stderr.write(`migrate: ${fname} malformed frontmatter
12027
+ process.stderr.write(`migrate: ${fname} \u2014 malformed frontmatter
11976
12028
  `);
11977
12029
  skipped++;
11978
12030
  continue;
@@ -12019,8 +12071,8 @@ async function migrateCommand(migrationName, cutoffDate, vaultDir) {
12019
12071
 
12020
12072
  // src/commands/internal/orphan-scan.ts
12021
12073
  var import_yaml6 = __toESM(require_dist(), 1);
12022
- import { readFile as readFile5, readdir as readdir4 } from "node:fs/promises";
12023
- import { join as join9 } from "node:path";
12074
+ import { readFile as readFile5, readdir as readdir4 } from "fs/promises";
12075
+ import { join as join9 } from "path";
12024
12076
  function parseFrontmatter(rawText) {
12025
12077
  const text = rawText.replace(/\r\n/g, `
12026
12078
  `);
@@ -12154,290 +12206,28 @@ async function qmdReindexCommand(vaultRoot) {
12154
12206
  }
12155
12207
  }
12156
12208
 
12157
- // src/commands/internal/register-hooks.ts
12158
- init_dist2();
12209
+ // src/index.ts
12210
+ init_register_hooks();
12211
+
12212
+ // src/commands/internal/session-init.ts
12159
12213
  init_lib();
12160
- init_harness();
12161
- var import_picocolors7 = __toESM(require_picocolors(), 1);
12162
- import { mkdir as mkdir4, readFile as readFile6, rename as rename4, writeFile as writeFile5 } from "node:fs/promises";
12163
- import { homedir as homedir4 } from "node:os";
12164
- import { dirname as dirname4, join as join10 } from "node:path";
12165
- var HOOK_COMMANDS2 = {
12166
- Stop: "onebrain checkpoint stop",
12167
- PostCompact: "onebrain checkpoint postcompact"
12168
- };
12169
- var HOOK_EVENTS2 = ["Stop", "PostCompact"];
12170
- var STALE_HOOK_COMMANDS2 = {
12171
- PreCompact: "onebrain checkpoint precompact"
12172
- };
12173
- var PERMISSIONS_TO_ADD2 = [
12174
- "Read",
12175
- "Write",
12176
- "Edit",
12177
- "Glob",
12178
- "Grep",
12179
- "Bash(git *)",
12180
- "Bash(bun *)",
12181
- "Bash(gh *)",
12182
- "Bash(node *)",
12183
- "Bash(onebrain *)",
12184
- "Bash(bun install -g @onebrain-ai/cli*)",
12185
- "Bash(npm install -g @onebrain-ai/cli*)",
12186
- "WebFetch",
12187
- "WebSearch"
12188
- ];
12189
- var ONEBRAIN_MARKER2 = "# onebrain";
12190
- var PATH_EXPORT2 = 'export PATH="$HOME/.bun/bin:$HOME/.npm-global/bin:$PATH"';
12191
- async function readSettings2(settingsPath) {
12192
- try {
12193
- const text = await readFile6(settingsPath, "utf8");
12194
- return JSON.parse(text);
12195
- } catch (err) {
12196
- if (err.code === "ENOENT")
12197
- return {};
12198
- throw err;
12199
- }
12200
- }
12201
- async function writeSettings2(settingsPath, settings) {
12202
- await mkdir4(dirname4(settingsPath), { recursive: true });
12203
- const tmpPath = `${settingsPath}.tmp`;
12204
- await writeFile5(tmpPath, JSON.stringify(settings, null, 4), "utf8");
12205
- await rename4(tmpPath, settingsPath);
12206
- }
12207
- function checkHookPresence2(groups, targetCmd) {
12208
- let foundMigrate = false;
12209
- for (const group of groups) {
12210
- for (const entry of group.hooks ?? []) {
12211
- const cmd = entry.command ?? "";
12212
- if (cmd === targetCmd)
12213
- return "found";
12214
- if (cmd.includes("checkpoint-hook.sh"))
12215
- foundMigrate = true;
12216
- }
12217
- }
12218
- return foundMigrate ? "migrate" : "missing";
12219
- }
12220
- function applyHooks2(settings) {
12221
- if (!settings.hooks)
12222
- settings.hooks = {};
12223
- const hooks = settings.hooks;
12224
- const result = {};
12225
- for (const [event, staleCmd] of Object.entries(STALE_HOOK_COMMANDS2)) {
12226
- if (!hooks[event])
12227
- continue;
12228
- hooks[event] = hooks[event].filter((group) => !group.hooks?.some((entry) => entry.command === staleCmd));
12229
- if (hooks[event].length === 0)
12230
- delete hooks[event];
12231
- }
12232
- for (const event of HOOK_EVENTS2) {
12233
- const cmd = HOOK_COMMANDS2[event];
12234
- if (!cmd)
12235
- continue;
12236
- if (!hooks[event])
12237
- hooks[event] = [];
12238
- const groups = hooks[event];
12239
- const presence = checkHookPresence2(groups, cmd);
12240
- if (presence === "found") {
12241
- result[event] = "ok";
12242
- } else if (presence === "migrate") {
12243
- for (const group of groups) {
12244
- if (group.matcher === undefined)
12245
- group.matcher = "";
12246
- for (const entry of group.hooks ?? []) {
12247
- if ((entry.command ?? "").includes("checkpoint-hook.sh")) {
12248
- entry.command = cmd;
12249
- if (!entry.type)
12250
- entry.type = "command";
12251
- }
12252
- }
12253
- }
12254
- result[event] = "migrated";
12255
- } else {
12256
- groups.push({ matcher: "", hooks: [{ type: "command", command: cmd }] });
12257
- result[event] = "added";
12258
- }
12259
- }
12260
- return result;
12261
- }
12262
- var QMD_CMD2 = "onebrain qmd-reindex";
12263
- var QMD_MATCHER2 = "Write|Edit";
12264
- function applyQmdHook2(settings) {
12265
- if (!settings.hooks)
12266
- settings.hooks = {};
12267
- if (!settings.hooks["PostToolUse"])
12268
- settings.hooks["PostToolUse"] = [];
12269
- const groups = settings.hooks["PostToolUse"];
12270
- const already = groups.some((g) => g.hooks?.some((h) => h.command === QMD_CMD2));
12271
- if (already)
12272
- return "ok";
12273
- groups.push({ matcher: QMD_MATCHER2, hooks: [{ type: "command", command: QMD_CMD2 }] });
12274
- return "added";
12275
- }
12276
- function applyPermissions2(settings) {
12277
- if (!settings.permissions)
12278
- settings.permissions = {};
12279
- if (!settings.permissions.allow)
12280
- settings.permissions.allow = [];
12281
- const allow = settings.permissions.allow;
12282
- const added = [];
12283
- for (const perm of PERMISSIONS_TO_ADD2) {
12284
- if (!allow.includes(perm)) {
12285
- allow.push(perm);
12286
- added.push(perm);
12287
- }
12288
- }
12289
- return added;
12290
- }
12291
- async function registerGeminiHooks2(vaultRoot) {
12292
- const geminiSettingsPath = join10(vaultRoot, ".gemini", "settings.json");
12293
- try {
12294
- const text = await readFile6(geminiSettingsPath, "utf8");
12295
- const settings = JSON.parse(text);
12296
- applyHooks2(settings);
12297
- await writeSettings2(geminiSettingsPath, settings);
12298
- } catch (err) {
12299
- if (err.code !== "ENOENT") {
12300
- process.stderr.write(`register-hooks: gemini warning: ${err instanceof Error ? err.message : String(err)}
12301
- `);
12302
- }
12303
- }
12304
- }
12305
- async function registerDirectPath2() {
12306
- const home = homedir4();
12307
- const candidates = [join10(home, ".zshrc"), join10(home, ".bashrc"), join10(home, ".profile")];
12308
- let profilePath;
12309
- for (const candidate of candidates) {
12310
- try {
12311
- await readFile6(candidate, "utf8");
12312
- profilePath = candidate;
12313
- break;
12314
- } catch {}
12315
- }
12316
- if (!profilePath)
12317
- return;
12318
- const content = await readFile6(profilePath, "utf8");
12319
- if (content.includes(ONEBRAIN_MARKER2))
12320
- return;
12321
- const updated = `${content}
12322
- ${ONEBRAIN_MARKER2}
12323
- ${PATH_EXPORT2}
12324
- `;
12325
- const tmpPath = `${profilePath}.tmp`;
12326
- await writeFile5(tmpPath, updated, "utf8");
12327
- await rename4(tmpPath, profilePath);
12328
- }
12329
- async function runRegisterHooks2(opts = {}) {
12330
- const vaultRoot = opts.vaultDir ?? process.cwd();
12331
- const isTTY = opts.isTTY ?? process.stdout.isTTY ?? false;
12332
- const harness = await detectHarness(vaultRoot);
12333
- let qmdCollection;
12334
- try {
12335
- const vaultConfig = await loadVaultConfig(vaultRoot);
12336
- qmdCollection = vaultConfig.qmd_collection;
12337
- } catch (err) {
12338
- if (err.code !== "ENOENT") {
12339
- process.stderr.write(`register-hooks: warning: could not read vault.yml: ${err instanceof Error ? err.message : String(err)}
12340
- `);
12341
- }
12342
- }
12343
- const result = {
12344
- ok: false,
12345
- hooks: {},
12346
- permissionsAdded: []
12347
- };
12348
- const settingsPath = join10(vaultRoot, ".claude", "settings.json");
12349
- const note = (msg) => {
12350
- if (opts.silent)
12351
- return;
12352
- process.stdout.write(`register-hooks: ${msg}
12353
- `);
12354
- };
12355
- let hooksSpinner = null;
12356
- let permSpinner = null;
12357
- try {
12358
- if (harness === "claude") {
12359
- hooksSpinner = isTTY ? L2() : null;
12360
- hooksSpinner?.start("Registering hooks...");
12361
- const settings = await readSettings2(settingsPath);
12362
- result.hooks = applyHooks2(settings);
12363
- let qmdStatus;
12364
- if (qmdCollection)
12365
- qmdStatus = applyQmdHook2(settings);
12366
- if (isTTY) {
12367
- const parts = HOOK_EVENTS2.map((e2) => {
12368
- const status = result.hooks[e2];
12369
- const icon = import_picocolors7.default.green(status === "ok" ? "✓" : status === "migrated" ? "↑" : "+");
12370
- return `${import_picocolors7.default.dim(e2)} ${icon}`;
12371
- });
12372
- if (qmdStatus)
12373
- parts.push(`${import_picocolors7.default.dim("PostToolUse")} ${import_picocolors7.default.green(qmdStatus === "ok" ? "✓" : "+")}`);
12374
- hooksSpinner?.stop(`Hooks ${parts.join(" ")}`);
12375
- } else {
12376
- const hookLine = HOOK_EVENTS2.map((e2) => {
12377
- const status = result.hooks[e2];
12378
- const label = status === "ok" || status === "added" || status === "migrated" ? "ok" : status ?? "ok";
12379
- return `${e2} ${label}`;
12380
- }).join(" ");
12381
- note(hookLine);
12382
- if (qmdStatus)
12383
- note(`PostToolUse ${qmdStatus === "added" ? "added" : "ok"}`);
12384
- }
12385
- permSpinner = isTTY ? L2() : null;
12386
- permSpinner?.start("Updating permissions...");
12387
- result.permissionsAdded = applyPermissions2(settings);
12388
- await writeSettings2(settingsPath, settings);
12389
- permSpinner?.stop("Permissions ok");
12390
- if (!isTTY)
12391
- note("permissions ok");
12392
- }
12393
- if (harness === "gemini") {
12394
- await registerGeminiHooks2(vaultRoot);
12395
- }
12396
- if (harness === "direct") {
12397
- await registerDirectPath2();
12398
- }
12399
- result.ok = true;
12400
- if (!isTTY) {
12401
- note("done");
12402
- }
12403
- } catch (err) {
12404
- hooksSpinner?.stop("Registration failed");
12405
- permSpinner?.stop("Permissions failed");
12406
- const msg = err instanceof Error ? err.message : String(err);
12407
- result.error = msg;
12408
- process.stderr.write(`register-hooks: error: ${msg}
12409
- `);
12410
- }
12411
- return result;
12412
- }
12413
- async function registerHooksCommand2(vaultDir) {
12414
- const result = await runRegisterHooks2({
12415
- ...vaultDir !== undefined ? { vaultDir } : {}
12416
- });
12417
- if (!result.ok) {
12418
- process.exit(1);
12419
- }
12420
- }
12421
-
12422
- // src/commands/internal/session-init.ts
12423
- init_lib();
12424
- import { unlink as unlink2 } from "node:fs/promises";
12425
- import { tmpdir as osTmpdir2 } from "node:os";
12426
- import { join as join11 } from "node:path";
12427
- var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
12428
- var MONTH_NAMES = [
12429
- "Jan",
12430
- "Feb",
12431
- "Mar",
12432
- "Apr",
12433
- "May",
12434
- "Jun",
12435
- "Jul",
12436
- "Aug",
12437
- "Sep",
12438
- "Oct",
12439
- "Nov",
12440
- "Dec"
12214
+ import { unlink as unlink2 } from "fs/promises";
12215
+ import { tmpdir as osTmpdir2 } from "os";
12216
+ import { join as join10 } from "path";
12217
+ var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
12218
+ var MONTH_NAMES = [
12219
+ "Jan",
12220
+ "Feb",
12221
+ "Mar",
12222
+ "Apr",
12223
+ "May",
12224
+ "Jun",
12225
+ "Jul",
12226
+ "Aug",
12227
+ "Sep",
12228
+ "Oct",
12229
+ "Nov",
12230
+ "Dec"
12441
12231
  ];
12442
12232
  function formatDatetime(date) {
12443
12233
  const dow = DAY_NAMES[date.getDay()];
@@ -12446,7 +12236,7 @@ function formatDatetime(date) {
12446
12236
  const year = date.getFullYear();
12447
12237
  const hh = String(date.getHours()).padStart(2, "0");
12448
12238
  const mm = String(date.getMinutes()).padStart(2, "0");
12449
- return `${dow} · ${day} ${mon} ${year} · ${hh}:${mm}`;
12239
+ return `${dow} \xB7 ${day} ${mon} ${year} \xB7 ${hh}:${mm}`;
12450
12240
  }
12451
12241
  async function resolveSessionToken(tmpDir = osTmpdir2()) {
12452
12242
  const wtSession = process.env["WT_SESSION"];
@@ -12455,644 +12245,147 @@ async function resolveSessionToken(tmpDir = osTmpdir2()) {
12455
12245
  if (stripped.length > 0)
12456
12246
  return stripped;
12457
12247
  }
12458
- const tmuxPane = process.env["TMUX_PANE"];
12459
- if (tmuxPane) {
12460
- const stripped = tmuxPane.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
12461
- if (stripped.length > 0)
12462
- return stripped;
12463
- }
12464
- const termSessionId = process.env["TERM_SESSION_ID"];
12465
- if (termSessionId) {
12466
- const stripped = termSessionId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
12467
- if (stripped.length > 0)
12468
- return stripped;
12469
- }
12470
- const today = new Date;
12471
- const yyyymmdd = [
12472
- today.getFullYear(),
12473
- String(today.getMonth() + 1).padStart(2, "0"),
12474
- String(today.getDate()).padStart(2, "0")
12475
- ].join("");
12476
- const cacheFile = join11(tmpDir, `onebrain-day-${yyyymmdd}.token`);
12477
- const cacheExists = await Bun.file(cacheFile).exists();
12478
- if (cacheExists) {
12479
- const cached = (await Bun.file(cacheFile).text()).trim();
12480
- const n = Number(cached);
12481
- if (!Number.isNaN(n) && n > 1)
12482
- return cached;
12483
- }
12484
- const ppid = process.ppid;
12485
- if (ppid !== undefined && ppid > 1) {
12486
- const token2 = String(ppid);
12487
- await Bun.write(cacheFile, token2);
12488
- return token2;
12489
- }
12490
- try {
12491
- const ps = Bun.spawn([
12492
- "powershell.exe",
12493
- "-NoProfile",
12494
- "-NonInteractive",
12495
- "-Command",
12496
- "(Get-Process -Id $PID).Parent.Id"
12497
- ], {
12498
- stdout: "pipe",
12499
- stderr: "pipe"
12500
- });
12501
- const timeoutMs = 3000;
12502
- let timerId;
12503
- const race = await Promise.race([
12504
- ps.exited,
12505
- new Promise((resolve) => {
12506
- timerId = setTimeout(() => resolve("timeout"), timeoutMs);
12507
- })
12508
- ]);
12509
- if (timerId !== undefined)
12510
- clearTimeout(timerId);
12511
- if (race !== "timeout") {
12512
- const out = (await new Response(ps.stdout).text()).replace(/\D/g, "").trim();
12513
- if (out && Number(out) > 1) {
12514
- await Bun.write(cacheFile, out);
12515
- return out;
12516
- }
12517
- } else {
12518
- ps.kill();
12519
- }
12520
- } catch {}
12521
- const token = String(Math.floor(Math.random() * 90000) + 1e4);
12522
- await Bun.write(cacheFile, token);
12523
- return token;
12524
- }
12525
- async function cleanStaleStateFile(token, tmpDir) {
12526
- try {
12527
- const processStartMs = Date.now() - performance.now();
12528
- const stateFile = join11(tmpDir, `onebrain-${token}.state`);
12529
- const f2 = Bun.file(stateFile);
12530
- const exists = await f2.exists();
12531
- if (!exists)
12532
- return;
12533
- const { mtime } = await f2.stat();
12534
- const mtimeMs = mtime instanceof Date ? mtime.getTime() : Number(mtime) * 1000;
12535
- if (mtimeMs < processStartMs) {
12536
- try {
12537
- await unlink2(stateFile);
12538
- } catch {}
12539
- }
12540
- } catch {}
12541
- }
12542
- async function queryQmdUnembedded() {
12543
- try {
12544
- const qmdArgs = process.platform === "win32" ? ["powershell.exe", "-NoProfile", "-Command", "qmd status --json"] : ["qmd", "status", "--json"];
12545
- const proc = Bun.spawn(qmdArgs, {
12546
- stdout: "pipe",
12547
- stderr: "pipe"
12548
- });
12549
- const timeoutMs = 2000;
12550
- let timerId;
12551
- const race = await Promise.race([
12552
- proc.exited,
12553
- new Promise((resolve) => {
12554
- timerId = setTimeout(() => resolve("timeout"), timeoutMs);
12555
- })
12556
- ]);
12557
- if (timerId !== undefined)
12558
- clearTimeout(timerId);
12559
- if (race === "timeout") {
12560
- proc.kill();
12561
- return 0;
12562
- }
12563
- const stdout = await new Response(proc.stdout).text();
12564
- const parsed = JSON.parse(stdout);
12565
- const unembedded = parsed["unembedded"];
12566
- return typeof unembedded === "number" ? unembedded : 0;
12567
- } catch {
12568
- return 0;
12569
- }
12570
- }
12571
- async function runSessionInit(vaultRoot, tmpDir = osTmpdir2()) {
12572
- try {
12573
- await loadVaultConfig(vaultRoot);
12574
- } catch {
12575
- return { decision: "block", reason: "onebrain-init-required" };
12576
- }
12577
- const sessionToken = await resolveSessionToken(tmpDir);
12578
- await cleanStaleStateFile(sessionToken, tmpDir);
12579
- const datetime = formatDatetime(new Date);
12580
- const qmdUnembedded = await queryQmdUnembedded();
12581
- return {
12582
- datetime,
12583
- session_token: sessionToken,
12584
- qmd_unembedded: qmdUnembedded
12585
- };
12586
- }
12587
- async function sessionInitCommand(vaultRoot) {
12588
- const result = await runSessionInit(vaultRoot);
12589
- process.stdout.write(`${JSON.stringify(result)}
12590
- `);
12591
- }
12592
-
12593
- // src/commands/internal/vault-sync.ts
12594
- init_dist2();
12595
- init_harness();
12596
- var import_yaml7 = __toESM(require_dist(), 1);
12597
- import {
12598
- mkdir as mkdir5,
12599
- mkdtemp as mkdtemp2,
12600
- readFile as readFile7,
12601
- readdir as readdir5,
12602
- rename as rename5,
12603
- rm as rm2,
12604
- stat as stat5,
12605
- unlink as unlink3,
12606
- writeFile as writeFile6
12607
- } from "node:fs/promises";
12608
- import { homedir as homedir5, tmpdir as tmpdir2 } from "node:os";
12609
- import { dirname as dirname5, join as join12, relative as relative2 } from "node:path";
12610
- function resolveBranch2(updateChannel) {
12611
- return updateChannel === "stable" ? "main" : "next";
12612
- }
12613
- async function downloadTarball2(branch, fetchFn) {
12614
- const url = `https://api.github.com/repos/kengio/onebrain/tarball/${branch}`;
12615
- const response = await fetchFn(url);
12616
- if (!response.ok) {
12617
- const hints = {
12618
- 403: " — check repo permissions or GITHUB_TOKEN",
12619
- 404: " — repo or branch not found",
12620
- 429: " — rate limited, wait and retry"
12621
- };
12622
- const hint = hints[response.status] ?? "";
12623
- throw new Error(`HTTP ${response.status} downloading tarball from ${url}${hint}`);
12624
- }
12625
- const tarball = await response.arrayBuffer();
12626
- const tmpDir = await mkdtemp2(join12(tmpdir2(), "onebrain-sync-"));
12627
- return { tarball, tmpDir };
12628
- }
12629
- async function extractTarball2(tarball, destDir) {
12630
- const tarPath = join12(destDir, "bundle.tar.gz");
12631
- await writeFile6(tarPath, Buffer.from(tarball));
12632
- const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", destDir], {
12633
- stdout: "pipe",
12634
- stderr: "pipe"
12635
- });
12636
- const exitCode = await proc.exited;
12637
- if (exitCode !== 0) {
12638
- const errText = await new Response(proc.stderr).text();
12639
- throw new Error(`tar extraction failed (exit ${exitCode}): ${errText.trim()}`);
12640
- }
12641
- await unlink3(tarPath);
12642
- const entries = await readdir5(destDir);
12643
- const topLevel = entries.find((e2) => e2 !== "bundle.tar.gz");
12644
- if (!topLevel) {
12645
- throw new Error("Extracted tarball contains no top-level directory");
12646
- }
12647
- return join12(destDir, topLevel);
12648
- }
12649
- async function listFilesRecursive2(dir) {
12650
- const results = [];
12651
- const queue = [dir];
12652
- while (queue.length > 0) {
12653
- const current = queue.pop();
12654
- let entries;
12655
- try {
12656
- entries = await readdir5(current);
12657
- } catch {
12658
- continue;
12659
- }
12660
- for (const entry of entries) {
12661
- const fullPath = join12(current, entry);
12662
- let s;
12663
- try {
12664
- s = await stat5(fullPath);
12665
- } catch {
12666
- continue;
12667
- }
12668
- if (s.isDirectory()) {
12669
- queue.push(fullPath);
12670
- } else {
12671
- results.push(fullPath);
12672
- }
12673
- }
12674
- }
12675
- return results;
12676
- }
12677
- async function syncPluginFiles2(extractedDir, vaultRoot, unlinkFn = unlink3) {
12678
- const sourcePlugin = join12(extractedDir, ".claude", "plugins", "onebrain");
12679
- const destPlugin = join12(vaultRoot, ".claude", "plugins", "onebrain");
12680
- await mkdir5(destPlugin, { recursive: true });
12681
- const sourceFiles = await listFilesRecursive2(sourcePlugin);
12682
- const sourceRelSet = new Set(sourceFiles.map((f2) => relative2(sourcePlugin, f2)));
12683
- const destFiles = await listFilesRecursive2(destPlugin);
12684
- const destRelSet = new Set(destFiles.map((f2) => relative2(destPlugin, f2)));
12685
- const staleRels = [];
12686
- for (const rel of destRelSet) {
12687
- if (!sourceRelSet.has(rel)) {
12688
- staleRels.push(rel);
12689
- }
12690
- }
12691
- let filesAdded = 0;
12692
- for (const srcPath of sourceFiles) {
12693
- const rel = relative2(sourcePlugin, srcPath);
12694
- const destPath = join12(destPlugin, rel);
12695
- await mkdir5(dirname5(destPath), { recursive: true });
12696
- const content = await readFile7(srcPath);
12697
- await writeFile6(destPath, content);
12698
- filesAdded++;
12699
- }
12700
- let filesRemoved = 0;
12701
- for (const rel of staleRels) {
12702
- const destPath = join12(destPlugin, rel);
12703
- try {
12704
- await unlinkFn(destPath);
12705
- filesRemoved++;
12706
- } catch {}
12707
- }
12708
- return { filesAdded, filesRemoved };
12709
- }
12710
- async function copyRootDocs2(extractedDir, vaultRoot) {
12711
- const docs = ["CONTRIBUTING.md", "CHANGELOG.md", "PLUGIN-CHANGELOG.md"];
12712
- for (const doc of docs) {
12713
- const srcPath = join12(extractedDir, doc);
12714
- const destPath = join12(vaultRoot, doc);
12715
- try {
12716
- const content = await readFile7(srcPath);
12717
- await writeFile6(destPath, content);
12718
- } catch {}
12719
- }
12720
- }
12721
- async function mergeHarnessFile2(extractedDir, vaultRoot, filename) {
12722
- const srcPath = join12(extractedDir, filename);
12723
- const destPath = join12(vaultRoot, filename);
12724
- let repoText;
12725
- try {
12726
- repoText = await readFile7(srcPath, "utf8");
12727
- } catch {
12728
- return 0;
12729
- }
12730
- let vaultText;
12731
- try {
12732
- vaultText = await readFile7(destPath, "utf8");
12733
- } catch {
12734
- await writeFile6(destPath, repoText, "utf8");
12735
- return repoText.split(`
12736
- `).filter((l2) => l2.startsWith("@")).length;
12737
- }
12738
- const vaultAtSet = new Set(vaultText.split(`
12739
- `).filter((l2) => l2.startsWith("@")).map((l2) => l2.trim()));
12740
- const newImports = repoText.split(`
12741
- `).filter((l2) => l2.startsWith("@") && !vaultAtSet.has(l2.trim())).map((l2) => l2.trimEnd());
12742
- if (newImports.length === 0) {
12743
- return 0;
12744
- }
12745
- const vaultLines = vaultText.split(`
12746
- `);
12747
- const lastAtIdx = vaultLines.reduce((acc, l2, i) => l2.startsWith("@") ? i : acc, -1);
12748
- if (lastAtIdx >= 0) {
12749
- vaultLines.splice(lastAtIdx, 0, ...newImports);
12750
- } else {
12751
- vaultLines.push("", ...newImports);
12752
- }
12753
- const merged = vaultLines.join(`
12754
- `);
12755
- await writeFile6(destPath, merged, "utf8");
12756
- return newImports.length;
12757
- }
12758
- async function mergeHarnessFiles2(extractedDir, vaultRoot) {
12759
- const harnessFiles = ["CLAUDE.md", "GEMINI.md", "AGENTS.md"];
12760
- let totalImportsAdded = 0;
12761
- const results = await Promise.all(harnessFiles.map((f2) => mergeHarnessFile2(extractedDir, vaultRoot, f2)));
12762
- for (const n of results) {
12763
- totalImportsAdded += n;
12764
- }
12765
- return totalImportsAdded;
12766
- }
12767
- async function updateVaultYml2(vaultRoot, updateChannel) {
12768
- const vaultYmlPath = join12(vaultRoot, "vault.yml");
12769
- let text;
12770
- try {
12771
- text = await readFile7(vaultYmlPath, "utf8");
12772
- } catch {
12773
- text = "";
12774
- }
12775
- const raw = import_yaml7.parse(text) ?? {};
12776
- raw["update_channel"] = updateChannel;
12777
- const updated = import_yaml7.stringify(raw, { lineWidth: 0 });
12778
- const tmpPath = `${vaultYmlPath}.tmp`;
12779
- await writeFile6(tmpPath, updated, "utf8");
12780
- await rename5(tmpPath, vaultYmlPath);
12781
- }
12782
- async function readPluginVersion2(vaultRoot) {
12783
- const pluginJsonPath = join12(vaultRoot, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json");
12784
- try {
12785
- const text = await readFile7(pluginJsonPath, "utf8");
12786
- const parsed = JSON.parse(text);
12787
- return typeof parsed["version"] === "string" ? parsed["version"] : "unknown";
12788
- } catch {
12789
- return "unknown";
12790
- }
12791
- }
12792
- async function pinToVault2(vaultRoot, installedPluginsPath, installedPluginsCacheDir) {
12793
- let text;
12794
- try {
12795
- text = await readFile7(installedPluginsPath, "utf8");
12796
- } catch {
12797
- return { skipped: true };
12798
- }
12799
- let data;
12800
- try {
12801
- data = JSON.parse(text);
12802
- } catch {
12803
- throw new Error(`installed_plugins.json is not valid JSON: ${installedPluginsPath}`);
12804
- }
12805
- const plugins = data["plugins"];
12806
- if (!plugins) {
12807
- return { skipped: true };
12808
- }
12809
- const onebrainKeys = Object.keys(plugins).filter((k2) => k2.startsWith("onebrain@"));
12810
- if (onebrainKeys.length === 0) {
12811
- return { skipped: true };
12812
- }
12813
- const vaultPluginDir = join12(vaultRoot, ".claude", "plugins", "onebrain");
12814
- const pluginVersion = await readPluginVersion2(vaultRoot);
12815
- const cacheDir = installedPluginsCacheDir ?? join12(dirname5(installedPluginsPath), "cache");
12816
- const hasMarketplace = onebrainKeys.some((k2) => {
12817
- const entries = plugins[k2];
12818
- return entries.some((e2) => e2["source"] === "marketplace");
12819
- });
12820
- if (hasMarketplace) {
12821
- return { skipped: true };
12822
- }
12823
- let changed = false;
12824
- for (const key of onebrainKeys) {
12825
- const entries = plugins[key];
12826
- for (const entry of entries) {
12827
- const installPath = entry["installPath"];
12828
- if (typeof installPath !== "string") {
12829
- continue;
12830
- }
12831
- let inCache = false;
12832
- try {
12833
- inCache = installPath.startsWith(`${cacheDir}/`) || installPath === cacheDir;
12834
- } catch {
12835
- inCache = false;
12836
- }
12837
- if (!inCache) {
12838
- continue;
12839
- }
12840
- entry["installPath"] = vaultPluginDir;
12841
- entry["version"] = pluginVersion;
12842
- changed = true;
12843
- }
12844
- }
12845
- if (!changed) {
12846
- return { skipped: false };
12847
- }
12848
- const tmpPath = `${installedPluginsPath}.tmp`;
12849
- await writeFile6(tmpPath, JSON.stringify(data, null, 4), "utf8");
12850
- await rename5(tmpPath, installedPluginsPath);
12851
- return { skipped: false };
12852
- }
12853
- async function cleanPluginCache2(installedPluginsPath, installedPluginsCacheDir) {
12854
- const cacheDir = installedPluginsCacheDir ?? join12(dirname5(installedPluginsPath), "cache");
12855
- try {
12856
- await stat5(cacheDir);
12857
- } catch {
12858
- return 0;
12859
- }
12860
- const onebrainDirs = [];
12861
- try {
12862
- const text = await readFile7(installedPluginsPath, "utf8");
12863
- const data = JSON.parse(text);
12864
- const plugins = data["plugins"];
12865
- if (plugins) {
12866
- for (const key of Object.keys(plugins)) {
12867
- if (!key.startsWith("onebrain@"))
12868
- continue;
12869
- const marketplace = key.split("@")[1];
12870
- const candidate = join12(cacheDir, marketplace, "onebrain");
12871
- try {
12872
- await stat5(candidate);
12873
- onebrainDirs.push(candidate);
12874
- } catch {}
12875
- }
12876
- }
12877
- } catch {}
12878
- if (onebrainDirs.length === 0) {
12879
- try {
12880
- const marketplaceDirs = await readdir5(cacheDir);
12881
- for (const mp of marketplaceDirs) {
12882
- const candidate = join12(cacheDir, mp, "onebrain");
12883
- try {
12884
- await stat5(candidate);
12885
- onebrainDirs.push(candidate);
12886
- } catch {}
12887
- }
12888
- } catch {
12889
- return 0;
12890
- }
12891
- }
12892
- let removed = 0;
12893
- for (const pluginDir of onebrainDirs) {
12894
- let versionDirs;
12895
- try {
12896
- versionDirs = await readdir5(pluginDir);
12897
- } catch {
12898
- continue;
12899
- }
12900
- for (const versionDir of versionDirs) {
12901
- const fullPath = join12(pluginDir, versionDir);
12902
- try {
12903
- const s = await stat5(fullPath);
12904
- if (s.isDirectory()) {
12905
- await rm2(fullPath, { recursive: true, force: true });
12906
- removed++;
12907
- }
12908
- } catch {}
12909
- }
12910
- }
12911
- return removed;
12912
- }
12913
- async function runVaultSync2(vaultRoot, opts = {}) {
12914
- const fetchFn = opts.fetchFn ?? globalThis.fetch;
12915
- const isTTY = opts.isTTY ?? process.stdout.isTTY;
12916
- const unlinkFn = opts.unlinkFn ?? unlink3;
12917
- let updateChannel = "stable";
12918
- try {
12919
- const vaultYmlText = await readFile7(join12(vaultRoot, "vault.yml"), "utf8");
12920
- const vaultYml = import_yaml7.parse(vaultYmlText) ?? {};
12921
- if (typeof vaultYml["update_channel"] === "string") {
12922
- updateChannel = vaultYml["update_channel"];
12923
- }
12924
- } catch {}
12925
- const harness = await detectHarness(vaultRoot);
12926
- const branch = opts.branch ?? resolveBranch2(updateChannel);
12927
- const installedPluginsPath = opts.installedPluginsPath ?? join12(homedir5(), ".claude", "plugins", "installed_plugins.json");
12928
- const installedPluginsCacheDir = opts.installedPluginsCacheDir;
12929
- const result = {
12930
- ok: false,
12931
- version: "unknown",
12932
- branch,
12933
- filesAdded: 0,
12934
- filesRemoved: 0,
12935
- importsAdded: 0,
12936
- pinSkipped: true,
12937
- cacheRemoved: 0
12938
- };
12939
- let s = null;
12940
- function startSpinner(msg) {
12941
- if (isTTY) {
12942
- s = L2();
12943
- s.start(msg);
12944
- } else {
12945
- process.stdout.write(`vault-sync: ${msg}
12946
- `);
12947
- }
12248
+ const tmuxPane = process.env["TMUX_PANE"];
12249
+ if (tmuxPane) {
12250
+ const stripped = tmuxPane.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
12251
+ if (stripped.length > 0)
12252
+ return stripped;
12948
12253
  }
12949
- function stopSpinner(msg) {
12950
- if (isTTY && s) {
12951
- s.stop(msg);
12952
- s = null;
12953
- }
12254
+ const termSessionId = process.env["TERM_SESSION_ID"];
12255
+ if (termSessionId) {
12256
+ const stripped = termSessionId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
12257
+ if (stripped.length > 0)
12258
+ return stripped;
12954
12259
  }
12955
- if (isTTY) {
12956
- we("OneBrain Vault Sync");
12260
+ const today = new Date;
12261
+ const yyyymmdd = [
12262
+ today.getFullYear(),
12263
+ String(today.getMonth() + 1).padStart(2, "0"),
12264
+ String(today.getDate()).padStart(2, "0")
12265
+ ].join("");
12266
+ const cacheFile = join10(tmpDir, `onebrain-day-${yyyymmdd}.token`);
12267
+ const cacheExists = await Bun.file(cacheFile).exists();
12268
+ if (cacheExists) {
12269
+ const cached = (await Bun.file(cacheFile).text()).trim();
12270
+ const n = Number(cached);
12271
+ if (!Number.isNaN(n) && n > 1)
12272
+ return cached;
12273
+ }
12274
+ const ppid = process.ppid;
12275
+ if (ppid !== undefined && ppid > 1) {
12276
+ const token2 = String(ppid);
12277
+ await Bun.write(cacheFile, token2);
12278
+ return token2;
12957
12279
  }
12958
- let tmpDir = null;
12959
12280
  try {
12960
- startSpinner("Downloading tarball...");
12961
- let extractedDir;
12962
- try {
12963
- const dl = await downloadTarball2(branch, fetchFn);
12964
- tmpDir = dl.tmpDir;
12965
- extractedDir = await extractTarball2(dl.tarball, tmpDir);
12966
- } catch (err) {
12967
- stopSpinner("Download failed");
12968
- const msg = err instanceof Error ? err.message : String(err);
12969
- result.error = msg;
12970
- process.stderr.write(`vault-sync: download failed: ${msg}
12971
- `);
12972
- return result;
12973
- }
12974
- try {
12975
- const pjText = await readFile7(join12(extractedDir, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json"), "utf8");
12976
- const pj = JSON.parse(pjText);
12977
- if (typeof pj["version"] === "string") {
12978
- result.version = pj["version"];
12281
+ const ps = Bun.spawn([
12282
+ "powershell.exe",
12283
+ "-NoProfile",
12284
+ "-NonInteractive",
12285
+ "-Command",
12286
+ "(Get-Process -Id $PID).Parent.Id"
12287
+ ], {
12288
+ stdout: "pipe",
12289
+ stderr: "pipe"
12290
+ });
12291
+ const timeoutMs = 3000;
12292
+ let timerId;
12293
+ const race = await Promise.race([
12294
+ ps.exited,
12295
+ new Promise((resolve) => {
12296
+ timerId = setTimeout(() => resolve("timeout"), timeoutMs);
12297
+ })
12298
+ ]);
12299
+ if (timerId !== undefined)
12300
+ clearTimeout(timerId);
12301
+ if (race !== "timeout") {
12302
+ const out2 = (await new Response(ps.stdout).text()).replace(/\D/g, "").trim();
12303
+ if (out2 && Number(out2) > 1) {
12304
+ await Bun.write(cacheFile, out2);
12305
+ return out2;
12979
12306
  }
12980
- } catch {}
12981
- stopSpinner(`kengio/onebrain@${branch} (v${result.version})`);
12982
- startSpinner("Syncing plugin files...");
12983
- try {
12984
- const { filesAdded, filesRemoved } = await syncPluginFiles2(extractedDir, vaultRoot, unlinkFn);
12985
- result.filesAdded = filesAdded;
12986
- result.filesRemoved = filesRemoved;
12987
- } catch (err) {
12988
- stopSpinner("Plugin sync failed");
12989
- const msg = err instanceof Error ? err.message : String(err);
12990
- result.error = msg;
12991
- process.stderr.write(`vault-sync: plugin sync failed: ${msg}
12992
- `);
12993
- return result;
12994
- }
12995
- stopSpinner(`${result.filesAdded} file${result.filesAdded !== 1 ? "s" : ""} synced, ${result.filesRemoved} removed`);
12996
- if (opts.includeObsidian) {
12997
- const sourceObsidian = join12(extractedDir, ".obsidian");
12998
- const destObsidian = join12(vaultRoot, ".obsidian");
12999
- try {
13000
- const obsidianFiles = await listFilesRecursive2(sourceObsidian);
13001
- for (const srcPath of obsidianFiles) {
13002
- const rel = relative2(sourceObsidian, srcPath);
13003
- const destPath = join12(destObsidian, rel);
13004
- await mkdir5(dirname5(destPath), { recursive: true });
13005
- const content = await readFile7(srcPath);
13006
- await writeFile6(destPath, content);
13007
- }
13008
- } catch {}
13009
- }
13010
- await copyRootDocs2(extractedDir, vaultRoot);
13011
- startSpinner("Updating harness files...");
13012
- let importsAdded = 0;
13013
- try {
13014
- importsAdded = await mergeHarnessFiles2(extractedDir, vaultRoot);
13015
- result.importsAdded = importsAdded;
13016
- } catch (err) {
13017
- stopSpinner("Harness merge failed");
13018
- const msg = err instanceof Error ? err.message : String(err);
13019
- result.error = msg;
13020
- process.stderr.write(`vault-sync: harness merge failed: ${msg}
13021
- `);
13022
- return result;
13023
- }
13024
- if (importsAdded > 0) {
13025
- stopSpinner(`${importsAdded} import${importsAdded !== 1 ? "s" : ""} added`);
13026
12307
  } else {
13027
- stopSpinner("harness files up-to-date");
13028
- }
13029
- try {
13030
- await updateVaultYml2(vaultRoot, updateChannel);
13031
- } catch (err) {
13032
- const msg = err instanceof Error ? err.message : String(err);
13033
- result.error = msg;
13034
- process.stderr.write(`vault-sync: vault.yml update failed: ${msg}
13035
- `);
13036
- return result;
12308
+ ps.kill();
13037
12309
  }
13038
- if (harness === "claude") {
13039
- startSpinner("Pinning to vault...");
13040
- try {
13041
- const pinResult = await pinToVault2(vaultRoot, installedPluginsPath, installedPluginsCacheDir);
13042
- result.pinSkipped = pinResult.skipped;
13043
- if (pinResult.skipped) {
13044
- stopSpinner("pin skipped (not found or marketplace)");
13045
- } else {
13046
- stopSpinner("installPath .claude/plugins/onebrain");
13047
- }
13048
- } catch (err) {
13049
- const msg = err instanceof Error ? err.message : String(err);
13050
- process.stderr.write(`vault-sync: pin warning: ${msg}
13051
- `);
13052
- result.pinSkipped = true;
13053
- stopSpinner("pin skipped (error non-fatal)");
13054
- }
13055
- startSpinner("Cleaning cache...");
12310
+ } catch {}
12311
+ const token = String(Math.floor(Math.random() * 90000) + 1e4);
12312
+ await Bun.write(cacheFile, token);
12313
+ return token;
12314
+ }
12315
+ async function cleanStaleStateFile(token, tmpDir) {
12316
+ try {
12317
+ const processStartMs = Date.now() - performance.now();
12318
+ const stateFile = join10(tmpDir, `onebrain-${token}.state`);
12319
+ const f2 = Bun.file(stateFile);
12320
+ const exists = await f2.exists();
12321
+ if (!exists)
12322
+ return;
12323
+ const { mtime } = await f2.stat();
12324
+ const mtimeMs = mtime instanceof Date ? mtime.getTime() : Number(mtime) * 1000;
12325
+ if (mtimeMs < processStartMs) {
13056
12326
  try {
13057
- const cacheRemoved = await cleanPluginCache2(installedPluginsPath, installedPluginsCacheDir);
13058
- result.cacheRemoved = cacheRemoved;
13059
- if (cacheRemoved > 0) {
13060
- stopSpinner(`${cacheRemoved} cached version${cacheRemoved !== 1 ? "s" : ""} removed`);
13061
- } else {
13062
- stopSpinner("no cache to clean");
13063
- }
13064
- } catch (err) {
13065
- const msg = err instanceof Error ? err.message : String(err);
13066
- process.stderr.write(`vault-sync: cache clean warning: ${msg}
13067
- `);
13068
- stopSpinner("cache clean skipped (error — non-fatal)");
13069
- }
13070
- }
13071
- result.ok = true;
13072
- if (isTTY) {
13073
- fe(`Done — v${result.version} synced`);
13074
- } else {
13075
- process.stdout.write(`vault-sync: done
13076
- `);
12327
+ await unlink2(stateFile);
12328
+ } catch {}
13077
12329
  }
13078
- } finally {
13079
- if (tmpDir) {
13080
- rm2(tmpDir, { recursive: true, force: true }).catch(() => {});
12330
+ } catch {}
12331
+ }
12332
+ async function queryQmdUnembedded() {
12333
+ try {
12334
+ const qmdArgs = process.platform === "win32" ? ["powershell.exe", "-NoProfile", "-Command", "qmd status --json"] : ["qmd", "status", "--json"];
12335
+ const proc = Bun.spawn(qmdArgs, {
12336
+ stdout: "pipe",
12337
+ stderr: "pipe"
12338
+ });
12339
+ const timeoutMs = 2000;
12340
+ let timerId;
12341
+ const race = await Promise.race([
12342
+ proc.exited,
12343
+ new Promise((resolve) => {
12344
+ timerId = setTimeout(() => resolve("timeout"), timeoutMs);
12345
+ })
12346
+ ]);
12347
+ if (timerId !== undefined)
12348
+ clearTimeout(timerId);
12349
+ if (race === "timeout") {
12350
+ proc.kill();
12351
+ return 0;
13081
12352
  }
12353
+ const stdout = await new Response(proc.stdout).text();
12354
+ const parsed = JSON.parse(stdout);
12355
+ const unembedded = parsed["unembedded"];
12356
+ return typeof unembedded === "number" ? unembedded : 0;
12357
+ } catch {
12358
+ return 0;
13082
12359
  }
13083
- return result;
13084
12360
  }
13085
- async function vaultSyncCommand2(vaultRoot, opts = {}) {
13086
- const result = await runVaultSync2(vaultRoot, opts);
13087
- if (!result.ok) {
13088
- process.exit(1);
12361
+ async function runSessionInit(vaultRoot, tmpDir = osTmpdir2()) {
12362
+ try {
12363
+ await loadVaultConfig(vaultRoot);
12364
+ } catch {
12365
+ return { decision: "block", reason: "onebrain-init-required" };
13089
12366
  }
12367
+ const sessionToken = await resolveSessionToken(tmpDir);
12368
+ await cleanStaleStateFile(sessionToken, tmpDir);
12369
+ const datetime = formatDatetime(new Date);
12370
+ const qmdUnembedded = await queryQmdUnembedded();
12371
+ return {
12372
+ datetime,
12373
+ session_token: sessionToken,
12374
+ qmd_unembedded: qmdUnembedded
12375
+ };
12376
+ }
12377
+ async function sessionInitCommand(vaultRoot) {
12378
+ const result = await runSessionInit(vaultRoot);
12379
+ process.stdout.write(`${JSON.stringify(result)}
12380
+ `);
13090
12381
  }
13091
12382
 
12383
+ // src/index.ts
12384
+ init_vault_sync();
12385
+
13092
12386
  // src/commands/update.ts
13093
12387
  var import_picocolors8 = __toESM(require_picocolors(), 1);
13094
- import { access } from "node:fs/promises";
13095
- import { join as join13 } from "node:path";
12388
+ init_cli_ui();
13096
12389
  var GITHUB_REPO = "https://api.github.com/repos/kengio/onebrain";
13097
12390
  var GITHUB_RELEASES_URL = `${GITHUB_REPO}/releases/latest`;
13098
12391
  async function fetchLatestRelease(fetchFn) {
@@ -13113,14 +12406,6 @@ async function fetchLatestRelease(fetchFn) {
13113
12406
  function formatReleaseDate(date) {
13114
12407
  return date.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });
13115
12408
  }
13116
- function daysBehind(date) {
13117
- const days = Math.floor((Date.now() - date.getTime()) / (1000 * 60 * 60 * 24));
13118
- if (days <= 0)
13119
- return "just released";
13120
- if (days === 1)
13121
- return "1 day behind";
13122
- return `${days} days behind`;
13123
- }
13124
12409
  var _windowsShell;
13125
12410
  function windowsShell() {
13126
12411
  if (_windowsShell !== undefined)
@@ -13178,7 +12463,6 @@ async function defaultCurrentVersion() {
13178
12463
  }
13179
12464
  }
13180
12465
  async function runUpdate(opts = {}) {
13181
- const vaultDir = opts.vaultDir ?? process.cwd();
13182
12466
  const isTTY = opts.isTTY ?? process.stdout.isTTY ?? false;
13183
12467
  const check = opts.check ?? false;
13184
12468
  const fetchFn = opts.fetchFn ?? globalThis.fetch;
@@ -13187,35 +12471,19 @@ async function runUpdate(opts = {}) {
13187
12471
  const currentVersionFn = opts.currentVersionFn ?? defaultCurrentVersion;
13188
12472
  const result = { ok: false, exitCode: 0 };
13189
12473
  const createStep = makeStepFn(isTTY);
13190
- const delay = opts.delayFn ?? ((ms) => new Promise((r2) => setTimeout(r2, ms)));
13191
- const randDelay = () => isTTY ? delay(Math.floor(Math.random() * 1000) + 1000) : Promise.resolve();
13192
12474
  if (isTTY) {
13193
12475
  await printBanner();
13194
12476
  } else {
13195
12477
  writeLine("OneBrain Update");
13196
12478
  }
13197
- try {
13198
- await access(join13(vaultDir, "vault.yml"));
13199
- } catch {
13200
- const msg = `vault.yml not found in ${vaultDir}. Run 'onebrain update' from inside an OneBrain vault.`;
13201
- if (isTTY) {
13202
- close(msg, true);
13203
- } else {
13204
- writeLine(`error: ${msg}`);
13205
- }
13206
- result.error = msg;
13207
- result.exitCode = 1;
13208
- return result;
13209
- }
13210
12479
  const sp1 = createStep("\uD83D\uDD0D", "Local version");
13211
12480
  const { version: currentVersion, publishedAt: localPublishedAt } = await currentVersionFn();
13212
12481
  result.currentVersion = currentVersion;
13213
- await randDelay();
13214
- const localVersionLabel = localPublishedAt ? `${import_picocolors8.default.dim(currentVersion)} ${import_picocolors8.default.dim("·")} ${import_picocolors8.default.dim(formatReleaseDate(localPublishedAt))}` : import_picocolors8.default.dim(currentVersion);
12482
+ const localVersionLabel = localPublishedAt ? `${import_picocolors8.default.dim(currentVersion)} ${import_picocolors8.default.dim("\xB7")} ${import_picocolors8.default.dim(formatReleaseDate(localPublishedAt))}` : import_picocolors8.default.dim(currentVersion);
13215
12483
  if (sp1)
13216
12484
  sp1.stop(localVersionLabel);
13217
12485
  else
13218
- writeLine(`current: ${currentVersion}${localPublishedAt ? ` (${formatReleaseDate(localPublishedAt)})` : ""}`);
12486
+ writeLine(`current: ${currentVersion}`);
13219
12487
  const sp2 = createStep("\uD83C\uDF10", "Remote version");
13220
12488
  let latestVersion;
13221
12489
  let publishedAt = null;
@@ -13223,14 +12491,11 @@ async function runUpdate(opts = {}) {
13223
12491
  const release = await fetchLatestRelease(fetchFn);
13224
12492
  latestVersion = release.version;
13225
12493
  publishedAt = release.publishedAt;
13226
- await randDelay();
13227
- const isOutdated = latestVersion !== currentVersion;
13228
- const behindSuffix = isOutdated && publishedAt ? ` ${import_picocolors8.default.dim("·")} ${import_picocolors8.default.dim(daysBehind(publishedAt))}` : "";
13229
- const dateSuffix = publishedAt ? ` ${import_picocolors8.default.dim("·")} ${import_picocolors8.default.dim(formatReleaseDate(publishedAt))}${behindSuffix}` : "";
12494
+ const dateSuffix = publishedAt ? ` ${import_picocolors8.default.dim("\xB7")} ${import_picocolors8.default.dim(formatReleaseDate(publishedAt))}` : "";
13230
12495
  if (sp2)
13231
12496
  sp2.stop(`${import_picocolors8.default.green(latestVersion)}${dateSuffix}`);
13232
12497
  else
13233
- writeLine(`latest: ${latestVersion}${isOutdated && publishedAt ? ` (${daysBehind(publishedAt)})` : ""}`);
12498
+ writeLine(`latest: ${latestVersion}`);
13234
12499
  } catch (err) {
13235
12500
  const msg = err instanceof Error ? err.message : String(err);
13236
12501
  if (sp2)
@@ -13249,12 +12514,12 @@ async function runUpdate(opts = {}) {
13249
12514
  if (check) {
13250
12515
  if (isTTY) {
13251
12516
  if (currentVersion !== latestVersion) {
13252
- barLine(`⬆️ ${import_picocolors8.default.dim(currentVersion)} ${import_picocolors8.default.green(latestVersion)} · binary would upgrade`);
12517
+ barLine(`\u2B06\uFE0F ${import_picocolors8.default.dim(currentVersion)} \u2192 ${import_picocolors8.default.green(latestVersion)} \xB7 binary would upgrade`);
13253
12518
  barBlank();
13254
12519
  }
13255
- close("Dry run complete no changes made");
12520
+ close("Dry run complete \u2014 no changes made");
13256
12521
  } else {
13257
- writeLine("done: dry run complete no changes made");
12522
+ writeLine("done: dry run complete \u2014 no changes made");
13258
12523
  }
13259
12524
  result.ok = true;
13260
12525
  result.exitCode = 0;
@@ -13262,11 +12527,9 @@ async function runUpdate(opts = {}) {
13262
12527
  }
13263
12528
  if (latestVersion === currentVersion) {
13264
12529
  if (isTTY) {
13265
- const dateSuffix = publishedAt ? ` · ${formatReleaseDate(publishedAt)}` : "";
13266
- close(`Already up to date — @onebrain-ai/cli ${import_picocolors8.default.dim(latestVersion)}${import_picocolors8.default.dim(dateSuffix)}`);
12530
+ close(`Already up to date \u2014 @onebrain-ai/cli ${import_picocolors8.default.dim(latestVersion)}`);
13267
12531
  } else {
13268
- const dateSuffix = publishedAt ? ` (${formatReleaseDate(publishedAt)})` : "";
13269
- writeLine(`already up to date: @onebrain-ai/cli ${latestVersion}${dateSuffix}`);
12532
+ writeLine(`already up to date: @onebrain-ai/cli ${latestVersion}`);
13270
12533
  writeLine("done: nothing to do");
13271
12534
  }
13272
12535
  result.ok = true;
@@ -13274,13 +12537,12 @@ async function runUpdate(opts = {}) {
13274
12537
  return result;
13275
12538
  }
13276
12539
  if (isTTY) {
13277
- barLine(`⬆️ ${import_picocolors8.default.dim(currentVersion)} ${import_picocolors8.default.green(latestVersion)}`);
12540
+ barLine(`\u2B06\uFE0F ${import_picocolors8.default.dim(currentVersion)} \u2192 ${import_picocolors8.default.green(latestVersion)}`);
13278
12541
  barBlank();
13279
12542
  }
13280
12543
  const sp3 = createStep("\uD83D\uDCE6", "Installing @onebrain-ai/cli");
13281
12544
  try {
13282
12545
  await installBinaryFn(latestVersion);
13283
- await randDelay();
13284
12546
  if (sp3)
13285
12547
  sp3.stop(import_picocolors8.default.green(latestVersion));
13286
12548
  else
@@ -13299,9 +12561,8 @@ async function runUpdate(opts = {}) {
13299
12561
  }
13300
12562
  return result;
13301
12563
  }
13302
- const sp4 = createStep("", "Validating binary");
12564
+ const sp4 = createStep("\u2705", "Validating binary");
13303
12565
  const binaryValid = await validateBinaryFn();
13304
- await randDelay();
13305
12566
  if (!binaryValid) {
13306
12567
  if (sp4)
13307
12568
  sp4.stop("failed");
@@ -13320,7 +12581,7 @@ async function runUpdate(opts = {}) {
13320
12581
  result.ok = true;
13321
12582
  result.exitCode = 0;
13322
12583
  if (isTTY) {
13323
- close(`Done run ${import_picocolors8.default.cyan("/update")} in Claude to sync vault files`);
12584
+ close(`Done \u2014 run ${import_picocolors8.default.cyan("/update")} in Claude to sync vault files`);
13324
12585
  } else {
13325
12586
  writeLine("done: run /update in Claude to sync vault files");
13326
12587
  }
@@ -13333,11 +12594,24 @@ async function updateCommand(opts = {}) {
13333
12594
  }
13334
12595
  }
13335
12596
 
12597
+ // src/lib/patch-utf8.ts
12598
+ function patchUtf8(stream) {
12599
+ const orig = stream.write.bind(stream);
12600
+ stream.write = (chunk, encodingOrCb, cb) => {
12601
+ const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
12602
+ if (typeof encodingOrCb === "function")
12603
+ return orig(buf, encodingOrCb);
12604
+ if (cb !== undefined)
12605
+ return orig(buf, cb);
12606
+ return orig(buf);
12607
+ };
12608
+ }
12609
+
13336
12610
  // src/index.ts
13337
- var VERSION = "2.1.0";
12611
+ var VERSION = "2.1.4";
13338
12612
  var RELEASE_DATE = "2026-04-28";
13339
- process.stdout.setDefaultEncoding("utf8");
13340
- process.stderr.setDefaultEncoding("utf8");
12613
+ patchUtf8(process.stdout);
12614
+ patchUtf8(process.stderr);
13341
12615
  var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
13342
12616
  if (process.argv.slice(2).length === 0) {
13343
12617
  console.log(VERSION_STRING);
@@ -13349,9 +12623,9 @@ function findVaultRoot(startDir) {
13349
12623
  return process.cwd();
13350
12624
  let dir = startDir;
13351
12625
  while (true) {
13352
- if (existsSync(join14(dir, "vault.yml")))
12626
+ if (existsSync(join11(dir, "vault.yml")))
13353
12627
  return dir;
13354
- const parent = dirname6(dir);
12628
+ const parent = dirname4(dir);
13355
12629
  if (parent === dir)
13356
12630
  return startDir;
13357
12631
  dir = parent;
@@ -13365,11 +12639,9 @@ program2.command("init").description("Initialize a new OneBrain vault").option("
13365
12639
  ...opts.force !== undefined ? { force: opts.force } : {}
13366
12640
  });
13367
12641
  });
13368
- program2.command("update").description("Update OneBrain plugin files from GitHub").option("--check", "show what would change and exit without making changes").option("--channel <channel>", "update channel: stable | next").option("--vault-dir <path>", "vault root directory (default: auto-detect from cwd)").action(async (opts) => {
12642
+ program2.command("update").description("Update @onebrain-ai/cli to the latest version").option("--check", "show what would change and exit without making changes").action(async (opts) => {
13369
12643
  await updateCommand({
13370
- vaultDir: opts.vaultDir ?? findVaultRoot(process.cwd()),
13371
- ...opts.check !== undefined ? { check: opts.check } : {},
13372
- ...opts.channel !== undefined ? { channel: opts.channel } : {}
12644
+ ...opts.check !== undefined ? { check: opts.check } : {}
13373
12645
  });
13374
12646
  });
13375
12647
  program2.command("doctor").description("Run vault health checks and report issues").option("--fix", "auto-fix detected issues").action(async (opts) => {
@@ -13400,10 +12672,10 @@ program2.command("qmd-reindex", { hidden: true }).description("Trigger qmd index
13400
12672
  });
13401
12673
  program2.command("vault-sync", { hidden: true }).description("Sync plugin files from GitHub to vault").argument("[vault_root]", "vault root directory (default: cwd)").option("--branch <branch>", "override branch (main | next)").action(async (vaultRoot, opts) => {
13402
12674
  const root = vaultRoot ?? process.cwd();
13403
- await vaultSyncCommand2(root, opts.branch !== undefined ? { branch: opts.branch } : {});
12675
+ await vaultSyncCommand(root, opts.branch !== undefined ? { branch: opts.branch } : {});
13404
12676
  });
13405
12677
  program2.command("register-hooks", { hidden: true }).description("Install Claude Code hooks into settings.json").option("--vault-dir <path>", "vault root directory (default: cwd)").action(async (opts) => {
13406
- await registerHooksCommand2(opts.vaultDir);
12678
+ await registerHooksCommand(opts.vaultDir);
13407
12679
  });
13408
12680
  program2.command("migrate", { hidden: true }).description("Run one-time migration scripts").argument("<name>", "migration name: backfill-recapped").argument("[cutoff_date]", "ISO date cutoff (YYYY-MM-DD) \u2014 skip logs newer than this date").action(async (name, cutoffDate) => {
13409
12681
  await migrateCommand(name, cutoffDate);