@onebrain-ai/cli 2.1.7 → 2.1.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 (3) hide show
  1. package/README.md +118 -18
  2. package/dist/onebrain +149 -94
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
1
  <p align="center">
2
- <img src="assets/banner.png" alt="OneBrain — Your personal AI OS" width="100%" />
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="assets/header-dark.png">
4
+ <img alt="OneBrain — Your AI Thinking Partner" src="assets/header-light.png" width="640">
5
+ </picture>
3
6
  </p>
4
7
 
5
8
  <p align="center">
6
- <a href="https://github.com/onebrain-ai/onebrain/releases"><img src="https://img.shields.io/badge/dynamic/json?url=https://raw.githubusercontent.com/onebrain-ai/onebrain/main/.claude/plugins/onebrain/.claude-plugin/plugin.json&query=%24.version&label=version&style=flat-square&color=blue" alt="Version" /></a>
7
- <a href="LICENSE"><img src="https://img.shields.io/github/license/onebrain-ai/onebrain?style=flat-square" alt="License" /></a>
8
- <a href="https://github.com/onebrain-ai/onebrain/stargazers"><img src="https://img.shields.io/github/stars/onebrain-ai/onebrain?style=flat-square" alt="GitHub Stars" /></a>
9
- <a href="https://github.com/onebrain-ai/onebrain/commits/main"><img src="https://img.shields.io/github/last-commit/onebrain-ai/onebrain?style=flat-square" alt="Last Commit" /></a>
9
+ <a href="https://onebrain.run"><img alt="Website" src="https://img.shields.io/badge/onebrain.run-0a0a14?style=for-the-badge&labelColor=ff2d92"></a>
10
+ <a href="https://x.com/onebrain_run"><img alt="@onebrain_run on X" src="https://img.shields.io/badge/follow-@onebrain__run-000000?style=for-the-badge&logo=x&logoColor=white"></a>
11
+ <a href="https://www.npmjs.com/package/@onebrain-ai/cli"><img alt="npm" src="https://img.shields.io/npm/v/@onebrain-ai/cli?style=for-the-badge&logo=npm&color=cb3837&label=%40onebrain-ai%2Fcli"></a>
12
+ <a href="https://github.com/onebrain-ai/onebrain/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/onebrain-ai/onebrain?style=for-the-badge&color=00f3ff&logo=github"></a>
13
+ <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-7c3aed?style=for-the-badge"></a>
10
14
  </p>
11
15
 
12
- <h1 align="center">OneBrain</h1>
13
-
14
16
  <p align="center">
15
17
  <em>Your AI forgets everything when the session ends.<br>
16
18
  Your notes, your AI, and your tools live in separate silos.<br>
@@ -34,11 +36,81 @@ OneBrain is an AI operating system layer built on top of Obsidian. It gives your
34
36
 
35
37
  Unlike chat-based AI tools, OneBrain lives in plain Markdown files you own forever. No cloud sync required. No proprietary format. Just your agent, your vault, your data.
36
38
 
