@chappibunny/repolens 1.9.12 → 1.10.0
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/CHANGELOG.md +31 -2
- package/README.md +2 -2
- package/docs/ROADMAP.md +1 -1
- package/docs/STABILITY.md +3 -3
- package/package.json +1 -1
- package/src/ai/generate-sections.js +2 -2
- package/src/ai/provider.js +13 -2
- package/src/cli.js +9 -9
- package/src/init.js +437 -33
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.10.0
|
|
6
|
+
|
|
7
|
+
### ✨ Interactive Init is Now Default
|
|
8
|
+
|
|
9
|
+
- **`repolens init` is now fully interactive**: No more `--interactive` flag needed — the wizard runs by default
|
|
10
|
+
- **Use `--quick` to skip the wizard**: For CI or minimal scaffolding, use `repolens init --quick`
|
|
11
|
+
- **Comprehensive wizard**: Configures ALL publishers (Notion, Confluence, GitHub Wiki), not just Notion
|
|
12
|
+
- Collects credentials for each selected publisher
|
|
13
|
+
- Guides AI provider selection with environment validation
|
|
14
|
+
- Discord webhook configuration
|
|
15
|
+
- Branch filtering for Notion/Confluence publishing
|
|
16
|
+
- Shows clear summary of GitHub Actions secrets needed
|
|
17
|
+
- **Credentials written to `.env`**: All collected credentials are saved locally (gitignored)
|
|
18
|
+
- **Better validation**: Shows specific status for each credential (✓ set vs ○ needed)
|
|
19
|
+
|
|
20
|
+
### 🤖 AI Setup from CLI
|
|
21
|
+
|
|
22
|
+
- **Browser auto-open**: Wizard offers to open API key signup pages (OpenAI, Anthropic, Google) in your browser
|
|
23
|
+
- **API key validation**: Tests your AI key immediately after you paste it — confirms it works before saving
|
|
24
|
+
- **GitHub Token testing**: Validates existing `GITHUB_TOKEN` works with GitHub Models
|
|
25
|
+
- **Clear feedback**: Shows ✓ valid / ⚠ invalid with specific error messages
|
|
26
|
+
- **Writes all config**: `.env` now includes `REPOLENS_AI_ENABLED=true` and `REPOLENS_AI_PROVIDER=...`
|
|
27
|
+
|
|
28
|
+
### 🔧 AI Diagnostics
|
|
29
|
+
|
|
30
|
+
- **Better error messages**: AI failures now show the actual reason (e.g., "Missing API key (expected REPOLENS_AI_API_KEY)")
|
|
31
|
+
- **Config logging**: First AI call logs the provider, model, and key prefix being used
|
|
32
|
+
- **Workflow template updated**: Default workflow now shows both GitHub Models (free) AND OpenAI options with clear comments
|
|
33
|
+
|
|
5
34
|
## 1.9.12
|
|
6
35
|
|
|
7
36
|
### 🐛 Bug Fixes
|
|
@@ -149,7 +178,7 @@ Users without AI API keys now get production-quality documentation instead of sp
|
|
|
149
178
|
- **Zero-config AI in CI**: When `ai.provider: github` is set in `.repolens.yml`, RepoLens uses the default `GITHUB_TOKEN` injected by GitHub Actions. No secrets to create or manage.
|
|
150
179
|
- **Config-driven AI settings**: `ai.enabled`, `ai.provider`, `ai.model`, `ai.temperature`, and `ai.base_url` in `.repolens.yml` are now fully respected at runtime (env vars still take precedence). Previously these config values were ignored.
|
|
151
180
|
- **Init wizard fixes**: Provider selection now uses correct runtime values (`github`, `openai_compatible`, `anthropic`, `google`) instead of mismatched labels. The wizard now emits `ai.provider` to the generated YAML. Added `github_wiki` to publisher choices.
|
|
152
|
-
- **Demo AI upsell**: `repolens demo` now shows a hint about GitHub Models (free) when AI is not enabled, guiding users to `repolens init
|
|
181
|
+
- **Demo AI upsell**: `repolens demo` now shows a hint about GitHub Models (free) when AI is not enabled, guiding users to `repolens init`.
|
|
153
182
|
- **Uninstall command**: `repolens uninstall` removes all RepoLens-generated files (`.repolens/`, `.repolens.yml`, workflow, `.env.example`, `README.repolens.md`) with confirmation prompt and `--force` flag.
|
|
154
183
|
- **Doctor validation**: `repolens doctor` now checks for `GITHUB_TOKEN` when provider is `github`, and `REPOLENS_AI_API_KEY` for other providers.
|
|
155
184
|
|
|
@@ -449,7 +478,7 @@ RepoLens v1.0.0 marks the first stable release with a frozen public API. All CLI
|
|
|
449
478
|
## 0.7.0
|
|
450
479
|
|
|
451
480
|
### ✨ New Features
|
|
452
|
-
- **Interactive Init Wizard**: `repolens init
|
|
481
|
+
- **Interactive Init Wizard**: `repolens init` — step-by-step configuration wizard with scan presets (Next.js, Express, generic), publisher selection, AI provider setup, and branch filtering (now default behavior; use `--quick` to skip)
|
|
453
482
|
- **Watch Mode**: `repolens watch` — watches source directories for changes and regenerates Markdown docs with 500ms debounce (no API calls)
|
|
454
483
|
- **Enhanced Error Messages**: Centralized error catalog with actionable guidance — every error now shows what went wrong, why, and how to fix it
|
|
455
484
|
- **Performance Monitoring**: Scan, render, and publish timing summary printed after every `publish` run
|
package/README.md
CHANGED
|
@@ -165,8 +165,8 @@ Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki,
|
|
|
165
165
|
|
|
166
166
|
| Command | Description |
|
|
167
167
|
|---|---|
|
|
168
|
-
| `npx @chappibunny/repolens init` |
|
|
169
|
-
| `npx @chappibunny/repolens init --
|
|
168
|
+
| `npx @chappibunny/repolens init` | **Interactive wizard** — configure publishers, AI, credentials |
|
|
169
|
+
| `npx @chappibunny/repolens init --quick` | Minimal setup, skip wizard |
|
|
170
170
|
| `npx @chappibunny/repolens publish` | Scan, generate, and publish documentation |
|
|
171
171
|
| `npx @chappibunny/repolens demo` | Quick local preview — no API keys needed |
|
|
172
172
|
| `npx @chappibunny/repolens doctor` | Validate your setup |
|
package/docs/ROADMAP.md
CHANGED
|
@@ -56,7 +56,7 @@ Everything below is live, tested, and available on npm.
|
|
|
56
56
|
- ✅ User feedback via `repolens feedback` command
|
|
57
57
|
|
|
58
58
|
### Polish & Reliability (v0.7.0)
|
|
59
|
-
- ✅ Interactive configuration wizard (`repolens init
|
|
59
|
+
- ✅ Interactive configuration wizard (now default for `repolens init`)
|
|
60
60
|
- ✅ Watch mode for local development (`repolens watch`)
|
|
61
61
|
- ✅ Enhanced error messages with actionable guidance (centralized error catalog)
|
|
62
62
|
- ✅ Performance monitoring (scan/render/publish timing summary)
|
package/docs/STABILITY.md
CHANGED
|
@@ -16,8 +16,8 @@ RepoLens follows semantic versioning (semver):
|
|
|
16
16
|
|
|
17
17
|
| Command | Status | Description |
|
|
18
18
|
|---------|--------|-------------|
|
|
19
|
-
| `init` | Stable |
|
|
20
|
-
| `init --
|
|
19
|
+
| `init` | Stable | **Interactive wizard** — configure publishers, AI, credentials |
|
|
20
|
+
| `init --quick` | Stable | Minimal scaffolding, skip wizard |
|
|
21
21
|
| `doctor` | Stable | Validate repository setup |
|
|
22
22
|
| `publish` | Stable | Scan, generate, and publish documentation |
|
|
23
23
|
| `demo` | Stable | Generate local docs without API keys (quick preview) |
|
|
@@ -33,7 +33,7 @@ RepoLens follows semantic versioning (semver):
|
|
|
33
33
|
|--------|-------|--------|-------------|
|
|
34
34
|
| `--config <path>` | — | Stable | Path to `.repolens.yml` |
|
|
35
35
|
| `--target <path>` | — | Stable | Target repository path (init, doctor, migrate) |
|
|
36
|
-
| `--
|
|
36
|
+
| `--quick` | — | Stable | Skip interactive wizard for init |
|
|
37
37
|
| `--dry-run` | — | Stable | Preview changes without applying (migrate) |
|
|
38
38
|
| `--force` | — | Stable | Skip confirmation prompts (migrate) |
|
|
39
39
|
| `--verbose` | — | Stable | Enable verbose logging |
|
package/package.json
CHANGED
|
@@ -63,7 +63,7 @@ async function generateWithStructuredFallback(key, promptText, maxTokens, fallba
|
|
|
63
63
|
if (md) return sanitizeAIOutput(md);
|
|
64
64
|
}
|
|
65
65
|
// If structured mode failed, fall through to plain-text
|
|
66
|
-
warn(`Structured AI failed for ${key}
|
|
66
|
+
warn(`Structured AI failed for ${key}: ${result.error || "invalid/empty response"}`);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Plain-text AI fallback
|
|
@@ -76,7 +76,7 @@ async function generateWithStructuredFallback(key, promptText, maxTokens, fallba
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
if (!result.success) {
|
|
79
|
-
warn(
|
|
79
|
+
warn(`AI generation failed: ${result.error || "unknown error"}`);
|
|
80
80
|
return fallbackFn();
|
|
81
81
|
}
|
|
82
82
|
|
package/src/ai/provider.js
CHANGED
|
@@ -33,6 +33,9 @@ const AI_PRESETS = {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
// Module-level flag to log config only once
|
|
37
|
+
let hasLoggedConfig = false;
|
|
38
|
+
|
|
36
39
|
export async function generateText({ system, user, temperature, maxTokens, config, jsonMode, jsonSchema }) {
|
|
37
40
|
// Check if AI is enabled (env var takes precedence, then config)
|
|
38
41
|
const aiConfig = config?.ai || {};
|
|
@@ -69,14 +72,22 @@ export async function generateText({ system, user, temperature, maxTokens, confi
|
|
|
69
72
|
|
|
70
73
|
// Validate configuration
|
|
71
74
|
if (!apiKey) {
|
|
72
|
-
|
|
75
|
+
const keySource = provider === "github" ? "GITHUB_TOKEN or REPOLENS_AI_API_KEY" : "REPOLENS_AI_API_KEY";
|
|
76
|
+
warn(`AI: No API key found. Expected ${keySource} in environment.`);
|
|
73
77
|
return {
|
|
74
78
|
success: false,
|
|
75
|
-
error:
|
|
79
|
+
error: `Missing API key (expected ${keySource})`,
|
|
76
80
|
fallback: true
|
|
77
81
|
};
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
// Log configuration once per run
|
|
85
|
+
if (!hasLoggedConfig) {
|
|
86
|
+
const keyPreview = apiKey.substring(0, 8) + "...";
|
|
87
|
+
info(`AI Config: provider=${provider}, model=${model}, key=${keyPreview}`);
|
|
88
|
+
hasLoggedConfig = true;
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
if (!baseUrl && provider === "openai_compatible") {
|
|
81
92
|
warn("REPOLENS_AI_BASE_URL not set. Using OpenAI default.");
|
|
82
93
|
}
|
package/src/cli.js
CHANGED
|
@@ -134,10 +134,8 @@ function showPostGenerationAINotice() {
|
|
|
134
134
|
info(`${fmt.cyan("│")} ${fmt.yellow("•")} Developer Onboarding — getting started guide for new hires ${fmt.cyan("│")}`);
|
|
135
135
|
info(`${fmt.cyan("│")} ${fmt.cyan("│")}`);
|
|
136
136
|
info(`${fmt.cyan("│")} ${fmt.boldGreen("🆓 Enable for FREE with GitHub Models:")} ${fmt.cyan("│")}`);
|
|
137
|
-
info(`${fmt.cyan("│")} ${fmt.
|
|
138
|
-
info(`${fmt.cyan("│")} ${fmt.green("repolens demo")}
|
|
139
|
-
info(`${fmt.cyan("│")} ${fmt.cyan("│")}`);
|
|
140
|
-
info(`${fmt.cyan("│")} Or run: ${fmt.brightCyan("repolens init --interactive")} → select GitHub Models ${fmt.cyan("│")}`);
|
|
137
|
+
info(`${fmt.cyan("│")} Run: ${fmt.brightCyan("repolens init")} → the wizard will guide you through setup ${fmt.cyan("│")}`);
|
|
138
|
+
info(`${fmt.cyan("│")} Or quick preview: ${fmt.green("export GITHUB_TOKEN=<your-token> && repolens demo")} ${fmt.cyan("│")}`);
|
|
141
139
|
info(`${fmt.cyan("└──────────────────────────────────────────────────────────────────┘")}`);
|
|
142
140
|
}
|
|
143
141
|
|
|
@@ -242,7 +240,7 @@ Commands:
|
|
|
242
240
|
Options:
|
|
243
241
|
--config Path to .repolens.yml (auto-discovered if not provided)
|
|
244
242
|
--target Target repository path for init/doctor/migrate
|
|
245
|
-
--
|
|
243
|
+
--quick Run init with minimal prompts (skip wizard)
|
|
246
244
|
--dry-run Preview migration changes without applying them
|
|
247
245
|
--force Skip interactive confirmation for migration
|
|
248
246
|
--verbose Enable verbose logging
|
|
@@ -250,8 +248,8 @@ Options:
|
|
|
250
248
|
--help Show this help message
|
|
251
249
|
|
|
252
250
|
Examples:
|
|
253
|
-
repolens init #
|
|
254
|
-
repolens init --
|
|
251
|
+
repolens init # Full interactive wizard (recommended)
|
|
252
|
+
repolens init --quick # Minimal setup, skip wizard
|
|
255
253
|
repolens init --target /tmp/my-repo
|
|
256
254
|
repolens doctor --target /tmp/my-repo
|
|
257
255
|
repolens migrate # Migrate workflows in current directory
|
|
@@ -295,7 +293,9 @@ async function main() {
|
|
|
295
293
|
if (command === "init") {
|
|
296
294
|
await printBanner();
|
|
297
295
|
const targetDir = getArg("--target") || process.cwd();
|
|
298
|
-
|
|
296
|
+
// Interactive is now the default; use --quick for minimal scaffolding
|
|
297
|
+
const quick = process.argv.includes("--quick") || process.argv.includes("--non-interactive");
|
|
298
|
+
const interactive = !quick;
|
|
299
299
|
info(`Initializing RepoLens in: ${targetDir}`);
|
|
300
300
|
|
|
301
301
|
const timer = startTimer("init");
|
|
@@ -598,7 +598,7 @@ async function main() {
|
|
|
598
598
|
|
|
599
599
|
if (aiResult.enabled && aiResult.wasPrompted) {
|
|
600
600
|
info(`\n🤖 AI-enhanced docs were generated using ${fmt.boldGreen("GitHub Models (FREE)")}`);
|
|
601
|
-
info(" To keep AI enabled permanently, run: repolens init
|
|
601
|
+
info(" To keep AI enabled permanently, run: repolens init");
|
|
602
602
|
} else if (aiResult.noToken) {
|
|
603
603
|
// No GITHUB_TOKEN - show instructions
|
|
604
604
|
showPostGenerationAINotice();
|
package/src/init.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { exec } from "node:child_process";
|
|
4
5
|
import { info, warn } from "./utils/logger.js";
|
|
5
6
|
|
|
6
7
|
const PUBLISHER_CHOICES = ["markdown", "notion", "confluence", "github_wiki"];
|
|
7
8
|
const AI_PROVIDERS = [
|
|
8
|
-
{ value: "github",
|
|
9
|
-
{ value: "openai_compatible", label: "OpenAI / Compatible (GPT-5, GPT-4o, etc.)" },
|
|
10
|
-
{ value: "anthropic", label: "Anthropic (Claude)" },
|
|
11
|
-
{ value: "google", label: "Google (Gemini)" },
|
|
9
|
+
{ value: "github", label: "GitHub Models (free in GitHub Actions)", signupUrl: null },
|
|
10
|
+
{ value: "openai_compatible", label: "OpenAI / Compatible (GPT-5, GPT-4o, etc.)", signupUrl: "https://platform.openai.com/api-keys" },
|
|
11
|
+
{ value: "anthropic", label: "Anthropic (Claude)", signupUrl: "https://console.anthropic.com/settings/keys" },
|
|
12
|
+
{ value: "google", label: "Google (Gemini)", signupUrl: "https://aistudio.google.com/app/apikey" },
|
|
12
13
|
];
|
|
13
14
|
const SCAN_PRESETS = {
|
|
14
15
|
nextjs: {
|
|
@@ -89,10 +90,14 @@ jobs:
|
|
|
89
90
|
CONFLUENCE_API_TOKEN: \${{ secrets.CONFLUENCE_API_TOKEN }}
|
|
90
91
|
CONFLUENCE_SPACE_KEY: \${{ secrets.CONFLUENCE_SPACE_KEY }}
|
|
91
92
|
CONFLUENCE_PARENT_PAGE_ID: \${{ secrets.CONFLUENCE_PARENT_PAGE_ID }}
|
|
92
|
-
# AI-enhanced docs
|
|
93
|
+
# AI-enhanced docs: Choose ONE option below
|
|
93
94
|
REPOLENS_AI_ENABLED: true
|
|
95
|
+
# Option A: GitHub Models (free — uses GITHUB_TOKEN)
|
|
94
96
|
REPOLENS_AI_PROVIDER: github
|
|
95
97
|
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
98
|
+
# Option B: OpenAI / Anthropic / Google (comment out Option A, uncomment below)
|
|
99
|
+
# REPOLENS_AI_API_KEY: \${{ secrets.REPOLENS_AI_API_KEY }}
|
|
100
|
+
# REPOLENS_AI_PROVIDER: openai_compatible # or: anthropic, google
|
|
96
101
|
run: npx @chappibunny/repolens@latest publish
|
|
97
102
|
`;
|
|
98
103
|
|
|
@@ -283,6 +288,117 @@ async function dirExists(dirPath) {
|
|
|
283
288
|
}
|
|
284
289
|
}
|
|
285
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Open a URL in the default browser (cross-platform).
|
|
293
|
+
*/
|
|
294
|
+
function openUrl(url) {
|
|
295
|
+
const platform = process.platform;
|
|
296
|
+
let cmd;
|
|
297
|
+
if (platform === "darwin") {
|
|
298
|
+
cmd = `open "${url}"`;
|
|
299
|
+
} else if (platform === "win32") {
|
|
300
|
+
cmd = `start "" "${url}"`;
|
|
301
|
+
} else {
|
|
302
|
+
cmd = `xdg-open "${url}"`;
|
|
303
|
+
}
|
|
304
|
+
return new Promise((resolve) => {
|
|
305
|
+
exec(cmd, (err) => {
|
|
306
|
+
if (err) {
|
|
307
|
+
warn(`Could not open browser: ${err.message}`);
|
|
308
|
+
resolve(false);
|
|
309
|
+
} else {
|
|
310
|
+
resolve(true);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Test an AI API key by making a minimal request.
|
|
318
|
+
* Returns { success: true } or { success: false, error: string }.
|
|
319
|
+
*/
|
|
320
|
+
async function testAIKey(provider, apiKey) {
|
|
321
|
+
try {
|
|
322
|
+
const timeout = 15000;
|
|
323
|
+
const controller = new AbortController();
|
|
324
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
325
|
+
|
|
326
|
+
let url, headers, body;
|
|
327
|
+
|
|
328
|
+
if (provider === "github") {
|
|
329
|
+
url = "https://models.inference.ai.azure.com/chat/completions";
|
|
330
|
+
headers = {
|
|
331
|
+
"Content-Type": "application/json",
|
|
332
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
333
|
+
};
|
|
334
|
+
body = JSON.stringify({
|
|
335
|
+
model: "gpt-4o-mini",
|
|
336
|
+
messages: [{ role: "user", content: "Say OK" }],
|
|
337
|
+
max_tokens: 5,
|
|
338
|
+
});
|
|
339
|
+
} else if (provider === "openai_compatible") {
|
|
340
|
+
url = "https://api.openai.com/v1/chat/completions";
|
|
341
|
+
headers = {
|
|
342
|
+
"Content-Type": "application/json",
|
|
343
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
344
|
+
};
|
|
345
|
+
body = JSON.stringify({
|
|
346
|
+
model: "gpt-4o-mini",
|
|
347
|
+
messages: [{ role: "user", content: "Say OK" }],
|
|
348
|
+
max_tokens: 5,
|
|
349
|
+
});
|
|
350
|
+
} else if (provider === "anthropic") {
|
|
351
|
+
url = "https://api.anthropic.com/v1/messages";
|
|
352
|
+
headers = {
|
|
353
|
+
"Content-Type": "application/json",
|
|
354
|
+
"x-api-key": apiKey,
|
|
355
|
+
"anthropic-version": "2023-06-01",
|
|
356
|
+
};
|
|
357
|
+
body = JSON.stringify({
|
|
358
|
+
model: "claude-sonnet-4-20250514",
|
|
359
|
+
max_tokens: 5,
|
|
360
|
+
messages: [{ role: "user", content: "Say OK" }],
|
|
361
|
+
});
|
|
362
|
+
} else if (provider === "google") {
|
|
363
|
+
url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
|
|
364
|
+
headers = { "Content-Type": "application/json" };
|
|
365
|
+
body = JSON.stringify({
|
|
366
|
+
contents: [{ parts: [{ text: "Say OK" }] }],
|
|
367
|
+
generationConfig: { maxOutputTokens: 5 },
|
|
368
|
+
});
|
|
369
|
+
} else {
|
|
370
|
+
return { success: false, error: "Unknown provider" };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const response = await fetch(url, {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers,
|
|
376
|
+
body,
|
|
377
|
+
signal: controller.signal,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
clearTimeout(timeoutId);
|
|
381
|
+
|
|
382
|
+
if (response.ok) {
|
|
383
|
+
return { success: true };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const errorBody = await response.text().catch(() => "");
|
|
387
|
+
if (response.status === 401 || response.status === 403) {
|
|
388
|
+
return { success: false, error: "Invalid API key" };
|
|
389
|
+
}
|
|
390
|
+
if (response.status === 429) {
|
|
391
|
+
return { success: false, error: "Rate limited — but key is valid" };
|
|
392
|
+
}
|
|
393
|
+
return { success: false, error: `API error ${response.status}: ${errorBody.slice(0, 100)}` };
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (err.name === "AbortError") {
|
|
396
|
+
return { success: false, error: "Request timed out" };
|
|
397
|
+
}
|
|
398
|
+
return { success: false, error: err.message };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
286
402
|
async function detectRepoStructure(repoRoot) {
|
|
287
403
|
const detectedRoots = [];
|
|
288
404
|
|
|
@@ -482,7 +598,8 @@ async function promptNotionCredentials() {
|
|
|
482
598
|
});
|
|
483
599
|
|
|
484
600
|
try {
|
|
485
|
-
info("\n📝
|
|
601
|
+
info("\n📝 Quick Setup — Notion Publishing");
|
|
602
|
+
info("(Use 'repolens init' without --quick for full wizard)\n");
|
|
486
603
|
const useNotion = await rl.question("Would you like to publish to Notion? (Y/n): ");
|
|
487
604
|
|
|
488
605
|
if (useNotion.toLowerCase() === 'n') {
|
|
@@ -564,15 +681,20 @@ async function runInteractiveWizard(repoRoot) {
|
|
|
564
681
|
|
|
565
682
|
try {
|
|
566
683
|
info("\n🧙 Interactive Configuration Wizard\n");
|
|
684
|
+
info("This wizard will help you configure RepoLens for your project.");
|
|
685
|
+
info("Press Enter to accept defaults shown in parentheses.\n");
|
|
567
686
|
|
|
568
687
|
// 1. Project name
|
|
569
688
|
const defaultName = path.basename(repoRoot) || "my-project";
|
|
570
|
-
const projectName = (await ask(
|
|
689
|
+
const projectName = (await ask(`📦 Project name (${defaultName}): `)).trim() || defaultName;
|
|
571
690
|
|
|
572
691
|
// 2. Publishers
|
|
573
|
-
info("\
|
|
574
|
-
PUBLISHER_CHOICES.forEach((p, i) =>
|
|
575
|
-
|
|
692
|
+
info("\n📤 Select publishers (comma-separated numbers):");
|
|
693
|
+
PUBLISHER_CHOICES.forEach((p, i) => {
|
|
694
|
+
const desc = PUBLISHER_DESCRIPTIONS[p] || "";
|
|
695
|
+
info(` ${i + 1}. ${p}${desc ? ` — ${desc}` : ""}`);
|
|
696
|
+
});
|
|
697
|
+
const pubInput = (await ask(`Publishers [1] (default: 1 markdown): `)).trim() || "1";
|
|
576
698
|
const publishers = pubInput
|
|
577
699
|
.split(",")
|
|
578
700
|
.map((n) => parseInt(n.trim(), 10))
|
|
@@ -580,24 +702,156 @@ async function runInteractiveWizard(repoRoot) {
|
|
|
580
702
|
.map((n) => PUBLISHER_CHOICES[n - 1]);
|
|
581
703
|
if (publishers.length === 0) publishers.push("markdown");
|
|
582
704
|
|
|
583
|
-
// 3.
|
|
584
|
-
const
|
|
705
|
+
// 3. Collect credentials for each publisher
|
|
706
|
+
const credentials = {};
|
|
707
|
+
const githubSecretsNeeded = [];
|
|
708
|
+
|
|
709
|
+
// Notion setup
|
|
710
|
+
if (publishers.includes("notion")) {
|
|
711
|
+
info("\n📝 Notion Setup");
|
|
712
|
+
info(" Get your integration token from: https://www.notion.so/my-integrations");
|
|
713
|
+
info(" Find page ID in the URL: notion.so/workspace/PAGE_ID_HERE\n");
|
|
714
|
+
|
|
715
|
+
const setupNow = (await ask(" Configure Notion credentials now? (Y/n): ")).trim().toLowerCase();
|
|
716
|
+
if (setupNow !== "n") {
|
|
717
|
+
credentials.notion = {
|
|
718
|
+
token: (await ask(" NOTION_TOKEN: ")).trim(),
|
|
719
|
+
parentPageId: (await ask(" NOTION_PARENT_PAGE_ID: ")).trim(),
|
|
720
|
+
};
|
|
721
|
+
if (!credentials.notion.token || !credentials.notion.parentPageId) {
|
|
722
|
+
warn(" Incomplete credentials. You'll need to set them manually.");
|
|
723
|
+
delete credentials.notion;
|
|
724
|
+
} else {
|
|
725
|
+
info(" ✓ Notion credentials collected");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
githubSecretsNeeded.push("NOTION_TOKEN", "NOTION_PARENT_PAGE_ID");
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Confluence setup
|
|
732
|
+
if (publishers.includes("confluence")) {
|
|
733
|
+
info("\n📝 Confluence Setup");
|
|
734
|
+
info(" Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens");
|
|
735
|
+
info(" Find space key in the URL: confluence.atlassian.net/wiki/spaces/SPACE_KEY/...\n");
|
|
736
|
+
|
|
737
|
+
const setupNow = (await ask(" Configure Confluence credentials now? (Y/n): ")).trim().toLowerCase();
|
|
738
|
+
if (setupNow !== "n") {
|
|
739
|
+
credentials.confluence = {
|
|
740
|
+
url: (await ask(" CONFLUENCE_URL (e.g., https://company.atlassian.net/wiki): ")).trim(),
|
|
741
|
+
email: (await ask(" CONFLUENCE_EMAIL: ")).trim(),
|
|
742
|
+
apiToken: (await ask(" CONFLUENCE_API_TOKEN: ")).trim(),
|
|
743
|
+
spaceKey: (await ask(" CONFLUENCE_SPACE_KEY: ")).trim(),
|
|
744
|
+
parentPageId: (await ask(" CONFLUENCE_PARENT_PAGE_ID (optional): ")).trim(),
|
|
745
|
+
};
|
|
746
|
+
const conf = credentials.confluence;
|
|
747
|
+
if (!conf.url || !conf.email || !conf.apiToken || !conf.spaceKey) {
|
|
748
|
+
warn(" Incomplete credentials. You'll need to set them manually.");
|
|
749
|
+
delete credentials.confluence;
|
|
750
|
+
} else {
|
|
751
|
+
info(" ✓ Confluence credentials collected");
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
githubSecretsNeeded.push("CONFLUENCE_URL", "CONFLUENCE_EMAIL", "CONFLUENCE_API_TOKEN", "CONFLUENCE_SPACE_KEY", "CONFLUENCE_PARENT_PAGE_ID");
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// GitHub Wiki setup
|
|
758
|
+
if (publishers.includes("github_wiki")) {
|
|
759
|
+
info("\n📝 GitHub Wiki Setup");
|
|
760
|
+
info(" Requires GITHUB_TOKEN with repo scope.");
|
|
761
|
+
if (process.env.GITHUB_TOKEN) {
|
|
762
|
+
info(" ✓ GITHUB_TOKEN is set in your environment");
|
|
763
|
+
} else {
|
|
764
|
+
warn(" GITHUB_TOKEN not found in environment.");
|
|
765
|
+
info(" For local use: export GITHUB_TOKEN=your_token");
|
|
766
|
+
info(" For GitHub Actions: Uses ${{ secrets.GITHUB_TOKEN }} automatically");
|
|
767
|
+
}
|
|
768
|
+
githubSecretsNeeded.push("GITHUB_TOKEN");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// 4. AI Configuration
|
|
772
|
+
info("\n🤖 AI-Enhanced Documentation");
|
|
773
|
+
info(" Adds natural language explanations for non-technical stakeholders.");
|
|
774
|
+
const enableAi = (await ask(" Enable AI features? (Y/n): ")).trim().toLowerCase() !== "n";
|
|
775
|
+
|
|
585
776
|
let aiProvider = null;
|
|
777
|
+
let aiApiKey = null;
|
|
778
|
+
|
|
586
779
|
if (enableAi) {
|
|
587
|
-
info("Select AI provider:");
|
|
588
|
-
AI_PROVIDERS.forEach((p, i) => info(`
|
|
589
|
-
const aiInput = (await ask(`Provider [1] (default: 1 GitHub Models — free): `)).trim() || "1";
|
|
780
|
+
info("\n Select AI provider:");
|
|
781
|
+
AI_PROVIDERS.forEach((p, i) => info(` ${i + 1}. ${p.label}`));
|
|
782
|
+
const aiInput = (await ask(` Provider [1] (default: 1 GitHub Models — free): `)).trim() || "1";
|
|
590
783
|
const idx = parseInt(aiInput, 10);
|
|
591
784
|
const chosen = AI_PROVIDERS[(idx >= 1 && idx <= AI_PROVIDERS.length) ? idx - 1 : 0];
|
|
592
785
|
aiProvider = chosen.value;
|
|
786
|
+
|
|
593
787
|
if (aiProvider === "github") {
|
|
594
|
-
info("\n
|
|
595
|
-
|
|
788
|
+
info("\n ✨ GitHub Models is free and uses your GITHUB_TOKEN.");
|
|
789
|
+
|
|
790
|
+
// Check for existing token
|
|
791
|
+
const existingToken = process.env.GITHUB_TOKEN;
|
|
792
|
+
if (existingToken) {
|
|
793
|
+
info(" Testing your GITHUB_TOKEN...");
|
|
794
|
+
const testResult = await testAIKey("github", existingToken);
|
|
795
|
+
if (testResult.success) {
|
|
796
|
+
info(" ✓ GITHUB_TOKEN is valid — AI will work locally and in Actions");
|
|
797
|
+
credentials.ai = { provider: "github", useGitHubToken: true };
|
|
798
|
+
} else {
|
|
799
|
+
warn(` ⚠ GITHUB_TOKEN test failed: ${testResult.error}`);
|
|
800
|
+
info(" AI will still work in GitHub Actions with ${{ secrets.GITHUB_TOKEN }}");
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
info(" No GITHUB_TOKEN found in environment.");
|
|
804
|
+
info(" In GitHub Actions: Works automatically with ${{ secrets.GITHUB_TOKEN }}");
|
|
805
|
+
info(" For local testing: export GITHUB_TOKEN=your_personal_access_token");
|
|
806
|
+
}
|
|
807
|
+
githubSecretsNeeded.push("GITHUB_TOKEN");
|
|
808
|
+
credentials.ai = { ...(credentials.ai || {}), provider: "github", enabled: true };
|
|
809
|
+
} else {
|
|
810
|
+
// Non-GitHub provider: help them get an API key
|
|
811
|
+
info(`\n ${chosen.label} requires an API key.`);
|
|
812
|
+
|
|
813
|
+
// Offer to open signup URL
|
|
814
|
+
if (chosen.signupUrl) {
|
|
815
|
+
const openBrowser = (await ask(` Open ${chosen.value} signup page in browser? (Y/n): `)).trim().toLowerCase();
|
|
816
|
+
if (openBrowser !== "n") {
|
|
817
|
+
info(` Opening ${chosen.signupUrl}...`);
|
|
818
|
+
await openUrl(chosen.signupUrl);
|
|
819
|
+
info(" Create an API key, then paste it below.\n");
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const keyInput = (await ask(` Paste your API key (or press Enter to skip): `)).trim();
|
|
824
|
+
if (keyInput) {
|
|
825
|
+
info(" Testing your API key...");
|
|
826
|
+
const testResult = await testAIKey(aiProvider, keyInput);
|
|
827
|
+
|
|
828
|
+
if (testResult.success) {
|
|
829
|
+
info(" ✓ API key is valid!");
|
|
830
|
+
aiApiKey = keyInput;
|
|
831
|
+
credentials.ai = { apiKey: keyInput, provider: aiProvider, enabled: true };
|
|
832
|
+
} else if (testResult.error === "Rate limited — but key is valid") {
|
|
833
|
+
info(" ✓ API key is valid (rate limited, but will work)");
|
|
834
|
+
aiApiKey = keyInput;
|
|
835
|
+
credentials.ai = { apiKey: keyInput, provider: aiProvider, enabled: true };
|
|
836
|
+
} else {
|
|
837
|
+
warn(` ⚠ API key test failed: ${testResult.error}`);
|
|
838
|
+
const useAnyway = (await ask(` Save this key anyway? (y/N): `)).trim().toLowerCase();
|
|
839
|
+
if (useAnyway === "y") {
|
|
840
|
+
aiApiKey = keyInput;
|
|
841
|
+
credentials.ai = { apiKey: keyInput, provider: aiProvider, enabled: true };
|
|
842
|
+
} else {
|
|
843
|
+
warn(" Skipping AI configuration. You can set REPOLENS_AI_API_KEY later.");
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
warn(" No API key provided. Set REPOLENS_AI_API_KEY in .env or GitHub secrets.");
|
|
848
|
+
}
|
|
849
|
+
githubSecretsNeeded.push("REPOLENS_AI_API_KEY");
|
|
596
850
|
}
|
|
597
851
|
}
|
|
598
852
|
|
|
599
|
-
//
|
|
600
|
-
info("\
|
|
853
|
+
// 5. Scan preset
|
|
854
|
+
info("\n📂 Scan Preset (determines which files to analyze):");
|
|
601
855
|
const presetKeys = Object.keys(SCAN_PRESETS);
|
|
602
856
|
presetKeys.forEach((p, i) => info(` ${i + 1}. ${p}`));
|
|
603
857
|
const presetInput = (await ask(`Preset [3] (default: 3 generic): `)).trim() || "3";
|
|
@@ -605,20 +859,80 @@ async function runInteractiveWizard(repoRoot) {
|
|
|
605
859
|
const presetKey = presetKeys[(presetIdx >= 1 && presetIdx <= presetKeys.length) ? presetIdx - 1 : 2];
|
|
606
860
|
const preset = SCAN_PRESETS[presetKey];
|
|
607
861
|
|
|
608
|
-
//
|
|
609
|
-
|
|
862
|
+
// 6. Branch filtering
|
|
863
|
+
info("\n🌿 Branch Filtering");
|
|
864
|
+
info(" Limits which branches can publish to Notion/Confluence.");
|
|
865
|
+
const branchInput = (await ask(" Allowed branches (comma-separated, default: main): ")).trim() || "main";
|
|
610
866
|
const branches = branchInput.split(",").map((b) => b.trim()).filter(Boolean);
|
|
611
867
|
|
|
612
|
-
//
|
|
613
|
-
|
|
868
|
+
// 7. Discord notifications
|
|
869
|
+
info("\n🔔 Discord Notifications");
|
|
870
|
+
const enableDiscord = (await ask(" Enable Discord notifications? (y/N): ")).trim().toLowerCase() === "y";
|
|
871
|
+
|
|
872
|
+
let discordWebhook = null;
|
|
873
|
+
if (enableDiscord) {
|
|
874
|
+
info(" Get webhook URL from: Server Settings > Integrations > Webhooks");
|
|
875
|
+
discordWebhook = (await ask(" DISCORD_WEBHOOK_URL (leave blank to skip): ")).trim() || null;
|
|
876
|
+
if (discordWebhook) {
|
|
877
|
+
credentials.discord = { webhookUrl: discordWebhook };
|
|
878
|
+
}
|
|
879
|
+
githubSecretsNeeded.push("DISCORD_WEBHOOK_URL");
|
|
880
|
+
}
|
|
614
881
|
|
|
615
|
-
|
|
616
|
-
|
|
882
|
+
// Summary
|
|
883
|
+
info("\n" + "═".repeat(60));
|
|
884
|
+
info("📋 Configuration Summary");
|
|
885
|
+
info("═".repeat(60));
|
|
886
|
+
info(` Project: ${projectName}`);
|
|
887
|
+
info(` Publishers: ${publishers.join(", ")}`);
|
|
888
|
+
info(` AI: ${enableAi ? `Enabled (${aiProvider})` : "Disabled"}`);
|
|
889
|
+
info(` Scan: ${presetKey} preset`);
|
|
890
|
+
info(` Branches: ${branches.join(", ")}`);
|
|
891
|
+
info(` Discord: ${enableDiscord ? "Enabled" : "Disabled"}`);
|
|
892
|
+
|
|
893
|
+
// GitHub secrets summary
|
|
894
|
+
const uniqueSecrets = [...new Set(githubSecretsNeeded)];
|
|
895
|
+
if (uniqueSecrets.length > 0) {
|
|
896
|
+
info("\n📌 GitHub Actions Secrets Required:");
|
|
897
|
+
info(" Add these at: https://github.com/YOUR_ORG/YOUR_REPO/settings/secrets/actions");
|
|
898
|
+
for (const secret of uniqueSecrets) {
|
|
899
|
+
const status = credentials[secret.toLowerCase().split("_")[0]] ? "✓" : "○";
|
|
900
|
+
info(` ${status} ${secret}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
info("\n" + "═".repeat(60));
|
|
905
|
+
|
|
906
|
+
const proceed = (await ask("\nProceed with this configuration? (Y/n): ")).trim().toLowerCase();
|
|
907
|
+
if (proceed === "n") {
|
|
908
|
+
info("Configuration cancelled.");
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
info("\n✓ Wizard complete. Generating files...\n");
|
|
913
|
+
return {
|
|
914
|
+
projectName,
|
|
915
|
+
publishers,
|
|
916
|
+
enableAi,
|
|
917
|
+
aiProvider,
|
|
918
|
+
preset,
|
|
919
|
+
branches,
|
|
920
|
+
enableDiscord,
|
|
921
|
+
credentials,
|
|
922
|
+
githubSecretsNeeded: uniqueSecrets
|
|
923
|
+
};
|
|
617
924
|
} finally {
|
|
618
925
|
rl.close();
|
|
619
926
|
}
|
|
620
927
|
}
|
|
621
928
|
|
|
929
|
+
const PUBLISHER_DESCRIPTIONS = {
|
|
930
|
+
markdown: "Local files in .repolens/",
|
|
931
|
+
notion: "Notion workspace pages",
|
|
932
|
+
confluence: "Atlassian Confluence pages",
|
|
933
|
+
github_wiki: "Repository wiki pages",
|
|
934
|
+
};
|
|
935
|
+
|
|
622
936
|
/**
|
|
623
937
|
* Build a .repolens.yml from wizard answers.
|
|
624
938
|
*/
|
|
@@ -719,16 +1033,70 @@ function buildWizardConfig(answers) {
|
|
|
719
1033
|
return lines.join("\n");
|
|
720
1034
|
}
|
|
721
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* Build .env content from wizard credentials.
|
|
1038
|
+
*/
|
|
1039
|
+
function buildEnvFromCredentials(credentials) {
|
|
1040
|
+
const lines = [];
|
|
1041
|
+
|
|
1042
|
+
if (credentials.notion) {
|
|
1043
|
+
lines.push("# Notion Publishing");
|
|
1044
|
+
lines.push(`NOTION_TOKEN=${credentials.notion.token}`);
|
|
1045
|
+
lines.push(`NOTION_PARENT_PAGE_ID=${credentials.notion.parentPageId}`);
|
|
1046
|
+
lines.push(`NOTION_VERSION=2022-06-28`);
|
|
1047
|
+
lines.push("");
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (credentials.confluence) {
|
|
1051
|
+
lines.push("# Confluence Publishing");
|
|
1052
|
+
lines.push(`CONFLUENCE_URL=${credentials.confluence.url}`);
|
|
1053
|
+
lines.push(`CONFLUENCE_EMAIL=${credentials.confluence.email}`);
|
|
1054
|
+
lines.push(`CONFLUENCE_API_TOKEN=${credentials.confluence.apiToken}`);
|
|
1055
|
+
lines.push(`CONFLUENCE_SPACE_KEY=${credentials.confluence.spaceKey}`);
|
|
1056
|
+
if (credentials.confluence.parentPageId) {
|
|
1057
|
+
lines.push(`CONFLUENCE_PARENT_PAGE_ID=${credentials.confluence.parentPageId}`);
|
|
1058
|
+
}
|
|
1059
|
+
lines.push("");
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (credentials.ai?.enabled) {
|
|
1063
|
+
lines.push("# AI Configuration");
|
|
1064
|
+
lines.push(`REPOLENS_AI_ENABLED=true`);
|
|
1065
|
+
if (credentials.ai.provider) {
|
|
1066
|
+
lines.push(`REPOLENS_AI_PROVIDER=${credentials.ai.provider}`);
|
|
1067
|
+
}
|
|
1068
|
+
if (credentials.ai.apiKey) {
|
|
1069
|
+
lines.push(`REPOLENS_AI_API_KEY=${credentials.ai.apiKey}`);
|
|
1070
|
+
}
|
|
1071
|
+
if (credentials.ai.provider === "github") {
|
|
1072
|
+
lines.push("# GitHub Models uses GITHUB_TOKEN (set separately or auto-available in Actions)");
|
|
1073
|
+
}
|
|
1074
|
+
lines.push("");
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (credentials.discord) {
|
|
1078
|
+
lines.push("# Discord Notifications");
|
|
1079
|
+
lines.push(`DISCORD_WEBHOOK_URL=${credentials.discord.webhookUrl}`);
|
|
1080
|
+
lines.push("");
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return lines.join("\n");
|
|
1084
|
+
}
|
|
1085
|
+
|
|
722
1086
|
export async function runInit(targetDir = process.cwd(), options = {}) {
|
|
723
1087
|
const repoRoot = path.resolve(targetDir);
|
|
724
1088
|
|
|
725
1089
|
// Ensure target directory exists
|
|
726
1090
|
await fs.mkdir(repoRoot, { recursive: true });
|
|
727
1091
|
|
|
728
|
-
// Interactive wizard
|
|
1092
|
+
// Interactive wizard is now the default (--quick skips it)
|
|
729
1093
|
let wizardAnswers = null;
|
|
730
1094
|
if (options.interactive) {
|
|
731
1095
|
wizardAnswers = await runInteractiveWizard(repoRoot);
|
|
1096
|
+
if (!wizardAnswers) {
|
|
1097
|
+
// User cancelled the wizard
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
732
1100
|
}
|
|
733
1101
|
|
|
734
1102
|
// Prompt for Notion credentials interactively (only in non-wizard mode)
|
|
@@ -765,7 +1133,7 @@ export async function runInit(targetDir = process.cwd(), options = {}) {
|
|
|
765
1133
|
for (const root of detectedRoots) {
|
|
766
1134
|
info(` - ${root}`);
|
|
767
1135
|
}
|
|
768
|
-
} else {
|
|
1136
|
+
} else if (!wizardAnswers) {
|
|
769
1137
|
info(`No known roots detected. Falling back to default config.`);
|
|
770
1138
|
}
|
|
771
1139
|
|
|
@@ -792,18 +1160,25 @@ export async function runInit(targetDir = process.cwd(), options = {}) {
|
|
|
792
1160
|
info(`Skipped existing ${envExamplePath}`);
|
|
793
1161
|
}
|
|
794
1162
|
|
|
795
|
-
// Create .env file with collected credentials
|
|
796
|
-
if (
|
|
1163
|
+
// Create .env file with collected credentials (wizard mode)
|
|
1164
|
+
if (wizardAnswers?.credentials && Object.keys(wizardAnswers.credentials).length > 0 && !envExists) {
|
|
1165
|
+
const envContent = buildEnvFromCredentials(wizardAnswers.credentials);
|
|
1166
|
+
if (envContent.trim()) {
|
|
1167
|
+
await fs.writeFile(envPath, envContent, "utf8");
|
|
1168
|
+
info(`✅ Created ${envPath} with your credentials`);
|
|
1169
|
+
await ensureEnvInGitignore(repoRoot);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
// Legacy: Create .env file with Notion credentials (non-wizard mode)
|
|
1173
|
+
else if (notionCredentials && !envExists) {
|
|
797
1174
|
const envContent = `NOTION_TOKEN=${notionCredentials.token}
|
|
798
1175
|
NOTION_PARENT_PAGE_ID=${notionCredentials.parentPageId}
|
|
799
1176
|
NOTION_VERSION=2022-06-28
|
|
800
1177
|
`;
|
|
801
1178
|
await fs.writeFile(envPath, envContent, "utf8");
|
|
802
1179
|
info(`✅ Created ${envPath} with your Notion credentials`);
|
|
803
|
-
|
|
804
|
-
// Ensure .env is in .gitignore
|
|
805
1180
|
await ensureEnvInGitignore(repoRoot);
|
|
806
|
-
} else if (notionCredentials && envExists) {
|
|
1181
|
+
} else if ((notionCredentials || wizardAnswers?.credentials) && envExists) {
|
|
807
1182
|
warn(`Skipped existing ${envPath} - your credentials were not overwritten`);
|
|
808
1183
|
}
|
|
809
1184
|
|
|
@@ -815,7 +1190,36 @@ NOTION_VERSION=2022-06-28
|
|
|
815
1190
|
}
|
|
816
1191
|
|
|
817
1192
|
info("\n✨ RepoLens initialization complete!\n");
|
|
818
|
-
|
|
1193
|
+
|
|
1194
|
+
// Wizard mode: Show tailored summary
|
|
1195
|
+
if (wizardAnswers) {
|
|
1196
|
+
info("📁 Files created:");
|
|
1197
|
+
info(" • .repolens.yml — Configuration");
|
|
1198
|
+
info(" • .github/workflows/repolens.yml — GitHub Actions workflow");
|
|
1199
|
+
info(" • .env.example — Template for credentials");
|
|
1200
|
+
if (wizardAnswers.credentials && Object.keys(wizardAnswers.credentials).length > 0) {
|
|
1201
|
+
info(" • .env — Your credentials (gitignored)");
|
|
1202
|
+
}
|
|
1203
|
+
info(" • README.repolens.md — Getting started guide");
|
|
1204
|
+
|
|
1205
|
+
if (wizardAnswers.githubSecretsNeeded && wizardAnswers.githubSecretsNeeded.length > 0) {
|
|
1206
|
+
info("\n🔐 GitHub Actions Secrets:");
|
|
1207
|
+
info(" Add at: Settings → Secrets → Actions");
|
|
1208
|
+
for (const secret of wizardAnswers.githubSecretsNeeded) {
|
|
1209
|
+
info(` • ${secret}`);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
info("\n🚀 Next steps:");
|
|
1214
|
+
info(" 1. Test locally: npx @chappibunny/repolens publish");
|
|
1215
|
+
info(" 2. Add GitHub secrets (see above)");
|
|
1216
|
+
info(" 3. Commit and push to trigger workflow");
|
|
1217
|
+
info(" 4. Run 'npx @chappibunny/repolens doctor' to validate setup");
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Non-wizard mode: Original output
|
|
1222
|
+
if (hasGitHubToken && !wizardAnswers) {
|
|
819
1223
|
info("🤖 Detected GITHUB_TOKEN — AI-enhanced docs enabled via GitHub Models (free)");
|
|
820
1224
|
info(" Your workflow and config are pre-configured. No extra setup needed.\n");
|
|
821
1225
|
}
|