@geravant/sinain 1.23.1 → 1.23.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geravant/sinain",
3
- "version": "1.23.1",
3
+ "version": "1.23.2",
4
4
  "description": "Ambient intelligence that sees what you see, hears what you hear, and acts on your behalf",
5
5
  "type": "module",
6
6
  "bin": {
@@ -212,6 +212,8 @@ export class AudioPipeline extends EventEmitter {
212
212
 
213
213
  let headerSkipped = name !== "sox";
214
214
  let headerBuf = Buffer.alloc(0);
215
+ let stderrAccum = "";
216
+ const spawnTime = Date.now();
215
217
 
216
218
  proc.stdout?.on("data", (data: Buffer) => {
217
219
  if (!this.running) return;
@@ -237,6 +239,12 @@ export class AudioPipeline extends EventEmitter {
237
239
  if (msg && !/^In:.*Out:/.test(msg)) {
238
240
  log(TAG, `${name} stderr: ${msg.slice(0, 200)}`);
239
241
  }
242
+ // Accumulate stderr for TCC detection on exit
243
+ stderrAccum += data.toString();
244
+ // Cap accumulation to avoid unbounded growth (4KB is enough for any TCC message)
245
+ if (stderrAccum.length > 4096) {
246
+ stderrAccum = stderrAccum.slice(-4096);
247
+ }
240
248
  });
241
249
 
242
250
  proc.on("error", (err) => {
@@ -252,6 +260,45 @@ export class AudioPipeline extends EventEmitter {
252
260
  if (this.running && code !== 0) {
253
261
  this.errorCount++;
254
262
  this.profiler?.gauge("audio.errors", this.errorCount);
263
+
264
+ // Detect TCC (macOS Screen Recording / Microphone) permission denial.
265
+ // sck-capture logs "declined TCCs" to stderr when the entitlement is
266
+ // missing. The chicken-and-egg: clicking "Allow" on the prompt doesn't
267
+ // apply to a running process — the user must restart Terminal and
268
+ // re-run. We print a prominent banner and request graceful shutdown
269
+ // so users aren't left wondering why the agent never escalates.
270
+ const elapsedMs = Date.now() - spawnTime;
271
+ const isTccDenial = stderrAccum.includes("declined TCCs");
272
+ if (isTccDenial && elapsedMs < 5000) {
273
+ process.stdout.write([
274
+ "",
275
+ "=======================================================================",
276
+ " WARNING: Screen Recording permission needed",
277
+ "=======================================================================",
278
+ "",
279
+ " sck-capture cannot access screen capture and audio without",
280
+ " TCC (Screen Recording) permission from macOS.",
281
+ "",
282
+ " If you just clicked Allow -- that is normal! macOS does not apply",
283
+ " the permission to processes that are already running. To fix:",
284
+ "",
285
+ " 1. Press Ctrl+C to stop sinain",
286
+ " 2. Quit and restart your Terminal app (Cmd+Q, then reopen)",
287
+ " 3. Run again: npx @geravant/sinain@latest start",
288
+ "",
289
+ " Already declined? Re-grant permission:",
290
+ " System Settings > Privacy & Security > Screen Recording",
291
+ " > enable Terminal (or your terminal app)",
292
+ "",
293
+ "=======================================================================",
294
+ "",
295
+ ].join("\n"));
296
+
297
+ // Emit TCC-specific error so index.ts can initiate graceful shutdown
298
+ this.emit("tcc-denied");
299
+ return;
300
+ }
301
+
255
302
  warn(TAG, `${name} exited unexpectedly, stopping pipeline`);
256
303
  this.stop();
257
304
  }
@@ -543,7 +543,14 @@ async function importKnowledgeToLocal(data: string): Promise<string> {
543
543
  const dbPath = `${localDir}/knowledge-graph.db`;
544
544
 
545
545
  const __dir = dirname(fileURLToPath(import.meta.url));
546
- const scriptsDir = resolve(__dir, "..", "..", "sinain-hud-plugin", "sinain-memory");
546
+ // Two package layouts are supported:
547
+ // dev/monorepo: <repo>/sinain-core/src/ → ../../sinain-hud-plugin/sinain-memory
548
+ // npm-published flat: <pkg>/sinain-core/src/ → ../../sinain-memory
549
+ const scriptsDir = [
550
+ resolve(__dir, "..", "..", "sinain-hud-plugin", "sinain-memory"), // dev/monorepo layout
551
+ resolve(__dir, "..", "..", "sinain-memory"), // npm-published flat layout
552
+ resolve(__dir, "..", "sinain-memory"), // legacy alt
553
+ ].find(p => existsSync(`${p}/triplestore.py`)) || resolve(__dir, "..", "..", "sinain-memory");
547
554
 
548
555
  // Convert facts to graph ops for knowledge_integrator
549
556
  const graphOps = facts.map((f: any) => ({
@@ -834,6 +841,12 @@ async function main() {
834
841
  wsHandler.updateState({ audio: "muted" });
835
842
  });
836
843
 
844
+ systemAudioPipeline.on("tcc-denied", () => {
845
+ // Banner already printed by pipeline.ts. Initiate graceful shutdown so
846
+ // sinain exits cleanly rather than continuing with audio dead.
847
+ shutdown("TCC-DENIED").catch(() => process.exit(1));
848
+ });
849
+
837
850
  systemAudioPipeline.on("muted", () => {
838
851
  log(TAG, "system audio muted (capture process still running)");
839
852
  wsHandler.updateState({ audio: "muted" });
@@ -55,10 +55,14 @@ export class EntityCache {
55
55
  return;
56
56
  }
57
57
 
58
- // Query entity names directly via SQLite
58
+ // Query entity names directly via SQLite.
59
+ // Two package layouts are supported:
60
+ // dev/monorepo: <repo>/sinain-core/src/learning/ → ../../../sinain-hud-plugin/sinain-memory
61
+ // npm-published flat: <pkg>/sinain-core/src/learning/ → ../../../sinain-memory
59
62
  const scriptCandidates = [
60
- resolve(__dir, "..", "..", "sinain-hud-plugin", "sinain-memory", "graph_query.py"),
61
- resolve(__dir, "..", "sinain-memory", "graph_query.py"),
63
+ resolve(__dir, "..", "..", "..", "sinain-hud-plugin", "sinain-memory", "graph_query.py"), // dev/monorepo layout
64
+ resolve(__dir, "..", "..", "..", "sinain-memory", "graph_query.py"), // npm-published flat layout
65
+ resolve(__dir, "..", "..", "sinain-memory", "graph_query.py"), // legacy alt
62
66
  ];
63
67
  const scriptPath = scriptCandidates.find(p => existsSync(p));
64
68
  if (!scriptPath) return;
@@ -23,18 +23,26 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
23
23
 
24
24
  /** Resolve the sinain-memory Python scripts directory. */
25
25
  function resolveScriptsDir(): string {
26
- // Look for sinain-memory scripts in known locations
26
+ // Look for sinain-memory scripts in known locations.
27
+ // Two package layouts are supported:
28
+ // dev/monorepo: <repo>/sinain-core/src/learning/ → ../../../sinain-hud-plugin/sinain-memory
29
+ // npm-published flat: <pkg>/sinain-core/src/learning/ → ../../../sinain-memory
27
30
  const candidates = [
28
- resolve(__dirname, "..", "..", "..", "sinain-hud-plugin", "sinain-memory"),
29
- resolve(__dirname, "..", "..", "sinain-memory"),
30
- resolve(process.env.HOME || "", ".sinain", "sinain-memory"),
31
+ resolve(__dirname, "..", "..", "..", "sinain-hud-plugin", "sinain-memory"), // dev/monorepo layout
32
+ resolve(__dirname, "..", "..", "..", "sinain-memory"), // npm-published flat layout
33
+ resolve(__dirname, "..", "..", "sinain-memory"), // legacy alt
34
+ resolve(process.env.HOME || "", ".sinain", "sinain-memory"), // user-local fallback
31
35
  ];
32
36
  for (const dir of candidates) {
33
37
  if (existsSync(resolve(dir, "session_distiller.py"))) {
34
38
  return dir;
35
39
  }
36
40
  }
37
- return candidates[0]; // Fallback
41
+ error(TAG, `sinain-memory scripts not found. Searched ${candidates.length} locations:`);
42
+ for (const dir of candidates) {
43
+ error(TAG, ` - ${dir}`);
44
+ }
45
+ return candidates[candidates.length - 1]; // Return user-local path as sentinel
38
46
  }
39
47
 
40
48
  /** Resolve the local memory directory. */