@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,561 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { formatExamples } from './utils/format';
5
+ import type { ToolContext, ToolDefinition, ToolExample } from './types';
6
+ import { createSpecsHandler } from './tools/create-specs';
7
+ import { renderKatexHandler } from './tools/render-katex';
8
+ import { renderErrorBookHandler } from './tools/render-error-book';
9
+ import { docsToVoiceHandler } from './tools/docs-to-voice';
10
+ import { generateStoryboardImagesHandler } from './tools/generate-storyboard-images';
11
+ import { enforceVideoAspectRatioHandler } from './tools/enforce-video-aspect-ratio';
12
+ import { extractPdfTextHandler } from './tools/extract-pdf-text';
13
+ import { filterLogsHandler } from './tools/filter-logs';
14
+ import { searchLogsHandler } from './tools/search-logs';
15
+ import { openGitHubIssueHandler } from './tools/open-github-issue';
16
+ import { findGitHubIssuesHandler } from './tools/find-github-issues';
17
+ import { readGitHubIssueHandler } from './tools/read-github-issue';
18
+ import { reviewThreadsHandler } from './tools/review-threads';
19
+ import { architectureHandler } from './tools/architecture';
20
+ import { extractConversationsHandler } from './tools/extract-conversations';
21
+ import { syncMemoryIndexHandler } from './tools/sync-memory-index';
22
+ import { validateSkillFrontmatterHandler } from './tools/validate-skill-frontmatter';
23
+ import { validateOpenaiAgentConfigHandler } from './tools/validate-openai-agent-config';
24
+
25
+ const HELP_FLAGS = new Set(['--help', '-h']);
26
+
27
+ function toolExamples(...examples: ToolExample[]): ToolExample[] {
28
+ return examples;
29
+ }
30
+
31
+ const TOOL_COMMANDS: ToolDefinition[] = [
32
+ {
33
+ name: 'architecture',
34
+ category: 'Planning & architecture',
35
+ skill: 'init-project-html',
36
+ handler: architectureHandler,
37
+ description: 'Open the project HTML architecture atlas, or render a paginated diff (`architecture diff`).',
38
+ help: {
39
+ purpose: 'Inspect, mutate, validate, and diff the repository architecture atlas without hand-editing generated HTML files.',
40
+ useWhen: [
41
+ 'You need to browse or update `resources/project-architecture/` through the declarative atlas CLI.',
42
+ 'You need to compare proposed spec overlays under `docs/plans/**/architecture_diff/` against the base atlas.',
43
+ ],
44
+ insteadOf: [
45
+ 'Editing rendered architecture HTML files directly.',
46
+ 'Inventing atlas YAML changes without validating them through `apltk architecture validate`.',
47
+ ],
48
+ examples: toolExamples(
49
+ {
50
+ command: 'apltk architecture',
51
+ result: 'Prints the base atlas HTML path and opens it in a browser unless `--no-open` is set.',
52
+ },
53
+ {
54
+ command: 'apltk architecture diff',
55
+ result: 'Builds a paginated before/after viewer and prints the generated diff viewer path.',
56
+ },
57
+ ),
58
+ },
59
+ },
60
+ {
61
+ name: 'filter-logs',
62
+ category: 'Observability',
63
+ skill: 'analyse-app-logs',
64
+ handler: filterLogsHandler,
65
+ description: 'Filter log lines by timestamp window.',
66
+ aliases: ['filter-logs-by-time'],
67
+ help: {
68
+ purpose: 'Narrow a log file to an exact time window before deeper investigation.',
69
+ useWhen: ['You know the incident time range and want only the matching log slice.'],
70
+ insteadOf: ['Searching the full log file when the main problem is time scoping.'],
71
+ examples: toolExamples({
72
+ command: 'apltk filter-logs app.log --start 2026-03-24T10:00:00Z --end 2026-03-24T10:15:00Z',
73
+ result: 'Prints only the lines whose timestamps fall within the requested window.',
74
+ }),
75
+ },
76
+ },
77
+ {
78
+ name: 'search-logs',
79
+ category: 'Observability',
80
+ skill: 'analyse-app-logs',
81
+ handler: searchLogsHandler,
82
+ description: 'Search logs by keyword or regex.',
83
+ help: {
84
+ purpose: 'Search logs by keyword or regex after you know which file or slice to inspect.',
85
+ useWhen: ['You need to find recurring messages, IDs, stack traces, or regex matches inside logs.'],
86
+ insteadOf: ['Filtering by time alone when you already know the error text or pattern to match.'],
87
+ examples: toolExamples({
88
+ command: 'apltk search-logs app.log --pattern "timeout|ECONNRESET"',
89
+ result: 'Prints matching log lines or matching groups, depending on the script flags you choose.',
90
+ }),
91
+ },
92
+ },
93
+ {
94
+ name: 'docs-to-voice',
95
+ category: 'Rendering & media',
96
+ skill: 'docs-to-voice',
97
+ handler: docsToVoiceHandler,
98
+ description: 'Convert text into audio, timeline JSON, and SRT.',
99
+ help: {
100
+ purpose: 'Turn text or documents into narrated audio plus subtitle timelines.',
101
+ useWhen: ['You need spoken output, subtitle timing, or a voiceover pipeline for docs and scripts.'],
102
+ examples: toolExamples({
103
+ command: 'apltk docs-to-voice --input notes.md --project-name lecture-01',
104
+ result: 'Writes audio plus subtitle artifacts under the project audio output directory.',
105
+ }),
106
+ },
107
+ },
108
+ {
109
+ name: 'create-specs',
110
+ category: 'Planning & architecture',
111
+ skill: 'generate-spec',
112
+ handler: createSpecsHandler,
113
+ description: 'Create spec planning documents from templates.',
114
+ help: {
115
+ purpose: 'Generate a new planning scaffold under `docs/plans/` for a requested change or batch.',
116
+ useWhen: ['You need a spec-first workflow before implementing a user-visible or risky change.'],
117
+ insteadOf: ['Starting implementation before a spec path exists when planning is required.'],
118
+ examples: toolExamples({
119
+ command: 'apltk create-specs "Membership upgrade flow" --change-name membership-upgrade-flow',
120
+ result: 'Creates a dated spec directory with the planning templates for that change.',
121
+ }),
122
+ },
123
+ },
124
+ {
125
+ name: 'render-katex',
126
+ category: 'Rendering & media',
127
+ skill: 'katex',
128
+ handler: renderKatexHandler,
129
+ description: 'Render TeX with KaTeX into reusable output.',
130
+ help: {
131
+ purpose: 'Render TeX formulas into insertion-ready KaTeX output.',
132
+ useWhen: ['You need verified inline or display math output for Markdown, HTML, or generated documents.'],
133
+ examples: toolExamples({
134
+ command: 'apltk render-katex --tex "\\\\int_0^1 x^2 dx"',
135
+ result: 'Prints or writes the rendered KaTeX output in the format selected by the script flags.',
136
+ }),
137
+ },
138
+ },
139
+ {
140
+ name: 'render-error-book',
141
+ category: 'Rendering & media',
142
+ skill: 'learning-error-book',
143
+ handler: renderErrorBookHandler,
144
+ description: 'Render structured error-book JSON into PDF.',
145
+ help: {
146
+ purpose: 'Convert structured error-book data into a finished PDF deliverable.',
147
+ useWhen: ['You already have JSON error-book content and need a rendered PDF artifact.'],
148
+ examples: toolExamples({
149
+ command: 'apltk render-error-book --input mistakes.json --output mistakes.pdf',
150
+ result: 'Writes the rendered PDF to the requested output path.',
151
+ }),
152
+ },
153
+ },
154
+ {
155
+ name: 'open-github-issue',
156
+ category: 'GitHub workflows',
157
+ skill: 'open-github-issue',
158
+ handler: openGitHubIssueHandler,
159
+ description: 'Publish or draft a structured GitHub issue.',
160
+ help: {
161
+ purpose: 'Create a structured GitHub issue with auth fallbacks and a stable JSON output contract.',
162
+ useWhen: ['You need to publish a confirmed problem, proposal, docs gap, security issue, or observability issue to GitHub.'],
163
+ insteadOf: ['Hand-building issue bodies inline in shell commands when rich Markdown could be corrupted by quoting.'],
164
+ examples: toolExamples({
165
+ command: 'apltk open-github-issue --payload-file /tmp/issue.json --repo owner/repo',
166
+ result: 'Prints a JSON result describing the publish mode, rendered body, and issue URL when creation succeeds.',
167
+ }),
168
+ },
169
+ },
170
+ {
171
+ name: 'generate-storyboard-images',
172
+ category: 'Rendering & media',
173
+ skill: 'openai-text-to-image-storyboard',
174
+ handler: generateStoryboardImagesHandler,
175
+ description: 'Generate storyboard image sets from text.',
176
+ help: {
177
+ purpose: 'Generate storyboard image assets from chapters, scenes, or other written prompts.',
178
+ useWhen: ['You need picture outputs under the storyboard workflow rather than a generic one-off image.'],
179
+ examples: toolExamples({
180
+ command: 'apltk generate-storyboard-images --input chapter.txt --project-name teaser',
181
+ result: 'Writes storyboard image files into the storyboard output directory for that project.',
182
+ }),
183
+ },
184
+ },
185
+ {
186
+ name: 'find-github-issues',
187
+ category: 'GitHub workflows',
188
+ skill: 'read-github-issue',
189
+ handler: findGitHubIssuesHandler,
190
+ description: 'List GitHub issues through gh.',
191
+ help: {
192
+ purpose: 'Search and list GitHub issues from a repository through a stable wrapper over `gh`.',
193
+ useWhen: ['You need to discover candidate issues before reading one in detail.'],
194
+ insteadOf: ['Opening a single issue directly when you first need to search or filter the issue list.'],
195
+ examples: toolExamples({
196
+ command: 'apltk find-github-issues --repo owner/repo --query "architecture"',
197
+ result: 'Prints matching issues in the format selected by the script flags, often table or JSON.',
198
+ }),
199
+ },
200
+ },
201
+ {
202
+ name: 'read-github-issue',
203
+ category: 'GitHub workflows',
204
+ skill: 'read-github-issue',
205
+ handler: readGitHubIssueHandler,
206
+ description: 'Read GitHub issue details through gh.',
207
+ help: {
208
+ purpose: 'Read one GitHub issue in detail, including comments when supported by the script flags.',
209
+ useWhen: ['You already know the issue number and want its full context.'],
210
+ insteadOf: ['Listing issues when you already have the exact issue you need to inspect.'],
211
+ examples: toolExamples({
212
+ command: 'apltk read-github-issue --repo owner/repo --issue 123',
213
+ result: 'Prints the issue body and related context in the output format chosen by the script.',
214
+ }),
215
+ },
216
+ },
217
+ {
218
+ name: 'review-threads',
219
+ category: 'GitHub workflows',
220
+ skill: 'resolve-review-comments',
221
+ handler: reviewThreadsHandler,
222
+ description: 'List or resolve GitHub PR review threads.',
223
+ help: {
224
+ purpose: 'List unresolved review threads or resolve them after handling the requested changes.',
225
+ useWhen: ['You need to inspect PR review feedback or close addressed review threads.'],
226
+ insteadOf: ['Using generic PR commands when the task is specifically about review threads.'],
227
+ examples: toolExamples(
228
+ {
229
+ command: 'apltk review-threads list --repo owner/repo --pr 42',
230
+ result: 'Prints the matching review threads in the selected output format.',
231
+ },
232
+ {
233
+ command: 'apltk review-threads resolve --repo owner/repo --thread-id PRT_abc123',
234
+ result: 'Marks the addressed review thread as resolved when GitHub permissions allow it.',
235
+ },
236
+ ),
237
+ },
238
+ },
239
+ {
240
+ name: 'enforce-video-aspect-ratio',
241
+ category: 'Rendering & media',
242
+ skill: 'text-to-short-video',
243
+ handler: enforceVideoAspectRatioHandler,
244
+ description: 'Resize video output to a target aspect ratio.',
245
+ help: {
246
+ purpose: 'Normalize rendered video output to a required aspect ratio.',
247
+ useWhen: ['You already have a video file and need a controlled resize or crop step.'],
248
+ examples: toolExamples({
249
+ command: 'apltk enforce-video-aspect-ratio --input clip.mp4 --output clip-vertical.mp4 --aspect 9:16',
250
+ result: 'Writes a transformed video file that matches the requested aspect ratio.',
251
+ }),
252
+ },
253
+ },
254
+ {
255
+ name: 'extract-pdf-text-pdfkit',
256
+ category: 'Rendering & media',
257
+ skill: 'weekly-financial-event-report',
258
+ handler: extractPdfTextHandler,
259
+ description: 'Extract PDF text with macOS PDFKit fallback.',
260
+ help: {
261
+ purpose: 'Extract per-page text from a PDF through macOS PDFKit.',
262
+ useWhen: ['You need a lightweight PDF text extraction fallback on macOS.'],
263
+ examples: toolExamples({
264
+ command: 'apltk extract-pdf-text-pdfkit /absolute/path/to/source.pdf',
265
+ result: 'Prints `PDF_PATH=...`, `PAGE_COUNT=...`, and a per-page text section for each extracted page.',
266
+ }),
267
+ },
268
+ },
269
+ {
270
+ name: 'extract-codex-conversations',
271
+ category: 'Codex memory & learning',
272
+ skill: 'codex-memory-manager',
273
+ handler: extractConversationsHandler,
274
+ description: 'Extract recent Codex sessions for memory updates.',
275
+ help: {
276
+ purpose: 'Extract recent Codex chats so memory maintenance can review them.',
277
+ useWhen: ['You need the last conversation set before generating or refreshing Codex memory artifacts.'],
278
+ examples: toolExamples({
279
+ command: 'apltk extract-codex-conversations --hours 24',
280
+ result: 'Prints or writes the extracted recent-session data needed by the memory workflow.',
281
+ }),
282
+ },
283
+ },
284
+ {
285
+ name: 'sync-codex-memory-index',
286
+ category: 'Codex memory & learning',
287
+ skill: 'codex-memory-manager',
288
+ handler: syncMemoryIndexHandler,
289
+ description: 'Sync the Codex memory index in AGENTS.md.',
290
+ help: {
291
+ purpose: 'Refresh the Codex memory index after the memory documents have been updated.',
292
+ useWhen: ['You need to sync the AGENTS.md memory index to match the latest memory files.'],
293
+ examples: toolExamples({
294
+ command: 'apltk sync-codex-memory-index',
295
+ result: 'Updates the memory index output and reports what was synchronized.',
296
+ }),
297
+ },
298
+ },
299
+ {
300
+ name: 'extract-skill-conversations',
301
+ category: 'Codex memory & learning',
302
+ skill: 'learn-skill-from-conversations',
303
+ handler: extractConversationsHandler,
304
+ description: 'Extract recent Codex sessions for skill learning.',
305
+ help: {
306
+ purpose: 'Extract recent conversation history for learning or improving skills from past chats.',
307
+ useWhen: ['You need recent session input for the learn-skill-from-conversations workflow.'],
308
+ examples: toolExamples({
309
+ command: 'apltk extract-skill-conversations --hours 24',
310
+ result: 'Prints or writes the conversation data consumed by the skill-learning pipeline.',
311
+ }),
312
+ },
313
+ },
314
+ {
315
+ name: 'validate-skill-frontmatter',
316
+ category: 'Catalog maintenance',
317
+ handler: validateSkillFrontmatterHandler,
318
+ description: 'Validate SKILL.md frontmatter across the catalog.',
319
+ help: {
320
+ purpose: 'Check top-level skill frontmatter for required keys, naming, and description constraints.',
321
+ useWhen: ['You changed `SKILL.md` frontmatter or want a catalog-wide metadata validation pass.'],
322
+ examples: toolExamples({
323
+ command: 'apltk validate-skill-frontmatter',
324
+ result: 'Prints either a pass summary or one error per invalid skill frontmatter file.',
325
+ }),
326
+ },
327
+ },
328
+ {
329
+ name: 'validate-openai-agent-config',
330
+ category: 'Catalog maintenance',
331
+ handler: validateOpenaiAgentConfigHandler,
332
+ description: 'Validate every skill agents/openai.yaml config.',
333
+ help: {
334
+ purpose: 'Validate `agents/openai.yaml` for every top-level skill against the repo rules.',
335
+ useWhen: ['You changed skill agent configs or need a catalog-wide agent-config validation pass.'],
336
+ examples: toolExamples({
337
+ command: 'apltk validate-openai-agent-config',
338
+ result: 'Prints either a pass summary or one error per invalid `agents/openai.yaml` file.',
339
+ }),
340
+ },
341
+ },
342
+ ];
343
+
344
+ const TOOL_BY_NAME = new Map<string, ToolDefinition>();
345
+
346
+ for (const tool of TOOL_COMMANDS) {
347
+ TOOL_BY_NAME.set(tool.name, tool);
348
+ for (const alias of tool.aliases || []) {
349
+ TOOL_BY_NAME.set(alias, { ...tool, name: alias, canonicalName: tool.name });
350
+ }
351
+ }
352
+
353
+ export function getToolCommand(name: string): ToolDefinition | null {
354
+ return TOOL_BY_NAME.get(name) || null;
355
+ }
356
+
357
+ export function listToolCommands(): ToolDefinition[] {
358
+ return [...TOOL_COMMANDS].sort((left, right) => left.name.localeCompare(right.name));
359
+ }
360
+
361
+ export function resolveToolCommand(name: string, sourceRoot: string): (ToolDefinition & { scriptPath?: string }) | null {
362
+ const tool = getToolCommand(name);
363
+ if (!tool) return null;
364
+ return {
365
+ ...tool,
366
+ scriptPath: tool.script ? path.join(sourceRoot, tool.script) : undefined,
367
+ };
368
+ }
369
+
370
+ export function formatToolList(): string {
371
+ const tools = listToolCommands();
372
+ const width = tools.reduce((max, tool) => Math.max(max, tool.name.length), 0);
373
+ return tools.map((tool) => {
374
+ const name = tool.name.padEnd(width, ' ');
375
+ return ` ${name} ${tool.description}`;
376
+ }).join('\n');
377
+ }
378
+
379
+ function buildToolOverview(name: string): string | null {
380
+ const tool = getToolCommand(name);
381
+ if (!tool) return null;
382
+
383
+ const lines = [
384
+ `apltk ${tool.name} — ${tool.description}`,
385
+ '',
386
+ 'Usage:',
387
+ ` apltk ${tool.name} [...args]`,
388
+ ` apltk tools ${tool.name} [...args]`,
389
+ ];
390
+
391
+ if (tool.help?.purpose) {
392
+ lines.push('', 'Purpose:', ` ${tool.help.purpose}`);
393
+ }
394
+
395
+ if (tool.help?.useWhen?.length) {
396
+ lines.push('', 'Use this when:', ...tool.help.useWhen.map((item) => ` - ${item}`));
397
+ }
398
+
399
+ if (tool.help?.insteadOf?.length) {
400
+ lines.push('', 'Instead of:', ...tool.help.insteadOf.map((item) => ` - ${item}`));
401
+ }
402
+
403
+ lines.push('', 'Exact flags:', ' The native tool help appears below.');
404
+ return lines.join('\n');
405
+ }
406
+
407
+ function buildToolExamples(name: string): string {
408
+ const tool = getToolCommand(name);
409
+ if (!tool || !tool.help?.examples?.length) return '';
410
+ return ['Examples:', formatExamples(tool.help.examples)].join('\n');
411
+ }
412
+
413
+ export function buildToolDiscoveryHelp(): string {
414
+ const categories = new Map<string, ToolDefinition[]>();
415
+ for (const tool of listToolCommands()) {
416
+ const category = tool.category || 'Other';
417
+ if (!categories.has(category)) {
418
+ categories.set(category, []);
419
+ }
420
+ categories.get(category)!.push(tool);
421
+ }
422
+
423
+ const lines = ['Common goals:'];
424
+ for (const [category, tools] of categories.entries()) {
425
+ lines.push(` ${category}:`);
426
+ for (const tool of tools) {
427
+ const firstUseCase = tool.help?.useWhen?.[0] || tool.description;
428
+ lines.push(` - \`${tool.name}\`: ${firstUseCase}`);
429
+ }
430
+ }
431
+ lines.push('', 'Next step:', ' Run `apltk tools <tool> --help` for the exact flags, behavior notes, and examples of one tool.');
432
+ return lines.join('\n');
433
+ }
434
+
435
+ function isTopLevelToolHelpRequest(toolArgs: string[]): boolean {
436
+ return Array.isArray(toolArgs) && toolArgs.length > 0 && toolArgs.every((arg) => HELP_FLAGS.has(arg));
437
+ }
438
+
439
+ function captureCommandOutput(
440
+ tool: ToolDefinition,
441
+ toolArgs: string[],
442
+ context: ToolContext = {},
443
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
444
+ const sourceRoot = context.sourceRoot || path.resolve(__dirname, '../..');
445
+ const stderr = context.stderr || process.stderr;
446
+ const env = context.env || process.env;
447
+ const spawnCommand = context.spawnCommand || spawn;
448
+ const toolEntry = resolveToolCommand(tool.name, sourceRoot);
449
+ if (!toolEntry || !toolEntry.runner) {
450
+ stderr.write(`Tool not fully configured: ${tool.name}\n`);
451
+ return Promise.resolve({ exitCode: 1, stdout: '', stderr: '' });
452
+ }
453
+
454
+ return new Promise((resolve) => {
455
+ const child = spawnCommand(toolEntry.runner, [toolEntry.scriptPath, ...toolArgs], {
456
+ cwd: context.cwd || process.cwd(),
457
+ env,
458
+ stdio: ['ignore', 'pipe', 'pipe'],
459
+ timeout: 30000,
460
+ });
461
+
462
+ let stdoutText = '';
463
+ let stderrText = '';
464
+ let settled = false;
465
+
466
+ child.stdout?.on('data', (chunk: Buffer) => {
467
+ stdoutText += String(chunk);
468
+ });
469
+ child.stderr?.on('data', (chunk: Buffer) => {
470
+ stderrText += String(chunk);
471
+ });
472
+
473
+ child.on('error', (error: Error) => {
474
+ if (settled) return;
475
+ settled = true;
476
+ if ((error as any).killed) {
477
+ stderr.write(`Tool timed out after 30s: ${toolEntry.runner}\n`);
478
+ } else {
479
+ stderr.write(`Failed to start ${toolEntry.runner}: ${error.message}\n`);
480
+ }
481
+ resolve({ exitCode: 1, stdout: stdoutText, stderr: stderrText });
482
+ });
483
+
484
+ child.on('close', (code: number | null) => {
485
+ if (settled) return;
486
+ settled = true;
487
+ resolve({
488
+ exitCode: typeof code === 'number' ? code : 1,
489
+ stdout: stdoutText,
490
+ stderr: stderrText,
491
+ });
492
+ });
493
+ });
494
+ }
495
+
496
+ export function runTool(toolName: string, toolArgs: string[], context: ToolContext = {}): Promise<number> {
497
+ const sourceRoot = context.sourceRoot || path.resolve(__dirname, '../..');
498
+ const stdout = context.stdout || process.stdout;
499
+ const stderr = context.stderr || process.stderr;
500
+ const env = context.env || process.env;
501
+ const spawnCommand = context.spawnCommand || spawn;
502
+ const tool = getToolCommand(toolName);
503
+
504
+ if (!tool) {
505
+ stderr.write(`Unknown tool: ${toolName}\n\nAvailable tools:\n${formatToolList()}\n`);
506
+ return Promise.resolve(1);
507
+ }
508
+
509
+ // Direct handler invocation (preferred)
510
+ if (tool.handler) {
511
+ return tool.handler(toolArgs, context);
512
+ }
513
+
514
+ // Spawn fallback when no handler is registered
515
+ const toolEntry = resolveToolCommand(toolName, sourceRoot);
516
+ if (!toolEntry || !toolEntry.scriptPath) {
517
+ stderr.write(`Tool not fully configured: ${toolName}\n`);
518
+ return Promise.resolve(1);
519
+ }
520
+
521
+ if (!fs.existsSync(toolEntry.scriptPath)) {
522
+ stderr.write(`Tool script not found: ${toolEntry.scriptPath}\n`);
523
+ return Promise.resolve(1);
524
+ }
525
+
526
+ if (isTopLevelToolHelpRequest(toolArgs)) {
527
+ return captureCommandOutput(tool, ['--help'], context).then((nativeHelp) => {
528
+ const blocks = [buildToolOverview(tool.name)];
529
+ const nativeText = [nativeHelp.stdout, nativeHelp.stderr].filter(Boolean).join('').trim();
530
+ if (nativeText) {
531
+ blocks.push('Native flags and behavior:', nativeText);
532
+ }
533
+ const examplesBlock = buildToolExamples(tool.name);
534
+ if (examplesBlock) {
535
+ blocks.push(examplesBlock);
536
+ }
537
+ stdout.write(`${blocks.filter(Boolean).join('\n\n')}\n`);
538
+ return nativeHelp.exitCode;
539
+ });
540
+ }
541
+
542
+ return new Promise((resolve) => {
543
+ const child = spawnCommand(toolEntry.runner!, [toolEntry.scriptPath, ...toolArgs], {
544
+ cwd: context.cwd || process.cwd(),
545
+ env,
546
+ stdio: context.stdio || 'inherit',
547
+ });
548
+
549
+ child.on('error', (error: Error) => {
550
+ stderr.write(`Failed to start ${toolEntry!.runner}: ${error.message}\n`);
551
+ resolve(1);
552
+ });
553
+
554
+ child.on('close', (code: number | null) => {
555
+ resolve(typeof code === 'number' ? code : 1);
556
+ });
557
+ });
558
+ }
559
+
560
+ // Re-export for backward compatibility
561
+ export { formatExamples, buildToolOverview, buildToolExamples, isTopLevelToolHelpRequest, captureCommandOutput };
@@ -0,0 +1,34 @@
1
+ import path from 'node:path';
2
+ import type { ToolContext } from '../types';
3
+
4
+ const LEGACY_VERBS = new Set(['open', 'diff']);
5
+
6
+ export function architectureHandler(args: string[], context: ToolContext): Promise<number> {
7
+ const sourceRoot = context.sourceRoot || path.resolve(__dirname, '..', '..', '..');
8
+
9
+ // Delegate to the existing atlas CLI (still in JS)
10
+ const cliPath = path.join(sourceRoot, 'init-project-html', 'lib', 'atlas', 'cli.js');
11
+
12
+ try {
13
+ const cli = require(cliPath);
14
+
15
+ const verb = args[0];
16
+ if (!verb || verb.startsWith('-') || LEGACY_VERBS.has(verb)) {
17
+ // Sync execution for legacy verbs
18
+ const code = cli.main(args, {
19
+ stdout: context.stdout || process.stdout,
20
+ stderr: context.stderr || process.stderr,
21
+ });
22
+ return Promise.resolve(code);
23
+ }
24
+
25
+ // Async dispatch for declarative verbs
26
+ return cli.dispatch(args, {
27
+ stdout: context.stdout || process.stdout,
28
+ stderr: context.stderr || process.stderr,
29
+ });
30
+ } catch (error: any) {
31
+ (context.stderr || process.stderr).write(`Error loading atlas CLI: ${error.message}\n`);
32
+ return Promise.resolve(1);
33
+ }
34
+ }