stellavault 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -161
- package/SECURITY.md +23 -0
- package/dist/stellavault.js +196 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,249 +2,297 @@
|
|
|
2
2
|
|
|
3
3
|
> **Drop anything. It compiles itself into knowledge.** Claude remembers everything you know.
|
|
4
4
|
|
|
5
|
-
Self-compiling
|
|
5
|
+
Self-compiling knowledge base with 3D neural graph, AI-powered search, and spaced repetition — available as a **desktop app**, **CLI**, **Obsidian plugin**, and **MCP server**. Your vault files are never modified.
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="images/screenshots/graph-main-2.png" alt="3D Knowledge Graph" width="800" />
|
|
9
9
|
<br><em>Your vault as a neural network. Local-first, no cloud required.</em>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Three Ways to Use Stellavault
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
Any input → auto-classify → raw/ → compile → wiki → connected knowledge
|
|
17
|
-
```
|
|
18
|
-
PDF, DOCX, PPTX, XLSX, YouTube (with transcript), URL, text — everything goes through the same pipeline. You never manually organize.
|
|
14
|
+
### 1. Desktop App (Recommended)
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
Download and run — no terminal needed.
|
|
17
|
+
|
|
18
|
+
| Platform | Download | Size |
|
|
19
|
+
|----------|----------|------|
|
|
20
|
+
| **Windows x64** | [Stellavault-win32-x64-0.1.0.zip](https://github.com/Evanciel/stellavault/releases/download/desktop-v0.1.0/Stellavault-win32-x64-0.1.0.zip) | 116 MB |
|
|
21
|
+
| **Linux x64** | [Stellavault-linux-x64-0.1.0.zip](https://github.com/Evanciel/stellavault/releases/download/desktop-v0.1.0/Stellavault-linux-x64-0.1.0.zip) | 107 MB |
|
|
22
|
+
| macOS | Coming soon (requires Apple code signing) | — |
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
**What you get:**
|
|
25
|
+
- Full markdown editor with WYSIWYG toolbar
|
|
26
|
+
- File tree sidebar with search filter
|
|
27
|
+
- `[[wikilink]]` autocomplete as you type
|
|
28
|
+
- Multi-tab editing with Ctrl+S save
|
|
29
|
+
- 3D knowledge graph panel
|
|
30
|
+
- AI panel — semantic search, vault stats, re-index
|
|
31
|
+
- Backlinks panel — see who links to your note
|
|
32
|
+
- Quick Switcher (Ctrl+P) and Command Palette (Ctrl+Shift+P)
|
|
33
|
+
- Dark/light theme
|
|
34
|
+
|
|
35
|
+
### 2. CLI + Web Graph
|
|
36
|
+
|
|
37
|
+
For developers and power users.
|
|
27
38
|
|
|
28
39
|
```bash
|
|
29
|
-
npm install -g stellavault
|
|
30
|
-
stellavault init
|
|
31
|
-
stellavault graph
|
|
40
|
+
npm install -g stellavault # or: npx stellavault
|
|
41
|
+
stellavault init # Interactive setup wizard
|
|
42
|
+
stellavault graph # Launch 3D graph in browser
|
|
32
43
|
```
|
|
33
44
|
|
|
34
|
-
> **Prerequisites**: Node.js 20
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
> **Prerequisites**: Node.js 20+. Run `stellavault doctor` to diagnose setup issues.
|
|
46
|
+
|
|
47
|
+
### 3. Obsidian Plugin
|
|
48
|
+
|
|
49
|
+
Use Stellavault intelligence inside Obsidian.
|
|
50
|
+
|
|
51
|
+
1. Download from [stellavault-obsidian releases](https://github.com/Evanciel/stellavault-obsidian/releases/latest)
|
|
52
|
+
2. Place `main.js`, `manifest.json`, `styles.css` in `.obsidian/plugins/stellavault/`
|
|
53
|
+
3. Enable in Settings → Community plugins
|
|
54
|
+
4. Start the API server: `npx stellavault graph` in your vault folder
|
|
55
|
+
|
|
56
|
+
**Features:** Semantic search modal, memory decay sidebar, learning path suggestions, auto-indexing on file changes.
|
|
57
|
+
|
|
58
|
+
---
|
|
37
59
|
|
|
38
60
|
## The Pipeline
|
|
39
61
|
|
|
40
62
|
```
|
|
41
63
|
Capture ──→ Organize ──→ Distill ──→ Express
|
|
42
64
|
|
|
43
|
-
|
|
44
|
-
→ auto-extract text # unpdf, mammoth, yt-dlp
|
|
45
|
-
→ raw/ (fleeting) # Zettelkasten inbox
|
|
46
|
-
→ compile → _wiki/ # Auto: concepts + backlinks
|
|
47
|
-
→ stellavault draft "topic" # Blog, report, or outline
|
|
65
|
+
Drop anything → auto-extract → raw/ → compile → _wiki/ → draft
|
|
48
66
|
```
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
Inspired by [Karpathy's self-compiling knowledge](https://karpathy.ai/) architecture. Every input flows through the same four-stage pipeline.
|
|
69
|
+
|
|
70
|
+
### Ingest Anything (14 formats)
|
|
51
71
|
|
|
52
72
|
| Input | How |
|
|
53
73
|
|-------|-----|
|
|
54
|
-
| PDF, DOCX, PPTX, XLSX | `stellavault ingest report.pdf`
|
|
55
|
-
| JSON, CSV, XML, YAML | `stellavault ingest data.json`
|
|
56
|
-
| HTML, RTF | `stellavault ingest page.html` — clean text extraction |
|
|
74
|
+
| PDF, DOCX, PPTX, XLSX | `stellavault ingest report.pdf` |
|
|
75
|
+
| JSON, CSV, XML, YAML, HTML, RTF | `stellavault ingest data.json` |
|
|
57
76
|
| YouTube | `stellavault ingest https://youtu.be/...` — transcript + timestamps |
|
|
58
|
-
| URL | `stellavault ingest https://...` — HTML →
|
|
77
|
+
| URL | `stellavault ingest https://...` — HTML → markdown |
|
|
59
78
|
| Text | `stellavault ingest "quick thought"` |
|
|
60
79
|
| Folder | `stellavault ingest ./papers/` — batch all files |
|
|
61
|
-
| Web UI | Drag & drop files
|
|
80
|
+
| Desktop / Web UI | Drag & drop files directly |
|
|
62
81
|
|
|
63
82
|
### Express: Get Knowledge Out
|
|
64
83
|
|
|
65
84
|
```bash
|
|
66
85
|
stellavault draft "AI" # Rule-based scaffold (free)
|
|
67
|
-
stellavault draft "AI" --ai # Claude API writes full draft
|
|
68
|
-
stellavault draft "AI" --format report # Formal report
|
|
69
|
-
stellavault draft --format
|
|
86
|
+
stellavault draft "AI" --ai # Claude API writes full draft
|
|
87
|
+
stellavault draft "AI" --format report # Formal report
|
|
88
|
+
stellavault draft --format instagram # Social media format
|
|
70
89
|
```
|
|
71
90
|
|
|
72
|
-
|
|
91
|
+
## MCP Integration (21 Tools)
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
Session → session-save → daily-log → flush → wiki
|
|
78
|
-
↑ ↓
|
|
79
|
-
└──── Claude reads wiki via MCP (20 tools) ←─┘
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Every conversation makes your knowledge base smarter:
|
|
93
|
+
Connect Stellavault to Claude Code or Claude Desktop:
|
|
83
94
|
|
|
84
95
|
```bash
|
|
85
|
-
|
|
86
|
-
echo "Decided to use JWT. Lesson: never store tokens in localStorage" | stellavault session-save
|
|
87
|
-
|
|
88
|
-
# Flush daily logs → extract concepts → rebuild wiki
|
|
89
|
-
stellavault flush
|
|
90
|
-
|
|
91
|
-
# Or set up Claude Code hooks for full automation
|
|
92
|
-
# See: docs/hooks-setup.md
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Daily Commands
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
stellavault ask "What did I learn about X?" # Q&A from vault
|
|
99
|
-
stellavault brief # Morning knowledge briefing
|
|
100
|
-
stellavault decay # What's fading from memory?
|
|
101
|
-
stellavault lint # Health score (0-100)
|
|
102
|
-
stellavault learn # AI learning path
|
|
103
|
-
stellavault flush # Daily logs → wiki compilation
|
|
104
|
-
stellavault digest --visual # Weekly Mermaid chart report
|
|
96
|
+
claude mcp add stellavault -- stellavault serve
|
|
105
97
|
```
|
|
106
98
|
|
|
107
|
-
|
|
99
|
+
Claude can now search, ask, draft, lint, and analyze your vault directly.
|
|
108
100
|
|
|
109
101
|
| Tool | What it does |
|
|
110
102
|
|------|-------------|
|
|
111
|
-
| `search` | Hybrid
|
|
112
|
-
| `ask` | Q&A with
|
|
113
|
-
| `generate-draft` |
|
|
114
|
-
| `get-
|
|
115
|
-
| `get-related` | Semantically similar documents |
|
|
116
|
-
| `list-topics` | Topic cloud |
|
|
117
|
-
| `get-decay-status` | Memory decay report |
|
|
118
|
-
| `get-morning-brief` | Daily knowledge briefing |
|
|
119
|
-
| `get-learning-path` | AI learning recommendations |
|
|
103
|
+
| `search` | Hybrid BM25 + vector + RRF search |
|
|
104
|
+
| `ask` | Q&A with vault-grounded answers |
|
|
105
|
+
| `generate-draft` | AI drafts from your knowledge |
|
|
106
|
+
| `get-decay-status` | Memory decay report (FSRS) |
|
|
120
107
|
| `detect-gaps` | Knowledge gap analysis |
|
|
121
|
-
| `get-
|
|
122
|
-
| `link-code` | Code-knowledge connections |
|
|
108
|
+
| `get-learning-path` | Personalized review recommendations |
|
|
123
109
|
| `create-knowledge-node` | AI creates wiki-quality notes |
|
|
124
|
-
| `
|
|
125
|
-
|
|
|
126
|
-
| `create-snapshot` / `load-snapshot` | Context snapshots |
|
|
127
|
-
| `generate-claude-md` | Auto-generate CLAUDE.md |
|
|
128
|
-
| `export` | JSON/CSV export |
|
|
129
|
-
| `federated-search` | P2P federated search |
|
|
110
|
+
| `federated-search` | P2P search across connected vaults |
|
|
111
|
+
| + 13 more | Documents, topics, decisions, snapshots, export |
|
|
130
112
|
|
|
131
|
-
##
|
|
113
|
+
## Intelligence
|
|
132
114
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
stellavault
|
|
136
|
-
stellavault
|
|
137
|
-
|
|
138
|
-
|
|
115
|
+
| Feature | Command |
|
|
116
|
+
|---------|---------|
|
|
117
|
+
| Memory Decay | `stellavault decay` — what you're forgetting (FSRS) |
|
|
118
|
+
| Gap Detection | `stellavault gaps` — weak connections between topics |
|
|
119
|
+
| Contradictions | `stellavault contradictions` — conflicting statements |
|
|
120
|
+
| Duplicates | `stellavault duplicates` — redundant notes |
|
|
121
|
+
| Learning Path | `stellavault learn` — AI review recommendations |
|
|
122
|
+
| Health Check | `stellavault lint` — overall knowledge score |
|
|
123
|
+
| Daily Brief | `stellavault brief` — morning knowledge briefing |
|
|
124
|
+
| Weekly Digest | `stellavault digest --visual` — Mermaid chart report |
|
|
139
125
|
|
|
140
|
-
##
|
|
126
|
+
## Self-Evolving Memory
|
|
141
127
|
|
|
142
|
-
```bash
|
|
143
|
-
stellavault fleeting "raw idea" # → raw/
|
|
144
|
-
stellavault ingest report.pdf # → auto text extract → raw/
|
|
145
|
-
stellavault compile # → raw/ → _wiki/ (concepts + backlinks)
|
|
146
|
-
stellavault promote note.md --to permanent # Upgrade stage
|
|
147
|
-
stellavault autopilot # Full cycle: inbox → compile → lint → archive
|
|
148
128
|
```
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- **Frontmatter-first scanning**: 10x token reduction
|
|
153
|
-
- **Configurable folders**: override raw/_wiki/_literature/ in `.stellavault.json`
|
|
154
|
-
|
|
155
|
-
```json
|
|
156
|
-
{
|
|
157
|
-
"vaultPath": "/path/to/vault",
|
|
158
|
-
"folders": {
|
|
159
|
-
"fleeting": "01-Inbox",
|
|
160
|
-
"literature": "02-Reading",
|
|
161
|
-
"permanent": "03-Notes",
|
|
162
|
-
"wiki": "04-Wiki"
|
|
163
|
-
}
|
|
164
|
-
}
|
|
129
|
+
Session → session-save → daily-log → flush → wiki
|
|
130
|
+
↑ ↓
|
|
131
|
+
└──── Claude reads wiki via MCP (21 tools) ←─┘
|
|
165
132
|
```
|
|
166
133
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
| Feature | Command |
|
|
170
|
-
|---------|---------|
|
|
171
|
-
| FSRS Decay | `sv decay` — spaced repetition memory tracking |
|
|
172
|
-
| Gap Detection | `sv gaps` — missing connections between topics |
|
|
173
|
-
| Contradictions | `sv contradictions` — conflicting statements |
|
|
174
|
-
| Duplicates | `sv duplicates` — redundant notes |
|
|
175
|
-
| Learning Path | `sv learn` — AI review recommendations |
|
|
176
|
-
| Code Linker | MCP `link-code` — connect code to knowledge |
|
|
177
|
-
|
|
178
|
-
## 3D Visualization
|
|
179
|
-
|
|
180
|
-
- Neural graph with cluster coloring
|
|
181
|
-
- Constellation view (MST star patterns)
|
|
182
|
-
- Heatmap overlay (activity score)
|
|
183
|
-
- Timeline slider (creation/modification filter)
|
|
184
|
-
- Decay overlay (fading knowledge)
|
|
185
|
-
- **Multiverse view** — your vault as a universe in a P2P network
|
|
186
|
-
- Dark/Light theme
|
|
187
|
-
- Mobile responsive + PWA installable
|
|
188
|
-
|
|
189
|
-
## Multiverse — P2P Knowledge Federation
|
|
190
|
-
|
|
191
|
-
<p align="center">
|
|
192
|
-
<img src="images/screenshots/multiverse-view.png" alt="Multiverse View" width="800" />
|
|
193
|
-
<br><em>"Your universe floats alone — for now."</em>
|
|
194
|
-
</p>
|
|
134
|
+
Every conversation makes your knowledge base smarter. Set up [Claude Code hooks](docs/hooks-setup.md) for full automation.
|
|
195
135
|
|
|
196
|
-
|
|
136
|
+
## Zettelkasten Workflow
|
|
197
137
|
|
|
198
|
-
|
|
138
|
+
Three-stage flow: **fleeting → literature → permanent** (Luhmann + Karpathy).
|
|
199
139
|
|
|
200
|
-
**From the CLI**:
|
|
201
140
|
```bash
|
|
202
|
-
stellavault
|
|
203
|
-
stellavault
|
|
141
|
+
stellavault fleeting "raw idea" # → raw/
|
|
142
|
+
stellavault ingest report.pdf # → auto-extract → raw/
|
|
143
|
+
stellavault compile # → raw/ → _wiki/ (concepts + backlinks)
|
|
144
|
+
stellavault promote note.md --to permanent # Upgrade stage
|
|
145
|
+
stellavault autopilot # Full cycle: inbox → compile → lint
|
|
204
146
|
```
|
|
205
147
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
148
|
+
Auto-assigned Luhmann index codes, frontmatter-first scanning, configurable folders.
|
|
149
|
+
|
|
150
|
+
## P2P Federation (Multiverse)
|
|
151
|
+
|
|
152
|
+
Your vault is a universe. Connect with others through P2P federation.
|
|
153
|
+
|
|
154
|
+
- **Hyperswarm P2P** — NAT-traversal, no central server
|
|
155
|
+
- **Embeddings only** — original text never leaves your machine
|
|
209
156
|
- **Differential privacy** — mathematical privacy guarantees
|
|
210
|
-
- **Trust & reputation** — good knowledge earns credits
|
|
211
|
-
- **Federated search** — search across connected vaults via MCP
|
|
212
157
|
|
|
213
|
-
|
|
158
|
+
In the desktop app or web UI, click the **Federation badge** in the header to join/leave the Stella Network.
|
|
214
159
|
|
|
215
160
|
## Tech Stack
|
|
216
161
|
|
|
217
162
|
| Layer | Tech |
|
|
218
163
|
|-------|------|
|
|
164
|
+
| Desktop | Electron + React + TipTap + Zustand |
|
|
219
165
|
| Runtime | Node.js 20+ (ESM, TypeScript) |
|
|
220
|
-
| Vector Store | SQLite-vec (local,
|
|
221
|
-
| Embedding |
|
|
166
|
+
| Vector Store | SQLite-vec (local, zero config) |
|
|
167
|
+
| Embedding | MiniLM-L12-v2 (local, 50+ languages) |
|
|
222
168
|
| Search | BM25 + Cosine + RRF Fusion |
|
|
223
169
|
| File Parsing | unpdf, mammoth, officeparser, SheetJS |
|
|
224
170
|
| Memory | FSRS (Free Spaced Repetition Scheduler) |
|
|
225
171
|
| 3D | React Three Fiber + Three.js |
|
|
226
172
|
| AI | MCP (Model Context Protocol) + Anthropic SDK |
|
|
173
|
+
| P2P | Hyperswarm (optional) |
|
|
227
174
|
|
|
228
175
|
## Full Feature List
|
|
229
176
|
|
|
230
177
|
| Category | Features |
|
|
231
178
|
|----------|----------|
|
|
232
|
-
| **
|
|
179
|
+
| **Desktop** | File tree sidebar, multi-tab editor, [[wikilink]] autocomplete, Quick Switcher, Command Palette, 3D graph panel, AI panel, backlinks, dark/light theme |
|
|
180
|
+
| **Capture** | 14 formats (PDF/DOCX/PPTX/XLSX/JSON/CSV/XML/HTML/YAML/RTF/YouTube/URL/text), batch folders, drag & drop, voice capture, Quick Capture |
|
|
233
181
|
| **Organize** | Zettelkasten 3-stage, auto index codes, wikilink auto-connect, configurable folders |
|
|
234
182
|
| **Distill** | compile (raw→wiki), lint (health score), gaps, contradictions, duplicates |
|
|
235
|
-
| **Express** | draft (blog/report/outline/instagram/thread/script), blueprint, --ai
|
|
183
|
+
| **Express** | draft (blog/report/outline/instagram/thread/script), blueprint, --ai mode |
|
|
236
184
|
| **Memory** | FSRS decay, session-save, flush, compounding loop, ADR templates |
|
|
237
185
|
| **Search** | hybrid (BM25+vector+RRF), multilingual 50+, ask Q&A, quotes mode |
|
|
238
|
-
| **Visualize** | 3D graph, heatmap, timeline,
|
|
239
|
-
| **AI
|
|
240
|
-
| **
|
|
241
|
-
| **CLI** | 40+ commands, `sv` alias,
|
|
186
|
+
| **Visualize** | 3D graph, heatmap, timeline, constellation view, decay overlay, multiverse |
|
|
187
|
+
| **AI** | 21 MCP tools, Claude Code hooks, Anthropic SDK |
|
|
188
|
+
| **Federation** | Hyperswarm P2P, embedding-only sharing, differential privacy |
|
|
189
|
+
| **CLI** | 40+ commands, `sv` alias, `stellavault doctor` diagnostics |
|
|
242
190
|
|
|
243
|
-
##
|
|
191
|
+
## Getting Started Guide
|
|
192
|
+
|
|
193
|
+
### Desktop App (easiest)
|
|
194
|
+
|
|
195
|
+
1. **Download** from [Releases](https://github.com/Evanciel/stellavault/releases/latest)
|
|
196
|
+
2. **Unzip** to any folder
|
|
197
|
+
3. **Run** `stellavault.exe` (Windows) — first launch asks you to pick your notes folder
|
|
198
|
+
4. **Explore** — your notes appear in the sidebar, click to open in the editor
|
|
199
|
+
5. **Search** — press `Ctrl+P` to quick-switch between notes, or open the AI panel (✦ button) for semantic search
|
|
200
|
+
|
|
201
|
+
### CLI (for developers)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Step 1: Install
|
|
205
|
+
npm install -g stellavault
|
|
206
|
+
|
|
207
|
+
# Step 2: Setup (interactive wizard)
|
|
208
|
+
stellavault init
|
|
209
|
+
# → Asks for vault path → indexes all .md files → tests search
|
|
210
|
+
|
|
211
|
+
# Step 3: Daily use
|
|
212
|
+
stellavault search "machine learning" # Find notes
|
|
213
|
+
stellavault ingest paper.pdf # Add new knowledge
|
|
214
|
+
stellavault graph # Open 3D graph in browser
|
|
215
|
+
stellavault brief # Morning briefing
|
|
216
|
+
stellavault decay # What are you forgetting?
|
|
217
|
+
|
|
218
|
+
# Step 4: Connect to Claude
|
|
219
|
+
claude mcp add stellavault -- stellavault serve
|
|
220
|
+
# → Claude can now read your vault via MCP
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Obsidian Plugin
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Step 1: Start the API server (keep running)
|
|
227
|
+
npx stellavault graph
|
|
244
228
|
|
|
245
|
-
|
|
229
|
+
# Step 2: Install plugin
|
|
230
|
+
# Download main.js + manifest.json + styles.css from:
|
|
231
|
+
# https://github.com/Evanciel/stellavault-obsidian/releases/latest
|
|
232
|
+
# Place in: <vault>/.obsidian/plugins/stellavault/
|
|
233
|
+
|
|
234
|
+
# Step 3: Enable in Settings → Community Plugins → Stellavault
|
|
235
|
+
|
|
236
|
+
# Step 4: Use
|
|
237
|
+
# - Click brain icon (🧠) for semantic search
|
|
238
|
+
# - Cmd+Shift+D for memory decay panel
|
|
239
|
+
# - Cmd+Shift+L for learning path suggestions
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Quick Reference
|
|
243
|
+
|
|
244
|
+
| Action | Desktop | CLI | Obsidian |
|
|
245
|
+
|--------|---------|-----|----------|
|
|
246
|
+
| Search notes | Ctrl+P or AI panel | `stellavault search "query"` | 🧠 icon |
|
|
247
|
+
| Add a note | + Note button | `stellavault ingest "text"` | Normal editing |
|
|
248
|
+
| See 3D graph | ◉ button | `stellavault graph` | N/A |
|
|
249
|
+
| Check memory decay | AI panel → Memory | `stellavault decay` | Decay sidebar |
|
|
250
|
+
| Find duplicates | AI panel → Stats | `stellavault duplicates` | N/A |
|
|
251
|
+
| Generate draft | N/A (v0.2) | `stellavault draft "topic"` | N/A |
|
|
252
|
+
| Connect to Claude | N/A (v0.2) | `claude mcp add stellavault` | N/A |
|
|
253
|
+
|
|
254
|
+
### Configuration
|
|
255
|
+
|
|
256
|
+
All settings live in `~/.stellavault.json`:
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"vaultPath": "/path/to/your/notes",
|
|
261
|
+
"dbPath": "~/.stellavault/index.db",
|
|
262
|
+
"embedding": { "model": "local", "localModel": "all-MiniLM-L6-v2" },
|
|
263
|
+
"mcp": { "mode": "stdio", "port": 3333 }
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Run `stellavault doctor` anytime to check your setup.
|
|
268
|
+
|
|
269
|
+
### Keyboard Shortcuts (Desktop)
|
|
270
|
+
|
|
271
|
+
| Shortcut | Action |
|
|
272
|
+
|----------|--------|
|
|
273
|
+
| `Ctrl+P` | Quick Switcher (fuzzy file search) |
|
|
274
|
+
| `Ctrl+Shift+P` | Command Palette (all actions) |
|
|
275
|
+
| `Ctrl+S` | Save current note |
|
|
276
|
+
| `Ctrl+B` | Toggle bold |
|
|
277
|
+
| `Ctrl+I` | Toggle italic |
|
|
278
|
+
| `Ctrl+E` | Toggle inline code |
|
|
279
|
+
| `[[` | Wikilink autocomplete |
|
|
280
|
+
|
|
281
|
+
## Troubleshooting
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
stellavault doctor # Check config, vault, DB, model, Node version
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Common issues:
|
|
288
|
+
- **"Command not found"** → Reinstall: `npm i -g stellavault@latest`
|
|
289
|
+
- **"API server not found"** → Start the server: `npx stellavault graph`
|
|
290
|
+
- **Empty graph** → Run `stellavault index` to re-index your vault
|
|
291
|
+
- **Slow first run** → The AI model downloads ~30MB on first use (one time only)
|
|
292
|
+
|
|
293
|
+
## Security
|
|
246
294
|
|
|
247
|
-
See [SECURITY.md](SECURITY.md)
|
|
295
|
+
Local-first — no data leaves your machine unless you explicitly use `--ai` (Anthropic API). Vault files are never modified. See [SECURITY.md](SECURITY.md).
|
|
248
296
|
|
|
249
297
|
## License
|
|
250
298
|
|
|
@@ -252,6 +300,7 @@ MIT — full source code available for audit.
|
|
|
252
300
|
|
|
253
301
|
## Links
|
|
254
302
|
|
|
303
|
+
- **[Download Desktop App](https://github.com/Evanciel/stellavault/releases/latest)**
|
|
255
304
|
- [Landing Page](https://evanciel.github.io/stellavault/)
|
|
256
305
|
- [Obsidian Plugin](https://github.com/Evanciel/stellavault-obsidian)
|
|
257
306
|
- [npm](https://www.npmjs.com/package/stellavault)
|
package/SECURITY.md
CHANGED
|
@@ -39,6 +39,29 @@ Stellavault is **local-first**. Your knowledge stays on your machine.
|
|
|
39
39
|
- **URL validation**: Image URLs restricted to `https://` scheme
|
|
40
40
|
- **SSRF protection**: Private/local IP addresses blocked for URL ingest
|
|
41
41
|
|
|
42
|
+
## Desktop App Security (Electron)
|
|
43
|
+
|
|
44
|
+
- **Context Isolation**: enabled — renderer cannot access Node.js APIs
|
|
45
|
+
- **Sandbox**: enabled — renderer runs with reduced OS privileges
|
|
46
|
+
- **Node Integration**: disabled — no `require()` in renderer
|
|
47
|
+
- **IPC Allowlist**: explicit channel whitelist in preload (17 channels)
|
|
48
|
+
- **Path Validation**: all vault filesystem IPC handlers validate paths stay inside vault root
|
|
49
|
+
- **Auth Token**: API server generates per-session random token for all mutating endpoints
|
|
50
|
+
- **CSP**: strict Content Security Policy (no unsafe-eval in production)
|
|
51
|
+
|
|
52
|
+
## Federation Security
|
|
53
|
+
|
|
54
|
+
- **Embeddings only**: original text never transmitted over the network
|
|
55
|
+
- **Buffer limits**: 1MB per connection, 64KB per message
|
|
56
|
+
- **Message validation**: schema checking on all incoming messages
|
|
57
|
+
- **Leave authentication**: leave messages only accepted from the owning connection
|
|
58
|
+
- **Differential privacy**: noise added to shared embeddings
|
|
59
|
+
|
|
60
|
+
## Known Accepted Risks
|
|
61
|
+
|
|
62
|
+
- **LOW-03**: `data:` URIs allowed in desktop CSP for inline images in markdown editor
|
|
63
|
+
- **LOW-05**: Cloud sync uses Bearer token instead of AWS Signature v4 (R2-specific)
|
|
64
|
+
|
|
42
65
|
## Reporting Vulnerabilities
|
|
43
66
|
|
|
44
67
|
Please report security issues to: https://github.com/Evanciel/stellavault/issues (label: security)
|
package/dist/stellavault.js
CHANGED
|
@@ -433,10 +433,15 @@ function createLocalEmbedder(modelName = "nomic-embed-text-v1.5") {
|
|
|
433
433
|
const output = await pipeline(text, { pooling: "mean", normalize: true });
|
|
434
434
|
return Array.from(output.data).slice(0, dims);
|
|
435
435
|
},
|
|
436
|
-
async embedBatch(texts) {
|
|
436
|
+
async embedBatch(texts, batchSize = 32) {
|
|
437
437
|
const results = [];
|
|
438
|
-
for (
|
|
439
|
-
|
|
438
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
439
|
+
const batch = texts.slice(i, i + batchSize);
|
|
440
|
+
const output = await pipeline(batch, { pooling: "mean", normalize: true });
|
|
441
|
+
const flat = output.data;
|
|
442
|
+
for (let j = 0; j < batch.length; j++) {
|
|
443
|
+
results.push(Array.from(flat.slice(j * dims, (j + 1) * dims)));
|
|
444
|
+
}
|
|
440
445
|
}
|
|
441
446
|
return results;
|
|
442
447
|
},
|
|
@@ -1971,6 +1976,104 @@ function extractAutoTags(content, type) {
|
|
|
1971
1976
|
tags.add("analysis");
|
|
1972
1977
|
if (/일기|diary|journal|오늘|today|daily/.test(lc))
|
|
1973
1978
|
tags.add("journal");
|
|
1979
|
+
const stopWords = /* @__PURE__ */ new Set([
|
|
1980
|
+
"the",
|
|
1981
|
+
"a",
|
|
1982
|
+
"an",
|
|
1983
|
+
"and",
|
|
1984
|
+
"or",
|
|
1985
|
+
"but",
|
|
1986
|
+
"in",
|
|
1987
|
+
"on",
|
|
1988
|
+
"at",
|
|
1989
|
+
"to",
|
|
1990
|
+
"for",
|
|
1991
|
+
"of",
|
|
1992
|
+
"with",
|
|
1993
|
+
"by",
|
|
1994
|
+
"from",
|
|
1995
|
+
"is",
|
|
1996
|
+
"are",
|
|
1997
|
+
"was",
|
|
1998
|
+
"were",
|
|
1999
|
+
"be",
|
|
2000
|
+
"been",
|
|
2001
|
+
"being",
|
|
2002
|
+
"have",
|
|
2003
|
+
"has",
|
|
2004
|
+
"had",
|
|
2005
|
+
"do",
|
|
2006
|
+
"does",
|
|
2007
|
+
"did",
|
|
2008
|
+
"will",
|
|
2009
|
+
"would",
|
|
2010
|
+
"could",
|
|
2011
|
+
"should",
|
|
2012
|
+
"may",
|
|
2013
|
+
"might",
|
|
2014
|
+
"can",
|
|
2015
|
+
"this",
|
|
2016
|
+
"that",
|
|
2017
|
+
"these",
|
|
2018
|
+
"those",
|
|
2019
|
+
"it",
|
|
2020
|
+
"its",
|
|
2021
|
+
"they",
|
|
2022
|
+
"them",
|
|
2023
|
+
"their",
|
|
2024
|
+
"we",
|
|
2025
|
+
"our",
|
|
2026
|
+
"you",
|
|
2027
|
+
"your",
|
|
2028
|
+
"he",
|
|
2029
|
+
"she",
|
|
2030
|
+
"his",
|
|
2031
|
+
"her",
|
|
2032
|
+
"not",
|
|
2033
|
+
"no",
|
|
2034
|
+
"all",
|
|
2035
|
+
"each",
|
|
2036
|
+
"every",
|
|
2037
|
+
"both",
|
|
2038
|
+
"few",
|
|
2039
|
+
"more",
|
|
2040
|
+
"most",
|
|
2041
|
+
"other",
|
|
2042
|
+
"some",
|
|
2043
|
+
"such",
|
|
2044
|
+
"than",
|
|
2045
|
+
"too",
|
|
2046
|
+
"very",
|
|
2047
|
+
"just",
|
|
2048
|
+
"about",
|
|
2049
|
+
"above",
|
|
2050
|
+
"after",
|
|
2051
|
+
"before",
|
|
2052
|
+
"between",
|
|
2053
|
+
"into",
|
|
2054
|
+
"through",
|
|
2055
|
+
"during",
|
|
2056
|
+
"without",
|
|
2057
|
+
"also",
|
|
2058
|
+
"how",
|
|
2059
|
+
"what",
|
|
2060
|
+
"which",
|
|
2061
|
+
"who",
|
|
2062
|
+
"when",
|
|
2063
|
+
"where",
|
|
2064
|
+
"why",
|
|
2065
|
+
"if",
|
|
2066
|
+
"then"
|
|
2067
|
+
]);
|
|
2068
|
+
const wordFreq = /* @__PURE__ */ new Map();
|
|
2069
|
+
const words = content.toLowerCase().replace(/[^a-z가-힣\s]/g, " ").split(/\s+/);
|
|
2070
|
+
for (const w of words) {
|
|
2071
|
+
if (w.length < 3 || stopWords.has(w))
|
|
2072
|
+
continue;
|
|
2073
|
+
wordFreq.set(w, (wordFreq.get(w) ?? 0) + 1);
|
|
2074
|
+
}
|
|
2075
|
+
const topKeywords = [...wordFreq.entries()].filter(([, count]) => count >= 2).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([word]) => word);
|
|
2076
|
+
topKeywords.forEach((k) => tags.add(k));
|
|
1974
2077
|
return [...tags].slice(0, 15);
|
|
1975
2078
|
}
|
|
1976
2079
|
function cleanContent(content, type) {
|
|
@@ -2499,8 +2602,11 @@ var init_node = __esm({
|
|
|
2499
2602
|
break;
|
|
2500
2603
|
}
|
|
2501
2604
|
case "leave": {
|
|
2502
|
-
this.peers.
|
|
2503
|
-
|
|
2605
|
+
const legit = this.peers.get(msg.peerId);
|
|
2606
|
+
if (legit && legit.conn === conn) {
|
|
2607
|
+
this.peers.delete(msg.peerId);
|
|
2608
|
+
this.emit("peer_left", { peerId: msg.peerId });
|
|
2609
|
+
}
|
|
2504
2610
|
break;
|
|
2505
2611
|
}
|
|
2506
2612
|
}
|
|
@@ -4506,7 +4612,7 @@ function createMcpServer(options) {
|
|
|
4506
4612
|
const askTool = createAskTool(searchEngine, vaultPath);
|
|
4507
4613
|
const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
|
|
4508
4614
|
const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
|
|
4509
|
-
const server = new Server({ name: "stellavault", version: "0.
|
|
4615
|
+
const server = new Server({ name: "stellavault", version: "0.6.0" }, { capabilities: { tools: {} } });
|
|
4510
4616
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
4511
4617
|
tools: [
|
|
4512
4618
|
searchToolDef,
|
|
@@ -4847,6 +4953,7 @@ Author: ${pack2.author}`,
|
|
|
4847
4953
|
init_graph_data();
|
|
4848
4954
|
import express from "express";
|
|
4849
4955
|
import cors from "cors";
|
|
4956
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4850
4957
|
|
|
4851
4958
|
// packages/core/dist/intelligence/duplicate-detector.js
|
|
4852
4959
|
async function detectDuplicates(store, threshold = 0.88, limit = 20) {
|
|
@@ -4898,8 +5005,51 @@ function cosineSimilarity2(a, b) {
|
|
|
4898
5005
|
function createApiServer(options) {
|
|
4899
5006
|
const { store, searchEngine, port = 3333, vaultName = "", vaultPath = "", decayEngine, graphUiPath } = options;
|
|
4900
5007
|
const app = express();
|
|
4901
|
-
|
|
5008
|
+
const authToken = randomBytes2(32).toString("hex");
|
|
5009
|
+
app.use((_req, res, next) => {
|
|
5010
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
5011
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
5012
|
+
next();
|
|
5013
|
+
});
|
|
5014
|
+
const allowedOrigins = [
|
|
5015
|
+
"http://localhost:5173",
|
|
5016
|
+
"http://127.0.0.1:5173",
|
|
5017
|
+
`http://127.0.0.1:${port}`,
|
|
5018
|
+
`http://localhost:${port}`
|
|
5019
|
+
];
|
|
5020
|
+
app.use(cors({ origin: allowedOrigins }));
|
|
4902
5021
|
app.use(express.json());
|
|
5022
|
+
const rateLimiter = /* @__PURE__ */ new Map();
|
|
5023
|
+
function rateLimit(key, windowMs = 6e4, maxHits = 30) {
|
|
5024
|
+
const now = Date.now();
|
|
5025
|
+
const hits = (rateLimiter.get(key) ?? []).filter((t2) => now - t2 < windowMs);
|
|
5026
|
+
if (hits.length >= maxHits)
|
|
5027
|
+
return false;
|
|
5028
|
+
hits.push(now);
|
|
5029
|
+
rateLimiter.set(key, hits);
|
|
5030
|
+
return true;
|
|
5031
|
+
}
|
|
5032
|
+
function requireAuth(req, res, next) {
|
|
5033
|
+
const token = req.headers["x-stellavault-token"];
|
|
5034
|
+
if (token === authToken)
|
|
5035
|
+
return next();
|
|
5036
|
+
if (req.query.token === authToken)
|
|
5037
|
+
return next();
|
|
5038
|
+
res.status(403).json({ error: "Invalid or missing auth token. GET /api/token first." });
|
|
5039
|
+
}
|
|
5040
|
+
app.get("/api/token", (_req, res) => {
|
|
5041
|
+
res.json({ token: authToken });
|
|
5042
|
+
});
|
|
5043
|
+
function assertNotPrivateUrl(url) {
|
|
5044
|
+
const parsed = new URL(url);
|
|
5045
|
+
if (!["http:", "https:"].includes(parsed.protocol))
|
|
5046
|
+
throw new Error("Only http/https URLs allowed");
|
|
5047
|
+
const host = parsed.hostname.toLowerCase();
|
|
5048
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.startsWith("192.168.") || host.startsWith("10.") || // Fix: 172.16.0.0/12 covers 172.16.* through 172.31.*
|
|
5049
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(host) || host.startsWith("169.254.") || host === "0.0.0.0") {
|
|
5050
|
+
throw new Error("Internal URLs not allowed");
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
4903
5053
|
if (graphUiPath) {
|
|
4904
5054
|
app.use(express.static(graphUiPath, { index: "index.html", extensions: ["html"] }));
|
|
4905
5055
|
}
|
|
@@ -4935,7 +5085,7 @@ function createApiServer(options) {
|
|
|
4935
5085
|
res.json(reindexProgress);
|
|
4936
5086
|
});
|
|
4937
5087
|
let isReindexing = false;
|
|
4938
|
-
app.post("/api/reindex", async (_req, res) => {
|
|
5088
|
+
app.post("/api/reindex", requireAuth, async (_req, res) => {
|
|
4939
5089
|
if (isReindexing) {
|
|
4940
5090
|
res.json({ success: false, error: "Reindexing already in progress", progress: reindexProgress });
|
|
4941
5091
|
return;
|
|
@@ -4967,7 +5117,7 @@ function createApiServer(options) {
|
|
|
4967
5117
|
});
|
|
4968
5118
|
} catch (err) {
|
|
4969
5119
|
console.error("[reindex]", err);
|
|
4970
|
-
res.status(500).json({ error:
|
|
5120
|
+
res.status(500).json({ error: "Reindex failed" });
|
|
4971
5121
|
} finally {
|
|
4972
5122
|
isReindexing = false;
|
|
4973
5123
|
reindexProgress = { active: false, current: 0, total: 0, phase: "done" };
|
|
@@ -4975,8 +5125,8 @@ function createApiServer(options) {
|
|
|
4975
5125
|
});
|
|
4976
5126
|
app.get("/api/search", async (req, res) => {
|
|
4977
5127
|
try {
|
|
4978
|
-
const query = String(req.query.q || "");
|
|
4979
|
-
const limit = parseInt(String(req.query.limit || "10"), 10);
|
|
5128
|
+
const query = String(req.query.q || "").slice(0, 1e3);
|
|
5129
|
+
const limit = Math.min(parseInt(String(req.query.limit || "10"), 10), 100);
|
|
4980
5130
|
if (!query) {
|
|
4981
5131
|
res.json({ results: [], query: "" });
|
|
4982
5132
|
return;
|
|
@@ -5008,7 +5158,7 @@ function createApiServer(options) {
|
|
|
5008
5158
|
});
|
|
5009
5159
|
app.get("/api/document/:id", async (req, res) => {
|
|
5010
5160
|
try {
|
|
5011
|
-
const doc = await store.getDocument(req.params.id);
|
|
5161
|
+
const doc = await store.getDocument(String(req.params.id));
|
|
5012
5162
|
if (!doc) {
|
|
5013
5163
|
res.status(404).json({ error: "Not found" });
|
|
5014
5164
|
return;
|
|
@@ -5129,9 +5279,9 @@ function createApiServer(options) {
|
|
|
5129
5279
|
res.status(500).json({ error: "Internal server error" });
|
|
5130
5280
|
}
|
|
5131
5281
|
});
|
|
5132
|
-
app.put("/api/document/:id", async (req, res) => {
|
|
5282
|
+
app.put("/api/document/:id", requireAuth, async (req, res) => {
|
|
5133
5283
|
try {
|
|
5134
|
-
const
|
|
5284
|
+
const id = String(req.params.id);
|
|
5135
5285
|
const { title, content, tags } = req.body;
|
|
5136
5286
|
const doc = await store.getDocument(id);
|
|
5137
5287
|
if (!doc) {
|
|
@@ -5151,7 +5301,8 @@ function createApiServer(options) {
|
|
|
5151
5301
|
updated = updated.replace(/^title:\s*.+$/m, `title: "${title.replace(/"/g, "''")}"`);
|
|
5152
5302
|
}
|
|
5153
5303
|
if (tags) {
|
|
5154
|
-
const
|
|
5304
|
+
const safeTags = tags.map((t2) => t2.replace(/["\\\n\r\]]/g, "").trim()).filter(Boolean);
|
|
5305
|
+
const tagStr = `tags: [${safeTags.map((t2) => `"${t2}"`).join(", ")}]`;
|
|
5155
5306
|
if (updated.match(/^tags:\s*.+$/m)) {
|
|
5156
5307
|
updated = updated.replace(/^tags:\s*.+$/m, tagStr);
|
|
5157
5308
|
}
|
|
@@ -5176,12 +5327,12 @@ function createApiServer(options) {
|
|
|
5176
5327
|
res.json({ success: true, id, title: title ?? doc.title });
|
|
5177
5328
|
} catch (err) {
|
|
5178
5329
|
console.error("[edit]", err);
|
|
5179
|
-
res.status(500).json({ error:
|
|
5330
|
+
res.status(500).json({ error: "Edit failed" });
|
|
5180
5331
|
}
|
|
5181
5332
|
});
|
|
5182
|
-
app.delete("/api/document/:id", async (req, res) => {
|
|
5333
|
+
app.delete("/api/document/:id", requireAuth, async (req, res) => {
|
|
5183
5334
|
try {
|
|
5184
|
-
const
|
|
5335
|
+
const id = String(req.params.id);
|
|
5185
5336
|
const doc = await store.getDocument(id);
|
|
5186
5337
|
if (!doc) {
|
|
5187
5338
|
res.status(404).json({ error: "Document not found" });
|
|
@@ -5201,7 +5352,7 @@ function createApiServer(options) {
|
|
|
5201
5352
|
res.json({ success: true, id, deleted: doc.filePath });
|
|
5202
5353
|
} catch (err) {
|
|
5203
5354
|
console.error("[delete]", err);
|
|
5204
|
-
res.status(500).json({ error:
|
|
5355
|
+
res.status(500).json({ error: "Delete failed" });
|
|
5205
5356
|
}
|
|
5206
5357
|
});
|
|
5207
5358
|
app.get("/api/ask", async (req, res) => {
|
|
@@ -5223,7 +5374,7 @@ function createApiServer(options) {
|
|
|
5223
5374
|
res.status(500).json({ error: "Ask failed" });
|
|
5224
5375
|
}
|
|
5225
5376
|
});
|
|
5226
|
-
app.post("/api/ingest", async (req, res) => {
|
|
5377
|
+
app.post("/api/ingest", requireAuth, async (req, res) => {
|
|
5227
5378
|
try {
|
|
5228
5379
|
const { input, type, tags, title, stage, locale } = req.body;
|
|
5229
5380
|
if (locale) {
|
|
@@ -5260,6 +5411,12 @@ function createApiServer(options) {
|
|
|
5260
5411
|
}
|
|
5261
5412
|
}
|
|
5262
5413
|
} else if (input.startsWith("http")) {
|
|
5414
|
+
try {
|
|
5415
|
+
assertNotPrivateUrl(input);
|
|
5416
|
+
} catch (e) {
|
|
5417
|
+
res.status(400).json({ error: e.message });
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5263
5420
|
try {
|
|
5264
5421
|
const resp = await fetch(input, { signal: AbortSignal.timeout(8e3) });
|
|
5265
5422
|
const html = await resp.text();
|
|
@@ -5313,7 +5470,7 @@ function createApiServer(options) {
|
|
|
5313
5470
|
res.status(500).json({ error: "Ingest failed" });
|
|
5314
5471
|
}
|
|
5315
5472
|
});
|
|
5316
|
-
app.post("/api/ingest/file", async (req, res) => {
|
|
5473
|
+
app.post("/api/ingest/file", requireAuth, async (req, res) => {
|
|
5317
5474
|
try {
|
|
5318
5475
|
const multer = (await import("multer")).default;
|
|
5319
5476
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } });
|
|
@@ -5354,7 +5511,8 @@ function createApiServer(options) {
|
|
|
5354
5511
|
const { writeFileSync: writeFileSync21, unlinkSync } = await import("node:fs");
|
|
5355
5512
|
const { join: join30 } = await import("node:path");
|
|
5356
5513
|
const { tmpdir } = await import("node:os");
|
|
5357
|
-
const
|
|
5514
|
+
const safeName = (file.originalname ?? "upload").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 100);
|
|
5515
|
+
const tmpPath = join30(tmpdir(), `sv-upload-${Date.now()}-${safeName}`);
|
|
5358
5516
|
writeFileSync21(tmpPath, file.buffer);
|
|
5359
5517
|
const { extractFileContent: extractFileContent2, isBinaryFormat: isBinaryFormat2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
5360
5518
|
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -5416,7 +5574,7 @@ function createApiServer(options) {
|
|
|
5416
5574
|
wordCount: result.wordCount
|
|
5417
5575
|
});
|
|
5418
5576
|
} catch (err) {
|
|
5419
|
-
res.status(500).json({ error:
|
|
5577
|
+
res.status(500).json({ error: "Processing failed" });
|
|
5420
5578
|
}
|
|
5421
5579
|
});
|
|
5422
5580
|
} catch (err) {
|
|
@@ -5517,7 +5675,7 @@ function createApiServer(options) {
|
|
|
5517
5675
|
res.status(500).json({ error: "Internal server error" });
|
|
5518
5676
|
}
|
|
5519
5677
|
});
|
|
5520
|
-
app.post("/api/duplicates/merge", async (req, res) => {
|
|
5678
|
+
app.post("/api/duplicates/merge", requireAuth, async (req, res) => {
|
|
5521
5679
|
try {
|
|
5522
5680
|
const { docAId, docBId } = req.body;
|
|
5523
5681
|
if (!docAId || !docBId) {
|
|
@@ -5564,7 +5722,7 @@ ${removed.content}`;
|
|
|
5564
5722
|
res.status(500).json({ error: "Merge failed" });
|
|
5565
5723
|
}
|
|
5566
5724
|
});
|
|
5567
|
-
app.post("/api/gaps/create-bridge", async (req, res) => {
|
|
5725
|
+
app.post("/api/gaps/create-bridge", requireAuth, async (req, res) => {
|
|
5568
5726
|
try {
|
|
5569
5727
|
const { clusterA, clusterB } = req.body;
|
|
5570
5728
|
if (!clusterA || !clusterB) {
|
|
@@ -5715,7 +5873,6 @@ ${removed.content}`;
|
|
|
5715
5873
|
if (month)
|
|
5716
5874
|
monthlyActivity[month] = (monthlyActivity[month] ?? 0) + 1;
|
|
5717
5875
|
}
|
|
5718
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5719
5876
|
res.json({
|
|
5720
5877
|
name: vaultName || "Knowledge Vault",
|
|
5721
5878
|
stats: {
|
|
@@ -5768,7 +5925,6 @@ ${removed.content}`;
|
|
|
5768
5925
|
]
|
|
5769
5926
|
};
|
|
5770
5927
|
});
|
|
5771
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
5772
5928
|
res.json({
|
|
5773
5929
|
nodes: embedNodes,
|
|
5774
5930
|
edges: selectedEdges,
|
|
@@ -5787,7 +5943,7 @@ ${removed.content}`;
|
|
|
5787
5943
|
result: "",
|
|
5788
5944
|
output: ""
|
|
5789
5945
|
};
|
|
5790
|
-
app.post("/api/sync", async (_req, res) => {
|
|
5946
|
+
app.post("/api/sync", requireAuth, async (_req, res) => {
|
|
5791
5947
|
if (syncState.running) {
|
|
5792
5948
|
res.json({ success: false, error: "Sync already running", state: syncState });
|
|
5793
5949
|
return;
|
|
@@ -5807,7 +5963,7 @@ ${removed.content}`;
|
|
|
5807
5963
|
return;
|
|
5808
5964
|
}
|
|
5809
5965
|
syncState = { running: true, startedAt: (/* @__PURE__ */ new Date()).toISOString(), completedAt: "", result: "", output: "" };
|
|
5810
|
-
const child = spawn3("node", [syncScript], { cwd: syncDir, stdio: ["ignore", "pipe", "pipe"], shell:
|
|
5966
|
+
const child = spawn3("node", [syncScript], { cwd: syncDir, stdio: ["ignore", "pipe", "pipe"], shell: false });
|
|
5811
5967
|
let output = "";
|
|
5812
5968
|
child.stdout.on("data", (d) => {
|
|
5813
5969
|
output += d.toString();
|
|
@@ -5831,7 +5987,7 @@ ${removed.content}`;
|
|
|
5831
5987
|
app.get("/api/sync/status", (_req, res) => {
|
|
5832
5988
|
res.json(syncState);
|
|
5833
5989
|
});
|
|
5834
|
-
app.post("/api/clip", async (req, res) => {
|
|
5990
|
+
app.post("/api/clip", requireAuth, async (req, res) => {
|
|
5835
5991
|
try {
|
|
5836
5992
|
const { url } = req.body;
|
|
5837
5993
|
if (!url) {
|
|
@@ -5839,18 +5995,9 @@ ${removed.content}`;
|
|
|
5839
5995
|
return;
|
|
5840
5996
|
}
|
|
5841
5997
|
try {
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
return;
|
|
5846
|
-
}
|
|
5847
|
-
const host = parsed.hostname.toLowerCase();
|
|
5848
|
-
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.startsWith("192.168.") || host.startsWith("10.") || host.startsWith("172.16.")) {
|
|
5849
|
-
res.status(400).json({ error: "Internal URLs not allowed" });
|
|
5850
|
-
return;
|
|
5851
|
-
}
|
|
5852
|
-
} catch {
|
|
5853
|
-
res.status(400).json({ error: "Invalid URL" });
|
|
5998
|
+
assertNotPrivateUrl(url);
|
|
5999
|
+
} catch (e) {
|
|
6000
|
+
res.status(400).json({ error: e.message });
|
|
5854
6001
|
return;
|
|
5855
6002
|
}
|
|
5856
6003
|
const isYT = /youtube\.com\/watch|youtu\.be\//.test(url);
|
|
@@ -6438,7 +6585,7 @@ async function captureVoice(audioPath, options) {
|
|
|
6438
6585
|
}
|
|
6439
6586
|
|
|
6440
6587
|
// packages/core/dist/cloud/sync.js
|
|
6441
|
-
import { createCipheriv, createDecipheriv, randomBytes as
|
|
6588
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, createHash as createHash5 } from "node:crypto";
|
|
6442
6589
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync14, existsSync as existsSync14, mkdirSync as mkdirSync14, chmodSync as chmodSync2 } from "node:fs";
|
|
6443
6590
|
import { join as join15 } from "node:path";
|
|
6444
6591
|
import { homedir as homedir7 } from "node:os";
|
|
@@ -6446,7 +6593,7 @@ var CLOUD_DIR = join15(homedir7(), ".stellavault", "cloud");
|
|
|
6446
6593
|
var KEY_FILE = join15(CLOUD_DIR, "encryption.key");
|
|
6447
6594
|
var SYNC_STATE_FILE = join15(CLOUD_DIR, "sync-state.json");
|
|
6448
6595
|
function encrypt(data, key) {
|
|
6449
|
-
const iv =
|
|
6596
|
+
const iv = randomBytes3(16);
|
|
6450
6597
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
6451
6598
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
6452
6599
|
const tag = cipher.getAuthTag();
|
|
@@ -6467,7 +6614,7 @@ function getOrCreateEncryptionKey(userKey) {
|
|
|
6467
6614
|
if (existsSync14(KEY_FILE)) {
|
|
6468
6615
|
return Buffer.from(readFileSync14(KEY_FILE, "utf-8").trim(), "hex");
|
|
6469
6616
|
}
|
|
6470
|
-
const key =
|
|
6617
|
+
const key = randomBytes3(32);
|
|
6471
6618
|
writeFileSync14(KEY_FILE, key.toString("hex"), { encoding: "utf-8", mode: 384 });
|
|
6472
6619
|
try {
|
|
6473
6620
|
chmodSync2(KEY_FILE, 384);
|
|
@@ -6807,10 +6954,9 @@ async function graphCommand() {
|
|
|
6807
6954
|
const hasDevGraph = existsSync15(resolve11(devGraphDir, "package.json"));
|
|
6808
6955
|
if (hasDevGraph) {
|
|
6809
6956
|
console.error(chalk5.dim(" \u{1F680} Starting Vite dev server..."));
|
|
6810
|
-
const vite = spawn("npx", ["vite", "--host"], {
|
|
6957
|
+
const vite = spawn(process.platform === "win32" ? "npx.cmd" : "npx", ["vite", "--host"], {
|
|
6811
6958
|
cwd: devGraphDir,
|
|
6812
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
6813
|
-
shell: true
|
|
6959
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6814
6960
|
});
|
|
6815
6961
|
vite.stderr.on("data", (data) => {
|
|
6816
6962
|
const line = data.toString();
|
|
@@ -7048,7 +7194,7 @@ function runScript(scriptPath, cwd) {
|
|
|
7048
7194
|
const child = spawn2("node", [scriptPath], {
|
|
7049
7195
|
cwd,
|
|
7050
7196
|
stdio: "inherit",
|
|
7051
|
-
shell:
|
|
7197
|
+
shell: false
|
|
7052
7198
|
});
|
|
7053
7199
|
child.on("close", (code) => {
|
|
7054
7200
|
if (code === 0)
|
|
@@ -9337,7 +9483,7 @@ if (nodeVersion < 20) {
|
|
|
9337
9483
|
process.exit(1);
|
|
9338
9484
|
}
|
|
9339
9485
|
var program = new Command();
|
|
9340
|
-
var SV_VERSION = true ? "0.6.
|
|
9486
|
+
var SV_VERSION = true ? "0.6.1" : "0.0.0-dev";
|
|
9341
9487
|
program.name("stellavault").description("Stellavault \u2014 Self-compiling knowledge base for your Obsidian vault").version(SV_VERSION).option("--json", "Output in JSON format (for scripting)").option("--quiet", "Suppress non-essential output");
|
|
9342
9488
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
9343
9489
|
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|
package/package.json
CHANGED