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.
Files changed (159) hide show
  1. package/dist/auth/index.d.ts +13 -3
  2. package/dist/auth/index.js +101 -9
  3. package/dist/auth/keychain.d.ts +5 -0
  4. package/dist/auth/keychain.js +82 -0
  5. package/dist/auth/notices.d.ts +3 -0
  6. package/dist/auth/notices.js +42 -0
  7. package/dist/autofix/index.d.ts +0 -4
  8. package/dist/autofix/index.js +10 -24
  9. package/dist/capture/browser.d.ts +11 -0
  10. package/dist/capture/browser.js +173 -0
  11. package/dist/capture/diff.d.ts +23 -0
  12. package/dist/capture/diff.js +52 -0
  13. package/dist/capture/index.d.ts +23 -0
  14. package/dist/capture/index.js +210 -0
  15. package/dist/capture/naming.d.ts +17 -0
  16. package/dist/capture/naming.js +45 -0
  17. package/dist/capture/parser.d.ts +15 -0
  18. package/dist/capture/parser.js +80 -0
  19. package/dist/capture/types.d.ts +57 -0
  20. package/dist/capture/types.js +1 -0
  21. package/dist/cli.js +20 -3
  22. package/dist/commands/autofix.js +136 -120
  23. package/dist/commands/cron.js +58 -47
  24. package/dist/commands/deploy.js +123 -102
  25. package/dist/commands/generate.js +125 -7
  26. package/dist/commands/heal.d.ts +10 -0
  27. package/dist/commands/heal.js +201 -0
  28. package/dist/commands/i18n.js +146 -111
  29. package/dist/commands/import.d.ts +2 -0
  30. package/dist/commands/import.js +157 -0
  31. package/dist/commands/init.js +19 -7
  32. package/dist/commands/lint.js +50 -44
  33. package/dist/commands/llms-txt.js +59 -49
  34. package/dist/commands/login.js +63 -34
  35. package/dist/commands/mcp.js +6 -0
  36. package/dist/commands/monitor.js +13 -8
  37. package/dist/commands/qa.d.ts +2 -0
  38. package/dist/commands/qa.js +43 -0
  39. package/dist/commands/review-pr.js +108 -92
  40. package/dist/commands/sdk.js +128 -122
  41. package/dist/commands/security.d.ts +2 -0
  42. package/dist/commands/security.js +109 -0
  43. package/dist/commands/test.js +91 -92
  44. package/dist/commands/version.js +104 -75
  45. package/dist/commands/watch.js +130 -114
  46. package/dist/config/types.js +2 -2
  47. package/dist/context-hub/index.d.ts +23 -0
  48. package/dist/context-hub/index.js +179 -0
  49. package/dist/context-hub/mappings.d.ts +8 -0
  50. package/dist/context-hub/mappings.js +55 -0
  51. package/dist/context-hub/types.d.ts +33 -0
  52. package/dist/context-hub/types.js +1 -0
  53. package/dist/generator/generator.js +39 -6
  54. package/dist/generator/types.d.ts +7 -0
  55. package/dist/generator/writer.d.ts +3 -1
  56. package/dist/generator/writer.js +36 -7
  57. package/dist/importers/confluence.d.ts +5 -0
  58. package/dist/importers/confluence.js +137 -0
  59. package/dist/importers/detect.d.ts +20 -0
  60. package/dist/importers/detect.js +121 -0
  61. package/dist/importers/docusaurus.d.ts +5 -0
  62. package/dist/importers/docusaurus.js +279 -0
  63. package/dist/importers/gitbook.d.ts +5 -0
  64. package/dist/importers/gitbook.js +189 -0
  65. package/dist/importers/github.d.ts +8 -0
  66. package/dist/importers/github.js +99 -0
  67. package/dist/importers/index.d.ts +15 -0
  68. package/dist/importers/index.js +30 -0
  69. package/dist/importers/markdown.d.ts +6 -0
  70. package/dist/importers/markdown.js +105 -0
  71. package/dist/importers/mintlify.d.ts +5 -0
  72. package/dist/importers/mintlify.js +172 -0
  73. package/dist/importers/notion.d.ts +5 -0
  74. package/dist/importers/notion.js +174 -0
  75. package/dist/importers/readme.d.ts +5 -0
  76. package/dist/importers/readme.js +184 -0
  77. package/dist/importers/transform.d.ts +90 -0
  78. package/dist/importers/transform.js +457 -0
  79. package/dist/importers/types.d.ts +37 -0
  80. package/dist/importers/types.js +1 -0
  81. package/dist/llm/anthropic-client.d.ts +1 -0
  82. package/dist/llm/anthropic-client.js +3 -1
  83. package/dist/llm/index.d.ts +6 -4
  84. package/dist/llm/index.js +76 -261
  85. package/dist/llm/openai-client.d.ts +1 -0
  86. package/dist/llm/openai-client.js +7 -2
  87. package/dist/plugins/index.js +7 -0
  88. package/dist/qa/checks.d.ts +10 -0
  89. package/dist/qa/checks.js +492 -0
  90. package/dist/qa/fixes.d.ts +30 -0
  91. package/dist/qa/fixes.js +277 -0
  92. package/dist/qa/index.d.ts +29 -0
  93. package/dist/qa/index.js +187 -0
  94. package/dist/qa/types.d.ts +24 -0
  95. package/dist/qa/types.js +1 -0
  96. package/dist/scanner/csharp.d.ts +23 -0
  97. package/dist/scanner/csharp.js +421 -0
  98. package/dist/scanner/index.js +53 -26
  99. package/dist/scanner/java.d.ts +39 -0
  100. package/dist/scanner/java.js +318 -0
  101. package/dist/scanner/kotlin.d.ts +23 -0
  102. package/dist/scanner/kotlin.js +389 -0
  103. package/dist/scanner/php.d.ts +57 -0
  104. package/dist/scanner/php.js +351 -0
  105. package/dist/scanner/python.js +17 -0
  106. package/dist/scanner/ruby.d.ts +36 -0
  107. package/dist/scanner/ruby.js +431 -0
  108. package/dist/scanner/swift.d.ts +25 -0
  109. package/dist/scanner/swift.js +392 -0
  110. package/dist/scanner/types.d.ts +1 -1
  111. package/dist/template/content/docs/_navigation.json +46 -0
  112. package/dist/template/content/docs/_sidebars.json +684 -0
  113. package/dist/template/content/docs/core.md +4544 -0
  114. package/dist/template/content/docs/index.mdx +89 -0
  115. package/dist/template/content/docs/integrations.md +1158 -0
  116. package/dist/template/content/docs/llms-full.md +403 -0
  117. package/dist/template/content/docs/llms.txt +4588 -0
  118. package/dist/template/content/docs/other.md +10379 -0
  119. package/dist/template/content/docs/tools.md +746 -0
  120. package/dist/template/content/docs/types.md +531 -0
  121. package/dist/template/docs.json +13 -11
  122. package/dist/template/mdx-components.tsx +27 -2
  123. package/dist/template/package.json +6 -0
  124. package/dist/template/public/search-index.json +1 -1
  125. package/dist/template/scripts/build-search-index.mjs +149 -13
  126. package/dist/template/src/app/api/chat/route.ts +83 -128
  127. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  128. package/dist/template/src/app/docs/llms-full.md +151 -4
  129. package/dist/template/src/app/docs/llms.txt +2464 -847
  130. package/dist/template/src/app/docs/page.mdx +48 -38
  131. package/dist/template/src/app/layout.tsx +3 -1
  132. package/dist/template/src/app/page.tsx +22 -8
  133. package/dist/template/src/components/ai-chat.tsx +73 -64
  134. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  135. package/dist/template/src/components/copy-button.tsx +13 -9
  136. package/dist/template/src/components/copy-page-button.tsx +54 -0
  137. package/dist/template/src/components/docs-layout.tsx +37 -25
  138. package/dist/template/src/components/header.tsx +51 -10
  139. package/dist/template/src/components/mdx/card.tsx +17 -3
  140. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  141. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  142. package/dist/template/src/components/mdx/heading.tsx +15 -2
  143. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  144. package/dist/template/src/components/mdx/index.tsx +2 -0
  145. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  146. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  147. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  148. package/dist/template/src/components/sidebar.tsx +12 -18
  149. package/dist/template/src/components/table-of-contents.tsx +9 -0
  150. package/dist/template/src/lib/highlight.ts +3 -88
  151. package/dist/template/src/lib/navigation.ts +159 -0
  152. package/dist/template/src/lib/search-types.ts +4 -1
  153. package/dist/template/src/lib/search.ts +30 -7
  154. package/dist/template/src/styles/globals.css +17 -6
  155. package/dist/utils/files.d.ts +9 -1
  156. package/dist/utils/files.js +59 -10
  157. package/dist/utils/validation.d.ts +0 -3
  158. package/dist/utils/validation.js +0 -26
  159. 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
- return {
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 ext = doc.codeLanguage === 'typescript' ? 'ts' : doc.codeLanguage === 'python' ? 'py' : 'js';
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): Promise<{
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
  }>;
@@ -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: "${topic.name}"
179
- description: "${topic.description || `API reference for ${topic.name}`}"
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
- content += ` <Card title="${doc.element.name}" icon="${icon}" href="#${anchor}">\n`;
189
- content += ` ${doc.element.kind}\n`;
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, '&quot;');
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,5 @@
1
+ import type { ImportResult } from './types.js';
2
+ /**
3
+ * Import Confluence HTML space export.
4
+ */
5
+ export declare function importConfluence(dir: string, name?: string): ImportResult;
@@ -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
+ };