@vespermcp/setup 3.0.1 → 3.1.1

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/wizard.js +142 -13
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Interactive setup for the Vesper MCP stack: link your [getvesper.dev](https://getvesper.dev) account, store your API key locally, and auto-write MCP config for Cursor, Claude, VS Code, Codex, and Gemini CLI.
4
4
 
5
+ The wizard also asks whether you want **web URL extraction** (`vesper.extract_web`). If you opt in and have **Docker**, it runs **`docker pull`** / **`docker run`** for the official [Crawl4AI](https://hub.docker.com/r/unclecode/crawl4ai) image and sets `VESPER_CRAWL4AI_URL` in your MCP env — no separate web search required.
6
+
5
7
  ## Run
6
8
 
7
9
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vespermcp/setup",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "description": "Zero-friction setup wizard for Vesper — local MCP server, unified dataset API, and agent auto-config in 60 seconds",
5
5
  "bin": {
6
6
  "setup": "wizard.js"
package/wizard.js CHANGED
@@ -388,13 +388,18 @@ function buildMcpServerEntry() {
388
388
  const npxCmd = IS_WIN ? 'npx.cmd' : 'npx';
389
389
  const state = readToml(CONFIG_TOML);
390
390
  const apiKey = String(state.api_key || '').trim();
391
+ const crawl4aiUrl = String(state.crawl4ai_url || '').trim();
392
+ const env = {
393
+ VESPER_API_URL: getMcpVesperApiUrl(),
394
+ VESPER_API_KEY: apiKey || 'your-key-from-getvesper.dev',
395
+ };
396
+ if (crawl4aiUrl) {
397
+ env.VESPER_CRAWL4AI_URL = crawl4aiUrl;
398
+ }
391
399
  return {
392
400
  command: npxCmd,
393
401
  args: ['-y', '-p', '@vespermcp/mcp-server@latest', 'vespermcp'],
394
- env: {
395
- VESPER_API_URL: getMcpVesperApiUrl(),
396
- VESPER_API_KEY: apiKey || 'your-key-from-getvesper.dev',
397
- },
402
+ env,
398
403
  };
399
404
  }
400
405
 
@@ -405,13 +410,17 @@ function escapeTomlDoubleQuoted(value) {
405
410
  function upsertTomlMcpVesperBlock(content, serverEntry) {
406
411
  const key = escapeTomlDoubleQuoted(serverEntry.env.VESPER_API_KEY);
407
412
  const url = escapeTomlDoubleQuoted(serverEntry.env.VESPER_API_URL);
413
+ const crawlLine = serverEntry.env.VESPER_CRAWL4AI_URL
414
+ ? `VESPER_CRAWL4AI_URL = "${escapeTomlDoubleQuoted(serverEntry.env.VESPER_CRAWL4AI_URL)}"\n`
415
+ : '';
408
416
  const block =
409
417
  `[mcp_servers.vesper]\n` +
410
418
  `command = "${serverEntry.command}"\n` +
411
419
  `args = [${serverEntry.args.map((a) => `"${a}"`).join(', ')}]\n\n` +
412
420
  `[mcp_servers.vesper.env]\n` +
413
421
  `VESPER_API_KEY = "${key}"\n` +
414
- `VESPER_API_URL = "${url}"\n`;
422
+ `VESPER_API_URL = "${url}"\n` +
423
+ crawlLine;
415
424
  const re = /\[mcp_servers\.vesper\][\s\S]*?(?=\n\[|$)/;
416
425
  if (re.test(content)) {
417
426
  return content.replace(re, block.trim() + '\n');
@@ -512,6 +521,106 @@ async function checkServerHealth() {
512
521
  }
513
522
  }
514
523
 
524
+ const CRAWL4AI_CONTAINER = 'vesper-crawl4ai';
525
+ const CRAWL4AI_DEFAULT_URL = 'http://127.0.0.1:11235';
526
+
527
+ function printManualCrawl4AiInstructions() {
528
+ console.log(`\n ${dim('Run these in a terminal when Docker is available:')}`);
529
+ console.log(` ${cyan('docker pull unclecode/crawl4ai')}`);
530
+ console.log(
531
+ ` ${cyan(
532
+ `docker run -d --name ${CRAWL4AI_CONTAINER} -p 11235:11235 --shm-size=1g --restart unless-stopped unclecode/crawl4ai`,
533
+ )}`,
534
+ );
535
+ console.log(` ${dim('Then set in MCP env:')} ${cyan(`VESPER_CRAWL4AI_URL=${CRAWL4AI_DEFAULT_URL}`)}`);
536
+ console.log(` ${dim('Docs:')} https://github.com/vesper/mcp-server/blob/main/docs/web-extraction.md\n`);
537
+ }
538
+
539
+ function dockerCliWorks() {
540
+ const r = spawnSync('docker', ['info'], { encoding: 'utf8', timeout: 20000, stdio: ['pipe', 'pipe', 'pipe'] });
541
+ return r.status === 0;
542
+ }
543
+
544
+ function refreshMcpConfigsForAgents() {
545
+ const agents = getAllAgentConfigs();
546
+ for (const agent of agents) {
547
+ const dirExists = fs.existsSync(path.dirname(agent.path));
548
+ const fileExists = fs.existsSync(agent.path);
549
+ if (fileExists || dirExists) {
550
+ installMcpToAgent(agent);
551
+ }
552
+ }
553
+ }
554
+
555
+ async function runCrawl4AiDockerSetup() {
556
+ if (!dockerCliWorks()) {
557
+ console.log(` ${yellow('!')} ${yellow('Docker CLI did not respond (is Docker Desktop / the daemon running?)')}`);
558
+ printManualCrawl4AiInstructions();
559
+ return false;
560
+ }
561
+
562
+ console.log(`\n ${dim('Pulling image unclecode/crawl4ai (first run may take a few minutes)...')}`);
563
+ const pull = spawnSync('docker', ['pull', 'unclecode/crawl4ai'], {
564
+ stdio: 'inherit',
565
+ timeout: 600000,
566
+ });
567
+ if (pull.error) {
568
+ console.log(` ${red('✗')} docker pull failed: ${pull.error.message || pull.error}`);
569
+ printManualCrawl4AiInstructions();
570
+ return false;
571
+ }
572
+ if (pull.status !== 0) {
573
+ console.log(` ${red('✗')} docker pull exited with status ${pull.status}`);
574
+ printManualCrawl4AiInstructions();
575
+ return false;
576
+ }
577
+
578
+ const inspect = spawnSync('docker', ['inspect', CRAWL4AI_CONTAINER], {
579
+ encoding: 'utf8',
580
+ stdio: ['pipe', 'pipe', 'pipe'],
581
+ });
582
+ if (inspect.status === 0) {
583
+ console.log(` ${dim('Container')} ${cyan(CRAWL4AI_CONTAINER)} ${dim('exists — starting it...')}`);
584
+ const start = spawnSync('docker', ['start', CRAWL4AI_CONTAINER], { stdio: 'inherit', timeout: 120000 });
585
+ if (start.status !== 0) {
586
+ console.log(` ${yellow('!')} docker start failed — try: ${cyan(`docker start ${CRAWL4AI_CONTAINER}`)}`);
587
+ }
588
+ } else {
589
+ console.log(` ${dim('Creating container')} ${cyan(CRAWL4AI_CONTAINER)}...`);
590
+ const run = spawnSync(
591
+ 'docker',
592
+ [
593
+ 'run',
594
+ '-d',
595
+ '--name',
596
+ CRAWL4AI_CONTAINER,
597
+ '-p',
598
+ '11235:11235',
599
+ '--shm-size',
600
+ '1g',
601
+ '--restart',
602
+ 'unless-stopped',
603
+ 'unclecode/crawl4ai',
604
+ ],
605
+ { stdio: 'inherit', timeout: 180000 },
606
+ );
607
+ if (run.status !== 0) {
608
+ console.log(` ${red('✗')} docker run failed`);
609
+ printManualCrawl4AiInstructions();
610
+ return false;
611
+ }
612
+ }
613
+
614
+ const merged = { ...readToml(CONFIG_TOML), crawl4ai_url: CRAWL4AI_DEFAULT_URL };
615
+ writeToml(CONFIG_TOML, merged);
616
+ console.log(` ${green('✓')} Saved ${dim('crawl4ai_url')} = ${cyan(CRAWL4AI_DEFAULT_URL)} ${dim('→')} ${dim(CONFIG_TOML)}`);
617
+
618
+ refreshMcpConfigsForAgents();
619
+ console.log(` ${green('✓')} Updated MCP client configs with ${cyan('VESPER_CRAWL4AI_URL')}`);
620
+ console.log(` ${dim('Crawl4AI API (after a few seconds):')} ${cyan(`${CRAWL4AI_DEFAULT_URL}/playground`)}`);
621
+ return true;
622
+ }
623
+
515
624
  // ── Main Wizard ──────────────────────────────────────────────
516
625
  async function main() {
517
626
  if (!isInteractiveTerminal()) {
@@ -526,7 +635,7 @@ async function main() {
526
635
  console.log(` ${green('→')} Setting up Vesper on ${bold(os.hostname())}\n`);
527
636
 
528
637
  // ─── Step 1: Create directories ────────────────────────────
529
- process.stdout.write(` ${dim('[')}${cyan('1/6')}${dim(']')} Creating local directories...`);
638
+ process.stdout.write(` ${dim('[')}${cyan('1/7')}${dim(']')} Creating local directories...`);
530
639
  ensureDir(VESPER_DIR);
531
640
  ensureDir(DATA_DIR);
532
641
  ensureDir(path.join(DATA_DIR, 'raw'));
@@ -535,7 +644,7 @@ async function main() {
535
644
  console.log(` ${green('✓')}`);
536
645
 
537
646
  // ─── Step 2: Authenticate (device flow or local key) ──────
538
- console.log(`\n ${dim('[')}${cyan('2/6')}${dim(']')} Authentication`);
647
+ console.log(`\n ${dim('[')}${cyan('2/7')}${dim(']')} Authentication`);
539
648
 
540
649
  const existing = readToml(CONFIG_TOML);
541
650
  let localKey = existing.api_key || '';
@@ -564,7 +673,7 @@ async function main() {
564
673
  console.log(` ${dim('Key:')} ${dim(localKey.slice(0, 24) + '...')} ${dim('→')} ${dim(CONFIG_TOML)}`);
565
674
 
566
675
  // ─── Step 3: Local vault initialization ────────────────────
567
- process.stdout.write(`\n ${dim('[')}${cyan('3/6')}${dim(']')} Initializing local credentials vault...`);
676
+ process.stdout.write(`\n ${dim('[')}${cyan('3/7')}${dim(']')} Initializing local credentials vault...`);
568
677
  const vaultData = readToml(CONFIG_TOML);
569
678
  if (!vaultData.auth_mode) vaultData.auth_mode = 'local_unified';
570
679
  writeToml(CONFIG_TOML, vaultData);
@@ -572,7 +681,7 @@ async function main() {
572
681
  console.log(` ${dim('Mode:')} ${dim(vaultData.auth_mode === 'cloud' ? 'cloud (linked to Vesper account)' : 'single local Vesper key (no external keys required)')}`);
573
682
 
574
683
  // ─── Step 4: Verify @vespermcp/mcp-server command ───────────
575
- console.log(`\n ${dim('[')}${cyan('4/6')}${dim(']')} Verifying Vesper MCP server command...`);
684
+ console.log(`\n ${dim('[')}${cyan('4/7')}${dim(']')} Verifying Vesper MCP server command...`);
576
685
  try {
577
686
  const npmCmd = IS_WIN ? 'npx.cmd' : 'npx';
578
687
  const verify = spawnSync(npmCmd, ['-y', '-p', '@vespermcp/mcp-server@latest', 'vespermcp', '--version'], {
@@ -590,7 +699,7 @@ async function main() {
590
699
  }
591
700
 
592
701
  // ─── Step 5: Auto-configure all detected IDEs ──────────────
593
- process.stdout.write(`\n ${dim('[')}${cyan('5/6')}${dim(']')} Configuring coding agents...`);
702
+ process.stdout.write(`\n ${dim('[')}${cyan('5/7')}${dim(']')} Configuring coding agents...`);
594
703
  const agents = getAllAgentConfigs();
595
704
  const configuredAgents = [];
596
705
  const skippedAgents = [];
@@ -616,8 +725,27 @@ async function main() {
616
725
  console.log(` └───────────────────────────────────────────────┘`);
617
726
  }
618
727
 
619
- // ─── Step 6: Verify ────────────────────────────────────────
620
- console.log(`\n ${dim('[')}${cyan('6/6')}${dim(']')} Verifying installation...`);
728
+ // ─── Step 6: Web extraction (Crawl4AI + Docker) ─────────────
729
+ console.log(`\n ${dim('[')}${cyan('6/7')}${dim(']')} Web extraction (optional)`);
730
+ console.log(
731
+ ` ${dim('Tools like')} ${cyan('vesper.extract_web')} ${dim('use Crawl4AI. The easiest way is Docker on your machine (no extra API keys).')}\n`,
732
+ );
733
+
734
+ const wantWebExtraction = await askYesNo(`${cyan('→')} Enable web URL extraction (Crawl4AI + Docker)?`);
735
+ if (wantWebExtraction) {
736
+ const hasDocker = await askYesNo(`${cyan('→')} Do you have Docker installed and running (Docker Desktop / engine)?`);
737
+ if (hasDocker) {
738
+ await runCrawl4AiDockerSetup();
739
+ } else {
740
+ console.log(`\n ${dim('Install Docker from https://docs.docker.com/get-docker/ then run the commands below, or re-run this wizard.')}`);
741
+ printManualCrawl4AiInstructions();
742
+ }
743
+ } else {
744
+ console.log(` ${dim('Skipped. To enable later, see docs/web-extraction.md')}`);
745
+ }
746
+
747
+ // ─── Step 7: Verify ────────────────────────────────────────
748
+ console.log(`\n ${dim('[')}${cyan('7/7')}${dim(']')} Verifying installation...`);
621
749
 
622
750
  const dbExists = fs.existsSync(path.join(DATA_DIR, 'metadata.db'));
623
751
  const vecExists = fs.existsSync(path.join(DATA_DIR, 'vectors.json')) || fs.existsSync(path.join(DATA_DIR, 'vectors.bin'));
@@ -649,7 +777,8 @@ ${dim('════════════════════════
649
777
  ${dim('1.')} ${isCloud ? 'Linked to your Vesper cloud account' : 'Generated a local API key (never leaves your machine)'}
650
778
  ${dim('2.')} Initialized local credentials vault
651
779
  ${dim('3.')} Auto-configured MCP for ${configuredAgents.length > 0 ? configuredAgents.join(', ') : 'detected agents'}
652
- ${dim('4.')} Vesper server ready on stdio transport
780
+ ${dim('4.')} Optional: Crawl4AI for web extraction (${dim('vesper.extract_web')}) — Docker step if you chose it
781
+ ${dim('5.')} Vesper server ready on stdio transport
653
782
 
654
783
  ${dim('─────────────────────────────────────────────────')}
655
784