37
- **Works with:** Claude Code · Gemini CLI · any agent that reads Markdown
39
+ > Most tools ask you to query an AI. OneBrain **co-evolves** with you every preference you teach sharpens the agent, every link it surfaces sharpens you.
40
+
41
+ **The bidirectional flow:**
42
+
43
+ - **Human → Agent** — Every preference, decision, and correction becomes persistent memory. The agent calibrates to you with every interaction.
44
+ - **Agent → Human** — Captures, classifies, links, and synthesizes the noise of your day — so your attention stays on what only you can do.
45
+
46
+ **Harness-agnostic** — Claude Code · Gemini CLI · OpenAI Codex · Qwen · or BYO LLM via API key. [See the architecture ↓](#the-harness-os-architecture)
47
+
48
+ ---
49
+
50
+ ## The Harness OS Architecture
51
+
52
+ OneBrain doesn't compete with Claude Code, Gemini CLI, or any other AI harness. It sits **underneath them** — the OS layer that keeps your context, memory, and skills consistent no matter which harness you're driving.
53
+
54
+ <p align="center">
55
+ <img alt="OneBrain Harness OS — 5-layer architecture: Obsidian Vault, OneBrain Plugin, OneBrain CLI, Harness, LLM" src="assets/diagrams/harness-os-stack.svg" width="780">
56
+ </p>
57
+
58
+ | # | Layer | Role | What lives here |
59
+ |---|---|---|---|
60
+ | 01 | **Obsidian Vault** | Cognitive interface | Plain Markdown — notes, memory, decisions, knowledge graph |
61
+ | 02 | **OneBrain Plugin** | Skills + hooks | 24+ skills + lifecycle hooks, loaded into any harness |
62
+ | 03 | **OneBrain CLI** | Harness orchestrator | Indexing, checkpoints, vault sync, harness routing |
63
+ | 04 | **Harness** | Agentic runtime | Bring your own — Claude Code · Gemini CLI · Codex · Qwen · ... |
64
+ | 05 | **LLM** | Intelligence source | Local (mlx, ollama) · cloud (claude, gemini, gpt) · raw API |
65
+
66
+ The **Harness** layer is where most AI tools pick a fight with each other. We don't — pick whichever harness you love. By familiarity, by task, or by cost. Your vault stays the same.
67
+
68
+ ### Pick Your Harness
69
+
70
+ Each harness reads OneBrain's instruction file automatically. Install it, run it inside your vault, and the plugin loads on first prompt.
71
+
72
+ | Harness | Install | Run | Reads |
73
+ |---|---|---|---|
74
+ | **Claude Code** *(recommended)* | `npm install -g @anthropic-ai/claude-code` | `claude` | `CLAUDE.md` |
75
+ | **Gemini CLI** | `npm install -g @google/gemini-cli` | `gemini` | `GEMINI.md` |
76
+ | **OpenAI Codex** | `npm install -g @openai/codex` | `codex` | `AGENTS.md` |
77
+ | **Qwen Code** | `npm install -g @qwen-code/qwen-code` | `qwen` | `AGENTS.md` |
78
+
79
+ > Auto-checkpoint and the Stop hook are wired up for Claude Code today. The other harnesses get the rest of the skill surface (24+ commands) immediately, and gain hook coverage as upstream support lands.
80
+
81
+ ### Bring Your Own LLM (via Claude Code)
82
+
83
+ Already love Claude Code? Use it as a universal frontend. Point `ANTHROPIC_BASE_URL` at any OpenAI-compatible endpoint — Claude Code stays the harness, the LLM behind it changes per task.
84
+
85
+ ```bash
86
+ # Recommended: claude-code-router handles Anthropic ↔ provider translation
87
+ npm install -g @musistudio/claude-code-router
88
+ ccr code # first-run config, then launches Claude Code via the router
89
+ # (later) ccr stop # tear down the router before going native again
90
+
91
+ # Or direct: point ANTHROPIC_BASE_URL at any Anthropic-protocol endpoint
92
+ export ANTHROPIC_BASE_URL=https://your-router-or-anthropic-compatible-host
93
+ export ANTHROPIC_API_KEY=sk-byok-key
94
+ cd vault && claude
95
+
96
+ # Switch back to native Claude any time (manual-export route)
97
+ unset ANTHROPIC_BASE_URL ANTHROPIC_API_KEY
98
+ claude
99
+ ```
100
+
101
+ | Route | What it gets you |
102
+ |---|---|
103
+ | **Local** (mlx, ollama, llama.cpp) | Cost-free routine work, full privacy. Pair with [`litellm`](https://github.com/BerriAI/litellm) or [`claude-code-router`](https://github.com/musistudio/claude-code-router). |
104
+ | **Cloud BYOK** (Claude, Gemini, GPT, Groq, OpenRouter) | Pay-as-you-go premium reasoning. One env-var swap, no code changes. |
105
+ | **Hybrid** (route by task or by cost) | Cheap models for routine, premium when it counts. |
106
+
107
+ Same vault. Same skills. Same memory. The LLM swaps; OneBrain doesn't notice.
38
108
 
39
109
  ---
40
110
 
41
- ## Features
111
+ ## Built for Synergetic Thinking
112
+
113
+ OneBrain doesn't just store markdown. Every feature exists to make you and the agent better at each other's job.
42
114
 
43
115
  | | Feature | Description |
44
116
  |---|---|---|
@@ -46,7 +118,7 @@ Unlike chat-based AI tools, OneBrain lives in plain Markdown files you own forev
46
118
  | 🖥️ | **Personal AI OS** | Full local stack: Claude Code + Obsidian + tmux + Telegram — no cloud infra needed |
47
119
  | ⚡ | **24+ Skills** | Braindump, research, consolidate, bookmark, import files, daily briefing, and more |
48
120
  | 📂 | **Vault-native Markdown** | Plain Markdown, no lock-in. Your data stays yours forever |
49
- | 🤖 | **Multi-agent** | Works with Claude Code, Gemini CLI, or any agent that reads Markdown |
121
+ | 🔀 | **Multi-Harness OS** | Switch between Claude Code, Gemini CLI, Codex, Qwen, or BYO LLM context never breaks. [See architecture ↑](#the-harness-os-architecture) |
50
122
  | 🔌 | **Zero Config** | Clone, open in Obsidian, run `/onboarding`. Ready in under 2 minutes |
51
123
  | 📓 | **Session Logs & Checkpoints** | Every conversation saved with summaries and action items. Auto-checkpoints fire every 15 messages or 30 min so nothing is lost mid-session *(auto-checkpoint requires Claude Code)* |
52
124
  | 💾 | **Auto Session Summary** | When you say "bye", the agent silently saves a complete session log — no `/wrapup` needed |
@@ -97,7 +169,29 @@ Turn your AI into a knowledge curator: research, summarize, import files, and bu
97
169
 
98
170
  ---
99
171
 
100
- ## How It Works
172
+ ## One Vault, All Projects — The Command Center
173
+
174
+ Obsidian becomes your dispatch hub for everything you do:
175
+
176
+ - **Read once, understand all** — agent context lives in one place, never re-explained.
177
+ - **Code in repos, orchestration in vault** — agent dispatches from here to wherever the work actually lives.
178
+ - **Markdown replaces Slack / Linear / Notion** — version-controlled, AI-readable, yours forever.
179
+
180
+ The agent reaches outward FROM the vault to: **CLI / repo · website · cloud infra · social media · office docs · project notes · research · MCP servers**.
181
+
182
+ No tab juggling. No tool sprawl.
183
+
184
+ ---
185
+
186
+ ## How It Works — The Path to Co-Evolution
187
+
188
+ A tightening 3-step loop:
189
+
190
+ 1. **Initiate** — Install the CLI, run `/onboarding`. The agent learns your name, vault, and identity. → `npm install -g @onebrain-ai/cli`
191
+ 2. **Capture intent** — Talk in natural language. The agent writes, classifies, and links in real time. → `/braindump` · `/capture` · `/bookmark`
192
+ 3. **Mutual evolution** — `/research` and `/distill` expand your knowledge. `/learn` deepens the agent. The loop tightens. → `/research` · `/distill` · `/learn`
193
+
194
+ ### Behind the loop
101
195
 
102
196
  After `/onboarding`, your AI agent:
103
197
 
@@ -202,13 +296,7 @@ In Claude Code: `/onboarding`
202
296
 
203
297
  ---
204
298
 
205
- ## Supported Agents
206
-
207
- | Agent | Instruction file | Setup |
208
- |-------|-----------------|-------|
209
- | Claude Code | `CLAUDE.md` | Loaded automatically |
210
- | Gemini CLI | `GEMINI.md` | Loaded automatically |
211
- | Any agent | `AGENTS.md` | Read manually or via system prompt |
299
+ > **Choosing a harness?** See [The Harness OS Architecture ↑](#the-harness-os-architecture) for install commands per harness, BYO-LLM via Claude Code, and the full 5-layer stack.
212
300
 
213
301
  ---
214
302
 
@@ -347,6 +435,18 @@ Tasks live inline in your notes — the Tasks plugin surfaces them across the va
347
435
 
348
436
  </details>
349
437
 
438
+ ## OneBrain Cloud
439
+
440
+ Multi-device sync and hosted agent runtimes. Your unified intelligence travels with you.
441
+
442
+ | Tier | What you get | Status |
443
+ |---|---|---|
444
+ | **FREE** | Local vault · OSS skills · BYOK | ✅ Available now |
445
+ | **PRO** | Sync · mobile · hosted runtime | 🟡 [Join waitlist](https://onebrain.run) |
446
+ | **TEAM** | Shared intelligence · team mesh | 🟡 Coming soon |
447
+
448
+ ---
449
+
350
450
  <details>
351
451
  <summary><strong>⚙️ Prerequisites & Detailed Setup</strong></summary>
352
452
  <br>
package/dist/onebrain CHANGED
@@ -9459,7 +9459,7 @@ var init_lib = __esm(() => {
9459
9459
  var require_package = __commonJS((exports, module) => {
9460
9460
  module.exports = {
9461
9461
  name: "@onebrain-ai/cli",
9462
- version: "2.1.7",
9462
+ version: "2.1.9",
9463
9463
  description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
9464
9464
  keywords: [
9465
9465
  "onebrain",
@@ -9474,7 +9474,7 @@ var require_package = __commonJS((exports, module) => {
9474
9474
  "productivity",
9475
9475
  "vault"
9476
9476
  ],
9477
- homepage: "https://github.com/onebrain-ai/onebrain",
9477
+ homepage: "https://onebrain.run",
9478
9478
  repository: {
9479
9479
  type: "git",
9480
9480
  url: "git+https://github.com/onebrain-ai/onebrain.git"
@@ -10761,7 +10761,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
10761
10761
  var import_picocolors = __toESM(require_picocolors(), 1);
10762
10762
  function resolveBinaryVersion() {
10763
10763
  if (true)
10764
- return "2.1.7";
10764
+ return "2.1.9";
10765
10765
  try {
10766
10766
  const pkg = require_package();
10767
10767
  return pkg.version ?? "dev";
@@ -10770,62 +10770,91 @@ function resolveBinaryVersion() {
10770
10770
  }
10771
10771
  }
10772
10772
  var ART_LINES = [
10773
- ` \u25C6${"\u2500".repeat(26)}\u25C6`,
10774
- " \u250C\u2500\u2510\u250C\u2510\u2577\u250C\u2500\u2574\u250C\u2510 \u250C\u2500\u2510\u250C\u2500\u2510\u2577\u250C\u2510\u2577",
10775
- " \u2502 \u2502\u2502\u2514\u2524\u251C\u2574 \u251C\u2534\u2510\u251C\u252C\u2518\u251C\u2500\u2524\u2502\u2502\u2514\u2524",
10776
- " \u2514\u2500\u2518\u2575 \u2575\u2514\u2500\u2574\u2514\u2500\u2518\u2575\u2514\u2574\u2575 \u2575\u2575\u2575 \u2575",
10777
- ` \u25C6${"\u2500".repeat(26)}\u25C6`
10773
+ " ____ ____ _ ",
10774
+ " / __ \\ | _ \\ (_) ",
10775
+ "| | | |_ __ ___| |_) |_ __ __ _ _ _ __ ",
10776
+ "| | | | '_ \\ / _ \\ _ <| '__/ _` | | '_ \\ ",
10777
+ "| |__| | | | | __/ |_) | | | (_| | | | | |",
10778
+ " \\____/|_| |_|\\___|____/|_| \\__,_|_|_| |_|"
10778
10779
  ];
10779
- var PREFIX = "Your AI ";
10780
- var TAGLINE_LEAD = " ";
10781
- var TAGLINE_FALLBACK = `${PREFIX}Thinking Partner`;
10780
+ var PREFIX = "YOUR AI ";
10781
+ var TAGLINE_LEAD = " ";
10782
+ var TAGLINE_FALLBACK = `${PREFIX}THINKING PARTNER`;
10783
+ var SUBTITLE = "A unified intelligence in your Obsidian vault";
10782
10784
  var BANNER_LINE_COUNT = 1 + ART_LINES.length + 3;
10783
- var PREFIX_COLOR = [120, 230, 255];
10784
- var TRAILING_COLOR = [255, 80, 255];
10785
- var FINAL_COLOR = [120, 230, 255];
10785
+ var PREFIX_COLOR = [0, 243, 255];
10786
+ var TRAILING_COLOR = [255, 45, 146];
10787
+ var FINAL_COLOR = [0, 243, 255];
10788
+ var SUBTITLE_COLOR = [0, 170, 178];
10786
10789
  var SENTENCES = [
10787
- { trailing: "Remembers You", trailingWords: ["Remembers", "You"], wordTicks: [24, 32] },
10788
- { trailing: "Catches Insights", trailingWords: ["Catches", "Insights"], wordTicks: [27, 26] },
10789
- { trailing: "Thinking Partner", trailingWords: ["Thinking", "Partner"], wordTicks: [26, 31] }
10790
+ { trailing: "REMEMBERS YOU", trailingWords: ["REMEMBERS", "YOU"], wordTicks: [24, 32] },
10791
+ { trailing: "CATCHES INSIGHTS", trailingWords: ["CATCHES", "INSIGHTS"], wordTicks: [27, 26] },
10792
+ { trailing: "THINKING PARTNER", trailingWords: ["THINKING", "PARTNER"], wordTicks: [26, 31] }
10790
10793
  ];
10791
10794
  function supportsRgb() {
10795
+ if (process.env["FORCE_COLOR"] === "3")
10796
+ return true;
10792
10797
  const c = process.env["COLORTERM"] ?? "";
10793
10798
  return c === "truecolor" || c === "24bit";
10794
10799
  }
10800
+ function isInteractiveStdout() {
10801
+ if (process.env["ONEBRAIN_FORCE_TTY"] === "1")
10802
+ return true;
10803
+ if (process.env["FORCE_COLOR"] === "3")
10804
+ return true;
10805
+ return Boolean(process.stdout.isTTY);
10806
+ }
10795
10807
  function rgb(r, g, b, ch) {
10796
10808
  return `\x1B[1;38;2;${r};${g};${b}m${ch}\x1B[0m`;
10797
10809
  }
10798
10810
  function rgbStr(c, ch) {
10799
10811
  return rgb(c[0], c[1], c[2], ch);
10800
10812
  }
10801
- function hsvToRgb(h, floor = 80) {
10802
- const c = 255;
10803
- const x = Math.round(c * (1 - Math.abs(h / 60 % 2 - 1)));
10804
- let r = 0;
10805
- let g = 0;
10806
- let b = 0;
10807
- if (h < 60)
10808
- [r, g, b] = [c, x, 0];
10809
- else if (h < 120)
10810
- [r, g, b] = [x, c, 0];
10811
- else if (h < 180)
10812
- [r, g, b] = [0, c, x];
10813
- else if (h < 240)
10814
- [r, g, b] = [0, x, c];
10815
- else if (h < 300)
10816
- [r, g, b] = [x, 0, c];
10817
- else
10818
- [r, g, b] = [c, 0, x];
10819
- return [Math.min(255, r + floor), Math.min(255, g + floor), Math.min(255, b + floor)];
10813
+ var BRAND_STOPS = [
10814
+ { t: 0, rgb: [255, 45, 146] },
10815
+ { t: 0.55, rgb: [255, 90, 163] },
10816
+ { t: 1, rgb: [0, 243, 255] }
10817
+ ];
10818
+ function brandGradient(t) {
10819
+ const tt = Math.max(0, Math.min(1, t));
10820
+ for (let i = 0;i < BRAND_STOPS.length - 1; i++) {
10821
+ const a = BRAND_STOPS[i];
10822
+ const b = BRAND_STOPS[i + 1];
10823
+ if (tt <= b.t) {
10824
+ const local = (tt - a.t) / (b.t - a.t);
10825
+ return [
10826
+ Math.round(a.rgb[0] + (b.rgb[0] - a.rgb[0]) * local),
10827
+ Math.round(a.rgb[1] + (b.rgb[1] - a.rgb[1]) * local),
10828
+ Math.round(a.rgb[2] + (b.rgb[2] - a.rgb[2]) * local)
10829
+ ];
10830
+ }
10831
+ }
10832
+ return BRAND_STOPS[BRAND_STOPS.length - 1].rgb;
10833
+ }
10834
+ var [DIAG_MIN, DIAG_MAX] = (() => {
10835
+ let min = 0;
10836
+ let max = 0;
10837
+ for (let row = 0;row < ART_LINES.length; row++) {
10838
+ min = Math.min(min, -row * 3);
10839
+ max = Math.max(max, ART_LINES[row].length - 1 - row * 3);
10840
+ }
10841
+ return [min, max];
10842
+ })();
10843
+ var DIAG_RANGE = DIAG_MAX - DIAG_MIN;
10844
+ function gradientForCell(row, col) {
10845
+ const d = col - 3 * row;
10846
+ return brandGradient((d - DIAG_MIN) / DIAG_RANGE);
10847
+ }
10848
+ var WHITE_SGR = "\x1B[1;97m";
10849
+ var SGR_RESET = "\x1B[0m";
10850
+ function whiteCell(ch) {
10851
+ return `${WHITE_SGR}${ch}${SGR_RESET}`;
10820
10852
  }
10821
- var HUE_PER_CHAR = 10;
10822
- var HUE_PER_ROW = 30;
10823
- function neonLine(line, lineIndex = 0, floor = 80) {
10824
- return line.split("").map((ch, i) => {
10853
+ function neonLine(line, lineIndex = 0) {
10854
+ return line.split("").map((ch, col) => {
10825
10855
  if (ch === " ")
10826
10856
  return ch;
10827
- const hue = ((i * HUE_PER_CHAR - lineIndex * HUE_PER_ROW) % 360 + 360) % 360;
10828
- const [r, g, b] = hsvToRgb(hue, floor);
10857
+ const [r, g, b] = gradientForCell(lineIndex, col);
10829
10858
  return rgb(r, g, b, ch);
10830
10859
  }).join("");
10831
10860
  }
@@ -10835,13 +10864,18 @@ function whiteLine(line) {
10835
10864
  function whiteGlowLine(line, alpha) {
10836
10865
  return line.split("").map((ch) => ch === " " ? ch : `\x1B[1;38;2;${alpha};${alpha};${alpha}m${ch}\x1B[0m`).join("");
10837
10866
  }
10867
+ function renderSubtitle() {
10868
+ const [r, g, b] = SUBTITLE_COLOR;
10869
+ return `\x1B[2;38;2;${r};${g};${b}m${SUBTITLE}\x1B[0m`;
10870
+ }
10838
10871
  function dimLine(line) {
10839
- return line.split("").map((ch) => ch === " " ? ch : `\x1B[2;38;2;50;50;70m${ch}\x1B[0m`).join("");
10872
+ return line.split("").map((ch) => ch === " " ? ch : `\x1B[2;38;2;30;60;70m${ch}\x1B[0m`).join("");
10840
10873
  }
10841
10874
  function scanLineCh(line) {
10842
10875
  return line.split("").map((ch) => ch === " " ? ch : rgb(140, 255, 255, ch)).join("");
10843
10876
  }
10844
- var CURSOR = rgb(140, 255, 255, "\u258C");
10877
+ var SCAN_CYAN = [140, 255, 255];
10878
+ var CURSOR = rgb(SCAN_CYAN[0], SCAN_CYAN[1], SCAN_CYAN[2], "\u258C");
10845
10879
  var GLYPHS = "\u2593\u2591\u2592\u2588\u2502\u2524\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u256A\u256B\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u2518\u250C\u2551\u258C\u2580\u2584\u2590\u2206\u0192\u03A9\xA7\xB6\xB1\xF7\xD7\xF8\xA5\u20AC";
10846
10880
  var randGlyph = () => GLYPHS[Math.floor(Math.random() * GLYPHS.length)] ?? "?";
10847
10881
  var glitchWhite = (g) => `\x1B[1;97m${g}\x1B[0m`;
@@ -10883,7 +10917,7 @@ var SENTENCE_HOLD_MS = 500;
10883
10917
  var WIPE_TICK_MS = 22;
10884
10918
  var WIPE_TRAIL = 3;
10885
10919
  var WIPE_PAUSE_MS = 80;
10886
- async function playBannerIntro(rainbowArt, whiteArt) {
10920
+ async function playBannerIntro(brandArt, whiteArt) {
10887
10921
  const delay = (ms) => new Promise((r) => setTimeout(r, ms));
10888
10922
  const up = (n) => outb(`\x1B[${n}F`);
10889
10923
  printFrame(ART_LINES.map(dimLine), blankTagline());
@@ -10906,32 +10940,25 @@ async function playBannerIntro(rainbowArt, whiteArt) {
10906
10940
  up(BANNER_LINE_COUNT);
10907
10941
  printFrame(whiteArt, blankTagline());
10908
10942
  await delay(600);
10909
- let minD = 0;
10910
- let maxD = 0;
10911
- for (let row = 0;row < ART_LINES.length; row++) {
10912
- minD = Math.min(minD, -row * 3);
10913
- maxD = Math.max(maxD, ART_LINES[row].length - 1 - row * 3);
10914
- }
10915
10943
  function flowFrame(frontD) {
10916
10944
  return ART_LINES.map((line, row) => line.split("").map((ch, col) => {
10917
10945
  if (ch === " ")
10918
10946
  return ch;
10919
10947
  const d = col - 3 * row;
10920
10948
  if (d <= frontD) {
10921
- const hue = ((col * HUE_PER_CHAR - row * HUE_PER_ROW) % 360 + 360) % 360;
10922
- const [r, g, b] = hsvToRgb(hue);
10949
+ const [r, g, b] = gradientForCell(row, col);
10923
10950
  return rgb(r, g, b, ch);
10924
10951
  }
10925
- return `\x1B[1;97m${ch}\x1B[0m`;
10952
+ return whiteCell(ch);
10926
10953
  }).join(""));
10927
10954
  }
10928
- for (let d = minD;d <= maxD; d++) {
10955
+ for (let d = DIAG_MIN;d <= DIAG_MAX; d++) {
10929
10956
  await delay(9);
10930
10957
  up(BANNER_LINE_COUNT);
10931
10958
  printFrame(flowFrame(d), blankTagline());
10932
10959
  }
10933
10960
  up(BANNER_LINE_COUNT);
10934
- printFrame(rainbowArt, blankTagline());
10961
+ printFrame(brandArt, blankTagline());
10935
10962
  await delay(180);
10936
10963
  function shimmerArtFrame(highlight) {
10937
10964
  return ART_LINES.map((line, row) => line.split("").map((ch, col) => {
@@ -10939,25 +10966,24 @@ async function playBannerIntro(rainbowArt, whiteArt) {
10939
10966
  return ch;
10940
10967
  const d = col - 3 * row;
10941
10968
  if (Math.abs(d - highlight) <= 1)
10942
- return `\x1B[1;97m${ch}\x1B[0m`;
10943
- const hue = ((col * HUE_PER_CHAR - row * HUE_PER_ROW) % 360 + 360) % 360;
10944
- const [r, g, b] = hsvToRgb(hue);
10969
+ return whiteCell(ch);
10970
+ const [r, g, b] = gradientForCell(row, col);
10945
10971
  return rgb(r, g, b, ch);
10946
10972
  }).join(""));
10947
10973
  }
10948
- for (let d = minD;d <= maxD; d++) {
10974
+ for (let d = DIAG_MIN;d <= DIAG_MAX; d++) {
10949
10975
  await delay(9);
10950
10976
  up(BANNER_LINE_COUNT);
10951
10977
  printFrame(shimmerArtFrame(d), blankTagline());
10952
10978
  }
10953
10979
  up(BANNER_LINE_COUNT);
10954
- printFrame(rainbowArt, blankTagline());
10980
+ printFrame(brandArt, blankTagline());
10955
10981
  await delay(80);
10956
10982
  }
10957
- async function decodeFirstSentence(rainbowArt, s) {
10983
+ async function decodeFirstSentence(brandArt, s) {
10958
10984
  const delay = (ms) => new Promise((r) => setTimeout(r, ms));
10959
10985
  const up = (n) => outb(`\x1B[${n}F`);
10960
- const prefixWords = ["Your", "AI"];
10986
+ const prefixWords = ["YOUR", "AI"];
10961
10987
  for (let wi = 0;wi < prefixWords.length; wi++) {
10962
10988
  const w = prefixWords[wi];
10963
10989
  const tickMs = PREFIX_TICK_MS[wi];
@@ -10986,16 +11012,16 @@ async function decodeFirstSentence(rainbowArt, s) {
10986
11012
  }
10987
11013
  }
10988
11014
  const trailingBlank = " ".repeat(s.trailing.length);
10989
- printFrame(rainbowArt, `${prefixPart}${trailingBlank}\x1B[K`);
11015
+ printFrame(brandArt, `${prefixPart}${trailingBlank}\x1B[K`);
10990
11016
  }
10991
11017
  if (wi < prefixWords.length - 1) {
10992
11018
  await delay(INTER_WORD_PAUSE_MS);
10993
11019
  }
10994
11020
  }
10995
11021
  await delay(INTER_WORD_PAUSE_MS);
10996
- await decodeTrailing(rainbowArt, s, PREFIX.length);
11022
+ await decodeTrailing(brandArt, s, PREFIX.length);
10997
11023
  }
10998
- async function decodeTrailing(rainbowArt, s, lockedPrefixChars) {
11024
+ async function decodeTrailing(brandArt, s, lockedPrefixChars) {
10999
11025
  const delay = (ms) => new Promise((r) => setTimeout(r, ms));
11000
11026
  const up = (n) => outb(`\x1B[${n}F`);
11001
11027
  const words = s.trailingWords;
@@ -11047,14 +11073,14 @@ async function decodeTrailing(rainbowArt, s, lockedPrefixChars) {
11047
11073
  trailing += " ";
11048
11074
  }
11049
11075
  }
11050
- printFrame(rainbowArt, buildTaglineLine(lockedPrefixChars, trailing));
11076
+ printFrame(brandArt, buildTaglineLine(lockedPrefixChars, trailing));
11051
11077
  }
11052
11078
  if (wi < words.length - 1) {
11053
11079
  await delay(INTER_WORD_PAUSE_MS);
11054
11080
  }
11055
11081
  }
11056
11082
  }
11057
- async function wipeSwapTransition(rainbowArt, from, to) {
11083
+ async function wipeSwapTransition(brandArt, from, to) {
11058
11084
  const delay = (ms) => new Promise((r) => setTimeout(r, ms));
11059
11085
  const up = (n) => outb(`\x1B[${n}F`);
11060
11086
  for (let pos = from.trailing.length - 1;pos >= -WIPE_TRAIL; pos--) {
@@ -11076,20 +11102,20 @@ async function wipeSwapTransition(rainbowArt, from, to) {
11076
11102
  trailing += rgbStr(TRAILING_COLOR, ch);
11077
11103
  }
11078
11104
  }
11079
- printFrame(rainbowArt, buildTaglineLine(PREFIX.length, trailing));
11105
+ printFrame(brandArt, buildTaglineLine(PREFIX.length, trailing));
11080
11106
  }
11081
11107
  await delay(WIPE_PAUSE_MS);
11082
- await decodeTrailing(rainbowArt, to, PREFIX.length);
11108
+ await decodeTrailing(brandArt, to, PREFIX.length);
11083
11109
  }
11084
- async function lockShimmer(rainbowArt, s) {
11110
+ async function lockShimmer(brandArt, s) {
11085
11111
  const delay = (ms) => new Promise((r) => setTimeout(r, ms));
11086
11112
  const up = (n) => outb(`\x1B[${n}F`);
11087
11113
  const SHIMMER_TICK_MS = 22;
11088
11114
  const TRAIL = 3;
11089
11115
  const STOPS = [
11090
11116
  [255, 255, 255],
11091
- [200, 245, 255],
11092
- [150, 235, 255]
11117
+ [180, 220, 255],
11118
+ [0, 243, 255]
11093
11119
  ];
11094
11120
  const fullText = PREFIX + s.trailing;
11095
11121
  const N = fullText.length;
@@ -11114,7 +11140,7 @@ async function lockShimmer(rainbowArt, s) {
11114
11140
  }
11115
11141
  }
11116
11142
  line += "\x1B[K";
11117
- printFrame(rainbowArt, line);
11143
+ printFrame(brandArt, line);
11118
11144
  }
11119
11145
  up(BANNER_LINE_COUNT);
11120
11146
  let finalLine = TAGLINE_LEAD;
@@ -11123,38 +11149,68 @@ async function lockShimmer(rainbowArt, s) {
11123
11149
  finalLine += ch === " " ? " " : rgbStr(FINAL_COLOR, ch);
11124
11150
  }
11125
11151
  finalLine += "\x1B[K";
11126
- printFrame(rainbowArt, finalLine);
11152
+ const subtitleLine = `${TAGLINE_LEAD}${renderSubtitle()}\x1B[K`;
11153
+ outb(`
11154
+ `);
11155
+ for (const l of brandArt)
11156
+ outb(`${l}
11157
+ `);
11158
+ outb(`
11159
+ `);
11160
+ outb(`${finalLine}
11161
+ `);
11162
+ outb(`${subtitleLine}
11163
+ `);
11164
+ outb(`
11165
+ `);
11127
11166
  await delay(150);
11128
11167
  }
11129
- async function printBanner() {
11130
- if (!process.stdout.isTTY)
11131
- return;
11132
- if (!supportsRgb()) {
11133
- outb(`
11168
+ function printStaticBanner() {
11169
+ const truecolor = supportsRgb();
11170
+ outb(`
11134
11171
  `);
11172
+ if (truecolor) {
11173
+ for (let i = 0;i < ART_LINES.length; i++)
11174
+ outb(`${neonLine(ART_LINES[i], i)}
11175
+ `);
11176
+ } else {
11135
11177
  for (const l of ART_LINES)
11136
11178
  outb(`${import_picocolors.default.bold(import_picocolors.default.cyan(l))}
11137
11179
  `);
11138
- outb(`
11180
+ }
11181
+ outb(`
11182
+ `);
11183
+ if (truecolor) {
11184
+ outb(`${TAGLINE_LEAD}${rgbStr(FINAL_COLOR, TAGLINE_FALLBACK)}
11139
11185
  `);
11186
+ outb(`${TAGLINE_LEAD}${renderSubtitle()}
11187
+ `);
11188
+ } else {
11140
11189
  outb(`${TAGLINE_LEAD}${import_picocolors.default.bold(import_picocolors.default.cyan(TAGLINE_FALLBACK))}
11141
11190
  `);
11142
- outb(`
11191
+ outb(`${TAGLINE_LEAD}${import_picocolors.default.dim(import_picocolors.default.cyan(SUBTITLE))}
11143
11192
  `);
11193
+ }
11194
+ outb(`
11195
+ `);
11196
+ }
11197
+ async function printBanner() {
11198
+ if (!isInteractiveStdout() || !supportsRgb()) {
11199
+ printStaticBanner();
11144
11200
  return;
11145
11201
  }
11146
- const rainbowArt = ART_LINES.map((l, i) => neonLine(l, i));
11202
+ const brandArt = ART_LINES.map((l, i) => neonLine(l, i));
11147
11203
  const whiteArt = ART_LINES.map((l) => whiteLine(l));
11148
11204
  try {
11149
11205
  outb("\x1B[?25l");
11150
- await playBannerIntro(rainbowArt, whiteArt);
11151
- await decodeFirstSentence(rainbowArt, SENTENCES[0]);
11206
+ await playBannerIntro(brandArt, whiteArt);
11207
+ await decodeFirstSentence(brandArt, SENTENCES[0]);
11152
11208
  await new Promise((r) => setTimeout(r, SENTENCE_HOLD_MS));
11153
- await wipeSwapTransition(rainbowArt, SENTENCES[0], SENTENCES[1]);
11209
+ await wipeSwapTransition(brandArt, SENTENCES[0], SENTENCES[1]);
11154
11210
  await new Promise((r) => setTimeout(r, SENTENCE_HOLD_MS));
11155
- await wipeSwapTransition(rainbowArt, SENTENCES[1], SENTENCES[2]);
11211
+ await wipeSwapTransition(brandArt, SENTENCES[1], SENTENCES[2]);
11156
11212
  await new Promise((r) => setTimeout(r, SENTENCE_HOLD_MS));
11157
- await lockShimmer(rainbowArt, SENTENCES[2]);
11213
+ await lockShimmer(brandArt, SENTENCES[2]);
11158
11214
  } finally {
11159
11215
  outb("\x1B[?25h");
11160
11216
  }
@@ -12340,10 +12396,9 @@ async function scanMonthDir(monthDir, currentToken, today, seenTokens) {
12340
12396
  }
12341
12397
  return count;
12342
12398
  }
12343
- async function runOrphanScan(logsFolder, sessionToken) {
12344
- const now = new Date;
12399
+ async function runOrphanScan(logsFolder, sessionToken, now) {
12345
12400
  const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
12346
- const { thisYear, thisMonth, prevYear, prevMonth } = getMonthParts();
12401
+ const { thisYear, thisMonth, prevYear, prevMonth } = getMonthParts(now);
12347
12402
  const monthDirs = [
12348
12403
  { year: thisYear, month: thisMonth },
12349
12404
  { year: prevYear, month: prevMonth }
@@ -12357,7 +12412,7 @@ async function runOrphanScan(logsFolder, sessionToken) {
12357
12412
  return { orphan_count: totalOrphans };
12358
12413
  }
12359
12414
  async function orphanScanCommand(logsFolder, sessionToken) {
12360
- const result = await runOrphanScan(logsFolder, sessionToken);
12415
+ const result = await runOrphanScan(logsFolder, sessionToken, new Date);
12361
12416
  process.stdout.write(`${JSON.stringify(result)}
12362
12417
  `);
12363
12418
  }
@@ -12793,8 +12848,8 @@ function patchUtf8(stream) {
12793
12848
  }
12794
12849
 
12795
12850
  // src/index.ts
12796
- var VERSION = "2.1.7";
12797
- var RELEASE_DATE = "2026-04-30";
12851
+ var VERSION = "2.1.9";
12852
+ var RELEASE_DATE = "2026-05-05";
12798
12853
  patchUtf8(process.stdout);
12799
12854
  patchUtf8(process.stderr);
12800
12855
  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.7",
3
+ "version": "2.1.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",
@@ -15,7 +15,7 @@
15
15
  "productivity",
16
16
  "vault"
17
17
  ],
18
- "homepage": "https://github.com/onebrain-ai/onebrain",
18
+ "homepage": "https://onebrain.run",
19
19
  "repository": {
20
20
  "type": "git",
21
21
  "url": "git+https://github.com/onebrain-ai/onebrain.git"