indieclaw-agent 2.0.0 → 2.2.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/index.js +85 -68
- package/install.sh +69 -22
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -90,6 +90,7 @@ if (TLS_ENABLED) {
|
|
|
90
90
|
|
|
91
91
|
const terminals = new Map(); // id -> pty process
|
|
92
92
|
const activeChats = new Map(); // id -> http.ClientRequest
|
|
93
|
+
const activeSearches = new Map(); // ws -> child_process (one search per connection)
|
|
93
94
|
|
|
94
95
|
// --- Deep Link & QR Code ---
|
|
95
96
|
function getMachineIP() {
|
|
@@ -143,12 +144,14 @@ try {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
// --- OpenClaw Detection ---
|
|
146
|
-
|
|
147
|
+
const OPENCLAW_PORTS = [18789, 8080, 11434, 1234, 8000];
|
|
148
|
+
|
|
149
|
+
function tryOpenClawPort(port) {
|
|
147
150
|
return new Promise((resolve) => {
|
|
148
151
|
const req = http.request(
|
|
149
152
|
{
|
|
150
153
|
hostname: '127.0.0.1',
|
|
151
|
-
port
|
|
154
|
+
port,
|
|
152
155
|
path: '/v1/models',
|
|
153
156
|
method: 'GET',
|
|
154
157
|
timeout: 2000,
|
|
@@ -160,30 +163,44 @@ function detectOpenClaw() {
|
|
|
160
163
|
try {
|
|
161
164
|
const json = JSON.parse(body);
|
|
162
165
|
const models = (json.data || []).map((m) => m.id);
|
|
163
|
-
|
|
166
|
+
if (models.length > 0) {
|
|
167
|
+
resolve({ available: true, models, port });
|
|
168
|
+
} else {
|
|
169
|
+
resolve({ available: false, models: [], port });
|
|
170
|
+
}
|
|
164
171
|
} catch {
|
|
165
|
-
resolve({ available: false, models: [] });
|
|
172
|
+
resolve({ available: false, models: [], port });
|
|
166
173
|
}
|
|
167
174
|
});
|
|
168
175
|
}
|
|
169
176
|
);
|
|
170
177
|
req.on('timeout', () => {
|
|
171
178
|
req.destroy();
|
|
172
|
-
resolve({ available: false, models: [] });
|
|
179
|
+
resolve({ available: false, models: [], port });
|
|
173
180
|
});
|
|
174
181
|
req.on('error', () => {
|
|
175
|
-
resolve({ available: false, models: [] });
|
|
182
|
+
resolve({ available: false, models: [], port });
|
|
176
183
|
});
|
|
177
184
|
req.end();
|
|
178
185
|
});
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
async function detectOpenClaw() {
|
|
189
|
+
// Try all common ports in parallel
|
|
190
|
+
const results = await Promise.all(OPENCLAW_PORTS.map(tryOpenClawPort));
|
|
191
|
+
const found = results.find((r) => r.available);
|
|
192
|
+
if (found) {
|
|
193
|
+
return { available: true, models: found.models, port: found.port };
|
|
194
|
+
}
|
|
195
|
+
return { available: false, models: [], port: null };
|
|
196
|
+
}
|
|
197
|
+
|
|
181
198
|
// Run detection on startup
|
|
182
199
|
detectOpenClaw().then((oc) => {
|
|
183
200
|
if (oc.available) {
|
|
184
|
-
console.log(` [OpenClaw] Detected! Models: ${oc.models.join(', ')}`);
|
|
201
|
+
console.log(` [OpenClaw] Detected on port ${oc.port}! Models: ${oc.models.join(', ')}`);
|
|
185
202
|
} else {
|
|
186
|
-
console.log(
|
|
203
|
+
console.log(` [OpenClaw] Not detected on ports ${OPENCLAW_PORTS.join(', ')}`);
|
|
187
204
|
}
|
|
188
205
|
});
|
|
189
206
|
|
|
@@ -243,7 +260,9 @@ wss.on('connection', (ws) => {
|
|
|
243
260
|
}
|
|
244
261
|
|
|
245
262
|
// Route message
|
|
246
|
-
handleMessage(ws, msg)
|
|
263
|
+
handleMessage(ws, msg).catch((err) => {
|
|
264
|
+
console.error('[handleMessage] Unhandled error:', err.message || err);
|
|
265
|
+
});
|
|
247
266
|
});
|
|
248
267
|
|
|
249
268
|
ws.on('close', () => {
|
|
@@ -261,6 +280,12 @@ wss.on('connection', (ws) => {
|
|
|
261
280
|
activeChats.delete(id);
|
|
262
281
|
}
|
|
263
282
|
}
|
|
283
|
+
// Kill any active search process for this connection
|
|
284
|
+
const search = activeSearches.get(ws);
|
|
285
|
+
if (search) {
|
|
286
|
+
search.kill();
|
|
287
|
+
activeSearches.delete(ws);
|
|
288
|
+
}
|
|
264
289
|
});
|
|
265
290
|
});
|
|
266
291
|
|
|
@@ -338,9 +363,9 @@ async function handleMessage(ws, msg) {
|
|
|
338
363
|
case 'cron.history':
|
|
339
364
|
return handleCronHistory(ws, msg);
|
|
340
365
|
case 'agent.list':
|
|
341
|
-
return handleAgentList(ws, msg);
|
|
366
|
+
return await handleAgentList(ws, msg);
|
|
342
367
|
case 'agent.logs':
|
|
343
|
-
return handleAgentLogs(ws, msg);
|
|
368
|
+
return await handleAgentLogs(ws, msg);
|
|
344
369
|
default:
|
|
345
370
|
return replyError(ws, id, `Unknown message type: ${type}`);
|
|
346
371
|
}
|
|
@@ -1062,7 +1087,7 @@ function handleLogsSystem(ws, { id, lines = 200 }) {
|
|
|
1062
1087
|
const platform = os.platform();
|
|
1063
1088
|
|
|
1064
1089
|
if (platform === 'linux') {
|
|
1065
|
-
exec(`journalctl -n ${lines} --no-pager -o json`, { timeout: 10000, maxBuffer: 1024 * 1024 * 5 }, (err, stdout) => {
|
|
1090
|
+
exec(`journalctl -n ${lines} --no-pager --since "1 hour ago" -o json`, { timeout: 10000, maxBuffer: 1024 * 1024 * 5 }, (err, stdout) => {
|
|
1066
1091
|
if (err) return replyError(ws, id, err.message);
|
|
1067
1092
|
try {
|
|
1068
1093
|
const entries = stdout.trim().split('\n').filter(Boolean).map((line) => {
|
|
@@ -1121,32 +1146,44 @@ function handleLogsSystem(ws, { id, lines = 200 }) {
|
|
|
1121
1146
|
function handleLogsSearch(ws, { id, query, lines = 100 }) {
|
|
1122
1147
|
const platform = os.platform();
|
|
1123
1148
|
// Sanitize query to prevent command injection
|
|
1124
|
-
const safeQuery = query.replace(/["
|
|
1149
|
+
const safeQuery = query.replace(/["`$\\!;']/g, '');
|
|
1150
|
+
|
|
1151
|
+
if (!safeQuery || safeQuery.length < 2) {
|
|
1152
|
+
return reply(ws, id, []);
|
|
1153
|
+
}
|
|
1125
1154
|
|
|
1155
|
+
// Kill any previous search for this connection to prevent pile-up
|
|
1156
|
+
const prev = activeSearches.get(ws);
|
|
1157
|
+
if (prev) {
|
|
1158
|
+
prev.kill();
|
|
1159
|
+
activeSearches.delete(ws);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
let cmd;
|
|
1126
1163
|
if (platform === 'linux') {
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const entries = stdout.trim().split('\n').filter(Boolean).map((line) => ({
|
|
1130
|
-
timestamp: null,
|
|
1131
|
-
level: 'info',
|
|
1132
|
-
message: line,
|
|
1133
|
-
source: '',
|
|
1134
|
-
}));
|
|
1135
|
-
reply(ws, id, entries);
|
|
1136
|
-
});
|
|
1164
|
+
// --since "24h ago" limits search scope instead of grepping entire journal
|
|
1165
|
+
cmd = `journalctl -n ${lines} --no-pager --since "24 hours ago" -g "${safeQuery}"`;
|
|
1137
1166
|
} else {
|
|
1138
|
-
|
|
1139
|
-
exec(`grep -i "${safeQuery}" /var/log/system.log | tail -${lines}`, { timeout: 10000 }, (err, stdout) => {
|
|
1140
|
-
if (err) return replyError(ws, id, err.message || 'No matches found');
|
|
1141
|
-
const entries = stdout.trim().split('\n').filter(Boolean).map((line) => ({
|
|
1142
|
-
timestamp: null,
|
|
1143
|
-
level: 'info',
|
|
1144
|
-
message: line,
|
|
1145
|
-
source: '',
|
|
1146
|
-
}));
|
|
1147
|
-
reply(ws, id, entries);
|
|
1148
|
-
});
|
|
1167
|
+
cmd = `grep -i "${safeQuery}" /var/log/system.log | tail -${lines}`;
|
|
1149
1168
|
}
|
|
1169
|
+
|
|
1170
|
+
const child = exec(cmd, { timeout: 10000, maxBuffer: 1024 * 1024 * 2 }, (err, stdout) => {
|
|
1171
|
+
activeSearches.delete(ws);
|
|
1172
|
+
if (err) {
|
|
1173
|
+
// journalctl returns exit code 1 when no matches found — not an error
|
|
1174
|
+
if (err.killed) return; // process was killed by a newer search
|
|
1175
|
+
return reply(ws, id, []);
|
|
1176
|
+
}
|
|
1177
|
+
const entries = stdout.trim().split('\n').filter(Boolean).map((line) => ({
|
|
1178
|
+
timestamp: null,
|
|
1179
|
+
level: 'info',
|
|
1180
|
+
message: line,
|
|
1181
|
+
source: '',
|
|
1182
|
+
}));
|
|
1183
|
+
reply(ws, id, entries);
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
activeSearches.set(ws, child);
|
|
1150
1187
|
}
|
|
1151
1188
|
|
|
1152
1189
|
// --- Cron History ---
|
|
@@ -1190,42 +1227,22 @@ function handleCronHistory(ws, { id }) {
|
|
|
1190
1227
|
}
|
|
1191
1228
|
|
|
1192
1229
|
// --- Agent List (OpenClaw Models) ---
|
|
1193
|
-
function handleAgentList(ws, { id }) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
res.on('data', (chunk) => (body += chunk));
|
|
1205
|
-
res.on('end', () => {
|
|
1206
|
-
try {
|
|
1207
|
-
const json = JSON.parse(body);
|
|
1208
|
-
const models = (json.data || []).map((m) => ({
|
|
1209
|
-
id: m.id,
|
|
1210
|
-
name: m.id,
|
|
1211
|
-
status: 'running',
|
|
1212
|
-
port: 18789,
|
|
1213
|
-
}));
|
|
1214
|
-
reply(ws, id, models);
|
|
1215
|
-
} catch {
|
|
1216
|
-
reply(ws, id, []);
|
|
1217
|
-
}
|
|
1218
|
-
});
|
|
1230
|
+
async function handleAgentList(ws, { id }) {
|
|
1231
|
+
try {
|
|
1232
|
+
const oc = await detectOpenClaw();
|
|
1233
|
+
if (oc.available) {
|
|
1234
|
+
const models = oc.models.map((modelId) => ({
|
|
1235
|
+
id: modelId,
|
|
1236
|
+
name: modelId,
|
|
1237
|
+
status: 'running',
|
|
1238
|
+
port: oc.port,
|
|
1239
|
+
}));
|
|
1240
|
+
return reply(ws, id, models);
|
|
1219
1241
|
}
|
|
1220
|
-
);
|
|
1221
|
-
req.on('timeout', () => {
|
|
1222
|
-
req.destroy();
|
|
1223
1242
|
reply(ws, id, []);
|
|
1224
|
-
})
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
});
|
|
1228
|
-
req.end();
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
replyError(ws, id, 'OpenClaw detection failed: ' + (err.message || String(err)));
|
|
1245
|
+
}
|
|
1229
1246
|
}
|
|
1230
1247
|
|
|
1231
1248
|
// --- Agent Logs ---
|
package/install.sh
CHANGED
|
@@ -23,7 +23,7 @@ else
|
|
|
23
23
|
fi
|
|
24
24
|
|
|
25
25
|
# Step 1: Check/Install Node.js
|
|
26
|
-
echo -e "${YELLOW}[1/
|
|
26
|
+
echo -e "${YELLOW}[1/5]${NC} Checking Node.js..."
|
|
27
27
|
if command -v node &> /dev/null; then
|
|
28
28
|
NODE_VERSION=$(node -v)
|
|
29
29
|
echo -e " ${GREEN}✓${NC} Node.js ${NODE_VERSION} found"
|
|
@@ -46,12 +46,13 @@ else
|
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
48
|
# Step 2: Install indieclaw-agent
|
|
49
|
-
echo -e "${YELLOW}[2/
|
|
50
|
-
$SUDO npm install -g indieclaw-agent 2>/dev/null || npm install -g indieclaw-agent
|
|
51
|
-
|
|
49
|
+
echo -e "${YELLOW}[2/5]${NC} Installing indieclaw-agent..."
|
|
50
|
+
$SUDO npm install -g indieclaw-agent@latest 2>/dev/null || npm install -g indieclaw-agent@latest
|
|
51
|
+
AGENT_VERSION=$(node -e "try{console.log(require(require('child_process').execSync('which indieclaw-agent',{encoding:'utf8'}).trim().replace(/indieclaw-agent$/,'')+'../lib/node_modules/indieclaw-agent/package.json').version)}catch{console.log('2.0.0')}" 2>/dev/null || echo "2.0.0")
|
|
52
|
+
echo -e " ${GREEN}✓${NC} indieclaw-agent v${AGENT_VERSION} installed"
|
|
52
53
|
|
|
53
54
|
# Step 3: Create systemd service
|
|
54
|
-
echo -e "${YELLOW}[3/
|
|
55
|
+
echo -e "${YELLOW}[3/5]${NC} Setting up background service..."
|
|
55
56
|
AGENT_PATH=$(which indieclaw-agent)
|
|
56
57
|
CURRENT_USER=$(whoami)
|
|
57
58
|
|
|
@@ -74,32 +75,78 @@ EOF
|
|
|
74
75
|
|
|
75
76
|
$SUDO systemctl daemon-reload
|
|
76
77
|
$SUDO systemctl enable indieclaw-agent
|
|
77
|
-
$SUDO systemctl
|
|
78
|
+
$SUDO systemctl restart indieclaw-agent
|
|
78
79
|
echo -e " ${GREEN}✓${NC} Service created and started"
|
|
79
80
|
|
|
80
|
-
# Step 4: Wait for token
|
|
81
|
+
# Step 4: Wait for token and detect IP
|
|
82
|
+
echo -e "${YELLOW}[4/5]${NC} Detecting configuration..."
|
|
81
83
|
sleep 2
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
echo -e "${CYAN}═══════════════════════════════════════${NC}"
|
|
86
|
-
echo ""
|
|
85
|
+
PORT=3100
|
|
86
|
+
HOSTNAME=$(hostname)
|
|
87
87
|
|
|
88
|
+
# Get token
|
|
88
89
|
if [ -f "$HOME/.indieclaw-token" ]; then
|
|
89
90
|
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
91
|
else
|
|
99
|
-
echo -e " ${
|
|
92
|
+
echo -e " ${YELLOW}⚠${NC} Token file not found yet. Check: cat ~/.indieclaw-token"
|
|
93
|
+
TOKEN=""
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Detect IP (same logic as agent: try tailscale first, fallback to network)
|
|
97
|
+
MACHINE_IP=""
|
|
98
|
+
if command -v tailscale &> /dev/null; then
|
|
99
|
+
MACHINE_IP=$(tailscale ip -4 2>/dev/null || true)
|
|
100
|
+
fi
|
|
101
|
+
if [ -z "$MACHINE_IP" ]; then
|
|
102
|
+
MACHINE_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || ip route get 1 2>/dev/null | awk '{print $7; exit}' || echo "")
|
|
103
|
+
fi
|
|
104
|
+
if [ -z "$MACHINE_IP" ]; then
|
|
105
|
+
MACHINE_IP="YOUR_SERVER_IP"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
echo -e " ${GREEN}✓${NC} IP: ${MACHINE_IP}"
|
|
109
|
+
|
|
110
|
+
# Step 5: Check OpenClaw
|
|
111
|
+
echo -e "${YELLOW}[5/5]${NC} Checking OpenClaw..."
|
|
112
|
+
OPENCLAW_STATUS="not detected"
|
|
113
|
+
if curl -s --max-time 2 http://127.0.0.1:18789/v1/models > /dev/null 2>&1; then
|
|
114
|
+
OPENCLAW_STATUS="detected on port 18789"
|
|
115
|
+
echo -e " ${GREEN}✓${NC} OpenClaw detected on port 18789"
|
|
116
|
+
else
|
|
117
|
+
echo -e " ${YELLOW}—${NC} OpenClaw not detected on port 18789 (optional, for AI features)"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Build deep link
|
|
121
|
+
DEEP_LINK="indieclaw://connect?host=${MACHINE_IP}&port=${PORT}&token=${TOKEN}&name=${HOSTNAME}&tls=0"
|
|
122
|
+
|
|
123
|
+
# Show results
|
|
124
|
+
echo ""
|
|
125
|
+
echo -e "${CYAN}╔═══════════════════════════════════════╗${NC}"
|
|
126
|
+
echo -e "${CYAN}║ ${BOLD}IndieClaw Agent v${AGENT_VERSION}${NC}${CYAN} ║${NC}"
|
|
127
|
+
echo -e "${CYAN}╠═══════════════════════════════════════╣${NC}"
|
|
128
|
+
echo -e "${CYAN}║${NC} Port: ${BOLD}${PORT}${NC}"
|
|
129
|
+
echo -e "${CYAN}║${NC} IP: ${BOLD}${MACHINE_IP}${NC}"
|
|
130
|
+
echo -e "${CYAN}║${NC} OpenClaw: ${BOLD}${OPENCLAW_STATUS}${NC}"
|
|
131
|
+
echo -e "${CYAN}╚═══════════════════════════════════════╝${NC}"
|
|
132
|
+
echo ""
|
|
133
|
+
|
|
134
|
+
if [ -n "$TOKEN" ]; then
|
|
135
|
+
echo -e " ${BOLD}Auth Token:${NC}"
|
|
136
|
+
echo -e " ${CYAN}${TOKEN}${NC}"
|
|
100
137
|
echo ""
|
|
101
|
-
echo -e "
|
|
102
|
-
echo -e " ${
|
|
138
|
+
echo -e " ${BOLD}Deep Link (paste in IndieClaw app → Add Machine → Paste Link):${NC}"
|
|
139
|
+
echo -e " ${CYAN}${DEEP_LINK}${NC}"
|
|
140
|
+
echo ""
|
|
141
|
+
|
|
142
|
+
# Generate QR code using the installed qrcode-terminal package
|
|
143
|
+
AGENT_MODULES=$(npm root -g 2>/dev/null)/indieclaw-agent/node_modules
|
|
144
|
+
if [ -d "$AGENT_MODULES/qrcode-terminal" ]; then
|
|
145
|
+
echo -e " ${BOLD}Scan with IndieClaw app:${NC}"
|
|
146
|
+
echo ""
|
|
147
|
+
node -e "require('${AGENT_MODULES}/qrcode-terminal').generate('${DEEP_LINK}', {small: true}, function(qr) { qr.split('\n').forEach(function(l) { console.log(' ' + l) }) })" 2>/dev/null || true
|
|
148
|
+
echo ""
|
|
149
|
+
fi
|
|
103
150
|
fi
|
|
104
151
|
|
|
105
152
|
echo ""
|