@geravant/sinain 1.0.8 → 1.0.10

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 (2) hide show
  1. package/install.js +204 -71
  2. package/package.json +4 -2
package/install.js CHANGED
@@ -3,40 +3,37 @@ 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";
7
6
 
8
- const HOME = os.homedir();
7
+ const HOME = os.homedir();
9
8
  const PLUGIN_DIR = path.join(HOME, ".openclaw/extensions/sinain-hud");
10
9
  const SOURCES_DIR = path.join(HOME, ".openclaw/sinain-sources");
11
10
  const OC_JSON = path.join(HOME, ".openclaw/openclaw.json");
12
11
  const WORKSPACE = path.join(HOME, ".openclaw/workspace");
13
12
 
14
13
  // PKG_DIR = sinain-hud-plugin/ inside the npm package
15
- const PKG_DIR = path.dirname(new URL(import.meta.url).pathname);
14
+ const PKG_DIR = path.dirname(new URL(import.meta.url).pathname);
16
15
  const MEMORY_SRC = path.join(PKG_DIR, "sinain-memory");
17
16
  const HEARTBEAT = path.join(PKG_DIR, "HEARTBEAT.md");
18
17
 
19
18
  console.log("\nInstalling sinain plugin...");
20
19
 
21
- // 1. Copy plugin files (remove stale extensions/sinain dir if present from old installs)
22
- const stalePluginDir = path.join(HOME, ".openclaw/extensions/sinain");
23
- if (fs.existsSync(stalePluginDir)) fs.rmSync(stalePluginDir, { recursive: true, force: true });
20
+ // 1. Stage plugin files to local ~/.openclaw (used by both paths)
24
21
  fs.mkdirSync(PLUGIN_DIR, { recursive: true });
25
22
  fs.copyFileSync(path.join(PKG_DIR, "index.ts"), path.join(PLUGIN_DIR, "index.ts"));
26
23
  fs.copyFileSync(path.join(PKG_DIR, "openclaw.plugin.json"), path.join(PLUGIN_DIR, "openclaw.plugin.json"));
27
24
  console.log(" ✓ Plugin files copied");
28
25
 
29
- // 2. Copy sinain-memory from bundled package files
26
+ // 2. Stage sinain-memory
30
27
  fs.mkdirSync(SOURCES_DIR, { recursive: true });
31
28
  const memoryDst = path.join(SOURCES_DIR, "sinain-memory");
32
29
  copyDir(MEMORY_SRC, memoryDst);
33
30
  console.log(" ✓ sinain-memory copied");
34
31
 
35
- // 3. Copy HEARTBEAT.md
32
+ // 3. Stage HEARTBEAT.md
36
33
  fs.copyFileSync(HEARTBEAT, path.join(SOURCES_DIR, "HEARTBEAT.md"));
37
34
  console.log(" ✓ HEARTBEAT.md copied");
38
35
 
39
- // 4. Install Python deps
36
+ // 4. Install Python deps (for local bare-metal path; harmless to run here)
40
37
  const reqFile = path.join(memoryDst, "requirements.txt");
41
38
  if (fs.existsSync(reqFile)) {
42
39
  console.log(" Installing Python dependencies...");
@@ -44,92 +41,228 @@ if (fs.existsSync(reqFile)) {
44
41
  execSync(`pip3 install -r "${reqFile}" --quiet --break-system-packages`, { stdio: "inherit" });
45
42
  console.log(" ✓ Python dependencies installed");
46
43
  } catch {
47
- console.warn(" ⚠ pip3 unavailable — Python eval features disabled");
44
+ try {
45
+ execSync(`pip3 install -r "${reqFile}" --quiet`, { stdio: "inherit" });
46
+ console.log(" ✓ Python dependencies installed");
47
+ } catch {
48
+ console.warn(" ⚠ pip3 unavailable — Python eval features disabled");
49
+ }
48
50
  }
49
51
  }
50
52
 
