skrypt-ai 0.1.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/autofix/index.d.ts +46 -0
  4. package/dist/autofix/index.js +240 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/commands/autofix.d.ts +2 -0
  8. package/dist/commands/autofix.js +143 -0
  9. package/dist/commands/generate.d.ts +2 -0
  10. package/dist/commands/generate.js +320 -0
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +56 -0
  13. package/dist/commands/review-pr.d.ts +2 -0
  14. package/dist/commands/review-pr.js +117 -0
  15. package/dist/commands/watch.d.ts +2 -0
  16. package/dist/commands/watch.js +142 -0
  17. package/dist/config/index.d.ts +2 -0
  18. package/dist/config/index.js +2 -0
  19. package/dist/config/loader.d.ts +9 -0
  20. package/dist/config/loader.js +82 -0
  21. package/dist/config/types.d.ts +24 -0
  22. package/dist/config/types.js +34 -0
  23. package/dist/generator/generator.d.ts +15 -0
  24. package/dist/generator/generator.js +144 -0
  25. package/dist/generator/index.d.ts +4 -0
  26. package/dist/generator/index.js +4 -0
  27. package/dist/generator/organizer.d.ts +29 -0
  28. package/dist/generator/organizer.js +222 -0
  29. package/dist/generator/types.d.ts +83 -0
  30. package/dist/generator/types.js +1 -0
  31. package/dist/generator/writer.d.ts +28 -0
  32. package/dist/generator/writer.js +320 -0
  33. package/dist/github/pr-comments.d.ts +40 -0
  34. package/dist/github/pr-comments.js +308 -0
  35. package/dist/llm/anthropic-client.d.ts +16 -0
  36. package/dist/llm/anthropic-client.js +92 -0
  37. package/dist/llm/index.d.ts +53 -0
  38. package/dist/llm/index.js +400 -0
  39. package/dist/llm/llm.manual-test.d.ts +1 -0
  40. package/dist/llm/llm.manual-test.js +112 -0
  41. package/dist/llm/llm.mock-test.d.ts +4 -0
  42. package/dist/llm/llm.mock-test.js +79 -0
  43. package/dist/llm/openai-client.d.ts +17 -0
  44. package/dist/llm/openai-client.js +90 -0
  45. package/dist/llm/types.d.ts +60 -0
  46. package/dist/llm/types.js +20 -0
  47. package/dist/scanner/content-type.d.ts +39 -0
  48. package/dist/scanner/content-type.js +194 -0
  49. package/dist/scanner/content-type.test.d.ts +1 -0
  50. package/dist/scanner/content-type.test.js +231 -0
  51. package/dist/scanner/go.d.ts +20 -0
  52. package/dist/scanner/go.js +269 -0
  53. package/dist/scanner/index.d.ts +21 -0
  54. package/dist/scanner/index.js +137 -0
  55. package/dist/scanner/python.d.ts +6 -0
  56. package/dist/scanner/python.js +57 -0
  57. package/dist/scanner/python_parser.py +230 -0
  58. package/dist/scanner/rust.d.ts +23 -0
  59. package/dist/scanner/rust.js +304 -0
  60. package/dist/scanner/scanner.test.d.ts +1 -0
  61. package/dist/scanner/scanner.test.js +210 -0
  62. package/dist/scanner/types.d.ts +50 -0
  63. package/dist/scanner/types.js +1 -0
  64. package/dist/scanner/typescript.d.ts +34 -0
  65. package/dist/scanner/typescript.js +327 -0
  66. package/dist/scanner/typescript.manual-test.d.ts +1 -0
  67. package/dist/scanner/typescript.manual-test.js +112 -0
  68. package/dist/template/docs.json +32 -0
  69. package/dist/template/mdx-components.tsx +62 -0
  70. package/dist/template/next-env.d.ts +6 -0
  71. package/dist/template/next.config.mjs +17 -0
  72. package/dist/template/package.json +39 -0
  73. package/dist/template/postcss.config.mjs +5 -0
  74. package/dist/template/public/search-index.json +1 -0
  75. package/dist/template/scripts/build-search-index.mjs +120 -0
  76. package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
  77. package/dist/template/src/app/api/openapi/route.ts +48 -0
  78. package/dist/template/src/app/api/rate-limit/route.ts +84 -0
  79. package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
  80. package/dist/template/src/app/docs/layout.tsx +9 -0
  81. package/dist/template/src/app/docs/page.mdx +67 -0
  82. package/dist/template/src/app/error.tsx +63 -0
  83. package/dist/template/src/app/layout.tsx +71 -0
  84. package/dist/template/src/app/page.tsx +18 -0
  85. package/dist/template/src/app/reference/route.ts +36 -0
  86. package/dist/template/src/app/robots.ts +14 -0
  87. package/dist/template/src/app/sitemap.ts +64 -0
  88. package/dist/template/src/components/breadcrumbs.tsx +41 -0
  89. package/dist/template/src/components/copy-button.tsx +29 -0
  90. package/dist/template/src/components/docs-layout.tsx +35 -0
  91. package/dist/template/src/components/edit-link.tsx +39 -0
  92. package/dist/template/src/components/feedback.tsx +52 -0
  93. package/dist/template/src/components/header.tsx +66 -0
  94. package/dist/template/src/components/mdx/accordion.tsx +48 -0
  95. package/dist/template/src/components/mdx/api-badge.tsx +57 -0
  96. package/dist/template/src/components/mdx/callout.tsx +111 -0
  97. package/dist/template/src/components/mdx/card.tsx +62 -0
  98. package/dist/template/src/components/mdx/changelog.tsx +57 -0
  99. package/dist/template/src/components/mdx/code-block.tsx +42 -0
  100. package/dist/template/src/components/mdx/code-group.tsx +125 -0
  101. package/dist/template/src/components/mdx/code-playground.tsx +322 -0
  102. package/dist/template/src/components/mdx/go-playground.tsx +235 -0
  103. package/dist/template/src/components/mdx/heading.tsx +37 -0
  104. package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
  105. package/dist/template/src/components/mdx/index.tsx +15 -0
  106. package/dist/template/src/components/mdx/param-table.tsx +71 -0
  107. package/dist/template/src/components/mdx/python-playground.tsx +293 -0
  108. package/dist/template/src/components/mdx/steps.tsx +43 -0
  109. package/dist/template/src/components/mdx/tabs.tsx +81 -0
  110. package/dist/template/src/components/rate-limit-display.tsx +183 -0
  111. package/dist/template/src/components/search-dialog.tsx +178 -0
  112. package/dist/template/src/components/sidebar.tsx +129 -0
  113. package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
  114. package/dist/template/src/components/table-of-contents.tsx +84 -0
  115. package/dist/template/src/components/theme-toggle.tsx +46 -0
  116. package/dist/template/src/components/version-selector.tsx +61 -0
  117. package/dist/template/src/contexts/syntax-theme.tsx +52 -0
  118. package/dist/template/src/lib/highlight.ts +83 -0
  119. package/dist/template/src/lib/search-types.ts +37 -0
  120. package/dist/template/src/lib/search.ts +125 -0
  121. package/dist/template/src/lib/utils.ts +6 -0
  122. package/dist/template/src/styles/globals.css +152 -0
  123. package/dist/template/tsconfig.json +25 -0
  124. package/dist/template/tsconfig.tsbuildinfo +1 -0
  125. package/package.json +72 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 autodocs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # skrypt
