ai-lens 0.8.68 → 0.8.69
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 +1 -1
- package/CHANGELOG.md +4 -0
- package/cli/hooks.js +28 -4
- package/cli/init.js +33 -7
- package/package.json +1 -1
package/.commithash
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
fe949b0
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
History of changes to the `ai-lens` CLI package on npm. New entries go on top. Format: `## X.Y.Z — YYYY-MM-DD`, followed by user-facing bullets.
|
|
4
4
|
|
|
5
|
+
## 0.8.69 — 2026-05-27
|
|
6
|
+
- feat: per-machine launcher (`~/.ai-lens/client/run.sh` / `run.cmd`) now also accepts an optional script path as its first argument. With no args it still execs the sibling `capture.js` (default install), but `~/.ai-lens/client/run.sh path/to/some/capture.js` execs that script with the launcher's resolved node — letting bootstrap-style workflows (e.g. meta-cursor) route through the launcher for proper node resolution while keeping `capture.js` under git in the workspace repo.
|
|
7
|
+
- feat: new `--install-launcher` flag for `init`. Forces launcher installation even when `--no-hooks` is set, so the meta-cursor setup skill can wire up `~/.ai-lens/client/run.sh` without touching the static hook templates in the workspace repo.
|
|
8
|
+
|
|
5
9
|
## 0.8.68 — 2026-05-27
|
|
6
10
|
- fix: hooks now resolve node from nvm, asdf, fnm, volta and n in addition to Homebrew. Previously `init` fell back to `/usr/bin/env node`, which made GUI Cursor on macOS silently lose every event because launchd's minimal PATH doesn't include node. Affected users with custom node installs (e.g. `/Users/<you>/node/bin/node`) saw hundreds of dropped events with no visible error.
|
|
7
11
|
- feat: `init` now installs a per-machine launcher (`~/.ai-lens/client/run.sh` on macOS/Linux, `run.cmd` on Windows) with the resolved node path baked in. Hook commands invoke the launcher directly, so they no longer depend on the GUI app's PATH and survive `brew upgrade node`.
|
package/cli/hooks.js
CHANGED
|
@@ -242,8 +242,20 @@ export function launcherFilename(platform = process.platform) {
|
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
244
|
* Write a per-machine launcher script with the resolved node path baked in.
|
|
245
|
-
*
|
|
246
|
-
*
|
|
245
|
+
*
|
|
246
|
+
* The launcher acts as a node-resolution shim that supports two invocation modes:
|
|
247
|
+
*
|
|
248
|
+
* 1. No arguments — run the sibling capture.js (the default install flow where
|
|
249
|
+
* both the launcher and capture.js live in ~/.ai-lens/client/).
|
|
250
|
+
* 2. Script path as $1 — run that script with the resolved node. This is the
|
|
251
|
+
* repo-path mode (e.g. meta-cursor static hooks pointing at
|
|
252
|
+
* `internal/analytics/ai-lens/client/capture.js`) where capture.js auto-
|
|
253
|
+
* updates via `git pull` and only the node binary needs per-machine baking.
|
|
254
|
+
*
|
|
255
|
+
* In both cases the launcher is independent of HOME / USERPROFILE / cwd — for
|
|
256
|
+
* mode 1 the path is derived from `dirname $0`; for mode 2 it's whatever the
|
|
257
|
+
* caller passes through (typically a workspace-relative path that resolves
|
|
258
|
+
* against the cwd Cursor/Claude Code set when firing the hook).
|
|
247
259
|
*
|
|
248
260
|
* @param {object} opts
|
|
249
261
|
* @param {string} opts.clientDir — directory where the launcher (and capture.js) live
|
|
@@ -256,7 +268,13 @@ export function writeLauncher({ clientDir = CLIENT_INSTALL_DIR, nodePath, platfo
|
|
|
256
268
|
|
|
257
269
|
if (platform === 'win32') {
|
|
258
270
|
const escaped = nodePath.replace(/"/g, '""');
|
|
259
|
-
const content =
|
|
271
|
+
const content =
|
|
272
|
+
'@echo off\r\n'
|
|
273
|
+
+ 'if "%~1"=="" goto default\r\n'
|
|
274
|
+
+ `"${escaped}" %*\r\n`
|
|
275
|
+
+ 'goto :eof\r\n'
|
|
276
|
+
+ ':default\r\n'
|
|
277
|
+
+ `"${escaped}" "%~dp0capture.js"\r\n`;
|
|
260
278
|
const target = join(clientDir, 'run.cmd');
|
|
261
279
|
writeFileSync(target, content);
|
|
262
280
|
return target;
|
|
@@ -265,8 +283,14 @@ export function writeLauncher({ clientDir = CLIENT_INSTALL_DIR, nodePath, platfo
|
|
|
265
283
|
const escaped = shellEscape(nodePath, 'linux'); // POSIX single-quote escape
|
|
266
284
|
const content =
|
|
267
285
|
'#!/bin/sh\n'
|
|
286
|
+
+ `# AI Lens per-machine launcher. Two modes:\n`
|
|
287
|
+
+ `# $0 → exec node on sibling capture.js (default install)\n`
|
|
288
|
+
+ `# $0 <script> → exec node on <script> with remaining args (repo-path mode)\n`
|
|
289
|
+
+ 'if [ $# -gt 0 ]; then\n'
|
|
290
|
+
+ ` exec ${escaped} "$@"\n`
|
|
291
|
+
+ 'fi\n'
|
|
268
292
|
+ 'DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)\n'
|
|
269
|
-
+ `exec ${escaped} "$DIR/capture.js"
|
|
293
|
+
+ `exec ${escaped} "$DIR/capture.js"\n`;
|
|
270
294
|
const target = join(clientDir, 'run.sh');
|
|
271
295
|
writeFileSync(target, content);
|
|
272
296
|
try { chmodSync(target, 0o755); } catch { /* best effort */ }
|
package/cli/init.js
CHANGED
|
@@ -20,8 +20,9 @@ import {
|
|
|
20
20
|
getClaudeCodeHookDefsWithPath, getCursorHookDefsWithPath,
|
|
21
21
|
cleanupLegacyHooks, cleanupOppositeScope, cleanupEmptyMcpJson, addCursorMcp, removeCursorMcp,
|
|
22
22
|
checkHooksDisabled, enableHooks,
|
|
23
|
-
findStableNodePath, isVersionPinnedNodePath,
|
|
23
|
+
findStableNodePath, isVersionPinnedNodePath, writeLauncher,
|
|
24
24
|
} from './hooks.js';
|
|
25
|
+
import { mkdirSync } from 'node:fs';
|
|
25
26
|
import { scanNestedClaudeProjects, summarizeNestedProjects } from './scan.js';
|
|
26
27
|
|
|
27
28
|
function ask(question) {
|
|
@@ -351,6 +352,9 @@ function getInitArgs() {
|
|
|
351
352
|
case '--use-repo-path':
|
|
352
353
|
flags.useRepoPath = true;
|
|
353
354
|
break;
|
|
355
|
+
case '--install-launcher':
|
|
356
|
+
flags.installLauncher = true;
|
|
357
|
+
break;
|
|
354
358
|
case '--mcp-scope':
|
|
355
359
|
if (i + 1 < args.length) flags.mcpScope = args[++i];
|
|
356
360
|
else process.stderr.write('Warning: --mcp-scope requires a value\n');
|
|
@@ -359,7 +363,7 @@ function getInitArgs() {
|
|
|
359
363
|
const a = args[i];
|
|
360
364
|
if (a.startsWith('-')) {
|
|
361
365
|
process.stderr.write(`Warning: unknown flag "${a}" — did you mean --${a.replace(/^-+/, '')}?\n`);
|
|
362
|
-
} else if (['server', 'projects', 'yes', 'no-mcp', 'no-hooks', 'project-hooks', 'use-repo-path', 'mcp-scope'].includes(a)) {
|
|
366
|
+
} else if (['server', 'projects', 'yes', 'no-mcp', 'no-hooks', 'project-hooks', 'use-repo-path', 'install-launcher', 'mcp-scope'].includes(a)) {
|
|
363
367
|
process.stderr.write(`Warning: unexpected argument "${a}" — did you mean --${a}?\n`);
|
|
364
368
|
}
|
|
365
369
|
}
|
|
@@ -387,11 +391,17 @@ export default async function init() {
|
|
|
387
391
|
//
|
|
388
392
|
// --project-hooks ALWAYS writes hooks regardless of whether ~/.cursor or
|
|
389
393
|
// ~/.claude exist globally — so willWriteHooks must consider it.
|
|
394
|
+
//
|
|
395
|
+
// --install-launcher (added 0.8.69) forces launcher installation even when
|
|
396
|
+
// --no-hooks. Used by bootstrap workflows like meta-cursor where static hook
|
|
397
|
+
// templates point at ~/.ai-lens/client/run.sh but init must NOT overwrite
|
|
398
|
+
// those templates with the dynamic hook form.
|
|
390
399
|
const globalTools = detectInstalledTools();
|
|
391
400
|
const willWriteHooks = !flags.noHooks && (globalTools.length > 0 || !!flags.projectHooks);
|
|
401
|
+
const willInstallLauncher = willWriteHooks || !!flags.installLauncher;
|
|
392
402
|
let nodeResolution = null;
|
|
393
403
|
let ctx = null;
|
|
394
|
-
if (
|
|
404
|
+
if (willInstallLauncher) {
|
|
395
405
|
nodeResolution = findStableNodePath();
|
|
396
406
|
if (!nodeResolution) {
|
|
397
407
|
error('Could not find any node binary on this system.');
|
|
@@ -406,7 +416,9 @@ export default async function init() {
|
|
|
406
416
|
} else {
|
|
407
417
|
detail(` Resolved node: ${nodeResolution.path} (${nodeResolution.source})`);
|
|
408
418
|
}
|
|
409
|
-
|
|
419
|
+
if (willWriteHooks) {
|
|
420
|
+
ctx = { nodeResolution, platform: process.platform, clientDir: join(homedir(), '.ai-lens', 'client') };
|
|
421
|
+
}
|
|
410
422
|
}
|
|
411
423
|
|
|
412
424
|
// Detect installed tools — re-detect with ctx now that it's available.
|
|
@@ -545,9 +557,8 @@ export default async function init() {
|
|
|
545
557
|
// Build new config in memory — saved after "Proceed?" confirmation
|
|
546
558
|
const newConfig = { ...currentConfig, serverUrl, projects };
|
|
547
559
|
|
|
548
|
-
// Install client files to ~/.ai-lens/client/ (skip when --use-repo-path
|
|
549
|
-
//
|
|
550
|
-
// — --no-hooks installs leave it out (and don't require a resolved node path).
|
|
560
|
+
// Install client files to ~/.ai-lens/client/ (skip when --use-repo-path because
|
|
561
|
+
// capture.js comes from the monorepo / package source in that mode).
|
|
551
562
|
if (!flags.useRepoPath) {
|
|
552
563
|
heading('Installing client files...');
|
|
553
564
|
try {
|
|
@@ -564,6 +575,21 @@ export default async function init() {
|
|
|
564
575
|
error(` Failed to install client files: ${err.message}`);
|
|
565
576
|
return;
|
|
566
577
|
}
|
|
578
|
+
} else if (willInstallLauncher) {
|
|
579
|
+
// --use-repo-path with --install-launcher: keep capture.js out of the install
|
|
580
|
+
// dir (it lives in the monorepo, auto-updates via git pull) but still create
|
|
581
|
+
// the per-machine launcher so static hooks can route through it for proper
|
|
582
|
+
// node resolution. The meta-cursor bootstrap is the primary consumer.
|
|
583
|
+
heading('Installing launcher (--install-launcher) ...');
|
|
584
|
+
try {
|
|
585
|
+
const clientDir = join(homedir(), '.ai-lens', 'client');
|
|
586
|
+
mkdirSync(clientDir, { recursive: true });
|
|
587
|
+
writeLauncher({ clientDir, nodePath: nodeResolution.path, platform: process.platform });
|
|
588
|
+
success(' Wrote per-machine launcher to ~/.ai-lens/client/');
|
|
589
|
+
} catch (err) {
|
|
590
|
+
error(` Failed to write launcher: ${err.message}`);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
567
593
|
} else {
|
|
568
594
|
detail(' Skipping client install (--use-repo-path: using package copy).');
|
|
569
595
|
}
|