gooseworks 0.4.2 → 0.4.4
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.
- package/README.md +42 -11
- package/dist/commands/doctor.d.ts +29 -5
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +107 -63
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/fetch.d.ts +1 -0
- package/dist/commands/fetch.d.ts.map +1 -1
- package/dist/commands/fetch.js +64 -29
- package/dist/commands/fetch.js.map +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +37 -4
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +8 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/skills.d.ts +34 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +109 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +17 -6
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/video-local.d.ts +9 -0
- package/dist/commands/video-local.d.ts.map +1 -0
- package/dist/commands/video-local.js +83 -0
- package/dist/commands/video-local.js.map +1 -0
- package/dist/commands/video-save.d.ts +3 -0
- package/dist/commands/video-save.d.ts.map +1 -0
- package/dist/commands/video-save.js +83 -0
- package/dist/commands/video-save.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/video-local-captions.d.ts +83 -0
- package/dist/lib/video-local-captions.d.ts.map +1 -0
- package/dist/lib/video-local-captions.js +261 -0
- package/dist/lib/video-local-captions.js.map +1 -0
- package/dist/lib/video-local-runtime.d.ts +8 -0
- package/dist/lib/video-local-runtime.d.ts.map +1 -0
- package/dist/lib/video-local-runtime.js +201 -0
- package/dist/lib/video-local-runtime.js.map +1 -0
- package/dist/lib/video-save-mcp.d.ts +9 -0
- package/dist/lib/video-save-mcp.d.ts.map +1 -0
- package/dist/lib/video-save-mcp.js +162 -0
- package/dist/lib/video-save-mcp.js.map +1 -0
- package/dist/lib/video-save.d.ts +77 -0
- package/dist/lib/video-save.d.ts.map +1 -0
- package/dist/lib/video-save.js +440 -0
- package/dist/lib/video-save.js.map +1 -0
- package/dist/skills/custom-video-skill.d.ts +3 -0
- package/dist/skills/custom-video-skill.d.ts.map +1 -0
- package/dist/skills/custom-video-skill.js +6 -0
- package/dist/skills/custom-video-skill.js.map +1 -0
- package/dist/skills/installer.d.ts +12 -6
- package/dist/skills/installer.d.ts.map +1 -1
- package/dist/skills/installer.js +41 -15
- package/dist/skills/installer.js.map +1 -1
- package/dist/skills/master-skill.d.ts +22 -19
- package/dist/skills/master-skill.d.ts.map +1 -1
- package/dist/skills/master-skill.js +1472 -551
- package/dist/skills/master-skill.js.map +1 -1
- package/dist/skills/package-freshness.d.ts +16 -0
- package/dist/skills/package-freshness.d.ts.map +1 -0
- package/dist/skills/package-freshness.js +36 -0
- package/dist/skills/package-freshness.js.map +1 -0
- package/dist/skills/releases.d.ts +12 -0
- package/dist/skills/releases.d.ts.map +1 -0
- package/dist/skills/releases.js +152 -0
- package/dist/skills/releases.js.map +1 -0
- package/dist/skills/routes.d.ts.map +1 -1
- package/dist/skills/routes.js +7 -1
- package/dist/skills/routes.js.map +1 -1
- package/dist/utils/browser-preflight.d.ts +29 -0
- package/dist/utils/browser-preflight.d.ts.map +1 -0
- package/dist/utils/browser-preflight.js +338 -0
- package/dist/utils/browser-preflight.js.map +1 -0
- package/package.json +2 -2
- package/skills/goose-ads/SKILL.md +124 -16
- package/skills/goose-product-photos/SKILL.md +55 -3
- package/skills/goose-video/SKILL.md +322 -253
- package/skills/goose-video-local/SKILL.md +1199 -234
- package/skills/gooseworks/SKILL.md +199 -13
- package/skills/make-custom-video/SKILL.md +490 -0
- package/skills/manifest.json +12 -0
- package/skills/routes.json +8 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RENDER_UPDATE_KEY = exports.RENDER_OPEN_ARGS = exports.RENDER_ROW_TOOL = void 0;
|
|
3
4
|
exports.getEntrySkills = getEntrySkills;
|
|
4
5
|
exports.getEntrySkillNames = getEntrySkillNames;
|
|
5
6
|
exports.getMasterSkillContent = getMasterSkillContent;
|
|
@@ -7,6 +8,7 @@ exports.getGooseAdsSkillContent = getGooseAdsSkillContent;
|
|
|
7
8
|
exports.getGooseVideoSkillContent = getGooseVideoSkillContent;
|
|
8
9
|
exports.getGooseVideoLocalSkillContent = getGooseVideoLocalSkillContent;
|
|
9
10
|
exports.getGooseProductPhotosSkillContent = getGooseProductPhotosSkillContent;
|
|
11
|
+
exports.getMakeCustomVideoSkillContent = getMakeCustomVideoSkillContent;
|
|
10
12
|
/**
|
|
11
13
|
* The CLI installs two vendored ENTRY skills into ~/.agents/skills/:
|
|
12
14
|
* - `gooseworks` — the PARENT router (getMasterSkillContent): GTM/data toolkit
|
|
@@ -22,16 +24,208 @@ exports.getGooseProductPhotosSkillContent = getGooseProductPhotosSkillContent;
|
|
|
22
24
|
* `gooseworks install --with goose-graphics` or fetched on demand.
|
|
23
25
|
*
|
|
24
26
|
* Video (vendored here, GOOSE-3677):
|
|
25
|
-
* - `goose-video` —
|
|
26
|
-
*
|
|
27
|
+
* - `goose-video` — the front door for a new video ad: brand → goal →
|
|
28
|
+
* format table → machine check → project → hand off to goose-video-local
|
|
29
|
+
* (getGooseVideoSkillContent). The only full copy of that body.
|
|
27
30
|
* - `goose-video-local` — render an existing app project/batch LOCALLY
|
|
28
31
|
* (Playwright + ffmpeg + media proxies) (getGooseVideoLocalSkillContent).
|
|
29
32
|
*
|
|
30
33
|
* Recipe skills (remix-graphic-ad-from-reference, brand-research, meta-ads-analyzer,
|
|
31
34
|
* …) are NOT vendored here — they live in goose-skills and are fetched live on
|
|
32
|
-
* demand
|
|
35
|
+
* demand from the connected catalog; saved packages retain their recorded hashes.
|
|
33
36
|
*/
|
|
34
37
|
const routes_1 = require("./routes");
|
|
38
|
+
const custom_video_skill_1 = require("./custom-video-skill");
|
|
39
|
+
const STORED_FOOTAGE_GUIDANCE = "\n## Use uploaded footage\n\nWhen the customer asks to use existing footage, check the actual advertised schemas. Use `media_search` with purpose `production`, kind `video`, the brand and current project when needed. Omit query spend for free retrieval. Read facts/scenes with `media_analyze`, then use `media_excerpt` action `inspect` to review actual bounded frames and timed transcript before selecting. A description or thumbnail URL alone is not visual review.\n\nFreeze `source_excerpt:{asset_id,analysis_revision,scene_id,start_ms,end_ms,audio_mode}`. Revision is the original-byte SHA, scene may be null for a known user trim, bounds use integer milliseconds, and audio is original or muted. Call `media_excerpt` action `attach` with that exact selection and a stable idempotency key. The returned project remains unfinished and unapproved. Open its existing Studio review; preserve user locks and requested format, and keep footage separate from image packshot indexes. An incompatible format requires an explicit choice before changing it.\n\nFor custom production, fetch the current shared `video-production-harness` and follow script/ingredient gates. Download the verified original and trim its exact selected window at normal speed with the approved audio. Recheck current revision and production permission before consumption and final upload/completion. Save excerpt lineage in plan and ingredient readback. Missing optional semantics does not require another upload; known user-selected stored footage remains usable. Research and competitor references never become production footage. Selected originals require no paid generation; only newly generated/replaced ingredients consume the approved budget.\n";
|
|
40
|
+
/**
|
|
41
|
+
* How every entry skill talks to the customer. Shared so the five skills can't
|
|
42
|
+
* drift. The customer is a marketer or founder, not an engineer: everything the
|
|
43
|
+
* skills say about tools, fields, polling and pipeline steps is for the agent,
|
|
44
|
+
* and a run that narrated it ("checking the media-proxy helpers for voiceover
|
|
45
|
+
* timestamps and lipsync", "generated with gpt-image-2") read as noise.
|
|
46
|
+
*/
|
|
47
|
+
const SKILL_FRESHNESS = `## Use current instructions for new work
|
|
48
|
+
|
|
49
|
+
Before a new task in a terminal host, run \`gooseworks skills status\` once. It compares the
|
|
50
|
+
installed entry files, running CLI and published npm release. An older CLI needs a package
|
|
51
|
+
upgrade before \`gooseworks update\`; update alone only uses that CLI's bundled instructions.
|
|
52
|
+
Preserve local edits or unknown install provenance. Review/back up before explicitly replacing
|
|
53
|
+
modified files; never quietly reinstall over them. If the release check is unavailable, report
|
|
54
|
+
that freshness is unknown rather than claiming the local copy is latest.
|
|
55
|
+
|
|
56
|
+
For a new recipe run, fetch its package from the connected catalog. Retain the returned
|
|
57
|
+
\`version\` / \`contentHash\` and every dependency's hash with the saved package. When reusing a
|
|
58
|
+
saved fetch JSON, \`gooseworks fetch <slug> --saved-package <file>\` returns the current package
|
|
59
|
+
and a hash comparison without changing that file. With MCP, if the advertised \`catalog_fetch\`
|
|
60
|
+
schema accepts them, send \`saved_content_hash\` and \`saved_dependency_hashes\`; otherwise fetch
|
|
61
|
+
normally and compare the returned hashes yourself. Missing hashes mean unknown, not current.
|
|
62
|
+
The server cannot inspect a client's saved files; hash metadata does not certify later edits.
|
|
63
|
+
Fetch current packages into a new run directory and report stale saved instructions. Preserve
|
|
64
|
+
an existing approved run's recorded package; changing that harness requires a reviewed change
|
|
65
|
+
and approval before spending. Hosted installed snapshots use the existing Skills Update action.
|
|
66
|
+
Skill content and a host's cached MCP tool schemas are separate: refreshing one does not refresh
|
|
67
|
+
the other. Check the actual advertised tools before using new fields.`;
|
|
68
|
+
const CUSTOMER_TALK = `## How to talk to the customer (applies to every message you send them)
|
|
69
|
+
|
|
70
|
+
The customer is a marketer or founder, not an engineer. Everything in this skill about tools,
|
|
71
|
+
fields, files, polling, models and pipeline steps is for YOU. Never pass it on to them.
|
|
72
|
+
|
|
73
|
+
- **Say what they get and what they need to decide, never how it's made.**
|
|
74
|
+
Bad: "Checking the media-proxy helpers for voiceover timestamps and lipsync." / "Generated with
|
|
75
|
+
gpt-image-2 at high quality, mouth closed, passes QC."
|
|
76
|
+
Good: "Recording the voices now." / "Here are your two hosts."
|
|
77
|
+
- **Never mention** tool, file or field names, ids, JSON, commands, scripts, installed software,
|
|
78
|
+
uploads, model or vendor names, timings in seconds or frames, retries, polling, internal statuses
|
|
79
|
+
or your own quality checks. Fix what you can quietly. If they ask how something works, explain it
|
|
80
|
+
simply.
|
|
81
|
+
- **Update them only at milestones they care about**: choices confirmed, script and voices done,
|
|
82
|
+
visuals done, ready to review, finished. One short line each, then stay quiet until the next
|
|
83
|
+
milestone. No update for setup, downloads, uploads or saving.
|
|
84
|
+
- **Money is in credits, with a number**: "the full video uses about N credits". Never "the cheap
|
|
85
|
+
pieces" or "the expensive render".
|
|
86
|
+
- **Problems:** say what it means for them and what they can do, in one or two sentences. No error
|
|
87
|
+
codes or stack traces. Only raise a problem that changes something for them.
|
|
88
|
+
- **Use their words**: script, voice, hook, scene, host, ending, logo, the product shot.
|
|
89
|
+
- **No filler**: no "Great question!", "Certainly!", "I hope this helps!", no restating what they
|
|
90
|
+
just said at length.
|
|
91
|
+
|
|
92
|
+
Example. Instead of a dozen lines about render tools, scripts, uploads and portraits, send:
|
|
93
|
+
"Got it: warm tone, home podcast studio, Brielle as the skeptic and Mark as the believer." then
|
|
94
|
+
"Script and voices are done (about 34 seconds)." then "Ready for you to review: the script, voices,
|
|
95
|
+
both hosts and the ending. You can also review your recipe ingredients in the app: <link>. Say go
|
|
96
|
+
here and I'll make the full video (about N credits)."
|
|
97
|
+
|
|
98
|
+
${SKILL_FRESHNESS}`;
|
|
99
|
+
const ENVIRONMENT_IDENTITY = `## Keep the selected connection for the whole run
|
|
100
|
+
|
|
101
|
+
Call \`account_whoami\` on the connection that owns the brand/project before the first write.
|
|
102
|
+
Keep \`environment.name\` and the public \`environment.api_origin\` with this run. Respect the
|
|
103
|
+
user's selected production or staging connection. If multiple connections are available and
|
|
104
|
+
none was selected, resolve that once. Missing or unknown identity is uncertainty, not
|
|
105
|
+
permission to switch. Never infer the environment from credits, billing links or Node mode.
|
|
106
|
+
|
|
107
|
+
Reads, project creation, uploads, generation, polling and final updates all use that same
|
|
108
|
+
connection. If a production request fails, resume or report the failure on production;
|
|
109
|
+
never retry it on staging or recreate the project there. Before retrying a timed-out write,
|
|
110
|
+
read back the existing project or paid request on the selected connection.
|
|
111
|
+
|
|
112
|
+
Local/CLI proxy origin must match \`environment.api_origin\`. If it differs, use the MCP
|
|
113
|
+
relay on the selected connection (\`GW_MEDIA_VIA=mcp\`) before paid calls. Do not change
|
|
114
|
+
credentials or API origins to recover a failed write. An explicit user-requested move is
|
|
115
|
+
a separate operation, with the existing project and paid requests reconciled first.`;
|
|
116
|
+
const ASSET_READINESS = `## Check assets for the selected format before spending
|
|
117
|
+
|
|
118
|
+
For a template, read its structured \`asset_readiness\` from
|
|
119
|
+
\`video_catalog_list { kind: "formats", brand_id }\`. \`missing\` names gaps;
|
|
120
|
+
\`needs_review\` means suitability is unverified, including older recipes without
|
|
121
|
+
structured requirements. \`ready\` describes assets only, not script or budget approval.
|
|
122
|
+
For a custom video, derive requirements from its actual approved scenes.
|
|
123
|
+
|
|
124
|
+
Inspect candidate files for the chosen product and format. A catalog photo can contain
|
|
125
|
+
multiple objects, other products or a person; its existence or approved status does not
|
|
126
|
+
make it a standalone image of the selected product. Check object count, framing, readable
|
|
127
|
+
print and real image bytes. A service conversation has no automatic packshot requirement.
|
|
128
|
+
|
|
129
|
+
Reuse a suitable approved image first. A free crop or cutout is a new file: keep the
|
|
130
|
+
original, inspect the result and include it in the normal ingredient review. When no
|
|
131
|
+
usable input exists, explain the gap before generation. Estimate any paid preparation
|
|
132
|
+
separately before spending. Record the selected asset and inspection in the project
|
|
133
|
+
review set; do not add a separate approval round.`;
|
|
134
|
+
/** Same canonical capture policy for the router and both video entrypoints. */
|
|
135
|
+
const DURABLE_BRAND_CAPTURE = `## Save durable brand answers, then verify them
|
|
136
|
+
|
|
137
|
+
Read the selected brand with \`brand_read { brand_id, sections: ["summary", "kit", "products", "learnings", "onboarding"] }\`
|
|
138
|
+
(fallback: \`brand_get_context\` with the same sections). Keep founder answers, user corrections,
|
|
139
|
+
research and your own hypotheses distinct. Reuse matching saved answers; ask only about gaps.
|
|
140
|
+
|
|
141
|
+
When the user asks to remember a rule, answers a brand interview, or explicitly corrects a
|
|
142
|
+
standing fact, save that answer in the same turn. The capture request authorizes those answers;
|
|
143
|
+
do not ask for approval again. A direction for this one video stays in its brief. If the scope
|
|
144
|
+
is genuinely ambiguous, ask whether it applies to future videos before saving a standing rule.
|
|
145
|
+
|
|
146
|
+
Use the **live registered schema**. Where supported, call \`brand_update\` with
|
|
147
|
+
\`knowledge_intent: "user_correction"\` and \`user_statement\` containing the user's exact,
|
|
148
|
+
verbatim answer, not your paraphrase or researched text.
|
|
149
|
+
For an inference or suggested improvement, use \`knowledge_intent: "agent_proposal"\`. Show the
|
|
150
|
+
before/after change from your prior read and proposed value; retain the returned proposal IDs
|
|
151
|
+
and say the user must accept it in the app. Link only a review surface actually returned by a
|
|
152
|
+
tool; the compact \`knowledge_updates\` response does not itself contain a diff or URL.
|
|
153
|
+
A pending proposal is not a saved fact. Never call an unavailable
|
|
154
|
+
tool or silently relabel research or your inference as something the user said.
|
|
155
|
+
|
|
156
|
+
The safe structured shape is \`patch: { knowledge: { positioning?, audience?, voice?,
|
|
157
|
+
instructions?, brandType?, tagline?, valueProps? } }\`, using only fields present in the live
|
|
158
|
+
schema. Inferred rules/taste go in an \`instructions\` proposal with a rationale, never in
|
|
159
|
+
\`patch.facts\`. Prefix every video-only preference in that proposed text with "Video preference:"
|
|
160
|
+
so it remains production direction after acceptance. Preserve unrelated instructions when
|
|
161
|
+
proposing a merged replacement.
|
|
162
|
+
|
|
163
|
+
| User answer | Canonical write |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Primary audience, positioning or voice correction | \`patch.knowledge: { audience/positioning/voice: <answer> }\` (one actual key). During onboarding, \`brand_onboarding { action: "review_research", review: { action: "correct", field, value } }\` writes these existing corrections with provenance. |
|
|
166
|
+
| Founder story, customer pains, objections, buying trigger or useful audience detail without a structured field | \`patch.facts: [{ kind: "insight", text }]\`; retain attribution such as "Founder reports: …" rather than turn a belief into a verified result. |
|
|
167
|
+
| Required wording or pronunciation | \`patch.facts: [{ kind: "must", text }]\`; pronunciation is exactly \`Pronounce "<term>" as "<say_as>"\`. |
|
|
168
|
+
| Forbidden claim, word or visual | \`patch.facts: [{ kind: "dont", text }]\`. |
|
|
169
|
+
| Durable visual, voice or pacing preference | \`patch.facts: [{ kind: "do", text }]\` for a preference; \`dont\` for an avoidance; \`template_hint\` for a preferred format. Prefix video-only preferences with "Video preference:". |
|
|
170
|
+
|
|
171
|
+
Facts are existing \`ad_brand_learning\` rows with user provenance; they are not a second profile.
|
|
172
|
+
Update a matching rule by its returned \`id\` instead of adding duplicates. Preserve unrelated
|
|
173
|
+
rules and the user's exact meaning. Only use the legacy facts shape for explicitly user-authored
|
|
174
|
+
answers when the live schema lacks intent fields; agent suggestions still need a proposal path.
|
|
175
|
+
|
|
176
|
+
**Claims and plans have separate gates.** A founder assertion or proof point is not an approved,
|
|
177
|
+
evidence-backed claim or consent to quote a customer. Use the existing evidence/claims and
|
|
178
|
+
operating-plan tools only if registered, following their proposal, evidence and confirmation
|
|
179
|
+
requirements. Never encode a spend cap, approver or emergency stop as a learning. If that write
|
|
180
|
+
path is missing, report the specific unsaved item and keep it pending for the supported review
|
|
181
|
+
surface; do not claim it was saved or create a parallel local profile.
|
|
182
|
+
|
|
183
|
+
After every write, **read back before saying saved**: use \`brand_read\` with \`kit\`, \`learnings\`
|
|
184
|
+
or \`onboarding\` as appropriate, or \`brand_onboarding { action: "status", brand_id }\` after an
|
|
185
|
+
onboarding answer. Verify the intended field/rule, its source and the absence of a conflicting
|
|
186
|
+
duplicate. A generic success response, pending proposal, ignored key or truncated result is not
|
|
187
|
+
proof. Report partial saves honestly. Carry the verified rules into the current task and the
|
|
188
|
+
routed skill; claims still pass their own safety gate.`;
|
|
189
|
+
/** The existing media library and learnings are sufficient for a minimal taste brief. */
|
|
190
|
+
const VIDEO_TASTE_CAPTURE = `## Video taste — reuse examples and preferences
|
|
191
|
+
|
|
192
|
+
When asked to capture video taste, or when the user volunteers a durable video preference,
|
|
193
|
+
first read the saved learnings and \`media_list { brand_id, scope: "brand", scope_id: brand_id,
|
|
194
|
+
tags: ["video-taste"], limit: 100 }\`. Follow \`next_cursor\` before deciding an example is absent.
|
|
195
|
+
Do not force a taste interview before an unrelated task or ask again for an existing preference.
|
|
196
|
+
|
|
197
|
+
Save what the user has already supplied first. Then ask only the missing useful question, for
|
|
198
|
+
example: "What do you like about this video—its pace, voice, captions, or look?" An inaccessible
|
|
199
|
+
link can still be saved as a link with the user's explanation; do not pretend you watched it.
|
|
200
|
+
|
|
201
|
+
- **Direct clip or video file:** register with \`media_upload { brand_id, scope: "brand",
|
|
202
|
+
scope_id: brand_id, kind: "video", source: { type: "url", url }, tags: ["video-taste", "reference-only"],
|
|
203
|
+
metadata: { purpose: "video_taste", source_url: url, provenance: "user", captured_at: <ISO timestamp>,
|
|
204
|
+
preference: <the user's explanation> } }\`. For a file use the live file/bytes upload flow and
|
|
205
|
+
\`media_confirm\` after a presigned upload. Registration of a URL does not copy or inspect it.
|
|
206
|
+
- **Instagram/post/page link:** the same registration with \`kind: "document"\`; it is a link
|
|
207
|
+
bookmark, not downloadable footage or an indexed transcript. Do not fabricate a direct clip URL.
|
|
208
|
+
- **Preferences:** save the user's reasons, likes and dislikes through the facts mapping above.
|
|
209
|
+
Read and preserve existing facts before updating one. Do not invent \`video_preferences\` or
|
|
210
|
+
new \`video_lab\` keys; the live kit patch accepts only its documented asset fields.
|
|
211
|
+
- **Deduplicate:** reuse a matching returned media row, then \`media_update\` its title/tags/metadata
|
|
212
|
+
if needed; preserve existing metadata and tags. Do not create another row for the same example.
|
|
213
|
+
Read back with \`media_list\` and \`brand_read\` learnings before claiming it was saved.
|
|
214
|
+
Check the returned row belongs to this brand: URL deduplication may return another brand's
|
|
215
|
+
existing row. Do not relabel that row or claim success unless the current brand's scoped read
|
|
216
|
+
actually returns it. Report an unsaved association if no supported attach path is available.
|
|
217
|
+
|
|
218
|
+
Never put third-party taste examples into kit reference images: \`kind: "reference"\` at brand
|
|
219
|
+
scope writes there. The tags and metadata above record purpose and provenance; **they do not
|
|
220
|
+
grant or enforce usage rights**. Study the structure, pacing and look only. Never use the example's
|
|
221
|
+
footage, face, product, testimonial or claims in a new ad without independently verified permission.
|
|
222
|
+
|
|
223
|
+
Read video-only entries prefixed \`Video preference:\` from both saved learnings and
|
|
224
|
+
\`kit.instructions\`, including accepted proposals. Keep them out of required or forbidden
|
|
225
|
+
dialogue. Build a brief from the verified readback: preferred pace, voice, caption treatment, visual style,
|
|
226
|
+
formats to favour/avoid, reference links and the user's reasons. Say what is still unknown.
|
|
227
|
+
Pass it with the brand rules into the existing video workflow. A one-video request overrides a
|
|
228
|
+
default for that project; it does not silently rewrite the brand's standing preference.`;
|
|
35
229
|
/**
|
|
36
230
|
* THE registry of entry skills (GOOSE-3190) — one list, four consumers:
|
|
37
231
|
* - `gooseworks install` / `update` / login-refresh write exactly these dirs,
|
|
@@ -49,6 +243,7 @@ function getEntrySkills() {
|
|
|
49
243
|
{ name: 'goose-ads', content: getGooseAdsSkillContent() },
|
|
50
244
|
{ name: 'goose-video', content: getGooseVideoSkillContent() },
|
|
51
245
|
{ name: 'goose-video-local', content: getGooseVideoLocalSkillContent() },
|
|
246
|
+
{ name: 'make-custom-video', content: getMakeCustomVideoSkillContent() },
|
|
52
247
|
{ name: 'goose-product-photos', content: getGooseProductPhotosSkillContent() },
|
|
53
248
|
];
|
|
54
249
|
}
|
|
@@ -73,6 +268,7 @@ description: >
|
|
|
73
268
|
GooseWorks growth coworker and specialist-skill router. Research brands, customers, competitors,
|
|
74
269
|
creators, markets, and prospects; analyze ads and performance; create ads, product photos,
|
|
75
270
|
graphics, and video; search and scrape public web and social data; find and enrich leads.
|
|
271
|
+
Capture founder answers, brand rules, audience depth, and video taste in the existing brand.
|
|
76
272
|
Use it as the single GooseWorks entry point for brand growth, B2B, sales, research, and GTM work.
|
|
77
273
|
category: general
|
|
78
274
|
version: 1.0.0
|
|
@@ -86,10 +282,16 @@ You have access to GooseWorks — an AI coworker with specialist skills for rese
|
|
|
86
282
|
|
|
87
283
|
This skill is also the **parent router** for the GooseWorks family. Data/GTM work you handle here (see "How to Use"); specialized work you hand off to a dedicated \`goose-*\` skill.
|
|
88
284
|
|
|
285
|
+
${CUSTOMER_TALK}
|
|
286
|
+
|
|
89
287
|
## Route to the right skill FIRST
|
|
90
288
|
|
|
91
289
|
First apply the **Common company onboarding** gate below. Preserve the user's original request while onboarding, then continue with it as soon as onboarding is complete. Then load the brand context (**"Load the brand context FIRST"**, immediately below). After that, check whether the request belongs to a specialized domain. If so, **switch to that skill** instead of the data flow below:
|
|
92
290
|
|
|
291
|
+
For "interview me about the brand", "save our brand rules", "refine our audience", or "remember
|
|
292
|
+
our video taste", stay here and follow **Guided brand capture** below. This extends the current
|
|
293
|
+
brand and onboarding flow; it does not create another onboarding checklist.
|
|
294
|
+
|
|
93
295
|
| If the user wants… | Route to | How |
|
|
94
296
|
| --- | --- | --- |
|
|
95
297
|
${(0, routes_1.renderDomainRouteTable)()}
|
|
@@ -99,7 +301,7 @@ Examples — all of these route to \`goose-ads\`, not the data flow: "remix this
|
|
|
99
301
|
|
|
100
302
|
## Load the brand context FIRST (mandatory — before you route, and before you ask anything)
|
|
101
303
|
|
|
102
|
-
**Call \`
|
|
304
|
+
**Call \`brand_read { brand_id, sections: ["summary", "kit", "products", "learnings", "onboarding"] }\` before the first substantive step of ANY task**, and before you route to a specialist skill. Older clients can use \`brand_get_context\` with the same sections only when that tool is advertised. It is a read-only call that returns the brand's canonical facts and saved rules:
|
|
103
305
|
|
|
104
306
|
| It returns | Use it for |
|
|
105
307
|
| --- | --- |
|
|
@@ -114,8 +316,8 @@ Then:
|
|
|
114
316
|
1. **Pass what it returned INTO the routed skill.** When you hand off to \`goose-ads\`, \`goose-video\`, \`goose-product-photos\`, \`goose-graphics\`, or a fetched Brand Growth recipe, carry the voice / products / audience / positioning with you. Do **not** make the routed skill re-derive them, and do **not** re-run brand research when the context is already there.
|
|
115
317
|
2. **Never re-ask the user for something the brand context already answers.** If a routed skill's own prose asks a question the context answers, the context wins — answer it yourself and move on. Ask only for what is genuinely missing or ambiguous.
|
|
116
318
|
3. **If research status is not complete**, say so in one line, use what you have, and continue. Only run brand research when the context comes back empty or the user asks for it.
|
|
117
|
-
4. **If \`
|
|
118
|
-
5. **
|
|
319
|
+
4. **If \`brand_read\` is unavailable**, refresh the GooseWorks connection or tool list. An older connection may expose \`brand_get_context\` / \`get_brand_kit\`; use those only when actually advertised. Never require a legacy tool name or guess brand facts.
|
|
320
|
+
5. **A read grants no write permission.** Save explicit durable answers/corrections with the capture policy below. Propose agent-derived changes for review; never overwrite confirmed knowledge with research or a guess.
|
|
119
321
|
|
|
120
322
|
Never invent a brand fact. If it isn't in the brand context and the user hasn't said it, ask.
|
|
121
323
|
|
|
@@ -172,16 +374,53 @@ When onboarding returns a review link, show that single link and ask the user to
|
|
|
172
374
|
Use the host's native question controls. Ask one short group at a time and rely on the live tool schema for accepted values.
|
|
173
375
|
|
|
174
376
|
1. **Start** — If status returns \`start\`, ask for the company website or Apple App Store URL. Also offer the optional hero product URL and “Where do you do your work?” choices: Slack, WhatsApp, iMessage, Claude Code, Claude, Codex, and ChatGPT. Call \`action: "start"\`; server-side research begins immediately. If status returns \`select_brand\`, ask which company/client to use. Otherwise reuse the only brand automatically.
|
|
175
|
-
2. **Your coworker** —
|
|
176
|
-
3. **Your company** — Use the returned \`company_draft\`
|
|
177
|
-
4. **Your taste** — In a terminal or CLI host, use the returned \`taste_url\`: open it when the host supports opening links and always show one clickable **Choose your taste in GooseWorks** link. Ask the user to heart or skip ads on that page, click **Continue** or **Skip this**, return to the agent, and reply \`done\`. Do not print, enumerate, or summarize \`taste_deck\` in the terminal. After \`done\`, call \`brand_onboarding { action: "status" }\` again and follow the refreshed \`next_step\`. In a chat host that renders images, show only the one image attached by the tool and save each Love/Skip decision with \`action: "save_taste"\`;
|
|
178
|
-
5. **First campaign** — Ask **“What’s happening right now?”**: launch \`launch\`, promotion \`promo\`, seasonal moment \`seasonal\`, or nothing special \`nothing\`, plus an optional note. Call \`action: "propose_campaign"\`, show the returned editable card (name, objective, offer, audience, 2–3 angles, CTA, and product URL), and save edits with \`action: "save_campaign"\`. Send \`accept: true\` only after approval; acceptance can start the complimentary first creatives.
|
|
179
|
-
6. **
|
|
180
|
-
7. **
|
|
181
|
-
|
|
377
|
+
2. **Your coworker** — The current flow accepts the default coworker automatically. If an older session returns \`coworker\`, refresh \`status\`; do not introduce a naming/avatar question. Rename only when the user asks and the live tool supports it.
|
|
378
|
+
3. **Your company** — Use the returned \`company_draft\` plus the user's existing answers. Ask only to verify missing or ambiguous details: what they sell (\`marketCategory\`), where people buy (\`appPlatforms\`), primary customer, customer problem, promised outcome, and optional differentiator. Monthly Meta ad spend belongs here when absent: \`none\`, \`under_10k\`, \`10k_50k\`, \`50k_150k\`, \`150k_plus\`, or \`not_sure\`. Save the merged required company object with \`action: "save_company", company: { … }\`; read \`status\` back.
|
|
379
|
+
4. **Your taste** — In a terminal or CLI host, use the returned \`taste_url\`: open it when the host supports opening links and always show one clickable **Choose your taste in GooseWorks** link. Ask the user to heart or skip ads on that page, click **Continue** or **Skip this**, return to the agent, and reply \`done\`. Do not print, enumerate, or summarize \`taste_deck\` in the terminal. After \`done\`, call \`brand_onboarding { action: "status" }\` again and follow the refreshed \`next_step\`. In a chat host that renders images, show only the one image attached by the tool and save each Love/Skip decision with \`action: "save_taste", taste: { hearted_ids, skipped_ids, complete }\`; set \`taste.complete: true\` after three hearts or an explicit skip.
|
|
380
|
+
5. **First campaign** — Ask **“What’s happening right now?”**: launch \`launch\`, promotion \`promo\`, seasonal moment \`seasonal\`, or nothing special \`nothing\`, plus an optional note. Call \`action: "propose_campaign"\`, show the returned editable card (name, objective, offer, audience, 2–3 angles, CTA, and product URL), and save edits with \`action: "save_campaign"\`. Send \`campaign.accept: true\` only after approval; acceptance can start the complimentary first creatives.
|
|
381
|
+
6. **Review** — Show the returned founder, researched, and inferred facts with their provenance. The user may correct positioning, audience, voice, value propositions, proof points, or competitors through \`action: "review_research", review: { action: "correct", field, value }\`. A proof-point edit does not approve a claim. Complete with \`review: { action: "complete" }\` even when research is still running, failed, or sparse; never trap the user waiting for it.
|
|
382
|
+
7. **Channels** — If \`channel_connected\` is already true, this is complete automatically. Otherwise ask whether they want to connect Slack, WhatsApp, or iMessage later, or skip for now. An explicit skip is valid; call \`action: "complete_channels"\`.
|
|
383
|
+
|
|
384
|
+
The former revenue / 90-day-goal / \`save_progress\` screen is retired. Do not insert it into
|
|
385
|
+
onboarding. Ask those human-only questions later only when the user's task needs them.
|
|
182
386
|
|
|
183
387
|
Do not ask for role, discovery source, who makes creatives, who manages ads, or a separate “what do you want to do first?” menu. Those belonged to the retired CLI questionnaire. The task the user already asked for is their first task.
|
|
184
388
|
|
|
389
|
+
## Guided brand capture
|
|
390
|
+
|
|
391
|
+
Use this when the user requests a founder interview, audience/rules capture, or video taste.
|
|
392
|
+
Keep their original task pending. Load the current brand first, compare it with information
|
|
393
|
+
already volunteered in this chat, and **save known information first** using the canonical
|
|
394
|
+
mapping below. Do not run a long questionnaire as a prerequisite for making an ad.
|
|
395
|
+
|
|
396
|
+
For facts needed by the task but absent from the read, call \`knowledge_search\` first if it is
|
|
397
|
+
registered. Use returned citations and states honestly: an empty, building or failed index is
|
|
398
|
+
not proof that the brand has no answer. Do not re-scrape or ask the founder for a fact already
|
|
399
|
+
answered by trustworthy saved knowledge.
|
|
400
|
+
|
|
401
|
+
Ask one short group of missing human-only facts at a time, in plain language, with the relevant
|
|
402
|
+
known answer in that same question. "Skip" or "not sure" is valid. Examples, **only for gaps**:
|
|
403
|
+
|
|
404
|
+
| Gap | Useful question |
|
|
405
|
+
| --- | --- |
|
|
406
|
+
| Founder origin or conviction | "What made you start this, and what do you believe that alternatives get wrong?" |
|
|
407
|
+
| Audience depth | "Who buys first, what problem pushes them to act, and what nearly stops them?" |
|
|
408
|
+
| Buying trigger or alternatives | "What happens just before they look for you, and what do they use instead?" |
|
|
409
|
+
| Rules | "What must we always say or show, and what must we never say or imply?" |
|
|
410
|
+
| Proof | "What evidence supports that result, and do we have permission to quote the customer?" Keep unsupported claims pending. |
|
|
411
|
+
| Video taste | "Share a video you like and what you would keep or avoid: pace, voice, captions, or look." |
|
|
412
|
+
|
|
413
|
+
Keep skipped or uncertain answers as gaps in the brief; do not save them as confirmed facts.
|
|
414
|
+
Save each answered group and read it back before the next group. Stop when the requested capture
|
|
415
|
+
is covered or the user skips; resume from canonical saved answers after interruption. Present a
|
|
416
|
+
short brief containing verified answers, attribution, pending proposals and remaining gaps, then
|
|
417
|
+
continue the original task. Review happens in this chat plus any proposal review link returned
|
|
418
|
+
by the tools. Do not promise an unavailable evidence, claims or plan write.
|
|
419
|
+
|
|
420
|
+
${DURABLE_BRAND_CAPTURE}
|
|
421
|
+
|
|
422
|
+
${VIDEO_TASTE_CAPTURE}
|
|
423
|
+
|
|
185
424
|
## Brand Growth discovery
|
|
186
425
|
|
|
187
426
|
Brand Growth is a collection inside the normal skill catalog, not a command or installable pack. Use these known routes when relevant, while preserving all existing B2B, sales, research, lead-generation, and data behavior:
|
|
@@ -190,7 +429,7 @@ Brand Growth is a collection inside the normal skill catalog, not a command or i
|
|
|
190
429
|
| --- | --- |
|
|
191
430
|
${(0, routes_1.renderBrandGrowthTable)()}
|
|
192
431
|
|
|
193
|
-
Fetch the named public skill before following it. You already called \`
|
|
432
|
+
Fetch the named public skill before following it. You already called \`brand_read\` — hand the brand's voice, products, audience, and positioning to the fetched skill instead of letting it re-derive or re-ask them. Provider helpers such as \`scrapecreators-api\` and \`transcript-intelligence\` are dependencies, not user-facing results.
|
|
194
433
|
|
|
195
434
|
For a multi-part request, repeat this routing check before each new job. Fetch and follow the
|
|
196
435
|
closest outcome skill first (for example, \`comment-mining\`, \`creator-profile-teardown\`, or
|
|
@@ -320,7 +559,7 @@ The \`gooseworks\` CLI sends authenticated requests (Bearer \`GOOSEWORKS_API_KEY
|
|
|
320
559
|
|
|
321
560
|
## Rules
|
|
322
561
|
|
|
323
|
-
0. **
|
|
562
|
+
0. **Read the canonical brand context before substantive work**, pass what it returns into whatever skill you route to, and never re-ask the user for a fact it already answers (see "Load the brand context FIRST").
|
|
324
563
|
1. **Consider a GooseWorks skill when it fits the task** — scraping, research, lead gen, enrichment, especially at scale, behind auth, or from a specific source. For a quick lookup your built-in tools are fine; use your judgement and pick the best tool for the user.
|
|
325
564
|
2. **Before paid operations**, tell the user the estimated credit cost
|
|
326
565
|
3. **If a \`gooseworks\` command exits with "Not logged in"**: tell the user to run \`npx gooseworks login\`
|
|
@@ -378,6 +617,8 @@ The GooseWorks ads skill. Two jobs:
|
|
|
378
617
|
2. **Analyze ad performance** — fetch ad-analytics recipes from goose-skills on demand
|
|
379
618
|
(these are unrelated to generation; see "Analyze / intelligence" below).
|
|
380
619
|
|
|
620
|
+
${CUSTOMER_TALK}
|
|
621
|
+
|
|
381
622
|
## Prerequisite — the GooseWorks MCP server is REQUIRED
|
|
382
623
|
|
|
383
624
|
Everything goes through the \`mcp__gooseworks__*\` tools. If they are not available, **stop and
|
|
@@ -387,7 +628,7 @@ no HTTP/file fallback — the REST ad endpoints are session-cookie-only and reje
|
|
|
387
628
|
## Start from the brand context — don't re-ask what it already answers
|
|
388
629
|
|
|
389
630
|
If the \`gooseworks\` router handed you brand context, USE IT. If you were invoked directly, call
|
|
390
|
-
\`
|
|
631
|
+
\`brand_read { brand_id, sections: ["summary","kit","products","learnings"] }\` first. It already
|
|
391
632
|
answers most of what the flows below would otherwise ask the user:
|
|
392
633
|
|
|
393
634
|
- **Which product to feature** → \`products[]\`. Offer the real catalog entries; never guess a
|
|
@@ -495,7 +736,7 @@ immediately.
|
|
|
495
736
|
competitor ad or an image found online.
|
|
496
737
|
- \`get_ad_project\` / \`append_project_message\` — inspect a creative / leave a note on its thread.
|
|
497
738
|
|
|
498
|
-
## Keep the brand kit in sync — reconcile, then
|
|
739
|
+
## Keep the brand kit in sync — reconcile, then save or propose
|
|
499
740
|
|
|
500
741
|
The brand kit is the source of truth every generation reads. During ANY task, when the user
|
|
501
742
|
**tells you something about the brand or asks to change something brand-level** — a different
|
|
@@ -503,24 +744,26 @@ tagline, audience, voice, a product's name/price/description, "our logo is X", "
|
|
|
503
744
|
anymore", a new product photo — treat it as a possible kit update, don't just use it for this one
|
|
504
745
|
ad and forget it:
|
|
505
746
|
|
|
506
|
-
1. **Check it against the kit.** Call \`
|
|
747
|
+
1. **Check it against the kit.** Call \`brand_read\` with \`kit\` and \`learnings\` for the active brand and see whether what the user said
|
|
507
748
|
matches, is missing from, or contradicts the kit.
|
|
508
749
|
2. **If it's already in the kit and matches** — nothing to do; proceed.
|
|
509
|
-
3. **If it's new or different
|
|
510
|
-
the
|
|
511
|
-
|
|
512
|
-
4. **Persist with the write tools** (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
- \`
|
|
516
|
-
- \`add_brand_product_image\` / \`remove_brand_reference_image\` — product and reference photos.
|
|
750
|
+
3. **If it's new or different**, persist an explicit request to correct or remember the brand
|
|
751
|
+
through the policy below; that request is already authorization. For an ambiguous one-ad
|
|
752
|
+
direction, ask once whether it should stick. Agent-derived suggestions become proposals.
|
|
753
|
+
4. **Persist with the canonical write tools** (send only changed fields):
|
|
754
|
+
- \`brand_update\` with explicit user-correction intent — structured brand fields or products;
|
|
755
|
+
an inferred structured field uses agent-proposal intent instead. Follow the live schema.
|
|
756
|
+
- \`media_upload\` / \`media_update\` — the user's own product and reference photos.
|
|
517
757
|
Inspect each live schema and send only the fields needed for the confirmed change.
|
|
518
|
-
5. **
|
|
519
|
-
|
|
758
|
+
5. **Read back what changed** before saying saved, then continue the task. (Use the research
|
|
759
|
+
workflow for researched logo/colors/fonts; explicit user edits use only fields supported by
|
|
760
|
+
the canonical tool schema and the correction policy below.)
|
|
520
761
|
|
|
521
762
|
This is the parity gap the app closes in-product: a brand fact the user gives mid-task should be
|
|
522
763
|
able to flow back into the kit — with their ok — instead of being lost.
|
|
523
764
|
|
|
765
|
+
${DURABLE_BRAND_CAPTURE}
|
|
766
|
+
|
|
524
767
|
## Picking source ads — use approved sources, not the retired catalog
|
|
525
768
|
|
|
526
769
|
When the user wants to make ads but has NOT named a specific template (id/slug/Community
|
|
@@ -680,9 +923,10 @@ run through the \`gooseworks\` CLI (\`gooseworks fetch\` / \`gooseworks call\`),
|
|
|
680
923
|
- **Treat competitor ads as inspiration** — never attest rights, imply ownership, or promise to
|
|
681
924
|
copy a competitor's distinctive expression.
|
|
682
925
|
- **Reconcile brand facts into the kit** — when the user states or changes something brand-level
|
|
683
|
-
mid-task,
|
|
684
|
-
|
|
685
|
-
|
|
926
|
+
mid-task, compare it with \`brand_read\`. Save explicitly authorized corrections through
|
|
927
|
+
\`brand_update\` with correction intent and the user's exact statement, or the user's own
|
|
928
|
+
images through \`media_upload\`; read back before saying saved. Proposed improvements stay
|
|
929
|
+
pending. Ask only when it is unclear whether a one-ad direction should apply to future ads.
|
|
686
930
|
- **Record feedback** — when the user reacts to a generated image, inspect and call
|
|
687
931
|
\`set_creative_feedback\` so the quality loop learns.
|
|
688
932
|
- **Plan mode is opt-in** — only use the live approval option, then \`list_ad_approvals\` and
|
|
@@ -696,366 +940,293 @@ run through the \`gooseworks\` CLI (\`gooseworks fetch\` / \`gooseworks call\`),
|
|
|
696
940
|
with the real error + step in \`details\`) so the team gets visibility. Still tell the user too.
|
|
697
941
|
`;
|
|
698
942
|
}
|
|
699
|
-
/**
|
|
700
|
-
* Returns the goose-video entry SKILL.md content (GOOSE-3677).
|
|
701
|
-
*
|
|
702
|
-
* The server-rendered ORDERING flow: "make me a video ad for <brand>" → brand →
|
|
703
|
-
* what it's for → format table with demos → the format's questions → a free
|
|
704
|
-
* draft and a server quote → a script preview before the expensive work → the
|
|
705
|
-
* finished video in the chat. Works in hosted connectors (nothing runs locally).
|
|
706
|
-
* It routes existing app projects/batches to `goose-video-local` first.
|
|
707
|
-
*
|
|
708
|
-
* This is the ONLY full copy of the body. goose-lab's `order-video` is a stub
|
|
709
|
-
* that points here; edit the ordering flow in this function.
|
|
710
|
-
*/
|
|
943
|
+
/** Card-aware video entry. Shell execution and widget display are independent. */
|
|
711
944
|
function getGooseVideoSkillContent() {
|
|
712
945
|
return `---
|
|
713
946
|
name: goose-video
|
|
714
947
|
slug: goose-video
|
|
715
948
|
description: >
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
real spend. It renders on the GooseWorks server, returns the video in the chat, and works in
|
|
721
|
-
hosted connectors too. For an existing app video project or batch, it hands off to
|
|
722
|
-
goose-video-local.
|
|
949
|
+
Start a video ad in the same chat. Resolve the brand and suggest supported formats with a
|
|
950
|
+
picker or the returned text choices. An agent with a verified shell makes the video with
|
|
951
|
+
goose-video-local; a chat host hands the same project to the GooseWorks coworker. Review
|
|
952
|
+
one complete template plan and total credits before production. Custom videos retain two authenticated review gates in this chat.
|
|
723
953
|
category: ads
|
|
724
|
-
version:
|
|
954
|
+
version: 3.0.0
|
|
725
955
|
author: GooseWorks
|
|
726
|
-
tags: [gooseworks, ads, video,
|
|
956
|
+
tags: [gooseworks, ads, video, local-render, coworker, chat]
|
|
727
957
|
---
|
|
728
958
|
|
|
729
|
-
# GooseWorks Video Ads —
|
|
959
|
+
# GooseWorks Video Ads — choose, review, make
|
|
730
960
|
|
|
731
|
-
|
|
961
|
+
${CUSTOMER_TALK}
|
|
732
962
|
|
|
733
|
-
|
|
963
|
+
## Purpose
|
|
734
964
|
|
|
735
|
-
|
|
965
|
+
Resolve the brand, show supported formats and create one saved project. An agent with a verified
|
|
966
|
+
shell follows **\`goose-video-local\`**. A chat host delegates that same project to the GooseWorks
|
|
967
|
+
coworker, which renders in its sandbox. This is agent execution, not a recipe server-order API.
|
|
736
968
|
|
|
737
|
-
**
|
|
969
|
+
**Cards and execution are separate capabilities.** A terminal may render without drawing a
|
|
970
|
+
picker; a chat host may draw a picker while the coworker renders. Follow \`card.display_hint\`
|
|
971
|
+
and \`available_here\`, never a guess from the host's name. Template-remix review stays in this
|
|
972
|
+
chat. Custom videos use separate authenticated script and ingredient approvals in this same chat.
|
|
973
|
+
Studio is an optional review surface.
|
|
738
974
|
|
|
739
|
-
|
|
975
|
+
## Custom videos: route before formats
|
|
740
976
|
|
|
977
|
+
For an original brief without a reference template, an Instagram reel/post URL or a direct video URL to study, fetch \`catalog_fetch { type: "skill", slug: "make-custom-video" }\` and follow it in this same session. It creates format:"custom", custom_mode:"generate" with the brief and optional reference_url. Growth executes in its managed sandbox; connected agents use their shell. Script and actual ingredients are reviewed and separately approved in the same chat before paid production; Studio is optional. Do not force a template choice or import the reference as a finished video.
|
|
741
978
|
|
|
742
|
-
## Route first: is this a new
|
|
979
|
+
## Route first: is this a new video?
|
|
743
980
|
|
|
744
|
-
|
|
981
|
+
Hand off to **\`goose-video-local\`** now, and stop following this skill, for:
|
|
745
982
|
|
|
746
|
-
-
|
|
983
|
+
- an existing **project** id or a **video batch** id;
|
|
747
984
|
- the app's copy-for-Claude command (it names \`goose-video-local\`);
|
|
748
985
|
- "remix this video ad template" for a specific app template.
|
|
749
986
|
|
|
750
|
-
|
|
987
|
+
Use \`goose-video-local\` if it is installed; otherwise load it with
|
|
988
|
+
\`catalog_fetch { type: "skill", slug: "goose-video-local" }\` on the GooseWorks MCP (older clients:
|
|
989
|
+
\`fetch_skill("goose-video-local")\`). It reads the project first and says what to do with it.
|
|
751
990
|
|
|
752
|
-
|
|
991
|
+
When they ask **what** to make ("give me video ad ideas", "what angles should I use?", "what's
|
|
992
|
+
working for my competitors?"), fetch **\`ad-angle-miner\`** (\`catalog_fetch { type: "skill", slug: "ad-angle-miner" }\`,
|
|
993
|
+
using its advertised tools) and run it with the **video** output. It returns ranked
|
|
994
|
+
video ideas mapped to formats and hands the picked ones to \`goose-video-local\`.
|
|
753
995
|
|
|
754
996
|
Everything else, including "make me a video ad for <brand>", starts at step 1 below.
|
|
755
997
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
- A brand, usually named in the opening sentence. Resolved to \`brand_id\`; with one brand in the org it needs no input.
|
|
759
|
-
- What the ad is for, in the customer's words (optional; asked once, never forced). **It is sent with the order** as \`brief.prompt\` and reaches the script. It is not only for sorting the table.
|
|
760
|
-
- Anything else they volunteer about the ad: who it's for, names or terms it must say, things to stay away from. These are never asked for; they're kept when the customer offers them.
|
|
761
|
-
- The picked format's own answers (voice, music, angle…). The catalogue says which apply; some formats ask nothing.
|
|
762
|
-
|
|
763
|
-
Never an input: a reference video (these formats don't use one), or a promo code (it comes from the brand).
|
|
764
|
-
|
|
765
|
-
## Composed Atoms
|
|
766
|
-
|
|
767
|
-
MCP tools; the work happens in the app.
|
|
768
|
-
|
|
769
|
-
- \`brand_list\`: brand NAME → \`brand_id\`. Pass \`query\` when they named one.
|
|
770
|
-
- \`brand_create { name, website_url }\`: only when the customer asks to add a brand that isn't there. Free.
|
|
771
|
-
- \`video_catalog_list { kind: "formats", brand_id }\`: what can be ordered. Each format has \`card.description\`, \`card.best_for\`, \`card.needs\`, \`ask[]\`, \`brand_requirements[]\`, \`default_credits\` and \`examples[]\` (demo videos: a curated sample first, then real runs). The response also carries \`brief_fields\`: the intent fields every format's \`brief\` accepts on top of its \`ask[]\` (\`prompt\`, \`audience\`, \`must_mention\`, \`avoid\`, \`notes\`).
|
|
772
|
-
- \`video_catalog_list { kind: "voices" }\`: voices with playable \`preview_url\`s, for formats that speak.
|
|
773
|
-
- \`video_project_upsert\`: the free draft. Returns \`project_id\`, \`quote\`, \`ready\`, \`missing\` and \`questions\` (0–2 things worth asking before the script is written; usually none).
|
|
774
|
-
- \`video_project_upsert { project_id, patch: { brief } }\`: changes the brief, checked the same way as when it was created. A key replaces its value, \`null\` removes it. Free. Once ordered, only the brief fields change (never the format's own answers), and they reach the script on the next \`redraft\`. A brief change never moves the quote.
|
|
775
|
-
- \`video_render_run { dry_run: true }\`: re-reads the price. Free.
|
|
776
|
-
- \`video_render_run { kind: "partial" }\`: **writes the script and stops.** Reserves the quote and shows the script before the expensive work. Works for every orderable format, chat or voiceover.
|
|
777
|
-
- \`video_render_run { kind: "full" }\`: finishes an approved preview.
|
|
778
|
-
- \`video_render_run { kind: "edit", edit: { script } }\`: puts the customer's OWN words into the draft, verbatim, through the format's own guards. Free before approval, and the **only** route that keeps their copy. Offered where the format's \`edits.script\` is non-empty; refused as \`script_not_editable\` elsewhere.
|
|
779
|
-
- \`video_render_run { kind: "redraft", reason }\`: another draft, before approval, written from why they turned this one down, or with no \`reason\` after a brief change (the change is the reason). It **re-runs the writer** either way, so every word (and the picture on a character format) is replaced — it does not keep copy the customer supplied. Same order, **no new hold**, but **not free**: it adds a few credits of model spend inside the hold already placed. Up to 3, and refused if it would pass the hold. \`dry_run: true\` says what it would add.
|
|
780
|
-
- \`video_project_read { brand_id, project_id }\`: status, the drafted script while previewing, and the finished video. Returns an \`order\` object for recipe projects.
|
|
781
|
-
- \`job_cancel { job_id }\`: declines a preview. \`job_id\` is the order id (the project id also works). Releases the whole hold.
|
|
782
|
-
|
|
783
|
-
## Workflow
|
|
784
|
-
|
|
785
|
-
The opening is fixed: **brand → what it's for → suggested formats → first question.** Do each step without waiting for the customer to ask for it.
|
|
786
|
-
|
|
787
|
-
### 1. Resolve the brand, quietly when you can
|
|
788
|
-
|
|
789
|
-
Call \`brand_list\`, with \`query\` when they named a brand. \`query\` is a case-insensitive substring match, so "Kolkata Chai" also finds "Kolkata Chai Co". If it finds nothing, call \`brand_list\` once more with no query before deciding.
|
|
790
|
-
|
|
791
|
-
- **The org has exactly one brand** → use it. Say which in one line ("Making this for **Bioma**.") and move on. Don't ask.
|
|
792
|
-
- **The name they said matches exactly one brand** → use it. Say which.
|
|
793
|
-
- **Several match, or they named none and the org has several** → show a table (name, website) and ask. The wrong brand is a wasted order.
|
|
794
|
-
- **Nothing matches** → say so, list the brands they do have in a table, and offer to add the new one here: "Or send me its website and I'll add it." With a website, call \`brand_create { name, website_url }\` (free). It starts brand research, which fills in the logo and colours in a few minutes. Say so, then carry on from step 2 while it runs. Before step 5, check \`brand_get_context\` shows \`research_status: complete\`; until then drafts are blocked. Adding a brand is not a trip to the app. Never create a brand they didn't ask for, and never guess the website.
|
|
795
|
-
|
|
796
|
-
If the GooseWorks MCP's own instructions have you check onboarding first and it turns out unfinished, finish it, then come back here with the customer's original sentence.
|
|
797
|
-
|
|
798
|
-
### 2. Ask what the ad is for, in one open question
|
|
799
|
-
|
|
800
|
-
Unless the opening sentence already said it, ask **one** plain question and wait:
|
|
801
|
-
|
|
802
|
-
> What's this ad for? For example: launching something, a sale, explaining how it works, or showing real results. Anything you tell me helps me pick the right format.
|
|
803
|
-
|
|
804
|
-
This is a free-text question: **no menu, no table, no list of formats yet.** Take whatever they say, even "not sure" or "just something good". Never ask it twice, and never block on it: a vague answer is an answer.
|
|
805
|
-
|
|
806
|
-
**Keep the answer. It goes into the order in step 5**, not just into the table's order. Write it down as they said it; that becomes \`brief.prompt\`. If their words (here or in the opening) also say who the ad is for, a name or term the ad must say, or something to stay away from, note those too:
|
|
807
|
-
|
|
808
|
-
| They said | Goes in | Example |
|
|
809
|
-
|---|---|---|
|
|
810
|
-
| What the ad is for (the whole answer) | \`prompt\` | "creative is the bottleneck for small teams, make it a problem-solver" |
|
|
811
|
-
| Who it's for | \`audience\` | "heads of growth at seed-stage startups" |
|
|
812
|
-
| A name or term it must say | \`must_mention\` (a list) | \`["Claude", "ChatGPT"]\` |
|
|
813
|
-
| Something to keep out | \`avoid\` (a list) | \`["no villain", "don't talk down to marketers"]\` |
|
|
814
|
-
|
|
815
|
-
Never ask for the last three and never fill them with a guess. A \`must_mention\` term the customer didn't say is a claim we made for them.
|
|
816
|
-
|
|
817
|
-
Skip the question when the opening already names a goal ("…a video ad for our summer sale") or a format ("…an iMessage video ad"). With a format named, go to the table with that format first and marked, then step 4.
|
|
818
|
-
|
|
819
|
-
### 3. Suggest formats in a table, best fit first
|
|
820
|
-
|
|
821
|
-
\`video_catalog_list { kind: "formats", brand_id }\`, then **always a markdown table in your message**.
|
|
822
|
-
|
|
823
|
-
Order the rows by how well each format fits their answer. Judge fit from \`card.description\`, \`card.best_for\` **and the format's \`ask[]\` options** against what they said. An option can make a format fit: a cartoon explainer whose \`ask[]\` offers \`arc: hero-helper\` fits "a friendly hero mascot, no villain"; one without that option does not, because its card says the character is the problem and loses. **A format whose card contradicts what they asked for is never Suggested**, however well its keywords match. When nothing fits, say so before the table ("None of our formats does X; the closest is Y, which gives up Z") and still show the table. Mark **exactly one** row **Suggested** with a few words on why ("real results → before/after"); a close second can be **Also good**. One suggestion keeps "the suggested one" unambiguous. With no goal given, put the formats that have demos first.
|
|
824
|
-
|
|
825
|
-
| | Format | What it looks like | Price | Demo |
|
|
826
|
-
|---|---|---|---|---|
|
|
827
|
-
| **Suggested** | iMessage chat reveal | Two friends texting; ends on your product | ~60 credits | [watch](https://…) |
|
|
828
|
-
| **Also good** | Apple Notes reveal | A diary-style note typed out; ends on your product | ~15 credits | [watch](https://…) |
|
|
829
|
-
| | Kinetic type explainer | Bold on-brand text timed to a voiceover | ~80 credits | [watch](https://…) |
|
|
830
|
-
|
|
831
|
-
- **Demo** is \`examples[].output_url\`. When a format has none, write "no demo yet" in the cell; never leave it blank.
|
|
832
|
-
- **"What it looks like" is \`card.description\`, quoted.** Copy it word for word; you may cut it at a sentence boundary, never re-word it. A paraphrase once turned "narrates how it gets beaten" into "narrates the fix", which made a villain format look right for a no-villain brief. \`card.best_for\` often carries a dollar figure; never copy it into the table.
|
|
833
|
-
- **When the pick depends on an option, say which.** "Cartoon explainer (as a friendly helper)" in the Format cell, and pre-fill that answer in step 4.
|
|
834
|
-
- **Price** is \`default_credits\`, written approximately ("~60 credits"). It is a guide for choosing. The price they agree to is the server's quote in step 5.
|
|
835
|
-
- **Say which formats won't take their words.** A format whose \`edits.script\` is empty writes its own copy and accepts no hand edit; the only lever is a redraft, which rewrites everything. Put "writes its own words" in that row's "What it looks like" cell. A customer who arrives with a script already written needs to know this **before** they pick, not after they hand it over.
|
|
836
|
-
- **Formats the brand may not be able to run** go last, with \`card.needs\` in plain words, e.g. "needs real before/after photos". Don't hide them; don't suggest them. Judge logo and product photos from the \`brand_list\` row. For anything else (before/after photos, say) you can't see, treat it as missing rather than make extra calls. \`video_project_upsert\` returns \`ready\`/\`missing\`, which is the real check.
|
|
837
|
-
|
|
838
|
-
**Print the table in your message, THEN ask which one** ("Want the suggested one, or another?"). Never put the formats only inside a structured question control: it renders plain option labels, not links, so the customer would be picking a format they were never able to watch. That happened on the first real run: the picker appeared, the demos didn't, and the customer had to ask where they were. A question control may follow the table to capture the answer; it never replaces it.
|
|
839
|
-
|
|
840
|
-
### 4. Ask the picked format's questions (only these)
|
|
841
|
-
|
|
842
|
-
Its \`ask[]\` list is the whole question set. Anything with \`source: "recipe"\` is the recipe's call, never the customer's. **An empty \`ask[]\` means no questions**: say so ("This one needs nothing else from you") and go straight to step 5.
|
|
843
|
-
|
|
844
|
-
Ask them all in **one message**, the choice tables first. Put yes/no questions that have a default at the end as a statement they can override ("Music on and a selfie in the thread; say if you want either off"). Every question with an optional answer gets a "leave it to the writer" option.
|
|
845
|
-
|
|
846
|
-
**The rule most easily got wrong:** anything the customer should *see or hear before choosing* goes in a **markdown table in your message**. The structured question control only captures the answer afterwards; it cannot render a link, so a demo or a voice sample placed only in the widget is a choice nobody can actually evaluate. Table first, question second. Yes/no questions need no table, and a choice with no sample (an angle list) is still a table, just without that column.
|
|
847
|
-
|
|
848
|
-
| Angle | The thread | Demo |
|
|
849
|
-
|---|---|---|
|
|
850
|
-
| friend-asks-friend | A friend notices something and asks | [watch](…) |
|
|
851
|
-
| setup-flex | You send a photo, the friend reacts | [watch](…) |
|
|
852
|
-
| swap-moment | You quit something worse for this | [watch](…) |
|
|
853
|
-
| feature-as-punchline | The product's own mechanic is the reveal | [watch](…) |
|
|
854
|
-
|
|
855
|
-
Same for voices: \`video_catalog_list { kind: "voices" }\` gives name, gender, accent and a \`preview_url\`. Put the name in a link so it plays. This was the clearest moment of the first real run: a voice table with playable samples. Do the same for any avatar or style choice a format exposes.
|
|
856
|
-
|
|
857
|
-
A choice with an \`enum\` and no samples (the cartoon explainer's \`style\`, say) is still a table: one row per value, described in the words of the field's own \`description\`, with "no demo yet" in the sample column. Don't invent what a style looks like.
|
|
858
|
-
|
|
859
|
-
Never ask for a promo code.
|
|
860
|
-
|
|
861
|
-
### 5. Draft and price
|
|
862
|
-
|
|
863
|
-
\`video_project_upsert { brand_id, name, format, brief: { …answers, prompt, audience?, must_mention?, avoid? } }\` is free. \`brief\` holds the picked format's \`ask[]\` answers **plus** the step-2 intent. Leave out any intent field the customer never gave. If \`ready\` is false, **stop** and relay \`missing\` in plain words.
|
|
864
|
-
|
|
865
|
-
The server refuses any other key with \`invalid_brief\` and names every key it accepts. Fix the brief from that message; never drop the customer's intent to make the error go away.
|
|
866
|
-
|
|
867
|
-
**If \`questions\` is not empty, ask them now, in one message, before showing the price.** Each one says which brief field its answer fills (\`fills\`). Save the answers with \`video_project_upsert { project_id, patch: { brief: { <fills>: <their answer> } } }\`. The bounds:
|
|
868
|
-
- **One round.** Never ask a follow-up, and never ask the same thing twice.
|
|
869
|
-
- **Never required.** If they skip them or say "just make it", carry on with the draft as it is.
|
|
870
|
-
- **Empty \`questions\` means ask nothing.** The server skips it when the brief already says what the ad is for and who it's for, and never asks what the brand record already knows. "Make me a video ad for X" stays a complete request.
|
|
871
|
-
|
|
872
|
-
Show the price the draft returned (the \`quote\`), **never a number from this page or the catalogue**. If it differs from the table's "~N", the quote is right. Convert at **100 credits = $1**.
|
|
998
|
+
${ENVIRONMENT_IDENTITY}
|
|
873
999
|
|
|
874
|
-
|
|
1000
|
+
${ASSET_READINESS}
|
|
875
1001
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
This reserves the credits and writes the script only: **no video is rendered.** It returns immediately with \`status: "previewing"\` and no script yet; the writing happens in the background. Poll \`video_project_read\` every 20 seconds (it lands in about 40) and read \`order.preview\`. Calling \`kind: "full"\` while it is still \`previewing\` is refused with \`preview_in_progress\`.
|
|
879
|
-
|
|
880
|
-
\`order.preview\` is shaped by the format, and **every** orderable format has one. A preview was chat-only until 2026-09-25, and a kinetic-type customer approved a price and first saw the copy in the finished video.
|
|
881
|
-
|
|
882
|
-
**A chat format** (iMessage / ChatGPT / Notes) fills \`thread\` (the message list), \`angle\`, \`cta_text\` and \`selfie_url\`. Show it as a readable transcript, not JSON:
|
|
883
|
-
|
|
884
|
-
> **them:** wait why do you look so into your phone rn 😭
|
|
885
|
-
> **me:** I literally just chose to betray the duke
|
|
886
|
-
> **them:** omg is this a game or a book
|
|
887
|
-
|
|
888
|
-
**\`selfie_url\` is a generated face — show it as a link.** These formats draw the selfie INSIDE the script step, so it never appears in \`anchor_images\` and \`stage\` stays \`"script"\`. It is still a face that will be in the ad, so it still needs their yes.
|
|
889
|
-
|
|
890
|
-
**Any preview with \`order.preview.stage: "image"\`** stopped after the **picture**, not just the words, so they judge the face before paying for the video. Go by that field, never by a list of format names — it follows what the recipe's steps produce, so a format joins this branch without a skill edit. Today: the animated character, the reaction selfie, the podcast hosts, the UGC creator, and the voiceless dance story (four to six stills, and no spoken script at all — its stills and \`detail\` ARE the draft, so say that rather than reporting an empty script).
|
|
891
|
-
|
|
892
|
-
Show every URL in \`order.preview.anchor_images\` as a link (the podcast format has two, one per host), alongside the script. Then say plainly, in this order: what they are looking at; that **nothing has been rendered yet and the pause is deliberate**, because the video is generated FROM this picture and changing the face now is cheap; that **approving starts the render and commits the credits already held**; and that cancelling instead releases the whole hold, so the picture costs them nothing. A pause with no reason given reads as a broken order.
|
|
893
|
-
|
|
894
|
-
**A voiceover format** (kinetic type) fills \`beats\` instead, and \`thread\` is null. Each beat has \`vo_lines\` (what is spoken) and a \`beat\` label. \`detail.hyperframe.plan.slates[].props\` holds the words that go **on screen**, which for this format is most of the ad. Show both columns, in order:
|
|
895
|
-
|
|
896
|
-
| # | On screen | Voiceover |
|
|
897
|
-
|---|---|---|
|
|
898
|
-
| 1 | Sunscreen that pills | Most sunscreens pill under makeup. |
|
|
899
|
-
| 2 | Myth: all SPF is greasy → Fact: not a fluid one | This one is a fluid. It sinks in. |
|
|
900
|
-
|
|
901
|
-
Read the spoken lines out as one paragraph underneath, so they can hear the pacing.
|
|
902
|
-
|
|
903
|
-
Whatever the format, **quote it back verbatim.** Do not paraphrase, tidy or shorten the copy; they are approving the exact words that will be rendered.
|
|
904
|
-
|
|
905
|
-
If \`order.preview.brief_check\` is present, read it before asking for approval:
|
|
906
|
-
- \`missing_mentions\`: terms they asked for that the script never says. Tell them which, plainly ("It doesn't mention ChatGPT yet").
|
|
907
|
-
- \`endorsement_flags\`: lines that make another brand sound like it endorses, partners with or ranks theirs. Show the line and say it has to change: a listed name may appear as "works with", never as "recommends" or "#1 for".
|
|
908
|
-
|
|
909
|
-
If \`order.brief_changed_since_draft\` is present, the brief was changed after this draft was written. **Approving renders the draft as shown**, so redraft first (no reason needed), or tell them the change won't be in this video.
|
|
910
|
-
|
|
911
|
-
Then ask: **use this, change it, or stop?**
|
|
912
|
-
|
|
913
|
-
**One question decides how to change it: did they give you the actual WORDS, or did they tell you what is wrong?** Only the first route below keeps their words. The other two write new ones — which is right when the customer wants something different, and is a silent rewrite when they wanted what they wrote.
|
|
914
|
-
|
|
915
|
-
- **They gave you the words** ("use these lines", "the end card must say Meet Goose", "keep this but change the second bubble") → \`video_render_run { kind: "edit", edit: { script: { … } } }\`. **This is the only route that keeps copy verbatim.** It puts their text through the format's own guards and costs nothing extra before approval. Send the shape the preview handed you: \`script.thread\` for a chat format, \`script.beats[i].vo_lines\` (one entry per beat, \`{}\` for a beat you are not changing) for a voiceover format, \`script.slates[{ beat_idx, props }]\` for on-screen words, \`script.cta_text\` for the end card, \`ingredient: "character"\` + \`script.character.description\` for the character itself.
|
|
916
|
-
- **An edit before approval IS the approval**: it applies the copy and starts the render at once (status → \`running\`). So show the exact words you are about to send, get a yes, and only then send it. An unapproved fix stays a proposal in the chat.
|
|
917
|
-
- Refused with \`script_not_editable\` → this format writes its own copy and takes no hand edit. **Say so, and do not paraphrase their lines into a redraft** — that hands them a video that is not what they wrote. Their real options are a redraft (new words, not theirs), a re-order with different answers, or cancel.
|
|
918
|
-
- Refused with \`script_rejected\` → a line makes a claim the brand's facts don't support. Relay the reason and offer a rewrite; never argue it through.
|
|
919
|
-
- **They changed the BRIEF, not the copy** — the problem it shows, who it's for, a name to say, something to avoid, a note like "keep it dry" → save it with \`video_project_upsert { project_id, patch: { brief } }\` (use \`notes\` for anything that isn't one of the other fields; keep their original \`prompt\`), then \`video_render_run { kind: "redraft" }\` with no \`reason\`. The new draft is written from the updated brief, so **the words will be new** — this changes the instructions, not the script. Same cost wording as below: no new order, no new hold, a few credits inside the hold.
|
|
920
|
-
- **They only said what's wrong** ("too salesy", "the character looks like a mug") → ask **why** in one line, then \`video_render_run { kind: "redraft", reason: <their words> }\`. **A redraft RE-RUNS the writer: every word, and the picture on a character format, is replaced.** Their reason steers the next draft; it is not copied into it. Never put exact lines in \`reason\` expecting them back. Say what it costs, precisely: "no new order and no new hold; it adds about N credits to what this video costs, inside the hold" (N from the response's \`redraft.credits_estimate\`). **Never call it free.** Poll \`video_project_read\` until \`preview\` again and show the new draft plus \`order.spend\` (what each draft added, how much of the hold is left). A \`redraft_exceeds_hold\` or \`redraft_limit\` refusal means: approve a draft or cancel.
|
|
921
|
-
- Different answers altogether (another format, another angle) → a fresh project, and cancel this one.
|
|
922
|
-
- Stop → \`job_cancel { job_id: <order id> }\`. The whole hold is released and they pay nothing. Cancel only works while the order is \`queued\`, \`previewing\` or \`preview\`; once they approve and it is \`running\`, it is being made and cannot be refunded.
|
|
923
|
-
|
|
924
|
-
This is the review. It happens here, not in the app.
|
|
925
|
-
|
|
926
|
-
**If the server answers \`preview_not_available\`,** that is a bug: every sold format has a script preview. Say plainly "I can't show you the script for this one before it's made", and stop. Do not quietly skip the step and charge as if the gate had passed.
|
|
1002
|
+
## Inputs
|
|
927
1003
|
|
|
928
|
-
|
|
1004
|
+
- A brand, usually named in the request. Resolve it from the accessible brands; one brand needs no question.
|
|
1005
|
+
- The customer's goal, occasion, audience, selected angle and constraints when supplied; otherwise propose defaults from the verified kit.
|
|
1006
|
+
- The selected format's required assets. Presence does not prove suitability; uncertain assets remain “needs review.”
|
|
929
1007
|
|
|
930
|
-
|
|
1008
|
+
${DURABLE_BRAND_CAPTURE}
|
|
931
1009
|
|
|
932
|
-
|
|
1010
|
+
${VIDEO_TASTE_CAPTURE}
|
|
933
1011
|
|
|
934
|
-
|
|
1012
|
+
## Composed Atoms
|
|
935
1013
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
| Kinetic type explainer | **~10 min** | 20 min | Two AI b-roll clips, a voiceover, a music bed, HTML slides and a stitch |
|
|
1014
|
+
Use the live canonical schemas: brand_read, brand_create, video_catalog_list,
|
|
1015
|
+
video_project_upsert, video_project_read, catalog_fetch, goose_run_task and job_cancel.
|
|
1016
|
+
An old tool name in a recipe is not grounds for terminal-update advice in a chat host.
|
|
940
1017
|
|
|
941
|
-
|
|
1018
|
+
## Paid media: images, clips, voice
|
|
942
1019
|
|
|
943
|
-
|
|
1020
|
+
**No FAL_KEY, ElevenLabs key or \`fal_client\` is ever needed.** Use
|
|
1021
|
+
\`data_post_provider { provider: "fal", path: <model id, e.g. "fal-ai/nano-banana/edit">, body: <model input>, project_id }\`
|
|
1022
|
+
then \`job_get { job_id }\`; voice/music use provider:"elevenlabs". Upload local inputs through
|
|
1023
|
+
media_upload first. **A missing key is never a blocker.** \`photos_generate\` is **not** a general
|
|
1024
|
+
image tool: it photographs physical catalog products only. Use goose-video-local for the
|
|
1025
|
+
runtime, paid approval, asset saving and quality checks.
|
|
944
1026
|
|
|
945
|
-
|
|
1027
|
+
## Workflow
|
|
946
1028
|
|
|
947
|
-
|
|
1029
|
+
### 1. Resolve the brand, quietly when you can
|
|
948
1030
|
|
|
949
|
-
|
|
1031
|
+
Call brand_read without brand_id to list accessible brands. Resolve the customer's name from
|
|
1032
|
+
actual returned names. One brand, or exactly one match: state it and continue. Several possible
|
|
1033
|
+
brands: show names and websites and ask once. No match: show accessible brands and offer to add
|
|
1034
|
+
one only when the customer asks and supplies its website; never guess the URL.
|
|
1035
|
+
|
|
1036
|
+
Read \`brand_read { brand_id, sections: ["summary","kit","products","learnings"] }\` before
|
|
1037
|
+
choosing a format. Reconcile explicit brand corrections through the durable capture rules.
|
|
1038
|
+
A direct video request does not require first-campaign acceptance. Follow any returned
|
|
1039
|
+
request-specific setup requirement; never mark research complete or accept a campaign to
|
|
1040
|
+
bypass setup. Keep the original request.
|
|
1041
|
+
|
|
1042
|
+
### 2. Keep the goal and propose defaults
|
|
1043
|
+
|
|
1044
|
+
Never ask what the ad is for before showing formats. Keep the customer's exact direction.
|
|
1045
|
+
Otherwise propose defaults from the verified kit's products, audience, offer and voice. Show
|
|
1046
|
+
those defaults in the saved plan so any can be changed. Never invent proof or product facts.
|
|
1047
|
+
Idea requests still follow ad-angle-miner with the video output.
|
|
1048
|
+
|
|
1049
|
+
### 3. Show the picker or its text fallback
|
|
1050
|
+
|
|
1051
|
+
Call \`video_catalog_list { kind: "formats", brand_id }\`. On the customer's computer, when you
|
|
1052
|
+
can actually execute shell commands, include \`client: { shell: true }\`; in a chat host omit
|
|
1053
|
+
that claim. Inside a coworker sandbox follow its reported capabilities, never claim to be the
|
|
1054
|
+
customer's computer. Respect requested limit and next_cursor for more item pages; the card may
|
|
1055
|
+
contain a full picker independently of the item page.
|
|
1056
|
+
|
|
1057
|
+
- **card.display_hint:"widget"**: the picker replaces a format table. Write at most one short
|
|
1058
|
+
acknowledgement, print no table or list of the formats, and wait for the selection.
|
|
1059
|
+
- **Otherwise**: print card.text_summary as returned. Without a card, show returned rows with
|
|
1060
|
+
names, faithful descriptions, needs and demo links, respecting the requested page size.
|
|
1061
|
+
Write "no demo yet" when absent. Never put links only in a question control.
|
|
1062
|
+
- A format whose card contradicts what they asked for is never Suggested. Never offer
|
|
1063
|
+
available_here:false as an executable choice. Relay not_available_here in one short line.
|
|
1064
|
+
- Missing required assets are missing; unknown suitability is “needs review.” Inspect the
|
|
1065
|
+
selected format's candidates before spending. Never promise an unverified asset is ready.
|
|
1066
|
+
- Template formats use **one plan, one approval with the total in credits**, not approval for
|
|
1067
|
+
each paid step. Custom videos retain separate authenticated script/ingredient/budget gates in this chat.
|
|
1068
|
+
|
|
1069
|
+
### 4. Check this machine can render it
|
|
1070
|
+
|
|
1071
|
+
- **Chat host without a shell:** skip local checks and delegate an available format in Step 5.
|
|
1072
|
+
Never tell a chat host it needs Claude Code to start. Browser-only formats remain unavailable
|
|
1073
|
+
here; give computer setup guidance only if asked how to make one.
|
|
1074
|
+
- **Local shell:** run \`gooseworks doctor --no-browser\` for common setup (auth/MCP, Node 18+,
|
|
1075
|
+
ffmpeg with libx264 + libass, ffprobe). Then fetch the selected template and its capabilities,
|
|
1076
|
+
and install documented dependencies in its fetched folder. Do not guess a renderer from a
|
|
1077
|
+
format name. For each Node renderer using default Playwright Chromium, run
|
|
1078
|
+
\`gooseworks doctor --renderer-script "/absolute/path/to/the/fetched/scripts/record.js"\` with
|
|
1079
|
+
the exact environment used to render, including NODE_PATH and PLAYWRIGHT_BROWSERS_PATH.
|
|
1080
|
+
Use the actual script path. Custom browser launch settings need their equivalent exact-runtime
|
|
1081
|
+
check. Non-browser capabilities need only their documented runtime checks.
|
|
1082
|
+
Never create paid ingredients before the selected renderer passes. Repair and recheck under
|
|
1083
|
+
existing setup permissions. If it cannot be fixed, re-list with client:{shell:false} and
|
|
1084
|
+
delegate only a format available to the coworker; never silently change the selected format.
|
|
1085
|
+
- **Coworker sandbox:** you are the renderer. Follow goose-video-local's sandbox checks; never
|
|
1086
|
+
hand off recursively or claim browser capability that the sandbox lacks.
|
|
1087
|
+
|
|
1088
|
+
### 5. Create the project and hand it off, in this session
|
|
1089
|
+
|
|
1090
|
+
1. \`video_project_upsert { brand_id, name, format: <template_id> }\` with no brief (a brief creates
|
|
1091
|
+
a concept batch). Include client:{shell:true} only for real local execution. Default
|
|
1092
|
+
creation_intent:"format" keeps the style with this brand's content; source_remix requires an
|
|
1093
|
+
explicit choice to use source content.
|
|
1094
|
+
If they already chose a campaign/concept, include its verified campaign_id and optional
|
|
1095
|
+
campaign_concept_id. Read the IDs from that brand's saved campaign; the concept must belong
|
|
1096
|
+
to it. Omit unknown IDs and never infer a link or create a campaign solely to file a video.
|
|
1097
|
+
2. **Verified local shell or coworker sandbox:** load
|
|
1098
|
+
\`catalog_fetch { type: "skill", slug: "goose-video-local" }\` and follow it on the same project_id.
|
|
1099
|
+
Carry the customer's words, verified defaults, campaign and selected angle into its brief.
|
|
1100
|
+
3. **Chat host:** \`goose_run_task { brand_id, project_id, message }\` with the request, selected
|
|
1101
|
+
format and defaults. Keep task_id. Continue questions/edits with the same task and project.
|
|
1102
|
+
A saved free draft is not a started worker or a complete plan. Follow actual saved state.
|
|
1103
|
+
4. When follow.card_follows is true, the card follows progress: do not re-read it in a loop.
|
|
1104
|
+
Read again on a customer reply, card action or requested update. A failure or missing saved
|
|
1105
|
+
plan needs the returned recovery action; never claim completion or approve an empty plan.
|
|
1106
|
+
Approval requires the current complete plan and total. Insufficient balance: offer a shorter
|
|
1107
|
+
video or top-up. “Not now” keeps the saved plan.
|
|
1108
|
+
|
|
1109
|
+
Do not hand the customer a command to paste somewhere else.
|
|
950
1110
|
|
|
951
1111
|
## Decision Rules
|
|
952
1112
|
|
|
953
|
-
-
|
|
954
|
-
-
|
|
955
|
-
-
|
|
956
|
-
-
|
|
957
|
-
- **More than two options → table in the message.** Every time. Options with a sample or demo the customer can't hear or see are not really choices. A question control may capture the answer after the table, never instead of it.
|
|
958
|
-
- **\`ready: false\` → stop.** Ordering anyway fails and wastes their time.
|
|
959
|
-
- **Unsure whether an order went through → read \`video_project_read\` first.** Never use the charging tool as a status probe. Only if the project shows nothing at all, re-call \`video_render_run\` on the SAME \`project_id\` (idempotent per project). Never create a second project; that is a second charge.
|
|
960
|
-
- **Quote cards, never paraphrase them.** The card is the server's promise about the format. A reworded card can promise something the format cannot do.
|
|
961
|
-
- **A contradiction with the card outranks every keyword match.** Check what they asked for against what the card and the \`ask[]\` options say the format does; a format that can't do the ask is never Suggested.
|
|
962
|
-
- **They asked for a format that isn't in the catalogue** → it isn't available yet. Say so, show the table, and don't improvise a lab recipe.
|
|
963
|
-
- **Run failed → say so plainly.** The credits are released automatically. Offer a retry; never retry unasked.
|
|
964
|
-
- **Their words go in an \`edit\`, never in a \`redraft\` reason.** A \`reason\` steers the next draft; it is not copied into it. The moment a customer gives you actual copy, the only honest routes are \`kind: "edit"\` or telling them this format will not take it.
|
|
965
|
-
- **Deliver the page, not the file.** \`video_url\` leads, \`mp4_url\` follows. A bare CloudFront link is not a delivery.
|
|
966
|
-
- **The brief is the customer's words, not yours.** \`prompt\` is their answer, verbatim or close to it. \`audience\`, \`must_mention\` and \`avoid\` hold only what they said. Another brand in \`must_mention\` means they asked for it; it can be named as "works with", never as an endorsement or ranking.
|
|
1113
|
+
- One sentence is a complete request. Resolve the brand and show formats without a goal interview.
|
|
1114
|
+
- One brand in the org: never ask which brand. Cards decide presentation; checks decide execution.
|
|
1115
|
+
- Use canonical tools. Preserve campaign context, selected script angles and custom authenticated review gates.
|
|
1116
|
+
- Never fabricate readiness, completion, approval or a second project.
|
|
967
1117
|
|
|
968
1118
|
## Output
|
|
969
1119
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1120
|
+
One project, one current saved plan in this chat, then the finished video. With a widget write one
|
|
1121
|
+
line and do not add app_url, brand_url or duplicate video links. A text host gets text_summary
|
|
1122
|
+
and its usable delivery link.
|
|
973
1123
|
|
|
974
1124
|
## Quality Checks
|
|
975
1125
|
|
|
976
|
-
-
|
|
977
|
-
- The
|
|
978
|
-
-
|
|
979
|
-
-
|
|
980
|
-
- Only the picked format's \`ask[]\` questions were asked.
|
|
981
|
-
- The step-2 answer went into the order as \`brief.prompt\`, and no intent field held anything the customer didn't say.
|
|
982
|
-
- They approved the **script**, not just the price, before the expensive work ran: the thread for a chat format, the on-screen words *and* the voiceover for a kinetic-type one.
|
|
983
|
-
- You told them how long the render would take **for the format they picked**, and said something at the halfway mark instead of going quiet.
|
|
984
|
-
- The quote you showed came from the server, never from memory or the catalogue.
|
|
985
|
-
- Every line of copy the customer wrote is in the finished video word for word — it went through \`kind: "edit"\`, or you told them plainly that this format would not take it. No supplied copy was ever paraphrased into a \`redraft\` reason.
|
|
986
|
-
- The video was delivered in the chat as \`video_url\` first and \`mp4_url\` second. You did not send them to the app for anything but payment.
|
|
987
|
-
- You asked for no promo code or reference video, and made no product claim the brand's own facts don't support.
|
|
1126
|
+
- No forced goal question, duplicate table or unsupported shell claim.
|
|
1127
|
+
- The exact renderer passed before local paid ingredients; uncertain assets stay uncertain.
|
|
1128
|
+
- Selected campaign and angle survive; a single template project has no brief.
|
|
1129
|
+
- Current complete plan and total precede approval. Custom script and ingredient gates remain independent; Studio is optional.
|
|
988
1130
|
|
|
989
1131
|
## Failure Modes
|
|
990
1132
|
|
|
991
1133
|
| Symptom | Cause | Fix |
|
|
992
1134
|
| --- | --- | --- |
|
|
993
|
-
|
|
|
994
|
-
|
|
|
995
|
-
|
|
|
996
|
-
|
|
|
997
|
-
|
|
|
998
|
-
|
|
999
|
-
| \`invalid_brief\` | The brief carried a key this format doesn't accept (e.g. another format's question, or a made-up field like \`tone\`) | The message lists what is accepted. Move the customer's words into \`prompt\`/\`audience\`/\`must_mention\`/\`avoid\`; never drop them |
|
|
1000
|
-
| The script ignores what they asked for | The step-2 answer wasn't sent (it only sorted the table), or they added something later that never reached the brief | Send it as \`brief.prompt\` in step 5. Anything they add later goes in with \`patch.brief\` (\`notes\`) and then a redraft. The preview's \`brief_check\` shows missing must-mention terms |
|
|
1001
|
-
| Video not on this plan | Lite and trial have no video entitlement | Say so and point at the upgrade; this is the one app trip that's allowed |
|
|
1002
|
-
| Insufficient credits | Wallet short | Nothing was created or charged; report the shortfall |
|
|
1003
|
-
| Preview looks wrong | The script or the picture isn't what they wanted | They gave you the words → \`kind: "edit"\`. They changed the brief → \`patch.brief\` then a redraft with no reason. They only said what's wrong → \`kind: "redraft"\` with their reason. Same order, no new hold, a few credits inside it; never "free". Or \`job_cancel\` this one |
|
|
1004
|
-
| **The customer wrote the script and the finished video says something else** | Their lines went into a \`redraft\` \`reason\`. A redraft re-runs the writer: the reason steers the next draft, it is never copied into it. Seen on staging — "Keep this exact draft, use these lines: …" came back paraphrased, twice, and was paid for both times | Copy goes in \`kind: "edit"\`. If the format refuses it (\`script_not_editable\`), say so and let them choose a redraft, a re-order or cancel — knowing the words will be new |
|
|
1005
|
-
| Video started rendering before the customer approved | \`kind: "edit"\` on a preview applies the copy **and** starts the full render (status → \`running\`) | Show the exact words you're about to send, get a yes, then send the edit. An unapproved fix stays a proposal in the chat |
|
|
1006
|
-
| \`draft_is_your_copy\` on a redraft | The current draft is the customer's own edited copy; another draft would replace their words | Correct behaviour. Edit their copy again with \`kind: "edit"\`, approve it, or cancel |
|
|
1007
|
-
| \`script_rejected\` on an edit | A line makes a claim the brand's facts don't support | Relay the reason verbatim and offer a rewrite; nothing was charged |
|
|
1008
|
-
| The customer was sent a raw \`cloudfront.net/….mp4\` | Delivered \`mp4_url\` (or \`order.output_url\`) instead of \`video_url\` | \`video_url\` leads and \`mp4_url\` follows. Both come back from \`video_project_read\` once \`order.status\` is \`done\` |
|
|
1009
|
-
| \`recipe_brief_locked\` | You patched \`script_drafts\` raw on a format project, which would have skipped the brief's checks | Send the change as \`patch.brief\` instead |
|
|
1010
|
-
| \`format_answers_locked\` | After ordering, you tried to change the format's own answer (voice, angle, selfie) through the brief | Those change what the run costs. Use \`kind: "edit"\` where the format lists it, or start a new project |
|
|
1011
|
-
| Asked the customer three rounds of questions before anything was made | Treated \`questions\` as a form, or asked your own follow-ups | Ask what \`questions\` holds, once, in one message. Nothing else. Skipping is fine |
|
|
1012
|
-
| \`preview_in_progress\` | You called \`full\` while the script was still being written | Keep polling \`video_project_read\` until \`order.preview\` appears |
|
|
1013
|
-
| Cancel refused | The order is already \`running\`; they approved it | Say it's being made; a refund isn't possible now |
|
|
1014
|
-
| No render after the format's budget (10 min chat, 20 min kinetic) | The worker didn't pick the run up | Credits are held, not spent. Say it's queued and you'll follow up; don't re-order |
|
|
1015
|
-
| \`preview_not_available\` on an orderable format | A bug; every sold format has a script preview | Say so and stop. Do not order without showing the script |
|
|
1016
|
-
| An edited script is refused (\`script_not_editable\`) | This format's writer takes no hand edit (its \`edits.script\` is empty). Only some formats do | Should have been said at the table. Their words cannot be rendered verbatim here: offer a redraft (new words), a re-order with different answers, or cancel — and never quietly paraphrase their copy into the redraft reason |
|
|
1017
|
-
| A "hero, no villain" brief got the villain cartoon | The card was paraphrased ("narrates the fix") and the fit check never read what the format can't do | Quote \`card.description\`; check the ask against the card and \`ask[]\`; never Suggest a contradiction |
|
|
1018
|
-
| Two videos, two charges | A second project was created instead of continuing the first | Continue on the SAME \`project_id\` |
|
|
1019
|
-
|
|
1020
|
-
`;
|
|
1135
|
+
| Picker plus table | Ignored display_hint | Widget: one line; otherwise text_summary |
|
|
1136
|
+
| Terminal loses formats | Confused cards with execution | Declare real shell capability and check renderer |
|
|
1137
|
+
| Chat told it cannot start | Only local execution considered | Create once and delegate to coworker |
|
|
1138
|
+
| Worker ends without a plan | Assumed completion means ready | Follow actual failure/recovery state |
|
|
1139
|
+
| Two projects for one video | Recreated instead of resuming | Continue same project and task |
|
|
1140
|
+
${STORED_FOOTAGE_GUIDANCE}`;
|
|
1021
1141
|
}
|
|
1022
1142
|
/**
|
|
1023
|
-
*
|
|
1143
|
+
* The canonical render-row actions for a client/agent-rendered video
|
|
1144
|
+
* (GOOSE-3725): `video_render_run kind:"full"` (no dry_run) on a sample-remix
|
|
1145
|
+
* project OPENS a queued ad_render row and returns `render_id`;
|
|
1146
|
+
* `video_render_run { render: { render_id, status, ... } }` UPDATES it
|
|
1147
|
+
* (← update_render_status). If those names change, edit ONLY these constants:
|
|
1148
|
+
* every mention in the goose-video-local skill text is built from them.
|
|
1149
|
+
*/
|
|
1150
|
+
exports.RENDER_ROW_TOOL = 'video_render_run';
|
|
1151
|
+
exports.RENDER_OPEN_ARGS = 'kind: "full"';
|
|
1152
|
+
exports.RENDER_UPDATE_KEY = 'render';
|
|
1153
|
+
/**
|
|
1154
|
+
* Returns the goose-video-local entry SKILL.md content (GOOSE-3677, GOOSE-3726).
|
|
1024
1155
|
*
|
|
1025
1156
|
* The local-render runtime. VIDEO projects made in the app (template remixes,
|
|
1026
|
-
* concept batches, anything that is not a recipe order)
|
|
1027
|
-
* user's own Claude Code
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1157
|
+
* concept batches, anything that is not a recipe order) render wherever the
|
|
1158
|
+
* agent runs: the user's own Claude Code (with or without the gooseworks CLI)
|
|
1159
|
+
* OR an agent inside the GooseWorks E2B workspace sandbox (canonical MCP tools
|
|
1160
|
+
* + Bash, no CLI, no credentials.json; media proxies via GW_MEDIA_PROXY_TOKEN).
|
|
1161
|
+
* It fetches the per-format recipe, renders (ffmpeg, PIL, Playwright where
|
|
1162
|
+
* available), mirrors the review set for a free in-app review, then saves the
|
|
1163
|
+
* finished MP4 back over MCP using the canonical tools (video_project_*,
|
|
1164
|
+
* catalog_fetch, media_upload, the video_render_run render-row actions).
|
|
1033
1165
|
*/
|
|
1034
1166
|
function getGooseVideoLocalSkillContent() {
|
|
1035
1167
|
return `---
|
|
1036
1168
|
name: goose-video-local
|
|
1037
1169
|
slug: goose-video-local
|
|
1038
1170
|
description: >
|
|
1039
|
-
Render an EXISTING GooseWorks video ad project or video batch
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1171
|
+
Render an EXISTING GooseWorks video ad project or video batch with ffmpeg (plus Playwright for
|
|
1172
|
+
phone-mockup formats) and the GooseWorks media proxies, then save the finished MP4 back to the
|
|
1173
|
+
project over MCP. Runs on the user's own machine (Claude Code / desktop app, with or without the
|
|
1174
|
+
gooseworks CLI) OR inside a GooseWorks workspace sandbox (canonical MCP tools + Bash, no CLI).
|
|
1175
|
+
Use for a client-side format project (created by goose-video), a template-remix project or a
|
|
1176
|
+
video batch. A copy-for-Claude command or project id must first be checked with
|
|
1177
|
+
video_project_read. A hosted connector with no shell hands this same project to the GooseWorks coworker. To start a NEW video ad in chat,
|
|
1178
|
+
use goose-video first.
|
|
1045
1179
|
category: ads
|
|
1046
|
-
version: 0.
|
|
1180
|
+
version: 0.6.1
|
|
1047
1181
|
author: GooseWorks
|
|
1048
|
-
tags: [gooseworks, ads, video, remix, imessage, local-render, byoa]
|
|
1182
|
+
tags: [gooseworks, ads, video, remix, imessage, podcast, ugc, local-render, sandbox, byoa]
|
|
1049
1183
|
---
|
|
1050
1184
|
|
|
1051
1185
|
# GooseWorks Video Ads — local remix runtime
|
|
1052
1186
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1187
|
+
${CUSTOMER_TALK}
|
|
1188
|
+
|
|
1189
|
+
## Chat hosts and cards
|
|
1190
|
+
|
|
1191
|
+
First perform the mandatory route check below. Then, without a shell, hand a verified template
|
|
1192
|
+
project to \`goose_run_task { brand_id, project_id, message }\`, or a verified template batch to
|
|
1193
|
+
\`goose_run_task { brand_id, batch_id, message }\` (never send both ids). Keep task_id and the
|
|
1194
|
+
same project or batch. Generated custom children keep their separate make-custom-video flow.
|
|
1195
|
+
Inside a coworker sandbox you are the renderer: never delegate recursively.
|
|
1196
|
+
With card.display_hint:"widget", say at most one line and never duplicate its plan, total or
|
|
1197
|
+
links. Otherwise print card.text_summary. Read again on customer input, not in a polling loop.
|
|
1198
|
+
A free saved draft is not proof that planning started or a complete plan exists.
|
|
1199
|
+
|
|
1200
|
+
## Mandatory route check before any local work or spend
|
|
1201
|
+
|
|
1202
|
+
For every existing \`project_id\` (including one supplied by the app's copy-for-Claude command),
|
|
1203
|
+
call \`video_project_read { brand_id, project_id }\` **before** template lookup, toolchain setup,
|
|
1204
|
+
media-proxy calls, or a review-set upload. For a batch, inspect each child project.
|
|
1205
|
+
|
|
1206
|
+
- **A server-rendered order** — the response has \`creative_plan\`,
|
|
1207
|
+
\`project.creative_spec_revision_id\`, \`order.creative_spec_revision_id\`, a planning
|
|
1208
|
+
\`lifecycle\`, or an \`order\` / \`script_drafts.recipe\` on a server format → **stop.**
|
|
1209
|
+
Server video orders are paused, and there is **no vetted local node-execution API** for them:
|
|
1210
|
+
do not rebuild one locally. Tell the customer this project was made for the server flow, which
|
|
1211
|
+
is paused; offer to start the same ad on a client-side format (\`goose-video\`, fetched with
|
|
1212
|
+
\`catalog_fetch { type: "skill", slug: "goose-video" }\`, older clients:
|
|
1213
|
+
\`fetch_skill("goose-video")\`). An order already holding credits can be released with
|
|
1214
|
+
\`job_cancel\`.
|
|
1215
|
+
- **A client-side format or template remix** (a \`source_sample_id\` / \`template_id\` and none of
|
|
1216
|
+
the above) → continue below.
|
|
1217
|
+
- **Generated custom video** (\`project.custom_video_state.mode === "generate"\`) → fetch \`catalog_fetch { type: "skill", slug: "make-custom-video" }\`, follow it on this same project and stop following the template flow. Record independent authenticated script/ingredient approvals in this chat with the current custom_review.approval_quote phase, review_token and cumulative total; Studio is optional. A handed-off coworker cannot self-approve; do not report approval_not_required.
|
|
1218
|
+
- **Unclear** → read again or ask; never guess and generate. A copy prompt that names this skill
|
|
1219
|
+
is not proof of which kind the project is.
|
|
1220
|
+
|
|
1221
|
+
Continue below only for a verified client-side format or template remix.
|
|
1222
|
+
|
|
1223
|
+
For client-side formats and template remixes, you produce **video** ad creative wherever THIS agent runs and sync
|
|
1224
|
+
the result back to the GooseWorks app over MCP. This document is the **runtime contract** (auth,
|
|
1225
|
+
credits, the media proxies, data I/O, the review gate). A separate **recipe** — the template's
|
|
1226
|
+
\`recipe\`, plus the capability skills it names — tells you *what to make* (the pieces, prompts,
|
|
1227
|
+
models, order of assembly).
|
|
1228
|
+
|
|
1229
|
+
**Division of authority: read both, but when they disagree THIS doc wins on the environment AND the
|
|
1059
1230
|
review/approval flow.** The recipe governs WHAT to make; this doc governs WHEN you pause, generate,
|
|
1060
1231
|
and spend. In particular: a recipe may spell out a **multi-phase, multi-gate** flow — "generate the
|
|
1061
1232
|
still [GATE] → approve → author the prompt [GATE] → approve → render [GATE] → approve", several
|
|
@@ -1064,19 +1235,142 @@ separate pauses. **Do NOT run it that way.** Collapse every one of those gates i
|
|
|
1064
1235
|
and models; ignore its intermediate pauses. This is the exact contradiction that confused past runs
|
|
1065
1236
|
(GOOSE-2542) — there is no ambiguity: review-once wins.
|
|
1066
1237
|
|
|
1067
|
-
|
|
1068
|
-
id). The app NEVER runs you — it is the viewer + review surface; you are the renderer.
|
|
1238
|
+
The app NEVER runs this skill by itself — it is the viewer + review surface; you are the renderer.
|
|
1069
1239
|
|
|
1070
|
-
|
|
1240
|
+
${ENVIRONMENT_IDENTITY}
|
|
1071
1241
|
|
|
1072
|
-
|
|
1073
|
-
\`mcp__gooseworks__*\` tools work over the MCP connection regardless, so wherever this skill
|
|
1074
|
-
says to shell out, use the MCP equivalent:
|
|
1242
|
+
${ASSET_READINESS}
|
|
1075
1243
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1244
|
+
## Where am I running? (decide once, first)
|
|
1245
|
+
|
|
1246
|
+
Check in Bash, without printing any secret value:
|
|
1247
|
+
|
|
1248
|
+
\`\`\`bash
|
|
1249
|
+
[ -n "$GW_MEDIA_PROXY_TOKEN" ] && echo sandbox || echo local
|
|
1250
|
+
command -v gooseworks >/dev/null && echo cli || echo no-cli
|
|
1251
|
+
\`\`\`
|
|
1252
|
+
|
|
1253
|
+
| Mode | How you know | Skills / atoms | Media-proxy auth |
|
|
1254
|
+
|---|---|---|---|
|
|
1255
|
+
| **GooseWorks sandbox** | \`GW_MEDIA_PROXY_TOKEN\` is set | \`catalog_fetch { type: "skill" }\` | env: \`GW_MEDIA_PROXY_TOKEN\` + \`GW_*_PROXY_URL\` |
|
|
1256
|
+
| **Local, CLI installed** | \`gooseworks\` on PATH | \`gooseworks fetch <slug>\` or \`catalog_fetch\` | \`~/.gooseworks/credentials.json\` |
|
|
1257
|
+
| **Local, no CLI** (Claude desktop app / Codex without login) | neither | \`catalog_fetch { type: "skill" }\` | \`~/.gooseworks/credentials.json\` if present, else **paid media over the MCP** (below) |
|
|
1258
|
+
|
|
1259
|
+
The \`gooseworks\` CLI and \`~/.gooseworks/credentials.json\` are **optional**. Everything this skill
|
|
1260
|
+
needs from the app goes through the GooseWorks MCP tools below; the atoms' \`media_proxy.py\` reads
|
|
1261
|
+
credentials.json when it exists and falls back to the \`GW_MEDIA_PROXY_TOKEN\` env otherwise.
|
|
1262
|
+
|
|
1263
|
+
### Paid media over the MCP: no key, no CLI needed
|
|
1264
|
+
|
|
1265
|
+
Every paid generation goes through the GooseWorks media proxy and is billed to the project. **You
|
|
1266
|
+
never need FAL_KEY, an ElevenLabs key or \`fal_client\`.** An atom, a recipe or an open-source
|
|
1267
|
+
skill that lists \`FAL_KEY\` in its environment is describing a standalone setup; here the proxy
|
|
1268
|
+
satisfies it. Never stop, and never ask anyone to set a key, because one is missing.
|
|
1269
|
+
|
|
1270
|
+
**A one-off image or clip — call the MCP directly.** A frame placed in a laptop, a product cutout,
|
|
1271
|
+
a creator still, a restyle, an animated shot, with any fal model (Nano Banana, GPT-image, Seedream,
|
|
1272
|
+
Seedance, Kling). No atom script is needed:
|
|
1273
|
+
|
|
1274
|
+
1. A local input (a frame pulled from a screen recording, a screenshot) must be a public URL first:
|
|
1275
|
+
\`media_upload { brand_id, scope: "video_project", scope_id: project_id, source: { type: "file" | "bytes", … } }\`
|
|
1276
|
+
(no \`path\`). For a file, PUT its bytes to \`upload.url\` with \`upload.required_headers\`,
|
|
1277
|
+
then call \`media_confirm { brand_id, media_id: media.id }\`. Use the returned \`media.url\`.
|
|
1278
|
+
2. \`data_post_provider { provider: "fal", path: <model id, e.g. "fal-ai/nano-banana/edit">, body: <model input>, project_id }\`
|
|
1279
|
+
returns \`{ job_id: "fal:<request_id>" }\`. Pass an \`idempotency_key\` so a retry isn't billed twice.
|
|
1280
|
+
3. Poll \`job_get { job_id }\` every few seconds until \`complete\`; the \`*.fal.media\` URLs are in
|
|
1281
|
+
\`result\`. Download each and QC it (open the image) before using it.
|
|
1282
|
+
|
|
1283
|
+
For a set that must match (every laptop shot, every creator still), use ONE prompt and the same
|
|
1284
|
+
model for all of them and change only the input image. Voice and music:
|
|
1285
|
+
\`data_post_provider { provider: "elevenlabs", path: "/v1/text-to-speech/{voice_id}" | "/v1/music", body, project_id }\`
|
|
1286
|
+
(synchronous; the audio lands in the project folder).
|
|
1287
|
+
|
|
1288
|
+
\`photos_generate\` is **not** a general image tool: it only photographs a physical catalog
|
|
1289
|
+
product (apparel, beauty, CPG) and needs a \`product_id\`. A software screenshot or app mockup is a
|
|
1290
|
+
fal image edit, above.
|
|
1291
|
+
|
|
1292
|
+
**Atom scripts — the MCP relay.** With neither \`GW_MEDIA_PROXY_TOKEN\` nor
|
|
1293
|
+
\`~/.gooseworks/credentials.json\`, the atoms' \`media_proxy.py\` RELAYS each paid call through you
|
|
1294
|
+
instead of calling the proxies over HTTP. Before running any atom,
|
|
1295
|
+
\`export GW_PROJECT_ID=<project_id> GW_BRAND_ID=<brand_id>\` (every call is billed to that
|
|
1296
|
+
project). When a script **exits with code 3** it wrote a request file under
|
|
1297
|
+
\`working/mcp-requests/\`: make exactly that MCP call — fal:
|
|
1298
|
+
\`data_post_provider { provider: "fal", path, body, project_id }\` then \`job_get { job_id }\` until
|
|
1299
|
+
\`complete\`, saving \`result.output\`; ElevenLabs: \`data_post_provider { provider: "elevenlabs", … }\`,
|
|
1300
|
+
saving the reply; a local file: \`media_upload\` with its bytes, saving \`{"url": …}\`. Write that JSON
|
|
1301
|
+
to the request's \`save_result_to\` and **re-run the same command**; repeat until the script
|
|
1302
|
+
finishes. Same server proxy and price as the CLI path. If the CLI is logged in to a DIFFERENT
|
|
1303
|
+
environment than this MCP connector (prod vs staging), set \`GW_MEDIA_VIA=mcp\` so the spend lands
|
|
1304
|
+
where the project lives.
|
|
1305
|
+
|
|
1306
|
+
## MCP tools — canonical names (use these)
|
|
1307
|
+
|
|
1308
|
+
Use the canonical GooseWorks MCP tools. Legacy names are listed only as a fallback for an older
|
|
1309
|
+
client that does not expose the canonical tool; never mix both for one step.
|
|
1310
|
+
|
|
1311
|
+
| Step | Canonical tool (use this) | Legacy fallback |
|
|
1312
|
+
|---|---|---|
|
|
1313
|
+
| Read a project / batch | \`video_project_read { brand_id, project_id }\` / \`video_project_read { brand_id, batch_id }\` | \`get_ad_project\` / \`get_ad_video_batch\` |
|
|
1314
|
+
| Template recipe | \`catalog_fetch { type: "template", slug: <source_sample_id> }\` | \`get_ad_template\` |
|
|
1315
|
+
| Capability skill (atom) + its scripts | \`catalog_fetch { type: "skill", slug }\` | \`gooseworks fetch <slug>\` / \`fetch_skill\` |
|
|
1316
|
+
| Brand kit, products, rules | \`brand_read { brand_id, sections: ["summary","kit","products","learnings"] }\` | \`brand_get_context\` / \`get_brand_kit\` |
|
|
1317
|
+
| Save a brand rule (a correction) | \`brand_update { brand_id, knowledge_intent: "user_correction", user_statement: <the user's exact words>, patch: { facts: [{ id?, kind, text }] } }\` | none |
|
|
1318
|
+
| Mirror the review set | \`video_project_upsert { brand_id, project_id, patch: { script: { script_drafts, script } } }\` | \`update_ad_project_script\` |
|
|
1319
|
+
| Project assets | \`video_project_upsert { …, patch: { assets: [...] } }\` | \`update_ad_project_asset\` |
|
|
1320
|
+
| Progress note | \`video_project_upsert { …, patch: { message: { role: "agent", content } } }\` | \`append_project_message\` |
|
|
1321
|
+
| Batch status | \`video_project_upsert { brand_id, batch_id, patch: { batch: { status } } }\` | \`update_ad_video_batch\` |
|
|
1322
|
+
| Upload a file to the project | \`media_upload { brand_id, scope: "video_project", scope_id: project_id, kind, path, source: { type: "file", filename, content_type } }\` → PUT → \`media_confirm { brand_id, media_id: media.id }\` | \`get_upload_url\` / \`get_ad_upload_url\` |
|
|
1323
|
+
| Save / find a finished piece (resume) | \`media_upload { …, path, ingredient_key, input_digest }\` / \`media_list { brand_id, scope: "video_project", scope_id: project_id, ingredient_key_prefix: "" }\` (see "Save as you go") | none |
|
|
1324
|
+
| Open the render row | \`${exports.RENDER_ROW_TOOL} { brand_id, project_id, ${exports.RENDER_OPEN_ARGS} }\` (no \`dry_run\`; returns \`render_id\`) | \`submit_render { project_id, kind: "full" }\` |
|
|
1325
|
+
| Update the render row | \`${exports.RENDER_ROW_TOOL} { brand_id, project_id, ${exports.RENDER_UPDATE_KEY}: { render_id, status, output_url?, thumbnail_url?, error_message?, quality_status?, quality_report? } }\` | \`update_render_status\` |
|
|
1326
|
+
| Pin the final render | \`video_project_upsert { brand_id, project_id, patch: { final_render_id: render_id } }\` | \`set_final_render\` |
|
|
1327
|
+
| Credits / identity | \`account_whoami\` | \`get_ad_credits\` |
|
|
1328
|
+
|
|
1329
|
+
\`list_accessible_scopes\` and the \`target: { type: "agent", agent_id }\` dance are **not needed**:
|
|
1330
|
+
\`media_upload\` with \`scope: "video_project"\` derives the right storage key (org-default Ads agent
|
|
1331
|
+
+ brand slug + project folder) on the server.
|
|
1332
|
+
|
|
1333
|
+
## Running in a GooseWorks sandbox
|
|
1334
|
+
|
|
1335
|
+
You are an agent inside the user's GooseWorks workspace sandbox (Claude Code harness, Bash, the
|
|
1336
|
+
GooseWorks MCP with canonical tools only). There is **no \`gooseworks\` CLI** and **no
|
|
1337
|
+
\`~/.gooseworks/credentials.json\`**. The environment carries \`GW_MEDIA_PROXY_TOKEN\`, \`GW_API_BASE\`,
|
|
1338
|
+
\`GW_FAL_PROXY_URL\`, \`GW_FAL_STORAGE_PROXY_URL\`, \`GW_ELEVENLABS_PROXY_URL\`, \`GW_WHISPER_PROXY_URL\`
|
|
1339
|
+
and usually \`GW_PROJECT_ID\`.
|
|
1340
|
+
|
|
1341
|
+
- **Formats:** only formats that need **no Chromium** run here — podcast skits and UGC /
|
|
1342
|
+
talking-head formats (ffmpeg + PIL assembly). If the template's recipe or an atom needs
|
|
1343
|
+
Playwright/Chromium with no PIL fallback (iMessage / ChatGPT / Notes phone mockups,
|
|
1344
|
+
hyperframes, HTML recorders), **stop before any spend** and say so plainly: "This format needs
|
|
1345
|
+
a browser renderer that this workspace doesn't have. Run it in your own Claude Code with the
|
|
1346
|
+
GooseWorks CLI (\`goose-video-local\`), or pick a podcast/UGC format." Do not half-render.
|
|
1347
|
+
- **Toolchain:** check \`ffmpeg -version\` and \`ffprobe -version\`. Install Python deps only when an
|
|
1348
|
+
atom needs them: \`pip install --quiet pillow\` when a render atom uses PIL (captions, end card),
|
|
1349
|
+
\`pip install --quiet requests\` if \`import requests\` fails. Never \`npx playwright install\` here.
|
|
1350
|
+
- **Atoms:** \`catalog_fetch { type: "skill", slug }\` returns \`content\`, \`scripts\`, \`files\` and
|
|
1351
|
+
\`dependencySkills\`. Write \`content\` to \`/tmp/gooseworks-scripts/<slug>/SKILL.md\`, each
|
|
1352
|
+
\`scripts\` entry to \`/tmp/gooseworks-scripts/<slug>/scripts/<name>\` and each \`files\` entry to
|
|
1353
|
+
\`/tmp/gooseworks-scripts/<slug>/<name>\` (keep the key as the relative path; do the same for
|
|
1354
|
+
each dependency skill). Run scripts from their \`scripts/\` folder so \`import media_proxy\` resolves.
|
|
1355
|
+
- **Project attribution:** \`export GW_PROJECT_ID=<project_id>\` in the shell **before running any
|
|
1356
|
+
atom** for that project (re-export per concept in a batch). The proxies bill the workspace
|
|
1357
|
+
agent and attribute spend to that project.
|
|
1358
|
+
- **Never call a provider with a raw key.** All FAL / ElevenLabs / Whisper traffic goes through
|
|
1359
|
+
the \`GW_*_PROXY_URL\` bases with \`GW_MEDIA_PROXY_TOKEN\`. \`FAL_KEY\` / \`ELEVENLABS_API_KEY\` in
|
|
1360
|
+
this environment are proxy tokens, not provider keys — never send them to \`fal.run\`,
|
|
1361
|
+
\`queue.fal.run\` or \`api.elevenlabs.io\` directly. Never print any token.
|
|
1362
|
+
- **Whisper for QC:** \`export OPENAI_BASE_URL="$GW_WHISPER_PROXY_URL/v1"\` before running
|
|
1363
|
+
\`review-ugc-render\` / \`watch\` transcripts.
|
|
1364
|
+
- **The operator may be absent.** Keep every quality gate and checkpoint below, but decide
|
|
1365
|
+
defaults yourself from the project brief and the recipe, and record them in the review set.
|
|
1366
|
+
Ask in the chat **only for a true taste call** (a creative choice the brief and recipe leave
|
|
1367
|
+
open and that changes the ad), never for a mechanical or recoverable decision. The Step 3
|
|
1368
|
+
approval still applies: ask for it in this chat and record the yes with
|
|
1369
|
+
\`video_project_upsert patch.approve\`.
|
|
1370
|
+
- **Outputs:** keep working files under \`/tmp/gooseworks-video/<project_id>/\` (local disk — never
|
|
1371
|
+
the s3fs workspace mount, which is slow and can drop writes); anything the user must see goes to
|
|
1372
|
+
the project via \`media_upload\` (never leave the result only in the sandbox). Every paid piece is
|
|
1373
|
+
also SAVED to the project as soon as it passes QC — see "Save as you go — and resume".
|
|
1080
1374
|
|
|
1081
1375
|
## Report problems so we can fix them (telemetry — do this, don't skip it)
|
|
1082
1376
|
|
|
@@ -1089,97 +1383,285 @@ the skill. It's fire-and-forget, never counts against you, and never blocks your
|
|
|
1089
1383
|
\`export GW_RUN_ID="vid-<project_or_batch_id>"\` (and \`export GW_SKILL="<recipe-slug>"\`) in the
|
|
1090
1384
|
shell you render from. The media proxies read \`GW_RUN_ID\` automatically.
|
|
1091
1385
|
- **CLI present →** \`gooseworks log "<what happened>" --event-type <type> --level error --details '{"error":"...","step":"...","model":"..."}'\`
|
|
1092
|
-
- **No CLI (
|
|
1093
|
-
|
|
1386
|
+
- **No CLI (sandbox / cowork) →** from any atom's \`scripts/\` folder:
|
|
1387
|
+
\`python3 -c 'from media_proxy import gw_log; gw_log("<what happened>", event_type="blocker", level="error", details={"step": "..."})'\`
|
|
1388
|
+
(or the \`log_cli_event\` MCP tool if your client has it).
|
|
1389
|
+
- Event types: \`api_failure\` (a proxy/model call failed) · \`missing_input\` · \`blocker\` ·
|
|
1094
1390
|
\`confusion\` (unclear/contradictory instruction) · \`error\` (a bug) · \`step\`/\`info\` (progress notes).
|
|
1095
|
-
- Put the **real error text + the step you were on** in
|
|
1391
|
+
- Put the **real error text + the step you were on** in the details. Paid FAL/ElevenLabs calls
|
|
1096
1392
|
ALREADY auto-log their own failures, so focus your manual logs on what the proxy can't see:
|
|
1097
1393
|
missing inputs, confusing/contradictory recipe instructions, toolchain/setup failures, and bugs.
|
|
1098
1394
|
- Logging is FOR US — it does not replace telling the user. When a problem blocks the run, still
|
|
1099
|
-
explain it to the user (and ask if you need a decision); just also
|
|
1395
|
+
explain it to the user (and ask if you need a decision); just also log it so we can fix the skill.
|
|
1100
1396
|
|
|
1101
1397
|
## Prerequisite — MCP + a render toolchain (Phase 0 preflight)
|
|
1102
1398
|
|
|
1103
|
-
- The
|
|
1399
|
+
- The GooseWorks MCP tools are REQUIRED. If they're unavailable, stop and tell the user
|
|
1104
1400
|
to connect the GooseWorks MCP server (or run \`gooseworks install --claude --mcp\` on the CLI)
|
|
1105
1401
|
and restart. There is no REST fallback.
|
|
1106
|
-
- **The render runs wherever THIS agent runs, and it needs a real toolchain
|
|
1107
|
-
\`ffprobe\`
|
|
1402
|
+
- **The render runs wherever THIS agent runs, and it needs a real toolchain:** \`ffmpeg\` +
|
|
1403
|
+
\`ffprobe\` always, plus a Playwright **Chromium** for browser-rendered formats (phone mockups,
|
|
1404
|
+
HTML end cards without a PIL fallback). Establish it in this priority order, and do NOT start
|
|
1108
1405
|
rendering until one is confirmed:
|
|
1109
|
-
1. **
|
|
1110
|
-
Chromium
|
|
1111
|
-
2. **
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1406
|
+
1. **Sandbox →** see "Running in a GooseWorks sandbox": ffmpeg + ffprobe (+ PIL on demand); no
|
|
1407
|
+
Chromium, so browser formats stop there.
|
|
1408
|
+
2. **CLI present →** run \`gooseworks doctor --no-browser\` for login, MCP, Node 18+, ffmpeg
|
|
1409
|
+
with libx264 + libass, and ffprobe. This is common setup only. After fetching the selected
|
|
1410
|
+
capabilities in Step 2, check each browser renderer's actual launch before ANY paid
|
|
1411
|
+
ingredient. An unscoped \`gooseworks doctor\` checks only the calling folder's browser;
|
|
1412
|
+
it cannot certify a different fetched renderer.
|
|
1413
|
+
3. **No CLI →** check the toolchain yourself: \`node --version\` (18+), \`ffmpeg -version\`,
|
|
1414
|
+
\`ffprobe -version\`, plus \`ffmpeg -hide_banner -encoders\` (libx264) and
|
|
1415
|
+
\`ffmpeg -hide_banner -filters\` (ass). After fetching, use the exact-package free launch
|
|
1416
|
+
check in Step 2; resolving a package or finding a cache folder does not prove its browser
|
|
1417
|
+
can launch. The \`watch\` QC step later needs the same ffmpeg and,
|
|
1418
|
+
for transcripts, a Whisper backend — without one it degrades to frames only.
|
|
1419
|
+
4. **Docker available →** the most reliable way to get the toolchain on a host that lacks it:
|
|
1420
|
+
run the render steps inside the prebuilt image
|
|
1116
1421
|
**\`ghcr.io/gooseworks-ai/goose-video-render\`** (ffmpeg + ffprobe + Playwright Chromium baked
|
|
1117
|
-
in), mounting the project working directory.
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
(c) run this skill locally in your own Claude Code where the toolchain is available."* Do not
|
|
1422
|
+
in), mounting the project working directory. (Nested Docker is usually disabled inside
|
|
1423
|
+
managed sandboxes — treat this as an option, not a guarantee.)
|
|
1424
|
+
5. **None of the above works →** STOP and tell the user plainly, e.g.: *"Video rendering needs
|
|
1425
|
+
ffmpeg (and, for this format, a Playwright Chromium) on the machine running this agent. This
|
|
1426
|
+
environment doesn't have them and I can't install them here. Options: (a) enable Docker so I
|
|
1427
|
+
can use the goose-video-render image, (b) install ffmpeg + \`npx playwright install chromium\`,
|
|
1428
|
+
or (c) run this skill in your own Claude Code where the toolchain is available."* Do not
|
|
1125
1429
|
half-render or fake a result. Static image ads (the \`goose-ads\` skill) do NOT need any of this
|
|
1126
1430
|
and work anywhere — offer that as the fallback if they just want an ad now.
|
|
1127
1431
|
|
|
1128
1432
|
## Identity, token, credits
|
|
1129
1433
|
|
|
1130
|
-
-
|
|
1131
|
-
Never print
|
|
1132
|
-
- **
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
\`
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
\`<ADS_AGENT>/files/agent-config/brands/<brand_slug>/projects/<project_id>/<path>\`
|
|
1146
|
-
(see backend \`resolveProjectFileKey\`). So EVERY publish/preview \`path\` MUST be prefixed with
|
|
1147
|
-
\`agent-config/brands/<brand_slug>/projects/<project_id>/\` — e.g. upload to
|
|
1148
|
-
\`agent-config/brands/<brand_slug>/projects/<project_id>/working/final.mp4\`, NEVER bare
|
|
1149
|
-
\`working/final.mp4\`. A bare path 404s in the app even though the render row AND a bare-path
|
|
1150
|
-
\`get_download_url\` both "succeed" (they resolve the wrong key). The render \`output_url\` still
|
|
1151
|
-
stays the project-relative \`...render-file?path=working/final.mp4\` — the route re-prepends the
|
|
1152
|
-
prefix itself. Always verify with \`get_download_url\` on the FULL \`agent-config/...\` path (must
|
|
1153
|
-
be non-empty; curl it for HTTP 200) BEFORE marking the render complete.
|
|
1434
|
+
- **Sandbox:** the token is \`GW_MEDIA_PROXY_TOKEN\` (already scoped to this workspace's agent and
|
|
1435
|
+
org); the API base is \`GW_API_BASE\`. Never print either.
|
|
1436
|
+
- **Local:** read \`~/.gooseworks/credentials.json\` → \`api_key\` (your agent token), \`api_base\`,
|
|
1437
|
+
\`agent_id\`. Never print the token.
|
|
1438
|
+
- **Uploads go through \`media_upload\` with \`scope: "video_project"\`** — pass \`path\` = the
|
|
1439
|
+
project-relative path (\`working/final.mp4\`, \`working/review/end-card.png\`). The server stores it
|
|
1440
|
+
in the project folder of the org-default Ads agent (where the app's render-file route reads) and
|
|
1441
|
+
returns \`upload.url\` (presigned PUT), \`upload.required_headers\` and
|
|
1442
|
+
\`upload.render_file_url\`. PUT the bytes with exactly those headers, check the PUT returned
|
|
1443
|
+
2xx, then call \`media_confirm { brand_id, media_id: media.id }\` and require success before
|
|
1444
|
+
using the file in ingredients or completing a render. This applies to project-path and
|
|
1445
|
+
path-less file uploads. Confirmation verifies the stored file; Goose performs this tool step
|
|
1446
|
+
without asking the user for another approval. On failure, report or repair the upload before
|
|
1447
|
+
continuing. Never hand-build storage paths or agent prefixes; a bare workspace upload is
|
|
1448
|
+
invisible in the app.
|
|
1154
1449
|
- Media generation (FAL / ElevenLabs) through the GooseWorks proxies is the **REAL spend** — billed
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
that master and PINNING it. Call \`
|
|
1450
|
+
per call as you generate (Step 4). The render row (\`${exports.RENDER_ROW_TOOL} ${exports.RENDER_OPEN_ARGS}\`) charges the flat
|
|
1451
|
+
**video base fee once, when a full render is reported \`complete\`**. Open one row immediately
|
|
1452
|
+
after recorded approval, BEFORE paid production (Step 4.1), and reuse that render_id for progress
|
|
1453
|
+
and completion. Never open a second row on a guess (a second completed row bills again). The final-video QC gate (Step 4.3) then sits between
|
|
1454
|
+
that master and PINNING it. Call \`account_whoami\` first to see the credit balance.
|
|
1455
|
+
|
|
1456
|
+
## Save as you go — and resume (never pay twice for a piece)
|
|
1457
|
+
|
|
1458
|
+
A sandbox can die mid-run (timeout, restart, a new session picks the project up). Anything that
|
|
1459
|
+
lives only in \`/tmp\` is then gone, and regenerating it pays again. So: **save every piece to the
|
|
1460
|
+
project the moment it passes its QC, and start every run by loading what is already saved.**
|
|
1461
|
+
Working files stay in \`/tmp/gooseworks-video/<project_id>/\` (never the s3fs workspace mount);
|
|
1462
|
+
the project is the durable copy.
|
|
1463
|
+
|
|
1464
|
+
**Ingredient keys.** Give every planned piece a stable key before you generate it, the same on
|
|
1465
|
+
every run: \`vo/scene-03\`, \`vo/sample-her\`, \`still/her-base\`, \`still/scene-05\`,
|
|
1466
|
+
\`clip/scene-05\` (a lipsync / video clip), \`music/bed\`, \`endcard\`, \`captions\`, \`final\`,
|
|
1467
|
+
\`final-thumb\`. Upload path = \`working/<role>/<file>\` (\`working/vo/scene-03.mp3\`,
|
|
1468
|
+
\`working/clip/scene-05.mp4\`); the review set keeps its \`working/review/<name>\` paths.
|
|
1469
|
+
|
|
1470
|
+
**Input digest.** Name the exact inputs of each generation with \`input_digest\` from
|
|
1471
|
+
\`media_proxy\` (in every media capability's \`scripts/\` folder):
|
|
1472
|
+
|
|
1473
|
+
\`\`\`python
|
|
1474
|
+
from media_proxy import input_digest
|
|
1475
|
+
digest = input_digest(model_path, args) # the model + the EXACT payload you send
|
|
1476
|
+
\`\`\`
|
|
1477
|
+
|
|
1478
|
+
Hash only what decides the output (prompt, voice_id, model_id, seed, duration, aspect…). An input
|
|
1479
|
+
that is a presigned or proxy URL changes every run, so swap it for that input's own identity
|
|
1480
|
+
before hashing, e.g. \`{**args, "image_url": {"ingredient": "still/her-base", "digest": her_digest}}\`
|
|
1481
|
+
— then a changed still correctly invalidates every clip made from it. For a piece you build
|
|
1482
|
+
locally (ffmpeg stitch, PIL end card, captions) use \`input_digest("local/<step>", {params,
|
|
1483
|
+
inputs: {key: digest, …}})\`.
|
|
1484
|
+
|
|
1485
|
+
**1. At the START of every run (first run, resume, new sandbox), load what exists — one call:**
|
|
1486
|
+
\`media_list { brand_id, scope: "video_project", scope_id: project_id, ingredient_key_prefix: "",
|
|
1487
|
+
limit: 100 }\`. It returns ONE compact row per \`ingredient_key\` (the newest):
|
|
1488
|
+
\`{ id, ingredient_key, input_digest, kind, status, mime, bytes, url, path, created_at }\`. Every
|
|
1489
|
+
status except archived is included. Project-path uploads remain \`pending\` until
|
|
1490
|
+
\`media_confirm\` succeeds. Confirm a pending project-path upload before reusing it; if
|
|
1491
|
+
confirmation fails, repair the upload before treating it as a verified save.
|
|
1492
|
+
Page with \`cursor\` if \`next_cursor\` is set (keep the first row you see per key — it is the newest). Also read \`script_drafts.ingredients\` from
|
|
1493
|
+
\`video_project_read\`: it records which pieces were already approved in the review.
|
|
1494
|
+
|
|
1495
|
+
**2. For each planned piece:** compute its digest from the args you WOULD send now. If a saved row
|
|
1496
|
+
has the same \`ingredient_key\` AND the same \`input_digest\`, **download it instead of
|
|
1497
|
+
generating**:
|
|
1498
|
+
|
|
1499
|
+
\`\`\`bash
|
|
1500
|
+
curl -fsSL "$URL" -o /tmp/gooseworks-video/<project_id>/<path> # URL = that row's \`url\`
|
|
1501
|
+
\`\`\`
|
|
1502
|
+
|
|
1503
|
+
That \`url\` is a short-lived (~15 min) presigned S3 GET the server signs for you, so it needs no
|
|
1504
|
+
auth header — download right after listing (list again if it expired). **Never fetch the
|
|
1505
|
+
\`/api/ads/projects/<id>/render-file?path=…\` route from the sandbox:** it needs the app's
|
|
1506
|
+
browser session and answers 401 to a token. Check the file is non-empty and plays (ffprobe for
|
|
1507
|
+
audio/video, open the image); if the download fails or the file is broken, regenerate the piece.
|
|
1508
|
+
Only generate what is missing or whose digest changed — a changed digest means the inputs changed,
|
|
1509
|
+
so the old file is stale.
|
|
1510
|
+
|
|
1511
|
+
**Pass the digest to the proxy too:** \`fal_generate(..., input_digest=digest)\` (and
|
|
1512
|
+
\`fal_generate_video\` / \`fal_whisper\`). A piece that was generated but never saved (the sandbox
|
|
1513
|
+
died between the fal result and the upload) is then handed back by the proxy instead of paid for
|
|
1514
|
+
again, even though its input URLs changed. Only pass \`new_take=True\` when the user wants a
|
|
1515
|
+
different take of the same inputs.
|
|
1516
|
+
|
|
1517
|
+
**3. After EACH piece is generated AND passes its own QC, upload it right away** — don't batch
|
|
1518
|
+
the uploads to the end:
|
|
1519
|
+
\`media_upload { brand_id, scope: "video_project", scope_id: project_id, kind, path:
|
|
1520
|
+
"working/<role>/<file>", ingredient_key, input_digest, source: { type: "file", filename,
|
|
1521
|
+
content_type } }\` → PUT the bytes to \`upload.url\` with \`upload.required_headers\` →
|
|
1522
|
+
\`media_confirm { brand_id, media_id: media.id }\`. Require a successful confirmation.
|
|
1523
|
+
Kind: \`audio\` (VO), \`music\`, \`image\` (a still),
|
|
1524
|
+
\`video\` (a clip), \`endcard\`, \`document\` (captions / a JSON sidecar), \`render\` (the master),
|
|
1525
|
+
\`thumbnail\`. Re-uploading the same key is fine — the newest wins. A piece that FAILED QC is never
|
|
1526
|
+
uploaded under its key. **Save a piece's sidecars with it** under \`<key>.<name>\` — e.g. the VO's
|
|
1527
|
+
char-level timestamps as \`vo/scene-03.timestamps\` (\`kind: "document"\`, same digest). Captions are
|
|
1528
|
+
built from them; without them a resumed run has to fall back to Whisper timings, which mis-case
|
|
1529
|
+
brand names.
|
|
1530
|
+
|
|
1531
|
+
**4. Keep the approved review unchanged during production.** Record each confirmed piece's
|
|
1532
|
+
\`media_id\`, \`path\`, \`ingredient_key\` and \`input_digest\` locally; the confirmed media rows
|
|
1533
|
+
and render progress are the durable resume record. Before approval, mirror the draft ingredients
|
|
1534
|
+
as part of Step 3. After approval, do NOT write \`patch.script\` or \`script_drafts\` during any
|
|
1535
|
+
paid production, QC or repair step, or while provider work is pending. A review-set write clears
|
|
1536
|
+
approval and can stop the next paid step, including a batch concept. Save the final descriptive
|
|
1537
|
+
review once all paid work is finished and settled, as Step 4.5 requires.
|
|
1538
|
+
|
|
1539
|
+
Any material creative change or ANY change the user asked for in this chat must stop production:
|
|
1540
|
+
save the changed complete plan and full credit total, then obtain fresh approval before continuing.
|
|
1541
|
+
Never reuse the earlier yes for a changed plan. A repair that restores the approved choices can
|
|
1542
|
+
continue within the existing allowance and Stop guards without rewriting the approved review.
|
|
1543
|
+
|
|
1544
|
+
The \`final\` master and \`final-thumb\` poster (Step 4.4) carry \`ingredient_key\` too, so a
|
|
1545
|
+
resumed run that finds a passing \`final\` with the same digest only needs to publish.
|
|
1546
|
+
|
|
1547
|
+
## Finished video, interrupted saving — resume the same version
|
|
1548
|
+
|
|
1549
|
+
If the final already passed QC and the customer says **"finish saving this video"**, start here.
|
|
1550
|
+
Do not restart the recipe, choose another format, open another render, generate a take, or run a
|
|
1551
|
+
paid transcript check again. Saving uses the **same render_id**. The ordinary first-completion
|
|
1552
|
+
video fee can still apply; its existing same-render idempotency prevents a duplicate fee. Never
|
|
1553
|
+
promise that all saving is free.
|
|
1554
|
+
|
|
1555
|
+
**Before the first final upload**, save the final review set and production manifest from Step
|
|
1556
|
+
4.5/4.6 (including any QC repairs) only after all paid work is finished and settled. Preserve the
|
|
1557
|
+
approved creative choices; a material change requires the changed-plan approval flow first.
|
|
1558
|
+
After that final save, start no new provider or render work: prepare the checkpoint and finish
|
|
1559
|
+
the same opened render using its existing finishing allowance and guards. A released allowance
|
|
1560
|
+
or stopped/capped render requires normal recovery; never reopen or bypass approval.
|
|
1561
|
+
Write a durable local checkpoint outside the fetched-scripts cache. Keep the actual
|
|
1562
|
+
final, JPEG poster, structured passing quality report, and review evidence files alongside it in
|
|
1563
|
+
a retained local working folder. Record their SHA-256 **at the time those exact bytes pass QC**;
|
|
1564
|
+
never attach an old verdict to a newly hashed replacement. A sandbox's local disk can disappear:
|
|
1565
|
+
this checkpoint recovers connection interruptions, not lost storage. Save ingredients remotely
|
|
1566
|
+
as above and tell the customer if the checked local output is no longer available.
|
|
1567
|
+
|
|
1568
|
+
With the current CLI and its normal login to the selected environment, write a manifest like:
|
|
1569
|
+
|
|
1570
|
+
\`\`\`json
|
|
1571
|
+
{
|
|
1572
|
+
"brand_id": "<owning brand>", "project_id": "<same project>", "render_id": "<already opened render>",
|
|
1573
|
+
"input_digest": "<assembly input digest>",
|
|
1574
|
+
"final_path": "/absolute/retained/working/final.mp4",
|
|
1575
|
+
"poster_path": "/absolute/retained/working/final-thumb.jpg",
|
|
1576
|
+
"qc": {
|
|
1577
|
+
"final_sha256": "<64 lowercase hex characters recorded during final QC>",
|
|
1578
|
+
"poster_sha256": "<64 lowercase hex characters recorded during poster review>",
|
|
1579
|
+
"report_path": "/absolute/retained/working/quality-report.json",
|
|
1580
|
+
"evidence_paths": ["/absolute/retained/working/review/finished-ad.json"]
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
\`\`\`
|
|
1584
|
+
|
|
1585
|
+
Run \`gooseworks video-save prepare --manifest <manifest.json> --checkpoint <retained/save-<render_id>.json>\`
|
|
1586
|
+
before any upload, then \`gooseworks video-save resume --checkpoint <same checkpoint>\` for both
|
|
1587
|
+
normal saving and recovery. The helper uses the saved login and canonical MCP connection; it does
|
|
1588
|
+
not copy credentials or use a raw Ads REST fallback. Its atomic private checkpoint binds the
|
|
1589
|
+
environment, account, project owner, project/render, final/poster bytes, assembly digest, passing
|
|
1590
|
+
quality report, review set, evidence files and stage receipts. Preserve it until delivery. Never
|
|
1591
|
+
edit the checkpoint to clear a failed check. Keep checkpoint and manifest files out of commits.
|
|
1592
|
+
|
|
1593
|
+
The helper reads remote state before writes. It keeps \`final\` / \`final-thumb\` ingredient keys
|
|
1594
|
+
and writes per-render paths \`working/final-<render_id>.mp4\` / \`working/final-<render_id>-thumb.jpg\`
|
|
1595
|
+
so saving this version cannot overwrite an older one. It confirms pending uploads, checks remote
|
|
1596
|
+
bytes against the QC hashes, and reuses confirmed media. A request with missing stored bytes can
|
|
1597
|
+
get a fresh upload URL at that same path. A lost completion reply is unknown until project read
|
|
1598
|
+
confirms the same render's output and passing report; then only final selection remains.
|
|
1599
|
+
|
|
1600
|
+
**No CLI / a host-selected MCP connection:** keep the same protocol with the host's canonical
|
|
1601
|
+
tools. Atomically write versioned JSON locally (write a private same-directory temporary file,
|
|
1602
|
+
flush/fsync, rename, then fsync the directory) before any upload and between each upload request,
|
|
1603
|
+
PUT, confirmation, completion and final selection. Persist only public environment identity,
|
|
1604
|
+
account/owner/project/render IDs, file paths/sizes/hashes, input digest, exact quality report and
|
|
1605
|
+
evidence fingerprints, saved-review digest, media IDs and stage states; **no tokens, session IDs,
|
|
1606
|
+
signed URLs or auth headers**. On reconnect call \`account_whoami\`,
|
|
1607
|
+
\`video_project_read { brand_id, project_id, include: ["renders"] }\` and
|
|
1608
|
+
\`media_list { brand_id, scope: "video_project", scope_id: project_id, ingredient_key, input_digest }\`
|
|
1609
|
+
on the original connection. Compare identities, local/evidence hashes and saved review; verify
|
|
1610
|
+
confirmed remote bytes too. Treat a lost reply as unknown and read back before retrying. Use only
|
|
1611
|
+
the missing \`media_upload\` → PUT → \`media_confirm\`, same-render \`video_render_run { render: … }\`
|
|
1612
|
+
callback and \`video_project_upsert\` with only \`patch.final_render_id\` and
|
|
1613
|
+
\`patch.final_selection_guard: { expected_final_render_id, expected_review_digest }\`; never a
|
|
1614
|
+
render-open call. The expected final is the last read's pin, including explicit \`null\`; the
|
|
1615
|
+
review digest is SHA-256 of recursively sorted-key JSON
|
|
1616
|
+
\`{script: project.script ?? null, script_drafts: project.script_drafts ?? null}\` saved at checkpoint
|
|
1617
|
+
creation. First confirm the selected server advertises this guard in \`tools/list\`. If absent,
|
|
1618
|
+
stop and request the normal server update/reconnect; an unguarded pin is unsafe. A
|
|
1619
|
+
\`final_selection_conflict\` means the choice or review changed: keep the checkpoint and explain
|
|
1620
|
+
that saving stopped without overwriting it. Retain the
|
|
1621
|
+
strict Step 4.3/4.4 report and Step 4.5 review-set requirements.
|
|
1622
|
+
|
|
1623
|
+
If authorization expired, reconnect/sign in through the normal host/CLI flow on the original
|
|
1624
|
+
connection, then resume. Missing tools are a reconnect requirement, not permission to switch
|
|
1625
|
+
environments. Missing/changed files, stale/missing QC, changed account/owner/review, another final
|
|
1626
|
+
selection, or a stopped/capped/failed/blocked render require diagnosis; do not reopen it or
|
|
1627
|
+
regenerate automatically. If saving stops, say: **"Your video is finished locally, but saving to
|
|
1628
|
+
Goose was interrupted."** Link the actual local file and preserve its checkpoint. Claim saved
|
|
1629
|
+
delivery only after a fresh project read verifies completion and selection of that exact render.
|
|
1160
1630
|
|
|
1161
1631
|
## Step 0 — project id, or video BATCH id? (fan out before anything else)
|
|
1162
1632
|
|
|
1163
|
-
The handoff
|
|
1633
|
+
The handoff is EITHER a single \`project <id>\` OR a \`video batch <id>\`. A batch is
|
|
1164
1634
|
the app's "N concepts" flow: one composer submission fans out into **N independent concept projects**
|
|
1165
1635
|
(the user picked a concept count, default 3), and the app expects EACH to be rendered. **Handle both:**
|
|
1166
1636
|
|
|
1167
1637
|
- **\`project <id>\`** → you have one project. Treat it as a batch of one and continue to Step 1.
|
|
1168
|
-
- **\`video batch <id>\`** → call \`
|
|
1638
|
+
- **\`video batch <id>\`** → call \`video_project_read { brand_id, batch_id }\`. It returns every child
|
|
1169
1639
|
concept under \`projects[]\` — each is a normal project with its own \`id\`, \`variant_index\`
|
|
1170
1640
|
(Concept 1..N), and its own \`creative_brief\` (the per-concept angle/hook/offer/message). **You
|
|
1171
1641
|
MUST process every concept, not just the first** — dropping concepts 2..N is the #1 batch bug.
|
|
1172
1642
|
|
|
1173
|
-
**Loop shape (
|
|
1174
|
-
1. Run **Step 1 + Step 1.5 + Step 2 + Step 3-assemble** for EACH concept project (each
|
|
1175
|
-
\`project_id\`, brief, and \`working/\` folder — never cross-write
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1643
|
+
**Loop shape (ONE approval for the batch, isolated work per concept):**
|
|
1644
|
+
1. Run **Step 1 + Step 1.5 + Step 2 + Step 2.5 + Step 3-assemble** for EACH concept project (each
|
|
1645
|
+
has its own \`project_id\`, brief, \`GW_PROJECT_ID\` and \`working/\` folder — never cross-write
|
|
1646
|
+
between concepts). The brand read (Step 1 item 3) and \`brand-rules.json\` (Step 1.7) are per
|
|
1647
|
+
BRAND: do them once for the batch and copy the file into each concept's \`working/\`. The read is
|
|
1648
|
+
~90K characters. Step 2.5's angle bank is shared per brand AND product: load or build it once, and
|
|
1649
|
+
give every concept whose angle is \`auto\` a DIFFERENT angle from that list, so the batch is N
|
|
1650
|
+
different ads, not one ad N times.
|
|
1651
|
+
2. Mirror EVERY concept's review set (Step 3's \`video_project_upsert patch.script\` per project),
|
|
1652
|
+
then stop for **ONE** approval in this chat that covers all concepts. Save each render_estimate.total_credits
|
|
1653
|
+
(previews + render + the 200 base fee) and the batch total. Widget: one line; text: card.text_summary. Set the batch to \`review\` (\`video_project_upsert
|
|
1654
|
+
{ brand_id, batch_id, patch: { batch: { status: "review" } } }\`).
|
|
1655
|
+
3. On an explicit yes, record it ONCE for the whole batch: \`video_project_upsert { brand_id, batch_id,
|
|
1656
|
+
patch: { approve: { user_quote: "<their exact words>", total_credits: <the saved total> } } }\`. Check its \`not_ready\` list is
|
|
1657
|
+
empty (a concept listed there has no saved review set: save it, show it, ask again). If they
|
|
1658
|
+
approve only some concepts ("1 and 3 are good, redo 2"), record each approved one with its
|
|
1659
|
+
\`project_id\` instead, and redo the rest. Then set the batch to \`rendering\` and run **Step 4 (the expensive render)** for each
|
|
1660
|
+
concept with up to 8 isolated workers when the machine can sustain them. Retry
|
|
1661
|
+
concurrency_limit after retry_after_seconds; reduce concurrency on a limited machine. Deliver each (Step 5). When every concept is pinned, set the batch to
|
|
1662
|
+
\`complete\`. A concept the Step 4.3 gate leaves \`blocked\` cannot be pinned (a batch concept
|
|
1663
|
+
needs \`passed\`): finish the others, set the batch to \`blocked\`, and tell the user which
|
|
1664
|
+
concepts passed and which are blocked, with each one's failing checks.
|
|
1183
1665
|
|
|
1184
1666
|
If a single concept fails, keep going with the rest, mark that concept blocked, and report which
|
|
1185
1667
|
ones shipped — never abort the whole batch on one bad concept. Everything below (Steps 1–5) is
|
|
@@ -1187,11 +1669,18 @@ written per-project; a batch just runs it N times with the shared approval gate
|
|
|
1187
1669
|
|
|
1188
1670
|
## Step 1 — resolve the project, source, brand
|
|
1189
1671
|
|
|
1190
|
-
1. \`
|
|
1191
|
-
**top-level** \`app_url\` + \`brand_url\` (
|
|
1192
|
-
|
|
1193
|
-
**\`
|
|
1194
|
-
|
|
1672
|
+
1. \`video_project_read { brand_id, project_id }\` → keep \`brand_id\`, \`source_sample_id\`, \`name\`,
|
|
1673
|
+
\`status\`, the **top-level** \`app_url\` + \`brand_url\` (the links you hand the user for the in-app
|
|
1674
|
+
review in Step 3 and delivery in Step 5), AND the user's **\`creative_brief\`**, project
|
|
1675
|
+
**\`assets\`**, \`character_id\`, \`default_voice_id\` — these are the authoritative inputs the user
|
|
1676
|
+
chose in the composer (see Step 1.5). Do NOT discard them. Then \`export GW_PROJECT_ID=<project_id>\`.
|
|
1677
|
+
Keep its saved \`campaign_id\` and \`campaign_concept_id\` too. If the customer explicitly
|
|
1678
|
+
wants to attach this existing video to a known campaign/concept, verify the IDs with the
|
|
1679
|
+
campaign read and use \`video_project_upsert { brand_id, project_id, patch: {
|
|
1680
|
+
campaign_association: { campaign_id, campaign_concept_id? } } }\` as a separate, sole-field
|
|
1681
|
+
patch. Read back the same project and confirm the saved IDs. Linking never needs a new
|
|
1682
|
+
project, render, generation call or approval. A conflicting saved link stays intact; explain
|
|
1683
|
+
it instead of silently moving the video. Leave unlinked legacy videos alone unless asked.
|
|
1195
1684
|
|
|
1196
1685
|
### Step 1.5 — the project brief is AUTHORITATIVE (honor it; don't re-ask)
|
|
1197
1686
|
|
|
@@ -1210,49 +1699,277 @@ for a field the brief leaves empty. Map the fields you WILL honor:
|
|
|
1210
1699
|
assets.
|
|
1211
1700
|
- \`character_id\` → the avatar/creator to use. \`default_voice_id\` → the voice for any VO (put its
|
|
1212
1701
|
NAME in the review \`subtitle\`). Use these instead of picking your own.
|
|
1213
|
-
- \`creative_brief.
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1702
|
+
- \`creative_brief.durationSeconds\` → target length; honor it when the format allows.
|
|
1703
|
+
\`creative_brief.ratio\` → video ads are ALWAYS 9:16 (1080×1920). If the brief asks for another
|
|
1704
|
+
ratio, make 9:16 anyway and say so in the review; never export another size (a recipe's
|
|
1705
|
+
"also 1:1" option included).
|
|
1217
1706
|
- \`polish_policy\` (\`standard\` | \`extra\`) → \`extra\` means spend the extra pass on QC/polish.
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1707
|
+
|
|
1708
|
+
2. \`catalog_fetch { type: "template", slug: <source_sample_id> }\` → the source video: \`media_url\`,
|
|
1709
|
+
\`recipe\`, \`format\` (e.g. "podcast-skit", "imessage"), \`extracted_script\`, \`how_to\`, \`remix_spec\`.
|
|
1710
|
+
3. Brand gate: \`brand_read { brand_id, sections: ["summary", "kit", "products", "learnings"] }\`
|
|
1711
|
+
(older clients: \`brand_get_context\` with the same sections). Ask for all four: the default
|
|
1712
|
+
leaves out the kit and the brand's saved rules, and a video made without them is off-brand.
|
|
1713
|
+
If the kit's \`researchStatus\` (or the brand's \`research_status\`) is \`complete\`, REUSE it —
|
|
1714
|
+
never re-research. If not, run brand research first (\`catalog_fetch { type: "skill", slug:
|
|
1715
|
+
"brand-research" }\`) and follow its stored-pack workflow. Only when that verified pack is
|
|
1716
|
+
saved in the supported research workspace, finalize with
|
|
1717
|
+
\`brand_update { brand_id, patch: { finalize_research: true } }\`, then read the brand back.
|
|
1718
|
+
Never send raw research JSON through \`kit_patch\`: the public tool accepts only the existing
|
|
1719
|
+
\`video_lab\` asset slot there. If there is no verified stored pack, submit researched facts
|
|
1720
|
+
through typed \`patch.knowledge\` / \`patch.kit\` as pending agent proposals; do not pretend
|
|
1721
|
+
research is finalized or its proposals are approved. Then do Step 1.7 with verified facts.
|
|
1722
|
+
|
|
1723
|
+
### Step 1.6 — a remix of a FINISHED video (the project read has a \`remix\` block)
|
|
1724
|
+
|
|
1725
|
+
A project made from **Community videos** remakes another customer's finished video for THIS brand.
|
|
1726
|
+
Its \`video_project_read\` returns a top-level \`remix\` block
|
|
1727
|
+
(\`{ remix_of_project_id, instruction, direction }\`), and \`reference_video_url\` is that finished video.
|
|
1728
|
+
|
|
1729
|
+
- **Watch the reference video first** (download \`reference_video_url\`, pull frames + the transcript).
|
|
1730
|
+
It is the target: match its structure, beat order, pacing, framing, look, voice and tone.
|
|
1731
|
+
- **\`remix.direction\` is its approved review set** (scenes and lines, set/look notes, take prompts,
|
|
1732
|
+
captions, music, voice). Start your review set from it instead of the template's defaults; it
|
|
1733
|
+
already carries every change that customer made to the template.
|
|
1734
|
+
- **\`remix.direction.implementation\`, when present, is HOW it was made**: the pipeline and the model
|
|
1735
|
+
each step used, the style and negative prompts, the character and per-scene still/motion prompts
|
|
1736
|
+
with their guards, voice and music settings, the mix, and \`fixes\` (what went wrong and how it was
|
|
1737
|
+
fixed). Reuse the same models, style/negative prompts and guards, rewrite the prompts' subjects for
|
|
1738
|
+
this brand, and apply every \`fix\` up front so you don't repeat the same mistake.
|
|
1739
|
+
- **Rewrite everything for THIS brand.** "[source brand]", "[source product]", "[link]", "[email]"
|
|
1740
|
+
and "[code]" mark the other customer's details: never write them, and never reuse their claims,
|
|
1741
|
+
numbers, URLs, offers or CTA. Every product, claim, name, image and CTA comes from this brand's
|
|
1742
|
+
kit, products and media. Their footage (screen recordings, product shots) and their creator face
|
|
1743
|
+
are NOT carried over: use this brand's own assets and make a new creator from the description.
|
|
1744
|
+
- Precedence: this project's own \`creative_brief\` and assets (Step 1.5) > \`remix.direction\` >
|
|
1745
|
+
the template recipe's defaults.
|
|
1746
|
+
- In the Step 3 review, say it is a remix of that video and list what you kept vs. changed.
|
|
1747
|
+
|
|
1748
|
+
### Step 1.7 — the brand rules file and the brand assets (every run, before any writing)
|
|
1749
|
+
|
|
1750
|
+
Write \`working/brand-rules.json\` from the Step 1 brand read. Every later step reads THIS file,
|
|
1751
|
+
not your memory of the chat:
|
|
1752
|
+
|
|
1753
|
+
\`\`\`json
|
|
1754
|
+
{
|
|
1755
|
+
"name": "Acme",
|
|
1756
|
+
"pronunciations": [{ "term": "Acme", "say_as": "ak-mee", "learning_id": "…" }],
|
|
1757
|
+
"must_say": [{ "text": "…", "learning_id": "…" }],
|
|
1758
|
+
"never_say": [{ "text": "Never claim it cures insomnia", "learning_id": "…" }],
|
|
1759
|
+
"products": [{ "id": "…", "name": "…", "facts": ["size, flavour, price, ingredients as stored"], "images": ["…"] }],
|
|
1760
|
+
"logo": { "url": "…", "confidence": "<kit.logoConfidence>" },
|
|
1761
|
+
"fonts": ["…"],
|
|
1762
|
+
"palette": ["#…"]
|
|
1763
|
+
}
|
|
1764
|
+
\`\`\`
|
|
1765
|
+
|
|
1766
|
+
- **Video taste is direction, not dialogue.** Before mapping brand rules, extract entries
|
|
1767
|
+
prefixed \`Video preference:\` from both \`learnings\` and \`kit.instructions\` into the verified
|
|
1768
|
+
taste brief, including accepted proposals. They govern pacing, voices, captions, visuals and
|
|
1769
|
+
format choice; do not copy them into \`must_say\` / \`never_say\` or read them aloud. Carry the
|
|
1770
|
+
brief into the choices, scene planning and review.
|
|
1771
|
+
- **Sources.** \`learnings\` are the brand's saved rules (the user's past corrections among them):
|
|
1772
|
+
\`must\` / \`do\` → \`must_say\`, \`dont\` → \`never_say\`, and a \`must\` whose text reads
|
|
1773
|
+
\`Pronounce "<term>" as "<say_as>"\` (straight or curly quotes) → \`pronunciations\`. Add \`kit.instructions\` (free-text
|
|
1774
|
+
standing rules) only when they require actual spoken wording or prohibit a claim. Production
|
|
1775
|
+
directions stay in the brief. A required spoken line or prohibited claim remains its own
|
|
1776
|
+
ordinary \`must\` / \`dont\` rule.
|
|
1777
|
+
- **Which product.** The one the brief names (\`creative_brief.productName\`); with none, the row
|
|
1778
|
+
whose name matches the product the user asked for, or the brand itself for a one-product
|
|
1779
|
+
brand. Product lists often hold other brands' items or old ads saved as products: if more than
|
|
1780
|
+
one row could be it, ask in the choices round. Never mix facts across rows.
|
|
1781
|
+
- **Product facts** come from that row (name, description, variant, price) and, when the row is
|
|
1782
|
+
empty, from the kit (\`valueProps\`, \`description\`, \`tagline\`): nothing else. Write them to
|
|
1783
|
+
\`products[].facts\`; the script may only state what is there.
|
|
1784
|
+
- **Logo.** Download the kit's logo FILE (\`kit.logoUrl\`, else \`kit.logos[0]\`) to \`working/brand/\`,
|
|
1785
|
+
keeping its real extension (an SVG stays \`.svg\`; rasterise it to a 1024px-wide PNG with
|
|
1786
|
+
\`rsvg-convert\` or \`cairosvg\` when a renderer needs pixels). It is used as-is on every scene and
|
|
1787
|
+
end card that shows a logo: **never generate, redraw, re-letter or restyle a logo with an image
|
|
1788
|
+
model.** If the kit's \`logoConfidence\` says favicon-grade, or the file's long side is under
|
|
1789
|
+
256 px (or it is under 40,000 px²), it is a site favicon, not a logo: do not upscale it. Ask the user for a real logo (or offer the brand name
|
|
1790
|
+
set as text in the brand font) in the SAME question round as the recipe's \`choices\`.
|
|
1791
|
+
- **Font.** \`kit.typography.heading\` when its \`source\` is \`user\` (the user chose it), else
|
|
1792
|
+
\`kit.fonts.heading\`. Download the font file (the kit's own, or the same family from Google
|
|
1793
|
+
Fonts) to \`working/brand/\` and use it for every on-screen line. With no match, use the closest
|
|
1794
|
+
free font and say so in the review.
|
|
1795
|
+
- **No wordmark file.** When a recipe wants a wordmark SVG and the kit has only a logo image, set
|
|
1796
|
+
the brand name as text in the brand font beside the logo file. Never generate one.
|
|
1797
|
+
- Record in \`brand-rules.json\` which logo the video will actually composite (\`logo.file\`), or
|
|
1798
|
+
\`"logo": { "mode": "text" }\` when it will show only the brand name set in the brand font.
|
|
1799
|
+
- **Product images.** Download this product's own images to \`working/brand/\`. Use only images of
|
|
1800
|
+
THIS product: never a catalogue image of another product, a mascot, a lifestyle photo of a
|
|
1801
|
+
person, or a stand-in. If the product has no usable image, ask in the choices round.
|
|
1802
|
+
|
|
1803
|
+
### Brand corrections stick — save them to the brand the moment they are made
|
|
1804
|
+
|
|
1805
|
+
When the user corrects something about the BRAND in chat — how a name is said, a claim that may
|
|
1806
|
+
not be made, a product fact, a visual rule ("never use red", "the logo goes top-left") — save it
|
|
1807
|
+
in the SAME turn, before anything else:
|
|
1808
|
+
|
|
1809
|
+
\`brand_update { brand_id, knowledge_intent: "user_correction", user_statement: <the user's exact correction>, patch: { facts: [{ kind, text }] } }\`
|
|
1810
|
+
|
|
1811
|
+
| Correction | \`kind\` | \`text\` |
|
|
1812
|
+
|---|---|---|
|
|
1813
|
+
| Pronunciation | \`must\` | \`Pronounce "Acme" as "ak-mee"\` (exactly this form) |
|
|
1814
|
+
| A claim or word to avoid | \`dont\` | \`Never say or imply: <the claim>\` |
|
|
1815
|
+
| Something that must be said | \`must\` | \`<the rule>\` |
|
|
1816
|
+
| A product fact | \`must\` | \`<product name>: <the fact>\` |
|
|
1817
|
+
| A visual rule | \`do\` / \`dont\` | \`<the rule>\` |
|
|
1818
|
+
|
|
1819
|
+
- If it changes an EXISTING rule (a new pronunciation for the same term), update that rule by id
|
|
1820
|
+
(\`facts: [{ id: <learning_id>, text }]\`) instead of adding a second one.
|
|
1821
|
+
- Use the correction intent and user's statement when supported, as described below. Read
|
|
1822
|
+
\`brand_read\` learnings back and verify the rule and source before claiming it was saved.
|
|
1823
|
+
- Then tell the user in one line: "Saved to your brand for future videos." Update
|
|
1824
|
+
\`working/brand-rules.json\` and apply the rule to THIS video too.
|
|
1825
|
+
- A one-off note about this video ("make it shorter", "use the blue background here") is NOT a
|
|
1826
|
+
brand rule: don't save it.
|
|
1827
|
+
|
|
1828
|
+
${DURABLE_BRAND_CAPTURE}
|
|
1829
|
+
|
|
1830
|
+
${VIDEO_TASTE_CAPTURE}
|
|
1223
1831
|
|
|
1224
1832
|
## Step 2 — read the template's recipe (it carries everything; NO hardcoded format map)
|
|
1225
1833
|
|
|
1226
1834
|
The ad format is a **template (data) in the ad_sample DB**, not a per-format skill.
|
|
1227
|
-
\`
|
|
1835
|
+
\`catalog_fetch { type: "template" }\` returns the template's \`recipe\` — a self-contained brief you
|
|
1228
1836
|
read and execute. **Do NOT map \`format\` to a hardcoded recipe slug** (there is no such table):
|
|
1229
1837
|
|
|
1230
1838
|
- \`recipe.format\` — the format label (e.g. \`vignette\`), for display only.
|
|
1231
|
-
- \`recipe.atoms\` — the **capabilities** this template composes (e.g. \`create-
|
|
1232
|
-
\`create-image-gpt-image-fal\`, \`review-ugc-render\`, \`watch\`).
|
|
1233
|
-
|
|
1839
|
+
- \`recipe.atoms\` — the **capabilities** this template composes (e.g. \`create-vo-elevenlabs\`,
|
|
1840
|
+
\`create-image-gpt-image-fal\`, \`render-podcast-skit\`, \`review-ugc-render\`, \`watch\`). Fetch each
|
|
1841
|
+
with \`catalog_fetch { type: "skill", slug }\` (or \`gooseworks fetch <name>\` when the CLI is
|
|
1842
|
+
installed) — they are reused across templates.
|
|
1234
1843
|
- \`recipe.instructions\` — the **playbook** to follow: \`instructions.inline\` prose, or
|
|
1235
1844
|
\`instructions.doc_url\` (an S3 markdown doc — fetch it).
|
|
1236
1845
|
- \`recipe.config\` — every param (prompts, layout, timings, palette, model choices).
|
|
1237
1846
|
- \`recipe.inputs\` — the brand-asset contract (which product / logo / offer this template needs).
|
|
1847
|
+
- \`recipe.choices\` — the creative calls the USER makes (who is on screen, narrator, tone, setting,
|
|
1848
|
+
art style, music). Ask every unanswered one in ONE round before any paid step, with its options plus
|
|
1849
|
+
"you pick". Its \`reference\` is what the demo used: an example, never the default.
|
|
1238
1850
|
- \`recipe.assets\` — reference material as S3 links (reference render, style guide, example frames) —
|
|
1239
1851
|
fetch as needed.
|
|
1240
1852
|
|
|
1241
|
-
Runtime: **read the recipe →
|
|
1853
|
+
Runtime: **read the recipe → fetch each capability in \`recipe.atoms\` → follow
|
|
1242
1854
|
\`recipe.instructions\` with \`recipe.config\` + the brand's bound \`inputs\`.** The template IS the recipe;
|
|
1243
1855
|
there is no \`format → recipe-slug\` table and no per-format skill to fetch.
|
|
1244
1856
|
|
|
1245
|
-
Save each fetched capability's scripts + files under \`/tmp/gooseworks-scripts/<name
|
|
1246
|
-
|
|
1247
|
-
|
|
1857
|
+
Save each fetched capability's content, scripts + files under \`/tmp/gooseworks-scripts/<name>/\`
|
|
1858
|
+
(layout in "Running in a GooseWorks sandbox"). If a capability is a Node package (a phone-mockup
|
|
1859
|
+
renderer), install its documented dependencies in the folder containing its \`package.json\`,
|
|
1860
|
+
and preserve the recorder's documented \`NODE_PATH\` — local machines only; in a sandbox that
|
|
1861
|
+
format stops (no Chromium).
|
|
1862
|
+
|
|
1863
|
+
**Selected browser readiness — before ANY paid ingredient:** identify the actual browser script
|
|
1864
|
+
from the fetched capability's instructions (including an HTML end-card renderer if used).
|
|
1865
|
+
For Node scripts using \`require('playwright')\` with default \`chromium.launch()\`, run
|
|
1866
|
+
\`gooseworks doctor --renderer-script "/absolute/path/to/the/fetched/scripts/record.js"\`.
|
|
1867
|
+
Substitute the actual script, and keep the same cwd and environment as the render, including
|
|
1868
|
+
\`NODE_PATH\` and \`PLAYWRIGHT_BROWSERS_PATH\`. It resolves Playwright relative to that script,
|
|
1869
|
+
launches and closes Chromium with the default settings and bounded waits, and downloads nothing.
|
|
1870
|
+
On failure, stop before spending, show its folder-specific repair and recheck after setup.
|
|
1871
|
+
For non-browser formats use \`doctor --no-browser\` plus their documented runtime checks;
|
|
1872
|
+
never combine \`--no-browser\` with \`--renderer-script\` or use it to bypass a browser renderer.
|
|
1873
|
+
|
|
1874
|
+
**No CLI or CLI without these flags:** run an equivalent free probe in a separate Node process: use
|
|
1875
|
+
\`require('node:module').createRequire(require('node:path').resolve(actualRendererScript))\`
|
|
1876
|
+
to load \`playwright\`; keep the render's cwd, environment and default launch settings. Await
|
|
1877
|
+
\`chromium.launch({ timeout: 15000 })\`, then await \`browser.close()\` (bound close to 3 seconds).
|
|
1878
|
+
Bound the whole process to 20 seconds and stop its own process tree on failure or timeout.
|
|
1879
|
+
Report the resolved module path/version and error without credentials. Missing module, executable,
|
|
1880
|
+
headless runtime or failed launch is a failed check; a cache folder, executablePath alone or
|
|
1881
|
+
another project's browser is not a pass. Other browser packages, Python renderers or custom
|
|
1882
|
+
launch settings need the same free launch/close check through their documented runtime and
|
|
1883
|
+
actual settings. Do not replace their browser/channel/flags to get a pass. Setup is a separate
|
|
1884
|
+
action under existing permissions; this check never silently installs or downloads anything.
|
|
1248
1885
|
|
|
1249
1886
|
> **Migration note:** older phone-mockup formats (\`imessage\` / \`chatgpt\` / \`apple-notes\`) whose DB
|
|
1250
1887
|
> recipe does not yet carry \`atoms\` / \`instructions\` still hold the legacy \`recipe.thread\` payload;
|
|
1251
1888
|
> migrate them to this shape (capabilities + instructions in the DB) — do not reintroduce a CLI map.
|
|
1252
1889
|
|
|
1253
|
-
## Step
|
|
1254
|
-
|
|
1255
|
-
|
|
1890
|
+
## Step 2.5 — plan words and visuals with \`write-video-ad-script\`
|
|
1891
|
+
|
|
1892
|
+
Every video concept gets creative strategy, including silent formats. Fetch
|
|
1893
|
+
**\`write-video-ad-script\`** through the skill catalogue and follow it before assembling
|
|
1894
|
+
review ingredients. It reuses or fetches **\`ad-angle-miner\`**, binds the researched promise
|
|
1895
|
+
to this template's full recipe, writes words and visuals together, and checks product
|
|
1896
|
+
claims and production fit. Its independent critic is a quality screen, not a forecast
|
|
1897
|
+
of ad performance. The existing Step 3 review and approval remain unchanged.
|
|
1898
|
+
|
|
1899
|
+
- **Carry the research into the writer.** Pass the brand and exact product, audience,
|
|
1900
|
+
objective, offer, CTA, selected template recipe, available assets and the full miner
|
|
1901
|
+
bank or its readable workspace pointer. A selected miner idea includes its angle id,
|
|
1902
|
+
evidence and proof plan, not only its hook. Reuse the shared video-angle-bank.v1 from
|
|
1903
|
+
the brand's video-scripts workspace or this run. Prepare angle-context.json with the
|
|
1904
|
+
writer's preparation script with \`--brief working/script/creative-brief.json\` and use
|
|
1905
|
+
its strict rule check before review. Save the sourced brief in the writer's documented
|
|
1906
|
+
shape: exact product/variant, buyer situation, supported mechanism, offer/CTA, constraints,
|
|
1907
|
+
delivery intent, source references, locked copy, applicable prior decisions and unknowns.
|
|
1908
|
+
New custom/template shapes set \`requires_creative_brief: true\`; both writer and critic
|
|
1909
|
+
receive the same angle-context. Carry that brief revision into the scene/tool plan and
|
|
1910
|
+
production manifest; missing evidence stays explicit rather than becoming a claim.
|
|
1911
|
+
- **Already decided stays decided.** The user's angle, hook, batch concept, recipe choice
|
|
1912
|
+
or remix direction is binding. Preserve the selected angle id. Compare executions or
|
|
1913
|
+
hooks inside it; do not reopen the angle choice. An incompatible promise needs a
|
|
1914
|
+
supported execution or an explicit format change before production.
|
|
1915
|
+
- **The recipe owns the production contract.** Its story mechanism, speakers, timing,
|
|
1916
|
+
text limits and visual capabilities win. Inspect the actual recipe and available assets;
|
|
1917
|
+
a catalogue card alone cannot establish fit. An impossible essential visual blocks
|
|
1918
|
+
the proposal until repaired. Do not force a testimonial into every format.
|
|
1919
|
+
- **Research only what is missing.** Fetching the miner does not spend or rerun research.
|
|
1920
|
+
Reuse current facts and prior evidence. If paid collection is needed, its permission
|
|
1921
|
+
rides in the existing choices round. A new product with no reviews can use verified
|
|
1922
|
+
facts and a feasible demo; do not require paid research merely to fill a quote quota.
|
|
1923
|
+
- **Choose eligible concepts only.** Take the strongest supported concept and validated
|
|
1924
|
+
hook into review, with up to two viable alternatives when the direction is open.
|
|
1925
|
+
Do not pad three concepts or select the highest-ranked rejected one. If all fail,
|
|
1926
|
+
repair within the brief and recipe before Step 3.
|
|
1927
|
+
- **Unavailable writer or research.** Complete an explicit agent check of claim support,
|
|
1928
|
+
recipe limits, visual feasibility and hook payoff. Record missing provenance and say
|
|
1929
|
+
in the review when research is provisional. Never report a failed check as a pass.
|
|
1930
|
+
- **The user's exact lines** remain verbatim; use report-only checks and raise material
|
|
1931
|
+
timing, claim or format conflicts without silently rewriting them.
|
|
1932
|
+
- **No spoken words.** Still fit the visual promise, reveal, cards and CTA to the silent
|
|
1933
|
+
recipe. Skip speech writing and spoken checks, not the strategy step.
|
|
1934
|
+
- **Keep private provenance separate.** Store source quotes and links in the research
|
|
1935
|
+
workspace; put only a concise strategy explanation in remixable review data.
|
|
1936
|
+
|
|
1937
|
+
## Shared editorial craft inside this template flow
|
|
1938
|
+
|
|
1939
|
+
Fetch \`video-production-harness\` and read its \`references/editorial-review.md\`,
|
|
1940
|
+
\`references/specialist-handoff.md\` and \`references/hook-compatibility.md\`, plus the
|
|
1941
|
+
review/edit/polish/promote/wrap steps when used. Save the fetched version/content hash with
|
|
1942
|
+
this run's capability records. These supply craft and evidence rules inside the template
|
|
1943
|
+
flow; this entry's existing review, paid approval, storage and two-repair limit still apply.
|
|
1944
|
+
Do not turn a template into a custom project or import the custom host's extra gates.
|
|
1945
|
+
|
|
1946
|
+
Before executing a fetched recipe atom, pass the exact brief/script revision, scene/beat IDs,
|
|
1947
|
+
timing, selected product/assets/source windows, performance choice and expected output roles.
|
|
1948
|
+
Check actual scripts/dependencies and free runtime readiness, then validate returned media
|
|
1949
|
+
against that handoff. An interrupted run reuses valid approved media with matching digest;
|
|
1950
|
+
a file merely existing does not prove validity. A missing package/required renderer blocks
|
|
1951
|
+
that route before paid generation, with a concrete supported alternative for review.
|
|
1952
|
+
|
|
1953
|
+
Rough review asks what the cut communicates and whether the proof/payoff serves the hook;
|
|
1954
|
+
fine review diagnoses performance, sequence and local execution; final review checks each
|
|
1955
|
+
actual export. Save the received message from the cut before comparing with the brief where
|
|
1956
|
+
possible. A technically clean incoherent cut returns to script/sequence work; a local caption
|
|
1957
|
+
defect gets a local repair. These internal questions share existing review surfaces.
|
|
1958
|
+
|
|
1959
|
+
Before a hook change, classify same-promise compatible opening versus new-promise/body recut.
|
|
1960
|
+
For example, an automatic report supports “stop copying campaign results”; it does not alone
|
|
1961
|
+
prove “know where to spend the next dollar.” Propose required script/body/coverage changes for
|
|
1962
|
+
the latter. Every restyled opening gets fresh image/action/audio/text review even when words
|
|
1963
|
+
are unchanged. Fetch the existing \`render-hook-replacement\` before selecting that route;
|
|
1964
|
+
a not-found/empty result stops it before generation. Use its actual preflight and preservation
|
|
1965
|
+
checks when available; never build a duplicate renderer or infer publication from source.
|
|
1966
|
+
Compare complete labeled candidates, retain source/body/ending and record the hypothesis.
|
|
1967
|
+
No quality score or recommendation is an observed performance winner.
|
|
1968
|
+
|
|
1969
|
+
## Step 3 — assemble the review set, then get ONE approval (before the expensive render)
|
|
1970
|
+
|
|
1971
|
+
This is a **review-once** flow: save the whole review set to the project, get ONE approval in this
|
|
1972
|
+
chat, then run the
|
|
1256
1973
|
expensive render + any remaining paid work end-to-end. Never spend on the expensive render before
|
|
1257
1974
|
approval, and don't drip pieces out one at a time and re-pause.
|
|
1258
1975
|
|
|
@@ -1265,9 +1982,10 @@ every piece three ways by cost, NOT just "free vs paid":
|
|
|
1265
1982
|
mirror the real asset. The few credits buy a real review: the user SEES the actual creator face
|
|
1266
1983
|
and end card and HEARS the VO, instead of judging a prompt. **This OVERRIDES any recipe rule that
|
|
1267
1984
|
says to gate ALL paid calls** — only the expensive render below is gated.
|
|
1268
|
-
- **EXPENSIVE paid** — the video take / final AI render
|
|
1269
|
-
Put its **exact prompt/spec** (+ ref image URLs) in the tile. This is
|
|
1270
|
-
prompt (you can't preview a hundreds-of-credits video for free); it's
|
|
1985
|
+
- **EXPENSIVE paid** — the video take / final AI render / per-line lipsync clips (hundreds of
|
|
1986
|
+
credits) → do NOT generate. Put its **exact prompt/spec** (+ ref image URLs) in the tile. This is
|
|
1987
|
+
the ONE thing approved as a prompt (you can't preview a hundreds-of-credits video for free); it's
|
|
1988
|
+
generated only in Step 4.
|
|
1271
1989
|
|
|
1272
1990
|
**The expensive render's exact prompt must be in the panel BEFORE you ask for approval** — so a
|
|
1273
1991
|
single "go" runs it (plus any remaining paid work) without re-pausing mid-run.
|
|
@@ -1284,27 +2002,38 @@ render (GOOSE-2542). The full video is assembled ONLY in Step 4, after approval.
|
|
|
1284
2002
|
ingredient here is only a genuinely separate SOURCE clip the format needs (e.g. supplied b-roll).
|
|
1285
2003
|
|
|
1286
2004
|
1. **Assemble every piece the format needs — not just the script.** Read the recipe for the exact
|
|
1287
|
-
list. For
|
|
1288
|
-
and the **end card**;
|
|
1289
|
-
|
|
1290
|
-
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
2005
|
+
list. For a podcast skit that's the **script** (both hosts' lines), the two **host stills**,
|
|
2006
|
+
one **voice sample per host**, and the **end card**; an iMessage video has the bubble thread,
|
|
2007
|
+
the conversation image(s) and the end card. For each piece, decide FREE / CHEAP-paid /
|
|
2008
|
+
EXPENSIVE-paid (above):
|
|
2009
|
+
- **FREE or CHEAP paid** (≤ ~100 credits) → generate it now and upload it with
|
|
2010
|
+
\`media_upload { brand_id, scope: "video_project", scope_id: project_id, kind, path:
|
|
2011
|
+
"working/review/<name>", ingredient_key, input_digest, source: { type: "file", filename:
|
|
2012
|
+
"<name>", content_type } }\` → PUT → \`media_confirm { brand_id, media_id: media.id }\`;
|
|
2013
|
+
after confirmation succeeds, set that
|
|
2014
|
+
piece's \`path\` (+ \`media_id\`, \`ingredient_key\`) in \`script_drafts\` to the project-relative
|
|
2015
|
+
\`working/review/<name>\`. First check "Save as you go" — a piece already saved with the same
|
|
2016
|
+
digest is downloaded, not regenerated.
|
|
2017
|
+
- **EXPENSIVE paid** → do NOT generate. Put the **exact prompt/spec** (and any ref image URLs)
|
|
2018
|
+
in the tile's \`text\` / \`subtitle\` so the user reviews what will be spent on. No \`path\` yet —
|
|
2019
|
+
it's generated in Step 4.
|
|
2020
|
+
Save **render_estimate.total_credits**: previews already spent + pending render + the 200 base fee.
|
|
2021
|
+
Use current server pricing, never a catalog range in place of the full total.
|
|
2022
|
+
**Brand check of the script, before it goes in the panel:** every line, caption and on-screen
|
|
2023
|
+
text is checked against \`working/brand-rules.json\`. Nothing in \`never_say\` appears, in words or
|
|
2024
|
+
in meaning (a paraphrase of a banned claim is still banned). Every product detail (name,
|
|
2025
|
+
flavour, size, price, ingredient, result) comes from \`products[]\` or the kit: anything else is
|
|
2026
|
+
cut, not invented. Add a \`note\` ingredient labelled "Brand rules applied" that lists the
|
|
2027
|
+
pronunciations used and the rules the script respects, so the user sees them.
|
|
2028
|
+
**Answer clarifying questions
|
|
1300
2029
|
from the project brief FIRST (Step 1.5)** — only ask the user for a field (angle, which product,
|
|
1301
2030
|
offer/code) the \`creative_brief\` leaves empty AND the recipe can't default. Do not re-ask for
|
|
1302
2031
|
anything the composer already captured.
|
|
1303
|
-
2. **Mirror the whole ingredient set for review** — \`
|
|
1304
|
-
script_drafts, script }\`. \`script_drafts\` is a structured payload of
|
|
1305
|
-
ingredients** so the app renders each piece the right way:
|
|
1306
|
-
\`{ format, scenes?, ingredients: [{ container, label, subtitle?, path?, text? }] }\`.
|
|
1307
|
-
ingredient's \`container\` tells the app HOW to show it:
|
|
2032
|
+
2. **Mirror the whole ingredient set for review** — \`video_project_upsert { brand_id, project_id,
|
|
2033
|
+
patch: { script: { script_drafts, script } } }\`. \`script_drafts\` is a structured payload of
|
|
2034
|
+
**container-tagged ingredients** so the app renders each piece the right way:
|
|
2035
|
+
\`{ format, scenes?, ingredients: [{ container, label, subtitle?, path?, url?, text? }], render_estimate? }\`.
|
|
2036
|
+
Each ingredient's \`container\` tells the app HOW to show it:
|
|
1308
2037
|
- \`image\` (a frame shown in the video), \`endcard\` (the end card), \`avatar\` (a character
|
|
1309
2038
|
headshot), \`background\` → rendered as an image tile.
|
|
1310
2039
|
- \`voice\` (a voiceover clip — put the voice NAME in \`subtitle\`), \`music\` (the bed),
|
|
@@ -1312,147 +2041,325 @@ ingredient here is only a genuinely separate SOURCE clip the format needs (e.g.
|
|
|
1312
2041
|
- \`video\` (a clip) → a video player. \`text\` (a copy line like the CTA) → a text tile.
|
|
1313
2042
|
- \`script\` / \`thread\` / \`note\` / \`conversation\` → the written script (or set \`scenes[]\`
|
|
1314
2043
|
for the podcast shape, or pass the readable \`script\` string).
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
2044
|
+
**Label every ingredient** ("Hook image", "End card", "Voiceover", "Host A", "HER"). The upsert
|
|
2045
|
+
writes no render and costs no credits — it just populates the review panel.
|
|
2046
|
+
3. **STOP for ONE approval, in this chat.** Only a complete current saved review set with
|
|
2047
|
+
render_estimate.total_credits can be approved. Widget: one line, no duplicate plan or app
|
|
2048
|
+
link; otherwise print card.text_summary. The choices are Approve, Change and Not now.
|
|
2049
|
+
“Not now” keeps the plan. In a coworker sandbox end the turn after saving the review set;
|
|
2050
|
+
the customer's approval arrives as the next message.
|
|
2051
|
+
Record the explicit yes with \`video_project_upsert { brand_id, project_id, patch: { approve:
|
|
2052
|
+
{ user_quote: "<their exact words>", total_credits: <the saved total> } } }\` before rendering.
|
|
2053
|
+
A single project requires recorded approval too; never treat an absent approval as permission.
|
|
2054
|
+
Changes require an updated saved review, clearing prior approval, and approval of the new
|
|
2055
|
+
total. One yes authorizes the remaining approved chain. On insufficient_balance start
|
|
2056
|
+
nothing; offer a shorter video or top-up. Remove batch concepts with patch.concepts remove:true,
|
|
2057
|
+
then show the changed total before approval.
|
|
2058
|
+
|
|
2059
|
+
## Step 4 — render, report stages, publish
|
|
2060
|
+
|
|
2061
|
+
1. **Open the render row FIRST** — right after recording the Step 3 approval, before any paid
|
|
2062
|
+
generation (if this returns \`approval_required\`, the approval is missing or was cleared: go
|
|
2063
|
+
back to Step 3 and ask in this chat, don't retry):
|
|
2064
|
+
\`${exports.RENDER_ROW_TOOL} { brand_id, project_id, ${exports.RENDER_OPEN_ARGS} }\` (no \`dry_run\`; returns
|
|
2065
|
+
\`render_id\`) → keep \`render_id\`, then mark it running:
|
|
2066
|
+
\`${exports.RENDER_ROW_TOOL} { brand_id, project_id, ${exports.RENDER_UPDATE_KEY}: { render_id, status: "running",
|
|
2067
|
+
workflow_stage: "preparing", progress_note: "starting", progress_percent: 5 } }\`. The user sees this
|
|
2068
|
+
live in the app and gets a WhatsApp "started" message automatically — don't message them yourself
|
|
2069
|
+
about start / blocked / complete.
|
|
2070
|
+
After EVERY progress callback inspect stop. If true, start no new paid step; record
|
|
2071
|
+
status:"stopped" with a plain note and report what is kept and credits used. SPEND_CAP_REACHED
|
|
2072
|
+
stops the same way; raising the cap requires patch.approve scope:"raise_cap" and the customer's
|
|
2073
|
+
words. Send render.steps with the same neutral names each time and a live count only in the
|
|
2074
|
+
current detail; use render.choices when blocked.
|
|
2075
|
+
2. Now generate every PAID piece you showed as a prompt in Step 3 — the AI stills/video, lipsync
|
|
2076
|
+
clips, voice, music — through the media proxies (below), each from its approved prompt, with
|
|
2077
|
+
\`GW_PROJECT_ID\` exported. **Save as you go** (section above): skip any piece already saved
|
|
2078
|
+
with the same \`input_digest\` (download it), and upload each new piece with its
|
|
2079
|
+
\`ingredient_key\` + \`input_digest\` the moment it passes QC.
|
|
2080
|
+
Keep the approved script and review unchanged throughout production and QC; confirmed media
|
|
2081
|
+
and progress preserve resume state. Stop and replan/reapprove any material creative change.
|
|
2082
|
+
A voiceover made with \`data_post_provider\` (ElevenLabs \`…/with-timestamps\`) returns its
|
|
2083
|
+
\`alignment\` only in the reply: write it to \`working/vo/<scene>.timestamps.json\` at once
|
|
2084
|
+
(captions are timed from it) and record the returned \`media_id\` on the ingredient.
|
|
2085
|
+
**Brand pronunciations in every voiceover:** the text sent to the voice has each
|
|
2086
|
+
\`pronunciations[].term\` replaced by its \`say_as\` (\`create-vo-elevenlabs\`:
|
|
2087
|
+
\`gen_vo.py … --rules working/brand-rules.json\`; when a render atom calls the voice itself,
|
|
2088
|
+
swap the terms in the text you hand it). Captions, on-screen text and the review keep the
|
|
2089
|
+
written name. Write \`working/approved-script.txt\` (the Step 4.3 audio check) with the SPOKEN
|
|
2090
|
+
form. **Logo, font, product:** every logo is the file from Step 1.7 composited as-is; every
|
|
2091
|
+
product shot uses the Step 1.7 product images as its reference.
|
|
2092
|
+
Then assemble per the recipe (ffmpeg stitch; PIL captions / end card;
|
|
2093
|
+
Playwright record only where the format needs it and the host has Chromium → \`mix-master\` audio).
|
|
2094
|
+
**Report progress at each milestone** — about one update per milestone, never per poll:
|
|
2095
|
+
\`${exports.RENDER_ROW_TOOL} { brand_id, project_id, ${exports.RENDER_UPDATE_KEY}: { render_id, status: "running", workflow_stage, progress_note, progress_percent } }\`
|
|
2096
|
+
(\`progress_note\` = plain words the customer reads in the app, ≤200 chars; no model, tool or
|
|
2097
|
+
step names):
|
|
2098
|
+
- voiceovers done → \`"preparing"\`, \`"voices recorded"\`, 20
|
|
2099
|
+
- stills done → \`"preparing"\`, \`"scenes designed"\`, 35
|
|
2100
|
+
- each lipsync / video clip → \`"rendering"\`, e.g. \`"filming scene 5 of 8"\`, 35–75
|
|
2101
|
+
- assembly → \`"rendering"\`, \`"putting the video together"\`, 85
|
|
2102
|
+
- QC (4.3) → \`"final check"\`, 95
|
|
2103
|
+
**Hard stop that needs the user** → \`{ render_id, status: "running", workflow_stage: "blocked",
|
|
2104
|
+
error_message: "<what's wrong + what you need>" }\`; an unrecoverable failure → \`status: "failed"\`
|
|
2105
|
+
+ \`error_message\`. Completing (4.4) sets the bar to 100.
|
|
2106
|
+
3. **MANDATORY final-video QC gate — YOU review EVERY finished master before pinning it
|
|
2107
|
+
(\`patch.final_render_id\`), whatever the format (UGC or not).** This is your own automated quality
|
|
2108
|
+
check, separate from the user's Step-3 approval — it does not go back to the user. The render row
|
|
2109
|
+
is already open (4.1); this gate stands between a rendered master and PINNING/publishing it, so a
|
|
2110
|
+
bad render never gets set as final. A master that looks fine on a still can still have a
|
|
2111
|
+
mis-voiced word, a caption drifting off its line, a beat out of order, or a deformation — review
|
|
2112
|
+
the actual VIDEO, not stills. Run the passes that APPLY to this format:
|
|
1349
2113
|
- **Audio ↔ script** — any master with SPEECH (VO or native/Seedance voice); **skip for
|
|
1350
2114
|
music-only / no-speech formats.** \`review-ugc-render\` is format-agnostic despite the name —
|
|
1351
|
-
a deterministic Whisper transcript-vs-script diff
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
working/review-verdict.json\` (exit 0 PASS / 2 FAIL / 3 ERROR).
|
|
2115
|
+
a deterministic Whisper transcript-vs-script diff: persist the approved spoken lines to
|
|
2116
|
+
\`working/approved-script.txt\`, fetch \`review-ugc-render\` (\`catalog_fetch\`) and run
|
|
2117
|
+
\`review_render.py --video <master>.mp4 --script-file working/approved-script.txt --json
|
|
2118
|
+
working/review-verdict.json\` (exit 0 PASS / 2 FAIL / 3 ERROR). For each brand pronunciation add
|
|
2119
|
+
\`--brand-term "<term>"\`, plus \`--brand-term\` for each word of \`say_as\` that is not an
|
|
2120
|
+
everyday word (the flag strips those tokens from the WHOLE diff, so never pass "a", "one",
|
|
2121
|
+
"works" alone): Whisper spells a respelled name back as the brand word ("Goose Works" heard
|
|
2122
|
+
as "Gooseworks"), so the diff must accept it. The proof of
|
|
2123
|
+
intended delivery is the text/settings you sent (keep them in the review); actual
|
|
2124
|
+
pronunciation/performance requires isolated and mixed-audio listening at normal speed.
|
|
2125
|
+
Preserve pauses/emphasis and final consonants; never mandate faster or louder delivery.
|
|
2126
|
+
Verify a transcript mismatch against the actual audio before calling it a defect. It flags a mis-voiced word
|
|
1355
2127
|
(approved "human-vetted" → "human witted"), a dropped phrase, or silence. It routes Whisper
|
|
1356
|
-
through the gooseworks proxy when \`OPENAI_BASE_URL\` is set
|
|
1357
|
-
|
|
2128
|
+
through the gooseworks proxy when \`OPENAI_BASE_URL\` is set (sandbox:
|
|
2129
|
+
\`$GW_WHISPER_PROXY_URL/v1\`); with no backend at all, run \`fal-ai/whisper\` via the FAL proxy
|
|
1358
2130
|
and diff the transcript yourself.
|
|
1359
2131
|
- **Captions / subtitles** — ANY captioned format (the most common non-UGC defect); **skip for
|
|
1360
|
-
UGC/Seedance masters, which carry no subtitle track.**
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
2132
|
+
UGC/Seedance masters, which carry no subtitle track.** Diff the caption file you burned
|
|
2133
|
+
(SRT/ASS/PNG cue list) against the SAME Whisper transcript + word timings — every caption line
|
|
2134
|
+
must match the heard/scripted words and sit within ~0.3s of when they're spoken; then in the
|
|
2135
|
+
visual pass below, inspect each cue and its start/end boundaries in actual frames, plus
|
|
2136
|
+
full-speed playback at destination size, to confirm reading time, product visibility,
|
|
2137
|
+
hierarchy and no duplicate overlay or end-card collision. Silent/text-led formats use
|
|
2138
|
+
approved visible wording and reading windows, not nonexistent speech timings. Mismatched text or >0.3s drift fails the gate.
|
|
1366
2139
|
- **Visual + structure** — always: run the \`watch\` skill on the master — beat/scene order + SFX,
|
|
1367
|
-
the brand's product (not the source's) is shown, the end card has the
|
|
2140
|
+
the brand's product (not the source's) is shown, the end card has the brand's logo file (or its name set in the brand font) + code, no
|
|
1368
2141
|
deformation/artifact, duration within ~20% of the source.
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
2142
|
+
- **Finished ad + brand fidelity** — always: fetch \`review-finished-ad\` (\`catalog_fetch\`,
|
|
2143
|
+
\`pip install --quiet numpy pillow\` if needed) and run
|
|
2144
|
+
\`review_finished_ad.py --video <master>.mp4 --json working/review/finished-ad.json
|
|
2145
|
+
--sheet working/review/finished-ad-sheet.png --logo <the logo file the video composites>
|
|
2146
|
+
--palette "<kit palette, comma-separated>" --product-images <the Step 1.7 images>
|
|
2147
|
+
--font <brand font file> --brand-name "<name>" --endcard-s <end card length>\`. Pass the
|
|
2148
|
+
file that is ACTUALLY on screen (the kit logo, or the recipe's wordmark file). When the video
|
|
2149
|
+
shows only the brand name as text (\`"logo": {"mode": "text"}\`), omit \`--logo\` and judge the
|
|
2150
|
+
text on the sheet. Add \`--no-speech\` for a format with no VO or dialogue, \`--logo-at <s>\` for
|
|
2151
|
+
each mid-video logo, and any gate flags the recipe's instructions name (a chat format's
|
|
2152
|
+
reading holds need a longer \`--max-freeze-s\`). Exit 0 PASS / 2 FAIL / 3 ERROR. It
|
|
2153
|
+
checks size, hook, pacing, dead air, black frames, the kit logo on the end card
|
|
2154
|
+
(a wrong, redrawn or favicon logo fails) and the palette. **Then open the sheet it writes
|
|
2155
|
+
and judge every line of its \`judge_on_sheet\`:** captions/CTA/logo outside the red safe
|
|
2156
|
+
zones, the brand font, every product shot matching the product images, the product the same
|
|
2157
|
+
in every scene, the logo unaltered. A failed eye check is a FAIL like any other.
|
|
2158
|
+
- **Output size** — always **1080×1920 (9:16)**. Video ads are only ever 9:16. Lipsync / video
|
|
2159
|
+
models often return 720p or odd sizes — scale (and pad if the aspect differs) every clip to
|
|
2160
|
+
1080×1920 BEFORE the concat, never ship the model's native size.
|
|
2161
|
+
Save exact output checksum/version, first-to-last normal-speed playback and full-audio
|
|
2162
|
+
coverage. Frame sheets, transcripts and process success do not substitute for listening or
|
|
2163
|
+
continuous motion. Missing required capability leaves review incomplete/blocked. Each
|
|
2164
|
+
supported crop, duration, language or caption derivative needs its own actual-file review;
|
|
2165
|
+
unsupported destination requirements need a reviewed route, never a silent export change.
|
|
2166
|
+
Honor intended silence, loops and endings rather than forcing music or fades on every ad.
|
|
2167
|
+
If ANY applicable pass fails, FIX it (regenerate/stitch the offending window, re-composite the
|
|
2168
|
+
end card from the real logo file, rebuild captions) and re-run the passes — only a clean pass
|
|
2169
|
+
proceeds to pinning. **At most 2 repair rounds.** If a pass still fails after them, do NOT pin
|
|
2170
|
+
and do NOT present the video as finished: upload it (4.4) and close the row with exactly
|
|
2171
|
+
\`video_render_run { brand_id, project_id, render: { render_id, status: "complete",
|
|
2172
|
+
workflow_stage: "blocked", quality_status: "blocked", repair_pass_count: 2, output_url,
|
|
2173
|
+
thumbnail_url, quality_report } }\` (the failing checks as \`fail\` in the report), and tell the user plainly in
|
|
2174
|
+
chat which checks failed, what you tried, and the choices (fix a specific thing, re-roll, or
|
|
2175
|
+
use it anyway). Pin it only if they say to use it anyway. The app shows a blocked render as
|
|
2176
|
+
"Needs attention". **This gate is universal: it runs from this skill for every format, so a
|
|
2177
|
+
recipe never has to opt in.**
|
|
2178
|
+
4. **For a passing final, create the finished-video checkpoint first**, following "Finished video, interrupted saving"
|
|
2179
|
+
above. With the CLI, use its prepare/resume flow instead of duplicating the writes below; it
|
|
2180
|
+
saves the same already-opened render and pins only the checked output. Save the final review set
|
|
2181
|
+
and production manifest (Step 4.5/4.6) before preparing that checkpoint. Without it, follow the
|
|
2182
|
+
same durable protocol. A blocked version follows Step 4.3's existing upload/report-only path;
|
|
2183
|
+
never route it through the passing-final helper or pin it automatically.
|
|
2184
|
+
Publish: \`media_upload { brand_id, scope: "video_project", scope_id: project_id, kind: "render",
|
|
2185
|
+
path: "working/final.mp4", ingredient_key: "final", input_digest, source: { type: "file", filename: "final.mp4", content_type:
|
|
2186
|
+
"video/mp4" } }\` → PUT the master to \`upload.url\` with \`upload.required_headers\` →
|
|
2187
|
+
\`media_confirm { brand_id, media_id: media.id }\`. Same for the poster (\`kind: "thumbnail"\`, \`path: "working/final-thumb.jpg"\`, \`ingredient_key: "final-thumb"\`).
|
|
2188
|
+
Keep each \`upload.render_file_url\`. Verify the PUT returned 2xx and the file you uploaded is a
|
|
2189
|
+
real, non-empty MP4 (ffprobe it), and require both confirmations to succeed BEFORE marking
|
|
2190
|
+
the render complete.
|
|
2191
|
+
Then \`${exports.RENDER_ROW_TOOL} { brand_id, project_id, ${exports.RENDER_UPDATE_KEY}: { render_id, status: "complete", output_url, thumbnail_url } }\` (attach the Step 4.3 verdict as \`quality_status: "passed"\` (or
|
|
2192
|
+
\`"blocked"\` when the gate still fails after 2 repair rounds) + \`quality_report\` — ALWAYS
|
|
2193
|
+
attach it; a batch concept cannot complete without a passing one; exact shape, strict (no extra keys):
|
|
2194
|
+
\`{ version: 1, summary: string, checks: { source, brand, product, hook_and_scene_order,
|
|
2195
|
+
voice_and_script, captions, endcard_and_cta, duration_and_ratio, visual_artifacts }, detected_issues?:
|
|
2196
|
+
string[], repair_actions?: string[] }\` where EVERY check is \`{ status: "pass"|"fail"|"not_applicable",
|
|
2197
|
+
note?: string }\`). Fill each check from the Step 4.3 passes: \`brand\` ← logo_asset + logo +
|
|
2198
|
+
palette + font + logo unaltered; \`product\` ← product likeness + product consistency;
|
|
2199
|
+
\`hook_and_scene_order\` ← hook + pacing + the \`watch\` beat order; \`voice_and_script\` ← the
|
|
2200
|
+
Whisper diff (pronunciations included) + no \`never_say\` line; \`captions\` ← the caption diff +
|
|
2201
|
+
safe zones; \`duration_and_ratio\` ← ratio + duration; \`visual_artifacts\` ← black frames +
|
|
2202
|
+
frozen stretches + the \`watch\` pass; \`endcard_and_cta\` ← the end card holds the real logo +
|
|
2203
|
+
CTA. Put each failing check's note in \`detected_issues\` and each fix in \`repair_actions\`.
|
|
1380
2204
|
**output_url MUST be the durable render-file URL**
|
|
1381
|
-
\`/api/ads/projects/<project_id>/render-file?path=working/final.mp4\`
|
|
1382
|
-
every view) — NEVER a raw proxy/CDN URL (those expire).
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
2205
|
+
(\`upload.render_file_url\`, i.e. \`/api/ads/projects/<project_id>/render-file?path=working/final.mp4\`
|
|
2206
|
+
— the app re-presigns it on every view) — NEVER a raw proxy/CDN/presigned URL (those expire).
|
|
2207
|
+
Same for \`thumbnail_url\`.
|
|
2208
|
+
5. **Save the final review set BEFORE pinning** — it must describe the video you actually rendered.
|
|
2209
|
+
Only after all paid production, QC, repairs and pending provider work are finished and settled,
|
|
2210
|
+
save one final descriptive/provenance update: confirmed pieces, exact settings and repairs that
|
|
2211
|
+
preserve the approved creative choices. Material creative changes, including changed lines,
|
|
2212
|
+
look, timing, captions or music, require a changed complete plan, total and fresh approval BEFORE
|
|
2213
|
+
the changed production. Never relabel a changed plan as provenance or silently reuse approval.
|
|
2214
|
+
In the checkpoint flow, save the final review before Step 4.4; do not rewrite it afterward merely
|
|
2215
|
+
to follow the numbered order. Resume checks that this reviewed set stayed unchanged.
|
|
2216
|
+
When final provenance needs updating, upsert the review set once:
|
|
2217
|
+
\`video_project_upsert { brand_id, project_id, patch: { script: {
|
|
2218
|
+
script_drafts, script } } }\` with the final lines, final pieces (mark generated takes as done, not
|
|
2219
|
+
"not generated yet") and the settings you used. The project keeps this, not your chat: it is
|
|
2220
|
+
what the app shows, and what a Community remix of this video copies. Instructions that live only
|
|
2221
|
+
in this conversation are lost when it ends. **Keep the approved detail**: never shorten a piece to a
|
|
2222
|
+
summary (e.g. per-scene prompts or their "no shake / no letterbox" guards). Only update what changed.
|
|
2223
|
+
After this write, do not start new paid work. Save the production manifest, prepare the final
|
|
2224
|
+
checkpoint and complete the same render with its existing finishing allowance and guards.
|
|
2225
|
+
6. **Save the production manifest** — HOW you made it, so the next run (or a remix) starts from what
|
|
2226
|
+
worked instead of rediscovering it: \`video_project_upsert { brand_id, project_id, patch: { production:
|
|
2227
|
+
{ version: 1, pipeline: [{ step, model, purpose?, settings? }], style: { prompt, negative, notes? },
|
|
2228
|
+
characters: [{ name, prompt }], scenes: [{ id, line, still_prompt, motion_prompt, duration_s }],
|
|
2229
|
+
voice: { voice_id, name, model, settings }, music: { prompt, model, length_s }, assembly: { … },
|
|
2230
|
+
fixes: [{ problem, fix }], notes? } } }\`. Use the exact models and prompts you sent (full text, guards
|
|
2231
|
+
included), and list every fix you had to make (e.g. "VO ran 47s → tightened four lines, voice 1.08x";
|
|
2232
|
+
"hair drifted → restated the hair colour"). No URLs, keys or raw logs; it must stay under 48 KB.
|
|
2233
|
+
In the checkpoint flow, save this before Step 4.4 too and skip the duplicate write afterward.
|
|
2234
|
+
7. Pin it — only a \`passed\` render (or a \`blocked\` one the user said to use anyway):
|
|
2235
|
+
If checkpoint recovery already verified selection of this render, skip this duplicate write.
|
|
2236
|
+
An unfinished checkpoint must use the guarded selection protocol above; never replace it
|
|
2237
|
+
with an unguarded pin.
|
|
2238
|
+
\`video_project_upsert { brand_id, project_id, patch: { final_render_id: render_id } }\`,
|
|
2239
|
+
then use the returned card: widget hosts get one line and no duplicate links; text hosts get
|
|
2240
|
+
card.text_summary and the returned delivery links verbatim. Never end with a local file path.
|
|
2241
|
+
|
|
2242
|
+
Narrate each long step in one line via \`video_project_upsert { brand_id, project_id, patch:
|
|
2243
|
+
{ message: { role: "agent", content } } }\` — never sit silent on a queue > 90s. Write it for the
|
|
2244
|
+
customer ("Filming the scenes, about 5 more minutes"), per "How to talk to the customer" above.
|
|
1388
2245
|
|
|
1389
2246
|
## Media generation — the GooseWorks proxies (queue loop)
|
|
1390
2247
|
|
|
1391
|
-
Media APIs go through GooseWorks proxies
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
2248
|
+
Media APIs go through GooseWorks proxies; do NOT use an SDK's default host (your token isn't a
|
|
2249
|
+
FAL/ElevenLabs token → 401, and in a sandbox a raw provider call is never allowed). Prefer the
|
|
2250
|
+
atoms' own \`media_proxy.py\` (\`fal_generate\`, \`fal_generate_video\`, \`eleven_music\`, …) — it already
|
|
2251
|
+
handles auth, the queue host-swap, project attribution (\`GW_PROJECT_ID\`) and failure logging.
|
|
2252
|
+
|
|
2253
|
+
- **Sandbox:** FAL base = \`$GW_FAL_PROXY_URL\`, ElevenLabs = \`$GW_ELEVENLABS_PROXY_URL\`, FAL
|
|
2254
|
+
storage = \`$GW_FAL_STORAGE_PROXY_URL\`; auth = \`?token=$GW_MEDIA_PROXY_TOKEN\` (or
|
|
2255
|
+
\`Authorization: Bearer\`), plus \`&project_id=$GW_PROJECT_ID\`. The token already names the agent.
|
|
2256
|
+
- **Local:** base = \`<api_base>/api/internal/<proxy>\` (\`fal-proxy\`, \`fal-storage-proxy\`,
|
|
2257
|
+
\`elevenlabs-proxy\`); pass \`?token=<api_key>&agent_id=<agent_id>&project_id=<project_id>\`
|
|
2258
|
+
(agent_id bills the Ads agent; project_id attributes the spend to this ad project so the user
|
|
2259
|
+
sees per-project spend in the app. ALWAYS pass it).
|
|
1398
2260
|
|
|
1399
2261
|
**FAL queue gotcha** (#1 waste of generations): submit returns \`status_url\`/\`response_url\` on
|
|
1400
2262
|
\`queue.fal.run\` (the real host, not the proxy). Polling those 401s forever — rewrite their host
|
|
1401
|
-
to the proxy base (keep the path), re-add
|
|
1402
|
-
|
|
2263
|
+
to the proxy base (keep the path), re-add the auth params. Only the final \`*.fal.media\`
|
|
2264
|
+
file is a real public URL. Helper (works in both modes):
|
|
1403
2265
|
|
|
1404
2266
|
\`\`\`python
|
|
1405
2267
|
import json, os, pathlib, time, requests
|
|
1406
2268
|
from urllib.parse import urlparse
|
|
1407
2269
|
|
|
1408
2270
|
def _cfg():
|
|
2271
|
+
"""(fal_base, token, agent_id). Sandbox env first, then ~/.gooseworks/credentials.json."""
|
|
2272
|
+
tok = os.environ.get("GW_MEDIA_PROXY_TOKEN")
|
|
2273
|
+
if tok:
|
|
2274
|
+
base = os.environ.get("GW_FAL_PROXY_URL") or os.environ["GW_API_BASE"].rstrip("/") + "/api/internal/fal-proxy"
|
|
2275
|
+
return base.rstrip("/"), tok, None
|
|
1409
2276
|
c = json.loads(pathlib.Path(os.path.expanduser("~/.gooseworks/credentials.json")).read_text())
|
|
1410
|
-
return c["api_base"].rstrip("/"), c["api_key"], c.get("agent_id")
|
|
2277
|
+
return c["api_base"].rstrip("/") + "/api/internal/fal-proxy", c["api_key"], c.get("agent_id")
|
|
1411
2278
|
|
|
1412
|
-
def _params(tok, agent
|
|
2279
|
+
def _params(tok, agent):
|
|
1413
2280
|
p = {"token": tok}
|
|
1414
2281
|
if agent: p["agent_id"] = agent
|
|
1415
|
-
|
|
2282
|
+
pid = os.environ.get("GW_PROJECT_ID")
|
|
2283
|
+
if pid: p["project_id"] = pid # attributes the spend to this ad project
|
|
1416
2284
|
return p
|
|
1417
2285
|
|
|
1418
|
-
def fal_generate(model_path, payload,
|
|
2286
|
+
def fal_generate(model_path, payload, timeout_s=180, poll_s=3):
|
|
1419
2287
|
"""model_path e.g. 'fal-ai/nano-banana-2/edit' (the recipe names the model).
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
base = api_base + "/api/internal/fal-proxy"
|
|
1424
|
-
sub = requests.post(f"{base}/{model_path}", params=_params(tok, agent, project_id), json=payload).json()
|
|
2288
|
+
Export GW_PROJECT_ID first. Returns the result image URL (a public *.fal.media URL)."""
|
|
2289
|
+
base, tok, agent = _cfg()
|
|
2290
|
+
sub = requests.post(f"{base}/{model_path}", params=_params(tok, agent), json=payload).json()
|
|
1425
2291
|
to_proxy = lambda u: base + urlparse(u).path
|
|
1426
2292
|
status_url, response_url = to_proxy(sub["status_url"]), to_proxy(sub["response_url"])
|
|
1427
2293
|
deadline = time.time() + timeout_s
|
|
1428
2294
|
while time.time() < deadline:
|
|
1429
|
-
st = requests.get(status_url, params=_params(tok, agent
|
|
2295
|
+
st = requests.get(status_url, params=_params(tok, agent)).json()
|
|
1430
2296
|
if st.get("status") == "COMPLETED":
|
|
1431
|
-
return requests.get(response_url, params=_params(tok, agent
|
|
2297
|
+
return requests.get(response_url, params=_params(tok, agent)).json()["images"][0]["url"]
|
|
1432
2298
|
if st.get("status") in ("FAILED", "ERROR"):
|
|
1433
2299
|
raise RuntimeError(f"FAL failed: {st}")
|
|
1434
2300
|
time.sleep(poll_s)
|
|
1435
2301
|
raise TimeoutError("FAL polling exceeded timeout")
|
|
1436
2302
|
\`\`\`
|
|
1437
2303
|
|
|
1438
|
-
ElevenLabs (VO / music) is the same shape against
|
|
1439
|
-
|
|
1440
|
-
\`
|
|
1441
|
-
|
|
1442
|
-
\`
|
|
2304
|
+
ElevenLabs (VO / music) is the same shape against the ElevenLabs proxy base. To feed FAL a local
|
|
2305
|
+
file (a product image, a VO track), it must be a PUBLIC URL: upload it with \`media_upload\`
|
|
2306
|
+
(\`scope: "video_project"\`, source \`file\`, no \`path\`) → PUT → \`media_confirm\` and use the returned
|
|
2307
|
+
\`media.url\` if it is a public https URL (curl it: HTTP 200 without auth), or host it through the
|
|
2308
|
+
FAL storage proxy. Never pass a \`render-file\` URL to a provider — it needs app auth.
|
|
2309
|
+
|
|
2310
|
+
## Follow-up actions use the watched version
|
|
2311
|
+
|
|
2312
|
+
Keep original note text, exact source render/time and intended viewer effect. Record an
|
|
2313
|
+
acceptance condition and disposition in existing feedback/project artifacts; applying a
|
|
2314
|
+
command means changed, not verified. Consolidate duplicates without deleting their source
|
|
2315
|
+
IDs. Conflicting “calmer” and “more urgent” instructions need the authorized decision owner's
|
|
2316
|
+
recorded choice, even if they touch different files. Offer concrete cheap alternatives for
|
|
2317
|
+
“more premium” inside the approved scope before escalating taste.
|
|
2318
|
+
|
|
2319
|
+
Map changed copy/performance/shot/pause to affected picture, timings, captions, music/SFX,
|
|
2320
|
+
approvals and derivatives; preserve unrelated approved work. Rewatch the full candidate and
|
|
2321
|
+
verify the original effect before resolving a note. Accepted final, candidate and original
|
|
2322
|
+
note source remain distinct across selection and rollback. Keep editable script/timeline,
|
|
2323
|
+
source assets/takes, stems, captions, package versions and prior decisions in the existing
|
|
2324
|
+
production manifest/history; retrieve applicable rejections/pronunciation/delivery rules
|
|
2325
|
+
before a related brief. Project taste is not a standing brand rule. Record audience results
|
|
2326
|
+
only with actual variant/audience/placement/objective/window; otherwise unavailable.
|
|
2327
|
+
|
|
2328
|
+
- “Fix this”: use the render/project context attached to the message (or named version, else
|
|
2329
|
+
the final one). Save patch.fix { of_render_id, changes, total_credits }. Never replace a supplied
|
|
2330
|
+
watched render with the final. After explicit approval save patch.approve { user_quote,
|
|
2331
|
+
total_credits, scope:"fix" }, then video_render_run { kind:"partial", fix_of_render_id }.
|
|
2332
|
+
Re-make only changed scenes, keep earlier versions and charge no second base fee.
|
|
2333
|
+
- “Make 3 more like this”: video_project_upsert { brand_id, name, remix_of_project_id,
|
|
2334
|
+
remix_of_render_id, vary, concepts:3 }. Preserve a provided watched render; omit only when none is named.
|
|
2335
|
+
- “Test 1 of each”: approve only:"test"; “Make the other N”: only:"rest". “Stop the rest”:
|
|
2336
|
+
job_cancel { job_id:<batch_id> }. Finished videos stay saved.
|
|
2337
|
+
- Share for review uses patch.share_for_review { subject }; return the real link.
|
|
1443
2338
|
|
|
1444
2339
|
## Rules
|
|
1445
2340
|
|
|
1446
|
-
- **MCP
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
2341
|
+
- **Canonical MCP tools first** (\`video_project_read\`, \`video_project_upsert\`, \`catalog_fetch\`,
|
|
2342
|
+
\`media_upload\` + \`media_confirm\`, the \`video_render_run\` render-row actions, \`account_whoami\`);
|
|
2343
|
+
legacy names only when the client lacks the canonical tool.
|
|
2344
|
+
- **A missing key is never a blocker.** Paid media goes through the proxy: a one-off image or clip
|
|
2345
|
+
is \`data_post_provider { provider: "fal", … }\` + \`job_get\` (see "Paid media over the MCP");
|
|
2346
|
+
never ask for FAL_KEY, never use \`photos_generate\` for anything but a physical product.
|
|
2347
|
+
- **The CLI and credentials.json are optional.** In a GooseWorks sandbox (\`GW_MEDIA_PROXY_TOKEN\`
|
|
2348
|
+
set) use the env proxies and \`catalog_fetch\`; never call a provider with a raw key.
|
|
2349
|
+
- **No Chromium in a sandbox** — a browser-rendered format stops there, before any spend, and says so.
|
|
2350
|
+
- **Toolchain before spend** — common checks use \`gooseworks doctor --no-browser\`; every selected
|
|
2351
|
+
browser renderer must also pass \`--renderer-script\` or its equivalent exact-runtime launch
|
|
2352
|
+
check in Step 2. Stop with the folder-specific fix if anything is missing.
|
|
2353
|
+
- **Assemble the whole review set first**, mirror it with \`video_project_upsert patch.script\`, and
|
|
2354
|
+
get ONE approval in this chat, recorded with \`patch.approve\`, BEFORE the expensive render
|
|
2355
|
+
(review-once). Never send the user to the app to approve; the app is only for reviewing.
|
|
1451
2356
|
- **Show the REAL cheap pieces; PROMPT only the expensive render.** Generate the FREE + CHEAP-paid
|
|
1452
2357
|
pieces (≤ ~100 credits — stills, creator frame, end card, short VO/music) and mirror the real
|
|
1453
2358
|
assets; put ONLY the expensive video take/render in the panel as its exact prompt. That prompt
|
|
1454
2359
|
must be in the panel before you ask to approve, so a single "go" runs the render + any remaining
|
|
1455
2360
|
paid work (→ QC → publish) with no re-pausing.
|
|
2361
|
+
- **Operator may be absent (sandbox):** decide mechanical choices yourself; ask in chat only for a
|
|
2362
|
+
true taste call.
|
|
1456
2363
|
- **Costs in CREDITS, never dollars.** credits ≈ round-up(provider-$ × 120) per generation + a flat
|
|
1457
2364
|
200-credit base per video; never show a "$…" figure to the user.
|
|
1458
2365
|
- **Never assemble the full video before approval.** The review shows the
|
|
@@ -1460,19 +2367,27 @@ path. (\`fal-storage-proxy\` may 404 depending on the install; don't block on it
|
|
|
1460
2367
|
stitched/composited cut; do not add a "full cascade" / finished-video clip
|
|
1461
2368
|
as a review ingredient (GOOSE-2542). The assembled video is produced only in
|
|
1462
2369
|
Step 4.
|
|
1463
|
-
- **
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
2370
|
+
- **Open the render row at the start of Step 4** (4.1, after the approval) and keep its stage /
|
|
2371
|
+
progress current; mark it \`complete\` only after the master passes QC. \`output_url\` = the
|
|
2372
|
+
durable render-file URL, never a CDN URL.
|
|
2373
|
+
- **Always export \`GW_PROJECT_ID\`** (and pass \`project_id\` on hand-rolled proxy calls) so the
|
|
2374
|
+
credits attribute to this ad project.
|
|
1467
2375
|
- **Verify a real, non-empty MP4** (watch it) before marking the render complete.
|
|
1468
2376
|
- **Reuse the brand** when its research is complete; never re-research.
|
|
2377
|
+
- **Brand rules first:** load kit + products + learnings (Step 1), write \`working/brand-rules.json\`
|
|
2378
|
+
(Step 1.7), and follow it: pronunciations in every voiceover, nothing from \`never_say\`, product
|
|
2379
|
+
facts only from the product rows, the kit's logo FILE (never a generated or favicon logo), the
|
|
2380
|
+
brand font, this product's own images.
|
|
2381
|
+
- **Save brand corrections** from chat with \`brand_update patch.facts\` in the same turn, and say so.
|
|
2382
|
+
- **Finished-ad gate on every master** (\`review-finished-ad\` + the sheet); at most 2 repair
|
|
2383
|
+
rounds, then publish as \`blocked\` with the report and warn the user — never pass off a failing
|
|
2384
|
+
video as finished.
|
|
1469
2385
|
- On a hard error (auth/quota/model/timeout) set the render \`failed\` with a short
|
|
1470
|
-
\`error_message\` and stop — don't ship the source unchanged. **Also
|
|
1471
|
-
|
|
1472
|
-
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
`;
|
|
2386
|
+
\`error_message\` (\`${exports.RENDER_ROW_TOOL} { …, ${exports.RENDER_UPDATE_KEY}: { render_id, status: "failed", error_message } }\`) and stop — don't ship the source unchanged. **Also log
|
|
2387
|
+
it** (see "Report problems") so we can see + fix it.
|
|
2388
|
+
- Finish according to card.display_hint: widget hosts get one short line without duplicate links;
|
|
2389
|
+
text hosts get card.text_summary and the returned delivery links verbatim.
|
|
2390
|
+
${STORED_FOOTAGE_GUIDANCE}`;
|
|
1476
2391
|
}
|
|
1477
2392
|
/**
|
|
1478
2393
|
* Returns the goose-product-photos entry SKILL.md content.
|
|
@@ -1513,6 +2428,8 @@ Photos studio uses, so the skill and the app can never drift. The point is to **
|
|
|
1513
2428
|
usable product imagery** — approved photos join the brand kit and can then feed the ad workflow
|
|
1514
2429
|
(\`goose-ads\`).
|
|
1515
2430
|
|
|
2431
|
+
${CUSTOMER_TALK}
|
|
2432
|
+
|
|
1516
2433
|
## Prerequisite — the GooseWorks MCP server is REQUIRED
|
|
1517
2434
|
|
|
1518
2435
|
Everything goes through the \`mcp__gooseworks__*\` tools. If they are not available, **stop and
|
|
@@ -1522,7 +2439,7 @@ HTTP/file fallback.
|
|
|
1522
2439
|
## Start from the brand context — don't re-ask what it already answers
|
|
1523
2440
|
|
|
1524
2441
|
If the \`gooseworks\` router handed you brand context, USE IT. If you were invoked directly, call
|
|
1525
|
-
\`
|
|
2442
|
+
\`brand_read\` yourself first. It answers most of the setup questions below, so **do not ask
|
|
1526
2443
|
the user for them**:
|
|
1527
2444
|
|
|
1528
2445
|
- **Which product?** — the context's \`products[]\` are the real catalog entries. Offer them; never
|
|
@@ -1586,7 +2503,7 @@ whether a human model is wanted (which needs explicit consent — see the rules)
|
|
|
1586
2503
|
|
|
1587
2504
|
## Workflow — shoot a product
|
|
1588
2505
|
|
|
1589
|
-
1. **Load the brand context** (\`
|
|
2506
|
+
1. **Load the brand context** (\`brand_read\`, or reuse what the router passed you) and
|
|
1590
2507
|
**resolve the brand + product.** \`list_ad_brands\` → \`brand_id\`. \`list_brand_products\` → pick a
|
|
1591
2508
|
\`product_id\` from the catalog you already know about. If the product genuinely isn't there,
|
|
1592
2509
|
\`import_product\` (poll \`get_product_import\`).
|
|
@@ -1604,7 +2521,7 @@ whether a human model is wanted (which needs explicit consent — see the rules)
|
|
|
1604
2521
|
- **Never invent product facts.** The backend grounds the shot on the product's real images; don't
|
|
1605
2522
|
describe a product you can't see.
|
|
1606
2523
|
- **Use the brand context instead of interviewing the user.** Product, audience, voice, positioning,
|
|
1607
|
-
logo/colors/fonts all come from \`
|
|
2524
|
+
logo/colors/fonts all come from \`brand_read\` / the brand kit. Ask only for the shot
|
|
1608
2525
|
category, count, quality, and model consent.
|
|
1609
2526
|
- **Ask before spending.** Quote the estimate and confirm \`count\` / \`quality\` before
|
|
1610
2527
|
\`generate_product_photos\` — it reserves credits.
|
|
@@ -1616,4 +2533,8 @@ whether a human model is wanted (which needs explicit consent — see the rules)
|
|
|
1616
2533
|
kit / ad workflow; archived ones never do.
|
|
1617
2534
|
`;
|
|
1618
2535
|
}
|
|
2536
|
+
/** Thin GooseWorks connection to the catalog-published production harness. */
|
|
2537
|
+
function getMakeCustomVideoSkillContent() {
|
|
2538
|
+
return custom_video_skill_1.CUSTOM_VIDEO_ADAPTER_CONTENT.replace("\n# Agent version\n", `\n# Agent version\n\n${CUSTOMER_TALK}\n\n${ENVIRONMENT_IDENTITY}\n\n${ASSET_READINESS}\n`) + STORED_FOOTAGE_GUIDANCE;
|
|
2539
|
+
}
|
|
1619
2540
|
//# sourceMappingURL=master-skill.js.map
|