skrypt-ai 0.5.0 → 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 (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 +108 -102
  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
@@ -7,97 +7,103 @@ import { keychainAvailable, getKeychainPlatformName } from '../auth/keychain.js'
7
7
  export const securityCommand = new Command('security')
8
8
  .description('Show security and key storage details')
9
9
  .action(async () => {
10
- console.log('skrypt security\n');
11
- const configDir = join(homedir(), '.skrypt');
12
- const authFile = join(configDir, 'auth.json');
13
- // 1. Key Storage
14
- console.log(' \x1b[1mKey Storage\x1b[0m');
15
- const method = await getKeyStorageMethod();
16
- const hasKeychain = await keychainAvailable();
17
- if (method === 'env') {
18
- console.log(' ✓ Using SKRYPT_API_KEY environment variable');
19
- }
20
- else if (method === 'keychain') {
21
- console.log(` ✓ API key stored in ${getKeychainPlatformName()}`);
22
- console.log(' Hardware-backed encryption (Secure Enclave on macOS)');
23
- }
24
- else if (method === 'file') {
25
- console.log(` ● API key stored in ${authFile}`);
26
- console.log(' File permissions: 0600 (owner read/write only)');
27
- if (!hasKeychain) {
28
- console.log(' \x1b[33mTip: Install @napi-rs/keyring for OS keychain storage\x1b[0m');
10
+ try {
11
+ console.log('skrypt security\n');
12
+ const configDir = join(homedir(), '.skrypt');
13
+ const authFile = join(configDir, 'auth.json');
14
+ // 1. Key Storage
15
+ console.log(' \x1b[1mKey Storage\x1b[0m');
16
+ const method = await getKeyStorageMethod();
17
+ const hasKeychain = await keychainAvailable();
18
+ if (method === 'env') {
19
+ console.log(' ✓ Using SKRYPT_API_KEY environment variable');
29
20
  }
30
- }
31
- else {
32
- console.log(' No API key configured');
33
- console.log(' Run: skrypt login');
34
- }
35
- console.log('');
36
- // 2. Data Flow
37
- console.log(' \x1b[1mData Flow\x1b[0m');
38
- const envKeys = {
39
- OPENAI_API_KEY: !!process.env.OPENAI_API_KEY,
40
- ANTHROPIC_API_KEY: !!process.env.ANTHROPIC_API_KEY,
41
- GOOGLE_API_KEY: !!process.env.GOOGLE_API_KEY,
42
- DEEPSEEK_API_KEY: !!process.env.DEEPSEEK_API_KEY,
43
- };
44
- const hasBYOK = Object.values(envKeys).some(Boolean);
45
- if (hasBYOK) {
46
- const providers = Object.entries(envKeys)
47
- .filter(([, v]) => v)
48
- .map(([k]) => k.replace('_API_KEY', '').toLowerCase());
49
- console.log(` ✓ BYOK mode: LLM calls go directly to ${providers.join(', ')}`);
50
- console.log(' Your keys never touch Skrypt servers');
51
- }
52
- else {
53
- const config = await getAuthConfigAsync();
54
- if (config.apiKey) {
55
- console.log(' ● Proxy mode: LLM calls routed through Skrypt API');
56
- console.log(' Your Skrypt key authenticates requests');
21
+ else if (method === 'keychain') {
22
+ console.log(` ✓ API key stored in ${getKeychainPlatformName()}`);
23
+ console.log(' Hardware-backed encryption (Secure Enclave on macOS)');
24
+ }
25
+ else if (method === 'file') {
26
+ console.log(` ● API key stored in ${authFile}`);
27
+ console.log(' File permissions: 0600 (owner read/write only)');
28
+ if (!hasKeychain) {
29
+ console.log(' \x1b[33mTip: Install @napi-rs/keyring for OS keychain storage\x1b[0m');
30
+ }
57
31
  }
58
32
  else {
59
- console.log(' ○ No provider keys or Skrypt key configured');
33
+ console.log(' ○ No API key configured');
34
+ console.log(' Run: skrypt login');
60
35
  }
61
- }
62
- console.log('');
63
- // 3. Server-Side Security
64
- console.log(' \x1b[1mServer-Side Security\x1b[0m');
65
- console.log(' • API keys hashed with SHA-256 — never stored in plaintext');
66
- console.log(' • Encrypted at rest with AES-256 via AWS KMS');
67
- console.log(' • TLS 1.3 for all API communication');
68
- console.log('');
69
- // 4. Permissions
70
- console.log(' \x1b[1mPermissions\x1b[0m');
71
- if (existsSync(configDir)) {
72
- try {
73
- const dirStat = statSync(configDir);
74
- const dirMode = (dirStat.mode & 0o777).toString(8);
75
- console.log(` ~/.skrypt/ ${dirMode} (${dirMode === '700' ? '✓' : ' expected 700'})`);
36
+ console.log('');
37
+ // 2. Data Flow
38
+ console.log(' \x1b[1mData Flow\x1b[0m');
39
+ const envKeys = {
40
+ OPENAI_API_KEY: !!process.env.OPENAI_API_KEY,
41
+ ANTHROPIC_API_KEY: !!process.env.ANTHROPIC_API_KEY,
42
+ GOOGLE_API_KEY: !!process.env.GOOGLE_API_KEY,
43
+ DEEPSEEK_API_KEY: !!process.env.DEEPSEEK_API_KEY,
44
+ };
45
+ const hasBYOK = Object.values(envKeys).some(Boolean);
46
+ if (hasBYOK) {
47
+ const providers = Object.entries(envKeys)
48
+ .filter(([, v]) => v)
49
+ .map(([k]) => k.replace('_API_KEY', '').toLowerCase());
50
+ console.log(` BYOK mode: LLM calls go directly to ${providers.join(', ')}`);
51
+ console.log(' Your keys never touch Skrypt servers');
76
52
  }
77
- catch {
78
- console.log(' ~/.skrypt/ unable to read');
53
+ else {
54
+ const config = await getAuthConfigAsync();
55
+ if (config.apiKey) {
56
+ console.log(' ● Proxy mode: LLM calls routed through Skrypt API');
57
+ console.log(' Your Skrypt key authenticates requests');
58
+ }
59
+ else {
60
+ console.log(' ○ No provider keys or Skrypt key configured');
61
+ }
79
62
  }
80
- if (existsSync(authFile)) {
63
+ console.log('');
64
+ // 3. Server-Side Security
65
+ console.log(' \x1b[1mServer-Side Security\x1b[0m');
66
+ console.log(' • API keys hashed with SHA-256 — never stored in plaintext');
67
+ console.log(' • Encrypted at rest with AES-256 via AWS KMS');
68
+ console.log(' • TLS 1.3 for all API communication');
69
+ console.log('');
70
+ // 4. Permissions
71
+ console.log(' \x1b[1mPermissions\x1b[0m');
72
+ if (existsSync(configDir)) {
81
73
  try {
82
- const fileStat = statSync(authFile);
83
- const fileMode = (fileStat.mode & 0o777).toString(8);
84
- console.log(` ~/.skrypt/auth.json ${fileMode} (${fileMode === '600' ? '✓' : '⚠ expected 600'})`);
74
+ const dirStat = statSync(configDir);
75
+ const dirMode = (dirStat.mode & 0o777).toString(8);
76
+ console.log(` ~/.skrypt/ ${dirMode} (${dirMode === '700' ? '✓' : '⚠ expected 700'})`);
85
77
  }
86
78
  catch {
87
- console.log(' ~/.skrypt/auth.json unable to read');
79
+ console.log(' ~/.skrypt/ unable to read');
88
80
  }
81
+ if (existsSync(authFile)) {
82
+ try {
83
+ const fileStat = statSync(authFile);
84
+ const fileMode = (fileStat.mode & 0o777).toString(8);
85
+ console.log(` ~/.skrypt/auth.json ${fileMode} (${fileMode === '600' ? '✓' : '⚠ expected 600'})`);
86
+ }
87
+ catch {
88
+ console.log(' ~/.skrypt/auth.json unable to read');
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ console.log(' ~/.skrypt/ does not exist yet');
89
94
  }
95
+ console.log('');
96
+ // 5. Environment
97
+ console.log(' \x1b[1mEnvironment\x1b[0m');
98
+ console.log(` Platform: ${process.platform}`);
99
+ console.log(` OS keychain: ${hasKeychain ? '✓ available' : '✗ not available'}`);
100
+ console.log(` SKRYPT_API_KEY: ${process.env.SKRYPT_API_KEY ? '✓ set' : '○ not set'}`);
101
+ console.log(` OPENAI_API_KEY: ${process.env.OPENAI_API_KEY ? '✓ set' : '○ not set'}`);
102
+ console.log(` ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? '✓ set' : '○ not set'}`);
103
+ console.log('');
90
104
  }
91
- else {
92
- console.log(' ~/.skrypt/ does not exist yet');
105
+ catch (err) {
106
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
107
+ process.exit(1);
93
108
  }
94
- console.log('');
95
- // 5. Environment
96
- console.log(' \x1b[1mEnvironment\x1b[0m');
97
- console.log(` Platform: ${process.platform}`);
98
- console.log(` OS keychain: ${hasKeychain ? '✓ available' : '✗ not available'}`);
99
- console.log(` SKRYPT_API_KEY: ${process.env.SKRYPT_API_KEY ? '✓ set' : '○ not set'}`);
100
- console.log(` OPENAI_API_KEY: ${process.env.OPENAI_API_KEY ? '✓ set' : '○ not set'}`);
101
- console.log(` ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? '✓ set' : '○ not set'}`);
102
- console.log('');
103
109
  });
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
3
3
  import { resolve, join, extname, relative } from 'path';
4
4
  import { spawn } from 'child_process';
5
- import { writeFileSync, unlinkSync, mkdirSync } from 'fs';
5
+ import { writeFileSync, mkdirSync, rmSync } from 'fs';
6
6
  import { tmpdir } from 'os';
7
7
  import { randomUUID } from 'crypto';
8
8
  import { requirePro } from '../auth/index.js';
@@ -123,17 +123,10 @@ async function runCodeBlock(block, timeoutMs) {
123
123
  finally {
124
124
  // Cleanup temp directory
125
125
  try {
126
- const files = readdirSync(tempDir);
127
- for (const file of files) {
128
- unlinkSync(join(tempDir, file));
129
- }
130
- readdirSync(tempDir); // Verify empty
131
- // Remove the directory
132
- const { rmdirSync } = await import('fs');
133
- rmdirSync(tempDir);
126
+ rmSync(tempDir, { recursive: true, force: true });
134
127
  }
135
128
  catch {
136
- // Ignore cleanup errors
129
+ // Ignore cleanup errors — OS will clean tmpdir
137
130
  }
138
131
  }
139
132
  }
@@ -241,95 +234,101 @@ export const testCommand = new Command('test')
241
234
  .option('-t, --timeout <ms>', 'Timeout per code block in milliseconds', '10000')
242
235
  .option('-v, --verbose', 'Show detailed output')
243
236
  .action(async (path, options) => {
244
- // Pro feature - requires subscription
245
- if (!await requirePro('test')) {
246
- process.exit(1);
247
- }
248
- const targetPath = resolve(options.file || path);
249
- if (!existsSync(targetPath)) {
250
- console.error(`Error: Path not found: ${targetPath}`);
251
- process.exit(1);
252
- }
253
- const timeoutMs = parseInt(options.timeout);
254
- if (isNaN(timeoutMs) || timeoutMs <= 0) {
255
- console.error(`Error: Invalid timeout value: ${options.timeout}`);
256
- process.exit(1);
257
- }
258
- console.log('skrypt test');
259
- console.log(` path: ${targetPath}`);
260
- if (options.language) {
261
- console.log(` language: ${options.language}`);
262
- }
263
- console.log(` timeout: ${timeoutMs}ms`);
264
- console.log('');
265
- // Find all doc files
266
- const files = statSync(targetPath).isDirectory()
267
- ? findDocFiles(targetPath)
268
- : [targetPath];
269
- if (files.length === 0) {
270
- console.log('No .md or .mdx files found.');
271
- process.exit(0);
272
- }
273
- // Extract all code blocks
274
- const allBlocks = [];
275
- for (const file of files) {
276
- const blocks = extractCodeBlocks(file, options.language);
277
- allBlocks.push(...blocks);
278
- }
279
- if (allBlocks.length === 0) {
280
- console.log('No testable code blocks found.');
281
- if (options.language) {
282
- console.log(` (filtered by language: ${options.language})`);
237
+ try {
238
+ // Pro feature - requires subscription
239
+ if (!await requirePro('test')) {
240
+ process.exit(1);
283
241
  }
284
- console.log(` Supported languages: ${SUPPORTED_LANGUAGES.join(', ')}`);
285
- process.exit(0);
286
- }
287
- console.log(`Found ${allBlocks.length} code block(s) in ${files.length} file(s)`);
288
- console.log('');
289
- console.log('Running tests...\n');
290
- const results = [];
291
- const failedBlocks = [];
292
- const startTime = Date.now();
293
- for (let i = 0; i < allBlocks.length; i++) {
294
- const block = allBlocks[i];
295
- if (!block)
296
- continue;
297
- const result = await runCodeBlock(block, timeoutMs);
298
- results.push(result);
299
- printResult(result, targetPath, options.verbose || false);
300
- if (!result.passed) {
301
- failedBlocks.push(block);
242
+ const targetPath = resolve(options.file || path);
243
+ if (!existsSync(targetPath)) {
244
+ console.error(`Error: Path not found: ${targetPath}`);
245
+ process.exit(1);
302
246
  }
303
- }
304
- const totalDuration = Date.now() - startTime;
305
- const summary = {
306
- total: allBlocks.length,
307
- passed: results.filter(r => r.passed).length,
308
- failed: results.filter(r => !r.passed).length,
309
- skipped: 0,
310
- duration: totalDuration,
311
- };
312
- printSummary(summary);
313
- // Handle --fix flag
314
- if (options.fix && failedBlocks.length > 0) {
315
- console.log('');
316
- console.log('Attempting to auto-fix failing examples...');
317
- console.log(' Run: skrypt autofix <file> for each failing file');
247
+ const timeoutMs = parseInt(options.timeout);
248
+ if (isNaN(timeoutMs) || timeoutMs <= 0) {
249
+ console.error(`Error: Invalid timeout value: ${options.timeout}`);
250
+ process.exit(1);
251
+ }
252
+ console.log('skrypt test');
253
+ console.log(` path: ${targetPath}`);
254
+ if (options.language) {
255
+ console.log(` language: ${options.language}`);
256
+ }
257
+ console.log(` timeout: ${timeoutMs}ms`);
318
258
  console.log('');
319
- // Get unique files with failures
320
- const failedFiles = [...new Set(failedBlocks.map(b => b.file))];
321
- for (const file of failedFiles) {
322
- console.log(` → ${relative(targetPath, file)}`);
259
+ // Find all doc files
260
+ const files = statSync(targetPath).isDirectory()
261
+ ? findDocFiles(targetPath)
262
+ : [targetPath];
263
+ if (files.length === 0) {
264
+ console.log('No .md or .mdx files found.');
265
+ process.exit(0);
266
+ }
267
+ // Extract all code blocks
268
+ const allBlocks = [];
269
+ for (const file of files) {
270
+ const blocks = extractCodeBlocks(file, options.language);
271
+ allBlocks.push(...blocks);
272
+ }
273
+ if (allBlocks.length === 0) {
274
+ console.log('No testable code blocks found.');
275
+ if (options.language) {
276
+ console.log(` (filtered by language: ${options.language})`);
277
+ }
278
+ console.log(` Supported languages: ${SUPPORTED_LANGUAGES.join(', ')}`);
279
+ process.exit(0);
323
280
  }
281
+ console.log(`Found ${allBlocks.length} code block(s) in ${files.length} file(s)`);
324
282
  console.log('');
325
- console.log('To auto-fix, run:');
326
- for (const file of failedFiles) {
327
- console.log(` skrypt autofix "${file}"`);
283
+ console.log('Running tests...\n');
284
+ const results = [];
285
+ const failedBlocks = [];
286
+ const startTime = Date.now();
287
+ for (let i = 0; i < allBlocks.length; i++) {
288
+ const block = allBlocks[i];
289
+ if (!block)
290
+ continue;
291
+ const result = await runCodeBlock(block, timeoutMs);
292
+ results.push(result);
293
+ printResult(result, targetPath, options.verbose || false);
294
+ if (!result.passed) {
295
+ failedBlocks.push(block);
296
+ }
297
+ }
298
+ const totalDuration = Date.now() - startTime;
299
+ const summary = {
300
+ total: allBlocks.length,
301
+ passed: results.filter(r => r.passed).length,
302
+ failed: results.filter(r => !r.passed).length,
303
+ skipped: 0,
304
+ duration: totalDuration,
305
+ };
306
+ printSummary(summary);
307
+ // Handle --fix flag
308
+ if (options.fix && failedBlocks.length > 0) {
309
+ console.log('');
310
+ console.log('Attempting to auto-fix failing examples...');
311
+ console.log(' Run: skrypt autofix <file> for each failing file');
312
+ console.log('');
313
+ // Get unique files with failures
314
+ const failedFiles = [...new Set(failedBlocks.map(b => b.file))];
315
+ for (const file of failedFiles) {
316
+ console.log(` → ${relative(targetPath, file)}`);
317
+ }
318
+ console.log('');
319
+ console.log('To auto-fix, run:');
320
+ for (const file of failedFiles) {
321
+ console.log(` skrypt autofix "${file}"`);
322
+ }
328
323
  }
324
+ // Exit with error code if tests failed
325
+ if (summary.failed > 0) {
326
+ process.exit(1);
327
+ }
328
+ console.log('\nAll tests passed!');
329
329
  }
330
- // Exit with error code if tests failed
331
- if (summary.failed > 0) {
330
+ catch (err) {
331
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
332
332
  process.exit(1);
333
333
  }
334
- console.log('\nAll tests passed!');
335
334
  });
@@ -5,7 +5,12 @@ const CONFIG_FILE = 'skrypt.versions.json';
5
5
  function loadVersionConfig(docsPath) {
6
6
  const configPath = join(docsPath, CONFIG_FILE);
7
7
  if (existsSync(configPath)) {
8
- return JSON.parse(readFileSync(configPath, 'utf-8'));
8
+ try {
9
+ return JSON.parse(readFileSync(configPath, 'utf-8'));
10
+ }
11
+ catch {
12
+ return { versions: [], current: '' };
13
+ }
9
14
  }
10
15
  return { versions: [], current: '' };
11
16
  }
@@ -20,16 +25,22 @@ versionCommand
20
25
  .description('List all versions')
21
26
  .argument('[docs-path]', 'Documentation directory', './docs')
22
27
  .action((docsPath) => {
23
- const config = loadVersionConfig(resolve(docsPath));
24
- if (config.versions.length === 0) {
25
- console.log('No versions configured.');
26
- console.log('Run: skrypt version create <version>');
27
- return;
28
+ try {
29
+ const config = loadVersionConfig(resolve(docsPath));
30
+ if (config.versions.length === 0) {
31
+ console.log('No versions configured.');
32
+ console.log('Run: skrypt version create <version>');
33
+ return;
34
+ }
35
+ console.log('Versions:');
36
+ for (const v of config.versions) {
37
+ const marker = v === config.current ? ' (current)' : '';
38
+ console.log(` ${v}${marker}`);
39
+ }
28
40
  }
29
- console.log('Versions:');
30
- for (const v of config.versions) {
31
- const marker = v === config.current ? ' (current)' : '';
32
- console.log(` ${v}${marker}`);
41
+ catch (err) {
42
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
43
+ process.exit(1);
33
44
  }
34
45
  });
35
46
  versionCommand
@@ -39,49 +50,55 @@ versionCommand
39
50
  .argument('[docs-path]', 'Documentation directory', './docs')
40
51
  .option('--from <version>', 'Copy from existing version instead of current')
41
52
  .action((version, docsPath, options) => {
42
- const resolvedPath = resolve(docsPath);
43
- const config = loadVersionConfig(resolvedPath);
44
- if (config.versions.includes(version)) {
45
- console.error(`Version ${version} already exists.`);
46
- process.exit(1);
47
- }
48
- const versionDir = join(resolvedPath, 'versions', version);
49
- if (existsSync(versionDir)) {
50
- console.error(`Directory already exists: ${versionDir}`);
51
- process.exit(1);
52
- }
53
- // Determine source
54
- let sourceDir;
55
- if (options.from) {
56
- sourceDir = join(resolvedPath, 'versions', options.from);
57
- if (!existsSync(sourceDir)) {
58
- console.error(`Source version not found: ${options.from}`);
53
+ try {
54
+ const resolvedPath = resolve(docsPath);
55
+ const config = loadVersionConfig(resolvedPath);
56
+ if (config.versions.includes(version)) {
57
+ console.error(`Version ${version} already exists.`);
59
58
  process.exit(1);
60
59
  }
60
+ const versionDir = join(resolvedPath, 'versions', version);
61
+ if (existsSync(versionDir)) {
62
+ console.error(`Directory already exists: ${versionDir}`);
63
+ process.exit(1);
64
+ }
65
+ // Determine source
66
+ let sourceDir;
67
+ if (options.from) {
68
+ sourceDir = join(resolvedPath, 'versions', options.from);
69
+ if (!existsSync(sourceDir)) {
70
+ console.error(`Source version not found: ${options.from}`);
71
+ process.exit(1);
72
+ }
73
+ }
74
+ else {
75
+ // Copy current docs (excluding versions folder)
76
+ sourceDir = resolvedPath;
77
+ }
78
+ // Create version directory
79
+ mkdirSync(versionDir, { recursive: true });
80
+ // Copy files
81
+ const entries = readdirSync(sourceDir, { withFileTypes: true });
82
+ for (const entry of entries) {
83
+ if (entry.name === 'versions' || entry.name === CONFIG_FILE)
84
+ continue;
85
+ const srcPath = join(sourceDir, entry.name);
86
+ const destPath = join(versionDir, entry.name);
87
+ cpSync(srcPath, destPath, { recursive: true });
88
+ }
89
+ // Update config
90
+ config.versions.push(version);
91
+ if (!config.current) {
92
+ config.current = version;
93
+ }
94
+ saveVersionConfig(resolvedPath, config);
95
+ console.log(`Created version: ${version}`);
96
+ console.log(` Location: ${versionDir}`);
61
97
  }
62
- else {
63
- // Copy current docs (excluding versions folder)
64
- sourceDir = resolvedPath;
65
- }
66
- // Create version directory
67
- mkdirSync(versionDir, { recursive: true });
68
- // Copy files
69
- const entries = readdirSync(sourceDir, { withFileTypes: true });
70
- for (const entry of entries) {
71
- if (entry.name === 'versions' || entry.name === CONFIG_FILE)
72
- continue;
73
- const srcPath = join(sourceDir, entry.name);
74
- const destPath = join(versionDir, entry.name);
75
- cpSync(srcPath, destPath, { recursive: true });
76
- }
77
- // Update config
78
- config.versions.push(version);
79
- if (!config.current) {
80
- config.current = version;
98
+ catch (err) {
99
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
100
+ process.exit(1);
81
101
  }
82
- saveVersionConfig(resolvedPath, config);
83
- console.log(`Created version: ${version}`);
84
- console.log(` Location: ${versionDir}`);
85
102
  });
86
103
  versionCommand
87
104
  .command('set-current')
@@ -89,16 +106,22 @@ versionCommand
89
106
  .argument('<version>', 'Version to set as current')
90
107
  .argument('[docs-path]', 'Documentation directory', './docs')
91
108
  .action((version, docsPath) => {
92
- const resolvedPath = resolve(docsPath);
93
- const config = loadVersionConfig(resolvedPath);
94
- if (!config.versions.includes(version)) {
95
- console.error(`Version ${version} not found.`);
96
- console.log('Available versions:', config.versions.join(', '));
109
+ try {
110
+ const resolvedPath = resolve(docsPath);
111
+ const config = loadVersionConfig(resolvedPath);
112
+ if (!config.versions.includes(version)) {
113
+ console.error(`Version ${version} not found.`);
114
+ console.log('Available versions:', config.versions.join(', '));
115
+ process.exit(1);
116
+ }
117
+ config.current = version;
118
+ saveVersionConfig(resolvedPath, config);
119
+ console.log(`Set current version to: ${version}`);
120
+ }
121
+ catch (err) {
122
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
97
123
  process.exit(1);
98
124
  }
99
- config.current = version;
100
- saveVersionConfig(resolvedPath, config);
101
- console.log(`Set current version to: ${version}`);
102
125
  });
103
126
  versionCommand
104
127
  .command('delete')
@@ -107,25 +130,31 @@ versionCommand
107
130
  .argument('[docs-path]', 'Documentation directory', './docs')
108
131
  .option('--force', 'Skip confirmation')
109
132
  .action((version, docsPath, options) => {
110
- const resolvedPath = resolve(docsPath);
111
- const config = loadVersionConfig(resolvedPath);
112
- if (!config.versions.includes(version)) {
113
- console.error(`Version ${version} not found.`);
114
- process.exit(1);
133
+ try {
134
+ const resolvedPath = resolve(docsPath);
135
+ const config = loadVersionConfig(resolvedPath);
136
+ if (!config.versions.includes(version)) {
137
+ console.error(`Version ${version} not found.`);
138
+ process.exit(1);
139
+ }
140
+ if (version === config.current && !options.force) {
141
+ console.error(`Cannot delete current version. Use --force or set a different current version first.`);
142
+ process.exit(1);
143
+ }
144
+ const versionDir = join(resolvedPath, 'versions', version);
145
+ // Remove from config
146
+ config.versions = config.versions.filter(v => v !== version);
147
+ if (config.current === version) {
148
+ config.current = config.versions[0] || '';
149
+ }
150
+ saveVersionConfig(resolvedPath, config);
151
+ // Note: Not actually deleting files for safety
152
+ console.log(`Removed version ${version} from config.`);
153
+ console.log(`Files still exist at: ${versionDir}`);
154
+ console.log(`To fully delete, run: rm -rf "${versionDir}"`);
115
155
  }
116
- if (version === config.current && !options.force) {
117
- console.error(`Cannot delete current version. Use --force or set a different current version first.`);
156
+ catch (err) {
157
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
118
158
  process.exit(1);
119
159
  }
120
- const versionDir = join(resolvedPath, 'versions', version);
121
- // Remove from config
122
- config.versions = config.versions.filter(v => v !== version);
123
- if (config.current === version) {
124
- config.current = config.versions[0] || '';
125
- }
126
- saveVersionConfig(resolvedPath, config);
127
- // Note: Not actually deleting files for safety
128
- console.log(`Removed version ${version} from config.`);
129
- console.log(`Files still exist at: ${versionDir}`);
130
- console.log(`To fully delete, run: rm -rf "${versionDir}"`);
131
160
  });