videodraft 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -124,7 +124,7 @@ npx videodraft skills install # zero-install: npx fetches the C
124
124
  videodraft skills install # if the CLI is on PATH — auto-detects your installed agents
125
125
  videodraft skills install --agent claude,codex # target specific agents (repeatable/comma; --all for every agent)
126
126
  videodraft skills install --project # into ./.claude/skills for just this repo (else global)
127
- videodraft skills show # print the skill (also: skills show models|examples|pipeline|--all)
127
+ videodraft skills show # print the skill (also: skills show editor|models|examples|pipeline|--all)
128
128
 
129
129
  # Or install straight from the repo — no CLI on PATH needed first:
130
130
  npx -y skills add videodraft-ai/cli -g # vercel-labs skills tool; npx -y skips npx's install prompt; -g = user scope
package/dist/client.d.ts CHANGED
@@ -217,6 +217,12 @@ interface DownloadedFile {
217
217
  url: string;
218
218
  path: string;
219
219
  bytes: number;
220
+ /** Downscaled view copy in <dir>/previews/ (images over the size threshold
221
+ * only). Agents should inspect this instead of the full-res original —
222
+ * every full-res image an agent views is re-sent with each later message
223
+ * of its chat. Best-effort: absent when preview generation failed or was
224
+ * skipped. The original at `path` is always the deliverable. */
225
+ preview?: string;
220
226
  }
221
227
  declare function extFromUrl(url: string, fallback?: string): string;
