skrypt-ai 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/auth/index.js +8 -1
  2. package/dist/autofix/index.d.ts +0 -4
  3. package/dist/autofix/index.js +0 -21
  4. package/dist/capture/browser.d.ts +11 -0
  5. package/dist/capture/browser.js +173 -0
  6. package/dist/capture/diff.d.ts +23 -0
  7. package/dist/capture/diff.js +52 -0
  8. package/dist/capture/index.d.ts +23 -0
  9. package/dist/capture/index.js +210 -0
  10. package/dist/capture/naming.d.ts +17 -0
  11. package/dist/capture/naming.js +45 -0
  12. package/dist/capture/parser.d.ts +15 -0
  13. package/dist/capture/parser.js +80 -0
  14. package/dist/capture/types.d.ts +57 -0
  15. package/dist/capture/types.js +1 -0
  16. package/dist/cli.js +4 -0
  17. package/dist/commands/autofix.js +136 -120
  18. package/dist/commands/cron.js +58 -47
  19. package/dist/commands/deploy.js +123 -102
  20. package/dist/commands/generate.js +88 -6
  21. package/dist/commands/heal.d.ts +10 -0
  22. package/dist/commands/heal.js +201 -0
  23. package/dist/commands/i18n.js +146 -111
  24. package/dist/commands/lint.js +50 -44
  25. package/dist/commands/llms-txt.js +59 -49
  26. package/dist/commands/login.js +61 -43
  27. package/dist/commands/mcp.js +6 -0
  28. package/dist/commands/monitor.js +13 -8
  29. package/dist/commands/qa.d.ts +2 -0
  30. package/dist/commands/qa.js +43 -0
  31. package/dist/commands/review-pr.js +114 -103
  32. package/dist/commands/sdk.js +128 -122
  33. package/dist/commands/security.js +86 -80
  34. package/dist/commands/test.js +91 -92
  35. package/dist/commands/version.js +104 -75
  36. package/dist/commands/watch.js +130 -114
  37. package/dist/config/types.js +2 -2
  38. package/dist/context-hub/index.d.ts +23 -0
  39. package/dist/context-hub/index.js +179 -0
  40. package/dist/context-hub/mappings.d.ts +8 -0
  41. package/dist/context-hub/mappings.js +55 -0
  42. package/dist/context-hub/types.d.ts +33 -0
  43. package/dist/context-hub/types.js +1 -0
  44. package/dist/generator/generator.js +39 -6
  45. package/dist/generator/types.d.ts +7 -0
  46. package/dist/generator/writer.d.ts +3 -1
  47. package/dist/generator/writer.js +24 -4
  48. package/dist/llm/anthropic-client.d.ts +1 -0
  49. package/dist/llm/anthropic-client.js +3 -1
  50. package/dist/llm/index.d.ts +6 -4
  51. package/dist/llm/index.js +76 -261
  52. package/dist/llm/openai-client.d.ts +1 -0
  53. package/dist/llm/openai-client.js +7 -2
  54. package/dist/qa/checks.d.ts +10 -0
  55. package/dist/qa/checks.js +492 -0
  56. package/dist/qa/fixes.d.ts +30 -0
  57. package/dist/qa/fixes.js +277 -0
  58. package/dist/qa/index.d.ts +29 -0
  59. package/dist/qa/index.js +187 -0
  60. package/dist/qa/types.d.ts +24 -0
  61. package/dist/qa/types.js +1 -0
  62. package/dist/scanner/csharp.d.ts +23 -0
  63. package/dist/scanner/csharp.js +421 -0
  64. package/dist/scanner/index.js +16 -2
  65. package/dist/scanner/java.d.ts +39 -0
  66. package/dist/scanner/java.js +318 -0
  67. package/dist/scanner/kotlin.d.ts +23 -0
  68. package/dist/scanner/kotlin.js +389 -0
  69. package/dist/scanner/php.d.ts +57 -0
  70. package/dist/scanner/php.js +351 -0
  71. package/dist/scanner/ruby.d.ts +36 -0
  72. package/dist/scanner/ruby.js +431 -0
  73. package/dist/scanner/swift.d.ts +25 -0
  74. package/dist/scanner/swift.js +392 -0
  75. package/dist/scanner/types.d.ts +1 -1
  76. package/dist/template/content/docs/_navigation.json +46 -0
  77. package/dist/template/content/docs/_sidebars.json +684 -0
  78. package/dist/template/content/docs/core.md +4544 -0
  79. package/dist/template/content/docs/index.mdx +89 -0
  80. package/dist/template/content/docs/integrations.md +1158 -0
  81. package/dist/template/content/docs/llms-full.md +403 -0
  82. package/dist/template/content/docs/llms.txt +4588 -0
  83. package/dist/template/content/docs/other.md +10379 -0
  84. package/dist/template/content/docs/tools.md +746 -0
  85. package/dist/template/content/docs/types.md +531 -0
  86. package/dist/template/docs.json +13 -11
  87. package/dist/template/mdx-components.tsx +27 -2
  88. package/dist/template/package.json +6 -0
  89. package/dist/template/public/search-index.json +1 -1
  90. package/dist/template/scripts/build-search-index.mjs +84 -6
  91. package/dist/template/src/app/api/chat/route.ts +83 -128
  92. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  93. package/dist/template/src/app/docs/llms-full.md +151 -4
  94. package/dist/template/src/app/docs/llms.txt +2464 -847
  95. package/dist/template/src/app/docs/page.mdx +48 -38
  96. package/dist/template/src/app/layout.tsx +3 -1
  97. package/dist/template/src/app/page.tsx +22 -8
  98. package/dist/template/src/components/ai-chat.tsx +73 -64
  99. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  100. package/dist/template/src/components/copy-button.tsx +13 -9
  101. package/dist/template/src/components/copy-page-button.tsx +54 -0
  102. package/dist/template/src/components/docs-layout.tsx +37 -25
  103. package/dist/template/src/components/header.tsx +51 -10
  104. package/dist/template/src/components/mdx/card.tsx +17 -3
  105. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  106. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  107. package/dist/template/src/components/mdx/heading.tsx +15 -2
  108. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  109. package/dist/template/src/components/mdx/index.tsx +2 -0
  110. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  111. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  112. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  113. package/dist/template/src/components/sidebar.tsx +12 -18
  114. package/dist/template/src/components/table-of-contents.tsx +9 -0
  115. package/dist/template/src/lib/highlight.ts +3 -88
  116. package/dist/template/src/lib/navigation.ts +159 -0
  117. package/dist/template/src/styles/globals.css +17 -6
  118. package/dist/utils/validation.d.ts +0 -3
  119. package/dist/utils/validation.js +0 -26
  120. package/package.json +3 -2
