wyrm-mcp 7.5.1 → 7.5.2
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/COMPARISON.md +1 -1
- package/README.md +77 -478
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/wyrm-cli.js +76 -47
- package/dist/wyrm-manifest.json +1 -1
- package/package.json +5 -5
- package/scripts/postinstall.cjs +3 -2
package/COMPARISON.md
CHANGED
|
@@ -29,7 +29,7 @@ This table compares facts that mean the same thing for every system.
|
|
|
29
29
|
|
|
30
30
|
| System | Open source | Runs fully local / no cloud | LLM at **write** (ingest) | LLM at **answer** | Headline metric |
|
|
31
31
|
|---|---|---|---|---|---|
|
|
32
|
-
| **Wyrm** |
|
|
32
|
+
| **Wyrm** | No (proprietary freeware) | ✅ yes | ❌ **none** (deterministic FTS/vector) | ❌ **none** (retrieval only) | retrieval recall@k |
|
|
33
33
|
| Mem0 | Apache-2.0 (+ managed cloud) | self-host possible | ✅ required (LLM extraction + ADD/UPDATE/DELETE/NOOP) | ✅ required | QA accuracy (LLM-judge "J") |
|
|
34
34
|
| Zep / Graphiti | Graphiti Apache-2.0 (Zep cloud) | self-host possible | ✅ required (LLM graph construction) | ✅ required | QA accuracy (LLM-judge) |
|
|
35
35
|
| SuperMemory · cognee · SuperLocalMemory | varies | varies | varies | varies | *no citable LoCoMo/LongMemEval number found* (see below) |
|
package/README.md
CHANGED
|
@@ -1,522 +1,121 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- **Negative learning** — the benchmark no recall-only memory runs: the firewall blocks a repeated mistake at **100% recall · 100% precision** (0 false blocks on novel actions), deterministic, p50 ~0.1 ms.
|
|
31
|
-
- **Reranker (opt-in)** — a local cross-encoder lifts recall@1 **33% → 53%** (+19 pts) and MRR **.45 → .61**.
|
|
32
|
-
- **Honest vs the field** → **[COMPARISON.md](COMPARISON.md)**: Wyrm reports *retrieval recall@k*; mem0 / Zep report *LLM-judged QA-accuracy* — different axes, never conflated, every competitor figure cited + vendor-self-reported.
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
You: "What did we decide about the auth architecture last week?"
|
|
36
|
-
AI: *calls wyrm_session_prime* "We went with JWT + refresh tokens, stored
|
|
37
|
-
in httpOnly cookies. Truth #142 cites the Tuesday session — there's
|
|
38
|
-
a downstream impact on the OAuth flow we should check first."
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
You don't ask for memory. The AI cites it on its own. Visibly, with sources.
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/main/assets/dragon-mark.svg" alt="Wyrm" width="96" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://raw.githubusercontent.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/main/assets/hero.svg" alt="Wyrm — memory that survives the session" width="100%" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/wyrm-mcp"><img src="https://img.shields.io/npm/v/wyrm-mcp?style=flat-square&labelColor=050505&color=c9ccd1&label=npm" alt="npm version" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/wyrm-mcp"><img src="https://img.shields.io/npm/dm/wyrm-mcp?style=flat-square&labelColor=050505&color=c9ccd1&label=downloads" alt="npm downloads" /></a>
|
|
12
|
+
<a href="#requirements"><img src="https://img.shields.io/node/v/wyrm-mcp?style=flat-square&labelColor=050505&color=c9ccd1" alt="node" /></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/license-proprietary-c9ccd1?style=flat-square&labelColor=050505" alt="license: proprietary" />
|
|
14
|
+
<img src="https://img.shields.io/badge/registry-lk.ghosts%2Fwyrm-34d399?style=flat-square&labelColor=050505" alt="MCP registry" />
|
|
15
|
+
<img src="https://img.shields.io/badge/runs-local--first-34d399?style=flat-square&labelColor=050505" alt="local-first" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<b>Persistent memory for AI agents, over MCP.</b><br/>
|
|
20
|
+
Ground truths, recorded failures that block repeats, decision causality, and hybrid recall.<br/>
|
|
21
|
+
A structured SQLite memory on your machine. No cloud, no separate LLM.
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<a href="https://ghosts.lk/wyrm">Website</a> ·
|
|
26
|
+
<a href="#quickstart">Quickstart</a> ·
|
|
27
|
+
<a href="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/discussions">Discussions</a> ·
|
|
28
|
+
<a href="CHANGELOG.md">Changelog</a>
|
|
29
|
+
</p>
|
|
42
30
|
|
|
43
31
|
---
|
|
44
32
|
|
|
45
|
-
##
|
|
33
|
+
## What it is
|
|
46
34
|
|
|
47
|
-
|
|
35
|
+
Most AI coding sessions start from zero. Wyrm gives the agent a memory that persists across them. It keeps your project's decisions, conventions, open work, and dead-ends in a structured database on your own machine, and hands them back to the model at the start of the next session. An agent connected to Wyrm recalls what was decided last week instead of re-deriving it, and can be stopped from repeating an approach that already failed.
|
|
48
36
|
|
|
49
|
-
|
|
37
|
+
It speaks the Model Context Protocol, so it drops into Claude, Cursor, Copilot, Windsurf, and Codex without glue code.
|
|
50
38
|
|
|
51
|
-
|
|
52
|
-
- **Run-native attribution.** First-class `runs`/`run_agents` tables; every write attributable to `(agent_id, run_id)` (explicit param > `_meta['wyrm/actor']` > env > clientInfo), bound into the signed Ed25519 audit chain.
|
|
53
|
-
- **Fleet negative learning.** A failure recorded by one subagent **blocks the repeat for every sibling**: run-scoped quarantine with principled promotion, structured `failure_check` verdicts (`{blocked, matches[], recorded_by_agent, run_id, confidence}` — deterministic, no LLM), and **`wyrm-guard`** — a PreToolUse hook bin that enforces it harness-side (block / warn / silent when clean; measured p95 ≤70ms incl. node startup). `wyrm_stats view=failures` prints the ROI number nobody else can: *"N repeats blocked this run."*
|
|
54
|
-
- **The run loop.** `wyrm_run` `action=start|join|status|debrief|end` — debrief fans each agent's learnings through LOCAL-only extraction (Ollama / deterministic fallback, never a cloud LLM) into a run-scoped review queue; `end` promotes/expires quarantined failures and releases the run's claims. Fleet-mode `session_prime {run_id, role}` returns a byte-stable cached brief (≤1,200 tokens) — 12 simultaneous primes share one prompt-cache prefix. **Distribute-once**: an orchestrator that primes one `{run_id, role, for_spawn: true}` per role gets that brief back as clean embeddable markdown (+ `brief_hash` + the `run_briefs` cache key) and embeds it in each same-role agent's spawn-prompt **prefix** — so the cacheable share of the fleet's prime cost becomes never-sent bytes (measured: a 12-agent / 4-role run's prime deliveries drop 12 → 4; bench `S3b` is −38.7% vs the `S3` floor). Agent-side prime stays the byte-identical CAS-row fallback, so a forgetful orchestrator degrades to the unchanged per-agent cost, never below.
|
|
55
|
-
- **A typed machine wire.** The 7 hot-path domains (capture, recall, search, failure, quest, session/prime, review) return schema-validated `structuredContent`; `content[0].text` is *derived* from it (plain ASCII default, `WYRM_FANCY=1` for glyphs); SEP-1303 structured errors fleets self-correct on; 100% of advertised tools annotated; `prime`/`debrief` exposed as MCP protocol-level prompts.
|
|
56
|
-
- **Single-channel wire (opt-in token economy).** `WYRM_CHANNEL` (vendor-neutral, set per MCP-client config) chooses which channel(s) ride the wire. Default **`both`** = today's exact bytes (no change unless you opt in). **`structured`** keeps the full `structuredContent` body byte-identical and collapses `content[0].text` to the template's summary line + a "full body in structuredContent" pointer — **lossless by construction** (the text was always *derived* from the body) and protocol-valid, but requires that your harness forwards `structuredContent` to the model. **`text`** drops `structuredContent` and keeps the full prose (the human-complete derivation) — the lean mode for harnesses that read text only; **note:** a strict MCP client rejects a body-less response for a tool that declares an `outputSchema`, so `text` is for non-strict / text-only consumers. Measured on `bench/token-economy.mjs --channel`: an opted-in working hour drops **~40%** (`structured`) to **~49%** (`text`); recall (82% of that hour) is the lever. `session_rehydrate` is exempt (its briefing markdown *is* the payload).
|
|
57
|
-
- **CLI exile + deletions.** 22 operator/egress tools moved to `wyrm` subcommands — their MCP names return a structured redirect with the exact command (`wyrm_cloud_backup`/`wyrm_sync_export` keep executing through 7.0.x so scheduled backups never silently stop). The legacy `wyrm-http` server is **deleted** (its 6.18 access log measured zero traffic) — `wyrm serve` is the HTTP surface.
|
|
39
|
+
## Quickstart
|
|
58
40
|
|
|
59
|
-
**Compatibility:** all 137 names callable; text output reformats (exact-string consumers pin the 6.x line — kept on the `legacy` dist-tag (`wyrm-mcp@6.18.1`): `npm i wyrm-mcp@legacy`); a v6.x database opens with 100% rows (migrations 20–24, all additive + guarded). Per-name dispositions: `specs/wyrm-v7-brood/disposition.md` in the repo.
|
|
60
|
-
|
|
61
|
-
## New in 6.0 — "Present"
|
|
62
|
-
|
|
63
|
-
- **Live statusline** — `wyrm-statusline` binary renders Wyrm's live state into your Claude Code (or Cursor / Windsurf) statusline: `Wyrm · ProjectName · 3 ⟶ · ~12k saved · ⬢ 7`. Auto-spawned daemon, idle-times-out, privacy mode for screen-shares.
|
|
64
|
-
- **✧ Cross-project memory** — `wyrm_constellation` queries FTS5 across every registered Wyrm project at once. *"Have I solved this before in any project?"* Privacy-gated: per-row `cross_project_visibility` defaults to `'within'`.
|
|
65
|
-
- **▰ Tool profiles** — `essential` / `standard` / `full` profiles. *(7.0: client-name auto-detection removed — `WYRM_PROFILE` is the one lever; `full` = `legacy`.)*
|
|
66
|
-
- **⊡ Cache markers** — `wyrm_context_build` segregates a stable preamble for Anthropic prompt-caching (10× cost cut on cached portions).
|
|
67
|
-
- **↺ `wyrm_migrate_prompt`** — rewrite Wyrm-managed blocks in `.cursor/rules`, `.github/copilot-instructions.md`, `CLAUDE.md` idempotently. Operator content outside the markers is preserved verbatim.
|
|
68
|
-
|
|
69
|
-
Enable the statusline in Claude Code:
|
|
70
|
-
```json
|
|
71
|
-
{ "statusLine": { "type": "command", "command": "wyrm-statusline", "padding": 0 } }
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## What's in the box (32 advertised tools · 137 names callable)
|
|
75
|
-
|
|
76
|
-
- **⌇ Memory** — projects, sessions, quests, ground truths, memory artifacts, hybrid FTS5 + vector search
|
|
77
|
-
- **✖ Counter-pattern** — failure check / record / resolve, blocks repeated mistakes at suggestion time
|
|
78
|
-
- **⬢ Ground truths** — validated facts versioned with cascade-invalidation when upstream changes
|
|
79
|
-
- **✱ Decision chain** — `wyrm_decided_because`, `decision_upstream` / `_downstream`, blast-radius traversal
|
|
80
|
-
- **→ Agent loop** — `wyrm_goal_set`, `wyrm_agent_init`, daemon runs OODA until success criteria are met
|
|
81
|
-
- **↯ Federation** — `wyrm_mcp_register` for outbound MCP, Buddy Protocol v1.0 for peer AI tools
|
|
82
|
-
- **🕸 Knowledge graph** — entity / relationship graph with shortest-path and neighborhood queries
|
|
83
|
-
- **🎨 Design substrate** — `design_tokens` + tagged `design_references` library
|
|
84
|
-
- **🛡 Audit chain** — hash-chained, Ed25519-signable audit log for SOC2 / HIPAA
|
|
85
|
-
- **💰 Hour ledger + invoicing** — generate invoices from session content
|
|
86
|
-
- **🌐 Federated team sync** — per-row `is_shared` flag; PII never leaves your box unless explicitly shared
|
|
87
|
-
- **🔒 Encryption** — AES-256-GCM; keys never leave your machine
|
|
88
|
-
- **👁 Visibility tools** — `wyrm_intro` + `wyrm_digest` make Wyrm's work product legible to humans
|
|
89
|
-
|
|
90
|
-
## Built for these workflows
|
|
91
|
-
|
|
92
|
-
- **Founders** with multi-product portfolios — cross-project memory makes patterns from project A inform project B
|
|
93
|
-
- **Designers / creatives** — design tokens + reference clipper turn Wyrm into a creative substrate, not just a code-memory tool
|
|
94
|
-
- **Co-founders / operators** — `wyrm_digest` shows what Wyrm did this week in plain English
|
|
95
|
-
- **Long-running autonomous work** — agent loop runs your standing goals overnight
|
|
96
|
-
- 🧠 **Memory Artifacts** — Store lessons, patterns, anti-patterns, heuristics with confidence scoring and lifecycle tracking
|
|
97
|
-
- 🧱 **Ground Truths** — Versioned, validated project facts injected into every AI context build
|
|
98
|
-
- 🧭 **Reasoning Scaffolds** — Structured checklists surfaced automatically when task type matches
|
|
99
|
-
- 🔬 **Distillation Queue** — Batch-extract session knowledge into reviewed, curated memory
|
|
100
|
-
- 🔍 **Hybrid Search** — Reciprocal Rank Fusion combining FTS5 + vector candidates
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## Installation
|
|
105
|
-
|
|
106
|
-
### npm (recommended)
|
|
107
41
|
```bash
|
|
108
|
-
npm install -g wyrm-mcp
|
|
42
|
+
npm install -g wyrm-mcp # install
|
|
43
|
+
wyrm-setup # wire it into your AI clients, then restart them
|
|
109
44
|
```
|
|
110
45
|
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
brew tap ghosts-lk/wyrm
|
|
114
|
-
brew install wyrm-mcp
|
|
115
|
-
```
|
|
46
|
+
Then, from inside your client, ask it to call `wyrm_capabilities` to confirm the connection. The everyday loop is four steps the agent runs on its own once the habit sets in:
|
|
116
47
|
|
|
117
|
-
### Docker
|
|
118
|
-
```bash
|
|
119
|
-
docker run -d -p 3000:3000 -v wyrm-data:/data ghcr.io/ghosts-lk/wyrm-mcp:latest
|
|
120
48
|
```
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
49
|
+
prime → load the project's truths, quests, and dead-ends at session start
|
|
50
|
+
recall → retrieve what you know before re-deriving it
|
|
51
|
+
check → before retrying an approach, ask if it already failed
|
|
52
|
+
capture → store durable facts, lessons, and tasks as you go
|
|
125
53
|
```
|
|
126
54
|
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
npx wyrm-mcp
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## Quick Start
|
|
135
|
-
|
|
136
|
-
### 1. Install
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
npm install -g wyrm-mcp
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### 2. Connect to Your AI
|
|
143
|
-
|
|
144
|
-
Add Wyrm to your AI client's MCP configuration:
|
|
145
|
-
|
|
146
|
-
<details>
|
|
147
|
-
<summary><strong>Claude Desktop</strong></summary>
|
|
148
|
-
|
|
149
|
-
Edit `~/.config/claude/claude_desktop_config.json` (Linux) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
150
|
-
|
|
151
|
-
```json
|
|
152
|
-
{
|
|
153
|
-
"mcpServers": {
|
|
154
|
-
"wyrm": {
|
|
155
|
-
"command": "wyrm-mcp"
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
55
|
+
## Measured, not asserted
|
|
160
56
|
|
|
161
|
-
|
|
57
|
+
<p align="center">
|
|
58
|
+
<img src="https://raw.githubusercontent.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/main/assets/receipt.svg" alt="Wyrm measured receipt" width="720" />
|
|
59
|
+
</p>
|
|
162
60
|
|
|
163
|
-
|
|
164
|
-
<summary><strong>GitHub Copilot (VS Code)</strong></summary>
|
|
61
|
+
Every number Wyrm publishes comes from a benchmark committed to the source, reproducible on your own data. The negative-learning firewall and the recall lift are the two that matter most, and both are covered below.
|
|
165
62
|
|
|
166
|
-
|
|
63
|
+
## Why Wyrm
|
|
167
64
|
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
"mcp": {
|
|
171
|
-
"servers": {
|
|
172
|
-
"wyrm": {
|
|
173
|
-
"command": "wyrm-mcp"
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
</details>
|
|
181
|
-
|
|
182
|
-
<details>
|
|
183
|
-
<summary><strong>GitHub Copilot CLI</strong></summary>
|
|
184
|
-
|
|
185
|
-
Add to `~/.copilot/mcp-config.json`:
|
|
186
|
-
|
|
187
|
-
```json
|
|
188
|
-
{
|
|
189
|
-
"mcpServers": {
|
|
190
|
-
"wyrm": {
|
|
191
|
-
"command": "wyrm-mcp"
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
</details>
|
|
65
|
+
### It remembers what failed, not just what worked
|
|
198
66
|
|
|
199
|
-
|
|
200
|
-
<summary><strong>Cursor</strong></summary>
|
|
67
|
+
Most memory tools store successes. Wyrm also records dead-ends and blocks the repeat. You record a failed approach with `wyrm_failure_record`, and a later `wyrm_failure_check` surfaces it before the agent walks back into it. Across a session that stops re-litigating solved problems; across a fleet of agents, one worker's dead-end warns the rest, once.
|
|
201
68
|
|
|
202
|
-
|
|
69
|
+
### Recall that finds things by meaning, not just keywords
|
|
203
70
|
|
|
204
|
-
|
|
205
|
-
{
|
|
206
|
-
"mcpServers": {
|
|
207
|
-
"wyrm": {
|
|
208
|
-
"command": "wyrm-mcp"
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
</details>
|
|
71
|
+
`wyrm_recall` runs keyword search (FTS5) and semantic search over a vector index, fuses them, and reranks. It is hybrid by default, no configuration required. For higher accuracy you can opt into NVIDIA NIM retrieval (below).
|
|
215
72
|
|
|
216
|
-
|
|
217
|
-
<summary><strong>Windsurf</strong></summary>
|
|
73
|
+
### Local-first, and honest about egress
|
|
218
74
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
```json
|
|
222
|
-
{
|
|
223
|
-
"mcpServers": {
|
|
224
|
-
"wyrm": {
|
|
225
|
-
"command": "wyrm-mcp"
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
```
|
|
75
|
+
By default nothing leaves your machine. The database is a single SQLite file at `~/.wyrm/wyrm.db`. When you do opt into a hosted embedding path, Wyrm reports exactly what left and where, in a determinism receipt and on its health endpoint. The privacy claim is one you can verify from the runtime, not just the docs.
|
|
230
76
|
|
|
231
|
-
|
|
77
|
+
### Built for one agent or a fleet
|
|
232
78
|
|
|
233
|
-
|
|
79
|
+
Every memory is attributed to the agent and run that produced it, so a swarm of agents can share one accountable memory bus, with failures kept private to your account by default. A live event stream keeps devices in sync.
|
|
234
80
|
|
|
235
|
-
|
|
81
|
+
## NVIDIA NIM retrieval (optional)
|
|
236
82
|
|
|
237
|
-
Wyrm
|
|
83
|
+
Wyrm can use NVIDIA NIM for embeddings and reranking when accuracy is worth a hosted call. On a retrieval benchmark committed in the repo, recall@1 moved from 33% on the local baseline to 47% with NIM embeddings and 52% with NIM reranking added. It is an explicit opt-in, off by default, and the egress is disclosed on every call.
|
|
238
84
|
|
|
239
85
|
```bash
|
|
240
|
-
|
|
241
|
-
|
|
86
|
+
export WYRM_VECTOR_PROVIDER=nim
|
|
87
|
+
export WYRM_RERANK_PROVIDER=nim
|
|
88
|
+
export NIM_API_KEY=nvapi-...
|
|
242
89
|
```
|
|
243
90
|
|
|
244
|
-
|
|
91
|
+
<sub>Ghost Protocol (Pvt) Ltd is a member of NVIDIA Inception.</sub>
|
|
245
92
|
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
WYRM_VECTOR_PROVIDER=openai OPENAI_API_KEY=sk-... wyrm-mcp
|
|
249
|
-
```
|
|
93
|
+
## Works with
|
|
250
94
|
|
|
251
|
-
|
|
95
|
+
Claude (Code, desktop, web) · Cursor · GitHub Copilot · Windsurf · Codex, and any MCP-capable client.
|
|
252
96
|
|
|
253
|
-
|
|
97
|
+
## Requirements
|
|
254
98
|
|
|
255
|
-
|
|
256
|
-
"Scan ~/projects for git repositories"
|
|
257
|
-
"Start a session for my auth refactor"
|
|
258
|
-
"What quests are pending across all projects?"
|
|
259
|
-
"Search my memory for anything about database migrations"
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## MCP Tools Reference
|
|
265
|
-
|
|
266
|
-
Wyrm exposes a frozen 32-verb typed surface (137 tools callable) via the Model Context Protocol:
|
|
267
|
-
|
|
268
|
-
### Projects
|
|
269
|
-
|
|
270
|
-
| Tool | Description |
|
|
271
|
-
|------|-------------|
|
|
272
|
-
| `wyrm_scan_projects` | Discover git projects in a directory tree |
|
|
273
|
-
| `wyrm_list_projects` | List all registered projects with metadata |
|
|
274
|
-
| `wyrm_project_context` | Get full context for a project (sessions, quests, data) |
|
|
275
|
-
| `wyrm_global_context` | Overview across all projects |
|
|
276
|
-
|
|
277
|
-
### Sessions
|
|
278
|
-
|
|
279
|
-
| Tool | Description |
|
|
280
|
-
|------|-------------|
|
|
281
|
-
| `wyrm_session_start` | Start or continue a named session with context |
|
|
282
|
-
| `wyrm_session_update` | Update session with completed work, decisions, and notes |
|
|
283
|
-
|
|
284
|
-
### Quests (Task Tracking)
|
|
285
|
-
|
|
286
|
-
| Tool | Description |
|
|
287
|
-
|------|-------------|
|
|
288
|
-
| `wyrm_quest_add` | Add a task with priority, tags, and description |
|
|
289
|
-
| `wyrm_quest_complete` | Mark a task as done with completion notes |
|
|
290
|
-
| `wyrm_all_quests` | List pending tasks across all projects |
|
|
291
|
-
|
|
292
|
-
### Data Lake
|
|
293
|
-
|
|
294
|
-
| Tool | Description |
|
|
295
|
-
|------|-------------|
|
|
296
|
-
| `wyrm_data_insert` | Store a data point with namespace and metadata |
|
|
297
|
-
| `wyrm_data_batch_insert` | Bulk insert for large datasets |
|
|
298
|
-
| `wyrm_data_query` | Query stored data by namespace, key, or content |
|
|
299
|
-
| `wyrm_data_categories` | List all data categories for a project |
|
|
300
|
-
|
|
301
|
-
### Skills Registry
|
|
302
|
-
|
|
303
|
-
| Tool | Description |
|
|
304
|
-
|------|-------------|
|
|
305
|
-
| `wyrm_skill_register` | Register or update a skill with metadata |
|
|
306
|
-
| `wyrm_skill_list` | List skills with filtering options |
|
|
307
|
-
| `wyrm_skill_get` | Get detailed skill information |
|
|
308
|
-
| `wyrm_skill_search` | Search skills by name, description, or tags |
|
|
309
|
-
| `wyrm_skill_activate` / `wyrm_skill_deactivate` | Toggle skill status |
|
|
310
|
-
| `wyrm_skill_delete` | Remove a skill |
|
|
311
|
-
| `wyrm_skill_stats` | Skill registry statistics |
|
|
312
|
-
|
|
313
|
-
### Search & Utilities
|
|
314
|
-
|
|
315
|
-
| Tool | Description |
|
|
316
|
-
|------|-------------|
|
|
317
|
-
| `wyrm_search` | Full-text search across all projects, sessions, quests, and data |
|
|
318
|
-
| `wyrm_set_global` | Set global context that applies across all projects |
|
|
319
|
-
| `wyrm_sync` | Sync database with `.wyrm/` markdown files |
|
|
320
|
-
| `wyrm_stats` | Database statistics and health |
|
|
321
|
-
| `wyrm_usage` | Token usage stats, cache hit rates, and cost estimates |
|
|
322
|
-
| `wyrm_maintenance` | Vacuum, archive old data, optimize indexes |
|
|
323
|
-
| `wyrm_setup` | Auto-configure Wyrm in every supported AI client it detects |
|
|
324
|
-
|
|
325
|
-
### Auto-Orchestration
|
|
326
|
-
|
|
327
|
-
| Tool | Description |
|
|
328
|
-
|------|-------------|
|
|
329
|
-
| `wyrm_orchestrate_task` | Classify a task and get an orchestration plan |
|
|
330
|
-
| `wyrm_orchestration_config` | View or update orchestration settings |
|
|
331
|
-
| `wyrm_orchestration_stats` | Orchestration effectiveness and task distribution |
|
|
332
|
-
|
|
333
|
-
### Knowledge Graph
|
|
334
|
-
|
|
335
|
-
| Tool | Description |
|
|
336
|
-
|------|-------------|
|
|
337
|
-
| `wyrm_entity_add` | Add a named entity to the project's knowledge graph |
|
|
338
|
-
| `wyrm_entity_link` | Create a typed, directional relationship between two entities |
|
|
339
|
-
| `wyrm_entity_search` | Find entities by name or alias |
|
|
340
|
-
| `wyrm_entity_graph` | Get the neighborhood graph around an entity (recursive traversal) |
|
|
341
|
-
| `wyrm_entity_path` | Find shortest path between two entities |
|
|
342
|
-
| `wyrm_entity_merge` | Merge two duplicate entities, consolidating aliases and relationships |
|
|
343
|
-
|
|
344
|
-
### Memory Artifacts
|
|
345
|
-
|
|
346
|
-
| Tool | Description |
|
|
347
|
-
|------|-------------|
|
|
348
|
-
| `wyrm_remember` | Store a distilled memory artifact — pattern, lesson, anti-pattern, heuristic, or reasoning trace |
|
|
349
|
-
| `wyrm_recall` | Retrieve relevant memory artifacts for a task — hybrid FTS5 + local-vector retrieval (convex score blend) when embeddings are available, lexical+tag fallback offline |
|
|
350
|
-
| `wyrm_feedback` | Record whether a recalled artifact was helpful (updates confidence via Bayesian adjustment) |
|
|
351
|
-
| `wyrm_context_build` | Assemble a task-specific context brief: ground truths + best scaffold + relevant memory artifacts |
|
|
352
|
-
|
|
353
|
-
### Intelligence Amplification
|
|
354
|
-
|
|
355
|
-
| Tool | Description |
|
|
356
|
-
|------|-------------|
|
|
357
|
-
| `wyrm_truth_set` | Store a validated ground truth (architecture, constraints, decisions) — versioned, history preserved |
|
|
358
|
-
| `wyrm_truth_get` | Retrieve all current ground truths for a project, optionally filtered by category |
|
|
359
|
-
| `wyrm_scaffold_save` | Save a reasoning scaffold — structured step-by-step checklist for a problem type |
|
|
360
|
-
| `wyrm_scaffold_get` | Find the best matching reasoning scaffold for a task description |
|
|
361
|
-
| `wyrm_distill` | Queue candidate memory artifacts from a session for human review |
|
|
362
|
-
| `wyrm_review` | Approve (activate) or reject (permanently delete) any queued review candidate — from auto-capture, harvest, capture conflict-routing, imports, or distillation |
|
|
363
|
-
|
|
364
|
-
---
|
|
365
|
-
|
|
366
|
-
## Configuration
|
|
367
|
-
|
|
368
|
-
### Environment Variables
|
|
369
|
-
|
|
370
|
-
| Variable | Default | Description |
|
|
371
|
-
|----------|---------|-------------|
|
|
372
|
-
| `WYRM_DB_PATH` | `~/.wyrm/wyrm.db` | SQLite database location |
|
|
373
|
-
| `WYRM_ENCRYPTION_KEY` | *(none)* | AES-256-GCM encryption key for sensitive data |
|
|
374
|
-
| `WYRM_LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) |
|
|
375
|
-
| `WYRM_HTTP_PORT` | `3333` | HTTP API server port |
|
|
376
|
-
|
|
377
|
-
### Encryption
|
|
378
|
-
|
|
379
|
-
To enable encryption for sensitive context data:
|
|
380
|
-
|
|
381
|
-
```bash
|
|
382
|
-
# Generate a key
|
|
383
|
-
openssl rand -hex 32
|
|
384
|
-
|
|
385
|
-
# Set it before starting Wyrm
|
|
386
|
-
export WYRM_ENCRYPTION_KEY="your-64-char-hex-key"
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
All encryption happens locally. Keys never leave your machine.
|
|
390
|
-
|
|
391
|
-
### Per-Project Files
|
|
392
|
-
|
|
393
|
-
Each tracked project gets a `.wyrm/` directory:
|
|
394
|
-
|
|
395
|
-
```
|
|
396
|
-
project/
|
|
397
|
-
└── .wyrm/
|
|
398
|
-
├── hoard.md # Project knowledge — architecture, decisions, context
|
|
399
|
-
├── chronicles.md # 📜 Session history — what was discussed and built
|
|
400
|
-
├── quests.md # ⚔️ Task queue — pending and completed work
|
|
401
|
-
└── protocol.md # 🔥 AI guidelines — project-specific instructions
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
These sync bi-directionally with the SQLite database via `wyrm_sync`.
|
|
405
|
-
|
|
406
|
-
---
|
|
407
|
-
|
|
408
|
-
## Architecture
|
|
409
|
-
|
|
410
|
-
```
|
|
411
|
-
┌─────────────────────────────────────────────────────────┐
|
|
412
|
-
│ AI Assistants │
|
|
413
|
-
│ (Copilot · Claude · Cursor · Windsurf · Zed) │
|
|
414
|
-
└────────────────────────┬────────────────────────────────┘
|
|
415
|
-
│ MCP (stdio)
|
|
416
|
-
▼
|
|
417
|
-
┌─────────────────────────────────────────────────────────┐
|
|
418
|
-
│ Wyrm MCP Server │
|
|
419
|
-
│ │
|
|
420
|
-
│ ┌──────────────────────────────────────────────────┐ │
|
|
421
|
-
│ │ Intelligence Layer │ │
|
|
422
|
-
│ │ Ground Truths · Reasoning Scaffolds · Distill │ │
|
|
423
|
-
│ └──────────────────────────────────────────────────┘ │
|
|
424
|
-
│ ┌──────────────────────────────────────────────────┐ │
|
|
425
|
-
│ │ Memory Artifacts · Knowledge Graph │ │
|
|
426
|
-
│ └──────────────────────────────────────────────────┘ │
|
|
427
|
-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
|
|
428
|
-
│ │ Projects │ │ Sessions │ │ Quests │ │ Skills │ │
|
|
429
|
-
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
|
|
430
|
-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
|
|
431
|
-
│ │ DataLake │ │ Hybrid │ │ Crypto │ │ Sync │ │
|
|
432
|
-
│ │ │ │ Search │ │(AES-256) │ │ (.md) │ │
|
|
433
|
-
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
|
|
434
|
-
│ │
|
|
435
|
-
│ SQLite Database (WAL mode) │
|
|
436
|
-
│ Optional HTTP API (:3333) │
|
|
437
|
-
└─────────────────────────────────────────────────────────┘
|
|
438
|
-
```
|
|
99
|
+
Node.js 22 or newer. Optional: a local Ollama with `nomic-embed-text` for semantic recall without any hosted call.
|
|
439
100
|
|
|
440
|
-
|
|
101
|
+
## Community and feedback
|
|
441
102
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
## Development
|
|
445
|
-
|
|
446
|
-
```bash
|
|
447
|
-
# Clone and install
|
|
448
|
-
git clone https://github.com/Ghosts-Protocol-Pvt-Ltd/Wyrm.git
|
|
449
|
-
cd Wyrm/packages/mcp-server
|
|
450
|
-
npm install
|
|
451
|
-
|
|
452
|
-
# Build
|
|
453
|
-
npm run build
|
|
454
|
-
|
|
455
|
-
# Run tests
|
|
456
|
-
npm test
|
|
457
|
-
|
|
458
|
-
# Watch mode
|
|
459
|
-
npm run dev
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
### Project Structure
|
|
463
|
-
|
|
464
|
-
```
|
|
465
|
-
Wyrm/
|
|
466
|
-
├── packages/
|
|
467
|
-
│ ├── mcp-server/ # Core MCP server
|
|
468
|
-
│ │ ├── src/
|
|
469
|
-
│ │ │ ├── index.ts # MCP tool definitions & server
|
|
470
|
-
│ │ │ ├── database.ts # SQLite schema & queries
|
|
471
|
-
│ │ │ ├── crypto.ts # AES-256-GCM encryption
|
|
472
|
-
│ │ │ ├── security.ts # Input sanitization
|
|
473
|
-
│ │ │ └── ...
|
|
474
|
-
│ │ ├── tests/ # Jest test suite
|
|
475
|
-
│ │ └── package.json
|
|
476
|
-
│ └── vscode-extension/ # VS Code extension (optional)
|
|
477
|
-
├── config/ # Configuration templates
|
|
478
|
-
├── templates/ # Project template files
|
|
479
|
-
├── docs/ # Documentation
|
|
480
|
-
├── examples/ # Usage examples
|
|
481
|
-
└── scripts/ # Utility scripts
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
---
|
|
103
|
+
Wyrm sends no telemetry, so the way we learn what is working is you telling us.
|
|
485
104
|
|
|
486
|
-
|
|
105
|
+
- **`wyrm feedback`** from your terminal opens a prefilled report. Add `--bug`, `--idea`, or `--question`.
|
|
106
|
+
- **[Discussions](https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/discussions)** for questions, ideas, and how it is going.
|
|
107
|
+
- **[Issues](https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/issues)** for bugs and concrete requests.
|
|
108
|
+
- Email [ryan@ghosts.lk](mailto:ryan@ghosts.lk).
|
|
487
109
|
|
|
488
|
-
Wyrm
|
|
489
|
-
|
|
490
|
-
Issues and bug reports are welcome: please open an issue or email ryan@ghosts.lk.
|
|
491
|
-
|
|
492
|
-
---
|
|
493
|
-
|
|
494
|
-
## Security
|
|
495
|
-
|
|
496
|
-
- **Local-only** — All your data stays on your machine. The only default outbound call is a once-a-day npm version check (no data sent beyond the request itself; opt out with `WYRM_NO_VERSION_CHECK=1`).
|
|
497
|
-
- **No telemetry** — Zero data collection, zero phone-home.
|
|
498
|
-
- **Optional encryption** — AES-256-GCM for sensitive context data.
|
|
499
|
-
- **Audited** — Security audits documented in [SECURITY_AUDIT.md](SECURITY_AUDIT.md) and [SECURITY_AUDIT_V2.md](SECURITY_AUDIT_V2.md).
|
|
500
|
-
|
|
501
|
-
Found a vulnerability? Please open an issue or email ryan@ghosts.lk.
|
|
502
|
-
|
|
503
|
-
---
|
|
110
|
+
If Wyrm earned a place in your workflow, a star helps other people find it.
|
|
504
111
|
|
|
505
112
|
## License
|
|
506
113
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
- **The Wyrm Memory Protocol** ([`PROTOCOL.md`](../../PROTOCOL.md)) is an **open spec, freely implementable** by anyone (CC-BY-4.0) — build a WMP-compatible memory without touching Wyrm's code.
|
|
510
|
-
- **This implementation** (`wyrm-mcp`) is **[Proprietary Freeware](LICENSE)** — free to use, all rights reserved. Copyright © 2024-2026 [Ghost Protocol (Pvt) Ltd](https://ghosts.lk).
|
|
511
|
-
|
|
512
|
-
For a **commercial license** (embedding Wyrm in a closed product, or running it as a managed service), contact [ryan@ghosts.lk](mailto:ryan@ghosts.lk).
|
|
513
|
-
|
|
514
|
-
---
|
|
515
|
-
|
|
516
|
-
<div align="center">
|
|
517
|
-
|
|
518
|
-
*The dragon remembers.*
|
|
114
|
+
Wyrm is proprietary software, free to use under the [Wyrm license](LICENSE) and Terms of Service. This repository is the public home for the docs, the changelog, and the community. The source is not published here. For a commercial license (embedding Wyrm in a closed product, or running it as a managed service), contact [ryan@ghosts.lk](mailto:ryan@ghosts.lk).
|
|
519
115
|
|
|
520
|
-
|
|
116
|
+
<br/>
|
|
521
117
|
|
|
522
|
-
|
|
118
|
+
<p align="center">
|
|
119
|
+
<sub>Built by <a href="https://ghosts.lk">Ghost Protocol</a> · Colombo, Sri Lanka</sub><br/>
|
|
120
|
+
<sub>NVIDIA, the NVIDIA logo, and NVIDIA Inception are trademarks and/or registered trademarks of NVIDIA Corporation.</sub>
|
|
121
|
+
</p>
|
package/dist/tool-manifest.json
CHANGED
package/dist/wyrm-cli.js
CHANGED
|
@@ -1,78 +1,106 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{join as
|
|
2
|
+
import{join as K,dirname as le}from"path";import{homedir as Ie}from"os";import{existsSync as J,readFileSync as V}from"fs";import{fileURLToPath as de}from"url";import{spawnSync as z}from"child_process";import{createInterface as Q}from"readline";import{WyrmDB as De}from"./database.js";import{MemoryArtifacts as B}from"./memory-artifacts.js";import{GroundTruths as pe}from"./intelligence.js";import{Causality as ue}from"./causality.js";import{wireTruthAutoCascade as me}from"./truth-cascade.js";import{Rehydration as Me}from"./rehydration.js";import{makeRenderDeps as fe,buildRenderPlan as ne,renderToDisk as Ne,renderForClient as ye,detectClients as Ae,ALL_RENDER_CLIENTS as re,ALL_CLIENT_MARKERS as Oe}from"./render-target.js";import{classifyCapture as Le}from"./capture.js";import{importFrom as We,IMPORT_SOURCES as ge}from"./importers.js";import{c as d,colors as M,formatTable as I,printSection as S,printSuccess as b,printError as p,icons as U}from"./cli.js";import{loadConnectorConfigs as he,upsertConnectorConfig as Ue,runConnector as Ye,runAllConnectors as qe}from"./connectors/index.js";import{readActor as Fe}from"./attribution.js";function A(){try{const l=le(de(import.meta.url));return JSON.parse(V(K(l,"..","package.json"),"utf-8"))}catch{return{}}}function T(l,a){const e=typeof l=="string"?parseInt(l,10):NaN;return Number.isFinite(e)?e:a}function Z(l,a){const e=typeof l=="string"?parseFloat(l):NaN;return Number.isFinite(e)?e:a}function He(){return process.env.WYRM_DB_PATH??K(Ie(),".wyrm","wyrm.db")}function _(){return new De(He())}function C(l){const a=[],e={};let n=0;for(;n<l.length;){const r=l[n];if(r.startsWith("--")){const t=r.slice(2),i=l[n+1];i&&!i.startsWith("--")?(e[t]=i,n+=2):(e[t]=!0,n++)}else a.push(r),n++}return{positional:a,flags:e}}function P(l,a){return a?l.getDatabase().prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${a}%`)??null:null}async function Ve(l){const{positional:a,flags:e}=C(l),n=a[0];n||(p("Usage: wyrm search <query> [--project <name>] [--type all|memories|truths|quests|data]"),process.exit(1));const r=_(),t=r.getDatabase(),i=e.type??"all",o=e.project,u=(o?P(r,o):null)?.id;S(`Search: "${n}"`);const c=[];if(i==="all"||i==="memories")try{const f=u?`AND m.project_id = ${u}`:"",y=t.prepare(`
|
|
3
3
|
SELECT m.id, m.kind, m.problem, m.project_id FROM memory_artifacts m
|
|
4
4
|
JOIN memory_artifacts_fts fts ON m.id = fts.rowid
|
|
5
5
|
WHERE memory_artifacts_fts MATCH ? ${f}
|
|
6
6
|
LIMIT 20
|
|
7
|
-
`).all(
|
|
7
|
+
`).all(n);for(const m of y)c.push([`mem:${m.id}`,"memory",m.kind,m.problem.slice(0,80)])}catch{}if(i==="all"||i==="sessions")try{const f=u?`AND s.project_id = ${u}`:"",y=t.prepare(`
|
|
8
8
|
SELECT s.id, s.objectives, s.project_id FROM sessions s
|
|
9
9
|
JOIN sessions_fts fts ON s.id = fts.rowid
|
|
10
10
|
WHERE sessions_fts MATCH ? ${f}
|
|
11
11
|
LIMIT 10
|
|
12
|
-
`).all(
|
|
12
|
+
`).all(n);for(const m of y)c.push([`session:${m.id}`,"session","",m.objectives.slice(0,80)])}catch{}if(i==="all"||i==="quests")try{const f=u?`AND q.project_id = ${u}`:"",y=t.prepare(`
|
|
13
13
|
SELECT q.id, q.title, q.priority, q.project_id FROM quests q
|
|
14
14
|
JOIN quests_fts fts ON q.id = fts.rowid
|
|
15
15
|
WHERE quests_fts MATCH ? ${f}
|
|
16
16
|
LIMIT 10
|
|
17
|
-
`).all(
|
|
17
|
+
`).all(n);for(const m of y)c.push([`quest:${m.id}`,"quest",m.priority,m.title.slice(0,80)])}catch{}if(i==="all"||i==="data")try{const f=u?`AND d.project_id = ${u}`:"",y=t.prepare(`
|
|
18
18
|
SELECT d.id, d.category, d.key, d.value FROM data_lake d
|
|
19
19
|
JOIN data_lake_fts fts ON d.id = fts.rowid
|
|
20
20
|
WHERE data_lake_fts MATCH ? ${f}
|
|
21
21
|
LIMIT 10
|
|
22
|
-
`).all(
|
|
23
|
-
${c.length} result${c.length!==1?"s":""}`))}async function Be(l){const{flags:a}=C(l),e=a.type??"all",
|
|
22
|
+
`).all(n);for(const m of y)c.push([`data:${m.id}`,"data",m.category,m.value.slice(0,80)])}catch{}if(r.close(),c.length===0){console.log(d.dim(` No results found for "${n}"`));return}console.log(I(["ID","Type","Subtype","Preview"],c)),console.log(d.dim(`
|
|
23
|
+
${c.length} result${c.length!==1?"s":""}`))}async function Be(l){const{flags:a}=C(l),e=a.type??"all",n=T(a.limit,20),r=a.project,t=_(),i=t.getDatabase(),s=(r?P(t,r):null)?.id,u=s?`WHERE project_id = ${s}`:"",c=s?`AND project_id = ${s}`:"";if(S("Wyrm Memory"),e==="all"||e==="memories"){S("Memories");const f=i.prepare(`
|
|
24
24
|
SELECT id, kind, confidence, problem, tags FROM memory_artifacts
|
|
25
|
-
${
|
|
25
|
+
${u}
|
|
26
26
|
ORDER BY created_at DESC LIMIT ?
|
|
27
|
-
`).all(
|
|
27
|
+
`).all(n);if(f.length>0){const y=f.map(m=>[`mem:${m.id}`,m.kind,`${Math.round(m.confidence*100)}%`,m.problem.slice(0,60),m.tags?.slice(0,30)??""]);console.log(I(["ID","Kind","Conf","Preview","Tags"],y))}else console.log(d.dim(" No memories yet."))}if(e==="all"||e==="truths"){S("Ground Truths");const f=i.prepare(`
|
|
28
28
|
SELECT id, category, key, value FROM ground_truths
|
|
29
29
|
WHERE is_current = 1 ${c}
|
|
30
30
|
ORDER BY created_at DESC LIMIT ?
|
|
31
|
-
`).all(
|
|
31
|
+
`).all(n);if(f.length>0){const y=f.map(m=>[`truth:${m.id}`,m.category,m.key.slice(0,30),m.value.slice(0,60)]);console.log(I(["ID","Category","Key","Value"],y))}else console.log(d.dim(" No ground truths yet."))}if(e==="all"||e==="quests"){S("Quests");const f=i.prepare(`
|
|
32
32
|
SELECT id, priority, title, status FROM quests
|
|
33
|
-
${
|
|
33
|
+
${u}
|
|
34
34
|
ORDER BY created_at DESC LIMIT ?
|
|
35
|
-
`).all(
|
|
36
|
-
`+
|
|
37
|
-
`+
|
|
38
|
-
`+
|
|
39
|
-
`+
|
|
40
|
-
`+
|
|
41
|
-
`+
|
|
42
|
-
`+String(
|
|
43
|
-
`+
|
|
44
|
-
`+
|
|
45
|
-
`+
|
|
46
|
-
`);return}const h=
|
|
47
|
-
`);return}c=h[0].id}const f=
|
|
35
|
+
`).all(n);if(f.length>0){const y=f.map(m=>[`quest:${m.id}`,m.priority,m.title.slice(0,60),m.status]);console.log(I(["ID","Priority","Title","Status"],y))}else console.log(d.dim(" No quests yet."))}t.close()}async function Ge(l){const{positional:a}=C(l),e=a[0];e||(p("Usage: wyrm show <typed-id> (e.g. mem:41, quest:12, truth:7, data:5, session:3)"),process.exit(1));const[n,r]=e.split(":"),t=parseInt(r??"",10);(!n||isNaN(t))&&(p(`Invalid typed ID: ${e}. Format: type:number (e.g. mem:41)`),process.exit(1));const i=_(),o=i.getDatabase();switch(S(`${e}`),n){case"mem":{const s=o.prepare("SELECT * FROM memory_artifacts WHERE id = ?").get(t);if(!s){p(`Memory artifact ${t} not found`);break}console.log(d.bold("Kind: ")+s.kind),console.log(d.bold("Confidence:")+` ${Math.round(s.confidence*100)}%`),console.log(d.bold("Problem: ")+`
|
|
36
|
+
`+s.problem),s.validated_fix&&console.log(d.bold("Solution: ")+`
|
|
37
|
+
`+s.validated_fix),s.why_it_worked&&console.log(d.bold("Why: ")+`
|
|
38
|
+
`+s.why_it_worked),s.tags&&console.log(d.bold("Tags: ")+s.tags),console.log(d.bold("Created: ")+s.created_at);break}case"quest":{const s=o.prepare("SELECT * FROM quests WHERE id = ?").get(t);if(!s){p(`Quest ${t} not found`);break}console.log(d.bold("Title: ")+s.title),console.log(d.bold("Priority: ")+s.priority),console.log(d.bold("Status: ")+s.status),s.description&&console.log(d.bold("Desc: ")+`
|
|
39
|
+
`+s.description),s.tags&&console.log(d.bold("Tags: ")+s.tags),console.log(d.bold("Created: ")+s.created_at);break}case"truth":{const s=o.prepare("SELECT * FROM ground_truths WHERE id = ?").get(t);if(!s){p(`Ground truth ${t} not found`);break}console.log(d.bold("Category: ")+s.category),console.log(d.bold("Key: ")+s.key),console.log(d.bold("Value: ")+`
|
|
40
|
+
`+s.value),s.rationale&&console.log(d.bold("Rationale:")+`
|
|
41
|
+
`+s.rationale),console.log(d.bold("Active: ")+(s.is_current?"Yes":"No (superseded)")),console.log(d.bold("Created: ")+s.created_at);break}case"data":{const s=o.prepare("SELECT * FROM data_lake WHERE id = ?").get(t);if(!s){p(`Data point ${t} not found`);break}console.log(d.bold("Category: ")+s.category),console.log(d.bold("Key: ")+s.key),console.log(d.bold("Value: ")+`
|
|
42
|
+
`+String(s.value).slice(0,500)),console.log(d.bold("Created: ")+s.created_at);break}case"session":{const s=o.prepare("SELECT * FROM sessions WHERE id = ?").get(t);if(!s){p(`Session ${t} not found`);break}console.log(d.bold("Date: ")+s.date),console.log(d.bold("Objectives: ")+`
|
|
43
|
+
`+s.objectives),s.completed&&console.log(d.bold("Completed: ")+`
|
|
44
|
+
`+s.completed),s.notes&&console.log(d.bold("Notes: ")+`
|
|
45
|
+
`+s.notes);break}default:p(`Unknown type prefix: ${n}. Use mem|quest|truth|data|session`)}i.close()}async function Ke(l){const{positional:a,flags:e}=C(l),n=a[0];n||(p('Usage: wyrm capture "<content>" [--project <name>] [--mode auto|quest|truth|memory]'),process.exit(1));const r=e.project,t=e.mode,i=_(),o=i.getDatabase();let s=null;if(r){const $=P(i,r);$||(p(`Project not found: ${r}`),i.close(),process.exit(1)),s=$.id}let u=Le(n);t&&t!=="auto"&&(u={type:t,subtype:{quest:"quest",truth:"decision",memory:"pattern"}[t]??t,confidence:100,reasoning:`Mode override: ${t}`});const{type:c,subtype:f,confidence:y,reasoning:m}=u;(c==="quest"||c==="truth"||c==="memory")&&s===null&&(p('A --project is required to capture. Use: wyrm capture "<text>" --project <name>'),i.close(),process.exit(1));let h=0,g="",w=!1;const v=new B(o),R=new pe(o);if(me(R,new ue(o)),c==="quest")h=i.addQuest(s,n.slice(0,200),"","medium").id,g="quest";else if(c==="truth")t!=="truth"&&y<100?(h=v.add(s,{kind:"pattern",problem:n,confidence:y/100,needsReview:1}).id,g="mem",w=!0):(h=R.set(s,{category:"decision",key:n.slice(0,60),value:n}).id,g="truth");else{const $=y>=75;h=v.add(s,{kind:f,problem:n,confidence:y/100,needsReview:$?0:1}).id,g="mem",$||(w=!0)}i.close(),b(`Captured as ${c}: ${f}`),console.log(`${d.dim("Confidence:")} ${y}% | ${d.dim(m)}`),console.log(`${d.dim("ID:")} ${g}:${h}`),w&&console.log(`${U.warning} Stored for review \u2014 run ${d.cyan("wyrm review")} to activate`)}async function Je(l){const{flags:a}=C(l),e=T(a.session,0),n=a.path,r=a.project,t=T(a["max-chars"],6e3),i=a.quiet===!0,o=console.log;console.log=()=>{};const s=_();try{const u=new Me(s.getDatabase());let c=e;if(c<=0){let m=n?s.getProject(n):void 0;if(!m&&r&&(m=P(s,r)??void 0),m||(m=s.getProject(process.cwd())),!m){i||process.stderr.write(`wyrm rehydrate: no Wyrm project for this directory, nothing to restore.
|
|
46
|
+
`);return}const h=s.getRecentSessions(m.id,1);if(h.length===0){i||process.stderr.write(`wyrm rehydrate: project "${m.name}" has no prior sessions yet.
|
|
47
|
+
`);return}c=h[0].id}const f=u.rehydrate(c);if(!f){i||process.stderr.write(`wyrm rehydrate: session ${c} not found.
|
|
48
48
|
`);return}let y=f.briefing_markdown;t>0&&y.length>t&&(y=y.slice(0,t)+`
|
|
49
49
|
|
|
50
50
|
_... brief truncated at ${t} chars, run \`wyrm show session:${f.session_id}\` for the full record._`),process.stdout.write(y+`
|
|
51
|
-
`)}finally{
|
|
52
|
-
`);return}const w=
|
|
53
|
-
`).filter(g=>g.trim()),y=new B(
|
|
51
|
+
`)}finally{s.close(),console.log=o}}async function ze(l){const{flags:a}=C(l),e=a.path,n=a.project,r=a.out,t=a.brief===!0,i=a.force===!0,o=a.quiet===!0,s=(typeof a.client=="string"?a.client:"").split(",").map(m=>m.trim().toLowerCase()).filter(Boolean);let u=s.filter(m=>re.includes(m));const c=s.filter(m=>!re.includes(m));c.length>0&&(p(`Unknown --client value(s): ${c.join(", ")} (valid: ${re.join("|")})`),process.exit(1));const f=console.log;t&&(console.log=()=>{});const y=_();try{let m=e?y.getProject(e):void 0;!m&&n&&(m=P(y,n)??void 0),m||(m=y.getProject(process.cwd())),m||(console.log=f,p("wyrm render: no Wyrm project for this directory (use --path or --project)."),process.exit(1));const h=fe(y.getDatabase()),g={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()};if(t){const $=ne(h,m,g);console.log=f,process.stdout.write($.sessionBrief+`
|
|
52
|
+
`);return}const w=r??m.path;u.length===0&&s.length===0&&(u=Ae(Oe.filter($=>J(K(w,$)))));{const $=await import("./reverse-bridge.js"),E=ne(h,m,g),j={"MEMORY.md":E.memoryMd};for(const Y of u){const L=ye(Y,E.model,g);j[L.relPath]=L.block}try{const Y=$.makeBridgeDeps(y.getDatabase()),L=await $.sweepProject(Y,{id:m.id,path:m.path},j,{rootDir:w});L.added>0&&!o&&console.log(` ${U.warning} harvested ${L.added} human edit(s) \u2192 review queue before overwrite`)}catch{}}const{plan:v,writes:R}=Ne(h,m,g,{rootDir:w,clients:u,force:i});if(!o){b(`Rendered ${m.name} memory (${v.model.truths.length} truths, ${v.model.failures.length} failures, ${v.model.quests.length} quests, ${v.model.artifacts.length} patterns) to ${w}`);for(const $ of R){const E=$.action==="created"?U.success:$.action==="updated"?U.info:U.warning;console.log(` ${E} ${$.action.padEnd(7)} ${$.path}${$.reason?` (${$.reason})`:""}`)}}}finally{y.close(),console.log=f}}async function Qe(l){const{flags:a}=C(l),e=a.path,n=a.project,r=a.root,t=a["dry-run"]===!0||a.dry===!0,i=(typeof a.client=="string"?a.client:"").split(",").map(f=>f.trim().toLowerCase()).filter(Boolean),o=["claude","cursor","copilot","agents"],s=i.filter(f=>o.includes(f)),u=await import("./reverse-bridge.js"),c=_();try{let f=e?c.getProject(e):void 0;if(!f&&n&&(f=P(c,n)??void 0),f||(f=c.getProject(process.cwd())),!f){p("wyrm reverse-bridge: no Wyrm project for this directory (use --path or --project)."),process.exitCode=1;return}const y=fe(c.getDatabase()),m={wyrm_version:A().version??"unknown",compiled_at:new Date().toISOString()},h=ne(y,f,m),g={"MEMORY.md":h.memoryMd};for(const R of s){const $=ye(R,h.model,m);g[$.relPath]=$.block}const w=u.makeBridgeDeps(c.getDatabase()),v=await u.sweepProject(w,{id:f.id,path:f.path},g,{dryRun:t,rootDir:r});S(`Reverse bridge ${t?"(dry run) ":""}\u2014 ${v.added} candidate(s) queued, ${v.skipped} already present (${v.filesWithEdits}/${v.filesScanned} file(s) with edits)`);for(const R of v.sample)console.log(` ${U.bullet} ${R}`);!t&&v.added>0&&b("Review with: wyrm review")}finally{c.close()}}async function Ze(l){const a=C(l);if(typeof a.flags.from=="string"){await Xe(a);return}const{positional:e,flags:n}=C(l.slice(1)),r=l[0],t=n.project;if(r==="git"){const i=T(n.last,20),o=_(),s=o.getDatabase();let u=null;if(t){const g=P(o,t);g||(p(`Project not found: ${t}`),o.close(),process.exit(1)),u=g.id}else{const g=s.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();g&&(u=g.id)}u||(p("No project found. Use --project <name>"),o.close(),process.exit(1));const c=z("git",["log","--pretty=format:%H%x1f%s%x1f%an%x1f%ai",`-${i}`],{cwd:process.cwd(),encoding:"utf-8",timeout:1e4,shell:!1});(c.error||c.status!==0)&&(p("git log failed. Make sure you are in a git repository."),o.close(),process.exit(1));const f=c.stdout.split(`
|
|
53
|
+
`).filter(g=>g.trim()),y=new B(s);let m=0,h=0;for(const g of f){const[,w,v,R]=g.split(""),$=w??"";if(/^Merge /i.test($)||/^(chore|bump|release|version)/i.test($)){h++;continue}let E="pattern";/^fix(\(.+\))?:/i.test($)?E="lesson":/^refactor(\(.+\))?:/i.test($)&&(E="heuristic");const j=$.split(":")[0]??"commit";y.add(u,{kind:E,problem:$,whyItWorked:`Committed by ${v??"unknown"} on ${R??"unknown"}`,tags:["git","commit",j.toLowerCase()],confidence:.6,needsReview:1}),m++}o.close(),b(`Imported ${m} commits (${h} skipped). Run ${d.cyan("wyrm review")} to activate.`)}else if(r==="rules"){const i=e[0],o=n.format??"plain";i||(p("Usage: wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),process.exit(1)),J(i)||(p(`File not found: ${i}`),process.exit(1));const s=V(i,"utf-8"),u=_(),c=u.getDatabase();let f=null;if(t){const j=P(u,t);j||(p(`Project not found: ${t}`),u.close(),process.exit(1)),f=j.id}else{const j=c.prepare("SELECT p.* FROM projects p JOIN sessions s ON s.project_id = p.id ORDER BY s.created_at DESC LIMIT 1").get();j&&(f=j.id)}f||(p("No project found. Use --project <name>"),u.close(),process.exit(1));const y=i.split("/").pop()??"rules",m=["imported",o,y],h=s.split(/\n(?=#)/),g=s.split(/\n\n+/),w=(h.length>=g.length?h:g).map(j=>j.trim()).filter(j=>j.length>=15),v=new B(c),R=new pe(c);me(R,new ue(c));let $=0,E=0;for(const j of w)/\b(always|never|must|use|don't|avoid|prefer)\b/i.test(j)?(R.set(f,{category:"constraint",key:j.slice(0,50).replace(/\n/g," "),value:j,source:y}),$++):(v.add(f,{kind:"heuristic",problem:j,tags:m,confidence:.7,needsReview:1}),E++);u.close(),b(`Imported ${$} ground truths + ${E} artifacts (pending review).`)}else p("Usage: wyrm import git [--project <name>] [--last N]"),p(" wyrm import rules <path> [--project <name>] [--format cursorrules|copilot|plain]"),p(` wyrm import --from ${ge.join("|")} <file.json> [--project <name>]`),process.exit(1)}async function Xe(l){const a=l.flags.from,e=l.positional[0],n=l.flags.project,r=`Usage: wyrm import --from ${ge.join("|")} <file.json> [--project <name>]`;e||(p(r),process.exit(1)),J(e)||(p(`File not found: ${e}`),process.exit(1));let t;try{t=JSON.parse(V(e,"utf-8"))}catch(s){p(`Could not parse JSON from ${e}: ${s.message}`),process.exit(1)}let i;try{i=We(a,t)}catch(s){p(s.message),process.exit(1)}const o=_();try{let s=n?P(o,n):null;if(s||(s=o.getProject(process.cwd())??null),!s){p(n?`Project not found: ${n}`:"wyrm import: no Wyrm project for this directory (pass --project <name>)."),process.exitCode=1;return}if(i.length===0){p(`No importable memories found in ${e} for source "${a}".`);return}const u=o.getDatabase(),c=new B(u);try{const{createVectorStore:y}=await import("./vectors.js"),m=process.env.WYRM_VECTOR_PROVIDER??"auto";c.setVectorStore(y({provider:m},u))}catch{}let f=0;for(const y of i)c.add(s.id,{kind:"lesson",problem:y.text,tags:y.tags,constraints:y.metadata?JSON.stringify(y.metadata):void 0,whyItWorked:`Imported from ${y.source}`,outcome:"neutral",confidence:.6,needsReview:1}),f++;b(`Imported ${f} ${f===1?"memory":"memories"} from ${a} into ${d.cyan(s.name)} review queue. Run ${d.cyan("wyrm review")} to vet (tagged ${d.dim(`imported_from:${i[0]?.source??a}`)}).`)}finally{o.close()}}async function et(l){const{flags:a}=C(l),e=a.project,n=_(),r=n.getDatabase();if(S("Wyrm Statistics"),e){const t=P(n,e);t||(p(`Project not found: ${e}`),n.close(),process.exit(1));const i=n.getProjectStats(t.id),o=[["Sessions",String(i.sessions)],["Quests (pending)",String(i.quests.pending)],["Quests (completed)",String(i.quests.completed)],["Data Points",String(i.dataPoints)]];console.log(I(["Metric","Value"],o))}else{const t=n.getStats(),i=[["Projects",String(t.projects)],["Sessions",String(t.sessions)],["Quests",String(t.quests)],["Data Points",String(t.dataPoints)],["DB Size",t.dbSize]],o=r.prepare("SELECT COUNT(*) as n FROM memory_artifacts").get().n,s=r.prepare("SELECT COUNT(*) as n FROM ground_truths WHERE is_current = 1").get().n;i.push(["Memories",String(o)],["Ground Truths",String(s)]),console.log(I(["Metric","Value"],i))}n.close()}async function tt(l){const{flags:a}=C(l),e=a.project,n=_(),r=n.getDatabase();let t=null;if(e){const c=P(n,e);c||(p(`Project not found: ${e}`),n.close(),process.exit(1)),t=c.id}const i=t?`AND project_id = ${t}`:"",o=r.prepare(`
|
|
54
54
|
SELECT id, kind, problem FROM memory_artifacts
|
|
55
55
|
WHERE needs_review = 1 ${i}
|
|
56
56
|
ORDER BY created_at ASC
|
|
57
|
-
`).all();if(o.length===0){console.log(d.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),
|
|
58
|
-
${d.bold(`[${c.kind}] #${c.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(c.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const y=(await
|
|
59
|
-
Review complete.`)}async function ot(l){const{positional:a,flags:e}=C(l),
|
|
57
|
+
`).all();if(o.length===0){console.log(d.dim(` No artifacts pending review${e?` for ${e}`:""}.`)),n.close();return}S(`Review Queue (${o.length} items)`);const s=Q({input:process.stdin,output:process.stdout}),u=c=>new Promise(f=>{s.question(c,f)});for(const c of o){console.log(`
|
|
58
|
+
${d.bold(`[${c.kind}] #${c.id}`)}`),console.log(d.dim("\u2500".repeat(60))),console.log(c.problem.slice(0,300)),console.log(d.dim("\u2500".repeat(60)));const y=(await u(`${d.cyan("[a]")}pprove / ${d.red("[r]")}eject / ${d.yellow("[s]")}kip? `)).trim().toLowerCase();y==="a"?(r.prepare("UPDATE memory_artifacts SET needs_review = 0, updated_at = datetime('now') WHERE id = ?").run(c.id),b(`Approved #${c.id}`)):y==="r"?(r.prepare("DELETE FROM memory_artifacts WHERE id = ?").run(c.id),console.log(`${U.cross} Rejected #${c.id}`)):console.log(d.dim(` Skipped #${c.id}`))}s.close(),n.close(),console.log(`
|
|
59
|
+
Review complete.`)}async function ot(l){const{positional:a,flags:e}=C(l),n=a[0];(!n||!["export","import","preview"].includes(n))&&(p("Usage: wyrm sync export --out <path> | wyrm sync import --from <path> | wyrm sync preview --from <path>"),process.exit(1));const{randomBytes:r,pbkdf2Sync:t,createCipheriv:i,createDecipheriv:o}=await import("crypto"),{readFileSync:s,writeFileSync:u,copyFileSync:c,unlinkSync:f,existsSync:y,chmodSync:m}=await import("fs"),{homedir:h}=await import("os"),{join:g}=await import("path"),w=(await import("better-sqlite3")).default;let v=process.env.WYRM_SYNC_PASSPHRASE??"";if(!v){const x=Q({input:process.stdin,output:process.stdout});v=await new Promise(D=>{process.stdout.write("Passphrase: "),process.stdin.isTTY&&process.stdin.setRawMode?.(!0),x.question("",W=>{process.stdin.isTTY&&process.stdin.setRawMode?.(!1),console.log(""),x.close(),D(W)})})}v||(p("Passphrase is required. Set WYRM_SYNC_PASSPHRASE or enter interactively."),process.exit(1));const R=g(h(),".wyrm"),$=_();if(n==="export"){const x=e.out;x||(p("--out <path> is required"),process.exit(1));const D=g(R,"wyrm_cli_export_temp.db");try{const W=$.getDatabase();y(D)&&f(D),W.prepare("VACUUM INTO ?").run(D);const H=s(D),q=r(32),G=r(16),Re=t(v,q,6e5,32,"sha256"),se=i("aes-256-gcm",Re,G),Ee=Buffer.concat([se.update(H),se.final()]),xe=se.getAuthTag(),Pe=Buffer.from("WYRM"),ae=Buffer.alloc(1);ae.writeUInt8(1,0);const ce=Buffer.concat([Pe,ae,q,G,xe,Ee]);u(x,ce);try{m(x,384)}catch{}try{f(D)}catch{}const Te=(ce.length/(1024*1024)).toFixed(2);b(`Exported to ${x} (${Te} MB)`)}catch(W){try{y(D)&&f(D)}catch{}p(`Export failed: ${W}`)}$.close();return}const E=e.from;E||(p("--from <path> is required"),process.exit(1));const j=s(E);j.subarray(0,4).toString("ascii")!=="WYRM"&&(p("Invalid Wyrm snapshot file."),process.exit(1));const Y=j.readUInt8(4);Y!==1&&(p(`Unsupported snapshot version: ${Y}`),process.exit(1));const L=j.subarray(5,37),ve=j.subarray(37,53),ke=j.subarray(53,69),Se=j.subarray(69),je=t(v,L,6e5,32,"sha256"),X=o("aes-256-gcm",je,ve);X.setAuthTag(ke);let ee;try{ee=Buffer.concat([X.update(Se),X.final()])}catch{p("Decryption failed \u2014 wrong passphrase or corrupted file."),process.exit(1)}if(n==="preview"){const x=g(R,"wyrm_cli_preview_temp.db");y(x)&&f(x),u(x,ee);try{const D=new w(x,{readonly:!0}),W=["projects","sessions","ground_truths","memory_artifacts","quests"];S("Snapshot Preview");const H=[];for(const q of W)try{const G=D.prepare(`SELECT COUNT(*) as n FROM ${q}`).get();H.push([q,String(G.n)])}catch{H.push([q,"?"])}console.log(I(["Table","Count"],H)),D.close()}catch(D){p(`Preview failed: ${D}`)}try{y(x)&&f(x)}catch{}$.close();return}const F=$.getDatabasePath(),ie=Q({input:process.stdin,output:process.stdout}),_e=await new Promise(x=>{ie.question("This will REPLACE your current database. Type CONFIRM to proceed: ",x)});if(ie.close(),_e.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),$.close();return}const Ce=new Date().toISOString().replace(/[:.]/g,"-"),te=`${F}.backup.${Ce}`;c(F,te),b(`Backed up to ${te}`);const oe=g(R,"wyrm_cli_restore_temp.db");u(oe,ee),$.getDatabase().close(),c(oe,F);for(const x of["-wal","-shm"])try{y(F+x)&&f(F+x)}catch{}try{f(oe)}catch{}b(`Restored from ${E}. Backup at ${te}`)}async function st(l){const{flags:a}=C(l),e=a.project,n=a.path,r=Z(a["min-confidence"],.3),t=T(a["older-than"],90),i=a["no-dry-run"]===!0,o=a.yes===!0,s=_(),u=s.getDatabase();let c=null;if(n||e){const g=n?s.getProject(n):P(s,e);g||(p(`Project not found: ${n??e}`),s.close(),process.exit(1)),c=g.id}const f=new B(u),{candidates:y}=f.pruneStale({projectId:c,minConfidence:r,olderThanDays:t,dryRun:!0});if(S(`Prune Candidates${i?" (LIVE DELETE)":" (dry-run)"}`),y.length===0){console.log(d.dim(" No artifacts match prune criteria.")),s.close();return}const m=y.map(g=>[String(g.id),g.kind,g.problem.slice(0,60),(g.confidence*100).toFixed(0)+"%",g.last_accessed_at??"never"]);if(console.log(I(["ID","Kind","Problem","Conf","Last Accessed"],m)),console.log(`
|
|
60
60
|
Total: ${y.length} candidate(s)`),!i){console.log(d.dim(`
|
|
61
|
-
This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),
|
|
62
|
-
Delete these ${y.length} artifact(s)? Type CONFIRM to proceed: `,v)});if(g.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),
|
|
61
|
+
This is a dry run. Use --no-dry-run to delete (confirm each ID).`)),s.close();return}if(!o){const g=Q({input:process.stdin,output:process.stdout}),w=await new Promise(v=>{g.question(`
|
|
62
|
+
Delete these ${y.length} artifact(s)? Type CONFIRM to proceed: `,v)});if(g.close(),w.trim()!=="CONFIRM"){console.log(d.dim("Aborted.")),s.close();return}}const h=f.deleteArtifacts(y.map(g=>g.id));b(`Deleted ${h} artifact(s).`),s.close()}async function nt(l){const{positional:a,flags:e}=C(l),n=a[0]||"list",r=_();try{if(n==="list"||n==="ls"){const t=he(r);if(t.length===0){S("Connectors"),console.log(`${M.dim} none configured. Add one with: wyrm connector add --name <n> --url <bridge-url> --workspace <w>${M.reset}`);return}S("Connectors");const i=t.map(o=>[o.name,o.source,o.enabled?"on":"off",o.workspace,dt(o.baseUrl),o.allowlist.length?`${o.allowlist.length} chats`:"all"]);console.log(I(["Name","Source","Enabled","Workspace","Bridge","Allowlist"],i));return}if(n==="add"){const t=O(e.name),i=O(e.url),o=O(e.workspace);if(!t||!i||!o){p("usage: wyrm connector add --name <n> --url <bridge-url> --workspace <w> [--allowlist a,b] [--token T] [--disabled]"),process.exitCode=1;return}const s={name:t,source:"bridge",enabled:!e.disabled,baseUrl:i,workspace:o,allowlist:O(e.allowlist)?O(e.allowlist).split(",").map(u=>u.trim()).filter(Boolean):[],...O(e.token)?{authToken:O(e.token)}:{}};Ue(r,s),b(`Connector "${t}" saved (source=bridge, workspace=${o}). Token, if any, stored without being printed.`),console.log(`${M.dim} Tip: prefer the env var WYRM_CONNECTOR_TOKEN_${t.toUpperCase().replace(/[^A-Z0-9]/g,"_")} over --token.${M.reset}`);return}if(n==="sync"){const t=a[1]||O(e.name);if(t){const o=he(r).find(u=>u.name===t);if(!o){p(`Connector not found: ${t}`),process.exitCode=1;return}const s=await Ye(r,o);b(`${s.source}: fetched ${s.fetched}, ingested ${s.ingested}, skipped ${s.skipped} -> ${s.workspace}`);return}const i=await qe(r);if(i.length===0){console.log(`${M.dim} no enabled connectors.${M.reset}`);return}for(const o of i)"error"in o?p(`${o.name}: ${o.error}`):b(`${o.source}: fetched ${o.fetched}, ingested ${o.ingested}, skipped ${o.skipped} -> ${o.workspace}`);return}p(`Unknown connector subcommand: ${n} (use list | add | sync)`),process.exitCode=1}finally{r.close()}}function we(){return V(0,"utf-8").trim()}async function rt(){const{initializeLicense:l,getLicenseInfo:a,getTier:e}=await import("./license.js");l();const n=a();S("Wyrm License");const r=[["Tier",e()],["Status",n.valid?"valid":"free tier (no license key)"]];n.valid&&n.key&&(r.push(["Key",n.key]),r.push(["Issued to",n.issuedTo??"unknown"]),r.push(["Expires",n.expiresAt?new Date(n.expiresAt).toLocaleDateString():"never"])),r.push(["Features",n.features.join(", ")||"(free)"]),console.log(I(["Field","Value"],r)),n.valid||console.log(d.dim(`
|
|
63
63
|
Activate with: wyrm login (free) \xB7 or: wyrm activate <license.json | key>`)),console.log(d.dim(`
|
|
64
|
-
\xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function it(){const l=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),a=(()=>{try{return
|
|
65
|
-
${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(
|
|
66
|
-
Waiting for approval\u2026 (Ctrl-C to cancel)`));const
|
|
67
|
-
Database size: ${
|
|
64
|
+
\xA9 2026 Ghost Protocol (Pvt) Ltd \xB7 Proprietary \xB7 https://wyrm.ghosts.lk`)),console.log(d.dim(" Licensed under the Wyrm Terms of Service. No open-source license is granted."))}async function it(){const l=(process.env.WYRM_ACCOUNT_URL??"https://account.ghosts.lk").replace(/\/$/,""),a=(()=>{try{return A().version??"unknown"}catch{return"unknown"}})();let e;try{const c=await fetch(`${l}/api/v1/cli/auth/start`,{method:"POST",headers:{"x-wyrm-version":a}});if(!c.ok)throw new Error(`HTTP ${c.status}`);e=await c.json()}catch(c){p(`Couldn't reach ${l} (${c instanceof Error?c.message:"network error"}).`),process.exitCode=1;return}const n=e.verification_uri_complete||e.verification_uri||`${l}/cli`;console.log(`
|
|
65
|
+
${d.cyan("Sign in to activate Wyrm (free):")}`),console.log(` 1. Open ${d.cyan(n)}`),console.log(` 2. Approve the code ${d.cyan(e.user_code)}`),console.log(d.dim(`
|
|
66
|
+
Waiting for approval\u2026 (Ctrl-C to cancel)`));const r=(e.interval??3)*1e3,t=Date.now()+(e.expires_in??600)*1e3;let i="";for(;Date.now()<t;){await new Promise(c=>{setTimeout(c,r)});try{const f=await(await fetch(`${l}/api/v1/cli/auth/poll`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({device_code:e.device_code})})).json();if(f.status==="approved"&&f.token){i=f.token;break}if(f.status==="denied"||f.error==="expired"){p("Login was denied or the code expired. Run `wyrm login` again."),process.exitCode=1;return}}catch{}}if(!i){p("Login timed out. Run `wyrm login` again."),process.exitCode=1;return}let o;try{const c=await fetch(`${l}/api/v1/license/free`,{method:"POST",headers:{authorization:`Bearer ${i}`,"x-wyrm-version":a}});if(!c.ok){const f=await c.json().catch(()=>({}));p(`Activation failed (${c.status}): ${f.hint||f.error||"unknown error"}`),process.exitCode=1;return}o=JSON.stringify(await c.json())}catch(c){p(`Activation request failed (${c instanceof Error?c.message:"network error"}).`),process.exitCode=1;return}const{activateLicense:s}=await import("./license.js"),u=s(o);u.valid?(b(`Signed in & activated \u2014 ${u.tier} tier (expires ${u.expiresAt??"never"}).`),console.log(d.dim(" Restart the Wyrm MCP server / daemon to apply."))):(p(`Activation failed: ${u.error??"unknown error"}`),process.exitCode=1)}async function at(l){const{positional:a}=C(l),e=a[0];let n;e&&J(e)?n=V(e,"utf-8"):e?n=e:process.stdin.isTTY?(p("Usage: wyrm activate <license.json path | license JSON> (or pipe the JSON on stdin)"),process.exit(1)):n=we();const{activateLicense:r}=await import("./license.js");try{const t=r(n);t.valid?(b(`License activated \u2014 ${t.tier} tier (${t.features.join(", ")})`),console.log(d.dim(" Restart Wyrm (MCP server / daemon) to apply all features."))):(p(`License activation failed: ${t.error??"unknown error"}`),process.exitCode=1)}catch{p("Invalid license format. Please verify your license key."),process.exitCode=1}}async function ct(l){const{flags:a}=C(l),{runMaintenance:e}=await import("./maintenance.js"),{FailurePatterns:n}=await import("./failure-patterns.js"),{SessionSeen:r}=await import("./session-seen.js"),{AgentPresence:t}=await import("./presence.js"),i=_();try{const o=i.getDatabase(),s=T(a["archive-days"],0),u=e({db:i,sessionSeen:new r(o),failures:new n(o),presence:new t(o)},{vacuum:a.vacuum===!0,archiveDays:s>0?s:void 0});S("Maintenance complete");for(const c of u.lines)console.log(` - ${c}`);console.log(d.dim(`
|
|
67
|
+
Database size: ${u.dbSize}`))}finally{i.close()}}async function lt(l){const{positional:a,flags:e}=C(l),n=a[0]??"status",{createVectorStore:r}=await import("./vectors.js"),i={provider:e.provider??process.env.WYRM_VECTOR_PROVIDER??"auto",model:e.model,apiKey:e["api-key"]??process.env.OPENAI_API_KEY,ollamaUrl:e["ollama-url"]};if(n==="setup"){const{createProvider:s}=await import("./providers/embedding-provider.js"),u=s(i);if(!await u.isReady()&&i.provider!=="none"){p(`Provider not ready: ${u.name}. Check the configuration and try again.`),process.exitCode=1;return}b(`Vector provider verified: ${u.name} (model ${u.model}, ${u.dimensions}d)`),console.log(d.dim(" The MCP server reads WYRM_VECTOR_PROVIDER (and provider-specific env) at boot \u2014")),console.log(d.dim(` set WYRM_VECTOR_PROVIDER=${u.name==="none"?"none":i.provider} in the server env, then: wyrm index rebuild`));return}const o=_();try{const s=o.getDatabase(),u=r(i,s);if(n==="status"){const c=u.getStats();S("Vector index");const f=[["Provider",c.provider],["Model",c.model],["Vectors",String(c.total)],...Object.entries(c.byType).map(([y,m])=>[` ${y}`,String(m)])];console.log(I(["Field","Value"],f));return}if(n==="rebuild"){const{reindexProjects:c}=await import("./reindex.js"),f=e["dry-run"]===!0,y=e.project;let m;if(y){const w=o.getProject(y)??P(o,y);if(!w){p(`Project not found: ${y}`),process.exitCode=1;return}m=[w.id]}else m=o.getAllProjects(1e3).map(w=>w.id);const{indexed:h,skipped:g}=await c(s,u,m,{dryRun:f,onError:(w,v)=>p(`${w}: ${JSON.stringify(v)}`)});b(`Reindex ${f?"(dry run) ":""}\u2014 ${m.length} project(s), ${h} indexed, ${g} skipped`);return}p("Usage: wyrm index <setup|rebuild|status> [--provider auto|local|ollama|openai|none] [--model M] [--project P] [--dry-run]"),process.exitCode=1}finally{o.close()}}function O(l){return typeof l=="string"?l:""}function dt(l){try{return new URL(l).host}catch{return l}}async function pt(l){const{flags:a}=C(l),{getUpdateStatus:e}=await import("./version-check.js"),n=A().version??"0.0.0",r=_();let t;try{t=await e(r.getDatabase(),n,{force:a.force===!0||a.check===!0})}finally{r.close()}if(S("Wyrm update"),console.log(I(["Field","Value"],[["Current",t.current],["Latest",t.latest??"unknown (offline?)"],["Update available",t.updateAvailable?"yes":"no"],["Checked",`${t.checkedAt} (${t.source})`]])),a.check===!0)return;if(!t.updateAvailable&&a.force!==!0){console.log(d.dim(`
|
|
68
68
|
Already up to date. (Use --force to reinstall anyway.)`));return}console.log(d.dim(`
|
|
69
69
|
Running: npm install -g wyrm-mcp@latest
|
|
70
|
-
`));const i=z("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});i.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(p(`npm install exited with ${i.status??"unknown"}`),process.exitCode=i.status??1)}async function ut(l){const{positional:a,flags:e}=C(l),
|
|
71
|
-
Total: ${c.total_hours.toFixed(2)}h across ${c.entries.length} session(s)`+(c.estimated_sessions>0?d.dim(` (${c.estimated_sessions} estimated)`):""))}finally{o.close()}}async function ft(l){const{positional:a,flags:e}=C(l),
|
|
72
|
-
`,"utf-8"),b(`Invoice written to ${
|
|
73
|
-
`)}finally{
|
|
74
|
-
=== Recent log ===`),console.log(i.recentLog(40)));return}case"stop":{const o=await i.stop({grace_ms:e.grace!==void 0?T(e.grace,3e3):void 0});if(!o.ok){p(`Stop failed: ${o.error}`),process.exitCode=1;return}b(o.was_running?`Agent stopped (was pid ${o.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const o=await i.restart({interval_seconds:e.interval!==void 0?T(e.interval,600):void 0,max_steps:e["max-steps"]!==void 0&&T(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!o.ok){p(`Restart failed: ${o.error}`),process.exitCode=1;return}b(`Agent restarted \u2014 pid ${o.status.pid}. Active goals: ${o.status.active_goals}.`);return}default:p("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{t.close()}}async function gt(l){if(l.includes("--encrypt")){const{initializeLicense:
|
|
75
|
-
|
|
70
|
+
`));const i=z("npm",["install","-g","wyrm-mcp@latest"],{stdio:"inherit",shell:!1});i.status===0?b("Updated. Restart your MCP clients to pick up the new binary."):(p(`npm install exited with ${i.status??"unknown"}`),process.exitCode=i.status??1)}async function ut(l){const{positional:a,flags:e}=C(l),n=a[0],r=e.project??process.cwd();if(n==="inject"){const{injectSystemPrompt:t}=await import("./autoconfig.js"),i=typeof e.clients=="string"?e.clients.split(",").map(s=>s.trim()).filter(Boolean):[],o=t(r,i);S("System prompt injection");for(const s of o.injected)console.log(` + ${s}`);for(const s of o.skipped)console.log(d.dim(` o skipped (unknown client): ${s}`));for(const s of o.errors)p(s);o.injected.length>0&&b("AI clients in this project will now call wyrm_session_prime at conversation start."),o.errors.length>0&&(process.exitCode=1);return}if(n==="migrate"){const{migrateProject:t,renderMigrationReport:i}=await import("./migrate-prompt.js"),{WYRM_INJECT_BLOCK:o}=await import("./autoconfig.js"),s=e.apply===!0,u=t({projectPath:r,newBlock:o,apply:s});console.log(i(u,s));return}p("Usage: wyrm prompt inject [--project <path>] [--clients copilot,cursor] | wyrm prompt migrate [--project <path>] [--apply]"),process.exitCode=1}async function mt(l){const{positional:a,flags:e}=C(l);(a[0]??"report")!=="report"&&(p("Usage: wyrm hours report --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--session-hours H] [--json]"),process.exit(1));const r=e.from,t=e.to;(!r||!t)&&(p("--from and --to are required (YYYY-MM-DD)"),process.exit(1));const{HourLedger:i}=await import("./hours.js"),o=_();try{const s=e.project,u=s?P(o,s):null;if(s&&!u){p(`Project not found: ${s}`),process.exitCode=1;return}const c=new i(o.getDatabase()).report({range_start:r,range_end:t,project_id:u?.id,default_session_hours:Z(e["session-hours"],1)});if(e.json===!0){console.log(JSON.stringify(c,null,2));return}if(S(`Hours ${c.range.start} \u2192 ${c.range.end}`),c.by_project.length===0){console.log(d.dim(" No sessions in range."));return}console.log(I(["Project","Sessions","Hours"],c.by_project.map(f=>[f.project_name,String(f.session_count),f.hours.toFixed(2)]))),console.log(`
|
|
71
|
+
Total: ${c.total_hours.toFixed(2)}h across ${c.entries.length} session(s)`+(c.estimated_sessions>0?d.dim(` (${c.estimated_sessions} estimated)`):""))}finally{o.close()}}async function ft(l){const{positional:a,flags:e}=C(l),n=a[0]??"generate",r=e.client,t=Z(e.rate,NaN),i=e.from,o=e.to;(n!=="generate"||!r||!Number.isFinite(t)||!i||!o)&&(p('Usage: wyrm invoice generate --client <name> --rate <usd/hour> --from YYYY-MM-DD --to YYYY-MM-DD [--project <name>] [--number INV-X] [--currency USD] [--notes "\u2026"] [--out <path>]'),process.exit(1));const{HourLedger:s}=await import("./hours.js"),u=_();try{const c=e.project,f=c?P(u,c):null;if(c&&!f){p(`Project not found: ${c}`),process.exitCode=1;return}const y=new s(u.getDatabase()).invoice({client_name:r,hourly_rate_usd:t,range_start:i,range_end:o,project_id:f?.id,invoice_number:e.number,currency:e.currency,notes:e.notes,business_name:e["business-name"],business_address:e["business-address"],business_contact:e["business-contact"],client_address:e["client-address"],default_session_hours:Z(e["session-hours"],1)}),m=e.out;if(m){const{writeFileSync:h}=await import("node:fs");h(m,y+`
|
|
72
|
+
`,"utf-8"),b(`Invoice written to ${m}`)}else process.stdout.write(y+`
|
|
73
|
+
`)}finally{u.close()}}async function yt(l){const{positional:a,flags:e}=C(l),n=a[0]??"status",{AgentDaemon:r}=await import("./agent-daemon.js"),t=_();try{const i=new r(t.getDatabase());switch(n){case"init":case"start":{const o=Math.max(10,Math.min(T(e.interval,600),86400)),s=i.start({interval_seconds:o,max_steps:e["max-steps"]!==void 0&&T(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!s.ok){p(`Agent init failed: ${s.error}`),process.exitCode=1;return}const u=s.status;b(`Agent ${u.pid!=null&&u.pid!==s.pid?"already running":"started"} \u2014 pid ${u.pid}`),console.log(` Interval: ${o}s \xB7 Active goals: ${u.active_goals} \xB7 Total iterations: ${u.total_iterations}`),console.log(d.dim(` Log: ${u.log_file}`));return}case"status":{const o=i.status();S("Wyrm agent"),console.log(o.running?` RUNNING \u2014 pid ${o.pid}${o.started_at?` (since ${o.started_at})`:""}`:" NOT RUNNING. Start it with: wyrm agent init"),console.log(` Active goals: ${o.active_goals} \xB7 Total iterations: ${o.total_iterations}`),o.last_action&&console.log(` Last action (${o.last_action.ran_at}): ${o.last_action.summary} [${o.last_action.result_status??"?"}]`),e.log===!0&&(console.log(`
|
|
74
|
+
=== Recent log ===`),console.log(i.recentLog(40)));return}case"stop":{const o=await i.stop({grace_ms:e.grace!==void 0?T(e.grace,3e3):void 0});if(!o.ok){p(`Stop failed: ${o.error}`),process.exitCode=1;return}b(o.was_running?`Agent stopped (was pid ${o.pid}). Goals remain in DB \u2014 wyrm agent init resumes.`:"Agent was not running. (No-op.)");return}case"restart":{const o=await i.restart({interval_seconds:e.interval!==void 0?T(e.interval,600):void 0,max_steps:e["max-steps"]!==void 0&&T(e["max-steps"],0)||void 0,project_path:e.project,verbose:e.verbose===!0});if(!o.ok){p(`Restart failed: ${o.error}`),process.exitCode=1;return}b(`Agent restarted \u2014 pid ${o.status.pid}. Active goals: ${o.status.active_goals}.`);return}default:p("Usage: wyrm agent <init|status|stop|restart> [--interval N] [--max-steps N] [--project <path>] [--verbose] [--log]"),process.exitCode=1}}finally{t.close()}}async function gt(l){if(l.includes("--encrypt")){const{initializeLicense:n,hasFeature:r}=await import("./license.js");if(n(),!r("encryption")){p("Encryption setup requires a Pro license or higher. See: wyrm license"),process.exitCode=1;return}const{getCrypto:t,initializeCrypto:i}=await import("./crypto.js"),o=l.includes("--enable"),s=l.includes("--test");if(o){const c=process.env.WYRM_ENCRYPTION_KEY??(process.stdin.isTTY?"":we());if(!c||c.length<8){p('Password must be at least 8 characters. Set WYRM_ENCRYPTION_KEY or pipe it: printf %s "$PW" | wyrm setup --encrypt --enable'),process.exitCode=1;return}i(c),b("Encryption enabled (AES-256-GCM, key derived via PBKDF2). Store your password safely \u2014 it cannot be recovered.");return}const u=t();if(s){if(!u.isEnabled()){p("Encryption not enabled. Run: wyrm setup --encrypt --enable"),process.exitCode=1;return}const c="Wyrm encryption test "+Date.now();u.decrypt(u.encrypt(c))===c?b("Encryption test PASSED (encrypt \u2192 decrypt roundtrip)."):(p("Encryption test FAILED."),process.exitCode=1);return}S("Encryption status"),console.log(` Enabled: ${u.isEnabled()?"yes \u2014 new data is encrypted at rest":"no"}`),console.log(" Algorithm: AES-256-GCM"),u.isEnabled()||console.log(d.dim(" Enable with: wyrm setup --encrypt --enable (password via WYRM_ENCRYPTION_KEY or stdin)"));return}const a=le(de(import.meta.url)),e=z(process.execPath,[K(a,"setup.js"),...l],{stdio:"inherit",shell:!1});process.exitCode=e.status??0}async function ht(l){const a="https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp",n=`- wyrm-mcp: ${A().version??"unknown"}
|
|
75
|
+
- node: ${process.version}
|
|
76
|
+
- platform: ${process.platform} ${process.arch}`,r=l.includes("--bug")?"bug":l.includes("--idea")||l.includes("--feature")?"idea":l.includes("--question")||l.includes("--ask")?"question":"feedback",t=l.filter(u=>!u.startsWith("--")).join(" ").trim(),i={bug:`**What happened**
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
**What you expected**
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
**Steps to reproduce**
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
_environment_
|
|
87
|
+
${n}`,idea:`**The idea**
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
**Why it would help**
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
${n}`,question:`**Your question**
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
${n}`,feedback:`**Your feedback** (what's working, what's rough)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
${n}`},o={bug:"bug",idea:"enhancement",question:"question",feedback:"feedback"};let s;r==="question"?s=`${a}/discussions/new?category=q-a`+(t?`&title=${encodeURIComponent(t)}`:"")+`&body=${encodeURIComponent(i.question)}`:s=`${a}/issues/new?labels=${encodeURIComponent(o[r])}&title=${encodeURIComponent(`[${r}] ${t}`.trim())}&body=${encodeURIComponent(i[r])}`,console.log(""),console.log(" "+d.bold("Thanks for helping make Wyrm better.")),console.log(" Opening a prefilled report in your browser. If it does not open, use this link:"),console.log(""),console.log(" "+d.cyan(s)),console.log(""),console.log(d.dim(" Prefer email? ryan@ghosts.lk \xB7 Bug: wyrm feedback --bug Idea: --idea Question: --question")),console.log("");try{const{spawn:u}=await import("child_process"),c=process.platform,f=c==="darwin"?u("open",[s],{stdio:"ignore",detached:!0}):c==="win32"?u("cmd",["/c","start","",s],{stdio:"ignore",detached:!0}):u("xdg-open",[s],{stdio:"ignore",detached:!0});f.on("error",()=>{}),f.unref()}catch{}}function be(){console.log(`
|
|
103
|
+
${M.brightMagenta}\u{F115D} Wyrm CLI v${A().version??"unknown"}${M.reset}
|
|
76
104
|
${M.dim}Persistent AI Memory System${M.reset}
|
|
77
105
|
|
|
78
106
|
${d.bold("Usage:")} wyrm <command> [options]
|
|
@@ -110,6 +138,7 @@ ${d.bold("Commands:")}
|
|
|
110
138
|
${d.cyan("hours")} report --from --to Hours report derived from session history
|
|
111
139
|
${d.cyan("invoice")} generate --client \u2026 Markdown invoice from the hours ledger
|
|
112
140
|
${d.cyan("agent")} <init|status|stop|restart> Background agent daemon (goal loop)
|
|
141
|
+
${d.cyan("feedback")} [--bug|--idea|--question] Send feedback or report a bug (opens a prefilled GitHub report)
|
|
113
142
|
|
|
114
143
|
${d.bold("Options:")}
|
|
115
144
|
--project <name> Filter or associate with a specific project
|
|
@@ -139,13 +168,13 @@ ${d.bold("Examples:")}
|
|
|
139
168
|
wyrm sync export --out ~/wyrm-backup.wyrm
|
|
140
169
|
wyrm sync preview --from ~/wyrm-backup.wyrm
|
|
141
170
|
wyrm prune --project MyApp --min-confidence 0.2 --older-than 30
|
|
142
|
-
`)}const[,,N,...k]=process.argv;function $e(l){const a=l.ref_table?`${l.ref_table}${l.ref_id?"#"+l.ref_id:""}`:"",e=new Date(l.created_at).toLocaleTimeString();return`#${l.cursor} ${l.kind.padEnd(15)} ${a.padEnd(16)} ${
|
|
171
|
+
`)}const[,,N,...k]=process.argv;function $e(l){const a=l.ref_table?`${l.ref_table}${l.ref_id?"#"+l.ref_id:""}`:"",e=new Date(l.created_at).toLocaleTimeString();return`#${l.cursor} ${l.kind.padEnd(15)} ${a.padEnd(16)} ${Fe(l.actor)} ${e}`}async function wt(l){const{positional:a,flags:e}=C(l),n=a[0]||"since",r=_();try{if(!r.liveMemoryEnabled()){p("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),process.exitCode=1;return}const t=(typeof e.project=="string"?e.project:"")||process.cwd(),i=r.getProject(t)??r.getProjectByName(t);if(!i){p(`Project not found: ${t}`),process.exitCode=1;return}if(n==="publish"){const o=a[1]||(typeof e.kind=="string"?e.kind:"");if(!o){p("usage: wyrm events publish <kind> --project <p> [--actor A] [--ref-table T --ref-id ID]"),process.exitCode=1;return}r.publishEvent({projectId:i.id,kind:o,refTable:typeof e["ref-table"]=="string"?e["ref-table"]:void 0,refId:typeof e["ref-id"]=="string"?e["ref-id"]:void 0,actor:typeof e.actor=="string"?e.actor:void 0}),b(`Event published (${o}) to ${i.name}`);return}if(n==="since"){const o=T(e.cursor,0),s=T(e.limit,50),u=r.eventsSince(i.id,o,s);for(const c of u)console.log($e(c));S(`${u.length} event(s) for '${i.name}' since cursor ${o}`);return}p("usage: wyrm events <publish|since> ..."),process.exitCode=1}finally{r.close()}}async function bt(l){const{flags:a}=C(l),e=_();if(!e.liveMemoryEnabled()){p("Live Memory is disabled (set WYRM_LIVE_MEMORY=1)."),e.close(),process.exitCode=1;return}const n=(typeof a.project=="string"?a.project:"")||process.cwd(),r=e.getProject(n)??P(e,n);if(!r){p(`Project not found: ${n}`),e.close(),process.exitCode=1;return}const t=Math.max(250,T(a.interval,1e3));let i=T(a.since,e.subscribeEvents(r.id,1).cursor);S(`Watching '${r.name}' (cursor ${i}, every ${t}ms) \u2014 Ctrl-C to stop`);const o=()=>{try{for(const c of e.eventsSince(r.id,i,200))i=c.cursor,console.log($e(c))}catch{}};o();const s=setInterval(o,t),u=()=>{clearInterval(s);try{e.close()}catch{}process.exit(0)};process.on("SIGINT",u),process.on("SIGTERM",u)}async function $t(l){const{flags:a}=C(l),{embedAll:e,removeAll:n,statusAll:r}=await import("./priority-embed.js"),t={projectDir:typeof a.project=="string"?a.project:void 0,allClients:a.all===!0},i=o=>console.log(` ${String(o.result??o.status).padEnd(9)} [${o.scope}] ${o.file}`);if(a.status){S("Wyrm priority embedding \u2014 status"),r(t).forEach(i);return}if(a.remove){S("Wyrm priority embedding \u2014 removed"),n(t).forEach(i);return}S("Wyrm is now FIRST-PRIORITY memory"),e(t).forEach(i);try{const{installClaudeCodeHooks:o,installClaudeStatusline:s}=await import("./autoconfig.js");o()?b("Proactive hooks installed (SessionStart rehydrate + capture + tool-trace)."):console.log(" (Claude Code not detected \u2014 skipped hook install.)");const c=s();c&&b(`Buddy statusline: ${c.message}`)}catch{}b("Wyrm will now be read first, primed proactively, and shown in the TUI at all times.")}async function vt(l){const{flags:a}=C(l),{harvestProjects:e}=await import("./harvest.js"),{MemoryArtifacts:n}=await import("./memory-artifacts.js"),{escapeLikePattern:r}=await import("./auto-capture.js"),t=_();try{const i=t.getDatabase(),o=new n(i),s={existsBySig:(w,v)=>!!i.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(w,"%"+r(v)+"%"),addCandidate:(w,v)=>o.add(w,{kind:v.kind,problem:v.text,tags:[...v.tags,v.sig],confidence:v.confidence,needsReview:1,createdBy:"harvest"})},u=typeof a.project=="string"?a.project:void 0;let c;if(u){const w=t.getProject(u)??t.getProjectByName(u);if(!w){p(`Project not found: ${u}`),process.exitCode=1;return}c=[{id:w.id,name:w.name,path:w.path}]}else c=t.getAllProjects(500).map(w=>({id:w.id,name:w.name,path:w.path}));const f=a["dry-run"]===!0||a.dry===!0,y=a.code===!0||a["include-code"]===!0,{reports:m,totalAdded:h,totalSkipped:g}=e(s,c,{dryRun:f,gitLimit:T(a.limit,30),includeCode:y});S(`Harvest ${f?"(dry run) ":""}\u2014 ${h} candidate(s), ${g} already present (${c.length} project(s))`);for(const w of m.filter(v=>v.added>0).sort((v,R)=>R.added-v.added).slice(0,25))console.log(` +${String(w.added).padStart(3)} (skip ${w.skipped}) ${w.project}`);if(y&&!f){const{SymbolGraph:w}=await import("./symbols.js"),v=new w(i);let R=0,$=0;for(const E of c)try{const j=v.indexProject(E.id,E.path);R+=j.symbols,$+=j.files}catch{}console.log(` \u{1F4D0} Indexed ${R} code symbols (${$} files) \u2192 searchable via 'wyrm search'`)}!f&&h>0&&b("Review with: wyrm review")}finally{t.close()}}async function kt(l){const a=await import("./vault.js"),[e,...n]=l;try{switch(e){case"set":{const r=n[0];if(!r){p("usage: wyrm vault set <name> (the secret is read from STDIN, never argv)"),process.exitCode=1;return}if(process.stdin.isTTY){p(`pipe the secret in, e.g.: printf %s "$TOKEN" | wyrm vault set ${r}`),process.exitCode=1;return}const i=(await import("node:fs")).readFileSync(0,"utf8").replace(/\r?\n$/,"");if(!i){p("empty secret on stdin"),process.exitCode=1;return}a.vaultSet(r,i),b(`Stored "${r}" (AES-256-GCM). Use it without exposing it: wyrm vault exec ${r} -- <command>`);break}case"get":{const r=n[0];if(!r){p("usage: wyrm vault get <name>"),process.exitCode=1;return}const t=a.vaultGet(r);if(t===void 0){p(`no secret named "${r}"`),process.exitCode=1;return}process.stdout.write(t);break}case"list":case"ls":{const r=a.vaultList();if(!r.length){console.log("(vault is empty)");break}S(`Vault \u2014 ${r.length} secret(s)`);for(const t of r)console.log(" \u2022 "+t);break}case"rm":case"remove":case"delete":{const r=n[0];if(!r){p("usage: wyrm vault rm <name>"),process.exitCode=1;return}b(a.vaultRemove(r)?`Removed "${r}"`:`(no secret named "${r}")`);break}case"exec":{const r=n[0],t=n.indexOf("--");if(!r||t===-1||t+1>=n.length){p("usage: wyrm vault exec <name> [--as ENVVAR] -- <command...>"),process.exitCode=1;return}const i=n.slice(1,t),o=i.indexOf("--as"),s=o>=0?i[o+1]:r.toUpperCase().replace(/[^A-Z0-9]+/g,"_"),u=n.slice(t+1),c=a.vaultGet(r);if(c===void 0){p(`no secret named "${r}"`),process.exitCode=1;return}const f=z(u[0],u.slice(1),{stdio:"inherit",env:{...process.env,[s]:c}});process.exitCode=f.status??1;break}case"import-npm":{const r=await import("node:fs"),t=await import("node:os"),o=(await import("node:path")).join(t.homedir(),".npmrc");if(!r.existsSync(o)){p("~/.npmrc not found"),process.exitCode=1;return}const s=r.readFileSync(o,"utf8").match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);if(!s){p("no npm authToken found in ~/.npmrc"),process.exitCode=1;return}a.vaultSet("npm-token",s[1].trim()),b('Imported npm token \u2192 vault as "npm-token". You can now scrub the plaintext from ~/.npmrc and use: wyrm vault exec npm-token --as NODE_AUTH_TOKEN -- npm publish');break}case"setup":{const r=a.vaultPaths();if(r.secure)b(`Vault is already secure (backend: ${r.backend}, ${r.count} secret(s)).`);else{const t=r.keychainAvailable?"keychain":"passphrase";if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){p("No OS keychain on this host. Set WYRM_VAULT_PASSPHRASE, then re-run: wyrm vault setup"),process.exitCode=1;return}const i=a.vaultSecure({backend:t});b(`Vault secured: ${i.from} \u2192 ${i.to}${i.rotated?" (key rotated)":""}.`),i.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),i.backup&&console.log(` ciphertext backup: ${i.backup} (delete once confirmed)`)}S("Store & use credentials safely"),console.log(' store: printf %s "$TOKEN" | wyrm vault set <name> # reads STDIN \u2014 never argv/shell history'),console.log(" use: wyrm vault exec <name> --as ENV_VAR -- <cmd> # injected as env var, never printed"),console.log(" list: wyrm vault list inspect: wyrm vault info");break}case"secure":{const r=n.indexOf("--backend"),t=r>=0?n[r+1]:"keychain";if(t!=="keychain"&&t!=="passphrase"){p("usage: wyrm vault secure [--backend keychain|passphrase] [--no-rotate]"),process.exitCode=1;return}if(t==="passphrase"&&!process.env.WYRM_VAULT_PASSPHRASE){p("set WYRM_VAULT_PASSPHRASE before: wyrm vault secure --backend passphrase"),process.exitCode=1;return}const i=!n.includes("--no-rotate"),o=a.vaultSecure({backend:t,rotate:i});b(`Vault secured: ${o.from} \u2192 ${o.to}${o.rotated?" (key rotated)":""}.`),console.log(` secrets re-encrypted: ${o.secrets}`),o.keyfileShredded&&console.log(" plaintext vault.key: shredded \u2714"),o.backup&&console.log(` ciphertext backup: ${o.backup} (delete once you've confirmed)`),console.log(t==="keychain"?" master key now lives in the OS keychain \u2014 not on disk.":" master key now derived from WYRM_VAULT_PASSPHRASE \u2014 keep that set for future use.");break}case"info":{const r=a.vaultPaths();S("Vault"),console.log(` backend: ${r.backend}`),console.log(` secrets: ${r.count}`),console.log(` store: ${r.vault} (0600)`),console.log(` key: ${r.backend==="keyfile"?r.key+" (0600)":r.backend==="keychain"?"(OS keychain \u2014 no key on disk)":"(derived from WYRM_VAULT_PASSPHRASE \u2014 no key on disk)"}`),console.log(` secure: ${r.secure?"yes \u2014 key is not a plaintext file beside the ciphertext":"NO \u2014 key sits beside ciphertext"}`),r.secure||console.log(r.keychainAvailable?" \u26A0 run `wyrm vault secure` to move the key into the OS keychain.":" \u26A0 no OS keychain found \u2014 set WYRM_VAULT_PASSPHRASE and run `wyrm vault secure --backend passphrase`.");break}default:p("usage: wyrm vault <setup|set|get|list|rm|exec|import-npm|secure|info>"),process.exitCode=1}}catch(r){p(`vault: ${r.message}`),process.exitCode=1}}async function St(l){const a=l[0];if(!a||a==="help"||a==="--help"){console.log(`Usage:
|
|
143
172
|
wyrm skill backfill-content read every skill's SKILL.md into the registry (idempotent)
|
|
144
173
|
wyrm skill export <targetDir> [--all] materialize SKILL.md files from stored content
|
|
145
|
-
wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}const e=_();try{if(a==="backfill-content"||a==="backfill"){S("Backfill SKILL.md content into the registry");const
|
|
174
|
+
wyrm skill share <name|--all|--tier T> [--public|--private] [--include-inactive] set cloud-sync visibility (single or bulk)`);return}const e=_();try{if(a==="backfill-content"||a==="backfill"){S("Backfill SKILL.md content into the registry");const n=e.backfillSkillContent();b(`${n.filled} filled . ${n.unchanged} unchanged . ${n.missing} missing-file (of ${n.total} registered)`),n.missing>0&&console.log(d.yellow(` ${n.missing} skill(s) had no readable SKILL.md \u2014 re-run after restoring their files.`));return}if(a==="export"){const n=l[1];n||(p("Usage: wyrm skill export <targetDir> [--all]"),e.close(),process.exit(1));const r=l.includes("--all");S(`Export skills \u2192 ${n}`);const t=e.exportSkillContent(n,{includeInactive:r});b(`${t.written} SKILL.md written . ${t.skipped_no_content} skipped (no stored content) (of ${t.total} ${r?"total":"active"})`),t.skipped_no_content>0&&console.log(d.yellow(" Run `wyrm skill backfill-content` on the source machine to populate content first.")),t.collisions>0&&console.log(d.yellow(` ${t.collisions} slug collision(s) disambiguated with a name-hash suffix (distinct skills, same slug) \u2014 no content lost.`));return}if(a==="share"){const n=l.includes("--private")?"within":l.includes("--public")?"public":"org",r=l.includes("--include-inactive"),t=l.indexOf("--tier"),i=t>=0?l[t+1]:void 0;if(t>=0&&(!i||i.startsWith("--"))&&(p("Usage: wyrm skill share --tier <god|mega|atomic> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),l.includes("--all")||!!i){const c=e.setAllSkillsVisibility(n,{tier:i,includeInactive:r}),f=i?`tier '${i}'`:r?"all skills":"all active skills";b(`${c} skill(s) (${f}) visibility \u2192 '${n}'.`),console.log(n==="within"?" These skills will NOT egress on cloud sync (private).":` ${c} skills are now cloud-sync-eligible (they leave on the next \`wyrm cloud sync\`).`);return}const s=l[1];s||(p("Usage: wyrm skill share <name|--all|--tier <tier>> [--public|--private] [--include-inactive]"),e.close(),process.exit(1)),e.setSkillVisibility(s,n)||(p(`Skill not found: ${s}`),e.close(),process.exit(1)),b(`Skill "${s}" visibility \u2192 '${n}'.`),console.log(n==="within"?" This skill will NOT egress on cloud sync (private).":" This skill is now cloud-sync-eligible (it leaves on the next `wyrm cloud sync`).");return}p(`Unknown skill subcommand: ${a}`),process.exit(1)}finally{e.close()}}async function jt(l){const a=l[0]??"status",e=_(),n=e.getDatabase();if(n.prepare("PRAGMA table_info(projects)").all().some(t=>t.name==="sync_policy")||(p("Grove sync policy is not on this database yet (upgrade Wyrm so migrations apply)."),e.close(),process.exit(1)),a==="status"||a==="ls"||a==="list"){S("Grove sync policy + leak audit");const t=n.prepare("SELECT id, name, sync_policy FROM projects ORDER BY id").all(),i=["ground_truths","memory_artifacts","quests","design_tokens","design_references"],o=["ground_truths","memory_artifacts","quests","sessions","decision_edges"],s=(f,y,m)=>{let h=0;for(const g of f)try{h+=n.prepare(`SELECT COUNT(*) AS n FROM ${g} WHERE project_id = ? AND ${m}`).get(y).n}catch{}return h},u=[];let c=0;for(const f of t){const y=s(i,f.id,"cross_project_visibility IN ('org','public')"),m=s(o,f.id,"is_shared = 1"),h=f.sync_policy==="private"&&y+m>0;h&&c++;const g=h?d.red(`LEAK: ${y} promoted + ${m} shared in a PRIVATE grove`):`${y} promoted / ${m} shared`;u.push([String(f.id),f.name,f.sync_policy,g])}console.log(I(["#","Grove","sync_policy","rows eligible to leave"],u)),console.log(`
|
|
146
175
|
private = never replicates . cloud = your own cloud backup . team = federates to a team Wyrm`),c>0&&console.log(d.red(`
|
|
147
|
-
! ${c} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),e.close();return}if(a==="policy"||a==="set"){const t=l[1],i=l[2];(!t||!["private","cloud","team"].includes(i))&&(p("Usage: wyrm grove policy <project|id> <private|cloud|team>"),e.close(),process.exit(1));let o=P(e,t);!o&&/^\d+$/.test(t)&&(o=
|
|
176
|
+
! ${c} private grove(s) hold rows marked to leave. Re-private those rows or change the grove lane.`)),e.close();return}if(a==="policy"||a==="set"){const t=l[1],i=l[2];(!t||!["private","cloud","team"].includes(i))&&(p("Usage: wyrm grove policy <project|id> <private|cloud|team>"),e.close(),process.exit(1));let o=P(e,t);!o&&/^\d+$/.test(t)&&(o=n.prepare("SELECT id, name FROM projects WHERE id = ?").get(Number(t))),o||(p(`Grove not found: ${t}`),e.close(),process.exit(1)),n.prepare("UPDATE projects SET sync_policy = ? WHERE id = ?").run(i,o.id),b(`Grove "${o.name}" set to '${i}'.`),i!=="private"&&console.log(` Rows still only leave when also marked ${i==="team"?"is_shared (team)":"org/public (cloud)"}. The grove is the outer gate.`),e.close();return}p(`Unknown grove subcommand: ${a}`),console.log(`Usage:
|
|
148
177
|
wyrm grove status
|
|
149
|
-
wyrm grove policy <project|id> <private|cloud|team>`),e.close(),process.exit(1)}async function
|
|
150
|
-
`),process.stderr.write(`Run ${w} started by ${
|
|
151
|
-
`);break}case"join":{const
|
|
178
|
+
wyrm grove policy <project|id> <private|cloud|team>`),e.close(),process.exit(1)}async function _t(l){const a=l[0],{flags:e}=C(l.slice(1)),{ulid:n}=await import("./ulid.js"),{createRun:r,setRunStatus:t,registerAgent:i,getRun:o,getAgents:s}=await import("./handlers/run.js"),{sanitizeActorId:u}=await import("./handlers/boundary.js"),c=m=>typeof m=="string"?u(m):null,f=_(),y=f.getDatabase();try{switch(a){case"start":{const m=(typeof e.orchestrator=="string"?e.orchestrator:"cli").slice(0,200),h=c(e.agent)??u(m)??"cli",g=c(e.parent);if(g&&!o(y,g)){p(`Parent run not found: ${g}`),process.exitCode=1;return}const w=n();r(y,w,g,m),i(y,w,h,"orchestrator"),process.stdout.write(w+`
|
|
179
|
+
`),process.stderr.write(`Run ${w} started by ${m}.
|
|
180
|
+
`);break}case"join":{const m=c(e.run),h=c(e.agent);if(!m||!h){p("Usage: wyrm run join --run <RUNID> --agent <ID> [--role <ROLE>]"),process.exitCode=1;return}if(!o(y,m)){p(`Run not found: ${m}`),process.exitCode=1;return}const g=typeof e.role=="string"?e.role.slice(0,200):null;i(y,m,h,g),b(`${h} joined run ${m}${g?` as ${g}`:""}.`);break}case"end":{const m=c(e.run);if(!m){p("Usage: wyrm run end --run <RUNID> [--status completed|failed|abandoned]"),process.exitCode=1;return}if(!o(y,m)){p(`Run not found: ${m}`),process.exitCode=1;return}const h=typeof e.status=="string"?e.status:"completed",w=["completed","failed","abandoned"].includes(h)?h:"completed";t(y,m,w),b(`Run ${m} ended (${w}).`);break}case"status":{const m=c(e.run);if(!m){p("Usage: wyrm run status --run <RUNID>"),process.exitCode=1;return}const h=o(y,m);if(!h){p(`Run not found: ${m}`),process.exitCode=1;return}const g=s(y,m);S(`Run ${h.run_id} [${h.status}]`),h.orchestrator&&console.log(d.bold("Orchestrator: ")+h.orchestrator),h.parent_run_id&&console.log(d.bold("Parent: ")+h.parent_run_id),console.log(d.bold("Created: ")+h.created_at),console.log(d.bold("Updated: ")+h.updated_at),g.length>0?console.log(I(["Agent","Role","Joined"],g.map(w=>[w.agent_id,w.role??"",w.joined_at]))):console.log(d.dim(" No agents registered."));break}default:p("Usage: wyrm run <start|join|end|status> [options]"),console.log(" wyrm run start [--orchestrator NAME] [--agent ID] [--parent RUNID]"),console.log(" wyrm run join --run RUNID --agent ID [--role ROLE]"),console.log(" wyrm run end --run RUNID [--status completed|failed|abandoned]"),console.log(" wyrm run status --run RUNID"),process.exitCode=1;return}}finally{f.close()}}if(N==="--version"||N==="-v"||N==="version"){const l=A();console.log(`${l.name??"wyrm-mcp"} v${l.version??"unknown"}`)}else!N||N==="--help"||N==="-h"||N==="help"?be():(async()=>{try{switch(N){case"search":await Ve(k);break;case"ls":await Be(k);break;case"show":await Ge(k);break;case"capture":await Ke(k);break;case"rehydrate":await Je(k);break;case"render":await ze(k);break;case"reverse-bridge":await Qe(k);break;case"import":await Ze(k);break;case"stats":await et(k);break;case"review":await tt(k);break;case"sync":await ot(k);break;case"cloud":{const{cmdCloud:l}=await import("./cloud/cli.js");await l(k);break}case"grove":await jt(k);break;case"run":await _t(k);break;case"skill":await St(k);break;case"prune":await st(k);break;case"license":await rt();break;case"login":await it();break;case"activate":await at(k);break;case"maintenance":await ct(k);break;case"index":await lt(k);break;case"update":await pt(k);break;case"prompt":await ut(k);break;case"hours":await mt(k);break;case"invoice":await ft(k);break;case"agent":await yt(k);break;case"feedback":await ht(k);break;case"setup":await gt(k);break;case"intro":{const{renderIntro:l}=await import("./visibility.js");console.log(l(A().version??"unknown"));break}case"events":await wt(k);break;case"watch":await bt(k);break;case"embed":await $t(k);break;case"harvest":await vt(k);break;case"vault":await kt(k);break;case"connector":case"connectors":await nt(k);break;case"statusline":{const{installClaudeStatusline:l,removeClaudeStatusline:a}=await import("./autoconfig.js"),e=k.includes("--remove")?a():l();e?b(e.message):p("Claude Code not detected (~/.claude missing).");break}case"guard":{const{installWyrmGuardHooks:l,removeWyrmGuardHooks:a,wyrmGuardHookStatus:e}=await import("./autoconfig.js");if(k.includes("--status")){const r=e();if(console.log(` settings: ${r.settingsPath}`),r.installed){b(`wyrm-guard hooks installed (${r.commands.length} entr${r.commands.length===1?"y":"ies"})`);for(const t of r.commands)console.log(d.dim(` ${t}`))}else console.log(d.yellow(" wyrm-guard hooks not installed \u2014 run: wyrm guard"));break}const n=k.includes("--remove")||k.includes("--uninstall")?a():l();n?n.action==="failed"?(p(n.message),process.exitCode=1):b(n.message):p("Claude Code not detected (~/.claude missing).");break}case"ui":case"dashboard":k.includes("--ui")||k.push("--ui");case"serve":{const l=k.includes("--ui");if(l){const{enableDevMode:r}=await import("./http-auth.js");r()}const{server:a}=await import("./http-fast.js"),e=parseInt(process.env.WYRM_PORT??process.env.PORT??"3333",10),n=process.env.WYRM_BIND_HOST||"127.0.0.1";a.listen(e,n,()=>{if(b(`Wyrm HTTP server running on ${n}:${e}`),process.env.WYRM_UI_READONLY==="1"&&console.log("\u{1F512} READ-ONLY mode: writes + off-box egress are blocked; safe to expose."),l){const r=`http://localhost:${e}/ui`;console.log(`\u{1F5A5}\uFE0F Dashboard: ${r}`),import("child_process").then(({spawn:t})=>{const i=process.platform;try{const o=i==="darwin"?t("open",[r],{stdio:"ignore",detached:!0}):i==="win32"?t("cmd",["/c","start","",r],{stdio:"ignore",detached:!0}):t("xdg-open",[r],{stdio:"ignore",detached:!0});o.on("error",()=>{}),o.unref()}catch{}}).catch(()=>{})}});break}default:p(`Unknown command: ${N}`),be(),process.exit(1)}}catch(l){p(String(l)),process.exit(1)}})();
|
package/dist/wyrm-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wyrm-mcp",
|
|
3
|
-
"version": "7.5.
|
|
3
|
+
"version": "7.5.2",
|
|
4
4
|
"mcpName": "lk.ghosts/wyrm",
|
|
5
|
-
"description": "Local-first persistent memory for AI agents over MCP. Ground truths, negative learning (recorded failures block repeats), decision causality, hybrid recall, live memory streams, run-attributed fleet memory
|
|
5
|
+
"description": "Local-first persistent memory for AI agents over MCP. Ground truths, negative learning (recorded failures block repeats), decision causality, hybrid recall, live memory streams, run-attributed fleet memory — a structured SQLite memory on your machine, no cloud or LLM required. Claude / Copilot / Cursor / Windsurf / Codex.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"bin": {
|
|
@@ -86,13 +86,13 @@
|
|
|
86
86
|
],
|
|
87
87
|
"author": "Ghost Protocol (Pvt) Ltd <ryan@ghosts.lk>",
|
|
88
88
|
"license": "UNLICENSED",
|
|
89
|
-
"homepage": "https://
|
|
89
|
+
"homepage": "https://ghosts.lk/wyrm",
|
|
90
90
|
"repository": {
|
|
91
91
|
"type": "git",
|
|
92
|
-
"url": "git+https://github.com/Ghosts-Protocol-Pvt-Ltd/
|
|
92
|
+
"url": "git+https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp.git"
|
|
93
93
|
},
|
|
94
94
|
"bugs": {
|
|
95
|
-
"url": "https://github.com/Ghosts-Protocol-Pvt-Ltd/
|
|
95
|
+
"url": "https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/issues"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
98
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -93,8 +93,9 @@ function firstInstallPitch() {
|
|
|
93
93
|
' 2. (Optional) Enable semantic search: ollama pull nomic-embed-text',
|
|
94
94
|
' 3. Then paste into your AI: "What does Wyrm do?"',
|
|
95
95
|
'',
|
|
96
|
-
'Docs: https://github.com/Ghosts-Protocol-Pvt-Ltd/
|
|
97
|
-
'Troubleshoot: https://github.com/Ghosts-Protocol-Pvt-Ltd/
|
|
96
|
+
'Docs: https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp',
|
|
97
|
+
'Troubleshoot: https://github.com/Ghosts-Protocol-Pvt-Ltd/wyrm-mcp/blob/main/TROUBLESHOOTING.md',
|
|
98
|
+
'Feedback: wyrm feedback (a rough edge or an idea both help — takes 30s)',
|
|
98
99
|
'',
|
|
99
100
|
'Silence this banner on future installs: WYRM_SKIP_POSTINSTALL=1',
|
|
100
101
|
'─────────────────────────────────────────────────────────────────────',
|