2
+
3
+ AI-powered documentation generator with code examples.
4
+
5
+ Scans your codebase, extracts API signatures, and generates beautiful documentation with working code examples.
6
+
7
+ ## Features
8
+
9
+ - **Multi-language scanning**: TypeScript, Python, Go, Rust
10
+ - **AI-generated docs**: Clear descriptions, parameters, return types
11
+ - **Code examples**: Working examples in TypeScript and Python
12
+ - **Topic organization**: Group elements by concept, not file
13
+ - **MDX output**: Works with any doc platform
14
+ - **Doc site template**: Beautiful, searchable documentation site
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install -g skrypt
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### 1. Initialize a documentation site
25
+
26
+ ```bash
27
+ skrypt init my-docs
28
+ cd my-docs
29
+ npm install
30
+ ```
31
+
32
+ ### 2. Generate API documentation
33
+
34
+ ```bash
35
+ skrypt generate ../my-project/src -o ./content/docs
36
+ ```
37
+
38
+ ### 3. Start the dev server
39
+
40
+ ```bash
41
+ npm run dev
42
+ ```
43
+
44
+ Open http://localhost:3000 to see your docs.
45
+
46
+ ## CLI Commands
47
+
48
+ ### `skrypt init [directory]`
49
+
50
+ Initialize a new documentation site.
51
+
52
+ ```bash
53
+ skrypt init my-docs --name "My API Docs"
54
+ ```
55
+
56
+ Options:
57
+ - `--name <name>` - Project name (default: "my-docs")
58
+
59
+ ### `skrypt generate <source>`
60
+
61
+ Generate documentation from source code.
62
+
63
+ ```bash
64
+ skrypt generate ./src -o ./docs
65
+
66
+ # Multi-language examples
67
+ skrypt generate ./src -o ./docs --multi-lang
68
+
69
+ # Organize by topic
70
+ skrypt generate ./src -o ./docs --by-topic
71
+ ```
72
+
73
+ Options:
74
+ - `-o, --output <dir>` - Output directory
75
+ - `-c, --config <file>` - Config file path
76
+ - `--provider <name>` - LLM provider (deepseek, openai, anthropic, google, ollama, openrouter)
77
+ - `--model <name>` - LLM model name
78
+ - `--multi-lang` - Generate TypeScript + Python examples
79
+ - `--by-topic` - Organize by topic instead of file
80
+ - `--dry-run` - Scan only, don't generate
81
+ - `--public-only` - Only document exported/public APIs
82
+ - `--exclude <patterns...>` - Exclude patterns (files or `name:pattern`)
83
+ - `--llms-txt` - Generate llms.txt for Answer Engine Optimization
84
+ - `--project-name <name>` - Project name for llms.txt header
85
+ - `--openapi <file>` - Include OpenAPI spec for API Playground
86
+
87
+ ## Privacy Controls
88
+
89
+ Prevent internal code from being documented:
90
+
91
+ ```bash
92
+ # Only document exported APIs
93
+ skrypt generate ./src --public-only
94
+
95
+ # Exclude specific patterns
96
+ skrypt generate ./src --exclude "**/internal/**" "name:_private*"
97
+ ```
98
+
99
+ Create `.skryptignore` in your source directory:
100
+
101
+ ```gitignore
102
+ # Exclude test files
103
+ **/*.test.ts
104
+ **/*.spec.ts
105
+
106
+ # Exclude internal modules
107
+ **/internal/**
108
+
109
+ # Exclude by function name (regex supported)
110
+ name:_privateHelper
111
+ name:__internal.*
112
+ ```
113
+
114
+ ## Answer Engine Optimization (AEO)
115
+
116
+ Generate `llms.txt` for AI search engines:
117
+
118
+ ```bash
119
+ skrypt generate ./src --llms-txt --project-name "My API"
120
+ ```
121
+
122
+ This creates:
123
+ - `llms.txt` - Condensed API summary for LLMs
124
+ - `llms-full.md` - Full API reference in LLM-friendly format
125
+
126
+ ## Configuration
127
+
128
+ Create `.skrypt.yaml` in your project:
129
+
130
+ ```yaml
131
+ version: 1
132
+
133
+ source:
134
+ path: ./src
135
+ include: ["**/*.ts", "**/*.py"]
136
+ exclude: ["**/node_modules/**"]
137
+
138
+ output:
139
+ path: ./docs
140
+ format: mdx
141
+
142
+ llm:
143
+ provider: deepseek
144
+ model: deepseek-v3
145
+ ```
146
+
147
+ ## Doc Site Features
148
+
149
+ The generated documentation site includes:
150
+
151
+ - **Search**: Full-text search across all docs
152
+ - **Syntax highlighting**: Beautiful code blocks with Shiki
153
+ - **MDX components**: Cards, Tabs, CodeGroup, Callouts, Accordions, Steps
154
+ - **Dark mode**: Automatic dark/light theme switching
155
+ - **Responsive**: Works on all device sizes
156
+
157
+ ## MDX Components
158
+
159
+ Use these components in your MDX files:
160
+
161
+ ```mdx
162
+ <CardGroup cols={2}>
163
+ <Card title="Getting Started" icon="Zap" href="/docs/quickstart">
164
+ Get up and running in 5 minutes
165
+ </Card>
166
+ </CardGroup>
167
+
168
+ <Info title="Note">
169
+ Important information here.
170
+ </Info>
171
+
172
+ <CodeGroup>
173
+ \`\`\`typescript
174
+ const client = new Client()
175
+ \`\`\`
176
+
177
+ \`\`\`python
178
+ client = Client()
179
+ \`\`\`
180
+ </CodeGroup>
181
+
182
+ <Steps>
183
+ <Step title="Install">Install the package</Step>
184
+ <Step title="Configure">Set up your config</Step>
185
+ </Steps>
186
+ ```
187
+
188
+ ## Environment Variables
189
+
190
+ Set API keys for your LLM provider:
191
+
192
+ ```bash
193
+ export DEEPSEEK_API_KEY=your-key
194
+ export OPENAI_API_KEY=your-key
195
+ export ANTHROPIC_API_KEY=your-key
196
+ ```
197
+
198
+ ## License
199
+
200
+ MIT
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Auto-fix Broken Code Examples
3
+ *
4
+ * Uses LLM to automatically fix code examples that fail validation.
5
+ * - Free tier: 3 fix attempts per example
6
+ * - Pro tier: 10 fix attempts per example
7
+ */
8
+ import { LLMClient } from '../llm/index.js';
9
+ export interface CodeExample {
10
+ code: string;
11
+ language: string;
12
+ context: string;
13
+ }
14
+ export interface FixResult {
15
+ success: boolean;
16
+ fixedCode: string;
17
+ iterations: number;
18
+ errors: string[];
19
+ explanation?: string;
20
+ }
21
+ export interface AutoFixOptions {
22
+ maxIterations?: number;
23
+ validateFn?: (code: string) => Promise<ValidationResult>;
24
+ language?: string;
25
+ }
26
+ export interface ValidationResult {
27
+ valid: boolean;
28
+ errors: string[];
29
+ output?: string;
30
+ }
31
+ /**
32
+ * Auto-fix a code example using LLM
33
+ */
34
+ export declare function autoFixExample(example: CodeExample, client: LLMClient, options?: AutoFixOptions): Promise<FixResult>;
35
+ /**
36
+ * Batch auto-fix multiple examples
37
+ */
38
+ export declare function autoFixBatch(examples: CodeExample[], client: LLMClient, options?: AutoFixOptions): Promise<Map<number, FixResult>>;
39
+ /**
40
+ * Create a TypeScript validator using tsc
41
+ */
42
+ export declare function createTypeScriptValidator(): (code: string) => Promise<ValidationResult>;
43
+ /**
44
+ * Create a Python validator (requires python3 in PATH)
45
+ */
46
+ export declare function createPythonValidator(): (code: string) => Promise<ValidationResult>;
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Auto-fix Broken Code Examples
3
+ *
4
+ * Uses LLM to automatically fix code examples that fail validation.
5
+ * - Free tier: 3 fix attempts per example
6
+ * - Pro tier: 10 fix attempts per example
7
+ */
8
+ /**
9
+ * Default TypeScript/JavaScript validator using eval
10
+ */
11
+ async function defaultValidator(code) {
12
+ try {
13
+ // Basic syntax check - try to parse as function
14
+ new Function(code);
15
+ return { valid: true, errors: [] };
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ return {
20
+ valid: false,
21
+ errors: [message],
22
+ };
23
+ }
24
+ }
25
+ /**
26
+ * Auto-fix a code example using LLM
27
+ */
28
+ export async function autoFixExample(example, client, options = {}) {
29
+ const maxIterations = options.maxIterations ?? 3;
30
+ const validateFn = options.validateFn ?? defaultValidator;
31
+ const language = options.language ?? example.language;
32
+ let currentCode = example.code;
33
+ const allErrors = [];
34
+ let iterations = 0;
35
+ // First, validate the original code
36
+ const initialValidation = await validateFn(currentCode);
37
+ if (initialValidation.valid) {
38
+ return {
39
+ success: true,
40
+ fixedCode: currentCode,
41
+ iterations: 0,
42
+ errors: [],
43
+ explanation: 'Original code is valid, no fixes needed.',
44
+ };
45
+ }
46
+ allErrors.push(...initialValidation.errors);
47
+ // Iterate until fixed or max iterations reached
48
+ while (iterations < maxIterations) {
49
+ iterations++;
50
+ const prompt = buildFixPrompt(currentCode, language, example.context, allErrors);
51
+ const response = await client.complete({
52
+ messages: [
53
+ { role: 'system', content: 'You are a code fixer. Return only the fixed code in a code block.' },
54
+ { role: 'user', content: prompt }
55
+ ],
56
+ temperature: 0,
57
+ maxTokens: 2048
58
+ });
59
+ // Extract code from response
60
+ const fixedCode = extractCode(response.content, language);
61
+ if (!fixedCode) {
62
+ allErrors.push(`Iteration ${iterations}: Could not extract code from LLM response`);
63
+ continue;
64
+ }
65
+ // Validate the fixed code
66
+ const validation = await validateFn(fixedCode);
67
+ if (validation.valid) {
68
+ return {
69
+ success: true,
70
+ fixedCode,
71
+ iterations,
72
+ errors: allErrors,
73
+ explanation: `Fixed after ${iterations} iteration(s).`,
74
+ };
75
+ }
76
+ // Add new errors and continue
77
+ allErrors.push(`Iteration ${iterations}: ${validation.errors.join(', ')}`);
78
+ currentCode = fixedCode;
79
+ }
80
+ return {
81
+ success: false,
82
+ fixedCode: currentCode,
83
+ iterations,
84
+ errors: allErrors,
85
+ explanation: `Could not fix code after ${maxIterations} iterations.`,
86
+ };
87
+ }
88
+ /**
89
+ * Build the LLM prompt for fixing code
90
+ */
91
+ function buildFixPrompt(code, language, context, errors) {
92
+ return `You are a code fixer. Fix the following ${language} code example.
93
+
94
+ ## Context
95
+ ${context}
96
+
97
+ ## Current Code
98
+ \`\`\`${language}
99
+ ${code}
100
+ \`\`\`
101
+
102
+ ## Errors
103
+ ${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}
104
+
105
+ ## Instructions
106
+ 1. Analyze the errors and understand what's wrong
107
+ 2. Fix the code to make it work correctly
108
+ 3. Keep the fix minimal - don't change working parts
109
+ 4. Maintain the original intent and style
110
+ 5. Return ONLY the fixed code in a code block
111
+
112
+ ## Fixed Code
113
+ `;
114
+ }
115
+ /**
116
+ * Extract code from LLM response
117
+ */
118
+ function extractCode(response, language) {
119
+ // Try to find code block with language
120
+ const langRegex = new RegExp(`\`\`\`${language}\\n([\\s\\S]*?)\\n\`\`\``, 'i');
121
+ const langMatch = response.match(langRegex);
122
+ if (langMatch?.[1])
123
+ return langMatch[1].trim();
124
+ // Try generic code block
125
+ const genericMatch = response.match(/```\w*\n([\s\S]*?)\n```/);
126
+ if (genericMatch?.[1])
127
+ return genericMatch[1].trim();
128
+ // Try to find code without block
129
+ const lines = response.split('\n');
130
+ const codeLines = lines.filter(line => {
131
+ const trimmed = line.trim();
132
+ return trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('//');
133
+ });
134
+ return codeLines.length > 0 ? codeLines.join('\n') : null;
135
+ }
136
+ /**
137
+ * Batch auto-fix multiple examples
138
+ */
139
+ export async function autoFixBatch(examples, client, options = {}) {
140
+ const results = new Map();
141
+ for (let i = 0; i < examples.length; i++) {
142
+ const example = examples[i];
143
+ if (!example)
144
+ continue;
145
+ console.log(` [${i + 1}/${examples.length}] Fixing ${example.language} example...`);
146
+ const result = await autoFixExample(example, client, options);
147
+ results.set(i, result);
148
+ if (result.success) {
149
+ console.log(` ✓ Fixed in ${result.iterations} iteration(s)`);
150
+ }
151
+ else {
152
+ console.log(` ✗ Could not fix after ${result.iterations} iteration(s)`);
153
+ }
154
+ }
155
+ return results;
156
+ }
157
+ /**
158
+ * Create a TypeScript validator using tsc
159
+ */
160
+ export function createTypeScriptValidator() {
161
+ return async (code) => {
162
+ try {
163
+ const ts = await import('typescript');
164
+ const result = ts.transpileModule(code, {
165
+ compilerOptions: {
166
+ module: ts.ModuleKind.ESNext,
167
+ target: ts.ScriptTarget.ES2020,
168
+ strict: true,
169
+ noEmit: true,
170
+ },
171
+ reportDiagnostics: true,
172
+ });
173
+ if (result.diagnostics && result.diagnostics.length > 0) {
174
+ return {
175
+ valid: false,
176
+ errors: result.diagnostics.map(d => typeof d.messageText === 'string' ? d.messageText : d.messageText.messageText),
177
+ };
178
+ }
179
+ return { valid: true, errors: [] };
180
+ }
181
+ catch (err) {
182
+ const message = err instanceof Error ? err.message : String(err);
183
+ return {
184
+ valid: false,
185
+ errors: [message],
186
+ };
187
+ }
188
+ };
189
+ }
190
+ /**
191
+ * Create a Python validator (requires python3 in PATH)
192
+ */
193
+ export function createPythonValidator() {
194
+ return async (code) => {
195
+ try {
196
+ const { spawnSync } = await import('child_process');
197
+ const { writeFileSync, unlinkSync } = await import('fs');
198
+ const { join } = await import('path');
199
+ const { tmpdir } = await import('os');
200
+ const { randomUUID } = await import('crypto');
201
+ // Use crypto.randomUUID() to avoid timestamp collisions
202
+ const tempFile = join(tmpdir(), `autofix_${randomUUID()}.py`);
203
+ writeFileSync(tempFile, code);
204
+ try {
205
+ // Use spawnSync with array args to avoid command injection
206
+ const result = spawnSync('python3', ['-m', 'py_compile', tempFile], {
207
+ encoding: 'utf-8',
208
+ stdio: ['pipe', 'pipe', 'pipe'],
209
+ });
210
+ if (result.status !== 0) {
211
+ throw { stderr: result.stderr, message: result.stderr };
212
+ }
213
+ return { valid: true, errors: [] };
214
+ }
215
+ catch (err) {
216
+ const errObj = err;
217
+ const message = errObj.stderr || errObj.message || String(err);
218
+ return {
219
+ valid: false,
220
+ errors: [message],
221
+ };
222
+ }
223
+ finally {
224
+ try {
225
+ unlinkSync(tempFile);
226
+ }
227
+ catch {
228
+ // Ignore cleanup errors
229
+ }
230
+ }
231
+ }
232
+ catch (err) {
233
+ const message = err instanceof Error ? err.message : String(err);
234
+ return {
235
+ valid: false,
236
+ errors: [message],
237
+ };
238
+ }
239
+ };
240
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { generateCommand } from './commands/generate.js';
4
+ import { initCommand } from './commands/init.js';
5
+ import { watchCommand } from './commands/watch.js';
6
+ import { autofixCommand } from './commands/autofix.js';
7
+ import { reviewPRCommand } from './commands/review-pr.js';
8
+ const VERSION = '0.1.0';
9
+ async function checkForUpdates() {
10
+ try {
11
+ const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
12
+ signal: AbortSignal.timeout(2000)
13
+ });
14
+ if (res.ok) {
15
+ const data = await res.json();
16
+ const latest = data.version;
17
+ if (latest && latest !== VERSION) {
18
+ console.log(`\n Update available: ${VERSION} → ${latest}`);
19
+ console.log(` Run: npm install -g skrypt-ai@latest\n`);
20
+ }
21
+ }
22
+ }
23
+ catch {
24
+ // Silently fail - don't block CLI
25
+ }
26
+ }
27
+ const program = new Command();
28
+ program
29
+ .name('skrypt')
30
+ .description('AI-powered documentation generator with code examples')
31
+ .version(VERSION)
32
+ .hook('postAction', async () => {
33
+ await checkForUpdates();
34
+ });
35
+ program.addCommand(initCommand);
36
+ program.addCommand(generateCommand);
37
+ program.addCommand(watchCommand);
38
+ program.addCommand(autofixCommand);
39
+ program.addCommand(reviewPRCommand);
40
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const autofixCommand: Command;
@@ -0,0 +1,143 @@
1
+ import { Command } from 'commander';
2
+ import { readFileSync, writeFileSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { loadConfig, checkApiKey } from '../config/index.js';
5
+ import { createLLMClient } from '../llm/index.js';
6
+ import { autoFixExample, createTypeScriptValidator, createPythonValidator } from '../autofix/index.js';
7
+ export const autofixCommand = new Command('autofix')
8
+ .description('Auto-fix broken code examples in documentation')
9
+ .argument('<file>', 'Documentation file to fix (markdown/mdx)')
10
+ .option('-c, --config <file>', 'Config file path')
11
+ .option('--provider <name>', 'LLM provider')
12
+ .option('--model <name>', 'LLM model')
13
+ .option('--max-iterations <n>', 'Max fix attempts per example', '3')
14
+ .option('--dry-run', 'Show fixes without writing')
15
+ .option('--language <lang>', 'Force language for validation')
16
+ .action(async (file, options) => {
17
+ const config = loadConfig(options.config);
18
+ if (options.provider)
19
+ config.llm.provider = options.provider;
20
+ if (options.model)
21
+ config.llm.model = options.model;
22
+ const { ok, envKey } = checkApiKey(config.llm.provider);
23
+ if (!ok && envKey) {
24
+ console.error(`Error: ${envKey} environment variable required`);
25
+ process.exit(1);
26
+ }
27
+ const filePath = resolve(file);
28
+ console.log(`skrypt autofix`);
29
+ console.log(` file: ${filePath}`);
30
+ console.log(` provider: ${config.llm.provider}`);
31
+ console.log(` max iterations: ${options.maxIterations}`);
32
+ console.log('');
33
+ // Read the file
34
+ let content;
35
+ try {
36
+ content = readFileSync(filePath, 'utf-8');
37
+ }
38
+ catch (err) {
39
+ const errObj = err;
40
+ if (errObj.code === 'ENOENT') {
41
+ console.error(`Error: File not found: ${filePath}`);
42
+ }
43
+ else if (errObj.code === 'EACCES') {
44
+ console.error(`Error: Permission denied: ${filePath}`);
45
+ }
46
+ else {
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ console.error(`Error: Could not read file: ${filePath} (${message})`);
49
+ }
50
+ process.exit(1);
51
+ }
52
+ // Find all code blocks
53
+ const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
54
+ const codeBlocks = [];
55
+ let match;
56
+ while ((match = codeBlockRegex.exec(content)) !== null) {
57
+ const code = match[2];
58
+ if (!code)
59
+ continue;
60
+ codeBlocks.push({
61
+ match: match[0],
62
+ language: options.language || match[1] || 'javascript',
63
+ code,
64
+ index: match.index,
65
+ });
66
+ }
67
+ if (codeBlocks.length === 0) {
68
+ console.log('No code blocks found in file.');
69
+ process.exit(0);
70
+ }
71
+ console.log(`Found ${codeBlocks.length} code block(s)`);
72
+ console.log('');
73
+ const client = createLLMClient({
74
+ provider: config.llm.provider,
75
+ model: config.llm.model,
76
+ baseUrl: config.llm.baseUrl,
77
+ });
78
+ const maxIterations = parseInt(options.maxIterations);
79
+ // Create validators
80
+ const validators = {
81
+ javascript: createTypeScriptValidator(),
82
+ typescript: createTypeScriptValidator(),
83
+ ts: createTypeScriptValidator(),
84
+ js: createTypeScriptValidator(),
85
+ python: createPythonValidator(),
86
+ py: createPythonValidator(),
87
+ };
88
+ let fixedCount = 0;
89
+ let failedCount = 0;
90
+ let newContent = content;
91
+ for (let i = 0; i < codeBlocks.length; i++) {
92
+ const block = codeBlocks[i];
93
+ if (!block)
94
+ continue;
95
+ const validator = validators[block.language.toLowerCase()];
96
+ if (!validator) {
97
+ console.log(`[${i + 1}/${codeBlocks.length}] Skipping ${block.language} (no validator)`);
98
+ continue;
99
+ }
100
+ console.log(`[${i + 1}/${codeBlocks.length}] Checking ${block.language} example...`);
101
+ // First check if it's valid
102
+ const initial = await validator(block.code);
103
+ if (initial.valid) {
104
+ console.log(` ✓ Already valid`);
105
+ continue;
106
+ }
107
+ console.log(` ✗ Invalid: ${initial.errors[0]?.slice(0, 60)}...`);
108
+ console.log(` → Attempting to fix...`);
109
+ const result = await autoFixExample({
110
+ code: block.code,
111
+ language: block.language,
112
+ context: `Code example from documentation file: ${file}`,
113
+ }, client, {
114
+ maxIterations,
115
+ validateFn: validator,
116
+ language: block.language,
117
+ });
118
+ if (result.success) {
119
+ console.log(` ✓ Fixed in ${result.iterations} iteration(s)`);
120
+ fixedCount++;
121
+ // Replace in content
122
+ const newBlock = '```' + block.language + '\n' + result.fixedCode + '```';
123
+ newContent = newContent.replace(block.match, newBlock);
124
+ }
125
+ else {
126
+ console.log(` ✗ Could not fix after ${result.iterations} iterations`);
127
+ failedCount++;
128
+ }
129
+ }
130
+ console.log('');
131
+ console.log('=== Summary ===');
132
+ console.log(` Checked: ${codeBlocks.length}`);
133
+ console.log(` Fixed: ${fixedCount}`);
134
+ console.log(` Failed: ${failedCount}`);
135
+ if (fixedCount > 0 && !options.dryRun) {
136
+ writeFileSync(filePath, newContent);
137
+ console.log(`\nWrote fixes to ${filePath}`);
138
+ }
139
+ else if (fixedCount > 0 && options.dryRun) {
140
+ console.log(`\n[dry run - no changes written]`);
141
+ }
142
+ console.log('\nDone!');
143
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const generateCommand: Command;