indieclaw-agent 1.1.0 → 1.1.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.
Files changed (2) hide show
  1. package/index.js +131 -38
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -210,6 +210,7 @@ function handleFsDelete(ws, { id, path: filePath }) {
210
210
  // --- System Stats ---
211
211
  function handleSystemStats(ws, { id }) {
212
212
  const platform = os.platform();
213
+ const mem = platform === 'darwin' ? getDarwinMemory() : getLinuxMemory();
213
214
  const stats = {
214
215
  hostname: os.hostname(),
215
216
  platform,
@@ -220,17 +221,61 @@ function handleSystemStats(ws, { id }) {
220
221
  cores: os.cpus().length,
221
222
  usage: getCpuUsage(platform),
222
223
  },
223
- memory: {
224
- total: os.totalmem(),
225
- free: os.freemem(),
226
- used: os.totalmem() - os.freemem(),
227
- usagePercent: Math.round(((os.totalmem() - os.freemem()) / os.totalmem()) * 100),
228
- },
224
+ memory: mem,
229
225
  disk: getDiskUsage(platform),
230
226
  };
231
227
  reply(ws, id, stats);
232
228
  }
233
229
 
230
+ function getDarwinMemory() {
231
+ const total = os.totalmem();
232
+ try {
233
+ const out = execSync('vm_stat', { timeout: 3000 }).toString();
234
+ const pageMatch = out.match(/page size of (\d+) bytes/);
235
+ const pageSize = pageMatch ? parseInt(pageMatch[1], 10) : 16384;
236
+
237
+ const val = (key) => {
238
+ const m = out.match(new RegExp(key + ':\\s+(\\d+)'));
239
+ return m ? parseInt(m[1], 10) : 0;
240
+ };
241
+
242
+ const wired = val('Pages wired down');
243
+ const active = val('Pages active');
244
+ const compressed = val('Pages occupied by compressor');
245
+ const purgeable = val('Pages purgeable');
246
+
247
+ // Used = wired + active (minus purgeable) + compressed — matches Activity Monitor
248
+ const used = (wired + active - purgeable + compressed) * pageSize;
249
+ const free = total - used;
250
+ return {
251
+ total,
252
+ free: Math.max(free, 0),
253
+ used: Math.min(used, total),
254
+ usagePercent: Math.round((Math.min(used, total) / total) * 100),
255
+ };
256
+ } catch {
257
+ // Fallback to Node.js (inaccurate on macOS but better than nothing)
258
+ const free = os.freemem();
259
+ return {
260
+ total,
261
+ free,
262
+ used: total - free,
263
+ usagePercent: Math.round(((total - free) / total) * 100),
264
+ };
265
+ }
266
+ }
267
+
268
+ function getLinuxMemory() {
269
+ const total = os.totalmem();
270
+ const free = os.freemem();
271
+ return {
272
+ total,
273
+ free,
274
+ used: total - free,
275
+ usagePercent: Math.round(((total - free) / total) * 100),
276
+ };
277
+ }
278
+
234
279
  function getCpuUsage(platform) {
235
280
  try {
236
281
  if (platform === 'linux') {
@@ -251,23 +296,32 @@ function getCpuUsage(platform) {
251
296
 
252
297
  function getDiskUsage(platform) {
253
298
  try {
254
- const cmd = platform === 'darwin' ? 'df -k /' : 'df -k / --output=source,size,used,avail,pcent,target';
255
- const out = execSync(cmd, { timeout: 5000 }).toString();
256
- const lines = out.trim().split('\n').slice(1);
257
- return lines.map((line) => {
258
- const parts = line.trim().split(/\s+/);
259
- if (platform === 'darwin') {
260
- // macOS df: Filesystem 512-blocks Used Available Capacity ...
261
- // df -k gives 1K-blocks
299
+ if (platform === 'darwin') {
300
+ // macOS APFS: "Used" column only shows one volume's data
301
+ // Calculate used = total - available for accurate results
302
+ const out = execSync('df -k /', { timeout: 5000 }).toString();
303
+ const lines = out.trim().split('\n').slice(1);
304
+ return lines.map((line) => {
305
+ const parts = line.trim().split(/\s+/);
306
+ // Columns: Filesystem 1K-blocks Used Available Capacity iused ifree %iused Mounted
307
+ const total = parseInt(parts[1], 10) * 1024;
308
+ const available = parseInt(parts[3], 10) * 1024;
309
+ const used = total - available;
262
310
  return {
263
311
  filesystem: parts[0],
264
- mount: parts[8] || parts[5] || '/',
265
- total: parseInt(parts[1], 10) * 1024,
266
- used: parseInt(parts[2], 10) * 1024,
267
- available: parseInt(parts[3], 10) * 1024,
268
- usagePercent: parseInt(parts[4], 10),
312
+ mount: parts[8] || '/',
313
+ total,
314
+ used,
315
+ available,
316
+ usagePercent: Math.round((used / total) * 100),
269
317
  };
270
- }
318
+ });
319
+ }
320
+ // Linux
321
+ const out = execSync('df -k / --output=source,size,used,avail,pcent,target', { timeout: 5000 }).toString();
322
+ const lines = out.trim().split('\n').slice(1);
323
+ return lines.map((line) => {
324
+ const parts = line.trim().split(/\s+/);
271
325
  return {
272
326
  filesystem: parts[0],
273
327
  total: parseInt(parts[1], 10) * 1024,
@@ -455,46 +509,85 @@ function handleCronList(ws, { id }) {
455
509
  });
456
510
  }
457
511
 
458
- // --- Terminal (PTY) ---
512
+ // --- Terminal (PTY with child_process fallback) ---
459
513
  function handleTerminalStart(ws, { id }) {
460
- if (!pty) {
461
- return replyError(ws, id, 'Terminal not available (node-pty not installed)');
514
+ const shell = process.env.SHELL || '/bin/bash';
515
+
516
+ // Try node-pty first
517
+ if (pty) {
518
+ try {
519
+ const term = pty.spawn(shell, [], {
520
+ name: 'xterm-256color',
521
+ cols: 80,
522
+ rows: 24,
523
+ cwd: os.homedir(),
524
+ env: { ...process.env, TERM: 'xterm-256color' },
525
+ });
526
+
527
+ term._ws = ws;
528
+ term._isPty = true;
529
+ terminals.set(id, term);
530
+
531
+ term.onData((data) => {
532
+ send(ws, { type: 'terminal.output', id, data });
533
+ });
534
+
535
+ term.onExit(({ exitCode }) => {
536
+ send(ws, { type: 'terminal.exit', id, exitCode });
537
+ terminals.delete(id);
538
+ });
539
+
540
+ return reply(ws, id, { pid: term.pid });
541
+ } catch (err) {
542
+ console.log(`[agent] node-pty spawn failed (${err.message}), using fallback`);
543
+ }
462
544
  }
463
545
 
464
- const shell = process.env.SHELL || '/bin/bash';
465
- const term = pty.spawn(shell, [], {
466
- name: 'xterm-256color',
467
- cols: 80,
468
- rows: 24,
546
+ // Fallback: child_process.spawn
547
+ const { spawn } = require('child_process');
548
+ const proc = spawn(shell, ['-i'], {
469
549
  cwd: os.homedir(),
470
- env: { ...process.env, TERM: 'xterm-256color' },
550
+ env: { ...process.env, TERM: 'dumb' },
551
+ stdio: ['pipe', 'pipe', 'pipe'],
471
552
  });
472
553
 
473
- term._ws = ws;
474
- terminals.set(id, term);
554
+ proc._ws = ws;
555
+ proc._isPty = false;
475
556
 
476
- term.onData((data) => {
477
- send(ws, { type: 'terminal.output', id, data });
557
+ proc.stdout.on('data', (data) => {
558
+ send(ws, { type: 'terminal.output', id, data: data.toString() });
478
559
  });
479
560
 
480
- term.onExit(({ exitCode }) => {
481
- send(ws, { type: 'terminal.exit', id, exitCode });
561
+ proc.stderr.on('data', (data) => {
562
+ send(ws, { type: 'terminal.output', id, data: data.toString() });
563
+ });
564
+
565
+ proc.on('exit', (exitCode) => {
566
+ send(ws, { type: 'terminal.exit', id, exitCode: exitCode ?? 0 });
482
567
  terminals.delete(id);
483
568
  });
484
569
 
485
- reply(ws, id, { pid: term.pid });
570
+ terminals.set(id, proc);
571
+ reply(ws, id, { pid: proc.pid });
486
572
  }
487
573
 
488
574
  function handleTerminalInput(ws, { id, data }) {
489
575
  const term = terminals.get(id);
490
576
  if (!term) return replyError(ws, id, 'Terminal not found');
491
- term.write(data);
577
+ if (term._isPty) {
578
+ term.write(data);
579
+ } else {
580
+ term.stdin.write(data);
581
+ }
492
582
  }
493
583
 
494
584
  function handleTerminalResize(ws, { id, cols, rows }) {
495
585
  const term = terminals.get(id);
496
586
  if (!term) return replyError(ws, id, 'Terminal not found');
497
- term.resize(cols, rows);
587
+ if (term._isPty && term.resize) {
588
+ term.resize(cols, rows);
589
+ }
590
+ // fallback processes don't support resize
498
591
  }
499
592
 
500
593
  function handleTerminalStop(ws, { id }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indieclaw-agent",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Manage your server from your phone. Agent for the IndieClaw mobile app.",
5
5
  "main": "index.js",
6
6
  "bin": {