ai-lens 0.8.47 → 0.8.49

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/.commithash CHANGED
@@ -1 +1 @@
1
- b068fdc
1
+ b5da117
package/cli/status.js CHANGED
@@ -281,16 +281,51 @@ function checkCaptureRun(installedTools) {
281
281
  }
282
282
  } catch { /* best effort */ }
283
283
 
284
- toolResults.push({ name, ok: exitOk, cmd: testCmd, exitDetail, captureNote, shellOk, shellCmd: command, shellDetail, shellCaptureNote });
284
+ // GUI PATH test: on macOS, GUI apps (Cursor) get a minimal PATH from launchd,
285
+ // which often lacks paths added by .zshrc/.bashrc (e.g. nvm, miniforge, homebrew).
286
+ // Test the hook command with a stripped-down PATH to surface "env: node: No such file or directory".
287
+ let guiOk = true;
288
+ let guiDetail = '';
289
+ if (process.platform === 'darwin' && isCursor && shellOk) {
290
+ try {
291
+ const guiPath = '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin';
292
+ const guiEnv = { HOME: homedir(), PATH: guiPath, AI_LENS_PROJECTS: join(homedir(), '.ai-lens-status-check-nonexistent'), AI_LENS_STATUS_CHECK: '1' };
293
+ // Preserve AI_LENS_* config vars so capture.js can load config
294
+ for (const [k, v] of Object.entries(process.env)) {
295
+ if (k.startsWith('AI_LENS_') && !(k in guiEnv)) guiEnv[k] = v;
296
+ }
297
+ const guiResult = spawnSync('/bin/sh', ['-c', shellCommand], {
298
+ input: shellEvent,
299
+ encoding: 'utf-8',
300
+ timeout: 10_000,
301
+ env: guiEnv,
302
+ windowsHide: true,
303
+ });
304
+ if (guiResult.error) throw guiResult.error;
305
+ guiOk = guiResult.status === 0;
306
+ const stderr = (guiResult.stderr || '').trim();
307
+ guiDetail = guiOk ? 'exit 0' : `Exit code: ${guiResult.status}\nError: ${stderr || '(no stderr)'}`;
308
+ if (!guiOk && stderr.includes('No such file or directory')) {
309
+ guiDetail += '\nHint: node is not in the system PATH. GUI apps (Cursor) cannot find it.\n'
310
+ + 'Fix: sudo ln -s $(which node) /usr/local/bin/node';
311
+ }
312
+ } catch (err) {
313
+ guiOk = false;
314
+ guiDetail = `Exit code: N/A\nError: ${err.message}`;
315
+ }
316
+ }
317
+
318
+ toolResults.push({ name, ok: exitOk, cmd: testCmd, exitDetail, captureNote, shellOk, shellCmd: command, shellDetail, shellCaptureNote, guiOk, guiDetail });
285
319
  }
286
320
 
287
- const allOk = toolResults.every(r => r.ok && r.shellOk);
288
- const failedTools = toolResults.filter(r => !r.ok || !r.shellOk).map(r => r.name);
321
+ const allOk = toolResults.every(r => r.ok && r.shellOk && r.guiOk);
322
+ const failedTools = toolResults.filter(r => !r.ok || !r.shellOk || !r.guiOk).map(r => r.name);
289
323
  const summaryParts = toolResults.map(r => {
290
324
  const directStatus = r.ok ? 'OK' : 'FAILED';
291
325
  const shellStatus = r.shellOk ? 'OK' : 'FAILED';
292
326
  const parts = [`${r.name}: ${directStatus}`];
293
327
  if (!r.shellOk) parts[0] += `, shell: ${shellStatus}`;
328
+ if (!r.guiOk) parts[0] += ', GUI PATH: FAILED';
294
329
  if (r.captureNote) parts[0] += ` (${r.captureNote})`;
295
330
  return parts[0];
296
331
  });
@@ -302,6 +337,7 @@ function checkCaptureRun(installedTools) {
302
337
  if (r.captureNote) text += `\n Capture log: ${r.captureNote}`;
303
338
  text += `\n Shell: ${r.shellCmd} < (test event)\n Shell result: ${r.shellDetail}`;
304
339
  if (r.shellCaptureNote) text += `\n Shell capture log: ${r.shellCaptureNote}`;
340
+ if (r.guiDetail) text += `\n GUI PATH test: ${r.guiDetail}`;
305
341
  return text;
306
342
  }).join('\n\n');
307
343
 
package/client/sender.js CHANGED
@@ -670,7 +670,13 @@ async function main() {
670
670
  events.filter(e => !sentEventIds.has(e.event_id)).map(e => e.event_id).filter(Boolean)
671
671
  );
672
672
  partialRollback(sendingDir, acquiredPendingDir, unsentIds, eventFileMap);
673
- log({ msg: 'failed', error: err.message, sent: sentEventIds.size, unsent: unsentIds.size, server: serverUrl });
673
+ // Node.js fetch wraps the real error in err.cause (e.g. DNS, TLS, ECONNREFUSED).
674
+ // Log both so sender.log shows the actual root cause, not just "fetch failed".
675
+ const cause = err.cause;
676
+ const errorDetail = cause
677
+ ? { message: cause.message, code: cause.code, ...(cause.cause ? { inner: cause.cause.message, innerCode: cause.cause.code } : {}) }
678
+ : undefined;
679
+ log({ msg: 'failed', error: err.message, ...(errorDetail ? { cause: errorDetail } : {}), sent: sentEventIds.size, unsent: unsentIds.size, server: serverUrl });
674
680
  if (err.message.includes('401')) {
675
681
  log({ msg: 'auth-failed', error: 'Token invalid or revoked. Run: npx -y ai-lens init' });
676
682
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.47",
3
+ "version": "0.8.49",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {