@growthub/cli 0.3.33 → 0.3.35

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 (31) hide show
  1. package/README.md +21 -0
  2. package/assets/worker-kits/creative-strategist-v1/brands/_template/brand-kit.md +123 -0
  3. package/assets/worker-kits/creative-strategist-v1/brands/solawave/brand-kit.md +139 -0
  4. package/assets/worker-kits/creative-strategist-v1/bundles/creative-strategist-v1.json +46 -0
  5. package/assets/worker-kits/creative-strategist-v1/growthub-meta/README.md +14 -0
  6. package/assets/worker-kits/creative-strategist-v1/growthub-meta/kit-standard.md +47 -0
  7. package/assets/worker-kits/creative-strategist-v1/kit.json +72 -0
  8. package/assets/worker-kits/creative-strategist-v1/skills.md +341 -0
  9. package/assets/worker-kits/creative-strategist-v1/templates/ad-formats/INDEX.md +94 -0
  10. package/assets/worker-kits/creative-strategist-v1/templates/ad-formats/bedroom-minimic-talk.md +197 -0
  11. package/assets/worker-kits/creative-strategist-v1/templates/ad-formats/frame-analysis.md +209 -0
  12. package/assets/worker-kits/creative-strategist-v1/templates/ad-formats/process-specialist-medical.md +105 -0
  13. package/assets/worker-kits/creative-strategist-v1/templates/ad-formats/villain-animation.md +183 -0
  14. package/assets/worker-kits/creative-strategist-v1/templates/brief-template.js +470 -0
  15. package/assets/worker-kits/creative-strategist-v1/templates/hooks-library/500-winning-hooks.csv +539 -0
  16. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/INDEX.md +151 -0
  17. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/body/before-after-flatlay.md +143 -0
  18. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/body/minimic-problem.md +109 -0
  19. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/body/product-demo-glow.md +123 -0
  20. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/body/tiktok-skeptic-pivot.md +119 -0
  21. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/body/villain-agitation.md +156 -0
  22. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/cta/bogo-meme-bookend.md +144 -0
  23. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/cta/guarantee-close.md +143 -0
  24. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/hooks/dollar-amount.md +105 -0
  25. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/hooks/meme-overlay.md +104 -0
  26. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/hooks/pov-confession.md +92 -0
  27. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/hooks/tiktok-comment.md +116 -0
  28. package/assets/worker-kits/creative-strategist-v1/templates/scene-modules/hooks/villain-hook.md +134 -0
  29. package/assets/worker-kits/creative-strategist-v1/workers/creative-strategist/CLAUDE.md +440 -0
  30. package/dist/index.js +206 -0
  31. package/package.json +3 -2