222
228
  declare function renderTemplate(template: string, vars: {
package/dist/client.js CHANGED
@@ -818,10 +818,136 @@ async function pollGenerations(client, jobIds, options = {}) {
818
818
  }
819
819
 
820
820
  // src/core/download.ts
821
+ import { execFile } from "child_process";
821
822
  import fs2 from "fs";
822
823
  import path2 from "path";
823
824
  import { pipeline } from "stream/promises";
825
+ import { promisify } from "util";
824
826
  import { Readable } from "stream";
827
+ var execFileAsync = promisify(execFile);
828
+ var PREVIEW_SOURCE_RE = /\.(png|jpe?g|webp|tiff?)$/i;
829
+ var PREVIEW_SKIP_BYTES = 500 * 1024;
830
+ var PREVIEW_MAX_DIM = 1536;
831
+ var PREVIEW_JPEG_QUALITY = 85;
832
+ var PREVIEW_CDN_WIDTH = 1080;
833
+ var PREVIEW_EXTS = ["jpg", "webp", "png"];
834
+ function previewPathFor(originalPath, ext = "jpg") {
835
+ return path2.join(
836
+ path2.dirname(originalPath),
837
+ "previews",
838
+ `${path2.basename(originalPath)}.${ext}`
839
+ );
840
+ }
841
+ function invalidatePreviews(originalPath) {
842
+ for (const ext of PREVIEW_EXTS) {
843
+ try {
844
+ fs2.rmSync(previewPathFor(originalPath, ext), { force: true });
845
+ } catch {
846
+ }
847
+ }
848
+ }
849
+ function shouldWritePreview(originalPath, bytes) {
850
+ return PREVIEW_SOURCE_RE.test(originalPath) && bytes >= PREVIEW_SKIP_BYTES;
851
+ }
852
+ function tempPreviewPath(previewPath) {
853
+ return `${previewPath}.tmp-${process.pid}`;
854
+ }
855
+ function commitPreview(tmpPath, previewPath) {
856
+ try {
857
+ if (fs2.existsSync(tmpPath) && fs2.statSync(tmpPath).size > 0) {
858
+ fs2.renameSync(tmpPath, previewPath);
859
+ return true;
860
+ }
861
+ } catch {
862
+ }
863
+ fs2.rmSync(tmpPath, { force: true });
864
+ return false;
865
+ }
866
+ async function writePreviewWithSips(originalPath, previewPath) {
867
+ if (process.platform !== "darwin") return false;
868
+ const tmp = tempPreviewPath(previewPath);
869
+ try {
870
+ await execFileAsync(
871
+ "sips",
872
+ [
873
+ "-Z",
874
+ String(PREVIEW_MAX_DIM),
875
+ "-s",
876
+ "format",
877
+ "jpeg",
878
+ "-s",
879
+ "formatOptions",
880
+ String(PREVIEW_JPEG_QUALITY),
881
+ originalPath,
882
+ "--out",
883
+ tmp
884
+ ],
885
+ { timeout: 1e4 }
886
+ );
887
+ } catch {
888
+ fs2.rmSync(tmp, { force: true });
889
+ return false;
890
+ }
891
+ return commitPreview(tmp, previewPath);
892
+ }
893
+ var cdnPreviewConsecutiveFailures = 0;
894
+ var PREVIEW_CDN_MAX_CONSECUTIVE_FAILURES = 2;
895
+ var PREVIEW_CDN_TIMEOUT_MS = 1e4;
896
+ async function writePreviewFromCdn(sourceUrl, originalPath, fetchImpl) {
897
+ let host;
898
+ try {
899
+ host = new URL(sourceUrl).hostname;
900
+ } catch {
901
+ return void 0;
902
+ }
903
+ if (host !== "cdn.videodraft.ai") return void 0;
904
+ if (cdnPreviewConsecutiveFailures >= PREVIEW_CDN_MAX_CONSECUTIVE_FAILURES) return void 0;
905
+ const variant = `${DEFAULT_BASE_URL}/_next/image?url=${encodeURIComponent(sourceUrl)}&w=${PREVIEW_CDN_WIDTH}&q=75`;
906
+ let res;
907
+ try {
908
+ res = await fetchImpl(variant, {
909
+ headers: { Accept: "image/webp,image/jpeg,image/*" },
910
+ signal: AbortSignal.timeout(PREVIEW_CDN_TIMEOUT_MS)
911
+ });
912
+ } catch (err) {
913
+ cdnPreviewConsecutiveFailures++;
914
+ throw err;
915
+ }
916
+ if (!res.ok || !res.body) {
917
+ cdnPreviewConsecutiveFailures++;
918
+ return void 0;
919
+ }
920
+ cdnPreviewConsecutiveFailures = 0;
921
+ const contentType = res.headers.get("content-type") ?? "";
922
+ const ext = contentType.includes("webp") ? "webp" : contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("png") ? "png" : "webp";
923
+ const previewPath = previewPathFor(originalPath, ext);
924
+ const tmp = tempPreviewPath(previewPath);
925
+ try {
926
+ await pipeline(Readable.fromWeb(res.body), fs2.createWriteStream(tmp));
927
+ } catch {
928
+ fs2.rmSync(tmp, { force: true });
929
+ return void 0;
930
+ }
931
+ return commitPreview(tmp, previewPath) ? previewPath : void 0;
932
+ }
933
+ async function writePreview(originalPath, sourceUrl, bytes, fetchImpl = fetch) {
934
+ if (!PREVIEW_SOURCE_RE.test(originalPath)) return void 0;
935
+ invalidatePreviews(originalPath);
936
+ if (!shouldWritePreview(originalPath, bytes)) return void 0;
937
+ try {
938
+ const jpgPath = previewPathFor(originalPath, "jpg");
939
+ fs2.mkdirSync(path2.dirname(jpgPath), { recursive: true });
940
+ if (await writePreviewWithSips(originalPath, jpgPath).catch(() => false)) {
941
+ return jpgPath;
942
+ }
943
+ const cdnPath = await writePreviewFromCdn(sourceUrl, originalPath, fetchImpl).catch(
944
+ () => void 0
945
+ );
946
+ if (cdnPath) return cdnPath;
947
+ } catch {
948
+ }
949
+ return void 0;
950
+ }
825
951
  function extFromUrl(url, fallback = "bin") {
826
952
  try {
827
953
  const pathname = new URL(url).pathname;
@@ -866,7 +992,10 @@ async function downloadOutputs(urls, template, vars, fetchImpl = fetch) {
866
992
  index: i,
867
993
  ext: extFromUrl(url)
868
994
  });
869
- files.push(await downloadUrl(url, dest, fetchImpl));
995
+ const file = await downloadUrl(url, dest, fetchImpl);
996
+ const preview = await writePreview(file.path, url, file.bytes, fetchImpl);
997
+ if (preview) file.preview = preview;
998
+ files.push(file);
870
999
  }
871
1000
  return files;
872
1001
  }