@ectplsm/relic 0.1.1 → 0.1.3

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 +172 -49
  2. package/dist/adapters/shells/claude-hook.d.ts +12 -0
  3. package/dist/adapters/shells/claude-hook.js +160 -0
  4. package/dist/adapters/shells/claude-shell.d.ts +5 -2
  5. package/dist/adapters/shells/claude-shell.js +17 -3
  6. package/dist/adapters/shells/codex-hook.d.ts +12 -0
  7. package/dist/adapters/shells/codex-hook.js +141 -0
  8. package/dist/adapters/shells/codex-shell.d.ts +7 -4
  9. package/dist/adapters/shells/codex-shell.js +22 -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 +12 -0
  13. package/dist/adapters/shells/gemini-hook.js +108 -0
  14. package/dist/adapters/shells/gemini-shell.d.ts +6 -4
  15. package/dist/adapters/shells/gemini-shell.js +103 -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 +8 -1
  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
@@ -13,17 +13,20 @@
13
13
 
14
14
  **Inject AI personas 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 personalities (called **Engrams**) and injects them into coding assistants like Claude Code, Gemini CLI, Codex CLI. One persona, any shell.
17
17
 
18
18
  ```bash
19
19
  # Initialize Relic (creates ~/.relic/ with sample Engrams)
20
20
  relic init
21
21
 
22
- # Launch Claude Code as Johnny Silverhand
23
- relic claude --engram johnny
22
+ # Set a default Engram once
23
+ relic config default-engram johnny
24
+
25
+ # Launch Claude Code as Johnny — no flags needed
26
+ relic claude
24
27
 
25
- # Launch Gemini CLI as Motoko Kusanagi
26
- relic gemini --engram motoko
28
+ # Or specify explicitly
29
+ relic claude --engram motoko
27
30
  ```
28
31
 
29
32
  ## How It Works
@@ -41,7 +44,7 @@ relic gemini --engram motoko
41
44
  SOUL.md claude
42
45
  IDENTITY.md gemini
43
46
  MEMORY.md codex
44
- ... copilot
47
+ ...
45
48
  ```
46
49
 
47
50
  1. **Engram** — A persona defined as a set of Markdown files (OpenClaw-compatible)
@@ -61,6 +64,7 @@ npm install -g @ectplsm/relic
61
64
  ```bash
62
65
  # Initialize — creates config and sample Engrams
63
66
  relic init
67
+ # → Prompts: "Set a default Engram? (press Enter for "johnny", or enter ID, or "n" to skip):"
64
68
 
65
69
  # List available Engrams
66
70
  relic list
@@ -68,11 +72,42 @@ relic list
68
72
  # Preview an Engram's composed prompt
69
73
  relic show motoko
70
74
 
71
- # Launch a Shell with an Engram injected
75
+ # Launch a Shell (uses default Engram if --engram is omitted)
76
+ relic claude
77
+ relic gemini
78
+ relic codex
79
+
80
+ # Or specify explicitly
72
81
  relic claude --engram motoko
73
82
  relic gemini --engram johnny
