@onebrain-ai/cli 2.1.10 → 2.1.11

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 +216 -75
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -9268,6 +9268,10 @@ async function checkVaultYmlKeys(vaultRoot) {
9268
9268
  if (raw[key] === undefined)
9269
9269
  errors.push(`missing key: ${key}`);
9270
9270
  }
9271
+ for (const key of SOFT_REQUIRED_VAULT_YML_KEYS) {
9272
+ if (raw[key] === undefined)
9273
+ warnings.push(`missing key: ${key}`);
9274
+ }
9271
9275
  const folders = raw["folders"] ?? {};
9272
9276
  for (const key of REQUIRED_FOLDER_KEYS) {
9273
9277
  if (folders[key] === undefined)
@@ -9305,8 +9309,16 @@ async function checkVaultYmlKeys(vaultRoot) {
9305
9309
  };
9306
9310
  }
9307
9311
  if (warnings.length > 0) {
9308
- const fixableWarnings = warnings.filter((w) => w.includes("onebrain_version") || w.includes("method") || w.includes("runtime.harness"));
9309
- const hint = fixableWarnings.length > 0 ? "Run onebrain doctor --fix to remove deprecated keys" : undefined;
9312
+ const hasDeprecated = warnings.some((w) => w.includes("onebrain_version") || w.includes("method") || w.includes("runtime.harness"));
9313
+ const hasMissingSoftKey = warnings.some((w) => w.startsWith("missing key:"));
9314
+ let hint;
9315
+ if (hasMissingSoftKey && hasDeprecated) {
9316
+ hint = "Run onebrain doctor --fix to repair vault.yml";
9317
+ } else if (hasMissingSoftKey) {
9318
+ hint = "Run onebrain doctor --fix to backfill defaults";
9319
+ } else if (hasDeprecated) {
9320
+ hint = "Run onebrain doctor --fix to remove deprecated keys";
9321
+ }
9310
9322
  return {
9311
9323
  check: "vault.yml-keys",
9312
9324
  status: "warn",
@@ -9321,6 +9333,39 @@ async function checkVaultYmlKeys(vaultRoot) {
9321
9333
  message: "schema ok"
9322
9334
  };
9323
9335
  }
9336
+ async function checkClaudeSettings(vaultRoot) {
9337
+ const settingsPath = join2(vaultRoot, ".claude", "settings.json");
9338
+ const file = Bun.file(settingsPath);
9339
+ if (!await file.exists()) {
9340
+ return { check: "claude-settings", status: "ok", message: "no vault settings.json" };
9341
+ }
9342
+ let raw;
9343
+ try {
9344
+ raw = JSON.parse(await file.text());
9345
+ } catch {
9346
+ return {
9347
+ check: "claude-settings",
9348
+ status: "warn",
9349
+ message: "settings.json contains invalid JSON"
9350
+ };
9351
+ }
9352
+ const marketplaces = raw["extraKnownMarketplaces"];
9353
+ const onebrain = marketplaces?.["onebrain"];
9354
+ const source = onebrain?.["source"];
9355
+ const repo = source?.["repo"];
9356
+ if (repo === STALE_MARKETPLACE_REPO) {
9357
+ return {
9358
+ check: "claude-settings",
9359
+ status: "warn",
9360
+ message: "stale marketplace repo",
9361
+ hint: "Run onebrain doctor --fix to rewrite to onebrain-ai/onebrain",
9362
+ details: [
9363
+ `stale extraKnownMarketplaces.onebrain.source.repo: ${STALE_MARKETPLACE_REPO} \u2192 ${CANONICAL_MARKETPLACE_REPO}`
9364
+ ]
9365
+ };
9366
+ }
9367
+ return { check: "claude-settings", status: "ok", message: "ok" };
9368
+ }
9324
9369
  function hookPresent(settings, event, cmdSubstring) {
9325
9370
  const groups = settings.hooks?.[event] ?? [];
9326
9371
  return groups.some((g) => g.hooks?.some((h) => (h.command ?? "").includes(cmdSubstring)));
@@ -9407,7 +9452,7 @@ async function checkSettingsHooks(vaultRoot, config) {
9407
9452
  ...okDetails.length > 0 ? { details: okDetails } : {}
9408
9453
  };
9409
9454
  }
9410
- var import_yaml2, STANDARD_FOLDER_KEYS, REQUIRED_PLUGIN_FILES, REQUIRED_PLUGIN_DIRS, STALE_BASH_FILES, REQUIRED_VAULT_YML_KEYS, REQUIRED_FOLDER_KEYS, REQUIRED_HOOKS, ALLOWED_HOOK_EVENTS, QMD_HOOK_SUBSTRING = "onebrain qmd-reindex", ONEBRAIN_COMMAND_SUBSTRING = "onebrain", REQUIRED_PERMISSION = "Bash(onebrain *)", STALE_HOOK_SUBSTRINGS;
9455
+ var import_yaml2, STANDARD_FOLDER_KEYS, REQUIRED_PLUGIN_FILES, REQUIRED_PLUGIN_DIRS, STALE_BASH_FILES, REQUIRED_VAULT_YML_KEYS, SOFT_REQUIRED_VAULT_YML_KEYS, REQUIRED_FOLDER_KEYS, STALE_MARKETPLACE_REPO = "kengio/onebrain", CANONICAL_MARKETPLACE_REPO = "onebrain-ai/onebrain", REQUIRED_HOOKS, ALLOWED_HOOK_EVENTS, QMD_HOOK_SUBSTRING = "onebrain qmd-reindex", ONEBRAIN_COMMAND_SUBSTRING = "onebrain", REQUIRED_PERMISSION = "Bash(onebrain *)", STALE_HOOK_SUBSTRINGS;
9411
9456
  var init_validator = __esm(() => {
9412
9457
  import_yaml2 = __toESM(require_dist(), 1);
9413
9458
  STANDARD_FOLDER_KEYS = [
@@ -9431,7 +9476,8 @@ var init_validator = __esm(() => {
9431
9476
  "qmd-reindex.sh",
9432
9477
  "backfill-recapped.sh"
9433
9478
  ];
9434
- REQUIRED_VAULT_YML_KEYS = ["update_channel", "folders"];
9479
+ REQUIRED_VAULT_YML_KEYS = ["folders"];
9480
+ SOFT_REQUIRED_VAULT_YML_KEYS = ["update_channel"];
9435
9481
  REQUIRED_FOLDER_KEYS = [
9436
9482
  "inbox",
9437
9483
  "projects",
@@ -9449,17 +9495,33 @@ var init_validator = __esm(() => {
9449
9495
  STALE_HOOK_SUBSTRINGS = ["checkpoint-hook.sh", "session-init.sh"];
9450
9496
  });
9451
9497
 
9498
+ // src/lib/fs-atomic.ts
9499
+ import { rename, unlink, writeFile } from "fs/promises";
9500
+ async function atomicWrite(dest, contents, label) {
9501
+ const tmpPath = `${dest}.tmp`;
9502
+ await writeFile(tmpPath, contents, "utf8");
9503
+ try {
9504
+ await rename(tmpPath, dest);
9505
+ } catch (err) {
9506
+ await unlink(tmpPath).catch(() => {});
9507
+ const msg = err instanceof Error ? err.message : String(err);
9508
+ throw new Error(`rename failed for ${label ?? dest}; tmp cleaned up: ${msg}`, { cause: err });
9509
+ }
9510
+ }
9511
+ var init_fs_atomic = () => {};
9512
+
9452
9513
  // src/lib/index.ts
9453
9514
  var init_lib = __esm(() => {
9454
9515
  init_parser();
9455
9516
  init_validator();
9517
+ init_fs_atomic();
9456
9518
  });
9457
9519
 
9458
9520
  // package.json
9459
9521
  var require_package = __commonJS((exports, module) => {
9460
9522
  module.exports = {
9461
9523
  name: "@onebrain-ai/cli",
9462
- version: "2.1.10",
9524
+ version: "2.1.11",
9463
9525
  description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9464
9526
  keywords: [
9465
9527
  "onebrain",
@@ -9940,7 +10002,7 @@ __export(exports_register_hooks, {
9940
10002
  runRegisterHooks: () => runRegisterHooks,
9941
10003
  registerHooksCommand: () => registerHooksCommand
9942
10004
  });
9943
- import { mkdir, readFile, rename, writeFile } from "fs/promises";
10005
+ import { mkdir, readFile, rename as rename2, writeFile as writeFile2 } from "fs/promises";
9944
10006
  import { homedir } from "os";
9945
10007
  import { dirname, join as join4 } from "path";
9946
10008
  async function readSettings(settingsPath) {
@@ -9956,8 +10018,8 @@ async function readSettings(settingsPath) {
9956
10018
  async function writeSettings(settingsPath, settings) {
9957
10019
  await mkdir(dirname(settingsPath), { recursive: true });
9958
10020
  const tmpPath = `${settingsPath}.tmp`;
9959
- await writeFile(tmpPath, JSON.stringify(settings, null, 4), "utf8");
9960
- await rename(tmpPath, settingsPath);
10021
+ await writeFile2(tmpPath, JSON.stringify(settings, null, 4), "utf8");
10022
+ await rename2(tmpPath, settingsPath);
9961
10023
  }
9962
10024
  function checkHookPresence(groups, targetCmd) {
9963
10025
  let foundMigrate = false;
@@ -10086,8 +10148,8 @@ ${ONEBRAIN_MARKER}
10086
10148
  ${PATH_EXPORT}
10087
10149
  `;
10088
10150
  const tmpPath = `${profilePath}.tmp`;
10089
- await writeFile(tmpPath, updated, "utf8");
10090
- await rename(tmpPath, profilePath);
10151
+ await writeFile2(tmpPath, updated, "utf8");
10152
+ await rename2(tmpPath, profilePath);
10091
10153
  }
10092
10154
  async function runRegisterHooks(opts = {}) {
10093
10155
  const vaultRoot = opts.vaultDir ?? process.cwd();
@@ -10216,17 +10278,7 @@ __export(exports_vault_sync, {
10216
10278
  vaultSyncCommand: () => vaultSyncCommand,
10217
10279
  runVaultSync: () => runVaultSync
10218
10280
  });
10219
- import {
10220
- mkdir as mkdir2,
10221
- mkdtemp,
10222
- readFile as readFile2,
10223
- readdir,
10224
- rename as rename2,
10225
- rm,
10226
- stat as stat3,
10227
- unlink,
10228
- writeFile as writeFile2
10229
- } from "fs/promises";
10281
+ import { mkdir as mkdir2, mkdtemp, readFile as readFile2, readdir, rm, stat as stat3, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
10230
10282
  import { homedir as homedir2, tmpdir } from "os";
10231
10283
  import { dirname as dirname2, join as join5, relative } from "path";
10232
10284
  function resolveBranch(updateChannel) {
@@ -10250,7 +10302,7 @@ async function downloadTarball(branch, fetchFn) {
10250
10302
  }
10251
10303
  async function extractTarball(tarball, destDir) {
10252
10304
  const tarPath = join5(destDir, "bundle.tar.gz");
10253
- await writeFile2(tarPath, Buffer.from(tarball));
10305
+ await writeFile3(tarPath, Buffer.from(tarball));
10254
10306
  const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", destDir], {
10255
10307
  stdout: "pipe",
10256
10308
  stderr: "pipe"
@@ -10260,7 +10312,7 @@ async function extractTarball(tarball, destDir) {
10260
10312
  const errText = await new Response(proc.stderr).text();
10261
10313
  throw new Error(`tar extraction failed (exit ${exitCode}): ${errText.trim()}`);
10262
10314
  }
10263
- await unlink(tarPath);
10315
+ await unlink2(tarPath);
10264
10316
  const entries = await readdir(destDir);
10265
10317
  const topLevel = entries.find((e2) => e2 !== "bundle.tar.gz");
10266
10318
  if (!topLevel) {
@@ -10296,7 +10348,7 @@ async function listFilesRecursive(dir) {
10296
10348
  }
10297
10349
  return results;
10298
10350
  }
10299
- async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink) {
10351
+ async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink2) {
10300
10352
  const sourcePlugin = join5(extractedDir, ".claude", "plugins", "onebrain");
10301
10353
  const destPlugin = join5(vaultRoot, ".claude", "plugins", "onebrain");
10302
10354
  await mkdir2(destPlugin, { recursive: true });
@@ -10316,7 +10368,7 @@ async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink) {
10316
10368
  const destPath = join5(destPlugin, rel);
10317
10369
  await mkdir2(dirname2(destPath), { recursive: true });
10318
10370
  const content = await readFile2(srcPath);
10319
- await writeFile2(destPath, content);
10371
+ await writeFile3(destPath, content);
10320
10372
  filesAdded++;
10321
10373
  }
10322
10374
  let filesRemoved = 0;
@@ -10336,7 +10388,7 @@ async function copyRootDocs(extractedDir, vaultRoot) {
10336
10388
  const destPath = join5(vaultRoot, doc);
10337
10389
  try {
10338
10390
  const content = await readFile2(srcPath);
10339
- await writeFile2(destPath, content);
10391
+ await writeFile3(destPath, content);
10340
10392
  } catch {}
10341
10393
  }
10342
10394
  }
@@ -10353,7 +10405,7 @@ async function mergeHarnessFile(extractedDir, vaultRoot, filename) {
10353
10405
  try {
10354
10406
  vaultText = await readFile2(destPath, "utf8");
10355
10407
  } catch {
10356
- await writeFile2(destPath, repoText, "utf8");
10408
+ await writeFile3(destPath, repoText, "utf8");
10357
10409
  return repoText.split(`
10358
10410
  `).filter((l2) => l2.startsWith("@")).length;
10359
10411
  }
@@ -10374,7 +10426,7 @@ async function mergeHarnessFile(extractedDir, vaultRoot, filename) {
10374
10426
  }
10375
10427
  const merged = vaultLines.join(`
10376
10428
  `);
10377
- await writeFile2(destPath, merged, "utf8");
10429
+ await writeFile3(destPath, merged, "utf8");
10378
10430
  return newImports.length;
10379
10431
  }
10380
10432
  async function mergeHarnessFiles(extractedDir, vaultRoot) {
@@ -10396,22 +10448,21 @@ async function updateVaultYml(vaultRoot, updateChannel) {
10396
10448
  }
10397
10449
  const raw = import_yaml3.parse(text) ?? {};
10398
10450
  raw["update_channel"] = updateChannel;
10399
- const updated = import_yaml3.stringify(raw, { lineWidth: 0 });
10400
- const tmpPath = `${vaultYmlPath}.tmp`;
10401
- await writeFile2(tmpPath, updated, "utf8");
10402
- await rename2(tmpPath, vaultYmlPath);
10451
+ await atomicWrite(vaultYmlPath, import_yaml3.stringify(raw, { lineWidth: 0 }), "vault.yml");
10403
10452
  }
10404
- async function readPluginVersion(vaultRoot) {
10453
+ async function readPluginMetadata(vaultRoot) {
10405
10454
  const pluginJsonPath = join5(vaultRoot, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json");
10406
10455
  try {
10407
10456
  const text = await readFile2(pluginJsonPath, "utf8");
10408
10457
  const parsed = JSON.parse(text);
10409
- return typeof parsed["version"] === "string" ? parsed["version"] : "unknown";
10458
+ const version = typeof parsed["version"] === "string" ? parsed["version"] : "unknown";
10459
+ const lastUpdated = typeof parsed["lastUpdated"] === "string" ? parsed["lastUpdated"] : undefined;
10460
+ return { version, lastUpdated };
10410
10461
  } catch {
10411
- return "unknown";
10462
+ return { version: "unknown", lastUpdated: undefined };
10412
10463
  }
10413
10464
  }
10414
- async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir) {
10465
+ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir, now = () => new Date) {
10415
10466
  let text;
10416
10467
  try {
10417
10468
  text = await readFile2(installedPluginsPath, "utf8");
@@ -10433,7 +10484,8 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
10433
10484
  return { skipped: true };
10434
10485
  }
10435
10486
  const vaultPluginDir = join5(vaultRoot, ".claude", "plugins", "onebrain");
10436
- const pluginVersion = await readPluginVersion(vaultRoot);
10487
+ const { version: pluginVersion, lastUpdated: pluginLastUpdated } = await readPluginMetadata(vaultRoot);
10488
+ const updatedAt = pluginLastUpdated ?? now().toISOString();
10437
10489
  const cacheDir = installedPluginsCacheDir ?? join5(dirname2(installedPluginsPath), "cache");
10438
10490
  const hasMarketplace = onebrainKeys.some((k2) => {
10439
10491
  const entries = plugins[k2];
@@ -10443,6 +10495,33 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
10443
10495
  return { skipped: true };
10444
10496
  }
10445
10497
  let changed = false;
10498
+ const ONEBRAIN_KEY = "onebrain@onebrain";
10499
+ if (Array.isArray(plugins[ONEBRAIN_KEY])) {
10500
+ const before = plugins[ONEBRAIN_KEY];
10501
+ const keep = [];
10502
+ for (const entry of before) {
10503
+ const projectPath = entry["projectPath"];
10504
+ if (typeof projectPath !== "string") {
10505
+ keep.push(entry);
10506
+ continue;
10507
+ }
10508
+ try {
10509
+ await stat3(projectPath);
10510
+ keep.push(entry);
10511
+ } catch (err) {
10512
+ const code = err?.code;
10513
+ if (code === "ENOENT")
10514
+ continue;
10515
+ process.stderr.write(`vault-sync: pin warning: stat ${projectPath}: ${code ?? "unknown"}
10516
+ `);
10517
+ keep.push(entry);
10518
+ }
10519
+ }
10520
+ if (keep.length !== before.length) {
10521
+ plugins[ONEBRAIN_KEY] = keep;
10522
+ changed = true;
10523
+ }
10524
+ }
10446
10525
  for (const key of onebrainKeys) {
10447
10526
  const entries = plugins[key];
10448
10527
  for (const entry of entries) {
@@ -10456,20 +10535,24 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
10456
10535
  } catch {
10457
10536
  inCache = false;
10458
10537
  }
10459
- if (!inCache) {
10538
+ const isThisVault = installPath === vaultPluginDir;
10539
+ if (inCache) {
10540
+ entry["installPath"] = vaultPluginDir;
10541
+ changed = true;
10542
+ } else if (!isThisVault) {
10460
10543
  continue;
10461
10544
  }
10462
- entry["installPath"] = vaultPluginDir;
10463
- entry["version"] = pluginVersion;
10464
- changed = true;
10545
+ if (entry["version"] !== pluginVersion) {
10546
+ entry["version"] = pluginVersion;
10547
+ entry["lastUpdated"] = updatedAt;
10548
+ changed = true;
10549
+ }
10465
10550
  }
10466
10551
  }
10467
10552
  if (!changed) {
10468
10553
  return { skipped: false };
10469
10554
  }
10470
- const tmpPath = `${installedPluginsPath}.tmp`;
10471
- await writeFile2(tmpPath, JSON.stringify(data, null, 4), "utf8");
10472
- await rename2(tmpPath, installedPluginsPath);
10555
+ await atomicWrite(installedPluginsPath, JSON.stringify(data, null, 4), "installed_plugins.json");
10473
10556
  return { skipped: false };
10474
10557
  }
10475
10558
  async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir) {
@@ -10535,7 +10618,7 @@ async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir)
10535
10618
  async function runVaultSync(vaultRoot, opts = {}) {
10536
10619
  const fetchFn = opts.fetchFn ?? globalThis.fetch;
10537
10620
  const isTTY = opts.isTTY ?? process.stdout.isTTY;
10538
- const unlinkFn = opts.unlinkFn ?? unlink;
10621
+ const unlinkFn = opts.unlinkFn ?? unlink2;
10539
10622
  let updateChannel = "stable";
10540
10623
  try {
10541
10624
  const vaultYmlText = await readFile2(join5(vaultRoot, "vault.yml"), "utf8");
@@ -10637,7 +10720,7 @@ async function runVaultSync(vaultRoot, opts = {}) {
10637
10720
  const destPath = join5(destObsidian, rel);
10638
10721
  await mkdir2(dirname2(destPath), { recursive: true });
10639
10722
  const content = await readFile2(srcPath);
10640
- await writeFile2(destPath, content);
10723
+ await writeFile3(destPath, content);
10641
10724
  }
10642
10725
  } catch {}
10643
10726
  }
@@ -10672,7 +10755,7 @@ async function runVaultSync(vaultRoot, opts = {}) {
10672
10755
  if (harness === "claude") {
10673
10756
  startSpinner("\uD83D\uDCCC", "Pinning to vault");
10674
10757
  try {
10675
- const pinResult = await pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir);
10758
+ const pinResult = await pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir, opts.now);
10676
10759
  result.pinSkipped = pinResult.skipped;
10677
10760
  if (pinResult.skipped) {
10678
10761
  stopSpinner("pin skipped (not found or marketplace)");
@@ -10727,6 +10810,7 @@ async function vaultSyncCommand(vaultRoot, opts = {}) {
10727
10810
  var import_picocolors6, import_yaml3;
10728
10811
  var init_vault_sync = __esm(() => {
10729
10812
  init_dist2();
10813
+ init_lib();
10730
10814
  init_cli_ui();
10731
10815
  init_harness();
10732
10816
  import_picocolors6 = __toESM(require_picocolors(), 1);
@@ -10761,7 +10845,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
10761
10845
  var import_picocolors = __toESM(require_picocolors(), 1);
10762
10846
  function resolveBinaryVersion() {
10763
10847
  if (true)
10764
- return "2.1.10";
10848
+ return "2.1.11";
10765
10849
  try {
10766
10850
  const pkg = require_package();
10767
10851
  return pkg.version ?? "dev";
@@ -11229,6 +11313,7 @@ async function runDoctor(opts = {}) {
11229
11313
  const checkPluginFilesFn = opts.checkPluginFilesFn ?? checkPluginFiles;
11230
11314
  const checkVaultYmlKeysFn = opts.checkVaultYmlKeysFn ?? checkVaultYmlKeys;
11231
11315
  const checkSettingsHooksFn = opts.checkSettingsHooksFn ?? checkSettingsHooks;
11316
+ const checkClaudeSettingsFn = opts.checkClaudeSettingsFn ?? checkClaudeSettings;
11232
11317
  if (isTTY) {
11233
11318
  await printBanner();
11234
11319
  }
@@ -11259,7 +11344,14 @@ async function runDoctor(opts = {}) {
11259
11344
  if (vaultYmlResult.status === "ok") {
11260
11345
  try {
11261
11346
  config = await loadVaultConfigFn(vaultDir);
11262
- } catch {}
11347
+ } catch (err) {
11348
+ const code = err?.code;
11349
+ if (code !== "ENOENT") {
11350
+ const msg = err instanceof Error ? err.message : String(err);
11351
+ process.stderr.write(`doctor: vault.yml load warning: ${msg}
11352
+ `);
11353
+ }
11354
+ }
11263
11355
  }
11264
11356
  await randDelay();
11265
11357
  sp1?.stop(fmtResult(vaultYmlResult), vaultYmlResult.details);
@@ -11269,6 +11361,7 @@ async function runDoctor(opts = {}) {
11269
11361
  let pluginFilesResult;
11270
11362
  let vaultYmlKeysResult;
11271
11363
  let settingsHooksResult;
11364
+ let claudeSettingsResult;
11272
11365
  if (isTTY) {
11273
11366
  const sp2 = createStep("\u2699\uFE0F", "Config schema");
11274
11367
  vaultYmlKeysResult = await checkVaultYmlKeysFn(vaultDir);
@@ -11294,6 +11387,10 @@ async function runDoctor(opts = {}) {
11294
11387
  qmdResult = await checkQmdEmbeddingsFn(config);
11295
11388
  await randDelay();
11296
11389
  sp7.stop(fmtResult(qmdResult), qmdResult.details);
11390
+ const sp8 = createStep("\uD83D\uDED2", "Marketplace config");
11391
+ claudeSettingsResult = await checkClaudeSettingsFn(vaultDir);
11392
+ await randDelay();
11393
+ sp8.stop(fmtResult(claudeSettingsResult), claudeSettingsResult.details);
11297
11394
  } else {
11298
11395
  [
11299
11396
  foldersResult,
@@ -11301,14 +11398,16 @@ async function runDoctor(opts = {}) {
11301
11398
  orphanResult,
11302
11399
  pluginFilesResult,
11303
11400
  vaultYmlKeysResult,
11304
- settingsHooksResult
11401
+ settingsHooksResult,
11402
+ claudeSettingsResult
11305
11403
  ] = await Promise.all([
11306
11404
  checkFoldersFn(vaultDir, config),
11307
11405
  checkQmdEmbeddingsFn(config),
11308
11406
  checkOrphanCheckpointsFn(vaultDir, config),
11309
11407
  checkPluginFilesFn(vaultDir),
11310
11408
  checkVaultYmlKeysFn(vaultDir),
11311
- checkSettingsHooksFn(vaultDir, config)
11409
+ checkSettingsHooksFn(vaultDir, config),
11410
+ checkClaudeSettingsFn(vaultDir)
11312
11411
  ]);
11313
11412
  }
11314
11413
  const results = [
@@ -11318,7 +11417,8 @@ async function runDoctor(opts = {}) {
11318
11417
  pluginFilesResult,
11319
11418
  settingsHooksResult,
11320
11419
  orphanResult,
11321
- qmdResult
11420
+ qmdResult,
11421
+ claudeSettingsResult
11322
11422
  ];
11323
11423
  const totalChecks = results.length;
11324
11424
  const errorCount = results.filter((r2) => r2.status === "error").length;
@@ -11351,14 +11451,17 @@ async function runDoctor(opts = {}) {
11351
11451
  `);
11352
11452
  }
11353
11453
  }
11454
+ let fixFailedCount = 0;
11354
11455
  if (opts.fix) {
11355
- await applyFixes(vaultDir, results, isTTY, opts.registerHooksFn);
11456
+ fixFailedCount = await applyFixes(vaultDir, results, isTTY, opts.registerHooksFn);
11356
11457
  }
11458
+ const ok = errorCount === 0 && fixFailedCount === 0;
11357
11459
  return {
11358
- ok: errorCount === 0,
11359
- exitCode: errorCount > 0 ? 1 : 0,
11460
+ ok,
11461
+ exitCode: ok ? 0 : 1,
11360
11462
  errorCount,
11361
- warningCount
11463
+ warningCount,
11464
+ fixFailedCount
11362
11465
  };
11363
11466
  }
11364
11467
  async function doctorCommand(opts = {}) {
@@ -11406,12 +11509,18 @@ function getFix(r2) {
11406
11509
  };
11407
11510
  }
11408
11511
  const hasDeprecatedKeys = r2.details?.some((d) => d.includes("deprecated key: onebrain_version") || d.includes("deprecated key: method") || d.includes("deprecated key: runtime.harness"));
11409
- if (r2.check === "vault.yml-keys" && r2.status === "warn" && hasDeprecatedKeys) {
11512
+ const hasMissingUpdateChannel = r2.details?.some((d) => d === "missing key: update_channel");
11513
+ if (r2.check === "vault.yml-keys" && r2.status === "warn" && (hasDeprecatedKeys || hasMissingUpdateChannel)) {
11410
11514
  const deprecated = (r2.details ?? []).filter((d) => d.startsWith("deprecated key:")).map((d) => d.slice("deprecated key: ".length).split(" ")[0] ?? d);
11411
- const description = deprecated.length > 0 ? `Remove deprecated keys from vault.yml: ${deprecated.join(", ")}` : "Remove deprecated keys from vault.yml";
11515
+ const fixParts = [];
11516
+ if (hasMissingUpdateChannel)
11517
+ fixParts.push("add update_channel: stable");
11518
+ if (deprecated.length > 0)
11519
+ fixParts.push(`remove deprecated: ${deprecated.join(", ")}`);
11520
+ const description = fixParts.length > 0 ? `Fix vault.yml: ${fixParts.join("; ")}` : "Fix vault.yml";
11412
11521
  return {
11413
11522
  fn: async (vaultDir) => {
11414
- const { readFile: readFile2, writeFile: writeFile2, rename: rename2 } = await import("fs/promises");
11523
+ const { readFile: readFile2 } = await import("fs/promises");
11415
11524
  const { join: join5 } = await import("path");
11416
11525
  const { parse: parse3, stringify } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
11417
11526
  const vaultYmlPath = join5(vaultDir, "vault.yml");
@@ -11431,10 +11540,10 @@ function getFix(r2) {
11431
11540
  }
11432
11541
  }
11433
11542
  }
11434
- const updated = stringify(raw, { lineWidth: 0 });
11435
- const tmpPath = `${vaultYmlPath}.tmp`;
11436
- await writeFile2(tmpPath, updated, "utf8");
11437
- await rename2(tmpPath, vaultYmlPath);
11543
+ if (details.some((d) => d === "missing key: update_channel")) {
11544
+ raw["update_channel"] = "stable";
11545
+ }
11546
+ await atomicWrite(vaultYmlPath, stringify(raw, { lineWidth: 0 }), "vault.yml");
11438
11547
  },
11439
11548
  description
11440
11549
  };
@@ -11469,6 +11578,29 @@ function getFix(r2) {
11469
11578
  description: `Embed ${count} unembedded document(s) (qmd update + embed)`
11470
11579
  };
11471
11580
  }
11581
+ if (r2.check === "claude-settings" && r2.status === "warn" && r2.details?.some((d) => d.startsWith("stale extraKnownMarketplaces.onebrain.source.repo:"))) {
11582
+ return {
11583
+ fn: async (vaultDir) => {
11584
+ const { readFile: readFile2 } = await import("fs/promises");
11585
+ const { join: join5 } = await import("path");
11586
+ const settingsPath = join5(vaultDir, ".claude", "settings.json");
11587
+ const text = await readFile2(settingsPath, "utf8");
11588
+ const raw = JSON.parse(text);
11589
+ const marketplaces = raw["extraKnownMarketplaces"];
11590
+ const onebrain = marketplaces?.["onebrain"];
11591
+ const source = onebrain?.["source"];
11592
+ if (!source || source["repo"] !== "kengio/onebrain")
11593
+ return;
11594
+ source["repo"] = "onebrain-ai/onebrain";
11595
+ const trailingNewline = text.endsWith(`
11596
+ `) ? `
11597
+ ` : "";
11598
+ const updated = `${JSON.stringify(raw, null, 2)}${trailingNewline}`;
11599
+ await atomicWrite(settingsPath, updated, ".claude/settings.json");
11600
+ },
11601
+ description: "Rewrite stale marketplace repo: kengio/onebrain \u2192 onebrain-ai/onebrain"
11602
+ };
11603
+ }
11472
11604
  if (r2.check === "folders" && r2.status === "error" && r2.hint) {
11473
11605
  const missingStr = r2.hint.replace("Missing: ", "");
11474
11606
  return {
@@ -11492,7 +11624,7 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11492
11624
  writeLine(`${import_picocolors5.default.green("\u25C6")} Nothing to fix`);
11493
11625
  else
11494
11626
  writeLine("nothing to fix");
11495
- return;
11627
+ return 0;
11496
11628
  }
11497
11629
  if (isTTY) {
11498
11630
  writeLine("");
@@ -11507,12 +11639,13 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11507
11639
  barLine(import_picocolors5.default.dim("No"));
11508
11640
  barBlank();
11509
11641
  close(`No changes made \u2014 run ${import_picocolors5.default.cyan("onebrain doctor --fix")} to apply`);
11510
- return;
11642
+ return 0;
11511
11643
  }
11512
11644
  barLine("Yes");
11513
11645
  barBlank();
11514
11646
  }
11515
11647
  let fixed = 0;
11648
+ let fixFailed = 0;
11516
11649
  const unfixable = [];
11517
11650
  for (const r2 of results) {
11518
11651
  if (r2.status === "ok")
@@ -11528,6 +11661,7 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11528
11661
  if (isTTY)
11529
11662
  barLine(`${import_picocolors5.default.green("\u25C6")} ${fix.description}`);
11530
11663
  } catch (err) {
11664
+ fixFailed++;
11531
11665
  const errMsg = err instanceof Error ? err.message : String(err);
11532
11666
  if (isTTY) {
11533
11667
  barLine(`${import_picocolors5.default.yellow("\u25B2")} Could not fix ${r2.check}: ${errMsg}`);
@@ -11541,6 +11675,9 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11541
11675
  barBlank();
11542
11676
  if (fixed > 0)
11543
11677
  barLine(`${import_picocolors5.default.green("\u25C6")} Fixed ${fixed} issue(s)`);
11678
+ if (fixFailed > 0) {
11679
+ barLine(`${import_picocolors5.default.yellow("\u25B2")} ${fixFailed} fix(es) failed \u2014 see warnings above`);
11680
+ }
11544
11681
  if (unfixable.length > 0) {
11545
11682
  barLine(`${import_picocolors5.default.yellow("\u25B2")} ${unfixable.length} issue(s) require manual action:`);
11546
11683
  for (const r2 of unfixable) {
@@ -11551,18 +11688,22 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
11551
11688
  close("Done");
11552
11689
  } else {
11553
11690
  process.stdout.write(`fixed: ${fixed}
11691
+ `);
11692
+ if (fixFailed > 0)
11693
+ process.stdout.write(`fix-failed: ${fixFailed}
11554
11694
  `);
11555
11695
  if (unfixable.length > 0) {
11556
11696
  process.stdout.write(`manual: ${unfixable.map((r2) => r2.check).join(", ")}
11557
11697
  `);
11558
11698
  }
11559
11699
  }
11700
+ return fixFailed;
11560
11701
  }
11561
11702
 
11562
11703
  // src/commands/init.ts
11563
11704
  var import_picocolors7 = __toESM(require_picocolors(), 1);
11564
11705
  var import_yaml4 = __toESM(require_dist(), 1);
11565
- import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as writeFile3 } from "fs/promises";
11706
+ import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as writeFile4 } from "fs/promises";
11566
11707
  import { homedir as homedir3 } from "os";
11567
11708
  import { dirname as dirname3, join as join6 } from "path";
11568
11709
  init_cli_ui();
@@ -11617,7 +11758,7 @@ var VAULT_YML_DEFAULTS = {
11617
11758
  };
11618
11759
  async function writeVaultYml(vaultDir) {
11619
11760
  const content = import_yaml4.stringify(VAULT_YML_DEFAULTS, { lineWidth: 0 });
11620
- await writeFile3(join6(vaultDir, "vault.yml"), content, "utf8");
11761
+ await writeFile4(join6(vaultDir, "vault.yml"), content, "utf8");
11621
11762
  }
11622
11763
  async function downloadPluginFiles(vaultDir, vaultSyncFn) {
11623
11764
  const pluginJsonPath = join6(vaultDir, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json");
@@ -11706,7 +11847,7 @@ async function registerPlugin(vaultDir, installedPluginsPath) {
11706
11847
  const tmpPath = `${installedPluginsPath}.tmp`;
11707
11848
  try {
11708
11849
  await mkdir3(dirname3(installedPluginsPath), { recursive: true });
11709
- await writeFile3(tmpPath, JSON.stringify(data, null, 4), "utf8");
11850
+ await writeFile4(tmpPath, JSON.stringify(data, null, 4), "utf8");
11710
11851
  await rename3(tmpPath, installedPluginsPath);
11711
11852
  } catch (err) {
11712
11853
  const msg = err instanceof Error ? err.message : String(err);
@@ -11817,7 +11958,7 @@ async function installObsidianPlugins(vaultDir, opts) {
11817
11958
  throw new Error(`HTTP ${resp.status}`);
11818
11959
  }
11819
11960
  const buf = await resp.arrayBuffer();
11820
- await writeFile3(join6(pluginDir, assetName), Buffer.from(buf));
11961
+ await writeFile4(join6(pluginDir, assetName), Buffer.from(buf));
11821
11962
  } catch (err) {
11822
11963
  if (assetName === "styles.css")
11823
11964
  continue;
@@ -12201,7 +12342,7 @@ async function checkpointCommand(mode, token, vaultRoot) {
12201
12342
  // src/commands/internal/migrate.ts
12202
12343
  init_lib();
12203
12344
  var import_yaml5 = __toESM(require_dist(), 1);
12204
- import { readFile as readFile4, readdir as readdir3, writeFile as writeFile4 } from "fs/promises";
12345
+ import { readFile as readFile4, readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
12205
12346
  import { join as join8 } from "path";
12206
12347
  function parseFrontmatterWithRest(rawText) {
12207
12348
  const text = rawText.replace(/\r\n/g, `
@@ -12285,7 +12426,7 @@ async function runBackfillRecapped(logsFolder, cutoffDate) {
12285
12426
  const updatedContent = `---
12286
12427
  ${updatedFm}---
12287
12428
  ${rest}`;
12288
- await writeFile4(fpath, updatedContent, "utf8");
12429
+ await writeFile5(fpath, updatedContent, "utf8");
12289
12430
  backfilled++;
12290
12431
  } catch (error) {
12291
12432
  process.stderr.write(`migrate: error processing ${fname}: ${error}
@@ -12451,7 +12592,7 @@ init_register_hooks();
12451
12592
 
12452
12593
  // src/commands/internal/session-init.ts
12453
12594
  init_lib();
12454
- import { unlink as unlink2 } from "fs/promises";
12595
+ import { unlink as unlink3 } from "fs/promises";
12455
12596
  import { tmpdir as osTmpdir2 } from "os";
12456
12597
  import { join as join10 } from "path";
12457
12598
  var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
@@ -12564,7 +12705,7 @@ async function cleanStaleStateFile(token, tmpDir) {
12564
12705
  const mtimeMs = mtime instanceof Date ? mtime.getTime() : Number(mtime) * 1000;
12565
12706
  if (mtimeMs < processStartMs) {
12566
12707
  try {
12567
- await unlink2(stateFile);
12708
+ await unlink3(stateFile);
12568
12709
  } catch {}
12569
12710
  }
12570
12711
  } catch {}
@@ -12848,8 +12989,8 @@ function patchUtf8(stream) {
12848
12989
  }
12849
12990
 
12850
12991
  // src/index.ts
12851
- var VERSION = "2.1.10";
12852
- var RELEASE_DATE = "2026-05-05";
12992
+ var VERSION = "2.1.11";
12993
+ var RELEASE_DATE = "2026-05-06";
12853
12994
  patchUtf8(process.stdout);
12854
12995
  patchUtf8(process.stderr);
12855
12996
  var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.1.10",
3
+ "version": "2.1.11",
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",