@onebrain-ai/cli 2.0.4 → 2.0.6

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 +147 -210
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -10210,14 +10210,18 @@ var init_register_hooks = __esm(() => {
10210
10210
  };
10211
10211
  HOOK_EVENTS = ["Stop", "PreCompact", "PostCompact", "SessionStart"];
10212
10212
  PERMISSIONS_TO_ADD = [
10213
- "Bash(onebrain:*)",
10214
- "Bash(bun install -g @onebrain-ai/cli:*)",
10215
- "Bash(npm install -g @onebrain-ai/cli:*)"
10213
+ "Bash(onebrain *)",
10214
+ "Bash(bun install -g @onebrain-ai/cli*)",
10215
+ "Bash(npm install -g @onebrain-ai/cli*)"
10216
10216
  ];
10217
10217
  BUN_BIN = join4(homedir2(), ".bun", "bin");
10218
10218
  NPM_GLOBAL_BIN = join4(homedir2(), ".npm-global", "bin");
10219
10219
  });
10220
10220
 
10221
+ // src/index.ts
10222
+ import { existsSync } from "fs";
10223
+ import { dirname as dirname6, join as join13 } from "path";
10224
+
10221
10225
  // ../../node_modules/commander/esm.mjs
10222
10226
  var import__ = __toESM(require_commander(), 1);
10223
10227
  var {
@@ -10431,7 +10435,8 @@ async function checkQmdEmbeddings(config) {
10431
10435
  };
10432
10436
  }
