@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.
Files changed (154) hide show
  1. package/AGENTS.md +7 -7
  2. package/CHANGELOG.md +36 -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 +23 -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 +190 -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 +20 -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 +213 -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,491 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reviewThreadsHandler = reviewThreadsHandler;
4
+ // @ts-nocheck
5
+ const node_child_process_1 = require("node:child_process");
6
+ const node_fs_1 = require("node:fs");
7
+ const LIST_QUERY = `
8
+ query($owner: String!, $name: String!, $number: Int!, $after: String) {
9
+ repository(owner: $owner, name: $name) {
10
+ pullRequest(number: $number) {
11
+ reviewThreads(first: 100, after: $after) {
12
+ nodes {
13
+ id
14
+ isResolved
15
+ isOutdated
16
+ path
17
+ line
18
+ startLine
19
+ comments(first: 20) {
20
+ nodes {
21
+ id
22
+ url
23
+ body
24
+ author {
25
+ login
26
+ }
27
+ createdAt
28
+ path
29
+ line
30
+ outdated
31
+ }
32
+ }
33
+ }
34
+ pageInfo {
35
+ hasNextPage
36
+ endCursor
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ `;
43
+ const RESOLVE_MUTATION = `
44
+ mutation($threadId: ID!) {
45
+ resolveReviewThread(input: {threadId: $threadId}) {
46
+ thread {
47
+ id
48
+ isResolved
49
+ }
50
+ }
51
+ }
52
+ `;
53
+ function parseArgs(argv) {
54
+ const args = {
55
+ command: '',
56
+ repo: null,
57
+ pr: null,
58
+ state: 'unresolved',
59
+ output: 'table',
60
+ threadId: [],
61
+ threadIdFile: null,
62
+ allUnresolved: false,
63
+ dryRun: false,
64
+ };
65
+ // First argument is the subcommand (list/resolve)
66
+ let i = 0;
67
+ if (i < argv.length && !argv[i].startsWith('-')) {
68
+ args.command = argv[i++];
69
+ }
70
+ while (i < argv.length) {
71
+ const arg = argv[i];
72
+ switch (arg) {
73
+ case '--repo':
74
+ if (i + 1 < argv.length)
75
+ args.repo = argv[++i];
76
+ break;
77
+ case '--pr':
78
+ if (i + 1 < argv.length) {
79
+ const n = parseInt(argv[++i], 10);
80
+ if (n > 0)
81
+ args.pr = n;
82
+ }
83
+ break;
84
+ case '--state':
85
+ if (i + 1 < argv.length) {
86
+ const val = argv[++i];
87
+ if (['unresolved', 'resolved', 'all'].includes(val))
88
+ args.state = val;
89
+ }
90
+ break;
91
+ case '--output':
92
+ if (i + 1 < argv.length) {
93
+ const val = argv[++i];
94
+ if (val === 'table' || val === 'json')
95
+ args.output = val;
96
+ }
97
+ break;
98
+ case '--thread-id':
99
+ if (i + 1 < argv.length)
100
+ args.threadId.push(argv[++i]);
101
+ break;
102
+ case '--thread-id-file':
103
+ if (i + 1 < argv.length)
104
+ args.threadIdFile = argv[++i];
105
+ break;
106
+ case '--all-unresolved':
107
+ args.allUnresolved = true;
108
+ break;
109
+ case '--dry-run':
110
+ args.dryRun = true;
111
+ break;
112
+ default:
113
+ break;
114
+ }
115
+ i++;
116
+ }
117
+ return args;
118
+ }
119
+ function runGh(cmdArgs) {
120
+ return new Promise((resolve) => {
121
+ (0, node_child_process_1.execFile)('gh', cmdArgs, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
122
+ if (error) {
123
+ resolve({
124
+ stdout: stdout || '',
125
+ stderr: stderr || '',
126
+ exitCode: error.status ?? 1,
127
+ });
128
+ }
129
+ else {
130
+ resolve({ stdout: stdout || '', stderr: stderr || '', exitCode: 0 });
131
+ }
132
+ });
133
+ });
134
+ }
135
+ function runGhJson(cmdArgs) {
136
+ return runGh(cmdArgs).then((result) => {
137
+ if (result.exitCode !== 0) {
138
+ throw new Error(result.stderr.trim() || 'gh command failed');
139
+ }
140
+ try {
141
+ return JSON.parse(result.stdout);
142
+ }
143
+ catch (exc) {
144
+ throw new Error('Failed to parse gh JSON output');
145
+ }
146
+ });
147
+ }
148
+ function parseOwnerRepo(repo) {
149
+ const parts = repo.split('/');
150
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
151
+ throw new Error('repo must be in owner/name format');
152
+ }
153
+ return [parts[0], parts[1]];
154
+ }
155
+ async function resolveRepo(repo) {
156
+ if (repo) {
157
+ parseOwnerRepo(repo);
158
+ return repo;
159
+ }
160
+ const result = await runGh([
161
+ 'repo',
162
+ 'view',
163
+ '--json',
164
+ 'nameWithOwner',
165
+ '--jq',
166
+ '.nameWithOwner',
167
+ ]);
168
+ if (result.exitCode !== 0) {
169
+ throw new Error(result.stderr.trim() || 'Unable to resolve current repo');
170
+ }
171
+ return result.stdout.trim();
172
+ }
173
+ async function resolvePrNumber(repo, pr) {
174
+ if (pr !== null)
175
+ return pr;
176
+ const result = await runGh([
177
+ 'pr',
178
+ 'view',
179
+ '--repo',
180
+ repo,
181
+ '--json',
182
+ 'number',
183
+ '--jq',
184
+ '.number',
185
+ ]);
186
+ if (result.exitCode !== 0) {
187
+ throw new Error('Unable to infer PR number from current branch context');
188
+ }
189
+ return parseInt(result.stdout.trim(), 10);
190
+ }
191
+ function ghGraphql(query, variables) {
192
+ const cmdArgs = ['api', 'graphql', '-f', `query=${query}`];
193
+ for (const [key, value] of Object.entries(variables)) {
194
+ cmdArgs.push('-F', `${key}=${JSON.stringify(value)}`);
195
+ }
196
+ return runGhJson(cmdArgs);
197
+ }
198
+ // ---- Thread fetching ----
199
+ async function fetchReviewThreads(repo, prNumber) {
200
+ const [owner, name] = parseOwnerRepo(repo);
201
+ const threads = [];
202
+ let after = null;
203
+ while (true) {
204
+ const payload = await ghGraphql(LIST_QUERY, {
205
+ owner,
206
+ name,
207
+ number: prNumber,
208
+ after,
209
+ });
210
+ const pr = payload.data?.repository;
211
+ if (!pr) {
212
+ throw new Error(`PR #${prNumber} not found in ${repo}`);
213
+ }
214
+ const reviewThreads = pr.reviewThreads;
215
+ const nodes = reviewThreads.nodes || [];
216
+ threads.push(...nodes);
217
+ const pageInfo = reviewThreads.pageInfo;
218
+ if (!pageInfo.hasNextPage)
219
+ break;
220
+ after = pageInfo.endCursor || null;
221
+ if (!after)
222
+ break;
223
+ }
224
+ return threads;
225
+ }
226
+ function filterThreads(threads, state) {
227
+ if (state === 'all')
228
+ return threads;
229
+ if (state === 'resolved') {
230
+ return threads.filter((item) => item.isResolved);
231
+ }
232
+ return threads.filter((item) => !item.isResolved);
233
+ }
234
+ function normalizeThread(thread) {
235
+ const commentNodes = thread.comments?.nodes;
236
+ const normalizedComments = (commentNodes || []).map((comment) => ({
237
+ id: comment.id,
238
+ url: comment.url,
239
+ author: comment.author?.login || null,
240
+ body: comment.body || '',
241
+ created_at: comment.createdAt,
242
+ path: comment.path,
243
+ line: comment.line,
244
+ outdated: comment.outdated,
245
+ }));
246
+ return {
247
+ thread_id: thread.id,
248
+ is_resolved: thread.isResolved,
249
+ is_outdated: thread.isOutdated,
250
+ path: thread.path,
251
+ line: thread.line,
252
+ start_line: thread.startLine,
253
+ comments: normalizedComments,
254
+ };
255
+ }
256
+ function truncate(text, width) {
257
+ if (text.length <= width)
258
+ return text;
259
+ if (width <= 3)
260
+ return text.slice(0, width);
261
+ return text.slice(0, width - 3) + '...';
262
+ }
263
+ function previewBody(thread) {
264
+ const comments = thread.comments;
265
+ if (!comments || comments.length === 0)
266
+ return '-';
267
+ const body = (comments[0].body || '').replace(/\n/g, ' ').trim();
268
+ return truncate(body || '-', 72);
269
+ }
270
+ function renderLocation(thread) {
271
+ const path = thread.path || '-';
272
+ const line = thread.line;
273
+ if (line == null)
274
+ return path;
275
+ return `${path}:${line}`;
276
+ }
277
+ function printTable(threads, context) {
278
+ const { stdout } = context;
279
+ const widths = {
280
+ idx: 4,
281
+ thread: 12,
282
+ location: 36,
283
+ author: 18,
284
+ preview: 72,
285
+ };
286
+ const header = `${'#'.padEnd(widths.idx)} ` +
287
+ `${'THREAD_ID'.padEnd(widths.thread)} ` +
288
+ `${'LOCATION'.padEnd(widths.location)} ` +
289
+ `${'AUTHOR'.padEnd(widths.author)} ` +
290
+ `${'COMMENT_PREVIEW'.padEnd(widths.preview)}`;
291
+ stdout.write(header + '\n');
292
+ stdout.write('-'.repeat(header.length) + '\n');
293
+ for (let idx = 0; idx < threads.length; idx++) {
294
+ const thread = threads[idx];
295
+ const comments = thread.comments;
296
+ const author = comments?.[0]?.author ?? '-';
297
+ const row = `${String(idx + 1).padEnd(widths.idx)} ` +
298
+ `${truncate(String(thread.thread_id ?? '-'), widths.thread).padEnd(widths.thread)} ` +
299
+ `${truncate(renderLocation(thread), widths.location).padEnd(widths.location)} ` +
300
+ `${truncate(String(author ?? '-'), widths.author).padEnd(widths.author)} ` +
301
+ `${previewBody(thread).padEnd(widths.preview)}`;
302
+ stdout.write(row + '\n');
303
+ }
304
+ }
305
+ // ---- Thread ID loading ----
306
+ function loadThreadIds(filePath) {
307
+ const raw = (0, node_fs_1.readFileSync)(filePath, 'utf-8');
308
+ const payload = JSON.parse(raw);
309
+ let ids;
310
+ if (Array.isArray(payload)) {
311
+ ids = payload;
312
+ }
313
+ else if (typeof payload === 'object' && payload !== null) {
314
+ const p = payload;
315
+ if (Array.isArray(p.thread_ids)) {
316
+ ids = p.thread_ids;
317
+ }
318
+ else if (Array.isArray(p.adopted_thread_ids)) {
319
+ ids = p.adopted_thread_ids;
320
+ }
321
+ else if (Array.isArray(p.threads)) {
322
+ ids = p.threads
323
+ .filter((item) => typeof item === 'object' && item !== null)
324
+ .map((item) => item.thread_id)
325
+ .filter((id) => id !== undefined);
326
+ }
327
+ else {
328
+ throw new Error('JSON must include thread_ids, adopted_thread_ids, or threads');
329
+ }
330
+ }
331
+ else {
332
+ throw new Error('Unsupported JSON payload for thread IDs');
333
+ }
334
+ const output = ids
335
+ .filter((item) => typeof item === 'string' && item.trim().length > 0)
336
+ .map((item) => item.trim());
337
+ return [...new Set(output)];
338
+ }
339
+ function collectThreadIds(args, unresolvedThreads) {
340
+ const ids = [];
341
+ if (args.allUnresolved) {
342
+ for (const item of unresolvedThreads) {
343
+ if (item.thread_id) {
344
+ ids.push(item.thread_id);
345
+ }
346
+ }
347
+ }
348
+ ids.push(...args.threadId);
349
+ if (args.threadIdFile) {
350
+ ids.push(...loadThreadIds(args.threadIdFile));
351
+ }
352
+ const normalized = ids.filter(Boolean);
353
+ return [...new Set(normalized)];
354
+ }
355
+ async function resolveThreads(threadIds, dryRun) {
356
+ const resolved = [];
357
+ const failed = [];
358
+ for (const threadId of threadIds) {
359
+ if (dryRun) {
360
+ resolved.push(threadId);
361
+ continue;
362
+ }
363
+ try {
364
+ const payload = await ghGraphql(RESOLVE_MUTATION, { threadId });
365
+ const thread = payload.data?.resolveReviewThread;
366
+ if (!thread?.thread) {
367
+ throw new Error('thread did not resolve');
368
+ }
369
+ const resolvedThread = thread.thread;
370
+ if (!resolvedThread.isResolved) {
371
+ throw new Error('thread did not resolve');
372
+ }
373
+ resolved.push(threadId);
374
+ }
375
+ catch (exc) {
376
+ failed.push({ thread_id: threadId, error: exc.message });
377
+ }
378
+ }
379
+ return { resolved, failed };
380
+ }
381
+ // ---- Subcommands ----
382
+ async function cmdList(args, context) {
383
+ const { stdout, stderr } = context;
384
+ let repo;
385
+ try {
386
+ repo = await resolveRepo(args.repo);
387
+ }
388
+ catch (err) {
389
+ stderr.write(`Error: ${err.message}\n`);
390
+ return 1;
391
+ }
392
+ let prNumber;
393
+ try {
394
+ prNumber = await resolvePrNumber(repo, args.pr);
395
+ }
396
+ catch (err) {
397
+ stderr.write(`Error: ${err.message}\n`);
398
+ return 1;
399
+ }
400
+ let threads;
401
+ try {
402
+ threads = await fetchReviewThreads(repo, prNumber);
403
+ }
404
+ catch (err) {
405
+ stderr.write(`Error: ${err.message}\n`);
406
+ return 1;
407
+ }
408
+ const filtered = filterThreads(threads, args.state);
409
+ const normalized = filtered.map(normalizeThread);
410
+ const result = {
411
+ repo,
412
+ pr_number: prNumber,
413
+ state: args.state,
414
+ thread_count: normalized.length,
415
+ threads: normalized,
416
+ };
417
+ if (args.output === 'json') {
418
+ stdout.write(JSON.stringify(result, null, 2) + '\n');
419
+ }
420
+ else {
421
+ stdout.write(`Repository: ${repo}\n`);
422
+ stdout.write(`PR: #${prNumber}\n`);
423
+ stdout.write(`Threads (${args.state}): ${normalized.length}\n`);
424
+ printTable(normalized, context);
425
+ }
426
+ return 0;
427
+ }
428
+ async function cmdResolve(args, context) {
429
+ const { stdout, stderr } = context;
430
+ let repo;
431
+ try {
432
+ repo = await resolveRepo(args.repo);
433
+ }
434
+ catch (err) {
435
+ stderr.write(`Error: ${err.message}\n`);
436
+ return 1;
437
+ }
438
+ let prNumber;
439
+ try {
440
+ prNumber = await resolvePrNumber(repo, args.pr);
441
+ }
442
+ catch (err) {
443
+ stderr.write(`Error: ${err.message}\n`);
444
+ return 1;
445
+ }
446
+ let threads;
447
+ try {
448
+ threads = await fetchReviewThreads(repo, prNumber);
449
+ }
450
+ catch (err) {
451
+ stderr.write(`Error: ${err.message}\n`);
452
+ return 1;
453
+ }
454
+ const unresolved = filterThreads(threads, 'unresolved').map(normalizeThread);
455
+ const threadIds = collectThreadIds(args, unresolved);
456
+ if (threadIds.length === 0) {
457
+ stderr.write('Error: no thread IDs selected. Use --thread-id, --thread-id-file, or --all-unresolved.\n');
458
+ return 1;
459
+ }
460
+ const { resolved, failed } = await resolveThreads(threadIds, args.dryRun);
461
+ const summary = {
462
+ repo,
463
+ pr_number: prNumber,
464
+ requested: threadIds,
465
+ resolved,
466
+ failed,
467
+ dry_run: args.dryRun,
468
+ };
469
+ stdout.write(JSON.stringify(summary, null, 2) + '\n');
470
+ return failed.length > 0 ? 1 : 0;
471
+ }
472
+ // ---- Main handler ----
473
+ async function reviewThreadsHandler(argv, context) {
474
+ const { stderr } = context;
475
+ const args = parseArgs(argv);
476
+ try {
477
+ switch (args.command) {
478
+ case 'list':
479
+ return await cmdList(args, context);
480
+ case 'resolve':
481
+ return await cmdResolve(args, context);
482
+ default:
483
+ stderr.write(`Unsupported command: ${args.command}\n`);
484
+ return 1;
485
+ }
486
+ }
487
+ catch (err) {
488
+ stderr.write(`Error: ${err.message}\n`);
489
+ return 1;
490
+ }
491
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function searchLogsHandler(argv: string[], context: ToolContext): Promise<number>;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.searchLogsHandler = searchLogsHandler;
4
+ const log_cli_utils_1 = require("./log-cli-utils");
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ paths: [],
8
+ keyword: [],
9
+ regex: [],
10
+ mode: 'any',
11
+ ignoreCase: false,
12
+ start: null,
13
+ end: null,
14
+ assumeTimezone: 'UTC',
15
+ beforeContext: 0,
16
+ afterContext: 0,
17
+ countOnly: false,
18
+ };
19
+ let i = 0;
20
+ while (i < argv.length) {
21
+ const arg = argv[i];
22
+ if (arg === '--keyword' && i + 1 < argv.length) {
23
+ args.keyword.push(argv[++i]);
24
+ }
25
+ else if (arg === '--regex' && i + 1 < argv.length) {
26
+ args.regex.push(argv[++i]);
27
+ }
28
+ else if (arg === '--mode' && i + 1 < argv.length) {
29
+ const val = argv[++i];
30
+ if (val === 'any' || val === 'all') {
31
+ args.mode = val;
32
+ }
33
+ }
34
+ else if (arg === '--ignore-case') {
35
+ args.ignoreCase = true;
36
+ }
37
+ else if (arg === '--start' && i + 1 < argv.length) {
38
+ args.start = argv[++i];
39
+ }
40
+ else if (arg === '--end' && i + 1 < argv.length) {
41
+ args.end = argv[++i];
42
+ }
43
+ else if (arg === '--assume-timezone' && i + 1 < argv.length) {
44
+ args.assumeTimezone = argv[++i];
45
+ }
46
+ else if (arg === '--before-context' && i + 1 < argv.length) {
47
+ args.beforeContext = parseInt(argv[++i], 10) || 0;
48
+ }
49
+ else if (arg === '--after-context' && i + 1 < argv.length) {
50
+ args.afterContext = parseInt(argv[++i], 10) || 0;
51
+ }
52
+ else if (arg === '--count-only') {
53
+ args.countOnly = true;
54
+ }
55
+ else if (arg.startsWith('-')) {
56
+ // skip unknown flags
57
+ }
58
+ else {
59
+ args.paths.push(arg);
60
+ }
61
+ i++;
62
+ }
63
+ return args;
64
+ }
65
+ function buildMatchers(args) {
66
+ const matchers = [];
67
+ for (const keyword of args.keyword) {
68
+ const needle = args.ignoreCase ? keyword.toLowerCase() : keyword;
69
+ matchers.push((line) => {
70
+ const haystack = args.ignoreCase ? line.toLowerCase() : line;
71
+ return haystack.includes(needle);
72
+ });
73
+ }
74
+ for (const pattern of args.regex) {
75
+ const flags = args.ignoreCase ? 'i' : '';
76
+ const compiled = new RegExp(pattern, flags);
77
+ matchers.push((line) => compiled.test(line));
78
+ }
79
+ return matchers;
80
+ }
81
+ function lineMatches(line, matchers, mode) {
82
+ if (matchers.length === 0)
83
+ return true;
84
+ if (mode === 'any') {
85
+ return matchers.some((m) => m(line));
86
+ }
87
+ return matchers.every((m) => m(line));
88
+ }
89
+ async function searchLogsHandler(argv, context) {
90
+ const { stdout, stderr } = context;
91
+ const args = parseArgs(argv);
92
+ try {
93
+ (0, log_cli_utils_1.buildTimezone)(args.assumeTimezone);
94
+ }
95
+ catch (err) {
96
+ stderr.write(`Error: invalid timezone: ${args.assumeTimezone}\n`);
97
+ return 1;
98
+ }
99
+ let start = null;
100
+ let end = null;
101
+ try {
102
+ if (args.start) {
103
+ start = (0, log_cli_utils_1.parseCliTimestamp)(args.start, args.assumeTimezone);
104
+ }
105
+ if (args.end) {
106
+ end = (0, log_cli_utils_1.parseCliTimestamp)(args.end, args.assumeTimezone);
107
+ }
108
+ }
109
+ catch (err) {
110
+ stderr.write(`Error: ${err.message}\n`);
111
+ return 1;
112
+ }
113
+ if (!(0, log_cli_utils_1.validateTimeWindow)(start, end, stderr)) {
114
+ return 1;
115
+ }
116
+ const matchers = buildMatchers(args);
117
+ let matches = 0;
118
+ const beforeBuffer = [];
119
+ let afterRemaining = 0;
120
+ try {
121
+ for await (const line of (0, log_cli_utils_1.iterInputLines)(args.paths)) {
122
+ const timestamp = (0, log_cli_utils_1.extractTimestamp)(line, args.assumeTimezone);
123
+ // When time filter is active, skip lines outside the window
124
+ if (args.start || args.end) {
125
+ if (!(0, log_cli_utils_1.inWindow)(timestamp, start, end)) {
126
+ beforeBuffer.push(line);
127
+ if (beforeBuffer.length > args.beforeContext) {
128
+ beforeBuffer.shift();
129
+ }
130
+ continue;
131
+ }
132
+ }
133
+ const isMatch = lineMatches(line, matchers, args.mode);
134
+ if (isMatch) {
135
+ matches++;
136
+ if (!args.countOnly) {
137
+ // Flush before context
138
+ for (const ctxLine of beforeBuffer) {
139
+ stdout.write(ctxLine + '\n');
140
+ }
141
+ stdout.write(line + '\n');
142
+ }
143
+ afterRemaining = args.afterContext;
144
+ }
145
+ else if (afterRemaining > 0 && !args.countOnly) {
146
+ stdout.write(line + '\n');
147
+ afterRemaining--;
148
+ }
149
+ // Maintain before context buffer
150
+ beforeBuffer.push(line);
151
+ if (beforeBuffer.length > args.beforeContext) {
152
+ beforeBuffer.shift();
153
+ }
154
+ }
155
+ }
156
+ catch (err) {
157
+ stderr.write(`Error: ${err.message}\n`);
158
+ return 1;
159
+ }
160
+ if (args.countOnly) {
161
+ stdout.write(String(matches) + '\n');
162
+ }
163
+ return 0;
164
+ }
@@ -0,0 +1,2 @@
1
+ import type { ToolContext } from '../types';
2
+ export declare function syncMemoryIndexHandler(args: string[], context: ToolContext): Promise<number>;