@onebrain-ai/cli 2.2.0 → 2.2.2
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.
- package/README.md +2 -2
- package/dist/onebrain +111 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Obsidian becomes your dispatch hub for everything you do:
|
|
|
103
103
|
<p align="center">
|
|
104
104
|
<picture>
|
|
105
105
|
<source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/vault-hub-dark.svg">
|
|
106
|
-
<img alt="Obsidian as command center — eight spokes radiate from the vault to CLI/repo, website, cloud infra, social media, office docs, project notes, research, and MCP server" src="assets/diagrams/vault-hub-light.svg" width="
|
|
106
|
+
<img alt="Obsidian as command center — eight spokes radiate from the vault to CLI/repo, website, cloud infra, social media, office docs, project notes, research, and MCP server" src="assets/diagrams/vault-hub-light.svg" width="460">
|
|
107
107
|
</picture>
|
|
108
108
|
</p>
|
|
109
109
|
|
|
@@ -118,7 +118,7 @@ OneBrain runs as a tight 3-step loop. Each cycle, both sides sharpen.
|
|
|
118
118
|
<p align="center">
|
|
119
119
|
<picture>
|
|
120
120
|
<source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/coevo-loop-dark.svg">
|
|
121
|
-
<img alt="Co-Evolution loop — three nodes (01 CAPTURE at top, 02 EVOLVE at bottom-right, 03 WRAPUP at bottom-left) connected by curved arrows flowing clockwise" src="assets/diagrams/coevo-loop-light.svg" width="
|
|
121
|
+
<img alt="Co-Evolution loop — three nodes (01 CAPTURE at top, 02 EVOLVE at bottom-right, 03 WRAPUP at bottom-left) connected by curved arrows flowing clockwise" src="assets/diagrams/coevo-loop-light.svg" width="350">
|
|
122
122
|
</picture>
|
|
123
123
|
</p>
|
|
124
124
|
|
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(
|
|
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.
|
|
9563
|
+
version: "2.2.2",
|
|
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.
|
|
10994
|
+
return "2.2.2";
|
|
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);
|
|
@@ -12408,9 +12411,7 @@ function loadVaultSettings(vaultRoot) {
|
|
|
12408
12411
|
}
|
|
12409
12412
|
}
|
|
12410
12413
|
function maxCheckpointNnSync(vaultRoot, date, token, logsFolder) {
|
|
12411
|
-
const
|
|
12412
|
-
const mm = date.slice(5, 7);
|
|
12413
|
-
const dir = join7(vaultRoot, logsFolder, yyyy, mm);
|
|
12414
|
+
const dir = join7(vaultRoot, logsFolder, "checkpoint");
|
|
12414
12415
|
const prefix = `${date}-${token}-checkpoint-`;
|
|
12415
12416
|
try {
|
|
12416
12417
|
let max = 0;
|
|
@@ -12525,14 +12526,15 @@ async function runBackfillRecapped(logsFolder, cutoffDate) {
|
|
|
12525
12526
|
const today = new Date().toISOString().slice(0, 10);
|
|
12526
12527
|
let backfilled = 0;
|
|
12527
12528
|
let skipped = 0;
|
|
12529
|
+
const sessionRoot = join8(logsFolder, "session");
|
|
12528
12530
|
let yearDirs = [];
|
|
12529
12531
|
try {
|
|
12530
|
-
yearDirs = await readdir3(
|
|
12532
|
+
yearDirs = await readdir3(sessionRoot);
|
|
12531
12533
|
} catch {
|
|
12532
12534
|
return { backfilled: 0, skipped: 0 };
|
|
12533
12535
|
}
|
|
12534
12536
|
for (const yearDir of yearDirs) {
|
|
12535
|
-
const yearPath = join8(
|
|
12537
|
+
const yearPath = join8(sessionRoot, yearDir);
|
|
12536
12538
|
let monthDirs = [];
|
|
12537
12539
|
try {
|
|
12538
12540
|
monthDirs = await readdir3(yearPath);
|
|
@@ -12603,9 +12605,12 @@ async function migrateCommand(migrationName, cutoffDate, vaultDir) {
|
|
|
12603
12605
|
}
|
|
12604
12606
|
|
|
12605
12607
|
// src/commands/internal/orphan-scan.ts
|
|
12608
|
+
init_parser();
|
|
12606
12609
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
12607
|
-
import { readFile as readFile5, readdir as readdir4 } from "fs/promises";
|
|
12610
|
+
import { readFile as readFile5, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
12608
12611
|
import { join as join9 } from "path";
|
|
12612
|
+
var MIN_GUARD_MINUTES = 60;
|
|
12613
|
+
var DEFAULT_ACTIVE_SESSION_GUARD_MS = 60 * 60 * 1000;
|
|
12609
12614
|
function parseFrontmatter(rawText) {
|
|
12610
12615
|
const text = rawText.replace(/\r\n/g, `
|
|
12611
12616
|
`);
|
|
@@ -12623,14 +12628,6 @@ function parseFrontmatter(rawText) {
|
|
|
12623
12628
|
return null;
|
|
12624
12629
|
}
|
|
12625
12630
|
}
|
|
12626
|
-
function getMonthParts(now = new Date) {
|
|
12627
|
-
const thisYear = String(now.getFullYear());
|
|
12628
|
-
const thisMonth = String(now.getMonth() + 1).padStart(2, "0");
|
|
12629
|
-
const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
12630
|
-
const prevYear = String(prevDate.getFullYear());
|
|
12631
|
-
const prevMonth = String(prevDate.getMonth() + 1).padStart(2, "0");
|
|
12632
|
-
return { thisYear, thisMonth, prevYear, prevMonth };
|
|
12633
|
-
}
|
|
12634
12631
|
async function listMdFiles2(dir) {
|
|
12635
12632
|
try {
|
|
12636
12633
|
const entries = await readdir4(dir);
|
|
@@ -12639,6 +12636,61 @@ async function listMdFiles2(dir) {
|
|
|
12639
12636
|
return [];
|
|
12640
12637
|
}
|
|
12641
12638
|
}
|
|
12639
|
+
async function getActiveSessionGuardMs(vaultRoot) {
|
|
12640
|
+
try {
|
|
12641
|
+
const config = await loadVaultConfig(vaultRoot);
|
|
12642
|
+
const cpMinutes = config.checkpoint.minutes;
|
|
12643
|
+
if (typeof cpMinutes !== "number" || !Number.isFinite(cpMinutes) || cpMinutes <= 0) {
|
|
12644
|
+
return DEFAULT_ACTIVE_SESSION_GUARD_MS;
|
|
12645
|
+
}
|
|
12646
|
+
const minutes = Math.max(MIN_GUARD_MINUTES, 2 * cpMinutes);
|
|
12647
|
+
return minutes * 60 * 1000;
|
|
12648
|
+
} catch (err) {
|
|
12649
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12650
|
+
const isExpectedAbsence = msg.startsWith(VAULT_YML_NOT_FOUND_PREFIX);
|
|
12651
|
+
if (!isExpectedAbsence) {
|
|
12652
|
+
try {
|
|
12653
|
+
process.stderr.write(`onebrain orphan-scan: vault.yml unreadable, using ${MIN_GUARD_MINUTES}-min Active-Session Guard default (${msg})
|
|
12654
|
+
`);
|
|
12655
|
+
} catch {}
|
|
12656
|
+
}
|
|
12657
|
+
return DEFAULT_ACTIVE_SESSION_GUARD_MS;
|
|
12658
|
+
}
|
|
12659
|
+
}
|
|
12660
|
+
async function getMtimeMs(path) {
|
|
12661
|
+
try {
|
|
12662
|
+
const s = await stat6(path);
|
|
12663
|
+
if (typeof s.mtimeMs !== "number" || !Number.isFinite(s.mtimeMs))
|
|
12664
|
+
return null;
|
|
12665
|
+
return s.mtimeMs;
|
|
12666
|
+
} catch {
|
|
12667
|
+
return null;
|
|
12668
|
+
}
|
|
12669
|
+
}
|
|
12670
|
+
async function getNewestMtimeMs(filePaths) {
|
|
12671
|
+
if (filePaths.length === 0)
|
|
12672
|
+
return null;
|
|
12673
|
+
let newest = Number.NEGATIVE_INFINITY;
|
|
12674
|
+
for (const p2 of filePaths) {
|
|
12675
|
+
const m3 = await getMtimeMs(p2);
|
|
12676
|
+
if (m3 === null)
|
|
12677
|
+
return null;
|
|
12678
|
+
if (m3 > newest)
|
|
12679
|
+
newest = m3;
|
|
12680
|
+
}
|
|
12681
|
+
return Number.isFinite(newest) ? newest : null;
|
|
12682
|
+
}
|
|
12683
|
+
async function isGroupActiveOrAmbiguous(filePaths, nowMs, guardMs) {
|
|
12684
|
+
if (!Number.isFinite(guardMs) || guardMs <= 0)
|
|
12685
|
+
return true;
|
|
12686
|
+
const newest = await getNewestMtimeMs(filePaths);
|
|
12687
|
+
if (newest === null)
|
|
12688
|
+
return true;
|
|
12689
|
+
const ageMs = nowMs - newest;
|
|
12690
|
+
if (ageMs < 0)
|
|
12691
|
+
return true;
|
|
12692
|
+
return ageMs < guardMs;
|
|
12693
|
+
}
|
|
12642
12694
|
async function hasManualSessionLog(monthDir, date) {
|
|
12643
12695
|
const files = await listMdFiles2(monthDir);
|
|
12644
12696
|
const sessionLogs = files.filter((f2) => f2.startsWith(date) && f2.includes("-session-") && f2.endsWith(".md"));
|
|
@@ -12653,10 +12705,22 @@ async function hasManualSessionLog(monthDir, date) {
|
|
|
12653
12705
|
}
|
|
12654
12706
|
return false;
|
|
12655
12707
|
}
|
|
12656
|
-
async function
|
|
12657
|
-
const
|
|
12708
|
+
async function collectCandidateGroups(checkpointDir, sessionDir, currentToken, today) {
|
|
12709
|
+
const groups = new Map;
|
|
12710
|
+
const files = await listMdFiles2(checkpointDir);
|
|
12658
12711
|
const checkpoints = files.filter((f2) => f2.includes("-checkpoint-") && f2.endsWith(".md"));
|
|
12659
|
-
|
|
12712
|
+
const manualLogCache = new Map;
|
|
12713
|
+
async function dateHasManualLog(date) {
|
|
12714
|
+
const cached = manualLogCache.get(date);
|
|
12715
|
+
if (cached !== undefined)
|
|
12716
|
+
return cached;
|
|
12717
|
+
const year = date.slice(0, 4);
|
|
12718
|
+
const month = date.slice(5, 7);
|
|
12719
|
+
const sessionMonthDir = join9(sessionDir, year, month);
|
|
12720
|
+
const result = await hasManualSessionLog(sessionMonthDir, date);
|
|
12721
|
+
manualLogCache.set(date, result);
|
|
12722
|
+
return result;
|
|
12723
|
+
}
|
|
12660
12724
|
for (const fname of checkpoints) {
|
|
12661
12725
|
const dateMatch = fname.match(/^(\d{4}-\d{2}-\d{2})-/);
|
|
12662
12726
|
if (!dateMatch)
|
|
@@ -12673,32 +12737,37 @@ async function scanMonthDir(monthDir, currentToken, today, seenTokens) {
|
|
|
12673
12737
|
continue;
|
|
12674
12738
|
if (ftoken === currentToken)
|
|
12675
12739
|
continue;
|
|
12676
|
-
if (
|
|
12677
|
-
continue;
|
|
12678
|
-
if (await hasManualSessionLog(monthDir, fdate))
|
|
12740
|
+
if (await dateHasManualLog(fdate))
|
|
12679
12741
|
continue;
|
|
12680
|
-
|
|
12681
|
-
|
|
12742
|
+
const fpath = join9(checkpointDir, fname);
|
|
12743
|
+
const existing = groups.get(ftoken);
|
|
12744
|
+
if (existing)
|
|
12745
|
+
existing.push(fpath);
|
|
12746
|
+
else
|
|
12747
|
+
groups.set(ftoken, [fpath]);
|
|
12682
12748
|
}
|
|
12683
|
-
return
|
|
12749
|
+
return groups;
|
|
12684
12750
|
}
|
|
12685
|
-
async function runOrphanScan(logsFolder, sessionToken, now) {
|
|
12751
|
+
async function runOrphanScan(logsFolder, sessionToken, now, vaultRoot) {
|
|
12752
|
+
if (!vaultRoot) {
|
|
12753
|
+
throw new Error("runOrphanScan: vaultRoot is required and must be a non-empty path");
|
|
12754
|
+
}
|
|
12686
12755
|
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
12687
|
-
const
|
|
12688
|
-
const
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
const seenTokens = new Set;
|
|
12756
|
+
const checkpointDir = join9(logsFolder, "checkpoint");
|
|
12757
|
+
const sessionDir = join9(logsFolder, "session");
|
|
12758
|
+
const allGroups = await collectCandidateGroups(checkpointDir, sessionDir, sessionToken, today);
|
|
12759
|
+
const guardMs = await getActiveSessionGuardMs(vaultRoot);
|
|
12760
|
+
const nowMs = now.getTime();
|
|
12693
12761
|
let totalOrphans = 0;
|
|
12694
|
-
for (const
|
|
12695
|
-
|
|
12696
|
-
|
|
12762
|
+
for (const [, files] of allGroups) {
|
|
12763
|
+
if (await isGroupActiveOrAmbiguous(files, nowMs, guardMs))
|
|
12764
|
+
continue;
|
|
12765
|
+
totalOrphans++;
|
|
12697
12766
|
}
|
|
12698
12767
|
return { orphan_count: totalOrphans };
|
|
12699
12768
|
}
|
|
12700
12769
|
async function orphanScanCommand(logsFolder, sessionToken) {
|
|
12701
|
-
const result = await runOrphanScan(logsFolder, sessionToken, new Date);
|
|
12770
|
+
const result = await runOrphanScan(logsFolder, sessionToken, new Date, process.cwd());
|
|
12702
12771
|
process.stdout.write(`${JSON.stringify(result)}
|
|
12703
12772
|
`);
|
|
12704
12773
|
}
|
|
@@ -13220,8 +13289,8 @@ function patchUtf8(stream) {
|
|
|
13220
13289
|
}
|
|
13221
13290
|
|
|
13222
13291
|
// src/index.ts
|
|
13223
|
-
var VERSION = "2.2.
|
|
13224
|
-
var RELEASE_DATE = "2026-05-
|
|
13292
|
+
var VERSION = "2.2.2";
|
|
13293
|
+
var RELEASE_DATE = "2026-05-10";
|
|
13225
13294
|
patchUtf8(process.stdout);
|
|
13226
13295
|
patchUtf8(process.stderr);
|
|
13227
13296
|
var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
|