@laitszkin/apollo-toolkit 4.1.4 → 5.0.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 (205) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/apollo-toolkit.ts +4 -0
  3. package/dist/bin/apollo-toolkit.js +4 -0
  4. package/package.json +4 -2
  5. package/packages/cli/dist/help-text-builder.d.ts +23 -0
  6. package/packages/cli/dist/help-text-builder.js +166 -0
  7. package/packages/cli/dist/index.d.ts +6 -17
  8. package/packages/cli/dist/index.js +52 -246
  9. package/packages/cli/dist/installer.d.ts +1 -0
  10. package/packages/cli/dist/installer.js +20 -7
  11. package/packages/cli/dist/parsers/install-parser.d.ts +15 -0
  12. package/packages/cli/dist/parsers/install-parser.js +87 -0
  13. package/packages/cli/dist/parsers/parser-utils.d.ts +9 -0
  14. package/packages/cli/dist/parsers/parser-utils.js +16 -0
  15. package/packages/cli/dist/parsers/tool-parser.d.ts +16 -0
  16. package/packages/cli/dist/parsers/tool-parser.js +58 -0
  17. package/packages/cli/dist/parsers/types.d.ts +50 -0
  18. package/packages/cli/dist/parsers/types.js +1 -0
  19. package/packages/cli/dist/parsers/uninstall-parser.d.ts +15 -0
  20. package/packages/cli/dist/parsers/uninstall-parser.js +67 -0
  21. package/packages/cli/dist/tool-registration.d.ts +2 -0
  22. package/packages/cli/dist/tool-registration.js +2 -0
  23. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  24. package/packages/cli/dist/types.d.ts +3 -1
  25. package/packages/cli/dist/updater.js +11 -5
  26. package/packages/cli/help-text-builder.ts +180 -0
  27. package/packages/cli/index.ts +59 -251
  28. package/packages/cli/installer.ts +19 -7
  29. package/packages/cli/package.json +6 -3
  30. package/packages/cli/parsers/install-parser.ts +94 -0
  31. package/packages/cli/parsers/parser-utils.ts +17 -0
  32. package/packages/cli/parsers/tool-parser.ts +65 -0
  33. package/packages/cli/parsers/types.ts +56 -0
  34. package/packages/cli/parsers/uninstall-parser.ts +75 -0
  35. package/packages/cli/tool-registration.ts +3 -0
  36. package/packages/cli/types.ts +6 -1
  37. package/packages/cli/updater.ts +11 -5
  38. package/packages/tool-registry/dist/registry.js +3 -4
  39. package/packages/tool-registry/dist/tsconfig.tsbuildinfo +1 -1
  40. package/packages/tool-registry/dist/types.d.ts +2 -9
  41. package/packages/tool-registry/package.json +3 -3
  42. package/packages/tool-registry/registry.ts +3 -4
  43. package/packages/tool-registry/tsconfig.json +6 -2
  44. package/packages/tool-registry/types.ts +3 -9
  45. package/packages/tool-utils/app-error.ts +97 -0
  46. package/packages/tool-utils/dist/app-error.d.ts +49 -0
  47. package/packages/tool-utils/dist/app-error.js +80 -0
  48. package/packages/tool-utils/dist/index.d.ts +5 -0
  49. package/packages/tool-utils/dist/index.js +3 -0
  50. package/packages/tool-utils/dist/platform-adapter.d.ts +48 -0
  51. package/packages/tool-utils/dist/platform-adapter.js +73 -0
  52. package/packages/tool-utils/dist/schema.d.ts +68 -0
  53. package/packages/tool-utils/dist/schema.js +67 -0
  54. package/packages/tool-utils/dist/tsconfig.tsbuildinfo +1 -1
  55. package/packages/tool-utils/index.ts +12 -0
  56. package/packages/tool-utils/package.json +3 -3
  57. package/packages/tool-utils/platform-adapter.ts +112 -0
  58. package/packages/tool-utils/schema.ts +122 -0
  59. package/packages/tools/architecture/dist/index.d.ts +13 -0
  60. package/packages/tools/architecture/dist/index.js +55 -57
  61. package/packages/tools/architecture/dist/index.test.js +17 -4
  62. package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
  63. package/packages/tools/architecture/index.test.ts +27 -14
  64. package/packages/tools/architecture/index.ts +85 -88
  65. package/packages/tools/architecture/package.json +3 -3
  66. package/packages/tools/codegraph/dist/index.js +12 -22
  67. package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
  68. package/packages/tools/codegraph/index.ts +13 -22
  69. package/packages/tools/codegraph/package.json +3 -3
  70. package/packages/tools/create-review-report/dist/index.d.ts +1 -2
  71. package/packages/tools/create-review-report/dist/index.js +46 -77
  72. package/packages/tools/create-review-report/dist/tsconfig.tsbuildinfo +1 -1
  73. package/packages/tools/create-review-report/index.ts +52 -81
  74. package/packages/tools/create-review-report/package.json +3 -3
  75. package/packages/tools/create-specs/dist/index.d.ts +1 -2
  76. package/packages/tools/create-specs/dist/index.js +70 -123
  77. package/packages/tools/create-specs/dist/tsconfig.tsbuildinfo +1 -1
  78. package/packages/tools/create-specs/index.ts +82 -128
  79. package/packages/tools/create-specs/package.json +3 -3
  80. package/packages/tools/docs-to-voice/dist/index.d.ts +1 -2
  81. package/packages/tools/docs-to-voice/dist/index.js +116 -219
  82. package/packages/tools/docs-to-voice/dist/tsconfig.tsbuildinfo +1 -1
  83. package/packages/tools/docs-to-voice/index.ts +265 -385
  84. package/packages/tools/docs-to-voice/package.json +3 -3
  85. package/packages/tools/enforce-video-aspect-ratio/dist/index.d.ts +1 -2
  86. package/packages/tools/enforce-video-aspect-ratio/dist/index.js +77 -154
  87. package/packages/tools/enforce-video-aspect-ratio/dist/tsconfig.tsbuildinfo +1 -1
  88. package/packages/tools/enforce-video-aspect-ratio/index.ts +87 -172
  89. package/packages/tools/enforce-video-aspect-ratio/package.json +3 -3
  90. package/packages/tools/eval/dist/index.js +7 -0
  91. package/packages/tools/eval/dist/tsconfig.tsbuildinfo +1 -1
  92. package/packages/tools/eval/index.ts +8 -0
  93. package/packages/tools/eval/package.json +3 -3
  94. package/packages/tools/extract-conversations/dist/index.d.ts +1 -2
  95. package/packages/tools/extract-conversations/dist/index.js +31 -29
  96. package/packages/tools/extract-conversations/dist/tsconfig.tsbuildinfo +1 -1
  97. package/packages/tools/extract-conversations/index.ts +37 -30
  98. package/packages/tools/extract-conversations/package.json +3 -3
  99. package/packages/tools/extract-pdf-text/dist/index.d.ts +1 -2
  100. package/packages/tools/extract-pdf-text/dist/index.js +44 -65
  101. package/packages/tools/extract-pdf-text/dist/tsconfig.tsbuildinfo +1 -1
  102. package/packages/tools/extract-pdf-text/index.ts +55 -74
  103. package/packages/tools/extract-pdf-text/package.json +3 -3
  104. package/packages/tools/filter-logs/dist/index.js +60 -84
  105. package/packages/tools/filter-logs/dist/tsconfig.tsbuildinfo +1 -1
  106. package/packages/tools/filter-logs/index.ts +67 -97
  107. package/packages/tools/filter-logs/package.json +3 -3
  108. package/packages/tools/find-github-issues/dist/index.d.ts +10 -0
  109. package/packages/tools/find-github-issues/dist/index.js +34 -5
  110. package/packages/tools/find-github-issues/dist/tsconfig.tsbuildinfo +1 -1
  111. package/packages/tools/find-github-issues/index.ts +37 -5
  112. package/packages/tools/find-github-issues/package.json +3 -3
  113. package/packages/tools/generate-storyboard-images/dist/index.d.ts +1 -2
  114. package/packages/tools/generate-storyboard-images/dist/index.js +98 -173
  115. package/packages/tools/generate-storyboard-images/dist/tsconfig.tsbuildinfo +1 -1
  116. package/packages/tools/generate-storyboard-images/index.ts +100 -188
  117. package/packages/tools/generate-storyboard-images/package.json +3 -3
  118. package/packages/tools/open-github-issue/dist/index.d.ts +13 -0
  119. package/packages/tools/open-github-issue/dist/index.js +67 -68
  120. package/packages/tools/open-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  121. package/packages/tools/open-github-issue/index.ts +71 -72
  122. package/packages/tools/open-github-issue/package.json +3 -3
  123. package/packages/tools/read-github-issue/dist/index.d.ts +16 -1
  124. package/packages/tools/read-github-issue/dist/index.js +32 -40
  125. package/packages/tools/read-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  126. package/packages/tools/read-github-issue/index.ts +32 -45
  127. package/packages/tools/read-github-issue/package.json +3 -3
  128. package/packages/tools/render-error-book/dist/index.d.ts +1 -2
  129. package/packages/tools/render-error-book/dist/index.js +74 -95
  130. package/packages/tools/render-error-book/dist/tsconfig.tsbuildinfo +1 -1
  131. package/packages/tools/render-error-book/index.ts +88 -103
  132. package/packages/tools/render-error-book/package.json +3 -3
  133. package/packages/tools/render-katex/dist/index.d.ts +1 -2
  134. package/packages/tools/render-katex/dist/index.js +70 -157
  135. package/packages/tools/render-katex/dist/tsconfig.tsbuildinfo +1 -1
  136. package/packages/tools/render-katex/index.ts +138 -222
  137. package/packages/tools/render-katex/package.json +3 -3
  138. package/packages/tools/review-threads/dist/index.d.ts +12 -0
  139. package/packages/tools/review-threads/dist/index.js +83 -86
  140. package/packages/tools/review-threads/dist/tsconfig.tsbuildinfo +1 -1
  141. package/packages/tools/review-threads/index.ts +90 -84
  142. package/packages/tools/review-threads/package.json +3 -3
  143. package/packages/tools/search-logs/dist/index.js +100 -136
  144. package/packages/tools/search-logs/dist/tsconfig.tsbuildinfo +1 -1
  145. package/packages/tools/search-logs/index.ts +113 -145
  146. package/packages/tools/search-logs/package.json +3 -3
  147. package/packages/tools/sync-memory-index/dist/index.js +34 -28
  148. package/packages/tools/sync-memory-index/dist/tsconfig.tsbuildinfo +1 -1
  149. package/packages/tools/sync-memory-index/index.ts +37 -28
  150. package/packages/tools/sync-memory-index/package.json +3 -3
  151. package/packages/tools/validate-openai-agent-config/dist/index.js +13 -7
  152. package/packages/tools/validate-openai-agent-config/dist/tsconfig.tsbuildinfo +1 -1
  153. package/packages/tools/validate-openai-agent-config/index.ts +13 -7
  154. package/packages/tools/validate-openai-agent-config/package.json +3 -3
  155. package/packages/tools/validate-skill-frontmatter/dist/index.js +12 -6
  156. package/packages/tools/validate-skill-frontmatter/dist/tsconfig.tsbuildinfo +1 -1
  157. package/packages/tools/validate-skill-frontmatter/index.ts +12 -6
  158. package/packages/tools/validate-skill-frontmatter/package.json +3 -3
  159. package/packages/tui/dist/index.d.ts +2 -1
  160. package/packages/tui/dist/index.js +1 -0
  161. package/packages/tui/dist/stdio-adapter.d.ts +36 -0
  162. package/packages/tui/dist/stdio-adapter.js +69 -0
  163. package/packages/tui/dist/terminal.js +3 -1
  164. package/packages/tui/dist/tsconfig.tsbuildinfo +1 -1
  165. package/packages/tui/dist/types.d.ts +17 -0
  166. package/packages/tui/index.ts +2 -1
  167. package/packages/tui/package.json +6 -5
  168. package/packages/tui/stdio-adapter.ts +85 -0
  169. package/packages/tui/terminal.ts +3 -1
  170. package/packages/tui/tsconfig.json +5 -2
  171. package/packages/tui/types.ts +19 -0
  172. package/resources/project-architecture/assets/architecture.css +2 -1
  173. package/resources/project-architecture/atlas/atlas.history.log +1 -0
  174. package/resources/project-architecture/atlas/atlas.history.undo.json +13 -2
  175. package/resources/project-architecture/atlas/atlas.history.undo.stack.json +610 -0
  176. package/resources/project-architecture/atlas/atlas.index.yaml +81 -5
  177. package/resources/project-architecture/atlas/features/cli-dispatch.yaml +43 -0
  178. package/resources/project-architecture/atlas/features/terminal-ui.yaml +29 -0
  179. package/resources/project-architecture/atlas/features/tool-registry.yaml +22 -0
  180. package/resources/project-architecture/atlas/features/tool-utils.yaml +22 -0
  181. package/resources/project-architecture/features/cli-dispatch/arg-parser.html +40 -0
  182. package/resources/project-architecture/features/cli-dispatch/help-builder.html +40 -0
  183. package/resources/project-architecture/features/cli-dispatch/index.html +64 -0
  184. package/resources/project-architecture/features/cli-dispatch/installer-core.html +40 -0
  185. package/resources/project-architecture/features/cli-dispatch/tool-discovery.html +40 -0
  186. package/resources/project-architecture/features/cli-dispatch/update-checker.html +40 -0
  187. package/resources/project-architecture/features/terminal-ui/banner-display.html +40 -0
  188. package/resources/project-architecture/features/terminal-ui/index.html +50 -0
  189. package/resources/project-architecture/features/terminal-ui/interactive-prompts.html +40 -0
  190. package/resources/project-architecture/features/terminal-ui/terminal-detection.html +40 -0
  191. package/resources/project-architecture/features/tool-registry/formatter.html +40 -0
  192. package/resources/project-architecture/features/tool-registry/index.html +43 -0
  193. package/resources/project-architecture/features/tool-registry/registry-core.html +40 -0
  194. package/resources/project-architecture/features/tool-utils/index.html +43 -0
  195. package/resources/project-architecture/features/tool-utils/log-utils.html +40 -0
  196. package/resources/project-architecture/features/tool-utils/skill-discovery.html +40 -0
  197. package/resources/project-architecture/index.html +365 -121
  198. package/scripts/rewrite-imports.mjs +2 -2
  199. package/scripts/test.sh +144 -8
  200. package/skills/design/SKILL.md +57 -64
  201. package/skills/design/assets/templates/DESIGN.md +12 -0
  202. package/skills/design/references/code-smells.md +94 -0
  203. package/skills/design/references/module-boundary-adjustment.md +126 -0
  204. package/skills/design/references/module-internal-restructuring.md +132 -0
  205. package/skills/design/references/module-internal-simplification.md +164 -0
