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 +1 -1
- package/CHANGELOG.md +6 -0
- package/cli/init.js +60 -8
- package/cli/logger.js +16 -7
- package/cli/status.js +32 -5
- package/package.json +1 -1
package/.commithash
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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(
|
|
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`
|
|
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
|
-
|
|
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
|
}
|