@latent-space-labs/open-auto-doc 0.5.2 → 0.5.3
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 +354 -153
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import net from "net";
|
|
|
10
10
|
import path10 from "path";
|
|
11
11
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12
12
|
import { spawn } from "child_process";
|
|
13
|
+
import { Octokit as Octokit4 } from "@octokit/rest";
|
|
13
14
|
|
|
14
15
|
// src/auth/device-flow.ts
|
|
15
16
|
import * as p from "@clack/prompts";
|
|
@@ -258,48 +259,58 @@ function getGitHubUsername(octokit) {
|
|
|
258
259
|
return octokit.rest.users.getAuthenticated().then((res) => res.data.login);
|
|
259
260
|
}
|
|
260
261
|
async function createAndPushDocsRepo(params) {
|
|
261
|
-
const { token, docsDir, config } = params;
|
|
262
|
+
const { token, docsDir, config, preCollected } = params;
|
|
262
263
|
const octokit = new Octokit2({ auth: token });
|
|
263
264
|
const username = await getGitHubUsername(octokit);
|
|
264
|
-
let
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
265
|
+
let owner;
|
|
266
|
+
let repoName;
|
|
267
|
+
let visibility;
|
|
268
|
+
if (preCollected) {
|
|
269
|
+
owner = preCollected.owner;
|
|
270
|
+
repoName = preCollected.repoName;
|
|
271
|
+
visibility = preCollected.visibility;
|
|
272
|
+
} else {
|
|
273
|
+
let orgs = [];
|
|
274
|
+
try {
|
|
275
|
+
const { data } = await octokit.rest.orgs.listForAuthenticatedUser({ per_page: 100 });
|
|
276
|
+
orgs = data;
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
const ownerOptions = [
|
|
280
|
+
{ value: username, label: username, hint: "Personal account" },
|
|
281
|
+
...orgs.map((org) => ({ value: org.login, label: org.login, hint: "Organization" }))
|
|
282
|
+
];
|
|
283
|
+
owner = username;
|
|
284
|
+
if (ownerOptions.length > 1) {
|
|
285
|
+
const selected = await p3.select({
|
|
286
|
+
message: "Where should the docs repo be created?",
|
|
287
|
+
options: ownerOptions
|
|
288
|
+
});
|
|
289
|
+
if (p3.isCancel(selected)) return null;
|
|
290
|
+
owner = selected;
|
|
291
|
+
}
|
|
292
|
+
const isOrg2 = owner !== username;
|
|
293
|
+
const slug = config?.projectName ? config.projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") : config?.repos?.[0]?.name;
|
|
294
|
+
const defaultName = slug ? `${slug}-docs` : "my-project-docs";
|
|
295
|
+
repoName = await p3.text({
|
|
296
|
+
message: "Name for the docs GitHub repo:",
|
|
297
|
+
initialValue: defaultName,
|
|
298
|
+
validate: (v) => {
|
|
299
|
+
if (!v || v.length === 0) return "Repo name is required";
|
|
300
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(v)) return "Invalid repo name";
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
if (p3.isCancel(repoName)) return null;
|
|
304
|
+
visibility = await p3.select({
|
|
305
|
+
message: "Repository visibility:",
|
|
306
|
+
options: [
|
|
307
|
+
{ value: "public", label: "Public" },
|
|
308
|
+
{ value: "private", label: "Private" }
|
|
309
|
+
]
|
|
279
310
|
});
|
|
280
|
-
if (p3.isCancel(
|
|
281
|
-
owner = selected;
|
|
311
|
+
if (p3.isCancel(visibility)) return null;
|
|
282
312
|
}
|
|
283
313
|
const isOrg = owner !== username;
|
|
284
|
-
const slug = config?.projectName ? config.projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") : config?.repos?.[0]?.name;
|
|
285
|
-
const defaultName = slug ? `${slug}-docs` : "my-project-docs";
|
|
286
|
-
const repoName = await p3.text({
|
|
287
|
-
message: "Name for the docs GitHub repo:",
|
|
288
|
-
initialValue: defaultName,
|
|
289
|
-
validate: (v) => {
|
|
290
|
-
if (!v || v.length === 0) return "Repo name is required";
|
|
291
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(v)) return "Invalid repo name";
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
if (p3.isCancel(repoName)) return null;
|
|
295
|
-
const visibility = await p3.select({
|
|
296
|
-
message: "Repository visibility:",
|
|
297
|
-
options: [
|
|
298
|
-
{ value: "public", label: "Public" },
|
|
299
|
-
{ value: "private", label: "Private" }
|
|
300
|
-
]
|
|
301
|
-
});
|
|
302
|
-
if (p3.isCancel(visibility)) return null;
|
|
303
314
|
const spinner9 = p3.spinner();
|
|
304
315
|
spinner9.start(`Creating GitHub repo ${owner}/${repoName}...`);
|
|
305
316
|
let repoUrl;
|
|
@@ -438,8 +449,18 @@ async function authenticateVercel() {
|
|
|
438
449
|
}
|
|
439
450
|
p4.log.warn("Saved Vercel token is invalid.");
|
|
440
451
|
}
|
|
441
|
-
p4.
|
|
442
|
-
|
|
452
|
+
p4.note(
|
|
453
|
+
[
|
|
454
|
+
"To create a Vercel access token:",
|
|
455
|
+
"",
|
|
456
|
+
" 1. Go to https://vercel.com/account/settings/tokens",
|
|
457
|
+
" 2. Click 'Create Token'",
|
|
458
|
+
" 3. Enter a name (e.g., 'open-auto-doc')",
|
|
459
|
+
" 4. Select the scope (your personal account or a team)",
|
|
460
|
+
" 5. Set an expiration (or no expiration for convenience)",
|
|
461
|
+
" 6. Click 'Create Token' and copy the value"
|
|
462
|
+
].join("\n"),
|
|
463
|
+
"Vercel Token Required"
|
|
443
464
|
);
|
|
444
465
|
const tokenInput = await p4.text({
|
|
445
466
|
message: "Enter your Vercel token",
|
|
@@ -469,29 +490,32 @@ async function authenticateVercel() {
|
|
|
469
490
|
}
|
|
470
491
|
async function deployToVercel(params) {
|
|
471
492
|
const { token, githubOwner, githubRepo, docsDir, config } = params;
|
|
472
|
-
const { data: userData } = await vercelFetch(
|
|
473
|
-
"/v2/user",
|
|
474
|
-
token
|
|
475
|
-
);
|
|
476
|
-
const userId = userData.user?.id;
|
|
477
|
-
const { data: teamsData } = await vercelFetch(
|
|
478
|
-
"/v2/teams",
|
|
479
|
-
token
|
|
480
|
-
);
|
|
481
|
-
const teams = teamsData.teams ?? [];
|
|
482
493
|
let teamId;
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
494
|
+
if (params.scope) {
|
|
495
|
+
teamId = params.scope.teamId;
|
|
496
|
+
} else {
|
|
497
|
+
const { data: userData } = await vercelFetch(
|
|
498
|
+
"/v2/user",
|
|
499
|
+
token
|
|
500
|
+
);
|
|
501
|
+
const { data: teamsData } = await vercelFetch(
|
|
502
|
+
"/v2/teams",
|
|
503
|
+
token
|
|
504
|
+
);
|
|
505
|
+
const teams = teamsData.teams ?? [];
|
|
506
|
+
if (teams.length > 0) {
|
|
507
|
+
const scopeOptions = [
|
|
508
|
+
{ value: "__personal__", label: userData.user?.username ?? "Personal", hint: "Personal account" },
|
|
509
|
+
...teams.map((t) => ({ value: t.id, label: t.name || t.slug, hint: "Team" }))
|
|
510
|
+
];
|
|
511
|
+
const selectedScope = await p4.select({
|
|
512
|
+
message: "Which Vercel scope should own this project?",
|
|
513
|
+
options: scopeOptions
|
|
514
|
+
});
|
|
515
|
+
if (p4.isCancel(selectedScope)) return null;
|
|
516
|
+
if (selectedScope !== "__personal__") {
|
|
517
|
+
teamId = selectedScope;
|
|
518
|
+
}
|
|
495
519
|
}
|
|
496
520
|
}
|
|
497
521
|
const spinner9 = p4.spinner();
|
|
@@ -2802,18 +2826,24 @@ async function createCiWorkflow(params) {
|
|
|
2802
2826
|
return createCiWorkflowsMultiRepo({
|
|
2803
2827
|
token,
|
|
2804
2828
|
config,
|
|
2805
|
-
docsRepoUrl
|
|
2829
|
+
docsRepoUrl,
|
|
2830
|
+
branch: params.branch
|
|
2806
2831
|
});
|
|
2807
2832
|
}
|
|
2808
2833
|
const relativeOutputDir = path7.relative(gitRoot, path7.resolve(outputDir));
|
|
2809
2834
|
p6.log.info(`Docs repo: ${docsRepoUrl}`);
|
|
2810
2835
|
p6.log.info(`Output directory: ${relativeOutputDir}`);
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2836
|
+
let branch;
|
|
2837
|
+
if (params.branch) {
|
|
2838
|
+
branch = params.branch;
|
|
2839
|
+
} else {
|
|
2840
|
+
branch = await p6.text({
|
|
2841
|
+
message: "Which branch should trigger doc updates?",
|
|
2842
|
+
initialValue: "main",
|
|
2843
|
+
validate: (v) => v.length === 0 ? "Branch name is required" : void 0
|
|
2844
|
+
});
|
|
2845
|
+
if (p6.isCancel(branch)) return null;
|
|
2846
|
+
}
|
|
2817
2847
|
const workflowDir = path7.join(gitRoot, ".github", "workflows");
|
|
2818
2848
|
const workflowPath = path7.join(workflowDir, "update-docs.yml");
|
|
2819
2849
|
fs7.mkdirSync(workflowDir, { recursive: true });
|
|
@@ -2850,12 +2880,17 @@ async function createCiWorkflowsMultiRepo(params) {
|
|
|
2850
2880
|
const octokit = new Octokit3({ auth: token });
|
|
2851
2881
|
p6.log.info(`Setting up CI for ${config.repos.length} repositories`);
|
|
2852
2882
|
p6.log.info(`Docs repo: ${docsRepoUrl}`);
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2883
|
+
let branch;
|
|
2884
|
+
if (params.branch) {
|
|
2885
|
+
branch = params.branch;
|
|
2886
|
+
} else {
|
|
2887
|
+
branch = await p6.text({
|
|
2888
|
+
message: "Which branch should trigger doc updates?",
|
|
2889
|
+
initialValue: "main",
|
|
2890
|
+
validate: (v) => v.length === 0 ? "Branch name is required" : void 0
|
|
2891
|
+
});
|
|
2892
|
+
if (p6.isCancel(branch)) return null;
|
|
2893
|
+
}
|
|
2859
2894
|
const createdRepos = [];
|
|
2860
2895
|
const workflowPath = ".github/workflows/update-docs.yml";
|
|
2861
2896
|
for (const repo of config.repos) {
|
|
@@ -3011,19 +3046,18 @@ import * as p7 from "@clack/prompts";
|
|
|
3011
3046
|
import fs8 from "fs";
|
|
3012
3047
|
import path8 from "path";
|
|
3013
3048
|
var MCP_SERVER_KEY = "project-docs";
|
|
3014
|
-
function getMcpConfig() {
|
|
3049
|
+
function getMcpConfig(docsDir) {
|
|
3015
3050
|
return {
|
|
3016
3051
|
command: "npx",
|
|
3017
|
-
args: ["-y", "@latent-space-labs/open-auto-doc-mcp", "--project-dir",
|
|
3052
|
+
args: ["-y", "@latent-space-labs/open-auto-doc-mcp", "--project-dir", docsDir]
|
|
3018
3053
|
};
|
|
3019
3054
|
}
|
|
3020
3055
|
async function setupMcpConfig(opts) {
|
|
3021
|
-
const
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
writeMcpJson(process.cwd());
|
|
3056
|
+
const absOutputDir = path8.resolve(opts.outputDir);
|
|
3057
|
+
writeMcpJson(process.cwd(), absOutputDir);
|
|
3058
|
+
writeMcpJson(absOutputDir, absOutputDir);
|
|
3059
|
+
addMcpScript(absOutputDir);
|
|
3060
|
+
showMcpInstructions(absOutputDir);
|
|
3027
3061
|
}
|
|
3028
3062
|
async function setupMcpCommand() {
|
|
3029
3063
|
p7.intro("open-auto-doc \u2014 MCP Server Setup");
|
|
@@ -3047,11 +3081,48 @@ Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
|
|
|
3047
3081
|
p7.log.error("Cache directory exists but contains no analysis files.");
|
|
3048
3082
|
process.exit(1);
|
|
3049
3083
|
}
|
|
3050
|
-
|
|
3084
|
+
const absOutputDir = path8.resolve(config.outputDir);
|
|
3085
|
+
writeMcpJson(process.cwd(), absOutputDir);
|
|
3086
|
+
writeMcpJson(absOutputDir, absOutputDir);
|
|
3087
|
+
addMcpScript(absOutputDir);
|
|
3088
|
+
showMcpInstructions(absOutputDir);
|
|
3089
|
+
p7.outro("Open Claude Code in this project to start using the tools.");
|
|
3090
|
+
}
|
|
3091
|
+
function writeMcpJson(projectRoot, docsDir) {
|
|
3092
|
+
const mcpPath = path8.join(projectRoot, ".mcp.json");
|
|
3093
|
+
const relDocsDir = path8.relative(projectRoot, docsDir);
|
|
3094
|
+
const isSubdir = !relDocsDir.startsWith("..") && !path8.isAbsolute(relDocsDir);
|
|
3095
|
+
const configDocsDir = isSubdir ? `./${relDocsDir}` : docsDir;
|
|
3096
|
+
let existing = {};
|
|
3097
|
+
if (fs8.existsSync(mcpPath)) {
|
|
3098
|
+
try {
|
|
3099
|
+
existing = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
|
|
3100
|
+
} catch {
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
const mcpServers = existing.mcpServers ?? {};
|
|
3104
|
+
mcpServers[MCP_SERVER_KEY] = getMcpConfig(configDocsDir);
|
|
3105
|
+
const merged = { ...existing, mcpServers };
|
|
3106
|
+
fs8.writeFileSync(mcpPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3107
|
+
p7.log.step(`Wrote ${mcpPath}`);
|
|
3108
|
+
}
|
|
3109
|
+
function addMcpScript(docsDir) {
|
|
3110
|
+
const pkgPath = path8.join(docsDir, "package.json");
|
|
3111
|
+
if (!fs8.existsSync(pkgPath)) return;
|
|
3112
|
+
try {
|
|
3113
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
3114
|
+
pkg.scripts = pkg.scripts ?? {};
|
|
3115
|
+
pkg.scripts.mcp = "npx -y @latent-space-labs/open-auto-doc-mcp --project-dir .";
|
|
3116
|
+
fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
3117
|
+
p7.log.step(`Added 'mcp' script to ${pkgPath}`);
|
|
3118
|
+
} catch {
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
function showMcpInstructions(docsDir) {
|
|
3051
3122
|
p7.log.success("MCP server configured!");
|
|
3052
3123
|
p7.note(
|
|
3053
3124
|
[
|
|
3054
|
-
"The following tools are now available
|
|
3125
|
+
"The following tools are now available:",
|
|
3055
3126
|
"",
|
|
3056
3127
|
" get_project_overview \u2014 Project summary and tech stack",
|
|
3057
3128
|
" search_documentation \u2014 Full-text search across all docs",
|
|
@@ -3062,24 +3133,31 @@ Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
|
|
|
3062
3133
|
" get_diagram \u2014 Mermaid diagrams",
|
|
3063
3134
|
" get_business_rules \u2014 Domain concepts and workflows"
|
|
3064
3135
|
].join("\n"),
|
|
3065
|
-
"Available MCP
|
|
3136
|
+
"Available MCP Tools"
|
|
3137
|
+
);
|
|
3138
|
+
p7.note(
|
|
3139
|
+
[
|
|
3140
|
+
"A .mcp.json file has been written to your project.",
|
|
3141
|
+
"Claude Code will automatically detect it.",
|
|
3142
|
+
"",
|
|
3143
|
+
"To use from another project, add this to its .mcp.json:",
|
|
3144
|
+
"",
|
|
3145
|
+
` {`,
|
|
3146
|
+
` "mcpServers": {`,
|
|
3147
|
+
` "project-docs": {`,
|
|
3148
|
+
` "command": "npx",`,
|
|
3149
|
+
` "args": ["-y", "@latent-space-labs/open-auto-doc-mcp",`,
|
|
3150
|
+
` "--project-dir", "${docsDir}"]`,
|
|
3151
|
+
` }`,
|
|
3152
|
+
` }`,
|
|
3153
|
+
` }`,
|
|
3154
|
+
"",
|
|
3155
|
+
"Or run the MCP server directly from the docs directory:",
|
|
3156
|
+
"",
|
|
3157
|
+
` cd ${docsDir} && npm run mcp`
|
|
3158
|
+
].join("\n"),
|
|
3159
|
+
"How to Connect"
|
|
3066
3160
|
);
|
|
3067
|
-
p7.outro("Open Claude Code in this project to start using the tools.");
|
|
3068
|
-
}
|
|
3069
|
-
function writeMcpJson(projectRoot) {
|
|
3070
|
-
const mcpPath = path8.join(projectRoot, ".mcp.json");
|
|
3071
|
-
let existing = {};
|
|
3072
|
-
if (fs8.existsSync(mcpPath)) {
|
|
3073
|
-
try {
|
|
3074
|
-
existing = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
|
|
3075
|
-
} catch {
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
const mcpServers = existing.mcpServers ?? {};
|
|
3079
|
-
mcpServers[MCP_SERVER_KEY] = getMcpConfig();
|
|
3080
|
-
const merged = { ...existing, mcpServers };
|
|
3081
|
-
fs8.writeFileSync(mcpPath, JSON.stringify(merged, null, 2) + "\n");
|
|
3082
|
-
p7.log.step(`Wrote ${path8.relative(projectRoot, mcpPath)}`);
|
|
3083
3161
|
}
|
|
3084
3162
|
|
|
3085
3163
|
// ../generator/dist/index.js
|
|
@@ -3961,6 +4039,146 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
3961
4039
|
process.exit(0);
|
|
3962
4040
|
}
|
|
3963
4041
|
p8.log.info(`Using ${model}`);
|
|
4042
|
+
const shouldSetupMcp = await p8.confirm({
|
|
4043
|
+
message: "Set up MCP server so Claude Code can query your docs?"
|
|
4044
|
+
});
|
|
4045
|
+
const wantsMcp = !p8.isCancel(shouldSetupMcp) && shouldSetupMcp;
|
|
4046
|
+
const shouldDeploy = await p8.confirm({
|
|
4047
|
+
message: "Would you like to deploy your docs to GitHub?"
|
|
4048
|
+
});
|
|
4049
|
+
const wantsDeploy = !p8.isCancel(shouldDeploy) && shouldDeploy;
|
|
4050
|
+
let deployConfig;
|
|
4051
|
+
let vercelToken = null;
|
|
4052
|
+
let vercelScope;
|
|
4053
|
+
let wantsVercel = false;
|
|
4054
|
+
let wantsCi = false;
|
|
4055
|
+
let ciBranch;
|
|
4056
|
+
if (wantsDeploy) {
|
|
4057
|
+
const octokit = new Octokit4({ auth: token });
|
|
4058
|
+
let username;
|
|
4059
|
+
try {
|
|
4060
|
+
const { data } = await octokit.rest.users.getAuthenticated();
|
|
4061
|
+
username = data.login;
|
|
4062
|
+
} catch {
|
|
4063
|
+
p8.log.error("Failed to fetch GitHub user info.");
|
|
4064
|
+
process.exit(1);
|
|
4065
|
+
}
|
|
4066
|
+
let orgs = [];
|
|
4067
|
+
try {
|
|
4068
|
+
const { data } = await octokit.rest.orgs.listForAuthenticatedUser({ per_page: 100 });
|
|
4069
|
+
orgs = data;
|
|
4070
|
+
} catch {
|
|
4071
|
+
}
|
|
4072
|
+
const ownerOptions = [
|
|
4073
|
+
{ value: username, label: username, hint: "Personal account" },
|
|
4074
|
+
...orgs.map((org) => ({ value: org.login, label: org.login, hint: "Organization" }))
|
|
4075
|
+
];
|
|
4076
|
+
let owner = username;
|
|
4077
|
+
if (ownerOptions.length > 1) {
|
|
4078
|
+
const selected = await p8.select({
|
|
4079
|
+
message: "Where should the docs repo be created?",
|
|
4080
|
+
options: ownerOptions
|
|
4081
|
+
});
|
|
4082
|
+
if (p8.isCancel(selected)) {
|
|
4083
|
+
p8.cancel("Operation cancelled");
|
|
4084
|
+
process.exit(0);
|
|
4085
|
+
}
|
|
4086
|
+
owner = selected;
|
|
4087
|
+
}
|
|
4088
|
+
const slug = projectName ? projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") : repos[0]?.name;
|
|
4089
|
+
const defaultName = slug ? `${slug}-docs` : "my-project-docs";
|
|
4090
|
+
const repoNameInput = await p8.text({
|
|
4091
|
+
message: "Name for the docs GitHub repo:",
|
|
4092
|
+
initialValue: defaultName,
|
|
4093
|
+
validate: (v) => {
|
|
4094
|
+
if (!v || v.length === 0) return "Repo name is required";
|
|
4095
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(v)) return "Invalid repo name";
|
|
4096
|
+
}
|
|
4097
|
+
});
|
|
4098
|
+
if (p8.isCancel(repoNameInput)) {
|
|
4099
|
+
p8.cancel("Operation cancelled");
|
|
4100
|
+
process.exit(0);
|
|
4101
|
+
}
|
|
4102
|
+
const visibilityInput = await p8.select({
|
|
4103
|
+
message: "Repository visibility:",
|
|
4104
|
+
options: [
|
|
4105
|
+
{ value: "public", label: "Public" },
|
|
4106
|
+
{ value: "private", label: "Private" }
|
|
4107
|
+
]
|
|
4108
|
+
});
|
|
4109
|
+
if (p8.isCancel(visibilityInput)) {
|
|
4110
|
+
p8.cancel("Operation cancelled");
|
|
4111
|
+
process.exit(0);
|
|
4112
|
+
}
|
|
4113
|
+
deployConfig = {
|
|
4114
|
+
owner,
|
|
4115
|
+
repoName: repoNameInput,
|
|
4116
|
+
visibility: visibilityInput
|
|
4117
|
+
};
|
|
4118
|
+
const shouldDeployVercel = await p8.confirm({
|
|
4119
|
+
message: "Would you like to deploy to Vercel? (auto-deploys on every push)"
|
|
4120
|
+
});
|
|
4121
|
+
wantsVercel = !p8.isCancel(shouldDeployVercel) && shouldDeployVercel;
|
|
4122
|
+
if (wantsVercel) {
|
|
4123
|
+
vercelToken = await authenticateVercel();
|
|
4124
|
+
if (vercelToken) {
|
|
4125
|
+
try {
|
|
4126
|
+
const teamsRes = await fetch("https://api.vercel.com/v2/teams", {
|
|
4127
|
+
headers: { Authorization: `Bearer ${vercelToken}` }
|
|
4128
|
+
});
|
|
4129
|
+
const teamsData = await teamsRes.json();
|
|
4130
|
+
const teams = teamsData?.teams ?? [];
|
|
4131
|
+
if (teams.length > 0) {
|
|
4132
|
+
const userRes = await fetch("https://api.vercel.com/v2/user", {
|
|
4133
|
+
headers: { Authorization: `Bearer ${vercelToken}` }
|
|
4134
|
+
});
|
|
4135
|
+
const userData = await userRes.json();
|
|
4136
|
+
const vercelUsername = userData?.user?.username ?? "Personal";
|
|
4137
|
+
const scopeOptions = [
|
|
4138
|
+
{ value: "__personal__", label: vercelUsername, hint: "Personal account" },
|
|
4139
|
+
...teams.map((t) => ({ value: t.id, label: t.name || t.slug, hint: "Team" }))
|
|
4140
|
+
];
|
|
4141
|
+
const selectedScope = await p8.select({
|
|
4142
|
+
message: "Which Vercel scope should own this project?",
|
|
4143
|
+
options: scopeOptions
|
|
4144
|
+
});
|
|
4145
|
+
if (p8.isCancel(selectedScope)) {
|
|
4146
|
+
p8.cancel("Operation cancelled");
|
|
4147
|
+
process.exit(0);
|
|
4148
|
+
}
|
|
4149
|
+
vercelScope = {
|
|
4150
|
+
teamId: selectedScope === "__personal__" ? void 0 : selectedScope
|
|
4151
|
+
};
|
|
4152
|
+
} else {
|
|
4153
|
+
vercelScope = { teamId: void 0 };
|
|
4154
|
+
}
|
|
4155
|
+
} catch {
|
|
4156
|
+
vercelScope = { teamId: void 0 };
|
|
4157
|
+
}
|
|
4158
|
+
} else {
|
|
4159
|
+
wantsVercel = false;
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
const shouldSetupCi = await p8.confirm({
|
|
4163
|
+
message: "Would you like to set up CI to auto-update docs on every push?"
|
|
4164
|
+
});
|
|
4165
|
+
wantsCi = !p8.isCancel(shouldSetupCi) && shouldSetupCi;
|
|
4166
|
+
if (wantsCi) {
|
|
4167
|
+
const branchInput = await p8.text({
|
|
4168
|
+
message: "Which branch should trigger doc updates?",
|
|
4169
|
+
initialValue: "main",
|
|
4170
|
+
validate: (v) => v.length === 0 ? "Branch name is required" : void 0
|
|
4171
|
+
});
|
|
4172
|
+
if (p8.isCancel(branchInput)) {
|
|
4173
|
+
p8.cancel("Operation cancelled");
|
|
4174
|
+
process.exit(0);
|
|
4175
|
+
}
|
|
4176
|
+
ciBranch = branchInput;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
p8.log.step("All configured! Starting analysis and generation...");
|
|
4180
|
+
const outputDir = path10.resolve(options.output || "docs-site");
|
|
4181
|
+
const cacheDir = path10.join(outputDir, ".autodoc-cache");
|
|
3964
4182
|
const cloneSpinner = p8.spinner();
|
|
3965
4183
|
cloneSpinner.start(`Cloning ${repos.length} repositories...`);
|
|
3966
4184
|
const clones = [];
|
|
@@ -3998,6 +4216,11 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
3998
4216
|
progressTable.update(repoName, { activity: formatToolActivity(event) });
|
|
3999
4217
|
}
|
|
4000
4218
|
});
|
|
4219
|
+
try {
|
|
4220
|
+
const headSha = getHeadSha(cloned.localPath);
|
|
4221
|
+
saveCache(cacheDir, repoName, headSha, result);
|
|
4222
|
+
} catch {
|
|
4223
|
+
}
|
|
4001
4224
|
progressTable.update(repoName, { status: "done", summary: buildRepoSummary(result) });
|
|
4002
4225
|
return { repo: repoName, result };
|
|
4003
4226
|
} catch (err) {
|
|
@@ -4033,7 +4256,6 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
4033
4256
|
p8.log.warn(`Cross-repo error: ${err instanceof Error ? err.message : err}`);
|
|
4034
4257
|
}
|
|
4035
4258
|
}
|
|
4036
|
-
const outputDir = path10.resolve(options.output || "docs-site");
|
|
4037
4259
|
if (!projectName) {
|
|
4038
4260
|
projectName = results.length === 1 ? results[0].repoName : "My Project";
|
|
4039
4261
|
}
|
|
@@ -4081,10 +4303,7 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
4081
4303
|
p8.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
|
|
4082
4304
|
}
|
|
4083
4305
|
p8.log.success("Documentation generated successfully!");
|
|
4084
|
-
|
|
4085
|
-
message: "Set up MCP server so Claude Code can query your docs?"
|
|
4086
|
-
});
|
|
4087
|
-
if (!p8.isCancel(shouldSetupMcp) && shouldSetupMcp) {
|
|
4306
|
+
if (wantsMcp) {
|
|
4088
4307
|
await setupMcpConfig({ outputDir });
|
|
4089
4308
|
}
|
|
4090
4309
|
let devServer;
|
|
@@ -4097,10 +4316,7 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
4097
4316
|
p8.log.warn("Could not start preview server. You can run it manually:");
|
|
4098
4317
|
p8.log.info(` cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`);
|
|
4099
4318
|
}
|
|
4100
|
-
|
|
4101
|
-
message: "Would you like to deploy your docs to GitHub?"
|
|
4102
|
-
});
|
|
4103
|
-
if (p8.isCancel(shouldDeploy) || !shouldDeploy) {
|
|
4319
|
+
if (!wantsDeploy || !deployConfig) {
|
|
4104
4320
|
if (devServer) {
|
|
4105
4321
|
killDevServer(devServer);
|
|
4106
4322
|
}
|
|
@@ -4117,7 +4333,8 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
4117
4333
|
const deployResult = await createAndPushDocsRepo({
|
|
4118
4334
|
token,
|
|
4119
4335
|
docsDir: outputDir,
|
|
4120
|
-
config
|
|
4336
|
+
config,
|
|
4337
|
+
preCollected: deployConfig
|
|
4121
4338
|
});
|
|
4122
4339
|
if (!deployResult) {
|
|
4123
4340
|
p8.note(
|
|
@@ -4128,51 +4345,35 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
|
|
|
4128
4345
|
return;
|
|
4129
4346
|
}
|
|
4130
4347
|
let vercelDeployed = false;
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
});
|
|
4144
|
-
if (vercelResult) {
|
|
4145
|
-
p8.log.success(`Live at: ${vercelResult.deploymentUrl}`);
|
|
4146
|
-
vercelDeployed = true;
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
const shouldSetupCi = await p8.confirm({
|
|
4151
|
-
message: "Would you like to set up CI to auto-update docs on every push?"
|
|
4152
|
-
});
|
|
4153
|
-
if (p8.isCancel(shouldSetupCi) || !shouldSetupCi) {
|
|
4154
|
-
if (!vercelDeployed) {
|
|
4155
|
-
showVercelInstructions(deployResult.owner, deployResult.repoName);
|
|
4348
|
+
if (wantsVercel && vercelToken) {
|
|
4349
|
+
const vercelResult = await deployToVercel({
|
|
4350
|
+
token: vercelToken,
|
|
4351
|
+
githubOwner: deployResult.owner,
|
|
4352
|
+
githubRepo: deployResult.repoName,
|
|
4353
|
+
docsDir: outputDir,
|
|
4354
|
+
config,
|
|
4355
|
+
scope: vercelScope
|
|
4356
|
+
});
|
|
4357
|
+
if (vercelResult) {
|
|
4358
|
+
p8.log.success(`Live at: ${vercelResult.deploymentUrl}`);
|
|
4359
|
+
vercelDeployed = true;
|
|
4156
4360
|
}
|
|
4157
|
-
p8.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
|
|
4158
|
-
return;
|
|
4159
4361
|
}
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4362
|
+
if (wantsCi) {
|
|
4363
|
+
const gitRoot = getGitRoot();
|
|
4364
|
+
if (!gitRoot) {
|
|
4365
|
+
p8.log.warn("Not in a git repository \u2014 skipping CI setup. Run `open-auto-doc setup-ci` from your project root later.");
|
|
4366
|
+
} else {
|
|
4367
|
+
await createCiWorkflow({
|
|
4368
|
+
gitRoot,
|
|
4369
|
+
docsRepoUrl: deployResult.repoUrl,
|
|
4370
|
+
outputDir,
|
|
4371
|
+
token,
|
|
4372
|
+
config,
|
|
4373
|
+
branch: ciBranch
|
|
4374
|
+
});
|
|
4165
4375
|
}
|
|
4166
|
-
p8.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
|
|
4167
|
-
return;
|
|
4168
4376
|
}
|
|
4169
|
-
const ciResult = await createCiWorkflow({
|
|
4170
|
-
gitRoot,
|
|
4171
|
-
docsRepoUrl: deployResult.repoUrl,
|
|
4172
|
-
outputDir,
|
|
4173
|
-
token,
|
|
4174
|
-
config
|
|
4175
|
-
});
|
|
4176
4377
|
if (!vercelDeployed) {
|
|
4177
4378
|
showVercelInstructions(deployResult.owner, deployResult.repoName);
|
|
4178
4379
|
}
|
|
@@ -4579,7 +4780,7 @@ async function logoutCommand() {
|
|
|
4579
4780
|
|
|
4580
4781
|
// src/index.ts
|
|
4581
4782
|
var program = new Command();
|
|
4582
|
-
program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.5.
|
|
4783
|
+
program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.5.3");
|
|
4583
4784
|
program.command("init", { isDefault: true }).description("Initialize and generate documentation for your repositories").option("-o, --output <dir>", "Output directory", "docs-site").action(initCommand);
|
|
4584
4785
|
program.command("generate").description("Regenerate documentation using existing configuration").option("--incremental", "Only re-analyze changed files (uses cached results)").option("--force", "Force full regeneration (ignore cache)").option("--repo <name>", "Only analyze this repo (uses cache for others)").action(generateCommand);
|
|
4585
4786
|
program.command("deploy").description("Create a GitHub repo for docs and push (connect to Vercel for auto-deploy)").option("-d, --dir <path>", "Docs site directory").action(deployCommand);
|