@onebrain-ai/cli 2.3.2 → 2.3.3

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 +194 -59
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -9590,7 +9590,7 @@ var init_lib = __esm(() => {
9590
9590
  var require_package = __commonJS((exports, module) => {
9591
9591
  module.exports = {
9592
9592
  name: "@onebrain-ai/cli",
9593
- version: "2.3.2",
9593
+ version: "2.3.3",
9594
9594
  description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9595
9595
  keywords: [
9596
9596
  "onebrain",
@@ -11050,9 +11050,90 @@ var init_vault_sync = __esm(() => {
11050
11050
  import_yaml3 = __toESM(require_dist(), 1);
11051
11051
  });
11052
11052
 
11053
+ // src/commands/run-skill.ts
11054
+ var exports_run_skill = {};
11055
+ __export(exports_run_skill, {
11056
+ runSkillCommand: () => runSkillCommand,
11057
+ buildPrompt: () => buildPrompt
11058
+ });
11059
+ import { spawn } from "child_process";
11060
+ import { existsSync } from "fs";
11061
+ import { constants as osConstants } from "os";
11062
+ import { join as join11 } from "path";
11063
+ function resolveClaudeBin(override) {
11064
+ if (override)
11065
+ return override;
11066
+ const fromEnv = process.env["CLAUDE_BIN"];
11067
+ if (fromEnv) {
11068
+ if (existsSync(fromEnv))
11069
+ return fromEnv;
11070
+ console.error(import_picocolors8.default.yellow(`CLAUDE_BIN points to a missing file: ${fromEnv} \u2014 ignoring and probing defaults`));
11071
+ }
11072
+ for (const candidate of CLAUDE_FALLBACK_PATHS) {
11073
+ if (existsSync(candidate))
11074
+ return candidate;
11075
+ }
11076
+ return "claude";
11077
+ }
11078
+ function buildPrompt(skill, args) {
11079
+ const bare = skill.replace(/^\//, "");
11080
+ if (!bare) {
11081
+ throw new Error('skill name must not be empty (got "/" or "")');
11082
+ }
11083
+ const namespaced = bare.includes(":") ? bare : `onebrain:${bare}`;
11084
+ const slash = `/${namespaced}`;
11085
+ if (!args)
11086
+ return slash;
11087
+ const tokens = Object.entries(args).map(([k2, v2]) => `${k2}=${v2}`);
11088
+ return tokens.length ? `${slash} ${tokens.join(" ")}` : slash;
11089
+ }
11090
+ async function runSkillCommand(opts) {
11091
+ const vault = opts.vault;
11092
+ if (!existsSync(join11(vault, "vault.yml"))) {
11093
+ console.error(import_picocolors8.default.red(`Vault not found at ${vault} (no vault.yml present)`));
11094
+ return 78;
11095
+ }
11096
+ const claudeBin = resolveClaudeBin(opts.claudeBin);
11097
+ const prompt = buildPrompt(opts.skill, opts.args);
11098
+ const spawnFn = opts.spawnFn ?? spawn;
11099
+ const spawnOpts = {
11100
+ cwd: vault,
11101
+ stdio: "inherit"
11102
+ };
11103
+ const child = spawnFn(claudeBin, ["-p", prompt, "--add-dir", vault], spawnOpts);
11104
+ return await new Promise((resolve) => {
11105
+ child.on("exit", (code, signal) => {
11106
+ if (signal) {
11107
+ console.error(import_picocolors8.default.red(`claude terminated by signal: ${signal}`));
11108
+ resolve(128 + signalNumber(signal));
11109
+ return;
11110
+ }
11111
+ resolve(code ?? 1);
11112
+ });
11113
+ child.on("error", (err) => {
11114
+ console.error(import_picocolors8.default.red(`Failed to spawn claude (${claudeBin}): ${err.message}`));
11115
+ resolve(127);
11116
+ });
11117
+ });
11118
+ }
11119
+ function signalNumber(signal) {
11120
+ const sigs = osConstants.signals;
11121
+ return sigs[signal] ?? 0;
11122
+ }
11123
+ var import_picocolors8, HOME, CLAUDE_FALLBACK_PATHS;
11124
+ var init_run_skill = __esm(() => {
11125
+ import_picocolors8 = __toESM(require_picocolors(), 1);
11126
+ HOME = process.env["HOME"] ?? "";
11127
+ CLAUDE_FALLBACK_PATHS = [
11128
+ ...HOME ? [join11(HOME, ".local/bin/claude")] : [],
11129
+ "/opt/homebrew/bin/claude",
11130
+ "/usr/local/bin/claude"
11131
+ ];
11132
+ });
11133
+
11053
11134
  // src/index.ts
11054
- import { existsSync as existsSync2 } from "fs";
11055
- import { dirname as dirname4, join as join12 } from "path";
11135
+ import { existsSync as existsSync3 } from "fs";
11136
+ import { dirname as dirname4, join as join13 } from "path";
11056
11137
 
11057
11138
  // node_modules/commander/esm.mjs
11058
11139
  var import__ = __toESM(require_commander(), 1);
@@ -11078,7 +11159,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
11078
11159
  var import_picocolors = __toESM(require_picocolors(), 1);
11079
11160
  function resolveBinaryVersion() {
11080
11161
  if (true)
11081
- return "2.3.2";
11162
+ return "2.3.3";
11082
11163
  try {
11083
11164
  const pkg = require_package();
11084
11165
  return pkg.version ?? "dev";
@@ -13152,12 +13233,13 @@ async function sessionInitCommand(vaultRoot) {
13152
13233
  init_vault_sync();
13153
13234
 
13154
13235
  // src/commands/register-schedule.ts
13155
- var import_picocolors8 = __toESM(require_picocolors(), 1);
13236
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
13156
13237
  var import_yaml7 = __toESM(require_dist(), 1);
13157
- import { existsSync } from "fs";
13238
+ import { execFileSync } from "child_process";
13239
+ import { existsSync as existsSync2 } from "fs";
13158
13240
  import { readFile as readFile6, unlink as unlink4, writeFile as writeFile6 } from "fs/promises";
13159
13241
  import { homedir as homedir4 } from "os";
13160
- import { join as join11 } from "path";
13242
+ import { isAbsolute, join as join12, resolve as pathResolve } from "path";
13161
13243
 
13162
13244
  // src/lib/scheduler/cron-parse.ts
13163
13245
  var CRON_FIELD_RE = /^(\*|\d+)$/;
@@ -13276,9 +13358,10 @@ function validateEntry(entry) {
13276
13358
  }
13277
13359
 
13278
13360
  // src/lib/scheduler/launchd.ts
13361
+ import { basename } from "path";
13279
13362
  var xmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13280
13363
  function labelForEntry(entry) {
13281
- const raw = isCommandMode(entry) ? entry.command : (entry.skill ?? "").replace(/^\//, "");
13364
+ const raw = isCommandMode(entry) ? basename(entry.command) : (entry.skill ?? "").replace(/^\//, "");
13282
13365
  return raw.replace(/[^a-zA-Z0-9-]/g, "-");
13283
13366
  }
13284
13367
  function generatePlist(entry, ctx) {
@@ -13300,9 +13383,9 @@ function generatePlist(entry, ctx) {
13300
13383
  <string>-c</string>
13301
13384
  <string>${shellLine}</string>`;
13302
13385
  } else {
13303
- const plistFilePath = plistPath(entry.skill ?? "", ctx.homedir);
13304
- const argsFlags = entry.args ? ` ${Object.entries(entry.args).map(([k2, v2]) => `--${k2}="${v2}"`).join(" ")}` : "";
13305
- const shellLine = xmlEscape(`"${ctx.skillCliPath}" --vault="${ctx.vaultPath}" --skill="${entry.skill}" --headless${argsFlags}; launchctl bootout gui/${ctx.uid}/${label}; rm -f "${plistFilePath}"`);
13386
+ const plistFilePath = `${ctx.homedir}/Library/LaunchAgents/${label}.plist`;
13387
+ const argsFlags = entry.args ? ` ${Object.entries(entry.args).map(([k2, v2]) => `--arg="${k2}=${v2}"`).join(" ")}` : "";
13388
+ const shellLine = xmlEscape(`"${ctx.skillCliPath}" run-skill --vault="${ctx.vaultPath}" --skill="${entry.skill}"${argsFlags}; launchctl bootout gui/${ctx.uid}/${label}; rm -f "${plistFilePath}"`);
13306
13389
  programArgumentsBlock = ` <string>/bin/sh</string>
13307
13390
  <string>-c</string>
13308
13391
  <string>${shellLine}</string>`;
@@ -13316,14 +13399,17 @@ function generatePlist(entry, ctx) {
13316
13399
  `);
13317
13400
  } else {
13318
13401
  const argsBlock = entry.args ? `
13319
- ${Object.entries(entry.args).map(([k2, v2]) => ` <string>--${xmlEscape(k2)}=${xmlEscape(v2)}</string>`).join(`
13402
+ ${Object.entries(entry.args).flatMap(([k2, v2]) => [
13403
+ " <string>--arg</string>",
13404
+ ` <string>${xmlEscape(`${k2}=${v2}`)}</string>`
13405
+ ]).join(`
13320
13406
  `)}` : "";
13321
13407
  programArgumentsBlock = ` <string>${xmlEscape(ctx.skillCliPath)}</string>
13408
+ <string>run-skill</string>
13322
13409
  <string>--vault</string>
13323
13410
  <string>${xmlEscape(ctx.vaultPath)}</string>
13324
13411
  <string>--skill</string>
13325
- <string>${xmlEscape(entry.skill ?? "")}</string>
13326
- <string>--headless</string>${argsBlock}`;
13412
+ <string>${xmlEscape(entry.skill ?? "")}</string>${argsBlock}`;
13327
13413
  }
13328
13414
  return `<?xml version="1.0" encoding="UTF-8"?>
13329
13415
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -13364,12 +13450,12 @@ async function registerSchedule(opts) {
13364
13450
  if (opts.resume)
13365
13451
  return await resumeSkill(opts.vault, opts.resume);
13366
13452
  if (opts.refresh) {
13367
- console.log(import_picocolors8.default.dim("(--refresh: re-emitting plists with current vault path)"));
13453
+ console.log(import_picocolors9.default.dim("(--refresh: re-emitting plists with current vault path)"));
13368
13454
  }
13369
13455
  const config = await readVaultConfig(opts.vault);
13370
13456
  const entries = config.schedule ?? [];
13371
13457
  if (entries.length === 0) {
13372
- console.log(import_picocolors8.default.yellow("No schedule entries in vault.yml. Nothing to register."));
13458
+ console.log(import_picocolors9.default.yellow("No schedule entries in vault.yml. Nothing to register."));
13373
13459
  return;
13374
13460
  }
13375
13461
  for (const entry of entries) {
@@ -13390,16 +13476,17 @@ async function registerSchedule(opts) {
13390
13476
  await validateSchedulable(opts.vault, entry);
13391
13477
  }
13392
13478
  }
13479
+ const resolvedEntries = entries.map((entry) => isCommandMode(entry) ? { ...entry, command: resolveCommandBinary(entry.command, opts.vault) } : entry);
13393
13480
  const skillCliPath = process.argv[1] ?? "onebrain";
13394
13481
  const ctx = {
13395
13482
  vaultPath: opts.vault,
13396
13483
  skillCliPath,
13397
- logBasePath: join11(opts.vault, "07-logs/scheduler"),
13484
+ logBasePath: join12(opts.vault, "07-logs/scheduler"),
13398
13485
  uid: process.getuid?.() ?? 501,
13399
13486
  homedir: homedir4()
13400
13487
  };
13401
13488
  const seen = new Map;
13402
- for (const entry of entries) {
13489
+ for (const entry of resolvedEntries) {
13403
13490
  const target = plistPath(labelForEntry(entry), ctx.homedir);
13404
13491
  if (seen.has(target)) {
13405
13492
  const existing = seen.get(target);
@@ -13411,28 +13498,28 @@ async function registerSchedule(opts) {
13411
13498
  }
13412
13499
  seen.set(target, entry);
13413
13500
  }
13414
- for (const entry of entries) {
13501
+ for (const entry of resolvedEntries) {
13415
13502
  const plistContent = generatePlist(entry, ctx);
13416
13503
  const targetPath = plistPath(labelForEntry(entry), ctx.homedir);
13417
13504
  if (opts.dryRun) {
13418
- console.log(import_picocolors8.default.cyan(`--- ${targetPath} ---`));
13505
+ console.log(import_picocolors9.default.cyan(`--- ${targetPath} ---`));
13419
13506
  console.log(plistContent);
13420
13507
  continue;
13421
13508
  }
13422
13509
  await writeFile6(targetPath, plistContent, "utf8");
13423
- console.log(import_picocolors8.default.green(`\u2713 Wrote ${targetPath}`));
13510
+ console.log(import_picocolors9.default.green(`\u2713 Wrote ${targetPath}`));
13424
13511
  }
13425
- console.log(import_picocolors8.default.green(`
13512
+ console.log(import_picocolors9.default.green(`
13426
13513
  Registered ${entries.length} schedule entries.`));
13427
- console.log(import_picocolors8.default.dim("Use launchctl to load (or restart launchd):"));
13428
- for (const entry of entries) {
13514
+ console.log(import_picocolors9.default.dim("Use launchctl to load (or restart launchd):"));
13515
+ for (const entry of resolvedEntries) {
13429
13516
  const target = plistPath(labelForEntry(entry), ctx.homedir);
13430
- console.log(import_picocolors8.default.dim(` launchctl load ${target}`));
13517
+ console.log(import_picocolors9.default.dim(` launchctl load ${target}`));
13431
13518
  }
13432
13519
  }
13433
13520
  async function readVaultConfig(vault) {
13434
- const yamlPath = join11(vault, "vault.yml");
13435
- if (!existsSync(yamlPath))
13521
+ const yamlPath = join12(vault, "vault.yml");
13522
+ if (!existsSync2(yamlPath))
13436
13523
  return {};
13437
13524
  const raw = await readFile6(yamlPath, "utf8");
13438
13525
  return import_yaml7.parse(raw) ?? {};
@@ -13450,8 +13537,8 @@ async function validateSchedulable(vault, entry) {
13450
13537
  throw new Error("validateSchedulable invoked on non-skill entry \u2014 caller bug");
13451
13538
  }
13452
13539
  const skillName = entry.skill.replace(/^\//, "");
13453
- const skillPath = join11(vault, ".claude/plugins/onebrain/skills", skillName, "SKILL.md");
13454
- if (!existsSync(skillPath)) {
13540
+ const skillPath = join12(vault, ".claude/plugins/onebrain/skills", skillName, "SKILL.md");
13541
+ if (!existsSync2(skillPath)) {
13455
13542
  throw new Error(`Skill ${entry.skill} not found at ${skillPath}`);
13456
13543
  }
13457
13544
  const raw = await readFile6(skillPath, "utf8");
@@ -13486,55 +13573,85 @@ async function removeAll(vault) {
13486
13573
  const entries = config.schedule ?? [];
13487
13574
  for (const entry of entries) {
13488
13575
  const target = plistPath(labelForEntry(entry), homedir4());
13489
- if (existsSync(target)) {
13576
+ if (existsSync2(target)) {
13490
13577
  await unlink4(target);
13491
- console.log(import_picocolors8.default.green(`\u2713 Removed ${target}`));
13578
+ console.log(import_picocolors9.default.green(`\u2713 Removed ${target}`));
13492
13579
  }
13493
13580
  }
13494
13581
  }
13495
13582
  async function printStatus(vault) {
13496
13583
  const config = await readVaultConfig(vault);
13497
13584
  const entries = config.schedule ?? [];
13498
- console.log(import_picocolors8.default.cyan(`Registered schedules: ${entries.length}`));
13585
+ console.log(import_picocolors9.default.cyan(`Registered schedules: ${entries.length}`));
13499
13586
  for (const entry of entries) {
13500
13587
  const target = plistPath(labelForEntry(entry), homedir4());
13501
- const installed = existsSync(target) ? "\u2713" : "\u2717";
13588
+ const installed = existsSync2(target) ? "\u2713" : "\u2717";
13502
13589
  const when = entry.at ?? entry.cron ?? "?";
13503
- const tag = entry.at ? import_picocolors8.default.magenta("[once]") : import_picocolors8.default.dim("[cron]");
13590
+ const tag = entry.at ? import_picocolors9.default.magenta("[once]") : import_picocolors9.default.dim("[cron]");
13504
13591
  let targetLabel;
13505
13592
  if (isCommandMode(entry)) {
13506
13593
  const argv = entry.args ?? [];
13507
13594
  const argStr = argv.length ? ` ${argv.join(" ")}` : "";
13508
- targetLabel = `${import_picocolors8.default.yellow("cmd:")} ${entry.command}${argStr}`;
13595
+ targetLabel = `${import_picocolors9.default.yellow("cmd:")} ${entry.command}${argStr}`;
13509
13596
  } else {
13510
13597
  const argsMap = entry.args ?? {};
13511
13598
  const argStr = Object.keys(argsMap).length ? ` (${Object.entries(argsMap).map(([k2, v2]) => `${k2}=${v2}`).join(", ")})` : "";
13512
- targetLabel = `${import_picocolors8.default.green("skill:")} ${entry.skill}${argStr}`;
13599
+ targetLabel = `${import_picocolors9.default.green("skill:")} ${entry.skill}${argStr}`;
13513
13600
  }
13514
13601
  console.log(` ${installed} ${tag} ${when} ${targetLabel}`);
13515
13602
  }
13516
13603
  }
13517
13604
  async function testRun(vault, skill) {
13518
- console.log(import_picocolors8.default.cyan(`Testing scheduled invocation of ${skill}...`));
13519
- console.log(import_picocolors8.default.dim("(Spawns headless Claude Code. Output streams here.)"));
13520
- const { spawn } = await import("child_process");
13521
- const child = spawn("claude", ["--vault", vault, "--skill", skill, "--headless"], {
13522
- stdio: "inherit"
13523
- });
13524
- await new Promise((resolve) => child.on("exit", resolve));
13605
+ console.log(import_picocolors9.default.cyan(`Testing scheduled invocation of ${skill}...`));
13606
+ console.log(import_picocolors9.default.dim("(Spawns `onebrain run-skill` which shells out to Claude Code.)"));
13607
+ const { runSkillCommand: runSkillCommand2 } = await Promise.resolve().then(() => (init_run_skill(), exports_run_skill));
13608
+ const code = await runSkillCommand2({ vault, skill });
13609
+ if (code !== 0) {
13610
+ console.error(import_picocolors9.default.red(`Test run exited with code ${code}`));
13611
+ process.exit(code);
13612
+ }
13613
+ }
13614
+ var WHICH_BIN = "/usr/bin/which";
13615
+ function resolveCommandBinary(name, vaultRoot) {
13616
+ if (isAbsolute(name)) {
13617
+ if (!existsSync2(name)) {
13618
+ throw new Error(`Command not found at absolute path: ${name}. Check the path in vault.yml \u2014 launchd will silently fail at run time if the binary is missing.`);
13619
+ }
13620
+ return name;
13621
+ }
13622
+ if (name.startsWith("./") || name.startsWith("../")) {
13623
+ const base = vaultRoot ?? process.cwd();
13624
+ const resolved = pathResolve(base, name);
13625
+ if (!existsSync2(resolved)) {
13626
+ throw new Error(`Command not found at relative path: ${name} (resolved: ${resolved})`);
13627
+ }
13628
+ return resolved;
13629
+ }
13630
+ try {
13631
+ const out2 = execFileSync(WHICH_BIN, [name], {
13632
+ encoding: "utf8",
13633
+ stdio: ["ignore", "pipe", "ignore"]
13634
+ }).trim();
13635
+ if (out2 && existsSync2(out2))
13636
+ return out2;
13637
+ } catch {}
13638
+ throw new Error(`Command "${name}" not found in PATH. Use an absolute path in vault.yml (launchd's PATH is restricted to /usr/bin:/bin:/usr/sbin:/sbin and won't find ${name}).`);
13525
13639
  }
13526
13640
  async function resumeSkill(vault, skill) {
13527
- const marker = join11(vault, "07-logs/scheduler/.paused", `${skill.replace(/^\//, "")}.txt`);
13528
- if (existsSync(marker)) {
13641
+ const marker = join12(vault, "07-logs/scheduler/.paused", `${skill.replace(/^\//, "")}.txt`);
13642
+ if (existsSync2(marker)) {
13529
13643
  await unlink4(marker);
13530
- console.log(import_picocolors8.default.green(`\u2713 Resumed ${skill}`));
13644
+ console.log(import_picocolors9.default.green(`\u2713 Resumed ${skill}`));
13531
13645
  } else {
13532
- console.log(import_picocolors8.default.yellow(`${skill} is not paused.`));
13646
+ console.log(import_picocolors9.default.yellow(`${skill} is not paused.`));
13533
13647
  }
13534
13648
  }
13535
13649
 
13650
+ // src/index.ts
13651
+ init_run_skill();
13652
+
13536
13653
  // src/commands/update.ts
13537
- var import_picocolors9 = __toESM(require_picocolors(), 1);
13654
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
13538
13655
  init_cli_ui();
13539
13656
  var GITHUB_REPO = "https://api.github.com/repos/onebrain-ai/onebrain";
13540
13657
  var GITHUB_RELEASES_URL = `${GITHUB_REPO}/releases/latest`;
@@ -13629,7 +13746,7 @@ async function runUpdate(opts = {}) {
13629
13746
  const sp1 = createStep("\uD83D\uDD0D", "Local version");
13630
13747
  const { version: currentVersion, publishedAt: localPublishedAt } = await currentVersionFn();
13631
13748
  result.currentVersion = currentVersion;
13632
- const localVersionLabel = localPublishedAt ? `${import_picocolors9.default.dim(currentVersion)} ${import_picocolors9.default.dim("\xB7")} ${import_picocolors9.default.dim(formatReleaseDate(localPublishedAt))}` : import_picocolors9.default.dim(currentVersion);
13749
+ const localVersionLabel = localPublishedAt ? `${import_picocolors10.default.dim(currentVersion)} ${import_picocolors10.default.dim("\xB7")} ${import_picocolors10.default.dim(formatReleaseDate(localPublishedAt))}` : import_picocolors10.default.dim(currentVersion);
13633
13750
  if (sp1)
13634
13751
  sp1.stop(localVersionLabel);
13635
13752
  else
@@ -13641,9 +13758,9 @@ async function runUpdate(opts = {}) {
13641
13758
  const release = await fetchLatestRelease(fetchFn);
13642
13759
  latestVersion = release.version;
13643
13760
  publishedAt = release.publishedAt;
13644
- const dateSuffix = publishedAt ? ` ${import_picocolors9.default.dim("\xB7")} ${import_picocolors9.default.dim(formatReleaseDate(publishedAt))}` : "";
13761
+ const dateSuffix = publishedAt ? ` ${import_picocolors10.default.dim("\xB7")} ${import_picocolors10.default.dim(formatReleaseDate(publishedAt))}` : "";
13645
13762
  if (sp2)
13646
- sp2.stop(`${import_picocolors9.default.green(latestVersion)}${dateSuffix}`);
13763
+ sp2.stop(`${import_picocolors10.default.green(latestVersion)}${dateSuffix}`);
13647
13764
  else
13648
13765
  writeLine(`latest: ${latestVersion}`);
13649
13766
  } catch (err) {
@@ -13664,7 +13781,7 @@ async function runUpdate(opts = {}) {
13664
13781
  if (check) {
13665
13782
  if (isTTY) {
13666
13783
  if (currentVersion !== latestVersion) {
13667
- barLine(`\u2B06\uFE0F ${import_picocolors9.default.dim(currentVersion)} \u2192 ${import_picocolors9.default.green(latestVersion)} \xB7 binary would upgrade`);
13784
+ barLine(`\u2B06\uFE0F ${import_picocolors10.default.dim(currentVersion)} \u2192 ${import_picocolors10.default.green(latestVersion)} \xB7 binary would upgrade`);
13668
13785
  barBlank();
13669
13786
  }
13670
13787
  close("Dry run complete \u2014 no changes made");
@@ -13677,7 +13794,7 @@ async function runUpdate(opts = {}) {
13677
13794
  }
13678
13795
  if (latestVersion === currentVersion) {
13679
13796
  if (isTTY) {
13680
- close(`Already up to date \u2014 @onebrain-ai/cli ${import_picocolors9.default.dim(latestVersion)}`);
13797
+ close(`Already up to date \u2014 @onebrain-ai/cli ${import_picocolors10.default.dim(latestVersion)}`);
13681
13798
  } else {
13682
13799
  writeLine(`already up to date: @onebrain-ai/cli ${latestVersion}`);
13683
13800
  writeLine("done: nothing to do");
@@ -13687,14 +13804,14 @@ async function runUpdate(opts = {}) {
13687
13804
  return result;
13688
13805
  }
13689
13806
  if (isTTY) {
13690
- barLine(`\u2B06\uFE0F ${import_picocolors9.default.dim(currentVersion)} \u2192 ${import_picocolors9.default.green(latestVersion)}`);
13807
+ barLine(`\u2B06\uFE0F ${import_picocolors10.default.dim(currentVersion)} \u2192 ${import_picocolors10.default.green(latestVersion)}`);
13691
13808
  barBlank();
13692
13809
  }
13693
13810
  const sp3 = createStep("\uD83D\uDCE6", "Installing @onebrain-ai/cli");
13694
13811
  try {
13695
13812
  await installBinaryFn(latestVersion);
13696
13813
  if (sp3)
13697
- sp3.stop(import_picocolors9.default.green(latestVersion));
13814
+ sp3.stop(import_picocolors10.default.green(latestVersion));
13698
13815
  else
13699
13816
  writeLine(`upgrading: @onebrain-ai/cli ${latestVersion} installed`);
13700
13817
  } catch (err) {
@@ -13731,7 +13848,7 @@ async function runUpdate(opts = {}) {
13731
13848
  result.ok = true;
13732
13849
  result.exitCode = 0;
13733
13850
  if (isTTY) {
13734
- close(`Done \u2014 run ${import_picocolors9.default.cyan("/update")} in Claude to sync vault files`);
13851
+ close(`Done \u2014 run ${import_picocolors10.default.cyan("/update")} in Claude to sync vault files`);
13735
13852
  } else {
13736
13853
  writeLine("done: run /update in Claude to sync vault files");
13737
13854
  }
@@ -13758,8 +13875,8 @@ function patchUtf8(stream) {
13758
13875
  }
13759
13876
 
13760
13877
  // src/index.ts
13761
- var VERSION = "2.3.2";
13762
- var RELEASE_DATE = "2026-05-12";
13878
+ var VERSION = "2.3.3";
13879
+ var RELEASE_DATE = "2026-05-13";
13763
13880
  patchUtf8(process.stdout);
13764
13881
  patchUtf8(process.stderr);
13765
13882
  var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
@@ -13773,7 +13890,7 @@ function findVaultRoot(startDir) {
13773
13890
  return process.cwd();
13774
13891
  let dir = startDir;
13775
13892
  while (true) {
13776
- if (existsSync2(join12(dir, "vault.yml")))
13893
+ if (existsSync3(join13(dir, "vault.yml")))
13777
13894
  return dir;
13778
13895
  const parent = dirname4(dir);
13779
13896
  if (parent === dir)
@@ -13830,7 +13947,25 @@ program2.command("vault-sync", { hidden: true }).description("Sync plugin files
13830
13947
  program2.command("register-hooks", { hidden: true }).description("Install Claude Code hooks into settings.json").option("--vault-dir <path>", "vault root directory (default: cwd)").action(async (opts) => {
13831
13948
  await registerHooksCommand(opts.vaultDir);
13832
13949
  });
13950
+ program2.command("run-skill", { hidden: true }).description("Run a OneBrain skill headlessly via Claude Code (used by the scheduler)").requiredOption("--vault <path>", "Vault root directory").requiredOption("--skill <name>", "Skill name (e.g. /daily)").option("--arg <key=value>", "Skill argument, repeatable (e.g. --arg topic=this-week)", collectKeyValue, {}).action(async (opts) => {
13951
+ const exitCode = await runSkillCommand({
13952
+ vault: opts.vault,
13953
+ skill: opts.skill,
13954
+ ...Object.keys(opts.arg).length > 0 ? { args: opts.arg } : {}
13955
+ });
13956
+ process.exit(exitCode);
13957
+ });
13833
13958
  program2.command("migrate", { hidden: true }).description("Run one-time migration scripts").argument("<name>", "migration name: backfill-recapped").argument("[cutoff_date]", "ISO date cutoff (YYYY-MM-DD) \u2014 skip logs newer than this date").action(async (name, cutoffDate) => {
13834
13959
  await migrateCommand(name, cutoffDate);
13835
13960
  });
13961
+ function collectKeyValue(value, prev) {
13962
+ const eq = value.indexOf("=");
13963
+ if (eq === -1) {
13964
+ throw new Error(`Invalid --arg format: "${value}" (expected key=value)`);
13965
+ }
13966
+ if (eq === 0) {
13967
+ throw new Error(`Invalid --arg: key is empty in "${value}"`);
13968
+ }
13969
+ return { ...prev, [value.slice(0, eq)]: value.slice(eq + 1) };
13970
+ }
13836
13971
  program2.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
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",