skrypt-ai 0.3.4 → 0.4.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 (95) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.ts +0 -1
  3. package/dist/auth/index.js +3 -5
  4. package/dist/autofix/index.js +15 -3
  5. package/dist/cli.js +19 -4
  6. package/dist/commands/check-links.js +164 -174
  7. package/dist/commands/deploy.js +5 -2
  8. package/dist/commands/generate.js +206 -199
  9. package/dist/commands/i18n.js +3 -20
  10. package/dist/commands/init.js +47 -40
  11. package/dist/commands/lint.js +3 -20
  12. package/dist/commands/mcp.js +125 -122
  13. package/dist/commands/monitor.js +125 -108
  14. package/dist/commands/review-pr.js +1 -1
  15. package/dist/commands/sdk.js +1 -1
  16. package/dist/config/loader.js +21 -2
  17. package/dist/generator/organizer.d.ts +3 -0
  18. package/dist/generator/organizer.js +4 -9
  19. package/dist/generator/writer.js +2 -10
  20. package/dist/github/pr-comments.js +21 -8
  21. package/dist/plugins/index.js +1 -0
  22. package/dist/scanner/index.js +8 -2
  23. package/dist/template/docs.json +2 -1
  24. package/dist/template/next.config.mjs +2 -1
  25. package/dist/template/package.json +17 -15
  26. package/dist/template/public/favicon.svg +4 -0
  27. package/dist/template/public/search-index.json +1 -1
  28. package/dist/template/scripts/build-search-index.mjs +120 -25
  29. package/dist/template/src/app/api/chat/route.ts +11 -3
  30. package/dist/template/src/app/docs/README.md +28 -0
  31. package/dist/template/src/app/docs/[...slug]/page.tsx +139 -16
  32. package/dist/template/src/app/docs/auth/page.mdx +589 -0
  33. package/dist/template/src/app/docs/autofix/page.mdx +624 -0
  34. package/dist/template/src/app/docs/cli/page.mdx +217 -0
  35. package/dist/template/src/app/docs/config/page.mdx +428 -0
  36. package/dist/template/src/app/docs/configuration/page.mdx +86 -0
  37. package/dist/template/src/app/docs/deployment/page.mdx +112 -0
  38. package/dist/template/src/app/docs/error.tsx +20 -0
  39. package/dist/template/src/app/docs/generator/generator.md +504 -0
  40. package/dist/template/src/app/docs/generator/organizer.md +779 -0
  41. package/dist/template/src/app/docs/generator/page.mdx +613 -0
  42. package/dist/template/src/app/docs/github/page.mdx +502 -0
  43. package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
  44. package/dist/template/src/app/docs/llm/index.md +471 -0
  45. package/dist/template/src/app/docs/llm/page.mdx +428 -0
  46. package/dist/template/src/app/docs/llms-full.md +256 -0
  47. package/dist/template/src/app/docs/llms.txt +2971 -0
  48. package/dist/template/src/app/docs/not-found.tsx +23 -0
  49. package/dist/template/src/app/docs/page.mdx +0 -3
  50. package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
  51. package/dist/template/src/app/docs/pro/page.mdx +121 -0
  52. package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
  53. package/dist/template/src/app/docs/scanner/content-type.md +599 -0
  54. package/dist/template/src/app/docs/scanner/index.md +212 -0
  55. package/dist/template/src/app/docs/scanner/page.mdx +307 -0
  56. package/dist/template/src/app/docs/scanner/python.md +469 -0
  57. package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
  58. package/dist/template/src/app/docs/scanner/rust.md +325 -0
  59. package/dist/template/src/app/docs/scanner/typescript.md +201 -0
  60. package/dist/template/src/app/error.tsx +3 -3
  61. package/dist/template/src/app/icon.tsx +29 -0
  62. package/dist/template/src/app/layout.tsx +42 -0
  63. package/dist/template/src/app/not-found.tsx +35 -0
  64. package/dist/template/src/app/page.tsx +62 -28
  65. package/dist/template/src/components/ai-chat.tsx +26 -21
  66. package/dist/template/src/components/breadcrumbs.tsx +46 -2
  67. package/dist/template/src/components/copy-button.tsx +17 -3
  68. package/dist/template/src/components/docs-layout.tsx +142 -8
  69. package/dist/template/src/components/feedback.tsx +4 -2
  70. package/dist/template/src/components/footer.tsx +42 -0
  71. package/dist/template/src/components/header.tsx +29 -5
  72. package/dist/template/src/components/mdx/accordion.tsx +7 -6
  73. package/dist/template/src/components/mdx/card.tsx +19 -7
  74. package/dist/template/src/components/mdx/code-block.tsx +17 -3
  75. package/dist/template/src/components/mdx/code-group.tsx +65 -18
  76. package/dist/template/src/components/mdx/code-playground.tsx +3 -0
  77. package/dist/template/src/components/mdx/go-playground.tsx +3 -0
  78. package/dist/template/src/components/mdx/highlighted-code.tsx +171 -76
  79. package/dist/template/src/components/mdx/python-playground.tsx +2 -0
  80. package/dist/template/src/components/mdx/tabs.tsx +74 -6
  81. package/dist/template/src/components/page-header.tsx +19 -0
  82. package/dist/template/src/components/scroll-to-top.tsx +33 -0
  83. package/dist/template/src/components/search-dialog.tsx +206 -52
  84. package/dist/template/src/components/sidebar.tsx +136 -77
  85. package/dist/template/src/components/table-of-contents.tsx +23 -7
  86. package/dist/template/src/lib/highlight.ts +90 -31
  87. package/dist/template/src/lib/search.ts +14 -4
  88. package/dist/template/src/lib/theme-utils.ts +140 -0
  89. package/dist/template/src/styles/globals.css +307 -166
  90. package/dist/template/src/types/remark-gfm.d.ts +2 -0
  91. package/dist/utils/files.d.ts +9 -0
  92. package/dist/utils/files.js +33 -0
  93. package/dist/utils/validation.d.ts +4 -0
  94. package/dist/utils/validation.js +38 -0
  95. package/package.json +1 -4
