@geravant/sinain 1.23.1 → 1.23.3
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/cli.js +11 -0
- package/launcher.js +11 -0
- package/onboard.js +21 -0
- package/package.json +2 -1
- package/setup-embedding.js +163 -0
- package/sinain-core/src/audio/pipeline.ts +47 -0
- package/sinain-core/src/index.ts +31 -1
- package/sinain-core/src/learning/entity-cache.ts +7 -3
- package/sinain-core/src/learning/local-curation.ts +13 -5
package/cli.js
CHANGED
|
@@ -69,6 +69,13 @@ switch (cmd) {
|
|
|
69
69
|
break;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
case "setup-embedding": {
|
|
73
|
+
const { cacheEmbeddingModel } = await import("./setup-embedding.js");
|
|
74
|
+
const forceUpdate = process.argv.includes("--update");
|
|
75
|
+
await cacheEmbeddingModel({ forceUpdate });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
case "install":
|
|
73
80
|
// --if-openclaw: only run if OpenClaw is installed (for postinstall)
|
|
74
81
|
if (process.argv.includes("--if-openclaw")) {
|
|
@@ -400,6 +407,7 @@ Usage:
|
|
|
400
407
|
sinain setup (deprecated — use onboard)
|
|
401
408
|
sinain setup-overlay Download pre-built overlay app
|
|
402
409
|
sinain setup-sck-capture Download sck-capture audio binary (macOS)
|
|
410
|
+
sinain setup-embedding Pre-cache sentence-transformer model (~90MB)
|
|
403
411
|
sinain export-knowledge Export knowledge for transfer to another machine
|
|
404
412
|
sinain import-knowledge <file> Import knowledge from export file
|
|
405
413
|
sinain install Install OpenClaw plugin (server-side)
|
|
@@ -416,6 +424,9 @@ Start options:
|
|
|
416
424
|
Setup-overlay options:
|
|
417
425
|
--from-source Build from Flutter source instead of downloading
|
|
418
426
|
--update Force re-download even if version matches
|
|
427
|
+
|
|
428
|
+
Setup-embedding options:
|
|
429
|
+
--update Force re-download even if model is already cached
|
|
419
430
|
`);
|
|
420
431
|
|
|
421
432
|
}
|
package/launcher.js
CHANGED
|
@@ -139,6 +139,17 @@ async function main() {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
// Pre-cache embedding model if not already cached (prevents 10s huggingface.co
|
|
143
|
+
// download at sinain-core first-startup; skipped silently if SINAIN_SKIP_EMBEDDING_SETUP=1)
|
|
144
|
+
if (process.env.SINAIN_SKIP_EMBEDDING_SETUP !== "1") {
|
|
145
|
+
try {
|
|
146
|
+
const { cacheEmbeddingModel } = await import("./setup-embedding.js");
|
|
147
|
+
await cacheEmbeddingModel({ silent: true });
|
|
148
|
+
} catch (e) {
|
|
149
|
+
warn(`embedding model pre-cache skipped: ${e.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
142
153
|
// Start core
|
|
143
154
|
log("Starting sinain-core...");
|
|
144
155
|
const coreDir = path.join(PKG_DIR, "sinain-core");
|
package/onboard.js
CHANGED
|
@@ -346,6 +346,27 @@ export async function runOnboard(args = {}) {
|
|
|
346
346
|
|
|
347
347
|
await stepOverlay(base);
|
|
348
348
|
|
|
349
|
+
// ── Embedding model ───────────────────────────────────────────────────
|
|
350
|
+
// Pre-cache Xenova/all-MiniLM-L6-v2 (~90MB) so sinain-core startup is
|
|
351
|
+
// a cache-hit with no network activity. Helps all users, not just paranoid
|
|
352
|
+
// mode — runtime load goes from ~10s download to <1s cache read.
|
|
353
|
+
|
|
354
|
+
{
|
|
355
|
+
const s = p.spinner();
|
|
356
|
+
s.start("Pre-caching sentence-transformer model (~90MB)...");
|
|
357
|
+
try {
|
|
358
|
+
const { cacheEmbeddingModel } = await import("./setup-embedding.js");
|
|
359
|
+
await cacheEmbeddingModel({ silent: true });
|
|
360
|
+
s.stop(c.green("Embedding model cached."));
|
|
361
|
+
} catch (err) {
|
|
362
|
+
s.stop(c.yellow(`Embedding model pre-cache skipped: ${err.message}`));
|
|
363
|
+
p.note(
|
|
364
|
+
"Run manually: sinain setup-embedding\nOr set SINAIN_SKIP_EMBEDDING_SETUP=1 to skip.",
|
|
365
|
+
"Embedding",
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
349
370
|
// ── Health check ──────────────────────────────────────────────────────
|
|
350
371
|
|
|
351
372
|
await runHealthCheck();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geravant/sinain",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.3",
|
|
4
4
|
"description": "Ambient intelligence that sees what you see, hears what you hear, and acts on your behalf",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"mcp-register.js",
|
|
23
23
|
"setup-overlay.js",
|
|
24
24
|
"setup-sck-capture.js",
|
|
25
|
+
"setup-embedding.js",
|
|
25
26
|
"pack-prepare.js",
|
|
26
27
|
"install.js",
|
|
27
28
|
"index.ts",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sinain setup-embedding — pre-cache sentence-transformer model at setup time
|
|
3
|
+
//
|
|
4
|
+
// Moves the ~90MB Xenova/all-MiniLM-L6-v2 download from sinain-core's
|
|
5
|
+
// first-startup to install time, keeping runtime fully offline in paranoid mode.
|
|
6
|
+
// Mirrors the style of setup-overlay.js / setup-sck-capture.js.
|
|
7
|
+
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import { createRequire } from "module";
|
|
12
|
+
|
|
13
|
+
const HOME = os.homedir();
|
|
14
|
+
const MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
15
|
+
|
|
16
|
+
// sinain-core's node_modules contains @huggingface/transformers — load from there
|
|
17
|
+
// so we use the SAME package instance (and thus the same cache key) as runtime.
|
|
18
|
+
const PKG_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
19
|
+
const CORE_DIR = path.join(PKG_DIR, "sinain-core");
|
|
20
|
+
|
|
21
|
+
const BOLD = "\x1b[1m";
|
|
22
|
+
const GREEN = "\x1b[32m";
|
|
23
|
+
const YELLOW = "\x1b[33m";
|
|
24
|
+
const RED = "\x1b[31m";
|
|
25
|
+
const DIM = "\x1b[2m";
|
|
26
|
+
const RESET = "\x1b[0m";
|
|
27
|
+
|
|
28
|
+
function log(msg) { console.log(`${BOLD}[setup-embedding]${RESET} ${msg}`); }
|
|
29
|
+
function ok(msg) { console.log(`${BOLD}[setup-embedding]${RESET} ${GREEN}✓${RESET} ${msg}`); }
|
|
30
|
+
function warn(msg) { console.log(`${BOLD}[setup-embedding]${RESET} ${YELLOW}⚠${RESET} ${msg}`); }
|
|
31
|
+
function fail(msg) { console.error(`${BOLD}[setup-embedding]${RESET} ${RED}✗${RESET} ${msg}`); process.exit(1); }
|
|
32
|
+
|
|
33
|
+
// ── Entry point (only when run directly, not when imported) ──────────────────
|
|
34
|
+
|
|
35
|
+
const isMain = process.argv[1] && (
|
|
36
|
+
import.meta.url === `file://${process.argv[1]}` ||
|
|
37
|
+
import.meta.url === new URL(process.argv[1], "file://").href
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (isMain) {
|
|
41
|
+
const args = process.argv.slice(2);
|
|
42
|
+
const forceUpdate = args.includes("--update");
|
|
43
|
+
await cacheEmbeddingModel({ forceUpdate });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Resolve cache directory (matches @huggingface/transformers default) ──────
|
|
47
|
+
//
|
|
48
|
+
// @huggingface/transformers stores models under:
|
|
49
|
+
// $TRANSFORMERS_CACHE OR
|
|
50
|
+
// $HF_HOME/hub OR
|
|
51
|
+
// ~/.cache/huggingface/hub/
|
|
52
|
+
//
|
|
53
|
+
// We use the same resolution order so setup and runtime share the same cache.
|
|
54
|
+
|
|
55
|
+
function resolveHfCacheDir() {
|
|
56
|
+
if (process.env.TRANSFORMERS_CACHE) return process.env.TRANSFORMERS_CACHE;
|
|
57
|
+
if (process.env.HF_HOME) return path.join(process.env.HF_HOME, "hub");
|
|
58
|
+
return path.join(HOME, ".cache", "huggingface", "hub");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Model files land at: <cacheDir>/models--<org>--<name>/snapshots/**
|
|
62
|
+
// A snapshot directory means the model was cached successfully.
|
|
63
|
+
|
|
64
|
+
function isModelCached() {
|
|
65
|
+
const cacheDir = resolveHfCacheDir();
|
|
66
|
+
// Convert "Xenova/all-MiniLM-L6-v2" → "models--Xenova--all-MiniLM-L6-v2"
|
|
67
|
+
const modelFolder = `models--${MODEL_ID.replace("/", "--")}`;
|
|
68
|
+
const snapshotsDir = path.join(cacheDir, modelFolder, "snapshots");
|
|
69
|
+
if (!fs.existsSync(snapshotsDir)) return false;
|
|
70
|
+
// At least one snapshot sub-directory must exist and not be empty
|
|
71
|
+
try {
|
|
72
|
+
const snapshots = fs.readdirSync(snapshotsDir);
|
|
73
|
+
return snapshots.length > 0;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Download / cache the model ───────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export async function cacheEmbeddingModel({ silent = false, forceUpdate = false } = {}) {
|
|
82
|
+
const _log = silent ? () => {} : log;
|
|
83
|
+
const _ok = silent ? () => {} : ok;
|
|
84
|
+
const _warn = silent ? () => {} : warn;
|
|
85
|
+
|
|
86
|
+
// Skip if already cached and not forcing update
|
|
87
|
+
if (!forceUpdate && isModelCached()) {
|
|
88
|
+
_ok(`Embedding model already cached (${MODEL_ID})`);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (forceUpdate && isModelCached()) {
|
|
93
|
+
_log("Force update requested — re-downloading model...");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Verify sinain-core's node_modules are present (installDeps runs first in launcher)
|
|
97
|
+
const transformersPath = path.join(CORE_DIR, "node_modules", "@huggingface", "transformers");
|
|
98
|
+
if (!fs.existsSync(transformersPath)) {
|
|
99
|
+
const msg = `sinain-core/node_modules not found at ${CORE_DIR}.\n` +
|
|
100
|
+
` Run 'npm install' in sinain-core/ first, or let 'sinain start' handle it.`;
|
|
101
|
+
if (silent) { _warn(msg); return false; }
|
|
102
|
+
fail(msg);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_log(`Downloading sentence-transformer model (~90MB): ${MODEL_ID}`);
|
|
106
|
+
_log("This may take 30-60 seconds on a slow connection...");
|
|
107
|
+
|
|
108
|
+
const cacheDir = resolveHfCacheDir();
|
|
109
|
+
_log(`${DIM}Cache location: ${cacheDir}${RESET}`);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Load @huggingface/transformers from sinain-core's node_modules
|
|
113
|
+
// to guarantee the same module instance (and cache key) as the runtime.
|
|
114
|
+
const require = createRequire(path.join(CORE_DIR, "package.json"));
|
|
115
|
+
// Use dynamic import with the resolved path — createRequire gives us the path
|
|
116
|
+
const transformersEntry = require.resolve("@huggingface/transformers");
|
|
117
|
+
const { pipeline } = await import(transformersEntry);
|
|
118
|
+
|
|
119
|
+
const start = Date.now();
|
|
120
|
+
|
|
121
|
+
// Trigger the download by initialising the pipeline — identical call to
|
|
122
|
+
// EmbeddingService.loadAsync() in sinain-core/src/embedding/service.ts.
|
|
123
|
+
// When this resolves, the model is cached and subsequent calls (including
|
|
124
|
+
// sinain-core startup) are cache-hits with no network activity.
|
|
125
|
+
await pipeline("feature-extraction", MODEL_ID);
|
|
126
|
+
|
|
127
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
128
|
+
_ok(`Embedding model cached in ${elapsed}s`);
|
|
129
|
+
|
|
130
|
+
if (!silent) {
|
|
131
|
+
const cacheDir = resolveHfCacheDir();
|
|
132
|
+
console.log(`
|
|
133
|
+
${GREEN}✓${RESET} Embedding model ready!
|
|
134
|
+
Model: ${MODEL_ID}
|
|
135
|
+
Cache: ${cacheDir}
|
|
136
|
+
sinain-core will load it from cache at startup (no network needed)
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
const isNetworkError =
|
|
142
|
+
e.message?.includes("fetch") ||
|
|
143
|
+
e.message?.includes("network") ||
|
|
144
|
+
e.message?.includes("ENOTFOUND") ||
|
|
145
|
+
e.message?.includes("ECONNREFUSED") ||
|
|
146
|
+
e.message?.includes("huggingface") ||
|
|
147
|
+
e.code === "ENOTFOUND" ||
|
|
148
|
+
e.code === "ECONNREFUSED";
|
|
149
|
+
|
|
150
|
+
if (isNetworkError) {
|
|
151
|
+
const networkMsg =
|
|
152
|
+
`Failed to download embedding model from huggingface.co.\n` +
|
|
153
|
+
` Check network access. To skip and let runtime download (not recommended\n` +
|
|
154
|
+
` for paranoid mode), set SINAIN_SKIP_EMBEDDING_SETUP=1.`;
|
|
155
|
+
if (silent) { _warn(networkMsg); return false; }
|
|
156
|
+
fail(networkMsg);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const errorMsg = `Embedding model download failed: ${e.message?.slice(0, 200)}`;
|
|
160
|
+
if (silent) { _warn(errorMsg); return false; }
|
|
161
|
+
fail(errorMsg);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -212,6 +212,8 @@ export class AudioPipeline extends EventEmitter {
|
|
|
212
212
|
|
|
213
213
|
let headerSkipped = name !== "sox";
|
|
214
214
|
let headerBuf = Buffer.alloc(0);
|
|
215
|
+
let stderrAccum = "";
|
|
216
|
+
const spawnTime = Date.now();
|
|
215
217
|
|
|
216
218
|
proc.stdout?.on("data", (data: Buffer) => {
|
|
217
219
|
if (!this.running) return;
|
|
@@ -237,6 +239,12 @@ export class AudioPipeline extends EventEmitter {
|
|
|
237
239
|
if (msg && !/^In:.*Out:/.test(msg)) {
|
|
238
240
|
log(TAG, `${name} stderr: ${msg.slice(0, 200)}`);
|
|
239
241
|
}
|
|
242
|
+
// Accumulate stderr for TCC detection on exit
|
|
243
|
+
stderrAccum += data.toString();
|
|
244
|
+
// Cap accumulation to avoid unbounded growth (4KB is enough for any TCC message)
|
|
245
|
+
if (stderrAccum.length > 4096) {
|
|
246
|
+
stderrAccum = stderrAccum.slice(-4096);
|
|
247
|
+
}
|
|
240
248
|
});
|
|
241
249
|
|
|
242
250
|
proc.on("error", (err) => {
|
|
@@ -252,6 +260,45 @@ export class AudioPipeline extends EventEmitter {
|
|
|
252
260
|
if (this.running && code !== 0) {
|
|
253
261
|
this.errorCount++;
|
|
254
262
|
this.profiler?.gauge("audio.errors", this.errorCount);
|
|
263
|
+
|
|
264
|
+
// Detect TCC (macOS Screen Recording / Microphone) permission denial.
|
|
265
|
+
// sck-capture logs "declined TCCs" to stderr when the entitlement is
|
|
266
|
+
// missing. The chicken-and-egg: clicking "Allow" on the prompt doesn't
|
|
267
|
+
// apply to a running process — the user must restart Terminal and
|
|
268
|
+
// re-run. We print a prominent banner and request graceful shutdown
|
|
269
|
+
// so users aren't left wondering why the agent never escalates.
|
|
270
|
+
const elapsedMs = Date.now() - spawnTime;
|
|
271
|
+
const isTccDenial = stderrAccum.includes("declined TCCs");
|
|
272
|
+
if (isTccDenial && elapsedMs < 5000) {
|
|
273
|
+
process.stdout.write([
|
|
274
|
+
"",
|
|
275
|
+
"=======================================================================",
|
|
276
|
+
" WARNING: Screen Recording permission needed",
|
|
277
|
+
"=======================================================================",
|
|
278
|
+
"",
|
|
279
|
+
" sck-capture cannot access screen capture and audio without",
|
|
280
|
+
" TCC (Screen Recording) permission from macOS.",
|
|
281
|
+
"",
|
|
282
|
+
" If you just clicked Allow -- that is normal! macOS does not apply",
|
|
283
|
+
" the permission to processes that are already running. To fix:",
|
|
284
|
+
"",
|
|
285
|
+
" 1. Press Ctrl+C to stop sinain",
|
|
286
|
+
" 2. Quit and restart your Terminal app (Cmd+Q, then reopen)",
|
|
287
|
+
" 3. Run again: npx @geravant/sinain@latest start",
|
|
288
|
+
"",
|
|
289
|
+
" Already declined? Re-grant permission:",
|
|
290
|
+
" System Settings > Privacy & Security > Screen Recording",
|
|
291
|
+
" > enable Terminal (or your terminal app)",
|
|
292
|
+
"",
|
|
293
|
+
"=======================================================================",
|
|
294
|
+
"",
|
|
295
|
+
].join("\n"));
|
|
296
|
+
|
|
297
|
+
// Emit TCC-specific error so index.ts can initiate graceful shutdown
|
|
298
|
+
this.emit("tcc-denied");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
255
302
|
warn(TAG, `${name} exited unexpectedly, stopping pipeline`);
|
|
256
303
|
this.stop();
|
|
257
304
|
}
|
package/sinain-core/src/index.ts
CHANGED
|
@@ -543,7 +543,14 @@ async function importKnowledgeToLocal(data: string): Promise<string> {
|
|
|
543
543
|
const dbPath = `${localDir}/knowledge-graph.db`;
|
|
544
544
|
|
|
545
545
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
546
|
-
|
|
546
|
+
// Two package layouts are supported:
|
|
547
|
+
// dev/monorepo: <repo>/sinain-core/src/ → ../../sinain-hud-plugin/sinain-memory
|
|
548
|
+
// npm-published flat: <pkg>/sinain-core/src/ → ../../sinain-memory
|
|
549
|
+
const scriptsDir = [
|
|
550
|
+
resolve(__dir, "..", "..", "sinain-hud-plugin", "sinain-memory"), // dev/monorepo layout
|
|
551
|
+
resolve(__dir, "..", "..", "sinain-memory"), // npm-published flat layout
|
|
552
|
+
resolve(__dir, "..", "sinain-memory"), // legacy alt
|
|
553
|
+
].find(p => existsSync(`${p}/triplestore.py`)) || resolve(__dir, "..", "..", "sinain-memory");
|
|
547
554
|
|
|
548
555
|
// Convert facts to graph ops for knowledge_integrator
|
|
549
556
|
const graphOps = facts.map((f: any) => ({
|
|
@@ -834,6 +841,12 @@ async function main() {
|
|
|
834
841
|
wsHandler.updateState({ audio: "muted" });
|
|
835
842
|
});
|
|
836
843
|
|
|
844
|
+
systemAudioPipeline.on("tcc-denied", () => {
|
|
845
|
+
// Banner already printed by pipeline.ts. Initiate graceful shutdown so
|
|
846
|
+
// sinain exits cleanly rather than continuing with audio dead.
|
|
847
|
+
shutdown("TCC-DENIED").catch(() => process.exit(1));
|
|
848
|
+
});
|
|
849
|
+
|
|
837
850
|
systemAudioPipeline.on("muted", () => {
|
|
838
851
|
log(TAG, "system audio muted (capture process still running)");
|
|
839
852
|
wsHandler.updateState({ audio: "muted" });
|
|
@@ -1006,6 +1019,23 @@ async function main() {
|
|
|
1006
1019
|
}
|
|
1007
1020
|
}
|
|
1008
1021
|
|
|
1022
|
+
// Pre-populate the roster from agents.json profiles so launchers that
|
|
1023
|
+
// don't run the bare-agent process (e.g. start.sh / start-local.sh) still
|
|
1024
|
+
// surface the user's configured profiles in the overlay's agent picker.
|
|
1025
|
+
// When the bare-agent IS running (npm install + cli.js start), its first
|
|
1026
|
+
// /bareagent/register POST narrows the list to PATH-installed binaries —
|
|
1027
|
+
// same final state as before, just with a usable initial state for the
|
|
1028
|
+
// dev-loop launcher.
|
|
1029
|
+
if (escalatorAgentsCfg?.profiles) {
|
|
1030
|
+
const profileNames = Object.keys(escalatorAgentsCfg.profiles)
|
|
1031
|
+
.filter((n) => AGENT_NAME_RE.test(n));
|
|
1032
|
+
if (profileNames.length > 0) {
|
|
1033
|
+
const defaultAgent = escalatorAgentsCfg.default ?? profileNames[0];
|
|
1034
|
+
registerBareAgent(profileNames, defaultAgent);
|
|
1035
|
+
log(TAG, `roster pre-populated from agents.json: ${profileNames.join(",")}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1009
1039
|
// ── Create HTTP + WS server ──
|
|
1010
1040
|
const server = createAppServer({
|
|
1011
1041
|
config,
|
|
@@ -55,10 +55,14 @@ export class EntityCache {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// Query entity names directly via SQLite
|
|
58
|
+
// Query entity names directly via SQLite.
|
|
59
|
+
// Two package layouts are supported:
|
|
60
|
+
// dev/monorepo: <repo>/sinain-core/src/learning/ → ../../../sinain-hud-plugin/sinain-memory
|
|
61
|
+
// npm-published flat: <pkg>/sinain-core/src/learning/ → ../../../sinain-memory
|
|
59
62
|
const scriptCandidates = [
|
|
60
|
-
resolve(__dir, "..", "..", "sinain-hud-plugin", "sinain-memory", "graph_query.py"),
|
|
61
|
-
resolve(__dir, "..", "sinain-memory", "graph_query.py"),
|
|
63
|
+
resolve(__dir, "..", "..", "..", "sinain-hud-plugin", "sinain-memory", "graph_query.py"), // dev/monorepo layout
|
|
64
|
+
resolve(__dir, "..", "..", "..", "sinain-memory", "graph_query.py"), // npm-published flat layout
|
|
65
|
+
resolve(__dir, "..", "..", "sinain-memory", "graph_query.py"), // legacy alt
|
|
62
66
|
];
|
|
63
67
|
const scriptPath = scriptCandidates.find(p => existsSync(p));
|
|
64
68
|
if (!scriptPath) return;
|
|
@@ -23,18 +23,26 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
23
23
|
|
|
24
24
|
/** Resolve the sinain-memory Python scripts directory. */
|
|
25
25
|
function resolveScriptsDir(): string {
|
|
26
|
-
// Look for sinain-memory scripts in known locations
|
|
26
|
+
// Look for sinain-memory scripts in known locations.
|
|
27
|
+
// Two package layouts are supported:
|
|
28
|
+
// dev/monorepo: <repo>/sinain-core/src/learning/ → ../../../sinain-hud-plugin/sinain-memory
|
|
29
|
+
// npm-published flat: <pkg>/sinain-core/src/learning/ → ../../../sinain-memory
|
|
27
30
|
const candidates = [
|
|
28
|
-
resolve(__dirname, "..", "..", "..", "sinain-hud-plugin", "sinain-memory"),
|
|
29
|
-
resolve(__dirname, "..", "..", "sinain-memory"),
|
|
30
|
-
resolve(
|
|
31
|
+
resolve(__dirname, "..", "..", "..", "sinain-hud-plugin", "sinain-memory"), // dev/monorepo layout
|
|
32
|
+
resolve(__dirname, "..", "..", "..", "sinain-memory"), // npm-published flat layout
|
|
33
|
+
resolve(__dirname, "..", "..", "sinain-memory"), // legacy alt
|
|
34
|
+
resolve(process.env.HOME || "", ".sinain", "sinain-memory"), // user-local fallback
|
|
31
35
|
];
|
|
32
36
|
for (const dir of candidates) {
|
|
33
37
|
if (existsSync(resolve(dir, "session_distiller.py"))) {
|
|
34
38
|
return dir;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
|
-
|
|
41
|
+
error(TAG, `sinain-memory scripts not found. Searched ${candidates.length} locations:`);
|
|
42
|
+
for (const dir of candidates) {
|
|
43
|
+
error(TAG, ` - ${dir}`);
|
|
44
|
+
}
|
|
45
|
+
return candidates[candidates.length - 1]; // Return user-local path as sentinel
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
/** Resolve the local memory directory. */
|