skrypt-ai 0.6.1 → 0.8.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 (180) 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/auth/index.js +6 -4
  11. package/dist/cli.js +12 -2
  12. package/dist/commands/audit.d.ts +2 -0
  13. package/dist/commands/audit.js +59 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +73 -0
  16. package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
  17. package/dist/commands/generate/index.js +393 -0
  18. package/dist/commands/generate/scan.d.ts +41 -0
  19. package/dist/commands/generate/scan.js +256 -0
  20. package/dist/commands/generate/verify.d.ts +14 -0
  21. package/dist/commands/generate/verify.js +122 -0
  22. package/dist/commands/generate/write.d.ts +25 -0
  23. package/dist/commands/generate/write.js +120 -0
  24. package/dist/commands/import.js +4 -1
  25. package/dist/commands/llms-txt.js +6 -4
  26. package/dist/commands/refresh.d.ts +2 -0
  27. package/dist/commands/refresh.js +158 -0
  28. package/dist/commands/review.d.ts +2 -0
  29. package/dist/commands/review.js +110 -0
  30. package/dist/commands/test.js +177 -236
  31. package/dist/commands/watch.js +29 -20
  32. package/dist/config/loader.d.ts +6 -2
  33. package/dist/config/loader.js +39 -3
  34. package/dist/config/types.d.ts +7 -0
  35. package/dist/generator/agents-md.d.ts +25 -0
  36. package/dist/generator/agents-md.js +122 -0
  37. package/dist/generator/generator.js +2 -1
  38. package/dist/generator/index.d.ts +2 -0
  39. package/dist/generator/index.js +2 -0
  40. package/dist/generator/mdx-serializer.d.ts +11 -0
  41. package/dist/generator/mdx-serializer.js +135 -0
  42. package/dist/generator/organizer.d.ts +1 -16
  43. package/dist/generator/organizer.js +0 -38
  44. package/dist/generator/types.d.ts +3 -0
  45. package/dist/generator/writer.js +65 -32
  46. package/dist/github/org-discovery.d.ts +17 -0
  47. package/dist/github/org-discovery.js +93 -0
  48. package/dist/llm/index.d.ts +2 -0
  49. package/dist/llm/index.js +8 -2
  50. package/dist/llm/proxy-client.d.ts +32 -0
  51. package/dist/llm/proxy-client.js +103 -0
  52. package/dist/next-actions/actions.d.ts +2 -0
  53. package/dist/next-actions/actions.js +190 -0
  54. package/dist/next-actions/index.d.ts +6 -0
  55. package/dist/next-actions/index.js +39 -0
  56. package/dist/next-actions/setup.d.ts +2 -0
  57. package/dist/next-actions/setup.js +72 -0
  58. package/dist/next-actions/state.d.ts +7 -0
  59. package/dist/next-actions/state.js +68 -0
  60. package/dist/next-actions/suggest.d.ts +3 -0
  61. package/dist/next-actions/suggest.js +47 -0
  62. package/dist/next-actions/types.d.ts +26 -0
  63. package/dist/refresh/differ.d.ts +9 -0
  64. package/dist/refresh/differ.js +67 -0
  65. package/dist/refresh/index.d.ts +4 -0
  66. package/dist/refresh/index.js +4 -0
  67. package/dist/refresh/manifest.d.ts +18 -0
  68. package/dist/refresh/manifest.js +71 -0
  69. package/dist/refresh/splicer.d.ts +9 -0
  70. package/dist/refresh/splicer.js +50 -0
  71. package/dist/refresh/types.d.ts +37 -0
  72. package/dist/review/index.d.ts +8 -0
  73. package/dist/review/index.js +94 -0
  74. package/dist/review/parser.d.ts +16 -0
  75. package/dist/review/parser.js +95 -0
  76. package/dist/review/types.d.ts +18 -0
  77. package/dist/scanner/csharp.d.ts +0 -4
  78. package/dist/scanner/csharp.js +9 -49
  79. package/dist/scanner/go.d.ts +0 -3
  80. package/dist/scanner/go.js +8 -35
  81. package/dist/scanner/java.d.ts +0 -4
  82. package/dist/scanner/java.js +9 -49
  83. package/dist/scanner/kotlin.d.ts +0 -3
  84. package/dist/scanner/kotlin.js +6 -33
  85. package/dist/scanner/php.d.ts +0 -10
  86. package/dist/scanner/php.js +11 -55
  87. package/dist/scanner/ruby.d.ts +0 -3
  88. package/dist/scanner/ruby.js +8 -38
  89. package/dist/scanner/rust.d.ts +0 -3
  90. package/dist/scanner/rust.js +10 -37
  91. package/dist/scanner/swift.d.ts +0 -3
  92. package/dist/scanner/swift.js +8 -35
  93. package/dist/scanner/types.d.ts +2 -0
  94. package/dist/scanner/utils.d.ts +41 -0
  95. package/dist/scanner/utils.js +97 -0
  96. package/dist/structure/index.d.ts +19 -0
  97. package/dist/structure/index.js +92 -0
  98. package/dist/structure/planner.d.ts +8 -0
  99. package/dist/structure/planner.js +180 -0
  100. package/dist/structure/topology.d.ts +16 -0
  101. package/dist/structure/topology.js +49 -0
  102. package/dist/structure/types.d.ts +26 -0
  103. package/dist/template/docs.json +5 -2
  104. package/dist/template/next.config.mjs +31 -0
  105. package/dist/template/package.json +5 -3
  106. package/dist/template/src/app/layout.tsx +13 -13
  107. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  108. package/dist/template/src/app/llms.txt/route.ts +29 -0
  109. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  110. package/dist/template/src/app/reference/route.ts +22 -18
  111. package/dist/template/src/app/sitemap.ts +1 -1
  112. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  113. package/dist/template/src/components/ai-chat.tsx +20 -193
  114. package/dist/template/src/components/mdx/index.tsx +27 -4
  115. package/dist/template/src/lib/fonts.ts +135 -0
  116. package/dist/template/src/middleware.ts +101 -0
  117. package/dist/template/src/styles/globals.css +28 -20
  118. package/dist/testing/comparator.d.ts +7 -0
  119. package/dist/testing/comparator.js +77 -0
  120. package/dist/testing/docker.d.ts +21 -0
  121. package/dist/testing/docker.js +234 -0
  122. package/dist/testing/env.d.ts +16 -0
  123. package/dist/testing/env.js +58 -0
  124. package/dist/testing/extractor.d.ts +9 -0
  125. package/dist/testing/extractor.js +195 -0
  126. package/dist/testing/index.d.ts +6 -0
  127. package/dist/testing/index.js +6 -0
  128. package/dist/testing/runner.d.ts +5 -0
  129. package/dist/testing/runner.js +225 -0
  130. package/dist/testing/types.d.ts +58 -0
  131. package/dist/utils/files.d.ts +0 -8
  132. package/dist/utils/files.js +0 -33
  133. package/package.json +1 -1
  134. package/dist/autofix/autofix.test.js +0 -487
  135. package/dist/commands/generate.js +0 -445
  136. package/dist/generator/generator.test.js +0 -259
  137. package/dist/generator/writer.test.js +0 -411
  138. package/dist/llm/llm.manual-test.js +0 -112
  139. package/dist/llm/llm.mock-test.d.ts +0 -4
  140. package/dist/llm/llm.mock-test.js +0 -79
  141. package/dist/plugins/index.d.ts +0 -47
  142. package/dist/plugins/index.js +0 -181
  143. package/dist/scanner/content-type.test.js +0 -231
  144. package/dist/scanner/integration.test.d.ts +0 -4
  145. package/dist/scanner/integration.test.js +0 -180
  146. package/dist/scanner/scanner.test.js +0 -210
  147. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  148. package/dist/scanner/typescript.manual-test.js +0 -112
  149. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  150. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  151. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  152. package/dist/template/src/app/docs/config/page.mdx +0 -428
  153. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  154. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  155. package/dist/template/src/app/docs/generator/generator.md +0 -504
  156. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  157. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  158. package/dist/template/src/app/docs/github/page.mdx +0 -502
  159. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  160. package/dist/template/src/app/docs/llm/index.md +0 -471
  161. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  162. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  163. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  164. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  165. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  166. package/dist/template/src/app/docs/scanner/index.md +0 -212
  167. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  168. package/dist/template/src/app/docs/scanner/python.md +0 -469
  169. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  170. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  171. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  172. package/dist/template/src/app/icon.tsx +0 -29
  173. package/dist/utils/validation.d.ts +0 -1
  174. package/dist/utils/validation.js +0 -12
  175. /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
  176. /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
  177. /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
  178. /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
  179. /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
  180. /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
