@geravant/sinain 1.0.19 → 1.2.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 +10 -1
- package/cli.js +176 -0
- package/index.ts +4 -2
- package/install.js +89 -14
- package/launcher.js +622 -0
- package/openclaw.plugin.json +4 -0
- package/pack-prepare.js +48 -0
- package/package.json +24 -5
- package/sense_client/README.md +82 -0
- package/sense_client/__init__.py +1 -0
- package/sense_client/__main__.py +462 -0
- package/sense_client/app_detector.py +54 -0
- package/sense_client/app_detector_win.py +83 -0
- package/sense_client/capture.py +215 -0
- package/sense_client/capture_win.py +88 -0
- package/sense_client/change_detector.py +86 -0
- package/sense_client/config.py +64 -0
- package/sense_client/gate.py +145 -0
- package/sense_client/ocr.py +347 -0
- package/sense_client/privacy.py +65 -0
- package/sense_client/requirements.txt +13 -0
- package/sense_client/roi_extractor.py +84 -0
- package/sense_client/sender.py +173 -0
- package/sense_client/tests/__init__.py +0 -0
- package/sense_client/tests/test_stream1_optimizations.py +234 -0
- package/setup-overlay.js +82 -0
- package/sinain-agent/.env.example +17 -0
- package/sinain-agent/CLAUDE.md +87 -0
- package/sinain-agent/mcp-config.json +12 -0
- package/sinain-agent/run.sh +248 -0
- package/sinain-core/.env.example +93 -0
- package/sinain-core/package-lock.json +552 -0
- package/sinain-core/package.json +21 -0
- package/sinain-core/src/agent/analyzer.ts +366 -0
- package/sinain-core/src/agent/context-window.ts +172 -0
- package/sinain-core/src/agent/loop.ts +404 -0
- package/sinain-core/src/agent/situation-writer.ts +187 -0
- package/sinain-core/src/agent/traits.ts +520 -0
- package/sinain-core/src/audio/capture-spawner-macos.ts +44 -0
- package/sinain-core/src/audio/capture-spawner-win.ts +37 -0
- package/sinain-core/src/audio/capture-spawner.ts +14 -0
- package/sinain-core/src/audio/pipeline.ts +335 -0
- package/sinain-core/src/audio/transcription-local.ts +141 -0
- package/sinain-core/src/audio/transcription.ts +278 -0
- package/sinain-core/src/buffers/feed-buffer.ts +71 -0
- package/sinain-core/src/buffers/sense-buffer.ts +425 -0
- package/sinain-core/src/config.ts +245 -0
- package/sinain-core/src/escalation/escalation-slot.ts +136 -0
- package/sinain-core/src/escalation/escalator.ts +828 -0
- package/sinain-core/src/escalation/message-builder.ts +370 -0
- package/sinain-core/src/escalation/openclaw-ws.ts +726 -0
- package/sinain-core/src/escalation/scorer.ts +166 -0
- package/sinain-core/src/index.ts +537 -0
- package/sinain-core/src/learning/feedback-store.ts +253 -0
- package/sinain-core/src/learning/signal-collector.ts +218 -0
- package/sinain-core/src/log.ts +24 -0
- package/sinain-core/src/overlay/commands.ts +126 -0
- package/sinain-core/src/overlay/ws-handler.ts +267 -0
- package/sinain-core/src/privacy/index.ts +18 -0
- package/sinain-core/src/privacy/presets.ts +40 -0
- package/sinain-core/src/privacy/redact.ts +92 -0
- package/sinain-core/src/profiler.ts +181 -0
- package/sinain-core/src/recorder.ts +186 -0
- package/sinain-core/src/server.ts +456 -0
- package/sinain-core/src/trace/trace-store.ts +73 -0
- package/sinain-core/src/trace/tracer.ts +94 -0
- package/sinain-core/src/types.ts +427 -0
- package/sinain-core/src/util/dedup.ts +48 -0
- package/sinain-core/src/util/task-store.ts +84 -0
- package/sinain-core/tsconfig.json +18 -0
- package/sinain-knowledge/curation/engine.ts +137 -24
- package/sinain-knowledge/data/git-store.ts +26 -0
- package/sinain-knowledge/data/store.ts +117 -0
- package/sinain-mcp-server/index.ts +417 -0
- package/sinain-mcp-server/package.json +19 -0
- package/sinain-mcp-server/tsconfig.json +15 -0
- package/sinain-memory/graph_query.py +185 -0
- package/sinain-memory/knowledge_integrator.py +450 -0
- package/sinain-memory/memory-config.json +3 -1
- package/sinain-memory/session_distiller.py +162 -0
package/README.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
# sinain
|
|
1
|
+
# @geravant/sinain
|
|
2
|
+
|
|
3
|
+
This package serves two roles:
|
|
4
|
+
|
|
5
|
+
1. **Standalone launcher** — run `npx @geravant/sinain start` to launch the full sinain stack (core, sense, overlay, agent) on your Mac. See the [main README](../README.md#quick-start) for usage.
|
|
6
|
+
2. **OpenClaw plugin** — when installed on an OpenClaw gateway server (`npx @geravant/sinain install`), it manages the sinain-hud agent lifecycle.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## OpenClaw Plugin
|
|
2
11
|
|
|
3
12
|
Plugin for the [anthillnet fork of OpenClaw](https://github.com/anthillnet/openclaw) that manages the sinain-hud agent lifecycle on the server.
|
|
4
13
|
|
package/cli.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import net from "net";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
const cmd = process.argv[2];
|
|
9
|
+
|
|
10
|
+
switch (cmd) {
|
|
11
|
+
case "start":
|
|
12
|
+
await import("./launcher.js");
|
|
13
|
+
break;
|
|
14
|
+
|
|
15
|
+
case "stop":
|
|
16
|
+
await stopServices();
|
|
17
|
+
break;
|
|
18
|
+
|
|
19
|
+
case "status":
|
|
20
|
+
await showStatus();
|
|
21
|
+
break;
|
|
22
|
+
|
|
23
|
+
case "setup-overlay":
|
|
24
|
+
await import("./setup-overlay.js");
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
case "install":
|
|
28
|
+
// --if-openclaw: only run if OpenClaw is installed (for postinstall)
|
|
29
|
+
if (process.argv.includes("--if-openclaw")) {
|
|
30
|
+
const ocJson = path.join(os.homedir(), ".openclaw/openclaw.json");
|
|
31
|
+
if (!fs.existsSync(ocJson)) {
|
|
32
|
+
console.log(" OpenClaw not detected — skipping plugin install");
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await import("./install.js");
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
default:
|
|
40
|
+
printUsage();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Stop ──────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
async function stopServices() {
|
|
47
|
+
let killed = false;
|
|
48
|
+
|
|
49
|
+
const patterns = [
|
|
50
|
+
"tsx.*src/index.ts",
|
|
51
|
+
"tsx watch src/index.ts",
|
|
52
|
+
"python3 -m sense_client",
|
|
53
|
+
"Python -m sense_client",
|
|
54
|
+
"flutter run -d macos",
|
|
55
|
+
"sinain_hud.app/Contents/MacOS/sinain_hud",
|
|
56
|
+
"sinain-agent/run.sh",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const pat of patterns) {
|
|
60
|
+
try {
|
|
61
|
+
execSync(`pkill -f "${pat}"`, { stdio: "pipe" });
|
|
62
|
+
killed = true;
|
|
63
|
+
} catch { /* not running */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Free port 9500
|
|
67
|
+
try {
|
|
68
|
+
const pid = execSync("lsof -i :9500 -sTCP:LISTEN -t", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
69
|
+
if (pid) {
|
|
70
|
+
execSync(`kill ${pid}`, { stdio: "pipe" });
|
|
71
|
+
killed = true;
|
|
72
|
+
}
|
|
73
|
+
} catch { /* port already free */ }
|
|
74
|
+
|
|
75
|
+
// Clean PID file
|
|
76
|
+
const pidFile = "/tmp/sinain-pids.txt";
|
|
77
|
+
if (fs.existsSync(pidFile)) {
|
|
78
|
+
fs.unlinkSync(pidFile);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (killed) {
|
|
82
|
+
console.log("sinain services stopped.");
|
|
83
|
+
} else {
|
|
84
|
+
console.log("No sinain services were running.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Status ────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
async function showStatus() {
|
|
91
|
+
const CYAN = "\x1b[36m";
|
|
92
|
+
const YELLOW = "\x1b[33m";
|
|
93
|
+
const MAGENTA = "\x1b[35m";
|
|
94
|
+
const GREEN = "\x1b[32m";
|
|
95
|
+
const RED = "\x1b[31m";
|
|
96
|
+
const BOLD = "\x1b[1m";
|
|
97
|
+
const DIM = "\x1b[2m";
|
|
98
|
+
const RESET = "\x1b[0m";
|
|
99
|
+
|
|
100
|
+
console.log(`\n${BOLD}── SinainHUD Status ────────────────────${RESET}`);
|
|
101
|
+
|
|
102
|
+
// Core: check port 9500
|
|
103
|
+
const coreUp = await isPortOpen(9500);
|
|
104
|
+
if (coreUp) {
|
|
105
|
+
console.log(` ${CYAN}core${RESET} :9500 ${GREEN}✓${RESET} running`);
|
|
106
|
+
} else {
|
|
107
|
+
console.log(` ${CYAN}core${RESET} :9500 ${RED}✗${RESET} stopped`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Sense: check pgrep
|
|
111
|
+
const senseUp = isProcessRunning("python3 -m sense_client") || isProcessRunning("Python -m sense_client");
|
|
112
|
+
if (senseUp) {
|
|
113
|
+
console.log(` ${YELLOW}sense${RESET} ${GREEN}✓${RESET} running`);
|
|
114
|
+
} else {
|
|
115
|
+
console.log(` ${YELLOW}sense${RESET} ${DIM}— stopped${RESET}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Overlay
|
|
119
|
+
const overlayUp = isProcessRunning("sinain_hud.app") || isProcessRunning("flutter run -d macos");
|
|
120
|
+
if (overlayUp) {
|
|
121
|
+
console.log(` ${MAGENTA}overlay${RESET} ${GREEN}✓${RESET} running`);
|
|
122
|
+
} else {
|
|
123
|
+
console.log(` ${MAGENTA}overlay${RESET} ${DIM}— stopped${RESET}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Agent
|
|
127
|
+
const agentUp = isProcessRunning("sinain-agent/run.sh");
|
|
128
|
+
if (agentUp) {
|
|
129
|
+
console.log(` ${GREEN}agent${RESET} ${GREEN}✓${RESET} running`);
|
|
130
|
+
} else {
|
|
131
|
+
console.log(` ${GREEN}agent${RESET} ${DIM}— stopped${RESET}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`${BOLD}────────────────────────────────────────${RESET}\n`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isPortOpen(port) {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
const sock = new net.Socket();
|
|
140
|
+
sock.setTimeout(500);
|
|
141
|
+
sock.on("connect", () => { sock.destroy(); resolve(true); });
|
|
142
|
+
sock.on("error", () => resolve(false));
|
|
143
|
+
sock.on("timeout", () => { sock.destroy(); resolve(false); });
|
|
144
|
+
sock.connect(port, "127.0.0.1");
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isProcessRunning(pattern) {
|
|
149
|
+
try {
|
|
150
|
+
execSync(`pgrep -f "${pattern}"`, { stdio: "pipe" });
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Usage ─────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
function printUsage() {
|
|
160
|
+
console.log(`
|
|
161
|
+
sinain — AI overlay system for macOS
|
|
162
|
+
|
|
163
|
+
Usage:
|
|
164
|
+
sinain start [options] Launch sinain services
|
|
165
|
+
sinain stop Stop all sinain services
|
|
166
|
+
sinain status Check what's running
|
|
167
|
+
sinain setup-overlay Clone and build the overlay app
|
|
168
|
+
sinain install Install OpenClaw plugin (server-side)
|
|
169
|
+
|
|
170
|
+
Start options:
|
|
171
|
+
--no-sense Skip screen capture (sense_client)
|
|
172
|
+
--no-overlay Skip Flutter overlay
|
|
173
|
+
--no-agent Skip agent poll loop
|
|
174
|
+
--agent=<name> Agent to use: claude, codex, goose, aider (default: claude)
|
|
175
|
+
`);
|
|
176
|
+
}
|
package/index.ts
CHANGED
|
@@ -560,8 +560,10 @@ export default function sinainHudPlugin(api: OpenClawPluginApi): void {
|
|
|
560
560
|
`sinain-hud: session summary written (${toolCount} tools, ${Math.round(durationMs / 1000)}s)`,
|
|
561
561
|
);
|
|
562
562
|
|
|
563
|
-
// Fire-and-forget: ingest session summary into
|
|
564
|
-
|
|
563
|
+
// Fire-and-forget: ingest session summary into knowledge graph
|
|
564
|
+
// NOTE: Main knowledge integration happens in heartbeat tick (session_distiller + knowledge_integrator).
|
|
565
|
+
// This is a lightweight best-effort path for when agent_end fires (won't fire on kill).
|
|
566
|
+
if (false && state.workspaceDir) {
|
|
565
567
|
api.runtime.system.runCommandWithTimeout(
|
|
566
568
|
["uv", "run", "--with", "requests", "python3",
|
|
567
569
|
"sinain-memory/triple_ingest.py",
|
package/install.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import os from "os";
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
|
+
import { randomBytes } from "crypto";
|
|
6
7
|
|
|
7
8
|
const HOME = os.homedir();
|
|
8
9
|
const PLUGIN_DIR = path.join(HOME, ".openclaw/extensions/sinain-hud");
|
|
@@ -18,7 +19,9 @@ const SKILL = path.join(PKG_DIR, "SKILL.md");
|
|
|
18
19
|
|
|
19
20
|
console.log("\nInstalling sinain plugin...");
|
|
20
21
|
|
|
21
|
-
// 1.
|
|
22
|
+
// 1. Copy plugin files (remove stale extensions/sinain dir if present from old installs)
|
|
23
|
+
const stalePluginDir = path.join(HOME, ".openclaw/extensions/sinain");
|
|
24
|
+
if (fs.existsSync(stalePluginDir)) fs.rmSync(stalePluginDir, { recursive: true, force: true });
|
|
22
25
|
fs.mkdirSync(PLUGIN_DIR, { recursive: true });
|
|
23
26
|
fs.copyFileSync(path.join(PKG_DIR, "index.ts"), path.join(PLUGIN_DIR, "index.ts"));
|
|
24
27
|
fs.copyFileSync(path.join(PKG_DIR, "openclaw.plugin.json"), path.join(PLUGIN_DIR, "openclaw.plugin.json"));
|
|
@@ -115,13 +118,15 @@ async function installNemoClaw({ sandboxName }) {
|
|
|
115
118
|
sessionKey: "agent:main:sinain"
|
|
116
119
|
}
|
|
117
120
|
};
|
|
121
|
+
// Remove stale "sinain" entry if present from a previous install
|
|
122
|
+
delete cfg.plugins.entries["sinain"];
|
|
118
123
|
if (!cfg.plugins.allow.includes("sinain-hud")) cfg.plugins.allow.push("sinain-hud");
|
|
119
124
|
cfg.agents ??= {};
|
|
120
125
|
cfg.agents.defaults ??= {};
|
|
121
126
|
cfg.agents.defaults.sandbox ??= {};
|
|
122
127
|
cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
|
|
123
128
|
// NemoClaw: gateway bind/auth are managed by OpenShell — but compaction must be set explicitly
|
|
124
|
-
cfg.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
|
|
129
|
+
cfg.agents.defaults.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
|
|
125
130
|
|
|
126
131
|
const token = cfg.gateway?.auth?.token ?? "(see sandbox openclaw.json)";
|
|
127
132
|
|
|
@@ -145,6 +150,19 @@ async function installNemoClaw({ sandboxName }) {
|
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
152
|
|
|
153
|
+
// Knowledge snapshot repo (optional) — inside sandbox
|
|
154
|
+
const snapshotUrl = process.env.SINAIN_SNAPSHOT_REPO;
|
|
155
|
+
if (snapshotUrl) {
|
|
156
|
+
try {
|
|
157
|
+
await checkRepoPrivacy(snapshotUrl);
|
|
158
|
+
run(`ssh -T openshell-${sandboxName} 'mkdir -p ~/.sinain/knowledge-snapshots && cd ~/.sinain/knowledge-snapshots && ([ -d .git ] || (git init && git config user.name sinain-knowledge && git config user.email sinain@local)) && (git remote get-url origin >/dev/null 2>&1 && git remote set-url origin "${snapshotUrl}" || git remote add origin "${snapshotUrl}") && (git fetch origin && git checkout -B main origin/main) 2>/dev/null || true'`);
|
|
159
|
+
console.log(" ✓ Snapshot repo configured in sandbox");
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error("\n ✗ Snapshot repo setup aborted:", e.message, "\n");
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
148
166
|
// Restart openclaw gateway inside sandbox (kill existing PID + start fresh)
|
|
149
167
|
try {
|
|
150
168
|
// Find the gateway PID, kill it, then start a new instance detached
|
|
@@ -201,19 +219,32 @@ async function installLocal() {
|
|
|
201
219
|
sessionKey: "agent:main:sinain"
|
|
202
220
|
}
|
|
203
221
|
};
|
|
222
|
+
// Remove stale "sinain" entry if present from a previous install
|
|
223
|
+
delete cfg.plugins.entries["sinain"];
|
|
224
|
+
cfg.plugins.allow ??= [];
|
|
225
|
+
if (!cfg.plugins.allow.includes("sinain-hud")) cfg.plugins.allow.push("sinain-hud");
|
|
204
226
|
cfg.agents ??= {};
|
|
205
227
|
cfg.agents.defaults ??= {};
|
|
206
228
|
cfg.agents.defaults.sandbox ??= {};
|
|
207
229
|
cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
|
|
208
|
-
cfg.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
|
|
230
|
+
cfg.agents.defaults.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
|
|
209
231
|
cfg.gateway ??= {};
|
|
210
232
|
cfg.gateway.bind = "lan"; // allow remote Mac to connect
|
|
211
233
|
|
|
234
|
+
// Propagate snapshot repo path to plugin config (used for periodic git-backed knowledge backups)
|
|
235
|
+
if (process.env.SINAIN_SNAPSHOT_REPO) {
|
|
236
|
+
cfg.plugins.entries["sinain-hud"].config.snapshotRepoPath = process.env.SINAIN_SNAPSHOT_REPO;
|
|
237
|
+
}
|
|
238
|
+
|
|
212
239
|
fs.mkdirSync(path.dirname(OC_JSON), { recursive: true });
|
|
213
240
|
fs.writeFileSync(OC_JSON, JSON.stringify(cfg, null, 2));
|
|
214
241
|
console.log(" ✓ openclaw.json patched");
|
|
215
242
|
|
|
216
243
|
// Memory restore from backup repo
|
|
244
|
+
// Set SINAIN_BACKUP_REPO to a private GitHub repo URL to seed the workspace with
|
|
245
|
+
// existing memory (session summaries, playbook, triplestore, identity files).
|
|
246
|
+
// Expected repo structure: memory/, playbook.md, identity/, triplestore.jsonl, SITUATION.md
|
|
247
|
+
// The repo MUST be private — the installer verifies this via GitHub API.
|
|
217
248
|
const backupUrl = process.env.SINAIN_BACKUP_REPO;
|
|
218
249
|
if (backupUrl) {
|
|
219
250
|
try {
|
|
@@ -232,25 +263,69 @@ async function installLocal() {
|
|
|
232
263
|
}
|
|
233
264
|
}
|
|
234
265
|
|
|
235
|
-
//
|
|
266
|
+
// Knowledge snapshot repo (optional)
|
|
267
|
+
await setupSnapshotRepo();
|
|
268
|
+
|
|
269
|
+
// Start / restart gateway
|
|
236
270
|
try {
|
|
237
|
-
execSync("openclaw
|
|
238
|
-
console.log(" ✓ Gateway
|
|
271
|
+
execSync("openclaw gateway restart --background", { stdio: "pipe" });
|
|
272
|
+
console.log(" ✓ Gateway restarted");
|
|
239
273
|
} catch {
|
|
240
274
|
try {
|
|
241
|
-
execSync("openclaw
|
|
242
|
-
console.log(" ✓ Gateway
|
|
275
|
+
execSync("openclaw gateway start --background", { stdio: "pipe" });
|
|
276
|
+
console.log(" ✓ Gateway started");
|
|
243
277
|
} catch {
|
|
244
278
|
console.warn(" ⚠ Could not start gateway — run: openclaw gateway");
|
|
245
279
|
}
|
|
246
280
|
}
|
|
247
281
|
|
|
248
|
-
console.log(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
282
|
+
console.log("\n✓ sinain installed successfully.");
|
|
283
|
+
console.log(" Plugin config: ~/.openclaw/openclaw.json");
|
|
284
|
+
console.log(` Auth token: ${authToken}`);
|
|
285
|
+
console.log(" Next: run 'openclaw gateway' in a new terminal, then run ./setup-nemoclaw.sh on your Mac.\n");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Knowledge snapshot repo setup (shared) ──────────────────────────────────
|
|
289
|
+
|
|
290
|
+
const SNAPSHOT_DIR = path.join(HOME, ".sinain", "knowledge-snapshots");
|
|
291
|
+
|
|
292
|
+
async function setupSnapshotRepo() {
|
|
293
|
+
const snapshotUrl = process.env.SINAIN_SNAPSHOT_REPO;
|
|
294
|
+
if (!snapshotUrl) return;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
await checkRepoPrivacy(snapshotUrl);
|
|
298
|
+
fs.mkdirSync(SNAPSHOT_DIR, { recursive: true });
|
|
299
|
+
|
|
300
|
+
if (!fs.existsSync(path.join(SNAPSHOT_DIR, ".git"))) {
|
|
301
|
+
execSync(`git init "${SNAPSHOT_DIR}"`, { stdio: "pipe" });
|
|
302
|
+
execSync(`git -C "${SNAPSHOT_DIR}" config user.name "sinain-knowledge"`, { stdio: "pipe" });
|
|
303
|
+
execSync(`git -C "${SNAPSHOT_DIR}" config user.email "sinain@local"`, { stdio: "pipe" });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Set remote (add or update)
|
|
307
|
+
try {
|
|
308
|
+
run_capture(`git -C "${SNAPSHOT_DIR}" remote get-url origin`);
|
|
309
|
+
execSync(`git -C "${SNAPSHOT_DIR}" remote set-url origin "${snapshotUrl}"`, { stdio: "pipe" });
|
|
310
|
+
} catch {
|
|
311
|
+
execSync(`git -C "${SNAPSHOT_DIR}" remote add origin "${snapshotUrl}"`, { stdio: "pipe" });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Pull existing snapshots if remote has content
|
|
315
|
+
try {
|
|
316
|
+
execSync(`git -C "${SNAPSHOT_DIR}" fetch origin`, { stdio: "pipe", timeout: 15_000 });
|
|
317
|
+
execSync(`git -C "${SNAPSHOT_DIR}" checkout -B main origin/main`, { stdio: "pipe" });
|
|
318
|
+
console.log(" ✓ Snapshot repo restored from", snapshotUrl);
|
|
319
|
+
} catch {
|
|
320
|
+
console.log(" ✓ Snapshot repo configured (empty remote)");
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
if (e.message?.startsWith("SECURITY") || e.message?.startsWith("Refusing")) {
|
|
324
|
+
console.error("\n ✗ Snapshot repo setup aborted:", e.message, "\n");
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
console.warn(" ⚠ Snapshot repo setup failed:", e.message);
|
|
328
|
+
}
|
|
254
329
|
}
|
|
255
330
|
|
|
256
331
|
// ── Detection ────────────────────────────────────────────────────────────────
|