@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,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findGitHubIssuesHandler = findGitHubIssuesHandler;
4
+ // @ts-nocheck
5
+ const node_child_process_1 = require("node:child_process");
6
+ const ISSUE_FIELDS = 'number,title,state,updatedAt,url,labels,assignees';
7
+ function parseArgs(argv) {
8
+ const args = {
9
+ repo: null,
10
+ state: 'open',
11
+ limit: 50,
12
+ label: [],
13
+ search: null,
14
+ output: 'table',
15
+ };
16
+ let i = 0;
17
+ while (i < argv.length) {
18
+ const arg = argv[i];
19
+ switch (arg) {
20
+ case '--repo':
21
+ if (i + 1 < argv.length)
22
+ args.repo = argv[++i];
23
+ break;
24
+ case '--state':
25
+ if (i + 1 < argv.length) {
26
+ const val = argv[++i];
27
+ if (['open', 'closed', 'all'].includes(val))
28
+ args.state = val;
29
+ }
30
+ break;
31
+ case '--limit':
32
+ if (i + 1 < argv.length) {
33
+ const n = parseInt(argv[++i], 10);
34
+ if (n > 0)
35
+ args.limit = n;
36
+ }
37
+ break;
38
+ case '--label':
39
+ if (i + 1 < argv.length)
40
+ args.label.push(argv[++i]);
41
+ break;
42
+ case '--search':
43
+ if (i + 1 < argv.length)
44
+ args.search = argv[++i];
45
+ break;
46
+ case '--output':
47
+ if (i + 1 < argv.length) {
48
+ const val = argv[++i];
49
+ if (val === 'table' || val === 'json')
50
+ args.output = val;
51
+ }
52
+ break;
53
+ default:
54
+ break;
55
+ }
56
+ i++;
57
+ }
58
+ return args;
59
+ }
60
+ function runGh(cmdArgs) {
61
+ return new Promise((resolve) => {
62
+ (0, node_child_process_1.execFile)('gh', cmdArgs, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
63
+ if (error) {
64
+ resolve({
65
+ stdout: stdout || '',
66
+ stderr: stderr || '',
67
+ exitCode: error.status ?? 1,
68
+ });
69
+ }
70
+ else {
71
+ resolve({ stdout: stdout || '', stderr: stderr || '', exitCode: 0 });
72
+ }
73
+ });
74
+ });
75
+ }
76
+ function buildCommand(args) {
77
+ const cmd = [
78
+ 'issue',
79
+ 'list',
80
+ '--state',
81
+ args.state,
82
+ '--limit',
83
+ String(args.limit),
84
+ '--json',
85
+ ISSUE_FIELDS,
86
+ ];
87
+ if (args.repo) {
88
+ cmd.push('--repo', args.repo);
89
+ }
90
+ for (const label of args.label) {
91
+ cmd.push('--label', label);
92
+ }
93
+ if (args.search) {
94
+ cmd.push('--search', args.search);
95
+ }
96
+ return cmd;
97
+ }
98
+ function truncate(text, width) {
99
+ if (text.length <= width)
100
+ return text;
101
+ if (width <= 3)
102
+ return text.slice(0, width);
103
+ return text.slice(0, width - 3) + '...';
104
+ }
105
+ function formatLabels(issue) {
106
+ const labels = issue.labels;
107
+ if (!labels)
108
+ return '';
109
+ const names = labels
110
+ .map((item) => String(item.name || ''))
111
+ .filter(Boolean);
112
+ return names.join(',');
113
+ }
114
+ function formatAssignees(issue) {
115
+ const assignees = issue.assignees;
116
+ if (!assignees)
117
+ return '-';
118
+ const logins = assignees
119
+ .map((item) => String(item.login || ''))
120
+ .filter(Boolean);
121
+ return logins.length > 0 ? logins.join(',') : '-';
122
+ }
123
+ function printTable(issues, context) {
124
+ const { stdout } = context;
125
+ const columns = {
126
+ number: 7,
127
+ title: 54,
128
+ labels: 22,
129
+ assignees: 18,
130
+ updated: 20,
131
+ };
132
+ const header = `${'NUMBER'.padEnd(columns.number)} ` +
133
+ `${'TITLE'.padEnd(columns.title)} ` +
134
+ `${'LABELS'.padEnd(columns.labels)} ` +
135
+ `${'ASSIGNEES'.padEnd(columns.assignees)} ` +
136
+ `${'UPDATED'.padEnd(columns.updated)}`;
137
+ stdout.write(header + '\n');
138
+ stdout.write('-'.repeat(header.length) + '\n');
139
+ for (const issue of issues) {
140
+ const number = `#${issue.number ?? ''}`;
141
+ const title = truncate(String(issue.title ?? ''), columns.title);
142
+ const labels = truncate(formatLabels(issue), columns.labels);
143
+ const assignees = truncate(formatAssignees(issue), columns.assignees);
144
+ const updated = truncate(String(issue.updatedAt ?? ''), columns.updated);
145
+ const row = `${number.padEnd(columns.number)} ` +
146
+ `${title.padEnd(columns.title)} ` +
147
+ `${labels.padEnd(columns.labels)} ` +
148
+ `${assignees.padEnd(columns.assignees)} ` +
149
+ `${updated.padEnd(columns.updated)}`;
150
+ stdout.write(row + '\n');
151
+ }
152
+ }
153
+ async function findGitHubIssuesHandler(argv, context) {
154
+ const { stdout, stderr } = context;
155
+ const args = parseArgs(argv);
156
+ const cmd = buildCommand(args);
157
+ const result = await runGh(cmd);
158
+ if (result.exitCode !== 0) {
159
+ stderr.write(result.stderr.trim() || 'gh issue list failed.\n');
160
+ return result.exitCode;
161
+ }
162
+ let issues;
163
+ try {
164
+ issues = JSON.parse(result.stdout);
165
+ }
166
+ catch {
167
+ stderr.write('Error: unable to parse gh output as JSON.\n');
168
+ return 1;
169
+ }
170
+ if (args.output === 'json') {
171
+ stdout.write(JSON.stringify(issues, null, 2) + '\n');
172
+ return 0;
173
+ }
174
+ printTable(issues, context);
175
+ return 0;
176
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function generateStoryboardImagesHandler(args: string[], context: ToolContext): Promise<number>;
@@ -0,0 +1,419 @@
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.generateStoryboardImagesHandler = generateStoryboardImagesHandler;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_https_1 = __importDefault(require("node:https"));
10
+ const node_http_1 = __importDefault(require("node:http"));
11
+ function parseArgs(args) {
12
+ const parsed = {
13
+ contentName: null,
14
+ projectDir: '.',
15
+ envFile: null,
16
+ apiUrl: null,
17
+ apiKey: null,
18
+ promptsFile: null,
19
+ prompts: [],
20
+ imageModel: null,
21
+ aspectRatio: null,
22
+ imageSize: null,
23
+ quality: null,
24
+ style: null,
25
+ help: false,
26
+ };
27
+ for (let i = 0; i < args.length; i++) {
28
+ const arg = args[i];
29
+ if (arg === '--help' || arg === '-h') {
30
+ parsed.help = true;
31
+ continue;
32
+ }
33
+ if (arg.startsWith('--')) {
34
+ const eqIndex = arg.indexOf('=');
35
+ let key;
36
+ let value;
37
+ if (eqIndex !== -1) {
38
+ key = arg.slice(2, eqIndex);
39
+ value = arg.slice(eqIndex + 1);
40
+ }
41
+ else {
42
+ key = arg.slice(2);
43
+ if (key === 'prompt') {
44
+ // --prompt can be multiple
45
+ value = args[++i] || '';
46
+ parsed.prompts.push(value);
47
+ continue;
48
+ }
49
+ value = args[++i] || '';
50
+ }
51
+ switch (key) {
52
+ case 'input':
53
+ case 'content-name':
54
+ parsed.contentName = value;
55
+ break;
56
+ case 'project-dir':
57
+ parsed.projectDir = value;
58
+ break;
59
+ case 'env-file':
60
+ parsed.envFile = value;
61
+ break;
62
+ case 'api-url':
63
+ parsed.apiUrl = value;
64
+ break;
65
+ case 'api-key':
66
+ parsed.apiKey = value;
67
+ break;
68
+ case 'prompts-file':
69
+ parsed.promptsFile = value;
70
+ break;
71
+ case 'image-model':
72
+ parsed.imageModel = value;
73
+ break;
74
+ case 'aspect-ratio':
75
+ parsed.aspectRatio = value;
76
+ break;
77
+ case 'image-size':
78
+ case 'size':
79
+ parsed.imageSize = value;
80
+ break;
81
+ case 'quality':
82
+ parsed.quality = value;
83
+ break;
84
+ case 'style':
85
+ parsed.style = value;
86
+ break;
87
+ }
88
+ }
89
+ else if (!parsed.contentName && !arg.startsWith('-')) {
90
+ // positional: content name
91
+ parsed.contentName = arg;
92
+ }
93
+ }
94
+ return parsed;
95
+ }
96
+ function sanitizeComponent(name, fallback) {
97
+ return name
98
+ .replace(/[\\/:*?"<>|]+/g, '_')
99
+ .replace(/\s+/g, '_')
100
+ .replace(/_+/g, '_')
101
+ .replace(/^[._]|[._]$/g, '') || fallback;
102
+ }
103
+ function uniquePath(filePath) {
104
+ if (!node_fs_1.default.existsSync(filePath))
105
+ return filePath;
106
+ const dir = node_path_1.default.dirname(filePath);
107
+ const ext = node_path_1.default.extname(filePath);
108
+ const base = node_path_1.default.basename(filePath, ext);
109
+ let index = 2;
110
+ while (node_fs_1.default.existsSync(node_path_1.default.join(dir, `${base}_${index}${ext}`))) {
111
+ index++;
112
+ }
113
+ return node_path_1.default.join(dir, `${base}_${index}${ext}`);
114
+ }
115
+ function postJson(baseUrl, endpoint, apiKey, payload) {
116
+ return new Promise((resolve, reject) => {
117
+ const url = `${baseUrl.replace(/\/+$/, '')}${endpoint}`;
118
+ const urlObj = new URL(url);
119
+ const body = JSON.stringify(payload);
120
+ const client = urlObj.protocol === 'https:' ? node_https_1.default : node_http_1.default;
121
+ const options = {
122
+ hostname: urlObj.hostname,
123
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
124
+ path: urlObj.pathname + urlObj.search,
125
+ method: 'POST',
126
+ headers: {
127
+ 'Authorization': `Bearer ${apiKey}`,
128
+ 'Content-Type': 'application/json',
129
+ },
130
+ timeout: 180000,
131
+ };
132
+ const req = client.request(options, (res) => {
133
+ const chunks = [];
134
+ res.on('data', (chunk) => chunks.push(chunk));
135
+ res.on('end', () => {
136
+ const raw = Buffer.concat(chunks).toString('utf-8');
137
+ try {
138
+ resolve(JSON.parse(raw));
139
+ }
140
+ catch {
141
+ reject(new Error(`Invalid JSON response: ${raw.slice(0, 200)}`));
142
+ }
143
+ });
144
+ res.on('error', reject);
145
+ });
146
+ req.on('error', reject);
147
+ req.on('timeout', () => { req.destroy(); reject(new Error('API request timed out')); });
148
+ req.write(body);
149
+ req.end();
150
+ });
151
+ }
152
+ function fetchBinary(url) {
153
+ return new Promise((resolve, reject) => {
154
+ const urlObj = new URL(url);
155
+ const client = urlObj.protocol === 'https:' ? node_https_1.default : node_http_1.default;
156
+ client.get(url, { timeout: 180000 }, (res) => {
157
+ const chunks = [];
158
+ res.on('data', (chunk) => chunks.push(chunk));
159
+ res.on('end', () => resolve(Buffer.concat(chunks)));
160
+ res.on('error', reject);
161
+ }).on('error', reject);
162
+ });
163
+ }
164
+ function parsePromptEntries(raw) {
165
+ const items = [];
166
+ for (let i = 0; i < raw.length; i++) {
167
+ const item = raw[i];
168
+ if (typeof item === 'string') {
169
+ const prompt = item.trim();
170
+ if (!prompt)
171
+ throw new Error(`Empty prompt at index ${i}`);
172
+ items.push({ title: `scene-${i + 1}`, prompt });
173
+ }
174
+ else if (item && typeof item === 'object') {
175
+ const obj = item;
176
+ const prompt = String(obj.prompt || '').trim();
177
+ const title = String(obj.title || `scene-${i + 1}`).trim() || `scene-${i + 1}`;
178
+ if (!prompt)
179
+ throw new Error(`Empty prompt in object at index ${i}`);
180
+ items.push({ title, prompt });
181
+ }
182
+ else {
183
+ throw new Error(`Invalid item type at index ${i}: expected string or object`);
184
+ }
185
+ }
186
+ if (items.length === 0)
187
+ throw new Error('No prompts found.');
188
+ return items;
189
+ }
190
+ function parsePromptsFile(filePath) {
191
+ const raw = JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf-8'));
192
+ if (Array.isArray(raw)) {
193
+ return parsePromptEntries(raw);
194
+ }
195
+ if (raw && typeof raw === 'object') {
196
+ const scenes = raw.scenes;
197
+ if (!Array.isArray(scenes) || scenes.length === 0) {
198
+ throw new Error('Object mode requires a top-level "scenes" array.');
199
+ }
200
+ const characters = {};
201
+ if (Array.isArray(raw.characters)) {
202
+ for (const char of raw.characters) {
203
+ if (!char || typeof char !== 'object')
204
+ continue;
205
+ const c = char;
206
+ const id = String(c.id || '').trim();
207
+ if (id) {
208
+ characters[id] = {
209
+ name: String(c.name || ''),
210
+ appearance: String(c.appearance || ''),
211
+ outfit: String(c.outfit || ''),
212
+ description: String(c.description || ''),
213
+ };
214
+ }
215
+ }
216
+ }
217
+ const items = [];
218
+ for (let si = 0; si < scenes.length; si++) {
219
+ const scene = scenes[si];
220
+ if (!scene || typeof scene !== 'object') {
221
+ throw new Error(`Invalid scene at index ${si}: expected object.`);
222
+ }
223
+ const title = String(scene.title || `scene-${si + 1}`).trim() || `scene-${si + 1}`;
224
+ const description = String(scene.description || '').trim();
225
+ if (!description)
226
+ throw new Error(`Scene ${si}: 'description' is required.`);
227
+ let promptPayload = {
228
+ scene_title: title,
229
+ description,
230
+ };
231
+ const characterIds = [];
232
+ if (Array.isArray(scene.character_ids)) {
233
+ for (const cid of scene.character_ids) {
234
+ characterIds.push(String(cid).trim());
235
+ }
236
+ const sceneChars = characterIds
237
+ .map((cid) => characters[cid])
238
+ .filter(Boolean);
239
+ if (sceneChars.length > 0) {
240
+ promptPayload.characters = sceneChars;
241
+ }
242
+ }
243
+ if (scene.style)
244
+ promptPayload.style = String(scene.style);
245
+ if (scene.camera)
246
+ promptPayload.camera = String(scene.camera);
247
+ if (scene.lighting)
248
+ promptPayload.lighting = String(scene.lighting);
249
+ items.push({
250
+ title,
251
+ prompt: JSON.stringify(promptPayload, undefined, 0),
252
+ });
253
+ }
254
+ return items;
255
+ }
256
+ throw new Error('Top-level JSON must be an array or an object.');
257
+ }
258
+ async function generateStoryboardImagesHandler(args, context) {
259
+ const stdout = context.stdout || process.stdout;
260
+ const stderr = context.stderr || process.stderr;
261
+ try {
262
+ const opts = parseArgs(args);
263
+ if (opts.help || (!opts.contentName)) {
264
+ stdout.write(`Usage: apltk generate-storyboard-images --input <name> [options]
265
+
266
+ Generate storyboard images from prompts via OpenAI-compatible API.
267
+
268
+ Options:
269
+ --input, --content-name <name> Output subfolder name under pictures/
270
+ --project-dir <path> Project root (default: .)
271
+ --env-file <path> Path to .env file
272
+ --api-url <url> API base URL for /images/generations
273
+ --api-key <key> API key
274
+ --prompts-file <path> JSON file with prompt entries
275
+ --prompt <text> Image prompt (repeatable)
276
+ --image-model <model> Image model (default: gpt-image-1)
277
+ --aspect-ratio <ratio> Aspect ratio, e.g. 16:9
278
+ --image-size <size> Image size, e.g. 1024x768
279
+ --quality <q> Image quality
280
+ --style <style> Image style
281
+
282
+ Either --prompts-file or at least one --prompt is required.
283
+ `);
284
+ return opts.contentName ? 1 : 0;
285
+ }
286
+ const projectDir = node_path_1.default.resolve(opts.projectDir);
287
+ const contentName = opts.contentName;
288
+ // Resolve API config
289
+ const sourceRoot = context.sourceRoot || node_path_1.default.resolve(__dirname, '..', '..');
290
+ // Try loading env file
291
+ const envFilePath = opts.envFile
292
+ ? node_path_1.default.resolve(opts.envFile)
293
+ : node_path_1.default.join(sourceRoot, 'openai-text-to-image-storyboard', '.env');
294
+ if (node_fs_1.default.existsSync(envFilePath)) {
295
+ const envContent = node_fs_1.default.readFileSync(envFilePath, 'utf-8');
296
+ for (const line of envContent.split('\n')) {
297
+ const trimmed = line.trim();
298
+ if (!trimmed || trimmed.startsWith('#'))
299
+ continue;
300
+ const eqIndex = trimmed.indexOf('=');
301
+ if (eqIndex === -1)
302
+ continue;
303
+ let key = trimmed.slice(0, eqIndex).trim();
304
+ let val = trimmed.slice(eqIndex + 1).trim();
305
+ if (key.startsWith('export '))
306
+ key = key.slice(7).trim();
307
+ // Strip quotes
308
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
309
+ val = val.slice(1, -1);
310
+ }
311
+ if (!process.env[key]) {
312
+ process.env[key] = val;
313
+ }
314
+ }
315
+ }
316
+ const apiUrl = opts.apiUrl || process.env.OPENAI_API_URL || '';
317
+ const apiKey = opts.apiKey || process.env.OPENAI_API_KEY || '';
318
+ const imageModel = opts.imageModel || process.env.OPENAI_IMAGE_MODEL || 'gpt-image-1';
319
+ const aspectRatio = opts.aspectRatio || process.env.OPENAI_IMAGE_RATIO || process.env.OPENAI_IMAGE_ASPECT_RATIO || null;
320
+ const imageSize = opts.imageSize || process.env.OPENAI_IMAGE_SIZE || null;
321
+ const quality = opts.quality || process.env.OPENAI_IMAGE_QUALITY || null;
322
+ const style = opts.style || process.env.OPENAI_IMAGE_STYLE || null;
323
+ if (!apiUrl) {
324
+ stderr.write('Error: Missing API URL. Set --api-url or OPENAI_API_URL.\n');
325
+ return 1;
326
+ }
327
+ if (!apiKey) {
328
+ stderr.write('Error: Missing API key. Set --api-key or OPENAI_API_KEY.\n');
329
+ return 1;
330
+ }
331
+ // Build prompt items
332
+ let promptItems;
333
+ if (opts.promptsFile) {
334
+ promptItems = parsePromptsFile(node_path_1.default.resolve(opts.promptsFile));
335
+ }
336
+ else if (opts.prompts.length > 0) {
337
+ promptItems = opts.prompts.map((p, i) => ({ title: `scene-${i + 1}`, prompt: p.trim() }));
338
+ }
339
+ else {
340
+ stderr.write('Error: Either --prompts-file or at least one --prompt is required.\n');
341
+ return 1;
342
+ }
343
+ if (promptItems.length === 0) {
344
+ stderr.write('Error: No prompts provided.\n');
345
+ return 1;
346
+ }
347
+ const outputDir = node_path_1.default.join(projectDir, 'pictures', sanitizeComponent(contentName, 'untitled-content'));
348
+ node_fs_1.default.mkdirSync(outputDir, { recursive: true });
349
+ const records = [];
350
+ for (let i = 0; i < promptItems.length; i++) {
351
+ const item = promptItems[i];
352
+ const titleSlug = sanitizeComponent(item.title, `scene-${i + 1}`);
353
+ const imagePath = uniquePath(node_path_1.default.join(outputDir, `${String(i + 1).padStart(2, '0')}_${titleSlug}.png`));
354
+ const payload = {
355
+ model: imageModel,
356
+ prompt: item.prompt,
357
+ };
358
+ if (aspectRatio)
359
+ payload.aspect_ratio = aspectRatio;
360
+ if (imageSize)
361
+ payload.size = imageSize;
362
+ if (quality)
363
+ payload.quality = quality;
364
+ if (style)
365
+ payload.style = style;
366
+ const response = await postJson(apiUrl, '/images/generations', apiKey, payload);
367
+ const data = response.data;
368
+ if (!Array.isArray(data) || data.length === 0) {
369
+ stderr.write(`Error: No image data returned for prompt ${i + 1}.\n`);
370
+ continue;
371
+ }
372
+ const first = data[0];
373
+ let imageBytes;
374
+ if (typeof first.b64_json === 'string') {
375
+ imageBytes = Buffer.from(first.b64_json, 'base64');
376
+ }
377
+ else if (typeof first.url === 'string') {
378
+ imageBytes = await fetchBinary(first.url);
379
+ }
380
+ else {
381
+ stderr.write(`Error: Image payload missing b64_json/url for prompt ${i + 1}.\n`);
382
+ continue;
383
+ }
384
+ node_fs_1.default.writeFileSync(imagePath, imageBytes);
385
+ const record = {
386
+ index: i + 1,
387
+ title: item.title,
388
+ prompt: item.prompt,
389
+ file: imagePath,
390
+ };
391
+ if (typeof first.revised_prompt === 'string') {
392
+ record.revised_prompt = first.revised_prompt;
393
+ }
394
+ records.push(record);
395
+ stdout.write(`[OK] Generated ${imagePath}\n`);
396
+ }
397
+ // Write summary
398
+ const summary = {
399
+ content_name: contentName,
400
+ project_dir: projectDir,
401
+ output_dir: outputDir,
402
+ image_model: imageModel,
403
+ images: records,
404
+ };
405
+ if (aspectRatio)
406
+ summary.aspect_ratio = aspectRatio;
407
+ if (imageSize)
408
+ summary.image_size = imageSize;
409
+ const summaryPath = node_path_1.default.join(outputDir, 'storyboard.json');
410
+ node_fs_1.default.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf-8');
411
+ stdout.write(`[OK] Wrote plan to ${summaryPath}\n`);
412
+ return 0;
413
+ }
414
+ catch (err) {
415
+ const msg = err instanceof Error ? err.message : 'Unknown error';
416
+ stderr.write(`Error: ${msg}\n`);
417
+ return 1;
418
+ }
419
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Normalize a raw timestamp string: strip whitespace, replace comma with dot,
3
+ * and convert trailing Z to +00:00.
4
+ */
5
+ export declare function normalizeTimestamp(raw: string): string;
6
+ /**
7
+ * Parse a CLI-provided timestamp string into a Date.
8
+ * Naive timestamps are assigned the assumeTimezone offset.
9
+ */
10
+ export declare function parseCliTimestamp(raw: string, assumeTimezone: string): Date;
11
+ /**
12
+ * Extract an ISO 8601 timestamp from a log line and parse it into a Date.
13
+ * Returns null if no timestamp is found or parsing fails.
14
+ */
15
+ export declare function extractTimestamp(line: string, assumeTimezone: string): Date | null;
16
+ /**
17
+ * Build a timezone offset in minutes from a timezone string.
18
+ * Supports "UTC" and "±HH:MM" formats.
19
+ */
20
+ export declare function buildTimezone(raw: string): number;
21
+ /**
22
+ * Validate that start is not later than end.
23
+ * Writes an error message to stderr and returns false if the order is inverted.
24
+ */
25
+ export declare function validateTimeWindow(start: Date | null, end: Date | null, stderr: NodeJS.WriteStream): boolean;
26
+ /**
27
+ * Check whether a timestamp falls within a given [start, end] window (inclusive).
28
+ * Returns false if timestamp is null.
29
+ */
30
+ export declare function inWindow(timestamp: Date | null, start: Date | null, end: Date | null): boolean;
31
+ /**
32
+ * Iterate over lines from file paths or stdin.
33
+ * When paths is empty or contains "-", reads from stdin.
34
+ */
35
+ export declare function iterInputLines(paths: string[]): AsyncGenerator<string>;