skrypt-ai 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/auth/index.d.ts +13 -3
  2. package/dist/auth/index.js +101 -9
  3. package/dist/auth/keychain.d.ts +5 -0
  4. package/dist/auth/keychain.js +82 -0
  5. package/dist/auth/notices.d.ts +3 -0
  6. package/dist/auth/notices.js +42 -0
  7. package/dist/autofix/index.d.ts +0 -4
  8. package/dist/autofix/index.js +10 -24
  9. package/dist/capture/browser.d.ts +11 -0
  10. package/dist/capture/browser.js +173 -0
  11. package/dist/capture/diff.d.ts +23 -0
  12. package/dist/capture/diff.js +52 -0
  13. package/dist/capture/index.d.ts +23 -0
  14. package/dist/capture/index.js +210 -0
  15. package/dist/capture/naming.d.ts +17 -0
  16. package/dist/capture/naming.js +45 -0
  17. package/dist/capture/parser.d.ts +15 -0
  18. package/dist/capture/parser.js +80 -0
  19. package/dist/capture/types.d.ts +57 -0
  20. package/dist/capture/types.js +1 -0
  21. package/dist/cli.js +20 -3
  22. package/dist/commands/autofix.js +136 -120
  23. package/dist/commands/cron.js +58 -47
  24. package/dist/commands/deploy.js +123 -102
  25. package/dist/commands/generate.js +125 -7
  26. package/dist/commands/heal.d.ts +10 -0
  27. package/dist/commands/heal.js +201 -0
  28. package/dist/commands/i18n.js +146 -111
  29. package/dist/commands/import.d.ts +2 -0
  30. package/dist/commands/import.js +157 -0
  31. package/dist/commands/init.js +19 -7
  32. package/dist/commands/lint.js +50 -44
  33. package/dist/commands/llms-txt.js +59 -49
  34. package/dist/commands/login.js +63 -34
  35. package/dist/commands/mcp.js +6 -0
  36. package/dist/commands/monitor.js +13 -8
  37. package/dist/commands/qa.d.ts +2 -0
  38. package/dist/commands/qa.js +43 -0
  39. package/dist/commands/review-pr.js +108 -92
  40. package/dist/commands/sdk.js +128 -122
  41. package/dist/commands/security.d.ts +2 -0
  42. package/dist/commands/security.js +109 -0
  43. package/dist/commands/test.js +91 -92
  44. package/dist/commands/version.js +104 -75
  45. package/dist/commands/watch.js +130 -114
  46. package/dist/config/types.js +2 -2
  47. package/dist/context-hub/index.d.ts +23 -0
  48. package/dist/context-hub/index.js +179 -0
  49. package/dist/context-hub/mappings.d.ts +8 -0
  50. package/dist/context-hub/mappings.js +55 -0
  51. package/dist/context-hub/types.d.ts +33 -0
  52. package/dist/context-hub/types.js +1 -0
  53. package/dist/generator/generator.js +39 -6
  54. package/dist/generator/types.d.ts +7 -0
  55. package/dist/generator/writer.d.ts +3 -1
  56. package/dist/generator/writer.js +36 -7
  57. package/dist/importers/confluence.d.ts +5 -0
  58. package/dist/importers/confluence.js +137 -0
  59. package/dist/importers/detect.d.ts +20 -0
  60. package/dist/importers/detect.js +121 -0
  61. package/dist/importers/docusaurus.d.ts +5 -0
  62. package/dist/importers/docusaurus.js +279 -0
  63. package/dist/importers/gitbook.d.ts +5 -0
  64. package/dist/importers/gitbook.js +189 -0
  65. package/dist/importers/github.d.ts +8 -0
  66. package/dist/importers/github.js +99 -0
  67. package/dist/importers/index.d.ts +15 -0
  68. package/dist/importers/index.js +30 -0
  69. package/dist/importers/markdown.d.ts +6 -0
  70. package/dist/importers/markdown.js +105 -0
  71. package/dist/importers/mintlify.d.ts +5 -0
  72. package/dist/importers/mintlify.js +172 -0
  73. package/dist/importers/notion.d.ts +5 -0
  74. package/dist/importers/notion.js +174 -0
  75. package/dist/importers/readme.d.ts +5 -0
  76. package/dist/importers/readme.js +184 -0
  77. package/dist/importers/transform.d.ts +90 -0
  78. package/dist/importers/transform.js +457 -0
  79. package/dist/importers/types.d.ts +37 -0
  80. package/dist/importers/types.js +1 -0
  81. package/dist/llm/anthropic-client.d.ts +1 -0
  82. package/dist/llm/anthropic-client.js +3 -1
  83. package/dist/llm/index.d.ts +6 -4
  84. package/dist/llm/index.js +76 -261
  85. package/dist/llm/openai-client.d.ts +1 -0
  86. package/dist/llm/openai-client.js +7 -2
  87. package/dist/plugins/index.js +7 -0
  88. package/dist/qa/checks.d.ts +10 -0
  89. package/dist/qa/checks.js +492 -0
  90. package/dist/qa/fixes.d.ts +30 -0
  91. package/dist/qa/fixes.js +277 -0
  92. package/dist/qa/index.d.ts +29 -0
  93. package/dist/qa/index.js +187 -0
  94. package/dist/qa/types.d.ts +24 -0
  95. package/dist/qa/types.js +1 -0
  96. package/dist/scanner/csharp.d.ts +23 -0
  97. package/dist/scanner/csharp.js +421 -0
  98. package/dist/scanner/index.js +53 -26
  99. package/dist/scanner/java.d.ts +39 -0
  100. package/dist/scanner/java.js +318 -0
  101. package/dist/scanner/kotlin.d.ts +23 -0
  102. package/dist/scanner/kotlin.js +389 -0
  103. package/dist/scanner/php.d.ts +57 -0
  104. package/dist/scanner/php.js +351 -0
  105. package/dist/scanner/python.js +17 -0
  106. package/dist/scanner/ruby.d.ts +36 -0
  107. package/dist/scanner/ruby.js +431 -0
  108. package/dist/scanner/swift.d.ts +25 -0
  109. package/dist/scanner/swift.js +392 -0
  110. package/dist/scanner/types.d.ts +1 -1
  111. package/dist/template/content/docs/_navigation.json +46 -0
  112. package/dist/template/content/docs/_sidebars.json +684 -0
  113. package/dist/template/content/docs/core.md +4544 -0
  114. package/dist/template/content/docs/index.mdx +89 -0
  115. package/dist/template/content/docs/integrations.md +1158 -0
  116. package/dist/template/content/docs/llms-full.md +403 -0
  117. package/dist/template/content/docs/llms.txt +4588 -0
  118. package/dist/template/content/docs/other.md +10379 -0
  119. package/dist/template/content/docs/tools.md +746 -0
  120. package/dist/template/content/docs/types.md +531 -0
  121. package/dist/template/docs.json +13 -11
  122. package/dist/template/mdx-components.tsx +27 -2
  123. package/dist/template/package.json +6 -0
  124. package/dist/template/public/search-index.json +1 -1
  125. package/dist/template/scripts/build-search-index.mjs +149 -13
  126. package/dist/template/src/app/api/chat/route.ts +83 -128
  127. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  128. package/dist/template/src/app/docs/llms-full.md +151 -4
  129. package/dist/template/src/app/docs/llms.txt +2464 -847
  130. package/dist/template/src/app/docs/page.mdx +48 -38
  131. package/dist/template/src/app/layout.tsx +3 -1
  132. package/dist/template/src/app/page.tsx +22 -8
  133. package/dist/template/src/components/ai-chat.tsx +73 -64
  134. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  135. package/dist/template/src/components/copy-button.tsx +13 -9
  136. package/dist/template/src/components/copy-page-button.tsx +54 -0
  137. package/dist/template/src/components/docs-layout.tsx +37 -25
  138. package/dist/template/src/components/header.tsx +51 -10
  139. package/dist/template/src/components/mdx/card.tsx +17 -3
  140. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  141. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  142. package/dist/template/src/components/mdx/heading.tsx +15 -2
  143. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  144. package/dist/template/src/components/mdx/index.tsx +2 -0
  145. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  146. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  147. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  148. package/dist/template/src/components/sidebar.tsx +12 -18
  149. package/dist/template/src/components/table-of-contents.tsx +9 -0
  150. package/dist/template/src/lib/highlight.ts +3 -88
  151. package/dist/template/src/lib/navigation.ts +159 -0
  152. package/dist/template/src/lib/search-types.ts +4 -1
  153. package/dist/template/src/lib/search.ts +30 -7
  154. package/dist/template/src/styles/globals.css +17 -6
  155. package/dist/utils/files.d.ts +9 -1
  156. package/dist/utils/files.js +59 -10
  157. package/dist/utils/validation.d.ts +0 -3
  158. package/dist/utils/validation.js +0 -26
  159. package/package.json +5 -1
@@ -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
  });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const securityCommand: Command;
@@ -0,0 +1,109 @@
1
+ import { Command } from 'commander';
2
+ import { existsSync, statSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { getAuthConfigAsync, getKeyStorageMethod } from '../auth/index.js';
6
+ import { keychainAvailable, getKeychainPlatformName } from '../auth/keychain.js';
7
+ export const securityCommand = new Command('security')
8
+ .description('Show security and key storage details')
9
+ .action(async () => {
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');
20
+ }
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
+ }
31
+ }
32
+ else {
33
+ console.log(' ○ No API key configured');
34
+ console.log(' Run: skrypt login');
35
+ }
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');
52
+ }
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
+ }
62
+ }
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)) {
73
+ try {
74
+ const dirStat = statSync(configDir);
75
+ const dirMode = (dirStat.mode & 0o777).toString(8);
76
+ console.log(` ~/.skrypt/ ${dirMode} (${dirMode === '700' ? '✓' : '⚠ expected 700'})`);
77
+ }
78
+ catch {
79
+ console.log(' ~/.skrypt/ unable to read');
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');
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('');
104
+ }
105
+ catch (err) {
106
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
107
+ process.exit(1);
108
+ }
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
  });