@cachly-dev/mcp-server 0.10.76 → 0.10.78
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 +40 -0
- package/dist/src/handlers/brain.js +17 -1
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/share.d.ts +6 -0
- package/dist/src/handlers/share.js +256 -0
- package/dist/src/handlers/share.js.map +1 -0
- package/dist/src/index.js +106 -36
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +74 -0
- package/dist/src/tools.js +42 -0
- package/dist/src/tools.js.map +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { safeJsonParse } from '../utils.js';
|
|
2
|
+
export const SHARE_TOOL_NAMES = new Set([
|
|
3
|
+
'brain_share', 'brain_import',
|
|
4
|
+
]);
|
|
5
|
+
async function scanAllLessons(redis, topicFilter) {
|
|
6
|
+
const keys = [];
|
|
7
|
+
const stream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 200 });
|
|
8
|
+
await new Promise((res, rej) => {
|
|
9
|
+
stream.on('data', (batch) => keys.push(...batch));
|
|
10
|
+
stream.on('end', res);
|
|
11
|
+
stream.on('error', rej);
|
|
12
|
+
});
|
|
13
|
+
const lessons = [];
|
|
14
|
+
for (const key of keys.slice(0, 500)) {
|
|
15
|
+
const raw = await redis.get(key);
|
|
16
|
+
if (!raw)
|
|
17
|
+
continue;
|
|
18
|
+
const lesson = safeJsonParse(raw, null);
|
|
19
|
+
if (!lesson || !lesson.topic)
|
|
20
|
+
continue;
|
|
21
|
+
if (topicFilter && topicFilter.length > 0) {
|
|
22
|
+
const matches = topicFilter.some(f => lesson.topic.includes(f));
|
|
23
|
+
if (!matches)
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Strip fields that should not be shared (internal audit trails, etc.)
|
|
27
|
+
lessons.push({
|
|
28
|
+
topic: lesson.topic,
|
|
29
|
+
outcome: lesson.outcome,
|
|
30
|
+
...(lesson.what_worked ? { what_worked: lesson.what_worked } : {}),
|
|
31
|
+
...(lesson.what_failed ? { what_failed: lesson.what_failed } : {}),
|
|
32
|
+
...(lesson.ctx ? { ctx: lesson.ctx } : {}),
|
|
33
|
+
...(lesson.tags?.length ? { tags: lesson.tags } : {}),
|
|
34
|
+
...(lesson.author ? { author: lesson.author } : {}),
|
|
35
|
+
...(lesson.service ? { service: lesson.service } : {}),
|
|
36
|
+
confidence: lesson.confidence ?? 1.0,
|
|
37
|
+
ts: lesson.ts,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return lessons;
|
|
41
|
+
}
|
|
42
|
+
export async function handleShareTool(name, args, getConnection, apiFetch) {
|
|
43
|
+
switch (name) {
|
|
44
|
+
// ── brain_share ────────────────────────────────────────────────────────────
|
|
45
|
+
case 'brain_share': {
|
|
46
|
+
const { instance_id, title = 'My Brain Snapshot', description = '', topic_filter, visibility = 'unlisted', max_lessons = 100, dry_run = false, } = args;
|
|
47
|
+
if (!instance_id)
|
|
48
|
+
return '⚠️ `brain_share` requires `instance_id`.';
|
|
49
|
+
const redis = await getConnection(instance_id);
|
|
50
|
+
const topicFilter = Array.isArray(topic_filter) && topic_filter.length > 0 ? topic_filter : undefined;
|
|
51
|
+
const allLessons = await scanAllLessons(redis, topicFilter);
|
|
52
|
+
const lessons = allLessons.slice(0, max_lessons);
|
|
53
|
+
if (lessons.length === 0) {
|
|
54
|
+
return [
|
|
55
|
+
`📤 **brain_share** — nothing to share yet`,
|
|
56
|
+
``,
|
|
57
|
+
`No lessons found in Brain \`${instance_id}\`${topicFilter ? ` matching topics: ${topicFilter.join(', ')}` : ''}.`,
|
|
58
|
+
``,
|
|
59
|
+
`Store lessons first with \`learn_from_attempts(topic="...", what_worked="...")\`.`,
|
|
60
|
+
].join('\n');
|
|
61
|
+
}
|
|
62
|
+
if (dry_run) {
|
|
63
|
+
const sample = lessons.slice(0, 5).map(l => {
|
|
64
|
+
const icon = l.outcome === 'success' ? '✅' : l.outcome === 'failure' ? '❌' : '⚠️';
|
|
65
|
+
return ` ${icon} \`${l.topic}\` — ${(l.what_worked ?? l.what_failed ?? '').slice(0, 80)}`;
|
|
66
|
+
});
|
|
67
|
+
return [
|
|
68
|
+
`📤 **brain_share (DRY RUN)** — would share ${lessons.length} lesson${lessons.length !== 1 ? 's' : ''}`,
|
|
69
|
+
``,
|
|
70
|
+
`**Title:** ${title}`,
|
|
71
|
+
`**Visibility:** ${visibility}`,
|
|
72
|
+
topicFilter ? `**Topic filter:** ${topicFilter.join(', ')}` : '',
|
|
73
|
+
``,
|
|
74
|
+
`**Sample lessons (first 5):**`,
|
|
75
|
+
...sample,
|
|
76
|
+
lessons.length > 5 ? ` _...and ${lessons.length - 5} more_` : '',
|
|
77
|
+
``,
|
|
78
|
+
`_Remove \`dry_run=true\` to create the public share._`,
|
|
79
|
+
].filter(l => l !== '').join('\n');
|
|
80
|
+
}
|
|
81
|
+
// POST to API
|
|
82
|
+
let shareId = null;
|
|
83
|
+
let shareUrl = null;
|
|
84
|
+
try {
|
|
85
|
+
const result = await apiFetch('/api/v1/brains/share', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
88
|
+
body: JSON.stringify({ title, description, visibility, lessons }),
|
|
89
|
+
});
|
|
90
|
+
shareId = result.share_id ?? null;
|
|
91
|
+
if (shareId)
|
|
92
|
+
shareUrl = `https://cachly.dev/brain/share/${shareId}`;
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
// API endpoint may not be live yet — store locally and return JSON
|
|
96
|
+
const err = e.message ?? '';
|
|
97
|
+
if (err.includes('404') || err.includes('not found') || err.includes('ECONNREFUSED')) {
|
|
98
|
+
const exportJson = JSON.stringify({ title, description, visibility, lessons, exported_at: new Date().toISOString() }, null, 2);
|
|
99
|
+
return [
|
|
100
|
+
`📤 **brain_share** — exported ${lessons.length} lesson${lessons.length !== 1 ? 's' : ''}`,
|
|
101
|
+
``,
|
|
102
|
+
`⚠️ The public share API is not yet available. Here is your portable snapshot:`,
|
|
103
|
+
``,
|
|
104
|
+
`\`\`\`json`,
|
|
105
|
+
exportJson.slice(0, 3000) + (exportJson.length > 3000 ? '\n... (truncated)' : ''),
|
|
106
|
+
`\`\`\``,
|
|
107
|
+
``,
|
|
108
|
+
`_To import this on another Brain, use \`brain_import\` with the JSON payload._`,
|
|
109
|
+
].join('\n');
|
|
110
|
+
}
|
|
111
|
+
return `❌ brain_share failed: ${e.message}`;
|
|
112
|
+
}
|
|
113
|
+
const sample = lessons.slice(0, 5).map(l => {
|
|
114
|
+
const icon = l.outcome === 'success' ? '✅' : l.outcome === 'failure' ? '❌' : '⚠️';
|
|
115
|
+
return ` ${icon} \`${l.topic}\``;
|
|
116
|
+
});
|
|
117
|
+
// Record provenance
|
|
118
|
+
try {
|
|
119
|
+
const prov = JSON.stringify({ share_id: shareId, title, visibility, lesson_count: lessons.length, created_at: new Date().toISOString() });
|
|
120
|
+
await redis.rpush('cachly:brain:shares', prov);
|
|
121
|
+
await redis.ltrim('cachly:brain:shares', -20, -1);
|
|
122
|
+
}
|
|
123
|
+
catch { /* non-critical */ }
|
|
124
|
+
return [
|
|
125
|
+
`📤 **brain_share** — ${lessons.length} lesson${lessons.length !== 1 ? 's' : ''} shared`,
|
|
126
|
+
``,
|
|
127
|
+
`**Share ID:** \`${shareId}\``,
|
|
128
|
+
`**URL:** ${shareUrl}`,
|
|
129
|
+
`**Title:** ${title}`,
|
|
130
|
+
`**Visible:** ${visibility}`,
|
|
131
|
+
``,
|
|
132
|
+
`**Sample lessons:**`,
|
|
133
|
+
...sample,
|
|
134
|
+
lessons.length > 5 ? ` _...and ${lessons.length - 5} more_` : '',
|
|
135
|
+
``,
|
|
136
|
+
`**Anyone can import this Brain with:**`,
|
|
137
|
+
`\`brain_import(instance_id="<their-id>", share_id="${shareId}")\``,
|
|
138
|
+
``,
|
|
139
|
+
`_To list all your shares, run \`brain_share_list\` (coming soon)._`,
|
|
140
|
+
].filter(l => l !== '').join('\n');
|
|
141
|
+
}
|
|
142
|
+
// ── brain_import ───────────────────────────────────────────────────────────
|
|
143
|
+
case 'brain_import': {
|
|
144
|
+
const { instance_id, share_id, topic_prefix = '', min_confidence = 0.0, dry_run = false, overwrite = false, } = args;
|
|
145
|
+
if (!instance_id)
|
|
146
|
+
return '⚠️ `brain_import` requires `instance_id`.';
|
|
147
|
+
if (!share_id)
|
|
148
|
+
return '⚠️ `brain_import` requires `share_id`. Get one from `brain_share`.';
|
|
149
|
+
// Extract share ID from URL if passed as URL
|
|
150
|
+
const cleanShareId = share_id.replace(/^.*\/brain\/share\//, '').trim();
|
|
151
|
+
// Fetch the public share
|
|
152
|
+
let payload;
|
|
153
|
+
try {
|
|
154
|
+
payload = await apiFetch(`/api/v1/brains/share/${cleanShareId}`);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
const err = e.message ?? '';
|
|
158
|
+
if (err.includes('404') || err.includes('not found')) {
|
|
159
|
+
return [
|
|
160
|
+
`❌ **brain_import** — share not found`,
|
|
161
|
+
``,
|
|
162
|
+
`Share ID \`${cleanShareId}\` does not exist or has been removed.`,
|
|
163
|
+
``,
|
|
164
|
+
`Ask the Brain owner to re-run \`brain_share\` and share the new ID with you.`,
|
|
165
|
+
].join('\n');
|
|
166
|
+
}
|
|
167
|
+
return `❌ brain_import failed: ${e.message}`;
|
|
168
|
+
}
|
|
169
|
+
const lessons = payload.lessons ?? [];
|
|
170
|
+
if (lessons.length === 0) {
|
|
171
|
+
return `⚠️ Share \`${cleanShareId}\` exists but contains no lessons.`;
|
|
172
|
+
}
|
|
173
|
+
const redis = await getConnection(instance_id);
|
|
174
|
+
let imported = 0;
|
|
175
|
+
let skipped = 0;
|
|
176
|
+
let conflicts = 0;
|
|
177
|
+
const importLog = [];
|
|
178
|
+
for (const lesson of lessons) {
|
|
179
|
+
if ((lesson.confidence ?? 1.0) < min_confidence) {
|
|
180
|
+
skipped++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const topic = topic_prefix ? `${topic_prefix}:${lesson.topic}` : lesson.topic;
|
|
184
|
+
const bestKey = `cachly:lesson:best:${topic}`;
|
|
185
|
+
// Check for existing lesson
|
|
186
|
+
if (!overwrite) {
|
|
187
|
+
const existing = await redis.get(bestKey);
|
|
188
|
+
if (existing) {
|
|
189
|
+
conflicts++;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const lessonToStore = JSON.stringify({
|
|
194
|
+
...lesson,
|
|
195
|
+
topic,
|
|
196
|
+
imported_from: cleanShareId,
|
|
197
|
+
imported_at: new Date().toISOString(),
|
|
198
|
+
});
|
|
199
|
+
if (!dry_run) {
|
|
200
|
+
await redis.set(bestKey, lessonToStore);
|
|
201
|
+
const listKey = `cachly:lessons:${topic}`;
|
|
202
|
+
await redis.rpush(listKey, lessonToStore);
|
|
203
|
+
await redis.ltrim(listKey, -100, -1);
|
|
204
|
+
await redis.expire(listKey, 90 * 86400);
|
|
205
|
+
}
|
|
206
|
+
imported++;
|
|
207
|
+
if (importLog.length < 6) {
|
|
208
|
+
const icon = lesson.outcome === 'success' ? '✅' : lesson.outcome === 'failure' ? '❌' : '⚠️';
|
|
209
|
+
importLog.push(` ${icon} \`${topic}\` — ${(lesson.what_worked ?? lesson.what_failed ?? '').slice(0, 80)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Record import provenance
|
|
213
|
+
if (!dry_run && imported > 0) {
|
|
214
|
+
try {
|
|
215
|
+
const prov = JSON.stringify({
|
|
216
|
+
share_id: cleanShareId, title: payload.title,
|
|
217
|
+
imported: imported, skipped, conflicts,
|
|
218
|
+
topic_prefix, imported_at: new Date().toISOString(),
|
|
219
|
+
});
|
|
220
|
+
await redis.rpush('cachly:brain:imports', prov);
|
|
221
|
+
await redis.ltrim('cachly:brain:imports', -20, -1);
|
|
222
|
+
}
|
|
223
|
+
catch { /* non-critical */ }
|
|
224
|
+
}
|
|
225
|
+
const dryTag = dry_run ? ' (DRY RUN — no writes)' : '';
|
|
226
|
+
const shareTitle = payload.title ?? cleanShareId;
|
|
227
|
+
const lines = [
|
|
228
|
+
`📥 **brain_import${dryTag}** — from "${shareTitle}"`,
|
|
229
|
+
``,
|
|
230
|
+
`**Share ID:** \`${cleanShareId}\``,
|
|
231
|
+
`**Lessons imported:** ${imported}`,
|
|
232
|
+
...(skipped > 0 ? [`**Skipped (low confidence):** ${skipped}`] : []),
|
|
233
|
+
...(conflicts > 0 ? [`**Skipped (already exist):** ${conflicts} _(pass \`overwrite=true\` to replace)_`] : []),
|
|
234
|
+
``,
|
|
235
|
+
];
|
|
236
|
+
if (importLog.length > 0) {
|
|
237
|
+
lines.push(`**Sample lessons imported:**`, ...importLog);
|
|
238
|
+
if (imported > 6)
|
|
239
|
+
lines.push(` _...and ${imported - 6} more_`);
|
|
240
|
+
}
|
|
241
|
+
if (imported > 0 && !dry_run) {
|
|
242
|
+
lines.push(``, `✅ Brain updated — use \`smart_recall(query="...")\` to query the new knowledge.`);
|
|
243
|
+
}
|
|
244
|
+
else if (dry_run) {
|
|
245
|
+
lines.push(``, `_Remove \`dry_run=true\` to actually import these lessons._`);
|
|
246
|
+
}
|
|
247
|
+
else if (imported === 0 && conflicts > 0) {
|
|
248
|
+
lines.push(``, `ℹ️ All lessons already exist in your Brain. Pass \`overwrite=true\` to update them.`);
|
|
249
|
+
}
|
|
250
|
+
return lines.join('\n');
|
|
251
|
+
}
|
|
252
|
+
default:
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../../../src/handlers/share.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -60,10 +60,12 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
60
60
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
61
61
|
import { Redis } from 'ioredis';
|
|
62
62
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
63
|
+
// Mutable so the `setup`/`init` CLI can honor a `--api-url` flag for self-hosting.
|
|
64
|
+
// Reassigned (if at all) before any network call is made.
|
|
65
|
+
let API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
64
66
|
let JWT = process.env.CACHLY_JWT ?? '';
|
|
65
67
|
const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
|
|
66
|
-
const CURRENT_VERSION = '0.10.
|
|
68
|
+
const CURRENT_VERSION = '0.10.78';
|
|
67
69
|
// Max time to wait for a freshly-provisioned instance to become "running" before
|
|
68
70
|
// giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
|
|
69
71
|
// old hard-coded 25s caused spurious "instance_not_reachable" on first run.
|
|
@@ -607,6 +609,7 @@ import { handleRoadmapTool } from './handlers/roadmap.js';
|
|
|
607
609
|
import { handleAdvancedTool } from './handlers/advanced.js';
|
|
608
610
|
import { handleSyndicateTool } from './handlers/syndicate.js';
|
|
609
611
|
import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
|
|
612
|
+
import { handleShareTool } from './handlers/share.js';
|
|
610
613
|
// ── Tools (imported from tools.ts) ─────────────────────────────────────────
|
|
611
614
|
import { TOOLS } from './tools.js';
|
|
612
615
|
// ── Handlers ──────────────────────────────────────────────────────────────────
|
|
@@ -837,6 +840,9 @@ async function handleTool(name, args) {
|
|
|
837
840
|
}
|
|
838
841
|
return syndicateResult;
|
|
839
842
|
}
|
|
843
|
+
const shareResult = await handleShareTool(name, args, getConnection, apiFetch);
|
|
844
|
+
if (shareResult !== null)
|
|
845
|
+
return shareResult;
|
|
840
846
|
const fedbrainResult = await handleFedbrainTool(name, args, getConnection, apiFetch);
|
|
841
847
|
if (fedbrainResult !== null) {
|
|
842
848
|
// brain_from_git and brain_predict_failures are in fedbrain handler.
|
|
@@ -1110,13 +1116,26 @@ const EDITOR_FILES = {
|
|
|
1110
1116
|
};
|
|
1111
1117
|
const CLAUDE_MD_MARKER_START = '<!-- cachly-brain-start -->';
|
|
1112
1118
|
const CLAUDE_MD_MARKER_END = '<!-- cachly-brain-end -->';
|
|
1119
|
+
const DEFAULT_API_URL = 'https://api.cachly.dev';
|
|
1120
|
+
// Build the env block written into an editor's MCP config. Self-hosting is
|
|
1121
|
+
// first-class: if the operator runs setup/init against their own backend
|
|
1122
|
+
// (CACHLY_API_URL set to a non-default URL), that URL is baked into the config so
|
|
1123
|
+
// the editor-launched server talks to the self-hosted instance — not api.cachly.dev.
|
|
1124
|
+
// For the default cloud backend we OMIT CACHLY_API_URL to keep configs clean (the
|
|
1125
|
+
// binary already defaults to it).
|
|
1126
|
+
function buildServerEnv(apiKey, instanceId) {
|
|
1127
|
+
const env = { CACHLY_JWT: apiKey, CACHLY_BRAIN_INSTANCE_ID: instanceId };
|
|
1128
|
+
if (API_URL && API_URL !== DEFAULT_API_URL)
|
|
1129
|
+
env.CACHLY_API_URL = API_URL;
|
|
1130
|
+
return env;
|
|
1131
|
+
}
|
|
1113
1132
|
function buildMcpConfig(apiKey, instanceId, editor) {
|
|
1114
1133
|
if (editor === 'continue') {
|
|
1115
1134
|
return JSON.stringify({
|
|
1116
1135
|
experimental: {
|
|
1117
1136
|
modelContextProtocolServers: [{
|
|
1118
1137
|
transport: { type: 'stdio', command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'] },
|
|
1119
|
-
env:
|
|
1138
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1120
1139
|
}],
|
|
1121
1140
|
},
|
|
1122
1141
|
}, null, 2);
|
|
@@ -1128,7 +1147,7 @@ function buildMcpConfig(apiKey, instanceId, editor) {
|
|
|
1128
1147
|
command: {
|
|
1129
1148
|
path: 'npx',
|
|
1130
1149
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
1131
|
-
env:
|
|
1150
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1132
1151
|
},
|
|
1133
1152
|
settings: {},
|
|
1134
1153
|
},
|
|
@@ -1140,7 +1159,7 @@ function buildMcpConfig(apiKey, instanceId, editor) {
|
|
|
1140
1159
|
cachly: {
|
|
1141
1160
|
command: 'npx',
|
|
1142
1161
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
1143
|
-
env:
|
|
1162
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1144
1163
|
},
|
|
1145
1164
|
},
|
|
1146
1165
|
}, null, 2);
|
|
@@ -1151,7 +1170,7 @@ async function mergeMcpConfig(configPath, apiKey, instanceId, editor, fsOps) {
|
|
|
1151
1170
|
const cachlyEntry = {
|
|
1152
1171
|
command: 'npx',
|
|
1153
1172
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
1154
|
-
env:
|
|
1173
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1155
1174
|
};
|
|
1156
1175
|
if (!fsOps.existsSync(configPath))
|
|
1157
1176
|
return buildMcpConfig(apiKey, instanceId, editor);
|
|
@@ -1558,7 +1577,9 @@ if (process.argv[2] === 'join') {
|
|
|
1558
1577
|
env['CACHLY_BRAIN_INSTANCE_ID'] = instance_id;
|
|
1559
1578
|
if (joinJwt && !env['CACHLY_JWT'])
|
|
1560
1579
|
env['CACHLY_JWT'] = joinJwt;
|
|
1561
|
-
|
|
1580
|
+
// Only persist a self-hosted backend URL; leave default-cloud configs clean.
|
|
1581
|
+
if (API_URL !== DEFAULT_API_URL)
|
|
1582
|
+
env['CACHLY_API_URL'] = API_URL;
|
|
1562
1583
|
servers['cachly'] = { ...existing, command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'], env };
|
|
1563
1584
|
cfg['mcpServers'] = servers;
|
|
1564
1585
|
await wfJ(ePath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
@@ -1923,6 +1944,11 @@ if (process.argv[2] === 'init') {
|
|
|
1923
1944
|
let apiKey = flag('api-key') ?? process.env.CACHLY_JWT;
|
|
1924
1945
|
const editor = (flag('editor') ?? 'claude').toLowerCase();
|
|
1925
1946
|
const projectDir = resolve(flag('project-dir') ?? '.');
|
|
1947
|
+
// Self-hosting: --api-url overrides the backend baked into the written config.
|
|
1948
|
+
const apiUrlFlag = flag('api-url');
|
|
1949
|
+
if (apiUrlFlag) {
|
|
1950
|
+
API_URL = apiUrlFlag.replace(/\/+$/, '');
|
|
1951
|
+
}
|
|
1926
1952
|
// Zero-arg path: fall back to credentials saved by a previous `setup`
|
|
1927
1953
|
// (~/.claude/mcp.json). Makes `init` a fast, idempotent re-configuration command
|
|
1928
1954
|
// you can run in any project without copy-pasting tokens.
|
|
@@ -1944,7 +1970,7 @@ if (process.argv[2] === 'init') {
|
|
|
1944
1970
|
catch { /* fall through to the usage error below */ }
|
|
1945
1971
|
}
|
|
1946
1972
|
if (!instanceId || !apiKey) {
|
|
1947
|
-
console.error('\nUsage: npx @cachly-dev/mcp-server@latest init --instance-id <uuid> --api-key <cky_live_...> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path]\n');
|
|
1973
|
+
console.error('\nUsage: npx @cachly-dev/mcp-server@latest init --instance-id <uuid> --api-key <cky_live_...> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path] [--api-url https://your-self-hosted-backend]\n');
|
|
1948
1974
|
console.error('First time? Run the zero-config wizard (signs you in, auto-provisions): npx @cachly-dev/mcp-server@latest setup\n');
|
|
1949
1975
|
console.error('Get your credentials from: https://cachly.dev/setup-ai\n');
|
|
1950
1976
|
process.exit(1);
|
|
@@ -2108,6 +2134,11 @@ if (process.argv[2] === 'health') {
|
|
|
2108
2134
|
if (!jwt) {
|
|
2109
2135
|
fail('CACHLY_JWT not set — run: npx @cachly-dev/mcp-server@latest setup');
|
|
2110
2136
|
}
|
|
2137
|
+
else if (jwt.startsWith('cky_')) {
|
|
2138
|
+
// Long-lived API key (the credential `setup` provisions). It is not a JWT and
|
|
2139
|
+
// never expires client-side — treat a well-formed key as healthy.
|
|
2140
|
+
ok('Long-lived API key set (cky_…) — no client-side expiry');
|
|
2141
|
+
}
|
|
2111
2142
|
else {
|
|
2112
2143
|
try {
|
|
2113
2144
|
const parts = jwt.split('.');
|
|
@@ -2127,7 +2158,7 @@ if (process.argv[2] === 'health') {
|
|
|
2127
2158
|
}
|
|
2128
2159
|
}
|
|
2129
2160
|
catch {
|
|
2130
|
-
fail('CACHLY_JWT format invalid (expected
|
|
2161
|
+
fail('CACHLY_JWT format invalid (expected a cky_ API key or a 3-part JWT)');
|
|
2131
2162
|
}
|
|
2132
2163
|
}
|
|
2133
2164
|
// ── 2. Brain API reachability ───────────────────────────────────────────────
|
|
@@ -2275,6 +2306,30 @@ if (process.argv[2] === 'health') {
|
|
|
2275
2306
|
else {
|
|
2276
2307
|
warn('.git/hooks/post-commit not found — run: npx @cachly-dev/mcp-server@latest setup');
|
|
2277
2308
|
}
|
|
2309
|
+
// ── 6. Embedding provider (BYOK) ─────────────────────────────────────────────
|
|
2310
|
+
console.log('\n🧬 Embedding provider (semantic search)');
|
|
2311
|
+
{
|
|
2312
|
+
const providerKeys = [
|
|
2313
|
+
['OPENAI_API_KEY', 'openai'], ['GEMINI_API_KEY', 'gemini'],
|
|
2314
|
+
['MISTRAL_API_KEY', 'mistral'], ['COHERE_API_KEY', 'cohere'],
|
|
2315
|
+
['OLLAMA_BASE_URL', 'ollama'],
|
|
2316
|
+
];
|
|
2317
|
+
const explicit = (process.env.CACHLY_EMBED_PROVIDER ?? '').toLowerCase();
|
|
2318
|
+
const byok = providerKeys.find(([envVar]) => process.env[envVar]);
|
|
2319
|
+
if (explicit && explicit !== 'cachly') {
|
|
2320
|
+
const envVar = providerKeys.find(([, p]) => p === explicit)?.[0];
|
|
2321
|
+
if (envVar && process.env[envVar])
|
|
2322
|
+
ok(`BYOK: ${explicit} (key present in ${envVar})`);
|
|
2323
|
+
else
|
|
2324
|
+
warn(`CACHLY_EMBED_PROVIDER=${explicit} but its key env var is not set — semantic search will fail`);
|
|
2325
|
+
}
|
|
2326
|
+
else if (byok) {
|
|
2327
|
+
ok(`BYOK: ${byok[1]} auto-detected (${byok[0]} present)`);
|
|
2328
|
+
}
|
|
2329
|
+
else {
|
|
2330
|
+
ok('Server-side embeddings (cachly) — no key needed, uses your JWT');
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2278
2333
|
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
2279
2334
|
console.log(`\n${'─'.repeat(40)}`);
|
|
2280
2335
|
if (failed === 0) {
|
|
@@ -2295,6 +2350,13 @@ if (process.argv[2] === 'setup') {
|
|
|
2295
2350
|
const { resolve, dirname } = await import('node:path');
|
|
2296
2351
|
const { createInterface } = await import('node:readline');
|
|
2297
2352
|
const setupStartMs = Date.now();
|
|
2353
|
+
// Self-hosting: `setup --api-url https://cachly.mycorp.internal` points the whole
|
|
2354
|
+
// wizard (auth, provisioning, config writes) at a private backend.
|
|
2355
|
+
const _apiUrlIdx = process.argv.indexOf('--api-url');
|
|
2356
|
+
if (_apiUrlIdx !== -1 && process.argv[_apiUrlIdx + 1]) {
|
|
2357
|
+
API_URL = process.argv[_apiUrlIdx + 1].replace(/\/+$/, '');
|
|
2358
|
+
console.log(`ℹ️ Using self-hosted backend: ${API_URL}\n`);
|
|
2359
|
+
}
|
|
2298
2360
|
sendFunnelEvent('setup_started');
|
|
2299
2361
|
// --yes / -y → non-interactive mode (skips all prompts, picks defaults).
|
|
2300
2362
|
// Also auto-detect a non-interactive stdin (VSCode tasks, CI, piped input):
|
|
@@ -2656,7 +2718,7 @@ if (process.argv[2] === 'setup') {
|
|
|
2656
2718
|
globalConfig.mcpServers['cachly'] = {
|
|
2657
2719
|
command: 'npx',
|
|
2658
2720
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
2659
|
-
env:
|
|
2721
|
+
env: buildServerEnv(token, instance.id),
|
|
2660
2722
|
};
|
|
2661
2723
|
await writeFile(globalClaudePath, JSON.stringify(globalConfig, null, 2), 'utf-8');
|
|
2662
2724
|
console.log(`✅ Written: ~/.claude/mcp.json (global — works in every project)`);
|
|
@@ -2932,37 +2994,45 @@ if (process.argv[2] === 'learn-git') {
|
|
|
2932
2994
|
process.exit(0);
|
|
2933
2995
|
}
|
|
2934
2996
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
2935
|
-
//
|
|
2936
|
-
//
|
|
2937
|
-
//
|
|
2997
|
+
// Credential-missing handling at startup. The critical distinction is HOW we were
|
|
2998
|
+
// launched, because writing anything to stdout in stdio-MCP mode corrupts the
|
|
2999
|
+
// JSON-RPC stream and exiting kills the zero-credential device-flow onboarding.
|
|
3000
|
+
// • Human in a real terminal (TTY), no args → show the setup banner, exit 0.
|
|
3001
|
+
// • Editor as an MCP stdio server (non-TTY) → ONE stderr hint, then KEEP RUNNING
|
|
3002
|
+
// so tools/list works and the first tool call starts the browser sign-in.
|
|
3003
|
+
// Skip entirely for CLI subcommands that intentionally run without credentials.
|
|
2938
3004
|
const _cliNoAuthCommands = ['demo', 'share', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
|
|
2939
|
-
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
if (!process.argv[2] && _isMain) {
|
|
3005
|
+
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')) {
|
|
3006
|
+
const runningInTerminal = !process.argv[2] && process.stdout.isTTY === true && _isMain;
|
|
3007
|
+
if (runningInTerminal) {
|
|
3008
|
+
// A human ran `npx @cachly-dev/mcp-server` directly with no credentials.
|
|
3009
|
+
// Show them how to set up, then exit cleanly.
|
|
3010
|
+
const banner = '\n' +
|
|
3011
|
+
'╔══════════════════════════════════════════════════════════════════╗\n' +
|
|
3012
|
+
'║ 🧠 cachly AI Brain — Setup required ║\n' +
|
|
3013
|
+
'╠══════════════════════════════════════════════════════════════════╣\n' +
|
|
3014
|
+
'║ ║\n' +
|
|
3015
|
+
'║ CACHLY_JWT is not set. Get your free credentials at: ║\n' +
|
|
3016
|
+
'║ ║\n' +
|
|
3017
|
+
'║ 👉 https://cachly.dev/setup-ai ║\n' +
|
|
3018
|
+
'║ ║\n' +
|
|
3019
|
+
'║ Then run the interactive setup wizard: ║\n' +
|
|
3020
|
+
'║ ║\n' +
|
|
3021
|
+
'║ npx @cachly-dev/mcp-server@latest setup ║\n' +
|
|
3022
|
+
'║ ║\n' +
|
|
3023
|
+
'║ Free tier — no credit card required. ║\n' +
|
|
3024
|
+
'╚══════════════════════════════════════════════════════════════════╝\n' +
|
|
3025
|
+
'\n';
|
|
2961
3026
|
process.stdout.write(banner);
|
|
2962
3027
|
process.exit(0);
|
|
2963
3028
|
}
|
|
2964
3029
|
else {
|
|
2965
|
-
|
|
3030
|
+
// Launched as an MCP stdio server (or HTTP) without credentials. NEVER touch
|
|
3031
|
+
// stdout here — it carries the JSON-RPC protocol. Emit a single actionable
|
|
3032
|
+
// hint to stderr (shown in the editor's MCP log) and fall through so the
|
|
3033
|
+
// server starts and the zero-credential device flow can run on first tool call.
|
|
3034
|
+
process.stderr.write('\n🧠 cachly: no CACHLY_JWT set yet — call any cachly tool and a 10-second browser sign-in starts automatically.\n' +
|
|
3035
|
+
' (Or run once: npx @cachly-dev/mcp-server@latest setup)\n\n');
|
|
2966
3036
|
}
|
|
2967
3037
|
}
|
|
2968
3038
|
else {
|