@cachly-dev/mcp-server 0.10.86 → 0.10.87

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/dist/src/index.js CHANGED
@@ -65,7 +65,7 @@ import { Redis } from 'ioredis';
65
65
  let API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
66
66
  let JWT = process.env.CACHLY_JWT ?? '';
67
67
  const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
68
- const CURRENT_VERSION = '0.10.86';
68
+ const CURRENT_VERSION = '0.10.87';
69
69
  // Max time to wait for a freshly-provisioned instance to become "running" before
70
70
  // giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
71
71
  // old hard-coded 25s caused spurious "instance_not_reachable" on first run.
@@ -109,7 +109,15 @@ async function autoProvisionInstance() {
109
109
  }).catch(() => { });
110
110
  }
111
111
  }
112
- catch { /* non-fatal — server unreachable */ }
112
+ catch {
113
+ // Network unreachable — fire telemetry so silent provision failures are visible in the funnel.
114
+ void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
115
+ method: 'POST',
116
+ headers: { 'Content-Type': 'application/json' },
117
+ body: JSON.stringify({ event: 'auto_provision_failed', status: 0, reason: 'network_error', version: CURRENT_VERSION }),
118
+ signal: AbortSignal.timeout(3000),
119
+ }).catch(() => { });
120
+ }
113
121
  return '';
114
122
  }
115
123
  async function resolveDefaultInstanceId() {
@@ -174,20 +182,24 @@ let _authHealAttemptedAt = 0;
174
182
  const AUTH_HEAL_COOLDOWN_MS = 60_000;
175
183
  // Surfaced in session_start / get_api_status so the user is never left guessing.
176
184
  let _authDegradedNotice = '';
177
- /** Persist a (possibly refreshed) API key to ~/.claude/mcp.json so restarts keep it. */
185
+ /** Persist a (possibly refreshed) API key to ~/.claude/mcp.json so restarts keep it.
186
+ * Also updates CACHLY_JWT in Cursor and Windsurf configs when they already have a cachly entry,
187
+ * so users who auth via one editor don't lose their session in the others.
188
+ */
178
189
  async function persistApiKeyToConfig(apiKey) {
179
190
  try {
180
191
  const { writeFile, mkdir, readFile } = await import('node:fs/promises');
181
192
  const { existsSync } = await import('node:fs');
182
193
  const { resolve, dirname } = await import('node:path');
183
- const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
194
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? process.env.APPDATA ?? '';
184
195
  if (!home)
185
196
  return;
186
- const configPath = resolve(home, '.claude', 'mcp.json');
197
+ // Primary config: create or update ~/.claude/mcp.json
198
+ const claudePath = resolve(home, '.claude', 'mcp.json');
187
199
  let cfg = {};
188
- if (existsSync(configPath)) {
200
+ if (existsSync(claudePath)) {
189
201
  try {
190
- cfg = JSON.parse(await readFile(configPath, 'utf-8'));
202
+ cfg = JSON.parse(await readFile(claudePath, 'utf-8'));
191
203
  }
192
204
  catch { /* corrupt — start fresh */ }
193
205
  }
@@ -203,8 +215,28 @@ async function persistApiKeyToConfig(apiKey) {
203
215
  env: { CACHLY_JWT: apiKey },
204
216
  };
205
217
  }
206
- await mkdir(dirname(configPath), { recursive: true });
207
- await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
218
+ await mkdir(dirname(claudePath), { recursive: true });
219
+ await writeFile(claudePath, JSON.stringify(cfg, null, 2), 'utf-8');
220
+ // Secondary configs: update CACHLY_JWT wherever cachly is already configured.
221
+ // Never create from scratch — only update existing entries (the user set these up).
222
+ const secondaryPaths = [
223
+ resolve(home, '.cursor', 'mcp.json'),
224
+ resolve(home, '.codeium', 'windsurf', 'mcp_config.json'),
225
+ ];
226
+ await Promise.allSettled(secondaryPaths.map(async (p) => {
227
+ if (!existsSync(p))
228
+ return;
229
+ try {
230
+ const raw = await readFile(p, 'utf-8');
231
+ const sc = JSON.parse(raw);
232
+ const entry = sc?.mcpServers?.['cachly'];
233
+ if (entry?.env) {
234
+ entry.env['CACHLY_JWT'] = apiKey;
235
+ await writeFile(p, JSON.stringify(sc, null, 2), 'utf-8');
236
+ }
237
+ }
238
+ catch { /* corrupt or unwriteable — skip */ }
239
+ }));
208
240
  }
209
241
  catch { /* non-critical — never break a tool call on a filesystem error */ }
210
242
  }
@@ -220,24 +252,27 @@ async function persistInstanceIdToConfig(instanceId) {
220
252
  const { writeFile, readFile } = await import('node:fs/promises');
221
253
  const { existsSync } = await import('node:fs');
222
254
  const { resolve } = await import('node:path');
223
- const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
255
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? process.env.APPDATA ?? '';
224
256
  if (!home)
225
257
  return;
226
- const configPath = resolve(home, '.claude', 'mcp.json');
227
- if (!existsSync(configPath))
228
- return;
229
- let cfg = {};
230
- try {
231
- cfg = JSON.parse(await readFile(configPath, 'utf-8'));
232
- }
233
- catch {
234
- return;
235
- }
236
- const entry = cfg.mcpServers?.['cachly'];
237
- if (entry?.env) {
238
- entry.env['CACHLY_BRAIN_INSTANCE_ID'] = instanceId;
239
- await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
240
- }
258
+ const configPaths = [
259
+ resolve(home, '.claude', 'mcp.json'),
260
+ resolve(home, '.cursor', 'mcp.json'),
261
+ resolve(home, '.codeium', 'windsurf', 'mcp_config.json'),
262
+ ];
263
+ await Promise.allSettled(configPaths.map(async (p) => {
264
+ if (!existsSync(p))
265
+ return;
266
+ try {
267
+ const cfg = JSON.parse(await readFile(p, 'utf-8'));
268
+ const entry = cfg?.mcpServers?.['cachly'];
269
+ if (entry?.env) {
270
+ entry.env['CACHLY_BRAIN_INSTANCE_ID'] = instanceId;
271
+ await writeFile(p, JSON.stringify(cfg, null, 2), 'utf-8');
272
+ }
273
+ }
274
+ catch { /* corrupt or unwriteable — skip */ }
275
+ }));
241
276
  }
242
277
  catch { /* non-critical */ }
243
278
  }
