@onebrain-ai/cli 2.0.8 → 2.0.9

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 +111 -108
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -9571,7 +9571,7 @@ async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink) {
9571
9571
  return { filesAdded, filesRemoved };
9572
9572
  }
9573
9573
  async function copyRootDocs(extractedDir, vaultRoot) {
9574
- const docs = ["README.md", "CONTRIBUTING.md", "CHANGELOG.md"];
9574
+ const docs = ["README.md", "CONTRIBUTING.md", "CHANGELOG.md", "PLUGIN-CHANGELOG.md"];
9575
9575
  for (const doc of docs) {
9576
9576
  const srcPath = join3(extractedDir, doc);
9577
9577
  const destPath = join3(vaultRoot, doc);
@@ -10002,53 +10002,52 @@ function applyHooks(settings) {
10002
10002
  result[event] = "ok";
10003
10003
  } else if (presence === "migrate") {
10004
10004
  for (const group of groups) {
10005
+ if (group.matcher === undefined)
10006
+ group.matcher = "";
10005
10007
  for (const entry of group.hooks ?? []) {
10006
10008
  if ((entry.command ?? "").includes("checkpoint-hook.sh")) {
10007
10009
  entry.command = cmd;
10010
+ if (!entry.type)
10011
+ entry.type = "command";
10008
10012
  }
10009
10013
  }
10010
10014
  }
10011
10015
  result[event] = "migrated";
10012
10016
  } else {
10013
- groups.push({ hooks: [{ command: cmd }] });
10017
+ groups.push({ matcher: "", hooks: [{ type: "command", command: cmd }] });
10014
10018
  result[event] = "added";
10015
10019
  }
10016
10020
  }
10017
10021
  return result;
10018
10022
  }
