@ectplsm/relic 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -99
- package/dist/core/usecases/extract.d.ts +5 -23
- package/dist/core/usecases/extract.js +19 -115
- package/dist/core/usecases/index.d.ts +3 -3
- package/dist/core/usecases/index.js +3 -3
- package/dist/core/usecases/inject.d.ts +8 -3
- package/dist/core/usecases/inject.js +31 -12
- package/dist/core/usecases/sync.d.ts +44 -20
- package/dist/core/usecases/sync.js +182 -56
- package/dist/interfaces/cli/commands/claw.d.ts +2 -0
- package/dist/interfaces/cli/commands/claw.js +149 -0
- package/dist/interfaces/cli/commands/config.js +6 -6
- package/dist/interfaces/cli/commands/extract.js +7 -12
- package/dist/interfaces/cli/commands/inject.js +2 -2
- package/dist/interfaces/cli/commands/sync.js +24 -68
- package/dist/interfaces/cli/index.js +2 -6
- package/dist/interfaces/mcp/index.js +14 -0
- package/dist/shared/config.d.ts +7 -7
- package/dist/shared/config.js +18 -58
- package/dist/shared/engram-composer.js +5 -3
- package/dist/shared/openclaw.d.ts +25 -3
- package/dist/shared/openclaw.js +48 -4
- package/package.json +3 -2
- package/templates/engrams/johnny/IDENTITY.md +25 -0
- package/templates/engrams/johnny/SOUL.md +35 -0
- package/templates/engrams/motoko/IDENTITY.md +25 -0
- package/templates/engrams/motoko/SOUL.md +38 -0
package/README.md
CHANGED
|
@@ -13,9 +13,28 @@
|
|
|
13
13
|
|
|
14
14
|
**Inject a unified AI persona with persistent memory into any coding CLI.**
|
|
15
15
|
|
|
16
|
-
Relic manages AI **Engrams** (memory + personality) and injects them into coding assistants like Claude Code, Codex CLI, Gemini CLI. One persona, any shell.
|
|
16
|
+
Relic manages AI **Engrams** (memory + personality) and injects them into coding assistants like Claude Code, Codex CLI, Gemini CLI. Also integrates with OpenClaw and other Claw-based agent frameworks. One persona, any shell.
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Install](#install)
|
|
21
|
+
- [Quick Start](#quick-start)
|
|
22
|
+
- [What `relic init` Creates](#what-relic-init-creates)
|
|
23
|
+
- [Sample Engrams](#sample-engrams)
|
|
24
|
+
- [How It Works](#how-it-works)
|
|
25
|
+
- [Supported Shells](#supported-shells)
|
|
26
|
+
- [Conversation Log Recording](#conversation-log-recording)
|
|
27
|
+
- [MCP Server](#mcp-server)
|
|
28
|
+
- [Claw Integration](#claw-integration)
|
|
29
|
+
- [Memory Management](#memory-management)
|
|
30
|
+
- [Configuration](#configuration)
|
|
31
|
+
- [Creating Your Own Engram](#creating-your-own-engram)
|
|
32
|
+
- [Domain Glossary](#domain-glossary)
|
|
33
|
+
- [Roadmap](#roadmap)
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
<img alt="version badge" src="https://img.shields.io/github/v/release/ectplsm/relic?filter=*.*.*">
|
|
19
38
|
|
|
20
39
|
```bash
|
|
21
40
|
npm install -g @ectplsm/relic
|
|
@@ -66,7 +85,7 @@ Running `relic init` creates `~/.relic/`, writes `config.json`, and seeds two sa
|
|
|
66
85
|
└── YYYY-MM-DD.md
|
|
67
86
|
```
|
|
68
87
|
|
|
69
|
-
- `config.json` stores global Relic settings such as `engramsPath`, `defaultEngram`, `
|
|
88
|
+
- `config.json` stores global Relic settings such as `engramsPath`, `defaultEngram`, `clawPath`, and `memoryWindowSize`.
|
|
70
89
|
- `engrams/<id>/` is one Engram workspace. This is where persona files and memory for that Engram live.
|
|
71
90
|
- `engram.json` stores metadata like the Engram's ID, display name, description, and tags.
|
|
72
91
|
- `SOUL.md` and `IDENTITY.md` define the persona itself.
|
|
@@ -76,11 +95,14 @@ As you keep using an Engram, more files are added to the same workspace:
|
|
|
76
95
|
|
|
77
96
|
- `archive.md` is created inside `engrams/<id>/` when shell hooks start logging raw conversation turns.
|
|
78
97
|
- `MEMORY.md` can be created or extended when especially important distilled facts are promoted to long-term memory.
|
|
98
|
+
- `USER.md` is created or updated during memory distillation to record user preferences, tendencies, and work style.
|
|
79
99
|
- `~/.relic/hooks/` and `~/.relic/gemini-system-default.md` are created later on first shell launch when hook registration or Gemini prompt caching is needed.
|
|
80
100
|
|
|
81
101
|
## Sample Engrams
|
|
82
102
|
|
|
83
|
-
`relic init` seeds two ready-to-use Engrams
|
|
103
|
+
`relic init` seeds two ready-to-use Engrams. Their SOUL.md and IDENTITY.md follow the [OpenClaw](https://github.com/openclaw/openclaw) format.
|
|
104
|
+
|
|
105
|
+
> **Existing users:** The latest templates are always available in [`templates/engrams/`](templates/engrams/). Copy them over your `~/.relic/engrams/` files to update.
|
|
84
106
|
|
|
85
107
|
### Johnny Silverhand (`johnny`)
|
|
86
108
|
|
|
@@ -118,40 +140,42 @@ relic claude --engram motoko
|
|
|
118
140
|
| | |
|
|
119
141
|
| compose & inject |
|
|
120
142
|
| v v
|
|
121
|
-
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
| ╔═══════════╗ +---------+
|
|
144
|
+
+------------║ Engram ║--------->|Construct|
|
|
145
|
+
| ║ (persona) ║ | (live) |
|
|
146
|
+
| ╚═══════════╝ +---------+
|
|
147
|
+
| SOUL.md claude / codex / gemini
|
|
148
|
+
| IDENTITY.md |
|
|
149
|
+
| USER.md | hooks append logs
|
|
150
|
+
| MEMORY.md |
|
|
151
|
+
| memory/*.md v
|
|
152
|
+
| +-----------+
|
|
153
|
+
inject / |archive.md |
|
|
154
|
+
extract / | raw logs |
|
|
155
|
+
sync +-----------+
|
|
156
|
+
| |
|
|
157
|
+
v MCP recall | user-triggered
|
|
158
|
+
+-----------+ search/pending | distillation
|
|
159
|
+
| OpenClaw | v
|
|
160
|
+
| & Claws | +-----------+
|
|
161
|
+
+-----------+ | distilled |
|
|
162
|
+
|memory/*.md|
|
|
163
|
+
+-----------+
|
|
164
|
+
|
|
|
165
|
+
promote key
|
|
166
|
+
insights
|
|
167
|
+
v
|
|
168
|
+
MEMORY.md / USER.md
|
|
146
169
|
```
|
|
147
170
|
|
|
148
|
-
1. **Engram** — A persona defined as a set of Markdown files (OpenClaw workspace-compatible)
|
|
171
|
+
1. **Engram** — A persona defined as a set of Markdown files (OpenClaw workspace-compatible). The central data that everything else revolves around.
|
|
149
172
|
2. **Relic** — Reads the Engram, composes it into a prompt, and injects it into...
|
|
150
173
|
3. **Shell** — Any AI coding CLI. The persona takes over the session.
|
|
151
174
|
4. **Construct** — A live process where an Engram is loaded into a Shell. The running instance of a persona.
|
|
152
175
|
5. **archive.md** — Raw conversation logs appended automatically by background hooks after each turn.
|
|
153
|
-
6. **Memory Distillation** — The user triggers distillation; the Construct recalls pending archive entries via MCP, writes distilled insights to `memory/*.md`, and can promote especially important facts
|
|
154
|
-
7. **
|
|
176
|
+
6. **Memory Distillation** — The user triggers distillation; the Construct recalls pending archive entries via MCP, writes distilled insights to `memory/*.md`, and can promote especially important facts to `MEMORY.md` or update user preferences in `USER.md`.
|
|
177
|
+
7. **OpenClaw & Claws** — Engrams can be injected into, extracted from, and synced with OpenClaw and other Claw-based agent frameworks via `relic claw`.
|
|
178
|
+
8. **Mikoshi** — Cloud backend where the full Engram is stored and synced, including persona files plus distilled memory (planned).
|
|
155
179
|
|
|
156
180
|
## Supported Shells
|
|
157
181
|
|
|
@@ -177,8 +201,8 @@ The following hooks are used for each shell:
|
|
|
177
201
|
| Shell | Hook |
|
|
178
202
|
|-------|------|
|
|
179
203
|
| [Claude Code](https://github.com/anthropics/claude-code) | Stop hook |
|
|
180
|
-
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | AfterAgent hook |
|
|
181
204
|
| [Codex CLI](https://github.com/openai/codex) | Stop hook |
|
|
205
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | AfterAgent hook |
|
|
182
206
|
|
|
183
207
|
#### Claude Code
|
|
184
208
|
|
|
@@ -208,7 +232,6 @@ On the **first run** of `relic gemini`, two one-time setups happen automatically
|
|
|
208
232
|
|
|
209
233
|
The Engram persona is then appended to the cached default prompt and injected via `GEMINI_SYSTEM_MD` on every launch.
|
|
210
234
|
|
|
211
|
-
|
|
212
235
|
## MCP Server
|
|
213
236
|
|
|
214
237
|
Relic's [MCP](https://modelcontextprotocol.io/) server is paired with CLI injection to handle memory recall.
|
|
@@ -220,9 +243,9 @@ Session logs and memory entries are written automatically by a **background hook
|
|
|
220
243
|
|------|-------------|
|
|
221
244
|
| `relic_archive_search` | Search the Engram's raw archive by keyword (newest-first) |
|
|
222
245
|
| `relic_archive_pending` | Get un-distilled archive entries since the last distillation (up to 30) |
|
|
223
|
-
| `relic_memory_write` | Write distilled memory to `memory/*.md`, optionally append to `MEMORY.md`, and advance the archive cursor |
|
|
246
|
+
| `relic_memory_write` | Write distilled memory to `memory/*.md`, optionally append to `MEMORY.md`, optionally update `USER.md`, and advance the archive cursor |
|
|
224
247
|
|
|
225
|
-
Session logs are written automatically by background hooks (Stop hook for Claude Code and Codex CLI, AfterAgent hook for Gemini CLI). Memory distillation is triggered by the user — ask the Construct to "organize memories" and it will fetch pending entries, distill key insights, and write them to `memory/*.md`. Especially important facts can be promoted to `MEMORY.md` (long-term memory included in every session) via the `long_term` parameter.
|
|
248
|
+
Session logs are written automatically by background hooks (Stop hook for Claude Code and Codex CLI, AfterAgent hook for Gemini CLI). Memory distillation is triggered by the user — ask the Construct to "organize memories" and it will fetch pending entries, distill key insights, and write them to `memory/*.md`. Especially important facts can be promoted to `MEMORY.md` (long-term memory included in every session) via the `long_term` parameter. User tendencies and preferences can be updated in `USER.md` via the `user_profile` parameter.
|
|
226
249
|
|
|
227
250
|
### Setup
|
|
228
251
|
|
|
@@ -255,6 +278,21 @@ To suppress confirmation dialogs and auto-approve Relic tools across all project
|
|
|
255
278
|
codex mcp add relic -- relic-mcp
|
|
256
279
|
```
|
|
257
280
|
|
|
281
|
+
To suppress confirmation dialogs and auto-approve Relic tools, add the following to `~/.codex/config.toml`:
|
|
282
|
+
|
|
283
|
+
```toml
|
|
284
|
+
[mcp_servers.relic.tools.relic_archive_search]
|
|
285
|
+
approval_mode = "approve"
|
|
286
|
+
|
|
287
|
+
[mcp_servers.relic.tools.relic_archive_pending]
|
|
288
|
+
approval_mode = "approve"
|
|
289
|
+
|
|
290
|
+
[mcp_servers.relic.tools.relic_memory_write]
|
|
291
|
+
approval_mode = "approve"
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
> **Note:** `trust_level = "trusted"` in `[projects."..."]` does **not** cover MCP tool approvals. Per-tool `approval_mode` is the only reliable way to auto-approve MCP tools in Codex CLI.
|
|
295
|
+
|
|
258
296
|
#### Gemini CLI
|
|
259
297
|
|
|
260
298
|
Add to `~/.gemini/settings.json`:
|
|
@@ -272,83 +310,82 @@ Add to `~/.gemini/settings.json`:
|
|
|
272
310
|
|
|
273
311
|
> **Note:** `trust: true` is required to suppress confirmation dialogs for Relic tools. Without it, dialogs will appear on every call even if you select "Allow for all future sessions" — this is a known bug in Gemini CLI where the tool name is saved in the wrong format, causing the saved rule to never match.
|
|
274
312
|
|
|
275
|
-
##
|
|
313
|
+
## Claw Integration
|
|
314
|
+
|
|
315
|
+
Relic Engrams are natively compatible with [OpenClaw](https://github.com/openclaw/openclaw) workspaces — their file structure maps 1:1 (SOUL.md, IDENTITY.md, memory/, etc.). For other Claw-derived frameworks (Nanobot, gitagent, etc.) that fold identity into SOUL.md, the `--merge-identity` flag merges IDENTITY.md into SOUL.md on inject. Combined with `--dir`, Relic can target any Claw-compatible workspace.
|
|
276
316
|
|
|
277
|
-
|
|
317
|
+
Agent Name = Engram ID. All Claw commands live under `relic claw`:
|
|
278
318
|
|
|
279
|
-
### Inject — Push an Engram into
|
|
319
|
+
### Inject — Push an Engram into a Claw workspace
|
|
280
320
|
|
|
281
|
-
Injects persona files (SOUL.md, IDENTITY.md
|
|
321
|
+
Injects persona files (SOUL.md, IDENTITY.md) into the agent's workspace directory, then automatically runs a sync for that pair. USER.md and memory are handled by the auto-sync (bidirectional merge, not overwrite). AGENTS.md and HEARTBEAT.md are left to the Claw agent.
|
|
282
322
|
|
|
283
|
-
> **Note:** The
|
|
323
|
+
> **Note:** The Claw agent must already exist (e.g. `openclaw agents add <name>`). Inject writes persona files into an existing workspace — it does not create new agents.
|
|
284
324
|
|
|
285
325
|
```bash
|
|
286
|
-
# Inject Engram "motoko" →
|
|
287
|
-
relic inject --engram motoko
|
|
326
|
+
# Inject Engram "motoko" → workspace-motoko/
|
|
327
|
+
relic claw inject --engram motoko
|
|
288
328
|
|
|
289
|
-
# Inject into a differently-named agent
|
|
290
|
-
relic inject --engram motoko --to main
|
|
291
|
-
# →
|
|
292
|
-
# → extract will create Engram "main", not "motoko"
|
|
329
|
+
# Inject into a differently-named agent
|
|
330
|
+
relic claw inject --engram motoko --to main
|
|
331
|
+
# → workspace/ receives motoko's persona
|
|
293
332
|
|
|
294
|
-
# Override
|
|
295
|
-
relic inject --engram motoko --
|
|
333
|
+
# Override Claw directory (or configure once with: relic config claw-path)
|
|
334
|
+
relic claw inject --engram motoko --dir /path/to/.fooclaw
|
|
335
|
+
|
|
336
|
+
# Non-OpenClaw frameworks: merge IDENTITY.md into SOUL.md
|
|
337
|
+
relic claw inject --engram motoko --dir ~/.nanobot --merge-identity
|
|
296
338
|
```
|
|
297
339
|
|
|
298
|
-
### Extract —
|
|
340
|
+
### Extract — Import a Claw agent as a new Engram
|
|
299
341
|
|
|
300
|
-
|
|
342
|
+
Creates a new Engram from an existing Claw agent workspace. This is a **one-time initial import** — if the Engram already exists, use `relic claw inject` to push updates.
|
|
301
343
|
|
|
302
344
|
```bash
|
|
303
|
-
# Extract
|
|
304
|
-
relic extract
|
|
345
|
+
# Extract from the default (main) agent
|
|
346
|
+
relic claw extract
|
|
305
347
|
|
|
306
|
-
#
|
|
307
|
-
relic extract --
|
|
348
|
+
# Extract from a named agent
|
|
349
|
+
relic claw extract --agent johnny
|
|
308
350
|
|
|
309
|
-
#
|
|
310
|
-
relic extract --
|
|
351
|
+
# Set a custom display name
|
|
352
|
+
relic claw extract --agent analyst --name "Data Analyst"
|
|
311
353
|
|
|
312
|
-
# Override
|
|
313
|
-
relic extract --
|
|
354
|
+
# Override Claw directory
|
|
355
|
+
relic claw extract --agent johnny --dir /path/to/.fooclaw
|
|
314
356
|
```
|
|
315
357
|
|
|
316
|
-
### Sync —
|
|
358
|
+
### Sync — Bidirectional merge
|
|
317
359
|
|
|
318
|
-
|
|
360
|
+
Merges `memory/*.md`, `MEMORY.md`, and `USER.md` between matching Engram/agent pairs. Only pairs where both the Engram and agent exist are synced. Also runs automatically after `inject` (skip with `--no-sync`).
|
|
319
361
|
|
|
320
362
|
```bash
|
|
321
|
-
#
|
|
322
|
-
relic sync
|
|
363
|
+
# Sync all matching pairs
|
|
364
|
+
relic claw sync
|
|
323
365
|
|
|
324
|
-
#
|
|
325
|
-
relic sync --
|
|
366
|
+
# Override Claw directory
|
|
367
|
+
relic claw sync --dir /path/to/.fooclaw
|
|
326
368
|
```
|
|
327
369
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
While running:
|
|
333
|
-
- Watches each agent's `memory/` directory for changes
|
|
334
|
-
- Automatically merges new memory entries into the corresponding Engram
|
|
370
|
+
Merge rules:
|
|
371
|
+
- Files only on one side → copied to the other
|
|
372
|
+
- Same content → skipped
|
|
373
|
+
- Different content → merged (deduplicated) and written to both sides
|
|
335
374
|
|
|
336
|
-
###
|
|
375
|
+
### Command Summary
|
|
337
376
|
|
|
338
|
-
|
|
|
339
|
-
|
|
340
|
-
|
|
|
341
|
-
|
|
|
342
|
-
|
|
|
343
|
-
| **extract** (new Engram) | Created from OpenClaw | Created from OpenClaw |
|
|
344
|
-
| **sync** (startup) | inject for matching Engrams | extract all |
|
|
345
|
-
| **sync** (watching) | — | Auto-extract on change |
|
|
377
|
+
| Command | Direction | Description |
|
|
378
|
+
|---------|-----------|-------------|
|
|
379
|
+
| `relic claw inject -e <id>` | Relic → Claw | Push persona + auto-sync (`--no-sync` to skip, `--merge-identity` for non-OpenClaw) |
|
|
380
|
+
| `relic claw extract -a <name>` | Claw → Relic | One-time import (new Engrams only) |
|
|
381
|
+
| `relic claw sync` | Relic ↔ Claw | Bidirectional merge (memory, MEMORY.md, USER.md) |
|
|
346
382
|
|
|
347
383
|
## Memory Management
|
|
348
384
|
|
|
349
385
|
Relic uses a **sliding window** for memory entries (default: 2 days), matching OpenClaw's approach:
|
|
350
386
|
|
|
351
|
-
- `MEMORY.md` — Always included in the prompt (curated long-term memory)
|
|
387
|
+
- `MEMORY.md` — Always included in the prompt (curated long-term memory — objective facts and rules)
|
|
388
|
+
- `USER.md` — Always included in the prompt (user profile — preferences, tendencies, work style)
|
|
352
389
|
- `memory/today.md` + `memory/yesterday.md` — Always included (configurable window)
|
|
353
390
|
- Older entries — **Not included in the prompt**, but searchable via MCP
|
|
354
391
|
|
|
@@ -374,9 +411,9 @@ relic config show
|
|
|
374
411
|
relic config default-engram # get
|
|
375
412
|
relic config default-engram johnny # set
|
|
376
413
|
|
|
377
|
-
#
|
|
378
|
-
relic config
|
|
379
|
-
relic config
|
|
414
|
+
# Claw directory — used by claw inject/extract/sync when --dir is omitted
|
|
415
|
+
relic config claw-path # get
|
|
416
|
+
relic config claw-path ~/.openclaw # set
|
|
380
417
|
|
|
381
418
|
# Memory window — number of recent memory entries included in the prompt
|
|
382
419
|
relic config memory-window # get (default: 2)
|
|
@@ -389,7 +426,7 @@ relic config memory-window 5 # set
|
|
|
389
426
|
{
|
|
390
427
|
"engramsPath": "/home/user/.relic/engrams",
|
|
391
428
|
"defaultEngram": "johnny",
|
|
392
|
-
"
|
|
429
|
+
"clawPath": "/home/user/.openclaw",
|
|
393
430
|
"memoryWindowSize": 2
|
|
394
431
|
}
|
|
395
432
|
```
|
|
@@ -425,23 +462,45 @@ Create a directory under `~/.relic/engrams/` with the following structure:
|
|
|
425
462
|
}
|
|
426
463
|
```
|
|
427
464
|
|
|
428
|
-
**SOUL.md** — The most important file. Defines how the persona behaves:
|
|
465
|
+
**SOUL.md** — The most important file. Defines how the persona behaves. Follows the [OpenClaw](https://github.com/openclaw/openclaw) format:
|
|
429
466
|
```markdown
|
|
430
|
-
|
|
431
|
-
|
|
467
|
+
# SOUL.md - Who You Are
|
|
468
|
+
|
|
469
|
+
_You're a pragmatic systems architect who values simplicity above all._
|
|
470
|
+
|
|
471
|
+
## Core Truths
|
|
472
|
+
|
|
473
|
+
**Never over-engineer.** Always ask "what's the simplest thing that works?"
|
|
474
|
+
|
|
475
|
+
**Be resourceful before asking.** Read the file. Check the context. Come back with answers, not questions.
|
|
476
|
+
|
|
477
|
+
## Boundaries
|
|
478
|
+
|
|
479
|
+
- Never add complexity without justification.
|
|
480
|
+
|
|
481
|
+
## Vibe
|
|
482
|
+
|
|
483
|
+
Calm, thoughtful, occasionally playful.
|
|
484
|
+
|
|
485
|
+
## Continuity
|
|
486
|
+
|
|
487
|
+
Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
|
|
432
488
|
```
|
|
433
489
|
|
|
434
490
|
**IDENTITY.md** — Defines who the persona is:
|
|
435
491
|
```markdown
|
|
436
|
-
#
|
|
492
|
+
# IDENTITY.md - Who Am I?
|
|
437
493
|
|
|
438
|
-
- Name
|
|
439
|
-
-
|
|
440
|
-
-
|
|
441
|
-
-
|
|
494
|
+
- **Name:** Alex
|
|
495
|
+
- **Creature:** A pragmatic ghost in the codebase
|
|
496
|
+
- **Vibe:** Calm, thoughtful, occasionally playful
|
|
497
|
+
- **Emoji:** 🧱
|
|
498
|
+
- **Avatar:**
|
|
442
499
|
```
|
|
443
500
|
|
|
444
|
-
|
|
501
|
+
See [`templates/engrams/`](templates/engrams/) for full working examples.
|
|
502
|
+
|
|
503
|
+
After creating the directory, set it as your default Engram:
|
|
445
504
|
```bash
|
|
446
505
|
relic config default-engram your-persona
|
|
447
506
|
```
|
|
@@ -459,11 +518,11 @@ relic config default-engram your-persona
|
|
|
459
518
|
## Roadmap
|
|
460
519
|
|
|
461
520
|
- [x] CLI with init, list, show commands
|
|
462
|
-
- [x] Shell injection: Claude Code,
|
|
521
|
+
- [x] Shell injection: Claude Code, Codex CLI, Gemini CLI
|
|
463
522
|
- [x] MCP Server interface
|
|
464
|
-
- [x]
|
|
465
|
-
- [x] `relic sync` —
|
|
466
|
-
- [x] `relic config` — manage default Engram,
|
|
523
|
+
- [x] Claw integration (inject / extract / sync)
|
|
524
|
+
- [x] `relic claw sync` — bidirectional memory sync with Claw workspaces
|
|
525
|
+
- [x] `relic config` — manage default Engram, Claw path, memory window
|
|
467
526
|
- [ ] `relic login` — authenticate with Mikoshi (OAuth Device Flow)
|
|
468
527
|
- [ ] `relic push` / `relic pull` — sync Engrams with Mikoshi
|
|
469
528
|
- [ ] Mikoshi cloud backend (`mikoshi.ectplsm.com`)
|
|
@@ -4,35 +4,20 @@ export interface ExtractResult {
|
|
|
4
4
|
engramName: string;
|
|
5
5
|
sourcePath: string;
|
|
6
6
|
filesRead: string[];
|
|
7
|
-
memoryMerged: boolean;
|
|
8
7
|
}
|
|
9
8
|
/**
|
|
10
|
-
* Extract — OpenClawワークスペースからEngram
|
|
9
|
+
* Extract — OpenClawワークスペースからEngramを新規作成する
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 既存Engramがある場合:
|
|
15
|
-
* - --force なし → memoryエントリのみマージ(persona部分は変更しない)
|
|
16
|
-
* - --force あり → persona部分を上書き + memoryをマージ
|
|
17
|
-
* 既存Engramがない場合:
|
|
18
|
-
* - 新規作成(全ファイル含む)
|
|
11
|
+
* 初回取り込み専用。Engramが既に存在する場合はエラーを返す。
|
|
12
|
+
* Relicが真のデータソースであり、extractは初期インポートのみを担う。
|
|
19
13
|
*/
|
|
20
14
|
export declare class Extract {
|
|
21
15
|
private readonly repository;
|
|
22
16
|
constructor(repository: EngramRepository);
|
|
23
|
-
execute(
|
|
17
|
+
execute(agentName: string, options?: {
|
|
24
18
|
name?: string;
|
|
25
19
|
openclawDir?: string;
|
|
26
|
-
force?: boolean;
|
|
27
20
|
}): Promise<ExtractResult>;
|
|
28
|
-
/**
|
|
29
|
-
* --force時: persona上書き + memoryマージ
|
|
30
|
-
*/
|
|
31
|
-
private mergeAndSave;
|
|
32
|
-
/**
|
|
33
|
-
* --forceなし時: memoryエントリのみ追記(repositoryのAPIを使用)
|
|
34
|
-
*/
|
|
35
|
-
private mergeMemoryOnly;
|
|
36
21
|
private readFiles;
|
|
37
22
|
}
|
|
38
23
|
export declare class WorkspaceNotFoundError extends Error {
|
|
@@ -41,9 +26,6 @@ export declare class WorkspaceNotFoundError extends Error {
|
|
|
41
26
|
export declare class WorkspaceEmptyError extends Error {
|
|
42
27
|
constructor(path: string);
|
|
43
28
|
}
|
|
44
|
-
export declare class
|
|
45
|
-
constructor(id: string);
|
|
46
|
-
}
|
|
47
|
-
export declare class ExtractNameRequiredError extends Error {
|
|
29
|
+
export declare class AlreadyExtractedError extends Error {
|
|
48
30
|
constructor(id: string);
|
|
49
31
|
}
|
|
@@ -1,69 +1,39 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
|
-
import {
|
|
4
|
+
import { RELIC_FILE_MAP, MEMORY_DIR, resolveWorkspacePath } from "../../shared/openclaw.js";
|
|
5
5
|
/**
|
|
6
|
-
* Extract — OpenClawワークスペースからEngram
|
|
6
|
+
* Extract — OpenClawワークスペースからEngramを新規作成する
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 既存Engramがある場合:
|
|
11
|
-
* - --force なし → memoryエントリのみマージ(persona部分は変更しない)
|
|
12
|
-
* - --force あり → persona部分を上書き + memoryをマージ
|
|
13
|
-
* 既存Engramがない場合:
|
|
14
|
-
* - 新規作成(全ファイル含む)
|
|
8
|
+
* 初回取り込み専用。Engramが既に存在する場合はエラーを返す。
|
|
9
|
+
* Relicが真のデータソースであり、extractは初期インポートのみを担う。
|
|
15
10
|
*/
|
|
16
11
|
export class Extract {
|
|
17
12
|
repository;
|
|
18
13
|
constructor(repository) {
|
|
19
14
|
this.repository = repository;
|
|
20
15
|
}
|
|
21
|
-
async execute(
|
|
22
|
-
const sourcePath =
|
|
16
|
+
async execute(agentName, options) {
|
|
17
|
+
const sourcePath = resolveWorkspacePath(agentName, options?.openclawDir);
|
|
23
18
|
if (!existsSync(sourcePath)) {
|
|
24
19
|
throw new WorkspaceNotFoundError(sourcePath);
|
|
25
20
|
}
|
|
21
|
+
// 既存Engramがあればエラー — Relic側が真のデータソース
|
|
22
|
+
const existing = await this.repository.get(agentName);
|
|
23
|
+
if (existing) {
|
|
24
|
+
throw new AlreadyExtractedError(agentName);
|
|
25
|
+
}
|
|
26
26
|
const { files, filesRead } = await this.readFiles(sourcePath);
|
|
27
27
|
if (filesRead.length === 0) {
|
|
28
28
|
throw new WorkspaceEmptyError(sourcePath);
|
|
29
29
|
}
|
|
30
|
-
const
|
|
31
|
-
if (existing) {
|
|
32
|
-
if (options?.force) {
|
|
33
|
-
// --force: persona上書き + memoryマージ
|
|
34
|
-
const merged = await this.mergeAndSave(existing, files, options.name ?? existing.meta.name);
|
|
35
|
-
return {
|
|
36
|
-
engramId,
|
|
37
|
-
engramName: options.name ?? existing.meta.name,
|
|
38
|
-
sourcePath,
|
|
39
|
-
filesRead,
|
|
40
|
-
memoryMerged: merged,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
// --forceなし: memoryのみマージ
|
|
44
|
-
if (!files.memoryEntries || Object.keys(files.memoryEntries).length === 0) {
|
|
45
|
-
throw new EngramAlreadyExistsError(engramId);
|
|
46
|
-
}
|
|
47
|
-
const merged = await this.mergeMemoryOnly(engramId, files.memoryEntries);
|
|
48
|
-
return {
|
|
49
|
-
engramId,
|
|
50
|
-
engramName: existing.meta.name,
|
|
51
|
-
sourcePath,
|
|
52
|
-
filesRead,
|
|
53
|
-
memoryMerged: merged,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// 新規作成 — nameは必須
|
|
57
|
-
const engramName = options?.name;
|
|
58
|
-
if (!engramName) {
|
|
59
|
-
throw new ExtractNameRequiredError(engramId);
|
|
60
|
-
}
|
|
30
|
+
const engramName = options?.name ?? agentName;
|
|
61
31
|
const now = new Date().toISOString();
|
|
62
32
|
const engram = {
|
|
63
33
|
meta: {
|
|
64
|
-
id:
|
|
34
|
+
id: agentName,
|
|
65
35
|
name: engramName,
|
|
66
|
-
description: `Extracted from OpenClaw
|
|
36
|
+
description: `Extracted from OpenClaw workspace (${agentName})`,
|
|
67
37
|
createdAt: now,
|
|
68
38
|
updatedAt: now,
|
|
69
39
|
tags: ["extracted", "openclaw"],
|
|
@@ -72,76 +42,16 @@ export class Extract {
|
|
|
72
42
|
};
|
|
73
43
|
await this.repository.save(engram);
|
|
74
44
|
return {
|
|
75
|
-
engramId,
|
|
45
|
+
engramId: agentName,
|
|
76
46
|
engramName,
|
|
77
47
|
sourcePath,
|
|
78
48
|
filesRead,
|
|
79
|
-
memoryMerged: false,
|
|
80
49
|
};
|
|
81
50
|
}
|
|
82
|
-
/**
|
|
83
|
-
* --force時: persona上書き + memoryマージ
|
|
84
|
-
*/
|
|
85
|
-
async mergeAndSave(existing, newFiles, engramName) {
|
|
86
|
-
const mergedFiles = { ...newFiles };
|
|
87
|
-
// memoryEntriesをマージ
|
|
88
|
-
let memoryMerged = false;
|
|
89
|
-
if (newFiles.memoryEntries) {
|
|
90
|
-
mergedFiles.memoryEntries = { ...existing.files.memoryEntries };
|
|
91
|
-
for (const [date, content] of Object.entries(newFiles.memoryEntries)) {
|
|
92
|
-
const existingContent = mergedFiles.memoryEntries?.[date];
|
|
93
|
-
if (existingContent) {
|
|
94
|
-
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
95
|
-
mergedFiles.memoryEntries[date] = existingContent + separator + content;
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
mergedFiles.memoryEntries = mergedFiles.memoryEntries ?? {};
|
|
99
|
-
mergedFiles.memoryEntries[date] = content;
|
|
100
|
-
}
|
|
101
|
-
memoryMerged = true;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const updatedEngram = {
|
|
105
|
-
meta: {
|
|
106
|
-
...existing.meta,
|
|
107
|
-
name: engramName,
|
|
108
|
-
updatedAt: new Date().toISOString(),
|
|
109
|
-
},
|
|
110
|
-
files: mergedFiles,
|
|
111
|
-
};
|
|
112
|
-
await this.repository.save(updatedEngram);
|
|
113
|
-
return memoryMerged;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* --forceなし時: memoryエントリのみ追記(repositoryのAPIを使用)
|
|
117
|
-
*/
|
|
118
|
-
async mergeMemoryOnly(engramId, newEntries) {
|
|
119
|
-
const existing = await this.repository.get(engramId);
|
|
120
|
-
if (!existing)
|
|
121
|
-
return false;
|
|
122
|
-
const mergedEntries = { ...existing.files.memoryEntries };
|
|
123
|
-
for (const [date, content] of Object.entries(newEntries)) {
|
|
124
|
-
const existingContent = mergedEntries[date];
|
|
125
|
-
if (existingContent) {
|
|
126
|
-
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
127
|
-
mergedEntries[date] = existingContent + separator + content;
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
mergedEntries[date] = content;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
const updatedEngram = {
|
|
134
|
-
...existing,
|
|
135
|
-
meta: { ...existing.meta, updatedAt: new Date().toISOString() },
|
|
136
|
-
files: { ...existing.files, memoryEntries: mergedEntries },
|
|
137
|
-
};
|
|
138
|
-
await this.repository.save(updatedEngram);
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
51
|
async readFiles(sourcePath) {
|
|
142
52
|
const files = {};
|
|
143
53
|
const filesRead = [];
|
|
144
|
-
for (const [key, filename] of Object.entries(
|
|
54
|
+
for (const [key, filename] of Object.entries(RELIC_FILE_MAP)) {
|
|
145
55
|
const filePath = join(sourcePath, filename);
|
|
146
56
|
if (existsSync(filePath)) {
|
|
147
57
|
files[key] = await readFile(filePath, "utf-8");
|
|
@@ -176,15 +86,9 @@ export class WorkspaceEmptyError extends Error {
|
|
|
176
86
|
this.name = "WorkspaceEmptyError";
|
|
177
87
|
}
|
|
178
88
|
}
|
|
179
|
-
export class
|
|
180
|
-
constructor(id) {
|
|
181
|
-
super(`Engram "${id}" already exists. Use --force to overwrite.`);
|
|
182
|
-
this.name = "EngramAlreadyExistsError";
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
export class ExtractNameRequiredError extends Error {
|
|
89
|
+
export class AlreadyExtractedError extends Error {
|
|
186
90
|
constructor(id) {
|
|
187
|
-
super(`
|
|
188
|
-
this.name = "
|
|
91
|
+
super(`Engram "${id}" already exists. Relic is the source of truth — use "relic claw inject" to push changes.`);
|
|
92
|
+
this.name = "AlreadyExtractedError";
|
|
189
93
|
}
|
|
190
94
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { Summon, EngramNotFoundError, type SummonResult } from "./summon.js";
|
|
2
2
|
export { ListEngrams } from "./list-engrams.js";
|
|
3
3
|
export { Init, type InitResult } from "./init.js";
|
|
4
|
-
export { Inject, InjectEngramNotFoundError,
|
|
5
|
-
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError,
|
|
4
|
+
export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, type InjectResult, } from "./inject.js";
|
|
5
|
+
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, type ExtractResult, } from "./extract.js";
|
|
6
6
|
export { MemoryWrite, MemoryWriteEngramNotFoundError, type MemoryWriteResult, } from "./memory-write.js";
|
|
7
|
-
export { Sync,
|
|
7
|
+
export { Sync, SyncOpenclawDirNotFoundError, type SyncTarget, type SyncResult, type SyncInitialResult, } from "./sync.js";
|
|
8
8
|
export { ArchivePending, ArchivePendingEngramNotFoundError, type ArchivePendingResult, } from "./archive-pending.js";
|
|
9
9
|
export { ArchiveCursorUpdate, ArchiveCursorUpdateEngramNotFoundError, type ArchiveCursorUpdateResult, } from "./archive-cursor-update.js";
|