@onebrain-ai/cli 2.1.13 → 2.1.15

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 +143 -32
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -9510,18 +9510,55 @@ async function atomicWrite(dest, contents, label) {
9510
9510
  }
9511
9511
  var init_fs_atomic = () => {};
9512
9512
 
9513
+ // src/lib/fs-mkdir-safe.ts
9514
+ import { mkdir, stat as stat2 } from "fs/promises";
9515
+ async function mkdirIdempotent(path) {
9516
+ try {
9517
+ await mkdir(path, { recursive: true });
9518
+ } catch (err) {
9519
+ const code = err?.code;
9520
+ if (code !== "EEXIST")
9521
+ throw err;
9522
+ let info;
9523
+ try {
9524
+ info = await stat2(path);
9525
+ } catch (statErr) {
9526
+ const statCode = statErr?.code;
9527
+ throw statCode ? statErr : err;
9528
+ }
9529
+ if (!info.isDirectory())
9530
+ throw err;
9531
+ }
9532
+ }
9533
+ var init_fs_mkdir_safe = () => {};
9534
+
9513
9535
  // src/lib/index.ts
9536
+ var exports_lib = {};
9537
+ __export(exports_lib, {
9538
+ mkdirIdempotent: () => mkdirIdempotent,
9539
+ loadVaultConfig: () => loadVaultConfig,
9540
+ checkVaultYmlKeys: () => checkVaultYmlKeys,
9541
+ checkVaultYml: () => checkVaultYml,
9542
+ checkSettingsHooks: () => checkSettingsHooks,
9543
+ checkQmdEmbeddings: () => checkQmdEmbeddings,
9544
+ checkPluginFiles: () => checkPluginFiles,
9545
+ checkOrphanCheckpoints: () => checkOrphanCheckpoints,
9546
+ checkFolders: () => checkFolders,
9547
+ checkClaudeSettings: () => checkClaudeSettings,
9548
+ atomicWrite: () => atomicWrite
9549
+ });
9514
9550
  var init_lib = __esm(() => {
9515
9551
  init_parser();
9516
9552
  init_validator();
9517
9553
  init_fs_atomic();
9554
+ init_fs_mkdir_safe();
9518
9555
  });
9519
9556
 
9520
9557
  // package.json
