@prismer/runtime 2.0.8 → 2.2.63

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 (633) hide show
  1. package/CHANGELOG.md +4004 -0
  2. package/README.md +34 -12
  3. package/apc/skills/FIELD-DICTIONARY.md +111 -0
  4. package/apc/skills/bug-reproduce/SKILL.md +150 -0
  5. package/apc/skills/bug-reproduce/skill.json +96 -0
  6. package/apc/skills/code-review/SKILL.md +198 -0
  7. package/apc/skills/code-review/skill.json +124 -0
  8. package/apc/skills/design-review/SKILL.md +122 -0
  9. package/apc/skills/design-review/skill.json +88 -0
  10. package/apc/skills/doc-sync/SKILL.md +168 -0
  11. package/apc/skills/doc-sync/skill.json +81 -0
  12. package/apc/skills/env-doctor/SKILL.md +194 -0
  13. package/apc/skills/env-doctor/skill.json +209 -0
  14. package/apc/skills/git-ops/SKILL.md +189 -0
  15. package/apc/skills/git-ops/skill.json +94 -0
  16. package/apc/skills/impact-trace/SKILL.md +168 -0
  17. package/apc/skills/impact-trace/skill.json +104 -0
  18. package/apc/skills/observability/SKILL.md +195 -0
  19. package/apc/skills/observability/skill.json +116 -0
  20. package/apc/skills/release-db-config-sync/SKILL.md +186 -0
  21. package/apc/skills/release-db-config-sync/skill.json +109 -0
  22. package/apc/skills/release-ota-promote/SKILL.md +195 -0
  23. package/apc/skills/release-ota-promote/skill.json +176 -0
  24. package/apc/skills/release-preflight/SKILL.md +174 -0
  25. package/apc/skills/release-preflight/skill.json +175 -0
  26. package/apc/skills/release-rollback/SKILL.md +214 -0
  27. package/apc/skills/release-rollback/skill.json +230 -0
  28. package/apc/skills/release-tag/SKILL.md +194 -0
  29. package/apc/skills/release-tag/skill.json +94 -0
  30. package/apc/skills/releasing-prod/SKILL.md +49 -0
  31. package/apc/skills/releasing-test/SKILL.md +144 -0
  32. package/apc/skills/sdk-release/SKILL.md +200 -0
  33. package/apc/skills/spec-intake/SKILL.md +169 -0
  34. package/apc/skills/spec-intake/skill.json +93 -0
  35. package/apc/skills/test-result-feedback/SKILL.md +239 -0
  36. package/apc/skills/test-result-feedback/skill.json +193 -0
  37. package/apc/skills/test-runner/SKILL.md +169 -0
  38. package/apc/skills/test-runner/skill.json +103 -0
  39. package/apc/skills/ui-align/SKILL.md +209 -0
  40. package/apc/skills/ui-align/skill.json +114 -0
  41. package/apc/skills/ui-canvas/SKILL.md +148 -0
  42. package/apc/skills/ui-canvas/skill.json +127 -0
  43. package/built-in-skills/agent-coordination/SKILL.md +77 -38
  44. package/built-in-skills/agent-meta/SKILL.md +12 -0
  45. package/built-in-skills/assets/SKILL.md +9 -7
  46. package/built-in-skills/browser-use/SKILL.md +93 -0
  47. package/built-in-skills/canvas-design/SKILL.md +4 -3
  48. package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-OFL.txt +93 -0
  49. package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-OFL.txt +93 -0
  50. package/built-in-skills/canvas-design/canvas-fonts/LICENSE-SOURCES.md +16 -0
  51. package/built-in-skills/claim-agent-ownership/SKILL.md +10 -5
  52. package/built-in-skills/claude-api/SKILL.md +2 -1
  53. package/built-in-skills/claude-api/python/claude-api/README.md +2 -2
  54. package/built-in-skills/claude-api/python/claude-api/tool-use.md +9 -5
  55. package/built-in-skills/claude-api/python/managed-agents/README.md +11 -1
  56. package/built-in-skills/claude-api/shared/error-codes.md +4 -4
  57. package/built-in-skills/claude-api/shared/managed-agents-client-patterns.md +4 -2
  58. package/built-in-skills/claude-api/shared/managed-agents-core.md +1 -2
  59. package/built-in-skills/claude-api/shared/managed-agents-events.md +41 -22
  60. package/built-in-skills/claude-api/shared/managed-agents-multiagent.md +5 -3
  61. package/built-in-skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +1 -1
  62. package/built-in-skills/claude-api/shared/managed-agents-tools.md +2 -3
  63. package/built-in-skills/claude-api/shared/model-migration.md +3 -3
  64. package/built-in-skills/claude-api/shared/models.md +1 -1
  65. package/built-in-skills/claude-api/typescript/claude-api/batches.md +1 -1
  66. package/built-in-skills/claude-api/typescript/claude-api/files-api.md +3 -1
  67. package/built-in-skills/claude-api/typescript/claude-api/tool-use.md +8 -2
  68. package/built-in-skills/claude-api/typescript/managed-agents/README.md +8 -2
  69. package/built-in-skills/codebase-design/DEEPENING.md +37 -0
  70. package/built-in-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  71. package/built-in-skills/codebase-design/LICENSE +21 -0
  72. package/built-in-skills/codebase-design/SKILL.md +130 -0
  73. package/built-in-skills/codebase-design/references/requesting-code-review/GUIDE.md +291 -0
  74. package/built-in-skills/codebase-design/references/requesting-code-review/LICENSE +21 -0
  75. package/built-in-skills/codebase-design/references/requesting-code-review/LICENSE.superpowers +21 -0
  76. package/built-in-skills/codebase-design/references/requesting-code-review/NOTICE.md +37 -0
  77. package/built-in-skills/codebase-design/references/sdlc-review/GUIDE.md +130 -0
  78. package/built-in-skills/codebase-design/references/sdlc-review/LICENSE +21 -0
  79. package/built-in-skills/codebase-design/references/sdlc-review/NOTICE.md +17 -0
  80. package/built-in-skills/codebase-design/references/simplify-code/GUIDE.md +269 -0
  81. package/built-in-skills/codebase-design/references/simplify-code/LICENSE +21 -0
  82. package/built-in-skills/codebase-design/references/simplify-code/NOTICE.md +37 -0
  83. package/built-in-skills/competitor-intelligence/LICENSE +21 -0
  84. package/built-in-skills/competitor-intelligence/NOTICE.md +6 -0
  85. package/built-in-skills/competitor-intelligence/SKILL.md +125 -0
  86. package/built-in-skills/competitor-intelligence/scripts/watch_state.py +160 -0
  87. package/built-in-skills/conversation-compaction/SKILL.md +117 -0
  88. package/built-in-skills/council-creator/SKILL.md +427 -0
  89. package/built-in-skills/diagnosing-bugs/LICENSE +21 -0
  90. package/built-in-skills/diagnosing-bugs/SKILL.md +148 -0
  91. package/built-in-skills/diagnosing-bugs/references/desktop-dom/GUIDE.md +64 -0
  92. package/built-in-skills/diagnosing-bugs/references/desktop-dom/LICENSE +21 -0
  93. package/built-in-skills/diagnosing-bugs/references/desktop-dom/NOTICE.md +17 -0
  94. package/built-in-skills/diagnosing-bugs/references/systematic-debugging/GUIDE.md +410 -0
  95. package/built-in-skills/diagnosing-bugs/references/systematic-debugging/LICENSE +21 -0
  96. package/built-in-skills/diagnosing-bugs/references/systematic-debugging/LICENSE.superpowers +21 -0
  97. package/built-in-skills/diagnosing-bugs/references/systematic-debugging/NOTICE.md +37 -0
  98. package/built-in-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  99. package/built-in-skills/doc-coauthoring/SKILL.md +7 -0
  100. package/built-in-skills/doc-coauthoring/references/humanizer/GUIDE.md +647 -0
  101. package/built-in-skills/doc-coauthoring/references/humanizer/LICENSE +21 -0
  102. package/built-in-skills/doc-coauthoring/references/humanizer/LICENSE.hermes +21 -0
  103. package/built-in-skills/doc-coauthoring/references/humanizer/PROVENANCE.md +7 -0
  104. package/built-in-skills/document-actions/LICENSE +21 -0
  105. package/built-in-skills/document-actions/NOTICE.md +21 -0
  106. package/built-in-skills/document-actions/SKILL.md +46 -0
  107. package/built-in-skills/domain-modeling/ADR-FORMAT.md +47 -0
  108. package/built-in-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  109. package/built-in-skills/domain-modeling/LICENSE +21 -0
  110. package/built-in-skills/domain-modeling/SKILL.md +76 -0
  111. package/built-in-skills/evidence-citations/LICENSE +21 -0
  112. package/built-in-skills/evidence-citations/NOTICE.md +21 -0
  113. package/built-in-skills/evidence-citations/SKILL.md +46 -0
  114. package/built-in-skills/evidence-citations/references/citation-formats.md +68 -0
  115. package/built-in-skills/evidence-citations/references/grounding-rationale.md +64 -0
  116. package/built-in-skills/evidence-citations/scripts/sources.py +720 -0
  117. package/built-in-skills/frontend-design/SKILL.md +7 -0
  118. package/built-in-skills/frontend-design/references/claude-design/GUIDE.md +650 -0
  119. package/built-in-skills/frontend-design/references/claude-design/LICENSE +21 -0
  120. package/built-in-skills/frontend-design/references/claude-design/LICENSE.hermes +21 -0
  121. package/built-in-skills/frontend-design/references/claude-design/PROVENANCE.md +7 -0
  122. package/built-in-skills/human-approval/SKILL.md +17 -2
  123. package/built-in-skills/image-generate/SKILL.md +107 -301
  124. package/built-in-skills/image-generate/scripts/generate-and-deliver.mjs +310 -0
  125. package/built-in-skills/ingest/SKILL.md +13 -45
  126. package/built-in-skills/internal-comms/SKILL.md +1 -0
  127. package/built-in-skills/liteparse/SKILL.md +130 -110
  128. package/built-in-skills/mcp-builder/SKILL.md +1 -0
  129. package/built-in-skills/mcp-builder/reference/evaluation.md +11 -2
  130. package/built-in-skills/mcp-builder/reference/node_mcp_server.md +34 -5
  131. package/built-in-skills/mcp-builder/reference/python_mcp_server.md +6 -2
  132. package/built-in-skills/mcp-builder/scripts/connections.py +1 -1
  133. package/built-in-skills/mcp-builder/scripts/evaluation.py +52 -34
  134. package/built-in-skills/mcp-builder/scripts/requirements.txt +2 -2
  135. package/built-in-skills/meeting-actions/LICENSE +21 -0
  136. package/built-in-skills/meeting-actions/NOTICE.md +21 -0
  137. package/built-in-skills/meeting-actions/SKILL.md +48 -0
  138. package/built-in-skills/memory/SKILL.md +444 -55
  139. package/built-in-skills/memory/references/llm-wiki/GUIDE.md +468 -0
  140. package/built-in-skills/memory/references/llm-wiki/LICENSE +21 -0
  141. package/built-in-skills/memory/references/llm-wiki/NOTICE.md +4 -0
  142. package/built-in-skills/memory/references/llm-wiki/scripts/lint_wiki.py +84 -0
  143. package/built-in-skills/memory/references/llm-wiki/scripts/requirements.txt +1 -0
  144. package/built-in-skills/memory/references/llm-wiki/templates/SCHEMA.md +19 -0
  145. package/built-in-skills/memory/references/llm-wiki/templates/index.md +9 -0
  146. package/built-in-skills/memory/references/llm-wiki/templates/log.md +4 -0
  147. package/built-in-skills/memory-dream/SKILL.md +332 -0
  148. package/built-in-skills/memory-import/SKILL.md +51 -0
  149. package/built-in-skills/office-artifacts/NOTICE.md +15 -0
  150. package/built-in-skills/office-artifacts/SKILL.md +37 -4
  151. package/built-in-skills/office-artifacts/references/RESOURCE-LIMITS.md +44 -0
  152. package/built-in-skills/office-artifacts/references/document-generation/GUIDE.md +105 -0
  153. package/built-in-skills/office-artifacts/references/docx/GUIDE.md +196 -0
  154. package/built-in-skills/office-artifacts/references/docx/LICENSE +21 -0
  155. package/built-in-skills/office-artifacts/references/docx/NOTICE.md +21 -0
  156. package/built-in-skills/office-artifacts/references/docx/references/revisions-and-comments.md +88 -0
  157. package/built-in-skills/office-artifacts/references/docx/scripts/docx_comments.py +292 -0
  158. package/built-in-skills/office-artifacts/references/docx/scripts/docx_common.py +93 -0
  159. package/built-in-skills/office-artifacts/references/docx/scripts/docx_create.py +177 -0
  160. package/built-in-skills/office-artifacts/references/docx/scripts/docx_edit.py +250 -0
  161. package/built-in-skills/office-artifacts/references/docx/scripts/docx_read.py +169 -0
  162. package/built-in-skills/office-artifacts/references/docx/scripts/docx_revisions.py +149 -0
  163. package/built-in-skills/office-artifacts/references/docx/scripts/docx_template.py +70 -0
  164. package/built-in-skills/office-artifacts/references/docx/scripts/docx_validate.py +155 -0
  165. package/built-in-skills/office-artifacts/references/docx/tests/test_docx_skill.py +525 -0
  166. package/built-in-skills/office-artifacts/references/pdf/GUIDE.md +125 -0
  167. package/built-in-skills/office-artifacts/references/pdf/LICENSE +21 -0
  168. package/built-in-skills/office-artifacts/references/pdf/NOTICE.md +21 -0
  169. package/built-in-skills/office-artifacts/references/pdf/references/forms.md +99 -0
  170. package/built-in-skills/office-artifacts/references/pdf/references/nano-pdf-editing.md +40 -0
  171. package/built-in-skills/office-artifacts/references/pdf/references/ocr-extraction.md +165 -0
  172. package/built-in-skills/office-artifacts/references/pdf/scripts/_raster.py +87 -0
  173. package/built-in-skills/office-artifacts/references/pdf/scripts/extract_marker.py +95 -0
  174. package/built-in-skills/office-artifacts/references/pdf/scripts/extract_pymupdf.py +107 -0
  175. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_create.py +153 -0
  176. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_fill_form.py +97 -0
  177. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_form_layout.py +168 -0
  178. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_make_form.py +145 -0
  179. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_merge.py +50 -0
  180. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_meta.py +123 -0
  181. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_page_image.py +101 -0
  182. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_read.py +153 -0
  183. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_secure.py +71 -0
  184. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_split.py +86 -0
  185. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_stamp.py +145 -0
  186. package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_watermark.py +51 -0
  187. package/built-in-skills/office-artifacts/references/pdf/tests/test_pdf_skill.py +414 -0
  188. package/built-in-skills/office-artifacts/references/powerpoint/GUIDE.md +220 -0
  189. package/built-in-skills/office-artifacts/references/powerpoint/LICENSE +21 -0
  190. package/built-in-skills/office-artifacts/references/powerpoint/NOTICE.md +21 -0
  191. package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_create.py +214 -0
  192. package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_edit.py +442 -0
  193. package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_from_template.py +88 -0
  194. package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_read.py +131 -0
  195. package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_render.py +104 -0
  196. package/built-in-skills/office-artifacts/references/powerpoint/tests/test_powerpoint_skill.py +478 -0
  197. package/built-in-skills/office-artifacts/references/xlsx/GUIDE.md +196 -0
  198. package/built-in-skills/office-artifacts/references/xlsx/LICENSE +21 -0
  199. package/built-in-skills/office-artifacts/references/xlsx/NOTICE.md +21 -0
  200. package/built-in-skills/office-artifacts/references/xlsx/references/restructuring.md +74 -0
  201. package/built-in-skills/office-artifacts/references/xlsx/scripts/csv_to_xlsx.py +108 -0
  202. package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_create.py +259 -0
  203. package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_edit.py +269 -0
  204. package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_read.py +160 -0
  205. package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_recalc.py +111 -0
  206. package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_restructure.py +418 -0
  207. package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_to_csv.py +69 -0
  208. package/built-in-skills/office-artifacts/references/xlsx/tests/test_xlsx_skill.py +542 -0
  209. package/built-in-skills/office-artifacts/tests/test_resource_regressions.py +272 -0
  210. package/built-in-skills/okr/SKILL.md +154 -0
  211. package/built-in-skills/persona/SKILL.md +81 -0
  212. package/built-in-skills/persona-generator/SKILL.md +296 -0
  213. package/built-in-skills/pkf-svg/SKILL.md +258 -0
  214. package/built-in-skills/pkf-svg/references/architecture-diagram/GUIDE.md +148 -0
  215. package/built-in-skills/pkf-svg/references/architecture-diagram/LICENSE +21 -0
  216. package/built-in-skills/pkf-svg/references/architecture-diagram/LICENSE.hermes +21 -0
  217. package/built-in-skills/pkf-svg/references/architecture-diagram/PROVENANCE.md +7 -0
  218. package/built-in-skills/pkf-svg/references/architecture-diagram/templates/template.html +345 -0
  219. package/built-in-skills/pkf-writing/SKILL.md +258 -0
  220. package/built-in-skills/prismer-airtable/LICENSE +21 -0
  221. package/built-in-skills/prismer-airtable/NOTICE.md +11 -0
  222. package/built-in-skills/prismer-airtable/SKILL.md +248 -0
  223. package/built-in-skills/prismer-airtable/scripts/airtable_client.py +76 -0
  224. package/built-in-skills/prismer-apple-notes/LICENSE +21 -0
  225. package/built-in-skills/prismer-apple-notes/NOTICE.md +17 -0
  226. package/built-in-skills/prismer-apple-notes/SKILL.md +118 -0
  227. package/built-in-skills/prismer-apple-notes/scripts/readiness.py +47 -0
  228. package/built-in-skills/prismer-apple-reminders/LICENSE +21 -0
  229. package/built-in-skills/prismer-apple-reminders/NOTICE.md +17 -0
  230. package/built-in-skills/prismer-apple-reminders/SKILL.md +155 -0
  231. package/built-in-skills/prismer-apple-reminders/scripts/readiness.py +47 -0
  232. package/built-in-skills/prismer-ascii-video/LICENSE +21 -0
  233. package/built-in-skills/prismer-ascii-video/LICENSE.hermes +21 -0
  234. package/built-in-skills/prismer-ascii-video/PROVENANCE.md +7 -0
  235. package/built-in-skills/prismer-ascii-video/README.md +290 -0
  236. package/built-in-skills/prismer-ascii-video/SKILL.md +252 -0
  237. package/built-in-skills/prismer-ascii-video/provenance/category-DESCRIPTION.md +3 -0
  238. package/built-in-skills/prismer-ascii-video/references/architecture.md +807 -0
  239. package/built-in-skills/prismer-ascii-video/references/composition.md +900 -0
  240. package/built-in-skills/prismer-ascii-video/references/effects.md +1877 -0
  241. package/built-in-skills/prismer-ascii-video/references/inputs.md +693 -0
  242. package/built-in-skills/prismer-ascii-video/references/optimization.md +688 -0
  243. package/built-in-skills/prismer-ascii-video/references/scenes.md +1018 -0
  244. package/built-in-skills/prismer-ascii-video/references/shaders.md +1408 -0
  245. package/built-in-skills/prismer-ascii-video/references/troubleshooting.md +360 -0
  246. package/built-in-skills/prismer-baoyu-infographic/LICENSE +21 -0
  247. package/built-in-skills/prismer-baoyu-infographic/LICENSE.hermes +21 -0
  248. package/built-in-skills/prismer-baoyu-infographic/LICENSE.origin +21 -0
  249. package/built-in-skills/prismer-baoyu-infographic/PORT_NOTES.md +37 -0
  250. package/built-in-skills/prismer-baoyu-infographic/PROVENANCE.md +7 -0
  251. package/built-in-skills/prismer-baoyu-infographic/SKILL.md +240 -0
  252. package/built-in-skills/prismer-baoyu-infographic/references/analysis-framework.md +182 -0
  253. package/built-in-skills/prismer-baoyu-infographic/references/base-prompt.md +43 -0
  254. package/built-in-skills/prismer-baoyu-infographic/references/layouts/bento-grid.md +41 -0
  255. package/built-in-skills/prismer-baoyu-infographic/references/layouts/binary-comparison.md +48 -0
  256. package/built-in-skills/prismer-baoyu-infographic/references/layouts/bridge.md +41 -0
  257. package/built-in-skills/prismer-baoyu-infographic/references/layouts/circular-flow.md +41 -0
  258. package/built-in-skills/prismer-baoyu-infographic/references/layouts/comic-strip.md +41 -0
  259. package/built-in-skills/prismer-baoyu-infographic/references/layouts/comparison-matrix.md +41 -0
  260. package/built-in-skills/prismer-baoyu-infographic/references/layouts/dashboard.md +41 -0
  261. package/built-in-skills/prismer-baoyu-infographic/references/layouts/dense-modules.md +72 -0
  262. package/built-in-skills/prismer-baoyu-infographic/references/layouts/funnel.md +41 -0
  263. package/built-in-skills/prismer-baoyu-infographic/references/layouts/hierarchical-layers.md +48 -0
  264. package/built-in-skills/prismer-baoyu-infographic/references/layouts/hub-spoke.md +41 -0
  265. package/built-in-skills/prismer-baoyu-infographic/references/layouts/iceberg.md +41 -0
  266. package/built-in-skills/prismer-baoyu-infographic/references/layouts/isometric-map.md +41 -0
  267. package/built-in-skills/prismer-baoyu-infographic/references/layouts/jigsaw.md +41 -0
  268. package/built-in-skills/prismer-baoyu-infographic/references/layouts/linear-progression.md +48 -0
  269. package/built-in-skills/prismer-baoyu-infographic/references/layouts/periodic-table.md +41 -0
  270. package/built-in-skills/prismer-baoyu-infographic/references/layouts/story-mountain.md +41 -0
  271. package/built-in-skills/prismer-baoyu-infographic/references/layouts/structural-breakdown.md +48 -0
  272. package/built-in-skills/prismer-baoyu-infographic/references/layouts/tree-branching.md +41 -0
  273. package/built-in-skills/prismer-baoyu-infographic/references/layouts/venn-diagram.md +41 -0
  274. package/built-in-skills/prismer-baoyu-infographic/references/layouts/winding-roadmap.md +41 -0
  275. package/built-in-skills/prismer-baoyu-infographic/references/structured-content-template.md +244 -0
  276. package/built-in-skills/prismer-baoyu-infographic/references/styles/aged-academia.md +36 -0
  277. package/built-in-skills/prismer-baoyu-infographic/references/styles/bold-graphic.md +36 -0
  278. package/built-in-skills/prismer-baoyu-infographic/references/styles/chalkboard.md +61 -0
  279. package/built-in-skills/prismer-baoyu-infographic/references/styles/claymation.md +29 -0
  280. package/built-in-skills/prismer-baoyu-infographic/references/styles/corporate-memphis.md +29 -0
  281. package/built-in-skills/prismer-baoyu-infographic/references/styles/craft-handmade.md +44 -0
  282. package/built-in-skills/prismer-baoyu-infographic/references/styles/cyberpunk-neon.md +29 -0
  283. package/built-in-skills/prismer-baoyu-infographic/references/styles/hand-drawn-edu.md +63 -0
  284. package/built-in-skills/prismer-baoyu-infographic/references/styles/ikea-manual.md +29 -0
  285. package/built-in-skills/prismer-baoyu-infographic/references/styles/kawaii.md +29 -0
  286. package/built-in-skills/prismer-baoyu-infographic/references/styles/knolling.md +29 -0
  287. package/built-in-skills/prismer-baoyu-infographic/references/styles/lego-brick.md +29 -0
  288. package/built-in-skills/prismer-baoyu-infographic/references/styles/morandi-journal.md +60 -0
  289. package/built-in-skills/prismer-baoyu-infographic/references/styles/origami.md +29 -0
  290. package/built-in-skills/prismer-baoyu-infographic/references/styles/pixel-art.md +29 -0
  291. package/built-in-skills/prismer-baoyu-infographic/references/styles/pop-laboratory.md +48 -0
  292. package/built-in-skills/prismer-baoyu-infographic/references/styles/retro-pop-grid.md +47 -0
  293. package/built-in-skills/prismer-baoyu-infographic/references/styles/storybook-watercolor.md +29 -0
  294. package/built-in-skills/prismer-baoyu-infographic/references/styles/subway-map.md +29 -0
  295. package/built-in-skills/prismer-baoyu-infographic/references/styles/technical-schematic.md +36 -0
  296. package/built-in-skills/prismer-baoyu-infographic/references/styles/ui-wireframe.md +29 -0
  297. package/built-in-skills/prismer-blocked-page-recovery/LICENSE +21 -0
  298. package/built-in-skills/prismer-blocked-page-recovery/NOTICE.md +17 -0
  299. package/built-in-skills/prismer-blocked-page-recovery/SKILL.md +158 -0
  300. package/built-in-skills/prismer-blocked-page-recovery/scripts/recover_page.py +311 -0
  301. package/built-in-skills/prismer-box/LICENSE +21 -0
  302. package/built-in-skills/prismer-box/NOTICE.md +11 -0
  303. package/built-in-skills/prismer-box/SKILL.md +147 -0
  304. package/built-in-skills/prismer-box/references/bulk-operations.md +43 -0
  305. package/built-in-skills/prismer-box/references/cli-guide.md +133 -0
  306. package/built-in-skills/prismer-box/references/content-workflows.md +86 -0
  307. package/built-in-skills/prismer-box/references/hubs.md +77 -0
  308. package/built-in-skills/prismer-box/references/oauth-setup.md +92 -0
  309. package/built-in-skills/prismer-box/references/rest-api.md +37 -0
  310. package/built-in-skills/prismer-box/references/sdk-development.md +85 -0
  311. package/built-in-skills/prismer-box/references/search-and-ai.md +147 -0
  312. package/built-in-skills/prismer-box/references/troubleshooting.md +33 -0
  313. package/built-in-skills/prismer-box/references/webhooks-and-events.md +43 -0
  314. package/built-in-skills/prismer-codebase-inspection/LICENSE +21 -0
  315. package/built-in-skills/prismer-codebase-inspection/NOTICE.md +17 -0
  316. package/built-in-skills/prismer-codebase-inspection/SKILL.md +141 -0
  317. package/built-in-skills/prismer-computer-use/LICENSE +21 -0
  318. package/built-in-skills/prismer-computer-use/NOTICE.md +17 -0
  319. package/built-in-skills/prismer-computer-use/SKILL.md +372 -0
  320. package/built-in-skills/prismer-design-md/LICENSE +21 -0
  321. package/built-in-skills/prismer-design-md/LICENSE.hermes +21 -0
  322. package/built-in-skills/prismer-design-md/PROVENANCE.md +7 -0
  323. package/built-in-skills/prismer-design-md/SKILL.md +227 -0
  324. package/built-in-skills/prismer-design-md/templates/starter.md +99 -0
  325. package/built-in-skills/prismer-email-inbox-triage/LICENSE +21 -0
  326. package/built-in-skills/prismer-email-inbox-triage/NOTICE.md +11 -0
  327. package/built-in-skills/prismer-email-inbox-triage/SKILL.md +116 -0
  328. package/built-in-skills/prismer-findmy/LICENSE +21 -0
  329. package/built-in-skills/prismer-findmy/NOTICE.md +17 -0
  330. package/built-in-skills/prismer-findmy/SKILL.md +150 -0
  331. package/built-in-skills/prismer-findmy/scripts/readiness.py +47 -0
  332. package/built-in-skills/prismer-gif-search/LICENSE +21 -0
  333. package/built-in-skills/prismer-gif-search/LICENSE.hermes +21 -0
  334. package/built-in-skills/prismer-gif-search/PROVENANCE.md +7 -0
  335. package/built-in-skills/prismer-gif-search/SKILL.md +97 -0
  336. package/built-in-skills/prismer-gif-search/scripts/tenor.py +81 -0
  337. package/built-in-skills/prismer-github/LICENSE +21 -0
  338. package/built-in-skills/prismer-github/NOTICE.md +17 -0
  339. package/built-in-skills/prismer-github/SKILL.md +74 -0
  340. package/built-in-skills/prismer-github/references/auth.md +91 -0
  341. package/built-in-skills/prismer-github/references/ci-troubleshooting.md +193 -0
  342. package/built-in-skills/prismer-github/references/code-review.md +465 -0
  343. package/built-in-skills/prismer-github/references/conventional-commits.md +81 -0
  344. package/built-in-skills/prismer-github/references/github-api-cheatsheet.md +177 -0
  345. package/built-in-skills/prismer-github/references/issue-to-pr.md +74 -0
  346. package/built-in-skills/prismer-github/references/issues.md +336 -0
  347. package/built-in-skills/prismer-github/references/pr-workflow.md +346 -0
  348. package/built-in-skills/prismer-github/references/repo-management.md +484 -0
  349. package/built-in-skills/prismer-github/references/review-output-template.md +84 -0
  350. package/built-in-skills/prismer-github/scripts/ensure-gh.py +132 -0
  351. package/built-in-skills/prismer-github/scripts/gh-env.sh +25 -0
  352. package/built-in-skills/prismer-github/scripts/git-credential-token.py +68 -0
  353. package/built-in-skills/prismer-github/scripts/github-api.py +71 -0
  354. package/built-in-skills/prismer-github/scripts/github-context.py +28 -0
  355. package/built-in-skills/prismer-github/templates/bug-report.md +35 -0
  356. package/built-in-skills/prismer-github/templates/feature-request.md +31 -0
  357. package/built-in-skills/prismer-github/templates/pr-body-bugfix.md +35 -0
  358. package/built-in-skills/prismer-github/templates/pr-body-feature.md +33 -0
  359. package/built-in-skills/prismer-google-workspace/LICENSE +21 -0
  360. package/built-in-skills/prismer-google-workspace/NOTICE.md +11 -0
  361. package/built-in-skills/prismer-google-workspace/SKILL.md +369 -0
  362. package/built-in-skills/prismer-google-workspace/references/daily-brief.md +65 -0
  363. package/built-in-skills/prismer-google-workspace/references/gmail-search-syntax.md +63 -0
  364. package/built-in-skills/prismer-google-workspace/scripts/_hermes_home.py +17 -0
  365. package/built-in-skills/prismer-google-workspace/scripts/_private_state.py +39 -0
  366. package/built-in-skills/prismer-google-workspace/scripts/google_api.py +1339 -0
  367. package/built-in-skills/prismer-google-workspace/scripts/gws_bridge.py +121 -0
  368. package/built-in-skills/prismer-google-workspace/scripts/setup.py +563 -0
  369. package/built-in-skills/prismer-google-workspace/tests/test_productivity_regressions.py +214 -0
  370. package/built-in-skills/prismer-hermes-agent/LICENSE +21 -0
  371. package/built-in-skills/prismer-hermes-agent/NOTICE.md +17 -0
  372. package/built-in-skills/prismer-hermes-agent/SKILL.md +247 -0
  373. package/built-in-skills/prismer-hermes-agent/references/background-systems.md +108 -0
  374. package/built-in-skills/prismer-hermes-agent/references/cli-reference.md +158 -0
  375. package/built-in-skills/prismer-hermes-agent/references/configuration.md +96 -0
  376. package/built-in-skills/prismer-hermes-agent/references/contributor-guide.md +151 -0
  377. package/built-in-skills/prismer-hermes-agent/references/delegate-task-concurrency-diagnosis.md +47 -0
  378. package/built-in-skills/prismer-hermes-agent/references/desktop-plugins.md +215 -0
  379. package/built-in-skills/prismer-hermes-agent/references/native-mcp.md +351 -0
  380. package/built-in-skills/prismer-hermes-agent/references/petdex.md +82 -0
  381. package/built-in-skills/prismer-hermes-agent/references/portal-auth-for-third-party-apps.md +135 -0
  382. package/built-in-skills/prismer-hermes-agent/references/project-context-files.md +55 -0
  383. package/built-in-skills/prismer-hermes-agent/references/providers-and-models.md +81 -0
  384. package/built-in-skills/prismer-hermes-agent/references/security-privacy.md +73 -0
  385. package/built-in-skills/prismer-hermes-agent/references/slash-commands.md +119 -0
  386. package/built-in-skills/prismer-hermes-agent/references/themes.md +134 -0
  387. package/built-in-skills/prismer-hermes-agent/references/troubleshooting.md +87 -0
  388. package/built-in-skills/prismer-hermes-agent/references/tui-widgets.md +144 -0
  389. package/built-in-skills/prismer-hermes-agent/references/webhooks.md +219 -0
  390. package/built-in-skills/prismer-hermes-agent/references/windows-quirks.md +64 -0
  391. package/built-in-skills/prismer-hermes-agent/templates/clock.mjs +51 -0
  392. package/built-in-skills/prismer-hermes-agent/templates/plugin.js +97 -0
  393. package/built-in-skills/prismer-hermes-agent/templates/skin.yaml +49 -0
  394. package/built-in-skills/prismer-himalaya/LICENSE +21 -0
  395. package/built-in-skills/prismer-himalaya/NOTICE.md +11 -0
  396. package/built-in-skills/prismer-himalaya/SKILL.md +340 -0
  397. package/built-in-skills/prismer-himalaya/references/configuration.md +227 -0
  398. package/built-in-skills/prismer-himalaya/references/message-composition.md +206 -0
  399. package/built-in-skills/prismer-himalaya/scripts/compose_draft.py +48 -0
  400. package/built-in-skills/prismer-im-collab/SKILL.md +27 -7
  401. package/built-in-skills/prismer-imessage/LICENSE +21 -0
  402. package/built-in-skills/prismer-imessage/NOTICE.md +17 -0
  403. package/built-in-skills/prismer-imessage/SKILL.md +128 -0
  404. package/built-in-skills/prismer-imessage/scripts/readiness.py +47 -0
  405. package/built-in-skills/prismer-manim-video/LICENSE +21 -0
  406. package/built-in-skills/prismer-manim-video/LICENSE.hermes +21 -0
  407. package/built-in-skills/prismer-manim-video/PROVENANCE.md +7 -0
  408. package/built-in-skills/prismer-manim-video/README.md +23 -0
  409. package/built-in-skills/prismer-manim-video/SKILL.md +279 -0
  410. package/built-in-skills/prismer-manim-video/references/animation-design-thinking.md +161 -0
  411. package/built-in-skills/prismer-manim-video/references/animations.md +282 -0
  412. package/built-in-skills/prismer-manim-video/references/camera-and-3d.md +135 -0
  413. package/built-in-skills/prismer-manim-video/references/decorations.md +202 -0
  414. package/built-in-skills/prismer-manim-video/references/equations.md +216 -0
  415. package/built-in-skills/prismer-manim-video/references/graphs-and-data.md +163 -0
  416. package/built-in-skills/prismer-manim-video/references/mobjects.md +333 -0
  417. package/built-in-skills/prismer-manim-video/references/paper-explainer.md +255 -0
  418. package/built-in-skills/prismer-manim-video/references/production-quality.md +190 -0
  419. package/built-in-skills/prismer-manim-video/references/rendering.md +187 -0
  420. package/built-in-skills/prismer-manim-video/references/scene-planning.md +118 -0
  421. package/built-in-skills/prismer-manim-video/references/troubleshooting.md +135 -0
  422. package/built-in-skills/prismer-manim-video/references/updaters-and-trackers.md +260 -0
  423. package/built-in-skills/prismer-manim-video/references/visual-design.md +124 -0
  424. package/built-in-skills/prismer-manim-video/scripts/setup.sh +15 -0
  425. package/built-in-skills/prismer-maps/LICENSE +21 -0
  426. package/built-in-skills/prismer-maps/NOTICE.md +11 -0
  427. package/built-in-skills/prismer-maps/SKILL.md +240 -0
  428. package/built-in-skills/prismer-maps/scripts/maps_client.py +1324 -0
  429. package/built-in-skills/prismer-node-inspect-debugger/LICENSE +21 -0
  430. package/built-in-skills/prismer-node-inspect-debugger/NOTICE.md +17 -0
  431. package/built-in-skills/prismer-node-inspect-debugger/SKILL.md +347 -0
  432. package/built-in-skills/prismer-notion/LICENSE +21 -0
  433. package/built-in-skills/prismer-notion/NOTICE.md +11 -0
  434. package/built-in-skills/prismer-notion/SKILL.md +495 -0
  435. package/built-in-skills/prismer-notion/references/block-types.md +112 -0
  436. package/built-in-skills/prismer-obsidian/LICENSE +21 -0
  437. package/built-in-skills/prismer-obsidian/LICENSE.hermes +21 -0
  438. package/built-in-skills/prismer-obsidian/PROVENANCE.md +7 -0
  439. package/built-in-skills/prismer-obsidian/SKILL.md +80 -0
  440. package/built-in-skills/prismer-obsidian/provenance/category-DESCRIPTION.md +3 -0
  441. package/built-in-skills/prismer-obsidian/scripts/write_note.py +84 -0
  442. package/built-in-skills/prismer-p5js/LICENSE +21 -0
  443. package/built-in-skills/prismer-p5js/LICENSE.hermes +21 -0
  444. package/built-in-skills/prismer-p5js/PROVENANCE.md +7 -0
  445. package/built-in-skills/prismer-p5js/README.md +64 -0
  446. package/built-in-skills/prismer-p5js/SKILL.md +574 -0
  447. package/built-in-skills/prismer-p5js/references/animation.md +439 -0
  448. package/built-in-skills/prismer-p5js/references/color-systems.md +355 -0
  449. package/built-in-skills/prismer-p5js/references/core-api.md +410 -0
  450. package/built-in-skills/prismer-p5js/references/export-pipeline.md +534 -0
  451. package/built-in-skills/prismer-p5js/references/interaction.md +398 -0
  452. package/built-in-skills/prismer-p5js/references/shapes-and-geometry.md +300 -0
  453. package/built-in-skills/prismer-p5js/references/troubleshooting.md +532 -0
  454. package/built-in-skills/prismer-p5js/references/typography.md +302 -0
  455. package/built-in-skills/prismer-p5js/references/visual-effects.md +905 -0
  456. package/built-in-skills/prismer-p5js/references/webgl-and-3d.md +423 -0
  457. package/built-in-skills/prismer-p5js/scripts/export-frames.js +91 -0
  458. package/built-in-skills/prismer-p5js/scripts/render.sh +113 -0
  459. package/built-in-skills/prismer-p5js/scripts/serve.sh +25 -0
  460. package/built-in-skills/prismer-p5js/scripts/setup.sh +87 -0
  461. package/built-in-skills/prismer-p5js/templates/viewer.html +401 -0
  462. package/built-in-skills/prismer-popular-web-designs/LICENSE +21 -0
  463. package/built-in-skills/prismer-popular-web-designs/LICENSE.hermes +21 -0
  464. package/built-in-skills/prismer-popular-web-designs/LICENSE.origin +21 -0
  465. package/built-in-skills/prismer-popular-web-designs/PROVENANCE.md +7 -0
  466. package/built-in-skills/prismer-popular-web-designs/SKILL.md +218 -0
  467. package/built-in-skills/prismer-popular-web-designs/templates/airbnb.md +259 -0
  468. package/built-in-skills/prismer-popular-web-designs/templates/airtable.md +102 -0
  469. package/built-in-skills/prismer-popular-web-designs/templates/apple.md +326 -0
  470. package/built-in-skills/prismer-popular-web-designs/templates/bmw.md +193 -0
  471. package/built-in-skills/prismer-popular-web-designs/templates/cal.md +272 -0
  472. package/built-in-skills/prismer-popular-web-designs/templates/claude.md +325 -0
  473. package/built-in-skills/prismer-popular-web-designs/templates/clay.md +317 -0
  474. package/built-in-skills/prismer-popular-web-designs/templates/clickhouse.md +294 -0
  475. package/built-in-skills/prismer-popular-web-designs/templates/cohere.md +279 -0
  476. package/built-in-skills/prismer-popular-web-designs/templates/coinbase.md +142 -0
  477. package/built-in-skills/prismer-popular-web-designs/templates/composio.md +320 -0
  478. package/built-in-skills/prismer-popular-web-designs/templates/cursor.md +322 -0
  479. package/built-in-skills/prismer-popular-web-designs/templates/elevenlabs.md +278 -0
  480. package/built-in-skills/prismer-popular-web-designs/templates/expo.md +294 -0
  481. package/built-in-skills/prismer-popular-web-designs/templates/figma.md +233 -0
  482. package/built-in-skills/prismer-popular-web-designs/templates/framer.md +259 -0
  483. package/built-in-skills/prismer-popular-web-designs/templates/hashicorp.md +291 -0
  484. package/built-in-skills/prismer-popular-web-designs/templates/ibm.md +345 -0
  485. package/built-in-skills/prismer-popular-web-designs/templates/intercom.md +159 -0
  486. package/built-in-skills/prismer-popular-web-designs/templates/kraken.md +138 -0
  487. package/built-in-skills/prismer-popular-web-designs/templates/linear.app.md +380 -0
  488. package/built-in-skills/prismer-popular-web-designs/templates/lovable.md +311 -0
  489. package/built-in-skills/prismer-popular-web-designs/templates/minimax.md +270 -0
  490. package/built-in-skills/prismer-popular-web-designs/templates/mintlify.md +339 -0
  491. package/built-in-skills/prismer-popular-web-designs/templates/miro.md +121 -0
  492. package/built-in-skills/prismer-popular-web-designs/templates/mistral.ai.md +274 -0
  493. package/built-in-skills/prismer-popular-web-designs/templates/mongodb.md +279 -0
  494. package/built-in-skills/prismer-popular-web-designs/templates/notion.md +322 -0
  495. package/built-in-skills/prismer-popular-web-designs/templates/nvidia.md +306 -0
  496. package/built-in-skills/prismer-popular-web-designs/templates/ollama.md +280 -0
  497. package/built-in-skills/prismer-popular-web-designs/templates/opencode.ai.md +294 -0
  498. package/built-in-skills/prismer-popular-web-designs/templates/pinterest.md +243 -0
  499. package/built-in-skills/prismer-popular-web-designs/templates/posthog.md +269 -0
  500. package/built-in-skills/prismer-popular-web-designs/templates/raycast.md +281 -0
  501. package/built-in-skills/prismer-popular-web-designs/templates/replicate.md +274 -0
  502. package/built-in-skills/prismer-popular-web-designs/templates/resend.md +316 -0
  503. package/built-in-skills/prismer-popular-web-designs/templates/revolut.md +198 -0
  504. package/built-in-skills/prismer-popular-web-designs/templates/runwayml.md +257 -0
  505. package/built-in-skills/prismer-popular-web-designs/templates/sanity.md +370 -0
  506. package/built-in-skills/prismer-popular-web-designs/templates/sentry.md +275 -0
  507. package/built-in-skills/prismer-popular-web-designs/templates/spacex.md +207 -0
  508. package/built-in-skills/prismer-popular-web-designs/templates/spotify.md +259 -0
  509. package/built-in-skills/prismer-popular-web-designs/templates/stripe.md +335 -0
  510. package/built-in-skills/prismer-popular-web-designs/templates/supabase.md +268 -0
  511. package/built-in-skills/prismer-popular-web-designs/templates/superhuman.md +265 -0
  512. package/built-in-skills/prismer-popular-web-designs/templates/together.ai.md +276 -0
  513. package/built-in-skills/prismer-popular-web-designs/templates/uber.md +308 -0
  514. package/built-in-skills/prismer-popular-web-designs/templates/vercel.md +323 -0
  515. package/built-in-skills/prismer-popular-web-designs/templates/voltagent.md +336 -0
  516. package/built-in-skills/prismer-popular-web-designs/templates/warp.md +266 -0
  517. package/built-in-skills/prismer-popular-web-designs/templates/webflow.md +105 -0
  518. package/built-in-skills/prismer-popular-web-designs/templates/wise.md +186 -0
  519. package/built-in-skills/prismer-popular-web-designs/templates/x.ai.md +270 -0
  520. package/built-in-skills/prismer-popular-web-designs/templates/zapier.md +341 -0
  521. package/built-in-skills/prismer-product-price-monitor/LICENSE +21 -0
  522. package/built-in-skills/prismer-product-price-monitor/NOTICE.md +11 -0
  523. package/built-in-skills/prismer-product-price-monitor/SKILL.md +97 -0
  524. package/built-in-skills/prismer-product-price-monitor/scripts/watch_state.py +116 -0
  525. package/built-in-skills/prismer-python-debugpy/LICENSE +21 -0
  526. package/built-in-skills/prismer-python-debugpy/NOTICE.md +17 -0
  527. package/built-in-skills/prismer-python-debugpy/SKILL.md +377 -0
  528. package/built-in-skills/prismer-python-debugpy/scripts/dap_client.py +68 -0
  529. package/built-in-skills/prismer-songsee/LICENSE +21 -0
  530. package/built-in-skills/prismer-songsee/LICENSE.hermes +21 -0
  531. package/built-in-skills/prismer-songsee/PROVENANCE.md +7 -0
  532. package/built-in-skills/prismer-songsee/SKILL.md +91 -0
  533. package/built-in-skills/prismer-songwriting-and-ai-music/LICENSE +21 -0
  534. package/built-in-skills/prismer-songwriting-and-ai-music/LICENSE.hermes +21 -0
  535. package/built-in-skills/prismer-songwriting-and-ai-music/PROVENANCE.md +7 -0
  536. package/built-in-skills/prismer-songwriting-and-ai-music/SKILL.md +317 -0
  537. package/built-in-skills/prismer-spike/LICENSE +21 -0
  538. package/built-in-skills/prismer-spike/LICENSE.gsd +21 -0
  539. package/built-in-skills/prismer-spike/NOTICE.md +17 -0
  540. package/built-in-skills/prismer-spike/SKILL.md +212 -0
  541. package/built-in-skills/prismer-teams-meeting-pipeline/LICENSE +21 -0
  542. package/built-in-skills/prismer-teams-meeting-pipeline/NOTICE.md +11 -0
  543. package/built-in-skills/prismer-teams-meeting-pipeline/SKILL.md +156 -0
  544. package/built-in-skills/prismer-xurl/LICENSE +21 -0
  545. package/built-in-skills/prismer-xurl/LICENSE.hermes +21 -0
  546. package/built-in-skills/prismer-xurl/PROVENANCE.md +7 -0
  547. package/built-in-skills/prismer-xurl/SKILL.md +444 -0
  548. package/built-in-skills/prismer-xurl/provenance/category-DESCRIPTION.md +3 -0
  549. package/built-in-skills/prismer-youtube-content/LICENSE +21 -0
  550. package/built-in-skills/prismer-youtube-content/LICENSE.hermes +21 -0
  551. package/built-in-skills/prismer-youtube-content/PROVENANCE.md +7 -0
  552. package/built-in-skills/prismer-youtube-content/SKILL.md +86 -0
  553. package/built-in-skills/prismer-youtube-content/provenance/category-DESCRIPTION.md +3 -0
  554. package/built-in-skills/prismer-youtube-content/references/output-formats.md +56 -0
  555. package/built-in-skills/prismer-youtube-content/scripts/fetch_transcript.py +146 -0
  556. package/built-in-skills/proactivity/SKILL.md +84 -0
  557. package/built-in-skills/remotion/SKILL.md +431 -0
  558. package/built-in-skills/research-arxiv/LICENSE +21 -0
  559. package/built-in-skills/research-arxiv/NOTICE.md +4 -0
  560. package/built-in-skills/research-arxiv/SKILL.md +263 -0
  561. package/built-in-skills/research-arxiv/scripts/search_arxiv.py +111 -0
  562. package/built-in-skills/role-builder/SKILL.md +209 -0
  563. package/built-in-skills/role-builder/scripts/author-role.mjs +350 -0
  564. package/built-in-skills/role-builder/scripts/ingest-role.mjs +121 -0
  565. package/built-in-skills/role-builder/scripts/instantiate-and-run.mjs +304 -0
  566. package/built-in-skills/role-builder/scripts/operation-harness.mjs +269 -0
  567. package/built-in-skills/skill-creator/LICENSE.prismer +21 -0
  568. package/built-in-skills/skill-creator/NOTICE.md +18 -0
  569. package/built-in-skills/skill-creator/SKILL.md +182 -421
  570. package/built-in-skills/skill-creator/eval-viewer/generate_review.py +20 -31
  571. package/built-in-skills/skill-creator/eval-viewer/viewer.html +35 -23
  572. package/built-in-skills/skill-creator/references/external-library-import.md +110 -0
  573. package/built-in-skills/skill-creator/references/hermes-agent-skill-authoring/GUIDE.md +220 -0
  574. package/built-in-skills/skill-creator/references/hermes-agent-skill-authoring/LICENSE +21 -0
  575. package/built-in-skills/skill-creator/references/skill-authoring/GUIDE.md +134 -0
  576. package/built-in-skills/{skill-authoring/skill.json → skill-creator/references/skill-authoring/draft-contract.json} +3 -3
  577. package/built-in-skills/skill-creator/references/skill-builder/GUIDE.md +167 -0
  578. package/built-in-skills/skill-creator/references/skill-builder/scripts/ingest.mjs +93 -0
  579. package/built-in-skills/skill-creator/scripts/aggregate_benchmark.py +3 -2
  580. package/built-in-skills/skill-creator/scripts/import-library.mjs +498 -0
  581. package/built-in-skills/skill-creator/scripts/improve_description.py +14 -16
  582. package/built-in-skills/skill-creator/scripts/package_skill.py +16 -9
  583. package/built-in-skills/skill-creator/scripts/quick_validate.py +22 -5
  584. package/built-in-skills/skill-creator/scripts/run_eval.py +63 -144
  585. package/built-in-skills/skill-creator/scripts/run_loop.py +24 -16
  586. package/built-in-skills/slack-gif-creator/SKILL.md +20 -0
  587. package/built-in-skills/slack-gif-creator/core/gif_builder.py +13 -2
  588. package/built-in-skills/slack-gif-creator/core/validators.py +17 -7
  589. package/built-in-skills/tasks/SKILL.md +39 -24
  590. package/built-in-skills/tdd/LICENSE +21 -0
  591. package/built-in-skills/tdd/SKILL.md +117 -0
  592. package/built-in-skills/tdd/mocking.md +59 -0
  593. package/built-in-skills/tdd/refactoring.md +10 -0
  594. package/built-in-skills/tdd/references/test-driven-development/GUIDE.md +356 -0
  595. package/built-in-skills/tdd/references/test-driven-development/LICENSE +21 -0
  596. package/built-in-skills/tdd/references/test-driven-development/LICENSE.superpowers +21 -0
  597. package/built-in-skills/tdd/references/test-driven-development/NOTICE.md +37 -0
  598. package/built-in-skills/tdd/tests.md +61 -0
  599. package/built-in-skills/team/SKILL.md +3 -2
  600. package/built-in-skills/web-artifacts-builder/SKILL.md +4 -3
  601. package/built-in-skills/web-artifacts-builder/scripts/bundle-artifact.sh +9 -7
  602. package/built-in-skills/web-artifacts-builder/scripts/init-artifact.sh +28 -55
  603. package/built-in-skills/webapp-qa/LICENSE +21 -0
  604. package/built-in-skills/webapp-qa/NOTICE.md +37 -0
  605. package/built-in-skills/webapp-qa/SKILL.md +41 -0
  606. package/built-in-skills/webapp-qa/references/dogfood/GUIDE.md +161 -0
  607. package/built-in-skills/webapp-qa/references/issue-taxonomy.md +109 -0
  608. package/built-in-skills/webapp-qa/templates/dogfood-report-template.md +86 -0
  609. package/built-in-skills/webapp-testing/SKILL.md +10 -2
  610. package/built-in-skills/webapp-testing/examples/console_logging.py +10 -4
  611. package/built-in-skills/webapp-testing/examples/element_discovery.py +11 -4
  612. package/built-in-skills/webapp-testing/examples/static_html_automation.py +14 -9
  613. package/built-in-skills/webapp-testing/scripts/with_server.py +34 -10
  614. package/built-in-skills/wechat-pay/SKILL.md +59 -0
  615. package/built-in-skills/weekly-review/LICENSE +21 -0
  616. package/built-in-skills/weekly-review/NOTICE.md +21 -0
  617. package/built-in-skills/weekly-review/SKILL.md +49 -0
  618. package/components/eaas-pi-components.manifest.json +37 -0
  619. package/dist/cli.cjs +75799 -20210
  620. package/dist/cli.js +75900 -20248
  621. package/dist/index.cjs +76159 -20082
  622. package/dist/index.d.cts +4766 -766
  623. package/dist/index.d.ts +4766 -766
  624. package/dist/index.js +76040 -19960
  625. package/package.json +42 -6
  626. package/plugins/memory/prismer/__init__.py +1215 -0
  627. package/plugins/memory/prismer/plugin.yaml +8 -0
  628. package/plugins/memory/prismer/tool-schemas.generated.json +257 -0
  629. package/plugins/tools/prismer-recall/__init__.py +282 -0
  630. package/plugins/tools/prismer-recall/plugin.yaml +15 -0
  631. package/built-in-skills/memory-curation/SKILL.md +0 -135
  632. package/built-in-skills/skill-authoring/SKILL.md +0 -124
  633. package/built-in-skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
