ai-lens 0.8.108 → 0.8.110

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
- 3b3e261
1
+ a16390a
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
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.110 — 2026-06-23
6
+ - feat: `init` now sends a status report to the server right after it finishes, so the server has your current config (including which `projects` are tracked) and confirmation that hooks fire — no need to run `ai-lens status` by hand. The diagnostic runs quietly: just one summary line on screen.
7
+
8
+ ## 0.8.109 — 2026-06-22
9
+ - fix: `init --yes` no longer captures every project or silently imports your local Claude history. It now scopes capture to the git root of the current folder (still `--projects all` for everything), and imports past history only when you pass `--import`.
10
+
5
11
  ## 0.8.108 — 2026-06-22
6
12
  - feat: `ai-lens init --mcp-only` registers just the MCP server in Claude Code and Cursor — for when you only need to read sessions from Claude, not capture hooks. It skips hook setup, authentication, and history import. The MCP scope now mirrors how AI Lens is installed: a local/project install registers the MCP at project scope, a global install at user scope (local wins if both are present); pass `--mcp-scope` to override.
7
13
  - change: `init --project-hooks` now also registers the MCP at project scope (Claude `local` + the project `.cursor/mcp.json`) to match where it writes hooks, instead of always registering globally. Pass `--mcp-scope` to override.
package/cli/init.js CHANGED
@@ -35,6 +35,37 @@ function ask(question) {
35
35
  });
36
36
  }
37
37
 
