@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.
- package/dist/index.js +434 -191
- 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
|
|
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
|
|
43
|
-
|
|
42
|
+
const spinner9 = p.spinner();
|
|
43
|
+
spinner9.start("Waiting for GitHub authorization...");
|
|
44
44
|
const token = await pollForToken(deviceData.device_code, deviceData.interval);
|
|
45
|
-
|
|
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
|
|
168
|
-
|
|
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
|
-
|
|
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
|
|
296
|
-
|
|
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
|
-
|
|
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
|
-
|
|
325
|
+
spinner9.stop(`Created ${data.full_name}`);
|
|
318
326
|
}
|
|
319
327
|
} catch (err) {
|
|
320
|
-
|
|
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
|
-
|
|
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
|
-
|
|
355
|
+
spinner9.stop("Pushed to GitHub.");
|
|
348
356
|
} catch (err) {
|
|
349
|
-
|
|
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
|
|
361
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
+
spinner9.stop("Pushed updates to docs repo.");
|
|
377
385
|
return true;
|
|
378
386
|
} catch (err) {
|
|
379
|
-
|
|
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/
|
|
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((
|
|
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(
|
|
786
|
-
if (
|
|
787
|
-
const truncated =
|
|
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((
|
|
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
|
|
2426
|
-
|
|
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
|
-
|
|
2627
|
+
spinner9.stop("Documentation build verified");
|
|
2431
2628
|
return { success: true, attempts: attempt };
|
|
2432
2629
|
}
|
|
2433
|
-
|
|
2630
|
+
spinner9.stop("Build errors detected");
|
|
2434
2631
|
const errorSummary = extractErrorSummary(output);
|
|
2435
|
-
|
|
2632
|
+
p5.log.warn(`Build error output:
|
|
2436
2633
|
${errorSummary}`);
|
|
2437
2634
|
if (attempt >= maxAttempts) {
|
|
2438
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
2647
|
+
(text5) => spinner9.message(text5)
|
|
2451
2648
|
);
|
|
2452
2649
|
if (!result.fixed) {
|
|
2453
|
-
|
|
2454
|
-
|
|
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
|
-
|
|
2458
|
-
|
|
2654
|
+
spinner9.stop(`Fixed ${result.filesChanged.length} file(s): ${result.summary}`);
|
|
2655
|
+
spinner9.start("Re-verifying build...");
|
|
2459
2656
|
} catch (err) {
|
|
2460
|
-
|
|
2461
|
-
|
|
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
|
|
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
|
-
|
|
2613
|
-
|
|
2614
|
-
const branch = await
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
2655
|
-
|
|
2656
|
-
const branch = await
|
|
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 (
|
|
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
|
|
2666
|
-
|
|
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
|
-
|
|
2889
|
+
spinner9.stop(`Created workflow in ${repo.fullName}`);
|
|
2693
2890
|
} catch (err) {
|
|
2694
|
-
|
|
2695
|
-
|
|
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
|
-
|
|
2896
|
+
p6.log.error("Failed to create workflows in any repository.");
|
|
2700
2897
|
return null;
|
|
2701
2898
|
}
|
|
2702
|
-
|
|
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
|
|
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 (
|
|
2917
|
+
if (p6.isCancel(shouldVerify) || !shouldVerify) {
|
|
2721
2918
|
showSecretsInstructions(repos.length > 1);
|
|
2722
2919
|
return;
|
|
2723
2920
|
}
|
|
2724
|
-
|
|
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
|
-
|
|
2742
|
-
|
|
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
|
|
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
|
-
|
|
2953
|
+
p6.log.success(`${secret} \u2014 found`);
|
|
2757
2954
|
} else {
|
|
2758
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
2980
|
+
p6.note(lines.join("\n"), "All secrets verified!");
|
|
2784
2981
|
} else {
|
|
2785
|
-
|
|
2786
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3029
|
+
p7.intro("open-auto-doc \u2014 MCP Server Setup");
|
|
2833
3030
|
const config = loadConfig();
|
|
2834
3031
|
if (!config) {
|
|
2835
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2855
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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", (
|
|
3059
|
-
if (typeof
|
|
3060
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3905
|
+
p8.log.info("Let's connect your GitHub account.");
|
|
3709
3906
|
token = await authenticateWithGithub();
|
|
3710
3907
|
setGithubToken(token);
|
|
3711
3908
|
} else {
|
|
3712
|
-
|
|
3909
|
+
p8.log.success("Using saved GitHub credentials.");
|
|
3713
3910
|
}
|
|
3714
3911
|
const repos = await pickRepos(token);
|
|
3715
|
-
|
|
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
|
|
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 (
|
|
3726
|
-
|
|
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
|
|
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 (
|
|
3741
|
-
|
|
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
|
|
3942
|
+
const saveKey = await p8.confirm({
|
|
3746
3943
|
message: "Save API key for future use?"
|
|
3747
3944
|
});
|
|
3748
|
-
if (saveKey && !
|
|
3945
|
+
if (saveKey && !p8.isCancel(saveKey)) {
|
|
3749
3946
|
setAnthropicKey(apiKey);
|
|
3750
3947
|
}
|
|
3751
3948
|
} else {
|
|
3752
|
-
|
|
3949
|
+
p8.log.success("Using saved Anthropic API key.");
|
|
3753
3950
|
}
|
|
3754
|
-
const model = await
|
|
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 (
|
|
3763
|
-
|
|
3959
|
+
if (p8.isCancel(model)) {
|
|
3960
|
+
p8.cancel("Operation cancelled");
|
|
3764
3961
|
process.exit(0);
|
|
3765
3962
|
}
|
|
3766
|
-
|
|
3767
|
-
const cloneSpinner =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
4024
|
+
const crossSpinner = p8.spinner();
|
|
3828
4025
|
crossSpinner.start("Analyzing cross-repository relationships...");
|
|
3829
4026
|
try {
|
|
3830
|
-
crossRepo = await analyzeCrossRepos(results, apiKey, model, (
|
|
3831
|
-
crossSpinner.message(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4081
|
+
p8.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
|
|
3885
4082
|
}
|
|
3886
|
-
|
|
3887
|
-
const shouldSetupMcp = await
|
|
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 (!
|
|
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
|
-
|
|
3898
|
-
|
|
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
|
-
|
|
3901
|
-
|
|
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
|
|
4100
|
+
const shouldDeploy = await p8.confirm({
|
|
3904
4101
|
message: "Would you like to deploy your docs to GitHub?"
|
|
3905
4102
|
});
|
|
3906
|
-
if (
|
|
4103
|
+
if (p8.isCancel(shouldDeploy) || !shouldDeploy) {
|
|
3907
4104
|
if (devServer) {
|
|
3908
4105
|
killDevServer(devServer);
|
|
3909
4106
|
}
|
|
3910
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4123
|
+
p8.note(
|
|
3927
4124
|
`cd ${path10.relative(process.cwd(), outputDir)} && npm run dev`,
|
|
3928
4125
|
"Next steps"
|
|
3929
4126
|
);
|
|
3930
|
-
|
|
4127
|
+
p8.outro("Done!");
|
|
3931
4128
|
return;
|
|
3932
4129
|
}
|
|
3933
|
-
|
|
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 (
|
|
3937
|
-
|
|
3938
|
-
|
|
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
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
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
|
-
|
|
3956
|
-
|
|
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
|
|
4239
|
+
import * as p9 from "@clack/prompts";
|
|
4017
4240
|
import path11 from "path";
|
|
4018
4241
|
async function generateCommand(options) {
|
|
4019
|
-
|
|
4242
|
+
p9.intro("open-auto-doc \u2014 Regenerating documentation");
|
|
4020
4243
|
const config = loadConfig();
|
|
4021
4244
|
if (!config) {
|
|
4022
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
4044
|
-
|
|
4266
|
+
if (p9.isCancel(model)) {
|
|
4267
|
+
p9.cancel("Cancelled.");
|
|
4045
4268
|
process.exit(0);
|
|
4046
4269
|
}
|
|
4047
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4288
|
+
p9.log.info(`Using cached analysis for ${repo.name}`);
|
|
4066
4289
|
} else {
|
|
4067
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
4408
|
+
const crossSpinner = p9.spinner();
|
|
4186
4409
|
crossSpinner.start("Analyzing cross-repository relationships...");
|
|
4187
4410
|
try {
|
|
4188
|
-
crossRepo = await analyzeCrossRepos(results, apiKey, model, (
|
|
4189
|
-
crossSpinner.message(
|
|
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
|
-
|
|
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
|
-
|
|
4426
|
+
p9.log.warn(`Build check skipped: ${err instanceof Error ? err.message : err}`);
|
|
4204
4427
|
}
|
|
4205
|
-
|
|
4428
|
+
p9.log.success("Documentation regenerated!");
|
|
4206
4429
|
}
|
|
4207
4430
|
for (const clone of clones) {
|
|
4208
4431
|
cleanupClone(clone);
|
|
4209
4432
|
}
|
|
4210
|
-
|
|
4433
|
+
p9.outro("Done!");
|
|
4211
4434
|
}
|
|
4212
4435
|
|
|
4213
4436
|
// src/commands/deploy.ts
|
|
4214
|
-
import * as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4461
|
+
p10.intro("open-auto-doc \u2014 Deploy docs to GitHub");
|
|
4239
4462
|
const token = getGithubToken();
|
|
4240
4463
|
if (!token) {
|
|
4241
|
-
|
|
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
|
-
|
|
4469
|
+
p10.log.info(`Docs directory: ${docsDir}`);
|
|
4247
4470
|
if (config?.docsRepo) {
|
|
4248
|
-
|
|
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
|
-
|
|
4474
|
+
p10.outro("Docs updated! Vercel will auto-deploy from the push.");
|
|
4252
4475
|
} else {
|
|
4253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4514
|
+
import * as p11 from "@clack/prompts";
|
|
4272
4515
|
async function setupCiCommand() {
|
|
4273
|
-
|
|
4516
|
+
p11.intro("open-auto-doc \u2014 CI/CD Setup");
|
|
4274
4517
|
const config = loadConfig();
|
|
4275
4518
|
if (!config?.docsRepo) {
|
|
4276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4543
|
+
p11.cancel("Setup cancelled.");
|
|
4301
4544
|
process.exit(0);
|
|
4302
4545
|
}
|
|
4303
4546
|
if ("repos" in result) {
|
|
4304
|
-
|
|
4547
|
+
p11.outro("Per-repo CI workflows created! Add the required secrets to each source repo.");
|
|
4305
4548
|
} else {
|
|
4306
|
-
|
|
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
|
|
4554
|
+
import * as p12 from "@clack/prompts";
|
|
4312
4555
|
async function loginCommand() {
|
|
4313
|
-
|
|
4556
|
+
p12.intro("open-auto-doc \u2014 GitHub Login");
|
|
4314
4557
|
const existing = getGithubToken();
|
|
4315
4558
|
if (existing) {
|
|
4316
|
-
const overwrite = await
|
|
4559
|
+
const overwrite = await p12.confirm({
|
|
4317
4560
|
message: "You're already logged in. Re-authenticate?"
|
|
4318
4561
|
});
|
|
4319
|
-
if (!overwrite ||
|
|
4320
|
-
|
|
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
|
-
|
|
4569
|
+
p12.outro("Logged in successfully!");
|
|
4327
4570
|
}
|
|
4328
4571
|
|
|
4329
4572
|
// src/commands/logout.ts
|
|
4330
|
-
import * as
|
|
4573
|
+
import * as p13 from "@clack/prompts";
|
|
4331
4574
|
async function logoutCommand() {
|
|
4332
|
-
|
|
4575
|
+
p13.intro("open-auto-doc \u2014 Logout");
|
|
4333
4576
|
clearAll();
|
|
4334
|
-
|
|
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.
|
|
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);
|