indieclaw-agent 2.1.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.
Files changed (3) hide show
  1. package/index.js +61 -36
  2. package/install.sh +10 -4
  3. 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() {
@@ -259,7 +260,9 @@ wss.on('connection', (ws) => {
259
260
  }
260
261
 
261
262
  // Route message
262
- handleMessage(ws, msg);
263
+ handleMessage(ws, msg).catch((err) => {
264
+ console.error('[handleMessage] Unhandled error:', err.message || err);
265
+ });
263
266
  });
264
267
 
265
268
  ws.on('close', () => {
@@ -277,6 +280,12 @@ wss.on('connection', (ws) => {
277
280
  activeChats.delete(id);
278
281
  }
279
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
+ }
280
289
  });
281
290
  });
282
291
 
@@ -354,9 +363,9 @@ async function handleMessage(ws, msg) {
354
363
  case 'cron.history':
355
364
  return handleCronHistory(ws, msg);
356
365
  case 'agent.list':
357
- return handleAgentList(ws, msg);
366
+ return await handleAgentList(ws, msg);
358
367
  case 'agent.logs':
359
- return handleAgentLogs(ws, msg);
368
+ return await handleAgentLogs(ws, msg);
360
369
  default:
361
370
  return replyError(ws, id, `Unknown message type: ${type}`);
362
371
  }
@@ -1078,7 +1087,7 @@ function handleLogsSystem(ws, { id, lines = 200 }) {
1078
1087
  const platform = os.platform();
1079
1088
 
1080
1089
  if (platform === 'linux') {
1081
- 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) => {
1082
1091
  if (err) return replyError(ws, id, err.message);
1083
1092
  try {
1084
1093
  const entries = stdout.trim().split('\n').filter(Boolean).map((line) => {
@@ -1137,32 +1146,44 @@ function handleLogsSystem(ws, { id, lines = 200 }) {
1137
1146
  function handleLogsSearch(ws, { id, query, lines = 100 }) {
1138
1147
  const platform = os.platform();
1139
1148
  // Sanitize query to prevent command injection
1140
- const safeQuery = query.replace(/["`$\\]/g, '');
1149
+ const safeQuery = query.replace(/["`$\\!;']/g, '');
1150
+
1151
+ if (!safeQuery || safeQuery.length < 2) {
1152
+ return reply(ws, id, []);
1153
+ }
1141
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;
1142
1163
  if (platform === 'linux') {
1143
- exec(`journalctl -n ${lines} --no-pager -g "${safeQuery}"`, { timeout: 10000, maxBuffer: 1024 * 1024 * 5 }, (err, stdout) => {
1144
- if (err) return replyError(ws, id, err.message);
1145
- const entries = stdout.trim().split('\n').filter(Boolean).map((line) => ({
1146
- timestamp: null,
1147
- level: 'info',
1148
- message: line,
1149
- source: '',
1150
- }));
1151
- reply(ws, id, entries);
1152
- });
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}"`;
1153
1166
  } else {
1154
- // macOS
1155
- exec(`grep -i "${safeQuery}" /var/log/system.log | tail -${lines}`, { timeout: 10000 }, (err, stdout) => {
1156
- if (err) return replyError(ws, id, err.message || 'No matches found');
1157
- const entries = stdout.trim().split('\n').filter(Boolean).map((line) => ({
1158
- timestamp: null,
1159
- level: 'info',
1160
- message: line,
1161
- source: '',
1162
- }));
1163
- reply(ws, id, entries);
1164
- });
1167
+ cmd = `grep -i "${safeQuery}" /var/log/system.log | tail -${lines}`;
1165
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);
1166
1187
  }
1167
1188
 
1168
1189
  // --- Cron History ---
@@ -1207,17 +1228,21 @@ function handleCronHistory(ws, { id }) {
1207
1228
 
1208
1229
  // --- Agent List (OpenClaw Models) ---
1209
1230
  async function handleAgentList(ws, { id }) {
1210
- const oc = await detectOpenClaw();
1211
- if (oc.available) {
1212
- const models = oc.models.map((modelId) => ({
1213
- id: modelId,
1214
- name: modelId,
1215
- status: 'running',
1216
- port: oc.port,
1217
- }));
1218
- return reply(ws, id, models);
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);
1241
+ }
1242
+ reply(ws, id, []);
1243
+ } catch (err) {
1244
+ replyError(ws, id, 'OpenClaw detection failed: ' + (err.message || String(err)));
1219
1245
  }
1220
- reply(ws, id, []);
1221
1246
  }
1222
1247
 
1223
1248
  // --- Agent Logs ---
package/install.sh CHANGED
@@ -135,12 +135,18 @@ if [ -n "$TOKEN" ]; then
135
135
  echo -e " ${BOLD}Auth Token:${NC}"
136
136
  echo -e " ${CYAN}${TOKEN}${NC}"
137
137
  echo ""
138
- echo -e " ${BOLD}Deep Link (paste in phone browser):${NC}"
138
+ echo -e " ${BOLD}Deep Link (paste in IndieClaw app → Add Machine → Paste Link):${NC}"
139
139
  echo -e " ${CYAN}${DEEP_LINK}${NC}"
140
140
  echo ""
141
- echo -e " ${BOLD}Or scan QR code:${NC}"
142
- echo -e " Run: ${BOLD}indieclaw-agent${NC} interactively to see QR code"
143
- echo -e " Or: ${BOLD}sudo journalctl -u indieclaw-agent --no-pager | head -50${NC}"
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
144
150
  fi
145
151
 
146
152
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indieclaw-agent",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Manage your server from your phone. Agent for the IndieClaw mobile app.",
5
5
  "main": "index.js",
6
6
  "bin": {