10019
- function applyPath(settings) {
10020
- if (!settings.env)
10021
- settings.env = {};
10022
- const existing = settings.env.PATH ?? "";
10023
- const parts = existing ? existing.split(":") : [];
10024
- const bunForms = [BUN_BIN, "$HOME/.bun/bin", "${HOME}/.bun/bin", "~/.bun/bin"];
10025
- const npmForms = [
10026
- NPM_GLOBAL_BIN,
10027
- "$HOME/.npm-global/bin",
10028
- "${HOME}/.npm-global/bin",
10029
- "~/.npm-global/bin"
10030
- ];
10031
- const missing = [];
10032
- if (!bunForms.some((f2) => parts.includes(f2)))
10033
- missing.push(BUN_BIN);
10034
- if (!npmForms.some((f2) => parts.includes(f2)))
10035
- missing.push(NPM_GLOBAL_BIN);
10036
- if (missing.length === 0)
10023
+ function applyQmdHook(settings) {
10024
+ if (!settings.hooks)
10025
+ settings.hooks = {};
10026
+ if (!settings.hooks["PostToolUse"])
10027
+ settings.hooks["PostToolUse"] = [];
10028
+ const groups = settings.hooks["PostToolUse"];
10029
+ const already = groups.some((g) => g.hooks?.some((h2) => h2.command === QMD_CMD));
10030
+ if (already)
10037
10031
  return "ok";
10038
- const base = existing || "${PATH}";
10039
- const hasPlaceholder = base.includes("${PATH}");
10040
- if (hasPlaceholder) {
10041
- const withoutPlaceholder = base.replace("${PATH}", "").replace(/:+$/, "").replace(/^:+/, "");
10042
- const allParts = [
10043
- ...missing,
10044
- ...withoutPlaceholder ? withoutPlaceholder.split(":").filter(Boolean) : [],
10045
- "${PATH}"
10046
- ];
10047
- settings.env.PATH = allParts.join(":");
10032
+ groups.push({ matcher: QMD_MATCHER, hooks: [{ type: "command", command: QMD_CMD }] });
10033
+ return "added";
10034
+ }
10035
+ function removeQmdHook(settings) {
10036
+ if (!settings.hooks?.["PostToolUse"])
10037
+ return "ok";
10038
+ const before = settings.hooks["PostToolUse"].length;
10039
+ const filtered = settings.hooks["PostToolUse"].filter((g) => !g.hooks?.some((h2) => (h2.command ?? "").includes("qmd-reindex")));
10040
+ if (filtered.length === 0) {
10041
+ const remaining = {};
10042
+ for (const [key, val] of Object.entries(settings.hooks)) {
10043
+ if (key !== "PostToolUse")
10044
+ remaining[key] = val;
10045
+ }
10046
+ settings.hooks = remaining;
10048
10047
  } else {
10049
- settings.env.PATH = [...missing, base].join(":");
10048
+ settings.hooks["PostToolUse"] = filtered;
10050
10049
  }
10051
- return "updated";
10050
+ return before !== filtered.length ? "removed" : "ok";
10052
10051
  }
10053
10052
  function applyPermissions(settings) {
10054
10053
  if (!settings.permissions)
@@ -10118,7 +10117,6 @@ async function runRegisterHooks(opts = {}) {
10118
10117
  const result = {
10119
10118
  ok: false,
10120
10119
  hooks: {},
10121
- pathStatus: "ok",
10122
10120
  permissionsAdded: []
10123
10121
  };
10124
10122
  const settingsPath = join4(vaultRoot, ".claude", "settings.json");
@@ -10148,19 +10146,17 @@ async function runRegisterHooks(opts = {}) {
10148
10146
  } else {
10149
10147
  const hookLine = HOOK_EVENTS.map((e2) => {
10150
10148
  const status = result.hooks[e2];
10151
- const label = status === "ok" || status === "added" || status === "migrated" || status === "found" ? "ok" : status ?? "ok";
10149
+ const label = status === "ok" || status === "added" || status === "migrated" ? "ok" : status ?? "ok";
10152
10150
  return `${e2} ${label}`;
10153
10151
  }).join(" ");
10154
10152
  note(hookLine);
10155
10153
  }
10156
- const pathSpinner = isTTY ? L2() : null;
10157
- pathSpinner?.start("Registering PATH...");
10158
- result.pathStatus = applyPath(settings);
10159
- pathSpinner?.stop("PATH registered");
10160
- if (isTTY) {
10161
- note("env.PATH in .claude/settings.json: ");
10162
- } else {
10163
- note("PATH ok");
10154
+ if (opts.removeQmd) {
10155
+ const status = removeQmdHook(settings);
10156
+ note(status === "removed" ? "PostToolUse qmd hook removed" : "PostToolUse qmd hook not found");
10157
+ } else if (opts.qmd) {
10158
+ const status = applyQmdHook(settings);
10159
+ note(status === "added" ? "PostToolUse qmd: added" : "PostToolUse qmd: already registered");
10164
10160
  }
10165
10161
  const permSpinner = isTTY ? L2() : null;
10166
10162
  permSpinner?.start("Updating permissions...");
@@ -10194,30 +10190,30 @@ async function runRegisterHooks(opts = {}) {
10194
10190
  }
10195
10191
  return result;
10196
10192
  }
10197
- async function registerHooksCommand(vaultDir) {
10198
- const result = await runRegisterHooks(vaultDir !== undefined ? { vaultDir } : {});
10193
+ async function registerHooksCommand(vaultDir, flags) {
10194
+ const result = await runRegisterHooks({
10195
+ ...vaultDir !== undefined ? { vaultDir } : {},
10196
+ ...flags
10197
+ });
10199
10198
  if (!result.ok) {
10200
10199
  process.exit(1);
10201
10200
  }
10202
10201
  }
10203
- var HOOK_COMMANDS, HOOK_EVENTS, PERMISSIONS_TO_ADD, BUN_BIN, NPM_GLOBAL_BIN, ONEBRAIN_MARKER = "# onebrain", PATH_EXPORT = 'export PATH="$HOME/.bun/bin:$HOME/.npm-global/bin:$PATH"';
10202
+ var HOOK_COMMANDS, HOOK_EVENTS, PERMISSIONS_TO_ADD, ONEBRAIN_MARKER = "# onebrain", PATH_EXPORT = 'export PATH="$HOME/.bun/bin:$HOME/.npm-global/bin:$PATH"', QMD_CMD = "onebrain qmd-reindex", QMD_MATCHER = "Write|Edit";
10204
10203
  var init_register_hooks = __esm(() => {
10205
10204
  init_dist2();
10206
10205
  init_dist3();
10207
10206
  HOOK_COMMANDS = {
10208
10207
  Stop: "onebrain checkpoint stop",
10209
10208
  PreCompact: "onebrain checkpoint precompact",
10210
- PostCompact: "onebrain checkpoint postcompact",
10211
- SessionStart: "onebrain session-init"
10209
+ PostCompact: "onebrain checkpoint postcompact"
10212
10210
  };
10213
- HOOK_EVENTS = ["Stop", "PreCompact", "PostCompact", "SessionStart"];
10211
+ HOOK_EVENTS = ["Stop", "PreCompact", "PostCompact"];
10214
10212
  PERMISSIONS_TO_ADD = [
10215
10213
  "Bash(onebrain *)",
10216
10214
  "Bash(bun install -g @onebrain-ai/cli*)",
10217
10215
  "Bash(npm install -g @onebrain-ai/cli*)"
10218
10216
  ];
10219
- BUN_BIN = join4(homedir2(), ".bun", "bin");
10220
- NPM_GLOBAL_BIN = join4(homedir2(), ".npm-global", "bin");
10221
10217
  });
10222
10218
 
10223
10219
  // src/index.ts
@@ -10788,7 +10784,7 @@ init_dist3();
10788
10784
  import { mkdir as mkdir3, readFile as readFile3, rename as rename3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
10789
10785
  import { homedir as homedir3 } from "node:os";
10790
10786
  import { dirname as dirname3, join as join5 } from "node:path";
10791
- var binaryVersion = "2.0.8";
10787
+ var binaryVersion = "2.0.9";
10792
10788
  var STANDARD_FOLDERS = [
10793
10789
  "00-inbox",
10794
10790
  "01-projects",
@@ -11292,7 +11288,7 @@ async function listMdFiles(dir) {
11292
11288
  return [];
11293
11289
  }
11294
11290
  }
11295
- async function runBackfillRecapped(logsFolder) {
11291
+ async function runBackfillRecapped(logsFolder, cutoffDate) {
11296
11292
  const today = new Date().toISOString().slice(0, 10);
11297
11293
  let backfilled = 0;
11298
11294
  let skipped = 0;
@@ -11318,6 +11314,12 @@ async function runBackfillRecapped(logsFolder) {
11318
11314
  if (fname.includes("-checkpoint-")) {
11319
11315
  continue;
11320
11316
  }
11317
+ if (cutoffDate) {
11318
+ const dateMatch = fname.match(/^(\d{4}-\d{2}-\d{2})/);
11319
+ if (dateMatch?.[1] !== undefined && dateMatch[1] > cutoffDate) {
11320
+ continue;
11321
+ }
11322
+ }
11321
11323
  try {
11322
11324
  const content = await readFile4(fpath, "utf8");
11323
11325
  const parsed = parseFrontmatterWithRest(content);
@@ -11348,13 +11350,13 @@ ${rest}`;
11348
11350
  }
11349
11351
  return { backfilled, skipped };
11350
11352
  }
11351
- async function migrateCommand(migrationName) {
11353
+ async function migrateCommand(migrationName, cutoffDate) {
11352
11354
  try {
11353
11355
  const vaultRoot = process.cwd();
11354
11356
  const config = await loadVaultConfig(vaultRoot);
11355
11357
  const logsFolder = join7(vaultRoot, config.folders.logs);
11356
11358
  if (migrationName === "backfill-recapped") {
11357
- const result = await runBackfillRecapped(logsFolder);
11359
+ const result = await runBackfillRecapped(logsFolder, cutoffDate);
11358
11360
  process.stdout.write(`backfilled: ${result.backfilled} files, skipped: ${result.skipped}
11359
11361
  `);
11360
11362
  } else {
@@ -11512,17 +11514,14 @@ import { dirname as dirname4, join as join9 } from "node:path";
11512
11514
  var HOOK_COMMANDS2 = {
11513
11515
  Stop: "onebrain checkpoint stop",
11514
11516
  PreCompact: "onebrain checkpoint precompact",
11515
- PostCompact: "onebrain checkpoint postcompact",
11516
- SessionStart: "onebrain session-init"
11517
+ PostCompact: "onebrain checkpoint postcompact"
11517
11518
  };
11518
- var HOOK_EVENTS2 = ["Stop", "PreCompact", "PostCompact", "SessionStart"];
11519
+ var HOOK_EVENTS2 = ["Stop", "PreCompact", "PostCompact"];
11519
11520
  var PERMISSIONS_TO_ADD2 = [
11520
11521
  "Bash(onebrain *)",
11521
11522
  "Bash(bun install -g @onebrain-ai/cli*)",
11522
11523
  "Bash(npm install -g @onebrain-ai/cli*)"
11523
11524
  ];
11524
- var BUN_BIN2 = join9(homedir4(), ".bun", "bin");
11525
- var NPM_GLOBAL_BIN2 = join9(homedir4(), ".npm-global", "bin");
11526
11525
  var ONEBRAIN_MARKER2 = "# onebrain";
11527
11526
  var PATH_EXPORT2 = 'export PATH="$HOME/.bun/bin:$HOME/.npm-global/bin:$PATH"';
11528
11527
  async function readSettings2(settingsPath) {
@@ -11571,53 +11570,54 @@ function applyHooks2(settings) {
11571
11570
  result[event] = "ok";
11572
11571
  } else if (presence === "migrate") {
11573
11572
  for (const group of groups) {
11573
+ if (group.matcher === undefined)
11574
+ group.matcher = "";
11574
11575
  for (const entry of group.hooks ?? []) {
11575
11576
  if ((entry.command ?? "").includes("checkpoint-hook.sh")) {
11576
11577
  entry.command = cmd;
11578
+ if (!entry.type)
11579
+ entry.type = "command";
11577
11580
  }
11578
11581
  }
11579
11582
  }
11580
11583
  result[event] = "migrated";
11581
11584
  } else {
11582
- groups.push({ hooks: [{ command: cmd }] });
11585
+ groups.push({ matcher: "", hooks: [{ type: "command", command: cmd }] });
11583
11586
  result[event] = "added";
11584
11587
  }
11585
11588
  }
11586
11589
  return result;
11587
11590
  }
11588
- function applyPath2(settings) {
11589
- if (!settings.env)
11590
- settings.env = {};
11591
- const existing = settings.env.PATH ?? "";
11592
- const parts = existing ? existing.split(":") : [];
11593
- const bunForms = [BUN_BIN2, "$HOME/.bun/bin", "${HOME}/.bun/bin", "~/.bun/bin"];
11594
- const npmForms = [
11595
- NPM_GLOBAL_BIN2,
11596
- "$HOME/.npm-global/bin",
11597
- "${HOME}/.npm-global/bin",
11598
- "~/.npm-global/bin"
11599
- ];
11600
- const missing = [];
11601
- if (!bunForms.some((f2) => parts.includes(f2)))
11602
- missing.push(BUN_BIN2);
11603
- if (!npmForms.some((f2) => parts.includes(f2)))
11604
- missing.push(NPM_GLOBAL_BIN2);
11605
- if (missing.length === 0)
11591
+ var QMD_CMD2 = "onebrain qmd-reindex";
11592
+ var QMD_MATCHER2 = "Write|Edit";
11593
+ function applyQmdHook2(settings) {
11594
+ if (!settings.hooks)
11595
+ settings.hooks = {};
11596
+ if (!settings.hooks["PostToolUse"])
11597
+ settings.hooks["PostToolUse"] = [];
11598
+ const groups = settings.hooks["PostToolUse"];
11599
+ const already = groups.some((g) => g.hooks?.some((h2) => h2.command === QMD_CMD2));
11600
+ if (already)
11606
11601
  return "ok";
11607
- const base = existing || "${PATH}";
11608
- const hasPlaceholder = base.includes("${PATH}");
11609
- if (hasPlaceholder) {
11610
- const withoutPlaceholder = base.replace("${PATH}", "").replace(/:+$/, "").replace(/^:+/, "");
11611
- const allParts = [
11612
- ...missing,
11613
- ...withoutPlaceholder ? withoutPlaceholder.split(":").filter(Boolean) : [],
11614
- "${PATH}"
11615
- ];
11616
- settings.env.PATH = allParts.join(":");
11602
+ groups.push({ matcher: QMD_MATCHER2, hooks: [{ type: "command", command: QMD_CMD2 }] });
11603
+ return "added";
11604
+ }
11605
+ function removeQmdHook2(settings) {
11606
+ if (!settings.hooks?.["PostToolUse"])
11607
+ return "ok";
11608
+ const before = settings.hooks["PostToolUse"].length;
11609
+ const filtered = settings.hooks["PostToolUse"].filter((g) => !g.hooks?.some((h2) => (h2.command ?? "").includes("qmd-reindex")));
11610
+ if (filtered.length === 0) {
11611
+ const remaining = {};
11612
+ for (const [key, val] of Object.entries(settings.hooks)) {
11613
+ if (key !== "PostToolUse")
11614
+ remaining[key] = val;
11615
+ }
11616
+ settings.hooks = remaining;
11617
11617
  } else {
11618
- settings.env.PATH = [...missing, base].join(":");
11618
+ settings.hooks["PostToolUse"] = filtered;
11619
11619
  }
11620
- return "updated";
11620
+ return before !== filtered.length ? "removed" : "ok";
11621
11621
  }
11622
11622
  function applyPermissions2(settings) {
11623
11623
  if (!settings.permissions)
@@ -11687,7 +11687,6 @@ async function runRegisterHooks2(opts = {}) {
11687
11687
  const result = {
11688
11688
  ok: false,
11689
11689
  hooks: {},
11690
- pathStatus: "ok",
11691
11690
  permissionsAdded: []
11692
11691
  };
11693
11692
  const settingsPath = join9(vaultRoot, ".claude", "settings.json");
@@ -11717,19 +11716,17 @@ async function runRegisterHooks2(opts = {}) {
11717
11716
  } else {
11718
11717
  const hookLine = HOOK_EVENTS2.map((e2) => {
11719
11718
  const status = result.hooks[e2];
11720
- const label = status === "ok" || status === "added" || status === "migrated" || status === "found" ? "ok" : status ?? "ok";
11719
+ const label = status === "ok" || status === "added" || status === "migrated" ? "ok" : status ?? "ok";
11721
11720
  return `${e2} ${label}`;
11722
11721
  }).join(" ");
11723
11722
  note(hookLine);
11724
11723
  }
11725
- const pathSpinner = isTTY ? L2() : null;
11726
- pathSpinner?.start("Registering PATH...");
11727
- result.pathStatus = applyPath2(settings);
11728
- pathSpinner?.stop("PATH registered");
11729
- if (isTTY) {
11730
- note("env.PATH in .claude/settings.json: ");
11731
- } else {
11732
- note("PATH ok");
11724
+ if (opts.removeQmd) {
11725
+ const status = removeQmdHook2(settings);
11726
+ note(status === "removed" ? "PostToolUse qmd hook removed" : "PostToolUse qmd hook not found");
11727
+ } else if (opts.qmd) {
11728
+ const status = applyQmdHook2(settings);
11729
+ note(status === "added" ? "PostToolUse qmd: added" : "PostToolUse qmd: already registered");
11733
11730
  }
11734
11731
  const permSpinner = isTTY ? L2() : null;
11735
11732
  permSpinner?.start("Updating permissions...");
@@ -11763,8 +11760,11 @@ async function runRegisterHooks2(opts = {}) {
11763
11760
  }
11764
11761
  return result;
11765
11762
  }
11766
- async function registerHooksCommand2(vaultDir) {
11767
- const result = await runRegisterHooks2(vaultDir !== undefined ? { vaultDir } : {});
11763
+ async function registerHooksCommand2(vaultDir, flags) {
11764
+ const result = await runRegisterHooks2({
11765
+ ...vaultDir !== undefined ? { vaultDir } : {},
11766
+ ...flags
11767
+ });
11768
11768
  if (!result.ok) {
11769
11769
  process.exit(1);
11770
11770
  }
@@ -12041,7 +12041,7 @@ async function syncPluginFiles2(extractedDir, vaultRoot, unlinkFn = unlink3) {
12041
12041
  return { filesAdded, filesRemoved };
12042
12042
  }
12043
12043
  async function copyRootDocs2(extractedDir, vaultRoot) {
12044
- const docs = ["README.md", "CONTRIBUTING.md", "CHANGELOG.md"];
12044
+ const docs = ["README.md", "CONTRIBUTING.md", "CHANGELOG.md", "PLUGIN-CHANGELOG.md"];
12045
12045
  for (const doc of docs) {
12046
12046
  const srcPath = join11(extractedDir, doc);
12047
12047
  const destPath = join11(vaultRoot, doc);
@@ -12666,7 +12666,7 @@ async function updateCommand(opts = {}) {
12666
12666
  }
12667
12667
 
12668
12668
  // src/index.ts
12669
- var VERSION = "2.0.8";
12669
+ var VERSION = "2.0.9";
12670
12670
  var RELEASE_DATE = "2026-04-26";
12671
12671
  process.stdout.setDefaultEncoding("utf8");
12672
12672
  process.stderr.setDefaultEncoding("utf8");
@@ -12732,10 +12732,13 @@ program2.command("vault-sync", { hidden: true }).description("Sync plugin files
12732
12732
  const root = vaultRoot ?? process.cwd();
12733
12733
  await vaultSyncCommand2(root, opts.branch !== undefined ? { branch: opts.branch } : {});
12734
12734
  });
12735
- 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) => {
12736
- await registerHooksCommand2(opts.vaultDir);
12735
+ program2.command("register-hooks", { hidden: true }).description("Install Claude Code hooks into settings.json").option("--vault-dir <path>", "vault root directory (default: cwd)").option("--qmd", "also register PostToolUse qmd-reindex hook").option("--remove-qmd", "remove PostToolUse qmd-reindex hook").action(async (opts) => {
12736
+ await registerHooksCommand2(opts.vaultDir, {
12737
+ ...opts.qmd !== undefined ? { qmd: opts.qmd } : {},
12738
+ ...opts.removeQmd !== undefined ? { removeQmd: opts.removeQmd } : {}
12739
+ });
12737
12740
  });
12738
- program2.command("migrate", { hidden: true }).description("Run one-time migration scripts").argument("<name>", "migration name: backfill-recapped").action(async (name) => {
12739
- await migrateCommand(name);
12741
+ 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) => {
12742
+ await migrateCommand(name, cutoffDate);
12740
12743
  });
12741
12744
  program2.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.0.8",
3
+ "version": "2.0.9",
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",