@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 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 Clone and build the overlay app
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 Flutter overlay
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 overlayDir = findOverlayDir();
143
- const hasFlutter = commandExists("flutter");
144
- if (overlayDir && hasFlutter) {
145
- log("Starting overlay...");
146
- startProcess("overlay", "flutter", ["run", "-d", "macos"], {
147
- cwd: overlayDir,
148
- color: MAGENTA,
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 (!overlayDir) {
160
- warn("overlay not found — run: sinain setup-overlay");
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("flutter not found — overlay skipped");
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
- warn("flutter not found overlay will be skipped");
240
- skipOverlay = true;
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 findOverlayDir() {
465
- // 1. Sibling overlay/ (running from cloned repo)
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
- // 2. ~/.sinain/overlay/ (installed via setup-overlay)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geravant/sinain",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Ambient AI overlay invisible to screen capture — real-time insights from audio + screen context",
5
5
  "type": "module",
6
6
  "bin": {
package/setup-overlay.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // sinain setup-overlay — clone and build the Flutter overlay app
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) { console.log(`${BOLD}[setup-overlay]${RESET} ${msg}`); }
20
- function ok(msg) { console.log(`${BOLD}[setup-overlay]${RESET} ${GREEN}✓${RESET} ${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
- // Check flutter
24
- try {
25
- execSync("which flutter", { stdio: "pipe" });
26
- } catch {
27
- fail("flutter not found. Install it: https://docs.flutter.dev/get-started/install");
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
- const flutterVer = execSync("flutter --version 2>&1", { encoding: "utf-8" }).split("\n")[0];
31
- ok(`flutter: ${flutterVer}`);
46
+ // ── Download pre-built .app ──────────────────────────────────────────────────
32
47
 
33
- fs.mkdirSync(SINAIN_DIR, { recursive: true });
48
+ async function downloadPrebuilt() {
49
+ fs.mkdirSync(APP_DIR, { recursive: true });
34
50
 
35
- // Clone or update
36
- if (fs.existsSync(path.join(REPO_DIR, ".git"))) {
37
- log("Updating existing overlay repo...");
38
- execSync("git pull --ff-only", { cwd: REPO_DIR, stdio: "inherit" });
39
- ok("Repository updated");
40
- } else {
41
- log("Cloning overlay (sparse checkout — only overlay/ directory)...");
42
- if (fs.existsSync(REPO_DIR)) {
43
- fs.rmSync(REPO_DIR, { recursive: true, force: true });
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
- // Build
54
- const overlayDir = path.join(REPO_DIR, "overlay");
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
- log("Installing Flutter dependencies...");
60
- execSync("flutter pub get", { cwd: overlayDir, stdio: "inherit" });
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
- log("Building overlay (this may take a few minutes)...");
63
- execSync("flutter build macos", { cwd: overlayDir, stdio: "inherit" });
64
- ok("Overlay built successfully");
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
- // Symlink ~/.sinain/overlay → the overlay source dir
67
- try {
68
- if (fs.existsSync(OVERLAY_LINK)) {
69
- fs.unlinkSync(OVERLAY_LINK);
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
- fs.symlinkSync(overlayDir, OVERLAY_LINK);
72
- ok(`Symlinked: ${OVERLAY_LINK} ${overlayDir}`);
73
- } catch (e) {
74
- // Symlink may fail on some systems — fall back to just noting the path
75
- log(`Overlay built at: ${overlayDir}`);
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
- console.log(`
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
+ }