38
+ // Default `projects` scope for `init --yes` on a FRESH config: the git root of
39
+ // cwd (the dev's work tree — the workspace when init is run there). Returns null
40
+ // when cwd isn't inside a git repo (e.g. bot containers under /app) → capture-all,
41
+ // which stays safe because the 90-day history import is separately gated to
42
+ // opt-in (importMode).
43
+ //
44
+ // Uses the INNERMOST git root (`git rev-parse --show-toplevel`), NOT an outermost
45
+ // walk-up: a `~/.git` dotfiles repo must never escalate the scope to $HOME (that
46
+ // would re-introduce capture-all + a personal-history import). Without this,
47
+ // `init --yes` left projects=null (= capture ALL).
48
+ export function defaultProjectsScope(cwd = process.cwd()) {
49
+ try {
50
+ const root = execSync('git rev-parse --show-toplevel', {
51
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
52
+ }).trim();
53
+ return root || null;
54
+ } catch {
55
+ return null; // not a git repo (or git unavailable) → keep capture-all
56
+ }
57
+ }
58
+
59
+ // History-import decision for `init`. Opt-in by design: `--import` runs it,
60
+ // interactive prompts, and `--yes` SKIPS (it used to auto-run, which silently
61
+ // imported the full 90-day ~/.claude history including personal projects).
62
+ // `--no-import` is handled earlier (skips outright before the preview).
63
+ export function importMode(flags = {}) {
64
+ if (flags.importHistory) return 'run'; // explicit opt-in (highest priority)
65
+ if (flags.yes) return 'skip'; // non-interactive: never auto-import
66
+ return 'prompt'; // interactive: ask
67
+ }
68
+
38
69
  function getJson(url) {
39
70
  return new Promise((resolve, reject) => {
40
71
  const parsed = new URL(url);
@@ -743,7 +774,11 @@ export default async function init() {
743
774
  if (flags.projects) {
744
775
  projects = flags.projects;
745
776
  } else if (auto) {
746
- projects = currentProjects || projectHooksDefault;
777
+ // Fresh config under --yes: default to the git root of cwd (the work tree),
778
+ // not null. null = capture ALL projects. Stays null only when cwd isn't a
779
+ // git repo (bots/containers) — safe because the import is separately gated.
780
+ // `--projects all` is the explicit opt-in for capture-everything.
781
+ projects = currentProjects || projectHooksDefault || defaultProjectsScope();
747
782
  } else {
748
783
  const projectsDefault = currentProjects || projectHooksDefault || 'all';
749
784
  const projectsInput = await ask(
@@ -1401,12 +1436,26 @@ export default async function init() {
1401
1436
  // so the dashboard isn't empty on first open.
1402
1437
  await maybeOfferImportHistory(flags);
1403
1438
 
1439
+ // Push a full status snapshot to the server right after every init — so it always has
1440
+ // this machine's FRESH config (crucially the `projects` filter, which the ingest path
1441
+ // never carries) plus post-install health (hooks actually fire). Runs quietly: the full
1442
+ // diagnostic is muted on screen (only one summary line) but still POSTed and written to
1443
+ // init.log. Best-effort: a token is required (the report needs auth), and any failure is
1444
+ // swallowed so it can never block or fail init.
1445
+ if (finalConfig.authToken && finalConfig.serverUrl) {
1446
+ try {
1447
+ const { default: status } = await import('./status.js');
1448
+ await status({ report: true, quiet: true });
1449
+ } catch { /* best-effort — never break init on a status/report failure */ }
1450
+ }
1451
+
1404
1452
  detail(`Log: ${getLogPath()}`);
1405
1453
  }
1406
1454
 
1407
1455
  /**
1408
1456
  * After a successful init, offer to import the developer's local Claude Code
1409
- * history. `--no-import` skips; `--import` or `--yes` runs it without prompting;
1457
+ * history. `--no-import` skips; `--import` runs it without prompting; `--yes`
1458
+ * does NOT import (opt-in only — avoids silently pulling personal history);
1410
1459
  * otherwise asks interactively. No-op if there's no `~/.claude/projects`.
1411
1460
  */
1412
1461
  async function maybeOfferImportHistory(flags) {
@@ -1429,18 +1478,21 @@ async function maybeOfferImportHistory(flags) {
1429
1478
  ? `Found ${preview.count} Claude Code session${preview.count === 1 ? '' : 's'} from ${day(preview.earliest)} to ${day(preview.latest)} (last 90d).`
1430
1479
  : 'Local Claude Code history found.';
1431
1480
 
1432
- let run = flags.importHistory || flags.yes;
1433
- if (run) {
1434
- info(` ${previewLine}`);
1435
- } else {
1481
+ info(` ${previewLine}`);
1482
+ // History import is opt-IN (importMode): `--import` runs, interactive asks,
1483
+ // `--yes` SKIPS. `--yes` used to auto-import (`|| flags.yes`), silently pulling
1484
+ // the full 90-day ~/.claude history (incl. personal projects) on a fresh config.
1485
+ const mode = importMode(flags);
1486
+ let run = mode === 'run';
1487
+ if (mode === 'prompt') {
1436
1488
  try {
1437
- const answer = (await ask(`${previewLine} Import now? (Y/n) `)).toLowerCase();
1489
+ const answer = (await ask(' Import now? (Y/n) ')).toLowerCase();
1438
1490
  run = answer === '' || answer === 'y' || answer === 'yes';
1439
1491
  } catch { run = false; }
1440
1492
  }
1441
1493
  if (!run) {
1442
1494
  blank();
1443
- info(' Skipped import. Run `npx -y ai-lens import claude-code` anytime to bring it in.');
1495
+ info(' Skipped local history import. Run `npx -y ai-lens import claude-code` (or pass `--import`) to bring it in.');
1444
1496
  return;
1445
1497
  }
1446
1498
 
package/cli/logger.js CHANGED
@@ -7,6 +7,14 @@ const LOG_FILE = join(DATA_DIR, 'init.log');
7
7
 
8
8
  let logFile = null;
9
9
 
10
+ // Screen-quiet mode: suppress stdout (console.log) while STILL writing to init.log.
11
+ // Used by init's end-of-run auto status report — the full diagnostic snapshot is sent
12
+ // to the server and logged to ~/.ai-lens/init.log, but the screen shows only the single
13
+ // summary line the caller prints (after flipping this back off). Toggled in a finally so
14
+ // a throw never leaves the terminal muted.
15
+ let screenQuiet = false;
16
+ export function setScreenQuiet(v) { screenQuiet = Boolean(v); }
17
+
10
18
  export function initLogger(versionStr) {
11
19
  mkdirSync(DATA_DIR, { recursive: true });
12
20
  logFile = LOG_FILE;
@@ -53,6 +61,7 @@ let progressLastPaint = 0;
53
61
 
54
62
  /** Show/update a transient one-line progress indicator (TTY only, throttled). */
55
63
  export function progress(text) {
64
+ if (screenQuiet) return;
56
65
  if (!useColor) return; // useColor === stdout.isTTY
57
66
  const now = Date.now();
58
67
  if (progressActive && now - progressLastPaint < PROGRESS_MIN_REPAINT_MS) return;
@@ -71,43 +80,43 @@ export function progressDone() {
71
80
 
72
81
  export function info(msg) {
73
82
  progressDone();
74
- console.log(msg);
83
+ if (!screenQuiet) console.log(msg);
75
84
  write('INFO', msg);
76
85
  }
77
86
 
78
87
  export function success(msg) {
79
88
  progressDone();
80
- console.log(`${GREEN}${msg}${RESET}`);
89
+ if (!screenQuiet) console.log(`${GREEN}${msg}${RESET}`);
81
90
  write('INFO', msg);
82
91
  }
83
92
 
84
93
  export function warn(msg) {
85
94
  progressDone();
86
- console.log(`${YELLOW}${msg}${RESET}`);
95
+ if (!screenQuiet) console.log(`${YELLOW}${msg}${RESET}`);
87
96
  write('WARN', msg);
88
97
  }
89
98
 
90
99
  export function error(msg) {
91
100
  progressDone();
92
- console.log(`${RED}${msg}${RESET}`);
101
+ if (!screenQuiet) console.log(`${RED}${msg}${RESET}`);
93
102
  write('ERROR', msg);
94
103
  }
95
104
 
96
105
  export function heading(msg) {
97
106
  progressDone();
98
- console.log(`\n${BOLD}${CYAN}${msg}${RESET}`);
107
+ if (!screenQuiet) console.log(`\n${BOLD}${CYAN}${msg}${RESET}`);
99
108
  write('INFO', msg);
100
109
  }
101
110
 
102
111
  export function detail(msg) {
103
112
  progressDone();
104
- console.log(`${DIM} ${msg}${RESET}`);
113
+ if (!screenQuiet) console.log(`${DIM} ${msg}${RESET}`);
105
114
  write('INFO', msg);
106
115
  }
107
116
 
108
117
  export function blank() {
109
118
  progressDone();
110
- console.log();
119
+ if (!screenQuiet) console.log();
111
120
  }
112
121
 
113
122
  export function getLogPath() {
package/cli/status.js CHANGED
@@ -10,7 +10,7 @@ import { TLS_TRUST_CODES, tlsCodeOf, tlsVerdictSummary, issuerName } from '../cl
10
10
  import { getVersionInfo, readLensConfig, detectInstalledTools, getCursorToolConfig, getClaudeCodeToolConfig, getCodexToolConfig, analyzeToolHooks, checkHooksDisabled, verifyCodexHookTrust, CAPTURE_PATH, TOOL_CONFIGS, isClaudeProjectDirCommand, analyzeClaudeLocalOverlay, extractProjectDirRelPath, globalClaudeHooksActive, CONHOST_HEADLESS_PREFIX_RE } from './hooks.js';
11
11
  import { DATA_DIR, PENDING_DIR, SENDING_DIR, SESSION_PATHS_DIR, LOG_PATH, CAPTURE_LOG_PATH, LAST_STATUS_REPORT_PATH, getGitIdentity, getMonitoredProjects } from '../client/config.js';
12
12
  import { isLockStale } from '../client/sender.js';
13
- import { initLogger, info, success, warn, error, heading, blank } from './logger.js';
13
+ import { initLogger, info, success, warn, error, heading, blank, detail, setScreenQuiet } from './logger.js';
14
14
  import { scanNestedProjects, summarizeNestedProjects } from './scan.js';
15
15
 
16
16
  const INIT_LOG_PATH = join(DATA_DIR, 'init.log');
@@ -1414,7 +1414,7 @@ function collectHookConfigs(allTools) {
1414
1414
  }
1415
1415
 
1416
1416
  async function sendStatusReport(results, warnings, clientVersion, clientCommit, serverUrl, authToken, allTools = TOOL_CONFIGS) {
1417
- if (!serverUrl || !authToken) return;
1417
+ if (!serverUrl || !authToken) return { ok: false, reason: 'no-auth' };
1418
1418
 
1419
1419
  const payload = {
1420
1420
  timestamp: new Date().toISOString(),
@@ -1446,8 +1446,10 @@ async function sendStatusReport(results, warnings, clientVersion, clientCommit,
1446
1446
  if (res.ok) {
1447
1447
  try { writeFileSync(LAST_STATUS_REPORT_PATH, new Date().toISOString()); } catch {}
1448
1448
  }
1449
- } catch {
1449
+ return { ok: res.ok, status: res.status };
1450
+ } catch (err) {
1450
1451
  // Silent — report is best-effort
1452
+ return { ok: false, reason: err.message };
1451
1453
  }
1452
1454
  }
1453
1455
 
@@ -1455,7 +1457,15 @@ async function sendStatusReport(results, warnings, clientVersion, clientCommit,
1455
1457
  // Main
1456
1458
  // ---------------------------------------------------------------------------
1457
1459
 
1458
- export default async function status({ report = false } = {}) {
1460
+ export default async function status({ report = false, quiet = false } = {}) {
1461
+ // quiet: run the full diagnostic + report, but suppress the on-screen dump — the
1462
+ // caller (init) prints a single summary line. The full snapshot still ships to the
1463
+ // server and is written to ~/.ai-lens/init.log. setScreenQuiet is flipped back in a
1464
+ // finally so a throw never leaves the terminal muted.
1465
+ if (quiet) setScreenQuiet(true);
1466
+ let reportOutcome = null;
1467
+ let failedChecks = 0;
1468
+ try {
1459
1469
  const versionResult = checkVersion();
1460
1470
  // Print to screen in both modes. --report additionally POSTs to the server
1461
1471
  // (and skips the local text-file write + "Full report → path" line at the end).
@@ -1672,7 +1682,7 @@ export default async function status({ report = false } = {}) {
1672
1682
  if (report) {
1673
1683
  // --report mode: same on-screen output as normal status, but POST the
1674
1684
  // structured JSON to the server instead of writing the local text file.
1675
- await sendStatusReport(results, warnings, version, commit, serverUrl, authToken, allToolsForReport);
1685
+ reportOutcome = await sendStatusReport(results, warnings, version, commit, serverUrl, authToken, allToolsForReport);
1676
1686
  blank();
1677
1687
  } else {
1678
1688
  // Normal mode: write text report file
@@ -1688,4 +1698,21 @@ export default async function status({ report = false } = {}) {
1688
1698
  }
1689
1699
  blank();
1690
1700
  }
1701
+ failedChecks = results.filter(r => r && r.ok === false).length;
1702
+ } finally {
1703
+ if (quiet) setScreenQuiet(false);
1704
+ }
1705
+
1706
+ // Quiet mode (init's end-of-run auto report): the full diagnostic above was muted —
1707
+ // print exactly one summary line now that the screen is un-muted.
1708
+ if (quiet) {
1709
+ if (reportOutcome?.ok) {
1710
+ const tail = failedChecks > 0
1711
+ ? ` (${failedChecks} проверок с замечаниями — запусти ai-lens status)`
1712
+ : '';
1713
+ success(` 📡 Диагностика и конфиг отправлены на сервер${tail}`);
1714
+ } else {
1715
+ detail('📡 Не удалось отправить диагностику на сервер (не критично)');
1716
+ }
1717
+ }
1691
1718
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.108",
3
+ "version": "0.8.110",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {