@cliangdev/conductor 0.20.6 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/assets/claude/skills/conductor-creative/SKILL.md +144 -0
  2. package/assets/claude/skills/conductor-publisher/SKILL.md +4 -0
  3. package/assets/cli-manifest.json +100 -0
  4. package/dist/commands/creative.d.ts +3 -0
  5. package/dist/commands/creative.d.ts.map +1 -0
  6. package/dist/commands/creative.js +60 -0
  7. package/dist/commands/creative.js.map +1 -0
  8. package/dist/creative/assertions.js +221 -0
  9. package/dist/creative/font.js +39 -0
  10. package/dist/creative/frame.css +126 -0
  11. package/dist/creative/frame.html +29 -0
  12. package/dist/creative/frame.js +76 -0
  13. package/dist/creative/job/render.mjs +203 -0
  14. package/dist/creative/job/server.mjs +72 -0
  15. package/dist/creative/job/transport.mjs +167 -0
  16. package/dist/creative/layouts/bleed/layout.css +47 -0
  17. package/dist/creative/layouts/bleed/layout.json +17 -0
  18. package/dist/creative/layouts/card/layout.css +24 -0
  19. package/dist/creative/layouts/card/layout.json +17 -0
  20. package/dist/creative/layouts/index.js +62 -0
  21. package/dist/creative/layouts/index.json +1 -0
  22. package/dist/creative/layouts/split/layout.css +22 -0
  23. package/dist/creative/layouts/split/layout.json +17 -0
  24. package/dist/creative/layouts/stacked/layout.css +40 -0
  25. package/dist/creative/layouts/stacked/layout.json +23 -0
  26. package/dist/creative/placements.js +200 -0
  27. package/dist/creative/placements.json +232 -0
  28. package/dist/creative/render.js +457 -0
  29. package/dist/creative/sheet.html +27 -0
  30. package/dist/creative/sheet.js +94 -0
  31. package/dist/creative/styles.css +314 -0
  32. package/dist/creative/tokens.css +33 -0
  33. package/dist/index.js +2 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/creative-browser.d.ts +25 -0
  36. package/dist/lib/creative-browser.d.ts.map +1 -0
  37. package/dist/lib/creative-browser.js +36 -0
  38. package/dist/lib/creative-browser.js.map +1 -0
  39. package/dist/lib/creative-id.d.ts +8 -0
  40. package/dist/lib/creative-id.d.ts.map +1 -0
  41. package/dist/lib/creative-id.js +14 -0
  42. package/dist/lib/creative-id.js.map +1 -0
  43. package/dist/lib/creative-render.d.ts +32 -0
  44. package/dist/lib/creative-render.d.ts.map +1 -0
  45. package/dist/lib/creative-render.js +71 -0
  46. package/dist/lib/creative-render.js.map +1 -0
  47. package/dist/lib/image-dimensions.d.ts +20 -0
  48. package/dist/lib/image-dimensions.d.ts.map +1 -0
  49. package/dist/lib/image-dimensions.js +115 -0
  50. package/dist/lib/image-dimensions.js.map +1 -0
  51. package/dist/lib/plugin-assets.d.ts.map +1 -1
  52. package/dist/lib/plugin-assets.js +1 -0
  53. package/dist/lib/plugin-assets.js.map +1 -1
  54. package/dist/mcp/index.d.ts +18 -0
  55. package/dist/mcp/index.d.ts.map +1 -1
  56. package/dist/mcp/index.js +301 -0
  57. package/dist/mcp/index.js.map +1 -1
  58. package/dist/mcp/tools/creatives.d.ts +93 -0
  59. package/dist/mcp/tools/creatives.d.ts.map +1 -0
  60. package/dist/mcp/tools/creatives.js +291 -0
  61. package/dist/mcp/tools/creatives.js.map +1 -0
  62. package/dist/mcp/tools/insights.d.ts +11 -0
  63. package/dist/mcp/tools/insights.d.ts.map +1 -0
  64. package/dist/mcp/tools/insights.js +85 -0
  65. package/dist/mcp/tools/insights.js.map +1 -0
  66. package/package.json +4 -3
  67. package/scripts/build-creative.mjs +93 -0
