@cachly-dev/mcp-server 0.10.72 → 0.10.74
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 +1 -1
- package/dist/src/handlers/brain.js +28 -5
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/team.d.ts +10 -0
- package/dist/src/handlers/team.js +100 -0
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/index.js +183 -58
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +51 -0
- package/dist/src/tools.js +37 -0
- package/dist/src/tools.js.map +1 -1
- package/package.json +3 -2
- package/server.json +3 -3
package/dist/src/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal } from './auth.js';
|
|
3
3
|
import { handleTcoTool } from './handlers/tco.js';
|
|
4
4
|
import { notify } from './notifier.js';
|
|
5
|
-
import { readFile } from 'node:fs/promises';
|
|
6
5
|
import { fileURLToPath } from 'node:url';
|
|
7
6
|
// True only when this file is the entry point (not imported by tests or other modules).
|
|
8
7
|
const _isMain = process.argv[1] != null &&
|
|
@@ -64,7 +63,7 @@ import { Redis } from 'ioredis';
|
|
|
64
63
|
const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
65
64
|
let JWT = process.env.CACHLY_JWT ?? '';
|
|
66
65
|
const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
|
|
67
|
-
const CURRENT_VERSION = '0.10.
|
|
66
|
+
const CURRENT_VERSION = '0.10.74';
|
|
68
67
|
// Max time to wait for a freshly-provisioned instance to become "running" before
|
|
69
68
|
// giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
|
|
70
69
|
// old hard-coded 25s caused spurious "instance_not_reachable" on first run.
|
|
@@ -210,13 +209,34 @@ async function selfHealAuth() {
|
|
|
210
209
|
}
|
|
211
210
|
let _deviceFlow = null;
|
|
212
211
|
async function startDeviceFlow() {
|
|
213
|
-
|
|
214
|
-
const CLIENT_ID = 'cachly-cli';
|
|
212
|
+
// Try the API proxy first (more reliable than direct Keycloak endpoint)
|
|
215
213
|
try {
|
|
214
|
+
const res = await fetch(`${API_URL}/api/v1/auth/device/code`, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
217
|
+
body: JSON.stringify({ client_id: 'cachly-mcp-cli' }),
|
|
218
|
+
signal: AbortSignal.timeout(8000),
|
|
219
|
+
});
|
|
220
|
+
if (res.ok) {
|
|
221
|
+
const data = await res.json();
|
|
222
|
+
return {
|
|
223
|
+
deviceCode: data.device_code,
|
|
224
|
+
userCode: data.user_code,
|
|
225
|
+
verifyUrl: data.verification_uri,
|
|
226
|
+
pollInterval: (data.interval ?? 5) * 1000,
|
|
227
|
+
deadline: Date.now() + 10 * 60 * 1000,
|
|
228
|
+
polling: false,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch { /* fall through to Keycloak */ }
|
|
233
|
+
// Fallback: direct Keycloak endpoint
|
|
234
|
+
try {
|
|
235
|
+
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
216
236
|
const res = await fetch(`${AUTH_BASE}/auth/device`, {
|
|
217
237
|
method: 'POST',
|
|
218
238
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
219
|
-
body: `client_id
|
|
239
|
+
body: `client_id=cachly-cli&scope=openid`,
|
|
220
240
|
signal: AbortSignal.timeout(8000),
|
|
221
241
|
});
|
|
222
242
|
if (!res.ok)
|
|
@@ -227,7 +247,7 @@ async function startDeviceFlow() {
|
|
|
227
247
|
userCode: data.user_code,
|
|
228
248
|
verifyUrl: data.verification_uri_complete,
|
|
229
249
|
pollInterval: (data.interval ?? 5) * 1000,
|
|
230
|
-
deadline: Date.now() + 10 * 60 * 1000,
|
|
250
|
+
deadline: Date.now() + 10 * 60 * 1000,
|
|
231
251
|
polling: false,
|
|
232
252
|
};
|
|
233
253
|
}
|
|
@@ -238,16 +258,34 @@ async function startDeviceFlow() {
|
|
|
238
258
|
async function pollDeviceFlow(flow) {
|
|
239
259
|
if (Date.now() > flow.deadline)
|
|
240
260
|
return 'expired';
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
261
|
+
// Try API proxy first, then direct Keycloak as fallback
|
|
262
|
+
const tryProxy = async () => {
|
|
263
|
+
const res = await fetch(`${API_URL}/api/v1/auth/device/token`, {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: { 'Content-Type': 'application/json' },
|
|
266
|
+
body: JSON.stringify({ device_code: flow.deviceCode, client_id: 'cachly-mcp-cli' }),
|
|
267
|
+
signal: AbortSignal.timeout(8000),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok)
|
|
270
|
+
return null;
|
|
271
|
+
return res.json();
|
|
272
|
+
};
|
|
273
|
+
const tryKeycloak = async () => {
|
|
274
|
+
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
244
275
|
const res = await fetch(`${AUTH_BASE}/token`, {
|
|
245
276
|
method: 'POST',
|
|
246
277
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
247
|
-
body: `client_id
|
|
278
|
+
body: `client_id=cachly-cli&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${flow.deviceCode}`,
|
|
248
279
|
signal: AbortSignal.timeout(8000),
|
|
249
280
|
});
|
|
250
|
-
|
|
281
|
+
return res.json();
|
|
282
|
+
};
|
|
283
|
+
try {
|
|
284
|
+
let data = await tryProxy().catch(() => null);
|
|
285
|
+
if (!data)
|
|
286
|
+
data = await tryKeycloak().catch(() => null);
|
|
287
|
+
if (!data)
|
|
288
|
+
return 'pending';
|
|
251
289
|
if (data.access_token) {
|
|
252
290
|
// Exchange Keycloak JWT → long-lived API key
|
|
253
291
|
let apiKey = data.access_token;
|
|
@@ -666,7 +704,7 @@ async function handleTool(name, args) {
|
|
|
666
704
|
'',
|
|
667
705
|
'After sign-in: call **any tool again** — your Brain activates instantly.',
|
|
668
706
|
'',
|
|
669
|
-
'✨ Free forever · No credit card ·
|
|
707
|
+
'✨ Free forever · No credit card · 107 MCP tools · GDPR · EU servers',
|
|
670
708
|
].join('\n');
|
|
671
709
|
}
|
|
672
710
|
// Device flow unavailable (network issue) — fall back to manual setup
|
|
@@ -1869,24 +1907,45 @@ if (!process.argv[2] && process.stdout.isTTY) {
|
|
|
1869
1907
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest upgrade\x1b[0m ← Check for updates');
|
|
1870
1908
|
console.log('');
|
|
1871
1909
|
console.log(' \x1b[90mWorks with: Claude Code · Cursor · Windsurf · GitHub Copilot · Cline · Zed\x1b[0m');
|
|
1872
|
-
console.log(' \x1b[90mFree forever · GDPR · German servers ·
|
|
1910
|
+
console.log(' \x1b[90mFree forever · GDPR · German servers · 107 MCP tools\x1b[0m');
|
|
1873
1911
|
console.log('');
|
|
1874
1912
|
process.exit(0);
|
|
1875
1913
|
}
|
|
1876
1914
|
// ── CLI: cachly init ──────────────────────────────────────────────────────────
|
|
1877
1915
|
// Usage: npx @cachly-dev/mcp-server init --instance-id <id> --api-key <key> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path]
|
|
1878
1916
|
if (process.argv[2] === 'init') {
|
|
1879
|
-
const
|
|
1917
|
+
const initStart = Date.now();
|
|
1918
|
+
const { writeFile, mkdir, readFile } = await import('node:fs/promises');
|
|
1880
1919
|
const { resolve, dirname } = await import('node:path');
|
|
1881
1920
|
const argv = process.argv.slice(3);
|
|
1882
1921
|
const flag = (name) => { const i = argv.indexOf(`--${name}`); return i !== -1 ? argv[i + 1] : undefined; };
|
|
1883
|
-
|
|
1884
|
-
|
|
1922
|
+
let instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
|
|
1923
|
+
let apiKey = flag('api-key') ?? process.env.CACHLY_JWT;
|
|
1885
1924
|
const editor = (flag('editor') ?? 'claude').toLowerCase();
|
|
1886
1925
|
const projectDir = resolve(flag('project-dir') ?? '.');
|
|
1926
|
+
// Zero-arg path: fall back to credentials saved by a previous `setup`
|
|
1927
|
+
// (~/.claude/mcp.json). Makes `init` a fast, idempotent re-configuration command
|
|
1928
|
+
// you can run in any project without copy-pasting tokens.
|
|
1929
|
+
if (!instanceId || !apiKey) {
|
|
1930
|
+
try {
|
|
1931
|
+
const { existsSync } = await import('node:fs');
|
|
1932
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
1933
|
+
const savedPath = home ? resolve(home, '.claude', 'mcp.json') : '';
|
|
1934
|
+
if (savedPath && existsSync(savedPath)) {
|
|
1935
|
+
const saved = JSON.parse(await readFile(savedPath, 'utf-8'));
|
|
1936
|
+
const env = saved.mcpServers?.['cachly']?.env ?? {};
|
|
1937
|
+
apiKey ??= env['CACHLY_JWT'];
|
|
1938
|
+
instanceId ??= env['CACHLY_BRAIN_INSTANCE_ID'];
|
|
1939
|
+
if (apiKey && instanceId) {
|
|
1940
|
+
console.log('✓ Reusing credentials from ~/.claude/mcp.json (saved by a prior setup)');
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
catch { /* fall through to the usage error below */ }
|
|
1945
|
+
}
|
|
1887
1946
|
if (!instanceId || !apiKey) {
|
|
1888
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');
|
|
1889
|
-
console.error('
|
|
1948
|
+
console.error('First time? Run the zero-config wizard (signs you in, auto-provisions): npx @cachly-dev/mcp-server@latest setup\n');
|
|
1890
1949
|
console.error('Get your credentials from: https://cachly.dev/setup-ai\n');
|
|
1891
1950
|
process.exit(1);
|
|
1892
1951
|
}
|
|
@@ -1894,9 +1953,17 @@ if (process.argv[2] === 'init') {
|
|
|
1894
1953
|
const configPath = resolve(projectDir, configFile);
|
|
1895
1954
|
await mkdir(dirname(configPath), { recursive: true });
|
|
1896
1955
|
const { existsSync: exInit } = await import('node:fs');
|
|
1956
|
+
const configExisted = exInit(configPath);
|
|
1957
|
+
const prevConfig = configExisted ? await readFile(configPath, 'utf-8').catch(() => '') : '';
|
|
1897
1958
|
const merged = await mergeMcpConfig(configPath, apiKey, instanceId, editor, { readFile, existsSync: exInit });
|
|
1898
|
-
|
|
1899
|
-
|
|
1959
|
+
// Idempotent: only write when the content actually changes.
|
|
1960
|
+
if (merged !== prevConfig) {
|
|
1961
|
+
await writeFile(configPath, merged, 'utf-8');
|
|
1962
|
+
console.log(`\n✅ ${configExisted ? 'Updated' : 'Written'}: ${configFile}`);
|
|
1963
|
+
}
|
|
1964
|
+
else {
|
|
1965
|
+
console.log(`\n✓ Already configured: ${configFile} (no change)`);
|
|
1966
|
+
}
|
|
1900
1967
|
// Always write CLAUDE.md (idempotent — safe to run multiple times)
|
|
1901
1968
|
const result = await writeClaudeMd(projectDir, instanceId);
|
|
1902
1969
|
const action = result === 'updated' ? '✅ Updated' : result === 'appended' ? '✅ Appended to' : '✅ Written';
|
|
@@ -1942,8 +2009,10 @@ if (process.argv[2] === 'init') {
|
|
|
1942
2009
|
}
|
|
1943
2010
|
}
|
|
1944
2011
|
catch { /* non-critical — git hook is a best-effort feature */ }
|
|
1945
|
-
|
|
2012
|
+
const initSecs = ((Date.now() - initStart) / 1000).toFixed(1);
|
|
2013
|
+
console.log(`\n🧠 Cachly AI Brain configured for ${editor === 'claude' ? 'Claude Code' : editor}! (${initSecs}s)`);
|
|
1946
2014
|
console.log(` Restart your editor — the \`cachly\` MCP tools will appear.`);
|
|
2015
|
+
console.log(` Re-run \`init\` anytime — it's idempotent (only writes what changed).`);
|
|
1947
2016
|
console.log(`\n 📛 Add a live badge to your README:`);
|
|
1948
2017
|
console.log(` npx @cachly-dev/mcp-server@latest badge\n`);
|
|
1949
2018
|
process.exit(0);
|
|
@@ -2244,41 +2313,52 @@ if (process.argv[2] === 'setup') {
|
|
|
2244
2313
|
console.log('✓ Using token from CACHLY_JWT env var\n');
|
|
2245
2314
|
}
|
|
2246
2315
|
else {
|
|
2247
|
-
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
2248
|
-
const CLIENT_ID = 'cachly-cli';
|
|
2249
2316
|
console.log('Step 1: Sign in to cachly (free, no credit card)\n');
|
|
2250
2317
|
sendFunnelEvent('setup_auth_started');
|
|
2251
|
-
// Start device flow
|
|
2318
|
+
// Start device flow — try API proxy first, fall back to direct Keycloak
|
|
2252
2319
|
let deviceCode = '', userCode = '', verifyUri = '', pollInterval = 5000;
|
|
2320
|
+
let deviceFlowOk = false;
|
|
2321
|
+
// Attempt 1: API proxy (recommended path)
|
|
2253
2322
|
try {
|
|
2254
|
-
const deviceRes = await fetch(`${
|
|
2323
|
+
const deviceRes = await fetch(`${API_URL}/api/v1/auth/device/code`, {
|
|
2255
2324
|
method: 'POST',
|
|
2256
|
-
headers: { 'Content-Type': 'application/
|
|
2257
|
-
body:
|
|
2325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2326
|
+
body: JSON.stringify({ client_id: 'cachly-mcp-cli' }),
|
|
2327
|
+
signal: AbortSignal.timeout(8000),
|
|
2258
2328
|
});
|
|
2259
|
-
if (
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2329
|
+
if (deviceRes.ok) {
|
|
2330
|
+
const data = await deviceRes.json();
|
|
2331
|
+
deviceCode = data.device_code;
|
|
2332
|
+
userCode = data.user_code;
|
|
2333
|
+
verifyUri = data.verification_uri;
|
|
2334
|
+
pollInterval = (data.interval ?? 5) * 1000;
|
|
2335
|
+
deviceFlowOk = true;
|
|
2336
|
+
}
|
|
2266
2337
|
}
|
|
2267
|
-
catch
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2338
|
+
catch { /* fall through */ }
|
|
2339
|
+
// Attempt 2: Direct Keycloak device flow
|
|
2340
|
+
if (!deviceFlowOk) {
|
|
2341
|
+
try {
|
|
2342
|
+
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
2343
|
+
const deviceRes = await fetch(`${AUTH_BASE}/auth/device`, {
|
|
2344
|
+
method: 'POST',
|
|
2345
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
2346
|
+
body: `client_id=cachly-cli&scope=openid`,
|
|
2347
|
+
signal: AbortSignal.timeout(8000),
|
|
2348
|
+
});
|
|
2349
|
+
if (deviceRes.ok) {
|
|
2350
|
+
const data = await deviceRes.json();
|
|
2351
|
+
deviceCode = data.device_code;
|
|
2352
|
+
userCode = data.user_code;
|
|
2353
|
+
verifyUri = data.verification_uri_complete;
|
|
2354
|
+
pollInterval = (data.interval ?? 5) * 1000;
|
|
2355
|
+
deviceFlowOk = true;
|
|
2356
|
+
}
|
|
2275
2357
|
}
|
|
2276
|
-
|
|
2277
|
-
console.log('');
|
|
2278
|
-
deviceCode = ''; // mark as fallback so we skip polling
|
|
2358
|
+
catch { /* fall through */ }
|
|
2279
2359
|
}
|
|
2280
|
-
if (deviceCode) {
|
|
2281
|
-
// Open browser
|
|
2360
|
+
if (deviceFlowOk && deviceCode) {
|
|
2361
|
+
// Open browser and show code
|
|
2282
2362
|
console.log(` Code: \x1b[1;33m${userCode}\x1b[0m`);
|
|
2283
2363
|
console.log(` URL: ${verifyUri}\n`);
|
|
2284
2364
|
try {
|
|
@@ -2290,29 +2370,49 @@ if (process.argv[2] === 'setup') {
|
|
|
2290
2370
|
catch {
|
|
2291
2371
|
console.log(' 👉 Open the URL above in your browser and enter the code.\n');
|
|
2292
2372
|
}
|
|
2293
|
-
// Poll for token
|
|
2373
|
+
// Poll for token with proper timeouts
|
|
2294
2374
|
process.stdout.write(' Waiting for authorization');
|
|
2295
|
-
const deadline = Date.now() + 10 * 60 * 1000;
|
|
2375
|
+
const deadline = Date.now() + 10 * 60 * 1000;
|
|
2296
2376
|
while (Date.now() < deadline) {
|
|
2297
2377
|
await new Promise(r => setTimeout(r, pollInterval));
|
|
2298
2378
|
process.stdout.write('.');
|
|
2299
2379
|
try {
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2380
|
+
let tokenData = null;
|
|
2381
|
+
try {
|
|
2382
|
+
const proxyRes = await fetch(`${API_URL}/api/v1/auth/device/token`, {
|
|
2383
|
+
method: 'POST',
|
|
2384
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2385
|
+
body: JSON.stringify({ device_code: deviceCode, client_id: 'cachly-mcp-cli' }),
|
|
2386
|
+
signal: AbortSignal.timeout(8000),
|
|
2387
|
+
});
|
|
2388
|
+
if (proxyRes.ok)
|
|
2389
|
+
tokenData = await proxyRes.json();
|
|
2390
|
+
}
|
|
2391
|
+
catch { /* try Keycloak */ }
|
|
2392
|
+
if (!tokenData) {
|
|
2393
|
+
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
2394
|
+
try {
|
|
2395
|
+
const kcRes = await fetch(`${AUTH_BASE}/token`, {
|
|
2396
|
+
method: 'POST',
|
|
2397
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
2398
|
+
body: `client_id=cachly-cli&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${deviceCode}`,
|
|
2399
|
+
signal: AbortSignal.timeout(8000),
|
|
2400
|
+
});
|
|
2401
|
+
tokenData = await kcRes.json();
|
|
2402
|
+
}
|
|
2403
|
+
catch { /* network hiccup */ }
|
|
2404
|
+
}
|
|
2405
|
+
if (tokenData?.access_token) {
|
|
2307
2406
|
token = tokenData.access_token;
|
|
2308
2407
|
console.log(' \x1b[32m✓ Authorized!\x1b[0m\n');
|
|
2309
2408
|
sendFunnelEvent('setup_auth_completed');
|
|
2409
|
+
sendFunnelEvent('device_flow_completed');
|
|
2310
2410
|
break;
|
|
2311
2411
|
}
|
|
2312
|
-
|
|
2313
|
-
if (tokenData.error === 'slow_down')
|
|
2412
|
+
if (tokenData?.error === 'slow_down')
|
|
2314
2413
|
pollInterval = Math.min(pollInterval + 2000, 15000);
|
|
2315
|
-
else if (tokenData
|
|
2414
|
+
else if (tokenData?.error && tokenData.error !== 'authorization_pending') {
|
|
2415
|
+
sendFunnelEvent('device_flow_failed', { reason: tokenData.error });
|
|
2316
2416
|
console.error(`\nAuth error: ${tokenData.error}. Aborting.\n`);
|
|
2317
2417
|
rl.close();
|
|
2318
2418
|
process.exit(1);
|
|
@@ -2321,12 +2421,37 @@ if (process.argv[2] === 'setup') {
|
|
|
2321
2421
|
catch { /* network hiccup — keep polling */ }
|
|
2322
2422
|
}
|
|
2323
2423
|
if (!token) {
|
|
2424
|
+
sendFunnelEvent('device_flow_failed', { reason: 'timeout' });
|
|
2324
2425
|
console.error('\nTimed out waiting for authorization. Aborting.\n');
|
|
2325
2426
|
rl.close();
|
|
2326
2427
|
process.exit(1);
|
|
2327
2428
|
}
|
|
2328
2429
|
console.log('');
|
|
2329
2430
|
}
|
|
2431
|
+
else {
|
|
2432
|
+
// Device flow unavailable — web fallback with automatic browser open
|
|
2433
|
+
sendFunnelEvent('device_flow_failed', { reason: 'device_flow_unavailable' });
|
|
2434
|
+
const signupUrl = 'https://cachly.dev/setup-ai';
|
|
2435
|
+
console.log(' ⚠️ Could not start automatic sign-in. Opening browser for web sign-in...\n');
|
|
2436
|
+
console.log(` URL: \x1b[36m${signupUrl}\x1b[0m`);
|
|
2437
|
+
console.log(' 1. Sign up / log in at the URL above');
|
|
2438
|
+
console.log(' 2. Go to Settings → API Keys → Create new key');
|
|
2439
|
+
console.log(' 3. Copy the key (starts with cky_live_...)\n');
|
|
2440
|
+
try {
|
|
2441
|
+
const { execSync } = await import('node:child_process');
|
|
2442
|
+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
2443
|
+
execSync(`${openCmd} "${signupUrl}"`, { stdio: 'ignore' });
|
|
2444
|
+
}
|
|
2445
|
+
catch { /* can't open browser — instructions shown above */ }
|
|
2446
|
+
token = await ask(' Paste API key (cky_live_...): ');
|
|
2447
|
+
if (!token) {
|
|
2448
|
+
console.error('\nAPI key is required. Aborting.\n');
|
|
2449
|
+
rl.close();
|
|
2450
|
+
process.exit(1);
|
|
2451
|
+
}
|
|
2452
|
+
sendFunnelEvent('setup_auth_completed');
|
|
2453
|
+
console.log('');
|
|
2454
|
+
}
|
|
2330
2455
|
}
|
|
2331
2456
|
// ── Step 1b: Exchange Keycloak JWT → long-lived cky_live_ API key ──────────
|
|
2332
2457
|
// Only do this when the token looks like a Keycloak JWT (starts with "eyJ"),
|