9521
9558
  var require_package = __commonJS((exports, module) => {
9522
9559
  module.exports = {
9523
9560
  name: "@onebrain-ai/cli",
9524
- version: "2.1.13",
9561
+ version: "2.1.15",
9525
9562
  description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9526
9563
  keywords: [
9527
9564
  "onebrain",
@@ -9966,11 +10003,11 @@ var init_dist2 = __esm(() => {
9966
10003
  });
9967
10004
 
9968
10005
  // src/commands/internal/harness.ts
9969
- import { stat as stat2 } from "fs/promises";
10006
+ import { stat as stat3 } from "fs/promises";
9970
10007
  import { join as join3 } from "path";
9971
10008
  async function pathExists(p2) {
9972
10009
  try {
9973
- await stat2(p2);
10010
+ await stat3(p2);
9974
10011
  return true;
9975
10012
  } catch {
9976
10013
  return false;
@@ -10002,7 +10039,7 @@ __export(exports_register_hooks, {
10002
10039
  runRegisterHooks: () => runRegisterHooks,
10003
10040
  registerHooksCommand: () => registerHooksCommand
10004
10041
  });
10005
- import { mkdir, readFile, rename as rename2, writeFile as writeFile2 } from "fs/promises";
10042
+ import { readFile, rename as rename2, writeFile as writeFile2 } from "fs/promises";
10006
10043
  import { homedir } from "os";
10007
10044
  import { dirname, join as join4 } from "path";
10008
10045
  async function readSettings(settingsPath) {
@@ -10016,7 +10053,7 @@ async function readSettings(settingsPath) {
10016
10053
  }
10017
10054
  }
10018
10055
  async function writeSettings(settingsPath, settings) {
10019
- await mkdir(dirname(settingsPath), { recursive: true });
10056
+ await mkdirIdempotent(dirname(settingsPath));
10020
10057
  const tmpPath = `${settingsPath}.tmp`;
10021
10058
  await writeFile2(tmpPath, JSON.stringify(settings, null, 4), "utf8");
10022
10059
  await rename2(tmpPath, settingsPath);
@@ -10086,15 +10123,70 @@ function applyHooks(settings) {
10086
10123
  }
10087
10124
  return result;
10088
10125
  }
10126
+ function isLegacyQmdCmd(cmd) {
10127
+ return /\bqmd\s+update\b/.test(cmd);
10128
+ }
10129
+ function migrateLegacyQmdEntries(groups, keepCanonical) {
10130
+ let touched = false;
10131
+ for (const group of groups) {
10132
+ if (!group.hooks)
10133
+ continue;
10134
+ if (keepCanonical) {
10135
+ let groupTouched = false;
10136
+ for (const entry of group.hooks) {
10137
+ if (isLegacyQmdCmd(entry.command ?? "")) {
10138
+ entry.command = QMD_CMD;
10139
+ if (!entry.type)
10140
+ entry.type = "command";
10141
+ groupTouched = true;
10142
+ }
10143
+ }
10144
+ if (groupTouched) {
10145
+ group.matcher = QMD_MATCHER;
10146
+ touched = true;
10147
+ }
10148
+ } else {
10149
+ const before = group.hooks.length;
10150
+ group.hooks = group.hooks.filter((h) => !isLegacyQmdCmd(h.command ?? ""));
10151
+ if (group.hooks.length !== before)
10152
+ touched = true;
10153
+ }
10154
+ }
10155
+ if (keepCanonical) {
10156
+ let seenCanonical = false;
10157
+ for (const group of groups) {
10158
+ if (!group.hooks)
10159
+ continue;
10160
+ const before = group.hooks.length;
10161
+ group.hooks = group.hooks.filter((h) => {
10162
+ if (h.command !== QMD_CMD)
10163
+ return true;
10164
+ if (seenCanonical)
10165
+ return false;
10166
+ seenCanonical = true;
10167
+ return true;
10168
+ });
10169
+ if (group.hooks.length !== before)
10170
+ touched = true;
10171
+ }
10172
+ }
10173
+ for (let i = groups.length - 1;i >= 0; i--) {
10174
+ const g = groups[i];
10175
+ if (g && (g.hooks?.length ?? 0) === 0)
10176
+ groups.splice(i, 1);
10177
+ }
10178
+ return touched;
10179
+ }
10089
10180
  function applyQmdHook(settings) {
10090
10181
  if (!settings.hooks)
10091
10182
  settings.hooks = {};
10092
10183
  if (!settings.hooks["PostToolUse"])
10093
10184
  settings.hooks["PostToolUse"] = [];
10094
10185
  const groups = settings.hooks["PostToolUse"];
10186
+ const migrated = migrateLegacyQmdEntries(groups, true);
10095
10187
  const already = groups.some((g) => g.hooks?.some((h) => h.command === QMD_CMD));
10096
10188
  if (already)
10097
- return "ok";
10189
+ return migrated ? "migrated" : "ok";
10098
10190
  groups.push({ matcher: QMD_MATCHER, hooks: [{ type: "command", command: QMD_CMD }] });
10099
10191
  return "added";
10100
10192
  }
@@ -10186,16 +10278,25 @@ async function runRegisterHooks(opts = {}) {
10186
10278
  const settings = await readSettings(settingsPath);
10187
10279
  result.hooks = applyHooks(settings);
10188
10280
  let qmdStatus;
10189
- if (qmdCollection)
10281
+ if (qmdCollection) {
10190
10282
  qmdStatus = applyQmdHook(settings);
10283
+ } else {
10284
+ const groups = settings.hooks?.["PostToolUse"] ?? [];
10285
+ const stripped = migrateLegacyQmdEntries(groups, false);
10286
+ if (stripped && groups.length === 0 && settings.hooks) {
10287
+ delete settings.hooks["PostToolUse"];
10288
+ }
10289
+ }
10191
10290
  if (isTTY) {
10192
10291
  const parts = HOOK_EVENTS.map((e2) => {
10193
10292
  const status = result.hooks[e2];
10194
10293
  const icon = import_picocolors4.default.green(status === "ok" ? "\u2713" : status === "migrated" ? "\u2191" : "+");
10195
10294
  return `${import_picocolors4.default.dim(e2)} ${icon}`;
10196
10295
  });
10197
- if (qmdStatus)
10198
- parts.push(`${import_picocolors4.default.dim("PostToolUse")} ${import_picocolors4.default.green(qmdStatus === "ok" ? "\u2713" : "+")}`);
10296
+ if (qmdStatus) {
10297
+ const qmdIcon = qmdStatus === "ok" ? "\u2713" : qmdStatus === "migrated" ? "\u2191" : "+";
10298
+ parts.push(`${import_picocolors4.default.dim("PostToolUse")} ${import_picocolors4.default.green(qmdIcon)}`);
10299
+ }
10199
10300
  hooksSpinner?.stop(`Hooks ${parts.join(" ")}`);
10200
10301
  } else {
10201
10302
  const hookLine = HOOK_EVENTS.map((e2) => {
@@ -10205,7 +10306,7 @@ async function runRegisterHooks(opts = {}) {
10205
10306
  }).join(" ");
10206
10307
  note(hookLine);
10207
10308
  if (qmdStatus)
10208
- note(`PostToolUse ${qmdStatus === "added" ? "added" : "ok"}`);
10309
+ note(`PostToolUse ${qmdStatus}`);
10209
10310
  }
10210
10311
  permSpinner = isTTY ? L2() : null;
10211
10312
  permSpinner?.start("Updating permissions...");
@@ -10276,9 +10377,10 @@ var init_register_hooks = __esm(() => {
10276
10377
  var exports_vault_sync = {};
10277
10378
  __export(exports_vault_sync, {
10278
10379
  vaultSyncCommand: () => vaultSyncCommand,
10279
- runVaultSync: () => runVaultSync
10380
+ runVaultSync: () => runVaultSync,
10381
+ buildTarSpawnOverrides: () => buildTarSpawnOverrides
10280
10382
  });
10281
- import { mkdir as mkdir2, mkdtemp, readFile as readFile2, readdir, rm, stat as stat3, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
10383
+ import { mkdtemp, readFile as readFile2, readdir, rm, stat as stat4, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
10282
10384
  import { homedir as homedir2, tmpdir } from "os";
10283
10385
  import { dirname as dirname2, join as join5, sep as pathSep, relative, resolve as resolvePath } from "path";
10284
10386
  function normalizePath(p2) {
@@ -10304,17 +10406,25 @@ async function downloadTarball(branch, fetchFn) {
10304
10406
  const tmpDir = await mkdtemp(join5(tmpdir(), "onebrain-sync-"));
10305
10407
  return { tarball, tmpDir };
10306
10408
  }
10409
+ function buildTarSpawnOverrides(platform = process.platform, parentEnv = process.env) {
10410
+ if (platform !== "win32")
10411
+ return {};
10412
+ return { env: { ...parentEnv, TAR_OPTIONS: "--force-local" } };
10413
+ }
10307
10414
  async function extractTarball(tarball, destDir) {
10308
10415
  const tarPath = join5(destDir, "bundle.tar.gz");
10309
10416
  await writeFile3(tarPath, Buffer.from(tarball));
10417
+ const tarOverrides = buildTarSpawnOverrides();
10310
10418
  const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", destDir], {
10311
10419
  stdout: "pipe",
10312
- stderr: "pipe"
10420
+ stderr: "pipe",
10421
+ ...tarOverrides
10313
10422
  });
10314
10423
  const exitCode = await proc.exited;
10315
10424
  if (exitCode !== 0) {
10316
10425
  const errText = await new Response(proc.stderr).text();
10317
- throw new Error(`tar extraction failed (exit ${exitCode}): ${errText.trim()}`);
10426
+ const envHint = tarOverrides.env ? ", TAR_OPTIONS=--force-local" : "";
10427
+ throw new Error(`tar extraction failed (exit ${exitCode}${envHint}): ${errText.trim()}`);
10318
10428
  }
10319
10429
  await unlink2(tarPath);
10320
10430
  const entries = await readdir(destDir);
@@ -10339,7 +10449,7 @@ async function listFilesRecursive(dir) {
10339
10449
  const fullPath = join5(current, entry);
10340
10450
  let s;
10341
10451
  try {
10342
- s = await stat3(fullPath);
10452
+ s = await stat4(fullPath);
10343
10453
  } catch {
10344
10454
  continue;
10345
10455
  }
@@ -10355,7 +10465,7 @@ async function listFilesRecursive(dir) {
10355
10465
  async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink2) {
10356
10466
  const sourcePlugin = join5(extractedDir, ".claude", "plugins", "onebrain");
10357
10467
  const destPlugin = join5(vaultRoot, ".claude", "plugins", "onebrain");
10358
- await mkdir2(destPlugin, { recursive: true });
10468
+ await mkdirIdempotent(destPlugin);
10359
10469
  const sourceFiles = await listFilesRecursive(sourcePlugin);
10360
10470
  const sourceRelSet = new Set(sourceFiles.map((f2) => relative(sourcePlugin, f2)));
10361
10471
  const destFiles = await listFilesRecursive(destPlugin);
@@ -10370,7 +10480,7 @@ async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink2) {
10370
10480
  for (const srcPath of sourceFiles) {
10371
10481
  const rel = relative(sourcePlugin, srcPath);
10372
10482
  const destPath = join5(destPlugin, rel);
10373
- await mkdir2(dirname2(destPath), { recursive: true });
10483
+ await mkdirIdempotent(dirname2(destPath));
10374
10484
  const content = await readFile2(srcPath);
10375
10485
  await writeFile3(destPath, content);
10376
10486
  filesAdded++;
@@ -10513,7 +10623,7 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
10513
10623
  continue;
10514
10624
  }
10515
10625
  try {
10516
- await stat3(projectPath);
10626
+ await stat4(projectPath);
10517
10627
  keep.push(entry);
10518
10628
  } catch (err) {
10519
10629
  const code = err?.code;
@@ -10571,7 +10681,7 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
10571
10681
  async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir) {
10572
10682
  const cacheDir = installedPluginsCacheDir ?? join5(dirname2(installedPluginsPath), "cache");
10573
10683
  try {
10574
- await stat3(cacheDir);
10684
+ await stat4(cacheDir);
10575
10685
  } catch {
10576
10686
  return 0;
10577
10687
  }
@@ -10587,7 +10697,7 @@ async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir)
10587
10697
  const marketplace = key.split("@")[1];
10588
10698
  const candidate = join5(cacheDir, marketplace, "onebrain");
10589
10699
  try {
10590
- await stat3(candidate);
10700
+ await stat4(candidate);
10591
10701
  onebrainDirs.push(candidate);
10592
10702
  } catch {}
10593
10703
  }
@@ -10599,7 +10709,7 @@ async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir)
10599
10709
  for (const mp of marketplaceDirs) {
10600
10710
  const candidate = join5(cacheDir, mp, "onebrain");
10601
10711
  try {
10602
- await stat3(candidate);
10712
+ await stat4(candidate);
10603
10713
  onebrainDirs.push(candidate);
10604
10714
  } catch {}
10605
10715
  }
@@ -10618,7 +10728,7 @@ async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir)
10618
10728
  for (const versionDir of versionDirs) {
10619
10729
  const fullPath = join5(pluginDir, versionDir);
10620
10730
  try {
10621
- const s = await stat3(fullPath);
10731
+ const s = await stat4(fullPath);
10622
10732
  if (s.isDirectory()) {
10623
10733
  await rm(fullPath, { recursive: true, force: true });
10624
10734
  removed++;
@@ -10731,7 +10841,7 @@ async function runVaultSync(vaultRoot, opts = {}) {
10731
10841
  for (const srcPath of obsidianFiles) {
10732
10842
  const rel = relative(sourceObsidian, srcPath);
10733
10843
  const destPath = join5(destObsidian, rel);
10734
- await mkdir2(dirname2(destPath), { recursive: true });
10844
+ await mkdirIdempotent(dirname2(destPath));
10735
10845
  const content = await readFile2(srcPath);
10736
10846
  await writeFile3(destPath, content);
10737
10847
  }
@@ -10858,7 +10968,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
10858
10968
  var import_picocolors = __toESM(require_picocolors(), 1);
10859
10969
  function resolveBinaryVersion() {
10860
10970
  if (true)
10861
- return "2.1.13";
10971
+ return "2.1.15";
10862
10972
  try {
10863
10973
  const pkg = require_package();
10864
10974
  return pkg.version ?? "dev";
@@ -11618,11 +11728,11 @@ function getFix(r2) {
11618
11728
  const missingStr = r2.hint.replace("Missing: ", "");
11619
11729
  return {
11620
11730
  fn: async (vaultDir) => {
11621
- const { mkdir: mkdir2 } = await import("fs/promises");
11731
+ const { mkdirIdempotent: mkdirIdempotent2 } = await Promise.resolve().then(() => (init_lib(), exports_lib));
11622
11732
  const { join: join5 } = await import("path");
11623
11733
  const missing = missingStr.split(", ").map((f2) => f2.trim()).filter(Boolean);
11624
11734
  for (const folder of missing) {
11625
- await mkdir2(join5(vaultDir, folder), { recursive: true });
11735
+ await mkdirIdempotent2(join5(vaultDir, folder));
11626
11736
  }
11627
11737
  },
11628
11738
  description: `Create missing folders: ${missingStr}`
@@ -11714,9 +11824,10 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11714
11824
  }
11715
11825
 
11716
11826
  // src/commands/init.ts
11827
+ init_lib();
11717
11828
  var import_picocolors7 = __toESM(require_picocolors(), 1);
11718
11829
  var import_yaml4 = __toESM(require_dist(), 1);
11719
- import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as writeFile4 } from "fs/promises";
11830
+ import { readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat5, writeFile as writeFile4 } from "fs/promises";
11720
11831
  import { homedir as homedir3 } from "os";
11721
11832
  import { dirname as dirname3, join as join6 } from "path";
11722
11833
  init_cli_ui();
@@ -11734,7 +11845,7 @@ var STANDARD_FOLDERS = [
11734
11845
  var INBOX_IMPORTS = join6("00-inbox", "imports");
11735
11846
  async function pathExists2(p2) {
11736
11847
  try {
11737
- await stat4(p2);
11848
+ await stat5(p2);
11738
11849
  return true;
11739
11850
  } catch {
11740
11851
  return false;
@@ -11746,7 +11857,7 @@ async function createFolders(vaultDir) {
11746
11857
  for (const rel of allPaths) {
11747
11858
  const full = join6(vaultDir, rel);
11748
11859
  if (!await pathExists2(full)) {
11749
- await mkdir3(full, { recursive: true });
11860
+ await mkdirIdempotent(full);
11750
11861
  created++;
11751
11862
  }
11752
11863
  }
@@ -11859,7 +11970,7 @@ async function registerPlugin(vaultDir, installedPluginsPath) {
11859
11970
  }
11860
11971
  const tmpPath = `${installedPluginsPath}.tmp`;
11861
11972
  try {
11862
- await mkdir3(dirname3(installedPluginsPath), { recursive: true });
11973
+ await mkdirIdempotent(dirname3(installedPluginsPath));
11863
11974
  await writeFile4(tmpPath, JSON.stringify(data, null, 4), "utf8");
11864
11975
  await rename3(tmpPath, installedPluginsPath);
11865
11976
  } catch (err) {
@@ -11946,7 +12057,7 @@ async function installObsidianPlugins(vaultDir, opts) {
11946
12057
  });
11947
12058
  continue;
11948
12059
  }
11949
- await mkdir3(pluginDir, { recursive: true });
12060
+ await mkdirIdempotent(pluginDir);
11950
12061
  let pluginFailed = false;
11951
12062
  for (const assetName of ["main.js", "manifest.json", "styles.css"]) {
11952
12063
  const asset = assets.find((a2) => a2.name === assetName);
@@ -13088,7 +13199,7 @@ function patchUtf8(stream) {
13088
13199
  }
13089
13200
 
13090
13201
  // src/index.ts
13091
- var VERSION = "2.1.13";
13202
+ var VERSION = "2.1.15";
13092
13203
  var RELEASE_DATE = "2026-05-06";
13093
13204
  patchUtf8(process.stdout);
13094
13205
  patchUtf8(process.stderr);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.1.13",
3
+ "version": "2.1.15",
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",