@nodaro/shared 3.11.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/index.cjs +2047 -84
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1556 -27
  4. package/dist/index.d.ts +1556 -27
  5. package/dist/index.js +1861 -85
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/__tests__/caption-styles.test.ts +207 -0
  9. package/src/__tests__/edl-multicam.test.ts +304 -0
  10. package/src/__tests__/edl.test.ts +822 -0
  11. package/src/__tests__/fan-out-rows.test.ts +208 -0
  12. package/src/__tests__/instagram-scrape.test.ts +66 -0
  13. package/src/__tests__/llm-models.test.ts +48 -11
  14. package/src/__tests__/meta-ads-scrape.test.ts +284 -0
  15. package/src/__tests__/node-runtime-keys.test.ts +15 -0
  16. package/src/__tests__/presentation-utils.test.ts +67 -0
  17. package/src/__tests__/producer-types.test.ts +19 -0
  18. package/src/__tests__/schedule-rules.test.ts +265 -0
  19. package/src/__tests__/speaker-layouts.test.ts +203 -0
  20. package/src/__tests__/transcribe-capabilities.test.ts +104 -0
  21. package/src/__tests__/transcribe-preflight.test.ts +60 -0
  22. package/src/__tests__/trigger-feeds.test.ts +39 -0
  23. package/src/__tests__/video-duration-auto.test.ts +65 -0
  24. package/src/__tests__/video-duration.test.ts +56 -0
  25. package/src/__tests__/video-link.test.ts +137 -0
  26. package/src/__tests__/workflow-export-strip.test.ts +59 -1
  27. package/src/caption-styles.ts +240 -0
  28. package/src/credit-identifiers.ts +31 -0
  29. package/src/edit-plan-contract.ts +96 -0
  30. package/src/edl-multicam.ts +185 -0
  31. package/src/edl.ts +747 -0
  32. package/src/entity-image-handle.ts +24 -1
  33. package/src/fan-out-rows.ts +213 -0
  34. package/src/index.ts +206 -3
  35. package/src/instagram-scrape.ts +204 -0
  36. package/src/llm-models.ts +80 -3
  37. package/src/meta-ads-scrape.ts +463 -0
  38. package/src/model-catalog.ts +48 -5
  39. package/src/model-constants.ts +148 -5
  40. package/src/node-mappable-fields.ts +2 -0
  41. package/src/node-runtime-keys.ts +28 -0
  42. package/src/presentation-utils.ts +49 -0
  43. package/src/producer-types.ts +20 -0
  44. package/src/schedule-rules.ts +484 -0
  45. package/src/speaker-layouts.ts +220 -0
  46. package/src/transcribe-preflight.ts +101 -0
  47. package/src/trigger-feeds.ts +59 -0
  48. package/src/trigger-node-types.ts +20 -0
  49. package/src/video-duration-auto.ts +18 -0
  50. package/src/video-duration.ts +32 -0
  51. package/src/video-link.ts +167 -0
  52. package/src/workflow-export.ts +37 -1
@@ -47,12 +47,31 @@ const AGGREGATE_LANE_EFFECTIVE_TYPE: Readonly<Record<string, string>> = {
47
47
  "out-text": "list",
48
48
  }
49
49
 
50
+ /**
51
+ * Meta Ads scraper: besides its `json` handle it emits the FEATURED ad's
52
+ * copy, creative image and creative video on typed handles. Each behaves as
53
+ * the canonical single-media producer of that type — `text` maps to
54
+ * `combine-text` (ONE string, not a list), so it reaches prompt inputs but
55
+ * not the list consumers.
56
+ */
57
+ // Shared by every scraper node (Meta Ads, Instagram, …): the typed
58
+ // text / image / video handles emit the canonical single-media producers.
59
+ const SCRAPER_HANDLE_EFFECTIVE_TYPE: Readonly<Record<string, string>> = {
60
+ text: "combine-text",
61
+ image: "upload-image",
62
+ video: "upload-video",
63
+ }
64
+ const SCRAPER_SOURCE_TYPES = new Set(["meta-ads-scrape", "instagram-scrape"])
65
+
50
66
  /**
51
67
  * The effective output TYPE a given source handle emits. Returns the raw node
52
68
  * type for every `(type, handle)` pair EXCEPT:
53
69
  * - an entity `image` handle → `"upload-image"` (a plain image producer);
54
70
  * - an aggregate (group / collect) lane handle → the plain producer of that
55
- * lane's media type (see AGGREGATE_LANE_EFFECTIVE_TYPE).
71
+ * lane's media type (see AGGREGATE_LANE_EFFECTIVE_TYPE);
72
+ * - a Meta Ads `text` / `image` / `video` handle → the plain producer of
73
+ * that type (see META_ADS_HANDLE_EFFECTIVE_TYPE); its `json` handle keeps
74
+ * the raw type.
56
75
  * Pure — safe for both frontend and backend.
57
76
  */
