skrypt-ai 0.6.1 → 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.
Files changed (180) hide show
  1. package/dist/audit/doc-parser.d.ts +5 -0
  2. package/dist/audit/doc-parser.js +106 -0
  3. package/dist/audit/index.d.ts +4 -0
  4. package/dist/audit/index.js +4 -0
  5. package/dist/audit/matcher.d.ts +6 -0
  6. package/dist/audit/matcher.js +94 -0
  7. package/dist/audit/reporter.d.ts +9 -0
  8. package/dist/audit/reporter.js +106 -0
  9. package/dist/audit/types.d.ts +37 -0
  10. package/dist/auth/index.js +6 -4
  11. package/dist/cli.js +12 -2
  12. package/dist/commands/audit.d.ts +2 -0
  13. package/dist/commands/audit.js +59 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +73 -0
  16. package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
  17. package/dist/commands/generate/index.js +393 -0
  18. package/dist/commands/generate/scan.d.ts +41 -0
  19. package/dist/commands/generate/scan.js +256 -0
  20. package/dist/commands/generate/verify.d.ts +14 -0
  21. package/dist/commands/generate/verify.js +122 -0
  22. package/dist/commands/generate/write.d.ts +25 -0
  23. package/dist/commands/generate/write.js +120 -0
  24. package/dist/commands/import.js +4 -1
  25. package/dist/commands/llms-txt.js +6 -4
  26. package/dist/commands/refresh.d.ts +2 -0
  27. package/dist/commands/refresh.js +158 -0
  28. package/dist/commands/review.d.ts +2 -0
  29. package/dist/commands/review.js +110 -0
  30. package/dist/commands/test.js +177 -236
  31. package/dist/commands/watch.js +29 -20
  32. package/dist/config/loader.d.ts +6 -2
  33. package/dist/config/loader.js +39 -3
  34. package/dist/config/types.d.ts +7 -0
  35. package/dist/generator/agents-md.d.ts +25 -0
  36. package/dist/generator/agents-md.js +122 -0
  37. package/dist/generator/generator.js +2 -1
  38. package/dist/generator/index.d.ts +2 -0
  39. package/dist/generator/index.js +2 -0
  40. package/dist/generator/mdx-serializer.d.ts +11 -0
  41. package/dist/generator/mdx-serializer.js +135 -0
  42. package/dist/generator/organizer.d.ts +1 -16
  43. package/dist/generator/organizer.js +0 -38
  44. package/dist/generator/types.d.ts +3 -0
  45. package/dist/generator/writer.js +65 -32
  46. package/dist/github/org-discovery.d.ts +17 -0
  47. package/dist/github/org-discovery.js +93 -0
  48. package/dist/llm/index.d.ts +2 -0
  49. package/dist/llm/index.js +8 -2
  50. package/dist/llm/proxy-client.d.ts +32 -0
  51. package/dist/llm/proxy-client.js +103 -0
  52. package/dist/next-actions/actions.d.ts +2 -0
  53. package/dist/next-actions/actions.js +190 -0
  54. package/dist/next-actions/index.d.ts +6 -0
  55. package/dist/next-actions/index.js +39 -0
  56. package/dist/next-actions/setup.d.ts +2 -0
  57. package/dist/next-actions/setup.js +72 -0
  58. package/dist/next-actions/state.d.ts +7 -0
  59. package/dist/next-actions/state.js +68 -0
  60. package/dist/next-actions/suggest.d.ts +3 -0
  61. package/dist/next-actions/suggest.js +47 -0
  62. package/dist/next-actions/types.d.ts +26 -0
  63. package/dist/refresh/differ.d.ts +9 -0
  64. package/dist/refresh/differ.js +67 -0
  65. package/dist/refresh/index.d.ts +4 -0
  66. package/dist/refresh/index.js +4 -0
  67. package/dist/refresh/manifest.d.ts +18 -0
  68. package/dist/refresh/manifest.js +71 -0
  69. package/dist/refresh/splicer.d.ts +9 -0
  70. package/dist/refresh/splicer.js +50 -0
  71. package/dist/refresh/types.d.ts +37 -0
  72. package/dist/review/index.d.ts +8 -0
  73. package/dist/review/index.js +94 -0
  74. package/dist/review/parser.d.ts +16 -0
  75. package/dist/review/parser.js +95 -0
  76. package/dist/review/types.d.ts +18 -0
  77. package/dist/scanner/csharp.d.ts +0 -4
  78. package/dist/scanner/csharp.js +9 -49
  79. package/dist/scanner/go.d.ts +0 -3
  80. package/dist/scanner/go.js +8 -35
  81. package/dist/scanner/java.d.ts +0 -4
  82. package/dist/scanner/java.js +9 -49
  83. package/dist/scanner/kotlin.d.ts +0 -3
  84. package/dist/scanner/kotlin.js +6 -33
  85. package/dist/scanner/php.d.ts +0 -10
  86. package/dist/scanner/php.js +11 -55
  87. package/dist/scanner/ruby.d.ts +0 -3
  88. package/dist/scanner/ruby.js +8 -38
  89. package/dist/scanner/rust.d.ts +0 -3
  90. package/dist/scanner/rust.js +10 -37
  91. package/dist/scanner/swift.d.ts +0 -3
  92. package/dist/scanner/swift.js +8 -35
  93. package/dist/scanner/types.d.ts +2 -0
  94. package/dist/scanner/utils.d.ts +41 -0
  95. package/dist/scanner/utils.js +97 -0
  96. package/dist/structure/index.d.ts +19 -0
  97. package/dist/structure/index.js +92 -0
  98. package/dist/structure/planner.d.ts +8 -0
  99. package/dist/structure/planner.js +180 -0
  100. package/dist/structure/topology.d.ts +16 -0
  101. package/dist/structure/topology.js +49 -0
  102. package/dist/structure/types.d.ts +26 -0
  103. package/dist/template/docs.json +5 -2
  104. package/dist/template/next.config.mjs +31 -0
  105. package/dist/template/package.json +5 -3
  106. package/dist/template/src/app/layout.tsx +13 -13
  107. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  108. package/dist/template/src/app/llms.txt/route.ts +29 -0
  109. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  110. package/dist/template/src/app/reference/route.ts +22 -18
  111. package/dist/template/src/app/sitemap.ts +1 -1
  112. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  113. package/dist/template/src/components/ai-chat.tsx +20 -193
  114. package/dist/template/src/components/mdx/index.tsx +27 -4
  115. package/dist/template/src/lib/fonts.ts +135 -0
  116. package/dist/template/src/middleware.ts +101 -0
  117. package/dist/template/src/styles/globals.css +28 -20
  118. package/dist/testing/comparator.d.ts +7 -0
  119. package/dist/testing/comparator.js +77 -0
  120. package/dist/testing/docker.d.ts +21 -0
  121. package/dist/testing/docker.js +234 -0
  122. package/dist/testing/env.d.ts +16 -0
  123. package/dist/testing/env.js +58 -0
  124. package/dist/testing/extractor.d.ts +9 -0
  125. package/dist/testing/extractor.js +195 -0
  126. package/dist/testing/index.d.ts +6 -0
  127. package/dist/testing/index.js +6 -0
  128. package/dist/testing/runner.d.ts +5 -0
  129. package/dist/testing/runner.js +225 -0
  130. package/dist/testing/types.d.ts +58 -0
  131. package/dist/utils/files.d.ts +0 -8
  132. package/dist/utils/files.js +0 -33
  133. package/package.json +1 -1
  134. package/dist/autofix/autofix.test.js +0 -487
  135. package/dist/commands/generate.js +0 -445
  136. package/dist/generator/generator.test.js +0 -259
  137. package/dist/generator/writer.test.js +0 -411
  138. package/dist/llm/llm.manual-test.js +0 -112
  139. package/dist/llm/llm.mock-test.d.ts +0 -4
  140. package/dist/llm/llm.mock-test.js +0 -79
  141. package/dist/plugins/index.d.ts +0 -47
  142. package/dist/plugins/index.js +0 -181
  143. package/dist/scanner/content-type.test.js +0 -231
  144. package/dist/scanner/integration.test.d.ts +0 -4
  145. package/dist/scanner/integration.test.js +0 -180
  146. package/dist/scanner/scanner.test.js +0 -210
  147. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  148. package/dist/scanner/typescript.manual-test.js +0 -112
  149. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  150. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  151. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  152. package/dist/template/src/app/docs/config/page.mdx +0 -428
  153. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  154. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  155. package/dist/template/src/app/docs/generator/generator.md +0 -504
  156. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  157. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  158. package/dist/template/src/app/docs/github/page.mdx +0 -502
  159. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  160. package/dist/template/src/app/docs/llm/index.md +0 -471
  161. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  162. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  163. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  164. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  165. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  166. package/dist/template/src/app/docs/scanner/index.md +0 -212
  167. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  168. package/dist/template/src/app/docs/scanner/python.md +0 -469
  169. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  170. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  171. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  172. package/dist/template/src/app/icon.tsx +0 -29
  173. package/dist/utils/validation.d.ts +0 -1
  174. package/dist/utils/validation.js +0 -12
  175. /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
  176. /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
  177. /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
  178. /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
  179. /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
  180. /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
@@ -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 {};