@laitszkin/apollo-toolkit 3.13.2 → 3.14.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 (154) hide show
  1. package/AGENTS.md +7 -7
  2. package/CHANGELOG.md +27 -0
  3. package/CLAUDE.md +8 -8
  4. package/analyse-app-logs/SKILL.md +3 -3
  5. package/bin/apollo-toolkit.ts +7 -0
  6. package/codex/codex-memory-manager/SKILL.md +2 -2
  7. package/codex/learn-skill-from-conversations/SKILL.md +3 -3
  8. package/dist/bin/apollo-toolkit.d.ts +2 -0
  9. package/dist/bin/apollo-toolkit.js +7 -0
  10. package/dist/lib/cli.d.ts +41 -0
  11. package/dist/lib/cli.js +655 -0
  12. package/dist/lib/installer.d.ts +59 -0
  13. package/dist/lib/installer.js +404 -0
  14. package/dist/lib/tool-runner.d.ts +19 -0
  15. package/dist/lib/tool-runner.js +536 -0
  16. package/dist/lib/tools/architecture.d.ts +2 -0
  17. package/dist/lib/tools/architecture.js +34 -0
  18. package/dist/lib/tools/create-specs.d.ts +2 -0
  19. package/dist/lib/tools/create-specs.js +175 -0
  20. package/dist/lib/tools/docs-to-voice.d.ts +2 -0
  21. package/dist/lib/tools/docs-to-voice.js +705 -0
  22. package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
  23. package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
  24. package/dist/lib/tools/extract-conversations.d.ts +2 -0
  25. package/dist/lib/tools/extract-conversations.js +105 -0
  26. package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
  27. package/dist/lib/tools/extract-pdf-text.js +92 -0
  28. package/dist/lib/tools/filter-logs.d.ts +2 -0
  29. package/dist/lib/tools/filter-logs.js +94 -0
  30. package/dist/lib/tools/find-github-issues.d.ts +2 -0
  31. package/dist/lib/tools/find-github-issues.js +176 -0
  32. package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
  33. package/dist/lib/tools/generate-storyboard-images.js +419 -0
  34. package/dist/lib/tools/log-cli-utils.d.ts +35 -0
  35. package/dist/lib/tools/log-cli-utils.js +233 -0
  36. package/dist/lib/tools/open-github-issue.d.ts +2 -0
  37. package/dist/lib/tools/open-github-issue.js +750 -0
  38. package/dist/lib/tools/read-github-issue.d.ts +2 -0
  39. package/dist/lib/tools/read-github-issue.js +134 -0
  40. package/dist/lib/tools/render-error-book.d.ts +2 -0
  41. package/dist/lib/tools/render-error-book.js +265 -0
  42. package/dist/lib/tools/render-katex.d.ts +2 -0
  43. package/dist/lib/tools/render-katex.js +294 -0
  44. package/dist/lib/tools/review-threads.d.ts +2 -0
  45. package/dist/lib/tools/review-threads.js +491 -0
  46. package/dist/lib/tools/search-logs.d.ts +2 -0
  47. package/dist/lib/tools/search-logs.js +164 -0
  48. package/dist/lib/tools/sync-memory-index.d.ts +2 -0
  49. package/dist/lib/tools/sync-memory-index.js +113 -0
  50. package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
  51. package/dist/lib/tools/validate-openai-agent-config.js +184 -0
  52. package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
  53. package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
  54. package/dist/lib/types.d.ts +82 -0
  55. package/dist/lib/types.js +2 -0
  56. package/dist/lib/updater.d.ts +34 -0
  57. package/dist/lib/updater.js +112 -0
  58. package/dist/lib/utils/format.d.ts +2 -0
  59. package/dist/lib/utils/format.js +6 -0
  60. package/dist/lib/utils/terminal.d.ts +12 -0
  61. package/dist/lib/utils/terminal.js +26 -0
  62. package/docs-to-voice/SKILL.md +0 -1
  63. package/generate-spec/SKILL.md +1 -1
  64. package/katex/SKILL.md +1 -2
  65. package/lib/cli.ts +780 -0
  66. package/lib/installer.ts +466 -0
  67. package/lib/tool-runner.ts +561 -0
  68. package/lib/tools/architecture.ts +34 -0
  69. package/lib/tools/create-specs.ts +204 -0
  70. package/lib/tools/docs-to-voice.ts +799 -0
  71. package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
  72. package/lib/tools/extract-conversations.ts +114 -0
  73. package/lib/tools/extract-pdf-text.ts +99 -0
  74. package/lib/tools/filter-logs.ts +118 -0
  75. package/lib/tools/find-github-issues.ts +211 -0
  76. package/lib/tools/generate-storyboard-images.ts +455 -0
  77. package/lib/tools/log-cli-utils.ts +262 -0
  78. package/lib/tools/open-github-issue.ts +930 -0
  79. package/lib/tools/read-github-issue.ts +179 -0
  80. package/lib/tools/render-error-book.ts +300 -0
  81. package/lib/tools/render-katex.ts +325 -0
  82. package/lib/tools/review-threads.ts +590 -0
  83. package/lib/tools/search-logs.ts +200 -0
  84. package/lib/tools/sync-memory-index.ts +114 -0
  85. package/lib/tools/validate-openai-agent-config.ts +209 -0
  86. package/lib/tools/validate-skill-frontmatter.ts +124 -0
  87. package/lib/types.ts +90 -0
  88. package/lib/updater.ts +165 -0
  89. package/lib/utils/format.ts +7 -0
  90. package/lib/utils/terminal.ts +22 -0
  91. package/open-github-issue/SKILL.md +2 -2
  92. package/optimise-skill/SKILL.md +1 -1
  93. package/package.json +13 -4
  94. package/resources/project-architecture/assets/architecture.css +764 -0
  95. package/resources/project-architecture/assets/viewer.client.js +144 -0
  96. package/resources/project-architecture/index.html +42 -0
  97. package/review-spec-related-changes/SKILL.md +1 -1
  98. package/solve-issues-found-during-review/SKILL.md +2 -1
  99. package/tsconfig.json +28 -0
  100. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  101. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  102. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  103. package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
  104. package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
  105. package/analyse-app-logs/scripts/search_logs.py +0 -137
  106. package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
  107. package/analyse-app-logs/tests/test_search_logs.py +0 -100
  108. package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
  109. package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
  110. package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
  111. package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
  112. package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
  113. package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
  114. package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
  115. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  116. package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
  117. package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
  118. package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
  119. package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
  120. package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
  121. package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
  122. package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
  123. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  124. package/generate-spec/scripts/create-specs +0 -215
  125. package/generate-spec/tests/test_create_specs.py +0 -200
  126. package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
  127. package/init-project-html/scripts/architecture.js +0 -296
  128. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  129. package/katex/scripts/render_katex.py +0 -247
  130. package/katex/scripts/render_katex.sh +0 -11
  131. package/katex/tests/test_render_katex.py +0 -174
  132. package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
  133. package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
  134. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  135. package/open-github-issue/scripts/open_github_issue.py +0 -705
  136. package/open-github-issue/tests/test_open_github_issue.py +0 -381
  137. package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
  138. package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
  139. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  140. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  141. package/read-github-issue/scripts/find_issues.py +0 -148
  142. package/read-github-issue/scripts/read_issue.py +0 -108
  143. package/read-github-issue/tests/test_find_issues.py +0 -127
  144. package/read-github-issue/tests/test_read_issue.py +0 -109
  145. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  146. package/resolve-review-comments/scripts/review_threads.py +0 -425
  147. package/resolve-review-comments/tests/test_review_threads.py +0 -74
  148. package/scripts/validate_openai_agent_config.py +0 -209
  149. package/scripts/validate_skill_frontmatter.py +0 -131
  150. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
  151. package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
  152. package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
  153. package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
  154. package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function enforceVideoAspectRatioHandler(args: string[], context: ToolContext): Promise<number>;
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.enforceVideoAspectRatioHandler = enforceVideoAspectRatioHandler;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ function parseArgs(args) {
11
+ const parsed = {
12
+ inputVideo: null,
13
+ outputVideo: null,
14
+ inPlace: false,
15
+ targetSize: null,
16
+ targetWidth: null,
17
+ targetHeight: null,
18
+ aspect: null,
19
+ force: false,
20
+ ffmpegBin: 'ffmpeg',
21
+ ffprobeBin: 'ffprobe',
22
+ help: false,
23
+ };
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (arg === '--help' || arg === '-h') {
27
+ parsed.help = true;
28
+ continue;
29
+ }
30
+ if (arg.startsWith('--')) {
31
+ const eqIndex = arg.indexOf('=');
32
+ let key;
33
+ let value;
34
+ if (eqIndex !== -1) {
35
+ key = arg.slice(2, eqIndex);
36
+ value = arg.slice(eqIndex + 1);
37
+ }
38
+ else {
39
+ key = arg.slice(2);
40
+ value = args[++i] || '';
41
+ }
42
+ switch (key) {
43
+ case 'input':
44
+ case 'input-video':
45
+ parsed.inputVideo = value;
46
+ break;
47
+ case 'output':
48
+ case 'output-video':
49
+ parsed.outputVideo = value;
50
+ break;
51
+ case 'in-place':
52
+ parsed.inPlace = true;
53
+ break;
54
+ case 'aspect':
55
+ parsed.aspect = value;
56
+ break;
57
+ case 'target-size':
58
+ parsed.targetSize = value;
59
+ break;
60
+ case 'target-width':
61
+ parsed.targetWidth = parseInt(value, 10) || null;
62
+ break;
63
+ case 'target-height':
64
+ parsed.targetHeight = parseInt(value, 10) || null;
65
+ break;
66
+ case 'force':
67
+ parsed.force = true;
68
+ break;
69
+ case 'ffmpeg-bin':
70
+ parsed.ffmpegBin = value;
71
+ break;
72
+ case 'ffprobe-bin':
73
+ parsed.ffprobeBin = value;
74
+ break;
75
+ }
76
+ }
77
+ else if (!parsed.inputVideo && !arg.startsWith('-')) {
78
+ parsed.inputVideo = arg;
79
+ }
80
+ }
81
+ return parsed;
82
+ }
83
+ function parseSize(value) {
84
+ const match = value.trim().toLowerCase().match(/^(\d{2,5})x(\d{2,5})$/);
85
+ if (!match)
86
+ throw new Error('Invalid size format. Use WIDTHxHEIGHT, for example 1080x1920.');
87
+ const width = parseInt(match[1], 10);
88
+ const height = parseInt(match[2], 10);
89
+ if (width <= 0 || height <= 0)
90
+ throw new Error('Width and height must be positive integers.');
91
+ return { width, height };
92
+ }
93
+ function parseRatio(value) {
94
+ const match = value.trim().match(/^(\d+):(\d+)$/);
95
+ if (!match)
96
+ throw new Error('Invalid aspect ratio format. Use WIDTH:HEIGHT, for example 16:9.');
97
+ const width = parseInt(match[1], 10);
98
+ const height = parseInt(match[2], 10);
99
+ if (width <= 0 || height <= 0)
100
+ throw new Error('Aspect ratio values must be positive integers.');
101
+ return { width, height };
102
+ }
103
+ function probeVideoSize(videoPath, ffprobeBin) {
104
+ const result = (0, node_child_process_1.execSync)(`${ffprobeBin} -v error -select_streams v:0 -show_entries stream=width,height -of json "${videoPath}"`, { encoding: 'utf-8', timeout: 30000 });
105
+ const payload = JSON.parse(result);
106
+ const streams = payload.streams;
107
+ if (!Array.isArray(streams) || streams.length === 0) {
108
+ throw new Error(`No video stream found in ${videoPath}.`);
109
+ }
110
+ const first = streams[0];
111
+ const width = first.width;
112
+ const height = first.height;
113
+ if (typeof width !== 'number' || typeof height !== 'number' || width <= 0 || height <= 0) {
114
+ throw new Error(`Invalid video dimensions from ffprobe for ${videoPath}.`);
115
+ }
116
+ return { width, height };
117
+ }
118
+ function evenFloor(value, minimum = 2) {
119
+ const floored = value % 2 === 0 ? value : value - 1;
120
+ return Math.max(floored, minimum);
121
+ }
122
+ function buildVideoFilter(inputWidth, inputHeight, targetWidth, targetHeight) {
123
+ const sameRatio = inputWidth * targetHeight === inputHeight * targetWidth;
124
+ const sameSize = inputWidth === targetWidth && inputHeight === targetHeight;
125
+ if (sameRatio && sameSize)
126
+ return { filter: null, cropApplied: false };
127
+ if (sameRatio)
128
+ return { filter: `scale=${targetWidth}:${targetHeight}`, cropApplied: false };
129
+ const inputWider = inputWidth * targetHeight > inputHeight * targetWidth;
130
+ let cropWidth;
131
+ let cropHeight;
132
+ if (inputWider) {
133
+ cropWidth = Math.floor(inputHeight * targetWidth / targetHeight);
134
+ cropHeight = inputHeight;
135
+ }
136
+ else {
137
+ cropWidth = inputWidth;
138
+ cropHeight = Math.floor(inputWidth * targetHeight / targetWidth);
139
+ }
140
+ cropWidth = Math.min(evenFloor(cropWidth), evenFloor(inputWidth));
141
+ cropHeight = Math.min(evenFloor(cropHeight), evenFloor(inputHeight));
142
+ const offsetX = Math.max(Math.floor((inputWidth - cropWidth) / 2), 0);
143
+ const offsetY = Math.max(Math.floor((inputHeight - cropHeight) / 2), 0);
144
+ return {
145
+ filter: `crop=${cropWidth}:${cropHeight}:${offsetX}:${offsetY},scale=${targetWidth}:${targetHeight}`,
146
+ cropApplied: true,
147
+ };
148
+ }
149
+ function resolveTargetSize(opts) {
150
+ if (opts.aspect) {
151
+ // Aspect ratio mode: derive target size from input
152
+ // We'll resolve this later in the handler
153
+ return { width: 0, height: 0 };
154
+ }
155
+ if (opts.targetSize && (opts.targetWidth !== null || opts.targetHeight !== null)) {
156
+ throw new Error('Use either --target-size or --target-width/--target-height, not both.');
157
+ }
158
+ if (opts.targetSize)
159
+ return parseSize(opts.targetSize);
160
+ const width = opts.targetWidth || parseInt(process.env.TEXT_TO_SHORT_VIDEO_WIDTH || '1080', 10);
161
+ const height = opts.targetHeight || parseInt(process.env.TEXT_TO_SHORT_VIDEO_HEIGHT || '1920', 10);
162
+ if (width <= 0 || height <= 0)
163
+ throw new Error('Target width and height must be positive integers.');
164
+ return { width, height };
165
+ }
166
+ async function enforceVideoAspectRatioHandler(args, context) {
167
+ const stdout = context.stdout || process.stdout;
168
+ const stderr = context.stderr || process.stderr;
169
+ try {
170
+ const opts = parseArgs(args);
171
+ if (opts.help) {
172
+ stdout.write(`Usage: apltk enforce-video-aspect-ratio [options]
173
+
174
+ Resize video output to a target aspect ratio or size.
175
+
176
+ Options:
177
+ --input, --input-video <path> Path to input video (required)
178
+ --output, --output-video <path> Path to output video
179
+ --in-place Overwrite input file
180
+ --aspect <ratio> Target aspect ratio, e.g. 9:16 or 16:9
181
+ --target-size <size> Target size, e.g. 1080x1920
182
+ --target-width <px> Target width
183
+ --target-height <px> Target height
184
+ --force Overwrite existing output
185
+ --ffmpeg-bin <path> ffmpeg executable (default: ffmpeg)
186
+ --ffprobe-bin <path> ffprobe executable (default: ffprobe)
187
+ `);
188
+ return 0;
189
+ }
190
+ if (!opts.inputVideo) {
191
+ stderr.write('Error: --input-video is required.\n');
192
+ return 1;
193
+ }
194
+ const inputPath = node_path_1.default.resolve(opts.inputVideo);
195
+ if (!node_fs_1.default.existsSync(inputPath)) {
196
+ stderr.write(`Error: Input video not found: ${inputPath}\n`);
197
+ return 1;
198
+ }
199
+ if (opts.inPlace && opts.outputVideo) {
200
+ stderr.write('Error: Do not pass --output-video with --in-place.\n');
201
+ return 1;
202
+ }
203
+ // Validate ffmpeg/ffprobe availability
204
+ try {
205
+ (0, node_child_process_1.execSync)(`which ${opts.ffmpegBin}`, { stdio: 'ignore' });
206
+ (0, node_child_process_1.execSync)(`which ${opts.ffprobeBin}`, { stdio: 'ignore' });
207
+ }
208
+ catch {
209
+ stderr.write(`Error: Missing required commands: ${opts.ffmpegBin}, ${opts.ffprobeBin}\n`);
210
+ return 1;
211
+ }
212
+ const inputSize = probeVideoSize(inputPath, opts.ffprobeBin);
213
+ // Resolve target dimensions
214
+ let targetWidth;
215
+ let targetHeight;
216
+ if (opts.aspect) {
217
+ // Derive from aspect ratio based on input dimensions
218
+ const ratio = parseRatio(opts.aspect);
219
+ const inputWider = inputSize.width * ratio.height > inputSize.height * ratio.width;
220
+ if (inputWider) {
221
+ targetWidth = Math.floor(inputSize.height * ratio.width / ratio.height);
222
+ targetHeight = inputSize.height;
223
+ }
224
+ else {
225
+ targetWidth = inputSize.width;
226
+ targetHeight = Math.floor(inputSize.width * ratio.height / ratio.width);
227
+ }
228
+ // Ensure even dimensions
229
+ targetWidth = evenFloor(targetWidth);
230
+ targetHeight = evenFloor(targetHeight);
231
+ }
232
+ else {
233
+ const target = resolveTargetSize(opts);
234
+ targetWidth = target.width;
235
+ targetHeight = target.height;
236
+ }
237
+ // Resolve output path
238
+ let outputPath;
239
+ let replaceInPlace = false;
240
+ if (opts.inPlace) {
241
+ outputPath = node_path_1.default.join(node_path_1.default.dirname(inputPath), `.tmp_${node_path_1.default.basename(inputPath)}`);
242
+ replaceInPlace = true;
243
+ }
244
+ else if (opts.outputVideo) {
245
+ outputPath = node_path_1.default.resolve(opts.outputVideo);
246
+ if (outputPath === inputPath) {
247
+ stderr.write('Error: Output path equals input path. Use --in-place to replace the input file.\n');
248
+ return 1;
249
+ }
250
+ }
251
+ else {
252
+ const parsed = node_path_1.default.parse(inputPath);
253
+ outputPath = node_path_1.default.join(parsed.dir, `${parsed.name}_aspect_fixed.mp4`);
254
+ }
255
+ if (!replaceInPlace && node_fs_1.default.existsSync(outputPath) && !opts.force) {
256
+ stderr.write(`Error: Output already exists: ${outputPath}. Use --force to overwrite.\n`);
257
+ return 1;
258
+ }
259
+ const { filter, cropApplied } = buildVideoFilter(inputSize.width, inputSize.height, targetWidth, targetHeight);
260
+ if (filter === null) {
261
+ stdout.write(`[INFO] Video already matches target size and aspect ratio: ${inputSize.width}x${inputSize.height}.\n`);
262
+ if (!replaceInPlace && outputPath !== inputPath) {
263
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
264
+ node_fs_1.default.copyFileSync(inputPath, outputPath);
265
+ stdout.write(`[OK] Copied original video to: ${outputPath}\n`);
266
+ }
267
+ return 0;
268
+ }
269
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
270
+ const ffmpegCmd = [
271
+ opts.ffmpegBin,
272
+ '-hide_banner',
273
+ '-loglevel', 'error',
274
+ opts.force || replaceInPlace ? '-y' : '-n',
275
+ '-i', inputPath,
276
+ '-vf', filter,
277
+ '-map', '0:v:0',
278
+ '-map', '0:a?',
279
+ '-c:v', 'libx264',
280
+ '-preset', 'medium',
281
+ '-crf', '18',
282
+ '-c:a', 'aac',
283
+ '-movflags', '+faststart',
284
+ outputPath,
285
+ ];
286
+ try {
287
+ (0, node_child_process_1.execSync)(ffmpegCmd.join(' '), { stdio: 'ignore', timeout: 300000 });
288
+ }
289
+ catch (err) {
290
+ if (replaceInPlace && node_fs_1.default.existsSync(outputPath))
291
+ node_fs_1.default.unlinkSync(outputPath);
292
+ const msg = err instanceof Error ? err.message : 'unknown error';
293
+ throw new Error(`ffmpeg failed: ${msg}`);
294
+ }
295
+ if (replaceInPlace) {
296
+ node_fs_1.default.renameSync(outputPath, inputPath);
297
+ outputPath = inputPath;
298
+ }
299
+ const finalSize = probeVideoSize(outputPath, opts.ffprobeBin);
300
+ stdout.write(`[OK] Processed video written: ${outputPath}\n` +
301
+ `[INFO] Input size: ${inputSize.width}x${inputSize.height}\n` +
302
+ `[INFO] Target size: ${targetWidth}x${targetHeight}\n` +
303
+ `[INFO] Output size: ${finalSize.width}x${finalSize.height}\n` +
304
+ `[INFO] Center crop applied: ${cropApplied ? 'yes' : 'no'}\n`);
305
+ return 0;
306
+ }
307
+ catch (err) {
308
+ const msg = err instanceof Error ? err.message : 'Unknown error';
309
+ stderr.write(`Error: ${msg}\n`);
310
+ return 1;
311
+ }
312
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function extractConversationsHandler(args: string[], context: ToolContext): Promise<number>;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractConversationsHandler = extractConversationsHandler;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ function getCodexHome() {
10
+ return process.env.CODEX_HOME || node_path_1.default.join(process.env.HOME || '', '.codex');
11
+ }
12
+ function readSessionsDir(sessionsDir) {
13
+ if (!node_fs_1.default.existsSync(sessionsDir))
14
+ return [];
15
+ const sessions = [];
16
+ const entries = node_fs_1.default.readdirSync(sessionsDir, { withFileTypes: true });
17
+ for (const entry of entries) {
18
+ if (!entry.isDirectory())
19
+ continue;
20
+ const sessionDir = node_path_1.default.join(sessionsDir, entry.name);
21
+ const metaFile = node_path_1.default.join(sessionDir, 'session.json');
22
+ if (!node_fs_1.default.existsSync(metaFile))
23
+ continue;
24
+ try {
25
+ const meta = JSON.parse(node_fs_1.default.readFileSync(metaFile, 'utf8'));
26
+ sessions.push({
27
+ id: entry.name,
28
+ title: meta.title || entry.name,
29
+ startedAt: meta.startedAt || '',
30
+ updatedAt: meta.updatedAt || '',
31
+ messageCount: meta.messages?.length || 0,
32
+ });
33
+ }
34
+ catch {
35
+ // skip malformed sessions
36
+ }
37
+ }
38
+ return sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
39
+ }
40
+ function filterSessionsByHours(sessions, hours) {
41
+ if (hours <= 0)
42
+ return sessions;
43
+ const cutoff = Date.now() - hours * 60 * 60 * 1000;
44
+ return sessions.filter((s) => {
45
+ const t = new Date(s.updatedAt).getTime();
46
+ return !isNaN(t) && t >= cutoff;
47
+ });
48
+ }
49
+ function extractConversationsHandler(args, context) {
50
+ try {
51
+ let hours = 24;
52
+ let format = 'text';
53
+ for (let i = 0; i < args.length; i++) {
54
+ if (args[i] === '--hours' && i + 1 < args.length)
55
+ hours = parseInt(args[++i], 10) || 24;
56
+ else if (args[i] === '--format' && i + 1 < args.length) {
57
+ const val = args[++i];
58
+ if (val === 'json' || val === 'text')
59
+ format = val;
60
+ }
61
+ }
62
+ const codexHome = getCodexHome();
63
+ const sessionsDir = node_path_1.default.join(codexHome, 'sessions');
64
+ const archivedDir = node_path_1.default.join(codexHome, 'sessions', '.archived');
65
+ let sessions = readSessionsDir(sessionsDir);
66
+ // Also read archived sessions
67
+ if (node_fs_1.default.existsSync(archivedDir)) {
68
+ const archived = readSessionsDir(archivedDir);
69
+ sessions = [...sessions, ...archived];
70
+ }
71
+ sessions = filterSessionsByHours(sessions, hours);
72
+ if (format === 'json') {
73
+ const output = {
74
+ totalSessions: sessions.length,
75
+ hours,
76
+ sessions: sessions.map((s) => ({
77
+ id: s.id,
78
+ title: s.title,
79
+ startedAt: s.startedAt,
80
+ updatedAt: s.updatedAt,
81
+ messageCount: s.messageCount,
82
+ sessionPath: node_path_1.default.join(sessionsDir, s.id),
83
+ })),
84
+ };
85
+ context.stdout?.write(JSON.stringify(output, null, 2));
86
+ context.stdout?.write('\n');
87
+ }
88
+ else {
89
+ context.stdout?.write(`Recent Codex sessions (last ${hours}h):\n`);
90
+ context.stdout?.write(`Found ${sessions.length} sessions\n\n`);
91
+ for (const session of sessions) {
92
+ context.stdout?.write(` [${session.id}] ${session.title}\n`);
93
+ context.stdout?.write(` Started: ${session.startedAt}\n`);
94
+ context.stdout?.write(` Updated: ${session.updatedAt}\n`);
95
+ context.stdout?.write(` Messages: ${session.messageCount}\n\n`);
96
+ }
97
+ }
98
+ return Promise.resolve(0);
99
+ }
100
+ catch (err) {
101
+ const stderr = context.stderr || process.stderr;
102
+ stderr.write(`Error: ${err.message}\n`);
103
+ return Promise.resolve(1);
104
+ }
105
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function extractPdfTextHandler(args: string[], context: ToolContext): Promise<number>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractPdfTextHandler = extractPdfTextHandler;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const SWIFT_SCRIPT = [
10
+ 'import Foundation',
11
+ 'import PDFKit',
12
+ '',
13
+ 'let args = CommandLine.arguments',
14
+ 'guard args.count > 1 else { print("PDF_PATH="); exit(1) }',
15
+ 'let pdfPath = args[1]',
16
+ 'let pdfURL = URL(fileURLWithPath: pdfPath)',
17
+ 'guard let document = PDFDocument(url: pdfURL) else { fputs("Unable to open PDF at \\(pdfPath)\\n", stderr); exit(1) }',
18
+ 'print("PDF_PATH=\\(pdfPath)")',
19
+ 'print("PAGE_COUNT=\\(document.pageCount)")',
20
+ 'for pageIndex in 0..<document.pageCount {',
21
+ ' guard let page = document.page(at: pageIndex) else { continue }',
22
+ ' let text = page.string?.replacingOccurrences(of: "\\u{000C}", with: "\\n").trimmingCharacters(in: .whitespacesAndNewlines) ?? ""',
23
+ ' print("=== PAGE \\(pageIndex + 1) ===")',
24
+ ' if text.isEmpty { print("[NO_TEXT_EXTRACTED]") } else { print(text) }',
25
+ '}',
26
+ ].join('\n');
27
+ async function extractPdfTextHandler(args, context) {
28
+ const stdout = context.stdout || process.stdout;
29
+ const stderr = context.stderr || process.stderr;
30
+ // Find positional pdfPath arg
31
+ let pdfPath = '';
32
+ let showHelp = false;
33
+ for (let i = 0; i < args.length; i++) {
34
+ const arg = args[i];
35
+ if (arg === '--help' || arg === '-h') {
36
+ showHelp = true;
37
+ break;
38
+ }
39
+ if (!arg.startsWith('-')) {
40
+ pdfPath = arg;
41
+ }
42
+ }
43
+ if (showHelp || !pdfPath) {
44
+ stdout.write(`Usage: apltk extract-pdf-text-pdfkit <path>
45
+
46
+ Extract per-page text from a PDF through macOS PDFKit.
47
+
48
+ Arguments:
49
+ path Absolute path to the source PDF file
50
+
51
+ Output format:
52
+ PDF_PATH=<path>
53
+ PAGE_COUNT=<N>
54
+ === PAGE 1 ===
55
+ <page text>
56
+ === PAGE 2 ===
57
+ ...
58
+ `);
59
+ return pdfPath ? 1 : 0;
60
+ }
61
+ const resolvedPath = node_path_1.default.resolve(pdfPath);
62
+ return new Promise((resolve) => {
63
+ const child = (0, node_child_process_1.spawn)('swift', ['-', resolvedPath], {
64
+ stdio: ['pipe', 'pipe', 'pipe'],
65
+ env: context.env || process.env,
66
+ });
67
+ // Write the Swift script to stdin for inline execution
68
+ child.stdin.write(SWIFT_SCRIPT);
69
+ child.stdin.end();
70
+ let stdoutText = '';
71
+ let stderrText = '';
72
+ child.stdout.on('data', (chunk) => {
73
+ stdoutText += String(chunk);
74
+ });
75
+ child.stderr.on('data', (chunk) => {
76
+ stderrText += String(chunk);
77
+ });
78
+ child.on('error', (err) => {
79
+ stderr.write(`Failed to start swift: ${err.message}\n`);
80
+ resolve(1);
81
+ });
82
+ child.on('close', (code) => {
83
+ if (stdoutText) {
84
+ stdout.write(stdoutText);
85
+ }
86
+ if (stderrText) {
87
+ stderr.write(stderrText);
88
+ }
89
+ resolve(typeof code === 'number' ? code : 1);
90
+ });
91
+ });
92
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function filterLogsHandler(argv: string[], context: ToolContext): Promise<number>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.filterLogsHandler = filterLogsHandler;
4
+ const log_cli_utils_1 = require("./log-cli-utils");
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ paths: [],
8
+ start: null,
9
+ end: null,
10
+ assumeTimezone: 'UTC',
11
+ keepUndated: false,
12
+ countOnly: false,
13
+ };
14
+ let i = 0;
15
+ while (i < argv.length) {
16
+ const arg = argv[i];
17
+ if (arg === '--start' && i + 1 < argv.length) {
18
+ args.start = argv[++i];
19
+ }
20
+ else if (arg === '--end' && i + 1 < argv.length) {
21
+ args.end = argv[++i];
22
+ }
23
+ else if (arg === '--assume-timezone' && i + 1 < argv.length) {
24
+ args.assumeTimezone = argv[++i];
25
+ }
26
+ else if (arg === '--keep-undated') {
27
+ args.keepUndated = true;
28
+ }
29
+ else if (arg === '--count-only') {
30
+ args.countOnly = true;
31
+ }
32
+ else if (arg.startsWith('-')) {
33
+ // skip unknown flags
34
+ }
35
+ else {
36
+ args.paths.push(arg);
37
+ }
38
+ i++;
39
+ }
40
+ return args;
41
+ }
42
+ async function filterLogsHandler(argv, context) {
43
+ const { stdout, stderr } = context;
44
+ const args = parseArgs(argv);
45
+ let tzOffset;
46
+ try {
47
+ tzOffset = (0, log_cli_utils_1.buildTimezone)(args.assumeTimezone);
48
+ }
49
+ catch (err) {
50
+ stderr.write(`Error: invalid timezone: ${args.assumeTimezone}\n`);
51
+ return 1;
52
+ }
53
+ let start = null;
54
+ let end = null;
55
+ try {
56
+ if (args.start) {
57
+ start = (0, log_cli_utils_1.parseCliTimestamp)(args.start, args.assumeTimezone);
58
+ }
59
+ if (args.end) {
60
+ end = (0, log_cli_utils_1.parseCliTimestamp)(args.end, args.assumeTimezone);
61
+ }
62
+ }
63
+ catch (err) {
64
+ stderr.write(`Error: ${err.message}\n`);
65
+ return 1;
66
+ }
67
+ if (!(0, log_cli_utils_1.validateTimeWindow)(start, end, stderr)) {
68
+ return 1;
69
+ }
70
+ let matches = 0;
71
+ try {
72
+ for await (const line of (0, log_cli_utils_1.iterInputLines)(args.paths)) {
73
+ const timestamp = (0, log_cli_utils_1.extractTimestamp)(line, args.assumeTimezone);
74
+ if (timestamp === null && !args.keepUndated) {
75
+ continue;
76
+ }
77
+ if (timestamp !== null && !(0, log_cli_utils_1.inWindow)(timestamp, start, end)) {
78
+ continue;
79
+ }
80
+ matches++;
81
+ if (!args.countOnly) {
82
+ stdout.write(line + '\n');
83
+ }
84
+ }
85
+ }
86
+ catch (err) {
87
+ stderr.write(`Error: ${err.message}\n`);
88
+ return 1;
89
+ }
90
+ if (args.countOnly) {
91
+ stdout.write(String(matches) + '\n');
92
+ }
93
+ return 0;
94
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function findGitHubIssuesHandler(argv: string[], context: ToolContext): Promise<number>;