@onebrain-ai/cli 2.1.12 → 2.1.14

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.
Files changed (2) hide show
  1. package/dist/onebrain +163 -13
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -9521,7 +9521,7 @@ var init_lib = __esm(() => {
9521
9521
  var require_package = __commonJS((exports, module) => {
9522
9522
  module.exports = {
9523
9523
  name: "@onebrain-ai/cli",
9524
- version: "2.1.12",
9524
+ version: "2.1.14",
9525
9525
  description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9526
9526
  keywords: [
9527
9527
  "onebrain",
@@ -10086,15 +10086,70 @@ function applyHooks(settings) {
10086
10086
  }
10087
10087
  return result;
10088
10088
  }
10089
+ function isLegacyQmdCmd(cmd) {
10090
+ return /\bqmd\s+update\b/.test(cmd);
10091
+ }
10092
+ function migrateLegacyQmdEntries(groups, keepCanonical) {
10093
+ let touched = false;
10094
+ for (const group of groups) {
10095
+ if (!group.hooks)
10096
+ continue;
10097
+ if (keepCanonical) {
10098
+ let groupTouched = false;
10099
+ for (const entry of group.hooks) {
10100
+ if (isLegacyQmdCmd(entry.command ?? "")) {
10101
+ entry.command = QMD_CMD;
10102
+ if (!entry.type)
10103
+ entry.type = "command";
10104
+ groupTouched = true;
10105
+ }
10106
+ }
10107
+ if (groupTouched) {
10108
+ group.matcher = QMD_MATCHER;
10109
+ touched = true;
10110
+ }
10111
+ } else {
10112
+ const before = group.hooks.length;
10113
+ group.hooks = group.hooks.filter((h) => !isLegacyQmdCmd(h.command ?? ""));
10114
+ if (group.hooks.length !== before)
10115
+ touched = true;
10116
+ }
10117
+ }
10118
+ if (keepCanonical) {
10119
+ let seenCanonical = false;
10120
+ for (const group of groups) {
10121
+ if (!group.hooks)
10122
+ continue;
10123
+ const before = group.hooks.length;
10124
+ group.hooks = group.hooks.filter((h) => {
10125
+ if (h.command !== QMD_CMD)
10126
+ return true;
10127
+ if (seenCanonical)
10128
+ return false;
10129
+ seenCanonical = true;
10130
+ return true;
10131
+ });
10132
+ if (group.hooks.length !== before)
10133
+ touched = true;
10134
+ }
10135
+ }
10136
+ for (let i = groups.length - 1;i >= 0; i--) {
10137
+ const g = groups[i];
10138
+ if (g && (g.hooks?.length ?? 0) === 0)
10139
+ groups.splice(i, 1);
10140
+ }
10141
+ return touched;
10142
+ }
10089
10143
  function applyQmdHook(settings) {
10090
10144
  if (!settings.hooks)
10091
10145
  settings.hooks = {};
10092
10146
  if (!settings.hooks["PostToolUse"])
10093
10147
  settings.hooks["PostToolUse"] = [];
10094
10148
  const groups = settings.hooks["PostToolUse"];
10149
+ const migrated = migrateLegacyQmdEntries(groups, true);
10095
10150
  const already = groups.some((g) => g.hooks?.some((h) => h.command === QMD_CMD));
10096
10151
  if (already)
10097
- return "ok";
10152
+ return migrated ? "migrated" : "ok";
10098
10153
  groups.push({ matcher: QMD_MATCHER, hooks: [{ type: "command", command: QMD_CMD }] });
10099
10154
  return "added";
10100
10155
  }
@@ -10186,16 +10241,25 @@ async function runRegisterHooks(opts = {}) {
10186
10241
  const settings = await readSettings(settingsPath);
10187
10242
  result.hooks = applyHooks(settings);
10188
10243
  let qmdStatus;
10189
- if (qmdCollection)
10244
+ if (qmdCollection) {
10190
10245
  qmdStatus = applyQmdHook(settings);
10246
+ } else {
10247
+ const groups = settings.hooks?.["PostToolUse"] ?? [];
10248
+ const stripped = migrateLegacyQmdEntries(groups, false);
10249
+ if (stripped && groups.length === 0 && settings.hooks) {
10250
+ delete settings.hooks["PostToolUse"];
10251
+ }
10252
+ }
10191
10253
  if (isTTY) {
10192
10254
  const parts = HOOK_EVENTS.map((e2) => {
10193
10255
  const status = result.hooks[e2];
10194
10256
  const icon = import_picocolors4.default.green(status === "ok" ? "\u2713" : status === "migrated" ? "\u2191" : "+");
10195
10257
  return `${import_picocolors4.default.dim(e2)} ${icon}`;
10196
10258
  });
10197
- if (qmdStatus)
10198
- parts.push(`${import_picocolors4.default.dim("PostToolUse")} ${import_picocolors4.default.green(qmdStatus === "ok" ? "\u2713" : "+")}`);
10259
+ if (qmdStatus) {
10260
+ const qmdIcon = qmdStatus === "ok" ? "\u2713" : qmdStatus === "migrated" ? "\u2191" : "+";
10261
+ parts.push(`${import_picocolors4.default.dim("PostToolUse")} ${import_picocolors4.default.green(qmdIcon)}`);
10262
+ }
10199
10263
  hooksSpinner?.stop(`Hooks ${parts.join(" ")}`);
10200
10264
  } else {
10201
10265
  const hookLine = HOOK_EVENTS.map((e2) => {
@@ -10205,7 +10269,7 @@ async function runRegisterHooks(opts = {}) {
10205
10269
  }).join(" ");
10206
10270
  note(hookLine);
10207
10271
  if (qmdStatus)
10208
- note(`PostToolUse ${qmdStatus === "added" ? "added" : "ok"}`);
10272
+ note(`PostToolUse ${qmdStatus}`);
10209
10273
  }
10210
10274
  permSpinner = isTTY ? L2() : null;
10211
10275
  permSpinner?.start("Updating permissions...");
@@ -10858,7 +10922,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
10858
10922
  var import_picocolors = __toESM(require_picocolors(), 1);
10859
10923
  function resolveBinaryVersion() {
10860
10924
  if (true)
10861
- return "2.1.12";
10925
+ return "2.1.14";
10862
10926
  try {
10863
10927
  const pkg = require_package();
10864
10928
  return pkg.version ?? "dev";
@@ -12632,7 +12696,74 @@ function formatDatetime(date) {
12632
12696
  const mm = String(date.getMinutes()).padStart(2, "0");
12633
12697
  return `${dow} \xB7 ${day} ${mon} ${year} \xB7 ${hh}:${mm}`;
12634
12698
  }
12635
- async function resolveSessionToken(tmpDir = osTmpdir2()) {
12699
+ function commBasenameOf(raw) {
12700
+ return raw.trim().replace(/^.*[/\\]/, "").replace(/\.exe$/i, "");
12701
+ }
12702
+ var defaultProcLookup = (pid) => {
12703
+ if (process.platform === "win32")
12704
+ return null;
12705
+ let result;
12706
+ try {
12707
+ result = Bun.spawnSync(["ps", "-o", "ppid=,comm=", "-p", String(pid)], {
12708
+ stdout: "pipe",
12709
+ stderr: "pipe"
12710
+ });
12711
+ } catch (err) {
12712
+ const code = err?.code;
12713
+ process.stderr.write(`onebrain: ps spawn failed for pid ${pid} (${code ?? "unknown"})
12714
+ `);
12715
+ return null;
12716
+ }
12717
+ if (!result.success) {
12718
+ process.stderr.write(`onebrain: ps -p ${pid} exited ${result.exitCode}
12719
+ `);
12720
+ return null;
12721
+ }
12722
+ const out2 = new TextDecoder().decode(result.stdout).trim();
12723
+ if (!out2) {
12724
+ process.stderr.write(`onebrain: ps -p ${pid} returned empty output
12725
+ `);
12726
+ return null;
12727
+ }
12728
+ if (out2.includes(`
12729
+ `)) {
12730
+ process.stderr.write(`onebrain: ps -p ${pid} returned multi-line output: ${out2.replace(/\n/g, " | ").slice(0, 120)}
12731
+ `);
12732
+ return null;
12733
+ }
12734
+ const match = out2.match(/^\s*(\d+)\s+(.+)$/);
12735
+ if (!match) {
12736
+ process.stderr.write(`onebrain: ps -p ${pid} unparseable: ${out2.slice(0, 120)}
12737
+ `);
12738
+ return null;
12739
+ }
12740
+ const ppid = Number(match[1]);
12741
+ if (Number.isNaN(ppid))
12742
+ return null;
12743
+ return { ppid, commBasename: commBasenameOf(match[2] ?? "") };
12744
+ };
12745
+ function findClaudeAncestorPid(startPid, lookup = defaultProcLookup, maxDepth = 12) {
12746
+ let current = startPid;
12747
+ for (let i = 0;i < maxDepth; i++) {
12748
+ if (current <= 1)
12749
+ return null;
12750
+ const info = lookup(current);
12751
+ if (!info)
12752
+ return null;
12753
+ if (info.commBasename === "claude")
12754
+ return current;
12755
+ if (info.ppid <= 1) {
12756
+ process.stderr.write(`onebrain: walk-up reached init from pid ${startPid} without finding claude (last comm=${info.commBasename})
12757
+ `);
12758
+ return null;
12759
+ }
12760
+ current = info.ppid;
12761
+ }
12762
+ process.stderr.write(`onebrain: walk-up exhausted ${maxDepth} hops from pid ${startPid} without finding claude
12763
+ `);
12764
+ return null;
12765
+ }
12766
+ async function resolveSessionToken(tmpDir = osTmpdir2(), procLookup = defaultProcLookup) {
12636
12767
  const wtSession = process.env["WT_SESSION"];
12637
12768
  if (wtSession) {
12638
12769
  const stripped = wtSession.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
@@ -12651,6 +12782,13 @@ async function resolveSessionToken(tmpDir = osTmpdir2()) {
12651
12782
  if (stripped.length > 0)
12652
12783
  return stripped;
12653
12784
  }
12785
+ const startPpid = process.ppid;
12786
+ if (startPpid !== undefined && startPpid > 1) {
12787
+ const claudePid = findClaudeAncestorPid(startPpid, procLookup);
12788
+ if (claudePid !== null && claudePid > 1) {
12789
+ return String(claudePid);
12790
+ }
12791
+ }
12654
12792
  const today = new Date;
12655
12793
  const yyyymmdd = [
12656
12794
  today.getFullYear(),
@@ -12719,9 +12857,21 @@ async function cleanStaleStateFile(token, tmpDir) {
12719
12857
  if (mtimeMs < processStartMs) {
12720
12858
  try {
12721
12859
  await unlink3(stateFile);
12722
- } catch {}
12860
+ } catch (err) {
12861
+ const code = err?.code;
12862
+ if (code !== "ENOENT") {
12863
+ process.stderr.write(`onebrain: cannot remove stale state file ${stateFile} (${code ?? "unknown"})
12864
+ `);
12865
+ }
12866
+ }
12723
12867
  }
12724
- } catch {}
12868
+ } catch (err) {
12869
+ const code = err?.code;
12870
+ if (code && code !== "ENOENT") {
12871
+ process.stderr.write(`onebrain: cleanStaleStateFile failed (${code})
12872
+ `);
12873
+ }
12874
+ }
12725
12875
  }
12726
12876
  async function queryQmdUnembedded() {
12727
12877
  try {
@@ -12752,13 +12902,13 @@ async function queryQmdUnembedded() {
12752
12902
  return 0;
12753
12903
  }
12754
12904
  }
12755
- async function runSessionInit(vaultRoot, tmpDir = osTmpdir2()) {
12905
+ async function runSessionInit(vaultRoot, tmpDir = osTmpdir2(), procLookup = defaultProcLookup) {
12756
12906
  try {
12757
12907
  await loadVaultConfig(vaultRoot);
12758
12908
  } catch {
12759
12909
  return { decision: "block", reason: "onebrain-init-required" };
12760
12910
  }
12761
- const sessionToken = await resolveSessionToken(tmpDir);
12911
+ const sessionToken = await resolveSessionToken(tmpDir, procLookup);
12762
12912
  await cleanStaleStateFile(sessionToken, tmpDir);
12763
12913
  const datetime = formatDatetime(new Date);
12764
12914
  const qmdUnembedded = await queryQmdUnembedded();
@@ -13002,7 +13152,7 @@ function patchUtf8(stream) {
13002
13152
  }
13003
13153
 
13004
13154
  // src/index.ts
13005
- var VERSION = "2.1.12";
13155
+ var VERSION = "2.1.14";
13006
13156
  var RELEASE_DATE = "2026-05-06";
13007
13157
  patchUtf8(process.stdout);
13008
13158
  patchUtf8(process.stderr);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.1.12",
3
+ "version": "2.1.14",
4
4
  "description": "CLI for OneBrain — personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
5
5
  "keywords": [
6
6
  "onebrain",