@latent-space-labs/open-auto-doc 0.5.2 → 0.5.4

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 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 orgs = [];
265
- try {
266
- const { data } = await octokit.rest.orgs.listForAuthenticatedUser({ per_page: 100 });
267
- orgs = data;
268
- } catch {
269
- }
270
- const ownerOptions = [
271
- { value: username, label: username, hint: "Personal account" },
272
- ...orgs.map((org) => ({ value: org.login, label: org.login, hint: "Organization" }))
273
- ];
274
- let owner = username;
275
- if (ownerOptions.length > 1) {
276
- const selected = await p3.select({
277
- message: "Where should the docs repo be created?",
278
- options: ownerOptions
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(selected)) return null;
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;
@@ -309,7 +320,8 @@ async function createAndPushDocsRepo(params) {
309
320
  org: owner,
310
321
  name: repoName,
311
322
  private: visibility === "private",
312
- description: "Auto-generated documentation site",
323
+ description: "Documentation auto-generated by https://www.openautodoc.com",
324
+ homepage: "https://www.openautodoc.com",
313
325
  auto_init: false
314
326
  });
315
327
  repoUrl = data.clone_url;
@@ -318,7 +330,8 @@ async function createAndPushDocsRepo(params) {
318
330
  const { data } = await octokit.rest.repos.createForAuthenticatedUser({
319
331
  name: repoName,
320
332
  private: visibility === "private",
321
- description: "Auto-generated documentation site",
333
+ description: "Documentation auto-generated by https://www.openautodoc.com",
334
+ homepage: "https://www.openautodoc.com",
322
335
  auto_init: false
323
336
  });
324
337
  repoUrl = data.clone_url;
@@ -438,8 +451,18 @@ async function authenticateVercel() {
438
451
  }
439
452
  p4.log.warn("Saved Vercel token is invalid.");
440
453
  }
441
- p4.log.info(
442
- "Create a Vercel token at: https://vercel.com/account/tokens"
454
+ p4.note(
455
+ [
456
+ "To create a Vercel access token:",
457
+ "",
458
+ " 1. Go to https://vercel.com/account/settings/tokens",
459
+ " 2. Click 'Create Token'",
460
+ " 3. Enter a name (e.g., 'open-auto-doc')",
461
+ " 4. Select the scope (your personal account or a team)",
462
+ " 5. Set an expiration (or no expiration for convenience)",
463
+ " 6. Click 'Create Token' and copy the value"
464
+ ].join("\n"),
465
+ "Vercel Token Required"
443
466
  );
444
467
  const tokenInput = await p4.text({
445
468
  message: "Enter your Vercel token",
@@ -469,29 +492,32 @@ async function authenticateVercel() {
469
492
  }
470
493
  async function deployToVercel(params) {
471
494
  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
495
  let teamId;
483
- if (teams.length > 0) {
484
- const scopeOptions = [
485
- { value: "__personal__", label: userData.user?.username ?? "Personal", hint: "Personal account" },
486
- ...teams.map((t) => ({ value: t.id, label: t.name || t.slug, hint: "Team" }))
487
- ];
488
- const selectedScope = await p4.select({
489
- message: "Which Vercel scope should own this project?",
490
- options: scopeOptions
491
- });
492
- if (p4.isCancel(selectedScope)) return null;
493
- if (selectedScope !== "__personal__") {
494
- teamId = selectedScope;
496
+ if (params.scope) {
497
+ teamId = params.scope.teamId;
498
+ } else {
499
+ const { data: userData } = await vercelFetch(
500
+ "/v2/user",
501
+ token
502
+ );
503
+ const { data: teamsData } = await vercelFetch(
504
+ "/v2/teams",
505
+ token
506
+ );
507
+ const teams = teamsData.teams ?? [];
508
+ if (teams.length > 0) {
509
+ const scopeOptions = [
510
+ { value: "__personal__", label: userData.user?.username ?? "Personal", hint: "Personal account" },
511
+ ...teams.map((t) => ({ value: t.id, label: t.name || t.slug, hint: "Team" }))
512
+ ];
513
+ const selectedScope = await p4.select({
514
+ message: "Which Vercel scope should own this project?",
515
+ options: scopeOptions
516
+ });
517
+ if (p4.isCancel(selectedScope)) return null;
518
+ if (selectedScope !== "__personal__") {
519
+ teamId = selectedScope;
520
+ }
495
521
  }
496
522
  }
497
523
  const spinner9 = p4.spinner();
@@ -543,6 +569,29 @@ async function deployToVercel(params) {
543
569
  const projectId = project.id;
544
570
  const projectName = project.name;
545
571
  spinner9.stop(`Created Vercel project: ${projectName}`);
572
+ spinner9.start("Triggering first deployment...");
573
+ const { ok: deployOk } = await vercelFetch(
574
+ "/v13/deployments",
575
+ token,
576
+ {
577
+ method: "POST",
578
+ teamId,
579
+ body: {
580
+ name: projectName,
581
+ project: projectId,
582
+ target: "production",
583
+ gitSource: {
584
+ type: "github",
585
+ org: githubOwner,
586
+ repo: githubRepo,
587
+ ref: "main"
588
+ }
589
+ }
590
+ }
591
+ );
592
+ if (!deployOk) {
593
+ spinner9.message("Waiting for deployment...");
594
+ }
546
595
  spinner9.start("Waiting for first deployment...");
547
596
  const maxWaitMs = 5 * 60 * 1e3;
548
597
  const pollIntervalMs = 5e3;
@@ -2802,18 +2851,24 @@ async function createCiWorkflow(params) {
2802
2851
  return createCiWorkflowsMultiRepo({
2803
2852
  token,
2804
2853
  config,
2805
- docsRepoUrl
2854
+ docsRepoUrl,
2855
+ branch: params.branch
2806
2856
  });
2807
2857
  }
2808
2858
  const relativeOutputDir = path7.relative(gitRoot, path7.resolve(outputDir));
2809
2859
  p6.log.info(`Docs repo: ${docsRepoUrl}`);
2810
2860
  p6.log.info(`Output directory: ${relativeOutputDir}`);
2811
- const branch = await p6.text({
2812
- message: "Which branch should trigger doc updates?",
2813
- initialValue: "main",
2814
- validate: (v) => v.length === 0 ? "Branch name is required" : void 0
2815
- });
2816
- if (p6.isCancel(branch)) return null;
2861
+ let branch;
2862
+ if (params.branch) {
2863
+ branch = params.branch;
2864
+ } else {
2865
+ branch = await p6.text({
2866
+ message: "Which branch should trigger doc updates?",
2867
+ initialValue: "main",
2868
+ validate: (v) => v.length === 0 ? "Branch name is required" : void 0
2869
+ });
2870
+ if (p6.isCancel(branch)) return null;
2871
+ }
2817
2872
  const workflowDir = path7.join(gitRoot, ".github", "workflows");
2818
2873
  const workflowPath = path7.join(workflowDir, "update-docs.yml");
2819
2874
  fs7.mkdirSync(workflowDir, { recursive: true });
@@ -2850,12 +2905,17 @@ async function createCiWorkflowsMultiRepo(params) {
2850
2905
  const octokit = new Octokit3({ auth: token });
2851
2906
  p6.log.info(`Setting up CI for ${config.repos.length} repositories`);
2852
2907
  p6.log.info(`Docs repo: ${docsRepoUrl}`);
2853
- const branch = await p6.text({
2854
- message: "Which branch should trigger doc updates?",
2855
- initialValue: "main",
2856
- validate: (v) => v.length === 0 ? "Branch name is required" : void 0
2857
- });
2858
- if (p6.isCancel(branch)) return null;
2908
+ let branch;
2909
+ if (params.branch) {
2910
+ branch = params.branch;
2911
+ } else {
2912
+ branch = await p6.text({
2913
+ message: "Which branch should trigger doc updates?",
2914
+ initialValue: "main",
2915
+ validate: (v) => v.length === 0 ? "Branch name is required" : void 0
2916
+ });
2917
+ if (p6.isCancel(branch)) return null;
2918
+ }
2859
2919
  const createdRepos = [];
2860
2920
  const workflowPath = ".github/workflows/update-docs.yml";
2861
2921
  for (const repo of config.repos) {
@@ -3011,19 +3071,18 @@ import * as p7 from "@clack/prompts";
3011
3071
  import fs8 from "fs";
3012
3072
  import path8 from "path";
3013
3073
  var MCP_SERVER_KEY = "project-docs";
3014
- function getMcpConfig() {
3074
+ function getMcpConfig(docsDir) {
3015
3075
  return {
3016
3076
  command: "npx",
3017
- args: ["-y", "@latent-space-labs/open-auto-doc-mcp", "--project-dir", "."]
3077
+ args: ["-y", "@latent-space-labs/open-auto-doc-mcp", "--project-dir", docsDir]
3018
3078
  };
3019
3079
  }
3020
3080
  async function setupMcpConfig(opts) {
3021
- const cacheDir = path8.join(opts.outputDir, ".autodoc-cache");
3022
- if (!fs8.existsSync(cacheDir)) {
3023
- p7.log.warn("No analysis cache found \u2014 skipping MCP setup. Run setup-mcp after generating docs.");
3024
- return;
3025
- }
3026
- writeMcpJson(process.cwd());
3081
+ const absOutputDir = path8.resolve(opts.outputDir);
3082
+ writeMcpJson(process.cwd(), absOutputDir);
3083
+ writeMcpJson(absOutputDir, absOutputDir);
3084
+ addMcpScript(absOutputDir);
3085
+ showMcpInstructions(absOutputDir);
3027
3086
  }
3028
3087
  async function setupMcpCommand() {
3029
3088
  p7.intro("open-auto-doc \u2014 MCP Server Setup");
@@ -3047,11 +3106,48 @@ Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
3047
3106
  p7.log.error("Cache directory exists but contains no analysis files.");
3048
3107
  process.exit(1);
3049
3108
  }
3050
- writeMcpJson(process.cwd());
3109
+ const absOutputDir = path8.resolve(config.outputDir);
3110
+ writeMcpJson(process.cwd(), absOutputDir);
3111
+ writeMcpJson(absOutputDir, absOutputDir);
3112
+ addMcpScript(absOutputDir);
3113
+ showMcpInstructions(absOutputDir);
3114
+ p7.outro("Open Claude Code in this project to start using the tools.");
3115
+ }
3116
+ function writeMcpJson(projectRoot, docsDir) {
3117
+ const mcpPath = path8.join(projectRoot, ".mcp.json");
3118
+ const relDocsDir = path8.relative(projectRoot, docsDir);
3119
+ const isSubdir = !relDocsDir.startsWith("..") && !path8.isAbsolute(relDocsDir);
3120
+ const configDocsDir = isSubdir ? `./${relDocsDir}` : docsDir;
3121
+ let existing = {};
3122
+ if (fs8.existsSync(mcpPath)) {
3123
+ try {
3124
+ existing = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
3125
+ } catch {
3126
+ }
3127
+ }
3128
+ const mcpServers = existing.mcpServers ?? {};
3129
+ mcpServers[MCP_SERVER_KEY] = getMcpConfig(configDocsDir);
3130
+ const merged = { ...existing, mcpServers };
3131
+ fs8.writeFileSync(mcpPath, JSON.stringify(merged, null, 2) + "\n");
3132
+ p7.log.step(`Wrote ${mcpPath}`);
3133
+ }
3134
+ function addMcpScript(docsDir) {
3135
+ const pkgPath = path8.join(docsDir, "package.json");
3136
+ if (!fs8.existsSync(pkgPath)) return;
3137
+ try {
3138
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
3139
+ pkg.scripts = pkg.scripts ?? {};
3140
+ pkg.scripts.mcp = "npx -y @latent-space-labs/open-auto-doc-mcp --project-dir .";
3141
+ fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
3142
+ p7.log.step(`Added 'mcp' script to ${pkgPath}`);
3143
+ } catch {
3144
+ }
3145
+ }
3146
+ function showMcpInstructions(docsDir) {
3051
3147
  p7.log.success("MCP server configured!");
3052
3148
  p7.note(
3053
3149
  [
3054
- "The following tools are now available in Claude Code:",
3150
+ "The following tools are now available:",
3055
3151
  "",
3056
3152
  " get_project_overview \u2014 Project summary and tech stack",
3057
3153
  " search_documentation \u2014 Full-text search across all docs",
@@ -3062,24 +3158,31 @@ Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
3062
3158
  " get_diagram \u2014 Mermaid diagrams",
3063
3159
  " get_business_rules \u2014 Domain concepts and workflows"
3064
3160
  ].join("\n"),
3065
- "Available MCP tools"
3161
+ "Available MCP Tools"
3162
+ );
3163
+ p7.note(
3164
+ [
3165
+ "A .mcp.json file has been written to your project.",
3166
+ "Claude Code will automatically detect it.",
3167
+ "",
3168
+ "To use from another project, add this to its .mcp.json:",
3169
+ "",
3170
+ ` {`,
3171
+ ` "mcpServers": {`,
3172
+ ` "project-docs": {`,
3173
+ ` "command": "npx",`,
3174
+ ` "args": ["-y", "@latent-space-labs/open-auto-doc-mcp",`,
3175
+ ` "--project-dir", "${docsDir}"]`,
3176
+ ` }`,
3177
+ ` }`,
3178
+ ` }`,
3179
+ "",
3180
+ "Or run the MCP server directly from the docs directory:",
3181
+ "",
3182
+ ` cd ${docsDir} && npm run mcp`
3183
+ ].join("\n"),
3184
+ "How to Connect"
3066
3185
  );
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
3186
  }
3084
3187
 
3085
3188
  // ../generator/dist/index.js
@@ -3961,6 +4064,146 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3961
4064
  process.exit(0);
3962
4065
  }
3963
4066
  p8.log.info(`Using ${model}`);
4067
+ const shouldSetupMcp = await p8.confirm({
4068
+ message: "Set up MCP server so Claude Code can query your docs?"
4069
+ });
4070
+ const wantsMcp = !p8.isCancel(shouldSetupMcp) && shouldSetupMcp;
4071
+ const shouldDeploy = await p8.confirm({
4072
+ message: "Would you like to deploy your docs to GitHub?"
4073
+ });
4074
+ const wantsDeploy = !p8.isCancel(shouldDeploy) && shouldDeploy;
4075
+ let deployConfig;
4076
+ let vercelToken = null;
4077
+ let vercelScope;
4078
+ let wantsVercel = false;
4079
+ let wantsCi = false;
4080
+ let ciBranch;
4081
+ if (wantsDeploy) {
4082
+ const octokit = new Octokit4({ auth: token });
4083
+ let username;
4084
+ try {
4085
+ const { data } = await octokit.rest.users.getAuthenticated();
4086
+ username = data.login;
4087
+ } catch {
4088
+ p8.log.error("Failed to fetch GitHub user info.");
4089
+ process.exit(1);
4090
+ }
4091
+ let orgs = [];
4092
+ try {
4093
+ const { data } = await octokit.rest.orgs.listForAuthenticatedUser({ per_page: 100 });
4094
+ orgs = data;
4095
+ } catch {
4096
+ }
4097
+ const ownerOptions = [
4098
+ { value: username, label: username, hint: "Personal account" },
4099
+ ...orgs.map((org) => ({ value: org.login, label: org.login, hint: "Organization" }))
4100
+ ];
4101
+ let owner = username;
4102
+ if (ownerOptions.length > 1) {
4103
+ const selected = await p8.select({
4104
+ message: "Where should the docs repo be created?",
4105
+ options: ownerOptions
4106
+ });
4107
+ if (p8.isCancel(selected)) {
4108
+ p8.cancel("Operation cancelled");
4109
+ process.exit(0);
4110
+ }
4111
+ owner = selected;
4112
+ }
4113
+ const slug = projectName ? projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") : repos[0]?.name;
4114
+ const defaultName = slug ? `${slug}-docs` : "my-project-docs";
4115
+ const repoNameInput = await p8.text({
4116
+ message: "Name for the docs GitHub repo:",
4117
+ initialValue: defaultName,
4118
+ validate: (v) => {
4119
+ if (!v || v.length === 0) return "Repo name is required";
4120
+ if (!/^[a-zA-Z0-9._-]+$/.test(v)) return "Invalid repo name";
4121
+ }
4122
+ });
4123
+ if (p8.isCancel(repoNameInput)) {
4124
+ p8.cancel("Operation cancelled");
4125
+ process.exit(0);
4126
+ }
4127
+ const visibilityInput = await p8.select({
4128
+ message: "Repository visibility:",
4129
+ options: [
4130
+ { value: "public", label: "Public" },
4131
+ { value: "private", label: "Private" }
4132
+ ]
4133
+ });
4134
+ if (p8.isCancel(visibilityInput)) {
4135
+ p8.cancel("Operation cancelled");
4136
+ process.exit(0);
4137
+ }
4138
+ deployConfig = {
4139
+ owner,
4140
+ repoName: repoNameInput,
4141
+ visibility: visibilityInput
4142
+ };
4143
+ const shouldDeployVercel = await p8.confirm({
4144
+ message: "Would you like to deploy to Vercel? (auto-deploys on every push)"
4145
+ });
4146
+ wantsVercel = !p8.isCancel(shouldDeployVercel) && shouldDeployVercel;
4147
+ if (wantsVercel) {
4148
+ vercelToken = await authenticateVercel();
4149
+ if (vercelToken) {
4150
+ try {
4151
+ const teamsRes = await fetch("https://api.vercel.com/v2/teams", {
4152
+ headers: { Authorization: `Bearer ${vercelToken}` }
4153
+ });
4154
+ const teamsData = await teamsRes.json();
4155
+ const teams = teamsData?.teams ?? [];
4156
+ if (teams.length > 0) {
4157
+ const userRes = await fetch("https://api.vercel.com/v2/user", {
4158
+ headers: { Authorization: `Bearer ${vercelToken}` }
4159
+ });
4160
+ const userData = await userRes.json();
4161
+ const vercelUsername = userData?.user?.username ?? "Personal";
4162
+ const scopeOptions = [
4163
+ { value: "__personal__", label: vercelUsername, hint: "Personal account" },
4164
+ ...teams.map((t) => ({ value: t.id, label: t.name || t.slug, hint: "Team" }))
4165
+ ];
4166
+ const selectedScope = await p8.select({
4167
+ message: "Which Vercel scope should own this project?",
4168
+ options: scopeOptions
4169
+ });
4170
+ if (p8.isCancel(selectedScope)) {
4171
+ p8.cancel("Operation cancelled");
4172
+ process.exit(0);
4173
+ }
4174
+ vercelScope = {
4175
+ teamId: selectedScope === "__personal__" ? void 0 : selectedScope
4176
+ };
4177
+ } else {
4178
+ vercelScope = { teamId: void 0 };
4179
+ }
4180
+ } catch {
4181
+ vercelScope = { teamId: void 0 };
4182
+ }
4183
+ } else {
4184
+ wantsVercel = false;
4185
+ }
4186
+ }
4187
+ const shouldSetupCi = await p8.confirm({
4188
+ message: "Would you like to set up CI to auto-update docs on every push?"
4189
+ });
4190
+ wantsCi = !p8.isCancel(shouldSetupCi) && shouldSetupCi;
4191
+ if (wantsCi) {
4192
+ const branchInput = await p8.text({
4193
+ message: "Which branch should trigger doc updates?",
4194
+ initialValue: "main",
4195
+ validate: (v) => v.length === 0 ? "Branch name is required" : void 0
4196
+ });
4197
+ if (p8.isCancel(branchInput)) {
4198
+ p8.cancel("Operation cancelled");
4199
+ process.exit(0);
4200
+ }
4201
+ ciBranch = branchInput;
4202
+ }
4203
+ }
4204
+ p8.log.step("All configured! Starting analysis and generation...");
4205
+ const outputDir = path10.resolve(options.output || "docs-site");
4206
+ const cacheDir = path10.join(outputDir, ".autodoc-cache");
3964
4207
  const cloneSpinner = p8.spinner();
3965
4208
  cloneSpinner.start(`Cloning ${repos.length} repositories...`);
3966
4209
  const clones = [];
@@ -3998,6 +4241,11 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3998
4241
  progressTable.update(repoName, { activity: formatToolActivity(event) });
3999
4242
  }
4000
4243
  });
4244
+ try {
4245
+ const headSha = getHeadSha(cloned.localPath);
4246
+ saveCache(cacheDir, repoName, headSha, result);
4247
+ } catch {
4248
+ }
4001
4249
  progressTable.update(repoName, { status: "done", summary: buildRepoSummary(result) });
4002
4250
  return { repo: repoName, result };
4003
4251
  } catch (err) {
@@ -4033,7 +4281,6 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
4033
4281
  p8.log.warn(`Cross-repo error: ${err instanceof Error ? err.message : err}`);
4034
4282
  }
4035
4283
  }
4036
- const outputDir = path10.resolve(options.output || "docs-site");
4037
4284
  if (!projectName) {
4038
4285
  projectName = results.length === 1 ? results[0].repoName : "My Project";
4039
4286
  }
@@ -4081,10 +4328,7 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
4081
4328
  p8.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
4082
4329
  }
4083
4330
  p8.log.success("Documentation generated successfully!");
4084
- const shouldSetupMcp = await p8.confirm({
4085
- message: "Set up MCP server so Claude Code can query your docs?"
4086
- });
4087
- if (!p8.isCancel(shouldSetupMcp) && shouldSetupMcp) {
4331
+ if (wantsMcp) {
4088
4332
  await setupMcpConfig({ outputDir });
4089
4333
  }
4090
4334
  let devServer;
@@ -4097,10 +4341,7 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
4097
4341
  p8.log.warn("Could not start preview server. You can run it manually:");
4098
4342
  p8.log.info(` cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`);
4099
4343
  }
4100
- const shouldDeploy = await p8.confirm({
4101
- message: "Would you like to deploy your docs to GitHub?"
4102
- });
4103
- if (p8.isCancel(shouldDeploy) || !shouldDeploy) {
4344
+ if (!wantsDeploy || !deployConfig) {
4104
4345
  if (devServer) {
4105
4346
  killDevServer(devServer);
4106
4347
  }
@@ -4117,7 +4358,8 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
4117
4358
  const deployResult = await createAndPushDocsRepo({
4118
4359
  token,
4119
4360
  docsDir: outputDir,
4120
- config
4361
+ config,
4362
+ preCollected: deployConfig
4121
4363
  });
4122
4364
  if (!deployResult) {
4123
4365
  p8.note(
@@ -4128,51 +4370,35 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
4128
4370
  return;
4129
4371
  }
4130
4372
  let vercelDeployed = false;
4131
- const shouldDeployVercel = await p8.confirm({
4132
- message: "Would you like to deploy to Vercel? (auto-deploys on every push)"
4133
- });
4134
- if (!p8.isCancel(shouldDeployVercel) && shouldDeployVercel) {
4135
- const vercelToken = await authenticateVercel();
4136
- if (vercelToken) {
4137
- const vercelResult = await deployToVercel({
4138
- token: vercelToken,
4139
- githubOwner: deployResult.owner,
4140
- githubRepo: deployResult.repoName,
4141
- docsDir: outputDir,
4142
- config
4143
- });
4144
- if (vercelResult) {
4145
- p8.log.success(`Live at: ${vercelResult.deploymentUrl}`);
4146
- vercelDeployed = true;
4147
- }
4373
+ if (wantsVercel && vercelToken) {
4374
+ const vercelResult = await deployToVercel({
4375
+ token: vercelToken,
4376
+ githubOwner: deployResult.owner,
4377
+ githubRepo: deployResult.repoName,
4378
+ docsDir: outputDir,
4379
+ config,
4380
+ scope: vercelScope
4381
+ });
4382
+ if (vercelResult) {
4383
+ p8.log.success(`Live at: ${vercelResult.deploymentUrl}`);
4384
+ vercelDeployed = true;
4148
4385
  }
4149
4386
  }
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);
4156
- }
4157
- p8.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
4158
- return;
4159
- }
4160
- const gitRoot = getGitRoot();
4161
- if (!gitRoot) {
4162
- p8.log.warn("Not in a git repository \u2014 skipping CI setup. Run `open-auto-doc setup-ci` from your project root later.");
4163
- if (!vercelDeployed) {
4164
- showVercelInstructions(deployResult.owner, deployResult.repoName);
4387
+ if (wantsCi) {
4388
+ const gitRoot = getGitRoot();
4389
+ if (!gitRoot) {
4390
+ p8.log.warn("Not in a git repository \u2014 skipping CI setup. Run `open-auto-doc setup-ci` from your project root later.");
4391
+ } else {
4392
+ await createCiWorkflow({
4393
+ gitRoot,
4394
+ docsRepoUrl: deployResult.repoUrl,
4395
+ outputDir,
4396
+ token,
4397
+ config,
4398
+ branch: ciBranch
4399
+ });
4165
4400
  }
4166
- p8.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
4167
- return;
4168
4401
  }
4169
- const ciResult = await createCiWorkflow({
4170
- gitRoot,
4171
- docsRepoUrl: deployResult.repoUrl,
4172
- outputDir,
4173
- token,
4174
- config
4175
- });
4176
4402
  if (!vercelDeployed) {
4177
4403
  showVercelInstructions(deployResult.owner, deployResult.repoName);
4178
4404
  }
@@ -4579,7 +4805,7 @@ async function logoutCommand() {
4579
4805
 
4580
4806
  // src/index.ts
4581
4807
  var program = new Command();
4582
- program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.5.2");
4808
+ program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.5.4");
4583
4809
  program.command("init", { isDefault: true }).description("Initialize and generate documentation for your repositories").option("-o, --output <dir>", "Output directory", "docs-site").action(initCommand);
4584
4810
  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
4811
  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);
@@ -0,0 +1,3 @@
1
+ # {{projectName}}
2
+
3
+ Documentation auto-generated by [Open Auto Doc](https://www.openautodoc.com).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latent-space-labs/open-auto-doc",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Auto-generate beautiful documentation websites from GitHub repositories using AI",
5
5
  "type": "module",
6
6
  "bin": {