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

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 +434 -191
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import * as p7 from "@clack/prompts";
7
+ import * as p8 from "@clack/prompts";
8
8
  import fs10 from "fs";
9
9
  import net from "net";
10
10
  import path10 from "path";
@@ -39,10 +39,10 @@ Opening ${deviceData.verification_uri} in your browser...`,
39
39
  } catch {
40
40
  p.log.warn(`Could not open browser. Please visit: ${deviceData.verification_uri}`);
41
41
  }
42
- const spinner8 = p.spinner();
43
- spinner8.start("Waiting for GitHub authorization...");
42
+ const spinner9 = p.spinner();
43
+ spinner9.start("Waiting for GitHub authorization...");
44
44
  const token = await pollForToken(deviceData.device_code, deviceData.interval);
45
- spinner8.stop("GitHub authentication successful!");
45
+ spinner9.stop("GitHub authentication successful!");
46
46
  return token;
47
47
  }
48
48
  async function pollForToken(deviceCode, interval) {
@@ -127,6 +127,14 @@ function setAnthropicKey(key) {
127
127
  creds.anthropicKey = key;
128
128
  writeCredentials(creds);
129
129
  }
130
+ function getVercelToken() {
131
+ return process.env.VERCEL_TOKEN || readCredentials().vercelToken;
132
+ }
133
+ function setVercelToken(token) {
134
+ const creds = readCredentials();
135
+ creds.vercelToken = token;
136
+ writeCredentials(creds);
137
+ }
130
138
  function clearAll() {
131
139
  if (fs.existsSync(CREDENTIALS_FILE)) {
132
140
  fs.unlinkSync(CREDENTIALS_FILE);
@@ -164,8 +172,8 @@ import { Octokit } from "@octokit/rest";
164
172
  import * as p2 from "@clack/prompts";
165
173
  async function pickRepos(token) {
166
174
  const octokit = new Octokit({ auth: token });
167
- const spinner8 = p2.spinner();
168
- spinner8.start("Fetching your repositories...");
175
+ const spinner9 = p2.spinner();
176
+ spinner9.start("Fetching your repositories...");
169
177
  const repos = [];
170
178
  let page = 1;
171
179
  while (true) {
@@ -190,7 +198,7 @@ async function pickRepos(token) {
190
198
  if (data.length < 100) break;
191
199
  page++;
192
200
  }
193
- spinner8.stop(`Found ${repos.length} repositories`);
201
+ spinner9.stop(`Found ${repos.length} repositories`);
194
202
  const selected = await p2.multiselect({
195
203
  message: "Select repositories to document",
196
204
  options: repos.slice(0, 50).map((r) => ({
@@ -292,8 +300,8 @@ async function createAndPushDocsRepo(params) {
292
300
  ]
293
301
  });
294
302
  if (p3.isCancel(visibility)) return null;
295
- const spinner8 = p3.spinner();
296
- spinner8.start(`Creating GitHub repo ${owner}/${repoName}...`);
303
+ const spinner9 = p3.spinner();
304
+ spinner9.start(`Creating GitHub repo ${owner}/${repoName}...`);
297
305
  let repoUrl;
298
306
  try {
299
307
  if (isOrg) {
@@ -305,7 +313,7 @@ async function createAndPushDocsRepo(params) {
305
313
  auto_init: false
306
314
  });
307
315
  repoUrl = data.clone_url;
308
- spinner8.stop(`Created ${data.full_name}`);
316
+ spinner9.stop(`Created ${data.full_name}`);
309
317
  } else {
310
318
  const { data } = await octokit.rest.repos.createForAuthenticatedUser({
311
319
  name: repoName,
@@ -314,10 +322,10 @@ async function createAndPushDocsRepo(params) {
314
322
  auto_init: false
315
323
  });
316
324
  repoUrl = data.clone_url;
317
- spinner8.stop(`Created ${data.full_name}`);
325
+ spinner9.stop(`Created ${data.full_name}`);
318
326
  }
319
327
  } catch (err) {
320
- spinner8.stop("Failed to create repo.");
328
+ spinner9.stop("Failed to create repo.");
321
329
  if (err?.status === 422) {
322
330
  p3.log.error(`Repository "${repoName}" already exists. Choose a different name or delete it first.`);
323
331
  } else {
@@ -325,7 +333,7 @@ async function createAndPushDocsRepo(params) {
325
333
  }
326
334
  return null;
327
335
  }
328
- spinner8.start("Pushing docs to GitHub...");
336
+ spinner9.start("Pushing docs to GitHub...");
329
337
  try {
330
338
  const gitignorePath = path4.join(docsDir, ".gitignore");
331
339
  if (!fs4.existsSync(gitignorePath)) {
@@ -344,9 +352,9 @@ async function createAndPushDocsRepo(params) {
344
352
  exec(`git remote add origin ${pushUrl}`, docsDir);
345
353
  exec("git push -u origin main", docsDir);
346
354
  exec("git remote set-url origin " + repoUrl, docsDir);
347
- spinner8.stop("Pushed to GitHub.");
355
+ spinner9.stop("Pushed to GitHub.");
348
356
  } catch (err) {
349
- spinner8.stop("Git push failed.");
357
+ spinner9.stop("Git push failed.");
350
358
  p3.log.error(`${err instanceof Error ? err.message : err}`);
351
359
  return null;
352
360
  }
@@ -357,8 +365,8 @@ async function createAndPushDocsRepo(params) {
357
365
  }
358
366
  async function pushUpdates(params) {
359
367
  const { token, docsDir, docsRepo } = params;
360
- const spinner8 = p3.spinner();
361
- spinner8.start("Pushing updates to docs repo...");
368
+ const spinner9 = p3.spinner();
369
+ spinner9.start("Pushing updates to docs repo...");
362
370
  try {
363
371
  if (!fs4.existsSync(path4.join(docsDir, ".git"))) {
364
372
  exec("git init", docsDir);
@@ -367,16 +375,16 @@ async function pushUpdates(params) {
367
375
  exec("git add -A", docsDir);
368
376
  try {
369
377
  exec("git diff --cached --quiet", docsDir);
370
- spinner8.stop("No changes to push.");
378
+ spinner9.stop("No changes to push.");
371
379
  return false;
372
380
  } catch {
373
381
  }
374
382
  exec('git commit -m "Update documentation"', docsDir);
375
383
  exec("git push -u origin main", docsDir);
376
- spinner8.stop("Pushed updates to docs repo.");
384
+ spinner9.stop("Pushed updates to docs repo.");
377
385
  return true;
378
386
  } catch (err) {
379
- spinner8.stop("Push failed.");
387
+ spinner9.stop("Push failed.");
380
388
  p3.log.error(`${err instanceof Error ? err.message : err}`);
381
389
  return false;
382
390
  }
@@ -397,8 +405,197 @@ function showVercelInstructions(owner, repoName) {
397
405
  );
398
406
  }
399
407
 
400
- // src/actions/build-check.ts
408
+ // src/actions/vercel-action.ts
401
409
  import * as p4 from "@clack/prompts";
410
+ var VERCEL_API = "https://api.vercel.com";
411
+ async function vercelFetch(path13, token, options = {}) {
412
+ const { method = "GET", body, teamId } = options;
413
+ const url = new URL(path13, VERCEL_API);
414
+ if (teamId) {
415
+ url.searchParams.set("teamId", teamId);
416
+ }
417
+ const res = await fetch(url.toString(), {
418
+ method,
419
+ headers: {
420
+ Authorization: `Bearer ${token}`,
421
+ "Content-Type": "application/json"
422
+ },
423
+ ...body ? { body: JSON.stringify(body) } : {}
424
+ });
425
+ const data = await res.json().catch(() => ({}));
426
+ return { ok: res.ok, status: res.status, data };
427
+ }
428
+ async function authenticateVercel() {
429
+ const existing = getVercelToken();
430
+ if (existing) {
431
+ const { ok: ok2 } = await vercelFetch(
432
+ "/v2/user",
433
+ existing
434
+ );
435
+ if (ok2) {
436
+ p4.log.success("Using saved Vercel token.");
437
+ return existing;
438
+ }
439
+ p4.log.warn("Saved Vercel token is invalid.");
440
+ }
441
+ p4.log.info(
442
+ "Create a Vercel token at: https://vercel.com/account/tokens"
443
+ );
444
+ const tokenInput = await p4.text({
445
+ message: "Enter your Vercel token",
446
+ placeholder: "xxxxxxxxxxxxxxxxxxxxxxxx",
447
+ validate: (v) => {
448
+ if (!v || v.trim().length === 0) return "Token is required";
449
+ }
450
+ });
451
+ if (p4.isCancel(tokenInput)) return null;
452
+ const token = tokenInput.trim();
453
+ const { ok, data } = await vercelFetch(
454
+ "/v2/user",
455
+ token
456
+ );
457
+ if (!ok) {
458
+ p4.log.error("Invalid Vercel token. Please check and try again.");
459
+ return null;
460
+ }
461
+ p4.log.success(`Authenticated as ${data.user?.username ?? "Vercel user"}`);
462
+ const save = await p4.confirm({
463
+ message: "Save Vercel token for future use?"
464
+ });
465
+ if (!p4.isCancel(save) && save) {
466
+ setVercelToken(token);
467
+ }
468
+ return token;
469
+ }
470
+ async function deployToVercel(params) {
471
+ 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
+ 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;
495
+ }
496
+ }
497
+ const spinner9 = p4.spinner();
498
+ spinner9.start("Creating Vercel project...");
499
+ const repoSlug = `${githubOwner}/${githubRepo}`;
500
+ const createProject = async () => {
501
+ return vercelFetch(
502
+ "/v10/projects",
503
+ token,
504
+ {
505
+ method: "POST",
506
+ teamId,
507
+ body: {
508
+ name: githubRepo,
509
+ framework: "nextjs",
510
+ gitRepository: {
511
+ type: "github",
512
+ repo: repoSlug
513
+ }
514
+ }
515
+ }
516
+ );
517
+ };
518
+ let projectRes = await createProject();
519
+ if (!projectRes.ok && (projectRes.status === 400 || projectRes.status === 403)) {
520
+ spinner9.stop("GitHub integration required.");
521
+ const errorData = projectRes.data;
522
+ const errorMsg = errorData?.error?.message || "Vercel cannot access your GitHub repo.";
523
+ p4.log.warn(errorMsg);
524
+ p4.log.info(
525
+ "Install the Vercel GitHub App: https://github.com/apps/vercel"
526
+ );
527
+ const retry = await p4.confirm({
528
+ message: "Have you installed the Vercel GitHub App? Retry?"
529
+ });
530
+ if (p4.isCancel(retry) || !retry) return null;
531
+ spinner9.start("Retrying project creation...");
532
+ projectRes = await createProject();
533
+ }
534
+ if (!projectRes.ok) {
535
+ spinner9.stop("Failed to create Vercel project.");
536
+ const errorData = projectRes.data;
537
+ p4.log.error(
538
+ `Vercel API error (${projectRes.status}): ${errorData?.error?.message || JSON.stringify(errorData)}`
539
+ );
540
+ return null;
541
+ }
542
+ const project = projectRes.data;
543
+ const projectId = project.id;
544
+ const projectName = project.name;
545
+ spinner9.stop(`Created Vercel project: ${projectName}`);
546
+ spinner9.start("Waiting for first deployment...");
547
+ const maxWaitMs = 5 * 60 * 1e3;
548
+ const pollIntervalMs = 5e3;
549
+ const startTime = Date.now();
550
+ let deploymentUrl;
551
+ while (Date.now() - startTime < maxWaitMs) {
552
+ const { ok: deploymentsOk, data: deploymentsData } = await vercelFetch(
553
+ `/v6/deployments?projectId=${projectId}&limit=1`,
554
+ token,
555
+ { teamId }
556
+ );
557
+ if (deploymentsOk) {
558
+ const deployments = deploymentsData.deployments ?? [];
559
+ if (deployments.length > 0) {
560
+ const deployment = deployments[0];
561
+ const state = deployment.state?.toUpperCase();
562
+ if (state === "READY") {
563
+ deploymentUrl = `https://${deployment.url}`;
564
+ break;
565
+ } else if (state === "ERROR" || state === "CANCELED") {
566
+ spinner9.stop("Deployment failed.");
567
+ p4.log.warn(
568
+ `Check the Vercel dashboard: https://vercel.com/${teamId ? `team/${teamId}` : "dashboard"}/${projectName}`
569
+ );
570
+ return null;
571
+ }
572
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
573
+ spinner9.message(`Waiting for deployment... (${state}, ${elapsed}s)`);
574
+ }
575
+ }
576
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
577
+ }
578
+ if (!deploymentUrl) {
579
+ spinner9.stop("Deployment is still in progress.");
580
+ const dashboardUrl = `https://vercel.com/${teamId ? `team/${teamId}` : "dashboard"}/${projectName}`;
581
+ p4.log.warn(
582
+ `Timed out waiting for deployment. Check status at: ${dashboardUrl}`
583
+ );
584
+ const projectUrl2 = `https://vercel.com/${teamId ? `team/${teamId}` : "dashboard"}/${projectName}`;
585
+ return { projectUrl: projectUrl2, deploymentUrl: dashboardUrl };
586
+ }
587
+ spinner9.stop(`Deployed successfully!`);
588
+ const projectUrl = `https://vercel.com/${teamId ? `team/${teamId}` : "dashboard"}/${projectName}`;
589
+ try {
590
+ const updatedConfig = { ...config, vercelUrl: deploymentUrl };
591
+ saveConfig(updatedConfig);
592
+ } catch {
593
+ }
594
+ return { projectUrl, deploymentUrl };
595
+ }
596
+
597
+ // src/actions/build-check.ts
598
+ import * as p5 from "@clack/prompts";
402
599
  import { execSync as execSync4 } from "child_process";
