@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.
- package/LICENCE.md +21 -0
- package/README.md +324 -0
- package/dist/adapters/local/index.d.ts +1 -0
- package/dist/adapters/local/index.js +1 -0
- package/dist/adapters/local/local-engram-repository.d.ts +28 -0
- package/dist/adapters/local/local-engram-repository.js +130 -0
- package/dist/adapters/shells/claude-shell.d.ts +13 -0
- package/dist/adapters/shells/claude-shell.js +33 -0
- package/dist/adapters/shells/codex-shell.d.ts +14 -0
- package/dist/adapters/shells/codex-shell.js +34 -0
- package/dist/adapters/shells/copilot-shell.d.ts +14 -0
- package/dist/adapters/shells/copilot-shell.js +43 -0
- package/dist/adapters/shells/gemini-shell.d.ts +14 -0
- package/dist/adapters/shells/gemini-shell.js +43 -0
- package/dist/adapters/shells/index.d.ts +4 -0
- package/dist/adapters/shells/index.js +4 -0
- package/dist/adapters/shells/override-preamble.d.ts +8 -0
- package/dist/adapters/shells/override-preamble.js +19 -0
- package/dist/adapters/shells/spawn-shell.d.ts +16 -0
- package/dist/adapters/shells/spawn-shell.js +47 -0
- package/dist/core/entities/engram.d.ts +180 -0
- package/dist/core/entities/engram.js +67 -0
- package/dist/core/entities/index.d.ts +1 -0
- package/dist/core/entities/index.js +1 -0
- package/dist/core/ports/engram-repository.d.ts +17 -0
- package/dist/core/ports/engram-repository.js +1 -0
- package/dist/core/ports/index.d.ts +2 -0
- package/dist/core/ports/index.js +1 -0
- package/dist/core/ports/shell-launcher.d.ts +27 -0
- package/dist/core/ports/shell-launcher.js +1 -0
- package/dist/core/usecases/extract.d.ts +49 -0
- package/dist/core/usecases/extract.js +190 -0
- package/dist/core/usecases/index.d.ts +8 -0
- package/dist/core/usecases/index.js +8 -0
- package/dist/core/usecases/init.d.ts +19 -0
- package/dist/core/usecases/init.js +19 -0
- package/dist/core/usecases/inject.d.ts +28 -0
- package/dist/core/usecases/inject.js +57 -0
- package/dist/core/usecases/list-engrams.d.ts +10 -0
- package/dist/core/usecases/list-engrams.js +12 -0
- package/dist/core/usecases/memory-search.d.ts +23 -0
- package/dist/core/usecases/memory-search.js +59 -0
- package/dist/core/usecases/memory-write.d.ts +20 -0
- package/dist/core/usecases/memory-write.js +47 -0
- package/dist/core/usecases/summon.d.ts +23 -0
- package/dist/core/usecases/summon.js +31 -0
- package/dist/core/usecases/sync.d.ts +40 -0
- package/dist/core/usecases/sync.js +94 -0
- package/dist/interfaces/cli/commands/extract.d.ts +2 -0
- package/dist/interfaces/cli/commands/extract.js +41 -0
- package/dist/interfaces/cli/commands/init.d.ts +2 -0
- package/dist/interfaces/cli/commands/init.js +19 -0
- package/dist/interfaces/cli/commands/inject.d.ts +2 -0
- package/dist/interfaces/cli/commands/inject.js +33 -0
- package/dist/interfaces/cli/commands/list.d.ts +2 -0
- package/dist/interfaces/cli/commands/list.js +30 -0
- package/dist/interfaces/cli/commands/shell.d.ts +2 -0
- package/dist/interfaces/cli/commands/shell.js +69 -0
- package/dist/interfaces/cli/commands/show.d.ts +2 -0
- package/dist/interfaces/cli/commands/show.js +26 -0
- package/dist/interfaces/cli/commands/sync.d.ts +2 -0
- package/dist/interfaces/cli/commands/sync.js +91 -0
- package/dist/interfaces/cli/index.d.ts +2 -0
- package/dist/interfaces/cli/index.js +22 -0
- package/dist/interfaces/mcp/index.d.ts +2 -0
- package/dist/interfaces/mcp/index.js +418 -0
- package/dist/shared/config.d.ts +37 -0
- package/dist/shared/config.js +122 -0
- package/dist/shared/engram-composer.d.ts +18 -0
- package/dist/shared/engram-composer.js +48 -0
- package/dist/shared/openclaw.d.ts +9 -0
- package/dist/shared/openclaw.js +21 -0
- 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
|
+
}
|