@rvboris/opencode-mempalace 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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/README.ru.md +230 -0
  4. package/dist/bridge/mempalace_adapter.py +54 -0
  5. package/dist/plugin/hooks/chat-params.d.ts +5 -0
  6. package/dist/plugin/hooks/chat-params.js +8 -0
  7. package/dist/plugin/hooks/event.d.ts +19 -0
  8. package/dist/plugin/hooks/event.js +110 -0
  9. package/dist/plugin/hooks/system.d.ts +10 -0
  10. package/dist/plugin/hooks/system.js +50 -0
  11. package/dist/plugin/hooks/tool.d.ts +17 -0
  12. package/dist/plugin/hooks/tool.js +61 -0
  13. package/dist/plugin/index.d.ts +2 -0
  14. package/dist/plugin/index.js +18 -0
  15. package/dist/plugin/lib/adapter.d.ts +1 -0
  16. package/dist/plugin/lib/adapter.js +42 -0
  17. package/dist/plugin/lib/autosave.d.ts +52 -0
  18. package/dist/plugin/lib/autosave.js +242 -0
  19. package/dist/plugin/lib/config.d.ts +13 -0
  20. package/dist/plugin/lib/config.js +53 -0
  21. package/dist/plugin/lib/context.d.ts +12 -0
  22. package/dist/plugin/lib/context.js +37 -0
  23. package/dist/plugin/lib/derive.d.ts +1 -0
  24. package/dist/plugin/lib/derive.js +36 -0
  25. package/dist/plugin/lib/enforcement.d.ts +1 -0
  26. package/dist/plugin/lib/enforcement.js +10 -0
  27. package/dist/plugin/lib/log.d.ts +11 -0
  28. package/dist/plugin/lib/log.js +48 -0
  29. package/dist/plugin/lib/privacy.d.ts +3 -0
  30. package/dist/plugin/lib/privacy.js +19 -0
  31. package/dist/plugin/lib/scope.d.ts +8 -0
  32. package/dist/plugin/lib/scope.js +16 -0
  33. package/dist/plugin/tools/mempalace-memory.d.ts +42 -0
  34. package/dist/plugin/tools/mempalace-memory.js +89 -0
  35. package/example-opencode.json +4 -0
  36. package/package.json +45 -0
