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.
- package/dist/auth/index.d.ts +13 -3
- package/dist/auth/index.js +101 -9
- package/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +82 -0
- package/dist/auth/notices.d.ts +3 -0
- package/dist/auth/notices.js +42 -0
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +10 -24
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +20 -3
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +125 -7
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/import.d.ts +2 -0
- package/dist/commands/import.js +157 -0
- package/dist/commands/init.js +19 -7
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +63 -34
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -92
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +36 -7
- package/dist/importers/confluence.d.ts +5 -0
- package/dist/importers/confluence.js +137 -0
- package/dist/importers/detect.d.ts +20 -0
- package/dist/importers/detect.js +121 -0
- package/dist/importers/docusaurus.d.ts +5 -0
- package/dist/importers/docusaurus.js +279 -0
- package/dist/importers/gitbook.d.ts +5 -0
- package/dist/importers/gitbook.js +189 -0
- package/dist/importers/github.d.ts +8 -0
- package/dist/importers/github.js +99 -0
- package/dist/importers/index.d.ts +15 -0
- package/dist/importers/index.js +30 -0
- package/dist/importers/markdown.d.ts +6 -0
- package/dist/importers/markdown.js +105 -0
- package/dist/importers/mintlify.d.ts +5 -0
- package/dist/importers/mintlify.js +172 -0
- package/dist/importers/notion.d.ts +5 -0
- package/dist/importers/notion.js +174 -0
- package/dist/importers/readme.d.ts +5 -0
- package/dist/importers/readme.js +184 -0
- package/dist/importers/transform.d.ts +90 -0
- package/dist/importers/transform.js +457 -0
- package/dist/importers/types.d.ts +37 -0
- package/dist/importers/types.js +1 -0
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/plugins/index.js +7 -0
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +53 -26
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/python.js +17 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +149 -13
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +5 -1
package/dist/commands/sdk.js
CHANGED
|
@@ -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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
317
|
+
allSamples.push({ path, method, operationId, summary, samples });
|
|
315
318
|
}
|
|
316
|
-
allSamples.push({ path, method, operationId, summary, samples });
|
|
317
319
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
358
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
console.log(
|
|
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,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
|
+
});
|
package/dist/commands/test.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
//
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
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('
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
331
|
-
|
|
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
|
});
|