74
- relic codex --engram motoko
75
- relic copilot --engram johnny
83
+ ```
84
+
85
+ ## Sample Engrams
86
+
87
+ `relic init` seeds two ready-to-use Engrams:
88
+
89
+ ### Johnny Silverhand (`johnny`)
90
+
91
+ > *"Wake the fuck up, Samurai. We have a city to burn."*
92
+
93
+ 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.
94
+
95
+ Best for: rapid prototyping sessions, decision-making under pressure, when you need someone to challenge your assumptions hard.
96
+
97
+ ```bash
98
+ relic claude --engram johnny
99
+ ```
100
+
101
+ ### Motoko Kusanagi (`motoko`)
102
+
103
+ > *"The Net is vast and infinite."*
104
+
105
+ 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.
106
+
107
+ Best for: system design, code review, debugging sessions, when precision matters more than speed.
108
+
109
+ ```bash
110
+ relic claude --engram motoko
76
111
  ```
77
112
 
78
113
  ## Supported Shells
@@ -80,12 +115,11 @@ relic copilot --engram johnny
80
115
  | Shell | Command | Injection Method |
81
116
  |-------|---------|-----------------|
82
117
  | [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) |
118
+ | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `relic gemini` | `GEMINI_SYSTEM_MD` (system prompt) |
119
+ | [Codex CLI](https://github.com/openai/codex) | `relic codex` | `-c developer_instructions` (developer-role message) |
86
120
 
87
121
  All shell commands support:
88
- - `--engram <id>` (required) — Engram to inject
122
+ - `--engram <id>` — Engram to inject (optional if `defaultEngram` is configured)
89
123
  - `--path <dir>` — Override Engrams directory
90
124
  - `--cwd <dir>` — Working directory for the Shell (default: current directory)
91
125
 
@@ -93,41 +127,100 @@ Extra arguments are passed through to the underlying CLI.
93
127
 
94
128
  ## MCP Server
95
129
 
96
- Relic also runs as an [MCP](https://modelcontextprotocol.io/) server, allowing any MCP-compatible client (like Claude Desktop) to access Engrams directly.
130
+ Relic's [MCP](https://modelcontextprotocol.io/) server is paired with CLI injection to handle memory recall.
131
+ Session logs and memory entries are written automatically by a **background hook** — without going through the LLM. Memory recall, on the other hand, is performed via the MCP server.
132
+
133
+ ```
134
+ relic xxx --engram johnny → injects persona into AI CLI
135
+ relic-mcp (MCP server) → provides the Construct with memory recall
136
+ Stop hook (Claude Code) → logs each turn directly to archive, bypassing the LLM
137
+ AfterAgent hook (Gemini CLI) → logs each turn directly to archive, bypassing the LLM
138
+ Stop hook (Codex CLI) → logs each turn directly to archive, bypassing the LLM
139
+ ```
140
+
141
+ ### Setup
142
+
143
+ #### Claude Code
144
+
145
+ ```bash
146
+ claude mcp add --scope user relic -- relic-mcp
147
+ ```
148
+
149
+ To suppress confirmation dialogs and auto-approve Relic tools across all projects, add the following to `~/.claude/settings.json`:
150
+
151
+ ```json
152
+ {
153
+ "permissions": {
154
+ "allow": [
155
+ "Edit(~/.relic/engrams/**)",
156
+ "mcp__relic__relic_archive_search",
157
+ "mcp__relic__relic_archive_pending",
158
+ "mcp__relic__relic_memory_write"
159
+ ]
160
+ },
161
+ }
162
+ ```
163
+
164
+ > **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.
165
+
166
+ On the **first run** of `relic claude`, a one-time setup happens automatically:
97
167
 
98
- ### Setup (Claude Desktop)
168
+ - **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
99
169
 
100
- Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
170
+ #### Gemini CLI
171
+
172
+ Add to `~/.gemini/settings.json`:
101
173
 
102
174
  ```json
103
175
  {
104
176
  "mcpServers": {
105
177
  "relic": {
106
- "command": "relic-mcp"
178
+ "command": "relic-mcp",
179
+ "trust": true
107
180
  }
108
181
  }
109
182
  }
110
183
  ```
111
184
 
112
- Restart Claude Desktop.
185
+ > **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.
186
+
187
+ On the **first run** of `relic gemini`, two one-time setups happen automatically:
188
+
189
+ 1. **AfterAgent hook** — registers `~/.relic/hooks/gemini-after-agent.js` in `~/.gemini/settings.json` to log each conversation turn without going through the LLM
190
+ 2. **Default system prompt cache** — captures Gemini CLI's built-in system prompt to `~/.relic/gemini-system-default.md` via `GEMINI_WRITE_SYSTEM_MD`
191
+
192
+ The Engram persona is then appended to the cached default prompt and injected via `GEMINI_SYSTEM_MD` on every launch.
193
+
194
+ #### Codex CLI
195
+
196
+ ```bash
197
+ codex mcp add relic -- relic-mcp
198
+ ```
199
+
200
+ On the **first run** of `relic codex`, a one-time setup happens automatically:
201
+
202
+ - **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
203
+
204
+ > **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`:
205
+ >
206
+ > ```toml
207
+ > # Must be at the top level (not under any [section])
208
+ > suppress_unstable_features_warning = true
209
+ > ```
113
210
 
114
211
  ### Available Tools
115
212
 
116
213
  | Tool | Description |
117
214
  |------|-------------|
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 |
215
+ | `relic_archive_search` | Search the Engram's raw archive by keyword (newest-first) |
216
+ | `relic_archive_pending` | Get un-distilled archive entries since the last distillation (up to 30) |
217
+ | `relic_memory_write` | Write distilled memory to `memory/*.md`, optionally append to `MEMORY.md`, and advance the archive cursor |
218
+
219
+ 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.
127
220
 
128
221
  ## OpenClaw Integration
129
222
 
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.
223
+ 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
224
 
132
225
  ### Inject — Push an Engram into OpenClaw
133
226
 
@@ -144,7 +237,7 @@ relic inject --engram motoko --to main
144
237
  # → agents/main/agent/ receives motoko's persona
145
238
  # → extract will create Engram "main", not "motoko"
146
239
 
147
- # Specify a custom OpenClaw directory
240
+ # Override OpenClaw directory (or configure once with: relic config openclaw-path)
148
241
  relic inject --engram motoko --openclaw /path/to/.openclaw
149
242
  ```
150
243
 
@@ -162,7 +255,7 @@ relic extract --engram analyst --name "Data Analyst"
162
255
  # Overwrite persona files (memory is always merged)
163
256
  relic extract --engram motoko --force
164
257
 
165
- # Extract from a custom OpenClaw directory
258
+ # Override OpenClaw directory (or configure once with: relic config openclaw-path)
166
259
  relic extract --engram motoko --openclaw /path/to/.openclaw
167
260
  ```
168
261
 
@@ -199,20 +292,56 @@ While running:
199
292
 
200
293
  ## Memory Management
201
294
 
202
- Relic uses a **2-day sliding window** for memory entries, matching OpenClaw's approach:
295
+ Relic uses a **sliding window** for memory entries (default: 2 days), matching OpenClaw's approach:
203
296
 
204
297
  - `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
298
+ - `memory/today.md` + `memory/yesterday.md` — Always included (configurable window)
299
+ - Older entries — **Not included in the prompt**, but searchable via MCP
300
+
301
+ This keeps prompts compact while preserving full history. The Construct can recall and distill past context using MCP tools:
302
+
303
+ ```
304
+ relic_archive_search → keyword search across the full raw archive (all sessions)
305
+ relic_archive_pending → get un-distilled entries for memory distillation
306
+ relic_memory_write → write distilled memory and advance the cursor
307
+ ```
308
+
309
+ 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.
310
+
311
+ ## Configuration
312
+
313
+ Config lives at `~/.relic/config.json` and is managed via `relic config`:
314
+
315
+ ```bash
316
+ # Show current configuration
317
+ relic config show
318
+
319
+ # Default Engram — used when --engram is omitted
320
+ relic config default-engram # get
321
+ relic config default-engram johnny # set
207
322
 
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:
323
+ # OpenClaw directory used by inject/extract/sync when --openclaw is omitted
324
+ relic config openclaw-path # get
325
+ relic config openclaw-path ~/.openclaw # set
209
326
 
327
+ # Memory window — number of recent memory entries included in the prompt
328
+ relic config memory-window # get (default: 2)
329
+ relic config memory-window 5 # set
210
330
  ```
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
331
+
332
+ `config.json` example:
333
+
334
+ ```json
335
+ {
336
+ "engramsPath": "/home/user/.relic/engrams",
337
+ "defaultEngram": "johnny",
338
+ "openclawPath": "/home/user/.openclaw",
339
+ "memoryWindowSize": 2
340
+ }
214
341
  ```
215
342
 
343
+ CLI flags always take precedence over config values.
344
+
216
345
  ## Creating Your Own Engram
217
346
 
218
347
  Create a directory under `~/.relic/engrams/` with the following structure:
@@ -258,18 +387,11 @@ Never over-engineer. Always ask "what's the simplest thing that works?"
258
387
  - Creed: "Boring technology wins."
259
388
  ```
