vidpipe 1.3.18 → 1.3.20

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/index.js CHANGED
@@ -38,6 +38,116 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  mod
39
39
  ));
40
40
 
41
+ // src/L0-pure/pipelineSpec/types.ts
42
+ var init_types = __esm({
43
+ "src/L0-pure/pipelineSpec/types.ts"() {
44
+ "use strict";
45
+ }
46
+ });
47
+
48
+ // src/L0-pure/pipelineSpec/presets.ts
49
+ var PRESET_FULL;
50
+ var init_presets = __esm({
51
+ "src/L0-pure/pipelineSpec/presets.ts"() {
52
+ "use strict";
53
+ PRESET_FULL = {
54
+ name: "full",
55
+ description: "Full pipeline with hook-first shorts, per-platform optimization",
56
+ processing: {
57
+ silenceRemoval: true,
58
+ visualEnhancement: true,
59
+ captions: true,
60
+ introOutro: true
61
+ },
62
+ clips: {
63
+ shorts: {
64
+ enabled: true,
65
+ strategy: "hook-first",
66
+ duration: { min: 15, max: 60 },
67
+ minViralScore: 8,
68
+ maxClips: 5
69
+ },
70
+ medium: {
71
+ enabled: true,
72
+ strategy: "chronological",
73
+ duration: { min: 60, max: 180 },
74
+ minViralScore: 10,
75
+ maxClips: 5
76
+ }
77
+ },
78
+ content: {
79
+ chapters: true,
80
+ summary: true,
81
+ blog: true
82
+ },
83
+ distribution: {
84
+ enabled: true,
85
+ publish: true,
86
+ platforms: {
87
+ targets: ["youtube", "linkedin", "instagram", "x", "tiktok"],
88
+ toneStrategy: "per-platform",
89
+ variants: true
90
+ }
91
+ }
92
+ };
93
+ }
94
+ });
95
+
96
+ // src/L0-pure/pipelineSpec/validation.ts
97
+ var init_validation = __esm({
98
+ "src/L0-pure/pipelineSpec/validation.ts"() {
99
+ "use strict";
100
+ }
101
+ });
102
+
103
+ // src/L0-pure/pipelineSpec/merger.ts
104
+ function applySkipFlags(spec, flags) {
105
+ return {
106
+ ...spec,
107
+ processing: {
108
+ silenceRemoval: spec.processing.silenceRemoval && !flags.SKIP_SILENCE_REMOVAL,
109
+ visualEnhancement: spec.processing.visualEnhancement && !flags.SKIP_VISUAL_ENHANCEMENT,
110
+ captions: spec.processing.captions && !flags.SKIP_CAPTIONS,
111
+ introOutro: spec.processing.introOutro && !flags.SKIP_INTRO_OUTRO
112
+ },
113
+ clips: {
114
+ shorts: {
115
+ ...spec.clips.shorts,
116
+ enabled: spec.clips.shorts.enabled && !flags.SKIP_SHORTS
117
+ },
118
+ medium: {
119
+ ...spec.clips.medium,
120
+ enabled: spec.clips.medium.enabled && !flags.SKIP_MEDIUM_CLIPS
121
+ }
122
+ },
123
+ distribution: {
124
+ ...spec.distribution,
125
+ enabled: spec.distribution.enabled && !flags.SKIP_SOCIAL,
126
+ publish: spec.distribution.publish && !flags.SKIP_SOCIAL_PUBLISH
127
+ }
128
+ };
129
+ }
130
+ function resolveFromFlags(flags) {
131
+ return applySkipFlags(PRESET_FULL, flags);
132
+ }
133
+ var init_merger = __esm({
134
+ "src/L0-pure/pipelineSpec/merger.ts"() {
135
+ "use strict";
136
+ init_presets();
137
+ }
138
+ });
139
+
140
+ // src/L0-pure/pipelineSpec/index.ts
141
+ var init_pipelineSpec = __esm({
142
+ "src/L0-pure/pipelineSpec/index.ts"() {
143
+ "use strict";
144
+ init_types();
145
+ init_presets();
146
+ init_validation();
147
+ init_merger();
148
+ }
149
+ });
150
+
41
151
  // src/L0-pure/types/index.ts
42
152
  function getStageInfo(stage) {
43
153
  const info = PIPELINE_STAGES.find((s) => s.stage === stage);
@@ -66,9 +176,10 @@ function normalizePlatformString(raw) {
66
176
  return lower;
67
177
  }
68
178
  var Platform, PipelineStage, PIPELINE_STAGES, TOTAL_STAGES, PLATFORM_CHAR_LIMITS, SUPPORTED_VIDEO_EXTENSIONS;
69
- var init_types = __esm({
179
+ var init_types2 = __esm({
70
180
  "src/L0-pure/types/index.ts"() {
71
181
  "use strict";
182
+ init_pipelineSpec();
72
183
  Platform = /* @__PURE__ */ ((Platform2) => {
73
184
  Platform2["TikTok"] = "tiktok";
74
185
  Platform2["YouTube"] = "youtube";
@@ -1632,7 +1743,7 @@ var STATUS_LABEL_PREFIX, PLATFORM_LABEL_PREFIX, PRIORITY_LABEL_PREFIX, COMMENT_M
1632
1743
  var init_ideaService = __esm({
1633
1744
  "src/L3-services/ideaService/ideaService.ts"() {
1634
1745
  "use strict";
1635
- init_types();
1746
+ init_types2();
1636
1747
  init_githubClient();
1637
1748
  init_environment();
1638
1749
  init_configLogger();
@@ -4987,23 +5098,63 @@ function createSessionRpc(connection, sessionId) {
4987
5098
  readFile: async (params) => connection.sendRequest("session.workspace.readFile", { sessionId, ...params }),
4988
5099
  createFile: async (params) => connection.sendRequest("session.workspace.createFile", { sessionId, ...params })
4989
5100
  },
5101
+ /** @experimental */
4990
5102
  fleet: {
4991
5103
  start: async (params) => connection.sendRequest("session.fleet.start", { sessionId, ...params })
4992
5104
  },
5105
+ /** @experimental */
4993
5106
  agent: {
4994
5107
  list: async () => connection.sendRequest("session.agent.list", { sessionId }),
4995
5108
  getCurrent: async () => connection.sendRequest("session.agent.getCurrent", { sessionId }),
4996
5109
  select: async (params) => connection.sendRequest("session.agent.select", { sessionId, ...params }),
4997
- deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId })
5110
+ deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId }),
5111
+ reload: async () => connection.sendRequest("session.agent.reload", { sessionId })
5112
+ },
5113
+ /** @experimental */
5114
+ skills: {
5115
+ list: async () => connection.sendRequest("session.skills.list", { sessionId }),
5116
+ enable: async (params) => connection.sendRequest("session.skills.enable", { sessionId, ...params }),
5117
+ disable: async (params) => connection.sendRequest("session.skills.disable", { sessionId, ...params }),
5118
+ reload: async () => connection.sendRequest("session.skills.reload", { sessionId })
5119
+ },
5120
+ /** @experimental */
5121
+ mcp: {
5122
+ list: async () => connection.sendRequest("session.mcp.list", { sessionId }),
5123
+ enable: async (params) => connection.sendRequest("session.mcp.enable", { sessionId, ...params }),
5124
+ disable: async (params) => connection.sendRequest("session.mcp.disable", { sessionId, ...params }),
5125
+ reload: async () => connection.sendRequest("session.mcp.reload", { sessionId })
5126
+ },
5127
+ /** @experimental */
5128
+ plugins: {
5129
+ list: async () => connection.sendRequest("session.plugins.list", { sessionId })
4998
5130
  },
5131
+ /** @experimental */
5132
+ extensions: {
5133
+ list: async () => connection.sendRequest("session.extensions.list", { sessionId }),
5134
+ enable: async (params) => connection.sendRequest("session.extensions.enable", { sessionId, ...params }),
5135
+ disable: async (params) => connection.sendRequest("session.extensions.disable", { sessionId, ...params }),
5136
+ reload: async () => connection.sendRequest("session.extensions.reload", { sessionId })
5137
+ },
5138
+ /** @experimental */
4999
5139
  compaction: {
5000
5140
  compact: async () => connection.sendRequest("session.compaction.compact", { sessionId })
5001
5141
  },
5002
5142
  tools: {
5003
5143
  handlePendingToolCall: async (params) => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params })
5004
5144
  },
5145
+ commands: {
5146
+ handlePendingCommand: async (params) => connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params })
5147
+ },
5148
+ ui: {
5149
+ elicitation: async (params) => connection.sendRequest("session.ui.elicitation", { sessionId, ...params })
5150
+ },
5005
5151
  permissions: {
5006
5152
  handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
5153
+ },
5154
+ log: async (params) => connection.sendRequest("session.log", { sessionId, ...params }),
5155
+ shell: {
5156
+ exec: async (params) => connection.sendRequest("session.shell.exec", { sessionId, ...params }),
5157
+ kill: async (params) => connection.sendRequest("session.shell.kill", { sessionId, ...params })
5007
5158
  }
5008
5159
  };
5009
5160
  }
@@ -5025,13 +5176,30 @@ var init_sdkProtocolVersion = __esm({
5025
5176
  }
5026
5177
  });
5027
5178
 
5179
+ // node_modules/@github/copilot-sdk/dist/telemetry.js
5180
+ async function getTraceContext(provider) {
5181
+ if (!provider) return {};
5182
+ try {
5183
+ return await provider() ?? {};
5184
+ } catch {
5185
+ return {};
5186
+ }
5187
+ }
5188
+ var init_telemetry = __esm({
5189
+ "node_modules/@github/copilot-sdk/dist/telemetry.js"() {
5190
+ "use strict";
5191
+ }
5192
+ });
5193
+
5028
5194
  // node_modules/@github/copilot-sdk/dist/session.js