@@ -0,0 +1,11 @@
1
+ type LogLevel = "INFO" | "WARN" | "ERROR";
2
+ export declare const setLogger: (client: {
3
+ app?: {
4
+ log?: (input: {
5
+ body: any;
6
+ }) => Promise<unknown>;
7
+ };
8
+ }) => void;
9
+ export declare const resetLogger: () => void;
10
+ export declare const writeLog: (level: LogLevel, message: string, details?: Record<string, unknown>) => Promise<void>;
11
+ export {};
@@ -0,0 +1,48 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ let logger = async () => { };
5
+ const getFileLogPath = () => {
6
+ return process.env.MEMPALACE_AUTOSAVE_LOG_FILE || path.join(os.homedir(), ".mempalace", "opencode_autosave.log");
7
+ };
8
+ const writeFileLog = async (level, message, details) => {
9
+ try {
10
+ const filePath = getFileLogPath();
11
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
12
+ await fs.appendFile(filePath, `${JSON.stringify({ timestamp: new Date().toISOString(), level, message, details })}\n`, "utf8");
13
+ }
14
+ catch {
15
+ // avoid crashing plugin because of file logging
16
+ }
17
+ };
18
+ const toAppLevel = (level) => {
19
+ if (level === "WARN")
20
+ return "warn";
21
+ if (level === "ERROR")
22
+ return "error";
23
+ return "info";
24
+ };
25
+ export const setLogger = (client) => {
26
+ logger = async (level, message, details) => {
27
+ await writeFileLog(level, message, details);
28
+ try {
29
+ await client.app?.log?.({
30
+ body: {
31
+ service: "mempalace-autosave",
32
+ level: toAppLevel(level),
33
+ message,
34
+ extra: details,
35
+ },
36
+ });
37
+ }
38
+ catch {
39
+ // avoid crashing plugin because of logging
40
+ }
41
+ };
42
+ };
43
+ export const resetLogger = () => {
44
+ logger = async () => { };
45
+ };
46
+ export const writeLog = async (level, message, details) => {
47
+ await logger(level, message, details);
48
+ };
@@ -0,0 +1,3 @@
1
+ export declare const stripPrivateContent: (text: string) => string;
2
+ export declare const isFullyPrivate: (text: string) => boolean;
3
+ export declare const redactSecrets: (text: string) => string;
@@ -0,0 +1,19 @@
1
+ import { sanitizeText } from "./derive";
2
+ const PRIVATE_BLOCK_PATTERN = /<private>[\s\S]*?<\/private>/gi;
3
+ const SECRET_PATTERNS = [
4
+ /sk-[A-Za-z0-9]{20,}/gi,
5
+ /ghp_[A-Za-z0-9]{20,}/gi,
6
+ /xox[baprs]-[A-Za-z0-9-]{10,}/gi,
7
+ /AKIA[0-9A-Z]{16}/gi,
8
+ /-----BEGIN [A-Z ]+PRIVATE KEY-----/g,
9
+ /password\s*[:=]\s*\S+/gi,
10
+ /token\s*[:=]\s*\S+/gi,
11
+ ];
12
+ export const stripPrivateContent = (text) => sanitizeText(text.replace(PRIVATE_BLOCK_PATTERN, "[REDACTED_PRIVATE]"));
13
+ export const isFullyPrivate = (text) => {
14
+ const stripped = text.replace(PRIVATE_BLOCK_PATTERN, "").trim();
15
+ return stripped.length === 0;
16
+ };
17
+ export const redactSecrets = (text) => {
18
+ return SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, "[REDACTED_SECRET]"), stripPrivateContent(text));
19
+ };
@@ -0,0 +1,8 @@
1
+ export declare const getProjectScope: (projectName: string | undefined, projectWingPrefix: string) => {
2
+ wing: string;
3
+ rooms: string[];
4
+ };
5
+ export declare const getUserScope: (userWingPrefix: string) => {
6
+ wing: string;
7
+ rooms: string[];
8
+ };
@@ -0,0 +1,16 @@
1
+ const slugify = (value, fallback) => {
2
+ const slug = value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3
+ return slug || fallback;
4
+ };
5
+ export const getProjectScope = (projectName, projectWingPrefix) => {
6
+ return {
7
+ wing: `${projectWingPrefix}_${slugify(projectName || "default", "default")}`,
8
+ rooms: ["architecture", "workflow", "decisions", "bugs", "setup"],
9
+ };
10
+ };
11
+ export const getUserScope = (userWingPrefix) => {
12
+ return {
13
+ wing: `${userWingPrefix}_profile`,
14
+ rooms: ["preferences", "workflow", "communication"],
15
+ };
16
+ };
@@ -0,0 +1,42 @@
1
+ type PluginContext = {
2
+ project: any;
3
+ $: any;
4
+ };
5
+ export declare const mempalaceMemoryTool: (ctx: PluginContext) => {
6
+ description: string;
7
+ args: {
8
+ mode: import("zod").ZodEnum<{
9
+ search: "search";
10
+ save: "save";
11
+ kg_add: "kg_add";
12
+ diary_write: "diary_write";
13
+ }>;
14
+ scope: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodEnum<{
15
+ user: "user";
16
+ project: "project";
17
+ }>>>;
18
+ room: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodString>>;
19
+ content: import("zod").ZodOptional<import("zod").ZodString>;
20
+ query: import("zod").ZodOptional<import("zod").ZodString>;
21
+ subject: import("zod").ZodOptional<import("zod").ZodString>;
22
+ predicate: import("zod").ZodOptional<import("zod").ZodString>;
23
+ object: import("zod").ZodOptional<import("zod").ZodString>;
24
+ topic: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodString>>;
25
+ agent_name: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodString>>;
26
+ limit: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodNumber>>;
27
+ };
28
+ execute(args: {
29
+ mode: "search" | "save" | "kg_add" | "diary_write";
30
+ scope: "user" | "project";
31
+ room: string;
32
+ topic: string;
33
+ agent_name: string;
34
+ limit: number;
35
+ content?: string | undefined;
36
+ query?: string | undefined;
37
+ subject?: string | undefined;
38
+ predicate?: string | undefined;
39
+ object?: string | undefined;
40
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
41
+ };
42
+ export {};
@@ -0,0 +1,89 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { executeAdapter } from "../lib/adapter";
3
+ import { loadConfig } from "../lib/config";
4
+ import { sanitizeText } from "../lib/derive";
5
+ import { isFullyPrivate, redactSecrets } from "../lib/privacy";
6
+ import { getProjectScope, getUserScope } from "../lib/scope";
7
+ const getProjectWing = (projectName, prefix) => {
8
+ return getProjectScope(projectName, prefix).wing;
9
+ };
10
+ const getUserWing = (prefix) => {
11
+ return getUserScope(prefix).wing;
12
+ };
13
+ const normalizeValue = (value, redact) => {
14
+ if (value == null)
15
+ return value;
16
+ const sanitized = sanitizeText(value);
17
+ return redact ? redactSecrets(sanitized) : sanitized;
18
+ };
19
+ export const mempalaceMemoryTool = (ctx) => tool({
20
+ description: "Save or search memory in MemPalace with scope/privacy enforcement",
21
+ args: {
22
+ mode: tool.schema.enum(["save", "search", "kg_add", "diary_write"]),
23
+ scope: tool.schema.enum(["user", "project"]).optional().default("project"),
24
+ room: tool.schema.string().optional().default("workflow"),
25
+ content: tool.schema.string().optional(),
26
+ query: tool.schema.string().optional(),
27
+ subject: tool.schema.string().optional(),
28
+ predicate: tool.schema.string().optional(),
29
+ object: tool.schema.string().optional(),
30
+ topic: tool.schema.string().optional().default("autosave"),
31
+ agent_name: tool.schema.string().optional().default("opencode"),
32
+ limit: tool.schema.number().optional().default(5),
33
+ },
34
+ async execute(args) {
35
+ const config = await loadConfig();
36
+ const wing = args.scope === "user"
37
+ ? getUserWing(config.userWingPrefix)
38
+ : getProjectWing(ctx.project?.name, config.projectWingPrefix);
39
+ if (args.mode === "save") {
40
+ if (!args.content)
41
+ return JSON.stringify({ success: false, error: "content is required" });
42
+ if (isFullyPrivate(args.content)) {
43
+ return JSON.stringify({ success: false, error: "content is fully private and will not be saved" });
44
+ }
45
+ const content = normalizeValue(args.content, config.privacyRedactionEnabled);
46
+ const result = await executeAdapter(ctx.$, {
47
+ mode: "save",
48
+ wing,
49
+ room: normalizeValue(args.room, false),
50
+ content,
51
+ added_by: "opencode",
52
+ });
53
+ return JSON.stringify(result);
54
+ }
55
+ if (args.mode === "search") {
56
+ if (!args.query)
57
+ return JSON.stringify({ success: false, error: "query is required" });
58
+ const result = await executeAdapter(ctx.$, {
59
+ mode: "search",
60
+ query: normalizeValue(args.query, config.privacyRedactionEnabled),
61
+ wing,
62
+ room: normalizeValue(args.room, false),
63
+ limit: args.limit,
64
+ });
65
+ return JSON.stringify(result);
66
+ }
67
+ if (args.mode === "kg_add") {
68
+ if (!args.subject || !args.predicate || !args.object) {
69
+ return JSON.stringify({ success: false, error: "subject, predicate, and object are required" });
70
+ }
71
+ const result = await executeAdapter(ctx.$, {
72
+ mode: "kg_add",
73
+ subject: normalizeValue(args.subject, config.privacyRedactionEnabled),
74
+ predicate: normalizeValue(args.predicate, false),
75
+ object: normalizeValue(args.object, config.privacyRedactionEnabled),
76
+ valid_from: new Date().toISOString().slice(0, 10),
77
+ source_closet: "",
78
+ });
79
+ return JSON.stringify(result);
80
+ }
81
+ const result = await executeAdapter(ctx.$, {
82
+ mode: "diary_write",
83
+ agent_name: normalizeValue(args.agent_name, false),
84
+ entry: normalizeValue(args.content || "", config.privacyRedactionEnabled),
85
+ topic: normalizeValue(args.topic, false),
86
+ });
87
+ return JSON.stringify(result);
88
+ },
89
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "plugin": ["@rvboris/opencode-mempalace"]
4
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@rvboris/opencode-mempalace",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode plugin for hidden MemPalace retrieval and autosave via a local Python adapter.",
5
+ "type": "module",
6
+ "main": "dist/plugin/index.js",
7
+ "types": "dist/plugin/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/plugin/index.js",
11
+ "types": "./dist/plugin/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "README.ru.md",
18
+ "LICENSE",
19
+ "example-opencode.json"
20
+ ],
21
+ "keywords": [
22
+ "opencode",
23
+ "opencode-plugin",
24
+ "mempalace",
25
+ "memory"
26
+ ],
27
+ "license": "MIT",
28
+ "peerDependencies": {
29
+ "@opencode-ai/plugin": "^1.4.0"
30
+ },
31
+ "devDependencies": {
32
+ "@opencode-ai/plugin": "^1.4.0",
33
+ "@types/bun": "^1.3.0",
34
+ "@types/node": "^22.13.9",
35
+ "typescript": "^5.8.2"
36
+ },
37
+ "scripts": {
38
+ "build": "node scripts/build.mjs",
39
+ "prepublishOnly": "npm run build",
40
+ "test": "bun test"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }