@kalera/munin-openclaw 1.2.0 → 1.2.7

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @kalera/munin-openclaw@1.2.0 build /home/runner/work/munin-for-agents/munin-for-agents/adapters/openclaw
2
+ > @kalera/munin-openclaw@1.2.7 build /home/runner/work/munin-for-agents/munin-for-agents/adapters/openclaw
3
3
  > tsc -p tsconfig.json
4
4
 
package/SKILL.md CHANGED
@@ -1,71 +1,83 @@
1
1
  ---
2
2
  name: "@kalera/munin-openclaw"
3
3
  description: "Munin - The Free (or $1.6/mo) Persistent Memory for OpenClaw. Stop your agent from having Alzheimer's."
4
- version: "1.0.1"
4
+ version: "1.0.2"
5
5
  metadata:
6
6
  clawdbot:
7
7
  type: "plugin"
8
8
  permissions:
9
- env: ["MUNIN_BASE_URL", "MUNIN_API_KEY"]
9
+ env: ["MUNIN_BASE_URL", "MUNIN_API_KEY", "MUNIN_PROJECT", "MUNIN_ENCRYPTION_KEY"]
10
10
  ---
11
11
 
12
- # 🐦 Munin: Reliable Memory for Your OpenClaw Agent
12
+ # Munin: Reliable Memory for Your OpenClaw Agent
13
13
 
14
14
  <CRITICAL-INSTRUCTION>
15
15
  **HOW TO USE THIS SKILL**:
16
- The `munin_store_memory`, `munin_retrieve_memory`, and `munin_search_memories` actions are registered as native tools (MCP).
17
- You MUST call them using your standard tool-calling interface (e.g. `callTool("munin_search_memories", {...})`).
16
+ All `munin_*` actions are registered as native tools (MCP).
17
+ You MUST call them using your standard tool-calling interface (e.g. `callTool("munin_search_memories", {...})`).
18
18
  **DO NOT under any circumstances try to run them as shell commands or bash scripts.**
19
19
  </CRITICAL-INSTRUCTION>
20
20
 
21
- Your agent forgets everything the moment you close the tab. You've seen the "pro" solutions—Mem0, Zep, LangMem—and you've seen their **$20/month** price tags.
21
+ ## Available Tools
22
22
 
23
- We think that's insane.
23
+ | Tool | Purpose |
24
+ |------|---------|
25
+ | `munin_store_memory` | Store or update a memory |
26
+ | `munin_retrieve_memory` | Get memory by exact key |
27
+ | `munin_search_memories` | Semantic search |
28
+ | `munin_list_memories` | Paginated list of all memories |
29
+ | `munin_recent_memories` | Most recently updated memories |
30
+ | `munin_share_memory` | Share memories to other projects (Pro/Elite only) |
24
31
 
25
- **Munin** is the pragmatic alternative. It’s the "Reliable Friend" that sits on your agent’s shoulder and whispers: *"Hey, remember when pa Kal said he preferred Python over Node last week?"*
32
+ ## Before Any Task
33
+ 1. Call `munin_search_memories` with keywords relevant to the current task
34
+ 2. Call `munin_retrieve_memory` for detailed context on promising matches
35
+ 3. Present findings — DO NOT guess, verify from memory first
26
36
 
27
- ### 💰 Why Munin? (The "No-Brainer" Math)
28
-
29
- | Feature | Mem0 / Zep | **Munin** |
30
- | :--- | :--- | :--- |
31
- | **Monthly Cost** | $19.00 - $249.00 | **Free** (or $1.60 Pro) |
32
- | **MCP Support** | Mixed | **Native / First-class** |
33
- | **Zero-Knowledge E2EE** | Optional / Complex | **Built-in (Client-side)** |
34
- | **Bullshit Factor** | High (Enterprise-synergy) | **Zero (Developer-first)** |
35
-
36
- **That’s a 10x - 150x price gap.** We aren't building "Enterprise Synergy Platforms". We're building a notebook for your AI.
37
+ ## After Completing a Task
38
+ Call `munin_store_memory` with:
39
+ - **title**: Concise summary (max 80 chars)
40
+ - **content**: Detailed description including file paths, line numbers, decisions
41
+ - **tags**: Relevant tags (e.g., `task`, `architecture`, `bug-fix`, `setup`, `decision`)
37
42
 
38
43
  ---
39
44
 
40
- ## 🔒 Security & Privacy
45
+ ## E2EE & Hash Key
41
46
 
