@cachly-dev/mcp-server 0.10.83 → 0.10.85
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/auth.d.ts +14 -0
- package/dist/src/auth.js +35 -0
- package/dist/src/auth.js.map +1 -1
- package/dist/src/handlers/brain.js +14 -1
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/index.js +101 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/telemetry-types.d.ts +1 -1
- package/dist/src/toolspecs.d.ts +43 -0
- package/dist/src/toolspecs.js +121 -0
- package/dist/src/toolspecs.js.map +1 -0
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal } from './auth.js';
|
|
2
|
+
import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal, readClientCredentialsFromEnv, buildClientCredentialsBody, clientCredentialsTokenUrl } from './auth.js';
|
|
3
3
|
import { handleTcoTool } from './handlers/tco.js';
|
|
4
4
|
import { notify } from './notifier.js';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -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.
|
|
68
|
+
const CURRENT_VERSION = '0.10.85';
|
|
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.
|
|
@@ -1977,6 +1977,36 @@ if (process.argv[2] === 'publish') {
|
|
|
1977
1977
|
}
|
|
1978
1978
|
process.exit(0);
|
|
1979
1979
|
}
|
|
1980
|
+
// ── CLI: cachly tool-specs / openapi ───────────────────────────────────────────
|
|
1981
|
+
// Emit the 114-tool surface in any agent framework's dialect, derived from the
|
|
1982
|
+
// single TOOLS source of truth. Lets OpenAI Assistants, the Anthropic Messages
|
|
1983
|
+
// API, LangChain, CrewAI and AutoGen wrap cachly without hand-written glue.
|
|
1984
|
+
// npx @cachly-dev/mcp-server tool-specs --format=openai > cachly.openai.json
|
|
1985
|
+
// npx @cachly-dev/mcp-server openapi > cachly.openapi.json
|
|
1986
|
+
if (process.argv[2] === 'tool-specs' || process.argv[2] === 'openapi') {
|
|
1987
|
+
const { renderToolSpecs } = await import('./toolspecs.js');
|
|
1988
|
+
// Accept both "--format openai" and "--format=openai".
|
|
1989
|
+
let format = 'openapi';
|
|
1990
|
+
if (process.argv[2] !== 'openapi') {
|
|
1991
|
+
const eqArg = process.argv.find(a => a.startsWith('--format='));
|
|
1992
|
+
if (eqArg) {
|
|
1993
|
+
format = eqArg.slice('--format='.length);
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
const fmtFlag = process.argv.indexOf('--format');
|
|
1997
|
+
if (fmtFlag !== -1)
|
|
1998
|
+
format = process.argv[fmtFlag + 1] ?? 'openapi';
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
const valid = ['openapi', 'openai', 'anthropic', 'langchain'];
|
|
2002
|
+
if (!valid.includes(format)) {
|
|
2003
|
+
process.stderr.write(`\n❌ Unknown format "${format}". Use one of: ${valid.join(', ')}\n\n`);
|
|
2004
|
+
process.exit(1);
|
|
2005
|
+
}
|
|
2006
|
+
// Exit only after stdout has fully flushed — on a pipe, write() is async and an
|
|
2007
|
+
// immediate process.exit() truncates large payloads (~64KB) mid-flush.
|
|
2008
|
+
process.stdout.write(renderToolSpecs(TOOLS, format, CURRENT_VERSION) + '\n', () => process.exit(0));
|
|
2009
|
+
}
|
|
1980
2010
|
// ── CLI: cachly badge ─────────────────────────────────────────────────────────
|
|
1981
2011
|
// Outputs the Markdown + HTML snippet for embedding a live Brain lesson-count
|
|
1982
2012
|
// badge in any README or website. Badge SVG served by cachly API (public, no auth).
|
|
@@ -2026,8 +2056,9 @@ if (!process.argv[2] && process.stdout.isTTY) {
|
|
|
2026
2056
|
console.log(' cachly gives it a permanent brain — pre-briefed every session.');
|
|
2027
2057
|
console.log('');
|
|
2028
2058
|
console.log(' \x1b[1mCommands:\x1b[0m');
|
|
2029
|
-
console.log(' \x1b[32m npx @cachly-dev/mcp-server@latest
|
|
2030
|
-
console.log(' \x1b[32m npx @cachly-dev/mcp-server@latest
|
|
2059
|
+
console.log(' \x1b[1m\x1b[32m npx @cachly-dev/mcp-server@latest autopilot\x1b[0m ← \x1b[1mOne command, fully ready\x1b[0m');
|
|
2060
|
+
console.log(' \x1b[32m npx @cachly-dev/mcp-server@latest demo\x1b[0m ← Try it first (no account)');
|
|
2061
|
+
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest setup\x1b[0m ← Interactive setup (pick editors)');
|
|
2031
2062
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest health\x1b[0m ← Check everything works');
|
|
2032
2063
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest digest\x1b[0m ← Weekly Brain summary');
|
|
2033
2064
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest share\x1b[0m ← Share your Brain stats');
|
|
@@ -2037,7 +2068,7 @@ if (!process.argv[2] && process.stdout.isTTY) {
|
|
|
2037
2068
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest upgrade\x1b[0m ← Check for updates');
|
|
2038
2069
|
console.log('');
|
|
2039
2070
|
console.log(' \x1b[90mWorks with: Claude Code · Cursor · Windsurf · GitHub Copilot · Cline · Zed\x1b[0m');
|
|
2040
|
-
console.log(' \x1b[90mFree forever · GDPR · German servers ·
|
|
2071
|
+
console.log(' \x1b[90mFree forever · GDPR · German servers · 114 MCP tools\x1b[0m');
|
|
2041
2072
|
console.log('');
|
|
2042
2073
|
process.exit(0);
|
|
2043
2074
|
}
|
|
@@ -2453,7 +2484,11 @@ if (process.argv[2] === 'health') {
|
|
|
2453
2484
|
}
|
|
2454
2485
|
// ── CLI: cachly setup (interactive — no flags required) ───────────────────────
|
|
2455
2486
|
// Usage: npx @cachly-dev/mcp-server setup
|
|
2456
|
-
|
|
2487
|
+
// `autopilot` is the one-command entrypoint: it runs the exact same wizard as
|
|
2488
|
+
// `setup` but fully automatic (no prompts) — auth → instance → all editor
|
|
2489
|
+
// configs → CLAUDE.md → git/starter bootstrap → health, in a single shot.
|
|
2490
|
+
const _isAutopilotCli = process.argv[2] === 'autopilot';
|
|
2491
|
+
if (process.argv[2] === 'setup' || _isAutopilotCli) {
|
|
2457
2492
|
const { writeFile, mkdir, readFile } = await import('node:fs/promises');
|
|
2458
2493
|
const { existsSync } = await import('node:fs');
|
|
2459
2494
|
const { resolve, dirname } = await import('node:path');
|
|
@@ -2472,8 +2507,12 @@ if (process.argv[2] === 'setup') {
|
|
|
2472
2507
|
// a readline question against a non-TTY stdin never resolves and hangs the
|
|
2473
2508
|
// wizard forever. Treat that exactly like --yes so we never block.
|
|
2474
2509
|
const stdinIsInteractive = process.stdin.isTTY === true;
|
|
2475
|
-
|
|
2476
|
-
|
|
2510
|
+
// `autopilot` is always fully automatic — it IS the one-step onboarding.
|
|
2511
|
+
const nonInteractive = _isAutopilotCli || process.argv.includes('--yes') || process.argv.includes('-y') || !stdinIsInteractive;
|
|
2512
|
+
if (_isAutopilotCli) {
|
|
2513
|
+
console.log('🤖 Autopilot — one-command setup (auth → instance → configs → Brain), zero prompts.\n');
|
|
2514
|
+
}
|
|
2515
|
+
else if (!stdinIsInteractive && !process.argv.includes('--yes') && !process.argv.includes('-y')) {
|
|
2477
2516
|
console.log('ℹ️ Non-interactive terminal detected — running in automatic mode (no prompts).\n');
|
|
2478
2517
|
}
|
|
2479
2518
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -2842,22 +2881,40 @@ if (process.argv[2] === 'setup') {
|
|
|
2842
2881
|
// ── Step 5b: Bootstrap Brain from git history ─────────────────────────────
|
|
2843
2882
|
// Pre-populates the Brain with real lessons so the first session_start shows
|
|
2844
2883
|
// actual project knowledge instead of an empty brain.
|
|
2884
|
+
JWT = token; // set global JWT so handleTool can authenticate
|
|
2885
|
+
let gitLessonCount = 0;
|
|
2845
2886
|
if (existsSync(resolve(cwd, '.git'))) {
|
|
2846
2887
|
process.stdout.write('⏳ Bootstrapping Brain from git history (~10s)...');
|
|
2847
2888
|
try {
|
|
2848
|
-
JWT = token; // set global JWT so handleTool can authenticate
|
|
2849
2889
|
const gitResult = await handleTool('brain_from_git', {
|
|
2850
2890
|
instance_id: instance.id,
|
|
2851
2891
|
repo_path: cwd,
|
|
2852
2892
|
limit: 100,
|
|
2853
2893
|
});
|
|
2854
2894
|
const match = gitResult.match(/(\d+) lesson/);
|
|
2895
|
+
gitLessonCount = match ? parseInt(match[1], 10) : 0;
|
|
2855
2896
|
console.log(` ✓ ${match ? match[1] + ' lessons' : 'done'} extracted from git history`);
|
|
2856
2897
|
}
|
|
2857
2898
|
catch (e) {
|
|
2858
2899
|
console.log(` (skipped: ${e.message.slice(0, 80)})`);
|
|
2859
2900
|
}
|
|
2860
2901
|
}
|
|
2902
|
+
// ── Step 5c: Seed the starter corpus when git yielded nothing ─────────────
|
|
2903
|
+
// Closes the empty-brain gap: a fresh repo (no .git, shallow clone, or no
|
|
2904
|
+
// fix-shaped commits) would otherwise leave the Brain empty and the very first
|
|
2905
|
+
// smart_recall returning nothing. Seeding 16 curated universal lessons makes
|
|
2906
|
+
// the single setup command produce an immediately-useful Brain — true 1-step
|
|
2907
|
+
// onboarding. Idempotent; never overwrites real lessons.
|
|
2908
|
+
if (gitLessonCount === 0) {
|
|
2909
|
+
process.stdout.write('🌱 Seeding 16 universal engineering lessons (first recall works instantly)...');
|
|
2910
|
+
try {
|
|
2911
|
+
await handleTool('brain_seed_starter', { instance_id: instance.id });
|
|
2912
|
+
console.log(' ✓');
|
|
2913
|
+
}
|
|
2914
|
+
catch (e) {
|
|
2915
|
+
console.log(` (skipped: ${e.message.slice(0, 60)})`);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2861
2918
|
// ── Step 6: Show Brain health (Aha moment) ────────────────────────────────
|
|
2862
2919
|
// Fetch brain health from the API to show the user what their agent will see.
|
|
2863
2920
|
process.stdout.write('\n⏳ Fetching your Brain health preview...');
|
|
@@ -3103,6 +3160,40 @@ if (process.argv[2] === 'learn-git') {
|
|
|
3103
3160
|
process.exit(0);
|
|
3104
3161
|
}
|
|
3105
3162
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
3163
|
+
// M2M / headless auth: if no JWT but client credentials are present, run the
|
|
3164
|
+
// OAuth2 client_credentials grant before any credential-missing handling. This
|
|
3165
|
+
// makes cachly fully non-interactive for CI runners, agent orchestrators and
|
|
3166
|
+
// AI-to-AI pipelines — no device flow, no human, no browser.
|
|
3167
|
+
if (!JWT) {
|
|
3168
|
+
const creds = readClientCredentialsFromEnv();
|
|
3169
|
+
if (creds) {
|
|
3170
|
+
try {
|
|
3171
|
+
const res = await fetch(clientCredentialsTokenUrl(creds), {
|
|
3172
|
+
method: 'POST',
|
|
3173
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
3174
|
+
body: buildClientCredentialsBody(creds),
|
|
3175
|
+
signal: AbortSignal.timeout(8000),
|
|
3176
|
+
});
|
|
3177
|
+
if (res.ok) {
|
|
3178
|
+
const tok = await res.json();
|
|
3179
|
+
if (tok.access_token) {
|
|
3180
|
+
JWT = tok.access_token;
|
|
3181
|
+
setEmbedJwt(tok.access_token);
|
|
3182
|
+
sendFunnelEvent('m2m_auth_completed', { grant: 'client_credentials', client_id: creds.clientId });
|
|
3183
|
+
process.stderr.write('🤖 cachly: authenticated via client_credentials (M2M, headless).\n');
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
else {
|
|
3187
|
+
process.stderr.write(`⚠️ cachly: client_credentials auth failed (${res.status}). Check CACHLY_CLIENT_ID / CACHLY_CLIENT_SECRET.\n`);
|
|
3188
|
+
sendFunnelEvent('m2m_auth_failed', { status: res.status });
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
catch (err) {
|
|
3192
|
+
process.stderr.write(`⚠️ cachly: client_credentials auth error: ${err.message}\n`);
|
|
3193
|
+
sendFunnelEvent('m2m_auth_failed', { reason: 'network' });
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3106
3197
|
// Credential-missing handling at startup. The critical distinction is HOW we were
|
|
3107
3198
|
// launched, because writing anything to stdout in stdio-MCP mode corrupts the
|
|
3108
3199
|
// JSON-RPC stream and exiting kills the zero-credential device-flow onboarding.
|
|
@@ -3110,7 +3201,7 @@ if (process.argv[2] === 'learn-git') {
|
|
|
3110
3201
|
// • Editor as an MCP stdio server (non-TTY) → ONE stderr hint, then KEEP RUNNING
|
|
3111
3202
|
// so tools/list works and the first tool call starts the browser sign-in.
|
|
3112
3203
|
// Skip entirely for CLI subcommands that intentionally run without credentials.
|
|
3113
|
-
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
|
|
3204
|
+
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'setup', 'autopilot', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade', 'tool-specs', 'openapi'];
|
|
3114
3205
|
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')) {
|
|
3115
3206
|
const runningInTerminal = !process.argv[2] && process.stdout.isTTY === true && _isMain;
|
|
3116
3207
|
if (runningInTerminal) {
|