@ectplsm/relic 0.1.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.
Files changed (73) hide show
  1. package/LICENCE.md +21 -0
  2. package/README.md +324 -0
  3. package/dist/adapters/local/index.d.ts +1 -0
  4. package/dist/adapters/local/index.js +1 -0
  5. package/dist/adapters/local/local-engram-repository.d.ts +28 -0
  6. package/dist/adapters/local/local-engram-repository.js +130 -0
  7. package/dist/adapters/shells/claude-shell.d.ts +13 -0
  8. package/dist/adapters/shells/claude-shell.js +33 -0
  9. package/dist/adapters/shells/codex-shell.d.ts +14 -0
  10. package/dist/adapters/shells/codex-shell.js +34 -0
  11. package/dist/adapters/shells/copilot-shell.d.ts +14 -0
  12. package/dist/adapters/shells/copilot-shell.js +43 -0
  13. package/dist/adapters/shells/gemini-shell.d.ts +14 -0
  14. package/dist/adapters/shells/gemini-shell.js +43 -0
  15. package/dist/adapters/shells/index.d.ts +4 -0
  16. package/dist/adapters/shells/index.js +4 -0
  17. package/dist/adapters/shells/override-preamble.d.ts +8 -0
  18. package/dist/adapters/shells/override-preamble.js +19 -0
  19. package/dist/adapters/shells/spawn-shell.d.ts +16 -0
  20. package/dist/adapters/shells/spawn-shell.js +47 -0
  21. package/dist/core/entities/engram.d.ts +180 -0
  22. package/dist/core/entities/engram.js +67 -0
  23. package/dist/core/entities/index.d.ts +1 -0
  24. package/dist/core/entities/index.js +1 -0
  25. package/dist/core/ports/engram-repository.d.ts +17 -0
  26. package/dist/core/ports/engram-repository.js +1 -0
  27. package/dist/core/ports/index.d.ts +2 -0
  28. package/dist/core/ports/index.js +1 -0
  29. package/dist/core/ports/shell-launcher.d.ts +27 -0
  30. package/dist/core/ports/shell-launcher.js +1 -0
  31. package/dist/core/usecases/extract.d.ts +49 -0
  32. package/dist/core/usecases/extract.js +190 -0
  33. package/dist/core/usecases/index.d.ts +8 -0
  34. package/dist/core/usecases/index.js +8 -0
  35. package/dist/core/usecases/init.d.ts +19 -0
  36. package/dist/core/usecases/init.js +19 -0
  37. package/dist/core/usecases/inject.d.ts +28 -0
  38. package/dist/core/usecases/inject.js +57 -0
  39. package/dist/core/usecases/list-engrams.d.ts +10 -0
  40. package/dist/core/usecases/list-engrams.js +12 -0
  41. package/dist/core/usecases/memory-search.d.ts +23 -0
  42. package/dist/core/usecases/memory-search.js +59 -0
  43. package/dist/core/usecases/memory-write.d.ts +20 -0
  44. package/dist/core/usecases/memory-write.js +47 -0
  45. package/dist/core/usecases/summon.d.ts +23 -0
  46. package/dist/core/usecases/summon.js +31 -0
  47. package/dist/core/usecases/sync.d.ts +40 -0
  48. package/dist/core/usecases/sync.js +94 -0
  49. package/dist/interfaces/cli/commands/extract.d.ts +2 -0
  50. package/dist/interfaces/cli/commands/extract.js +41 -0
  51. package/dist/interfaces/cli/commands/init.d.ts +2 -0
  52. package/dist/interfaces/cli/commands/init.js +19 -0
  53. package/dist/interfaces/cli/commands/inject.d.ts +2 -0
  54. package/dist/interfaces/cli/commands/inject.js +33 -0
  55. package/dist/interfaces/cli/commands/list.d.ts +2 -0
  56. package/dist/interfaces/cli/commands/list.js +30 -0
  57. package/dist/interfaces/cli/commands/shell.d.ts +2 -0
  58. package/dist/interfaces/cli/commands/shell.js +69 -0
  59. package/dist/interfaces/cli/commands/show.d.ts +2 -0
  60. package/dist/interfaces/cli/commands/show.js +26 -0
  61. package/dist/interfaces/cli/commands/sync.d.ts +2 -0
  62. package/dist/interfaces/cli/commands/sync.js +91 -0
  63. package/dist/interfaces/cli/index.d.ts +2 -0
  64. package/dist/interfaces/cli/index.js +22 -0
  65. package/dist/interfaces/mcp/index.d.ts +2 -0
  66. package/dist/interfaces/mcp/index.js +418 -0
  67. package/dist/shared/config.d.ts +37 -0
  68. package/dist/shared/config.js +122 -0
  69. package/dist/shared/engram-composer.d.ts +18 -0
  70. package/dist/shared/engram-composer.js +48 -0
  71. package/dist/shared/openclaw.d.ts +9 -0
  72. package/dist/shared/openclaw.js +21 -0
  73. package/package.json +44 -0
