@aksp/opencrew 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +24 -0
  3. package/README.md +118 -0
  4. package/bin/opencrew.js +8 -0
  5. package/package.json +57 -0
  6. package/src/cli.js +70 -0
  7. package/src/commands/init.js +97 -0
  8. package/src/commands/update.js +58 -0
  9. package/src/lib/fsx.js +55 -0
  10. package/src/lib/ides.js +120 -0
  11. package/src/lib/paths.js +8 -0
  12. package/src/lib/prompts.js +35 -0
  13. package/src/lib/ui.js +20 -0
  14. package/templates/.env.example +23 -0
  15. package/templates/.mcp.json +8 -0
  16. package/templates/AGENTS.md +105 -0
  17. package/templates/_opencrew/.opencrew-version +1 -0
  18. package/templates/_opencrew/_investigations/.gitkeep +0 -0
  19. package/templates/_opencrew/_memory/company.md +4 -0
  20. package/templates/_opencrew/_memory/preferences.md +9 -0
  21. package/templates/_opencrew/config/playwright.config.json +11 -0
  22. package/templates/_opencrew/core/architect.agent.yaml +110 -0
  23. package/templates/_opencrew/core/best-practices/_catalog.yaml +116 -0
  24. package/templates/_opencrew/core/best-practices/blog-post.md +151 -0
  25. package/templates/_opencrew/core/best-practices/blog-seo.md +146 -0
  26. package/templates/_opencrew/core/best-practices/copywriting.md +446 -0
  27. package/templates/_opencrew/core/best-practices/data-analysis.md +420 -0
  28. package/templates/_opencrew/core/best-practices/email-newsletter.md +136 -0
  29. package/templates/_opencrew/core/best-practices/email-sales.md +127 -0
  30. package/templates/_opencrew/core/best-practices/image-design.md +365 -0
  31. package/templates/_opencrew/core/best-practices/instagram-feed.md +252 -0
  32. package/templates/_opencrew/core/best-practices/instagram-reels.md +128 -0
  33. package/templates/_opencrew/core/best-practices/instagram-stories.md +123 -0
  34. package/templates/_opencrew/core/best-practices/linkedin-article.md +133 -0
  35. package/templates/_opencrew/core/best-practices/linkedin-post.md +138 -0
  36. package/templates/_opencrew/core/best-practices/researching.md +366 -0
  37. package/templates/_opencrew/core/best-practices/review.md +286 -0
  38. package/templates/_opencrew/core/best-practices/social-networks-publishing.md +311 -0
  39. package/templates/_opencrew/core/best-practices/strategist.md +361 -0
  40. package/templates/_opencrew/core/best-practices/technical-writing.md +382 -0
  41. package/templates/_opencrew/core/best-practices/twitter-post.md +122 -0
  42. package/templates/_opencrew/core/best-practices/twitter-thread.md +139 -0
  43. package/templates/_opencrew/core/best-practices/whatsapp-broadcast.md +124 -0
  44. package/templates/_opencrew/core/best-practices/youtube-script.md +139 -0
  45. package/templates/_opencrew/core/best-practices/youtube-shorts.md +129 -0
  46. package/templates/_opencrew/core/prompts/build.prompt.md +547 -0
  47. package/templates/_opencrew/core/prompts/design.prompt.md +469 -0
  48. package/templates/_opencrew/core/prompts/discovery.prompt.md +269 -0
  49. package/templates/_opencrew/core/prompts/sherlock-instagram.md +123 -0
  50. package/templates/_opencrew/core/prompts/sherlock-linkedin.md +73 -0
  51. package/templates/_opencrew/core/prompts/sherlock-shared.md +684 -0
  52. package/templates/_opencrew/core/prompts/sherlock-twitter.md +78 -0
  53. package/templates/_opencrew/core/prompts/sherlock-youtube.md +85 -0
  54. package/templates/_opencrew/core/runner.pipeline.md +611 -0
  55. package/templates/_opencrew/core/skills.engine.md +388 -0
  56. package/templates/_opencrew/logs/.gitkeep +0 -0
  57. package/templates/crews/.gitkeep +0 -0
  58. package/templates/gitignore +8 -0
  59. package/templates/skills/apify/SKILL.md +55 -0
  60. package/templates/skills/blotato/SKILL.md +63 -0
  61. package/templates/skills/canva/SKILL.md +60 -0
  62. package/templates/skills/image-ai-generator/SKILL.md +124 -0
  63. package/templates/skills/image-ai-generator/scripts/generate.py +175 -0
  64. package/templates/skills/image-creator/SKILL.md +155 -0
  65. package/templates/skills/image-fetcher/SKILL.md +91 -0
  66. package/templates/skills/instagram-publisher/SKILL.md +119 -0
  67. package/templates/skills/instagram-publisher/scripts/publish.js +165 -0
  68. package/templates/skills/opencrew-best-practice-creator/SKILL.md +192 -0
  69. package/templates/skills/opencrew-skill-creator/SKILL.md +420 -0
  70. package/templates/skills/opencrew-skill-creator/agents/analyzer.md +274 -0
  71. package/templates/skills/opencrew-skill-creator/agents/comparator.md +202 -0
  72. package/templates/skills/opencrew-skill-creator/agents/grader.md +223 -0
  73. package/templates/skills/opencrew-skill-creator/assets/eval_review.html +146 -0
  74. package/templates/skills/opencrew-skill-creator/eval-viewer/generate_review.py +471 -0
  75. package/templates/skills/opencrew-skill-creator/eval-viewer/viewer.html +1325 -0
  76. package/templates/skills/opencrew-skill-creator/references/schemas.md +430 -0
  77. package/templates/skills/opencrew-skill-creator/references/skill-format.md +235 -0
  78. package/templates/skills/opencrew-skill-creator/scripts/__init__.py +0 -0
  79. package/templates/skills/opencrew-skill-creator/scripts/aggregate_benchmark.py +401 -0
  80. package/templates/skills/opencrew-skill-creator/scripts/quick_validate.py +103 -0
  81. package/templates/skills/opencrew-skill-creator/scripts/run_eval.py +310 -0
  82. package/templates/skills/opencrew-skill-creator/scripts/utils.py +47 -0
  83. package/templates/skills/resend/SKILL.md +80 -0
  84. package/templates/skills/template-designer/SKILL.md +208 -0
  85. package/templates/skills/template-designer/base-templates/model-a.html +27 -0
  86. package/templates/skills/template-designer/base-templates/model-b.html +31 -0
  87. package/templates/skills/template-designer/base-templates/model-c.html +42 -0
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python3
2
+ """Run trigger evaluation for a skill description.
3
+
4
+ Tests whether a skill's description causes Claude to trigger (read the skill)
5
+ for a set of queries. Outputs results as JSON.
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import select
12
+ import subprocess
13
+ import sys
14
+ import time
15
+ import uuid
16
+ from concurrent.futures import ProcessPoolExecutor, as_completed
17
+ from pathlib import Path
18
+
19
+ from scripts.utils import parse_skill_md
20
+
21
+
22
+ def find_project_root() -> Path:
23
+ """Find the project root by walking up from cwd looking for .claude/.
24
+
25
+ Mimics how Claude Code discovers its project root, so the command file
26
+ we create ends up where claude -p will look for it.
27
+ """
28
+ current = Path.cwd()
29
+ for parent in [current, *current.parents]:
30
+ if (parent / ".claude").is_dir():
31
+ return parent
32
+ return current
33
+
34
+
35
+ def run_single_query(
36
+ query: str,
37
+ skill_name: str,
38
+ skill_description: str,
39
+ timeout: int,
40
+ project_root: str,
41
+ model: str | None = None,
42
+ ) -> bool:
43
+ """Run a single query and return whether the skill was triggered.
44
+
45
+ Creates a command file in .claude/commands/ so it appears in Claude's
46
+ available_skills list, then runs `claude -p` with the raw query.
47
+ Uses --include-partial-messages to detect triggering early from
48
+ stream events (content_block_start) rather than waiting for the
49
+ full assistant message, which only arrives after tool execution.
50
+ """
51
+ unique_id = uuid.uuid4().hex[:8]
52
+ clean_name = f"{skill_name}-skill-{unique_id}"
53
+ project_commands_dir = Path(project_root) / ".claude" / "commands"
54
+ command_file = project_commands_dir / f"{clean_name}.md"
55
+
56
+ try:
57
+ project_commands_dir.mkdir(parents=True, exist_ok=True)
58
+ # Use YAML block scalar to avoid breaking on quotes in description
59
+ indented_desc = "\n ".join(skill_description.split("\n"))
60
+ command_content = (
61
+ f"---\n"
62
+ f"description: |\n"
63
+ f" {indented_desc}\n"
64
+ f"---\n\n"
65
+ f"# {skill_name}\n\n"
66
+ f"This skill handles: {skill_description}\n"
67
+ )
68
+ command_file.write_text(command_content)
69
+
70
+ cmd = [
71
+ "claude",
72
+ "-p", query,
73
+ "--output-format", "stream-json",
74
+ "--verbose",
75
+ "--include-partial-messages",
76
+ ]
77
+ if model:
78
+ cmd.extend(["--model", model])
79
+
80
+ # Remove CLAUDECODE env var to allow nesting claude -p inside a
81
+ # Claude Code session. The guard is for interactive terminal conflicts;
82
+ # programmatic subprocess usage is safe.
83
+ env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}
84
+
85
+ process = subprocess.Popen(
86
+ cmd,
87
+ stdout=subprocess.PIPE,
88
+ stderr=subprocess.DEVNULL,
89
+ cwd=project_root,
90
+ env=env,
91
+ )
92
+
93
+ triggered = False
94
+ start_time = time.time()
95
+ buffer = ""
96
+ # Track state for stream event detection
97
+ pending_tool_name = None
98
+ accumulated_json = ""
99
+
100
+ try:
101
+ while time.time() - start_time < timeout:
102
+ if process.poll() is not None:
103
+ remaining = process.stdout.read()
104
+ if remaining:
105
+ buffer += remaining.decode("utf-8", errors="replace")
106
+ break
107
+
108
+ ready, _, _ = select.select([process.stdout], [], [], 1.0)
109
+ if not ready:
110
+ continue
111
+
112
+ chunk = os.read(process.stdout.fileno(), 8192)
113
+ if not chunk:
114
+ break
115
+ buffer += chunk.decode("utf-8", errors="replace")
116
+
117
+ while "\n" in buffer:
118
+ line, buffer = buffer.split("\n", 1)
119
+ line = line.strip()
120
+ if not line:
121
+ continue
122
+
123
+ try:
124
+ event = json.loads(line)
125
+ except json.JSONDecodeError:
126
+ continue
127
+
128
+ # Early detection via stream events
129
+ if event.get("type") == "stream_event":
130
+ se = event.get("event", {})
131
+ se_type = se.get("type", "")
132
+
133
+ if se_type == "content_block_start":
134
+ cb = se.get("content_block", {})
135
+ if cb.get("type") == "tool_use":
136
+ tool_name = cb.get("name", "")
137
+ if tool_name in ("Skill", "Read"):
138
+ pending_tool_name = tool_name
139
+ accumulated_json = ""
140
+ else:
141
+ return False
142
+
143
+ elif se_type == "content_block_delta" and pending_tool_name:
144
+ delta = se.get("delta", {})
145
+ if delta.get("type") == "input_json_delta":
146
+ accumulated_json += delta.get("partial_json", "")
147
+ if clean_name in accumulated_json:
148
+ return True
149
+
150
+ elif se_type in ("content_block_stop", "message_stop"):
151
+ if pending_tool_name:
152
+ return clean_name in accumulated_json
153
+ if se_type == "message_stop":
154
+ return False
155
+
156
+ # Fallback: full assistant message
157
+ elif event.get("type") == "assistant":
158
+ message = event.get("message", {})
159
+ for content_item in message.get("content", []):
160
+ if content_item.get("type") != "tool_use":
161
+ continue
162
+ tool_name = content_item.get("name", "")
163
+ tool_input = content_item.get("input", {})
164
+ if tool_name == "Skill" and clean_name in tool_input.get("skill", ""):
165
+ triggered = True
166
+ elif tool_name == "Read" and clean_name in tool_input.get("file_path", ""):
167
+ triggered = True
168
+ return triggered
169
+
170
+ elif event.get("type") == "result":
171
+ return triggered
172
+ finally:
173
+ # Clean up process on any exit path (return, exception, timeout)
174
+ if process.poll() is None:
175
+ process.kill()
176
+ process.wait()
177
+
178
+ return triggered
179
+ finally:
180
+ if command_file.exists():
181
+ command_file.unlink()
182
+
183
+
184
+ def run_eval(
185
+ eval_set: list[dict],
186
+ skill_name: str,
187
+ description: str,
188
+ num_workers: int,
189
+ timeout: int,
190
+ project_root: Path,
191
+ runs_per_query: int = 1,
192
+ trigger_threshold: float = 0.5,
193
+ model: str | None = None,
194
+ ) -> dict:
195
+ """Run the full eval set and return results."""
196
+ results = []
197
+
198
+ with ProcessPoolExecutor(max_workers=num_workers) as executor:
199
+ future_to_info = {}
200
+ for item in eval_set:
201
+ for run_idx in range(runs_per_query):
202
+ future = executor.submit(
203
+ run_single_query,
204
+ item["query"],
205
+ skill_name,
206
+ description,
207
+ timeout,
208
+ str(project_root),
209
+ model,
210
+ )
211
+ future_to_info[future] = (item, run_idx)
212
+
213
+ query_triggers: dict[str, list[bool]] = {}
214
+ query_items: dict[str, dict] = {}
215
+ for future in as_completed(future_to_info):
216
+ item, _ = future_to_info[future]
217
+ query = item["query"]
218
+ query_items[query] = item
219
+ if query not in query_triggers:
220
+ query_triggers[query] = []
221
+ try:
222
+ query_triggers[query].append(future.result())
223
+ except Exception as e:
224
+ print(f"Warning: query failed: {e}", file=sys.stderr)
225
+ query_triggers[query].append(False)
226
+
227
+ for query, triggers in query_triggers.items():
228
+ item = query_items[query]
229
+ trigger_rate = sum(triggers) / len(triggers)
230
+ should_trigger = item["should_trigger"]
231
+ if should_trigger:
232
+ did_pass = trigger_rate >= trigger_threshold
233
+ else:
234
+ did_pass = trigger_rate < trigger_threshold
235
+ results.append({
236
+ "query": query,
237
+ "should_trigger": should_trigger,
238
+ "trigger_rate": trigger_rate,
239
+ "triggers": sum(triggers),
240
+ "runs": len(triggers),
241
+ "pass": did_pass,
242
+ })
243
+
244
+ passed = sum(1 for r in results if r["pass"])
245
+ total = len(results)
246
+
247
+ return {
248
+ "skill_name": skill_name,
249
+ "description": description,
250
+ "results": results,
251
+ "summary": {
252
+ "total": total,
253
+ "passed": passed,
254
+ "failed": total - passed,
255
+ },
256
+ }
257
+
258
+
259
+ def main():
260
+ parser = argparse.ArgumentParser(description="Run trigger evaluation for a skill description")
261
+ parser.add_argument("--eval-set", required=True, help="Path to eval set JSON file")
262
+ parser.add_argument("--skill-path", required=True, help="Path to skill directory")
263
+ parser.add_argument("--description", default=None, help="Override description to test")
264
+ parser.add_argument("--num-workers", type=int, default=10, help="Number of parallel workers")
265
+ parser.add_argument("--timeout", type=int, default=30, help="Timeout per query in seconds")
266
+ parser.add_argument("--runs-per-query", type=int, default=3, help="Number of runs per query")
267
+ parser.add_argument("--trigger-threshold", type=float, default=0.5, help="Trigger rate threshold")
268
+ parser.add_argument("--model", default=None, help="Model to use for claude -p (default: user's configured model)")
269
+ parser.add_argument("--verbose", action="store_true", help="Print progress to stderr")
270
+ args = parser.parse_args()
271
+
272
+ eval_set = json.loads(Path(args.eval_set).read_text())
273
+ skill_path = Path(args.skill_path)
274
+
275
+ if not (skill_path / "SKILL.md").exists():
276
+ print(f"Error: No SKILL.md found at {skill_path}", file=sys.stderr)
277
+ sys.exit(1)
278
+
279
+ name, original_description, content = parse_skill_md(skill_path)
280
+ description = args.description or original_description
281
+ project_root = find_project_root()
282
+
283
+ if args.verbose:
284
+ print(f"Evaluating: {description}", file=sys.stderr)
285
+
286
+ output = run_eval(
287
+ eval_set=eval_set,
288
+ skill_name=name,
289
+ description=description,
290
+ num_workers=args.num_workers,
291
+ timeout=args.timeout,
292
+ project_root=project_root,
293
+ runs_per_query=args.runs_per_query,
294
+ trigger_threshold=args.trigger_threshold,
295
+ model=args.model,
296
+ )
297
+
298
+ if args.verbose:
299
+ summary = output["summary"]
300
+ print(f"Results: {summary['passed']}/{summary['total']} passed", file=sys.stderr)
301
+ for r in output["results"]:
302
+ status = "PASS" if r["pass"] else "FAIL"
303
+ rate_str = f"{r['triggers']}/{r['runs']}"
304
+ print(f" [{status}] rate={rate_str} expected={r['should_trigger']}: {r['query'][:70]}", file=sys.stderr)
305
+
306
+ print(json.dumps(output, indent=2))
307
+
308
+
309
+ if __name__ == "__main__":
310
+ main()
@@ -0,0 +1,47 @@
1
+ """Shared utilities for skill-creator scripts."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+
7
+ def parse_skill_md(skill_path: Path) -> tuple[str, str, str]:
8
+ """Parse a SKILL.md file, returning (name, description, full_content)."""
9
+ content = (skill_path / "SKILL.md").read_text()
10
+ lines = content.split("\n")
11
+
12
+ if lines[0].strip() != "---":
13
+ raise ValueError("SKILL.md missing frontmatter (no opening ---)")
14
+
15
+ end_idx = None
16
+ for i, line in enumerate(lines[1:], start=1):
17
+ if line.strip() == "---":
18
+ end_idx = i
19
+ break
20
+
21
+ if end_idx is None:
22
+ raise ValueError("SKILL.md missing frontmatter (no closing ---)")
23
+
24
+ name = ""
25
+ description = ""
26
+ frontmatter_lines = lines[1:end_idx]
27
+ i = 0
28
+ while i < len(frontmatter_lines):
29
+ line = frontmatter_lines[i]
30
+ if line.startswith("name:"):
31
+ name = line[len("name:"):].strip().strip('"').strip("'")
32
+ elif line.startswith("description:"):
33
+ value = line[len("description:"):].strip()
34
+ # Handle YAML multiline indicators (>, |, >-, |-)
35
+ if value in (">", "|", ">-", "|-"):
36
+ continuation_lines: list[str] = []
37
+ i += 1
38
+ while i < len(frontmatter_lines) and (frontmatter_lines[i].startswith(" ") or frontmatter_lines[i].startswith("\t")):
39
+ continuation_lines.append(frontmatter_lines[i].strip())
40
+ i += 1
41
+ description = " ".join(continuation_lines)
42
+ continue
43
+ else:
44
+ description = value.strip('"').strip("'")
45
+ i += 1
46
+
47
+ return name, description, content
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: resend
3
+ description: >
4
+ Send emails through Resend's official MCP server.
5
+ Supports single send, batch send, HTML and plain text bodies,
6
+ attachments, CC/BCC, scheduling, and contact management.
7
+ description_pt-BR: >
8
+ Envie emails pelo servidor MCP oficial da Resend.
9
+ Suporta envio individual, envio em lote, corpo HTML e texto puro,
10
+ anexos, CC/BCC, agendamento e gerenciamento de contatos.
11
+ description_es: >
12
+ Enviar correos electrónicos a través del servidor MCP oficial de Resend.
13
+ Soporta envío individual, envío por lotes, cuerpo HTML y texto plano,
14
+ adjuntos, CC/BCC, programación y gestión de contactos.
15
+ type: mcp
16
+ version: "1.0.0"
17
+ mcp:
18
+ server_name: resend
19
+ command: npx
20
+ args: ["-y", "resend-mcp"]
21
+ transport: stdio
22
+ env:
23
+ - RESEND_API_KEY
24
+ categories: [email, automation, communication]
25
+ ---
26
+
27
+ # Resend — Email Skill
28
+
29
+ ## When to use
30
+
31
+ Use this skill when a crew needs to send emails — welcome messages, notifications,
32
+ reports, newsletters, or any transactional/marketing email. Resend handles delivery
33
+ so the crew only needs to compose the content and call the MCP tools.
34
+
35
+ ## Instructions
36
+
37
+ ### Sending a single email
38
+
39
+ 1. Prepare **from**, **to**, **subject**, and **body** (HTML or plain text).
40
+ 2. Call the Resend MCP `send_email` tool.
41
+ 3. Check the response for a successful `id` — that confirms the email was queued.
42
+
43
+ ### Sending a batch
44
+
45
+ 1. Build an array of email objects (same fields as single send).
46
+ 2. Call the Resend MCP `batch_send_emails` tool.
47
+ 3. Each item in the response will have its own `id` or error.
48
+
49
+ ### Attachments
50
+
51
+ Pass attachments as an array with `filename`, `path` (local file), `url`, or `content` (base64).
52
+
53
+ ### Scheduling
54
+
55
+ Include a `scheduled_at` field (ISO 8601 datetime) to schedule future delivery.
56
+
57
+ ## Best practices
58
+
59
+ - Validate **from** against a verified domain before sending — Resend rejects unverified senders.
60
+ - Keep subject lines under 80 characters for better deliverability.
61
+ - For batch sends, group by shared content to reduce payload size.
62
+ - Always check the response for errors and surface them to the user rather than silently failing.
63
+ - When composing HTML emails, keep the markup simple — most email clients ignore complex CSS.
64
+
65
+ ## Available operations
66
+
67
+ - **Send Email** — Single email with HTML/text body, attachments, CC/BCC, reply-to
68
+ - **Batch Send** — Multiple emails in one call
69
+ - **Schedule Email** — Queue an email for future delivery
70
+ - **List/Get Emails** — Check delivery status of sent emails
71
+ - **Cancel Email** — Cancel a scheduled email before it sends
72
+ - **Manage Contacts** — Create, list, update, and remove contacts from audiences
73
+ - **Manage Domains** — Add and verify sender domains
74
+
75
+ ## Setup
76
+
77
+ 1. Create a free account at [resend.com](https://resend.com)
78
+ 2. Generate an API key (starts with `re_`)
79
+ 3. Add a verified sender domain (or use Resend's shared `onboarding@resend.dev` for testing)
80
+ 4. Set `RESEND_API_KEY` in your `.env` file
@@ -0,0 +1,208 @@
1
+ ---
2
+ name: template-designer
3
+ description: Visual template selection for image design agents. Generates template variations, renders them as images for user review, and saves the approved visual identity.
4
+ description_pt-BR: >
5
+ Seleção de template visual para agentes de design de imagem. Gera variações de template,
6
+ renderiza como imagens para o usuário revisar e salva a identidade visual aprovada.
7
+ description_es: >
8
+ Selección de plantilla visual para agentes de diseño de imagen. Genera variaciones de
9
+ plantilla, las renderiza como imágenes para revisión del usuario y guarda la identidad
10
+ visual aprobada.
11
+ type: prompt
12
+ version: "2.0.0"
13
+ categories: [design, visual, templates]
14
+ ---
15
+
16
+ # Template Designer
17
+
18
+ Visual template selection and refinement for crew creation and editing.
19
+
20
+ ## When to Use
21
+
22
+ - During crew creation: when the Design phase identifies an image design agent and the user opts to choose a template
23
+ - During crew editing: when the user asks to define, edit, or change the visual identity / template of a design agent
24
+ - Trigger: presence of `image-creator` skill (or similar image-producing skill) in the crew's skill list
25
+
26
+ ## Prerequisites
27
+
28
+ - A crew with a design agent that produces images (uses `image-creator` skill)
29
+ - Crew's `_build/` directory must exist (created during Discovery/Design phases)
30
+ - `image-creator` skill installed (for rendering HTML to PNG)
31
+
32
+ ## How It Works
33
+
34
+ 1. You read context and base templates
35
+ 2. You generate 3 adapted HTML template variations
36
+ 3. You render each as a PNG image using the `image-creator` skill
37
+ 4. You present the image file paths to the user for review
38
+ 5. You iterate with feedback until approval
39
+ 6. You save the approved template as HTML reference + structured style rules
40
+
41
+ ## Generating Templates
42
+
43
+ ### Step 0: Read Design Guidelines (MANDATORY)
44
+
45
+ Before generating any template, read and internalize the design best practices:
46
+ - `_opencrew/core/best-practices/image-design.md` — **REQUIRED reading**. Contains platform-specific minimum font sizes, typography rules, spacing guidelines, color palette constraints, contrast requirements, and layout methodology. Every template you generate MUST comply with these rules.
47
+
48
+ Key rules to always follow:
49
+ - **Font sizes**: Hero 58px, Heading 43px, Body 34px, Caption 24px minimum for Instagram carousel (1080x1440). Absolute minimum 20px for any readable text on any platform.
50
+ - **Font weight**: 500 or higher for body text and above.
51
+ - **Colors**: Maximum 5 colors per design system (primary, secondary, accent, background, text).
52
+ - **Contrast**: WCAG AA minimum 4.5:1 for all text against background.
53
+ - **Layout**: CSS Grid or Flexbox only. No absolute positioning for primary content.
54
+ - **Self-contained HTML**: Inline CSS only. Only Google Fonts @import allowed as external resource.
55
+ - **No slide counters**: Never include "1/7" or similar. Instagram has native navigation.
56
+
57
+ You should also apply general web design best practices: proper white space, visual hierarchy through scale and weight, consistent spacing rhythm, and balanced composition.
58
+
59
+ ### HARD RULES — Dimensions and Typography
60
+
61
+ These rules are NON-NEGOTIABLE. Every template must comply:
62
+
63
+ **Fixed Dimensions (never use height: auto or flexible height):**
64
+ - Instagram Carousel: `width: 1080px; height: 1440px` (3:4 portrait)
65
+ - Instagram Story/Reel: `width: 1080px; height: 1920px` (9:16 portrait)
66
+ - Instagram Post: `width: 1080px; height: 1080px` (1:1 square)
67
+ - LinkedIn Post: `width: 1200px; height: 627px` (1.91:1 horizontal)
68
+
69
+ The root container of every template MUST set explicit `width` and `height` in pixels. The template must render at exactly these dimensions — no overflow, no scrolling, no flexible height.
70
+
71
+ **Minimum Font Sizes (Instagram at 1080px width):**
72
+ - Hero/Title: **58px** minimum
73
+ - Heading: **43px** minimum
74
+ - Body text: **34px** minimum
75
+ - Caption/small text: **24px** minimum
76
+ - Absolute minimum for ANY readable text on ANY platform: **20px**
77
+
78
+ **Font Weight:** 500 or higher for body text and above. Never use font-weight below 400 for any visible text.
79
+
80
+ Templates that violate these rules are rejected — no exceptions.
81
+
82
+ ### Step 1: Read Context
83
+
84
+ Read these files to understand the crew:
85
+ - `crews/{code}/_build/discovery.yaml` — platform, domain, tone, language
86
+ - `crews/{code}/_build/design.yaml` — agents, purpose, skills
87
+ - `crews/{code}/_investigations/consolidated-analysis.md` (if exists) — visual patterns from reference profiles
88
+ - `_opencrew/_memory/company.md` — company name, brand, industry, target audience
89
+ - `_opencrew/_memory/preferences.md` — user preferences (language, style, tone)
90
+
91
+ Use the company context and user preferences to adapt template content: example text should reflect the company's domain and audience, colors should align with brand if available, and language should match the user's Output Language preference.
92
+
93
+ ### Step 2: Read Base Templates
94
+
95
+ Read the 3 base templates from `skills/template-designer/base-templates/`:
96
+ - `model-a.html`
97
+ - `model-b.html`
98
+ - `model-c.html`
99
+
100
+ ### Step 3: Generate Adapted Variations
101
+
102
+ For each base template, create an adapted version:
103
+ - Adjust colors to match the crew's domain/brand (use Sherlock palette if available, company brand colors from company.md if available)
104
+ - Adjust typography following the platform-specific minimum font sizes from `image-design.md`
105
+ - Replace example content with domain-relevant content that reflects the company's industry, audience, and language
106
+ - Set the root container to the exact fixed dimensions from HARD RULES above. Never use percentage heights or auto heights.
107
+ - Add any visual elements that match the crew's personality
108
+ - Apply proper white space, visual hierarchy, and spacing rhythm per `image-design.md` methodology
109
+
110
+ Write each adapted template as a **complete, self-contained HTML file** (with `<!DOCTYPE html>`, inline CSS, and Google Fonts imports if needed).
111
+
112
+ Save to:
113
+ - `crews/{code}/_build/template-a.html`
114
+ - `crews/{code}/_build/template-b.html`
115
+ - `crews/{code}/_build/template-c.html`
116
+
117
+ ### Step 4: Render as Images
118
+
119
+ Use the `image-creator` skill to render each HTML template as a PNG image:
120
+
121
+ 1. Read `skills/image-creator/SKILL.md` for rendering instructions
122
+ 2. Render each template HTML to PNG using the image-creator workflow
123
+ 3. Save rendered images to:
124
+ - `crews/{code}/_build/template-a.png`
125
+ - `crews/{code}/_build/template-b.png`
126
+ - `crews/{code}/_build/template-c.png`
127
+
128
+ ### Step 5: Present to User
129
+
130
+ Present the 3 template options to the user using **clickable markdown links with absolute file paths** so they can open and review:
131
+
132
+ > "Here are 3 template options for your crew's visual identity:
133
+ >
134
+ > - [preview-a.png]({absolute_path}/crews/{code}/_build/template-a.png) — Template A
135
+ > - [preview-b.png]({absolute_path}/crews/{code}/_build/template-b.png) — Template B
136
+ > - [preview-c.png]({absolute_path}/crews/{code}/_build/template-c.png) — Template C
137
+ >
138
+ > Click the links above to open each image. Tell me which one you prefer. I can also mix elements from different templates or adjust colors, fonts, and layout."
139
+
140
+ **Important:** Always use the full absolute path (e.g., `d:\Coding Projects\opencrew\crews\my-crew\_build\template-a.png`) inside the markdown link — relative paths are not clickable in the IDE.
141
+
142
+ ## Iteration Loop
143
+
144
+ 1. Present template images with clickable markdown links using absolute file paths
145
+ 2. Wait for user feedback in terminal
146
+ 3. Generate new version based on feedback — save as `template-v2.html`, render as `template-v2.png`, etc.
147
+ 4. Present updated image with clickable markdown link using absolute file path
148
+ 5. Repeat until user approves
149
+
150
+ ## Saving the Approved Template
151
+
152
+ When the user approves, create two files:
153
+
154
+ ### 1. Template Reference HTML
155
+
156
+ Save to: `crews/{code}/pipeline/data/template-reference.html`
157
+
158
+ The complete, self-contained HTML/CSS of the approved template at full resolution (e.g., 1080x1440). This is the literal example the design agent will use.
159
+
160
+ ### 2. Visual Identity Rules
161
+
162
+ Save to: `crews/{code}/pipeline/data/visual-identity.md`
163
+
164
+ Extract structured rules from the approved template:
165
+
166
+ ~~~markdown
167
+ # Visual Identity
168
+
169
+ ## Color Palette
170
+ - **Primary:** #HEXCODE — usage description
171
+ - **Secondary:** #HEXCODE — usage description
172
+ - **Background:** #HEXCODE
173
+ - **Text:** #HEXCODE
174
+ - **Accent:** #HEXCODE — usage description
175
+
176
+ ## Typography
177
+ - **Headings:** Font Family, weight, size range
178
+ - **Body:** Font Family, weight, size range
179
+ - **Caption:** Font Family, weight, size range
180
+ - **Minimum sizes:** body 32px, caption 24px, heading 48px
181
+
182
+ ## Layout
183
+ - **Viewport:** WIDTHxHEIGHT px
184
+ - **Padding:** value
185
+ - **Grid:** description
186
+ - **Spacing rules:** description
187
+
188
+ ## Composition Rules
189
+ - Logo/profile placement: description
190
+ - Image treatment: description
191
+ - Visual hierarchy: description
192
+ - Footer/CTA pattern: description
193
+
194
+ ## Adaptation Rules
195
+ - How to handle different viewport sizes
196
+ - What stays fixed vs. what adapts
197
+ - Color usage rules (when to use primary vs accent)
198
+ ~~~
199
+
200
+ ### 3. Update Crew Files
201
+
202
+ If the crew is being created (Build phase hasn't run yet):
203
+ - The design.yaml context now includes the template data — Build will pick it up
204
+
205
+ If the crew already exists (editing flow):
206
+ - Add `pipeline/data/template-reference.html` and `pipeline/data/visual-identity.md` to `crew.yaml` `data:` list
207
+ - Update the design agent's `.agent.md` to reference both files
208
+ - Update the design agent's tasks to include the rule: "always follow visual-identity.md and use template-reference.html as the base model"
@@ -0,0 +1,27 @@
1
+ <!-- Model A: Twitter Editorial — approved 2026-03-28 -->
2
+ <!-- Style: Black background, tweet-style layout with avatar, verified badge, editorial text, contextual image -->
3
+ <!-- Fonts: Inter (body), weight 400-700 -->
4
+ <!-- Colors: #000 bg, #fff text, #1D9BF0 verified, #71767B muted, #2a2a2a borders/avatar, #1a1a2e image gradient -->
5
+ <div style="width:1080px;height:1440px;background:#000;color:#fff;font-family:'Inter',sans-serif;display:flex;flex-direction:column;padding:72px;gap:0;position:relative;overflow:hidden;">
6
+ <!-- Tweet header -->
7
+ <div style="display:flex;gap:24px;align-items:center;">
8
+ <div style="width:80px;height:80px;border-radius:50%;background:#2a2a2a;flex-shrink:0;"></div>
9
+ <div>
10
+ <div style="display:flex;align-items:center;gap:8px;">
11
+ <span style="font-weight:700;font-size:34px;">Nome Sobrenome</span>
12
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="#1D9BF0"><path d="M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.34 2.19c-1.39-.46-2.9-.2-3.91.81s-1.27 2.52-.81 3.91C2.63 9.33 1.75 10.57 1.75 12s.88 2.67 2.19 3.34c-.46 1.39-.2 2.9.81 3.91s2.52 1.27 3.91.81c.66 1.31 1.91 2.19 3.34 2.19s2.67-.88 3.34-2.19c1.39.46 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.08 4.71c-.22.22-.58.22-.8 0L7.4 13.74c-.22-.22-.22-.58 0-.8.22-.22.58-.22.8 0l2.57 2.57 6.03-6.03c.22-.22.58-.22.8 0 .22.22.22.58 0 .8l-6.43 6.43z"/></svg>
13
+ </div>
14
+ <span style="font-size:28px;color:#71767B;">@username</span>
15
+ </div>
16
+ </div>
17
+ <!-- Tweet text -->
18
+ <div style="font-size:38px;line-height:1.45;font-weight:400;margin-top:48px;">
19
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.
20
+ <br><br>
21
+ Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit. Neque porro quisquam est qui dolorem.
22
+ </div>
23
+ <!-- Image -->
24
+ <div style="width:100%;height:35%;background:linear-gradient(180deg,#1a1a2e,#0d0d0d);border-radius:24px;display:flex;align-items:center;justify-content:center;border:1px solid #2a2a2a;flex-shrink:0;margin-top:auto;">
25
+ <span style="color:#555;font-size:34px;">Imagem contextual</span>
26
+ </div>
27
+ </div>