@onebrain-ai/cli 2.0.5 → 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 +107 -198
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -10218,6 +10218,10 @@ var init_register_hooks = __esm(() => {
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 {
@@ -10743,7 +10747,7 @@ init_dist();
10743
10747
  import { mkdir as mkdir3, readFile as readFile3, rename as rename3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
10744
10748
  import { homedir as homedir3 } from "node:os";
10745
10749
  import { dirname as dirname3, join as join5 } from "node:path";
10746
- var binaryVersion = "2.0.5";
10750
+ var binaryVersion = "2.0.6";
10747
10751
  var STANDARD_FOLDERS = [
10748
10752
  "00-inbox",
10749
10753
  "01-projects",
@@ -11024,7 +11028,7 @@ async function initCommand(opts = {}) {
11024
11028
  // src/commands/update.ts
11025
11029
  init_dist3();
11026
11030
  init_dist();
11027
- 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";
11028
11032
  import { join as join6 } from "node:path";
11029
11033
  var GITHUB_RELEASES_URL = "https://api.github.com/repos/kengio/onebrain/releases/latest";
11030
11034
  function resolveBranch2(channel) {
@@ -11135,6 +11139,19 @@ async function runUpdate(opts = {}) {
11135
11139
  } else {
11136
11140
  writeLine("OneBrain Update");
11137
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
+ }
11138
11155
  let latestVersion;
11139
11156
  try {
11140
11157
  latestVersion = await fetchLatestVersion(fetchFn);
@@ -11165,30 +11182,47 @@ async function runUpdate(opts = {}) {
11165
11182
  }
11166
11183
  let filesAdded = 0;
11167
11184
  let filesRemoved = 0;
11185
+ const syncSpinner = isTTY ? L2() : null;
11186
+ syncSpinner?.start("Syncing plugin files…");
11168
11187
  try {
11169
11188
  const syncResult = await vaultSyncFn(vaultDir, { branch });
11170
11189
  filesAdded = syncResult.filesAdded;
11171
11190
  filesRemoved = syncResult.filesRemoved;
11191
+ syncSpinner?.stop(`Synced — ${filesAdded} added, ${filesRemoved} removed`);
11192
+ if (!isTTY) {
11193
+ writeLine(`syncing: ${filesAdded} files synced, ${filesRemoved} removed`);
11194
+ }
11172
11195
  } catch (err) {
11173
11196
  const msg = err instanceof Error ? err.message : String(err);
11197
+ syncSpinner?.stop("Sync failed");
11174
11198
  result.error = `vault-sync failed: ${msg}`;
11175
11199
  result.exitCode = 1;
11176
11200
  process.stderr.write(`update: ${result.error}
11177
11201
  `);
11178
11202
  return result;
11179
11203
  }
11180
- noteStep("syncing", `${filesAdded} files synced, ${filesRemoved} removed`);
11181
- try {
11182
- await installBinaryFn(latestVersion);
11183
- } catch (err) {
11184
- const msg = err instanceof Error ? err.message : String(err);
11185
- result.error = `Binary install failed: ${msg}`;
11186
- result.exitCode = 1;
11187
- 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}
11188
11220
  `);
11189
- return result;
11221
+ return result;
11222
+ }
11223
+ } else {
11224
+ noteStep("binary", `@onebrain-ai/cli ${latestVersion} already up to date`);
11190
11225
  }
11191
- noteStep("upgrading", `@onebrain-ai/cli ${latestVersion} installed`);
11192
11226
  const binaryValid = await validateBinaryFn();
11193
11227
  if (!binaryValid) {
11194
11228
  result.error = "Binary validation failed. Check PATH. register-hooks NOT called.";
@@ -11245,7 +11279,6 @@ async function updateCommand(opts = {}) {
11245
11279
 
11246
11280
  // src/internal/checkpoint.ts
11247
11281
  import { readFileSync, readdirSync, writeFileSync } from "node:fs";
11248
- import { mkdir as mkdir4, readdir as readdir2, writeFile as writeFile5 } from "node:fs/promises";
11249
11282
  import { tmpdir as osTmpdir } from "node:os";
11250
11283
  import { join as join7 } from "node:path";
11251
11284
  var SKIP_WINDOW = 60;
@@ -11267,15 +11300,13 @@ function readState(token, tmpDir = osTmpdir()) {
11267
11300
  const count = Number(parts[0]);
11268
11301
  const last_ts = Number(parts[1]);
11269
11302
  const last_stop_nn = parts[2] ?? "00";
11270
- const pending_stub = parts[3] && parts[3].length > 0 ? parts[3] : undefined;
11271
11303
  if (!Number.isInteger(count) || !Number.isInteger(last_ts) || !/^\d{2}$/.test(last_stop_nn)) {
11272
11304
  throw new Error("malformed state");
11273
11305
  }
11274
- return { count, last_ts, last_stop_nn, pending_stub };
11306
+ return { count, last_ts, last_stop_nn };
11275
11307
  } catch {
11276
- const now = Math.floor(Date.now() / 1000);
11277
11308
  try {
11278
- writeFileSync(stateFilePath(token, tmpDir), `0:${now}:00`, "utf8");
11309
+ writeFileSync(stateFilePath(token, tmpDir), "0:0:00", "utf8");
11279
11310
  } catch (writeErr) {
11280
11311
  process.stderr.write(`checkpoint: failed to rewrite state file for token ${token}: ${writeErr}
11281
11312
  `);
@@ -11289,8 +11320,7 @@ function readState(token, tmpDir = osTmpdir()) {
11289
11320
  }
11290
11321
  function writeState(token, state, tmpDir = osTmpdir()) {
11291
11322
  const path = stateFilePath(token, tmpDir);
11292
- const base = `${state.count}:${state.last_ts}:${state.last_stop_nn}`;
11293
- const content = state.pending_stub !== undefined ? `${base}:${state.pending_stub}` : base;
11323
+ const content = `${state.count}:${state.last_ts}:${state.last_stop_nn}`;
11294
11324
  try {
11295
11325
  writeFileSync(path, content, "utf8");
11296
11326
  } catch (err) {
@@ -11357,12 +11387,6 @@ function formatDate(epochSeconds) {
11357
11387
  const dd = String(d2.getDate()).padStart(2, "0");
11358
11388
  return `${yyyy}-${mm}-${dd}`;
11359
11389
  }
11360
- function formatYYYY(epochSeconds) {
11361
- return new Date(epochSeconds * 1000).getFullYear().toString();
11362
- }
11363
- function formatMM(epochSeconds) {
11364
- return String(new Date(epochSeconds * 1000).getMonth() + 1).padStart(2, "0");
11365
- }
11366
11390
  function emitBlock(reason) {
11367
11391
  process.stdout.write(`${JSON.stringify({ decision: "block", reason })}
11368
11392
  `);
@@ -11394,156 +11418,26 @@ function handleStop(token, vaultRoot, now = Math.floor(Date.now() / 1000), tmpDi
11394
11418
  const since = maxNn === 0 ? " since start" : ` since checkpoint-${String(maxNn).padStart(2, "0")}`;
11395
11419
  const filename = `${date}-${token}-checkpoint-${nextNn}.md`;
11396
11420
  emitBlock(`${filename}${since}`);
11397
- 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);
11398
11422
  }
11399
- var PRECOMPACT_STUB_TEMPLATE = (date, nn) => `---
11400
- tags: [checkpoint, session-log]
11401
- date: ${date}
11402
- checkpoint: ${nn}
11403
- trigger: precompact
11404
- merged: false
11405
- ---
11406
-
11407
- ## What We Worked On
11408
-
11409
- <!-- stub: written automatically before compact — fill in via postcompact -->
11410
-
11411
- ## Key Decisions
11412
-
11413
- -
11414
-
11415
- ## Insights & Learnings
11416
-
11417
- -
11418
-
11419
- ## What Worked / Didn't Work
11420
-
11421
- -
11422
-
11423
- ## Action Items
11424
-
11425
- -
11426
-
11427
- ## Open Questions
11428
-
11429
- -
11430
- `;
11431
- 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()) {
11432
11424
  const state = readState(token, tmpDir);
11433
11425
  if (state.last_ts > 0 && now - state.last_ts < PRECOMPACT_RECENCY) {
11434
11426
  return;
11435
11427
  }
11436
- if (state.pending_stub) {
11437
- return;
11438
- }
11439
- const date = formatDate(now);
11440
- let logsFolder = DEFAULT_LOGS_FOLDER;
11441
- try {
11442
- const config = await loadVaultConfig(vaultRoot);
11443
- logsFolder = config.folders.logs;
11444
- } catch {}
11445
- const yyyy = formatYYYY(now);
11446
- const mm = formatMM(now);
11447
- const stubDir = join7(vaultRoot, logsFolder, yyyy, mm);
11448
- const existingFiles = await readdir2(stubDir).catch(() => []);
11449
- const prefix = `${date}-${token}-checkpoint-`;
11450
- const maxNn = existingFiles.reduce((max, f2) => {
11451
- if (!f2.startsWith(prefix) || !f2.endsWith(".md"))
11452
- return max;
11453
- const m3 = f2.match(/-checkpoint-(\d{2})\.md$/);
11454
- return m3 ? Math.max(max, Number(m3[1])) : max;
11455
- }, 0);
11456
- const stubNn = String(maxNn + 1).padStart(2, "0");
11457
- const stubFilename = `${date}-${token}-checkpoint-${stubNn}.md`;
11458
- const stubPath = join7(stubDir, stubFilename);
11459
- try {
11460
- await mkdir4(stubDir, { recursive: true });
11461
- await writeFile5(stubPath, PRECOMPACT_STUB_TEMPLATE(date, stubNn), "utf8");
11462
- } catch (err) {
11463
- process.stderr.write(`checkpoint: failed to write stub file ${stubPath}: ${err}
11464
- `);
11465
- return;
11466
- }
11467
- writeState(token, {
11468
- count: 0,
11469
- last_ts: state.last_ts,
11470
- last_stop_nn: state.last_stop_nn,
11471
- pending_stub: stubFilename
11472
- }, tmpDir);
11428
+ writeState(token, { count: 0, last_ts: state.last_ts, last_stop_nn: state.last_stop_nn }, tmpDir);
11473
11429
  }
11474
- 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()) {
11475
11431
  const state = readState(token, tmpDir);
11476
- if (!state.pending_stub) {
11432
+ if (state.last_ts > 0 && now - state.last_ts < PRECOMPACT_RECENCY) {
11477
11433
  writeState(token, { count: 0, last_ts: state.last_ts, last_stop_nn: state.last_stop_nn }, tmpDir);
11478
11434
  return;
11479
11435
  }
11480
- const stubNnMatch = state.pending_stub.match(/-checkpoint-(\d{2})\.md$/);
11481
- const stubNn = stubNnMatch?.[1] ?? "01";
11482
- const stubNnNum = Number(stubNn);
11483
- const { logsFolder } = loadVaultSettings(vaultRoot);
11484
- const date = state.pending_stub.slice(0, 10);
11485
- const yyyy = date.slice(0, 4);
11486
- const mm = date.slice(5, 7);
11487
- const dir = join7(vaultRoot, logsFolder, yyyy, mm);
11488
- const prefix = `${date}-${token}-checkpoint-`;
11489
- let predecessorNn = 0;
11490
- try {
11491
- for (const f2 of readdirSync(dir)) {
11492
- if (!f2.startsWith(prefix) || !f2.endsWith(".md"))
11493
- continue;
11494
- const m3 = f2.match(/-checkpoint-(\d{2})\.md$/);
11495
- if (m3) {
11496
- const nn = Number(m3[1]);
11497
- if (nn < stubNnNum)
11498
- predecessorNn = Math.max(predecessorNn, nn);
11499
- }
11500
- }
11501
- } catch {}
11502
- const since = predecessorNn === 0 ? " since start" : ` since checkpoint-${String(predecessorNn).padStart(2, "0")}`;
11503
- emitBlock(`fill-checkpoint: ${state.pending_stub}${since}`);
11504
- 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);
11505
11438
  }
11506
11439
  function postcompactFallback(token, vaultRoot, now = Math.floor(Date.now() / 1000), tmpDir = osTmpdir()) {
11507
- const state = readState(token, tmpDir);
11508
- if (state.pending_stub) {
11509
- handlePostcompact(token, vaultRoot, now, tmpDir);
11510
- return;
11511
- }
11512
- const { logsFolder } = loadVaultSettings(vaultRoot);
11513
- const date = formatDate(now);
11514
- const yyyy = date.slice(0, 4);
11515
- const mm = date.slice(5, 7);
11516
- const dir = join7(vaultRoot, logsFolder, yyyy, mm);
11517
- const prefix = `${date}-${token}-checkpoint-`;
11518
- const stubs = [];
11519
- const allNns = [];
11520
- try {
11521
- for (const f2 of readdirSync(dir)) {
11522
- if (!f2.startsWith(prefix) || !f2.endsWith(".md"))
11523
- continue;
11524
- const m3 = f2.match(/-checkpoint-(\d{2})\.md$/);
11525
- if (!m3)
11526
- continue;
11527
- allNns.push(Number(m3[1]));
11528
- const content = readFileSync(join7(dir, f2), "utf8");
11529
- if (/^trigger:\s*precompact/m.test(content) && !/^merged:\s*true/m.test(content)) {
11530
- stubs.push(f2);
11531
- }
11532
- }
11533
- } catch {}
11534
- if (stubs.length === 0) {
11535
- writeState(token, { count: 0, last_ts: state.last_ts, last_stop_nn: state.last_stop_nn }, tmpDir);
11536
- return;
11537
- }
11538
- stubs.sort();
11539
- const stubFilename = stubs[stubs.length - 1];
11540
- const stubNnMatch = stubFilename.match(/-checkpoint-(\d{2})\.md$/);
11541
- const stubNn = stubNnMatch?.[1] ?? "01";
11542
- const stubNnNum = Number(stubNn);
11543
- const predecessorNn = allNns.filter((n) => n < stubNnNum).reduce((max, n) => Math.max(max, n), 0);
11544
- const since = predecessorNn === 0 ? " since start" : ` since checkpoint-${String(predecessorNn).padStart(2, "0")}`;
11545
- emitBlock(`fill-checkpoint: ${stubFilename}${since}`);
11546
- writeState(token, { count: 0, last_ts: now, last_stop_nn: stubNn }, tmpDir);
11440
+ handlePostcompact(token, vaultRoot, now, tmpDir);
11547
11441
  }
11548
11442
  async function checkpointCommand(mode, token, vaultRoot) {
11549
11443
  try {
@@ -11552,7 +11446,7 @@ async function checkpointCommand(mode, token, vaultRoot) {
11552
11446
  handleStop(token, vaultRoot);
11553
11447
  break;
11554
11448
  case "precompact":
11555
- await handlePrecompact(token, vaultRoot);
11449
+ handlePrecompact(token, vaultRoot);
11556
11450
  break;
11557
11451
  case "postcompact":
11558
11452
  postcompactFallback(token, vaultRoot);
@@ -11571,7 +11465,7 @@ async function checkpointCommand(mode, token, vaultRoot) {
11571
11465
  }
11572
11466
 
11573
11467
  // src/internal/migrate.ts
11574
- 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";
11575
11469
  import { join as join8 } from "node:path";
11576
11470
  init_dist();
11577
11471
  function parseFrontmatterWithRest(rawText) {
@@ -11600,7 +11494,7 @@ function parseFrontmatterWithRest(rawText) {
11600
11494
  }
11601
11495
  async function listMdFiles(dir) {
11602
11496
  try {
11603
- const entries = await readdir3(dir);
11497
+ const entries = await readdir2(dir);
11604
11498
  return entries.filter((e2) => e2.endsWith(".md"));
11605
11499
  } catch {
11606
11500
  return [];
@@ -11612,7 +11506,7 @@ async function runBackfillRecapped(logsFolder) {
11612
11506
  let skipped = 0;
11613
11507
  let yearDirs = [];
11614
11508
  try {
11615
- yearDirs = await readdir3(logsFolder);
11509
+ yearDirs = await readdir2(logsFolder);
11616
11510
  } catch {
11617
11511
  return { backfilled: 0, skipped: 0 };
11618
11512
  }
@@ -11620,7 +11514,7 @@ async function runBackfillRecapped(logsFolder) {
11620
11514
  const yearPath = join8(logsFolder, yearDir);
11621
11515
  let monthDirs = [];
11622
11516
  try {
11623
- monthDirs = await readdir3(yearPath);
11517
+ monthDirs = await readdir2(yearPath);
11624
11518
  } catch {
11625
11519
  continue;
11626
11520
  }
@@ -11650,7 +11544,7 @@ async function runBackfillRecapped(logsFolder) {
11650
11544
  const updatedContent = `---
11651
11545
  ${updatedFm}---
11652
11546
  ${rest}`;
11653
- await writeFile6(fpath, updatedContent, "utf8");
11547
+ await writeFile5(fpath, updatedContent, "utf8");
11654
11548
  backfilled++;
11655
11549
  } catch (error) {
11656
11550
  process.stderr.write(`migrate: error processing ${fname}: ${error}
@@ -11683,7 +11577,7 @@ async function migrateCommand(migrationName) {
11683
11577
 
11684
11578
  // src/internal/orphan-scan.ts
11685
11579
  init_dist();
11686
- import { readFile as readFile6, readdir as readdir4 } from "node:fs/promises";
11580
+ import { readFile as readFile6, readdir as readdir3 } from "node:fs/promises";
11687
11581
  import { join as join9 } from "node:path";
11688
11582
  function parseFrontmatter(rawText) {
11689
11583
  const text = rawText.replace(/\r\n/g, `
@@ -11712,7 +11606,7 @@ function getMonthParts(now = new Date) {
11712
11606
  }
11713
11607
  async function listMdFiles2(dir) {
11714
11608
  try {
11715
- const entries = await readdir4(dir);
11609
+ const entries = await readdir3(dir);
11716
11610
  return entries.filter((e2) => e2.endsWith(".md"));
11717
11611
  } catch {
11718
11612
  return [];
@@ -11820,7 +11714,7 @@ async function qmdReindexCommand(vaultRoot) {
11820
11714
  // src/internal/register-hooks.ts
11821
11715
  init_dist3();
11822
11716
  init_dist();
11823
- 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";
11824
11718
  import { homedir as homedir4 } from "node:os";
11825
11719
  import { dirname as dirname4, join as join10 } from "node:path";
11826
11720
  var HOOK_COMMANDS2 = {
@@ -11850,9 +11744,9 @@ async function readSettings2(settingsPath) {
11850
11744
  }
11851
11745
  }
11852
11746
  async function writeSettings2(settingsPath, settings) {
11853
- await mkdir5(dirname4(settingsPath), { recursive: true });
11747
+ await mkdir4(dirname4(settingsPath), { recursive: true });
11854
11748
  const tmpPath = `${settingsPath}.tmp`;
11855
- await writeFile7(tmpPath, JSON.stringify(settings, null, 4), "utf8");
11749
+ await writeFile6(tmpPath, JSON.stringify(settings, null, 4), "utf8");
11856
11750
  await rename5(tmpPath, settingsPath);
11857
11751
  }
11858
11752
  function checkHookPresence2(groups, targetCmd) {
@@ -11981,7 +11875,7 @@ ${ONEBRAIN_MARKER2}
11981
11875
  ${PATH_EXPORT2}
11982
11876
  `;
11983
11877
  const tmpPath = `${profilePath}.tmp`;
11984
- await writeFile7(tmpPath, updated, "utf8");
11878
+ await writeFile6(tmpPath, updated, "utf8");
11985
11879
  await rename5(tmpPath, profilePath);
11986
11880
  }
11987
11881
  async function runRegisterHooks2(opts = {}) {
@@ -12240,15 +12134,15 @@ async function sessionInitCommand(vaultRoot) {
12240
12134
  init_dist3();
12241
12135
  init_dist();
12242
12136
  import {
12243
- mkdir as mkdir6,
12137
+ mkdir as mkdir5,
12244
12138
  mkdtemp as mkdtemp2,
12245
12139
  readFile as readFile8,
12246
- readdir as readdir5,
12140
+ readdir as readdir4,
12247
12141
  rename as rename6,
12248
12142
  rm as rm2,
12249
12143
  stat as stat4,
12250
12144
  unlink as unlink3,
12251
- writeFile as writeFile8
12145
+ writeFile as writeFile7
12252
12146
  } from "node:fs/promises";
12253
12147
  import { homedir as homedir5, tmpdir as tmpdir2 } from "node:os";
12254
12148
  import { dirname as dirname5, join as join12, relative as relative2 } from "node:path";
@@ -12273,7 +12167,7 @@ async function downloadTarball2(branch, fetchFn) {
12273
12167
  }
12274
12168
  async function extractTarball2(tarball, destDir) {
12275
12169
  const tarPath = join12(destDir, "bundle.tar.gz");
12276
- await writeFile8(tarPath, Buffer.from(tarball));
12170
+ await writeFile7(tarPath, Buffer.from(tarball));
12277
12171
  const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", destDir], {
12278
12172
  stdout: "pipe",
12279
12173
  stderr: "pipe"
@@ -12284,7 +12178,7 @@ async function extractTarball2(tarball, destDir) {
12284
12178
  throw new Error(`tar extraction failed (exit ${exitCode}): ${errText.trim()}`);
12285
12179
  }
12286
12180
  await unlink3(tarPath);
12287
- const entries = await readdir5(destDir);
12181
+ const entries = await readdir4(destDir);
12288
12182
  const topLevel = entries.find((e2) => e2 !== "bundle.tar.gz");
12289
12183
  if (!topLevel) {
12290
12184
  throw new Error("Extracted tarball contains no top-level directory");
@@ -12298,7 +12192,7 @@ async function listFilesRecursive2(dir) {
12298
12192
  const current = queue.pop();
12299
12193
  let entries;
12300
12194
  try {
12301
- entries = await readdir5(current);
12195
+ entries = await readdir4(current);
12302
12196
  } catch {
12303
12197
  continue;
12304
12198
  }
@@ -12322,7 +12216,7 @@ async function listFilesRecursive2(dir) {
12322
12216
  async function syncPluginFiles2(extractedDir, vaultRoot, unlinkFn = unlink3) {
12323
12217
  const sourcePlugin = join12(extractedDir, ".claude", "plugins", "onebrain");
12324
12218
  const destPlugin = join12(vaultRoot, ".claude", "plugins", "onebrain");
12325
- await mkdir6(destPlugin, { recursive: true });
12219
+ await mkdir5(destPlugin, { recursive: true });
12326
12220
  const sourceFiles = await listFilesRecursive2(sourcePlugin);
12327
12221
  const sourceRelSet = new Set(sourceFiles.map((f2) => relative2(sourcePlugin, f2)));
12328
12222
  const destFiles = await listFilesRecursive2(destPlugin);
@@ -12337,9 +12231,9 @@ async function syncPluginFiles2(extractedDir, vaultRoot, unlinkFn = unlink3) {
12337
12231
  for (const srcPath of sourceFiles) {
12338
12232
  const rel = relative2(sourcePlugin, srcPath);
12339
12233
  const destPath = join12(destPlugin, rel);
12340
- await mkdir6(dirname5(destPath), { recursive: true });
12234
+ await mkdir5(dirname5(destPath), { recursive: true });
12341
12235
  const content = await readFile8(srcPath);
12342
- await writeFile8(destPath, content);
12236
+ await writeFile7(destPath, content);
12343
12237
  filesAdded++;
12344
12238
  }
12345
12239
  let filesRemoved = 0;
@@ -12359,7 +12253,7 @@ async function copyRootDocs2(extractedDir, vaultRoot) {
12359
12253
  const destPath = join12(vaultRoot, doc);
12360
12254
  try {
12361
12255
  const content = await readFile8(srcPath);
12362
- await writeFile8(destPath, content);
12256
+ await writeFile7(destPath, content);
12363
12257
  } catch {}
12364
12258
  }
12365
12259
  }
@@ -12376,7 +12270,7 @@ async function mergeHarnessFile2(extractedDir, vaultRoot, filename) {
12376
12270
  try {
12377
12271
  vaultText = await readFile8(destPath, "utf8");
12378
12272
  } catch {
12379
- await writeFile8(destPath, repoText, "utf8");
12273
+ await writeFile7(destPath, repoText, "utf8");
12380
12274
  return repoText.split(`
12381
12275
  `).filter((l2) => l2.startsWith("@")).length;
12382
12276
  }
@@ -12397,7 +12291,7 @@ async function mergeHarnessFile2(extractedDir, vaultRoot, filename) {
12397
12291
  }
12398
12292
  const merged = vaultLines.join(`
12399
12293
  `);
12400
- await writeFile8(destPath, merged, "utf8");
12294
+ await writeFile7(destPath, merged, "utf8");
12401
12295
  return newImports.length;
12402
12296
  }
12403
12297
  async function mergeHarnessFiles2(extractedDir, vaultRoot) {
@@ -12422,7 +12316,7 @@ async function updateVaultYml2(vaultRoot, version, updateChannel) {
12422
12316
  raw.update_channel = updateChannel;
12423
12317
  const updated = $stringify(raw, { lineWidth: 0 });
12424
12318
  const tmpPath = `${vaultYmlPath}.tmp`;
12425
- await writeFile8(tmpPath, updated, "utf8");
12319
+ await writeFile7(tmpPath, updated, "utf8");
12426
12320
  await rename6(tmpPath, vaultYmlPath);
12427
12321
  }
12428
12322
  async function readPluginVersion2(vaultRoot) {
@@ -12492,7 +12386,7 @@ async function pinToVault2(vaultRoot, installedPluginsPath, installedPluginsCach
12492
12386
  return { skipped: false };
12493
12387
  }
12494
12388
  const tmpPath = `${installedPluginsPath}.tmp`;
12495
- await writeFile8(tmpPath, JSON.stringify(data, null, 4), "utf8");
12389
+ await writeFile7(tmpPath, JSON.stringify(data, null, 4), "utf8");
12496
12390
  await rename6(tmpPath, installedPluginsPath);
12497
12391
  return { skipped: false };
12498
12392
  }
@@ -12523,7 +12417,7 @@ async function cleanPluginCache2(installedPluginsPath, installedPluginsCacheDir)
12523
12417
  } catch {}
12524
12418
  if (onebrainDirs.length === 0) {
12525
12419
  try {
12526
- const marketplaceDirs = await readdir5(cacheDir);
12420
+ const marketplaceDirs = await readdir4(cacheDir);
12527
12421
  for (const mp of marketplaceDirs) {
12528
12422
  const candidate = join12(cacheDir, mp, "onebrain");
12529
12423
  try {
@@ -12539,7 +12433,7 @@ async function cleanPluginCache2(installedPluginsPath, installedPluginsCacheDir)
12539
12433
  for (const pluginDir of onebrainDirs) {
12540
12434
  let versionDirs;
12541
12435
  try {
12542
- versionDirs = await readdir5(pluginDir);
12436
+ versionDirs = await readdir4(pluginDir);
12543
12437
  } catch {
12544
12438
  continue;
12545
12439
  }
@@ -12726,7 +12620,7 @@ async function vaultSyncCommand2(vaultRoot, opts = {}) {
12726
12620
  }
12727
12621
 
12728
12622
  // src/index.ts
12729
- var VERSION = "2.0.5";
12623
+ var VERSION = "2.0.6";
12730
12624
  var RELEASE_DATE = "2026-04-26";
12731
12625
  if (process.platform === "win32") {
12732
12626
  process.stdout.setDefaultEncoding("utf8");
@@ -12738,6 +12632,19 @@ if (process.argv.slice(2).length === 0) {
12738
12632
  console.log("Run `onebrain help` for available commands.");
12739
12633
  process.exit(0);
12740
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
+ }
12741
12648
  var program2 = new Command;
12742
12649
  program2.name("onebrain").description("OneBrain CLI \u2014 personal AI OS for Obsidian").version(VERSION_STRING, "-v, --version");
12743
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) => {
@@ -12747,29 +12654,31 @@ program2.command("init").description("Initialize a new OneBrain vault").option("
12747
12654
  force: opts.force
12748
12655
  });
12749
12656
  });
12750
- 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) => {
12751
12658
  await updateCommand({
12659
+ vaultDir: opts.vaultDir ?? findVaultRoot(process.cwd()),
12752
12660
  check: opts.check,
12753
12661
  channel: opts.channel
12754
12662
  });
12755
12663
  });
12756
12664
  program2.command("doctor").description("Run vault health checks and report issues").action(async () => {
12757
- const vaultRoot = process.cwd();
12665
+ const vaultRoot = findVaultRoot(process.cwd());
12758
12666
  await doctorCommand({ vaultDir: vaultRoot, binaryVersion: VERSION });
12759
12667
  });
12760
12668
  program2.command("help").description("Show this help message").action(() => {
12761
12669
  program2.help();
12762
12670
  });
12763
- program2.command("session-init", { hidden: true }).description("Emit session token and datetime (called by Claude Code hook)").action(async () => {
12764
- 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());
12765
12673
  await sessionInitCommand(vaultRoot);
12766
12674
  });
12767
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) => {
12768
12676
  await orphanScanCommand(logsFolder, sessionToken);
12769
12677
  });
12770
- 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) => {
12771
12679
  const token = await resolveSessionToken();
12772
- await checkpointCommand(mode, token, process.cwd());
12680
+ const vaultRoot = opts.vaultDir ?? findVaultRoot(process.cwd());
12681
+ await checkpointCommand(mode, token, vaultRoot);
12773
12682
  });
12774
12683
  program2.command("qmd-reindex", { hidden: true }).description("Trigger qmd index rebuild").action(async () => {
12775
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.5",
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",