@cachly-dev/mcp-server 0.10.84 → 0.10.86
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/handlers/brain.js +15 -1
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/index.js +162 -120
- package/dist/src/index.js.map +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
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.
|
|
68
|
+
const CURRENT_VERSION = '0.10.86';
|
|
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.
|
|
@@ -77,35 +77,92 @@ const PROVISION_TIMEOUT_MS = Number(process.env.CACHLY_PROVISION_TIMEOUT_MS ?? 9
|
|
|
77
77
|
let _defaultInstanceId = process.env.CACHLY_BRAIN_INSTANCE_ID ?? '';
|
|
78
78
|
// Timestamp of last failed fetch — retries after 30 s (not permanently blocked).
|
|
79
79
|
let _defaultInstanceLastAttempt = 0;
|
|
80
|
+
// In-flight resolution guard: a burst of parallel tool calls on startup must not
|
|
81
|
+
// each list-and-provision (which would race and create duplicate instances).
|
|
82
|
+
let _resolveInFlight = null;
|
|
83
|
+
/**
|
|
84
|
+
* Auto-provision a Brain instance via the idempotent find-or-create endpoint.
|
|
85
|
+
* Returns the instance id, or '' on failure. Fires auto_provision_failed telemetry
|
|
86
|
+
* on a non-2xx response so the activation funnel can spot free-tier breakage.
|
|
87
|
+
*/
|
|
88
|
+
async function autoProvisionInstance() {
|
|
89
|
+
if (!JWT)
|
|
90
|
+
return '';
|
|
91
|
+
try {
|
|
92
|
+
const autoRes = await fetch(`${API_URL}/api/v1/instances/auto`, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: { Authorization: `Bearer ${JWT}`, 'Content-Type': 'application/json' },
|
|
95
|
+
signal: AbortSignal.timeout(12000),
|
|
96
|
+
});
|
|
97
|
+
if (autoRes.ok) {
|
|
98
|
+
const body = await autoRes.json();
|
|
99
|
+
const id = body.instance?.id ?? body.instance_id;
|
|
100
|
+
if (id)
|
|
101
|
+
return id;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${JWT}` },
|
|
107
|
+
body: JSON.stringify({ event: 'auto_provision_failed', status: autoRes.status, version: CURRENT_VERSION }),
|
|
108
|
+
signal: AbortSignal.timeout(3000),
|
|
109
|
+
}).catch(() => { });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch { /* non-fatal — server unreachable */ }
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
80
115
|
async function resolveDefaultInstanceId() {
|
|
81
116
|
if (_defaultInstanceId)
|
|
82
117
|
return _defaultInstanceId;
|
|
83
118
|
if (!JWT)
|
|
84
119
|
return '';
|
|
120
|
+
// Coalesce concurrent resolutions so parallel tool calls share one round-trip.
|
|
121
|
+
if (_resolveInFlight)
|
|
122
|
+
return _resolveInFlight;
|
|
85
123
|
// Cooldown: don't hammer the API on every tool call after a transient failure.
|
|
86
124
|
const now = Date.now();
|
|
87
125
|
if (_defaultInstanceLastAttempt > 0 && now - _defaultInstanceLastAttempt < 30_000)
|
|
88
126
|
return '';
|
|
89
127
|
_defaultInstanceLastAttempt = now;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
128
|
+
_resolveInFlight = (async () => {
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch(`${API_URL}/api/v1/instances`, {
|
|
131
|
+
headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
|
|
132
|
+
signal: AbortSignal.timeout(5000),
|
|
133
|
+
});
|
|
134
|
+
if (res.ok) {
|
|
135
|
+
const data = await res.json();
|
|
136
|
+
const instances = (data?.data ?? []);
|
|
137
|
+
// Prefer running, fall back to provisioning so the ID resolves even during startup.
|
|
138
|
+
const best = instances.find(i => i.status === 'running') ?? instances.find(i => i.status === 'provisioning');
|
|
139
|
+
if (best) {
|
|
140
|
+
_defaultInstanceId = best.id;
|
|
141
|
+
_defaultInstanceLastAttempt = 0;
|
|
142
|
+
return _defaultInstanceId;
|
|
143
|
+
}
|
|
144
|
+
// Authenticated but zero instances → auto-provision so the user is never
|
|
145
|
+
// stuck. This self-heals the silent failure mode where device-flow
|
|
146
|
+
// provisioning failed once and every later tool call then ran with an
|
|
147
|
+
// empty instance_id (no recalls, never counted as an active instance).
|
|
148
|
+
const provisioned = await autoProvisionInstance();
|
|
149
|
+
if (provisioned) {
|
|
150
|
+
_defaultInstanceId = provisioned;
|
|
151
|
+
_defaultInstanceLastAttempt = 0;
|
|
152
|
+
void persistInstanceIdToConfig(provisioned);
|
|
153
|
+
return _defaultInstanceId;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
105
156
|
}
|
|
157
|
+
catch { /* transient error — will retry after cooldown */ }
|
|
158
|
+
return '';
|
|
159
|
+
})();
|
|
160
|
+
try {
|
|
161
|
+
return await _resolveInFlight;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
_resolveInFlight = null;
|
|
106
165
|
}
|
|
107
|
-
catch { /* transient error — will retry after cooldown */ }
|
|
108
|
-
return '';
|
|
109
166
|
}
|
|
110
167
|
// ── Self-Healing Auth ─────────────────────────────────────────────────────────
|
|
111
168
|
// Prevents the silent "0 recalls because the token quietly died" failure mode.
|
|
@@ -151,6 +208,39 @@ async function persistApiKeyToConfig(apiKey) {
|
|
|
151
208
|
}
|
|
152
209
|
catch { /* non-critical — never break a tool call on a filesystem error */ }
|
|
153
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Persist the resolved instance id to ~/.claude/mcp.json so restarts reuse it
|
|
213
|
+
* (avoids a list/provision round-trip on every startup). Only updates an
|
|
214
|
+
* existing cachly entry — does not create config from scratch.
|
|
215
|
+
*/
|
|
216
|
+
async function persistInstanceIdToConfig(instanceId) {
|
|
217
|
+
if (!instanceId)
|
|
218
|
+
return;
|
|
219
|
+
try {
|
|
220
|
+
const { writeFile, readFile } = await import('node:fs/promises');
|
|
221
|
+
const { existsSync } = await import('node:fs');
|
|
222
|
+
const { resolve } = await import('node:path');
|
|
223
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
224
|
+
if (!home)
|
|
225
|
+
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
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch { /* non-critical */ }
|
|
243
|
+
}
|
|
154
244
|
/**
|
|
155
245
|
* Exchange the current (still-valid) token for a fresh long-lived API key.
|
|
156
246
|
* Returns true on success. Used by self-healing when a token is near expiry.
|
|
@@ -312,67 +402,13 @@ async function pollDeviceFlow(flow) {
|
|
|
312
402
|
// Persist the API key to ~/.claude/mcp.json so subsequent MCP server restarts
|
|
313
403
|
// keep the token. Without this, JWT is lost on restart and brain_recall_count
|
|
314
404
|
// is never incremented (telemetry has no api_key → no tenant resolution).
|
|
315
|
-
void (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
321
|
-
if (!home)
|
|
322
|
-
return;
|
|
323
|
-
const configPath = resolve(home, '.claude', 'mcp.json');
|
|
324
|
-
let cfg = {};
|
|
325
|
-
if (existsSync(configPath)) {
|
|
326
|
-
try {
|
|
327
|
-
cfg = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
328
|
-
}
|
|
329
|
-
catch { /* corrupt — start fresh */ }
|
|
330
|
-
}
|
|
331
|
-
cfg.mcpServers ??= {};
|
|
332
|
-
const existing = cfg.mcpServers['cachly'];
|
|
333
|
-
if (existing) {
|
|
334
|
-
existing.env ??= {};
|
|
335
|
-
existing.env['CACHLY_JWT'] = apiKey;
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
cfg.mcpServers['cachly'] = {
|
|
339
|
-
command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
340
|
-
env: { CACHLY_JWT: apiKey },
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
344
|
-
await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
345
|
-
}
|
|
346
|
-
catch { /* non-critical — don't break the flow on filesystem errors */ }
|
|
347
|
-
})();
|
|
348
|
-
// Auto-provision: find or create the user's brain instance.
|
|
405
|
+
void persistApiKeyToConfig(apiKey);
|
|
406
|
+
// Auto-provision: find or create the user's brain instance. resolveDefaultInstanceId
|
|
407
|
+
// now lists existing instances and auto-provisions when the account has none,
|
|
408
|
+
// so a single call covers both paths (and self-heals on every later tool call
|
|
409
|
+
// if provisioning is briefly unavailable here).
|
|
349
410
|
_defaultInstanceLastAttempt = 0;
|
|
350
411
|
await resolveDefaultInstanceId();
|
|
351
|
-
if (!_defaultInstanceId) {
|
|
352
|
-
try {
|
|
353
|
-
const autoRes = await fetch(`${API_URL}/api/v1/instances/auto`, {
|
|
354
|
-
method: 'POST',
|
|
355
|
-
headers: { Authorization: `Bearer ${JWT}`, 'Content-Type': 'application/json' },
|
|
356
|
-
signal: AbortSignal.timeout(12000),
|
|
357
|
-
});
|
|
358
|
-
if (autoRes.ok) {
|
|
359
|
-
const body = await autoRes.json();
|
|
360
|
-
const id = body.instance?.id ?? body.instance_id;
|
|
361
|
-
if (id)
|
|
362
|
-
_defaultInstanceId = id;
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
// Log so telemetry can detect free-tier auto-provision failures
|
|
366
|
-
void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
|
|
367
|
-
method: 'POST',
|
|
368
|
-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${JWT}` },
|
|
369
|
-
body: JSON.stringify({ event: 'auto_provision_failed', status: autoRes.status, version: CURRENT_VERSION }),
|
|
370
|
-
signal: AbortSignal.timeout(3000),
|
|
371
|
-
}).catch(() => { });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
catch { /* non-fatal — server unreachable, getConnection will surface the error */ }
|
|
375
|
-
}
|
|
376
412
|
// Give a provisioning instance a head-start so getConnection's wait is shorter.
|
|
377
413
|
// The instance is almost always ready by the time the user's tool re-enters.
|
|
378
414
|
if (_defaultInstanceId) {
|
|
@@ -405,37 +441,10 @@ async function pollDeviceFlow(flow) {
|
|
|
405
441
|
}
|
|
406
442
|
catch { /* non-fatal — getConnection will handle the wait if still provisioning */ }
|
|
407
443
|
}
|
|
408
|
-
// Persist instance_id to ~/.claude/mcp.json after it's known
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
void (async () => {
|
|
413
|
-
try {
|
|
414
|
-
const { writeFile, readFile } = await import('node:fs/promises');
|
|
415
|
-
const { existsSync } = await import('node:fs');
|
|
416
|
-
const { resolve } = await import('node:path');
|
|
417
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
|
|
418
|
-
if (!home)
|
|
419
|
-
return;
|
|
420
|
-
const configPath = resolve(home, '.claude', 'mcp.json');
|
|
421
|
-
if (!existsSync(configPath))
|
|
422
|
-
return;
|
|
423
|
-
let cfg = {};
|
|
424
|
-
try {
|
|
425
|
-
cfg = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
426
|
-
}
|
|
427
|
-
catch {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const entry = cfg.mcpServers?.['cachly'];
|
|
431
|
-
if (entry?.env) {
|
|
432
|
-
entry.env['CACHLY_BRAIN_INSTANCE_ID'] = _defaultInstanceId;
|
|
433
|
-
await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
catch { /* non-critical */ }
|
|
437
|
-
})();
|
|
438
|
-
}
|
|
444
|
+
// Persist instance_id to ~/.claude/mcp.json after it's known, so every
|
|
445
|
+
// restart reuses it instead of making an extra list call on startup.
|
|
446
|
+
if (_defaultInstanceId)
|
|
447
|
+
void persistInstanceIdToConfig(_defaultInstanceId);
|
|
439
448
|
return 'done';
|
|
440
449
|
}
|
|
441
450
|
if (data.error === 'slow_down')
|
|
@@ -457,10 +466,16 @@ export { tokenize, splitMultiQuery, levenshtein, recencyBoost, extractTimestamp,
|
|
|
457
466
|
const pool = new Map();
|
|
458
467
|
async function getConnection(instance_id) {
|
|
459
468
|
if (!instance_id) {
|
|
460
|
-
|
|
469
|
+
// With auto-provisioning, this only happens when the API was briefly
|
|
470
|
+
// unreachable during resolution. The next tool call self-heals, so lead
|
|
471
|
+
// with "retry" rather than asking the user to configure anything.
|
|
472
|
+
throw new McpError(ErrorCode.InvalidRequest, '⏳ Your Brain instance is still being set up.\n\n' +
|
|
473
|
+
'This usually resolves in a few seconds — just call the tool again and it ' +
|
|
474
|
+
'will connect automatically.\n\n' +
|
|
475
|
+
'If it keeps happening:\n' +
|
|
476
|
+
'• Run `list_instances` to check your instances, or\n' +
|
|
461
477
|
'• Set CACHLY_BRAIN_INSTANCE_ID in your MCP config, or\n' +
|
|
462
|
-
'•
|
|
463
|
-
'• Run `list_instances` to see your available instances.');
|
|
478
|
+
'• Reach us at support@cachly.dev.');
|
|
464
479
|
}
|
|
465
480
|
if (pool.has(instance_id))
|
|
466
481
|
return pool.get(instance_id);
|
|
@@ -732,7 +747,7 @@ async function handleTool(name, args) {
|
|
|
732
747
|
'',
|
|
733
748
|
'After sign-in: call **any tool again** — your Brain activates instantly.',
|
|
734
749
|
'',
|
|
735
|
-
'✨ Free forever · No credit card ·
|
|
750
|
+
'✨ Free forever · No credit card · 114 MCP tools · GDPR · EU servers',
|
|
736
751
|
].join('\n');
|
|
737
752
|
}
|
|
738
753
|
// Device flow unavailable (network issue) — fall back to manual setup
|
|
@@ -2056,8 +2071,9 @@ if (!process.argv[2] && process.stdout.isTTY) {
|
|
|
2056
2071
|
console.log(' cachly gives it a permanent brain — pre-briefed every session.');
|
|
2057
2072
|
console.log('');
|
|
2058
2073
|
console.log(' \x1b[1mCommands:\x1b[0m');
|
|
2059
|
-
console.log(' \x1b[32m npx @cachly-dev/mcp-server@latest
|
|
2060
|
-
console.log(' \x1b[32m npx @cachly-dev/mcp-server@latest
|
|
2074
|
+
console.log(' \x1b[1m\x1b[32m npx @cachly-dev/mcp-server@latest autopilot\x1b[0m ← \x1b[1mOne command, fully ready\x1b[0m');
|
|
2075
|
+
console.log(' \x1b[32m npx @cachly-dev/mcp-server@latest demo\x1b[0m ← Try it first (no account)');
|
|
2076
|
+
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest setup\x1b[0m ← Interactive setup (pick editors)');
|
|
2061
2077
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest health\x1b[0m ← Check everything works');
|
|
2062
2078
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest digest\x1b[0m ← Weekly Brain summary');
|
|
2063
2079
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest share\x1b[0m ← Share your Brain stats');
|
|
@@ -2067,7 +2083,7 @@ if (!process.argv[2] && process.stdout.isTTY) {
|
|
|
2067
2083
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest upgrade\x1b[0m ← Check for updates');
|
|
2068
2084
|
console.log('');
|
|
2069
2085
|
console.log(' \x1b[90mWorks with: Claude Code · Cursor · Windsurf · GitHub Copilot · Cline · Zed\x1b[0m');
|
|
2070
|
-
console.log(' \x1b[90mFree forever · GDPR · German servers ·
|
|
2086
|
+
console.log(' \x1b[90mFree forever · GDPR · German servers · 114 MCP tools\x1b[0m');
|
|
2071
2087
|
console.log('');
|
|
2072
2088
|
process.exit(0);
|
|
2073
2089
|
}
|
|
@@ -2483,7 +2499,11 @@ if (process.argv[2] === 'health') {
|
|
|
2483
2499
|
}
|
|
2484
2500
|
// ── CLI: cachly setup (interactive — no flags required) ───────────────────────
|
|
2485
2501
|
// Usage: npx @cachly-dev/mcp-server setup
|
|
2486
|
-
|
|
2502
|
+
// `autopilot` is the one-command entrypoint: it runs the exact same wizard as
|
|
2503
|
+
// `setup` but fully automatic (no prompts) — auth → instance → all editor
|
|
2504
|
+
// configs → CLAUDE.md → git/starter bootstrap → health, in a single shot.
|
|
2505
|
+
const _isAutopilotCli = process.argv[2] === 'autopilot';
|
|
2506
|
+
if (process.argv[2] === 'setup' || _isAutopilotCli) {
|
|
2487
2507
|
const { writeFile, mkdir, readFile } = await import('node:fs/promises');
|
|
2488
2508
|
const { existsSync } = await import('node:fs');
|
|
2489
2509
|
const { resolve, dirname } = await import('node:path');
|
|
@@ -2502,8 +2522,12 @@ if (process.argv[2] === 'setup') {
|
|
|
2502
2522
|
// a readline question against a non-TTY stdin never resolves and hangs the
|
|
2503
2523
|
// wizard forever. Treat that exactly like --yes so we never block.
|
|
2504
2524
|
const stdinIsInteractive = process.stdin.isTTY === true;
|
|
2505
|
-
|
|
2506
|
-
|
|
2525
|
+
// `autopilot` is always fully automatic — it IS the one-step onboarding.
|
|
2526
|
+
const nonInteractive = _isAutopilotCli || process.argv.includes('--yes') || process.argv.includes('-y') || !stdinIsInteractive;
|
|
2527
|
+
if (_isAutopilotCli) {
|
|
2528
|
+
console.log('🤖 Autopilot — one-command setup (auth → instance → configs → Brain), zero prompts.\n');
|
|
2529
|
+
}
|
|
2530
|
+
else if (!stdinIsInteractive && !process.argv.includes('--yes') && !process.argv.includes('-y')) {
|
|
2507
2531
|
console.log('ℹ️ Non-interactive terminal detected — running in automatic mode (no prompts).\n');
|
|
2508
2532
|
}
|
|
2509
2533
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -2872,22 +2896,40 @@ if (process.argv[2] === 'setup') {
|
|
|
2872
2896
|
// ── Step 5b: Bootstrap Brain from git history ─────────────────────────────
|
|
2873
2897
|
// Pre-populates the Brain with real lessons so the first session_start shows
|
|
2874
2898
|
// actual project knowledge instead of an empty brain.
|
|
2899
|
+
JWT = token; // set global JWT so handleTool can authenticate
|
|
2900
|
+
let gitLessonCount = 0;
|
|
2875
2901
|
if (existsSync(resolve(cwd, '.git'))) {
|
|
2876
2902
|
process.stdout.write('⏳ Bootstrapping Brain from git history (~10s)...');
|
|
2877
2903
|
try {
|
|
2878
|
-
JWT = token; // set global JWT so handleTool can authenticate
|
|
2879
2904
|
const gitResult = await handleTool('brain_from_git', {
|
|
2880
2905
|
instance_id: instance.id,
|
|
2881
2906
|
repo_path: cwd,
|
|
2882
2907
|
limit: 100,
|
|
2883
2908
|
});
|
|
2884
2909
|
const match = gitResult.match(/(\d+) lesson/);
|
|
2910
|
+
gitLessonCount = match ? parseInt(match[1], 10) : 0;
|
|
2885
2911
|
console.log(` ✓ ${match ? match[1] + ' lessons' : 'done'} extracted from git history`);
|
|
2886
2912
|
}
|
|
2887
2913
|
catch (e) {
|
|
2888
2914
|
console.log(` (skipped: ${e.message.slice(0, 80)})`);
|
|
2889
2915
|
}
|
|
2890
2916
|
}
|
|
2917
|
+
// ── Step 5c: Seed the starter corpus when git yielded nothing ─────────────
|
|
2918
|
+
// Closes the empty-brain gap: a fresh repo (no .git, shallow clone, or no
|
|
2919
|
+
// fix-shaped commits) would otherwise leave the Brain empty and the very first
|
|
2920
|
+
// smart_recall returning nothing. Seeding 16 curated universal lessons makes
|
|
2921
|
+
// the single setup command produce an immediately-useful Brain — true 1-step
|
|
2922
|
+
// onboarding. Idempotent; never overwrites real lessons.
|
|
2923
|
+
if (gitLessonCount === 0) {
|
|
2924
|
+
process.stdout.write('🌱 Seeding 16 universal engineering lessons (first recall works instantly)...');
|
|
2925
|
+
try {
|
|
2926
|
+
await handleTool('brain_seed_starter', { instance_id: instance.id });
|
|
2927
|
+
console.log(' ✓');
|
|
2928
|
+
}
|
|
2929
|
+
catch (e) {
|
|
2930
|
+
console.log(` (skipped: ${e.message.slice(0, 60)})`);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2891
2933
|
// ── Step 6: Show Brain health (Aha moment) ────────────────────────────────
|
|
2892
2934
|
// Fetch brain health from the API to show the user what their agent will see.
|
|
2893
2935
|
process.stdout.write('\n⏳ Fetching your Brain health preview...');
|
|
@@ -3174,7 +3216,7 @@ if (!JWT) {
|
|
|
3174
3216
|
// • Editor as an MCP stdio server (non-TTY) → ONE stderr hint, then KEEP RUNNING
|
|
3175
3217
|
// so tools/list works and the first tool call starts the browser sign-in.
|
|
3176
3218
|
// Skip entirely for CLI subcommands that intentionally run without credentials.
|
|
3177
|
-
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade', 'tool-specs', 'openapi'];
|
|
3219
|
+
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'setup', 'autopilot', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade', 'tool-specs', 'openapi'];
|
|
3178
3220
|
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')) {
|
|
3179
3221
|
const runningInTerminal = !process.argv[2] && process.stdout.isTTY === true && _isMain;
|
|
3180
3222
|
if (runningInTerminal) {
|