@cachly-dev/mcp-server 0.10.117 → 0.10.118
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/README.md +2 -2
- package/dist/src/ambient-cli.d.ts +85 -0
- package/dist/src/ambient-cli.js +199 -0
- package/dist/src/ambient-cli.js.map +1 -0
- package/dist/src/ambient-hooks.d.ts +128 -0
- package/dist/src/ambient-hooks.js +269 -0
- package/dist/src/ambient-hooks.js.map +1 -0
- package/dist/src/ambient-ledger.d.ts +19 -0
- package/dist/src/ambient-ledger.js +95 -0
- package/dist/src/ambient-ledger.js.map +1 -0
- package/dist/src/ambient-recall.d.ts +59 -0
- package/dist/src/ambient-recall.js +96 -0
- package/dist/src/ambient-recall.js.map +1 -0
- package/dist/src/doctor.d.ts +47 -0
- package/dist/src/doctor.js +247 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/first-contact.d.ts +56 -0
- package/dist/src/first-contact.js +108 -0
- package/dist/src/first-contact.js.map +1 -0
- package/dist/src/handlers/brain.d.ts +1 -0
- package/dist/src/handlers/brain.js +37 -7
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/fedbrain.js +48 -16
- package/dist/src/handlers/fedbrain.js.map +1 -1
- package/dist/src/handlers/syndicate.js +91 -1
- package/dist/src/handlers/syndicate.js.map +1 -1
- package/dist/src/handlers/team.d.ts +10 -0
- package/dist/src/handlers/team.js +45 -1
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/index.d.ts +8 -5
- package/dist/src/index.js +211 -14
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +5 -0
- package/dist/src/tools.js +12 -1
- package/dist/src/tools.js.map +1 -1
- package/glama.json +1 -1
- package/package.json +2 -2
- package/server.json +4 -4
package/dist/src/index.js
CHANGED
|
@@ -694,6 +694,10 @@ import { handleAdvancedTool } from './handlers/advanced.js';
|
|
|
694
694
|
import { handleSyndicateTool } from './handlers/syndicate.js';
|
|
695
695
|
import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
|
|
696
696
|
import { installClsPostCommitHook, CLS_HOOK_VERSION } from './cls-hook.js';
|
|
697
|
+
import { installAmbientHooks, AMBIENT_HOOK_VERSION } from './ambient-hooks.js';
|
|
698
|
+
import { runAmbient, truncateToTokens, parseHookPayload, stopObservation } from './ambient-cli.js';
|
|
699
|
+
import { appendLedgerEntry, readLedger, defaultLedgerPath } from './ambient-ledger.js';
|
|
700
|
+
import { netBalance, shouldBackoff } from './ambient-recall.js';
|
|
697
701
|
import { handleShareTool } from './handlers/share.js';
|
|
698
702
|
import { handleVizTool } from './handlers/viz.js';
|
|
699
703
|
// ── Tools (imported from tools.ts) ─────────────────────────────────────────
|
|
@@ -748,6 +752,21 @@ function sendFunnelEvent(event, extra, metrics) {
|
|
|
748
752
|
signal: AbortSignal.timeout(3000),
|
|
749
753
|
}).catch(() => { });
|
|
750
754
|
}
|
|
755
|
+
// Mirrors one ambient net-token ledger entry to the API (Phase 4 v2: the
|
|
756
|
+
// org-wide dashboard aggregates these per team). Best-effort fire-and-forget —
|
|
757
|
+
// the local JSONL ledger stays authoritative; this never throws and never
|
|
758
|
+
// blocks a hook. Returns the fetch promise so short-lived CLI paths can flush
|
|
759
|
+
// (bounded) before process.exit kills the socket.
|
|
760
|
+
function reportAmbientLedgerEvent(instanceId, entry) {
|
|
761
|
+
if (!instanceId || !JWT || process.env.CACHLY_NO_TELEMETRY === '1')
|
|
762
|
+
return Promise.resolve();
|
|
763
|
+
return fetch(`${API_URL}/api/v1/instances/${instanceId}/ambient-events`, {
|
|
764
|
+
method: 'POST',
|
|
765
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${JWT}` },
|
|
766
|
+
body: JSON.stringify({ events: [entry] }),
|
|
767
|
+
signal: AbortSignal.timeout(3000),
|
|
768
|
+
}).then(() => undefined, () => undefined);
|
|
769
|
+
}
|
|
751
770
|
async function sendAnonymousTelemetry(toolName) {
|
|
752
771
|
if (_telemetryPingSent)
|
|
753
772
|
return;
|
|
@@ -1178,7 +1197,19 @@ async function handleTool(name, args) {
|
|
|
1178
1197
|
return `⚠️ Unknown tool: ${name}`;
|
|
1179
1198
|
}
|
|
1180
1199
|
}
|
|
1181
|
-
|
|
1200
|
+
// Pushed to every MCP host at connect time (initialize response). This is the
|
|
1201
|
+
// "SessionStart-equivalent" for harnesses without a per-prompt hook — Cursor,
|
|
1202
|
+
// Windsurf, Cline, Copilot agent-mode all surface `instructions`. It's a one-time
|
|
1203
|
+
// nudge (not per-prompt recall), so it stays short and behavior-focused.
|
|
1204
|
+
const CACHLY_MCP_INSTRUCTIONS = `This project has a Cachly AI Brain — persistent, cross-session memory of past fixes, decisions, and gotchas for THIS codebase. Use it proactively; it makes you faster and stops you repeating known mistakes.
|
|
1205
|
+
|
|
1206
|
+
- BEFORE a non-trivial task: call smart_recall with a short description of what you're about to do. If it returns lessons, apply them before researching from scratch.
|
|
1207
|
+
- WHEN debugging an error: call causal_trace with the exact error first — a known fix may already exist.
|
|
1208
|
+
- AFTER a fix, deploy, or discovery: call learn_from_attempts so the knowledge persists.
|
|
1209
|
+
- BEFORE a risky op (deploy, migration, dependency bump): call brain_predict.
|
|
1210
|
+
|
|
1211
|
+
The Brain instance id comes from the CACHLY_BRAIN_INSTANCE_ID environment variable.`;
|
|
1212
|
+
const server = new Server({ name: 'cachly-mcp', version: CURRENT_VERSION }, { capabilities: { tools: {} }, instructions: CACHLY_MCP_INSTRUCTIONS });
|
|
1182
1213
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
1183
1214
|
// ── Auto-session management ───────────────────────────────────────────────────
|
|
1184
1215
|
// Transparently starts a Brain session on the first tool call of a process
|
|
@@ -1467,31 +1498,49 @@ Report predicted failure risks to the user before proceeding.
|
|
|
1467
1498
|
Never commit code that does not compile. Run \`tsc --noEmit\` / \`go build ./...\` before every commit.
|
|
1468
1499
|
${CLAUDE_MD_MARKER_END}`;
|
|
1469
1500
|
}
|
|
1501
|
+
// Cursor loads rules from .cursor/rules/*.mdc with YAML frontmatter. alwaysApply
|
|
1502
|
+
// makes the block active in every request (the closest Cursor gets to a
|
|
1503
|
+
// SessionStart briefing). The frontmatter sits OUTSIDE the brain markers so
|
|
1504
|
+
// idempotent marker-replacement leaves it intact.
|
|
1505
|
+
const CURSOR_MDC_FRONTMATTER = '---\n' +
|
|
1506
|
+
'description: Cachly AI Brain — persistent memory protocol (recall before tasks, learn after fixes)\n' +
|
|
1507
|
+
'alwaysApply: true\n' +
|
|
1508
|
+
'---\n\n';
|
|
1470
1509
|
/**
|
|
1471
|
-
* Schreibt das Brain-Protokoll in alle relevanten Instruction-Dateien:
|
|
1472
|
-
* - CLAUDE.md
|
|
1473
|
-
* - AGENTS.md
|
|
1474
|
-
* - .github/copilot-instructions.md
|
|
1475
|
-
*
|
|
1510
|
+
* Schreibt das Brain-Protokoll in alle relevanten Instruction-/Rules-Dateien:
|
|
1511
|
+
* - CLAUDE.md (Claude Code)
|
|
1512
|
+
* - AGENTS.md (Codex / generic agents)
|
|
1513
|
+
* - .github/copilot-instructions.md (GitHub Copilot)
|
|
1514
|
+
* - .cursor/rules/cachly.mdc (Cursor — frontmatter + alwaysApply)
|
|
1515
|
+
* - .windsurfrules (Windsurf / Cascade)
|
|
1516
|
+
* - .clinerules (Cline)
|
|
1517
|
+
* Idempotent, Marker-basiert. Cross-Harness Tier A (siehe make_cachly_great_again.md §6.7).
|
|
1476
1518
|
*/
|
|
1477
1519
|
export async function writeInstructions(projectDir, instanceId) {
|
|
1478
1520
|
const { writeFile, appendFile, readFile, mkdir } = await import('node:fs/promises');
|
|
1479
1521
|
const { existsSync } = await import('node:fs');
|
|
1480
1522
|
const { resolve, dirname } = await import('node:path');
|
|
1523
|
+
// `prefix` is written only when the file is created fresh (Cursor .mdc needs it);
|
|
1524
|
+
// on marker-update/append the prefix is left untouched.
|
|
1481
1525
|
const files = [
|
|
1482
|
-
resolve(projectDir, 'CLAUDE.md'),
|
|
1483
|
-
resolve(projectDir, 'AGENTS.md'),
|
|
1484
|
-
resolve(projectDir, '.github', 'copilot-instructions.md'),
|
|
1526
|
+
{ path: resolve(projectDir, 'CLAUDE.md') },
|
|
1527
|
+
{ path: resolve(projectDir, 'AGENTS.md') },
|
|
1528
|
+
{ path: resolve(projectDir, '.github', 'copilot-instructions.md') },
|
|
1529
|
+
{ path: resolve(projectDir, '.cursor', 'rules', 'cachly.mdc'), prefix: CURSOR_MDC_FRONTMATTER },
|
|
1530
|
+
{ path: resolve(projectDir, '.windsurfrules') },
|
|
1531
|
+
{ path: resolve(projectDir, '.clinerules') },
|
|
1485
1532
|
];
|
|
1486
1533
|
const block = '\n' + buildClaudeMdBlock(instanceId) + '\n';
|
|
1487
1534
|
const results = {};
|
|
1488
|
-
for (const file of files) {
|
|
1535
|
+
for (const { path: file, prefix } of files) {
|
|
1489
1536
|
await mkdir(dirname(file), { recursive: true });
|
|
1490
1537
|
if (existsSync(file)) {
|
|
1491
1538
|
const existing = await readFile(file, 'utf-8');
|
|
1492
1539
|
if (existing.includes(CLAUDE_MD_MARKER_START)) {
|
|
1493
|
-
// Idempotent update: replace existing block
|
|
1494
|
-
|
|
1540
|
+
// Idempotent update: replace existing block ([\s\S] must keep its
|
|
1541
|
+
// backslashes so it matches any char — a bare [\s\S] in the template
|
|
1542
|
+
// literal collapses to [sS] and the replace silently no-ops).
|
|
1543
|
+
const updated = existing.replace(new RegExp(`${CLAUDE_MD_MARKER_START}[\\s\\S]*?${CLAUDE_MD_MARKER_END}`), buildClaudeMdBlock(instanceId));
|
|
1495
1544
|
await writeFile(file, updated, 'utf-8');
|
|
1496
1545
|
results[file] = 'updated';
|
|
1497
1546
|
continue;
|
|
@@ -1500,7 +1549,7 @@ export async function writeInstructions(projectDir, instanceId) {
|
|
|
1500
1549
|
results[file] = 'appended';
|
|
1501
1550
|
continue;
|
|
1502
1551
|
}
|
|
1503
|
-
await writeFile(file, block.trimStart(), 'utf-8');
|
|
1552
|
+
await writeFile(file, (prefix ?? '') + block.trimStart(), 'utf-8');
|
|
1504
1553
|
results[file] = 'written';
|
|
1505
1554
|
}
|
|
1506
1555
|
return results;
|
|
@@ -2408,6 +2457,22 @@ if (process.argv[2] === 'init') {
|
|
|
2408
2457
|
console.log(`✓ CLS hook already current in .git/hooks/post-commit`);
|
|
2409
2458
|
}
|
|
2410
2459
|
catch { /* non-critical — git hook is a best-effort feature */ }
|
|
2460
|
+
// ── Ambient Recall Phase 4: auto-install SessionStart + UserPromptSubmit hooks ─
|
|
2461
|
+
// Only for Claude Code (the hooks config lives in .claude/settings.json).
|
|
2462
|
+
if (editor === 'claude') {
|
|
2463
|
+
try {
|
|
2464
|
+
const a = await installAmbientHooks(projectDir, instanceId, JWT || undefined);
|
|
2465
|
+
const scriptVerb = a.scripts === 'written' ? '✅ Written' : a.scripts === 'upgraded' ? `✅ Upgraded (→ ${AMBIENT_HOOK_VERSION})` : '✓ Unchanged';
|
|
2466
|
+
console.log(`${scriptVerb}: .claude/hooks/ (Ambient Recall — session briefing, per-prompt recall, file briefing, auto-learn)`);
|
|
2467
|
+
if (a.settings === 'written')
|
|
2468
|
+
console.log(`✅ Written: .claude/settings.json (Ambient Recall hooks wired)`);
|
|
2469
|
+
else if (a.settings === 'merged')
|
|
2470
|
+
console.log(`✅ Merged: .claude/settings.json (Ambient Recall hooks wired)`);
|
|
2471
|
+
else
|
|
2472
|
+
console.log(`✓ Ambient Recall hooks already wired in .claude/settings.json`);
|
|
2473
|
+
}
|
|
2474
|
+
catch { /* non-critical — ambient hooks are a best-effort feature */ }
|
|
2475
|
+
}
|
|
2411
2476
|
const initSecs = ((Date.now() - initStart) / 1000).toFixed(1);
|
|
2412
2477
|
console.log(`\n🧠 Cachly AI Brain configured for ${editor === 'claude' ? 'Claude Code' : editor}! (${initSecs}s)`);
|
|
2413
2478
|
console.log(` Restart your editor — the \`cachly\` MCP tools will appear.`);
|
|
@@ -3395,6 +3460,138 @@ if (process.argv[2] === 'learn-git') {
|
|
|
3395
3460
|
}
|
|
3396
3461
|
process.exit(0);
|
|
3397
3462
|
}
|
|
3463
|
+
// ── ambient-recall: CLI entrypoint for the Claude Code hooks ──────────────────
|
|
3464
|
+
// Invoked as: <hook payload JSON on stdin> | cachly ambient-recall
|
|
3465
|
+
// SessionStart/UserPromptSubmit/PreToolUse payloads recall through the §6.3
|
|
3466
|
+
// relevance gate (ambient-cli.ts) and print the `hookSpecificOutput` JSON that
|
|
3467
|
+
// Claude Code injects as additionalContext — or nothing. Stop payloads instead
|
|
3468
|
+
// feed a fix-signal observation to auto_learn_session (the automatic
|
|
3469
|
+
// `learn_from_attempts`). Injections are booked into the net-token ledger
|
|
3470
|
+
// (§6.2) and auto-backoff kicks in when the recent window is net-negative.
|
|
3471
|
+
// Best-effort: no JWT, no stdin, a slow brain or any error → prints nothing and
|
|
3472
|
+
// exits 0 so a hook can NEVER block or corrupt the agent's turn.
|
|
3473
|
+
if (process.argv[2] === 'ambient-recall') {
|
|
3474
|
+
try {
|
|
3475
|
+
if (process.stdin.isTTY)
|
|
3476
|
+
process.exit(0); // no piped payload → nothing to do
|
|
3477
|
+
const chunks = [];
|
|
3478
|
+
for await (const chunk of process.stdin)
|
|
3479
|
+
chunks.push(chunk);
|
|
3480
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
3481
|
+
if (!JWT)
|
|
3482
|
+
process.exit(0); // not authenticated → silent no-op
|
|
3483
|
+
const instanceId = process.env.CACHLY_BRAIN_INSTANCE_ID ?? _defaultInstanceId;
|
|
3484
|
+
// Stop event → auto-learn, never inject (roadmap §6.1 PostToolUse/Stop row).
|
|
3485
|
+
const stopPayload = parseHookPayload(raw);
|
|
3486
|
+
if (stopPayload?.hook_event_name === 'Stop') {
|
|
3487
|
+
const obs = stopObservation(stopPayload);
|
|
3488
|
+
if (obs && instanceId) {
|
|
3489
|
+
await handleTool('auto_learn_session', { instance_id: instanceId, observations: [obs] });
|
|
3490
|
+
}
|
|
3491
|
+
process.exit(0);
|
|
3492
|
+
}
|
|
3493
|
+
let reported;
|
|
3494
|
+
const out = await runAmbient(raw, {
|
|
3495
|
+
timeoutMs: 3000, // §6.3 guardrail 4: never stall the prompt on a slow brain
|
|
3496
|
+
// Ambient injection budget: one relevance-ranked briefing, hard-capped.
|
|
3497
|
+
// smart_recall already returns its hits best-first, so the capped head is
|
|
3498
|
+
// the high-signal part. Per-lesson structured candidates are a future slice.
|
|
3499
|
+
gate: { topK: 1, maxTokens: 500 },
|
|
3500
|
+
backoff: async () => shouldBackoff(await readLedger()),
|
|
3501
|
+
onInject: (tokens, event) => {
|
|
3502
|
+
const entry = { ts: new Date().toISOString(), event, injected: tokens, prevented: 0 };
|
|
3503
|
+
void appendLedgerEntry(entry);
|
|
3504
|
+
reported = reportAmbientLedgerEvent(instanceId, entry); // org dashboard mirror
|
|
3505
|
+
},
|
|
3506
|
+
recall: async (query) => {
|
|
3507
|
+
if (!instanceId)
|
|
3508
|
+
return [];
|
|
3509
|
+
const text = String((await handleTool('smart_recall', { instance_id: instanceId, query })) ?? '');
|
|
3510
|
+
const miss = text.length < 80 || /No lessons found|no lessons|No matches found/i.test(text);
|
|
3511
|
+
if (miss)
|
|
3512
|
+
return [];
|
|
3513
|
+
return [{ id: 'ambient', summary: truncateToTokens(text, 500), confidence: 0.9, score: 0.9 }];
|
|
3514
|
+
},
|
|
3515
|
+
});
|
|
3516
|
+
if (out)
|
|
3517
|
+
process.stdout.write(out);
|
|
3518
|
+
// Bounded flush so the dashboard mirror survives process.exit — never more
|
|
3519
|
+
// than 500ms on top of a turn that already injected.
|
|
3520
|
+
if (reported)
|
|
3521
|
+
await Promise.race([reported, new Promise((r) => setTimeout(r, 500))]);
|
|
3522
|
+
}
|
|
3523
|
+
catch {
|
|
3524
|
+
// Swallow everything — an ambient hook must never break the agent.
|
|
3525
|
+
}
|
|
3526
|
+
process.exit(0);
|
|
3527
|
+
}
|
|
3528
|
+
// ── ambient-credit: agent-reported prevented-token credit (§6.2) ──────────────
|
|
3529
|
+
// Invoked as: cachly ambient-credit <tokens> [note…] — the injected context's
|
|
3530
|
+
// footer invites the agent to call this when a recalled lesson changed its path.
|
|
3531
|
+
// This is the client-side signal that makes the net ledger (and auto-backoff)
|
|
3532
|
+
// meaningful before the server-side dashboard exists. Silent + exit 0 always.
|
|
3533
|
+
if (process.argv[2] === 'ambient-credit') {
|
|
3534
|
+
try {
|
|
3535
|
+
const tokens = Math.round(Number(process.argv[3]));
|
|
3536
|
+
if (Number.isFinite(tokens) && tokens > 0) {
|
|
3537
|
+
// Cap a single credit: self-reported savings should never let one
|
|
3538
|
+
// enthusiastic claim mask weeks of negative balance.
|
|
3539
|
+
const capped = Math.min(tokens, 20_000);
|
|
3540
|
+
const note = process.argv.slice(4).join(' ').slice(0, 200) || undefined;
|
|
3541
|
+
const entry = { ts: new Date().toISOString(), event: 'credit', injected: 0, prevented: capped, note };
|
|
3542
|
+
await appendLedgerEntry(entry);
|
|
3543
|
+
await reportAmbientLedgerEvent(process.env.CACHLY_BRAIN_INSTANCE_ID ?? _defaultInstanceId, entry); // org dashboard mirror
|
|
3544
|
+
console.log(`✅ ambient credit recorded: ${capped} tokens${note ? ` (${note})` : ''}`);
|
|
3545
|
+
}
|
|
3546
|
+
else {
|
|
3547
|
+
console.error('Usage: npx @cachly-dev/mcp-server@latest ambient-credit <tokens-saved> [note]');
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
catch { /* telemetry — never fail loudly */ }
|
|
3551
|
+
process.exit(0);
|
|
3552
|
+
}
|
|
3553
|
+
// ── ambient-stats: the honest net-token readout (§6.2) ────────────────────────
|
|
3554
|
+
// Shows injected vs prevented and the NET — even when it is negative — plus
|
|
3555
|
+
// whether auto-backoff is currently pausing injection.
|
|
3556
|
+
if (process.argv[2] === 'ambient-stats') {
|
|
3557
|
+
const entries = await readLedger();
|
|
3558
|
+
const bal = netBalance(entries);
|
|
3559
|
+
const backing = shouldBackoff(entries);
|
|
3560
|
+
const recent = entries.slice(-5);
|
|
3561
|
+
console.log('\n🧠 Ambient Recall — net-token ledger\n');
|
|
3562
|
+
console.log(` Turns recorded: ${entries.length}`);
|
|
3563
|
+
console.log(` Injected tokens: ${bal.injected}`);
|
|
3564
|
+
console.log(` Prevented tokens: ${bal.prevented} (agent-reported via ambient-credit)`);
|
|
3565
|
+
console.log(` NET: ${bal.net >= 0 ? '+' : ''}${bal.net} tokens`);
|
|
3566
|
+
console.log(` Auto-backoff: ${backing ? '🔴 ACTIVE — recent window is net-negative, injection paused' : '🟢 inactive'}`);
|
|
3567
|
+
if (recent.length > 0) {
|
|
3568
|
+
console.log('\n Last entries:');
|
|
3569
|
+
for (const e of recent) {
|
|
3570
|
+
const what = e.prevented > 0 ? `+${e.prevented} prevented` : `-${e.injected} injected`;
|
|
3571
|
+
console.log(` • ${e.ts.slice(0, 19)} ${e.event}: ${what}${e.note ? ` (${e.note})` : ''}`);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
console.log('');
|
|
3575
|
+
process.exit(0);
|
|
3576
|
+
}
|
|
3577
|
+
// ── doctor: one-command setup diagnosis ───────────────────────────────────────
|
|
3578
|
+
// Walks the whole activation chain — runtime → credential → API → auth →
|
|
3579
|
+
// instance → hooks → ledger — and prints one ✅/⚠️/❌ report with a concrete
|
|
3580
|
+
// next step per finding. Exit 1 only on hard failures (warnings stay 0).
|
|
3581
|
+
if (process.argv[2] === 'doctor') {
|
|
3582
|
+
const { checkNodeVersion, checkCredential, checkApiReachable, checkAuthAccepted, checkInstance, inspectAmbientHooks, checkHooks, checkLedger, renderDoctorReport, doctorExitCode, } = await import('./doctor.js');
|
|
3583
|
+
const checks = [
|
|
3584
|
+
checkNodeVersion(),
|
|
3585
|
+
checkCredential(JWT),
|
|
3586
|
+
await checkApiReachable(API_URL),
|
|
3587
|
+
await checkAuthAccepted(API_URL, JWT),
|
|
3588
|
+
checkInstance(process.env.CACHLY_BRAIN_INSTANCE_ID ?? _defaultInstanceId),
|
|
3589
|
+
checkHooks(inspectAmbientHooks(process.cwd())),
|
|
3590
|
+
checkLedger(await readLedger(), defaultLedgerPath()),
|
|
3591
|
+
];
|
|
3592
|
+
console.log(renderDoctorReport(checks));
|
|
3593
|
+
process.exit(doctorExitCode(checks));
|
|
3594
|
+
}
|
|
3398
3595
|
// ── cls-ingest: CLI entrypoint for the git post-commit hook ───────────────────
|
|
3399
3596
|
// Invoked as: cachly cls-ingest '<json>' where json = { instance_id, source, payload }.
|
|
3400
3597
|
// Auth comes from CACHLY_JWT (embedded in the generated hook). This is a silent,
|
|
@@ -3468,7 +3665,7 @@ if (!JWT) {
|
|
|
3468
3665
|
// • Editor as an MCP stdio server (non-TTY) → ONE stderr hint, then KEEP RUNNING
|
|
3469
3666
|
// so tools/list works and the first tool call starts the browser sign-in.
|
|
3470
3667
|
// Skip entirely for CLI subcommands that intentionally run without credentials.
|
|
3471
|
-
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'autosetup', 'setup', 'autopilot', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade', 'bench', 'tool-specs', 'openapi'];
|
|
3668
|
+
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'autosetup', 'setup', 'autopilot', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade', 'bench', 'tool-specs', 'openapi', 'ambient-credit', 'ambient-stats', 'doctor'];
|
|
3472
3669
|
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')) {
|
|
3473
3670
|
const runningInTerminal = !process.argv[2] && process.stdout.isTTY === true && _isMain;
|
|
3474
3671
|
if (runningInTerminal) {
|