vidspotai-shared 1.0.95 → 1.0.96

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 (125) hide show
  1. package/lib/globals/aiModels/enums.d.ts +2 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +5 -0
  4. package/lib/globals/aiModels/index.d.ts.map +1 -1
  5. package/lib/globals/aiModels/index.js +2 -0
  6. package/lib/globals/aiModels/providers/hostedAvatars.d.ts +22 -0
  7. package/lib/globals/aiModels/providers/hostedAvatars.d.ts.map +1 -0
  8. package/lib/globals/aiModels/providers/hostedAvatars.js +129 -0
  9. package/lib/globals/types.d.ts +58 -1
  10. package/lib/globals/types.d.ts.map +1 -1
  11. package/lib/globals/types.js +61 -1
  12. package/lib/models/demoVideoProject.model.d.ts +244 -0
  13. package/lib/models/demoVideoProject.model.d.ts.map +1 -0
  14. package/lib/models/demoVideoProject.model.js +2 -0
  15. package/lib/models/githubConnection.model.d.ts +35 -0
  16. package/lib/models/githubConnection.model.d.ts.map +1 -0
  17. package/lib/models/githubConnection.model.js +2 -0
  18. package/lib/models/index.d.ts +3 -0
  19. package/lib/models/index.d.ts.map +1 -1
  20. package/lib/models/index.js +3 -0
  21. package/lib/models/outreach.model.d.ts +175 -0
  22. package/lib/models/outreach.model.d.ts.map +1 -0
  23. package/lib/models/outreach.model.js +2 -0
  24. package/lib/models/video.model.d.ts +4 -0
  25. package/lib/models/video.model.d.ts.map +1 -1
  26. package/lib/schemas/brandKit.schema.d.ts +4 -0
  27. package/lib/schemas/brandKit.schema.d.ts.map +1 -1
  28. package/lib/schemas/brandKit.schema.js +10 -0
  29. package/lib/schemas/demoQuestion.schema.d.ts +286 -0
  30. package/lib/schemas/demoQuestion.schema.d.ts.map +1 -0
  31. package/lib/schemas/demoQuestion.schema.js +210 -0
  32. package/lib/schemas/demoSpec.schema.d.ts +109 -6
  33. package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
  34. package/lib/schemas/demoSpec.schema.js +95 -7
  35. package/lib/schemas/index.d.ts +1 -0
  36. package/lib/schemas/index.d.ts.map +1 -1
  37. package/lib/schemas/index.js +1 -0
  38. package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
  39. package/lib/services/agent/providerFallback/chains.js +9 -1
  40. package/lib/services/agent/taskPoller.d.ts.map +1 -1
  41. package/lib/services/agent/taskPoller.js +17 -6
  42. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +21 -9
  43. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts.map +1 -1
  44. package/lib/services/agent/tools/generateAvatarVideo.tool.js +135 -39
  45. package/lib/services/aiGen/aiGenFactory.service.d.ts +1 -1
  46. package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
  47. package/lib/services/aiGen/aiGenFactory.service.js +10 -2
  48. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts +136 -0
  49. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts.map +1 -0
  50. package/lib/services/avatarGen/IAvatarVideoProvider.js +42 -0
  51. package/lib/services/avatarGen/avatarProviderFactory.d.ts +18 -0
  52. package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -0
  53. package/lib/services/avatarGen/avatarProviderFactory.js +54 -0
  54. package/lib/services/avatarGen/index.d.ts +6 -0
  55. package/lib/services/avatarGen/index.d.ts.map +1 -0
  56. package/lib/services/avatarGen/index.js +21 -0
  57. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts +45 -0
  58. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts.map +1 -0
  59. package/lib/services/avatarGen/providers/heygenAvatarProvider.js +190 -0
  60. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts +32 -0
  61. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts.map +1 -0
  62. package/lib/services/avatarGen/providers/klingAvatarProvider.js +69 -0
  63. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts +56 -0
  64. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts.map +1 -0
  65. package/lib/services/avatarGen/providers/tavusAvatarProvider.js +183 -0
  66. package/lib/services/demo/demoAuthor.d.ts +45 -0
  67. package/lib/services/demo/demoAuthor.d.ts.map +1 -0
  68. package/lib/services/demo/demoAuthor.js +75 -0
  69. package/lib/services/demo/demoAuthorNormalize.d.ts +49 -0
  70. package/lib/services/demo/demoAuthorNormalize.d.ts.map +1 -0
  71. package/lib/services/demo/demoAuthorNormalize.js +327 -0
  72. package/lib/services/demo/demoAuthorPrompt.d.ts +102 -0
  73. package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -0
  74. package/lib/services/demo/demoAuthorPrompt.js +243 -0
  75. package/lib/services/demo/demoCredits.d.ts +20 -0
  76. package/lib/services/demo/demoCredits.d.ts.map +1 -0
  77. package/lib/services/demo/demoCredits.js +51 -0
  78. package/lib/services/demo/demoMediaGuard.d.ts +43 -0
  79. package/lib/services/demo/demoMediaGuard.d.ts.map +1 -0
  80. package/lib/services/demo/demoMediaGuard.js +207 -0
  81. package/lib/services/demo/demoMusic.d.ts +13 -0
  82. package/lib/services/demo/demoMusic.d.ts.map +1 -0
  83. package/lib/services/demo/demoMusic.js +93 -0
  84. package/lib/services/demo/demoPrefill.d.ts +76 -0
  85. package/lib/services/demo/demoPrefill.d.ts.map +1 -0
  86. package/lib/services/demo/demoPrefill.js +435 -0
  87. package/lib/services/demo/demoRevisePrompt.d.ts +17 -0
  88. package/lib/services/demo/demoRevisePrompt.d.ts.map +1 -0
  89. package/lib/services/demo/demoRevisePrompt.js +85 -0
  90. package/lib/services/demo/demoReviser.d.ts +63 -0
  91. package/lib/services/demo/demoReviser.d.ts.map +1 -0
  92. package/lib/services/demo/demoReviser.js +81 -0
  93. package/lib/services/demo/demoScriptPrompt.d.ts +63 -0
  94. package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -0
  95. package/lib/services/demo/demoScriptPrompt.js +234 -0
  96. package/lib/services/demo/demoScriptWriter.d.ts +65 -0
  97. package/lib/services/demo/demoScriptWriter.d.ts.map +1 -0
  98. package/lib/services/demo/demoScriptWriter.js +211 -0
  99. package/lib/services/demo/demoTriage.d.ts +65 -0
  100. package/lib/services/demo/demoTriage.d.ts.map +1 -0
  101. package/lib/services/demo/demoTriage.js +130 -0
  102. package/lib/services/demo/githubOAuth.d.ts +78 -0
  103. package/lib/services/demo/githubOAuth.d.ts.map +1 -0
  104. package/lib/services/demo/githubOAuth.js +209 -0
  105. package/lib/services/demo/githubOAuthState.d.ts +10 -0
  106. package/lib/services/demo/githubOAuthState.d.ts.map +1 -0
  107. package/lib/services/demo/githubOAuthState.js +63 -0
  108. package/lib/services/demo/index.d.ts +24 -0
  109. package/lib/services/demo/index.d.ts.map +1 -0
  110. package/lib/services/demo/index.js +39 -0
  111. package/lib/services/firestore.service.d.ts +11 -0
  112. package/lib/services/firestore.service.d.ts.map +1 -1
  113. package/lib/services/firestore.service.js +24 -0
  114. package/lib/services/index.d.ts +2 -0
  115. package/lib/services/index.d.ts.map +1 -1
  116. package/lib/services/index.js +2 -0
  117. package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
  118. package/lib/services/tts/providers/elevenlabs.service.js +16 -8
  119. package/lib/utils/index.d.ts +1 -0
  120. package/lib/utils/index.d.ts.map +1 -1
  121. package/lib/utils/index.js +1 -0
  122. package/lib/utils/outreachTemplate.d.ts +46 -0
  123. package/lib/utils/outreachTemplate.d.ts.map +1 -0
  124. package/lib/utils/outreachTemplate.js +174 -0
  125. package/package.json +1 -1
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeDemoScript = writeDemoScript;
4
+ exports.normalizeDemoScript = normalizeDemoScript;
5
+ const zod_1 = require("zod");
6
+ const llmCallerRegistry_1 = require("../agent/llmCallerRegistry");
7
+ const modelRouter_1 = require("../agent/modelRouter");
8
+ const demoScriptPrompt_1 = require("./demoScriptPrompt");
9
+ /**
10
+ * DemoSpec narration writer — the "script" stage of the SaaS Demo Video pipeline
11
+ * (plan Phase 5 / §4 stage 4 / §6).
12
+ *
13
+ * ONE Claude `.structured()` call turns the already-rendered DemoSpec timeline
14
+ * into a per-scene, timeline-synced narration script. Because the scene durations
15
+ * are fixed by the time this runs, every segment's `startMs` is deterministic
16
+ * (cumulative scene offset) and its window is the scene's own duration — no
17
+ * recording analysis, no timestamp guessing. The voice stage (worker) then
18
+ * TTS-synthesises each segment and speed-fits it to the window.
19
+ *
20
+ * Mirrors `authorDemoSpec`: provider-agnostic (injected `LlmCaller`),
21
+ * model-routed, hardened by a deterministic normalize pass that enforces the
22
+ * invariants the prompt can only request (valid sceneIds, one segment per scene,
23
+ * timeline order + offsets).
24
+ */
25
+ /** LLM output contract — the model returns raw per-scene lines; we compute timing. */
26
+ const DemoScriptDraftSchema = zod_1.z.object({
27
+ segments: zod_1.z
28
+ .array(zod_1.z.object({
29
+ /** Must match a DemoScene.id from the spec. */
30
+ sceneId: zod_1.z.string(),
31
+ /** Spoken narration for the scene (empty ⇒ scene left silent). */
32
+ text: zod_1.z.string(),
33
+ }))
34
+ .default([]),
35
+ });
36
+ /**
37
+ * Write a timeline-synced narration script for a rendered DemoSpec. Pure
38
+ * orchestration: build the per-scene briefs, call `.structured()`, then normalize
39
+ * to concrete `IDemoVideoScriptSegment[]` (compute offsets, drop unknown/empty
40
+ * scenes, dedupe, order). Returns an empty `segments` list (not a throw) when the
41
+ * model narrates nothing — the caller decides whether that means "silent final".
42
+ */
43
+ async function writeDemoScript(spec, deps = {}) {
44
+ const llm = deps.llm ?? (0, llmCallerRegistry_1.getLlmCaller)();
45
+ const router = deps.router ?? new modelRouter_1.ModelRouter();
46
+ const model = router.pickFor("planner", { premium: deps.premium });
47
+ const fallbackModel = router.fallbackFor("planner");
48
+ const briefs = (0, demoScriptPrompt_1.buildDemoScriptBriefs)(spec);
49
+ const system = (0, demoScriptPrompt_1.buildDemoScriptSystemPrompt)();
50
+ const userText = (0, demoScriptPrompt_1.buildDemoScriptUserText)(spec, briefs);
51
+ const result = await llm.structured({
52
+ model,
53
+ fallbackModel,
54
+ messages: [
55
+ { role: "system", content: system },
56
+ { role: "user", content: userText },
57
+ ],
58
+ schema: DemoScriptDraftSchema,
59
+ schemaName: "DemoScript",
60
+ temperature: deps.temperature ?? 0.6,
61
+ maxTokens: deps.maxTokens ?? 2000,
62
+ });
63
+ const draft = DemoScriptDraftSchema.parse(result.data);
64
+ let { segments, adjustments } = normalizeDemoScript(spec, draft.segments);
65
+ let usedHintFallback = false;
66
+ // Robustness: the dedicated writer has been observed to return ZERO usable
67
+ // segments (empty array, or ids that all drop) even when the author gave a
68
+ // per-scene `narrationHint`. For a voiceover/captions video that silently
69
+ // ships a SILENT final (the worker skips voice/mux when segments is empty) —
70
+ // a whiff on one LLM call shouldn't strip a video's intended narration. Fall
71
+ // back to the authored hints (already scene-appropriate spoken intent) run
72
+ // through the same normalize path. Music-led videos are left silent as before.
73
+ if (segments.length === 0 && spec.style.audioIntent !== "music") {
74
+ const hintSegments = spec.scenes
75
+ .filter((s) => (s.narrationHint ?? "").trim().length > 0)
76
+ .map((s) => ({ sceneId: s.id, text: s.narrationHint.trim() }));
77
+ if (hintSegments.length > 0) {
78
+ const fromHints = normalizeDemoScript(spec, hintSegments);
79
+ segments = fromHints.segments;
80
+ adjustments = [
81
+ ...adjustments,
82
+ `script writer returned no usable segments — fell back to ${segments.length} authored narrationHint(s)`,
83
+ ...fromHints.adjustments,
84
+ ];
85
+ usedHintFallback = segments.length > 0;
86
+ }
87
+ }
88
+ return {
89
+ segments,
90
+ adjustments,
91
+ modelUsed: result.usedFallback && fallbackModel ? fallbackModel : model,
92
+ usedFallback: result.usedFallback,
93
+ usedHintFallback,
94
+ usage: result.usage,
95
+ };
96
+ }
97
+ /** Lowercase + strip non-alphanumerics — collapses id/type spelling drift. */
98
+ function normalizeIdKey(value) {
99
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
100
+ }
101
+ /**
102
+ * Deterministic guarantees the prompt can only request:
103
+ * - resolve each segment to a REAL scene (drop truly unknown sceneIds),
104
+ * - one segment per scene (keep the first; drop later duplicates),
105
+ * - drop blank narration,
106
+ * - stamp `startMs` (cumulative scene offset) + `durationMs` (the scene window;
107
+ * the voice stage overwrites `durationMs` with the actual spoken length),
108
+ * - emit in timeline order.
109
+ *
110
+ * `sceneId` resolution is tolerant by design. The script LLM is a separate call
111
+ * from the planner and has been observed to echo the scene *type* (`logoReveal`,
112
+ * `problem`, …) or a punctuation-drifted slug instead of the planner's exact
113
+ * `scene.id`. Rather than drop every segment (which silently ships a captionless,
114
+ * voiceover-less video), we resolve a raw id in three tiers: exact id → id with
115
+ * spelling drift normalized → the scene *type* when that type occurs exactly once
116
+ * in the timeline (so the mapping is unambiguous). Only a raw id that matches
117
+ * none of these is dropped. Each non-exact resolution is recorded as an
118
+ * adjustment for observability.
119
+ */
120
+ function normalizeDemoScript(spec, rawSegments) {
121
+ const adjustments = [];
122
+ // Scene id → { startMs, durationMs, order } by walking the timeline once, plus
123
+ // fallback lookup tables (normalized id, and type→id only when the type is
124
+ // unique so the resolution is unambiguous).
125
+ const sceneWindows = new Map();
126
+ const byNormalizedId = new Map();
127
+ const typeCount = new Map();
128
+ const firstIdOfType = new Map();
129
+ let offset = 0;
130
+ spec.scenes.forEach((scene, order) => {
131
+ sceneWindows.set(scene.id, { startMs: offset, durationMs: scene.durationMs, order });
132
+ offset += scene.durationMs;
133
+ byNormalizedId.set(normalizeIdKey(scene.id), scene.id);
134
+ typeCount.set(scene.type, (typeCount.get(scene.type) ?? 0) + 1);
135
+ if (!firstIdOfType.has(scene.type))
136
+ firstIdOfType.set(scene.type, scene.id);
137
+ });
138
+ // type (and normalized type) → id, only for types that appear exactly once.
139
+ const uniqueTypeToId = new Map();
140
+ for (const [type, count] of typeCount) {
141
+ if (count !== 1)
142
+ continue;
143
+ const id = firstIdOfType.get(type);
144
+ if (!id)
145
+ continue;
146
+ uniqueTypeToId.set(type, id);
147
+ uniqueTypeToId.set(normalizeIdKey(type), id);
148
+ }
149
+ /** Resolve a raw sceneId to a real scene id, or null if unresolvable. */
150
+ const resolve = (rawId) => {
151
+ if (sceneWindows.has(rawId))
152
+ return { id: rawId };
153
+ const key = normalizeIdKey(rawId);
154
+ const byId = byNormalizedId.get(key);
155
+ if (byId && byId !== rawId) {
156
+ return { id: byId, note: `resolved sceneId "${rawId}" → "${byId}" (spelling drift)` };
157
+ }
158
+ const byType = uniqueTypeToId.get(rawId) ?? uniqueTypeToId.get(key);
159
+ if (byType) {
160
+ return { id: byType, note: `resolved sceneId "${rawId}" → "${byType}" (matched unique scene type)` };
161
+ }
162
+ return null;
163
+ };
164
+ const seen = new Set();
165
+ const segments = [];
166
+ for (const raw of rawSegments) {
167
+ const text = (raw.text ?? "").trim();
168
+ const resolved = resolve(raw.sceneId);
169
+ if (!resolved) {
170
+ adjustments.push(`dropped segment for unknown sceneId "${raw.sceneId}"`);
171
+ continue;
172
+ }
173
+ const window = sceneWindows.get(resolved.id);
174
+ if (!window) {
175
+ // Unreachable (resolve only returns known ids) but keeps the type honest.
176
+ adjustments.push(`dropped segment for unknown sceneId "${raw.sceneId}"`);
177
+ continue;
178
+ }
179
+ if (!text) {
180
+ // An explicit empty line = "leave this scene silent"; not an error.
181
+ continue;
182
+ }
183
+ if (seen.has(resolved.id)) {
184
+ adjustments.push(`dropped duplicate segment for sceneId "${resolved.id}"`);
185
+ continue;
186
+ }
187
+ seen.add(resolved.id);
188
+ if (resolved.note)
189
+ adjustments.push(resolved.note);
190
+ // Hard-enforce the spoken-word budget the prompt only *asked* for: an
191
+ // over-budget line (the writer overshoots ~2×) is deterministically trimmed
192
+ // to fit the scene window, so the synthesized voice never overruns into the
193
+ // next scene (the overlapping-voices defect) or gets clipped/rushed by the
194
+ // voice stage. This is the correct-by-construction half of the no-overlap
195
+ // guarantee — the voice-stage atempo net now only ever handles minor residue.
196
+ const budget = (0, demoScriptPrompt_1.scriptWordBudget)(window.durationMs);
197
+ const fitted = (0, demoScriptPrompt_1.fitNarrationToBudget)(text, budget);
198
+ if (fitted.trimmed) {
199
+ adjustments.push(`trimmed narration for "${resolved.id}" to fit its ${Math.round(window.durationMs / 1000)}s window (${(0, demoScriptPrompt_1.wordCount)(text)}→${(0, demoScriptPrompt_1.wordCount)(fitted.text)} words, budget ${budget})`);
200
+ }
201
+ segments.push({
202
+ sceneId: resolved.id,
203
+ text: fitted.text,
204
+ startMs: window.startMs,
205
+ durationMs: window.durationMs,
206
+ });
207
+ }
208
+ // Timeline order — the model is asked for order but we don't rely on it.
209
+ segments.sort((a, b) => (sceneWindows.get(a.sceneId).order - sceneWindows.get(b.sceneId).order));
210
+ return { segments, adjustments };
211
+ }
@@ -0,0 +1,65 @@
1
+ import { LlmCaller } from "../agent/llmCaller";
2
+ import { ModelChoice, ModelRouter } from "../agent/modelRouter";
3
+ import { DemoTriageResult } from "../../schemas/demoQuestion.schema";
4
+ /**
5
+ * SaaS Demo Video Generator — DYNAMIC clarification triage (plan §clarification).
6
+ *
7
+ * The wizard's fixed §2.6 style/pace questions cover the GENERIC axes. This adds
8
+ * the PRODUCT-SPECIFIC layer the user asked for: after the initial read of the
9
+ * website + the details they gave, a cheap Haiku pass decides whether a few
10
+ * targeted questions would MATERIALLY improve the video — and, only if so, what to
11
+ * ask. A clear, complete brief returns `ask:false` (no questions, just generate).
12
+ *
13
+ * ONE contract (`DemoQuestion`) powers BOTH surfaces:
14
+ * - PRE-GEN: the /triage endpoint calls this before any spend; the wizard renders
15
+ * the questions inline; answers ride the create call as `clarifyAnswers`.
16
+ * - MID-RUN: the worker can call this at an ambiguity point and pause.
17
+ *
18
+ * Fail-soft everywhere: any error / unconfigured LLM ⇒ `{ ask:false }`, so
19
+ * clarification never blocks generation. Uncharged (a Haiku call over a short
20
+ * brief is negligible, exactly like demoPrefill).
21
+ */
22
+ /** What we know about the product at triage time (from the wizard + autofill). */
23
+ export interface DemoTriageContext {
24
+ productName: string;
25
+ tagline?: string;
26
+ description?: string;
27
+ audience?: string;
28
+ websiteUrl?: string;
29
+ /** Presence of each input source — lets triage ask to fill a gap. */
30
+ hasWebsite: boolean;
31
+ hasGithub: boolean;
32
+ hasScreenshots: boolean;
33
+ /** Featured media the user attached (drives "how should we use this clip?" questions). */
34
+ mediaAssets?: Array<{
35
+ kind: "image" | "video";
36
+ label?: string;
37
+ }>;
38
+ /** Features the user already listed to highlight. */
39
+ features?: string[];
40
+ /**
41
+ * Which fixed §2.6 axes the user ALREADY pinned (true = pinned, not "decide for
42
+ * me"). Triage must NOT re-ask a pinned axis — it complements, never duplicates,
43
+ * the static wizard.
44
+ */
45
+ pinned?: Partial<Record<"visualStyle" | "pace" | "targetLength" | "narrative" | "aspect" | "colorMood" | "audioIntent", boolean>>;
46
+ /** Optional extra page/summary signal (e.g. from the gather stage) to ground questions. */
47
+ pageSummary?: string;
48
+ }
49
+ export interface DemoTriageDeps {
50
+ llm?: LlmCaller;
51
+ router?: ModelRouter;
52
+ }
53
+ export interface DemoTriageOutcome extends DemoTriageResult {
54
+ modelUsed?: ModelChoice;
55
+ usage?: {
56
+ promptTokens?: number;
57
+ completionTokens?: number;
58
+ };
59
+ }
60
+ /**
61
+ * Run the triage pass. Returns `{ ask, reason, questions }`. Never throws — an
62
+ * unconfigured/erroring LLM degrades to `{ ask:false }` so generation proceeds.
63
+ */
64
+ export declare function triageDemoClarifications(ctx: DemoTriageContext, deps?: DemoTriageDeps): Promise<DemoTriageOutcome>;
65
+ //# sourceMappingURL=demoTriage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demoTriage.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoTriage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAEL,gBAAgB,EAEjB,MAAM,mCAAmC,CAAC;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AAEH,kFAAkF;AAClF,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CACd,MAAM,CACF,aAAa,GACb,MAAM,GACN,cAAc,GACd,WAAW,GACX,QAAQ,GACR,WAAW,GACX,aAAa,EACf,OAAO,CACR,CACF,CAAC;IACF,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D;AAmFD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,iBAAiB,EACtB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAgC5B"}
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.triageDemoClarifications = triageDemoClarifications;
4
+ const llmCallerRegistry_1 = require("../agent/llmCallerRegistry");
5
+ const modelRouter_1 = require("../agent/modelRouter");
6
+ const demoQuestion_schema_1 = require("../../schemas/demoQuestion.schema");
7
+ const NO_QUESTIONS = { ask: false, questions: [] };
8
+ function buildTriageSystem() {
9
+ return [
10
+ "You are the intake specialist for an autonomous SaaS product-demo VIDEO generator.",
11
+ "Given what we already know about a product, decide whether asking the user a FEW",
12
+ "targeted clarifying questions would MATERIALLY improve the generated video.",
13
+ "",
14
+ "Ask ONLY when it genuinely helps. If the brief is already clear and complete,",
15
+ "return ask=false with no questions — never ask for the sake of asking. Most",
16
+ "well-described products need ZERO questions.",
17
+ "",
18
+ "Good reasons to ask (max 3, ideally 1–2 questions):",
19
+ "- The product's core value or primary user flow is ambiguous from what's given.",
20
+ "- The user attached video/image assets but didn't say how to feature them.",
21
+ "- Which specific features to spotlight is unclear and there are clearly several.",
22
+ "- A concrete creative fork that changes the video and isn't already decided",
23
+ " (e.g. 'lead with the problem or the product?', 'show real UI or stylized?').",
24
+ "",
25
+ "NEVER ask:",
26
+ "- Anything already provided or already pinned by the user (you'll be told which).",
27
+ "- The generic style/pace/length/aspect/color/audio axes — a separate UI covers",
28
+ " those; only ask about them if a pinned value conflicts with the product.",
29
+ "- Anything you can reasonably infer yourself — you are meant to be autonomous.",
30
+ "",
31
+ "Question kinds (use the simplest that fits):",
32
+ '- single: pick one option. Fields: kind,id,prompt,help?,options[{value,label,description?}],allowOther?,required?',
33
+ "- multi: pick several. Same as single plus min?/max?.",
34
+ '- toggle: a yes/no. Fields: kind,id,prompt,help?,default,onLabel?,offLabel?.',
35
+ '- text: a short typed answer. Fields: kind,id,prompt,help?,placeholder?,multiline?,maxLength?.',
36
+ "",
37
+ "Rules: give every question a short stable snake_case id. Keep prompts under ~14",
38
+ "words. Provide 2–5 concrete options for single/multi. Mark a question required",
39
+ "only if the video truly can't proceed without it (rare — prefer optional).",
40
+ "Return valid JSON matching the schema.",
41
+ ].join("\n");
42
+ }
43
+ function buildTriageUser(ctx) {
44
+ const lines = [];
45
+ lines.push(`Product name: ${ctx.productName}`);
46
+ if (ctx.tagline)
47
+ lines.push(`Tagline: ${ctx.tagline}`);
48
+ if (ctx.description)
49
+ lines.push(`Description: ${ctx.description}`);
50
+ if (ctx.audience)
51
+ lines.push(`Audience: ${ctx.audience}`);
52
+ if (ctx.websiteUrl)
53
+ lines.push(`Website: ${ctx.websiteUrl}`);
54
+ if (ctx.features?.length)
55
+ lines.push(`Features to highlight: ${ctx.features.join(", ")}`);
56
+ const sources = [];
57
+ if (ctx.hasWebsite)
58
+ sources.push("website");
59
+ if (ctx.hasGithub)
60
+ sources.push("github repo");
61
+ if (ctx.hasScreenshots)
62
+ sources.push("screenshots");
63
+ lines.push(`Input sources available: ${sources.length ? sources.join(", ") : "none beyond the text above"}`);
64
+ if (ctx.mediaAssets?.length) {
65
+ const summary = ctx.mediaAssets
66
+ .map((a) => `${a.kind}${a.label ? ` ("${a.label}")` : ""}`)
67
+ .join(", ");
68
+ lines.push(`Featured media the user attached (must appear IN the video): ${summary}.`, "If it's unclear where/how to use these, that is a good thing to ask about.");
69
+ }
70
+ const pinned = Object.entries(ctx.pinned ?? {})
71
+ .filter(([, v]) => v)
72
+ .map(([k]) => k);
73
+ lines.push(`Already decided by the user (do NOT ask about these): ${pinned.length ? pinned.join(", ") : "none"}.`);
74
+ if (ctx.pageSummary) {
75
+ lines.push("", "Extra signal from the site:", ctx.pageSummary.slice(0, 1500));
76
+ }
77
+ lines.push("", "Decide: ask=true with 1–3 high-value questions, or ask=false if the brief is clear.");
78
+ return lines.join("\n");
79
+ }
80
+ /**
81
+ * Run the triage pass. Returns `{ ask, reason, questions }`. Never throws — an
82
+ * unconfigured/erroring LLM degrades to `{ ask:false }` so generation proceeds.
83
+ */
84
+ async function triageDemoClarifications(ctx, deps = {}) {
85
+ const llm = deps.llm ?? (0, llmCallerRegistry_1.getLlmCaller)();
86
+ const router = deps.router ?? new modelRouter_1.ModelRouter();
87
+ const model = router.pickFor("router");
88
+ const fallbackModel = router.fallbackFor("router");
89
+ try {
90
+ const result = await llm.structured({
91
+ model,
92
+ fallbackModel,
93
+ messages: [
94
+ { role: "system", content: buildTriageSystem() },
95
+ { role: "user", content: buildTriageUser(ctx) },
96
+ ],
97
+ schema: demoQuestion_schema_1.DemoTriageResultSchema,
98
+ schemaName: "DemoTriage",
99
+ temperature: 0.3,
100
+ maxTokens: 900,
101
+ });
102
+ const parsed = demoQuestion_schema_1.DemoTriageResultSchema.parse(result.data);
103
+ // Enforce our own guardrails regardless of what the model returned.
104
+ const questions = parsed.ask ? dedupeAndCap(parsed.questions) : [];
105
+ return {
106
+ ask: questions.length > 0,
107
+ ...(parsed.reason ? { reason: parsed.reason } : {}),
108
+ questions,
109
+ modelUsed: result.usedFallback && fallbackModel ? fallbackModel : model,
110
+ usage: result.usage,
111
+ };
112
+ }
113
+ catch {
114
+ return NO_QUESTIONS;
115
+ }
116
+ }
117
+ /** Drop duplicate ids and cap the set (defense-in-depth over the schema's max). */
118
+ function dedupeAndCap(questions, cap = 3) {
119
+ const seen = new Set();
120
+ const out = [];
121
+ for (const q of questions) {
122
+ if (seen.has(q.id))
123
+ continue;
124
+ seen.add(q.id);
125
+ out.push(q);
126
+ if (out.length >= cap)
127
+ break;
128
+ }
129
+ return out;
130
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * GitHub OAuth App client — the v2 "connect GitHub for private repos" provider
3
+ * (plan §5, Phase 9). VidSpotAI is the OAuth CLIENT; GitHub is the provider. A
4
+ * classic OAuth App 3-legged flow: authorize → exchange code → non-expiring
5
+ * access token. We request `repo` (private-repo read) + `read:user` (identity).
6
+ *
7
+ * DORMANT until provisioned: everything gates on `GITHUB_OAUTH_CLIENT_ID` +
8
+ * `GITHUB_OAUTH_CLIENT_SECRET`. Until an app is registered and those land in
9
+ * Doppler, `isGithubOAuthConfigured()` is false and the connect endpoint 503s —
10
+ * exactly like the rest of the demo feature ships behind a flag.
11
+ *
12
+ * Env:
13
+ * GITHUB_OAUTH_CLIENT_ID — the OAuth App client id
14
+ * GITHUB_OAUTH_CLIENT_SECRET — the OAuth App client secret
15
+ * GITHUB_OAUTH_REDIRECT_URI — must EXACTLY match the app's Authorization
16
+ * callback URL, e.g.
17
+ * https://<api-host>/v1/demo-video/github/callback
18
+ */
19
+ /**
20
+ * Scopes we request. `repo` is the classic scope that grants private-repo
21
+ * access (GitHub OAuth Apps have no read-only private-repo scope — that
22
+ * granularity requires a GitHub App installation, a much larger integration);
23
+ * `read:user` is identity only. Documented as broad in the plan §10 risks.
24
+ */
25
+ export declare const GITHUB_OAUTH_SCOPES: readonly ["read:user", "repo"];
26
+ /** True once both client id + secret are configured. Gate the connect flow on this. */
27
+ export declare function isGithubOAuthConfigured(): boolean;
28
+ /** The registered callback URL. Must byte-match the OAuth App's setting. */
29
+ export declare function githubRedirectUri(): string;
30
+ /** Build the GitHub authorize URL the client opens to start consent. */
31
+ export declare function buildGithubAuthUrl(state: string): string;
32
+ export interface GithubTokenResult {
33
+ accessToken: string;
34
+ /** Scopes GitHub actually granted (may be a subset of requested). */
35
+ scopes: string[];
36
+ }
37
+ /**
38
+ * Exchange the authorization `code` for an access token. Throws on any
39
+ * error response (GitHub returns 200 with an `error` field on failure).
40
+ */
41
+ export declare function exchangeGithubCode(code: string): Promise<GithubTokenResult>;
42
+ export interface GithubUser {
43
+ id: number;
44
+ login: string;
45
+ avatarUrl?: string;
46
+ }
47
+ /** Fetch the authenticated user's identity for display. Throws on failure. */
48
+ export declare function fetchGithubUser(accessToken: string): Promise<GithubUser>;
49
+ export interface GithubRepoSummary {
50
+ /** "owner/name" — what we store as the repo URL basis. */
51
+ fullName: string;
52
+ name: string;
53
+ owner: string;
54
+ private: boolean;
55
+ description?: string;
56
+ /** Default branch (e.g. "main") — handy for the contents API later. */
57
+ defaultBranch: string;
58
+ htmlUrl: string;
59
+ /** ISO timestamp of last push, for "sort by recently active" in the picker. */
60
+ updatedAt?: string;
61
+ }
62
+ /**
63
+ * List the connected user's repositories for the repo picker (plan §9 UX).
64
+ * Mirrors `fetchGithubUser`'s auth headers against `GET /user/repos`. Pulls up
65
+ * to `maxPages` pages of 100, sorted by most-recently pushed, spanning repos the
66
+ * user owns / collaborates on / can see via org membership. Best-effort per page:
67
+ * a mid-pagination failure returns what we have rather than throwing.
68
+ */
69
+ export declare function listGithubRepos(accessToken: string, opts?: {
70
+ maxPages?: number;
71
+ }): Promise<GithubRepoSummary[]>;
72
+ /**
73
+ * Best-effort provider-side revoke of a token grant (called on disconnect).
74
+ * Uses the OAuth App's client credentials via Basic auth. Non-fatal — returns
75
+ * false instead of throwing so a disconnect always completes locally.
76
+ */
77
+ export declare function revokeGithubToken(accessToken: string): Promise<boolean>;
78
+ //# sourceMappingURL=githubOAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"githubOAuth.d.ts","sourceRoot":"","sources":["../../../src/services/demo/githubOAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,gCAAiC,CAAC;AAIlE,uFAAuF;AACvF,wBAAgB,uBAAuB,IAAI,OAAO,CAKjD;AAcD,4EAA4E;AAC5E,wBAAgB,iBAAiB,IAAI,MAAM,CAI1C;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUxD;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAoC5B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,8EAA8E;AAC9E,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAwB9E;AAED,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA8C9B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsB7E"}