58
77
  export function resolveEffectiveSourceType(
@@ -66,6 +85,10 @@ export function resolveEffectiveSourceType(
66
85
  const effective = AGGREGATE_LANE_EFFECTIVE_TYPE[sourceHandleId ?? ""]
67
86
  if (effective) return effective
68
87
  }
88
+ if (SCRAPER_SOURCE_TYPES.has(rawSourceType ?? "")) {
89
+ const effective = SCRAPER_HANDLE_EFFECTIVE_TYPE[sourceHandleId ?? ""]
90
+ if (effective) return effective
91
+ }
69
92
  return rawSourceType ?? ""
70
93
  }
71
94
 
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Fan-out pairs by ROW.
3
+ *
4
+ * When several list wires reach one node ("prompt" + "negative" columns of the
5
+ * same List, two Extract Field lists cut from the same JSON array, …) the node
6
+ * runs once per row and every wire contributes the value of THAT row. Both DAG
7
+ * engines (backend orchestrator + in-browser executor) build the plan with the
8
+ * helpers below, so the rules live in exactly one place:
9
+ *
10
+ * - which list drives the fan-out, and which rows run, never depend on the
11
+ * order the wires were drawn in (`resolveListFanOut`);
12
+ * - the driving item is only written into the prompt when its wire actually
13
+ * feeds the prompt (`fanOutTextFeedsPrompt`, `NON_PROMPT_TEXT_LANES`);
14
+ * - an empty cell stays empty IN ITS ROW instead of pulling the rows below it
15
+ * up (`liveRowColumn`, `alignedFieldList`, `compactWithRows`);
16
+ * - an iteration keeps its row when Repeat xN multiplies it (`planFanOut`).
17
+ */
18
+ import { evaluateJsonPath, stringifyPathResults } from "./json-path.js"
19
+ import { expandItemsWithRepeat } from "./repeat-types.js"
20
+
21
+ /**
22
+ * The text LANES the input resolvers route somewhere OTHER than the prompt slot
23
+ * (`negative` → negativePrompt, `system-prompt` → systemPrompt, an avatar's
24
+ * `script`, an editor's `transcript`, …). A list that drives a fan-out through
25
+ * one of these must NOT also be written into the prompt: the per-row resolution
26
+ * already delivered it where it belongs.
27
+ *
28
+ * Keyed by handle, scoped by node type, because the routers are: `transcript`
29
+ * is the caption track of Add Captions but the very text Forced Alignment
30
+ * aligns. `"*"` = every node type (the router branch is unconditional).
31
+ *
32
+ * DERIVED, not remembered: each engine has a totality test
33
+ * (`fanout-text-handle-totality`) that routes a text source into EVERY input
34
+ * handle of EVERY node type through the real resolver and fails the build on any
35
+ * pair where this table and the router disagree — in either direction. An
36
+ * unlisted lane keeps the old behavior (the item is the prompt), so a miss can
37
+ * only ever reproduce the old bug on that one lane, never a new one.
38
+ */
39
+ export const NON_PROMPT_TEXT_LANES: Readonly<Record<string, "*" | readonly string[]>> = {
40
+ negative: "*",
41
+ "system-prompt": "*",
42
+ script: ["ai-avatar"],
43
+ transcript: ["add-captions", "apply-edl", "edit-plan"],
44
+ edl: ["apply-edl"],
45
+ silence: ["edit-plan"],
46
+ qrText: ["image-overlay"],
47
+ transition: ["slideshow"],
48
+ }
49
+
50
+ /** Does a TEXT list wired to `targetHandle` of a `nodeType` node feed its prompt slot? */
51
+ export function fanOutTextFeedsPrompt(nodeType: string | null | undefined, targetHandle: string | null | undefined): boolean {
52
+ const scope = Object.hasOwn(NON_PROMPT_TEXT_LANES, targetHandle ?? "") ? NON_PROMPT_TEXT_LANES[targetHandle ?? ""] : undefined
53
+ if (scope === undefined) return true
54
+ return scope !== "*" && !scope.includes(nodeType ?? "")
55
+ }
56
+
57
+ const isBlank = (v: unknown): boolean => typeof v !== "string" || v.trim().length === 0
58
+
59
+ /**
60
+ * Drop the empty entries of a row-aligned list, remembering the row every kept
61
+ * value came from. The items are what drives a fan-out (nothing runs for an
62
+ * empty cell); the rows are what keeps the OTHER wires on the same row.
63
+ */
64
+ export function compactWithRows(aligned: readonly string[]): { items: string[]; rowIndices: number[] } {
65
+ const items: string[] = []
66
+ const rowIndices: number[] = []
67
+ aligned.forEach((value, row) => {
68
+ if (isBlank(value)) return
69
+ items.push(value)
70
+ rowIndices.push(row)
71
+ })
72
+ return { items, rowIndices }
73
+ }
74
+
75
+ /**
76
+ * One column of a manual List table, row-aligned: a row is dropped only when
77
+ * EVERY cell of it is blank (the trailing empty row the editor keeps), and an
78
+ * empty cell of a row that has content stays in place as "".
79
+ */
80
+ export function liveRowColumn(rows: ReadonlyArray<ReadonlyArray<string | undefined>>, colIndex: number): string[] {
81
+ return rows
82
+ .filter((row) => row.some((cell) => !isBlank(cell)))
83
+ .map((row) => (typeof row[colIndex] === "string" ? (row[colIndex] as string).trim() : ""))
84
+ }
85
+
86
+ /** One list wire that could drive a fan-out. */
87
+ export interface FanOutCandidate {
88
+ /** Target handle of the consumer-side wire. */
89
+ targetHandle: string | null | undefined
90
+ /** The wire's values in row order — may hold "" for an empty row. */
91
+ aligned: readonly string[]
92
+ }
93
+
94
+ /**
95
+ * Turn the lists that reach a node into ONE fan-out, so that nothing depends on
96
+ * the order the wires were drawn in. `candidates` are the "each" lists that hold
97
+ * more than one value, in wire order.
98
+ *
99
+ * The PRIMARY is the list that holds the most values (the first of them when
100
+ * several tie): it sets the row space. Both engines get it from here, so the
101
+ * orchestrator and the in-browser executor cannot disagree on how many times a
102
+ * node runs. Lists with the SAME number of rows share that row space — two
103
+ * columns of one table, two Extract Field lists cut from one array — and are
104
+ * settled together:
105
+ *
106
+ * - the DRIVER (its item becomes the per-row override) is a text list that
107
+ * feeds the prompt, when there is one: that item is what must win over the
108
+ * typed prompt, and a list wired to `negative` must never supply it;
109
+ * - a ROW runs when ANY of those lists has a value in it. A row is never
110
+ * dropped because one column is empty there — that column just contributes
111
+ * nothing for the row — and a row with no value anywhere does not run.
112
+ *
113
+ * A list with a different number of rows is not part of this: it keeps
114
+ * wrapping around per iteration, as it always has.
115
+ */
116
+ export function resolveListFanOut<C extends FanOutCandidate>(
117
+ candidates: readonly C[],
118
+ nodeType: string | null | undefined,
119
+ ): ListFanOut | undefined {
120
+ if (candidates.length === 0) return undefined
121
+ const held = (c: C) => compactWithRows(c.aligned).items.length
122
+ const primary = candidates.reduce((best, c) => (held(c) > held(best) ? c : best))
123
+ const space = candidates.filter((c) => c.aligned.length === primary.aligned.length)
124
+ const feedsPrompt = (c: C) => fanOutTextFeedsPrompt(nodeType, c.targetHandle) && isTextList(c.aligned)
125
+ const driver = feedsPrompt(primary) ? primary : (space.find(feedsPrompt) ?? primary)
126
+ const rowIndices: number[] = []
127
+ for (let row = 0; row < primary.aligned.length; row++) {
128
+ if (space.some((c) => !isBlank(c.aligned[row]))) rowIndices.push(row)
129
+ }
130
+ return {
131
+ items: rowIndices.map((row) => (isBlank(driver.aligned[row]) ? "" : driver.aligned[row])),
132
+ rowIndices,
133
+ targetHandle: driver.targetHandle,
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Is this fan-out item a media URL (as opposed to text)? The ONE guess both
139
+ * engines make when a list item has to be applied as an override — kept here so
140
+ * the backend worker and the in-browser executor cannot disagree on it.
141
+ */
142
+ export function isFanOutUrlItem(item: string): boolean {
143
+ return item.startsWith("http") || /\.(png|jpg|jpeg|webp|gif|mp4|mov|webm|mp3|wav|ogg)(\?|$)/i.test(item)
144
+ }
145
+
146
+ /** A media list never supplies a prompt override, whatever handle it is wired to.
147
+ * The FIRST value decides — a column is one kind of thing; this is not a scan. */
148
+ function isTextList(aligned: readonly string[]): boolean {
149
+ const first = aligned.find((v) => !isBlank(v))
150
+ return first !== undefined && !isFanOutUrlItem(first)
151
+ }
152
+
153
+ /** What a fan-out source resolved to. */
154
+ export interface ListFanOut {
155
+ /** The driving list's value for every row that runs — "" where the driver has
156
+ * none for a row another list keeps alive (nothing is overridden there). */
157
+ items: string[]
158
+ /** `rowIndices[k]` = the row `items[k]` came from, in the driver's row space. */
159
+ rowIndices: number[]
160
+ /** Target handle of the consumer-side wire that drives the fan-out. */
161
+ targetHandle: string | null | undefined
162
+ }
163
+
164
+ /** The iterations a node will run, in order. */
165
+ export interface FanOutPlan {
166
+ /** One entry per iteration (list items, repeat / provider sentinels). */
167
+ items: string[]
168
+ /** Row each iteration reads its inputs from; undefined when nothing is list-driven. */
169
+ rows: Array<number | undefined>
170
+ /** Handle the driving list is wired to; undefined when nothing is list-driven. */
171
+ targetHandle: string | null | undefined
172
+ }
173
+
174
+ /**
175
+ * Expand a fan-out into its iterations (list x Repeat xN, providers, repeats —
176
+ * `expandItemsWithRepeat` stays the single rule for that) and pin every
177
+ * iteration to the ROW its item came from. Repeat xN runs a row N times: the
178
+ * copies share the row, they do not walk on to the next one.
179
+ */
180
+ export function planFanOut(
181
+ fanOut: ListFanOut | undefined,
182
+ nodeType: string,
183
+ nodeData: Record<string, unknown>,
184
+ ): FanOutPlan | null {
185
+ const items = expandItemsWithRepeat(fanOut?.items, nodeType, nodeData)
186
+ if (!items) return null
187
+ const listDriven = fanOut !== undefined && fanOut.items.length > 1
188
+ if (!listDriven) return { items, rows: items.map(() => undefined), targetHandle: undefined }
189
+ const perRow = items.length / fanOut.items.length
190
+ return {
191
+ items,
192
+ rows: items.map((_, k) => fanOut.rowIndices[Math.floor(k / perRow)]),
193
+ targetHandle: fanOut.targetHandle,
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Extract Field, List output, over a root ARRAY: one entry per element, "" where
199
+ * the element has no value — so two Extract Field lists cut from the same array
200
+ * stay row-aligned. Returns undefined when rows cannot be defined (the root is
201
+ * not an array, an element fans into several values, or no element carries the
202
+ * field at all) — the caller then keeps the plain "values that exist" list.
203
+ */
204
+ export function alignedFieldList(value: unknown, path: string): string[] | undefined {
205
+ if (!Array.isArray(value) || value.length === 0) return undefined
206
+ const out: string[] = []
207
+ for (const element of value) {
208
+ const found = evaluateJsonPath(element, path)
209
+ if (found.length > 1) return undefined
210
+ out.push(stringifyPathResults(found)[0] ?? "")
211
+ }
212
+ return out.some((v) => v.length > 0) ? out : undefined
213
+ }
package/src/index.ts CHANGED
@@ -71,6 +71,10 @@ export {
71
71
  TEXT_TO_VIDEO_PROVIDERS,
72
72
  VIDEO_GEN_PROVIDERS,
73
73
  VIDEO_TO_VIDEO_PROVIDERS,
74
+ SEEDANCE_VIDEO_EDIT_PROVIDERS,
75
+ isSeedanceVideoEditProvider,
76
+ VIDEO_TO_VIDEO_NODE_PROVIDERS,
77
+ SEEDANCE_VIDEO_EDIT_SHAPE,
74
78
  FACE_SWAP_PROVIDERS,
75
79
  VIDEO_UPSCALE_PROVIDERS,
76
80
  EXTEND_VIDEO_PROVIDERS,
@@ -85,6 +89,12 @@ export {
85
89
  TEXT_TO_AUDIO_PROVIDERS,
86
90
  MUSIC_PROVIDERS,
87
91
  TRANSCRIBE_PROVIDERS,
92
+ TRANSCRIBE_LANES,
93
+ TRANSCRIBE_PROVIDER_CAPABILITIES,
94
+ transcribeProvidersWithWordTimestamps,
95
+ transcribeLaneSupportsWordTimestamps,
96
+ DEFAULT_TRANSCRIBE_PROVIDER,
97
+ DEFAULT_TRANSCRIBE_NODE_PROVIDER,
88
98
  SCRIPT_PROVIDERS,
89
99
  AI_WRITER_PROVIDERS,
90
100
  QA_CHECK_PROVIDERS,
@@ -168,6 +178,8 @@ export {
168
178
  applyDefaultVideoSelection,
169
179
  PRICING_DEFAULT_DURATION_SEC,
170
180
  pricedOutputDurationSec,
181
+ supportsAutoVideoDuration,
182
+ maxVideoDurationSec,
171
183
  PRICING_DEFAULT_RESOLUTION,
172
184
  } from "./model-constants.js"
173
185
 
@@ -196,6 +208,8 @@ export type {
196
208
  VideoGenProvider,
197
209
  VideoModeAlias,
198
210
  VideoToVideoProvider,
211
+ SeedanceVideoEditProvider,
212
+ VideoToVideoNodeProvider,
199
213
  VideoUpscaleProvider,
200
214
  ExtendVideoProvider,
201
215
  FaceSwapProvider,
@@ -205,6 +219,7 @@ export type {
205
219
  TextToAudioProvider,
206
220
  MusicProvider,
207
221
  TranscribeProvider,
222
+ TranscribeLane,
208
223
  ScriptProvider,
209
224
  AiWriterProvider,
210
225
  QaCheckProvider,
@@ -251,6 +266,7 @@ export {
251
266
  resolveImageGenCreditIdentifier,
252
267
  resolveNormalizedImageGen,
253
268
  buildVideoCreditModelIdentifier,
269
+ seedanceVideoEditCreditId,
254
270
  pricedVideoSelection,
255
271
  buildMotionCreditModelIdentifier,
256
272
  sunoCreditType,
@@ -261,7 +277,7 @@ export {
261
277
  export type { NormalizedImageGen, PricedVideoSelection } from "./credit-identifiers.js"
262
278
 
263
279
  export * from "./credit-estimators/index.js"
264
- export { extractVideoDurationFromNode } from "./video-duration.js"
280
+ export { extractVideoDurationFromNode, editPlanSourceDurationSec } from "./video-duration.js"
265
281
 
266
282
  export {
267
283
  resolveTopazUpscale,
@@ -284,6 +300,7 @@ export {
284
300
  getNodeResult,
285
301
  getNodeLabel,
286
302
  getInputFieldSchema,
303
+ mergeNodeInputOverrides,
287
304
  flattenItems,
288
305
  migrateToItems,
289
306
  validateNoNestedGroups,
@@ -357,6 +374,8 @@ export {
357
374
  ADVANCED_MODE_UNAVAILABLE_REASON,
358
375
  motionGraphicsFeature,
359
376
  effectiveReasoningEffort,
377
+ REASONING_OUTPUT_FLOOR,
378
+ reasoningOutputFloor,
360
379
  type LlmTier,
361
380
  type LlmFeature,
362
381
  type KieApiFormat,
@@ -426,6 +445,18 @@ export type {
426
445
 
427
446
  export { REPEATABLE_NODE_TYPES, REPEAT_PLACEHOLDER, PROVIDER_PLACEHOLDER_PREFIX, encodeProviderItem, decodeProviderItem, getEffectiveRepeatCount, expandItemsWithRepeat } from "./repeat-types.js"
428
447
 
448
+ export {
449
+ NON_PROMPT_TEXT_LANES,
450
+ fanOutTextFeedsPrompt,
451
+ isFanOutUrlItem,
452
+ compactWithRows,
453
+ liveRowColumn,
454
+ resolveListFanOut,
455
+ planFanOut,
456
+ alignedFieldList,
457
+ } from "./fan-out-rows.js"
458
+ export type { FanOutCandidate, ListFanOut, FanOutPlan } from "./fan-out-rows.js"
459
+
429
460
  export { settledWithLimit } from "./settled-with-limit.js"
430
461
 
431
462
 
@@ -611,6 +642,39 @@ export {
611
642
  } from "./node-default-mappings.js"
612
643
 
613
644
  export { NODE_MAPPABLE_FIELDS, SUNO_FIELD_HANDLE_FIELDS, fieldKeyFromHandle } from "./node-mappable-fields.js"
645
+ export {
646
+ SCHEDULE_TRIGGER_NODE_TYPE,
647
+ WEBHOOK_TRIGGER_NODE_TYPE,
648
+ TELEGRAM_TRIGGER_NODE_TYPE,
649
+ PROJECTED_TRIGGER_NODE_TYPES,
650
+ isProjectedTriggerNodeType,
651
+ } from "./trigger-node-types.js"
652
+
653
+ export { buildFeedMaps, nodeFeedsAnything, type FeedNode, type FeedEdge, type FeedMaps } from "./trigger-feeds.js"
654
+
655
+ export {
656
+ SCHEDULE_RULE_KINDS,
657
+ SCHEDULE_EVERY_LIMITS,
658
+ isCronExpression,
659
+ isValidTimezone,
660
+ normalizeScheduleRule,
661
+ normalizeScheduleRules,
662
+ legacyScheduleToRules,
663
+ localTimeIn,
664
+ localMinuteKey,
665
+ timezoneOffsetMinutes,
666
+ matchesCronField,
667
+ matchesCron,
668
+ ruleMatches,
669
+ scheduleMatchesAt,
670
+ scheduleOccurrences,
671
+ nextScheduleRuns,
672
+ previewHorizonMs,
673
+ type ScheduleRuleKind,
674
+ type ScheduleRule,
675
+ type ScheduleSpec,
676
+ type LocalTime,
677
+ } from "./schedule-rules.js"
614
678
 
615
679
 
616
680
  export {
@@ -622,6 +686,101 @@ export {
622
686
  type ScraperActorId,
623
687
  } from "./scraper-actors.js"
624
688
 
689
+ export {
690
+ META_ADS_SCRAPE_NODE_TYPE,
691
+ META_ADS_SCRAPE_MODES,
692
+ META_ADS_SCRAPE_PERIODS,
693
+ META_ADS_SCRAPE_STATUSES,
694
+ META_ADS_PLATFORMS,
695
+ isMetaAdsPlatform,
696
+ type MetaAdsPlatform,
697
+ META_ADS_FORMATS,
698
+ isMetaAdsFormat,
699
+ classifyCreativeFormat,
700
+ clampMetaAdsFeaturedIndex,
701
+ featuredMetaAdOutputs,
702
+ type FeaturedMetaAdOutputs,
703
+ type MetaAdsFormat,
704
+ type MetaAdsCreativeFormat,
705
+ META_ADS_SCRAPE_COUNT_OPTIONS,
706
+ META_ADS_SCRAPE_DEFAULT_COUNT,
707
+ META_ADS_SCRAPE_MAX_COUNT,
708
+ META_ADS_SCRAPE_MAX_SOURCES,
709
+ META_ADS_SCRAPE_MAX_QUERY_LENGTH,
710
+ META_ADS_SCRAPE_DEFAULT_COUNTRY,
711
+ META_ADS_SCRAPE_TIERS,
712
+ META_ADS_SCRAPE_CREDIT_COSTS,
713
+ META_ADS_SCRAPE_FALLBACK_CREDIT_ID,
714
+ splitMetaAdsPageUrls,
715
+ isMetaAdsScrapeMode,
716
+ isMetaAdsScrapeCount,
717
+ metaAdsScrapeTier,
718
+ buildMetaAdsScrapeCreditId,
719
+ resolveMetaAdsScrapeCreditId,
720
+ META_ADS_NODE_MODES,
721
+ META_ADS_ADVERTISER_MAX_RESULTS,
722
+ META_ADS_ANALYSIS_TIERS,
723
+ META_ADS_ANALYSIS_CREDITS_PER_AD,
724
+ META_ADS_ANALYSIS_CREDIT_ID,
725
+ META_ADS_ANALYSIS_FOCUS_MAX,
726
+ metaAdsAnalysisCreditId,
727
+ metaAdsAnalysisTier,
728
+ metaAdsAnalysisTierFrom,
729
+ adCreativeAnalysisFrom,
730
+ metaAdsScrapeCreditIdFromNode,
731
+ type MetaAdsAnalysisTier,
732
+ type AdCreativeAnalysis,
733
+ type MetaAdsNodeQuoteFields,
734
+ metaAdsNodeMode,
735
+ isFacebookPageUrl,
736
+ isMetaCdnImageUrl,
737
+ splitMetaAdsAdvertiserNames,
738
+ metaAdsAdvertisersFrom,
739
+ metaAdsScrapeSources,
740
+ metaAdsScrapeWireSources,
741
+ type MetaAdsNodeMode,
742
+ type MetaAdsAdvertiser,
743
+ type MetaAdsNodeSourceFields,
744
+ type MetaAdsWireSources,
745
+ type MetaAdsScrapeMode,
746
+ type MetaAdsScrapePeriod,
747
+ type MetaAdsScrapeStatus,
748
+ type MetaAdsScrapeTier,
749
+ } from "./meta-ads-scrape.js"
750
+
751
+ export {
752
+ INSTAGRAM_SCRAPE_NODE_TYPE,
753
+ INSTAGRAM_SCRAPE_MODES,
754
+ INSTAGRAM_SCRAPE_PERIODS,
755
+ INSTAGRAM_SCRAPE_DEFAULT_COUNT,
756
+ INSTAGRAM_SCRAPE_MAX_COUNT,
757
+ INSTAGRAM_SCRAPE_MAX_SOURCES,
758
+ INSTAGRAM_SCRAPE_MAX_TARGET_LENGTH,
759
+ INSTAGRAM_SCRAPE_TIERS,
760
+ INSTAGRAM_SCRAPE_CREDIT_COSTS,
761
+ INSTAGRAM_SCRAPE_FALLBACK_CREDIT_ID,
762
+ INSTAGRAM_ANALYSIS_CREDIT_ID,
763
+ isInstagramScrapeMode,
764
+ instagramScrapeMode,
765
+ isInstagramScrapeCount,
766
+ instagramScrapeTier,
767
+ instagramAnalysisTierFrom,
768
+ instagramAnalysisCreditId,
769
+ buildInstagramScrapeCreditId,
770
+ resolveInstagramScrapeCreditId,
771
+ instagramScrapeSources,
772
+ instagramScrapeCreditIdFromNode,
773
+ splitInstagramTargets,
774
+ clampInstagramFeaturedIndex,
775
+ featuredInstagramOutputs,
776
+ type InstagramScrapeMode,
777
+ type InstagramScrapePeriod,
778
+ type InstagramScrapeTier,
779
+ type InstagramFormat,
780
+ type FeaturedInstagramOutputs,
781
+ type InstagramNodeQuoteFields,
782
+ } from "./instagram-scrape.js"
783
+
625
784
  export { VARIABLES_HANDLE_ID, buildConditionVariables } from "./condition-variables.js"
626
785
 
627
786
  export { extractAllGeneratedResults, extractGeneratedJsonAsList, spreadJsonArrayIfSingleton } from "./generated-results.js"
@@ -782,8 +941,27 @@ export {
782
941
  KINETIC_CAPTION_STYLES,
783
942
  ALL_CAPTION_STYLES,
784
943
  isKineticCaptionStyle,
944
+ CAPTION_LOOK_IDS,
945
+ CAPTION_LOOKS,
946
+ DEFAULT_CAPTION_LOOK,
947
+ DEFAULT_SUBTITLE_LOOK,
948
+ KINETIC_ONLY_CAPTION_LEVER_KEYS,
949
+ captionRoutesToRemotion,
950
+ CAPTION_MAX_WORDS_PER_LINE_MIN,
951
+ CAPTION_MAX_WORDS_PER_LINE_MAX,
952
+ CAPTION_LEVER_BOUNDS,
953
+ normalizeCaptionNumericLevers,
954
+ autoStrokeWidth,
955
+ resolveCaptionLook,
956
+ resolveCaptionLevers,
785
957
  } from "./caption-styles.js"
786
- export type { StaticCaptionStyle, KineticCaptionStyle, CaptionStyle } from "./caption-styles.js"
958
+ export type { StaticCaptionStyle, KineticCaptionStyle, CaptionStyle, CaptionLookId, CaptionLookLevers, KineticOnlyCaptionLeverKey } from "./caption-styles.js"
959
+
960
+ export {
961
+ transcribeWordTimestampsRefusal,
962
+ findWordlessTranscriptFeeds,
963
+ } from "./transcribe-preflight.js"
964
+ export type { PreflightGraphNode, PreflightGraphEdge, WordlessTranscriptFeed } from "./transcribe-preflight.js"
787
965
 
788
966
  // Sound parameter-node dimensions (music + voice pickers + backend hints)
789
967
 
@@ -822,7 +1000,7 @@ export type {
822
1000
  WorkflowImportSkippedAsset,
823
1001
  WorkflowAssetKind,
824
1002
  } from "./workflow-export.js"
825
- export { stripExportContent } from "./workflow-export.js"
1003
+ export { stripExportContent, stripUnownedRefs } from "./workflow-export.js"
826
1004
 
827
1005
  export { validateSubWorkflowRoutes } from "./sub-workflow-validation.js"
828
1006
 
@@ -972,6 +1150,22 @@ export {
972
1150
 
973
1151
  export { SUNO_TRACK_SOURCE_TYPES } from "./suno-track-sources.js"
974
1152
 
1153
+ // --- Video URL node + social-video import (host allowlist, node output rule) ---
1154
+ export {
1155
+ SOCIAL_VIDEO_HOSTS,
1156
+ YOUTUBE_HOSTS,
1157
+ INSTAGRAM_HOSTS,
1158
+ VIDEO_LINK_TOLERANT_CONSUMER_TYPES,
1159
+ hostnameMatchesAllowlist,
1160
+ hasUrlParserHazard,
1161
+ isSocialVideoUrl,
1162
+ detectVideoLinkPlatform,
1163
+ videoLinkDownloadedFile,
1164
+ resolveVideoLinkOutput,
1165
+ videoLinkNeedsDownload,
1166
+ } from "./video-link.js"
1167
+ export type { VideoLinkPlatform, VideoLinkNodeFields } from "./video-link.js"
1168
+
975
1169
  export {
976
1170
  VOICE_CHANGER_MODELS,
977
1171
  VOICE_CHANGER_MODEL_IDS,
@@ -1114,3 +1308,12 @@ export * from "./scene3d-input-assets.js"
1114
1308
  export * from "./video-output-canvas.js"
1115
1309
  export * from "./video-frame-fit.js"
1116
1310
  export type { CharacterMotionMetadata } from "./character-motion-metadata.js"
1311
+
1312
+ // --- EDL: the edit decision list contract (podcast editing primitives).
1313
+ // Types + validators + pure remap/duration functions; structural only. ---
1314
+ export * from "./edl.js"
1315
+ export * from "./speaker-layouts.js"
1316
+ export * from "./edl-multicam.js"
1317
+ export * from "./edit-plan-contract.js"
1318
+
1319
+ export { VIDEO_DURATION_AUTO, isAutoVideoDuration } from "./video-duration-auto.js"