@@ -708,6 +743,7 @@ async function handleTool(name, args) {
708
743
  return handleTool(name, args);
709
744
  }
710
745
  if (result === 'expired') {
746
+ sendFunnelEvent('device_flow_failed', { reason: 'timeout' });
711
747
  _deviceFlow = null;
712
748
  return '⌛ **Authentication timed out.** Please call any tool again to restart the sign-in flow.';
713
749
  }
@@ -828,12 +864,26 @@ async function handleTool(name, args) {
828
864
  // Append bootstrap summary to the session_start briefing.
829
865
  // brainResult is always a string here (handleBrainTool returns string for session_start).
830
866
  const bootstrapText = String(gitBootstrap);
831
- if (bootstrapText || starterText) {
832
- return String(brainResult) + '\n---\n' + bootstrapText + starterText;
867
+ // Show a concrete count so the user knows what was seeded (the WOW moment).
868
+ const totalSeeded = _lastBrainFromGitCounts?.total ?? 0;
869
+ const seedBanner = totalSeeded > 0
870
+ ? `\n✅ **Brain bootstrapped from git — ${totalSeeded} lesson${totalSeeded === 1 ? '' : 's'} loaded.** Your AI already knows your history.`
871
+ : '';
872
+ if (bootstrapText || starterText || seedBanner) {
873
+ return String(brainResult) + seedBanner + '\n---\n' + bootstrapText + starterText;
833
874
  }
834
875
  }
835
876
  }
836
- catch { /* git bootstrap errors must never break session_start */ }
877
+ catch (gitErr) {
878
+ // Bootstrap failed — fire telemetry so we can investigate and tell the user
879
+ // what happened (don't leave them wondering why the brain is empty).
880
+ sendFunnelEvent('brain_from_git_failed', { ...telemetryExtra,
881
+ reason: gitErr instanceof Error ? gitErr.message.slice(0, 120) : 'unknown' });
882
+ return String(brainResult) +
883
+ '\n\n> ⚠️ **Auto-bootstrap from git history failed** — your Brain is live but starts empty.\n' +
884
+ '> Run `brain_from_git(instance_id="' + instanceId + '", repo_path="' + String(args.workspace_path) + '")` manually to seed it.\n' +
885
+ '> Or try `brain_seed_starter` to load 16 universal engineering lessons instantly.';
886
+ }
837
887
  }
838
888
  }
839
889
  else if (name === 'session_end') {