@@ -2,112 +2,23 @@ import { execSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
5
-
6
- interface AspectArgs {
7
- inputVideo: string | null;
8
- outputVideo: string | null;
9
- inPlace: boolean;
10
- targetSize: string | null;
11
- targetWidth: number | null;
12
- targetHeight: number | null;
13
- aspect: string | null;
14
- force: boolean;
15
- ffmpegBin: string;
16
- ffprobeBin: string;
17
- help: boolean;
18
- }
19
-
20
- function parseArgs(args: string[]): AspectArgs {
21
- const parsed: AspectArgs = {
22
- inputVideo: null,
23
- outputVideo: null,
24
- inPlace: false,
25
- targetSize: null,
26
- targetWidth: null,
27
- targetHeight: null,
28
- aspect: null,
29
- force: false,
30
- ffmpegBin: 'ffmpeg',
31
- ffprobeBin: 'ffprobe',
32
- help: false,
33
- };
34
-
35
- for (let i = 0; i < args.length; i++) {
36
- const arg = args[i];
37
- if (arg === '--help' || arg === '-h') {
38
- parsed.help = true;
39
- continue;
40
- }
41
- if (arg.startsWith('--')) {
42
- const eqIndex = arg.indexOf('=');
43
- let key: string;
44
- let value: string;
45
-
46
- if (eqIndex !== -1) {
47
- key = arg.slice(2, eqIndex);
48
- value = arg.slice(eqIndex + 1);
49
- } else {
50
- key = arg.slice(2);
51
- value = args[++i] || '';
52
- }
53
-
54
- switch (key) {
55
- case 'input':
56
- case 'input-video':
57
- parsed.inputVideo = value;
58
- break;
59
- case 'output':
60
- case 'output-video':
61
- parsed.outputVideo = value;
62
- break;
63
- case 'in-place':
64
- parsed.inPlace = true;
65
- break;
66
- case 'aspect':
67
- parsed.aspect = value;
68
- break;
69
- case 'target-size':
70
- parsed.targetSize = value;
71
- break;
72
- case 'target-width':
73
- parsed.targetWidth = parseInt(value, 10) || null;
74
- break;
75
- case 'target-height':
76
- parsed.targetHeight = parseInt(value, 10) || null;
77
- break;
78
- case 'force':
79
- parsed.force = true;
80
- break;
81
- case 'ffmpeg-bin':
82
- parsed.ffmpegBin = value;
83
- break;
84
- case 'ffprobe-bin':
85
- parsed.ffprobeBin = value;
86
- break;
87
- }
88
- } else if (!parsed.inputVideo && !arg.startsWith('-')) {
89
- parsed.inputVideo = arg;
90
- }
91
- }
92
-
93
- return parsed;
94
- }
5
+ import { SystemError, UserInputError, createToolRunner } from '@laitszkin/tool-utils';
95
6
 
96
7
  function parseSize(value: string): { width: number; height: number } {
97
8
  const match = value.trim().toLowerCase().match(/^(\d{2,5})x(\d{2,5})$/);
98
- if (!match) throw new Error('Invalid size format. Use WIDTHxHEIGHT, for example 1080x1920.');
9
+ if (!match) throw new UserInputError('Invalid size format. Use WIDTHxHEIGHT, for example 1080x1920.');
99
10
  const width = parseInt(match[1], 10);
100
11
  const height = parseInt(match[2], 10);
101
- if (width <= 0 || height <= 0) throw new Error('Width and height must be positive integers.');
12
+ if (width <= 0 || height <= 0) throw new UserInputError('Width and height must be positive integers.');
102
13
  return { width, height };
103
14
  }
104
15
 
105
16
  function parseRatio(value: string): { width: number; height: number } {
106
17
  const match = value.trim().match(/^(\d+):(\d+)$/);
107
- if (!match) throw new Error('Invalid aspect ratio format. Use WIDTH:HEIGHT, for example 16:9.');
18
+ if (!match) throw new UserInputError('Invalid aspect ratio format. Use WIDTH:HEIGHT, for example 16:9.');
108
19
  const width = parseInt(match[1], 10);
109
20
  const height = parseInt(match[2], 10);
110
- if (width <= 0 || height <= 0) throw new Error('Aspect ratio values must be positive integers.');
21
+ if (width <= 0 || height <= 0) throw new UserInputError('Aspect ratio values must be positive integers.');
111
22
  return { width, height };
112
23
  }
113
24
 
@@ -120,14 +31,14 @@ function probeVideoSize(videoPath: string, ffprobeBin: string): { width: number;
120
31
  const payload = JSON.parse(result);
121
32
  const streams = payload.streams;
122
33
  if (!Array.isArray(streams) || streams.length === 0) {
123
- throw new Error(`No video stream found in ${videoPath}.`);
34
+ throw new SystemError(`No video stream found in ${videoPath}.`);
124
35
  }
125
36
 
126
37
  const first = streams[0];
127
38
  const width = first.width;
128
39
  const height = first.height;
129
40
  if (typeof width !== 'number' || typeof height !== 'number' || width <= 0 || height <= 0) {
130
- throw new Error(`Invalid video dimensions from ffprobe for ${videoPath}.`);
41
+ throw new SystemError(`Invalid video dimensions from ffprobe for ${videoPath}.`);
131
42
  }
132
43
  return { width, height };
133
44
  }
@@ -174,87 +85,97 @@ function buildVideoFilter(
174
85
  };
175
86
  }
