indieclaw-agent 1.0.0 → 1.1.1
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/README.md +1 -1
- package/index.js +167 -18
- package/install.sh +113 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# IndieClaw Agent
|
|
2
2
|
|
|
3
|
-
Manage your server from your phone. This is the server-side agent for the [IndieClaw](https://github.com/
|
|
3
|
+
Manage your server from your phone. This is the server-side agent for the [IndieClaw](https://github.com/Muhammed58/indieclaw) mobile app.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
package/index.js
CHANGED
|
@@ -119,6 +119,12 @@ async function handleMessage(ws, msg) {
|
|
|
119
119
|
return handleFsDelete(ws, msg);
|
|
120
120
|
case 'system.stats':
|
|
121
121
|
return handleSystemStats(ws, msg);
|
|
122
|
+
case 'system.top.cpu':
|
|
123
|
+
return handleTopCpu(ws, msg);
|
|
124
|
+
case 'system.top.memory':
|
|
125
|
+
return handleTopMemory(ws, msg);
|
|
126
|
+
case 'system.disk.details':
|
|
127
|
+
return handleDiskDetails(ws, msg);
|
|
122
128
|
case 'docker.list':
|
|
123
129
|
return handleDockerList(ws, msg);
|
|
124
130
|
case 'docker.logs':
|
|
@@ -276,6 +282,110 @@ function getDiskUsage(platform) {
|
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
|
|
285
|
+
// --- Top Processes (CPU) ---
|
|
286
|
+
function handleTopCpu(ws, { id }) {
|
|
287
|
+
const platform = os.platform();
|
|
288
|
+
const cmd = platform === 'darwin'
|
|
289
|
+
? 'ps aux -r | head -11'
|
|
290
|
+
: 'ps aux --sort=-%cpu | head -11';
|
|
291
|
+
|
|
292
|
+
exec(cmd, { timeout: 5000 }, (err, stdout) => {
|
|
293
|
+
if (err) return replyError(ws, id, err.message);
|
|
294
|
+
const processes = parsePsOutput(stdout);
|
|
295
|
+
reply(ws, id, { processes, timestamp: Date.now() });
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// --- Top Processes (Memory) ---
|
|
300
|
+
function handleTopMemory(ws, { id }) {
|
|
301
|
+
const platform = os.platform();
|
|
302
|
+
const cmd = platform === 'darwin'
|
|
303
|
+
? 'ps aux -m | head -11'
|
|
304
|
+
: 'ps aux --sort=-%mem | head -11';
|
|
305
|
+
|
|
306
|
+
exec(cmd, { timeout: 5000 }, (err, stdout) => {
|
|
307
|
+
if (err) return replyError(ws, id, err.message);
|
|
308
|
+
const processes = parsePsOutput(stdout);
|
|
309
|
+
reply(ws, id, { processes, timestamp: Date.now() });
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function parsePsOutput(stdout) {
|
|
314
|
+
const lines = stdout.trim().split('\n').slice(1); // skip header
|
|
315
|
+
return lines.map((line) => {
|
|
316
|
+
const parts = line.trim().split(/\s+/);
|
|
317
|
+
// ps aux columns: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
318
|
+
return {
|
|
319
|
+
pid: parseInt(parts[1], 10),
|
|
320
|
+
name: parts.slice(10).join(' '),
|
|
321
|
+
cpu: parseFloat(parts[2]) || 0,
|
|
322
|
+
memory: parseFloat(parts[3]) || 0,
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- Disk Details ---
|
|
328
|
+
function handleDiskDetails(ws, { id }) {
|
|
329
|
+
const platform = os.platform();
|
|
330
|
+
|
|
331
|
+
const virtualFs = new Set([
|
|
332
|
+
'devtmpfs', 'tmpfs', 'sysfs', 'proc', 'devpts', 'securityfs',
|
|
333
|
+
'cgroup', 'cgroup2', 'pstore', 'debugfs', 'hugetlbfs', 'mqueue',
|
|
334
|
+
'configfs', 'fusectl', 'tracefs', 'bpf', 'overlay', 'nsfs',
|
|
335
|
+
'autofs', 'binfmt_misc', 'efivarfs',
|
|
336
|
+
]);
|
|
337
|
+
|
|
338
|
+
const cmd = 'df -kT';
|
|
339
|
+
|
|
340
|
+
exec(cmd, { timeout: 5000 }, (err, stdout) => {
|
|
341
|
+
if (err) {
|
|
342
|
+
// Fallback: df -k without -T (macOS sometimes lacks -T)
|
|
343
|
+
return exec('df -k', { timeout: 5000 }, (err2, stdout2) => {
|
|
344
|
+
if (err2) return replyError(ws, id, err2.message);
|
|
345
|
+
const partitions = parseDfOutput(stdout2, false, virtualFs);
|
|
346
|
+
reply(ws, id, { partitions, timestamp: Date.now() });
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const partitions = parseDfOutput(stdout, true, virtualFs);
|
|
350
|
+
reply(ws, id, { partitions, timestamp: Date.now() });
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function parseDfOutput(stdout, hasType, virtualFs) {
|
|
355
|
+
const lines = stdout.trim().split('\n').slice(1);
|
|
356
|
+
return lines
|
|
357
|
+
.map((line) => {
|
|
358
|
+
const parts = line.trim().split(/\s+/);
|
|
359
|
+
if (hasType) {
|
|
360
|
+
// Filesystem Type 1K-blocks Used Available Use% Mounted
|
|
361
|
+
const fsType = parts[1];
|
|
362
|
+
if (virtualFs.has(fsType)) return null;
|
|
363
|
+
return {
|
|
364
|
+
filesystem: parts[0],
|
|
365
|
+
type: fsType,
|
|
366
|
+
total: parseInt(parts[2], 10) * 1024,
|
|
367
|
+
used: parseInt(parts[3], 10) * 1024,
|
|
368
|
+
available: parseInt(parts[4], 10) * 1024,
|
|
369
|
+
usagePercent: parseInt(parts[5], 10) || 0,
|
|
370
|
+
mount: parts.slice(6).join(' ') || '/',
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// Filesystem 1K-blocks Used Available Use% Mounted
|
|
374
|
+
const fsName = parts[0];
|
|
375
|
+
if (fsName === 'devfs' || fsName === 'map' || fsName.startsWith('map ')) return null;
|
|
376
|
+
return {
|
|
377
|
+
filesystem: fsName,
|
|
378
|
+
type: 'unknown',
|
|
379
|
+
total: parseInt(parts[1], 10) * 1024,
|
|
380
|
+
used: parseInt(parts[2], 10) * 1024,
|
|
381
|
+
available: parseInt(parts[3], 10) * 1024,
|
|
382
|
+
usagePercent: parseInt(parts[4], 10) || 0,
|
|
383
|
+
mount: parts.slice(5).join(' ') || '/',
|
|
384
|
+
};
|
|
385
|
+
})
|
|
386
|
+
.filter(Boolean);
|
|
387
|
+
}
|
|
388
|
+
|
|
279
389
|
// --- Docker ---
|
|
280
390
|
function handleDockerList(ws, { id }) {
|
|
281
391
|
exec(
|
|
@@ -345,46 +455,85 @@ function handleCronList(ws, { id }) {
|
|
|
345
455
|
});
|
|
346
456
|
}
|
|
347
457
|
|
|
348
|
-
// --- Terminal (PTY) ---
|
|
458
|
+
// --- Terminal (PTY with child_process fallback) ---
|
|
349
459
|
function handleTerminalStart(ws, { id }) {
|
|
350
|
-
|
|
351
|
-
|
|
460
|
+
const shell = process.env.SHELL || '/bin/bash';
|
|
461
|
+
|
|
462
|
+
// Try node-pty first
|
|
463
|
+
if (pty) {
|
|
464
|
+
try {
|
|
465
|
+
const term = pty.spawn(shell, [], {
|
|
466
|
+
name: 'xterm-256color',
|
|
467
|
+
cols: 80,
|
|
468
|
+
rows: 24,
|
|
469
|
+
cwd: os.homedir(),
|
|
470
|
+
env: { ...process.env, TERM: 'xterm-256color' },
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
term._ws = ws;
|
|
474
|
+
term._isPty = true;
|
|
475
|
+
terminals.set(id, term);
|
|
476
|
+
|
|
477
|
+
term.onData((data) => {
|
|
478
|
+
send(ws, { type: 'terminal.output', id, data });
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
term.onExit(({ exitCode }) => {
|
|
482
|
+
send(ws, { type: 'terminal.exit', id, exitCode });
|
|
483
|
+
terminals.delete(id);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return reply(ws, id, { pid: term.pid });
|
|
487
|
+
} catch (err) {
|
|
488
|
+
console.log(`[agent] node-pty spawn failed (${err.message}), using fallback`);
|
|
489
|
+
}
|
|
352
490
|
}
|
|
353
491
|
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
cols: 80,
|
|
358
|
-
rows: 24,
|
|
492
|
+
// Fallback: child_process.spawn
|
|
493
|
+
const { spawn } = require('child_process');
|
|
494
|
+
const proc = spawn(shell, ['-i'], {
|
|
359
495
|
cwd: os.homedir(),
|
|
360
|
-
env: { ...process.env, TERM: '
|
|
496
|
+
env: { ...process.env, TERM: 'dumb' },
|
|
497
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
361
498
|
});
|
|
362
499
|
|
|
363
|
-
|
|
364
|
-
|
|
500
|
+
proc._ws = ws;
|
|
501
|
+
proc._isPty = false;
|
|
502
|
+
|
|
503
|
+
proc.stdout.on('data', (data) => {
|
|
504
|
+
send(ws, { type: 'terminal.output', id, data: data.toString() });
|
|
505
|
+
});
|
|
365
506
|
|
|
366
|
-
|
|
367
|
-
send(ws, { type: 'terminal.output', id, data });
|
|
507
|
+
proc.stderr.on('data', (data) => {
|
|
508
|
+
send(ws, { type: 'terminal.output', id, data: data.toString() });
|
|
368
509
|
});
|
|
369
510
|
|
|
370
|
-
|
|
371
|
-
send(ws, { type: 'terminal.exit', id, exitCode });
|
|
511
|
+
proc.on('exit', (exitCode) => {
|
|
512
|
+
send(ws, { type: 'terminal.exit', id, exitCode: exitCode ?? 0 });
|
|
372
513
|
terminals.delete(id);
|
|
373
514
|
});
|
|
374
515
|
|
|
375
|
-
|
|
516
|
+
terminals.set(id, proc);
|
|
517
|
+
reply(ws, id, { pid: proc.pid });
|
|
376
518
|
}
|
|
377
519
|
|
|
378
520
|
function handleTerminalInput(ws, { id, data }) {
|
|
379
521
|
const term = terminals.get(id);
|
|
380
522
|
if (!term) return replyError(ws, id, 'Terminal not found');
|
|
381
|
-
term.
|
|
523
|
+
if (term._isPty) {
|
|
524
|
+
term.write(data);
|
|
525
|
+
} else {
|
|
526
|
+
term.stdin.write(data);
|
|
527
|
+
}
|
|
382
528
|
}
|
|
383
529
|
|
|
384
530
|
function handleTerminalResize(ws, { id, cols, rows }) {
|
|
385
531
|
const term = terminals.get(id);
|
|
386
532
|
if (!term) return replyError(ws, id, 'Terminal not found');
|
|
387
|
-
term.resize
|
|
533
|
+
if (term._isPty && term.resize) {
|
|
534
|
+
term.resize(cols, rows);
|
|
535
|
+
}
|
|
536
|
+
// fallback processes don't support resize
|
|
388
537
|
}
|
|
389
538
|
|
|
390
539
|
function handleTerminalStop(ws, { id }) {
|
package/install.sh
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Colors
|
|
5
|
+
GREEN='\033[0;32m'
|
|
6
|
+
YELLOW='\033[1;33m'
|
|
7
|
+
CYAN='\033[0;36m'
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
NC='\033[0m'
|
|
10
|
+
BOLD='\033[1m'
|
|
11
|
+
|
|
12
|
+
echo ""
|
|
13
|
+
echo -e "${CYAN}╔═══════════════════════════════════════╗${NC}"
|
|
14
|
+
echo -e "${CYAN}║ ${BOLD}IndieClaw Agent Installer${NC}${CYAN} ║${NC}"
|
|
15
|
+
echo -e "${CYAN}╚═══════════════════════════════════════╝${NC}"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
# Check if running as root
|
|
19
|
+
if [ "$EUID" -eq 0 ]; then
|
|
20
|
+
SUDO=""
|
|
21
|
+
else
|
|
22
|
+
SUDO="sudo"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Step 1: Check/Install Node.js
|
|
26
|
+
echo -e "${YELLOW}[1/4]${NC} Checking Node.js..."
|
|
27
|
+
if command -v node &> /dev/null; then
|
|
28
|
+
NODE_VERSION=$(node -v)
|
|
29
|
+
echo -e " ${GREEN}✓${NC} Node.js ${NODE_VERSION} found"
|
|
30
|
+
else
|
|
31
|
+
echo -e " Installing Node.js..."
|
|
32
|
+
if command -v apt-get &> /dev/null; then
|
|
33
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | $SUDO bash -
|
|
34
|
+
$SUDO apt-get install -y nodejs
|
|
35
|
+
elif command -v yum &> /dev/null; then
|
|
36
|
+
curl -fsSL https://rpm.nodesource.com/setup_20.x | $SUDO bash -
|
|
37
|
+
$SUDO yum install -y nodejs
|
|
38
|
+
elif command -v dnf &> /dev/null; then
|
|
39
|
+
curl -fsSL https://rpm.nodesource.com/setup_20.x | $SUDO bash -
|
|
40
|
+
$SUDO dnf install -y nodejs
|
|
41
|
+
else
|
|
42
|
+
echo -e " ${RED}✗${NC} Could not detect package manager. Install Node.js 18+ manually."
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
echo -e " ${GREEN}✓${NC} Node.js $(node -v) installed"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Step 2: Install indieclaw-agent
|
|
49
|
+
echo -e "${YELLOW}[2/4]${NC} Installing indieclaw-agent..."
|
|
50
|
+
$SUDO npm install -g indieclaw-agent 2>/dev/null || npm install -g indieclaw-agent
|
|
51
|
+
echo -e " ${GREEN}✓${NC} indieclaw-agent installed"
|
|
52
|
+
|
|
53
|
+
# Step 3: Create systemd service
|
|
54
|
+
echo -e "${YELLOW}[3/4]${NC} Setting up background service..."
|
|
55
|
+
AGENT_PATH=$(which indieclaw-agent)
|
|
56
|
+
CURRENT_USER=$(whoami)
|
|
57
|
+
|
|
58
|
+
$SUDO tee /etc/systemd/system/indieclaw-agent.service > /dev/null <<EOF
|
|
59
|
+
[Unit]
|
|
60
|
+
Description=IndieClaw Agent
|
|
61
|
+
After=network.target
|
|
62
|
+
|
|
63
|
+
[Service]
|
|
64
|
+
Type=simple
|
|
65
|
+
User=${CURRENT_USER}
|
|
66
|
+
ExecStart=${AGENT_PATH}
|
|
67
|
+
Restart=always
|
|
68
|
+
RestartSec=10
|
|
69
|
+
Environment=INDIECLAW_PORT=3100
|
|
70
|
+
|
|
71
|
+
[Install]
|
|
72
|
+
WantedBy=multi-user.target
|
|
73
|
+
EOF
|
|
74
|
+
|
|
75
|
+
$SUDO systemctl daemon-reload
|
|
76
|
+
$SUDO systemctl enable indieclaw-agent
|
|
77
|
+
$SUDO systemctl start indieclaw-agent
|
|
78
|
+
echo -e " ${GREEN}✓${NC} Service created and started"
|
|
79
|
+
|
|
80
|
+
# Step 4: Wait for token to be generated
|
|
81
|
+
sleep 2
|
|
82
|
+
|
|
83
|
+
# Show token
|
|
84
|
+
echo ""
|
|
85
|
+
echo -e "${CYAN}═══════════════════════════════════════${NC}"
|
|
86
|
+
echo ""
|
|
87
|
+
|
|
88
|
+
if [ -f "$HOME/.indieclaw-token" ]; then
|
|
89
|
+
TOKEN=$(cat "$HOME/.indieclaw-token")
|
|
90
|
+
echo -e " ${GREEN}${BOLD}Setup complete!${NC}"
|
|
91
|
+
echo ""
|
|
92
|
+
echo -e " Your auth token:"
|
|
93
|
+
echo ""
|
|
94
|
+
echo -e " ${BOLD}${CYAN}${TOKEN}${NC}"
|
|
95
|
+
echo ""
|
|
96
|
+
echo -e " Copy this token into the IndieClaw app."
|
|
97
|
+
echo -e " Port: ${BOLD}3100${NC}"
|
|
98
|
+
else
|
|
99
|
+
echo -e " ${GREEN}${BOLD}Setup complete!${NC}"
|
|
100
|
+
echo ""
|
|
101
|
+
echo -e " Run this to see your token:"
|
|
102
|
+
echo -e " ${BOLD}cat ~/.indieclaw-token${NC}"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo ""
|
|
106
|
+
echo -e "${CYAN}═══════════════════════════════════════${NC}"
|
|
107
|
+
echo ""
|
|
108
|
+
echo -e " Useful commands:"
|
|
109
|
+
echo -e " Status: ${BOLD}sudo systemctl status indieclaw-agent${NC}"
|
|
110
|
+
echo -e " Logs: ${BOLD}sudo journalctl -u indieclaw-agent -f${NC}"
|
|
111
|
+
echo -e " Restart: ${BOLD}sudo systemctl restart indieclaw-agent${NC}"
|
|
112
|
+
echo -e " Remove: ${BOLD}sudo systemctl stop indieclaw-agent && sudo systemctl disable indieclaw-agent${NC}"
|
|
113
|
+
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "indieclaw-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Manage your server from your phone. Agent for the IndieClaw mobile app.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/
|
|
27
|
+
"url": "https://github.com/Muhammed58/indieclaw-agent"
|
|
28
28
|
},
|
|
29
|
-
"homepage": "https://github.com/
|
|
29
|
+
"homepage": "https://github.com/Muhammed58/indieclaw-agent#readme",
|
|
30
30
|
"bugs": {
|
|
31
|
-
"url": "https://github.com/
|
|
31
|
+
"url": "https://github.com/Muhammed58/indieclaw-agent/issues"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=18.0.0"
|