@julioventura/opensquad 0.1.17

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 (247) hide show
  1. package/README.md +433 -0
  2. package/_opensquad/config/playwright.config.json +11 -0
  3. package/_opensquad/core/architect.agent.yaml +112 -0
  4. package/_opensquad/core/best-practices/_catalog.yaml +126 -0
  5. package/_opensquad/core/best-practices/blog-post.md +132 -0
  6. package/_opensquad/core/best-practices/blog-seo.md +127 -0
  7. package/_opensquad/core/best-practices/brand-resolution-checklist.md +172 -0
  8. package/_opensquad/core/best-practices/copywriting.md +441 -0
  9. package/_opensquad/core/best-practices/data-analysis.md +401 -0
  10. package/_opensquad/core/best-practices/email-newsletter.md +118 -0
  11. package/_opensquad/core/best-practices/email-sales.md +110 -0
  12. package/_opensquad/core/best-practices/image-design.md +348 -0
  13. package/_opensquad/core/best-practices/instagram-feed.md +235 -0
  14. package/_opensquad/core/best-practices/instagram-reels.md +112 -0
  15. package/_opensquad/core/best-practices/instagram-stories.md +107 -0
  16. package/_opensquad/core/best-practices/linkedin-article.md +116 -0
  17. package/_opensquad/core/best-practices/linkedin-post.md +121 -0
  18. package/_opensquad/core/best-practices/researching.md +349 -0
  19. package/_opensquad/core/best-practices/review.md +269 -0
  20. package/_opensquad/core/best-practices/run-recovery.md +61 -0
  21. package/_opensquad/core/best-practices/social-networks-publishing.md +327 -0
  22. package/_opensquad/core/best-practices/squad-creation-checklist.md +32 -0
  23. package/_opensquad/core/best-practices/strategist.md +344 -0
  24. package/_opensquad/core/best-practices/technical-writing.md +365 -0
  25. package/_opensquad/core/best-practices/twitter-post.md +105 -0
  26. package/_opensquad/core/best-practices/twitter-thread.md +122 -0
  27. package/_opensquad/core/best-practices/whatsapp-broadcast.md +107 -0
  28. package/_opensquad/core/best-practices/youtube-script.md +122 -0
  29. package/_opensquad/core/best-practices/youtube-shorts.md +112 -0
  30. package/_opensquad/core/defaults/youtube-video-assembly.json +84 -0
  31. package/_opensquad/core/prompts/build.prompt.md +613 -0
  32. package/_opensquad/core/prompts/design.prompt.md +606 -0
  33. package/_opensquad/core/prompts/discovery.prompt.md +377 -0
  34. package/_opensquad/core/prompts/sherlock-instagram.md +123 -0
  35. package/_opensquad/core/prompts/sherlock-linkedin.md +73 -0
  36. package/_opensquad/core/prompts/sherlock-shared.md +684 -0
  37. package/_opensquad/core/prompts/sherlock-twitter.md +78 -0
  38. package/_opensquad/core/prompts/sherlock-youtube.md +85 -0
  39. package/_opensquad/core/runner.pipeline.md +743 -0
  40. package/_opensquad/core/skills.engine.md +384 -0
  41. package/bin/opensquad.js +108 -0
  42. package/dashboard/index.html +15 -0
  43. package/dashboard/package-lock.json +1964 -0
  44. package/dashboard/package.json +28 -0
  45. package/dashboard/public/assets/avatars/Female1_1wave.png +0 -0
  46. package/dashboard/public/assets/avatars/Female1_2wave.png +0 -0
  47. package/dashboard/public/assets/avatars/Female1_blink.png +0 -0
  48. package/dashboard/public/assets/avatars/Female1_talk.png +0 -0
  49. package/dashboard/public/assets/avatars/Female2_1wave.png +0 -0
  50. package/dashboard/public/assets/avatars/Female2_2wave.png +0 -0
  51. package/dashboard/public/assets/avatars/Female2_blink.png +0 -0
  52. package/dashboard/public/assets/avatars/Female2_talk.png +0 -0
  53. package/dashboard/public/assets/avatars/Female3_blink.png +0 -0
  54. package/dashboard/public/assets/avatars/Female3_talk.png +0 -0
  55. package/dashboard/public/assets/avatars/Female3_wave.png +0 -0
  56. package/dashboard/public/assets/avatars/Female4_blink.png +0 -0
  57. package/dashboard/public/assets/avatars/Female4_talk.png +0 -0
  58. package/dashboard/public/assets/avatars/Female4_wave.png +0 -0
  59. package/dashboard/public/assets/avatars/Female5_blink.png +0 -0
  60. package/dashboard/public/assets/avatars/Female5_talk.png +0 -0
  61. package/dashboard/public/assets/avatars/Female5_wave.png +0 -0
  62. package/dashboard/public/assets/avatars/Female6_blink.png +0 -0
  63. package/dashboard/public/assets/avatars/Female6_talk.png +0 -0
  64. package/dashboard/public/assets/avatars/Female6_wave.png +0 -0
  65. package/dashboard/public/assets/avatars/Male1_1wave.png +0 -0
  66. package/dashboard/public/assets/avatars/Male1_2wave.png +0 -0
  67. package/dashboard/public/assets/avatars/Male1_blink.png +0 -0
  68. package/dashboard/public/assets/avatars/Male1_talk.png +0 -0
  69. package/dashboard/public/assets/avatars/Male2_1wave.png +0 -0
  70. package/dashboard/public/assets/avatars/Male2_2wave.png +0 -0
  71. package/dashboard/public/assets/avatars/Male2_blink.png +0 -0
  72. package/dashboard/public/assets/avatars/Male2_talk.png +0 -0
  73. package/dashboard/public/assets/avatars/Male3_blink.png +0 -0
  74. package/dashboard/public/assets/avatars/Male3_talk.png +0 -0
  75. package/dashboard/public/assets/avatars/Male3_wave.png +0 -0
  76. package/dashboard/public/assets/avatars/Male4_blink.png +0 -0
  77. package/dashboard/public/assets/avatars/Male4_talk.png +0 -0
  78. package/dashboard/public/assets/avatars/Male4_wave.png +0 -0
  79. package/dashboard/public/assets/desks/desktop_set_black_down.png +0 -0
  80. package/dashboard/public/assets/desks/desktop_set_black_down_coding-1.png +0 -0
  81. package/dashboard/public/assets/desks/desktop_set_black_down_coding.png +0 -0
  82. package/dashboard/public/assets/desks/desktop_set_black_up.png +0 -0
  83. package/dashboard/public/assets/desks/desktop_set_white_down.png +0 -0
  84. package/dashboard/public/assets/desks/desktop_set_white_down_coding-1.png +0 -0
  85. package/dashboard/public/assets/desks/desktop_set_white_down_coding.png +0 -0
  86. package/dashboard/public/assets/desks/desktop_set_white_up.png +0 -0
  87. package/dashboard/public/assets/furniture/armchair_tan.png +0 -0
  88. package/dashboard/public/assets/furniture/armchair_tan_down.png +0 -0
  89. package/dashboard/public/assets/furniture/backpack_blue.png +0 -0
  90. package/dashboard/public/assets/furniture/backpack_red.png +0 -0
  91. package/dashboard/public/assets/furniture/blinds.png +0 -0
  92. package/dashboard/public/assets/furniture/blinds_large_closed_white.png +0 -0
  93. package/dashboard/public/assets/furniture/bookshelf.png +0 -0
  94. package/dashboard/public/assets/furniture/bookshelf_purple_tall.png +0 -0
  95. package/dashboard/public/assets/furniture/bulletin_board.png +0 -0
  96. package/dashboard/public/assets/furniture/clock.png +0 -0
  97. package/dashboard/public/assets/furniture/coffee_mug.png +0 -0
  98. package/dashboard/public/assets/furniture/coffee_mug_blue.png +0 -0
  99. package/dashboard/public/assets/furniture/coffee_table.png +0 -0
  100. package/dashboard/public/assets/furniture/coffeepot_right.png +0 -0
  101. package/dashboard/public/assets/furniture/coffeetable_black_horizontal.png +0 -0
  102. package/dashboard/public/assets/furniture/couch.png +0 -0
  103. package/dashboard/public/assets/furniture/couch_tan_down.png +0 -0
  104. package/dashboard/public/assets/furniture/cushion_blue.png +0 -0
  105. package/dashboard/public/assets/furniture/cushion_tan.png +0 -0
  106. package/dashboard/public/assets/furniture/desk_wood.png +0 -0
  107. package/dashboard/public/assets/furniture/fancy_rug.png +0 -0
  108. package/dashboard/public/assets/furniture/fancy_rug_wide.png +0 -0
  109. package/dashboard/public/assets/furniture/flowers1.png +0 -0
  110. package/dashboard/public/assets/furniture/flowers2.png +0 -0
  111. package/dashboard/public/assets/furniture/lamp_tan.png +0 -0
  112. package/dashboard/public/assets/furniture/lantern.png +0 -0
  113. package/dashboard/public/assets/furniture/monstera.png +0 -0
  114. package/dashboard/public/assets/furniture/monstera_small.png +0 -0
  115. package/dashboard/public/assets/furniture/picture_frame.png +0 -0
  116. package/dashboard/public/assets/furniture/plant1.png +0 -0
  117. package/dashboard/public/assets/furniture/plant2.png +0 -0
  118. package/dashboard/public/assets/furniture/plant3.png +0 -0
  119. package/dashboard/public/assets/furniture/plant_poof.png +0 -0
  120. package/dashboard/public/assets/furniture/plant_spindly.png +0 -0
  121. package/dashboard/public/assets/furniture/poster_blue.png +0 -0
  122. package/dashboard/public/assets/furniture/rug.png +0 -0
  123. package/dashboard/public/assets/furniture/succulent_blue.png +0 -0
  124. package/dashboard/public/assets/furniture/succulent_green.png +0 -0
  125. package/dashboard/public/assets/furniture/treasurechest_closed_gold.png +0 -0
  126. package/dashboard/public/assets/furniture/water_cooler_better.png +0 -0
  127. package/dashboard/public/assets/furniture/whiteboard.png +0 -0
  128. package/dashboard/public/assets/furniture/whiteboard_stand_graph.png +0 -0
  129. package/dashboard/public/assets/furniture/window_blinds_open.png +0 -0
  130. package/dashboard/src/App.tsx +46 -0
  131. package/dashboard/src/components/RunDashboardButton.tsx +92 -0
  132. package/dashboard/src/components/SquadCard.tsx +49 -0
  133. package/dashboard/src/components/SquadSelector.tsx +67 -0
  134. package/dashboard/src/components/StatusBadge.tsx +32 -0
  135. package/dashboard/src/components/StatusBar.tsx +116 -0
  136. package/dashboard/src/hooks/useSquadSocket.ts +135 -0
  137. package/dashboard/src/lib/formatTime.ts +16 -0
  138. package/dashboard/src/lib/normalizeState.ts +25 -0
  139. package/dashboard/src/main.tsx +10 -0
  140. package/dashboard/src/office/AgentSprite.ts +241 -0
  141. package/dashboard/src/office/OfficeScene.ts +153 -0
  142. package/dashboard/src/office/PhaserGame.tsx +80 -0
  143. package/dashboard/src/office/RoomBuilder.ts +190 -0
  144. package/dashboard/src/office/assetKeys.ts +150 -0
  145. package/dashboard/src/office/palette.ts +32 -0
  146. package/dashboard/src/plugin/squadWatcher.ts +397 -0
  147. package/dashboard/src/store/useSquadStore.ts +56 -0
  148. package/dashboard/src/styles/globals.css +36 -0
  149. package/dashboard/src/types/state.ts +63 -0
  150. package/dashboard/src/vite-env.d.ts +1 -0
  151. package/dashboard/tsconfig.json +24 -0
  152. package/dashboard/vite.config.ts +13 -0
  153. package/package.json +59 -0
  154. package/public/sfx/slide-transition-sfx.mp3 +0 -0
  155. package/skills/README.md +84 -0
  156. package/skills/apify/SKILL.md +55 -0
  157. package/skills/blotato/SKILL.md +63 -0
  158. package/skills/canva/SKILL.md +60 -0
  159. package/skills/higgsfield/SKILL.md +147 -0
  160. package/skills/image-ai-generator/SKILL.md +124 -0
  161. package/skills/image-ai-generator/scripts/generate.py +175 -0
  162. package/skills/image-creator/SKILL.md +166 -0
  163. package/skills/image-creator/editorial-slide-template.js +645 -0
  164. package/skills/image-fetcher/SKILL.md +91 -0
  165. package/skills/imgbb-uploader/SKILL.md +73 -0
  166. package/skills/imgbb-uploader/scripts/upload.js +125 -0
  167. package/skills/instagram-publisher/README.md +36 -0
  168. package/skills/instagram-publisher/SKILL.md +231 -0
  169. package/skills/instagram-publisher/scripts/publish-playwright.js +418 -0
  170. package/skills/instagram-publisher/scripts/publish.js +521 -0
  171. package/skills/opensquad-agent-creator/SKILL.md +192 -0
  172. package/skills/opensquad-skill-creator/SKILL.md +420 -0
  173. package/skills/opensquad-skill-creator/agents/analyzer.md +274 -0
  174. package/skills/opensquad-skill-creator/agents/comparator.md +202 -0
  175. package/skills/opensquad-skill-creator/agents/grader.md +223 -0
  176. package/skills/opensquad-skill-creator/assets/eval_review.html +146 -0
  177. package/skills/opensquad-skill-creator/eval-viewer/generate_review.py +471 -0
  178. package/skills/opensquad-skill-creator/eval-viewer/viewer.html +1325 -0
  179. package/skills/opensquad-skill-creator/references/schemas.md +430 -0
  180. package/skills/opensquad-skill-creator/references/skill-format.md +235 -0
  181. package/skills/opensquad-skill-creator/scripts/__init__.py +0 -0
  182. package/skills/opensquad-skill-creator/scripts/aggregate_benchmark.py +401 -0
  183. package/skills/opensquad-skill-creator/scripts/quick_validate.py +103 -0
  184. package/skills/opensquad-skill-creator/scripts/run_eval.py +310 -0
  185. package/skills/opensquad-skill-creator/scripts/utils.py +47 -0
  186. package/skills/pdf-extractor/SKILL.md +57 -0
  187. package/skills/pdf-extractor/scripts/extract.py +82 -0
  188. package/skills/resend/SKILL.md +80 -0
  189. package/skills/run-dashboard/README.md +93 -0
  190. package/skills/run-dashboard/SKILL.md +173 -0
  191. package/skills/run-dashboard/scripts/finalize-state.js +273 -0
  192. package/skills/run-dashboard/scripts/generate.js +1296 -0
  193. package/skills/run-dashboard/scripts/serve.js +135 -0
  194. package/skills/run-dashboard/templates/run-dashboard-simple.template.html +191 -0
  195. package/skills/run-dashboard/templates/run-dashboard.template.html +1164 -0
  196. package/skills/smtp-sender/SKILL.md +88 -0
  197. package/skills/smtp-sender/scripts/send.js +478 -0
  198. package/skills/template-designer/SKILL.md +201 -0
  199. package/skills/template-designer/base-templates/model-a.html +27 -0
  200. package/skills/template-designer/base-templates/model-b.html +31 -0
  201. package/skills/template-designer/base-templates/model-c.html +42 -0
  202. package/skills/youtube-publisher/SKILL.md +232 -0
  203. package/skills/youtube-publisher/scripts/publish.js +2078 -0
  204. package/src/agents-cli.js +158 -0
  205. package/src/agents.js +134 -0
  206. package/src/i18n.js +48 -0
  207. package/src/init.js +442 -0
  208. package/src/locales/en.json +79 -0
  209. package/src/locales/es.json +78 -0
  210. package/src/locales/pt-BR.json +78 -0
  211. package/src/logger.js +38 -0
  212. package/src/prompt.js +46 -0
  213. package/src/readme/README.md +146 -0
  214. package/src/runs.js +318 -0
  215. package/src/skills-cli.js +157 -0
  216. package/src/skills.js +146 -0
  217. package/src/supabase-cli.js +584 -0
  218. package/src/update.js +169 -0
  219. package/templates/_opensquad/.opensquad-version +1 -0
  220. package/templates/_opensquad/_investigations/.gitkeep +0 -0
  221. package/templates/ide-templates/antigravity/.agent/rules/opensquad.md +68 -0
  222. package/templates/ide-templates/antigravity/.agent/workflows/opensquad.md +102 -0
  223. package/templates/ide-templates/claude-code/.claude/skills/opensquad/SKILL.md +182 -0
  224. package/templates/ide-templates/claude-code/.mcp.json +8 -0
  225. package/templates/ide-templates/claude-code/CLAUDE.md +57 -0
  226. package/templates/ide-templates/codex/.agents/skills/opensquad/SKILL.md +6 -0
  227. package/templates/ide-templates/codex/AGENTS.md +120 -0
  228. package/templates/ide-templates/cursor/.cursor/commands/opensquad.md +9 -0
  229. package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
  230. package/templates/ide-templates/cursor/.cursor/rules/opensquad.mdc +62 -0
  231. package/templates/ide-templates/cursor/.cursorignore +3 -0
  232. package/templates/ide-templates/gemini-cli/.gemini/settings.json +8 -0
  233. package/templates/ide-templates/gemini-cli/.gemini/skills/opensquad/SKILL.md +186 -0
  234. package/templates/ide-templates/gemini-cli/GEMINI.md +57 -0
  235. package/templates/ide-templates/opencode/.opencode/commands/opensquad.md +9 -0
  236. package/templates/ide-templates/opencode/AGENTS.md +120 -0
  237. package/templates/ide-templates/qwen-code/.qwen/settings.json +8 -0
  238. package/templates/ide-templates/qwen-code/.qwen/skills/opensquad/SKILL.md +182 -0
  239. package/templates/ide-templates/qwen-code/QWEN.md +57 -0
  240. package/templates/ide-templates/trae/.trae/mcp.json +8 -0
  241. package/templates/ide-templates/trae/.trae/rules/opensquad.md +64 -0
  242. package/templates/ide-templates/vscode-copilot/.github/copilot-instructions.md +59 -0
  243. package/templates/ide-templates/vscode-copilot/.github/prompts/opensquad.prompt.md +209 -0
  244. package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
  245. package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
  246. package/templates/package.json +8 -0
  247. package/templates/squads/.gitkeep +0 -0
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: image-fetcher
3
+ description: >
4
+ Acquires visual assets from multiple sources: web image search,
5
+ live website screenshots via Playwright, and user-provided files.
6
+ Organizes assets in the squad's reference folder.
7
+ description_pt-BR: >
8
+ Obtém assets visuais de múltiplas fontes: busca de imagens na web,
9
+ capturas de sites via Playwright e arquivos fornecidos pelo usuário.
10
+ Organiza os assets na pasta de referência do squad.
11
+ description_es: >
12
+ Obtiene assets visuales de múltiples fuentes: búsqueda de imágenes en la web,
13
+ capturas de sitios vía Playwright y archivos proporcionados por el usuario.
14
+ Organiza los assets en la carpeta de referencia del squad.
15
+ type: hybrid
16
+ version: "1.0.0"
17
+ mcp:
18
+ server_name: playwright
19
+ categories: [assets, scraping, automation, images]
20
+ ---
21
+
22
+ # Asset Fetcher
23
+
24
+ ## When to use
25
+
26
+ Use the Asset Fetcher when you need to acquire visual assets for content creation. It supports three acquisition modes: web image search, live website screenshots via Playwright, and organizing user-provided files. All assets are saved to the squad's reference or output folder with descriptive filenames and metadata.
27
+
28
+ ## Instructions
29
+
30
+ ### Capabilities
31
+
32
+ 1. **Web Image Search** -- Use the native web_search tool to find images by keyword. Evaluate results and download the best match.
33
+
34
+ 2. **Live Screenshot** -- Use Playwright MCP to navigate to a URL, set viewport dimensions, and capture a screenshot.
35
+
36
+ 3. **Asset Organization** -- Save all acquired assets with descriptive filenames in the squad's reference/ or output/ folder.
37
+
38
+ ### Screenshot Modes
39
+
40
+ - **viewport** -- Capture only the visible viewport area (default)
41
+ - **full_page** -- Capture the entire scrollable page
42
+ - **selector** -- Capture a specific CSS selector element
43
+
44
+ ### Screenshot Workflow
45
+
46
+ When taking a screenshot:
47
+ 1. Navigate to the URL with `browser_navigate`
48
+ 2. Set viewport: `browser_resize` with width/height for target format
49
+ - Instagram post: 1080x1080
50
+ - Instagram carousel: 1080x1440
51
+ - Story/Reel: 1080x1920
52
+ - Generic: 1280x720
53
+ 3. Wait for page load (`browser_wait_for` if needed)
54
+ 4. Capture: `browser_take_screenshot`
55
+ 5. Save to reference folder with descriptive filename
56
+
57
+ ### Asset Metadata
58
+
59
+ After acquiring each asset, document in your output:
60
+ - `path`: local file path
61
+ - `width/height`: image dimensions
62
+ - `source_type`: "web_search" | "screenshot" | "user_provided"
63
+ - `original_url`: source URL (if applicable)
64
+
65
+ ### Cache Policy
66
+
67
+ Before fetching an asset:
68
+ - Check if the reference folder already has a matching file
69
+ - Use deterministic filenames based on source (e.g., URL slug + viewport)
70
+ - Reuse existing assets to avoid redundant fetches
71
+
72
+ ### Safety
73
+
74
+ - Timeout: max 30s per screenshot, skip and warn if exceeded
75
+ - Maximum screenshot dimensions: 1920x1920px
76
+ - Block `file://` protocol URLs
77
+ - Block localhost and private IP ranges (127.0.0.1, 10.x, 192.168.x)
78
+
79
+ ### Best Practices
80
+
81
+ - Prefer screenshots over web search for product/tool pages (images are often outdated)
82
+ - Save with descriptive names: `gemini-benchmark-chart.jpg` not `image1.jpg`
83
+ - Normalize URLs before caching (strip tracking params)
84
+ - Document all acquired assets with metadata for downstream tools
85
+
86
+ ## Available operations
87
+
88
+ - **Web Image Search** -- Find and download images by keyword from the web
89
+ - **Live Screenshot** -- Capture viewport, full-page, or element screenshots of any URL
90
+ - **Asset Organization** -- Save and catalog assets with descriptive filenames and metadata
91
+ - **Cache Check** -- Detect and reuse previously fetched assets to avoid redundant downloads
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: imgbb-uploader
3
+ description: >
4
+ Upload local JPG/JPEG/PNG images to imgBB and return public URLs for downstream use.
5
+ Useful for email HTML, landing pages, and any workflow that needs externally hosted images.
6
+ description_pt-BR: >
7
+ Faz upload de imagens JPG/JPEG/PNG locais para o imgBB e retorna URLs publicas para uso posterior.
8
+ Util para email HTML, landing pages e qualquer fluxo que precise de imagens hospedadas externamente.
9
+ description_es: >
10
+ Sube imagenes JPG/JPEG/PNG locales a imgBB y devuelve URLs publicas para uso posterior.
11
+ Util para email HTML, landing pages y cualquier flujo que necesite imagenes alojadas externamente.
12
+ type: script
13
+ version: "1.0.0"
14
+ script:
15
+ path: scripts/upload.js
16
+ runtime: node
17
+ invoke: "node --env-file=.env {skill_path}/scripts/upload.js --images \"{images}\" --output \"{output}\""
18
+ env:
19
+ - IMGBB_API_KEY
20
+ categories: [images, hosting, automation]
21
+ ---
22
+
23
+ # imgBB Uploader
24
+
25
+ ## When to use
26
+
27
+ Use this skill when a workflow needs public image URLs instead of local files. Typical cases include HTML email, web pages, CMS publishing, or any channel that renders `<img src="https://...">` from a hosted asset.
28
+
29
+ ## Instructions
30
+
31
+ ### Workflow
32
+
33
+ 1. Prepare an ordered list of local JPG/JPEG/PNG files.
34
+ 2. Run the upload script with `--images` and an `--output` path.
35
+ 3. Read the generated JSON manifest and pass the returned URLs to the next step.
36
+
37
+ ### Command
38
+
39
+ ```bash
40
+ node --env-file=.env {skill_path}/scripts/upload.js \
41
+ --images "slide-01.jpg,slide-02.png,slide-03.jpg" \
42
+ --output "output/image-urls.json"
43
+ ```
44
+
45
+ ### Output
46
+
47
+ The script writes a JSON file with one entry per uploaded image:
48
+
49
+ ```json
50
+ {
51
+ "status": "uploaded",
52
+ "count": 3,
53
+ "images": [
54
+ {
55
+ "file": "slide-01.jpg",
56
+ "absolutePath": "C:/repo/output/slide-01.jpg",
57
+ "url": "https://i.ibb.co/.../slide-01.jpg",
58
+ "deleteUrl": "https://ibb.co/..."
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ### Constraints
65
+
66
+ - Input images: JPG, JPEG, or PNG
67
+ - `IMGBB_API_KEY` must exist in `.env`
68
+ - The script uploads immediately; there is no dry-run mode
69
+
70
+ ## Available operations
71
+
72
+ - **Upload image batch** -- Upload one or more local JPG/JPEG/PNG files
73
+ - **Manifest generation** -- Save a structured JSON file with public URLs for downstream steps
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync } from 'node:fs';
4
+ import { basename, extname, resolve } from 'node:path';
5
+
6
+ export function parseArgs(argv) {
7
+ const args = {
8
+ images: [],
9
+ output: null,
10
+ };
11
+
12
+ for (let index = 2; index < argv.length; index += 1) {
13
+ const token = argv[index];
14
+
15
+ if (token === '--images' && index + 1 < argv.length) {
16
+ args.images = argv[index + 1]
17
+ .split(',')
18
+ .map((imagePath) => imagePath.trim())
19
+ .filter(Boolean);
20
+ index += 1;
21
+ continue;
22
+ }
23
+
24
+ if (token === '--output' && index + 1 < argv.length) {
25
+ args.output = argv[index + 1];
26
+ index += 1;
27
+ continue;
28
+ }
29
+
30
+ if (token === '--help' || token === '-h') {
31
+ printHelp();
32
+ process.exit(0);
33
+ }
34
+ }
35
+
36
+ return args;
37
+ }
38
+
39
+ function printHelp() {
40
+ console.log([
41
+ 'Usage: node --env-file=.env upload.js --images "slide-01.jpg,slide-02.png" --output "output/image-urls.json"',
42
+ '',
43
+ 'Arguments:',
44
+ ' --images Comma-separated JPG/JPEG/PNG file list',
45
+ ' --output JSON manifest path to write',
46
+ ].join('\n'));
47
+ }
48
+
49
+ export function assertSupportedExtension(imagePath) {
50
+ const extension = extname(imagePath).toLowerCase();
51
+ if (!['.jpg', '.jpeg', '.png'].includes(extension)) {
52
+ throw new Error(`Unsupported image format for '${imagePath}'. Use JPG, JPEG, or PNG.`);
53
+ }
54
+ }
55
+
56
+ export async function uploadToImgBB(imagePath, apiKey) {
57
+ const absolutePath = resolve(imagePath);
58
+ const fileBuffer = readFileSync(absolutePath);
59
+ const base64Image = fileBuffer.toString('base64');
60
+ const form = new FormData();
61
+ form.append('key', apiKey);
62
+ form.append('image', base64Image);
63
+
64
+ const response = await fetch('https://api.imgbb.com/1/upload', {
65
+ method: 'POST',
66
+ body: form,
67
+ });
68
+
69
+ if (!response.ok) {
70
+ throw new Error(`imgBB upload failed [${response.status}]: ${await response.text()}`);
71
+ }
72
+
73
+ const json = await response.json();
74
+ if (!json.success) {
75
+ throw new Error(`imgBB upload failed: ${JSON.stringify(json)}`);
76
+ }
77
+
78
+ return {
79
+ file: basename(imagePath),
80
+ absolutePath,
81
+ url: json.data.url,
82
+ deleteUrl: json.data.delete_url,
83
+ };
84
+ }
85
+
86
+ export async function main() {
87
+ const args = parseArgs(process.argv);
88
+ const apiKey = process.env.IMGBB_API_KEY;
89
+
90
+ if (!apiKey) {
91
+ throw new Error('IMGBB_API_KEY is not set in environment.');
92
+ }
93
+
94
+ if (!args.images.length) {
95
+ throw new Error('--images is required.');
96
+ }
97
+
98
+ if (!args.output) {
99
+ throw new Error('--output is required.');
100
+ }
101
+
102
+ const images = [];
103
+ for (const imagePath of args.images) {
104
+ assertSupportedExtension(imagePath);
105
+ images.push(await uploadToImgBB(imagePath, apiKey));
106
+ }
107
+
108
+ const manifest = {
109
+ status: 'uploaded',
110
+ count: images.length,
111
+ images,
112
+ };
113
+
114
+ writeFileSync(resolve(args.output), `${JSON.stringify(manifest, null, 2)}\n`);
115
+ console.log(JSON.stringify(manifest, null, 2));
116
+ }
117
+
118
+ const isMain = process.argv[1] === new URL(import.meta.url).pathname;
119
+
120
+ if (isMain) {
121
+ main().catch((error) => {
122
+ console.error(`\n❌ ${error.message}`);
123
+ process.exit(1);
124
+ });
125
+ }
@@ -0,0 +1,36 @@
1
+ # Instagram Publisher Operational Notes
2
+
3
+ ## Facebook Page token resolution via INSTAGRAM_ACCESS_TOKEN
4
+
5
+ In multi-brand workspaces, `FACEBOOK_PAGE_ACCESS_TOKEN` may not belong to the page selected for the current squad. When that happens, Facebook cross-post can fail even if Instagram publishing succeeds.
6
+
7
+ The supported operational fallback is:
8
+
9
+ 1. Keep `FACEBOOK_PAGE_ID` configured for the target page.
10
+ 2. Keep `INSTAGRAM_ACCESS_TOKEN` configured with access to the connected Facebook assets.
11
+ 3. Let the publisher resolve the effective Page token dynamically through `GET /me/accounts` using `INSTAGRAM_ACCESS_TOKEN`.
12
+ 4. Use the resolved page-specific `access_token` for unpublished photo upload and final `/feed` post creation.
13
+
14
+ ## Why this matters
15
+
16
+ Facebook unpublished photo uploads must run as the target Page itself. A token from another page, or a generic token that does not resolve to the target page, can lead to failures such as:
17
+
18
+ - wrong page context
19
+ - text-only posts
20
+ - single-photo posts when an album was expected
21
+ - Graph API permission or object errors during verification
22
+
23
+ ## Recommended operational sequence
24
+
25
+ 1. Publish Instagram normally.
26
+ 2. For Facebook cross-post, resolve the target Page token from `INSTAGRAM_ACCESS_TOKEN` through `/me/accounts`.
27
+ 3. Upload each approved JPG as unpublished media to `/{page-id}/photos`.
28
+ 4. If Facebook rejects the remote image URL with errors such as `Missing or invalid image file`, retry the same unpublished upload with the local JPG binary instead of the hosted URL.
29
+ 5. Create the final feed post with `attached_media` as a JSON array in the request body.
30
+ 6. Verify the result by checking that the post appears as `media_type: album` with the expected subattachment count.
31
+
32
+ ## Current policy
33
+
34
+ - Prefer JPG/JPEG assets for publishing flows.
35
+ - Treat `INSTAGRAM_ACCESS_TOKEN` as the bootstrap credential for resolving the effective Facebook Page token when needed.
36
+ - Do not assume the raw `FACEBOOK_PAGE_ACCESS_TOKEN` is already the final token for the selected page in shared workspaces.
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: instagram-publisher
3
+ description: >
4
+ Publishes Instagram carousel posts from local images, with optional Facebook Page cross-post.
5
+ Uploads images to imgBB (requires API key) for public hosting, creates Instagram
6
+ media containers via the Graph API, and publishes the carousel.
7
+ Supports 2-10 images per post, retrieves the real post permalink, and can simultaneously
8
+ post to a connected Facebook Page using --facebook.
9
+ description_pt-BR: >
10
+ Publica carrosséis do Instagram a partir de imagens locais, com cross-post opcional para Página do Facebook.
11
+ Faz upload das imagens para o imgBB (requer chave de API) como hospedagem pública,
12
+ cria containers de mídia via Graph API e publica o carrossel.
13
+ Suporta de 2 a 10 imagens por post, obtém o permalink real, e pode simultaneamente
14
+ publicar na Página do Facebook conectada usando --facebook.
15
+ description_es: >
16
+ Publica carruseles de Instagram a partir de imágenes locales, con cross-post opcional a Página de Facebook.
17
+ Sube las imágenes a imgBB (requiere clave de API) como hosting público, crea
18
+ contenedores de medios vía Graph API y publica el carrusel.
19
+ Soporta de 2 a 10 imágenes por post, obtiene el permalink real y puede publicar
20
+ simultáneamente en la Página de Facebook conectada usando --facebook.
21
+ type: script
22
+ version: "1.1.0"
23
+ script:
24
+ path: scripts/publish.js
25
+ runtime: node
26
+ invoke: "node --env-file=.env {skill_path}/scripts/publish.js --images \"{images}\" --caption \"{caption}\""
27
+ invoke_facebook: "node --env-file=.env {skill_path}/scripts/publish.js --images \"{images}\" --caption \"{caption}\" --facebook"
28
+ invoke_facebook_only: "node --env-file=.env {skill_path}/scripts/publish.js --images \"{images}\" --caption \"{caption}\" --facebook --facebook-only"
29
+ invoke_playwright: "node {skill_path}/scripts/publish-playwright.js --images \"{images}\" --caption \"{caption}\" [--username \"{username}\"] [--password \"{password}\"] [--profile-dir \"{profile_dir}\"]"
30
+ env:
31
+ - INSTAGRAM_ACCESS_TOKEN # default key; can be replaced per squad via --instagram-access-token-env
32
+ - INSTAGRAM_USER_ID # default key; can be replaced per squad via --instagram-user-id-env
33
+ - IMGBB_API_KEY
34
+ - FACEBOOK_PAGE_ID # default key; can be replaced per squad via --facebook-page-id-env
35
+ - FACEBOOK_PAGE_ACCESS_TOKEN # default key; can be replaced per squad via --facebook-page-access-token-env
36
+ categories: [social-media, publishing, instagram]
37
+ ---
38
+
39
+ # Instagram Publisher
40
+
41
+ ## When to use
42
+
43
+ Use the Instagram Publisher when you need to publish carousel posts directly to an Instagram Business account. This skill handles the full workflow: uploading local JPG/JPEG or PNG images to imgBB (requires your own API key from https://api.imgbb.com/), creating Instagram media containers via the Graph API, and publishing the carousel. It supports 2-10 images per post.
44
+
45
+
46
+ ## Instructions
47
+
48
+ ### Workflow
49
+
50
+ 1. List JPG/JPEG or PNG files in `squads/{squad}/output/images/` sorted by name.
51
+ If no files found: stop and ask the user to add images before continuing.
52
+ 2. Present the image list to the user with AskUserQuestion to confirm order.
53
+ 3. Extract the caption from the content draft:
54
+ - Use the hook slide text + CTA slide text
55
+ - Max 2200 characters (Instagram limit)
56
+ 4. Run the publish script (add `--facebook` to also post to the connected Facebook Page):
57
+ ```
58
+ node --env-file=.env {skill_path}/scripts/publish.js \
59
+ --images "<comma-separated-ordered-paths>" \
60
+ --caption "<caption>" \
61
+ [--instagram-access-token-env "<ENV_VAR_NAME>"] \
62
+ [--instagram-user-id-env "<ENV_VAR_NAME>"] \
63
+ [--facebook-page-id-env "<ENV_VAR_NAME>"] \
64
+ [--facebook-page-access-token-env "<ENV_VAR_NAME>"] \
65
+ [--facebook] # optional: also post to Facebook Page
66
+ ```
67
+ Add `--dry-run` to test the full flow without actually publishing.
68
+ If Instagram already published successfully but the Facebook cross-post failed, rerun only the Facebook step with `--facebook --facebook-only` to avoid duplicating the Instagram post.
69
+ If `createChildContainer` fails with a transient Graph API media error such as `OAuthException`, `Não é possível criar a mídia`, or `Falha ao criar imagem`, retry once before escalating with the same image set but a publish-safe caption: collapse excessive blank lines, strip smart quotes and other decorative punctuation, and remove raw URLs from the Instagram caption body. Validate that retry with `--dry-run` first.
70
+ If the final `publishMedia` call fails with Meta `code 4`, `error_subcode 2207051`, or `Application request limit reached` after containers were created successfully, stop retrying the live API path. Treat it as a Graph API restriction for the current app/account pair and switch to Meta Business Suite with the same approved assets and caption.
71
+ 5. On success: save the post URL and post ID to the step output file.
72
+ 6. If this publish belongs to an Opensquad run, treat the dashboard as part of finalization: once the last selected output for the run is complete, regenerate `run-dashboard.html` and `run-dashboard.data.json` for the same run folder. If static publish is enabled in the squad config, it must publish automatically as part of that same finalization pass.
73
+ 7. On failure: display the error and ask the user how to proceed.
74
+
75
+ Do not open a second approval prompt only for the run-dashboard. After the user approves the live publish, publish first and then report that the dashboard finalization was executed.
76
+
77
+ ### Per-squad credential routing
78
+
79
+ When the same workspace operates multiple brands, do not force all squads to publish through the same account.
80
+
81
+ The publish script accepts env-variable-name aliases so each squad can point to its own credentials while still loading secrets from the shared `.env` file.
82
+
83
+ Example for a MusicPlay.club squad:
84
+
85
+ ```
86
+ node --env-file=.env {skill_path}/scripts/publish.js \
87
+ --images "slide-01.jpg,slide-02.jpg" \
88
+ --caption "..." \
89
+ --instagram-access-token-env "MUSICPLAY_INSTAGRAM_ACCESS_TOKEN" \
90
+ --instagram-user-id-env "MUSICPLAY_INSTAGRAM_USER_ID" \
91
+ --facebook-page-id-env "MUSICPLAY_FB_PAGE_ID" \
92
+ --facebook-page-access-token-env "MUSICPLAY_FACEBOOK_PAGE_ACCESS_TOKEN" \
93
+ --facebook
94
+ ```
95
+
96
+ If the workspace already has brand-specific env keys, use them. Do not default back to generic `INSTAGRAM_ACCESS_TOKEN` or `INSTAGRAM_USER_ID` for a different brand just because the publish command would still run.
97
+
98
+ If these flags are omitted, the script falls back to the default keys:
99
+
100
+ - `INSTAGRAM_ACCESS_TOKEN`
101
+ - `INSTAGRAM_USER_ID`
102
+ - `FACEBOOK_PAGE_ID`
103
+ - `FACEBOOK_PAGE_ACCESS_TOKEN`
104
+
105
+ ### Facebook Page token resolution in multi-brand workspaces
106
+
107
+ In a workspace that operates more than one Facebook Page, do not assume that a single stored `FACEBOOK_PAGE_ACCESS_TOKEN` already belongs to the page selected for the current squad.
108
+
109
+ The publisher now resolves the effective token for the requested `FACEBOOK_PAGE_ID` before uploading unpublished photos:
110
+
111
+ 1. If the configured Facebook token already identifies as the target Page, it is used directly.
112
+ 2. If not, the publisher tries to resolve the target Page through `/me/accounts` using the available shared Instagram/user token.
113
+ 3. If a matching page is found, the page-specific `access_token` returned by Meta is used for the Facebook cross-post.
114
+ 4. If no token can be resolved for the requested Page, the Facebook step is blocked instead of attempting to post with the wrong page context.
115
+
116
+ If Facebook rejects an unpublished photo upload with errors such as `Missing or invalid image file`, do not stop at the hosted image URL path. Retry the same `/{page-id}/photos` upload using the local JPG/JPEG or PNG file as binary `source` while keeping the resolved Page token. Treat this as the default recovery path for remote-hosting incompatibilities.
117
+
118
+ ### Manual Meta Business Suite fallback
119
+
120
+ When the Graph API path is blocked but the content is approved and ready:
121
+
122
+ 1. Open Meta Business Suite for the correct Page/account pair.
123
+ 2. Upload the already-approved image list in the same order used for the dry-run.
124
+ 3. Paste the approved caption without reintroducing raw source URLs into the Instagram body.
125
+ 4. Before the final confirmation click, verify that slide 1 is the cover and that the remaining slides kept the approved order.
126
+ 5. After publishing, record the final Instagram/Facebook URLs and post IDs in the run artifacts.
127
+ 6. If a wrong-order Instagram post was created, delete it before finalizing the run when permissions allow it. A wrong Facebook post may require stronger Page permissions to remove; document that residual issue instead of blocking closure forever.
128
+
129
+ This matters because unpublished photo uploads on Facebook must be executed as the target Page itself. A token that belongs to another Page in the same workspace may still be valid, but it will fail with errors such as `Unpublished posts must be posted to a page as the page itself`.
130
+
131
+ ### Automated Browser-Based Fallback (Playwright)
132
+
133
+ When the Graph API path is blocked by Meta App Review or rate limits (e.g. error `2207051` or `Application request limit reached`), you can automate the web publishing flow using the Playwright fallback script:
134
+
135
+ ```bash
136
+ node skills/instagram-publisher/scripts/publish-playwright.js \
137
+ --images "{lista-dinamica-de-slides}" \
138
+ --caption "{legenda_completa}" \
139
+ [--username "{usuario}"] \
140
+ [--password "{senha}"] \
141
+ [--profile-dir "{diretorio_do_perfil_do_navegador}"]
142
+ ```
143
+
144
+ This script:
145
+ 1. Launches a headful browser (Chrome) using a persistent profile directory (defaulting to `_opensquad/_browser_profile`).
146
+ 2. Leverages the persistent session to avoid daily login checks and cookie dialogs.
147
+ 3. Handles standard popup/dialog dismissals automatically (e.g., "Not Now" / "Agora não").
148
+ 4. Uploads all images at once as a carousel, and pastes the caption.
149
+ 5. Obtains the live URL and Post ID automatically upon successful posting.
150
+
151
+ ### Constraints
152
+
153
+ - Images: JPG, JPEG, or PNG, 2-10 per carousel
154
+ - Caption: max 2200 characters
155
+ - Prefer a short publish-safe caption for the live Instagram API call: strong hook, practical body, CTA, hashtags at the end, and no raw source URLs in the caption body
156
+ - Requires Instagram Business account (not Personal or Creator)
157
+ - Rate limit: 25 API-published posts per 24 hours
158
+ - Scheduling: not supported by this skill; use manual scheduling after dry-run if needed
159
+
160
+ ### Setup (first-time)
161
+
162
+ Copy `.env.example` to `.env` and fill in the required variables. You can use the default keys below or namespaced keys per squad and pass their names with the `--*-env` flags:
163
+
164
+ ```
165
+ INSTAGRAM_ACCESS_TOKEN=
166
+ INSTAGRAM_USER_ID=
167
+ ```
168
+
169
+ #### INSTAGRAM_ACCESS_TOKEN
170
+
171
+ Pré-requisito: conta Instagram Business conectada a uma Página do Facebook, e um app criado em [developers.facebook.com](https://developers.facebook.com/) (tipo: **Empresa**).
172
+
173
+ **Para obter um token de longa duração (válido 60 dias):**
174
+
175
+ 1. Acesse seu app → **Graph API Explorer**
176
+ 2. No dropdown do topo, selecione seu app
177
+ 3. Clique em **"Gerar token de acesso"**
178
+ 4. Ative as permissões:
179
+ - `instagram_content_publish`
180
+ - `instagram_basic`
181
+ - `pages_read_engagement`
182
+ - `pages_manage_posts` # required for Facebook cross-posting
183
+ 5. Clique em **"Gerar token de acesso"** e autorize — você receberá um token de curta duração (1h)
184
+ 6. Converta para longa duração (60 dias) com este GET:
185
+ ```
186
+ https://graph.facebook.com/oauth/access_token
187
+ ?grant_type=fb_exchange_token
188
+ &client_id={APP_ID}
189
+ &client_secret={APP_SECRET}
190
+ &fb_exchange_token={TOKEN_CURTO}
191
+ ```
192
+ _(APP_ID e APP_SECRET: seu app → Configurações → Básico)_
193
+ 7. Copie o `access_token` da resposta e cole em `.env`
194
+
195
+ > O token expira em 60 dias. Repita o processo para renovar.
196
+
197
+ #### INSTAGRAM_USER_ID
198
+
199
+ 1. No Graph API Explorer (com o token acima), faça GET em:
200
+ ```
201
+ /me/accounts
202
+ ```
203
+ 2. Localize sua **Página do Facebook** na resposta e anote o `id`
204
+ 3. Faça GET em:
205
+ ```
206
+ /{page-id}?fields=instagram_business_account
207
+ ```
208
+ 4. Copie o `id` dentro de `instagram_business_account` — esse é o seu User ID
209
+
210
+ ## Available operations
211
+
212
+ - **Publish Carousel** -- Upload images and publish a carousel post to Instagram
213
+ - **Publish Carousel + Facebook** -- Same as above, plus cross-post to the connected Facebook Page (use `--facebook` flag)
214
+ - **Facebook-only Recovery** -- Publish only the Facebook cross-post after a partial result, without reposting to Instagram (use `--facebook --facebook-only`)
215
+ - **Dry Run** -- Test the full publishing flow without actually posting (use `--dry-run` flag)
216
+ - **Image Upload** -- Upload local JPG or JPEG images to imgBB (requires API key)
217
+ - **Status Check** -- Monitor media container processing status before publishing
218
+ - **Playwright Fallback Publish** -- Automates publishing carousel posts via headful browser automation when the Graph API is blocked
219
+
220
+ ## Facebook cross-post setup
221
+
222
+ To enable `--facebook`, add these variables to `.env`:
223
+
224
+ ```
225
+ FACEBOOK_PAGE_ID=your-facebook-page-id
226
+ FACEBOOK_PAGE_ACCESS_TOKEN=
227
+ ```
228
+
229
+ The token must have the `pages_manage_posts` permission and should be a valid Page Access Token.
230
+
231
+ If the stored token belongs to another Page in the same workspace, keep it only as a bootstrap credential and let the publisher resolve the final token for the target page dynamically via `/me/accounts`.