skrypt-ai 0.5.0 → 0.6.1

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 (120) hide show
  1. package/dist/auth/index.js +8 -1
  2. package/dist/autofix/index.d.ts +0 -4
  3. package/dist/autofix/index.js +0 -21
  4. package/dist/capture/browser.d.ts +11 -0
  5. package/dist/capture/browser.js +173 -0
  6. package/dist/capture/diff.d.ts +23 -0
  7. package/dist/capture/diff.js +52 -0
  8. package/dist/capture/index.d.ts +23 -0
  9. package/dist/capture/index.js +210 -0
  10. package/dist/capture/naming.d.ts +17 -0
  11. package/dist/capture/naming.js +45 -0
  12. package/dist/capture/parser.d.ts +15 -0
  13. package/dist/capture/parser.js +80 -0
  14. package/dist/capture/types.d.ts +57 -0
  15. package/dist/capture/types.js +1 -0
  16. package/dist/cli.js +4 -0
  17. package/dist/commands/autofix.js +136 -120
  18. package/dist/commands/cron.js +58 -47
  19. package/dist/commands/deploy.js +123 -102
  20. package/dist/commands/generate.js +88 -6
  21. package/dist/commands/heal.d.ts +10 -0
  22. package/dist/commands/heal.js +201 -0
  23. package/dist/commands/i18n.js +146 -111
  24. package/dist/commands/lint.js +50 -44
  25. package/dist/commands/llms-txt.js +59 -49
  26. package/dist/commands/login.js +61 -43
  27. package/dist/commands/mcp.js +6 -0
  28. package/dist/commands/monitor.js +13 -8
  29. package/dist/commands/qa.d.ts +2 -0
  30. package/dist/commands/qa.js +43 -0
  31. package/dist/commands/review-pr.js +114 -103
  32. package/dist/commands/sdk.js +128 -122
  33. package/dist/commands/security.js +86 -80
  34. package/dist/commands/test.js +91 -92
  35. package/dist/commands/version.js +104 -75
  36. package/dist/commands/watch.js +130 -114
  37. package/dist/config/types.js +2 -2
  38. package/dist/context-hub/index.d.ts +23 -0
  39. package/dist/context-hub/index.js +179 -0
  40. package/dist/context-hub/mappings.d.ts +8 -0
  41. package/dist/context-hub/mappings.js +55 -0
  42. package/dist/context-hub/types.d.ts +33 -0
  43. package/dist/context-hub/types.js +1 -0
  44. package/dist/generator/generator.js +39 -6
  45. package/dist/generator/types.d.ts +7 -0
  46. package/dist/generator/writer.d.ts +3 -1
  47. package/dist/generator/writer.js +24 -4
  48. package/dist/llm/anthropic-client.d.ts +1 -0
  49. package/dist/llm/anthropic-client.js +3 -1
  50. package/dist/llm/index.d.ts +6 -4
  51. package/dist/llm/index.js +76 -261
  52. package/dist/llm/openai-client.d.ts +1 -0
  53. package/dist/llm/openai-client.js +7 -2
  54. package/dist/qa/checks.d.ts +10 -0
  55. package/dist/qa/checks.js +492 -0
  56. package/dist/qa/fixes.d.ts +30 -0
  57. package/dist/qa/fixes.js +277 -0
  58. package/dist/qa/index.d.ts +29 -0
  59. package/dist/qa/index.js +187 -0
  60. package/dist/qa/types.d.ts +24 -0
  61. package/dist/qa/types.js +1 -0
  62. package/dist/scanner/csharp.d.ts +23 -0
  63. package/dist/scanner/csharp.js +421 -0
  64. package/dist/scanner/index.js +16 -2
  65. package/dist/scanner/java.d.ts +39 -0
  66. package/dist/scanner/java.js +318 -0
  67. package/dist/scanner/kotlin.d.ts +23 -0
  68. package/dist/scanner/kotlin.js +389 -0
  69. package/dist/scanner/php.d.ts +57 -0
  70. package/dist/scanner/php.js +351 -0
  71. package/dist/scanner/ruby.d.ts +36 -0
  72. package/dist/scanner/ruby.js +431 -0
  73. package/dist/scanner/swift.d.ts +25 -0
  74. package/dist/scanner/swift.js +392 -0
  75. package/dist/scanner/types.d.ts +1 -1
  76. package/dist/template/content/docs/_navigation.json +46 -0
  77. package/dist/template/content/docs/_sidebars.json +684 -0
  78. package/dist/template/content/docs/core.md +4544 -0
  79. package/dist/template/content/docs/index.mdx +89 -0
  80. package/dist/template/content/docs/integrations.md +1158 -0
  81. package/dist/template/content/docs/llms-full.md +403 -0
  82. package/dist/template/content/docs/llms.txt +4588 -0
  83. package/dist/template/content/docs/other.md +10379 -0
  84. package/dist/template/content/docs/tools.md +746 -0
  85. package/dist/template/content/docs/types.md +531 -0
  86. package/dist/template/docs.json +13 -11
  87. package/dist/template/mdx-components.tsx +27 -2
  88. package/dist/template/package.json +6 -0
  89. package/dist/template/public/search-index.json +1 -1
  90. package/dist/template/scripts/build-search-index.mjs +84 -6
  91. package/dist/template/src/app/api/chat/route.ts +83 -128
  92. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  93. package/dist/template/src/app/docs/llms-full.md +151 -4
  94. package/dist/template/src/app/docs/llms.txt +2464 -847
  95. package/dist/template/src/app/docs/page.mdx +48 -38
  96. package/dist/template/src/app/layout.tsx +3 -1
  97. package/dist/template/src/app/page.tsx +22 -8
  98. package/dist/template/src/components/ai-chat.tsx +73 -64
  99. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  100. package/dist/template/src/components/copy-button.tsx +13 -9
  101. package/dist/template/src/components/copy-page-button.tsx +54 -0
  102. package/dist/template/src/components/docs-layout.tsx +37 -25
  103. package/dist/template/src/components/header.tsx +51 -10
  104. package/dist/template/src/components/mdx/card.tsx +17 -3
  105. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  106. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  107. package/dist/template/src/components/mdx/heading.tsx +15 -2
  108. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  109. package/dist/template/src/components/mdx/index.tsx +2 -0
  110. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  111. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  112. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  113. package/dist/template/src/components/sidebar.tsx +12 -18
  114. package/dist/template/src/components/table-of-contents.tsx +9 -0
  115. package/dist/template/src/lib/highlight.ts +3 -88
  116. package/dist/template/src/lib/navigation.ts +159 -0
  117. package/dist/template/src/styles/globals.css +17 -6
  118. package/dist/utils/validation.d.ts +0 -3
  119. package/dist/utils/validation.js +0 -26
  120. package/package.json +3 -2
