ai-lens 0.8.46 → 0.8.48

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
- 8f21797
1
+ 85bad35
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
@@ -16,8 +16,6 @@ import {
16
16
  } from 'node:fs';
17
17
  import { join, dirname } from 'node:path';
18
18
  import { randomUUID } from 'node:crypto';
19
- import { request as httpsRequest } from 'node:https';
20
- import { request as httpRequest } from 'node:http';
21
19
  import { fileURLToPath } from 'node:url';
22
20
  import {
23
21
  ensureDataDir,
@@ -576,53 +574,32 @@ export function filterOversized(batch, maxBytes = MAX_CHUNK_BYTES) {
576
574
  // HTTP
577
575
  // =============================================================================
578
576
 
579
- function postEvents(serverUrl, events, identity, authToken) {
580
- return new Promise((resolve, reject) => {
581
- const body = JSON.stringify(events);
582
- const url = new URL(`${serverUrl}/api/events`);
583
- const isHttps = url.protocol === 'https:';
584
- const requestFn = isHttps ? httpsRequest : httpRequest;
585
-
586
- const { version: clientVersion, commit: clientCommit } = getClientVersion();
587
- const headers = {
588
- 'Content-Type': 'application/json',
589
- 'Content-Length': Buffer.byteLength(body),
590
- 'X-Client-Version': `${clientVersion}+${clientCommit}`,
591
- };
592
- if (identity.email) headers['X-Developer-Git-Email'] = identity.email;
593
- if (identity.name) headers['X-Developer-Name'] = encodeURIComponent(identity.name);
594
-
595
- if (authToken) {
596
- headers['X-Auth-Token'] = authToken;
597
- }
598
-
599
- const options = {
600
- hostname: url.hostname,
601
- port: url.port || (isHttps ? 443 : 80),
602
- path: url.pathname,
603
- method: 'POST',
604
- headers,
605
- timeout: 30_000,
606
- };
607
-
608
- const req = requestFn(options, (res) => {
609
- let data = '';
610
- res.on('data', (chunk) => { data += chunk; });
611
- res.on('end', () => {
612
- if (res.statusCode >= 200 && res.statusCode < 300) {
613
- try { resolve(JSON.parse(data)); }
614
- catch (e) { reject(new Error(`Invalid JSON response: ${e.message}`)); }
615
- } else {
616
- reject(new Error(`Server responded ${res.statusCode}: ${data}`));
617
- }
618
- });
619
- });
620
-
621
- req.on('error', reject);
622
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
623
- req.write(body);
624
- req.end();
577
+ async function postEvents(serverUrl, events, identity, authToken) {
578
+ const body = JSON.stringify(events);
579
+ const url = `${serverUrl}/api/events`;
580
+
581
+ const { version: clientVersion, commit: clientCommit } = getClientVersion();
582
+ const headers = {
583
+ 'Content-Type': 'application/json',
584
+ 'X-Client-Version': `${clientVersion}+${clientCommit}`,
585
+ };
586
+ if (identity.email) headers['X-Developer-Git-Email'] = identity.email;
587
+ if (identity.name) headers['X-Developer-Name'] = encodeURIComponent(identity.name);
588
+ if (authToken) headers['X-Auth-Token'] = authToken;
589
+
590
+ const res = await fetch(url, {
591
+ method: 'POST',
592
+ headers,
593
+ body,
594
+ signal: AbortSignal.timeout(30_000),
625
595
  });
596
+
597
+ const data = await res.text();
598
+ if (res.ok) {
599
+ try { return JSON.parse(data); }
600
+ catch (e) { throw new Error(`Invalid JSON response: ${e.message}`); }
601
+ }
602
+ throw new Error(`Server responded ${res.status}: ${data}`);
626
603
  }
627
604
 
628
605
  // =============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.46",
3
+ "version": "0.8.48",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {