@vodailoc/kilo-kit-mcp 1.1.0 → 1.2.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 (582) hide show
  1. package/.mcp/kilo-kit.codex-windows.toml +5 -0
  2. package/LICENSE +190 -190
  3. package/QUICKSTART.md +265 -255
  4. package/README.md +321 -267
  5. package/mcp/README.md +64 -12
  6. package/mcp/dist/formatters.js +142 -1
  7. package/mcp/dist/orchestration-audit.js +20 -0
  8. package/mcp/dist/orchestration-memory.js +258 -0
  9. package/mcp/dist/orchestration-types.js +1 -0
  10. package/mcp/dist/orchestrator.js +222 -0
  11. package/mcp/dist/question-templates.js +249 -0
  12. package/mcp/dist/route-analytics.js +149 -0
  13. package/mcp/dist/router.js +75 -82
  14. package/mcp/dist/routing-policy-data.js +241 -0
  15. package/mcp/dist/routing-policy.js +145 -0
  16. package/mcp/dist/server.js +93 -4
  17. package/mcp/dist/smoke-env.js +18 -0
  18. package/mcp/dist/smoke.js +68 -1
  19. package/mcp/package.json +1 -2
  20. package/package.json +3 -2
  21. package/skills/README.md +647 -647
  22. package/skills/SKILLS_INDEX.md +139 -139
  23. package/skills/ai-media/ai-multimodal/.env.example +97 -97
  24. package/skills/ai-media/ai-multimodal/SKILL.md +357 -357
  25. package/skills/ai-media/ai-multimodal/references/audio-processing.md +373 -373
  26. package/skills/ai-media/ai-multimodal/references/image-generation.md +558 -558
  27. package/skills/ai-media/ai-multimodal/references/video-analysis.md +502 -502
  28. package/skills/ai-media/ai-multimodal/references/vision-understanding.md +483 -483
  29. package/skills/ai-media/ai-multimodal/scripts/document_converter.py +395 -395
  30. package/skills/ai-media/ai-multimodal/scripts/gemini_batch_process.py +480 -480
  31. package/skills/ai-media/ai-multimodal/scripts/media_optimizer.py +506 -506
  32. package/skills/ai-media/ai-multimodal/scripts/requirements.txt +26 -26
  33. package/skills/ai-media/ai-multimodal/scripts/tests/requirements.txt +20 -20
  34. package/skills/ai-media/ai-multimodal/scripts/tests/test_document_converter.py +299 -299
  35. package/skills/ai-media/ai-multimodal/scripts/tests/test_gemini_batch_process.py +362 -362
  36. package/skills/ai-media/ai-multimodal/scripts/tests/test_media_optimizer.py +373 -373
  37. package/skills/ai-media/media-processing/SKILL.md +358 -358
  38. package/skills/ai-media/media-processing/references/ffmpeg-encoding.md +358 -358
  39. package/skills/ai-media/media-processing/references/ffmpeg-filters.md +503 -503
  40. package/skills/ai-media/media-processing/references/ffmpeg-streaming.md +403 -403
  41. package/skills/ai-media/media-processing/references/format-compatibility.md +375 -375
  42. package/skills/ai-media/media-processing/references/imagemagick-batch.md +612 -612
  43. package/skills/ai-media/media-processing/references/imagemagick-editing.md +623 -623
  44. package/skills/ai-media/media-processing/scripts/batch_resize.py +342 -342
  45. package/skills/ai-media/media-processing/scripts/media_convert.py +311 -311
  46. package/skills/ai-media/media-processing/scripts/requirements.txt +24 -24
  47. package/skills/ai-media/media-processing/scripts/tests/requirements.txt +2 -2
  48. package/skills/ai-media/media-processing/scripts/tests/test_batch_resize.py +372 -372
  49. package/skills/ai-media/media-processing/scripts/tests/test_media_convert.py +259 -259
  50. package/skills/ai-media/media-processing/scripts/tests/test_video_optimize.py +397 -397
  51. package/skills/ai-media/media-processing/scripts/video_optimize.py +414 -414
  52. package/skills/ai-media/screenshot/LICENSE.txt +201 -201
  53. package/skills/ai-media/screenshot/SKILL.md +267 -267
  54. package/skills/ai-media/screenshot/agents/openai.yaml +6 -6
  55. package/skills/ai-media/screenshot/assets/screenshot-small.svg +5 -5
  56. package/skills/ai-media/screenshot/scripts/ensure_macos_permissions.sh +54 -54
  57. package/skills/ai-media/screenshot/scripts/macos_display_info.swift +22 -22
  58. package/skills/ai-media/screenshot/scripts/macos_permissions.swift +40 -40
  59. package/skills/ai-media/screenshot/scripts/macos_window_info.swift +126 -126
  60. package/skills/ai-media/screenshot/scripts/take_screenshot.ps1 +163 -163
  61. package/skills/ai-media/screenshot/scripts/take_screenshot.py +585 -585
  62. package/skills/ai-media/sora/LICENSE.txt +201 -201
  63. package/skills/ai-media/sora/SKILL.md +153 -153
  64. package/skills/ai-media/sora/agents/openai.yaml +6 -6
  65. package/skills/ai-media/sora/assets/sora-small.svg +4 -4
  66. package/skills/ai-media/sora/references/cinematic-shots.md +53 -53
  67. package/skills/ai-media/sora/references/cli.md +248 -248
  68. package/skills/ai-media/sora/references/codex-network.md +28 -28
  69. package/skills/ai-media/sora/references/prompting.md +137 -137
  70. package/skills/ai-media/sora/references/sample-prompts.md +95 -95
  71. package/skills/ai-media/sora/references/social-ads.md +42 -42
  72. package/skills/ai-media/sora/references/troubleshooting.md +58 -58
  73. package/skills/ai-media/sora/references/video-api.md +45 -45
  74. package/skills/ai-media/sora/scripts/sora.py +970 -970
  75. package/skills/design/aesthetic/SKILL.md +121 -121
  76. package/skills/design/aesthetic/assets/design-guideline-template.md +163 -163
  77. package/skills/design/aesthetic/assets/design-story-template.md +135 -135
  78. package/skills/design/aesthetic/references/design-principles.md +62 -62
  79. package/skills/design/aesthetic/references/design-resources.md +75 -75
  80. package/skills/design/aesthetic/references/micro-interactions.md +53 -53
  81. package/skills/design/aesthetic/references/storytelling-design.md +50 -50
  82. package/skills/design/figma/LICENSE.txt +202 -202
  83. package/skills/design/figma/SKILL.md +42 -42
  84. package/skills/design/figma/agents/openai.yaml +14 -14
  85. package/skills/design/figma/assets/figma-small.svg +3 -3
  86. package/skills/design/figma/assets/icon.svg +28 -28
  87. package/skills/design/figma/references/figma-mcp-config.md +35 -35
  88. package/skills/design/figma/references/figma-tools-and-prompts.md +34 -34
  89. package/skills/design/figma-implement-design/LICENSE.txt +202 -202
  90. package/skills/design/figma-implement-design/SKILL.md +264 -264
  91. package/skills/design/figma-implement-design/agents/openai.yaml +14 -14
  92. package/skills/design/figma-implement-design/assets/figma-small.svg +3 -3
  93. package/skills/design/figma-implement-design/assets/icon.svg +28 -28
  94. package/skills/design/frontend-design/SKILL.md +41 -41
  95. package/skills/design/frontend-design/references/animejs.md +395 -395
  96. package/skills/design/ui-styling/LICENSE.txt +201 -201
  97. package/skills/design/ui-styling/SKILL.md +321 -321
  98. package/skills/design/ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -93
  99. package/skills/design/ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -93
  100. package/skills/design/ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -93
  101. package/skills/design/ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -93
  102. package/skills/design/ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -93
  103. package/skills/design/ui-styling/canvas-fonts/DMMono-OFL.txt +93 -93
  104. package/skills/design/ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -94
  105. package/skills/design/ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -93
  106. package/skills/design/ui-styling/canvas-fonts/Gloock-OFL.txt +93 -93
  107. package/skills/design/ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -93
  108. package/skills/design/ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -93
  109. package/skills/design/ui-styling/canvas-fonts/Italiana-OFL.txt +93 -93
  110. package/skills/design/ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -93
  111. package/skills/design/ui-styling/canvas-fonts/Jura-OFL.txt +93 -93
  112. package/skills/design/ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -93
  113. package/skills/design/ui-styling/canvas-fonts/Lora-OFL.txt +93 -93
  114. package/skills/design/ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -93
  115. package/skills/design/ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -93
  116. package/skills/design/ui-styling/canvas-fonts/Outfit-OFL.txt +93 -93
  117. package/skills/design/ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -93
  118. package/skills/design/ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -93
  119. package/skills/design/ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -93
  120. package/skills/design/ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -93
  121. package/skills/design/ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -93
  122. package/skills/design/ui-styling/canvas-fonts/Tektur-OFL.txt +93 -93
  123. package/skills/design/ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -93
  124. package/skills/design/ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -93
  125. package/skills/design/ui-styling/references/canvas-design-system.md +320 -320
  126. package/skills/design/ui-styling/references/shadcn-accessibility.md +471 -471
  127. package/skills/design/ui-styling/references/shadcn-components.md +424 -424
  128. package/skills/design/ui-styling/references/shadcn-theming.md +373 -373
  129. package/skills/design/ui-styling/references/tailwind-customization.md +483 -483
  130. package/skills/design/ui-styling/references/tailwind-responsive.md +382 -382
  131. package/skills/design/ui-styling/references/tailwind-utilities.md +455 -455
  132. package/skills/design/ui-styling/scripts/requirements.txt +17 -17
  133. package/skills/design/ui-styling/scripts/shadcn_add.py +292 -292
  134. package/skills/design/ui-styling/scripts/tailwind_config_gen.py +456 -456
  135. package/skills/design/ui-styling/scripts/tests/requirements.txt +3 -3
  136. package/skills/design/ui-styling/scripts/tests/test_shadcn_add.py +266 -266
  137. package/skills/design/ui-styling/scripts/tests/test_tailwind_config_gen.py +336 -336
  138. package/skills/engineering/aspnet-core/LICENSE.txt +201 -201
  139. package/skills/engineering/aspnet-core/SKILL.md +61 -61
  140. package/skills/engineering/aspnet-core/agents/openai.yaml +5 -5
  141. package/skills/engineering/aspnet-core/references/_sections.md +40 -40
  142. package/skills/engineering/aspnet-core/references/apis-minimal-and-controllers.md +81 -81
  143. package/skills/engineering/aspnet-core/references/data-state-and-services.md +69 -69
  144. package/skills/engineering/aspnet-core/references/program-and-pipeline.md +103 -103
  145. package/skills/engineering/aspnet-core/references/realtime-grpc-and-background-work.md +58 -58
  146. package/skills/engineering/aspnet-core/references/security-and-identity.md +75 -75
  147. package/skills/engineering/aspnet-core/references/source-map.md +43 -43
  148. package/skills/engineering/aspnet-core/references/stack-selection.md +63 -63
  149. package/skills/engineering/aspnet-core/references/testing-performance-and-operations.md +92 -92
  150. package/skills/engineering/aspnet-core/references/ui-blazor.md +53 -53
  151. package/skills/engineering/aspnet-core/references/ui-mvc.md +56 -56
  152. package/skills/engineering/aspnet-core/references/ui-razor-pages.md +55 -55
  153. package/skills/engineering/aspnet-core/references/versioning-and-upgrades.md +51 -51
  154. package/skills/engineering/backend-development/SKILL.md +95 -95
  155. package/skills/engineering/backend-development/references/backend-api-design.md +495 -495
  156. package/skills/engineering/backend-development/references/backend-architecture.md +454 -454
  157. package/skills/engineering/backend-development/references/backend-authentication.md +338 -338
  158. package/skills/engineering/backend-development/references/backend-code-quality.md +659 -659
  159. package/skills/engineering/backend-development/references/backend-debugging.md +904 -904
  160. package/skills/engineering/backend-development/references/backend-devops.md +494 -494
  161. package/skills/engineering/backend-development/references/backend-mindset.md +387 -387
  162. package/skills/engineering/backend-development/references/backend-performance.md +397 -397
  163. package/skills/engineering/backend-development/references/backend-security.md +290 -290
  164. package/skills/engineering/backend-development/references/backend-technologies.md +256 -256
  165. package/skills/engineering/backend-development/references/backend-testing.md +429 -429
  166. package/skills/engineering/better-auth/SKILL.md +204 -204
  167. package/skills/engineering/better-auth/references/advanced-features.md +553 -553
  168. package/skills/engineering/better-auth/references/database-integration.md +577 -577
  169. package/skills/engineering/better-auth/references/email-password-auth.md +416 -416
  170. package/skills/engineering/better-auth/references/oauth-providers.md +430 -430
  171. package/skills/engineering/better-auth/scripts/better_auth_init.py +521 -521
  172. package/skills/engineering/better-auth/scripts/requirements.txt +15 -15
  173. package/skills/engineering/better-auth/scripts/tests/test_better_auth_init.py +421 -421
  174. package/skills/engineering/code-review/SKILL.md +140 -140
  175. package/skills/engineering/code-review/references/code-review-reception.md +208 -208
  176. package/skills/engineering/code-review/references/requesting-code-review.md +104 -104
  177. package/skills/engineering/code-review/references/verification-before-completion.md +138 -138
  178. package/skills/engineering/context-engineering/SKILL.md +86 -86
  179. package/skills/engineering/context-engineering/references/context-compression.md +84 -84
  180. package/skills/engineering/context-engineering/references/context-degradation.md +93 -93
  181. package/skills/engineering/context-engineering/references/context-fundamentals.md +75 -75
  182. package/skills/engineering/context-engineering/references/context-optimization.md +82 -82
  183. package/skills/engineering/context-engineering/references/evaluation.md +89 -89
  184. package/skills/engineering/context-engineering/references/memory-systems.md +88 -88
  185. package/skills/engineering/context-engineering/references/multi-agent-patterns.md +90 -90
  186. package/skills/engineering/context-engineering/references/project-development.md +97 -97
  187. package/skills/engineering/context-engineering/references/tool-design.md +86 -86
  188. package/skills/engineering/context-engineering/scripts/compression_evaluator.py +329 -329
  189. package/skills/engineering/context-engineering/scripts/context_analyzer.py +294 -294
  190. package/skills/engineering/databases/SKILL.md +232 -232
  191. package/skills/engineering/databases/references/mongodb-aggregation.md +447 -447
  192. package/skills/engineering/databases/references/mongodb-atlas.md +465 -465
  193. package/skills/engineering/databases/references/mongodb-crud.md +408 -408
  194. package/skills/engineering/databases/references/mongodb-indexing.md +442 -442
  195. package/skills/engineering/databases/references/postgresql-administration.md +594 -594
  196. package/skills/engineering/databases/references/postgresql-performance.md +527 -527
  197. package/skills/engineering/databases/references/postgresql-psql-cli.md +467 -467
  198. package/skills/engineering/databases/references/postgresql-queries.md +475 -475
  199. package/skills/engineering/databases/scripts/db_backup.py +502 -502
  200. package/skills/engineering/databases/scripts/db_migrate.py +414 -414
  201. package/skills/engineering/databases/scripts/db_performance_check.py +444 -444
  202. package/skills/engineering/databases/scripts/requirements.txt +20 -20
  203. package/skills/engineering/databases/scripts/tests/requirements.txt +4 -4
  204. package/skills/engineering/databases/scripts/tests/test_db_backup.py +340 -340
  205. package/skills/engineering/databases/scripts/tests/test_db_migrate.py +277 -277
  206. package/skills/engineering/databases/scripts/tests/test_db_performance_check.py +370 -370
  207. package/skills/engineering/diagnose/SKILL.md +117 -117
  208. package/skills/engineering/diagnose/scripts/hitl-loop.template.sh +41 -41
  209. package/skills/engineering/docs-seeker/SKILL.md +207 -207
  210. package/skills/engineering/docs-seeker/WORKFLOWS.md +505 -505
  211. package/skills/engineering/docs-seeker/references/best-practices.md +632 -632
  212. package/skills/engineering/docs-seeker/references/documentation-sources.md +461 -461
  213. package/skills/engineering/docs-seeker/references/error-handling.md +621 -621
  214. package/skills/engineering/docs-seeker/references/limitations.md +821 -821
  215. package/skills/engineering/docs-seeker/references/performance.md +574 -574
  216. package/skills/engineering/docs-seeker/references/tool-selection.md +262 -262
  217. package/skills/engineering/frontend-development/SKILL.md +398 -398
  218. package/skills/engineering/frontend-development/resources/common-patterns.md +330 -330
  219. package/skills/engineering/frontend-development/resources/complete-examples.md +871 -871
  220. package/skills/engineering/frontend-development/resources/component-patterns.md +501 -501
  221. package/skills/engineering/frontend-development/resources/data-fetching.md +766 -766
  222. package/skills/engineering/frontend-development/resources/file-organization.md +501 -501
  223. package/skills/engineering/frontend-development/resources/loading-and-error-states.md +500 -500
  224. package/skills/engineering/frontend-development/resources/performance.md +405 -405
  225. package/skills/engineering/frontend-development/resources/routing-guide.md +363 -363
  226. package/skills/engineering/frontend-development/resources/styling-guide.md +427 -427
  227. package/skills/engineering/frontend-development/resources/typescript-standards.md +417 -417
  228. package/skills/engineering/improve-codebase-architecture/DEEPENING.md +37 -37
  229. package/skills/engineering/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -44
  230. package/skills/engineering/improve-codebase-architecture/LANGUAGE.md +53 -53
  231. package/skills/engineering/improve-codebase-architecture/SKILL.md +71 -71
  232. package/skills/engineering/openai-docs/LICENSE.txt +201 -201
  233. package/skills/engineering/openai-docs/SKILL.md +69 -69
  234. package/skills/engineering/openai-docs/agents/openai.yaml +14 -14
  235. package/skills/engineering/openai-docs/assets/openai-small.svg +3 -3
  236. package/skills/engineering/openai-docs/references/gpt-5p4-prompting-guide.md +433 -433
  237. package/skills/engineering/openai-docs/references/latest-model.md +35 -35
  238. package/skills/engineering/openai-docs/references/upgrading-to-gpt-5p4.md +164 -164
  239. package/skills/engineering/playwright/LICENSE.txt +201 -201
  240. package/skills/engineering/playwright/NOTICE.txt +14 -14
  241. package/skills/engineering/playwright/SKILL.md +147 -147
  242. package/skills/engineering/playwright/agents/openai.yaml +6 -6
  243. package/skills/engineering/playwright/assets/playwright-small.svg +3 -3
  244. package/skills/engineering/playwright/references/cli.md +116 -116
  245. package/skills/engineering/playwright/references/workflows.md +95 -95
  246. package/skills/engineering/playwright/scripts/playwright_cli.sh +25 -25
  247. package/skills/engineering/playwright-interactive/LICENSE.txt +201 -201
  248. package/skills/engineering/playwright-interactive/NOTICE.txt +13 -13
  249. package/skills/engineering/playwright-interactive/SKILL.md +689 -689
  250. package/skills/engineering/playwright-interactive/agents/openai.yaml +6 -6
  251. package/skills/engineering/playwright-interactive/assets/playwright-small.svg +3 -3
  252. package/skills/engineering/render-deploy/LICENSE.txt +201 -201
  253. package/skills/engineering/render-deploy/SKILL.md +479 -479
  254. package/skills/engineering/render-deploy/agents/openai.yaml +14 -14
  255. package/skills/engineering/render-deploy/assets/docker.yaml +62 -62
  256. package/skills/engineering/render-deploy/assets/go-api.yaml +35 -35
  257. package/skills/engineering/render-deploy/assets/nextjs-postgres.yaml +35 -35
  258. package/skills/engineering/render-deploy/assets/node-express.yaml +25 -25
  259. package/skills/engineering/render-deploy/assets/python-django.yaml +89 -89
  260. package/skills/engineering/render-deploy/assets/render-small.svg +3 -3
  261. package/skills/engineering/render-deploy/assets/static-site.yaml +54 -54
  262. package/skills/engineering/render-deploy/references/blueprint-spec.md +718 -718
  263. package/skills/engineering/render-deploy/references/codebase-analysis.md +49 -49
  264. package/skills/engineering/render-deploy/references/configuration-guide.md +603 -603
  265. package/skills/engineering/render-deploy/references/deployment-details.md +224 -224
  266. package/skills/engineering/render-deploy/references/direct-creation.md +113 -113
  267. package/skills/engineering/render-deploy/references/error-patterns.md +13 -13
  268. package/skills/engineering/render-deploy/references/post-deploy-checks.md +36 -36
  269. package/skills/engineering/render-deploy/references/runtimes.md +473 -473
  270. package/skills/engineering/render-deploy/references/service-types.md +450 -450
  271. package/skills/engineering/render-deploy/references/troubleshooting-basics.md +36 -36
  272. package/skills/engineering/repomix/SKILL.md +215 -215
  273. package/skills/engineering/repomix/references/configuration.md +211 -211
  274. package/skills/engineering/repomix/references/usage-patterns.md +232 -232
  275. package/skills/engineering/repomix/scripts/README.md +179 -179
  276. package/skills/engineering/repomix/scripts/repomix_batch.py +455 -455
  277. package/skills/engineering/repomix/scripts/repos.example.json +15 -15
  278. package/skills/engineering/repomix/scripts/requirements.txt +15 -15
  279. package/skills/engineering/repomix/scripts/tests/test_repomix_batch.py +531 -531
  280. package/skills/engineering/setup-matt-pocock-skills/SKILL.md +121 -121
  281. package/skills/engineering/setup-matt-pocock-skills/domain.md +51 -51
  282. package/skills/engineering/setup-matt-pocock-skills/issue-tracker-github.md +22 -22
  283. package/skills/engineering/setup-matt-pocock-skills/issue-tracker-gitlab.md +23 -23
  284. package/skills/engineering/setup-matt-pocock-skills/issue-tracker-local.md +19 -19
  285. package/skills/engineering/setup-matt-pocock-skills/triage-labels.md +15 -15
  286. package/skills/engineering/shopify/README.md +66 -66
  287. package/skills/engineering/shopify/SKILL.md +319 -319
  288. package/skills/engineering/shopify/references/app-development.md +470 -470
  289. package/skills/engineering/shopify/references/extensions.md +493 -493
  290. package/skills/engineering/shopify/references/themes.md +498 -498
  291. package/skills/engineering/shopify/scripts/requirements.txt +19 -19
  292. package/skills/engineering/shopify/scripts/shopify_init.py +423 -423
  293. package/skills/engineering/shopify/scripts/tests/test_shopify_init.py +385 -385
  294. package/skills/engineering/tdd/SKILL.md +109 -109
  295. package/skills/engineering/tdd/deep-modules.md +33 -33
  296. package/skills/engineering/tdd/interface-design.md +31 -31
  297. package/skills/engineering/tdd/mocking.md +59 -59
  298. package/skills/engineering/tdd/refactoring.md +10 -10
  299. package/skills/engineering/tdd/tests.md +61 -61
  300. package/skills/engineering/to-issues/SKILL.md +81 -81
  301. package/skills/engineering/to-prd/SKILL.md +74 -74
  302. package/skills/engineering/triage/AGENT-BRIEF.md +168 -168
  303. package/skills/engineering/triage/OUT-OF-SCOPE.md +101 -101
  304. package/skills/engineering/triage/SKILL.md +103 -103
  305. package/skills/engineering/web-frameworks/SKILL.md +324 -324
  306. package/skills/engineering/web-frameworks/references/nextjs-app-router.md +465 -465
  307. package/skills/engineering/web-frameworks/references/nextjs-data-fetching.md +459 -459
  308. package/skills/engineering/web-frameworks/references/nextjs-optimization.md +511 -511
  309. package/skills/engineering/web-frameworks/references/nextjs-server-components.md +495 -495
  310. package/skills/engineering/web-frameworks/references/remix-icon-integration.md +603 -603
  311. package/skills/engineering/web-frameworks/references/turborepo-caching.md +551 -551
  312. package/skills/engineering/web-frameworks/references/turborepo-pipelines.md +517 -517
  313. package/skills/engineering/web-frameworks/references/turborepo-setup.md +542 -542
  314. package/skills/engineering/web-frameworks/scripts/nextjs_init.py +547 -547
  315. package/skills/engineering/web-frameworks/scripts/requirements.txt +16 -16
  316. package/skills/engineering/web-frameworks/scripts/tests/requirements.txt +3 -3
  317. package/skills/engineering/web-frameworks/scripts/tests/test_nextjs_init.py +319 -319
  318. package/skills/engineering/web-frameworks/scripts/tests/test_turborepo_migrate.py +374 -374
  319. package/skills/engineering/web-frameworks/scripts/turborepo_migrate.py +394 -394
  320. package/skills/engineering/write-a-skill/SKILL.md +117 -117
  321. package/skills/kilo-kit/SKILL.md +346 -346
  322. package/skills/kilo-kit/_template/SKILL.md +185 -185
  323. package/skills/kilo-kit/debugging/root-cause/SKILL.md +360 -360
  324. package/skills/kilo-kit/debugging/systematic/SKILL.md +339 -339
  325. package/skills/kilo-kit/debugging/verification/SKILL.md +424 -424
  326. package/skills/kilo-kit/development/backend/SKILL.md +540 -540
  327. package/skills/kilo-kit/development/security/SKILL.md +529 -529
  328. package/skills/kilo-kit/quality/code-review/SKILL.md +297 -297
  329. package/skills/kilo-kit/quality/testing/SKILL.md +540 -540
  330. package/skills/kilo-kit/references/output-formats.md +204 -204
  331. package/skills/kilo-kit/references/patterns.md +156 -156
  332. package/skills/kilo-kit/references/performance-benchmarks.md +90 -90
  333. package/skills/operations/chrome-devtools/SKILL.md +392 -392
  334. package/skills/operations/chrome-devtools/references/cdp-domains.md +694 -694
  335. package/skills/operations/chrome-devtools/references/performance-guide.md +940 -940
  336. package/skills/operations/chrome-devtools/references/puppeteer-reference.md +953 -953
  337. package/skills/operations/chrome-devtools/scripts/PERSISTENT-BROWSER.md +107 -107
  338. package/skills/operations/chrome-devtools/scripts/README.md +213 -213
  339. package/skills/operations/chrome-devtools/scripts/__tests__/selector.test.js +210 -210
  340. package/skills/operations/chrome-devtools/scripts/click.js +79 -79
  341. package/skills/operations/chrome-devtools/scripts/close-persistent.js +36 -36
  342. package/skills/operations/chrome-devtools/scripts/console.js +75 -75
  343. package/skills/operations/chrome-devtools/scripts/evaluate.js +49 -49
  344. package/skills/operations/chrome-devtools/scripts/fill.js +72 -72
  345. package/skills/operations/chrome-devtools/scripts/install-deps.sh +181 -181
  346. package/skills/operations/chrome-devtools/scripts/install.sh +83 -83
  347. package/skills/operations/chrome-devtools/scripts/launch-persistent.js +71 -71
  348. package/skills/operations/chrome-devtools/scripts/lib/browser.js +144 -144
  349. package/skills/operations/chrome-devtools/scripts/lib/selector.js +178 -178
  350. package/skills/operations/chrome-devtools/scripts/navigate.js +46 -46
  351. package/skills/operations/chrome-devtools/scripts/network.js +102 -102
  352. package/skills/operations/chrome-devtools/scripts/package-lock.json +1206 -1206
  353. package/skills/operations/chrome-devtools/scripts/package.json +15 -15
  354. package/skills/operations/chrome-devtools/scripts/performance.js +145 -145
  355. package/skills/operations/chrome-devtools/scripts/screenshot.js +180 -180
  356. package/skills/operations/chrome-devtools/scripts/snapshot.js +131 -131
  357. package/skills/operations/devops/.env.example +76 -76
  358. package/skills/operations/devops/SKILL.md +285 -285
  359. package/skills/operations/devops/references/browser-rendering.md +305 -305
  360. package/skills/operations/devops/references/cloudflare-d1-kv.md +123 -123
  361. package/skills/operations/devops/references/cloudflare-platform.md +271 -271
  362. package/skills/operations/devops/references/cloudflare-r2-storage.md +280 -280
  363. package/skills/operations/devops/references/cloudflare-workers-advanced.md +312 -312
  364. package/skills/operations/devops/references/cloudflare-workers-apis.md +309 -309
  365. package/skills/operations/devops/references/cloudflare-workers-basics.md +418 -418
  366. package/skills/operations/devops/references/docker-basics.md +297 -297
  367. package/skills/operations/devops/references/docker-compose.md +292 -292
  368. package/skills/operations/devops/references/gcloud-platform.md +297 -297
  369. package/skills/operations/devops/references/gcloud-services.md +304 -304
  370. package/skills/operations/devops/scripts/cloudflare_deploy.py +269 -269
  371. package/skills/operations/devops/scripts/docker_optimize.py +320 -320
  372. package/skills/operations/devops/scripts/requirements.txt +20 -20
  373. package/skills/operations/devops/scripts/tests/requirements.txt +3 -3
  374. package/skills/operations/devops/scripts/tests/test_cloudflare_deploy.py +285 -285
  375. package/skills/operations/devops/scripts/tests/test_docker_optimize.py +436 -436
  376. package/skills/operations/mcp-builder/LICENSE.txt +201 -201
  377. package/skills/operations/mcp-builder/SKILL.md +328 -328
  378. package/skills/operations/mcp-builder/reference/evaluation.md +601 -601
  379. package/skills/operations/mcp-builder/reference/mcp_best_practices.md +915 -915
  380. package/skills/operations/mcp-builder/reference/node_mcp_server.md +915 -915
  381. package/skills/operations/mcp-builder/reference/python_mcp_server.md +751 -751
  382. package/skills/operations/mcp-builder/scripts/connections.py +151 -151
  383. package/skills/operations/mcp-builder/scripts/evaluation.py +373 -373
  384. package/skills/operations/mcp-builder/scripts/example_evaluation.xml +22 -22
  385. package/skills/operations/mcp-builder/scripts/requirements.txt +2 -2
  386. package/skills/operations/mcp-management/README.md +219 -219
  387. package/skills/operations/mcp-management/SKILL.md +175 -175
  388. package/skills/operations/mcp-management/assets/tools.json +3043 -3043
  389. package/skills/operations/mcp-management/references/configuration.md +114 -114
  390. package/skills/operations/mcp-management/references/gemini-cli-integration.md +201 -201
  391. package/skills/operations/mcp-management/references/mcp-protocol.md +116 -116
  392. package/skills/operations/mcp-management/scripts/.env.example +10 -10
  393. package/skills/operations/mcp-management/scripts/cli.ts +155 -155
  394. package/skills/operations/mcp-management/scripts/dist/analyze-tools.js +70 -70
  395. package/skills/operations/mcp-management/scripts/dist/cli.js +131 -131
  396. package/skills/operations/mcp-management/scripts/dist/mcp-client.js +115 -115
  397. package/skills/operations/mcp-management/scripts/mcp-client.ts +163 -163
  398. package/skills/operations/mcp-management/scripts/package.json +18 -18
  399. package/skills/operations/mcp-management/scripts/tsconfig.json +15 -15
  400. package/skills/problem-solving/collision-zone-thinking/SKILL.md +62 -62
  401. package/skills/problem-solving/defense-in-depth/SKILL.md +130 -130
  402. package/skills/problem-solving/inversion-exercise/SKILL.md +58 -58
  403. package/skills/problem-solving/meta-pattern-recognition/SKILL.md +54 -54
  404. package/skills/problem-solving/root-cause-tracing/SKILL.md +177 -177
  405. package/skills/problem-solving/root-cause-tracing/find-polluter.sh +63 -63
  406. package/skills/problem-solving/scale-game/SKILL.md +63 -63
  407. package/skills/problem-solving/sequential-thinking/README.md +118 -118
  408. package/skills/problem-solving/sequential-thinking/SKILL.md +93 -93
  409. package/skills/problem-solving/sequential-thinking/references/advanced.md +122 -122
  410. package/skills/problem-solving/sequential-thinking/references/examples.md +274 -274
  411. package/skills/problem-solving/simplification-cascades/SKILL.md +76 -76
  412. package/skills/problem-solving/when-stuck/SKILL.md +88 -88
  413. package/skills/productivity/caveman/SKILL.md +49 -49
  414. package/skills/productivity/grill-me/SKILL.md +10 -10
  415. package/skills/productivity/grill-with-docs/ADR-FORMAT.md +47 -47
  416. package/skills/productivity/grill-with-docs/CONTEXT-FORMAT.md +77 -77
  417. package/skills/productivity/grill-with-docs/SKILL.md +88 -88
  418. package/skills/productivity/writing-skills/graphviz-conventions.dot +171 -171
  419. package/skills/productivity/zoom-out/SKILL.md +7 -7
  420. package/skills/writing-docs/doc/LICENSE.txt +201 -201
  421. package/skills/writing-docs/doc/SKILL.md +80 -80
  422. package/skills/writing-docs/doc/agents/openai.yaml +6 -6
  423. package/skills/writing-docs/doc/assets/doc-small.svg +3 -3
  424. package/skills/writing-docs/doc/scripts/render_docx.py +296 -296
  425. package/skills/writing-docs/docx/LICENSE.txt +30 -30
  426. package/skills/writing-docs/docx/SKILL.md +196 -196
  427. package/skills/writing-docs/docx/docx-js.md +349 -349
  428. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
  429. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
  430. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
  431. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
  432. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
  433. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
  434. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
  435. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
  436. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
  437. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
  438. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
  439. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
  440. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
  441. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
  442. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
  443. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
  444. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
  445. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
  446. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
  447. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
  448. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
  449. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
  450. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
  451. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
  452. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
  453. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
  454. package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
  455. package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
  456. package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
  457. package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
  458. package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
  459. package/skills/writing-docs/docx/ooxml/schemas/mce/mc.xsd +75 -75
  460. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -560
  461. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -67
  462. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -14
  463. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -20
  464. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -13
  465. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
  466. package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -8
  467. package/skills/writing-docs/docx/ooxml/scripts/pack.py +159 -159
  468. package/skills/writing-docs/docx/ooxml/scripts/unpack.py +29 -29
  469. package/skills/writing-docs/docx/ooxml/scripts/validate.py +69 -69
  470. package/skills/writing-docs/docx/ooxml/scripts/validation/__init__.py +15 -15
  471. package/skills/writing-docs/docx/ooxml/scripts/validation/base.py +951 -951
  472. package/skills/writing-docs/docx/ooxml/scripts/validation/docx.py +274 -274
  473. package/skills/writing-docs/docx/ooxml/scripts/validation/pptx.py +315 -315
  474. package/skills/writing-docs/docx/ooxml/scripts/validation/redlining.py +279 -279
  475. package/skills/writing-docs/docx/ooxml.md +609 -609
  476. package/skills/writing-docs/docx/scripts/__init__.py +1 -1
  477. package/skills/writing-docs/docx/scripts/document.py +1276 -1276
  478. package/skills/writing-docs/docx/scripts/templates/comments.xml +2 -2
  479. package/skills/writing-docs/docx/scripts/templates/commentsExtended.xml +2 -2
  480. package/skills/writing-docs/docx/scripts/templates/commentsExtensible.xml +2 -2
  481. package/skills/writing-docs/docx/scripts/templates/commentsIds.xml +2 -2
  482. package/skills/writing-docs/docx/scripts/templates/people.xml +2 -2
  483. package/skills/writing-docs/docx/scripts/utilities.py +374 -374
  484. package/skills/writing-docs/mermaidjs-v11/SKILL.md +115 -115
  485. package/skills/writing-docs/mermaidjs-v11/references/cli-usage.md +228 -228
  486. package/skills/writing-docs/mermaidjs-v11/references/configuration.md +232 -232
  487. package/skills/writing-docs/mermaidjs-v11/references/diagram-types.md +315 -315
  488. package/skills/writing-docs/mermaidjs-v11/references/examples.md +344 -344
  489. package/skills/writing-docs/mermaidjs-v11/references/integration.md +310 -310
  490. package/skills/writing-docs/pdf/LICENSE.txt +30 -30
  491. package/skills/writing-docs/pdf/SKILL.md +294 -294
  492. package/skills/writing-docs/pdf/forms.md +205 -205
  493. package/skills/writing-docs/pdf/reference.md +611 -611
  494. package/skills/writing-docs/pdf/scripts/check_bounding_boxes.py +70 -70
  495. package/skills/writing-docs/pdf/scripts/check_bounding_boxes_test.py +226 -226
  496. package/skills/writing-docs/pdf/scripts/check_fillable_fields.py +12 -12
  497. package/skills/writing-docs/pdf/scripts/convert_pdf_to_images.py +35 -35
  498. package/skills/writing-docs/pdf/scripts/create_validation_image.py +41 -41
  499. package/skills/writing-docs/pdf/scripts/extract_form_field_info.py +152 -152
  500. package/skills/writing-docs/pdf/scripts/fill_fillable_fields.py +114 -114
  501. package/skills/writing-docs/pdf/scripts/fill_pdf_form_with_annotations.py +107 -107
  502. package/skills/writing-docs/pptx/LICENSE.txt +30 -30
  503. package/skills/writing-docs/pptx/SKILL.md +483 -483
  504. package/skills/writing-docs/pptx/html2pptx.md +624 -624
  505. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
  506. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
  507. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
  508. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
  509. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
  510. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
  511. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
  512. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
  513. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
  514. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
  515. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
  516. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
  517. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
  518. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
  519. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
  520. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
  521. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
  522. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
  523. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
  524. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
  525. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
  526. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
  527. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
  528. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
  529. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
  530. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
  531. package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
  532. package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
  533. package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
  534. package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
  535. package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
  536. package/skills/writing-docs/pptx/ooxml/schemas/mce/mc.xsd +75 -75
  537. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -560
  538. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -67
  539. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -14
  540. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -20
  541. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -13
  542. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
  543. package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -8
  544. package/skills/writing-docs/pptx/ooxml/scripts/pack.py +159 -159
  545. package/skills/writing-docs/pptx/ooxml/scripts/unpack.py +29 -29
  546. package/skills/writing-docs/pptx/ooxml/scripts/validate.py +69 -69
  547. package/skills/writing-docs/pptx/ooxml/scripts/validation/__init__.py +15 -15
  548. package/skills/writing-docs/pptx/ooxml/scripts/validation/base.py +951 -951
  549. package/skills/writing-docs/pptx/ooxml/scripts/validation/docx.py +274 -274
  550. package/skills/writing-docs/pptx/ooxml/scripts/validation/pptx.py +315 -315
  551. package/skills/writing-docs/pptx/ooxml/scripts/validation/redlining.py +279 -279
  552. package/skills/writing-docs/pptx/ooxml.md +426 -426
  553. package/skills/writing-docs/pptx/scripts/html2pptx.js +978 -978
  554. package/skills/writing-docs/pptx/scripts/inventory.py +1020 -1020
  555. package/skills/writing-docs/pptx/scripts/rearrange.py +231 -231
  556. package/skills/writing-docs/pptx/scripts/replace.py +385 -385
  557. package/skills/writing-docs/pptx/scripts/thumbnail.py +450 -450
  558. package/skills/writing-docs/slides/LICENSE.txt +201 -201
  559. package/skills/writing-docs/slides/SKILL.md +71 -71
  560. package/skills/writing-docs/slides/agents/openai.yaml +6 -6
  561. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/code.js +104 -104
  562. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/image.js +333 -333
  563. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/index.js +33 -33
  564. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/latex.js +51 -51
  565. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/layout.js +643 -643
  566. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/layout_builders.js +358 -358
  567. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/svg.js +36 -36
  568. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/text.js +789 -789
  569. package/skills/writing-docs/slides/assets/pptxgenjs_helpers/util.js +24 -24
  570. package/skills/writing-docs/slides/assets/slides-small.svg +3 -3
  571. package/skills/writing-docs/slides/references/pptxgenjs-helpers.md +61 -61
  572. package/skills/writing-docs/slides/scripts/create_montage.py +300 -300
  573. package/skills/writing-docs/slides/scripts/detect_font.py +873 -873
  574. package/skills/writing-docs/slides/scripts/ensure_raster_image.py +202 -202
  575. package/skills/writing-docs/slides/scripts/render_slides.py +273 -273
  576. package/skills/writing-docs/slides/scripts/slides_test.py +201 -201
  577. package/skills/writing-docs/template-skill/SKILL.md +26 -26
  578. package/skills/writing-docs/xlsx/LICENSE.txt +30 -30
  579. package/skills/writing-docs/xlsx/SKILL.md +288 -288
  580. package/skills/writing-docs/xlsx/recalc.py +177 -177
  581. package/src/core/KILO_MASTER.md +448 -448
  582. package/src/tools/validate-skill.js +421 -421
