@cachly-dev/mcp-server 0.10.100 → 0.10.102
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 +6 -1
- package/dist/src/cls-hook.d.ts +15 -0
- package/dist/src/cls-hook.js +94 -0
- package/dist/src/cls-hook.js.map +1 -0
- package/dist/src/handlers/brain.js +44 -3
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/fedbrain.js +2 -14
- package/dist/src/handlers/fedbrain.js.map +1 -1
- package/dist/src/handlers/share.js +1 -1
- package/dist/src/handlers/share.js.map +1 -1
- package/dist/src/index.js +121 -70
- package/dist/src/index.js.map +1 -1
- package/dist/src/telemetry-types.d.ts +1 -1
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -706,6 +706,7 @@ import { handleRoadmapTool } from './handlers/roadmap.js';
|
|
|
706
706
|
import { handleAdvancedTool } from './handlers/advanced.js';
|
|
707
707
|
import { handleSyndicateTool } from './handlers/syndicate.js';
|
|
708
708
|
import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
|
|
709
|
+
import { installClsPostCommitHook, CLS_HOOK_VERSION } from './cls-hook.js';
|
|
709
710
|
import { handleShareTool } from './handlers/share.js';
|
|
710
711
|
import { handleVizTool } from './handlers/viz.js';
|
|
711
712
|
// ── Tools (imported from tools.ts) ─────────────────────────────────────────
|
|
@@ -776,6 +777,67 @@ async function sendAnonymousTelemetry(toolName) {
|
|
|
776
777
|
}
|
|
777
778
|
catch { /* fire-and-forget, never block the user */ }
|
|
778
779
|
}
|
|
780
|
+
// ── Feature-Gate (premium tools) ────────────────────────────────────────────
|
|
781
|
+
// Free tier delivers the magic moment: keyword + CKG recall, learn, sessions.
|
|
782
|
+
// The deeper intelligence layers — causal root-cause tracing, predictive risk,
|
|
783
|
+
// and the shared Team Brain — are Premium. Value through depth, not volume. A
|
|
784
|
+
// free user who reaches for one of these gets the value pitched at the exact
|
|
785
|
+
// moment of need, never a silent failure.
|
|
786
|
+
const PREMIUM_TOOLS = new Set([
|
|
787
|
+
// Predictive + causal intelligence
|
|
788
|
+
'causal_trace', 'brain_predict', 'brain_plan', 'brain_predict_failures',
|
|
789
|
+
// Team Brain (shared, multi-author knowledge)
|
|
790
|
+
'team_learn', 'team_confirm', 'team_recall', 'team_synthesize', 'team_crystallize',
|
|
791
|
+
'global_learn', 'global_recall',
|
|
792
|
+
]);
|
|
793
|
+
const PREMIUM_PITCH = {
|
|
794
|
+
causal_trace: 'Causal root-cause tracing — follow the failure chain to the real cause, not just the symptom.',
|
|
795
|
+
brain_predict: 'Predictive risk — your Brain warns you what will break *before* you run it.',
|
|
796
|
+
brain_plan: 'Generative planning — turn a goal into an ordered, risk-aware plan from your own history.',
|
|
797
|
+
brain_predict_failures: 'Failure prediction — catch CI/build breakage before it happens.',
|
|
798
|
+
team_learn: 'Team Brain — one shared memory across your whole team.',
|
|
799
|
+
team_confirm: 'Team Brain — peer + senior review that raises lesson trust.',
|
|
800
|
+
team_recall: 'Team Brain — recall everything your teammates have already solved.',
|
|
801
|
+
team_synthesize: 'Team Brain — synthesize collective knowledge across authors.',
|
|
802
|
+
team_crystallize: "Team Brain — compress your team's wisdom into a shareable crystal.",
|
|
803
|
+
global_learn: "Team Brain — contribute to your organization's shared memory.",
|
|
804
|
+
global_recall: 'Team Brain — recall across your entire organization.',
|
|
805
|
+
};
|
|
806
|
+
const UPGRADE_URL_FG = 'https://cachly.dev/billing';
|
|
807
|
+
const _tierCache = new Map();
|
|
808
|
+
async function getTierInfo(instanceId) {
|
|
809
|
+
const cached = _tierCache.get(instanceId);
|
|
810
|
+
if (cached && cached.expiresAt > Date.now())
|
|
811
|
+
return cached.info;
|
|
812
|
+
try {
|
|
813
|
+
const inst = await apiFetch(`/api/v1/instances/${instanceId}`);
|
|
814
|
+
const tier = (inst?.tier ?? 'free').toLowerCase();
|
|
815
|
+
const info = { tier, isFree: tier === 'free' || tier === '' };
|
|
816
|
+
_tierCache.set(instanceId, { info, expiresAt: Date.now() + 60_000 });
|
|
817
|
+
return info;
|
|
818
|
+
}
|
|
819
|
+
catch {
|
|
820
|
+
// Tier unknown → fail open (never block on a stats hiccup).
|
|
821
|
+
return { tier: 'unknown', isFree: false };
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
/** Returns an upgrade teaser if a free-tier caller reached for a premium tool, else null. */
|
|
825
|
+
async function featureGate(name, instanceId) {
|
|
826
|
+
if (!PREMIUM_TOOLS.has(name) || !instanceId)
|
|
827
|
+
return null;
|
|
828
|
+
const { isFree } = await getTierInfo(instanceId);
|
|
829
|
+
if (!isFree)
|
|
830
|
+
return null;
|
|
831
|
+
const pitch = PREMIUM_PITCH[name] ?? 'A Premium intelligence feature.';
|
|
832
|
+
return [
|
|
833
|
+
`🔒 **\`${name}\` is a Premium feature**`,
|
|
834
|
+
``,
|
|
835
|
+
`${pitch}`,
|
|
836
|
+
``,
|
|
837
|
+
`Your Free Brain keeps full keyword + causal-graph recall, learning, and sessions — forever.`,
|
|
838
|
+
`Unlock the deeper layer (causal tracing · predictive risk · Team Brain) → ${UPGRADE_URL_FG}`,
|
|
839
|
+
].join('\n');
|
|
840
|
+
}
|
|
779
841
|
async function handleTool(name, args) {
|
|
780
842
|
// Guard: if no JWT, return actionable onboarding message instead of HTTP 401
|
|
781
843
|
if (!JWT) {
|
|
@@ -843,6 +905,13 @@ async function handleTool(name, args) {
|
|
|
843
905
|
if (defaultId)
|
|
844
906
|
args = { ...args, instance_id: defaultId };
|
|
845
907
|
}
|
|
908
|
+
// Feature-Gate: free tier reaching for a Premium tool gets the upgrade pitch
|
|
909
|
+
// at the moment of need (the magic moment of basic recall stays untouched).
|
|
910
|
+
const gateMsg = await featureGate(name, args.instance_id);
|
|
911
|
+
if (gateMsg !== null) {
|
|
912
|
+
sendFunnelEvent('premium_gate_hit', { tool: name, instance_id: args.instance_id ?? '' });
|
|
913
|
+
return gateMsg;
|
|
914
|
+
}
|
|
846
915
|
// Delegate brain tools (learn, recall, session, etc.)
|
|
847
916
|
const brainResult = await handleBrainTool(name, args, getConnection, apiFetch);
|
|
848
917
|
if (brainResult !== null) {
|
|
@@ -1880,6 +1949,11 @@ if (process.argv[2] === 'bench') {
|
|
|
1880
1949
|
console.log(' \x1b[2m npx @cachly-dev/mcp-server@latest bench my-incidents.json\x1b[0m');
|
|
1881
1950
|
console.log(' \x1b[2m npx @cachly-dev/mcp-server@latest bench --json # machine-readable\x1b[0m');
|
|
1882
1951
|
console.log('');
|
|
1952
|
+
console.log(' \x1b[1m→ Give your AI this recall quality on your own work (free, 1–5 min):\x1b[0m');
|
|
1953
|
+
console.log(' \x1b[32m$ npx @cachly-dev/mcp-server@latest autopilot\x1b[0m');
|
|
1954
|
+
console.log(' \x1b[90m Signs in, configures every editor, bootstraps from your git history.\x1b[0m');
|
|
1955
|
+
console.log(' \x1b[90m Or create your Brain at: \x1b[36mhttps://cachly.dev/setup-ai\x1b[0m');
|
|
1956
|
+
console.log('');
|
|
1883
1957
|
}
|
|
1884
1958
|
catch (e) {
|
|
1885
1959
|
console.error(`\n❌ bench failed: ${e.message}\n`);
|
|
@@ -2340,43 +2414,15 @@ if (process.argv[2] === 'init') {
|
|
|
2340
2414
|
}
|
|
2341
2415
|
// ── CLS Phase 4: Auto-install git post-commit hook ─────────────────────────
|
|
2342
2416
|
try {
|
|
2343
|
-
const
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
if (
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
`CACHLY_INSTANCE="${instanceId}"`,
|
|
2353
|
-
`SHA=$(git rev-parse HEAD 2>/dev/null || echo "")`,
|
|
2354
|
-
`MSG=$(git log -1 --pretty=%B 2>/dev/null | head -1 | tr '"' "'" | cut -c1-200)`,
|
|
2355
|
-
`FILES=$(git diff-tree --no-commit-id -r --name-only HEAD 2>/dev/null | tr '\\n' ',' | sed 's/,$//')`,
|
|
2356
|
-
`node -e "try{require('child_process').execSync('npx @cachly-dev/mcp-server@latest cls-ingest \\''+ JSON.stringify({instance_id:'$CACHLY_INSTANCE',source:'git_commit',payload:{message:'$MSG',sha:'$SHA',files:'$FILES'.split(',').filter(Boolean)}})+'\\'' ,{stdio:'ignore',timeout:5000})}catch(e){}" 2>/dev/null &`,
|
|
2357
|
-
`exit 0`,
|
|
2358
|
-
].join('\n');
|
|
2359
|
-
let existing = '';
|
|
2360
|
-
try {
|
|
2361
|
-
const { readFile } = await import('node:fs/promises');
|
|
2362
|
-
existing = await readFile(hookPath, 'utf-8');
|
|
2363
|
-
}
|
|
2364
|
-
catch { /* no existing hook */ }
|
|
2365
|
-
if (existing && !existing.includes('cachly CLS')) {
|
|
2366
|
-
// Append to existing hook
|
|
2367
|
-
await writeFile(hookPath, existing.trimEnd() + '\n\n' + hookScript + '\n', 'utf-8');
|
|
2368
|
-
console.log(`✅ Appended: .git/hooks/post-commit (CLS hook)`);
|
|
2369
|
-
}
|
|
2370
|
-
else if (!existing) {
|
|
2371
|
-
await writeFile(hookPath, hookScript + '\n', 'utf-8');
|
|
2372
|
-
const { chmod } = await import('node:fs/promises');
|
|
2373
|
-
await chmod(hookPath, 0o755);
|
|
2374
|
-
console.log(`✅ Written: .git/hooks/post-commit (CLS hook)`);
|
|
2375
|
-
}
|
|
2376
|
-
else {
|
|
2377
|
-
console.log(`✓ CLS hook already present in .git/hooks/post-commit`);
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2417
|
+
const r = await installClsPostCommitHook(projectDir, instanceId, JWT || undefined);
|
|
2418
|
+
if (r === 'written')
|
|
2419
|
+
console.log(`✅ Written: .git/hooks/post-commit (CLS hook)`);
|
|
2420
|
+
else if (r === 'upgraded')
|
|
2421
|
+
console.log(`✅ Upgraded: .git/hooks/post-commit (CLS hook → ${CLS_HOOK_VERSION})`);
|
|
2422
|
+
else if (r === 'appended')
|
|
2423
|
+
console.log(`✅ Appended: .git/hooks/post-commit (CLS hook)`);
|
|
2424
|
+
else if (r === 'unchanged')
|
|
2425
|
+
console.log(`✓ CLS hook already current in .git/hooks/post-commit`);
|
|
2380
2426
|
}
|
|
2381
2427
|
catch { /* non-critical — git hook is a best-effort feature */ }
|
|
2382
2428
|
const initSecs = ((Date.now() - initStart) / 1000).toFixed(1);
|
|
@@ -3229,39 +3275,13 @@ if (process.argv[2] === 'setup' || _isAutopilotCli) {
|
|
|
3229
3275
|
}
|
|
3230
3276
|
// ── CLS Phase 4: Auto-install git post-commit hook in setup flow ─────────
|
|
3231
3277
|
try {
|
|
3232
|
-
const
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
await mk2(gitHookDir2, { recursive: true });
|
|
3240
|
-
const hs2 = [
|
|
3241
|
-
`#!/bin/sh`,
|
|
3242
|
-
`# cachly CLS — Continuous Learning Stream (installed by cachly setup)`,
|
|
3243
|
-
`CACHLY_INSTANCE="${instance.id}"`,
|
|
3244
|
-
`SHA=$(git rev-parse HEAD 2>/dev/null || echo "")`,
|
|
3245
|
-
`MSG=$(git log -1 --pretty=%B 2>/dev/null | head -1 | tr '"' "'" | cut -c1-200)`,
|
|
3246
|
-
`FILES=$(git diff-tree --no-commit-id -r --name-only HEAD 2>/dev/null | tr '\\n' ',' | sed 's/,$//')`,
|
|
3247
|
-
`node -e "try{require('child_process').execSync('npx @cachly-dev/mcp-server@latest cls-ingest \\''+ JSON.stringify({instance_id:'$CACHLY_INSTANCE',source:'git_commit',payload:{message:'$MSG',sha:'$SHA',files:'$FILES'.split(',').filter(Boolean)}})+'\\'' ,{stdio:'ignore',timeout:5000})}catch(e){}" 2>/dev/null &`,
|
|
3248
|
-
`exit 0`,
|
|
3249
|
-
].join('\n');
|
|
3250
|
-
let ex2 = '';
|
|
3251
|
-
try {
|
|
3252
|
-
ex2 = await rf2(hookPath2, 'utf-8');
|
|
3253
|
-
}
|
|
3254
|
-
catch { /* no existing */ }
|
|
3255
|
-
if (!ex2) {
|
|
3256
|
-
await wf2(hookPath2, hs2 + '\n', 'utf-8');
|
|
3257
|
-
await ch2(hookPath2, 0o755);
|
|
3258
|
-
console.log(`\n✅ CLS git hook installed — your brain will learn from every commit.`);
|
|
3259
|
-
}
|
|
3260
|
-
else if (!ex2.includes('cachly CLS')) {
|
|
3261
|
-
await wf2(hookPath2, ex2.trimEnd() + '\n\n' + hs2 + '\n', 'utf-8');
|
|
3262
|
-
console.log(`\n✅ CLS git hook appended to existing post-commit.`);
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3278
|
+
const r = await installClsPostCommitHook(process.cwd(), instance.id, token || undefined);
|
|
3279
|
+
if (r === 'written')
|
|
3280
|
+
console.log(`\n✅ CLS git hook installed — your brain will learn from every commit.`);
|
|
3281
|
+
else if (r === 'upgraded')
|
|
3282
|
+
console.log(`\n✅ CLS git hook upgraded to ${CLS_HOOK_VERSION}.`);
|
|
3283
|
+
else if (r === 'appended')
|
|
3284
|
+
console.log(`\n✅ CLS git hook appended to existing post-commit.`);
|
|
3265
3285
|
}
|
|
3266
3286
|
catch { /* non-critical */ }
|
|
3267
3287
|
// Setup reached the end — close the funnel. Report config-write outcome so the
|
|
@@ -3389,6 +3409,37 @@ if (process.argv[2] === 'learn-git') {
|
|
|
3389
3409
|
}
|
|
3390
3410
|
process.exit(0);
|
|
3391
3411
|
}
|
|
3412
|
+
// ── cls-ingest: CLI entrypoint for the git post-commit hook ───────────────────
|
|
3413
|
+
// Invoked as: cachly cls-ingest '<json>' where json = { instance_id, source, payload }.
|
|
3414
|
+
// Auth comes from CACHLY_JWT (embedded in the generated hook). This is a silent,
|
|
3415
|
+
// best-effort sink: ANY problem (no JWT, bad JSON, network) exits 0 so a commit
|
|
3416
|
+
// is never blocked or noisy. Without this command the hook was a no-op.
|
|
3417
|
+
if (process.argv[2] === 'cls-ingest') {
|
|
3418
|
+
try {
|
|
3419
|
+
const raw = process.argv[3];
|
|
3420
|
+
if (!raw || !JWT)
|
|
3421
|
+
process.exit(0); // nothing to do / not authenticated → silent
|
|
3422
|
+
let parsed;
|
|
3423
|
+
try {
|
|
3424
|
+
parsed = JSON.parse(raw);
|
|
3425
|
+
}
|
|
3426
|
+
catch {
|
|
3427
|
+
process.exit(0); // malformed payload → never block the commit
|
|
3428
|
+
}
|
|
3429
|
+
const instanceId = parsed.instance_id ?? _defaultInstanceId;
|
|
3430
|
+
if (!instanceId || !parsed.source)
|
|
3431
|
+
process.exit(0);
|
|
3432
|
+
await handleTool('cls_ingest', {
|
|
3433
|
+
instance_id: instanceId,
|
|
3434
|
+
source: parsed.source,
|
|
3435
|
+
payload: parsed.payload ?? {},
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
catch {
|
|
3439
|
+
// Swallow everything — a learning sink must never break `git commit`.
|
|
3440
|
+
}
|
|
3441
|
+
process.exit(0);
|
|
3442
|
+
}
|
|
3392
3443
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
3393
3444
|
// M2M / headless auth: if no JWT but client credentials are present, run the
|
|
3394
3445
|
// OAuth2 client_credentials grant before any credential-missing handling. This
|