@lotargo/memory_plugin 1.2.902 → 1.3.1

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,188 +1,203 @@
1
- import { readFile, writeFile, mkdir, unlink, readdir } from "fs/promises";
2
- import { existsSync } from "fs";
3
- import { join, basename, resolve } from "path";
4
- import { homedir } from "os";
5
-
6
- function resolveMemoryDir() {
7
- if (process.env.MEMORY_DIR) return process.env.MEMORY_DIR;
8
- if (process.env.OPENCODE_CONFIG_DIR) return join(process.env.OPENCODE_CONFIG_DIR, "memory");
9
-
10
- const legacyDir = join(homedir(), ".config", "opencode", "memory");
11
- if (existsSync(legacyDir)) {
12
- return legacyDir;
13
- }
14
-
15
- if (process.platform === "win32") {
16
- const appData = process.env.LOCALAPPDATA || process.env.APPDATA || join(homedir(), "AppData", "Local");
17
- return join(appData, "opencode", "memory");
18
- }
19
-
20
- const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
21
- return join(configHome, "opencode", "memory");
22
- }
23
-
24
- export const MEMORY_DIR = resolveMemoryDir();
25
- export const GLOBAL_KEY = "global";
26
-
27
- export async function ensureDir() {
28
- if (!existsSync(MEMORY_DIR)) await mkdir(MEMORY_DIR, { recursive: true });
29
- const storageDir = join(MEMORY_DIR, "storage");
30
- const blobsDir = join(storageDir, "blobs");
31
- const modelsDir = join(storageDir, "models");
32
- const exportsDir = join(MEMORY_DIR, "exports");
33
- if (!existsSync(blobsDir)) await mkdir(blobsDir, { recursive: true });
34
- if (!existsSync(modelsDir)) await mkdir(modelsDir, { recursive: true });
35
- if (!existsSync(exportsDir)) await mkdir(exportsDir, { recursive: true });
36
- }
37
-
38
- // Canonical absolute path key: forward slashes, lowercase drive letter on win32.
39
- export function canonicalPath(dir) {
40
- let p = resolve(dir || process.cwd());
41
- if (process.platform === "win32") {
42
- p = p.replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_, d) => `${d.toLowerCase()}:`);
43
- }
44
- return p;
45
- }
46
-
47
- // Project store key = full directory path. This removes basename collisions and
48
- // binds each store to the real project location.
49
- export function projectKey(worktree, directory) {
50
- return canonicalPath(worktree || directory);
51
- }
52
-
53
- // Display label for a project (basename of the resolved directory).
54
- export function projectName(worktree, directory) {
55
- const dir = worktree || directory || process.cwd();
56
- return dir ? basename(resolve(dir)) : "default";
57
- }
58
-
59
- export function scopeKey(scope, worktree, directory) {
60
- return scope === "global" ? GLOBAL_KEY : projectKey(worktree, directory);
61
- }
62
-
63
- function slugify(key) {
64
- return key.replace(/[^a-zA-Z0-9_-]/g, "_");
65
- }
66
-
67
- function memoryPath(key) {
68
- return join(MEMORY_DIR, `${slugify(key)}.md`);
69
- }
70
-
71
- export function memoryFileName(key) {
72
- return basename(memoryPath(key));
73
- }
74
-
75
- function parseMeta(content) {
76
- const m = content.match(/<!-- path: (.+?) -->/);
77
- return { path: m ? m[1].trim() : null };
78
- }
79
-
80
- function isSimpleKey(key) {
81
- return /^[a-zA-Z0-9_-]+$/.test(key);
82
- }
83
-
84
- // Lazy migration: when reading a project path store that doesn't exist yet but a
85
- // legacy <basename>.md store (without path binding) does, claim it under the path.
86
- async function maybeMigrateLegacy(key) {
87
- if (key === GLOBAL_KEY || isSimpleKey(key)) return null;
88
- const legacyBasename = basename(key);
89
- if (!legacyBasename) return null;
90
- const legacyFp = join(MEMORY_DIR, `${legacyBasename}.md`);
91
- if (slugify(key) === legacyBasename || !existsSync(legacyFp)) return null;
92
- const content = await readFile(legacyFp, "utf-8");
93
- if (parseMeta(content).path) return null; // already bound to another project
94
- // Collision guard: a different path with the same basename is already bound,
95
- // so this legacy store is ambiguous and must not be silently claimed.
96
- const files = await readdir(MEMORY_DIR).catch(() => []);
97
- for (const f of files) {
98
- if (!f.endsWith(".md") || f === `${legacyBasename}.md` || f === `${GLOBAL_KEY}.md`) continue;
99
- try {
100
- const other = parseMeta(await readFile(join(MEMORY_DIR, f), "utf-8")).path;
101
- if (other && basename(other) === legacyBasename) return null;
102
- } catch (e) {}
103
- }
104
- const facts = content.split("\n").filter((l) => l.startsWith("- ["));
105
- await writeMemory(key, facts);
106
- try {
107
- await unlink(legacyFp);
108
- } catch (e) {}
109
- return facts;
110
- }
111
-
112
- export async function readMemory(key) {
113
- const fp = memoryPath(key);
114
- if (existsSync(fp)) {
115
- const content = await readFile(fp, "utf-8");
116
- return content.split("\n").filter((l) => l.startsWith("- ["));
117
- }
118
- const migrated = await maybeMigrateLegacy(key);
119
- return migrated || [];
120
- }
121
-
122
- export async function readMemoryRaw(key) {
123
- return (await readMemory(key)).map((e) => e.slice(2));
124
- }
125
-
126
- export async function writeMemory(key, entries) {
127
- const lines = [];
128
- if (key === GLOBAL_KEY) {
129
- lines.push("# Global Memory", "");
130
- } else {
131
- lines.push(`# Memory: ${basename(key) || key}`, "");
132
- if (!isSimpleKey(key)) {
133
- lines.push(`<!-- path: ${key} -->`, "");
134
- }
135
- }
136
- const content = lines.join("\n") + "\n" + (entries.length ? entries.join("\n") + "\n" : "");
137
- await writeFile(memoryPath(key), content);
138
- }
139
-
140
- export async function listProjectStores() {
141
- const stores = [];
142
- const files = await readdir(MEMORY_DIR).catch(() => []);
143
- for (const f of files) {
144
- if (!f.endsWith(".md") || f === `${GLOBAL_KEY}.md`) continue;
145
- const fp = join(MEMORY_DIR, f);
146
- let content = "";
147
- try {
148
- content = await readFile(fp, "utf-8");
149
- } catch (e) {
150
- continue;
151
- }
152
- const facts = content.split("\n").filter((l) => l.startsWith("- ["));
153
- const meta = parseMeta(content);
154
- const key = meta.path || f.slice(0, -3);
155
- stores.push({
156
- key,
157
- path: meta.path,
158
- basename: basename(meta.path || key) || key,
159
- file: f,
160
- count: facts.length,
161
- legacy: !meta.path,
162
- });
163
- }
164
- stores.sort((a, b) => a.basename.localeCompare(b.basename));
165
- return stores;
166
- }
167
-
168
- // Bind an unbound legacy store (e.g. "comfy-meta-viewer") to a directory path.
169
- export async function migrateLegacyStore(legacyKey, targetDir) {
170
- const legacyFp = join(MEMORY_DIR, `${legacyKey.replace(/[^a-zA-Z0-9_-]/g, "_")}.md`);
171
- if (!existsSync(legacyFp)) return { ok: false, reason: "not_found", key: legacyKey };
172
- const content = await readFile(legacyFp, "utf-8");
173
- if (parseMeta(content).path) return { ok: false, reason: "already_bound", key: legacyKey };
174
- const targetKey = projectKey(targetDir, null);
175
- const facts = content.split("\n").filter((l) => l.startsWith("- ["));
176
- await writeMemory(targetKey, facts);
177
- try {
178
- await unlink(legacyFp);
179
- } catch (e) {}
180
- return { ok: true, key: targetKey, file: memoryPath(targetKey), facts: facts.length };
181
- }
182
-
183
- export function today() {
184
- const d = new Date();
185
- const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
186
- const time = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
187
- return `${date} ${time}`;
188
- }
1
+ import { readFile, writeFile, mkdir, unlink, readdir } from "fs/promises";
2
+ import { existsSync, mkdirSync } from "fs";
3
+ import { join, basename, resolve } from "path";
4
+ import { homedir } from "os";
5
+
6
+ function resolveMemoryDir() {
7
+ if (process.env.MEMORY_DIR) return process.env.MEMORY_DIR;
8
+ if (process.env.OPENCODE_CONFIG_DIR) return join(process.env.OPENCODE_CONFIG_DIR, "memory");
9
+
10
+ const legacyDir = join(homedir(), ".config", "opencode", "memory");
11
+ if (existsSync(legacyDir)) {
12
+ return legacyDir;
13
+ }
14
+
15
+ if (process.platform === "win32") {
16
+ const appData = process.env.LOCALAPPDATA || process.env.APPDATA || join(homedir(), "AppData", "Local");
17
+ return join(appData, "opencode", "memory");
18
+ }
19
+
20
+ const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
21
+ return join(configHome, "opencode", "memory");
22
+ }
23
+
24
+ export const MEMORY_DIR = resolveMemoryDir();
25
+ export const GLOBAL_KEY = "global";
26
+
27
+ export async function ensureDir() {
28
+ if (!existsSync(MEMORY_DIR)) await mkdir(MEMORY_DIR, { recursive: true });
29
+ const storageDir = join(MEMORY_DIR, "storage");
30
+ const blobsDir = join(storageDir, "blobs");
31
+ const modelsDir = join(storageDir, "models");
32
+ const exportsDir = join(MEMORY_DIR, "exports");
33
+ if (!existsSync(blobsDir)) await mkdir(blobsDir, { recursive: true });
34
+ if (!existsSync(modelsDir)) await mkdir(modelsDir, { recursive: true });
35
+ if (!existsSync(exportsDir)) await mkdir(exportsDir, { recursive: true });
36
+ }
37
+
38
+ export function ensureDirSync() {
39
+ if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
40
+ const storageDir = join(MEMORY_DIR, "storage");
41
+ const blobsDir = join(storageDir, "blobs");
42
+ const modelsDir = join(storageDir, "models");
43
+ const exportsDir = join(MEMORY_DIR, "exports");
44
+ if (!existsSync(blobsDir)) mkdirSync(blobsDir, { recursive: true });
45
+ if (!existsSync(modelsDir)) mkdirSync(modelsDir, { recursive: true });
46
+ if (!existsSync(exportsDir)) mkdirSync(exportsDir, { recursive: true });
47
+ }
48
+
49
+ // Canonical absolute path key: forward slashes, lowercase drive letter on win32.
50
+ export function canonicalPath(dir) {
51
+ let p = resolve(dir || process.cwd());
52
+ if (process.platform === "win32") {
53
+ p = p.replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_, d) => `${d.toLowerCase()}:`);
54
+ }
55
+ return p;
56
+ }
57
+
58
+ // Project store key = full directory path. This removes basename collisions and
59
+ // binds each store to the real project location.
60
+ export function projectKey(worktree, directory) {
61
+ return canonicalPath(worktree || directory);
62
+ }
63
+
64
+ // Display label for a project (basename of the resolved directory).
65
+ export function projectName(worktree, directory) {
66
+ const dir = worktree || directory || process.cwd();
67
+ return dir ? basename(resolve(dir)) : "default";
68
+ }
69
+
70
+ export function scopeKey(scope, worktree, directory) {
71
+ return scope === "global" ? GLOBAL_KEY : projectKey(worktree, directory);
72
+ }
73
+
74
+ function slugify(key) {
75
+ return key.replace(/[^a-zA-Z0-9_-]/g, "_");
76
+ }
77
+
78
+ function memoryPath(key) {
79
+ return join(MEMORY_DIR, `${slugify(key)}.md`);
80
+ }
81
+
82
+ export function memoryFileName(key) {
83
+ return basename(memoryPath(key));
84
+ }
85
+
86
+ export function storeFilePath(key) {
87
+ return memoryPath(key);
88
+ }
89
+
90
+ function parseMeta(content) {
91
+ const m = content.match(/<!-- path: (.+?) -->/);
92
+ return { path: m ? m[1].trim() : null };
93
+ }
94
+
95
+ function isSimpleKey(key) {
96
+ return /^[a-zA-Z0-9_-]+$/.test(key);
97
+ }
98
+
99
+ // Lazy migration: when reading a project path store that doesn't exist yet but a
100
+ // legacy <basename>.md store (without path binding) does, claim it under the path.
101
+ async function maybeMigrateLegacy(key) {
102
+ if (key === GLOBAL_KEY || isSimpleKey(key)) return null;
103
+ const legacyBasename = basename(key);
104
+ if (!legacyBasename) return null;
105
+ const legacyFp = join(MEMORY_DIR, `${legacyBasename}.md`);
106
+ if (slugify(key) === legacyBasename || !existsSync(legacyFp)) return null;
107
+ const content = await readFile(legacyFp, "utf-8");
108
+ if (parseMeta(content).path) return null; // already bound to another project
109
+ // Collision guard: a different path with the same basename is already bound,
110
+ // so this legacy store is ambiguous and must not be silently claimed.
111
+ const files = await readdir(MEMORY_DIR).catch(() => []);
112
+ for (const f of files) {
113
+ if (!f.endsWith(".md") || f === `${legacyBasename}.md` || f === `${GLOBAL_KEY}.md`) continue;
114
+ try {
115
+ const other = parseMeta(await readFile(join(MEMORY_DIR, f), "utf-8")).path;
116
+ if (other && basename(other) === legacyBasename) return null;
117
+ } catch (e) {}
118
+ }
119
+ const facts = content.split("\n").filter((l) => l.startsWith("- ["));
120
+ await writeMemory(key, facts);
121
+ try {
122
+ await unlink(legacyFp);
123
+ } catch (e) {}
124
+ return facts;
125
+ }
126
+
127
+ export async function readMemory(key) {
128
+ const fp = memoryPath(key);
129
+ if (existsSync(fp)) {
130
+ const content = await readFile(fp, "utf-8");
131
+ return content.split("\n").filter((l) => l.startsWith("- ["));
132
+ }
133
+ const migrated = await maybeMigrateLegacy(key);
134
+ return migrated || [];
135
+ }
136
+
137
+ export async function readMemoryRaw(key) {
138
+ return (await readMemory(key)).map((e) => e.slice(2));
139
+ }
140
+
141
+ export async function writeMemory(key, entries) {
142
+ const lines = [];
143
+ if (key === GLOBAL_KEY) {
144
+ lines.push("# Global Memory", "");
145
+ } else {
146
+ lines.push(`# Memory: ${basename(key) || key}`, "");
147
+ if (!isSimpleKey(key)) {
148
+ lines.push(`<!-- path: ${key} -->`, "");
149
+ }
150
+ }
151
+ const content = lines.join("\n") + "\n" + (entries.length ? entries.join("\n") + "\n" : "");
152
+ await writeFile(memoryPath(key), content);
153
+ }
154
+
155
+ export async function listProjectStores() {
156
+ const stores = [];
157
+ const files = await readdir(MEMORY_DIR).catch(() => []);
158
+ for (const f of files) {
159
+ if (!f.endsWith(".md") || f === `${GLOBAL_KEY}.md`) continue;
160
+ const fp = join(MEMORY_DIR, f);
161
+ let content = "";
162
+ try {
163
+ content = await readFile(fp, "utf-8");
164
+ } catch (e) {
165
+ continue;
166
+ }
167
+ const facts = content.split("\n").filter((l) => l.startsWith("- ["));
168
+ const meta = parseMeta(content);
169
+ const key = meta.path || f.slice(0, -3);
170
+ stores.push({
171
+ key,
172
+ path: meta.path,
173
+ basename: basename(meta.path || key) || key,
174
+ file: f,
175
+ count: facts.length,
176
+ legacy: !meta.path,
177
+ });
178
+ }
179
+ stores.sort((a, b) => a.basename.localeCompare(b.basename));
180
+ return stores;
181
+ }
182
+
183
+ // Bind an unbound legacy store (e.g. "comfy-meta-viewer") to a directory path.
184
+ export async function migrateLegacyStore(legacyKey, targetDir) {
185
+ const legacyFp = join(MEMORY_DIR, `${legacyKey.replace(/[^a-zA-Z0-9_-]/g, "_")}.md`);
186
+ if (!existsSync(legacyFp)) return { ok: false, reason: "not_found", key: legacyKey };
187
+ const content = await readFile(legacyFp, "utf-8");
188
+ if (parseMeta(content).path) return { ok: false, reason: "already_bound", key: legacyKey };
189
+ const targetKey = projectKey(targetDir, null);
190
+ const facts = content.split("\n").filter((l) => l.startsWith("- ["));
191
+ await writeMemory(targetKey, facts);
192
+ try {
193
+ await unlink(legacyFp);
194
+ } catch (e) {}
195
+ return { ok: true, key: targetKey, file: memoryPath(targetKey), facts: facts.length };
196
+ }
197
+
198
+ export function today() {
199
+ const d = new Date();
200
+ const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
201
+ const time = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
202
+ return `${date} ${time}`;
203
+ }
@@ -388,10 +388,10 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
388
388
  return rerankerInstance;
389
389
  }
390
390
 
391
- checkAndSelfHealModel(modelName);
391
+ const cacheDir = ensureValidModelDirectory();
392
392
 
393
393
  const { pipeline, env } = await import("@huggingface/transformers");
394
- env.cacheDir = MODELS_DIR;
394
+ env.cacheDir = cacheDir;
395
395
  env.allowLocalModels = true;
396
396
  env.allowRemoteModels = true;
397
397
  env.remoteHost = "https://huggingface.co";