package/LICENCE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2026 ectplsm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,324 @@
1
+ | English | [日本語](README-ja.md) |
2
+ |:---:|:---:|
3
+
4
+ # PROJECT RELIC
5
+
6
+ ```
7
+ ____ ________ ____________
8
+ / __ \/ ____/ / / _/ ____/
9
+ / /_/ / __/ / / / // /
10
+ / _, _/ /___/ /____/ // /___
11
+ /_/ |_/_____/_____/___/\____/
12
+ ```
13
+
14
+ **Inject AI personas into any coding CLI.**
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.
17
+
18
+ ```bash
19
+ # Initialize Relic (creates ~/.relic/ with sample Engrams)
20
+ relic init
21
+
22
+ # Launch Claude Code as Johnny Silverhand
23
+ relic claude --engram johnny
24
+
25
+ # Launch Gemini CLI as Motoko Kusanagi
26
+ relic gemini --engram motoko
27
+ ```
28
+
29
+ ## How It Works
30
+
31
+ ```
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
45
+ ```
46
+
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).
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ git clone https://github.com/ectplsm/relic.git
57
+ cd relic
58
+ npm install
59
+ npm link
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ```bash
65
+ # Initialize — creates config and sample Engrams
66
+ relic init
67
+
68
+ # List available Engrams
69
+ relic list
70
+
71
+ # Preview an Engram's composed prompt
72
+ relic show motoko
73
+
74
+ # Launch a Shell with an Engram injected
75
+ relic claude --engram motoko
76
+ relic gemini --engram johnny
77
+ relic codex --engram motoko
78
+ relic copilot --engram johnny
79
+ ```
80
+
81
+ ## Supported Shells
82
+
83
+ | Shell | Command | Injection Method |
84
+ |-------|---------|-----------------|
85
+ | [Claude Code](https://github.com/anthropics/claude-code) | `relic claude` | `--system-prompt` (direct override) |
86
+ | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | `relic gemini` | `--prompt-interactive` (first message) |
87
+ | [Codex CLI](https://github.com/openai/codex) | `relic codex` | `PROMPT` argument (first message) |
88
+ | [Copilot CLI](https://github.com/github/copilot-cli) | `relic copilot` | `--interactive` (first message) |
89
+
90
+ All shell commands support:
91
+ - `--engram <id>` (required) — Engram to inject
92
+ - `--path <dir>` — Override Engrams directory
93
+ - `--cwd <dir>` — Working directory for the Shell (default: current directory)
94
+
95
+ Extra arguments are passed through to the underlying CLI.
96
+
97
+ ## MCP Server
98
+
99
+ Relic also runs as an [MCP](https://modelcontextprotocol.io/) server, allowing any MCP-compatible client (like Claude Desktop) to access Engrams directly.
100
+
101
+ ### Setup (Claude Desktop)
102
+
103
+ 1. Build the project:
104
+ ```bash
105
+ npm run build
106
+ ```
107
+
108
+ 2. Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
109
+ ```json
110
+ {
111
+ "mcpServers": {
112
+ "relic": {
113
+ "command": "node",
114
+ "args": ["/path/to/relic/dist/interfaces/mcp/index.js"]
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ 3. Restart Claude Desktop.
121
+
122
+ ### Available Tools
123
+
124
+ | Tool | Description |
125
+ |------|-------------|
126
+ | `relic_init` | Initialize `~/.relic/` with config and sample Engrams |
127
+ | `relic_list` | List all available Engrams |
128
+ | `relic_show` | Preview an Engram's composed prompt |
129
+ | `relic_summon` | Summon an Engram and return the persona prompt for injection |
130
+ | `relic_inject` | Inject an Engram into an OpenClaw workspace |
131
+ | `relic_extract` | Extract an Engram from an OpenClaw workspace |
132
+ | `relic_memory_search` | Search an Engram's memory entries by keyword |
133
+ | `relic_memory_get` | Get a specific memory entry by date |
134
+ | `relic_memory_list` | List all memory entry dates for an Engram |
135
+
136
+ ## OpenClaw Integration
137
+
138
+ 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.
139
+
140
+ ### Inject — Push an Engram into OpenClaw
141
+
142
+ Injects persona files (SOUL.md, IDENTITY.md, etc.) into `agents/<engramId>/agent/`. Memory entries are **not** injected — they are managed by OpenClaw independently.
143
+
144
+ > **Note:** The OpenClaw agent must already exist. Inject writes persona files into an existing agent directory — it does not create new agents. Create the agent in OpenClaw first, then inject.
145
+
146
+ ```bash
147
+ # Inject Engram "motoko" → agents/motoko/agent/
148
+ relic inject --engram motoko
149
+
150
+ # Inject into a differently-named agent (one-way copy)
151
+ relic inject --engram motoko --to main
152
+ # → agents/main/agent/ receives motoko's persona
153
+ # → extract will create Engram "main", not "motoko"
154
+
155
+ # Specify a custom OpenClaw directory
156
+ relic inject --engram motoko --openclaw /path/to/.openclaw
157
+ ```
158
+
159
+ ### Extract — Sync memory from OpenClaw
160
+
161
+ Reads from `agents/<engramId>/agent/` and merges memory entries back to the Relic Engram.
162
+
163
+ ```bash
164
+ # Extract memory from agent "motoko" → merge into Engram "motoko"
165
+ relic extract --engram motoko
166
+
167
+ # New agent with no existing Engram (--name required)
168
+ relic extract --engram analyst --name "Data Analyst"
169
+
170
+ # Overwrite persona files (memory is always merged)
171
+ relic extract --engram motoko --force
172
+
173
+ # Extract from a custom OpenClaw directory
174
+ relic extract --engram motoko --openclaw /path/to/.openclaw
175
+ ```
176
+
177
+ ### Sync — Watch and auto-sync
178
+
179
+ Watches all agents under `~/.openclaw/agents/` and automatically syncs:
180
+
181
+ ```bash
182
+ # Start watching (Ctrl+C to stop)
183
+ relic sync
184
+
185
+ # Specify a custom OpenClaw directory
186
+ relic sync --openclaw /path/to/.openclaw
187
+ ```
188
+
189
+ On startup:
190
+ 1. Injects persona files for all agents that have a matching Engram
191
+ 2. Extracts memory entries from all agents
192
+
193
+ While running:
194
+ - Watches each agent's `memory/` directory for changes
195
+ - Automatically merges new memory entries into the corresponding Engram
196
+
197
+ ### Memory Sync Behavior
198
+
199
+ | Scenario | Persona (SOUL, IDENTITY...) | Memory entries |
200
+ |----------|---------------------------|----------------|
201
+ | **inject** | Relic → OpenClaw (overwrite) | Not copied (OpenClaw manages its own) |
202
+ | **extract** (existing Engram) | Not touched | OpenClaw → Relic (append) |
203
+ | **extract** + `--force` | OpenClaw → Relic (overwrite) | OpenClaw → Relic (append) |
204
+ | **extract** (new Engram) | Created from OpenClaw | Created from OpenClaw |
205
+ | **sync** (startup) | inject for matching Engrams | extract all |
206
+ | **sync** (watching) | — | Auto-extract on change |
207
+
208
+ ## Memory Management
209
+
210
+ Relic uses a **2-day sliding window** for memory entries, matching OpenClaw's approach:
211
+
212
+ - `MEMORY.md` — Always included in the prompt (curated long-term memory)
213
+ - `memory/today.md` + `memory/yesterday.md` — Always included
214
+ - Older entries — **Not included in the prompt**, but accessible via MCP tools
215
+
216
+ 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:
217
+
218
+ ```
219
+ relic_memory_search → keyword search across all entries
220
+ relic_memory_get → fetch a specific date's entry
221
+ relic_memory_list → list all available dates
222
+ ```
223
+
224
+ ## Creating Your Own Engram
225
+
226
+ Create a directory under `~/.relic/engrams/` with the following structure:
227
+
228
+ ```
229
+ ~/.relic/engrams/your-persona/
230
+ ├── engram.json # Metadata (id, name, description, tags)
231
+ ├── SOUL.md # Core directive — how the persona thinks and acts
232
+ ├── IDENTITY.md # Name, tone, background, personality
233
+ ├── AGENTS.md # (optional) Tool usage policies
234
+ ├── USER.md # (optional) User context
235
+ ├── MEMORY.md # (optional) Memory index
236
+ ├── HEARTBEAT.md # (optional) Periodic reflection
237
+ └── memory/ # (optional) Dated memory entries
238
+ └── 2026-03-21.md
239
+ ```
240
+
241
+ **engram.json:**
242
+ ```json
243
+ {
244
+ "id": "your-persona",
245
+ "name": "Display Name",
246
+ "description": "A short description",
247
+ "createdAt": "2026-03-21T00:00:00Z",
248
+ "updatedAt": "2026-03-21T00:00:00Z",
249
+ "tags": ["custom"]
250
+ }
251
+ ```
252
+
253
+ **SOUL.md** — The most important file. Defines how the persona behaves:
254
+ ```markdown
255
+ You are a pragmatic systems architect who values simplicity above all.
256
+ Never over-engineer. Always ask "what's the simplest thing that works?"
257
+ ```
258
+
259
+ **IDENTITY.md** — Defines who the persona is:
260
+ ```markdown
261
+ # Identity
262
+
263
+ - Name: Alex
264
+ - Tone: Calm, thoughtful, occasionally playful
265
+ - Background: 20 years of distributed systems experience
266
+ - Creed: "Boring technology wins."
267
+ ```
268
+
269
+ ## Configuration
270
+
271
+ Config lives at `~/.relic/config.json`:
272
+
273
+ ```json
274
+ {
275
+ "engramsPath": "/home/user/.relic/engrams"
276
+ }
277
+ ```
278
+
279
+ Priority: CLI `--path` flag > config file > default (`~/.relic/engrams`)
280
+
281
+ ## Architecture
282
+
283
+ Clean Architecture with dependency inversion:
284
+
285
+ ```
286
+ src/
287
+ ├── core/ # Business logic (no external deps except Zod)
288
+ │ ├── entities/ # Engram, Construct domain models
289
+ │ ├── usecases/ # Summon, ListEngrams, Init
290
+ │ └── ports/ # Abstract interfaces (EngramRepository, ShellLauncher)
291
+ ├── adapters/ # Concrete implementations
292
+ │ ├── local/ # Local filesystem EngramRepository
293
+ │ └── shells/ # Claude, Gemini, Codex, Copilot launchers
294
+ ├── interfaces/ # Entry points
295
+ │ ├── cli/ # Commander-based CLI
296
+ │ └── mcp/ # MCP Server (stdio transport)
297
+ └── shared/ # Engram composer, config management
298
+ ```
299
+
300
+ ## Domain Glossary
301
+
302
+ | Term | Role | Description |
303
+ |------|------|-------------|
304
+ | **Relic** | Injector | The core system. Adapts personas to any AI interface. |
305
+ | **Mikoshi** | Backend | Cloud fortress where all Engrams are stored (planned). |
306
+ | **Engram** | Data | A persona dataset — a set of Markdown files. |
307
+ | **Shell** | LLM | An AI CLI (Claude, Gemini, etc). A vessel with pure compute. |
308
+ | **Construct** | Process | A live process where an Engram is loaded into a Shell. |
309
+
310
+ ## Roadmap
311
+
312
+ - [x] CLI with init, list, show commands
313
+ - [x] Shell injection: Claude Code, Gemini CLI, Codex CLI, Copilot CLI
314
+ - [x] MCP Server interface
315
+ - [x] OpenClaw integration (inject / extract)
316
+ - [x] `relic sync` — watch OpenClaw agents and auto-sync (`--cloud` for Mikoshi: planned)
317
+ - [ ] `relic login` — authenticate with Mikoshi (OAuth Device Flow)
318
+ - [ ] `relic push` / `relic pull` — sync Engrams with Mikoshi
319
+ - [ ] Mikoshi cloud backend (`mikoshi.ectplsm.com`)
320
+ - [ ] `relic create` — interactive Engram creation wizard
321
+
322
+ ## License
323
+
324
+ [MIT](./LICENCE.md)
@@ -0,0 +1 @@
1
+ export { LocalEngramRepository } from "./local-engram-repository.js";
@@ -0,0 +1 @@
1
+ export { LocalEngramRepository } from "./local-engram-repository.js";
@@ -0,0 +1,28 @@
1
+ import type { Engram, EngramMeta } from "../../core/entities/engram.js";
2
+ import type { EngramRepository } from "../../core/ports/engram-repository.js";
3
+ /**
4
+ * LocalEngramRepository — ローカルファイルシステム上の
5
+ * OpenClaw互換ディレクトリからEngramを読み書きする。
6
+ *
7
+ * ディレクトリ構造:
8
+ * {basePath}/{engramId}/
9
+ * ├── engram.json (メタデータ)
10
+ * ├── SOUL.md
11
+ * ├── IDENTITY.md
12
+ * ├── AGENTS.md (optional)
13
+ * ├── USER.md (optional)
14
+ * ├── MEMORY.md (optional)
15
+ * ├── HEARTBEAT.md (optional)
16
+ * └── memory/ (optional)
17
+ * └── YYYY-MM-DD.md
18
+ */
19
+ export declare class LocalEngramRepository implements EngramRepository {
20
+ private readonly basePath;
21
+ constructor(basePath: string);
22
+ list(): Promise<EngramMeta[]>;
23
+ get(id: string): Promise<Engram | null>;
24
+ save(engram: Engram): Promise<void>;
25
+ delete(id: string): Promise<void>;
26
+ private readMeta;
27
+ private readFiles;
28
+ }
@@ -0,0 +1,130 @@
1
+ import { readdir, readFile, writeFile, mkdir, rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { EngramMetaSchema } from "../../core/entities/engram.js";
5
+ /**
6
+ * OpenClaw互換のファイル名マッピング
7
+ * EngramFiles のキー → 実ファイル名
8
+ */
9
+ const FILE_MAP = {
10
+ soul: "SOUL.md",
11
+ identity: "IDENTITY.md",
12
+ agents: "AGENTS.md",
13
+ user: "USER.md",
14
+ memory: "MEMORY.md",
15
+ heartbeat: "HEARTBEAT.md",
16
+ };
17
+ const META_FILE = "engram.json";
18
+ const MEMORY_DIR = "memory";
19
+ /**
20
+ * LocalEngramRepository — ローカルファイルシステム上の
21
+ * OpenClaw互換ディレクトリからEngramを読み書きする。
22
+ *
23
+ * ディレクトリ構造:
24
+ * {basePath}/{engramId}/
25
+ * ├── engram.json (メタデータ)
26
+ * ├── SOUL.md
27
+ * ├── IDENTITY.md
28
+ * ├── AGENTS.md (optional)
29
+ * ├── USER.md (optional)
30
+ * ├── MEMORY.md (optional)
31
+ * ├── HEARTBEAT.md (optional)
32
+ * └── memory/ (optional)
33
+ * └── YYYY-MM-DD.md
34
+ */
35
+ export class LocalEngramRepository {
36
+ basePath;
37
+ constructor(basePath) {
38
+ this.basePath = basePath;
39
+ }
40
+ async list() {
41
+ if (!existsSync(this.basePath)) {
42
+ return [];
43
+ }
44
+ const entries = await readdir(this.basePath, { withFileTypes: true });
45
+ const dirs = entries.filter((e) => e.isDirectory());
46
+ const metas = [];
47
+ for (const dir of dirs) {
48
+ const metaPath = join(this.basePath, dir.name, META_FILE);
49
+ if (!existsSync(metaPath))
50
+ continue;
51
+ const raw = await readFile(metaPath, "utf-8");
52
+ const parsed = EngramMetaSchema.safeParse(JSON.parse(raw));
53
+ if (parsed.success) {
54
+ metas.push(parsed.data);
55
+ }
56
+ }
57
+ return metas;
58
+ }
59
+ async get(id) {
60
+ const engramDir = join(this.basePath, id);
61
+ if (!existsSync(engramDir)) {
62
+ return null;
63
+ }
64
+ const meta = await this.readMeta(engramDir);
65
+ if (!meta)
66
+ return null;
67
+ const files = await this.readFiles(engramDir);
68
+ return { meta, files };
69
+ }
70
+ async save(engram) {
71
+ const engramDir = join(this.basePath, engram.meta.id);
72
+ await mkdir(engramDir, { recursive: true });
73
+ // メタデータ書き込み
74
+ await writeFile(join(engramDir, META_FILE), JSON.stringify(engram.meta, null, 2), "utf-8");
75
+ // 必須ファイル書き込み
76
+ for (const [key, filename] of Object.entries(FILE_MAP)) {
77
+ const content = engram.files[key];
78
+ if (content !== undefined) {
79
+ await writeFile(join(engramDir, filename), content, "utf-8");
80
+ }
81
+ }
82
+ // memory/ エントリ書き込み
83
+ if (engram.files.memoryEntries) {
84
+ const memoryDir = join(engramDir, MEMORY_DIR);
85
+ await mkdir(memoryDir, { recursive: true });
86
+ for (const [date, content] of Object.entries(engram.files.memoryEntries)) {
87
+ await writeFile(join(memoryDir, `${date}.md`), content, "utf-8");
88
+ }
89
+ }
90
+ }
91
+ async delete(id) {
92
+ const engramDir = join(this.basePath, id);
93
+ if (existsSync(engramDir)) {
94
+ await rm(engramDir, { recursive: true });
95
+ }
96
+ }
97
+ // --- private ---
98
+ async readMeta(engramDir) {
99
+ const metaPath = join(engramDir, META_FILE);
100
+ if (!existsSync(metaPath))
101
+ return null;
102
+ const raw = await readFile(metaPath, "utf-8");
103
+ const parsed = EngramMetaSchema.safeParse(JSON.parse(raw));
104
+ return parsed.success ? parsed.data : null;
105
+ }
106
+ async readFiles(engramDir) {
107
+ const files = {};
108
+ // 各Markdownファイルを読み込み
109
+ for (const [key, filename] of Object.entries(FILE_MAP)) {
110
+ const filePath = join(engramDir, filename);
111
+ if (existsSync(filePath)) {
112
+ files[key] = await readFile(filePath, "utf-8");
113
+ }
114
+ }
115
+ // memory/ ディレクトリの読み込み
116
+ const memoryDir = join(engramDir, MEMORY_DIR);
117
+ if (existsSync(memoryDir)) {
118
+ const memEntries = await readdir(memoryDir);
119
+ const mdFiles = memEntries.filter((f) => f.endsWith(".md")).sort();
120
+ if (mdFiles.length > 0) {
121
+ files.memoryEntries = {};
122
+ for (const mdFile of mdFiles) {
123
+ const date = mdFile.replace(/\.md$/, "");
124
+ files.memoryEntries[date] = await readFile(join(memoryDir, mdFile), "utf-8");
125
+ }
126
+ }
127
+ }
128
+ return files;
129
+ }
130
+ }
@@ -0,0 +1,13 @@
1
+ import type { ShellLauncher, InjectionMode } from "../../core/ports/shell-launcher.js";
2
+ /**
3
+ * Claude Code CLI アダプター
4
+ * --system-prompt フラグでEngramを直接注入する。
5
+ */
6
+ export declare class ClaudeShell implements ShellLauncher {
7
+ private readonly command;
8
+ readonly name = "Claude Code";
9
+ readonly injectionMode: InjectionMode;
10
+ constructor(command?: string);
11
+ isAvailable(): Promise<boolean>;
12
+ launch(prompt: string, extraArgs?: string[], cwd?: string): Promise<void>;
13
+ }
@@ -0,0 +1,33 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { spawnShell } from "./spawn-shell.js";
4
+ const execAsync = promisify(exec);
5
+ /**
6
+ * Claude Code CLI アダプター
7
+ * --system-prompt フラグでEngramを直接注入する。
8
+ */
9
+ export class ClaudeShell {
10
+ command;
11
+ name = "Claude Code";
12
+ injectionMode = "system-prompt";
13
+ constructor(command = "claude") {
14
+ this.command = command;
15
+ }
16
+ async isAvailable() {
17
+ try {
18
+ await execAsync(`which ${this.command}`);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ async launch(prompt, extraArgs = [], cwd) {
26
+ const args = [
27
+ "--system-prompt",
28
+ prompt,
29
+ ...extraArgs,
30
+ ];
31
+ await spawnShell(this.command, args, cwd);
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ import type { ShellLauncher, InjectionMode } from "../../core/ports/shell-launcher.js";
2
+ /**
3
+ * Codex CLI アダプター
4
+ * [PROMPT] 引数でEngramを初回メッセージとして注入し、
5
+ * インタラクティブモードを継続する。
6
+ */
7
+ export declare class CodexShell implements ShellLauncher {
8
+ private readonly command;
9
+ readonly name = "Codex CLI";
10
+ readonly injectionMode: InjectionMode;
11
+ constructor(command?: string);
12
+ isAvailable(): Promise<boolean>;
13
+ launch(prompt: string, extraArgs?: string[], cwd?: string): Promise<void>;
14
+ }
@@ -0,0 +1,34 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { spawnShell } from "./spawn-shell.js";
4
+ import { wrapWithOverride } from "./override-preamble.js";
5
+ const execAsync = promisify(exec);
6
+ /**
7
+ * Codex CLI アダプター
8
+ * [PROMPT] 引数でEngramを初回メッセージとして注入し、
9
+ * インタラクティブモードを継続する。
10
+ */
11
+ export class CodexShell {
12
+ command;
13
+ name = "Codex CLI";
14
+ injectionMode = "user-message";
15
+ constructor(command = "codex") {
16
+ this.command = command;
17
+ }
18
+ async isAvailable() {
19
+ try {
20
+ await execAsync(`which ${this.command}`);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ async launch(prompt, extraArgs = [], cwd) {
28
+ const args = [
29
+ ...extraArgs,
30
+ wrapWithOverride(prompt),
31
+ ];
32
+ await spawnShell(this.command, args, cwd);
33
+ }
34
+ }
@@ -0,0 +1,14 @@
1
+ import type { ShellLauncher, InjectionMode } from "../../core/ports/shell-launcher.js";
2
+ /**
3
+ * GitHub Copilot CLI アダプター (copilot コマンド)
4
+ * --interactive フラグでEngramを初回プロンプトとして注入し、
5
+ * インタラクティブモードを継続する。
6
+ */
7
+ export declare class CopilotShell implements ShellLauncher {
8
+ private readonly command;
9
+ readonly name = "GitHub Copilot CLI";
10
+ readonly injectionMode: InjectionMode;
11
+ constructor(command?: string);
12
+ isAvailable(): Promise<boolean>;
13
+ launch(prompt: string, extraArgs?: string[], cwd?: string): Promise<void>;
14
+ }
@@ -0,0 +1,43 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { readFileSync } from "node:fs";
4
+ import { spawnShell, writeTempPrompt } from "./spawn-shell.js";
5
+ import { wrapWithOverride } from "./override-preamble.js";
6
+ const execAsync = promisify(exec);
7
+ /**
8
+ * GitHub Copilot CLI アダプター (copilot コマンド)
9
+ * --interactive フラグでEngramを初回プロンプトとして注入し、
10
+ * インタラクティブモードを継続する。
11
+ */
12
+ export class CopilotShell {
13
+ command;
14
+ name = "GitHub Copilot CLI";
15
+ injectionMode = "user-message";
16
+ constructor(command = "copilot") {
17
+ this.command = command;
18
+ }
19
+ async isAvailable() {
20
+ try {
21
+ await execAsync(`which ${this.command}`);
22
+ return true;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ async launch(prompt, extraArgs = [], cwd) {
29
+ const tmp = writeTempPrompt(wrapWithOverride(prompt));
30
+ try {
31
+ const fileContent = readFileSync(tmp.path, "utf-8");
32
+ const args = [
33
+ "--interactive",
34
+ fileContent,
35
+ ...extraArgs,
36
+ ];
37
+ await spawnShell(this.command, args, cwd);
38
+ }
39
+ finally {
40
+ tmp.cleanup();
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,14 @@
1
+ import type { ShellLauncher, InjectionMode } from "../../core/ports/shell-launcher.js";
2
+ /**
3
+ * Gemini CLI アダプター
4
+ * プロンプトをtempファイルに書き出し、
5
+ * --prompt-interactive で読み込んで注入する。
6
+ */
7
+ export declare class GeminiShell implements ShellLauncher {
8
+ private readonly command;
9
+ readonly name = "Gemini CLI";
10
+ readonly injectionMode: InjectionMode;
11
+ constructor(command?: string);
12
+ isAvailable(): Promise<boolean>;
13
+ launch(prompt: string, extraArgs?: string[], cwd?: string): Promise<void>;
14
+ }