peon-mem 1.0.0
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/LICENSE +21 -0
- package/README.md +301 -0
- package/bin/peon-mem.mjs +273 -0
- package/dist/brain.d.ts +72 -0
- package/dist/brain.js +224 -0
- package/dist/compression.d.ts +9 -0
- package/dist/compression.js +37 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +99 -0
- package/dist/daemon-cli.d.ts +2 -0
- package/dist/daemon-cli.js +54 -0
- package/dist/daemon.d.ts +23 -0
- package/dist/daemon.js +1078 -0
- package/dist/embedding-store.d.ts +43 -0
- package/dist/embedding-store.js +169 -0
- package/dist/embeddings.d.ts +93 -0
- package/dist/embeddings.js +345 -0
- package/dist/entities.d.ts +61 -0
- package/dist/entities.js +191 -0
- package/dist/entity-extraction.d.ts +33 -0
- package/dist/entity-extraction.js +75 -0
- package/dist/eval-metrics.d.ts +27 -0
- package/dist/eval-metrics.js +50 -0
- package/dist/evaluation.d.ts +58 -0
- package/dist/evaluation.js +244 -0
- package/dist/global-extraction.d.ts +15 -0
- package/dist/global-extraction.js +61 -0
- package/dist/global-memory.d.ts +43 -0
- package/dist/global-memory.js +306 -0
- package/dist/global-promotion.d.ts +25 -0
- package/dist/global-promotion.js +29 -0
- package/dist/hyde.d.ts +31 -0
- package/dist/hyde.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +246 -0
- package/dist/injection.d.ts +38 -0
- package/dist/injection.js +133 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.js +63 -0
- package/dist/memory-mutations.d.ts +24 -0
- package/dist/memory-mutations.js +57 -0
- package/dist/memory-store.d.ts +194 -0
- package/dist/memory-store.js +1205 -0
- package/dist/monitor.d.ts +13 -0
- package/dist/monitor.js +977 -0
- package/dist/overview.d.ts +73 -0
- package/dist/overview.js +104 -0
- package/dist/processor.d.ts +90 -0
- package/dist/processor.js +450 -0
- package/dist/quality.d.ts +86 -0
- package/dist/quality.js +338 -0
- package/dist/recuration.d.ts +13 -0
- package/dist/recuration.js +65 -0
- package/dist/reranker.d.ts +34 -0
- package/dist/reranker.js +89 -0
- package/dist/retrieval.d.ts +106 -0
- package/dist/retrieval.js +392 -0
- package/dist/session-index.d.ts +34 -0
- package/dist/session-index.js +87 -0
- package/dist/temporal.d.ts +20 -0
- package/dist/temporal.js +62 -0
- package/dist/token-ab-monitor.d.ts +1 -0
- package/dist/token-ab-monitor.js +7 -0
- package/dist/tools.d.ts +232 -0
- package/dist/tools.js +546 -0
- package/dist/types.d.ts +169 -0
- package/dist/types.js +1 -0
- package/docs/assets/neural-universe.png +0 -0
- package/package.json +57 -0
- package/scripts/claude-peon-hook.mjs +522 -0
- package/scripts/codex-peon-hook.mjs +4 -0
- package/scripts/eval-retrieval-labeled.mjs +135 -0
- package/scripts/eval-retrieval.mjs +96 -0
- package/scripts/evaluate-peon.mjs +47 -0
- package/scripts/install-peon-stl.mjs +82 -0
- package/scripts/install-peon.mjs +318 -0
- package/scripts/lib/eval-ledger.mjs +104 -0
- package/scripts/lib/stl-classify.mjs +44 -0
- package/scripts/longmemeval-eval.mjs +144 -0
- package/scripts/peon-report.mjs +155 -0
- package/scripts/peon-stl.mjs +506 -0
- package/scripts/token-ab-monitor.html +235 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vineet Vora
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# ๐ง Peon โ a memory brain for your AI coding agents
|
|
2
|
+
|
|
3
|
+
**Local-first, hierarchical, self-improving memory for Claude Code, Codex, and any MCP client.**
|
|
4
|
+
|
|
5
|
+
Your AI forgets everything between sessions. Peon doesn't. It records your sessions, consolidates
|
|
6
|
+
them into typed *beliefs* with an LLM, and injects the relevant ones back into every prompt โ
|
|
7
|
+
automatically, from a daemon that never leaves your machine.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
PEON GLOBAL BRAIN โ user-level facts & preferences, inherited everywhere
|
|
11
|
+
/ | \
|
|
12
|
+
project A project B project C โ rooted child brains (.peon/ in each project)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why Peon
|
|
16
|
+
|
|
17
|
+
- **Hierarchical brains** โ one global parent brain (who you are, your rules, your tools) plus an
|
|
18
|
+
isolated child brain per project. Every injection = project memory + inherited global memory.
|
|
19
|
+
- **Two memory layers, honestly measured** โ consolidated *beliefs* (decisions, preferences, facts,
|
|
20
|
+
artifactsโฆ) for gist, plus an *episodic* verbatim layer that recovers exact details lossy
|
|
21
|
+
summaries drop (measured on LongMemEval: raw-episodic recall 61% vs belief-only 17%).
|
|
22
|
+
- **Auto capture + auto injection** โ Claude Code hooks record messages/events and inject a
|
|
23
|
+
query-ranked memory block (with an `โ MOST RELEVANT` headline) into every prompt. Zero effort.
|
|
24
|
+
- **Cost-gated consolidation** โ an LLM distills sessions into beliefs only when enough new
|
|
25
|
+
memory accumulates. Supersede / merge / conflict-detect; nothing is destructively deleted.
|
|
26
|
+
- **Hybrid retrieval** โ lexical + semantic RRF fusion, MMR diversity, reinforcement, recency;
|
|
27
|
+
query-embedding cache (persisted) so repeat prompts cost nothing.
|
|
28
|
+
- **The Neural Universe** โ a live monitor at `localhost:3737/monitor` that renders every belief
|
|
29
|
+
as a star: projects are galaxies, search makes matches flare, autonomous curation pulses.
|
|
30
|
+
- **A daily self-improvement loop (STL)** โ Peon audits itself every day: what it recorded,
|
|
31
|
+
injected, what failed, what consolidation did โ and files a report with a verdict.
|
|
32
|
+
- **Eval-gated development** โ a committed results ledger (git SHA + qrels + brain fingerprint per
|
|
33
|
+
row) so retrieval changes are *proven*, not asserted. Negative results stay documented.
|
|
34
|
+
- **Local-first & locked down** โ plain JSONL you can read, loopback-only daemon with
|
|
35
|
+
DNS-rebinding protection, secret redaction at the injection boundary, path-traversal guards.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
*The live monitor: 18k real beliefs rendered as stars. Type to make matching beliefs flare; click one to inspect it.*
|
|
42
|
+
|
|
43
|
+
## Why "Peon"?
|
|
44
|
+
|
|
45
|
+
The name comes from Indian offices. Every office had a **peon** โ the person who walked desk to
|
|
46
|
+
desk all day: collect a file from this table, note who needs what, carry it to the next table,
|
|
47
|
+
remember where everything is. Not the boss, not the star โ but the one person the whole office
|
|
48
|
+
quietly ran on. Nothing moved without him, and he never forgot where anything was.
|
|
49
|
+
|
|
50
|
+
That's exactly this framework, with AI. Peon walks between your sessions and your projects โ
|
|
51
|
+
collects what happened at one desk (a session), files it in the right cabinet (a project brain),
|
|
52
|
+
carries the relevant papers to the next desk before you ask (injection), and keeps the master
|
|
53
|
+
ledger upstairs (the global brain). Quiet clerk. Perfect memory. The office runs on him.
|
|
54
|
+
|
|
55
|
+
## How Peon differs from existing memory tools
|
|
56
|
+
|
|
57
|
+
| | **Peon** | mem0 | Letta/MemGPT | Zep/Graphiti | flat memory files (MEMORY.md) |
|
|
58
|
+
|---|---|---|---|---|---|
|
|
59
|
+
| Runs | **100% local daemon** | cloud or self-host | server | cloud/server | local |
|
|
60
|
+
| Storage | human-readable JSONL you can `cat` | vector DB | DB | graph DB | markdown |
|
|
61
|
+
| Memory model | **beliefs + verbatim episodic layer** | extracted facts | self-edited blocks | temporal knowledge graph | prose |
|
|
62
|
+
| Hierarchy | **global parent brain โ per-project child brains, inherited on every prompt** | user/agent/session scopes | per-agent | per-user | per-project file |
|
|
63
|
+
| Capture | **automatic via hooks** (zero effort) | SDK calls you write | agent-managed | SDK calls | agent must remember to write |
|
|
64
|
+
| Conflict handling | supersede/merge, **recoverable โ never hard-deletes** | LLM may DELETE | self-edit | invalidation | overwrite |
|
|
65
|
+
| Exact recall | episodic layer regression-tested (61% vs 17% belief-only, LongMemEval) | gist only | gist only | graph facts | whatever was written |
|
|
66
|
+
| Observability | **live Neural Universe monitor + daily self-audit (STL) + serve telemetry** | dashboard | โ | โ | โ |
|
|
67
|
+
| Verification | **committed eval ledger; negative results kept** | vendor benchmarks | โ | vendor benchmarks | โ |
|
|
68
|
+
|
|
69
|
+
Positioning in one line: mem0/Zep are memory **platforms for products you build**; Peon is a
|
|
70
|
+
memory **brain for the coding agents you already use** โ plug into Claude Code/Codex in five
|
|
71
|
+
minutes, watch it think, audit every number.
|
|
72
|
+
|
|
73
|
+
## Quickstart
|
|
74
|
+
|
|
75
|
+
Requirements: Node 20+, macOS or Linux. An [OpenRouter](https://openrouter.ai) API key is
|
|
76
|
+
recommended (consolidation + semantic embeddings); without one Peon still works lexical-only.
|
|
77
|
+
|
|
78
|
+
One line:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
curl -fsSL https://raw.githubusercontent.com/VineetV2/peon-mem/main/install.sh | bash
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
(or, once you have the CLI: `npm install -g peon-mem && peon-mem install`)
|
|
85
|
+
|
|
86
|
+
The guided setup asks four things:
|
|
87
|
+
|
|
88
|
+
1. **Where your global brain lives** (default: `~/Library/Application Support/Peon`)
|
|
89
|
+
2. **Which LLM** โ OpenRouter (one key, any model) ยท OpenAI ยท Anthropic ยท **Ollama (100% local & free)** ยท or skip
|
|
90
|
+
3. Installs the **daemon** as an auto-start service
|
|
91
|
+
4. **Detects your AI apps** and wires the MCP server (+ hooks for Claude Code) into the ones you
|
|
92
|
+
pick โ auto-configured: Claude Code, Claude Desktop, Codex, Gemini CLI, Cursor, Windsurf,
|
|
93
|
+
VS Code (Copilot MCP), Zed, LM Studio; detected with in-app instructions: ChatGPT Desktop,
|
|
94
|
+
Perplexity Desktop. Every touched config gets a `.peon-backup`.
|
|
95
|
+
|
|
96
|
+
That builds the package, starts the daemon as a service, wires your Claude Code hooks + MCP
|
|
97
|
+
server (with a backup of your settings), and writes a config template. Then add your key to
|
|
98
|
+
`~/Library/Application Support/Peon/.env` and open the monitor. `peon-mem install --dry-run`
|
|
99
|
+
shows every action first; `peon-mem uninstall` reverses it (memory data is never touched).
|
|
100
|
+
|
|
101
|
+
Or manually:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/VineetV2/peon-mem && cd peon-mem
|
|
105
|
+
npm install && npm run build
|
|
106
|
+
node bin/peon-mem.mjs install --dry-run # inspect, then run without --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Create `.env` in the repo root:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
113
|
+
PEON_PROCESSING_MODEL=google/gemini-2.5-flash-lite # cheap + good enough (measured)
|
|
114
|
+
PEON_EMBEDDING_MODEL=openai/text-embedding-3-small
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Start the daemon (the installer prints a launchd/systemd recipe, or just):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
node dist/daemon-cli.js # serves 127.0.0.1:3737
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then wire your agent (the installer prints these filled in for your paths):
|
|
124
|
+
|
|
125
|
+
- **Claude Code** โ add the hook to `~/.claude/settings.json` (SessionStart / UserPromptSubmit /
|
|
126
|
+
SessionEnd โ `scripts/claude-peon-hook.mjs`) and the MCP server (`dist/index.js`).
|
|
127
|
+
- **Codex / any MCP client** โ register `dist/index.js` as a stdio MCP server; 16 tools
|
|
128
|
+
(`start_session`, `get_context`, `search_memory`, `record_message`, `process_memory`, โฆ).
|
|
129
|
+
|
|
130
|
+
Open `http://127.0.0.1:3737/monitor` and watch your brain grow.
|
|
131
|
+
|
|
132
|
+
## How it works
|
|
133
|
+
|
|
134
|
+
1. **Record** โ hooks stream messages/events/tool-calls into `<project>/.peon/raw/` (append-only).
|
|
135
|
+
2. **Consolidate** โ past a size gate, an LLM turns the session delta into typed belief records in
|
|
136
|
+
`.peon/brain/memories.jsonl` (importance/confidence scores, entities, provenance pointers),
|
|
137
|
+
reconciling against existing beliefs: supersede, merge, conflict-flag. Recoverable, never deleted.
|
|
138
|
+
3. **Retrieve + inject** โ on every prompt, beliefs are ranked (RRF lexical+semantic, MMR,
|
|
139
|
+
reinforcement) and injected alongside episodic verbatim matches and inherited global beliefs.
|
|
140
|
+
4. **Self-curate** โ a background brain pass reinforces recalled beliefs, compresses stale
|
|
141
|
+
clusters, resolves duplicates โ every action logged and undoable.
|
|
142
|
+
5. **Self-audit (STL)** โ a daily job reports: recorded / injected / went-wrong / consolidation
|
|
143
|
+
correctness, with serve-latency telemetry and a health verdict.
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
## Full install (copy-paste)
|
|
147
|
+
|
|
148
|
+
### 1. Daemon (always-on, macOS launchd)
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
node scripts/install-peon.mjs # prints everything below filled in for YOUR paths
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or manually โ `~/Library/LaunchAgents/com.peon.daemon.plist`:
|
|
155
|
+
|
|
156
|
+
```xml
|
|
157
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
158
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
159
|
+
<plist version="1.0"><dict>
|
|
160
|
+
<key>Label</key><string>com.peon.daemon</string>
|
|
161
|
+
<key>ProgramArguments</key><array>
|
|
162
|
+
<string>/opt/homebrew/bin/node</string>
|
|
163
|
+
<string>/ABSOLUTE/PATH/TO/peon/dist/daemon-cli.js</string>
|
|
164
|
+
</array>
|
|
165
|
+
<key>RunAtLoad</key><true/>
|
|
166
|
+
<key>KeepAlive</key><true/>
|
|
167
|
+
</dict></plist>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
launchctl load ~/Library/LaunchAgents/com.peon.daemon.plist
|
|
172
|
+
curl http://127.0.0.1:3737/health # โ {"ok":true}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Linux: run `node dist/daemon-cli.js` under systemd (`Restart=always`).
|
|
176
|
+
|
|
177
|
+
### 2. Claude Code โ hooks (auto capture + injection)
|
|
178
|
+
|
|
179
|
+
Merge into `~/.claude/settings.json` (replace the path):
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"hooks": {
|
|
184
|
+
"SessionStart": [{ "hooks": [{ "type": "command",
|
|
185
|
+
"command": "node /ABSOLUTE/PATH/TO/peon/scripts/claude-peon-hook.mjs" }] }],
|
|
186
|
+
"UserPromptSubmit": [{ "hooks": [{ "type": "command",
|
|
187
|
+
"command": "node /ABSOLUTE/PATH/TO/peon/scripts/claude-peon-hook.mjs" }] }],
|
|
188
|
+
"SessionEnd": [{ "hooks": [{ "type": "command",
|
|
189
|
+
"command": "node /ABSOLUTE/PATH/TO/peon/scripts/claude-peon-hook.mjs" }] }]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 3. Claude Code โ MCP server (search/inspect tools)
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
claude mcp add peon -- node /ABSOLUTE/PATH/TO/peon/dist/index.js
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 4. Codex / any MCP client
|
|
201
|
+
|
|
202
|
+
`~/.codex/config.toml`:
|
|
203
|
+
|
|
204
|
+
```toml
|
|
205
|
+
[mcp_servers.peon]
|
|
206
|
+
command = "node"
|
|
207
|
+
args = ["/ABSOLUTE/PATH/TO/peon/dist/index.js"]
|
|
208
|
+
[mcp_servers.peon.env]
|
|
209
|
+
PEON_DAEMON_URL = "http://127.0.0.1:3737"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Codex has no hooks โ add usage rules to `~/.codex/AGENTS.md` telling it to call
|
|
213
|
+
`start_session` + `get_context` at session start and `record_message` for durable facts
|
|
214
|
+
(example block in [docs/](docs/)).
|
|
215
|
+
|
|
216
|
+
### 5. Verify
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
curl "http://127.0.0.1:3737/context?projectPath=$PWD&query=test" # JSON context
|
|
220
|
+
open http://127.0.0.1:3737/monitor # the Neural Universe
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Start a Claude Code session in any project, say something decision-shaped, end the session โ
|
|
224
|
+
within a minute the monitor shows the belief. Next session injects it.
|
|
225
|
+
|
|
226
|
+
### MCP tools exposed
|
|
227
|
+
|
|
228
|
+
`start_session` ยท `record_message` ยท `record_event` ยท `end_session` ยท `get_context` ยท
|
|
229
|
+
`search_memory` ยท `inspect_brain` ยท `build_injection` ยท `query_projects` (cross-project search) ยท
|
|
230
|
+
`quality_report` ยท `remember_global` ยท `search_global_memory` ยท `import_global_memory` ยท
|
|
231
|
+
`evaluate_project` ยท `process_memory` ยท `maybe_process_memory`
|
|
232
|
+
|
|
233
|
+
### Uninstall
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
launchctl unload ~/Library/LaunchAgents/com.peon.daemon.plist
|
|
237
|
+
# remove the hook entries + MCP server from your agent config
|
|
238
|
+
# your memory stays in <project>/.peon/ and ~/Library/Application\ Support/Peon/ โ plain files, delete when ready
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Troubleshooting / FAQ
|
|
242
|
+
|
|
243
|
+
- **No injection appearing?** `curl http://127.0.0.1:3737/health`; check hook is registered
|
|
244
|
+
(`claude` โ run any prompt โ monitor Systems page shows the request).
|
|
245
|
+
- **431 errors on huge prompts?** Handled โ the hook caps the retrieval query at 2k chars.
|
|
246
|
+
- **No OpenRouter key?** Everything still runs; retrieval is lexical + episodic only
|
|
247
|
+
(semantic ranking and consolidation need a model). `PEON_EMBEDDING_MODE=ollama` works too.
|
|
248
|
+
- **Cost?** Consolidation is gated (default: fires per ~6k new chars, ~cents/day with
|
|
249
|
+
flash-lite). Query embeddings are cached to disk โ repeats are free.
|
|
250
|
+
- **Multiple machines?** Brains are plain files in your repos โ commit `.peon/` if you want
|
|
251
|
+
memory to travel (redact first: raw layer contains session text).
|
|
252
|
+
- **Is my data sent anywhere?** Only consolidation/embedding calls to your configured model
|
|
253
|
+
provider. No telemetry, no cloud store. Daemon rejects non-loopback callers.
|
|
254
|
+
|
|
255
|
+
## Configuration (env)
|
|
256
|
+
|
|
257
|
+
| Var | Default | Purpose |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `OPENROUTER_API_KEY` | โ | consolidation + embeddings |
|
|
260
|
+
| `PEON_PROCESSING_MODEL` | `google/gemini-2.5-flash-lite` | consolidation model |
|
|
261
|
+
| `PEON_EMBEDDING_MODE` | auto | `api` / `ollama` / `local` / `off` |
|
|
262
|
+
| `PEON_EMBEDDING_MODEL` | โ | e.g. `openai/text-embedding-3-small` or an Ollama model |
|
|
263
|
+
| `PEON_OLLAMA_URL` | `http://127.0.0.1:11434` | local embedding server |
|
|
264
|
+
| `PEON_AI_MODE` | `gated` | `off` disables all LLM calls |
|
|
265
|
+
| `PEON_FLUSH_MIN_CHARS` | `6000` | consolidation cost gate |
|
|
266
|
+
| `PEON_MEMORY_DIR` | `.peon` | per-project brain dir name |
|
|
267
|
+
| `PEON_DAEMON_URL` | `http://127.0.0.1:3737` | daemon address |
|
|
268
|
+
| `PEON_CONSOLIDATION_MAX_DELTA_CHARS` | `60000` | anti truncation-stall chunking |
|
|
269
|
+
| `PEON_DISABLED` | โ | hard off-switch for A/B testing |
|
|
270
|
+
|
|
271
|
+
## Project brains
|
|
272
|
+
|
|
273
|
+
- A brain lives in `<project>/.peon/` โ human-readable JSONL + markdown. Commit it or ignore it;
|
|
274
|
+
your choice (`.gitignore` ships ignoring it).
|
|
275
|
+
- `.peon/root` marks a brain boundary. New brains are born rooted; a parent directory can never
|
|
276
|
+
swallow a project's memory.
|
|
277
|
+
- The global brain lives in `~/Library/Application Support/Peon/global/` (macOS).
|
|
278
|
+
|
|
279
|
+
## Honesty section
|
|
280
|
+
|
|
281
|
+
Peon's development is eval-gated and keeps its negative results: an associative entity graph was
|
|
282
|
+
built, measured (โ2.9% Recall@10), and turned OFF by default. Consolidation is lossy by design โ
|
|
283
|
+
that's why the episodic layer exists and is regression-tested. The eval harness + committed
|
|
284
|
+
ledger (`npm run eval`) let you verify retrieval changes on your own brain.
|
|
285
|
+
|
|
286
|
+
## Security
|
|
287
|
+
|
|
288
|
+
- Daemon binds `127.0.0.1` only and rejects non-loopback `Host`/`Origin` (DNS-rebinding defense).
|
|
289
|
+
- Secrets (API keys, tokens, JWTs) are redacted at the injection boundary.
|
|
290
|
+
- Path-traversal guarded; per-project write locks; atomic tmp+rename writes; automatic backups
|
|
291
|
+
before destructive-adjacent operations. Nothing is hard-deleted.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT ยฉ Vineet Vora
|
|
296
|
+
|
|
297
|
+
## Contributing
|
|
298
|
+
|
|
299
|
+
PRs welcome. Rules of the house: every retrieval/quality change ships with a test and an
|
|
300
|
+
eval-ledger run (`npm run eval`); negative results get documented, not deleted; nothing may
|
|
301
|
+
hard-delete user memory. `npm test` must stay green (255 tests).
|
package/bin/peon-mem.mjs
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* peon-mem โ guided installer/manager for the Peon memory brain.
|
|
4
|
+
*
|
|
5
|
+
* peon-mem install [--yes] [--dry-run] guided setup: memory home โ LLM โ daemon โ MCP apps
|
|
6
|
+
* peon-mem uninstall remove service + hooks (memory data is never touched)
|
|
7
|
+
* peon-mem daemon run the daemon in the foreground
|
|
8
|
+
* peon-mem doctor health + config check
|
|
9
|
+
*/
|
|
10
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from "node:fs";
|
|
12
|
+
import { homedir, platform } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { createInterface } from "node:readline/promises";
|
|
16
|
+
|
|
17
|
+
const PKG = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
|
+
const HOME = homedir();
|
|
19
|
+
const MAC = platform() === "darwin";
|
|
20
|
+
const DEFAULT_HOME = MAC ? join(HOME, "Library", "Application Support", "Peon") : join(HOME, ".local", "share", "peon");
|
|
21
|
+
const PLIST = join(HOME, "Library", "LaunchAgents", "com.peon.daemon.plist");
|
|
22
|
+
const HOOK = join(PKG, "scripts", "claude-peon-hook.mjs");
|
|
23
|
+
const DAEMON = join(PKG, "dist", "daemon-cli.js");
|
|
24
|
+
const MCP = join(PKG, "dist", "index.js");
|
|
25
|
+
const NODE = process.execPath;
|
|
26
|
+
|
|
27
|
+
const cmd = process.argv[2] || "help";
|
|
28
|
+
const DRY = process.argv.includes("--dry-run");
|
|
29
|
+
const YES = process.argv.includes("--yes") || !process.stdout.isTTY;
|
|
30
|
+
const log = (s) => console.log(s);
|
|
31
|
+
const act = (desc, fn) => { log((DRY ? " [dry-run] " : " โ ") + desc); if (!DRY) fn(); };
|
|
32
|
+
const rl = YES ? null : createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
async function ask(q, def) {
|
|
34
|
+
if (!rl) return def;
|
|
35
|
+
const a = (await rl.question(`${q}${def ? ` [${def}]` : ""}: `)).trim();
|
|
36
|
+
return a || def;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function which(bin) { return spawnSync("which", [bin], { stdio: "pipe" }).status === 0; }
|
|
40
|
+
function backupWrite(file, content) {
|
|
41
|
+
if (existsSync(file)) copyFileSync(file, file + ".peon-backup");
|
|
42
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
43
|
+
writeFileSync(file, content);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------- app detection ----------
|
|
47
|
+
// wire kinds: hooks+CLI (claude code) ยท toml (codex) ยท mcpServers JSON (most apps) ยท
|
|
48
|
+
// vscode "servers" JSON ยท zed context_servers ยท manual (UI-configured apps get instructions)
|
|
49
|
+
const APP_SUPPORT = MAC ? join(HOME, "Library", "Application Support") : join(HOME, ".config");
|
|
50
|
+
function detectApps() {
|
|
51
|
+
return [
|
|
52
|
+
{ id: "claude", name: "Claude Code", kind: "claude-code",
|
|
53
|
+
found: which("claude") || existsSync(join(HOME, ".claude")) },
|
|
54
|
+
{ id: "claude-desktop", name: "Claude Desktop", kind: "json",
|
|
55
|
+
file: join(APP_SUPPORT, "Claude", "claude_desktop_config.json"),
|
|
56
|
+
found: existsSync(join(APP_SUPPORT, "Claude")) || existsSync("/Applications/Claude.app") },
|
|
57
|
+
{ id: "codex", name: "Codex", kind: "toml",
|
|
58
|
+
found: existsSync(join(HOME, ".codex")) },
|
|
59
|
+
{ id: "gemini", name: "Gemini CLI", kind: "json",
|
|
60
|
+
file: join(HOME, ".gemini", "settings.json"),
|
|
61
|
+
found: which("gemini") || existsSync(join(HOME, ".gemini")) },
|
|
62
|
+
{ id: "cursor", name: "Cursor", kind: "json",
|
|
63
|
+
file: join(HOME, ".cursor", "mcp.json"),
|
|
64
|
+
found: existsSync(join(HOME, ".cursor")) || existsSync("/Applications/Cursor.app") },
|
|
65
|
+
{ id: "windsurf", name: "Windsurf", kind: "json",
|
|
66
|
+
file: join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
67
|
+
found: existsSync(join(HOME, ".codeium", "windsurf")) || existsSync("/Applications/Windsurf.app") },
|
|
68
|
+
{ id: "vscode", name: "VS Code (Copilot MCP)", kind: "vscode",
|
|
69
|
+
file: MAC ? join(APP_SUPPORT, "Code", "User", "mcp.json") : join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
70
|
+
found: which("code") || existsSync(MAC ? join(APP_SUPPORT, "Code") : join(HOME, ".config", "Code")) },
|
|
71
|
+
{ id: "zed", name: "Zed", kind: "zed",
|
|
72
|
+
file: join(HOME, ".config", "zed", "settings.json"),
|
|
73
|
+
found: existsSync(join(HOME, ".config", "zed")) || existsSync("/Applications/Zed.app") },
|
|
74
|
+
{ id: "lmstudio", name: "LM Studio", kind: "json",
|
|
75
|
+
file: join(HOME, ".lmstudio", "mcp.json"),
|
|
76
|
+
found: existsSync(join(HOME, ".lmstudio")) || existsSync("/Applications/LM Studio.app") },
|
|
77
|
+
{ id: "chatgpt", name: "ChatGPT Desktop", kind: "manual",
|
|
78
|
+
found: existsSync("/Applications/ChatGPT.app"),
|
|
79
|
+
how: "ChatGPT โ Settings โ Connectors โ Advanced โ enable Developer Mode โ add MCP server: command=" },
|
|
80
|
+
{ id: "perplexity", name: "Perplexity Desktop", kind: "manual",
|
|
81
|
+
found: existsSync("/Applications/Perplexity.app") || existsSync("/Applications/Perplexity- Ask Anything.app"),
|
|
82
|
+
how: "Perplexity โ Settings โ Connectors โ Add Connector โ Advanced: command=" }
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------- per-app wiring ----------
|
|
87
|
+
function wireClaude() {
|
|
88
|
+
const settings = join(HOME, ".claude", "settings.json");
|
|
89
|
+
let s = {}; try { s = JSON.parse(readFileSync(settings, "utf8")); } catch {}
|
|
90
|
+
s.hooks = s.hooks || {};
|
|
91
|
+
let changed = false;
|
|
92
|
+
for (const ev of ["SessionStart", "UserPromptSubmit", "SessionEnd"]) {
|
|
93
|
+
const arr = (s.hooks[ev] = s.hooks[ev] || []);
|
|
94
|
+
if (!JSON.stringify(arr).includes("claude-peon-hook.mjs")) {
|
|
95
|
+
arr.push({ hooks: [{ type: "command", command: `"${NODE}" "${HOOK}"` }] }); changed = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (changed) act("Claude Code: hooks โ " + settings + " (backup kept)", () => backupWrite(settings, JSON.stringify(s, null, 2) + "\n"));
|
|
99
|
+
else log(" โ Claude Code: hooks already present");
|
|
100
|
+
if (!DRY) {
|
|
101
|
+
const r = spawnSync("claude", ["mcp", "add", "peon", "--", NODE, MCP], { stdio: "ignore" });
|
|
102
|
+
log(r.status === 0 ? " โ Claude Code: MCP server registered" : ` โ run manually: claude mcp add peon -- "${NODE}" "${MCP}"`);
|
|
103
|
+
} else log(" [dry-run] Claude Code: claude mcp add peon");
|
|
104
|
+
}
|
|
105
|
+
function wireCodex() {
|
|
106
|
+
const f = join(HOME, ".codex", "config.toml");
|
|
107
|
+
let s = ""; try { s = readFileSync(f, "utf8"); } catch {}
|
|
108
|
+
if (s.includes("[mcp_servers.peon]")) return log(" โ Codex: MCP already configured");
|
|
109
|
+
const block = `\n[mcp_servers.peon]\ncommand = "${NODE}"\nargs = ["${MCP}"]\n\n[mcp_servers.peon.env]\nPEON_DAEMON_URL = "http://127.0.0.1:3737"\n`;
|
|
110
|
+
act("Codex: [mcp_servers.peon] โ " + f + " (backup kept)", () => backupWrite(f, s + block));
|
|
111
|
+
}
|
|
112
|
+
function wireJsonMcp(name, file) {
|
|
113
|
+
let s = {}; try { s = JSON.parse(readFileSync(file, "utf8")); } catch {}
|
|
114
|
+
s.mcpServers = s.mcpServers || {};
|
|
115
|
+
if (s.mcpServers.peon) return log(` โ ${name}: MCP already configured`);
|
|
116
|
+
s.mcpServers.peon = { command: NODE, args: [MCP], env: { PEON_DAEMON_URL: "http://127.0.0.1:3737" } };
|
|
117
|
+
act(`${name}: mcpServers.peon โ ${file} (backup kept)`, () => backupWrite(file, JSON.stringify(s, null, 2) + "\n"));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function wireVsCode(file) {
|
|
121
|
+
let s = {}; try { s = JSON.parse(readFileSync(file, "utf8")); } catch {}
|
|
122
|
+
s.servers = s.servers || {};
|
|
123
|
+
if (s.servers.peon) return log(" โ VS Code: MCP already configured");
|
|
124
|
+
s.servers.peon = { type: "stdio", command: NODE, args: [MCP], env: { PEON_DAEMON_URL: "http://127.0.0.1:3737" } };
|
|
125
|
+
act("VS Code: servers.peon โ " + file + " (backup kept)", () => backupWrite(file, JSON.stringify(s, null, 2) + "\n"));
|
|
126
|
+
}
|
|
127
|
+
function wireZed(file) {
|
|
128
|
+
let s = {}; try { s = JSON.parse(readFileSync(file, "utf8")); } catch {}
|
|
129
|
+
s.context_servers = s.context_servers || {};
|
|
130
|
+
if (s.context_servers.peon) return log(" โ Zed: MCP already configured");
|
|
131
|
+
s.context_servers.peon = { command: { path: NODE, args: [MCP] }, settings: {} };
|
|
132
|
+
act("Zed: context_servers.peon โ " + file + " (backup kept)", () => backupWrite(file, JSON.stringify(s, null, 2) + "\n"));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------- service ----------
|
|
136
|
+
function installService(memoryHome) {
|
|
137
|
+
if (MAC) {
|
|
138
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
139
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
140
|
+
<plist version="1.0"><dict>
|
|
141
|
+
<key>Label</key><string>com.peon.daemon</string>
|
|
142
|
+
<key>ProgramArguments</key><array>
|
|
143
|
+
<string>${NODE}</string>
|
|
144
|
+
<string>${DAEMON}</string>
|
|
145
|
+
</array>
|
|
146
|
+
<key>WorkingDirectory</key><string>${memoryHome}</string>
|
|
147
|
+
<key>RunAtLoad</key><true/>
|
|
148
|
+
<key>KeepAlive</key><true/>
|
|
149
|
+
<key>StandardOutPath</key><string>${memoryHome}/daemon.out.log</string>
|
|
150
|
+
<key>StandardErrorPath</key><string>${memoryHome}/daemon.err.log</string>
|
|
151
|
+
</dict></plist>\n`;
|
|
152
|
+
act("daemon service (launchd, auto-start) โ " + PLIST, () => {
|
|
153
|
+
mkdirSync(dirname(PLIST), { recursive: true });
|
|
154
|
+
writeFileSync(PLIST, plist);
|
|
155
|
+
spawnSync("launchctl", ["unload", PLIST], { stdio: "ignore" });
|
|
156
|
+
execFileSync("launchctl", ["load", PLIST]);
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
log(" โ Linux: systemd user unit:");
|
|
160
|
+
log(` ExecStart=${NODE} ${DAEMON}\n WorkingDirectory=${memoryHome}\n Restart=always`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function health() {
|
|
165
|
+
try { const r = await fetch("http://127.0.0.1:3737/health"); return (await r.json()).ok === true; } catch { return false; }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ================= commands =================
|
|
169
|
+
if (cmd === "install") {
|
|
170
|
+
log("\n๐ง Peon setup โ a memory brain for your AI agents\n");
|
|
171
|
+
|
|
172
|
+
// Step 1 โ memory home
|
|
173
|
+
log("Step 1/4 ยท Where should Peon's GLOBAL brain live?");
|
|
174
|
+
log(" (press Enter to accept the default)");
|
|
175
|
+
const memoryHome = await ask(" memory home", DEFAULT_HOME);
|
|
176
|
+
act("create " + memoryHome, () => mkdirSync(memoryHome, { recursive: true }));
|
|
177
|
+
|
|
178
|
+
// Step 2 โ LLM provider
|
|
179
|
+
log("\nStep 2/4 ยท LLM for consolidation + semantic retrieval");
|
|
180
|
+
log(" 1) OpenRouter โ one key, any model (default: gemini-2.5-flash-lite, ~cents/day)");
|
|
181
|
+
log(" 2) OpenAI โ gpt-4o-mini + text-embedding-3-small");
|
|
182
|
+
log(" 3) Anthropic โ Claude Haiku (embeddings fall back local โ pair with Ollama for semantic)");
|
|
183
|
+
log(" 4) Ollama โ 100% local + free (llama3.2 + nomic-embed-text on your machine)");
|
|
184
|
+
log(" 5) skip โ lexical-only memory, add a provider later");
|
|
185
|
+
const choice = await ask(" choose 1-5", "1");
|
|
186
|
+
const envFile = join(memoryHome, ".env");
|
|
187
|
+
let envLines = [`# Peon config โ generated by peon-mem install`];
|
|
188
|
+
if (choice === "1") {
|
|
189
|
+
const key = await ask(" OpenRouter API key (sk-or-โฆ)", "");
|
|
190
|
+
envLines.push("PEON_PROVIDER=openrouter", `OPENROUTER_API_KEY=${key}`,
|
|
191
|
+
"PEON_PROCESSING_MODEL=google/gemini-2.5-flash-lite", "PEON_EMBEDDING_MODEL=openai/text-embedding-3-small");
|
|
192
|
+
} else if (choice === "2") {
|
|
193
|
+
const key = await ask(" OpenAI API key (sk-โฆ)", "");
|
|
194
|
+
envLines.push("PEON_PROVIDER=openai", `OPENAI_API_KEY=${key}`,
|
|
195
|
+
"PEON_PROCESSING_MODEL=gpt-4o-mini", "PEON_EMBEDDING_MODEL=text-embedding-3-small");
|
|
196
|
+
} else if (choice === "3") {
|
|
197
|
+
const key = await ask(" Anthropic API key (sk-ant-โฆ)", "");
|
|
198
|
+
envLines.push("PEON_PROVIDER=anthropic", `ANTHROPIC_API_KEY=${key}`,
|
|
199
|
+
"PEON_PROCESSING_MODEL=claude-haiku-4-5-20251001",
|
|
200
|
+
"# Anthropic has no embeddings API โ install Ollama + set PEON_EMBEDDING_MODE=ollama for semantic retrieval");
|
|
201
|
+
} else if (choice === "4") {
|
|
202
|
+
envLines.push("PEON_PROVIDER=ollama", "PEON_EMBEDDING_MODE=ollama",
|
|
203
|
+
"PEON_PROCESSING_MODEL=llama3.2", "PEON_EMBEDDING_MODEL=nomic-embed-text");
|
|
204
|
+
const up = await fetch("http://127.0.0.1:11434/api/tags").then((r) => r.ok).catch(() => false);
|
|
205
|
+
if (!up) log(" โ Ollama not running. Install: https://ollama.com then: ollama pull llama3.2 && ollama pull nomic-embed-text");
|
|
206
|
+
else log(" โ Ollama detected on :11434 โ pull models if missing: ollama pull llama3.2 && ollama pull nomic-embed-text");
|
|
207
|
+
} else {
|
|
208
|
+
envLines.push("# no provider configured โ Peon runs lexical-only. Re-run `peon-mem install` anytime.");
|
|
209
|
+
}
|
|
210
|
+
if (existsSync(envFile)) log(" โ keeping existing " + envFile);
|
|
211
|
+
else act("write " + envFile, () => writeFileSync(envFile, envLines.join("\n") + "\n"));
|
|
212
|
+
|
|
213
|
+
// Step 3 โ daemon
|
|
214
|
+
log("\nStep 3/4 ยท Daemon (always-on, 127.0.0.1:3737)");
|
|
215
|
+
installService(memoryHome);
|
|
216
|
+
|
|
217
|
+
// Step 4 โ apps
|
|
218
|
+
log("\nStep 4/4 ยท Wire your AI apps (detected on this machine)");
|
|
219
|
+
const apps = detectApps();
|
|
220
|
+
apps.forEach((a, i) => log(` ${i + 1}) ${a.found ? "๐ข" : "โช"} ${a.name}${a.found ? "" : " (not detected)"}`));
|
|
221
|
+
const detected = apps.filter((a) => a.found).map((a) => a.id);
|
|
222
|
+
const pick = await ask(` install MCP into (comma ids or 'all') โ detected: ${detected.join(",") || "none"}`, detected.join(",") || "none");
|
|
223
|
+
const chosen = pick === "all" ? apps.map((a) => a.id) : pick.split(",").map((x) => x.trim()).filter(Boolean);
|
|
224
|
+
for (const id of chosen) {
|
|
225
|
+
const app = apps.find((a) => a.id === id);
|
|
226
|
+
if (!app) { log(" โ unknown app id: " + id); continue; }
|
|
227
|
+
if (app.kind === "claude-code") wireClaude();
|
|
228
|
+
else if (app.kind === "toml") wireCodex();
|
|
229
|
+
else if (app.kind === "json") wireJsonMcp(app.name, app.file);
|
|
230
|
+
else if (app.kind === "vscode") wireVsCode(app.file);
|
|
231
|
+
else if (app.kind === "zed") wireZed(app.file);
|
|
232
|
+
else if (app.kind === "manual") {
|
|
233
|
+
log(` โ ${app.name} is configured in-app (no config file). In the app:`);
|
|
234
|
+
log(` ${app.how}"${NODE}" args=["${MCP}"]`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!DRY) {
|
|
239
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
240
|
+
log("\n" + ((await health()) ? "โ daemon healthy โ http://127.0.0.1:3737" : "โ daemon not answering โ check " + memoryHome + "/daemon.err.log"));
|
|
241
|
+
}
|
|
242
|
+
log("๐ Monitor (the Neural Universe): http://127.0.0.1:3737/monitor");
|
|
243
|
+
log("Memory lives in <project>/.peon/ (child brains) + " + memoryHome + " (global brain)\n");
|
|
244
|
+
rl?.close();
|
|
245
|
+
} else if (cmd === "uninstall") {
|
|
246
|
+
if (MAC && existsSync(PLIST)) act("stop + remove daemon service", () => spawnSync("launchctl", ["unload", PLIST], { stdio: "ignore" }));
|
|
247
|
+
const settings = join(HOME, ".claude", "settings.json");
|
|
248
|
+
try {
|
|
249
|
+
const s = JSON.parse(readFileSync(settings, "utf8"));
|
|
250
|
+
for (const ev of Object.keys(s.hooks || {}))
|
|
251
|
+
s.hooks[ev] = s.hooks[ev].filter((h) => !JSON.stringify(h).includes("claude-peon-hook.mjs"));
|
|
252
|
+
act("remove Peon hooks from " + settings, () => backupWrite(settings, JSON.stringify(s, null, 2) + "\n"));
|
|
253
|
+
} catch {}
|
|
254
|
+
log("Remove [mcp_servers.peon] / mcpServers.peon from Codex/Gemini/Cursor configs if you added them.");
|
|
255
|
+
log("Memory data untouched: <project>/.peon/ and " + DEFAULT_HOME);
|
|
256
|
+
rl?.close();
|
|
257
|
+
} else if (cmd === "daemon") {
|
|
258
|
+
await import(DAEMON);
|
|
259
|
+
} else if (cmd === "doctor") {
|
|
260
|
+
log("package : " + PKG);
|
|
261
|
+
log("daemon : " + ((await health()) ? "healthy (127.0.0.1:3737)" : "DOWN"));
|
|
262
|
+
const settings = join(HOME, ".claude", "settings.json");
|
|
263
|
+
log("hooks : " + (existsSync(settings) && readFileSync(settings, "utf8").includes("claude-peon-hook.mjs") ? "installed" : "not installed"));
|
|
264
|
+
log("config : " + join(DEFAULT_HOME, ".env") + (existsSync(join(DEFAULT_HOME, ".env")) ? "" : " (missing โ run peon-mem install)"));
|
|
265
|
+
rl?.close();
|
|
266
|
+
} else {
|
|
267
|
+
log("peon-mem โ memory brain for AI coding agents");
|
|
268
|
+
log(" peon-mem install [--yes] [--dry-run] guided setup (memory home โ LLM โ daemon โ apps)");
|
|
269
|
+
log(" peon-mem uninstall remove service + hooks (data stays)");
|
|
270
|
+
log(" peon-mem daemon run daemon in foreground");
|
|
271
|
+
log(" peon-mem doctor health check");
|
|
272
|
+
rl?.close();
|
|
273
|
+
}
|