package/README.md CHANGED
@@ -16,7 +16,7 @@ Scans your codebase, extracts API signatures, and generates beautiful documentat
16
16
  ## Installation
17
17
 
18
18
  ```bash
19
- npm install -g skrypt
19
+ npm install -g skrypt-ai
20
20
  ```
21
21
 
22
22
  ## Quick Start
@@ -17,5 +17,4 @@ export declare function clearAuth(): void;
17
17
  export declare function checkPlan(apiKey: string): Promise<PlanCheckResponse>;
18
18
  export declare function requirePro(commandName: string): Promise<boolean>;
19
19
  export declare const PRO_COMMANDS: string[];
20
- export declare function isProCommand(command: string): boolean;
21
20
  export {};
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  const CONFIG_DIR = join(homedir(), '.skrypt');
@@ -16,8 +16,9 @@ export function getAuthConfig() {
16
16
  }
17
17
  }
18
18
  export function saveAuthConfig(config) {
19
- mkdirSync(CONFIG_DIR, { recursive: true });
19
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
20
20
  writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2));
21
+ chmodSync(AUTH_FILE, 0o600);
21
22
  }
22
23
  export function clearAuth() {
23
24
  if (existsSync(AUTH_FILE)) {
@@ -89,6 +90,3 @@ export const PRO_COMMANDS = [
89
90
  'ci',
90
91
  'mcp'
91
92
  ];
92
- export function isProCommand(command) {
93
- return PRO_COMMANDS.includes(command);
94
- }
@@ -5,13 +5,25 @@
5
5
  * - Free tier: 3 fix attempts per example
6
6
  * - Pro tier: 10 fix attempts per example
7
7
  */
8
+ import ts from 'typescript';
8
9
  /**
9
- * Default TypeScript/JavaScript validator using eval
10
+ * Default TypeScript/JavaScript validator using TypeScript compiler
10
11
  */
11
12
  async function defaultValidator(code) {
12
13
  try {
13
- // Basic syntax check - try to parse as function
14
- new Function(code);
14
+ const result = ts.transpileModule(code, {
15
+ compilerOptions: {
16
+ module: ts.ModuleKind.ESNext,
17
+ target: ts.ScriptTarget.ES2020,
18
+ },
19
+ reportDiagnostics: true,
20
+ });
21
+ if (result.diagnostics && result.diagnostics.length > 0) {
22
+ return {
23
+ valid: false,
24
+ errors: result.diagnostics.map(d => typeof d.messageText === 'string' ? d.messageText : d.messageText.messageText),
25
+ };
26
+ }
15
27
  return { valid: true, errors: [] };
16
28
  }
17
29
  catch (err) {
package/dist/cli.js CHANGED
@@ -18,7 +18,21 @@ import { loginCommand, logoutCommand, whoamiCommand } from './commands/login.js'
18
18
  import { cronCommand } from './commands/cron.js';
19
19
  import { deployCommand } from './commands/deploy.js';
20
20
  import { testCommand } from './commands/test.js';
21
- const VERSION = '0.3.4';
21
+ import { createRequire } from 'module';
22
+ process.on('uncaughtException', (err) => {
23
+ console.error('\x1b[31mFatal error:\x1b[0m', err.message);
24
+ if (process.env.DEBUG)
25
+ console.error(err.stack);
26
+ process.exit(1);
27
+ });
28
+ process.on('unhandledRejection', (reason) => {
29
+ console.error('\x1b[31mUnhandled promise rejection:\x1b[0m', reason);
30
+ if (process.env.DEBUG)
31
+ console.error(reason);
32
+ process.exit(1);
33
+ });
34
+ const require = createRequire(import.meta.url);
35
+ const { version: VERSION } = require('../package.json');
22
36
  async function checkForUpdates() {
23
37
  try {
24
38
  const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
@@ -26,15 +40,16 @@ async function checkForUpdates() {
26
40
  });
27
41
  if (res.ok) {
28
42
  const data = await res.json();
29
- const latest = data.version;
30
- if (latest && latest !== VERSION) {
43
+ const version = data.version;
44
+ if (typeof version === 'string' && /^\d+\.\d+\.\d+/.test(version) && version !== VERSION) {
45
+ const latest = version;
31
46
  console.log(`\n Update available: ${VERSION} → ${latest}`);
32
47
  console.log(` Run: npm install -g skrypt-ai@latest\n`);
33
48
  }
34
49
  }
35
50
  }
36
51
  catch {
37
- // Silently fail - don't block CLI
52
+ // Silently ignore — non-blocking update check
38
53
  }
39
54
  }
40
55
  const program = new Command();
@@ -1,24 +1,7 @@
1
1
  import { Command } from 'commander';
2
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
3
- import { resolve, join, extname, dirname, relative } from 'path';
4
- function findMdxFiles(dir) {
5
- const files = [];
6
- function walk(currentDir) {
7
- const entries = readdirSync(currentDir);
8
- for (const entry of entries) {
9
- const fullPath = join(currentDir, entry);
10
- const stat = statSync(fullPath);
11
- if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
12
- walk(fullPath);
13
- }
14
- else if (stat.isFile() && (extname(entry) === '.mdx' || extname(entry) === '.md')) {
15
- files.push(fullPath);
16
- }
17
- }
18
- }
19
- walk(dir);
20
- return files;
21
- }
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { resolve, dirname, relative } from 'path';
4
+ import { findMdxFiles } from '../utils/files.js';
22
5
  function extractLinks(content) {
23
6
  const links = [];
24
7
  const lines = content.split('\n');
@@ -109,178 +92,185 @@ export const checkLinksCommand = new Command('check-links')
109
92
  .option('--external', 'Also check external links (slower)')
110
93
  .option('--timeout <ms>', 'Timeout for external links', '5000')
111
94
  .action(async (path, options) => {
112
- const targetPath = resolve(path);
113
- if (!existsSync(targetPath)) {
114
- console.error(`Error: Path not found: ${targetPath}`);
115
- process.exit(1);
116
- }
117
- console.log('skrypt check-links');
118
- console.log(` path: ${targetPath}`);
119
- console.log(` external: ${options.external ? 'yes' : 'no'}`);
120
- console.log('');
121
- // Find all MDX files
122
- const files = findMdxFiles(targetPath);
123
- if (files.length === 0) {
124
- console.log('No .md or .mdx files found.');
125
- process.exit(0);
126
- }
127
- console.log(`Checking ${files.length} files...\n`);
128
- // Build a map of all files and their anchors
129
- const fileMap = new Map();
130
- const allAnchors = new Map();
131
- for (const file of files) {
132
- const content = readFileSync(file, 'utf-8');
133
- const anchors = extractAnchors(content);
134
- allAnchors.set(file, anchors);
135
- // Map various path formats to this file
136
- const relPath = relative(targetPath, file);
137
- const withoutExt = relPath.replace(/\.(mdx?|md)$/, '');
138
- fileMap.set(relPath, anchors);
139
- fileMap.set(withoutExt, anchors);
140
- fileMap.set('/' + relPath, anchors);
141
- fileMap.set('/' + withoutExt, anchors);
142
- }
143
- const brokenLinks = [];
144
- const externalToCheck = [];
145
- // Check all links in all files
146
- for (const file of files) {
147
- const content = readFileSync(file, 'utf-8');
148
- const links = extractLinks(content);
149
- const fileDir = dirname(file);
150
- const relFile = relative(targetPath, file);
151
- for (const { link, line } of links) {
152
- // Skip empty links
153
- if (!link || link.trim() === '')
154
- continue;
155
- // External link
156
- if (link.startsWith('http://') || link.startsWith('https://')) {
157
- if (options.external) {
158
- externalToCheck.push({ file: relFile, line, url: link });
95
+ try {
96
+ const targetPath = resolve(path);
97
+ if (!existsSync(targetPath)) {
98
+ console.error(`Error: Path not found: ${targetPath}`);
99
+ process.exit(1);
100
+ }
101
+ console.log('skrypt check-links');
102
+ console.log(` path: ${targetPath}`);
103
+ console.log(` external: ${options.external ? 'yes' : 'no'}`);
104
+ console.log('');
105
+ // Find all MDX files
106
+ const files = findMdxFiles(targetPath);
107
+ if (files.length === 0) {
108
+ console.log('No .md or .mdx files found.');
109
+ process.exit(0);
110
+ }
111
+ console.log(`Checking ${files.length} files...\n`);
112
+ // Build a map of all files and their anchors
113
+ const fileMap = new Map();
114
+ const allAnchors = new Map();
115
+ for (const file of files) {
116
+ const content = readFileSync(file, 'utf-8');
117
+ const anchors = extractAnchors(content);
118
+ allAnchors.set(file, anchors);
119
+ // Map various path formats to this file
120
+ const relPath = relative(targetPath, file);
121
+ const withoutExt = relPath.replace(/\.mdx?$/, '');
122
+ fileMap.set(relPath, anchors);
123
+ fileMap.set(withoutExt, anchors);
124
+ fileMap.set('/' + relPath, anchors);
125
+ fileMap.set('/' + withoutExt, anchors);
126
+ }
127
+ const brokenLinks = [];
128
+ const externalToCheck = [];
129
+ // Check all links in all files
130
+ for (const file of files) {
131
+ const content = readFileSync(file, 'utf-8');
132
+ const links = extractLinks(content);
133
+ const fileDir = dirname(file);
134
+ const relFile = relative(targetPath, file);
135
+ for (const { link, line } of links) {
136
+ // Skip empty links
137
+ if (!link || link.trim() === '')
138
+ continue;
139
+ // External link
140
+ if (link.startsWith('http://') || link.startsWith('https://')) {
141
+ if (options.external) {
142
+ externalToCheck.push({ file: relFile, line, url: link });
143
+ }
144
+ continue;
159
145
  }
160
- continue;
161
- }
162
- // Mailto, tel, etc.
163
- if (link.startsWith('mailto:') || link.startsWith('tel:') || link.startsWith('javascript:')) {
164
- continue;
165
- }
166
- // Anchor-only link
167
- if (link.startsWith('#')) {
168
- const anchor = link.slice(1);
169
- const fileAnchors = allAnchors.get(file) || new Set();
170
- if (!fileAnchors.has(anchor)) {
146
+ // Mailto, tel, etc.
147
+ if (link.startsWith('mailto:') || link.startsWith('tel:') || link.startsWith('javascript:')) {
148
+ continue;
149
+ }
150
+ // Anchor-only link
151
+ if (link.startsWith('#')) {
152
+ const anchor = link.slice(1);
153
+ const fileAnchors = allAnchors.get(file) || new Set();
154
+ if (!fileAnchors.has(anchor)) {
155
+ brokenLinks.push({
156
+ file: relFile,
157
+ line,
158
+ link,
159
+ type: 'anchor',
160
+ reason: 'Anchor not found in file',
161
+ });
162
+ }
163
+ continue;
164
+ }
165
+ // Internal link
166
+ let targetFile = link;
167
+ let targetAnchor = '';
168
+ // Split path and anchor
169
+ const hashIndex = link.indexOf('#');
170
+ if (hashIndex !== -1) {
171
+ targetFile = link.slice(0, hashIndex);
172
+ targetAnchor = link.slice(hashIndex + 1);
173
+ }
174
+ // Remove query string
175
+ const queryIndex = targetFile.indexOf('?');
176
+ if (queryIndex !== -1) {
177
+ targetFile = targetFile.slice(0, queryIndex);
178
+ }
179
+ // Resolve relative path
180
+ let resolvedPath;
181
+ if (targetFile.startsWith('/')) {
182
+ resolvedPath = targetFile.slice(1);
183
+ }
184
+ else {
185
+ resolvedPath = relative(targetPath, resolve(fileDir, targetFile));
186
+ }
187
+ // Check if file exists
188
+ const foundAnchors = fileMap.get(resolvedPath) ||
189
+ fileMap.get(resolvedPath + '.mdx') ||
190
+ fileMap.get(resolvedPath + '.md') ||
191
+ fileMap.get(resolvedPath + '/index') ||
192
+ fileMap.get(resolvedPath + '/index.mdx') ||
193
+ fileMap.get(resolvedPath + '/index.md');
194
+ if (!foundAnchors && !existsSync(resolve(targetPath, resolvedPath))) {
195
+ // Check for image/asset files
196
+ const assetPath = resolve(targetPath, resolvedPath);
197
+ if (!existsSync(assetPath)) {
198
+ brokenLinks.push({
199
+ file: relFile,
200
+ line,
201
+ link,
202
+ type: 'internal',
203
+ reason: 'File not found',
204
+ });
205
+ }
206
+ continue;
207
+ }
208
+ // Check anchor if specified
209
+ if (targetAnchor && foundAnchors && !foundAnchors.has(targetAnchor)) {
171
210
  brokenLinks.push({
172
211
  file: relFile,
173
212
  line,
174
213
  link,
175
214
  type: 'anchor',
176
- reason: 'Anchor not found in file',
215
+ reason: `Anchor #${targetAnchor} not found in target`,
177
216
  });
178
217
  }
179
- continue;
180
- }
181
- // Internal link
182
- let targetFile = link;
183
- let targetAnchor = '';
184
- // Split path and anchor
185
- const hashIndex = link.indexOf('#');
186
- if (hashIndex !== -1) {
187
- targetFile = link.slice(0, hashIndex);
188
- targetAnchor = link.slice(hashIndex + 1);
189
- }
190
- // Remove query string
191
- const queryIndex = targetFile.indexOf('?');
192
- if (queryIndex !== -1) {
193
- targetFile = targetFile.slice(0, queryIndex);
194
218
  }
195
- // Resolve relative path
196
- let resolvedPath;
197
- if (targetFile.startsWith('/')) {
198
- resolvedPath = targetFile.slice(1);
199
- }
200
- else {
201
- resolvedPath = relative(targetPath, resolve(fileDir, targetFile));
202
- }
203
- // Check if file exists
204
- const foundAnchors = fileMap.get(resolvedPath) ||
205
- fileMap.get(resolvedPath + '.mdx') ||
206
- fileMap.get(resolvedPath + '.md') ||
207
- fileMap.get(resolvedPath + '/index') ||
208
- fileMap.get(resolvedPath + '/index.mdx') ||
209
- fileMap.get(resolvedPath + '/index.md');
210
- if (!foundAnchors && !existsSync(resolve(targetPath, resolvedPath))) {
211
- // Check for image/asset files
212
- const assetPath = resolve(targetPath, resolvedPath);
213
- if (!existsSync(assetPath)) {
219
+ }
220
+ // Check external links if requested
221
+ if (options.external && externalToCheck.length > 0) {
222
+ console.log(`Checking ${externalToCheck.length} external links...\n`);
223
+ const timeout = Number(options.timeout) || 5000;
224
+ for (let i = 0; i < externalToCheck.length; i++) {
225
+ const { file, line, url } = externalToCheck[i];
226
+ process.stdout.write(`\r [${i + 1}/${externalToCheck.length}] ${url.slice(0, 50)}...`.padEnd(80));
227
+ const result = await checkExternalLink(url, timeout);
228
+ if (!result.ok) {
214
229
  brokenLinks.push({
215
- file: relFile,
230
+ file,
216
231
  line,
217
- link,
218
- type: 'internal',
219
- reason: 'File not found',
232
+ link: url,
233
+ type: 'external',
234
+ reason: result.reason,
220
235
  });
221
236
  }
222
- continue;
223
- }
224
- // Check anchor if specified
225
- if (targetAnchor && foundAnchors && !foundAnchors.has(targetAnchor)) {
226
- brokenLinks.push({
227
- file: relFile,
228
- line,
229
- link,
230
- type: 'anchor',
231
- reason: `Anchor #${targetAnchor} not found in target`,
232
- });
233
237
  }
238
+ console.log('\n');
234
239
  }
235
- }
236
- // Check external links if requested
237
- if (options.external && externalToCheck.length > 0) {
238
- console.log(`Checking ${externalToCheck.length} external links...\n`);
239
- const timeout = parseInt(options.timeout) || 5000;
240
- for (let i = 0; i < externalToCheck.length; i++) {
241
- const { file, line, url } = externalToCheck[i];
242
- process.stdout.write(`\r [${i + 1}/${externalToCheck.length}] ${url.slice(0, 50)}...`.padEnd(80));
243
- const result = await checkExternalLink(url, timeout);
244
- if (!result.ok) {
245
- brokenLinks.push({
246
- file,
247
- line,
248
- link: url,
249
- type: 'external',
250
- reason: result.reason,
251
- });
240
+ // Print results
241
+ if (brokenLinks.length === 0) {
242
+ console.log('✓ No broken links found!');
243
+ process.exit(0);
244
+ }
245
+ console.log(`Found ${brokenLinks.length} broken links:\n`);
246
+ // Group by file
247
+ const byFile = new Map();
248
+ for (const link of brokenLinks) {
249
+ if (!byFile.has(link.file)) {
250
+ byFile.set(link.file, []);
252
251
  }
252
+ byFile.get(link.file).push(link);
253
253
  }
254
- console.log('\n');
255
- }
256
- // Print results
257
- if (brokenLinks.length === 0) {
258
- console.log('✓ No broken links found!');
259
- process.exit(0);
260
- }
261
- console.log(`Found ${brokenLinks.length} broken links:\n`);
262
- // Group by file
263
- const byFile = new Map();
264
- for (const link of brokenLinks) {
265
- if (!byFile.has(link.file)) {
266
- byFile.set(link.file, []);
254
+ for (const [file, links] of byFile) {
255
+ console.log(`\n${file}`);
256
+ for (const link of links) {
257
+ const icon = link.type === 'external' ? '🌐' : link.type === 'anchor' ? '⚓' : '📄';
258
+ console.log(` ${icon} Line ${link.line}: ${link.link}`);
259
+ console.log(` └─ ${link.reason}`);
260
+ }
267
261
  }
268
- byFile.get(link.file).push(link);
262
+ // Summary
263
+ console.log('\n=== Summary ===');
264
+ console.log(` Files checked: ${files.length}`);
265
+ console.log(` Broken links: ${brokenLinks.length}`);
266
+ console.log(` Internal: ${brokenLinks.filter(l => l.type === 'internal').length}`);
267
+ console.log(` Anchors: ${brokenLinks.filter(l => l.type === 'anchor').length}`);
268
+ console.log(` External: ${brokenLinks.filter(l => l.type === 'external').length}`);
269
+ process.exit(1);
269
270
  }
270
- for (const [file, links] of byFile) {
271
- console.log(`\n${file}`);
272
- for (const link of links) {
273
- const icon = link.type === 'external' ? '🌐' : link.type === 'anchor' ? '⚓' : '📄';
274
- console.log(` ${icon} Line ${link.line}: ${link.link}`);
275
- console.log(` └─ ${link.reason}`);
276
- }
271
+ catch (err) {
272
+ const message = err instanceof Error ? err.message : String(err);
273
+ console.error(`Error: ${message}`);
274
+ process.exit(1);
277
275
  }
278
- // Summary
279
- console.log('\n=== Summary ===');
280
- console.log(` Files checked: ${files.length}`);
281
- console.log(` Broken links: ${brokenLinks.length}`);
282
- console.log(` Internal: ${brokenLinks.filter(l => l.type === 'internal').length}`);
283
- console.log(` Anchors: ${brokenLinks.filter(l => l.type === 'anchor').length}`);
284
- console.log(` External: ${brokenLinks.filter(l => l.type === 'external').length}`);
285
- process.exit(1);
286
276
  });
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { execSync, spawn } from 'child_process';
2
+ import { spawnSync, spawn } from 'child_process';
3
3
  import { existsSync, readFileSync, statSync } from 'fs';
4
4
  import { resolve, join } from 'path';
5
5
  import { homedir } from 'os';
@@ -113,9 +113,12 @@ async function bundleOutput(outDir) {
113
113
  const bundlePath = join(outDir, '..', 'deploy-bundle.tar.gz');
114
114
  console.log(' Creating deployment bundle...');
115
115
  // Use tar to create a gzipped tarball
116
- execSync(`tar -czf "${bundlePath}" -C "${outDir}" .`, {
116
+ const tarResult = spawnSync('tar', ['-czf', bundlePath, '-C', outDir, '.'], {
117
117
  stdio: 'pipe'
118
118
  });
119
+ if (tarResult.status !== 0) {
120
+ throw new Error(`tar failed: ${tarResult.stderr?.toString() || 'unknown error'}`);
121
+ }
119
122
  const stats = statSync(bundlePath);
120
123
  const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
121
124
  console.log(` Bundle size: ${sizeMB} MB`);