indieclaw-agent 1.0.0 → 1.1.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/README.md +1 -1
- package/index.js +110 -0
- 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(
|
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.0",
|
|
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"
|