ai-lens 0.8.92 → 0.8.93

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
- 2196993
1
+ 255d9a3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
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.93 — 2026-06-15
6
+ - fix: on Windows, committed Claude Code project hooks no longer flash a console window that steals focus on every event — the hook command is wrapped in `conhost.exe --headless` (Windows 10 1809+), which runs capture with no visible window while still capturing every event. Older Windows builds and macOS/Linux are unchanged
7
+
5
8
  ## 0.8.92 — 2026-06-11
6
9
  - fix: when global `~/.claude` hooks are active, `ai-lens init` no longer also installs the `.claude/settings.local.json` platform overlay (and removes a previously-written one) — global hooks already capture every project, and registering both made every hook event fire capture twice
7
10
  - improve: `ai-lens status` shows committed other-OS project hooks as healthy ("capture covered by global hooks") when global hooks are doing the work, instead of a false warning
package/cli/hooks.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, lstatSync, readFileSync, writeFileSync, copyFileSync, renameSync, mkdirSync, rmSync, unlinkSync, chmodSync, readdirSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
- import { homedir } from 'node:os';
3
+ import { homedir, release } from 'node:os';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { execFileSync } from 'node:child_process';
6
6
 
@@ -302,6 +302,22 @@ export function writeLauncher({ clientDir = CLIENT_INSTALL_DIR, nodePath, platfo
302
302
  // Hook command construction
303
303
  // ---------------------------------------------------------------------------
304
304
 
305
+ // `conhost.exe --headless` runs a console program with no visible window — the
306
+ // fix for Windows hooks flashing/stealing focus on every event (Claude Code spawns
307
+ // the hook process without CREATE_NO_WINDOW; upstream anthropics/claude-code#61051).
308
+ // Unlike wscript/`-WindowStyle Hidden`, it keeps stdin wired, so capture.js still
309
+ // receives the hook payload. `--headless` requires the modern conhost shipped in
310
+ // Windows 10 1809 (build 17763); on older builds the flag errors, so we gate on it
311
+ // and fall back to the bare-node form (still flashes, but captures).
312
+ const WIN_HEADLESS_MIN_BUILD = 17763;
313
+
314
+ export function supportsConhostHeadless(osRelease = release()) {
315
+ // os.release() on Windows looks like '10.0.22631'. Build = the third segment.
316
+ const m = /^\d+\.\d+\.(\d+)/.exec(String(osRelease || ''));
317
+ if (!m) return false;
318
+ return Number(m[1]) >= WIN_HEADLESS_MIN_BUILD;
319
+ }
320
+
305
321
  // Lazy default ctx for callers that don't pass one (legacy tests, TOOL_CONFIGS at
306
322
  // import-time). Resolver isn't called at module load — only when captureCommand runs.
307
323
  function resolveDefaultCtx() {
@@ -310,6 +326,7 @@ function resolveDefaultCtx() {
310
326
  nodeResolution,
311
327
  platform: process.platform,
312
328
  clientDir: CLIENT_INSTALL_DIR,
329
+ conhost: process.platform === 'win32' && supportsConhostHeadless(),
313
330
  };
314
331
  }
315
332
 
@@ -340,6 +357,9 @@ export function captureCommand(opts = {}) {
340
357
  const clientDir = ctx.clientDir ?? CLIENT_INSTALL_DIR;
341
358
  const nodeResolution = ctx.nodeResolution;
342
359
  const isWin = platform === 'win32';
360
+ // Windows-only windowless wrapper (see supportsConhostHeadless). Resolved per-ctx
361
+ // so a build < 1809 falls back to the bare form. Never applied off Windows.
362
+ const conhost = isWin && (ctx.conhost ?? supportsConhostHeadless());
343
363
  // shell hint distinguishes cmd.exe (Claude Code) from PowerShell (Cursor) on
344
364
  // Windows — the two need different escaping for paths with spaces.
345
365
  const isPS = shell === 'powershell';
@@ -354,6 +374,13 @@ export function captureCommand(opts = {}) {
354
374
  // POSIX shells use $VAR.
355
375
  if (projectDirRelPath != null) {
356
376
  const rel = projectDirRelPath.replace(/\\/g, '/').replace(/^\.?\/+/, '');
377
+ if (conhost) {
378
+ // Windowless form. conhost is the launched image (no cmd.exe shell), so the
379
+ // path must use $CLAUDE_PROJECT_DIR — Claude Code substitutes that variable
380
+ // itself before exec on every OS (the %VAR% form would survive unexpanded
381
+ // since there's no shell to expand it). Verified on Windows 11 / cc 2.1.177.
382
+ return `conhost.exe --headless node "$CLAUDE_PROJECT_DIR/${rel}"`;
383
+ }
357
384
  const dir = isWin ? '%CLAUDE_PROJECT_DIR%' : '$CLAUDE_PROJECT_DIR';
358
385
  return `node "${dir}/${rel}"`;
359
386
  }
@@ -945,10 +972,21 @@ function isAcceptableHookCommand(cmd) {
945
972
  export function isWrongPlatformProjectDirCommand(cmd, platform = process.platform) {
946
973
  if (!isClaudeProjectDirCommand(cmd)) return false;
947
974
  const n = (cmd || '').replace(/\\/g, '/');
975
+ // The conhost windowless form is unambiguously a Windows command (conhost.exe is
976
+ // Windows-only) and deliberately uses $CLAUDE_PROJECT_DIR — Claude Code substitutes
977
+ // that itself, so it expands on Windows too. It is therefore correct on win32 and
978
+ // wrong (won't run) anywhere else; the %VAR% rule below doesn't apply to it.
979
+ if (isConhostHeadlessCommand(n)) return platform !== 'win32';
948
980
  const correctVar = platform === 'win32' ? '%CLAUDE_PROJECT_DIR%' : '$CLAUDE_PROJECT_DIR';
949
981
  return !n.includes(correctVar);
950
982
  }
951
983
 
984
+ // `conhost.exe --headless …` windowless wrapper (Windows). Tolerates an optional
985
+ // `.exe` and arbitrary spacing between the two tokens.
986
+ export function isConhostHeadlessCommand(cmd) {
987
+ return /(^|[\\/\s])conhost(\.exe)?\s+--headless\b/i.test(String(cmd || '').replace(/\\/g, '/'));
988
+ }
989
+
952
990
  // Whether a hook-config file is committed (tracked) in git. The anti-churn rule
953
991
  // (treat both $ and % CLAUDE_PROJECT_DIR forms as current) exists ONLY to keep a
954
992
  // COMMITTED cross-platform hook file from being flipped to one OS's syntax and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.92",
3
+ "version": "0.8.93",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {