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.
Files changed (110) hide show
  1. package/dist/auth/index.js +3 -3
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/cron.js +0 -4
  4. package/dist/commands/generate/index.d.ts +3 -0
  5. package/dist/commands/generate/index.js +393 -0
  6. package/dist/commands/generate/scan.d.ts +41 -0
  7. package/dist/commands/generate/scan.js +256 -0
  8. package/dist/commands/generate/verify.d.ts +14 -0
  9. package/dist/commands/generate/verify.js +122 -0
  10. package/dist/commands/generate/write.d.ts +25 -0
  11. package/dist/commands/generate/write.js +120 -0
  12. package/dist/commands/import.js +4 -1
  13. package/dist/commands/llms-txt.js +6 -4
  14. package/dist/config/loader.d.ts +0 -1
  15. package/dist/config/loader.js +1 -1
  16. package/dist/generator/agents-md.d.ts +25 -0
  17. package/dist/generator/agents-md.js +122 -0
  18. package/dist/generator/index.d.ts +2 -0
  19. package/dist/generator/index.js +2 -0
  20. package/dist/generator/mdx-serializer.d.ts +11 -0
  21. package/dist/generator/mdx-serializer.js +135 -0
  22. package/dist/generator/organizer.d.ts +1 -16
  23. package/dist/generator/organizer.js +0 -38
  24. package/dist/generator/writer.js +5 -4
  25. package/dist/llm/proxy-client.d.ts +32 -0
  26. package/dist/llm/proxy-client.js +103 -0
  27. package/dist/scanner/csharp.d.ts +0 -4
  28. package/dist/scanner/csharp.js +9 -49
  29. package/dist/scanner/go.d.ts +0 -3
  30. package/dist/scanner/go.js +8 -35
  31. package/dist/scanner/java.d.ts +0 -4
  32. package/dist/scanner/java.js +9 -49
  33. package/dist/scanner/kotlin.d.ts +0 -3
  34. package/dist/scanner/kotlin.js +6 -33
  35. package/dist/scanner/php.d.ts +0 -10
  36. package/dist/scanner/php.js +11 -55
  37. package/dist/scanner/ruby.d.ts +0 -3
  38. package/dist/scanner/ruby.js +8 -38
  39. package/dist/scanner/rust.d.ts +0 -3
  40. package/dist/scanner/rust.js +10 -37
  41. package/dist/scanner/swift.d.ts +0 -3
  42. package/dist/scanner/swift.js +8 -35
  43. package/dist/scanner/utils.d.ts +41 -0
  44. package/dist/scanner/utils.js +97 -0
  45. package/dist/template/docs.json +5 -2
  46. package/dist/template/next.config.mjs +31 -0
  47. package/dist/template/package.json +5 -3
  48. package/dist/template/src/app/layout.tsx +13 -13
  49. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  50. package/dist/template/src/app/llms.txt/route.ts +29 -0
  51. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  52. package/dist/template/src/app/reference/route.ts +22 -18
  53. package/dist/template/src/app/sitemap.ts +1 -1
  54. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  55. package/dist/template/src/components/ai-chat.tsx +20 -193
  56. package/dist/template/src/components/mdx/index.tsx +27 -4
  57. package/dist/template/src/lib/fonts.ts +135 -0
  58. package/dist/template/src/middleware.ts +101 -0
  59. package/dist/template/src/styles/globals.css +28 -20
  60. package/dist/utils/files.d.ts +0 -8
  61. package/dist/utils/files.js +0 -33
  62. package/package.json +1 -1
  63. package/dist/autofix/autofix.test.d.ts +0 -1
  64. package/dist/autofix/autofix.test.js +0 -487
  65. package/dist/commands/generate.d.ts +0 -9
  66. package/dist/commands/generate.js +0 -739
  67. package/dist/generator/generator.test.d.ts +0 -1
  68. package/dist/generator/generator.test.js +0 -259
  69. package/dist/generator/writer.test.d.ts +0 -1
  70. package/dist/generator/writer.test.js +0 -411
  71. package/dist/llm/llm.manual-test.d.ts +0 -1
  72. package/dist/llm/llm.manual-test.js +0 -112
  73. package/dist/llm/llm.mock-test.d.ts +0 -4
  74. package/dist/llm/llm.mock-test.js +0 -79
  75. package/dist/plugins/index.d.ts +0 -47
  76. package/dist/plugins/index.js +0 -181
  77. package/dist/scanner/content-type.test.d.ts +0 -1
  78. package/dist/scanner/content-type.test.js +0 -231
  79. package/dist/scanner/integration.test.d.ts +0 -4
  80. package/dist/scanner/integration.test.js +0 -180
  81. package/dist/scanner/scanner.test.d.ts +0 -1
  82. package/dist/scanner/scanner.test.js +0 -210
  83. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  84. package/dist/scanner/typescript.manual-test.js +0 -112
  85. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  86. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  87. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  88. package/dist/template/src/app/docs/config/page.mdx +0 -428
  89. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  90. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  91. package/dist/template/src/app/docs/generator/generator.md +0 -504
  92. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  93. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  94. package/dist/template/src/app/docs/github/page.mdx +0 -502
  95. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  96. package/dist/template/src/app/docs/llm/index.md +0 -471
  97. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  98. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  99. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  100. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  101. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  102. package/dist/template/src/app/docs/scanner/index.md +0 -212
  103. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  104. package/dist/template/src/app/docs/scanner/python.md +0 -469
  105. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  106. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  107. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  108. package/dist/template/src/app/icon.tsx +0 -29
  109. package/dist/utils/validation.d.ts +0 -1
  110. package/dist/utils/validation.js +0 -12
@@ -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://api.skrypt.sh';
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}/v1/plan`, {
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';
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export { parseSourceArgs } from './scan.js';
3
+ export declare const generateCommand: Command;
@@ -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 {};