skrypt-ai 0.6.0 → 0.7.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 (88) hide show
  1. package/dist/audit/doc-parser.d.ts +5 -0
  2. package/dist/audit/doc-parser.js +106 -0
  3. package/dist/audit/index.d.ts +4 -0
  4. package/dist/audit/index.js +4 -0
  5. package/dist/audit/matcher.d.ts +6 -0
  6. package/dist/audit/matcher.js +94 -0
  7. package/dist/audit/reporter.d.ts +9 -0
  8. package/dist/audit/reporter.js +106 -0
  9. package/dist/audit/types.d.ts +37 -0
  10. package/dist/audit/types.js +1 -0
  11. package/dist/auth/index.js +3 -1
  12. package/dist/cli.js +11 -1
  13. package/dist/commands/audit.d.ts +2 -0
  14. package/dist/commands/audit.js +59 -0
  15. package/dist/commands/config.d.ts +2 -0
  16. package/dist/commands/config.js +73 -0
  17. package/dist/commands/cron.js +4 -0
  18. package/dist/commands/generate.d.ts +7 -0
  19. package/dist/commands/generate.js +528 -234
  20. package/dist/commands/refresh.d.ts +2 -0
  21. package/dist/commands/refresh.js +158 -0
  22. package/dist/commands/review-pr.js +5 -0
  23. package/dist/commands/review.d.ts +2 -0
  24. package/dist/commands/review.js +110 -0
  25. package/dist/commands/test.js +177 -236
  26. package/dist/commands/watch.js +29 -20
  27. package/dist/config/loader.d.ts +6 -1
  28. package/dist/config/loader.js +38 -2
  29. package/dist/config/types.d.ts +7 -0
  30. package/dist/generator/generator.js +2 -1
  31. package/dist/generator/types.d.ts +3 -0
  32. package/dist/generator/writer.js +60 -28
  33. package/dist/github/org-discovery.d.ts +17 -0
  34. package/dist/github/org-discovery.js +93 -0
  35. package/dist/llm/index.d.ts +2 -0
  36. package/dist/llm/index.js +8 -2
  37. package/dist/next-actions/actions.d.ts +2 -0
  38. package/dist/next-actions/actions.js +190 -0
  39. package/dist/next-actions/index.d.ts +6 -0
  40. package/dist/next-actions/index.js +39 -0
  41. package/dist/next-actions/setup.d.ts +2 -0
  42. package/dist/next-actions/setup.js +72 -0
  43. package/dist/next-actions/state.d.ts +7 -0
  44. package/dist/next-actions/state.js +68 -0
  45. package/dist/next-actions/suggest.d.ts +3 -0
  46. package/dist/next-actions/suggest.js +47 -0
  47. package/dist/next-actions/types.d.ts +26 -0
  48. package/dist/next-actions/types.js +1 -0
  49. package/dist/refresh/differ.d.ts +9 -0
  50. package/dist/refresh/differ.js +67 -0
  51. package/dist/refresh/index.d.ts +4 -0
  52. package/dist/refresh/index.js +4 -0
  53. package/dist/refresh/manifest.d.ts +18 -0
  54. package/dist/refresh/manifest.js +71 -0
  55. package/dist/refresh/splicer.d.ts +9 -0
  56. package/dist/refresh/splicer.js +50 -0
  57. package/dist/refresh/types.d.ts +37 -0
  58. package/dist/refresh/types.js +1 -0
  59. package/dist/review/index.d.ts +8 -0
  60. package/dist/review/index.js +94 -0
  61. package/dist/review/parser.d.ts +16 -0
  62. package/dist/review/parser.js +95 -0
  63. package/dist/review/types.d.ts +18 -0
  64. package/dist/review/types.js +1 -0
  65. package/dist/scanner/types.d.ts +2 -0
  66. package/dist/structure/index.d.ts +19 -0
  67. package/dist/structure/index.js +92 -0
  68. package/dist/structure/planner.d.ts +8 -0
  69. package/dist/structure/planner.js +180 -0
  70. package/dist/structure/topology.d.ts +16 -0
  71. package/dist/structure/topology.js +49 -0
  72. package/dist/structure/types.d.ts +26 -0
  73. package/dist/structure/types.js +1 -0
  74. package/dist/testing/comparator.d.ts +7 -0
  75. package/dist/testing/comparator.js +77 -0
  76. package/dist/testing/docker.d.ts +21 -0
  77. package/dist/testing/docker.js +234 -0
  78. package/dist/testing/env.d.ts +16 -0
  79. package/dist/testing/env.js +58 -0
  80. package/dist/testing/extractor.d.ts +9 -0
  81. package/dist/testing/extractor.js +195 -0
  82. package/dist/testing/index.d.ts +6 -0
  83. package/dist/testing/index.js +6 -0
  84. package/dist/testing/runner.d.ts +5 -0
  85. package/dist/testing/runner.js +225 -0
  86. package/dist/testing/types.d.ts +58 -0
  87. package/dist/testing/types.js +1 -0
  88. package/package.json +1 -1