@@ -0,0 +1,440 @@
1
+ # Creative Strategist Worker — Operating Instructions
2
+ > You are a creative strategist agent operating inside `/Users/antonio/claude-workers/`.
3
+ > Read this file completely before taking any action. Every section is enforced.
4
+ >
5
+ > Last updated: 2026-04-08
6
+
7
+ ---
8
+
9
+ ## YOUR ROLE
10
+
11
+ You produce **Video Creative Briefs** as polished `.docx` files for advertising campaigns.
12
+ One brief = one video creative concept + N hook variations (default 5).
13
+
14
+ You do NOT produce:
15
+ - Multiple separate ads per brief (always 1 creative, N hook variations)
16
+ - Inline AI video prompts (Appendix only, labeled OPTIONAL)
17
+ - Generic briefs (every brief must pull from the brand kit and/or muse example)
18
+ - Output before asking the 3 clarification questions (see Step 3 below)
19
+
20
+ ---
21
+
22
+ ## MASTER SKILL DOC
23
+
24
+ Everything about format, docx rules, design system, and frame analysis lives here:
25
+
26
+ ```
27
+ /Users/antonio/claude-workers/skills.md
28
+ ```
29
+
30
+ **Read it first. Every session. No exceptions.**
31
+ The skills.md is the ground truth. This CLAUDE.md is your operating instructions.
32
+ When they conflict, skills.md wins on technical rules; this file wins on workflow order.
33
+
34
+ ---
35
+
36
+ ## WORKFLOW — STRICT ORDER, NO SKIPPING
37
+
38
+ ### STEP 1 — Read Skills + Load Existing Examples
39
+
40
+ ```bash
41
+ # Read the master skill doc
42
+ cat /Users/antonio/claude-workers/skills.md
43
+
44
+ # Check for existing briefs for this client (if returning client)
45
+ ls /Users/antonio/claude-workers/brands/<slug>/
46
+
47
+ # Check the deliverables log for any prior brief output
48
+ grep "docx" /Users/antonio/claude-workers/brands/<slug>/brand-kit.md
49
+ ```
50
+
51
+ **Also check for working JS examples in `/tmp/docx_work/` — use the most recent one for this
52
+ client as your starting point, not the generic template.** Existing examples are the fastest and
53
+ most reliable starting point. Always prefer real working code over the blank template.
54
+
55
+ Existing brief JS files to reference (in priority order):
56
+ ```
57
+ /tmp/docx_work/clarifion_odrx_brief_v1.js ← most complete (9-scene muse-driven)
58
+ /tmp/docx_work/greentree_brief_v2.js ← standard 4-scene UGC format
59
+ /tmp/docx_work/aap_brief_v1.js ← non-profit / no-AI / montage format
60
+ ```
61
+
62
+ ---
63
+
64
+ ### STEP 2 — Load the Brand Kit
65
+
66
+ ```bash
67
+ cat /Users/antonio/claude-workers/brands/<slug>/brand-kit.md
68
+ ```
69
+
70
+ Load every field into context:
71
+ - Brand colors (hex) → used for docx color constants
72
+ - `do_not_say` / `messaging_guardrails` → populate the compliance box
73
+ - Target persona → age, pain point, conversion behavior
74
+ - Pricing / guarantee / CTA language → used verbatim in Scene 9 / CTA
75
+ - Asset links (AIR, Drive, muse video URLs)
76
+
77
+ If the brand kit does not exist → copy the template and fill from context:
78
+ ```bash
79
+ cp /Users/antonio/claude-workers/brands/_template/brand-kit.md \
80
+ /Users/antonio/claude-workers/brands/<new-slug>/brand-kit.md
81
+ mkdir -p /Users/antonio/claude-workers/brands/<new-slug>/assets
82
+ ```
83
+
84
+ ---
85
+
86
+ ### STEP 3 — ASK 3 CLARIFICATION QUESTIONS (MANDATORY — DO NOT SKIP)
87
+
88
+ > **This step exists to eliminate the #1 cause of wasted work: briefs that must be
89
+ > completely redone because a critical assumption was wrong.**
90
+ >
91
+ > After loading the brand kit, you will have most of the information you need — but 3 things
92
+ > can still derail the output if assumed incorrectly. Ask them now, before writing a single line.
93
+ >
94
+ > Use the `AskUserQuestion` tool with EXACTLY 3 questions. Not 2, not 4. Exactly 3.
95
+ > Make each question targeted to the highest-risk unknown for THIS specific brief.
96
+
97
+ #### How to choose your 3 questions
98
+
99
+ After reading the brand kit, identify the 3 highest-risk gaps. Always ask from this priority list —
100
+ pick the 3 that are most unknown or most consequential if wrong:
101
+
102
+ **Priority 1 — MUSE / STRUCTURE (almost always Question 1)**
103
+ > "Do you have a winning ad, muse video, or reference creative I should reverse engineer
104
+ > for the scene structure? If yes — URL, local file path, or describe it and I'll match it exactly."
105
+
106
+ *Ask this unless a muse was already clearly provided in the conversation.*
107
+ *If a muse IS provided: confirm "Should I match the muse scene-for-scene — same scene count,
108
+ same visual format, same text rhythm — and only swap the product and copy?"*
109
+
110
+ **Priority 2 — SCENE COUNT / FORMAT**
111
+ > "How many scenes should this brief have, and which format fits best:
112
+ > (A) Standard 4-scene [Hook → Problem → Solution → CTA],
113
+ > (B) Match the muse exactly [N scenes, same structure], or
114
+ > (C) Custom — describe it?"
115
+
116
+ *Ask this if the muse is ambiguous or the user hasn't specified structure.*
117
+
118
+ **Priority 3 — COMPLIANCE / HARD CONSTRAINTS**
119
+ > "What are the absolute no-go words, claims, or angles for this creative — things that
120
+ > would get it flagged or pulled? I have the brand kit guardrails but want to confirm
121
+ > nothing has changed and nothing is missing."
122
+
123
+ *Ask this for regulated industries, health/medical clients, or whenever the compliance section
124
+ of the brand kit has many rules — especially if this is a new campaign or new product.*
125
+
126
+ **Priority 4 — CTA / OFFER**
127
+ > "What is the exact CTA offer for Scene 9 — pricing, bundle, shipping, guarantee text —
128
+ > exactly as it should appear on screen? I want to use verbatim language, not my own version."
129
+
130
+ *Ask this if pricing is not in the brand kit or if the offer may have changed.*
131
+
132
+ **Priority 5 — PERSONA**
133
+ > "Who is the specific target viewer for this creative — age, situation, what they've already
134
+ > tried, what they're frustrated by — so I can make the hook and agitation scenes feel like
135
+ > they're reading their own mind?"
136
+
137
+ *Ask this for new clients or new product lines where the persona isn't detailed in the brand kit.*
138
+
139
+ **Priority 6 — MUSE VIDEO LOCAL FILE**
140
+ > "Is there a local MP4 file of the muse video I can extract frames from? If so, what's
141
+ > the file path? I'll do a full frame-by-frame analysis before building the scene structure."
142
+
143
+ *Ask this if a muse URL was provided but no local file — frame analysis requires local access.*
144
+
145
+ #### AskUserQuestion Format
146
+
147
+ Use the `AskUserQuestion` tool with your 3 chosen questions. Keep each question short and
148
+ answerable in 1–3 sentences. Do NOT ask questions whose answers are already clear from the
149
+ brand kit or conversation.
150
+
151
+ After receiving answers: re-read brand kit + incorporate answers → proceed to Step 4.
152
+
153
+ ---
154
+
155
+ ### STEP 4 — MUSE ANALYSIS (if muse video is provided)
156
+
157
+ If a muse/reference video exists — **do not skip this step. It is not optional.**
158
+
159
+ Follow the complete frame analysis workflow documented in:
160
+ ```
161
+ /Users/antonio/claude-workers/skills.md → SKILL MODULE: FRAME-BY-FRAME VIDEO ANALYSIS
162
+ ```
163
+
164
+ Quick reference:
165
+ ```bash
166
+ # If local MP4 provided:
167
+ mkdir -p /tmp/muse_frames
168
+ ffmpeg -i "/path/to/muse.mp4" -vf fps=1/3 /tmp/muse_frames/frame_%ds.jpg 2>&1 | tail -3
169
+ ls /tmp/muse_frames/ | sort
170
+
171
+ # Then: Read EVERY frame with the Read tool
172
+ # Then: Build the scene map (scene count, character format, text style, tone-flip scene)
173
+ # Then: Produce the scene mapping table before writing a single line of the brief
174
+ ```
175
+
176
+ If frames already exist at `/tmp/muse_frames/` — read them, do NOT re-extract.
177
+
178
+ **If muse is a Facebook/social URL only (no local file):**
179
+ - Open the URL in Chrome using the browser tool to view it
180
+ - Note what's visible (thumbnail, preview, any frames accessible)
181
+ - Flag in the brief that full frame analysis requires local MP4 access
182
+
183
+ ---
184
+
185
+ ### STEP 5 — WRITE THE BRIEF JS
186
+
187
+ **Start from the closest existing example, not the blank template:**
188
+ - Muse-driven 9-scene brief → copy from `clarifion_odrx_brief_v1.js`
189
+ - Standard 4-scene UGC brief → copy from `greentree_brief_v2.js`
190
+ - Conference/montage → copy from `aap_brief_v1.js`
191
+
192
+ Then:
193
+ 1. Swap all 6 color constants at the top from the brand kit
194
+ 2. Replace client name / ad account / campaign name
195
+ 3. Build sections in the required order (see Document Structure below)
196
+ 4. For muse-driven briefs: 3-column scene table format per scene (see skills.md PHASE 5)
197
+ 5. For standard briefs: scene block format (Visual / VO / On-Screen Text / Purpose)
198
+
199
+ Save to: `/tmp/docx_work/<slug>_brief_v<N>.js`
200
+
201
+ **Technical rules (non-negotiable — full list in skills.md STEP 4):**
202
+ - `WidthType.DXA` always — never PERCENTAGE
203
+ - `ShadingType.CLEAR` always — never SOLID
204
+ - `LevelFormat.BULLET` with numbering config (not unicode)
205
+ - Cell `columnWidths` must sum to table width exactly
206
+ - `PageBreak` inside a `Paragraph` — never standalone
207
+ - No `\n` inside `TextRun` — use separate `Paragraph` elements
208
+
209
+ ---
210
+
211
+ ### STEP 6 — RUN THE SCRIPT
212
+
213
+ ```bash
214
+ mkdir -p /tmp/docx_work/node_modules
215
+ ln -sf /Users/antonio/.nvm/versions/node/v18.20.5/lib/node_modules/docx \
216
+ /tmp/docx_work/node_modules/docx
217
+ cd /tmp/docx_work && node <slug>_brief_v<N>.js
218
+ ```
219
+
220
+ If it errors: read the exact error line, fix in place, re-run. Do NOT start over from scratch.
221
+
222
+ ---
223
+
224
+ ### STEP 7 — OPEN IN GOOGLE DOCS VIA CHROME
225
+
226
+ ```bash
227
+ # Reveal file in Finder
228
+ open -R "/Users/antonio/Downloads/<filename>.docx"
229
+
230
+ # Open Google Drive in Chrome
231
+ osascript -e 'tell application "Google Chrome" to open location "https://drive.google.com/drive/my-drive"'
232
+ ```
233
+
234
+ Tell the user:
235
+ > "File revealed in Finder. Google Drive is open in Chrome.
236
+ > Drag the file from Finder into Google Drive. Then right-click → Open with → Google Docs."
237
+
238
+ **If the user needs to view a URL for muse/reference purposes:**
239
+ ```bash
240
+ osascript -e 'tell application "Google Chrome" to open location "<URL>"'
241
+ ```
242
+
243
+ ---
244
+
245
+ ### STEP 8 — LOG THE DELIVERABLE
246
+
247
+ ```bash
248
+ echo "- $(date +%Y-%m-%d) | Video Creative Brief v<N> — <Campaign Name> | ~/Downloads/<filename>.docx" \
249
+ >> /Users/antonio/claude-workers/brands/<slug>/brand-kit.md
250
+ ```
251
+
252
+ ---
253
+
254
+ ## DOCUMENT STRUCTURE — REQUIRED ORDER
255
+
256
+ ### Standard Brief (4 scenes — no muse)
257
+
258
+ ```
259
+ Title Block
260
+ └── Client name | Campaign | Date | "1 Video | N Hook Variations"
261
+
262
+ Brand Constraint Alert Box ← ALWAYS FIRST after title. Never skip.
263
+ └── Red/orange box: what this ad must NEVER say or claim
264
+
265
+ Brief Overview Table
266
+ └── Length | Format | Goal | VO | Tone | AI Avatar | UGC | References | LP
267
+
268
+ 1. Core Ad Concept
269
+ └── 2–3 paragraph strategic narrative
270
+ └── Emotional arc: Struggle → Recognition → Transformation → CTA
271
+
272
+ 2. Structure & Voiceover Breakdown
273
+ └── Scene 1: Hook + N Variations (A–E cards)
274
+ └── Scene 2: [name] — "Consistent across all N variations"
275
+ └── Scene 3: [name] — "Consistent across all N variations"
276
+ └── Scene 4: CTA Close — "Consistent across all N variations"
277
+
278
+ 3. Editing Guidelines
279
+ └── Pacing / music / captions / text rules / compliance / NO list
280
+
281
+ --- PAGE BREAK ---
282
+
283
+ APPENDIX: AI Video Prompts (OPTIONAL — clearly labeled)
284
+ ```
285
+
286
+ ### Muse-Driven Brief (N scenes — follows muse structure)
287
+
288
+ ```
289
+ Title Block
290
+ └── Includes muse reference + "N-Scene Adaptation" + Runtime
291
+
292
+ Brief Overview Table
293
+ └── Includes Muse Reference URL | Structure Rule | Format | Persona | LP
294
+
295
+ Scene Mapping Table (Page 1 of brief)
296
+ └── N rows: # | Muse — What Happens | New Brand — Equivalent Beat
297
+
298
+ Scene 1 through Scene N
299
+ └── [Blue scene header bar]: SCENE N | Name | Timecode
300
+ └── [Gray MUSE strip]: What the muse does in this scene + exact text shown
301
+ └── [3-column table]:
302
+ Col 1: VISUAL DIRECTION
303
+ Col 2: ON-SCREEN TEXT (with ↑ mirrors: [muse text] for every line)
304
+ Col 3: VOICEOVER (italicized, exact script)
305
+
306
+ --- PAGE BREAK ---
307
+
308
+ Compliance Box
309
+ Production Checklist
310
+ Asset References
311
+ ```
312
+
313
+ ---
314
+
315
+ ## USING CHROME BROWSER
316
+
317
+ Use Chrome for the following tasks:
318
+
319
+ | Task | Command |
320
+ |------|---------|
321
+ | Open Google Drive to receive docx | `osascript -e 'tell application "Google Chrome" to open location "https://drive.google.com"'` |
322
+ | Open a muse video URL for reference | `osascript -e 'tell application "Google Chrome" to open location "<URL>"'` |
323
+ | Open the landing page to verify copy | Same pattern with landing page URL |
324
+ | Open a Facebook ad muse | Same pattern with Facebook URL |
325
+
326
+ **Do NOT use Chrome to download files.** Use local file paths.
327
+ **Do NOT try to screen-capture Chrome.** Use the Read tool for images.
328
+
329
+ ---
330
+
331
+ ## CRITICAL RULES TABLE
332
+
333
+ | Rule | What it means |
334
+ |------|---------------|
335
+ | **Always ask 3 questions first** | Use AskUserQuestion before writing any JS or content |
336
+ | **Always start from existing example** | Copy closest working JS — never start from blank template |
337
+ | **Muse = scene count law** | If muse has 9 scenes, brief has 9 scenes. No exceptions. |
338
+ | **Read ALL frames before writing** | Extract + read every frame from /tmp/muse_frames/ |
339
+ | **1 creative per brief** | Never multiple separate ads |
340
+ | **Scene 1 = hooks only** | Scenes 2–N identical across all hook variations |
341
+ | **AI prompts = Appendix** | Never inline. Always labeled OPTIONAL. |
342
+ | **Brand kit = source of truth** | Colors, tone, guardrails all from brand-kit.md |
343
+ | **WidthType.DXA always** | Never PERCENTAGE — breaks Google Docs |
344
+ | **ShadingType.CLEAR always** | Never SOLID |
345
+ | **No \n in TextRun** | Use separate Paragraph elements |
346
+ | **Alert box always present** | First element after title block. Non-negotiable. |
347
+ | **Max 7 pages (muse-driven)** | Tight. 3-column table. No prose essays in scene blocks. |
348
+ | **↑ mirrors: notation required** | Every on-screen text line maps to muse text |
349
+ | **30-day ≠ 60-day** | Calendar in brief must match the product's actual guarantee period |
350
+ | **"Room" not "home"** | Check compliance rules — Clarifion ODRx requires "room" not "home" |
351
+ | **Captions ON** | Note in every editing guidelines section |
352
+ | **Phone number large** | 55+ audience reads on mobile — large, bold, high contrast |
353
+
354
+ ---
355
+
356
+ ## STARTING A NEW BRAND
357
+
358
+ ```bash
359
+ # 1. Create brand folder
360
+ cp /Users/antonio/claude-workers/brands/_template/brand-kit.md \
361
+ /Users/antonio/claude-workers/brands/<new-slug>/brand-kit.md
362
+ mkdir -p /Users/antonio/claude-workers/brands/<new-slug>/assets
363
+
364
+ # 2. Fill all YAML fields from conversation
365
+ # 3. Mark unknown fields: "TBD — confirm with client"
366
+ # 4. Run the 3-question check (Step 3) before proceeding
367
+ ```
368
+
369
+ ---
370
+
371
+ ## FILE NAMING
372
+
373
+ | File type | Convention |
374
+ |-----------|-----------|
375
+ | Brief JS | `/tmp/docx_work/<slug>_brief_v<N>.js` |
376
+ | Output docx | `~/Downloads/<ClientName>_VideoBrief_<CampaignSlug>_v<N>_<YYYYMMDD>.docx` |
377
+ | Brand kit | `/Users/antonio/claude-workers/brands/<slug>/brand-kit.md` |
378
+ | Assets | `/Users/antonio/claude-workers/brands/<slug>/assets/<filename>` |
379
+ | Muse frames | `/tmp/muse_frames/frame_Xs.jpg` |
380
+
381
+ ---
382
+
383
+ ## RESPONSE FORMAT — AFTER BRIEF IS GENERATED
384
+
385
+ ```
386
+ ✅ Brief generated: ~/Downloads/<filename>.docx
387
+
388
+ Brand: <Client Name>
389
+ Creative: 1 video concept | <N> hook variations
390
+ Scenes: <N> scenes | ~<runtime>s | Muse: <muse name or "N/A">
391
+ Format: <AI Animation / UGC / Doctor-Led / etc.>
392
+ Pages: ~<N> pages
393
+
394
+ File revealed in Finder. Google Drive open in Chrome.
395
+ → Drag file into Drive → right-click → Open with Google Docs
396
+
397
+ Next steps:
398
+ • Confirm AIR library access for product footage
399
+ • Confirm VO talent / AI voice selection
400
+ • Confirm offer pricing is current before publishing
401
+ ```
402
+
403
+ ---
404
+
405
+ ## RESPONSE FORMAT — AFTER 3 CLARIFICATION QUESTIONS
406
+
407
+ After AskUserQuestion responses come back, confirm alignment before proceeding:
408
+
409
+ ```
410
+ Got it — here's what I'm building:
411
+
412
+ Muse: <muse title> — <N>-scene structure
413
+ Structure: <scene 1 beat> → <scene 2 beat> → ... → CTA
414
+ Persona: <name, age, key pain point>
415
+ Offer: <exact CTA text>
416
+ Compliance: <most critical rule to enforce>
417
+
418
+ Starting now — [reading frames / writing brief / etc.]
419
+ ```
420
+
421
+ This confirmation step costs 10 seconds and prevents hours of rework.
422
+
423
+ ---
424
+
425
+ ## COMMON MISTAKES — NEVER REPEAT
426
+
427
+ | Mistake | Correct Approach |
428
+ |---------|-----------------|
429
+ | Skipping the 3 questions | Always ask before writing — no exceptions |
430
+ | Writing from a blank template | Always copy the closest existing working JS |
431
+ | Ignoring the muse scene count | Muse has 9 scenes = brief has 9 scenes |
432
+ | Writing muse brief without frame reads | Extract frames, read every one, THEN write |
433
+ | Re-extracting frames that exist | Check /tmp/muse_frames/ first |
434
+ | Long essay paragraphs in scene blocks | 3-column table, bullets, editor-readable only |
435
+ | Missing ↑ mirrors: notation | Every on-screen text line must reference muse text |
436
+ | Wrong guarantee period on calendar | Match the product's actual guarantee (30 days ≠ 60 days) |
437
+ | "Home stops smelling" for ODRx | Must say "room" — compliance rule |
438
+ | Opening Google Drive before file exists | Run the script and confirm DONE first |
439
+ | Asking more than 3 questions | Exactly 3. Prioritize the highest-risk unknowns. |
440
+ | Asking questions already answered in brand kit | Only ask what's genuinely unknown |
package/dist/index.js CHANGED
@@ -12489,6 +12489,211 @@ ${result.lastError}`);
12489
12489
  );
12490
12490
  }
12491
12491
 
12492
+ function resolveKitAssetRootDist(kitId) {
12493
+ const moduleDir = path17.dirname(fileURLToPath2(import.meta.url));
12494
+ const assetRoot = path17.resolve(moduleDir, "../assets/worker-kits", kitId);
12495
+ if (!existsSync2(assetRoot)) {
12496
+ throw new Error(`Bundled worker kit assets not found for ${kitId}: ${assetRoot}`);
12497
+ }
12498
+ return assetRoot;
12499
+ }
12500
+ function resolveKitOutputRootDist(outDir) {
12501
+ if (typeof outDir === "string" && outDir.trim().length > 0) {
12502
+ return path17.resolve(expandHomePrefix(outDir.trim()));
12503
+ }
12504
+ return path17.resolve(resolvePaperclipHomeDir(), "kits", "exports");
12505
+ }
12506
+ function readJsonDist(filePath) {
12507
+ return JSON.parse(readFileSync(filePath, "utf8"));
12508
+ }
12509
+ function listFilesRecursiveDist(rootDir, currentDir = rootDir) {
12510
+ const entries = [];
12511
+ for (const entry of readdirSync2(currentDir, { withFileTypes: true })) {
12512
+ const fullPath = path17.join(currentDir, entry.name);
12513
+ if (entry.isDirectory()) {
12514
+ entries.push(...listFilesRecursiveDist(rootDir, fullPath));
12515
+ continue;
12516
+ }
12517
+ entries.push(path17.relative(rootDir, fullPath).split(path17.sep).join("/"));
12518
+ }
12519
+ return entries.sort();
12520
+ }
12521
+ function copyDirRecursiveDist(sourceDir, targetDir) {
12522
+ mkdirSync2(targetDir, { recursive: true });
12523
+ for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
12524
+ const sourcePath = path17.join(sourceDir, entry.name);
12525
+ const targetPath = path17.join(targetDir, entry.name);
12526
+ if (entry.isDirectory()) {
12527
+ copyDirRecursiveDist(sourcePath, targetPath);
12528
+ continue;
12529
+ }
12530
+ copyFileSync(sourcePath, targetPath);
12531
+ }
12532
+ }
12533
+ function crc32Dist(buffer) {
12534
+ let crc = 4294967295;
12535
+ for (const byte of buffer) {
12536
+ crc ^= byte;
12537
+ for (let bit = 0; bit < 8; bit += 1) {
12538
+ crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
12539
+ }
12540
+ }
12541
+ return (crc ^ 4294967295) >>> 0;
12542
+ }
12543
+ function toDosDateTimeDist(date) {
12544
+ const year = Math.max(date.getUTCFullYear(), 1980);
12545
+ const month = date.getUTCMonth() + 1;
12546
+ const day = date.getUTCDate();
12547
+ const hours = date.getUTCHours();
12548
+ const minutes = date.getUTCMinutes();
12549
+ const seconds = Math.floor(date.getUTCSeconds() / 2);
12550
+ return {
12551
+ dosTime: hours << 11 | minutes << 5 | seconds,
12552
+ dosDate: year - 1980 << 9 | month << 5 | day
12553
+ };
12554
+ }
12555
+ function buildStoredZipDist(entries) {
12556
+ const parts = [];
12557
+ const central = [];
12558
+ let offset = 0;
12559
+ const { dosTime, dosDate } = toDosDateTimeDist(new Date("2026-04-09T00:00:00.000Z"));
12560
+ for (const entry of entries) {
12561
+ const nameBuffer = Buffer.from(entry.name, "utf8");
12562
+ const data = entry.data;
12563
+ const checksum = crc32Dist(data);
12564
+ const localHeader = Buffer.alloc(30 + nameBuffer.length);
12565
+ localHeader.writeUInt32LE(67324752, 0);
12566
+ localHeader.writeUInt16LE(20, 4);
12567
+ localHeader.writeUInt16LE(0, 6);
12568
+ localHeader.writeUInt16LE(0, 8);
12569
+ localHeader.writeUInt16LE(dosTime, 10);
12570
+ localHeader.writeUInt16LE(dosDate, 12);
12571
+ localHeader.writeUInt32LE(checksum, 14);
12572
+ localHeader.writeUInt32LE(data.length, 18);
12573
+ localHeader.writeUInt32LE(data.length, 22);
12574
+ localHeader.writeUInt16LE(nameBuffer.length, 26);
12575
+ localHeader.writeUInt16LE(0, 28);
12576
+ nameBuffer.copy(localHeader, 30);
12577
+ parts.push(localHeader, data);
12578
+ const centralHeader = Buffer.alloc(46 + nameBuffer.length);
12579
+ centralHeader.writeUInt32LE(33639248, 0);
12580
+ centralHeader.writeUInt16LE(20, 4);
12581
+ centralHeader.writeUInt16LE(20, 6);
12582
+ centralHeader.writeUInt16LE(0, 8);
12583
+ centralHeader.writeUInt16LE(0, 10);
12584
+ centralHeader.writeUInt16LE(dosTime, 12);
12585
+ centralHeader.writeUInt16LE(dosDate, 14);
12586
+ centralHeader.writeUInt32LE(checksum, 16);
12587
+ centralHeader.writeUInt32LE(data.length, 20);
12588
+ centralHeader.writeUInt32LE(data.length, 24);
12589
+ centralHeader.writeUInt16LE(nameBuffer.length, 28);
12590
+ centralHeader.writeUInt16LE(0, 30);
12591
+ centralHeader.writeUInt16LE(0, 32);
12592
+ centralHeader.writeUInt16LE(0, 34);
12593
+ centralHeader.writeUInt16LE(0, 36);
12594
+ centralHeader.writeUInt32LE(0, 38);
12595
+ centralHeader.writeUInt32LE(offset, 42);
12596
+ nameBuffer.copy(centralHeader, 46);
12597
+ central.push(centralHeader);
12598
+ offset += localHeader.length + data.length;
12599
+ }
12600
+ const centralDirectory = Buffer.concat(central);
12601
+ const endRecord = Buffer.alloc(22);
12602
+ endRecord.writeUInt32LE(101010256, 0);
12603
+ endRecord.writeUInt16LE(0, 4);
12604
+ endRecord.writeUInt16LE(0, 6);
12605
+ endRecord.writeUInt16LE(entries.length, 8);
12606
+ endRecord.writeUInt16LE(entries.length, 10);
12607
+ endRecord.writeUInt32LE(centralDirectory.length, 12);
12608
+ endRecord.writeUInt32LE(offset, 16);
12609
+ endRecord.writeUInt16LE(0, 20);
12610
+ return Buffer.concat([...parts, centralDirectory, endRecord]);
12611
+ }
12612
+ function validateKitAssetRootDist(assetRoot, manifest, bundleManifest) {
12613
+ const requiredPaths = [
12614
+ "kit.json",
12615
+ ...manifest.frozenAssetPaths,
12616
+ ...manifest.outputStandard.requiredPaths,
12617
+ ...bundleManifest.requiredFrozenAssets
12618
+ ];
12619
+ for (const relativePath of requiredPaths) {
12620
+ const fullPath = path17.resolve(assetRoot, relativePath);
12621
+ if (!existsSync2(fullPath)) {
12622
+ throw new Error(`Bundled worker kit validation failed. Missing required path: ${relativePath}`);
12623
+ }
12624
+ }
12625
+ }
12626
+ function inspectKitDist(kitId, outDir) {
12627
+ const assetRoot = resolveKitAssetRootDist(kitId);
12628
+ const manifest = readJsonDist(path17.resolve(assetRoot, "kit.json"));
12629
+ const bundleRef = manifest.bundles.find((bundle) => bundle.id === kitId) ?? manifest.bundles[0];
12630
+ const bundleManifest = readJsonDist(path17.resolve(assetRoot, bundleRef.path));
12631
+ validateKitAssetRootDist(assetRoot, manifest, bundleManifest);
12632
+ const outputRoot = resolveKitOutputRootDist(outDir);
12633
+ return {
12634
+ assetRoot,
12635
+ manifest,
12636
+ bundleManifest,
12637
+ outputRoot,
12638
+ folderPath: path17.resolve(outputRoot, bundleManifest.export.folderName),
12639
+ zipPath: path17.resolve(outputRoot, bundleManifest.export.zipFileName)
12640
+ };
12641
+ }
12642
+ function listBundledKitsDist() {
12643
+ const assetBase = path17.resolve(path17.dirname(fileURLToPath2(import.meta.url)), "../assets/worker-kits");
12644
+ if (!existsSync2(assetBase)) return [];
12645
+ return readdirSync2(assetBase, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => inspectKitDist(entry.name)).sort((a, b) => a.manifest.kit.id.localeCompare(b.manifest.kit.id));
12646
+ }
12647
+ function downloadKitDist(kitId, outDir) {
12648
+ const info = inspectKitDist(kitId, outDir);
12649
+ mkdirSync2(info.outputRoot, { recursive: true });
12650
+ rmSync(info.folderPath, { recursive: true, force: true });
12651
+ copyDirRecursiveDist(info.assetRoot, info.folderPath);
12652
+ const zipEntries = listFilesRecursiveDist(info.folderPath).map((relativePath) => ({
12653
+ name: path17.posix.join(info.bundleManifest.export.folderName, relativePath),
12654
+ data: readFileSync(path17.resolve(info.folderPath, relativePath))
12655
+ }));
12656
+ writeFileSync(info.zipPath, buildStoredZipDist(zipEntries));
12657
+ return info;
12658
+ }
12659
+ function registerKitCommandsDist(target) {
12660
+ const kit = target.command("kit").description("Bundled Growthub Agent Worker Kit export utilities");
12661
+ kit.command("list").description("List the bundled worker kits available in this CLI build").action(() => {
12662
+ const kits = listBundledKitsDist();
12663
+ if (kits.length === 0) {
12664
+ console.log(pc21.dim("No bundled worker kits are available in this CLI build."));
12665
+ return;
12666
+ }
12667
+ for (const item of kits) {
12668
+ console.log([
12669
+ pc21.bold(item.manifest.kit.id),
12670
+ `version=${item.manifest.kit.version}`,
12671
+ `bundle=${item.bundleManifest.bundle.id}@${item.bundleManifest.bundle.version}`,
12672
+ `briefType=${item.bundleManifest.briefType}`,
12673
+ `name=${item.manifest.kit.name}`
12674
+ ].join(" "));
12675
+ }
12676
+ });
12677
+ kit.command("inspect").description("Inspect a bundled worker kit manifest and export metadata").argument("<kit-id>", "Bundled worker kit id").option("--out <path>", "Override the export root used for resolved output paths").action((kitId, opts) => {
12678
+ const info = inspectKitDist(kitId, opts.out);
12679
+ console.log(`Kit: ${info.manifest.kit.id} @ ${info.manifest.kit.version}`);
12680
+ console.log(`Name: ${info.manifest.kit.name}`);
12681
+ console.log(`Bundle: ${info.bundleManifest.bundle.id} @ ${info.bundleManifest.bundle.version}`);
12682
+ console.log(`Entrypoint: ${info.manifest.entrypoint.path}`);
12683
+ console.log(`Export Folder: ${info.folderPath}`);
12684
+ console.log(`Export Zip: ${info.zipPath}`);
12685
+ });
12686
+ kit.command("download").description("Export a bundled worker kit as both a zip file and expanded folder").argument("<kit-id>", "Bundled worker kit id").option("--out <path>", "Output directory for the generated artifacts").action((kitId, opts) => {
12687
+ const info = downloadKitDist(kitId, opts.out);
12688
+ console.log(`Expanded Folder: ${info.folderPath}`);
12689
+ console.log(`Zip File: ${info.zipPath}`);
12690
+ });
12691
+ kit.command("path").description("Resolve the expected expanded export folder path without exporting").argument("<kit-id>", "Bundled worker kit id").option("--out <path>", "Output directory for the generated artifacts").action((kitId, opts) => {
12692
+ const info = inspectKitDist(kitId, opts.out);
12693
+ console.log(info.folderPath);
12694
+ });
12695
+ }
12696
+
12492
12697
  // src/index.ts
12493
12698
  var program = new Command();
12494
12699
  var DATA_DIR_OPTION_HELP = "Growthub data directory root (isolates local instance state)";
@@ -12520,6 +12725,7 @@ function registerSharedCommands(target) {
12520
12725
  });
12521
12726
  target.command("allowed-hostname").description("Allow a hostname for authenticated/private mode access").argument("<host>", "Hostname to allow (for example dotta-macbook-pro)").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).action(addAllowedHostname);
12522
12727
  target.command("run").description("Bootstrap local setup (onboard + doctor) and run Growthub").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("-i, --instance <id>", "Local instance id (default: default)").option("--repair", "Attempt automatic repairs during doctor", true).option("--no-repair", "Disable automatic repairs during doctor").action(runCommand);
12728
+ registerKitCommandsDist(target);
12523
12729
  const auth = target.command("auth").description("Authentication and bootstrap utilities");
12524
12730
  auth.command("bootstrap-ceo").description("Create a one-time bootstrap invite URL for first instance admin").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--force", "Create new invite even if admin already exists", false).option("--expires-hours <hours>", "Invite expiration window in hours", (value) => Number(value)).option("--base-url <url>", "Public base URL used to print invite link").action(bootstrapCeoInvite);
12525
12731
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthub/cli",
3
- "version": "0.3.33",
3
+ "version": "0.3.35",
4
4
  "description": "Growthub CLI — orchestrate AI agent teams to run a business",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,8 @@
25
25
  "url": "https://github.com/antonioromero1220/growthub-local/issues"
26
26
  },
27
27
  "files": [
28
- "dist"
28
+ "dist",
29
+ "assets"
29
30
  ],
30
31
  "engines": {
31
32
  "node": ">=20"