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.
- package/dist/index.js +136 -63
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
${
|
|
167
|
-
${
|
|
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
|
-
`${
|
|
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
|
-
`${
|
|
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
|
-
`${
|
|
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
|
|
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
|
-
`${
|
|
429
|
-
${
|
|
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(`${
|
|
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${
|
|
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(`${
|
|
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(`${
|
|
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
|
-
`${
|
|
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(`${
|
|
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
|
-
${
|
|
578
|
+
${pc6.green("MCP ready")}
|
|
507
579
|
`);
|
|
508
|
-
process.stdout.write(`${
|
|
580
|
+
process.stdout.write(`${pc6.bold("Server:")} ${finalDetail.mcp.serverName}
|
|
509
581
|
`);
|
|
510
|
-
process.stdout.write(`${
|
|
582
|
+
process.stdout.write(`${pc6.bold("URL:")} ${finalDetail.mcp.url}
|
|
511
583
|
`);
|
|
512
|
-
process.stdout.write(`${
|
|
584
|
+
process.stdout.write(`${pc6.bold("Token:")} ${finalDetail.mcp.token}
|
|
513
585
|
`);
|
|
514
586
|
process.stdout.write(
|
|
515
|
-
`${
|
|
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(`${
|
|
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
|
-
`${
|
|
614
|
+
`${pc6.bold(project.name)} ${pc6.dim(`[${project.status}]`)} ${project.source}
|
|
543
615
|
`
|
|
544
616
|
);
|
|
545
|
-
process.stdout.write(` ${
|
|
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
|
-
`${
|
|
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(`${
|
|
664
|
+
process.stdout.write(`${pc7.dim("Ready MCPs")}
|
|
593
665
|
`);
|
|
594
666
|
ready.forEach((p, index) => {
|
|
595
667
|
process.stdout.write(
|
|
596
|
-
` ${
|
|
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
|
-
`${
|
|
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:
|
|
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:
|
|
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
|
-
`${
|
|
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(`${
|
|
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:
|
|
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
|
-
${
|
|
742
|
+
${pc7.green("\u25CF")} ${pc7.bold("doc2mcp")}
|
|
743
|
+
|
|
673
744
|
`);
|
|
674
|
-
|
|
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(
|
|
680
|
-
${
|
|
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
|
-
|
|
753
|
+
` ${pc7.cyan("\u2022")} ${source.title}
|
|
754
|
+
${pc7.dim(source.url)}
|
|
685
755
|
`
|
|
686
756
|
);
|
|
687
757
|
}
|
|
688
758
|
}
|
|
689
|
-
process.stdout.write(
|
|
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
|
-
`${
|
|
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
|
-
|
|
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(`${
|
|
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(
|
|
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.
|
|
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
|
-
`${
|
|
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(`${
|
|
877
|
+
process.stderr.write(`${pc8.red("Error:")} ${message}
|
|
805
878
|
`);
|
|
806
879
|
process.exit(1);
|
|
807
880
|
});
|
package/package.json
CHANGED