skrypt-ai 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.ts +13 -3
- package/dist/auth/index.js +101 -9
- package/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +82 -0
- package/dist/auth/notices.d.ts +3 -0
- package/dist/auth/notices.js +42 -0
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +10 -24
- 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 +20 -3
- 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 +125 -7
- 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/import.d.ts +2 -0
- package/dist/commands/import.js +157 -0
- package/dist/commands/init.js +19 -7
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +63 -34
- 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 +108 -92
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- 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 +36 -7
- package/dist/importers/confluence.d.ts +5 -0
- package/dist/importers/confluence.js +137 -0
- package/dist/importers/detect.d.ts +20 -0
- package/dist/importers/detect.js +121 -0
- package/dist/importers/docusaurus.d.ts +5 -0
- package/dist/importers/docusaurus.js +279 -0
- package/dist/importers/gitbook.d.ts +5 -0
- package/dist/importers/gitbook.js +189 -0
- package/dist/importers/github.d.ts +8 -0
- package/dist/importers/github.js +99 -0
- package/dist/importers/index.d.ts +15 -0
- package/dist/importers/index.js +30 -0
- package/dist/importers/markdown.d.ts +6 -0
- package/dist/importers/markdown.js +105 -0
- package/dist/importers/mintlify.d.ts +5 -0
- package/dist/importers/mintlify.js +172 -0
- package/dist/importers/notion.d.ts +5 -0
- package/dist/importers/notion.js +174 -0
- package/dist/importers/readme.d.ts +5 -0
- package/dist/importers/readme.js +184 -0
- package/dist/importers/transform.d.ts +90 -0
- package/dist/importers/transform.js +457 -0
- package/dist/importers/types.d.ts +37 -0
- package/dist/importers/types.js +1 -0
- 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/plugins/index.js +7 -0
- 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 +53 -26
- 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/python.js +17 -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 +149 -13
- 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/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +5 -1
|
@@ -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 {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { generateDocumentation } from '../llm/index.js';
|
|
2
|
+
import { matchImportsToContext } from '../context-hub/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* Build enhanced context for LLM from API element
|
|
4
5
|
*/
|
|
5
|
-
function buildElementContext(element) {
|
|
6
|
-
|
|
6
|
+
function buildElementContext(element, externalContext, projectContext) {
|
|
7
|
+
const ctx = {
|
|
7
8
|
kind: element.kind,
|
|
8
9
|
name: element.name,
|
|
9
10
|
signature: element.signature,
|
|
@@ -14,8 +15,13 @@ function buildElementContext(element) {
|
|
|
14
15
|
imports: element.imports,
|
|
15
16
|
packageName: element.packageName,
|
|
16
17
|
sourceContext: element.sourceContext,
|
|
17
|
-
filePath: element.filePath
|
|
18
|
+
filePath: element.filePath,
|
|
19
|
+
projectContext
|
|
18
20
|
};
|
|
21
|
+
if (externalContext && element.imports?.length) {
|
|
22
|
+
ctx.externalDocs = matchImportsToContext(element.imports, externalContext);
|
|
23
|
+
}
|
|
24
|
+
return ctx;
|
|
19
25
|
}
|
|
20
26
|
/**
|
|
21
27
|
* Detect code language from file path
|
|
@@ -31,6 +37,18 @@ function detectLanguageFromFile(filePath) {
|
|
|
31
37
|
return 'go';
|
|
32
38
|
if (filePath.endsWith('.rs'))
|
|
33
39
|
return 'rust';
|
|
40
|
+
if (filePath.endsWith('.java'))
|
|
41
|
+
return 'java';
|
|
42
|
+
if (filePath.endsWith('.cs'))
|
|
43
|
+
return 'csharp';
|
|
44
|
+
if (filePath.endsWith('.php'))
|
|
45
|
+
return 'php';
|
|
46
|
+
if (filePath.endsWith('.kt') || filePath.endsWith('.kts'))
|
|
47
|
+
return 'kotlin';
|
|
48
|
+
if (filePath.endsWith('.swift'))
|
|
49
|
+
return 'swift';
|
|
50
|
+
if (filePath.endsWith('.rb'))
|
|
51
|
+
return 'ruby';
|
|
34
52
|
return 'typescript';
|
|
35
53
|
}
|
|
36
54
|
/**
|
|
@@ -49,7 +67,7 @@ export async function generateForElement(element, client, options, onProgress) {
|
|
|
49
67
|
const useMultiLang = options.multiLanguage ?? false;
|
|
50
68
|
report('generating');
|
|
51
69
|
try {
|
|
52
|
-
const elementContext = buildElementContext(element);
|
|
70
|
+
const elementContext = buildElementContext(element, options.externalContext, options.projectContext);
|
|
53
71
|
const result = await generateDocumentation(client, elementContext, { multiLanguage: useMultiLang });
|
|
54
72
|
report('done');
|
|
55
73
|
return {
|
|
@@ -58,7 +76,8 @@ export async function generateForElement(element, client, options, onProgress) {
|
|
|
58
76
|
codeExample: result.codeExample,
|
|
59
77
|
codeLanguage,
|
|
60
78
|
typescriptExample: result.typescriptExample,
|
|
61
|
-
pythonExample: result.pythonExample
|
|
79
|
+
pythonExample: result.pythonExample,
|
|
80
|
+
usage: result.usage
|
|
62
81
|
};
|
|
63
82
|
}
|
|
64
83
|
catch (err) {
|
|
@@ -77,6 +96,8 @@ export async function generateForElement(element, client, options, onProgress) {
|
|
|
77
96
|
*/
|
|
78
97
|
export async function generateForElements(elements, client, options) {
|
|
79
98
|
const results = [];
|
|
99
|
+
let totalTokens = 0;
|
|
100
|
+
const MAX_TOKENS = options.maxTokens ?? 1_000_000; // default 1M tokens (~$10-20)
|
|
80
101
|
for (let i = 0; i < elements.length; i++) {
|
|
81
102
|
const element = elements[i];
|
|
82
103
|
if (!element)
|
|
@@ -89,6 +110,14 @@ export async function generateForElements(elements, client, options) {
|
|
|
89
110
|
});
|
|
90
111
|
});
|
|
91
112
|
results.push(doc);
|
|
113
|
+
if (doc.usage) {
|
|
114
|
+
totalTokens += (doc.usage.inputTokens ?? 0) + (doc.usage.outputTokens ?? 0);
|
|
115
|
+
}
|
|
116
|
+
if (totalTokens > MAX_TOKENS) {
|
|
117
|
+
console.warn(`\nToken budget exceeded (${totalTokens.toLocaleString()} tokens used). Stopping generation.`);
|
|
118
|
+
console.warn('Use --max-tokens to increase the limit.');
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
92
121
|
}
|
|
93
122
|
return results;
|
|
94
123
|
}
|
|
@@ -137,7 +166,11 @@ function formatDocSection(doc, headingLevel = '###') {
|
|
|
137
166
|
}
|
|
138
167
|
else if (doc.codeExample) {
|
|
139
168
|
section += '**Example:**\n\n';
|
|
140
|
-
const
|
|
169
|
+
const extMap = {
|
|
170
|
+
typescript: 'ts', javascript: 'js', python: 'py', go: 'go', rust: 'rs',
|
|
171
|
+
java: 'java', csharp: 'cs', php: 'php', kotlin: 'kt', swift: 'swift', ruby: 'rb'
|
|
172
|
+
};
|
|
173
|
+
const ext = extMap[doc.codeLanguage] ?? 'js';
|
|
141
174
|
section += `\`\`\`${doc.codeLanguage} example.${ext}\n${doc.codeExample}\n\`\`\`\n\n`;
|
|
142
175
|
}
|
|
143
176
|
return section;
|
|
@@ -10,6 +10,10 @@ export interface GeneratedDoc {
|
|
|
10
10
|
error?: string;
|
|
11
11
|
typescriptExample?: string;
|
|
12
12
|
pythonExample?: string;
|
|
13
|
+
usage?: {
|
|
14
|
+
inputTokens: number;
|
|
15
|
+
outputTokens: number;
|
|
16
|
+
};
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Progress callback for generation
|
|
@@ -26,6 +30,9 @@ export interface GenerationProgress {
|
|
|
26
30
|
export interface GenerationOptions {
|
|
27
31
|
onProgress?: (progress: GenerationProgress) => void;
|
|
28
32
|
multiLanguage?: boolean;
|
|
33
|
+
externalContext?: Map<string, string>;
|
|
34
|
+
maxTokens?: number;
|
|
35
|
+
projectContext?: string;
|
|
29
36
|
}
|
|
30
37
|
/**
|
|
31
38
|
* Result of generating docs for a file
|
|
@@ -10,7 +10,9 @@ export declare function writeLlmsTxt(docs: GeneratedDoc[], outputDir: string, op
|
|
|
10
10
|
/**
|
|
11
11
|
* Write generated docs to output directory
|
|
12
12
|
*/
|
|
13
|
-
export declare function writeDocsToDirectory(results: FileGenerationResult[], outputDir: string, sourceDir: string
|
|
13
|
+
export declare function writeDocsToDirectory(results: FileGenerationResult[], outputDir: string, sourceDir: string, options?: {
|
|
14
|
+
force?: boolean;
|
|
15
|
+
}): Promise<{
|
|
14
16
|
filesWritten: number;
|
|
15
17
|
totalDocs: number;
|
|
16
18
|
}>;
|
package/dist/generator/writer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
-
import { dirname, join, basename, relative } from 'path';
|
|
1
|
+
import { mkdir, writeFile, readdir } from 'fs/promises';
|
|
2
|
+
import { dirname, join, basename, relative, resolve, sep } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
import { formatAsMarkdown } from './generator.js';
|
|
4
5
|
import { organizeByTopic, detectCrossReferences, getCrossRefsForElement } from './organizer.js';
|
|
5
6
|
import { slugify } from '../utils/files.js';
|
|
@@ -71,9 +72,22 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
|
|
|
71
72
|
/**
|
|
72
73
|
* Write generated docs to output directory
|
|
73
74
|
*/
|
|
74
|
-
export async function writeDocsToDirectory(results, outputDir, sourceDir) {
|
|
75
|
+
export async function writeDocsToDirectory(results, outputDir, sourceDir, options) {
|
|
75
76
|
let filesWritten = 0;
|
|
76
77
|
let totalDocs = 0;
|
|
78
|
+
// Check for existing doc files before overwriting
|
|
79
|
+
if (existsSync(outputDir)) {
|
|
80
|
+
try {
|
|
81
|
+
const entries = await readdir(outputDir);
|
|
82
|
+
const existing = entries.filter(f => f.endsWith('.md') || f.endsWith('.mdx'));
|
|
83
|
+
if (existing.length > 0 && !options?.force) {
|
|
84
|
+
console.warn(`Warning: Output directory already contains ${existing.length} doc files. Use --force to overwrite.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Directory read failed, proceed anyway
|
|
89
|
+
}
|
|
90
|
+
}
|
|
77
91
|
// Create output directory
|
|
78
92
|
await mkdir(outputDir, { recursive: true });
|
|
79
93
|
for (const result of results) {
|
|
@@ -88,6 +102,12 @@ export async function writeDocsToDirectory(results, outputDir, sourceDir) {
|
|
|
88
102
|
}
|
|
89
103
|
const docFileName = relPath.replace(/\.[^.]+$/, '.md');
|
|
90
104
|
const outputPath = join(outputDir, docFileName);
|
|
105
|
+
// Double-check resolved path stays within output directory
|
|
106
|
+
const resolvedOutput = resolve(outputPath);
|
|
107
|
+
if (!resolvedOutput.startsWith(resolve(outputDir) + sep) && resolvedOutput !== resolve(outputDir)) {
|
|
108
|
+
console.warn(`Skipping file outside output directory: ${result.filePath}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
91
111
|
// Create subdirectories if needed
|
|
92
112
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
93
113
|
// Generate markdown content
|
|
@@ -174,9 +194,12 @@ export async function writeDocsByTopic(docs, outputDir) {
|
|
|
174
194
|
*/
|
|
175
195
|
function formatTopicMarkdown(topic, allCrossRefs) {
|
|
176
196
|
// MDX frontmatter (standard across doc platforms)
|
|
197
|
+
const safeTitle = topic.name.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ');
|
|
198
|
+
const rawDesc = topic.description || `API reference for ${topic.name}`;
|
|
199
|
+
const safeDesc = rawDesc.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ');
|
|
177
200
|
let content = `---
|
|
178
|
-
title: "${
|
|
179
|
-
description: "${
|
|
201
|
+
title: "${safeTitle}"
|
|
202
|
+
description: "${safeDesc}"
|
|
180
203
|
---
|
|
181
204
|
|
|
182
205
|
`;
|
|
@@ -185,8 +208,14 @@ description: "${topic.description || `API reference for ${topic.name}`}"
|
|
|
185
208
|
for (const doc of topic.docs) {
|
|
186
209
|
const anchor = slugify(doc.element.name);
|
|
187
210
|
const icon = doc.element.kind === 'class' ? 'cube' : doc.element.kind === 'method' ? 'code' : 'function';
|
|
188
|
-
|
|
189
|
-
|
|
211
|
+
// Use short description (first ~5 words) or fall back to kind
|
|
212
|
+
const rawDocstring = doc.element.docstring?.replace(/^@\w+\s+\S+\s*/, '')?.trim();
|
|
213
|
+
const desc = rawDocstring
|
|
214
|
+
? rawDocstring.split(/\s+/).slice(0, 5).join(' ').replace(/[.,:;]$/, '')
|
|
215
|
+
: doc.element.kind;
|
|
216
|
+
const safeName = doc.element.name.replace(/"/g, '"');
|
|
217
|
+
content += ` <Card title="${safeName}" icon="${icon}" href="#${anchor}">\n`;
|
|
218
|
+
content += ` ${desc}\n`;
|
|
190
219
|
content += ` </Card>\n`;
|
|
191
220
|
}
|
|
192
221
|
content += '</CardGroup>\n\n';
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, relative, basename, extname } from 'path';
|
|
3
|
+
import { transformConfluenceCallouts, transformConfluenceHtml, normalizeFrontmatter, } from './transform.js';
|
|
4
|
+
/**
|
|
5
|
+
* Import Confluence HTML space export.
|
|
6
|
+
*/
|
|
7
|
+
export function importConfluence(dir, name) {
|
|
8
|
+
const result = createEmptyResult(name);
|
|
9
|
+
const stats = { callouts: 0, tabs: 0, codeGroups: 0, steps: 0, accordions: 0, images: 0, other: 0 };
|
|
10
|
+
// Find all HTML files
|
|
11
|
+
const htmlFiles = findHtmlFiles(dir);
|
|
12
|
+
if (htmlFiles.length === 0) {
|
|
13
|
+
result.warnings.push('No .html files found in Confluence export');
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
const pages = [];
|
|
17
|
+
for (const filePath of htmlFiles) {
|
|
18
|
+
// Skip index.html (usually a listing page)
|
|
19
|
+
if (basename(filePath) === 'index.html')
|
|
20
|
+
continue;
|
|
21
|
+
const page = processConfluencePage(dir, filePath, stats, result);
|
|
22
|
+
if (page)
|
|
23
|
+
pages.push(page);
|
|
24
|
+
}
|
|
25
|
+
if (pages.length > 0) {
|
|
26
|
+
result.navigation.push({ group: name || 'Documentation', pages });
|
|
27
|
+
}
|
|
28
|
+
// Copy attachments
|
|
29
|
+
const attachmentsDir = join(dir, 'attachments');
|
|
30
|
+
if (existsSync(attachmentsDir)) {
|
|
31
|
+
collectAttachments(attachmentsDir, result, stats);
|
|
32
|
+
}
|
|
33
|
+
result.stats = {
|
|
34
|
+
pages: result.files.size,
|
|
35
|
+
groups: result.navigation.length,
|
|
36
|
+
transforms: stats,
|
|
37
|
+
};
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function findHtmlFiles(dir) {
|
|
41
|
+
const files = [];
|
|
42
|
+
try {
|
|
43
|
+
for (const entry of readdirSync(dir)) {
|
|
44
|
+
const fullPath = join(dir, entry);
|
|
45
|
+
try {
|
|
46
|
+
const stat = statSync(fullPath);
|
|
47
|
+
if (stat.isFile() && extname(entry) === '.html') {
|
|
48
|
+
files.push(fullPath);
|
|
49
|
+
}
|
|
50
|
+
else if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'attachments') {
|
|
51
|
+
files.push(...findHtmlFiles(fullPath));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch { /* skip */ }
|
|
60
|
+
return files;
|
|
61
|
+
}
|
|
62
|
+
function processConfluencePage(dir, filePath, stats, result) {
|
|
63
|
+
const rawContent = readFileSync(filePath, 'utf-8');
|
|
64
|
+
let content = rawContent;
|
|
65
|
+
// Extract body content
|
|
66
|
+
const bodyMatch = content.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
67
|
+
if (bodyMatch)
|
|
68
|
+
content = bodyMatch[1];
|
|
69
|
+
// Extract title from <title> tag or <h1> (use rawContent to search full HTML)
|
|
70
|
+
let title = '';
|
|
71
|
+
const titleMatch = rawContent.match(/<title>([^<]+)<\/title>/i);
|
|
72
|
+
if (titleMatch)
|
|
73
|
+
title = titleMatch[1].trim();
|
|
74
|
+
if (!title) {
|
|
75
|
+
const h1Match = content.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
76
|
+
if (h1Match)
|
|
77
|
+
title = h1Match[1].replace(/<[^>]+>/g, '').trim();
|
|
78
|
+
}
|
|
79
|
+
if (!title)
|
|
80
|
+
title = basename(filePath, '.html').replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
81
|
+
const originalContent = content;
|
|
82
|
+
// Apply transforms: callouts first, then general HTML conversion
|
|
83
|
+
content = transformConfluenceCallouts(content);
|
|
84
|
+
content = transformConfluenceHtml(content);
|
|
85
|
+
content = normalizeFrontmatter(content, { title });
|
|
86
|
+
stats.callouts += countNew(originalContent, content, '<Callout');
|
|
87
|
+
stats.accordions += countNew(originalContent, content, '<Accordion');
|
|
88
|
+
const rel = relative(dir, filePath);
|
|
89
|
+
const slug = basename(filePath, '.html').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
90
|
+
const outputPath = `content/docs/${slug}.mdx`;
|
|
91
|
+
result.files.set(outputPath, content);
|
|
92
|
+
return { title, slug, sourcePath: rel, content };
|
|
93
|
+
}
|
|
94
|
+
function collectAttachments(attachmentsDir, result, stats) {
|
|
95
|
+
const imgExts = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp']);
|
|
96
|
+
function walk(dir) {
|
|
97
|
+
try {
|
|
98
|
+
for (const entry of readdirSync(dir)) {
|
|
99
|
+
const fullPath = join(dir, entry);
|
|
100
|
+
try {
|
|
101
|
+
const stat = statSync(fullPath);
|
|
102
|
+
if (stat.isDirectory()) {
|
|
103
|
+
walk(fullPath);
|
|
104
|
+
}
|
|
105
|
+
else if (imgExts.has(extname(entry).toLowerCase())) {
|
|
106
|
+
const dest = `public/images/${entry}`;
|
|
107
|
+
result.assets.set(dest, fullPath);
|
|
108
|
+
stats.images++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch { /* skip */ }
|
|
117
|
+
}
|
|
118
|
+
walk(attachmentsDir);
|
|
119
|
+
}
|
|
120
|
+
function countNew(original, transformed, marker) {
|
|
121
|
+
const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
122
|
+
const origCount = (original.match(new RegExp(escaped, 'g')) || []).length;
|
|
123
|
+
const newCount = (transformed.match(new RegExp(escaped, 'g')) || []).length;
|
|
124
|
+
return Math.max(0, newCount - origCount);
|
|
125
|
+
}
|
|
126
|
+
function createEmptyResult(name) {
|
|
127
|
+
return {
|
|
128
|
+
navigation: [],
|
|
129
|
+
name: name || 'Documentation',
|
|
130
|
+
description: 'Imported from Confluence',
|
|
131
|
+
files: new Map(),
|
|
132
|
+
assets: new Map(),
|
|
133
|
+
warnings: [],
|
|
134
|
+
stats: { pages: 0, groups: 0, transforms: { callouts: 0, tabs: 0, codeGroups: 0, steps: 0, accordions: 0, images: 0, other: 0 } },
|
|
135
|
+
sourceFormat: 'confluence',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ImportFormat } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Auto-detect documentation format from a directory by checking marker files.
|
|
4
|
+
* First match wins (priority order).
|
|
5
|
+
*/
|
|
6
|
+
export declare function detectFormat(dir: string): ImportFormat;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a string is a GitHub URL
|
|
9
|
+
*/
|
|
10
|
+
export declare function isGitHubUrl(input: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Parse a GitHub URL into components
|
|
13
|
+
* Supports: https://github.com/owner/repo/tree/branch/path
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseGitHubUrl(url: string): {
|
|
16
|
+
owner: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
path: string;
|
|
19
|
+
ref: string;
|
|
20
|
+
};
|