@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.
Files changed (53) hide show
  1. package/README.md +261 -103
  2. package/dist/adapters/shells/claude-hook.d.ts +15 -0
  3. package/dist/adapters/shells/claude-hook.js +163 -0
  4. package/dist/adapters/shells/claude-shell.d.ts +5 -2
  5. package/dist/adapters/shells/claude-shell.js +19 -3
  6. package/dist/adapters/shells/codex-hook.d.ts +15 -0
  7. package/dist/adapters/shells/codex-hook.js +144 -0
  8. package/dist/adapters/shells/codex-shell.d.ts +7 -4
  9. package/dist/adapters/shells/codex-shell.js +24 -7
  10. package/dist/adapters/shells/copilot-shell.d.ts +2 -2
  11. package/dist/adapters/shells/copilot-shell.js +3 -3
  12. package/dist/adapters/shells/gemini-hook.d.ts +15 -0
  13. package/dist/adapters/shells/gemini-hook.js +111 -0
  14. package/dist/adapters/shells/gemini-shell.d.ts +6 -4
  15. package/dist/adapters/shells/gemini-shell.js +105 -14
  16. package/dist/adapters/shells/index.d.ts +0 -1
  17. package/dist/adapters/shells/index.js +0 -1
  18. package/dist/adapters/shells/spawn-shell.d.ts +1 -1
  19. package/dist/adapters/shells/spawn-shell.js +10 -3
  20. package/dist/adapters/shells/trust-registrar.d.ts +19 -0
  21. package/dist/adapters/shells/trust-registrar.js +141 -0
  22. package/dist/core/ports/shell-launcher.d.ts +17 -2
  23. package/dist/core/usecases/archive-cursor-update.d.ts +21 -0
  24. package/dist/core/usecases/archive-cursor-update.js +44 -0
  25. package/dist/core/usecases/archive-pending.d.ts +25 -0
  26. package/dist/core/usecases/archive-pending.js +61 -0
  27. package/dist/core/usecases/archive-search.d.ts +20 -0
  28. package/dist/core/usecases/archive-search.js +46 -0
  29. package/dist/core/usecases/inbox-search.d.ts +20 -0
  30. package/dist/core/usecases/inbox-search.js +46 -0
  31. package/dist/core/usecases/inbox-write.d.ts +27 -0
  32. package/dist/core/usecases/inbox-write.js +72 -0
  33. package/dist/core/usecases/index.d.ts +2 -1
  34. package/dist/core/usecases/index.js +2 -1
  35. package/dist/core/usecases/summon.d.ts +6 -1
  36. package/dist/core/usecases/summon.js +8 -2
  37. package/dist/interfaces/cli/commands/config.d.ts +2 -0
  38. package/dist/interfaces/cli/commands/config.js +83 -0
  39. package/dist/interfaces/cli/commands/extract.js +3 -2
  40. package/dist/interfaces/cli/commands/init.js +47 -0
  41. package/dist/interfaces/cli/commands/inject.js +3 -2
  42. package/dist/interfaces/cli/commands/shell.js +13 -11
  43. package/dist/interfaces/cli/index.js +2 -0
  44. package/dist/interfaces/mcp/index.js +68 -305
  45. package/dist/shared/config.d.ts +31 -0
  46. package/dist/shared/config.js +86 -16
  47. package/dist/shared/engram-composer.d.ts +16 -3
  48. package/dist/shared/engram-composer.js +50 -5
  49. package/dist/shared/memory-inbox.d.ts +78 -0
  50. package/dist/shared/memory-inbox.js +168 -0
  51. package/dist/shared/session-recorder.d.ts +47 -0
  52. package/dist/shared/session-recorder.js +112 -0
  53. package/package.json +5 -5
package/README.md CHANGED
@@ -11,123 +11,270 @@
11
11
  /_/ |_/_____/_____/___/\____/
