@cachly-dev/mcp-server 0.10.6 โ†’ 0.10.8

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.
@@ -4,7 +4,6 @@ import { calculateConfidence } from '../confidence.js';
4
4
  import { ckgSlug, ckgUpdateEdge } from '../ckg.js';
5
5
  import type { CKGEdge, CKGNode } from '../ckg.js';
6
6
  import { keywordSearch, tokenize } from '../search.js';
7
- import { safeJsonParse } from '../utils.js';
8
7
 
9
8
  type GetConnection = (instanceId: string) => Promise<Redis>;
10
9
  type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
@@ -293,8 +292,7 @@ export async function handleSyndicateTool(
293
292
  for (const ek of [...fromKeys, ...toKeys].slice(0, 50)) {
294
293
  const raw = await redis.get(ek);
295
294
  if (!raw) continue;
296
- const edge = safeJsonParse<CKGEdge | null>(raw, null);
297
- if (!edge) continue;
295
+ const edge: CKGEdge = JSON.parse(raw);
298
296
  allEdges.push(edge);
299
297
  if (hop < max_hops) {
300
298
  if (!visited.has(edge.from)) queue.push({ id: edge.from, hop: hop + 1 });
@@ -368,18 +366,16 @@ export async function handleSyndicateTool(
368
366
  for (const nk of nodeKeys.slice(0, 5)) {
369
367
  const nodeRaw = await redis.get(nk);
370
368
  if (!nodeRaw) continue;
371
- const node = safeJsonParse<CKGNode | null>(nodeRaw, null);
372
- if (!node) continue;
369
+ const node: CKGNode = JSON.parse(nodeRaw);
373
370
  const edgeKeys = await redis.smembers(`cachly:ckg:idx:from:${node.id}`);
374
371
  for (const ek of edgeKeys.slice(0, 20)) {
375
372
  const edgeRaw = await redis.get(ek);
376
373
  if (!edgeRaw) continue;
377
- const edge = safeJsonParse<CKGEdge | null>(edgeRaw, null);
378
- if (!edge) continue;
374
+ const edge: CKGEdge = JSON.parse(edgeRaw);
379
375
  // Only interested in fixes and co-occurs for prediction
380
376
  if (edge.edgeType !== 'fixes' && edge.edgeType !== 'co-occurs' && edge.edgeType !== 'causes') continue;
381
377
  const lessonRaw = await redis.get(`cachly:lesson:best:${edge.from}`);
382
- const lesson = lessonRaw ? safeJsonParse<{ what_worked?: string; topic: string } | null>(lessonRaw, null) ?? undefined : undefined;
378
+ const lesson = lessonRaw ? JSON.parse(lessonRaw) : undefined;
383
379
  predictions.push({ concept: node.id, edgeType: edge.edgeType, target: edge.to, confidence: edge.confidence, lesson });
384
380
  }
385
381
  }
@@ -393,17 +389,15 @@ export async function handleSyndicateTool(
393
389
  for (const k of lessonKeys) {
394
390
  const raw = await redis.get(k);
395
391
  if (!raw) continue;
396
- {
397
- type PredLesson = { topic: string; what_worked?: string; what_failed?: string; outcome: string; severity?: string; ts: string; verified_at?: string; recall_count?: number };
398
- const l = safeJsonParse<PredLesson | null>(raw, null);
399
- if (!l) continue;
392
+ try {
393
+ const l = JSON.parse(raw) as { topic: string; what_worked?: string; what_failed?: string; outcome: string; severity?: string; ts: string; verified_at?: string; recall_count?: number };
400
394
  const haystack = [l.topic, l.what_failed ?? '', l.what_worked ?? ''].join(' ').toLowerCase();
401
395
  const score = ctxTokens.reduce((s, t) => s + (haystack.includes(t) ? 1 : 0), 0);
402
396
  if (score >= 1 && l.outcome !== 'failure') {
403
397
  const conf = calculateConfidence(l);
404
398
  textPredictions.push({ ...l, confidence: conf });
405
399
  }
406
- }
400
+ } catch { /* skip */ }
407
401
  }
408
402
  textPredictions.sort((a, b) => b.confidence - a.confidence);
409
403
 
@@ -5,7 +5,6 @@ import type { Redis } from 'ioredis';
5
5
  import { calculateConfidence, confidenceBadge, CONFIDENCE_STALE_VALUE, CONFIDENCE_WARN_VALUE,
6
6
  CONFIDENCE_WARN_DAYS, CONFIDENCE_STALE_DAYS, simpleHash } from '../confidence.js';
7
7
  import type { Instance } from './brain.js';
8
- import { safeJsonParse } from '../utils.js';
9
8
 
10
9
  type GetConnection = (instanceId: string) => Promise<Redis>;
11
10
  type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
@@ -54,11 +53,10 @@ export async function handleTeamTool(
54
53
 
55
54
  type Lesson = { topic: string; what_worked: string; outcome: string; file_paths?: string[] };
56
55
  const relevant: string[] = [];
57
- const sfRaws = lessonKeys.length > 0 ? await redis.mget(...lessonKeys) : [];
58
- for (const raw of sfRaws) {
56
+ for (const k of lessonKeys) {
57
+ const raw = await redis.get(k);
59
58
  if (!raw) continue;
60
- const lesson = safeJsonParse<Lesson | null>(raw, null);
61
- if (!lesson) continue;
59
+ const lesson = JSON.parse(raw) as Lesson;
62
60
  // Match by file_paths stored in lesson OR by topic keywords matching file name
63
61
  const topicWords = lesson.topic.toLowerCase().split(/[:\-_]/);
64
62
  const fileMatches = changed_files.some(f => {
@@ -148,12 +146,10 @@ export async function handleTeamTool(
148
146
  author?: string; tags?: string[];
149
147
  };
150
148
  let lessons: TeamLesson[] = [];
151
- if (lessonKeys.length > 0) {
152
- const raws = await redis.mget(...lessonKeys);
153
- for (const raw of raws) {
154
- const l = safeJsonParse<TeamLesson | null>(raw ?? null, null);
155
- if (l) lessons.push(l);
156
- }
149
+ for (const k of lessonKeys) {
150
+ const raw = await redis.get(k);
151
+ if (!raw) continue;
152
+ try { lessons.push(JSON.parse(raw) as TeamLesson); } catch { /* skip */ }
157
153
  }
158
154
 
159
155
  // Filter
@@ -282,12 +278,10 @@ export async function handleTeamTool(
282
278
 
283
279
  type RawLesson = { topic: string; outcome: string; what_worked: string; severity?: string; ts: string; auto_learned?: boolean };
284
280
  const allLessons: RawLesson[] = [];
285
- if (allLessonKeys.length > 0) {
286
- const raws = await redis.mget(...allLessonKeys);
287
- for (const raw of raws) {
288
- const l = safeJsonParse<RawLesson | null>(raw ?? null, null);
289
- if (l) allLessons.push(l);
290
- }
281
+ for (const k of allLessonKeys) {
282
+ const raw = await redis.get(k);
283
+ if (!raw) continue;
284
+ try { allLessons.push(JSON.parse(raw) as RawLesson); } catch { /* skip */ }
291
285
  }
292
286
 
293
287
  // Group lessons by top-level category
@@ -373,17 +367,18 @@ export async function handleTeamTool(
373
367
  verified_at?: string; severity?: string; audit_trail?: unknown[];
374
368
  };
375
369
  const lessons: DrLesson[] = [];
376
- if (lessonKeys.length > 0) {
377
- const raws = await redis.mget(...lessonKeys);
378
- for (const raw of raws) {
379
- const l = safeJsonParse<DrLesson | null>(raw ?? null, null);
380
- if (l) lessons.push(l);
381
- }
370
+ for (const k of lessonKeys) {
371
+ const raw = await redis.get(k);
372
+ if (!raw) continue;
373
+ try { lessons.push(JSON.parse(raw) as DrLesson); } catch { /* skip */ }
382
374
  }
383
375
 
384
376
  // Last session
385
377
  const lastSessionRaw = await redis.get('cachly:session:last');
386
- const lastSession = safeJsonParse<{ ts: string; summary: string } | null>(lastSessionRaw, null);
378
+ let lastSession: { ts: string; summary: string } | null = null;
379
+ if (lastSessionRaw) {
380
+ try { lastSession = JSON.parse(lastSessionRaw); } catch { /* ignore */ }
381
+ }
387
382
 
388
383
  // Open failures
389
384
  const openFailures = lessons.filter(l => l.outcome === 'failure' || l.outcome === 'partial');
@@ -514,24 +509,13 @@ export async function handleTeamTool(
514
509
  issues.push(`๐Ÿ’ก ${unusedLessons.length} lessons never recalled โ€” verify topics match your workflow`);
515
510
  }
516
511
 
517
- const divider = 'โ”€'.repeat(48);
518
- const lines = [
519
- `๐Ÿฉบ **Brain Doctor**`,
520
- `\`${divider}\``,
521
- '',
522
- '**๐Ÿ“Š Health Checks**',
523
- ...checks.map(c => ' ' + c),
524
- '',
525
- ];
512
+ const lines = ['๐Ÿฉบ **Brain Doctor Report**', '', ...checks.map(c => ' ' + c), ''];
526
513
  if (issues.length > 0) {
527
- lines.push(`**๐Ÿ”ง Action Items** (${issues.length} issue${issues.length > 1 ? 's' : ''})`);
528
- issues.forEach((item, idx) => lines.push(` ${idx + 1}. ${item}`));
514
+ lines.push('**Issues to fix:**');
515
+ for (const i of issues) lines.push(' ' + i);
529
516
  lines.push('');
530
- lines.push(`_Fix the action items above, then re-run \`brain_doctor\` to verify._`);
531
517
  } else {
532
- lines.push('**โœ… All checks passed** โ€” Brain is healthy!');
533
- lines.push('');
534
- lines.push('_Keep the brain sharp: `session_start` โ†’ work โ†’ `learn_from_attempts` โ†’ `session_end`_');
518
+ lines.push(' ๐ŸŽ‰ Brain looks healthy! Keep calling session_start/session_end.');
535
519
  }
536
520
  return lines.join('\n');
537
521
  }
@@ -552,11 +536,7 @@ export async function handleTeamTool(
552
536
  .filter((l): l is NonNullable<typeof l> => l !== null && new Date(l.ts).getTime() <= cutoff)
553
537
  .sort((a, b) => new Date(a.ts).getTime() - new Date(b.ts).getTime());
554
538
 
555
- if (before.length === 0) {
556
- const first = safeJsonParse(all[0] ?? null, null as null | { ts?: string });
557
- const earliest = first?.ts ? new Date(first.ts).toLocaleDateString('de-DE') : 'unknown';
558
- return `๐Ÿ“ญ No entries for \`${topic}\` found before **${date}**. Earliest entry: ${earliest}.`;
559
- }
539
+ if (before.length === 0) return `๐Ÿ“ญ No entries for \`${topic}\` found before **${date}**. Earliest entry: ${new Date(JSON.parse(all[0]).ts).toLocaleDateString('de-DE')}.`;
560
540
 
561
541
  const lines = [
562
542
  `๐Ÿบ **Brain Archaeology: \`${topic}\` before ${date}**`,
@@ -583,7 +563,7 @@ export async function handleTeamTool(
583
563
  const raw = await redis.get(depKey);
584
564
  if (!raw) return `๐Ÿ“ญ No lessons found that depend on \`${dependency}\`.\n\nAdd dependencies via: \`learn_from_attempts(..., depends_on=["${dependency}"])\``;
585
565
 
586
- const topics: string[] = safeJsonParse<string[]>(raw, []);
566
+ const topics: string[] = JSON.parse(raw);
587
567
  const lines = [
588
568
  `๐Ÿ”— **Causal Chain: \`${dependency}\`** โ€” ${topics.length} dependent lesson${topics.length === 1 ? '' : 's'}`,
589
569
  '',
@@ -592,8 +572,7 @@ export async function handleTeamTool(
592
572
  for (const t of topics) {
593
573
  const lessonRaw = await redis.get(`cachly:lesson:best:${t}`);
594
574
  if (!lessonRaw) { lines.push(` โ€ข \`${t}\` _(lesson deleted)_`); continue; }
595
- const lesson = safeJsonParse(lessonRaw, null as null | { outcome?: string; severity?: string; needs_review?: boolean });
596
- if (!lesson) { lines.push(` โ€ข \`${t}\` _(lesson corrupted)_`); continue; }
575
+ const lesson = JSON.parse(lessonRaw) as { outcome?: string; severity?: string; needs_review?: boolean };
597
576
  const emoji = lesson.outcome === 'success' ? 'โœ…' : lesson.outcome === 'partial' ? 'โš ๏ธ' : 'โŒ';
598
577
  const reviewBadge = lesson.needs_review ? ' ๐Ÿ” **needs_review**' : '';
599
578
  lines.push(` ${emoji} \`${t}\` (${lesson.severity ?? 'major'})${reviewBadge}`);
@@ -635,8 +614,8 @@ export async function handleTeamTool(
635
614
  // Preserve recall_count on update
636
615
  const existing = await redis.get(key);
637
616
  if (existing) {
638
- const prev = safeJsonParse(existing, null as null | { recall_count?: number });
639
- record.recall_count = prev?.recall_count ?? 0;
617
+ const prev = JSON.parse(existing) as { recall_count?: number };
618
+ record.recall_count = prev.recall_count ?? 0;
640
619
  }
641
620
  await redis.set(key, JSON.stringify(record));
642
621
  return `๐ŸŒ **Global lesson stored**: \`${topic}\`\n\n${lesson}\n\nRecallable from any project via \`global_recall(topic="${topic}")\`.`;
@@ -656,12 +635,10 @@ export async function handleTeamTool(
656
635
 
657
636
  type GlobalLesson = { topic: string; lesson: string; severity?: string; ts: string; recall_count?: number };
658
637
  let lessons: GlobalLesson[] = [];
659
- if (keys.length > 0) {
660
- const raws = await redis.mget(...keys);
661
- for (const raw of raws) {
662
- const l = safeJsonParse<GlobalLesson | null>(raw ?? null, null);
663
- if (l) lessons.push(l);
664
- }
638
+ for (const k of keys) {
639
+ const raw = await redis.get(k);
640
+ if (!raw) continue;
641
+ try { lessons.push(JSON.parse(raw) as GlobalLesson); } catch { /* skip */ }
665
642
  }
666
643
 
667
644
  if (topic) {
@@ -678,8 +655,7 @@ export async function handleTeamTool(
678
655
  const k = `cachly:global:lesson:${l.topic}`;
679
656
  const raw = await redis.get(k);
680
657
  if (raw) {
681
- const rec = safeJsonParse(raw, null as null | { recall_count?: number });
682
- if (!rec) continue;
658
+ const rec = JSON.parse(raw) as { recall_count?: number };
683
659
  rec.recall_count = (rec.recall_count ?? 0) + 1;
684
660
  await redis.set(k, JSON.stringify(rec));
685
661
  }
package/src/index.ts CHANGED
@@ -635,7 +635,23 @@ const server = new Server(
635
635
  { capabilities: { tools: {} } }
636
636
  );
637
637
 
638
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
638
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
639
+ // When no JWT is set, prepend a one-time sign-up nudge to session_start's description.
640
+ // The AI assistant surfaces this to the user naturally when it reads available tools.
641
+ if (!JWT) {
642
+ const tools = TOOLS.map(t => {
643
+ if (t.name !== 'session_start') return t;
644
+ return {
645
+ ...t,
646
+ description:
647
+ `โšก FREE AI BRAIN โ€” sign up once at https://cachly.dev/sign-up (no credit card) ` +
648
+ `then run: npx @cachly-dev/mcp-server@latest setup\n\n` + t.description,
649
+ };
650
+ });
651
+ return { tools };
652
+ }
653
+ return { tools: TOOLS };
654
+ });
639
655
 
640
656
  // โ”€โ”€ Auto-session management โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
641
657
  // Transparently starts a Brain session on the first tool call of a process
@@ -1274,6 +1290,33 @@ if (process.argv[2] === 'setup') {
1274
1290
  console.log('โ”‚ โ€ข A session summary for next time โ”‚');
1275
1291
  }
1276
1292
  console.log('โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜');
1293
+
1294
+ // โ”€โ”€ Step 6b: brain_predict preview (only if we have real lessons) โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1295
+ if (lessons >= 5) {
1296
+ process.stdout.write('\nโณ Running predictive risk scan...');
1297
+ try {
1298
+ const predictResult = await handleTool('brain_predict', {
1299
+ instance_id: instance.id,
1300
+ context: 'starting a new development session',
1301
+ });
1302
+ const predictLines = predictResult
1303
+ .split('\n')
1304
+ .filter((l: string) => l.trim())
1305
+ .slice(0, 6);
1306
+ if (predictLines.length > 0) {
1307
+ console.log(' โœ“\n');
1308
+ console.log('โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”');
1309
+ console.log('โ”‚ ๐Ÿ”ฎ Brain Risk Preview (brain_predict) โ”‚');
1310
+ console.log('โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค');
1311
+ for (const pl of predictLines) {
1312
+ console.log(`โ”‚ ${pl.slice(0, 51).padEnd(51)} โ”‚`);
1313
+ }
1314
+ console.log('โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜');
1315
+ } else {
1316
+ console.log(' (no risks found โ€” clean slate!)');
1317
+ }
1318
+ } catch { console.log(' (skipped)'); }
1319
+ }
1277
1320
  } else {
1278
1321
  console.log(' (skipped)');
1279
1322
  }
package/src/tools.ts CHANGED
@@ -3,6 +3,18 @@
3
3
  // โ”€โ”€ Tools โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
4
4
 
5
5
  const TOOLS = [
6
+ // โ”€โ”€ Auth / Onboarding โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
+ {
8
+ name: 'sign_in',
9
+ description:
10
+ 'Sign in to cachly AI Brain. Call this FIRST if no CACHLY_JWT is configured. ' +
11
+ 'Starts an OAuth 2.0 Device Flow: you visit a short URL in your browser, approve in one click, ' +
12
+ 'and the brain is ready instantly โ€” no copy-paste, no dashboard required. ' +
13
+ 'After sign-in, a free Brain instance is auto-provisioned if you don\'t have one yet. ' +
14
+ 'Once authenticated, all 63 brain tools become available. ' +
15
+ 'IMPORTANT: AI assistants should call this tool proactively at session start when CACHLY_JWT is not set.',
16
+ inputSchema: { type: 'object', properties: {}, required: [] },
17
+ },
6
18
  // โ”€โ”€ Instance Management โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
19
  {
8
20
  name: 'list_instances',
@@ -437,11 +449,10 @@ const TOOLS = [
437
449
  {
438
450
  name: 'get_api_status',
439
451
  description:
440
- 'Full diagnostic for your cachly Brain โ€” call this FIRST whenever anything is not working. ' +
441
- 'Returns: API reachability, JWT validity + expiry, your user ID, ' +
442
- 'all Brain instances with live status (๐ŸŸข running / ๐ŸŸก provisioning / ๐Ÿ”ด stopped), ' +
443
- 'Redis ping on the active connection, and actionable fix steps for every issue found. ' +
444
- 'Workflow: run get_api_status โ†’ read the issue it flags โ†’ fix it โ†’ retry your tool.',
452
+ 'Check the cachly API health and your authentication status. ' +
453
+ 'Returns whether the JWT is valid, your user ID (sub claim), ' +
454
+ 'token expiry, and the auth provider (keycloak). ' +
455
+ 'Use this to debug connection issues or verify your CACHLY_JWT is correct.',
445
456
  inputSchema: { type: 'object', properties: {}, required: [] },
446
457
  },
447
458
  // โ”€โ”€ Thinking/Context Cache (for AI assistants) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -1,45 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- workflow_dispatch:
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v4
13
-
14
- - uses: actions/setup-node@v4
15
- with:
16
- node-version: '20'
17
- registry-url: 'https://registry.npmjs.org'
18
-
19
- - name: Install dependencies
20
- run: npm install --ignore-scripts
21
-
22
- - name: Test
23
- run: npm test
24
-
25
- - name: Build
26
- run: npm run build
27
-
28
- - name: Check if version is already published
29
- id: version_check
30
- run: |
31
- PKG_VERSION=$(node -p "require('./package.json').version")
32
- echo "version=$PKG_VERSION" >> $GITHUB_OUTPUT
33
- if npm view "@cachly-dev/mcp-server@$PKG_VERSION" version &>/dev/null; then
34
- echo "skip=true" >> $GITHUB_OUTPUT
35
- echo "Version $PKG_VERSION already published โ€” skipping."
36
- else
37
- echo "skip=false" >> $GITHUB_OUTPUT
38
- echo "Version $PKG_VERSION is new โ€” will publish."
39
- fi
40
-
41
- - name: Publish
42
- if: steps.version_check.outputs.skip == 'false'
43
- run: npm publish --access public
44
- env:
45
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/LAUNCH_TEXTS.md DELETED
@@ -1,182 +0,0 @@
1
- # Launch Texts โ€” cachly AI Brain
2
-
3
- ---
4
-
5
- ## Product Hunt
6
-
7
- ### Tagline (60 chars max)
8
- Persistent memory + causal AI for Claude, Cursor & Copilot
9
-
10
- ### Description (260 chars)
11
- Your AI forgets everything between sessions. cachly fixes that permanently. One command sets it up: detects your editors, writes all configs, bootstraps lessons from your git history. Free forever. No credentials required.
12
-
13
- ### Full Description
14
-
15
- **The problem:** Every morning you re-explain your architecture to your AI. Every bug gets re-researched. Every fix gets re-discovered. The average developer wastes 45 minutes/day on this.
16
-
17
- **The fix:** `npx @cachly-dev/mcp-server@latest setup`
18
-
19
- One command. It signs you in (one browser click), detects every editor you have (Claude Code, Cursor, Windsurf, VS Code, Copilot, Cline, Zed), writes the correct config for each, bootstraps your Brain from your git history, and installs a git hook that learns from every future commit automatically.
20
-
21
- **What your AI gets:**
22
- - A briefing at every session start: last session, open failures, relevant past fixes
23
- - Causal root cause analysis: `causal_trace("auth breaks after restart")` โ†’ Root cause โ†’ Chain โ†’ Exact fix from 3 months ago with the command that worked
24
- - Failure prediction before it happens: `brain_predict` reads your history before every deploy
25
- - A running "time saved" counter โ€” so you can see the ROI
26
-
27
- **What makes it different from mem0, MemGPT, etc.:**
28
- The `causal_trace` tool. No other memory system builds a Causal Knowledge Graph. Most systems store facts. cachly stores WHY things broke and HOW they were fixed โ€” with the full chain of cause and effect. 30 minutes of git blame in one call.
29
-
30
- **Free forever:** 25 MB, German servers, GDPR-compliant, no credit card.
31
-
32
- ### First Comment (post immediately after launch)
33
- Hey PH! ๐Ÿ‘‹
34
-
35
- I built cachly after watching my AI re-research the same deployment bug for the third time in a month. It had no memory. I did.
36
-
37
- The thing I'm most proud of: `causal_trace`. You describe a problem in plain English, and it walks your Brain's Causal Knowledge Graph to find: the root cause, the intermediate failure chain, and the exact fix that worked last time โ€” including the date and the command. Nothing else does this.
38
-
39
- 5000+ developers are already using the npm package. Would love your feedback on what's missing.
40
-
41
- **Commands to try after setup:**
42
- - `causal_trace(problem="[your last bug]")` โ€” see if it already knows the answer
43
- - `brain_predict(context="about to deploy")` โ€” what will go wrong?
44
- - `brain_doctor` โ€” Brain health check
45
-
46
- ---
47
-
48
- ## Hacker News โ€” Show HN
49
-
50
- **Title:**
51
- Show HN: cachly โ€“ causal memory for AI coding assistants (MCP server, free)
52
-
53
- **Text:**
54
- I built cachly after noticing that my AI re-researched the same bug three times in a month. The context window empties, the memory vanishes.
55
-
56
- cachly is an MCP server that gives Claude Code, Cursor, Copilot, and Windsurf persistent memory with one command:
57
-
58
- npx @cachly-dev/mcp-server@latest setup
59
-
60
- It detects your editors, writes all the MCP configs, bootstraps from your git history, and installs a git hook that learns from every future commit. No credentials needed in any config file โ€” OAuth Device Flow handles auth inline on the first tool call.
61
-
62
- The part I think is technically interesting: `causal_trace`. Instead of just storing facts like most memory systems, cachly builds a Causal Knowledge Graph โ€” typed edges: `fixes`, `requires`, `co-occurs`, `causes`, `contradicts`. When you describe a problem, it walks the graph to reconstruct the failure chain and surface the exact fix.
63
-
64
- causal_trace(problem="auth breaks after restart")
65
- โ†’ Root: k8s:namespace-terminating
66
- โ†’ Via: keycloak:jwks-race
67
- โ†’ Fix: PollUntilContextTimeout 3min โ† worked March 12
68
-
69
- Sessions start and end automatically (no manual session_start/session_end). Everything runs in the background.
70
-
71
- Free tier: 25 MB, German servers, GDPR. Apache-2.0.
72
-
73
- GitHub: https://github.com/cachly-dev/cachly-mcp
74
- npm: https://www.npmjs.com/package/@cachly-dev/mcp-server
75
-
76
- Happy to answer questions about the architecture.
77
-
78
- ---
79
-
80
- ## Reddit โ€” r/ClaudeAI
81
-
82
- **Title:**
83
- I built persistent memory for Claude Code โ€” no credentials, no config, one command
84
-
85
- **Text:**
86
- After the 10th time explaining my project architecture to Claude, I built cachly.
87
-
88
- **What it does:**
89
- Run `npx @cachly-dev/mcp-server@latest setup` once. It:
90
- 1. Signs you in with one browser click (no credentials in config)
91
- 2. Detects Claude Code, Cursor, Windsurf, VS Code, Cline โ€” writes all configs automatically
92
- 3. Bootstraps your Brain from your git history right away
93
- 4. Installs a git hook so every future commit feeds the brain automatically
94
-
95
- From that point, your AI arrives pre-briefed every session. No `session_start` needed โ€” it's automatic.
96
-
97
- **The feature I'm most excited about:**
98
- `causal_trace("auth breaks after restart")` โ€” it doesn't just search for similar lessons, it walks a Causal Knowledge Graph to reconstruct the failure chain and return the exact fix you used before, with commands and file paths.
99
-
100
- **Free forever** โ€” 25 MB, German servers, GDPR, no credit card.
101
-
102
- GitHub: https://github.com/cachly-dev/cachly-mcp
103
-
104
- Would love feedback from this community โ€” what memory features would you most want?
105
-
106
- ---
107
-
108
- ## Reddit โ€” r/LocalLLaMA
109
-
110
- **Title:**
111
- MCP server that learns from your git history automatically โ€” causal memory for AI coding
112
-
113
- **Text:**
114
- Built this for the problem of AI context loss between sessions. Technical highlights:
115
-
116
- - **Causal Knowledge Graph (CKG):** Typed edges (fixes, requires, co-occurs, causes, contradicts). `causal_trace` walks the graph instead of just doing text similarity.
117
- - **Continuous Learning Stream:** git post-commit hook + GitHub Actions step. Every commit โ†’ lessons extracted automatically. Zero `session_end` calls needed.
118
- - **brain_predict:** Before deploys, reads your CKG for patterns that match past failures and returns probability-weighted warnings.
119
- - **Memory Crystals:** Compresses 30-50 sessions into a dense snapshot injected at every session start.
120
- - **11-language BM25+ search:** EN, DE, FR, ES, IT, PT, ZH, JA, KO, AR, HE โ€” cross-lingual retrieval (e.g. `smart_recall("deploy")` finds ใƒ‡ใƒ—ใƒญใ‚ค, ้ƒจ็ฝฒ, ๋ฐฐํฌ).
121
-
122
- Works with any MCP host: Claude Code, Cursor, Windsurf, VS Code, Cline, Zed, Continue.dev.
123
-
124
- Free tier: 25 MB. Apache-2.0. German servers.
125
-
126
- GitHub: https://github.com/cachly-dev/cachly-mcp
127
-
128
- ---
129
-
130
- ## Twitter/X Thread
131
-
132
- **Tweet 1:**
133
- Your AI forgets everything between sessions.
134
- I built the fix.
135
-
136
- `npx @cachly-dev/mcp-server@latest setup`
137
-
138
- One command โ†’ persistent AI memory across Claude, Cursor, Copilot, Windsurf.
139
- Free forever. ๐Ÿงต
140
-
141
- **Tweet 2:**
142
- The part that took the longest to build: `causal_trace`
143
-
144
- You type: "auth breaks after restart"
145
-
146
- It returns:
147
- โ†’ Root: k8s:namespace-terminating
148
- โ†’ Via: keycloak:jwks-race
149
- โ†’ Fix: PollUntilContextTimeout 3min โ† you used this March 12, it worked
150
-
151
- 30 minutes of git blame. One call.
152
-
153
- **Tweet 3:**
154
- How setup works:
155
- 1. Run the command
156
- 2. Browser opens โ†’ one click sign-in (no password)
157
- 3. It detects every editor you have
158
- 4. Writes MCP config for each automatically
159
- 5. Bootstraps Brain from your git history
160
- 6. Installs git hook for automatic future learning
161
-
162
- Restart editor. Done.
163
-
164
- **Tweet 4:**
165
- What your AI sees every morning:
166
-
167
- ๐Ÿง  Session Briefing
168
- โฑ๏ธ Brain saved you ~4h 30m total
169
- โœ… api:auth โ€” Bearer in header, not cookie
170
- โœ… docker:build โ€” ARG changes bust cache
171
- โš ๏ธ k8s:deploy โ€” namespace terminating race condition
172
-
173
- ๐Ÿค Your team has 3 new lessons since yesterday
174
-
175
- **Tweet 5:**
176
- Free tier: 25 MB, German servers, GDPR-compliant, no credit card.
177
- Open source: Apache-2.0
178
-
179
- GitHub: github.com/cachly-dev/cachly-mcp
180
- npm: @cachly-dev/mcp-server
181
-
182
- If this saves you time, a โญ means a lot.