@@ -0,0 +1,17 @@
1
+ export interface DiscoveredRepo {
2
+ name: string;
3
+ full_name: string;
4
+ clone_url: string;
5
+ default_branch: string;
6
+ private: boolean;
7
+ }
8
+ /**
9
+ * Discover repositories in a GitHub organization.
10
+ * Returns up to MAX_REPOS repos sorted by most recently pushed.
11
+ */
12
+ export declare function discoverOrgRepos(org: string, token: string): Promise<DiscoveredRepo[]>;
13
+ /**
14
+ * Shallow clone a repository to a temporary directory.
15
+ * Uses spawnSync with array args to prevent shell injection.
16
+ */
17
+ export declare function cloneRepoToTemp(repo: DiscoveredRepo, token: string): Promise<string>;
@@ -0,0 +1,93 @@
1
+ import { spawnSync } from 'child_process';
2
+ import { mkdtempSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ const MAX_REPOS = 50;
6
+ /**
7
+ * Discover repositories in a GitHub organization.
8
+ * Returns up to MAX_REPOS repos sorted by most recently pushed.
9
+ */
10
+ export async function discoverOrgRepos(org, token) {
11
+ const repos = [];
12
+ let page = 1;
13
+ while (repos.length < MAX_REPOS) {
14
+ const url = `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?per_page=100&sort=pushed&page=${page}`;
15
+ const response = await fetch(url, {
16
+ headers: {
17
+ 'Authorization': `Bearer ${token}`,
18
+ 'Accept': 'application/vnd.github.v3+json',
19
+ 'User-Agent': 'Skrypt-CLI',
20
+ },
21
+ });
22
+ if (!response.ok) {
23
+ if (response.status === 404) {
24
+ throw new Error(`Organization "${org}" not found`);
25
+ }
26
+ if (response.status === 401 || response.status === 403) {
27
+ throw new Error('GitHub token does not have access to this organization');
28
+ }
29
+ throw new Error(`GitHub API error: ${response.status}`);
30
+ }
31
+ const data = await response.json();
32
+ if (data.length === 0)
33
+ break;
34
+ for (const repo of data) {
35
+ if (repos.length >= MAX_REPOS)
36
+ break;
37
+ // Skip archived and forked repos
38
+ if (repo.archived || repo.fork)
39
+ continue;
40
+ repos.push({
41
+ name: repo.name,
42
+ full_name: repo.full_name,
43
+ clone_url: repo.clone_url,
44
+ default_branch: repo.default_branch,
45
+ private: repo.private,
46
+ });
47
+ }
48
+ page++;
49
+ }
50
+ return repos;
51
+ }
52
+ /**
53
+ * Shallow clone a repository to a temporary directory.
54
+ * Uses spawnSync with array args to prevent shell injection.
55
+ */
56
+ export async function cloneRepoToTemp(repo, token) {
57
+ const tempDir = mkdtempSync(join(tmpdir(), `skrypt-${repo.name}-`));
58
+ // Build authenticated clone URL
59
+ const cloneUrl = repo.clone_url.replace('https://', `https://x-access-token:${token}@`);
60
+ let result;
61
+ try {
62
+ result = spawnSync('git', [
63
+ 'clone',
64
+ '--depth', '1',
65
+ '--single-branch',
66
+ '--branch', repo.default_branch,
67
+ cloneUrl,
68
+ tempDir,
69
+ ], {
70
+ stdio: 'pipe',
71
+ timeout: 60_000,
72
+ });
73
+ }
74
+ catch (err) {
75
+ // Clean up temp dir on spawn failure
76
+ try {
77
+ rmSync(tempDir, { recursive: true, force: true });
78
+ }
79
+ catch { /* ignore */ }
80
+ throw new Error(`Failed to clone ${repo.full_name}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
81
+ }
82
+ if (result.status !== 0) {
83
+ // Clean up temp dir on clone failure
84
+ try {
85
+ rmSync(tempDir, { recursive: true, force: true });
86
+ }
87
+ catch { /* ignore */ }
88
+ // Sanitize stderr to avoid leaking the token
89
+ const stderr = (result.stderr?.toString() || '').replace(/x-access-token:[^@]+@/g, 'x-access-token:***@');
90
+ throw new Error(`Failed to clone ${repo.full_name}: ${stderr}`);
91
+ }
92
+ return tempDir;
93
+ }
@@ -52,4 +52,6 @@ export interface GeneratedDocResult {
52
52
  */
53
53
  export declare function generateDocumentation(client: LLMClient, element: ElementContext, options?: {
54
54
  multiLanguage?: boolean;
55
+ verify?: boolean;
56
+ previousError?: string;
55
57
  }): Promise<GeneratedDocResult>;
package/dist/llm/index.js CHANGED
@@ -31,7 +31,10 @@ export function createLLMClient(config) {
31
31
  */
32
32
  export async function generateDocumentation(client, element, options) {
33
33
  const useMultiLang = options?.multiLanguage ?? true;
34
- const prompt = buildDocPrompt(element, useMultiLang);
34
+ let prompt = buildDocPrompt(element, useMultiLang, options?.verify);
35
+ if (options?.previousError) {
36
+ prompt += `\n\n⚠️ IMPORTANT: The previous code example for this element FAILED verification with the following error:\n\`\`\`\n${options.previousError}\n\`\`\`\nGenerate a DIFFERENT, working code example that avoids this error. Ensure the example is self-contained and runs without external dependencies unless specified.`;
37
+ }
35
38
  const response = await client.complete({
36
39
  messages: [
37
40
  {
@@ -127,7 +130,7 @@ Generate ONE self-contained, executable example:
127
130
  ---CODE---
128
131
  [Self-contained example — no markdown fences]
129
132
  ---END---`;
130
- function buildDocPrompt(element, multiLanguage = false) {
133
+ function buildDocPrompt(element, multiLanguage = false, verify = false) {
131
134
  let prompt = '';
132
135
  // Project context first — gives the LLM the "big picture" for better explanations
133
136
  if (element.projectContext) {
@@ -174,6 +177,9 @@ function buildDocPrompt(element, multiLanguage = false) {
174
177
  if (multiLanguage) {
175
178
  prompt += `\nGenerate BOTH TypeScript AND Python self-contained examples.`;
176
179
  }
180
+ if (verify) {
181
+ prompt += `\n\nIMPORTANT: Include \`// Output: <expected>\` comments showing expected console output for every \`console.log\` call (or \`# Output: <expected>\` for Python \`print\` calls). These will be verified by running the code.`;
182
+ }
177
183
  return prompt;
178
184
  }
179
185
  function parseDocResponse(content, elementName) {
@@ -0,0 +1,32 @@
1
+ import { LLMClient, CompletionRequest, CompletionResponse } from './types.js';
2
+ /**
3
+ * LLM client that routes requests through Skrypt's proxy API.
4
+ * Used when the user doesn't have their own API key but has a Skrypt account.
5
+ */
6
+ export declare class ProxyClient implements LLMClient {
7
+ provider: "openai";
8
+ private apiKey;
9
+ private sessionId;
10
+ private timeout;
11
+ constructor(config: {
12
+ apiKey: string;
13
+ sessionId: string;
14
+ timeout?: number;
15
+ });
16
+ complete(request: CompletionRequest): Promise<CompletionResponse>;
17
+ isConfigured(): boolean;
18
+ }
19
+ /**
20
+ * Start a proxy generation session.
21
+ * Returns sessionId + remaining generations info.
22
+ */
23
+ export declare function startProxySession(apiKey: string): Promise<{
24
+ sessionId: string;
25
+ plan: 'free' | 'pro';
26
+ remaining: number;
27
+ used: number;
28
+ }>;
29
+ /**
30
+ * Complete a proxy generation session (increments monthly usage counter).
31
+ */
32
+ export declare function completeProxySession(apiKey: string, sessionId: string): Promise<void>;
@@ -0,0 +1,103 @@
1
+ const API_BASE = process.env.SKRYPT_API_URL || 'https://app.skrypt.sh';
2
+ /**
3
+ * LLM client that routes requests through Skrypt's proxy API.
4
+ * Used when the user doesn't have their own API key but has a Skrypt account.
5
+ */
6
+ export class ProxyClient {
7
+ provider = 'openai'; // Proxy decides which model to use
8
+ apiKey;
9
+ sessionId;
10
+ timeout;
11
+ constructor(config) {
12
+ this.apiKey = config.apiKey;
13
+ this.sessionId = config.sessionId;
14
+ this.timeout = config.timeout ?? 120_000;
15
+ }
16
+ async complete(request) {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), this.timeout);
19
+ try {
20
+ const response = await fetch(`${API_BASE}/api/proxy/generate`, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Authorization': `Bearer ${this.apiKey}`,
24
+ 'Content-Type': 'application/json',
25
+ },
26
+ body: JSON.stringify({
27
+ sessionId: this.sessionId,
28
+ messages: request.messages,
29
+ temperature: request.temperature ?? 0,
30
+ maxTokens: request.maxTokens ?? 4096,
31
+ }),
32
+ signal: controller.signal,
33
+ });
34
+ if (!response.ok) {
35
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
36
+ throw new Error(error.error || `Proxy request failed: ${response.status}`);
37
+ }
38
+ const data = await response.json();
39
+ return {
40
+ content: data.content,
41
+ model: data.model,
42
+ usage: {
43
+ inputTokens: data.usage.inputTokens,
44
+ outputTokens: data.usage.outputTokens,
45
+ totalTokens: data.usage.totalTokens,
46
+ },
47
+ finishReason: data.finishReason || 'stop',
48
+ };
49
+ }
50
+ finally {
51
+ clearTimeout(timer);
52
+ }
53
+ }
54
+ isConfigured() {
55
+ return !!this.apiKey && !!this.sessionId;
56
+ }
57
+ }
58
+ /**
59
+ * Start a proxy generation session.
60
+ * Returns sessionId + remaining generations info.
61
+ */
62
+ export async function startProxySession(apiKey) {
63
+ const response = await fetch(`${API_BASE}/api/proxy/generate/start`, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Authorization': `Bearer ${apiKey}`,
67
+ 'Content-Type': 'application/json',
68
+ },
69
+ body: JSON.stringify({}),
70
+ });
71
+ if (!response.ok) {
72
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
73
+ const errData = error;
74
+ if (response.status === 429) {
75
+ throw new Error(`Monthly generation limit reached (${errData.used}/${errData.limit}). ` +
76
+ `Upgrade to Pro: ${errData.upgrade || 'https://skrypt.sh/pro'}`);
77
+ }
78
+ throw new Error(errData.error || `Failed to start session: ${response.status}`);
79
+ }
80
+ return await response.json();
81
+ }
82
+ /**
83
+ * Complete a proxy generation session (increments monthly usage counter).
84
+ */
85
+ export async function completeProxySession(apiKey, sessionId) {
86
+ try {
87
+ const response = await fetch(`${API_BASE}/api/proxy/generate/complete`, {
88
+ method: 'POST',
89
+ headers: {
90
+ 'Authorization': `Bearer ${apiKey}`,
91
+ 'Content-Type': 'application/json',
92
+ },
93
+ body: JSON.stringify({ sessionId }),
94
+ });
95
+ if (!response.ok) {
96
+ console.warn('Warning: Failed to complete generation session');
97
+ }
98
+ }
99
+ catch {
100
+ // Non-critical — network error, server down, etc.
101
+ console.warn('Warning: Failed to complete generation session');
102
+ }
103
+ }
@@ -0,0 +1,2 @@
1
+ import { ActionDefinition } from './types.js';
2
+ export declare const ACTION_DEFINITIONS: ActionDefinition[];
@@ -0,0 +1,190 @@
1
+ import { existsSync } from 'fs';
2
+ function noWorkflowFile() {
3
+ return !existsSync('.github/workflows/skrypt.yml') &&
4
+ !existsSync('.github/workflows/skrypt.yaml');
5
+ }
6
+ export const ACTION_DEFINITIONS = [
7
+ // ── After init ──────────────────────────────────────────────
8
+ {
9
+ id: 'gen-init',
10
+ afterCommands: ['init'],
11
+ category: 'workflow',
12
+ message: 'Generate docs from your source code',
13
+ command: 'skrypt generate ./src -o ./content/docs',
14
+ priority: 100,
15
+ },
16
+ // ── After generate ──────────────────────────────────────────
17
+ {
18
+ id: 'test-gen',
19
+ afterCommands: ['generate'],
20
+ category: 'workflow',
21
+ message: 'Verify code snippets execute correctly',
22
+ command: 'skrypt test ./docs',
23
+ priority: 100,
24
+ },
25
+ {
26
+ id: 'qa-gen',
27
+ afterCommands: ['generate'],
28
+ category: 'quality',
29
+ message: 'Run quality checks on generated docs',
30
+ command: 'skrypt qa ./docs',
31
+ priority: 80,
32
+ },
33
+ {
34
+ id: 'audit-gen',
35
+ afterCommands: ['generate'],
36
+ category: 'advanced',
37
+ message: 'Check documentation coverage',
38
+ command: 'skrypt audit ./src --docs ./docs',
39
+ priority: 50,
40
+ },
41
+ // ── After test ──────────────────────────────────────────────
42
+ {
43
+ id: 'audit-test',
44
+ afterCommands: ['test'],
45
+ category: 'advanced',
46
+ message: 'Check documentation coverage',
47
+ command: 'skrypt audit ./src --docs ./docs',
48
+ priority: 60,
49
+ },
50
+ // ── After qa ────────────────────────────────────────────────
51
+ {
52
+ id: 'heal-qa',
53
+ afterCommands: ['qa'],
54
+ category: 'quality',
55
+ message: 'Auto-fix QA issues',
56
+ command: 'skrypt heal',
57
+ priority: 90,
58
+ },
59
+ {
60
+ id: 'lint-qa',
61
+ afterCommands: ['qa'],
62
+ category: 'quality',
63
+ message: 'Lint markdown docs',
64
+ command: 'skrypt lint ./docs',
65
+ priority: 70,
66
+ },
67
+ {
68
+ id: 'security-qa',
69
+ afterCommands: ['qa'],
70
+ category: 'quality',
71
+ message: 'Check for security issues',
72
+ command: 'skrypt security ./docs',
73
+ priority: 60,
74
+ },
75
+ // ── After audit ─────────────────────────────────────────────
76
+ {
77
+ id: 'refresh-audit',
78
+ afterCommands: ['audit'],
79
+ category: 'advanced',
80
+ message: 'Update stale docs based on code changes',
81
+ command: 'skrypt refresh ./src --docs ./docs',
82
+ priority: 80,
83
+ },
84
+ {
85
+ id: 'review-audit',
86
+ afterCommands: ['audit'],
87
+ category: 'advanced',
88
+ message: 'Review docs quality with feedback',
89
+ command: 'skrypt review ./docs',
90
+ priority: 60,
91
+ },
92
+ // ── After refresh / review / heal / autofix ─────────────────
93
+ {
94
+ id: 'test-after-fix',
95
+ afterCommands: ['refresh', 'review', 'heal', 'autofix'],
96
+ category: 'workflow',
97
+ message: 'Verify updated docs pass tests',
98
+ command: 'skrypt test ./docs',
99
+ priority: 100,
100
+ },
101
+ // ── After deploy ────────────────────────────────────────────
102
+ {
103
+ id: 'watch-deploy',
104
+ afterCommands: ['deploy'],
105
+ category: 'workflow',
106
+ message: 'Watch for changes in development',
107
+ command: 'skrypt watch',
108
+ priority: 50,
109
+ },
110
+ // ── After import ────────────────────────────────────────────
111
+ {
112
+ id: 'qa-import',
113
+ afterCommands: ['import'],
114
+ category: 'quality',
115
+ message: 'Check imported docs quality',
116
+ command: 'skrypt qa ./docs',
117
+ priority: 90,
118
+ },
119
+ {
120
+ id: 'gen-import',
121
+ afterCommands: ['import'],
122
+ category: 'workflow',
123
+ message: 'Generate docs to fill gaps',
124
+ command: 'skrypt generate ./src -o ./content/docs',
125
+ priority: 70,
126
+ },
127
+ // ── After login ─────────────────────────────────────────────
128
+ {
129
+ id: 'pro-login',
130
+ afterCommands: ['login'],
131
+ category: 'workflow',
132
+ message: 'Unlock Pro: test, heal, autofix, refresh',
133
+ command: 'skrypt test ./docs',
134
+ priority: 100,
135
+ },
136
+ // ── After gh-action ─────────────────────────────────────────
137
+ {
138
+ id: 'push-ci',
139
+ afterCommands: ['gh-action'],
140
+ category: 'cicd',
141
+ message: 'Push to GitHub to trigger CI',
142
+ command: 'git push',
143
+ priority: 100,
144
+ },
145
+ // ── CI/CD setup (cross-command) ─────────────────────────────
146
+ {
147
+ id: 'ci-setup',
148
+ afterCommands: ['generate', 'test', 'deploy'],
149
+ category: 'cicd',
150
+ message: 'Set up GitHub Actions for CI',
151
+ command: 'skrypt gh-action',
152
+ priority: 70,
153
+ condition: noWorkflowFile,
154
+ },
155
+ // ── Deploy (cross-command) ──────────────────────────────────
156
+ {
157
+ id: 'deploy-docs',
158
+ afterCommands: ['generate', 'test', 'heal', 'review', 'refresh', 'autofix'],
159
+ category: 'workflow',
160
+ message: 'Deploy docs to skrypt.sh',
161
+ command: 'skrypt deploy',
162
+ priority: 40,
163
+ },
164
+ // ── After lint / check-links / security ─────────────────────
165
+ {
166
+ id: 'deploy-quality',
167
+ afterCommands: ['lint', 'check-links', 'security'],
168
+ category: 'workflow',
169
+ message: 'Deploy docs to skrypt.sh',
170
+ command: 'skrypt deploy',
171
+ priority: 40,
172
+ },
173
+ {
174
+ id: 'qa-quality',
175
+ afterCommands: ['lint', 'check-links', 'security'],
176
+ category: 'quality',
177
+ message: 'Run full quality checks',
178
+ command: 'skrypt qa ./docs',
179
+ priority: 60,
180
+ },
181
+ // ── After llms-txt ──────────────────────────────────────────
182
+ {
183
+ id: 'deploy-llms',
184
+ afterCommands: ['llms-txt'],
185
+ category: 'workflow',
186
+ message: 'Deploy docs with llms.txt',
187
+ command: 'skrypt deploy',
188
+ priority: 80,
189
+ },
190
+ ];
@@ -0,0 +1,6 @@
1
+ export { readPreferences, writePreferences, DEFAULT_PREFERENCES } from './state.js';
2
+ export { getSuggestions, printSuggestions } from './suggest.js';
3
+ export { runFirstTimeSetup } from './setup.js';
4
+ export { ACTION_DEFINITIONS } from './actions.js';
5
+ export type { NextActionPreferences, ProjectActionState, ActionDefinition, Suggestion } from './types.js';
6
+ export declare function handlePostAction(commandName: string): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { readPreferences, markCommandCompleted } from './state.js';
2
+ import { getSuggestions, printSuggestions } from './suggest.js';
3
+ import { runFirstTimeSetup } from './setup.js';
4
+ export { readPreferences, writePreferences, DEFAULT_PREFERENCES } from './state.js';
5
+ export { getSuggestions, printSuggestions } from './suggest.js';
6
+ export { runFirstTimeSetup } from './setup.js';
7
+ export { ACTION_DEFINITIONS } from './actions.js';
8
+ const SKIP_COMMANDS = new Set(['config', 'mcp', 'whoami', 'version', 'logout']);
9
+ export async function handlePostAction(commandName) {
10
+ try {
11
+ // Only in interactive mode
12
+ if (!process.stdin.isTTY)
13
+ return;
14
+ // Skip meta commands
15
+ if (SKIP_COMMANDS.has(commandName))
16
+ return;
17
+ let prefs = readPreferences();
18
+ // First-time setup
19
+ if (!prefs) {
20
+ try {
21
+ prefs = await runFirstTimeSetup();
22
+ }
23
+ catch {
24
+ // Non-interactive or error — skip silently
25
+ return;
26
+ }
27
+ }
28
+ if (!prefs.enabled)
29
+ return;
30
+ // Track this command
31
+ markCommandCompleted(commandName);
32
+ // Show suggestions
33
+ const suggestions = getSuggestions(commandName, prefs);
34
+ printSuggestions(suggestions);
35
+ }
36
+ catch {
37
+ // Never crash the CLI over suggestions
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ import { NextActionPreferences } from './types.js';
2
+ export declare function runFirstTimeSetup(): Promise<NextActionPreferences>;
@@ -0,0 +1,72 @@
1
+ import * as readline from 'readline';
2
+ import { writePreferences, DEFAULT_PREFERENCES } from './state.js';
3
+ function ask(rl, question) {
4
+ return new Promise((resolve, reject) => {
5
+ rl.question(question, answer => resolve(answer.trim()));
6
+ rl.once('close', () => reject(new Error('EOF')));
7
+ });
8
+ }
9
+ export async function runFirstTimeSetup() {
10
+ console.log('');
11
+ console.log(' \x1b[36m─────────────────────────────────────\x1b[0m');
12
+ console.log('');
13
+ console.log(' Skrypt can suggest next steps after each command.');
14
+ console.log('');
15
+ console.log(' 1) All suggestions (recommended)');
16
+ console.log(' 2) Workflow only (generate \u2192 test \u2192 deploy)');
17
+ console.log(' 3) Customize');
18
+ console.log(' 4) Disable');
19
+ console.log('');
20
+ const rl = readline.createInterface({
21
+ input: process.stdin,
22
+ output: process.stdout,
23
+ });
24
+ try {
25
+ const choice = await ask(rl, ' Choice [1]: ');
26
+ const selected = choice || '1';
27
+ let prefs;
28
+ if (selected === '4') {
29
+ prefs = {
30
+ enabled: false,
31
+ categories: { workflow: false, quality: false, cicd: false, advanced: false },
32
+ };
33
+ }
34
+ else if (selected === '2') {
35
+ prefs = {
36
+ enabled: true,
37
+ categories: { workflow: true, quality: false, cicd: false, advanced: false },
38
+ };
39
+ }
40
+ else if (selected === '3') {
41
+ console.log('');
42
+ const w = await ask(rl, ' Workflow (generate, test, deploy)? [Y/n] ');
43
+ const q = await ask(rl, ' Quality (qa, lint, security)? [Y/n] ');
44
+ const c = await ask(rl, ' CI/CD (GitHub Actions, deploy)? [Y/n] ');
45
+ const a = await ask(rl, ' Advanced (audit, refresh, review)? [Y/n] ');
46
+ prefs = {
47
+ enabled: true,
48
+ categories: {
49
+ workflow: w.toLowerCase() !== 'n',
50
+ quality: q.toLowerCase() !== 'n',
51
+ cicd: c.toLowerCase() !== 'n',
52
+ advanced: a.toLowerCase() !== 'n',
53
+ },
54
+ };
55
+ }
56
+ else {
57
+ prefs = { ...DEFAULT_PREFERENCES };
58
+ }
59
+ writePreferences(prefs);
60
+ console.log('');
61
+ if (prefs.enabled) {
62
+ console.log(' \x1b[32mSaved!\x1b[0m Change anytime with: skrypt config --suggestions');
63
+ }
64
+ else {
65
+ console.log(' Suggestions disabled. Re-enable with: skrypt config --suggestions');
66
+ }
67
+ return prefs;
68
+ }
69
+ finally {
70
+ rl.close();
71
+ }
72
+ }
@@ -0,0 +1,7 @@
1
+ import { NextActionPreferences, ProjectActionState } from './types.js';
2
+ export declare const DEFAULT_PREFERENCES: NextActionPreferences;
3
+ export declare function readPreferences(): NextActionPreferences | null;
4
+ export declare function writePreferences(prefs: NextActionPreferences): void;
5
+ export declare function readProjectState(): ProjectActionState;
6
+ export declare function writeProjectState(state: ProjectActionState): void;
7
+ export declare function markCommandCompleted(commandName: string): void;
@@ -0,0 +1,68 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { createHash } from 'crypto';
5
+ function skryptHome() {
6
+ return join(homedir(), '.skrypt');
7
+ }
8
+ function preferencesFile() {
9
+ return join(skryptHome(), 'preferences.json');
10
+ }
11
+ function projectsDir() {
12
+ return join(skryptHome(), 'projects');
13
+ }
14
+ export const DEFAULT_PREFERENCES = {
15
+ enabled: true,
16
+ categories: {
17
+ workflow: true,
18
+ quality: true,
19
+ cicd: true,
20
+ advanced: true,
21
+ },
22
+ };
23
+ function projectHash() {
24
+ return createHash('sha256').update(process.cwd()).digest('hex').slice(0, 12);
25
+ }
26
+ function projectStatePath() {
27
+ return join(projectsDir(), `${projectHash()}.json`);
28
+ }
29
+ export function readPreferences() {
30
+ try {
31
+ const path = preferencesFile();
32
+ if (!existsSync(path))
33
+ return null;
34
+ return JSON.parse(readFileSync(path, 'utf-8'));
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ export function writePreferences(prefs) {
41
+ const home = skryptHome();
42
+ mkdirSync(home, { recursive: true });
43
+ writeFileSync(preferencesFile(), JSON.stringify(prefs, null, 2), { mode: 0o600 });
44
+ }
45
+ export function readProjectState() {
46
+ try {
47
+ const path = projectStatePath();
48
+ if (!existsSync(path)) {
49
+ return { completedCommands: [], lastUpdated: '' };
50
+ }
51
+ return JSON.parse(readFileSync(path, 'utf-8'));
52
+ }
53
+ catch {
54
+ return { completedCommands: [], lastUpdated: '' };
55
+ }
56
+ }
57
+ export function writeProjectState(state) {
58
+ mkdirSync(projectsDir(), { recursive: true });
59
+ state.lastUpdated = new Date().toISOString();
60
+ writeFileSync(projectStatePath(), JSON.stringify(state, null, 2), { mode: 0o600 });
61
+ }
62
+ export function markCommandCompleted(commandName) {
63
+ const state = readProjectState();
64
+ if (!state.completedCommands.includes(commandName)) {
65
+ state.completedCommands.push(commandName);
66
+ }
67
+ writeProjectState(state);
68
+ }