@kortix/sandbox 0.4.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 (246) hide show
  1. package/config/customize.sh +143 -0
  2. package/config/kortix-env-setup.sh +25 -0
  3. package/kortix-master/package.json +22 -0
  4. package/kortix-master/src/config.ts +22 -0
  5. package/kortix-master/src/index.ts +44 -0
  6. package/kortix-master/src/routes/env.ts +65 -0
  7. package/kortix-master/src/routes/proxy.ts +108 -0
  8. package/kortix-master/src/routes/update.ts +185 -0
  9. package/kortix-master/src/services/proxy.ts +43 -0
  10. package/kortix-master/src/services/secret-store.ts +156 -0
  11. package/kortix-master/tsconfig.json +14 -0
  12. package/opencode/agents/kortix-browser.md +142 -0
  13. package/opencode/agents/kortix-build.md +62 -0
  14. package/opencode/agents/kortix-explore.md +66 -0
  15. package/opencode/agents/kortix-image-gen.md +33 -0
  16. package/opencode/agents/kortix-main.md +450 -0
  17. package/opencode/agents/kortix-plan.md +100 -0
  18. package/opencode/agents/kortix-research.md +84 -0
  19. package/opencode/agents/kortix-sheets.md +61 -0
  20. package/opencode/agents/kortix-slides.md +64 -0
  21. package/opencode/agents/kortix-web-dev.md +572 -0
  22. package/opencode/commands/email.md +36 -0
  23. package/opencode/commands/init.md +43 -0
  24. package/opencode/commands/journal.md +44 -0
  25. package/opencode/commands/memory-init.md +81 -0
  26. package/opencode/commands/memory-search.md +50 -0
  27. package/opencode/commands/memory-status.md +56 -0
  28. package/opencode/commands/research.md +36 -0
  29. package/opencode/commands/search.md +38 -0
  30. package/opencode/commands/slides.md +32 -0
  31. package/opencode/commands/spreadsheet.md +30 -0
  32. package/opencode/memory.json +37 -0
  33. package/opencode/ocx.jsonc +10 -0
  34. package/opencode/opencode.jsonc +103 -0
  35. package/opencode/package.json +25 -0
  36. package/opencode/patches/apply.sh +19 -0
  37. package/opencode/patches/opencode-pty-spawn.txt +49 -0
  38. package/opencode/plugin/background-agents.ts.disabled +483 -0
  39. package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
  40. package/opencode/plugin/kdco-primitives/index.ts +26 -0
  41. package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
  42. package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
  43. package/opencode/plugin/kdco-primitives/shell.ts +138 -0
  44. package/opencode/plugin/kdco-primitives/temp.ts +36 -0
  45. package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
  46. package/opencode/plugin/kdco-primitives/types.ts +13 -0
  47. package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
  48. package/opencode/plugin/memory.ts +306 -0
  49. package/opencode/plugin/worktree/state.ts +412 -0
  50. package/opencode/plugin/worktree/terminal.ts +1002 -0
  51. package/opencode/plugin/worktree.ts +861 -0
  52. package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
  53. package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
  54. package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
  55. package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
  56. package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
  57. package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
  58. package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
  59. package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
  60. package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
  61. package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
  62. package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
  63. package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
  64. package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
  65. package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
  66. package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
  67. package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
  68. package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
  69. package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
  70. package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
  71. package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
  72. package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
  73. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
  74. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
  75. package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
  76. package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
  77. package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
  78. package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
  79. package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
  80. package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
  81. package/opencode/skills/KORTIX-email/SKILL.md +145 -0
  82. package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
  83. package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
  84. package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
  85. package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
  86. package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
  87. package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
  88. package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
  89. package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
  90. package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
  91. package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
  92. package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
  93. package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
  94. package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
  95. package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
  96. package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
  97. package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
  98. package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
  99. package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
  100. package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
  101. package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
  102. package/opencode/skills/KORTIX-pdf/forms.md +36 -0
  103. package/opencode/skills/KORTIX-pdf/reference.md +105 -0
  104. package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
  105. package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
  106. package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
  107. package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
  108. package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
  109. package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
  110. package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
  111. package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  112. package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
  113. package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
  114. package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
  115. package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
  116. package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
  117. package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
  118. package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
  119. package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
  120. package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
  121. package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
  122. package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
  123. package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
  124. package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
  125. package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
  126. package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
  127. package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
  128. package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
  129. package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
  130. package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
  131. package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
  132. package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
  133. package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
  134. package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
  135. package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
  136. package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
  137. package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
  138. package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
  139. package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
  140. package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
  141. package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
  142. package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
  143. package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
  144. package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
  145. package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
  146. package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
  147. package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
  148. package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
  149. package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
  150. package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
  151. package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
  152. package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
  153. package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
  154. package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
  155. package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
  156. package/opencode/skills/KORTIX-session-search/Untitled +1 -0
  157. package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
  158. package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
  159. package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
  160. package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
  161. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
  162. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
  163. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  164. package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
  165. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  166. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  167. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  168. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  169. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  170. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  171. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  172. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  173. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  174. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  175. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  176. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  177. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  178. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  179. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  180. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  181. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  182. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  183. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  184. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  185. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  186. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  187. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  188. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  189. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  190. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  191. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  192. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  193. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  194. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  195. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  196. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  197. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  198. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  199. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  200. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  201. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  202. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  203. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  204. package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
  205. package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
  206. package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
  207. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
  208. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
  209. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
  210. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
  211. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
  212. package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
  213. package/opencode/tools/image-gen.ts +342 -0
  214. package/opencode/tools/image-search.ts +190 -0
  215. package/opencode/tools/memory-get.ts +168 -0
  216. package/opencode/tools/memory-search.ts +247 -0
  217. package/opencode/tools/presentation-gen.ts +723 -0
  218. package/opencode/tools/scrape-webpage.ts +115 -0
  219. package/opencode/tools/scripts/.python-version +1 -0
  220. package/opencode/tools/scripts/convert_pdf.py +184 -0
  221. package/opencode/tools/scripts/convert_pptx.py +562 -0
  222. package/opencode/tools/scripts/pyproject.toml +11 -0
  223. package/opencode/tools/scripts/uv.lock +287 -0
  224. package/opencode/tools/scripts/validate_slide.py +74 -0
  225. package/opencode/tools/show-user.ts +217 -0
  226. package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
  227. package/opencode/tools/tests/image-gen.test.ts +215 -0
  228. package/opencode/tools/tests/image-search.test.ts +125 -0
  229. package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
  230. package/opencode/tools/tests/presentation-gen.test.ts +389 -0
  231. package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
  232. package/opencode/tools/tests/show-user.test.ts +241 -0
  233. package/opencode/tools/tests/video-gen.test.ts +110 -0
  234. package/opencode/tools/tests/web-search.test.ts +106 -0
  235. package/opencode/tools/video-gen.ts +200 -0
  236. package/opencode/tools/web-search.ts +153 -0
  237. package/opencode/tsconfig.json +29 -0
  238. package/package.json +36 -0
  239. package/patch-agent-browser.js +100 -0
  240. package/postinstall.sh +88 -0
  241. package/services/KORTIX-presentation-viewer/run +37 -0
  242. package/services/agent-browser-viewer/run +48 -0
  243. package/services/kortix-master/run +16 -0
  244. package/services/lss-sync/run +22 -0
  245. package/services/opencode-serve/run +25 -0
  246. package/services/opencode-web/run +21 -0
