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
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Extract <Screenshot> directives from MDX files.
3
+ *
4
+ * Scans MDX content for <Screenshot> tags outside code blocks.
5
+ * Uses the same code-block-aware scanning pattern as src/qa/checks.ts.
6
+ */
7
+ /**
8
+ * Parse all <Screenshot> directives from MDX content.
9
+ *
10
+ * Supports both self-closing (<Screenshot ... />) and open/close tags.
11
+ * Extracts url, selector, alt, caption props via regex.
12
+ * Resolves relative URLs against baseUrl.
13
+ */
14
+ export function parseScreenshotDirectives(content, filePath, baseUrl) {
15
+ const directives = [];
16
+ const lines = content.split('\n');
17
+ let inCodeBlock = false;
18
+ for (let i = 0; i < lines.length; i++) {
19
+ const line = lines[i];
20
+ if (line === undefined)
21
+ continue;
22
+ if (line.startsWith('```')) {
23
+ inCodeBlock = !inCodeBlock;
24
+ continue;
25
+ }
26
+ if (inCodeBlock)
27
+ continue;
28
+ // Match <Screenshot ...> or <Screenshot ... />
29
+ const tagMatches = line.matchAll(/<Screenshot\s+([^>]*?)\/?>/g);
30
+ for (const match of tagMatches) {
31
+ const propsStr = match[1];
32
+ if (!propsStr)
33
+ continue;
34
+ const url = extractProp(propsStr, 'url');
35
+ const alt = extractProp(propsStr, 'alt');
36
+ if (!url || !alt)
37
+ continue;
38
+ const selector = extractProp(propsStr, 'selector');
39
+ const caption = extractProp(propsStr, 'caption');
40
+ // Resolve relative URLs against baseUrl
41
+ let resolvedUrl;
42
+ if (url.startsWith('http://') || url.startsWith('https://')) {
43
+ resolvedUrl = url;
44
+ }
45
+ else {
46
+ // Ensure baseUrl doesn't have trailing slash for clean join
47
+ const base = baseUrl.replace(/\/+$/, '');
48
+ const path = url.startsWith('/') ? url : `/${url}`;
49
+ resolvedUrl = `${base}${path}`;
50
+ }
51
+ directives.push({
52
+ url: resolvedUrl,
53
+ selector: selector || undefined,
54
+ alt,
55
+ caption: caption || undefined,
56
+ sourceFile: filePath,
57
+ sourceLine: i + 1,
58
+ });
59
+ }
60
+ }
61
+ return directives;
62
+ }
63
+ /**
64
+ * Extract a prop value from a JSX props string.
65
+ * Handles both quoted values: prop="value" and prop='value'
66
+ * Also handles JSX expressions: prop={"value"} and prop={'value'}
67
+ */
68
+ function extractProp(propsStr, propName) {
69
+ // Match prop="value" or prop='value'
70
+ const quoteRegex = new RegExp(`${propName}\\s*=\\s*["']([^"']*?)["']`);
71
+ const quoteMatch = propsStr.match(quoteRegex);
72
+ if (quoteMatch?.[1] !== undefined)
73
+ return quoteMatch[1];
74
+ // Match prop={"value"} or prop={'value'}
75
+ const jsxRegex = new RegExp(`${propName}\\s*=\\s*\\{\\s*["']([^"']*?)["']\\s*\\}`);
76
+ const jsxMatch = propsStr.match(jsxRegex);
77
+ if (jsxMatch?.[1] !== undefined)
78
+ return jsxMatch[1];
79
+ return null;
80
+ }
@@ -0,0 +1,57 @@
1
+ export interface ScreenshotDirective {
2
+ url: string;
3
+ selector?: string;
4
+ alt: string;
5
+ caption?: string;
6
+ sourceFile: string;
7
+ sourceLine: number;
8
+ }
9
+ export interface ScreenshotResult {
10
+ directive: ScreenshotDirective;
11
+ filename: string;
12
+ darkFilename?: string;
13
+ status: 'captured' | 'unchanged' | 'failed';
14
+ error?: string;
15
+ hash?: string;
16
+ duration: number;
17
+ }
18
+ export interface CaptureReport {
19
+ directives: number;
20
+ captured: number;
21
+ unchanged: number;
22
+ failed: number;
23
+ duration: number;
24
+ results: ScreenshotResult[];
25
+ }
26
+ export interface ScreenshotManifest {
27
+ version: 1;
28
+ capturedAt: string;
29
+ baseUrl: string;
30
+ viewport: {
31
+ width: number;
32
+ height: number;
33
+ };
34
+ screenshots: Record<string, {
35
+ url: string;
36
+ selector?: string;
37
+ filename: string;
38
+ darkFilename?: string;
39
+ capturedAt: string;
40
+ hash: string;
41
+ }>;
42
+ }
43
+ export interface CaptureOptions {
44
+ baseUrl: string;
45
+ outputDir: string;
46
+ viewport: {
47
+ width: number;
48
+ height: number;
49
+ };
50
+ dark: boolean;
51
+ diff: boolean;
52
+ dryRun: boolean;
53
+ timeout: number;
54
+ wait: number;
55
+ device?: string;
56
+ concurrency: number;
57
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { watchCommand } from './commands/watch.js';
6
6
  import { autofixCommand } from './commands/autofix.js';
7
7
  import { reviewPRCommand } from './commands/review-pr.js';
8
8
  import { lintCommand } from './commands/lint.js';
9
+ import { qaCommand } from './commands/qa.js';
9
10
  import { checkLinksCommand } from './commands/check-links.js';
10
11
  import { mcpCommand } from './commands/mcp.js';
11
12
  import { versionCommand } from './commands/version.js';
@@ -20,6 +21,7 @@ import { deployCommand } from './commands/deploy.js';
20
21
  import { testCommand } from './commands/test.js';
21
22
  import { securityCommand } from './commands/security.js';
22
23
  import { importCommand } from './commands/import.js';
24
+ import { healCommand } from './commands/heal.js';
23
25
  import { createRequire } from 'module';
24
26
  process.on('uncaughtException', (err) => {
25
27
  console.error('\x1b[31mFatal error:\x1b[0m', err.message);
@@ -77,6 +79,7 @@ program.addCommand(watchCommand);
77
79
  program.addCommand(autofixCommand);
78
80
  program.addCommand(reviewPRCommand);
79
81
  program.addCommand(lintCommand);
82
+ program.addCommand(qaCommand);
80
83
  program.addCommand(checkLinksCommand);
81
84
  program.addCommand(mcpCommand);
82
85
  program.addCommand(versionCommand);
@@ -93,4 +96,5 @@ program.addCommand(deployCommand);
93
96
  program.addCommand(testCommand);
94
97
  program.addCommand(securityCommand);
95
98
  program.addCommand(importCommand);
99
+ program.addCommand(healCommand);
96
100
  program.parse();
@@ -1,10 +1,11 @@
1
1
  import { Command } from 'commander';
2
2
  import { readFileSync, writeFileSync } from 'fs';
3
- import { resolve } from 'path';
3
+ import { resolve, dirname } from 'path';
4
4
  import { loadConfig, checkApiKey } from '../config/index.js';
5
5
  import { createLLMClient } from '../llm/index.js';
6
6
  import { autoFixExample, createTypeScriptValidator, createPythonValidator } from '../autofix/index.js';
7
7
  import { requirePro } from '../auth/index.js';
8
+ import { runQA, printQAReport, fixQAIssues, printFixReport } from '../qa/index.js';
8
9
  export const autofixCommand = new Command('autofix')
9
10
  .description('Auto-fix broken code examples in documentation')
10
11
  .argument('<file>', 'Documentation file to fix (markdown/mdx)')
@@ -15,134 +16,149 @@ export const autofixCommand = new Command('autofix')
15
16
  .option('--dry-run', 'Show fixes without writing')
16
17
  .option('--language <lang>', 'Force language for validation')
17
18
  .action(async (file, options) => {
18
- // Pro feature - requires subscription
19
- if (!await requirePro('autofix')) {
20
- process.exit(1);
21
- }
22
- const config = loadConfig(options.config);
23
- if (options.provider)
24
- config.llm.provider = options.provider;
25
- if (options.model)
26
- config.llm.model = options.model;
27
- const { ok, envKey } = checkApiKey(config.llm.provider);
28
- if (!ok && envKey) {
29
- console.error(`Error: ${envKey} environment variable required`);
30
- process.exit(1);
31
- }
32
- const filePath = resolve(file);
33
- console.log(`skrypt autofix`);
34
- console.log(` file: ${filePath}`);
35
- console.log(` provider: ${config.llm.provider}`);
36
- console.log(` max iterations: ${options.maxIterations}`);
37
- console.log('');
38
- // Read the file
39
- let content;
40
19
  try {
41
- content = readFileSync(filePath, 'utf-8');
42
- }
43
- catch (err) {
44
- const errObj = err;
45
- if (errObj.code === 'ENOENT') {
46
- console.error(`Error: File not found: ${filePath}`);
20
+ // Pro feature - requires subscription
21
+ if (!await requirePro('autofix')) {
22
+ process.exit(1);
47
23
  }
48
- else if (errObj.code === 'EACCES') {
49
- console.error(`Error: Permission denied: ${filePath}`);
24
+ const config = loadConfig(options.config);
25
+ if (options.provider)
26
+ config.llm.provider = options.provider;
27
+ if (options.model)
28
+ config.llm.model = options.model;
29
+ const { ok, envKey } = checkApiKey(config.llm.provider);
30
+ if (!ok && envKey) {
31
+ console.error(`Error: ${envKey} environment variable required`);
32
+ process.exit(1);
50
33
  }
51
- else {
52
- const message = err instanceof Error ? err.message : String(err);
53
- console.error(`Error: Could not read file: ${filePath} (${message})`);
34
+ const filePath = resolve(file);
35
+ console.log(`skrypt autofix`);
36
+ console.log(` file: ${filePath}`);
37
+ console.log(` provider: ${config.llm.provider}`);
38
+ console.log(` max iterations: ${options.maxIterations}`);
39
+ console.log('');
40
+ // Read the file
41
+ let content;
42
+ try {
43
+ content = readFileSync(filePath, 'utf-8');
54
44
  }
55
- process.exit(1);
56
- }
57
- // Find all code blocks
58
- const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
59
- const codeBlocks = [];
60
- let match;
61
- while ((match = codeBlockRegex.exec(content)) !== null) {
62
- const code = match[2];
63
- if (!code)
64
- continue;
65
- codeBlocks.push({
66
- match: match[0],
67
- language: options.language || match[1] || 'javascript',
68
- code,
69
- index: match.index,
70
- });
71
- }
72
- if (codeBlocks.length === 0) {
73
- console.log('No code blocks found in file.');
74
- process.exit(0);
75
- }
76
- console.log(`Found ${codeBlocks.length} code block(s)`);
77
- console.log('');
78
- const client = createLLMClient({
79
- provider: config.llm.provider,
80
- model: config.llm.model,
81
- baseUrl: config.llm.baseUrl,
82
- });
83
- const maxIterations = parseInt(options.maxIterations);
84
- // Create validators
85
- const validators = {
86
- javascript: createTypeScriptValidator(),
87
- typescript: createTypeScriptValidator(),
88
- ts: createTypeScriptValidator(),
89
- js: createTypeScriptValidator(),
90
- python: createPythonValidator(),
91
- py: createPythonValidator(),
92
- };
93
- let fixedCount = 0;
94
- let failedCount = 0;
95
- let newContent = content;
96
- for (let i = 0; i < codeBlocks.length; i++) {
97
- const block = codeBlocks[i];
98
- if (!block)
99
- continue;
100
- const validator = validators[block.language.toLowerCase()];
101
- if (!validator) {
102
- console.log(`[${i + 1}/${codeBlocks.length}] Skipping ${block.language} (no validator)`);
103
- continue;
45
+ catch (err) {
46
+ const errObj = err;
47
+ if (errObj.code === 'ENOENT') {
48
+ console.error(`Error: File not found: ${filePath}`);
49
+ }
50
+ else if (errObj.code === 'EACCES') {
51
+ console.error(`Error: Permission denied: ${filePath}`);
52
+ }
53
+ else {
54
+ const message = err instanceof Error ? err.message : String(err);
55
+ console.error(`Error: Could not read file: ${filePath} (${message})`);
56
+ }
57
+ process.exit(1);
104
58
  }
105
- console.log(`[${i + 1}/${codeBlocks.length}] Checking ${block.language} example...`);
106
- // First check if it's valid
107
- const initial = await validator(block.code);
108
- if (initial.valid) {
109
- console.log(` ✓ Already valid`);
110
- continue;
59
+ // Find all code blocks
60
+ const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
61
+ const codeBlocks = [];
62
+ let match;
63
+ while ((match = codeBlockRegex.exec(content)) !== null) {
64
+ const code = match[2];
65
+ if (!code)
66
+ continue;
67
+ codeBlocks.push({
68
+ match: match[0],
69
+ language: options.language || match[1] || 'javascript',
70
+ code,
71
+ index: match.index,
72
+ });
111
73
  }
112
- console.log(` ✗ Invalid: ${initial.errors[0]?.slice(0, 60)}...`);
113
- console.log(` → Attempting to fix...`);
114
- const result = await autoFixExample({
115
- code: block.code,
116
- language: block.language,
117
- context: `Code example from documentation file: ${file}`,
118
- }, client, {
119
- maxIterations,
120
- validateFn: validator,
121
- language: block.language,
74
+ if (codeBlocks.length === 0) {
75
+ console.log('No code blocks found in file.');
76
+ process.exit(0);
77
+ }
78
+ console.log(`Found ${codeBlocks.length} code block(s)`);
79
+ console.log('');
80
+ const client = createLLMClient({
81
+ provider: config.llm.provider,
82
+ model: config.llm.model,
83
+ baseUrl: config.llm.baseUrl,
122
84
  });
123
- if (result.success) {
124
- console.log(` ✓ Fixed in ${result.iterations} iteration(s)`);
125
- fixedCount++;
126
- // Replace in content
127
- const newBlock = '```' + block.language + '\n' + result.fixedCode + '```';
128
- newContent = newContent.replace(block.match, newBlock);
85
+ const maxIterations = parseInt(options.maxIterations, 10);
86
+ if (isNaN(maxIterations) || maxIterations < 1) {
87
+ console.error('Error: --max-iterations must be a positive number');
88
+ process.exit(1);
129
89
  }
130
- else {
131
- console.log(` ✗ Could not fix after ${result.iterations} iterations`);
132
- failedCount++;
90
+ // Create validators
91
+ const validators = {
92
+ javascript: createTypeScriptValidator(),
93
+ typescript: createTypeScriptValidator(),
94
+ ts: createTypeScriptValidator(),
95
+ js: createTypeScriptValidator(),
96
+ python: createPythonValidator(),
97
+ py: createPythonValidator(),
98
+ };
99
+ let fixedCount = 0;
100
+ let failedCount = 0;
101
+ let newContent = content;
102
+ for (let i = 0; i < codeBlocks.length; i++) {
103
+ const block = codeBlocks[i];
104
+ if (!block)
105
+ continue;
106
+ const validator = validators[block.language.toLowerCase()];
107
+ if (!validator) {
108
+ console.log(`[${i + 1}/${codeBlocks.length}] Skipping ${block.language} (no validator)`);
109
+ continue;
110
+ }
111
+ console.log(`[${i + 1}/${codeBlocks.length}] Checking ${block.language} example...`);
112
+ // First check if it's valid
113
+ const initial = await validator(block.code);
114
+ if (initial.valid) {
115
+ console.log(` ✓ Already valid`);
116
+ continue;
117
+ }
118
+ console.log(` ✗ Invalid: ${initial.errors[0]?.slice(0, 60)}...`);
119
+ console.log(` → Attempting to fix...`);
120
+ const result = await autoFixExample({
121
+ code: block.code,
122
+ language: block.language,
123
+ context: `Code example from documentation file: ${file}`,
124
+ }, client, {
125
+ maxIterations,
126
+ validateFn: validator,
127
+ language: block.language,
128
+ });
129
+ if (result.success) {
130
+ console.log(` ✓ Fixed in ${result.iterations} iteration(s)`);
131
+ fixedCount++;
132
+ // Replace in content
133
+ const newBlock = '```' + block.language + '\n' + result.fixedCode + '```';
134
+ newContent = newContent.replace(block.match, newBlock);
135
+ }
136
+ else {
137
+ console.log(` ✗ Could not fix after ${result.iterations} iterations`);
138
+ failedCount++;
139
+ }
133
140
  }
141
+ console.log('');
142
+ console.log('=== Summary ===');
143
+ console.log(` Checked: ${codeBlocks.length}`);
144
+ console.log(` Fixed: ${fixedCount}`);
145
+ console.log(` Failed: ${failedCount}`);
146
+ if (fixedCount > 0 && !options.dryRun) {
147
+ writeFileSync(filePath, newContent);
148
+ console.log(`\nWrote fixes to ${filePath}`);
149
+ // Auto-fix then run QA on the fixed file's directory
150
+ const fixReport = fixQAIssues(dirname(filePath));
151
+ printFixReport(fixReport);
152
+ const qaReport = runQA(dirname(filePath));
153
+ printQAReport(qaReport);
154
+ }
155
+ else if (fixedCount > 0 && options.dryRun) {
156
+ console.log(`\n[dry run - no changes written]`);
157
+ }
158
+ console.log('\nDone!');
134
159
  }
135
- console.log('');
136
- console.log('=== Summary ===');
137
- console.log(` Checked: ${codeBlocks.length}`);
138
- console.log(` Fixed: ${fixedCount}`);
139
- console.log(` Failed: ${failedCount}`);
140
- if (fixedCount > 0 && !options.dryRun) {
141
- writeFileSync(filePath, newContent);
142
- console.log(`\nWrote fixes to ${filePath}`);
143
- }
144
- else if (fixedCount > 0 && options.dryRun) {
145
- console.log(`\n[dry run - no changes written]`);
160
+ catch (err) {
161
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
162
+ process.exit(1);
146
163
  }
147
- console.log('\nDone!');
148
164
  });
@@ -54,53 +54,64 @@ export const cronCommand = new Command('cron')
54
54
  .option('-s, --schedule <cron>', 'Cron schedule (default: daily at 2am UTC)', '0 2 * * *')
55
55
  .option('-f, --force', 'Overwrite existing workflow')
56
56
  .action(async (repoPath, options) => {
57
- const resolvedPath = resolve(repoPath);
58
- const workflowDir = join(resolvedPath, '.github', 'workflows');
59
- const workflowPath = join(workflowDir, 'skrypt-docs.yml');
60
- console.log('skrypt cron');
61
- console.log(` repo: ${resolvedPath}`);
62
- console.log(` schedule: ${options.schedule}`);
63
- console.log('');
64
- // Check if workflow exists
65
- if (existsSync(workflowPath) && !options.force) {
66
- console.log('Workflow already exists. Use --force to overwrite.');
67
- console.log(` ${workflowPath}`);
68
- return;
57
+ try {
58
+ const resolvedPath = resolve(repoPath);
59
+ const workflowDir = join(resolvedPath, '.github', 'workflows');
60
+ const workflowPath = join(workflowDir, 'skrypt-docs.yml');
61
+ console.log('skrypt cron');
62
+ console.log(` repo: ${resolvedPath}`);
63
+ console.log(` schedule: ${options.schedule}`);
64
+ console.log('');
65
+ // Validate cron schedule format
66
+ if (options.schedule && !/^[\d\s*,\-/]+$/.test(options.schedule)) {
67
+ console.error('Error: Invalid cron schedule format');
68
+ process.exit(1);
69
+ }
70
+ // Check if workflow exists
71
+ if (existsSync(workflowPath) && !options.force) {
72
+ console.log('Workflow already exists. Use --force to overwrite.');
73
+ console.log(` ${workflowPath}`);
74
+ return;
75
+ }
76
+ // Create workflow directory
77
+ mkdirSync(workflowDir, { recursive: true });
78
+ // Customize schedule if provided
79
+ let workflow = CRON_WORKFLOW;
80
+ if (options.schedule && options.schedule !== '0 2 * * *') {
81
+ workflow = workflow.replace("cron: '0 2 * * *'", `cron: '${options.schedule}'`);
82
+ }
83
+ // Write workflow
84
+ writeFileSync(workflowPath, workflow);
85
+ console.log(`✓ Created: ${workflowPath}`);
86
+ console.log('');
87
+ console.log('=== Setup Instructions ===');
88
+ console.log('');
89
+ console.log('1. Add secrets to your GitHub repository:');
90
+ console.log(' Settings > Secrets and variables > Actions');
91
+ console.log('');
92
+ console.log(' Required:');
93
+ console.log(' - SKRYPT_API_KEY: Get from https://skrypt.sh/dashboard/settings');
94
+ console.log('');
95
+ console.log(' For AI generation (one of):');
96
+ console.log(' - OPENAI_API_KEY');
97
+ console.log(' - ANTHROPIC_API_KEY');
98
+ console.log('');
99
+ console.log('2. Commit and push:');
100
+ console.log(' git add .github/');
101
+ console.log(' git commit -m "Add Skrypt auto-update workflow"');
102
+ console.log(' git push');
103
+ console.log('');
104
+ console.log('3. Trigger manually (optional):');
105
+ console.log(' Go to Actions > Skrypt Auto-Update Docs > Run workflow');
106
+ console.log('');
107
+ console.log('Common schedules:');
108
+ console.log(' "0 2 * * *" - Daily at 2am UTC');
109
+ console.log(' "0 2 * * 1" - Weekly on Monday');
110
+ console.log(' "0 2 1 * *" - Monthly on the 1st');
111
+ console.log(' "0 */6 * * *" - Every 6 hours');
69
112
  }
70
- // Create workflow directory
71
- mkdirSync(workflowDir, { recursive: true });
72
- // Customize schedule if provided
73
- let workflow = CRON_WORKFLOW;
74
- if (options.schedule && options.schedule !== '0 2 * * *') {
75
- workflow = workflow.replace("cron: '0 2 * * *'", `cron: '${options.schedule}'`);
113
+ catch (err) {
114
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
115
+ process.exit(1);
76
116
  }
77
- // Write workflow
78
- writeFileSync(workflowPath, workflow);
79
- console.log(`✓ Created: ${workflowPath}`);
80
- console.log('');
81
- console.log('=== Setup Instructions ===');
82
- console.log('');
83
- console.log('1. Add secrets to your GitHub repository:');
84
- console.log(' Settings > Secrets and variables > Actions');
85
- console.log('');
86
- console.log(' Required:');
87
- console.log(' - SKRYPT_API_KEY: Get from https://skrypt.sh/dashboard/settings');
88
- console.log('');
89
- console.log(' For AI generation (one of):');
90
- console.log(' - OPENAI_API_KEY');
91
- console.log(' - ANTHROPIC_API_KEY');
92
- console.log('');
93
- console.log('2. Commit and push:');
94
- console.log(' git add .github/');
95
- console.log(' git commit -m "Add Skrypt auto-update workflow"');
96
- console.log(' git push');
97
- console.log('');
98
- console.log('3. Trigger manually (optional):');
99
- console.log(' Go to Actions > Skrypt Auto-Update Docs > Run workflow');
100
- console.log('');
101
- console.log('Common schedules:');
102
- console.log(' "0 2 * * *" - Daily at 2am UTC');
103
- console.log(' "0 2 * * 1" - Weekly on Monday');
104
- console.log(' "0 2 1 * *" - Monthly on the 1st');
105
- console.log(' "0 */6 * * *" - Every 6 hours');
106
117
  });