nio-cat 0.9.4 → 0.9.5

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": "nio-cat",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "nio: fes servir Claude Code, Codex, Grok, Antigravity i Ollama del teu ordinador des del mòbil.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "f.cat",
package/src/control.js CHANGED
@@ -9,7 +9,7 @@ const fs = require('fs');
9
9
  const os = require('os');
10
10
  const path = require('path');
11
11
  const crypto = require('crypto');
12
- const { execFile } = require('child_process');
12
+ const { spawn } = require('child_process');
13
13
  const { which, childEnv } = require('./proc');
14
14
  const config = require('./config');
15
15
  const { HOME } = config;
@@ -46,9 +46,32 @@ function classify(command) {
46
46
  return { tool, kind: KIND[tool], sub, act: !read };
47
47
  }
48
48
 
49
+ // Executa una eina amb un temps màxim que es compleix sempre. Al Mac, agent-browser és un llançador de Node que obre
50
+ // el binari com a fill: matar només el llançador deixava el fill amb la sortida oberta i la petició penjada.
51
+ // Per això: grup de processos propi, s'acaba quan surt el procés (no quan es tanquen les sortides) i, si passa
52
+ // el temps, es mata tot el grup.
49
53
  function run(bin, args, timeout = 15000) {
50
54
  return new Promise((resolve) => {
51
- execFile(bin, args, { timeout, env: childEnv(), maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => resolve({ ok: !err, out: String(stdout || ''), err: String(stderr || err?.message || '') }));
55
+ let out = '', err = '', done = false, child, drain;
56
+ const finish = (ok, why) => {
57
+ if (done) return;
58
+ done = true;
59
+ clearTimeout(timer); clearTimeout(drain);
60
+ child?.stdout?.destroy(); child?.stderr?.destroy();
61
+ resolve({ ok, out, err: err || why || '', timedOut: why === 'temps' });
62
+ };
63
+ const timer = setTimeout(() => {
64
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { try { child.kill('SIGKILL'); } catch { /* ja no hi és */ } }
65
+ finish(false, 'temps');
66
+ }, timeout);
67
+ try {
68
+ child = spawn(bin, args, { env: childEnv(), stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' });
69
+ } catch (e) { return finish(false, e.message); }
70
+ child.stdout.on('data', (d) => { if (out.length < 8 << 20) out += d; });
71
+ child.stderr.on('data', (d) => { if (err.length < 1 << 20) err += d; });
72
+ child.on('error', (e) => finish(false, e.message));
73
+ child.on('close', (code) => finish(code === 0));
74
+ child.on('exit', (code) => { drain = setTimeout(() => finish(code === 0), 300); });
52
75
  });
53
76
  }
54
77
 
@@ -77,7 +100,9 @@ class Control {
77
100
  let j = null;
78
101
  try { j = JSON.parse(r.out); } catch { /* text */ }
79
102
  desk.ready = j ? j.ok !== false : r.ok;
80
- desk.why = desk.ready ? '' : (j?.error?.message || r.err || r.out).split('\n')[0].slice(0, 200);
103
+ desk.why = desk.ready ? '' : r.timedOut
104
+ ? (process.platform === 'darwin' ? 'agent-desktop no respon. Dona-li permís a Configuració del Sistema → Privadesa i seguretat → Accessibilitat i torna-ho a provar.' : 'agent-desktop no respon.')
105
+ : (j?.error?.message || r.err || r.out).split('\n')[0].slice(0, 200);
81
106
  }
82
107
  const shotTool = process.platform === 'darwin' ? which('screencapture') : which('grim') || which('scrot') || which('gnome-screenshot');
83
108
  this.capsCache = {
@@ -97,7 +122,7 @@ class Control {
97
122
  const parts = [];
98
123
  const tries = kind === 'navegador' ? [['skills', 'get', 'core'], ['skills', 'get', 'agent-browser']] : [['skills', 'get', 'desktop'], ['skills', 'get', 'platform']];
99
124
  for (const args of tries) {
100
- const r = await run(bin, args, 20000);
125
+ const r = await run(bin, args, 8000);
101
126
  if (r.ok && r.out.trim()) {
102
127
  parts.push(r.out.trim().replace(/^---[\s\S]*?\n---\s*/, ''));
103
128
  if (kind === 'navegador') break;
@@ -214,7 +239,8 @@ class Control {
214
239
  this.audit.add({ type: 'control', what: `${kind === 'navegador' ? 'Control del navegador' : 'Control de l’escriptori'} ${body[kind] ? 'activat' : 'desactivat'}`, by: device?.name });
215
240
  }
216
241
  config.save(this.cfg);
217
- return { ...(await this.caps(true)), notes };
242
+ for (const kind of ['navegador', 'escriptori']) caps[kind].enabled = this.enabled(kind);
243
+ return { ...caps, notes };
218
244
  },
219
245
  },
220
246
  { method: 'POST', path: '/api/screen', handler: ({ body }) => this.screenshot(body?.from) },
package/web/app/app.js CHANGED
@@ -141,7 +141,9 @@ async function api(path, opts = {}) {
141
141
  const m = machine();
142
142
  if (!m) throw new Error('No hi ha cap ordinador connectat');
143
143
  const body = opts.body && typeof opts.body !== 'string' ? opts.body : opts.body ? JSON.parse(opts.body) : undefined;
144
- const res = await fetch(m.url + path, { ...opts, headers: { Authorization: `Bearer ${m.token}`, 'content-type': 'application/json', ...(opts.headers || {}) }, body: body ? JSON.stringify(body) : undefined });
144
+ let res;
145
+ try { res = await fetch(m.url + path, { ...opts, headers: { Authorization: `Bearer ${m.token}`, 'content-type': 'application/json', ...(opts.headers || {}) }, body: body ? JSON.stringify(body) : undefined }); }
146
+ catch (e) { if (e.name === 'AbortError') throw e; throw new Error(`No he pogut parlar amb ${m.name || 'l’ordinador'}. Comprova que nio hi estigui engegat i torna-ho a provar.`); }
145
147
  const data = await res.json().catch(() => ({}));
146
148
  if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
147
149
  if (Array.isArray(data.agents)) data.agents = data.agents.filter((x) => x.id !== 'demo'); // l'agent de proves del programa no s'ensenya
package/web/sw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // nio · service worker: obre l'app encara que no hi hagi connexió i mostra els avisos.
2
- const SHELL = 'nio-shell-v36';
2
+ const SHELL = 'nio-shell-v37';
3
3
  const FILES = ['/app', '/app/style.css', '/app/app.js', '/bot.css', '/bot.js', '/board.css', '/board.js', '/favicon.svg', '/manifest.webmanifest'];
4
4
 
5
5
  self.addEventListener('install', (e) => {