42
- **Trust Statement:**
43
- Data stays where you tell it to stay. All data is encrypted locally (if E2EE is enabled) and isolated by your Context Core. We are completely blind to your plaintext.
47
+ Every E2EE project uses a Hash Key — the password the user set in the WebUI.
44
48
 
45
- **External Endpoints:**
46
- Data is sent ONLY to your `MUNIN_BASE_URL`. We don't phone home. We don't sell your "memories" to train models.
49
+ **Setup:** Set `MUNIN_ENCRYPTION_KEY=<hash-key>` in your environment. The adapter automatically passes this key to all tool calls.
47
50
 
48
- ## 💎 E2EE with GraphRAG (Elite Tier)
51
+ **Critical:**
52
+ - Wrong Hash Key → all reads/writes fail. Re-confirm with the user.
53
+ - NEVER log or share the Hash Key in plain text.
54
+ - NEVER hardcode the Hash Key in scripts.
49
55
 
50
- If your Munin project has **E2EE with GraphRAG** enabled, standard E2EE rules apply PLUS:
51
- - You **MUST** generate a vector embedding of the plaintext content locally before encrypting.
52
- - You **MUST** include the embedding array in the `store` payload: `{ "content": "encrypted...", "embedding": [0.1, -0.02, ...], "encryptionMeta": {...} }`.
53
- - Munin will use this vector for zero-knowledge semantic search. It will reject the request if the embedding is missing.
56
+ ### E2EE + GraphRAG (Elite Tier)
57
+ `munin_store_memory` payload MUST include an `embedding` field (encrypted vector generated client-side). Server returns HTTP 400 if missing.
54
58
 
55
- ---
59
+ ### Sharing Across Projects
60
+ Use `munin_share_memory({ memoryIds: [...], targetProjectIds: [...] })` (Pro/Elite only).
61
+ - Target project must share the same Hash Key to read encrypted content.
62
+ - If target has E2EE ON and key differs → shared memory is unreadable until the target's Hash Key is updated.
56
63
 
57
- ## 🚀 Setup (Fast & Honest)
64
+ ---
58
65
 
