@koda-sl/baker-cli 0.122.0-dev.3a1b48e85 → 0.122.0-dev.4a85b9f30

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/dist/cli.js CHANGED
@@ -1,19 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ BackendClient,
3
4
  ELEVENLABS_MAX_MUSIC_LENGTH_MS,
4
5
  IMAGE_GENERATE_MODELS,
5
6
  LayerExecutionError,
6
7
  MODEL_REGISTRY,
8
+ REF_PREFIX,
7
9
  SEEDANCE_DURATIONS,
8
10
  ValidationError,
11
+ collectAssetRefLikes,
9
12
  createEngineFromEnv,
10
13
  defaultRegistry,
11
14
  describeFailureReason,
12
15
  elementMentionKeywords,
13
16
  generateCatalog,
17
+ isPersistedAssetRef,
18
+ parseRefExpr,
19
+ requireCredentialsFromEnv,
14
20
  resolveConcurrency,
21
+ sha256Hex,
22
+ toModelSafeImage,
23
+ ulid,
15
24
  validateCanvasDeep
16
- } from "./chunk-MWFJ5NOP.js";
25
+ } from "./chunk-43KBQLP5.js";
17
26
  import {
18
27
  csvOrJson,
19
28
  daysAgoIso,
@@ -49,7 +58,7 @@ import {
49
58
  import "./chunk-5WRI5ZAA.js";
50
59
 
51
60
  // src/cli.ts
52
- import { defineCommand as defineCommand161, runMain } from "citty";
61
+ import { defineCommand as defineCommand160, runMain } from "citty";
53
62
 
54
63
  // src/commands/actions/index.ts
55
64
  import { defineCommand as defineCommand18 } from "citty";
@@ -848,6 +857,7 @@ var LINKEDIN_LIMITS = {
848
857
  choiceOptionsMax: 30,
849
858
  choiceOptionTextMax: 100,
850
859
  thankYouMessageMax: 300,
860
+ privacyPolicyTextMax: 2e3,
851
861
  legalDisclaimerMax: 2e3,
852
862
  consentsMax: 5,
853
863
  // Campaign Manager caps disclosure checkboxes at 5
@@ -1442,6 +1452,7 @@ var leadFormFields = {
1442
1452
  /** Form language, e.g. { country: "US", language: "en" }. Defaults to the account locale on LinkedIn. */
1443
1453
  locale: z2.object({ country: z2.string().length(2), language: z2.string().length(2) }).optional(),
1444
1454
  privacyPolicyUrl: httpsUrlSchema,
1455
+ privacyPolicyText: z2.string().max(LEAD.privacyPolicyTextMax).optional(),
1445
1456
  questions: z2.array(leadFormQuestionSchema).min(1).max(LEAD.questionsMax),
1446
1457
  consents: z2.array(leadFormConsentSchema).max(LEAD.consentsMax).optional(),
1447
1458
  hiddenFields: z2.array(leadFormHiddenFieldSchema).max(LEAD.hiddenFieldsMax).optional(),
@@ -1916,241 +1927,142 @@ var imagesIngestResponseSchema = z4.object({
1916
1927
  contentHash: z4.string()
1917
1928
  });
1918
1929
 
1919
- // ../api/src/tags.ts
1920
- import { z as z5 } from "zod";
1921
- var TAG_TYPES = [
1922
- "meta",
1923
- "amplitude",
1924
- "googleAds",
1925
- "tiktok",
1926
- "vwo",
1927
- "hotjar",
1928
- "clarity",
1929
- "pinterest",
1930
- "code",
1931
- "googleAnalytics",
1932
- "googleTagManager",
1933
- "hubspot",
1934
- "linkedinInsightTag",
1935
- "onetrust",
1936
- "posthog",
1937
- "datafast",
1938
- "recaptcha",
1939
- "twitterAds"
1940
- ];
1941
- var tagTypeSchema = z5.enum(TAG_TYPES);
1942
- var tagDraftOpKindSchema = z5.enum(["create", "update", "delete"]);
1943
- var tagDraftOpViewSchema = z5.object({
1944
- /** `tag_temp_*` for staged creates; the real tag id for update/delete ops. */
1945
- ref: z5.string(),
1946
- kind: tagDraftOpKindSchema,
1947
- type: tagTypeSchema,
1948
- /** Present on update/delete ops — the real tag this op targets. */
1949
- tagId: z5.string().optional(),
1950
- /** Non-secret config (create: full; update: the staged patch). Secrets are structurally absent. */
1951
- config: z5.record(z5.string(), z5.string()),
1952
- /** Update only — fields the op explicitly clears. */
1953
- clearFields: z5.array(z5.string()).optional(),
1954
- /** Names of secret fields already provided via the dashboard secure form. Never values. */
1955
- secretsSet: z5.array(z5.string()),
1956
- /** Names of secret fields still awaiting user input. */
1957
- secretsPending: z5.array(z5.string()),
1958
- summary: z5.string(),
1959
- stagedAt: z5.number()
1960
- });
1961
- var tagsEffectiveEntrySchema = z5.object({
1962
- /** Real tag id, or `tag_temp_*` for staged creates. Use as flow side-effect `tagIds` value. */
1963
- ref: z5.string(),
1964
- tagId: z5.string().optional(),
1965
- type: tagTypeSchema,
1966
- /** Value of the type's identifying field, when set. */
1967
- identifier: z5.string().optional(),
1968
- /** Redacted config; for staged updates, production config with the patch merged. */
1969
- config: z5.record(z5.string(), z5.string()),
1970
- /** Absent = live production tag with no staged changes in this chat. */
1971
- staged: tagDraftOpKindSchema.optional(),
1972
- secretsSet: z5.array(z5.string()),
1973
- secretsPending: z5.array(z5.string())
1974
- });
1975
- var tagsListRequestSchema = z5.object({ chatId: z5.string() });
1976
- var tagsListResponseSchema = z5.object({ tags: z5.array(tagsEffectiveEntrySchema) });
1977
- var tagsDraftListRequestSchema = z5.object({ chatId: z5.string() });
1978
- var tagsDraftListResponseSchema = z5.object({
1979
- status: z5.enum(["active", "publishing", "applied", "discarded", "none"]),
1980
- ops: z5.array(tagDraftOpViewSchema)
1981
- });
1982
- var tagInputRequestSchema = z5.object({
1983
- // Every tag change is a tab in the approval form: create/edit show the full
1984
- // body; delete shows a confirm. No tag change bypasses this approval.
1985
- mode: z5.enum(["create", "edit", "delete"]),
1986
- tagType: tagTypeSchema,
1987
- /** Edit/delete mode — the real tag id or `tag_temp_*` ref being changed. */
1988
- ref: z5.string().optional(),
1989
- /** Non-secret values the agent proposes to prefill. Secret keys are stripped at every boundary. */
1990
- prefilledConfig: z5.record(z5.string(), z5.string()).optional(),
1991
- /** Secret field names the agent asks the user to provide. */
1992
- requestedSecretFields: z5.array(z5.string()).optional(),
1993
- /** Short message shown above the form explaining why the input is needed. */
1994
- message: z5.string().optional()
1995
- });
1996
- var tagChangeToolInputSchema = z5.object({
1997
- changes: z5.array(tagInputRequestSchema).min(1).max(8)
1998
- });
1999
- var tagInputResultSchema = z5.discriminatedUnion("status", [
2000
- z5.object({
2001
- status: z5.literal("submitted"),
2002
- ref: z5.string(),
2003
- type: tagTypeSchema,
2004
- /** Identifying field name → value (non-secret), when the type has one. */
2005
- identifier: z5.record(z5.string(), z5.string()).optional(),
2006
- secretFieldsSet: z5.array(z5.string()),
2007
- note: z5.string().optional()
2008
- }),
2009
- z5.object({
2010
- status: z5.literal("declined"),
2011
- reason: z5.string().optional()
2012
- })
2013
- ]);
2014
- var tagChangeToolResultSchema = z5.object({
2015
- results: z5.array(tagInputResultSchema)
2016
- });
2017
-
2018
1930
  // ../api/src/testimonials.ts
2019
- import { z as z6 } from "zod";
2020
- var testimonialSourceTypeSchema = z6.enum(["google", "trustpilot"]);
2021
- var testimonialStatusSchema = z6.enum(["pending", "processing", "ready", "error"]);
2022
- var testimonialSentimentSchema = z6.enum(["positive", "neutral", "negative"]);
2023
- var testimonialDocSchema = z6.object({
2024
- _id: z6.string(),
2025
- _creationTime: z6.number(),
2026
- companyId: z6.string(),
2027
- sourceId: z6.string(),
1931
+ import { z as z5 } from "zod";
1932
+ var testimonialSourceTypeSchema = z5.enum(["google", "trustpilot"]);
1933
+ var testimonialStatusSchema = z5.enum(["pending", "processing", "ready", "error"]);
1934
+ var testimonialSentimentSchema = z5.enum(["positive", "neutral", "negative"]);
1935
+ var testimonialDocSchema = z5.object({
1936
+ _id: z5.string(),
1937
+ _creationTime: z5.number(),
1938
+ companyId: z5.string(),
1939
+ sourceId: z5.string(),
2028
1940
  sourceType: testimonialSourceTypeSchema,
2029
- reviewText: z6.string(),
2030
- reviewTitle: z6.string().optional(),
2031
- searchText: z6.string().optional(),
2032
- reviewerName: z6.string().optional(),
2033
- reviewerImageUrl: z6.string().optional(),
2034
- reviewerImageId: z6.string().optional(),
2035
- reviewerLocation: z6.string().optional(),
2036
- rating: z6.number().optional(),
2037
- reviewDate: z6.number().optional(),
2038
- ownerAnswer: z6.string().optional(),
2039
- mediaUrls: z6.array(z6.string()).optional(),
2040
- imageIds: z6.array(z6.string()).optional(),
2041
- videoIds: z6.array(z6.string()).optional(),
2042
- sourceUrl: z6.string().optional(),
2043
- rawData: z6.unknown().optional(),
2044
- tags: z6.array(z6.string()),
2045
- highlight: z6.string().optional(),
2046
- language: z6.string().optional(),
2047
- summary: z6.string().optional(),
1941
+ reviewText: z5.string(),
1942
+ reviewTitle: z5.string().optional(),
1943
+ searchText: z5.string().optional(),
1944
+ reviewerName: z5.string().optional(),
1945
+ reviewerImageUrl: z5.string().optional(),
1946
+ reviewerImageId: z5.string().optional(),
1947
+ reviewerLocation: z5.string().optional(),
1948
+ rating: z5.number().optional(),
1949
+ reviewDate: z5.number().optional(),
1950
+ ownerAnswer: z5.string().optional(),
1951
+ mediaUrls: z5.array(z5.string()).optional(),
1952
+ imageIds: z5.array(z5.string()).optional(),
1953
+ videoIds: z5.array(z5.string()).optional(),
1954
+ sourceUrl: z5.string().optional(),
1955
+ rawData: z5.unknown().optional(),
1956
+ tags: z5.array(z5.string()),
1957
+ highlight: z5.string().optional(),
1958
+ language: z5.string().optional(),
1959
+ summary: z5.string().optional(),
2048
1960
  sentiment: testimonialSentimentSchema.optional(),
2049
- textEmbedding: z6.array(z6.number()).optional(),
2050
- externalId: z6.string().optional(),
2051
- contentHash: z6.string().optional(),
1961
+ textEmbedding: z5.array(z5.number()).optional(),
1962
+ externalId: z5.string().optional(),
1963
+ contentHash: z5.string().optional(),
2052
1964
  status: testimonialStatusSchema,
2053
- errorMessage: z6.string().optional(),
2054
- createdAt: z6.number(),
2055
- updatedAt: z6.number()
1965
+ errorMessage: z5.string().optional(),
1966
+ createdAt: z5.number(),
1967
+ updatedAt: z5.number()
2056
1968
  });
2057
- var testimonialsListRequestSchema = z6.object({
1969
+ var testimonialsListRequestSchema = z5.object({
2058
1970
  source: testimonialSourceTypeSchema.optional(),
2059
- rating_min: z6.coerce.number().int().min(1).max(5).optional(),
2060
- rating_max: z6.coerce.number().int().min(1).max(5).optional(),
2061
- tags: z6.string().transform((s) => s.split(",").filter(Boolean)).optional(),
1971
+ rating_min: z5.coerce.number().int().min(1).max(5).optional(),
1972
+ rating_max: z5.coerce.number().int().min(1).max(5).optional(),
1973
+ tags: z5.string().transform((s) => s.split(",").filter(Boolean)).optional(),
2062
1974
  status: testimonialStatusSchema.optional(),
2063
1975
  sentiment: testimonialSentimentSchema.optional(),
2064
- language: z6.string().min(2).max(5).optional(),
2065
- limit: z6.coerce.number().int().positive().max(200).optional()
2066
- });
2067
- var testimonialsListResponseSchema = z6.array(testimonialDocSchema);
2068
- var testimonialsGetRequestSchema = z6.object({ id: z6.string().min(1, "Missing id parameter") });
2069
- var testimonialsSearchRequestSchema = z6.object({
2070
- query: z6.string().min(1),
2071
- limit: z6.coerce.number().int().positive().max(100).optional(),
1976
+ language: z5.string().min(2).max(5).optional(),
1977
+ limit: z5.coerce.number().int().positive().max(200).optional()
1978
+ });
1979
+ var testimonialsListResponseSchema = z5.array(testimonialDocSchema);
1980
+ var testimonialsGetRequestSchema = z5.object({ id: z5.string().min(1, "Missing id parameter") });
1981
+ var testimonialsSearchRequestSchema = z5.object({
1982
+ query: z5.string().min(1),
1983
+ limit: z5.coerce.number().int().positive().max(100).optional(),
2072
1984
  source: testimonialSourceTypeSchema.optional(),
2073
- rating_min: z6.coerce.number().int().min(1).max(5).optional(),
2074
- rating_max: z6.coerce.number().int().min(1).max(5).optional(),
2075
- tags: z6.array(z6.string()).optional(),
1985
+ rating_min: z5.coerce.number().int().min(1).max(5).optional(),
1986
+ rating_max: z5.coerce.number().int().min(1).max(5).optional(),
1987
+ tags: z5.array(z5.string()).optional(),
2076
1988
  status: testimonialStatusSchema.optional(),
2077
1989
  sentiment: testimonialSentimentSchema.optional(),
2078
- language: z6.string().min(2).max(5).optional()
1990
+ language: z5.string().min(2).max(5).optional()
2079
1991
  }).refine(
2080
1992
  (data) => data.rating_min === void 0 || data.rating_max === void 0 || data.rating_min <= data.rating_max,
2081
1993
  { message: "rating_min must be less than or equal to rating_max" }
2082
1994
  );
2083
- var testimonialsSearchResponseSchema = z6.array(testimonialDocSchema);
2084
- var testimonialsOutscraperWebhookResponseSchema = z6.object({
2085
- ok: z6.literal(true),
2086
- note: z6.string().optional()
1995
+ var testimonialsSearchResponseSchema = z5.array(testimonialDocSchema);
1996
+ var testimonialsOutscraperWebhookResponseSchema = z5.object({
1997
+ ok: z5.literal(true),
1998
+ note: z5.string().optional()
2087
1999
  });
2088
2000
 
2089
2001
  // ../api/src/videos.ts
2090
- import { z as z7 } from "zod";
2091
- var videoStatusSchema = z7.enum(["uploading", "uploaded", "processing", "ready", "error"]);
2092
- var videoTranscriptSegmentSchema = z7.object({
2093
- text: z7.string(),
2094
- startSecond: z7.number(),
2095
- endSecond: z7.number()
2096
- });
2097
- var videoSceneSchema = z7.object({
2098
- title: z7.string(),
2099
- description: z7.string(),
2100
- startSecond: z7.number(),
2101
- endSecond: z7.number(),
2102
- thumbnailTime: z7.number()
2103
- });
2104
- var videoDocSchema = z7.object({
2105
- _id: z7.string(),
2106
- _creationTime: z7.number(),
2107
- companyId: z7.string(),
2108
- muxAssetId: z7.string(),
2109
- muxPlaybackId: z7.string(),
2110
- muxUploadId: z7.string(),
2111
- name: z7.string(),
2112
- description: z7.string(),
2113
- tags: z7.array(z7.string()),
2114
- source: z7.string(),
2115
- externalId: z7.string().optional(),
2116
- sourceId: z7.string().optional(),
2117
- width: z7.number().optional(),
2118
- height: z7.number().optional(),
2119
- aspectRatio: z7.number().optional(),
2120
- duration: z7.number().optional(),
2121
- transcript: z7.string().optional(),
2122
- transcriptSegments: z7.array(videoTranscriptSegmentSchema).optional(),
2123
- scenes: z7.array(videoSceneSchema).optional(),
2124
- descriptionEmbedding: z7.array(z7.number()).optional(),
2125
- searchText: z7.string().optional(),
2002
+ import { z as z6 } from "zod";
2003
+ var videoStatusSchema = z6.enum(["uploading", "uploaded", "processing", "ready", "error"]);
2004
+ var videoTranscriptSegmentSchema = z6.object({
2005
+ text: z6.string(),
2006
+ startSecond: z6.number(),
2007
+ endSecond: z6.number()
2008
+ });
2009
+ var videoSceneSchema = z6.object({
2010
+ title: z6.string(),
2011
+ description: z6.string(),
2012
+ startSecond: z6.number(),
2013
+ endSecond: z6.number(),
2014
+ thumbnailTime: z6.number()
2015
+ });
2016
+ var videoDocSchema = z6.object({
2017
+ _id: z6.string(),
2018
+ _creationTime: z6.number(),
2019
+ companyId: z6.string(),
2020
+ muxAssetId: z6.string(),
2021
+ muxPlaybackId: z6.string(),
2022
+ muxUploadId: z6.string(),
2023
+ name: z6.string(),
2024
+ description: z6.string(),
2025
+ tags: z6.array(z6.string()),
2026
+ source: z6.string(),
2027
+ externalId: z6.string().optional(),
2028
+ sourceId: z6.string().optional(),
2029
+ width: z6.number().optional(),
2030
+ height: z6.number().optional(),
2031
+ aspectRatio: z6.number().optional(),
2032
+ duration: z6.number().optional(),
2033
+ transcript: z6.string().optional(),
2034
+ transcriptSegments: z6.array(videoTranscriptSegmentSchema).optional(),
2035
+ scenes: z6.array(videoSceneSchema).optional(),
2036
+ descriptionEmbedding: z6.array(z6.number()).optional(),
2037
+ searchText: z6.string().optional(),
2126
2038
  status: videoStatusSchema,
2127
- errorMessage: z7.string().optional(),
2128
- createdAt: z7.number(),
2129
- updatedAt: z7.number(),
2130
- thumbnailUrl: z7.string()
2131
- });
2132
- var videosWebhookResponseSchema = z7.object({ ok: z7.literal(true) });
2133
- var videosGetRequestSchema = z7.object({ id: z7.string().min(1, "Missing id parameter") });
2134
- var videosSearchRequestSchema = z7.object({
2135
- query: z7.string().min(1),
2136
- limit: z7.coerce.number().int().positive().max(100).optional(),
2137
- tags: z7.array(z7.string()).optional()
2138
- });
2139
- var videoSearchResultSchema = z7.object({
2140
- _id: z7.string(),
2141
- thumbnailUrl: z7.string(),
2142
- name: z7.string(),
2143
- description: z7.string(),
2144
- tags: z7.array(z7.string()),
2145
- status: z7.string(),
2146
- duration: z7.number().optional(),
2147
- muxPlaybackId: z7.string(),
2148
- createdAt: z7.number()
2149
- });
2150
- var videosSearchResponseSchema = z7.array(videoSearchResultSchema);
2151
- var videosUploadResponseSchema = z7.object({ uploadUrl: z7.string(), videoId: z7.string() });
2152
- var videosDeleteRequestSchema = z7.object({ id: z7.string().min(1, "Missing video ID") });
2153
- var videosDeleteResponseSchema = z7.object({ ok: z7.literal(true) });
2039
+ errorMessage: z6.string().optional(),
2040
+ createdAt: z6.number(),
2041
+ updatedAt: z6.number(),
2042
+ thumbnailUrl: z6.string()
2043
+ });
2044
+ var videosWebhookResponseSchema = z6.object({ ok: z6.literal(true) });
2045
+ var videosGetRequestSchema = z6.object({ id: z6.string().min(1, "Missing id parameter") });
2046
+ var videosSearchRequestSchema = z6.object({
2047
+ query: z6.string().min(1),
2048
+ limit: z6.coerce.number().int().positive().max(100).optional(),
2049
+ tags: z6.array(z6.string()).optional()
2050
+ });
2051
+ var videoSearchResultSchema = z6.object({
2052
+ _id: z6.string(),
2053
+ thumbnailUrl: z6.string(),
2054
+ name: z6.string(),
2055
+ description: z6.string(),
2056
+ tags: z6.array(z6.string()),
2057
+ status: z6.string(),
2058
+ duration: z6.number().optional(),
2059
+ muxPlaybackId: z6.string(),
2060
+ createdAt: z6.number()
2061
+ });
2062
+ var videosSearchResponseSchema = z6.array(videoSearchResultSchema);
2063
+ var videosUploadResponseSchema = z6.object({ uploadUrl: z6.string(), videoId: z6.string() });
2064
+ var videosDeleteRequestSchema = z6.object({ id: z6.string().min(1, "Missing video ID") });
2065
+ var videosDeleteResponseSchema = z6.object({ ok: z6.literal(true) });
2154
2066
 
2155
2067
  // src/commands/actions/complete.ts
2156
2068
  import { defineCommand as defineCommand2 } from "citty";
@@ -3963,37 +3875,37 @@ var GEO_TARGET_CONSTANT_REGEX = /^geoTargetConstants\/\d+$/;
3963
3875
  var LANGUAGE_CONSTANT_REGEX = /^languageConstants\/\d+$/;
3964
3876
 
3965
3877
  // ../api/src/ads-google/ops.ts
3966
- import { z as z8 } from "zod";
3967
- var tempRefSchema2 = z8.string().regex(TEMP_REF_REGEX2, "expected a g_temp_* reference");
3968
- var refSchema = z8.union([
3969
- z8.string().regex(RESOURCE_NAME_REGEX, "expected a customers/\u2026/\u2026/\u2026 resource name"),
3970
- z8.string().regex(NUMERIC_ID_REGEX2, "expected a numeric id"),
3878
+ import { z as z7 } from "zod";
3879
+ var tempRefSchema2 = z7.string().regex(TEMP_REF_REGEX2, "expected a g_temp_* reference");
3880
+ var refSchema = z7.union([
3881
+ z7.string().regex(RESOURCE_NAME_REGEX, "expected a customers/\u2026/\u2026/\u2026 resource name"),
3882
+ z7.string().regex(NUMERIC_ID_REGEX2, "expected a numeric id"),
3971
3883
  tempRefSchema2
3972
3884
  ]);
3973
3885
  var targetRefSchema = refSchema;
3974
- var microsSchema = z8.number().int().positive("expected a positive micros amount");
3975
- var httpsUrlSchema2 = z8.string().url().refine((u) => u.startsWith("https://"), "final URLs must be https");
3976
- var customerIdSchema = z8.string().regex(NUMERIC_ID_REGEX2, "customerId must be the bare numeric customer id");
3977
- var stageableStatusSchema2 = z8.enum(STAGEABLE_CREATE_STATUSES2);
3978
- var matchTypeSchema = z8.enum(KEYWORD_MATCH_TYPES);
3979
- var keywordTextSchema = z8.string().min(1).max(GOOGLE_ADS_LIMITS.keyword.textMax).refine((t) => t.trim().split(/\s+/).length <= GOOGLE_ADS_LIMITS.keyword.wordsMax, "keyword exceeds 10 words");
3980
- var budgetCreateSchema = z8.object({
3981
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.budget.nameMax),
3886
+ var microsSchema = z7.number().int().positive("expected a positive micros amount");
3887
+ var httpsUrlSchema2 = z7.string().url().refine((u) => u.startsWith("https://"), "final URLs must be https");
3888
+ var customerIdSchema = z7.string().regex(NUMERIC_ID_REGEX2, "customerId must be the bare numeric customer id");
3889
+ var stageableStatusSchema2 = z7.enum(STAGEABLE_CREATE_STATUSES2);
3890
+ var matchTypeSchema = z7.enum(KEYWORD_MATCH_TYPES);
3891
+ var keywordTextSchema = z7.string().min(1).max(GOOGLE_ADS_LIMITS.keyword.textMax).refine((t) => t.trim().split(/\s+/).length <= GOOGLE_ADS_LIMITS.keyword.wordsMax, "keyword exceeds 10 words");
3892
+ var budgetCreateSchema = z7.object({
3893
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.budget.nameMax),
3982
3894
  amountMicros: microsSchema,
3983
- deliveryMethod: z8.enum(BUDGET_DELIVERY_METHODS).default("STANDARD"),
3984
- explicitlyShared: z8.boolean().default(false)
3895
+ deliveryMethod: z7.enum(BUDGET_DELIVERY_METHODS).default("STANDARD"),
3896
+ explicitlyShared: z7.boolean().default(false)
3985
3897
  });
3986
- var budgetUpdateSchema = z8.object({
3987
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.budget.nameMax).optional(),
3898
+ var budgetUpdateSchema = z7.object({
3899
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.budget.nameMax).optional(),
3988
3900
  amountMicros: microsSchema.optional(),
3989
- deliveryMethod: z8.enum(BUDGET_DELIVERY_METHODS).optional()
3901
+ deliveryMethod: z7.enum(BUDGET_DELIVERY_METHODS).optional()
3990
3902
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
3991
- var biddingConfigSchema = z8.object({
3992
- type: z8.enum(BIDDING_STRATEGY_TYPES),
3903
+ var biddingConfigSchema = z7.object({
3904
+ type: z7.enum(BIDDING_STRATEGY_TYPES),
3993
3905
  targetCpaMicros: microsSchema.optional(),
3994
- targetRoas: z8.number().positive().optional(),
3906
+ targetRoas: z7.number().positive().optional(),
3995
3907
  cpcBidCeilingMicros: microsSchema.optional(),
3996
- enhancedCpcEnabled: z8.boolean().optional()
3908
+ enhancedCpcEnabled: z7.boolean().optional()
3997
3909
  }).superRefine((p, ctx) => {
3998
3910
  if (p.type === "TARGET_CPA" && p.targetCpaMicros === void 0) {
3999
3911
  ctx.addIssue({ code: "custom", path: ["targetCpaMicros"], message: "TARGET_CPA needs targetCpaMicros" });
@@ -4002,17 +3914,17 @@ var biddingConfigSchema = z8.object({
4002
3914
  ctx.addIssue({ code: "custom", path: ["targetRoas"], message: "TARGET_ROAS needs targetRoas" });
4003
3915
  }
4004
3916
  });
4005
- var networkSettingsSchema = z8.object({
4006
- targetGoogleSearch: z8.boolean().optional(),
4007
- targetSearchNetwork: z8.boolean().optional(),
4008
- targetContentNetwork: z8.boolean().optional(),
4009
- targetPartnerSearchNetwork: z8.boolean().optional()
3917
+ var networkSettingsSchema = z7.object({
3918
+ targetGoogleSearch: z7.boolean().optional(),
3919
+ targetSearchNetwork: z7.boolean().optional(),
3920
+ targetContentNetwork: z7.boolean().optional(),
3921
+ targetPartnerSearchNetwork: z7.boolean().optional()
4010
3922
  });
4011
- var dateSchema = z8.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected a YYYY-MM-DD date");
4012
- var campaignCreateSchema2 = z8.object({
4013
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax),
4014
- channelType: z8.enum(ADVERTISING_CHANNEL_TYPES),
4015
- channelSubType: z8.enum(ADVERTISING_CHANNEL_SUB_TYPES).optional(),
3923
+ var dateSchema = z7.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected a YYYY-MM-DD date");
3924
+ var campaignCreateSchema2 = z7.object({
3925
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax),
3926
+ channelType: z7.enum(ADVERTISING_CHANNEL_TYPES),
3927
+ channelSubType: z7.enum(ADVERTISING_CHANNEL_SUB_TYPES).optional(),
4016
3928
  budget: refSchema,
4017
3929
  /** Inline standard bidding, or a portfolio strategy ref via biddingStrategy. */
4018
3930
  bidding: biddingConfigSchema.optional(),
@@ -4021,7 +3933,7 @@ var campaignCreateSchema2 = z8.object({
4021
3933
  startDate: dateSchema.optional(),
4022
3934
  endDate: dateSchema.optional(),
4023
3935
  /** Advisory Google Ads UI objective — drives warnings, not sent to the API. */
4024
- objective: z8.enum(CAMPAIGN_OBJECTIVES).optional(),
3936
+ objective: z7.enum(CAMPAIGN_OBJECTIVES).optional(),
4025
3937
  status: stageableStatusSchema2.default("PAUSED")
4026
3938
  }).superRefine((p, ctx) => {
4027
3939
  if (!p.bidding && !p.biddingStrategy) {
@@ -4045,129 +3957,129 @@ var campaignCreateSchema2 = z8.object({
4045
3957
  ctx.addIssue({ code: "custom", path: ["endDate"], message: "endDate must be after startDate" });
4046
3958
  }
4047
3959
  });
4048
- var campaignUpdateSchema2 = z8.object({
4049
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax).optional(),
3960
+ var campaignUpdateSchema2 = z7.object({
3961
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax).optional(),
4050
3962
  budget: refSchema.optional(),
4051
3963
  bidding: biddingConfigSchema.optional(),
4052
3964
  networkSettings: networkSettingsSchema.optional(),
4053
3965
  startDate: dateSchema.optional(),
4054
3966
  endDate: dateSchema.optional(),
4055
- status: z8.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
3967
+ status: z7.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
4056
3968
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4057
- var adGroupCreateSchema = z8.object({
4058
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.adGroup.nameMax),
3969
+ var adGroupCreateSchema = z7.object({
3970
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.adGroup.nameMax),
4059
3971
  campaign: refSchema,
4060
- type: z8.enum(AD_GROUP_TYPES).default("SEARCH_STANDARD"),
3972
+ type: z7.enum(AD_GROUP_TYPES).default("SEARCH_STANDARD"),
4061
3973
  cpcBidMicros: microsSchema.optional(),
4062
3974
  status: stageableStatusSchema2.default("PAUSED")
4063
3975
  });
4064
- var adGroupUpdateSchema = z8.object({
4065
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.adGroup.nameMax).optional(),
3976
+ var adGroupUpdateSchema = z7.object({
3977
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.adGroup.nameMax).optional(),
4066
3978
  cpcBidMicros: microsSchema.optional(),
4067
- status: z8.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
3979
+ status: z7.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
4068
3980
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4069
- var keywordAddSchema = z8.object({
3981
+ var keywordAddSchema = z7.object({
4070
3982
  adGroup: refSchema,
4071
3983
  text: keywordTextSchema,
4072
3984
  matchType: matchTypeSchema,
4073
3985
  cpcBidMicros: microsSchema.optional(),
4074
- finalUrls: z8.array(httpsUrlSchema2).optional(),
3986
+ finalUrls: z7.array(httpsUrlSchema2).optional(),
4075
3987
  status: stageableStatusSchema2.default("ENABLED")
4076
3988
  });
4077
- var keywordUpdateSchema = z8.object({
3989
+ var keywordUpdateSchema = z7.object({
4078
3990
  cpcBidMicros: microsSchema.optional(),
4079
- finalUrls: z8.array(httpsUrlSchema2).optional(),
4080
- status: z8.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
3991
+ finalUrls: z7.array(httpsUrlSchema2).optional(),
3992
+ status: z7.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
4081
3993
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4082
- var negativeKeywordAddSchema = z8.object({
4083
- level: z8.enum(["adGroup", "campaign"]),
3994
+ var negativeKeywordAddSchema = z7.object({
3995
+ level: z7.enum(["adGroup", "campaign"]),
4084
3996
  parent: refSchema,
4085
3997
  text: keywordTextSchema,
4086
3998
  matchType: matchTypeSchema
4087
3999
  });
4088
- var sharedSetCreateSchema = z8.object({
4089
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.sharedSet.nameMax),
4090
- type: z8.enum(SHARED_SET_TYPES).default("NEGATIVE_KEYWORDS")
4000
+ var sharedSetCreateSchema = z7.object({
4001
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.sharedSet.nameMax),
4002
+ type: z7.enum(SHARED_SET_TYPES).default("NEGATIVE_KEYWORDS")
4091
4003
  });
4092
- var sharedSetMemberAddSchema = z8.object({
4004
+ var sharedSetMemberAddSchema = z7.object({
4093
4005
  sharedSet: refSchema,
4094
4006
  text: keywordTextSchema,
4095
4007
  matchType: matchTypeSchema
4096
4008
  });
4097
- var campaignSharedSetAttachSchema = z8.object({
4009
+ var campaignSharedSetAttachSchema = z7.object({
4098
4010
  campaign: refSchema,
4099
4011
  sharedSet: refSchema
4100
4012
  });
4101
- var adTextAssetSchema = z8.object({
4102
- text: z8.string().min(1),
4103
- pinnedField: z8.enum(PINNED_FIELDS).optional()
4013
+ var adTextAssetSchema = z7.object({
4014
+ text: z7.string().min(1),
4015
+ pinnedField: z7.enum(PINNED_FIELDS).optional()
4104
4016
  });
4105
- var responsiveSearchAdSchema = z8.object({
4106
- format: z8.literal("responsiveSearch"),
4107
- headlines: z8.array(
4017
+ var responsiveSearchAdSchema = z7.object({
4018
+ format: z7.literal("responsiveSearch"),
4019
+ headlines: z7.array(
4108
4020
  adTextAssetSchema.refine(
4109
4021
  (a) => a.text.length <= GOOGLE_ADS_LIMITS.responsiveSearchAd.headlineTextMax,
4110
4022
  "headline exceeds 30 chars"
4111
4023
  )
4112
4024
  ).min(GOOGLE_ADS_LIMITS.responsiveSearchAd.headlinesMin).max(GOOGLE_ADS_LIMITS.responsiveSearchAd.headlinesMax),
4113
- descriptions: z8.array(
4025
+ descriptions: z7.array(
4114
4026
  adTextAssetSchema.refine(
4115
4027
  (a) => a.text.length <= GOOGLE_ADS_LIMITS.responsiveSearchAd.descriptionTextMax,
4116
4028
  "description exceeds 90 chars"
4117
4029
  )
4118
4030
  ).min(GOOGLE_ADS_LIMITS.responsiveSearchAd.descriptionsMin).max(GOOGLE_ADS_LIMITS.responsiveSearchAd.descriptionsMax),
4119
- path1: z8.string().max(GOOGLE_ADS_LIMITS.responsiveSearchAd.pathMax).optional(),
4120
- path2: z8.string().max(GOOGLE_ADS_LIMITS.responsiveSearchAd.pathMax).optional(),
4121
- finalUrls: z8.array(httpsUrlSchema2).min(1)
4122
- });
4123
- var responsiveDisplayAdSchema = z8.object({
4124
- format: z8.literal("responsiveDisplay"),
4125
- headlines: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.headlineTextMax) })).min(1).max(5),
4126
- longHeadline: z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.longHeadlineTextMax) }),
4127
- descriptions: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.descriptionTextMax) })).min(1).max(5),
4128
- businessName: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.businessNameMax),
4031
+ path1: z7.string().max(GOOGLE_ADS_LIMITS.responsiveSearchAd.pathMax).optional(),
4032
+ path2: z7.string().max(GOOGLE_ADS_LIMITS.responsiveSearchAd.pathMax).optional(),
4033
+ finalUrls: z7.array(httpsUrlSchema2).min(1)
4034
+ });
4035
+ var responsiveDisplayAdSchema = z7.object({
4036
+ format: z7.literal("responsiveDisplay"),
4037
+ headlines: z7.array(z7.object({ text: z7.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.headlineTextMax) })).min(1).max(5),
4038
+ longHeadline: z7.object({ text: z7.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.longHeadlineTextMax) }),
4039
+ descriptions: z7.array(z7.object({ text: z7.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.descriptionTextMax) })).min(1).max(5),
4040
+ businessName: z7.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.businessNameMax),
4129
4041
  // A Responsive Display Ad's images are fields on the ad's own content (never campaign-level
4130
4042
  // asset links). Google requires ≥1 landscape marketing image (1.91:1) AND ≥1 square marketing
4131
4043
  // image (1:1) to serve; the logo images are optional.
4132
- marketingImageAssets: z8.array(refSchema).optional(),
4133
- squareMarketingImageAssets: z8.array(refSchema).optional(),
4134
- logoImageAssets: z8.array(refSchema).optional(),
4135
- finalUrls: z8.array(httpsUrlSchema2).min(1)
4136
- });
4137
- var callAdSchema = z8.object({
4138
- format: z8.literal("call"),
4139
- countryCode: z8.string().length(2),
4140
- phoneNumber: z8.string().min(3),
4141
- headline1: z8.string().min(1).max(30),
4142
- headline2: z8.string().min(1).max(30),
4143
- description1: z8.string().min(1).max(90),
4144
- description2: z8.string().min(1).max(90),
4145
- businessName: z8.string().min(1).max(25),
4146
- finalUrls: z8.array(httpsUrlSchema2).min(1)
4147
- });
4148
- var appAdSchema = z8.object({
4149
- format: z8.literal("app"),
4150
- headlines: z8.array(z8.object({ text: z8.string().min(1).max(30) })).min(1),
4151
- descriptions: z8.array(z8.object({ text: z8.string().min(1).max(90) })).min(1)
4152
- });
4153
- var videoAdSchema = z8.object({
4154
- format: z8.literal("video"),
4044
+ marketingImageAssets: z7.array(refSchema).optional(),
4045
+ squareMarketingImageAssets: z7.array(refSchema).optional(),
4046
+ logoImageAssets: z7.array(refSchema).optional(),
4047
+ finalUrls: z7.array(httpsUrlSchema2).min(1)
4048
+ });
4049
+ var callAdSchema = z7.object({
4050
+ format: z7.literal("call"),
4051
+ countryCode: z7.string().length(2),
4052
+ phoneNumber: z7.string().min(3),
4053
+ headline1: z7.string().min(1).max(30),
4054
+ headline2: z7.string().min(1).max(30),
4055
+ description1: z7.string().min(1).max(90),
4056
+ description2: z7.string().min(1).max(90),
4057
+ businessName: z7.string().min(1).max(25),
4058
+ finalUrls: z7.array(httpsUrlSchema2).min(1)
4059
+ });
4060
+ var appAdSchema = z7.object({
4061
+ format: z7.literal("app"),
4062
+ headlines: z7.array(z7.object({ text: z7.string().min(1).max(30) })).min(1),
4063
+ descriptions: z7.array(z7.object({ text: z7.string().min(1).max(90) })).min(1)
4064
+ });
4065
+ var videoAdSchema = z7.object({
4066
+ format: z7.literal("video"),
4155
4067
  // A raw YouTube id is not a publishable Google Ads reference — the video must be staged as
4156
4068
  // its own `google.asset.create` (type: youtubeVideo) first, then referenced here by asset ref.
4157
- videoAssets: z8.array(refSchema).min(1),
4158
- finalUrls: z8.array(httpsUrlSchema2).min(1)
4159
- });
4160
- var demandGenAdSchema = z8.object({
4161
- format: z8.literal("demandGen"),
4162
- headlines: z8.array(z8.object({ text: z8.string().min(1).max(40) })).min(1).max(5),
4163
- descriptions: z8.array(z8.object({ text: z8.string().min(1).max(90) })).min(1).max(5),
4164
- businessName: z8.string().min(1).max(25),
4165
- finalUrls: z8.array(httpsUrlSchema2).min(1),
4166
- imageAssets: z8.array(refSchema).optional(),
4167
- squareImageAssets: z8.array(refSchema).optional(),
4168
- logoImageAssets: z8.array(refSchema).optional()
4169
- });
4170
- var adContentSchema = z8.discriminatedUnion("format", [
4069
+ videoAssets: z7.array(refSchema).min(1),
4070
+ finalUrls: z7.array(httpsUrlSchema2).min(1)
4071
+ });
4072
+ var demandGenAdSchema = z7.object({
4073
+ format: z7.literal("demandGen"),
4074
+ headlines: z7.array(z7.object({ text: z7.string().min(1).max(40) })).min(1).max(5),
4075
+ descriptions: z7.array(z7.object({ text: z7.string().min(1).max(90) })).min(1).max(5),
4076
+ businessName: z7.string().min(1).max(25),
4077
+ finalUrls: z7.array(httpsUrlSchema2).min(1),
4078
+ imageAssets: z7.array(refSchema).optional(),
4079
+ squareImageAssets: z7.array(refSchema).optional(),
4080
+ logoImageAssets: z7.array(refSchema).optional()
4081
+ });
4082
+ var adContentSchema = z7.discriminatedUnion("format", [
4171
4083
  responsiveSearchAdSchema,
4172
4084
  responsiveDisplayAdSchema,
4173
4085
  callAdSchema,
@@ -4175,45 +4087,45 @@ var adContentSchema = z8.discriminatedUnion("format", [
4175
4087
  videoAdSchema,
4176
4088
  demandGenAdSchema
4177
4089
  ]);
4178
- var adCreateSchema = z8.object({
4090
+ var adCreateSchema = z7.object({
4179
4091
  adGroup: refSchema,
4180
4092
  status: stageableStatusSchema2.default("PAUSED"),
4181
4093
  content: adContentSchema
4182
4094
  });
4183
- var adUpdateSchema = z8.object({
4184
- status: z8.enum(["ENABLED", "PAUSED", "REMOVED"]).optional(),
4095
+ var adUpdateSchema = z7.object({
4096
+ status: z7.enum(["ENABLED", "PAUSED", "REMOVED"]).optional(),
4185
4097
  /** Whole-content replacement for RSA-like formats; re-validated against adContentSchema. */
4186
- content: z8.record(z8.string(), z8.unknown()).optional()
4098
+ content: z7.record(z7.string(), z7.unknown()).optional()
4187
4099
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4188
- var textAssetSchema = z8.object({ type: z8.literal("text"), text: z8.string().min(1) });
4189
- var imageAssetSchema = z8.object({
4190
- type: z8.literal("image"),
4191
- imageId: z8.string().min(1),
4192
- name: z8.string().optional()
4193
- });
4194
- var youtubeVideoAssetSchema = z8.object({
4195
- type: z8.literal("youtubeVideo"),
4196
- youtubeVideoId: z8.string().min(1),
4197
- name: z8.string().optional()
4198
- });
4199
- var sitelinkAssetSchema = z8.object({
4200
- type: z8.literal("sitelink"),
4201
- linkText: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.sitelinkLinkTextMax),
4202
- description1: z8.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4203
- description2: z8.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4204
- finalUrls: z8.array(httpsUrlSchema2).min(1)
4205
- });
4206
- var calloutAssetSchema = z8.object({
4207
- type: z8.literal("callout"),
4208
- calloutText: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.calloutTextMax)
4209
- });
4210
- var structuredSnippetAssetSchema = z8.object({
4211
- type: z8.literal("structuredSnippet"),
4212
- header: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetHeaderMax),
4213
- values: z8.array(z8.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax)
4214
- });
4215
- var callToActionAssetSchema = z8.object({ type: z8.literal("callToAction"), callToAction: z8.string().min(1) });
4216
- var assetCreateSchema = z8.discriminatedUnion("type", [
4100
+ var textAssetSchema = z7.object({ type: z7.literal("text"), text: z7.string().min(1) });
4101
+ var imageAssetSchema = z7.object({
4102
+ type: z7.literal("image"),
4103
+ imageId: z7.string().min(1),
4104
+ name: z7.string().optional()
4105
+ });
4106
+ var youtubeVideoAssetSchema = z7.object({
4107
+ type: z7.literal("youtubeVideo"),
4108
+ youtubeVideoId: z7.string().min(1),
4109
+ name: z7.string().optional()
4110
+ });
4111
+ var sitelinkAssetSchema = z7.object({
4112
+ type: z7.literal("sitelink"),
4113
+ linkText: z7.string().min(1).max(GOOGLE_ADS_LIMITS.asset.sitelinkLinkTextMax),
4114
+ description1: z7.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4115
+ description2: z7.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4116
+ finalUrls: z7.array(httpsUrlSchema2).min(1)
4117
+ });
4118
+ var calloutAssetSchema = z7.object({
4119
+ type: z7.literal("callout"),
4120
+ calloutText: z7.string().min(1).max(GOOGLE_ADS_LIMITS.asset.calloutTextMax)
4121
+ });
4122
+ var structuredSnippetAssetSchema = z7.object({
4123
+ type: z7.literal("structuredSnippet"),
4124
+ header: z7.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetHeaderMax),
4125
+ values: z7.array(z7.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax)
4126
+ });
4127
+ var callToActionAssetSchema = z7.object({ type: z7.literal("callToAction"), callToAction: z7.string().min(1) });
4128
+ var assetCreateSchema = z7.discriminatedUnion("type", [
4217
4129
  textAssetSchema,
4218
4130
  imageAssetSchema,
4219
4131
  youtubeVideoAssetSchema,
@@ -4222,136 +4134,136 @@ var assetCreateSchema = z8.discriminatedUnion("type", [
4222
4134
  structuredSnippetAssetSchema,
4223
4135
  callToActionAssetSchema
4224
4136
  ]);
4225
- var assetUpdateSchema = z8.object({
4226
- name: z8.string().min(1).optional(),
4227
- linkText: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.sitelinkLinkTextMax).optional(),
4228
- description1: z8.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4229
- description2: z8.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4230
- finalUrls: z8.array(httpsUrlSchema2).min(1).optional(),
4231
- calloutText: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.calloutTextMax).optional(),
4232
- header: z8.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetHeaderMax).optional(),
4233
- values: z8.array(z8.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax).optional(),
4234
- callToAction: z8.string().min(1).optional(),
4235
- text: z8.string().min(1).optional()
4137
+ var assetUpdateSchema = z7.object({
4138
+ name: z7.string().min(1).optional(),
4139
+ linkText: z7.string().min(1).max(GOOGLE_ADS_LIMITS.asset.sitelinkLinkTextMax).optional(),
4140
+ description1: z7.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4141
+ description2: z7.string().max(GOOGLE_ADS_LIMITS.asset.sitelinkDescriptionMax).optional(),
4142
+ finalUrls: z7.array(httpsUrlSchema2).min(1).optional(),
4143
+ calloutText: z7.string().min(1).max(GOOGLE_ADS_LIMITS.asset.calloutTextMax).optional(),
4144
+ header: z7.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetHeaderMax).optional(),
4145
+ values: z7.array(z7.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax).optional(),
4146
+ callToAction: z7.string().min(1).optional(),
4147
+ text: z7.string().min(1).optional()
4236
4148
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4237
- var assetLinkAttachSchema = z8.object({
4238
- level: z8.enum(["campaign", "adGroup", "customer"]),
4149
+ var assetLinkAttachSchema = z7.object({
4150
+ level: z7.enum(["campaign", "adGroup", "customer"]),
4239
4151
  parent: refSchema.optional(),
4240
4152
  asset: refSchema,
4241
- fieldType: z8.enum(ASSET_FIELD_TYPES)
4153
+ fieldType: z7.enum(ASSET_FIELD_TYPES)
4242
4154
  }).superRefine((value, ctx) => {
4243
4155
  if (value.level !== "customer" && !value.parent) {
4244
4156
  ctx.addIssue({
4245
- code: z8.ZodIssueCode.custom,
4157
+ code: z7.ZodIssueCode.custom,
4246
4158
  path: ["parent"],
4247
4159
  message: `parent is required for a ${value.level}-level asset link (--parent-ref)`
4248
4160
  });
4249
4161
  }
4250
4162
  });
4251
- var assetGroupCreateSchema = z8.object({
4163
+ var assetGroupCreateSchema = z7.object({
4252
4164
  campaign: refSchema,
4253
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax),
4254
- finalUrls: z8.array(httpsUrlSchema2).min(1),
4255
- headlines: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.headlineTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.headlinesMin).max(GOOGLE_ADS_LIMITS.assetGroup.headlinesMax),
4256
- longHeadlines: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.longHeadlineTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.longHeadlinesMin).max(GOOGLE_ADS_LIMITS.assetGroup.longHeadlinesMax),
4257
- descriptions: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.descriptionTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.descriptionsMin).max(GOOGLE_ADS_LIMITS.assetGroup.descriptionsMax),
4258
- businessName: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.businessNameMax),
4259
- imageAssets: z8.array(refSchema).optional(),
4260
- squareImageAssets: z8.array(refSchema).optional(),
4261
- logoAssets: z8.array(refSchema).optional(),
4262
- status: z8.enum(["ENABLED", "PAUSED"]).default("PAUSED")
4263
- });
4264
- var assetGroupUpdateSchema = z8.object({
4265
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax).optional(),
4266
- finalUrls: z8.array(httpsUrlSchema2).min(1).optional(),
4267
- status: z8.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
4165
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax),
4166
+ finalUrls: z7.array(httpsUrlSchema2).min(1),
4167
+ headlines: z7.array(z7.object({ text: z7.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.headlineTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.headlinesMin).max(GOOGLE_ADS_LIMITS.assetGroup.headlinesMax),
4168
+ longHeadlines: z7.array(z7.object({ text: z7.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.longHeadlineTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.longHeadlinesMin).max(GOOGLE_ADS_LIMITS.assetGroup.longHeadlinesMax),
4169
+ descriptions: z7.array(z7.object({ text: z7.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.descriptionTextMax) })).min(GOOGLE_ADS_LIMITS.assetGroup.descriptionsMin).max(GOOGLE_ADS_LIMITS.assetGroup.descriptionsMax),
4170
+ businessName: z7.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.businessNameMax),
4171
+ imageAssets: z7.array(refSchema).optional(),
4172
+ squareImageAssets: z7.array(refSchema).optional(),
4173
+ logoAssets: z7.array(refSchema).optional(),
4174
+ status: z7.enum(["ENABLED", "PAUSED"]).default("PAUSED")
4175
+ });
4176
+ var assetGroupUpdateSchema = z7.object({
4177
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax).optional(),
4178
+ finalUrls: z7.array(httpsUrlSchema2).min(1).optional(),
4179
+ status: z7.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
4268
4180
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4269
- var audienceCreateSchema2 = z8.object({
4270
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.audience.nameMax),
4271
- type: z8.enum(USER_LIST_TYPES).default("BASIC"),
4272
- description: z8.string().optional(),
4181
+ var audienceCreateSchema2 = z7.object({
4182
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.audience.nameMax),
4183
+ type: z7.enum(USER_LIST_TYPES).default("BASIC"),
4184
+ description: z7.string().optional(),
4273
4185
  /** Customer-match members (crm-based) — file-first for large lists. */
4274
- members: z8.array(z8.record(z8.string(), z8.string())).optional(),
4275
- sourceFileRef: z8.string().optional()
4186
+ members: z7.array(z7.record(z7.string(), z7.string())).optional(),
4187
+ sourceFileRef: z7.string().optional()
4276
4188
  });
4277
- var audienceCriterionAttachSchema = z8.object({
4278
- level: z8.enum(["campaign", "adGroup"]),
4189
+ var audienceCriterionAttachSchema = z7.object({
4190
+ level: z7.enum(["campaign", "adGroup"]),
4279
4191
  parent: refSchema,
4280
4192
  userList: refSchema,
4281
- negative: z8.boolean().default(false)
4193
+ negative: z7.boolean().default(false)
4282
4194
  });
4283
- var conversionActionCreateSchema = z8.object({
4284
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.conversionAction.nameMax),
4285
- type: z8.enum(CONVERSION_ACTION_TYPES).default("WEBPAGE"),
4286
- category: z8.enum(CONVERSION_ACTION_CATEGORIES).default("DEFAULT"),
4287
- countingType: z8.enum(CONVERSION_COUNTING_TYPES).default("ONE_PER_CLICK"),
4195
+ var conversionActionCreateSchema = z7.object({
4196
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.conversionAction.nameMax),
4197
+ type: z7.enum(CONVERSION_ACTION_TYPES).default("WEBPAGE"),
4198
+ category: z7.enum(CONVERSION_ACTION_CATEGORIES).default("DEFAULT"),
4199
+ countingType: z7.enum(CONVERSION_COUNTING_TYPES).default("ONE_PER_CLICK"),
4288
4200
  defaultValueMicros: microsSchema.optional(),
4289
- defaultCurrencyCode: z8.string().length(3).optional(),
4290
- clickThroughLookbackWindowDays: z8.number().int().positive().optional(),
4291
- viewThroughLookbackWindowDays: z8.number().int().positive().optional(),
4292
- status: z8.enum(["ENABLED", "PAUSED"]).default("ENABLED")
4293
- });
4294
- var conversionActionUpdateSchema = z8.object({
4295
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.conversionAction.nameMax).optional(),
4296
- category: z8.enum(CONVERSION_ACTION_CATEGORIES).optional(),
4297
- countingType: z8.enum(CONVERSION_COUNTING_TYPES).optional(),
4201
+ defaultCurrencyCode: z7.string().length(3).optional(),
4202
+ clickThroughLookbackWindowDays: z7.number().int().positive().optional(),
4203
+ viewThroughLookbackWindowDays: z7.number().int().positive().optional(),
4204
+ status: z7.enum(["ENABLED", "PAUSED"]).default("ENABLED")
4205
+ });
4206
+ var conversionActionUpdateSchema = z7.object({
4207
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.conversionAction.nameMax).optional(),
4208
+ category: z7.enum(CONVERSION_ACTION_CATEGORIES).optional(),
4209
+ countingType: z7.enum(CONVERSION_COUNTING_TYPES).optional(),
4298
4210
  defaultValueMicros: microsSchema.optional(),
4299
- defaultCurrencyCode: z8.string().length(3).optional(),
4300
- clickThroughLookbackWindowDays: z8.number().int().positive().optional(),
4301
- viewThroughLookbackWindowDays: z8.number().int().positive().optional(),
4302
- status: z8.enum(["ENABLED", "REMOVED", "HIDDEN"]).optional()
4211
+ defaultCurrencyCode: z7.string().length(3).optional(),
4212
+ clickThroughLookbackWindowDays: z7.number().int().positive().optional(),
4213
+ viewThroughLookbackWindowDays: z7.number().int().positive().optional(),
4214
+ status: z7.enum(["ENABLED", "REMOVED", "HIDDEN"]).optional()
4303
4215
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4304
- var biddingStrategyCreateSchema = z8.object({
4305
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.biddingStrategy.nameMax),
4216
+ var biddingStrategyCreateSchema = z7.object({
4217
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.biddingStrategy.nameMax),
4306
4218
  config: biddingConfigSchema
4307
4219
  }).superRefine((p, ctx) => {
4308
4220
  if (p.config.type === "MANUAL_CPC") {
4309
4221
  ctx.addIssue({ code: "custom", path: ["config", "type"], message: "portfolio strategies cannot be Manual CPC" });
4310
4222
  }
4311
4223
  });
4312
- var biddingStrategyUpdateSchema = z8.object({
4313
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.biddingStrategy.nameMax).optional(),
4224
+ var biddingStrategyUpdateSchema = z7.object({
4225
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.biddingStrategy.nameMax).optional(),
4314
4226
  config: biddingConfigSchema.optional()
4315
4227
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
4316
- var labelCreateSchema = z8.object({
4317
- name: z8.string().min(1).max(GOOGLE_ADS_LIMITS.label.nameMax),
4318
- backgroundColor: z8.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
4319
- description: z8.string().optional()
4228
+ var labelCreateSchema = z7.object({
4229
+ name: z7.string().min(1).max(GOOGLE_ADS_LIMITS.label.nameMax),
4230
+ backgroundColor: z7.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
4231
+ description: z7.string().optional()
4320
4232
  });
4321
- var labelAttachSchema = z8.object({
4322
- level: z8.enum(["campaign", "adGroup", "ad"]),
4233
+ var labelAttachSchema = z7.object({
4234
+ level: z7.enum(["campaign", "adGroup", "ad"]),
4323
4235
  parent: refSchema,
4324
4236
  label: refSchema
4325
4237
  });
4326
- var locationCriterionSchema = z8.object({
4327
- criterionType: z8.literal("location"),
4328
- geoTargetConstant: z8.union([z8.string().regex(GEO_TARGET_CONSTANT_REGEX), z8.string().regex(NUMERIC_ID_REGEX2)])
4329
- });
4330
- var languageCriterionSchema = z8.object({
4331
- criterionType: z8.literal("language"),
4332
- languageConstant: z8.union([z8.string().regex(LANGUAGE_CONSTANT_REGEX), z8.string().regex(NUMERIC_ID_REGEX2)])
4333
- });
4334
- var adScheduleCriterionSchema = z8.object({
4335
- criterionType: z8.literal("adSchedule"),
4336
- dayOfWeek: z8.enum(DAYS_OF_WEEK),
4337
- startHour: z8.number().int().min(0).max(23),
4338
- startMinute: z8.enum(["ZERO", "FIFTEEN", "THIRTY", "FORTY_FIVE"]).default("ZERO"),
4339
- endHour: z8.number().int().min(0).max(24),
4340
- endMinute: z8.enum(["ZERO", "FIFTEEN", "THIRTY", "FORTY_FIVE"]).default("ZERO")
4341
- });
4342
- var deviceCriterionSchema = z8.object({
4343
- criterionType: z8.literal("device"),
4344
- device: z8.enum(DEVICE_TYPES),
4238
+ var locationCriterionSchema = z7.object({
4239
+ criterionType: z7.literal("location"),
4240
+ geoTargetConstant: z7.union([z7.string().regex(GEO_TARGET_CONSTANT_REGEX), z7.string().regex(NUMERIC_ID_REGEX2)])
4241
+ });
4242
+ var languageCriterionSchema = z7.object({
4243
+ criterionType: z7.literal("language"),
4244
+ languageConstant: z7.union([z7.string().regex(LANGUAGE_CONSTANT_REGEX), z7.string().regex(NUMERIC_ID_REGEX2)])
4245
+ });
4246
+ var adScheduleCriterionSchema = z7.object({
4247
+ criterionType: z7.literal("adSchedule"),
4248
+ dayOfWeek: z7.enum(DAYS_OF_WEEK),
4249
+ startHour: z7.number().int().min(0).max(23),
4250
+ startMinute: z7.enum(["ZERO", "FIFTEEN", "THIRTY", "FORTY_FIVE"]).default("ZERO"),
4251
+ endHour: z7.number().int().min(0).max(24),
4252
+ endMinute: z7.enum(["ZERO", "FIFTEEN", "THIRTY", "FORTY_FIVE"]).default("ZERO")
4253
+ });
4254
+ var deviceCriterionSchema = z7.object({
4255
+ criterionType: z7.literal("device"),
4256
+ device: z7.enum(DEVICE_TYPES),
4345
4257
  // Google's `CampaignCriterion.bid_modifier`: "The modifier must be in the range 0.1 - 10.0. Use 0
4346
4258
  // to opt out of a Device type." So 0 (exclude the device) and 0.1–10.0 are valid; the (0, 0.1) gap is not.
4347
- bidModifier: z8.number().min(0).max(10).optional().refine((v) => v === void 0 || v === 0 || v >= 0.1, {
4259
+ bidModifier: z7.number().min(0).max(10).optional().refine((v) => v === void 0 || v === 0 || v >= 0.1, {
4348
4260
  message: "bid modifier must be 0 (exclude the device) or between 0.1 and 10.0"
4349
4261
  })
4350
4262
  });
4351
- var campaignCriterionAddSchema = z8.object({
4263
+ var campaignCriterionAddSchema = z7.object({
4352
4264
  campaign: refSchema,
4353
- negative: z8.boolean().default(false),
4354
- criterion: z8.discriminatedUnion("criterionType", [
4265
+ negative: z7.boolean().default(false),
4266
+ criterion: z7.discriminatedUnion("criterionType", [
4355
4267
  locationCriterionSchema,
4356
4268
  languageCriterionSchema,
4357
4269
  adScheduleCriterionSchema,
@@ -4361,7 +4273,7 @@ var campaignCriterionAddSchema = z8.object({
4361
4273
  const c = val.criterion;
4362
4274
  if (c.criterionType === "adSchedule" && c.endHour === 24 && c.endMinute !== "ZERO") {
4363
4275
  ctx.addIssue({
4364
- code: z8.ZodIssueCode.custom,
4276
+ code: z7.ZodIssueCode.custom,
4365
4277
  message: "endHour 24 (midnight) cannot have a non-zero endMinute",
4366
4278
  path: ["criterion", "endMinute"]
4367
4279
  });
@@ -4413,17 +4325,17 @@ var GOOGLE_DRAFT_OP_KINDS = [
4413
4325
  "google.campaignCriterion.add",
4414
4326
  "google.campaignCriterion.remove"
4415
4327
  ];
4416
- var googleDraftOpKindSchema = z8.enum(GOOGLE_DRAFT_OP_KINDS);
4328
+ var googleDraftOpKindSchema = z7.enum(GOOGLE_DRAFT_OP_KINDS);
4417
4329
  function createOp2(kind, payload) {
4418
- return z8.object({ kind: z8.literal(kind), customerId: customerIdSchema, payload });
4330
+ return z7.object({ kind: z7.literal(kind), customerId: customerIdSchema, payload });
4419
4331
  }
4420
4332
  function updateOp2(kind, payload) {
4421
- return z8.object({ kind: z8.literal(kind), customerId: customerIdSchema, target: targetRefSchema, payload });
4333
+ return z7.object({ kind: z7.literal(kind), customerId: customerIdSchema, target: targetRefSchema, payload });
4422
4334
  }
4423
4335
  function targetOp(kind) {
4424
- return z8.object({ kind: z8.literal(kind), customerId: customerIdSchema, target: targetRefSchema });
4336
+ return z7.object({ kind: z7.literal(kind), customerId: customerIdSchema, target: targetRefSchema });
4425
4337
  }
4426
- var googleDraftOpInputSchema = z8.discriminatedUnion("kind", [
4338
+ var googleDraftOpInputSchema = z7.discriminatedUnion("kind", [
4427
4339
  createOp2("google.budget.create", budgetCreateSchema),
4428
4340
  updateOp2("google.budget.update", budgetUpdateSchema),
4429
4341
  createOp2("google.campaign.create", campaignCreateSchema2),
@@ -4471,132 +4383,132 @@ var googleDraftOpInputSchema = z8.discriminatedUnion("kind", [
4471
4383
  ]);
4472
4384
 
4473
4385
  // ../api/src/ads-google/wire.ts
4474
- import { z as z9 } from "zod";
4475
- var googleWriteModeSchema = z9.enum(["live", "simulated"]);
4476
- var googleDraftOpResultSchema = z9.object({
4477
- status: z9.enum(["applied", "simulated", "failed", "skipped"]),
4478
- resourceName: z9.string().optional(),
4479
- error: z9.string().optional(),
4480
- skippedBecause: z9.string().optional(),
4481
- executedAt: z9.number().optional()
4482
- });
4483
- var googleDraftStageRequestSchema = z9.object({
4484
- chatId: z9.string(),
4386
+ import { z as z8 } from "zod";
4387
+ var googleWriteModeSchema = z8.enum(["live", "simulated"]);
4388
+ var googleDraftOpResultSchema = z8.object({
4389
+ status: z8.enum(["applied", "simulated", "failed", "skipped"]),
4390
+ resourceName: z8.string().optional(),
4391
+ error: z8.string().optional(),
4392
+ skippedBecause: z8.string().optional(),
4393
+ executedAt: z8.number().optional()
4394
+ });
4395
+ var googleDraftStageRequestSchema = z8.object({
4396
+ chatId: z8.string(),
4485
4397
  op: googleDraftOpInputSchema
4486
4398
  });
4487
- var googleDraftStageResponseSchema = z9.object({
4488
- staged: z9.literal(true),
4489
- ref: z9.string(),
4399
+ var googleDraftStageResponseSchema = z8.object({
4400
+ staged: z8.literal(true),
4401
+ ref: z8.string(),
4490
4402
  kind: googleDraftOpKindSchema,
4491
4403
  mode: googleWriteModeSchema,
4492
- dependsOn: z9.array(z9.string()),
4493
- summary: z9.string(),
4494
- warnings: z9.array(z9.string()),
4404
+ dependsOn: z8.array(z8.string()),
4405
+ summary: z8.string(),
4406
+ warnings: z8.array(z8.string()),
4495
4407
  /** True when the op amended an already-staged op in place instead of appending a new one. */
4496
- amended: z9.boolean().optional()
4408
+ amended: z8.boolean().optional()
4497
4409
  });
4498
- var googleDraftAmendRequestSchema = z9.object({
4499
- chatId: z9.string(),
4500
- ref: z9.string(),
4501
- patch: z9.record(z9.string(), z9.unknown())
4410
+ var googleDraftAmendRequestSchema = z8.object({
4411
+ chatId: z8.string(),
4412
+ ref: z8.string(),
4413
+ patch: z8.record(z8.string(), z8.unknown())
4502
4414
  });
4503
- var googleDraftShowRequestSchema = z9.object({
4504
- chatId: z9.string(),
4505
- ref: z9.string()
4415
+ var googleDraftShowRequestSchema = z8.object({
4416
+ chatId: z8.string(),
4417
+ ref: z8.string()
4506
4418
  });
4507
4419
  var GOOGLE_DRAFT_BATCH_MAX = 500;
4508
- var googleDraftStageBatchRequestSchema = z9.object({
4509
- chatId: z9.string(),
4510
- ops: z9.array(googleDraftOpInputSchema).min(1).max(GOOGLE_DRAFT_BATCH_MAX)
4420
+ var googleDraftStageBatchRequestSchema = z8.object({
4421
+ chatId: z8.string(),
4422
+ ops: z8.array(googleDraftOpInputSchema).min(1).max(GOOGLE_DRAFT_BATCH_MAX)
4511
4423
  });
4512
- var googleDraftStageBatchResponseSchema = z9.object({
4513
- staged: z9.literal(true),
4424
+ var googleDraftStageBatchResponseSchema = z8.object({
4425
+ staged: z8.literal(true),
4514
4426
  mode: googleWriteModeSchema,
4515
- count: z9.number(),
4516
- ops: z9.array(
4517
- z9.object({
4518
- ref: z9.string(),
4427
+ count: z8.number(),
4428
+ ops: z8.array(
4429
+ z8.object({
4430
+ ref: z8.string(),
4519
4431
  kind: googleDraftOpKindSchema,
4520
- dependsOn: z9.array(z9.string()),
4521
- summary: z9.string(),
4522
- warnings: z9.array(z9.string())
4432
+ dependsOn: z8.array(z8.string()),
4433
+ summary: z8.string(),
4434
+ warnings: z8.array(z8.string())
4523
4435
  })
4524
4436
  )
4525
4437
  });
4526
- var googleDraftOpViewSchema = z9.object({
4527
- ref: z9.string(),
4438
+ var googleDraftOpViewSchema = z8.object({
4439
+ ref: z8.string(),
4528
4440
  kind: googleDraftOpKindSchema,
4529
- customerId: z9.string(),
4530
- target: z9.string().optional(),
4531
- dependsOn: z9.array(z9.string()),
4532
- summary: z9.string(),
4533
- stagedAt: z9.number(),
4441
+ customerId: z8.string(),
4442
+ target: z8.string().optional(),
4443
+ dependsOn: z8.array(z8.string()),
4444
+ summary: z8.string(),
4445
+ stagedAt: z8.number(),
4534
4446
  result: googleDraftOpResultSchema.optional()
4535
4447
  });
4536
- var googleDraftShowResponseSchema = z9.object({
4448
+ var googleDraftShowResponseSchema = z8.object({
4537
4449
  op: googleDraftOpViewSchema.extend({
4538
- payload: z9.unknown().optional(),
4539
- warnings: z9.array(z9.string()).optional(),
4540
- annotations: z9.unknown().optional()
4450
+ payload: z8.unknown().optional(),
4451
+ warnings: z8.array(z8.string()).optional(),
4452
+ annotations: z8.unknown().optional()
4541
4453
  })
4542
4454
  });
4543
- var googleDraftListRequestSchema = z9.object({
4544
- chatId: z9.string()
4455
+ var googleDraftListRequestSchema = z8.object({
4456
+ chatId: z8.string()
4545
4457
  });
4546
- var googleDraftAdvisorySchema = z9.object({
4547
- scope: z9.enum(["campaign", "adGroup"]),
4548
- message: z9.string()
4458
+ var googleDraftAdvisorySchema = z8.object({
4459
+ scope: z8.enum(["campaign", "adGroup"]),
4460
+ message: z8.string()
4549
4461
  });
4550
- var googleDraftStatusCollectionSchema = z9.object({
4551
- label: z9.string(),
4552
- added: z9.number(),
4553
- removed: z9.number(),
4554
- existing: z9.number()
4462
+ var googleDraftStatusCollectionSchema = z8.object({
4463
+ label: z8.string(),
4464
+ added: z8.number(),
4465
+ removed: z8.number(),
4466
+ existing: z8.number()
4555
4467
  });
4556
- var googleDraftChangeOperationSchema = z9.enum(["create", "update", "pause", "resume", "remove"]);
4557
- var googleDraftStatusNodeSchema = z9.lazy(
4558
- () => z9.object({
4559
- entity: z9.string(),
4560
- name: z9.string(),
4468
+ var googleDraftChangeOperationSchema = z8.enum(["create", "update", "pause", "resume", "remove"]);
4469
+ var googleDraftStatusNodeSchema = z8.lazy(
4470
+ () => z8.object({
4471
+ entity: z8.string(),
4472
+ name: z8.string(),
4561
4473
  operation: googleDraftChangeOperationSchema.optional(),
4562
- existing: z9.boolean(),
4563
- collections: z9.array(googleDraftStatusCollectionSchema),
4564
- children: z9.array(googleDraftStatusNodeSchema),
4565
- warnings: z9.array(z9.string()).optional()
4474
+ existing: z8.boolean(),
4475
+ collections: z8.array(googleDraftStatusCollectionSchema),
4476
+ children: z8.array(googleDraftStatusNodeSchema),
4477
+ warnings: z8.array(z8.string()).optional()
4566
4478
  })
4567
4479
  );
4568
- var googleDraftListResponseSchema = z9.object({
4569
- status: z9.enum(["active", "publishing", "applied", "discarded", "none"]),
4480
+ var googleDraftListResponseSchema = z8.object({
4481
+ status: z8.enum(["active", "publishing", "applied", "discarded", "none"]),
4570
4482
  mode: googleWriteModeSchema,
4571
- count: z9.number(),
4572
- ops: z9.array(googleDraftOpViewSchema),
4483
+ count: z8.number(),
4484
+ ops: z8.array(googleDraftOpViewSchema),
4573
4485
  /** Grouped campaign ▸ ad group ▸ ad tree for the readable CLI status view. */
4574
- tree: z9.array(googleDraftStatusNodeSchema).optional(),
4486
+ tree: z8.array(googleDraftStatusNodeSchema).optional(),
4575
4487
  /** Non-blocking completeness advisories for the whole draft. */
4576
- advisories: z9.array(googleDraftAdvisorySchema).optional()
4488
+ advisories: z8.array(googleDraftAdvisorySchema).optional()
4577
4489
  });
4578
- var googleDraftRemoveRequestSchema = z9.object({
4579
- chatId: z9.string(),
4580
- ref: z9.string()
4490
+ var googleDraftRemoveRequestSchema = z8.object({
4491
+ chatId: z8.string(),
4492
+ ref: z8.string()
4581
4493
  });
4582
- var googleDraftRemoveResponseSchema = z9.object({
4494
+ var googleDraftRemoveResponseSchema = z8.object({
4583
4495
  /** The requested ref plus any dependents removed by cascade. */
4584
- removed: z9.array(z9.string())
4496
+ removed: z8.array(z8.string())
4585
4497
  });
4586
- var googleDraftClearRequestSchema = z9.object({
4587
- chatId: z9.string()
4498
+ var googleDraftClearRequestSchema = z8.object({
4499
+ chatId: z8.string()
4588
4500
  });
4589
- var googleDraftClearResponseSchema = z9.object({
4590
- cleared: z9.number()
4501
+ var googleDraftClearResponseSchema = z8.object({
4502
+ cleared: z8.number()
4591
4503
  });
4592
- var googleFieldErrorSchema = z9.object({
4593
- path: z9.string(),
4594
- message: z9.string()
4504
+ var googleFieldErrorSchema = z8.object({
4505
+ path: z8.string(),
4506
+ message: z8.string()
4595
4507
  });
4596
- var googleDraftErrorResponseSchema = z9.object({
4597
- code: z9.string(),
4598
- error: z9.string(),
4599
- fields: z9.array(googleFieldErrorSchema).optional()
4508
+ var googleDraftErrorResponseSchema = z8.object({
4509
+ code: z8.string(),
4510
+ error: z8.string(),
4511
+ fields: z8.array(googleFieldErrorSchema).optional()
4600
4512
  });
4601
4513
 
4602
4514
  // src/commands/ads/google/draft-status.ts
@@ -4761,11 +4673,11 @@ function rawTextEntries(value) {
4761
4673
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
4762
4674
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
4763
4675
  }
4764
- function rawFileEntries(path12) {
4765
- if (typeof path12 !== "string" || path12.length === 0) {
4676
+ function rawFileEntries(path15) {
4677
+ if (typeof path15 !== "string" || path15.length === 0) {
4766
4678
  return [];
4767
4679
  }
4768
- return readFileSync2(path12, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
4680
+ return readFileSync2(path15, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
4769
4681
  }
4770
4682
  function keywordEntries(args) {
4771
4683
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -4788,19 +4700,19 @@ function keywordEntries(args) {
4788
4700
  }
4789
4701
  return entries;
4790
4702
  }
4791
- function loadJsonFileArg(path12) {
4792
- if (typeof path12 !== "string" || path12.length === 0) {
4703
+ function loadJsonFileArg(path15) {
4704
+ if (typeof path15 !== "string" || path15.length === 0) {
4793
4705
  return {};
4794
4706
  }
4795
4707
  try {
4796
- const parsed = JSON.parse(readFileSync2(path12, "utf8"));
4708
+ const parsed = JSON.parse(readFileSync2(path15, "utf8"));
4797
4709
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4798
- failWriteValidation(`${path12} must contain a JSON object`);
4710
+ failWriteValidation(`${path15} must contain a JSON object`);
4799
4711
  }
4800
4712
  return parsed;
4801
4713
  } catch (err) {
4802
4714
  if (err instanceof SyntaxError) {
4803
- failWriteValidation(`${path12} is not valid JSON: ${err.message}`);
4715
+ failWriteValidation(`${path15} is not valid JSON: ${err.message}`);
4804
4716
  }
4805
4717
  throw err;
4806
4718
  }
@@ -4911,10 +4823,10 @@ async function stageUpdate(kind, customerId, target, payload) {
4911
4823
  async function stageTarget(kind, customerId, target) {
4912
4824
  await stageGoogleOp({ kind, customerId, target });
4913
4825
  }
4914
- async function draftAction(path12, body) {
4826
+ async function draftAction(path15, body) {
4915
4827
  try {
4916
4828
  const chatId = requireChatId();
4917
- const response = await apiPost(path12, { chatId, ...body });
4829
+ const response = await apiPost(path15, { chatId, ...body });
4918
4830
  writeJsonEnvelope(response);
4919
4831
  } catch (err) {
4920
4832
  handleGoogleError(err);
@@ -8669,19 +8581,19 @@ function failWriteValidation2(message) {
8669
8581
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
8670
8582
  process.exit(1);
8671
8583
  }
8672
- function loadJsonFileArg2(path12) {
8673
- if (typeof path12 !== "string" || path12.length === 0) {
8584
+ function loadJsonFileArg2(path15) {
8585
+ if (typeof path15 !== "string" || path15.length === 0) {
8674
8586
  return {};
8675
8587
  }
8676
8588
  try {
8677
- const parsed = JSON.parse(readFileSync6(path12, "utf8"));
8589
+ const parsed = JSON.parse(readFileSync6(path15, "utf8"));
8678
8590
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8679
- failWriteValidation2(`${path12} must contain a JSON object`);
8591
+ failWriteValidation2(`${path15} must contain a JSON object`);
8680
8592
  }
8681
8593
  return parsed;
8682
8594
  } catch (err) {
8683
8595
  if (err instanceof SyntaxError) {
8684
- failWriteValidation2(`${path12} is not valid JSON: ${err.message}`);
8596
+ failWriteValidation2(`${path15} is not valid JSON: ${err.message}`);
8685
8597
  }
8686
8598
  throw err;
8687
8599
  }
@@ -8766,15 +8678,15 @@ function parseLocaleFlag(value) {
8766
8678
  }
8767
8679
  return { language: match[1], country: match[2].toUpperCase() };
8768
8680
  }
8769
- function loadTargetingFileArg(path12) {
8770
- if (typeof path12 !== "string" || path12.length === 0) {
8681
+ function loadTargetingFileArg(path15) {
8682
+ if (typeof path15 !== "string" || path15.length === 0) {
8771
8683
  return void 0;
8772
8684
  }
8773
- const parsed = loadJsonFileArg2(path12);
8685
+ const parsed = loadJsonFileArg2(path15);
8774
8686
  const criteria = parsed.targetingCriteria ?? parsed;
8775
8687
  if (!criteria.include) {
8776
8688
  failWriteValidation2(
8777
- `${path12} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
8689
+ `${path15} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
8778
8690
  );
8779
8691
  }
8780
8692
  return criteria;
@@ -8809,14 +8721,14 @@ function parseCsvLine(line) {
8809
8721
  cells.push(current);
8810
8722
  return cells.map((cell) => cell.trim());
8811
8723
  }
8812
- function parseListFileArg(path12, maxRows) {
8813
- if (typeof path12 !== "string" || path12.length === 0) {
8724
+ function parseListFileArg(path15, maxRows) {
8725
+ if (typeof path15 !== "string" || path15.length === 0) {
8814
8726
  return void 0;
8815
8727
  }
8816
- const raw = readFileSync6(path12, "utf8");
8728
+ const raw = readFileSync6(path15, "utf8");
8817
8729
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
8818
8730
  if (lines.length < 2) {
8819
- failWriteValidation2(`${path12} needs a header row and at least one data row`);
8731
+ failWriteValidation2(`${path15} needs a header row and at least one data row`);
8820
8732
  }
8821
8733
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
8822
8734
  const rows = [];
@@ -8835,7 +8747,7 @@ function parseListFileArg(path12, maxRows) {
8835
8747
  }
8836
8748
  }
8837
8749
  if (rows.length > maxRows) {
8838
- failWriteValidation2(`${path12} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
8750
+ failWriteValidation2(`${path15} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
8839
8751
  }
8840
8752
  return { columns, rows };
8841
8753
  }
@@ -9480,7 +9392,7 @@ var leadFormsCreateCommand = defineCommand38({
9480
9392
  Required: name, headline (\u226460), privacyPolicyUrl, questions[] (\u226412; playbook: \u22644 for completion).
9481
9393
  Each question is a predefined profile field ({ name, predefinedField: "EMAIL" }) or a custom question ({ name, questionType: "MULTIPLE_CHOICE", options: [...] }; \u22643 custom).
9482
9394
  Best-practice fields the preview will nudge for if missing: 1-3 qualifying questions, consents[] (disclosure checkboxes), thankYou.message + thankYou.landingUrl|appointmentUrl.
9483
- Also supported: locale, formImageId|formImageUrn, hiddenFields[], legalDisclaimer, thankYou.cta. Example: baker ads linkedin lead-forms create --file form.json`
9395
+ Also supported: locale, formImageId|formImageUrn, privacyPolicyText, hiddenFields[], legalDisclaimer, thankYou.cta. Example: baker ads linkedin lead-forms create --file form.json`
9484
9396
  },
9485
9397
  args: {
9486
9398
  ...accountArgs,
@@ -10904,72 +10816,72 @@ var NUMERIC_ID_REGEX3 = /^\d+$/;
10904
10816
  var IMAGE_HASH_REGEX = /^[A-Fa-f0-9]{16,}$/;
10905
10817
 
10906
10818
  // ../api/src/ads-meta/ops.ts
10907
- import { z as z10 } from "zod";
10908
- var tempRefSchema3 = z10.string().regex(TEMP_REF_REGEX3, "expected a meta_temp_* reference");
10909
- var parentRefSchema2 = z10.union([z10.string().regex(NUMERIC_ID_REGEX3, "expected a numeric id"), tempRefSchema3]);
10910
- var moneySchema2 = z10.object({
10911
- amount: z10.string().regex(/^\d+(\.\d{1,2})?$/, "expected a decimal amount like 50 or 50.00").refine((val) => Number(val) > 0, "amount must be greater than zero"),
10912
- currencyCode: z10.string().length(3).optional()
10913
- });
10914
- var httpsUrlSchema3 = z10.string().url().refine((u) => u.startsWith("https://"), "destination URLs must be https");
10915
- var bakerMediaIdSchema2 = z10.string().min(1);
10916
- var stageableStatusSchema3 = z10.enum(STAGEABLE_CREATE_STATUSES3);
10917
- var updateStatusSchema = z10.enum(UPDATE_STATUSES);
10819
+ import { z as z9 } from "zod";
10820
+ var tempRefSchema3 = z9.string().regex(TEMP_REF_REGEX3, "expected a meta_temp_* reference");
10821
+ var parentRefSchema2 = z9.union([z9.string().regex(NUMERIC_ID_REGEX3, "expected a numeric id"), tempRefSchema3]);
10822
+ var moneySchema2 = z9.object({
10823
+ amount: z9.string().regex(/^\d+(\.\d{1,2})?$/, "expected a decimal amount like 50 or 50.00").refine((val) => Number(val) > 0, "amount must be greater than zero"),
10824
+ currencyCode: z9.string().length(3).optional()
10825
+ });
10826
+ var httpsUrlSchema3 = z9.string().url().refine((u) => u.startsWith("https://"), "destination URLs must be https");
10827
+ var bakerMediaIdSchema2 = z9.string().min(1);
10828
+ var stageableStatusSchema3 = z9.enum(STAGEABLE_CREATE_STATUSES3);
10829
+ var updateStatusSchema = z9.enum(UPDATE_STATUSES);
10918
10830
  function currencyMinimums2(currencyCode) {
10919
10831
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
10920
10832
  }
10921
- function validateDailyBudgetFloor(money, ctx, path12) {
10833
+ function validateDailyBudgetFloor(money, ctx, path15) {
10922
10834
  if (money?.currencyCode) {
10923
10835
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
10924
10836
  if (Number(money.amount) < min) {
10925
- ctx.addIssue({ code: "custom", path: path12, message: `below the ${min} ${money.currencyCode} daily minimum` });
10837
+ ctx.addIssue({ code: "custom", path: path15, message: `below the ${min} ${money.currencyCode} daily minimum` });
10926
10838
  }
10927
10839
  }
10928
10840
  }
10929
- var geoLocationsSchema = z10.object({
10930
- countries: z10.array(z10.string().length(2)).optional(),
10931
- regions: z10.array(z10.object({ key: z10.string() })).optional(),
10932
- cities: z10.array(z10.object({ key: z10.string(), radius: z10.number().optional(), distance_unit: z10.string().optional() })).optional(),
10933
- zips: z10.array(z10.object({ key: z10.string() })).optional(),
10934
- location_types: z10.array(z10.string()).optional()
10935
- }).catchall(z10.unknown());
10936
- var idNameSchema = z10.object({ id: z10.string(), name: z10.string().optional() });
10937
- var metaTargetingSchema = z10.object({
10841
+ var geoLocationsSchema = z9.object({
10842
+ countries: z9.array(z9.string().length(2)).optional(),
10843
+ regions: z9.array(z9.object({ key: z9.string() })).optional(),
10844
+ cities: z9.array(z9.object({ key: z9.string(), radius: z9.number().optional(), distance_unit: z9.string().optional() })).optional(),
10845
+ zips: z9.array(z9.object({ key: z9.string() })).optional(),
10846
+ location_types: z9.array(z9.string()).optional()
10847
+ }).catchall(z9.unknown());
10848
+ var idNameSchema = z9.object({ id: z9.string(), name: z9.string().optional() });
10849
+ var metaTargetingSchema = z9.object({
10938
10850
  geo_locations: geoLocationsSchema.optional(),
10939
10851
  excluded_geo_locations: geoLocationsSchema.optional(),
10940
- age_min: z10.number().int().min(13).max(65).optional(),
10941
- age_max: z10.number().int().min(13).max(65).optional(),
10942
- genders: z10.array(z10.union([z10.literal(1), z10.literal(2)])).optional(),
10943
- locales: z10.array(z10.number().int()).optional(),
10944
- interests: z10.array(idNameSchema).optional(),
10945
- behaviors: z10.array(idNameSchema).optional(),
10946
- custom_audiences: z10.array(z10.object({ id: parentRefSchema2 })).optional(),
10947
- excluded_custom_audiences: z10.array(z10.object({ id: parentRefSchema2 })).optional(),
10948
- flexible_spec: z10.array(z10.record(z10.string(), z10.unknown())).optional(),
10949
- exclusions: z10.record(z10.string(), z10.unknown()).optional(),
10950
- publisher_platforms: z10.array(z10.string()).optional(),
10951
- facebook_positions: z10.array(z10.string()).optional(),
10952
- instagram_positions: z10.array(z10.string()).optional(),
10953
- audience_network_positions: z10.array(z10.string()).optional(),
10954
- messenger_positions: z10.array(z10.string()).optional(),
10955
- device_platforms: z10.array(z10.string()).optional(),
10956
- targeting_automation: z10.object({ advantage_audience: z10.union([z10.literal(0), z10.literal(1)]) }).partial().optional()
10957
- }).catchall(z10.unknown());
10958
- var specialAdCategoriesSchema = z10.array(z10.enum(SPECIAL_AD_CATEGORIES)).default(["NONE"]);
10959
- var campaignCreateSchema3 = z10.object({
10960
- name: z10.string().min(1).max(META_LIMITS.campaign.nameMax),
10961
- objective: z10.enum(OBJECTIVES),
10852
+ age_min: z9.number().int().min(13).max(65).optional(),
10853
+ age_max: z9.number().int().min(13).max(65).optional(),
10854
+ genders: z9.array(z9.union([z9.literal(1), z9.literal(2)])).optional(),
10855
+ locales: z9.array(z9.number().int()).optional(),
10856
+ interests: z9.array(idNameSchema).optional(),
10857
+ behaviors: z9.array(idNameSchema).optional(),
10858
+ custom_audiences: z9.array(z9.object({ id: parentRefSchema2 })).optional(),
10859
+ excluded_custom_audiences: z9.array(z9.object({ id: parentRefSchema2 })).optional(),
10860
+ flexible_spec: z9.array(z9.record(z9.string(), z9.unknown())).optional(),
10861
+ exclusions: z9.record(z9.string(), z9.unknown()).optional(),
10862
+ publisher_platforms: z9.array(z9.string()).optional(),
10863
+ facebook_positions: z9.array(z9.string()).optional(),
10864
+ instagram_positions: z9.array(z9.string()).optional(),
10865
+ audience_network_positions: z9.array(z9.string()).optional(),
10866
+ messenger_positions: z9.array(z9.string()).optional(),
10867
+ device_platforms: z9.array(z9.string()).optional(),
10868
+ targeting_automation: z9.object({ advantage_audience: z9.union([z9.literal(0), z9.literal(1)]) }).partial().optional()
10869
+ }).catchall(z9.unknown());
10870
+ var specialAdCategoriesSchema = z9.array(z9.enum(SPECIAL_AD_CATEGORIES)).default(["NONE"]);
10871
+ var campaignCreateSchema3 = z9.object({
10872
+ name: z9.string().min(1).max(META_LIMITS.campaign.nameMax),
10873
+ objective: z9.enum(OBJECTIVES),
10962
10874
  status: stageableStatusSchema3.default("PAUSED"),
10963
10875
  special_ad_categories: specialAdCategoriesSchema,
10964
- special_ad_category_country: z10.array(z10.string().length(2)).optional(),
10965
- buying_type: z10.enum(BUYING_TYPES).default("AUCTION"),
10966
- bid_strategy: z10.enum(BID_STRATEGIES).optional(),
10876
+ special_ad_category_country: z9.array(z9.string().length(2)).optional(),
10877
+ buying_type: z9.enum(BUYING_TYPES).default("AUCTION"),
10878
+ bid_strategy: z9.enum(BID_STRATEGIES).optional(),
10967
10879
  /** Campaign Budget Optimization (Advantage campaign budget) — mutually exclusive with ad-set budgets. */
10968
10880
  dailyBudget: moneySchema2.optional(),
10969
10881
  lifetimeBudget: moneySchema2.optional(),
10970
10882
  spendCap: moneySchema2.optional(),
10971
- start_time: z10.number().int().positive().optional(),
10972
- stop_time: z10.number().int().positive().optional()
10883
+ start_time: z9.number().int().positive().optional(),
10884
+ stop_time: z9.number().int().positive().optional()
10973
10885
  }).superRefine((p, ctx) => {
10974
10886
  if (p.dailyBudget && p.lifetimeBudget) {
10975
10887
  ctx.addIssue({ code: "custom", path: ["dailyBudget"], message: "set only one of dailyBudget or lifetimeBudget" });
@@ -10979,15 +10891,15 @@ var campaignCreateSchema3 = z10.object({
10979
10891
  ctx.addIssue({ code: "custom", path: ["stop_time"], message: "stop_time must be after start_time" });
10980
10892
  }
10981
10893
  });
10982
- var campaignUpdateSchema3 = z10.object({
10983
- name: z10.string().min(1).max(META_LIMITS.campaign.nameMax).optional(),
10894
+ var campaignUpdateSchema3 = z9.object({
10895
+ name: z9.string().min(1).max(META_LIMITS.campaign.nameMax).optional(),
10984
10896
  status: updateStatusSchema.optional(),
10985
- bid_strategy: z10.enum(BID_STRATEGIES).optional(),
10897
+ bid_strategy: z9.enum(BID_STRATEGIES).optional(),
10986
10898
  dailyBudget: moneySchema2.optional(),
10987
10899
  lifetimeBudget: moneySchema2.optional(),
10988
10900
  spendCap: moneySchema2.optional(),
10989
- start_time: z10.number().int().positive().optional(),
10990
- stop_time: z10.number().int().positive().optional()
10901
+ start_time: z9.number().int().positive().optional(),
10902
+ stop_time: z9.number().int().positive().optional()
10991
10903
  }).superRefine((p, ctx) => {
10992
10904
  if (!Object.values(p).some((val) => val !== void 0)) {
10993
10905
  ctx.addIssue({ code: "custom", message: "update needs at least one field" });
@@ -10997,38 +10909,38 @@ var campaignUpdateSchema3 = z10.object({
10997
10909
  }
10998
10910
  validateDailyBudgetFloor(p.dailyBudget, ctx, ["dailyBudget", "amount"]);
10999
10911
  });
11000
- var promotedObjectSchema = z10.object({
10912
+ var promotedObjectSchema = z9.object({
11001
10913
  page_id: parentRefSchema2.optional(),
11002
- pixel_id: z10.string().regex(NUMERIC_ID_REGEX3).optional(),
11003
- custom_event_type: z10.enum(CUSTOM_EVENT_TYPES).optional(),
11004
- application_id: z10.string().regex(NUMERIC_ID_REGEX3).optional(),
11005
- object_store_url: z10.string().url().optional(),
11006
- product_catalog_id: z10.string().regex(NUMERIC_ID_REGEX3).optional(),
11007
- product_set_id: z10.string().regex(NUMERIC_ID_REGEX3).optional(),
11008
- whatsapp_phone_number: z10.string().optional(),
11009
- offline_conversion_data_set_id: z10.string().regex(NUMERIC_ID_REGEX3).optional()
10914
+ pixel_id: z9.string().regex(NUMERIC_ID_REGEX3).optional(),
10915
+ custom_event_type: z9.enum(CUSTOM_EVENT_TYPES).optional(),
10916
+ application_id: z9.string().regex(NUMERIC_ID_REGEX3).optional(),
10917
+ object_store_url: z9.string().url().optional(),
10918
+ product_catalog_id: z9.string().regex(NUMERIC_ID_REGEX3).optional(),
10919
+ product_set_id: z9.string().regex(NUMERIC_ID_REGEX3).optional(),
10920
+ whatsapp_phone_number: z9.string().optional(),
10921
+ offline_conversion_data_set_id: z9.string().regex(NUMERIC_ID_REGEX3).optional()
11010
10922
  }).partial();
11011
- var attributionSpecSchema = z10.array(
11012
- z10.object({
11013
- event_type: z10.enum(ATTRIBUTION_EVENT_TYPES),
11014
- window_days: z10.union([z10.literal(1), z10.literal(7), z10.literal(28)])
10923
+ var attributionSpecSchema = z9.array(
10924
+ z9.object({
10925
+ event_type: z9.enum(ATTRIBUTION_EVENT_TYPES),
10926
+ window_days: z9.union([z9.literal(1), z9.literal(7), z9.literal(28)])
11015
10927
  })
11016
10928
  );
11017
10929
  var adSetFields = {
11018
- name: z10.string().min(1).max(META_LIMITS.adSet.nameMax),
10930
+ name: z9.string().min(1).max(META_LIMITS.adSet.nameMax),
11019
10931
  campaign_id: parentRefSchema2,
11020
10932
  status: stageableStatusSchema3.default("PAUSED"),
11021
10933
  dailyBudget: moneySchema2.optional(),
11022
10934
  lifetimeBudget: moneySchema2.optional(),
11023
10935
  bidAmount: moneySchema2.optional(),
11024
- bid_strategy: z10.enum(BID_STRATEGIES).optional(),
11025
- billing_event: z10.enum(BILLING_EVENTS),
11026
- optimization_goal: z10.enum(OPTIMIZATION_GOALS),
11027
- destination_type: z10.enum(DESTINATION_TYPES).optional(),
10936
+ bid_strategy: z9.enum(BID_STRATEGIES).optional(),
10937
+ billing_event: z9.enum(BILLING_EVENTS),
10938
+ optimization_goal: z9.enum(OPTIMIZATION_GOALS),
10939
+ destination_type: z9.enum(DESTINATION_TYPES).optional(),
11028
10940
  promoted_object: promotedObjectSchema.optional(),
11029
10941
  attribution_spec: attributionSpecSchema.optional(),
11030
- start_time: z10.number().int().positive().optional(),
11031
- end_time: z10.number().int().positive().optional(),
10942
+ start_time: z9.number().int().positive().optional(),
10943
+ end_time: z9.number().int().positive().optional(),
11032
10944
  targeting: metaTargetingSchema
11033
10945
  };
11034
10946
  function validateAdSetBudgetAndBid(p, ctx) {
@@ -11046,22 +10958,22 @@ function validateAdSetBudgetAndBid(p, ctx) {
11046
10958
  ctx.addIssue({ code: "custom", path: ["end_time"], message: "end_time must be after start_time" });
11047
10959
  }
11048
10960
  }
11049
- var adSetCreateSchema = z10.object(adSetFields).superRefine((p, ctx) => {
10961
+ var adSetCreateSchema = z9.object(adSetFields).superRefine((p, ctx) => {
11050
10962
  validateAdSetBudgetAndBid(p, ctx);
11051
10963
  });
11052
- var adSetUpdateSchema = z10.object({
10964
+ var adSetUpdateSchema = z9.object({
11053
10965
  name: adSetFields.name.optional(),
11054
10966
  status: updateStatusSchema.optional(),
11055
10967
  dailyBudget: moneySchema2.optional(),
11056
10968
  lifetimeBudget: moneySchema2.optional(),
11057
10969
  bidAmount: moneySchema2.optional(),
11058
- bid_strategy: z10.enum(BID_STRATEGIES).optional(),
11059
- optimization_goal: z10.enum(OPTIMIZATION_GOALS).optional(),
11060
- destination_type: z10.enum(DESTINATION_TYPES).optional(),
10970
+ bid_strategy: z9.enum(BID_STRATEGIES).optional(),
10971
+ optimization_goal: z9.enum(OPTIMIZATION_GOALS).optional(),
10972
+ destination_type: z9.enum(DESTINATION_TYPES).optional(),
11061
10973
  promoted_object: promotedObjectSchema.optional(),
11062
10974
  attribution_spec: attributionSpecSchema.optional(),
11063
- start_time: z10.number().int().positive().optional(),
11064
- end_time: z10.number().int().positive().optional(),
10975
+ start_time: z9.number().int().positive().optional(),
10976
+ end_time: z9.number().int().positive().optional(),
11065
10977
  targeting: metaTargetingSchema.optional()
11066
10978
  }).superRefine((p, ctx) => {
11067
10979
  if (!Object.values(p).some((val) => val !== void 0)) {
@@ -11069,38 +10981,38 @@ var adSetUpdateSchema = z10.object({
11069
10981
  }
11070
10982
  validateAdSetBudgetAndBid(p, ctx);
11071
10983
  });
11072
- var messageSchema = z10.string().min(1).max(META_LIMITS.creative.messageHardMax);
11073
- var headlineSchema2 = z10.string().min(1).max(META_LIMITS.creative.headlineMax);
11074
- var descriptionSchema = z10.string().min(1).max(META_LIMITS.creative.descriptionMax);
11075
- var callToActionSchema = z10.object({
11076
- type: z10.enum(CTA_TYPES2),
10984
+ var messageSchema = z9.string().min(1).max(META_LIMITS.creative.messageHardMax);
10985
+ var headlineSchema2 = z9.string().min(1).max(META_LIMITS.creative.headlineMax);
10986
+ var descriptionSchema = z9.string().min(1).max(META_LIMITS.creative.descriptionMax);
10987
+ var callToActionSchema = z9.object({
10988
+ type: z9.enum(CTA_TYPES2),
11077
10989
  /** Overrides the base link for the CTA button; defaults to the ad's link. */
11078
10990
  link: httpsUrlSchema3.optional()
11079
10991
  });
11080
- var creativeEnhancementsSchema = z10.object({
11081
- standardEnhancements: z10.enum(ENROLL_STATUSES).optional(),
11082
- features: z10.record(z10.string(), z10.enum(ENROLL_STATUSES)).optional()
10992
+ var creativeEnhancementsSchema = z9.object({
10993
+ standardEnhancements: z9.enum(ENROLL_STATUSES).optional(),
10994
+ features: z9.record(z9.string(), z9.enum(ENROLL_STATUSES)).optional()
11083
10995
  });
11084
10996
  var creativeSharedFields = {
11085
- name: z10.string().max(META_LIMITS.creative.nameMax).optional(),
10997
+ name: z9.string().max(META_LIMITS.creative.nameMax).optional(),
11086
10998
  /** Facebook Page id backing the ad's identity. */
11087
10999
  page_id: parentRefSchema2,
11088
11000
  /** Instagram account id for IG placements (aka instagram_actor_id on read). */
11089
- instagram_user_id: z10.string().regex(NUMERIC_ID_REGEX3).optional(),
11001
+ instagram_user_id: z9.string().regex(NUMERIC_ID_REGEX3).optional(),
11090
11002
  /** URL tracking parameters appended to the destination, e.g. "utm_source=fb&utm_campaign=x". */
11091
- url_tags: z10.string().max(1e3).optional(),
11003
+ url_tags: z9.string().max(1e3).optional(),
11092
11004
  enhancements: creativeEnhancementsSchema.optional()
11093
11005
  };
11094
11006
  var imageMediaFields = {
11095
- imageHash: z10.string().regex(IMAGE_HASH_REGEX).optional(),
11007
+ imageHash: z9.string().regex(IMAGE_HASH_REGEX).optional(),
11096
11008
  imageRef: tempRefSchema3.optional()
11097
11009
  };
11098
11010
  var videoMediaFields = {
11099
- videoId: z10.string().regex(NUMERIC_ID_REGEX3).optional(),
11011
+ videoId: z9.string().regex(NUMERIC_ID_REGEX3).optional(),
11100
11012
  videoRef: tempRefSchema3.optional(),
11101
11013
  /** Thumbnail for a video creative — image hash, ref, or public url. */
11102
- thumbnailHash: z10.string().regex(IMAGE_HASH_REGEX).optional(),
11103
- imageUrl: z10.string().url().optional()
11014
+ thumbnailHash: z9.string().regex(IMAGE_HASH_REGEX).optional(),
11015
+ imageUrl: z9.string().url().optional()
11104
11016
  };
11105
11017
  function countImageRefs(p) {
11106
11018
  return [p.imageHash, p.imageRef].filter(Boolean).length;
@@ -11108,8 +11020,8 @@ function countImageRefs(p) {
11108
11020
  function countVideoRefs(p) {
11109
11021
  return [p.videoId, p.videoRef].filter(Boolean).length;
11110
11022
  }
11111
- var singleCreativeSchema = z10.object({
11112
- creativeType: z10.literal("single"),
11023
+ var singleCreativeSchema = z9.object({
11024
+ creativeType: z9.literal("single"),
11113
11025
  ...creativeSharedFields,
11114
11026
  /** Primary text. */
11115
11027
  message: messageSchema,
@@ -11118,7 +11030,7 @@ var singleCreativeSchema = z10.object({
11118
11030
  headline: headlineSchema2.optional(),
11119
11031
  description: descriptionSchema.optional(),
11120
11032
  /** Display URL / caption shown under the headline. */
11121
- caption: z10.string().max(255).optional(),
11033
+ caption: z9.string().max(255).optional(),
11122
11034
  call_to_action: callToActionSchema.optional(),
11123
11035
  ...imageMediaFields,
11124
11036
  ...videoMediaFields
@@ -11142,10 +11054,10 @@ var singleCreativeSchema = z10.object({
11142
11054
  });
11143
11055
  }
11144
11056
  });
11145
- var carouselCardSchema = z10.object({
11057
+ var carouselCardSchema = z9.object({
11146
11058
  link: httpsUrlSchema3,
11147
- headline: z10.string().max(META_LIMITS.creative.headlineMax).optional(),
11148
- description: z10.string().max(META_LIMITS.creative.descriptionMax).optional(),
11059
+ headline: z9.string().max(META_LIMITS.creative.headlineMax).optional(),
11060
+ description: z9.string().max(META_LIMITS.creative.descriptionMax).optional(),
11149
11061
  call_to_action: callToActionSchema.optional(),
11150
11062
  ...imageMediaFields,
11151
11063
  ...videoMediaFields
@@ -11165,35 +11077,35 @@ var carouselCardSchema = z10.object({
11165
11077
  ctx.addIssue({ code: "custom", path: ["videoId"], message: "each card is an image OR a video, not both" });
11166
11078
  }
11167
11079
  });
11168
- var carouselCreativeSchema2 = z10.object({
11169
- creativeType: z10.literal("carousel"),
11080
+ var carouselCreativeSchema2 = z9.object({
11081
+ creativeType: z9.literal("carousel"),
11170
11082
  ...creativeSharedFields,
11171
11083
  message: messageSchema,
11172
11084
  /** Optional "see more" card destination applied when a card has no own link. */
11173
11085
  link: httpsUrlSchema3.optional(),
11174
11086
  call_to_action: callToActionSchema.optional(),
11175
- cards: z10.array(carouselCardSchema).min(META_LIMITS.creative.carouselCardsMin).max(META_LIMITS.creative.carouselCardsMax)
11087
+ cards: z9.array(carouselCardSchema).min(META_LIMITS.creative.carouselCardsMin).max(META_LIMITS.creative.carouselCardsMax)
11176
11088
  });
11177
- var dynamicImageSchema = z10.object({ ...imageMediaFields }).refine((p) => countImageRefs(p) === 1, "each dynamic image needs exactly one reference");
11178
- var dynamicVideoSchema = z10.object({
11089
+ var dynamicImageSchema = z9.object({ ...imageMediaFields }).refine((p) => countImageRefs(p) === 1, "each dynamic image needs exactly one reference");
11090
+ var dynamicVideoSchema = z9.object({
11179
11091
  videoId: videoMediaFields.videoId,
11180
11092
  videoRef: videoMediaFields.videoRef,
11181
11093
  thumbnailHash: videoMediaFields.thumbnailHash
11182
11094
  }).refine((p) => countVideoRefs(p) === 1, "each dynamic video needs exactly one reference");
11183
11095
  var DYN = META_LIMITS.creative;
11184
- var dynamicCreativeSchema = z10.object({
11185
- creativeType: z10.literal("dynamic"),
11096
+ var dynamicCreativeSchema = z9.object({
11097
+ creativeType: z9.literal("dynamic"),
11186
11098
  ...creativeSharedFields,
11187
- bodies: z10.array(z10.object({ text: messageSchema })).min(DYN.dynamicTextsMin).max(DYN.dynamicTextsMax),
11188
- titles: z10.array(z10.object({ text: headlineSchema2 })).min(DYN.dynamicTextsMin).max(DYN.dynamicTextsMax),
11189
- descriptions: z10.array(z10.object({ text: descriptionSchema })).max(DYN.dynamicTextsMax).optional(),
11190
- images: z10.array(dynamicImageSchema).optional(),
11191
- videos: z10.array(dynamicVideoSchema).optional(),
11192
- ad_formats: z10.array(z10.enum(AD_FORMATS2)).min(1),
11193
- call_to_action_types: z10.array(z10.enum(CTA_TYPES2)).optional(),
11194
- link_urls: z10.array(z10.object({ website_url: httpsUrlSchema3, display_url: z10.string().optional() })).min(1),
11099
+ bodies: z9.array(z9.object({ text: messageSchema })).min(DYN.dynamicTextsMin).max(DYN.dynamicTextsMax),
11100
+ titles: z9.array(z9.object({ text: headlineSchema2 })).min(DYN.dynamicTextsMin).max(DYN.dynamicTextsMax),
11101
+ descriptions: z9.array(z9.object({ text: descriptionSchema })).max(DYN.dynamicTextsMax).optional(),
11102
+ images: z9.array(dynamicImageSchema).optional(),
11103
+ videos: z9.array(dynamicVideoSchema).optional(),
11104
+ ad_formats: z9.array(z9.enum(AD_FORMATS2)).min(1),
11105
+ call_to_action_types: z9.array(z9.enum(CTA_TYPES2)).optional(),
11106
+ link_urls: z9.array(z9.object({ website_url: httpsUrlSchema3, display_url: z9.string().optional() })).min(1),
11195
11107
  /** Multi-language / placement customization — structural passthrough for v1. */
11196
- asset_customization_rules: z10.array(z10.record(z10.string(), z10.unknown())).optional()
11108
+ asset_customization_rules: z9.array(z9.record(z9.string(), z9.unknown())).optional()
11197
11109
  }).superRefine((p, ctx) => {
11198
11110
  if (!(p.images?.length || p.videos?.length)) {
11199
11111
  ctx.addIssue({
@@ -11203,57 +11115,57 @@ var dynamicCreativeSchema = z10.object({
11203
11115
  });
11204
11116
  }
11205
11117
  });
11206
- var existingPostCreativeSchema = z10.object({
11207
- creativeType: z10.literal("existing_post"),
11118
+ var existingPostCreativeSchema = z9.object({
11119
+ creativeType: z9.literal("existing_post"),
11208
11120
  name: creativeSharedFields.name,
11209
11121
  /** "<page_id>_<post_id>" object story id of the post to promote. */
11210
- object_story_id: z10.string().regex(/^\d+_\d+$/, 'expected "<page_id>_<post_id>"'),
11122
+ object_story_id: z9.string().regex(/^\d+_\d+$/, 'expected "<page_id>_<post_id>"'),
11211
11123
  instagram_user_id: creativeSharedFields.instagram_user_id,
11212
11124
  url_tags: creativeSharedFields.url_tags,
11213
11125
  enhancements: creativeSharedFields.enhancements
11214
11126
  });
11215
- var creativeContentSchema2 = z10.discriminatedUnion("creativeType", [
11127
+ var creativeContentSchema2 = z9.discriminatedUnion("creativeType", [
11216
11128
  singleCreativeSchema,
11217
11129
  carouselCreativeSchema2,
11218
11130
  dynamicCreativeSchema,
11219
11131
  existingPostCreativeSchema
11220
11132
  ]);
11221
11133
  var adCreativeCreateSchema = creativeContentSchema2;
11222
- var adCreativeUpdateSchema = z10.object({
11223
- name: z10.string().max(META_LIMITS.creative.nameMax).optional(),
11134
+ var adCreativeUpdateSchema = z9.object({
11135
+ name: z9.string().max(META_LIMITS.creative.nameMax).optional(),
11224
11136
  status: updateStatusSchema.optional(),
11225
11137
  /** Content patch — only honored when the target is a staged meta_temp_* creative. */
11226
- content: z10.record(z10.string(), z10.unknown()).optional()
11138
+ content: z9.record(z9.string(), z9.unknown()).optional()
11227
11139
  }).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
11228
- var adCreateSchema2 = z10.object({
11229
- name: z10.string().min(1).max(META_LIMITS.ad.nameMax),
11140
+ var adCreateSchema2 = z9.object({
11141
+ name: z9.string().min(1).max(META_LIMITS.ad.nameMax),
11230
11142
  adset_id: parentRefSchema2,
11231
11143
  status: stageableStatusSchema3.default("PAUSED"),
11232
- creative: z10.object({ creative_id: parentRefSchema2 }),
11144
+ creative: z9.object({ creative_id: parentRefSchema2 }),
11233
11145
  /** Conversion pixel / offline event set / view tags — structural passthrough. */
11234
- tracking_specs: z10.array(z10.record(z10.string(), z10.unknown())).optional()
11146
+ tracking_specs: z9.array(z9.record(z9.string(), z9.unknown())).optional()
11235
11147
  });
11236
- var adUpdateSchema2 = z10.object({
11237
- name: z10.string().min(1).max(META_LIMITS.ad.nameMax).optional(),
11148
+ var adUpdateSchema2 = z9.object({
11149
+ name: z9.string().min(1).max(META_LIMITS.ad.nameMax).optional(),
11238
11150
  status: updateStatusSchema.optional(),
11239
11151
  /** Swapping the creative is the Meta way to "edit" an ad's creative. */
11240
- creative: z10.object({ creative_id: parentRefSchema2 }).optional(),
11241
- tracking_specs: z10.array(z10.record(z10.string(), z10.unknown())).optional()
11152
+ creative: z9.object({ creative_id: parentRefSchema2 }).optional(),
11153
+ tracking_specs: z9.array(z9.record(z9.string(), z9.unknown())).optional()
11242
11154
  }).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
11243
- var lookalikeSpecSchema = z10.object({
11244
- origin: z10.array(z10.object({ id: parentRefSchema2 })).min(1),
11245
- ratio: z10.number().min(0.01).max(0.2).optional(),
11246
- country: z10.string().length(2).optional()
11247
- });
11248
- var customAudienceCreateSchema = z10.object({
11249
- name: z10.string().min(1).max(META_LIMITS.audience.nameMax),
11250
- subtype: z10.enum(CUSTOM_AUDIENCE_SUBTYPES),
11251
- description: z10.string().max(500).optional(),
11252
- customer_file_source: z10.string().optional(),
11253
- retention_days: z10.number().int().min(1).max(META_LIMITS.audience.retentionDaysMax).optional(),
11155
+ var lookalikeSpecSchema = z9.object({
11156
+ origin: z9.array(z9.object({ id: parentRefSchema2 })).min(1),
11157
+ ratio: z9.number().min(0.01).max(0.2).optional(),
11158
+ country: z9.string().length(2).optional()
11159
+ });
11160
+ var customAudienceCreateSchema = z9.object({
11161
+ name: z9.string().min(1).max(META_LIMITS.audience.nameMax),
11162
+ subtype: z9.enum(CUSTOM_AUDIENCE_SUBTYPES),
11163
+ description: z9.string().max(500).optional(),
11164
+ customer_file_source: z9.string().optional(),
11165
+ retention_days: z9.number().int().min(1).max(META_LIMITS.audience.retentionDaysMax).optional(),
11254
11166
  lookalike_spec: lookalikeSpecSchema.optional(),
11255
11167
  /** Website/engagement rule — structural passthrough validated by Meta. */
11256
- rule: z10.record(z10.string(), z10.unknown()).optional()
11168
+ rule: z9.record(z9.string(), z9.unknown()).optional()
11257
11169
  }).superRefine((p, ctx) => {
11258
11170
  if (p.subtype === "LOOKALIKE" && !p.lookalike_spec) {
11259
11171
  ctx.addIssue({ code: "custom", path: ["lookalike_spec"], message: "LOOKALIKE audiences need a lookalike_spec" });
@@ -11262,16 +11174,16 @@ var customAudienceCreateSchema = z10.object({
11262
11174
  ctx.addIssue({ code: "custom", path: ["rule"], message: `${p.subtype} audiences need a rule (use --file)` });
11263
11175
  }
11264
11176
  });
11265
- var customAudienceUpdateSchema = z10.object({
11266
- name: z10.string().min(1).max(META_LIMITS.audience.nameMax).optional(),
11267
- description: z10.string().max(500).optional()
11177
+ var customAudienceUpdateSchema = z9.object({
11178
+ name: z9.string().min(1).max(META_LIMITS.audience.nameMax).optional(),
11179
+ description: z9.string().max(500).optional()
11268
11180
  }).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
11269
- var mediaUploadSchema = z10.object({
11270
- kind: z10.enum(MEDIA_KINDS),
11181
+ var mediaUploadSchema = z9.object({
11182
+ kind: z9.enum(MEDIA_KINDS),
11271
11183
  bakerImageId: bakerMediaIdSchema2.optional(),
11272
11184
  bakerVideoId: bakerMediaIdSchema2.optional(),
11273
11185
  /** Optional display name / filename hint. */
11274
- name: z10.string().max(255).optional()
11186
+ name: z9.string().max(255).optional()
11275
11187
  }).superRefine((p, ctx) => {
11276
11188
  if (p.kind === "image" && !p.bakerImageId) {
11277
11189
  ctx.addIssue({ code: "custom", path: ["bakerImageId"], message: "image uploads need a bakerImageId" });
@@ -11293,16 +11205,16 @@ var META_DRAFT_OP_KINDS = [
11293
11205
  "customAudience.update",
11294
11206
  "media.upload"
11295
11207
  ];
11296
- var metaDraftOpKindSchema = z10.enum(META_DRAFT_OP_KINDS);
11297
- var accountIdSchema2 = z10.string().regex(NUMERIC_ID_REGEX3, "accountId must be the bare numeric ad account id");
11298
- var updateTargetSchema2 = z10.union([z10.string().regex(NUMERIC_ID_REGEX3), tempRefSchema3]);
11208
+ var metaDraftOpKindSchema = z9.enum(META_DRAFT_OP_KINDS);
11209
+ var accountIdSchema2 = z9.string().regex(NUMERIC_ID_REGEX3, "accountId must be the bare numeric ad account id");
11210
+ var updateTargetSchema2 = z9.union([z9.string().regex(NUMERIC_ID_REGEX3), tempRefSchema3]);
11299
11211
  function createOp3(kind, payload) {
11300
- return z10.object({ kind: z10.literal(kind), accountId: accountIdSchema2, payload });
11212
+ return z9.object({ kind: z9.literal(kind), accountId: accountIdSchema2, payload });
11301
11213
  }
11302
11214
  function updateOp3(kind, payload) {
11303
- return z10.object({ kind: z10.literal(kind), accountId: accountIdSchema2, target: updateTargetSchema2, payload });
11215
+ return z9.object({ kind: z9.literal(kind), accountId: accountIdSchema2, target: updateTargetSchema2, payload });
11304
11216
  }
11305
- var metaDraftOpInputSchema = z10.discriminatedUnion("kind", [
11217
+ var metaDraftOpInputSchema = z9.discriminatedUnion("kind", [
11306
11218
  createOp3("campaign.create", campaignCreateSchema3),
11307
11219
  updateOp3("campaign.update", campaignUpdateSchema3),
11308
11220
  createOp3("adSet.create", adSetCreateSchema),
@@ -11317,89 +11229,89 @@ var metaDraftOpInputSchema = z10.discriminatedUnion("kind", [
11317
11229
  ]);
11318
11230
 
11319
11231
  // ../api/src/ads-meta/wire.ts
11320
- import { z as z11 } from "zod";
11321
- var metaWriteModeSchema = z11.enum(["live", "simulated"]);
11322
- var metaDraftOpResultSchema = z11.object({
11323
- status: z11.enum(["applied", "simulated", "failed", "skipped"]),
11232
+ import { z as z10 } from "zod";
11233
+ var metaWriteModeSchema = z10.enum(["live", "simulated"]);
11234
+ var metaDraftOpResultSchema = z10.object({
11235
+ status: z10.enum(["applied", "simulated", "failed", "skipped"]),
11324
11236
  /** The resulting Meta node id (campaign/adset/creative/ad/audience) or simulated id. */
11325
- id: z11.string().optional(),
11237
+ id: z10.string().optional(),
11326
11238
  /** For media.upload ops: the resulting image hash. */
11327
- hash: z11.string().optional(),
11328
- error: z11.string().optional(),
11329
- skippedBecause: z11.string().optional(),
11330
- executedAt: z11.number().optional()
11239
+ hash: z10.string().optional(),
11240
+ error: z10.string().optional(),
11241
+ skippedBecause: z10.string().optional(),
11242
+ executedAt: z10.number().optional()
11331
11243
  });
11332
- var metaDraftStageRequestSchema = z11.object({
11333
- chatId: z11.string(),
11244
+ var metaDraftStageRequestSchema = z10.object({
11245
+ chatId: z10.string(),
11334
11246
  op: metaDraftOpInputSchema
11335
11247
  });
11336
- var metaDraftStageResponseSchema = z11.object({
11337
- staged: z11.literal(true),
11338
- ref: z11.string(),
11248
+ var metaDraftStageResponseSchema = z10.object({
11249
+ staged: z10.literal(true),
11250
+ ref: z10.string(),
11339
11251
  kind: metaDraftOpKindSchema,
11340
11252
  mode: metaWriteModeSchema,
11341
- dependsOn: z11.array(z11.string()),
11342
- summary: z11.string(),
11343
- warnings: z11.array(z11.string()),
11253
+ dependsOn: z10.array(z10.string()),
11254
+ summary: z10.string(),
11255
+ warnings: z10.array(z10.string()),
11344
11256
  /** True when the op amended an already-staged op in place instead of appending a new one. */
11345
- amended: z11.boolean().optional()
11346
- });
11347
- var metaDraftDuplicateRequestSchema = z11.object({
11348
- chatId: z11.string(),
11349
- accountId: z11.string(),
11350
- entity: z11.enum(["campaign", "adSet", "ad"]),
11351
- sourceId: z11.string(),
11352
- overrides: z11.record(z11.string(), z11.unknown()).optional(),
11257
+ amended: z10.boolean().optional()
11258
+ });
11259
+ var metaDraftDuplicateRequestSchema = z10.object({
11260
+ chatId: z10.string(),
11261
+ accountId: z10.string(),
11262
+ entity: z10.enum(["campaign", "adSet", "ad"]),
11263
+ sourceId: z10.string(),
11264
+ overrides: z10.record(z10.string(), z10.unknown()).optional(),
11353
11265
  /** Pause the original after the copy publishes. */
11354
- replace: z11.boolean().optional()
11266
+ replace: z10.boolean().optional()
11355
11267
  });
11356
- var metaDraftOpViewSchema = z11.object({
11357
- ref: z11.string(),
11268
+ var metaDraftOpViewSchema = z10.object({
11269
+ ref: z10.string(),
11358
11270
  kind: metaDraftOpKindSchema,
11359
- accountId: z11.string(),
11360
- target: z11.string().optional(),
11361
- dependsOn: z11.array(z11.string()),
11362
- summary: z11.string(),
11363
- stagedAt: z11.number(),
11271
+ accountId: z10.string(),
11272
+ target: z10.string().optional(),
11273
+ dependsOn: z10.array(z10.string()),
11274
+ summary: z10.string(),
11275
+ stagedAt: z10.number(),
11364
11276
  result: metaDraftOpResultSchema.optional()
11365
11277
  });
11366
- var metaDraftListRequestSchema = z11.object({
11367
- chatId: z11.string()
11278
+ var metaDraftListRequestSchema = z10.object({
11279
+ chatId: z10.string()
11368
11280
  });
11369
- var metaDraftAdvisorySchema = z11.object({
11370
- ref: z11.string(),
11371
- message: z11.string()
11281
+ var metaDraftAdvisorySchema = z10.object({
11282
+ ref: z10.string(),
11283
+ message: z10.string()
11372
11284
  });
11373
- var metaDraftListResponseSchema = z11.object({
11374
- status: z11.enum(["active", "publishing", "applied", "discarded", "none"]),
11285
+ var metaDraftListResponseSchema = z10.object({
11286
+ status: z10.enum(["active", "publishing", "applied", "discarded", "none"]),
11375
11287
  mode: metaWriteModeSchema,
11376
- count: z11.number(),
11377
- ops: z11.array(metaDraftOpViewSchema),
11288
+ count: z10.number(),
11289
+ ops: z10.array(metaDraftOpViewSchema),
11378
11290
  /** Non-blocking cross-op quality advisories — "good campaign, not just valid". */
11379
- advisories: z11.array(metaDraftAdvisorySchema)
11291
+ advisories: z10.array(metaDraftAdvisorySchema)
11380
11292
  });
11381
- var metaDraftRemoveRequestSchema = z11.object({
11382
- chatId: z11.string(),
11383
- ref: z11.string()
11293
+ var metaDraftRemoveRequestSchema = z10.object({
11294
+ chatId: z10.string(),
11295
+ ref: z10.string()
11384
11296
  });
11385
- var metaDraftRemoveResponseSchema = z11.object({
11297
+ var metaDraftRemoveResponseSchema = z10.object({
11386
11298
  /** The requested ref plus any dependents removed by cascade. */
11387
- removed: z11.array(z11.string())
11299
+ removed: z10.array(z10.string())
11388
11300
  });
11389
- var metaDraftClearRequestSchema = z11.object({
11390
- chatId: z11.string()
11301
+ var metaDraftClearRequestSchema = z10.object({
11302
+ chatId: z10.string()
11391
11303
  });
11392
- var metaDraftClearResponseSchema = z11.object({
11393
- cleared: z11.number()
11304
+ var metaDraftClearResponseSchema = z10.object({
11305
+ cleared: z10.number()
11394
11306
  });
11395
- var metaFieldErrorSchema = z11.object({
11396
- path: z11.string(),
11397
- message: z11.string()
11307
+ var metaFieldErrorSchema = z10.object({
11308
+ path: z10.string(),
11309
+ message: z10.string()
11398
11310
  });
11399
- var metaDraftErrorResponseSchema = z11.object({
11400
- code: z11.string(),
11401
- error: z11.string(),
11402
- fields: z11.array(metaFieldErrorSchema).optional()
11311
+ var metaDraftErrorResponseSchema = z10.object({
11312
+ code: z10.string(),
11313
+ error: z10.string(),
11314
+ fields: z10.array(metaFieldErrorSchema).optional()
11403
11315
  });
11404
11316
 
11405
11317
  // src/commands/ads/meta/write-shared.ts
@@ -11410,19 +11322,19 @@ function failWriteValidation3(message) {
11410
11322
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
11411
11323
  process.exit(1);
11412
11324
  }
11413
- function loadJsonFileArg3(path12) {
11414
- if (typeof path12 !== "string" || path12.length === 0) {
11325
+ function loadJsonFileArg3(path15) {
11326
+ if (typeof path15 !== "string" || path15.length === 0) {
11415
11327
  return {};
11416
11328
  }
11417
11329
  try {
11418
- const parsed = JSON.parse(readFileSync8(path12, "utf8"));
11330
+ const parsed = JSON.parse(readFileSync8(path15, "utf8"));
11419
11331
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
11420
- failWriteValidation3(`${path12} must contain a JSON object`);
11332
+ failWriteValidation3(`${path15} must contain a JSON object`);
11421
11333
  }
11422
11334
  return parsed;
11423
11335
  } catch (err) {
11424
11336
  if (err instanceof SyntaxError) {
11425
- failWriteValidation3(`${path12} is not valid JSON: ${err.message}`);
11337
+ failWriteValidation3(`${path15} is not valid JSON: ${err.message}`);
11426
11338
  }
11427
11339
  throw err;
11428
11340
  }
@@ -14484,8 +14396,8 @@ async function probeDuration(filePath) {
14484
14396
  }
14485
14397
 
14486
14398
  // src/commands/canvas/run.ts
14487
- import { readFile as readFile2 } from "fs/promises";
14488
- import path4 from "path";
14399
+ import { readFile as readFile3 } from "fs/promises";
14400
+ import path6 from "path";
14489
14401
  import { defineCommand as defineCommand88 } from "citty";
14490
14402
 
14491
14403
  // src/commands/canvas/placeholders.ts
@@ -14529,9 +14441,290 @@ function isResolvableRelative(value) {
14529
14441
  return typeof value === "string" && value.length > 0 && !value.includes("[TODO") && !path2.isAbsolute(value);
14530
14442
  }
14531
14443
 
14444
+ // src/commands/canvas/run-record.ts
14445
+ import path3 from "path";
14446
+ var MAX_RUN_NODES = 200;
14447
+ var MAX_OUTPUTS_PER_NODE = 10;
14448
+ var MAX_FINAL_OUTPUTS = 10;
14449
+ var MAX_PARAMS_PREVIEW_LENGTH = 4e3;
14450
+ function paramsPreviewFromParams(params) {
14451
+ if (params === void 0 || params === null) return void 0;
14452
+ const text = humanParamText(params, "prompt") ?? humanParamText(params, "source") ?? compactJson(params);
14453
+ if (!text) return void 0;
14454
+ return text.length > MAX_PARAMS_PREVIEW_LENGTH ? `${text.slice(0, MAX_PARAMS_PREVIEW_LENGTH - 1)}\u2026` : text;
14455
+ }
14456
+ function humanParamText(params, key) {
14457
+ const value = params[key];
14458
+ if (typeof value !== "string") return void 0;
14459
+ const trimmed = value.trim();
14460
+ return trimmed && !trimmed.startsWith("$ref:") ? trimmed : void 0;
14461
+ }
14462
+ function compactJson(params) {
14463
+ try {
14464
+ const json = JSON.stringify(params);
14465
+ return json && json !== "{}" ? json : void 0;
14466
+ } catch {
14467
+ return void 0;
14468
+ }
14469
+ }
14470
+ var MAX_CREATIVE_SLUG_LENGTH = 100;
14471
+ function creativeSlugFromCanvasPath(filePath) {
14472
+ const normalized = filePath.split(path3.sep).join("/");
14473
+ const match = normalized.match(/(?:^|\/)src\/creatives\/([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\//);
14474
+ const slug = match?.[1] ?? null;
14475
+ return slug && slug.length <= MAX_CREATIVE_SLUG_LENGTH ? slug : null;
14476
+ }
14477
+ var OUTPUT_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
14478
+ function toRecordOutput(slot, value) {
14479
+ const refs = collectAssetRefLikes(value);
14480
+ const ref = refs.length === 1 ? refs[0] : null;
14481
+ if (!ref || !isPersistedAssetRef(ref)) return null;
14482
+ const kind = typeof ref.kind === "string" && OUTPUT_KINDS.has(ref.kind) ? ref.kind : null;
14483
+ if (!kind) return null;
14484
+ return {
14485
+ slot,
14486
+ kind,
14487
+ sha256: ref.sha256,
14488
+ url: ref.url,
14489
+ mime: ref.mime,
14490
+ width: typeof ref.width === "number" ? ref.width : void 0,
14491
+ height: typeof ref.height === "number" ? ref.height : void 0,
14492
+ durationMs: typeof ref.duration_ms === "number" ? ref.duration_ms : void 0
14493
+ };
14494
+ }
14495
+ function nodeOutputsToRecord(nodeOutputs) {
14496
+ const out = [];
14497
+ for (const [slot, value] of Object.entries(nodeOutputs)) {
14498
+ if (Array.isArray(value)) {
14499
+ value.forEach((item, i) => {
14500
+ const rec = toRecordOutput(`${slot}#${i}`, item);
14501
+ if (rec) out.push(rec);
14502
+ });
14503
+ } else {
14504
+ const rec = toRecordOutput(slot, value);
14505
+ if (rec) out.push(rec);
14506
+ }
14507
+ }
14508
+ return out.slice(0, MAX_OUTPUTS_PER_NODE);
14509
+ }
14510
+ function finalOutputsToRecord(output) {
14511
+ if (Array.isArray(output)) {
14512
+ return output.map((item, i) => toRecordOutput(`final#${i}`, item)).filter((rec2) => rec2 !== null).slice(0, MAX_FINAL_OUTPUTS);
14513
+ }
14514
+ const rec = toRecordOutput("final", output);
14515
+ return rec ? [rec] : [];
14516
+ }
14517
+ function buildRunRecord(result, meta, plan) {
14518
+ const nodes = result.node_runs.slice(0, MAX_RUN_NODES).map((run) => {
14519
+ const planned = plan?.get(run.node_id);
14520
+ return {
14521
+ nodeId: run.node_id,
14522
+ nodeType: run.node_type,
14523
+ cached: run.cached,
14524
+ credits: run.credits,
14525
+ durationMs: run.duration_ms,
14526
+ outputs: nodeOutputsToRecord(result.outputs_by_node[run.node_id] ?? {}),
14527
+ deps: planned?.deps,
14528
+ status: plan ? "completed" : void 0,
14529
+ paramsPreview: planned?.paramsPreview
14530
+ };
14531
+ });
14532
+ const finalOutputs = finalOutputsToRecord(result.output);
14533
+ return {
14534
+ runId: result.run_id,
14535
+ creativeSlug: meta.creativeSlug,
14536
+ canvasPath: meta.canvasPath,
14537
+ canvasSha: meta.canvasSha,
14538
+ chatId: meta.chatId,
14539
+ status: "completed",
14540
+ stats: {
14541
+ totalNodes: result.stats.total_nodes,
14542
+ cachedNodes: result.stats.cached_nodes,
14543
+ totalCredits: result.stats.total_credits,
14544
+ durationMs: result.stats.duration_ms
14545
+ },
14546
+ nodes,
14547
+ finalOutputs: finalOutputs.length > 0 ? finalOutputs : void 0
14548
+ };
14549
+ }
14550
+ function buildFailedRunRecord(runId, errorMessage, meta) {
14551
+ return {
14552
+ runId,
14553
+ creativeSlug: meta.creativeSlug,
14554
+ canvasPath: meta.canvasPath,
14555
+ canvasSha: meta.canvasSha,
14556
+ chatId: meta.chatId,
14557
+ status: "failed",
14558
+ errorMessage: errorMessage.slice(0, 2e3),
14559
+ stats: { totalNodes: 0, cachedNodes: 0, totalCredits: 0, durationMs: 0 },
14560
+ nodes: []
14561
+ };
14562
+ }
14563
+
14564
+ // src/commands/canvas/run-progress.ts
14565
+ var RunProgressTracker = class {
14566
+ runId;
14567
+ meta;
14568
+ startedAt;
14569
+ nodes = /* @__PURE__ */ new Map();
14570
+ planned = false;
14571
+ constructor(runId, meta) {
14572
+ this.runId = runId;
14573
+ this.meta = meta;
14574
+ this.startedAt = Date.now();
14575
+ }
14576
+ apply(event) {
14577
+ if (event.kind === "plan") {
14578
+ for (const node of event.nodes) {
14579
+ this.nodes.set(node.node_id, {
14580
+ nodeId: node.node_id,
14581
+ nodeType: node.node_type,
14582
+ cached: false,
14583
+ credits: 0,
14584
+ durationMs: 0,
14585
+ outputs: [],
14586
+ deps: node.deps,
14587
+ status: "pending",
14588
+ paramsPreview: paramsPreviewFromParams(node.params)
14589
+ });
14590
+ }
14591
+ this.planned = true;
14592
+ return;
14593
+ }
14594
+ if (event.kind === "node_start") {
14595
+ this.patchNode(event.node_id, { status: "running" });
14596
+ return;
14597
+ }
14598
+ if (event.kind === "node_settled") {
14599
+ this.patchNode(event.run.node_id, {
14600
+ status: "completed",
14601
+ cached: event.run.cached,
14602
+ credits: event.run.credits,
14603
+ durationMs: event.run.duration_ms,
14604
+ outputs: nodeOutputsToRecord(event.outputs)
14605
+ });
14606
+ return;
14607
+ }
14608
+ this.patchNode(event.node_id, { status: "failed" });
14609
+ }
14610
+ /** True once the plan event landed — before that there is nothing worth posting. */
14611
+ hasPlan() {
14612
+ return this.planned;
14613
+ }
14614
+ /** Plan facts (deps + params preview) for stamping the terminal record's nodes. */
14615
+ planInfo() {
14616
+ const info = /* @__PURE__ */ new Map();
14617
+ for (const node of this.nodes.values()) {
14618
+ info.set(node.nodeId, { deps: node.deps ?? [], paramsPreview: node.paramsPreview });
14619
+ }
14620
+ return info;
14621
+ }
14622
+ /** The current in-flight state as a postable full record. */
14623
+ snapshot() {
14624
+ const nodes = [...this.nodes.values()];
14625
+ return {
14626
+ runId: this.runId,
14627
+ ...this.meta,
14628
+ status: "running",
14629
+ stats: {
14630
+ totalNodes: nodes.length,
14631
+ cachedNodes: nodes.filter((n) => n.cached).length,
14632
+ totalCredits: nodes.reduce((sum, n) => sum + n.credits, 0),
14633
+ durationMs: Date.now() - this.startedAt
14634
+ },
14635
+ nodes
14636
+ };
14637
+ }
14638
+ /**
14639
+ * Terminal record for a failed run, preserving what each node got to —
14640
+ * completed nodes keep their outputs so the graph shows exactly where the
14641
+ * run died instead of an empty husk.
14642
+ */
14643
+ failedSnapshot(errorMessage) {
14644
+ const snapshot = this.snapshot();
14645
+ return {
14646
+ ...snapshot,
14647
+ status: "failed",
14648
+ errorMessage: errorMessage.slice(0, 2e3),
14649
+ stats: { ...snapshot.stats, durationMs: Date.now() - this.startedAt }
14650
+ };
14651
+ }
14652
+ patchNode(nodeId, patch) {
14653
+ const existing = this.nodes.get(nodeId);
14654
+ if (!existing) return;
14655
+ this.nodes.set(nodeId, { ...existing, ...patch, status: patch.status ?? existing.status });
14656
+ }
14657
+ };
14658
+ var RunRecordPoster = class {
14659
+ post;
14660
+ latest = null;
14661
+ inflight = null;
14662
+ warned = false;
14663
+ keepaliveTimer = null;
14664
+ constructor(post) {
14665
+ this.post = post;
14666
+ }
14667
+ /** Queue a progress snapshot; returns immediately. */
14668
+ enqueue(payload) {
14669
+ this.latest = payload;
14670
+ if (!this.inflight) this.inflight = this.pump();
14671
+ }
14672
+ /**
14673
+ * Re-post the latest snapshot on an interval even with no new node events, so
14674
+ * the backend's `canvasRuns.updatedAt` heartbeat stays fresh during a long
14675
+ * single-clip poll (a video_generate clip can run minutes with no
14676
+ * intervening node events). When this process dies the keepalive stops → the
14677
+ * run's `updatedAt` goes stale → the backend reconciliation sweep force-fails
14678
+ * it as interrupted and surfaces the clips that finished. `produce` returns
14679
+ * null before the plan lands (nothing worth posting yet).
14680
+ */
14681
+ startKeepalive(produce, intervalMs = 6e4) {
14682
+ if (this.keepaliveTimer) return;
14683
+ this.keepaliveTimer = setInterval(() => {
14684
+ const snapshot = produce();
14685
+ if (snapshot) this.enqueue(snapshot);
14686
+ }, intervalMs);
14687
+ this.keepaliveTimer.unref?.();
14688
+ }
14689
+ stopKeepalive() {
14690
+ if (this.keepaliveTimer) {
14691
+ clearInterval(this.keepaliveTimer);
14692
+ this.keepaliveTimer = null;
14693
+ }
14694
+ }
14695
+ /**
14696
+ * Post the terminal record (awaited, errors surfaced to the caller). Any
14697
+ * queued progress snapshot is superseded — the terminal record is the full
14698
+ * state — but an in-flight POST is awaited first so it can't land after.
14699
+ */
14700
+ async flush(terminal) {
14701
+ this.stopKeepalive();
14702
+ this.latest = null;
14703
+ if (this.inflight) await this.inflight;
14704
+ await this.post(terminal);
14705
+ }
14706
+ async pump() {
14707
+ while (this.latest) {
14708
+ const payload = this.latest;
14709
+ this.latest = null;
14710
+ try {
14711
+ await this.post(payload);
14712
+ } catch (e) {
14713
+ if (!this.warned) {
14714
+ this.warned = true;
14715
+ const msg = e instanceof Error ? e.message : String(e);
14716
+ process.stderr.write(`[warn] live run progress not streaming (${msg})
14717
+ `);
14718
+ }
14719
+ }
14720
+ }
14721
+ this.inflight = null;
14722
+ }
14723
+ };
14724
+
14532
14725
  // src/commands/canvas/run-retention.ts
14533
14726
  import { rm } from "fs/promises";
14534
- import path3 from "path";
14727
+ import path4 from "path";
14535
14728
  function runDirsToPrune(entries, keep, currentRunId) {
14536
14729
  const runs = entries.filter((e) => /^r_[0-9A-Za-z]+$/.test(e) && e !== currentRunId).sort();
14537
14730
  if (keep <= 0) return runs;
@@ -14548,13 +14741,52 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
14548
14741
  const toPrune = runDirsToPrune(entries, keep, currentRunId);
14549
14742
  if (toPrune.length === 0) return;
14550
14743
  for (const dir of toPrune) {
14551
- await rm(path3.join(outputsDir, dir), { recursive: true, force: true }).catch(
14744
+ await rm(path4.join(outputsDir, dir), { recursive: true, force: true }).catch(
14552
14745
  (e) => log(`[prune ] could not remove ${dir}: ${e.message}`)
14553
14746
  );
14554
14747
  }
14555
14748
  log(`[prune ] removed ${toPrune.length} old run dir(s), kept the ${keep} newest`);
14556
14749
  }
14557
14750
 
14751
+ // src/commands/canvas/run-resume.ts
14752
+ import { mkdir, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
14753
+ import path5 from "path";
14754
+ function markerPath(outputsDir, canvasPath) {
14755
+ const key = sha256Hex(Buffer.from(path5.resolve(canvasPath))).slice(0, 32);
14756
+ return path5.join(outputsDir, ".inflight", `${key}.json`);
14757
+ }
14758
+ async function resolveRunId(opts) {
14759
+ if (opts.explicitRunId) return { runId: opts.explicitRunId, resumed: false };
14760
+ if (!opts.fresh) {
14761
+ const existing = await readMarkerRunId(opts.outputsDir, opts.canvasPath);
14762
+ if (existing) return { runId: existing, resumed: true };
14763
+ }
14764
+ return { runId: `r_${ulid()}`, resumed: false };
14765
+ }
14766
+ async function readMarkerRunId(outputsDir, canvasPath) {
14767
+ try {
14768
+ const raw = await readFile2(markerPath(outputsDir, canvasPath), "utf8");
14769
+ const parsed = JSON.parse(raw);
14770
+ return typeof parsed.runId === "string" && parsed.runId.length > 0 ? parsed.runId : null;
14771
+ } catch {
14772
+ return null;
14773
+ }
14774
+ }
14775
+ async function markRunInFlight(outputsDir, canvasPath, runId) {
14776
+ try {
14777
+ const file = markerPath(outputsDir, canvasPath);
14778
+ await mkdir(path5.dirname(file), { recursive: true });
14779
+ await writeFile(file, JSON.stringify({ runId, canvasPath: path5.resolve(canvasPath), startedAt: Date.now() }));
14780
+ } catch {
14781
+ }
14782
+ }
14783
+ async function clearRunMarker(outputsDir, canvasPath) {
14784
+ try {
14785
+ await rm2(markerPath(outputsDir, canvasPath), { force: true });
14786
+ } catch {
14787
+ }
14788
+ }
14789
+
14558
14790
  // src/commands/canvas/run.ts
14559
14791
  var runCommand = defineCommand88({
14560
14792
  meta: { name: "run", description: "Validate and execute a canvas JSON file." },
@@ -14562,8 +14794,17 @@ var runCommand = defineCommand88({
14562
14794
  file: { type: "positional", required: true, description: "Path to canvas JSON" },
14563
14795
  "cache-dir": { type: "string", description: "Cache root (default ./canvas/.cache)" },
14564
14796
  "outputs-dir": { type: "string", description: "Per-run outputs root (default ./canvas)" },
14565
- "run-id": { type: "string", description: "Override run id" },
14797
+ "run-id": { type: "string", description: "Override run id (also resumes that run, re-attaching its in-flight jobs)" },
14798
+ fresh: {
14799
+ type: "boolean",
14800
+ default: false,
14801
+ description: "Ignore any interrupted-run marker and start a new run id instead of resuming"
14802
+ },
14566
14803
  "cache-policy": { type: "string", description: "read_write | bypass | read_only" },
14804
+ regenerate: {
14805
+ type: "string",
14806
+ description: "Comma-separated node ids to force fresh THIS run (e.g. --regenerate gen_4x5,gen_9x16), bypassing the content cache for just those nodes + everything downstream. For a persistent re-render, bump a node's `regenerate` field in the canvas JSON instead."
14807
+ },
14567
14808
  concurrency: {
14568
14809
  type: "string",
14569
14810
  description: "Max nodes per layer in flight at once (default 5; env BAKER_CANVAS_CONCURRENCY)"
@@ -14575,11 +14816,23 @@ var runCommand = defineCommand88({
14575
14816
  "keep-runs": {
14576
14817
  type: "string",
14577
14818
  description: "After the run, prune old r_* run dirs, keeping the N newest (off by default)"
14819
+ },
14820
+ "remote-cache": {
14821
+ type: "string",
14822
+ description: "on | off \u2014 company-scoped remote cache + durable asset persistence (default on; env BAKER_CANVAS_REMOTE_CACHE)"
14823
+ },
14824
+ // citty consumes any `--no-<flag>` as a negation of `<flag>`, so the
14825
+ // opt-out spelling `--no-record` requires the flag to be named `record`
14826
+ // (a literal "no-record" arg would never receive a value).
14827
+ record: {
14828
+ type: "boolean",
14829
+ default: true,
14830
+ description: "Post the durable run-history record to Baker (disable with --no-record)"
14578
14831
  }
14579
14832
  },
14580
14833
  async run({ args }) {
14581
- const filePath = path4.resolve(String(args.file));
14582
- const raw = await readFile2(filePath, "utf8");
14834
+ const filePath = path6.resolve(String(args.file));
14835
+ const raw = await readFile3(filePath, "utf8");
14583
14836
  let parsed;
14584
14837
  try {
14585
14838
  parsed = JSON.parse(raw);
@@ -14589,7 +14842,7 @@ var runCommand = defineCommand88({
14589
14842
  `);
14590
14843
  process.exit(2);
14591
14844
  }
14592
- parsed = resolveRelativeCanvasPaths(parsed, path4.dirname(filePath));
14845
+ parsed = resolveRelativeCanvasPaths(parsed, path6.dirname(filePath));
14593
14846
  const pending = unsuppliedPlaceholderAssets(parsed);
14594
14847
  if (pending.length > 0) {
14595
14848
  process.stderr.write(
@@ -14609,26 +14862,86 @@ var runCommand = defineCommand88({
14609
14862
  );
14610
14863
  process.exit(2);
14611
14864
  }
14865
+ let regenerate;
14866
+ if (args.regenerate !== void 0) {
14867
+ const requested = String(args.regenerate).split(",").map((id) => id.trim()).filter((id) => id.length > 0);
14868
+ const known = new Set(canvasNodeIds(parsed));
14869
+ const unknown = requested.filter((id) => !known.has(id));
14870
+ if (unknown.length > 0) {
14871
+ process.stderr.write(
14872
+ `${JSON.stringify(
14873
+ {
14874
+ ok: false,
14875
+ error: {
14876
+ code: "unknown_regenerate_node",
14877
+ message: `--regenerate names node id(s) not in this canvas: ${unknown.join(", ")}. Known ids: ${[...known].join(", ")}`
14878
+ }
14879
+ },
14880
+ null,
14881
+ 2
14882
+ )}
14883
+ `
14884
+ );
14885
+ process.exit(2);
14886
+ }
14887
+ if (requested.length > 0) {
14888
+ regenerate = new Set(requested);
14889
+ process.stdout.write(`[regenerate] forcing fresh this run: ${[...regenerate].join(", ")} (+ downstream)
14890
+ `);
14891
+ }
14892
+ }
14893
+ const remoteCache = args["remote-cache"] !== void 0 ? String(args["remote-cache"]) !== "off" : void 0;
14612
14894
  const engine = createEngineFromEnv({
14613
14895
  cacheDir: args["cache-dir"] ? String(args["cache-dir"]) : void 0,
14614
14896
  outputsDir: args["outputs-dir"] ? String(args["outputs-dir"]) : void 0,
14615
14897
  log: (line) => process.stdout.write(`${line}
14616
- `)
14898
+ `),
14899
+ remoteCache
14900
+ });
14901
+ const outputsDir = args["outputs-dir"] ? path6.resolve(String(args["outputs-dir"])) : path6.resolve("canvas");
14902
+ const { runId, resumed } = await resolveRunId({
14903
+ explicitRunId: args["run-id"] ? String(args["run-id"]) : void 0,
14904
+ fresh: args.fresh === true,
14905
+ outputsDir,
14906
+ canvasPath: filePath
14617
14907
  });
14908
+ if (resumed) {
14909
+ process.stdout.write(`[resume] continuing interrupted run ${runId} \u2014 in-flight jobs re-attach, cached nodes skip
14910
+ `);
14911
+ }
14912
+ await markRunInFlight(outputsDir, filePath, runId);
14913
+ const recordMeta = {
14914
+ creativeSlug: creativeSlugFromCanvasPath(filePath) ?? void 0,
14915
+ canvasPath: path6.relative(process.cwd(), filePath) || void 0,
14916
+ canvasSha: sha256Hex(Buffer.from(raw)),
14917
+ chatId: getEnv().BAKER_CHAT_ID || void 0
14918
+ };
14919
+ const record = args.record === false ? null : buildRecorder();
14920
+ const progress = record ? new RunProgressTracker(runId, recordMeta) : null;
14921
+ const poster = record ? new RunRecordPoster(record) : null;
14922
+ if (progress && poster) {
14923
+ poster.startKeepalive(() => progress.hasPlan() ? progress.snapshot() : null);
14924
+ }
14618
14925
  try {
14619
14926
  const policy = args["cache-policy"] ?? "read_write";
14620
14927
  const result = await engine.run(parsed, {
14621
- run_id: args["run-id"] ? String(args["run-id"]) : void 0,
14928
+ run_id: runId,
14622
14929
  cache_policy: policy,
14623
14930
  concurrency: resolveConcurrency(
14624
14931
  // --concurrency wins; --parallel is the discoverable alias for the same bound.
14625
14932
  (args.concurrency ?? args.parallel) !== void 0 ? String(args.concurrency ?? args.parallel) : void 0,
14626
14933
  process.env.BAKER_CANVAS_CONCURRENCY
14627
- )
14934
+ ),
14935
+ regenerate,
14936
+ onProgress: progress && poster ? (event) => {
14937
+ progress.apply(event);
14938
+ if (progress.hasPlan()) poster.enqueue(progress.snapshot());
14939
+ } : void 0
14628
14940
  });
14941
+ await clearRunMarker(outputsDir, filePath);
14942
+ if (poster) await poster.flush(buildRunRecord(result, recordMeta, progress?.planInfo()));
14629
14943
  const keepRuns = args["keep-runs"] !== void 0 ? Number(args["keep-runs"]) : void 0;
14630
14944
  if (keepRuns !== void 0 && Number.isFinite(keepRuns)) {
14631
- const outputsDir = args["outputs-dir"] ? path4.resolve(String(args["outputs-dir"])) : path4.resolve("canvas");
14632
14945
  await pruneOldRuns(outputsDir, keepRuns, result.run_id, (line) => process.stdout.write(`${line}
14633
14946
  `));
14634
14947
  }
@@ -14647,6 +14960,7 @@ var runCommand = defineCommand88({
14647
14960
  `
14648
14961
  );
14649
14962
  } catch (e) {
14963
+ await clearRunMarker(outputsDir, filePath);
14650
14964
  if (e instanceof ValidationError) {
14651
14965
  process.stderr.write(
14652
14966
  `${JSON.stringify({ ok: false, error: { code: "validation", issues: e.issues } }, null, 2)}
@@ -14654,8 +14968,10 @@ var runCommand = defineCommand88({
14654
14968
  );
14655
14969
  process.exit(2);
14656
14970
  }
14971
+ const failedPayload = (message) => progress?.hasPlan() ? progress.failedSnapshot(message) : buildFailedRunRecord(runId, message, recordMeta);
14657
14972
  if (e instanceof LayerExecutionError) {
14658
14973
  const failures = e.failures.map((f) => ({ node_id: f.nodeId, message: describeFailureReason(f.reason) }));
14974
+ if (poster) await poster.flush(failedPayload(e.message));
14659
14975
  process.stderr.write(
14660
14976
  `${JSON.stringify({ ok: false, error: { code: "runtime", message: e.message, failures } }, null, 2)}
14661
14977
  `
@@ -14663,40 +14979,59 @@ var runCommand = defineCommand88({
14663
14979
  process.exit(1);
14664
14980
  }
14665
14981
  const msg = e instanceof Error ? e.message : String(e);
14982
+ if (poster) await poster.flush(failedPayload(msg));
14666
14983
  process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "runtime", message: msg } }, null, 2)}
14667
14984
  `);
14668
14985
  process.exit(1);
14669
14986
  }
14670
14987
  }
14671
14988
  });
14989
+ function canvasNodeIds(parsed) {
14990
+ const nodes = parsed?.nodes;
14991
+ if (!Array.isArray(nodes)) return [];
14992
+ return nodes.map((node) => node?.id).filter((id) => typeof id === "string");
14993
+ }
14994
+ function buildRecorder() {
14995
+ return async (payload) => {
14996
+ try {
14997
+ const creds = requireCredentialsFromEnv();
14998
+ const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
14999
+ await client.recordRun(payload);
15000
+ } catch (e) {
15001
+ const msg = e instanceof Error ? e.message : String(e);
15002
+ process.stderr.write(`[warn] run record not persisted (${msg})
15003
+ `);
15004
+ }
15005
+ };
15006
+ }
14672
15007
 
14673
15008
  // src/commands/canvas/scaffold-static-ad.ts
14674
- import { readFile as readFile3, writeFile } from "fs/promises";
14675
- import path6 from "path";
15009
+ import { access, mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
15010
+ import path9 from "path";
14676
15011
  import { defineCommand as defineCommand89 } from "citty";
14677
15012
 
14678
15013
  // src/engine/scaffold/staticAd.ts
14679
- import { z as z12 } from "zod";
15014
+ import { z as z11 } from "zod";
14680
15015
  var GEN_ASPECT_RATIOS = /* @__PURE__ */ new Set(["1:1", "4:5", "9:16", "16:9", "4:3", "3:4", "2:3", "3:2", "21:9"]);
14681
15016
  var DEFAULT_ASPECT_RATIO = "9:16";
14682
- var Blueprint = z12.object({
14683
- meta: z12.object({ estimated_aspect_ratio: z12.string().optional() }).loose().optional(),
14684
- text_content: z12.array(z12.object({ text: z12.string().optional() }).loose()).optional()
15017
+ var Blueprint = z11.object({
15018
+ meta: z11.object({ estimated_aspect_ratio: z11.string().optional() }).loose().optional(),
15019
+ text_content: z11.array(z11.object({ text: z11.string().optional() }).loose()).optional()
14685
15020
  }).loose();
14686
- var ElementLocator = z12.object({
14687
- collection: z12.enum(["subjects", "people", "brands_logos"]),
14688
- index: z12.number().int().nonnegative()
15021
+ var ElementLocator = z11.object({
15022
+ collection: z11.enum(["subjects", "people", "brands_logos"]),
15023
+ index: z11.number().int().nonnegative()
14689
15024
  }).loose();
14690
- var MainElement = z12.object({
15025
+ var MainElement = z11.object({
14691
15026
  // logo | product | person | animal | badge | other
14692
- type: z12.string(),
14693
- label: z12.string().optional(),
14694
- description: z12.string().optional(),
14695
- expression: z12.string().nullable().optional(),
14696
- reason: z12.string().optional(),
15027
+ type: z11.string(),
15028
+ label: z11.string().optional(),
15029
+ description: z11.string().optional(),
15030
+ expression: z11.string().nullable().optional(),
15031
+ reason: z11.string().optional(),
14697
15032
  locator: ElementLocator.optional()
14698
15033
  }).loose();
14699
- var MainElements = z12.array(MainElement);
15034
+ var MainElements = z11.array(MainElement);
14700
15035
  function sanitizeId(raw, fallback) {
14701
15036
  const id = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
14702
15037
  return /^[a-z]/.test(id) ? id : `${fallback}_${id}`.replace(/_+$/g, "") || fallback;
@@ -14858,18 +15193,175 @@ function staticAdReport(input, elementsInput, opts) {
14858
15193
  };
14859
15194
  }
14860
15195
 
15196
+ // src/commands/canvas/creative-definition.ts
15197
+ import path7 from "path";
15198
+ var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
15199
+ var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
15200
+ function titleFromSlug(slug) {
15201
+ const title = slug.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
15202
+ return title || slug;
15203
+ }
15204
+ function resolvePlatform(platform) {
15205
+ const value = platform?.trim();
15206
+ return value && PLATFORM_VALUES.includes(value) ? value : "meta";
15207
+ }
15208
+ function resolveFormats(aspect) {
15209
+ const value = aspect?.trim();
15210
+ return value && FORMAT_VALUES.includes(value) ? [value] : ["4:5"];
15211
+ }
15212
+ function referenceRelativePath(kind, ext) {
15213
+ const name = kind === "video" ? "source" : "original";
15214
+ return `references/${name}${ext}`;
15215
+ }
15216
+ function describeBlueprintIntent(blueprint) {
15217
+ const intent = blueprint?.ad_intent;
15218
+ if (typeof intent === "string" && intent.trim()) return intent.trim();
15219
+ if (intent && typeof intent === "object") {
15220
+ const summary = intent.summary ?? intent.feeling;
15221
+ if (typeof summary === "string" && summary.trim()) return summary.trim();
15222
+ }
15223
+ return void 0;
15224
+ }
15225
+ function yamlScalar(value) {
15226
+ return JSON.stringify(value);
15227
+ }
15228
+ function buildCreativeDefinition(input) {
15229
+ const lines = ["---", `title: ${yamlScalar(input.title)}`, `kind: ${input.kind}`, `platform: ${input.platform}`];
15230
+ lines.push(`formats: [${input.formats.map(yamlScalar).join(", ")}]`);
15231
+ lines.push(`status: ${input.status ?? "draft"}`);
15232
+ if (input.sourceReferenceUrl) lines.push(`sourceReferenceUrl: ${yamlScalar(input.sourceReferenceUrl)}`);
15233
+ if (input.sourceAdvertiser) lines.push(`sourceAdvertiser: ${yamlScalar(input.sourceAdvertiser)}`);
15234
+ if (input.sourceKind) lines.push(`sourceKind: ${input.sourceKind}`);
15235
+ if (input.sourcePath) lines.push(`sourcePath: ${yamlScalar(input.sourcePath)}`);
15236
+ lines.push("---", "");
15237
+ lines.push(input.description?.trim() || `${input.title} \u2014 canvas-built ${input.kind} ad for ${input.platform}.`);
15238
+ lines.push("");
15239
+ return lines.join("\n");
15240
+ }
15241
+
14861
15242
  // src/commands/canvas/scaffold-static-ad-paths.ts
14862
- import path5 from "path";
14863
- function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd()) {
15243
+ import path8 from "path";
15244
+ function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
14864
15245
  const file = rawFile.trim();
14865
15246
  const imageIsUrl = /^https?:\/\//i.test(file);
14866
- const imageSource = imageIsUrl ? file : path5.resolve(cwd, file);
14867
- const outPath = out ? path5.resolve(cwd, out) : imageIsUrl ? path5.join(cwd, "static-ad.canvas.json") : path5.join(path5.dirname(imageSource), "static-ad.canvas.json");
14868
- const blueprintPath = path5.join(path5.dirname(outPath), "prompt.json");
14869
- return { imageIsUrl, imageSource, outPath, blueprintPath };
15247
+ const imageSource = imageIsUrl ? file : path8.resolve(cwd, file);
15248
+ const outPath = out ? path8.resolve(cwd, out) : slug ? path8.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path8.join(cwd, "static-ad.canvas.json") : path8.join(path8.dirname(imageSource), "static-ad.canvas.json");
15249
+ const blueprintPath = path8.join(path8.dirname(outPath), "prompt.json");
15250
+ const creativeDir = slug ? path8.dirname(outPath) : null;
15251
+ const definitionPath = creativeDir ? path8.join(creativeDir, "_definition.md") : null;
15252
+ const referencesDir = creativeDir ? path8.join(creativeDir, "references") : null;
15253
+ return { imageIsUrl, imageSource, outPath, blueprintPath, creativeDir, definitionPath, referencesDir };
15254
+ }
15255
+ var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
15256
+ var SCAFFOLD_SLUG_MAX_LENGTH = 100;
15257
+ function isValidScaffoldSlug(slug) {
15258
+ return slug.length <= SCAFFOLD_SLUG_MAX_LENGTH && SCAFFOLD_SLUG_PATTERN.test(slug);
15259
+ }
15260
+
15261
+ // src/commands/canvas/definition-graph.ts
15262
+ var MAX_NODES = 300;
15263
+ function walkStrings(value, cb) {
15264
+ if (typeof value === "string") {
15265
+ cb(value);
15266
+ return;
15267
+ }
15268
+ if (Array.isArray(value)) {
15269
+ for (const v of value) walkStrings(v, cb);
15270
+ return;
15271
+ }
15272
+ if (value && typeof value === "object") {
15273
+ for (const v of Object.values(value)) walkStrings(v, cb);
15274
+ }
15275
+ }
15276
+ function canvasToDefinitionGraph(canvas) {
15277
+ const rawNodes = canvas?.nodes;
15278
+ if (!Array.isArray(rawNodes)) return null;
15279
+ const parsed = [];
15280
+ for (const raw of rawNodes) {
15281
+ const id = raw?.id;
15282
+ const type = raw?.type;
15283
+ if (typeof id !== "string" || typeof type !== "string") continue;
15284
+ parsed.push({ id, type, inputs: raw.inputs, params: raw.params });
15285
+ if (parsed.length >= MAX_NODES) break;
15286
+ }
15287
+ if (parsed.length === 0) return null;
15288
+ const ids = new Set(parsed.map((n) => n.id));
15289
+ const nodes = parsed.map(({ id, type, inputs, params }) => {
15290
+ const deps = /* @__PURE__ */ new Set();
15291
+ const collect = (s) => {
15292
+ if (!s.startsWith(REF_PREFIX)) return;
15293
+ const expr = parseRefExpr(s);
15294
+ if (expr && expr.nodeId !== id && ids.has(expr.nodeId)) deps.add(expr.nodeId);
15295
+ };
15296
+ walkStrings(inputs, collect);
15297
+ walkStrings(params, collect);
15298
+ return deps.size > 0 ? { id, type, deps: [...deps] } : { id, type };
15299
+ });
15300
+ const rawOutput = canvas?.output;
15301
+ const outNode = rawOutput?.node;
15302
+ const outSlot = rawOutput?.output;
15303
+ const output = typeof outNode === "string" && typeof outSlot === "string" ? { node: outNode, output: outSlot } : void 0;
15304
+ return { nodes, output };
15305
+ }
15306
+
15307
+ // src/commands/canvas/sync-definition.ts
15308
+ async function syncCreativeDefinitionBestEffort(input) {
15309
+ const chatId = process.env.BAKER_CHAT_ID;
15310
+ if (!chatId) return;
15311
+ const graph = canvasToDefinitionGraph(input.canvas);
15312
+ if (!graph || graph.nodes.length === 0) return;
15313
+ try {
15314
+ const creds = requireCredentialsFromEnv();
15315
+ const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
15316
+ await client.syncCreativeDefinition({
15317
+ slug: input.slug,
15318
+ title: input.title,
15319
+ platform: input.platform,
15320
+ formats: input.formats,
15321
+ sourceReferenceUrl: input.sourceReferenceUrl,
15322
+ graph,
15323
+ chatId
15324
+ });
15325
+ process.stdout.write(`[definition] synced workflow graph (${graph.nodes.length} nodes) \u2014 view it in the dashboard
15326
+ `);
15327
+ } catch (e) {
15328
+ const msg = e instanceof Error ? e.message : String(e);
15329
+ process.stderr.write(`[warn] workflow graph not synced (${msg})
15330
+ `);
15331
+ }
14870
15332
  }
14871
15333
 
14872
15334
  // src/commands/canvas/scaffold-static-ad.ts
15335
+ async function fileExists(target) {
15336
+ try {
15337
+ await access(target);
15338
+ return true;
15339
+ } catch {
15340
+ return false;
15341
+ }
15342
+ }
15343
+ var MODEL_SAFE_EXT_BY_MIME = {
15344
+ "image/png": ".png",
15345
+ "image/jpeg": ".jpg",
15346
+ "image/gif": ".gif",
15347
+ "image/webp": ".webp"
15348
+ };
15349
+ async function copySourceIntoReferences(source, isUrl, referencesDir) {
15350
+ await mkdir2(referencesDir, { recursive: true });
15351
+ let bytes;
15352
+ if (isUrl) {
15353
+ const res = await fetch(source);
15354
+ if (!res.ok) throw new Error(`failed to download source image (${res.status})`);
15355
+ bytes = Buffer.from(await res.arrayBuffer());
15356
+ } else {
15357
+ bytes = await readFile4(source);
15358
+ }
15359
+ const safe = await toModelSafeImage(bytes);
15360
+ const relPath = referenceRelativePath("image", MODEL_SAFE_EXT_BY_MIME[safe.mime] ?? ".png");
15361
+ const dest = path9.join(referencesDir, path9.basename(relPath));
15362
+ await writeFile2(dest, safe.bytes);
15363
+ return relPath;
15364
+ }
14873
15365
  function resolveModel(kind, preferred) {
14874
15366
  const ids = Object.keys(MODEL_REGISTRY[kind]);
14875
15367
  return ids.includes(preferred) ? preferred : ids[0] ?? preferred;
@@ -14912,7 +15404,7 @@ DROP background extras, decorative props, generic scenery, and anything small or
14912
15404
  For each kept element return: { "type": one of logo|product|person|animal|badge, "label": a short UPPER_SNAKE_CASE name (e.g. LOGO, PRODUCT, HERO_DOG, TRUSTPILOT), "description": a concrete reusable description to source/shoot the real asset (include the exact expression for a living subject, and its castable attributes \u2014 breed/species for an animal, apparent age band, apparent origin/ethnicity, and wardrobe/setting for a person \u2014 so it can be recast to fit OUR audience/market), "expression": the facial expression for a living subject or null, "reason": why it is identity-critical, "locator": the blueprint entry this element came from as { "collection": one of "subjects" | "people" | "brands_logos", "index": its 0-based position in that array } (people -> people; logos/badges -> brands_logos; products/animals/objects -> subjects). Output ONLY the JSON object.`;
14913
15405
  async function loadAssetText(ref, label) {
14914
15406
  const r = ref;
14915
- if (typeof r?.path === "string") return readFile3(r.path, "utf8");
15407
+ if (typeof r?.path === "string") return readFile4(r.path, "utf8");
14916
15408
  if (typeof r?.url === "string") {
14917
15409
  const res = await fetch(r.url);
14918
15410
  if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
@@ -15029,6 +15521,16 @@ var scaffoldStaticAdCommand = defineCommand89({
15029
15521
  file: { type: "positional", required: true, description: "Path or http(s) URL to the source/inspiration image" },
15030
15522
  context: { type: "string", description: "Known provenance (advertiser, category, market) to ground the describe" },
15031
15523
  out: { type: "string", description: "Output canvas path (default <image-dir>/static-ad.canvas.json)" },
15524
+ slug: {
15525
+ type: "string",
15526
+ description: "Creative slug \u2014 writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)"
15527
+ },
15528
+ title: { type: "string", description: "Creative title for _definition.md (default: title-cased slug)" },
15529
+ platform: {
15530
+ type: "string",
15531
+ description: "Ad platform for _definition.md (meta|google|linkedin|tiktok|youtube|x|other; default meta)"
15532
+ },
15533
+ advertiser: { type: "string", description: "Source advertiser recorded in _definition.md" },
15032
15534
  "describe-model": { type: "string", description: "Override the image_describe model id" },
15033
15535
  "select-model": { type: "string", description: "Override the text_generate model id for element selection" },
15034
15536
  "layout-model": { type: "string", description: "Override the text_generate model id for the layout pass" },
@@ -15037,10 +15539,21 @@ var scaffoldStaticAdCommand = defineCommand89({
15037
15539
  "skip-font": { type: "boolean", description: "Skip the brand-font \u2192 type-specimen slot" }
15038
15540
  },
15039
15541
  async run({ args }) {
15040
- const { imageIsUrl, imageSource, outPath, blueprintPath } = resolveScaffoldStaticAdPaths(
15542
+ const slug = args.slug ? String(args.slug) : void 0;
15543
+ if (slug && !isValidScaffoldSlug(slug)) {
15544
+ process.stderr.write(
15545
+ `${JSON.stringify({ ok: false, error: { code: "invalid_slug", message: "--slug must be lowercase kebab (a-z, 0-9, hyphens), max 100 chars" } }, null, 2)}
15546
+ `
15547
+ );
15548
+ process.exit(2);
15549
+ }
15550
+ const { imageIsUrl, imageSource, outPath, blueprintPath, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(
15041
15551
  String(args.file),
15042
- args.out ? String(args.out) : void 0
15552
+ args.out ? String(args.out) : void 0,
15553
+ process.cwd(),
15554
+ slug
15043
15555
  );
15556
+ await mkdir2(path9.dirname(outPath), { recursive: true });
15044
15557
  const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
15045
15558
  const describeCanvas = buildDescribeCanvas(
15046
15559
  imageSource,
@@ -15055,13 +15568,23 @@ var scaffoldStaticAdCommand = defineCommand89({
15055
15568
  if (layout && annotated && typeof annotated === "object") {
15056
15569
  annotated.layout = layout;
15057
15570
  }
15058
- await writeFile(blueprintPath, `${JSON.stringify(annotated, null, 2)}
15571
+ await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
15059
15572
  `, "utf8");
15573
+ let canvasImagePath = imageSource;
15574
+ let canvasImageIsUrl = imageIsUrl;
15575
+ let canvasBlueprintPath = blueprintPath;
15576
+ let sourceRelPath;
15577
+ if (referencesDir) {
15578
+ sourceRelPath = await copySourceIntoReferences(imageSource, imageIsUrl, referencesDir);
15579
+ canvasImagePath = sourceRelPath;
15580
+ canvasImageIsUrl = false;
15581
+ canvasBlueprintPath = "./prompt.json";
15582
+ }
15060
15583
  const opts = {
15061
15584
  genModel,
15062
- imagePath: imageSource,
15063
- imageIsUrl,
15064
- blueprintPath,
15585
+ imagePath: canvasImagePath,
15586
+ imageIsUrl: canvasImageIsUrl,
15587
+ blueprintPath: canvasBlueprintPath,
15065
15588
  aspectRatio: args.aspect ? String(args.aspect) : void 0,
15066
15589
  includeFont: !args["skip-font"]
15067
15590
  };
@@ -15081,14 +15604,43 @@ var scaffoldStaticAdCommand = defineCommand89({
15081
15604
  );
15082
15605
  process.exit(2);
15083
15606
  }
15084
- await writeFile(outPath, `${JSON.stringify(canvas, null, 2)}
15607
+ await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
15085
15608
  `, "utf8");
15609
+ if (definitionPath && !await fileExists(definitionPath)) {
15610
+ await writeFile2(
15611
+ definitionPath,
15612
+ buildCreativeDefinition({
15613
+ title: args.title ? String(args.title) : titleFromSlug(slug ?? ""),
15614
+ kind: "static",
15615
+ platform: resolvePlatform(args.platform ? String(args.platform) : void 0),
15616
+ formats: resolveFormats(args.aspect ? String(args.aspect) : report.aspect_ratio),
15617
+ sourceReferenceUrl: imageIsUrl ? imageSource : void 0,
15618
+ sourceAdvertiser: args.advertiser ? String(args.advertiser) : args.context ? String(args.context) : void 0,
15619
+ sourceKind: "image",
15620
+ sourcePath: sourceRelPath,
15621
+ description: describeBlueprintIntent(blueprint)
15622
+ }),
15623
+ "utf8"
15624
+ );
15625
+ }
15626
+ if (slug) {
15627
+ await syncCreativeDefinitionBestEffort({
15628
+ slug,
15629
+ title: args.title ? String(args.title) : titleFromSlug(slug),
15630
+ platform: resolvePlatform(args.platform ? String(args.platform) : void 0),
15631
+ formats: resolveFormats(args.aspect ? String(args.aspect) : report.aspect_ratio),
15632
+ sourceReferenceUrl: imageIsUrl ? imageSource : void 0,
15633
+ canvas
15634
+ });
15635
+ }
15086
15636
  process.stdout.write(
15087
15637
  `${JSON.stringify(
15088
15638
  {
15089
15639
  ok: true,
15090
15640
  canvas_path: outPath,
15091
15641
  prompt_path: blueprintPath,
15642
+ definition_path: definitionPath ?? void 0,
15643
+ source_reference: sourceRelPath ?? void 0,
15092
15644
  output: canvas.output,
15093
15645
  models: { describe: describeModel, select: selectModel, layout: layoutModel, gen: opts.genModel },
15094
15646
  aspect_ratio: report.aspect_ratio,
@@ -15099,10 +15651,10 @@ var scaffoldStaticAdCommand = defineCommand89({
15099
15651
  run_estimated_credits: validation.estimatedCredits
15100
15652
  },
15101
15653
  checklist: {
15102
- edit_prompt: `Edit ${path6.basename(blueprintPath)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
15654
+ edit_prompt: `Edit ${path9.basename(blueprintPath)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
15103
15655
  assets_to_supply: report.elements,
15104
15656
  font_slot: report.includes_font ? "Drop a brand font at the [TODO] brandfont path, or delete the brandfont + type_ref nodes to skip it." : "skipped (--skip-font)",
15105
- note: "Replace every [TODO] ingest path with a real file, then `baker canvas validate` and `baker canvas run`. Running generates a billed image \u2014 it is not free."
15657
+ note: "Populate as you go: for each [TODO] ingest slot, source its real asset and wire it into the slot right away \u2014 one at a time, not all sourced first then reconciled at the end. When every slot is filled, `baker canvas validate` then `baker canvas run`. Running generates a billed image \u2014 it is not free."
15106
15658
  }
15107
15659
  },
15108
15660
  null,
@@ -15114,13 +15666,14 @@ var scaffoldStaticAdCommand = defineCommand89({
15114
15666
  });
15115
15667
 
15116
15668
  // src/commands/canvas/scaffold-video.ts
15117
- import { cp, mkdir, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
15118
- import path9 from "path";
15669
+ import { access as access2, cp, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
15670
+ import { tmpdir as tmpdir2 } from "os";
15671
+ import path12 from "path";
15119
15672
  import { defineCommand as defineCommand90 } from "citty";
15120
15673
 
15121
15674
  // src/engine/nodes/local/lib/sceneDetect.ts
15122
15675
  import { execFile as execFile2 } from "child_process";
15123
- import { mkdtemp, readdir as readdir2, readFile as readFile4, rm as rm2 } from "fs/promises";
15676
+ import { mkdtemp, readdir as readdir2, readFile as readFile5, rm as rm3 } from "fs/promises";
15124
15677
  import { tmpdir } from "os";
15125
15678
  import { join as join2 } from "path";
15126
15679
  import { promisify as promisify2 } from "util";
@@ -15196,9 +15749,9 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
15196
15749
  );
15197
15750
  const csvName = (await readdir2(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
15198
15751
  if (!csvName) return [];
15199
- return parsePySceneDetectCsvCuts(await readFile4(join2(outDir, csvName), "utf-8"));
15752
+ return parsePySceneDetectCsvCuts(await readFile5(join2(outDir, csvName), "utf-8"));
15200
15753
  } finally {
15201
- await rm2(outDir, { recursive: true, force: true });
15754
+ await rm3(outDir, { recursive: true, force: true });
15202
15755
  }
15203
15756
  }
15204
15757
  async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
@@ -15232,7 +15785,7 @@ import { toCardinal as nwKo } from "n2words/ko-KR";
15232
15785
  import { toCardinal as nwNl } from "n2words/nl-NL";
15233
15786
  import { toCardinal as nwPl } from "n2words/pl-PL";
15234
15787
  import { toCardinal as nwPt } from "n2words/pt-PT";
15235
- import { z as z13 } from "zod";
15788
+ import { z as z12 } from "zod";
15236
15789
 
15237
15790
  // src/engine/scaffold/lib/shoot-modes.ts
15238
15791
  var SHOOT_MODES = [
@@ -15545,71 +16098,71 @@ function trimArgs(durationS, offsetS = 0, dims) {
15545
16098
  "{{out.video}}"
15546
16099
  ];
15547
16100
  }
15548
- var FrameAsset = z13.object({ url: z13.string().optional() }).loose().optional();
15549
- var DialogueLine = z13.object({
15550
- speaker: z13.string().optional(),
15551
- line: z13.string().optional(),
16101
+ var FrameAsset = z12.object({ url: z12.string().optional() }).loose().optional();
16102
+ var DialogueLine = z12.object({
16103
+ speaker: z12.string().optional(),
16104
+ line: z12.string().optional(),
15552
16105
  // Absolute seconds on the source timeline (the deconstruct emits both).
15553
- start_s: z13.number().optional(),
15554
- end_s: z13.number().optional(),
15555
- delivery: z13.string().optional(),
15556
- voice_description: z13.string().optional(),
16106
+ start_s: z12.number().optional(),
16107
+ end_s: z12.number().optional(),
16108
+ delivery: z12.string().optional(),
16109
+ voice_description: z12.string().optional(),
15557
16110
  // DECON-supplied: is this speaker's FACE visibly speaking in THIS scene? Element
15558
16111
  // presence alone can't answer that — a founder pictured in a polaroid close-up is
15559
16112
  // "present" yet the line is voiceover, and treating it as on-camera produced a
15560
16113
  // native Seedance lip-sync clip of a still photograph. `false` pins the line to
15561
16114
  // the VO path; absent keeps the presence-based decision (old blueprints).
15562
- on_camera: z13.boolean().optional()
16115
+ on_camera: z12.boolean().optional()
15563
16116
  }).loose();
15564
- var Sfx = z13.object({
15565
- at_s: z13.number().optional(),
15566
- duration_s: z13.number().optional(),
15567
- sound_effect_prompt: z13.string().optional(),
15568
- description: z13.string().optional()
16117
+ var Sfx = z12.object({
16118
+ at_s: z12.number().optional(),
16119
+ duration_s: z12.number().optional(),
16120
+ sound_effect_prompt: z12.string().optional(),
16121
+ description: z12.string().optional()
15569
16122
  }).loose();
15570
- var CompositionRegion = z13.object({
16123
+ var CompositionRegion = z12.object({
15571
16124
  // full | top | bottom | left | right | inset
15572
- panel: z13.string().optional(),
16125
+ panel: z12.string().optional(),
15573
16126
  // 9-grid anchor for an `inset` presenter box.
15574
- position: z13.string().optional(),
15575
- is_presenter: z13.boolean().optional(),
16127
+ position: z12.string().optional(),
16128
+ is_presenter: z12.boolean().optional(),
15576
16129
  // The cast id shown/speaking in this region (routes lip-sync + element refs).
15577
- cast_ref: z13.string().optional(),
16130
+ cast_ref: z12.string().optional(),
15578
16131
  // What the region's content IS: camera | screen_capture | static_graphic |
15579
16132
  // generated. Authoritative for routing when present (regex-over-prose fallback
15580
16133
  // otherwise): screen_capture/static_graphic are rebuilt from REAL surfaces on the
15581
16134
  // overlay layer, never AI-generated.
15582
- kind: z13.string().optional(),
16135
+ kind: z12.string().optional(),
15583
16136
  // Opaque id naming the SPECIFIC on-screen document/note/app-state this
15584
16137
  // screen_capture region shows. Two scenes share it only when they show the SAME
15585
16138
  // recording continuing (scrolling/typing/waiting within it) — a genuinely
15586
16139
  // DIFFERENT document/note/recording (a source video splicing two screen captures)
15587
16140
  // gets a different id. Breaks a persistent-layout run into separate surface stubs
15588
16141
  // instead of asking the operator for one screenshot that can't cover both.
15589
- surface_id: z13.string().optional(),
16142
+ surface_id: z12.string().optional(),
15590
16143
  // Camera bubble(s)/inset(s) embedded INSIDE this region's surface (a Loom-style
15591
16144
  // presenter bubble inside a screen recording) — video-in-video the reproduction
15592
16145
  // must re-composite, not paint into the surface.
15593
- nested: z13.array(z13.object({}).loose()).optional(),
15594
- summary: z13.string().optional(),
15595
- frame_prompt: z13.string().optional(),
15596
- motion_prompt: z13.string().optional()
16146
+ nested: z12.array(z12.object({}).loose()).optional(),
16147
+ summary: z12.string().optional(),
16148
+ frame_prompt: z12.string().optional(),
16149
+ motion_prompt: z12.string().optional()
15597
16150
  }).loose();
15598
- var SceneComposition = z13.object({
16151
+ var SceneComposition = z12.object({
15599
16152
  // full_frame (default) | split_screen | pip | keyed_overlay
15600
- layout: z13.string().optional(),
16153
+ layout: z12.string().optional(),
15601
16154
  // split_screen only: vertical (top/bottom) | horizontal (left/right).
15602
- split_axis: z13.string().optional(),
15603
- regions: z13.array(CompositionRegion).optional()
16155
+ split_axis: z12.string().optional(),
16156
+ regions: z12.array(CompositionRegion).optional()
15604
16157
  }).loose();
15605
- var CameraMotion = z13.object({ movement: z13.string().optional(), detail: z13.string().optional() }).loose();
15606
- var TranscriptWord = z13.object({ text: z13.string().optional() }).loose();
15607
- var Scene = z13.object({
15608
- start_s: z13.number().optional(),
15609
- end_s: z13.number().optional(),
15610
- duration_s: z13.number().optional(),
15611
- summary: z13.string().optional(),
15612
- action_detail: z13.string().optional(),
16158
+ var CameraMotion = z12.object({ movement: z12.string().optional(), detail: z12.string().optional() }).loose();
16159
+ var TranscriptWord = z12.object({ text: z12.string().optional() }).loose();
16160
+ var Scene = z12.object({
16161
+ start_s: z12.number().optional(),
16162
+ end_s: z12.number().optional(),
16163
+ duration_s: z12.number().optional(),
16164
+ summary: z12.string().optional(),
16165
+ action_detail: z12.string().optional(),
15613
16166
  // The scene's spatial layout. Absent/full_frame ⇒ one uncut shot (default path).
15614
16167
  // A layered layout (split_screen/pip/keyed_overlay) with regions ⇒ the scaffold
15615
16168
  // builds one clip per region and stacks/overlays them into the scene picture.
@@ -15617,82 +16170,82 @@ var Scene = z13.object({
15617
16170
  // The capture "look" for this scene — selected from the ad-native shoot-mode
15618
16171
  // grammar (see lib/shoot-modes.ts). When absent the scaffold auto-derives a
15619
16172
  // UGC/product mode; a human can override per scene by setting this.
15620
- shoot_mode: z13.string().optional(),
16173
+ shoot_mode: z12.string().optional(),
15621
16174
  // Diegetic ambient the clip's native audio should carry (no music). When
15622
16175
  // absent the scene falls back to its shoot mode's default ambience.
15623
- ambient: z13.string().optional(),
16176
+ ambient: z12.string().optional(),
15624
16177
  camera_motion: CameraMotion.optional(),
15625
- start_frame_prompt: z13.string().optional(),
15626
- end_frame_prompt: z13.string().optional(),
15627
- motion_prompt: z13.string().optional(),
16178
+ start_frame_prompt: z12.string().optional(),
16179
+ end_frame_prompt: z12.string().optional(),
16180
+ motion_prompt: z12.string().optional(),
15628
16181
  // The scene's role in the ad's persuasion arc (DECON-supplied); drives the
15629
16182
  // script re-craft checklist. Inferred from position when absent.
15630
- narrative_role: z13.string().optional(),
16183
+ narrative_role: z12.string().optional(),
15631
16184
  // DECON-supplied on the HOOK scene: the engineered physical/emotional state that
15632
16185
  // makes the first frame stop the scroll (sweaty/breathless/urgent …). Injected
15633
16186
  // into the hook's start-frame description so the generator renders that state,
15634
16187
  // not a calm influencer (CCA-11).
15635
- hook_mechanic: z13.object({ mechanic: z13.string().optional(), why_it_stops_scroll: z13.string().optional() }).loose().optional(),
16188
+ hook_mechanic: z12.object({ mechanic: z12.string().optional(), why_it_stops_scroll: z12.string().optional() }).loose().optional(),
15636
16189
  // DECON-supplied per-scene location (so a gym hook isn't flattened to "home").
15637
- scene_setting: z13.string().optional(),
16190
+ scene_setting: z12.string().optional(),
15638
16191
  // How this scene cuts to the next (DECON-supplied). A recognized non-cut type
15639
16192
  // (fade/whip/zoom/dissolve/swipe) is reproduced as an ffmpeg xfade at the
15640
16193
  // boundary; cut/match_cut/none/other stay hard cuts. The last scene's value is
15641
16194
  // ignored (nothing follows it).
15642
- transition_out: z13.object({ type: z13.string().optional(), description: z13.string().optional() }).loose().optional(),
15643
- dialogue: z13.array(DialogueLine).optional(),
15644
- sfx: z13.array(Sfx).optional(),
15645
- overlays: z13.array(z13.unknown()).optional(),
15646
- floating_elements: z13.array(z13.unknown()).optional(),
16195
+ transition_out: z12.object({ type: z12.string().optional(), description: z12.string().optional() }).loose().optional(),
16196
+ dialogue: z12.array(DialogueLine).optional(),
16197
+ sfx: z12.array(Sfx).optional(),
16198
+ overlays: z12.array(z12.unknown()).optional(),
16199
+ floating_elements: z12.array(z12.unknown()).optional(),
15647
16200
  // DECON-supplied: how much the picture itself moves within the shot. Gates the
15648
16201
  // flash-hold optimization — a sub-2s b-roll flash with REAL subject motion
15649
16202
  // (pouring, spreading, hands working) must stay a real clip; freezing it turns
15650
16203
  // a montage into a slideshow. Absent (old blueprints) keeps the cheap still.
15651
- motion_level: z13.enum(["static", "subtle", "dynamic"]).optional(),
15652
- transcript_slice: z13.array(TranscriptWord).optional(),
16204
+ motion_level: z12.enum(["static", "subtle", "dynamic"]).optional(),
16205
+ transcript_slice: z12.array(TranscriptWord).optional(),
15653
16206
  start_frame_asset: FrameAsset,
15654
16207
  end_frame_asset: FrameAsset,
15655
16208
  // DECON-supplied: true when this scene is a length-split CONTINUATION of the
15656
16209
  // previous one (the SAME physical shot, broken up only because it exceeded the
15657
16210
  // clip ceiling). The scaffold then shares the splice keyframe — this scene's
15658
16211
  // start frame IS the previous scene's end frame — so the join is seamless.
15659
- continues_previous: z13.boolean().optional()
16212
+ continues_previous: z12.boolean().optional()
15660
16213
  }).loose();
15661
- var VideoBlueprint = z13.object({
15662
- source: z13.object({ aspect_ratio: z13.string().optional(), duration_s: z13.number().optional() }).loose().optional(),
15663
- global: z13.object({
15664
- music: z13.object({
15665
- present: z13.boolean().optional(),
15666
- music_prompt: z13.string().optional(),
16214
+ var VideoBlueprint = z12.object({
16215
+ source: z12.object({ aspect_ratio: z12.string().optional(), duration_s: z12.number().optional() }).loose().optional(),
16216
+ global: z12.object({
16217
+ music: z12.object({
16218
+ present: z12.boolean().optional(),
16219
+ music_prompt: z12.string().optional(),
15667
16220
  // Absolute second the music enters in the reference (the bed often
15668
16221
  // kicks in mid-ad, after the hook). We start the regenerated track here
15669
16222
  // instead of at 0 so the timing matches.
15670
- starts_at_s: z13.number().optional(),
16223
+ starts_at_s: z12.number().optional(),
15671
16224
  // Populated by the deconstruct when AudD (Shazam-style) recognizes the
15672
16225
  // reference track. We never reuse it — only style the regenerated bed.
15673
- identified_track: z13.object({ title: z13.string().optional(), artist: z13.string().optional() }).loose().nullish()
16226
+ identified_track: z12.object({ title: z12.string().optional(), artist: z12.string().optional() }).loose().nullish()
15674
16227
  }).loose().optional(),
15675
- cast: z13.array(
15676
- z13.object({
15677
- id: z13.string().optional(),
15678
- description: z13.string().optional(),
16228
+ cast: z12.array(
16229
+ z12.object({
16230
+ id: z12.string().optional(),
16231
+ description: z12.string().optional(),
15679
16232
  // The deconstruct's note on the target-market localization (e.g. "native
15680
16233
  // French speaker") — read to derive the spoken-track language code.
15681
- market_localization_note: z13.string().optional()
16234
+ market_localization_note: z12.string().optional()
15682
16235
  }).loose()
15683
16236
  ).optional(),
15684
- voiceover: z13.object({
16237
+ voiceover: z12.object({
15685
16238
  // on_camera | mixed → mouths are on screen (lip-sync candidates);
15686
16239
  // voiceover | none → narration over the picture (no lip-sync).
15687
- mode: z13.string().optional(),
15688
- voice_description: z13.string().optional(),
15689
- persona: z13.string().optional()
16240
+ mode: z12.string().optional(),
16241
+ voice_description: z12.string().optional(),
16242
+ persona: z12.string().optional()
15690
16243
  }).loose().optional(),
15691
16244
  // Visual palette — read only to colour a clean brand-card/CTA plate (the
15692
16245
  // first hex is the dominant brand colour); never to drive frame generation.
15693
- style: z13.object({ palette: z13.array(z13.object({ hex: z13.string().optional() }).loose()).optional() }).loose().optional()
16246
+ style: z12.object({ palette: z12.array(z12.object({ hex: z12.string().optional() }).loose()).optional() }).loose().optional()
15694
16247
  }).loose().optional(),
15695
- scenes: z13.array(Scene).min(1)
16248
+ scenes: z12.array(Scene).min(1)
15696
16249
  }).loose();
15697
16250
  function injectHookPhysicality(blueprint) {
15698
16251
  for (const scene of blueprint.scenes) {
@@ -15702,26 +16255,26 @@ function injectHookPhysicality(blueprint) {
15702
16255
  scene.start_frame_prompt = `${prompt} The subject's physical state IS the scroll-stopper \u2014 render it explicitly, not a calm pose: ${why}.`;
15703
16256
  }
15704
16257
  }
15705
- var AppearsItem = z13.union([z13.number(), z13.object({ scene: z13.number(), edge: z13.string().optional() }).loose()]);
15706
- var RecurringElement = z13.object({
16258
+ var AppearsItem = z12.union([z12.number(), z12.object({ scene: z12.number(), edge: z12.string().optional() }).loose()]);
16259
+ var RecurringElement = z12.object({
15707
16260
  // person | animal | product | logo | badge | other
15708
- type: z13.string(),
15709
- label: z13.string().optional(),
15710
- description: z13.string().optional(),
15711
- expression: z13.string().nullable().optional(),
16261
+ type: z12.string(),
16262
+ label: z12.string().optional(),
16263
+ description: z12.string().optional(),
16264
+ expression: z12.string().nullable().optional(),
15712
16265
  // When the element maps to a global cast entry, its stable id (for annotation).
15713
- cast_id: z13.string().nullable().optional(),
16266
+ cast_id: z12.string().nullable().optional(),
15714
16267
  // The label of another element that is the SAME individual as this one, shown
15715
16268
  // in a DIFFERENT wardrobe/persona/state (e.g. one creator playing skeptic in a
15716
16269
  // pink shirt and believer in a white shirt). Each look gets its own reference
15717
16270
  // slot, but the face/identity must stay identical across them.
15718
- same_as: z13.string().nullable().optional(),
16271
+ same_as: z12.string().nullable().optional(),
15719
16272
  // Scenes the element appears in. Either a bare list of scene indices (both
15720
16273
  // edges) or per-{scene,edge} entries. Both forms are accepted and merged.
15721
- scenes: z13.array(z13.number()).optional(),
15722
- appears_in: z13.array(AppearsItem).optional()
16274
+ scenes: z12.array(z12.number()).optional(),
16275
+ appears_in: z12.array(AppearsItem).optional()
15723
16276
  }).loose();
15724
- var RecurringElements = z13.array(RecurringElement);
16277
+ var RecurringElements = z12.array(RecurringElement);
15725
16278
  function sanitizeId2(raw, fallback) {
15726
16279
  const id = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
15727
16280
  return /^[a-z]/.test(id) ? id : `${fallback}_${id}`.replace(/_+$/g, "") || fallback;
@@ -15823,6 +16376,13 @@ function slimBlueprintForSelection(blueprintInput) {
15823
16376
  }
15824
16377
  return out;
15825
16378
  }
16379
+ function slimBlueprintForFrameStyle(blueprintInput) {
16380
+ if (!blueprintInput || typeof blueprintInput !== "object" || Array.isArray(blueprintInput)) return blueprintInput;
16381
+ const bp = blueprintInput;
16382
+ const out = {};
16383
+ for (const k of ["version", "source", "global", "reference_elements"]) if (k in bp) out[k] = bp[k];
16384
+ return out;
16385
+ }
15826
16386
  function roleForType2(type) {
15827
16387
  switch (type.toLowerCase()) {
15828
16388
  case "logo":
@@ -16080,9 +16640,10 @@ function buildFrameRef(edge, url, framePrompt, present, ctx, nodes) {
16080
16640
  id: genId,
16081
16641
  type: "image_generate",
16082
16642
  // `params.prompt` is this frame's authoritative, edit-per-frame description.
16083
- // `target_blueprint` is the shared ad spec (cast identity, palette, brand, type)
16084
- // the frame must stay consistent with editing one frame never touches another.
16085
- inputs: { target_blueprint: "$ref:prompt.asset", ...reference.length > 0 ? { reference } : {} },
16643
+ // `target_blueprint` is the SLIM shared ad spec (global cast identity, palette, brand,
16644
+ // type — no per-scene content) the frame must stay consistent with; editing one frame
16645
+ // never touches another, and no image inlines the whole film to render one frame.
16646
+ inputs: { target_blueprint: "$ref:prompt_style.asset", ...reference.length > 0 ? { reference } : {} },
16086
16647
  params: genParams
16087
16648
  });
16088
16649
  return `$ref:${genId}.images#0`;
@@ -16139,7 +16700,7 @@ function scrubFloatSentences(text, floatDescs) {
16139
16700
  return kept;
16140
16701
  }
16141
16702
  function sceneFloatDescs(scene) {
16142
- const floats = z13.array(FloatingElement).safeParse(scene.floating_elements ?? []);
16703
+ const floats = z12.array(FloatingElement).safeParse(scene.floating_elements ?? []);
16143
16704
  if (!floats.success) return [];
16144
16705
  return floats.data.map((f) => f.description?.trim() ?? "").filter(Boolean);
16145
16706
  }
@@ -16604,18 +17165,24 @@ function emitFlashHold(i, scene, slots, ctx, lengths, out, outAr, nodes, clips)
16604
17165
  });
16605
17166
  clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
16606
17167
  }
16607
- function emitScreenScene(i, scene, lengths, out, outAr, nodes, clips) {
16608
- const label = commentSafe((scene.summary || scene.start_frame_prompt || "the app screen").slice(0, 120));
16609
- const refId = `s${i}_screen_ref`;
16610
- nodes.push({
16611
- id: refId,
16612
- type: "ingest",
16613
- params: {
16614
- source: "path",
16615
- path: `[TODO: supply the REAL screen for "${label}" \u2014 NEVER AI-generate a UI. Capture a clean, text-free screenshot with \`baker images screenshot https://<brand-domain>/<path>\` (image-library skill); spoken/overlay text rides the overlay layer, not the screenshot]`,
16616
- expect: "image"
16617
- }
16618
- });
17168
+ function emitScreenScene(i, scene, lengths, out, outAr, surfaceIngests, nodes, clips) {
17169
+ const regions = (scene.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
17170
+ const surfaceId = regions.find((r) => r.surface_id)?.surface_id;
17171
+ let refId = surfaceId ? surfaceIngests.get(surfaceId) : void 0;
17172
+ if (!refId) {
17173
+ const label = commentSafe((scene.summary || scene.start_frame_prompt || "the app screen").slice(0, 120));
17174
+ refId = `s${i}_screen_ref`;
17175
+ nodes.push({
17176
+ id: refId,
17177
+ type: "ingest",
17178
+ params: {
17179
+ source: "path",
17180
+ path: `[TODO: supply the REAL screen for "${label}" \u2014 NEVER AI-generate a UI. Capture a clean, text-free screenshot with \`baker images screenshot https://<brand-domain>/<path>\` (image-library skill); spoken/overlay text rides the overlay layer, not the screenshot]`,
17181
+ expect: "image"
17182
+ }
17183
+ });
17184
+ if (surfaceId) surfaceIngests.set(surfaceId, refId);
17185
+ }
16619
17186
  nodes.push({
16620
17187
  id: `s${i}_clip`,
16621
17188
  type: "ffmpeg",
@@ -16859,9 +17426,16 @@ function makePresenterPresent(slots, canonical, opts = {}) {
16859
17426
  return presence.has(sceneIndex);
16860
17427
  };
16861
17428
  }
16862
- var PAUSE_GAP_S = 0.6;
16863
17429
  var SEEDANCE_SAFE_MAX_S = SEEDANCE_DURATIONS.find((d) => d >= 10) ?? 10;
16864
17430
  var PHRASE_MAX_S = SEEDANCE_SAFE_MAX_S;
17431
+ var PAUSE_GAP_S = 0.6;
17432
+ function isAdjacentShownCut(ln, lastShownScene, scenes) {
17433
+ if (!ln.shown || lastShownScene === null) return false;
17434
+ return ln.sceneIndex === lastShownScene + 1 && scenes[ln.sceneIndex]?.continues_previous !== true;
17435
+ }
17436
+ function breaksPhrase(cur, ln, lineCover, lineClipStart, scenes) {
17437
+ return cur.speaker !== ln.speaker || ln.start - cur.end > PAUSE_GAP_S || isAdjacentShownCut(ln, cur.lastShownScene, scenes) || Math.max(cur.coverEnd, lineCover) - Math.min(cur.clipStart, lineClipStart) > PHRASE_MAX_S;
17438
+ }
16865
17439
  var JOIN_DEDUP_MAX_WORDS = 4;
16866
17440
  function joinKey(word) {
16867
17441
  return word.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
@@ -16904,10 +17478,7 @@ function collapseVoiceover(blueprint) {
16904
17478
  const presenter = [...presenters][0];
16905
17479
  return (speaker) => NARRATOR_SPEAKERS.has(speaker.toLowerCase()) ? presenter : speaker;
16906
17480
  }
16907
- function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, presentStrict) {
16908
- const casts = castIdSet(blueprint);
16909
- const cameraOn = onCameraDialogue(blueprint);
16910
- const sceneEndS = (i) => blueprint.scenes[i]?.end_s ?? blueprint.scenes[i]?.start_s ?? 0;
17481
+ function multiSpeakerScenes(blueprint, casts, cameraOn, canonical, presentStrict) {
16911
17482
  const multiSpeaker = /* @__PURE__ */ new Set();
16912
17483
  blueprint.scenes.forEach((scene, i) => {
16913
17484
  const onCamAll = new Set(
@@ -16917,32 +17488,45 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
16917
17488
  const effective = onCamPresent.length > 0 ? new Set(onCamPresent) : onCamAll;
16918
17489
  if (effective.size >= 2) multiSpeaker.add(i);
16919
17490
  });
16920
- const lines = blueprint.scenes.flatMap(
16921
- (scene, sceneIndex) => compositeScenes.has(sceneIndex) ? [] : (scene.dialogue ?? []).filter((l) => Boolean(l.line?.trim())).map((l) => {
17491
+ return multiSpeaker;
17492
+ }
17493
+ function lineClipWindow(ln, scenes) {
17494
+ if (!ln.shown) return { cover: ln.end, clipStart: ln.start };
17495
+ const sc = scenes[ln.sceneIndex];
17496
+ const sceneEnd = sc?.end_s ?? sc?.start_s ?? 0;
17497
+ return { cover: Math.max(ln.end, sceneEnd), clipStart: Math.min(ln.start, sc?.start_s ?? ln.start) };
17498
+ }
17499
+ function dialogueLines(blueprint, ctx) {
17500
+ return blueprint.scenes.flatMap((scene, sceneIndex) => {
17501
+ if (ctx.compositeScenes.has(sceneIndex)) return [];
17502
+ return (scene.dialogue ?? []).filter((l) => Boolean(l.line?.trim())).map((l) => {
16922
17503
  const raw = l.speaker ?? "voiceover";
16923
- const sp = canonical(raw);
16924
17504
  const text = l.line.trim();
16925
17505
  const start = l.start_s ?? scene.start_s ?? 0;
17506
+ const shown = l.on_camera !== false && !sceneIsAllGraphic(scene) && isOnCameraSpeaker(raw, ctx.casts, ctx.cameraOn) && !ctx.multiSpeaker.has(sceneIndex) && ctx.presenterPresent(ctx.canonical(raw), sceneIndex);
16926
17507
  return {
16927
17508
  sceneIndex,
16928
- speaker: sp,
16929
- // Shown = a cast member speaking AND their element is actually on screen
16930
- // here (not a cutaway). A b-roll cutaway mid-phrase fails this and gets
16931
- // its own clip while the phrase voice plays under it. An explicit
16932
- // deconstruct voiceover stamp (`on_camera: false`) wins over element
16933
- // presence — a speaker pictured in a photo is "present" but not talking.
16934
- // An all-graphic composition (no camera region) is voiceover by
16935
- // definition: nobody is on screen to lip-sync.
16936
- shown: l.on_camera !== false && !sceneIsAllGraphic(scene) && isOnCameraSpeaker(raw, casts, cameraOn) && !multiSpeaker.has(sceneIndex) && presenterPresent(sp, sceneIndex),
17509
+ speaker: ctx.canonical(raw),
17510
+ shown,
16937
17511
  start,
16938
- // Real speech end. When the deconstruct gives no end_s, estimate it from
16939
- // the words — NOT the scene end (which would fabricate continuity across
16940
- // a long silent b-roll gap and wrongly merge two separate phrases).
16941
17512
  end: l.end_s ?? start + estSpeechS(text),
16942
17513
  text
16943
17514
  };
16944
- })
16945
- ).sort((a, b) => a.start - b.start);
17515
+ });
17516
+ }).sort((a, b) => a.start - b.start);
17517
+ }
17518
+ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, presentStrict) {
17519
+ const casts = castIdSet(blueprint);
17520
+ const cameraOn = onCameraDialogue(blueprint);
17521
+ const multiSpeaker = multiSpeakerScenes(blueprint, casts, cameraOn, canonical, presentStrict);
17522
+ const lines = dialogueLines(blueprint, {
17523
+ compositeScenes,
17524
+ multiSpeaker,
17525
+ canonical,
17526
+ casts,
17527
+ cameraOn,
17528
+ presenterPresent
17529
+ });
16946
17530
  const phrases = [];
16947
17531
  let cur = null;
16948
17532
  const flush = () => {
@@ -16960,12 +17544,8 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
16960
17544
  cur = null;
16961
17545
  };
16962
17546
  for (const ln of lines) {
16963
- const lineCover = ln.shown ? Math.max(ln.end, sceneEndS(ln.sceneIndex)) : ln.end;
16964
- const lineClipStart = ln.shown ? Math.min(ln.start, blueprint.scenes[ln.sceneIndex]?.start_s ?? ln.start) : ln.start;
16965
- const breakRun = !cur || cur.speaker !== ln.speaker || ln.start - cur.end > PAUSE_GAP_S || // Cap by SCENE COVERAGE span, not line end — a presenter run whose sliced scenes span
16966
- // more than one Seedance clip splits into the next take here (at this scene's
16967
- // boundary, never mid-scene), so no segment ever reads past the generated clip.
16968
- Math.max(cur.coverEnd, lineCover) - Math.min(cur.clipStart, lineClipStart) > PHRASE_MAX_S;
17547
+ const { cover: lineCover, clipStart: lineClipStart } = lineClipWindow(ln, blueprint.scenes);
17548
+ const breakRun = !cur || breaksPhrase(cur, ln, lineCover, lineClipStart, blueprint.scenes);
16969
17549
  if (breakRun || !cur) {
16970
17550
  flush();
16971
17551
  cur = {
@@ -16976,6 +17556,7 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
16976
17556
  coverEnd: lineCover,
16977
17557
  clipStart: lineClipStart,
16978
17558
  texts: [ln.text],
17559
+ lastShownScene: ln.shown ? ln.sceneIndex : null,
16979
17560
  shown: /* @__PURE__ */ new Set()
16980
17561
  };
16981
17562
  } else {
@@ -16983,6 +17564,7 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
16983
17564
  cur.end = Math.max(cur.end, ln.end);
16984
17565
  cur.coverEnd = Math.max(cur.coverEnd, lineCover);
16985
17566
  cur.clipStart = Math.min(cur.clipStart, lineClipStart);
17567
+ if (ln.shown) cur.lastShownScene = ln.sceneIndex;
16986
17568
  }
16987
17569
  if (ln.shown) cur.shown.add(ln.sceneIndex);
16988
17570
  }
@@ -17090,19 +17672,12 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17090
17672
  inputs: { clip: clipRef },
17091
17673
  params: { args: audioExtractArgs(extractLen, speechOffset), outputs: { audio: { kind: "audio", ext: "mp3" } } }
17092
17674
  });
17093
- const convId = `s${anchor}_conv`;
17094
- nodes.push({
17095
- id: convId,
17096
- type: "audio_voice_convert",
17097
- inputs: { audio: `$ref:s${anchor}_voextract.audio`, voice_ref: `$ref:${voiceNode}.voice_id` },
17098
- params: { model: FIXED_VOICE_CONVERT_MODEL, voice: "{{voice_ref}}" }
17099
- });
17100
- out.voTracks.push({
17101
- slot: convId,
17102
- ref: `$ref:${convId}.audio`,
17675
+ const convId = `${voiceNode}_conv`;
17676
+ out.nativeSegments.push({
17677
+ voiceNode,
17678
+ ref: `$ref:s${anchor}_voextract.audio`,
17103
17679
  start_s: phrase.start_s,
17104
- end_s: phrase.end_s,
17105
- kind: "vo"
17680
+ end_s: phrase.start_s + extractLen
17106
17681
  });
17107
17682
  out.voSegments.push({
17108
17683
  slot: convId,
@@ -17118,18 +17693,35 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17118
17693
  est_speech_s: Math.round(estSpeechWindowS(phrase.text, phrase.start_s, phrase.end_s) * 100) / 100,
17119
17694
  speech_words: wordCount(phrase.text)
17120
17695
  });
17121
- for (const s of phrase.shownScenes) {
17122
- const sc = env.blueprint.scenes[s];
17123
- if (!sc) continue;
17124
- const rawOffset = (sc.start_s ?? clipStart) - clipStart;
17125
- out.sceneSlice.set(s, {
17696
+ registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, Boolean(chained), env, out);
17697
+ }
17698
+ function registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, chained, env, out) {
17699
+ const shown = [...phrase.shownScenes].sort((a, b) => a - b);
17700
+ let r = 0;
17701
+ let firstRegistered = true;
17702
+ while (r < shown.length) {
17703
+ const first = shown[r];
17704
+ let last = first;
17705
+ while (r + 1 < shown.length && shown[r + 1] === last + 1) last = shown[++r];
17706
+ r++;
17707
+ const firstSc = env.blueprint.scenes[first];
17708
+ if (!firstSc) continue;
17709
+ const firstStart = firstSc.start_s ?? clipStart;
17710
+ const rawOffset = firstStart - clipStart;
17711
+ const runEnd = env.blueprint.scenes[last]?.end_s ?? firstStart + sceneDurationS(firstSc);
17712
+ out.sceneSlice.set(first, {
17126
17713
  clipRef,
17127
- // Snap a sub-frame offset (line-start vs scene-start drift) to 0 so a single-scene
17128
- // phrase hits the whole-clip fast path instead of a needless re-encode + tiny shift.
17714
+ // Snap a sub-frame offset (line-start vs scene-start drift) to 0 so a run that tiles
17715
+ // the clip hits the whole-clip fast path instead of a needless re-encode + tiny shift.
17129
17716
  offset: rawOffset < 0.05 ? 0 : rawOffset,
17130
- len: sceneDurationS(sc),
17131
- clipDur: genDur
17717
+ len: Math.max(0.5, runEnd - firstStart),
17718
+ clipDur: genDur,
17719
+ ...firstRegistered && chained ? { continuesFrame: true } : {}
17132
17720
  });
17721
+ firstRegistered = false;
17722
+ for (let s = first + 1; s <= last; s++) {
17723
+ out.sceneSlice.set(s, { clipRef, offset: 0, len: 0, clipDur: genDur, skip: true });
17724
+ }
17133
17725
  }
17134
17726
  }
17135
17727
  function emitPhraseTts(phrase, voiceNode, idx, used, nodes, out, languageCode) {
@@ -17272,7 +17864,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17272
17864
  return void 0;
17273
17865
  }
17274
17866
  if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
17275
- emitScreenScene(i, scene, lengths, lengths.out, env.outAr, nodes, out.clips);
17867
+ emitScreenScene(i, scene, lengths, lengths.out, env.outAr, env.surfaceIngests, nodes, out.clips);
17276
17868
  return void 0;
17277
17869
  }
17278
17870
  const isCta = scene.narrative_role?.trim() === "cta" || isLast;
@@ -17284,7 +17876,8 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17284
17876
  emitFlashHold(i, scene, env.slots, ctx, lengths, lengths.out, env.outAr, nodes, out.clips);
17285
17877
  return void 0;
17286
17878
  }
17287
- const first = scene.continues_previous && prevEndFrame ? prevEndFrame : buildFrameRef(
17879
+ const sharesPrevFrame = Boolean(scene.continues_previous && prevEndFrame);
17880
+ const first = sharesPrevFrame && prevEndFrame ? prevEndFrame : buildFrameRef(
17288
17881
  "start",
17289
17882
  scene.start_frame_asset?.url,
17290
17883
  scene.start_frame_prompt,
@@ -17332,9 +17925,26 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17332
17925
  out.nativeSegments
17333
17926
  );
17334
17927
  }
17335
- out.clips.push(clip);
17928
+ out.clips.push(sharesPrevFrame ? { ...clip, continuesFrame: true } : clip);
17336
17929
  return last;
17337
17930
  }
17931
+ function emitPresenterSliceClip(i, slice, env, nodes, out) {
17932
+ if (slice.skip) return;
17933
+ const cont = slice.continuesFrame ? { continuesFrame: true } : {};
17934
+ const normDims = env.genAr !== env.outAr ? canvasDims(env.outAr) : void 0;
17935
+ const whole = slice.offset === 0 && Math.abs(slice.len - slice.clipDur) <= 0.05 && !normDims;
17936
+ if (whole) {
17937
+ out.clips.push({ ref: slice.clipRef, scene_s: slice.len, out: null, ...cont });
17938
+ return;
17939
+ }
17940
+ nodes.push({
17941
+ id: `s${i}_seg`,
17942
+ type: "ffmpeg",
17943
+ inputs: { clip: slice.clipRef },
17944
+ params: { args: trimArgs(slice.len, slice.offset, normDims), outputs: { video: { kind: "video", ext: "mp4" } } }
17945
+ });
17946
+ out.clips.push({ ref: `$ref:s${i}_seg.video`, scene_s: slice.len, out: null, ...cont });
17947
+ }
17338
17948
  function buildTimeline(blueprint, slots, opts, nodes) {
17339
17949
  const reuse = opts.frames === "reuse";
17340
17950
  const uiRouted = uiRoutedSceneSet(blueprint);
@@ -17407,22 +18017,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
17407
18017
  }
17408
18018
  const slice = out.sceneSlice.get(i);
17409
18019
  if (slice) {
17410
- const normDims = env.genAr !== env.outAr ? canvasDims(env.outAr) : void 0;
17411
- const whole = slice.offset === 0 && Math.abs(slice.len - slice.clipDur) <= 0.05 && !normDims;
17412
- if (whole) {
17413
- out.clips.push({ ref: slice.clipRef, scene_s: slice.len, out: null });
17414
- } else {
17415
- nodes.push({
17416
- id: `s${i}_seg`,
17417
- type: "ffmpeg",
17418
- inputs: { clip: slice.clipRef },
17419
- params: {
17420
- args: trimArgs(slice.len, slice.offset, normDims),
17421
- outputs: { video: { kind: "video", ext: "mp4" } }
17422
- }
17423
- });
17424
- out.clips.push({ ref: `$ref:s${i}_seg.video`, scene_s: slice.len, out: null });
17425
- }
18020
+ emitPresenterSliceClip(i, slice, env, nodes, out);
17426
18021
  prevEndFrame = void 0;
17427
18022
  return;
17428
18023
  }
@@ -17479,25 +18074,25 @@ function buildSfxMusic(blueprint, nodes) {
17479
18074
  }
17480
18075
  return tracks;
17481
18076
  }
17482
- var OverlayStyle = z13.object({ color_hex: z13.string().optional(), background: z13.string().optional(), size: z13.string().optional() }).loose();
17483
- var Overlay = z13.object({
17484
- text: z13.string().optional(),
17485
- appears_at_s: z13.number().optional(),
17486
- duration_s: z13.number().optional(),
17487
- position: z13.string().optional(),
17488
- role: z13.string().optional(),
17489
- animation: z13.string().optional(),
17490
- animation_detail: z13.string().optional(),
18077
+ var OverlayStyle = z12.object({ color_hex: z12.string().optional(), background: z12.string().optional(), size: z12.string().optional() }).loose();
18078
+ var Overlay = z12.object({
18079
+ text: z12.string().optional(),
18080
+ appears_at_s: z12.number().optional(),
18081
+ duration_s: z12.number().optional(),
18082
+ position: z12.string().optional(),
18083
+ role: z12.string().optional(),
18084
+ animation: z12.string().optional(),
18085
+ animation_detail: z12.string().optional(),
17491
18086
  style: OverlayStyle.optional()
17492
18087
  }).loose();
17493
- var FloatingElement = z13.object({
17494
- kind: z13.string().optional(),
17495
- description: z13.string().optional(),
17496
- brand_name: z13.string().nullish(),
17497
- what_it_represents: z13.string().optional(),
17498
- appears_at_s: z13.number().optional(),
17499
- duration_s: z13.number().optional(),
17500
- position: z13.string().optional()
18088
+ var FloatingElement = z12.object({
18089
+ kind: z12.string().optional(),
18090
+ description: z12.string().optional(),
18091
+ brand_name: z12.string().nullish(),
18092
+ what_it_represents: z12.string().optional(),
18093
+ appears_at_s: z12.number().optional(),
18094
+ duration_s: z12.number().optional(),
18095
+ position: z12.string().optional()
17501
18096
  }).loose();
17502
18097
  function escapeHtml(s) {
17503
18098
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -17529,7 +18124,7 @@ function positionClass(position) {
17529
18124
  function collectCaptions(blueprint) {
17530
18125
  return blueprint.scenes.flatMap((scene) => {
17531
18126
  const sceneStart = scene.start_s ?? 0;
17532
- const overlays = z13.array(Overlay).safeParse(scene.overlays ?? []);
18127
+ const overlays = z12.array(Overlay).safeParse(scene.overlays ?? []);
17533
18128
  return overlays.success ? overlays.data.filter((ov) => Boolean(ov.text?.trim())).map((ov) => {
17534
18129
  const at = ov.appears_at_s ?? sceneStart;
17535
18130
  return { text: ov.text.trim(), at, end: at + (ov.duration_s ?? 2.5), ov };
@@ -17609,7 +18204,7 @@ function collectFloatWindows(blueprint, uiRouted) {
17609
18204
  const windows = /* @__PURE__ */ new Map();
17610
18205
  blueprint.scenes.forEach((scene, i) => {
17611
18206
  const sceneStart = scene.start_s ?? 0;
17612
- const floats = z13.array(FloatingElement).safeParse(scene.floating_elements ?? []);
18207
+ const floats = z12.array(FloatingElement).safeParse(scene.floating_elements ?? []);
17613
18208
  if (!floats.success) return;
17614
18209
  for (const fe of floats.data) {
17615
18210
  const at = fe.appears_at_s ?? sceneStart;
@@ -17708,25 +18303,40 @@ function lastSceneEnd(blueprint) {
17708
18303
  for (const s of blueprint.scenes) end = Math.max(end, s.end_s ?? 0);
17709
18304
  return end > 0 ? end : 8;
17710
18305
  }
17711
- function concatArgs(count) {
18306
+ function seamDropOps(clips, i, seam) {
18307
+ if (seam === "off") return null;
18308
+ if (seam === "head" && i > 0 && clips[i]?.continuesFrame) return "trim=start_frame=1,setpts=PTS-STARTPTS";
18309
+ if (seam === "tail" && clips[i + 1]?.continuesFrame) return "reverse,trim=start_frame=1,setpts=PTS-STARTPTS,reverse";
18310
+ return null;
18311
+ }
18312
+ function concatArgs(clips, seam) {
17712
18313
  const inputs = [];
17713
- let labels = "";
17714
- for (let i = 0; i < count; i++) {
18314
+ const pre = [];
18315
+ const labels = [];
18316
+ clips.forEach((_, i) => {
17715
18317
  inputs.push("-i", `{{in.c${i}}}`);
17716
- labels += `[${i}:v]`;
17717
- }
17718
- return [...inputs, "-filter_complex", `${labels}concat=n=${count}:v=1:a=0[v]`, "-map", "[v]", "{{out.video}}"];
18318
+ const ops = seamDropOps(clips, i, seam);
18319
+ if (ops) {
18320
+ pre.push(`[${i}:v]${ops}[c${i}]`);
18321
+ labels.push(`[c${i}]`);
18322
+ } else {
18323
+ labels.push(`[${i}:v]`);
18324
+ }
18325
+ });
18326
+ const graph = [...pre, `${labels.join("")}concat=n=${clips.length}:v=1:a=0[v]`].join(";");
18327
+ return [...inputs, "-filter_complex", graph, "-map", "[v]", "{{out.video}}"];
17719
18328
  }
17720
18329
  function clipInputLen(c) {
17721
18330
  return c.scene_s + (c.out?.dur ?? 0);
17722
18331
  }
17723
- function xfadeSpineArgs(clips) {
18332
+ function xfadeSpineArgs(clips, seam) {
17724
18333
  const n = clips.length;
17725
18334
  const inputs = [];
17726
18335
  const filt = [];
17727
18336
  for (let i = 0; i < n; i++) {
17728
18337
  inputs.push("-i", `{{in.c${i}}}`);
17729
- filt.push(`[${i}:v]format=yuv420p,fps=30,setsar=1,settb=AVTB[c${i}]`);
18338
+ const ops = seamDropOps(clips, i, seam);
18339
+ filt.push(`[${i}:v]format=yuv420p,fps=30,setsar=1,settb=AVTB${ops ? `,${ops}` : ""}[c${i}]`);
17730
18340
  }
17731
18341
  let cur = "c0";
17732
18342
  let accLen = clipInputLen(clips[0]);
@@ -17748,13 +18358,13 @@ function xfadeSpineArgs(clips) {
17748
18358
  }
17749
18359
  return [...inputs, "-filter_complex", filt.join(";"), "-map", "[v]", "{{out.video}}"];
17750
18360
  }
17751
- function buildSpine(clips, nodes) {
18361
+ function buildSpine(clips, seam, nodes) {
17752
18362
  const inputs = {};
17753
18363
  clips.forEach((c, i) => {
17754
18364
  inputs[`c${i}`] = c.ref;
17755
18365
  });
17756
18366
  const hasTransition = clips.length > 1 && clips.some((c) => c.out);
17757
- const args = hasTransition ? xfadeSpineArgs(clips) : concatArgs(clips.length);
18367
+ const args = hasTransition ? xfadeSpineArgs(clips, seam) : concatArgs(clips, seam);
17758
18368
  nodes.push({
17759
18369
  id: "spine",
17760
18370
  type: "ffmpeg",
@@ -17763,16 +18373,24 @@ function buildSpine(clips, nodes) {
17763
18373
  });
17764
18374
  return "$ref:spine.video";
17765
18375
  }
17766
- function scaffoldVideoCanvas(input, elementsInput, opts) {
17767
- const blueprint = VideoBlueprint.parse(input);
17768
- injectHookPhysicality(blueprint);
17769
- const elements = RecurringElements.parse(elementsInput);
17770
- const nodes = [];
18376
+ function emitBlueprintIngests(opts, nodes) {
17771
18377
  nodes.push({
17772
18378
  id: "prompt",
17773
18379
  type: "ingest",
17774
18380
  params: { source: "path", path: opts.blueprintPath ?? "./prompt.json", expect: "json" }
17775
18381
  });
18382
+ nodes.push({
18383
+ id: "prompt_style",
18384
+ type: "ingest",
18385
+ params: { source: "path", path: opts.blueprintStylePath ?? "./prompt.style.json", expect: "json" }
18386
+ });
18387
+ }
18388
+ function scaffoldVideoCanvas(input, elementsInput, opts) {
18389
+ const blueprint = VideoBlueprint.parse(input);
18390
+ injectHookPhysicality(blueprint);
18391
+ const elements = RecurringElements.parse(elementsInput);
18392
+ const nodes = [];
18393
+ emitBlueprintIngests(opts, nodes);
17776
18394
  const slots = buildElementSlots(elements);
17777
18395
  extendPresenceByPromptMentions(slots, blueprint);
17778
18396
  slots.forEach((slot, i) => {
@@ -17784,7 +18402,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
17784
18402
  });
17785
18403
  buildElementSheets(slots, nodes);
17786
18404
  const { clips, voTracks, vo_segments, talking_scenes } = buildTimeline(blueprint, slots, opts, nodes);
17787
- let videoRef = buildSpine(clips, nodes);
18405
+ let videoRef = buildSpine(clips, opts.seamDedup ?? "head", nodes);
17788
18406
  let videoNode = "spine";
17789
18407
  const overlays = blueprint.scenes.flatMap((s) => s.overlays ?? []);
17790
18408
  const floating = blueprint.scenes.flatMap((s) => s.floating_elements ?? []);
@@ -17995,8 +18613,8 @@ function buildMotionBoard(blueprint) {
17995
18613
  const end_s = scene.end_s ?? start_s + sceneDurationS(scene);
17996
18614
  cursor = end_s;
17997
18615
  const spoken = sceneSpokenText(scene);
17998
- const overlays = z13.array(Overlay).safeParse(scene.overlays ?? []);
17999
- const floats = z13.array(FloatingElement).safeParse(scene.floating_elements ?? []);
18616
+ const overlays = z12.array(Overlay).safeParse(scene.overlays ?? []);
18617
+ const floats = z12.array(FloatingElement).safeParse(scene.floating_elements ?? []);
18000
18618
  const graphics = [
18001
18619
  ...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
18002
18620
  kind: "text",
@@ -18026,7 +18644,7 @@ function buildMotionBoard(blueprint) {
18026
18644
  });
18027
18645
  }
18028
18646
  var VIDEO_GUIDE = [
18029
- "Scaffolded by `baker canvas scaffold-video` \u2014 a runnable reproduction of your reference video, built like an editing timeline. The VOICE is cut at PAUSES, not at visual cuts: each continuous-speech PHRASE is ONE Seedance clip (native lip-sync + audio) re-voiced to one brand voice, so a sentence never breaks mid-word across a cut. Each scene's PICTURE is independent: a scene that SHOWS the speaker slices its window out of the phrase clip; a b-roll cutaway gets its own silent clip (or a still hold for a sub-2s flash) laid over the continuing voice; a pure-voiceover stretch is one ElevenLabs tts read. Every clip gets a CLEAN-PLATE start AND end keyframe (no baked text), RECAST to your dropped reference assets \u2014 Seedance interpolates real in-shot motion between them. Each frame grounds ONLY on its own extracted frame + el_* slots (never another generated frame), so all frames render in PARALLEL (no cross-frame cascade). A SPLIT-SCREEN / PICTURE-IN-PICTURE / KEYED-PRESENTER scene is reproduced as one clip PER REGION, stacked or overlaid (see `metadata.todo.composition`). On-screen text/graphics are a separate HTML overlay layer you paint; audio is the voice + SFX + a ducked music bed, normalized stereo. It is a STARTING POINT, not a locked render: add, delete, reorder, split, merge, or re-time scenes freely (a b-roll cutaway INSIDE a phrase lands at an approximate beat \u2014 nudge it) \u2014 see `metadata.todo.full_flexibility`.",
18647
+ "Scaffolded by `baker canvas scaffold-video` \u2014 a runnable reproduction of your reference video, built like an editing timeline. It is a sequence of clear SHOTS separated at COMPLETE BREAKS (hard cuts): two adjacent presenter shots at a cut are TWO clips, never glued into one take. What stays continuous is the VOICE \u2014 a voiceover narration is ONE read across the b-roll it plays over, and a b-roll CUTAWAY between two on-camera moments leaves the presenter shot continuous with the insert sliced in \u2014 and each person keeps ONE brand voice (all their clips' native audio re-voiced in a single per-speaker pass), so timbre holds across the cuts. A presenter shot is ONE Seedance clip (native lip-sync + audio); a pure-voiceover stretch is one ElevenLabs tts read; a sub-2s flash is a still hold. A single shot too long for one clip splits into takes that share a boundary frame \u2014 the spine drops the duplicated frame (`--seam-dedup head|tail|off`). Every clip gets a CLEAN-PLATE start AND end keyframe (no baked text), RECAST to your dropped reference assets \u2014 Seedance interpolates real in-shot motion between them. Each frame grounds ONLY on its own extracted frame + el_* slots (never another generated frame), so all frames render in PARALLEL (no cross-frame cascade). A SPLIT-SCREEN / PICTURE-IN-PICTURE / KEYED-PRESENTER scene is reproduced as one clip PER REGION, stacked or overlaid (see `metadata.todo.composition`). On-screen text/graphics are a separate HTML overlay layer you paint; audio is the voice + SFX + a ducked music bed, normalized stereo. It is a STARTING POINT, not a locked render: add, delete, reorder, split, merge, or re-time scenes freely \u2014 see `metadata.todo.full_flexibility`.",
18030
18648
  "",
18031
18649
  "WHAT TO DO NEXT:",
18032
18650
  "0. RE-CRAFT THE SCRIPT FIRST (don't clone). This reference already won in-market, but copying a video is much harder than a static: the hook is targeting and may not transfer, and the message must become TRUE for our brand. Work the `metadata.todo.script_recraft` checklist \u2014 for each scene judge its role (hook/body/CTA), decide keep/cut/reorder/replace, and re-author every line for OUR customer's pain + OUR offer. See `references/script-craft.md` (hook/body/CTA framework) and the `meta-ads-playbook` skill. Most of the work lives here.",
@@ -18110,9 +18728,9 @@ function buildVideoTodo(report, overlayCount, floatingCount, opts, blueprint) {
18110
18728
  voice_description: d.voice_description,
18111
18729
  line: d.line
18112
18730
  })),
18113
- talking_head_note: "PHRASE-NATIVE: a continuous-speech phrase where the speaker is shown is ONE Seedance clip (the full phrase quoted in s<anchor>_clip's prompt + generate_audio) so lips+voice are generated together \u2014 no tts, no veed-lipsync. Scenes that show the speaker slice their window out of that clip (s<i>_seg); edit the phrase line in the s<anchor>_clip prompt to re-author it. A pure-voiceover phrase (speaker never shown) is one ElevenLabs tts read instead.",
18114
- voice_note: "ONE voice per person: a single voice_select is reused across all that person's phrases (on-camera AND off \u2014 the deconstruct's `voiceover` label folds into the sole presenter). Each presenter phrase's native audio is re-voiced to that brand voice via audio_voice_convert (eleven_multilingual_sts_v2, one convert per phrase, timing preserved so lips stay matched). Set voice_select.voice_id's gender/language to match the creator.",
18115
- native_timing: "The voice is cut at PAUSES, not at visual cuts, so a sentence spanning a cut stays one continuous read (no mid-word break). The clip is generated long enough for the estimated speech; if a line runs longer than its phrase window the voice continues a beat into the following pause (natural VO continuity). `metadata.video.talking_scenes` carries each phrase's scene_s vs est_speech_s. CAVEAT: a b-roll cutaway INSIDE a phrase lands at an approximate (proportional) time \u2014 Seedance exposes no word timing \u2014 so if a cutaway is off its beat, nudge the scene boundary (it's a starting point).",
18731
+ talking_head_note: "SHOT-NATIVE: a presenter shot is ONE Seedance clip (its line quoted in s<anchor>_clip's prompt + generate_audio) so lips+voice are generated together \u2014 no tts, no veed-lipsync. Two adjacent presenter shots at a hard cut are SEPARATE clips; a cutaway phrase (the presenter on camera, cut to b-roll, back on camera) stays one clip and slices its on-camera windows (s<i>_seg). Edit the line in the s<anchor>_clip prompt to re-author it. A pure-voiceover phrase (speaker never shown) is one ElevenLabs tts read instead.",
18732
+ voice_note: "ONE voice per person: a single voice_select is reused across all that person's shots (on-camera AND off \u2014 the deconstruct's `voiceover` label folds into the sole presenter). Every presenter clip's native audio is extracted and re-voiced to that brand voice through a SINGLE merged audio_voice_convert per speaker (<voice>_conv, eleven_multilingual_sts_v2, timing preserved so lips stay matched) \u2014 so timbre stays consistent across the separate shot clips. Set voice_select.voice_id's gender/language to match the creator.",
18733
+ native_timing: "Clips separate at COMPLETE BREAKS between shots, but the VOICE stays continuous where it should: a voiceover narration is ONE read across the b-roll it plays over, and a cutaway leaves the presenter's read continuous under the insert. Each clip is generated long enough for its estimated speech. `metadata.video.talking_scenes` carries each shot's scene_s vs est_speech_s. CAVEAT: a b-roll cutaway INSIDE a phrase lands at an approximate (proportional) time \u2014 Seedance exposes no word timing \u2014 so if a cutaway is off its beat, nudge the scene boundary (it's a starting point).",
18116
18734
  craft: {
18117
18735
  note: "Production-craft principles that raise every clip's realism. Full rationale: references/video-craft.md (production craft); references/script-craft.md + meta-ads-playbook for the hook/message layer.",
18118
18736
  principles: [
@@ -18223,23 +18841,23 @@ function videoReport(input, elementsInput) {
18223
18841
 
18224
18842
  // src/commands/canvas/composition-path.ts
18225
18843
  import { existsSync as existsSync3 } from "fs";
18226
- import path7 from "path";
18844
+ import path10 from "path";
18227
18845
  function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth = 8) {
18228
- const rel = path7.join("canvas", name);
18846
+ const rel = path10.join("canvas", name);
18229
18847
  let dir = startDir;
18230
18848
  for (let i = 0; i < maxDepth; i++) {
18231
- const candidate = path7.join(dir, rel);
18232
- if (exists(path7.join(candidate, "meta.json"))) return candidate;
18233
- const parent = path7.dirname(dir);
18849
+ const candidate = path10.join(dir, rel);
18850
+ if (exists(path10.join(candidate, "meta.json"))) return candidate;
18851
+ const parent = path10.dirname(dir);
18234
18852
  if (parent === dir) break;
18235
18853
  dir = parent;
18236
18854
  }
18237
- return path7.resolve(startDir, "../../../", rel);
18855
+ return path10.resolve(startDir, "../../../", rel);
18238
18856
  }
18239
18857
 
18240
18858
  // src/commands/canvas/gitignore.ts
18241
- import { appendFile, readFile as readFile5 } from "fs/promises";
18242
- import path8 from "path";
18859
+ import { appendFile, readFile as readFile6 } from "fs/promises";
18860
+ import path11 from "path";
18243
18861
  function missingGitignoreEntries(existing, entries) {
18244
18862
  const present = new Set(
18245
18863
  existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
@@ -18247,10 +18865,10 @@ function missingGitignoreEntries(existing, entries) {
18247
18865
  return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
18248
18866
  }
18249
18867
  async function ensureGitignore(dir, entries) {
18250
- const file = path8.join(dir, ".gitignore");
18868
+ const file = path11.join(dir, ".gitignore");
18251
18869
  let existing;
18252
18870
  try {
18253
- existing = await readFile5(file, "utf8");
18871
+ existing = await readFile6(file, "utf8");
18254
18872
  } catch {
18255
18873
  return;
18256
18874
  }
@@ -18289,7 +18907,7 @@ ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wa
18289
18907
  For each kept element return: { "type": one of person|animal|product|logo|badge|location, "label": a short UPPER_SNAKE_CASE name (e.g. HERO, CREATOR_SKEPTIC, INSURANCE_CARD, LOGO), "description": a concrete reusable description to source/shoot the real asset \u2014 for a person/animal give a NEUTRAL castable role (e.g. "hero pet-owner, woman in her 30s" or "a small beagle"), NOT the original individual's literal face/identity: we RECAST with a FRESH person/animal, so never tell the agent to reuse the original. "expression": a living subject's typical expression or null, "cast_id": the global.cast id if it maps to one else null, "same_as": the label of another element this is the SAME individual as (different wardrobe/persona) else null, "scenes": the 0-based indices of ONLY the scenes where the element is ACTUALLY VISIBLE ON SCREEN \u2014 judged from that scene's start_frame_prompt / end_frame_prompt subjects and its action_detail, NOT from who is merely speaking. A narrator heard over b-roll is NOT present in that b-roll scene; a dog-running cutaway does NOT contain the couch creator just because she talks across it. Do NOT pad the list \u2014 an element wrongly listed in a scene makes the reproduction render the wrong subject there (e.g. the creator appearing in a pure-dog b-roll). When in doubt, leave a scene OUT. Output ONLY the JSON object.`;
18290
18908
  async function loadAssetText2(ref, label) {
18291
18909
  const r = ref;
18292
- if (typeof r?.path === "string") return readFile6(r.path, "utf8");
18910
+ if (typeof r?.path === "string") return readFile7(r.path, "utf8");
18293
18911
  if (typeof r?.url === "string") {
18294
18912
  const res = await fetch(r.url);
18295
18913
  if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
@@ -18308,7 +18926,7 @@ async function loadTranscriptBestEffort(ref) {
18308
18926
  async function stageCaptions(outDir, transcript) {
18309
18927
  const text = transcript?.trim();
18310
18928
  if (!text || text === "[]") return {};
18311
- const compositionPath = path9.join(outDir, "tiktok-captions-composition");
18929
+ const compositionPath = path12.join(outDir, "tiktok-captions-composition");
18312
18930
  await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
18313
18931
  return { compositionPath };
18314
18932
  }
@@ -18326,12 +18944,12 @@ function patchCompositionHtml(html, dims) {
18326
18944
  return html.replace(/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+(")/i, `$1${dims.w}$2${dims.h}$3`).replace(/(width:\s*)\d+(px;\s*height:\s*)\d+(px;)/i, `$1${dims.w}$2${dims.h}$3`).replace(/(data-width=")\d+(")/i, `$1${dims.w}$2`).replace(/(data-height=")\d+(")/i, `$1${dims.h}$2`);
18327
18945
  }
18328
18946
  async function stampCompositionDims(compositionDir, dims) {
18329
- const metaPath = path9.join(compositionDir, "meta.json");
18330
- const rawMeta = await readFile6(metaPath, "utf8");
18331
- await writeFile2(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
18332
- const htmlPath = path9.join(compositionDir, "index.html");
18333
- const rawHtml = await readFile6(htmlPath, "utf8");
18334
- await writeFile2(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
18947
+ const metaPath = path12.join(compositionDir, "meta.json");
18948
+ const rawMeta = await readFile7(metaPath, "utf8");
18949
+ await writeFile3(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
18950
+ const htmlPath = path12.join(compositionDir, "index.html");
18951
+ const rawHtml = await readFile7(htmlPath, "utf8");
18952
+ await writeFile3(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
18335
18953
  }
18336
18954
  function parseElements2(raw) {
18337
18955
  const parsed = JSON.parse(raw);
@@ -18371,6 +18989,62 @@ function fail2(code, message) {
18371
18989
  `);
18372
18990
  process.exit(2);
18373
18991
  }
18992
+ var VIDEO_EXT_BY_MIME = {
18993
+ "video/mp4": ".mp4",
18994
+ "video/quicktime": ".mov",
18995
+ "video/webm": ".webm",
18996
+ "video/x-matroska": ".mkv"
18997
+ };
18998
+ function referenceVideoExt(url, contentType) {
18999
+ const fromPath = path12.extname(new URL(url).pathname).toLowerCase();
19000
+ if (fromPath && fromPath.length <= 5) return fromPath;
19001
+ const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
19002
+ return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
19003
+ }
19004
+ async function fileExists2(target) {
19005
+ return access2(target).then(
19006
+ () => true,
19007
+ () => false
19008
+ );
19009
+ }
19010
+ function videoSourceReference(blueprint, fileArg2) {
19011
+ const bp = blueprint ?? {};
19012
+ const durable = typeof bp.source?.url === "string" ? bp.source.url : void 0;
19013
+ const original = /^https?:\/\//i.test(fileArg2) ? fileArg2 : void 0;
19014
+ const brand = bp.global?.branding?.brand_name;
19015
+ return { url: durable ?? original, advertiser: typeof brand === "string" && brand.trim() ? brand.trim() : void 0 };
19016
+ }
19017
+ function videoDefinitionDescription(blueprint) {
19018
+ const g = (blueprint ?? {}).global ?? {};
19019
+ const notes = g.reproduction_notes;
19020
+ if (typeof notes === "string" && notes.trim()) return notes.trim();
19021
+ const product = g.branding?.product;
19022
+ return typeof product === "string" && product.trim() ? product.trim() : void 0;
19023
+ }
19024
+ async function materializeReferenceVideo(fileArg2) {
19025
+ if (!/^https?:\/\//i.test(fileArg2)) return path12.resolve(fileArg2);
19026
+ let res;
19027
+ try {
19028
+ res = await fetch(fileArg2);
19029
+ } catch (e) {
19030
+ throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
19031
+ }
19032
+ if (!res.ok) throw new Error(`failed to download reference video (${res.status} ${res.statusText})`);
19033
+ const bytes = Buffer.from(await res.arrayBuffer());
19034
+ if (bytes.length === 0) throw new Error("reference video download was empty");
19035
+ const dest = path12.join(
19036
+ tmpdir2(),
19037
+ `baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, res.headers.get("content-type"))}`
19038
+ );
19039
+ await writeFile3(dest, bytes);
19040
+ return dest;
19041
+ }
19042
+ function resolveSeamDedup(raw) {
19043
+ if (raw === void 0) return "head";
19044
+ const v = String(raw);
19045
+ if (v === "head" || v === "tail" || v === "off") return v;
19046
+ throw new Error(`--seam-dedup must be "head", "tail", or "off" (got "${v}")`);
19047
+ }
18374
19048
  function resolveModels2(args) {
18375
19049
  const pick = (flag, kind, fallback) => args[flag] ? String(args[flag]) : resolveModel2(kind, fallback);
18376
19050
  return {
@@ -18467,13 +19141,25 @@ var scaffoldVideoCommand = defineCommand90({
18467
19141
  description: "Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes \u2014 video_deconstruct (the full scene-by-scene blueprint + transcript, baked to prompt.json as the editable 'prompt') and an AI selection of the video's RECURRING identity elements (person/animal/product/logo) \u2014 then scaffolds a pipeline where every scene boundary is a static-ad-grade frame (the blueprint as target_blueprint, a reference legend, the real frame as anchor) and each recurring element gets ONE shared [TODO] ingest slot wired into every frame it appears in. The clips feed Seedance an ultra-detailed motion brief (action, camera, dialogue, transcript). Edit prompt.json, drop the real source images, then `baker canvas run`."
18468
19142
  },
18469
19143
  args: {
18470
- file: { type: "positional", required: true, description: "Path to the reference video" },
19144
+ file: {
19145
+ type: "positional",
19146
+ required: true,
19147
+ description: "Reference video \u2014 a local path OR an http(s) URL (e.g. a winning-ads link). A URL is downloaded for you; pass --slug or --out with it."
19148
+ },
18471
19149
  out: { type: "string", description: "Output canvas path (default <video-dir>/<name>.video.canvas.json)" },
19150
+ slug: {
19151
+ type: "string",
19152
+ description: "Creative slug \u2014 writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)"
19153
+ },
18472
19154
  frames: { type: "string", description: '"generate" (default, anchored regen) or "reuse" (wire real frames in)' },
18473
19155
  ambient: {
18474
19156
  type: "boolean",
18475
19157
  description: "Give silent b-roll scenes native diegetic ambient mixed deep under the music bed (off by default)"
18476
19158
  },
19159
+ "seam-dedup": {
19160
+ type: "string",
19161
+ description: `How to dedup the frame two clips SHARE when a long shot is split for length: "head" (default, drop the second clip's first frame), "tail" (drop the first clip's last frame), or "off" (keep both).`
19162
+ },
18477
19163
  "max-scenes": { type: "string", description: "Cap the number of scenes the deconstruct emits" },
18478
19164
  "shot-threshold": {
18479
19165
  type: "string",
@@ -18481,6 +19167,14 @@ var scaffoldVideoCommand = defineCommand90({
18481
19167
  },
18482
19168
  language: { type: "string", description: "Transcript/dialogue language hint (e.g. fr, en)" },
18483
19169
  focus: { type: "string", description: "Known provenance/emphasis to ground the deconstruct" },
19170
+ advertiser: {
19171
+ type: "string",
19172
+ description: "Source advertiser recorded in _definition.md (default: the brand the deconstruct identified)"
19173
+ },
19174
+ platform: {
19175
+ type: "string",
19176
+ description: "Ad platform for _definition.md (meta|google|linkedin|tiktok|youtube|x|other; default meta)"
19177
+ },
18484
19178
  "deconstruct-model": { type: "string", description: "Override the video_deconstruct model id" },
18485
19179
  "select-model": { type: "string", description: "Override the text_generate model id for element selection" },
18486
19180
  "image-model": { type: "string", description: "Override the image_generate model id for frames" },
@@ -18495,11 +19189,33 @@ var scaffoldVideoCommand = defineCommand90({
18495
19189
  }
18496
19190
  },
18497
19191
  async run({ args }) {
18498
- const videoPath = path9.resolve(String(args.file));
18499
- const base = path9.basename(videoPath, path9.extname(videoPath));
18500
- const outPath = args.out ? path9.resolve(String(args.out)) : path9.join(path9.dirname(videoPath), `${base}.video.canvas.json`);
18501
- const outDir = path9.dirname(outPath);
18502
- const blueprintPath = path9.join(outDir, "prompt.json");
19192
+ const fileArg2 = String(args.file);
19193
+ const slug = args.slug ? String(args.slug) : void 0;
19194
+ if (slug && !isValidScaffoldSlug(slug)) {
19195
+ process.stderr.write(
19196
+ `${JSON.stringify({ ok: false, error: { code: "invalid_slug", message: "--slug must be lowercase kebab (a-z, 0-9, hyphens), max 100 chars" } }, null, 2)}
19197
+ `
19198
+ );
19199
+ process.exit(2);
19200
+ }
19201
+ const isUrl = /^https?:\/\//i.test(fileArg2);
19202
+ if (isUrl && !slug && !args.out) {
19203
+ return fail2(
19204
+ "missing_output_target",
19205
+ "When the reference is a URL, pass --slug (writes src/creatives/<slug>/) or --out <path> so the scaffolded canvas has a home in the repo."
19206
+ );
19207
+ }
19208
+ let videoPath;
19209
+ try {
19210
+ videoPath = await materializeReferenceVideo(fileArg2);
19211
+ } catch (e) {
19212
+ return fail2("download", e instanceof Error ? e.message : String(e));
19213
+ }
19214
+ const base = path12.basename(videoPath, path12.extname(videoPath));
19215
+ const outPath = args.out ? path12.resolve(String(args.out)) : slug ? path12.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path12.join(path12.dirname(videoPath), `${base}.video.canvas.json`);
19216
+ const outDir = path12.dirname(outPath);
19217
+ const blueprintPath = path12.join(outDir, "prompt.json");
19218
+ const blueprintStylePath = path12.join(outDir, "prompt.style.json");
18503
19219
  const frames = args.frames === "reuse" ? "reuse" : "generate";
18504
19220
  const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
18505
19221
  if (Number.isFinite(maxScenes)) {
@@ -18518,10 +19234,16 @@ var scaffoldVideoCommand = defineCommand90({
18518
19234
  shotCuts
18519
19235
  });
18520
19236
  const { blueprint, elements, transcript, creditsSpent } = await runAnalysisPasses(deconstructCanvas, selectModel);
18521
- await mkdir(outDir, { recursive: true });
19237
+ await mkdir3(outDir, { recursive: true });
18522
19238
  const annotated = annotateBlueprintWithElements(blueprint, elements);
18523
- await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
19239
+ await writeFile3(blueprintPath, `${JSON.stringify(annotated, null, 2)}
18524
19240
  `, "utf8");
19241
+ await writeFile3(
19242
+ blueprintStylePath,
19243
+ `${JSON.stringify(slimBlueprintForFrameStyle(annotated), null, 2)}
19244
+ `,
19245
+ "utf8"
19246
+ );
18525
19247
  let aspect;
18526
19248
  try {
18527
19249
  aspect = resolveAspect(
@@ -18539,12 +19261,12 @@ var scaffoldVideoCommand = defineCommand90({
18539
19261
  `
18540
19262
  );
18541
19263
  }
18542
- const compositionDest = path9.join(outDir, "video-overlay-composition");
19264
+ const compositionDest = path12.join(outDir, "video-overlay-composition");
18543
19265
  await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
18544
19266
  await stampCompositionDims(compositionDest, outDims);
18545
- const indexPath = path9.join(compositionDest, "index.html");
19267
+ const indexPath = path12.join(compositionDest, "index.html");
18546
19268
  const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript) });
18547
- const indexHtml = await readFile6(indexPath, "utf8");
19269
+ const indexHtml = await readFile7(indexPath, "utf8");
18548
19270
  const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
18549
19271
  if (injected === indexHtml && overlayHtml.trim()) {
18550
19272
  fail2(
@@ -18552,17 +19274,19 @@ var scaffoldVideoCommand = defineCommand90({
18552
19274
  `video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker \u2014 cannot inject the overlay layer`
18553
19275
  );
18554
19276
  }
18555
- await writeFile2(indexPath, injected, "utf8");
19277
+ await writeFile3(indexPath, injected, "utf8");
18556
19278
  const captions = await stageCaptions(outDir, transcript);
18557
19279
  if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);
18558
19280
  const opts = {
18559
19281
  imageModel,
18560
19282
  videoModel,
18561
- overlayCompositionPath: path9.relative(outDir, compositionDest),
18562
- captionsCompositionPath: captions.compositionPath ? path9.relative(outDir, captions.compositionPath) : void 0,
18563
- blueprintPath: path9.relative(outDir, blueprintPath),
19283
+ overlayCompositionPath: path12.relative(outDir, compositionDest),
19284
+ captionsCompositionPath: captions.compositionPath ? path12.relative(outDir, captions.compositionPath) : void 0,
19285
+ blueprintPath: path12.relative(outDir, blueprintPath),
19286
+ blueprintStylePath: path12.relative(outDir, blueprintStylePath),
18564
19287
  frames,
18565
19288
  ambient: Boolean(args.ambient),
19289
+ seamDedup: resolveSeamDedup(args["seam-dedup"]),
18566
19290
  ...args.aspect ? { aspect: String(args.aspect) } : {},
18567
19291
  ...args.resolution ? { resolution: String(args.resolution) } : {}
18568
19292
  };
@@ -18581,7 +19305,7 @@ var scaffoldVideoCommand = defineCommand90({
18581
19305
  todo.blocking_validation_issues = validation.issues;
18582
19306
  meta.todo = todo;
18583
19307
  }
18584
- await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
19308
+ await writeFile3(outPath, `${JSON.stringify(canvas, null, 2)}
18585
19309
  `, "utf8");
18586
19310
  if (!validation.ok) {
18587
19311
  process.stderr.write(
@@ -18601,12 +19325,42 @@ var scaffoldVideoCommand = defineCommand90({
18601
19325
  process.exit(2);
18602
19326
  }
18603
19327
  await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
19328
+ const sourceRef = videoSourceReference(blueprint, fileArg2);
19329
+ if (slug) {
19330
+ const definitionPath = path12.join(outDir, "_definition.md");
19331
+ if (!await fileExists2(definitionPath)) {
19332
+ await writeFile3(
19333
+ definitionPath,
19334
+ buildCreativeDefinition({
19335
+ title: titleFromSlug(slug),
19336
+ kind: "video",
19337
+ platform: resolvePlatform(args.platform ? String(args.platform) : void 0),
19338
+ formats: resolveFormats(aspect.outAr),
19339
+ sourceReferenceUrl: sourceRef.url,
19340
+ sourceAdvertiser: args.advertiser ? String(args.advertiser) : sourceRef.advertiser,
19341
+ sourceKind: "video",
19342
+ description: videoDefinitionDescription(blueprint)
19343
+ }),
19344
+ "utf8"
19345
+ );
19346
+ }
19347
+ }
19348
+ if (slug) {
19349
+ await syncCreativeDefinitionBestEffort({
19350
+ slug,
19351
+ title: titleFromSlug(slug),
19352
+ formats: [aspect.outAr],
19353
+ canvas,
19354
+ sourceReferenceUrl: sourceRef.url
19355
+ });
19356
+ }
18604
19357
  process.stdout.write(
18605
19358
  `${JSON.stringify(
18606
19359
  {
18607
19360
  ok: true,
18608
19361
  canvas_path: outPath,
18609
19362
  prompt_path: blueprintPath,
19363
+ source_reference: sourceRef.url,
18610
19364
  composition_dir: compositionDest,
18611
19365
  output: canvas.output,
18612
19366
  frames_mode: frames,
@@ -18618,7 +19372,7 @@ var scaffoldVideoCommand = defineCommand90({
18618
19372
  run_estimated_credits: validation.estimatedCredits
18619
19373
  },
18620
19374
  checklist: {
18621
- edit_prompt: `Edit ${path9.basename(blueprintPath)} \u2014 the blueprint deconstructed from your video; rewrite it into the ad you want (cast, palette, copy, claims). Every scene frame reads it via target_blueprint.`,
19375
+ edit_prompt: `Edit ${path12.basename(blueprintPath)} \u2014 the blueprint deconstructed from your video; rewrite it into the ad you want (cast, palette, copy, claims). Every scene frame reads it via target_blueprint.`,
18622
19376
  recurring_elements_to_supply: report.elements,
18623
19377
  voices_to_confirm: report.dialogue.map((d) => ({
18624
19378
  scene: d.scene,
@@ -18632,6 +19386,13 @@ var scaffoldVideoCommand = defineCommand90({
18632
19386
  scenes_clamped_to_15s: report.clamped_scenes,
18633
19387
  oversize_scenes: report.oversize_scenes,
18634
19388
  overstuffed_scenes: report.overstuffed_scenes,
19389
+ // A photoreal on-camera person/animal on Seedance can trip ByteDance's
19390
+ // real-person-likeness filter (422 content_policy_blocked, NON-retryable — no
19391
+ // prompt reframe clears it). Surface the escape BEFORE the billed run so a
19392
+ // face-heavy ad isn't discovered broken mid-render.
19393
+ ...report.elements.some((e) => e.type === "person" || e.type === "animal") && /seedance/i.test(videoModel) ? {
19394
+ content_policy_risk: "This ad has a photoreal on-camera cast generating on Seedance. ByteDance's real-person-likeness filter can reject a photoreal AI face with a NON-retryable 422 (content_policy_blocked) \u2014 no prompt change clears it. If clips fail that way, regenerate on Veo (re-run with `--video-model google/veo-3.1-fast`) or make the frame less photoreal."
19395
+ } : {},
18635
19396
  note: "Drop ONE real source image at each el_* [TODO] (reused across every frame that element appears in), confirm each voice_select casting, then `baker canvas validate` and `baker canvas run`. Running generates many billed image/video/audio assets \u2014 it is not free."
18636
19397
  }
18637
19398
  },
@@ -18644,8 +19405,8 @@ var scaffoldVideoCommand = defineCommand90({
18644
19405
  });
18645
19406
 
18646
19407
  // src/commands/canvas/set-prompt.ts
18647
- import { readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
18648
- import path10 from "path";
19408
+ import { readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
19409
+ import path13 from "path";
18649
19410
  import { defineCommand as defineCommand91 } from "citty";
18650
19411
  function setNodePrompt(canvas, nodeId, text) {
18651
19412
  const nodes = canvas?.nodes;
@@ -18673,17 +19434,17 @@ var setPromptCommand = defineCommand91({
18673
19434
  "text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
18674
19435
  },
18675
19436
  async run({ args }) {
18676
- const filePath = path10.resolve(String(args.file));
19437
+ const filePath = path13.resolve(String(args.file));
18677
19438
  let canvas;
18678
19439
  try {
18679
- canvas = JSON.parse(await readFile7(filePath, "utf8"));
19440
+ canvas = JSON.parse(await readFile8(filePath, "utf8"));
18680
19441
  } catch (e) {
18681
19442
  process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "parse", message: String(e) } }, null, 2)}
18682
19443
  `);
18683
19444
  process.exit(2);
18684
19445
  }
18685
19446
  let text;
18686
- if (args["text-file"]) text = await readFile7(path10.resolve(String(args["text-file"])), "utf8");
19447
+ if (args["text-file"]) text = await readFile8(path13.resolve(String(args["text-file"])), "utf8");
18687
19448
  else if (args.text !== void 0) text = String(args.text);
18688
19449
  else {
18689
19450
  process.stderr.write(
@@ -18704,14 +19465,14 @@ var setPromptCommand = defineCommand91({
18704
19465
  process.exit(2);
18705
19466
  return;
18706
19467
  }
18707
- const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path10.dirname(filePath)), defaultRegistry());
19468
+ const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path13.dirname(filePath)), defaultRegistry());
18708
19469
  if (!validation.ok) {
18709
19470
  process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
18710
19471
  `);
18711
19472
  process.exit(2);
18712
19473
  return;
18713
19474
  }
18714
- await writeFile3(filePath, `${JSON.stringify(updated, null, 2)}
19475
+ await writeFile4(filePath, `${JSON.stringify(updated, null, 2)}
18715
19476
  `, "utf8");
18716
19477
  process.stdout.write(`${JSON.stringify({ ok: true, node: String(args.node), bytes: text.length }, null, 2)}
18717
19478
  `);
@@ -18719,8 +19480,8 @@ var setPromptCommand = defineCommand91({
18719
19480
  });
18720
19481
 
18721
19482
  // src/commands/canvas/validate.ts
18722
- import { readFile as readFile8 } from "fs/promises";
18723
- import path11 from "path";
19483
+ import { readFile as readFile9 } from "fs/promises";
19484
+ import path14 from "path";
18724
19485
  import { defineCommand as defineCommand92 } from "citty";
18725
19486
  var validateCommand = defineCommand92({
18726
19487
  meta: {
@@ -18729,8 +19490,8 @@ var validateCommand = defineCommand92({
18729
19490
  },
18730
19491
  args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
18731
19492
  async run({ args }) {
18732
- const filePath = path11.resolve(String(args.file));
18733
- const raw = await readFile8(filePath, "utf8");
19493
+ const filePath = path14.resolve(String(args.file));
19494
+ const raw = await readFile9(filePath, "utf8");
18734
19495
  let parsed;
18735
19496
  try {
18736
19497
  parsed = JSON.parse(raw);
@@ -18740,7 +19501,7 @@ var validateCommand = defineCommand92({
18740
19501
  `);
18741
19502
  process.exit(2);
18742
19503
  }
18743
- parsed = resolveRelativeCanvasPaths(parsed, path11.dirname(filePath));
19504
+ parsed = resolveRelativeCanvasPaths(parsed, path14.dirname(filePath));
18744
19505
  const result = await validateCanvasDeep(parsed, defaultRegistry());
18745
19506
  if (!result.ok) {
18746
19507
  process.stderr.write(`${JSON.stringify({ ok: false, issues: result.issues }, null, 2)}
@@ -18799,7 +19560,7 @@ import { defineCommand as defineCommand95 } from "citty";
18799
19560
  import { defineCommand as defineCommand94 } from "citty";
18800
19561
 
18801
19562
  // src/commands/images/api.ts
18802
- import { readFile as readFile9 } from "fs/promises";
19563
+ import { readFile as readFile10 } from "fs/promises";
18803
19564
  import { extname } from "path";
18804
19565
  var imageProcessingTimeoutMs = 18e4;
18805
19566
  var imageReadyPollIntervalMs = 2e3;
@@ -18813,7 +19574,7 @@ var mimeMap = {
18813
19574
  ".avif": "image/avif"
18814
19575
  };
18815
19576
  var defaultImageApiDeps = {
18816
- readFile: readFile9,
19577
+ readFile: readFile10,
18817
19578
  post: apiPost,
18818
19579
  get: apiGet,
18819
19580
  sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
@@ -18877,6 +19638,16 @@ registerSchema({
18877
19638
  type: "string",
18878
19639
  description: "Optional URL of the original reference ad",
18879
19640
  required: false
19641
+ },
19642
+ slug: {
19643
+ type: "string",
19644
+ description: "Creative slug (src/creatives/<slug>/) \u2014 attaches the image to that creative's row",
19645
+ required: false
19646
+ },
19647
+ runId: {
19648
+ type: "string",
19649
+ description: "Canvas run id (r_\u2026) of the approved generation to pin as published",
19650
+ required: false
18880
19651
  }
18881
19652
  }
18882
19653
  });
@@ -18886,6 +19657,13 @@ function detectCreativeContentType(filePath) {
18886
19657
  unsupportedMessage: "Unsupported creative image extension. Use PNG, JPG, or WebP."
18887
19658
  });
18888
19659
  }
19660
+ function chatIdFromEnv() {
19661
+ try {
19662
+ return getEnv().BAKER_CHAT_ID || void 0;
19663
+ } catch {
19664
+ return void 0;
19665
+ }
19666
+ }
18889
19667
  function parseOptionalUrl(value) {
18890
19668
  if (value === void 0 || value.trim() === "") {
18891
19669
  return void 0;
@@ -18916,7 +19694,11 @@ async function publishCreative(args, deps = defaultImageApiDeps) {
18916
19694
  return publishImageAsCreative(deps, {
18917
19695
  imageId: upload.imageId,
18918
19696
  title,
18919
- sourceReferenceUrl
19697
+ sourceReferenceUrl,
19698
+ slug: args.slug,
19699
+ runId: args.runId,
19700
+ // Attribute the publish to the driving chat (injected by the bridge).
19701
+ chatId: chatIdFromEnv()
18920
19702
  });
18921
19703
  }
18922
19704
  var publishCommand = defineCommand94({
@@ -18932,6 +19714,16 @@ var publishCommand = defineCommand94({
18932
19714
  type: "string",
18933
19715
  description: "Optional URL of the original reference ad",
18934
19716
  required: false
19717
+ },
19718
+ slug: {
19719
+ type: "string",
19720
+ description: "Creative slug (src/creatives/<slug>/) \u2014 attaches the image to that creative's row",
19721
+ required: false
19722
+ },
19723
+ runId: {
19724
+ type: "string",
19725
+ description: "Canvas run id (r_\u2026) of the approved generation to pin as published",
19726
+ required: false
18935
19727
  }
18936
19728
  },
18937
19729
  run: async ({ args }) => {
@@ -18950,7 +19742,9 @@ var publishCommand = defineCommand94({
18950
19742
  file,
18951
19743
  title,
18952
19744
  context: args.context,
18953
- sourceReferenceUrl: args.sourceReferenceUrl
19745
+ sourceReferenceUrl: args.sourceReferenceUrl,
19746
+ slug: args.slug,
19747
+ runId: args.runId
18954
19748
  });
18955
19749
  writeJson({ ok: true, data });
18956
19750
  } catch (err) {
@@ -19810,7 +20604,7 @@ function cropSprite(input, region) {
19810
20604
 
19811
20605
  // src/lib/image/io.ts
19812
20606
  import { randomBytes } from "crypto";
19813
- import { glob as fsGlob, readFile as readFile10, rename, stat as stat2, writeFile as writeFile4 } from "fs/promises";
20607
+ import { glob as fsGlob, readFile as readFile11, rename, stat as stat2, writeFile as writeFile5 } from "fs/promises";
19814
20608
  import { dirname as dirname2, extname as extname2, join as join3, resolve as resolve4 } from "path";
19815
20609
  var REMOTE_RE = /^https?:\/\//i;
19816
20610
  var GLOB_RE = /[*?[\]{}]/;
@@ -19846,11 +20640,11 @@ async function readImageBuffer(pathOrUrl) {
19846
20640
  }
19847
20641
  return Buffer.from(await response.arrayBuffer());
19848
20642
  }
19849
- return readFile10(pathOrUrl);
20643
+ return readFile11(pathOrUrl);
19850
20644
  }
19851
- async function isDirectory(path12) {
20645
+ async function isDirectory(path15) {
19852
20646
  try {
19853
- const s = await stat2(path12);
20647
+ const s = await stat2(path15);
19854
20648
  return s.isDirectory();
19855
20649
  } catch {
19856
20650
  return false;
@@ -19869,7 +20663,7 @@ async function atomicWrite(targetPath, data) {
19869
20663
  const absolute = resolve4(targetPath);
19870
20664
  const dir = dirname2(absolute);
19871
20665
  const tmp = join3(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
19872
- await writeFile4(tmp, data);
20666
+ await writeFile5(tmp, data);
19873
20667
  await rename(tmp, absolute);
19874
20668
  }
19875
20669
 
@@ -20212,7 +21006,7 @@ var findCommand = defineCommand108({
20212
21006
  });
20213
21007
 
20214
21008
  // src/commands/images/generate.ts
20215
- import { readFile as readFile11 } from "fs/promises";
21009
+ import { readFile as readFile12 } from "fs/promises";
20216
21010
  import { defineCommand as defineCommand109 } from "citty";
20217
21011
  import sharp2 from "sharp";
20218
21012
  var GENERATE_TIMEOUT_MS = 18e4;
@@ -20302,7 +21096,7 @@ async function resolveReferences(spec) {
20302
21096
  }
20303
21097
  let raw;
20304
21098
  try {
20305
- raw = await readFile11(entry);
21099
+ raw = await readFile12(entry);
20306
21100
  } catch {
20307
21101
  throw new ApiError("VALIDATION_ERROR", `Reference file not found: ${entry}`);
20308
21102
  }
@@ -23912,121 +24706,11 @@ var schemaCommand = defineCommand147({
23912
24706
  }
23913
24707
  });
23914
24708
 
23915
- // src/commands/tags/index.ts
23916
- import { defineCommand as defineCommand148 } from "citty";
23917
-
23918
- // src/commands/tags/shared.ts
23919
- function failApi3(err) {
23920
- if (err instanceof ApiError) {
23921
- writeJson({ ok: false, error: { code: err.code, message: err.message } });
23922
- process.exit(1);
23923
- }
23924
- if (err instanceof Error) {
23925
- writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: err.message } });
23926
- process.exit(1);
23927
- }
23928
- writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
23929
- process.exit(1);
23930
- }
23931
- function renderEffectiveEntry(entry) {
23932
- const parts = [entry.ref, entry.type];
23933
- if (entry.identifier !== void 0) {
23934
- parts.push(entry.identifier);
23935
- }
23936
- if (entry.staged !== void 0) {
23937
- parts.push(`[staged: ${entry.staged}]`);
23938
- }
23939
- const secretBits = [
23940
- ...entry.secretsSet.map((field) => `${field} \u2713 set`),
23941
- ...entry.secretsPending.map((field) => `${field} \u23F3 pending`)
23942
- ];
23943
- if (secretBits.length > 0) {
23944
- parts.push(`[secrets: ${secretBits.join(", ")}]`);
23945
- }
23946
- return parts.join(" ");
23947
- }
23948
-
23949
- // src/commands/tags/index.ts
23950
- registerSchema({
23951
- command: "tags.list",
23952
- description: "Effective marketing tags for this chat: production tags overlaid with the changes staged in this chat. The printed refs (tag ids or tag_temp_*) are exactly what flow side-effect tagIds should reference. Read-only \u2014 every tag change (create, edit, delete) goes through the request_tag_input approval tool.",
23953
- args: {
23954
- json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list", required: false }
23955
- }
23956
- });
23957
- async function listTags(json) {
23958
- try {
23959
- const chatId = requireChatId();
23960
- const response = await apiPost("/api/tags/list", { chatId });
23961
- if (json) {
23962
- writeJson({ ok: true, data: response });
23963
- return;
23964
- }
23965
- if (response.tags.length === 0) {
23966
- process.stdout.write("No tags configured. Propose one with the request_tag_input tool (baker_ui).\n");
23967
- return;
23968
- }
23969
- process.stdout.write(`${response.tags.map(renderEffectiveEntry).join("\n")}
23970
- `);
23971
- } catch (err) {
23972
- failApi3(err);
23973
- }
23974
- }
23975
- var listCommand7 = defineCommand148({
23976
- meta: {
23977
- name: "list",
23978
- description: "Effective tags for this chat (production + staged). Refs printed here are what flow side-effect tagIds should use. Example: baker tags list"
23979
- },
23980
- args: { json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" } },
23981
- run: async ({ args }) => {
23982
- await listTags(args.json === true);
23983
- }
23984
- });
23985
- async function listDraft3() {
23986
- try {
23987
- const chatId = requireChatId();
23988
- const response = await apiPost("/api/tags/draft", { chatId });
23989
- writeJson({ ok: true, data: response });
23990
- } catch (err) {
23991
- failApi3(err);
23992
- }
23993
- }
23994
- var draftCommand3 = defineCommand148({
23995
- meta: {
23996
- name: "draft",
23997
- description: "Review the tag changes staged in this chat (read-only). Staged changes were approved via request_tag_input and apply when the chat is published; to amend or drop one, propose a follow-up change through the same tool (a delete on a tag_temp_* ref drops the staged create)."
23998
- },
23999
- run: async () => {
24000
- await listDraft3();
24001
- }
24002
- });
24003
- var tagsCommand3 = defineCommand148({
24004
- meta: {
24005
- name: "tags",
24006
- description: `Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, \u2026) \u2014 production tags plus the changes staged in this chat.
24007
-
24008
- This command is READ-ONLY. Every tag change \u2014 create, edit, or delete \u2014 goes through the request_tag_input tool (baker_ui MCP server): propose one or more changes (each becomes a tab in one approval form), pre-fill the non-secret fields you know, and the user reviews, fills secrets, and approves or skips each. Secret values (accessToken, apiSecret, authorizationToken, apiKey, conversionToken, OAuth connections) never pass through the CLI or the chat.
24009
-
24010
- Refs: \`baker tags list\` prints each tag's ref \u2014 a real tag id, or tag_temp_* for creates staged in this chat. Use these refs as flow side-effect tagIds; they keep resolving after publish.
24011
-
24012
- Examples:
24013
- baker tags list # production + staged, with secret status
24014
- baker tags draft # review the staged changes awaiting publish`
24015
- },
24016
- subCommands: {
24017
- list: listCommand7,
24018
- draft: draftCommand3
24019
- },
24020
- run: async () => {
24021
- await listTags(false);
24022
- }
24023
- });
24024
-
24025
24709
  // src/commands/testimonials/index.ts
24026
- import { defineCommand as defineCommand152 } from "citty";
24710
+ import { defineCommand as defineCommand151 } from "citty";
24027
24711
 
24028
24712
  // src/commands/testimonials/get.ts
24029
- import { defineCommand as defineCommand149 } from "citty";
24713
+ import { defineCommand as defineCommand148 } from "citty";
24030
24714
  registerSchema({
24031
24715
  command: "testimonials.get",
24032
24716
  description: "Get a single testimonial by ID",
@@ -24034,7 +24718,7 @@ registerSchema({
24034
24718
  id: { type: "string", description: "Testimonial ID", required: true }
24035
24719
  }
24036
24720
  });
24037
- var getCommand4 = defineCommand149({
24721
+ var getCommand4 = defineCommand148({
24038
24722
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
24039
24723
  args: {
24040
24724
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -24071,7 +24755,7 @@ var getCommand4 = defineCommand149({
24071
24755
  });
24072
24756
 
24073
24757
  // src/commands/testimonials/list.ts
24074
- import { defineCommand as defineCommand150 } from "citty";
24758
+ import { defineCommand as defineCommand149 } from "citty";
24075
24759
  registerSchema({
24076
24760
  command: "testimonials.list",
24077
24761
  description: "List testimonials with optional filters.",
@@ -24101,7 +24785,7 @@ registerSchema({
24101
24785
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
24102
24786
  }
24103
24787
  });
24104
- var listCommand8 = defineCommand150({
24788
+ var listCommand7 = defineCommand149({
24105
24789
  meta: {
24106
24790
  name: "list",
24107
24791
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -24150,7 +24834,7 @@ var listCommand8 = defineCommand150({
24150
24834
  });
24151
24835
 
24152
24836
  // src/commands/testimonials/search.ts
24153
- import { defineCommand as defineCommand151 } from "citty";
24837
+ import { defineCommand as defineCommand150 } from "citty";
24154
24838
  registerSchema({
24155
24839
  command: "testimonials.search",
24156
24840
  description: "Search testimonials by text query. Uses hybrid BM25 + vector + reranking.",
@@ -24181,7 +24865,7 @@ registerSchema({
24181
24865
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
24182
24866
  }
24183
24867
  });
24184
- var searchCommand2 = defineCommand151({
24868
+ var searchCommand2 = defineCommand150({
24185
24869
  meta: {
24186
24870
  name: "search",
24187
24871
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -24252,10 +24936,10 @@ var searchCommand2 = defineCommand151({
24252
24936
  });
24253
24937
 
24254
24938
  // src/commands/testimonials/tags.ts
24255
- var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
24939
+ var tagsCommand3 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
24256
24940
 
24257
24941
  // src/commands/testimonials/index.ts
24258
- var testimonialsCommand = defineCommand152({
24942
+ var testimonialsCommand = defineCommand151({
24259
24943
  meta: {
24260
24944
  name: "testimonials",
24261
24945
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -24270,16 +24954,16 @@ Examples:
24270
24954
  subCommands: {
24271
24955
  get: getCommand4,
24272
24956
  search: searchCommand2,
24273
- list: listCommand8,
24274
- tags: tagsCommand4
24957
+ list: listCommand7,
24958
+ tags: tagsCommand3
24275
24959
  }
24276
24960
  });
24277
24961
 
24278
24962
  // src/commands/videos/index.ts
24279
- import { defineCommand as defineCommand157 } from "citty";
24963
+ import { defineCommand as defineCommand156 } from "citty";
24280
24964
 
24281
24965
  // src/commands/videos/delete.ts
24282
- import { defineCommand as defineCommand153 } from "citty";
24966
+ import { defineCommand as defineCommand152 } from "citty";
24283
24967
  registerSchema({
24284
24968
  command: "videos.delete",
24285
24969
  description: "Delete a video by ID",
@@ -24293,7 +24977,7 @@ registerSchema({
24293
24977
  }
24294
24978
  }
24295
24979
  });
24296
- var deleteCommand3 = defineCommand153({
24980
+ var deleteCommand3 = defineCommand152({
24297
24981
  meta: {
24298
24982
  name: "delete",
24299
24983
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -24334,7 +25018,7 @@ var deleteCommand3 = defineCommand153({
24334
25018
  });
24335
25019
 
24336
25020
  // src/commands/videos/get.ts
24337
- import { defineCommand as defineCommand154 } from "citty";
25021
+ import { defineCommand as defineCommand153 } from "citty";
24338
25022
  registerSchema({
24339
25023
  command: "videos.get",
24340
25024
  description: "Get a single video by ID",
@@ -24342,7 +25026,7 @@ registerSchema({
24342
25026
  id: { type: "string", description: "Video ID", required: true }
24343
25027
  }
24344
25028
  });
24345
- var getCommand5 = defineCommand154({
25029
+ var getCommand5 = defineCommand153({
24346
25030
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
24347
25031
  args: {
24348
25032
  id: { type: "positional", description: "Video ID", required: false },
@@ -24379,7 +25063,7 @@ var getCommand5 = defineCommand154({
24379
25063
  });
24380
25064
 
24381
25065
  // src/commands/videos/search.ts
24382
- import { defineCommand as defineCommand155 } from "citty";
25066
+ import { defineCommand as defineCommand154 } from "citty";
24383
25067
  registerSchema({
24384
25068
  command: "videos.search",
24385
25069
  description: "Search videos by text query. Only returns ready videos.",
@@ -24389,7 +25073,7 @@ registerSchema({
24389
25073
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
24390
25074
  }
24391
25075
  });
24392
- var searchCommand3 = defineCommand155({
25076
+ var searchCommand3 = defineCommand154({
24393
25077
  meta: {
24394
25078
  name: "search",
24395
25079
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -24436,12 +25120,12 @@ var searchCommand3 = defineCommand155({
24436
25120
  });
24437
25121
 
24438
25122
  // src/commands/videos/tags.ts
24439
- var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
25123
+ var tagsCommand4 = makeTagsCommand("videos", "video", "/api/videos/tags");
24440
25124
 
24441
25125
  // src/commands/videos/upload.ts
24442
- import { readFile as readFile12, stat as stat3 } from "fs/promises";
25126
+ import { readFile as readFile13, stat as stat3 } from "fs/promises";
24443
25127
  import { extname as extname3 } from "path";
24444
- import { defineCommand as defineCommand156 } from "citty";
25128
+ import { defineCommand as defineCommand155 } from "citty";
24445
25129
  var MIME_MAP = {
24446
25130
  ".mp4": "video/mp4",
24447
25131
  ".mov": "video/quicktime",
@@ -24475,7 +25159,7 @@ function detectContentType(filePath) {
24475
25159
  }
24476
25160
  return mime;
24477
25161
  }
24478
- var uploadCommand2 = defineCommand156({
25162
+ var uploadCommand2 = defineCommand155({
24479
25163
  meta: {
24480
25164
  name: "upload",
24481
25165
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -24504,7 +25188,7 @@ var uploadCommand2 = defineCommand156({
24504
25188
  return;
24505
25189
  }
24506
25190
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
24507
- const fileBuffer = await readFile12(filePath);
25191
+ const fileBuffer = await readFile13(filePath);
24508
25192
  const uploadResponse = await fetch(uploadUrl, {
24509
25193
  method: "PUT",
24510
25194
  headers: { "Content-Type": contentType },
@@ -24529,7 +25213,7 @@ var uploadCommand2 = defineCommand156({
24529
25213
  });
24530
25214
 
24531
25215
  // src/commands/videos/index.ts
24532
- var videosCommand = defineCommand157({
25216
+ var videosCommand = defineCommand156({
24533
25217
  meta: {
24534
25218
  name: "videos",
24535
25219
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -24547,15 +25231,15 @@ Examples:
24547
25231
  search: searchCommand3,
24548
25232
  upload: uploadCommand2,
24549
25233
  delete: deleteCommand3,
24550
- tags: tagsCommand5
25234
+ tags: tagsCommand4
24551
25235
  }
24552
25236
  });
24553
25237
 
24554
25238
  // src/commands/winning-ads/index.ts
24555
- import { defineCommand as defineCommand160 } from "citty";
25239
+ import { defineCommand as defineCommand159 } from "citty";
24556
25240
 
24557
25241
  // src/commands/winning-ads/advertisers.ts
24558
- import { defineCommand as defineCommand158 } from "citty";
25242
+ import { defineCommand as defineCommand157 } from "citty";
24559
25243
  registerSchema({
24560
25244
  command: "winning-ads.advertisers",
24561
25245
  description: "Resolve a brand name to advertiser_id(s) in the ad-dna corpus \u2014 to find your OWN advertiser (to --exclude-advertiser) or a competitor (to --advertiser-id).",
@@ -24568,7 +25252,7 @@ registerSchema({
24568
25252
  function identity(record) {
24569
25253
  return record;
24570
25254
  }
24571
- var advertisersCommand2 = defineCommand158({
25255
+ var advertisersCommand2 = defineCommand157({
24572
25256
  meta: {
24573
25257
  name: "advertisers",
24574
25258
  description: 'Resolve a brand name to advertiser_id(s). Use it to find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id. Example: baker winning-ads advertisers "Deel" --output md'
@@ -24619,7 +25303,7 @@ var advertisersCommand2 = defineCommand158({
24619
25303
  });
24620
25304
 
24621
25305
  // src/commands/winning-ads/search.ts
24622
- import { defineCommand as defineCommand159 } from "citty";
25306
+ import { defineCommand as defineCommand158 } from "citty";
24623
25307
  registerSchema({
24624
25308
  command: "winning-ads.search",
24625
25309
  description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
@@ -24727,7 +25411,7 @@ function buildSearchBody(args) {
24727
25411
  }
24728
25412
  return body;
24729
25413
  }
24730
- var searchCommand4 = defineCommand159({
25414
+ var searchCommand4 = defineCommand158({
24731
25415
  meta: {
24732
25416
  name: "search",
24733
25417
  description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
@@ -24839,7 +25523,7 @@ var searchCommand4 = defineCommand159({
24839
25523
  });
24840
25524
 
24841
25525
  // src/commands/winning-ads/index.ts
24842
- var winningAdsCommand = defineCommand160({
25526
+ var winningAdsCommand = defineCommand159({
24843
25527
  meta: {
24844
25528
  name: "winning-ads",
24845
25529
  description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
@@ -24879,11 +25563,11 @@ function getCliVersion() {
24879
25563
  }
24880
25564
 
24881
25565
  // src/cli.ts
24882
- var main = defineCommand161({
25566
+ var main = defineCommand160({
24883
25567
  meta: {
24884
25568
  name: "baker",
24885
25569
  version: getCliVersion(),
24886
- description: `AI-agent CLI for finding and managing images, videos, testimonials, action items, scheduled actions, marketing tags, and ad platform data in Baker.
25570
+ description: `AI-agent CLI for finding and managing images, videos, testimonials, action items, scheduled actions, and ad platform data in Baker.
24887
25571
 
24888
25572
  Auth: Set BAKER_API_KEY (starts with bk_) and BAKER_API_URL environment variables.
24889
25573
  Chat: Set BAKER_CHAT_ID for action and scheduled-action commands that stage changes against a chat.
@@ -24903,7 +25587,6 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
24903
25587
  videos: videosCommand,
24904
25588
  testimonials: testimonialsCommand,
24905
25589
  canvas: canvasCommand,
24906
- tags: tagsCommand3,
24907
25590
  "winning-ads": winningAdsCommand,
24908
25591
  mcp: mcpCommand,
24909
25592
  schema: schemaCommand