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.
Files changed (84) hide show
  1. package/README.md +42 -11
  2. package/dist/commands/doctor.d.ts +29 -5
  3. package/dist/commands/doctor.d.ts.map +1 -1
  4. package/dist/commands/doctor.js +107 -63
  5. package/dist/commands/doctor.js.map +1 -1
  6. package/dist/commands/fetch.d.ts +1 -0
  7. package/dist/commands/fetch.d.ts.map +1 -1
  8. package/dist/commands/fetch.js +64 -29
  9. package/dist/commands/fetch.js.map +1 -1
  10. package/dist/commands/install.d.ts.map +1 -1
  11. package/dist/commands/install.js +37 -4
  12. package/dist/commands/install.js.map +1 -1
  13. package/dist/commands/login.d.ts.map +1 -1
  14. package/dist/commands/login.js +8 -3
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/skills.d.ts +34 -0
  17. package/dist/commands/skills.d.ts.map +1 -0
  18. package/dist/commands/skills.js +109 -0
  19. package/dist/commands/skills.js.map +1 -0
  20. package/dist/commands/update.d.ts.map +1 -1
  21. package/dist/commands/update.js +17 -6
  22. package/dist/commands/update.js.map +1 -1
  23. package/dist/commands/video-local.d.ts +9 -0
  24. package/dist/commands/video-local.d.ts.map +1 -0
  25. package/dist/commands/video-local.js +83 -0
  26. package/dist/commands/video-local.js.map +1 -0
  27. package/dist/commands/video-save.d.ts +3 -0
  28. package/dist/commands/video-save.d.ts.map +1 -0
  29. package/dist/commands/video-save.js +83 -0
  30. package/dist/commands/video-save.js.map +1 -0
  31. package/dist/index.js +7 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/lib/video-local-captions.d.ts +83 -0
  34. package/dist/lib/video-local-captions.d.ts.map +1 -0
  35. package/dist/lib/video-local-captions.js +261 -0
  36. package/dist/lib/video-local-captions.js.map +1 -0
  37. package/dist/lib/video-local-runtime.d.ts +8 -0
  38. package/dist/lib/video-local-runtime.d.ts.map +1 -0
  39. package/dist/lib/video-local-runtime.js +201 -0
  40. package/dist/lib/video-local-runtime.js.map +1 -0
  41. package/dist/lib/video-save-mcp.d.ts +9 -0
  42. package/dist/lib/video-save-mcp.d.ts.map +1 -0
  43. package/dist/lib/video-save-mcp.js +162 -0
  44. package/dist/lib/video-save-mcp.js.map +1 -0
  45. package/dist/lib/video-save.d.ts +77 -0
  46. package/dist/lib/video-save.d.ts.map +1 -0
  47. package/dist/lib/video-save.js +440 -0
  48. package/dist/lib/video-save.js.map +1 -0
  49. package/dist/skills/custom-video-skill.d.ts +3 -0
  50. package/dist/skills/custom-video-skill.d.ts.map +1 -0
  51. package/dist/skills/custom-video-skill.js +6 -0
  52. package/dist/skills/custom-video-skill.js.map +1 -0
  53. package/dist/skills/installer.d.ts +12 -6
  54. package/dist/skills/installer.d.ts.map +1 -1
  55. package/dist/skills/installer.js +41 -15
  56. package/dist/skills/installer.js.map +1 -1
  57. package/dist/skills/master-skill.d.ts +22 -19
  58. package/dist/skills/master-skill.d.ts.map +1 -1
  59. package/dist/skills/master-skill.js +1472 -551
  60. package/dist/skills/master-skill.js.map +1 -1
  61. package/dist/skills/package-freshness.d.ts +16 -0
  62. package/dist/skills/package-freshness.d.ts.map +1 -0
  63. package/dist/skills/package-freshness.js +36 -0
  64. package/dist/skills/package-freshness.js.map +1 -0
  65. package/dist/skills/releases.d.ts +12 -0
  66. package/dist/skills/releases.d.ts.map +1 -0
  67. package/dist/skills/releases.js +152 -0
  68. package/dist/skills/releases.js.map +1 -0
  69. package/dist/skills/routes.d.ts.map +1 -1
  70. package/dist/skills/routes.js +7 -1
  71. package/dist/skills/routes.js.map +1 -1
  72. package/dist/utils/browser-preflight.d.ts +29 -0
  73. package/dist/utils/browser-preflight.d.ts.map +1 -0
  74. package/dist/utils/browser-preflight.js +338 -0
  75. package/dist/utils/browser-preflight.js.map +1 -0
  76. package/package.json +2 -2
  77. package/skills/goose-ads/SKILL.md +124 -16
  78. package/skills/goose-product-photos/SKILL.md +55 -3
  79. package/skills/goose-video/SKILL.md +322 -253
  80. package/skills/goose-video-local/SKILL.md +1199 -234
  81. package/skills/gooseworks/SKILL.md +199 -13
  82. package/skills/make-custom-video/SKILL.md +490 -0
  83. package/skills/manifest.json +12 -0
  84. 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.2",
4
- "description": "GooseWorks CLI — give your coding agent real data tools",
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
- `brand_get_context` first (falling back to `get_brand_kit` for the selected brand). It already
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 update (ASK first)
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 `get_brand_kit` for the active brand and see whether what the user said
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 — ASK before writing.** Confirm in one line: *"Want me to update
162
- the brand kit so this sticks for future ads?"* Only persist on a yes (or when the user clearly
163
- asked you to change the brand). Don't silently mutate the kit, and don't nag on trivia.
164
- 4. **Persist with the write tools** (partial — only the fields you pass are touched; each edit is
165
- recorded as a user override that later re-research won't clobber):
166
- - `update_brand_kit` — structured brand fields.
167
- - `upsert_brand_product` / `delete_brand_product` — products.
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. **Confirm what changed** and continue the task. (Logo, colors, and fonts are owned by the
171
- backend research pass — prefer `update_ad_brand` / the research flow for those, not free text.)
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, check it against `get_brand_kit` and, with their ok, persist it via `update_brand_kit`
336
- / `upsert_brand_product` / `add_brand_product_image` so it sticks for future ads. Ask first;
337
- never silently mutate the kit.
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
- `brand_get_context` yourself first. It answers most of the setup questions below, so **do not ask
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** (`brand_get_context`, or reuse what the router passed you) and
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 `brand_get_context` / the brand kit. Ask only for the shot
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.