bps-kit 1.0.1 → 1.0.2

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 (368) hide show
  1. package/package.json +1 -1
  2. package/templates/.agents/agents/backend-specialist.md +263 -0
  3. package/templates/.agents/agents/code-archaeologist.md +106 -0
  4. package/templates/.agents/agents/database-architect.md +226 -0
  5. package/templates/.agents/agents/debugger.md +225 -0
  6. package/templates/.agents/agents/devops-engineer.md +242 -0
  7. package/templates/.agents/agents/documentation-writer.md +104 -0
  8. package/templates/.agents/agents/explorer-agent.md +73 -0
  9. package/templates/.agents/agents/frontend-specialist.md +593 -0
  10. package/templates/.agents/agents/game-developer.md +162 -0
  11. package/templates/.agents/agents/mobile-developer.md +377 -0
  12. package/templates/.agents/agents/orchestrator.md +416 -0
  13. package/templates/.agents/agents/penetration-tester.md +188 -0
  14. package/templates/.agents/agents/performance-optimizer.md +187 -0
  15. package/templates/.agents/agents/product-manager.md +112 -0
  16. package/templates/.agents/agents/product-owner.md +95 -0
  17. package/templates/.agents/agents/project-planner.md +406 -0
  18. package/templates/.agents/agents/qa-automation-engineer.md +103 -0
  19. package/templates/.agents/agents/security-auditor.md +170 -0
  20. package/templates/.agents/agents/seo-specialist.md +111 -0
  21. package/templates/.agents/agents/test-engineer.md +158 -0
  22. package/templates/.agents/rules/GEMINI.md +219 -0
  23. package/templates/.agents/scripts/auto_preview.py +148 -0
  24. package/templates/.agents/scripts/checklist.py +217 -0
  25. package/templates/.agents/scripts/session_manager.py +120 -0
  26. package/templates/.agents/scripts/verify_all.py +327 -0
  27. package/templates/.agents/workflows/brainstorm.md +113 -0
  28. package/templates/.agents/workflows/create.md +59 -0
  29. package/templates/.agents/workflows/debug.md +103 -0
  30. package/templates/.agents/workflows/deploy.md +176 -0
  31. package/templates/.agents/workflows/enhance.md +63 -0
  32. package/templates/.agents/workflows/orchestrate.md +237 -0
  33. package/templates/.agents/workflows/plan.md +89 -0
  34. package/templates/.agents/workflows/preview.md +81 -0
  35. package/templates/.agents/workflows/setup-brain.md +39 -0
  36. package/templates/.agents/workflows/status.md +86 -0
  37. package/templates/.agents/workflows/test.md +144 -0
  38. package/templates/.agents/workflows/ui-ux-pro-max.md +296 -0
  39. package/templates/skills_normal/api-patterns/scripts/api_validator.py +211 -0
  40. package/templates/skills_normal/database-design/scripts/schema_validator.py +172 -0
  41. package/templates/skills_normal/frontend-design/scripts/accessibility_checker.py +183 -0
  42. package/templates/skills_normal/frontend-design/scripts/ux_audit.py +722 -0
  43. package/templates/skills_normal/git-pushing/scripts/smart_commit.sh +19 -0
  44. package/templates/skills_normal/lint-and-validate/scripts/lint_runner.py +184 -0
  45. package/templates/skills_normal/lint-and-validate/scripts/type_coverage.py +173 -0
  46. package/templates/skills_normal/performance-profiling/scripts/lighthouse_audit.py +76 -0
  47. package/templates/skills_normal/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
  48. package/templates/skills_normal/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
  49. package/templates/skills_normal/senior-fullstack/scripts/project_scaffolder.py +114 -0
  50. package/templates/skills_normal/seo-fundamentals/scripts/seo_checker.py +219 -0
  51. package/templates/skills_normal/testing-patterns/scripts/test_runner.py +219 -0
  52. package/templates/skills_normal/vulnerability-scanner/scripts/security_scan.py +458 -0
  53. package/templates/vault/007/scripts/config.py +472 -0
  54. package/templates/vault/007/scripts/full_audit.py +1306 -0
  55. package/templates/vault/007/scripts/quick_scan.py +481 -0
  56. package/templates/vault/007/scripts/requirements.txt +26 -0
  57. package/templates/vault/007/scripts/scanners/__init__.py +0 -0
  58. package/templates/vault/007/scripts/scanners/dependency_scanner.py +1305 -0
  59. package/templates/vault/007/scripts/scanners/injection_scanner.py +1104 -0
  60. package/templates/vault/007/scripts/scanners/secrets_scanner.py +1008 -0
  61. package/templates/vault/007/scripts/score_calculator.py +693 -0
  62. package/templates/vault/agent-orchestrator/scripts/match_skills.py +329 -0
  63. package/templates/vault/agent-orchestrator/scripts/orchestrate.py +304 -0
  64. package/templates/vault/agent-orchestrator/scripts/requirements.txt +1 -0
  65. package/templates/vault/agent-orchestrator/scripts/scan_registry.py +508 -0
  66. package/templates/vault/ai-studio-image/scripts/config.py +613 -0
  67. package/templates/vault/ai-studio-image/scripts/generate.py +630 -0
  68. package/templates/vault/ai-studio-image/scripts/prompt_engine.py +424 -0
  69. package/templates/vault/ai-studio-image/scripts/requirements.txt +4 -0
  70. package/templates/vault/ai-studio-image/scripts/templates.py +349 -0
  71. package/templates/vault/android_ui_verification/scripts/verify_ui.sh +32 -0
  72. package/templates/vault/apify-audience-analysis/reference/scripts/run_actor.js +363 -0
  73. package/templates/vault/apify-brand-reputation-monitoring/reference/scripts/run_actor.js +363 -0
  74. package/templates/vault/apify-competitor-intelligence/reference/scripts/run_actor.js +363 -0
  75. package/templates/vault/apify-content-analytics/reference/scripts/run_actor.js +363 -0
  76. package/templates/vault/apify-ecommerce/reference/scripts/package.json +3 -0
  77. package/templates/vault/apify-ecommerce/reference/scripts/run_actor.js +369 -0
  78. package/templates/vault/apify-influencer-discovery/reference/scripts/run_actor.js +363 -0
  79. package/templates/vault/apify-lead-generation/reference/scripts/run_actor.js +363 -0
  80. package/templates/vault/apify-market-research/reference/scripts/run_actor.js +363 -0
  81. package/templates/vault/apify-trend-analysis/reference/scripts/run_actor.js +363 -0
  82. package/templates/vault/apify-ultimate-scraper/reference/scripts/run_actor.js +363 -0
  83. package/templates/vault/audio-transcriber/scripts/install-requirements.sh +190 -0
  84. package/templates/vault/audio-transcriber/scripts/transcribe.py +486 -0
  85. package/templates/vault/claude-monitor/scripts/api_bench.py +240 -0
  86. package/templates/vault/claude-monitor/scripts/config.py +69 -0
  87. package/templates/vault/claude-monitor/scripts/health_check.py +362 -0
  88. package/templates/vault/claude-monitor/scripts/monitor.py +296 -0
  89. package/templates/vault/content-creator/scripts/brand_voice_analyzer.py +185 -0
  90. package/templates/vault/content-creator/scripts/seo_optimizer.py +419 -0
  91. package/templates/vault/context-agent/scripts/active_context.py +227 -0
  92. package/templates/vault/context-agent/scripts/compressor.py +149 -0
  93. package/templates/vault/context-agent/scripts/config.py +69 -0
  94. package/templates/vault/context-agent/scripts/context_loader.py +155 -0
  95. package/templates/vault/context-agent/scripts/context_manager.py +302 -0
  96. package/templates/vault/context-agent/scripts/models.py +103 -0
  97. package/templates/vault/context-agent/scripts/project_registry.py +132 -0
  98. package/templates/vault/context-agent/scripts/requirements.txt +6 -0
  99. package/templates/vault/context-agent/scripts/search.py +115 -0
  100. package/templates/vault/context-agent/scripts/session_parser.py +206 -0
  101. package/templates/vault/context-agent/scripts/session_summary.py +319 -0
  102. package/templates/vault/context-guardian/scripts/context_snapshot.py +229 -0
  103. package/templates/vault/docx/ooxml/scripts/pack.py +159 -0
  104. package/templates/vault/docx/ooxml/scripts/unpack.py +29 -0
  105. package/templates/vault/docx/ooxml/scripts/validate.py +69 -0
  106. package/templates/vault/docx/ooxml/scripts/validation/__init__.py +15 -0
  107. package/templates/vault/docx/ooxml/scripts/validation/base.py +951 -0
  108. package/templates/vault/docx/ooxml/scripts/validation/docx.py +274 -0
  109. package/templates/vault/docx/ooxml/scripts/validation/pptx.py +315 -0
  110. package/templates/vault/docx/ooxml/scripts/validation/redlining.py +279 -0
  111. package/templates/vault/docx/scripts/__init__.py +1 -0
  112. package/templates/vault/docx/scripts/document.py +1276 -0
  113. package/templates/vault/docx/scripts/templates/comments.xml +3 -0
  114. package/templates/vault/docx/scripts/templates/commentsExtended.xml +3 -0
  115. package/templates/vault/docx/scripts/templates/commentsExtensible.xml +3 -0
  116. package/templates/vault/docx/scripts/templates/commentsIds.xml +3 -0
  117. package/templates/vault/docx/scripts/templates/people.xml +3 -0
  118. package/templates/vault/docx/scripts/utilities.py +374 -0
  119. package/templates/vault/docx-official/ooxml/scripts/pack.py +159 -0
  120. package/templates/vault/docx-official/ooxml/scripts/unpack.py +29 -0
  121. package/templates/vault/docx-official/ooxml/scripts/validate.py +69 -0
  122. package/templates/vault/docx-official/ooxml/scripts/validation/__init__.py +15 -0
  123. package/templates/vault/docx-official/ooxml/scripts/validation/base.py +951 -0
  124. package/templates/vault/docx-official/ooxml/scripts/validation/docx.py +274 -0
  125. package/templates/vault/docx-official/ooxml/scripts/validation/pptx.py +315 -0
  126. package/templates/vault/docx-official/ooxml/scripts/validation/redlining.py +279 -0
  127. package/templates/vault/docx-official/scripts/__init__.py +1 -0
  128. package/templates/vault/docx-official/scripts/document.py +1276 -0
  129. package/templates/vault/docx-official/scripts/templates/comments.xml +3 -0
  130. package/templates/vault/docx-official/scripts/templates/commentsExtended.xml +3 -0
  131. package/templates/vault/docx-official/scripts/templates/commentsExtensible.xml +3 -0
  132. package/templates/vault/docx-official/scripts/templates/commentsIds.xml +3 -0
  133. package/templates/vault/docx-official/scripts/templates/people.xml +3 -0
  134. package/templates/vault/docx-official/scripts/utilities.py +374 -0
  135. package/templates/vault/geo-fundamentals/scripts/geo_checker.py +289 -0
  136. package/templates/vault/helm-chart-scaffolding/scripts/validate-chart.sh +244 -0
  137. package/templates/vault/i18n-localization/scripts/i18n_checker.py +241 -0
  138. package/templates/vault/instagram/scripts/account_setup.py +233 -0
  139. package/templates/vault/instagram/scripts/analyze.py +221 -0
  140. package/templates/vault/instagram/scripts/api_client.py +444 -0
  141. package/templates/vault/instagram/scripts/auth.py +411 -0
  142. package/templates/vault/instagram/scripts/comments.py +160 -0
  143. package/templates/vault/instagram/scripts/config.py +111 -0
  144. package/templates/vault/instagram/scripts/db.py +467 -0
  145. package/templates/vault/instagram/scripts/export.py +138 -0
  146. package/templates/vault/instagram/scripts/governance.py +233 -0
  147. package/templates/vault/instagram/scripts/hashtags.py +114 -0
  148. package/templates/vault/instagram/scripts/insights.py +170 -0
  149. package/templates/vault/instagram/scripts/media.py +65 -0
  150. package/templates/vault/instagram/scripts/messages.py +103 -0
  151. package/templates/vault/instagram/scripts/profile.py +58 -0
  152. package/templates/vault/instagram/scripts/publish.py +449 -0
  153. package/templates/vault/instagram/scripts/requirements.txt +5 -0
  154. package/templates/vault/instagram/scripts/run_all.py +189 -0
  155. package/templates/vault/instagram/scripts/schedule.py +189 -0
  156. package/templates/vault/instagram/scripts/serve_api.py +234 -0
  157. package/templates/vault/instagram/scripts/templates.py +155 -0
  158. package/templates/vault/junta-leiloeiros/scripts/db.py +216 -0
  159. package/templates/vault/junta-leiloeiros/scripts/export.py +137 -0
  160. package/templates/vault/junta-leiloeiros/scripts/requirements.txt +15 -0
  161. package/templates/vault/junta-leiloeiros/scripts/run_all.py +190 -0
  162. package/templates/vault/junta-leiloeiros/scripts/scraper/__init__.py +4 -0
  163. package/templates/vault/junta-leiloeiros/scripts/scraper/base_scraper.py +209 -0
  164. package/templates/vault/junta-leiloeiros/scripts/scraper/generic_scraper.py +110 -0
  165. package/templates/vault/junta-leiloeiros/scripts/scraper/jucap.py +110 -0
  166. package/templates/vault/junta-leiloeiros/scripts/scraper/juceac.py +72 -0
  167. package/templates/vault/junta-leiloeiros/scripts/scraper/juceal.py +72 -0
  168. package/templates/vault/junta-leiloeiros/scripts/scraper/juceb.py +68 -0
  169. package/templates/vault/junta-leiloeiros/scripts/scraper/jucec.py +63 -0
  170. package/templates/vault/junta-leiloeiros/scripts/scraper/jucema.py +211 -0
  171. package/templates/vault/junta-leiloeiros/scripts/scraper/jucemg.py +218 -0
  172. package/templates/vault/junta-leiloeiros/scripts/scraper/jucep.py +70 -0
  173. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepa.py +74 -0
  174. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepar.py +80 -0
  175. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepe.py +78 -0
  176. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepi.py +69 -0
  177. package/templates/vault/junta-leiloeiros/scripts/scraper/jucer.py +256 -0
  178. package/templates/vault/junta-leiloeiros/scripts/scraper/jucerja.py +170 -0
  179. package/templates/vault/junta-leiloeiros/scripts/scraper/jucern.py +71 -0
  180. package/templates/vault/junta-leiloeiros/scripts/scraper/jucesc.py +89 -0
  181. package/templates/vault/junta-leiloeiros/scripts/scraper/jucesp.py +233 -0
  182. package/templates/vault/junta-leiloeiros/scripts/scraper/jucetins.py +134 -0
  183. package/templates/vault/junta-leiloeiros/scripts/scraper/jucis_df.py +63 -0
  184. package/templates/vault/junta-leiloeiros/scripts/scraper/jucisrs.py +299 -0
  185. package/templates/vault/junta-leiloeiros/scripts/scraper/states.py +99 -0
  186. package/templates/vault/junta-leiloeiros/scripts/serve_api.py +164 -0
  187. package/templates/vault/junta-leiloeiros/scripts/web_scraper_fallback.py +233 -0
  188. package/templates/vault/last30days/scripts/last30days.py +521 -0
  189. package/templates/vault/last30days/scripts/lib/__init__.py +1 -0
  190. package/templates/vault/last30days/scripts/lib/cache.py +152 -0
  191. package/templates/vault/last30days/scripts/lib/dates.py +124 -0
  192. package/templates/vault/last30days/scripts/lib/dedupe.py +120 -0
  193. package/templates/vault/last30days/scripts/lib/env.py +149 -0
  194. package/templates/vault/last30days/scripts/lib/http.py +152 -0
  195. package/templates/vault/last30days/scripts/lib/models.py +175 -0
  196. package/templates/vault/last30days/scripts/lib/normalize.py +160 -0
  197. package/templates/vault/last30days/scripts/lib/openai_reddit.py +230 -0
  198. package/templates/vault/last30days/scripts/lib/reddit_enrich.py +232 -0
  199. package/templates/vault/last30days/scripts/lib/render.py +383 -0
  200. package/templates/vault/last30days/scripts/lib/schema.py +336 -0
  201. package/templates/vault/last30days/scripts/lib/score.py +311 -0
  202. package/templates/vault/last30days/scripts/lib/ui.py +324 -0
  203. package/templates/vault/last30days/scripts/lib/websearch.py +401 -0
  204. package/templates/vault/last30days/scripts/lib/xai_x.py +217 -0
  205. package/templates/vault/leiloeiro-avaliacao/scripts/governance.py +106 -0
  206. package/templates/vault/leiloeiro-avaliacao/scripts/requirements.txt +1 -0
  207. package/templates/vault/leiloeiro-edital/scripts/governance.py +106 -0
  208. package/templates/vault/leiloeiro-edital/scripts/requirements.txt +1 -0
  209. package/templates/vault/leiloeiro-ia/scripts/governance.py +106 -0
  210. package/templates/vault/leiloeiro-ia/scripts/requirements.txt +1 -0
  211. package/templates/vault/leiloeiro-juridico/scripts/governance.py +106 -0
  212. package/templates/vault/leiloeiro-juridico/scripts/requirements.txt +1 -0
  213. package/templates/vault/leiloeiro-mercado/scripts/governance.py +106 -0
  214. package/templates/vault/leiloeiro-mercado/scripts/requirements.txt +1 -0
  215. package/templates/vault/leiloeiro-risco/scripts/governance.py +106 -0
  216. package/templates/vault/leiloeiro-risco/scripts/requirements.txt +1 -0
  217. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/database.ts +24 -0
  218. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/db.ts +35 -0
  219. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/index.ts +2 -0
  220. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/migrations.ts +31 -0
  221. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/schema.sql +8 -0
  222. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/index.ts +44 -0
  223. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts +155 -0
  224. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/types/index.ts +35 -0
  225. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.css +384 -0
  226. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.tsx +81 -0
  227. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/api/todos.ts +57 -0
  228. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/ConfirmDialog.tsx +26 -0
  229. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/EmptyState.tsx +8 -0
  230. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoForm.tsx +43 -0
  231. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoItem.tsx +36 -0
  232. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoList.tsx +27 -0
  233. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/hooks/useTodos.ts +81 -0
  234. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/index.css +48 -0
  235. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/main.tsx +10 -0
  236. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/vite-env.d.ts +1 -0
  237. package/templates/vault/loki-mode/scripts/export-to-vibe-kanban.sh +178 -0
  238. package/templates/vault/loki-mode/scripts/loki-wrapper.sh +281 -0
  239. package/templates/vault/loki-mode/scripts/take-screenshots.js +55 -0
  240. package/templates/vault/matematico-tao/scripts/complexity_analyzer.py +544 -0
  241. package/templates/vault/matematico-tao/scripts/dependency_graph.py +538 -0
  242. package/templates/vault/mcp-builder/scripts/connections.py +151 -0
  243. package/templates/vault/mcp-builder/scripts/evaluation.py +373 -0
  244. package/templates/vault/mcp-builder/scripts/example_evaluation.xml +22 -0
  245. package/templates/vault/mcp-builder/scripts/requirements.txt +2 -0
  246. package/templates/vault/mobile-design/scripts/mobile_audit.py +670 -0
  247. package/templates/vault/notebooklm/scripts/__init__.py +81 -0
  248. package/templates/vault/notebooklm/scripts/ask_question.py +256 -0
  249. package/templates/vault/notebooklm/scripts/auth_manager.py +358 -0
  250. package/templates/vault/notebooklm/scripts/browser_session.py +255 -0
  251. package/templates/vault/notebooklm/scripts/browser_utils.py +107 -0
  252. package/templates/vault/notebooklm/scripts/cleanup_manager.py +302 -0
  253. package/templates/vault/notebooklm/scripts/config.py +44 -0
  254. package/templates/vault/notebooklm/scripts/notebook_manager.py +410 -0
  255. package/templates/vault/notebooklm/scripts/run.py +102 -0
  256. package/templates/vault/notebooklm/scripts/setup_environment.py +204 -0
  257. package/templates/vault/pdf/scripts/check_bounding_boxes.py +70 -0
  258. package/templates/vault/pdf/scripts/check_bounding_boxes_test.py +226 -0
  259. package/templates/vault/pdf/scripts/check_fillable_fields.py +12 -0
  260. package/templates/vault/pdf/scripts/convert_pdf_to_images.py +35 -0
  261. package/templates/vault/pdf/scripts/create_validation_image.py +41 -0
  262. package/templates/vault/pdf/scripts/extract_form_field_info.py +152 -0
  263. package/templates/vault/pdf/scripts/fill_fillable_fields.py +114 -0
  264. package/templates/vault/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  265. package/templates/vault/pdf-official/scripts/check_bounding_boxes.py +70 -0
  266. package/templates/vault/pdf-official/scripts/check_bounding_boxes_test.py +226 -0
  267. package/templates/vault/pdf-official/scripts/check_fillable_fields.py +12 -0
  268. package/templates/vault/pdf-official/scripts/convert_pdf_to_images.py +35 -0
  269. package/templates/vault/pdf-official/scripts/create_validation_image.py +41 -0
  270. package/templates/vault/pdf-official/scripts/extract_form_field_info.py +152 -0
  271. package/templates/vault/pdf-official/scripts/fill_fillable_fields.py +114 -0
  272. package/templates/vault/pdf-official/scripts/fill_pdf_form_with_annotations.py +108 -0
  273. package/templates/vault/planning-with-files/scripts/check-complete.sh +44 -0
  274. package/templates/vault/planning-with-files/scripts/init-session.sh +120 -0
  275. package/templates/vault/pptx/ooxml/scripts/pack.py +159 -0
  276. package/templates/vault/pptx/ooxml/scripts/unpack.py +29 -0
  277. package/templates/vault/pptx/ooxml/scripts/validate.py +69 -0
  278. package/templates/vault/pptx/ooxml/scripts/validation/__init__.py +15 -0
  279. package/templates/vault/pptx/ooxml/scripts/validation/base.py +951 -0
  280. package/templates/vault/pptx/ooxml/scripts/validation/docx.py +274 -0
  281. package/templates/vault/pptx/ooxml/scripts/validation/pptx.py +315 -0
  282. package/templates/vault/pptx/ooxml/scripts/validation/redlining.py +279 -0
  283. package/templates/vault/pptx/scripts/html2pptx.js +979 -0
  284. package/templates/vault/pptx/scripts/inventory.py +1020 -0
  285. package/templates/vault/pptx/scripts/rearrange.py +231 -0
  286. package/templates/vault/pptx/scripts/replace.py +385 -0
  287. package/templates/vault/pptx/scripts/thumbnail.py +450 -0
  288. package/templates/vault/pptx-official/ooxml/scripts/pack.py +159 -0
  289. package/templates/vault/pptx-official/ooxml/scripts/unpack.py +29 -0
  290. package/templates/vault/pptx-official/ooxml/scripts/validate.py +69 -0
  291. package/templates/vault/pptx-official/ooxml/scripts/validation/__init__.py +15 -0
  292. package/templates/vault/pptx-official/ooxml/scripts/validation/base.py +951 -0
  293. package/templates/vault/pptx-official/ooxml/scripts/validation/docx.py +274 -0
  294. package/templates/vault/pptx-official/ooxml/scripts/validation/pptx.py +315 -0
  295. package/templates/vault/pptx-official/ooxml/scripts/validation/redlining.py +279 -0
  296. package/templates/vault/pptx-official/scripts/html2pptx.js +979 -0
  297. package/templates/vault/pptx-official/scripts/inventory.py +1020 -0
  298. package/templates/vault/pptx-official/scripts/rearrange.py +231 -0
  299. package/templates/vault/pptx-official/scripts/replace.py +385 -0
  300. package/templates/vault/pptx-official/scripts/thumbnail.py +450 -0
  301. package/templates/vault/product-manager-toolkit/scripts/customer_interview_analyzer.py +441 -0
  302. package/templates/vault/product-manager-toolkit/scripts/rice_prioritizer.py +296 -0
  303. package/templates/vault/prompt-engineering-patterns/scripts/optimize-prompt.py +279 -0
  304. package/templates/vault/scripts/.skill_cache.json +7538 -0
  305. package/templates/vault/scripts/skill_search.py +228 -0
  306. package/templates/vault/senior-architect/scripts/architecture_diagram_generator.py +114 -0
  307. package/templates/vault/senior-architect/scripts/dependency_analyzer.py +114 -0
  308. package/templates/vault/senior-architect/scripts/project_architect.py +114 -0
  309. package/templates/vault/shopify-development/scripts/requirements.txt +19 -0
  310. package/templates/vault/shopify-development/scripts/shopify_graphql.py +428 -0
  311. package/templates/vault/shopify-development/scripts/shopify_init.py +441 -0
  312. package/templates/vault/shopify-development/scripts/tests/test_shopify_init.py +379 -0
  313. package/templates/vault/skill-creator/scripts/init_skill.py +303 -0
  314. package/templates/vault/skill-creator/scripts/package_skill.py +110 -0
  315. package/templates/vault/skill-creator/scripts/quick_validate.py +95 -0
  316. package/templates/vault/skill-installer/scripts/detect_skills.py +318 -0
  317. package/templates/vault/skill-installer/scripts/install_skill.py +1708 -0
  318. package/templates/vault/skill-installer/scripts/package_skill.py +417 -0
  319. package/templates/vault/skill-installer/scripts/requirements.txt +1 -0
  320. package/templates/vault/skill-installer/scripts/validate_skill.py +430 -0
  321. package/templates/vault/skill-sentinel/scripts/analyzers/__init__.py +13 -0
  322. package/templates/vault/skill-sentinel/scripts/analyzers/code_quality.py +247 -0
  323. package/templates/vault/skill-sentinel/scripts/analyzers/cross_skill.py +134 -0
  324. package/templates/vault/skill-sentinel/scripts/analyzers/dependencies.py +121 -0
  325. package/templates/vault/skill-sentinel/scripts/analyzers/documentation.py +189 -0
  326. package/templates/vault/skill-sentinel/scripts/analyzers/governance_audit.py +153 -0
  327. package/templates/vault/skill-sentinel/scripts/analyzers/performance.py +164 -0
  328. package/templates/vault/skill-sentinel/scripts/analyzers/security.py +189 -0
  329. package/templates/vault/skill-sentinel/scripts/config.py +158 -0
  330. package/templates/vault/skill-sentinel/scripts/cost_optimizer.py +146 -0
  331. package/templates/vault/skill-sentinel/scripts/db.py +354 -0
  332. package/templates/vault/skill-sentinel/scripts/governance.py +58 -0
  333. package/templates/vault/skill-sentinel/scripts/recommender.py +228 -0
  334. package/templates/vault/skill-sentinel/scripts/report_generator.py +224 -0
  335. package/templates/vault/skill-sentinel/scripts/requirements.txt +1 -0
  336. package/templates/vault/skill-sentinel/scripts/run_audit.py +290 -0
  337. package/templates/vault/skill-sentinel/scripts/scanner.py +271 -0
  338. package/templates/vault/stability-ai/scripts/config.py +266 -0
  339. package/templates/vault/stability-ai/scripts/generate.py +687 -0
  340. package/templates/vault/stability-ai/scripts/requirements.txt +4 -0
  341. package/templates/vault/stability-ai/scripts/styles.py +174 -0
  342. package/templates/vault/telegram/assets/boilerplate/nodejs/src/bot-client.ts +86 -0
  343. package/templates/vault/telegram/assets/boilerplate/nodejs/src/handlers.ts +79 -0
  344. package/templates/vault/telegram/assets/boilerplate/nodejs/src/index.ts +32 -0
  345. package/templates/vault/telegram/scripts/send_message.py +143 -0
  346. package/templates/vault/telegram/scripts/setup_project.py +103 -0
  347. package/templates/vault/telegram/scripts/test_bot.py +144 -0
  348. package/templates/vault/typescript-expert/scripts/ts_diagnostic.py +203 -0
  349. package/templates/vault/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  350. package/templates/vault/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
  351. package/templates/vault/ui-ux-pro-max/scripts/core.py +257 -0
  352. package/templates/vault/ui-ux-pro-max/scripts/design_system.py +487 -0
  353. package/templates/vault/ui-ux-pro-max/scripts/search.py +76 -0
  354. package/templates/vault/videodb/scripts/ws_listener.py +204 -0
  355. package/templates/vault/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  356. package/templates/vault/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  357. package/templates/vault/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  358. package/templates/vault/webapp-testing/scripts/with_server.py +106 -0
  359. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/index.ts +125 -0
  360. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/template-manager.ts +67 -0
  361. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/types.ts +216 -0
  362. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts +173 -0
  363. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/whatsapp-client.ts +193 -0
  364. package/templates/vault/whatsapp-cloud-api/scripts/send_test_message.py +137 -0
  365. package/templates/vault/whatsapp-cloud-api/scripts/setup_project.py +118 -0
  366. package/templates/vault/whatsapp-cloud-api/scripts/validate_config.py +190 -0
  367. package/templates/vault/youtube-summarizer/scripts/extract-transcript.py +65 -0
  368. package/templates/vault/youtube-summarizer/scripts/install-dependencies.sh +28 -0
