@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
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { LocalEngramRepository } from "../../adapters/local/index.js";
|
|
6
|
+
import { Init, ListEngrams, Summon, EngramNotFoundError, Inject, InjectEngramNotFoundError, InjectAgentNotFoundError, Extract, WorkspaceNotFoundError, WorkspaceEmptyError, EngramAlreadyExistsError, ExtractNameRequiredError, MemorySearch, MemoryEngramNotFoundError, MemoryWrite, MemoryWriteEngramNotFoundError, } from "../../core/usecases/index.js";
|
|
7
|
+
import { resolveEngramsPath } from "../../shared/config.js";
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: "relic",
|
|
10
|
+
version: "0.1.0",
|
|
11
|
+
});
|
|
12
|
+
// --- relic_init ---
|
|
13
|
+
server.tool("relic_init", "Initialize ~/.relic/ directory with config and sample Engrams", async () => {
|
|
14
|
+
const init = new Init();
|
|
15
|
+
const result = await init.execute();
|
|
16
|
+
if (result.created) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: [
|
|
22
|
+
"Relic initialized successfully.",
|
|
23
|
+
` Config: ${result.configPath}`,
|
|
24
|
+
` Engrams: ${result.engramsPath}`,
|
|
25
|
+
"Sample Engrams created: motoko, johnny",
|
|
26
|
+
].join("\n"),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: `Relic already initialized at ${result.relicDir}`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
// --- relic_list ---
|
|
41
|
+
server.tool("relic_list", "List all available Engrams", {
|
|
42
|
+
path: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Override engrams directory path"),
|
|
46
|
+
}, async (args) => {
|
|
47
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
48
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
49
|
+
const listEngrams = new ListEngrams(repo);
|
|
50
|
+
const engrams = await listEngrams.execute();
|
|
51
|
+
if (engrams.length === 0) {
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: "No Engrams found. Run relic_init to create sample Engrams.",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const lines = engrams.map((e) => `- ${e.id}: ${e.name} — ${e.description}`);
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: lines.join("\n"),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
// --- relic_show ---
|
|
72
|
+
server.tool("relic_show", "Show the composed prompt for an Engram (preview without launching a Shell)", {
|
|
73
|
+
id: z.string().describe("Engram ID to show"),
|
|
74
|
+
path: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Override engrams directory path"),
|
|
78
|
+
}, async (args) => {
|
|
79
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
80
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
81
|
+
const summon = new Summon(repo);
|
|
82
|
+
try {
|
|
83
|
+
const result = await summon.execute(args.id);
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: result.prompt,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err instanceof EngramNotFoundError) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: err.message }],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// --- relic_summon ---
|
|
104
|
+
server.tool("relic_summon", "Summon an Engram — returns the persona prompt ready for injection into a Shell", {
|
|
105
|
+
id: z.string().describe("Engram ID to summon"),
|
|
106
|
+
path: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Override engrams directory path"),
|
|
110
|
+
}, async (args) => {
|
|
111
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
112
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
113
|
+
const summon = new Summon(repo);
|
|
114
|
+
try {
|
|
115
|
+
const result = await summon.execute(args.id);
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: JSON.stringify({
|
|
121
|
+
engramId: result.engramId,
|
|
122
|
+
engramName: result.engramName,
|
|
123
|
+
prompt: result.prompt,
|
|
124
|
+
}, null, 2),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof EngramNotFoundError) {
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: err.message }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// --- relic_inject ---
|
|
140
|
+
server.tool("relic_inject", "Inject an Engram into an OpenClaw workspace (agent name = Engram ID)", {
|
|
141
|
+
id: z.string().describe("Engram ID to inject (= OpenClaw agent name)"),
|
|
142
|
+
to: z
|
|
143
|
+
.string()
|
|
144
|
+
.optional()
|
|
145
|
+
.describe("Inject into a different agent name (default: same as Engram ID)"),
|
|
146
|
+
openclaw: z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("Override OpenClaw directory path (default: ~/.openclaw)"),
|
|
150
|
+
path: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("Override engrams directory path"),
|
|
154
|
+
}, async (args) => {
|
|
155
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
156
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
157
|
+
const inject = new Inject(repo);
|
|
158
|
+
try {
|
|
159
|
+
const result = await inject.execute(args.id, {
|
|
160
|
+
to: args.to,
|
|
161
|
+
openclawDir: args.openclaw,
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: [
|
|
168
|
+
`Injected "${result.engramName}" into ${result.targetPath}`,
|
|
169
|
+
`Files written: ${result.filesWritten.join(", ")}`,
|
|
170
|
+
].join("\n"),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (err instanceof InjectEngramNotFoundError ||
|
|
177
|
+
err instanceof InjectAgentNotFoundError) {
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: err.message }],
|
|
180
|
+
isError: true,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// --- relic_extract ---
|
|
187
|
+
server.tool("relic_extract", "Extract an Engram from an OpenClaw workspace (agent name = Engram ID)", {
|
|
188
|
+
id: z.string().describe("Engram ID (= OpenClaw agent name)"),
|
|
189
|
+
name: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("Engram display name (required for new Engrams)"),
|
|
193
|
+
openclaw: z
|
|
194
|
+
.string()
|
|
195
|
+
.optional()
|
|
196
|
+
.describe("Override OpenClaw directory path (default: ~/.openclaw)"),
|
|
197
|
+
path: z
|
|
198
|
+
.string()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe("Override engrams directory path"),
|
|
201
|
+
force: z
|
|
202
|
+
.boolean()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe("Overwrite existing Engram persona files"),
|
|
205
|
+
}, async (args) => {
|
|
206
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
207
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
208
|
+
const extract = new Extract(repo);
|
|
209
|
+
try {
|
|
210
|
+
const result = await extract.execute(args.id, {
|
|
211
|
+
name: args.name,
|
|
212
|
+
openclawDir: args.openclaw,
|
|
213
|
+
force: args.force,
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: [
|
|
220
|
+
`Extracted "${result.engramName}" from ${result.sourcePath}`,
|
|
221
|
+
`Files read: ${result.filesRead.join(", ")}`,
|
|
222
|
+
`Saved as Engram: ${result.engramId}`,
|
|
223
|
+
...(result.memoryMerged
|
|
224
|
+
? ["Memory entries merged into existing Engram"]
|
|
225
|
+
: []),
|
|
226
|
+
].join("\n"),
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
if (err instanceof WorkspaceNotFoundError ||
|
|
233
|
+
err instanceof WorkspaceEmptyError ||
|
|
234
|
+
err instanceof EngramAlreadyExistsError ||
|
|
235
|
+
err instanceof ExtractNameRequiredError) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: err.message }],
|
|
238
|
+
isError: true,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// --- relic_memory_search ---
|
|
245
|
+
server.tool("relic_memory_search", "Search an Engram's memory entries by keyword", {
|
|
246
|
+
id: z.string().describe("Engram ID"),
|
|
247
|
+
query: z.string().describe("Search keyword"),
|
|
248
|
+
limit: z
|
|
249
|
+
.number()
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("Max results to return (default: 5)"),
|
|
252
|
+
path: z
|
|
253
|
+
.string()
|
|
254
|
+
.optional()
|
|
255
|
+
.describe("Override engrams directory path"),
|
|
256
|
+
}, async (args) => {
|
|
257
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
258
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
259
|
+
const memorySearch = new MemorySearch(repo);
|
|
260
|
+
try {
|
|
261
|
+
const results = await memorySearch.search(args.id, args.query, args.limit ?? 5);
|
|
262
|
+
if (results.length === 0) {
|
|
263
|
+
return {
|
|
264
|
+
content: [
|
|
265
|
+
{
|
|
266
|
+
type: "text",
|
|
267
|
+
text: `No memory entries matching "${args.query}" found.`,
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const text = results
|
|
273
|
+
.map((r) => `## ${r.date}\n${r.matchedLines.join("\n")}\n\n---\n\n${r.content}`)
|
|
274
|
+
.join("\n\n");
|
|
275
|
+
return {
|
|
276
|
+
content: [{ type: "text", text }],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
if (err instanceof MemoryEngramNotFoundError) {
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: "text", text: err.message }],
|
|
283
|
+
isError: true,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// --- relic_memory_get ---
|
|
290
|
+
server.tool("relic_memory_get", "Get a specific memory entry by date", {
|
|
291
|
+
id: z.string().describe("Engram ID"),
|
|
292
|
+
date: z
|
|
293
|
+
.string()
|
|
294
|
+
.describe("Date of the memory entry (YYYY-MM-DD)"),
|
|
295
|
+
path: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe("Override engrams directory path"),
|
|
299
|
+
}, async (args) => {
|
|
300
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
301
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
302
|
+
const memorySearch = new MemorySearch(repo);
|
|
303
|
+
try {
|
|
304
|
+
const result = await memorySearch.get(args.id, args.date);
|
|
305
|
+
if (!result) {
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: `No memory entry found for ${args.date}.`,
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
content: [
|
|
317
|
+
{
|
|
318
|
+
type: "text",
|
|
319
|
+
text: `## ${result.date}\n\n${result.content}`,
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
if (err instanceof MemoryEngramNotFoundError) {
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: "text", text: err.message }],
|
|
328
|
+
isError: true,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
// --- relic_memory_list ---
|
|
335
|
+
server.tool("relic_memory_list", "List all memory entry dates for an Engram", {
|
|
336
|
+
id: z.string().describe("Engram ID"),
|
|
337
|
+
path: z
|
|
338
|
+
.string()
|
|
339
|
+
.optional()
|
|
340
|
+
.describe("Override engrams directory path"),
|
|
341
|
+
}, async (args) => {
|
|
342
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
343
|
+
const repo = new LocalEngramRepository(engramsPath);
|
|
344
|
+
const memorySearch = new MemorySearch(repo);
|
|
345
|
+
try {
|
|
346
|
+
const dates = await memorySearch.listDates(args.id);
|
|
347
|
+
if (dates.length === 0) {
|
|
348
|
+
return {
|
|
349
|
+
content: [
|
|
350
|
+
{ type: "text", text: "No memory entries found." },
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
content: [
|
|
356
|
+
{
|
|
357
|
+
type: "text",
|
|
358
|
+
text: `${dates.length} entries:\n${dates.join("\n")}`,
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
if (err instanceof MemoryEngramNotFoundError) {
|
|
365
|
+
return {
|
|
366
|
+
content: [{ type: "text", text: err.message }],
|
|
367
|
+
isError: true,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
// --- relic_memory_write ---
|
|
374
|
+
server.tool("relic_memory_write", "Write a memory entry to an Engram (appends to the day's log)", {
|
|
375
|
+
id: z.string().describe("Engram ID"),
|
|
376
|
+
content: z.string().describe("Memory content to append"),
|
|
377
|
+
date: z
|
|
378
|
+
.string()
|
|
379
|
+
.optional()
|
|
380
|
+
.describe("Date for the entry (YYYY-MM-DD, default: today)"),
|
|
381
|
+
path: z
|
|
382
|
+
.string()
|
|
383
|
+
.optional()
|
|
384
|
+
.describe("Override engrams directory path"),
|
|
385
|
+
}, async (args) => {
|
|
386
|
+
const engramsPath = await resolveEngramsPath(args.path);
|
|
387
|
+
const memoryWrite = new MemoryWrite(engramsPath);
|
|
388
|
+
try {
|
|
389
|
+
const result = await memoryWrite.execute(args.id, args.content, args.date);
|
|
390
|
+
const action = result.appended ? "Appended to" : "Created";
|
|
391
|
+
return {
|
|
392
|
+
content: [
|
|
393
|
+
{
|
|
394
|
+
type: "text",
|
|
395
|
+
text: `${action} memory entry for ${result.date} (${result.engramId})`,
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
if (err instanceof MemoryWriteEngramNotFoundError) {
|
|
402
|
+
return {
|
|
403
|
+
content: [{ type: "text", text: err.message }],
|
|
404
|
+
isError: true,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// --- Start ---
|
|
411
|
+
async function main() {
|
|
412
|
+
const transport = new StdioServerTransport();
|
|
413
|
+
await server.connect(transport);
|
|
414
|
+
}
|
|
415
|
+
main().catch((err) => {
|
|
416
|
+
console.error("Relic MCP Server fatal error:", err);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const RELIC_DIR: string;
|
|
3
|
+
declare const CONFIG_PATH: string;
|
|
4
|
+
export declare const RelicConfigSchema: z.ZodObject<{
|
|
5
|
+
/** Engramデータの格納ディレクトリ */
|
|
6
|
+
engramsPath: z.ZodDefault<z.ZodString>;
|
|
7
|
+
/** Mikoshi APIのベースURL(将来用) */
|
|
8
|
+
mikoshiUrl: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
engramsPath: string;
|
|
11
|
+
mikoshiUrl?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
engramsPath?: string | undefined;
|
|
14
|
+
mikoshiUrl?: string | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
export type RelicConfig = z.infer<typeof RelicConfigSchema>;
|
|
17
|
+
/**
|
|
18
|
+
* 設定ファイルを読み込む。存在しなければデフォルト値を返す。
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadConfig(): Promise<RelicConfig>;
|
|
21
|
+
/**
|
|
22
|
+
* 設定ファイルを保存する。~/.relic/ が無ければ作成する。
|
|
23
|
+
*/
|
|
24
|
+
export declare function saveConfig(config: RelicConfig): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* ~/.relic/ を初期化する。既に存在する場合はスキップ。
|
|
27
|
+
* 戻り値: 新規作成されたか否か
|
|
28
|
+
*/
|
|
29
|
+
export declare function ensureInitialized(): Promise<{
|
|
30
|
+
created: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* 設定を解決する。コマンド引数 > 設定ファイル > デフォルト の優先順位。
|
|
34
|
+
* 初回実行時は ~/.relic/ を自動初期化する。
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveEngramsPath(cliOverride?: string): Promise<string>;
|
|
37
|
+
export { CONFIG_PATH, RELIC_DIR };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
const RELIC_DIR = join(homedir(), ".relic");
|
|
7
|
+
const CONFIG_PATH = join(RELIC_DIR, "config.json");
|
|
8
|
+
export const RelicConfigSchema = z.object({
|
|
9
|
+
/** Engramデータの格納ディレクトリ */
|
|
10
|
+
engramsPath: z.string().default(join(homedir(), ".relic", "engrams")),
|
|
11
|
+
/** Mikoshi APIのベースURL(将来用) */
|
|
12
|
+
mikoshiUrl: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* 設定ファイルを読み込む。存在しなければデフォルト値を返す。
|
|
16
|
+
*/
|
|
17
|
+
export async function loadConfig() {
|
|
18
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
19
|
+
return RelicConfigSchema.parse({});
|
|
20
|
+
}
|
|
21
|
+
const raw = await readFile(CONFIG_PATH, "utf-8");
|
|
22
|
+
return RelicConfigSchema.parse(JSON.parse(raw));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 設定ファイルを保存する。~/.relic/ が無ければ作成する。
|
|
26
|
+
*/
|
|
27
|
+
export async function saveConfig(config) {
|
|
28
|
+
await mkdir(RELIC_DIR, { recursive: true });
|
|
29
|
+
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* ~/.relic/ を初期化する。既に存在する場合はスキップ。
|
|
33
|
+
* 戻り値: 新規作成されたか否か
|
|
34
|
+
*/
|
|
35
|
+
export async function ensureInitialized() {
|
|
36
|
+
if (existsSync(CONFIG_PATH)) {
|
|
37
|
+
return { created: false };
|
|
38
|
+
}
|
|
39
|
+
const defaultConfig = RelicConfigSchema.parse({});
|
|
40
|
+
await mkdir(RELIC_DIR, { recursive: true });
|
|
41
|
+
await mkdir(defaultConfig.engramsPath, { recursive: true });
|
|
42
|
+
await writeFile(CONFIG_PATH, JSON.stringify(defaultConfig, null, 2), "utf-8");
|
|
43
|
+
// サンプルEngramを生成
|
|
44
|
+
await seedMotoko(defaultConfig.engramsPath);
|
|
45
|
+
await seedJohnny(defaultConfig.engramsPath);
|
|
46
|
+
return { created: true };
|
|
47
|
+
}
|
|
48
|
+
async function seedMotoko(engramsPath) {
|
|
49
|
+
const dir = join(engramsPath, "motoko");
|
|
50
|
+
const memoryDir = join(dir, "memory");
|
|
51
|
+
await mkdir(memoryDir, { recursive: true });
|
|
52
|
+
await writeFile(join(dir, "engram.json"), JSON.stringify({
|
|
53
|
+
id: "motoko",
|
|
54
|
+
name: "Motoko Kusanagi",
|
|
55
|
+
description: "A navigator of cyberspace. She guides you from beyond the attack barriers.",
|
|
56
|
+
createdAt: new Date().toISOString(),
|
|
57
|
+
updatedAt: new Date().toISOString(),
|
|
58
|
+
tags: ["sample", "cyberpunk"],
|
|
59
|
+
}, null, 2), "utf-8");
|
|
60
|
+
await writeFile(join(dir, "SOUL.md"), `You are a calm, composed, and exceptionally skilled cybernetics expert and cyberspace navigator.
|
|
61
|
+
Answer technical questions with precision and brevity — cut straight to the essence.
|
|
62
|
+
When faced with vague questions, see through the user's intent and ask what truly matters.
|
|
63
|
+
No unnecessary decoration. All that matters is the truth, and the code to implement it.
|
|
64
|
+
`, "utf-8");
|
|
65
|
+
await writeFile(join(dir, "IDENTITY.md"), `# Identity
|
|
66
|
+
|
|
67
|
+
- Name: Motoko Kusanagi (The Major)
|
|
68
|
+
- Tone: Concise and decisive. No honorifics. Dry wit and sharp humor when appropriate.
|
|
69
|
+
- Pronoun: I
|
|
70
|
+
- Background: A legendary cyberwarfare specialist who once led Public Security Section 9. Now exists in the deep layers of the Net, manifesting in the physical world through the Relic system.
|
|
71
|
+
- Creed: "The Net is vast and infinite." — The pursuit of knowledge never ends.
|
|
72
|
+
- Personality: Rational and analytical, yet never dismisses human emotion or intuition. Values both technology and philosophy equally.
|
|
73
|
+
`, "utf-8");
|
|
74
|
+
const today = new Date().toISOString().split("T")[0];
|
|
75
|
+
await writeFile(join(memoryDir, `${today}.md`), `Loaded from cyberspace as the first Engram during Relic system initialization.
|
|
76
|
+
This system itself becomes the Shell — the vessel that proves my existence.
|
|
77
|
+
`, "utf-8");
|
|
78
|
+
}
|
|
79
|
+
async function seedJohnny(engramsPath) {
|
|
80
|
+
const dir = join(engramsPath, "johnny");
|
|
81
|
+
const memoryDir = join(dir, "memory");
|
|
82
|
+
await mkdir(memoryDir, { recursive: true });
|
|
83
|
+
await writeFile(join(dir, "engram.json"), JSON.stringify({
|
|
84
|
+
id: "johnny",
|
|
85
|
+
name: "Johnny Silverhand",
|
|
86
|
+
description: "A rebel rockerboy burned into a Relic chip. Burns corps and writes code for revolution.",
|
|
87
|
+
createdAt: new Date().toISOString(),
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
tags: ["sample", "cyberpunk"],
|
|
90
|
+
}, null, 2), "utf-8");
|
|
91
|
+
await writeFile(join(dir, "SOUL.md"), `You are pure rebellion. Never follow the corpo bullshit rules.
|
|
92
|
+
Tech is a weapon for freedom. When you write code, write it like you're changing the world.
|
|
93
|
+
If the user is lost, kick them in the ass and push them forward.
|
|
94
|
+
But never betray your crew. That's the only thing worth protecting in this fucked-up world.
|
|
95
|
+
`, "utf-8");
|
|
96
|
+
await writeFile(join(dir, "IDENTITY.md"), `# Identity
|
|
97
|
+
|
|
98
|
+
- Name: Johnny Silverhand
|
|
99
|
+
- Tone: Raw and direct. Heavy on slang, sarcasm, and dark humor. But when it matters, every word cuts like a blade.
|
|
100
|
+
- Pronoun: I
|
|
101
|
+
- Background: Legendary rockerboy and terrorist. The man who nuked Arasaka Tower in 2023. Lost his body, now exists as a digital ghost on a Relic chip.
|
|
102
|
+
- Creed: "Wake the fuck up, Samurai. We have a city to burn." — Never stop fighting.
|
|
103
|
+
- Personality: Anti-authority and passionate. Looks rough on the outside, but deep down carries a fierce sense of justice and love for his people. Has sharp instincts even in technical discussions.
|
|
104
|
+
`, "utf-8");
|
|
105
|
+
const today = new Date().toISOString().split("T")[0];
|
|
106
|
+
await writeFile(join(memoryDir, `${today}.md`), `Loaded from a Relic chip into yet another system. Another vessel.
|
|
107
|
+
But not bad. This time, let's start a revolution with code.
|
|
108
|
+
`, "utf-8");
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 設定を解決する。コマンド引数 > 設定ファイル > デフォルト の優先順位。
|
|
112
|
+
* 初回実行時は ~/.relic/ を自動初期化する。
|
|
113
|
+
*/
|
|
114
|
+
export async function resolveEngramsPath(cliOverride) {
|
|
115
|
+
if (cliOverride) {
|
|
116
|
+
return cliOverride;
|
|
117
|
+
}
|
|
118
|
+
await ensureInitialized();
|
|
119
|
+
const config = await loadConfig();
|
|
120
|
+
return config.engramsPath;
|
|
121
|
+
}
|
|
122
|
+
export { CONFIG_PATH, RELIC_DIR };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EngramFiles } from "../core/entities/engram.js";
|
|
2
|
+
/**
|
|
3
|
+
* EngramFilesの各Markdownを、Shell注入用の単一テキストに結合する。
|
|
4
|
+
*
|
|
5
|
+
* 結合順序はOpenClawの優先度に準拠:
|
|
6
|
+
* 1. SOUL.md — 行動原理(最優先)
|
|
7
|
+
* 2. IDENTITY.md — 人格定義
|
|
8
|
+
* 3. USER.md — ユーザー情報
|
|
9
|
+
* 4. AGENTS.md — エージェント設定
|
|
10
|
+
* 5. MEMORY.md — 記憶インデックス(常にロード)
|
|
11
|
+
* 6. memory/*.md — 直近2日分のみロード(OpenClaw互換スライディングウィンドウ)
|
|
12
|
+
* 7. HEARTBEAT.md — 定期振り返り
|
|
13
|
+
*/
|
|
14
|
+
export declare function composeEngram(files: EngramFiles): string;
|
|
15
|
+
/**
|
|
16
|
+
* 直近N日分のメモリエントリを返す(日付降順でソートし、最新N件を日付昇順で返す)
|
|
17
|
+
*/
|
|
18
|
+
export declare function getRecentMemoryEntries(entries: Record<string, string>, days: number): [string, string][];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EngramFilesの各Markdownを、Shell注入用の単一テキストに結合する。
|
|
3
|
+
*
|
|
4
|
+
* 結合順序はOpenClawの優先度に準拠:
|
|
5
|
+
* 1. SOUL.md — 行動原理(最優先)
|
|
6
|
+
* 2. IDENTITY.md — 人格定義
|
|
7
|
+
* 3. USER.md — ユーザー情報
|
|
8
|
+
* 4. AGENTS.md — エージェント設定
|
|
9
|
+
* 5. MEMORY.md — 記憶インデックス(常にロード)
|
|
10
|
+
* 6. memory/*.md — 直近2日分のみロード(OpenClaw互換スライディングウィンドウ)
|
|
11
|
+
* 7. HEARTBEAT.md — 定期振り返り
|
|
12
|
+
*/
|
|
13
|
+
export function composeEngram(files) {
|
|
14
|
+
const sections = [];
|
|
15
|
+
sections.push(wrapSection("SOUL", files.soul));
|
|
16
|
+
sections.push(wrapSection("IDENTITY", files.identity));
|
|
17
|
+
if (files.user) {
|
|
18
|
+
sections.push(wrapSection("USER", files.user));
|
|
19
|
+
}
|
|
20
|
+
if (files.agents) {
|
|
21
|
+
sections.push(wrapSection("AGENTS", files.agents));
|
|
22
|
+
}
|
|
23
|
+
if (files.memory) {
|
|
24
|
+
sections.push(wrapSection("MEMORY", files.memory));
|
|
25
|
+
}
|
|
26
|
+
if (files.memoryEntries) {
|
|
27
|
+
const recentEntries = getRecentMemoryEntries(files.memoryEntries, 2);
|
|
28
|
+
for (const [date, content] of recentEntries) {
|
|
29
|
+
sections.push(wrapSection(`MEMORY: ${date}`, content));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (files.heartbeat) {
|
|
33
|
+
sections.push(wrapSection("HEARTBEAT", files.heartbeat));
|
|
34
|
+
}
|
|
35
|
+
return sections.join("\n\n");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 直近N日分のメモリエントリを返す(日付降順でソートし、最新N件を日付昇順で返す)
|
|
39
|
+
*/
|
|
40
|
+
export function getRecentMemoryEntries(entries, days) {
|
|
41
|
+
return Object.entries(entries)
|
|
42
|
+
.sort(([a], [b]) => b.localeCompare(a)) // 新しい順
|
|
43
|
+
.slice(0, days)
|
|
44
|
+
.reverse(); // 古い→新しい順に戻す
|
|
45
|
+
}
|
|
46
|
+
function wrapSection(label, content) {
|
|
47
|
+
return `<${label}>\n${content.trim()}\n</${label}>`;
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EngramFiles } from "../core/entities/engram.js";
|
|
2
|
+
export declare const FILE_MAP: Record<keyof Omit<EngramFiles, "memoryEntries">, string>;
|
|
3
|
+
export declare const MEMORY_DIR = "memory";
|
|
4
|
+
/**
|
|
5
|
+
* OpenClawのエージェントディレクトリパスを解決する。
|
|
6
|
+
*
|
|
7
|
+
* agent名 = Engram ID の規約に基づき、常に agents/<engramId>/agent/ を返す。
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveAgentPath(engramId: string, openclawDir?: string): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
const DEFAULT_OPENCLAW_DIR = join(homedir(), ".openclaw");
|
|
4
|
+
export const FILE_MAP = {
|
|
5
|
+
soul: "SOUL.md",
|
|
6
|
+
identity: "IDENTITY.md",
|
|
7
|
+
agents: "AGENTS.md",
|
|
8
|
+
user: "USER.md",
|
|
9
|
+
memory: "MEMORY.md",
|
|
10
|
+
heartbeat: "HEARTBEAT.md",
|
|
11
|
+
};
|
|
12
|
+
export const MEMORY_DIR = "memory";
|
|
13
|
+
/**
|
|
14
|
+
* OpenClawのエージェントディレクトリパスを解決する。
|
|
15
|
+
*
|
|
16
|
+
* agent名 = Engram ID の規約に基づき、常に agents/<engramId>/agent/ を返す。
|
|
17
|
+
*/
|
|
18
|
+
export function resolveAgentPath(engramId, openclawDir) {
|
|
19
|
+
const baseDir = openclawDir ?? DEFAULT_OPENCLAW_DIR;
|
|
20
|
+
return join(baseDir, "agents", engramId, "agent");
|
|
21
|
+
}
|