51
- // 5. Patch openclaw.json
52
- let cfg = {};
53
- if (fs.existsSync(OC_JSON)) {
54
- try { cfg = JSON.parse(fs.readFileSync(OC_JSON, "utf8")); } catch {}
55
- }
56
- cfg.plugins ??= {};
57
- cfg.plugins.entries ??= {};
58
- cfg.plugins.entries["sinain-hud"] = {
59
- enabled: true,
60
- config: {
61
- heartbeatPath: path.join(SOURCES_DIR, "HEARTBEAT.md"),
62
- memoryPath: memoryDst,
63
- sessionKey: "agent:main:sinain"
64
- }
65
- };
66
- // Remove stale "sinain" entry if present from a previous install
67
- delete cfg.plugins.entries["sinain"];
68
- cfg.plugins.allow ??= [];
69
- if (!cfg.plugins.allow.includes("sinain-hud")) cfg.plugins.allow.push("sinain-hud");
70
- cfg.agents ??= {};
71
- cfg.agents.defaults ??= {};
72
- cfg.agents.defaults.sandbox ??= {};
73
- cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
74
- cfg.gateway ??= {};
75
- cfg.gateway.mode = "local"; // required for gateway to start
76
- cfg.gateway.bind = "lan"; // allow remote Mac to connect
77
- cfg.gateway.auth ??= {};
78
- cfg.gateway.auth.mode ??= "token";
79
- if (!cfg.gateway.auth.token) {
80
- cfg.gateway.auth.token = randomBytes(24).toString("hex");
53
+ // ── Detect environment and branch ───────────────────────────────────────────
54
+
55
+ const nemoClaw = detectNemoClaw();
56
+ if (nemoClaw) {
57
+ await installNemoClaw(nemoClaw);
58
+ } else {
59
+ await installLocal();
81
60
  }
82
- const authToken = cfg.gateway.auth.token;
83
61
 
84
- fs.mkdirSync(path.dirname(OC_JSON), { recursive: true });
85
- fs.writeFileSync(OC_JSON, JSON.stringify(cfg, null, 2));
86
- console.log(" openclaw.json patched");
62
+ // ── NemoClaw path: upload staged files into the OpenShell sandbox ────────────
63
+ //
64
+ // NemoClaw runs OpenClaw inside an OpenShell sandbox pod. The sandbox has
65
+ // outbound network policy (npm registry is blocked at the binary level) so
66
+ // `npx` / `npm install` cannot reach registry.npmjs.org from inside the pod.
67
+ // Instead we:
68
+ // 1. Stage files locally (done above — steps 1-3)
69
+ // 2. Upload them into the sandbox via `openshell sandbox upload`
70
+ // 3. Download sandbox openclaw.json, patch it, upload back
71
+ // 4. Reload the openclaw gateway inside the sandbox over SSH
72
+ // 5. Print the sandbox's auth token so setup-nemoclaw.sh can use it
73
+
74
+ async function installNemoClaw({ sandboxName }) {
75
+ console.log(`\n NemoClaw sandbox detected: '${sandboxName}'`);
87
76
 
88
- // 6. Memory restore from backup repo (if SINAIN_BACKUP_REPO is set)
89
- const backupUrl = process.env.SINAIN_BACKUP_REPO;
90
- if (backupUrl) {
77
+ // Ensure SSH config has an entry for this sandbox
91
78
  try {
92
- await checkRepoPrivacy(backupUrl);
93
- if (!fs.existsSync(path.join(WORKSPACE, ".git"))) {
94
- console.log(" Restoring memory from backup repo...");
95
- execSync(`git clone "${backupUrl}" "${WORKSPACE}" --quiet`, { stdio: "inherit" });
79
+ const sshEntry = run_capture(`openshell sandbox ssh-config ${sandboxName}`);
80
+ const sshFile = path.join(HOME, ".ssh/config");
81
+ const existing = fs.existsSync(sshFile) ? fs.readFileSync(sshFile, "utf8") : "";
82
+ if (!existing.includes(`Host openshell-${sandboxName}`)) {
83
+ fs.mkdirSync(path.join(HOME, ".ssh"), { recursive: true, mode: 0o700 });
84
+ fs.appendFileSync(sshFile, "\n" + sshEntry + "\n");
85
+ }
86
+ } catch { /* ssh-config is optional; connect may still work */ }
87
+
88
+ // Upload plugin dir into sandbox
89
+ run(`openshell sandbox upload ${sandboxName} "${PLUGIN_DIR}" /sandbox/.openclaw/extensions/sinain-hud`);
90
+ console.log(" ✓ Plugin uploaded to sandbox");
91
+
92
+ // Upload sinain-sources dir into sandbox
93
+ run(`openshell sandbox upload ${sandboxName} "${SOURCES_DIR}" /sandbox/.openclaw/sinain-sources`);
94
+ console.log(" ✓ sinain-sources uploaded to sandbox");
95
+
96
+ // Download sandbox openclaw.json, patch, re-upload
97
+ const tmpJson = path.join(os.tmpdir(), `sinain-openclaw-${Date.now()}.json`);
98
+ run(`openshell sandbox download ${sandboxName} /sandbox/.openclaw/openclaw.json "${tmpJson}"`);
99
+
100
+ let cfg = {};
101
+ try { cfg = JSON.parse(fs.readFileSync(tmpJson, "utf8")); } catch {}
102
+
103
+ cfg.plugins ??= {};
104
+ cfg.plugins.entries ??= {};
105
+ cfg.plugins.allow ??= [];
106
+ cfg.plugins.entries["sinain-hud"] = {
107
+ enabled: true,
108
+ config: {
109
+ heartbeatPath: "/sandbox/.openclaw/sinain-sources/HEARTBEAT.md",
110
+ memoryPath: "/sandbox/.openclaw/sinain-sources/sinain-memory",
111
+ sessionKey: "agent:main:sinain"
112
+ }
113
+ };
114
+ if (!cfg.plugins.allow.includes("sinain-hud")) cfg.plugins.allow.push("sinain-hud");
115
+ cfg.agents ??= {};
116
+ cfg.agents.defaults ??= {};
117
+ cfg.agents.defaults.sandbox ??= {};
118
+ cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
119
+ cfg.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
120
+ // NemoClaw: gateway bind/auth are managed by OpenShell — do not overwrite them
121
+
122
+ const token = cfg.gateway?.auth?.token ?? "(see sandbox openclaw.json)";
123
+
124
+ fs.writeFileSync(tmpJson, JSON.stringify(cfg, null, 2));
125
+ run(`openshell sandbox upload ${sandboxName} "${tmpJson}" /sandbox/.openclaw/openclaw.json`);
126
+ fs.rmSync(tmpJson, { force: true });
127
+ console.log(" ✓ openclaw.json patched in sandbox");
128
+
129
+ // Memory restore from backup repo (workspace lives inside sandbox)
130
+ const backupUrl = process.env.SINAIN_BACKUP_REPO;
131
+ if (backupUrl) {
132
+ try {
133
+ await checkRepoPrivacy(backupUrl);
134
+ run(`ssh -T openshell-${sandboxName} 'if [ ! -d /sandbox/.openclaw/workspace/.git ]; then git clone "${backupUrl}" /sandbox/.openclaw/workspace --quiet; fi'`);
96
135
  console.log(" ✓ Memory restored from", backupUrl);
97
- } else {
98
- execSync(`git -C "${WORKSPACE}" remote set-url origin "${backupUrl}"`, { stdio: "pipe" });
99
- console.log(" ✓ Workspace git remote updated");
136
+ } catch (e) {
137
+ console.error("\n ✗ Memory restore aborted:", e.message, "\n");
138
+ process.exit(1);
100
139
  }
101
- } catch (e) {
102
- console.error("\n ✗ Memory restore aborted:", e.message, "\n");
103
- process.exit(1);
104
140
  }
141
+
142
+ // Reload openclaw gateway inside sandbox
143
+ try {
144
+ run_capture(`ssh -T openshell-${sandboxName} 'openclaw reload'`);
145
+ console.log(" ✓ Gateway reloaded");
146
+ } catch {
147
+ console.warn(" ⚠ Could not reload gateway — it will pick up changes on next start");
148
+ }
149
+
150
+ console.log(`
151
+ ✓ sinain installed successfully.
152
+ Sandbox: ${sandboxName}
153
+ Auth token: ${token}
154
+
155
+ Next steps:
156
+ 1. Note the token above
157
+ 2. Run ./setup-nemoclaw.sh on your Mac
158
+ NemoClaw URL: ws://YOUR-BREV-IP:18789
159
+ Auth token: ${token}
160
+ `);
105
161
  }
106
162
 
107
- // 7. Start / restart gateway
108
- try {
109
- execSync("openclaw gateway restart --background", { stdio: "pipe" });
110
- console.log(" ✓ Gateway restarted");
111
- } catch {
163
+ // ── Standard path: bare-metal OpenClaw (e.g. strato server) ─────────────────
164
+
165
+ async function installLocal() {
166
+ // Patch openclaw.json
167
+ let cfg = {};
168
+ if (fs.existsSync(OC_JSON)) {
169
+ try { cfg = JSON.parse(fs.readFileSync(OC_JSON, "utf8")); } catch {}
170
+ }
171
+ cfg.plugins ??= {};
172
+ cfg.plugins.entries ??= {};
173
+ cfg.plugins.entries["sinain-hud"] = {
174
+ enabled: true,
175
+ config: {
176
+ heartbeatPath: path.join(SOURCES_DIR, "HEARTBEAT.md"),
177
+ memoryPath: memoryDst,
178
+ sessionKey: "agent:main:sinain"
179
+ }
180
+ };
181
+ cfg.agents ??= {};
182
+ cfg.agents.defaults ??= {};
183
+ cfg.agents.defaults.sandbox ??= {};
184
+ cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
185
+ cfg.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
186
+ cfg.gateway ??= {};
187
+ cfg.gateway.bind = "lan"; // allow remote Mac to connect
188
+
189
+ fs.mkdirSync(path.dirname(OC_JSON), { recursive: true });
190
+ fs.writeFileSync(OC_JSON, JSON.stringify(cfg, null, 2));
191
+ console.log(" ✓ openclaw.json patched");
192
+
193
+ // Memory restore from backup repo
194
+ const backupUrl = process.env.SINAIN_BACKUP_REPO;
195
+ if (backupUrl) {
196
+ try {
197
+ await checkRepoPrivacy(backupUrl);
198
+ if (!fs.existsSync(path.join(WORKSPACE, ".git"))) {
199
+ console.log(" Restoring memory from backup repo...");
200
+ execSync(`git clone "${backupUrl}" "${WORKSPACE}" --quiet`, { stdio: "inherit" });
201
+ console.log(" ✓ Memory restored from", backupUrl);
202
+ } else {
203
+ execSync(`git -C "${WORKSPACE}" remote set-url origin "${backupUrl}"`, { stdio: "pipe" });
204
+ console.log(" ✓ Workspace git remote updated");
205
+ }
206
+ } catch (e) {
207
+ console.error("\n ✗ Memory restore aborted:", e.message, "\n");
208
+ process.exit(1);
209
+ }
210
+ }
211
+
212
+ // Reload gateway
112
213
  try {
113
- execSync("openclaw gateway start --background", { stdio: "pipe" });
114
- console.log(" ✓ Gateway started");
214
+ execSync("openclaw reload", { stdio: "pipe" });
215
+ console.log(" ✓ Gateway reloaded");
115
216
  } catch {
116
- console.warn(" ⚠ Could not start gateway — run: openclaw gateway");
217
+ try {
218
+ execSync("openclaw stop && sleep 1 && openclaw start --background", { stdio: "pipe" });
219
+ console.log(" ✓ Gateway restarted");
220
+ } catch {
221
+ console.warn(" ⚠ Could not start gateway — run: openclaw gateway");
222
+ }
117
223
  }
224
+
225
+ console.log(`
226
+ ✓ sinain installed successfully.
227
+ Plugin config: ~/.openclaw/openclaw.json
228
+ Auth token: check your Brev dashboard → 'Gateway Token'
229
+ Then run ./setup-nemoclaw.sh on your Mac.
230
+ `);
118
231
  }
119
232
 
120
- console.log("\n✓ sinain installed successfully.");
121
- console.log(" Plugin config: ~/.openclaw/openclaw.json");
122
- console.log(` Auth token: ${authToken}`);
123
- console.log(" Next: run 'openclaw gateway' in a new terminal, then run ./setup-nemoclaw.sh on your Mac.\n");
233
+ // ── Detection ────────────────────────────────────────────────────────────────
234
+
235
+ function detectNemoClaw() {
236
+ // NemoClaw writes ~/.nemoclaw/sandboxes.json with a defaultSandbox field
237
+ const sandboxesJson = path.join(HOME, ".nemoclaw/sandboxes.json");
238
+ if (!fs.existsSync(sandboxesJson)) return null;
239
+ try {
240
+ const data = JSON.parse(fs.readFileSync(sandboxesJson, "utf8"));
241
+ const sandboxName = data.defaultSandbox;
242
+ if (!sandboxName || !data.sandboxes?.[sandboxName]) return null;
243
+ // Verify openshell CLI is reachable
244
+ try { execSync("which openshell", { stdio: "pipe" }); } catch { return null; }
245
+ return { sandboxName };
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
124
250
 
125
251
  // ── Helpers ──────────────────────────────────────────────────────────────────
126
252
 
253
+ function run(cmd) {
254
+ execSync(cmd, { stdio: "inherit" });
255
+ }
256
+
257
+ function run_capture(cmd) {
258
+ return execSync(cmd, { encoding: "utf-8", stdio: ["pipe","pipe","pipe"] }).trim();
259
+ }
260
+
127
261
  function copyDir(src, dst) {
128
262
  fs.mkdirSync(dst, { recursive: true });
129
263
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
130
264
  const s = path.join(src, entry.name);
131
265
  const d = path.join(dst, entry.name);
132
- // Skip __pycache__ and .pytest_cache to keep the deploy lean
133
266
  if (entry.name === "__pycache__" || entry.name === ".pytest_cache") continue;
134
267
  entry.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
135
268
  }
@@ -164,5 +297,5 @@ async function checkRepoPrivacy(url) {
164
297
  } else if (res.status !== 404) {
165
298
  throw new Error(`Cannot verify repo privacy (HTTP ${res.status}). Aborting for safety.`);
166
299
  }
167
- // 404 = private (unauthenticated can't see it) — OK
300
+ // 404 = private repo (unauthenticated can't see it) — OK
168
301
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geravant/sinain",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "sinain OpenClaw plugin — AI overlay for macOS",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,8 @@
16
16
  "sinain-memory",
17
17
  "HEARTBEAT.md"
18
18
  ],
19
- "engines": { "node": ">=18" },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
20
22
  "license": "MIT"
21
23
  }