@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
@@ -0,0 +1,94 @@
1
+ import { join } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { Inject } from "./inject.js";
6
+ import { Extract } from "./extract.js";
7
+ const DEFAULT_OPENCLAW_DIR = join(homedir(), ".openclaw");
8
+ /**
9
+ * Sync — OpenClawのagentsディレクトリをスキャンし、
10
+ * 一致するEngramをinject / memoryをextractする。
11
+ *
12
+ * 初回スキャン後は、呼び出し側がファイル監視を行い、
13
+ * 変更検知時に syncMemory() を呼ぶ。
14
+ */
15
+ export class Sync {
16
+ repository;
17
+ inject;
18
+ extract;
19
+ constructor(repository) {
20
+ this.repository = repository;
21
+ this.inject = new Inject(repository);
22
+ this.extract = new Extract(repository);
23
+ }
24
+ /**
25
+ * OpenClawのagentsディレクトリをスキャンし、
26
+ * Engramが存在するagentにはinject、全agentからmemoryをextractする。
27
+ */
28
+ async initialSync(openclawDir) {
29
+ const baseDir = openclawDir ?? DEFAULT_OPENCLAW_DIR;
30
+ const agentsDir = join(baseDir, "agents");
31
+ if (!existsSync(agentsDir)) {
32
+ throw new SyncAgentsDirNotFoundError(agentsDir);
33
+ }
34
+ const targets = await this.scanAgents(agentsDir);
35
+ const injected = [];
36
+ const extracted = [];
37
+ for (const target of targets) {
38
+ // Engramがあるagentにはpersonaを注入
39
+ if (target.hasEngram) {
40
+ try {
41
+ await this.inject.execute(target.engramId, { openclawDir });
42
+ injected.push(target.engramId);
43
+ }
44
+ catch {
45
+ // inject失敗は警告として続行
46
+ }
47
+ }
48
+ // 全agentからmemoryを抽出
49
+ try {
50
+ await this.extract.execute(target.engramId, {
51
+ openclawDir,
52
+ });
53
+ extracted.push(target.engramId);
54
+ }
55
+ catch {
56
+ // extract失敗(新規Engramでname未指定等)はスキップ
57
+ }
58
+ }
59
+ return { injected, extracted, targets };
60
+ }
61
+ /**
62
+ * 特定agentのmemoryを同期(ファイル変更検知時に呼ばれる)
63
+ */
64
+ async syncMemory(engramId, openclawDir) {
65
+ await this.extract.execute(engramId, { openclawDir });
66
+ }
67
+ /**
68
+ * agentsディレクトリをスキャンしてターゲット一覧を返す
69
+ */
70
+ async scanAgents(agentsDir) {
71
+ const entries = await readdir(agentsDir, { withFileTypes: true });
72
+ const targets = [];
73
+ for (const entry of entries) {
74
+ if (!entry.isDirectory())
75
+ continue;
76
+ const agentPath = join(agentsDir, entry.name, "agent");
77
+ if (!existsSync(agentPath))
78
+ continue;
79
+ const engram = await this.repository.get(entry.name);
80
+ targets.push({
81
+ engramId: entry.name,
82
+ agentPath,
83
+ hasEngram: engram !== null,
84
+ });
85
+ }
86
+ return targets;
87
+ }
88
+ }
89
+ export class SyncAgentsDirNotFoundError extends Error {
90
+ constructor(path) {
91
+ super(`OpenClaw agents directory not found at ${path}`);
92
+ this.name = "SyncAgentsDirNotFoundError";
93
+ }
94
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerExtractCommand(program: Command): void;
@@ -0,0 +1,41 @@
1
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
+ import { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, EngramAlreadyExistsError, ExtractNameRequiredError, } from "../../../core/usecases/index.js";
3
+ import { resolveEngramsPath } from "../../../shared/config.js";
4
+ export function registerExtractCommand(program) {
5
+ program
6
+ .command("extract")
7
+ .description("Extract an Engram from an OpenClaw workspace")
8
+ .requiredOption("-e, --engram <id>", "Engram ID (= agent name)")
9
+ .option("--name <name>", "Engram display name (required for new Engrams)")
10
+ .option("--openclaw <dir>", "Override OpenClaw directory path (default: ~/.openclaw)")
11
+ .option("-p, --path <dir>", "Override engrams directory path")
12
+ .option("-f, --force", "Overwrite existing Engram persona files")
13
+ .action(async (opts) => {
14
+ const engramsPath = await resolveEngramsPath(opts.path);
15
+ const repo = new LocalEngramRepository(engramsPath);
16
+ const extract = new Extract(repo);
17
+ try {
18
+ const result = await extract.execute(opts.engram, {
19
+ name: opts.name,
20
+ openclawDir: opts.openclaw,
21
+ force: opts.force,
22
+ });
23
+ console.log(`Extracted "${result.engramName}" from ${result.sourcePath}`);
24
+ console.log(` Files read: ${result.filesRead.join(", ")}`);
25
+ console.log(` Saved as Engram: ${result.engramId}`);
26
+ if (result.memoryMerged) {
27
+ console.log(` Memory entries merged into existing Engram`);
28
+ }
29
+ }
30
+ catch (err) {
31
+ if (err instanceof WorkspaceNotFoundError ||
32
+ err instanceof WorkspaceEmptyError ||
33
+ err instanceof EngramAlreadyExistsError ||
34
+ err instanceof ExtractNameRequiredError) {
35
+ console.error(`Error: ${err.message}`);
36
+ process.exit(1);
37
+ }
38
+ throw err;
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,19 @@
1
+ import { Init } from "../../../core/usecases/init.js";
2
+ export function registerInitCommand(program) {
3
+ program
4
+ .command("init")
5
+ .description("Initialize ~/.relic/ directory and config")
6
+ .action(async () => {
7
+ const init = new Init();
8
+ const result = await init.execute();
9
+ if (result.created) {
10
+ console.log("Initialized RELIC:");
11
+ console.log(` Config: ${result.configPath}`);
12
+ console.log(` Engrams: ${result.engramsPath}`);
13
+ }
14
+ else {
15
+ console.log("Already initialized.");
16
+ console.log(` Config: ${result.configPath}`);
17
+ }
18
+ });
19
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInjectCommand(program: Command): void;
@@ -0,0 +1,33 @@
1
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
+ import { Inject, InjectEngramNotFoundError, InjectAgentNotFoundError, } from "../../../core/usecases/index.js";
3
+ import { resolveEngramsPath } from "../../../shared/config.js";
4
+ export function registerInjectCommand(program) {
5
+ program
6
+ .command("inject")
7
+ .description("Inject an Engram into an OpenClaw workspace")
8
+ .requiredOption("-e, --engram <id>", "Engram ID to inject (= agent name)")
9
+ .option("--to <agent>", "Inject into a different agent name")
10
+ .option("--openclaw <dir>", "Override OpenClaw directory path (default: ~/.openclaw)")
11
+ .option("-p, --path <dir>", "Override engrams directory path")
12
+ .action(async (opts) => {
13
+ const engramsPath = await resolveEngramsPath(opts.path);
14
+ const repo = new LocalEngramRepository(engramsPath);
15
+ const inject = new Inject(repo);
16
+ try {
17
+ const result = await inject.execute(opts.engram, {
18
+ to: opts.to,
19
+ openclawDir: opts.openclaw,
20
+ });
21
+ console.log(`Injected "${result.engramName}" into ${result.targetPath}`);
22
+ console.log(` Files written: ${result.filesWritten.join(", ")}`);
23
+ }
24
+ catch (err) {
25
+ if (err instanceof InjectEngramNotFoundError ||
26
+ err instanceof InjectAgentNotFoundError) {
27
+ console.error(`Error: ${err.message}`);
28
+ process.exit(1);
29
+ }
30
+ throw err;
31
+ }
32
+ });
33
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerListCommand(program: Command): void;
@@ -0,0 +1,30 @@
1
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
+ import { ListEngrams } from "../../../core/usecases/index.js";
3
+ import { resolveEngramsPath } from "../../../shared/config.js";
4
+ export function registerListCommand(program) {
5
+ program
6
+ .command("list")
7
+ .description("List all Engrams in Mikoshi")
8
+ .option("-p, --path <dir>", "Override engrams directory path")
9
+ .action(async (opts) => {
10
+ const engramsPath = await resolveEngramsPath(opts.path);
11
+ const repo = new LocalEngramRepository(engramsPath);
12
+ const listEngrams = new ListEngrams(repo);
13
+ const engrams = await listEngrams.execute();
14
+ if (engrams.length === 0) {
15
+ console.log("No Engrams found.");
16
+ console.log(` Path: ${engramsPath}`);
17
+ return;
18
+ }
19
+ console.log(`Engrams (${engrams.length}):\n`);
20
+ for (const e of engrams) {
21
+ const tags = e.tags?.length ? ` [${e.tags.join(", ")}]` : "";
22
+ console.log(` ${e.id}`);
23
+ console.log(` ${e.name}${tags}`);
24
+ if (e.description) {
25
+ console.log(` ${e.description}`);
26
+ }
27
+ console.log();
28
+ }
29
+ });
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerShellCommands(program: Command): void;
@@ -0,0 +1,69 @@
1
+ import { resolve } from "node:path";
2
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
3
+ import { Summon, EngramNotFoundError } from "../../../core/usecases/index.js";
4
+ import { resolveEngramsPath } from "../../../shared/config.js";
5
+ import { ClaudeShell } from "../../../adapters/shells/claude-shell.js";
6
+ import { GeminiShell } from "../../../adapters/shells/gemini-shell.js";
7
+ import { CodexShell } from "../../../adapters/shells/codex-shell.js";
8
+ import { CopilotShell } from "../../../adapters/shells/copilot-shell.js";
9
+ const SHELLS = [
10
+ {
11
+ name: "claude",
12
+ description: "Summon an Engram into Claude Code CLI",
13
+ create: () => new ClaudeShell(),
14
+ },
15
+ {
16
+ name: "gemini",
17
+ description: "Summon an Engram into Gemini CLI",
18
+ create: () => new GeminiShell(),
19
+ },
20
+ {
21
+ name: "codex",
22
+ description: "Summon an Engram into Codex CLI",
23
+ create: () => new CodexShell(),
24
+ },
25
+ {
26
+ name: "copilot",
27
+ description: "Summon an Engram into GitHub Copilot CLI",
28
+ create: () => new CopilotShell(),
29
+ },
30
+ ];
31
+ export function registerShellCommands(program) {
32
+ for (const shell of SHELLS) {
33
+ program
34
+ .command(shell.name)
35
+ .description(shell.description)
36
+ .requiredOption("-e, --engram <id>", "Engram ID to summon")
37
+ .option("-p, --path <dir>", "Override engrams directory path")
38
+ .option("--cwd <dir>", "Working directory for the Shell (default: current directory)")
39
+ .allowUnknownOption(true)
40
+ .action(async (opts, cmd) => {
41
+ const launcher = shell.create();
42
+ // Shell利用可能チェック
43
+ const available = await launcher.isAvailable();
44
+ if (!available) {
45
+ console.error(`Error: ${launcher.name} is not installed or not in PATH.`);
46
+ process.exit(1);
47
+ }
48
+ // Engram取得 & プロンプト生成
49
+ const engramsPath = await resolveEngramsPath(opts.path);
50
+ const repo = new LocalEngramRepository(engramsPath);
51
+ const summon = new Summon(repo);
52
+ try {
53
+ const result = await summon.execute(opts.engram);
54
+ console.log(`Summoning "${result.engramName}" into ${launcher.name}...\n`);
55
+ // --engram, --path, --cwd 以外の引数をShellにパススルー
56
+ const extraArgs = cmd.args;
57
+ const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
58
+ await launcher.launch(result.prompt, extraArgs, cwd);
59
+ }
60
+ catch (err) {
61
+ if (err instanceof EngramNotFoundError) {
62
+ console.error(`Error: ${err.message}`);
63
+ process.exit(1);
64
+ }
65
+ throw err;
66
+ }
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerShowCommand(program: Command): void;
@@ -0,0 +1,26 @@
1
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
2
+ import { Summon, EngramNotFoundError } from "../../../core/usecases/index.js";
3
+ import { resolveEngramsPath } from "../../../shared/config.js";
4
+ export function registerShowCommand(program) {
5
+ program
6
+ .command("show")
7
+ .description("Show an Engram's composed prompt")
8
+ .argument("<id>", "Engram ID to show")
9
+ .option("-p, --path <dir>", "Override engrams directory path")
10
+ .action(async (id, opts) => {
11
+ const engramsPath = await resolveEngramsPath(opts.path);
12
+ const repo = new LocalEngramRepository(engramsPath);
13
+ const summon = new Summon(repo);
14
+ try {
15
+ const result = await summon.execute(id);
16
+ console.log(result.prompt);
17
+ }
18
+ catch (err) {
19
+ if (err instanceof EngramNotFoundError) {
20
+ console.error(`Error: ${err.message}`);
21
+ process.exit(1);
22
+ }
23
+ throw err;
24
+ }
25
+ });
26
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerSyncCommand(program: Command): void;
@@ -0,0 +1,91 @@
1
+ import { watch } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { LocalEngramRepository } from "../../../adapters/local/index.js";
4
+ import { Sync, SyncAgentsDirNotFoundError, } from "../../../core/usecases/index.js";
5
+ import { resolveEngramsPath } from "../../../shared/config.js";
6
+ export function registerSyncCommand(program) {
7
+ program
8
+ .command("sync")
9
+ .description("Watch OpenClaw agents and auto-sync with Engrams")
10
+ .option("--openclaw <dir>", "Override OpenClaw directory path (default: ~/.openclaw)")
11
+ .option("-p, --path <dir>", "Override engrams directory path")
12
+ .action(async (opts) => {
13
+ const engramsPath = await resolveEngramsPath(opts.path);
14
+ const repo = new LocalEngramRepository(engramsPath);
15
+ const sync = new Sync(repo);
16
+ try {
17
+ console.log("Starting Relic sync...");
18
+ // 初回同期
19
+ const result = await sync.initialSync(opts.openclaw);
20
+ if (result.injected.length > 0) {
21
+ console.log(` Injected: ${result.injected.join(", ")}`);
22
+ }
23
+ if (result.extracted.length > 0) {
24
+ console.log(` Extracted memory: ${result.extracted.join(", ")}`);
25
+ }
26
+ if (result.targets.length === 0) {
27
+ console.log(" No agents found.");
28
+ return;
29
+ }
30
+ // ファイル監視を開始
31
+ const watchers = startWatching(result.targets, sync, opts.openclaw);
32
+ console.log(`\nWatching ${result.targets.length} agent(s) for memory changes...`);
33
+ console.log("Press Ctrl+C to stop.\n");
34
+ // Ctrl+C でクリーンアップ
35
+ const cleanup = () => {
36
+ console.log("\nStopping sync...");
37
+ for (const w of watchers) {
38
+ w.close();
39
+ }
40
+ process.exit(0);
41
+ };
42
+ process.on("SIGINT", cleanup);
43
+ process.on("SIGTERM", cleanup);
44
+ // プロセスを維持
45
+ await new Promise(() => { });
46
+ }
47
+ catch (err) {
48
+ if (err instanceof SyncAgentsDirNotFoundError) {
49
+ console.error(`Error: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+ throw err;
53
+ }
54
+ });
55
+ }
56
+ function startWatching(targets, sync, openclawDir) {
57
+ const watchers = [];
58
+ // デバウンス用タイマー
59
+ const debounceTimers = new Map();
60
+ for (const target of targets) {
61
+ const memoryDir = join(target.agentPath, "memory");
62
+ try {
63
+ const watcher = watch(memoryDir, { recursive: true }, (_event, filename) => {
64
+ if (!filename?.endsWith(".md"))
65
+ return;
66
+ // デバウンス(同一agentの連続変更を500msでまとめる)
67
+ const existing = debounceTimers.get(target.engramId);
68
+ if (existing)
69
+ clearTimeout(existing);
70
+ debounceTimers.set(target.engramId, setTimeout(async () => {
71
+ debounceTimers.delete(target.engramId);
72
+ try {
73
+ await sync.syncMemory(target.engramId, openclawDir);
74
+ const now = new Date().toLocaleTimeString();
75
+ console.log(`[${now}] Synced memory: ${target.engramId} (${filename})`);
76
+ }
77
+ catch {
78
+ // sync失敗は警告のみ
79
+ console.error(`[warn] Failed to sync memory for ${target.engramId}`);
80
+ }
81
+ }, 500));
82
+ });
83
+ watchers.push(watcher);
84
+ }
85
+ catch {
86
+ // memory/ ディレクトリが存在しない場合はスキップ
87
+ // (エージェントにまだメモリがない)
88
+ }
89
+ }
90
+ return watchers;
91
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { registerInitCommand } from "./commands/init.js";
4
+ import { registerListCommand } from "./commands/list.js";
5
+ import { registerShowCommand } from "./commands/show.js";
6
+ import { registerShellCommands } from "./commands/shell.js";
7
+ import { registerInjectCommand } from "./commands/inject.js";
8
+ import { registerExtractCommand } from "./commands/extract.js";
9
+ import { registerSyncCommand } from "./commands/sync.js";
10
+ const program = new Command();
11
+ program
12
+ .name("relic")
13
+ .description("PROJECT RELIC — Engram injection system for AI constructs")
14
+ .version("0.1.0");
15
+ registerInitCommand(program);
16
+ registerListCommand(program);
17
+ registerShowCommand(program);
18
+ registerShellCommands(program);
19
+ registerInjectCommand(program);
20
+ registerExtractCommand(program);
21
+ registerSyncCommand(program);
22
+ program.parse();
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};