5029
- var import_node, CopilotSession;
5195
+ var import_node, NO_RESULT_PERMISSION_V2_ERROR, CopilotSession;
5030
5196
  var init_session = __esm({
5031
5197
  "node_modules/@github/copilot-sdk/dist/session.js"() {
5032
5198
  "use strict";
5033
5199
  import_node = __toESM(require_node(), 1);
5034
5200
  init_rpc();
5201
+ init_telemetry();
5202
+ NO_RESULT_PERMISSION_V2_ERROR = "Permission handlers cannot return 'no-result' when connected to a protocol v2 server.";
5035
5203
  CopilotSession = class {
5036
5204
  /**
5037
5205
  * Creates a new CopilotSession instance.
@@ -5039,12 +5207,14 @@ var init_session = __esm({
5039
5207
  * @param sessionId - The unique identifier for this session
5040
5208
  * @param connection - The JSON-RPC message connection to the Copilot CLI
5041
5209
  * @param workspacePath - Path to the session workspace directory (when infinite sessions enabled)
5210
+ * @param traceContextProvider - Optional callback to get W3C Trace Context for outbound RPCs
5042
5211
  * @internal This constructor is internal. Use {@link CopilotClient.createSession} to create sessions.
5043
5212
  */
5044
- constructor(sessionId, connection, _workspacePath) {
5213
+ constructor(sessionId, connection, _workspacePath, traceContextProvider) {
5045
5214
  this.sessionId = sessionId;
5046
5215
  this.connection = connection;
5047
5216
  this._workspacePath = _workspacePath;
5217
+ this.traceContextProvider = traceContextProvider;
5048
5218
  }
5049
5219
  eventHandlers = /* @__PURE__ */ new Set();
5050
5220
  typedEventHandlers = /* @__PURE__ */ new Map();
@@ -5052,7 +5222,9 @@ var init_session = __esm({
5052
5222
  permissionHandler;
5053
5223
  userInputHandler;
5054
5224
  hooks;
5225
+ transformCallbacks;
5055
5226
  _rpc = null;
5227
+ traceContextProvider;
5056
5228
  /**
5057
5229
  * Typed session-scoped RPC methods.
5058
5230
  */
@@ -5090,6 +5262,7 @@ var init_session = __esm({
5090
5262
  */
5091
5263
  async send(options) {
5092
5264
  const response = await this.connection.sendRequest("session.send", {
5265
+ ...await getTraceContext(this.traceContextProvider),
5093
5266
  sessionId: this.sessionId,
5094
5267
  prompt: options.prompt,
5095
5268
  attachments: options.attachments,
@@ -5219,9 +5392,19 @@ var init_session = __esm({
5219
5392
  const { requestId, toolName } = event.data;
5220
5393
  const args = event.data.arguments;
5221
5394
  const toolCallId = event.data.toolCallId;
5395
+ const traceparent = event.data.traceparent;
5396
+ const tracestate = event.data.tracestate;
5222
5397
  const handler = this.toolHandlers.get(toolName);
5223
5398
  if (handler) {
5224
- void this._executeToolAndRespond(requestId, toolName, toolCallId, args, handler);
5399
+ void this._executeToolAndRespond(
5400
+ requestId,
5401
+ toolName,
5402
+ toolCallId,
5403
+ args,
5404
+ handler,
5405
+ traceparent,
5406
+ tracestate
5407
+ );
5225
5408
  }
5226
5409
  } else if (event.type === "permission.requested") {
5227
5410
  const { requestId, permissionRequest } = event.data;
@@ -5234,13 +5417,15 @@ var init_session = __esm({
5234
5417
  * Executes a tool handler and sends the result back via RPC.
5235
5418
  * @internal
5236
5419
  */
5237
- async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler) {
5420
+ async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler, traceparent, tracestate) {
5238
5421
  try {
5239
5422
  const rawResult = await handler(args, {
5240
5423
  sessionId: this.sessionId,
5241
5424
  toolCallId,
5242
5425
  toolName,
5243
- arguments: args
5426
+ arguments: args,
5427
+ traceparent,
5428
+ tracestate
5244
5429
  });
5245
5430
  let result;
5246
5431
  if (rawResult == null) {
@@ -5271,6 +5456,9 @@ var init_session = __esm({
5271
5456
  const result = await this.permissionHandler(permissionRequest, {
5272
5457
  sessionId: this.sessionId
5273
5458
  });
5459
+ if (result.kind === "no-result") {
5460
+ return;
5461
+ }
5274
5462
  await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
5275
5463
  } catch (_error) {
5276
5464
  try {
@@ -5351,6 +5539,40 @@ var init_session = __esm({
5351
5539
  registerHooks(hooks) {
5352
5540
  this.hooks = hooks;
5353
5541
  }
5542
+ /**
5543
+ * Registers transform callbacks for system message sections.
5544
+ *
5545
+ * @param callbacks - Map of section ID to transform callback, or undefined to clear
5546
+ * @internal This method is typically called internally when creating a session.
5547
+ */
5548
+ registerTransformCallbacks(callbacks) {
5549
+ this.transformCallbacks = callbacks;
5550
+ }
5551
+ /**
5552
+ * Handles a systemMessage.transform request from the runtime.
5553
+ * Dispatches each section to its registered transform callback.
5554
+ *
5555
+ * @param sections - Map of section IDs to their current rendered content
5556
+ * @returns A promise that resolves with the transformed sections
5557
+ * @internal This method is for internal use by the SDK.
5558
+ */
5559
+ async _handleSystemMessageTransform(sections) {
5560
+ const result = {};
5561
+ for (const [sectionId, { content }] of Object.entries(sections)) {
5562
+ const callback = this.transformCallbacks?.get(sectionId);
5563
+ if (callback) {
5564
+ try {
5565
+ const transformed = await callback(content);
5566
+ result[sectionId] = { content: transformed };
5567
+ } catch (_error) {
5568
+ result[sectionId] = { content };
5569
+ }
5570
+ } else {
5571
+ result[sectionId] = { content };
5572
+ }
5573
+ }
5574
+ return { sections: result };
5575
+ }
5354
5576
  /**
5355
5577
  * Handles a permission request in the v2 protocol format (synchronous RPC).
5356
5578
  * Used as a back-compat adapter when connected to a v2 server.
@@ -5367,8 +5589,14 @@ var init_session = __esm({
5367
5589
  const result = await this.permissionHandler(request, {
5368
5590
  sessionId: this.sessionId
5369
5591
  });
5592
+ if (result.kind === "no-result") {
5593
+ throw new Error(NO_RESULT_PERMISSION_V2_ERROR);
5594
+ }
5370
5595
  return result;
5371
- } catch (_error) {
5596
+ } catch (error) {
5597
+ if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
5598
+ throw error;
5599
+ }
5372
5600
  return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
5373
5601
  }
5374
5602
  }
@@ -5524,14 +5752,35 @@ var init_session = __esm({
5524
5752
  * The new model takes effect for the next message. Conversation history is preserved.
5525
5753
  *
5526
5754
  * @param model - Model ID to switch to
5755
+ * @param options - Optional settings for the new model
5527
5756
  *
5528
5757
  * @example
5529
5758
  * ```typescript
5530
5759
  * await session.setModel("gpt-4.1");
5760
+ * await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" });
5761
+ * ```
5762
+ */
5763
+ async setModel(model, options) {
5764
+ await this.rpc.model.switchTo({ modelId: model, ...options });
5765
+ }
5766
+ /**
5767
+ * Log a message to the session timeline.
5768
+ * The message appears in the session event stream and is visible to SDK consumers
5769
+ * and (for non-ephemeral messages) persisted to the session event log on disk.
5770
+ *
5771
+ * @param message - Human-readable message text
5772
+ * @param options - Optional log level and ephemeral flag
5773
+ *
5774
+ * @example
5775
+ * ```typescript
5776
+ * await session.log("Processing started");
5777
+ * await session.log("Disk usage high", { level: "warning" });
5778
+ * await session.log("Connection failed", { level: "error" });
5779
+ * await session.log("Debug info", { ephemeral: true });
5531
5780
  * ```
5532
5781
  */
5533
- async setModel(model) {
5534
- await this.rpc.model.switchTo({ modelId: model });
5782
+ async log(message, options) {
5783
+ await this.rpc.log({ message, ...options });
5535
5784
  }
5536
5785
  };
5537
5786
  }
@@ -5539,7 +5788,9 @@ var init_session = __esm({
5539
5788
 
5540
5789
  // node_modules/@github/copilot-sdk/dist/client.js
5541
5790
  import { spawn } from "child_process";
5791
+ import { randomUUID } from "crypto";
5542
5792
  import { existsSync as existsSync4 } from "fs";
5793
+ import { createRequire as createRequire2 } from "module";
5543
5794
  import { Socket } from "net";
5544
5795
  import { dirname as dirname3, join as join5 } from "path";
5545
5796
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -5553,6 +5804,30 @@ function toJsonSchema(parameters) {
5553
5804
  }
5554
5805
  return parameters;
5555
5806
  }
5807
+ function extractTransformCallbacks(systemMessage) {
5808
+ if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
5809
+ return { wirePayload: systemMessage, transformCallbacks: void 0 };
5810
+ }
5811
+ const transformCallbacks = /* @__PURE__ */ new Map();
5812
+ const wireSections = {};
5813
+ for (const [sectionId, override] of Object.entries(systemMessage.sections)) {
5814
+ if (!override) continue;
5815
+ if (typeof override.action === "function") {
5816
+ transformCallbacks.set(sectionId, override.action);
5817
+ wireSections[sectionId] = { action: "transform" };
5818
+ } else {
5819
+ wireSections[sectionId] = { action: override.action, content: override.content };
5820
+ }
5821
+ }
5822
+ if (transformCallbacks.size === 0) {
5823
+ return { wirePayload: systemMessage, transformCallbacks: void 0 };
5824
+ }
5825
+ const wirePayload = {
5826
+ ...systemMessage,
5827
+ sections: wireSections
5828
+ };
5829
+ return { wirePayload, transformCallbacks };
5830
+ }
5556
5831
  function getNodeExecPath() {
5557
5832
  if (process.versions.bun) {
5558
5833
  return "node";
@@ -5560,9 +5835,22 @@ function getNodeExecPath() {
5560
5835
  return process.execPath;
5561
5836
  }
5562
5837
  function getBundledCliPath() {
5563
- const sdkUrl = import.meta.resolve("@github/copilot/sdk");
5564
- const sdkPath = fileURLToPath3(sdkUrl);
5565
- return join5(dirname3(dirname3(sdkPath)), "index.js");
5838
+ if (typeof import.meta.resolve === "function") {
5839
+ const sdkUrl = import.meta.resolve("@github/copilot/sdk");
5840
+ const sdkPath = fileURLToPath3(sdkUrl);
5841
+ return join5(dirname3(dirname3(sdkPath)), "index.js");
5842
+ }
5843
+ const req = createRequire2(__filename);
5844
+ const searchPaths = req.resolve.paths("@github/copilot") ?? [];
5845
+ for (const base of searchPaths) {
5846
+ const candidate = join5(base, "@github", "copilot", "index.js");
5847
+ if (existsSync4(candidate)) {
5848
+ return candidate;
5849
+ }
5850
+ }
5851
+ throw new Error(
5852
+ `Could not find @github/copilot package. Searched ${searchPaths.length} paths. Ensure it is installed, or pass cliPath/cliUrl to CopilotClient.`
5853
+ );
5566
5854
  }
5567
5855
  var import_node2, MIN_PROTOCOL_VERSION, CopilotClient;
5568
5856
  var init_client = __esm({
@@ -5572,6 +5860,7 @@ var init_client = __esm({
5572
5860
  init_rpc();
5573
5861
  init_sdkProtocolVersion();
5574
5862
  init_session();
5863
+ init_telemetry();
5575
5864
  MIN_PROTOCOL_VERSION = 2;
5576
5865
  CopilotClient = class {
5577
5866
  cliProcess = null;
@@ -5586,6 +5875,8 @@ var init_client = __esm({
5586
5875
  options;
5587
5876
  isExternalServer = false;
5588
5877
  forceStopping = false;
5878
+ onListModels;
5879
+ onGetTraceContext;
5589
5880
  modelsCache = null;
5590
5881
  modelsCacheLock = Promise.resolve();
5591
5882
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
@@ -5651,8 +5942,10 @@ var init_client = __esm({
5651
5942
  if (options.isChildProcess) {
5652
5943
  this.isExternalServer = true;
5653
5944
  }
5945
+ this.onListModels = options.onListModels;
5946
+ this.onGetTraceContext = options.onGetTraceContext;
5654
5947
  this.options = {
5655
- cliPath: options.cliPath || getBundledCliPath(),
5948
+ cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
5656
5949
  cliArgs: options.cliArgs ?? [],
5657
5950
  cwd: options.cwd ?? process.cwd(),
5658
5951
  port: options.port || 0,
@@ -5662,11 +5955,12 @@ var init_client = __esm({
5662
5955
  cliUrl: options.cliUrl,
5663
5956
  logLevel: options.logLevel || "debug",
5664
5957
  autoStart: options.autoStart ?? true,
5665
- autoRestart: options.autoRestart ?? true,
5958
+ autoRestart: false,
5666
5959
  env: options.env ?? process.env,
5667
5960
  githubToken: options.githubToken,
5668
5961
  // Default useLoggedInUser to false when githubToken is provided, otherwise true
5669
- useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true)
5962
+ useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
5963
+ telemetry: options.telemetry
5670
5964
  };
5671
5965
  }
5672
5966
  /**
@@ -5918,36 +6212,13 @@ var init_client = __esm({
5918
6212
  throw new Error("Client not connected. Call start() first.");
5919
6213
  }
5920
6214
  }
5921
- const response = await this.connection.sendRequest("session.create", {
5922
- model: config2.model,
5923
- sessionId: config2.sessionId,
5924
- clientName: config2.clientName,
5925
- reasoningEffort: config2.reasoningEffort,
5926
- tools: config2.tools?.map((tool) => ({
5927
- name: tool.name,
5928
- description: tool.description,
5929
- parameters: toJsonSchema(tool.parameters),
5930
- overridesBuiltInTool: tool.overridesBuiltInTool
5931
- })),
5932
- systemMessage: config2.systemMessage,
5933
- availableTools: config2.availableTools,
5934
- excludedTools: config2.excludedTools,
5935
- provider: config2.provider,
5936
- requestPermission: true,
5937
- requestUserInput: !!config2.onUserInputRequest,
5938
- hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
5939
- workingDirectory: config2.workingDirectory,
5940
- streaming: config2.streaming,
5941
- mcpServers: config2.mcpServers,
5942
- envValueMode: "direct",
5943
- customAgents: config2.customAgents,
5944
- configDir: config2.configDir,
5945
- skillDirectories: config2.skillDirectories,
5946
- disabledSkills: config2.disabledSkills,
5947
- infiniteSessions: config2.infiniteSessions
5948
- });
5949
- const { sessionId, workspacePath } = response;
5950
- const session = new CopilotSession(sessionId, this.connection, workspacePath);
6215
+ const sessionId = config2.sessionId ?? randomUUID();
6216
+ const session = new CopilotSession(
6217
+ sessionId,
6218
+ this.connection,
6219
+ void 0,
6220
+ this.onGetTraceContext
6221
+ );
5951
6222
  session.registerTools(config2.tools);
5952
6223
  session.registerPermissionHandler(config2.onPermissionRequest);
5953
6224
  if (config2.onUserInputRequest) {
@@ -5956,7 +6227,54 @@ var init_client = __esm({
5956
6227
  if (config2.hooks) {
5957
6228
  session.registerHooks(config2.hooks);
5958
6229
  }
6230
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
6231
+ config2.systemMessage
6232
+ );
6233
+ if (transformCallbacks) {
6234
+ session.registerTransformCallbacks(transformCallbacks);
6235
+ }
6236
+ if (config2.onEvent) {
6237
+ session.on(config2.onEvent);
6238
+ }
5959
6239
  this.sessions.set(sessionId, session);
6240
+ try {
6241
+ const response = await this.connection.sendRequest("session.create", {
6242
+ ...await getTraceContext(this.onGetTraceContext),
6243
+ model: config2.model,
6244
+ sessionId,
6245
+ clientName: config2.clientName,
6246
+ reasoningEffort: config2.reasoningEffort,
6247
+ tools: config2.tools?.map((tool) => ({
6248
+ name: tool.name,
6249
+ description: tool.description,
6250
+ parameters: toJsonSchema(tool.parameters),
6251
+ overridesBuiltInTool: tool.overridesBuiltInTool,
6252
+ skipPermission: tool.skipPermission
6253
+ })),
6254
+ systemMessage: wireSystemMessage,
6255
+ availableTools: config2.availableTools,
6256
+ excludedTools: config2.excludedTools,
6257
+ provider: config2.provider,
6258
+ requestPermission: true,
6259
+ requestUserInput: !!config2.onUserInputRequest,
6260
+ hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
6261
+ workingDirectory: config2.workingDirectory,
6262
+ streaming: config2.streaming,
6263
+ mcpServers: config2.mcpServers,
6264
+ envValueMode: "direct",
6265
+ customAgents: config2.customAgents,
6266
+ agent: config2.agent,
6267
+ configDir: config2.configDir,
6268
+ skillDirectories: config2.skillDirectories,
6269
+ disabledSkills: config2.disabledSkills,
6270
+ infiniteSessions: config2.infiniteSessions
6271
+ });
6272
+ const { workspacePath } = response;
6273
+ session["_workspacePath"] = workspacePath;
6274
+ } catch (e) {
6275
+ this.sessions.delete(sessionId);
6276
+ throw e;
6277
+ }
5960
6278
  return session;
5961
6279
  }
5962
6280
  /**
@@ -5996,37 +6314,12 @@ var init_client = __esm({
5996
6314
  throw new Error("Client not connected. Call start() first.");
5997
6315
  }
5998
6316
  }
5999
- const response = await this.connection.sendRequest("session.resume", {
6317
+ const session = new CopilotSession(
6000
6318
  sessionId,
6001
- clientName: config2.clientName,
6002
- model: config2.model,
6003
- reasoningEffort: config2.reasoningEffort,
6004
- systemMessage: config2.systemMessage,
6005
- availableTools: config2.availableTools,
6006
- excludedTools: config2.excludedTools,
6007
- tools: config2.tools?.map((tool) => ({
6008
- name: tool.name,
6009
- description: tool.description,
6010
- parameters: toJsonSchema(tool.parameters),
6011
- overridesBuiltInTool: tool.overridesBuiltInTool
6012
- })),
6013
- provider: config2.provider,
6014
- requestPermission: true,
6015
- requestUserInput: !!config2.onUserInputRequest,
6016
- hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
6017
- workingDirectory: config2.workingDirectory,
6018
- configDir: config2.configDir,
6019
- streaming: config2.streaming,
6020
- mcpServers: config2.mcpServers,
6021
- envValueMode: "direct",
6022
- customAgents: config2.customAgents,
6023
- skillDirectories: config2.skillDirectories,
6024
- disabledSkills: config2.disabledSkills,
6025
- infiniteSessions: config2.infiniteSessions,
6026
- disableResume: config2.disableResume
6027
- });
6028
- const { sessionId: resumedSessionId, workspacePath } = response;
6029
- const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
6319
+ this.connection,
6320
+ void 0,
6321
+ this.onGetTraceContext
6322
+ );
6030
6323
  session.registerTools(config2.tools);
6031
6324
  session.registerPermissionHandler(config2.onPermissionRequest);
6032
6325
  if (config2.onUserInputRequest) {
@@ -6035,7 +6328,55 @@ var init_client = __esm({
6035
6328
  if (config2.hooks) {
6036
6329
  session.registerHooks(config2.hooks);
6037
6330
  }
6038
- this.sessions.set(resumedSessionId, session);
6331
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
6332
+ config2.systemMessage
6333
+ );
6334
+ if (transformCallbacks) {
6335
+ session.registerTransformCallbacks(transformCallbacks);
6336
+ }
6337
+ if (config2.onEvent) {
6338
+ session.on(config2.onEvent);
6339
+ }
6340
+ this.sessions.set(sessionId, session);
6341
+ try {
6342
+ const response = await this.connection.sendRequest("session.resume", {
6343
+ ...await getTraceContext(this.onGetTraceContext),
6344
+ sessionId,
6345
+ clientName: config2.clientName,
6346
+ model: config2.model,
6347
+ reasoningEffort: config2.reasoningEffort,
6348
+ systemMessage: wireSystemMessage,
6349
+ availableTools: config2.availableTools,
6350
+ excludedTools: config2.excludedTools,
6351
+ tools: config2.tools?.map((tool) => ({
6352
+ name: tool.name,
6353
+ description: tool.description,
6354
+ parameters: toJsonSchema(tool.parameters),
6355
+ overridesBuiltInTool: tool.overridesBuiltInTool,
6356
+ skipPermission: tool.skipPermission
6357
+ })),
6358
+ provider: config2.provider,
6359
+ requestPermission: true,
6360
+ requestUserInput: !!config2.onUserInputRequest,
6361
+ hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
6362
+ workingDirectory: config2.workingDirectory,
6363
+ configDir: config2.configDir,
6364
+ streaming: config2.streaming,
6365
+ mcpServers: config2.mcpServers,
6366
+ envValueMode: "direct",
6367
+ customAgents: config2.customAgents,
6368
+ agent: config2.agent,
6369
+ skillDirectories: config2.skillDirectories,
6370
+ disabledSkills: config2.disabledSkills,
6371
+ infiniteSessions: config2.infiniteSessions,
6372
+ disableResume: config2.disableResume
6373
+ });
6374
+ const { workspacePath } = response;
6375
+ session["_workspacePath"] = workspacePath;
6376
+ } catch (e) {
6377
+ this.sessions.delete(sessionId);
6378
+ throw e;
6379
+ }
6039
6380
  return session;
6040
6381
  }
6041
6382
  /**
@@ -6096,15 +6437,15 @@ var init_client = __esm({
6096
6437
  /**
6097
6438
  * List available models with their metadata.
6098
6439
  *
6440
+ * If an `onListModels` handler was provided in the client options,
6441
+ * it is called instead of querying the CLI server.
6442
+ *
6099
6443
  * Results are cached after the first successful call to avoid rate limiting.
6100
6444
  * The cache is cleared when the client disconnects.
6101
6445
  *
6102
- * @throws Error if not authenticated
6446
+ * @throws Error if not connected (when no custom handler is set)
6103
6447
  */
6104
6448
  async listModels() {
6105
- if (!this.connection) {
6106
- throw new Error("Client not connected");
6107
- }
6108
6449
  await this.modelsCacheLock;
6109
6450
  let resolveLock;
6110
6451
  this.modelsCacheLock = new Promise((resolve3) => {
@@ -6114,10 +6455,18 @@ var init_client = __esm({
6114
6455
  if (this.modelsCache !== null) {
6115
6456
  return [...this.modelsCache];
6116
6457
  }
6117
- const result = await this.connection.sendRequest("models.list", {});
6118
- const response = result;
6119
- const models = response.models;
6120
- this.modelsCache = models;
6458
+ let models;
6459
+ if (this.onListModels) {
6460
+ models = await this.onListModels();
6461
+ } else {
6462
+ if (!this.connection) {
6463
+ throw new Error("Client not connected");
6464
+ }
6465
+ const result = await this.connection.sendRequest("models.list", {});
6466
+ const response = result;
6467
+ models = response.models;
6468
+ }
6469
+ this.modelsCache = [...models];
6121
6470
  return [...models];
6122
6471
  } finally {
6123
6472
  resolveLock();
@@ -6330,6 +6679,27 @@ var init_client = __esm({
6330
6679
  if (this.options.githubToken) {
6331
6680
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
6332
6681
  }
6682
+ if (!this.options.cliPath) {
6683
+ throw new Error(
6684
+ "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
6685
+ );
6686
+ }
6687
+ if (this.options.telemetry) {
6688
+ const t = this.options.telemetry;
6689
+ envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
6690
+ if (t.otlpEndpoint !== void 0)
6691
+ envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
6692
+ if (t.filePath !== void 0)
6693
+ envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
6694
+ if (t.exporterType !== void 0)
6695
+ envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
6696
+ if (t.sourceName !== void 0)
6697
+ envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
6698
+ if (t.captureContent !== void 0)
6699
+ envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
6700
+ t.captureContent
6701
+ );
6702
+ }
6333
6703
  if (!existsSync4(this.options.cliPath)) {
6334
6704
  throw new Error(
6335
6705
  `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
@@ -6429,8 +6799,6 @@ stderr: ${stderrOutput}`
6429
6799
  } else {
6430
6800
  reject(new Error(`CLI server exited with code ${code}`));
6431
6801
  }
6432
- } else if (this.options.autoRestart && this.state === "connected") {
6433
- void this.reconnect();
6434
6802
  }
6435
6803
  });
6436
6804
  setTimeout(() => {
@@ -6535,12 +6903,15 @@ stderr: ${stderrOutput}`
6535
6903
  "hooks.invoke",
6536
6904
  async (params) => await this.handleHooksInvoke(params)
6537
6905
  );
6906
+ this.connection.onRequest(
6907
+ "systemMessage.transform",
6908
+ async (params) => await this.handleSystemMessageTransform(params)
6909
+ );
6538
6910
  this.connection.onClose(() => {
6539
- if (this.state === "connected" && this.options.autoRestart) {
6540
- void this.reconnect();
6541
- }
6911
+ this.state = "disconnected";
6542
6912
  });
6543
6913
  this.connection.onError((_error) => {
6914
+ this.state = "disconnected";
6544
6915
  });
6545
6916
  }
6546
6917
  handleSessionEventNotification(notification) {
@@ -6599,6 +6970,16 @@ stderr: ${stderrOutput}`
6599
6970
  const output = await session._handleHooksInvoke(params.hookType, params.input);
6600
6971
  return { output };
6601
6972
  }
6973
+ async handleSystemMessageTransform(params) {
6974
+ if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
6975
+ throw new Error("Invalid systemMessage.transform payload");
6976
+ }
6977
+ const session = this.sessions.get(params.sessionId);
6978
+ if (!session) {
6979
+ throw new Error(`Session not found: ${params.sessionId}`);
6980
+ }
6981
+ return await session._handleSystemMessageTransform(params.sections);
6982
+ }
6602
6983
  // ========================================================================
6603
6984
  // Protocol v2 backward-compatibility adapters
6604
6985
  // ========================================================================
@@ -6627,11 +7008,15 @@ stderr: ${stderrOutput}`
6627
7008
  };
6628
7009
  }
6629
7010
  try {
7011
+ const traceparent = params.traceparent;
7012
+ const tracestate = params.tracestate;
6630
7013
  const invocation = {
6631
7014
  sessionId: params.sessionId,
6632
7015
  toolCallId: params.toolCallId,
6633
7016
  toolName: params.toolName,
6634
- arguments: params.arguments
7017
+ arguments: params.arguments,
7018
+ traceparent,
7019
+ tracestate
6635
7020
  };
6636
7021
  const result = await handler(params.arguments, invocation);
6637
7022
  return { result: this.normalizeToolResultV2(result) };
@@ -6661,7 +7046,10 @@ stderr: ${stderrOutput}`
6661
7046
  try {
6662
7047
  const result = await session._handlePermissionRequestV2(params.permissionRequest);
6663
7048
  return { result };
6664
- } catch (_error) {
7049
+ } catch (error) {
7050
+ if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
7051
+ throw error;
7052
+ }
6665
7053
  return {
6666
7054
  result: {
6667
7055
  kind: "denied-no-approval-rule-and-could-not-request-from-user"
@@ -6691,24 +7079,13 @@ stderr: ${stderrOutput}`
6691
7079
  isToolResultObject(value) {
6692
7080
  return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
6693
7081
  }
6694
- /**
6695
- * Attempt to reconnect to the server
6696
- */
6697
- async reconnect() {
6698
- this.state = "disconnected";
6699
- try {
6700
- await this.stop();
6701
- await this.start();
6702
- } catch (_error) {
6703
- }
6704
- }
6705
7082
  };
6706
7083
  }
6707
7084
  });
6708
7085
 
6709
7086
  // node_modules/@github/copilot-sdk/dist/types.js
6710
7087
  var approveAll;
6711
- var init_types2 = __esm({
7088
+ var init_types3 = __esm({
6712
7089
  "node_modules/@github/copilot-sdk/dist/types.js"() {
6713
7090
  "use strict";
6714
7091
  approveAll = () => ({ kind: "approved" });
@@ -6721,21 +7098,21 @@ var init_dist = __esm({
6721
7098
  "use strict";
6722
7099
  init_client();
6723
7100
  init_session();
6724
- init_types2();
7101
+ init_types3();
6725
7102
  }
6726
7103
  });
6727
7104
 
6728
7105
  // src/L1-infra/ai/copilot.ts
6729
7106
  import { existsSync as existsSync5 } from "fs";
6730
7107
  import { join as join6, dirname as dirname4 } from "path";
6731
- import { createRequire as createRequire2 } from "module";
7108
+ import { createRequire as createRequire3 } from "module";
6732
7109
  function resolveCopilotCliPath() {
6733
7110
  const platform = process.platform;
6734
7111
  const arch = process.arch;
6735
7112
  const binaryName = platform === "win32" ? "copilot.exe" : "copilot";
6736
7113
  const platformPkg = `@github/copilot-${platform}-${arch}`;
6737
7114
  try {
6738
- const require_ = createRequire2(import.meta.url);
7115
+ const require_ = createRequire3(import.meta.url);
6739
7116
  const searchPaths = require_.resolve.paths(platformPkg) ?? [];
6740
7117
  for (const base of searchPaths) {
6741
7118
  const candidate = join6(base, platformPkg, binaryName);
@@ -9795,7 +10172,7 @@ Call the generate_thumbnail tool with a detailed, vivid prompt that will create
9795
10172
  });
9796
10173
 
9797
10174
  // src/L7-app/sdk/VidPipeSDK.ts
9798
- init_types();
10175
+ init_types2();
9799
10176
  init_environment();
9800
10177
  init_globalConfig();
9801
10178
  init_fileSystem();
@@ -10054,6 +10431,31 @@ var LateApiClient = class {
10054
10431
  }
10055
10432
  return allPosts;
10056
10433
  }
10434
+ // ── Queue management ─────────────────────────────────────────────────
10435
+ async listQueues(profileId, all = false) {
10436
+ const params = new URLSearchParams({ profileId });
10437
+ if (all) params.set("all", "true");
10438
+ return this.request(`/queue/slots?${params}`);
10439
+ }
10440
+ async createQueue(params) {
10441
+ return this.request("/queue/slots", { method: "POST", body: JSON.stringify(params) });
10442
+ }
10443
+ async updateQueue(params) {
10444
+ return this.request("/queue/slots", { method: "PUT", body: JSON.stringify(params) });
10445
+ }
10446
+ async deleteQueue(profileId, queueId) {
10447
+ return this.request(`/queue/slots?profileId=${profileId}&queueId=${queueId}`, { method: "DELETE" });
10448
+ }
10449
+ async previewQueue(profileId, queueId, count = 20) {
10450
+ const params = new URLSearchParams({ profileId, count: String(count) });
10451
+ if (queueId) params.set("queueId", queueId);
10452
+ return this.request(`/queue/preview?${params}`);
10453
+ }
10454
+ async getNextQueueSlot(profileId, queueId) {
10455
+ const params = new URLSearchParams({ profileId });
10456
+ if (queueId) params.set("queueId", queueId);
10457
+ return this.request(`/queue/next-slot?${params}`);
10458
+ }
10057
10459
  // ── Helper ───────────────────────────────────────────────────────────
10058
10460
  async validateConnection() {
10059
10461
  try {
@@ -10074,6 +10476,129 @@ function createLateApiClient(...args) {
10074
10476
  return new LateApiClient(...args);
10075
10477
  }
10076
10478
 
10479
+ // src/L3-services/queueMapping/queueMapping.ts
10480
+ init_configLogger();
10481
+ init_fileSystem();
10482
+ init_paths();
10483
+ var CACHE_FILE = ".vidpipe-queue-cache.json";
10484
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
10485
+ var memoryCache = null;
10486
+ function cachePath() {
10487
+ return join(process.cwd(), CACHE_FILE);
10488
+ }
10489
+ function isCacheValid(cache) {
10490
+ const fetchedAtTime = new Date(cache.fetchedAt).getTime();
10491
+ if (Number.isNaN(fetchedAtTime)) {
10492
+ logger_default.warn("Invalid fetchedAt in queue cache; treating as stale", {
10493
+ fetchedAt: cache.fetchedAt
10494
+ });
10495
+ return false;
10496
+ }
10497
+ const age = Date.now() - fetchedAtTime;
10498
+ return age < CACHE_TTL_MS;
10499
+ }
10500
+ async function readFileCache() {
10501
+ try {
10502
+ const raw = await readTextFile(cachePath());
10503
+ const cache = JSON.parse(raw);
10504
+ if (cache.mappings && cache.profileId && cache.fetchedAt && isCacheValid(cache)) {
10505
+ return cache;
10506
+ }
10507
+ return null;
10508
+ } catch {
10509
+ return null;
10510
+ }
10511
+ }
10512
+ async function writeFileCache(cache) {
10513
+ try {
10514
+ if (!cache || typeof cache !== "object" || !cache.mappings || !cache.profileId || !cache.fetchedAt) {
10515
+ logger_default.warn("Invalid queue cache structure, skipping write");
10516
+ return;
10517
+ }
10518
+ const sanitized = {
10519
+ mappings: typeof cache.mappings === "object" ? { ...cache.mappings } : {},
10520
+ profileId: String(cache.profileId),
10521
+ fetchedAt: String(cache.fetchedAt)
10522
+ };
10523
+ for (const [name, id] of Object.entries(sanitized.mappings)) {
10524
+ if (typeof name !== "string" || typeof id !== "string" || /[\x00-\x1f]/.test(name) || /[\x00-\x1f]/.test(id)) {
10525
+ logger_default.warn("Invalid queue mapping data from API, skipping cache write");
10526
+ return;
10527
+ }
10528
+ }
10529
+ const resolvedCachePath = resolve(cachePath());
10530
+ if (!resolvedCachePath.startsWith(resolve(process.cwd()) + sep)) {
10531
+ throw new Error("Cache path outside working directory");
10532
+ }
10533
+ await writeTextFile(resolvedCachePath, JSON.stringify(sanitized, null, 2));
10534
+ } catch (err) {
10535
+ logger_default.warn("Failed to write queue cache file", { error: err });
10536
+ }
10537
+ }
10538
+ async function fetchAndCache() {
10539
+ const client = new LateApiClient();
10540
+ const profiles = await client.listProfiles();
10541
+ if (profiles.length === 0) {
10542
+ logger_default.warn("No Late API profiles found \u2014 queue mappings will be empty");
10543
+ const emptyCache = {
10544
+ mappings: {},
10545
+ profileId: "",
10546
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
10547
+ };
10548
+ memoryCache = emptyCache;
10549
+ return emptyCache;
10550
+ }
10551
+ const profileId = profiles[0]._id;
10552
+ const { queues } = await client.listQueues(profileId, true);
10553
+ if (queues.length === 0) {
10554
+ logger_default.warn(
10555
+ "No queues found in Late API \u2014 run `vidpipe sync-queues` to create platform queues"
10556
+ );
10557
+ }
10558
+ const mappings = {};
10559
+ for (const queue of queues) {
10560
+ mappings[queue.name] = queue._id;
10561
+ }
10562
+ const cache = {
10563
+ mappings,
10564
+ profileId,
10565
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
10566
+ };
10567
+ memoryCache = cache;
10568
+ await writeFileCache(cache);
10569
+ logger_default.info("Refreshed Late queue mappings", {
10570
+ queueCount: queues.length,
10571
+ queues: Object.keys(mappings)
10572
+ });
10573
+ return cache;
10574
+ }
10575
+ async function ensureMappings() {
10576
+ if (memoryCache && isCacheValid(memoryCache)) {
10577
+ return memoryCache;
10578
+ }
10579
+ const fileCache = await readFileCache();
10580
+ if (fileCache) {
10581
+ memoryCache = fileCache;
10582
+ return fileCache;
10583
+ }
10584
+ try {
10585
+ return await fetchAndCache();
10586
+ } catch (err) {
10587
+ logger_default.error("Failed to fetch Late queue mappings", { error: err });
10588
+ return { mappings: {}, profileId: "", fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
10589
+ }
10590
+ }
10591
+ async function getQueueId(platform, clipType) {
10592
+ const cache = await ensureMappings();
10593
+ const normalizedPlatform = platform === "twitter" ? "x" : platform;
10594
+ const queueName = `${normalizedPlatform}-${clipType}`;
10595
+ return cache.mappings[queueName] ?? null;
10596
+ }
10597
+ async function getProfileId() {
10598
+ const cache = await ensureMappings();
10599
+ return cache.profileId;
10600
+ }
10601
+
10077
10602
  // src/L2-clients/scheduleStore/scheduleStore.ts
10078
10603
  init_fileSystem();
10079
10604
  init_paths();
@@ -10376,7 +10901,7 @@ function getIdeaSpacingConfig() {
10376
10901
  }
10377
10902
 
10378
10903
  // src/L3-services/postStore/postStore.ts
10379
- init_types();
10904
+ init_types2();
10380
10905
  init_environment();
10381
10906
  init_configLogger();
10382
10907
  init_fileSystem();
@@ -11249,11 +11774,11 @@ async function markFailed(slug, error) {
11249
11774
  init_fileSystem();
11250
11775
  init_paths();
11251
11776
  init_configLogger();
11252
- init_types();
11253
- init_types();
11777
+ init_types2();
11778
+ init_types2();
11254
11779
 
11255
11780
  // src/L3-services/socialPosting/platformContentStrategy.ts
11256
- init_types();
11781
+ init_types2();
11257
11782
  var CONTENT_MATRIX = {
11258
11783
  ["youtube" /* YouTube */]: {
11259
11784
  video: { captions: true, variantKey: null },
@@ -11301,10 +11826,10 @@ async function generateImage2(prompt, outputPath, options) {
11301
11826
  }
11302
11827
 
11303
11828
  // src/L3-services/queueBuilder/queueBuilder.ts
11304
- function resolveShortMedia(clip, platform) {
11829
+ function resolveShortMedia(clip, platform, variantsEnabled) {
11305
11830
  const rule = getMediaRule(platform, "short");
11306
11831
  if (!rule) return null;
11307
- if (rule.variantKey && clip.variants?.length) {
11832
+ if (variantsEnabled !== false && rule.variantKey && clip.variants?.length) {
11308
11833
  const match = clip.variants.find((v) => v.platform === rule.variantKey);
11309
11834
  if (match) return match.path;
11310
11835
  if (platform === "instagram" /* Instagram */) {
@@ -11351,7 +11876,7 @@ async function parsePostFrontmatter(postPath) {
11351
11876
  function stripFrontmatter(content) {
11352
11877
  return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
11353
11878
  }
11354
- async function buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds) {
11879
+ async function buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds, variantsEnabled) {
11355
11880
  const result = { itemsCreated: 0, itemsSkipped: 0, errors: [] };
11356
11881
  for (const post of socialPosts) {
11357
11882
  try {
@@ -11370,7 +11895,7 @@ async function buildPublishQueue(video, shorts, mediumClips, socialPosts, captio
11370
11895
  clipSlug = short.slug;
11371
11896
  clipType = "short";
11372
11897
  sourceClip = dirname(short.outputPath);
11373
- mediaPath = resolveShortMedia(short, post.platform);
11898
+ mediaPath = resolveShortMedia(short, post.platform, variantsEnabled);
11374
11899
  thumbnailPath = short.thumbnailPath ?? null;
11375
11900
  clipIdeaIssueNumber = short.ideaIssueNumber;
11376
11901
  } else if (medium) {
@@ -11508,7 +12033,7 @@ init_modelConfig();
11508
12033
  init_configLogger();
11509
12034
  init_ideaService();
11510
12035
  init_providerFactory();
11511
- init_types();
12036
+ init_types2();
11512
12037
  init_BaseAgent();
11513
12038
  var BASE_SYSTEM_PROMPT = `You are a content strategist for a tech content creator. Your role is to research trending topics, analyze what's working, and generate compelling video ideas grounded in real-world data.
11514
12039
 
@@ -14823,7 +15348,7 @@ var SocialPostAsset = class extends TextAsset {
14823
15348
  // src/L5-assets/ShortVideoAsset.ts
14824
15349
  init_paths();
14825
15350
  init_fileSystem();
14826
- init_types();
15351
+ init_types2();
14827
15352
 
14828
15353
  // src/L5-assets/thumbnailGeneration.ts
14829
15354
  init_paths();
@@ -15083,7 +15608,7 @@ var ShortVideoAsset = class extends VideoAsset {
15083
15608
  // src/L5-assets/MediumClipAsset.ts
15084
15609
  init_paths();
15085
15610
  init_fileSystem();
15086
- init_types();
15611
+ init_types2();
15087
15612
  var MediumClipAsset = class extends VideoAsset {
15088
15613
  /** Parent video this clip was extracted from */
15089
15614
  parent;
@@ -15493,7 +16018,39 @@ init_videoOperations();
15493
16018
  init_fileSystem();
15494
16019
  init_paths();
15495
16020
  init_configLogger();
15496
- var SYSTEM_PROMPT5 = `You are a viral short-form video strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most compelling, shareable moments** as shorts (15\u201360 seconds each).
16021
+ function buildShortsSystemPrompt(clipConfig) {
16022
+ const minDuration = clipConfig?.duration?.min ?? 15;
16023
+ const maxDuration = clipConfig?.duration?.max ?? 60;
16024
+ const minViralScore = clipConfig?.minViralScore ?? 8;
16025
+ const maxClips = clipConfig?.maxClips ?? 5;
16026
+ const strategy = clipConfig?.strategy ?? "hook-first";
16027
+ const isHookFirst = strategy === "hook-first";
16028
+ const hookFirstSection = isHookFirst ? `
16029
+ ### Hook-First Video Ordering
16030
+
16031
+ If a short's natural content flows A\u2192B\u2192C\u2192D, the final short should play as D\u2192A\u2192B\u2192C \u2014 the **payoff moment (D) is moved to the front as the hook**, then the content plays from the beginning up to that point. The hook does NOT repeat.
16032
+
16033
+ **How to implement:**
16034
+ 1. Plan the content as normal (full story A\u2192D)
16035
+ 2. Identify the single most arresting 2-5 second moment \u2014 usually the payoff, punchline, or emotional peak
16036
+ 3. That moment becomes the FIRST segment in the segments array
16037
+ 4. The remaining content plays chronologically from start to just before the hook
16038
+ 5. Example: content [120s\u2013150s], best moment [145s\u2013150s] \u2192 segments: [{start: 145, end: 150}, {start: 120, end: 145}]
16039
+
16040
+ **Hook quality rules (NEVER violate):**
16041
+ - The hook segment MUST start and end on a **complete sentence or clause boundary**
16042
+ - The hook MUST be a **self-contained, complete thought** \u2014 understandable without prior context
16043
+ - If no moment qualifies as a clean hook, **keep segments chronological** and use hook text only` : `
16044
+ ### Chronological Video Ordering
16045
+
16046
+ Segments MUST be ordered chronologically \u2014 preserve the original video timeline. Do NOT reorder segments for hook-first ordering. Instead, rely on a strong verbal hook (the \`hook\` text field) as a text overlay to capture attention in the first 3 seconds while the content plays in its natural order.
16047
+
16048
+ **How to implement:**
16049
+ 1. Plan the content as normal \u2014 segments play in order from the video
16050
+ 2. Identify the most compelling hook text that teases the payoff
16051
+ 3. All segments in the segments array must be in ascending time order
16052
+ 4. The hook text overlay provides the attention-grab, not segment reordering`;
16053
+ return `You are a viral short-form video strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most compelling, shareable moments** as shorts (${minDuration}\u2013${maxDuration} seconds each).
15497
16054
 
15498
16055
  ## Core Philosophy: Quality Over Quantity
15499
16056
 
@@ -15510,7 +16067,7 @@ Design every clip to maximize rewatches and shares, not passive likes.
15510
16067
  ## Your workflow
15511
16068
  1. Read the transcript and note the total duration.
15512
16069
  2. Work through the transcript **section by section**. For each chunk, identify moments with genuine viral potential.
15513
- 3. For each potential short, score it using the Viral Score Framework (see below). **Only extract clips scoring 8 or higher.**
16070
+ 3. For each potential short, score it using the Viral Score Framework (see below). **Only extract clips scoring ${minViralScore} or higher.**
15514
16071
  4. Call **add_shorts** for each batch of qualifying shorts. You can call it as many times as needed.
15515
16072
  5. After your first pass, call **review_shorts** to see everything you've planned so far.
15516
16073
  6. Review critically: Would YOU share each of these? Could any be combined into stronger composites? Are there moments you underscored?
@@ -15525,7 +16082,7 @@ Viral Score = (Hook Strength \xD7 3) + (Emotional Intensity \xD7 2) +
15525
16082
  (Replay Potential \xD7 2)
15526
16083
 
15527
16084
  Maximum score: 60 \u2192 Normalized to 1-20 scale (divide by 3)
15528
- Minimum to extract: 8/20
16085
+ Minimum to extract: ${minViralScore}/20
15529
16086
  \`\`\`
15530
16087
 
15531
16088
  | Factor | 1 (Weak) | 3 (Moderate) | 5 (Strong) |
@@ -15561,22 +16118,7 @@ Minimum to extract: 8/20
15561
16118
  | **result-first** | Show the outcome immediately, then explain how | Tutorials, before/after |
15562
16119
  | **bold-claim** | Make a specific, surprising statement of fact | Data-driven, authority content |
15563
16120
  | **question** | "Want to know why X?" \u2014 engage curiosity directly | Engagement-focused, relatable |
15564
-
15565
- ### Hook-First Video Ordering
15566
-
15567
- If a short's natural content flows A\u2192B\u2192C\u2192D, the final short should play as D\u2192A\u2192B\u2192C \u2014 the **payoff moment (D) is moved to the front as the hook**, then the content plays from the beginning up to that point. The hook does NOT repeat.
15568
-
15569
- **How to implement:**
15570
- 1. Plan the content as normal (full story A\u2192D)
15571
- 2. Identify the single most arresting 2-5 second moment \u2014 usually the payoff, punchline, or emotional peak
15572
- 3. That moment becomes the FIRST segment in the segments array
15573
- 4. The remaining content plays chronologically from start to just before the hook
15574
- 5. Example: content [120s\u2013150s], best moment [145s\u2013150s] \u2192 segments: [{start: 145, end: 150}, {start: 120, end: 145}]
15575
-
15576
- **Hook quality rules (NEVER violate):**
15577
- - The hook segment MUST start and end on a **complete sentence or clause boundary**
15578
- - The hook MUST be a **self-contained, complete thought** \u2014 understandable without prior context
15579
- - If no moment qualifies as a clean hook, **keep segments chronological** and use hook text only
16121
+ ${hookFirstSection}
15580
16122
 
15581
16123
  ## Narrative structures (classify every short)
15582
16124
 
@@ -15627,14 +16169,14 @@ Composites (multi-segment shorts) often make the **best** shorts:
15627
16169
 
15628
16170
  ## Rules
15629
16171
 
15630
- 1. Each short must be 15-60 seconds total duration.
16172
+ 1. Each short must be ${minDuration}-${maxDuration} seconds total duration.
15631
16173
  2. Timestamps must align to word boundaries from the transcript.
15632
16174
  3. Prefer natural sentence boundaries for clean cuts.
15633
16175
  4. Every short needs a catchy, descriptive title (5-10 words).
15634
16176
  5. Tags should be lowercase, no hashes, 3-6 per short.
15635
16177
  6. A 1-second buffer is automatically added around each segment boundary.
15636
16178
  7. Avoid significant timestamp overlap between shorts.
15637
- 8. **Minimum viral score of 8/20 to extract.** Be ruthless about quality.
16179
+ 8. **Minimum viral score of ${minViralScore}/20 to extract.** Be ruthless about quality.
15638
16180
  9. Every short MUST have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.
15639
16181
 
15640
16182
  ## Using Clip Direction
@@ -15647,8 +16189,10 @@ You may receive AI-generated clip direction with suggested shorts. Use these as
15647
16189
 
15648
16190
  Before adding a short, ask yourself: **"Would I interrupt someone to show them this?"**
15649
16191
  - If YES \u2192 strong clip, add it
15650
- - If "maybe, it's interesting" \u2192 score it honestly and only keep if \u22658
16192
+ - If "maybe, it's interesting" \u2192 score it honestly and only keep if \u2265${minViralScore}
15651
16193
  - If NO \u2192 drop it, no matter how "complete" the topic coverage feels`;
16194
+ }
16195
+ var SYSTEM_PROMPT5 = buildShortsSystemPrompt();
15652
16196
  var ADD_SHORTS_SCHEMA = {
15653
16197
  type: "object",
15654
16198
  properties: {
@@ -15798,20 +16342,22 @@ Review critically:
15798
16342
  return this.isFinalized;
15799
16343
  }
15800
16344
  };
15801
- async function generateShorts(video, transcript, model, clipDirection, webcamOverride, ideas) {
15802
- const systemPrompt = SYSTEM_PROMPT5 + (ideas?.length ? buildIdeaContext(ideas) : "");
16345
+ async function generateShorts(video, transcript, model, clipDirection, webcamOverride, ideas, clipConfig, generateVariants) {
16346
+ const basePrompt = clipConfig ? buildShortsSystemPrompt(clipConfig) : SYSTEM_PROMPT5;
16347
+ const systemPrompt = basePrompt + (ideas?.length ? buildIdeaContext(ideas) : "");
15803
16348
  const agent = new ShortsAgent(systemPrompt, model);
15804
16349
  const transcriptLines = transcript.segments.map((seg) => {
15805
16350
  const words = seg.words.map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`).join(" ");
15806
16351
  return `[${seg.start.toFixed(2)}s \u2013 ${seg.end.toFixed(2)}s] ${seg.text}
15807
16352
  Words: ${words}`;
15808
16353
  });
16354
+ const minViralScore = clipConfig?.minViralScore ?? 8;
15809
16355
  const promptParts = [
15810
16356
  `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and find the most viral-worthy moments for shorts.
15811
16357
  `,
15812
16358
  `Video: ${video.filename}`,
15813
16359
  `Duration: ${transcript.duration.toFixed(1)}s`,
15814
- `Focus on quality over quantity \u2014 only extract clips scoring 8+ on the viral score framework. Every clip must have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.
16360
+ `Focus on quality over quantity \u2014 only extract clips scoring ${minViralScore}+ on the viral score framework. Every clip must have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.
15815
16361
  `,
15816
16362
  "--- TRANSCRIPT ---\n",
15817
16363
  transcriptLines.join("\n\n"),
@@ -15863,23 +16409,27 @@ Words: ${words}`;
15863
16409
  } else {
15864
16410
  await extractCompositeClip2(video.repoPath, segments, outputPath);
15865
16411
  }
15866
- let variants;
15867
- try {
15868
- const defaultPlatforms = ["tiktok", "youtube-shorts", "instagram-reels", "instagram-feed", "linkedin"];
15869
- const results = await generatePlatformVariants2(outputPath, shortsDir, shortSlug, defaultPlatforms, { webcamOverride });
15870
- if (results.length > 0) {
15871
- variants = results.map((v) => ({
15872
- path: v.path,
15873
- aspectRatio: v.aspectRatio,
15874
- platform: v.platform,
15875
- width: v.width,
15876
- height: v.height
15877
- }));
15878
- logger_default.info(`[ShortsAgent] Generated ${variants.length} platform variants for: ${plan.title}`);
16412
+ let clipVariants;
16413
+ if (generateVariants !== false) {
16414
+ try {
16415
+ const defaultPlatforms = ["tiktok", "youtube-shorts", "instagram-reels", "instagram-feed", "linkedin"];
16416
+ const results = await generatePlatformVariants2(outputPath, shortsDir, shortSlug, defaultPlatforms, { webcamOverride });
16417
+ if (results.length > 0) {
16418
+ clipVariants = results.map((v) => ({
16419
+ path: v.path,
16420
+ aspectRatio: v.aspectRatio,
16421
+ platform: v.platform,
16422
+ width: v.width,
16423
+ height: v.height
16424
+ }));
16425
+ logger_default.info(`[ShortsAgent] Generated ${clipVariants.length} platform variants for: ${plan.title}`);
16426
+ }
16427
+ } catch (err) {
16428
+ const message = err instanceof Error ? err.message : String(err);
16429
+ logger_default.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`);
15879
16430
  }
15880
- } catch (err) {
15881
- const message = err instanceof Error ? err.message : String(err);
15882
- logger_default.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`);
16431
+ } else {
16432
+ logger_default.info(`[ShortsAgent] Skipping variant generation for: ${plan.title} (disabled by spec)`);
15883
16433
  }
15884
16434
  let captionedPath;
15885
16435
  try {
@@ -15894,8 +16444,8 @@ Words: ${words}`;
15894
16444
  logger_default.warn(`[ShortsAgent] Caption burning failed for ${plan.title}: ${message}`);
15895
16445
  captionedPath = void 0;
15896
16446
  }
15897
- if (variants) {
15898
- const portraitVariants = variants.filter((v) => v.aspectRatio === "9:16");
16447
+ if (clipVariants) {
16448
+ const portraitVariants = clipVariants.filter((v) => v.aspectRatio === "9:16");
15899
16449
  if (portraitVariants.length > 0) {
15900
16450
  try {
15901
16451
  const hookText = plan.hook ?? plan.title;
@@ -15913,7 +16463,7 @@ Words: ${words}`;
15913
16463
  logger_default.warn(`[ShortsAgent] Portrait caption burning failed for ${plan.title}: ${message}`);
15914
16464
  }
15915
16465
  }
15916
- const nonPortraitVariants = variants.filter((v) => v.aspectRatio !== "9:16");
16466
+ const nonPortraitVariants = clipVariants.filter((v) => v.aspectRatio !== "9:16");
15917
16467
  for (const variant of nonPortraitVariants) {
15918
16468
  try {
15919
16469
  const variantAssContent = segments.length === 1 ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end) : generateStyledASSForComposite(transcript, segments);
@@ -15964,7 +16514,7 @@ Words: ${words}`;
15964
16514
  description: plan.description,
15965
16515
  tags: plan.tags,
15966
16516
  hook: plan.hook,
15967
- variants,
16517
+ variants: clipVariants,
15968
16518
  hookType: plan.hookType,
15969
16519
  emotionalTrigger: plan.emotionalTrigger,
15970
16520
  viralScore: plan.viralScore,
@@ -15987,7 +16537,15 @@ init_videoOperations();
15987
16537
  init_fileSystem();
15988
16538
  init_paths();
15989
16539
  init_configLogger();
15990
- var SYSTEM_PROMPT6 = `You are a medium-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most valuable, engaging 1-3 minute segments** as standalone medium-form clips.
16540
+ function buildMediumSystemPrompt(clipConfig) {
16541
+ const minDuration = clipConfig?.duration?.min ?? 60;
16542
+ const maxDuration = clipConfig?.duration?.max ?? 180;
16543
+ const minViralScore = clipConfig?.minViralScore ?? 10;
16544
+ const maxClips = clipConfig?.maxClips ?? 5;
16545
+ const minMinutes = Math.floor(minDuration / 60);
16546
+ const maxMinutes = Math.ceil(maxDuration / 60);
16547
+ const durationLabel = `${minMinutes}-${maxMinutes} minute`;
16548
+ return `You are a medium-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most valuable, engaging ${minMinutes}-${maxMinutes} minute segments** as standalone medium-form clips.
15991
16549
 
15992
16550
  ## Core Philosophy: Value Density Over Coverage
15993
16551
 
@@ -16004,7 +16562,7 @@ Design every clip to maximize saves and shares.
16004
16562
  ## Your workflow
16005
16563
  1. Read the transcript and note the total duration.
16006
16564
  2. Work through the transcript **section by section** (roughly 5-8 minute chunks). For each chunk, identify segments with genuine standalone value.
16007
- 3. For each potential clip, score it using the Viral Score Framework (see below). **Only extract clips scoring 10 or higher.**
16565
+ 3. For each potential clip, score it using the Viral Score Framework (see below). **Only extract clips scoring ${minViralScore} or higher.**
16008
16566
  4. Call **add_medium_clips** for each batch of clips you find. You can call it as many times as needed.
16009
16567
  5. After your first pass, call **review_medium_clips** to see everything you've planned so far.
16010
16568
  6. Review critically: Does each clip deliver clear, standalone value? Would someone save it? Could segments be combined into something stronger?
@@ -16019,7 +16577,7 @@ Viral Score = (Hook Strength \xD7 3) + (Emotional Intensity \xD7 2) +
16019
16577
  (Replay Potential \xD7 2)
16020
16578
 
16021
16579
  Maximum score: 60 \u2192 Normalized to 1-20 scale (divide by 3)
16022
- Minimum to extract: 10/20 (higher bar than shorts \u2014 medium clips cost more to produce)
16580
+ Minimum to extract: ${minViralScore}/20 (higher bar than shorts \u2014 medium clips cost more to produce)
16023
16581
  \`\`\`
16024
16582
 
16025
16583
  | Factor | 1 (Weak) | 3 (Moderate) | 5 (Strong) |
@@ -16098,10 +16656,10 @@ Identify the PRIMARY emotion driving engagement:
16098
16656
 
16099
16657
  ## Duration optimization
16100
16658
 
16101
- - **Sweet spot**: 60-120 seconds (1-2 minutes)
16102
- - **Maximum**: 180 seconds \u2014 only if retention quality is exceptional
16103
- - **Under 60 seconds**: Too short for medium format \u2014 should be a short instead
16104
- - **Over 120 seconds**: Requires multiple micro-hooks and exceptional pacing
16659
+ - **Sweet spot**: ${minDuration}-${Math.min(maxDuration, 120)} seconds (${minMinutes}-${Math.min(maxMinutes, 2)} minutes)
16660
+ - **Maximum**: ${maxDuration} seconds \u2014 only if retention quality is exceptional
16661
+ - **Under ${minDuration} seconds**: Too short for medium format \u2014 should be a short instead
16662
+ - **Over ${Math.min(maxDuration, 120)} seconds**: Requires multiple micro-hooks and exceptional pacing
16105
16663
 
16106
16664
  ## Differences from shorts
16107
16665
 
@@ -16123,7 +16681,7 @@ For compilations, segments must be in chronological order.
16123
16681
 
16124
16682
  ## Rules
16125
16683
 
16126
- 1. Each clip must be 60-180 seconds total duration.
16684
+ 1. Each clip must be ${minDuration}-${maxDuration} seconds total duration.
16127
16685
  2. Timestamps must align to word boundaries from the transcript.
16128
16686
  3. Prefer natural sentence and paragraph boundaries for clean entry/exit points.
16129
16687
  4. Each clip must be self-contained \u2014 a viewer with no other context should get value.
@@ -16131,7 +16689,7 @@ For compilations, segments must be in chronological order.
16131
16689
  6. For compilations, specify segments in **chronological order**.
16132
16690
  7. Tags should be lowercase, no hashes, 3-6 per clip.
16133
16691
  8. A 1-second buffer is automatically added around each segment boundary.
16134
- 9. **Minimum viral score of 10/20 to extract.** Medium clips cost more to produce \u2014 quality bar is higher.
16692
+ 9. **Minimum viral score of ${minViralScore}/20 to extract.** Medium clips cost more to produce \u2014 quality bar is higher.
16135
16693
  10. Every clip MUST have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.
16136
16694
  11. Avoid significant overlap with content that would work better as a short.
16137
16695
 
@@ -16139,7 +16697,7 @@ For compilations, segments must be in chronological order.
16139
16697
 
16140
16698
  Before adding a clip, ask yourself: **"Would I bookmark this to come back to later?"**
16141
16699
  - If YES \u2192 strong clip, add it
16142
- - If "it's informative but not reference-worthy" \u2192 score it honestly and only keep if \u226510
16700
+ - If "it's informative but not reference-worthy" \u2192 score it honestly and only keep if \u2265${minViralScore}
16143
16701
  - If NO \u2192 drop it or consider if it works better as a short
16144
16702
 
16145
16703
  ## Using Clip Direction
@@ -16148,6 +16706,8 @@ You may receive AI-generated clip direction with suggested medium clips. Use the
16148
16706
  - Feel free to adjust timestamps, combine suggestions, or ignore ones that don't work
16149
16707
  - You may also find good clips NOT in the suggestions \u2014 always analyze the full transcript
16150
16708
  - Pay special attention to suggested hooks and topic arcs \u2014 they come from multimodal analysis`;
16709
+ }
16710
+ var SYSTEM_PROMPT6 = buildMediumSystemPrompt();
16151
16711
  var ADD_MEDIUM_CLIPS_SCHEMA = {
16152
16712
  type: "object",
16153
16713
  properties: {
@@ -16305,20 +16865,26 @@ Review critically:
16305
16865
  return this.isFinalized;
16306
16866
  }
16307
16867
  };
16308
- async function generateMediumClips(video, transcript, model, clipDirection, ideas) {
16309
- const systemPrompt = SYSTEM_PROMPT6 + (ideas?.length ? buildIdeaContext(ideas) : "");
16868
+ async function generateMediumClips(video, transcript, model, clipDirection, ideas, clipConfig) {
16869
+ const basePrompt = clipConfig ? buildMediumSystemPrompt(clipConfig) : SYSTEM_PROMPT6;
16870
+ const systemPrompt = basePrompt + (ideas?.length ? buildIdeaContext(ideas) : "");
16310
16871
  const agent = new MediumVideoAgent(systemPrompt, model);
16311
16872
  const transcriptLines = transcript.segments.map((seg) => {
16312
16873
  const words = seg.words.map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`).join(" ");
16313
16874
  return `[${seg.start.toFixed(2)}s \u2013 ${seg.end.toFixed(2)}s] ${seg.text}
16314
16875
  Words: ${words}`;
16315
16876
  });
16877
+ const minDuration = clipConfig?.duration?.min ?? 60;
16878
+ const maxDuration = clipConfig?.duration?.max ?? 180;
16879
+ const minViralScore = clipConfig?.minViralScore ?? 10;
16880
+ const minMinutes = Math.floor(minDuration / 60);
16881
+ const maxMinutes = Math.ceil(maxDuration / 60);
16316
16882
  const promptParts = [
16317
- `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and find the most valuable segments for medium-length clips (1-3 minutes each).
16883
+ `Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and find the most valuable segments for medium-length clips (${minMinutes}-${maxMinutes} minutes each).
16318
16884
  `,
16319
16885
  `Video: ${video.filename}`,
16320
16886
  `Duration: ${transcript.duration.toFixed(1)}s`,
16321
- `Focus on value density over coverage \u2014 only extract clips scoring 10+ on the viral score framework. Every clip must have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.
16887
+ `Focus on value density over coverage \u2014 only extract clips scoring ${minViralScore}+ on the viral score framework. Every clip must have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.
16322
16888
  `,
16323
16889
  "--- TRANSCRIPT ---\n",
16324
16890
  transcriptLines.join("\n\n"),
@@ -17203,17 +17769,27 @@ init_paths();
17203
17769
  init_BaseAgent();
17204
17770
  init_configLogger();
17205
17771
  init_environment();
17206
- init_types();
17207
- var SYSTEM_PROMPT8 = `You are a viral social-media content strategist.
17208
- Given a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.
17209
- Each post must match the platform's tone, format, and constraints exactly.
17210
-
17211
- Platform guidelines:
17772
+ init_types2();
17773
+ var PER_PLATFORM_GUIDELINES = `Platform guidelines:
17212
17774
  1. **TikTok** \u2013 Casual, hook-driven, trending hashtags, 150 chars max, emoji-heavy.
17213
17775
  2. **YouTube** \u2013 Descriptive, SEO-optimized title + description, relevant tags.
17214
17776
  3. **Instagram** \u2013 Visual storytelling, emoji-rich, 30 hashtags max, engaging caption.
17215
17777
  4. **LinkedIn** \u2013 Professional, thought-leadership, industry insights, 1-3 hashtags.
17216
- 5. **X (Twitter)** \u2013 Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready.
17778
+ 5. **X (Twitter)** \u2013 Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready.`;
17779
+ var UNIFIED_TONE_GUIDELINES = `Platform guidelines:
17780
+ For all platforms, use the same professional but approachable tone. Keep posts concise, informative, and authentic. Use the same core message \u2014 adapt only for platform-specific formatting constraints (character limits, hashtag placement).
17781
+ 1. **TikTok** \u2013 150 chars max, include relevant hashtags.
17782
+ 2. **YouTube** \u2013 SEO-optimized title + description, relevant tags.
17783
+ 3. **Instagram** \u2013 30 hashtags max, engaging caption.
17784
+ 4. **LinkedIn** \u2013 1-3 hashtags, professional framing.
17785
+ 5. **X (Twitter)** \u2013 280 chars max, 2-5 hashtags.`;
17786
+ function buildSocialMediaSystemPrompt(toneStrategy) {
17787
+ const platformGuidelines = toneStrategy === "unified" ? UNIFIED_TONE_GUIDELINES : PER_PLATFORM_GUIDELINES;
17788
+ return `You are a viral social-media content strategist.
17789
+ Given a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.
17790
+ Each post must match the platform's format and constraints exactly.
17791
+
17792
+ ${platformGuidelines}
17217
17793
 
17218
17794
  IMPORTANT \u2013 Content format:
17219
17795
  The "content" field you provide must be the FINAL, ready-to-post text that can be directly copied and pasted onto the platform. Do NOT use markdown headers, bullet points, or any formatting inside the content. Include hashtags inline at the end of the post text where appropriate. The content is saved as-is for direct posting.
@@ -17225,6 +17801,8 @@ Workflow:
17225
17801
 
17226
17802
  Include relevant links in posts when search results provide them.
17227
17803
  Always call "create_posts" exactly once with all 5 platform posts.`;
17804
+ }
17805
+ var SYSTEM_PROMPT8 = buildSocialMediaSystemPrompt();
17228
17806
  var SocialMediaAgent = class extends BaseAgent {
17229
17807
  collectedPosts = [];
17230
17808
  constructor(systemPrompt = SYSTEM_PROMPT8, model) {
@@ -17405,8 +17983,9 @@ async function generateShortPosts(video, short, transcript, model, summary) {
17405
17983
  await agent.destroy();
17406
17984
  }
17407
17985
  }
17408
- async function generateSocialPosts(video, transcript, summary, outputDir, model, ideas) {
17409
- const systemPrompt = SYSTEM_PROMPT8 + (ideas?.length ? buildIdeaContextForPosts(ideas) : "");
17986
+ async function generateSocialPosts(video, transcript, summary, outputDir, model, ideas, toneStrategy) {
17987
+ const basePrompt = toneStrategy ? buildSocialMediaSystemPrompt(toneStrategy) : SYSTEM_PROMPT8;
17988
+ const systemPrompt = basePrompt + (ideas?.length ? buildIdeaContextForPosts(ideas) : "");
17410
17989
  const agent = new SocialMediaAgent(systemPrompt, model);
17411
17990
  try {
17412
17991
  const userMessage = [
@@ -17839,7 +18418,7 @@ async function enhanceVideo(videoPath, transcript, video) {
17839
18418
  // src/L5-assets/MainVideoAsset.ts
17840
18419
  init_environment();
17841
18420
  init_configLogger();
17842
- init_types();
18421
+ init_types2();
17843
18422
  var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
17844
18423
  sourcePath;
17845
18424
  videoDir;
@@ -17848,6 +18427,16 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
17848
18427
  _ideas = [];
17849
18428
  /** Per-clip idea assignments from idea discovery (clipId → ideaIssueNumber) */
17850
18429
  _clipIdeaMap = /* @__PURE__ */ new Map();
18430
+ /** Pipeline spec controlling clip and platform configuration */
18431
+ _spec;
18432
+ /** Set the pipeline spec for configuring agent behavior */
18433
+ setSpec(spec) {
18434
+ this._spec = spec;
18435
+ }
18436
+ /** Get the pipeline spec */
18437
+ get spec() {
18438
+ return this._spec;
18439
+ }
17851
18440
  /** Set ideas for editorial direction */
17852
18441
  setIdeas(ideas) {
17853
18442
  this._ideas = ideas;
@@ -18397,7 +18986,18 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
18397
18986
  async generateShortsInternal() {
18398
18987
  const transcript = await this.getTranscript();
18399
18988
  const videoFile = await this.toVideoFile();
18400
- const shorts = await generateShorts(videoFile, transcript);
18989
+ const shortsConfig = this._spec?.clips.shorts;
18990
+ const shouldGenerateVariants = this._spec?.distribution.platforms.variants;
18991
+ const shorts = await generateShorts(
18992
+ videoFile,
18993
+ transcript,
18994
+ void 0,
18995
+ void 0,
18996
+ void 0,
18997
+ void 0,
18998
+ shortsConfig,
18999
+ shouldGenerateVariants
19000
+ );
18401
19001
  logger_default.info(`Generated ${shorts.length} short clips`);
18402
19002
  return shorts;
18403
19003
  }
@@ -18429,7 +19029,15 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
18429
19029
  async generateMediumClipsInternal() {
18430
19030
  const transcript = await this.getTranscript();
18431
19031
  const videoFile = await this.toVideoFile();
18432
- const clips = await generateMediumClips(videoFile, transcript);
19032
+ const mediumConfig = this._spec?.clips.medium;
19033
+ const clips = await generateMediumClips(
19034
+ videoFile,
19035
+ transcript,
19036
+ void 0,
19037
+ void 0,
19038
+ void 0,
19039
+ mediumConfig
19040
+ );
18433
19041
  logger_default.info(`Generated ${clips.length} medium clips for ${this.slug}`);
18434
19042
  return clips;
18435
19043
  }
@@ -18452,7 +19060,16 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
18452
19060
  const summary = await this.getSummary();
18453
19061
  const video = await this.toVideoFile();
18454
19062
  await ensureDirectory(this.socialPostsDir);
18455
- const posts = await generateSocialPosts(video, transcript, summary, this.socialPostsDir);
19063
+ const toneStrategy = this._spec?.distribution.platforms.toneStrategy;
19064
+ const posts = await generateSocialPosts(
19065
+ video,
19066
+ transcript,
19067
+ summary,
19068
+ this.socialPostsDir,
19069
+ void 0,
19070
+ void 0,
19071
+ toneStrategy
19072
+ );
18456
19073
  await this.markSocialPostsComplete();
18457
19074
  return posts;
18458
19075
  }
@@ -18826,7 +19443,8 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
18826
19443
  async buildPublishQueueData(shorts, mediumClips, socialPosts, captionedVideoPath) {
18827
19444
  const video = await this.toVideoFile();
18828
19445
  const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => String(idea.issueNumber)) : void 0;
18829
- return buildPublishQueue2(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds);
19446
+ const variantsEnabled = this._spec?.distribution.platforms.variants;
19447
+ return buildPublishQueue2(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds, variantsEnabled);
18830
19448
  }
18831
19449
  /**
18832
19450
  * Build the publish queue (simplified wrapper for pipeline).
@@ -18838,7 +19456,7 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
18838
19456
  };
18839
19457
 
18840
19458
  // src/L6-pipeline/pipeline.ts
18841
- init_types();
19459
+ init_types2();
18842
19460
  async function runStage(stageName, fn, stageResults) {
18843
19461
  const start = Date.now();
18844
19462
  if (progressEmitter.isEnabled()) {
@@ -18892,11 +19510,22 @@ async function runStage(stageName, fn, stageResults) {
18892
19510
  return void 0;
18893
19511
  }
18894
19512
  }
18895
- async function processVideo(videoPath, ideas, publishBy) {
19513
+ async function processVideo(videoPath, ideas, publishBy, spec) {
18896
19514
  const pipelineStart = Date.now();
18897
19515
  const stageResults = [];
18898
19516
  const cfg = getConfig();
18899
19517
  let stagesSkipped = 0;
19518
+ const skipFlags = {
19519
+ SKIP_SILENCE_REMOVAL: cfg.SKIP_SILENCE_REMOVAL,
19520
+ SKIP_VISUAL_ENHANCEMENT: cfg.SKIP_VISUAL_ENHANCEMENT,
19521
+ SKIP_CAPTIONS: cfg.SKIP_CAPTIONS,
19522
+ SKIP_INTRO_OUTRO: cfg.SKIP_INTRO_OUTRO,
19523
+ SKIP_SHORTS: cfg.SKIP_SHORTS,
19524
+ SKIP_MEDIUM_CLIPS: cfg.SKIP_MEDIUM_CLIPS,
19525
+ SKIP_SOCIAL: cfg.SKIP_SOCIAL,
19526
+ SKIP_SOCIAL_PUBLISH: cfg.SKIP_SOCIAL_PUBLISH
19527
+ };
19528
+ const effectiveSpec = spec ? applySkipFlags(spec, skipFlags) : resolveFromFlags(skipFlags);
18900
19529
  costTracker3.reset();
18901
19530
  function trackStage(stage, fn) {
18902
19531
  costTracker3.setStage(stage);
@@ -18961,43 +19590,44 @@ async function processVideo(videoPath, ideas, publishBy) {
18961
19590
  asset.setIdeas(ideas);
18962
19591
  logger_default.info(`Pipeline using ${ideas.length} idea(s) for editorial direction`);
18963
19592
  }
19593
+ asset.setSpec(effectiveSpec);
18964
19594
  try {
18965
19595
  const transcript = await trackStage("transcription" /* Transcription */, () => asset.getTranscript());
18966
19596
  let editedVideoPath;
18967
- if (!cfg.SKIP_SILENCE_REMOVAL) {
19597
+ if (effectiveSpec.processing.silenceRemoval) {
18968
19598
  editedVideoPath = await trackStage("silence-removal" /* SilenceRemoval */, () => asset.getEditedVideo());
18969
19599
  } else {
18970
- skipStage("silence-removal" /* SilenceRemoval */, "SKIP_SILENCE_REMOVAL");
19600
+ skipStage("silence-removal" /* SilenceRemoval */, "SPEC_DISABLED");
18971
19601
  }
18972
19602
  let enhancedVideoPath;
18973
- if (!cfg.SKIP_VISUAL_ENHANCEMENT) {
19603
+ if (effectiveSpec.processing.visualEnhancement) {
18974
19604
  enhancedVideoPath = await trackStage("visual-enhancement" /* VisualEnhancement */, () => asset.getEnhancedVideo());
18975
19605
  } else {
18976
- skipStage("visual-enhancement" /* VisualEnhancement */, "SKIP_VISUAL_ENHANCEMENT");
19606
+ skipStage("visual-enhancement" /* VisualEnhancement */, "SPEC_DISABLED");
18977
19607
  }
18978
19608
  let captions;
18979
- if (!cfg.SKIP_CAPTIONS) {
19609
+ if (effectiveSpec.processing.captions) {
18980
19610
  captions = await trackStage("captions" /* Captions */, () => asset.getCaptions());
18981
19611
  } else {
18982
- skipStage("captions" /* Captions */, "SKIP_CAPTIONS");
19612
+ skipStage("captions" /* Captions */, "SPEC_DISABLED");
18983
19613
  }
18984
19614
  let captionedVideoPath;
18985
- if (!cfg.SKIP_CAPTIONS) {
19615
+ if (effectiveSpec.processing.captions) {
18986
19616
  captionedVideoPath = await trackStage("caption-burn" /* CaptionBurn */, () => asset.getCaptionedVideo());
18987
19617
  } else {
18988
- skipStage("caption-burn" /* CaptionBurn */, "SKIP_CAPTIONS");
19618
+ skipStage("caption-burn" /* CaptionBurn */, "SPEC_DISABLED");
18989
19619
  }
18990
19620
  let introOutroVideoPath;
18991
- if (!cfg.SKIP_INTRO_OUTRO) {
19621
+ if (effectiveSpec.processing.introOutro) {
18992
19622
  introOutroVideoPath = await trackStage("intro-outro" /* IntroOutro */, () => asset.getIntroOutroVideo());
18993
19623
  } else {
18994
- skipStage("intro-outro" /* IntroOutro */, "SKIP_INTRO_OUTRO");
19624
+ skipStage("intro-outro" /* IntroOutro */, "SPEC_DISABLED");
18995
19625
  }
18996
19626
  let shorts = [];
18997
- if (!cfg.SKIP_SHORTS) {
19627
+ if (effectiveSpec.clips.shorts.enabled) {
18998
19628
  const shortAssets = await trackStage("shorts" /* Shorts */, async () => {
18999
19629
  const assets = await asset.getShorts();
19000
- if (!cfg.SKIP_INTRO_OUTRO) {
19630
+ if (effectiveSpec.processing.introOutro) {
19001
19631
  for (const shortAsset of assets) {
19002
19632
  const introOutroPath = await shortAsset.getIntroOutroVideo();
19003
19633
  if (introOutroPath !== shortAsset.clip.outputPath) {
@@ -19024,13 +19654,13 @@ async function processVideo(videoPath, ideas, publishBy) {
19024
19654
  }
19025
19655
  }
19026
19656
  } else {
19027
- skipStage("shorts" /* Shorts */, "SKIP_SHORTS");
19657
+ skipStage("shorts" /* Shorts */, "SPEC_DISABLED");
19028
19658
  }
19029
19659
  let mediumClips = [];
19030
- if (!cfg.SKIP_MEDIUM_CLIPS) {
19660
+ if (effectiveSpec.clips.medium.enabled) {
19031
19661
  const mediumAssets = await trackStage("medium-clips" /* MediumClips */, async () => {
19032
19662
  const assets = await asset.getMediumClips();
19033
- if (!cfg.SKIP_INTRO_OUTRO) {
19663
+ if (effectiveSpec.processing.introOutro) {
19034
19664
  for (const clipAsset of assets) {
19035
19665
  const introOutroPath = await clipAsset.getIntroOutroVideo();
19036
19666
  if (introOutroPath !== clipAsset.clip.outputPath) {
@@ -19050,7 +19680,7 @@ async function processVideo(videoPath, ideas, publishBy) {
19050
19680
  }
19051
19681
  }
19052
19682
  } else {
19053
- skipStage("medium-clips" /* MediumClips */, "SKIP_MEDIUM_CLIPS");
19683
+ skipStage("medium-clips" /* MediumClips */, "SPEC_DISABLED");
19054
19684
  }
19055
19685
  const chapters = await trackStage("chapters" /* Chapters */, () => asset.getChapters());
19056
19686
  const summary = await trackStage("summary" /* Summary */, () => asset.getSummary());
@@ -19069,7 +19699,7 @@ async function processVideo(videoPath, ideas, publishBy) {
19069
19699
  skipStage("idea-discovery" /* IdeaDiscovery */, "NO_CLIPS");
19070
19700
  }
19071
19701
  let socialPosts = [];
19072
- if (!cfg.SKIP_SOCIAL) {
19702
+ if (effectiveSpec.distribution.enabled) {
19073
19703
  const mainPosts = await trackStage("social-media" /* SocialMedia */, () => asset.getSocialPosts()) ?? [];
19074
19704
  socialPosts.push(...mainPosts);
19075
19705
  if (shorts.length > 0) {
@@ -19093,14 +19723,14 @@ async function processVideo(videoPath, ideas, publishBy) {
19093
19723
  skipStage("medium-clip-posts" /* MediumClipPosts */, "NO_MEDIUM_CLIPS");
19094
19724
  }
19095
19725
  } else {
19096
- skipStage("social-media" /* SocialMedia */, "SKIP_SOCIAL");
19097
- skipStage("short-posts" /* ShortPosts */, "SKIP_SOCIAL");
19098
- skipStage("medium-clip-posts" /* MediumClipPosts */, "SKIP_SOCIAL");
19726
+ skipStage("social-media" /* SocialMedia */, "SPEC_DISABLED");
19727
+ skipStage("short-posts" /* ShortPosts */, "SPEC_DISABLED");
19728
+ skipStage("medium-clip-posts" /* MediumClipPosts */, "SPEC_DISABLED");
19099
19729
  }
19100
- if (!cfg.SKIP_SOCIAL_PUBLISH && socialPosts.length > 0) {
19730
+ if (effectiveSpec.distribution.publish && socialPosts.length > 0) {
19101
19731
  await trackStage("queue-build" /* QueueBuild */, () => asset.buildQueue(shorts, mediumClips, socialPosts, introOutroVideoPath ?? captionedVideoPath));
19102
- } else if (cfg.SKIP_SOCIAL_PUBLISH) {
19103
- skipStage("queue-build" /* QueueBuild */, "SKIP_SOCIAL_PUBLISH");
19732
+ } else if (!effectiveSpec.distribution.publish) {
19733
+ skipStage("queue-build" /* QueueBuild */, "SPEC_DISABLED");
19104
19734
  } else {
19105
19735
  skipStage("queue-build" /* QueueBuild */, "NO_SOCIAL_POSTS");
19106
19736
  }
@@ -19192,13 +19822,13 @@ function generateCostMarkdown(report) {
19192
19822
  }
19193
19823
  return md;
19194
19824
  }
19195
- async function processVideoSafe(videoPath, ideas, publishBy) {
19825
+ async function processVideoSafe(videoPath, ideas, publishBy, spec) {
19196
19826
  const filename = basename(videoPath);
19197
19827
  const slug = filename.replace(/\.(mp4|mov|webm|avi|mkv)$/i, "");
19198
19828
  await markPending3(slug, videoPath);
19199
19829
  await markProcessing3(slug);
19200
19830
  try {
19201
- const result = await processVideo(videoPath, ideas, publishBy);
19831
+ const result = await processVideo(videoPath, ideas, publishBy, spec);
19202
19832
  await markCompleted3(slug);
19203
19833
  return result;
19204
19834
  } catch (err) {
@@ -19811,6 +20441,19 @@ function createVidPipe(sdkConfig) {
19811
20441
  },
19812
20442
  schedule: {
19813
20443
  async findNextSlot(platform, clipType, options) {
20444
+ const effectiveClipType = clipType || "short";
20445
+ const queueId = await getQueueId(platform, effectiveClipType);
20446
+ if (queueId) {
20447
+ try {
20448
+ const profileId = await getProfileId();
20449
+ const client = createLateApiClient();
20450
+ const preview = await client.previewQueue(profileId, queueId, 1);
20451
+ if (preview.slots?.length > 0) {
20452
+ return preview.slots[0];
20453
+ }
20454
+ } catch {
20455
+ }
20456
+ }
19814
20457
  return await findNextSlot(platform, clipType, {
19815
20458
  ideaIds: options?.ideaIds?.map((ideaId) => String(ideaId)),
19816
20459
  publishBy: options?.publishBy
@@ -19910,7 +20553,7 @@ function createVidPipe(sdkConfig) {
19910
20553
  }
19911
20554
 
19912
20555
  // src/index.ts
19913
- init_types();
20556
+ init_types2();
19914
20557
  export {
19915
20558
  PLATFORM_CHAR_LIMITS,
19916
20559
  PipelineStage,