@laitszkin/apollo-toolkit 3.13.2 → 3.14.1
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.
- package/AGENTS.md +7 -7
- package/CHANGELOG.md +36 -0
- package/CLAUDE.md +8 -8
- package/analyse-app-logs/SKILL.md +3 -3
- package/bin/apollo-toolkit.ts +7 -0
- package/codex/codex-memory-manager/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/SKILL.md +3 -3
- package/dist/bin/apollo-toolkit.d.ts +2 -0
- package/dist/bin/apollo-toolkit.js +7 -0
- package/dist/lib/cli.d.ts +41 -0
- package/dist/lib/cli.js +655 -0
- package/dist/lib/installer.d.ts +59 -0
- package/dist/lib/installer.js +404 -0
- package/dist/lib/tool-runner.d.ts +19 -0
- package/dist/lib/tool-runner.js +536 -0
- package/dist/lib/tools/architecture.d.ts +2 -0
- package/dist/lib/tools/architecture.js +23 -0
- package/dist/lib/tools/create-specs.d.ts +2 -0
- package/dist/lib/tools/create-specs.js +175 -0
- package/dist/lib/tools/docs-to-voice.d.ts +2 -0
- package/dist/lib/tools/docs-to-voice.js +705 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
- package/dist/lib/tools/extract-conversations.d.ts +2 -0
- package/dist/lib/tools/extract-conversations.js +105 -0
- package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
- package/dist/lib/tools/extract-pdf-text.js +92 -0
- package/dist/lib/tools/filter-logs.d.ts +2 -0
- package/dist/lib/tools/filter-logs.js +94 -0
- package/dist/lib/tools/find-github-issues.d.ts +2 -0
- package/dist/lib/tools/find-github-issues.js +176 -0
- package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
- package/dist/lib/tools/generate-storyboard-images.js +419 -0
- package/dist/lib/tools/log-cli-utils.d.ts +35 -0
- package/dist/lib/tools/log-cli-utils.js +233 -0
- package/dist/lib/tools/open-github-issue.d.ts +2 -0
- package/dist/lib/tools/open-github-issue.js +750 -0
- package/dist/lib/tools/read-github-issue.d.ts +2 -0
- package/dist/lib/tools/read-github-issue.js +134 -0
- package/dist/lib/tools/render-error-book.d.ts +2 -0
- package/dist/lib/tools/render-error-book.js +265 -0
- package/dist/lib/tools/render-katex.d.ts +2 -0
- package/dist/lib/tools/render-katex.js +294 -0
- package/dist/lib/tools/review-threads.d.ts +2 -0
- package/dist/lib/tools/review-threads.js +491 -0
- package/dist/lib/tools/search-logs.d.ts +2 -0
- package/dist/lib/tools/search-logs.js +164 -0
- package/dist/lib/tools/sync-memory-index.d.ts +2 -0
- package/dist/lib/tools/sync-memory-index.js +113 -0
- package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
- package/dist/lib/tools/validate-openai-agent-config.js +190 -0
- package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
- package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
- package/dist/lib/types.d.ts +82 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/updater.d.ts +34 -0
- package/dist/lib/updater.js +112 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +6 -0
- package/dist/lib/utils/terminal.d.ts +12 -0
- package/dist/lib/utils/terminal.js +26 -0
- package/docs-to-voice/SKILL.md +0 -1
- package/generate-spec/SKILL.md +1 -1
- package/katex/SKILL.md +1 -2
- package/lib/cli.ts +780 -0
- package/lib/installer.ts +466 -0
- package/lib/tool-runner.ts +561 -0
- package/lib/tools/architecture.ts +20 -0
- package/lib/tools/create-specs.ts +204 -0
- package/lib/tools/docs-to-voice.ts +799 -0
- package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
- package/lib/tools/extract-conversations.ts +114 -0
- package/lib/tools/extract-pdf-text.ts +99 -0
- package/lib/tools/filter-logs.ts +118 -0
- package/lib/tools/find-github-issues.ts +211 -0
- package/lib/tools/generate-storyboard-images.ts +455 -0
- package/lib/tools/log-cli-utils.ts +262 -0
- package/lib/tools/open-github-issue.ts +930 -0
- package/lib/tools/read-github-issue.ts +179 -0
- package/lib/tools/render-error-book.ts +300 -0
- package/lib/tools/render-katex.ts +325 -0
- package/lib/tools/review-threads.ts +590 -0
- package/lib/tools/search-logs.ts +200 -0
- package/lib/tools/sync-memory-index.ts +114 -0
- package/lib/tools/validate-openai-agent-config.ts +213 -0
- package/lib/tools/validate-skill-frontmatter.ts +124 -0
- package/lib/types.ts +90 -0
- package/lib/updater.ts +165 -0
- package/lib/utils/format.ts +7 -0
- package/lib/utils/terminal.ts +22 -0
- package/open-github-issue/SKILL.md +2 -2
- package/optimise-skill/SKILL.md +1 -1
- package/package.json +13 -4
- package/resources/project-architecture/assets/architecture.css +764 -0
- package/resources/project-architecture/assets/viewer.client.js +144 -0
- package/resources/project-architecture/index.html +42 -0
- package/review-spec-related-changes/SKILL.md +1 -1
- package/solve-issues-found-during-review/SKILL.md +2 -1
- package/tsconfig.json +28 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
- package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
- package/analyse-app-logs/scripts/search_logs.py +0 -137
- package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
- package/analyse-app-logs/tests/test_search_logs.py +0 -100
- package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
- package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
- package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
- package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
- package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
- package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/scripts/create-specs +0 -215
- package/generate-spec/tests/test_create_specs.py +0 -200
- package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
- package/init-project-html/scripts/architecture.js +0 -296
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/scripts/render_katex.py +0 -247
- package/katex/scripts/render_katex.sh +0 -11
- package/katex/tests/test_render_katex.py +0 -174
- package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +0 -705
- package/open-github-issue/tests/test_open_github_issue.py +0 -381
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/find_issues.py +0 -148
- package/read-github-issue/scripts/read_issue.py +0 -108
- package/read-github-issue/tests/test_find_issues.py +0 -127
- package/read-github-issue/tests/test_read_issue.py +0 -109
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/review_threads.py +0 -425
- package/resolve-review-comments/tests/test_review_threads.py +0 -74
- package/scripts/validate_openai_agent_config.py +0 -209
- package/scripts/validate_skill_frontmatter.py +0 -131
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
- package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
|
@@ -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,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,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,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
|
+
}
|