doc2mcp 0.1.18 → 0.1.19

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 (2) hide show
  1. package/dist/index.js +136 -63
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc7 from "picocolors";
5
+ import pc8 from "picocolors";
6
6
 
7
7
  // src/commands/account.ts
8
8
  import pc from "picocolors";
@@ -77,7 +77,7 @@ async function runWhoami() {
77
77
  import { createInterface } from "readline/promises";
78
78
  import { stdin as input, stdout as output } from "process";
79
79
  import ora2 from "ora";
80
- import pc6 from "picocolors";
80
+ import pc7 from "picocolors";
81
81
 
82
82
  // src/api.ts
83
83
  import pc2 from "picocolors";
@@ -139,13 +139,85 @@ function printError(error) {
139
139
  `);
140
140
  }
141
141
 
142
+ // src/markdown.ts
143
+ import pc3 from "picocolors";
144
+ var LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
145
+ var INLINE_CODE = /`([^`]+)`/g;
146
+ var BOLD = /\*\*([^*]+)\*\*/g;
147
+ var ITALIC = /(^|[^*])\*([^*\n]+)\*/g;
148
+ var HEADING = /^(#{1,6})\s+(.*)$/;
149
+ var BULLET = /^(\s*)[-*]\s+(.*)$/;
150
+ var ORDERED = /^(\s*)(\d+)\.\s+(.*)$/;
151
+ var FENCE = /^```(\w*)\s*$/;
152
+ var BLOCKQUOTE = /^>\s?(.*)$/;
153
+ function renderInline(text) {
154
+ let out = text.replace(
155
+ LINK,
156
+ (_m, label, url) => `${pc3.cyan(pc3.underline(label))} ${pc3.dim(`(${url})`)}`
157
+ );
158
+ out = out.replace(INLINE_CODE, (_m, code) => pc3.yellow(code));
159
+ out = out.replace(BOLD, (_m, bold) => pc3.bold(bold));
160
+ out = out.replace(
161
+ ITALIC,
162
+ (_m, prefix, italic) => `${prefix}${pc3.italic(italic)}`
163
+ );
164
+ return out;
165
+ }
166
+ function renderMarkdown(markdown) {
167
+ const lines = markdown.split("\n");
168
+ const out = [];
169
+ let inCode = false;
170
+ for (const line of lines) {
171
+ const fence = line.match(FENCE);
172
+ if (fence) {
173
+ if (inCode) {
174
+ out.push(pc3.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500"));
175
+ inCode = false;
176
+ } else {
177
+ const lang = fence[1] || "code";
178
+ out.push(pc3.dim(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500 ${lang}`));
179
+ inCode = true;
180
+ }
181
+ continue;
182
+ }
183
+ if (inCode) {
184
+ out.push(` ${pc3.green(line)}`);
185
+ continue;
186
+ }
187
+ const heading = line.match(HEADING);
188
+ if (heading) {
189
+ out.push(pc3.bold(pc3.cyan(renderInline(heading[2] ?? ""))));
190
+ continue;
191
+ }
192
+ const ordered = line.match(ORDERED);
193
+ if (ordered) {
194
+ out.push(
195
+ `${ordered[1] ?? ""}${pc3.cyan(`${ordered[2] ?? ""}.`)} ${renderInline(ordered[3] ?? "")}`
196
+ );
197
+ continue;
198
+ }
199
+ const bullet = line.match(BULLET);
200
+ if (bullet) {
201
+ out.push(`${bullet[1] ?? ""}${pc3.cyan("\u2022")} ${renderInline(bullet[2] ?? "")}`);
202
+ continue;
203
+ }
204
+ const quote = line.match(BLOCKQUOTE);
205
+ if (quote) {
206
+ out.push(`${pc3.dim("\u2502")} ${pc3.dim(renderInline(quote[1] ?? ""))}`);
207
+ continue;
208
+ }
209
+ out.push(renderInline(line));
210
+ }
211
+ return out.join("\n");
212
+ }
213
+
142
214
  // src/commands/convert.ts
143
- import pc5 from "picocolors";
215
+ import pc6 from "picocolors";
144
216
 
145
217
  // src/commands/login.ts
146
218
  import open from "open";
147
219
  import ora from "ora";