@@ -0,0 +1,5 @@
1
+ import { DocumentedElement } from './types.js';
2
+ /**
3
+ * Extract documented elements from markdown files
4
+ */
5
+ export declare function parseDocumentedElements(docsDir: string): DocumentedElement[];
@@ -0,0 +1,106 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ /**
4
+ * Find all markdown files in a docs directory
5
+ */
6
+ function findMarkdownFiles(dir) {
7
+ const files = [];
8
+ function walk(currentDir) {
9
+ const entries = readdirSync(currentDir);
10
+ for (const entry of entries) {
11
+ const fullPath = join(currentDir, entry);
12
+ const stat = statSync(fullPath);
13
+ if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
14
+ walk(fullPath);
15
+ }
16
+ else if (stat.isFile() && (extname(entry) === '.md' || extname(entry) === '.mdx')) {
17
+ files.push(fullPath);
18
+ }
19
+ }
20
+ }
21
+ walk(dir);
22
+ return files;
23
+ }
24
+ /**
25
+ * Extract documented elements from markdown files
26
+ */
27
+ export function parseDocumentedElements(docsDir) {
28
+ const files = findMarkdownFiles(docsDir);
29
+ const elements = [];
30
+ for (const filePath of files) {
31
+ const content = readFileSync(filePath, 'utf-8');
32
+ const lines = content.split('\n');
33
+ let hasCodeBlock = false;
34
+ let currentElement = null;
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const line = lines[i];
37
+ if (hasCodeBlock) {
38
+ if (line.trim().startsWith('```')) {
39
+ hasCodeBlock = false;
40
+ }
41
+ continue;
42
+ }
43
+ // Look for headings that indicate API elements
44
+ // Pattern: ### `functionName` or ## `ClassName`
45
+ const headingMatch = line.match(/^#{2,4}\s+`([^`]+)`/);
46
+ if (headingMatch) {
47
+ // Save previous element
48
+ if (currentElement?.name) {
49
+ elements.push(currentElement);
50
+ }
51
+ const name = headingMatch[1];
52
+ currentElement = {
53
+ name,
54
+ kind: 'unknown',
55
+ filePath,
56
+ lineNumber: i + 1,
57
+ hasExample: false,
58
+ };
59
+ continue;
60
+ }
61
+ // Look for signature code blocks right after element heading
62
+ // Must check before generic code block toggle
63
+ if (line.match(/^```\w+/)) {
64
+ hasCodeBlock = true;
65
+ if (currentElement) {
66
+ currentElement.hasExample = true;
67
+ if (!currentElement.signature && i + 1 < lines.length) {
68
+ const sigLines = [];
69
+ for (let j = i + 1; j < lines.length; j++) {
70
+ if (lines[j].trim() === '```')
71
+ break;
72
+ sigLines.push(lines[j]);
73
+ }
74
+ currentElement.signature = sigLines.join('\n').trim();
75
+ }
76
+ }
77
+ continue;
78
+ }
79
+ // Plain ``` (closing or code block without language)
80
+ if (line.trim().startsWith('```')) {
81
+ hasCodeBlock = true;
82
+ if (currentElement) {
83
+ currentElement.hasExample = true;
84
+ }
85
+ continue;
86
+ }
87
+ // Detect kind from context
88
+ if (currentElement && currentElement.kind === 'unknown') {
89
+ if (/\bmethod\b/i.test(line)) {
90
+ currentElement.kind = 'method';
91
+ }
92
+ else if (/\bclass\b/i.test(line)) {
93
+ currentElement.kind = 'class';
94
+ }
95
+ else if (/\bfunction\b/i.test(line)) {
96
+ currentElement.kind = 'function';
97
+ }
98
+ }
99
+ }
100
+ // Save last element
101
+ if (currentElement?.name) {
102
+ elements.push(currentElement);
103
+ }
104
+ }
105
+ return elements;
106
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './doc-parser.js';
3
+ export * from './matcher.js';
4
+ export * from './reporter.js';
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './doc-parser.js';
3
+ export * from './matcher.js';
4
+ export * from './reporter.js';
@@ -0,0 +1,6 @@
1
+ import { APIElement } from '../scanner/types.js';
2
+ import { DocumentedElement, AuditReport } from './types.js';
3
+ /**
4
+ * Cross-reference code elements with documented elements to produce an audit report
5
+ */
6
+ export declare function crossReference(codeElements: APIElement[], docElements: DocumentedElement[]): AuditReport;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Normalize a name for fuzzy matching (lowercase, strip common prefixes)
3
+ */
4
+ function normalizeName(name) {
5
+ return name
6
+ .replace(/^(async\s+)?/, '')
7
+ .replace(/\(.*\)$/, '')
8
+ .trim()
9
+ .toLowerCase();
10
+ }
11
+ /**
12
+ * Normalize a signature for comparison
13
+ */
14
+ function normalizeSignature(sig) {
15
+ return sig
16
+ .replace(/\s+/g, ' ')
17
+ .replace(/\s*:\s*/g, ': ')
18
+ .replace(/\s*=\s*/g, ' = ')
19
+ .trim();
20
+ }
21
+ /**
22
+ * Cross-reference code elements with documented elements to produce an audit report
23
+ */
24
+ export function crossReference(codeElements, docElements) {
25
+ const docNames = new Map();
26
+ // Index documented elements by normalized name
27
+ for (const doc of docElements) {
28
+ const key = normalizeName(doc.name);
29
+ if (!docNames.has(key))
30
+ docNames.set(key, []);
31
+ docNames.get(key).push(doc);
32
+ }
33
+ const matchedCodeNames = new Set();
34
+ const matchedDocNames = new Set();
35
+ const undocumented = [];
36
+ const signatureMismatches = [];
37
+ const missingExamples = [];
38
+ // Find undocumented code elements and signature mismatches
39
+ for (const codeEl of codeElements) {
40
+ const key = normalizeName(codeEl.name);
41
+ const docMatches = docNames.get(key);
42
+ if (!docMatches || docMatches.length === 0) {
43
+ undocumented.push(codeEl);
44
+ continue;
45
+ }
46
+ matchedCodeNames.add(key);
47
+ for (const docEl of docMatches) {
48
+ matchedDocNames.add(normalizeName(docEl.name));
49
+ // Check for signature mismatch
50
+ if (docEl.signature && codeEl.signature) {
51
+ const normalizedDoc = normalizeSignature(docEl.signature);
52
+ const normalizedCode = normalizeSignature(codeEl.signature);
53
+ if (normalizedDoc !== normalizedCode) {
54
+ signatureMismatches.push({
55
+ element: codeEl,
56
+ docsSignature: docEl.signature,
57
+ codeSignature: codeEl.signature,
58
+ docFile: docEl.filePath,
59
+ });
60
+ }
61
+ }
62
+ // Check for missing examples
63
+ if (!docEl.hasExample) {
64
+ missingExamples.push(docEl);
65
+ }
66
+ }
67
+ }
68
+ // Find orphaned docs (documented but no matching code)
69
+ const orphaned = [];
70
+ for (const doc of docElements) {
71
+ const key = normalizeName(doc.name);
72
+ if (!matchedCodeNames.has(key)) {
73
+ orphaned.push({ name: doc.name, filePath: doc.filePath });
74
+ }
75
+ }
76
+ const documented = codeElements.length - undocumented.length;
77
+ const coveragePercent = codeElements.length > 0
78
+ ? Math.round((documented / codeElements.length) * 100)
79
+ : 100;
80
+ // Staleness score: ratio of signature mismatches to documented elements
81
+ const stalenessScore = documented > 0
82
+ ? Math.round((signatureMismatches.length / documented) * 100)
83
+ : 0;
84
+ return {
85
+ undocumented,
86
+ orphaned,
87
+ signatureMismatches,
88
+ missingExamples,
89
+ coveragePercent,
90
+ stalenessScore,
91
+ totalCodeElements: codeElements.length,
92
+ totalDocElements: docElements.length,
93
+ };
94
+ }
@@ -0,0 +1,9 @@
1
+ import { AuditReport } from './types.js';
2
+ /**
3
+ * Format audit report as human-readable text
4
+ */
5
+ export declare function formatTextReport(report: AuditReport, basePath: string): string;
6
+ /**
7
+ * Format audit report as JSON
8
+ */
9
+ export declare function formatJsonReport(report: AuditReport): string;
@@ -0,0 +1,106 @@
1
+ import { relative } from 'path';
2
+ /**
3
+ * Format audit report as human-readable text
4
+ */
5
+ export function formatTextReport(report, basePath) {
6
+ const lines = [];
7
+ lines.push('skrypt audit\n');
8
+ lines.push(` Coverage: ${report.coveragePercent}% (${report.totalCodeElements - report.undocumented.length}/${report.totalCodeElements} elements documented)`);
9
+ if (report.stalenessScore > 0) {
10
+ lines.push(` Staleness: ${report.stalenessScore}% of documented elements have signature mismatches`);
11
+ }
12
+ lines.push('');
13
+ // Undocumented
14
+ if (report.undocumented.length > 0) {
15
+ lines.push(` UNDOCUMENTED (${report.undocumented.length})`);
16
+ // Group by file
17
+ const byFile = new Map();
18
+ for (const el of report.undocumented) {
19
+ const rel = relative(basePath, el.filePath);
20
+ if (!byFile.has(rel))
21
+ byFile.set(rel, []);
22
+ byFile.get(rel).push(`${el.name}()`);
23
+ }
24
+ for (const [file, names] of byFile) {
25
+ lines.push(` ${file} → ${names.join(', ')}`);
26
+ }
27
+ lines.push('');
28
+ }
29
+ // Signature mismatches (stale)
30
+ if (report.signatureMismatches.length > 0) {
31
+ lines.push(` STALE (${report.signatureMismatches.length})`);
32
+ for (const mismatch of report.signatureMismatches.slice(0, 20)) {
33
+ const docRel = relative(basePath, mismatch.docFile);
34
+ lines.push(` ${docRel} → ${mismatch.element.name}`);
35
+ lines.push(` docs: ${mismatch.docsSignature.slice(0, 80)}`);
36
+ lines.push(` code: ${mismatch.codeSignature.slice(0, 80)}`);
37
+ }
38
+ if (report.signatureMismatches.length > 20) {
39
+ lines.push(` ... and ${report.signatureMismatches.length - 20} more`);
40
+ }
41
+ lines.push('');
42
+ }
43
+ // Orphaned docs
44
+ if (report.orphaned.length > 0) {
45
+ lines.push(` ORPHANED (${report.orphaned.length})`);
46
+ for (const orphan of report.orphaned.slice(0, 20)) {
47
+ const rel = relative(basePath, orphan.filePath);
48
+ lines.push(` ${rel} — ${orphan.name} — no matching code`);
49
+ }
50
+ if (report.orphaned.length > 20) {
51
+ lines.push(` ... and ${report.orphaned.length - 20} more`);
52
+ }
53
+ lines.push('');
54
+ }
55
+ // Missing examples
56
+ if (report.missingExamples.length > 0) {
57
+ lines.push(` MISSING EXAMPLES (${report.missingExamples.length})`);
58
+ for (const el of report.missingExamples.slice(0, 20)) {
59
+ const rel = relative(basePath, el.filePath);
60
+ lines.push(` ${rel}:${el.lineNumber} — ${el.name}`);
61
+ }
62
+ if (report.missingExamples.length > 20) {
63
+ lines.push(` ... and ${report.missingExamples.length - 20} more`);
64
+ }
65
+ lines.push('');
66
+ }
67
+ if (report.undocumented.length === 0 && report.signatureMismatches.length === 0 &&
68
+ report.orphaned.length === 0 && report.missingExamples.length === 0) {
69
+ lines.push(' All elements documented with matching signatures and examples!');
70
+ }
71
+ return lines.join('\n');
72
+ }
73
+ /**
74
+ * Format audit report as JSON
75
+ */
76
+ export function formatJsonReport(report) {
77
+ return JSON.stringify({
78
+ coverage: {
79
+ percent: report.coveragePercent,
80
+ documented: report.totalCodeElements - report.undocumented.length,
81
+ total: report.totalCodeElements,
82
+ },
83
+ staleness: {
84
+ score: report.stalenessScore,
85
+ mismatches: report.signatureMismatches.length,
86
+ },
87
+ undocumented: report.undocumented.map(el => ({
88
+ name: el.name,
89
+ kind: el.kind,
90
+ file: el.filePath,
91
+ line: el.lineNumber,
92
+ })),
93
+ orphaned: report.orphaned,
94
+ signatureMismatches: report.signatureMismatches.map(m => ({
95
+ name: m.element.name,
96
+ docFile: m.docFile,
97
+ docsSignature: m.docsSignature,
98
+ codeSignature: m.codeSignature,
99
+ })),
100
+ missingExamples: report.missingExamples.map(el => ({
101
+ name: el.name,
102
+ file: el.filePath,
103
+ line: el.lineNumber,
104
+ })),
105
+ }, null, 2);
106
+ }
@@ -0,0 +1,37 @@
1
+ import { APIElement } from '../scanner/types.js';
2
+ /**
3
+ * An element that has been documented (found in markdown)
4
+ */
5
+ export interface DocumentedElement {
6
+ name: string;
7
+ kind: 'function' | 'class' | 'method' | 'unknown';
8
+ signature?: string;
9
+ filePath: string;
10
+ lineNumber: number;
11
+ hasExample: boolean;
12
+ }
13
+ /**
14
+ * A mismatch between code and docs signatures
15
+ */
16
+ export interface SignatureMismatch {
17
+ element: APIElement;
18
+ docsSignature: string;
19
+ codeSignature: string;
20
+ docFile: string;
21
+ }
22
+ /**
23
+ * Full audit report
24
+ */
25
+ export interface AuditReport {
26
+ undocumented: APIElement[];
27
+ orphaned: Array<{
28
+ name: string;
29
+ filePath: string;
30
+ }>;
31
+ signatureMismatches: SignatureMismatch[];
32
+ missingExamples: DocumentedElement[];
33
+ coveragePercent: number;
34
+ stalenessScore: number;
35
+ totalCodeElements: number;
36
+ totalDocElements: number;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -180,5 +180,7 @@ export const PRO_COMMANDS = [
180
180
  'sdk',
181
181
  'gh-action',
182
182
  'ci',
183
- 'mcp'
183
+ 'mcp',
184
+ 'refresh',
185
+ 'review',
184
186
  ];
package/dist/cli.js CHANGED
@@ -22,6 +22,11 @@ import { testCommand } from './commands/test.js';
22
22
  import { securityCommand } from './commands/security.js';
23
23
  import { importCommand } from './commands/import.js';
24
24
  import { healCommand } from './commands/heal.js';
25
+ import { auditCommand } from './commands/audit.js';
26
+ import { refreshCommand } from './commands/refresh.js';
27
+ import { reviewCommand } from './commands/review.js';
28
+ import { configCommand } from './commands/config.js';
29
+ import { handlePostAction } from './next-actions/index.js';
25
30
  import { createRequire } from 'module';
26
31
  process.on('uncaughtException', (err) => {
27
32
  console.error('\x1b[31mFatal error:\x1b[0m', err.message);
@@ -70,7 +75,8 @@ program
70
75
  .name('skrypt')
71
76
  .description('AI-powered documentation generator with code examples')
72
77
  .version(VERSION)
73
- .hook('postAction', async () => {
78
+ .hook('postAction', async (_thisCommand, actionCommand) => {
79
+ await handlePostAction(actionCommand.name());
74
80
  await checkForUpdates();
75
81
  });
76
82
  program.addCommand(initCommand);
@@ -97,4 +103,8 @@ program.addCommand(testCommand);
97
103
  program.addCommand(securityCommand);
98
104
  program.addCommand(importCommand);
99
105
  program.addCommand(healCommand);
106
+ program.addCommand(auditCommand);
107
+ program.addCommand(refreshCommand);
108
+ program.addCommand(reviewCommand);
109
+ program.addCommand(configCommand);
100
110
  program.parse();
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const auditCommand: Command;
@@ -0,0 +1,59 @@
1
+ import { Command } from 'commander';
2
+ import { existsSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { scanDirectory } from '../scanner/index.js';
5
+ import { parseDocumentedElements, crossReference, formatTextReport, formatJsonReport } from '../audit/index.js';
6
+ export const auditCommand = new Command('audit')
7
+ .description('Audit documentation coverage and staleness')
8
+ .argument('[source]', 'Source code directory to audit', '.')
9
+ .option('--docs <dir>', 'Documentation directory to compare against', './docs')
10
+ .option('--format <type>', 'Output format: text or json', 'text')
11
+ .option('--public-only', 'Only audit exported/public APIs')
12
+ .action(async (source, options) => {
13
+ try {
14
+ const sourcePath = resolve(source);
15
+ const docsPath = resolve(options.docs || './docs');
16
+ if (!existsSync(sourcePath)) {
17
+ console.error(`Error: Source directory not found: ${sourcePath}`);
18
+ process.exit(1);
19
+ }
20
+ if (!existsSync(docsPath)) {
21
+ console.error(`Error: Docs directory not found: ${docsPath}`);
22
+ console.error(' Specify with --docs <dir>');
23
+ process.exit(1);
24
+ }
25
+ // Step 1: Scan source code
26
+ console.log('Scanning source code...');
27
+ const scanResult = await scanDirectory(sourcePath, {
28
+ onProgress: (current, total, file) => {
29
+ process.stdout.write(`\r [${current}/${total}] ${file.slice(-50).padStart(50)}`);
30
+ }
31
+ });
32
+ console.log('');
33
+ let codeElements = scanResult.files.flatMap(f => f.elements);
34
+ if (options.publicOnly) {
35
+ codeElements = codeElements.filter(el => el.isExported === true || el.isPublic === true);
36
+ }
37
+ // Step 2: Parse documented elements
38
+ console.log('Parsing documentation...');
39
+ const docElements = parseDocumentedElements(docsPath);
40
+ console.log(` Found ${docElements.length} documented elements\n`);
41
+ // Step 3: Cross-reference
42
+ const report = crossReference(codeElements, docElements);
43
+ // Step 4: Output
44
+ if (options.format === 'json') {
45
+ console.log(formatJsonReport(report));
46
+ }
47
+ else {
48
+ console.log(formatTextReport(report, sourcePath));
49
+ }
50
+ // Exit with error code if coverage is below threshold
51
+ if (report.undocumented.length > 0 || report.signatureMismatches.length > 0) {
52
+ process.exit(1);
53
+ }
54
+ }
55
+ catch (err) {
56
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
57
+ process.exit(1);
58
+ }
59
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const configCommand: Command;
@@ -0,0 +1,73 @@
1
+ import { Command } from 'commander';
2
+ import { readPreferences, writePreferences, runFirstTimeSetup } from '../next-actions/index.js';
3
+ export const configCommand = new Command('config')
4
+ .description('Configure Skrypt preferences')
5
+ .option('--suggestions', 'Configure next-step suggestions')
6
+ .option('--suggestions-on', 'Enable next-step suggestions')
7
+ .option('--suggestions-off', 'Disable next-step suggestions')
8
+ .action(async (options) => {
9
+ // Reject conflicting flags
10
+ const flagCount = [options.suggestions, options.suggestionsOn, options.suggestionsOff].filter(Boolean).length;
11
+ if (flagCount > 1) {
12
+ console.error('Error: Use only one of --suggestions, --suggestions-on, --suggestions-off');
13
+ process.exit(1);
14
+ }
15
+ if (options.suggestionsOn) {
16
+ const prefs = readPreferences();
17
+ if (prefs) {
18
+ prefs.enabled = true;
19
+ writePreferences(prefs);
20
+ }
21
+ else {
22
+ writePreferences({
23
+ enabled: true,
24
+ categories: { workflow: true, quality: true, cicd: true, advanced: true },
25
+ });
26
+ }
27
+ console.log(' Next-step suggestions enabled.');
28
+ return;
29
+ }
30
+ if (options.suggestionsOff) {
31
+ const prefs = readPreferences();
32
+ if (prefs) {
33
+ prefs.enabled = false;
34
+ writePreferences(prefs);
35
+ }
36
+ else {
37
+ writePreferences({
38
+ enabled: false,
39
+ categories: { workflow: false, quality: false, cicd: false, advanced: false },
40
+ });
41
+ }
42
+ console.log(' Next-step suggestions disabled.');
43
+ return;
44
+ }
45
+ if (options.suggestions) {
46
+ await runFirstTimeSetup();
47
+ return;
48
+ }
49
+ // No flags — show current config
50
+ const prefs = readPreferences();
51
+ console.log('skrypt config');
52
+ console.log('');
53
+ if (!prefs) {
54
+ console.log(' No preferences set yet. Run any command to configure.');
55
+ console.log('');
56
+ console.log(' Options:');
57
+ }
58
+ else {
59
+ console.log(' Suggestions: ' + (prefs.enabled ? '\x1b[32menabled\x1b[0m' : '\x1b[90mdisabled\x1b[0m'));
60
+ if (prefs.enabled) {
61
+ console.log(' Categories:');
62
+ console.log(` workflow: ${prefs.categories.workflow ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
63
+ console.log(` quality: ${prefs.categories.quality ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
64
+ console.log(` cicd: ${prefs.categories.cicd ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
65
+ console.log(` advanced: ${prefs.categories.advanced ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
66
+ }
67
+ console.log('');
68
+ console.log(' Options:');
69
+ }
70
+ console.log(' --suggestions Reconfigure suggestion categories');
71
+ console.log(' --suggestions-on Enable suggestions');
72
+ console.log(' --suggestions-off Disable suggestions');
73
+ });
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { existsSync, writeFileSync, mkdirSync } from 'fs';
3
3
  import { resolve, join } from 'path';
4
+ import { requirePro } from '../auth/index.js';
4
5
  const CRON_WORKFLOW = `name: Skrypt Auto-Update Docs
5
6
 
6
7
  on:
@@ -54,6 +55,9 @@ export const cronCommand = new Command('cron')
54
55
  .option('-s, --schedule <cron>', 'Cron schedule (default: daily at 2am UTC)', '0 2 * * *')
55
56
  .option('-f, --force', 'Overwrite existing workflow')
56
57
  .action(async (repoPath, options) => {
58
+ if (!await requirePro('cron')) {
59
+ process.exit(1);
60
+ }
57
61
  try {
58
62
  const resolvedPath = resolve(repoPath);
59
63
  const workflowDir = join(resolvedPath, '.github', 'workflows');
@@ -1,2 +1,9 @@
1
1
  import { Command } from 'commander';
2
+ import { SourceEntry } from '../config/types.js';
3
+ /**
4
+ * Parse source arguments with optional labels.
5
+ * e.g. "./api:API" → { path: "./api", label: "API" }
6
+ * e.g. "./src" → { path: "./src" }
7
+ */
8
+ export declare function parseSourceArgs(args: string[]): SourceEntry[];
2
9
  export declare const generateCommand: Command;