403
600
 
404
601
  // ../analyzer/dist/index.js
@@ -528,7 +725,7 @@ function detectEntryFiles(flatFiles) {
528
725
  /^app\/page\.tsx$/,
529
726
  /^pages\/index\.\w+$/
530
727
  ];
531
- return flatFiles.filter((f) => entryPatterns.some((p13) => p13.test(f)));
728
+ return flatFiles.filter((f) => entryPatterns.some((p14) => p14.test(f)));
532
729
  }
533
730
  var DEP_FILES = [
534
731
  {
@@ -782,9 +979,9 @@ var EFFICIENCY_HINTS = `
782
979
  - NEVER use angle-bracket placeholders like <your-api-key> or <repository-url> in your output. Use backtick-wrapped text instead: \`your-api-key\`, \`repository-url\`. Angle brackets break MDX parsing.
783
980
  - When including code examples with triple backticks, ensure the opening and closing fence markers start at column 0 (no leading spaces before the backtick fence).`;
784
981
  var FILLER_PATTERNS = /^(let me|i'll|i need to|i want to|i should|i will|now i'll|now let me|now i need|first,? i|next,? i|okay|alright)/i;
785
- function truncateAtWord(text4, max) {
786
- if (text4.length <= max) return text4;
787
- const truncated = text4.slice(0, max);
982
+ function truncateAtWord(text5, max) {
983
+ if (text5.length <= max) return text5;
984
+ const truncated = text5.slice(0, max);
788
985
  const lastSpace = truncated.lastIndexOf(" ");
789
986
  return (lastSpace > max * 0.5 ? truncated.slice(0, lastSpace) : truncated) + "...";
790
987
  }
@@ -1731,7 +1928,7 @@ function classifyChanges(entries, staticAnalysis) {
1731
1928
  const affected = /* @__PURE__ */ new Set();
1732
1929
  for (const entry of entries) {
1733
1930
  for (const [section, patterns] of Object.entries(SECTION_PATTERNS)) {
1734
- if (patterns.some((p13) => p13.test(entry.filePath))) {
1931
+ if (patterns.some((p14) => p14.test(entry.filePath))) {
1735
1932
  affected.add(section);
1736
1933
  }
1737
1934
  }
@@ -2422,43 +2619,43 @@ function extractErrorSummary(output, maxLines = 30) {
2422
2619
  }
2423
2620
  async function runBuildCheck(options) {
2424
2621
  const { docsDir, apiKey, model, maxAttempts = 3 } = options;
2425
- const spinner8 = p4.spinner();
2426
- spinner8.start("Verifying documentation build...");
2622
+ const spinner9 = p5.spinner();
2623
+ spinner9.start("Verifying documentation build...");
2427
2624
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2428
2625
  const { success, output } = runBuild(docsDir);
2429
2626
  if (success) {
2430
- spinner8.stop("Documentation build verified");
2627
+ spinner9.stop("Documentation build verified");
2431
2628
  return { success: true, attempts: attempt };
2432
2629
  }
2433
- spinner8.stop("Build errors detected");
2630
+ spinner9.stop("Build errors detected");
2434
2631
  const errorSummary = extractErrorSummary(output);
2435
- p4.log.warn(`Build error output:
2632
+ p5.log.warn(`Build error output:
2436
2633
  ${errorSummary}`);
2437
2634
  if (attempt >= maxAttempts) {
2438
- p4.log.warn(
2635
+ p5.log.warn(
2439
2636
  "Documentation build has errors that could not be auto-fixed.\nYour docs site may still work \u2014 some errors are non-fatal.\nYou can fix remaining issues manually and run `npm run build` in the docs directory."
2440
2637
  );
2441
2638
  return { success: false, attempts: attempt, lastErrors: output };
2442
2639
  }
2443
- spinner8.start(`AI is diagnosing and fixing build errors (attempt ${attempt}/${maxAttempts})...`);
2640
+ spinner9.start(`AI is diagnosing and fixing build errors (attempt ${attempt}/${maxAttempts})...`);
2444
2641
  try {
2445
2642
  const result = await fixMdxBuildErrors(
2446
2643
  docsDir,
2447
2644
  truncateErrors(output),
2448
2645
  apiKey,
2449
2646
  model,
2450
- (text4) => spinner8.message(text4)
2647
+ (text5) => spinner9.message(text5)
2451
2648
  );
2452
2649
  if (!result.fixed) {
2453
- spinner8.stop("AI fixer could not resolve the build errors");
2454
- p4.log.warn(result.summary);
2650
+ spinner9.stop("AI fixer could not resolve the build errors");
2651
+ p5.log.warn(result.summary);
2455
2652
  return { success: false, attempts: attempt, lastErrors: output };
2456
2653
  }
2457
- spinner8.stop(`Fixed ${result.filesChanged.length} file(s): ${result.summary}`);
2458
- spinner8.start("Re-verifying build...");
2654
+ spinner9.stop(`Fixed ${result.filesChanged.length} file(s): ${result.summary}`);
2655
+ spinner9.start("Re-verifying build...");
2459
2656
  } catch (err) {
2460
- spinner8.stop("AI fixer encountered an error");
2461
- p4.log.warn(`Fixer error: ${err instanceof Error ? err.message : err}`);
2657
+ spinner9.stop("AI fixer encountered an error");
2658
+ p5.log.warn(`Fixer error: ${err instanceof Error ? err.message : err}`);
2462
2659
  return { success: false, attempts: attempt, lastErrors: output };
2463
2660
  }
2464
2661
  }
@@ -2466,7 +2663,7 @@ ${errorSummary}`);
2466
2663
  }
2467
2664
 
2468
2665
  // src/actions/setup-ci-action.ts
2469
- import * as p5 from "@clack/prompts";
2666
+ import * as p6 from "@clack/prompts";
2470
2667
  import { execSync as execSync5 } from "child_process";
2471
2668
  import fs7 from "fs";
2472
2669
  import path7 from "path";
@@ -2609,14 +2806,14 @@ async function createCiWorkflow(params) {
2609
2806
  });
2610
2807
  }
2611
2808
  const relativeOutputDir = path7.relative(gitRoot, path7.resolve(outputDir));
2612
- p5.log.info(`Docs repo: ${docsRepoUrl}`);
2613
- p5.log.info(`Output directory: ${relativeOutputDir}`);
2614
- const branch = await p5.text({
2809
+ p6.log.info(`Docs repo: ${docsRepoUrl}`);
2810
+ p6.log.info(`Output directory: ${relativeOutputDir}`);
2811
+ const branch = await p6.text({
2615
2812
  message: "Which branch should trigger doc updates?",
2616
2813
  initialValue: "main",
2617
2814
  validate: (v) => v.length === 0 ? "Branch name is required" : void 0
2618
2815
  });
2619
- if (p5.isCancel(branch)) return null;
2816
+ if (p6.isCancel(branch)) return null;
2620
2817
  const workflowDir = path7.join(gitRoot, ".github", "workflows");
2621
2818
  const workflowPath = path7.join(workflowDir, "update-docs.yml");
2622
2819
  fs7.mkdirSync(workflowDir, { recursive: true });
@@ -2625,7 +2822,7 @@ async function createCiWorkflow(params) {
2625
2822
  generateWorkflow(branch, docsRepoUrl, relativeOutputDir),
2626
2823
  "utf-8"
2627
2824
  );
2628
- p5.log.success(`Created ${path7.relative(gitRoot, workflowPath)}`);
2825
+ p6.log.success(`Created ${path7.relative(gitRoot, workflowPath)}`);
2629
2826
  if (token) {
2630
2827
  try {
2631
2828
  const origin = execSync5("git remote get-url origin", {
@@ -2651,19 +2848,19 @@ async function createCiWorkflow(params) {
2651
2848
  async function createCiWorkflowsMultiRepo(params) {
2652
2849
  const { token, config, docsRepoUrl } = params;
2653
2850
  const octokit = new Octokit3({ auth: token });
2654
- p5.log.info(`Setting up CI for ${config.repos.length} repositories`);
2655
- p5.log.info(`Docs repo: ${docsRepoUrl}`);
2656
- const branch = await p5.text({
2851
+ p6.log.info(`Setting up CI for ${config.repos.length} repositories`);
2852
+ p6.log.info(`Docs repo: ${docsRepoUrl}`);
2853
+ const branch = await p6.text({
2657
2854
  message: "Which branch should trigger doc updates?",
2658
2855
  initialValue: "main",
2659
2856
  validate: (v) => v.length === 0 ? "Branch name is required" : void 0
2660
2857
  });
2661
- if (p5.isCancel(branch)) return null;
2858
+ if (p6.isCancel(branch)) return null;
2662
2859
  const createdRepos = [];
2663
2860
  const workflowPath = ".github/workflows/update-docs.yml";
2664
2861
  for (const repo of config.repos) {
2665
- const spinner8 = p5.spinner();
2666
- spinner8.start(`Pushing workflow to ${repo.fullName}...`);
2862
+ const spinner9 = p6.spinner();
2863
+ spinner9.start(`Pushing workflow to ${repo.fullName}...`);
2667
2864
  try {
2668
2865
  const [owner, repoName] = repo.fullName.split("/");
2669
2866
  const workflowContent = generatePerRepoWorkflow(branch, repo.name, docsRepoUrl);
@@ -2689,17 +2886,17 @@ async function createCiWorkflowsMultiRepo(params) {
2689
2886
  ...existingSha ? { sha: existingSha } : {}
2690
2887
  });
2691
2888
  createdRepos.push(repo.fullName);
2692
- spinner8.stop(`Created workflow in ${repo.fullName}`);
2889
+ spinner9.stop(`Created workflow in ${repo.fullName}`);
2693
2890
  } catch (err) {
2694
- spinner8.stop(`Failed for ${repo.fullName}`);
2695
- p5.log.warn(`Could not push workflow to ${repo.fullName}: ${err?.message || err}`);
2891
+ spinner9.stop(`Failed for ${repo.fullName}`);
2892
+ p6.log.warn(`Could not push workflow to ${repo.fullName}: ${err?.message || err}`);
2696
2893
  }
2697
2894
  }
2698
2895
  if (createdRepos.length === 0) {
2699
- p5.log.error("Failed to create workflows in any repository.");
2896
+ p6.log.error("Failed to create workflows in any repository.");
2700
2897
  return null;
2701
2898
  }
2702
- p5.log.success(`Created workflows in ${createdRepos.length}/${config.repos.length} repositories`);
2899
+ p6.log.success(`Created workflows in ${createdRepos.length}/${config.repos.length} repositories`);
2703
2900
  await verifySecretsInteractive(octokit, createdRepos);
2704
2901
  return { repos: createdRepos, branch };
2705
2902
  }
@@ -2713,15 +2910,15 @@ async function checkSecret(octokit, owner, repo, secretName) {
2713
2910
  }
2714
2911
  }
2715
2912
  async function verifySecretsInteractive(octokit, repos) {
2716
- const shouldVerify = await p5.confirm({
2913
+ const shouldVerify = await p6.confirm({
2717
2914
  message: "Would you like to verify secrets now? (you can add them later)",
2718
2915
  initialValue: true
2719
2916
  });
2720
- if (p5.isCancel(shouldVerify) || !shouldVerify) {
2917
+ if (p6.isCancel(shouldVerify) || !shouldVerify) {
2721
2918
  showSecretsInstructions(repos.length > 1);
2722
2919
  return;
2723
2920
  }
2724
- p5.note(
2921
+ p6.note(
2725
2922
  [
2726
2923
  "Each source repo needs two Actions secrets:",
2727
2924
  "",
@@ -2738,11 +2935,11 @@ async function verifySecretsInteractive(octokit, repos) {
2738
2935
  const summary = {};
2739
2936
  for (const fullName of repos) {
2740
2937
  const [owner, repoName] = fullName.split("/");
2741
- p5.log.step(`Add secrets to ${fullName}`);
2742
- p5.log.message(` https://github.com/${fullName}/settings/secrets/actions`);
2938
+ p6.log.step(`Add secrets to ${fullName}`);
2939
+ p6.log.message(` https://github.com/${fullName}/settings/secrets/actions`);
2743
2940
  let allFound = false;
2744
2941
  while (!allFound) {
2745
- await p5.text({
2942
+ await p6.text({
2746
2943
  message: `Press enter when you've added the secrets to ${fullName}...`,
2747
2944
  defaultValue: "",
2748
2945
  placeholder: ""
@@ -2753,19 +2950,19 @@ async function verifySecretsInteractive(octokit, repos) {
2753
2950
  }
2754
2951
  for (const secret of REQUIRED_SECRETS) {
2755
2952
  if (results[secret]) {
2756
- p5.log.success(`${secret} \u2014 found`);
2953
+ p6.log.success(`${secret} \u2014 found`);
2757
2954
  } else {
2758
- p5.log.warn(`${secret} \u2014 not found`);
2955
+ p6.log.warn(`${secret} \u2014 not found`);
2759
2956
  }
2760
2957
  }
2761
2958
  summary[fullName] = results;
2762
2959
  allFound = REQUIRED_SECRETS.every((s) => results[s]);
2763
2960
  if (!allFound) {
2764
- const retry = await p5.confirm({
2961
+ const retry = await p6.confirm({
2765
2962
  message: "Some secrets are missing. Would you like to try again?",
2766
2963
  initialValue: true
2767
2964
  });
2768
- if (p5.isCancel(retry) || !retry) break;
2965
+ if (p6.isCancel(retry) || !retry) break;
2769
2966
  }
2770
2967
  }
2771
2968
  }
@@ -2780,15 +2977,15 @@ async function verifySecretsInteractive(octokit, repos) {
2780
2977
  lines.push(`${fullName} \u2014 ${parts.join(" ")}`);
2781
2978
  }
2782
2979
  if (allGreen) {
2783
- p5.note(lines.join("\n"), "All secrets verified!");
2980
+ p6.note(lines.join("\n"), "All secrets verified!");
2784
2981
  } else {
2785
- p5.note(lines.join("\n"), "Secret status");
2786
- p5.log.warn("Some secrets are still missing \u2014 workflows will fail until they are added.");
2982
+ p6.note(lines.join("\n"), "Secret status");
2983
+ p6.log.warn("Some secrets are still missing \u2014 workflows will fail until they are added.");
2787
2984
  }
2788
2985
  }
2789
2986
  function showSecretsInstructions(multiRepo = false) {
2790
2987
  const repoNote = multiRepo ? "Add these secrets to EACH source repository:" : "Add these secrets to your GitHub repository:";
2791
- p5.note(
2988
+ p6.note(
2792
2989
  [
2793
2990
  repoNote,
2794
2991
  "(Settings \u2192 Secrets and variables \u2192 Actions \u2192 New repository secret)",
@@ -2810,7 +3007,7 @@ function showSecretsInstructions(multiRepo = false) {
2810
3007
  }
2811
3008
 
2812
3009
  // src/commands/setup-mcp.ts
2813
- import * as p6 from "@clack/prompts";
3010
+ import * as p7 from "@clack/prompts";
2814
3011
  import fs8 from "fs";
2815
3012
  import path8 from "path";
2816
3013
  var MCP_SERVER_KEY = "project-docs";
@@ -2823,23 +3020,23 @@ function getMcpConfig() {
2823
3020
  async function setupMcpConfig(opts) {
2824
3021
  const cacheDir = path8.join(opts.outputDir, ".autodoc-cache");
2825
3022
  if (!fs8.existsSync(cacheDir)) {
2826
- p6.log.warn("No analysis cache found \u2014 skipping MCP setup. Run setup-mcp after generating docs.");
3023
+ p7.log.warn("No analysis cache found \u2014 skipping MCP setup. Run setup-mcp after generating docs.");
2827
3024
  return;
2828
3025
  }
2829
3026
  writeMcpJson(process.cwd());
2830
3027
  }
2831
3028
  async function setupMcpCommand() {
2832
- p6.intro("open-auto-doc \u2014 MCP Server Setup");
3029
+ p7.intro("open-auto-doc \u2014 MCP Server Setup");
2833
3030
  const config = loadConfig();
2834
3031
  if (!config) {
2835
- p6.log.error(
3032
+ p7.log.error(
2836
3033
  "No .autodocrc.json found. Run `open-auto-doc init` first to generate documentation."
2837
3034
  );
2838
3035
  process.exit(1);
2839
3036
  }
2840
3037
  const cacheDir = path8.join(config.outputDir, ".autodoc-cache");
2841
3038
  if (!fs8.existsSync(cacheDir)) {
2842
- p6.log.error(
3039
+ p7.log.error(
2843
3040
  `No analysis cache found at ${cacheDir}.
2844
3041
  Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
2845
3042
  );
@@ -2847,12 +3044,12 @@ Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
2847
3044
  }
2848
3045
  const cacheFiles = fs8.readdirSync(cacheDir).filter((f) => f.endsWith("-analysis.json"));
2849
3046
  if (cacheFiles.length === 0) {
2850
- p6.log.error("Cache directory exists but contains no analysis files.");
3047
+ p7.log.error("Cache directory exists but contains no analysis files.");
2851
3048
  process.exit(1);
2852
3049
  }
2853
3050
  writeMcpJson(process.cwd());
2854
- p6.log.success("MCP server configured!");
2855
- p6.note(
3051
+ p7.log.success("MCP server configured!");
3052
+ p7.note(
2856
3053
  [
2857
3054
  "The following tools are now available in Claude Code:",
2858
3055
  "",
@@ -2867,7 +3064,7 @@ Run \`open-auto-doc init\` or \`open-auto-doc generate\` first.`
2867
3064
  ].join("\n"),
2868
3065
  "Available MCP tools"
2869
3066
  );
2870
- p6.outro("Open Claude Code in this project to start using the tools.");
3067
+ p7.outro("Open Claude Code in this project to start using the tools.");
2871
3068
  }
2872
3069
  function writeMcpJson(projectRoot) {
2873
3070
  const mcpPath = path8.join(projectRoot, ".mcp.json");
@@ -2882,7 +3079,7 @@ function writeMcpJson(projectRoot) {
2882
3079
  mcpServers[MCP_SERVER_KEY] = getMcpConfig();
2883
3080
  const merged = { ...existing, mcpServers };
2884
3081
  fs8.writeFileSync(mcpPath, JSON.stringify(merged, null, 2) + "\n");
2885
- p6.log.step(`Wrote ${path8.relative(projectRoot, mcpPath)}`);
3082
+ p7.log.step(`Wrote ${path8.relative(projectRoot, mcpPath)}`);
2886
3083
  }
2887
3084
 
2888
3085
  // ../generator/dist/index.js
@@ -3055,9 +3252,9 @@ Handlebars.registerHelper("join", (arr, sep) => {
3055
3252
  if (!Array.isArray(arr)) return "";
3056
3253
  return arr.join(typeof sep === "string" ? sep : ", ");
3057
3254
  });
3058
- Handlebars.registerHelper("pipeEscape", (text4) => {
3059
- if (typeof text4 !== "string") return "";
3060
- return text4.replace(/\|/g, "\\|");
3255
+ Handlebars.registerHelper("pipeEscape", (text5) => {
3256
+ if (typeof text5 !== "string") return "";
3257
+ return text5.replace(/\|/g, "\\|");
3061
3258
  });
3062
3259
  var templatesLoaded = false;
3063
3260
  var templates = {};
@@ -3693,10 +3890,10 @@ function buildRepoSummary(result) {
3693
3890
  // src/commands/init.ts
3694
3891
  var __dirname2 = path10.dirname(fileURLToPath2(import.meta.url));
3695
3892
  async function initCommand(options) {
3696
- p7.intro("open-auto-doc \u2014 AI-powered documentation generator");
3893
+ p8.intro("open-auto-doc \u2014 AI-powered documentation generator");
3697
3894
  const templateDir = resolveTemplateDir();
3698
3895
  if (!fs10.existsSync(path10.join(templateDir, "package.json"))) {
3699
- p7.log.error(
3896
+ p8.log.error(
3700
3897
  `Site template not found at: ${templateDir}
3701
3898
  This usually means the npm package was not built correctly.
3702
3899
  Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
@@ -3705,53 +3902,53 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3705
3902
  }
3706
3903
  let token = getGithubToken();
3707
3904
  if (!token) {
3708
- p7.log.info("Let's connect your GitHub account.");
3905
+ p8.log.info("Let's connect your GitHub account.");
3709
3906
  token = await authenticateWithGithub();
3710
3907
  setGithubToken(token);
3711
3908
  } else {
3712
- p7.log.success("Using saved GitHub credentials.");
3909
+ p8.log.success("Using saved GitHub credentials.");
3713
3910
  }
3714
3911
  const repos = await pickRepos(token);
3715
- p7.log.info(`Selected ${repos.length} ${repos.length === 1 ? "repository" : "repositories"}`);
3912
+ p8.log.info(`Selected ${repos.length} ${repos.length === 1 ? "repository" : "repositories"}`);
3716
3913
  let projectName;
3717
3914
  if (repos.length > 1) {
3718
- const nameInput = await p7.text({
3915
+ const nameInput = await p8.text({
3719
3916
  message: "What would you like to name this project?",
3720
3917
  placeholder: "My Project",
3721
3918
  validate: (v) => {
3722
3919
  if (!v || v.trim().length === 0) return "Project name is required";
3723
3920
  }
3724
3921
  });
3725
- if (p7.isCancel(nameInput)) {
3726
- p7.cancel("Operation cancelled");
3922
+ if (p8.isCancel(nameInput)) {
3923
+ p8.cancel("Operation cancelled");
3727
3924
  process.exit(0);
3728
3925
  }
3729
3926
  projectName = nameInput;
3730
3927
  }
3731
3928
  let apiKey = getAnthropicKey();
3732
3929
  if (!apiKey) {
3733
- const keyInput = await p7.text({
3930
+ const keyInput = await p8.text({
3734
3931
  message: "Enter your Anthropic API key",
3735
3932
  placeholder: "sk-ant-...",
3736
3933
  validate: (v) => {
3737
3934
  if (!v || !v.startsWith("sk-ant-")) return "Please enter a valid Anthropic API key";
3738
3935
  }
3739
3936
  });
3740
- if (p7.isCancel(keyInput)) {
3741
- p7.cancel("Operation cancelled");
3937
+ if (p8.isCancel(keyInput)) {
3938
+ p8.cancel("Operation cancelled");
3742
3939
  process.exit(0);
3743
3940
  }
3744
3941
  apiKey = keyInput;
3745
- const saveKey = await p7.confirm({
3942
+ const saveKey = await p8.confirm({
3746
3943
  message: "Save API key for future use?"
3747
3944
  });
3748
- if (saveKey && !p7.isCancel(saveKey)) {
3945
+ if (saveKey && !p8.isCancel(saveKey)) {
3749
3946
  setAnthropicKey(apiKey);
3750
3947
  }
3751
3948
  } else {
3752
- p7.log.success("Using saved Anthropic API key.");
3949
+ p8.log.success("Using saved Anthropic API key.");
3753
3950
  }
3754
- const model = await p7.select({
3951
+ const model = await p8.select({
3755
3952
  message: "Which model should analyze your repos?",
3756
3953
  options: [
3757
3954
  { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", hint: "Fast & capable (recommended)" },
@@ -3759,12 +3956,12 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3759
3956
  { value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "Most capable, slowest" }
3760
3957
  ]
3761
3958
  });
3762
- if (p7.isCancel(model)) {
3763
- p7.cancel("Operation cancelled");
3959
+ if (p8.isCancel(model)) {
3960
+ p8.cancel("Operation cancelled");
3764
3961
  process.exit(0);
3765
3962
  }
3766
- p7.log.info(`Using ${model}`);
3767
- const cloneSpinner = p7.spinner();
3963
+ p8.log.info(`Using ${model}`);
3964
+ const cloneSpinner = p8.spinner();
3768
3965
  cloneSpinner.start(`Cloning ${repos.length} repositories...`);
3769
3966
  const clones = [];
3770
3967
  for (const repo of repos) {
@@ -3773,12 +3970,12 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3773
3970
  const cloned = cloneRepo(repo, token);
3774
3971
  clones.push(cloned);
3775
3972
  } catch (err) {
3776
- p7.log.warn(`Failed to clone ${repo.name}: ${err instanceof Error ? err.message : err}`);
3973
+ p8.log.warn(`Failed to clone ${repo.name}: ${err instanceof Error ? err.message : err}`);
3777
3974
  }
3778
3975
  }
3779
3976
  cloneSpinner.stop(`Cloned ${clones.length}/${repos.length} repositories`);
3780
3977
  if (clones.length === 0) {
3781
- p7.log.error("No repositories were cloned.");
3978
+ p8.log.error("No repositories were cloned.");
3782
3979
  process.exit(1);
3783
3980
  }
3784
3981
  const total = clones.length;
@@ -3806,7 +4003,7 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3806
4003
  } catch (err) {
3807
4004
  const errMsg = err instanceof Error ? err.message : String(err);
3808
4005
  progressTable.update(repoName, { status: "failed", error: errMsg });
3809
- p7.log.warn(`[${repoName}] Analysis failed: ${errMsg}`);
4006
+ p8.log.warn(`[${repoName}] Analysis failed: ${errMsg}`);
3810
4007
  return { repo: repoName, result: null };
3811
4008
  }
3812
4009
  });
@@ -3814,40 +4011,40 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3814
4011
  progressTable.stop();
3815
4012
  const results = settled.filter((s) => s.result !== null).map((s) => s.result);
3816
4013
  const { done, failed } = progressTable.getSummary();
3817
- p7.log.step(
4014
+ p8.log.step(
3818
4015
  `Analyzed ${done}/${total} repositories` + (failed > 0 ? ` (${failed} failed)` : "") + (results.length > 0 ? ` \u2014 ${results.reduce((n, r) => n + r.apiEndpoints.length, 0)} endpoints, ${results.reduce((n, r) => n + r.components.length, 0)} components, ${results.reduce((n, r) => n + r.diagrams.length, 0)} diagrams` : "")
3819
4016
  );
3820
4017
  if (results.length === 0) {
3821
- p7.log.error("No repositories were successfully analyzed.");
4018
+ p8.log.error("No repositories were successfully analyzed.");
3822
4019
  cleanup(clones);
3823
4020
  process.exit(1);
3824
4021
  }
3825
4022
  let crossRepo;
3826
4023
  if (results.length > 1) {
3827
- const crossSpinner = p7.spinner();
4024
+ const crossSpinner = p8.spinner();
3828
4025
  crossSpinner.start("Analyzing cross-repository relationships...");
3829
4026
  try {
3830
- crossRepo = await analyzeCrossRepos(results, apiKey, model, (text4) => {
3831
- crossSpinner.message(text4.slice(0, 80));
4027
+ crossRepo = await analyzeCrossRepos(results, apiKey, model, (text5) => {
4028
+ crossSpinner.message(text5.slice(0, 80));
3832
4029
  });
3833
4030
  crossSpinner.stop(`Cross-repo analysis complete \u2014 ${crossRepo.repoRelationships.length} relationships found`);
3834
4031
  } catch (err) {
3835
4032
  crossSpinner.stop("Cross-repo analysis failed (non-fatal)");
3836
- p7.log.warn(`Cross-repo error: ${err instanceof Error ? err.message : err}`);
4033
+ p8.log.warn(`Cross-repo error: ${err instanceof Error ? err.message : err}`);
3837
4034
  }
3838
4035
  }
3839
4036
  const outputDir = path10.resolve(options.output || "docs-site");
3840
4037
  if (!projectName) {
3841
4038
  projectName = results.length === 1 ? results[0].repoName : "My Project";
3842
4039
  }
3843
- const genSpinner = p7.spinner();
4040
+ const genSpinner = p8.spinner();
3844
4041
  try {
3845
4042
  genSpinner.start("Scaffolding documentation site...");
3846
4043
  await scaffoldSite(outputDir, projectName, templateDir);
3847
4044
  genSpinner.stop("Site scaffolded");
3848
4045
  } catch (err) {
3849
4046
  genSpinner.stop("Scaffold failed");
3850
- p7.log.error(`Scaffold error: ${err instanceof Error ? err.stack || err.message : err}`);
4047
+ p8.log.error(`Scaffold error: ${err instanceof Error ? err.stack || err.message : err}`);
3851
4048
  cleanup(clones);
3852
4049
  process.exit(1);
3853
4050
  }
@@ -3859,7 +4056,7 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3859
4056
  genSpinner.stop("Documentation content written");
3860
4057
  } catch (err) {
3861
4058
  genSpinner.stop("Content writing failed");
3862
- p7.log.error(`Content error: ${err instanceof Error ? err.stack || err.message : err}`);
4059
+ p8.log.error(`Content error: ${err instanceof Error ? err.stack || err.message : err}`);
3863
4060
  cleanup(clones);
3864
4061
  process.exit(1);
3865
4062
  }
@@ -3881,37 +4078,37 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3881
4078
  try {
3882
4079
  await runBuildCheck({ docsDir: outputDir, apiKey, model });
3883
4080
  } catch (err) {
3884
- p7.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
4081
+ p8.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
3885
4082
  }
3886
- p7.log.success("Documentation generated successfully!");
3887
- const shouldSetupMcp = await p7.confirm({
4083
+ p8.log.success("Documentation generated successfully!");
4084
+ const shouldSetupMcp = await p8.confirm({
3888
4085
  message: "Set up MCP server so Claude Code can query your docs?"
3889
4086
  });
3890
- if (!p7.isCancel(shouldSetupMcp) && shouldSetupMcp) {
4087
+ if (!p8.isCancel(shouldSetupMcp) && shouldSetupMcp) {
3891
4088
  await setupMcpConfig({ outputDir });
3892
4089
  }
3893
4090
  let devServer;
3894
4091
  const devPort = await findFreePort(3e3);
3895
4092
  try {
3896
4093
  devServer = startDevServer(outputDir, devPort);
3897
- p7.log.success(`Documentation site running at http://localhost:${devPort}`);
3898
- p7.log.info("Open the link above to preview your docs site.");
4094
+ p8.log.success(`Documentation site running at http://localhost:${devPort}`);
4095
+ p8.log.info("Open the link above to preview your docs site.");
3899
4096
  } catch {
3900
- p7.log.warn("Could not start preview server. You can run it manually:");
3901
- p7.log.info(` cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`);
4097
+ p8.log.warn("Could not start preview server. You can run it manually:");
4098
+ p8.log.info(` cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`);
3902
4099
  }
3903
- const shouldDeploy = await p7.confirm({
4100
+ const shouldDeploy = await p8.confirm({
3904
4101
  message: "Would you like to deploy your docs to GitHub?"
3905
4102
  });
3906
- if (p7.isCancel(shouldDeploy) || !shouldDeploy) {
4103
+ if (p8.isCancel(shouldDeploy) || !shouldDeploy) {
3907
4104
  if (devServer) {
3908
4105
  killDevServer(devServer);
3909
4106
  }
3910
- p7.note(
4107
+ p8.note(
3911
4108
  `cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`,
3912
4109
  "To start the dev server again"
3913
4110
  );
3914
- p7.outro("Done!");
4111
+ p8.outro("Done!");
3915
4112
  return;
3916
4113
  }
3917
4114
  if (devServer) {
@@ -3923,26 +4120,50 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3923
4120
  config
3924
4121
  });
3925
4122
  if (!deployResult) {
3926
- p7.note(
4123
+ p8.note(
3927
4124
  `cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`,
3928
4125
  "Next steps"
3929
4126
  );
3930
- p7.outro("Done!");
4127
+ p8.outro("Done!");
3931
4128
  return;
3932
4129
  }
3933
- const shouldSetupCi = await p7.confirm({
4130
+ 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
+ }
4148
+ }
4149
+ }
4150
+ const shouldSetupCi = await p8.confirm({
3934
4151
  message: "Would you like to set up CI to auto-update docs on every push?"
3935
4152
  });
3936
- if (p7.isCancel(shouldSetupCi) || !shouldSetupCi) {
3937
- showVercelInstructions(deployResult.owner, deployResult.repoName);
3938
- p7.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
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}`);
3939
4158
  return;
3940
4159
  }
3941
4160
  const gitRoot = getGitRoot();
3942
4161
  if (!gitRoot) {
3943
- p7.log.warn("Not in a git repository \u2014 skipping CI setup. Run `open-auto-doc setup-ci` from your project root later.");
3944
- showVercelInstructions(deployResult.owner, deployResult.repoName);
3945
- p7.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
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);
4165
+ }
4166
+ p8.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
3946
4167
  return;
3947
4168
  }
3948
4169
  const ciResult = await createCiWorkflow({
@@ -3952,8 +4173,10 @@ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
3952
4173
  token,
3953
4174
  config
3954
4175
  });
3955
- showVercelInstructions(deployResult.owner, deployResult.repoName);
3956
- p7.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
4176
+ if (!vercelDeployed) {
4177
+ showVercelInstructions(deployResult.owner, deployResult.repoName);
4178
+ }
4179
+ p8.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
3957
4180
  }
3958
4181
  function resolveTemplateDir() {
3959
4182
  const candidates = [
@@ -4013,26 +4236,26 @@ function killDevServer(child) {
4013
4236
  }
4014
4237
 
4015
4238
  // src/commands/generate.ts
4016
- import * as p8 from "@clack/prompts";
4239
+ import * as p9 from "@clack/prompts";
4017
4240
  import path11 from "path";
4018
4241
  async function generateCommand(options) {
4019
- p8.intro("open-auto-doc \u2014 Regenerating documentation");
4242
+ p9.intro("open-auto-doc \u2014 Regenerating documentation");
4020
4243
  const config = loadConfig();
4021
4244
  if (!config) {
4022
- p8.log.error("No .autodocrc.json found. Run `open-auto-doc init` first.");
4245
+ p9.log.error("No .autodocrc.json found. Run `open-auto-doc init` first.");
4023
4246
  process.exit(1);
4024
4247
  }
4025
4248
  const token = getGithubToken();
4026
4249
  const apiKey = getAnthropicKey();
4027
4250
  if (!token) {
4028
- p8.log.error("Not authenticated. Run `open-auto-doc login` or set GITHUB_TOKEN env var.");
4251
+ p9.log.error("Not authenticated. Run `open-auto-doc login` or set GITHUB_TOKEN env var.");
4029
4252
  process.exit(1);
4030
4253
  }
4031
4254
  if (!apiKey) {
4032
- p8.log.error("No Anthropic API key found. Run `open-auto-doc init` or set ANTHROPIC_API_KEY env var.");
4255
+ p9.log.error("No Anthropic API key found. Run `open-auto-doc init` or set ANTHROPIC_API_KEY env var.");
4033
4256
  process.exit(1);
4034
4257
  }
4035
- const model = await p8.select({
4258
+ const model = await p9.select({
4036
4259
  message: "Which model should analyze your repos?",
4037
4260
  options: [
4038
4261
  { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", hint: "Fast & capable (recommended)" },
@@ -4040,11 +4263,11 @@ async function generateCommand(options) {
4040
4263
  { value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "Most capable, slowest" }
4041
4264
  ]
4042
4265
  });
4043
- if (p8.isCancel(model)) {
4044
- p8.cancel("Cancelled.");
4266
+ if (p9.isCancel(model)) {
4267
+ p9.cancel("Cancelled.");
4045
4268
  process.exit(0);
4046
4269
  }
4047
- p8.log.info(`Using ${model}`);
4270
+ p9.log.info(`Using ${model}`);
4048
4271
  const incremental = options.incremental && !options.force;
4049
4272
  const cacheDir = path11.join(config.outputDir, ".autodoc-cache");
4050
4273
  const targetRepoName = options.repo;
@@ -4053,7 +4276,7 @@ async function generateCommand(options) {
4053
4276
  if (targetRepoName) {
4054
4277
  const targetRepo = config.repos.find((r) => r.name === targetRepoName);
4055
4278
  if (!targetRepo) {
4056
- p8.log.error(`Repo "${targetRepoName}" not found in config. Available: ${config.repos.map((r) => r.name).join(", ")}`);
4279
+ p9.log.error(`Repo "${targetRepoName}" not found in config. Available: ${config.repos.map((r) => r.name).join(", ")}`);
4057
4280
  process.exit(1);
4058
4281
  }
4059
4282
  reposToAnalyze = [targetRepo];
@@ -4062,13 +4285,13 @@ async function generateCommand(options) {
4062
4285
  const cached = loadCache(cacheDir, repo.name);
4063
4286
  if (cached) {
4064
4287
  cachedResults.push(cached.result);
4065
- p8.log.info(`Using cached analysis for ${repo.name}`);
4288
+ p9.log.info(`Using cached analysis for ${repo.name}`);
4066
4289
  } else {
4067
- p8.log.warn(`No cached analysis for ${repo.name} \u2014 its docs will be stale until it pushes`);
4290
+ p9.log.warn(`No cached analysis for ${repo.name} \u2014 its docs will be stale until it pushes`);
4068
4291
  }
4069
4292
  }
4070
4293
  }
4071
- const cloneSpinner = p8.spinner();
4294
+ const cloneSpinner = p9.spinner();
4072
4295
  cloneSpinner.start(`Cloning ${reposToAnalyze.length} ${reposToAnalyze.length === 1 ? "repository" : "repositories"}...`);
4073
4296
  const clones = [];
4074
4297
  for (const repo of reposToAnalyze) {
@@ -4087,12 +4310,12 @@ async function generateCommand(options) {
4087
4310
  );
4088
4311
  clones.push(cloned);
4089
4312
  } catch (err) {
4090
- p8.log.warn(`Failed to clone ${repo.name}: ${err instanceof Error ? err.message : err}`);
4313
+ p9.log.warn(`Failed to clone ${repo.name}: ${err instanceof Error ? err.message : err}`);
4091
4314
  }
4092
4315
  }
4093
4316
  cloneSpinner.stop(`Cloned ${clones.length}/${reposToAnalyze.length} ${reposToAnalyze.length === 1 ? "repository" : "repositories"}`);
4094
4317
  if (clones.length === 0) {
4095
- p8.log.error("No repositories were cloned.");
4318
+ p9.log.error("No repositories were cloned.");
4096
4319
  process.exit(1);
4097
4320
  }
4098
4321
  const total = clones.length;
@@ -4167,7 +4390,7 @@ async function generateCommand(options) {
4167
4390
  } catch (err) {
4168
4391
  const errMsg = err instanceof Error ? err.message : String(err);
4169
4392
  progressTable.update(repoName, { status: "failed", error: errMsg });
4170
- p8.log.warn(`[${repoName}] Analysis failed: ${errMsg}`);
4393
+ p9.log.warn(`[${repoName}] Analysis failed: ${errMsg}`);
4171
4394
  return { repo: repoName, result: null };
4172
4395
  }
4173
4396
  });
@@ -4175,23 +4398,23 @@ async function generateCommand(options) {
4175
4398
  progressTable.stop();
4176
4399
  const freshResults = settled.filter((s) => s.result !== null).map((s) => s.result);
4177
4400
  const { done: analyzedCount, failed: failedCount } = progressTable.getSummary();
4178
- p8.log.step(
4401
+ p9.log.step(
4179
4402
  `Analyzed ${analyzedCount}/${total} ${total === 1 ? "repository" : "repositories"}` + (failedCount > 0 ? ` (${failedCount} failed)` : "")
4180
4403
  );
4181
4404
  const results = [...freshResults, ...cachedResults];
4182
4405
  if (results.length > 0) {
4183
4406
  let crossRepo;
4184
4407
  if (results.length > 1) {
4185
- const crossSpinner = p8.spinner();
4408
+ const crossSpinner = p9.spinner();
4186
4409
  crossSpinner.start("Analyzing cross-repository relationships...");
4187
4410
  try {
4188
- crossRepo = await analyzeCrossRepos(results, apiKey, model, (text4) => {
4189
- crossSpinner.message(text4.slice(0, 80));
4411
+ crossRepo = await analyzeCrossRepos(results, apiKey, model, (text5) => {
4412
+ crossSpinner.message(text5.slice(0, 80));
4190
4413
  });
4191
4414
  crossSpinner.stop(`Cross-repo analysis complete \u2014 ${crossRepo.repoRelationships.length} relationships found`);
4192
4415
  } catch (err) {
4193
4416
  crossSpinner.stop("Cross-repo analysis failed (non-fatal)");
4194
- p8.log.warn(`Cross-repo error: ${err instanceof Error ? err.message : err}`);
4417
+ p9.log.warn(`Cross-repo error: ${err instanceof Error ? err.message : err}`);
4195
4418
  }
4196
4419
  }
4197
4420
  const contentDir = path11.join(config.outputDir, "content", "docs");
@@ -4200,25 +4423,25 @@ async function generateCommand(options) {
4200
4423
  try {
4201
4424
  await runBuildCheck({ docsDir: config.outputDir, apiKey, model });
4202
4425
  } catch (err) {
4203
- p8.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
4426
+ p9.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
4204
4427
  }
4205
- p8.log.success("Documentation regenerated!");
4428
+ p9.log.success("Documentation regenerated!");
4206
4429
  }
4207
4430
  for (const clone of clones) {
4208
4431
  cleanupClone(clone);
4209
4432
  }
4210
- p8.outro("Done!");
4433
+ p9.outro("Done!");
4211
4434
  }
4212
4435
 
4213
4436
  // src/commands/deploy.ts
4214
- import * as p9 from "@clack/prompts";
4437
+ import * as p10 from "@clack/prompts";
4215
4438
  import fs11 from "fs";
4216
4439
  import path12 from "path";
4217
4440
  function resolveDocsDir(config, dirOption) {
4218
4441
  if (dirOption) {
4219
4442
  const resolved = path12.resolve(dirOption);
4220
4443
  if (!fs11.existsSync(resolved)) {
4221
- p9.log.error(`Directory not found: ${resolved}`);
4444
+ p10.log.error(`Directory not found: ${resolved}`);
4222
4445
  process.exit(1);
4223
4446
  }
4224
4447
  return resolved;
@@ -4229,28 +4452,28 @@ function resolveDocsDir(config, dirOption) {
4229
4452
  if (fs11.existsSync(path12.resolve("docs-site"))) {
4230
4453
  return path12.resolve("docs-site");
4231
4454
  }
4232
- p9.log.error(
4455
+ p10.log.error(
4233
4456
  "Could not find docs site directory. Use --dir to specify the path, or run `open-auto-doc init` first."
4234
4457
  );
4235
4458
  process.exit(1);
4236
4459
  }
4237
4460
  async function deployCommand(options) {
4238
- p9.intro("open-auto-doc \u2014 Deploy docs to GitHub");
4461
+ p10.intro("open-auto-doc \u2014 Deploy docs to GitHub");
4239
4462
  const token = getGithubToken();
4240
4463
  if (!token) {
4241
- p9.log.error("Not authenticated. Run `open-auto-doc login` first.");
4464
+ p10.log.error("Not authenticated. Run `open-auto-doc login` first.");
4242
4465
  process.exit(1);
4243
4466
  }
4244
4467
  const config = loadConfig();
4245
4468
  const docsDir = resolveDocsDir(config, options.dir);
4246
- p9.log.info(`Docs directory: ${docsDir}`);
4469
+ p10.log.info(`Docs directory: ${docsDir}`);
4247
4470
  if (config?.docsRepo) {
4248
- p9.log.info(`Docs repo already configured: ${config.docsRepo}`);
4471
+ p10.log.info(`Docs repo already configured: ${config.docsRepo}`);
4249
4472
  const pushed = await pushUpdates({ token, docsDir, docsRepo: config.docsRepo });
4250
4473
  if (pushed) {
4251
- p9.outro("Docs updated! Vercel will auto-deploy from the push.");
4474
+ p10.outro("Docs updated! Vercel will auto-deploy from the push.");
4252
4475
  } else {
4253
- p9.outro("Docs are up to date!");
4476
+ p10.outro("Docs are up to date!");
4254
4477
  }
4255
4478
  return;
4256
4479
  }
@@ -4260,20 +4483,40 @@ async function deployCommand(options) {
4260
4483
  config: config || { repos: [], outputDir: docsDir }
4261
4484
  });
4262
4485
  if (!result) {
4263
- p9.cancel("Deploy cancelled.");
4486
+ p10.cancel("Deploy cancelled.");
4264
4487
  process.exit(0);
4265
4488
  }
4489
+ const shouldDeployVercel = await p10.confirm({
4490
+ message: "Would you like to deploy to Vercel? (auto-deploys on every push)"
4491
+ });
4492
+ if (!p10.isCancel(shouldDeployVercel) && shouldDeployVercel) {
4493
+ const vercelToken = await authenticateVercel();
4494
+ if (vercelToken) {
4495
+ const vercelResult = await deployToVercel({
4496
+ token: vercelToken,
4497
+ githubOwner: result.owner,
4498
+ githubRepo: result.repoName,
4499
+ docsDir,
4500
+ config: config || { repos: [], outputDir: docsDir }
4501
+ });
4502
+ if (vercelResult) {
4503
+ p10.log.success(`Live at: ${vercelResult.deploymentUrl}`);
4504
+ p10.outro(`Docs repo: https://github.com/${result.owner}/${result.repoName}`);
4505
+ return;
4506
+ }
4507
+ }
4508
+ }
4266
4509
  showVercelInstructions(result.owner, result.repoName);
4267
- p9.outro(`Docs repo: https://github.com/${result.owner}/${result.repoName}`);
4510
+ p10.outro(`Docs repo: https://github.com/${result.owner}/${result.repoName}`);
4268
4511
  }
4269
4512
 
4270
4513
  // src/commands/setup-ci.ts
4271
- import * as p10 from "@clack/prompts";
4514
+ import * as p11 from "@clack/prompts";
4272
4515
  async function setupCiCommand() {
4273
- p10.intro("open-auto-doc \u2014 CI/CD Setup");
4516
+ p11.intro("open-auto-doc \u2014 CI/CD Setup");
4274
4517
  const config = loadConfig();
4275
4518
  if (!config?.docsRepo) {
4276
- p10.log.error(
4519
+ p11.log.error(
4277
4520
  "No docs repo configured. Run `open-auto-doc deploy` first to create a docs GitHub repo."
4278
4521
  );
4279
4522
  process.exit(1);
@@ -4281,12 +4524,12 @@ async function setupCiCommand() {
4281
4524
  const token = getGithubToken();
4282
4525
  const isMultiRepo = config.repos.length > 1;
4283
4526
  if (isMultiRepo && !token) {
4284
- p10.log.error("Not authenticated. Run `open-auto-doc login` first (needed to push workflows to source repos).");
4527
+ p11.log.error("Not authenticated. Run `open-auto-doc login` first (needed to push workflows to source repos).");
4285
4528
  process.exit(1);
4286
4529
  }
4287
4530
  const gitRoot = getGitRoot();
4288
4531
  if (!isMultiRepo && !gitRoot) {
4289
- p10.log.error("Not in a git repository. Run this command from your project root.");
4532
+ p11.log.error("Not in a git repository. Run this command from your project root.");
4290
4533
  process.exit(1);
4291
4534
  }
4292
4535
  const result = await createCiWorkflow({
@@ -4297,46 +4540,46 @@ async function setupCiCommand() {
4297
4540
  config
4298
4541
  });
4299
4542
  if (!result) {
4300
- p10.cancel("Setup cancelled.");
4543
+ p11.cancel("Setup cancelled.");
4301
4544
  process.exit(0);
4302
4545
  }
4303
4546
  if ("repos" in result) {
4304
- p10.outro("Per-repo CI workflows created! Add the required secrets to each source repo.");
4547
+ p11.outro("Per-repo CI workflows created! Add the required secrets to each source repo.");
4305
4548
  } else {
4306
- p10.outro("CI/CD workflow is ready! Commit and push to activate.");
4549
+ p11.outro("CI/CD workflow is ready! Commit and push to activate.");
4307
4550
  }
4308
4551
  }
4309
4552
 
4310
4553
  // src/commands/login.ts
4311
- import * as p11 from "@clack/prompts";
4554
+ import * as p12 from "@clack/prompts";
4312
4555
  async function loginCommand() {
4313
- p11.intro("open-auto-doc \u2014 GitHub Login");
4556
+ p12.intro("open-auto-doc \u2014 GitHub Login");
4314
4557
  const existing = getGithubToken();
4315
4558
  if (existing) {
4316
- const overwrite = await p11.confirm({
4559
+ const overwrite = await p12.confirm({
4317
4560
  message: "You're already logged in. Re-authenticate?"
4318
4561
  });
4319
- if (!overwrite || p11.isCancel(overwrite)) {
4320
- p11.cancel("Keeping existing credentials");
4562
+ if (!overwrite || p12.isCancel(overwrite)) {
4563
+ p12.cancel("Keeping existing credentials");
4321
4564
  return;
4322
4565
  }
4323
4566
  }
4324
4567
  const token = await authenticateWithGithub();
4325
4568
  setGithubToken(token);
4326
- p11.outro("Logged in successfully!");
4569
+ p12.outro("Logged in successfully!");
4327
4570
  }
4328
4571
 
4329
4572
  // src/commands/logout.ts
4330
- import * as p12 from "@clack/prompts";
4573
+ import * as p13 from "@clack/prompts";
4331
4574
  async function logoutCommand() {
4332
- p12.intro("open-auto-doc \u2014 Logout");
4575
+ p13.intro("open-auto-doc \u2014 Logout");
4333
4576
  clearAll();
4334
- p12.outro("Credentials cleared. You've been logged out.");
4577
+ p13.outro("Credentials cleared. You've been logged out.");
4335
4578
  }
4336
4579
 
4337
4580
  // src/index.ts
4338
4581
  var program = new Command();
4339
- program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.5.1");
4582
+ program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.5.2");
4340
4583
  program.command("init", { isDefault: true }).description("Initialize and generate documentation for your repositories").option("-o, --output <dir>", "Output directory", "docs-site").action(initCommand);
4341
4584
  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);
4342
4585
  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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latent-space-labs/open-auto-doc",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Auto-generate beautiful documentation websites from GitHub repositories using AI",
5
5
  "type": "module",
6
6
  "bin": {