@@ -0,0 +1,93 @@
1
+ import { Config } from '../config.js';
2
+ export declare function getBrandKit(params: {
3
+ kitId?: string;
4
+ }, config: Config): Promise<Record<string, unknown>>;
5
+ export declare function listCreatives(params: {
6
+ state?: string;
7
+ brandKitId?: string;
8
+ }, config: Config): Promise<Array<Record<string, unknown>>>;
9
+ export declare function getCreative(params: {
10
+ creativeId: string;
11
+ }, config: Config): Promise<Record<string, unknown>>;
12
+ /**
13
+ * Starts a hook experiment on a Creative family (>= 2 lettered variants; one RUNNING experiment per
14
+ * family at a time — a second attempt is refused with a 409). It settles on its own once every variant
15
+ * has published and reported (or via the weekly "what's working" job) — nothing further to call. Read
16
+ * the result through get_creative, whose experiment field carries the current state, and once DECIDED,
17
+ * the winner and its numbers.
18
+ */
19
+ export declare function createExperiment(params: {
20
+ creativeId: string;
21
+ metric?: string;
22
+ windowHours?: number;
23
+ }, config: Config): Promise<Record<string, unknown>>;
24
+ export interface CreativeLayoutOverrides {
25
+ band?: Record<string, number>;
26
+ padBottom?: Record<string, number>;
27
+ }
28
+ export interface CreativeFields {
29
+ brandKitId?: string;
30
+ name?: string;
31
+ state?: string;
32
+ layout?: string;
33
+ theme?: string;
34
+ photoId?: string;
35
+ focalOverride?: Record<string, string>;
36
+ headline?: string;
37
+ body?: string;
38
+ caption?: string;
39
+ altText?: string;
40
+ placements?: string[];
41
+ sequenceKind?: string;
42
+ sequence?: Array<Record<string, unknown>>;
43
+ carouselRatio?: string;
44
+ /** 'plain' (default) or 'chip' — puts the logo lockup on a white pill, for busy photography. */
45
+ lockup?: string;
46
+ /** Per-placement overrides of layout-derived numbers, in pixels — see conductor-creative/README.md. */
47
+ layoutOverrides?: CreativeLayoutOverrides;
48
+ }
49
+ export interface CreateCreativeParams extends CreativeFields {
50
+ /** Cuts a lettered variant of this existing Creative instead of creating a fresh one — everything
51
+ * else on this call except `headline` and `name` is ignored, matching the variants endpoint. */
52
+ variantOf?: string;
53
+ }
54
+ export declare function createCreative(params: CreateCreativeParams, config: Config): Promise<Record<string, unknown>>;
55
+ export interface UpdateCreativeParams extends CreativeFields {
56
+ creativeId: string;
57
+ version: number;
58
+ }
59
+ export declare function updateCreative(params: UpdateCreativeParams, config: Config): Promise<Record<string, unknown>>;
60
+ export declare function uploadCreativePhoto(params: {
61
+ filePath?: string;
62
+ url?: string;
63
+ label?: string;
64
+ source?: string;
65
+ licence?: string;
66
+ aiGenerated?: boolean;
67
+ }, config: Config): Promise<Record<string, unknown>>;
68
+ export declare function renderCreativeTool(params: {
69
+ creativeId: string;
70
+ previewOnly?: boolean;
71
+ renderer?: string;
72
+ workflowRunId?: string;
73
+ }, config: Config): Promise<Record<string, unknown>>;
74
+ export interface PreviewCreativeResult {
75
+ renderId?: string;
76
+ state?: string;
77
+ url?: string;
78
+ image?: {
79
+ data: Buffer;
80
+ mimeType: string;
81
+ };
82
+ note?: string;
83
+ }
84
+ export declare function previewCreative(params: {
85
+ creativeId: string;
86
+ }, config: Config): Promise<PreviewCreativeResult>;
87
+ export declare function attachCreativeToPost(params: {
88
+ creativeId: string;
89
+ postId?: string;
90
+ workItemId?: string;
91
+ renderId?: string;
92
+ }, config: Config): Promise<Record<string, unknown>>;
93
+ //# sourceMappingURL=creatives.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creatives.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/creatives.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,291 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { basename } from 'node:path';
3
+ import { apiGet, apiPost, apiPatch, putBytes, ApiError } from '../api.js';
4
+ import { readImageDimensions } from '../../lib/image-dimensions.js';
5
+ import { renderCreative as runLocalRender } from '../../lib/creative-render.js';
6
+ /**
7
+ * Conductor Creatives: a Brand Kit (tokens, copy rules, approved lines) governs one or more
8
+ * Creatives (photo + headline + layout, optionally a story/carousel sequence and lettered
9
+ * variants), which render locally (Playwright, driven by this MCP server) to upload-ready JPEGs
10
+ * that attach to a Post like any other media. See docs/mcp-tool-guidelines.md's action-verify and
11
+ * dispatch-status patterns, both used below.
12
+ */
13
+ const V2_PROJECT = (config) => `/api/v2/projects/${config.projectId}`;
14
+ const creativesBase = (config) => `${V2_PROJECT(config)}/marketing/creatives`;
15
+ const experimentsBase = (config) => `${V2_PROJECT(config)}/marketing/experiments`;
16
+ /** Rounds a rate-like number to 4 decimals; leaves null/undefined alone. */
17
+ function round4(n) {
18
+ if (typeof n !== 'number')
19
+ return n;
20
+ return Math.round(n * 10000) / 10000;
21
+ }
22
+ /** The fields worth an agent's context for one variant's performance — a subset of CreativePerformanceEntry. */
23
+ function trimPerformanceEntry(entry) {
24
+ return {
25
+ creativeId: entry['creativeId'],
26
+ label: entry['label'],
27
+ headline: entry['headline'],
28
+ posts: entry['posts'],
29
+ views: entry['views'],
30
+ engagementRate: round4(entry['engagementRate']),
31
+ avgViewPct: round4(entry['avgViewPct']),
32
+ views72h: entry['views72h'],
33
+ };
34
+ }
35
+ /** The fields worth an agent's context for the family's active/last experiment. */
36
+ function trimExperiment(experiment) {
37
+ return {
38
+ id: experiment['id'],
39
+ state: experiment['state'],
40
+ metric: experiment['metric'],
41
+ windowHours: experiment['windowHours'],
42
+ winnerCreativeId: experiment['winnerCreativeId'],
43
+ decidedAt: experiment['decidedAt'],
44
+ summary: experiment['summary'],
45
+ winnerLineConfirmedAt: experiment['winnerLineConfirmedAt'],
46
+ };
47
+ }
48
+ /**
49
+ * The backend's 422 body for a Creative validation failure is `{ message, violations: [{ field,
50
+ * ruleId, message }] }` — not the RFC 7807 `detail`/`title` shape `ApiError` otherwise expects, so
51
+ * its `message` ends up holding the raw JSON text. This re-parses that into one readable string
52
+ * (`[field] message`, one per violation) so the model gets something it can act on directly rather
53
+ * than a JSON string to parse itself. A 409 (stale `version`) already carries a plain-English
54
+ * `detail` and passes through unchanged.
55
+ */
56
+ async function withCreativeErrors(fn) {
57
+ try {
58
+ return await fn();
59
+ }
60
+ catch (err) {
61
+ if (err instanceof ApiError && (err.status === 422 || err.status === 409)) {
62
+ let violations;
63
+ try {
64
+ const parsed = JSON.parse(err.message);
65
+ if (Array.isArray(parsed.violations))
66
+ violations = parsed.violations;
67
+ }
68
+ catch {
69
+ // Not JSON (e.g. a plain 409 conflict message) — use it as-is below.
70
+ }
71
+ const message = violations
72
+ ? violations.map((v) => `[${v.field ?? v.ruleId ?? '?'}] ${v.message}`).join('; ')
73
+ : err.message;
74
+ throw new ApiError(err.status, message, { code: err.code, title: err.title });
75
+ }
76
+ throw err;
77
+ }
78
+ }
79
+ // --- Brand Kit ------------------------------------------------------------
80
+ export async function getBrandKit(params, config) {
81
+ if (params.kitId) {
82
+ return apiGet(`${V2_PROJECT(config)}/marketing/brand-kits/${params.kitId}`, config);
83
+ }
84
+ const kits = await apiGet(`${V2_PROJECT(config)}/marketing/brand-kits`, config);
85
+ const found = kits.find((k) => k['isDefault']) ?? kits[0];
86
+ if (!found) {
87
+ throw new Error('This project has no Brand Kit yet — one is created for every workspace by default; check Settings.');
88
+ }
89
+ return found;
90
+ }
91
+ // --- Creatives --------------------------------------------------------------
92
+ export async function listCreatives(params, config) {
93
+ const query = new URLSearchParams();
94
+ if (params.state)
95
+ query.set('state', params.state);
96
+ if (params.brandKitId)
97
+ query.set('brandKitId', params.brandKitId);
98
+ const qs = query.toString();
99
+ return apiGet(`${creativesBase(config)}${qs ? `?${qs}` : ''}`, config);
100
+ }
101
+ export async function getCreative(params, config) {
102
+ const [creative, readiness, all, performance] = await Promise.all([
103
+ apiGet(`${creativesBase(config)}/${params.creativeId}`, config),
104
+ apiGet(`${creativesBase(config)}/${params.creativeId}/readiness`, config),
105
+ apiGet(creativesBase(config), config),
106
+ apiGet(`${creativesBase(config)}/${params.creativeId}/performance`, config),
107
+ ]);
108
+ const variants = all
109
+ .filter((c) => c['number'] === creative['number'] && c['id'] !== creative['id'])
110
+ .map((c) => ({ id: c['id'], displayId: c['displayId'], state: c['state'], headline: c['headline'] }));
111
+ const activeExperimentId = creative['activeExperimentId'];
112
+ let experiment;
113
+ if (activeExperimentId) {
114
+ experiment = await apiGet(`${experimentsBase(config)}/${activeExperimentId}`, config);
115
+ }
116
+ else {
117
+ const experiments = await apiGet(`${experimentsBase(config)}?creativeId=${params.creativeId}`, config);
118
+ experiment = experiments[0];
119
+ }
120
+ const family = performance['family'] ?? [];
121
+ return {
122
+ ...creative,
123
+ readiness,
124
+ variants,
125
+ performance: family.map(trimPerformanceEntry),
126
+ experiment: experiment ? trimExperiment(experiment) : null,
127
+ };
128
+ }
129
+ /**
130
+ * Starts a hook experiment on a Creative family (>= 2 lettered variants; one RUNNING experiment per
131
+ * family at a time — a second attempt is refused with a 409). It settles on its own once every variant
132
+ * has published and reported (or via the weekly "what's working" job) — nothing further to call. Read
133
+ * the result through get_creative, whose experiment field carries the current state, and once DECIDED,
134
+ * the winner and its numbers.
135
+ */
136
+ export async function createExperiment(params, config) {
137
+ return withCreativeErrors(() => apiPost(experimentsBase(config), { creativeId: params.creativeId, metric: params.metric, windowHours: params.windowHours }, config));
138
+ }
139
+ export async function createCreative(params, config) {
140
+ return withCreativeErrors(async () => {
141
+ if (params.variantOf) {
142
+ return apiPost(`${creativesBase(config)}/${params.variantOf}/variants`, { headline: params.headline, name: params.name }, config);
143
+ }
144
+ return apiPost(creativesBase(config), { ...params, variantOf: undefined }, config);
145
+ });
146
+ }
147
+ export async function updateCreative(params, config) {
148
+ const { creativeId, ...patch } = params;
149
+ return withCreativeErrors(() => apiPatch(`${creativesBase(config)}/${creativeId}`, patch, config));
150
+ }
151
+ // --- Photos -----------------------------------------------------------------
152
+ async function readPhotoSource(params) {
153
+ if (params.filePath) {
154
+ return { bytes: new Uint8Array(await readFile(params.filePath)), filename: basename(params.filePath) };
155
+ }
156
+ const raw = params.url;
157
+ let parsed;
158
+ try {
159
+ parsed = new URL(raw);
160
+ }
161
+ catch {
162
+ throw new Error(`"${raw}" is not a valid URL`);
163
+ }
164
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
165
+ throw new Error(`Only http(s) URLs can be fetched, not ${parsed.protocol}`);
166
+ }
167
+ const response = await fetch(parsed);
168
+ if (!response.ok) {
169
+ throw new Error(`Fetching ${raw} failed: HTTP ${response.status}`);
170
+ }
171
+ const bytes = new Uint8Array(await response.arrayBuffer());
172
+ return { bytes, filename: basename(parsed.pathname) || 'photo' };
173
+ }
174
+ export async function uploadCreativePhoto(params, config) {
175
+ if (!params.filePath && !params.url) {
176
+ throw new Error('Pass filePath (a file on this machine) or url (a public http(s) URL).');
177
+ }
178
+ const { bytes, filename } = await readPhotoSource(params);
179
+ const info = readImageDimensions(bytes);
180
+ if (!info) {
181
+ throw new Error(`Could not read "${filename}" as a PNG, JPEG or WebP — Creative photos accept only those three formats.`);
182
+ }
183
+ const mint = await apiPost(`${V2_PROJECT(config)}/marketing/photos`, {
184
+ label: params.label ?? filename,
185
+ contentType: info.contentType,
186
+ sizeBytes: bytes.byteLength,
187
+ width: info.width,
188
+ height: info.height,
189
+ source: params.source ?? null,
190
+ licence: params.licence ?? null,
191
+ aiGenerated: !!params.aiGenerated,
192
+ }, config);
193
+ if (!mint.uploadUrl) {
194
+ throw new Error('The mint response carried no uploadUrl — cannot upload bytes.');
195
+ }
196
+ await putBytes(mint.uploadUrl, info.contentType, bytes);
197
+ return apiPost(`${V2_PROJECT(config)}/marketing/photos/${mint.id}/confirm`, { sizeBytes: bytes.byteLength }, config);
198
+ }
199
+ export async function renderCreativeTool(params, config) {
200
+ const result = await runLocalRender({ creativeId: params.creativeId, previewOnly: !!params.previewOnly, renderer: params.renderer ?? 'mcp', workflowRunId: params.workflowRunId }, config);
201
+ return {
202
+ renderId: result.renderId,
203
+ state: result.state,
204
+ ok: result.ok,
205
+ error: result.error,
206
+ frames: result.frames,
207
+ nextStep: result.ok
208
+ ? 'Call preview_creative to look at the render, or get_creative for the stored frame list.'
209
+ : 'Fix what `error` names and call render_creative again.',
210
+ };
211
+ }
212
+ const MAX_INLINE_IMAGE_BYTES = 1000000; // ~1MB — no downscale available without an image library
213
+ // Only a sheet rendered from the creative as it is now is worth showing: an older one would show the model
214
+ // an ad it has since edited. One too large to inline is skipped too, so a fresh (small) one is rendered.
215
+ function findCurrentSheetFrame(renders, version) {
216
+ for (const render of renders) {
217
+ if (render.state !== 'SUCCEEDED')
218
+ continue;
219
+ if (version !== undefined && render.creativeVersion !== version)
220
+ continue;
221
+ const frame = (render.frames ?? []).find((f) => f.placementKey === 'sheet');
222
+ if (frame && (frame.sizeBytes ?? 0) <= MAX_INLINE_IMAGE_BYTES)
223
+ return { render, frame };
224
+ }
225
+ return undefined;
226
+ }
227
+ export async function previewCreative(params, config) {
228
+ const [creative, renders] = await Promise.all([
229
+ apiGet(`${creativesBase(config)}/${params.creativeId}`, config),
230
+ apiGet(`${creativesBase(config)}/${params.creativeId}/renders`, config),
231
+ ]);
232
+ let found = findCurrentSheetFrame(renders, creative?.version);
233
+ if (!found) {
234
+ const rendered = await runLocalRender({ creativeId: params.creativeId, previewOnly: true, renderer: 'mcp' }, config);
235
+ if (!rendered.ok || !rendered.renderId) {
236
+ return {
237
+ renderId: rendered.renderId,
238
+ state: rendered.state,
239
+ note: `Rendering a preview failed${rendered.error ? `: ${rendered.error}` : '.'} There is nothing to show.`,
240
+ };
241
+ }
242
+ const sheetFrame = rendered.frames.find((f) => f.placementKey === 'sheet');
243
+ if (!sheetFrame) {
244
+ return { renderId: rendered.renderId, state: rendered.state, note: 'The preview render produced no contact sheet frame.' };
245
+ }
246
+ found = { render: { id: rendered.renderId, state: rendered.state ?? 'SUCCEEDED' }, frame: sheetFrame };
247
+ }
248
+ const { render, frame } = found;
249
+ if (!frame.url) {
250
+ return { renderId: render.id, state: render.state, note: 'The contact sheet frame has no download URL yet.' };
251
+ }
252
+ if ((frame.sizeBytes ?? 0) > MAX_INLINE_IMAGE_BYTES) {
253
+ return {
254
+ renderId: render.id,
255
+ state: render.state,
256
+ url: frame.url,
257
+ note: `The rendered image is ${Math.round((frame.sizeBytes ?? 0) / 1024)} KB, larger than this tool can inline (~1 MB) — open the URL directly instead.`,
258
+ };
259
+ }
260
+ const response = await fetch(frame.url);
261
+ if (!response.ok) {
262
+ return { renderId: render.id, state: render.state, url: frame.url, note: `Could not download the frame (HTTP ${response.status}) — open the URL directly instead.` };
263
+ }
264
+ const bytes = new Uint8Array(await response.arrayBuffer());
265
+ if (bytes.byteLength > MAX_INLINE_IMAGE_BYTES) {
266
+ return { renderId: render.id, state: render.state, url: frame.url, note: 'The rendered image is larger than this tool can inline — open the URL directly instead.' };
267
+ }
268
+ // The `sheet` contact sheet is a 1x JPEG; the mime type comes off the response rather than being
269
+ // hardcoded so an older PNG sheet is never mislabelled (see docs/creatives.md).
270
+ const mimeType = (response.headers.get('content-type') || 'image/jpeg').split(';')[0].trim();
271
+ return { renderId: render.id, state: render.state, url: frame.url, image: { data: Buffer.from(bytes), mimeType } };
272
+ }
273
+ export async function attachCreativeToPost(params, config) {
274
+ // `postId` is the primary name (it matches get_post_status's own param); `workItemId` is kept as a
275
+ // deprecated alias so existing callers don't break.
276
+ const postId = params.postId ?? params.workItemId;
277
+ if (!postId) {
278
+ throw new Error('postId is required (the Post\'s Work Item id).');
279
+ }
280
+ let renderId = params.renderId;
281
+ if (!renderId) {
282
+ const renders = await apiGet(`${creativesBase(config)}/${params.creativeId}/renders`, config);
283
+ const latest = renders.find((r) => r.state === 'SUCCEEDED' && !r.previewOnly);
284
+ if (!latest) {
285
+ throw new Error('No SUCCEEDED render exists for this Creative yet — call render_creative (previewOnly: false) first, or pass an explicit renderId.');
286
+ }
287
+ renderId = latest.id;
288
+ }
289
+ return apiPost(`${creativesBase(config)}/${params.creativeId}/attach`, { renderId, workItemId: postId }, config);
290
+ }
291
+ //# sourceMappingURL=creatives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creatives.js","sourceRoot":"","sources":["../../../src/mcp/tools/creatives.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Config } from '../config.js';
2
+ /**
3
+ * What is working across the project's published Posts, trimmed to what an agent needs to reason from:
4
+ * totals and per-platform/per-format engagement, the best hours and weekdays to post, the best and worst
5
+ * destinations, and movers vs. the prior window of the same length.
6
+ */
7
+ export declare function getMarketingInsights(config: Config, params: {
8
+ window?: '7d' | '30d' | '90d';
9
+ platform?: string;
10
+ }): Promise<Record<string, unknown>>;
11
+ //# sourceMappingURL=insights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/insights.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AA6JrC;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAyBlC"}
@@ -0,0 +1,85 @@
1
+ import { apiGet } from '../api.js';
2
+ /** Rounds a rate-like number to 4 decimals; leaves null/undefined alone. */
3
+ function round4(n) {
4
+ if (n === null || n === undefined)
5
+ return n;
6
+ return Math.round(n * 10000) / 10000;
7
+ }
8
+ /** Drops null/undefined fields from an object, keeping the key order of what remains. */
9
+ function omitNulls(obj) {
10
+ const out = {};
11
+ for (const [key, value] of Object.entries(obj)) {
12
+ if (value !== null && value !== undefined)
13
+ out[key] = value;
14
+ }
15
+ return out;
16
+ }
17
+ function trimGroup(group) {
18
+ return omitNulls({
19
+ ...group,
20
+ engagementRate: round4(group.engagementRate),
21
+ avgViewPct: round4(group.avgViewPct),
22
+ });
23
+ }
24
+ /** The fields worth an agent's context for one destination — a subset of the backend's InsightsPost. */
25
+ function trimPost(post) {
26
+ return omitNulls({
27
+ displayId: post.displayId,
28
+ title: post.title,
29
+ platform: post.platform,
30
+ format: post.format,
31
+ captionExcerpt: post.captionExcerpt,
32
+ views: post.views,
33
+ engagementRate: round4(post.engagementRate),
34
+ permalink: post.permalink,
35
+ });
36
+ }
37
+ /** Groups with at least two posts and a computed engagement rate, best rate first, capped at three. */
38
+ function topGroups(groups) {
39
+ return groups
40
+ .filter((g) => g.posts >= 2 && g.engagementRate !== null && g.engagementRate !== undefined)
41
+ .sort((a, b) => b.engagementRate - a.engagementRate)
42
+ .slice(0, 3)
43
+ .map(trimGroup);
44
+ }
45
+ /** The fields worth an agent's context for one Creative variant's attributed performance. */
46
+ function trimCreative(entry) {
47
+ return omitNulls({
48
+ creativeId: entry.creativeId,
49
+ label: entry.label,
50
+ headline: entry.headline,
51
+ posts: entry.posts,
52
+ views: entry.views,
53
+ engagementRate: round4(entry.engagementRate),
54
+ avgViewPct: round4(entry.avgViewPct),
55
+ views72h: entry.views72h,
56
+ });
57
+ }
58
+ /**
59
+ * What is working across the project's published Posts, trimmed to what an agent needs to reason from:
60
+ * totals and per-platform/per-format engagement, the best hours and weekdays to post, the best and worst
61
+ * destinations, and movers vs. the prior window of the same length.
62
+ */
63
+ export async function getMarketingInsights(config, params) {
64
+ const query = new URLSearchParams();
65
+ if (params.window)
66
+ query.set('window', params.window);
67
+ if (params.platform)
68
+ query.set('platform', params.platform);
69
+ const qs = query.toString();
70
+ const data = await apiGet(`/api/v2/projects/${config.projectId}/marketing/insights${qs ? `?${qs}` : ''}`, config);
71
+ return {
72
+ window: data.window,
73
+ totals: trimGroup(data.totals),
74
+ byPlatform: data.byPlatform.map(trimGroup),
75
+ byFormat: data.byFormat.map(trimGroup),
76
+ bestHoursUtc: topGroups(data.byHour),
77
+ bestWeekdays: topGroups(data.byWeekday),
78
+ topPosts: data.topPosts.slice(0, 5).map(trimPost),
79
+ bottomPosts: data.bottomPosts.slice(0, 3).map(trimPost),
80
+ movers: data.movers.map((m) => omitNulls({ metric: m.metric, current: round4(m.current), previous: round4(m.previous), deltaPct: round4(m.deltaPct) })),
81
+ coverage: data.coverage,
82
+ topCreatives: (data.creatives ?? []).slice(0, 5).map(trimCreative),
83
+ };
84
+ }
85
+ //# sourceMappingURL=insights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.js","sourceRoot":"","sources":["../../../src/mcp/tools/insights.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliangdev/conductor",
3
- "version": "0.20.6",
3
+ "version": "0.22.0",
4
4
  "description": "Agentic software development platform — AI-driven PRD, implementation, and beyond",