@@ -7,6 +7,7 @@ import { DEFAULT_MODELS } from '../config/types.js';
7
7
  import { scanDirectory } from '../scanner/index.js';
8
8
  import { createLLMClient } from '../llm/index.js';
9
9
  import { generateForElements, groupDocsByFile, writeDocsToDirectory } from '../generator/index.js';
10
+ import { runQA, printQAReport, fixQAIssues, printFixReport } from '../qa/index.js';
10
11
  export const watchCommand = new Command('watch')
11
12
  .description('Watch source files and regenerate docs on changes')
12
13
  .argument('<source>', 'Source directory to watch')
@@ -16,127 +17,142 @@ export const watchCommand = new Command('watch')
16
17
  .option('--model <name>', 'LLM model name')
17
18
  .option('--debounce <ms>', 'Debounce time in milliseconds', '1000')
18
19
  .action(async (source, options) => {
19
- const config = loadConfig(options.config);
20
- if (source)
21
- config.source.path = source;
22
- if (options.output)
23
- config.output.path = options.output;
24
- if (options.provider) {
25
- config.llm.provider = options.provider;
26
- if (!options.model) {
27
- config.llm.model = DEFAULT_MODELS[config.llm.provider];
20
+ try {
21
+ const config = loadConfig(options.config);
22
+ if (source)
23
+ config.source.path = source;
24
+ if (options.output)
25
+ config.output.path = options.output;
26
+ if (options.provider) {
27
+ config.llm.provider = options.provider;
28
+ if (!options.model) {
29
+ config.llm.model = DEFAULT_MODELS[config.llm.provider];
30
+ }
28
31
  }
29
- }
30
- if (options.model)
31
- config.llm.model = options.model;
32
- const errors = validateConfig(config);
33
- if (errors.length > 0) {
34
- console.error('Config errors:', errors);
35
- process.exit(1);
36
- }
37
- const { ok, envKey } = checkApiKey(config.llm.provider);
38
- if (!ok && envKey) {
39
- console.error(`Error: ${envKey} required`);
40
- process.exit(1);
41
- }
42
- const sourcePath = resolve(config.source.path);
43
- const outputPath = resolve(config.output.path);
44
- if (!existsSync(sourcePath)) {
45
- console.error(`Source not found: ${sourcePath}`);
46
- process.exit(1);
47
- }
48
- console.log('skrypt watch');
49
- console.log(` source: ${sourcePath}`);
50
- console.log(` output: ${outputPath}`);
51
- console.log(` provider: ${config.llm.provider}`);
52
- console.log('');
53
- const client = createLLMClient({
54
- provider: config.llm.provider,
55
- model: config.llm.model,
56
- baseUrl: config.llm.baseUrl
57
- });
58
- let generating = false;
59
- let pendingGenerate = false;
60
- const debounceMs = parseInt(options.debounce);
61
- async function generate() {
62
- if (generating) {
63
- pendingGenerate = true;
64
- return;
32
+ if (options.model)
33
+ config.llm.model = options.model;
34
+ const errors = validateConfig(config);
35
+ if (errors.length > 0) {
36
+ console.error('Config errors:', errors);
37
+ process.exit(1);
38
+ }
39
+ const { ok, envKey } = checkApiKey(config.llm.provider);
40
+ if (!ok && envKey) {
41
+ console.error(`Error: ${envKey} required`);
42
+ process.exit(1);
65
43
  }
66
- generating = true;
67
- const startTime = Date.now();
68
- try {
69
- console.log('\n[' + new Date().toLocaleTimeString() + '] Regenerating docs...');
70
- const scanResult = await scanDirectory(sourcePath, {
71
- include: config.source.include,
72
- exclude: config.source.exclude,
73
- });
74
- if (scanResult.totalElements === 0) {
75
- console.log(' No elements found');
44
+ const sourcePath = resolve(config.source.path);
45
+ const outputPath = resolve(config.output.path);
46
+ if (!existsSync(sourcePath)) {
47
+ console.error(`Source not found: ${sourcePath}`);
48
+ process.exit(1);
49
+ }
50
+ console.log('skrypt watch');
51
+ console.log(` source: ${sourcePath}`);
52
+ console.log(` output: ${outputPath}`);
53
+ console.log(` provider: ${config.llm.provider}`);
54
+ console.log('');
55
+ const client = createLLMClient({
56
+ provider: config.llm.provider,
57
+ model: config.llm.model,
58
+ baseUrl: config.llm.baseUrl
59
+ });
60
+ let generating = false;
61
+ let pendingGenerate = false;
62
+ const debounceMs = parseInt(options.debounce, 10);
63
+ if (isNaN(debounceMs) || debounceMs < 1) {
64
+ console.error('Error: --debounce must be a positive number');
65
+ process.exit(1);
66
+ }
67
+ async function generate() {
68
+ if (generating) {
69
+ pendingGenerate = true;
76
70
  return;
77
71
  }
78
- const allElements = [];
79
- for (const file of scanResult.files) {
80
- allElements.push(...file.elements);
72
+ generating = true;
73
+ const startTime = Date.now();
74
+ try {
75
+ console.log('\n[' + new Date().toLocaleTimeString() + '] Regenerating docs...');
76
+ const scanResult = await scanDirectory(sourcePath, {
77
+ include: config.source.include,
78
+ exclude: config.source.exclude,
79
+ });
80
+ if (scanResult.totalElements === 0) {
81
+ console.log(' No elements found');
82
+ return;
83
+ }
84
+ const allElements = [];
85
+ for (const file of scanResult.files) {
86
+ allElements.push(...file.elements);
87
+ }
88
+ const docs = await generateForElements(allElements, client, {
89
+ onProgress: (p) => {
90
+ process.stdout.write(`\r [${p.current}/${p.total}] ${p.element}`.padEnd(60));
91
+ }
92
+ });
93
+ const fileResults = groupDocsByFile(docs);
94
+ await writeDocsToDirectory(fileResults, outputPath, sourcePath);
95
+ // Auto-fix then run QA on generated output
96
+ const fixReport = fixQAIssues(outputPath);
97
+ printFixReport(fixReport);
98
+ const qaReport = runQA(outputPath);
99
+ printQAReport(qaReport);
100
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
101
+ console.log(`\n Done in ${duration}s`);
102
+ }
103
+ catch (err) {
104
+ console.error('\n Error:', err);
81
105
  }
82
- const docs = await generateForElements(allElements, client, {
83
- onProgress: (p) => {
84
- process.stdout.write(`\r [${p.current}/${p.total}] ${p.element}`.padEnd(60));
106
+ finally {
107
+ generating = false;
108
+ if (pendingGenerate) {
109
+ pendingGenerate = false;
110
+ setTimeout(generate, debounceMs);
85
111
  }
86
- });
87
- const fileResults = groupDocsByFile(docs);
88
- await writeDocsToDirectory(fileResults, outputPath, sourcePath);
89
- const duration = ((Date.now() - startTime) / 1000).toFixed(1);
90
- console.log(`\n Done in ${duration}s`);
91
- }
92
- catch (err) {
93
- console.error('\n Error:', err);
94
- }
95
- finally {
96
- generating = false;
97
- if (pendingGenerate) {
98
- pendingGenerate = false;
99
- setTimeout(generate, debounceMs);
100
112
  }
101
113
  }
114
+ // Initial generation
115
+ await generate();
116
+ // Watch for changes
117
+ console.log('\nWatching for changes... (Ctrl+C to stop)');
118
+ let debounceTimer = null;
119
+ const watcher = watch(sourcePath, {
120
+ ignored: [
121
+ '**/node_modules/**',
122
+ '**/.git/**',
123
+ '**/dist/**',
124
+ '**/__pycache__/**',
125
+ ],
126
+ persistent: true,
127
+ ignoreInitial: true,
128
+ });
129
+ watcher.on('change', (path) => {
130
+ console.log(` Changed: ${path}`);
131
+ if (debounceTimer)
132
+ clearTimeout(debounceTimer);
133
+ debounceTimer = setTimeout(generate, debounceMs);
134
+ });
135
+ watcher.on('add', (path) => {
136
+ console.log(` Added: ${path}`);
137
+ if (debounceTimer)
138
+ clearTimeout(debounceTimer);
139
+ debounceTimer = setTimeout(generate, debounceMs);
140
+ });
141
+ watcher.on('unlink', (path) => {
142
+ console.log(` Removed: ${path}`);
143
+ if (debounceTimer)
144
+ clearTimeout(debounceTimer);
145
+ debounceTimer = setTimeout(generate, debounceMs);
146
+ });
147
+ // Keep process alive
148
+ process.on('SIGINT', () => {
149
+ console.log('\nStopping watch...');
150
+ watcher.close();
151
+ process.exit(0);
152
+ });
153
+ }
154
+ catch (err) {
155
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
156
+ process.exit(1);
102
157
  }
103
- // Initial generation
104
- await generate();
105
- // Watch for changes
106
- console.log('\nWatching for changes... (Ctrl+C to stop)');
107
- let debounceTimer = null;
108
- const watcher = watch(sourcePath, {
109
- ignored: [
110
- '**/node_modules/**',
111
- '**/.git/**',
112
- '**/dist/**',
113
- '**/__pycache__/**',
114
- ],
115
- persistent: true,
116
- ignoreInitial: true,
117
- });
118
- watcher.on('change', (path) => {
119
- console.log(` Changed: ${path}`);
120
- if (debounceTimer)
121
- clearTimeout(debounceTimer);
122
- debounceTimer = setTimeout(generate, debounceMs);
123
- });
124
- watcher.on('add', (path) => {
125
- console.log(` Added: ${path}`);
126
- if (debounceTimer)
127
- clearTimeout(debounceTimer);
128
- debounceTimer = setTimeout(generate, debounceMs);
129
- });
130
- watcher.on('unlink', (path) => {
131
- console.log(` Removed: ${path}`);
132
- if (debounceTimer)
133
- clearTimeout(debounceTimer);
134
- debounceTimer = setTimeout(generate, debounceMs);
135
- });
136
- // Keep process alive
137
- process.on('SIGINT', () => {
138
- console.log('\nStopping watch...');
139
- watcher.close();
140
- process.exit(0);
141
- });
142
158
  });
@@ -20,8 +20,8 @@ export const DEFAULT_CONFIG = {
20
20
  version: 1,
21
21
  source: {
22
22
  path: './src',
23
- include: ['**/*.py', '**/*.js', '**/*.ts', '**/*.go', '**/*.rs'],
24
- exclude: ['**/node_modules/**', '**/__pycache__/**', '**/dist/**', '**/target/**']
23
+ include: ['**/*.py', '**/*.js', '**/*.ts', '**/*.go', '**/*.rs', '**/*.java', '**/*.cs', '**/*.php', '**/*.kt', '**/*.kts', '**/*.swift', '**/*.rb'],
24
+ exclude: ['**/node_modules/**', '**/__pycache__/**', '**/dist/**', '**/target/**', '**/vendor/**', '**/build/**', '**/bin/**', '**/obj/**', '**/.build/**']
25
25
  },
26
26
  output: {
27
27
  path: './docs',
@@ -0,0 +1,23 @@
1
+ import type { ContextHubExportOptions, ContextHubExportResult, ContextHubContext } from './types.js';
2
+ import type { GeneratedDoc } from '../generator/types.js';
3
+ export type { ContextHubExportOptions, ContextHubExportResult, ContextHubContext };
4
+ /**
5
+ * Check if the `chub` CLI is installed
6
+ */
7
+ export declare function isChubInstalled(): boolean;
8
+ /**
9
+ * Extract Context Hub IDs from import lines by matching against known mappings
10
+ */
11
+ export declare function extractDependencyIds(imports: string[]): string[];
12
+ /**
13
+ * Fetch docs from Context Hub for the given IDs
14
+ */
15
+ export declare function fetchContextHubDocs(chubIds: string[], language?: string): Map<string, string>;
16
+ /**
17
+ * Given an element's imports, return relevant fetched docs as a single string
18
+ */
19
+ export declare function matchImportsToContext(imports: string[], contextMap: Map<string, string>): string | undefined;
20
+ /**
21
+ * Export generated docs to Context Hub format
22
+ */
23
+ export declare function exportToContextHub(docs: GeneratedDoc[], outputDir: string, options: ContextHubExportOptions): ContextHubExportResult;
@@ -0,0 +1,179 @@
1
+ import { spawnSync } from 'child_process';
2
+ import { mkdirSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { IMPORT_MAPPINGS } from './mappings.js';
5
+ /**
6
+ * Check if the `chub` CLI is installed
7
+ */
8
+ export function isChubInstalled() {
9
+ try {
10
+ const result = spawnSync('chub', ['--version'], {
11
+ encoding: 'utf-8',
12
+ stdio: ['pipe', 'pipe', 'pipe'],
13
+ timeout: 5000,
14
+ });
15
+ return result.status === 0;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ /**
22
+ * Extract Context Hub IDs from import lines by matching against known mappings
23
+ */
24
+ export function extractDependencyIds(imports) {
25
+ const hits = new Map();
26
+ for (const line of imports) {
27
+ for (const mapping of IMPORT_MAPPINGS) {
28
+ if (mapping.pattern.test(line)) {
29
+ hits.set(mapping.chubId, (hits.get(mapping.chubId) || 0) + 1);
30
+ }
31
+ }
32
+ }
33
+ // Sort by frequency (most imported first), cap at 10
34
+ return [...hits.entries()]
35
+ .sort((a, b) => b[1] - a[1])
36
+ .slice(0, 10)
37
+ .map(([id]) => id);
38
+ }
39
+ /**
40
+ * Fetch docs from Context Hub for the given IDs
41
+ */
42
+ export function fetchContextHubDocs(chubIds, language) {
43
+ const results = new Map();
44
+ for (const id of chubIds) {
45
+ try {
46
+ const args = ['get', id];
47
+ if (language) {
48
+ args.push('--lang', language);
49
+ }
50
+ const result = spawnSync('chub', args, {
51
+ encoding: 'utf-8',
52
+ stdio: ['pipe', 'pipe', 'pipe'],
53
+ timeout: 15000,
54
+ });
55
+ if (result.status === 0 && result.stdout?.trim()) {
56
+ results.set(id, result.stdout.trim());
57
+ }
58
+ }
59
+ catch {
60
+ // Skip failures silently
61
+ }
62
+ }
63
+ return results;
64
+ }
65
+ /**
66
+ * Given an element's imports, return relevant fetched docs as a single string
67
+ */
68
+ export function matchImportsToContext(imports, contextMap) {
69
+ if (!imports.length || !contextMap.size)
70
+ return undefined;
71
+ const ids = extractDependencyIds(imports);
72
+ const matched = [];
73
+ for (const id of ids) {
74
+ const content = contextMap.get(id);
75
+ if (content) {
76
+ matched.push(`## ${id}\n${content}`);
77
+ }
78
+ }
79
+ if (!matched.length)
80
+ return undefined;
81
+ return matched.join('\n\n');
82
+ }
83
+ /**
84
+ * Export generated docs to Context Hub format
85
+ */
86
+ export function exportToContextHub(docs, outputDir, options) {
87
+ const { projectName, languages, version, description } = options;
88
+ const isMultiLang = languages.length > 1;
89
+ // Create directory structure
90
+ const baseDir = join(outputDir, 'context-hub', projectName, 'docs', 'api');
91
+ let filesWritten = 0;
92
+ // Group docs by element kind for organized output
93
+ const byKind = new Map();
94
+ for (const doc of docs) {
95
+ if (doc.error)
96
+ continue;
97
+ const kind = doc.element.kind;
98
+ if (!byKind.has(kind))
99
+ byKind.set(kind, []);
100
+ byKind.get(kind).push(doc);
101
+ }
102
+ const buildContent = (lang) => {
103
+ const today = new Date().toISOString().split('T')[0];
104
+ let content = '---\n';
105
+ content += `name: "api"\n`;
106
+ content += `description: "${description || `API documentation for ${projectName}`}"\n`;
107
+ content += `metadata:\n`;
108
+ content += ` languages: "${lang || languages.join(', ')}"\n`;
109
+ content += ` versions: "${version || '1.0.0'}"\n`;
110
+ content += ` revision: 1\n`;
111
+ content += ` updated-on: "${today}"\n`;
112
+ content += ` source: community\n`;
113
+ content += ` tags: "${projectName},api,documentation"\n`;
114
+ content += '---\n\n';
115
+ content += `# ${projectName} API Documentation\n\n`;
116
+ for (const [kind, kindDocs] of byKind) {
117
+ const heading = kind.charAt(0).toUpperCase() + kind.slice(1) + 's';
118
+ content += `## ${heading}\n\n`;
119
+ for (const doc of kindDocs) {
120
+ content += `### ${doc.element.name}\n\n`;
121
+ if (doc.markdown) {
122
+ content += doc.markdown + '\n\n';
123
+ }
124
+ // Pick the right code example for this language
125
+ if (lang === 'python' && doc.pythonExample) {
126
+ content += `\`\`\`python\n${doc.pythonExample}\n\`\`\`\n\n`;
127
+ }
128
+ else if (lang === 'javascript' || lang === 'typescript') {
129
+ const code = doc.typescriptExample || doc.codeExample;
130
+ if (code) {
131
+ content += `\`\`\`typescript\n${code}\n\`\`\`\n\n`;
132
+ }
133
+ }
134
+ else if (doc.codeExample) {
135
+ content += `\`\`\`${doc.codeLanguage}\n${doc.codeExample}\n\`\`\`\n\n`;
136
+ }
137
+ }
138
+ }
139
+ return content;
140
+ };
141
+ if (isMultiLang) {
142
+ // Write per-language files
143
+ for (const lang of languages) {
144
+ const langDir = join(baseDir, lang);
145
+ mkdirSync(langDir, { recursive: true });
146
+ writeFileSync(join(langDir, 'DOC.md'), buildContent(lang));
147
+ filesWritten++;
148
+ }
149
+ }
150
+ else {
151
+ // Single-language file
152
+ mkdirSync(baseDir, { recursive: true });
153
+ writeFileSync(join(baseDir, 'DOC.md'), buildContent(languages[0]));
154
+ filesWritten++;
155
+ }
156
+ // Write submission instructions README
157
+ const readmeDir = join(outputDir, 'context-hub');
158
+ const readme = `# Context Hub Submission
159
+
160
+ Your documentation has been exported in Context Hub format.
161
+
162
+ ## How to submit
163
+
164
+ 1. Fork https://github.com/andrewyng/context-hub
165
+ 2. Copy the \`${projectName}/\` directory into the \`contexts/\` folder of your fork
166
+ 3. Open a pull request with a brief description of your project
167
+
168
+ ## What is Context Hub?
169
+
170
+ Context Hub is a curated registry of machine-readable API docs for AI coding agents.
171
+ Once submitted, any developer using \`chub\` will be able to pull your docs directly
172
+ into their AI coding workflow.
173
+
174
+ Learn more: https://github.com/andrewyng/context-hub
175
+ `;
176
+ writeFileSync(join(readmeDir, 'README.md'), readme);
177
+ filesWritten++;
178
+ return { outputDir: readmeDir, filesWritten, languages };
179
+ }
@@ -0,0 +1,8 @@
1
+ import { ImportMapping } from './types.js';
2
+ /**
3
+ * Static registry mapping import patterns to Context Hub IDs.
4
+ *
5
+ * Covers ES6 imports, CommonJS require(), and Python imports.
6
+ * Sorted roughly by ecosystem popularity.
7
+ */
8
+ export declare const IMPORT_MAPPINGS: ImportMapping[];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Static registry mapping import patterns to Context Hub IDs.
3
+ *
4
+ * Covers ES6 imports, CommonJS require(), and Python imports.
5
+ * Sorted roughly by ecosystem popularity.
6
+ */
7
+ export const IMPORT_MAPPINGS = [
8
+ // --- JavaScript / TypeScript ---
9
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]stripe['"]/, chubId: 'stripe/api' },
10
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]openai['"]/, chubId: 'openai/api' },
11
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@anthropic-ai\/sdk['"]/, chubId: 'anthropic/api' },
12
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@google-cloud\//, chubId: 'google-cloud/api' },
13
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]firebase['"]/, chubId: 'firebase/api' },
14
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@supabase\/supabase-js['"]/, chubId: 'supabase/api' },
15
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@prisma\/client['"]/, chubId: 'prisma/api' },
16
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]drizzle-orm['"]/, chubId: 'drizzle/api' },
17
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]next['"]/, chubId: 'nextjs/api' },
18
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]next\//, chubId: 'nextjs/api' },
19
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]express['"]/, chubId: 'express/api' },
20
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]hono['"]/, chubId: 'hono/api' },
21
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@aws-sdk\//, chubId: 'aws/api' },
22
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]axios['"]/, chubId: 'axios/api' },
23
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]zod['"]/, chubId: 'zod/api' },
24
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@trpc\//, chubId: 'trpc/api' },
25
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@tanstack\/react-query['"]/, chubId: 'tanstack-query/api' },
26
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@clerk\//, chubId: 'clerk/api' },
27
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@auth\//, chubId: 'authjs/api' },
28
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]mongoose['"]/, chubId: 'mongoose/api' },
29
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]typeorm['"]/, chubId: 'typeorm/api' },
30
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@langchain\//, chubId: 'langchain/api' },
31
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]llamaindex['"]/, chubId: 'llamaindex/api' },
32
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@pinecone-database\/pinecone['"]/, chubId: 'pinecone/api' },
33
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@vercel\/ai['"]/, chubId: 'vercel-ai/api' },
34
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]ai['"]/, chubId: 'vercel-ai/api' },
35
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@sentry\//, chubId: 'sentry/api' },
36
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@reduxjs\/toolkit['"]/, chubId: 'redux-toolkit/api' },
37
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]graphql['"]/, chubId: 'graphql/api' },
38
+ { pattern: /(?:from\s+|require\s*\(\s*)['"]@apollo\//, chubId: 'apollo/api' },
39
+ // --- Python ---
40
+ { pattern: /^(?:from|import)\s+fastapi/, chubId: 'fastapi/api' },
41
+ { pattern: /^(?:from|import)\s+django/, chubId: 'django/api' },
42
+ { pattern: /^(?:from|import)\s+flask/, chubId: 'flask/api' },
43
+ { pattern: /^(?:from|import)\s+sqlalchemy/, chubId: 'sqlalchemy/api' },
44
+ { pattern: /^(?:from|import)\s+pydantic/, chubId: 'pydantic/api' },
45
+ { pattern: /^(?:from|import)\s+pandas/, chubId: 'pandas/api' },
46
+ { pattern: /^(?:from|import)\s+numpy/, chubId: 'numpy/api' },
47
+ { pattern: /^(?:from|import)\s+torch/, chubId: 'pytorch/api' },
48
+ { pattern: /^(?:from|import)\s+tensorflow/, chubId: 'tensorflow/api' },
49
+ { pattern: /^(?:from|import)\s+langchain/, chubId: 'langchain/api' },
50
+ { pattern: /^(?:from|import)\s+anthropic/, chubId: 'anthropic/api' },
51
+ { pattern: /^(?:from|import)\s+openai/, chubId: 'openai/api' },
52
+ { pattern: /^(?:from|import)\s+stripe/, chubId: 'stripe/api' },
53
+ { pattern: /^(?:from|import)\s+boto3/, chubId: 'aws/api' },
54
+ { pattern: /^(?:from|import)\s+celery/, chubId: 'celery/api' },
55
+ ];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Options for exporting docs to Context Hub format
3
+ */
4
+ export interface ContextHubExportOptions {
5
+ projectName: string;
6
+ languages: string[];
7
+ version?: string;
8
+ description?: string;
9
+ }
10
+ /**
11
+ * Result of a Context Hub export
12
+ */
13
+ export interface ContextHubExportResult {
14
+ outputDir: string;
15
+ filesWritten: number;
16
+ languages: string[];
17
+ }
18
+ /**
19
+ * Fetched context from Context Hub for a dependency
20
+ */
21
+ export interface ContextHubContext {
22
+ id: string;
23
+ content: string;
24
+ }
25
+ /**
26
+ * Maps an import pattern to a Context Hub ID
27
+ */
28
+ export interface ImportMapping {
29
+ /** Regex pattern to match against import lines */
30
+ pattern: RegExp;
31
+ /** Context Hub ID (e.g., "stripe/api") */
32
+ chubId: string;
33
+ }
@@ -0,0 +1 @@
1
+ export {};