148
- import pc3 from "picocolors";
220
+ import pc4 from "picocolors";
149
221
  function sleep(ms) {
150
222
  return new Promise((resolve) => {
151
223
  setTimeout(resolve, ms);
@@ -163,13 +235,13 @@ async function runLogin() {
163
235
  spinner.stop();
164
236
  process.stdout.write(
165
237
  `
166
- ${pc3.cyan("Open this link to authorize:")}
167
- ${pc3.bold(start.verifyUrl)}
238
+ ${pc4.cyan("Open this link to authorize:")}
239
+ ${pc4.bold(start.verifyUrl)}
168
240
 
169
241
  `
170
242
  );
171
243
  process.stdout.write(
172
- `${pc3.dim("Code:")} ${pc3.bold(start.userCode)} ${pc3.dim("(also shown in browser)")}
244
+ `${pc4.dim("Code:")} ${pc4.bold(start.userCode)} ${pc4.dim("(also shown in browser)")}
173
245
 
174
246
  `
175
247
  );
@@ -177,7 +249,7 @@ ${pc3.bold(start.verifyUrl)}
177
249
  await open(start.verifyUrl);
178
250
  } catch {
179
251
  process.stdout.write(
180
- `${pc3.yellow("Could not auto-open browser. Open the link manually.")}
252
+ `${pc4.yellow("Could not auto-open browser. Open the link manually.")}
181
253
 
182
254
  `
183
255
  );
@@ -203,7 +275,7 @@ ${pc3.bold(start.verifyUrl)}
203
275
  user: poll.user
204
276
  });
205
277
  process.stdout.write(
206
- `${pc3.green("Logged in as")} ${poll.user.email}
278
+ `${pc4.green("Logged in as")} ${poll.user.email}
207
279
  `
208
280
  );
209
281
  return;
@@ -238,7 +310,7 @@ async function ensureLoggedIn() {
238
310
 
239
311
  // src/commands/install.ts
240
312
  import { confirm, multiselect } from "@clack/prompts";
241
- import pc4 from "picocolors";
313
+ import pc5 from "picocolors";
242
314
 
243
315
  // src/installers/detect.ts
244
316
  import { access } from "fs/promises";
@@ -425,8 +497,8 @@ async function promptInstall(install) {
425
497
  }
426
498
  await installToClient(client.id, client.configPath, install);
427
499
  process.stdout.write(
428
- `${pc4.green("Installed")} ${client.label}
429
- ${pc4.dim(client.configPath)}
500
+ `${pc5.green("Installed")} ${client.label}
501
+ ${pc5.dim(client.configPath)}
430
502
  `
431
503
  );
432
504
  }
@@ -436,7 +508,7 @@ async function runInstallCommand(projectId) {
436
508
  await ensureLoggedIn();
437
509
  const detail = await apiFetch(`/api/cli/projects/${projectId}`);
438
510
  if (!detail.install) {
439
- process.stderr.write(`${pc4.red("Project is not ready or missing install bundle.")}
511
+ process.stderr.write(`${pc5.red("Project is not ready or missing install bundle.")}
440
512
  `);
441
513
  process.exitCode = 1;
442
514
  return;
@@ -457,18 +529,18 @@ function sleep2(ms) {
457
529
  function printStatus(detail) {
458
530
  const { project } = detail;
459
531
  process.stdout.write(
460
- `\r${pc5.cyan("Status:")} ${project.status.padEnd(12)} ${pc5.dim(project.name)}`
532
+ `\r${pc6.cyan("Status:")} ${project.status.padEnd(12)} ${pc6.dim(project.name)}`
461
533
  );
462
534
  }
463
535
  async function convertUrlToProject(sourceUrl, options = { offerInstall: true }) {
464
536
  await ensureLoggedIn();
465
- process.stdout.write(`${pc5.bold("Converting")} ${sourceUrl}
537
+ process.stdout.write(`${pc6.bold("Converting")} ${sourceUrl}
466
538
  `);
467
539
  const created = await apiFetch("/api/cli/convert", {
468
540
  method: "POST",
469
541
  body: JSON.stringify({ sourceUrl })
470
542
  });
471
- process.stdout.write(`${pc5.dim("Project:")} ${created.id}
543
+ process.stdout.write(`${pc6.dim("Project:")} ${created.id}
472
544
  `);
473
545
  let delayMs = 2e3;
474
546
  const terminal = /* @__PURE__ */ new Set(["ready", "error"]);
@@ -490,29 +562,29 @@ async function convertUrlToProject(sourceUrl, options = { offerInstall: true })
490
562
  if (finalDetail.project.status === "error") {
491
563
  const lastLog = finalDetail.project.logs.at(-1);
492
564
  process.stderr.write(
493
- `${pc5.red("Conversion failed.")}${lastLog ? ` ${lastLog.message}` : ""}
565
+ `${pc6.red("Conversion failed.")}${lastLog ? ` ${lastLog.message}` : ""}
494
566
  `
495
567
  );
496
568
  process.exitCode = 1;
497
569
  return null;
498
570
  }
499
571
  if (!finalDetail.mcp || !finalDetail.install) {
500
- process.stderr.write(`${pc5.red("MCP ready but missing install bundle.")}
572
+ process.stderr.write(`${pc6.red("MCP ready but missing install bundle.")}
501
573
  `);
502
574
  process.exitCode = 1;
503
575
  return null;
504
576
  }
505
577
  process.stdout.write(`
506
- ${pc5.green("MCP ready")}
578
+ ${pc6.green("MCP ready")}
507
579
  `);
508
- process.stdout.write(`${pc5.bold("Server:")} ${finalDetail.mcp.serverName}
580
+ process.stdout.write(`${pc6.bold("Server:")} ${finalDetail.mcp.serverName}
509
581
  `);
510
- process.stdout.write(`${pc5.bold("URL:")} ${finalDetail.mcp.url}
582
+ process.stdout.write(`${pc6.bold("URL:")} ${finalDetail.mcp.url}
511
583
  `);
512
- process.stdout.write(`${pc5.bold("Token:")} ${finalDetail.mcp.token}
584
+ process.stdout.write(`${pc6.bold("Token:")} ${finalDetail.mcp.token}
513
585
  `);
514
586
  process.stdout.write(
515
- `${pc5.dim("Also listed in the doc2mcp marketplace when ready.")}
587
+ `${pc6.dim("Also listed in the doc2mcp marketplace when ready.")}
516
588
  `
517
589
  );
518
590
  if (options.offerInstall) {
@@ -533,16 +605,16 @@ async function runList() {
533
605
  await ensureLoggedIn();
534
606
  const data = await apiFetch("/api/cli/projects");
535
607
  if (data.projects.length === 0) {
536
- process.stdout.write(`${pc5.dim("No projects yet.")}
608
+ process.stdout.write(`${pc6.dim("No projects yet.")}
537
609
  `);
538
610
  return;
539
611
  }
540
612
  for (const project of data.projects) {
541
613
  process.stdout.write(
542
- `${pc5.bold(project.name)} ${pc5.dim(`[${project.status}]`)} ${project.source}
614
+ `${pc6.bold(project.name)} ${pc6.dim(`[${project.status}]`)} ${project.source}
543
615
  `
544
616
  );
545
- process.stdout.write(` ${pc5.dim(project.id)} ${project.sourceUrl ?? ""}
617
+ process.stdout.write(` ${pc6.dim(project.id)} ${project.sourceUrl ?? ""}
546
618
  `);
547
619
  }
548
620
  } catch (error) {
@@ -584,27 +656,27 @@ async function pickExistingProject() {
584
656
  const ready = await listReadyProjects();
585
657
  if (ready.length === 0) {
586
658
  process.stdout.write(
587
- `${pc6.yellow("No ready MCP projects yet.")} Paste a docs URL to create one.
659
+ `${pc7.yellow("No ready MCP projects yet.")} Paste a docs URL to create one.
588
660
  `
589
661
  );
590
662
  return null;
591
663
  }
592
- process.stdout.write(`${pc6.dim("Ready MCPs")}
664
+ process.stdout.write(`${pc7.dim("Ready MCPs")}
593
665
  `);
594
666
  ready.forEach((p, index) => {
595
667
  process.stdout.write(
596
- ` ${pc6.cyan(String(index + 1).padStart(2, " "))}. ${pc6.bold(p.name)} ${pc6.dim(p.sourceUrl ?? p.id)}
668
+ ` ${pc7.cyan(String(index + 1).padStart(2, " "))}. ${pc7.bold(p.name)} ${pc7.dim(p.sourceUrl ?? p.id)}
597
669
  `
598
670
  );
599
671
  });
600
672
  const choice = await readLine(
601
- `${pc6.bold(">")} choose number, paste URL, or paste project id: `
673
+ `${pc7.bold(">")} choose number, paste URL, or paste project id: `
602
674
  );
603
675
  if (!choice) {
604
676
  return null;
605
677
  }
606
678
  if (isDocsUrl(choice)) {
607
- return await convertUrlToProject(choice, { offerInstall: false });
679
+ return await convertUrlToProject(choice, { offerInstall: true });
608
680
  }
609
681
  const chosenIndex = Number(choice);
610
682
  if (Number.isInteger(chosenIndex) && chosenIndex > 0) {
@@ -618,23 +690,21 @@ async function pickExistingProject() {
618
690
  async function resolveProject(target) {
619
691
  if (target) {
620
692
  if (isDocsUrl(target)) {
621
- return await convertUrlToProject(target, { offerInstall: false });
693
+ return await convertUrlToProject(target, { offerInstall: true });
622
694
  }
623
695
  return await apiFetch(`/api/cli/projects/${target}`);
624
696
  }
625
- process.stdout.write(`${pc6.bold("doc2mcp chat")}
626
- `);
627
697
  process.stdout.write(
628
- `${pc6.dim("Paste a docs URL to create a new MCP, paste a project id, or press Enter to choose existing.")}
698
+ `${pc7.dim("Paste a docs URL to create a new MCP, a project id, or press Enter to pick an existing one.")}
629
699
 
630
700
  `
631
701
  );
632
- const first = await readLine(`${pc6.bold(">")} docs url or project id: `);
702
+ const first = await readLine(`${pc7.cyan("\u203A")} docs url or project id: `);
633
703
  if (!first) {
634
704
  return await pickExistingProject();
635
705
  }
636
706
  if (isDocsUrl(first)) {
637
- return await convertUrlToProject(first, { offerInstall: false });
707
+ return await convertUrlToProject(first, { offerInstall: true });
638
708
  }
639
709
  return await apiFetch(`/api/cli/projects/${first}`);
640
710
  }
@@ -669,26 +739,24 @@ async function askDocs(mcp, question) {
669
739
  }
670
740
  function renderAnswer(answer) {
671
741
  process.stdout.write(`
672
- ${pc6.cyan("\u256D\u2500 doc2mcp")}
742
+ ${pc7.green("\u25CF")} ${pc7.bold("doc2mcp")}
743
+
673
744
  `);
674
- for (const line of answer.answer.trim().split("\n")) {
675
- process.stdout.write(`${pc6.cyan("\u2502")} ${line}
745
+ process.stdout.write(`${renderMarkdown(answer.answer.trim())}
676
746
  `);
677
- }
678
747
  if (answer.sources && answer.sources.length > 0) {
679
- process.stdout.write(`${pc6.cyan("\u2502")}
680
- ${pc6.cyan("\u2502")} ${pc6.dim("Sources:")}
748
+ process.stdout.write(`
749
+ ${pc7.dim("Sources")}
681
750
  `);
682
751
  for (const source of answer.sources.slice(0, 6)) {
683
752
  process.stdout.write(
684
- `${pc6.cyan("\u2502")} ${pc6.dim("\u2022")} ${source.title} ${pc6.dim(source.url)}
753
+ ` ${pc7.cyan("\u2022")} ${source.title}
754
+ ${pc7.dim(source.url)}
685
755
  `
686
756
  );
687
757
  }
688
758
  }
689
- process.stdout.write(`${pc6.cyan("\u2570")}
690
-
691
- `);
759
+ process.stdout.write("\n");
692
760
  }
693
761
  async function answerOnce(mcp, question) {
694
762
  const spinner = ora2("Thinking\u2026").start();
@@ -710,7 +778,7 @@ async function runChat(target, options = {}) {
710
778
  }
711
779
  if (!detail.mcp) {
712
780
  process.stderr.write(
713
- `${pc6.red("That project is not ready yet.")} Check: ${pc6.bold("doc2mcp list")}
781
+ `${pc7.red("That project is not ready yet.")} Check: ${pc7.bold("doc2mcp list")}
714
782
  `
715
783
  );
716
784
  process.exitCode = 1;
@@ -721,21 +789,23 @@ async function runChat(target, options = {}) {
721
789
  await answerOnce(mcp, options.message);
722
790
  return;
723
791
  }
792
+ const title = ` doc2mcp chat \xB7 ${detail.project.name} `;
793
+ const bar = "\u2500".repeat(title.length);
794
+ process.stdout.write(`
795
+ ${pc7.cyan(`\u256D${bar}\u256E`)}
796
+ `);
797
+ process.stdout.write(`${pc7.cyan("\u2502")}${pc7.bold(title)}${pc7.cyan("\u2502")}
798
+ `);
799
+ process.stdout.write(`${pc7.cyan(`\u2570${bar}\u256F`)}
800
+ `);
724
801
  process.stdout.write(
725
- `
726
- ${pc6.cyan("\u256D\u2500")} ${pc6.bold(`Chatting with ${detail.project.name}`)}
727
- `
728
- );
729
- process.stdout.write(
730
- `${pc6.cyan("\u2502")} ${pc6.dim("Ask anything about these docs. Type /exit to leave.")}
802
+ `${pc7.dim("Ask anything about these docs. Type /exit to quit.")}
803
+
731
804
  `
732
805
  );
733
- process.stdout.write(`${pc6.cyan("\u2570")}
734
-
735
- `);
736
806
  let active = true;
737
807
  while (active) {
738
- const question = await readLine(`${pc6.bold(">")} `);
808
+ const question = await readLine(`${pc7.cyan("\u203A")} `);
739
809
  if (question === null) {
740
810
  active = false;
741
811
  break;
@@ -750,8 +820,11 @@ ${pc6.cyan("\u256D\u2500")} ${pc6.bold(`Chatting with ${detail.project.name}`)}
750
820
  }
751
821
  await answerOnce(mcp, trimmed);
752
822
  }
753
- process.stdout.write(`${pc6.dim("Bye \u2014 your docs MCP stays live for your editor.")}
754
- `);
823
+ process.stdout.write(
824
+ `
825
+ ${pc7.dim("Bye \u2014 your docs MCP stays live for your editor.")}
826
+ `
827
+ );
755
828
  } catch (error) {
756
829
  printError(error);
757
830
  process.exitCode = 1;
@@ -760,7 +833,7 @@ ${pc6.cyan("\u256D\u2500")} ${pc6.bold(`Chatting with ${detail.project.name}`)}
760
833
 
761
834
  // src/index.ts
762
835
  var program = new Command();
763
- program.name("doc2mcp").description("Generate documentation MCP servers from your terminal").version("0.1.18", "-v, --version", "Print the installed CLI version");
836
+ program.name("doc2mcp").description("Generate documentation MCP servers from your terminal").version("0.1.19", "-v, --version", "Print the installed CLI version");
764
837
  program.command("login").description("Authorize the CLI via browser").action(async () => {
765
838
  await runLogin();
766
839
  });
@@ -791,7 +864,7 @@ program.argument("[url]", "Documentation URL to convert").action(async (url) =>
791
864
  }
792
865
  } catch {
793
866
  process.stderr.write(
794
- `${pc7.red("Error:")} Invalid URL. Example: doc2mcp https://docs.example.com
867
+ `${pc8.red("Error:")} Invalid URL. Example: doc2mcp https://docs.example.com
795
868
  `
796
869
  );
797
870
  process.exitCode = 1;
@@ -801,7 +874,7 @@ program.argument("[url]", "Documentation URL to convert").action(async (url) =>
801
874
  });
802
875
  program.parseAsync(process.argv).catch((error) => {
803
876
  const message = error instanceof Error ? error.message : "Unknown error";
804
- process.stderr.write(`${pc7.red("Error:")} ${message}
877
+ process.stderr.write(`${pc8.red("Error:")} ${message}
805
878
  `);
806
879
  process.exit(1);
807
880
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc2mcp",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Turn any documentation site into a hosted MCP server from your terminal — for Cursor, Claude, VS Code, Windsurf, and OpenAI agents.",
5
5
  "type": "module",
6
6
  "bin": {