@@ -0,0 +1,115 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import FirecrawlApp from "@mendable/firecrawl-js";
3
+
4
+ interface ScrapeResult {
5
+ url: string;
6
+ success: boolean;
7
+ title?: string;
8
+ content?: string;
9
+ content_length?: number;
10
+ html?: string;
11
+ metadata?: Record<string, unknown>;
12
+ error?: string;
13
+ }
14
+
15
+ async function scrapeOne(
16
+ client: FirecrawlApp,
17
+ url: string,
18
+ includeHtml: boolean,
19
+ retries = 3,
20
+ ): Promise<ScrapeResult> {
21
+ const formats: ("markdown" | "html")[] = includeHtml
22
+ ? ["markdown", "html"]
23
+ : ["markdown"];
24
+
25
+ for (let attempt = 1; attempt <= retries; attempt++) {
26
+ try {
27
+ const response = (await client.scrape(url, {
28
+ formats,
29
+ timeout: 30000,
30
+ })) as Record<string, unknown>;
31
+
32
+ const metadata = (response.metadata ?? {}) as Record<string, string>;
33
+ const markdown = (response.markdown ?? "") as string;
34
+ const html = (response.html ?? "") as string;
35
+
36
+ const result: ScrapeResult = {
37
+ url,
38
+ success: true,
39
+ title: metadata.title ?? "",
40
+ content: markdown,
41
+ content_length: markdown.length,
42
+ };
43
+
44
+ if (includeHtml && html) result.html = html;
45
+ if (Object.keys(metadata).length > 0) result.metadata = metadata;
46
+ return result;
47
+ } catch (e) {
48
+ const msg = e instanceof Error ? e.message : String(e);
49
+ const isTimeout = msg.includes("timeout") || msg.includes("Timeout");
50
+
51
+ if (isTimeout && attempt < retries) {
52
+ await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
53
+ continue;
54
+ }
55
+ return { url, success: false, error: msg };
56
+ }
57
+ }
58
+ return { url, success: false, error: "max retries exceeded" };
59
+ }
60
+
61
+ export default tool({
62
+ description:
63
+ "Fetch and extract content from web pages using Firecrawl. " +
64
+ "Converts HTML to clean markdown. " +
65
+ "Supports multiple URLs separated by commas. " +
66
+ "Batch URLs in a single call for efficiency. " +
67
+ "For GitHub URLs, prefer gh CLI via Bash instead.",
68
+ args: {
69
+ urls: tool.schema
70
+ .string()
71
+ .describe(
72
+ "URLs to scrape, comma-separated (e.g. 'https://example.com/a,https://example.com/b')",
73
+ ),
74
+ include_html: tool.schema
75
+ .boolean()
76
+ .optional()
77
+ .describe("Include raw HTML alongside markdown. Default: false"),
78
+ },
79
+ async execute(args, _context) {
80
+ const apiKey = process.env.FIRECRAWL_API_KEY;
81
+ if (!apiKey) return "Error: FIRECRAWL_API_KEY not set.";
82
+
83
+ const client = new FirecrawlApp({
84
+ apiKey,
85
+ apiUrl: process.env.FIRECRAWL_URL ?? "https://api.firecrawl.dev",
86
+ });
87
+ const includeHtml = args.include_html ?? false;
88
+
89
+ const urlList = args.urls
90
+ .split(",")
91
+ .map((u) => u.trim())
92
+ .filter(Boolean);
93
+ if (urlList.length === 0) return "Error: no valid URLs provided.";
94
+
95
+ const results = await Promise.all(
96
+ urlList.map((u) => scrapeOne(client, u, includeHtml)),
97
+ );
98
+
99
+ const successful = results.filter((r) => r.success).length;
100
+ const failed = results.length - successful;
101
+
102
+ if (successful === 0) {
103
+ const errors = results.map((r) => `${r.url}: ${r.error}`).join("; ");
104
+ return `Error: Failed to scrape all ${results.length} URLs. ${errors}`;
105
+ }
106
+
107
+ if (urlList.length === 1) return JSON.stringify(results[0], null, 2);
108
+
109
+ return JSON.stringify(
110
+ { total: results.length, successful, failed, results },
111
+ null,
112
+ 2,
113
+ );
114
+ },
115
+ });
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,184 @@
1
+ """Convert HTML presentation slides to a single PDF.
2
+
3
+ Usage: uv run convert_pdf.py <presentation_dir> <output_path>
4
+
5
+ Reads metadata.json from <presentation_dir>, renders each slide HTML
6
+ at 1920x1080 via Playwright Chromium, merges into a single PDF.
7
+ """
8
+
9
+ import asyncio
10
+ import json
11
+ import sys
12
+ import tempfile
13
+ from pathlib import Path
14
+
15
+ from playwright.async_api import async_playwright
16
+ from PyPDF2 import PdfWriter, PdfReader
17
+
18
+
19
+ async def render_slide_to_pdf(
20
+ browser, slide_info: dict, temp_dir: Path, max_retries: int = 3
21
+ ) -> Path:
22
+ """Render a single HTML slide to PDF with retry logic."""
23
+ html_path = slide_info["path"]
24
+ slide_num = slide_info["number"]
25
+ last_error = None
26
+
27
+ for attempt in range(max_retries):
28
+ page = None
29
+ try:
30
+ if attempt > 0:
31
+ await asyncio.sleep(2.0 * attempt)
32
+
33
+ page = await browser.new_page()
34
+ await page.set_viewport_size({"width": 1920, "height": 1080})
35
+ await page.emulate_media(media="screen")
36
+ await page.evaluate(
37
+ "() => { Object.defineProperty(window, 'devicePixelRatio', { get: () => 1 }); }"
38
+ )
39
+
40
+ file_url = f"file://{html_path.resolve()}"
41
+ await page.goto(file_url, wait_until="networkidle", timeout=30000)
42
+ await page.wait_for_timeout(3000)
43
+
44
+ await page.evaluate("""
45
+ () => {
46
+ const sc = document.querySelector('.slide-container');
47
+ if (sc) { sc.style.width='1920px'; sc.style.height='1080px'; sc.style.transform='none'; sc.style.maxWidth='none'; sc.style.maxHeight='none'; }
48
+ document.body.style.margin='0'; document.body.style.padding='0';
49
+ document.body.style.width='1920px'; document.body.style.height='1080px';
50
+ document.body.style.overflow='hidden';
51
+ }
52
+ """)
53
+ await page.wait_for_timeout(1000)
54
+
55
+ temp_pdf = temp_dir / f"slide_{slide_num:02d}.pdf"
56
+ await page.pdf(
57
+ path=str(temp_pdf),
58
+ width="1920px",
59
+ height="1080px",
60
+ margin={"top": "0", "right": "0", "bottom": "0", "left": "0"},
61
+ print_background=True,
62
+ prefer_css_page_size=False,
63
+ )
64
+ return temp_pdf
65
+
66
+ except Exception as e:
67
+ last_error = e
68
+ err = str(e).lower()
69
+ retryable = any(
70
+ kw in err
71
+ for kw in [
72
+ "target closed", "crashed", "protocol error",
73
+ "printtopdf", "session closed", "timeout",
74
+ ]
75
+ )
76
+ if retryable and attempt < max_retries - 1:
77
+ continue
78
+ break
79
+ finally:
80
+ if page:
81
+ try:
82
+ await page.close()
83
+ except Exception:
84
+ pass
85
+
86
+ raise RuntimeError(
87
+ f"Slide {slide_num} failed after {max_retries} attempts: {last_error}"
88
+ )
89
+
90
+
91
+ def combine_pdfs(pdf_paths: list[Path], output_path: Path) -> None:
92
+ """Merge multiple single-page PDFs into one."""
93
+ writer = PdfWriter()
94
+ for p in pdf_paths:
95
+ if p.exists():
96
+ reader = PdfReader(str(p))
97
+ for pg in reader.pages:
98
+ writer.add_page(pg)
99
+ with open(output_path, "wb") as f:
100
+ writer.write(f)
101
+
102
+
103
+ async def convert(presentation_dir: str, output_path: str) -> dict:
104
+ """Main conversion entrypoint."""
105
+ pres_dir = Path(presentation_dir).resolve()
106
+ meta_path = pres_dir / "metadata.json"
107
+
108
+ if not pres_dir.exists():
109
+ return {"success": False, "error": f"Directory not found: {pres_dir}"}
110
+ if not meta_path.exists():
111
+ return {"success": False, "error": f"metadata.json not found in {pres_dir}"}
112
+
113
+ with open(meta_path, "r") as f:
114
+ metadata = json.load(f)
115
+
116
+ slides_info = []
117
+ for num_str, data in metadata.get("slides", {}).items():
118
+ file_path = data.get("file_path", "")
119
+ html_path = (pres_dir.parent.parent / file_path) if file_path else None
120
+ if html_path and html_path.exists():
121
+ slides_info.append({
122
+ "number": int(num_str),
123
+ "title": data.get("title", f"Slide {num_str}"),
124
+ "path": html_path,
125
+ })
126
+
127
+ if not slides_info:
128
+ return {"success": False, "error": "No valid slides found"}
129
+
130
+ slides_info.sort(key=lambda s: s["number"])
131
+
132
+ with tempfile.TemporaryDirectory() as tmp:
133
+ tmp_path = Path(tmp)
134
+
135
+ async with async_playwright() as p:
136
+ browser = await p.chromium.launch(
137
+ executable_path="/Users/markokraemer/Library/Caches/ms-playwright/chromium-1208/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing",
138
+ headless=True,
139
+ args=[
140
+ "--no-sandbox",
141
+ "--disable-setuid-sandbox",
142
+ "--disable-dev-shm-usage",
143
+ "--disable-gpu",
144
+ "--force-device-scale-factor=1",
145
+ ],
146
+ )
147
+
148
+ try:
149
+ sem = asyncio.Semaphore(5)
150
+
151
+ async def render_limited(si):
152
+ async with sem:
153
+ return await render_slide_to_pdf(browser, si, tmp_path)
154
+
155
+ pdf_paths = await asyncio.gather(
156
+ *[render_limited(si) for si in slides_info]
157
+ )
158
+ finally:
159
+ await browser.close()
160
+
161
+ sorted_paths = sorted(pdf_paths, key=lambda p: int(p.stem.split("_")[1]))
162
+ out = Path(output_path)
163
+ out.parent.mkdir(parents=True, exist_ok=True)
164
+ combine_pdfs(sorted_paths, out)
165
+
166
+ return {
167
+ "success": True,
168
+ "output_path": str(out),
169
+ "total_slides": len(slides_info),
170
+ }
171
+
172
+
173
+ def main():
174
+ if len(sys.argv) != 3:
175
+ print(json.dumps({"success": False, "error": "Usage: convert_pdf.py <presentation_dir> <output_path>"}))
176
+ sys.exit(1)
177
+
178
+ result = asyncio.run(convert(sys.argv[1], sys.argv[2]))
179
+ print(json.dumps(result))
180
+ sys.exit(0 if result["success"] else 1)
181
+
182
+
183
+ if __name__ == "__main__":
184
+ main()