@geravant/sinain 1.0.8 → 1.0.9
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/install.js +204 -71
- package/package.json +1 -1
- package/HEARTBEAT.md +0 -62
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
114
|
-
console.log(" ✓ Gateway
|
|
214
|
+
execSync("openclaw reload", { stdio: "pipe" });
|
|
215
|
+
console.log(" ✓ Gateway reloaded");
|
|
115
216
|
} catch {
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
package/HEARTBEAT.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# HEARTBEAT.md
|
|
2
|
-
|
|
3
|
-
> **Execution contract:**
|
|
4
|
-
> 1. Setup → call `sinain_heartbeat_tick` → act on result → HEARTBEAT_OK
|
|
5
|
-
> 2. You MUST call the tool. Do NOT skip it. Do NOT reply HEARTBEAT_OK without calling it first.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Setup
|
|
10
|
-
|
|
11
|
-
1. `sessions_history({ sessionKey: "agent:main:sinain", limit: 20, includeTools: false })`
|
|
12
|
-
2. Determine **IDLE** status: no user activity in the last 30 minutes
|
|
13
|
-
3. Compose **SESSION_SUMMARY**: 2–3 sentences describing the current state (what the user is doing, key topics, errors seen)
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Execute Tick
|
|
18
|
-
|
|
19
|
-
Call the `sinain_heartbeat_tick` tool:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
sinain_heartbeat_tick({ sessionSummary: "...", idle: true|false })
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The tool runs all scripts (git backup, signal analysis, insight synthesis) and writes the log entry automatically.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Act on Result
|
|
30
|
-
|
|
31
|
-
- If `recommendedAction.action === "sessions_spawn"` → spawn a subagent with `.task`
|
|
32
|
-
- If `recommendedAction.action === "telegram_tip"` → send `.task` to Telegram (chat_id: 59835117)
|
|
33
|
-
- If `output` is not null → send to Telegram (chat_id: 59835117): `Suggestion: {suggestion}\n\nInsight: {insight}`
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Finish
|
|
38
|
-
|
|
39
|
-
Reply **HEARTBEAT_OK**
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Rules
|
|
44
|
-
|
|
45
|
-
- **Proactivity quota:** on active days, at least 2 ticks MUST produce output
|
|
46
|
-
- Max 2 subagents. NEVER repeat a recent action. Prefer depth over breadth.
|
|
47
|
-
- Memory mining, feedback, and curation run via plugin timer — do NOT invoke manually.
|
|
48
|
-
- Module management is on-demand, not per-tick — see SKILL.md for module commands.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## NemoClaw Provisioning
|
|
53
|
-
|
|
54
|
-
New Brev instance setup (run in Code-Server terminal):
|
|
55
|
-
```
|
|
56
|
-
SINAIN_BACKUP_REPO=<git-url> npx sinain
|
|
57
|
-
```
|
|
58
|
-
- Copies plugin files, patches `openclaw.json`, installs Python deps, restores memory from backup repo, reloads gateway
|
|
59
|
-
- Token printed at end (or visible in Brev dashboard → Gateway Token)
|
|
60
|
-
- Mac side: `./setup-nemoclaw.sh` → 5 prompts → overlay starts
|
|
61
|
-
|
|
62
|
-
Memory is git-backed via `git_backup.sh` on every heartbeat tick. New instances restore instantly via `SINAIN_BACKUP_REPO`.
|