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.
- package/dist/auth/index.js +8 -1
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +0 -21
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +88 -6
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +61 -43
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +114 -103
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.js +86 -80
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +24 -4
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +16 -2
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +84 -6
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +3 -2
package/dist/commands/watch.js
CHANGED
|
@@ -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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
console.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
});
|
package/dist/config/types.js
CHANGED
|
@@ -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 {};
|