skrypt-ai 0.7.0 → 0.8.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/dist/auth/index.js +3 -3
- package/dist/cli.js +1 -1
- package/dist/commands/cron.js +0 -4
- package/dist/commands/generate/index.d.ts +3 -0
- package/dist/commands/generate/index.js +393 -0
- package/dist/commands/generate/scan.d.ts +41 -0
- package/dist/commands/generate/scan.js +256 -0
- package/dist/commands/generate/verify.d.ts +14 -0
- package/dist/commands/generate/verify.js +122 -0
- package/dist/commands/generate/write.d.ts +25 -0
- package/dist/commands/generate/write.js +120 -0
- package/dist/commands/import.js +4 -1
- package/dist/commands/llms-txt.js +6 -4
- package/dist/config/loader.d.ts +0 -1
- package/dist/config/loader.js +1 -1
- package/dist/generator/agents-md.d.ts +25 -0
- package/dist/generator/agents-md.js +122 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/mdx-serializer.d.ts +11 -0
- package/dist/generator/mdx-serializer.js +135 -0
- package/dist/generator/organizer.d.ts +1 -16
- package/dist/generator/organizer.js +0 -38
- package/dist/generator/writer.js +5 -4
- package/dist/llm/proxy-client.d.ts +32 -0
- package/dist/llm/proxy-client.js +103 -0
- package/dist/scanner/csharp.d.ts +0 -4
- package/dist/scanner/csharp.js +9 -49
- package/dist/scanner/go.d.ts +0 -3
- package/dist/scanner/go.js +8 -35
- package/dist/scanner/java.d.ts +0 -4
- package/dist/scanner/java.js +9 -49
- package/dist/scanner/kotlin.d.ts +0 -3
- package/dist/scanner/kotlin.js +6 -33
- package/dist/scanner/php.d.ts +0 -10
- package/dist/scanner/php.js +11 -55
- package/dist/scanner/ruby.d.ts +0 -3
- package/dist/scanner/ruby.js +8 -38
- package/dist/scanner/rust.d.ts +0 -3
- package/dist/scanner/rust.js +10 -37
- package/dist/scanner/swift.d.ts +0 -3
- package/dist/scanner/swift.js +8 -35
- package/dist/scanner/utils.d.ts +41 -0
- package/dist/scanner/utils.js +97 -0
- package/dist/template/docs.json +5 -2
- package/dist/template/next.config.mjs +31 -0
- package/dist/template/package.json +5 -3
- package/dist/template/src/app/layout.tsx +13 -13
- package/dist/template/src/app/llms-full.md/route.ts +29 -0
- package/dist/template/src/app/llms.txt/route.ts +29 -0
- package/dist/template/src/app/md/[...slug]/route.ts +174 -0
- package/dist/template/src/app/reference/route.ts +22 -18
- package/dist/template/src/app/sitemap.ts +1 -1
- package/dist/template/src/components/ai-chat-impl.tsx +206 -0
- package/dist/template/src/components/ai-chat.tsx +20 -193
- package/dist/template/src/components/mdx/index.tsx +27 -4
- package/dist/template/src/lib/fonts.ts +135 -0
- package/dist/template/src/middleware.ts +101 -0
- package/dist/template/src/styles/globals.css +28 -20
- package/dist/utils/files.d.ts +0 -8
- package/dist/utils/files.js +0 -33
- package/package.json +1 -1
- package/dist/autofix/autofix.test.d.ts +0 -1
- package/dist/autofix/autofix.test.js +0 -487
- package/dist/commands/generate.d.ts +0 -9
- package/dist/commands/generate.js +0 -739
- package/dist/generator/generator.test.d.ts +0 -1
- package/dist/generator/generator.test.js +0 -259
- package/dist/generator/writer.test.d.ts +0 -1
- package/dist/generator/writer.test.js +0 -411
- package/dist/llm/llm.manual-test.d.ts +0 -1
- package/dist/llm/llm.manual-test.js +0 -112
- package/dist/llm/llm.mock-test.d.ts +0 -4
- package/dist/llm/llm.mock-test.js +0 -79
- package/dist/plugins/index.d.ts +0 -47
- package/dist/plugins/index.js +0 -181
- package/dist/scanner/content-type.test.d.ts +0 -1
- package/dist/scanner/content-type.test.js +0 -231
- package/dist/scanner/integration.test.d.ts +0 -4
- package/dist/scanner/integration.test.js +0 -180
- package/dist/scanner/scanner.test.d.ts +0 -1
- package/dist/scanner/scanner.test.js +0 -210
- package/dist/scanner/typescript.manual-test.d.ts +0 -1
- package/dist/scanner/typescript.manual-test.js +0 -112
- package/dist/template/src/app/docs/auth/page.mdx +0 -589
- package/dist/template/src/app/docs/autofix/page.mdx +0 -624
- package/dist/template/src/app/docs/cli/page.mdx +0 -217
- package/dist/template/src/app/docs/config/page.mdx +0 -428
- package/dist/template/src/app/docs/configuration/page.mdx +0 -86
- package/dist/template/src/app/docs/deployment/page.mdx +0 -112
- package/dist/template/src/app/docs/generator/generator.md +0 -504
- package/dist/template/src/app/docs/generator/organizer.md +0 -779
- package/dist/template/src/app/docs/generator/page.mdx +0 -613
- package/dist/template/src/app/docs/github/page.mdx +0 -502
- package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
- package/dist/template/src/app/docs/llm/index.md +0 -471
- package/dist/template/src/app/docs/llm/page.mdx +0 -428
- package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
- package/dist/template/src/app/docs/pro/page.mdx +0 -121
- package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
- package/dist/template/src/app/docs/scanner/content-type.md +0 -599
- package/dist/template/src/app/docs/scanner/index.md +0 -212
- package/dist/template/src/app/docs/scanner/page.mdx +0 -307
- package/dist/template/src/app/docs/scanner/python.md +0 -469
- package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
- package/dist/template/src/app/docs/scanner/rust.md +0 -325
- package/dist/template/src/app/docs/scanner/typescript.md +0 -201
- package/dist/template/src/app/icon.tsx +0 -29
- package/dist/utils/validation.d.ts +0 -1
- package/dist/utils/validation.js +0 -12
package/dist/auth/index.js
CHANGED
|
@@ -8,7 +8,7 @@ if (!homeDir) {
|
|
|
8
8
|
}
|
|
9
9
|
const CONFIG_DIR = join(homeDir, '.skrypt');
|
|
10
10
|
const AUTH_FILE = join(CONFIG_DIR, 'auth.json');
|
|
11
|
-
const API_BASE = process.env.SKRYPT_API_URL || 'https://
|
|
11
|
+
const API_BASE = process.env.SKRYPT_API_URL || 'https://app.skrypt.sh';
|
|
12
12
|
/**
|
|
13
13
|
* Sync auth config reader — checks env var and auth file only (no keychain).
|
|
14
14
|
* Use getAuthConfigAsync() when keychain access is needed.
|
|
@@ -107,7 +107,7 @@ export async function getKeyStorageMethod() {
|
|
|
107
107
|
}
|
|
108
108
|
export async function checkPlan(apiKey) {
|
|
109
109
|
try {
|
|
110
|
-
const response = await fetch(`${API_BASE}/
|
|
110
|
+
const response = await fetch(`${API_BASE}/api/auth/plan`, {
|
|
111
111
|
headers: {
|
|
112
112
|
'Authorization': `Bearer ${apiKey}`,
|
|
113
113
|
'Content-Type': 'application/json'
|
|
@@ -179,8 +179,8 @@ export const PRO_COMMANDS = [
|
|
|
179
179
|
'test',
|
|
180
180
|
'sdk',
|
|
181
181
|
'gh-action',
|
|
182
|
-
'ci',
|
|
183
182
|
'mcp',
|
|
184
183
|
'refresh',
|
|
185
184
|
'review',
|
|
185
|
+
'review-pr',
|
|
186
186
|
];
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { generateCommand } from './commands/generate.js';
|
|
3
|
+
import { generateCommand } from './commands/generate/index.js';
|
|
4
4
|
import { initCommand } from './commands/init.js';
|
|
5
5
|
import { watchCommand } from './commands/watch.js';
|
|
6
6
|
import { autofixCommand } from './commands/autofix.js';
|
package/dist/commands/cron.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
3
|
import { resolve, join } from 'path';
|
|
4
|
-
import { requirePro } from '../auth/index.js';
|
|
5
4
|
const CRON_WORKFLOW = `name: Skrypt Auto-Update Docs
|
|
6
5
|
|
|
7
6
|
on:
|
|
@@ -55,9 +54,6 @@ export const cronCommand = new Command('cron')
|
|
|
55
54
|
.option('-s, --schedule <cron>', 'Cron schedule (default: daily at 2am UTC)', '0 2 * * *')
|
|
56
55
|
.option('-f, --force', 'Overwrite existing workflow')
|
|
57
56
|
.action(async (repoPath, options) => {
|
|
58
|
-
if (!await requirePro('cron')) {
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
57
|
try {
|
|
62
58
|
const resolvedPath = resolve(repoPath);
|
|
63
59
|
const workflowDir = join(resolvedPath, '.github', 'workflows');
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync, readFileSync, rmSync } from 'fs';
|
|
3
|
+
import { resolve, basename, join } from 'path';
|
|
4
|
+
import { loadConfig, validateConfig, checkApiKey } from '../../config/index.js';
|
|
5
|
+
import { DEFAULT_MODELS } from '../../config/types.js';
|
|
6
|
+
import { createLLMClient } from '../../llm/index.js';
|
|
7
|
+
import { generateForElements } from '../../generator/index.js';
|
|
8
|
+
import { showSecurityNotice } from '../../auth/notices.js';
|
|
9
|
+
import { requirePro, getAuthConfigAsync } from '../../auth/index.js';
|
|
10
|
+
import { ProxyClient, startProxySession, completeProxySession } from '../../llm/proxy-client.js';
|
|
11
|
+
import { runQA, printQAReport, fixQAIssues, printFixReport } from '../../qa/index.js';
|
|
12
|
+
import { extractDependencyIds, isChubInstalled, fetchContextHubDocs, exportToContextHub } from '../../context-hub/index.js';
|
|
13
|
+
import { planSmartStructure, generateWithStructure } from '../../structure/index.js';
|
|
14
|
+
import * as readline from 'readline';
|
|
15
|
+
import { scanSources } from './scan.js';
|
|
16
|
+
import { writeDocs, writeAssets } from './write.js';
|
|
17
|
+
import { verifyCodeExamples } from './verify.js';
|
|
18
|
+
function promptUser(question) {
|
|
19
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
return new Promise(resolve => {
|
|
21
|
+
rl.question(question, answer => {
|
|
22
|
+
rl.close();
|
|
23
|
+
resolve(answer.trim());
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export { parseSourceArgs } from './scan.js';
|
|
28
|
+
export const generateCommand = new Command('generate')
|
|
29
|
+
.description('Generate documentation with code examples')
|
|
30
|
+
.argument('[sources...]', 'Source directories to scan (use dir:Label for labels)')
|
|
31
|
+
.option('-o, --output <dir>', 'Output directory')
|
|
32
|
+
.option('-c, --config <file>', 'Config file path')
|
|
33
|
+
.option('--provider <name>', 'LLM provider (deepseek, openai, anthropic, google, ollama, openrouter)')
|
|
34
|
+
.option('--model <name>', 'LLM model name')
|
|
35
|
+
.option('--base-url <url>', 'Custom API base URL (for Ollama or proxies)')
|
|
36
|
+
.option('--dry-run', 'Scan only, do not generate docs')
|
|
37
|
+
.option('--multi-lang', 'Generate TypeScript + Python examples')
|
|
38
|
+
.option('--by-topic', 'Organize output by topic instead of file')
|
|
39
|
+
.option('--openapi <file>', 'Include OpenAPI spec file for API Playground')
|
|
40
|
+
.option('--public-only', 'Only document exported/public APIs')
|
|
41
|
+
.option('--exclude <patterns...>', 'Exclude patterns (files, names, or name:pattern)')
|
|
42
|
+
.option('--llms-txt', 'Generate llms.txt for Answer Engine Optimization (AEO)')
|
|
43
|
+
.option('--project-name <name>', 'Project name for llms.txt header')
|
|
44
|
+
.option('--org <name>', 'GitHub organization to discover repos from')
|
|
45
|
+
.option('--repos <list>', 'Comma-separated list of repos to include (with --org)')
|
|
46
|
+
.option('--exclude-repos <list>', 'Comma-separated list of repos to exclude (with --org)')
|
|
47
|
+
.option('--verify', 'Verify generated code examples by running them (Pro)')
|
|
48
|
+
.option('--env-file <file>', 'Load environment variables for code verification')
|
|
49
|
+
.option('--smart-structure', 'Organize docs by user journey instead of file structure (Pro)')
|
|
50
|
+
.option('--max-verify-iterations <n>', 'Max re-generation attempts for failing snippets', '2')
|
|
51
|
+
.option('--no-agents-md', 'Skip AGENTS.md / CLAUDE.md generation')
|
|
52
|
+
.action(async (sources = [], options) => {
|
|
53
|
+
try {
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
// Require at least one source or --org
|
|
56
|
+
if (sources.length === 0 && !options.org) {
|
|
57
|
+
console.error('Error: At least one source directory is required (or use --org)');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// Load config (file or defaults)
|
|
61
|
+
const config = loadConfig(options.config);
|
|
62
|
+
// CLI flags override config — first source is used for single-source compat
|
|
63
|
+
if (sources.length > 0)
|
|
64
|
+
config.source.path = sources[0];
|
|
65
|
+
if (options.output)
|
|
66
|
+
config.output.path = options.output;
|
|
67
|
+
if (options.provider) {
|
|
68
|
+
const validProviders = ['deepseek', 'openai', 'anthropic', 'google', 'ollama', 'openrouter'];
|
|
69
|
+
if (!validProviders.includes(options.provider)) {
|
|
70
|
+
console.error(`Error: Unknown provider "${options.provider}". Valid: ${validProviders.join(', ')}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
config.llm.provider = options.provider;
|
|
74
|
+
// Use provider's default model unless explicitly specified
|
|
75
|
+
if (!options.model) {
|
|
76
|
+
config.llm.model = DEFAULT_MODELS[config.llm.provider];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (options.model)
|
|
80
|
+
config.llm.model = options.model;
|
|
81
|
+
if (options.baseUrl)
|
|
82
|
+
config.llm.baseUrl = options.baseUrl;
|
|
83
|
+
// Validate
|
|
84
|
+
const errors = validateConfig(config);
|
|
85
|
+
if (errors.length > 0) {
|
|
86
|
+
console.error('Config errors:');
|
|
87
|
+
errors.forEach(e => console.error(` - ${e}`));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
// Determine LLM routing: own key (direct) or Skrypt proxy
|
|
91
|
+
let useProxy = false;
|
|
92
|
+
let proxySessionId;
|
|
93
|
+
let proxyApiKey;
|
|
94
|
+
if (!options.dryRun && config.llm.provider !== 'ollama') {
|
|
95
|
+
const { ok } = checkApiKey(config.llm.provider);
|
|
96
|
+
if (!ok) {
|
|
97
|
+
// No own API key — check if user is logged into Skrypt
|
|
98
|
+
const authConfig = await getAuthConfigAsync();
|
|
99
|
+
if (!authConfig.apiKey) {
|
|
100
|
+
console.error('\n No API key found. Choose one:\n');
|
|
101
|
+
console.error(` 1. Set your own key: export ${config.llm.provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'}=sk-...`);
|
|
102
|
+
console.error(' 2. Use Skrypt proxy: skrypt login\n');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
// User is logged in — use Skrypt proxy
|
|
106
|
+
proxyApiKey = authConfig.apiKey;
|
|
107
|
+
useProxy = true;
|
|
108
|
+
// Prompt for free tier confirmation
|
|
109
|
+
try {
|
|
110
|
+
const session = await startProxySession(proxyApiKey);
|
|
111
|
+
proxySessionId = session.sessionId;
|
|
112
|
+
if (session.plan === 'free') {
|
|
113
|
+
const limit = session.remaining + session.used;
|
|
114
|
+
const answer = await promptUser(`\n This will use your free generation for this month (${session.used}/${limit} used). Continue? (Y/n) `);
|
|
115
|
+
if (answer.toLowerCase() === 'n') {
|
|
116
|
+
// Complete the session so it doesn't count as abandoned
|
|
117
|
+
await completeProxySession(proxyApiKey, proxySessionId);
|
|
118
|
+
console.log('\n Cancelled. Set your own API key or upgrade to Pro: https://skrypt.sh/pro\n');
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error(`\n ${err instanceof Error ? err.message : 'Failed to start proxy session'}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Pro-gated flags
|
|
130
|
+
if (options.verify) {
|
|
131
|
+
if (!await requirePro('generate --verify')) {
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (options.smartStructure) {
|
|
136
|
+
if (!await requirePro('generate --smart-structure')) {
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// First-run security notice
|
|
141
|
+
showSecurityNotice();
|
|
142
|
+
console.log('skrypt generate');
|
|
143
|
+
if (options.org) {
|
|
144
|
+
console.log(` source: org:${options.org}`);
|
|
145
|
+
}
|
|
146
|
+
else if (sources.length > 1) {
|
|
147
|
+
console.log(` sources: ${sources.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(` source: ${config.source.path}`);
|
|
151
|
+
}
|
|
152
|
+
console.log(` output: ${config.output.path}`);
|
|
153
|
+
console.log(` provider: ${config.llm.provider}`);
|
|
154
|
+
console.log(` model: ${config.llm.model}`);
|
|
155
|
+
if (config.llm.baseUrl) {
|
|
156
|
+
console.log(` base url: ${config.llm.baseUrl}`);
|
|
157
|
+
}
|
|
158
|
+
// Routing transparency
|
|
159
|
+
const providerEnvKeys = {
|
|
160
|
+
openai: 'OPENAI_API_KEY',
|
|
161
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
162
|
+
google: 'GOOGLE_API_KEY',
|
|
163
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
164
|
+
};
|
|
165
|
+
const providerKey = providerEnvKeys[config.llm.provider];
|
|
166
|
+
if (providerKey && process.env[providerKey]) {
|
|
167
|
+
console.log(` routing: direct to ${config.llm.provider} (BYOK — your key never touches Skrypt)`);
|
|
168
|
+
}
|
|
169
|
+
else if (config.llm.provider !== 'ollama') {
|
|
170
|
+
console.log(' routing: via Skrypt API proxy');
|
|
171
|
+
}
|
|
172
|
+
console.log('');
|
|
173
|
+
// Scan sources
|
|
174
|
+
const scanResult = await scanSources(sources, {
|
|
175
|
+
org: options.org,
|
|
176
|
+
repos: options.repos,
|
|
177
|
+
excludeRepos: options.excludeRepos,
|
|
178
|
+
publicOnly: options.publicOnly,
|
|
179
|
+
exclude: options.exclude,
|
|
180
|
+
}, config);
|
|
181
|
+
const { allElements, primarySourcePath, tempDirs, isMultiSource } = scanResult;
|
|
182
|
+
try {
|
|
183
|
+
if (allElements.length === 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Dry run - stop here
|
|
187
|
+
if (options.dryRun) {
|
|
188
|
+
console.log('\n[dry run - stopping before generation]');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Auto-read project context from README for richer doc generation
|
|
192
|
+
let projectContext;
|
|
193
|
+
const readmeCandidates = ['README.md', 'README.mdx', 'readme.md', 'README.rst', 'README.txt'];
|
|
194
|
+
for (const candidate of readmeCandidates) {
|
|
195
|
+
const readmePath = join(primarySourcePath, candidate);
|
|
196
|
+
if (existsSync(readmePath)) {
|
|
197
|
+
try {
|
|
198
|
+
const raw = readFileSync(readmePath, 'utf-8');
|
|
199
|
+
// Take the first ~1500 chars (intro/description section, not the whole file)
|
|
200
|
+
projectContext = raw.slice(0, 1500);
|
|
201
|
+
console.log(` Project context: loaded from ${candidate}`);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Skip if unreadable
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Also check parent directory (common for monorepos where source is a subdirectory)
|
|
210
|
+
if (!projectContext) {
|
|
211
|
+
const parentReadme = join(primarySourcePath, '..', 'README.md');
|
|
212
|
+
if (existsSync(parentReadme)) {
|
|
213
|
+
try {
|
|
214
|
+
projectContext = readFileSync(parentReadme, 'utf-8').slice(0, 1500);
|
|
215
|
+
console.log(' Project context: loaded from parent README.md');
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Skip
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Context Hub enrichment: fetch third-party API docs if chub is installed
|
|
223
|
+
let externalContext;
|
|
224
|
+
const allImports = [];
|
|
225
|
+
for (const el of allElements) {
|
|
226
|
+
if (el.imports?.length) {
|
|
227
|
+
allImports.push(...el.imports);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const chubIds = extractDependencyIds(allImports);
|
|
231
|
+
if (chubIds.length > 0 && isChubInstalled()) {
|
|
232
|
+
console.log(`\n Context Hub: fetching docs for ${chubIds.length} dependencies...`);
|
|
233
|
+
externalContext = fetchContextHubDocs(chubIds);
|
|
234
|
+
if (externalContext.size > 0) {
|
|
235
|
+
console.log(` Context Hub: enriching with ${externalContext.size} API references`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Step 2: Generate docs
|
|
239
|
+
console.log('\nStep 2: Generating documentation...');
|
|
240
|
+
const client = useProxy && proxyApiKey && proxySessionId
|
|
241
|
+
? new ProxyClient({ apiKey: proxyApiKey, sessionId: proxySessionId })
|
|
242
|
+
: createLLMClient({
|
|
243
|
+
provider: config.llm.provider,
|
|
244
|
+
model: config.llm.model,
|
|
245
|
+
baseUrl: config.llm.baseUrl
|
|
246
|
+
});
|
|
247
|
+
let lastElement = '';
|
|
248
|
+
const multiLanguage = options.multiLang ?? false;
|
|
249
|
+
if (multiLanguage) {
|
|
250
|
+
console.log(' mode: multi-language (TypeScript + Python)');
|
|
251
|
+
}
|
|
252
|
+
const genOptions = {
|
|
253
|
+
multiLanguage,
|
|
254
|
+
externalContext,
|
|
255
|
+
projectContext,
|
|
256
|
+
onProgress: (progress) => {
|
|
257
|
+
if (progress.element !== lastElement) {
|
|
258
|
+
if (lastElement)
|
|
259
|
+
console.log('');
|
|
260
|
+
lastElement = progress.element;
|
|
261
|
+
}
|
|
262
|
+
process.stdout.write(`\r [${progress.current}/${progress.total}] ${progress.element}: ${progress.status}`.padEnd(80));
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
// Step 3: Write output
|
|
266
|
+
const outputPath = resolve(config.output.path);
|
|
267
|
+
let filesWritten;
|
|
268
|
+
let totalDocs;
|
|
269
|
+
let docs;
|
|
270
|
+
let errorCount;
|
|
271
|
+
if (options.smartStructure) {
|
|
272
|
+
// Smart structure: LLM-planned page organization
|
|
273
|
+
console.log(' mode: smart-structure (user-journey organization)');
|
|
274
|
+
console.log('\n Planning documentation structure...');
|
|
275
|
+
const structure = await planSmartStructure(allElements, client);
|
|
276
|
+
console.log(` Planned ${structure.pages.length} page(s)`);
|
|
277
|
+
for (const page of structure.pages) {
|
|
278
|
+
console.log(` ${page.category}/${page.slug}: ${page.elements.length} elements`);
|
|
279
|
+
}
|
|
280
|
+
console.log('\nStep 3: Generating & writing structured documentation...');
|
|
281
|
+
const result = await generateWithStructure(structure, client, outputPath, genOptions);
|
|
282
|
+
filesWritten = result.filesWritten;
|
|
283
|
+
totalDocs = result.totalDocs;
|
|
284
|
+
docs = result.docs;
|
|
285
|
+
errorCount = docs.filter(d => d.error).length;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
docs = await generateForElements(allElements, client, genOptions);
|
|
289
|
+
console.log('\n');
|
|
290
|
+
console.log('Step 3: Writing documentation...');
|
|
291
|
+
const writeResult = await writeDocs(docs, outputPath, primarySourcePath, isMultiSource, {
|
|
292
|
+
byTopic: options.byTopic,
|
|
293
|
+
});
|
|
294
|
+
filesWritten = writeResult.filesWritten;
|
|
295
|
+
totalDocs = writeResult.totalDocs;
|
|
296
|
+
errorCount = writeResult.errorCount;
|
|
297
|
+
}
|
|
298
|
+
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
299
|
+
// Post-write assets: OpenAPI, llms.txt, AGENTS.md, manifest
|
|
300
|
+
await writeAssets(docs, allElements, outputPath, primarySourcePath, config.output.path, filesWritten, {
|
|
301
|
+
openapi: options.openapi,
|
|
302
|
+
projectName: options.projectName,
|
|
303
|
+
baseUrl: options.baseUrl,
|
|
304
|
+
agentsMd: options.agentsMd,
|
|
305
|
+
});
|
|
306
|
+
// Step 4: Verify code examples (if --verify)
|
|
307
|
+
if (options.verify) {
|
|
308
|
+
await verifyCodeExamples(outputPath, allElements, client, primarySourcePath, {
|
|
309
|
+
envFile: options.envFile,
|
|
310
|
+
maxVerifyIterations: options.maxVerifyIterations,
|
|
311
|
+
multiLanguage,
|
|
312
|
+
externalContext,
|
|
313
|
+
projectContext,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
// Step 5: Embedded QA (auto-fix then check)
|
|
317
|
+
console.log(`\nStep ${options.verify ? '5' : '4'}: Running QA checks...`);
|
|
318
|
+
const fixReport = fixQAIssues(outputPath);
|
|
319
|
+
printFixReport(fixReport);
|
|
320
|
+
const qaReport = runQA(outputPath);
|
|
321
|
+
printQAReport(qaReport);
|
|
322
|
+
// Context Hub export prompt (TTY only — skip in CI/piped mode)
|
|
323
|
+
if (process.stdin.isTTY) {
|
|
324
|
+
console.log('');
|
|
325
|
+
console.log(' Context Hub: Make your docs discoverable by AI coding agents.');
|
|
326
|
+
console.log(' Context Hub is a curated registry by Andrew Ng (7K+ stars)');
|
|
327
|
+
console.log(' https://github.com/andrewyng/context-hub');
|
|
328
|
+
console.log('');
|
|
329
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
330
|
+
const answer = await new Promise((resolve) => {
|
|
331
|
+
rl.question(' Export for Context Hub? (y/N) ', (ans) => {
|
|
332
|
+
rl.close();
|
|
333
|
+
resolve(ans.trim().toLowerCase());
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
if (answer === 'y' || answer === 'yes') {
|
|
337
|
+
const languages = multiLanguage ? ['typescript', 'python'] : ['typescript'];
|
|
338
|
+
const projName = options.projectName || basename(primarySourcePath);
|
|
339
|
+
const result = exportToContextHub(docs, outputPath, {
|
|
340
|
+
projectName: projName,
|
|
341
|
+
languages,
|
|
342
|
+
description: `API documentation for ${projName}`,
|
|
343
|
+
});
|
|
344
|
+
console.log(`\n Exported ${result.filesWritten} files to ${result.outputDir}`);
|
|
345
|
+
console.log(' See context-hub/README.md for submission instructions');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
console.log('\n=== Summary ===');
|
|
349
|
+
console.log(` Total elements: ${totalDocs}`);
|
|
350
|
+
console.log(` Generated: ${totalDocs - errorCount}`);
|
|
351
|
+
if (errorCount > 0) {
|
|
352
|
+
console.log(` Errors: ${errorCount}`);
|
|
353
|
+
}
|
|
354
|
+
console.log(` Duration: ${duration}s`);
|
|
355
|
+
console.log(` Output: ${outputPath}`);
|
|
356
|
+
if (errorCount > 0) {
|
|
357
|
+
console.log('\n Elements with errors:');
|
|
358
|
+
docs.filter(d => d.error).slice(0, 10).forEach(d => {
|
|
359
|
+
console.log(` - ${d.element.name}: ${d.error?.slice(0, 50)}`);
|
|
360
|
+
});
|
|
361
|
+
if (errorCount > 10) {
|
|
362
|
+
console.log(` ... and ${errorCount - 10} more`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
console.log('\nDone!');
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
// Complete proxy session (increments monthly usage counter)
|
|
369
|
+
if (useProxy && proxyApiKey && proxySessionId) {
|
|
370
|
+
try {
|
|
371
|
+
await completeProxySession(proxyApiKey, proxySessionId);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// Don't mask the original error
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Clean up temp directories from --org clones
|
|
378
|
+
for (const dir of tempDirs) {
|
|
379
|
+
try {
|
|
380
|
+
rmSync(dir, { recursive: true, force: true });
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Ignore cleanup errors
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
390
|
+
console.error(`Error: ${message}`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SourceEntry } from '../../config/types.js';
|
|
2
|
+
import { APIElement } from '../../scanner/index.js';
|
|
3
|
+
import type { Config } from '../../config/types.js';
|
|
4
|
+
interface ScanOptions {
|
|
5
|
+
org?: string;
|
|
6
|
+
repos?: string;
|
|
7
|
+
excludeRepos?: string;
|
|
8
|
+
publicOnly?: boolean;
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ScanResult {
|
|
13
|
+
allElements: APIElement[];
|
|
14
|
+
sourceEntries: SourceEntry[];
|
|
15
|
+
primarySourcePath: string;
|
|
16
|
+
tempDirs: string[];
|
|
17
|
+
isMultiSource: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse source arguments with optional labels.
|
|
21
|
+
* e.g. "./api:API" -> { path: "./api", label: "API" }
|
|
22
|
+
* e.g. "./src" -> { path: "./src" }
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseSourceArgs(args: string[]): SourceEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* Read .skryptignore patterns from source directory
|
|
27
|
+
*/
|
|
28
|
+
export declare function readIgnorePatterns(sourcePath: string): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Auto-detect OpenAPI spec file in source directory
|
|
31
|
+
*/
|
|
32
|
+
export declare function findOpenAPISpec(sourcePath: string): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Check if element should be excluded based on patterns
|
|
35
|
+
*/
|
|
36
|
+
export declare function shouldExcludeElement(element: APIElement, patterns: string[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Scan all sources (CLI args, --org, or config), apply filters, return elements.
|
|
39
|
+
*/
|
|
40
|
+
export declare function scanSources(sources: string[], options: ScanOptions, config: Config): Promise<ScanResult>;
|
|
41
|
+
export {};
|