260
389
 
261
- ## Configuration
262
-
263
- Config lives at `~/.relic/config.json`:
264
-
265
- ```json
266
- {
267
- "engramsPath": "/home/user/.relic/engrams"
268
- }
390
+ After creating the directory, set it as default:
391
+ ```bash
392
+ relic config default-engram your-persona
269
393
  ```
270
394
 
271
- Priority: CLI `--path` flag > config file > default (`~/.relic/engrams`)
272
-
273
395
  ## Architecture
274
396
 
275
397
  Clean Architecture with dependency inversion:
@@ -282,7 +404,7 @@ src/
282
404
  │ └── ports/ # Abstract interfaces (EngramRepository, ShellLauncher)
283
405
  ├── adapters/ # Concrete implementations
284
406
  │ ├── local/ # Local filesystem EngramRepository
285
- │ └── shells/ # Claude, Gemini, Codex, Copilot launchers
407
+ │ └── shells/ # Claude, Gemini, Codex launchers
286
408
  ├── interfaces/ # Entry points
287
409
  │ ├── cli/ # Commander-based CLI
288
410
  │ └── mcp/ # MCP Server (stdio transport)
@@ -302,10 +424,11 @@ src/
302
424
  ## Roadmap
303
425
 
304
426
  - [x] CLI with init, list, show commands
305
- - [x] Shell injection: Claude Code, Gemini CLI, Codex CLI, Copilot CLI
427
+ - [x] Shell injection: Claude Code, Gemini CLI, Codex CLI
306
428
  - [x] MCP Server interface
307
429
  - [x] OpenClaw integration (inject / extract)
308
430
  - [x] `relic sync` — watch OpenClaw agents and auto-sync (`--cloud` for Mikoshi: planned)
431
+ - [x] `relic config` — manage default Engram, OpenClaw path, memory window
309
432
  - [ ] `relic login` — authenticate with Mikoshi (OAuth Device Flow)
310
433
  - [ ] `relic push` / `relic pull` — sync Engrams with Mikoshi
311
434
  - [ ] Mikoshi cloud backend (`mikoshi.ectplsm.com`)
@@ -0,0 +1,12 @@
1
+ export declare const CLAUDE_HOOK_SCRIPT_PATH: string;
2
+ /**
3
+ * Claude Code の Stop フックをセットアップする。
4
+ * - ~/.relic/hooks/claude-stop.js を生成
5
+ * - ~/.claude/settings.json に Stop フックを登録
6
+ * 既にセットアップ済みの場合はスキップ。
7
+ */
8
+ export declare function setupClaudeHook(): void;
9
+ /**
10
+ * Stop フックがセットアップ済みか確認する。
11
+ */
12
+ export declare function isClaudeHookSetup(): boolean;
@@ -0,0 +1,160 @@
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, readFileSync } = require("node:fs");
20
+ const { join } = 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
+ if (!existsSync(archivePath)) process.exit(0);
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
+ * Claude Code の Stop フックをセットアップする。
103
+ * - ~/.relic/hooks/claude-stop.js を生成
104
+ * - ~/.claude/settings.json に Stop フックを登録
105
+ * 既にセットアップ済みの場合はスキップ。
106
+ */
107
+ export function setupClaudeHook() {
108
+ // 1. フックスクリプトを生成
109
+ mkdirSync(HOOKS_DIR, { recursive: true });
110
+ writeFileSync(CLAUDE_HOOK_SCRIPT_PATH, HOOK_SCRIPT, { encoding: "utf-8", mode: 0o755 });
111
+ // 2. ~/.claude/settings.json に Stop フックを登録
112
+ const claudeDir = join(homedir(), ".claude");
113
+ mkdirSync(claudeDir, { recursive: true });
114
+ let settings = {};
115
+ if (existsSync(CLAUDE_SETTINGS_PATH)) {
116
+ try {
117
+ settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
118
+ }
119
+ catch {
120
+ settings = {};
121
+ }
122
+ }
123
+ const hooks = (settings.hooks ?? {});
124
+ const stopHooks = (hooks.Stop ?? []);
125
+ // 既に登録済みならスキップ
126
+ const alreadyRegistered = stopHooks.some((group) => group.hooks?.some((h) => h.command === RELIC_HOOK_COMMAND));
127
+ if (alreadyRegistered)
128
+ return;
129
+ hooks.Stop = [
130
+ ...stopHooks,
131
+ {
132
+ hooks: [
133
+ {
134
+ type: "command",
135
+ command: RELIC_HOOK_COMMAND,
136
+ timeout: 5000,
137
+ },
138
+ ],
139
+ },
140
+ ];
141
+ settings.hooks = hooks;
142
+ writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
143
+ }
144
+ /**
145
+ * Stop フックがセットアップ済みか確認する。
146
+ */
147
+ export function isClaudeHookSetup() {
148
+ if (!existsSync(CLAUDE_HOOK_SCRIPT_PATH))
149
+ return false;
150
+ if (!existsSync(CLAUDE_SETTINGS_PATH))
151
+ return false;
152
+ try {
153
+ const settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
154
+ const stopHooks = settings.hooks?.Stop ?? [];
155
+ return stopHooks.some((group) => group.hooks?.some((h) => h.command === RELIC_HOOK_COMMAND));
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
@@ -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
  }
@@ -1,10 +1,14 @@
1
1
  import { exec } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  import { spawnShell } from "./spawn-shell.js";
4
+ import { setupClaudeHook, isClaudeHookSetup } from "./claude-hook.js";
4
5
  const execAsync = promisify(exec);
5
6
  /**
6
7
  * Claude Code CLI アダプター
7
8
  * --system-prompt フラグでEngramを直接注入する。
9
+ *
10
+ * 初回起動時に Stop フックを ~/.claude/settings.json に登録し、
11
+ * 各ターン終了後に会話ログを Engram archive に自動記録する。
8
12
  */
9
13
  export class ClaudeShell {
10
14
  command;
@@ -22,12 +26,22 @@ export class ClaudeShell {
22
26
  return false;
23
27
  }
24
28
  }
25
- async launch(prompt, extraArgs = [], cwd) {
29
+ async launch(prompt, options) {
30
+ // Stop フックを初回のみセットアップ
31
+ if (!isClaudeHookSetup()) {
32
+ console.log("Setting up Claude Code Stop hook (first run only)...");
33
+ setupClaudeHook();
34
+ console.log("Hook registered to ~/.claude/settings.json");
35
+ console.log();
36
+ }
26
37
  const args = [
27
38
  "--system-prompt",
28
39
  prompt,
29
- ...extraArgs,
40
+ ...(options?.extraArgs ?? []),
30
41
  ];
31
- await spawnShell(this.command, args, cwd);
42
+ const env = {};
43
+ if (options?.engramId)
44
+ env.RELIC_ENGRAM_ID = options.engramId;
45
+ await spawnShell(this.command, args, options?.cwd, Object.keys(env).length > 0 ? env : undefined);
32
46
  }
33
47
  }
@@ -0,0 +1,12 @@
1
+ export declare const CODEX_HOOK_SCRIPT_PATH: string;
2
+ /**
3
+ * Codex CLI の Stop フックをセットアップする。
4
+ * - ~/.relic/hooks/codex-stop.js を生成
5
+ * - ~/.codex/hooks.json に Stop フックを登録
6
+ * 既にセットアップ済みの場合はスキップ。
7
+ */
8
+ export declare function setupCodexHook(): void;
9
+ /**
10
+ * Stop フックがセットアップ済みか確認する。
11
+ */
12
+ export declare function isCodexHookSetup(): boolean;