@@ -1,789 +1,789 @@
1
- // Copyright (c) OpenAI. All rights reserved.
2
- "use strict";
3
-
4
- const { spawnSync } = require("child_process");
5
- const { Canvas } = require("skia-canvas");
6
- // Unicode line-break iterator (UAX #14) so we mimic PPT/LibreOffice wrapping rules.
7
- const LineBreaker = require("linebreak");
8
- const fontkit = require("fontkit");
9
- const TEXT_MEASURER = getTextMeasurer();
10
- const registeredFontVariants = new Set();
11
- const fontPathCache = new Map();
12
- const fontKitCache = new Map();
13
-
14
- // Estimate the text box height for a given font size and line count.
15
- // NOTE: This is an analytical approximation, not an exact reproduction of
16
- // PowerPoint/LibreOffice layout. Always verify visually and adjust based on
17
- // actual rendering if precise fit is required.
18
- function calcTextBoxHeightSimple(
19
- fontSize,
20
- lines = 1,
21
- leading = 1.15,
22
- padding = 0.3
23
- ) {
24
- const lineHeightIn = (fontSize / 72) * leading;
25
- return lines * lineHeightIn + padding;
26
- }
27
-
28
- // Compute font size that fits given text within a fixed box.
29
- // NOTE: autoFontSize uses skia-canvas measurement stack to approximate the font size
30
- // that will fit in a given box. Rendering engines may differ slightly, so
31
- // treat the result as an estimate and tweak as needed after visual inspection.
32
- // Signature:
33
- // autoFontSize(textOrRuns, fontFace, opts?)
34
- // - fontFace must be provided as the 2nd positional argument and cannot be in opts.
35
- // - All modes always respect [minFontSize, maxFontSize] as a CLOSED interval when provided.
36
- // Modes:
37
- // - mode: "shrink" => shrink only (search [minFontSize, min(maxFontSize, fontSize)])
38
- // - mode: "enlarge" => enlarge only (search [max(minFontSize, fontSize), maxFontSize])
39
- // - mode: "auto" => shrink + enlarge (search [minFontSize, maxFontSize]); fontSize optional.
40
- // In "auto" mode fontSize is not required; when omitted we simply search the whole [minFontSize, maxFontSize] range.
41
- // Returns a cloned options object with computed fontSize. fit: "shrink" is appended only when mode === "shrink".
42
- function autoFontSize(textOrRuns, fontFace, opts = {}) {
43
- const x = toNumber(opts.x, 0);
44
- const y = toNumber(opts.y, 0);
45
- const w = toNumber(opts.w, 0);
46
- const h = toNumber(opts.h, 0);
47
- if (!(w > 0 && h > 0)) throw new Error("autoFontSize(): non-positive w or h");
48
-
49
- const face = typeof fontFace === "string" ? fontFace.trim() : "";
50
- if (face.length === 0) {
51
- throw new Error(
52
- "autoFontSize(): fontFace is required as the 2nd positional argument."
53
- );
54
- }
55
-
56
- // Fast-path: if there is no visible text content, just return the
57
- // (optionally clamped) reference fontSize; there is nothing to fit.
58
- const hasAnyText =
59
- normalizeText(textOrRuns).trim().length > 0 ||
60
- (Array.isArray(textOrRuns) &&
61
- textOrRuns.some(
62
- (run) => run && typeof run.text === "string" && run.text.trim().length
63
- ));
64
-
65
- const fontStyle =
66
- opts.italic === true || opts.fontStyle === "italic" ? "italic" : "normal";
67
- const fontWeight =
68
- opts.bold === true || String(opts.fontWeight || "").toLowerCase() === "bold"
69
- ? "bold"
70
- : "normal";
71
- const leading = toNumber(opts.leading, 1.15) || 1.15;
72
-
73
- const modeRaw = typeof opts.mode === "string" ? opts.mode : "auto"; // 'auto' (default) | 'shrink' | 'enlarge'
74
- const mode = modeRaw.toLowerCase();
75
- const isShrink = mode === "shrink";
76
- const isEnlarge = mode === "enlarge";
77
- const isAuto = mode === "auto";
78
-
79
- const refPtRaw = toNumber(opts.fontSize, NaN);
80
- const hasRefPt = Number.isFinite(refPtRaw);
81
- const refPt = hasRefPt ? refPtRaw : NaN;
82
-
83
- // Base bounds (closed interval). Defaults:
84
- // - minFontSize: 1pt
85
- // - maxFontSize: 1000pt (unless the caller provided a tighter bound)
86
- let minPt = toNumber(opts.minFontSize, NaN);
87
- let maxPt = toNumber(opts.maxFontSize, NaN);
88
- const userProvidedMax = Number.isFinite(maxPt);
89
- if (!Number.isFinite(minPt)) {
90
- minPt = 1;
91
- }
92
- if (!Number.isFinite(maxPt)) {
93
- maxPt = 1000;
94
- }
95
-
96
- if (isShrink || isEnlarge) {
97
- if (!hasRefPt) {
98
- throw new Error(
99
- "autoFontSize(): mode 'shrink' or 'enlarge' requires fontSize"
100
- );
101
- }
102
- }
103
-
104
- if (isShrink) {
105
- // Shrink only: never exceed the requested size (and respect maxFontSize).
106
- maxPt = Math.min(maxPt, refPt);
107
- } else if (isEnlarge) {
108
- // Enlarge only: never go below the requested size (and respect minFontSize).
109
- minPt = Math.max(minPt, refPt);
110
- } else if (isAuto && hasRefPt && userProvidedMax) {
111
- // Auto mode with an explicit maxFontSize: honor [minFontSize, maxFontSize]
112
- // as the search band while allowing both shrink and enlarge within it.
113
- } else if (!isAuto) {
114
- throw new Error(
115
- `autoFontSize(): unsupported mode "${modeRaw}", expected "auto" | "shrink" | "enlarge"`
116
- );
117
- }
118
-
119
- if (!(maxPt > 0 && maxPt >= minPt)) {
120
- throw new Error(
121
- "autoFontSize(): invalid minFontSize/maxFontSize bounds after normalization"
122
- );
123
- }
124
-
125
- // If there is no actual text, we can skip measurement entirely and just
126
- // clamp the reference size to [minPt, maxPt].
127
- if (!hasAnyText) {
128
- const chosen =
129
- (hasRefPt && Math.max(minPt, Math.min(maxPt, refPt))) || minPt;
130
- const out = { ...opts, x, y, w, h, fontSize: chosen };
131
- if (isShrink) out.fit = "shrink";
132
- return out;
133
- }
134
-
135
- // Search the space of candidate font sizes with a small step and a safety
136
- // bias baked into the fit test:
137
- // - precision: 0.05pt (~1/20pt) so we land very close to the true max-fit.
138
- // - safetyFactor: we require that the calcTextBox()-measured height is
139
- // within a small margin of the caller-provided box height, so that the
140
- // same layout engine used by calcTextBox drives autoFontSize decisions.
141
- const precision = 0.05; // point precision for search (~1/20pt)
142
- const safetyFactor = 0.97;
143
-
144
- let lo = minPt;
145
- let hi = maxPt;
146
- let best = lo;
147
- while (hi - lo > precision) {
148
- const mid = (lo + hi) / 2;
149
- // Delegate measurement to calcTextBox so that autoFontSize and
150
- // calcTextBox share the exact same layout pipeline (paragraph modeling,
151
- // bullet handling, margins, padding, width scaling, etc.).
152
- const layout = calcTextBox(mid, {
153
- text: textOrRuns,
154
- w,
155
- fontFace: face,
156
- fontStyle,
157
- fontWeight,
158
- leading,
159
- margin: opts.margin,
160
- padding: opts.padding,
161
- paraSpaceAfter: opts.paraSpaceAfter,
162
- });
163
- const fits = layout.h <= h * safetyFactor + 1e-6;
164
- if (fits) {
165
- best = mid;
166
- lo = mid; // try larger
167
- } else {
168
- hi = mid; // shrink
169
- }
170
- }
171
- // Closed interval: clamp to [minPt, maxPt].
172
- const finalPt = Math.max(minPt, Math.min(maxPt, best));
173
-
174
- // Pass through all original options, override fontSize and append fit: "shrink"
175
- const out = { ...opts, x, y, w, h, fontSize: finalPt };
176
- if (isShrink) out.fit = "shrink";
177
- return out;
178
- }
179
-
180
- // Calculate text box metrics using skia-canvas measurement (lines, height,
181
- // width) for a given font size and text payload.
182
- // NOTE: calcTextBox approximates how many lines and how much space text will
183
- // occupy using our JS measurement pipeline. It is designed to be close to
184
- // PowerPoint/LibreOffice but is not guaranteed pixel-perfect—always adjust
185
- // based on actual slide rendering when precision matters.
186
- // Signature:
187
- // calcTextBox(fontSizePt, opts)
188
- // - fontSizePt: number (points)
189
- // - opts (keywords): {
190
- // text?: string | runs[],
191
- // w?: number (inches),
192
- // h?: number (inches),
193
- // lines?: number,
194
- // fontFace?: string, // required when measuring by width/height with text
195
- // fontStyle?: 'normal' | 'italic', italic?: boolean,
196
- // fontWeight?: 'normal' | 'bold', bold?: boolean,
197
- // leading?: number (line height multiplier, default 1.15),
198
- // padding?: number (inches, default 0.3),
199
- // paraSpaceAfter?: number (points, default 0)
200
- // }
201
- // Modes (auto-detected):
202
- // a) Given lines -> compute height
203
- // b) Given width + text -> compute height and lines
204
- // c) Given height + text -> compute width and lines
205
- // Throws when insufficient info is provided.
206
- function calcTextBox(fontSizePt, opts = {}) {
207
- const textInput = opts.text ?? "";
208
- const text = normalizeText(textInput || "");
209
- const face =
210
- typeof opts.fontFace === "string" && opts.fontFace.trim().length > 0
211
- ? opts.fontFace.trim()
212
- : "";
213
- const fontStyle =
214
- opts.italic === true || opts.fontStyle === "italic" ? "italic" : "normal";
215
- const fontWeight =
216
- opts.bold === true || String(opts.fontWeight || "").toLowerCase() === "bold"
217
- ? "bold"
218
- : "normal";
219
- const leading = toNumber(opts.leading, 1.15) || 1.15;
220
- const padding = toNumber(opts.padding, 0.3); // inches (allow 0)
221
- const paraSpaceAfterPt = toNumber(opts.paraSpaceAfter, 0) || 0; // points
222
- const lineHeightIn = (fontSizePt / 72) * leading;
223
- const margins = normalizeMargins(opts.margin);
224
- const measurer = TEXT_MEASURER;
225
-
226
- const hasLines = Number.isFinite(toNumber(opts.lines, NaN));
227
- const hasWidth = Number.isFinite(toNumber(opts.w, NaN));
228
- const hasHeight = Number.isFinite(toNumber(opts.h, NaN));
229
- const paragraphs = buildParagraphModels(textInput, {
230
- fontSizePt,
231
- // Do not silently substitute a default font here; callers measuring by
232
- // width/height are required to pass an explicit fontFace so that our
233
- // metrics match the actual slide theme.
234
- fontFace: face,
235
- fontStyle,
236
- fontWeight,
237
- leading,
238
- paraSpaceAfterPt,
239
- });
240
- const hasAnyText = paragraphs.some((p) => p.text.length > 0);
241
-
242
- // Empirical top inset: PPT text frames render a small gutter above the first line
243
- // even with zero margins. Model it as a fraction of the font size so callers can
244
- // visually trim by shifting y up and growing h by the same amount.
245
- const topInsetIn = (fontSizePt / 72) * 0.2; // ~20% of font size (inches)
246
-
247
- if (hasLines) {
248
- // Mode (a): Given lines -> compute height only
249
- const lines = toNumber(opts.lines, 1);
250
- const contentH = Math.max(0, lines * lineHeightIn + padding);
251
- const h = contentH + margins.top + margins.bottom;
252
- const passthrough = buildPassthroughOptions(opts, fontSizePt, margins);
253
- return {
254
- ...passthrough,
255
- w: toNumber(opts.w, NaN) || null,
256
- h,
257
- lines,
258
- contentH,
259
- margins,
260
- topInset: topInsetIn,
261
- };
262
- }
263
-
264
- if (hasWidth && hasAnyText) {
265
- // Mode (b): Given width + text -> compute height and lines
266
- if (face.length === 0) {
267
- throw new Error(
268
- "calcTextBox(): opts.fontFace is required when measuring by width."
269
- );
270
- }
271
- const boxW = toNumber(opts.w, 0);
272
- if (!(boxW > 0))
273
- throw new Error("calcTextBox(): width must be > 0 in mode 'width'");
274
- const innerW = Math.max(0, boxW - margins.left - margins.right);
275
- const { lines, heightIn } = layoutGivenWidth(paragraphs, innerW);
276
- const contentH = Math.max(0, heightIn + padding);
277
- const h = contentH + margins.top + margins.bottom;
278
- const passthrough = buildPassthroughOptions(opts, fontSizePt, margins);
279
- return {
280
- ...passthrough,
281
- w: boxW,
282
- h,
283
- lines,
284
- contentH,
285
- margins,
286
- topInset: topInsetIn,
287
- };
288
- }
289
-
290
- if (hasHeight && hasAnyText) {
291
- // Mode (c): Given height + text -> compute minimal width and lines to fit
292
- if (face.length === 0) {
293
- throw new Error(
294
- "calcTextBox(): opts.fontFace is required when measuring by height."
295
- );
296
- }
297
- const boxH = toNumber(opts.h, 0);
298
- if (!(boxH > 0))
299
- throw new Error("calcTextBox(): height must be > 0 in mode 'height'");
300
- const innerH = Math.max(0, boxH - margins.top - margins.bottom);
301
- // Upper bound: single-line width across paragraphs
302
- const singleLineWidth = paragraphs.reduce((mx, p) => {
303
- const width = measureRunWidth(p, p.text) + p.textIndentIn;
304
- return Math.max(mx, width);
305
- }, 0);
306
- const minHeightOneLine = Math.max(
307
- 0,
308
- paragraphs.reduce((sum, p, idx) => {
309
- const lineHeight = (p.fontSizePt / 72) * p.leading;
310
- sum += lineHeight;
311
- if (idx !== paragraphs.length - 1) sum += p.paraSpaceAfterIn;
312
- return sum;
313
- }, 0)
314
- );
315
- if (minHeightOneLine + padding - innerH > 1e-6) {
316
- throw new Error(
317
- "calcTextBox(): height too small for one-line layout at this font size"
318
- );
319
- }
320
- // Lower bound: longest token width
321
- const longestTokenWidth = paragraphs.reduce((mx, p) => {
322
- const tokens = splitTextIntoTokens(p.text);
323
- for (const tk of tokens) {
324
- if (tk.length === 0) continue;
325
- const wIn = measureRunWidth(p, tk) + p.textIndentIn;
326
- if (wIn > mx) mx = wIn;
327
- }
328
- return mx;
329
- }, 0);
330
- let lo = Math.max(0.01, longestTokenWidth);
331
- let hi = Math.max(lo, singleLineWidth);
332
- let best = hi;
333
- for (let iter = 0; iter < 32; iter++) {
334
- const mid = (lo + hi) / 2;
335
- const { lines, heightIn } = layoutGivenWidth(paragraphs, mid);
336
- const totalH = heightIn + padding;
337
- if (totalH <= innerH + 1e-6) {
338
- best = mid;
339
- hi = mid;
340
- } else {
341
- lo = mid;
342
- }
343
- }
344
- const { lines, heightIn } = layoutGivenWidth(paragraphs, best);
345
- const contentH = heightIn + padding;
346
- const passthrough = buildPassthroughOptions(opts, fontSizePt, margins);
347
- return {
348
- ...passthrough,
349
- w: best + margins.left + margins.right,
350
- h: contentH + margins.top + margins.bottom,
351
- lines,
352
- contentH,
353
- margins,
354
- topInset: topInsetIn,
355
- };
356
- }
357
-
358
- throw new Error(
359
- "calcTextBox(): insufficient information. Provide {lines} or ({w,text}) or ({h,text})."
360
- );
361
- }
362
-
363
- function layoutGivenWidth(paragraphs, boxW) {
364
- let totalLines = 0;
365
- let heightIn = 0;
366
- for (let i = 0; i < paragraphs.length; i++) {
367
- const para = paragraphs[i];
368
- const widthScale = getWidthScaleForParagraph(para);
369
- const usableWidth = Math.max(0.01, boxW - para.textIndentIn) * widthScale;
370
- const lines = greedyWrap(para, usableWidth);
371
- const count = Math.max(1, lines.length);
372
- totalLines += count;
373
- const lineHeightIn = (para.fontSizePt / 72) * para.leading;
374
- heightIn += count * lineHeightIn;
375
- if (i !== paragraphs.length - 1) heightIn += para.paraSpaceAfterIn;
376
- }
377
- return { lines: totalLines, heightIn };
378
- }
379
-
380
- function greedyWrap(paragraph, maxWidthIn) {
381
- const text = paragraph.text || "";
382
- if (text.length === 0) return [""];
383
- const breaker = new LineBreaker(text);
384
- const breakpoints = [];
385
- let bk;
386
- while ((bk = breaker.nextBreak())) {
387
- breakpoints.push({ pos: bk.position, required: bk.required });
388
- }
389
- const lines = [];
390
- let start = skipTextWhitespace(text, 0);
391
- let idx = 0;
392
- while (start < text.length) {
393
- while (idx < breakpoints.length && breakpoints[idx].pos <= start) idx++;
394
- let chosen = null;
395
- let probe = idx;
396
- while (probe < breakpoints.length) {
397
- const br = breakpoints[probe];
398
- const slice = text.slice(start, br.pos);
399
- const width = measureRunWidth(paragraph, trimLineEnd(slice));
400
- if (width <= maxWidthIn + 1e-6) {
401
- chosen = br;
402
- probe++;
403
- if (br.required) break;
404
- } else {
405
- break;
406
- }
407
- }
408
- if (!chosen) {
409
- const forced = forceBreakSegment(text, start, maxWidthIn, paragraph);
410
- if (forced.segment.length === 0) break;
411
- lines.push(trimLineEnd(forced.segment));
412
- start = skipTextWhitespace(text, forced.nextIndex);
413
- continue;
414
- }
415
- const lineText = text.slice(start, chosen.pos);
416
- lines.push(trimLineEnd(lineText));
417
- start = skipTextWhitespace(text, chosen.pos);
418
- }
419
- if (!lines.length) lines.push("");
420
- return lines;
421
- }
422
-
423
- function splitTextIntoTokens(text) {
424
- if (typeof text !== "string") return [""];
425
- const tokens = text.split(/(\s+)/);
426
- return tokens.length ? tokens : [""];
427
- }
428
-
429
- function trimLineEnd(value) {
430
- return typeof value === "string" ? value.replace(/\s+$/u, "") : "";
431
- }
432
-
433
- function measureRunWidth(paragraph, text) {
434
- if (!text || text.length === 0) return 0;
435
- const fontData = getFontData(
436
- paragraph.fontFace,
437
- paragraph.fontStyle,
438
- paragraph.fontWeight
439
- );
440
- if (fontData && fontData.font) {
441
- const layout = fontData.font.layout(text);
442
- const widthPts =
443
- (layout.advanceWidth / fontData.font.unitsPerEm) * paragraph.fontSizePt;
444
- return Math.max(0, widthPts / 72);
445
- }
446
- return TEXT_MEASURER(
447
- text,
448
- paragraph.fontSizePt,
449
- paragraph.fontFace,
450
- paragraph.fontStyle,
451
- paragraph.fontWeight
452
- );
453
- }
454
-
455
- function forceBreakSegment(text, start, maxWidthIn, paragraph) {
456
- const chars = Array.from(text.slice(start));
457
- if (chars.length === 0) return { segment: "", nextIndex: text.length };
458
- let buffer = "";
459
- let consumedUnits = 0;
460
- for (let i = 0; i < chars.length; i++) {
461
- const candidate = buffer + chars[i];
462
- const width = measureRunWidth(paragraph, trimLineEnd(candidate));
463
- if (width <= maxWidthIn + 1e-6) {
464
- buffer = candidate;
465
- consumedUnits += chars[i].length;
466
- continue;
467
- }
468
- if (buffer.length === 0) {
469
- buffer = chars[i];
470
- consumedUnits += chars[i].length;
471
- }
472
- break;
473
- }
474
- if (buffer.length === 0) {
475
- buffer = chars[0] || "";
476
- consumedUnits = buffer.length;
477
- }
478
- return { segment: buffer, nextIndex: start + consumedUnits };
479
- }
480
-
481
- function skipTextWhitespace(text, index) {
482
- let idx = index;
483
- while (idx < text.length && /\s/.test(text[idx])) idx++;
484
- return idx;
485
- }
486
-
487
- function buildParagraphModels(textOrRuns, baseStyle) {
488
- const entries = collectParagraphEntries(textOrRuns);
489
- if (entries.length === 0) {
490
- return [resolveParagraphStyle({ text: "" }, baseStyle)];
491
- }
492
- return entries.map((entry) => resolveParagraphStyle(entry, baseStyle));
493
- }
494
-
495
- function collectParagraphEntries(textOrRuns) {
496
- const result = [];
497
- if (Array.isArray(textOrRuns)) {
498
- for (const entry of textOrRuns) {
499
- if (typeof entry === "string") {
500
- pushParagraphSegments(entry, undefined, result);
501
- } else if (entry && typeof entry === "object") {
502
- pushParagraphSegments(entry.text ?? "", entry.options || {}, result);
503
- }
504
- }
505
- return result;
506
- }
507
- pushParagraphSegments(textOrRuns ?? "", undefined, result);
508
- return result;
509
- }
510
-
511
- function pushParagraphSegments(text, options, target) {
512
- const normalized = String(text ?? "");
513
- const parts = normalized.split(/\r?\n/);
514
- if (parts.length === 0) {
515
- target.push({ text: "", options });
516
- return;
517
- }
518
- for (const part of parts) {
519
- target.push({ text: part, options });
520
- }
521
- }
522
-
523
- function resolveParagraphStyle(entry, baseStyle) {
524
- const opts = entry.options || {};
525
- const fontFace =
526
- (opts.fontFace && String(opts.fontFace).trim()) ||
527
- baseStyle.fontFace ||
528
- "Arial";
529
- const fontStyle =
530
- opts.italic === true || opts.fontStyle === "italic"
531
- ? "italic"
532
- : baseStyle.fontStyle || "normal";
533
- const fontWeight =
534
- opts.bold === true || String(opts.fontWeight || "").toLowerCase() === "bold"
535
- ? "bold"
536
- : baseStyle.fontWeight || "normal";
537
- const fontSizePt =
538
- toNumber(opts.fontSize, baseStyle.fontSizePt) || baseStyle.fontSizePt;
539
- const leading =
540
- toNumber(opts.leading, baseStyle.leading) || baseStyle.leading || 1.15;
541
- const paraSpaceAfterPt =
542
- toNumber(opts.paraSpaceAfter, baseStyle.paraSpaceAfterPt) ||
543
- baseStyle.paraSpaceAfterPt ||
544
- 0;
545
- const hasBullet = !!opts.bullet;
546
- let indentPt = toNumber(opts.indent, NaN);
547
- if (!Number.isFinite(indentPt) && hasBullet) {
548
- indentPt = toNumber(opts.bullet.indent, NaN);
549
- }
550
- if (!Number.isFinite(indentPt)) indentPt = 0;
551
- const hangingPt = toNumber(opts.hanging, 0) || 0;
552
- let textIndentIn = 0;
553
- if (indentPt > 0) {
554
- if (hasBullet) {
555
- // PowerPoint-style bullets: "indent" is the distance from the left edge
556
- // of the text box to the start of the text (the bullet itself is hung
557
- // using the hanging value). This means the available width for the text
558
- // is boxWidth - indent, not boxWidth - (indent - hanging). Modeling it
559
- // this way matches the manual line counts from PowerPoint/LibreOffice.
560
- textIndentIn = indentPt / 72;
561
- } else {
562
- // Non-bullet paragraphs keep the prior behavior where hanging reduces
563
- // the effective indent (similar to CSS text-indent).
564
- textIndentIn = Math.max(0, (indentPt - hangingPt) / 72);
565
- }
566
- }
567
- return {
568
- text: entry.text || "",
569
- fontFace,
570
- fontStyle,
571
- fontWeight,
572
- fontSizePt,
573
- leading,
574
- paraSpaceAfterIn: paraSpaceAfterPt / 72,
575
- textIndentIn,
576
- };
577
- }
578
-
579
- function getFontData(face, fontStyle, fontWeight) {
580
- const key = makeFontCacheKey(face, fontStyle, fontWeight);
581
- if (fontKitCache.has(key)) return fontKitCache.get(key);
582
- const fontPath = findFontPath(face, fontStyle, fontWeight);
583
- if (!fontPath) {
584
- fontKitCache.set(key, null);
585
- return null;
586
- }
587
- try {
588
- let font = fontkit.openSync(fontPath);
589
- if (font && typeof font.fonts === "object") {
590
- font = selectCollectionFont(font, fontStyle, fontWeight);
591
- }
592
- if (!font || typeof font.layout !== "function") {
593
- fontKitCache.set(key, null);
594
- return null;
595
- }
596
- registerCanvasFontVariant(fontPath, face, fontStyle, fontWeight, key);
597
- const payload = { font, path: fontPath };
598
- fontKitCache.set(key, payload);
599
- return payload;
600
- } catch (err) {
601
- fontKitCache.set(key, null);
602
- return null;
603
- }
604
- }
605
-
606
- function makeFontCacheKey(face, fontStyle, fontWeight) {
607
- const family = (face || "Arial").trim();
608
- const style = (fontStyle || "normal").toLowerCase();
609
- const weight = (fontWeight || "normal").toLowerCase();
610
- return `${family}::${style}::${weight}`;
611
- }
612
-
613
- function registerCanvasFontVariant(
614
- fontPath,
615
- face,
616
- fontStyle,
617
- fontWeight,
618
- cacheKey
619
- ) {
620
- if (registeredFontVariants.has(cacheKey)) return;
621
- try {
622
- Canvas.registerFont(fontPath, {
623
- family: face,
624
- style: fontStyle || "normal",
625
- weight: fontWeight || "normal",
626
- });
627
- registeredFontVariants.add(cacheKey);
628
- } catch (err) {
629
- // ignore registration failure; measurement will fall back to Skia default
630
- }
631
- }
632
-
633
- function findFontPath(face, fontStyle, fontWeight) {
634
- const family = (face || "").trim();
635
- if (family.length === 0) return null;
636
- const key = makeFontCacheKey(family, fontStyle, fontWeight);
637
- if (fontPathCache.has(key)) return fontPathCache.get(key);
638
- const styleParts = [];
639
- if ((fontWeight || "").toLowerCase() === "bold") styleParts.push("Bold");
640
- if ((fontStyle || "").toLowerCase() === "italic") styleParts.push("Italic");
641
- const styleQuery =
642
- styleParts.length > 0 ? `:style=${styleParts.join(" ")}` : "";
643
- const query = `${family}${styleQuery}`;
644
- const result = spawnSync("fc-match", ["-f", "%{file}", query], {
645
- encoding: "utf8",
646
- });
647
- if (result.status === 0) {
648
- const output = String(result.stdout || "").trim();
649
- if (output.length > 0) {
650
- fontPathCache.set(key, output);
651
- return output;
652
- }
653
- }
654
- fontPathCache.set(key, null);
655
- return null;
656
- }
657
-
658
- function selectCollectionFont(collection, fontStyle, fontWeight) {
659
- const fonts = collection.fonts || [];
660
- if (fonts.length === 0) return null;
661
- const wantItalic = (fontStyle || "").toLowerCase() === "italic";
662
- const wantBold = (fontWeight || "").toLowerCase() === "bold";
663
- let best = fonts[0];
664
- let bestScore = scoreFontVariant(best, wantItalic, wantBold);
665
- for (let i = 1; i < fonts.length; i++) {
666
- const candidate = fonts[i];
667
- const score = scoreFontVariant(candidate, wantItalic, wantBold);
668
- if (score > bestScore) {
669
- best = candidate;
670
- bestScore = score;
671
- }
672
- }
673
- return best;
674
- }
675
-
676
- function scoreFontVariant(font, wantItalic, wantBold) {
677
- if (!font) return -1;
678
- const name = String(font.fullName || font.postscriptName || "").toLowerCase();
679
- const isItalic = /italic|oblique/.test(name);
680
- const isBold = /bold|black|heavy|semibold|extrabold/.test(name);
681
- let score = 0;
682
- if (isItalic === wantItalic) score += 1;
683
- if (isBold === wantBold) score += 1;
684
- return score;
685
- }
686
-
687
- // Empirical width scaling to better match PowerPoint/LibreOffice line breaks.
688
- // A tiny global shrink (about -1.5%) nudges borderline words to wrap the same
689
- // way Office does, with per-script tweaks for cases where our measurer
690
- // systematically under- or over-estimates glyph widths. We intentionally avoid
691
- // per-font calibration so this helper generalizes beyond the regression deck.
692
- function getWidthScaleForParagraph(paragraph) {
693
- if (!paragraph || typeof paragraph.text !== "string") return 1;
694
- const text = paragraph.text;
695
- // Thai script: our measurer tends to slightly over-estimate, which can cause
696
- // extra wraps. Give it a bit more room horizontally.
697
- if (/[ก-๛]/u.test(text)) {
698
- return 1.2;
699
- }
700
-
701
- // Arabic: we usually underestimate, so shrink available width a bit more to
702
- // encourage earlier breaks.
703
- if (/[\u0600-\u06FF]/u.test(text)) {
704
- return 0.97;
705
- }
706
-
707
- // Base shrink for most Latin and other scripts.
708
- return 0.985;
709
- }
710
-
711
- // Build options to pass directly to pptx.addText. We exclude measurement-only
712
- // fields and fill sensible defaults (e.g., fontSize) so callers can spread
713
- // the result into addText just like the image sizing helpers.
714
- function buildPassthroughOptions(opts, fontSizePt, margins) {
715
- const exclude = new Set([
716
- "text",
717
- "lines",
718
- "w", // will be set by calcTextBox
719
- "h", // will be set by calcTextBox
720
- // fontFace/style/weight are useful for addText; allow passthrough
721
- "leading",
722
- "padding",
723
- ]);
724
- const out = {};
725
- for (const k of Object.keys(opts)) {
726
- if (!exclude.has(k)) out[k] = opts[k];
727
- }
728
- if (out.fontSize == null) out.fontSize = fontSizePt;
729
- if (opts.margin != null) out.margin = margins;
730
- return out;
731
- }
732
-
733
- function getTextMeasurer() {
734
- // Skia-canvas only for accurate shaping and Fontconfig-based resolution.
735
- // Throws if skia-canvas is not available.
736
- const canvas = new Canvas(2, 2);
737
- const ctx = canvas.getContext("2d");
738
- const PX_PER_IN = 96;
739
- return (text, fontSizePt, fontFace, fontStyle, fontWeight) => {
740
- const px = (fontSizePt / 72) * PX_PER_IN;
741
- const style = fontStyle || "normal";
742
- const weight = fontWeight || "normal";
743
- // CSS shorthand: style weight size family
744
- ctx.font = `${style} ${weight} ${px}px ${fontFace || "Arial"}`;
745
- const metrics = ctx.measureText(text);
746
- return (metrics.width || 0) / PX_PER_IN;
747
- };
748
- }
749
-
750
- function normalizeMargins(m) {
751
- const toInches = (value) =>
752
- typeof value === "number" && Number.isFinite(value) ? value / 72 : 0;
753
- if (m && typeof m === "object") {
754
- if (Number.isFinite(m.left) || Number.isFinite(m.top)) {
755
- return {
756
- left: toInches(m.left),
757
- right: toInches(m.right),
758
- top: toInches(m.top),
759
- bottom: toInches(m.bottom),
760
- };
761
- }
762
- }
763
- const all = toInches(m);
764
- return { left: all, right: all, top: all, bottom: all };
765
- }
766
-
767
- function normalizeText(textOrRuns) {
768
- if (Array.isArray(textOrRuns)) {
769
- return textOrRuns
770
- .map((item) => {
771
- if (typeof item === "string") return item;
772
- if (item && typeof item.text === "string") return item.text;
773
- return "";
774
- })
775
- .join("");
776
- }
777
- return typeof textOrRuns === "string" ? textOrRuns : String(textOrRuns ?? "");
778
- }
779
-
780
- function toNumber(v, fallback) {
781
- const n = typeof v === "string" ? parseFloat(v) : v;
782
- return Number.isFinite(n) ? n : fallback;
783
- }
784
-
785
- module.exports = {
786
- calcTextBoxHeightSimple,
787
- calcTextBox,
788
- autoFontSize,
789
- };
1
+ // Copyright (c) OpenAI. All rights reserved.
2
+ "use strict";
3
+
4
+ const { spawnSync } = require("child_process");
5
+ const { Canvas } = require("skia-canvas");
6
+ // Unicode line-break iterator (UAX #14) so we mimic PPT/LibreOffice wrapping rules.
7
+ const LineBreaker = require("linebreak");
8
+ const fontkit = require("fontkit");
9
+ const TEXT_MEASURER = getTextMeasurer();
10
+ const registeredFontVariants = new Set();
11
+ const fontPathCache = new Map();
12
+ const fontKitCache = new Map();
13
+
14
+ // Estimate the text box height for a given font size and line count.
15
+ // NOTE: This is an analytical approximation, not an exact reproduction of
16
+ // PowerPoint/LibreOffice layout. Always verify visually and adjust based on
17
+ // actual rendering if precise fit is required.
18
+ function calcTextBoxHeightSimple(
19
+ fontSize,
20
+ lines = 1,
21
+ leading = 1.15,
22
+ padding = 0.3
23
+ ) {
24
+ const lineHeightIn = (fontSize / 72) * leading;
25
+ return lines * lineHeightIn + padding;
26
+ }
27
+
28
+ // Compute font size that fits given text within a fixed box.
29
+ // NOTE: autoFontSize uses skia-canvas measurement stack to approximate the font size
30
+ // that will fit in a given box. Rendering engines may differ slightly, so
31
+ // treat the result as an estimate and tweak as needed after visual inspection.
32
+ // Signature:
33
+ // autoFontSize(textOrRuns, fontFace, opts?)
34
+ // - fontFace must be provided as the 2nd positional argument and cannot be in opts.
35
+ // - All modes always respect [minFontSize, maxFontSize] as a CLOSED interval when provided.
36
+ // Modes:
37
+ // - mode: "shrink" => shrink only (search [minFontSize, min(maxFontSize, fontSize)])
38
+ // - mode: "enlarge" => enlarge only (search [max(minFontSize, fontSize), maxFontSize])
39
+ // - mode: "auto" => shrink + enlarge (search [minFontSize, maxFontSize]); fontSize optional.
40
+ // In "auto" mode fontSize is not required; when omitted we simply search the whole [minFontSize, maxFontSize] range.
41
+ // Returns a cloned options object with computed fontSize. fit: "shrink" is appended only when mode === "shrink".
42
+ function autoFontSize(textOrRuns, fontFace, opts = {}) {
43
+ const x = toNumber(opts.x, 0);
44
+ const y = toNumber(opts.y, 0);
45
+ const w = toNumber(opts.w, 0);
46
+ const h = toNumber(opts.h, 0);
47
+ if (!(w > 0 && h > 0)) throw new Error("autoFontSize(): non-positive w or h");
48
+
49
+ const face = typeof fontFace === "string" ? fontFace.trim() : "";
50
+ if (face.length === 0) {
51
+ throw new Error(
52
+ "autoFontSize(): fontFace is required as the 2nd positional argument."
53
+ );
54
+ }
55
+
56
+ // Fast-path: if there is no visible text content, just return the
57
+ // (optionally clamped) reference fontSize; there is nothing to fit.
58
+ const hasAnyText =
59
+ normalizeText(textOrRuns).trim().length > 0 ||
60
+ (Array.isArray(textOrRuns) &&
61
+ textOrRuns.some(
62
+ (run) => run && typeof run.text === "string" && run.text.trim().length
63
+ ));
64
+
65
+ const fontStyle =
66
+ opts.italic === true || opts.fontStyle === "italic" ? "italic" : "normal";
67
+ const fontWeight =
68
+ opts.bold === true || String(opts.fontWeight || "").toLowerCase() === "bold"
69
+ ? "bold"
70
+ : "normal";
71
+ const leading = toNumber(opts.leading, 1.15) || 1.15;
72
+
73
+ const modeRaw = typeof opts.mode === "string" ? opts.mode : "auto"; // 'auto' (default) | 'shrink' | 'enlarge'
74
+ const mode = modeRaw.toLowerCase();
75
+ const isShrink = mode === "shrink";
76
+ const isEnlarge = mode === "enlarge";
77
+ const isAuto = mode === "auto";
78
+
79
+ const refPtRaw = toNumber(opts.fontSize, NaN);
80
+ const hasRefPt = Number.isFinite(refPtRaw);
81
+ const refPt = hasRefPt ? refPtRaw : NaN;
82
+
83
+ // Base bounds (closed interval). Defaults:
84
+ // - minFontSize: 1pt
85
+ // - maxFontSize: 1000pt (unless the caller provided a tighter bound)
86
+ let minPt = toNumber(opts.minFontSize, NaN);
87
+ let maxPt = toNumber(opts.maxFontSize, NaN);
88
+ const userProvidedMax = Number.isFinite(maxPt);
89
+ if (!Number.isFinite(minPt)) {
90
+ minPt = 1;
91
+ }
92
+ if (!Number.isFinite(maxPt)) {
93
+ maxPt = 1000;
94
+ }
95
+
96
+ if (isShrink || isEnlarge) {
97
+ if (!hasRefPt) {
98
+ throw new Error(
99
+ "autoFontSize(): mode 'shrink' or 'enlarge' requires fontSize"
100
+ );
101
+ }
102
+ }
103
+
104
+ if (isShrink) {
105
+ // Shrink only: never exceed the requested size (and respect maxFontSize).
106
+ maxPt = Math.min(maxPt, refPt);
107
+ } else if (isEnlarge) {
108
+ // Enlarge only: never go below the requested size (and respect minFontSize).
109
+ minPt = Math.max(minPt, refPt);
110
+ } else if (isAuto && hasRefPt && userProvidedMax) {
111
+ // Auto mode with an explicit maxFontSize: honor [minFontSize, maxFontSize]
112
+ // as the search band while allowing both shrink and enlarge within it.
113
+ } else if (!isAuto) {
114
+ throw new Error(
115
+ `autoFontSize(): unsupported mode "${modeRaw}", expected "auto" | "shrink" | "enlarge"`
116
+ );
117
+ }
118
+
119
+ if (!(maxPt > 0 && maxPt >= minPt)) {
120
+ throw new Error(
121
+ "autoFontSize(): invalid minFontSize/maxFontSize bounds after normalization"
122
+ );
123
+ }
124
+
125
+ // If there is no actual text, we can skip measurement entirely and just
126
+ // clamp the reference size to [minPt, maxPt].
127
+ if (!hasAnyText) {
128
+ const chosen =
129
+ (hasRefPt && Math.max(minPt, Math.min(maxPt, refPt))) || minPt;
130
+ const out = { ...opts, x, y, w, h, fontSize: chosen };
131
+ if (isShrink) out.fit = "shrink";
132
+ return out;
133
+ }
134
+
135
+ // Search the space of candidate font sizes with a small step and a safety
136
+ // bias baked into the fit test:
137
+ // - precision: 0.05pt (~1/20pt) so we land very close to the true max-fit.
138
+ // - safetyFactor: we require that the calcTextBox()-measured height is
139
+ // within a small margin of the caller-provided box height, so that the
140
+ // same layout engine used by calcTextBox drives autoFontSize decisions.
141
+ const precision = 0.05; // point precision for search (~1/20pt)
142
+ const safetyFactor = 0.97;
143
+
144
+ let lo = minPt;
145
+ let hi = maxPt;
146
+ let best = lo;
147
+ while (hi - lo > precision) {
148
+ const mid = (lo + hi) / 2;
149
+ // Delegate measurement to calcTextBox so that autoFontSize and
150
+ // calcTextBox share the exact same layout pipeline (paragraph modeling,
151
+ // bullet handling, margins, padding, width scaling, etc.).
152
+ const layout = calcTextBox(mid, {
153
+ text: textOrRuns,
154
+ w,
155
+ fontFace: face,
156
+ fontStyle,
157
+ fontWeight,
158
+ leading,
159
+ margin: opts.margin,
160
+ padding: opts.padding,
161
+ paraSpaceAfter: opts.paraSpaceAfter,
162
+ });
163
+ const fits = layout.h <= h * safetyFactor + 1e-6;
164
+ if (fits) {
165
+ best = mid;
166
+ lo = mid; // try larger
167
+ } else {
168
+ hi = mid; // shrink
169
+ }
170
+ }
171
+ // Closed interval: clamp to [minPt, maxPt].
172
+ const finalPt = Math.max(minPt, Math.min(maxPt, best));
173
+
174
+ // Pass through all original options, override fontSize and append fit: "shrink"
175
+ const out = { ...opts, x, y, w, h, fontSize: finalPt };
176
+ if (isShrink) out.fit = "shrink";
177
+ return out;
178
+ }
179
+
180
+ // Calculate text box metrics using skia-canvas measurement (lines, height,
181
+ // width) for a given font size and text payload.
182
+ // NOTE: calcTextBox approximates how many lines and how much space text will
183
+ // occupy using our JS measurement pipeline. It is designed to be close to
184
+ // PowerPoint/LibreOffice but is not guaranteed pixel-perfect—always adjust
185
+ // based on actual slide rendering when precision matters.
186
+ // Signature:
187
+ // calcTextBox(fontSizePt, opts)
188
+ // - fontSizePt: number (points)
189
+ // - opts (keywords): {
190
+ // text?: string | runs[],
191
+ // w?: number (inches),
192
+ // h?: number (inches),
193
+ // lines?: number,
194
+ // fontFace?: string, // required when measuring by width/height with text
195
+ // fontStyle?: 'normal' | 'italic', italic?: boolean,
196
+ // fontWeight?: 'normal' | 'bold', bold?: boolean,
197
+ // leading?: number (line height multiplier, default 1.15),
198
+ // padding?: number (inches, default 0.3),
199
+ // paraSpaceAfter?: number (points, default 0)
200
+ // }
201
+ // Modes (auto-detected):
202
+ // a) Given lines -> compute height
203
+ // b) Given width + text -> compute height and lines
204
+ // c) Given height + text -> compute width and lines
205
+ // Throws when insufficient info is provided.
206
+ function calcTextBox(fontSizePt, opts = {}) {
207
+ const textInput = opts.text ?? "";
208
+ const text = normalizeText(textInput || "");
209
+ const face =
210
+ typeof opts.fontFace === "string" && opts.fontFace.trim().length > 0
211
+ ? opts.fontFace.trim()
212
+ : "";
213
+ const fontStyle =
214
+ opts.italic === true || opts.fontStyle === "italic" ? "italic" : "normal";
215
+ const fontWeight =
216
+ opts.bold === true || String(opts.fontWeight || "").toLowerCase() === "bold"
217
+ ? "bold"
218
+ : "normal";
219
+ const leading = toNumber(opts.leading, 1.15) || 1.15;
220
+ const padding = toNumber(opts.padding, 0.3); // inches (allow 0)
221
+ const paraSpaceAfterPt = toNumber(opts.paraSpaceAfter, 0) || 0; // points
222
+ const lineHeightIn = (fontSizePt / 72) * leading;
223
+ const margins = normalizeMargins(opts.margin);
224
+ const measurer = TEXT_MEASURER;
225
+
226
+ const hasLines = Number.isFinite(toNumber(opts.lines, NaN));
227
+ const hasWidth = Number.isFinite(toNumber(opts.w, NaN));
228
+ const hasHeight = Number.isFinite(toNumber(opts.h, NaN));
229
+ const paragraphs = buildParagraphModels(textInput, {
230
+ fontSizePt,
231
+ // Do not silently substitute a default font here; callers measuring by
232
+ // width/height are required to pass an explicit fontFace so that our
233
+ // metrics match the actual slide theme.
234
+ fontFace: face,
235
+ fontStyle,
236
+ fontWeight,
237
+ leading,
238
+ paraSpaceAfterPt,
239
+ });
240
+ const hasAnyText = paragraphs.some((p) => p.text.length > 0);
241
+
242
+ // Empirical top inset: PPT text frames render a small gutter above the first line
243
+ // even with zero margins. Model it as a fraction of the font size so callers can
244
+ // visually trim by shifting y up and growing h by the same amount.
245
+ const topInsetIn = (fontSizePt / 72) * 0.2; // ~20% of font size (inches)
246
+
247
+ if (hasLines) {
248
+ // Mode (a): Given lines -> compute height only
249
+ const lines = toNumber(opts.lines, 1);
250
+ const contentH = Math.max(0, lines * lineHeightIn + padding);
251
+ const h = contentH + margins.top + margins.bottom;
252
+ const passthrough = buildPassthroughOptions(opts, fontSizePt, margins);
253
+ return {
254
+ ...passthrough,
255
+ w: toNumber(opts.w, NaN) || null,
256
+ h,
257
+ lines,
258
+ contentH,
259
+ margins,
260
+ topInset: topInsetIn,
261
+ };
262
+ }
263
+
264
+ if (hasWidth && hasAnyText) {
265
+ // Mode (b): Given width + text -> compute height and lines
266
+ if (face.length === 0) {
267
+ throw new Error(
268
+ "calcTextBox(): opts.fontFace is required when measuring by width."
269
+ );
270
+ }
271
+ const boxW = toNumber(opts.w, 0);
272
+ if (!(boxW > 0))
273
+ throw new Error("calcTextBox(): width must be > 0 in mode 'width'");
274
+ const innerW = Math.max(0, boxW - margins.left - margins.right);
275
+ const { lines, heightIn } = layoutGivenWidth(paragraphs, innerW);
276
+ const contentH = Math.max(0, heightIn + padding);
277
+ const h = contentH + margins.top + margins.bottom;
278
+ const passthrough = buildPassthroughOptions(opts, fontSizePt, margins);
279
+ return {
280
+ ...passthrough,
281
+ w: boxW,
282
+ h,
283
+ lines,
284
+ contentH,
285
+ margins,
286
+ topInset: topInsetIn,
287
+ };
288
+ }
289
+
290
+ if (hasHeight && hasAnyText) {
291
+ // Mode (c): Given height + text -> compute minimal width and lines to fit
292
+ if (face.length === 0) {
293
+ throw new Error(
294
+ "calcTextBox(): opts.fontFace is required when measuring by height."
295
+ );
296
+ }
297
+ const boxH = toNumber(opts.h, 0);
298
+ if (!(boxH > 0))
299
+ throw new Error("calcTextBox(): height must be > 0 in mode 'height'");
300
+ const innerH = Math.max(0, boxH - margins.top - margins.bottom);
301
+ // Upper bound: single-line width across paragraphs
302
+ const singleLineWidth = paragraphs.reduce((mx, p) => {
303
+ const width = measureRunWidth(p, p.text) + p.textIndentIn;
304
+ return Math.max(mx, width);
305
+ }, 0);
306
+ const minHeightOneLine = Math.max(
307
+ 0,
308
+ paragraphs.reduce((sum, p, idx) => {
309
+ const lineHeight = (p.fontSizePt / 72) * p.leading;
310
+ sum += lineHeight;
311
+ if (idx !== paragraphs.length - 1) sum += p.paraSpaceAfterIn;
312
+ return sum;
313
+ }, 0)
314
+ );
315
+ if (minHeightOneLine + padding - innerH > 1e-6) {
316
+ throw new Error(
317
+ "calcTextBox(): height too small for one-line layout at this font size"
318
+ );
319
+ }
320
+ // Lower bound: longest token width
321
+ const longestTokenWidth = paragraphs.reduce((mx, p) => {
322
+ const tokens = splitTextIntoTokens(p.text);
323
+ for (const tk of tokens) {
324
+ if (tk.length === 0) continue;
325
+ const wIn = measureRunWidth(p, tk) + p.textIndentIn;
326
+ if (wIn > mx) mx = wIn;
327
+ }
328
+ return mx;
329
+ }, 0);
330
+ let lo = Math.max(0.01, longestTokenWidth);
331
+ let hi = Math.max(lo, singleLineWidth);
332
+ let best = hi;
333
+ for (let iter = 0; iter < 32; iter++) {
334
+ const mid = (lo + hi) / 2;
335
+ const { lines, heightIn } = layoutGivenWidth(paragraphs, mid);
336
+ const totalH = heightIn + padding;
337
+ if (totalH <= innerH + 1e-6) {
338
+ best = mid;
339
+ hi = mid;
340
+ } else {
341
+ lo = mid;
342
+ }
343
+ }
344
+ const { lines, heightIn } = layoutGivenWidth(paragraphs, best);
345
+ const contentH = heightIn + padding;
346
+ const passthrough = buildPassthroughOptions(opts, fontSizePt, margins);
347
+ return {
348
+ ...passthrough,
349
+ w: best + margins.left + margins.right,
350
+ h: contentH + margins.top + margins.bottom,
351
+ lines,
352
+ contentH,
353
+ margins,
354
+ topInset: topInsetIn,
355
+ };
356
+ }
357
+
358
+ throw new Error(
359
+ "calcTextBox(): insufficient information. Provide {lines} or ({w,text}) or ({h,text})."
360
+ );
361
+ }
362
+
363
+ function layoutGivenWidth(paragraphs, boxW) {
364
+ let totalLines = 0;
365
+ let heightIn = 0;
366
+ for (let i = 0; i < paragraphs.length; i++) {
367
+ const para = paragraphs[i];
368
+ const widthScale = getWidthScaleForParagraph(para);
369
+ const usableWidth = Math.max(0.01, boxW - para.textIndentIn) * widthScale;
370
+ const lines = greedyWrap(para, usableWidth);
371
+ const count = Math.max(1, lines.length);
372
+ totalLines += count;
373
+ const lineHeightIn = (para.fontSizePt / 72) * para.leading;
374
+ heightIn += count * lineHeightIn;
375
+ if (i !== paragraphs.length - 1) heightIn += para.paraSpaceAfterIn;
376
+ }
377
+ return { lines: totalLines, heightIn };
378
+ }
379
+
380
+ function greedyWrap(paragraph, maxWidthIn) {
381
+ const text = paragraph.text || "";
382
+ if (text.length === 0) return [""];
383
+ const breaker = new LineBreaker(text);
384
+ const breakpoints = [];
385
+ let bk;
386
+ while ((bk = breaker.nextBreak())) {
387
+ breakpoints.push({ pos: bk.position, required: bk.required });
388
+ }
389
+ const lines = [];
390
+ let start = skipTextWhitespace(text, 0);
391
+ let idx = 0;
392
+ while (start < text.length) {
393
+ while (idx < breakpoints.length && breakpoints[idx].pos <= start) idx++;
394
+ let chosen = null;
395
+ let probe = idx;
396
+ while (probe < breakpoints.length) {
397
+ const br = breakpoints[probe];
398
+ const slice = text.slice(start, br.pos);
399
+ const width = measureRunWidth(paragraph, trimLineEnd(slice));
400
+ if (width <= maxWidthIn + 1e-6) {
401
+ chosen = br;
402
+ probe++;
403
+ if (br.required) break;
404
+ } else {
405
+ break;
406
+ }
407
+ }
408
+ if (!chosen) {
409
+ const forced = forceBreakSegment(text, start, maxWidthIn, paragraph);
410
+ if (forced.segment.length === 0) break;
411
+ lines.push(trimLineEnd(forced.segment));
412
+ start = skipTextWhitespace(text, forced.nextIndex);
413
+ continue;
414
+ }
415
+ const lineText = text.slice(start, chosen.pos);
416
+ lines.push(trimLineEnd(lineText));
417
+ start = skipTextWhitespace(text, chosen.pos);
418
+ }
419
+ if (!lines.length) lines.push("");
420
+ return lines;
421
+ }
422
+
423
+ function splitTextIntoTokens(text) {
424
+ if (typeof text !== "string") return [""];
425
+ const tokens = text.split(/(\s+)/);
426
+ return tokens.length ? tokens : [""];
427
+ }
428
+
429
+ function trimLineEnd(value) {
430
+ return typeof value === "string" ? value.replace(/\s+$/u, "") : "";
431
+ }
432
+
433
+ function measureRunWidth(paragraph, text) {
434
+ if (!text || text.length === 0) return 0;
435
+ const fontData = getFontData(
436
+ paragraph.fontFace,
437
+ paragraph.fontStyle,
438
+ paragraph.fontWeight
439
+ );
440
+ if (fontData && fontData.font) {
441
+ const layout = fontData.font.layout(text);
442
+ const widthPts =
443
+ (layout.advanceWidth / fontData.font.unitsPerEm) * paragraph.fontSizePt;
444
+ return Math.max(0, widthPts / 72);
445
+ }
446
+ return TEXT_MEASURER(
447
+ text,
448
+ paragraph.fontSizePt,
449
+ paragraph.fontFace,
450
+ paragraph.fontStyle,
451
+ paragraph.fontWeight
452
+ );
453
+ }
454
+
455
+ function forceBreakSegment(text, start, maxWidthIn, paragraph) {
456
+ const chars = Array.from(text.slice(start));
457
+ if (chars.length === 0) return { segment: "", nextIndex: text.length };
458
+ let buffer = "";
459
+ let consumedUnits = 0;
460
+ for (let i = 0; i < chars.length; i++) {
461
+ const candidate = buffer + chars[i];
462
+ const width = measureRunWidth(paragraph, trimLineEnd(candidate));
463
+ if (width <= maxWidthIn + 1e-6) {
464
+ buffer = candidate;
465
+ consumedUnits += chars[i].length;
466
+ continue;
467
+ }
468
+ if (buffer.length === 0) {
469
+ buffer = chars[i];
470
+ consumedUnits += chars[i].length;
471
+ }
472
+ break;
473
+ }
474
+ if (buffer.length === 0) {
475
+ buffer = chars[0] || "";
476
+ consumedUnits = buffer.length;
477
+ }
478
+ return { segment: buffer, nextIndex: start + consumedUnits };
479
+ }
480
+
481
+ function skipTextWhitespace(text, index) {
482
+ let idx = index;
483
+ while (idx < text.length && /\s/.test(text[idx])) idx++;
484
+ return idx;
485
+ }
486
+
487
+ function buildParagraphModels(textOrRuns, baseStyle) {
488
+ const entries = collectParagraphEntries(textOrRuns);
489
+ if (entries.length === 0) {
490
+ return [resolveParagraphStyle({ text: "" }, baseStyle)];
491
+ }
492
+ return entries.map((entry) => resolveParagraphStyle(entry, baseStyle));
493
+ }
494
+
495
+ function collectParagraphEntries(textOrRuns) {
496
+ const result = [];
497
+ if (Array.isArray(textOrRuns)) {
498
+ for (const entry of textOrRuns) {
499
+ if (typeof entry === "string") {
500
+ pushParagraphSegments(entry, undefined, result);
501
+ } else if (entry && typeof entry === "object") {
502
+ pushParagraphSegments(entry.text ?? "", entry.options || {}, result);
503
+ }
504
+ }
505
+ return result;
506
+ }
507
+ pushParagraphSegments(textOrRuns ?? "", undefined, result);
508
+ return result;
509
+ }
510
+
511
+ function pushParagraphSegments(text, options, target) {
512
+ const normalized = String(text ?? "");
513
+ const parts = normalized.split(/\r?\n/);
514
+ if (parts.length === 0) {
515
+ target.push({ text: "", options });
516
+ return;
517
+ }
518
+ for (const part of parts) {
519
+ target.push({ text: part, options });
520
+ }
521
+ }
522
+
523
+ function resolveParagraphStyle(entry, baseStyle) {
524
+ const opts = entry.options || {};
525
+ const fontFace =
526
+ (opts.fontFace && String(opts.fontFace).trim()) ||
527
+ baseStyle.fontFace ||
528
+ "Arial";
529
+ const fontStyle =
530
+ opts.italic === true || opts.fontStyle === "italic"
531
+ ? "italic"
532
+ : baseStyle.fontStyle || "normal";
533
+ const fontWeight =
534
+ opts.bold === true || String(opts.fontWeight || "").toLowerCase() === "bold"
535
+ ? "bold"
536
+ : baseStyle.fontWeight || "normal";
537
+ const fontSizePt =
538
+ toNumber(opts.fontSize, baseStyle.fontSizePt) || baseStyle.fontSizePt;
539
+ const leading =
540
+ toNumber(opts.leading, baseStyle.leading) || baseStyle.leading || 1.15;
541
+ const paraSpaceAfterPt =
542
+ toNumber(opts.paraSpaceAfter, baseStyle.paraSpaceAfterPt) ||
543
+ baseStyle.paraSpaceAfterPt ||
544
+ 0;
545
+ const hasBullet = !!opts.bullet;
546
+ let indentPt = toNumber(opts.indent, NaN);
547
+ if (!Number.isFinite(indentPt) && hasBullet) {
548
+ indentPt = toNumber(opts.bullet.indent, NaN);
549
+ }
550
+ if (!Number.isFinite(indentPt)) indentPt = 0;
551
+ const hangingPt = toNumber(opts.hanging, 0) || 0;
552
+ let textIndentIn = 0;
553
+ if (indentPt > 0) {
554
+ if (hasBullet) {
555
+ // PowerPoint-style bullets: "indent" is the distance from the left edge
556
+ // of the text box to the start of the text (the bullet itself is hung
557
+ // using the hanging value). This means the available width for the text
558
+ // is boxWidth - indent, not boxWidth - (indent - hanging). Modeling it
559
+ // this way matches the manual line counts from PowerPoint/LibreOffice.
560
+ textIndentIn = indentPt / 72;
561
+ } else {
562
+ // Non-bullet paragraphs keep the prior behavior where hanging reduces
563
+ // the effective indent (similar to CSS text-indent).
564
+ textIndentIn = Math.max(0, (indentPt - hangingPt) / 72);
565
+ }
566
+ }
567
+ return {
568
+ text: entry.text || "",
569
+ fontFace,
570
+ fontStyle,
571
+ fontWeight,
572
+ fontSizePt,
573
+ leading,
574
+ paraSpaceAfterIn: paraSpaceAfterPt / 72,
575
+ textIndentIn,
576
+ };
577
+ }
578
+
579
+ function getFontData(face, fontStyle, fontWeight) {
580
+ const key = makeFontCacheKey(face, fontStyle, fontWeight);
581
+ if (fontKitCache.has(key)) return fontKitCache.get(key);
582
+ const fontPath = findFontPath(face, fontStyle, fontWeight);
583
+ if (!fontPath) {
584
+ fontKitCache.set(key, null);
585
+ return null;
586
+ }
587
+ try {
588
+ let font = fontkit.openSync(fontPath);
589
+ if (font && typeof font.fonts === "object") {
590
+ font = selectCollectionFont(font, fontStyle, fontWeight);
591
+ }
592
+ if (!font || typeof font.layout !== "function") {
593
+ fontKitCache.set(key, null);
594
+ return null;
595
+ }
596
+ registerCanvasFontVariant(fontPath, face, fontStyle, fontWeight, key);
597
+ const payload = { font, path: fontPath };
598
+ fontKitCache.set(key, payload);
599
+ return payload;
600
+ } catch (err) {
601
+ fontKitCache.set(key, null);
602
+ return null;
603
+ }
604
+ }
605
+
606
+ function makeFontCacheKey(face, fontStyle, fontWeight) {
607
+ const family = (face || "Arial").trim();
608
+ const style = (fontStyle || "normal").toLowerCase();
609
+ const weight = (fontWeight || "normal").toLowerCase();
610
+ return `${family}::${style}::${weight}`;
611
+ }
612
+
613
+ function registerCanvasFontVariant(
614
+ fontPath,
615
+ face,
616
+ fontStyle,
617
+ fontWeight,
618
+ cacheKey
619
+ ) {
620
+ if (registeredFontVariants.has(cacheKey)) return;
621
+ try {
622
+ Canvas.registerFont(fontPath, {
623
+ family: face,
624
+ style: fontStyle || "normal",
625
+ weight: fontWeight || "normal",
626
+ });
627
+ registeredFontVariants.add(cacheKey);
628
+ } catch (err) {
629
+ // ignore registration failure; measurement will fall back to Skia default
630
+ }
631
+ }
632
+
633
+ function findFontPath(face, fontStyle, fontWeight) {
634
+ const family = (face || "").trim();
635
+ if (family.length === 0) return null;
636
+ const key = makeFontCacheKey(family, fontStyle, fontWeight);
637
+ if (fontPathCache.has(key)) return fontPathCache.get(key);
638
+ const styleParts = [];
639
+ if ((fontWeight || "").toLowerCase() === "bold") styleParts.push("Bold");
640
+ if ((fontStyle || "").toLowerCase() === "italic") styleParts.push("Italic");
641
+ const styleQuery =
642
+ styleParts.length > 0 ? `:style=${styleParts.join(" ")}` : "";
643
+ const query = `${family}${styleQuery}`;
644
+ const result = spawnSync("fc-match", ["-f", "%{file}", query], {
645
+ encoding: "utf8",
646
+ });
647
+ if (result.status === 0) {
648
+ const output = String(result.stdout || "").trim();
649
+ if (output.length > 0) {
650
+ fontPathCache.set(key, output);
651
+ return output;
652
+ }
653
+ }
654
+ fontPathCache.set(key, null);
655
+ return null;
656
+ }
657
+
658
+ function selectCollectionFont(collection, fontStyle, fontWeight) {
659
+ const fonts = collection.fonts || [];
660
+ if (fonts.length === 0) return null;
661
+ const wantItalic = (fontStyle || "").toLowerCase() === "italic";
662
+ const wantBold = (fontWeight || "").toLowerCase() === "bold";
663
+ let best = fonts[0];
664
+ let bestScore = scoreFontVariant(best, wantItalic, wantBold);
665
+ for (let i = 1; i < fonts.length; i++) {
666
+ const candidate = fonts[i];
667
+ const score = scoreFontVariant(candidate, wantItalic, wantBold);
668
+ if (score > bestScore) {
669
+ best = candidate;
670
+ bestScore = score;
671
+ }
672
+ }
673
+ return best;
674
+ }
675
+
676
+ function scoreFontVariant(font, wantItalic, wantBold) {
677
+ if (!font) return -1;
678
+ const name = String(font.fullName || font.postscriptName || "").toLowerCase();
679
+ const isItalic = /italic|oblique/.test(name);
680
+ const isBold = /bold|black|heavy|semibold|extrabold/.test(name);
681
+ let score = 0;
682
+ if (isItalic === wantItalic) score += 1;
683
+ if (isBold === wantBold) score += 1;
684
+ return score;
685
+ }
686
+
687
+ // Empirical width scaling to better match PowerPoint/LibreOffice line breaks.
688
+ // A tiny global shrink (about -1.5%) nudges borderline words to wrap the same
689
+ // way Office does, with per-script tweaks for cases where our measurer
690
+ // systematically under- or over-estimates glyph widths. We intentionally avoid
691
+ // per-font calibration so this helper generalizes beyond the regression deck.
692
+ function getWidthScaleForParagraph(paragraph) {
693
+ if (!paragraph || typeof paragraph.text !== "string") return 1;
694
+ const text = paragraph.text;
695
+ // Thai script: our measurer tends to slightly over-estimate, which can cause
696
+ // extra wraps. Give it a bit more room horizontally.
697
+ if (/[ก-๛]/u.test(text)) {
698
+ return 1.2;
699
+ }
700
+
701
+ // Arabic: we usually underestimate, so shrink available width a bit more to
702
+ // encourage earlier breaks.
703
+ if (/[\u0600-\u06FF]/u.test(text)) {
704
+ return 0.97;
705
+ }
706
+
707
+ // Base shrink for most Latin and other scripts.
708
+ return 0.985;
709
+ }
710
+
711
+ // Build options to pass directly to pptx.addText. We exclude measurement-only
712
+ // fields and fill sensible defaults (e.g., fontSize) so callers can spread
713
+ // the result into addText just like the image sizing helpers.
714
+ function buildPassthroughOptions(opts, fontSizePt, margins) {
715
+ const exclude = new Set([
716
+ "text",
717
+ "lines",
718
+ "w", // will be set by calcTextBox
719
+ "h", // will be set by calcTextBox
720
+ // fontFace/style/weight are useful for addText; allow passthrough
721
+ "leading",
722
+ "padding",
723
+ ]);
724
+ const out = {};
725
+ for (const k of Object.keys(opts)) {
726
+ if (!exclude.has(k)) out[k] = opts[k];
727
+ }
728
+ if (out.fontSize == null) out.fontSize = fontSizePt;
729
+ if (opts.margin != null) out.margin = margins;
730
+ return out;
731
+ }
732
+
733
+ function getTextMeasurer() {
734
+ // Skia-canvas only for accurate shaping and Fontconfig-based resolution.
735
+ // Throws if skia-canvas is not available.
736
+ const canvas = new Canvas(2, 2);
737
+ const ctx = canvas.getContext("2d");
738
+ const PX_PER_IN = 96;
739
+ return (text, fontSizePt, fontFace, fontStyle, fontWeight) => {
740
+ const px = (fontSizePt / 72) * PX_PER_IN;
741
+ const style = fontStyle || "normal";
742
+ const weight = fontWeight || "normal";
743
+ // CSS shorthand: style weight size family
744
+ ctx.font = `${style} ${weight} ${px}px ${fontFace || "Arial"}`;
745
+ const metrics = ctx.measureText(text);
746
+ return (metrics.width || 0) / PX_PER_IN;
747
+ };
748
+ }
749
+
750
+ function normalizeMargins(m) {
751
+ const toInches = (value) =>
752
+ typeof value === "number" && Number.isFinite(value) ? value / 72 : 0;
753
+ if (m && typeof m === "object") {
754
+ if (Number.isFinite(m.left) || Number.isFinite(m.top)) {
755
+ return {
756
+ left: toInches(m.left),
757
+ right: toInches(m.right),
758
+ top: toInches(m.top),
759
+ bottom: toInches(m.bottom),
760
+ };
761
+ }
762
+ }
763
+ const all = toInches(m);
764
+ return { left: all, right: all, top: all, bottom: all };
765
+ }
766
+
767
+ function normalizeText(textOrRuns) {
768
+ if (Array.isArray(textOrRuns)) {
769
+ return textOrRuns
770
+ .map((item) => {
771
+ if (typeof item === "string") return item;
772
+ if (item && typeof item.text === "string") return item.text;
773
+ return "";
774
+ })
775
+ .join("");
776
+ }
777
+ return typeof textOrRuns === "string" ? textOrRuns : String(textOrRuns ?? "");
778
+ }
779
+
780
+ function toNumber(v, fallback) {
781
+ const n = typeof v === "string" ? parseFloat(v) : v;
782
+ return Number.isFinite(n) ? n : fallback;
783
+ }
784
+
785
+ module.exports = {
786
+ calcTextBoxHeightSimple,
787
+ calcTextBox,
788
+ autoFontSize,
789
+ };