12
12
  ```
13
13
 
14
- **Inject AI personas into any coding CLI.**
14
+ **Inject a unified AI persona with persistent memory into any coding CLI.**
15
15
 
16
- Relic manages AI personalities (called **Engrams**) and injects them into coding assistants like Claude Code, Gemini CLI, Codex CLI, and GitHub Copilot 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. One persona, any shell.
17
+
18
+ ## Installation
17
19
 
18
20
  ```bash
19
- # Initialize Relic (creates ~/.relic/ with sample Engrams)
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
- # Launch Claude Code as Johnny Silverhand
23
- relic claude --engram johnny
31
+ # List available Engrams
32
+ relic list
33
+
34
+ # Preview an Engram's composed prompt
35
+ relic show motoko
24
36
 
25
- # Launch Gemini CLI as Motoko Kusanagi
26
- relic gemini --engram motoko
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
- ## How It Works
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
- | Mikoshi | | Relic | | Shell |
34
- | (backend) | | (injector) | | (AI CLI) |
35
- +--------------+ +--------------+ +--------------+
36
- | | |
37
- +---------+ compose & +---------+
38
- | Engram |------> inject ------------->|Construct|
39
- |(persona)| | (live) |
40
- +---------+ +---------+
41
- SOUL.md claude
42
- IDENTITY.md gemini
43
- MEMORY.md codex
44
- ... copilot
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
- 1. **Engram** A persona defined as a set of Markdown files (OpenClaw-compatible)
48
- 2. **Relic** Reads the Engram, composes it into a prompt, and injects it into...
49
- 3. **Shell** Any AI coding CLI. The persona takes over the session.
50
- 4. **Construct** — A live process where an Engram is loaded into a Shell. The running instance of a persona.
51
- 5. **Mikoshi** Cloud backend where Engrams are stored and synced (planned).
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
- ## Installation
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
- npm install -g @ectplsm/relic
94
+ relic claude --engram johnny
57
95
  ```
58
96
 
59
- ## Quick Start
97
+ ### Motoko Kusanagi (`motoko`)
60
98
 
61
- ```bash
62
- # Initialize — creates config and sample Engrams
63
- relic init
99
+ > *"The Net is vast and infinite."*
64
100
 
65
- # List available Engrams
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
- # Preview an Engram's composed prompt
69
- relic show motoko
103
+ Best for: system design, code review, debugging sessions, when precision matters more than speed.
70
104
 
71
- # Launch a Shell with an Engram injected
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
- | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `relic gemini` | `--prompt-interactive` (first message) |
84
- | [Codex CLI](https://github.com/openai/codex) | `relic codex` | `PROMPT` argument (first message) |
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>` (required) — Engram to inject
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 also runs as an [MCP](https://modelcontextprotocol.io/) server, allowing any MCP-compatible client (like Claude Desktop) to access Engrams directly.
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
- ### Setup (Claude Desktop)
231
+ ```bash
232
+ claude mcp add --scope user relic -- relic-mcp
233
+ ```
99
234
 
100
- Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
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
- Restart Claude Desktop.
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 is fully compatible with [OpenClaw](https://github.com/openclaw/openclaw) workspaces. **Agent name = Engram ID** — this simple convention eliminates the need for mapping configuration.
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
- # Specify a custom OpenClaw directory
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
- # Extract from a custom OpenClaw directory
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 **2-day sliding window** for memory entries, matching OpenClaw's approach:
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 accessible via MCP tools
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. AI clients (like Claude Desktop) can use the memory tools to search or retrieve older entries on demand:
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
- relic_memory_search → keyword search across all entries
212
- relic_memory_get fetch a specific date's entry
213
- relic_memory_list list all available dates
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
- ## Configuration
262
-
263
- Config lives at `~/.relic/config.json`:
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, Copilot 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, extraArgs?: string[], cwd?: string): Promise<void>;
15
+ launch(prompt: string, options?: ShellLaunchOptions): Promise<void>;
13
16
  }