5
5
  "license": "SEE LICENSE IN ../LICENSE",
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  "scripts/"
24
24
  ],
25
25
  "scripts": {
26
- "build": "tsc",
26
+ "build": "tsc && node scripts/build-creative.mjs",
27
27
  "typecheck:fast": "tsgo --noEmit",
28
28
  "dev": "ts-node src/index.ts",
29
29
  "test": "vitest run",
@@ -36,7 +36,8 @@
36
36
  "chokidar": "^5.0.0",
37
37
  "commander": "^15.0.0",
38
38
  "open": "^11.0.0",
39
- "ora": "^9.4.0"
39
+ "ora": "^9.4.0",
40
+ "playwright-core": "1.63.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/node": "^26.0.0",
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /* build-creative.mjs — copies the runtime files of the sibling
3
+ * `conductor-creative/` package into this package's `dist/creative/`, run by
4
+ * `npm run build` alongside `tsc`.
5
+ *
6
+ * Why a copy rather than a dependency: conductor-tools is published to npm
7
+ * (`@cliangdev/conductor`), so it cannot `file:`-depend on a sibling
8
+ * directory that only exists in this monorepo checkout at runtime — the
9
+ * published package has to carry what it needs itself. `conductor-creative/`
10
+ * has no build step of its own (plain ESM, no bundler), so "build" here just
11
+ * means "select the files the render job actually needs and lay them out at
12
+ * the same relative paths dist/creative/job/render.mjs's own
13
+ * `PACKAGE_ROOT = join(HERE, '..')` expects".
14
+ *
15
+ * Deliberately excluded: `mount.js`/`copy-rules.js` (editor-only, not
16
+ * imported by frame.js/sheet.js — the render core never reaches for them),
17
+ * `test/`, `node_modules/`, `job/package.json` (its `playwright` dependency
18
+ * is for running the job directly out of that repo; conductor-tools drives
19
+ * the same `run()` with its own `playwright-core`-based browser discovery —
20
+ * see src/lib/creative-browser.ts), and `README.md`.
21
+ *
22
+ * Kept in one explicit list (rather than "copy everything except...") so a
23
+ * new file added to conductor-creative/ has to be a deliberate decision here
24
+ * too, and `src/__tests__/build-creative.test.ts` asserts every listed file
25
+ * actually lands in dist/creative/ after this runs.
26
+ */
27
+ import { existsSync, mkdirSync, copyFileSync } from 'node:fs'
28
+ import { dirname, join } from 'node:path'
29
+ import { fileURLToPath } from 'node:url'
30
+
31
+ const __dirname = dirname(fileURLToPath(import.meta.url))
32
+ const REPO_ROOT = join(__dirname, '..', '..')
33
+ export const SOURCE_ROOT = join(REPO_ROOT, 'conductor-creative')
34
+ export const DEST_ROOT = join(__dirname, '..', 'dist', 'creative')
35
+
36
+ /** Relative paths (from conductor-creative/) this build needs. Order doesn't matter. */
37
+ export const CREATIVE_RUNTIME_FILES = [
38
+ 'render.js',
39
+ 'assertions.js',
40
+ 'placements.js',
41
+ 'placements.json',
42
+ 'styles.css',
43
+ 'tokens.css',
44
+ 'frame.css',
45
+ 'frame.html',
46
+ 'font.js',
47
+ 'frame.js',
48
+ 'sheet.html',
49
+ 'sheet.js',
50
+ 'layouts/index.js',
51
+ 'layouts/index.json',
52
+ 'layouts/stacked/layout.json',
53
+ 'layouts/stacked/layout.css',
54
+ 'layouts/bleed/layout.json',
55
+ 'layouts/bleed/layout.css',
56
+ 'layouts/card/layout.json',
57
+ 'layouts/card/layout.css',
58
+ 'layouts/split/layout.json',
59
+ 'layouts/split/layout.css',
60
+ 'job/render.mjs',
61
+ 'job/server.mjs',
62
+ 'job/transport.mjs',
63
+ ]
64
+
65
+ export function copyCreativeRuntime(sourceRoot = SOURCE_ROOT, destRoot = DEST_ROOT) {
66
+ const missing = []
67
+ for (const rel of CREATIVE_RUNTIME_FILES) {
68
+ const src = join(sourceRoot, rel)
69
+ if (!existsSync(src)) {
70
+ missing.push(rel)
71
+ continue
72
+ }
73
+ const dest = join(destRoot, rel)
74
+ mkdirSync(dirname(dest), { recursive: true })
75
+ copyFileSync(src, dest)
76
+ }
77
+ if (missing.length > 0) {
78
+ throw new Error(
79
+ `build-creative.mjs: expected file(s) missing from ${sourceRoot}:\n` +
80
+ missing.map((f) => ` - ${f}`).join('\n')
81
+ )
82
+ }
83
+ return CREATIVE_RUNTIME_FILES.map((rel) => join(destRoot, rel))
84
+ }
85
+
86
+ function isMain() {
87
+ return process.argv[1] && import.meta.url === `file://${process.argv[1]}`
88
+ }
89
+
90
+ if (isMain()) {
91
+ const written = copyCreativeRuntime()
92
+ console.log(`build-creative: copied ${written.length} file(s) into ${DEST_ROOT}`)
93
+ }