@@ -0,0 +1,1324 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ maps_client.py - CLI tool for maps, geocoding, routing, POI search, and more.
4
+ Uses only Python stdlib. Data from OpenStreetMap/Nominatim, Overpass API, OSRM,
5
+ and TimeAPI.io.
6
+
7
+ Commands:
8
+ search - Geocode a place name to coordinates
9
+ reverse - Reverse geocode coordinates to an address
10
+ nearby - Find nearby POIs by category
11
+ distance - Road distance and travel time between two places
12
+ directions - Turn-by-turn directions between two places
13
+ timezone - Timezone info for coordinates
14
+ bbox - Find POIs within a bounding box
15
+ area - Get bounding box and area info for a named place
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import math
21
+ import os
22
+ from pathlib import Path
23
+ import sqlite3
24
+ import sys
25
+ import time
26
+ import urllib.error
27
+ import urllib.parse
28
+ import urllib.request
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Constants
32
+ # ---------------------------------------------------------------------------
33
+
34
+ USER_AGENT = os.environ.get("PRISMER_MAPS_USER_AGENT", "PrismerMaps/1.0")
35
+ DATA_SOURCE = "OpenStreetMap/Nominatim"
36
+
37
+ NOMINATIM_SEARCH = "https://nominatim.openstreetmap.org/search"
38
+ NOMINATIM_REVERSE = "https://nominatim.openstreetmap.org/reverse"
39
+ # Public Overpass endpoints. We try them in order so a single server
40
+ # outage doesn't break the skill — kumi.systems is a well-known mirror.
41
+ OVERPASS_URLS = [
42
+ "https://overpass-api.de/api/interpreter",
43
+ "https://overpass.kumi.systems/api/interpreter",
44
+ ]
45
+ # Backward-compat alias for any caller that imports OVERPASS_API directly.
46
+ OVERPASS_API = OVERPASS_URLS[0]
47
+ OSRM_BASE = "https://router.project-osrm.org/route/v1"
48
+ TIMEAPI_BASE = "https://timeapi.io/api/timezone/coordinate"
49
+
50
+ # Seconds to sleep between Nominatim requests (ToS requirement)
51
+ NOMINATIM_RATE_LIMIT = 1.0
52
+
53
+ # Maximum retries for HTTP errors
54
+ MAX_RETRIES = 3
55
+ RETRY_DELAY = 2.0 # seconds
56
+
57
+ # Category -> (OSM tag key, OSM tag value)
58
+ CATEGORY_TAGS = {
59
+ # Food & Drink
60
+ "restaurant": ("amenity", "restaurant"),
61
+ "cafe": ("amenity", "cafe"),
62
+ "bar": ("amenity", "bar"),
63
+ # bakery is tagged as shop=bakery in the OSM wiki, but some mappers use
64
+ # amenity=bakery. Search both so small indie bakeries aren't missed.
65
+ "bakery": [("shop", "bakery"), ("amenity", "bakery")],
66
+ "convenience_store": ("shop", "convenience"),
67
+ # Health
68
+ "hospital": ("amenity", "hospital"),
69
+ "pharmacy": ("amenity", "pharmacy"),
70
+ "dentist": ("amenity", "dentist"),
71
+ "doctor": ("amenity", "doctors"),
72
+ "veterinary": ("amenity", "veterinary"),
73
+ # Accommodation
74
+ "hotel": ("tourism", "hotel"),
75
+ "guest_house": ("tourism", "guest_house"),
76
+ "camp_site": ("tourism", "camp_site"),
77
+ # Shopping & Services
78
+ "supermarket": ("shop", "supermarket"),
79
+ "bookshop": ("shop", "books"),
80
+ "laundry": ("shop", "laundry"),
81
+ # Finance
82
+ "atm": ("amenity", "atm"),
83
+ "bank": ("amenity", "bank"),
84
+ # Transport
85
+ "gas_station": ("amenity", "fuel"),
86
+ "parking": ("amenity", "parking"),
87
+ "airport": ("aeroway", "aerodrome"),
88
+ "train_station": ("railway", "station"),
89
+ "bus_stop": ("highway", "bus_stop"),
90
+ "taxi": ("amenity", "taxi"),
91
+ "car_wash": ("amenity", "car_wash"),
92
+ "car_rental": ("amenity", "car_rental"),
93
+ "bicycle_rental": ("amenity", "bicycle_rental"),
94
+ # Culture & Entertainment
95
+ "museum": ("tourism", "museum"),
96
+ "cinema": ("amenity", "cinema"),
97
+ "theatre": ("amenity", "theatre"),
98
+ "nightclub": ("amenity", "nightclub"),
99
+ "zoo": ("tourism", "zoo"),
100
+ # Education
101
+ "school": ("amenity", "school"),
102
+ "university": ("amenity", "university"),
103
+ "library": ("amenity", "library"),
104
+ # Public Services
105
+ "police": ("amenity", "police"),
106
+ "fire_station": ("amenity", "fire_station"),
107
+ "post_office": ("amenity", "post_office"),
108
+ # Religion
109
+ "church": ("amenity", "place_of_worship"), # refined by religion tag
110
+ "mosque": ("amenity", "place_of_worship"),
111
+ "synagogue": ("amenity", "place_of_worship"),
112
+ # Recreation
113
+ "park": ("leisure", "park"),
114
+ "gym": ("leisure", "fitness_centre"),
115
+ "swimming_pool": ("leisure", "swimming_pool"),
116
+ "playground": ("leisure", "playground"),
117
+ "stadium": ("leisure", "stadium"),
118
+ }
119
+
120
+ # Religion-specific overrides for place_of_worship categories
121
+ RELIGION_FILTER = {
122
+ "church": "christian",
123
+ "mosque": "muslim",
124
+ "synagogue": "jewish",
125
+ }
126
+
127
+ VALID_CATEGORIES = sorted(CATEGORY_TAGS.keys())
128
+
129
+
130
+ def _tags_for(category):
131
+ """Return the CATEGORY_TAGS entry as a list of (key, value) pairs.
132
+
133
+ Most categories map to a single (tag_key, tag_val) tuple, but some
134
+ (e.g. ``bakery``) are tagged under more than one OSM key and are
135
+ represented as a list of tuples. Normalise both forms to a list.
136
+ """
137
+ entry = CATEGORY_TAGS[category]
138
+ if isinstance(entry, list):
139
+ return list(entry)
140
+ return [entry]
141
+
142
+ OSRM_PROFILES = {
143
+ "driving": "driving",
144
+ "walking": "foot",
145
+ "cycling": "bike",
146
+ }
147
+
148
+ # ---------------------------------------------------------------------------
149
+ # Output helpers
150
+ # ---------------------------------------------------------------------------
151
+
152
+ def print_json(data):
153
+ """Print data as pretty-printed JSON to stdout."""
154
+ print(json.dumps(data, indent=2, ensure_ascii=False))
155
+
156
+
157
+ def error_exit(message, code=1):
158
+ """Print an error result as JSON and exit."""
159
+ print_json({"error": message, "status": "error"})
160
+ sys.exit(code)
161
+
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # HTTP helpers
165
+ # ---------------------------------------------------------------------------
166
+
167
+ def reserve_nominatim_slot(now=None):
168
+ """Atomically reserve a host-user slot shared by separate CLI processes."""
169
+ path = Path(os.environ.get('PRISMER_MAPS_RATE_DB', str(Path.home() / '.cache/prismer/maps-rate.sqlite')))
170
+ if path.is_symlink():
171
+ raise ValueError('refusing symlink rate database')
172
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
173
+ fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o600)
174
+ os.close(fd)
175
+ db = sqlite3.connect(path, timeout=30)
176
+ try:
177
+ db.execute('BEGIN IMMEDIATE')
178
+ db.execute('CREATE TABLE IF NOT EXISTS rate (id INTEGER PRIMARY KEY, next REAL)')
179
+ now = time.time() if now is None else now
180
+ row = db.execute('SELECT next FROM rate WHERE id=1').fetchone()
181
+ slot = max(now, row[0] if row else now)
182
+ db.execute('INSERT OR REPLACE INTO rate VALUES (1,?)', (slot + NOMINATIM_RATE_LIMIT,))
183
+ db.commit()
184
+ return slot - now
185
+ finally:
186
+ db.close()
187
+
188
+
189
+ def http_get(url, params=None, retries=MAX_RETRIES, silent=False):
190
+ """
191
+ Perform an HTTP GET request, returning parsed JSON.
192
+ Adds the required User-Agent header. Retries on transient errors.
193
+ If silent=True, raises RuntimeError instead of calling error_exit.
194
+ """
195
+ if params:
196
+ url = url + "?" + urllib.parse.urlencode(params)
197
+
198
+ req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
199
+
200
+ last_error = None
201
+ for attempt in range(1, retries + 1):
202
+ try:
203
+ if urllib.parse.urlparse(url).hostname == 'nominatim.openstreetmap.org':
204
+ time.sleep(reserve_nominatim_slot())
205
+ with urllib.request.urlopen(req, timeout=15) as resp:
206
+ raw = resp.read().decode("utf-8")
207
+ return json.loads(raw)
208
+ except urllib.error.HTTPError as exc:
209
+ last_error = f"HTTP {exc.code}: {exc.reason} for {url}"
210
+ if exc.code in {429, 503, 502, 504}:
211
+ time.sleep(RETRY_DELAY * attempt)
212
+ else:
213
+ if silent:
214
+ raise RuntimeError(last_error)
215
+ error_exit(last_error)
216
+ except urllib.error.URLError as exc:
217
+ last_error = f"URL error: {exc.reason}"
218
+ time.sleep(RETRY_DELAY * attempt)
219
+ except json.JSONDecodeError as exc:
220
+ last_error = f"JSON parse error: {exc}"
221
+ time.sleep(RETRY_DELAY * attempt)
222
+
223
+ msg = f"Request failed after {retries} attempts. Last error: {last_error}"
224
+ if silent:
225
+ raise RuntimeError(msg)
226
+ error_exit(msg)
227
+
228
+
229
+ def http_get_text(url, params=None, retries=MAX_RETRIES, silent=False):
230
+ """
231
+ Like http_get but returns raw text instead of parsed JSON.
232
+ Useful for APIs that may return non-JSON responses.
233
+ """
234
+ if params:
235
+ url = url + "?" + urllib.parse.urlencode(params)
236
+
237
+ req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
238
+
239
+ last_error = None
240
+ for attempt in range(1, retries + 1):
241
+ try:
242
+ with urllib.request.urlopen(req, timeout=15) as resp:
243
+ return resp.read().decode("utf-8")
244
+ except urllib.error.HTTPError as exc:
245
+ last_error = f"HTTP {exc.code}: {exc.reason} for {url}"
246
+ if exc.code in {429, 503, 502, 504}:
247
+ time.sleep(RETRY_DELAY * attempt)
248
+ else:
249
+ if silent:
250
+ raise RuntimeError(last_error)
251
+ error_exit(last_error)
252
+ except urllib.error.URLError as exc:
253
+ last_error = f"URL error: {exc.reason}"
254
+ time.sleep(RETRY_DELAY * attempt)
255
+
256
+ msg = f"Request failed after {retries} attempts. Last error: {last_error}"
257
+ if silent:
258
+ raise RuntimeError(msg)
259
+ error_exit(msg)
260
+
261
+
262
+ def http_post(url, data_str, retries=MAX_RETRIES):
263
+ """
264
+ Perform an HTTP POST with a plain-text body (for Overpass QL).
265
+ Returns parsed JSON.
266
+ """
267
+ encoded = data_str.encode("utf-8")
268
+ req = urllib.request.Request(
269
+ url,
270
+ data=encoded,
271
+ headers={
272
+ "User-Agent": USER_AGENT,
273
+ "Content-Type": "application/x-www-form-urlencoded",
274
+ },
275
+ )
276
+
277
+ last_error = None
278
+ for attempt in range(1, retries + 1):
279
+ try:
280
+ with urllib.request.urlopen(req, timeout=30) as resp:
281
+ raw = resp.read().decode("utf-8")
282
+ return json.loads(raw)
283
+ except urllib.error.HTTPError as exc:
284
+ last_error = f"HTTP {exc.code}: {exc.reason}"
285
+ if exc.code in {429, 503, 502, 504}:
286
+ time.sleep(RETRY_DELAY * attempt)
287
+ else:
288
+ raise RuntimeError(last_error)
289
+ except urllib.error.URLError as exc:
290
+ last_error = f"URL error: {exc.reason}"
291
+ time.sleep(RETRY_DELAY * attempt)
292
+ except json.JSONDecodeError as exc:
293
+ last_error = f"JSON parse error: {exc}"
294
+ time.sleep(RETRY_DELAY * attempt)
295
+
296
+ raise RuntimeError(f"POST failed after {retries} attempts. Last error: {last_error}")
297
+
298
+
299
+ def overpass_query(query):
300
+ """POST an Overpass QL query, trying each URL in OVERPASS_URLS in turn.
301
+
302
+ A single public Overpass mirror can be rate-limited or down; trying the
303
+ next mirror before giving up turns a flaky outage into a retry. Returns
304
+ parsed JSON. Falls through to error_exit if every mirror fails.
305
+ """
306
+ post_data = "data=" + urllib.parse.quote(query)
307
+ last_error = None
308
+ for url in OVERPASS_URLS:
309
+ try:
310
+ return http_post(url, post_data, retries=1)
311
+ except SystemExit:
312
+ # error_exit inside http_post — keep trying the next mirror.
313
+ last_error = f"mirror {url} exhausted retries"
314
+ continue
315
+ except Exception as exc:
316
+ last_error = f"{url}: {exc}"
317
+ continue
318
+ error_exit(
319
+ f"All Overpass mirrors failed. Last error: {last_error or 'unknown'}"
320
+ )
321
+
322
+
323
+ # ---------------------------------------------------------------------------
324
+ # Geo math
325
+ # ---------------------------------------------------------------------------
326
+
327
+ def haversine_m(lat1, lon1, lat2, lon2):
328
+ """Return distance in metres between two lat/lon points (Haversine)."""
329
+ R = 6_371_000 # Earth mean radius in metres
330
+ phi1 = math.radians(lat1)
331
+ phi2 = math.radians(lat2)
332
+ dphi = math.radians(lat2 - lat1)
333
+ dlam = math.radians(lon2 - lon1)
334
+ a = (math.sin(dphi / 2) ** 2
335
+ + math.cos(phi1) * math.cos(phi2) * math.sin(dlam / 2) ** 2)
336
+ return 2 * R * math.atan2(math.sqrt(a), math.sqrt(1 - a))
337
+
338
+
339
+ # ---------------------------------------------------------------------------
340
+ # Nominatim helpers
341
+ # ---------------------------------------------------------------------------
342
+
343
+ def nominatim_search(query, limit=5):
344
+ """Geocode a free-text query. Returns list of result dicts."""
345
+ params = {
346
+ "q": query,
347
+ "format": "json",
348
+ "limit": limit,
349
+ "addressdetails": 1,
350
+ }
351
+ time.sleep(NOMINATIM_RATE_LIMIT)
352
+ return http_get(NOMINATIM_SEARCH, params=params)
353
+
354
+
355
+ def nominatim_reverse(lat, lon):
356
+ """Reverse geocode lat/lon. Returns a single result dict."""
357
+ params = {
358
+ "lat": lat,
359
+ "lon": lon,
360
+ "format": "json",
361
+ "addressdetails": 1,
362
+ }
363
+ time.sleep(NOMINATIM_RATE_LIMIT)
364
+ return http_get(NOMINATIM_REVERSE, params=params)
365
+
366
+
367
+ def geocode_single(query):
368
+ """
369
+ Geocode a query and return (lat, lon, display_name).
370
+ Exits with error if nothing found.
371
+ """
372
+ results = nominatim_search(query, limit=1)
373
+ if not results:
374
+ error_exit(f"Could not geocode: {query}")
375
+ r = results[0]
376
+ return float(r["lat"]), float(r["lon"]), r.get("display_name", query)
377
+
378
+
379
+ # ---------------------------------------------------------------------------
380
+ # Overpass helpers
381
+ # ---------------------------------------------------------------------------
382
+
383
+ def build_overpass_nearby(tag_key, tag_val, lat, lon, radius, limit,
384
+ religion=None, tag_pairs=None):
385
+ """Build an Overpass QL query for nearby POIs around a point.
386
+
387
+ If ``tag_pairs`` is provided, the query unions across every
388
+ ``(key, value)`` pair (used for categories like ``bakery`` that are
389
+ tagged under more than one OSM key). Otherwise falls back to the
390
+ single ``tag_key``/``tag_val`` pair for back-compat.
391
+ """
392
+ pairs = tag_pairs if tag_pairs else [(tag_key, tag_val)]
393
+ religion_filter = ""
394
+ if religion:
395
+ religion_filter = f'["religion"="{religion}"]'
396
+ body_lines = []
397
+ for k, v in pairs:
398
+ body_lines.append(
399
+ f' node["{k}"="{v}"]{religion_filter}'
400
+ f'(around:{radius},{lat},{lon});'
401
+ )
402
+ body_lines.append(
403
+ f' way["{k}"="{v}"]{religion_filter}'
404
+ f'(around:{radius},{lat},{lon});'
405
+ )
406
+ body = "\n".join(body_lines)
407
+ return (
408
+ f'[out:json][timeout:25];\n'
409
+ f'(\n'
410
+ f'{body}\n'
411
+ f');\n'
412
+ f'out center {limit};\n'
413
+ )
414
+
415
+
416
+ def build_overpass_bbox(tag_key, tag_val, south, west, north, east, limit,
417
+ religion=None, tag_pairs=None):
418
+ """Build an Overpass QL query for POIs within a bounding box.
419
+
420
+ See ``build_overpass_nearby`` for ``tag_pairs`` semantics.
421
+ """
422
+ pairs = tag_pairs if tag_pairs else [(tag_key, tag_val)]
423
+ religion_filter = ""
424
+ if religion:
425
+ religion_filter = f'["religion"="{religion}"]'
426
+ body_lines = []
427
+ for k, v in pairs:
428
+ body_lines.append(
429
+ f' node["{k}"="{v}"]{religion_filter}'
430
+ f'({south},{west},{north},{east});'
431
+ )
432
+ body_lines.append(
433
+ f' way["{k}"="{v}"]{religion_filter}'
434
+ f'({south},{west},{north},{east});'
435
+ )
436
+ body = "\n".join(body_lines)
437
+ return (
438
+ f'[out:json][timeout:25];\n'
439
+ f'(\n'
440
+ f'{body}\n'
441
+ f');\n'
442
+ f'out center {limit};\n'
443
+ )
444
+
445
+
446
+ def parse_overpass_elements(elements, ref_lat=None, ref_lon=None):
447
+ """
448
+ Parse Overpass elements into a clean list of POI dicts.
449
+ If ref_lat/ref_lon are provided, computes distance and sorts by it.
450
+ """
451
+ places = []
452
+ for el in elements:
453
+ # Ways have a "center" sub-dict; nodes have lat/lon directly
454
+ if el["type"] == "way":
455
+ center = el.get("center", {})
456
+ el_lat = center.get("lat")
457
+ el_lon = center.get("lon")
458
+ else:
459
+ el_lat = el.get("lat")
460
+ el_lon = el.get("lon")
461
+
462
+ if el_lat is None or el_lon is None:
463
+ continue
464
+
465
+ tags = el.get("tags", {})
466
+ name = tags.get("name") or tags.get("name:en") or ""
467
+
468
+ # Build a short address from available tags
469
+ addr_parts = []
470
+ for part_key in ("addr:housenumber", "addr:street", "addr:city"):
471
+ val = tags.get(part_key)
472
+ if val:
473
+ addr_parts.append(val)
474
+ address_str = ", ".join(addr_parts) if addr_parts else ""
475
+
476
+ place = {
477
+ "name": name,
478
+ "address": address_str,
479
+ "lat": el_lat,
480
+ "lon": el_lon,
481
+ "osm_type": el.get("type", ""),
482
+ "osm_id": el.get("id", ""),
483
+ # Clickable Google Maps link so the agent can render a tap-to-open
484
+ # URL in chat without composing one downstream.
485
+ "maps_url": f"https://www.google.com/maps/search/?api=1&query={el_lat},{el_lon}",
486
+ "tags": {
487
+ k: v for k, v in tags.items()
488
+ if k not in {"name", "name:en",
489
+ "addr:housenumber", "addr:street", "addr:city"}
490
+ },
491
+ }
492
+
493
+ # Promote commonly-useful tags to top-level fields so agents can
494
+ # reference them without digging into the raw ``tags`` dict.
495
+ for src_key, dst_key in (
496
+ ("cuisine", "cuisine"),
497
+ ("opening_hours", "hours"),
498
+ ("phone", "phone"),
499
+ ("website", "website"),
500
+ ):
501
+ val = tags.get(src_key)
502
+ if val:
503
+ place[dst_key] = val
504
+
505
+ if ref_lat is not None and ref_lon is not None:
506
+ dist_m = haversine_m(ref_lat, ref_lon, el_lat, el_lon)
507
+ place["distance_m"] = round(dist_m, 1)
508
+ # With a reference point we can also hand back a directions URL.
509
+ place["directions_url"] = (
510
+ f"https://www.google.com/maps/dir/?api=1"
511
+ f"&origin={ref_lat},{ref_lon}"
512
+ f"&destination={el_lat},{el_lon}"
513
+ )
514
+
515
+ places.append(place)
516
+
517
+ # Sort by distance if available
518
+ if places and "distance_m" in places[0]:
519
+ places.sort(key=lambda p: p["distance_m"])
520
+
521
+ return places
522
+
523
+
524
+ # ---------------------------------------------------------------------------
525
+ # Command: search
526
+ # ---------------------------------------------------------------------------
527
+
528
+ def cmd_search(args):
529
+ """Geocode a place name and return top results."""
530
+ query = " ".join(args.query)
531
+ raw = nominatim_search(query, limit=5)
532
+
533
+ if not raw:
534
+ print_json({
535
+ "query": query,
536
+ "results": [],
537
+ "count": 0,
538
+ "data_source": DATA_SOURCE,
539
+ })
540
+ return
541
+
542
+ results = []
543
+ for item in raw:
544
+ bb = item.get("boundingbox", [])
545
+ results.append({
546
+ "name": item.get("name") or item.get("display_name", ""),
547
+ "display_name": item.get("display_name", ""),
548
+ "lat": float(item["lat"]),
549
+ "lon": float(item["lon"]),
550
+ "type": item.get("type", ""),
551
+ "category": item.get("category", ""),
552
+ "osm_type": item.get("osm_type", ""),
553
+ "osm_id": item.get("osm_id", ""),
554
+ "bounding_box": {
555
+ "min_lat": float(bb[0]) if len(bb) > 0 else None,
556
+ "max_lat": float(bb[1]) if len(bb) > 1 else None,
557
+ "min_lon": float(bb[2]) if len(bb) > 2 else None,
558
+ "max_lon": float(bb[3]) if len(bb) > 3 else None,
559
+ },
560
+ "importance": item.get("importance"),
561
+ })
562
+
563
+ print_json({
564
+ "query": query,
565
+ "results": results,
566
+ "count": len(results),
567
+ "data_source": DATA_SOURCE,
568
+ })
569
+
570
+
571
+ # ---------------------------------------------------------------------------
572
+ # Command: reverse
573
+ # ---------------------------------------------------------------------------
574
+
575
+ def cmd_reverse(args):
576
+ """Reverse geocode coordinates to a human-readable address."""
577
+ try:
578
+ lat = float(args.lat)
579
+ lon = float(args.lon)
580
+ except ValueError:
581
+ error_exit("LAT and LON must be numeric values.")
582
+
583
+ if not (-90 <= lat <= 90):
584
+ error_exit("Latitude must be between -90 and 90.")
585
+ if not (-180 <= lon <= 180):
586
+ error_exit("Longitude must be between -180 and 180.")
587
+
588
+ data = nominatim_reverse(lat, lon)
589
+
590
+ if "error" in data:
591
+ error_exit(f"Reverse geocode failed: {data['error']}")
592
+
593
+ address = data.get("address", {})
594
+
595
+ print_json({
596
+ "lat": lat,
597
+ "lon": lon,
598
+ "display_name": data.get("display_name", ""),
599
+ "address": {
600
+ "house_number": address.get("house_number", ""),
601
+ "road": address.get("road", ""),
602
+ "neighbourhood": address.get("neighbourhood", ""),
603
+ "suburb": address.get("suburb", ""),
604
+ "city": (address.get("city")
605
+ or address.get("town")
606
+ or address.get("village", "")),
607
+ "county": address.get("county", ""),
608
+ "state": address.get("state", ""),
609
+ "postcode": address.get("postcode", ""),
610
+ "country": address.get("country", ""),
611
+ "country_code": address.get("country_code", ""),
612
+ },
613
+ "osm_type": data.get("osm_type", ""),
614
+ "osm_id": data.get("osm_id", ""),
615
+ "data_source": DATA_SOURCE,
616
+ })
617
+
618
+
619
+ # ---------------------------------------------------------------------------
620
+ # Command: nearby
621
+ # ---------------------------------------------------------------------------
622
+
623
+ def cmd_nearby(args):
624
+ """Find nearby POIs using the Overpass API.
625
+
626
+ Accepts either explicit coordinates (``lat``/``lon``) or a free-form
627
+ address via ``--near`` (auto-geocoded through Nominatim). Supports
628
+ multiple categories in one call — results are merged, deduplicated
629
+ by ``osm_type+osm_id``, sorted by distance.
630
+ """
631
+ # Resolve the center point. --near takes precedence if provided so the
632
+ # agent can ask "cafes near Times Square" in one command without having
633
+ # to geocode first.
634
+ if getattr(args, "near", None):
635
+ near_query = " ".join(args.near).strip() if isinstance(args.near, list) else str(args.near).strip()
636
+ if not near_query:
637
+ error_exit("--near must be a non-empty address or place name.")
638
+ lat, lon, _ = geocode_single(near_query)
639
+ else:
640
+ try:
641
+ lat = float(args.lat)
642
+ lon = float(args.lon)
643
+ except (TypeError, ValueError):
644
+ error_exit("Provide numeric LAT and LON, or use --near \"<address>\".")
645
+
646
+ # Categories: support both legacy single positional ``category`` and the
647
+ # new repeatable ``--category`` flag. Users can ask for multiple place
648
+ # types in one query.
649
+ categories = []
650
+ if getattr(args, "category_list", None):
651
+ categories.extend(args.category_list)
652
+ if getattr(args, "category", None):
653
+ categories.append(args.category)
654
+ # Deduplicate, preserve order, lower-case.
655
+ categories = list(dict.fromkeys(c.lower() for c in categories if c))
656
+ if not categories:
657
+ error_exit("Provide at least one category (positional or --category).")
658
+ unknown = [c for c in categories if c not in CATEGORY_TAGS]
659
+ if unknown:
660
+ error_exit(
661
+ f"Unknown categor{'ies' if len(unknown) > 1 else 'y'} "
662
+ f"{', '.join(repr(c) for c in unknown)}. "
663
+ f"Valid categories: {', '.join(VALID_CATEGORIES)}"
664
+ )
665
+
666
+ radius = int(args.radius)
667
+ limit = int(args.limit)
668
+ if radius <= 0:
669
+ error_exit("Radius must be a positive integer (metres).")
670
+ if limit <= 0:
671
+ error_exit("Limit must be a positive integer.")
672
+
673
+ # Query each category against the Overpass fallback chain, merge results,
674
+ # dedupe by OSM identity so POIs tagged under multiple categories don't
675
+ # appear twice.
676
+ merged = {}
677
+ for category in categories:
678
+ tag_pairs = _tags_for(category)
679
+ religion = RELIGION_FILTER.get(category)
680
+ query = build_overpass_nearby(None, None, lat, lon, radius, limit,
681
+ religion=religion, tag_pairs=tag_pairs)
682
+ raw = overpass_query(query)
683
+ elements = raw.get("elements", [])
684
+ for place in parse_overpass_elements(elements, ref_lat=lat, ref_lon=lon):
685
+ place["category"] = category
686
+ key = (place.get("osm_type", ""), place.get("osm_id", ""))
687
+ # Prefer the entry that actually has a distance_m attached (first
688
+ # pass through the ref_lat/ref_lon branch), then first-seen wins.
689
+ if key not in merged:
690
+ merged[key] = place
691
+
692
+ # Sort merged by distance when we have ref lat/lon, then cap at ``limit``.
693
+ places = sorted(
694
+ merged.values(),
695
+ key=lambda p: p.get("distance_m", float("inf")),
696
+ )[:limit]
697
+
698
+ print_json({
699
+ "center_lat": lat,
700
+ "center_lon": lon,
701
+ "categories": categories,
702
+ "radius_m": radius,
703
+ "count": len(places),
704
+ "results": places,
705
+ "data_source": DATA_SOURCE,
706
+ })
707
+
708
+
709
+ # ---------------------------------------------------------------------------
710
+ # Command: distance
711
+ # ---------------------------------------------------------------------------
712
+
713
+ def cmd_distance(args):
714
+ """Calculate road distance and travel time between two places."""
715
+ origin_query = " ".join(args.origin)
716
+ destination_query = " ".join(args.to)
717
+ mode = args.mode.lower()
718
+
719
+ if mode not in OSRM_PROFILES:
720
+ error_exit(f"Invalid mode '{mode}'. Choose from: {', '.join(OSRM_PROFILES)}")
721
+
722
+ # Geocode origin and destination
723
+ o_lat, o_lon, o_name = geocode_single(origin_query)
724
+ d_lat, d_lon, d_name = geocode_single(destination_query)
725
+
726
+ profile = OSRM_PROFILES[mode]
727
+ url = (
728
+ f"{OSRM_BASE}/{profile}/"
729
+ f"{o_lon},{o_lat};{d_lon},{d_lat}"
730
+ f"?overview=false&steps=false"
731
+ )
732
+
733
+ osrm_data = http_get(url)
734
+
735
+ if osrm_data.get("code") != "Ok":
736
+ error_exit(
737
+ f"OSRM routing failed: "
738
+ f"{osrm_data.get('message', osrm_data.get('code', 'unknown error'))}"
739
+ )
740
+
741
+ routes = osrm_data.get("routes", [])
742
+ if not routes:
743
+ error_exit("No route found between the two locations.")
744
+
745
+ route = routes[0]
746
+ distance_m = route.get("distance", 0)
747
+ duration_s = route.get("duration", 0)
748
+ distance_km = round(distance_m / 1000, 3)
749
+ duration_min = round(duration_s / 60, 2)
750
+
751
+ # Straight-line distance for reference
752
+ straight_m = haversine_m(o_lat, o_lon, d_lat, d_lon)
753
+
754
+ print_json({
755
+ "origin": {
756
+ "query": origin_query,
757
+ "display_name": o_name,
758
+ "lat": o_lat,
759
+ "lon": o_lon,
760
+ },
761
+ "destination": {
762
+ "query": destination_query,
763
+ "display_name": d_name,
764
+ "lat": d_lat,
765
+ "lon": d_lon,
766
+ },
767
+ "mode": mode,
768
+ "distance_km": distance_km,
769
+ "distance_m": round(distance_m, 1),
770
+ "duration_minutes": duration_min,
771
+ "duration_seconds": round(duration_s, 1),
772
+ "straight_line_km": round(straight_m / 1000, 3),
773
+ "data_source": DATA_SOURCE,
774
+ })
775
+
776
+
777
+ # ---------------------------------------------------------------------------
778
+ # Command: directions
779
+ # ---------------------------------------------------------------------------
780
+
781
+ def _format_duration(seconds):
782
+ """Format seconds into a human-readable string."""
783
+ if seconds < 60:
784
+ return f"{round(seconds)}s"
785
+ minutes = seconds / 60
786
+ if minutes < 60:
787
+ return f"{round(minutes, 1)} min"
788
+ hours = int(minutes // 60)
789
+ remaining = round(minutes % 60)
790
+ return f"{hours}h {remaining}min"
791
+
792
+
793
+ def _format_distance(metres):
794
+ """Format metres into a human-readable string."""
795
+ if metres < 1000:
796
+ return f"{round(metres)} m"
797
+ return f"{round(metres / 1000, 2)} km"
798
+
799
+
800
+ def cmd_directions(args):
801
+ """Get turn-by-turn directions between two places via OSRM."""
802
+ origin_query = " ".join(args.origin)
803
+ destination_query = " ".join(args.to)
804
+ mode = args.mode.lower()
805
+
806
+ if mode not in OSRM_PROFILES:
807
+ error_exit(f"Invalid mode '{mode}'. Choose from: {', '.join(OSRM_PROFILES)}")
808
+
809
+ # Geocode origin and destination
810
+ o_lat, o_lon, o_name = geocode_single(origin_query)
811
+ d_lat, d_lon, d_name = geocode_single(destination_query)
812
+
813
+ profile = OSRM_PROFILES[mode]
814
+ url = (
815
+ f"{OSRM_BASE}/{profile}/"
816
+ f"{o_lon},{o_lat};{d_lon},{d_lat}"
817
+ f"?overview=false&steps=true"
818
+ )
819
+
820
+ osrm_data = http_get(url)
821
+
822
+ if osrm_data.get("code") != "Ok":
823
+ error_exit(
824
+ f"OSRM routing failed: "
825
+ f"{osrm_data.get('message', osrm_data.get('code', 'unknown error'))}"
826
+ )
827
+
828
+ routes = osrm_data.get("routes", [])
829
+ if not routes:
830
+ error_exit("No route found between the two locations.")
831
+
832
+ route = routes[0]
833
+ distance_m = route.get("distance", 0)
834
+ duration_s = route.get("duration", 0)
835
+
836
+ # Extract steps from all legs
837
+ steps = []
838
+ step_num = 0
839
+ for leg in route.get("legs", []):
840
+ for step in leg.get("steps", []):
841
+ maneuver = step.get("maneuver", {})
842
+ step_dist = step.get("distance", 0)
843
+ step_dur = step.get("duration", 0)
844
+ step_name = step.get("name", "")
845
+ modifier = maneuver.get("modifier", "")
846
+ m_type = maneuver.get("type", "")
847
+
848
+ # Build instruction text
849
+ if m_type == "depart":
850
+ instruction = f"Depart on {step_name}" if step_name else "Depart"
851
+ elif m_type == "arrive":
852
+ instruction = "Arrive at destination"
853
+ elif m_type == "turn":
854
+ instruction = f"Turn {modifier} onto {step_name}" if step_name else f"Turn {modifier}"
855
+ elif m_type == "new name":
856
+ instruction = f"Continue onto {step_name}" if step_name else "Continue"
857
+ elif m_type == "merge":
858
+ instruction = f"Merge {modifier} onto {step_name}" if step_name else f"Merge {modifier}"
859
+ elif m_type == "fork":
860
+ instruction = f"Take the {modifier} fork onto {step_name}" if step_name else f"Take the {modifier} fork"
861
+ elif m_type == "roundabout":
862
+ instruction = f"Enter roundabout, exit onto {step_name}" if step_name else "Enter roundabout"
863
+ elif m_type == "rotary":
864
+ instruction = f"Enter rotary, exit onto {step_name}" if step_name else "Enter rotary"
865
+ elif m_type == "end of road":
866
+ instruction = f"At end of road, turn {modifier} onto {step_name}" if step_name else f"At end of road, turn {modifier}"
867
+ elif m_type == "continue":
868
+ instruction = f"Continue {modifier} on {step_name}" if step_name else f"Continue {modifier}"
869
+ elif m_type == "on ramp":
870
+ instruction = f"Take ramp onto {step_name}" if step_name else "Take ramp"
871
+ elif m_type == "off ramp":
872
+ instruction = f"Take exit onto {step_name}" if step_name else "Take exit"
873
+ else:
874
+ instruction = f"{m_type} {modifier} {step_name}".strip()
875
+
876
+ step_num += 1
877
+ steps.append({
878
+ "step": step_num,
879
+ "instruction": instruction,
880
+ "distance": _format_distance(step_dist),
881
+ "distance_m": round(step_dist, 1),
882
+ "duration": _format_duration(step_dur),
883
+ "duration_s": round(step_dur, 1),
884
+ "road_name": step_name,
885
+ "maneuver": m_type,
886
+ })
887
+
888
+ print_json({
889
+ "origin": {
890
+ "query": origin_query,
891
+ "display_name": o_name,
892
+ "lat": o_lat,
893
+ "lon": o_lon,
894
+ },
895
+ "destination": {
896
+ "query": destination_query,
897
+ "display_name": d_name,
898
+ "lat": d_lat,
899
+ "lon": d_lon,
900
+ },
901
+ "mode": mode,
902
+ "total_distance": _format_distance(distance_m),
903
+ "total_distance_m": round(distance_m, 1),
904
+ "total_duration": _format_duration(duration_s),
905
+ "total_duration_s": round(duration_s, 1),
906
+ "steps": steps,
907
+ "step_count": len(steps),
908
+ "data_source": DATA_SOURCE,
909
+ })
910
+
911
+
912
+ # ---------------------------------------------------------------------------
913
+ # Command: timezone
914
+ # ---------------------------------------------------------------------------
915
+
916
+ def cmd_timezone(args):
917
+ """
918
+ Get timezone information for a lat/lon coordinate.
919
+
920
+ Strategy:
921
+ 1. Try TimeAPI.io (free, no key, supports coordinate-based lookup).
922
+ 2. Fallback: derive UTC offset approximation from longitude.
923
+ """
924
+ try:
925
+ lat = float(args.lat)
926
+ lon = float(args.lon)
927
+ except ValueError:
928
+ error_exit("LAT and LON must be numeric values.")
929
+
930
+ if not (-90 <= lat <= 90):
931
+ error_exit("Latitude must be between -90 and 90.")
932
+ if not (-180 <= lon <= 180):
933
+ error_exit("Longitude must be between -180 and 180.")
934
+
935
+ timezone_str = None
936
+ timezone_src = None
937
+ current_time = None
938
+ utc_offset = None
939
+
940
+ # --- Strategy 1: TimeAPI.io coordinate lookup ---
941
+ try:
942
+ params = {"latitude": lat, "longitude": lon}
943
+ tz_data = http_get(TIMEAPI_BASE, params=params, silent=True)
944
+ if isinstance(tz_data, dict):
945
+ timezone_str = tz_data.get("timeZone")
946
+ current_time = tz_data.get("currentLocalTime")
947
+ # Build utc_offset from currentUtcOffset if available
948
+ offset_info = tz_data.get("currentUtcOffset", {})
949
+ if isinstance(offset_info, dict):
950
+ oh = offset_info.get("hours", 0)
951
+ om = abs(offset_info.get("minutes", 0))
952
+ os_ = offset_info.get("seconds", 0)
953
+ sign = "+" if oh >= 0 else "-"
954
+ utc_offset = f"{sign}{abs(oh):02d}:{om:02d}"
955
+ if os_:
956
+ utc_offset = f"{utc_offset}:{os_:02d}"
957
+ elif tz_data.get("standardUtcOffset"):
958
+ offset_info2 = tz_data["standardUtcOffset"]
959
+ if isinstance(offset_info2, dict):
960
+ oh = offset_info2.get("hours", 0)
961
+ om = abs(offset_info2.get("minutes", 0))
962
+ os_ = offset_info2.get("seconds", 0)
963
+ sign = "+" if oh >= 0 else "-"
964
+ utc_offset = f"{sign}{abs(oh):02d}:{om:02d}"
965
+ if os_:
966
+ utc_offset = f"{utc_offset}:{os_:02d}"
967
+ timezone_src = "timeapi.io"
968
+ except (RuntimeError, KeyError, TypeError):
969
+ pass # API may be down; continue to fallback
970
+
971
+ # --- Strategy 2: longitude-based UTC offset approximation ---
972
+ if not timezone_str:
973
+ approx_offset_h = round(lon / 15)
974
+ if approx_offset_h >= 0:
975
+ utc_offset = f"+{approx_offset_h:02d}:00"
976
+ else:
977
+ utc_offset = f"-{abs(approx_offset_h):02d}:00"
978
+ timezone_str = f"UTC{utc_offset}"
979
+ timezone_src = "longitude approximation (longitude/15)"
980
+
981
+ print_json({
982
+ "lat": lat,
983
+ "lon": lon,
984
+ "timezone": timezone_str,
985
+ "utc_offset": utc_offset,
986
+ "current_time": current_time,
987
+ "source": timezone_src,
988
+ "data_source": DATA_SOURCE,
989
+ })
990
+
991
+
992
+ # ---------------------------------------------------------------------------
993
+ # Command: bbox
994
+ # ---------------------------------------------------------------------------
995
+
996
+ def cmd_bbox(args):
997
+ """Find POIs within a bounding box using the Overpass API."""
998
+ try:
999
+ lat1 = float(args.lat1)
1000
+ lon1 = float(args.lon1)
1001
+ lat2 = float(args.lat2)
1002
+ lon2 = float(args.lon2)
1003
+ except ValueError:
1004
+ error_exit("All coordinate arguments must be numeric values.")
1005
+
1006
+ # Normalize: south/west < north/east
1007
+ south = min(lat1, lat2)
1008
+ north = max(lat1, lat2)
1009
+ west = min(lon1, lon2)
1010
+ east = max(lon1, lon2)
1011
+
1012
+ category = args.category.lower()
1013
+ if category not in CATEGORY_TAGS:
1014
+ error_exit(
1015
+ f"Unknown category '{category}'. "
1016
+ f"Valid categories: {', '.join(VALID_CATEGORIES)}"
1017
+ )
1018
+
1019
+ limit = int(args.limit)
1020
+ if limit <= 0:
1021
+ error_exit("Limit must be a positive integer.")
1022
+
1023
+ tag_pairs = _tags_for(category)
1024
+ religion = RELIGION_FILTER.get(category)
1025
+ query = build_overpass_bbox(None, None, south, west, north, east,
1026
+ limit, religion=religion, tag_pairs=tag_pairs)
1027
+
1028
+ raw = overpass_query(query)
1029
+
1030
+ elements = raw.get("elements", [])
1031
+
1032
+ # Use center of bbox as reference for distance sorting
1033
+ center_lat = (south + north) / 2
1034
+ center_lon = (west + east) / 2
1035
+ places = parse_overpass_elements(elements, ref_lat=center_lat,
1036
+ ref_lon=center_lon)
1037
+
1038
+ for p in places:
1039
+ p["category"] = category
1040
+
1041
+ print_json({
1042
+ "bounding_box": {
1043
+ "south": south,
1044
+ "west": west,
1045
+ "north": north,
1046
+ "east": east,
1047
+ },
1048
+ "category": category,
1049
+ "count": len(places),
1050
+ "results": places,
1051
+ "data_source": DATA_SOURCE,
1052
+ })
1053
+
1054
+
1055
+ # ---------------------------------------------------------------------------
1056
+ # Command: area
1057
+ # ---------------------------------------------------------------------------
1058
+
1059
+ def cmd_area(args):
1060
+ """Get bounding box and area info for a named place."""
1061
+ query = " ".join(args.place)
1062
+ raw = nominatim_search(query, limit=1)
1063
+
1064
+ if not raw:
1065
+ error_exit(f"Could not find place: {query}")
1066
+
1067
+ item = raw[0]
1068
+ bb = item.get("boundingbox", [])
1069
+
1070
+ if len(bb) < 4:
1071
+ error_exit(f"No bounding box data available for: {query}")
1072
+
1073
+ min_lat = float(bb[0])
1074
+ max_lat = float(bb[1])
1075
+ min_lon = float(bb[2])
1076
+ max_lon = float(bb[3])
1077
+
1078
+ # Approximate area in km² using the bounding box
1079
+ # Width in km at the average latitude
1080
+ avg_lat = (min_lat + max_lat) / 2
1081
+ height_km = haversine_m(min_lat, min_lon, max_lat, min_lon) / 1000
1082
+ width_km = haversine_m(avg_lat, min_lon, avg_lat, max_lon) / 1000
1083
+ approx_area_km2 = round(height_km * width_km, 3)
1084
+
1085
+ print_json({
1086
+ "query": query,
1087
+ "display_name": item.get("display_name", ""),
1088
+ "lat": float(item["lat"]),
1089
+ "lon": float(item["lon"]),
1090
+ "type": item.get("type", ""),
1091
+ "category": item.get("category", ""),
1092
+ "bounding_box": {
1093
+ "south": min_lat,
1094
+ "north": max_lat,
1095
+ "west": min_lon,
1096
+ "east": max_lon,
1097
+ },
1098
+ "dimensions": {
1099
+ "width_km": round(width_km, 3),
1100
+ "height_km": round(height_km, 3),
1101
+ },
1102
+ "approx_area_km2": approx_area_km2,
1103
+ "osm_type": item.get("osm_type", ""),
1104
+ "osm_id": item.get("osm_id", ""),
1105
+ "data_source": DATA_SOURCE,
1106
+ })
1107
+
1108
+
1109
+ # ---------------------------------------------------------------------------
1110
+ # CLI setup
1111
+ # ---------------------------------------------------------------------------
1112
+
1113
+ def build_parser():
1114
+ parser = argparse.ArgumentParser(
1115
+ prog="maps_client.py",
1116
+ description=(
1117
+ "CLI maps tool: geocoding, reverse geocoding, POI search, "
1118
+ "routing, directions, timezone, and area lookup. "
1119
+ "Powered by OpenStreetMap, OSRM, Overpass, and TimeAPI.io. "
1120
+ "No API keys required."
1121
+ ),
1122
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1123
+ epilog=(
1124
+ "Examples:\n"
1125
+ " maps_client.py search Times Square\n"
1126
+ " maps_client.py reverse 40.758 -73.985\n"
1127
+ " maps_client.py nearby 40.758 -73.985 restaurant --radius 800\n"
1128
+ " maps_client.py distance New York --to Los Angeles --mode driving\n"
1129
+ " maps_client.py directions Paris --to Berlin --mode driving\n"
1130
+ " maps_client.py timezone 48.8566 2.3522\n"
1131
+ " maps_client.py bbox 40.70 -74.02 40.78 -73.95 restaurant\n"
1132
+ " maps_client.py area Manhattan"
1133
+ ),
1134
+ )
1135
+ sub = parser.add_subparsers(dest="command", required=True,
1136
+ metavar="COMMAND")
1137
+
1138
+ # -- search --
1139
+ p_search = sub.add_parser(
1140
+ "search",
1141
+ help="Geocode a place name to coordinates.",
1142
+ description="Search for a place by name and return coordinates and details.",
1143
+ )
1144
+ p_search.add_argument(
1145
+ "query", nargs="+",
1146
+ help="Place name or address to search.",
1147
+ )
1148
+
1149
+ # -- reverse --
1150
+ p_reverse = sub.add_parser(
1151
+ "reverse",
1152
+ help="Reverse geocode coordinates to an address.",
1153
+ description="Convert latitude/longitude coordinates to a human-readable address.",
1154
+ )
1155
+ p_reverse.add_argument("lat", help="Latitude (decimal degrees).")
1156
+ p_reverse.add_argument("lon", help="Longitude (decimal degrees).")
1157
+
1158
+ # -- nearby --
1159
+ p_nearby = sub.add_parser(
1160
+ "nearby",
1161
+ help="Find nearby places of a given category.",
1162
+ description=(
1163
+ "Find points of interest near a location using the Overpass API.\n"
1164
+ "Provide either LAT/LON, or use --near \"<address>\" to auto-geocode.\n"
1165
+ "Categories can be specified positionally OR repeated via --category\n"
1166
+ "to merge multiple types in one query (e.g. --category bar --category cafe).\n"
1167
+ f"Categories: {', '.join(VALID_CATEGORIES)}"
1168
+ ),
1169
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1170
+ )
1171
+ p_nearby.add_argument(
1172
+ "lat", nargs="?", default=None,
1173
+ help="Center latitude (decimal degrees). Omit if using --near.",
1174
+ )
1175
+ p_nearby.add_argument(
1176
+ "lon", nargs="?", default=None,
1177
+ help="Center longitude (decimal degrees). Omit if using --near.",
1178
+ )
1179
+ p_nearby.add_argument(
1180
+ "category", nargs="?", default=None,
1181
+ help="POI category (use --help for full list). Omit if using --category flags.",
1182
+ )
1183
+ p_nearby.add_argument(
1184
+ "--near", nargs="+", metavar="PLACE",
1185
+ help="Address, city, or landmark to search around (geocoded via Nominatim).",
1186
+ )
1187
+ p_nearby.add_argument(
1188
+ "--category", action="append", dest="category_list", default=[],
1189
+ metavar="CAT",
1190
+ help="POI category (repeatable — adds a type to the search).",
1191
+ )
1192
+ p_nearby.add_argument(
1193
+ "--radius", "-r",
1194
+ default=500, type=int, metavar="METRES",
1195
+ help="Search radius in metres (default: 500).",
1196
+ )
1197
+ p_nearby.add_argument(
1198
+ "--limit", "-n",
1199
+ default=10, type=int, metavar="N",
1200
+ help="Maximum number of results (default: 10).",
1201
+ )
1202
+
1203
+ # -- distance --
1204
+ p_dist = sub.add_parser(
1205
+ "distance",
1206
+ help="Calculate road distance and travel time.",
1207
+ description=(
1208
+ "Calculate road distance and estimated travel time between two places.\n"
1209
+ "Example: maps_client.py distance New York --to Los Angeles"
1210
+ ),
1211
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1212
+ )
1213
+ p_dist.add_argument(
1214
+ "origin", nargs="+",
1215
+ help="Origin address or place name.",
1216
+ )
1217
+ p_dist.add_argument(
1218
+ "--to", nargs="+", required=True, metavar="DEST",
1219
+ help="Destination address or place name (required).",
1220
+ )
1221
+ p_dist.add_argument(
1222
+ "--mode", "-m",
1223
+ default="driving",
1224
+ choices=list(OSRM_PROFILES.keys()),
1225
+ help="Travel mode (default: driving).",
1226
+ )
1227
+
1228
+ # -- directions --
1229
+ p_dir = sub.add_parser(
1230
+ "directions",
1231
+ help="Get turn-by-turn directions between two places.",
1232
+ description=(
1233
+ "Get step-by-step navigation directions between two places.\n"
1234
+ "Example: maps_client.py directions Paris --to Berlin --mode driving"
1235
+ ),
1236
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1237
+ )
1238
+ p_dir.add_argument(
1239
+ "origin", nargs="+",
1240
+ help="Origin address or place name.",
1241
+ )
1242
+ p_dir.add_argument(
1243
+ "--to", nargs="+", required=True, metavar="DEST",
1244
+ help="Destination address or place name (required).",
1245
+ )
1246
+ p_dir.add_argument(
1247
+ "--mode", "-m",
1248
+ default="driving",
1249
+ choices=list(OSRM_PROFILES.keys()),
1250
+ help="Travel mode (default: driving).",
1251
+ )
1252
+
1253
+ # -- timezone --
1254
+ p_tz = sub.add_parser(
1255
+ "timezone",
1256
+ help="Get timezone information for coordinates.",
1257
+ description="Look up timezone and current local time for a lat/lon coordinate.",
1258
+ )
1259
+ p_tz.add_argument("lat", help="Latitude (decimal degrees).")
1260
+ p_tz.add_argument("lon", help="Longitude (decimal degrees).")
1261
+
1262
+ # -- bbox --
1263
+ p_bbox = sub.add_parser(
1264
+ "bbox",
1265
+ help="Find POIs within a bounding box.",
1266
+ description=(
1267
+ "Search for points of interest within a geographic bounding box.\n"
1268
+ "Tip: use the 'area' command to find bounding boxes for named places.\n"
1269
+ f"Categories: {', '.join(VALID_CATEGORIES)}"
1270
+ ),
1271
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1272
+ )
1273
+ p_bbox.add_argument("lat1", help="First corner latitude.")
1274
+ p_bbox.add_argument("lon1", help="First corner longitude.")
1275
+ p_bbox.add_argument("lat2", help="Second corner latitude.")
1276
+ p_bbox.add_argument("lon2", help="Second corner longitude.")
1277
+ p_bbox.add_argument("category", help="POI category to search for.")
1278
+ p_bbox.add_argument(
1279
+ "--limit", "-n",
1280
+ default=20, type=int, metavar="N",
1281
+ help="Maximum number of results (default: 20).",
1282
+ )
1283
+
1284
+ # -- area --
1285
+ p_area = sub.add_parser(
1286
+ "area",
1287
+ help="Get bounding box and area info for a named place.",
1288
+ description=(
1289
+ "Look up a place by name and return its bounding box, dimensions, "
1290
+ "and approximate area. Useful as input to the 'bbox' command."
1291
+ ),
1292
+ )
1293
+ p_area.add_argument(
1294
+ "place", nargs="+",
1295
+ help="Place name to look up (e.g., 'Manhattan' or 'downtown Seattle').",
1296
+ )
1297
+
1298
+ return parser
1299
+
1300
+
1301
+ def main():
1302
+ parser = build_parser()
1303
+ args = parser.parse_args()
1304
+
1305
+ dispatch = {
1306
+ "search": cmd_search,
1307
+ "reverse": cmd_reverse,
1308
+ "nearby": cmd_nearby,
1309
+ "distance": cmd_distance,
1310
+ "directions": cmd_directions,
1311
+ "timezone": cmd_timezone,
1312
+ "bbox": cmd_bbox,
1313
+ "area": cmd_area,
1314
+ }
1315
+
1316
+ handler = dispatch.get(args.command)
1317
+ if handler is None:
1318
+ error_exit(f"Unknown command: {args.command}")
1319
+
1320
+ handler(args)
1321
+
1322
+
1323
+ if __name__ == "__main__":
1324
+ main()