@onebrain-ai/cli 2.1.9 → 2.1.11
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 +168 -151
- package/dist/onebrain +218 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
<p align="center">
|
|
9
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
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
11
|
<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>
|
|
12
|
+
</p>
|
|
13
|
+
<p align="center">
|
|
14
|
+
<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>
|
|
15
|
+
<a href="PLUGIN-CHANGELOG.md"><img alt="Plugin version" src="https://img.shields.io/github/package-json/v/onebrain-ai/onebrain?filename=.claude%2Fplugins%2Fonebrain%2F.claude-plugin%2Fplugin.json&style=for-the-badge&label=plugin&color=ff2d92"></a>
|
|
13
16
|
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-7c3aed?style=for-the-badge"></a>
|
|
14
17
|
</p>
|
|
15
18
|
|
|
@@ -43,129 +46,49 @@ Unlike chat-based AI tools, OneBrain lives in plain Markdown files you own forev
|
|
|
43
46
|
- **Human → Agent** — Every preference, decision, and correction becomes persistent memory. The agent calibrates to you with every interaction.
|
|
44
47
|
- **Agent → Human** — Captures, classifies, links, and synthesizes the noise of your day — so your attention stays on what only you can do.
|
|
45
48
|
|
|
49
|
+
<p align="center">
|
|
50
|
+
<picture>
|
|
51
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/bidir-flow-dark.svg">
|
|
52
|
+
<img alt="Bidirectional flow — Human sends preferences, decisions, and corrections to Agent; Agent returns captures, links, and synthesis. Every interaction sharpens both." src="assets/diagrams/bidir-flow-light.svg" width="640">
|
|
53
|
+
</picture>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
46
56
|
**Harness-agnostic** — Claude Code · Gemini CLI · OpenAI Codex · Qwen · or BYO LLM via API key. [See the architecture ↓](#the-harness-os-architecture)
|
|
47
57
|
|
|
48
58
|
---
|
|
49
59
|
|
|
50
60
|
## The Harness OS Architecture
|
|
51
61
|
|
|
52
|
-
OneBrain doesn't compete with Claude Code, Gemini CLI, or any other AI harness
|
|
62
|
+
OneBrain doesn't compete with Claude Code, Gemini CLI, or any other AI harness — **it extends them**. Whichever harness you drive, OneBrain adds the persistent memory, skill surface, and personal calibration that harnesses don't ship with. Same harness; suddenly it remembers who you are, what you're working on, and how you like to work — all while your Obsidian vault stays the durable source of truth underneath.
|
|
53
63
|
|
|
54
64
|
<p align="center">
|
|
55
|
-
<
|
|
65
|
+
<picture>
|
|
66
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/harness-os-stack-dark.svg">
|
|
67
|
+
<img alt="OneBrain Harness OS — 4-layer architecture: OneBrain (plugin + CLI) on top, Harness, LLM, Obsidian Vault as the source of truth at the base" src="assets/diagrams/harness-os-stack-light.svg" width="780">
|
|
68
|
+
</picture>
|
|
56
69
|
</p>
|
|
57
70
|
|
|
58
71
|
| # | Layer | Role | What lives here |
|
|
59
72
|
|---|---|---|---|
|
|
60
|
-
| 01 | **
|
|
61
|
-
| 02 | **
|
|
62
|
-
| 03 | **
|
|
63
|
-
| 04 | **
|
|
64
|
-
| 05 | **LLM** | Intelligence source | Local (mlx, ollama) · cloud (claude, gemini, gpt) · raw API |
|
|
73
|
+
| 01 | **OneBrain** | OS layer (plugin + CLI) | 24+ skills · lifecycle hooks · vault sync · indexing · checkpoints · harness routing |
|
|
74
|
+
| 02 | **Harness** | Agentic runtime | Bring your own — Claude Code · Gemini CLI · Codex · Qwen · ... |
|
|
75
|
+
| 03 | **LLM** | Intelligence source | Local (mlx, ollama) · cloud (claude, gemini, gpt) · raw API |
|
|
76
|
+
| 04 | **Obsidian Vault** | Source of truth | Plain Markdown — notes, memory, decisions, knowledge graph |
|
|
65
77
|
|
|
66
78
|
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
79
|
|
|
68
|
-
###
|
|
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
|
|
80
|
+
### Extend, don't replace
|
|
95
81
|
|
|
96
|
-
|
|
97
|
-
unset ANTHROPIC_BASE_URL ANTHROPIC_API_KEY
|
|
98
|
-
claude
|
|
99
|
-
```
|
|
82
|
+
A great harness already knows how to talk to an LLM, edit files, and run shell commands. It does **not** know who you are, what you've decided last week, or how you prefer to work. OneBrain fills exactly that gap.
|
|
100
83
|
|
|
101
|
-
|
|
|
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.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
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.
|
|
114
|
-
|
|
115
|
-
| | Feature | Description |
|
|
84
|
+
| | What OneBrain adds | Why it matters |
|
|
116
85
|
|---|---|---|
|
|
117
|
-
| 🧠 | **
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
| 🔀 | **Multi-Harness OS** | Switch between Claude Code, Gemini CLI, Codex, Qwen, or BYO LLM — context never breaks. [See architecture ↑](#the-harness-os-architecture) |
|
|
122
|
-
| 🔌 | **Zero Config** | Clone, open in Obsidian, run `/onboarding`. Ready in under 2 minutes |
|
|
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)* |
|
|
124
|
-
| 💾 | **Auto Session Summary** | When you say "bye", the agent silently saves a complete session log — no `/wrapup` needed |
|
|
125
|
-
| 🔗 | **Knowledge Synthesis** | `/consolidate` turns inbox captures into permanent connected knowledge |
|
|
126
|
-
| 🔬 | **Confidence-scored Memory** | Every insight carries `[conf:high/medium/low]` + `[verified:YYYY-MM-DD]` — knowledge that grows more reliable with use |
|
|
127
|
-
| 💎 | **Knowledge Distillation** | `/distill` crystallizes a completed research thread into a permanent structured note in your knowledge base |
|
|
128
|
-
| 🩺 | **Vault Doctor** | `/doctor` audits broken links, orphan notes, stale memory, and inbox backlog; `--fix` auto-repairs confidence scores and wikilinks |
|
|
129
|
-
| 🎓 | **Teachable AI** | `/learn` permanently shapes how your agent thinks and responds |
|
|
130
|
-
| 🪄 | **Smart Memory Review** | `/memory-review` lets you interactively prune, update, or archive memory entries one by one |
|
|
131
|
-
| 🔒 | **Concurrent-session Safe** | Each session generates an isolated 6-char token — multiple parallel sessions never mix checkpoints |
|
|
132
|
-
| 📱 | **Mobile Access** | Send instructions and receive briefings from anywhere via Telegram |
|
|
133
|
-
| ⚙️ | **CLI Binary** | `onebrain` binary handles checkpoints, session init, doctor, vault-sync, and updates — no Bun, Python, or Node.js required |
|
|
86
|
+
| 🧠 | **Memory** — Identity, preferences, decisions, project state — promoted across four tiers as it earns trust | The harness alone starts every session from zero. OneBrain doesn't. |
|
|
87
|
+
| ⚡ | **Skills** — 24+ vault-aware verbs (`/braindump`, `/research`, `/distill`, `/learn`, `/wrapup`, …) | Pre-built workflows the harness would otherwise need you to script every time. |
|
|
88
|
+
| 🎯 | **Calibration** — Every correction, every preference, every learned habit tunes the agent to *you* | The longer you use it, the sharper it gets — your vault is the training data. |
|
|
89
|
+
| 🔀 | **Continuity** — Context lives in the vault, not the harness | Switch from Claude Code to Gemini CLI to Codex. Same memory. Same skills. Same agent. |
|
|
134
90
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
## Use Cases
|
|
138
|
-
|
|
139
|
-
### 🖥️ Personal AI OS
|
|
140
|
-
|
|
141
|
-
Run OneBrain as your personal AI operating system — a complete AI environment that runs locally with no cloud infrastructure required.
|
|
142
|
-
|
|
143
|
-
**Recommended stack:**
|
|
144
|
-
|
|
145
|
-
| Tool | Role |
|
|
146
|
-
|------|------|
|
|
147
|
-
| [Claude Code](https://claude.ai/code) | Your AI agent, running in the terminal |
|
|
148
|
-
| [Obsidian](https://obsidian.md) | Your vault — single source of truth for memory and knowledge |
|
|
149
|
-
| [tmux](https://github.com/tmux/tmux) | Persistent sessions that survive disconnects and reboots |
|
|
150
|
-
| [Telegram](https://telegram.org) | Mobile access: send instructions, receive briefings from anywhere |
|
|
151
|
-
|
|
152
|
-
**Setting up the full stack:**
|
|
153
|
-
|
|
154
|
-
1. Install OneBrain and open your vault in Obsidian ([Get Started](#installation))
|
|
155
|
-
2. Start a tmux session: `tmux new -s onebrain`
|
|
156
|
-
3. Start Claude Code in your vault directory: `claude`
|
|
157
|
-
4. Run `/telegram:configure` to connect Claude Code's built-in Telegram channel — no custom bot or external infra needed
|
|
158
|
-
5. From any device, open Telegram and send instructions directly to your OneBrain agent
|
|
159
|
-
|
|
160
|
-
Your agent, your vault, your data — forever.
|
|
161
|
-
|
|
162
|
-
### 🧠 Thinking Partner
|
|
163
|
-
|
|
164
|
-
Use OneBrain as a daily thinking partner. Capture ideas with `/braindump`, research topics with `/research`, synthesize knowledge with `/consolidate`, and surface connections you'd never find manually with `/connect`.
|
|
165
|
-
|
|
166
|
-
### 📚 Knowledge Base Builder
|
|
167
|
-
|
|
168
|
-
Turn your AI into a knowledge curator: research, summarize, import files, and build a connected Markdown knowledge base that grows smarter over time.
|
|
91
|
+
> Pick a harness for **how it lets you work** (CLI, IDE, mobile, API). Pick OneBrain for **how it remembers you** across all of them.
|
|
169
92
|
|
|
170
93
|
---
|
|
171
94
|
|
|
@@ -177,15 +100,27 @@ Obsidian becomes your dispatch hub for everything you do:
|
|
|
177
100
|
- **Code in repos, orchestration in vault** — agent dispatches from here to wherever the work actually lives.
|
|
178
101
|
- **Markdown replaces Slack / Linear / Notion** — version-controlled, AI-readable, yours forever.
|
|
179
102
|
|
|
180
|
-
|
|
103
|
+
<p align="center">
|
|
104
|
+
<picture>
|
|
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="640">
|
|
107
|
+
</picture>
|
|
108
|
+
</p>
|
|
181
109
|
|
|
182
|
-
No tab juggling. No tool sprawl.
|
|
110
|
+
The agent reaches outward FROM the vault to every surface where the work actually lives. No tab juggling. No tool sprawl.
|
|
183
111
|
|
|
184
112
|
---
|
|
185
113
|
|
|
186
|
-
##
|
|
114
|
+
## The Path to Co-Evolution
|
|
187
115
|
|
|
188
|
-
A tightening 3-step loop
|
|
116
|
+
A tightening 3-step loop that sharpens with every cycle.
|
|
117
|
+
|
|
118
|
+
<p align="center">
|
|
119
|
+
<picture>
|
|
120
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/coevo-loop-dark.svg">
|
|
121
|
+
<img alt="Co-Evolution loop — three nodes (01 INITIATE at top, 02 CAPTURE_INTENT at bottom-right, 03 MUTUAL_EVOLUTION at bottom-left) connected by curved arrows flowing clockwise" src="assets/diagrams/coevo-loop-light.svg" width="540">
|
|
122
|
+
</picture>
|
|
123
|
+
</p>
|
|
189
124
|
|
|
190
125
|
1. **Initiate** — Install the CLI, run `/onboarding`. The agent learns your name, vault, and identity. → `npm install -g @onebrain-ai/cli`
|
|
191
126
|
2. **Capture intent** — Talk in natural language. The agent writes, classifies, and links in real time. → `/braindump` · `/capture` · `/bookmark`
|
|
@@ -200,9 +135,18 @@ After `/onboarding`, your AI agent:
|
|
|
200
135
|
3. **Remembers everything** — decisions, preferences, and insights accumulate over time
|
|
201
136
|
4. **Suggests next actions** — based on what's in your vault, not what it can infer from scratch
|
|
202
137
|
|
|
203
|
-
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Memory System
|
|
141
|
+
|
|
142
|
+
OneBrain uses a four-tier memory system — knowledge sinks downward as it gets validated, while the agent recalls upward on demand. The Semantic tier has two loading modes (always-loaded and lazy-loaded).
|
|
204
143
|
|
|
205
|
-
|
|
144
|
+
<p align="center">
|
|
145
|
+
<picture>
|
|
146
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/memory-tiers-dark.svg">
|
|
147
|
+
<img alt="Memory tiers — four-stage persistence stack: WORKING (00-inbox + current session) at top, EPISODIC (07-logs), SEMANTIC (05-agent/MEMORY.md + memory/), and KNOWLEDGE (03-knowledge) at the base" src="assets/diagrams/memory-tiers-light.svg" width="780">
|
|
148
|
+
</picture>
|
|
149
|
+
</p>
|
|
206
150
|
|
|
207
151
|
| Tier | Location | What it stores | Promoted by |
|
|
208
152
|
|------|----------|---------------|-------------|
|
|
@@ -212,11 +156,9 @@ OneBrain uses a four-tier memory system — each tier is more compressed and lon
|
|
|
212
156
|
| **Semantic** (lazy-loaded) | `05-agent/memory/` | Behavioral patterns, domain facts — loaded on demand via MEMORY-INDEX.md | `/learn`, `/recap`, `/memory-review` |
|
|
213
157
|
| **Knowledge** | `03-knowledge/` | Permanent synthesized notes | `/distill` |
|
|
214
158
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
## Memory Promotion
|
|
159
|
+
### Memory Promotion
|
|
218
160
|
|
|
219
|
-
|
|
161
|
+
Each tier has specific skills responsible for writing to it. Knowledge moves down the stack only as fast as it earns trust.
|
|
220
162
|
|
|
221
163
|
| Layer | Storage | Written by |
|
|
222
164
|
|---|---|---|
|
|
@@ -237,11 +179,9 @@ session → session log (`/wrapup`) → `memory/` files (`/recap`) → `MEMORY.m
|
|
|
237
179
|
- Only behaviors applying every session with high-impact failure if missed → MEMORY.md Critical Behaviors
|
|
238
180
|
- `MEMORY-INDEX.md` is loaded every session alongside `MEMORY.md` — it is the registry that enables lazy-loading of `memory/` files; updated automatically by any skill that writes to `memory/`
|
|
239
181
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
## Automatic Session Saving
|
|
182
|
+
### Automatic Session Saving
|
|
243
183
|
|
|
244
|
-
OneBrain has
|
|
184
|
+
OneBrain has automatic behaviors that run without you doing anything:
|
|
245
185
|
|
|
246
186
|
| Behavior | Trigger | What it does |
|
|
247
187
|
|----------|---------|-------------|
|
|
@@ -262,9 +202,83 @@ OneBrain has three automatic behaviors that run without you doing anything:
|
|
|
262
202
|
|
|
263
203
|
---
|
|
264
204
|
|
|
205
|
+
## Built for Synergetic Thinking
|
|
206
|
+
|
|
207
|
+
OneBrain doesn't just store markdown. Every feature exists to make you and the agent better at each other's job.
|
|
208
|
+
|
|
209
|
+
| | Feature | Description |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| 🧠 | **Persistent Memory** | Remembers your name, goals, preferences, and decisions across every session |
|
|
212
|
+
| 🖥️ | **Personal AI OS** | Full local stack: Claude Code + Obsidian + tmux + Telegram — no cloud infra needed |
|
|
213
|
+
| ⚡ | **24+ Skills** | Braindump, research, consolidate, bookmark, import files, daily briefing, and more |
|
|
214
|
+
| 📂 | **Vault-native Markdown** | Plain Markdown, no lock-in. Your data stays yours forever |
|
|
215
|
+
| 🔀 | **Multi-Harness OS** | Switch between Claude Code, Gemini CLI, Codex, Qwen, or BYO LLM — context never breaks. [See architecture ↑](#the-harness-os-architecture) |
|
|
216
|
+
| 🔌 | **Zero Config** | Clone, open in Obsidian, run `/onboarding`. Ready in under 2 minutes |
|
|
217
|
+
| 📓 | **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)* |
|
|
218
|
+
| 💾 | **Auto Session Summary** | When you say "bye", the agent silently saves a complete session log — no `/wrapup` needed |
|
|
219
|
+
| 🔗 | **Knowledge Synthesis** | `/consolidate` turns inbox captures into permanent connected knowledge |
|
|
220
|
+
| 🔬 | **Confidence-scored Memory** | Every insight carries `[conf:high/medium/low]` + `[verified:YYYY-MM-DD]` — knowledge that grows more reliable with use |
|
|
221
|
+
| 💎 | **Knowledge Distillation** | `/distill` crystallizes a completed research thread into a permanent structured note in your knowledge base |
|
|
222
|
+
| 🩺 | **Vault Doctor** | `/doctor` audits broken links, orphan notes, stale memory, and inbox backlog; `--fix` auto-repairs confidence scores and wikilinks |
|
|
223
|
+
| 🎓 | **Teachable AI** | `/learn` permanently shapes how your agent thinks and responds |
|
|
224
|
+
| 🪄 | **Smart Memory Review** | `/memory-review` lets you interactively prune, update, or archive memory entries one by one |
|
|
225
|
+
| 🔒 | **Concurrent-session Safe** | Each session generates an isolated 6-char token — multiple parallel sessions never mix checkpoints |
|
|
226
|
+
| 📱 | **Mobile Access** | Send instructions and receive briefings from anywhere via Telegram |
|
|
227
|
+
| ⚙️ | **CLI Binary** | `onebrain` binary handles checkpoints, session init, doctor, vault-sync, and updates — no Bun, Python, or Node.js required |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Use Cases
|
|
232
|
+
|
|
233
|
+
### 🖥️ Personal AI OS
|
|
234
|
+
|
|
235
|
+
Run OneBrain as your personal AI operating system — a complete AI environment that runs locally with no cloud infrastructure required.
|
|
236
|
+
|
|
237
|
+
**Recommended stack:**
|
|
238
|
+
|
|
239
|
+
| Tool | Role |
|
|
240
|
+
|------|------|
|
|
241
|
+
| [Claude Code](https://claude.ai/code) | Your AI agent, running in the terminal |
|
|
242
|
+
| [Obsidian](https://obsidian.md) | Your vault — single source of truth for memory and knowledge |
|
|
243
|
+
| [tmux](https://github.com/tmux/tmux) | Persistent sessions that survive disconnects and reboots |
|
|
244
|
+
| [Telegram](https://telegram.org) | Mobile access: send instructions, receive briefings from anywhere |
|
|
245
|
+
|
|
246
|
+
**Setting up the full stack:**
|
|
247
|
+
|
|
248
|
+
1. Install OneBrain and open your vault in Obsidian ([Get Started](#installation))
|
|
249
|
+
2. Start a tmux session: `tmux new -s onebrain`
|
|
250
|
+
3. Start Claude Code in your vault directory: `claude`
|
|
251
|
+
4. Run `/telegram:configure` to connect Claude Code's built-in Telegram channel — no custom bot or external infra needed
|
|
252
|
+
5. From any device, open Telegram and send instructions directly to your OneBrain agent
|
|
253
|
+
|
|
254
|
+
Your agent, your vault, your data — forever.
|
|
255
|
+
|
|
256
|
+
### 🧠 Thinking Partner
|
|
257
|
+
|
|
258
|
+
Use OneBrain as a daily thinking partner. Capture ideas with `/braindump`, research topics with `/research`, synthesize knowledge with `/consolidate`, and surface connections you'd never find manually with `/connect`.
|
|
259
|
+
|
|
260
|
+
### 📚 Knowledge Base Builder
|
|
261
|
+
|
|
262
|
+
Turn your AI into a knowledge curator: research, summarize, import files, and build a connected Markdown knowledge base that grows smarter over time.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
265
266
|
## Installation
|
|
266
267
|
|
|
267
|
-
###
|
|
268
|
+
### Pick Your Harness
|
|
269
|
+
|
|
270
|
+
Each harness reads OneBrain's instruction file automatically. Install it, run it inside your vault, and the plugin loads on first prompt.
|
|
271
|
+
|
|
272
|
+
| Harness | Install | Run | Reads |
|
|
273
|
+
|---|---|---|---|
|
|
274
|
+
| **Claude Code** *(recommended)* | `npm install -g @anthropic-ai/claude-code` | `claude` | `CLAUDE.md` |
|
|
275
|
+
| **Gemini CLI** | `npm install -g @google/gemini-cli` | `gemini` | `GEMINI.md` |
|
|
276
|
+
| **OpenAI Codex** | `npm install -g @openai/codex` | `codex` | `AGENTS.md` |
|
|
277
|
+
| **Qwen Code** | `npm install -g @qwen-code/qwen-code` | `qwen` | `AGENTS.md` |
|
|
278
|
+
|
|
279
|
+
> 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.
|
|
280
|
+
|
|
281
|
+
### 1. Install the OneBrain CLI
|
|
268
282
|
|
|
269
283
|
```bash
|
|
270
284
|
npm install -g @onebrain-ai/cli
|
|
@@ -286,25 +300,49 @@ File → Open Folder as Vault → select this folder
|
|
|
286
300
|
|
|
287
301
|
### 4. Personalize your vault
|
|
288
302
|
|
|
289
|
-
In
|
|
303
|
+
In your harness: `/onboarding`
|
|
290
304
|
|
|
291
305
|
> **Adding OneBrain to an existing vault?** `cd` into it and run `onebrain init`
|
|
292
306
|
|
|
293
|
-
|
|
307
|
+
### Bring Your Own LLM (via Claude Code)
|
|
294
308
|
|
|
295
|
-
|
|
309
|
+
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.
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# Recommended: claude-code-router handles Anthropic ↔ provider translation
|
|
313
|
+
npm install -g @musistudio/claude-code-router
|
|
314
|
+
ccr code # first-run config, then launches Claude Code via the router
|
|
315
|
+
# (later) ccr stop # tear down the router before going native again
|
|
316
|
+
|
|
317
|
+
# Or direct: point ANTHROPIC_BASE_URL at any Anthropic-protocol endpoint
|
|
318
|
+
export ANTHROPIC_BASE_URL=https://your-router-or-anthropic-compatible-host
|
|
319
|
+
export ANTHROPIC_API_KEY=sk-byok-key
|
|
320
|
+
cd vault && claude
|
|
321
|
+
|
|
322
|
+
# Switch back to native Claude any time (manual-export route)
|
|
323
|
+
unset ANTHROPIC_BASE_URL ANTHROPIC_API_KEY
|
|
324
|
+
claude
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
| Route | What it gets you |
|
|
328
|
+
|---|---|
|
|
329
|
+
| **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). |
|
|
330
|
+
| **Cloud BYOK** (Claude, Gemini, GPT, Groq, OpenRouter) | Pay-as-you-go premium reasoning. One env-var swap, no code changes. |
|
|
331
|
+
| **Hybrid** (route by task or by cost) | Cheap models for routine, premium when it counts. |
|
|
332
|
+
|
|
333
|
+
Same vault. Same skills. Same memory. The LLM swaps; OneBrain doesn't notice.
|
|
296
334
|
|
|
297
335
|
---
|
|
298
336
|
|
|
299
|
-
> **
|
|
337
|
+
> **After `/update`:** Run `/reload-plugins` to pick up changes in your current session, or simply start a new session.
|
|
300
338
|
|
|
301
339
|
---
|
|
302
340
|
|
|
303
341
|
<a id="commands"></a>
|
|
304
342
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
343
|
+
## 📋 24+ Commands
|
|
344
|
+
|
|
345
|
+
The full skill surface, alphabetized by workflow.
|
|
308
346
|
|
|
309
347
|
| Command | What it does |
|
|
310
348
|
|---------|-------------|
|
|
@@ -334,8 +372,6 @@ In Claude Code: `/onboarding`
|
|
|
334
372
|
| `/update` | Update skills, config, and plugins from GitHub |
|
|
335
373
|
| `/help` | List all available commands with descriptions |
|
|
336
374
|
|
|
337
|
-
</details>
|
|
338
|
-
|
|
339
375
|
<details>
|
|
340
376
|
<summary><strong>📁 Vault Structure</strong></summary>
|
|
341
377
|
<br>
|
|
@@ -403,28 +439,9 @@ Checkpoints: `07-logs/YYYY/MM/YYYY-MM-DD-{session_token}-checkpoint-NN.md` — a
|
|
|
403
439
|
|
|
404
440
|
</details>
|
|
405
441
|
|
|
406
|
-
|
|
407
|
-
<summary><strong>🧠 Memory System</strong></summary>
|
|
408
|
-
<br>
|
|
409
|
-
|
|
410
|
-
OneBrain uses a four-tier memory system, where knowledge flows upward as it gets validated. The Semantic tier has two loading modes (always-loaded and lazy-loaded):
|
|
442
|
+
## Task Syntax
|
|
411
443
|
|
|
412
|
-
|
|
413
|
-
Everything that hasn't been processed yet. Captures from `/braindump`, `/capture`, and quick notes land here. Process with `/consolidate` to move into the knowledge base.
|
|
414
|
-
|
|
415
|
-
**Tier 2 — Episodic memory** (`07-logs/`)
|
|
416
|
-
Session logs: `YYYY-MM-DD-session-NN.md` in `YYYY/MM/` subfolders. Contains summaries, decisions, insights, and action items from each session. Generated by `/wrapup`.
|
|
417
|
-
Checkpoints: `YYYY-MM-DD-{session_token}-checkpoint-NN.md` — auto-generated mid-session by hooks. Incorporated and deleted by `/wrapup`.
|
|
418
|
-
|
|
419
|
-
**Tier 3 — Semantic memory** (`05-agent/MEMORY.md` + `05-agent/MEMORY-INDEX.md` + `05-agent/memory/`)
|
|
420
|
-
Always loaded at session start: `MEMORY.md` holds Identity, Active Projects, and Critical Behaviors (~55 lines target). `MEMORY-INDEX.md` is the registry of all `memory/` files — loaded every session, enables lazy-loading. Individual `memory/` files are lazy-loaded on demand via MEMORY-INDEX.md. Only `/learn` writes to MEMORY.md Critical Behaviors. Use `/doctor --fix` to audit and repair stale entries.
|
|
421
|
-
|
|
422
|
-
**Tier 4 — Knowledge base** (`03-knowledge/`)
|
|
423
|
-
Permanent, synthesized notes. `/distill` crystallizes a completed topic thread into a structured note in `03-knowledge/`.
|
|
424
|
-
|
|
425
|
-
### Task Syntax
|
|
426
|
-
|
|
427
|
-
OneBrain uses the [Obsidian Tasks](https://publish.obsidian.md/tasks/) plugin format:
|
|
444
|
+
OneBrain creates tasks in [Obsidian Tasks](https://publish.obsidian.md/tasks/) plugin format:
|
|
428
445
|
|
|
429
446
|
```
|
|
430
447
|
- [ ] Task description 📅 2026-03-25
|
|
@@ -433,7 +450,7 @@ OneBrain uses the [Obsidian Tasks](https://publish.obsidian.md/tasks/) plugin fo
|
|
|
433
450
|
|
|
434
451
|
Tasks live inline in your notes — the Tasks plugin surfaces them across the vault. Run `/tasks` to open a live dashboard in Obsidian (`TASKS.md` at vault root) with sections for overdue, due this week, unscheduled, due later, and recently completed.
|
|
435
452
|
|
|
436
|
-
|
|
453
|
+
---
|
|
437
454
|
|
|
438
455
|
## OneBrain Cloud
|
|
439
456
|
|
package/dist/onebrain
CHANGED
|
@@ -9268,6 +9268,10 @@ async function checkVaultYmlKeys(vaultRoot) {
|
|
|
9268
9268
|
if (raw[key] === undefined)
|
|
9269
9269
|
errors.push(`missing key: ${key}`);
|
|
9270
9270
|
}
|
|
9271
|
+
for (const key of SOFT_REQUIRED_VAULT_YML_KEYS) {
|
|
9272
|
+
if (raw[key] === undefined)
|
|
9273
|
+
warnings.push(`missing key: ${key}`);
|
|
9274
|
+
}
|
|
9271
9275
|
const folders = raw["folders"] ?? {};
|
|
9272
9276
|
for (const key of REQUIRED_FOLDER_KEYS) {
|
|
9273
9277
|
if (folders[key] === undefined)
|
|
@@ -9305,8 +9309,16 @@ async function checkVaultYmlKeys(vaultRoot) {
|
|
|
9305
9309
|
};
|
|
9306
9310
|
}
|
|
9307
9311
|
if (warnings.length > 0) {
|
|
9308
|
-
const
|
|
9309
|
-
const
|
|
9312
|
+
const hasDeprecated = warnings.some((w) => w.includes("onebrain_version") || w.includes("method") || w.includes("runtime.harness"));
|
|
9313
|
+
const hasMissingSoftKey = warnings.some((w) => w.startsWith("missing key:"));
|
|
9314
|
+
let hint;
|
|
9315
|
+
if (hasMissingSoftKey && hasDeprecated) {
|
|
9316
|
+
hint = "Run onebrain doctor --fix to repair vault.yml";
|
|
9317
|
+
} else if (hasMissingSoftKey) {
|
|
9318
|
+
hint = "Run onebrain doctor --fix to backfill defaults";
|
|
9319
|
+
} else if (hasDeprecated) {
|
|
9320
|
+
hint = "Run onebrain doctor --fix to remove deprecated keys";
|
|
9321
|
+
}
|
|
9310
9322
|
return {
|
|
9311
9323
|
check: "vault.yml-keys",
|
|
9312
9324
|
status: "warn",
|
|
@@ -9321,6 +9333,39 @@ async function checkVaultYmlKeys(vaultRoot) {
|
|
|
9321
9333
|
message: "schema ok"
|
|
9322
9334
|
};
|
|
9323
9335
|
}
|
|
9336
|
+
async function checkClaudeSettings(vaultRoot) {
|
|
9337
|
+
const settingsPath = join2(vaultRoot, ".claude", "settings.json");
|
|
9338
|
+
const file = Bun.file(settingsPath);
|
|
9339
|
+
if (!await file.exists()) {
|
|
9340
|
+
return { check: "claude-settings", status: "ok", message: "no vault settings.json" };
|
|
9341
|
+
}
|
|
9342
|
+
let raw;
|
|
9343
|
+
try {
|
|
9344
|
+
raw = JSON.parse(await file.text());
|
|
9345
|
+
} catch {
|
|
9346
|
+
return {
|
|
9347
|
+
check: "claude-settings",
|
|
9348
|
+
status: "warn",
|
|
9349
|
+
message: "settings.json contains invalid JSON"
|
|
9350
|
+
};
|
|
9351
|
+
}
|
|
9352
|
+
const marketplaces = raw["extraKnownMarketplaces"];
|
|
9353
|
+
const onebrain = marketplaces?.["onebrain"];
|
|
9354
|
+
const source = onebrain?.["source"];
|
|
9355
|
+
const repo = source?.["repo"];
|
|
9356
|
+
if (repo === STALE_MARKETPLACE_REPO) {
|
|
9357
|
+
return {
|
|
9358
|
+
check: "claude-settings",
|
|
9359
|
+
status: "warn",
|
|
9360
|
+
message: "stale marketplace repo",
|
|
9361
|
+
hint: "Run onebrain doctor --fix to rewrite to onebrain-ai/onebrain",
|
|
9362
|
+
details: [
|
|
9363
|
+
`stale extraKnownMarketplaces.onebrain.source.repo: ${STALE_MARKETPLACE_REPO} \u2192 ${CANONICAL_MARKETPLACE_REPO}`
|
|
9364
|
+
]
|
|
9365
|
+
};
|
|
9366
|
+
}
|
|
9367
|
+
return { check: "claude-settings", status: "ok", message: "ok" };
|
|
9368
|
+
}
|
|
9324
9369
|
function hookPresent(settings, event, cmdSubstring) {
|
|
9325
9370
|
const groups = settings.hooks?.[event] ?? [];
|
|
9326
9371
|
return groups.some((g) => g.hooks?.some((h) => (h.command ?? "").includes(cmdSubstring)));
|
|
@@ -9407,7 +9452,7 @@ async function checkSettingsHooks(vaultRoot, config) {
|
|
|
9407
9452
|
...okDetails.length > 0 ? { details: okDetails } : {}
|
|
9408
9453
|
};
|
|
9409
9454
|
}
|
|
9410
|
-
var import_yaml2, STANDARD_FOLDER_KEYS, REQUIRED_PLUGIN_FILES, REQUIRED_PLUGIN_DIRS, STALE_BASH_FILES, REQUIRED_VAULT_YML_KEYS, REQUIRED_FOLDER_KEYS, REQUIRED_HOOKS, ALLOWED_HOOK_EVENTS, QMD_HOOK_SUBSTRING = "onebrain qmd-reindex", ONEBRAIN_COMMAND_SUBSTRING = "onebrain", REQUIRED_PERMISSION = "Bash(onebrain *)", STALE_HOOK_SUBSTRINGS;
|
|
9455
|
+
var import_yaml2, STANDARD_FOLDER_KEYS, REQUIRED_PLUGIN_FILES, REQUIRED_PLUGIN_DIRS, STALE_BASH_FILES, REQUIRED_VAULT_YML_KEYS, SOFT_REQUIRED_VAULT_YML_KEYS, REQUIRED_FOLDER_KEYS, STALE_MARKETPLACE_REPO = "kengio/onebrain", CANONICAL_MARKETPLACE_REPO = "onebrain-ai/onebrain", REQUIRED_HOOKS, ALLOWED_HOOK_EVENTS, QMD_HOOK_SUBSTRING = "onebrain qmd-reindex", ONEBRAIN_COMMAND_SUBSTRING = "onebrain", REQUIRED_PERMISSION = "Bash(onebrain *)", STALE_HOOK_SUBSTRINGS;
|
|
9411
9456
|
var init_validator = __esm(() => {
|
|
9412
9457
|
import_yaml2 = __toESM(require_dist(), 1);
|
|
9413
9458
|
STANDARD_FOLDER_KEYS = [
|
|
@@ -9431,7 +9476,8 @@ var init_validator = __esm(() => {
|
|
|
9431
9476
|
"qmd-reindex.sh",
|
|
9432
9477
|
"backfill-recapped.sh"
|
|
9433
9478
|
];
|
|
9434
|
-
REQUIRED_VAULT_YML_KEYS = ["
|
|
9479
|
+
REQUIRED_VAULT_YML_KEYS = ["folders"];
|
|
9480
|
+
SOFT_REQUIRED_VAULT_YML_KEYS = ["update_channel"];
|
|
9435
9481
|
REQUIRED_FOLDER_KEYS = [
|
|
9436
9482
|
"inbox",
|
|
9437
9483
|
"projects",
|
|
@@ -9449,17 +9495,33 @@ var init_validator = __esm(() => {
|
|
|
9449
9495
|
STALE_HOOK_SUBSTRINGS = ["checkpoint-hook.sh", "session-init.sh"];
|
|
9450
9496
|
});
|
|
9451
9497
|
|
|
9498
|
+
// src/lib/fs-atomic.ts
|
|
9499
|
+
import { rename, unlink, writeFile } from "fs/promises";
|
|
9500
|
+
async function atomicWrite(dest, contents, label) {
|
|
9501
|
+
const tmpPath = `${dest}.tmp`;
|
|
9502
|
+
await writeFile(tmpPath, contents, "utf8");
|
|
9503
|
+
try {
|
|
9504
|
+
await rename(tmpPath, dest);
|
|
9505
|
+
} catch (err) {
|
|
9506
|
+
await unlink(tmpPath).catch(() => {});
|
|
9507
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9508
|
+
throw new Error(`rename failed for ${label ?? dest}; tmp cleaned up: ${msg}`, { cause: err });
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
var init_fs_atomic = () => {};
|
|
9512
|
+
|
|
9452
9513
|
// src/lib/index.ts
|
|
9453
9514
|
var init_lib = __esm(() => {
|
|
9454
9515
|
init_parser();
|
|
9455
9516
|
init_validator();
|
|
9517
|
+
init_fs_atomic();
|
|
9456
9518
|
});
|
|
9457
9519
|
|
|
9458
9520
|
// package.json
|
|
9459
9521
|
var require_package = __commonJS((exports, module) => {
|
|
9460
9522
|
module.exports = {
|
|
9461
9523
|
name: "@onebrain-ai/cli",
|
|
9462
|
-
version: "2.1.
|
|
9524
|
+
version: "2.1.11",
|
|
9463
9525
|
description: "CLI for OneBrain \u2014 personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
|
|
9464
9526
|
keywords: [
|
|
9465
9527
|
"onebrain",
|
|
@@ -9940,7 +10002,7 @@ __export(exports_register_hooks, {
|
|
|
9940
10002
|
runRegisterHooks: () => runRegisterHooks,
|
|
9941
10003
|
registerHooksCommand: () => registerHooksCommand
|
|
9942
10004
|
});
|
|
9943
|
-
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
10005
|
+
import { mkdir, readFile, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
9944
10006
|
import { homedir } from "os";
|
|
9945
10007
|
import { dirname, join as join4 } from "path";
|
|
9946
10008
|
async function readSettings(settingsPath) {
|
|
@@ -9956,8 +10018,8 @@ async function readSettings(settingsPath) {
|
|
|
9956
10018
|
async function writeSettings(settingsPath, settings) {
|
|
9957
10019
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
9958
10020
|
const tmpPath = `${settingsPath}.tmp`;
|
|
9959
|
-
await
|
|
9960
|
-
await
|
|
10021
|
+
await writeFile2(tmpPath, JSON.stringify(settings, null, 4), "utf8");
|
|
10022
|
+
await rename2(tmpPath, settingsPath);
|
|
9961
10023
|
}
|
|
9962
10024
|
function checkHookPresence(groups, targetCmd) {
|
|
9963
10025
|
let foundMigrate = false;
|
|
@@ -10086,8 +10148,8 @@ ${ONEBRAIN_MARKER}
|
|
|
10086
10148
|
${PATH_EXPORT}
|
|
10087
10149
|
`;
|
|
10088
10150
|
const tmpPath = `${profilePath}.tmp`;
|
|
10089
|
-
await
|
|
10090
|
-
await
|
|
10151
|
+
await writeFile2(tmpPath, updated, "utf8");
|
|
10152
|
+
await rename2(tmpPath, profilePath);
|
|
10091
10153
|
}
|
|
10092
10154
|
async function runRegisterHooks(opts = {}) {
|
|
10093
10155
|
const vaultRoot = opts.vaultDir ?? process.cwd();
|
|
@@ -10216,17 +10278,7 @@ __export(exports_vault_sync, {
|
|
|
10216
10278
|
vaultSyncCommand: () => vaultSyncCommand,
|
|
10217
10279
|
runVaultSync: () => runVaultSync
|
|
10218
10280
|
});
|
|
10219
|
-
import {
|
|
10220
|
-
mkdir as mkdir2,
|
|
10221
|
-
mkdtemp,
|
|
10222
|
-
readFile as readFile2,
|
|
10223
|
-
readdir,
|
|
10224
|
-
rename as rename2,
|
|
10225
|
-
rm,
|
|
10226
|
-
stat as stat3,
|
|
10227
|
-
unlink,
|
|
10228
|
-
writeFile as writeFile2
|
|
10229
|
-
} from "fs/promises";
|
|
10281
|
+
import { mkdir as mkdir2, mkdtemp, readFile as readFile2, readdir, rm, stat as stat3, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
|
|
10230
10282
|
import { homedir as homedir2, tmpdir } from "os";
|
|
10231
10283
|
import { dirname as dirname2, join as join5, relative } from "path";
|
|
10232
10284
|
function resolveBranch(updateChannel) {
|
|
@@ -10250,7 +10302,7 @@ async function downloadTarball(branch, fetchFn) {
|
|
|
10250
10302
|
}
|
|
10251
10303
|
async function extractTarball(tarball, destDir) {
|
|
10252
10304
|
const tarPath = join5(destDir, "bundle.tar.gz");
|
|
10253
|
-
await
|
|
10305
|
+
await writeFile3(tarPath, Buffer.from(tarball));
|
|
10254
10306
|
const proc = Bun.spawn(["tar", "-xzf", tarPath, "-C", destDir], {
|
|
10255
10307
|
stdout: "pipe",
|
|
10256
10308
|
stderr: "pipe"
|
|
@@ -10260,7 +10312,7 @@ async function extractTarball(tarball, destDir) {
|
|
|
10260
10312
|
const errText = await new Response(proc.stderr).text();
|
|
10261
10313
|
throw new Error(`tar extraction failed (exit ${exitCode}): ${errText.trim()}`);
|
|
10262
10314
|
}
|
|
10263
|
-
await
|
|
10315
|
+
await unlink2(tarPath);
|
|
10264
10316
|
const entries = await readdir(destDir);
|
|
10265
10317
|
const topLevel = entries.find((e2) => e2 !== "bundle.tar.gz");
|
|
10266
10318
|
if (!topLevel) {
|
|
@@ -10296,7 +10348,7 @@ async function listFilesRecursive(dir) {
|
|
|
10296
10348
|
}
|
|
10297
10349
|
return results;
|
|
10298
10350
|
}
|
|
10299
|
-
async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn =
|
|
10351
|
+
async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink2) {
|
|
10300
10352
|
const sourcePlugin = join5(extractedDir, ".claude", "plugins", "onebrain");
|
|
10301
10353
|
const destPlugin = join5(vaultRoot, ".claude", "plugins", "onebrain");
|
|
10302
10354
|
await mkdir2(destPlugin, { recursive: true });
|
|
@@ -10316,7 +10368,7 @@ async function syncPluginFiles(extractedDir, vaultRoot, unlinkFn = unlink) {
|
|
|
10316
10368
|
const destPath = join5(destPlugin, rel);
|
|
10317
10369
|
await mkdir2(dirname2(destPath), { recursive: true });
|
|
10318
10370
|
const content = await readFile2(srcPath);
|
|
10319
|
-
await
|
|
10371
|
+
await writeFile3(destPath, content);
|
|
10320
10372
|
filesAdded++;
|
|
10321
10373
|
}
|
|
10322
10374
|
let filesRemoved = 0;
|
|
@@ -10336,7 +10388,7 @@ async function copyRootDocs(extractedDir, vaultRoot) {
|
|
|
10336
10388
|
const destPath = join5(vaultRoot, doc);
|
|
10337
10389
|
try {
|
|
10338
10390
|
const content = await readFile2(srcPath);
|
|
10339
|
-
await
|
|
10391
|
+
await writeFile3(destPath, content);
|
|
10340
10392
|
} catch {}
|
|
10341
10393
|
}
|
|
10342
10394
|
}
|
|
@@ -10353,7 +10405,7 @@ async function mergeHarnessFile(extractedDir, vaultRoot, filename) {
|
|
|
10353
10405
|
try {
|
|
10354
10406
|
vaultText = await readFile2(destPath, "utf8");
|
|
10355
10407
|
} catch {
|
|
10356
|
-
await
|
|
10408
|
+
await writeFile3(destPath, repoText, "utf8");
|
|
10357
10409
|
return repoText.split(`
|
|
10358
10410
|
`).filter((l2) => l2.startsWith("@")).length;
|
|
10359
10411
|
}
|
|
@@ -10374,7 +10426,7 @@ async function mergeHarnessFile(extractedDir, vaultRoot, filename) {
|
|
|
10374
10426
|
}
|
|
10375
10427
|
const merged = vaultLines.join(`
|
|
10376
10428
|
`);
|
|
10377
|
-
await
|
|
10429
|
+
await writeFile3(destPath, merged, "utf8");
|
|
10378
10430
|
return newImports.length;
|
|
10379
10431
|
}
|
|
10380
10432
|
async function mergeHarnessFiles(extractedDir, vaultRoot) {
|
|
@@ -10396,22 +10448,21 @@ async function updateVaultYml(vaultRoot, updateChannel) {
|
|
|
10396
10448
|
}
|
|
10397
10449
|
const raw = import_yaml3.parse(text) ?? {};
|
|
10398
10450
|
raw["update_channel"] = updateChannel;
|
|
10399
|
-
|
|
10400
|
-
const tmpPath = `${vaultYmlPath}.tmp`;
|
|
10401
|
-
await writeFile2(tmpPath, updated, "utf8");
|
|
10402
|
-
await rename2(tmpPath, vaultYmlPath);
|
|
10451
|
+
await atomicWrite(vaultYmlPath, import_yaml3.stringify(raw, { lineWidth: 0 }), "vault.yml");
|
|
10403
10452
|
}
|
|
10404
|
-
async function
|
|
10453
|
+
async function readPluginMetadata(vaultRoot) {
|
|
10405
10454
|
const pluginJsonPath = join5(vaultRoot, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json");
|
|
10406
10455
|
try {
|
|
10407
10456
|
const text = await readFile2(pluginJsonPath, "utf8");
|
|
10408
10457
|
const parsed = JSON.parse(text);
|
|
10409
|
-
|
|
10458
|
+
const version = typeof parsed["version"] === "string" ? parsed["version"] : "unknown";
|
|
10459
|
+
const lastUpdated = typeof parsed["lastUpdated"] === "string" ? parsed["lastUpdated"] : undefined;
|
|
10460
|
+
return { version, lastUpdated };
|
|
10410
10461
|
} catch {
|
|
10411
|
-
return "unknown";
|
|
10462
|
+
return { version: "unknown", lastUpdated: undefined };
|
|
10412
10463
|
}
|
|
10413
10464
|
}
|
|
10414
|
-
async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir) {
|
|
10465
|
+
async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir, now = () => new Date) {
|
|
10415
10466
|
let text;
|
|
10416
10467
|
try {
|
|
10417
10468
|
text = await readFile2(installedPluginsPath, "utf8");
|
|
@@ -10433,7 +10484,8 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
|
|
|
10433
10484
|
return { skipped: true };
|
|
10434
10485
|
}
|
|
10435
10486
|
const vaultPluginDir = join5(vaultRoot, ".claude", "plugins", "onebrain");
|
|
10436
|
-
const pluginVersion = await
|
|
10487
|
+
const { version: pluginVersion, lastUpdated: pluginLastUpdated } = await readPluginMetadata(vaultRoot);
|
|
10488
|
+
const updatedAt = pluginLastUpdated ?? now().toISOString();
|
|
10437
10489
|
const cacheDir = installedPluginsCacheDir ?? join5(dirname2(installedPluginsPath), "cache");
|
|
10438
10490
|
const hasMarketplace = onebrainKeys.some((k2) => {
|
|
10439
10491
|
const entries = plugins[k2];
|
|
@@ -10443,6 +10495,33 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
|
|
|
10443
10495
|
return { skipped: true };
|
|
10444
10496
|
}
|
|
10445
10497
|
let changed = false;
|
|
10498
|
+
const ONEBRAIN_KEY = "onebrain@onebrain";
|
|
10499
|
+
if (Array.isArray(plugins[ONEBRAIN_KEY])) {
|
|
10500
|
+
const before = plugins[ONEBRAIN_KEY];
|
|
10501
|
+
const keep = [];
|
|
10502
|
+
for (const entry of before) {
|
|
10503
|
+
const projectPath = entry["projectPath"];
|
|
10504
|
+
if (typeof projectPath !== "string") {
|
|
10505
|
+
keep.push(entry);
|
|
10506
|
+
continue;
|
|
10507
|
+
}
|
|
10508
|
+
try {
|
|
10509
|
+
await stat3(projectPath);
|
|
10510
|
+
keep.push(entry);
|
|
10511
|
+
} catch (err) {
|
|
10512
|
+
const code = err?.code;
|
|
10513
|
+
if (code === "ENOENT")
|
|
10514
|
+
continue;
|
|
10515
|
+
process.stderr.write(`vault-sync: pin warning: stat ${projectPath}: ${code ?? "unknown"}
|
|
10516
|
+
`);
|
|
10517
|
+
keep.push(entry);
|
|
10518
|
+
}
|
|
10519
|
+
}
|
|
10520
|
+
if (keep.length !== before.length) {
|
|
10521
|
+
plugins[ONEBRAIN_KEY] = keep;
|
|
10522
|
+
changed = true;
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10446
10525
|
for (const key of onebrainKeys) {
|
|
10447
10526
|
const entries = plugins[key];
|
|
10448
10527
|
for (const entry of entries) {
|
|
@@ -10456,20 +10535,24 @@ async function pinToVault(vaultRoot, installedPluginsPath, installedPluginsCache
|
|
|
10456
10535
|
} catch {
|
|
10457
10536
|
inCache = false;
|
|
10458
10537
|
}
|
|
10459
|
-
|
|
10538
|
+
const isThisVault = installPath === vaultPluginDir;
|
|
10539
|
+
if (inCache) {
|
|
10540
|
+
entry["installPath"] = vaultPluginDir;
|
|
10541
|
+
changed = true;
|
|
10542
|
+
} else if (!isThisVault) {
|
|
10460
10543
|
continue;
|
|
10461
10544
|
}
|
|
10462
|
-
entry["
|
|
10463
|
-
|
|
10464
|
-
|
|
10545
|
+
if (entry["version"] !== pluginVersion) {
|
|
10546
|
+
entry["version"] = pluginVersion;
|
|
10547
|
+
entry["lastUpdated"] = updatedAt;
|
|
10548
|
+
changed = true;
|
|
10549
|
+
}
|
|
10465
10550
|
}
|
|
10466
10551
|
}
|
|
10467
10552
|
if (!changed) {
|
|
10468
10553
|
return { skipped: false };
|
|
10469
10554
|
}
|
|
10470
|
-
|
|
10471
|
-
await writeFile2(tmpPath, JSON.stringify(data, null, 4), "utf8");
|
|
10472
|
-
await rename2(tmpPath, installedPluginsPath);
|
|
10555
|
+
await atomicWrite(installedPluginsPath, JSON.stringify(data, null, 4), "installed_plugins.json");
|
|
10473
10556
|
return { skipped: false };
|
|
10474
10557
|
}
|
|
10475
10558
|
async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir) {
|
|
@@ -10535,7 +10618,7 @@ async function cleanPluginCache(installedPluginsPath, installedPluginsCacheDir)
|
|
|
10535
10618
|
async function runVaultSync(vaultRoot, opts = {}) {
|
|
10536
10619
|
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
10537
10620
|
const isTTY = opts.isTTY ?? process.stdout.isTTY;
|
|
10538
|
-
const unlinkFn = opts.unlinkFn ??
|
|
10621
|
+
const unlinkFn = opts.unlinkFn ?? unlink2;
|
|
10539
10622
|
let updateChannel = "stable";
|
|
10540
10623
|
try {
|
|
10541
10624
|
const vaultYmlText = await readFile2(join5(vaultRoot, "vault.yml"), "utf8");
|
|
@@ -10637,7 +10720,7 @@ async function runVaultSync(vaultRoot, opts = {}) {
|
|
|
10637
10720
|
const destPath = join5(destObsidian, rel);
|
|
10638
10721
|
await mkdir2(dirname2(destPath), { recursive: true });
|
|
10639
10722
|
const content = await readFile2(srcPath);
|
|
10640
|
-
await
|
|
10723
|
+
await writeFile3(destPath, content);
|
|
10641
10724
|
}
|
|
10642
10725
|
} catch {}
|
|
10643
10726
|
}
|
|
@@ -10672,7 +10755,7 @@ async function runVaultSync(vaultRoot, opts = {}) {
|
|
|
10672
10755
|
if (harness === "claude") {
|
|
10673
10756
|
startSpinner("\uD83D\uDCCC", "Pinning to vault");
|
|
10674
10757
|
try {
|
|
10675
|
-
const pinResult = await pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir);
|
|
10758
|
+
const pinResult = await pinToVault(vaultRoot, installedPluginsPath, installedPluginsCacheDir, opts.now);
|
|
10676
10759
|
result.pinSkipped = pinResult.skipped;
|
|
10677
10760
|
if (pinResult.skipped) {
|
|
10678
10761
|
stopSpinner("pin skipped (not found or marketplace)");
|
|
@@ -10727,6 +10810,7 @@ async function vaultSyncCommand(vaultRoot, opts = {}) {
|
|
|
10727
10810
|
var import_picocolors6, import_yaml3;
|
|
10728
10811
|
var init_vault_sync = __esm(() => {
|
|
10729
10812
|
init_dist2();
|
|
10813
|
+
init_lib();
|
|
10730
10814
|
init_cli_ui();
|
|
10731
10815
|
init_harness();
|
|
10732
10816
|
import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
@@ -10761,7 +10845,7 @@ var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
|
10761
10845
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
10762
10846
|
function resolveBinaryVersion() {
|
|
10763
10847
|
if (true)
|
|
10764
|
-
return "2.1.
|
|
10848
|
+
return "2.1.11";
|
|
10765
10849
|
try {
|
|
10766
10850
|
const pkg = require_package();
|
|
10767
10851
|
return pkg.version ?? "dev";
|
|
@@ -11229,6 +11313,7 @@ async function runDoctor(opts = {}) {
|
|
|
11229
11313
|
const checkPluginFilesFn = opts.checkPluginFilesFn ?? checkPluginFiles;
|
|
11230
11314
|
const checkVaultYmlKeysFn = opts.checkVaultYmlKeysFn ?? checkVaultYmlKeys;
|
|
11231
11315
|
const checkSettingsHooksFn = opts.checkSettingsHooksFn ?? checkSettingsHooks;
|
|
11316
|
+
const checkClaudeSettingsFn = opts.checkClaudeSettingsFn ?? checkClaudeSettings;
|
|
11232
11317
|
if (isTTY) {
|
|
11233
11318
|
await printBanner();
|
|
11234
11319
|
}
|
|
@@ -11259,7 +11344,14 @@ async function runDoctor(opts = {}) {
|
|
|
11259
11344
|
if (vaultYmlResult.status === "ok") {
|
|
11260
11345
|
try {
|
|
11261
11346
|
config = await loadVaultConfigFn(vaultDir);
|
|
11262
|
-
} catch {
|
|
11347
|
+
} catch (err) {
|
|
11348
|
+
const code = err?.code;
|
|
11349
|
+
if (code !== "ENOENT") {
|
|
11350
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11351
|
+
process.stderr.write(`doctor: vault.yml load warning: ${msg}
|
|
11352
|
+
`);
|
|
11353
|
+
}
|
|
11354
|
+
}
|
|
11263
11355
|
}
|
|
11264
11356
|
await randDelay();
|
|
11265
11357
|
sp1?.stop(fmtResult(vaultYmlResult), vaultYmlResult.details);
|
|
@@ -11269,6 +11361,7 @@ async function runDoctor(opts = {}) {
|
|
|
11269
11361
|
let pluginFilesResult;
|
|
11270
11362
|
let vaultYmlKeysResult;
|
|
11271
11363
|
let settingsHooksResult;
|
|
11364
|
+
let claudeSettingsResult;
|
|
11272
11365
|
if (isTTY) {
|
|
11273
11366
|
const sp2 = createStep("\u2699\uFE0F", "Config schema");
|
|
11274
11367
|
vaultYmlKeysResult = await checkVaultYmlKeysFn(vaultDir);
|
|
@@ -11294,6 +11387,10 @@ async function runDoctor(opts = {}) {
|
|
|
11294
11387
|
qmdResult = await checkQmdEmbeddingsFn(config);
|
|
11295
11388
|
await randDelay();
|
|
11296
11389
|
sp7.stop(fmtResult(qmdResult), qmdResult.details);
|
|
11390
|
+
const sp8 = createStep("\uD83D\uDED2", "Marketplace config");
|
|
11391
|
+
claudeSettingsResult = await checkClaudeSettingsFn(vaultDir);
|
|
11392
|
+
await randDelay();
|
|
11393
|
+
sp8.stop(fmtResult(claudeSettingsResult), claudeSettingsResult.details);
|
|
11297
11394
|
} else {
|
|
11298
11395
|
[
|
|
11299
11396
|
foldersResult,
|
|
@@ -11301,14 +11398,16 @@ async function runDoctor(opts = {}) {
|
|
|
11301
11398
|
orphanResult,
|
|
11302
11399
|
pluginFilesResult,
|
|
11303
11400
|
vaultYmlKeysResult,
|
|
11304
|
-
settingsHooksResult
|
|
11401
|
+
settingsHooksResult,
|
|
11402
|
+
claudeSettingsResult
|
|
11305
11403
|
] = await Promise.all([
|
|
11306
11404
|
checkFoldersFn(vaultDir, config),
|
|
11307
11405
|
checkQmdEmbeddingsFn(config),
|
|
11308
11406
|
checkOrphanCheckpointsFn(vaultDir, config),
|
|
11309
11407
|
checkPluginFilesFn(vaultDir),
|
|
11310
11408
|
checkVaultYmlKeysFn(vaultDir),
|
|
11311
|
-
checkSettingsHooksFn(vaultDir, config)
|
|
11409
|
+
checkSettingsHooksFn(vaultDir, config),
|
|
11410
|
+
checkClaudeSettingsFn(vaultDir)
|
|
11312
11411
|
]);
|
|
11313
11412
|
}
|
|
11314
11413
|
const results = [
|
|
@@ -11318,7 +11417,8 @@ async function runDoctor(opts = {}) {
|
|
|
11318
11417
|
pluginFilesResult,
|
|
11319
11418
|
settingsHooksResult,
|
|
11320
11419
|
orphanResult,
|
|
11321
|
-
qmdResult
|
|
11420
|
+
qmdResult,
|
|
11421
|
+
claudeSettingsResult
|
|
11322
11422
|
];
|
|
11323
11423
|
const totalChecks = results.length;
|
|
11324
11424
|
const errorCount = results.filter((r2) => r2.status === "error").length;
|
|
@@ -11351,14 +11451,17 @@ async function runDoctor(opts = {}) {
|
|
|
11351
11451
|
`);
|
|
11352
11452
|
}
|
|
11353
11453
|
}
|
|
11454
|
+
let fixFailedCount = 0;
|
|
11354
11455
|
if (opts.fix) {
|
|
11355
|
-
await applyFixes(vaultDir, results, isTTY, opts.registerHooksFn);
|
|
11456
|
+
fixFailedCount = await applyFixes(vaultDir, results, isTTY, opts.registerHooksFn);
|
|
11356
11457
|
}
|
|
11458
|
+
const ok = errorCount === 0 && fixFailedCount === 0;
|
|
11357
11459
|
return {
|
|
11358
|
-
ok
|
|
11359
|
-
exitCode:
|
|
11460
|
+
ok,
|
|
11461
|
+
exitCode: ok ? 0 : 1,
|
|
11360
11462
|
errorCount,
|
|
11361
|
-
warningCount
|
|
11463
|
+
warningCount,
|
|
11464
|
+
fixFailedCount
|
|
11362
11465
|
};
|
|
11363
11466
|
}
|
|
11364
11467
|
async function doctorCommand(opts = {}) {
|
|
@@ -11406,12 +11509,18 @@ function getFix(r2) {
|
|
|
11406
11509
|
};
|
|
11407
11510
|
}
|
|
11408
11511
|
const hasDeprecatedKeys = r2.details?.some((d) => d.includes("deprecated key: onebrain_version") || d.includes("deprecated key: method") || d.includes("deprecated key: runtime.harness"));
|
|
11409
|
-
|
|
11512
|
+
const hasMissingUpdateChannel = r2.details?.some((d) => d === "missing key: update_channel");
|
|
11513
|
+
if (r2.check === "vault.yml-keys" && r2.status === "warn" && (hasDeprecatedKeys || hasMissingUpdateChannel)) {
|
|
11410
11514
|
const deprecated = (r2.details ?? []).filter((d) => d.startsWith("deprecated key:")).map((d) => d.slice("deprecated key: ".length).split(" ")[0] ?? d);
|
|
11411
|
-
const
|
|
11515
|
+
const fixParts = [];
|
|
11516
|
+
if (hasMissingUpdateChannel)
|
|
11517
|
+
fixParts.push("add update_channel: stable");
|
|
11518
|
+
if (deprecated.length > 0)
|
|
11519
|
+
fixParts.push(`remove deprecated: ${deprecated.join(", ")}`);
|
|
11520
|
+
const description = fixParts.length > 0 ? `Fix vault.yml: ${fixParts.join("; ")}` : "Fix vault.yml";
|
|
11412
11521
|
return {
|
|
11413
11522
|
fn: async (vaultDir) => {
|
|
11414
|
-
const { readFile: readFile2
|
|
11523
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
11415
11524
|
const { join: join5 } = await import("path");
|
|
11416
11525
|
const { parse: parse3, stringify } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
|
|
11417
11526
|
const vaultYmlPath = join5(vaultDir, "vault.yml");
|
|
@@ -11431,10 +11540,10 @@ function getFix(r2) {
|
|
|
11431
11540
|
}
|
|
11432
11541
|
}
|
|
11433
11542
|
}
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
await
|
|
11543
|
+
if (details.some((d) => d === "missing key: update_channel")) {
|
|
11544
|
+
raw["update_channel"] = "stable";
|
|
11545
|
+
}
|
|
11546
|
+
await atomicWrite(vaultYmlPath, stringify(raw, { lineWidth: 0 }), "vault.yml");
|
|
11438
11547
|
},
|
|
11439
11548
|
description
|
|
11440
11549
|
};
|
|
@@ -11469,6 +11578,29 @@ function getFix(r2) {
|
|
|
11469
11578
|
description: `Embed ${count} unembedded document(s) (qmd update + embed)`
|
|
11470
11579
|
};
|
|
11471
11580
|
}
|
|
11581
|
+
if (r2.check === "claude-settings" && r2.status === "warn" && r2.details?.some((d) => d.startsWith("stale extraKnownMarketplaces.onebrain.source.repo:"))) {
|
|
11582
|
+
return {
|
|
11583
|
+
fn: async (vaultDir) => {
|
|
11584
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
11585
|
+
const { join: join5 } = await import("path");
|
|
11586
|
+
const settingsPath = join5(vaultDir, ".claude", "settings.json");
|
|
11587
|
+
const text = await readFile2(settingsPath, "utf8");
|
|
11588
|
+
const raw = JSON.parse(text);
|
|
11589
|
+
const marketplaces = raw["extraKnownMarketplaces"];
|
|
11590
|
+
const onebrain = marketplaces?.["onebrain"];
|
|
11591
|
+
const source = onebrain?.["source"];
|
|
11592
|
+
if (!source || source["repo"] !== "kengio/onebrain")
|
|
11593
|
+
return;
|
|
11594
|
+
source["repo"] = "onebrain-ai/onebrain";
|
|
11595
|
+
const trailingNewline = text.endsWith(`
|
|
11596
|
+
`) ? `
|
|
11597
|
+
` : "";
|
|
11598
|
+
const updated = `${JSON.stringify(raw, null, 2)}${trailingNewline}`;
|
|
11599
|
+
await atomicWrite(settingsPath, updated, ".claude/settings.json");
|
|
11600
|
+
},
|
|
11601
|
+
description: "Rewrite stale marketplace repo: kengio/onebrain \u2192 onebrain-ai/onebrain"
|
|
11602
|
+
};
|
|
11603
|
+
}
|
|
11472
11604
|
if (r2.check === "folders" && r2.status === "error" && r2.hint) {
|
|
11473
11605
|
const missingStr = r2.hint.replace("Missing: ", "");
|
|
11474
11606
|
return {
|
|
@@ -11492,7 +11624,7 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
|
|
|
11492
11624
|
writeLine(`${import_picocolors5.default.green("\u25C6")} Nothing to fix`);
|
|
11493
11625
|
else
|
|
11494
11626
|
writeLine("nothing to fix");
|
|
11495
|
-
return;
|
|
11627
|
+
return 0;
|
|
11496
11628
|
}
|
|
11497
11629
|
if (isTTY) {
|
|
11498
11630
|
writeLine("");
|
|
@@ -11507,12 +11639,13 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
|
|
|
11507
11639
|
barLine(import_picocolors5.default.dim("No"));
|
|
11508
11640
|
barBlank();
|
|
11509
11641
|
close(`No changes made \u2014 run ${import_picocolors5.default.cyan("onebrain doctor --fix")} to apply`);
|
|
11510
|
-
return;
|
|
11642
|
+
return 0;
|
|
11511
11643
|
}
|
|
11512
11644
|
barLine("Yes");
|
|
11513
11645
|
barBlank();
|
|
11514
11646
|
}
|
|
11515
11647
|
let fixed = 0;
|
|
11648
|
+
let fixFailed = 0;
|
|
11516
11649
|
const unfixable = [];
|
|
11517
11650
|
for (const r2 of results) {
|
|
11518
11651
|
if (r2.status === "ok")
|
|
@@ -11528,6 +11661,7 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
|
|
|
11528
11661
|
if (isTTY)
|
|
11529
11662
|
barLine(`${import_picocolors5.default.green("\u25C6")} ${fix.description}`);
|
|
11530
11663
|
} catch (err) {
|
|
11664
|
+
fixFailed++;
|
|
11531
11665
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11532
11666
|
if (isTTY) {
|
|
11533
11667
|
barLine(`${import_picocolors5.default.yellow("\u25B2")} Could not fix ${r2.check}: ${errMsg}`);
|
|
@@ -11541,6 +11675,9 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
|
|
|
11541
11675
|
barBlank();
|
|
11542
11676
|
if (fixed > 0)
|
|
11543
11677
|
barLine(`${import_picocolors5.default.green("\u25C6")} Fixed ${fixed} issue(s)`);
|
|
11678
|
+
if (fixFailed > 0) {
|
|
11679
|
+
barLine(`${import_picocolors5.default.yellow("\u25B2")} ${fixFailed} fix(es) failed \u2014 see warnings above`);
|
|
11680
|
+
}
|
|
11544
11681
|
if (unfixable.length > 0) {
|
|
11545
11682
|
barLine(`${import_picocolors5.default.yellow("\u25B2")} ${unfixable.length} issue(s) require manual action:`);
|
|
11546
11683
|
for (const r2 of unfixable) {
|
|
@@ -11551,18 +11688,22 @@ async function applyFixes(vaultDir, results, isTTY, registerHooksFn) {
|
|
|
11551
11688
|
close("Done");
|
|
11552
11689
|
} else {
|
|
11553
11690
|
process.stdout.write(`fixed: ${fixed}
|
|
11691
|
+
`);
|
|
11692
|
+
if (fixFailed > 0)
|
|
11693
|
+
process.stdout.write(`fix-failed: ${fixFailed}
|
|
11554
11694
|
`);
|
|
11555
11695
|
if (unfixable.length > 0) {
|
|
11556
11696
|
process.stdout.write(`manual: ${unfixable.map((r2) => r2.check).join(", ")}
|
|
11557
11697
|
`);
|
|
11558
11698
|
}
|
|
11559
11699
|
}
|
|
11700
|
+
return fixFailed;
|
|
11560
11701
|
}
|
|
11561
11702
|
|
|
11562
11703
|
// src/commands/init.ts
|
|
11563
11704
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
11564
11705
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
11565
|
-
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as
|
|
11706
|
+
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename3, stat as stat4, writeFile as writeFile4 } from "fs/promises";
|
|
11566
11707
|
import { homedir as homedir3 } from "os";
|
|
11567
11708
|
import { dirname as dirname3, join as join6 } from "path";
|
|
11568
11709
|
init_cli_ui();
|
|
@@ -11617,7 +11758,7 @@ var VAULT_YML_DEFAULTS = {
|
|
|
11617
11758
|
};
|
|
11618
11759
|
async function writeVaultYml(vaultDir) {
|
|
11619
11760
|
const content = import_yaml4.stringify(VAULT_YML_DEFAULTS, { lineWidth: 0 });
|
|
11620
|
-
await
|
|
11761
|
+
await writeFile4(join6(vaultDir, "vault.yml"), content, "utf8");
|
|
11621
11762
|
}
|
|
11622
11763
|
async function downloadPluginFiles(vaultDir, vaultSyncFn) {
|
|
11623
11764
|
const pluginJsonPath = join6(vaultDir, ".claude", "plugins", "onebrain", ".claude-plugin", "plugin.json");
|
|
@@ -11706,7 +11847,7 @@ async function registerPlugin(vaultDir, installedPluginsPath) {
|
|
|
11706
11847
|
const tmpPath = `${installedPluginsPath}.tmp`;
|
|
11707
11848
|
try {
|
|
11708
11849
|
await mkdir3(dirname3(installedPluginsPath), { recursive: true });
|
|
11709
|
-
await
|
|
11850
|
+
await writeFile4(tmpPath, JSON.stringify(data, null, 4), "utf8");
|
|
11710
11851
|
await rename3(tmpPath, installedPluginsPath);
|
|
11711
11852
|
} catch (err) {
|
|
11712
11853
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -11817,7 +11958,7 @@ async function installObsidianPlugins(vaultDir, opts) {
|
|
|
11817
11958
|
throw new Error(`HTTP ${resp.status}`);
|
|
11818
11959
|
}
|
|
11819
11960
|
const buf = await resp.arrayBuffer();
|
|
11820
|
-
await
|
|
11961
|
+
await writeFile4(join6(pluginDir, assetName), Buffer.from(buf));
|
|
11821
11962
|
} catch (err) {
|
|
11822
11963
|
if (assetName === "styles.css")
|
|
11823
11964
|
continue;
|
|
@@ -12201,7 +12342,7 @@ async function checkpointCommand(mode, token, vaultRoot) {
|
|
|
12201
12342
|
// src/commands/internal/migrate.ts
|
|
12202
12343
|
init_lib();
|
|
12203
12344
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
12204
|
-
import { readFile as readFile4, readdir as readdir3, writeFile as
|
|
12345
|
+
import { readFile as readFile4, readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
|
|
12205
12346
|
import { join as join8 } from "path";
|
|
12206
12347
|
function parseFrontmatterWithRest(rawText) {
|
|
12207
12348
|
const text = rawText.replace(/\r\n/g, `
|
|
@@ -12258,7 +12399,7 @@ async function runBackfillRecapped(logsFolder, cutoffDate) {
|
|
|
12258
12399
|
const files = await listMdFiles(monthPath);
|
|
12259
12400
|
for (const fname of files) {
|
|
12260
12401
|
const fpath = join8(monthPath, fname);
|
|
12261
|
-
if (fname.includes("-
|
|
12402
|
+
if (!fname.includes("-session-")) {
|
|
12262
12403
|
continue;
|
|
12263
12404
|
}
|
|
12264
12405
|
if (cutoffDate) {
|
|
@@ -12285,7 +12426,7 @@ async function runBackfillRecapped(logsFolder, cutoffDate) {
|
|
|
12285
12426
|
const updatedContent = `---
|
|
12286
12427
|
${updatedFm}---
|
|
12287
12428
|
${rest}`;
|
|
12288
|
-
await
|
|
12429
|
+
await writeFile5(fpath, updatedContent, "utf8");
|
|
12289
12430
|
backfilled++;
|
|
12290
12431
|
} catch (error) {
|
|
12291
12432
|
process.stderr.write(`migrate: error processing ${fname}: ${error}
|
|
@@ -12355,7 +12496,7 @@ async function listMdFiles2(dir) {
|
|
|
12355
12496
|
}
|
|
12356
12497
|
async function hasManualSessionLog(monthDir, date) {
|
|
12357
12498
|
const files = await listMdFiles2(monthDir);
|
|
12358
|
-
const sessionLogs = files.filter((f2) => f2.startsWith(date) &&
|
|
12499
|
+
const sessionLogs = files.filter((f2) => f2.startsWith(date) && f2.includes("-session-") && f2.endsWith(".md"));
|
|
12359
12500
|
for (const logName of sessionLogs) {
|
|
12360
12501
|
try {
|
|
12361
12502
|
const content = await readFile5(join9(monthDir, logName), "utf8");
|
|
@@ -12451,7 +12592,7 @@ init_register_hooks();
|
|
|
12451
12592
|
|
|
12452
12593
|
// src/commands/internal/session-init.ts
|
|
12453
12594
|
init_lib();
|
|
12454
|
-
import { unlink as
|
|
12595
|
+
import { unlink as unlink3 } from "fs/promises";
|
|
12455
12596
|
import { tmpdir as osTmpdir2 } from "os";
|
|
12456
12597
|
import { join as join10 } from "path";
|
|
12457
12598
|
var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
@@ -12564,7 +12705,7 @@ async function cleanStaleStateFile(token, tmpDir) {
|
|
|
12564
12705
|
const mtimeMs = mtime instanceof Date ? mtime.getTime() : Number(mtime) * 1000;
|
|
12565
12706
|
if (mtimeMs < processStartMs) {
|
|
12566
12707
|
try {
|
|
12567
|
-
await
|
|
12708
|
+
await unlink3(stateFile);
|
|
12568
12709
|
} catch {}
|
|
12569
12710
|
}
|
|
12570
12711
|
} catch {}
|
|
@@ -12848,8 +12989,8 @@ function patchUtf8(stream) {
|
|
|
12848
12989
|
}
|
|
12849
12990
|
|
|
12850
12991
|
// src/index.ts
|
|
12851
|
-
var VERSION = "2.1.
|
|
12852
|
-
var RELEASE_DATE = "2026-05-
|
|
12992
|
+
var VERSION = "2.1.11";
|
|
12993
|
+
var RELEASE_DATE = "2026-05-06";
|
|
12853
12994
|
patchUtf8(process.stdout);
|
|
12854
12995
|
patchUtf8(process.stderr);
|
|
12855
12996
|
var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
|