@cachly-dev/mcp-server 0.10.97 → 0.10.99

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.
@@ -11,3 +11,11 @@ export declare function mergeMcpConfig(configPath: string, apiKey: string, insta
11
11
  existsSync: (p: string) => boolean;
12
12
  }): Promise<string>;
13
13
  export declare function buildClaudeMdBlock(instanceId: string): string;
14
+ /**
15
+ * Schreibt das Brain-Protokoll in alle relevanten Instruction-Dateien:
16
+ * - CLAUDE.md
17
+ * - AGENTS.md
18
+ * - .github/copilot-instructions.md
19
+ * Idempotent, Marker-basiert.
20
+ */
21
+ export declare function writeInstructions(projectDir: string, instanceId: string): Promise<Record<string, 'written' | 'updated' | 'appended'>>;
package/dist/src/index.js CHANGED
@@ -3,6 +3,7 @@ import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal, read
3
3
  import { handleTcoTool } from './handlers/tco.js';
4
4
  import { notify } from './notifier.js';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { createRequire } from 'node:module';
6
7
  // True only when this file is the entry point (not imported by tests or other modules).
7
8
  const _isMain = process.argv[1] != null &&
8
9
  (process.argv[1] === fileURLToPath(import.meta.url) ||
@@ -65,7 +66,26 @@ import { Redis } from 'ioredis';
65
66
  let API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
66
67
  let JWT = process.env.CACHLY_JWT ?? '';
67
68
  const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
68
- const CURRENT_VERSION = '0.10.88';
69
+ // Resolve the package version at runtime from package.json so the telemetry
70
+ // `version` field always matches the published npm version. A hardcoded constant
71
+ // silently drifted (0.10.88 vs published 0.10.98), making release adoption
72
+ // impossible to track. Paths cover both prod (dist/src/index.js → ../../) and
73
+ // dev (src/index.ts → ../). Falls back to a literal if resolution fails.
74
+ const CURRENT_VERSION = (() => {
75
+ try {
76
+ const req = createRequire(import.meta.url);
77
+ for (const rel of ['../../package.json', '../package.json']) {
78
+ try {
79
+ const pkg = req(rel);
80
+ if (pkg.name === '@cachly-dev/mcp-server' && pkg.version)
81
+ return pkg.version;
82
+ }
83
+ catch { /* try next candidate */ }
84
+ }
85
+ }
86
+ catch { /* fall through to literal */ }
87
+ return '0.10.99';
88
+ })();
69
89
  // Max time to wait for a freshly-provisioned instance to become "running" before
70
90
  // giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
71
91
  // old hard-coded 25s caused spurious "instance_not_reachable" on first run.
@@ -336,20 +356,26 @@ async function selfHealAuth() {
336
356
  }
337
357
  let _deviceFlow = null;
338
358
  async function startDeviceFlow() {
339
- // Try the API proxy first (more reliable than direct Keycloak endpoint)
359
+ // Try the cachly API device flow first. It returns a long-lived API key directly
360
+ // on poll (no separate JWT→key exchange needed) and is served at the ROOT paths
361
+ // /auth/device + /auth/device/token (NOT under /api/v1). Requires VALKEY_L1_URL on
362
+ // the API; when unset the API returns 503 and we fall through to Keycloak below.
340
363
  try {
341
- const res = await fetch(`${API_URL}/api/v1/auth/device/code`, {
364
+ const res = await fetch(`${API_URL}/auth/device`, {
342
365
  method: 'POST',
343
366
  headers: { 'Content-Type': 'application/json' },
344
- body: JSON.stringify({ client_id: 'cachly-mcp-cli' }),
367
+ body: JSON.stringify({}),
345
368
  signal: AbortSignal.timeout(8000),
346
369
  });
347
370
  if (res.ok) {
348
371
  const data = await res.json();
372
+ // Append the user_code so the web page (cachly.dev/device?code=…) auto-submits.
373
+ const base = data.verification_uri || 'https://cachly.dev/device';
374
+ const verifyUrl = `${base}${base.includes('?') ? '&' : '?'}code=${encodeURIComponent(data.user_code)}`;
349
375
  return {
350
376
  deviceCode: data.device_code,
351
377
  userCode: data.user_code,
352
- verifyUrl: data.verification_uri,
378
+ verifyUrl,
353
379
  pollInterval: (data.interval ?? 5) * 1000,
354
380
  deadline: Date.now() + 10 * 60 * 1000,
355
381
  polling: false,
@@ -385,12 +411,12 @@ async function startDeviceFlow() {
385
411
  async function pollDeviceFlow(flow) {
386
412
  if (Date.now() > flow.deadline)
387
413
  return 'expired';
388
- // Try API proxy first, then direct Keycloak as fallback
414
+ // Try the cachly API device flow first, then direct Keycloak as fallback.
389
415
  const tryProxy = async () => {
390
- const res = await fetch(`${API_URL}/api/v1/auth/device/token`, {
416
+ const res = await fetch(`${API_URL}/auth/device/token`, {
391
417
  method: 'POST',
392
418
  headers: { 'Content-Type': 'application/json' },
393
- body: JSON.stringify({ device_code: flow.deviceCode, client_id: 'cachly-mcp-cli' }),
419
+ body: JSON.stringify({ device_code: flow.deviceCode }),
394
420
  signal: AbortSignal.timeout(8000),
395
421
  });
396
422
  if (!res.ok)
@@ -783,7 +809,7 @@ async function handleTool(name, args) {
783
809
  '',
784
810
  'After sign-in: call **any tool again** — your Brain activates instantly.',
785
811
  '',
786
- '✨ Free forever · No credit card · 120 MCP tools · GDPR · EU servers',
812
+ '✨ Free forever · No credit card · 121 MCP tools · GDPR · EU servers',
787
813
  ].join('\n');
788
814
  }
789
815
  // Device flow unavailable (network issue) — fall back to manual setup
@@ -1378,25 +1404,43 @@ Report predicted failure risks to the user before proceeding.
1378
1404
  Never commit code that does not compile. Run \`tsc --noEmit\` / \`go build ./...\` before every commit.
1379
1405
  ${CLAUDE_MD_MARKER_END}`;
1380
1406
  }
1381
- async function writeClaudeMd(projectDir, instanceId) {
1382
- const { writeFile, appendFile, readFile } = await import('node:fs/promises');
1407
+ /**
1408
+ * Schreibt das Brain-Protokoll in alle relevanten Instruction-Dateien:
1409
+ * - CLAUDE.md
1410
+ * - AGENTS.md
1411
+ * - .github/copilot-instructions.md
1412
+ * Idempotent, Marker-basiert.
1413
+ */
1414
+ export async function writeInstructions(projectDir, instanceId) {
1415
+ const { writeFile, appendFile, readFile, mkdir } = await import('node:fs/promises');
1383
1416
  const { existsSync } = await import('node:fs');
1384
- const { resolve } = await import('node:path');
1385
- const claudeMdPath = resolve(projectDir, 'CLAUDE.md');
1417
+ const { resolve, dirname } = await import('node:path');
1418
+ const files = [
1419
+ resolve(projectDir, 'CLAUDE.md'),
1420
+ resolve(projectDir, 'AGENTS.md'),
1421
+ resolve(projectDir, '.github', 'copilot-instructions.md'),
1422
+ ];
1386
1423
  const block = '\n' + buildClaudeMdBlock(instanceId) + '\n';
1387
- if (existsSync(claudeMdPath)) {
1388
- const existing = await readFile(claudeMdPath, 'utf-8');
1389
- if (existing.includes(CLAUDE_MD_MARKER_START)) {
1390
- // Idempotent update: replace existing block (new instance-id, refreshed content)
1391
- const updated = existing.replace(new RegExp(`${CLAUDE_MD_MARKER_START}[\\s\\S]*?${CLAUDE_MD_MARKER_END}`), buildClaudeMdBlock(instanceId));
1392
- await writeFile(claudeMdPath, updated, 'utf-8');
1393
- return 'updated';
1394
- }
1395
- await appendFile(claudeMdPath, block, 'utf-8');
1396
- return 'appended';
1397
- }
1398
- await writeFile(claudeMdPath, block.trimStart(), 'utf-8');
1399
- return 'written';
1424
+ const results = {};
1425
+ for (const file of files) {
1426
+ await mkdir(dirname(file), { recursive: true });
1427
+ if (existsSync(file)) {
1428
+ const existing = await readFile(file, 'utf-8');
1429
+ if (existing.includes(CLAUDE_MD_MARKER_START)) {
1430
+ // Idempotent update: replace existing block
1431
+ const updated = existing.replace(new RegExp(`${CLAUDE_MD_MARKER_START}[\s\S]*?${CLAUDE_MD_MARKER_END}`), buildClaudeMdBlock(instanceId));
1432
+ await writeFile(file, updated, 'utf-8');
1433
+ results[file] = 'updated';
1434
+ continue;
1435
+ }
1436
+ await appendFile(file, block, 'utf-8');
1437
+ results[file] = 'appended';
1438
+ continue;
1439
+ }
1440
+ await writeFile(file, block.trimStart(), 'utf-8');
1441
+ results[file] = 'written';
1442
+ }
1443
+ return results;
1400
1444
  }
1401
1445
  // ── CLI: cachly digest ────────────────────────────────────────────────────────
1402
1446
  // Usage: npx @cachly-dev/mcp-server@latest digest
@@ -2212,7 +2256,7 @@ if (!process.argv[2] && process.stdout.isTTY) {
2212
2256
  console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest upgrade\x1b[0m ← Check for updates');
2213
2257
  console.log('');
2214
2258
  console.log(' \x1b[90mWorks with: Claude Code · Cursor · Windsurf · GitHub Copilot · Cline · Zed\x1b[0m');
2215
- console.log(' \x1b[90mFree forever · GDPR · German servers · 120 MCP tools\x1b[0m');
2259
+ console.log(' \x1b[90mFree forever · GDPR · German servers · 121 MCP tools\x1b[0m');
2216
2260
  console.log('');
2217
2261
  process.exit(0);
2218
2262
  }
@@ -2274,10 +2318,15 @@ if (process.argv[2] === 'init') {
2274
2318
  else {
2275
2319
  console.log(`\n✓ Already configured: ${configFile} (no change)`);
2276
2320
  }
2277
- // Always write CLAUDE.md (idempotent — safe to run multiple times)
2278
- const result = await writeClaudeMd(projectDir, instanceId);
2279
- const action = result === 'updated' ? '✅ Updated' : result === 'appended' ? '✅ Appended to' : '✅ Written';
2280
- console.log(`${action}: CLAUDE.md`);
2321
+ // Always write the brain protocol to all instruction files (idempotent —
2322
+ // safe to run multiple times). Covers CLAUDE.md, AGENTS.md and Copilot so any
2323
+ // agent the user runs arrives pre-briefed.
2324
+ const results = await writeInstructions(projectDir, instanceId);
2325
+ for (const [path, action] of Object.entries(results)) {
2326
+ const verb = action === 'updated' ? '✅ Updated' : action === 'appended' ? '✅ Appended to' : '✅ Written';
2327
+ const name = path.split(/[\\/]/).pop() ?? path;
2328
+ console.log(`${verb}: ${name}`);
2329
+ }
2281
2330
  // ── CLS Phase 4: Auto-install git post-commit hook ─────────────────────────
2282
2331
  try {
2283
2332
  const { existsSync } = await import('node:fs');
@@ -3028,10 +3077,12 @@ if (process.argv[2] === 'setup' || _isAutopilotCli) {
3028
3077
  console.log(`⚠️ Could not write ~/.claude/mcp.json: ${reason}`);
3029
3078
  configWriteFailures.push({ file: '~/.claude/mcp.json', reason });
3030
3079
  }
3031
- // ── Step 5: CLAUDE.md (always — idempotent) ───────────────────────────────
3032
- const mdResult = await writeClaudeMd(cwd, instance.id);
3033
- const mdLabel = mdResult === 'updated' ? '✅ Updated' : mdResult === 'appended' ? '✅ Appended to' : '✅ Written';
3034
- console.log(`${mdLabel}: CLAUDE.md\n`);
3080
+ // ── Step 5: Instructions für alle Vendors (immer, idempotent) ─────────────
3081
+ const instrResults = await writeInstructions(cwd, instance.id);
3082
+ for (const [file, res] of Object.entries(instrResults)) {
3083
+ const label = res === 'updated' ? '✅ Updated' : res === 'appended' ? '✅ Appended to' : '✅ Written';
3084
+ console.log(`${label}: ${file}`);
3085
+ }
3035
3086
  // ── Step 5b: Bootstrap Brain from git history ─────────────────────────────
3036
3087
  // Pre-populates the Brain with real lessons so the first session_start shows
3037
3088
  // actual project knowledge instead of an empty brain.