10433
10437
  try {
10434
- const proc = Bun.spawn(["qmd", "status", "--json"], {
10438
+ const qmdArgs = process.platform === "win32" ? ["powershell.exe", "-NoProfile", "-Command", "qmd status --json"] : ["qmd", "status", "--json"];
10439
+ const proc = Bun.spawn(qmdArgs, {
10435
10440
  stdout: "pipe",
10436
10441
  stderr: "pipe"
10437
10442
  });
@@ -10742,7 +10747,7 @@ init_dist();
10742
10747
  import { mkdir as mkdir3, readFile as readFile3, rename as rename3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
10743
10748
  import { homedir as homedir3 } from "node:os";
10744
10749
  import { dirname as dirname3, join as join5 } from "node:path";
10745
- var binaryVersion = "2.0.4";
10750
+ var binaryVersion = "2.0.6";
10746
10751
  var STANDARD_FOLDERS = [
10747
10752
  "00-inbox",
10748
10753
  "01-projects",
@@ -11023,7 +11028,7 @@ async function initCommand(opts = {}) {
11023
11028
  // src/commands/update.ts
11024
11029
  init_dist3();
11025
11030
  init_dist();
11026
- import { readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "node:fs/promises";
11031
+ import { access, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "node:fs/promises";
11027
11032
  import { join as join6 } from "node:path";
11028
11033
  var GITHUB_RELEASES_URL = "https://api.github.com/repos/kengio/onebrain/releases/latest";
11029
11034
  function resolveBranch2(channel) {
@@ -11060,9 +11065,22 @@ async function fetchLatestVersion(fetchFn) {
11060
11065
  }
11061
11066
  return tagName;
11062
11067
  }
11068
+ var _windowsShell;
11069
+ function windowsShell() {
11070
+ if (_windowsShell !== undefined)
11071
+ return _windowsShell;
11072
+ try {
11073
+ const r2 = Bun.spawnSync(["pwsh", "--version"], { stdout: "pipe", stderr: "pipe" });
11074
+ _windowsShell = r2.exitCode === 0 ? "pwsh" : "powershell.exe";
11075
+ } catch {
11076
+ _windowsShell = "powershell.exe";
11077
+ }
11078
+ return _windowsShell;
11079
+ }
11063
11080
  async function defaultInstallBinary(version) {
11064
11081
  const isWindows = process.platform === "win32";
11065
- const cmd = isWindows ? ["npm", "install", "-g", `@onebrain-ai/cli@${version}`] : ["bun", "install", "-g", `@onebrain-ai/cli@${version}`];
11082
+ const safeVersion = version.replace(/'/g, "''");
11083
+ const cmd = isWindows ? [windowsShell(), "-NoProfile", "-Command", `npm install -g '@onebrain-ai/cli@${safeVersion}'`] : ["bun", "install", "-g", `@onebrain-ai/cli@${version}`];
11066
11084
  const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
11067
11085
  const exitCode = await proc.exited;
11068
11086
  if (exitCode !== 0) {
@@ -11072,7 +11090,9 @@ async function defaultInstallBinary(version) {
11072
11090
  }
11073
11091
  async function defaultValidateBinary() {
11074
11092
  try {
11075
- const proc = Bun.spawn(["onebrain", "--version"], { stdout: "pipe", stderr: "pipe" });
11093
+ const isWindows = process.platform === "win32";
11094
+ const cmd = isWindows ? [windowsShell(), "-NoProfile", "-Command", "onebrain --version"] : ["onebrain", "--version"];
11095
+ const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
11076
11096
  const exitCode = await proc.exited;
11077
11097
  if (exitCode !== 0)
11078
11098
  return false;
@@ -11119,6 +11139,19 @@ async function runUpdate(opts = {}) {
11119
11139
  } else {
11120
11140
  writeLine("OneBrain Update");
11121
11141
  }
11142
+ try {
11143
+ await access(join6(vaultDir, "vault.yml"));
11144
+ } catch {
11145
+ const msg = `vault.yml not found in ${vaultDir}. Run 'onebrain update' from inside an OneBrain vault.`;
11146
+ if (isTTY) {
11147
+ v2.error(msg);
11148
+ } else {
11149
+ writeLine(`error: ${msg}`);
11150
+ }
11151
+ result.error = msg;
11152
+ result.exitCode = 1;
11153
+ return result;
11154
+ }
11122
11155
  let latestVersion;
11123
11156
  try {
11124
11157
  latestVersion = await fetchLatestVersion(fetchFn);
@@ -11149,30 +11182,47 @@ async function runUpdate(opts = {}) {
11149
11182
  }
11150
11183
  let filesAdded = 0;
11151
11184
  let filesRemoved = 0;
11185
+ const syncSpinner = isTTY ? L2() : null;
11186
+ syncSpinner?.start("Syncing plugin files…");
11152
11187
  try {
11153
11188
  const syncResult = await vaultSyncFn(vaultDir, { branch });
11154
11189
  filesAdded = syncResult.filesAdded;
11155
11190
  filesRemoved = syncResult.filesRemoved;
11191
+ syncSpinner?.stop(`Synced — ${filesAdded} added, ${filesRemoved} removed`);
11192
+ if (!isTTY) {
11193
+ writeLine(`syncing: ${filesAdded} files synced, ${filesRemoved} removed`);
11194
+ }
11156
11195
  } catch (err) {
11157
11196
  const msg = err instanceof Error ? err.message : String(err);
11197
+ syncSpinner?.stop("Sync failed");
11158
11198
  result.error = `vault-sync failed: ${msg}`;
11159
11199
  result.exitCode = 1;
11160
11200
  process.stderr.write(`update: ${result.error}
11161
11201
  `);
11162
11202
  return result;
11163
11203
  }
11164
- noteStep("syncing", `${filesAdded} files synced, ${filesRemoved} removed`);
11165
- try {
11166
- await installBinaryFn(latestVersion);
11167
- } catch (err) {
11168
- const msg = err instanceof Error ? err.message : String(err);
11169
- result.error = `Binary install failed: ${msg}`;
11170
- result.exitCode = 1;
11171
- process.stderr.write(`update: ${result.error}
11204
+ const needsBinaryUpdate = latestVersion !== currentVersion;
11205
+ if (needsBinaryUpdate) {
11206
+ const installSpinner = isTTY ? L2() : null;
11207
+ installSpinner?.start(`Installing @onebrain-ai/cli ${latestVersion}…`);
11208
+ try {
11209
+ await installBinaryFn(latestVersion);
11210
+ installSpinner?.stop(`Installed @onebrain-ai/cli ${latestVersion}`);
11211
+ if (!isTTY) {
11212
+ writeLine(`upgrading: @onebrain-ai/cli ${latestVersion} installed`);
11213
+ }
11214
+ } catch (err) {
11215
+ const msg = err instanceof Error ? err.message : String(err);
11216
+ installSpinner?.stop("Install failed");
11217
+ result.error = `Binary install failed: ${msg}`;
11218
+ result.exitCode = 1;
11219
+ process.stderr.write(`update: ${result.error}
11172
11220
  `);
11173
- return result;
11221
+ return result;
11222
+ }
11223
+ } else {
11224
+ noteStep("binary", `@onebrain-ai/cli ${latestVersion} already up to date`);
11174
11225
  }
11175
- noteStep("upgrading", `@onebrain-ai/cli ${latestVersion} installed`);
11176
11226
  const binaryValid = await validateBinaryFn();
11177
11227
  if (!binaryValid) {
11178
11228
  result.error = "Binary validation failed. Check PATH. register-hooks NOT called.";
@@ -11229,7 +11279,6 @@ async function updateCommand(opts = {}) {
11229
11279
 
11230
11280
  // src/internal/checkpoint.ts
11231
11281
  import { readFileSync, readdirSync, writeFileSync } from "node:fs";
11232
- import { mkdir as mkdir4, readdir as readdir2, writeFile as writeFile5 } from "node:fs/promises";
11233
11282
  import { tmpdir as osTmpdir } from "node:os";
11234
11283
  import { join as join7 } from "node:path";
11235
11284
  var SKIP_WINDOW = 60;
@@ -11251,15 +11300,13 @@ function readState(token, tmpDir = osTmpdir()) {
11251
11300
  const count = Number(parts[0]);
11252
11301
  const last_ts = Number(parts[1]);
11253
11302
  const last_stop_nn = parts[2] ?? "00";
11254
- const pending_stub = parts[3] && parts[3].length > 0 ? parts[3] : undefined;
11255
11303
  if (!Number.isInteger(count) || !Number.isInteger(last_ts) || !/^\d{2}$/.test(last_stop_nn)) {
11256
11304
  throw new Error("malformed state");
11257
11305
  }
11258
- return { count, last_ts, last_stop_nn, pending_stub };
11306
+ return { count, last_ts, last_stop_nn };
11259
11307
  } catch {
11260
- const now = Math.floor(Date.now() / 1000);
11261
11308
  try {
11262
- writeFileSync(stateFilePath(token, tmpDir), `0:${now}:00`, "utf8");
11309
+ writeFileSync(stateFilePath(token, tmpDir), "0:0:00", "utf8");
11263
11310
  } catch (writeErr) {
11264
11311
  process.stderr.write(`checkpoint: failed to rewrite state file for token ${token}: ${writeErr}
11265
11312
  `);
@@ -11273,8 +11320,7 @@ function readState(token, tmpDir = osTmpdir()) {
11273
11320
  }
11274
11321
  function writeState(token, state, tmpDir = osTmpdir()) {
11275
11322
  const path = stateFilePath(token, tmpDir);
11276
- const base = `${state.count}:${state.last_ts}:${state.last_stop_nn}`;
11277
- const content = state.pending_stub !== undefined ? `${base}:${state.pending_stub}` : base;
11323
+ const content = `${state.count}:${state.last_ts}:${state.last_stop_nn}`;
11278
11324
  try {
11279
11325
  writeFileSync(path, content, "utf8");
11280
11326
  } catch (err) {
@@ -11341,12 +11387,6 @@ function formatDate(epochSeconds) {
11341
11387
  const dd = String(d2.getDate()).padStart(2, "0");
11342
11388
  return `${yyyy}-${mm}-${dd}`;
11343
11389
  }
11344
- function formatYYYY(epochSeconds) {
11345
- return new Date(epochSeconds * 1000).getFullYear().toString();
11346
- }
11347
- function formatMM(epochSeconds) {
11348
- return String(new Date(epochSeconds * 1000).getMonth() + 1).padStart(2, "0");
11349
- }
11350
11390
  function emitBlock(reason) {
11351
11391
  process.stdout.write(`${JSON.stringify({ decision: "block", reason })}
11352
11392
  `);
@@ -11378,156 +11418,26 @@ function handleStop(token, vaultRoot, now = Math.floor(Date.now() / 1000), tmpDi
11378
11418
  const since = maxNn === 0 ? " since start" : ` since checkpoint-${String(maxNn).padStart(2, "0")}`;
11379
11419
  const filename = `${date}-${token}-checkpoint-${nextNn}.md`;
11380
11420
  emitBlock(`${filename}${since}`);
11381
- writeState(token, { count: 0, last_ts: now, last_stop_nn: nextNn, pending_stub: state.pending_stub }, tmpDir);
11421
+ writeState(token, { count: 0, last_ts: now, last_stop_nn: nextNn }, tmpDir);
11382
11422
  }
11383
- var PRECOMPACT_STUB_TEMPLATE = (date, nn) => `---
11384
- tags: [checkpoint, session-log]
11385
- date: ${date}
11386
- checkpoint: ${nn}
11387
- trigger: precompact
11388
- merged: false
11389
- ---
11390
-
11391
- ## What We Worked On
11392
-
11393
- <!-- stub: written automatically before compact — fill in via postcompact -->
11394
-
11395
- ## Key Decisions
11396
-
11397
- -
11398
-
11399
- ## Insights & Learnings
11400
-
11401
- -
11402
-
11403
- ## What Worked / Didn't Work
11404
-
11405
- -
11406
-
11407
- ## Action Items
11408
-
11409
- -
11410
-
11411
- ## Open Questions
11412
-
11413
- -
11414
- `;
11415
- async function handlePrecompact(token, vaultRoot, now = Math.floor(Date.now() / 1000), tmpDir = osTmpdir()) {
11423
+ function handlePrecompact(token, _vaultRoot, now = Math.floor(Date.now() / 1000), tmpDir = osTmpdir()) {
11416
11424
  const state = readState(token, tmpDir);
11417
11425
  if (state.last_ts > 0 && now - state.last_ts < PRECOMPACT_RECENCY) {
11418
11426
  return;
11419
11427
  }
11420
- if (state.pending_stub) {
11421
- return;
11422
- }
11423
- const date = formatDate(now);
11424
- let logsFolder = DEFAULT_LOGS_FOLDER;
11425
- try {
11426
- const config = await loadVaultConfig(vaultRoot);
11427
- logsFolder = config.folders.logs;
11428
- } catch {}
11429
- const yyyy = formatYYYY(now);
11430
- const mm = formatMM(now);
11431
- const stubDir = join7(vaultRoot, logsFolder, yyyy, mm);
11432
- const existingFiles = await readdir2(stubDir).catch(() => []);
11433
- const prefix = `${date}-${token}-checkpoint-`;
11434
- const maxNn = existingFiles.reduce((max, f2) => {
11435
- if (!f2.startsWith(prefix) || !f2.endsWith(".md"))
11436
- return max;
11437
- const m3 = f2.match(/-checkpoint-(\d{2})\.md$/);
11438
- return m3 ? Math.max(max, Number(m3[1])) : max;
11439
- }, 0);
11440
- const stubNn = String(maxNn + 1).padStart(2, "0");
11441
- const stubFilename = `${date}-${token}-checkpoint-${stubNn}.md`;
11442
- const stubPath = join7(stubDir, stubFilename);
11443
- try {
11444
- await mkdir4(stubDir, { recursive: true });
11445
- await writeFile5(stubPath, PRECOMPACT_STUB_TEMPLATE(date, stubNn), "utf8");
11446
- } catch (err) {
11447
- process.stderr.write(`checkpoint: failed to write stub file ${stubPath}: ${err}
11448
- `);
11449
- return;
11450
- }
11451
- writeState(token, {
11452
- count: 0,
11453
- last_ts: state.last_ts,
11454
- last_stop_nn: state.last_stop_nn,
11455
- pending_stub: stubFilename
11456
- }, tmpDir);
11428
+ writeState(token, { count: 0, last_ts: state.last_ts, last_stop_nn: state.last_stop_nn }, tmpDir);
11457
11429
  }
11458
- function handlePostcompact(token, vaultRoot, now = Math.floor(Date.now() / 1000), tmpDir = osTmpdir()) {
11430
+ function handlePostcompact(token, _vaultRoot, now = Math.floor(Date.now() / 1000), tmpDir = osTmpdir()) {
11459
11431
  const state = readState(token, tmpDir);
11460
- if (!state.pending_stub) {
11432
+ if (state.last_ts > 0 && now - state.last_ts < PRECOMPACT_RECENCY) {
11461
11433
  writeState(token, { count: 0, last_ts: state.last_ts, last_stop_nn: state.last_stop_nn }, tmpDir);
11462
11434
  return;
11463
11435
  }
11464
- const stubNnMatch = state.pending_stub.match(/-checkpoint-(\d{2})\.md$/);
11465
- const stubNn = stubNnMatch?.[1] ?? "01";
11466
- const stubNnNum = Number(stubNn);
11467
- const { logsFolder } = loadVaultSettings(vaultRoot);
11468
- const date = state.pending_stub.slice(0, 10);
11469
- const yyyy = date.slice(0, 4);
11470
- const mm = date.slice(5, 7);
11471
- const dir = join7(vaultRoot, logsFolder, yyyy, mm);
11472
- const prefix = `${date}-${token}-checkpoint-`;
11473
- let predecessorNn = 0;
11474
- try {
11475
- for (const f2 of readdirSync(dir)) {
11476
- if (!f2.startsWith(prefix) || !f2.endsWith(".md"))
11477
- continue;
11478
- const m3 = f2.match(/-checkpoint-(\d{2})\.md$/);
11479
- if (m3) {
11480
- const nn = Number(m3[1]);
11481
- if (nn < stubNnNum)
11482
- predecessorNn = Math.max(predecessorNn, nn);
11483
- }
11484
- }
11485
- } catch {}
11486
- const since = predecessorNn === 0 ? " since start" : ` since checkpoint-${String(predecessorNn).padStart(2, "0")}`;
11487
- emitBlock(`fill-checkpoint: ${state.pending_stub}${since}`);
11488
- writeState(token, { count: 0, last_ts: now, last_stop_nn: stubNn }, tmpDir);
11436
+ emitBlock(`auto-wrapup: ${token}`);
11437
+ writeState(token, { count: 0, last_ts: now, last_stop_nn: state.last_stop_nn }, tmpDir);
11489
11438
  }
11490
11439
  function postcompactFallback(token, vaultRoot, now = Math.floor(Date.now() / 1000), tmpDir = osTmpdir()) {
11491
- const state = readState(token, tmpDir);
11492
- if (state.pending_stub) {
11493
- handlePostcompact(token, vaultRoot, now, tmpDir);
11494
- return;
11495
- }
11496
- const { logsFolder } = loadVaultSettings(vaultRoot);
11497
- const date = formatDate(now);
11498
- const yyyy = date.slice(0, 4);
11499
- const mm = date.slice(5, 7);
11500
- const dir = join7(vaultRoot, logsFolder, yyyy, mm);
11501
- const prefix = `${date}-${token}-checkpoint-`;
11502
- const stubs = [];
11503
- const allNns = [];
11504
- try {
11505
- for (const f2 of readdirSync(dir)) {
11506
- if (!f2.startsWith(prefix) || !f2.endsWith(".md"))
11507
- continue;
11508
- const m3 = f2.match(/-checkpoint-(\d{2})\.md$/);
11509
- if (!m3)
11510
- continue;
11511
- allNns.push(Number(m3[1]));
11512
- const content = readFileSync(join7(dir, f2), "utf8");
11513
- if (/^trigger:\s*precompact/m.test(content) && !/^merged:\s*true/m.test(content)) {
11514
- stubs.push(f2);
11515
- }
11516
- }
11517
- } catch {}
11518
- if (stubs.length === 0) {
11519
- writeState(token, { count: 0, last_ts: state.last_ts, last_stop_nn: state.last_stop_nn }, tmpDir);
11520
- return;
11521
- }
11522
- stubs.sort();
11523
- const stubFilename = stubs[stubs.length - 1];
11524
- const stubNnMatch = stubFilename.match(/-checkpoint-(\d{2})\.md$/);
11525
- const stubNn = stubNnMatch?.[1] ?? "01";
11526
- const stubNnNum = Number(stubNn);
11527
- const predecessorNn = allNns.filter((n) => n < stubNnNum).reduce((max, n) => Math.max(max, n), 0);
11528
- const since = predecessorNn === 0 ? " since start" : ` since checkpoint-${String(predecessorNn).padStart(2, "0")}`;
11529
- emitBlock(`fill-checkpoint: ${stubFilename}${since}`);
11530
- writeState(token, { count: 0, last_ts: now, last_stop_nn: stubNn }, tmpDir);
11440
+ handlePostcompact(token, vaultRoot, now, tmpDir);
11531
11441
  }
11532
11442
  async function checkpointCommand(mode, token, vaultRoot) {
11533
11443
  try {
@@ -11536,7 +11446,7 @@ async function checkpointCommand(mode, token, vaultRoot) {
11536
11446
  handleStop(token, vaultRoot);
11537
11447
  break;
11538
11448
  case "precompact":
11539
- await handlePrecompact(token, vaultRoot);
11449
+ handlePrecompact(token, vaultRoot);
11540
11450
  break;
11541
11451
  case "postcompact":
11542
11452
  postcompactFallback(token, vaultRoot);
@@ -11555,7 +11465,7 @@ async function checkpointCommand(mode, token, vaultRoot) {
11555
11465
  }
11556
11466
 
11557
11467
  // src/internal/migrate.ts
11558
- import { readFile as readFile5, readdir as readdir3, writeFile as writeFile6 } from "node:fs/promises";
11468
+ import { readFile as readFile5, readdir as readdir2, writeFile as writeFile5 } from "node:fs/promises";
11559
11469
  import { join as join8 } from "node:path";
11560
11470
  init_dist();
11561
11471
  function parseFrontmatterWithRest(rawText) {
@@ -11584,7 +11494,7 @@ function parseFrontmatterWithRest(rawText) {
11584
11494
  }
11585
11495
  async function listMdFiles(dir) {
11586
11496
  try {
11587
- const entries = await readdir3(dir);
11497
+ const entries = await readdir2(dir);
11588
11498
  return entries.filter((e2) => e2.endsWith(".md"));
11589
11499
  } catch {
11590
11500
  return [];
@@ -11596,7 +11506,7 @@ async function runBackfillRecapped(logsFolder) {
11596
11506
  let skipped = 0;
11597
11507
  let yearDirs = [];
11598
11508
  try {
11599
- yearDirs = await readdir3(logsFolder);
11509
+ yearDirs = await readdir2(logsFolder);
11600
11510
  } catch {
11601
11511
  return { backfilled: 0, skipped: 0 };
11602
11512
  }
@@ -11604,7 +11514,7 @@ async function runBackfillRecapped(logsFolder) {
11604
11514
  const yearPath = join8(logsFolder, yearDir);
11605
11515
  let monthDirs = [];
11606
11516
  try {
11607
- monthDirs = await readdir3(yearPath);
11517
+ monthDirs = await readdir2(yearPath);
11608
11518
  } catch {
11609
11519
  continue;
11610
11520
  }
@@ -11634,7 +11544,7 @@ async function runBackfillRecapped(logsFolder) {
11634
11544
  const updatedContent = `---
11635
11545
  ${updatedFm}---
11636
11546
  ${rest}`;
11637
- await writeFile6(fpath, updatedContent, "utf8");
11547
+ await writeFile5(fpath, updatedContent, "utf8");
11638
11548
  backfilled++;
11639
11549
  } catch (error) {
11640
11550
  process.stderr.write(`migrate: error processing ${fname}: ${error}
@@ -11667,7 +11577,7 @@ async function migrateCommand(migrationName) {
11667
11577
 
11668
11578
  // src/internal/orphan-scan.ts
11669
11579
  init_dist();
11670
- import { readFile as readFile6, readdir as readdir4 } from "node:fs/promises";
11580
+ import { readFile as readFile6, readdir as readdir3 } from "node:fs/promises";
11671
11581
  import { join as join9 } from "node:path";
11672
11582
  function parseFrontmatter(rawText) {
11673
11583
  const text = rawText.replace(/\r\n/g, `
@@ -11696,7 +11606,7 @@ function getMonthParts(now = new Date) {
11696
11606
  }
11697
11607
  async function listMdFiles2(dir) {
11698
11608
  try {
11699
- const entries = await readdir4(dir);
11609
+ const entries = await readdir3(dir);
11700
11610
  return entries.filter((e2) => e2.endsWith(".md"));
11701
11611
  } catch {
11702
11612
  return [];
@@ -11774,6 +11684,13 @@ async function orphanScanCommand(logsFolder, sessionToken) {
11774
11684
  }
11775
11685
 
11776
11686
  // src/internal/qmd-reindex.ts
11687
+ function buildQmdSpawnArgs(collection, platform = process.platform) {
11688
+ if (platform === "win32") {
11689
+ const safe = collection.replace(/'/g, "''");
11690
+ return ["powershell.exe", "-NoProfile", "-Command", `qmd update -c '${safe}'`];
11691
+ }
11692
+ return ["qmd", "update", "-c", collection];
11693
+ }
11777
11694
  async function qmdReindexCommand(vaultRoot) {
11778
11695
  try {
11779
11696
  const config = await loadVaultConfig(vaultRoot);
@@ -11781,7 +11698,7 @@ async function qmdReindexCommand(vaultRoot) {
11781
11698
  if (!collection) {
11782
11699
  return;
11783
11700
  }
11784
- const proc = Bun.spawn(["qmd", "update", "-c", collection], {
11701
+ const proc = Bun.spawn(buildQmdSpawnArgs(collection), {
11785
11702
  detached: true,
11786
11703
  stdin: "ignore",
11787
11704
  stdout: "ignore",
@@ -11797,7 +11714,7 @@ async function qmdReindexCommand(vaultRoot) {
11797
11714
  // src/internal/register-hooks.ts
11798
11715
  init_dist3();
11799
11716
  init_dist();
11800
- import { mkdir as mkdir5, readFile as readFile7, rename as rename5, writeFile as writeFile7 } from "node:fs/promises";
11717
+ import { mkdir as mkdir4, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
11801
11718
  import { homedir as homedir4 } from "node:os";
11802
11719
  import { dirname as dirname4, join as join10 } from "node:path";
11803
11720
  var HOOK_COMMANDS2 = {
@@ -11808,9 +11725,9 @@ var HOOK_COMMANDS2 = {
11808
11725
  };
11809
11726
  var HOOK_EVENTS2 = ["Stop", "PreCompact", "PostCompact", "SessionStart"];
11810
11727
  var PERMISSIONS_TO_ADD2 = [
11811
- "Bash(onebrain:*)",
11812
- "Bash(bun install -g @onebrain-ai/cli:*)",
11813
- "Bash(npm install -g @onebrain-ai/cli:*)"
11728
+ "Bash(onebrain *)",
11729
+ "Bash(bun install -g @onebrain-ai/cli*)",
11730
+ "Bash(npm install -g @onebrain-ai/cli*)"
11814
11731
  ];
11815
11732
  var BUN_BIN2 = join10(homedir4(), ".bun", "bin");
11816
11733
  var NPM_GLOBAL_BIN2 = join10(homedir4(), ".npm-global", "bin");
@@ -11827,9 +11744,9 @@ async function readSettings2(settingsPath) {
11827
11744
  }
11828
11745
  }
11829
11746
  async function writeSettings2(settingsPath, settings) {
11830
- await mkdir5(dirname4(settingsPath), { recursive: true });
11747
+ await mkdir4(dirname4(settingsPath), { recursive: true });
11831
11748
  const tmpPath = `${settingsPath}.tmp`;
11832
- await writeFile7(tmpPath, JSON.stringify(settings, null, 4), "utf8");
11749
+ await writeFile6(tmpPath, JSON.stringify(settings, null, 4), "utf8");
11833
11750
  await rename5(tmpPath, settingsPath);
11834
11751
  }
11835
11752
  function checkHookPresence2(groups, targetCmd) {
@@ -11958,7 +11875,7 @@ ${ONEBRAIN_MARKER2}
11958
11875
  ${PATH_EXPORT2}
11959
11876
  `;
11960
11877
  const tmpPath = `${profilePath}.tmp`;
11961
- await writeFile7(tmpPath, updated, "utf8");
11878
+ await writeFile6(tmpPath, updated, "utf8");
11962
11879
  await rename5(tmpPath, profilePath);
11963
11880
  }
11964
11881
  async function runRegisterHooks2(opts = {}) {
@@ -12164,7 +12081,8 @@ async function cleanStaleStateFile(token, tmpDir) {
12164
12081
  }
12165
12082
  async function queryQmdUnembedded() {
12166
12083
  try {
12167
- const proc = Bun.spawn(["qmd", "status", "--json"], {
12084
+ const qmdArgs = process.platform === "win32" ? ["powershell.exe", "-NoProfile", "-Command", "qmd status --json"] : ["qmd", "status", "--json"];
12085
+ const proc = Bun.spawn(qmdArgs, {
12168
12086
  stdout: "pipe",
12169
12087
  stderr: "pipe"
12170
12088
  });
@@ -12216,15 +12134,15 @@ async function sessionInitCommand(vaultRoot) {
12216
12134
  init_dist3();
12217
12135
  init_dist();
12218
12136
  import {
12219
- mkdir as mkdir6,
12137
+ mkdir as mkdir5,
12220
12138
  mkdtemp as mkdtemp2,
12221
12139
  readFile as readFile8,
12222
- readdir as readdir5,
12140
+ readdir as readdir4,
12223
12141
  rename as rename6,
12224
12142
  rm as rm2,
12225
12143
  stat as stat4,
12226
12144
  unlink as unlink3,
12227
- writeFile as writeFile8
12145
+ writeFile as writeFile7
12228
12146
  } from "node:fs/promises";
12229
12147
  import { homedir as homedir5, tmpdir as tmpdir2 } from "node:os";
12230
12148
  import { dirname as dirname5, join as join12, relative as relative2 } from "node:path";
@@ -12249,7 +12167,7 @@ async function downloadTarball2(branch, fetchFn) {
12249
12167
  }
12250
12168
  async function extractTarball2(tarball, destDir) {
12251
12169
  const tarPath = join12(destDir, "bundle.tar.gz");
12252
- await writeFile8(tarPath, Buffer.from(tarball));
12170
+ await writeFile7(tarPath, Buffer.from(tarball));
12253
12171
  const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", destDir], {
12254
12172
  stdout: "pipe",
12255
12173
  stderr: "pipe"
@@ -12260,7 +12178,7 @@ async function extractTarball2(tarball, destDir) {
12260
12178
  throw new Error(`tar extraction failed (exit ${exitCode}): ${errText.trim()}`);
12261
12179
  }
12262
12180
  await unlink3(tarPath);
12263
- const entries = await readdir5(destDir);
12181
+ const entries = await readdir4(destDir);
12264
12182
  const topLevel = entries.find((e2) => e2 !== "bundle.tar.gz");
12265
12183
  if (!topLevel) {
12266
12184
  throw new Error("Extracted tarball contains no top-level directory");
@@ -12274,7 +12192,7 @@ async function listFilesRecursive2(dir) {
12274
12192
  const current = queue.pop();
12275
12193
  let entries;
12276
12194
  try {
12277
- entries = await readdir5(current);
12195
+ entries = await readdir4(current);
12278
12196
  } catch {
12279
12197
  continue;
12280
12198
  }
@@ -12298,7 +12216,7 @@ async function listFilesRecursive2(dir) {
12298
12216
  async function syncPluginFiles2(extractedDir, vaultRoot, unlinkFn = unlink3) {
12299
12217
  const sourcePlugin = join12(extractedDir, ".claude", "plugins", "onebrain");
12300
12218
  const destPlugin = join12(vaultRoot, ".claude", "plugins", "onebrain");
12301
- await mkdir6(destPlugin, { recursive: true });
12219
+ await mkdir5(destPlugin, { recursive: true });
12302
12220
  const sourceFiles = await listFilesRecursive2(sourcePlugin);
12303
12221
  const sourceRelSet = new Set(sourceFiles.map((f2) => relative2(sourcePlugin, f2)));
12304
12222
  const destFiles = await listFilesRecursive2(destPlugin);
@@ -12313,9 +12231,9 @@ async function syncPluginFiles2(extractedDir, vaultRoot, unlinkFn = unlink3) {
12313
12231
  for (const srcPath of sourceFiles) {
12314
12232
  const rel = relative2(sourcePlugin, srcPath);
12315
12233
  const destPath = join12(destPlugin, rel);
12316
- await mkdir6(dirname5(destPath), { recursive: true });
12234
+ await mkdir5(dirname5(destPath), { recursive: true });
12317
12235
  const content = await readFile8(srcPath);
12318
- await writeFile8(destPath, content);
12236
+ await writeFile7(destPath, content);
12319
12237
  filesAdded++;
12320
12238
  }
12321
12239
  let filesRemoved = 0;
@@ -12335,7 +12253,7 @@ async function copyRootDocs2(extractedDir, vaultRoot) {
12335
12253
  const destPath = join12(vaultRoot, doc);
12336
12254
  try {
12337
12255
  const content = await readFile8(srcPath);
12338
- await writeFile8(destPath, content);
12256
+ await writeFile7(destPath, content);
12339
12257
  } catch {}
12340
12258
  }
12341
12259
  }
@@ -12352,7 +12270,7 @@ async function mergeHarnessFile2(extractedDir, vaultRoot, filename) {
12352
12270
  try {
12353
12271
  vaultText = await readFile8(destPath, "utf8");
12354
12272
  } catch {
12355
- await writeFile8(destPath, repoText, "utf8");
12273
+ await writeFile7(destPath, repoText, "utf8");
12356
12274
  return repoText.split(`
12357
12275
  `).filter((l2) => l2.startsWith("@")).length;
12358
12276
  }
@@ -12373,7 +12291,7 @@ async function mergeHarnessFile2(extractedDir, vaultRoot, filename) {
12373
12291
  }
12374
12292
  const merged = vaultLines.join(`
12375
12293
  `);
12376
- await writeFile8(destPath, merged, "utf8");
12294
+ await writeFile7(destPath, merged, "utf8");
12377
12295
  return newImports.length;
12378
12296
  }
12379
12297
  async function mergeHarnessFiles2(extractedDir, vaultRoot) {
@@ -12398,7 +12316,7 @@ async function updateVaultYml2(vaultRoot, version, updateChannel) {
12398
12316
  raw.update_channel = updateChannel;
12399
12317
  const updated = $stringify(raw, { lineWidth: 0 });
12400
12318
  const tmpPath = `${vaultYmlPath}.tmp`;
12401
- await writeFile8(tmpPath, updated, "utf8");
12319
+ await writeFile7(tmpPath, updated, "utf8");
12402
12320
  await rename6(tmpPath, vaultYmlPath);
12403
12321
  }
12404
12322
  async function readPluginVersion2(vaultRoot) {
@@ -12468,7 +12386,7 @@ async function pinToVault2(vaultRoot, installedPluginsPath, installedPluginsCach
12468
12386
  return { skipped: false };
12469
12387
  }
12470
12388
  const tmpPath = `${installedPluginsPath}.tmp`;
12471
- await writeFile8(tmpPath, JSON.stringify(data, null, 4), "utf8");
12389
+ await writeFile7(tmpPath, JSON.stringify(data, null, 4), "utf8");
12472
12390
  await rename6(tmpPath, installedPluginsPath);
12473
12391
  return { skipped: false };
12474
12392
  }
@@ -12499,7 +12417,7 @@ async function cleanPluginCache2(installedPluginsPath, installedPluginsCacheDir)
12499
12417
  } catch {}
12500
12418
  if (onebrainDirs.length === 0) {
12501
12419
  try {
12502
- const marketplaceDirs = await readdir5(cacheDir);
12420
+ const marketplaceDirs = await readdir4(cacheDir);
12503
12421
  for (const mp of marketplaceDirs) {
12504
12422
  const candidate = join12(cacheDir, mp, "onebrain");
12505
12423
  try {
@@ -12515,7 +12433,7 @@ async function cleanPluginCache2(installedPluginsPath, installedPluginsCacheDir)
12515
12433
  for (const pluginDir of onebrainDirs) {
12516
12434
  let versionDirs;
12517
12435
  try {
12518
- versionDirs = await readdir5(pluginDir);
12436
+ versionDirs = await readdir4(pluginDir);
12519
12437
  } catch {
12520
12438
  continue;
12521
12439
  }
@@ -12702,14 +12620,31 @@ async function vaultSyncCommand2(vaultRoot, opts = {}) {
12702
12620
  }
12703
12621
 
12704
12622
  // src/index.ts
12705
- var VERSION = "2.0.4";
12706
- var RELEASE_DATE = "2026-04-25";
12623
+ var VERSION = "2.0.6";
12624
+ var RELEASE_DATE = "2026-04-26";
12625
+ if (process.platform === "win32") {
12626
+ process.stdout.setDefaultEncoding("utf8");
12627
+ process.stderr.setDefaultEncoding("utf8");
12628
+ }
12707
12629
  var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
12708
12630
  if (process.argv.slice(2).length === 0) {
12709
12631
  console.log(VERSION_STRING);
12710
12632
  console.log("Run `onebrain help` for available commands.");
12711
12633
  process.exit(0);
12712
12634
  }
12635
+ function findVaultRoot(startDir) {
12636
+ if (!startDir)
12637
+ return process.cwd();
12638
+ let dir = startDir;
12639
+ while (true) {
12640
+ if (existsSync(join13(dir, "vault.yml")))
12641
+ return dir;
12642
+ const parent = dirname6(dir);
12643
+ if (parent === dir)
12644
+ return startDir;
12645
+ dir = parent;
12646
+ }
12647
+ }
12713
12648
  var program2 = new Command;
12714
12649
  program2.name("onebrain").description("OneBrain CLI \u2014 personal AI OS for Obsidian").version(VERSION_STRING, "-v, --version");
12715
12650
  program2.command("init").description("Initialize a new OneBrain vault").option("--vault-dir <path>", "vault root directory (default: cwd)").option("--harness <harness>", "harness type: claude-code | gemini | direct").option("--force", "overwrite existing vault.yml without prompting").action(async (opts) => {
@@ -12719,29 +12654,31 @@ program2.command("init").description("Initialize a new OneBrain vault").option("
12719
12654
  force: opts.force
12720
12655
  });
12721
12656
  });
12722
- 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").action(async (opts) => {
12657
+ 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) => {
12723
12658
  await updateCommand({
12659
+ vaultDir: opts.vaultDir ?? findVaultRoot(process.cwd()),
12724
12660
  check: opts.check,
12725
12661
  channel: opts.channel
12726
12662
  });
12727
12663
  });
12728
12664
  program2.command("doctor").description("Run vault health checks and report issues").action(async () => {
12729
- const vaultRoot = process.cwd();
12665
+ const vaultRoot = findVaultRoot(process.cwd());
12730
12666
  await doctorCommand({ vaultDir: vaultRoot, binaryVersion: VERSION });
12731
12667
  });
12732
12668
  program2.command("help").description("Show this help message").action(() => {
12733
12669
  program2.help();
12734
12670
  });
12735
- program2.command("session-init", { hidden: true }).description("Emit session token and datetime (called by Claude Code hook)").action(async () => {
12736
- const vaultRoot = process.cwd();
12671
+ program2.command("session-init", { hidden: true }).description("Emit session token and datetime (called by Claude Code hook)").option("--vault-dir <path>", "vault root directory (default: auto-detect from cwd)").action(async (opts) => {
12672
+ const vaultRoot = opts.vaultDir ?? findVaultRoot(process.cwd());
12737
12673
  await sessionInitCommand(vaultRoot);
12738
12674
  });
12739
12675
  program2.command("orphan-scan", { hidden: true }).description("Scan for orphaned checkpoint files in logs folder").argument("<logs_folder>", "path to logs folder").argument("<session_token>", "current session token to exclude").action(async (logsFolder, sessionToken) => {
12740
12676
  await orphanScanCommand(logsFolder, sessionToken);
12741
12677
  });
12742
- program2.command("checkpoint", { hidden: true }).description("Handle checkpoint lifecycle (stop/precompact/postcompact/reset)").argument("<mode>", "stop | precompact | postcompact | reset").action(async (mode) => {
12678
+ program2.command("checkpoint", { hidden: true }).description("Handle checkpoint lifecycle (stop/precompact/postcompact/reset)").argument("<mode>", "stop | precompact | postcompact | reset").option("--vault-dir <path>", "vault root directory (default: auto-detect from cwd)").action(async (mode, opts) => {
12743
12679
  const token = await resolveSessionToken();
12744
- await checkpointCommand(mode, token, process.cwd());
12680
+ const vaultRoot = opts.vaultDir ?? findVaultRoot(process.cwd());
12681
+ await checkpointCommand(mode, token, vaultRoot);
12745
12682
  });
12746
12683
  program2.command("qmd-reindex", { hidden: true }).description("Trigger qmd index rebuild").action(async () => {
12747
12684
  const vaultRoot = process.cwd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
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",