mnemos-capture 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -113
- package/dist/cli/config.js +70 -0
- package/dist/cli/hooks.js +144 -5
- package/dist/cli/index.js +57 -7
- package/dist/cli/kos.js +123 -0
- package/dist/cli/mcp-server.js +8 -19
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -10,11 +10,28 @@
|
|
|
10
10
|
<img src="https://img.shields.io/badge/MCP-compatible-green" alt="MCP" />
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
<p align="center"><strong>
|
|
13
|
+
<p align="center"><strong>Capture anything. Your agents apply it.</strong></p>
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
<p align="center">
|
|
16
|
+
Humans consume information. Agents execute information.<br/>
|
|
17
|
+
<em>Mnemos converts one into the other.</em>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">A knowledge pipeline that turns what you learn into actionable context for AI agents.</p>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Your agent only knows what it was trained on. It doesn't know the article you read this morning, the framework you found last week, or the decision you made last sprint.
|
|
16
25
|
|
|
17
|
-
Mnemos bridges that gap
|
|
26
|
+
**Mnemos bridges that gap.**
|
|
27
|
+
|
|
28
|
+
Capture an insight once. Mnemos extracts it, stores it as plain Markdown in your own GitHub repository, indexes it, and serves it to any MCP-compatible agent.
|
|
29
|
+
|
|
30
|
+
Then it goes further. At session start, Mnemos briefs your agent on what matters right now, surfaces relevant knowledge in context, synthesizes reusable rules, and turns insights into implementation plans.
|
|
31
|
+
|
|
32
|
+
> **The result isn't another knowledge base.** It's a system that transforms what you learn into what your agents can execute.
|
|
33
|
+
|
|
34
|
+
<p align="center"><sub>Works with <strong>Claude Code</strong> · <strong>Codex</strong> · <strong>Cursor</strong> · <strong>Gemini</strong> · and any MCP-compatible assistant.</sub></p>
|
|
18
35
|
|
|
19
36
|
|
|
20
37
|
## Get started
|
|
@@ -26,141 +43,120 @@ Go to **[mnemos-capture.vercel.app](https://mnemos-capture.vercel.app)** → **S
|
|
|
26
43
|
During setup, Mnemos will:
|
|
27
44
|
|
|
28
45
|
- Create a knowledge repo in your GitHub account — plain Markdown files, no proprietary format
|
|
29
|
-
- Ask for your
|
|
46
|
+
- Ask for your API key (your provider, your key — Mnemos never pays for your API calls)
|
|
30
47
|
- Set a PIN so you can unlock the app quickly on mobile
|
|
31
48
|
|
|
32
49
|
No config files. No repos to clone. No CLI setup required.
|
|
33
50
|
|
|
34
|
-
### 2.
|
|
35
|
-
|
|
36
|
-
Open the app on any device — phone, tablet, or desktop. Paste any content and hit **Capture**.
|
|
37
|
-
|
|
38
|
-
The result is a structured Markdown file, auto-committed to your GitHub knowledge repo and immediately available to your agents.
|
|
51
|
+
### 2. Connect to your AI assistant
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
When you finish onboarding, Mnemos generates a personal **MCP API key** — copy it.
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
**Claude Code:**
|
|
56
|
+
```bash
|
|
57
|
+
claude mcp add mnemos -- npx -y mnemos-capture@latest serve-mcp --key <your-mcp-key>
|
|
58
|
+
```
|
|
43
59
|
|
|
60
|
+
**Any MCP-compatible client** (Cursor, Continue, Gemini CLI, etc.) — point it at the stdio proxy:
|
|
44
61
|
```bash
|
|
45
|
-
|
|
62
|
+
npx -y mnemos-capture@latest serve-mcp --key <your-mcp-key>
|
|
46
63
|
```
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
The proxy forwards your agent's MCP tool calls to the hosted Mnemos API. Nothing is stored locally — your knowledge lives in your GitHub repo.
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
### 3. Capture something
|
|
51
68
|
|
|
52
|
-
|
|
69
|
+
Open the app on any device — phone, tablet, or desktop. Paste any content and hit **Capture**. Or capture directly from your agent:
|
|
53
70
|
|
|
54
71
|
```
|
|
55
|
-
|
|
72
|
+
capture "The Mom Test — don't ask if your idea is good, ask about their life."
|
|
56
73
|
```
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
**Upgrade to a full session briefing (optional).** If you want more than a count — an LLM-composed briefing grounded in your current git branch, recent commits, and `CLAUDE.md` — run:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npx -y mnemos-capture@latest setup-hooks --key <your-api-key> --briefing
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Each session now starts with a focused summary: which captures are relevant to what you're working on right now, which synthesized rules apply, and what's waiting in your inbox. Adds ~5s to session start (calls `briefing` on the hosted API, uses your Anthropic key).
|
|
67
|
-
|
|
68
|
-
**Where the hook runs.** `SessionStart` hooks fire anywhere Claude Code reads your local settings — CLI, VS Code / JetBrains extensions, and the desktop app. For Claude Code on the web or other MCP clients (Cursor, etc.), the MCP tools (`list_inbox`, `briefing`, `recall`, …) still work — you just won't get the proactive nudge automatically. Add a line to that project's `CLAUDE.md` like `"At session start, call list_inbox to surface pending captures"` for a portable alternative.
|
|
69
|
-
|
|
70
|
-
### 4. Connect to any MCP-compatible agent
|
|
71
|
-
|
|
72
|
-
Your knowledge lives in a standard GitHub repo. Any agent that can read Git or speak MCP can access it — no lock-in, no custom integration required.
|
|
75
|
+
That's it. Your knowledge is now indexed and available to every agent session.
|
|
73
76
|
|
|
74
77
|
|
|
75
78
|
## How it works
|
|
76
79
|
|
|
77
80
|
```mermaid
|
|
78
81
|
flowchart TD
|
|
79
|
-
A["Article / research /\ntranscript / idea"] -->|paste| B["Mnemos\n(web / mobile /
|
|
80
|
-
B -->|extracts
|
|
81
|
-
C -->|
|
|
82
|
-
D -->|
|
|
83
|
-
E -->|
|
|
84
|
-
F -->|
|
|
85
|
-
F -->|no| H["Stays in\nknowledge base"]
|
|
82
|
+
A["Article / research /\ntranscript / idea"] -->|paste| B["Mnemos\n(web / mobile / agent)"]
|
|
83
|
+
B -->|extracts insight| C["Structured Markdown\ncommitted to your repo"]
|
|
84
|
+
C -->|session start| D["Briefing\nranked suggestions + plan"]
|
|
85
|
+
D -->|generate_plan| E["Implementation plan\nsaved to plans/"]
|
|
86
|
+
E -->|mnemos kos| F["Your AI assistant\nexecutes in isolated worktree"]
|
|
87
|
+
F -->|verify| G["Branch ready\nto review & merge"]
|
|
86
88
|
|
|
87
89
|
style A fill:#1a1a2e,stroke:#444,color:#fff
|
|
88
90
|
style B fill:#2A62C6,stroke:#1a4a9e,color:#fff
|
|
89
|
-
style C fill:#
|
|
90
|
-
style D fill:#
|
|
91
|
-
style E fill:#
|
|
92
|
-
style F fill:#
|
|
91
|
+
style C fill:#2d8a4e,stroke:#1e6b3a,color:#fff
|
|
92
|
+
style D fill:#d4a843,stroke:#b8912e,color:#fff
|
|
93
|
+
style E fill:#2A62C6,stroke:#1a4a9e,color:#fff
|
|
94
|
+
style F fill:#1a1a2e,stroke:#444,color:#fff
|
|
93
95
|
style G fill:#2d8a4e,stroke:#1e6b3a,color:#fff
|
|
94
|
-
style H fill:#1a1a2e,stroke:#444,color:#fff
|
|
95
96
|
```
|
|
96
97
|
|
|
97
|
-
1. **
|
|
98
|
-
2. **
|
|
99
|
-
3. **
|
|
100
|
-
4. **
|
|
98
|
+
1. **Capture** — paste anything text-based. Mnemos extracts the core idea, key takeaways, where to apply it, and auto-detects whether it's a URL, short note, or long paste. Committed to your GitHub repo as structured Markdown.
|
|
99
|
+
2. **Brief** — at session start, the `--briefing` flag sends your project context (branch, recent commits, CLAUDE.md) to Mnemos. It returns ranked suggestions — *why* each insight matters now, *what* applying it achieves, *where* in your codebase.
|
|
100
|
+
3. **Plan** — `generate_plan` reads your selected captures and produces a structured Markdown plan: Codebase Mapping table, per-file steps with effort tiers (`simple` / `complex` / `architectural`), and a Verification Checklist.
|
|
101
|
+
4. **Execute** — `mnemos kos` (coming) creates an isolated git worktree, launches your configured AI assistant with the plan as its contract, and runs the verification checklist when it's done.
|
|
101
102
|
|
|
102
|
-
### Example output
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
## What you can feed it
|
|
105
|
+
|
|
106
|
+
If it's text, Mnemos can extract insight from it. No manual tagging required.
|
|
107
|
+
|
|
108
|
+
- **Research papers and preprints** — new models, architectures, evaluation methods
|
|
109
|
+
- **Framework and library docs** — patterns, APIs, integration approaches worth keeping
|
|
110
|
+
- **Technical threads and writeups** — the argument or finding, not the noise
|
|
111
|
+
- **Optimization techniques** — prompt engineering, caching strategies, latency improvements
|
|
112
|
+
- **Your own ideas** — architecture decisions, workflow changes, things you want your agents to act on later
|
|
113
|
+
- **Transcripts and talks** — signal extracted, ready to apply
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## Example capture
|
|
117
|
+
|
|
118
|
+
Paste a research post about error observability. This is what lands in your repo:
|
|
105
119
|
|
|
106
120
|
```markdown
|
|
107
121
|
---
|
|
108
|
-
date: 2026-
|
|
109
|
-
source:
|
|
110
|
-
url: https://example.com/
|
|
122
|
+
date: 2026-06-05
|
|
123
|
+
source: Error Context First — Observability for AI Systems
|
|
124
|
+
url: https://example.com/error-context
|
|
111
125
|
type: post
|
|
112
|
-
|
|
126
|
+
source_type: url
|
|
127
|
+
tags: observability, error-handling, ai-agents, debugging
|
|
113
128
|
status: inbox
|
|
129
|
+
lowConfidence: false
|
|
114
130
|
---
|
|
115
131
|
|
|
116
|
-
#
|
|
132
|
+
# Error Context First — Observability for AI Systems
|
|
117
133
|
|
|
118
134
|
## Core idea
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
Opaque error messages make every fix a guess. Adding source context to every
|
|
136
|
+
error — which handler, which step, which input — turns debugging from archaeology
|
|
137
|
+
into a directed search.
|
|
122
138
|
|
|
123
139
|
## Key takeaways
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
collapse the entire workflow
|
|
128
|
-
|
|
129
|
-
## Quotes
|
|
130
|
-
> "The bottleneck in complex agentic tasks is not model capability — it's
|
|
131
|
-
> error propagation across sequential steps."
|
|
140
|
+
- Every catch block should prefix messages with where it came from
|
|
141
|
+
- "The string did not match the expected pattern" is useless; "[github-write] ..." is actionable
|
|
142
|
+
- Instrument before you fix — opaque errors cause repeated misdiagnoses
|
|
132
143
|
|
|
133
144
|
## Applied to
|
|
134
|
-
|
|
135
|
-
into parallel subagent calls — prioritize anything with 3+ sequential steps.
|
|
145
|
+
Add handler-name prefixes to every catch block in your agent's tool pipeline.
|
|
136
146
|
```
|
|
137
147
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
## What you can feed it
|
|
142
|
-
|
|
143
|
-
If it's text, Mnemos can extract insight from it. You don't need to categorize or tag anything yourself — that happens automatically.
|
|
144
|
-
|
|
145
|
-
- **Research papers and preprints** — new models, architectures, evaluation methods
|
|
146
|
-
- **Framework and library docs** — patterns, APIs, integration approaches worth keeping
|
|
147
|
-
- **Optimization techniques** — prompt engineering, caching strategies, latency improvements
|
|
148
|
-
- **Technical threads and writeups** — the argument or finding, not the noise
|
|
149
|
-
- **Your own ideas** — workflow improvements, architecture decisions, things you want your agents to act on later
|
|
150
|
-
- **Transcripts and talks** — the signal extracted, ready to apply
|
|
148
|
+
`source_type` is auto-detected (`url` / `note` / `paste`). `lowConfidence` flags short or ambiguous input for review before acting on it.
|
|
151
149
|
|
|
152
150
|
|
|
153
151
|
## Knowledge lifecycle
|
|
154
152
|
|
|
155
|
-
Knowledge that sits unreviewed rots. Mnemos forces a decision: apply it, save it for later, or discard it.
|
|
156
|
-
|
|
157
153
|
```
|
|
158
154
|
┌─────────┐
|
|
159
155
|
│ Capture │
|
|
160
156
|
└────┬─────┘
|
|
161
157
|
▼
|
|
162
158
|
┌─────────┐ ┌──────────┐
|
|
163
|
-
│ Inbox │────▶│ Applied │
|
|
159
|
+
│ Inbox │────▶│ Applied │ Insight used — plan executed, rule added, code changed
|
|
164
160
|
└────┬─────┘ └──────────┘
|
|
165
161
|
│
|
|
166
162
|
├──────────▶┌──────────┐
|
|
@@ -172,67 +168,152 @@ Knowledge that sits unreviewed rots. Mnemos forces a decision: apply it, save it
|
|
|
172
168
|
└──────────┘
|
|
173
169
|
```
|
|
174
170
|
|
|
175
|
-
|
|
171
|
+
All captures are tracked in `INDEX.md` — a master table your agents search across your entire knowledge base. Generated plans land in `plans/`.
|
|
176
172
|
|
|
177
|
-
1. **Capture** — every new capture lands in `inbox/`. After each capture, Mnemos tells you how many items are waiting for review.
|
|
178
|
-
2. **Review** — use `list_inbox` to see what's in your inbox (with summaries), and `read_capture` to read the full content of any capture.
|
|
179
|
-
3. **Apply** — when you've used an insight (added it to a project file, changed a workflow, created a rule), use `apply_capture` to move it to `applied/`. This closes the loop — the knowledge is now part of your system.
|
|
180
|
-
4. **Archive** — reviewed it but it's not actionable right now? Use `archive_capture` to move it to `archived/`. It stays searchable but out of your inbox.
|
|
181
|
-
5. **Delete** — not useful? Use `delete_capture` to permanently remove it.
|
|
182
173
|
|
|
183
|
-
|
|
174
|
+
## Session hooks
|
|
175
|
+
|
|
176
|
+
`serve-mcp` writes a `SessionStart` hook to `~/.claude/settings.json` on first run. Every session opens with your inbox count:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
Mnemos: 4 captures in inbox — run list_inbox to review
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Briefing mode
|
|
183
|
+
|
|
184
|
+
Opt in for a structured briefing instead of a count:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npx -y mnemos-capture@latest setup-hooks --key YOUR_KEY --briefing
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
At session start, Mnemos collects your project context — branch, recent commits, CLAUDE.md excerpt — and returns:
|
|
191
|
+
|
|
192
|
+
1. Ranked suggestions with `why`, `benefit`, `where`, and whether to apply now
|
|
193
|
+
2. Relevant synthesized rules
|
|
194
|
+
3. A `generate_plan` prompt pre-loaded with the captures to apply
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
3 insights to apply now:
|
|
198
|
+
|
|
199
|
+
1. error-context-first → src/app/api/capture/route.ts
|
|
200
|
+
Why: Your current branch rewrites the capture pipeline and all catch blocks are opaque
|
|
201
|
+
Benefit: Every error will identify its source in < 1 line
|
|
202
|
+
|
|
203
|
+
Run generate_plan with selected_captures: ["inbox/2026-06-05-error-context-first.md"]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Vault mode (PreToolCall hook)
|
|
207
|
+
|
|
208
|
+
Surfaces relevant captures *before* you edit a file — not just at session start:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npx -y mnemos-capture@latest setup-hooks --key YOUR_KEY --vault
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Before every `Edit`, `Write`, or `MultiEdit` tool call, Mnemos scans your vault for captures scoring ≥ 0.7 relevance to the file you're about to touch. Each capture is surfaced at most once per session.
|
|
215
|
+
|
|
216
|
+
Opt-in only — not installed by default because it fires on every file edit.
|
|
217
|
+
|
|
218
|
+
### Compatibility
|
|
219
|
+
|
|
220
|
+
| Surface | Hook fires |
|
|
221
|
+
|---|---|
|
|
222
|
+
| Claude Code CLI, VS Code / JetBrains extensions, desktop app | Yes |
|
|
223
|
+
| Claude Code on the web | No — `~/.claude/settings.json` is local only |
|
|
224
|
+
| Cursor / other MCP clients | No — distinct hook systems |
|
|
225
|
+
|
|
226
|
+
MCP tools work everywhere. For unsupported surfaces, add to `CLAUDE.md`:
|
|
227
|
+
|
|
228
|
+
> At session start, call `list_inbox` to surface pending captures.
|
|
184
229
|
|
|
185
230
|
|
|
186
231
|
## MCP tools
|
|
187
232
|
|
|
188
|
-
Mnemos exposes
|
|
233
|
+
Mnemos exposes 15 tools via MCP:
|
|
189
234
|
|
|
190
235
|
### Capture
|
|
191
236
|
|
|
192
237
|
| Tool | What it does |
|
|
193
238
|
|------|-------------|
|
|
194
|
-
| `capture` | Extracts insight from any pasted content
|
|
239
|
+
| `capture` | Extracts insight from any pasted content. Auto-detects source type, flags low-confidence input, auto-links to related captures. |
|
|
195
240
|
|
|
196
241
|
### Discover
|
|
197
242
|
|
|
198
243
|
| Tool | What it does |
|
|
199
244
|
|------|-------------|
|
|
200
|
-
| `list_inbox` |
|
|
201
|
-
| `search_captures` |
|
|
202
|
-
| `read_capture` |
|
|
245
|
+
| `list_inbox` | Unprocessed captures with summaries — title, type, tags, core idea. |
|
|
246
|
+
| `search_captures` | Keyword + tag search across inbox, applied, and archived. |
|
|
247
|
+
| `read_capture` | Full Markdown of any capture. |
|
|
248
|
+
| `recall` | Semantic search — describe your task, get the most relevant captures back. More powerful than keyword search. |
|
|
249
|
+
|
|
250
|
+
### Apply
|
|
251
|
+
|
|
252
|
+
| Tool | What it does |
|
|
253
|
+
|------|-------------|
|
|
254
|
+
| `apply_capture` | Moves inbox → applied. Records how and where knowledge was used, which plan drove it, and what changed. Triggers rule re-synthesis. |
|
|
255
|
+
| `archive_capture` | Moves inbox → archived. Reviewed but not actionable now. |
|
|
256
|
+
| `delete_capture` | Permanently removes a capture and its index entry. |
|
|
257
|
+
| `apply_to_context` | Paste task + stack + code snippet → get concrete, file-level suggestions for applying your captured knowledge. |
|
|
258
|
+
|
|
259
|
+
### Plan
|
|
260
|
+
|
|
261
|
+
| Tool | What it does |
|
|
262
|
+
|------|-------------|
|
|
263
|
+
| `briefing` | Session-start briefing: ranked suggestions with `applyNow` flags, relevant rules, and a `generate_plan` prompt. Requires `project_context`. |
|
|
264
|
+
| `generate_plan` | Reads selected captures + optional codebase files → structured Markdown plan with Codebase Mapping table, effort tiers per step, and Verification Checklist. Saved to `plans/`. |
|
|
265
|
+
| `list_plans` | Lists saved plans, or reads a specific plan in full. |
|
|
203
266
|
|
|
204
|
-
###
|
|
267
|
+
### Synthesize
|
|
205
268
|
|
|
206
269
|
| Tool | What it does |
|
|
207
270
|
|------|-------------|
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `delete_capture` | Permanently removes a capture and its index entry. For mistakes or captures that aren't useful. |
|
|
271
|
+
| `synthesize` | Distills captures on a tag into actionable rules. Updates `RULES.md`. |
|
|
272
|
+
| `get_rules` | Returns synthesized rules — filtered by tag or all. Use to populate `CLAUDE.md` or a system prompt. |
|
|
211
273
|
|
|
212
|
-
|
|
274
|
+
### Curate
|
|
275
|
+
|
|
276
|
+
| Tool | What it does |
|
|
277
|
+
|------|-------------|
|
|
278
|
+
| `curate` | Validates inbox captures: HEAD-checks URLs (404/410 → stale), flags low-confidence extractions. Optional `auto_archive`. Batch cap: 20 files. |
|
|
279
|
+
| `vault_scan` | Scans the full vault for captures relevant to your current activity (score ≥ 0.7). Used by the vault hook. |
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
## CLI
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
mnemos-capture Open hosted app
|
|
286
|
+
mnemos-capture serve-mcp --key KEY Run local MCP proxy (auto-installs hook)
|
|
287
|
+
mnemos-capture setup-hooks --key KEY [--briefing] [--vault] Install session hooks
|
|
288
|
+
mnemos-capture inbox-check --key KEY [--briefing] Debug what the hook runs
|
|
289
|
+
mnemos-capture vault-check --key KEY Debug what the vault hook runs
|
|
290
|
+
mnemos-capture help Show help
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
All commands run via `npx -y mnemos-capture@latest <subcommand>`. `@latest` forces npm to resolve against the registry on every invocation.
|
|
213
294
|
|
|
214
295
|
|
|
215
296
|
## Mobile
|
|
216
297
|
|
|
217
|
-
Open Mnemos in your phone's browser
|
|
298
|
+
Open Mnemos in your phone's browser → **Share → Add to Home Screen**. Runs full-screen like a native app. Capture while reading — your agents have it by the time you sit down to work.
|
|
218
299
|
|
|
219
|
-
Capture on the go — reading an article on your phone at lunch? Capture it. Your agents have it by the time you sit down to work.
|
|
220
300
|
|
|
221
301
|
## Your data, your storage
|
|
222
302
|
|
|
223
303
|
Your knowledge lives in a GitHub repo you own. Plain Markdown files, version-controlled, portable.
|
|
224
304
|
|
|
225
|
-
- **No lock-in** — clone it,
|
|
305
|
+
- **No lock-in** — clone it, move it, delete Mnemos and your repo stays exactly where it is
|
|
226
306
|
- **No proprietary format** — every capture is a readable `.md` file
|
|
227
|
-
- **No training on your data** — Mnemos never reads your captures for any purpose other than serving them back to you
|
|
307
|
+
- **No training on your data** — Mnemos never reads your captures for any purpose other than serving them back to you
|
|
228
308
|
- **Any tool can access it** — anything that reads Git or speaks MCP works with your knowledge base
|
|
309
|
+
- **BYOK** — your provider, your key, your cost
|
|
229
310
|
|
|
230
311
|
|
|
231
312
|
## Cost
|
|
232
313
|
|
|
233
|
-
Mnemos
|
|
314
|
+
Mnemos is BYOK — you bring your own API key, Mnemos never charges you for inference.
|
|
234
315
|
|
|
235
|
-
Extraction runs on
|
|
316
|
+
Extraction runs on a fast, low-cost model (Claude Haiku 4.5 by default) with prompt caching and input truncation:
|
|
236
317
|
|
|
237
318
|
| Usage | Estimated monthly cost |
|
|
238
319
|
|-------|----------------------|
|
|
@@ -240,28 +321,29 @@ Extraction runs on **Claude Haiku 4.5** with prompt caching and input truncation
|
|
|
240
321
|
| 100 captures/month | ~$0.30 |
|
|
241
322
|
| 200 captures/month | ~$0.60 |
|
|
242
323
|
|
|
243
|
-
|
|
324
|
+
Briefing and plan generation use your configured model and only run when you explicitly invoke them.
|
|
244
325
|
|
|
245
326
|
|
|
246
327
|
## Tech stack
|
|
247
328
|
|
|
248
|
-
Next.js · TypeScript ·
|
|
329
|
+
Next.js · TypeScript · Vercel Postgres · GitHub OAuth · GitHub Content API · MCP protocol · Anthropic SDK · Tailwind CSS · Vitest
|
|
330
|
+
|
|
249
331
|
|
|
250
332
|
## Roadmap
|
|
251
333
|
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
334
|
+
- **`mnemos kos`** — CLI orchestrator: creates isolated git worktree, launches your configured AI assistant with the plan as its contract, runs the Verification Checklist, reports the branch
|
|
335
|
+
- **Provider abstraction** — bring your OpenAI, Google, or any OpenAI-compatible key; Mnemos routes accordingly
|
|
336
|
+
- **Chrome extension** — one-click capture from anywhere in the browser
|
|
255
337
|
- Voice memo capture
|
|
256
338
|
|
|
257
339
|
|
|
258
340
|
## Built by
|
|
259
341
|
|
|
260
|
-
[Sofía Padrón Valdez](https://github.com/Soph20) — builder, AI systems architect.
|
|
342
|
+
[Sofía Padrón Valdez](https://github.com/Soph20) — builder, AI systems architect.
|
|
343
|
+
|
|
261
344
|
|
|
262
345
|
## Citation
|
|
263
346
|
|
|
264
|
-
If you use this repository in your agentic workflows, please consider citing as follows:
|
|
265
347
|
```
|
|
266
348
|
@misc{2026mnemos,
|
|
267
349
|
title = {Mnemos: A Knowledge Pipeline for AI Agents},
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mnemos CLI config — stores which AI assistant kos should drive, and an
|
|
3
|
+
* optional API key, in ~/.mnemos/config.json. Provider/agent-agnostic: the
|
|
4
|
+
* user tells Mnemos the exact command to launch their assistant.
|
|
5
|
+
*/
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
9
|
+
const CONFIG_DIR = join(homedir(), ".mnemos");
|
|
10
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
11
|
+
const ALLOWED_FIELDS = ["agent", "key"];
|
|
12
|
+
export function readConfig() {
|
|
13
|
+
if (!existsSync(CONFIG_PATH))
|
|
14
|
+
return {};
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function writeConfig(cfg) {
|
|
23
|
+
if (!existsSync(CONFIG_DIR))
|
|
24
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
25
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n");
|
|
26
|
+
}
|
|
27
|
+
export function setConfigField(field, value) {
|
|
28
|
+
if (!ALLOWED_FIELDS.includes(field)) {
|
|
29
|
+
throw new Error(`Unknown config field: ${field}. Allowed: ${ALLOWED_FIELDS.join(", ")}`);
|
|
30
|
+
}
|
|
31
|
+
const cfg = readConfig();
|
|
32
|
+
cfg[field] = value;
|
|
33
|
+
writeConfig(cfg);
|
|
34
|
+
}
|
|
35
|
+
/** Route `mnemos config <set|get> ...`. */
|
|
36
|
+
export function runConfigCommand(args) {
|
|
37
|
+
const action = args[0];
|
|
38
|
+
if (action === "set") {
|
|
39
|
+
const field = args[1];
|
|
40
|
+
const value = args.slice(2).join(" ");
|
|
41
|
+
if (!field || !value) {
|
|
42
|
+
console.error('Usage: mnemos config set <field> <value>');
|
|
43
|
+
console.error(' e.g. mnemos config set agent "claude -p"');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
setConfigField(field, value);
|
|
48
|
+
console.log(`Set ${field} = ${value}`);
|
|
49
|
+
console.log(`Saved to ${CONFIG_PATH}`);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (action === "get") {
|
|
58
|
+
const cfg = readConfig();
|
|
59
|
+
const field = args[1];
|
|
60
|
+
if (field) {
|
|
61
|
+
console.log(cfg[field] ?? "(not set)");
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.error("Usage: mnemos config set|get <field> [value]");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
package/dist/cli/hooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
1
|
+
import { homedir, tmpdir } from "os";
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
@@ -9,6 +9,9 @@ const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
|
9
9
|
function isMnemosHook(cmd) {
|
|
10
10
|
return cmd.includes("mnemos-capture") && cmd.includes("inbox-check");
|
|
11
11
|
}
|
|
12
|
+
function isMnemosPreToolHook(cmd) {
|
|
13
|
+
return cmd.includes("mnemos-capture") && cmd.includes("vault-check");
|
|
14
|
+
}
|
|
12
15
|
function readSettings() {
|
|
13
16
|
if (!existsSync(CLAUDE_SETTINGS_PATH))
|
|
14
17
|
return {};
|
|
@@ -25,6 +28,47 @@ function writeSettings(settings) {
|
|
|
25
28
|
mkdirSync(dir, { recursive: true });
|
|
26
29
|
writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
|
|
27
30
|
}
|
|
31
|
+
// ── Session state for vault deduplication ──
|
|
32
|
+
function sessionStateFile() {
|
|
33
|
+
const sessionId = process.env["CLAUDE_SESSION_ID"] ?? new Date().toISOString().split("T")[0] ?? "default";
|
|
34
|
+
return join(tmpdir(), `mnemos-vault-${sessionId}.json`);
|
|
35
|
+
}
|
|
36
|
+
function readSessionSurfaced() {
|
|
37
|
+
try {
|
|
38
|
+
const file = sessionStateFile();
|
|
39
|
+
if (!existsSync(file))
|
|
40
|
+
return [];
|
|
41
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function appendSessionSurfaced(filenames) {
|
|
48
|
+
try {
|
|
49
|
+
const file = sessionStateFile();
|
|
50
|
+
const existing = readSessionSurfaced();
|
|
51
|
+
const updated = [...new Set([...existing, ...filenames])];
|
|
52
|
+
writeFileSync(file, JSON.stringify(updated));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// best-effort
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// ── Stdin reader for hook context ──
|
|
59
|
+
async function readStdin() {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
if (process.stdin.isTTY) {
|
|
62
|
+
resolve("");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
let data = "";
|
|
66
|
+
process.stdin.setEncoding("utf-8");
|
|
67
|
+
process.stdin.on("data", (chunk) => { data += chunk; });
|
|
68
|
+
process.stdin.on("end", () => resolve(data));
|
|
69
|
+
setTimeout(() => resolve(data), 2000);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
28
72
|
export function setupHooks(apiKey, opts = {}) {
|
|
29
73
|
const settings = readSettings();
|
|
30
74
|
const hooks = settings.hooks ?? {};
|
|
@@ -73,6 +117,43 @@ export function setupHooks(apiKey, opts = {}) {
|
|
|
73
117
|
console.log("At the start of each Claude Code session you'll see your inbox count.");
|
|
74
118
|
}
|
|
75
119
|
}
|
|
120
|
+
if (opts.vault) {
|
|
121
|
+
setupPreToolHook(apiKey, { silent: opts.silent, skipIfExists: opts.skipIfExists });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function setupPreToolHook(apiKey, opts = {}) {
|
|
125
|
+
const settings = readSettings();
|
|
126
|
+
const hooks = settings.hooks ?? {};
|
|
127
|
+
const preToolCall = hooks.PreToolCall ?? [];
|
|
128
|
+
let existingCmd = null;
|
|
129
|
+
for (const group of preToolCall) {
|
|
130
|
+
for (const h of group.hooks ?? []) {
|
|
131
|
+
if (typeof h.command === "string" && isMnemosPreToolHook(h.command)) {
|
|
132
|
+
existingCmd = h.command;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (existingCmd)
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
if (existingCmd && opts.skipIfExists) {
|
|
140
|
+
if (existingCmd.includes("mnemos-capture@latest"))
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const command = `npx -y mnemos-capture@latest vault-check --key ${apiKey}`;
|
|
144
|
+
const filtered = preToolCall
|
|
145
|
+
.map((group) => ({
|
|
146
|
+
...group,
|
|
147
|
+
hooks: (group.hooks ?? []).filter((h) => !(typeof h.command === "string" && isMnemosPreToolHook(h.command))),
|
|
148
|
+
}))
|
|
149
|
+
.filter((group) => (group.hooks ?? []).length > 0);
|
|
150
|
+
filtered.push({ hooks: [{ type: "command", command }] });
|
|
151
|
+
settings.hooks = { ...hooks, PreToolCall: filtered };
|
|
152
|
+
writeSettings(settings);
|
|
153
|
+
if (!opts.silent) {
|
|
154
|
+
console.log("Mnemos vault hook installed (PreToolCall).");
|
|
155
|
+
console.log("Your knowledge vault will surface relevant captures as you edit files.");
|
|
156
|
+
}
|
|
76
157
|
}
|
|
77
158
|
function run(cmd) {
|
|
78
159
|
return execSync(cmd, { timeout: 2000, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
@@ -116,7 +197,7 @@ function detectProjectContext() {
|
|
|
116
197
|
catch { }
|
|
117
198
|
return parts.length > 0 ? parts.join("\n\n") : "current project";
|
|
118
199
|
}
|
|
119
|
-
async function callMcp(apiKey, toolName, toolArgs) {
|
|
200
|
+
export async function callMcp(apiKey, toolName, toolArgs, timeoutMs = 15000) {
|
|
120
201
|
const res = await fetch(HOSTED_URL, {
|
|
121
202
|
method: "POST",
|
|
122
203
|
headers: {
|
|
@@ -129,20 +210,78 @@ async function callMcp(apiKey, toolName, toolArgs) {
|
|
|
129
210
|
params: { name: toolName, arguments: toolArgs },
|
|
130
211
|
id: 1,
|
|
131
212
|
}),
|
|
132
|
-
signal: AbortSignal.timeout(
|
|
213
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
133
214
|
});
|
|
134
215
|
if (!res.ok)
|
|
135
216
|
return null;
|
|
136
217
|
const data = (await res.json());
|
|
137
218
|
return data.result?.content?.[0]?.text ?? null;
|
|
138
219
|
}
|
|
220
|
+
export async function vaultCheck(apiKey) {
|
|
221
|
+
try {
|
|
222
|
+
const inputJson = await readStdin();
|
|
223
|
+
if (!inputJson.trim())
|
|
224
|
+
return;
|
|
225
|
+
let hookData;
|
|
226
|
+
try {
|
|
227
|
+
hookData = JSON.parse(inputJson);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Only act on file-editing tool calls
|
|
233
|
+
const editingTools = ["Edit", "Write", "MultiEdit"];
|
|
234
|
+
if (!hookData.tool_name || !editingTools.includes(hookData.tool_name))
|
|
235
|
+
return;
|
|
236
|
+
const filePath = hookData.tool_input?.["file_path"] ??
|
|
237
|
+
hookData.tool_input?.["path"];
|
|
238
|
+
if (!filePath)
|
|
239
|
+
return;
|
|
240
|
+
const projectCtx = detectProjectContext();
|
|
241
|
+
const activityContext = `Editing file: ${filePath}\nProject context:\n${projectCtx}`;
|
|
242
|
+
const sessionSurfaced = readSessionSurfaced();
|
|
243
|
+
const text = await callMcp(apiKey, "vault_scan", {
|
|
244
|
+
activity_context: activityContext,
|
|
245
|
+
session_surfaced: sessionSurfaced,
|
|
246
|
+
}, 8000);
|
|
247
|
+
if (!text?.trim())
|
|
248
|
+
return;
|
|
249
|
+
// Track surfaced filenames to avoid repetition within the session
|
|
250
|
+
const filenameMatches = text.match(/(?:inbox|applied|archived)\/[\w-]+\.md/g) ?? [];
|
|
251
|
+
if (filenameMatches.length > 0)
|
|
252
|
+
appendSessionSurfaced(filenameMatches);
|
|
253
|
+
process.stdout.write(text.trim() + "\n");
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Silent fail — never interrupt a tool call
|
|
257
|
+
}
|
|
258
|
+
}
|
|
139
259
|
export async function inboxCheck(apiKey, opts = {}) {
|
|
140
260
|
try {
|
|
141
261
|
if (opts.briefing) {
|
|
142
262
|
const projectContext = detectProjectContext();
|
|
143
263
|
const text = await callMcp(apiKey, "briefing", { project_context: projectContext });
|
|
144
|
-
if (text?.trim())
|
|
145
|
-
|
|
264
|
+
if (!text?.trim())
|
|
265
|
+
return;
|
|
266
|
+
const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/);
|
|
267
|
+
let applyNow = [];
|
|
268
|
+
if (jsonMatch?.[1]) {
|
|
269
|
+
try {
|
|
270
|
+
const all = JSON.parse(jsonMatch[1]);
|
|
271
|
+
applyNow = all.filter((s) => s.applyNow);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
applyNow = [];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Strip the JSON block — display only the Markdown narrative
|
|
278
|
+
const displayText = text.replace(/```json[\s\S]*?```\n?/, "").trim();
|
|
279
|
+
process.stdout.write(displayText + "\n");
|
|
280
|
+
if (applyNow.length > 0) {
|
|
281
|
+
const filenames = applyNow.map((s) => `'${s.filename}'`).join(", ");
|
|
282
|
+
process.stdout.write(`\n---\nMnemos: ${applyNow.length} insight${applyNow.length === 1 ? "" : "s"} ready to apply now.\n` +
|
|
283
|
+
`Run generate_plan with selected_captures: [${filenames}] to get a full implementation plan.\n`);
|
|
284
|
+
}
|
|
146
285
|
return;
|
|
147
286
|
}
|
|
148
287
|
// Default: fast count via list_inbox (no LLM call)
|
package/dist/cli/index.js
CHANGED
|
@@ -17,10 +17,10 @@ async function main() {
|
|
|
17
17
|
const keyIdx = args.indexOf("--key");
|
|
18
18
|
const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : undefined;
|
|
19
19
|
if (!apiKey) {
|
|
20
|
-
console.error("Usage: npx mnemos-capture setup-hooks --key YOUR_API_KEY [--briefing]");
|
|
20
|
+
console.error("Usage: npx mnemos-capture setup-hooks --key YOUR_API_KEY [--briefing] [--vault]");
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
|
-
setupHooks(apiKey, { briefing: args.includes("--briefing") });
|
|
23
|
+
setupHooks(apiKey, { briefing: args.includes("--briefing"), vault: args.includes("--vault") });
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
if (command === "inbox-check") {
|
|
@@ -32,6 +32,34 @@ async function main() {
|
|
|
32
32
|
await inboxCheck(apiKey, { briefing: args.includes("--briefing") });
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
+
if (command === "vault-check") {
|
|
36
|
+
const { vaultCheck } = await import("./hooks.js");
|
|
37
|
+
const keyIdx = args.indexOf("--key");
|
|
38
|
+
const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : undefined;
|
|
39
|
+
if (!apiKey)
|
|
40
|
+
process.exit(0); // silent fail in hook context
|
|
41
|
+
await vaultCheck(apiKey);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (command === "config") {
|
|
45
|
+
const { runConfigCommand } = await import("./config.js");
|
|
46
|
+
runConfigCommand(args.slice(1));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (command === "kos") {
|
|
50
|
+
const { kos } = await import("./kos.js");
|
|
51
|
+
const { readConfig } = await import("./config.js");
|
|
52
|
+
const keyIdx = args.indexOf("--key");
|
|
53
|
+
const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : readConfig().key;
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
console.error("No API key. Pass --key YOUR_KEY or store it: mnemos config set key YOUR_KEY");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const planIdx = args.indexOf("--plan");
|
|
59
|
+
const plan = planIdx !== -1 ? args[planIdx + 1] : undefined;
|
|
60
|
+
await kos(apiKey, { plan });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
35
63
|
// Default: open the hosted app
|
|
36
64
|
console.log("");
|
|
37
65
|
console.log(" Mnemos — Knowledge capture for agentic workflows\n");
|
|
@@ -52,11 +80,30 @@ function printHelp() {
|
|
|
52
80
|
Mnemos — Knowledge capture for agentic workflows
|
|
53
81
|
|
|
54
82
|
Usage:
|
|
55
|
-
npx mnemos-capture
|
|
56
|
-
npx mnemos-capture serve-mcp --key KEY
|
|
57
|
-
npx mnemos-capture setup-hooks --key KEY
|
|
58
|
-
npx mnemos-capture setup-hooks --key KEY --briefing
|
|
59
|
-
npx mnemos-capture
|
|
83
|
+
npx mnemos-capture Open Mnemos in your browser
|
|
84
|
+
npx mnemos-capture serve-mcp --key KEY Start the MCP server for Claude Code
|
|
85
|
+
npx mnemos-capture setup-hooks --key KEY Install inbox count hook (fast)
|
|
86
|
+
npx mnemos-capture setup-hooks --key KEY --briefing Install full briefing hook (uses LLM)
|
|
87
|
+
npx mnemos-capture setup-hooks --key KEY --vault Install vault hook (PreToolCall)
|
|
88
|
+
npx mnemos-capture setup-hooks --key KEY --briefing --vault Install both hooks
|
|
89
|
+
npx mnemos-capture config set agent "claude -p" Set which AI assistant kos drives
|
|
90
|
+
npx mnemos-capture kos --key KEY [--plan FILE] Implement a plan in an isolated worktree
|
|
91
|
+
npx mnemos-capture help Show this help
|
|
92
|
+
|
|
93
|
+
MCP tools (via your AI assistant):
|
|
94
|
+
briefing — Session-start briefing with ranked insights to apply
|
|
95
|
+
generate_plan — Turn selected captures into a full implementation plan
|
|
96
|
+
list_plans — List or read saved implementation plans
|
|
97
|
+
vault_scan — Scan all captures for relevance to current activity
|
|
98
|
+
curate — Validate URLs and flag stale captures
|
|
99
|
+
|
|
100
|
+
kos — implement plans with your own AI assistant (model-agnostic):
|
|
101
|
+
1. Tell Mnemos which assistant to drive (once):
|
|
102
|
+
mnemos config set agent "claude -p" # or "codex exec", "aider --yes --message"
|
|
103
|
+
2. Generate a plan via the generate_plan MCP tool.
|
|
104
|
+
3. Run: mnemos kos --key YOUR_KEY
|
|
105
|
+
kos creates an isolated git worktree, hands the plan to your assistant,
|
|
106
|
+
and reports the branch + the plan's Verification Checklist when done.
|
|
60
107
|
|
|
61
108
|
Get started:
|
|
62
109
|
1. Run: npx mnemos-capture
|
|
@@ -74,6 +121,9 @@ function printHelp() {
|
|
|
74
121
|
The hook is installed automatically when you start the MCP server.
|
|
75
122
|
To upgrade to a full project briefing at session start:
|
|
76
123
|
npx mnemos-capture setup-hooks --key YOUR_API_KEY --briefing
|
|
124
|
+
|
|
125
|
+
To enable the vault (surfaces captures as you edit files — opt-in):
|
|
126
|
+
npx mnemos-capture setup-hooks --key YOUR_API_KEY --vault
|
|
77
127
|
`);
|
|
78
128
|
}
|
|
79
129
|
main().catch((err) => {
|
package/dist/cli/kos.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kos — Mnemos' Knowledge Operations orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Drives whatever AI assistant the user configured (`mnemos config set agent ...`)
|
|
5
|
+
* to implement a generated plan in an isolated git worktree. Mnemos owns the
|
|
6
|
+
* harness-independent parts: fetching the plan, the worktree, and reporting the
|
|
7
|
+
* Verification Checklist. The assistant is a black box — model-agnostic by design.
|
|
8
|
+
*/
|
|
9
|
+
import { tmpdir } from "os";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { writeFileSync, existsSync, appendFileSync, readFileSync } from "fs";
|
|
12
|
+
import { execSync } from "child_process";
|
|
13
|
+
import { callMcp } from "./hooks.js";
|
|
14
|
+
import { readConfig } from "./config.js";
|
|
15
|
+
// ── Pure helpers (exported for testing) ──
|
|
16
|
+
/** Extract `**filename.md**` plan names from a list_plans response. */
|
|
17
|
+
export function parsePlanFilenames(listText) {
|
|
18
|
+
const matches = listText.match(/\*\*([^*]+\.md)\*\*/g) ?? [];
|
|
19
|
+
return matches.map((m) => m.replace(/\*\*/g, ""));
|
|
20
|
+
}
|
|
21
|
+
/** Pick the newest plan. Filenames are date-prefixed (YYYY-MM-DD-...), so lexical desc = newest. */
|
|
22
|
+
export function latestPlanFilename(listText) {
|
|
23
|
+
const names = parsePlanFilenames(listText);
|
|
24
|
+
if (names.length === 0)
|
|
25
|
+
return null;
|
|
26
|
+
return [...names].sort((a, b) => b.localeCompare(a))[0] ?? null;
|
|
27
|
+
}
|
|
28
|
+
/** Pull the Verification Checklist items out of a plan's Markdown. */
|
|
29
|
+
export function parseChecklist(planText) {
|
|
30
|
+
const section = planText.match(/##\s*Verification Checklist\s*\n([\s\S]*?)(?=\n##\s|$)/i);
|
|
31
|
+
if (!section?.[1])
|
|
32
|
+
return [];
|
|
33
|
+
return section[1]
|
|
34
|
+
.split("\n")
|
|
35
|
+
.map((l) => l.trim())
|
|
36
|
+
.filter((l) => /^- \[[ x]\]/i.test(l))
|
|
37
|
+
.map((l) => l.replace(/^- \[[ x]\]\s*/i, ""));
|
|
38
|
+
}
|
|
39
|
+
/** Build a unique, sortable branch name for a kos run. */
|
|
40
|
+
export function buildKosBranch(now = new Date()) {
|
|
41
|
+
const stamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
42
|
+
return `mnemos/kos-${stamp}`;
|
|
43
|
+
}
|
|
44
|
+
/** The instruction handed to the assistant. The plan file is the real contract. */
|
|
45
|
+
export const KOS_PROMPT = "Implement the plan in MNEMOS_PLAN.md in this repository. " +
|
|
46
|
+
"Follow the Codebase Mapping table exactly — change only the files it lists. " +
|
|
47
|
+
"For each step apply the thinking effort named in its Effort column " +
|
|
48
|
+
"(low = quick/mechanical, medium = careful, high = deep reasoning). " +
|
|
49
|
+
"When finished, make sure every item in the Verification Checklist is satisfied.";
|
|
50
|
+
// ── Orchestrator ──
|
|
51
|
+
function run(cmd, cwd) {
|
|
52
|
+
return execSync(cmd, { cwd, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
53
|
+
}
|
|
54
|
+
export async function kos(apiKey, opts = {}) {
|
|
55
|
+
const cfg = readConfig();
|
|
56
|
+
if (!cfg.agent) {
|
|
57
|
+
console.error("No agent configured. Tell Mnemos which AI assistant to drive:\n");
|
|
58
|
+
console.error(' mnemos config set agent "claude -p" # Claude Code');
|
|
59
|
+
console.error(' mnemos config set agent "codex exec" # Codex');
|
|
60
|
+
console.error(' mnemos config set agent "aider --yes --message" # Aider');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
// 1. Resolve the plan — explicit filename, or the newest saved plan
|
|
64
|
+
const planFile = opts.plan ?? latestPlanFilename((await callMcp(apiKey, "list_plans", {}, 15000)) ?? "");
|
|
65
|
+
if (!planFile) {
|
|
66
|
+
console.error("No plans found. Generate one first with the generate_plan MCP tool.");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const planText = await callMcp(apiKey, "list_plans", { filename: planFile }, 15000);
|
|
70
|
+
if (!planText || !planText.trim()) {
|
|
71
|
+
console.error(`Could not read plan: ${planFile}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
// 2. Must be inside a git repo
|
|
75
|
+
let repoRoot;
|
|
76
|
+
try {
|
|
77
|
+
repoRoot = run("git rev-parse --show-toplevel");
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
console.error("mnemos kos must be run inside a git repository.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// 3. Isolated worktree on a fresh branch
|
|
85
|
+
const branch = buildKosBranch();
|
|
86
|
+
const worktreePath = join(tmpdir(), branch.replace(/\//g, "-"));
|
|
87
|
+
console.log(`Creating isolated worktree on branch ${branch}...`);
|
|
88
|
+
run(`git worktree add -b ${branch} "${worktreePath}"`, repoRoot);
|
|
89
|
+
// 4. Drop the plan in and keep git from tracking it (local exclude, not committed)
|
|
90
|
+
writeFileSync(join(worktreePath, "MNEMOS_PLAN.md"), planText);
|
|
91
|
+
try {
|
|
92
|
+
const excludePath = join(repoRoot, ".git", "info", "exclude");
|
|
93
|
+
if (existsSync(excludePath)) {
|
|
94
|
+
const current = readFileSync(excludePath, "utf-8");
|
|
95
|
+
if (!current.includes("MNEMOS_PLAN.md"))
|
|
96
|
+
appendFileSync(excludePath, "\nMNEMOS_PLAN.md\n");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// best-effort
|
|
101
|
+
}
|
|
102
|
+
// 5. Hand off to the user's assistant, running inside the worktree
|
|
103
|
+
console.log(`Launching: ${cfg.agent}\n`);
|
|
104
|
+
try {
|
|
105
|
+
execSync(`${cfg.agent} ${JSON.stringify(KOS_PROMPT)}`, { cwd: worktreePath, stdio: "inherit" });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
console.error("\nThe agent exited with an error. The worktree is preserved for inspection.");
|
|
109
|
+
}
|
|
110
|
+
// 6. Report — branch + the plan's own checklist as the review gate
|
|
111
|
+
const checklist = parseChecklist(planText);
|
|
112
|
+
console.log(`\n${"─".repeat(60)}`);
|
|
113
|
+
console.log(`kos finished. Plan: ${planFile}`);
|
|
114
|
+
console.log(`Branch: ${branch}`);
|
|
115
|
+
console.log(`Worktree: ${worktreePath}`);
|
|
116
|
+
if (checklist.length > 0) {
|
|
117
|
+
console.log(`\nVerify before merging:`);
|
|
118
|
+
for (const item of checklist)
|
|
119
|
+
console.log(` [ ] ${item}`);
|
|
120
|
+
}
|
|
121
|
+
console.log(`\nReview the branch, then merge. Clean up the worktree with:`);
|
|
122
|
+
console.log(` git worktree remove "${worktreePath}"`);
|
|
123
|
+
}
|
package/dist/cli/mcp-server.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Used by Claude Code: claude mcp add mnemos -- npx mnemos-capture serve-mcp --key <api-key>
|
|
3
3
|
const HOSTED_URL = "https://mnemos-capture.vercel.app/api/mcp";
|
|
4
4
|
function sendMessage(msg) {
|
|
5
|
-
|
|
6
|
-
process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
|
|
5
|
+
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
7
6
|
}
|
|
8
7
|
async function proxyToHosted(apiKey, msg) {
|
|
9
8
|
try {
|
|
@@ -62,31 +61,21 @@ export async function serveMcp() {
|
|
|
62
61
|
process.stdin.setEncoding("utf-8");
|
|
63
62
|
process.stdin.on("data", (chunk) => {
|
|
64
63
|
buffer += chunk;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
71
|
-
if (!match) {
|
|
72
|
-
buffer = buffer.slice(headerEnd + 4);
|
|
64
|
+
let newlineIdx;
|
|
65
|
+
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
66
|
+
const line = buffer.slice(0, newlineIdx).trim();
|
|
67
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
68
|
+
if (!line)
|
|
73
69
|
continue;
|
|
74
|
-
}
|
|
75
|
-
const contentLength = parseInt(match[1], 10);
|
|
76
|
-
const bodyStart = headerEnd + 4;
|
|
77
|
-
if (buffer.length < bodyStart + contentLength)
|
|
78
|
-
break;
|
|
79
|
-
const body = buffer.slice(bodyStart, bodyStart + contentLength);
|
|
80
|
-
buffer = buffer.slice(bodyStart + contentLength);
|
|
81
70
|
try {
|
|
82
|
-
const msg = JSON.parse(
|
|
71
|
+
const msg = JSON.parse(line);
|
|
83
72
|
// Handle notifications/initialized locally (no response needed)
|
|
84
73
|
if (msg.method === "notifications/initialized")
|
|
85
74
|
continue;
|
|
86
75
|
void proxyToHosted(apiKey, msg);
|
|
87
76
|
}
|
|
88
77
|
catch {
|
|
89
|
-
process.stderr.write(`Failed to parse: ${
|
|
78
|
+
process.stderr.write(`Failed to parse: ${line}\n`);
|
|
90
79
|
}
|
|
91
80
|
}
|
|
92
81
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnemos-capture",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Capture anything. Your agents apply it. A knowledge pipeline that ingests the outside world and makes it directly actionable by any AI agent over MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mnemos-capture": "./dist/cli/index.js"
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"build": "next build",
|
|
12
12
|
"start": "next start",
|
|
13
13
|
"build:cli": "tsc -p cli/tsconfig.json",
|
|
14
|
-
"prepublishOnly": "npm run build:cli"
|
|
14
|
+
"prepublishOnly": "npm run build:cli",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"test:e2e": "playwright test"
|
|
15
18
|
},
|
|
16
19
|
"keywords": [
|
|
17
20
|
"ai",
|
|
@@ -45,13 +48,15 @@
|
|
|
45
48
|
"react-dom": "^19.0.0"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|
|
51
|
+
"@playwright/test": "^1.61.1",
|
|
48
52
|
"@types/node": "^20",
|
|
49
53
|
"@types/react": "^19",
|
|
50
54
|
"@types/react-dom": "^19",
|
|
51
55
|
"autoprefixer": "^10.4.21",
|
|
52
56
|
"postcss": "^8.5.3",
|
|
53
57
|
"tailwindcss": "^3.4.17",
|
|
54
|
-
"typescript": "^5"
|
|
58
|
+
"typescript": "^5",
|
|
59
|
+
"vitest": "^4.1.8"
|
|
55
60
|
},
|
|
56
61
|
"engines": {
|
|
57
62
|
"node": ">=18"
|