176
87
 
177
- function resolveTargetSize(opts: AspectArgs): { width: number; height: number } {
178
- if (opts.aspect) {
179
- // Aspect ratio mode: derive target size from input
180
- // We'll resolve this later in the handler
181
- return { width: 0, height: 0 };
182
- }
183
-
184
- if (opts.targetSize && (opts.targetWidth !== null || opts.targetHeight !== null)) {
185
- throw new Error('Use either --target-size or --target-width/--target-height, not both.');
88
+ function resolveTargetSize(
89
+ targetSize: string | null,
90
+ targetWidth: number | null,
91
+ targetHeight: number | null,
92
+ ): { width: number; height: number } {
93
+ if (targetSize && (targetWidth !== null || targetHeight !== null)) {
94
+ throw new UserInputError('Use either --target-size or --target-width/--target-height, not both.');
186
95
  }
187
96
 
188
- if (opts.targetSize) return parseSize(opts.targetSize);
97
+ if (targetSize) return parseSize(targetSize);
189
98
 
190
- const width = opts.targetWidth || parseInt(process.env.TEXT_TO_SHORT_VIDEO_WIDTH || '1080', 10);
191
- const height = opts.targetHeight || parseInt(process.env.TEXT_TO_SHORT_VIDEO_HEIGHT || '1920', 10);
99
+ const width = targetWidth || parseInt(process.env.TEXT_TO_SHORT_VIDEO_WIDTH || '1080', 10);
100
+ const height = targetHeight || parseInt(process.env.TEXT_TO_SHORT_VIDEO_HEIGHT || '1920', 10);
192
101
 
193
- if (width <= 0 || height <= 0) throw new Error('Target width and height must be positive integers.');
102
+ if (width <= 0 || height <= 0) throw new UserInputError('Target width and height must be positive integers.');
194
103
  return { width, height };
195
104
  }
196
105
 
197
- export async function enforceVideoAspectRatioHandler(args: string[], context: ToolContext): Promise<number> {
198
- const stdout = context.stdout || process.stdout;
199
- const stderr = context.stderr || process.stderr;
200
-
201
- try {
202
- const opts = parseArgs(args);
203
-
204
- if (opts.help) {
205
- stdout.write(`Usage: apltk enforce-video-aspect-ratio [options]
206
-
207
- Resize video output to a target aspect ratio or size.
208
-
209
- Options:
210
- --input, --input-video <path> Path to input video (required)
211
- --output, --output-video <path> Path to output video
212
- --in-place Overwrite input file
213
- --aspect <ratio> Target aspect ratio, e.g. 9:16 or 16:9
214
- --target-size <size> Target size, e.g. 1080x1920
215
- --target-width <px> Target width
216
- --target-height <px> Target height
217
- --force Overwrite existing output
218
- --ffmpeg-bin <path> ffmpeg executable (default: ffmpeg)
219
- --ffprobe-bin <path> ffprobe executable (default: ffprobe)
220
- `);
221
- return 0;
222
- }
223
-
224
- if (!opts.inputVideo) {
225
- stderr.write('Error: --input-video is required.\n');
226
- return 1;
106
+ const schema = {
107
+ options: {
108
+ 'input': { type: 'string' as const },
109
+ 'input-video': { type: 'string' as const },
110
+ 'output': { type: 'string' as const },
111
+ 'output-video': { type: 'string' as const },
112
+ 'in-place': { type: 'boolean' as const, default: false },
113
+ 'aspect': { type: 'string' as const },
114
+ 'target-size': { type: 'string' as const },
115
+ 'target-width': { type: 'string' as const },
116
+ 'target-height': { type: 'string' as const },
117
+ 'force': { type: 'boolean' as const, default: false },
118
+ 'ffmpeg-bin': { type: 'string' as const, default: 'ffmpeg' },
119
+ 'ffprobe-bin': { type: 'string' as const, default: 'ffprobe' },
120
+ },
121
+ allowPositionals: true,
122
+ usage: 'apltk enforce-video-aspect-ratio [options]',
123
+ description: 'Resize video output to a target aspect ratio or size.',
124
+ handler: async (
125
+ values: Record<string, unknown>,
126
+ positionals: string[],
127
+ context: ToolContext,
128
+ ): Promise<number> => {
129
+ const stdout = context.stdout || process.stdout;
130
+
131
+ const inputVideo = (values['input'] as string | undefined) ||
132
+ (values['input-video'] as string | undefined) ||
133
+ positionals[0] ||
134
+ null;
135
+
136
+ const outputVideo = (values['output'] as string | undefined) ||
137
+ (values['output-video'] as string | undefined) ||
138
+ null;
139
+
140
+ const inPlace = !!values['in-place'];
141
+ const targetSize = (values['target-size'] as string | undefined) ?? null;
142
+ const targetWidthVal = values['target-width'] ? parseInt(values['target-width'] as string, 10) || null : null;
143
+ const targetHeightVal = values['target-height'] ? parseInt(values['target-height'] as string, 10) || null : null;
144
+ const aspect = (values['aspect'] as string | undefined) ?? null;
145
+ const force = !!values['force'];
146
+ const ffmpegBin = (values['ffmpeg-bin'] as string) || 'ffmpeg';
147
+ const ffprobeBin = (values['ffprobe-bin'] as string) || 'ffprobe';
148
+
149
+ if (!inputVideo) {
150
+ throw new UserInputError('--input-video is required.');
227
151
  }
228
152
 
229
- const inputPath = path.resolve(opts.inputVideo);
153
+ const inputPath = path.resolve(inputVideo);
230
154
  if (!fs.existsSync(inputPath)) {
231
- stderr.write(`Error: Input video not found: ${inputPath}\n`);
232
- return 1;
155
+ throw new UserInputError(`Input video not found: ${inputPath}`);
233
156
  }
234
157
 
235
- if (opts.inPlace && opts.outputVideo) {
236
- stderr.write('Error: Do not pass --output-video with --in-place.\n');
237
- return 1;
158
+ if (inPlace && outputVideo) {
159
+ throw new UserInputError('Do not pass --output-video with --in-place.');
238
160
  }
239
161
 
240
162
  // Validate ffmpeg/ffprobe availability
241
163
  try {
242
- execSync(`which ${opts.ffmpegBin}`, { stdio: 'ignore' });
243
- execSync(`which ${opts.ffprobeBin}`, { stdio: 'ignore' });
164
+ execSync(`which ${ffmpegBin}`, { stdio: 'ignore' });
165
+ execSync(`which ${ffprobeBin}`, { stdio: 'ignore' });
244
166
  } catch {
245
- stderr.write(`Error: Missing required commands: ${opts.ffmpegBin}, ${opts.ffprobeBin}\n`);
246
- return 1;
167
+ throw new UserInputError(`Missing required commands: ${ffmpegBin}, ${ffprobeBin}`);
247
168
  }
248
169
 
249
- const inputSize = probeVideoSize(inputPath, opts.ffprobeBin);
170
+ const inputSize = probeVideoSize(inputPath, ffprobeBin);
250
171
 
251
172
  // Resolve target dimensions
252
173
  let targetWidth: number;
253
174
  let targetHeight: number;
254
175
 
255
- if (opts.aspect) {
176
+ if (aspect) {
256
177
  // Derive from aspect ratio based on input dimensions
257
- const ratio = parseRatio(opts.aspect);
178
+ const ratio = parseRatio(aspect);
258
179
  const inputWider = inputSize.width * ratio.height > inputSize.height * ratio.width;
259
180
 
260
181
  if (inputWider) {
@@ -268,7 +189,7 @@ Options:
268
189
  targetWidth = evenFloor(targetWidth);
269
190
  targetHeight = evenFloor(targetHeight);
270
191
  } else {
271
- const target = resolveTargetSize(opts);
192
+ const target = resolveTargetSize(targetSize, targetWidthVal, targetHeightVal);
272
193
  targetWidth = target.width;
273
194
  targetHeight = target.height;
274
195
  }
@@ -277,26 +198,24 @@ Options:
277
198
  let outputPath: string;
278
199
  let replaceInPlace = false;
279
200
 
280
- if (opts.inPlace) {
201
+ if (inPlace) {
281
202
  outputPath = path.join(
282
203
  path.dirname(inputPath),
283
204
  `.tmp_${path.basename(inputPath)}`,
284
205
  );
285
206
  replaceInPlace = true;
286
- } else if (opts.outputVideo) {
287
- outputPath = path.resolve(opts.outputVideo);
207
+ } else if (outputVideo) {
208
+ outputPath = path.resolve(outputVideo);
288
209
  if (outputPath === inputPath) {
289
- stderr.write('Error: Output path equals input path. Use --in-place to replace the input file.\n');
290
- return 1;
210
+ throw new UserInputError('Output path equals input path. Use --in-place to replace the input file.');
291
211
  }
292
212
  } else {
293
213
  const parsed = path.parse(inputPath);
294
214
  outputPath = path.join(parsed.dir, `${parsed.name}_aspect_fixed.mp4`);
295
215
  }
296
216
 
297
- if (!replaceInPlace && fs.existsSync(outputPath) && !opts.force) {
298
- stderr.write(`Error: Output already exists: ${outputPath}. Use --force to overwrite.\n`);
299
- return 1;
217
+ if (!replaceInPlace && fs.existsSync(outputPath) && !force) {
218
+ throw new UserInputError(`Output already exists: ${outputPath}. Use --force to overwrite.`);
300
219
  }
301
220
 
302
221
  const { filter, cropApplied } = buildVideoFilter(
@@ -321,10 +240,10 @@ Options:
321
240
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
322
241
 
323
242
  const ffmpegCmd = [
324
- opts.ffmpegBin,
243
+ ffmpegBin,
325
244
  '-hide_banner',
326
245
  '-loglevel', 'error',
327
- opts.force || replaceInPlace ? '-y' : '-n',
246
+ force || replaceInPlace ? '-y' : '-n',
328
247
  '-i', inputPath,
329
248
  '-vf', filter,
330
249
  '-map', '0:v:0',
@@ -342,7 +261,7 @@ Options:
342
261
  } catch (err: unknown) {
343
262
  if (replaceInPlace && fs.existsSync(outputPath)) fs.unlinkSync(outputPath);
344
263
  const msg = err instanceof Error ? err.message : 'unknown error';
345
- throw new Error(`ffmpeg failed: ${msg}`);
264
+ throw new SystemError(`ffmpeg failed: ${msg}`, undefined, { cause: err });
346
265
  }
347
266
 
348
267
  if (replaceInPlace) {
@@ -350,7 +269,7 @@ Options:
350
269
  outputPath = inputPath;
351
270
  }
352
271
 
353
- const finalSize = probeVideoSize(outputPath, opts.ffprobeBin);
272
+ const finalSize = probeVideoSize(outputPath, ffprobeBin);
354
273
  stdout.write(
355
274
  `[OK] Processed video written: ${outputPath}\n` +
356
275
  `[INFO] Input size: ${inputSize.width}x${inputSize.height}\n` +
@@ -360,16 +279,12 @@ Options:
360
279
  );
361
280
 
362
281
  return 0;
363
- } catch (err: unknown) {
364
- const msg = err instanceof Error ? err.message : 'Unknown error';
365
- stderr.write(`Error: ${msg}\n`);
366
- return 1;
367
- }
368
- }
282
+ },
283
+ };
369
284
 
370
285
  export const tool: ToolDefinition = {
371
286
  name: 'enforce-video-aspect-ratio',
372
287
  category: 'media',
373
288
  description: 'Resize video output to a target aspect ratio or size.',
374
- handler: enforceVideoAspectRatioHandler,
289
+ handler: createToolRunner(schema),
375
290
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@laitszkin/tool-enforce-video-aspect-ratio",
3
- "version": "4.0.8",
4
- "description": "Apollo Toolkit CLI tool",
3
+ "version": "5.0.0",
4
+ "description": "Apollo Toolkit \u2014 CLI tool",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -19,4 +19,4 @@
19
19
  "dependencies": {
20
20
  "@laitszkin/tool-registry": "*"
21
21
  }
22
- }
22
+ }
@@ -24,6 +24,13 @@ import { scoreAllTests } from './scorer.js';
24
24
  import { generateReport, writeReport } from './reporter.js';
25
25
  import { loadAllScores, extractIssues, deduplicateIssues, generateSuggestedFix, generateOptimizationPlan, optimizeSkillMd, } from './optimizer.js';
26
26
  import { promisePool } from './lib/promise-pool.js';
27
+ // ╔══════════════════════════════════════════════════════════════════════════╗
28
+ // ║ SCOPE EXCLUSION NOTICE ║
29
+ // ║ This tool is explicitly excluded from the CLI refactoring scope ║
30
+ // ║ (SPEC.md L28). It predates createToolRunner, AppError, and ║
31
+ // ║ PlatformAdapter. DO NOT use this tool's patterns as a template ║
32
+ // ║ for new tools. New tools should use createToolRunner + AppError. ║
33
+ // ╚══════════════════════════════════════════════════════════════════════════╝
27
34
  // ─── Helpers ────────────────────────────────────────────────────────────────
28
35
  /**
29
36
  * Resolve the project root directory.