@@ -229,6 +229,12 @@ export const mcpCommand = new Command('mcp')
229
229
  // Get specific doc
230
230
  if (url.pathname.startsWith('/doc/')) {
231
231
  const docPath = decodeURIComponent(url.pathname.slice(5));
232
+ // Prevent path traversal
233
+ if (docPath.includes('..') || docPath.startsWith('/')) {
234
+ res.writeHead(400, { 'Content-Type': 'application/json' });
235
+ res.end(JSON.stringify({ error: 'Invalid path' }));
236
+ return;
237
+ }
232
238
  const doc = docs.find(d => d.path === docPath || d.path === docPath + '.mdx' || d.path === docPath + '.md');
233
239
  if (doc) {
234
240
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { execSync, spawnSync } from 'child_process';
2
+ import { spawnSync } from 'child_process';
3
3
  import { existsSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'fs';
4
4
  import { resolve, join, dirname } from 'path';
5
5
  import { createLLMClient } from '../llm/index.js';
@@ -275,16 +275,21 @@ export const monitorCommand = new Command('monitor')
275
275
  console.log('\n=== Creating GitHub PR ===\n');
276
276
  try {
277
277
  // Check if gh CLI is available
278
- execSync('gh --version', { stdio: 'ignore' });
279
- // Create branch
278
+ spawnSync('gh', ['--version'], { stdio: 'ignore' });
279
+ // Create branch (use spawnSync to avoid shell injection)
280
280
  const branchName = `docs/auto-update-${Date.now()}`;
281
- execSync(`git checkout -b ${branchName}`, { cwd: resolvedPath });
281
+ spawnSync('git', ['checkout', '-b', branchName], { cwd: resolvedPath, stdio: 'inherit' });
282
282
  // Stage and commit
283
- execSync('git add docs/', { cwd: resolvedPath });
284
- execSync(`git commit -m "docs: auto-update documentation for recent changes"`, { cwd: resolvedPath });
283
+ spawnSync('git', ['add', 'docs/'], { cwd: resolvedPath, stdio: 'inherit' });
284
+ spawnSync('git', ['commit', '-m', 'docs: auto-update documentation for recent changes'], { cwd: resolvedPath, stdio: 'inherit' });
285
285
  // Push and create PR
286
- execSync(`git push -u origin ${branchName}`, { cwd: resolvedPath });
287
- const prUrl = execSync(`gh pr create --title "docs: Auto-update documentation" --body "This PR was automatically generated by Skrypt monitor.\n\n## Changes\n${suggestions.map(s => `- ${s.action}: ${s.file}`).join('\n')}"`, { cwd: resolvedPath, encoding: 'utf-8' }).trim();
286
+ spawnSync('git', ['push', '-u', 'origin', branchName], { cwd: resolvedPath, stdio: 'inherit' });
287
+ const changesList = suggestions.map(s => `- ${s.action}: ${s.file}`).join('\n');
288
+ const prBody = `This PR was automatically generated by Skrypt monitor.\n\n## Changes\n${changesList}`;
289
+ const prResult = spawnSync('gh', ['pr', 'create', '--title', 'docs: Auto-update documentation', '--body', prBody], {
290
+ cwd: resolvedPath, encoding: 'utf-8'
291
+ });
292
+ const prUrl = (prResult.stdout || '').trim();
288
293
  console.log(`PR created: ${prUrl}`);
289
294
  }
290
295
  catch (err) {
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const qaCommand: Command;
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import { resolve } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { runQA, printQAReport, fixQAIssues, printFixReport } from '../qa/index.js';
5
+ export const qaCommand = new Command('qa')
6
+ .description('Run quality assurance checks on documentation')
7
+ .argument('<dir>', 'Documentation directory to check')
8
+ .option('--strict', 'Fail on warnings (not just errors)')
9
+ .option('--fix', 'Auto-fix safe issues before reporting')
10
+ .action((dir, options) => {
11
+ const targetDir = resolve(dir);
12
+ if (!existsSync(targetDir)) {
13
+ console.error(`Error: Directory not found: ${targetDir}`);
14
+ process.exit(1);
15
+ }
16
+ console.log('skrypt qa');
17
+ console.log(` target: ${targetDir}`);
18
+ // Auto-fix if requested
19
+ if (options.fix) {
20
+ const fixReport = fixQAIssues(targetDir);
21
+ printFixReport(fixReport);
22
+ }
23
+ const report = runQA(targetDir);
24
+ printQAReport(report);
25
+ // Show check-by-check summary
26
+ console.log('\n Checks:');
27
+ for (const check of report.checks) {
28
+ const icon = check.passed ? '✓' : '✗';
29
+ const count = check.issues.length;
30
+ const label = count > 0 ? ` (${count} issue${count > 1 ? 's' : ''})` : '';
31
+ console.log(` ${icon} ${check.check}${label}`);
32
+ }
33
+ console.log('');
34
+ if (!report.passed) {
35
+ console.log('QA failed — fix errors before deploying.\n');
36
+ process.exit(1);
37
+ }
38
+ if (options.strict && report.warnings > 0) {
39
+ console.log('QA failed (strict mode) — warnings present.\n');
40
+ process.exit(1);
41
+ }
42
+ console.log('QA passed.\n');
43
+ });
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { analyzePRForDocs, postPRComment, postInlineComments } from '../github/pr-comments.js';
3
+ import { requirePro } from '../auth/index.js';
3
4
  export const reviewPRCommand = new Command('review-pr')
4
5
  .description('Review a GitHub PR for documentation issues')
5
6
  .argument('<pr-url>', 'GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)')
@@ -7,121 +8,131 @@ export const reviewPRCommand = new Command('review-pr')
7
8
  .option('--dry-run', 'Show issues without posting comments')
8
9
  .option('--token <token>', 'GitHub token (or use GITHUB_TOKEN env var)')
9
10
  .action(async (prUrl, options) => {
10
- // Parse PR URL
11
- const urlMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
12
- if (!urlMatch) {
13
- console.error('Error: Invalid PR URL format');
14
- console.error('Expected: https://github.com/owner/repo/pull/123');
15
- process.exit(1);
16
- }
17
- const owner = urlMatch[1];
18
- const repo = urlMatch[2];
19
- const pullNumber = urlMatch[3];
20
- if (!owner || !repo || !pullNumber) {
21
- console.error('Error: Could not parse owner/repo/PR number from URL');
22
- process.exit(1);
23
- }
24
- // Validate owner/repo contain only safe characters (prevent API path injection)
25
- const safeNamePattern = /^[a-zA-Z0-9._-]+$/;
26
- if (!safeNamePattern.test(owner) || !safeNamePattern.test(repo)) {
27
- console.error('Error: Invalid characters in owner/repo name');
28
- process.exit(1);
29
- }
30
- console.log('skrypt review-pr');
31
- console.log(` repo: ${owner}/${repo}`);
32
- console.log(` PR: #${pullNumber}`);
33
- console.log('');
34
- if (options.token) {
35
- console.warn(' Warning: Passing tokens via CLI arguments exposes them in shell history.');
36
- console.warn(' Prefer: GITHUB_TOKEN env var\n');
37
- }
38
- const token = options.token || process.env.GITHUB_TOKEN;
39
- if (!token) {
40
- console.error('Error: GITHUB_TOKEN environment variable or --token required');
41
- process.exit(1);
42
- }
43
- // Analyze the PR
44
- console.log('Analyzing PR for documentation issues...');
45
- const config = {
46
- owner,
47
- repo,
48
- pullNumber: parseInt(pullNumber),
49
- token,
50
- };
51
- let issues;
52
11
  try {
53
- issues = await analyzePRForDocs(config);
54
- }
55
- catch (err) {
56
- const message = err instanceof Error ? err.message : String(err);
57
- console.error(`Error analyzing PR: ${message}`);
58
- process.exit(1);
59
- }
60
- if (issues.length === 0) {
61
- console.log('\n✓ No documentation issues found!');
62
- process.exit(0);
63
- }
64
- console.log(`\nFound ${issues.length} issue(s):`);
65
- console.log('');
66
- // Group by severity
67
- const errors = issues.filter(i => i.severity === 'error');
68
- const warnings = issues.filter(i => i.severity === 'warning');
69
- const infos = issues.filter(i => i.severity === 'info');
70
- if (errors.length > 0) {
71
- console.log(`❌ Errors (${errors.length}):`);
72
- for (const issue of errors) {
73
- console.log(` ${issue.file}: ${issue.message}`);
12
+ // Pro feature - requires subscription
13
+ if (!await requirePro('review-pr')) {
14
+ process.exit(1);
74
15
  }
75
- }
76
- if (warnings.length > 0) {
77
- console.log(`⚠️ Warnings (${warnings.length}):`);
78
- for (const issue of warnings) {
79
- console.log(` ${issue.file}: ${issue.message}`);
16
+ // Parse PR URL
17
+ const urlMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
18
+ if (!urlMatch) {
19
+ console.error('Error: Invalid PR URL format');
20
+ console.error('Expected: https://github.com/owner/repo/pull/123');
21
+ process.exit(1);
80
22
  }
81
- }
82
- if (infos.length > 0) {
83
- console.log(`ℹ️ Suggestions (${infos.length}):`);
84
- for (const issue of infos) {
85
- console.log(` ${issue.file}: ${issue.message}`);
23
+ const owner = urlMatch[1];
24
+ const repo = urlMatch[2];
25
+ const pullNumber = urlMatch[3];
26
+ if (!owner || !repo || !pullNumber) {
27
+ console.error('Error: Could not parse owner/repo/PR number from URL');
28
+ process.exit(1);
86
29
  }
87
- }
88
- if (options.dryRun) {
89
- console.log('\n[dry run - no comments posted]');
90
- process.exit(0);
91
- }
92
- // Post comments
93
- console.log('\nPosting comments to PR...');
94
- if (options.inline) {
95
- const inlineIssues = issues.filter(i => i.line);
96
- const generalIssues = issues.filter(i => !i.line);
97
- if (inlineIssues.length > 0) {
98
- const inlineResults = await postInlineComments(config, inlineIssues);
99
- const success = inlineResults.filter(r => r.success).length;
100
- console.log(` Posted ${success}/${inlineIssues.length} inline comments`);
101
- }
102
- if (generalIssues.length > 0) {
103
- const result = await postPRComment(config, generalIssues);
104
- if (result.success) {
105
- console.log(` Posted summary comment`);
30
+ // Validate owner/repo contain only safe characters (prevent API path injection)
31
+ const safeNamePattern = /^[a-zA-Z0-9._-]+$/;
32
+ if (!safeNamePattern.test(owner) || !safeNamePattern.test(repo)) {
33
+ console.error('Error: Invalid characters in owner/repo name');
34
+ process.exit(1);
35
+ }
36
+ console.log('skrypt review-pr');
37
+ console.log(` repo: ${owner}/${repo}`);
38
+ console.log(` PR: #${pullNumber}`);
39
+ console.log('');
40
+ if (options.token) {
41
+ console.warn(' Warning: Passing tokens via CLI arguments exposes them in shell history.');
42
+ console.warn(' Prefer: GITHUB_TOKEN env var\n');
43
+ }
44
+ const token = options.token || process.env.GITHUB_TOKEN;
45
+ if (!token) {
46
+ console.error('Error: GITHUB_TOKEN environment variable or --token required');
47
+ process.exit(1);
48
+ }
49
+ // Analyze the PR
50
+ console.log('Analyzing PR for documentation issues...');
51
+ const config = {
52
+ owner,
53
+ repo,
54
+ pullNumber: parseInt(pullNumber),
55
+ token,
56
+ };
57
+ let issues;
58
+ try {
59
+ issues = await analyzePRForDocs(config);
60
+ }
61
+ catch (err) {
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ console.error(`Error analyzing PR: ${message}`);
64
+ process.exit(1);
65
+ }
66
+ if (issues.length === 0) {
67
+ console.log('\n✓ No documentation issues found!');
68
+ process.exit(0);
69
+ }
70
+ console.log(`\nFound ${issues.length} issue(s):`);
71
+ console.log('');
72
+ // Group by severity
73
+ const errors = issues.filter(i => i.severity === 'error');
74
+ const warnings = issues.filter(i => i.severity === 'warning');
75
+ const infos = issues.filter(i => i.severity === 'info');
76
+ if (errors.length > 0) {
77
+ console.log(`❌ Errors (${errors.length}):`);
78
+ for (const issue of errors) {
79
+ console.log(` ${issue.file}: ${issue.message}`);
106
80
  }
107
- else {
108
- console.error(` Failed to post summary: ${result.error}`);
81
+ }
82
+ if (warnings.length > 0) {
83
+ console.log(`⚠️ Warnings (${warnings.length}):`);
84
+ for (const issue of warnings) {
85
+ console.log(` ${issue.file}: ${issue.message}`);
109
86
  }
110
87
  }
111
- }
112
- else {
113
- const result = await postPRComment(config, issues);
114
- if (result.success) {
115
- console.log(`✓ Comment posted successfully`);
88
+ if (infos.length > 0) {
89
+ console.log(`ℹ️ Suggestions (${infos.length}):`);
90
+ for (const issue of infos) {
91
+ console.log(` ${issue.file}: ${issue.message}`);
92
+ }
93
+ }
94
+ if (options.dryRun) {
95
+ console.log('\n[dry run - no comments posted]');
96
+ process.exit(0);
97
+ }
98
+ // Post comments
99
+ console.log('\nPosting comments to PR...');
100
+ if (options.inline) {
101
+ const inlineIssues = issues.filter(i => i.line);
102
+ const generalIssues = issues.filter(i => !i.line);
103
+ if (inlineIssues.length > 0) {
104
+ const inlineResults = await postInlineComments(config, inlineIssues);
105
+ const success = inlineResults.filter(r => r.success).length;
106
+ console.log(` Posted ${success}/${inlineIssues.length} inline comments`);
107
+ }
108
+ if (generalIssues.length > 0) {
109
+ const result = await postPRComment(config, generalIssues);
110
+ if (result.success) {
111
+ console.log(` Posted summary comment`);
112
+ }
113
+ else {
114
+ console.error(` Failed to post summary: ${result.error}`);
115
+ }
116
+ }
116
117
  }
117
118
  else {
118
- console.error(`✗ Failed to post comment: ${result.error}`);
119
+ const result = await postPRComment(config, issues);
120
+ if (result.success) {
121
+ console.log(`✓ Comment posted successfully`);
122
+ }
123
+ else {
124
+ console.error(`✗ Failed to post comment: ${result.error}`);
125
+ process.exit(1);
126
+ }
127
+ }
128
+ console.log('\nDone!');
129
+ // Exit with error code if there are errors
130
+ if (errors.length > 0) {
119
131
  process.exit(1);
120
132
  }
121
133
  }
122
- console.log('\nDone!');
123
- // Exit with error code if there are errors
124
- if (errors.length > 0) {
134
+ catch (err) {
135
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
125
136
  process.exit(1);
126
137
  }
127
138
  });
@@ -223,115 +223,116 @@ export const sdkCommand = new Command('sdk')
223
223
  .option('--model <name>', 'LLM model')
224
224
  .option('--format <type>', 'Output format (markdown or json)', 'markdown')
225
225
  .action(async (specPath, options) => {
226
- // Pro feature - requires subscription
227
- if (!await requirePro('sdk')) {
228
- process.exit(1);
229
- }
230
- const resolvedSpec = resolve(specPath);
231
- if (!existsSync(resolvedSpec)) {
232
- console.error(`Error: Spec file not found: ${resolvedSpec}`);
233
- process.exit(1);
234
- }
235
- console.log('skrypt sdk');
236
- console.log(` spec: ${resolvedSpec}`);
237
- console.log(` output: ${options.output}`);
238
- console.log(` languages: ${options.languages}`);
239
- console.log('');
240
- // Parse spec
241
- console.log('Step 1: Parsing OpenAPI spec...');
242
- const specContent = readFileSync(resolvedSpec, 'utf-8');
243
- let spec;
244
226
  try {
245
- spec = await parseOpenAPISpec(specContent);
246
- }
247
- catch (err) {
248
- console.error('Error: Failed to parse spec file:', err);
249
- process.exit(1);
250
- }
251
- const apiTitle = spec.info?.title || basename(specPath, '.json');
252
- const apiVersion = spec.info?.version || '1.0.0';
253
- console.log(` API: ${apiTitle} v${apiVersion}`);
254
- if (!spec.paths || Object.keys(spec.paths).length === 0) {
255
- console.error('Error: No paths found in spec');
256
- process.exit(1);
257
- }
258
- // Parse languages
259
- const languages = (options.languages || 'typescript,python,curl')
260
- .split(',')
261
- .map(l => l.trim().toLowerCase())
262
- .filter(l => SUPPORTED_LANGUAGES.includes(l));
263
- if (languages.length === 0) {
264
- console.error(`Error: No valid languages. Supported: ${SUPPORTED_LANGUAGES.join(', ')}`);
265
- process.exit(1);
266
- }
267
- console.log(` Generating for: ${languages.join(', ')}`);
268
- // Check if we should use AI for enhanced samples
269
- let client = null;
270
- if (options.provider) {
271
- const config = loadConfig();
272
- config.llm.provider = options.provider;
273
- const { ok } = checkApiKey(config.llm.provider);
274
- if (ok) {
275
- client = createLLMClient({
276
- provider: config.llm.provider,
277
- model: options.model || config.llm.model
278
- });
279
- console.log(` Using AI enhancement: ${config.llm.provider}`);
227
+ // Pro feature - requires subscription
228
+ if (!await requirePro('sdk')) {
229
+ process.exit(1);
280
230
  }
281
- }
282
- // Generate samples for each endpoint
283
- console.log('\nStep 2: Generating code samples...');
284
- const allSamples = [];
285
- for (const [path, methods] of Object.entries(spec.paths || {})) {
286
- for (const [method, details] of Object.entries(methods || {})) {
287
- if (!['get', 'post', 'put', 'patch', 'delete'].includes(method))
288
- continue;
289
- const operationId = details.operationId || `${method}${path.replace(/\//g, '_').replace(/[{}]/g, '')}`;
290
- const summary = details.summary || details.description || '';
291
- console.log(` ${method.toUpperCase()} ${path}`);
292
- const params = (details.parameters || []).map(p => ({
293
- name: p.name,
294
- type: p.schema?.type,
295
- required: p.required
296
- }));
297
- const hasBody = !!details.requestBody;
298
- const bodySchema = details.requestBody?.content?.['application/json']?.schema;
299
- const samples = [];
300
- for (const lang of languages) {
301
- let code;
302
- if (client) {
303
- code = await generateAISample({ path, method, operationId, summary, params, hasBody, bodySchema }, lang, client);
304
- }
305
- else {
306
- code = generateBasicSample({
307
- path,
308
- method,
309
- operationId,
310
- params: params.map(p => p.name),
311
- hasBody
312
- }, lang, '');
231
+ const resolvedSpec = resolve(specPath);
232
+ if (!existsSync(resolvedSpec)) {
233
+ console.error(`Error: Spec file not found: ${resolvedSpec}`);
234
+ process.exit(1);
235
+ }
236
+ console.log('skrypt sdk');
237
+ console.log(` spec: ${resolvedSpec}`);
238
+ console.log(` output: ${options.output}`);
239
+ console.log(` languages: ${options.languages}`);
240
+ console.log('');
241
+ // Parse spec
242
+ console.log('Step 1: Parsing OpenAPI spec...');
243
+ const specContent = readFileSync(resolvedSpec, 'utf-8');
244
+ let spec;
245
+ try {
246
+ spec = await parseOpenAPISpec(specContent);
247
+ }
248
+ catch (err) {
249
+ console.error('Error: Failed to parse spec file:', err);
250
+ process.exit(1);
251
+ }
252
+ const apiTitle = spec.info?.title || basename(specPath, '.json');
253
+ const apiVersion = spec.info?.version || '1.0.0';
254
+ console.log(` API: ${apiTitle} v${apiVersion}`);
255
+ if (!spec.paths || Object.keys(spec.paths).length === 0) {
256
+ console.error('Error: No paths found in spec');
257
+ process.exit(1);
258
+ }
259
+ // Parse languages
260
+ const languages = (options.languages || 'typescript,python,curl')
261
+ .split(',')
262
+ .map(l => l.trim().toLowerCase())
263
+ .filter(l => SUPPORTED_LANGUAGES.includes(l));
264
+ if (languages.length === 0) {
265
+ console.error(`Error: No valid languages. Supported: ${SUPPORTED_LANGUAGES.join(', ')}`);
266
+ process.exit(1);
267
+ }
268
+ console.log(` Generating for: ${languages.join(', ')}`);
269
+ // Check if we should use AI for enhanced samples
270
+ let client = null;
271
+ if (options.provider) {
272
+ const config = loadConfig();
273
+ config.llm.provider = options.provider;
274
+ const { ok } = checkApiKey(config.llm.provider);
275
+ if (ok) {
276
+ client = createLLMClient({
277
+ provider: config.llm.provider,
278
+ model: options.model || config.llm.model
279
+ });
280
+ console.log(` Using AI enhancement: ${config.llm.provider}`);
281
+ }
282
+ }
283
+ // Generate samples for each endpoint
284
+ console.log('\nStep 2: Generating code samples...');
285
+ const allSamples = [];
286
+ for (const [path, methods] of Object.entries(spec.paths || {})) {
287
+ for (const [method, details] of Object.entries(methods || {})) {
288
+ if (!['get', 'post', 'put', 'patch', 'delete'].includes(method))
289
+ continue;
290
+ const operationId = details.operationId || `${method}${path.replace(/\//g, '_').replace(/[{}]/g, '')}`;
291
+ const summary = details.summary || details.description || '';
292
+ console.log(` ${method.toUpperCase()} ${path}`);
293
+ const params = (details.parameters || []).map(p => ({
294
+ name: p.name,
295
+ type: p.schema?.type,
296
+ required: p.required
297
+ }));
298
+ const hasBody = !!details.requestBody;
299
+ const bodySchema = details.requestBody?.content?.['application/json']?.schema;
300
+ const samples = [];
301
+ for (const lang of languages) {
302
+ let code;
303
+ if (client) {
304
+ code = await generateAISample({ path, method, operationId, summary, params, hasBody, bodySchema }, lang, client);
305
+ }
306
+ else {
307
+ code = generateBasicSample({
308
+ path,
309
+ method,
310
+ operationId,
311
+ params: params.map(p => p.name),
312
+ hasBody
313
+ }, lang, '');
314
+ }
315
+ samples.push({ language: lang, code });
313
316
  }
314
- samples.push({ language: lang, code });
317
+ allSamples.push({ path, method, operationId, summary, samples });
315
318
  }
316
- allSamples.push({ path, method, operationId, summary, samples });
317
319
  }
318
- }
319
- // Write output
320
- console.log('\nStep 3: Writing output...');
321
- const outputDir = resolve(options.output || './docs/sdk');
322
- mkdirSync(outputDir, { recursive: true });
323
- if (options.format === 'json') {
324
- // JSON output
325
- const jsonPath = join(outputDir, 'sdk-samples.json');
326
- writeFileSync(jsonPath, JSON.stringify(allSamples, null, 2));
327
- console.log(` Written: ${jsonPath}`);
328
- }
329
- else {
330
- // Markdown output - one file per endpoint
331
- for (const endpoint of allSamples) {
332
- const fileName = `${endpoint.operationId}.mdx`;
333
- const filePath = join(outputDir, fileName);
334
- let content = `---
320
+ // Write output
321
+ console.log('\nStep 3: Writing output...');
322
+ const outputDir = resolve(options.output || './docs/sdk');
323
+ mkdirSync(outputDir, { recursive: true });
324
+ if (options.format === 'json') {
325
+ // JSON output
326
+ const jsonPath = join(outputDir, 'sdk-samples.json');
327
+ writeFileSync(jsonPath, JSON.stringify(allSamples, null, 2));
328
+ console.log(` Written: ${jsonPath}`);
329
+ }
330
+ else {
331
+ // Markdown output - one file per endpoint
332
+ for (const endpoint of allSamples) {
333
+ const fileName = `${endpoint.operationId}.mdx`;
334
+ const filePath = join(outputDir, fileName);
335
+ let content = `---
335
336
  title: ${endpoint.operationId}
336
337
  description: ${endpoint.summary || `${endpoint.method.toUpperCase()} ${endpoint.path}`}
337
338
  ---
@@ -345,21 +346,21 @@ ${endpoint.summary}
345
346
  ## Code Examples
346
347
 
347
348
  `;
348
- for (const sample of endpoint.samples) {
349
- content += `### ${sample.language.charAt(0).toUpperCase() + sample.language.slice(1)}
349
+ for (const sample of endpoint.samples) {
350
+ content += `### ${sample.language.charAt(0).toUpperCase() + sample.language.slice(1)}
350
351
 
351
352
  \`\`\`${sample.language}
352
353
  ${sample.code}
353
354
  \`\`\`
354
355
 
355
356
  `;
357
+ }
358
+ writeFileSync(filePath, content);
359
+ console.log(` Written: ${filePath}`);
356
360
  }
357
- writeFileSync(filePath, content);
358
- console.log(` Written: ${filePath}`);
359
- }
360
- // Write index file
361
- const indexPath = join(outputDir, 'index.mdx');
362
- let indexContent = `---
361
+ // Write index file
362
+ const indexPath = join(outputDir, 'index.mdx');
363
+ let indexContent = `---
363
364
  title: SDK Code Samples
364
365
  description: Code samples for ${apiTitle} API
365
366
  ---
@@ -371,16 +372,21 @@ Auto-generated code samples for the ${apiTitle} API v${apiVersion}.
371
372
  ## Endpoints
372
373
 
373
374
  `;
374
- for (const endpoint of allSamples) {
375
- indexContent += `- [${endpoint.operationId}](./${endpoint.operationId}) - \`${endpoint.method.toUpperCase()} ${endpoint.path}\`\n`;
375
+ for (const endpoint of allSamples) {
376
+ indexContent += `- [${endpoint.operationId}](./${endpoint.operationId}) - \`${endpoint.method.toUpperCase()} ${endpoint.path}\`\n`;
377
+ }
378
+ writeFileSync(indexPath, indexContent);
379
+ console.log(` Written: ${indexPath}`);
376
380
  }
377
- writeFileSync(indexPath, indexContent);
378
- console.log(` Written: ${indexPath}`);
381
+ // Summary
382
+ console.log('\n=== Summary ===');
383
+ console.log(` Endpoints: ${allSamples.length}`);
384
+ console.log(` Languages: ${languages.length}`);
385
+ console.log(` Total samples: ${allSamples.length * languages.length}`);
386
+ console.log(` Output: ${outputDir}`);
387
+ }
388
+ catch (err) {
389
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
390
+ process.exit(1);
379
391
  }
380
- // Summary
381
- console.log('\n=== Summary ===');
382
- console.log(` Endpoints: ${allSamples.length}`);
383
- console.log(` Languages: ${languages.length}`);
384
- console.log(` Total samples: ${allSamples.length * languages.length}`);
385
- console.log(` Output: ${outputDir}`);
386
392
  });