@@ -0,0 +1,232 @@
1
+ """Reddit thread enrichment with real engagement metrics."""
2
+
3
+ import re
4
+ from typing import Any, Dict, List, Optional
5
+ from urllib.parse import urlparse
6
+
7
+ from . import http, dates
8
+
9
+
10
+ def extract_reddit_path(url: str) -> Optional[str]:
11
+ """Extract the path from a Reddit URL.
12
+
13
+ Args:
14
+ url: Reddit URL
15
+
16
+ Returns:
17
+ Path component or None
18
+ """
19
+ try:
20
+ parsed = urlparse(url)
21
+ if "reddit.com" not in parsed.netloc:
22
+ return None
23
+ return parsed.path
24
+ except:
25
+ return None
26
+
27
+
28
+ def fetch_thread_data(url: str, mock_data: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
29
+ """Fetch Reddit thread JSON data.
30
+
31
+ Args:
32
+ url: Reddit thread URL
33
+ mock_data: Mock data for testing
34
+
35
+ Returns:
36
+ Thread data dict or None on failure
37
+ """
38
+ if mock_data is not None:
39
+ return mock_data
40
+
41
+ path = extract_reddit_path(url)
42
+ if not path:
43
+ return None
44
+
45
+ try:
46
+ data = http.get_reddit_json(path)
47
+ return data
48
+ except http.HTTPError:
49
+ return None
50
+
51
+
52
+ def parse_thread_data(data: Any) -> Dict[str, Any]:
53
+ """Parse Reddit thread JSON into structured data.
54
+
55
+ Args:
56
+ data: Raw Reddit JSON response
57
+
58
+ Returns:
59
+ Dict with submission and comments data
60
+ """
61
+ result = {
62
+ "submission": None,
63
+ "comments": [],
64
+ }
65
+
66
+ if not isinstance(data, list) or len(data) < 1:
67
+ return result
68
+
69
+ # First element is submission listing
70
+ submission_listing = data[0]
71
+ if isinstance(submission_listing, dict):
72
+ children = submission_listing.get("data", {}).get("children", [])
73
+ if children:
74
+ sub_data = children[0].get("data", {})
75
+ result["submission"] = {
76
+ "score": sub_data.get("score"),
77
+ "num_comments": sub_data.get("num_comments"),
78
+ "upvote_ratio": sub_data.get("upvote_ratio"),
79
+ "created_utc": sub_data.get("created_utc"),
80
+ "permalink": sub_data.get("permalink"),
81
+ "title": sub_data.get("title"),
82
+ "selftext": sub_data.get("selftext", "")[:500], # Truncate
83
+ }
84
+
85
+ # Second element is comments listing
86
+ if len(data) >= 2:
87
+ comments_listing = data[1]
88
+ if isinstance(comments_listing, dict):
89
+ children = comments_listing.get("data", {}).get("children", [])
90
+ for child in children:
91
+ if child.get("kind") != "t1": # t1 = comment
92
+ continue
93
+ c_data = child.get("data", {})
94
+ if not c_data.get("body"):
95
+ continue
96
+
97
+ comment = {
98
+ "score": c_data.get("score", 0),
99
+ "created_utc": c_data.get("created_utc"),
100
+ "author": c_data.get("author", "[deleted]"),
101
+ "body": c_data.get("body", "")[:300], # Truncate
102
+ "permalink": c_data.get("permalink"),
103
+ }
104
+ result["comments"].append(comment)
105
+
106
+ return result
107
+
108
+
109
+ def get_top_comments(comments: List[Dict], limit: int = 10) -> List[Dict[str, Any]]:
110
+ """Get top comments sorted by score.
111
+
112
+ Args:
113
+ comments: List of comment dicts
114
+ limit: Maximum number to return
115
+
116
+ Returns:
117
+ Top comments sorted by score
118
+ """
119
+ # Filter out deleted/removed
120
+ valid = [c for c in comments if c.get("author") not in ("[deleted]", "[removed]")]
121
+
122
+ # Sort by score descending
123
+ sorted_comments = sorted(valid, key=lambda c: c.get("score", 0), reverse=True)
124
+
125
+ return sorted_comments[:limit]
126
+
127
+
128
+ def extract_comment_insights(comments: List[Dict], limit: int = 7) -> List[str]:
129
+ """Extract key insights from top comments.
130
+
131
+ Uses simple heuristics to identify valuable comments:
132
+ - Has substantive text
133
+ - Contains actionable information
134
+ - Not just agreement/disagreement
135
+
136
+ Args:
137
+ comments: Top comments
138
+ limit: Max insights to extract
139
+
140
+ Returns:
141
+ List of insight strings
142
+ """
143
+ insights = []
144
+
145
+ for comment in comments[:limit * 2]: # Look at more comments than we need
146
+ body = comment.get("body", "").strip()
147
+ if not body or len(body) < 30:
148
+ continue
149
+
150
+ # Skip low-value patterns
151
+ skip_patterns = [
152
+ r'^(this|same|agreed|exactly|yep|nope|yes|no|thanks|thank you)\.?$',
153
+ r'^lol|lmao|haha',
154
+ r'^\[deleted\]',
155
+ r'^\[removed\]',
156
+ ]
157
+ if any(re.match(p, body.lower()) for p in skip_patterns):
158
+ continue
159
+
160
+ # Truncate to first meaningful sentence or ~150 chars
161
+ insight = body[:150]
162
+ if len(body) > 150:
163
+ # Try to find a sentence boundary
164
+ for i, char in enumerate(insight):
165
+ if char in '.!?' and i > 50:
166
+ insight = insight[:i+1]
167
+ break
168
+ else:
169
+ insight = insight.rstrip() + "..."
170
+
171
+ insights.append(insight)
172
+ if len(insights) >= limit:
173
+ break
174
+
175
+ return insights
176
+
177
+
178
+ def enrich_reddit_item(
179
+ item: Dict[str, Any],
180
+ mock_thread_data: Optional[Dict] = None,
181
+ ) -> Dict[str, Any]:
182
+ """Enrich a Reddit item with real engagement data.
183
+
184
+ Args:
185
+ item: Reddit item dict
186
+ mock_thread_data: Mock data for testing
187
+
188
+ Returns:
189
+ Enriched item dict
190
+ """
191
+ url = item.get("url", "")
192
+
193
+ # Fetch thread data
194
+ thread_data = fetch_thread_data(url, mock_thread_data)
195
+ if not thread_data:
196
+ return item
197
+
198
+ parsed = parse_thread_data(thread_data)
199
+ submission = parsed.get("submission")
200
+ comments = parsed.get("comments", [])
201
+
202
+ # Update engagement metrics
203
+ if submission:
204
+ item["engagement"] = {
205
+ "score": submission.get("score"),
206
+ "num_comments": submission.get("num_comments"),
207
+ "upvote_ratio": submission.get("upvote_ratio"),
208
+ }
209
+
210
+ # Update date from actual data
211
+ created_utc = submission.get("created_utc")
212
+ if created_utc:
213
+ item["date"] = dates.timestamp_to_date(created_utc)
214
+
215
+ # Get top comments
216
+ top_comments = get_top_comments(comments)
217
+ item["top_comments"] = []
218
+ for c in top_comments:
219
+ permalink = c.get("permalink", "")
220
+ comment_url = f"https://reddit.com{permalink}" if permalink else ""
221
+ item["top_comments"].append({
222
+ "score": c.get("score", 0),
223
+ "date": dates.timestamp_to_date(c.get("created_utc")),
224
+ "author": c.get("author", ""),
225
+ "excerpt": c.get("body", "")[:200],
226
+ "url": comment_url,
227
+ })
228
+
229
+ # Extract insights
230
+ item["comment_insights"] = extract_comment_insights(top_comments)
231
+
232
+ return item
@@ -0,0 +1,383 @@
1
+ """Output rendering for last30days skill."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import List, Optional
6
+
7
+ from . import schema
8
+
9
+ OUTPUT_DIR = Path.home() / ".local" / "share" / "last30days" / "out"
10
+
11
+
12
+ def ensure_output_dir():
13
+ """Ensure output directory exists."""
14
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
15
+
16
+
17
+ def _assess_data_freshness(report: schema.Report) -> dict:
18
+ """Assess how much data is actually from the last 30 days."""
19
+ reddit_recent = sum(1 for r in report.reddit if r.date and r.date >= report.range_from)
20
+ x_recent = sum(1 for x in report.x if x.date and x.date >= report.range_from)
21
+ web_recent = sum(1 for w in report.web if w.date and w.date >= report.range_from)
22
+
23
+ total_recent = reddit_recent + x_recent + web_recent
24
+ total_items = len(report.reddit) + len(report.x) + len(report.web)
25
+
26
+ return {
27
+ "reddit_recent": reddit_recent,
28
+ "x_recent": x_recent,
29
+ "web_recent": web_recent,
30
+ "total_recent": total_recent,
31
+ "total_items": total_items,
32
+ "is_sparse": total_recent < 5,
33
+ "mostly_evergreen": total_items > 0 and total_recent < total_items * 0.3,
34
+ }
35
+
36
+
37
+ def render_compact(report: schema.Report, limit: int = 15, missing_keys: str = "none") -> str:
38
+ """Render compact output for Claude to synthesize.
39
+
40
+ Args:
41
+ report: Report data
42
+ limit: Max items per source
43
+ missing_keys: 'both', 'reddit', 'x', or 'none'
44
+
45
+ Returns:
46
+ Compact markdown string
47
+ """
48
+ lines = []
49
+
50
+ # Header
51
+ lines.append(f"## Research Results: {report.topic}")
52
+ lines.append("")
53
+
54
+ # Assess data freshness and add honesty warning if needed
55
+ freshness = _assess_data_freshness(report)
56
+ if freshness["is_sparse"]:
57
+ lines.append("**⚠️ LIMITED RECENT DATA** - Few discussions from the last 30 days.")
58
+ lines.append(f"Only {freshness['total_recent']} item(s) confirmed from {report.range_from} to {report.range_to}.")
59
+ lines.append("Results below may include older/evergreen content. Be transparent with the user about this.")
60
+ lines.append("")
61
+
62
+ # Web-only mode banner (when no API keys)
63
+ if report.mode == "web-only":
64
+ lines.append("**🌐 WEB SEARCH MODE** - Claude will search blogs, docs & news")
65
+ lines.append("")
66
+ lines.append("---")
67
+ lines.append("**⚡ Want better results?** Add API keys to unlock Reddit & X data:")
68
+ lines.append("- `OPENAI_API_KEY` → Reddit threads with real upvotes & comments")
69
+ lines.append("- `XAI_API_KEY` → X posts with real likes & reposts")
70
+ lines.append("- Edit `~/.config/last30days/.env` to add keys")
71
+ lines.append("---")
72
+ lines.append("")
73
+
74
+ # Cache indicator
75
+ if report.from_cache:
76
+ age_str = f"{report.cache_age_hours:.1f}h old" if report.cache_age_hours else "cached"
77
+ lines.append(f"**⚡ CACHED RESULTS** ({age_str}) - use `--refresh` for fresh data")
78
+ lines.append("")
79
+
80
+ lines.append(f"**Date Range:** {report.range_from} to {report.range_to}")
81
+ lines.append(f"**Mode:** {report.mode}")
82
+ if report.openai_model_used:
83
+ lines.append(f"**OpenAI Model:** {report.openai_model_used}")
84
+ if report.xai_model_used:
85
+ lines.append(f"**xAI Model:** {report.xai_model_used}")
86
+ lines.append("")
87
+
88
+ # Coverage note for partial coverage
89
+ if report.mode == "reddit-only" and missing_keys == "x":
90
+ lines.append("*💡 Tip: Add XAI_API_KEY for X/Twitter data and better triangulation.*")
91
+ lines.append("")
92
+ elif report.mode == "x-only" and missing_keys == "reddit":
93
+ lines.append("*💡 Tip: Add OPENAI_API_KEY for Reddit data and better triangulation.*")
94
+ lines.append("")
95
+
96
+ # Reddit items
97
+ if report.reddit_error:
98
+ lines.append("### Reddit Threads")
99
+ lines.append("")
100
+ lines.append(f"**ERROR:** {report.reddit_error}")
101
+ lines.append("")
102
+ elif report.mode in ("both", "reddit-only") and not report.reddit:
103
+ lines.append("### Reddit Threads")
104
+ lines.append("")
105
+ lines.append("*No relevant Reddit threads found for this topic.*")
106
+ lines.append("")
107
+ elif report.reddit:
108
+ lines.append("### Reddit Threads")
109
+ lines.append("")
110
+ for item in report.reddit[:limit]:
111
+ eng_str = ""
112
+ if item.engagement:
113
+ eng = item.engagement
114
+ parts = []
115
+ if eng.score is not None:
116
+ parts.append(f"{eng.score}pts")
117
+ if eng.num_comments is not None:
118
+ parts.append(f"{eng.num_comments}cmt")
119
+ if parts:
120
+ eng_str = f" [{', '.join(parts)}]"
121
+
122
+ date_str = f" ({item.date})" if item.date else " (date unknown)"
123
+ conf_str = f" [date:{item.date_confidence}]" if item.date_confidence != "high" else ""
124
+
125
+ lines.append(f"**{item.id}** (score:{item.score}) r/{item.subreddit}{date_str}{conf_str}{eng_str}")
126
+ lines.append(f" {item.title}")
127
+ lines.append(f" {item.url}")
128
+ lines.append(f" *{item.why_relevant}*")
129
+
130
+ # Top comment insights
131
+ if item.comment_insights:
132
+ lines.append(f" Insights:")
133
+ for insight in item.comment_insights[:3]:
134
+ lines.append(f" - {insight}")
135
+
136
+ lines.append("")
137
+
138
+ # X items
139
+ if report.x_error:
140
+ lines.append("### X Posts")
141
+ lines.append("")
142
+ lines.append(f"**ERROR:** {report.x_error}")
143
+ lines.append("")
144
+ elif report.mode in ("both", "x-only", "all", "x-web") and not report.x:
145
+ lines.append("### X Posts")
146
+ lines.append("")
147
+ lines.append("*No relevant X posts found for this topic.*")
148
+ lines.append("")
149
+ elif report.x:
150
+ lines.append("### X Posts")
151
+ lines.append("")
152
+ for item in report.x[:limit]:
153
+ eng_str = ""
154
+ if item.engagement:
155
+ eng = item.engagement
156
+ parts = []
157
+ if eng.likes is not None:
158
+ parts.append(f"{eng.likes}likes")
159
+ if eng.reposts is not None:
160
+ parts.append(f"{eng.reposts}rt")
161
+ if parts:
162
+ eng_str = f" [{', '.join(parts)}]"
163
+
164
+ date_str = f" ({item.date})" if item.date else " (date unknown)"
165
+ conf_str = f" [date:{item.date_confidence}]" if item.date_confidence != "high" else ""
166
+
167
+ lines.append(f"**{item.id}** (score:{item.score}) @{item.author_handle}{date_str}{conf_str}{eng_str}")
168
+ lines.append(f" {item.text[:200]}...")
169
+ lines.append(f" {item.url}")
170
+ lines.append(f" *{item.why_relevant}*")
171
+ lines.append("")
172
+
173
+ # Web items (if any - populated by Claude)
174
+ if report.web_error:
175
+ lines.append("### Web Results")
176
+ lines.append("")
177
+ lines.append(f"**ERROR:** {report.web_error}")
178
+ lines.append("")
179
+ elif report.web:
180
+ lines.append("### Web Results")
181
+ lines.append("")
182
+ for item in report.web[:limit]:
183
+ date_str = f" ({item.date})" if item.date else " (date unknown)"
184
+ conf_str = f" [date:{item.date_confidence}]" if item.date_confidence != "high" else ""
185
+
186
+ lines.append(f"**{item.id}** [WEB] (score:{item.score}) {item.source_domain}{date_str}{conf_str}")
187
+ lines.append(f" {item.title}")
188
+ lines.append(f" {item.url}")
189
+ lines.append(f" {item.snippet[:150]}...")
190
+ lines.append(f" *{item.why_relevant}*")
191
+ lines.append("")
192
+
193
+ return "\n".join(lines)
194
+
195
+
196
+ def render_context_snippet(report: schema.Report) -> str:
197
+ """Render reusable context snippet.
198
+
199
+ Args:
200
+ report: Report data
201
+
202
+ Returns:
203
+ Context markdown string
204
+ """
205
+ lines = []
206
+ lines.append(f"# Context: {report.topic} (Last 30 Days)")
207
+ lines.append("")
208
+ lines.append(f"*Generated: {report.generated_at[:10]} | Sources: {report.mode}*")
209
+ lines.append("")
210
+
211
+ # Key sources summary
212
+ lines.append("## Key Sources")
213
+ lines.append("")
214
+
215
+ all_items = []
216
+ for item in report.reddit[:5]:
217
+ all_items.append((item.score, "Reddit", item.title, item.url))
218
+ for item in report.x[:5]:
219
+ all_items.append((item.score, "X", item.text[:50] + "...", item.url))
220
+ for item in report.web[:5]:
221
+ all_items.append((item.score, "Web", item.title[:50] + "...", item.url))
222
+
223
+ all_items.sort(key=lambda x: -x[0])
224
+ for score, source, text, url in all_items[:7]:
225
+ lines.append(f"- [{source}] {text}")
226
+
227
+ lines.append("")
228
+ lines.append("## Summary")
229
+ lines.append("")
230
+ lines.append("*See full report for best practices, prompt pack, and detailed sources.*")
231
+ lines.append("")
232
+
233
+ return "\n".join(lines)
234
+
235
+
236
+ def render_full_report(report: schema.Report) -> str:
237
+ """Render full markdown report.
238
+
239
+ Args:
240
+ report: Report data
241
+
242
+ Returns:
243
+ Full report markdown
244
+ """
245
+ lines = []
246
+
247
+ # Title
248
+ lines.append(f"# {report.topic} - Last 30 Days Research Report")
249
+ lines.append("")
250
+ lines.append(f"**Generated:** {report.generated_at}")
251
+ lines.append(f"**Date Range:** {report.range_from} to {report.range_to}")
252
+ lines.append(f"**Mode:** {report.mode}")
253
+ lines.append("")
254
+
255
+ # Models
256
+ lines.append("## Models Used")
257
+ lines.append("")
258
+ if report.openai_model_used:
259
+ lines.append(f"- **OpenAI:** {report.openai_model_used}")
260
+ if report.xai_model_used:
261
+ lines.append(f"- **xAI:** {report.xai_model_used}")
262
+ lines.append("")
263
+
264
+ # Reddit section
265
+ if report.reddit:
266
+ lines.append("## Reddit Threads")
267
+ lines.append("")
268
+ for item in report.reddit:
269
+ lines.append(f"### {item.id}: {item.title}")
270
+ lines.append("")
271
+ lines.append(f"- **Subreddit:** r/{item.subreddit}")
272
+ lines.append(f"- **URL:** {item.url}")
273
+ lines.append(f"- **Date:** {item.date or 'Unknown'} (confidence: {item.date_confidence})")
274
+ lines.append(f"- **Score:** {item.score}/100")
275
+ lines.append(f"- **Relevance:** {item.why_relevant}")
276
+
277
+ if item.engagement:
278
+ eng = item.engagement
279
+ lines.append(f"- **Engagement:** {eng.score or '?'} points, {eng.num_comments or '?'} comments")
280
+
281
+ if item.comment_insights:
282
+ lines.append("")
283
+ lines.append("**Key Insights from Comments:**")
284
+ for insight in item.comment_insights:
285
+ lines.append(f"- {insight}")
286
+
287
+ lines.append("")
288
+
289
+ # X section
290
+ if report.x:
291
+ lines.append("## X Posts")
292
+ lines.append("")
293
+ for item in report.x:
294
+ lines.append(f"### {item.id}: @{item.author_handle}")
295
+ lines.append("")
296
+ lines.append(f"- **URL:** {item.url}")
297
+ lines.append(f"- **Date:** {item.date or 'Unknown'} (confidence: {item.date_confidence})")
298
+ lines.append(f"- **Score:** {item.score}/100")
299
+ lines.append(f"- **Relevance:** {item.why_relevant}")
300
+
301
+ if item.engagement:
302
+ eng = item.engagement
303
+ lines.append(f"- **Engagement:** {eng.likes or '?'} likes, {eng.reposts or '?'} reposts")
304
+
305
+ lines.append("")
306
+ lines.append(f"> {item.text}")
307
+ lines.append("")
308
+
309
+ # Web section
310
+ if report.web:
311
+ lines.append("## Web Results")
312
+ lines.append("")
313
+ for item in report.web:
314
+ lines.append(f"### {item.id}: {item.title}")
315
+ lines.append("")
316
+ lines.append(f"- **Source:** {item.source_domain}")
317
+ lines.append(f"- **URL:** {item.url}")
318
+ lines.append(f"- **Date:** {item.date or 'Unknown'} (confidence: {item.date_confidence})")
319
+ lines.append(f"- **Score:** {item.score}/100")
320
+ lines.append(f"- **Relevance:** {item.why_relevant}")
321
+ lines.append("")
322
+ lines.append(f"> {item.snippet}")
323
+ lines.append("")
324
+
325
+ # Placeholders for Claude synthesis
326
+ lines.append("## Best Practices")
327
+ lines.append("")
328
+ lines.append("*To be synthesized by Claude*")
329
+ lines.append("")
330
+
331
+ lines.append("## Prompt Pack")
332
+ lines.append("")
333
+ lines.append("*To be synthesized by Claude*")
334
+ lines.append("")
335
+
336
+ return "\n".join(lines)
337
+
338
+
339
+ def write_outputs(
340
+ report: schema.Report,
341
+ raw_openai: Optional[dict] = None,
342
+ raw_xai: Optional[dict] = None,
343
+ raw_reddit_enriched: Optional[list] = None,
344
+ ):
345
+ """Write all output files.
346
+
347
+ Args:
348
+ report: Report data
349
+ raw_openai: Raw OpenAI API response
350
+ raw_xai: Raw xAI API response
351
+ raw_reddit_enriched: Raw enriched Reddit thread data
352
+ """
353
+ ensure_output_dir()
354
+
355
+ # report.json
356
+ with open(OUTPUT_DIR / "report.json", 'w') as f:
357
+ json.dump(report.to_dict(), f, indent=2)
358
+
359
+ # report.md
360
+ with open(OUTPUT_DIR / "report.md", 'w') as f:
361
+ f.write(render_full_report(report))
362
+
363
+ # last30days.context.md
364
+ with open(OUTPUT_DIR / "last30days.context.md", 'w') as f:
365
+ f.write(render_context_snippet(report))
366
+
367
+ # Raw responses
368
+ if raw_openai:
369
+ with open(OUTPUT_DIR / "raw_openai.json", 'w') as f:
370
+ json.dump(raw_openai, f, indent=2)
371
+
372
+ if raw_xai:
373
+ with open(OUTPUT_DIR / "raw_xai.json", 'w') as f:
374
+ json.dump(raw_xai, f, indent=2)
375
+
376
+ if raw_reddit_enriched:
377
+ with open(OUTPUT_DIR / "raw_reddit_threads_enriched.json", 'w') as f:
378
+ json.dump(raw_reddit_enriched, f, indent=2)
379
+
380
+
381
+ def get_context_path() -> str:
382
+ """Get path to context file."""
383
+ return str(OUTPUT_DIR / "last30days.context.md")