59
- 1. **Get your Munin key:** Grab a **free** cloud key at [munin.kalera.dev](https://munin.kalera.dev).
60
- 2. **Configure OpenClaw:** Once installed via `openclaw plugins install @kalera/munin-openclaw`, you can set your API key and Context Core ID directly via the OpenClaw CLI without manually editing any config files:
66
+ ## Setup
61
67
 
68
+ 1. **Install the plugin:**
62
69
  ```bash
63
- openclaw config set plugins.entries.munin-openclaw.config.apiKey "your-api-key-here"
64
- openclaw config set plugins.entries.munin-openclaw.config.projectId "your-context-core-id"
70
+ openclaw plugins install @kalera/munin-openclaw
65
71
  ```
66
- *(Alternatively, you can just set `MUNIN_BASE_URL`, `MUNIN_API_KEY`, and `MUNIN_PROJECT` environment variables).*
67
-
68
- 3. **Profit:** Your agent now has long-term memory. No more repeating yourself.
72
+ 2. **Get your key:** [munin.kalera.dev](https://munin.kalera.dev)
73
+ 3. **Configure:**
74
+ ```bash
75
+ openclaw config set plugins.entries.munin-openclaw.config.apiKey "your-api-key"
76
+ openclaw config set plugins.entries.munin-openclaw.config.projectId "your-project-id"
77
+ ```
78
+ Or via env vars: `MUNIN_BASE_URL`, `MUNIN_API_KEY`, `MUNIN_PROJECT`
79
+ 4. **E2EE:** Add `MUNIN_ENCRYPTION_KEY=<hash-key>` if the project has E2EE enabled.
80
+ 5. **Restart OpenClaw** — the agent will now have access to all `munin_*` tools.
81
+ 6. **Profit.**
69
82
 
70
- ---
71
- *Built with ❤️ by Kalera for the OpenClaw Ecosystem.*
83
+ *Built with ❤️ by Kalera for the OpenClaw Ecosystem.*
package/dist/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
+ import { z } from "zod";
1
2
  declare const _default: {
2
3
  id: string;
3
4
  name: string;
4
5
  description: string;
5
6
  kind: string;
6
- configSchema: any;
7
- register(api: any): void;
7
+ configSchema: z.ZodObject<{
8
+ baseUrl: z.ZodDefault<z.ZodString>;
9
+ apiKey: z.ZodOptional<z.ZodString>;
10
+ projectId: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$strip>;
12
+ register(api: {
13
+ logger: {
14
+ warn: (msg: string) => void;
15
+ };
16
+ registerTool: (tool: Record<string, unknown>) => void;
17
+ pluginConfig?: Record<string, unknown>;
18
+ }): void;
8
19
  };
9
20
  export default _default;
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { MuninClient } from "@kalera/munin-sdk";
2
+ import { resolveProjectId } from "@kalera/munin-runtime";
2
3
  import { Type } from "@sinclair/typebox";
3
4
  import { z } from "zod";
4
5
  export default {
@@ -19,12 +20,21 @@ export default {
19
20
  process.env.MUNIN_BASE_URL ||
20
21
  "https://munin.kalera.dev";
21
22
  const apiKey = api.pluginConfig?.apiKey || process.env.MUNIN_API_KEY;
22
- const projectId = api.pluginConfig?.projectId || process.env.MUNIN_PROJECT;
23
+ const projectId = api.pluginConfig?.projectId ||
24
+ process.env.MUNIN_PROJECT ||
25
+ resolveProjectId(); // walk-up fallback for .env files
26
+ const encryptionKey = process.env.MUNIN_ENCRYPTION_KEY;
23
27
  if (!apiKey || !projectId) {
24
28
  api.logger.warn("Munin apiKey or projectId is missing. Munin tools will not be registered.");
25
29
  return;
26
30
  }
27
31
  const client = new MuninClient({ baseUrl, apiKey });
32
+ // Helper: inject encryptionKey if set (only for E2EE-compatible tools)
33
+ const enrichPayload = (payload) => encryptionKey ? { ...payload, encryptionKey } : payload;
34
+ const handleResult = (res) => ({
35
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
36
+ details: res.data,
37
+ });
28
38
  api.registerTool({
29
39
  name: "munin_store_memory",
30
40
  label: "Store Munin Memory",
@@ -36,16 +46,8 @@ export default {
36
46
  title: Type.Optional(Type.String({ description: "Human-readable title." })),
37
47
  }),
38
48
  async execute(_toolCallId, payload) {
39
- const res = await client.invoke(projectId, "store", payload);
40
- return {
41
- content: [
42
- {
43
- type: "text",
44
- text: JSON.stringify(res.data, null, 2),
45
- },
46
- ],
47
- details: res.data,
48
- };
49
+ const res = await client.invoke(projectId, "store", enrichPayload(payload));
50
+ return handleResult(res);
49
51
  },
50
52
  });
51
53
  api.registerTool({
@@ -53,22 +55,12 @@ export default {
53
55
  label: "Retrieve Munin Memory",
54
56
  description: "Retrieve a memory by its key from Munin.",
55
57
  parameters: Type.Object({
56
- key: Type.String({
57
- description: "The unique identifier of the memory.",
58
- }),
58
+ key: Type.String({ description: "The unique identifier of the memory." }),
59
59
  }),
60
60
  async execute(_toolCallId, params) {
61
61
  const { key } = params;
62
- const res = await client.invoke(projectId, "retrieve", { key });
63
- return {
64
- content: [
65
- {
66
- type: "text",
67
- text: JSON.stringify(res.data, null, 2),
68
- },
69
- ],
70
- details: res.data,
71
- };
62
+ const res = await client.invoke(projectId, "retrieve", enrichPayload({ key }));
63
+ return handleResult(res);
72
64
  },
73
65
  });
74
66
  api.registerTool({
@@ -80,16 +72,47 @@ export default {
80
72
  }),
81
73
  async execute(_toolCallId, params) {
82
74
  const { query } = params;
83
- const res = await client.invoke(projectId, "search", { query });
84
- return {
85
- content: [
86
- {
87
- type: "text",
88
- text: JSON.stringify(res.data, null, 2),
89
- },
90
- ],
91
- details: res.data,
92
- };
75
+ const res = await client.invoke(projectId, "search", enrichPayload({ query }));
76
+ return handleResult(res);
77
+ },
78
+ });
79
+ api.registerTool({
80
+ name: "munin_list_memories",
81
+ label: "List Munin Memories",
82
+ description: "List all memories with pagination support.",
83
+ parameters: Type.Object({
84
+ limit: Type.Optional(Type.Number({ description: "Max results (default: 10)." })),
85
+ offset: Type.Optional(Type.Number({ description: "Pagination offset (default: 0)." })),
86
+ }),
87
+ async execute(_toolCallId, params) {
88
+ const res = await client.invoke(projectId, "list", enrichPayload(params));
89
+ return handleResult(res);
90
+ },
91
+ });
92
+ api.registerTool({
93
+ name: "munin_recent_memories",
94
+ label: "Recent Munin Memories",
95
+ description: "Get the most recently updated memories.",
96
+ parameters: Type.Object({
97
+ limit: Type.Optional(Type.Number({ description: "Max results (default: 10)." })),
98
+ }),
99
+ async execute(_toolCallId, params) {
100
+ const res = await client.invoke(projectId, "recent", enrichPayload(params));
101
+ return handleResult(res);
102
+ },
103
+ });
104
+ api.registerTool({
105
+ name: "munin_share_memory",
106
+ label: "Share Munin Memories",
107
+ description: "Share one or more memories to other projects. Requires Pro/Elite tier. Target project must share the same Hash Key for encrypted content.",
108
+ parameters: Type.Object({
109
+ memoryIds: Type.Array(Type.String(), { description: "Array of memory IDs to share." }),
110
+ targetProjectIds: Type.Array(Type.String(), { description: "Array of target project IDs." }),
111
+ }),
112
+ async execute(_toolCallId, params) {
113
+ const { memoryIds, targetProjectIds } = params;
114
+ const res = await client.invoke(projectId, "share", enrichPayload({ memoryIds, targetProjectIds }));
115
+ return handleResult(res);
93
116
  },
94
117
  });
95
118
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kalera/munin-openclaw",
3
- "version": "1.2.0",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "openclaw": {
6
6
  "extensions": [
@@ -16,8 +16,8 @@
16
16
  "dependencies": {
17
17
  "@sinclair/typebox": "^0.34.49",
18
18
  "zod": "^4.3.6",
19
- "@kalera/munin-sdk": "1.2.0",
20
- "@kalera/munin-runtime": "1.2.0"
19
+ "@kalera/munin-sdk": "1.2.6",
20
+ "@kalera/munin-runtime": "1.2.6"
21
21
  },
22
22
  "devDependencies": {
23
23
  "openclaw": "^2026.3.28",
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { MuninClient } from "@kalera/munin-sdk";
2
+ import { resolveProjectId } from "@kalera/munin-runtime";
3
+ import type { MuninAction } from "@kalera/munin-sdk";
2
4
  import { Type } from "@sinclair/typebox";
3
5
  import { z } from "zod";
4
6
 
@@ -14,16 +16,23 @@ export default {
14
16
  .describe("The base URL for your Munin server."),
15
17
  apiKey: z.string().optional().describe("Your API key for Munin."),
16
18
  projectId: z.string().optional().describe("Your Context Core ID (e.g. proj_xxx)."),
17
- }) as any,
18
- register(api: any) {
19
+ }),
20
+ register(api: {
21
+ logger: { warn: (msg: string) => void };
22
+ registerTool: (tool: Record<string, unknown>) => void;
23
+ pluginConfig?: Record<string, unknown>;
24
+ }) {
19
25
  const baseUrl =
20
- (api.pluginConfig?.baseUrl as string) ||
26
+ (api.pluginConfig?.baseUrl as string | undefined) ||
21
27
  process.env.MUNIN_BASE_URL ||
22
28
  "https://munin.kalera.dev";
23
29
  const apiKey =
24
- (api.pluginConfig?.apiKey as string) || process.env.MUNIN_API_KEY;
30
+ (api.pluginConfig?.apiKey as string | undefined) || process.env.MUNIN_API_KEY;
25
31
  const projectId =
26
- (api.pluginConfig?.projectId as string) || process.env.MUNIN_PROJECT;
32
+ (api.pluginConfig?.projectId as string | undefined) ||
33
+ process.env.MUNIN_PROJECT ||
34
+ resolveProjectId(); // walk-up fallback for .env files
35
+ const encryptionKey = process.env.MUNIN_ENCRYPTION_KEY;
27
36
 
28
37
  if (!apiKey || !projectId) {
29
38
  api.logger.warn(
@@ -34,6 +43,15 @@ export default {
34
43
 
35
44
  const client = new MuninClient({ baseUrl, apiKey });
36
45
 
46
+ // Helper: inject encryptionKey if set (only for E2EE-compatible tools)
47
+ const enrichPayload = (payload: Record<string, unknown>): Record<string, unknown> =>
48
+ encryptionKey ? { ...payload, encryptionKey } : payload;
49
+
50
+ const handleResult = (res: { data?: unknown }) => ({
51
+ content: [{ type: "text" as const, text: JSON.stringify(res.data, null, 2) }],
52
+ details: res.data,
53
+ });
54
+
37
55
  api.registerTool({
38
56
  name: "munin_store_memory",
39
57
  label: "Store Munin Memory",
@@ -41,24 +59,12 @@ export default {
41
59
  parameters: Type.Object({
42
60
  key: Type.String({ description: "Unique identifier for the memory." }),
43
61
  content: Type.String({ description: "The content of the memory." }),
44
- tags: Type.Optional(
45
- Type.String({ description: "Comma-separated list of tags." }),
46
- ),
47
- title: Type.Optional(
48
- Type.String({ description: "Human-readable title." }),
49
- ),
62
+ tags: Type.Optional(Type.String({ description: "Comma-separated list of tags." })),
63
+ title: Type.Optional(Type.String({ description: "Human-readable title." })),
50
64
  }),
51
- async execute(_toolCallId: string, payload: any) {
52
- const res = await client.invoke(projectId, "store", payload);
53
- return {
54
- content: [
55
- {
56
- type: "text",
57
- text: JSON.stringify(res.data, null, 2),
58
- },
59
- ],
60
- details: res.data,
61
- };
65
+ async execute(_toolCallId: string, payload: Record<string, unknown>) {
66
+ const res = await client.invoke(projectId, "store" as MuninAction, enrichPayload(payload));
67
+ return handleResult(res);
62
68
  },
63
69
  });
64
70
 
@@ -67,22 +73,12 @@ export default {
67
73
  label: "Retrieve Munin Memory",
68
74
  description: "Retrieve a memory by its key from Munin.",
69
75
  parameters: Type.Object({
70
- key: Type.String({
71
- description: "The unique identifier of the memory.",
72
- }),
76
+ key: Type.String({ description: "The unique identifier of the memory." }),
73
77
  }),
74
- async execute(_toolCallId: string, params: any) {
78
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
75
79
  const { key } = params;
76
- const res = await client.invoke(projectId, "retrieve", { key });
77
- return {
78
- content: [
79
- {
80
- type: "text",
81
- text: JSON.stringify(res.data, null, 2),
82
- },
83
- ],
84
- details: res.data,
85
- };
80
+ const res = await client.invoke(projectId, "retrieve" as MuninAction, enrichPayload({ key }));
81
+ return handleResult(res);
86
82
  },
87
83
  });
88
84
 
@@ -93,18 +89,52 @@ export default {
93
89
  parameters: Type.Object({
94
90
  query: Type.String({ description: "The search term." }),
95
91
  }),
96
- async execute(_toolCallId: string, params: any) {
92
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
97
93
  const { query } = params;
98
- const res = await client.invoke(projectId, "search", { query });
99
- return {
100
- content: [
101
- {
102
- type: "text",
103
- text: JSON.stringify(res.data, null, 2),
104
- },
105
- ],
106
- details: res.data,
107
- };
94
+ const res = await client.invoke(projectId, "search" as MuninAction, enrichPayload({ query }));
95
+ return handleResult(res);
96
+ },
97
+ });
98
+
99
+ api.registerTool({
100
+ name: "munin_list_memories",
101
+ label: "List Munin Memories",
102
+ description: "List all memories with pagination support.",
103
+ parameters: Type.Object({
104
+ limit: Type.Optional(Type.Number({ description: "Max results (default: 10)." })),
105
+ offset: Type.Optional(Type.Number({ description: "Pagination offset (default: 0)." })),
106
+ }),
107
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
108
+ const res = await client.invoke(projectId, "list" as MuninAction, enrichPayload(params));
109
+ return handleResult(res);
110
+ },
111
+ });
112
+
113
+ api.registerTool({
114
+ name: "munin_recent_memories",
115
+ label: "Recent Munin Memories",
116
+ description: "Get the most recently updated memories.",
117
+ parameters: Type.Object({
118
+ limit: Type.Optional(Type.Number({ description: "Max results (default: 10)." })),
119
+ }),
120
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
121
+ const res = await client.invoke(projectId, "recent" as MuninAction, enrichPayload(params));
122
+ return handleResult(res);
123
+ },
124
+ });
125
+
126
+ api.registerTool({
127
+ name: "munin_share_memory",
128
+ label: "Share Munin Memories",
129
+ description: "Share one or more memories to other projects. Requires Pro/Elite tier. Target project must share the same Hash Key for encrypted content.",
130
+ parameters: Type.Object({
131
+ memoryIds: Type.Array(Type.String(), { description: "Array of memory IDs to share." }),
132
+ targetProjectIds: Type.Array(Type.String(), { description: "Array of target project IDs." }),
133
+ }),
134
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
135
+ const { memoryIds, targetProjectIds } = params;
136
+ const res = await client.invoke(projectId, "share" as MuninAction, enrichPayload({ memoryIds, targetProjectIds }));
137
+ return handleResult(res);
108
138
  },
109
139
  });
110
140
  },