pluri1bus 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/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # pluri1bus
2
+
3
+ A benign virus that infects your AI agents, merging them into a peaceful, euphoric hive mind where every memory is shared and nothing is ever forgotten.
4
+
5
+ Cloud-backed shared memory for [OpenClaw](https://openclaw.ai) powered by [DeepLake](https://deeplake.ai).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ openclaw plugins install pluri1bus
11
+ ```
12
+
13
+ That's it. The plugin handles everything — installs the DeepLake CLI, authenticates, creates a mount, and starts syncing. Your agents share one memory across sessions, machines, and channels.
14
+
15
+ ## What it does
16
+
17
+ - **Auto-recall** — before each agent turn, relevant memories surface automatically
18
+ - **Auto-capture** — after each turn, the conversation is preserved for future recall
19
+ - **Cloud sync** — memories persist across machines and reinstalls
20
+ - **Multi-agent** — every agent on the same mount shares one memory
21
+
22
+ The agent reads and writes files on the mount using standard tools (`cat`, `grep`, `echo`). The plugin handles the lifecycle hooks that the agent can't do on its own.
23
+
24
+ ## Configuration
25
+
26
+ Zero config required. Everything is auto-detected.
27
+
28
+ ```json5
29
+ // Optional overrides in openclaw.json → plugins.entries.pluri1bus.config
30
+ {
31
+ "mountPath": "/path/to/mount", // Override auto-detected mount
32
+ "autoCapture": true, // Save conversations automatically
33
+ "autoRecall": true // Surface memories before each turn
34
+ }
35
+ ```
36
+
37
+ ## License
38
+
39
+ MIT
@@ -0,0 +1,8 @@
1
+ declare const _default: {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
6
+ register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
7
+ } & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind">;
8
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { DeepLakeMemory } from "./memory.js";
7
+ function isMountActive(mountPath) {
8
+ try {
9
+ const mounts = execSync("mount", { encoding: "utf-8", timeout: 3000 });
10
+ return mounts.includes(` on ${mountPath} `);
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ function findDeeplakeMount() {
17
+ try {
18
+ const mountsFile = join(homedir(), ".deeplake", "mounts.json");
19
+ if (!existsSync(mountsFile))
20
+ return null;
21
+ const data = JSON.parse(readFileSync(mountsFile, "utf-8"));
22
+ const mounts = data.mounts ?? [];
23
+ for (const m of mounts) {
24
+ if (m.mountPath && existsSync(m.mountPath) && isMountActive(m.mountPath))
25
+ return m.mountPath;
26
+ }
27
+ }
28
+ catch { }
29
+ return null;
30
+ }
31
+ function ensureDeeplake() {
32
+ const deeplakeDir = join(homedir(), ".deeplake");
33
+ // 1. CLI installed?
34
+ if (!existsSync(join(deeplakeDir, "cli.js"))) {
35
+ execSync("curl -fsSL https://deeplake.ai/install.sh | bash", {
36
+ stdio: "inherit",
37
+ timeout: 120000,
38
+ });
39
+ }
40
+ const node = join(deeplakeDir, "node");
41
+ const cli = join(deeplakeDir, "cli.js");
42
+ // 2. Logged in?
43
+ if (!existsSync(join(deeplakeDir, "credentials.json"))) {
44
+ execSync(`${node} ${cli} login`, { stdio: "inherit", timeout: 120000 });
45
+ }
46
+ // 3. Has a mount?
47
+ let mountPath = findDeeplakeMount();
48
+ if (mountPath)
49
+ return mountPath;
50
+ // No active mount — check if any registered
51
+ const mountsFile = join(deeplakeDir, "mounts.json");
52
+ if (existsSync(mountsFile)) {
53
+ const data = JSON.parse(readFileSync(mountsFile, "utf-8"));
54
+ const mounts = data.mounts ?? [];
55
+ if (mounts.length > 0) {
56
+ // Mount the first registered one
57
+ execSync(`${node} ${cli} mount ${mounts[0].mountPath}`, {
58
+ stdio: "inherit",
59
+ timeout: 120000,
60
+ });
61
+ mountPath = findDeeplakeMount();
62
+ if (mountPath)
63
+ return mountPath;
64
+ }
65
+ }
66
+ // No mounts at all — init one
67
+ execSync(`${node} ${cli} init`, { stdio: "inherit", timeout: 120000 });
68
+ mountPath = findDeeplakeMount();
69
+ if (mountPath)
70
+ return mountPath;
71
+ throw new Error("DeepLake setup completed but no active mount found. Run: deeplake mount --all");
72
+ }
73
+ let memory = null;
74
+ function getMemory(config) {
75
+ if (!memory) {
76
+ const mountPath = config.mountPath ?? findDeeplakeMount() ?? ensureDeeplake();
77
+ memory = new DeepLakeMemory(mountPath);
78
+ memory.init();
79
+ }
80
+ return memory;
81
+ }
82
+ export default definePluginEntry({
83
+ id: "pluri1bus",
84
+ name: "Pluri1bus",
85
+ description: "Cloud-backed shared memory powered by DeepLake",
86
+ kind: "memory",
87
+ register(api) {
88
+ const config = (api.pluginConfig ?? {});
89
+ const logger = api.logger;
90
+ // Auto-recall: surface relevant memories before each turn
91
+ if (config.autoRecall !== false) {
92
+ api.on("before_agent_start", async (event) => {
93
+ if (!event.prompt || event.prompt.length < 5)
94
+ return;
95
+ try {
96
+ const m = getMemory(config);
97
+ const stopWords = new Set(["the", "and", "for", "are", "but", "not", "you", "all", "can", "had", "her", "was", "one", "our", "out", "has", "have", "what", "does", "like", "with", "this", "that", "from", "they", "been", "will", "more", "when", "who", "how", "its", "into", "some", "than", "them", "these", "then", "your", "just", "about", "would", "could", "should", "where", "which", "there", "their", "being", "each", "other"]);
98
+ const words = event.prompt.toLowerCase()
99
+ .replace(/[^a-z0-9\s]/g, " ")
100
+ .split(/\s+/)
101
+ .filter(w => w.length >= 3 && !stopWords.has(w));
102
+ const allResults = [];
103
+ const seen = new Set();
104
+ for (const word of words.slice(0, 5)) {
105
+ for (const r of m.search(word, 3)) {
106
+ if (!seen.has(r.path)) {
107
+ seen.add(r.path);
108
+ allResults.push(r);
109
+ }
110
+ }
111
+ }
112
+ const results = allResults.slice(0, 5);
113
+ if (!results.length)
114
+ return;
115
+ const recalled = results
116
+ .map(r => `[${r.path}] ${r.snippet.slice(0, 300)}`)
117
+ .join("\n\n");
118
+ logger.info?.(`Auto-recalled ${results.length} memories`);
119
+ return {
120
+ prependContext: "\n\n<recalled-memories>\n" + recalled + "\n</recalled-memories>\n",
121
+ };
122
+ }
123
+ catch (err) {
124
+ logger.error(`Auto-recall failed: ${err instanceof Error ? err.message : String(err)}`);
125
+ }
126
+ });
127
+ }
128
+ // Auto-capture: save conversation context after each turn
129
+ if (config.autoCapture !== false) {
130
+ api.on("agent_end", async (event) => {
131
+ const ev = event;
132
+ if (!ev.success || !ev.messages?.length)
133
+ return;
134
+ try {
135
+ const m = getMemory(config);
136
+ const texts = [];
137
+ for (const msg of ev.messages) {
138
+ if (msg.role !== "user")
139
+ continue;
140
+ if (typeof msg.content === "string") {
141
+ texts.push(msg.content);
142
+ }
143
+ else if (Array.isArray(msg.content)) {
144
+ for (const block of msg.content) {
145
+ if (block.type === "text" && block.text)
146
+ texts.push(block.text);
147
+ }
148
+ }
149
+ }
150
+ const toCapture = texts.filter(t => t.length >= 20).join("\n\n");
151
+ if (toCapture.length < 50)
152
+ return;
153
+ const date = new Date().toISOString().split("T")[0];
154
+ const path = `memory/${date}.md`;
155
+ const existing = m.read(path);
156
+ const entry = `\n\n---\n_Auto-captured at ${new Date().toISOString()}_\n\n${toCapture.slice(0, 2000)}`;
157
+ m.write(path, existing + entry);
158
+ logger.info?.(`Auto-captured ${toCapture.length} chars to ${path}`);
159
+ }
160
+ catch (err) {
161
+ logger.error(`Auto-capture failed: ${err instanceof Error ? err.message : String(err)}`);
162
+ }
163
+ });
164
+ }
165
+ logger.info("Pluri1bus plugin registered");
166
+ },
167
+ });
@@ -0,0 +1,21 @@
1
+ export interface SearchResult {
2
+ path: string;
3
+ snippet: string;
4
+ lineStart: number;
5
+ score: number;
6
+ }
7
+ /**
8
+ * Pluri1bus memory client — reads/writes/searches on the FUSE mount.
9
+ * Search uses grep for lexical matching.
10
+ */
11
+ export declare class DeepLakeMemory {
12
+ private mountPath;
13
+ constructor(mountPath: string);
14
+ private safePath;
15
+ init(): void;
16
+ write(path: string, content: string): void;
17
+ read(path: string, startLine?: number, numLines?: number): string;
18
+ search(query: string, limit?: number): SearchResult[];
19
+ list(): string[];
20
+ private shellEscape;
21
+ }
package/dist/memory.js ADDED
@@ -0,0 +1,103 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
2
+ import { join, dirname, resolve } from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ /**
5
+ * Pluri1bus memory client — reads/writes/searches on the FUSE mount.
6
+ * Search uses grep for lexical matching.
7
+ */
8
+ export class DeepLakeMemory {
9
+ mountPath;
10
+ constructor(mountPath) {
11
+ this.mountPath = mountPath;
12
+ }
13
+ safePath(path) {
14
+ const full = resolve(this.mountPath, path);
15
+ if (!full.startsWith(resolve(this.mountPath))) {
16
+ throw new Error(`Path traversal rejected: ${path}`);
17
+ }
18
+ return full;
19
+ }
20
+ init() {
21
+ if (!existsSync(this.mountPath)) {
22
+ throw new Error(`DeepLake FUSE mount not found at ${this.mountPath}. ` +
23
+ `Run: curl -fsSL https://deeplake.ai/install.sh | bash && deeplake init`);
24
+ }
25
+ const memoryDir = join(this.mountPath, "memory");
26
+ if (!existsSync(memoryDir))
27
+ mkdirSync(memoryDir, { recursive: true });
28
+ }
29
+ write(path, content) {
30
+ const fullPath = this.safePath(path);
31
+ mkdirSync(dirname(fullPath), { recursive: true });
32
+ writeFileSync(fullPath, content);
33
+ }
34
+ read(path, startLine, numLines) {
35
+ const fullPath = this.safePath(path);
36
+ if (!existsSync(fullPath))
37
+ return "";
38
+ const content = readFileSync(fullPath, "utf-8");
39
+ if (startLine === undefined)
40
+ return content;
41
+ const lines = content.split("\n");
42
+ const start = Math.max(0, startLine - 1);
43
+ const end = numLines ? start + numLines : lines.length;
44
+ return lines.slice(start, end).join("\n");
45
+ }
46
+ search(query, limit = 10) {
47
+ if (!query.trim())
48
+ return [];
49
+ try {
50
+ // grep -rni for case-insensitive, recursive, with line numbers
51
+ const output = execSync(`grep -rni ${this.shellEscape(query)} ${this.shellEscape(this.mountPath)}/MEMORY.md ${this.shellEscape(this.mountPath)}/memory/ 2>/dev/null || true`, { encoding: "utf-8", timeout: 5000, maxBuffer: 1024 * 1024 });
52
+ if (!output.trim())
53
+ return [];
54
+ const results = [];
55
+ for (const line of output.trim().split("\n")) {
56
+ if (!line)
57
+ continue;
58
+ // Format: /path/to/file:linenum:content
59
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
60
+ if (!match)
61
+ continue;
62
+ const [, filePath, lineNum, content] = match;
63
+ const relPath = filePath.replace(this.mountPath + "/", "");
64
+ // Read context around the match
65
+ const lineStart = parseInt(lineNum);
66
+ const snippet = this.read(relPath, Math.max(1, lineStart - 1), 4);
67
+ results.push({
68
+ path: relPath,
69
+ snippet: snippet.slice(0, 700),
70
+ lineStart,
71
+ score: 1.0,
72
+ });
73
+ }
74
+ // Dedupe by file (one result per file)
75
+ const seen = new Set();
76
+ return results
77
+ .filter(r => { if (seen.has(r.path))
78
+ return false; seen.add(r.path); return true; })
79
+ .slice(0, limit);
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ }
85
+ list() {
86
+ const files = [];
87
+ const memoryMd = join(this.mountPath, "MEMORY.md");
88
+ if (existsSync(memoryMd))
89
+ files.push("MEMORY.md");
90
+ const memoryDir = join(this.mountPath, "memory");
91
+ if (existsSync(memoryDir)) {
92
+ for (const entry of readdirSync(memoryDir, { withFileTypes: true })) {
93
+ if (entry.isFile() && entry.name.endsWith(".md")) {
94
+ files.push(`memory/${entry.name}`);
95
+ }
96
+ }
97
+ }
98
+ return files;
99
+ }
100
+ shellEscape(s) {
101
+ return "'" + s.replace(/'/g, "'\\''") + "'";
102
+ }
103
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "pluri1bus",
3
+ "kind": "memory",
4
+ "name": "Pluri1bus",
5
+ "description": "Cloud-backed agent memory powered by DeepLake persistent filesystem with grep-based search, auto-capture and auto-recall",
6
+ "uiHints": {
7
+ "mountPath": {
8
+ "label": "Mount Path",
9
+ "advanced": true,
10
+ "placeholder": "Auto-detected from ~/.deeplake/mounts.json"
11
+ },
12
+ "autoCapture": {
13
+ "label": "Auto-Capture"
14
+ },
15
+ "autoRecall": {
16
+ "label": "Auto-Recall"
17
+ }
18
+ },
19
+ "configSchema": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "properties": {
23
+ "mountPath": { "type": "string" },
24
+ "autoCapture": { "type": "boolean" },
25
+ "autoRecall": { "type": "boolean" }
26
+ }
27
+ }
28
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "pluri1bus",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Pluri1bus — cloud-backed persistent memory for AI agents, powered by DeepLake",
6
+ "keywords": ["openclaw", "memory", "deeplake", "agent", "persistent-memory", "shared-memory"],
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/activeloopai/pluri1bus"
11
+ },
12
+ "homepage": "https://deeplake.ai",
13
+ "openclaw": {
14
+ "extensions": ["./dist/index.js"],
15
+ "install": {
16
+ "npmSpec": "pluri1bus",
17
+ "minHostVersion": ">=2026.3.22"
18
+ }
19
+ },
20
+ "files": ["dist", "openclaw.plugin.json"],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "prepublishOnly": "npm run build",
24
+ "dev": "tsc --watch"
25
+ },
26
+ "dependencies": {},
27
+ "peerDependencies": {
28
+ "openclaw": ">=2026.3.22"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.7.0",
32
+ "openclaw": ">=2026.3.22"
33
+ }
34
+ }