@matrixorigin/thememoria 0.4.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.
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PLUGIN_ID="memory-memoria"
5
+ DEFAULT_INSTALL_DIR="${HOME}/.local/share/openclaw-plugins/openclaw-memoria"
6
+
7
+ MEMORIA_TOOL_NAMES=(
8
+ memory_search
9
+ memory_get
10
+ memory_store
11
+ memory_retrieve
12
+ memory_recall
13
+ memory_list
14
+ memory_stats
15
+ memory_profile
16
+ memory_correct
17
+ memory_purge
18
+ memory_forget
19
+ memory_health
20
+ memory_observe
21
+ memory_governance
22
+ memory_consolidate
23
+ memory_reflect
24
+ memory_extract_entities
25
+ memory_link_entities
26
+ memory_rebuild_index
27
+ memory_capabilities
28
+ memory_snapshot
29
+ memory_snapshots
30
+ memory_rollback
31
+ memory_branch
32
+ memory_branches
33
+ memory_checkout
34
+ memory_branch_delete
35
+ memory_merge
36
+ memory_diff
37
+ )
38
+
39
+ log() {
40
+ printf '[memory-memoria] %s\n' "$*"
41
+ }
42
+
43
+ fail() {
44
+ printf '[memory-memoria] error: %s\n' "$*" >&2
45
+ exit 1
46
+ }
47
+
48
+ need_cmd() {
49
+ command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1"
50
+ }
51
+
52
+ usage() {
53
+ cat <<'EOF'
54
+ Remove the OpenClaw Memoria plugin and its OpenClaw config.
55
+
56
+ Usage:
57
+ bash scripts/uninstall-openclaw-memoria.sh [options]
58
+ curl -fsSL <raw-script-url> | bash -s --
59
+
60
+ Options:
61
+ --source-dir <path> Also delete this plugin checkout after uninstall.
62
+ --keep-source Keep the managed install directory on disk.
63
+ --help Show this help text.
64
+
65
+ Environment overrides:
66
+ OPENCLAW_HOME Optional target OpenClaw home.
67
+
68
+ What gets removed by default:
69
+ - plugins.entries["memory-memoria"]
70
+ - plugins.installs["memory-memoria"]
71
+ - plugins.allow entry for memory-memoria
72
+ - plugins.load.paths entries that point at this plugin
73
+ - tool policy entries for the Memoria tool surface
74
+ - managed companion skills in ~/.openclaw/skills: memoria-memory, memoria-recovery
75
+ - the default managed plugin dir: ~/.local/share/openclaw-plugins/openclaw-memoria
76
+
77
+ What gets restored:
78
+ - plugins.slots.memory -> memory-core
79
+ - plugins.entries["memory-core"].enabled -> true
80
+ EOF
81
+ }
82
+
83
+ config_file_path() {
84
+ if [[ -n "${OPENCLAW_HOME_VALUE}" ]]; then
85
+ printf '%s/.openclaw/openclaw.json' "${OPENCLAW_HOME_VALUE}"
86
+ else
87
+ printf '%s/.openclaw/openclaw.json' "${HOME}"
88
+ fi
89
+ }
90
+
91
+ skills_dir_path() {
92
+ if [[ -n "${OPENCLAW_HOME_VALUE}" ]]; then
93
+ printf '%s/.openclaw/skills' "${OPENCLAW_HOME_VALUE}"
94
+ else
95
+ printf '%s/.openclaw/skills' "${HOME}"
96
+ fi
97
+ }
98
+
99
+ OPENCLAW_HOME_VALUE="${OPENCLAW_HOME:-}"
100
+ SOURCE_DIR=""
101
+ KEEP_SOURCE=false
102
+
103
+ while [[ $# -gt 0 ]]; do
104
+ case "$1" in
105
+ --source-dir)
106
+ SOURCE_DIR="${2:?missing value for --source-dir}"
107
+ shift 2
108
+ ;;
109
+ --keep-source)
110
+ KEEP_SOURCE=true
111
+ shift
112
+ ;;
113
+ --help|-h)
114
+ usage
115
+ exit 0
116
+ ;;
117
+ *)
118
+ fail "Unknown option: $1"
119
+ ;;
120
+ esac
121
+ done
122
+
123
+ need_cmd node
124
+
125
+ CONFIG_FILE="$(config_file_path)"
126
+ MANAGED_SKILLS_DIR="$(skills_dir_path)"
127
+ MEMORIA_TOOL_NAMES_JSON="$(printf '%s\n' "${MEMORIA_TOOL_NAMES[@]}" | node -e 'const fs=require("node:fs"); const lines=fs.readFileSync(0,"utf8").trim().split(/\n+/).filter(Boolean); process.stdout.write(JSON.stringify(lines));')"
128
+
129
+ UNINSTALL_RESULT="$(
130
+ CONFIG_FILE="${CONFIG_FILE}" \
131
+ PLUGIN_ID="${PLUGIN_ID}" \
132
+ DEFAULT_INSTALL_DIR="${DEFAULT_INSTALL_DIR}" \
133
+ SOURCE_DIR_RAW="${SOURCE_DIR}" \
134
+ KEEP_SOURCE="${KEEP_SOURCE}" \
135
+ MEMORIA_TOOL_NAMES_JSON="${MEMORIA_TOOL_NAMES_JSON}" \
136
+ node - <<'NODE'
137
+ const fs = require("node:fs");
138
+ const path = require("node:path");
139
+
140
+ const configPath = path.resolve(process.env.CONFIG_FILE);
141
+ const pluginId = process.env.PLUGIN_ID;
142
+ const defaultInstallDir = path.resolve(process.env.DEFAULT_INSTALL_DIR);
143
+ const sourceDirRaw = process.env.SOURCE_DIR_RAW || "";
144
+ const keepSource = process.env.KEEP_SOURCE === "true";
145
+ const memoriaToolNames = JSON.parse(process.env.MEMORIA_TOOL_NAMES_JSON);
146
+
147
+ function readJson(filePath) {
148
+ if (!fs.existsSync(filePath)) {
149
+ return {};
150
+ }
151
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
152
+ }
153
+
154
+ function writeJson(filePath, value) {
155
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
156
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
157
+ }
158
+
159
+ function maybePath(rawPath) {
160
+ if (!rawPath) {
161
+ return null;
162
+ }
163
+ return path.resolve(rawPath.replace(/^~(?=$|\/|\\)/, process.env.HOME || "~"));
164
+ }
165
+
166
+ function manifestPluginId(candidatePath) {
167
+ try {
168
+ const manifestPath = path.join(candidatePath, "openclaw.plugin.json");
169
+ if (!fs.existsSync(manifestPath)) {
170
+ return null;
171
+ }
172
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
173
+ return typeof manifest.id === "string" ? manifest.id : null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ const data = readJson(configPath);
180
+ let changed = false;
181
+ const plugins = data.plugins && typeof data.plugins === "object" && !Array.isArray(data.plugins)
182
+ ? data.plugins
183
+ : {};
184
+ const tools = data.tools && typeof data.tools === "object" && !Array.isArray(data.tools)
185
+ ? data.tools
186
+ : {};
187
+
188
+ const candidatePaths = new Set([defaultInstallDir]);
189
+ const sourceDir = maybePath(sourceDirRaw);
190
+ if (sourceDir) {
191
+ candidatePaths.add(sourceDir);
192
+ }
193
+
194
+ if (plugins.entries && typeof plugins.entries === "object" && !Array.isArray(plugins.entries)) {
195
+ if (pluginId in plugins.entries) {
196
+ delete plugins.entries[pluginId];
197
+ changed = true;
198
+ }
199
+ const quotedKey = JSON.stringify(pluginId);
200
+ if (quotedKey in plugins.entries) {
201
+ delete plugins.entries[quotedKey];
202
+ changed = true;
203
+ }
204
+ if (Object.keys(plugins.entries).length === 0) {
205
+ delete plugins.entries;
206
+ }
207
+ }
208
+
209
+ if (plugins.installs && typeof plugins.installs === "object" && !Array.isArray(plugins.installs)) {
210
+ if (pluginId in plugins.installs) {
211
+ delete plugins.installs[pluginId];
212
+ changed = true;
213
+ }
214
+ if (Object.keys(plugins.installs).length === 0) {
215
+ delete plugins.installs;
216
+ }
217
+ }
218
+
219
+ if (Array.isArray(plugins.allow)) {
220
+ const next = plugins.allow.filter((entry) => entry !== pluginId);
221
+ if (next.length !== plugins.allow.length) {
222
+ plugins.allow = next;
223
+ changed = true;
224
+ }
225
+ if (plugins.allow.length === 0) {
226
+ delete plugins.allow;
227
+ }
228
+ }
229
+
230
+ if (plugins.load && typeof plugins.load === "object" && !Array.isArray(plugins.load) && Array.isArray(plugins.load.paths)) {
231
+ const next = plugins.load.paths.filter((entry) => {
232
+ if (typeof entry !== "string") {
233
+ return true;
234
+ }
235
+ const resolved = maybePath(entry);
236
+ if (resolved && candidatePaths.has(resolved)) {
237
+ changed = true;
238
+ return false;
239
+ }
240
+ if (resolved && manifestPluginId(resolved) === pluginId) {
241
+ changed = true;
242
+ return false;
243
+ }
244
+ if ((entry.includes("openclaw-memoria") || entry.includes(pluginId)) && (!resolved || !fs.existsSync(resolved))) {
245
+ changed = true;
246
+ return false;
247
+ }
248
+ return true;
249
+ });
250
+ plugins.load.paths = next;
251
+ if (plugins.load.paths.length === 0) {
252
+ delete plugins.load.paths;
253
+ }
254
+ if (Object.keys(plugins.load).length === 0) {
255
+ delete plugins.load;
256
+ }
257
+ }
258
+
259
+ if (plugins.slots && typeof plugins.slots === "object" && !Array.isArray(plugins.slots) && plugins.slots.memory === pluginId) {
260
+ plugins.slots.memory = "memory-core";
261
+ plugins.entries = plugins.entries && typeof plugins.entries === "object" && !Array.isArray(plugins.entries)
262
+ ? plugins.entries
263
+ : {};
264
+ const coreEntry = plugins.entries["memory-core"] && typeof plugins.entries["memory-core"] === "object" && !Array.isArray(plugins.entries["memory-core"])
265
+ ? plugins.entries["memory-core"]
266
+ : (plugins.entries["memory-core"] = {});
267
+ coreEntry.enabled = true;
268
+ changed = true;
269
+ }
270
+
271
+ for (const key of ["allow", "alsoAllow"]) {
272
+ if (Array.isArray(tools[key])) {
273
+ const next = tools[key].filter((entry) => !memoriaToolNames.includes(entry));
274
+ if (next.length !== tools[key].length) {
275
+ tools[key] = next;
276
+ changed = true;
277
+ }
278
+ if (tools[key].length === 0) {
279
+ delete tools[key];
280
+ }
281
+ }
282
+ }
283
+
284
+ if (data.agents && typeof data.agents === "object" && Array.isArray(data.agents.list)) {
285
+ for (const agent of data.agents.list) {
286
+ if (!agent || typeof agent !== "object" || Array.isArray(agent) || !agent.tools || typeof agent.tools !== "object" || Array.isArray(agent.tools)) {
287
+ continue;
288
+ }
289
+ for (const key of ["allow", "alsoAllow"]) {
290
+ if (Array.isArray(agent.tools[key])) {
291
+ const next = agent.tools[key].filter((entry) => !memoriaToolNames.includes(entry));
292
+ if (next.length !== agent.tools[key].length) {
293
+ agent.tools[key] = next;
294
+ changed = true;
295
+ }
296
+ if (agent.tools[key].length === 0) {
297
+ delete agent.tools[key];
298
+ }
299
+ }
300
+ }
301
+ if (Object.keys(agent.tools).length === 0) {
302
+ delete agent.tools;
303
+ }
304
+ }
305
+ }
306
+
307
+ if (Object.keys(plugins).length > 0) {
308
+ data.plugins = plugins;
309
+ } else {
310
+ delete data.plugins;
311
+ }
312
+ if (Object.keys(tools).length > 0) {
313
+ data.tools = tools;
314
+ } else {
315
+ delete data.tools;
316
+ }
317
+
318
+ if (changed && fs.existsSync(configPath)) {
319
+ writeJson(`${configPath}.bak`, readJson(configPath));
320
+ writeJson(configPath, data);
321
+ }
322
+
323
+ const deletedPaths = [];
324
+ if (!keepSource && fs.existsSync(defaultInstallDir)) {
325
+ fs.rmSync(defaultInstallDir, { recursive: true, force: true });
326
+ deletedPaths.push(defaultInstallDir);
327
+ }
328
+ if (sourceDir && fs.existsSync(sourceDir)) {
329
+ fs.rmSync(sourceDir, { recursive: true, force: true });
330
+ deletedPaths.push(sourceDir);
331
+ }
332
+
333
+ process.stdout.write(JSON.stringify({
334
+ ok: true,
335
+ configFile: configPath,
336
+ configChanged: changed,
337
+ deletedPaths
338
+ }));
339
+ NODE
340
+ )"
341
+
342
+ for skill_name in memoria-memory memoria-recovery; do
343
+ if [[ -d "${MANAGED_SKILLS_DIR}/${skill_name}" ]]; then
344
+ rm -rf "${MANAGED_SKILLS_DIR:?}/${skill_name}"
345
+ log "Removed managed skill: ${skill_name}"
346
+ fi
347
+ done
348
+
349
+ log "Removed OpenClaw Memoria plugin configuration"
350
+ log "${UNINSTALL_RESULT}"
351
+
352
+ cat <<EOF
353
+
354
+ Uninstall complete.
355
+
356
+ Config file: ${CONFIG_FILE}
357
+
358
+ Recommended follow-up checks:
359
+ cd ~
360
+ openclaw plugins list --json | rg 'memory-memoria|openclaw-memoria' || true
361
+ openclaw config get 'plugins.slots.memory'
362
+ EOF
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import net from "node:net";
5
+ import path from "node:path";
6
+
7
+ function readArg(name, fallback = "") {
8
+ const index = process.argv.indexOf(name);
9
+ if (index >= 0 && index + 1 < process.argv.length) {
10
+ return process.argv[index + 1];
11
+ }
12
+ return fallback;
13
+ }
14
+
15
+ const openclawBin = readArg("--openclaw-bin", "openclaw");
16
+ const memoriaBin = readArg("--memoria-bin", "memoria");
17
+ const configFile = path.resolve(readArg("--config-file", path.join(process.env.HOME || "", ".openclaw", "openclaw.json")));
18
+
19
+ function run(command, args) {
20
+ return execFileSync(command, args, {
21
+ encoding: "utf8",
22
+ stdio: ["ignore", "pipe", "pipe"],
23
+ }).trim();
24
+ }
25
+
26
+ function tryParseJson(raw) {
27
+ try {
28
+ return JSON.parse(raw);
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ function parseCommandJson(raw) {
35
+ const direct = tryParseJson(raw);
36
+ if (direct) {
37
+ return direct;
38
+ }
39
+ const start = raw.indexOf("{");
40
+ const end = raw.lastIndexOf("}");
41
+ if (start >= 0 && end > start) {
42
+ return tryParseJson(raw.slice(start, end + 1)) ?? raw;
43
+ }
44
+ return raw;
45
+ }
46
+
47
+ function parsePort(rawProtocol, rawPort) {
48
+ if (rawPort) {
49
+ return Number.parseInt(rawPort, 10);
50
+ }
51
+ return rawProtocol === "mysql:" ? 3306 : 80;
52
+ }
53
+
54
+ function checkTcp(host, port, timeoutMs = 1500) {
55
+ return new Promise((resolve) => {
56
+ const socket = new net.Socket();
57
+ let settled = false;
58
+
59
+ const finish = (value) => {
60
+ if (settled) {
61
+ return;
62
+ }
63
+ settled = true;
64
+ socket.destroy();
65
+ resolve(value);
66
+ };
67
+
68
+ socket.setTimeout(timeoutMs);
69
+ socket.once("connect", () => finish(true));
70
+ socket.once("timeout", () => finish(false));
71
+ socket.once("error", () => finish(false));
72
+ socket.connect(port, host);
73
+ });
74
+ }
75
+
76
+ if (!fs.existsSync(configFile)) {
77
+ throw new Error(`OpenClaw config file not found: ${configFile}`);
78
+ }
79
+
80
+ const config = JSON.parse(fs.readFileSync(configFile, "utf8"));
81
+ const pluginEntry = config?.plugins?.entries?.["memory-memoria"];
82
+ if (!pluginEntry || pluginEntry.enabled !== true) {
83
+ throw new Error("plugins.entries.memory-memoria is not enabled");
84
+ }
85
+
86
+ const pluginConfig = pluginEntry.config ?? {};
87
+ if (pluginConfig.memoriaExecutable == null) {
88
+ throw new Error("plugins.entries.memory-memoria.config.memoriaExecutable is missing");
89
+ }
90
+
91
+ const resolvedMemoriaBin = pluginConfig.memoriaExecutable || memoriaBin;
92
+ const openclawVersion = run(openclawBin, ["--version"]);
93
+ const configValidation = tryParseJson(run(openclawBin, ["config", "validate", "--json"]));
94
+ const memoriaVersion = run(resolvedMemoriaBin, ["--version"]);
95
+ const capabilities = run(openclawBin, ["memoria", "capabilities"]);
96
+
97
+ if (!capabilities.includes("memory_branch") || !capabilities.includes("memory_snapshot")) {
98
+ throw new Error("OpenClaw memoria capabilities output is missing expected Rust Memoria tools");
99
+ }
100
+
101
+ const result = {
102
+ ok: true,
103
+ configFile,
104
+ openclawVersion,
105
+ memoriaVersion,
106
+ memoriaExecutable: pluginConfig.memoriaExecutable,
107
+ configValidation,
108
+ deepChecks: {
109
+ backend: pluginConfig.backend ?? "embedded",
110
+ performed: false,
111
+ skipped: null,
112
+ },
113
+ };
114
+
115
+ if ((pluginConfig.backend ?? "embedded") === "embedded" && typeof pluginConfig.dbUrl === "string") {
116
+ const dbUrl = new URL(pluginConfig.dbUrl);
117
+ const dbReachable = await checkTcp(
118
+ dbUrl.hostname || "127.0.0.1",
119
+ parsePort(dbUrl.protocol, dbUrl.port),
120
+ );
121
+
122
+ result.deepChecks.dbUrl = pluginConfig.dbUrl;
123
+ result.deepChecks.dbReachable = dbReachable;
124
+
125
+ if (dbReachable) {
126
+ const userId =
127
+ typeof pluginConfig.defaultUserId === "string" && pluginConfig.defaultUserId.trim()
128
+ ? pluginConfig.defaultUserId.trim()
129
+ : "openclaw-user";
130
+ const statsRaw = run(openclawBin, ["memoria", "stats", "--user-id", userId]);
131
+ const listRaw = run(openclawBin, ["ltm", "list", "--limit", "1", "--user-id", userId]);
132
+ result.deepChecks.performed = true;
133
+ result.deepChecks.userId = userId;
134
+ result.deepChecks.stats = parseCommandJson(statsRaw);
135
+ result.deepChecks.list = parseCommandJson(listRaw);
136
+ } else {
137
+ result.deepChecks.skipped = "Embedded database is not reachable; skipped stats/list verification.";
138
+ }
139
+ } else {
140
+ result.deepChecks.skipped = "Deep verification is only automatic for embedded mode.";
141
+ }
142
+
143
+ console.log(
144
+ JSON.stringify(
145
+ result,
146
+ null,
147
+ 2,
148
+ ),
149
+ );
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: memoria-memory
3
+ description: |
4
+ Use Memoria for durable user/project memory in OpenClaw.
5
+ Triggers: "remember this", "save to memory", "what do you remember",
6
+ "forget this", "correct memory", "update memory", "use memoria".
7
+ ---
8
+
9
+ # Memoria Memory
10
+
11
+ Use Memoria tools for durable memory. Do not default to `MEMORY.md` or `memory/YYYY-MM-DD.md` unless the user explicitly asks for file-based memory.
12
+
13
+ ## When to use
14
+
15
+ - The user asks you to remember a fact, preference, decision, or workflow.
16
+ - The user asks what you already know about them, the project, or a prior session.
17
+ - The user asks to correct, update, or delete stored memory.
18
+
19
+ ## Store
20
+
21
+ 1. Choose the smallest durable fact worth keeping.
22
+ 2. Use the most specific tool available:
23
+ - `memory_profile` for stable user preferences or profile traits
24
+ - `memory_store` for facts, procedures, and project knowledge
25
+ 3. Prefer short, atomic entries.
26
+ 4. After storing something important, verify with `memory_recall` or `memory_search`.
27
+
28
+ ## Recall
29
+
30
+ 1. When the user asks "what do you know", "what do you remember", or refers to a prior session, query Memoria first.
31
+ 2. Use `memory_recall` for semantic retrieval and `memory_get` only when you already have a specific memory id.
32
+
33
+ ## Repair
34
+
35
+ 1. If the user says a memory is wrong, use `memory_correct`.
36
+ 2. If the user wants it removed, use `memory_forget` or `memory_purge`.
37
+ 3. After repair, verify with `memory_recall` or `memory_search`.
38
+
39
+ ## Rules
40
+
41
+ - Do not claim only `memory_search` and `memory_get` exist when other `memory_*` tools are available.
42
+ - Do not store transient small talk unless the user asks or it is clearly a stable preference.
43
+ - Prefer Memoria for durable cross-session memory; prefer workspace files only for explicit file-based notes.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: memoria-recovery
3
+ description: |
4
+ Recover from accidental memory loss with snapshots and rollback.
5
+ Triggers: "restore memory", "rollback memory", "undo deleted memory",
6
+ "recover deleted memory", "take a memory snapshot".
7
+ ---
8
+
9
+ # Memoria Recovery
10
+
11
+ Use Memoria snapshot tools when memory state needs a checkpoint or a rollback.
12
+
13
+ ## Before risky changes
14
+
15
+ 1. If you are about to bulk-delete, purge, or rewrite memory, create a snapshot first with `memory_snapshot`.
16
+ 2. Tell the user a checkpoint exists and can be restored.
17
+
18
+ ## After accidental deletion or corruption
19
+
20
+ 1. Inspect available recovery points with `memory_snapshots`.
21
+ 2. Choose the most recent good snapshot.
22
+ 3. Restore with `memory_rollback`.
23
+ 4. Verify recovery with `memory_recall`, `memory_list`, or `memory_stats`.
24
+
25
+ ## Rules
26
+
27
+ - Prefer rollback over manually re-creating many deleted memories.
28
+ - If the user wants a selective logical fix rather than full restore, use `memory_correct` or `memory_forget` instead.
29
+ - After rollback, state what was restored and what you verified.