ai-lens 0.8.96 → 0.8.97

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
- 549ce90
1
+ 13ab9a5
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.97 — 2026-06-17
6
+ - fix: `ai-lens init` no longer leaves an invalid `{ "version": 1 }` (no hooks) in `~/.cursor/hooks.json` when migrating to project hooks — Cursor flagged such a file as an error and stopped running ALL hooks, silently killing capture even when project hooks were correct. init now deletes the empty file, and repairs machines already left in this state on the next run
7
+
5
8
  ## 0.8.96 — 2026-06-17
6
9
  - fix: `ai-lens status` now reports a Windows Cursor (or Claude Code) hook that lacks the windowless `conhost.exe --headless` wrapper as outdated, so a normal re-run of `ai-lens init` (or `/setup`) upgrades it in place. Previously the console-flash fix from 0.8.95 only applied to brand-new installs; existing hooks were considered up-to-date and never rewritten. macOS/Linux, older Windows, and Codex hooks are unaffected
7
10
 
package/cli/hooks.js CHANGED
@@ -1312,8 +1312,15 @@ export function buildStrippedConfig(tool, existingConfig) {
1312
1312
  // If no hooks remain, clean up
1313
1313
  if (Object.keys(base.hooks).length === 0) {
1314
1314
  delete base.hooks;
1315
- // If other settings remain (shared config like settings.json), keep them
1316
- if (Object.keys(base).length > 0) {
1315
+ // Keep the file only if REAL user settings remain — i.e. keys beyond the tool's
1316
+ // OWN scaffolding (e.g. Cursor's `version: 1`, which we wrote ourselves). Leaving
1317
+ // a Cursor hooks.json as just `{ "version": 1 }` is an invalid hookless config:
1318
+ // Cursor flags it as an error and stops running hooks. In that case return null so
1319
+ // the caller deletes the file. Real settings (Claude settings.json env/statusLine,
1320
+ // not in topLevelFields) are still preserved.
1321
+ const scaffolding = new Set(Object.keys(tool.topLevelFields || {}));
1322
+ const meaningful = Object.keys(base).filter((k) => !scaffolding.has(k));
1323
+ if (meaningful.length > 0) {
1317
1324
  return base;
1318
1325
  }
1319
1326
  return null;
@@ -1800,7 +1807,19 @@ export function cleanupOppositeScope(activeTools) {
1800
1807
  try { config = JSON.parse(raw); } catch { continue; }
1801
1808
 
1802
1809
  const hooks = config.hooks;
1803
- if (!hooks || typeof hooks !== 'object') continue;
1810
+ if (!hooks || typeof hooks !== 'object') {
1811
+ // Repair: an older CLI could strip the opposite-scope hooks but leave behind an
1812
+ // invalid hookless scaffolding file (e.g. Cursor's `{ "version": 1 }`), which the
1813
+ // tool then flags as an error and stops running hooks. If a non-shared opposite
1814
+ // file has no hooks and only our own scaffolding keys, delete it.
1815
+ const scaffold = new Set(Object.keys(global.topLevelFields || {}));
1816
+ const onlyScaffold = Object.keys(config).every((k) => scaffold.has(k));
1817
+ if (!global.sharedConfig && onlyScaffold) {
1818
+ try { unlinkSync(oppositeConfigPath); } catch { /* already gone */ }
1819
+ results.push({ path: oppositeConfigPath, action: 'removed', toolName: oppositeToolName });
1820
+ }
1821
+ continue;
1822
+ }
1804
1823
 
1805
1824
  let hasAiLens = false;
1806
1825
  for (const entries of Object.values(hooks)) {
@@ -1816,8 +1835,18 @@ export function cleanupOppositeScope(activeTools) {
1816
1835
  if (stripped) {
1817
1836
  writeHooksConfig({ configPath: oppositeConfigPath }, stripped);
1818
1837
  results.push({ path: oppositeConfigPath, action: 'cleaned', toolName: oppositeToolName });
1838
+ } else if (global.sharedConfig) {
1839
+ // Shared config (Claude settings.json): drop the hooks but keep the file —
1840
+ // it may carry the user's other settings.
1841
+ writeHooksConfig({ configPath: oppositeConfigPath }, {});
1842
+ results.push({ path: oppositeConfigPath, action: 'cleaned', toolName: oppositeToolName });
1843
+ } else {
1844
+ // Non-shared (Cursor/Codex): the file held only AI Lens hooks plus our own
1845
+ // scaffolding (e.g. Cursor's `version: 1`). Delete it rather than leave an
1846
+ // invalid hookless `{ "version": 1 }` that breaks the tool's hook loading.
1847
+ try { unlinkSync(oppositeConfigPath); } catch { /* already gone */ }
1848
+ results.push({ path: oppositeConfigPath, action: 'removed', toolName: oppositeToolName });
1819
1849
  }
1820
- // Don't delete the file — global settings.json may have other settings
1821
1850
  }
1822
1851
  return results;
1823
1852
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.96",
3
+ "version": "0.8.97",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {