@mevdragon/vidfarm-devcli 0.18.1 → 0.19.1
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/.agents/skills/editor-capabilities/SKILL.md +166 -0
- package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
- package/.agents/skills/vidfarm-media/SKILL.md +2 -0
- package/SKILL.director.md +174 -16
- package/SKILL.platform.md +7 -3
- package/demo/dist/app.css +1 -1
- package/demo/dist/app.js +79 -75
- package/dist/src/account-pages-legacy.js +1 -1
- package/dist/src/app.js +1694 -217
- package/dist/src/cli.js +1365 -105
- package/dist/src/config.js +8 -0
- package/dist/src/devcli/clips.js +3 -0
- package/dist/src/devcli/composition-edit.js +401 -10
- package/dist/src/devcli/local-backend.js +123 -0
- package/dist/src/devcli/migrate-local.js +140 -0
- package/dist/src/devcli/sync.js +311 -0
- package/dist/src/devcli/timeline-edit.js +208 -1
- package/dist/src/editor-chat.js +37 -18
- package/dist/src/frontend/file-directory.js +271 -20
- package/dist/src/frontend/homepage-view.js +3 -3
- package/dist/src/frontend/template-editor-chat.js +6 -3
- package/dist/src/page-shell.js +1 -1
- package/dist/src/primitive-context.js +31 -2
- package/dist/src/primitive-registry.js +409 -4
- package/dist/src/reskin/chat-page.js +103 -15
- package/dist/src/reskin/discover-page.js +247 -10
- package/dist/src/reskin/document.js +147 -10
- package/dist/src/reskin/inpaint-clipper-page.js +649 -0
- package/dist/src/reskin/inpaint-page.js +2459 -452
- package/dist/src/reskin/inpaint-video-page.js +1339 -0
- package/dist/src/reskin/library-page.js +324 -82
- package/dist/src/reskin/portfolio-page.js +687 -0
- package/dist/src/reskin/theme.js +36 -11
- package/dist/src/services/billing.js +4 -0
- package/dist/src/services/clip-curation/hunt.js +2 -0
- package/dist/src/services/clip-records.js +28 -0
- package/dist/src/services/file-directory.js +6 -3
- package/dist/src/services/hyperframes.js +535 -86
- package/dist/src/services/serverless-jobs.js +3 -1
- package/dist/src/services/serverless-records.js +43 -0
- package/dist/src/services/storage.js +24 -2
- package/dist/src/template-editor-pages.js +1 -1
- package/package.json +1 -1
- package/public/assets/file-directory-app.js +2 -2
- package/public/assets/homepage-client-app.js +1 -1
- package/public/assets/page-runtime-client-app.js +2 -2
- package/public/assets/placeholders/scene-placeholder.png +0 -0
package/dist/src/reskin/theme.js
CHANGED
|
@@ -84,7 +84,11 @@ export const RESKIN_CSS = `
|
|
|
84
84
|
/* layout */
|
|
85
85
|
.rk-container{width:min(var(--rk-container),100%);margin:0 auto;padding:0 var(--rk-gutter)}
|
|
86
86
|
.rk-container-wide{width:min(85rem,100%);margin:0 auto;padding:0 var(--rk-gutter)}
|
|
87
|
-
.rk-
|
|
87
|
+
/* vertical-only so it never clobbers the horizontal gutter that .rk-container
|
|
88
|
+
/ .rk-container-wide set via padding-left/right (a full padding shorthand
|
|
89
|
+
here would zero the side gutter, leaving content flush to the viewport
|
|
90
|
+
edges, most visible on mobile) */
|
|
91
|
+
.rk-page{padding-top:24px;padding-bottom:80px}
|
|
88
92
|
.rk-page-head{display:grid;gap:4px;margin-bottom:18px}
|
|
89
93
|
.rk-h1{font-size:var(--rk-text-3xl);letter-spacing:-.03em}
|
|
90
94
|
.rk-sub{font-size:var(--rk-text-base);color:var(--rk-text-muted)}
|
|
@@ -116,11 +120,10 @@ export const RESKIN_CSS = `
|
|
|
116
120
|
transition:background var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
|
|
117
121
|
.rk-sidebar-item:hover{background:var(--rk-n-100);color:var(--rk-ink)}
|
|
118
122
|
.rk-sidebar-item.is-active{background:var(--rk-gold-tint);color:var(--rk-ink)}
|
|
119
|
-
/* signed-in identity — subtle, small, pinned to the
|
|
120
|
-
* tell which account you're in at a glance
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.rk-sidebar-account{margin-top:8px;padding:8px 13px 80px;border-top:1px solid var(--rk-border);
|
|
123
|
+
/* signed-in identity — subtle, small, pinned to the very bottom of the sidebar
|
|
124
|
+
* so you can tell which account you're in at a glance. The chat FAB hovers
|
|
125
|
+
* ABOVE this block (see .rk-signed-in .rk-fab) rather than sharing the spot. */
|
|
126
|
+
.rk-sidebar-account{margin-top:8px;padding:8px 13px 2px;border-top:1px solid var(--rk-border);
|
|
124
127
|
color:var(--rk-n-500);line-height:1.3;overflow:hidden}
|
|
125
128
|
.rk-sidebar-account-name{font-size:12px;font-weight:600;color:var(--rk-n-600);
|
|
126
129
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
@@ -135,6 +138,10 @@ export const RESKIN_CSS = `
|
|
|
135
138
|
border:0;cursor:grab;background:var(--rk-gold-500);color:var(--rk-ink);display:grid;place-items:center;
|
|
136
139
|
box-shadow:var(--rk-shadow-lg);touch-action:none;user-select:none;-webkit-user-select:none;
|
|
137
140
|
transition:transform var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease),opacity var(--rk-dur) var(--rk-ease)}
|
|
141
|
+
/* signed in: the name/email block sits at the sidebar foot, so lift the FAB's
|
|
142
|
+
* default resting spot to hover just above it (never overlapping the identity).
|
|
143
|
+
* Only applies to the CSS default — a dragged FAB uses its saved inline top. */
|
|
144
|
+
.rk-signed-in .rk-fab{bottom:calc(84px + env(safe-area-inset-bottom))}
|
|
138
145
|
.rk-fab:hover{transform:translateY(-2px);box-shadow:0 6px 18px -6px rgba(252,185,0,.6),var(--rk-shadow-lg)}
|
|
139
146
|
.rk-fab.is-dragging{cursor:grabbing;transition:none;transform:scale(1.08)}
|
|
140
147
|
.rk-fab.is-open{opacity:0;pointer-events:none;transform:scale(.6)}
|
|
@@ -231,6 +238,12 @@ export const RESKIN_CSS = `
|
|
|
231
238
|
a folder Open button, a per-row "…" kebab menu, and a copy-path toast.
|
|
232
239
|
Reused verbatim by the chat dock (attach mode) and /library/raws (standalone). */
|
|
233
240
|
.rk-dir{display:flex;flex-direction:column;min-height:0;flex:1}
|
|
241
|
+
.rk-dir-space{flex:none;display:inline-flex;gap:2px;margin:2px 12px 8px;padding:2px;
|
|
242
|
+
background:var(--rk-n-100,#f1ede6);border:1px solid var(--rk-border);border-radius:var(--rk-r-full);align-self:flex-start}
|
|
243
|
+
.rk-dir-space-btn{font-family:inherit;font-size:12px;font-weight:600;color:var(--rk-n-500,#7a7266);
|
|
244
|
+
background:transparent;border:0;border-radius:var(--rk-r-full);padding:4px 14px;cursor:pointer;
|
|
245
|
+
transition:background var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
|
|
246
|
+
.rk-dir-space-btn.is-on{background:var(--rk-surface,#fff);color:var(--rk-ink);box-shadow:var(--rk-shadow-sm,0 1px 2px rgba(0,0,0,.06))}
|
|
234
247
|
.rk-dir-search{flex:none;padding:0 12px 8px}
|
|
235
248
|
.rk-dir-search-input{width:100%;font-family:inherit;font-size:12.5px;color:var(--rk-ink);
|
|
236
249
|
background:var(--rk-surface,#fff);border:1px solid var(--rk-border);border-radius:var(--rk-r-full);padding:7px 12px;
|
|
@@ -302,8 +315,12 @@ export const RESKIN_CSS = `
|
|
|
302
315
|
.rk-aichat-tool:hover{background:var(--rk-n-100);color:var(--rk-ink)}
|
|
303
316
|
.rk-aichat-tool svg{width:15px;height:15px}
|
|
304
317
|
.rk-aichat-tool.is-on{background:var(--rk-gold-tint);border-color:var(--rk-gold-600);color:var(--rk-ink)}
|
|
305
|
-
|
|
306
|
-
|
|
318
|
+
/* overflow-x:hidden + a capped single column (minmax(0,1fr), NOT the default
|
|
319
|
+
auto track which grows to content) keep the log scrolling ONLY vertically —
|
|
320
|
+
a wide child (long tool note / URL / file path) can no longer widen the grid
|
|
321
|
+
and force a horizontal scrollbar in the narrow dock column. */
|
|
322
|
+
.rk-aichat-log{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:16px;display:grid;grid-template-columns:minmax(0,1fr);gap:10px;align-content:start;background:var(--rk-n-50)}
|
|
323
|
+
.rk-aichat-msg{max-width:85%;min-width:0;padding:10px 14px;border-radius:var(--rk-r-2xl);font-size:13.5px;line-height:1.5;overflow-wrap:anywhere;word-break:break-word}
|
|
307
324
|
.rk-aichat-msg.is-ai{background:#fff;border:1px solid var(--rk-border);color:var(--rk-text);justify-self:start;
|
|
308
325
|
border-bottom-left-radius:var(--rk-r-sm);box-shadow:var(--rk-shadow-xs)}
|
|
309
326
|
.rk-aichat-msg.is-user{background:var(--rk-gold-tint2);color:var(--rk-ink);justify-self:end;border-bottom-right-radius:var(--rk-r-sm)}
|
|
@@ -320,6 +337,11 @@ export const RESKIN_CSS = `
|
|
|
320
337
|
.rk-aichat-chip-label{max-width:150px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;
|
|
321
338
|
font-size:11.5px;font-weight:600;text-align:left;text-decoration:underline;text-underline-offset:2px;text-decoration-color:transparent}
|
|
322
339
|
.rk-aichat-chip-label:hover{text-decoration-color:currentColor}
|
|
340
|
+
.rk-aichat-chip.is-pending{opacity:.75}
|
|
341
|
+
.rk-aichat-chip.is-pending .rk-aichat-chip-label{text-decoration:none;cursor:default}
|
|
342
|
+
.rk-aichat-chip-spin{width:11px;height:11px;flex:none;border-radius:50%;border:2px solid currentColor;
|
|
343
|
+
border-top-color:transparent;animation:rk-aichat-chip-spin .7s linear infinite}
|
|
344
|
+
@keyframes rk-aichat-chip-spin{to{transform:rotate(360deg)}}
|
|
323
345
|
/* composer = one bordered "text area" box: the textarea fills the top and the
|
|
324
346
|
attach + send controls sit inside it along the bottom row. */
|
|
325
347
|
.rk-aichat-composer{display:flex;flex-direction:column;gap:8px;margin:12px;padding:10px 10px 9px;flex:none;background:#fff;
|
|
@@ -341,8 +363,8 @@ export const RESKIN_CSS = `
|
|
|
341
363
|
.rk-aichat-attach svg{width:18px;height:18px}
|
|
342
364
|
|
|
343
365
|
/* ── live agentic turn: status → actions (tool notes + HTTP cards) → text ── */
|
|
344
|
-
.rk-aichat-turn{display:grid;gap:8px;justify-items:start;width:100
|
|
345
|
-
.rk-aichat-stream{white-space:pre-wrap}
|
|
366
|
+
.rk-aichat-turn{display:grid;gap:8px;justify-items:start;width:100%;min-width:0}
|
|
367
|
+
.rk-aichat-stream{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;max-width:100%}
|
|
346
368
|
.rk-aichat-stream[hidden]{display:none}
|
|
347
369
|
.rk-aichat-actions{display:grid;gap:6px;width:100%}
|
|
348
370
|
.rk-aichat-actions:empty{display:none}
|
|
@@ -356,7 +378,7 @@ export const RESKIN_CSS = `
|
|
|
356
378
|
@keyframes rk-aichat-blink{0%,80%,100%{opacity:.28;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}
|
|
357
379
|
.rk-aichat-toolnote{display:inline-flex;align-items:center;gap:7px;font-size:11.5px;color:var(--rk-text-muted);
|
|
358
380
|
font-family:var(--rk-font-mono);background:#fff;border:1px solid var(--rk-border);
|
|
359
|
-
border-radius:var(--rk-r-lg);padding:5px 10px;width:fit-content}
|
|
381
|
+
border-radius:var(--rk-r-lg);padding:5px 10px;width:fit-content;max-width:100%;overflow-wrap:anywhere;word-break:break-word}
|
|
360
382
|
.rk-aichat-toolnote::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--rk-gold-500);flex:none}
|
|
361
383
|
.rk-aichat-loadmore{width:100%;margin-top:6px;padding:9px 12px;font-size:12.5px;font-weight:600;
|
|
362
384
|
color:var(--rk-text);background:var(--rk-white);border:1px solid var(--rk-border);
|
|
@@ -521,6 +543,9 @@ export const RESKIN_CSS = `
|
|
|
521
543
|
/* horizontal strip: kill the foot border + top offset so the account block
|
|
522
544
|
sits centered in the row instead of showing a stray hairline above it */
|
|
523
545
|
.rk-sidebar-account{padding:8px 13px 2px;border-top:0;margin-top:0}
|
|
546
|
+
/* strip/burger layout: the account block is no longer at the sidebar foot,
|
|
547
|
+
so drop the FAB back to its normal bottom-left resting spot */
|
|
548
|
+
.rk-signed-in .rk-fab{bottom:calc(16px + env(safe-area-inset-bottom))}
|
|
524
549
|
.rk-content{margin-left:0}
|
|
525
550
|
.rk-chat-left .rk-sidebar{visibility:visible}
|
|
526
551
|
}
|
|
@@ -164,6 +164,10 @@ function resolveCostCenterSlug(input) {
|
|
|
164
164
|
|| explicitSlug === "video_download_lambda"
|
|
165
165
|
|| explicitSlug === "video_ingest_lambda"
|
|
166
166
|
|| explicitSlug === "rapidapi_remove_background"
|
|
167
|
+
// Local sharp chroma-key background removal + the 2-step media-overlay
|
|
168
|
+
// primitive (BYOK image gen + chroma key): platform compute/convenience.
|
|
169
|
+
|| explicitSlug === "greenscreen_chroma_key"
|
|
170
|
+
|| explicitSlug === "media_overlay"
|
|
167
171
|
|| explicitSlug === "primitive_media_lambda"
|
|
168
172
|
|| explicitSlug === "job_runner_lambda"
|
|
169
173
|
|| explicitSlug === "step_functions_standard"
|
|
@@ -393,6 +393,8 @@ export function normalizeHuntSpec(raw) {
|
|
|
393
393
|
spec.clips_per_10min = Math.min(200, perTen);
|
|
394
394
|
if (typeof rec.source_url === "string" && rec.source_url.trim())
|
|
395
395
|
spec.source_url = rec.source_url.trim();
|
|
396
|
+
if (typeof rec.folder_path === "string" && rec.folder_path.trim())
|
|
397
|
+
spec.folder_path = rec.folder_path.trim().slice(0, 500);
|
|
396
398
|
return spec;
|
|
397
399
|
}
|
|
398
400
|
// ── misc ────────────────────────────────────────────────────────────────────
|
|
@@ -115,6 +115,34 @@ export class ClipRecordsService {
|
|
|
115
115
|
ExpressionAttributeValues: { ":fp": folderPath }
|
|
116
116
|
}));
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Rename/move a whole Raws folder: re-point every clip whose folder_path is
|
|
120
|
+
* `fromFolder` OR nested beneath it onto `toFolder` (prefix-preserving), so a
|
|
121
|
+
* folder rename carries its subfolders too. Returns the count moved.
|
|
122
|
+
* Paths are compared normalized (trimmed, no leading/trailing slashes).
|
|
123
|
+
*/
|
|
124
|
+
async moveClipsFolder(owner, fromFolder, toFolder) {
|
|
125
|
+
const norm = (v) => String(v ?? "").split("/").map((s) => s.trim()).filter(Boolean).join("/");
|
|
126
|
+
const from = norm(fromFolder);
|
|
127
|
+
const to = norm(toFolder);
|
|
128
|
+
if (!from || from === to)
|
|
129
|
+
return 0;
|
|
130
|
+
const clips = await this.listClips(owner, { limit: 5000 });
|
|
131
|
+
let moved = 0;
|
|
132
|
+
for (const clip of clips) {
|
|
133
|
+
const fp = norm(clip.folder_path);
|
|
134
|
+
let next = null;
|
|
135
|
+
if (fp === from)
|
|
136
|
+
next = to;
|
|
137
|
+
else if (fp.startsWith(from + "/"))
|
|
138
|
+
next = to + fp.slice(from.length);
|
|
139
|
+
if (next != null) {
|
|
140
|
+
await this.updateClipFolder(owner, clip.clip_id, next);
|
|
141
|
+
moved += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return moved;
|
|
145
|
+
}
|
|
118
146
|
// ── Presets ──────────────────────────────────────────────────────────────
|
|
119
147
|
async listPresets(owner) {
|
|
120
148
|
const res = await dynamodb.send(new QueryCommand({
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// /files/<subfolders…>/<name> → My Files attachments (users/<id>/files/…)
|
|
9
9
|
// /temp/<YYYY-MM-DD>/<name> → Temp files (users/<id>/temporary/…)
|
|
10
10
|
// /raws/<subfolders…>/<name> → Raws / clip library (clips/<owner>/…)
|
|
11
|
+
// /projects/<forkId>/<subpath…> → Composition forks (compositions/forks/<forkId>/…)
|
|
11
12
|
//
|
|
12
13
|
// It's a *virtual* layer: physical S3 keys are untouched. The canonical path is
|
|
13
14
|
// what the UI copies, what the chat agent navigates, and what search annotates.
|
|
@@ -17,18 +18,20 @@
|
|
|
17
18
|
// Pure logic, no runtime deps, so both `src/app.ts` (serve) and
|
|
18
19
|
// `infra/lambda/editor-chat.ts` (cloud) import it, and the frontend explorer
|
|
19
20
|
// client mirrors the same constants.
|
|
20
|
-
export const DIRECTORY_ROOT_KEYS = ["files", "temp", "raws"];
|
|
21
|
+
export const DIRECTORY_ROOT_KEYS = ["files", "temp", "raws", "projects", "approved"];
|
|
21
22
|
export const DIRECTORY_ROOTS = [
|
|
22
23
|
{ key: "files", label: "My Files", description: "Durable personal files — brand assets, characters, saved context. Vector-searchable via notes." },
|
|
23
24
|
{ key: "temp", label: "Temp", description: "Scratch space, auto-organized by date (YYYY-MM-DD) and auto-deleted after 30 days." },
|
|
24
|
-
{ key: "raws", label: "Raws", description: "The reusable clip / source-footage library, organized into folders by source." }
|
|
25
|
+
{ key: "raws", label: "Raws", description: "The reusable clip / source-footage library, organized into folders by source." },
|
|
26
|
+
{ key: "projects", label: "Projects", description: "Your video-editing projects — one folder per fork, holding that composition's working files (composition.html/json, cast, media) and saved versions. Read-only: browse to reference or study another fork." },
|
|
27
|
+
{ key: "approved", label: "Approved", description: "Finished, ready-to-post cuts, organized into folders (defaults to a folder per tracer/campaign)." }
|
|
25
28
|
];
|
|
26
29
|
// The attachments backend historically scopes its folders under the internal
|
|
27
30
|
// key "my_files"; the canonical public root name is "files". These two helpers
|
|
28
31
|
// bridge the canonical name and that backend scope so callers never hardcode it.
|
|
29
32
|
export const ATTACHMENTS_FOLDER_SCOPE = "my_files";
|
|
30
33
|
export function isDirectoryRoot(value) {
|
|
31
|
-
return value === "files" || value === "temp" || value === "raws";
|
|
34
|
+
return value === "files" || value === "temp" || value === "raws" || value === "projects" || value === "approved";
|
|
32
35
|
}
|
|
33
36
|
export function directoryRootLabel(root) {
|
|
34
37
|
const info = DIRECTORY_ROOTS.find((r) => r.key === root);
|