@geravant/sinain 1.2.1 → 1.3.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/cli.js +7 -2
- package/launcher.js +53 -19
- package/package.json +1 -1
- package/setup-overlay.js +199 -47
package/cli.js
CHANGED
|
@@ -164,13 +164,18 @@ Usage:
|
|
|
164
164
|
sinain start [options] Launch sinain services
|
|
165
165
|
sinain stop Stop all sinain services
|
|
166
166
|
sinain status Check what's running
|
|
167
|
-
sinain setup-overlay
|
|
167
|
+
sinain setup-overlay Download pre-built overlay app
|
|
168
168
|
sinain install Install OpenClaw plugin (server-side)
|
|
169
169
|
|
|
170
170
|
Start options:
|
|
171
171
|
--no-sense Skip screen capture (sense_client)
|
|
172
|
-
--no-overlay Skip
|
|
172
|
+
--no-overlay Skip overlay
|
|
173
173
|
--no-agent Skip agent poll loop
|
|
174
174
|
--agent=<name> Agent to use: claude, codex, goose, aider (default: claude)
|
|
175
|
+
|
|
176
|
+
Setup-overlay options:
|
|
177
|
+
--from-source Build from Flutter source instead of downloading
|
|
178
|
+
--update Force re-download even if version matches
|
|
175
179
|
`);
|
|
180
|
+
|
|
176
181
|
}
|
package/launcher.js
CHANGED
|
@@ -139,14 +139,19 @@ async function main() {
|
|
|
139
139
|
// Start overlay
|
|
140
140
|
let overlayStatus = "skipped";
|
|
141
141
|
if (!skipOverlay) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
const overlay = findOverlay();
|
|
143
|
+
if (overlay?.type === "prebuilt") {
|
|
144
|
+
// Remove quarantine if present (ad-hoc signed app)
|
|
145
|
+
try {
|
|
146
|
+
const xattrs = execSync(`xattr "${overlay.path}"`, { encoding: "utf-8" });
|
|
147
|
+
if (xattrs.includes("com.apple.quarantine")) {
|
|
148
|
+
execSync(`xattr -dr com.apple.quarantine "${overlay.path}"`, { stdio: "pipe" });
|
|
149
|
+
}
|
|
150
|
+
} catch { /* no quarantine or xattr failed — try launching anyway */ }
|
|
151
|
+
|
|
152
|
+
log("Starting overlay (pre-built)...");
|
|
153
|
+
const binary = path.join(overlay.path, "Contents/MacOS/sinain_hud");
|
|
154
|
+
startProcess("overlay", binary, [], { color: MAGENTA });
|
|
150
155
|
await sleep(2000);
|
|
151
156
|
const overlayChild = children.find(c => c.name === "overlay");
|
|
152
157
|
if (overlayChild && !overlayChild.proc.killed && overlayChild.proc.exitCode === null) {
|
|
@@ -156,10 +161,28 @@ async function main() {
|
|
|
156
161
|
warn("overlay exited early — check logs above");
|
|
157
162
|
overlayStatus = "failed";
|
|
158
163
|
}
|
|
159
|
-
} else if (
|
|
160
|
-
|
|
164
|
+
} else if (overlay?.type === "source") {
|
|
165
|
+
const hasFlutter = commandExists("flutter");
|
|
166
|
+
if (hasFlutter) {
|
|
167
|
+
log("Starting overlay (flutter run)...");
|
|
168
|
+
startProcess("overlay", "flutter", ["run", "-d", "macos"], {
|
|
169
|
+
cwd: overlay.path,
|
|
170
|
+
color: MAGENTA,
|
|
171
|
+
});
|
|
172
|
+
await sleep(2000);
|
|
173
|
+
const overlayChild = children.find(c => c.name === "overlay");
|
|
174
|
+
if (overlayChild && !overlayChild.proc.killed && overlayChild.proc.exitCode === null) {
|
|
175
|
+
ok(`overlay running (pid:${overlayChild.pid})`);
|
|
176
|
+
overlayStatus = "running";
|
|
177
|
+
} else {
|
|
178
|
+
warn("overlay exited early — check logs above");
|
|
179
|
+
overlayStatus = "failed";
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
warn("flutter not found — overlay source found but can't build");
|
|
183
|
+
}
|
|
161
184
|
} else {
|
|
162
|
-
warn("
|
|
185
|
+
warn("overlay not found — run: sinain setup-overlay");
|
|
163
186
|
}
|
|
164
187
|
}
|
|
165
188
|
|
|
@@ -227,7 +250,7 @@ async function preflight() {
|
|
|
227
250
|
skipSense = true;
|
|
228
251
|
}
|
|
229
252
|
|
|
230
|
-
// Flutter (optional)
|
|
253
|
+
// Flutter (optional — only needed if no pre-built overlay)
|
|
231
254
|
if (commandExists("flutter")) {
|
|
232
255
|
try {
|
|
233
256
|
const flutterVer = execSync("flutter --version 2>&1", { encoding: "utf-8" }).split("\n")[0].split(" ")[1];
|
|
@@ -236,8 +259,13 @@ async function preflight() {
|
|
|
236
259
|
ok("flutter (version unknown)");
|
|
237
260
|
}
|
|
238
261
|
} else {
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
const prebuiltApp = path.join(SINAIN_DIR, "overlay-app", "sinain_hud.app");
|
|
263
|
+
if (fs.existsSync(prebuiltApp)) {
|
|
264
|
+
ok("overlay: pre-built app");
|
|
265
|
+
} else {
|
|
266
|
+
warn("no overlay available — run: sinain setup-overlay");
|
|
267
|
+
skipOverlay = true;
|
|
268
|
+
}
|
|
241
269
|
}
|
|
242
270
|
|
|
243
271
|
// Port 9500
|
|
@@ -461,17 +489,23 @@ function generateMcpConfig() {
|
|
|
461
489
|
|
|
462
490
|
// ── Overlay discovery ───────────────────────────────────────────────────────
|
|
463
491
|
|
|
464
|
-
function
|
|
465
|
-
// 1.
|
|
492
|
+
function findOverlay() {
|
|
493
|
+
// 1. Dev monorepo: sibling overlay/ with pubspec.yaml (Flutter source)
|
|
466
494
|
const siblingOverlay = path.join(PKG_DIR, "..", "overlay");
|
|
467
495
|
if (fs.existsSync(path.join(siblingOverlay, "pubspec.yaml"))) {
|
|
468
|
-
return siblingOverlay;
|
|
496
|
+
return { type: "source", path: siblingOverlay };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// 2. Pre-built .app bundle (downloaded by setup-overlay)
|
|
500
|
+
const prebuiltApp = path.join(SINAIN_DIR, "overlay-app", "sinain_hud.app");
|
|
501
|
+
if (fs.existsSync(prebuiltApp)) {
|
|
502
|
+
return { type: "prebuilt", path: prebuiltApp };
|
|
469
503
|
}
|
|
470
504
|
|
|
471
|
-
//
|
|
505
|
+
// 3. Legacy: ~/.sinain/overlay/ source install (setup-overlay --from-source)
|
|
472
506
|
const installedOverlay = path.join(SINAIN_DIR, "overlay");
|
|
473
507
|
if (fs.existsSync(path.join(installedOverlay, "pubspec.yaml"))) {
|
|
474
|
-
return installedOverlay;
|
|
508
|
+
return { type: "source", path: installedOverlay };
|
|
475
509
|
}
|
|
476
510
|
|
|
477
511
|
return null;
|
package/package.json
CHANGED
package/setup-overlay.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sinain setup-overlay —
|
|
2
|
+
// sinain setup-overlay — download pre-built overlay app (or build from source)
|
|
3
3
|
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import fs from "fs";
|
|
@@ -8,75 +8,227 @@ import os from "os";
|
|
|
8
8
|
|
|
9
9
|
const HOME = os.homedir();
|
|
10
10
|
const SINAIN_DIR = path.join(HOME, ".sinain");
|
|
11
|
+
const APP_DIR = path.join(SINAIN_DIR, "overlay-app");
|
|
12
|
+
const APP_PATH = path.join(APP_DIR, "sinain_hud.app");
|
|
13
|
+
const VERSION_FILE = path.join(APP_DIR, "version.json");
|
|
14
|
+
|
|
15
|
+
const REPO = "anthillnet/sinain-hud";
|
|
16
|
+
const RELEASES_API = `https://api.github.com/repos/${REPO}/releases`;
|
|
17
|
+
|
|
18
|
+
// Legacy source-build paths
|
|
11
19
|
const REPO_DIR = path.join(SINAIN_DIR, "overlay-repo");
|
|
12
20
|
const OVERLAY_LINK = path.join(SINAIN_DIR, "overlay");
|
|
13
21
|
|
|
14
22
|
const BOLD = "\x1b[1m";
|
|
15
23
|
const GREEN = "\x1b[32m";
|
|
24
|
+
const YELLOW = "\x1b[33m";
|
|
16
25
|
const RED = "\x1b[31m";
|
|
26
|
+
const DIM = "\x1b[2m";
|
|
17
27
|
const RESET = "\x1b[0m";
|
|
18
28
|
|
|
19
|
-
function log(msg)
|
|
20
|
-
function ok(msg)
|
|
29
|
+
function log(msg) { console.log(`${BOLD}[setup-overlay]${RESET} ${msg}`); }
|
|
30
|
+
function ok(msg) { console.log(`${BOLD}[setup-overlay]${RESET} ${GREEN}✓${RESET} ${msg}`); }
|
|
31
|
+
function warn(msg) { console.log(`${BOLD}[setup-overlay]${RESET} ${YELLOW}⚠${RESET} ${msg}`); }
|
|
21
32
|
function fail(msg) { console.error(`${BOLD}[setup-overlay]${RESET} ${RED}✗${RESET} ${msg}`); process.exit(1); }
|
|
22
33
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
// ── Parse flags ──────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
const fromSource = args.includes("--from-source");
|
|
38
|
+
const forceUpdate = args.includes("--update");
|
|
39
|
+
|
|
40
|
+
if (fromSource) {
|
|
41
|
+
await buildFromSource();
|
|
42
|
+
} else {
|
|
43
|
+
await downloadPrebuilt();
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
ok(`flutter: ${flutterVer}`);
|
|
46
|
+
// ── Download pre-built .app ──────────────────────────────────────────────────
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
async function downloadPrebuilt() {
|
|
49
|
+
fs.mkdirSync(APP_DIR, { recursive: true });
|
|
34
50
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
// Find latest overlay release
|
|
52
|
+
log("Checking for latest overlay release...");
|
|
53
|
+
let release;
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`${RELEASES_API}?per_page=20`, {
|
|
56
|
+
signal: AbortSignal.timeout(10000),
|
|
57
|
+
headers: { "Accept": "application/vnd.github+json" },
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) throw new Error(`GitHub API returned ${res.status}`);
|
|
60
|
+
const releases = await res.json();
|
|
61
|
+
release = releases.find(r => r.tag_name?.startsWith("overlay-v"));
|
|
62
|
+
if (!release) throw new Error("No overlay release found");
|
|
63
|
+
} catch (e) {
|
|
64
|
+
fail(`Failed to fetch releases: ${e.message}\n Try: sinain setup-overlay --from-source`);
|
|
44
65
|
}
|
|
45
|
-
execSync(
|
|
46
|
-
`git clone --depth 1 --filter=blob:none --sparse https://github.com/anthillnet/sinain-hud.git "${REPO_DIR}"`,
|
|
47
|
-
{ stdio: "inherit" }
|
|
48
|
-
);
|
|
49
|
-
execSync("git sparse-checkout set overlay", { cwd: REPO_DIR, stdio: "inherit" });
|
|
50
|
-
ok("Repository cloned");
|
|
51
|
-
}
|
|
52
66
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
if (!fs.existsSync(path.join(overlayDir, "pubspec.yaml"))) {
|
|
56
|
-
fail("overlay/pubspec.yaml not found — sparse checkout may have failed");
|
|
57
|
-
}
|
|
67
|
+
const tag = release.tag_name;
|
|
68
|
+
const version = tag.replace("overlay-v", "");
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
// Check if already up-to-date
|
|
71
|
+
if (!forceUpdate && fs.existsSync(VERSION_FILE) && fs.existsSync(APP_PATH)) {
|
|
72
|
+
try {
|
|
73
|
+
const local = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
|
|
74
|
+
if (local.tag === tag) {
|
|
75
|
+
ok(`Overlay already up-to-date (${version})`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
log(`Updating: ${local.tag} → ${tag}`);
|
|
79
|
+
} catch { /* corrupt version file — re-download */ }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Find the .zip asset
|
|
83
|
+
const zipAsset = release.assets?.find(a => a.name === "sinain_hud.app.zip");
|
|
84
|
+
if (!zipAsset) {
|
|
85
|
+
fail(`Release ${tag} has no sinain_hud.app.zip asset.\n Try: sinain setup-overlay --from-source`);
|
|
86
|
+
}
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
// Download with progress
|
|
89
|
+
log(`Downloading overlay ${version} (${formatBytes(zipAsset.size)})...`);
|
|
90
|
+
const zipPath = path.join(APP_DIR, "sinain_hud.app.zip");
|
|
65
91
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(zipAsset.browser_download_url, {
|
|
94
|
+
signal: AbortSignal.timeout(120000),
|
|
95
|
+
redirect: "follow",
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
98
|
+
|
|
99
|
+
const total = parseInt(res.headers.get("content-length") || "0");
|
|
100
|
+
const chunks = [];
|
|
101
|
+
let downloaded = 0;
|
|
102
|
+
|
|
103
|
+
const reader = res.body.getReader();
|
|
104
|
+
while (true) {
|
|
105
|
+
const { done, value } = await reader.read();
|
|
106
|
+
if (done) break;
|
|
107
|
+
chunks.push(value);
|
|
108
|
+
downloaded += value.length;
|
|
109
|
+
if (total > 0) {
|
|
110
|
+
const pct = Math.round((downloaded / total) * 100);
|
|
111
|
+
process.stdout.write(`\r${BOLD}[setup-overlay]${RESET} ${DIM}${pct}% (${formatBytes(downloaded)} / ${formatBytes(total)})${RESET}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
process.stdout.write("\n");
|
|
115
|
+
|
|
116
|
+
const buffer = Buffer.concat(chunks);
|
|
117
|
+
fs.writeFileSync(zipPath, buffer);
|
|
118
|
+
ok(`Downloaded ${formatBytes(buffer.length)}`);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
fail(`Download failed: ${e.message}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Remove old app if present
|
|
124
|
+
if (fs.existsSync(APP_PATH)) {
|
|
125
|
+
fs.rmSync(APP_PATH, { recursive: true, force: true });
|
|
70
126
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
127
|
+
|
|
128
|
+
// Extract — ditto preserves macOS extended attributes (critical for code signing)
|
|
129
|
+
log("Extracting...");
|
|
130
|
+
try {
|
|
131
|
+
execSync(`ditto -x -k "${zipPath}" "${APP_DIR}"`, { stdio: "pipe" });
|
|
132
|
+
} catch {
|
|
133
|
+
// Fallback to unzip
|
|
134
|
+
try {
|
|
135
|
+
execSync(`unzip -o -q "${zipPath}" -d "${APP_DIR}"`, { stdio: "pipe" });
|
|
136
|
+
} catch (e) {
|
|
137
|
+
fail(`Extraction failed: ${e.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Remove quarantine attribute (ad-hoc signed app downloaded from internet)
|
|
142
|
+
try {
|
|
143
|
+
execSync(`xattr -cr "${APP_PATH}"`, { stdio: "pipe" });
|
|
144
|
+
} catch { /* xattr may not be needed */ }
|
|
145
|
+
|
|
146
|
+
// Write version marker
|
|
147
|
+
fs.writeFileSync(VERSION_FILE, JSON.stringify({
|
|
148
|
+
tag,
|
|
149
|
+
version,
|
|
150
|
+
installedAt: new Date().toISOString(),
|
|
151
|
+
}, null, 2));
|
|
152
|
+
|
|
153
|
+
// Clean up zip
|
|
154
|
+
fs.unlinkSync(zipPath);
|
|
155
|
+
|
|
156
|
+
ok(`Overlay ${version} installed`);
|
|
157
|
+
console.log(`
|
|
158
|
+
${GREEN}✓${RESET} Overlay ready!
|
|
159
|
+
Location: ${APP_PATH}
|
|
160
|
+
The overlay will auto-start with: ${BOLD}sinain start${RESET}
|
|
161
|
+
`);
|
|
76
162
|
}
|
|
77
163
|
|
|
78
|
-
|
|
164
|
+
// ── Build from source (legacy) ───────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
async function buildFromSource() {
|
|
167
|
+
// Check flutter
|
|
168
|
+
try {
|
|
169
|
+
execSync("which flutter", { stdio: "pipe" });
|
|
170
|
+
} catch {
|
|
171
|
+
fail("flutter not found. Install it: https://docs.flutter.dev/get-started/install");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const flutterVer = execSync("flutter --version 2>&1", { encoding: "utf-8" }).split("\n")[0];
|
|
175
|
+
ok(`flutter: ${flutterVer}`);
|
|
176
|
+
|
|
177
|
+
fs.mkdirSync(SINAIN_DIR, { recursive: true });
|
|
178
|
+
|
|
179
|
+
// Clone or update
|
|
180
|
+
if (fs.existsSync(path.join(REPO_DIR, ".git"))) {
|
|
181
|
+
log("Updating existing overlay repo...");
|
|
182
|
+
execSync("git pull --ff-only", { cwd: REPO_DIR, stdio: "inherit" });
|
|
183
|
+
ok("Repository updated");
|
|
184
|
+
} else {
|
|
185
|
+
log("Cloning overlay (sparse checkout — only overlay/ directory)...");
|
|
186
|
+
if (fs.existsSync(REPO_DIR)) {
|
|
187
|
+
fs.rmSync(REPO_DIR, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
execSync(
|
|
190
|
+
`git clone --depth 1 --filter=blob:none --sparse https://github.com/${REPO}.git "${REPO_DIR}"`,
|
|
191
|
+
{ stdio: "inherit" }
|
|
192
|
+
);
|
|
193
|
+
execSync("git sparse-checkout set overlay", { cwd: REPO_DIR, stdio: "inherit" });
|
|
194
|
+
ok("Repository cloned");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Build
|
|
198
|
+
const overlayDir = path.join(REPO_DIR, "overlay");
|
|
199
|
+
if (!fs.existsSync(path.join(overlayDir, "pubspec.yaml"))) {
|
|
200
|
+
fail("overlay/pubspec.yaml not found — sparse checkout may have failed");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
log("Installing Flutter dependencies...");
|
|
204
|
+
execSync("flutter pub get", { cwd: overlayDir, stdio: "inherit" });
|
|
205
|
+
|
|
206
|
+
log("Building overlay (this may take a few minutes)...");
|
|
207
|
+
execSync("flutter build macos", { cwd: overlayDir, stdio: "inherit" });
|
|
208
|
+
ok("Overlay built successfully");
|
|
209
|
+
|
|
210
|
+
// Symlink ~/.sinain/overlay → the overlay source dir
|
|
211
|
+
try {
|
|
212
|
+
if (fs.existsSync(OVERLAY_LINK)) {
|
|
213
|
+
fs.unlinkSync(OVERLAY_LINK);
|
|
214
|
+
}
|
|
215
|
+
fs.symlinkSync(overlayDir, OVERLAY_LINK);
|
|
216
|
+
ok(`Symlinked: ${OVERLAY_LINK} → ${overlayDir}`);
|
|
217
|
+
} catch (e) {
|
|
218
|
+
log(`Overlay built at: ${overlayDir}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(`
|
|
79
222
|
${GREEN}✓${RESET} Overlay setup complete!
|
|
80
223
|
The overlay will auto-start with: ${BOLD}sinain start${RESET}
|
|
81
224
|
Or run manually: cd ${overlayDir} && flutter run -d macos
|
|
82
225
|
`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
function formatBytes(bytes) {
|
|
231
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
232
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
233
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
234
|
+
}
|