@onebrain-ai/cli 2.2.0 → 2.2.1

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 +110 -21
  2. package/package.json +1 -1
package/dist/onebrain CHANGED
@@ -8909,7 +8909,7 @@ async function loadVaultConfig(vaultRoot) {
8909
8909
  const file = Bun.file(vaultYmlPath);
8910
8910
  const exists = await file.exists();
8911
8911
  if (!exists) {
8912
- throw new Error(`vault.yml not found at ${vaultYmlPath}. Run onebrain init to set up this vault.`);
8912
+ throw new Error(`${VAULT_YML_NOT_FOUND_PREFIX}${vaultYmlPath}. Run onebrain init to set up this vault.`);
8913
8913
  }
8914
8914
  const text = await file.text();
8915
8915
  const parsed = import_yaml.parse(text) ?? {};
@@ -8952,7 +8952,7 @@ async function loadVaultConfig(vaultRoot) {
8952
8952
  }
8953
8953
  return config;
8954
8954
  }
8955
- var import_yaml, DEFAULT_FOLDERS, DEFAULT_CHECKPOINT;
8955
+ var import_yaml, DEFAULT_FOLDERS, DEFAULT_CHECKPOINT, VAULT_YML_NOT_FOUND_PREFIX = "vault.yml not found at ";
8956
8956
  var init_parser = __esm(() => {
8957
8957
  import_yaml = __toESM(require_dist(), 1);
8958
8958
  DEFAULT_FOLDERS = {
@@ -9545,7 +9545,9 @@ __export(exports_lib, {
9545
9545
  checkOrphanCheckpoints: () => checkOrphanCheckpoints,
9546
9546
  checkFolders: () => checkFolders,
9547
9547
  checkClaudeSettings: () => checkClaudeSettings,
9548
- atomicWrite: () => atomicWrite
9548
+ atomicWrite: () => atomicWrite,
9549
+ VAULT_YML_NOT_FOUND_PREFIX: () => VAULT_YML_NOT_FOUND_PREFIX,
9550
+ DEFAULT_CHECKPOINT: () => DEFAULT_CHECKPOINT
9549
9551
  });
9550
9552
  var init_lib = __esm(() => {
9551
9553
  init_parser();
@@ -9558,7 +9560,7 @@ var init_lib = __esm(() => {
9558
9560
  var require_package = __commonJS((exports, module) => {
9559
9561
  module.exports = {
9560
9562
  name: "@onebrain-ai/cli",
9561
- version: "2.2.0",
9563
+ version: "2.2.1",
9562
9564
  description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9563
9565
  keywords: [
9564
9566
  "onebrain",
@@ -10989,7 +10991,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
10989
10991
  var import_picocolors = __toESM(require_picocolors(), 1);
10990
10992
  function resolveBinaryVersion() {
10991
10993
  if (true)
10992
- return "2.2.0";
10994
+ return "2.2.1";
10993
10995
  try {
10994
10996
  const pkg = require_package();
10995
10997
  return pkg.version ?? "dev";
@@ -11481,7 +11483,8 @@ async function runDoctor(opts = {}) {
11481
11483
  agent: "05-agent",
11482
11484
  archive: "06-archive",
11483
11485
  logs: "07-logs"
11484
- }
11486
+ },
11487
+ checkpoint: { ...DEFAULT_CHECKPOINT }
11485
11488
  };
11486
11489
  const sp1 = createStep("\uD83D\uDCCB", "vault.yml");
11487
11490
  const vaultYmlResult = await checkVaultYmlFn(vaultDir);
@@ -12603,9 +12606,12 @@ async function migrateCommand(migrationName, cutoffDate, vaultDir) {
12603
12606
  }
12604
12607
 
12605
12608
  // src/commands/internal/orphan-scan.ts
12609
+ init_parser();
12606
12610
  var import_yaml6 = __toESM(require_dist(), 1);
12607
- import { readFile as readFile5, readdir as readdir4 } from "fs/promises";
12611
+ import { readFile as readFile5, readdir as readdir4, stat as stat6 } from "fs/promises";
12608
12612
  import { join as join9 } from "path";
12613
+ var MIN_GUARD_MINUTES = 60;
12614
+ var DEFAULT_ACTIVE_SESSION_GUARD_MS = 60 * 60 * 1000;
12609
12615
  function parseFrontmatter(rawText) {
12610
12616
  const text = rawText.replace(/\r\n/g, `
12611
12617
  `);
@@ -12639,6 +12645,61 @@ async function listMdFiles2(dir) {
12639
12645
  return [];
12640
12646
  }
12641
12647
  }
12648
+ async function getActiveSessionGuardMs(vaultRoot) {
12649
+ try {
12650
+ const config = await loadVaultConfig(vaultRoot);
12651
+ const cpMinutes = config.checkpoint.minutes;
12652
+ if (typeof cpMinutes !== "number" || !Number.isFinite(cpMinutes) || cpMinutes <= 0) {
12653
+ return DEFAULT_ACTIVE_SESSION_GUARD_MS;
12654
+ }
12655
+ const minutes = Math.max(MIN_GUARD_MINUTES, 2 * cpMinutes);
12656
+ return minutes * 60 * 1000;
12657
+ } catch (err) {
12658
+ const msg = err instanceof Error ? err.message : String(err);
12659
+ const isExpectedAbsence = msg.startsWith(VAULT_YML_NOT_FOUND_PREFIX);
12660
+ if (!isExpectedAbsence) {
12661
+ try {
12662
+ process.stderr.write(`onebrain orphan-scan: vault.yml unreadable, using ${MIN_GUARD_MINUTES}-min Active-Session Guard default (${msg})
12663
+ `);
12664
+ } catch {}
12665
+ }
12666
+ return DEFAULT_ACTIVE_SESSION_GUARD_MS;
12667
+ }
12668
+ }
12669
+ async function getMtimeMs(path) {
12670
+ try {
12671
+ const s = await stat6(path);
12672
+ if (typeof s.mtimeMs !== "number" || !Number.isFinite(s.mtimeMs))
12673
+ return null;
12674
+ return s.mtimeMs;
12675
+ } catch {
12676
+ return null;
12677
+ }
12678
+ }
12679
+ async function getNewestMtimeMs(filePaths) {
12680
+ if (filePaths.length === 0)
12681
+ return null;
12682
+ let newest = Number.NEGATIVE_INFINITY;
12683
+ for (const p2 of filePaths) {
12684
+ const m3 = await getMtimeMs(p2);
12685
+ if (m3 === null)
12686
+ return null;
12687
+ if (m3 > newest)
12688
+ newest = m3;
12689
+ }
12690
+ return Number.isFinite(newest) ? newest : null;
12691
+ }
12692
+ async function isGroupActiveOrAmbiguous(filePaths, nowMs, guardMs) {
12693
+ if (!Number.isFinite(guardMs) || guardMs <= 0)
12694
+ return true;
12695
+ const newest = await getNewestMtimeMs(filePaths);
12696
+ if (newest === null)
12697
+ return true;
12698
+ const ageMs = nowMs - newest;
12699
+ if (ageMs < 0)
12700
+ return true;
12701
+ return ageMs < guardMs;
12702
+ }
12642
12703
  async function hasManualSessionLog(monthDir, date) {
12643
12704
  const files = await listMdFiles2(monthDir);
12644
12705
  const sessionLogs = files.filter((f2) => f2.startsWith(date) && f2.includes("-session-") && f2.endsWith(".md"));
@@ -12653,10 +12714,19 @@ async function hasManualSessionLog(monthDir, date) {
12653
12714
  }
12654
12715
  return false;
12655
12716
  }
12656
- async function scanMonthDir(monthDir, currentToken, today, seenTokens) {
12717
+ async function collectCandidateGroupsForMonth(monthDir, currentToken, today) {
12718
+ const groups = new Map;
12657
12719
  const files = await listMdFiles2(monthDir);
12658
12720
  const checkpoints = files.filter((f2) => f2.includes("-checkpoint-") && f2.endsWith(".md"));
12659
- let count = 0;
12721
+ const manualLogCache = new Map;
12722
+ async function dateHasManualLog(date) {
12723
+ const cached = manualLogCache.get(date);
12724
+ if (cached !== undefined)
12725
+ return cached;
12726
+ const result = await hasManualSessionLog(monthDir, date);
12727
+ manualLogCache.set(date, result);
12728
+ return result;
12729
+ }
12660
12730
  for (const fname of checkpoints) {
12661
12731
  const dateMatch = fname.match(/^(\d{4}-\d{2}-\d{2})-/);
12662
12732
  if (!dateMatch)
@@ -12673,32 +12743,51 @@ async function scanMonthDir(monthDir, currentToken, today, seenTokens) {
12673
12743
  continue;
12674
12744
  if (ftoken === currentToken)
12675
12745
  continue;
12676
- if (seenTokens.has(ftoken))
12746
+ if (await dateHasManualLog(fdate))
12677
12747
  continue;
12678
- if (await hasManualSessionLog(monthDir, fdate))
12679
- continue;
12680
- seenTokens.add(ftoken);
12681
- count++;
12748
+ const fpath = join9(monthDir, fname);
12749
+ const existing = groups.get(ftoken);
12750
+ if (existing)
12751
+ existing.push(fpath);
12752
+ else
12753
+ groups.set(ftoken, [fpath]);
12682
12754
  }
12683
- return count;
12755
+ return groups;
12684
12756
  }
12685
- async function runOrphanScan(logsFolder, sessionToken, now) {
12757
+ async function runOrphanScan(logsFolder, sessionToken, now, vaultRoot) {
12758
+ if (!vaultRoot) {
12759
+ throw new Error("runOrphanScan: vaultRoot is required and must be a non-empty path");
12760
+ }
12686
12761
  const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
12687
12762
  const { thisYear, thisMonth, prevYear, prevMonth } = getMonthParts(now);
12688
12763
  const monthDirs = [
12689
12764
  { year: thisYear, month: thisMonth },
12690
12765
  { year: prevYear, month: prevMonth }
12691
12766
  ];
12692
- const seenTokens = new Set;
12693
- let totalOrphans = 0;
12767
+ const allGroups = new Map;
12694
12768
  for (const { year, month } of monthDirs) {
12695
12769
  const monthDir = join9(logsFolder, year, month);
12696
- totalOrphans += await scanMonthDir(monthDir, sessionToken, today, seenTokens);
12770
+ const monthGroups = await collectCandidateGroupsForMonth(monthDir, sessionToken, today);
12771
+ for (const [token, files] of monthGroups) {
12772
+ const existing = allGroups.get(token);
12773
+ if (existing)
12774
+ existing.push(...files);
12775
+ else
12776
+ allGroups.set(token, [...files]);
12777
+ }
12778
+ }
12779
+ const guardMs = await getActiveSessionGuardMs(vaultRoot);
12780
+ const nowMs = now.getTime();
12781
+ let totalOrphans = 0;
12782
+ for (const [, files] of allGroups) {
12783
+ if (await isGroupActiveOrAmbiguous(files, nowMs, guardMs))
12784
+ continue;
12785
+ totalOrphans++;
12697
12786
  }
12698
12787
  return { orphan_count: totalOrphans };
12699
12788
  }
12700
12789
  async function orphanScanCommand(logsFolder, sessionToken) {
12701
- const result = await runOrphanScan(logsFolder, sessionToken, new Date);
12790
+ const result = await runOrphanScan(logsFolder, sessionToken, new Date, process.cwd());
12702
12791
  process.stdout.write(`${JSON.stringify(result)}
12703
12792
  `);
12704
12793
  }
@@ -13220,7 +13309,7 @@ function patchUtf8(stream) {
13220
13309
  }
13221
13310
 
13222
13311
  // src/index.ts
13223
- var VERSION = "2.2.0";
13312
+ var VERSION = "2.2.1";
13224
13313
  var RELEASE_DATE = "2026-05-07";
13225
13314
  patchUtf8(process.stdout);
13226
13315
  patchUtf8(process.stderr);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebrain-ai/cli",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
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",