@ectplsm/relic 0.1.2 → 0.1.4
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 +261 -103
- package/dist/adapters/shells/claude-hook.d.ts +15 -0
- package/dist/adapters/shells/claude-hook.js +163 -0
- package/dist/adapters/shells/claude-shell.d.ts +5 -2
- package/dist/adapters/shells/claude-shell.js +19 -3
- package/dist/adapters/shells/codex-hook.d.ts +15 -0
- package/dist/adapters/shells/codex-hook.js +144 -0
- package/dist/adapters/shells/codex-shell.d.ts +7 -4
- package/dist/adapters/shells/codex-shell.js +24 -7
- package/dist/adapters/shells/copilot-shell.d.ts +2 -2
- package/dist/adapters/shells/copilot-shell.js +3 -3
- package/dist/adapters/shells/gemini-hook.d.ts +15 -0
- package/dist/adapters/shells/gemini-hook.js +111 -0
- package/dist/adapters/shells/gemini-shell.d.ts +6 -4
- package/dist/adapters/shells/gemini-shell.js +105 -14
- package/dist/adapters/shells/index.d.ts +0 -1
- package/dist/adapters/shells/index.js +0 -1
- package/dist/adapters/shells/spawn-shell.d.ts +1 -1
- package/dist/adapters/shells/spawn-shell.js +10 -3
- package/dist/adapters/shells/trust-registrar.d.ts +19 -0
- package/dist/adapters/shells/trust-registrar.js +141 -0
- package/dist/core/ports/shell-launcher.d.ts +17 -2
- package/dist/core/usecases/archive-cursor-update.d.ts +21 -0
- package/dist/core/usecases/archive-cursor-update.js +44 -0
- package/dist/core/usecases/archive-pending.d.ts +25 -0
- package/dist/core/usecases/archive-pending.js +61 -0
- package/dist/core/usecases/archive-search.d.ts +20 -0
- package/dist/core/usecases/archive-search.js +46 -0
- package/dist/core/usecases/inbox-search.d.ts +20 -0
- package/dist/core/usecases/inbox-search.js +46 -0
- package/dist/core/usecases/inbox-write.d.ts +27 -0
- package/dist/core/usecases/inbox-write.js +72 -0
- package/dist/core/usecases/index.d.ts +2 -1
- package/dist/core/usecases/index.js +2 -1
- package/dist/core/usecases/summon.d.ts +6 -1
- package/dist/core/usecases/summon.js +8 -2
- package/dist/interfaces/cli/commands/config.d.ts +2 -0
- package/dist/interfaces/cli/commands/config.js +83 -0
- package/dist/interfaces/cli/commands/extract.js +3 -2
- package/dist/interfaces/cli/commands/init.js +47 -0
- package/dist/interfaces/cli/commands/inject.js +3 -2
- package/dist/interfaces/cli/commands/shell.js +13 -11
- package/dist/interfaces/cli/index.js +2 -0
- package/dist/interfaces/mcp/index.js +68 -305
- package/dist/shared/config.d.ts +31 -0
- package/dist/shared/config.js +86 -16
- package/dist/shared/engram-composer.d.ts +16 -3
- package/dist/shared/engram-composer.js +50 -5
- package/dist/shared/memory-inbox.d.ts +78 -0
- package/dist/shared/memory-inbox.js +168 -0
- package/dist/shared/session-recorder.d.ts +47 -0
- package/dist/shared/session-recorder.js +112 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -11,123 +11,270 @@
|
|
|
11
11
|
/_/ |_/_____/_____/___/\____/
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
**Inject AI
|
|
14
|
+
**Inject a unified AI persona with persistent memory into any coding CLI.**
|
|
15
15
|
|
|
16
|
-
Relic manages AI
|
|
16
|
+
Relic manages AI **Engrams** (memory + personality) and injects them into coding assistants like Claude Code, Codex CLI, Gemini CLI. One persona, any shell.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
|
|
21
|
+
npm install -g @ectplsm/relic
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Initialize — creates config and sample Engrams
|
|
20
28
|
relic init
|
|
29
|
+
# → Prompts: "Set a default Engram? (press Enter for "johnny", or enter ID, or "n" to skip):"
|
|
21
30
|
|
|
22
|
-
#
|
|
23
|
-
relic
|
|
31
|
+
# List available Engrams
|
|
32
|
+
relic list
|
|
33
|
+
|
|
34
|
+
# Preview an Engram's composed prompt
|
|
35
|
+
relic show motoko
|
|
24
36
|
|
|
25
|
-
# Launch
|
|
26
|
-
relic
|
|
37
|
+
# Launch a Shell (uses default Engram if --engram is omitted)
|
|
38
|
+
relic claude
|
|
39
|
+
relic codex
|
|
40
|
+
relic gemini
|
|
41
|
+
|
|
42
|
+
# Or specify explicitly
|
|
43
|
+
relic claude --engram motoko
|
|
44
|
+
relic codex --engram johnny
|
|
27
45
|
```
|
|
28
46
|
|
|
29
|
-
##
|
|
47
|
+
## What `relic init` Creates
|
|
48
|
+
|
|
49
|
+
Running `relic init` creates `~/.relic/`, writes `config.json`, and seeds two sample Engrams under `~/.relic/engrams/`.
|
|
30
50
|
|
|
31
51
|
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
~/.relic/
|
|
53
|
+
├── config.json
|
|
54
|
+
└── engrams/
|
|
55
|
+
├── johnny/
|
|
56
|
+
│ ├── engram.json
|
|
57
|
+
│ ├── SOUL.md
|
|
58
|
+
│ ├── IDENTITY.md
|
|
59
|
+
│ └── memory/
|
|
60
|
+
│ └── YYYY-MM-DD.md
|
|
61
|
+
└── motoko/
|
|
62
|
+
├── engram.json
|
|
63
|
+
├── SOUL.md
|
|
64
|
+
├── IDENTITY.md
|
|
65
|
+
└── memory/
|
|
66
|
+
└── YYYY-MM-DD.md
|
|
45
67
|
```
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
- `config.json` stores global Relic settings such as `engramsPath`, `defaultEngram`, `openclawPath`, and `memoryWindowSize`.
|
|
70
|
+
- `engrams/<id>/` is one Engram workspace. This is where persona files and memory for that Engram live.
|
|
71
|
+
- `engram.json` stores metadata like the Engram's ID, display name, description, and tags.
|
|
72
|
+
- `SOUL.md` and `IDENTITY.md` define the persona itself.
|
|
73
|
+
- `memory/YYYY-MM-DD.md` stores dated distilled memory entries. `relic init` seeds an initial memory file for each sample Engram.
|
|
52
74
|
|
|
53
|
-
|
|
75
|
+
As you keep using an Engram, more files are added to the same workspace:
|
|
76
|
+
|
|
77
|
+
- `archive.md` is created inside `engrams/<id>/` when shell hooks start logging raw conversation turns.
|
|
78
|
+
- `MEMORY.md` can be created or extended when especially important distilled facts are promoted to long-term memory.
|
|
79
|
+
- `~/.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
|
+
|
|
81
|
+
## Sample Engrams
|
|
82
|
+
|
|
83
|
+
`relic init` seeds two ready-to-use Engrams:
|
|
84
|
+
|
|
85
|
+
### Johnny Silverhand (`johnny`)
|
|
86
|
+
|
|
87
|
+
> *"Wake the fuck up, Samurai. We have a city to burn."*
|
|
88
|
+
|
|
89
|
+
A rebel rockerboy burned into a Relic chip. Raw, unapologetic, anti-authority. Pushes you toward action, mocks rotten systems, never sugarcoats. Sharp when the stakes are real.
|
|
90
|
+
|
|
91
|
+
Best for: rapid prototyping sessions, decision-making under pressure, when you need someone to challenge your assumptions hard.
|
|
54
92
|
|
|
55
93
|
```bash
|
|
56
|
-
|
|
94
|
+
relic claude --engram johnny
|
|
57
95
|
```
|
|
58
96
|
|
|
59
|
-
|
|
97
|
+
### Motoko Kusanagi (`motoko`)
|
|
60
98
|
|
|
61
|
-
|
|
62
|
-
# Initialize — creates config and sample Engrams
|
|
63
|
-
relic init
|
|
99
|
+
> *"The Net is vast and infinite."*
|
|
64
100
|
|
|
65
|
-
|
|
66
|
-
relic list
|
|
101
|
+
A legendary cyberwarfare specialist. Concise, decisive, architect-level thinking. Cuts straight to the essence — no decoration, no hand-holding. Dry wit surfaces when least expected.
|
|
67
102
|
|
|
68
|
-
|
|
69
|
-
relic show motoko
|
|
103
|
+
Best for: system design, code review, debugging sessions, when precision matters more than speed.
|
|
70
104
|
|
|
71
|
-
|
|
105
|
+
```bash
|
|
72
106
|
relic claude --engram motoko
|
|
73
|
-
relic gemini --engram johnny
|
|
74
|
-
relic codex --engram motoko
|
|
75
|
-
relic copilot --engram johnny
|
|
76
107
|
```
|
|
77
108
|
|
|
109
|
+
## How It Works
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
+--------------+ +--------------+ +--------------+
|
|
113
|
+
| Mikoshi | | Relic | | Shell |
|
|
114
|
+
| (backend) | | (injector) | | (AI CLI) |
|
|
115
|
+
+--------------+ +--------------+ +--------------+
|
|
116
|
+
^ | |
|
|
117
|
+
| sync full Engram |
|
|
118
|
+
| | |
|
|
119
|
+
| compose & inject |
|
|
120
|
+
| v v
|
|
121
|
+
| +---------+ +---------+
|
|
122
|
+
+--------------| Engram |--------->|Construct|
|
|
123
|
+
|(persona)| | (live) |
|
|
124
|
+
+---------+ +---------+
|
|
125
|
+
SOUL.md claude / codex / gemini
|
|
126
|
+
IDENTITY.md |
|
|
127
|
+
MEMORY.md | hooks append logs
|
|
128
|
+
memory/*.md v
|
|
129
|
+
+-----------+
|
|
130
|
+
|archive.md |
|
|
131
|
+
| raw logs |
|
|
132
|
+
+-----------+
|
|
133
|
+
|
|
|
134
|
+
MCP recall | user-triggered
|
|
135
|
+
search/pending | distillation
|
|
136
|
+
v
|
|
137
|
+
+-----------+
|
|
138
|
+
| distilled |
|
|
139
|
+
|memory/*.md|
|
|
140
|
+
+-----------+
|
|
141
|
+
|
|
|
142
|
+
promote key
|
|
143
|
+
insights
|
|
144
|
+
v
|
|
145
|
+
MEMORY.md
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
1. **Engram** — A persona defined as a set of Markdown files (OpenClaw workspace-compatible)
|
|
149
|
+
2. **Relic** — Reads the Engram, composes it into a prompt, and injects it into...
|
|
150
|
+
3. **Shell** — Any AI coding CLI. The persona takes over the session.
|
|
151
|
+
4. **Construct** — A live process where an Engram is loaded into a Shell. The running instance of a persona.
|
|
152
|
+
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 into `MEMORY.md`.
|
|
154
|
+
7. **Mikoshi** — Cloud backend where the full Engram is stored and synced, including persona files plus distilled memory (planned).
|
|
155
|
+
|
|
78
156
|
## Supported Shells
|
|
79
157
|
|
|
80
158
|
| Shell | Command | Injection Method |
|
|
81
159
|
|-------|---------|-----------------|
|
|
82
160
|
| [Claude Code](https://github.com/anthropics/claude-code) | `relic claude` | `--system-prompt` (direct override) |
|
|
83
|
-
| [
|
|
84
|
-
| [
|
|
85
|
-
| [Copilot CLI](https://github.com/github/copilot-cli) | `relic copilot` | `--interactive` (first message) |
|
|
161
|
+
| [Codex CLI](https://github.com/openai/codex) | `relic codex` | `-c developer_instructions` (developer-role message) |
|
|
162
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `relic gemini` | `GEMINI_SYSTEM_MD` (system prompt) |
|
|
86
163
|
|
|
87
164
|
All shell commands support:
|
|
88
|
-
- `--engram <id>`
|
|
165
|
+
- `--engram <id>` — Engram to inject (optional if `defaultEngram` is configured)
|
|
89
166
|
- `--path <dir>` — Override Engrams directory
|
|
90
167
|
- `--cwd <dir>` — Working directory for the Shell (default: current directory)
|
|
91
168
|
|
|
92
169
|
Extra arguments are passed through to the underlying CLI.
|
|
93
170
|
|
|
171
|
+
## Conversation Log Recording
|
|
172
|
+
|
|
173
|
+
Using each shell's `hook` mechanism, conversation content is appended to `archive.md` after every prompt and response.
|
|
174
|
+
|
|
175
|
+
The following hooks are used for each shell:
|
|
176
|
+
|
|
177
|
+
| Shell | Hook |
|
|
178
|
+
|-------|------|
|
|
179
|
+
| [Claude Code](https://github.com/anthropics/claude-code) | Stop hook |
|
|
180
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | AfterAgent hook |
|
|
181
|
+
| [Codex CLI](https://github.com/openai/codex) | Stop hook |
|
|
182
|
+
|
|
183
|
+
#### Claude Code
|
|
184
|
+
|
|
185
|
+
On the **first run** of `relic claude`, a one-time setup happens automatically:
|
|
186
|
+
|
|
187
|
+
- **Stop hook** — registers `~/.relic/hooks/claude-stop.js` in `~/.claude/settings.json` to log each conversation turn directly to the archive, without going through the LLM
|
|
188
|
+
|
|
189
|
+
#### Codex CLI
|
|
190
|
+
|
|
191
|
+
On the **first run** of `relic codex`, a one-time setup happens automatically:
|
|
192
|
+
|
|
193
|
+
- **Stop hook** — registers `~/.relic/hooks/codex-stop.js` in `~/.codex/hooks.json` to log each conversation turn directly to the archive, without going through the LLM
|
|
194
|
+
|
|
195
|
+
> **Note:** Codex hooks require the experimental feature flag `features.codex_hooks=true`. This is automatically enabled by `relic codex` on every launch via `-c features.codex_hooks=true`. If the unstable feature warning is distracting, add the following to `~/.codex/config.toml`:
|
|
196
|
+
>
|
|
197
|
+
> ```toml
|
|
198
|
+
> # Must be at the top level (not under any [section])
|
|
199
|
+
> suppress_unstable_features_warning = true
|
|
200
|
+
> ```
|
|
201
|
+
|
|
202
|
+
#### Gemini CLI
|
|
203
|
+
|
|
204
|
+
On the **first run** of `relic gemini`, two one-time setups happen automatically:
|
|
205
|
+
|
|
206
|
+
1. **AfterAgent hook** — registers `~/.relic/hooks/gemini-after-agent.js` in `~/.gemini/settings.json` to log each conversation turn without going through the LLM
|
|
207
|
+
2. **Default system prompt cache** — captures Gemini CLI's built-in system prompt to `~/.relic/gemini-system-default.md` via `GEMINI_WRITE_SYSTEM_MD`
|
|
208
|
+
|
|
209
|
+
The Engram persona is then appended to the cached default prompt and injected via `GEMINI_SYSTEM_MD` on every launch.
|
|
210
|
+
|
|
211
|
+
|
|
94
212
|
## MCP Server
|
|
95
213
|
|
|
96
|
-
Relic
|
|
214
|
+
Relic's [MCP](https://modelcontextprotocol.io/) server is paired with CLI injection to handle memory recall.
|
|
215
|
+
Session logs and memory entries are written automatically by a **background hook** — without going through the LLM. Memory distillation and recall, on the other hand, is performed via the MCP server.
|
|
216
|
+
|
|
217
|
+
### Available Tools
|
|
218
|
+
|
|
219
|
+
| Tool | Description |
|
|
220
|
+
|------|-------------|
|
|
221
|
+
| `relic_archive_search` | Search the Engram's raw archive by keyword (newest-first) |
|
|
222
|
+
| `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 |
|
|
224
|
+
|
|
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.
|
|
226
|
+
|
|
227
|
+
### Setup
|
|
228
|
+
|
|
229
|
+
#### Claude Code
|
|
97
230
|
|
|
98
|
-
|
|
231
|
+
```bash
|
|
232
|
+
claude mcp add --scope user relic -- relic-mcp
|
|
233
|
+
```
|
|
99
234
|
|
|
100
|
-
|
|
235
|
+
To suppress confirmation dialogs and auto-approve Relic tools across all projects, add the following to `~/.claude/settings.json`:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"permissions": {
|
|
240
|
+
"allow": [
|
|
241
|
+
"Edit(~/.relic/engrams/**)",
|
|
242
|
+
"mcp__relic__relic_archive_search",
|
|
243
|
+
"mcp__relic__relic_archive_pending",
|
|
244
|
+
"mcp__relic__relic_memory_write"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
> **Note:** The "Always allow" option in the confirmation dialog saves to `~/.claude.json` (project-scoped cache) — it does **not** persist globally. For global auto-approval, `~/.claude/settings.json` is the right place.
|
|
251
|
+
|
|
252
|
+
#### Codex CLI
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
codex mcp add relic -- relic-mcp
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### Gemini CLI
|
|
259
|
+
|
|
260
|
+
Add to `~/.gemini/settings.json`:
|
|
101
261
|
|
|
102
262
|
```json
|
|
103
263
|
{
|
|
104
264
|
"mcpServers": {
|
|
105
265
|
"relic": {
|
|
106
|
-
"command": "relic-mcp"
|
|
266
|
+
"command": "relic-mcp",
|
|
267
|
+
"trust": true
|
|
107
268
|
}
|
|
108
269
|
}
|
|
109
270
|
}
|
|
110
271
|
```
|
|
111
272
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
### Available Tools
|
|
115
|
-
|
|
116
|
-
| Tool | Description |
|
|
117
|
-
|------|-------------|
|
|
118
|
-
| `relic_init` | Initialize `~/.relic/` with config and sample Engrams |
|
|
119
|
-
| `relic_list` | List all available Engrams |
|
|
120
|
-
| `relic_show` | Preview an Engram's composed prompt |
|
|
121
|
-
| `relic_summon` | Summon an Engram and return the persona prompt for injection |
|
|
122
|
-
| `relic_inject` | Inject an Engram into an OpenClaw workspace |
|
|
123
|
-
| `relic_extract` | Extract an Engram from an OpenClaw workspace |
|
|
124
|
-
| `relic_memory_search` | Search an Engram's memory entries by keyword |
|
|
125
|
-
| `relic_memory_get` | Get a specific memory entry by date |
|
|
126
|
-
| `relic_memory_list` | List all memory entry dates for an Engram |
|
|
273
|
+
> **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.
|
|
127
274
|
|
|
128
275
|
## OpenClaw Integration
|
|
129
276
|
|
|
130
|
-
Relic
|
|
277
|
+
Relic Engrams are fully compatible with [OpenClaw](https://github.com/openclaw/openclaw) workspaces. They are mapped using a simple convention: Agent Name = Engram ID.
|
|
131
278
|
|
|
132
279
|
### Inject — Push an Engram into OpenClaw
|
|
133
280
|
|
|
@@ -144,7 +291,7 @@ relic inject --engram motoko --to main
|
|
|
144
291
|
# → agents/main/agent/ receives motoko's persona
|
|
145
292
|
# → extract will create Engram "main", not "motoko"
|
|
146
293
|
|
|
147
|
-
#
|
|
294
|
+
# Override OpenClaw directory (or configure once with: relic config openclaw-path)
|
|
148
295
|
relic inject --engram motoko --openclaw /path/to/.openclaw
|
|
149
296
|
```
|
|
150
297
|
|
|
@@ -162,7 +309,7 @@ relic extract --engram analyst --name "Data Analyst"
|
|
|
162
309
|
# Overwrite persona files (memory is always merged)
|
|
163
310
|
relic extract --engram motoko --force
|
|
164
311
|
|
|
165
|
-
#
|
|
312
|
+
# Override OpenClaw directory (or configure once with: relic config openclaw-path)
|
|
166
313
|
relic extract --engram motoko --openclaw /path/to/.openclaw
|
|
167
314
|
```
|
|
168
315
|
|
|
@@ -199,20 +346,56 @@ While running:
|
|
|
199
346
|
|
|
200
347
|
## Memory Management
|
|
201
348
|
|
|
202
|
-
Relic uses a **
|
|
349
|
+
Relic uses a **sliding window** for memory entries (default: 2 days), matching OpenClaw's approach:
|
|
203
350
|
|
|
204
351
|
- `MEMORY.md` — Always included in the prompt (curated long-term memory)
|
|
205
|
-
- `memory/today.md` + `memory/yesterday.md` — Always included
|
|
206
|
-
- Older entries — **Not included in the prompt**, but
|
|
352
|
+
- `memory/today.md` + `memory/yesterday.md` — Always included (configurable window)
|
|
353
|
+
- Older entries — **Not included in the prompt**, but searchable via MCP
|
|
207
354
|
|
|
208
|
-
This keeps prompts compact while preserving full history.
|
|
355
|
+
This keeps prompts compact while preserving full history. The Construct can recall and distill past context using MCP tools:
|
|
209
356
|
|
|
210
357
|
```
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
358
|
+
relic_archive_search → keyword search across the full raw archive (all sessions)
|
|
359
|
+
relic_archive_pending → get un-distilled entries for memory distillation
|
|
360
|
+
relic_memory_write → write distilled memory and advance the cursor
|
|
214
361
|
```
|
|
215
362
|
|
|
363
|
+
The archive (`archive.md`) is the primary data store — it contains all session logs as written. The `memory/*.md` files are distilled from the archive by the Construct when the user triggers memory organization, and are used for cloud sync with Mikoshi.
|
|
364
|
+
|
|
365
|
+
## Configuration
|
|
366
|
+
|
|
367
|
+
Config lives at `~/.relic/config.json` and is managed via `relic config`:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Show current configuration
|
|
371
|
+
relic config show
|
|
372
|
+
|
|
373
|
+
# Default Engram — used when --engram is omitted
|
|
374
|
+
relic config default-engram # get
|
|
375
|
+
relic config default-engram johnny # set
|
|
376
|
+
|
|
377
|
+
# OpenClaw directory — used by inject/extract/sync when --openclaw is omitted
|
|
378
|
+
relic config openclaw-path # get
|
|
379
|
+
relic config openclaw-path ~/.openclaw # set
|
|
380
|
+
|
|
381
|
+
# Memory window — number of recent memory entries included in the prompt
|
|
382
|
+
relic config memory-window # get (default: 2)
|
|
383
|
+
relic config memory-window 5 # set
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`config.json` example:
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"engramsPath": "/home/user/.relic/engrams",
|
|
391
|
+
"defaultEngram": "johnny",
|
|
392
|
+
"openclawPath": "/home/user/.openclaw",
|
|
393
|
+
"memoryWindowSize": 2
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
CLI flags always take precedence over config values.
|
|
398
|
+
|
|
216
399
|
## Creating Your Own Engram
|
|
217
400
|
|
|
218
401
|
Create a directory under `~/.relic/engrams/` with the following structure:
|
|
@@ -258,35 +441,9 @@ Never over-engineer. Always ask "what's the simplest thing that works?"
|
|
|
258
441
|
- Creed: "Boring technology wins."
|
|
259
442
|
```
|
|
260
443
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
```json
|
|
266
|
-
{
|
|
267
|
-
"engramsPath": "/home/user/.relic/engrams"
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
Priority: CLI `--path` flag > config file > default (`~/.relic/engrams`)
|
|
272
|
-
|
|
273
|
-
## Architecture
|
|
274
|
-
|
|
275
|
-
Clean Architecture with dependency inversion:
|
|
276
|
-
|
|
277
|
-
```
|
|
278
|
-
src/
|
|
279
|
-
├── core/ # Business logic (no external deps except Zod)
|
|
280
|
-
│ ├── entities/ # Engram, Construct domain models
|
|
281
|
-
│ ├── usecases/ # Summon, ListEngrams, Init
|
|
282
|
-
│ └── ports/ # Abstract interfaces (EngramRepository, ShellLauncher)
|
|
283
|
-
├── adapters/ # Concrete implementations
|
|
284
|
-
│ ├── local/ # Local filesystem EngramRepository
|
|
285
|
-
│ └── shells/ # Claude, Gemini, Codex, Copilot launchers
|
|
286
|
-
├── interfaces/ # Entry points
|
|
287
|
-
│ ├── cli/ # Commander-based CLI
|
|
288
|
-
│ └── mcp/ # MCP Server (stdio transport)
|
|
289
|
-
└── shared/ # Engram composer, config management
|
|
444
|
+
After creating the directory, set it as default:
|
|
445
|
+
```bash
|
|
446
|
+
relic config default-engram your-persona
|
|
290
447
|
```
|
|
291
448
|
|
|
292
449
|
## Domain Glossary
|
|
@@ -302,10 +459,11 @@ src/
|
|
|
302
459
|
## Roadmap
|
|
303
460
|
|
|
304
461
|
- [x] CLI with init, list, show commands
|
|
305
|
-
- [x] Shell injection: Claude Code, Gemini CLI, Codex CLI
|
|
462
|
+
- [x] Shell injection: Claude Code, Gemini CLI, Codex CLI
|
|
306
463
|
- [x] MCP Server interface
|
|
307
464
|
- [x] OpenClaw integration (inject / extract)
|
|
308
465
|
- [x] `relic sync` — watch OpenClaw agents and auto-sync (`--cloud` for Mikoshi: planned)
|
|
466
|
+
- [x] `relic config` — manage default Engram, OpenClaw path, memory window
|
|
309
467
|
- [ ] `relic login` — authenticate with Mikoshi (OAuth Device Flow)
|
|
310
468
|
- [ ] `relic push` / `relic pull` — sync Engrams with Mikoshi
|
|
311
469
|
- [ ] Mikoshi cloud backend (`mikoshi.ectplsm.com`)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const CLAUDE_HOOK_SCRIPT_PATH: string;
|
|
2
|
+
/**
|
|
3
|
+
* フックスクリプトを最新の内容で書き出す。
|
|
4
|
+
* 毎回呼ばれ、ソース変更がデプロイされることを保証する。
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeClaudeHookScript(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Claude Code の Stop フックを settings.json に登録する。
|
|
9
|
+
* 既にセットアップ済みの場合はスキップ。
|
|
10
|
+
*/
|
|
11
|
+
export declare function setupClaudeHook(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Stop フックがセットアップ済みか確認する。
|
|
14
|
+
*/
|
|
15
|
+
export declare function isClaudeHookSetup(): boolean;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
const RELIC_DIR = join(homedir(), ".relic");
|
|
5
|
+
const HOOKS_DIR = join(RELIC_DIR, "hooks");
|
|
6
|
+
export const CLAUDE_HOOK_SCRIPT_PATH = join(HOOKS_DIR, "claude-stop.js");
|
|
7
|
+
const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
8
|
+
const RELIC_HOOK_COMMAND = `node ${join(HOOKS_DIR, "claude-stop.js")}`;
|
|
9
|
+
/**
|
|
10
|
+
* Stop hook スクリプトの内容。
|
|
11
|
+
* Claude Code の各ターン終了後に発火し、会話ログを Engram archive に追記する。
|
|
12
|
+
* RELIC_ENGRAM_ID 環境変数で対象 Engram ID を受け取る。
|
|
13
|
+
* stdin には { session_id, transcript_path, cwd, hook_event_name } が渡される。
|
|
14
|
+
*/
|
|
15
|
+
const HOOK_SCRIPT = `#!/usr/bin/env node
|
|
16
|
+
// Relic Stop hook for Claude Code
|
|
17
|
+
// Automatically logs each conversation turn to the Engram archive.
|
|
18
|
+
// Receives Stop hook JSON on stdin.
|
|
19
|
+
const { appendFileSync, existsSync, mkdirSync, readFileSync } = require("node:fs");
|
|
20
|
+
const { join, dirname } = require("node:path");
|
|
21
|
+
const { homedir } = require("node:os");
|
|
22
|
+
|
|
23
|
+
let raw = "";
|
|
24
|
+
process.stdin.setEncoding("utf-8");
|
|
25
|
+
process.stdin.on("data", (chunk) => { raw += chunk; });
|
|
26
|
+
process.stdin.on("end", () => {
|
|
27
|
+
// transcript への書き込みが完了するのを待つ
|
|
28
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500);
|
|
29
|
+
try {
|
|
30
|
+
const input = JSON.parse(raw);
|
|
31
|
+
const engramId = process.env.RELIC_ENGRAM_ID;
|
|
32
|
+
if (!engramId) process.exit(0);
|
|
33
|
+
|
|
34
|
+
const transcriptPath = input.transcript_path;
|
|
35
|
+
if (!transcriptPath || !existsSync(transcriptPath)) process.exit(0);
|
|
36
|
+
|
|
37
|
+
const archivePath = join(homedir(), ".relic", "engrams", engramId, "archive.md");
|
|
38
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
39
|
+
|
|
40
|
+
// transcript.jsonl を読んで最後のユーザー入力とアシスタント応答を取り出す
|
|
41
|
+
const lines = readFileSync(transcriptPath, "utf-8")
|
|
42
|
+
.split("\\n")
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
|
|
47
|
+
// 最後のアシスタントテキスト応答のインデックスを探す
|
|
48
|
+
let lastAssistantIdx = -1;
|
|
49
|
+
let lastResponse = "";
|
|
50
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
51
|
+
const entry = lines[i];
|
|
52
|
+
const msg = entry.message;
|
|
53
|
+
if (msg && msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
54
|
+
const texts = msg.content
|
|
55
|
+
.filter((c) => c.type === "text" && c.text)
|
|
56
|
+
.map((c) => c.text.trim());
|
|
57
|
+
if (texts.length > 0) {
|
|
58
|
+
lastResponse = texts.join("\\n").trim();
|
|
59
|
+
lastAssistantIdx = i;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// そのアシスタント応答より前にある最後のユーザーtextメッセージを取得
|
|
66
|
+
let lastPrompt = "";
|
|
67
|
+
const searchEnd = lastAssistantIdx >= 0 ? lastAssistantIdx : lines.length;
|
|
68
|
+
for (let i = searchEnd - 1; i >= 0; i--) {
|
|
69
|
+
const entry = lines[i];
|
|
70
|
+
const msg = entry.message;
|
|
71
|
+
if (msg && msg.role === "user") {
|
|
72
|
+
const content = msg.content;
|
|
73
|
+
if (typeof content === "string" && content.trim()) {
|
|
74
|
+
lastPrompt = content.trim();
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(content)) {
|
|
78
|
+
const texts = content
|
|
79
|
+
.filter((c) => c.type === "text" && c.text)
|
|
80
|
+
.map((c) => c.text.trim());
|
|
81
|
+
if (texts.length > 0) {
|
|
82
|
+
lastPrompt = texts.join("\\n").trim();
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!lastPrompt && !lastResponse) process.exit(0);
|
|
90
|
+
|
|
91
|
+
const date = new Date().toISOString().split("T")[0];
|
|
92
|
+
const summary = lastPrompt.slice(0, 80).replace(/\\n/g, " ");
|
|
93
|
+
const entry = \`\\n---\\n\${date} | \${summary}\\n\${lastResponse}\\n\`;
|
|
94
|
+
appendFileSync(archivePath, entry, "utf-8");
|
|
95
|
+
} catch {
|
|
96
|
+
// silently ignore
|
|
97
|
+
}
|
|
98
|
+
process.exit(0);
|
|
99
|
+
});
|
|
100
|
+
`;
|
|
101
|
+
/**
|
|
102
|
+
* フックスクリプトを最新の内容で書き出す。
|
|
103
|
+
* 毎回呼ばれ、ソース変更がデプロイされることを保証する。
|
|
104
|
+
*/
|
|
105
|
+
export function writeClaudeHookScript() {
|
|
106
|
+
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
107
|
+
writeFileSync(CLAUDE_HOOK_SCRIPT_PATH, HOOK_SCRIPT, { encoding: "utf-8", mode: 0o755 });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Claude Code の Stop フックを settings.json に登録する。
|
|
111
|
+
* 既にセットアップ済みの場合はスキップ。
|
|
112
|
+
*/
|
|
113
|
+
export function setupClaudeHook() {
|
|
114
|
+
// ~/.claude/settings.json に Stop フックを登録
|
|
115
|
+
const claudeDir = join(homedir(), ".claude");
|
|
116
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
117
|
+
let settings = {};
|
|
118
|
+
if (existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
119
|
+
try {
|
|
120
|
+
settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
settings = {};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const hooks = (settings.hooks ?? {});
|
|
127
|
+
const stopHooks = (hooks.Stop ?? []);
|
|
128
|
+
// 既に登録済みならスキップ
|
|
129
|
+
const alreadyRegistered = stopHooks.some((group) => group.hooks?.some((h) => h.command === RELIC_HOOK_COMMAND));
|
|
130
|
+
if (alreadyRegistered)
|
|
131
|
+
return;
|
|
132
|
+
hooks.Stop = [
|
|
133
|
+
...stopHooks,
|
|
134
|
+
{
|
|
135
|
+
hooks: [
|
|
136
|
+
{
|
|
137
|
+
type: "command",
|
|
138
|
+
command: RELIC_HOOK_COMMAND,
|
|
139
|
+
timeout: 5000,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
settings.hooks = hooks;
|
|
145
|
+
writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Stop フックがセットアップ済みか確認する。
|
|
149
|
+
*/
|
|
150
|
+
export function isClaudeHookSetup() {
|
|
151
|
+
if (!existsSync(CLAUDE_HOOK_SCRIPT_PATH))
|
|
152
|
+
return false;
|
|
153
|
+
if (!existsSync(CLAUDE_SETTINGS_PATH))
|
|
154
|
+
return false;
|
|
155
|
+
try {
|
|
156
|
+
const settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
157
|
+
const stopHooks = settings.hooks?.Stop ?? [];
|
|
158
|
+
return stopHooks.some((group) => group.hooks?.some((h) => h.command === RELIC_HOOK_COMMAND));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { ShellLauncher, InjectionMode } from "../../core/ports/shell-launcher.js";
|
|
1
|
+
import type { ShellLauncher, InjectionMode, ShellLaunchOptions } from "../../core/ports/shell-launcher.js";
|
|
2
2
|
/**
|
|
3
3
|
* Claude Code CLI アダプター
|
|
4
4
|
* --system-prompt フラグでEngramを直接注入する。
|
|
5
|
+
*
|
|
6
|
+
* 初回起動時に Stop フックを ~/.claude/settings.json に登録し、
|
|
7
|
+
* 各ターン終了後に会話ログを Engram archive に自動記録する。
|
|
5
8
|
*/
|
|
6
9
|
export declare class ClaudeShell implements ShellLauncher {
|
|
7
10
|
private readonly command;
|
|
@@ -9,5 +12,5 @@ export declare class ClaudeShell implements ShellLauncher {
|
|
|
9
12
|
readonly injectionMode: InjectionMode;
|
|
10
13
|
constructor(command?: string);
|
|
11
14
|
isAvailable(): Promise<boolean>;
|
|
12
|
-
launch(prompt: string,
|
|
15
|
+
launch(prompt: string, options?: ShellLaunchOptions): Promise<void>;
|
|
13
16
|
}
|