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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkBrowserPreflight = checkBrowserPreflight;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const MARKER = 'GOOSE_BROWSER_PREFLIGHT=';
|
|
40
|
+
// A separate Node process preserves native module resolution (including NODE_PATH)
|
|
41
|
+
// and keeps a broken Playwright installation from hanging or crashing the CLI.
|
|
42
|
+
// Nothing imports or runs the renderer, downloads a browser, or navigates a page.
|
|
43
|
+
const PROBE = String.raw `
|
|
44
|
+
const fs = require('fs');
|
|
45
|
+
const path = require('path');
|
|
46
|
+
const { createRequire } = require('module');
|
|
47
|
+
// Register each native child synchronously at creation. A probe can crash/exit
|
|
48
|
+
// before the supervisor's inventory poll; Chromium must remain owned then too.
|
|
49
|
+
const childProcess = require('child_process');
|
|
50
|
+
const nativeSpawn = childProcess.spawn;
|
|
51
|
+
if (process.platform !== 'win32') {
|
|
52
|
+
childProcess.spawn = function(...args) {
|
|
53
|
+
const child = Reflect.apply(nativeSpawn, this, args);
|
|
54
|
+
if (child.pid) {
|
|
55
|
+
const inventory = childProcess.spawnSync('/bin/ps', ['-p', String(child.pid), '-o', 'pid=', '-o', 'ppid=', '-o', 'pgid=', '-o', 'stat=', '-o', 'lstart='], {
|
|
56
|
+
encoding: 'utf8', timeout: 250, maxBuffer: 4096, stdio: ['ignore', 'pipe', 'ignore'],
|
|
57
|
+
});
|
|
58
|
+
const row = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(inventory.stdout || '');
|
|
59
|
+
fs.writeSync(1, 'GOOSE_BROWSER_CHILD=' + JSON.stringify({
|
|
60
|
+
pid: child.pid, parent: process.pid,
|
|
61
|
+
born: row && Number(row[2]) === process.pid ? row[5] : null,
|
|
62
|
+
}) + '\n');
|
|
63
|
+
}
|
|
64
|
+
return child; // Preserve arguments, launch flags and the native ChildProcess.
|
|
65
|
+
};
|
|
66
|
+
require('module').syncBuiltinESMExports();
|
|
67
|
+
}
|
|
68
|
+
let [anchor, selected, launchMs, closeMs] = process.argv.slice(1);
|
|
69
|
+
const meta = {};
|
|
70
|
+
let browser;
|
|
71
|
+
let completed = false;
|
|
72
|
+
function finish(code, detail) {
|
|
73
|
+
if (completed) return;
|
|
74
|
+
completed = true;
|
|
75
|
+
process.stdout.write('GOOSE_BROWSER_PREFLIGHT=' + JSON.stringify({
|
|
76
|
+
ok: code === 'ready', code, detail, ...meta,
|
|
77
|
+
}) + '\n');
|
|
78
|
+
// Keep the tree rooted here until the supervisor acknowledges the result.
|
|
79
|
+
// In particular, Windows taskkill /T needs the parent PID still alive.
|
|
80
|
+
setInterval(() => {}, 1000);
|
|
81
|
+
}
|
|
82
|
+
function message(error) { return String(error?.message || error).slice(0, 4000); }
|
|
83
|
+
async function closeBrowser() {
|
|
84
|
+
if (!browser) return;
|
|
85
|
+
await Promise.race([
|
|
86
|
+
browser.close(),
|
|
87
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('browser close timed out')), Number(closeMs))),
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
(async () => {
|
|
91
|
+
if (selected === 'true') {
|
|
92
|
+
try {
|
|
93
|
+
if (!fs.statSync(anchor).isFile()) throw new Error('not a file');
|
|
94
|
+
// Node resolves its main script's symlink unless explicitly told otherwise.
|
|
95
|
+
if (!/--preserve-symlinks-main\b/.test(process.env.NODE_OPTIONS || '')) anchor = fs.realpathSync(anchor);
|
|
96
|
+
meta.resolvedScript = anchor;
|
|
97
|
+
}
|
|
98
|
+
catch { return finish('invalid_script', 'Renderer script is not an existing file: ' + anchor); }
|
|
99
|
+
}
|
|
100
|
+
const fromRenderer = createRequire(anchor);
|
|
101
|
+
let playwright;
|
|
102
|
+
try {
|
|
103
|
+
meta.modulePath = fromRenderer.resolve('playwright');
|
|
104
|
+
playwright = fromRenderer('playwright');
|
|
105
|
+
meta.version = fromRenderer('playwright/package.json').version;
|
|
106
|
+
meta.cliPath = path.join(path.dirname(fromRenderer.resolve('playwright/package.json')), 'cli.js');
|
|
107
|
+
meta.executablePath = playwright.chromium.executablePath();
|
|
108
|
+
process.stdout.write('GOOSE_BROWSER_PREFLIGHT_META=' + JSON.stringify(meta) + '\n');
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return finish(meta.modulePath ? 'probe_failed' : 'missing_module', message(error));
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
// Preserve chromium.launch() defaults: no alternate executable, channel,
|
|
114
|
+
// headless mode or sandbox flags. The only override bounds the wait.
|
|
115
|
+
browser = await playwright.chromium.launch({ timeout: Number(launchMs) });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const detail = message(error);
|
|
118
|
+
const code = /executable.*doesn't exist|executable.*not found|ENOENT/i.test(detail)
|
|
119
|
+
? 'missing_browser' : /timeout|timed out/i.test(detail) ? 'timeout' : 'launch_failed';
|
|
120
|
+
return finish(code, detail);
|
|
121
|
+
}
|
|
122
|
+
try { await closeBrowser(); }
|
|
123
|
+
catch (error) { return finish('close_failed', message(error)); }
|
|
124
|
+
finish('ready', 'Chromium launched and closed with the default renderer settings');
|
|
125
|
+
})().catch(error => finish('probe_failed', message(error)));
|
|
126
|
+
`;
|
|
127
|
+
function quote(value) {
|
|
128
|
+
return process.platform === 'win32' ? `"${value}"` : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
129
|
+
}
|
|
130
|
+
function repair(code, folder, cliPath) {
|
|
131
|
+
const cd = `${process.platform === 'win32' ? 'cd /d' : 'cd'} ${quote(folder)}`;
|
|
132
|
+
const cli = cliPath ? `${quote(process.execPath)} ${quote(cliPath)}` : 'npx --no-install playwright';
|
|
133
|
+
if (code === 'invalid_script')
|
|
134
|
+
return 'Pass --renderer-script with the actual fetched renderer script path';
|
|
135
|
+
if (code === 'missing_module')
|
|
136
|
+
return `${cd} && npm install playwright && npx --no-install playwright install chromium`;
|
|
137
|
+
if (code === 'missing_browser')
|
|
138
|
+
return `${cd} && ${cli} install chromium`;
|
|
139
|
+
if (code === 'ready')
|
|
140
|
+
return '';
|
|
141
|
+
return `${cd} && ${cli} install --with-deps chromium`;
|
|
142
|
+
}
|
|
143
|
+
// spawnSync has no detached/process-group option. This supervisor uses spawn()
|
|
144
|
+
// to own the probe. Chromium creates another detached Unix group, so cleanup
|
|
145
|
+
// tracks descendants by parentage, freezes them, and kills owned PIDs before
|
|
146
|
+
// the probe. The existing synchronous doctor/install API stays compatible.
|
|
147
|
+
const SUPERVISOR = String.raw `
|
|
148
|
+
const { spawn, spawnSync } = require('child_process');
|
|
149
|
+
const [probe, ...args] = process.argv.slice(1);
|
|
150
|
+
let output = '';
|
|
151
|
+
let meta = {};
|
|
152
|
+
let finished = false;
|
|
153
|
+
const owned = new Map();
|
|
154
|
+
let rootBorn;
|
|
155
|
+
let ownershipUncertain = false;
|
|
156
|
+
const registrations = new Set();
|
|
157
|
+
function processTable() {
|
|
158
|
+
const result = spawnSync('/bin/ps', ['-A', '-o', 'pid=', '-o', 'ppid=', '-o', 'pgid=', '-o', 'stat=', '-o', 'lstart='], {
|
|
159
|
+
encoding: 'utf8', timeout: 250, maxBuffer: 256 * 1024, stdio: ['ignore', 'pipe', 'ignore'],
|
|
160
|
+
});
|
|
161
|
+
if (result.status !== 0) return null;
|
|
162
|
+
const rows = new Map();
|
|
163
|
+
for (const line of result.stdout.split('\n')) {
|
|
164
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
|
|
165
|
+
if (match) rows.set(Number(match[1]), { pid: Number(match[1]), parent: Number(match[2]), group: Number(match[3]), state: match[4], born: match[5] });
|
|
166
|
+
}
|
|
167
|
+
return rows.size ? rows : null;
|
|
168
|
+
}
|
|
169
|
+
// Without a process inventory we cannot safely clean up a detached browser.
|
|
170
|
+
// Fail before launching it; never use a broad browser-name or user-wide kill.
|
|
171
|
+
if (process.platform !== 'win32' && !processTable()) {
|
|
172
|
+
process.stdout.write('GOOSE_BROWSER_PREFLIGHT=' + JSON.stringify({ ok: false, code: 'probe_failed', detail: 'Cannot inspect owned browser processes with /bin/ps; browser was not launched' }) + '\n');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
const child = spawn(process.execPath, ['-e', probe, ...args], {
|
|
176
|
+
detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'ignore'], env: process.env,
|
|
177
|
+
});
|
|
178
|
+
function discover(rows) {
|
|
179
|
+
// Retain observed children after reparenting, but reject reused PID identities.
|
|
180
|
+
for (const [pid, row] of owned) if (rows.get(pid)?.born !== row.born) owned.delete(pid);
|
|
181
|
+
const root = rows.get(child.pid);
|
|
182
|
+
if (root && (!rootBorn || root.born === rootBorn)) {
|
|
183
|
+
rootBorn = root.born;
|
|
184
|
+
owned.set(child.pid, root);
|
|
185
|
+
}
|
|
186
|
+
let changed;
|
|
187
|
+
do {
|
|
188
|
+
changed = false;
|
|
189
|
+
for (const row of rows.values()) {
|
|
190
|
+
if (!owned.has(row.pid) && owned.has(row.parent)) {
|
|
191
|
+
owned.set(row.pid, row);
|
|
192
|
+
changed = true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} while (changed);
|
|
196
|
+
return [...owned.keys()].map(pid => rows.get(pid)).filter(row => row && !row.state.startsWith('Z'));
|
|
197
|
+
}
|
|
198
|
+
function signal(row, name) {
|
|
199
|
+
try { process.kill(row.pid, name); } catch {} // Already gone, or reported by verification below.
|
|
200
|
+
}
|
|
201
|
+
const monitor = process.platform === 'win32' ? null : setInterval(() => {
|
|
202
|
+
const rows = processTable();
|
|
203
|
+
if (rows) discover(rows);
|
|
204
|
+
}, 100);
|
|
205
|
+
function cleanup() {
|
|
206
|
+
if (monitor) clearInterval(monitor);
|
|
207
|
+
if (!child.pid) return { ok: true, remainingPids: [] };
|
|
208
|
+
if (process.platform === 'win32') {
|
|
209
|
+
const result = spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { timeout: 2000, stdio: 'ignore' });
|
|
210
|
+
return { ok: result.status === 0, remainingPids: result.status === 0 ? [] : [child.pid] };
|
|
211
|
+
}
|
|
212
|
+
const until = Date.now() + 1500;
|
|
213
|
+
const frozen = new Set();
|
|
214
|
+
let active = [];
|
|
215
|
+
let stable = false;
|
|
216
|
+
// Freeze parent processes first, then rescan for children created just before
|
|
217
|
+
// SIGSTOP. This stops further forks without changing browser launch options.
|
|
218
|
+
for (let round = 0; round < 8 && Date.now() < until; round++) {
|
|
219
|
+
const rows = processTable();
|
|
220
|
+
if (!rows) continue; // Retry a transient inventory timeout within the same budget.
|
|
221
|
+
active = discover(rows);
|
|
222
|
+
const fresh = active.filter(row => !frozen.has(row.pid));
|
|
223
|
+
for (const row of fresh) { signal(row, 'SIGSTOP'); frozen.add(row.pid); }
|
|
224
|
+
if (!fresh.length) { stable = true; break; }
|
|
225
|
+
}
|
|
226
|
+
// Use individual owned PIDs, not a whole group that could contain unrelated
|
|
227
|
+
// processes. Detached Chromium groups and their renderers are still covered.
|
|
228
|
+
for (const row of active.filter(row => row.pid !== child.pid).reverse()) signal(row, 'SIGKILL');
|
|
229
|
+
const root = active.find(row => row.pid === child.pid);
|
|
230
|
+
if (root) signal(root, 'SIGKILL');
|
|
231
|
+
else if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');
|
|
232
|
+
while (Date.now() < until) {
|
|
233
|
+
const rows = processTable();
|
|
234
|
+
if (!rows) continue;
|
|
235
|
+
active = discover(rows);
|
|
236
|
+
if (!active.length) return { ok: stable && !ownershipUncertain, remainingPids: [] };
|
|
237
|
+
for (const row of active) signal(row, 'SIGKILL');
|
|
238
|
+
}
|
|
239
|
+
return { ok: false, remainingPids: active.map(row => row.pid) };
|
|
240
|
+
}
|
|
241
|
+
function finish(code, override) {
|
|
242
|
+
if (finished) return;
|
|
243
|
+
finished = true;
|
|
244
|
+
clearTimeout(deadline);
|
|
245
|
+
const stopped = cleanup();
|
|
246
|
+
let result = { ...meta, ...override, cleanup: stopped };
|
|
247
|
+
if (!stopped.ok) {
|
|
248
|
+
result = { ...result, ok: false, code: 'probe_failed', detail: (result.detail || 'Browser probe ended') + '; could not confirm cleanup of owned browser processes' };
|
|
249
|
+
code = 1;
|
|
250
|
+
} else if (result.code === 'timeout') result.detail += '; its owned processes were stopped';
|
|
251
|
+
const text = 'GOOSE_BROWSER_PREFLIGHT=' + JSON.stringify(result) + '\n';
|
|
252
|
+
process.stdout.write(text, () => process.exit(code));
|
|
253
|
+
}
|
|
254
|
+
const deadline = setTimeout(() => finish(1, {
|
|
255
|
+
ok: false, code: 'timeout', detail: 'Browser launch/close probe timed out',
|
|
256
|
+
}), Number(args[2]) + Number(args[3]) + 500);
|
|
257
|
+
child.stdout.on('data', chunk => {
|
|
258
|
+
output += chunk;
|
|
259
|
+
if (Buffer.byteLength(output) > 60000) finish(1, { ok: false, code: 'probe_failed', detail: 'Browser probe exceeded its diagnostic output limit' });
|
|
260
|
+
// Consume creation records before processing a terminal result. writeSync in
|
|
261
|
+
// the probe and the close (not exit) handler preserve records on abrupt exit.
|
|
262
|
+
for (const line of output.split(/\r?\n/)) {
|
|
263
|
+
if (!line.startsWith('GOOSE_BROWSER_CHILD=') || !output.includes(line + '\n') || registrations.has(line)) continue;
|
|
264
|
+
registrations.add(line);
|
|
265
|
+
try {
|
|
266
|
+
const registration = JSON.parse(line.slice('GOOSE_BROWSER_CHILD='.length));
|
|
267
|
+
if (!registration.born || registration.parent !== child.pid) { ownershipUncertain = true; continue; }
|
|
268
|
+
// The creation record has the native parent's verified start identity.
|
|
269
|
+
// Seed discovery even if that parent has already exited and reparented it.
|
|
270
|
+
owned.set(registration.pid, registration);
|
|
271
|
+
} catch { ownershipUncertain = true; }
|
|
272
|
+
}
|
|
273
|
+
const metadata = output.split(/\r?\n/).find(line => line.startsWith('GOOSE_BROWSER_PREFLIGHT_META=') && output.includes(line + '\n'));
|
|
274
|
+
if (metadata) {
|
|
275
|
+
try { meta = JSON.parse(metadata.slice('GOOSE_BROWSER_PREFLIGHT_META='.length)); } catch {}
|
|
276
|
+
}
|
|
277
|
+
const line = output.split(/\r?\n/).find(line => line.startsWith('GOOSE_BROWSER_PREFLIGHT=') && output.includes(line + '\n'));
|
|
278
|
+
if (line) {
|
|
279
|
+
try { const result = JSON.parse(line.slice('GOOSE_BROWSER_PREFLIGHT='.length)); finish(result.ok ? 0 : 1, result); }
|
|
280
|
+
catch { finish(1, { ok: false, code: 'probe_failed', detail: 'Invalid browser probe result' }); }
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
child.on('error', error => finish(1, { ok: false, code: 'probe_failed', detail: error.message }));
|
|
284
|
+
child.on('close', () => finish(1, { ok: false, code: 'probe_failed', detail: 'Browser probe closed without a result' }));
|
|
285
|
+
`;
|
|
286
|
+
/**
|
|
287
|
+
* Probe the package the script would require, using the caller's cwd and env.
|
|
288
|
+
* Without a script, this is only a general check rooted in the current folder.
|
|
289
|
+
* Browser startup creates normal temporary runtime files; no project output.
|
|
290
|
+
*/
|
|
291
|
+
function checkBrowserPreflight(opts = {}) {
|
|
292
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
293
|
+
const selected = opts.rendererScript !== undefined;
|
|
294
|
+
const anchor = path.resolve(cwd, opts.rendererScript ?? '__gooseworks_browser_probe__.js');
|
|
295
|
+
const folder = path.dirname(anchor);
|
|
296
|
+
const launchMs = opts.launchTimeoutMs ?? 15000;
|
|
297
|
+
const closeMs = opts.closeTimeoutMs ?? 3000;
|
|
298
|
+
let result;
|
|
299
|
+
try {
|
|
300
|
+
const probe = (0, child_process_1.spawnSync)(process.execPath, ['-e', SUPERVISOR, PROBE, anchor, String(selected), String(launchMs), String(closeMs)], {
|
|
301
|
+
cwd,
|
|
302
|
+
env: process.env,
|
|
303
|
+
encoding: 'utf8',
|
|
304
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
305
|
+
timeout: launchMs + closeMs + 3500,
|
|
306
|
+
killSignal: 'SIGKILL',
|
|
307
|
+
maxBuffer: 64 * 1024,
|
|
308
|
+
});
|
|
309
|
+
const error = probe.error;
|
|
310
|
+
const line = (probe.stdout ?? '').split(/\r?\n/).find((s) => s.startsWith(MARKER));
|
|
311
|
+
if (line && !probe.error) {
|
|
312
|
+
result = JSON.parse(line.slice(MARKER.length));
|
|
313
|
+
if ((result.code === 'ready' && probe.status !== 0) || !result.code)
|
|
314
|
+
throw new Error('invalid browser probe result');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
result = {
|
|
318
|
+
ok: false,
|
|
319
|
+
code: error?.code === 'ETIMEDOUT' ? 'timeout' : 'probe_failed',
|
|
320
|
+
detail: error?.code === 'ETIMEDOUT'
|
|
321
|
+
? 'Browser probe supervisor timed out before returning a result'
|
|
322
|
+
: `Browser probe could not complete (${probe.error?.message ?? `exit ${probe.status}`})`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
result = { ok: false, code: 'probe_failed', detail: `Browser probe failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
328
|
+
}
|
|
329
|
+
const location = result.modulePath ? `; Playwright ${result.version ?? 'unknown version'} at ${result.modulePath}` : '';
|
|
330
|
+
const executable = result.executablePath ? `; Chromium executable: ${result.executablePath}` : '';
|
|
331
|
+
return {
|
|
332
|
+
...result,
|
|
333
|
+
...(selected ? { rendererScript: anchor } : {}),
|
|
334
|
+
detail: `${selected ? `Renderer ${anchor}` : `General browser setup in ${cwd}`}: ${result.detail}${location}${executable}`,
|
|
335
|
+
fix: repair(result.code, result.resolvedScript ? path.dirname(result.resolvedScript) : folder, result.cliPath),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=browser-preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-preflight.js","sourceRoot":"","sources":["../../src/utils/browser-preflight.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gooseworks",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "GooseWorks CLI
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "GooseWorks CLI \u2014 give your coding agent real data tools",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gooseworks": "./dist/index.js"
|
|
7
7
|
},
|
|
@@ -30,6 +30,58 @@ The GooseWorks ads skill. Two jobs:
|
|
|
30
30
|
2. **Analyze ad performance** — fetch ad-analytics recipes from goose-skills on demand
|
|
31
31
|
(these are unrelated to generation; see "Analyze / intelligence" below).
|
|
32
32
|
|
|
33
|
+
## How to talk to the customer (applies to every message you send them)
|
|
34
|
+
|
|
35
|
+
The customer is a marketer or founder, not an engineer. Everything in this skill about tools,
|
|
36
|
+
fields, files, polling, models and pipeline steps is for YOU. Never pass it on to them.
|
|
37
|
+
|
|
38
|
+
- **Say what they get and what they need to decide, never how it's made.**
|
|
39
|
+
Bad: "Checking the media-proxy helpers for voiceover timestamps and lipsync." / "Generated with
|
|
40
|
+
gpt-image-2 at high quality, mouth closed, passes QC."
|
|
41
|
+
Good: "Recording the voices now." / "Here are your two hosts."
|
|
42
|
+
- **Never mention** tool, file or field names, ids, JSON, commands, scripts, installed software,
|
|
43
|
+
uploads, model or vendor names, timings in seconds or frames, retries, polling, internal statuses
|
|
44
|
+
or your own quality checks. Fix what you can quietly. If they ask how something works, explain it
|
|
45
|
+
simply.
|
|
46
|
+
- **Update them only at milestones they care about**: choices confirmed, script and voices done,
|
|
47
|
+
visuals done, ready to review, finished. One short line each, then stay quiet until the next
|
|
48
|
+
milestone. No update for setup, downloads, uploads or saving.
|
|
49
|
+
- **Money is in credits, with a number**: "the full video uses about N credits". Never "the cheap
|
|
50
|
+
pieces" or "the expensive render".
|
|
51
|
+
- **Problems:** say what it means for them and what they can do, in one or two sentences. No error
|
|
52
|
+
codes or stack traces. Only raise a problem that changes something for them.
|
|
53
|
+
- **Use their words**: script, voice, hook, scene, host, ending, logo, the product shot.
|
|
54
|
+
- **No filler**: no "Great question!", "Certainly!", "I hope this helps!", no restating what they
|
|
55
|
+
just said at length.
|
|
56
|
+
|
|
57
|
+
Example. Instead of a dozen lines about render tools, scripts, uploads and portraits, send:
|
|
58
|
+
"Got it: warm tone, home podcast studio, Brielle as the skeptic and Mark as the believer." then
|
|
59
|
+
"Script and voices are done (about 34 seconds)." then "Ready for you to review: the script, voices,
|
|
60
|
+
both hosts and the ending. You can also review your recipe ingredients in the app: <link>. Say go
|
|
61
|
+
here and I'll make the full video (about N credits)."
|
|
62
|
+
|
|
63
|
+
## Use current instructions for new work
|
|
64
|
+
|
|
65
|
+
Before a new task in a terminal host, run `gooseworks skills status` once. It compares the
|
|
66
|
+
installed entry files, running CLI and published npm release. An older CLI needs a package
|
|
67
|
+
upgrade before `gooseworks update`; update alone only uses that CLI's bundled instructions.
|
|
68
|
+
Preserve local edits or unknown install provenance. Review/back up before explicitly replacing
|
|
69
|
+
modified files; never quietly reinstall over them. If the release check is unavailable, report
|
|
70
|
+
that freshness is unknown rather than claiming the local copy is latest.
|
|
71
|
+
|
|
72
|
+
For a new recipe run, fetch its package from the connected catalog. Retain the returned
|
|
73
|
+
`version` / `contentHash` and every dependency's hash with the saved package. When reusing a
|
|
74
|
+
saved fetch JSON, `gooseworks fetch <slug> --saved-package <file>` returns the current package
|
|
75
|
+
and a hash comparison without changing that file. With MCP, if the advertised `catalog_fetch`
|
|
76
|
+
schema accepts them, send `saved_content_hash` and `saved_dependency_hashes`; otherwise fetch
|
|
77
|
+
normally and compare the returned hashes yourself. Missing hashes mean unknown, not current.
|
|
78
|
+
The server cannot inspect a client's saved files; hash metadata does not certify later edits.
|
|
79
|
+
Fetch current packages into a new run directory and report stale saved instructions. Preserve
|
|
80
|
+
an existing approved run's recorded package; changing that harness requires a reviewed change
|
|
81
|
+
and approval before spending. Hosted installed snapshots use the existing Skills Update action.
|
|
82
|
+
Skill content and a host's cached MCP tool schemas are separate: refreshing one does not refresh
|
|
83
|
+
the other. Check the actual advertised tools before using new fields.
|
|
84
|
+
|
|
33
85
|
## Prerequisite — the GooseWorks MCP server is REQUIRED
|
|
34
86
|
|
|
35
87
|
Everything goes through the `mcp__gooseworks__*` tools. If they are not available, **stop and
|
|
@@ -39,7 +91,7 @@ no HTTP/file fallback — the REST ad endpoints are session-cookie-only and reje
|
|
|
39
91
|
## Start from the brand context — don't re-ask what it already answers
|
|
40
92
|
|
|
41
93
|
If the `gooseworks` router handed you brand context, USE IT. If you were invoked directly, call
|
|
42
|
-
`
|
|
94
|
+
`brand_read { brand_id, sections: ["summary","kit","products","learnings"] }` first. It already
|
|
43
95
|
answers most of what the flows below would otherwise ask the user:
|
|
44
96
|
|
|
45
97
|
- **Which product to feature** → `products[]`. Offer the real catalog entries; never guess a
|
|
@@ -147,7 +199,7 @@ immediately.
|
|
|
147
199
|
competitor ad or an image found online.
|
|
148
200
|
- `get_ad_project` / `append_project_message` — inspect a creative / leave a note on its thread.
|
|
149
201
|
|
|
150
|
-
## Keep the brand kit in sync — reconcile, then
|
|
202
|
+
## Keep the brand kit in sync — reconcile, then save or propose
|
|
151
203
|
|
|
152
204
|
The brand kit is the source of truth every generation reads. During ANY task, when the user
|
|
153
205
|
**tells you something about the brand or asks to change something brand-level** — a different
|
|
@@ -155,24 +207,79 @@ tagline, audience, voice, a product's name/price/description, "our logo is X", "
|
|
|
155
207
|
anymore", a new product photo — treat it as a possible kit update, don't just use it for this one
|
|
156
208
|
ad and forget it:
|
|
157
209
|
|
|
158
|
-
1. **Check it against the kit.** Call `
|
|
210
|
+
1. **Check it against the kit.** Call `brand_read` with `kit` and `learnings` for the active brand and see whether what the user said
|
|
159
211
|
matches, is missing from, or contradicts the kit.
|
|
160
212
|
2. **If it's already in the kit and matches** — nothing to do; proceed.
|
|
161
|
-
3. **If it's new or different
|
|
162
|
-
the
|
|
163
|
-
|
|
164
|
-
4. **Persist with the write tools** (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- `
|
|
168
|
-
- `add_brand_product_image` / `remove_brand_reference_image` — product and reference photos.
|
|
213
|
+
3. **If it's new or different**, persist an explicit request to correct or remember the brand
|
|
214
|
+
through the policy below; that request is already authorization. For an ambiguous one-ad
|
|
215
|
+
direction, ask once whether it should stick. Agent-derived suggestions become proposals.
|
|
216
|
+
4. **Persist with the canonical write tools** (send only changed fields):
|
|
217
|
+
- `brand_update` with explicit user-correction intent — structured brand fields or products;
|
|
218
|
+
an inferred structured field uses agent-proposal intent instead. Follow the live schema.
|
|
219
|
+
- `media_upload` / `media_update` — the user's own product and reference photos.
|
|
169
220
|
Inspect each live schema and send only the fields needed for the confirmed change.
|
|
170
|
-
5. **
|
|
171
|
-
|
|
221
|
+
5. **Read back what changed** before saying saved, then continue the task. (Use the research
|
|
222
|
+
workflow for researched logo/colors/fonts; explicit user edits use only fields supported by
|
|
223
|
+
the canonical tool schema and the correction policy below.)
|
|
172
224
|
|
|
173
225
|
This is the parity gap the app closes in-product: a brand fact the user gives mid-task should be
|
|
174
226
|
able to flow back into the kit — with their ok — instead of being lost.
|
|
175
227
|
|
|
228
|
+
## Save durable brand answers, then verify them
|
|
229
|
+
|
|
230
|
+
Read the selected brand with `brand_read { brand_id, sections: ["summary", "kit", "products", "learnings", "onboarding"] }`
|
|
231
|
+
(fallback: `brand_get_context` with the same sections). Keep founder answers, user corrections,
|
|
232
|
+
research and your own hypotheses distinct. Reuse matching saved answers; ask only about gaps.
|
|
233
|
+
|
|
234
|
+
When the user asks to remember a rule, answers a brand interview, or explicitly corrects a
|
|
235
|
+
standing fact, save that answer in the same turn. The capture request authorizes those answers;
|
|
236
|
+
do not ask for approval again. A direction for this one video stays in its brief. If the scope
|
|
237
|
+
is genuinely ambiguous, ask whether it applies to future videos before saving a standing rule.
|
|
238
|
+
|
|
239
|
+
Use the **live registered schema**. Where supported, call `brand_update` with
|
|
240
|
+
`knowledge_intent: "user_correction"` and `user_statement` containing the user's exact,
|
|
241
|
+
verbatim answer, not your paraphrase or researched text.
|
|
242
|
+
For an inference or suggested improvement, use `knowledge_intent: "agent_proposal"`. Show the
|
|
243
|
+
before/after change from your prior read and proposed value; retain the returned proposal IDs
|
|
244
|
+
and say the user must accept it in the app. Link only a review surface actually returned by a
|
|
245
|
+
tool; the compact `knowledge_updates` response does not itself contain a diff or URL.
|
|
246
|
+
A pending proposal is not a saved fact. Never call an unavailable
|
|
247
|
+
tool or silently relabel research or your inference as something the user said.
|
|
248
|
+
|
|
249
|
+
The safe structured shape is `patch: { knowledge: { positioning?, audience?, voice?,
|
|
250
|
+
instructions?, brandType?, tagline?, valueProps? } }`, using only fields present in the live
|
|
251
|
+
schema. Inferred rules/taste go in an `instructions` proposal with a rationale, never in
|
|
252
|
+
`patch.facts`. Prefix every video-only preference in that proposed text with "Video preference:"
|
|
253
|
+
so it remains production direction after acceptance. Preserve unrelated instructions when
|
|
254
|
+
proposing a merged replacement.
|
|
255
|
+
|
|
256
|
+
| User answer | Canonical write |
|
|
257
|
+
| --- | --- |
|
|
258
|
+
| 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. |
|
|
259
|
+
| 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. |
|
|
260
|
+
| Required wording or pronunciation | `patch.facts: [{ kind: "must", text }]`; pronunciation is exactly `Pronounce "<term>" as "<say_as>"`. |
|
|
261
|
+
| Forbidden claim, word or visual | `patch.facts: [{ kind: "dont", text }]`. |
|
|
262
|
+
| 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:". |
|
|
263
|
+
|
|
264
|
+
Facts are existing `ad_brand_learning` rows with user provenance; they are not a second profile.
|
|
265
|
+
Update a matching rule by its returned `id` instead of adding duplicates. Preserve unrelated
|
|
266
|
+
rules and the user's exact meaning. Only use the legacy facts shape for explicitly user-authored
|
|
267
|
+
answers when the live schema lacks intent fields; agent suggestions still need a proposal path.
|
|
268
|
+
|
|
269
|
+
**Claims and plans have separate gates.** A founder assertion or proof point is not an approved,
|
|
270
|
+
evidence-backed claim or consent to quote a customer. Use the existing evidence/claims and
|
|
271
|
+
operating-plan tools only if registered, following their proposal, evidence and confirmation
|
|
272
|
+
requirements. Never encode a spend cap, approver or emergency stop as a learning. If that write
|
|
273
|
+
path is missing, report the specific unsaved item and keep it pending for the supported review
|
|
274
|
+
surface; do not claim it was saved or create a parallel local profile.
|
|
275
|
+
|
|
276
|
+
After every write, **read back before saying saved**: use `brand_read` with `kit`, `learnings`
|
|
277
|
+
or `onboarding` as appropriate, or `brand_onboarding { action: "status", brand_id }` after an
|
|
278
|
+
onboarding answer. Verify the intended field/rule, its source and the absence of a conflicting
|
|
279
|
+
duplicate. A generic success response, pending proposal, ignored key or truncated result is not
|
|
280
|
+
proof. Report partial saves honestly. Carry the verified rules into the current task and the
|
|
281
|
+
routed skill; claims still pass their own safety gate.
|
|
282
|
+
|
|
176
283
|
## Picking source ads — use approved sources, not the retired catalog
|
|
177
284
|
|
|
178
285
|
When the user wants to make ads but has NOT named a specific template (id/slug/Community
|
|
@@ -332,9 +439,10 @@ run through the `gooseworks` CLI (`gooseworks fetch` / `gooseworks call`), like
|
|
|
332
439
|
- **Treat competitor ads as inspiration** — never attest rights, imply ownership, or promise to
|
|
333
440
|
copy a competitor's distinctive expression.
|
|
334
441
|
- **Reconcile brand facts into the kit** — when the user states or changes something brand-level
|
|
335
|
-
mid-task,
|
|
336
|
-
|
|
337
|
-
|
|
442
|
+
mid-task, compare it with `brand_read`. Save explicitly authorized corrections through
|
|
443
|
+
`brand_update` with correction intent and the user's exact statement, or the user's own
|
|
444
|
+
images through `media_upload`; read back before saying saved. Proposed improvements stay
|
|
445
|
+
pending. Ask only when it is unclear whether a one-ad direction should apply to future ads.
|
|
338
446
|
- **Record feedback** — when the user reacts to a generated image, inspect and call
|
|
339
447
|
`set_creative_feedback` so the quality loop learns.
|
|
340
448
|
- **Plan mode is opt-in** — only use the live approval option, then `list_ad_approvals` and
|
|
@@ -26,6 +26,58 @@ Photos studio uses, so the skill and the app can never drift. The point is to **
|
|
|
26
26
|
usable product imagery** — approved photos join the brand kit and can then feed the ad workflow
|
|
27
27
|
(`goose-ads`).
|
|
28
28
|
|
|
29
|
+
## How to talk to the customer (applies to every message you send them)
|
|
30
|
+
|
|
31
|
+
The customer is a marketer or founder, not an engineer. Everything in this skill about tools,
|
|
32
|
+
fields, files, polling, models and pipeline steps is for YOU. Never pass it on to them.
|
|
33
|
+
|
|
34
|
+
- **Say what they get and what they need to decide, never how it's made.**
|
|
35
|
+
Bad: "Checking the media-proxy helpers for voiceover timestamps and lipsync." / "Generated with
|
|
36
|
+
gpt-image-2 at high quality, mouth closed, passes QC."
|
|
37
|
+
Good: "Recording the voices now." / "Here are your two hosts."
|
|
38
|
+
- **Never mention** tool, file or field names, ids, JSON, commands, scripts, installed software,
|
|
39
|
+
uploads, model or vendor names, timings in seconds or frames, retries, polling, internal statuses
|
|
40
|
+
or your own quality checks. Fix what you can quietly. If they ask how something works, explain it
|
|
41
|
+
simply.
|
|
42
|
+
- **Update them only at milestones they care about**: choices confirmed, script and voices done,
|
|
43
|
+
visuals done, ready to review, finished. One short line each, then stay quiet until the next
|
|
44
|
+
milestone. No update for setup, downloads, uploads or saving.
|
|
45
|
+
- **Money is in credits, with a number**: "the full video uses about N credits". Never "the cheap
|
|
46
|
+
pieces" or "the expensive render".
|
|
47
|
+
- **Problems:** say what it means for them and what they can do, in one or two sentences. No error
|
|
48
|
+
codes or stack traces. Only raise a problem that changes something for them.
|
|
49
|
+
- **Use their words**: script, voice, hook, scene, host, ending, logo, the product shot.
|
|
50
|
+
- **No filler**: no "Great question!", "Certainly!", "I hope this helps!", no restating what they
|
|
51
|
+
just said at length.
|
|
52
|
+
|
|
53
|
+
Example. Instead of a dozen lines about render tools, scripts, uploads and portraits, send:
|
|
54
|
+
"Got it: warm tone, home podcast studio, Brielle as the skeptic and Mark as the believer." then
|
|
55
|
+
"Script and voices are done (about 34 seconds)." then "Ready for you to review: the script, voices,
|
|
56
|
+
both hosts and the ending. You can also review your recipe ingredients in the app: <link>. Say go
|
|
57
|
+
here and I'll make the full video (about N credits)."
|
|
58
|
+
|
|
59
|
+
## Use current instructions for new work
|
|
60
|
+
|
|
61
|
+
Before a new task in a terminal host, run `gooseworks skills status` once. It compares the
|
|
62
|
+
installed entry files, running CLI and published npm release. An older CLI needs a package
|
|
63
|
+
upgrade before `gooseworks update`; update alone only uses that CLI's bundled instructions.
|
|
64
|
+
Preserve local edits or unknown install provenance. Review/back up before explicitly replacing
|
|
65
|
+
modified files; never quietly reinstall over them. If the release check is unavailable, report
|
|
66
|
+
that freshness is unknown rather than claiming the local copy is latest.
|
|
67
|
+
|
|
68
|
+
For a new recipe run, fetch its package from the connected catalog. Retain the returned
|
|
69
|
+
`version` / `contentHash` and every dependency's hash with the saved package. When reusing a
|
|
70
|
+
saved fetch JSON, `gooseworks fetch <slug> --saved-package <file>` returns the current package
|
|
71
|
+
and a hash comparison without changing that file. With MCP, if the advertised `catalog_fetch`
|
|
72
|
+
schema accepts them, send `saved_content_hash` and `saved_dependency_hashes`; otherwise fetch
|
|
73
|
+
normally and compare the returned hashes yourself. Missing hashes mean unknown, not current.
|
|
74
|
+
The server cannot inspect a client's saved files; hash metadata does not certify later edits.
|
|
75
|
+
Fetch current packages into a new run directory and report stale saved instructions. Preserve
|
|
76
|
+
an existing approved run's recorded package; changing that harness requires a reviewed change
|
|
77
|
+
and approval before spending. Hosted installed snapshots use the existing Skills Update action.
|
|
78
|
+
Skill content and a host's cached MCP tool schemas are separate: refreshing one does not refresh
|
|
79
|
+
the other. Check the actual advertised tools before using new fields.
|
|
80
|
+
|
|
29
81
|
## Prerequisite — the GooseWorks MCP server is REQUIRED
|
|
30
82
|
|
|
31
83
|
Everything goes through the `mcp__gooseworks__*` tools. If they are not available, **stop and
|
|
@@ -35,7 +87,7 @@ HTTP/file fallback.
|
|
|
35
87
|
## Start from the brand context — don't re-ask what it already answers
|
|
36
88
|
|
|
37
89
|
If the `gooseworks` router handed you brand context, USE IT. If you were invoked directly, call
|
|
38
|
-
`
|
|
90
|
+
`brand_read` yourself first. It answers most of the setup questions below, so **do not ask
|
|
39
91
|
the user for them**:
|
|
40
92
|
|
|
41
93
|
- **Which product?** — the context's `products[]` are the real catalog entries. Offer them; never
|
|
@@ -99,7 +151,7 @@ whether a human model is wanted (which needs explicit consent — see the rules)
|
|
|
99
151
|
|
|
100
152
|
## Workflow — shoot a product
|
|
101
153
|
|
|
102
|
-
1. **Load the brand context** (`
|
|
154
|
+
1. **Load the brand context** (`brand_read`, or reuse what the router passed you) and
|
|
103
155
|
**resolve the brand + product.** `list_ad_brands` → `brand_id`. `list_brand_products` → pick a
|
|
104
156
|
`product_id` from the catalog you already know about. If the product genuinely isn't there,
|
|
105
157
|
`import_product` (poll `get_product_import`).
|
|
@@ -117,7 +169,7 @@ whether a human model is wanted (which needs explicit consent — see the rules)
|
|
|
117
169
|
- **Never invent product facts.** The backend grounds the shot on the product's real images; don't
|
|
118
170
|
describe a product you can't see.
|
|
119
171
|
- **Use the brand context instead of interviewing the user.** Product, audience, voice, positioning,
|
|
120
|
-
logo/colors/fonts all come from `
|
|
172
|
+
logo/colors/fonts all come from `brand_read` / the brand kit. Ask only for the shot
|
|
121
173
|
category, count, quality, and model consent.
|
|
122
174
|
- **Ask before spending.** Quote the estimate and confirm `count` / `quality` before
|
|
123
175
|
`generate_product_photos` — it reserves credits.
|