@ectplsm/relic 0.3.2 → 0.5.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 +1 -1
- package/README.md +20 -13
- package/dist/adapters/mikoshi/client.d.ts +21 -0
- package/dist/adapters/mikoshi/client.js +84 -0
- package/dist/core/ports/index.d.ts +1 -0
- package/dist/core/ports/mikoshi.d.ts +937 -0
- package/dist/core/ports/mikoshi.js +209 -0
- package/dist/core/sync/crypto.d.ts +55 -0
- package/dist/core/sync/crypto.js +189 -0
- package/dist/core/sync/memory-hash.d.ts +35 -0
- package/dist/core/sync/memory-hash.js +61 -0
- package/dist/core/sync/memory-merge.d.ts +13 -0
- package/dist/core/sync/memory-merge.js +84 -0
- package/dist/core/sync/normalize.d.ts +10 -0
- package/dist/core/sync/normalize.js +21 -0
- package/dist/core/sync/persona-hash.d.ts +15 -0
- package/dist/core/sync/persona-hash.js +36 -0
- package/dist/core/usecases/archive-cursor-update.d.ts +6 -0
- package/dist/core/usecases/archive-cursor-update.js +18 -0
- package/dist/core/usecases/archive-pending.d.ts +18 -1
- package/dist/core/usecases/archive-pending.js +37 -2
- package/dist/core/usecases/claw-pull.d.ts +44 -0
- package/dist/core/usecases/claw-pull.js +117 -0
- package/dist/core/usecases/extract.d.ts +0 -1
- package/dist/core/usecases/extract.js +13 -27
- package/dist/core/usecases/index.d.ts +11 -3
- package/dist/core/usecases/index.js +10 -2
- package/dist/core/usecases/inject.d.ts +1 -0
- package/dist/core/usecases/inject.js +2 -0
- package/dist/core/usecases/memory-write.d.ts +5 -0
- package/dist/core/usecases/memory-write.js +26 -16
- package/dist/core/usecases/mikoshi-clone.d.ts +29 -0
- package/dist/core/usecases/mikoshi-clone.js +94 -0
- package/dist/core/usecases/mikoshi-download.d.ts +29 -0
- package/dist/core/usecases/mikoshi-download.js +94 -0
- package/dist/core/usecases/mikoshi-memory-pull.d.ts +38 -0
- package/dist/core/usecases/mikoshi-memory-pull.js +142 -0
- package/dist/core/usecases/mikoshi-memory-push.d.ts +34 -0
- package/dist/core/usecases/mikoshi-memory-push.js +105 -0
- package/dist/core/usecases/mikoshi-memory-sync.d.ts +36 -0
- package/dist/core/usecases/mikoshi-memory-sync.js +175 -0
- package/dist/core/usecases/mikoshi-pull.d.ts +44 -0
- package/dist/core/usecases/mikoshi-pull.js +124 -0
- package/dist/core/usecases/mikoshi-push.d.ts +39 -0
- package/dist/core/usecases/mikoshi-push.js +127 -0
- package/dist/core/usecases/mikoshi-status.d.ts +37 -0
- package/dist/core/usecases/mikoshi-status.js +92 -0
- package/dist/core/usecases/sync.d.ts +0 -5
- package/dist/core/usecases/sync.js +23 -61
- package/dist/interfaces/cli/commands/claw.js +134 -85
- package/dist/interfaces/cli/commands/config.js +60 -1
- package/dist/interfaces/cli/commands/create.js +17 -16
- package/dist/interfaces/cli/commands/init.js +11 -10
- package/dist/interfaces/cli/commands/list.js +9 -7
- package/dist/interfaces/cli/commands/migrate.js +8 -7
- package/dist/interfaces/cli/commands/mikoshi.d.ts +2 -0
- package/dist/interfaces/cli/commands/mikoshi.js +565 -0
- package/dist/interfaces/cli/commands/refresh-samples.js +10 -9
- package/dist/interfaces/cli/index.js +2 -0
- package/dist/interfaces/cli/output.d.ts +5 -0
- package/dist/interfaces/cli/output.js +16 -0
- package/dist/interfaces/cli/spinner.d.ts +13 -0
- package/dist/interfaces/cli/spinner.js +47 -0
- package/dist/interfaces/mcp/index.js +109 -38
- package/dist/shared/config.d.ts +25 -5
- package/dist/shared/config.js +33 -5
- package/dist/shared/engram-composer.d.ts +1 -1
- package/dist/shared/engram-composer.js +9 -5
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
+
import { mergeMemoryRecords } from "../sync/memory-merge.js";
|
|
5
6
|
import { extractAgentName, MEMORY_DIR } from "../../shared/openclaw.js";
|
|
6
7
|
const DEFAULT_OPENCLAW_DIR = join(homedir(), ".openclaw");
|
|
7
8
|
const MEMORY_INDEX = "MEMORY.md";
|
|
@@ -73,37 +74,20 @@ export class Sync {
|
|
|
73
74
|
const openclawMemDir = join(openclawDir, MEMORY_DIR);
|
|
74
75
|
const relicEntries = await this.readMemoryDir(relicMemDir);
|
|
75
76
|
const openclawEntries = await this.readMemoryDir(openclawMemDir);
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
let mergedCount = 0;
|
|
82
|
-
for (const date of allDates) {
|
|
83
|
-
const relicContent = relicEntries[date];
|
|
84
|
-
const openclawContent = openclawEntries[date];
|
|
85
|
-
if (relicContent && !openclawContent) {
|
|
86
|
-
// RELIC にだけある → OpenClaw にコピー
|
|
87
|
-
await mkdir(openclawMemDir, { recursive: true });
|
|
88
|
-
await writeFile(join(openclawMemDir, `${date}.md`), relicContent, "utf-8");
|
|
89
|
-
mergedCount++;
|
|
90
|
-
}
|
|
91
|
-
else if (!relicContent && openclawContent) {
|
|
92
|
-
// OpenClaw にだけある → RELIC にコピー
|
|
93
|
-
await mkdir(relicMemDir, { recursive: true });
|
|
94
|
-
await writeFile(join(relicMemDir, `${date}.md`), openclawContent, "utf-8");
|
|
95
|
-
mergedCount++;
|
|
96
|
-
}
|
|
97
|
-
else if (relicContent && openclawContent && relicContent !== openclawContent) {
|
|
98
|
-
// 両方にあるが内容が違う → マージして両方に書き込み
|
|
99
|
-
const merged = this.mergeContents(relicContent, openclawContent);
|
|
100
|
-
await writeFile(join(relicMemDir, `${date}.md`), merged, "utf-8");
|
|
101
|
-
await writeFile(join(openclawMemDir, `${date}.md`), merged, "utf-8");
|
|
102
|
-
mergedCount++;
|
|
103
|
-
}
|
|
104
|
-
// 内容が同じ → 何もしない
|
|
77
|
+
const relicRecord = Object.fromEntries(Object.entries(relicEntries).map(([date, content]) => [`memory/${date}.md`, content]));
|
|
78
|
+
const openclawRecord = Object.fromEntries(Object.entries(openclawEntries).map(([date, content]) => [`memory/${date}.md`, content]));
|
|
79
|
+
const { merged, changedPaths } = mergeMemoryRecords(relicRecord, openclawRecord);
|
|
80
|
+
if (changedPaths.length === 0) {
|
|
81
|
+
return 0;
|
|
105
82
|
}
|
|
106
|
-
|
|
83
|
+
await mkdir(relicMemDir, { recursive: true });
|
|
84
|
+
await mkdir(openclawMemDir, { recursive: true });
|
|
85
|
+
for (const [path, content] of Object.entries(merged)) {
|
|
86
|
+
const date = path.replace("memory/", "").replace(".md", "");
|
|
87
|
+
await writeFile(join(relicMemDir, `${date}.md`), content, "utf-8");
|
|
88
|
+
await writeFile(join(openclawMemDir, `${date}.md`), content, "utf-8");
|
|
89
|
+
}
|
|
90
|
+
return changedPaths.length;
|
|
107
91
|
}
|
|
108
92
|
/**
|
|
109
93
|
* MEMORY.md を双方向マージ
|
|
@@ -123,41 +107,19 @@ export class Sync {
|
|
|
123
107
|
const openclawContent = existsSync(openclawPath)
|
|
124
108
|
? await readFile(openclawPath, "utf-8")
|
|
125
109
|
: null;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const merged = this.mergeContents(relicContent, openclawContent);
|
|
136
|
-
await writeFile(relicPath, merged, "utf-8");
|
|
137
|
-
await writeFile(openclawPath, merged, "utf-8");
|
|
110
|
+
const relicRecord = relicContent ? { [filename]: relicContent } : {};
|
|
111
|
+
const openclawRecord = openclawContent ? { [filename]: openclawContent } : {};
|
|
112
|
+
const { merged, changedPaths } = mergeMemoryRecords(relicRecord, openclawRecord);
|
|
113
|
+
if (changedPaths.length > 0) {
|
|
114
|
+
const mergedContent = merged[filename];
|
|
115
|
+
if (mergedContent !== undefined) {
|
|
116
|
+
await writeFile(relicPath, mergedContent, "utf-8");
|
|
117
|
+
await writeFile(openclawPath, mergedContent, "utf-8");
|
|
118
|
+
}
|
|
138
119
|
return true;
|
|
139
120
|
}
|
|
140
121
|
return false;
|
|
141
122
|
}
|
|
142
|
-
/**
|
|
143
|
-
* 2つのテキスト内容をマージする。
|
|
144
|
-
* 重複行を除外しつつ、両方の内容を結合する。
|
|
145
|
-
*/
|
|
146
|
-
mergeContents(a, b) {
|
|
147
|
-
const aLines = a.trimEnd();
|
|
148
|
-
const bLines = b.trimEnd();
|
|
149
|
-
// 完全一致チェック(ここには来ないはずだが安全策)
|
|
150
|
-
if (aLines === bLines)
|
|
151
|
-
return a;
|
|
152
|
-
// b の中で a に含まれない部分を抽出して追記
|
|
153
|
-
const aSet = new Set(aLines.split("\n"));
|
|
154
|
-
const uniqueB = bLines
|
|
155
|
-
.split("\n")
|
|
156
|
-
.filter((line) => !aSet.has(line));
|
|
157
|
-
if (uniqueB.length === 0)
|
|
158
|
-
return a;
|
|
159
|
-
return aLines + "\n\n" + uniqueB.join("\n") + "\n";
|
|
160
|
-
}
|
|
161
123
|
/**
|
|
162
124
|
* memory/ ディレクトリから日付 → 内容のマップを読む
|
|
163
125
|
*/
|
|
@@ -2,22 +2,23 @@ import { createInterface } from "node:readline/promises";
|
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { LocalEngramRepository } from "../../../adapters/local/index.js";
|
|
5
|
-
import { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, Extract, WorkspaceNotFoundError, WorkspaceEmptyError,
|
|
6
|
-
import { resolveEngramsPath, resolveClawPath } from "../../../shared/config.js";
|
|
5
|
+
import { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, Extract, WorkspaceNotFoundError, WorkspaceEmptyError, ClawPull, ClawPullEngramNotFoundError, ClawPullWorkspaceNotFoundError, ClawPullPersonaMissingError, Sync, SyncOpenclawDirNotFoundError, } from "../../../core/usecases/index.js";
|
|
6
|
+
import { resolveEngramsPath, resolveClawPath, } from "../../../shared/config.js";
|
|
7
|
+
import { printDetail, printLine } from "../output.js";
|
|
7
8
|
import { resolveWorkspacePath } from "../../../shared/openclaw.js";
|
|
8
9
|
export function registerClawCommand(program) {
|
|
9
10
|
const claw = program
|
|
10
11
|
.command("claw")
|
|
11
12
|
.description("Manage Claw agent workspaces (OpenClaw and compatible)");
|
|
12
|
-
// --- claw
|
|
13
|
+
// --- claw push ---
|
|
13
14
|
claw
|
|
14
|
-
.command("
|
|
15
|
-
.description("Push
|
|
16
|
-
.requiredOption("-e, --engram <id>", "Engram ID to
|
|
15
|
+
.command("push")
|
|
16
|
+
.description("Push local Engram persona into a Claw workspace")
|
|
17
|
+
.requiredOption("-e, --engram <id>", "Engram ID to push")
|
|
17
18
|
.option("--dir <dir>", "Override Claw directory path (default: ~/.openclaw)")
|
|
18
19
|
.option("--merge-identity", "Merge IDENTITY.md into SOUL.md (for non-OpenClaw Claw frameworks)")
|
|
19
|
-
.option("-y, --yes", "Skip
|
|
20
|
-
.option("--no-sync", "Skip automatic memory sync after
|
|
20
|
+
.option("-y, --yes", "Skip create/overwrite confirmation")
|
|
21
|
+
.option("--no-sync", "Skip automatic memory sync after push")
|
|
21
22
|
.option("-p, --path <dir>", "Override engrams directory path")
|
|
22
23
|
.action(async (opts) => {
|
|
23
24
|
const engramsPath = await resolveEngramsPath(opts.path);
|
|
@@ -31,26 +32,36 @@ export function registerClawCommand(program) {
|
|
|
31
32
|
});
|
|
32
33
|
const alreadyInjected = diff.soul === "same" &&
|
|
33
34
|
(diff.identity === "same" || diff.identity === "skipped");
|
|
35
|
+
const needsCreate = !diff.targetExists ||
|
|
36
|
+
diff.soul === "missing" ||
|
|
37
|
+
diff.identity === "missing";
|
|
38
|
+
if (needsCreate && !opts.yes) {
|
|
39
|
+
const confirmed = await confirmOverwrite(`Claw workspace "${opts.engram}" does not have persona files yet. Create them from the local Relic Engram? [y/N] `);
|
|
40
|
+
if (!confirmed) {
|
|
41
|
+
printLine("Skipped.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
34
45
|
if (diff.overwriteRequired &&
|
|
35
46
|
!opts.yes &&
|
|
36
|
-
!(await confirmOverwrite(`
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
!(await confirmOverwrite(`Overwrite Claw workspace "${opts.engram}" persona with the local Relic version? [y/N] `))) {
|
|
48
|
+
printLine("Skipped.");
|
|
49
|
+
return;
|
|
39
50
|
}
|
|
40
51
|
if (alreadyInjected) {
|
|
41
|
-
console.log(
|
|
52
|
+
console.log(`✅ Persona already in sync (${diff.targetPath})`);
|
|
42
53
|
}
|
|
43
54
|
else {
|
|
44
55
|
const result = await inject.execute(opts.engram, {
|
|
45
56
|
openclawDir: clawDir,
|
|
46
57
|
mergeIdentity: opts.mergeIdentity,
|
|
47
58
|
});
|
|
48
|
-
console.log(
|
|
49
|
-
|
|
59
|
+
console.log(`✅ Pushed "${result.engramName}" into ${result.targetPath}`);
|
|
60
|
+
printDetail(`Files written: ${result.filesWritten.join(", ")}`);
|
|
50
61
|
}
|
|
51
62
|
if (!opts.sync)
|
|
52
63
|
return;
|
|
53
|
-
// Auto-sync memory after
|
|
64
|
+
// Auto-sync memory after push
|
|
54
65
|
const sync = new Sync(repo, engramsPath);
|
|
55
66
|
const workspacePath = resolveWorkspacePath(opts.engram, clawDir);
|
|
56
67
|
const syncResult = await sync.syncPair({
|
|
@@ -68,88 +79,108 @@ export function registerClawCommand(program) {
|
|
|
68
79
|
details.push("USER.md");
|
|
69
80
|
}
|
|
70
81
|
if (details.length > 0) {
|
|
71
|
-
console.log(
|
|
82
|
+
console.log(`✅ Memory synced: ${details.join(", ")} (${opts.engram})`);
|
|
72
83
|
}
|
|
73
84
|
else {
|
|
74
|
-
console.log(
|
|
85
|
+
console.log(`✅ Memory already in sync (${opts.engram})`);
|
|
75
86
|
}
|
|
76
87
|
}
|
|
77
88
|
catch (err) {
|
|
78
89
|
if (err instanceof InjectEngramNotFoundError ||
|
|
79
90
|
err instanceof InjectClawDirNotFoundError ||
|
|
80
91
|
err instanceof InjectWorkspaceNotFoundError) {
|
|
81
|
-
|
|
92
|
+
printError(`Error: ${err.message}`);
|
|
82
93
|
process.exit(1);
|
|
83
94
|
}
|
|
84
95
|
throw err;
|
|
85
96
|
}
|
|
86
97
|
});
|
|
87
|
-
// --- claw
|
|
98
|
+
// --- claw pull ---
|
|
88
99
|
claw
|
|
89
|
-
.command("
|
|
90
|
-
.description("
|
|
91
|
-
.
|
|
92
|
-
.option("--name <name>", "
|
|
100
|
+
.command("pull")
|
|
101
|
+
.description("Pull persona from a Claw workspace into local Relic")
|
|
102
|
+
.requiredOption("-e, --engram <id>", "Engram ID to pull")
|
|
103
|
+
.option("--name <name>", "Display name to use if a new local Engram is created")
|
|
93
104
|
.option("--dir <dir>", "Override Claw directory path (default: ~/.openclaw)")
|
|
94
|
-
.option("-
|
|
95
|
-
.option("-
|
|
96
|
-
.option("--no-sync", "Skip automatic memory sync after extract")
|
|
105
|
+
.option("-y, --yes", "Skip create/overwrite confirmation")
|
|
106
|
+
.option("--no-sync", "Skip automatic memory sync after pull")
|
|
97
107
|
.option("-p, --path <dir>", "Override engrams directory path")
|
|
98
108
|
.action(async (opts) => {
|
|
99
109
|
const engramsPath = await resolveEngramsPath(opts.path);
|
|
100
110
|
const clawDir = await resolveClawPath(opts.dir);
|
|
101
111
|
const repo = new LocalEngramRepository(engramsPath);
|
|
102
112
|
const extract = new Extract(repo);
|
|
113
|
+
const pull = new ClawPull(repo);
|
|
103
114
|
const sync = new Sync(repo, engramsPath);
|
|
104
115
|
try {
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
diff.name === "same" &&
|
|
115
|
-
diff.soul === "same" &&
|
|
116
|
-
diff.identity === "same";
|
|
117
|
-
if (diff.existing &&
|
|
118
|
-
diff.overwriteRequired &&
|
|
119
|
-
!opts.yes &&
|
|
120
|
-
!(await confirmOverwrite(`SOUL.md and/or IDENTITY.md already exist in local Engram "${agentName}" and differ from ${diff.sourcePath}. Overwrite with the Claw version? [y/N] `))) {
|
|
121
|
-
console.error("Error: Persona files already exist and differ. Re-run with --yes to overwrite from the Claw workspace.");
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
if (alreadyExtracted) {
|
|
125
|
-
console.log(opts.force
|
|
126
|
-
? ` Already extracted and updated (${agentName})`
|
|
127
|
-
: ` Already extracted (${agentName})`);
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
const result = await extract.execute(agentName, {
|
|
116
|
+
const engramId = opts.engram.trim();
|
|
117
|
+
const local = await repo.get(engramId);
|
|
118
|
+
if (!local) {
|
|
119
|
+
if (!opts.yes &&
|
|
120
|
+
!(await confirmOverwrite(`Local Engram "${engramId}" does not exist. Create it from the Claw workspace? [y/N] `))) {
|
|
121
|
+
printLine("Skipped.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const result = await extract.execute(engramId, {
|
|
131
125
|
name: opts.name,
|
|
132
126
|
openclawDir: clawDir,
|
|
133
|
-
force: opts.force,
|
|
134
127
|
});
|
|
135
|
-
console.log(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
128
|
+
console.log(`✅ Pulled "${result.engramName}" from ${result.sourcePath}`);
|
|
129
|
+
printDetail(`Files imported: ${result.filesRead.join(", ")}`);
|
|
130
|
+
printDetail(`Saved as Engram: ${result.engramId}`);
|
|
131
|
+
if (!opts.sync)
|
|
132
|
+
return;
|
|
133
|
+
const syncResult = await sync.syncPair({
|
|
134
|
+
engramId,
|
|
135
|
+
workspacePath: result.sourcePath,
|
|
136
|
+
});
|
|
137
|
+
const details = [];
|
|
138
|
+
if (syncResult.memoryFilesMerged > 0) {
|
|
139
|
+
details.push(`${syncResult.memoryFilesMerged} memory file(s)`);
|
|
140
|
+
}
|
|
141
|
+
if (syncResult.memoryIndexMerged) {
|
|
142
|
+
details.push("MEMORY.md");
|
|
143
|
+
}
|
|
144
|
+
if (syncResult.userMerged) {
|
|
145
|
+
details.push("USER.md");
|
|
146
|
+
}
|
|
147
|
+
if (details.length > 0) {
|
|
148
|
+
console.log(`✅ Memory synced: ${details.join(", ")} (${engramId})`);
|
|
142
149
|
}
|
|
143
150
|
else {
|
|
144
|
-
console.log(
|
|
145
|
-
|
|
151
|
+
console.log(`✅ Memory already in sync (${engramId})`);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const { result, apply } = await pull.check(engramId, {
|
|
156
|
+
openclawDir: clawDir,
|
|
157
|
+
});
|
|
158
|
+
if (result.outcome === "already_synced") {
|
|
159
|
+
console.log(`✅ Persona already in sync (${engramId})`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const diff = result.diff;
|
|
163
|
+
if (diff.soulDiff === "different") {
|
|
164
|
+
printDetail("SOUL.md: differs");
|
|
165
|
+
}
|
|
166
|
+
if (diff.identityDiff === "different") {
|
|
167
|
+
printDetail("IDENTITY.md: differs");
|
|
168
|
+
}
|
|
169
|
+
// Confirm overwrite
|
|
170
|
+
if (!opts.yes &&
|
|
171
|
+
!(await confirmOverwrite(`Overwrite local Engram "${engramId}" persona with the Claw workspace version? [y/N] `))) {
|
|
172
|
+
printLine("Skipped.");
|
|
173
|
+
return;
|
|
146
174
|
}
|
|
175
|
+
await apply();
|
|
176
|
+
console.log(`✅ Pulled "${result.engramName}" from ${result.diff.sourcePath}`);
|
|
147
177
|
}
|
|
148
178
|
if (!opts.sync)
|
|
149
179
|
return;
|
|
180
|
+
const workspacePath = resolveWorkspacePath(engramId, clawDir);
|
|
150
181
|
const syncResult = await sync.syncPair({
|
|
151
|
-
engramId
|
|
152
|
-
workspacePath
|
|
182
|
+
engramId,
|
|
183
|
+
workspacePath,
|
|
153
184
|
});
|
|
154
185
|
const details = [];
|
|
155
186
|
if (syncResult.memoryFilesMerged > 0) {
|
|
@@ -162,17 +193,19 @@ export function registerClawCommand(program) {
|
|
|
162
193
|
details.push("USER.md");
|
|
163
194
|
}
|
|
164
195
|
if (details.length > 0) {
|
|
165
|
-
console.log(
|
|
196
|
+
console.log(`✅ Memory synced: ${details.join(", ")} (${engramId})`);
|
|
166
197
|
}
|
|
167
198
|
else {
|
|
168
|
-
console.log(
|
|
199
|
+
console.log(`✅ Memory already in sync (${engramId})`);
|
|
169
200
|
}
|
|
170
201
|
}
|
|
171
202
|
catch (err) {
|
|
172
203
|
if (err instanceof WorkspaceNotFoundError ||
|
|
173
204
|
err instanceof WorkspaceEmptyError ||
|
|
174
|
-
err instanceof
|
|
175
|
-
|
|
205
|
+
err instanceof ClawPullEngramNotFoundError ||
|
|
206
|
+
err instanceof ClawPullWorkspaceNotFoundError ||
|
|
207
|
+
err instanceof ClawPullPersonaMissingError) {
|
|
208
|
+
printError(`Error: ${err.message}`);
|
|
176
209
|
process.exit(1);
|
|
177
210
|
}
|
|
178
211
|
throw err;
|
|
@@ -182,7 +215,8 @@ export function registerClawCommand(program) {
|
|
|
182
215
|
claw
|
|
183
216
|
.command("sync")
|
|
184
217
|
.description("Bidirectional memory sync between Engrams and Claw workspaces")
|
|
185
|
-
.option("-
|
|
218
|
+
.option("-e, --engram <id>", "Engram ID to sync")
|
|
219
|
+
.option("--all", "Sync all matching targets")
|
|
186
220
|
.option("--dir <dir>", "Override Claw directory path (default: ~/.openclaw)")
|
|
187
221
|
.option("-p, --path <dir>", "Override engrams directory path")
|
|
188
222
|
.action(async (opts) => {
|
|
@@ -191,24 +225,32 @@ export function registerClawCommand(program) {
|
|
|
191
225
|
const repo = new LocalEngramRepository(engramsPath);
|
|
192
226
|
const sync = new Sync(repo, engramsPath);
|
|
193
227
|
try {
|
|
194
|
-
if (opts.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
228
|
+
if (opts.engram && opts.all) {
|
|
229
|
+
printError("Error: --engram and --all cannot be used together.");
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
if (!opts.engram && !opts.all) {
|
|
233
|
+
printError("Error: Specify --engram <id> or --all.");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
if (opts.engram) {
|
|
237
|
+
const engramId = opts.engram.trim();
|
|
238
|
+
if (!engramId) {
|
|
239
|
+
printError(`Error: Invalid Engram ID "${opts.engram}".`);
|
|
198
240
|
process.exit(1);
|
|
199
241
|
}
|
|
200
|
-
const engram = await repo.get(
|
|
242
|
+
const engram = await repo.get(engramId);
|
|
201
243
|
if (!engram) {
|
|
202
|
-
|
|
244
|
+
printError(`Error: Engram "${engramId}" not found.`);
|
|
203
245
|
process.exit(1);
|
|
204
246
|
}
|
|
205
|
-
const workspacePath = resolveWorkspacePath(
|
|
247
|
+
const workspacePath = resolveWorkspacePath(engramId, clawDir);
|
|
206
248
|
if (!existsSync(workspacePath)) {
|
|
207
|
-
|
|
249
|
+
printError(`Error: Claw agent "${engramId}" workspace not found.`);
|
|
208
250
|
process.exit(1);
|
|
209
251
|
}
|
|
210
252
|
const result = await sync.syncPair({
|
|
211
|
-
engramId
|
|
253
|
+
engramId,
|
|
212
254
|
workspacePath,
|
|
213
255
|
});
|
|
214
256
|
const details = [];
|
|
@@ -222,16 +264,16 @@ export function registerClawCommand(program) {
|
|
|
222
264
|
details.push("USER.md");
|
|
223
265
|
}
|
|
224
266
|
if (details.length > 0) {
|
|
225
|
-
console.log(
|
|
267
|
+
console.log(`✅ ${engramId}: merged ${details.join(", ")}`);
|
|
226
268
|
}
|
|
227
269
|
else {
|
|
228
|
-
console.log(
|
|
270
|
+
console.log(`✅ Already in sync (${engramId})`);
|
|
229
271
|
}
|
|
230
272
|
return;
|
|
231
273
|
}
|
|
232
274
|
const result = await sync.execute(clawDir);
|
|
233
275
|
if (result.synced.length === 0 && result.skipped.length === 0) {
|
|
234
|
-
|
|
276
|
+
printLine("No Claw workspaces found.");
|
|
235
277
|
return;
|
|
236
278
|
}
|
|
237
279
|
for (const s of result.synced) {
|
|
@@ -246,19 +288,19 @@ export function registerClawCommand(program) {
|
|
|
246
288
|
details.push("USER.md");
|
|
247
289
|
}
|
|
248
290
|
if (details.length > 0) {
|
|
249
|
-
console.log(
|
|
291
|
+
console.log(`✅ ${s.engramId}: merged ${details.join(", ")}`);
|
|
250
292
|
}
|
|
251
293
|
else {
|
|
252
|
-
console.log(
|
|
294
|
+
console.log(`✅ Already in sync (${s.engramId})`);
|
|
253
295
|
}
|
|
254
296
|
}
|
|
255
297
|
if (result.skipped.length > 0) {
|
|
256
|
-
console.log(`
|
|
298
|
+
console.log(`Skipped (no matching Engram): ${result.skipped.join(", ")}`);
|
|
257
299
|
}
|
|
258
300
|
}
|
|
259
301
|
catch (err) {
|
|
260
302
|
if (err instanceof SyncOpenclawDirNotFoundError) {
|
|
261
|
-
|
|
303
|
+
printError(`Error: ${err.message}`);
|
|
262
304
|
process.exit(1);
|
|
263
305
|
}
|
|
264
306
|
throw err;
|
|
@@ -278,3 +320,10 @@ async function confirmOverwrite(message) {
|
|
|
278
320
|
rl.close();
|
|
279
321
|
}
|
|
280
322
|
}
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// ANSI colors
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
327
|
+
function printError(msg) {
|
|
328
|
+
console.error(red(msg));
|
|
329
|
+
}
|
|
@@ -80,10 +80,69 @@ export function registerConfigCommand(program) {
|
|
|
80
80
|
await saveConfig(cfg);
|
|
81
81
|
console.log(`Memory window set to: ${parsed}`);
|
|
82
82
|
});
|
|
83
|
+
// relic config mikoshi-url [url]
|
|
84
|
+
config
|
|
85
|
+
.command("mikoshi-url [url]")
|
|
86
|
+
.description("Get or set the Mikoshi API base URL (default: https://mikoshi.ectplsm.com)")
|
|
87
|
+
.action(async (url) => {
|
|
88
|
+
await ensureInitialized();
|
|
89
|
+
const cfg = await loadConfig();
|
|
90
|
+
if (!url) {
|
|
91
|
+
console.log(cfg.mikoshiUrl);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
cfg.mikoshiUrl = url;
|
|
95
|
+
await saveConfig(cfg);
|
|
96
|
+
console.log(`Mikoshi URL set to: ${url}`);
|
|
97
|
+
});
|
|
98
|
+
// relic config mikoshi-api-key [key]
|
|
99
|
+
config
|
|
100
|
+
.command("mikoshi-api-key [key]")
|
|
101
|
+
.description("Get or set the Mikoshi API key")
|
|
102
|
+
.action(async (key) => {
|
|
103
|
+
await ensureInitialized();
|
|
104
|
+
const cfg = await loadConfig();
|
|
105
|
+
if (!key) {
|
|
106
|
+
if (cfg.mikoshiApiKey) {
|
|
107
|
+
// マスク表示 — 先頭10文字だけ見せる
|
|
108
|
+
const masked = cfg.mikoshiApiKey.length > 10
|
|
109
|
+
? cfg.mikoshiApiKey.slice(0, 10) + "…"
|
|
110
|
+
: cfg.mikoshiApiKey;
|
|
111
|
+
console.log(masked);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log("(not set)");
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
cfg.mikoshiApiKey = key;
|
|
119
|
+
await saveConfig(cfg);
|
|
120
|
+
console.log("Mikoshi API key saved.");
|
|
121
|
+
});
|
|
122
|
+
// relic config mikoshi-passphrase [value]
|
|
123
|
+
config
|
|
124
|
+
.command("mikoshi-passphrase [value]")
|
|
125
|
+
.description("Get or set the passphrase for Mikoshi memory encryption")
|
|
126
|
+
.action(async (value) => {
|
|
127
|
+
await ensureInitialized();
|
|
128
|
+
const cfg = await loadConfig();
|
|
129
|
+
if (!value) {
|
|
130
|
+
if (cfg.mikoshiPassphrase) {
|
|
131
|
+
console.log("(set)");
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log("(not set)");
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
cfg.mikoshiPassphrase = value;
|
|
139
|
+
await saveConfig(cfg);
|
|
140
|
+
console.log("Mikoshi passphrase saved.");
|
|
141
|
+
});
|
|
83
142
|
// relic config distillation-batch-size [n]
|
|
84
143
|
config
|
|
85
144
|
.command("distillation-batch-size [n]")
|
|
86
|
-
.description("Get or set the number of archive entries to distill at once (default:
|
|
145
|
+
.description("Get or set the number of archive entries to distill at once (default: 100)")
|
|
87
146
|
.action(async (n) => {
|
|
88
147
|
await ensureInitialized();
|
|
89
148
|
const cfg = await loadConfig();
|
|
@@ -3,6 +3,7 @@ import { stdin as input, stdout as output } from "node:process";
|
|
|
3
3
|
import { loadConfig, ensureInitialized } from "../../../shared/config.js";
|
|
4
4
|
import { LocalEngramRepository } from "../../../adapters/local/index.js";
|
|
5
5
|
import { DEFAULT_SOUL, DEFAULT_IDENTITY } from "../../../shared/templates.js";
|
|
6
|
+
import { printBlank, printDetail, printLine } from "../output.js";
|
|
6
7
|
import { CreateEngram, EngramAlreadyExistsError, InvalidEngramIdError, ENGRAM_ID_PATTERN, } from "../../../core/usecases/create-engram.js";
|
|
7
8
|
// ============================================================
|
|
8
9
|
// Interactive prompt helpers
|
|
@@ -16,7 +17,7 @@ async function askRequired(rl, question, errorMsg) {
|
|
|
16
17
|
const answer = await ask(rl, question);
|
|
17
18
|
if (answer)
|
|
18
19
|
return answer;
|
|
19
|
-
|
|
20
|
+
printLine(errorMsg);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
@@ -24,14 +25,14 @@ async function askRequired(rl, question, errorMsg) {
|
|
|
24
25
|
*/
|
|
25
26
|
async function askValidId(rl, repo) {
|
|
26
27
|
while (true) {
|
|
27
|
-
const id = await askRequired(rl, "Engram ID (e.g. my-agent): ", "
|
|
28
|
+
const id = await askRequired(rl, "Engram ID (e.g. my-agent): ", "ID is required. Use lowercase letters, numbers, and hyphens.");
|
|
28
29
|
if (!ENGRAM_ID_PATTERN.test(id)) {
|
|
29
|
-
|
|
30
|
+
printLine("Invalid ID. Use lowercase letters, numbers, and hyphens (e.g. my-agent).");
|
|
30
31
|
continue;
|
|
31
32
|
}
|
|
32
33
|
const existing = await repo.get(id);
|
|
33
34
|
if (existing) {
|
|
34
|
-
|
|
35
|
+
printLine(`Engram "${id}" already exists. Choose a different ID.`);
|
|
35
36
|
continue;
|
|
36
37
|
}
|
|
37
38
|
return id;
|
|
@@ -91,7 +92,7 @@ export function registerCreateCommand(program) {
|
|
|
91
92
|
id = await askValidId(rl, repo);
|
|
92
93
|
}
|
|
93
94
|
if (!name) {
|
|
94
|
-
name = await askRequired(rl, "Name: ", "
|
|
95
|
+
name = await askRequired(rl, "Name: ", "Name is required.");
|
|
95
96
|
}
|
|
96
97
|
if (description === undefined) {
|
|
97
98
|
description = await ask(rl, "Description (optional): ") || undefined;
|
|
@@ -119,17 +120,17 @@ export function registerCreateCommand(program) {
|
|
|
119
120
|
});
|
|
120
121
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
121
122
|
const engramDir = `${config.engramsPath}/${result.engram.meta.id}`;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
printBlank();
|
|
124
|
+
printLine(green(`Created Engram "${result.engram.meta.name}" (${result.engram.meta.id})`));
|
|
125
|
+
printDetail(`→ ${engramDir}/`);
|
|
126
|
+
printBlank();
|
|
127
|
+
printLine("Files:");
|
|
128
|
+
printDetail("engram.json — metadata (name, description, tags)");
|
|
129
|
+
printDetail("manifest.json — system metadata (id, timestamps)");
|
|
130
|
+
printDetail("SOUL.md — core principles and behavior");
|
|
131
|
+
printDetail("IDENTITY.md — persona identity (fill in during first conversation)");
|
|
132
|
+
printBlank();
|
|
133
|
+
printLine(`Customize SOUL.md and IDENTITY.md, then run: relic claude ${result.engram.meta.id}`);
|
|
133
134
|
}
|
|
134
135
|
catch (err) {
|
|
135
136
|
if (err instanceof InvalidEngramIdError || err instanceof EngramAlreadyExistsError) {
|