@mevdragon/vidfarm-devcli 0.2.2 → 0.2.4

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/src/app.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { readFileSync, statSync } from "node:fs";
2
3
  import path from "node:path";
3
4
  import { Hono } from "hono";
@@ -60,6 +61,11 @@ const settingsProfileFormSchema = z.object({
60
61
  groupchat_url: z.union([z.literal(""), z.string().url()]).optional(),
61
62
  flockposter_api_key: z.string().max(1000).optional()
62
63
  });
64
+ const developerPreviewPresignSchema = z.object({
65
+ file_name: z.string().min(1).max(255),
66
+ content_type: z.string().trim().min(1).max(255).optional(),
67
+ directory: z.string().trim().max(500).optional()
68
+ });
63
69
  const listJobsQuerySchema = z.object({
64
70
  tracer: z.string().min(1).optional(),
65
71
  start_time: z.string().min(1).optional(),
@@ -195,6 +201,56 @@ function isAllowedAttachment(fileName, contentType) {
195
201
  || contentType === "text/plain"
196
202
  || contentType === "text/markdown";
197
203
  }
204
+ function sanitizeStorageSubpath(value) {
205
+ if (!value) {
206
+ return "";
207
+ }
208
+ return value
209
+ .split("/")
210
+ .map((segment) => segment.trim().replace(/[^\w.-]+/g, "_"))
211
+ .filter((segment) => segment && segment !== "." && segment !== "..")
212
+ .join("/");
213
+ }
214
+ function inferAttachmentContentType(fileName) {
215
+ switch (path.extname(fileName).toLowerCase()) {
216
+ case ".png":
217
+ return "image/png";
218
+ case ".gif":
219
+ return "image/gif";
220
+ case ".jpg":
221
+ case ".jpeg":
222
+ return "image/jpeg";
223
+ case ".webp":
224
+ return "image/webp";
225
+ case ".svg":
226
+ return "image/svg+xml";
227
+ case ".mp4":
228
+ case ".m4v":
229
+ return "video/mp4";
230
+ case ".mov":
231
+ return "video/quicktime";
232
+ case ".webm":
233
+ return "video/webm";
234
+ case ".mp3":
235
+ return "audio/mpeg";
236
+ case ".wav":
237
+ return "audio/wav";
238
+ case ".m4a":
239
+ return "audio/mp4";
240
+ case ".aac":
241
+ return "audio/aac";
242
+ case ".ogg":
243
+ return "audio/ogg";
244
+ case ".pdf":
245
+ return "application/pdf";
246
+ case ".md":
247
+ return "text/markdown; charset=utf-8";
248
+ case ".txt":
249
+ return "text/plain; charset=utf-8";
250
+ default:
251
+ return "application/octet-stream";
252
+ }
253
+ }
198
254
  function isFormFile(value) {
199
255
  return Boolean(value
200
256
  && typeof value === "object"
@@ -563,7 +619,7 @@ function requireCustomer(c) {
563
619
  }
564
620
  const requireAuth = async (c, next) => {
565
621
  try {
566
- const customer = auth.authenticate(c.req.header("vidfarm-user-id"), c.req.header("vidfarm-api-key"));
622
+ const customer = auth.authenticate(c.req.header("vidfarm-api-key"));
567
623
  c.set("customer", customer);
568
624
  await next();
569
625
  }
@@ -877,7 +933,7 @@ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId`, (c) => {
877
933
  }
878
934
  return c.json(serializeJob(job));
879
935
  });
880
- app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/logs`, (c) => {
936
+ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/logs`, async (c) => {
881
937
  const customer = requireCustomer(c);
882
938
  const template = templateRegistry.get(c.req.param("templateId"));
883
939
  if (!template) {
@@ -892,7 +948,7 @@ app.get(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/logs`, (c) => {
892
948
  end_time: c.req.query("end_time"),
893
949
  limit: c.req.query("limit")
894
950
  });
895
- const logs = jobs.listLogs({
951
+ const logs = await jobs.listLogs({
896
952
  jobId: job.id,
897
953
  startTime: query.start_time,
898
954
  endTime: query.end_time,
@@ -914,6 +970,39 @@ app.post(`${TEMPLATES_PREFIX}/:templateId/jobs/:jobId/cancel`, (c) => {
914
970
  return c.json({ ok: true, job_id: job.id, status: "cancelled" });
915
971
  });
916
972
  app.get(`${USER_PREFIX}/me`, (c) => c.json({ customer: requireCustomer(c) }));
973
+ app.post(`${USER_PREFIX}/me/developer/preview-media/presign`, async (c) => {
974
+ try {
975
+ const customer = requireCustomer(c);
976
+ const body = developerPreviewPresignSchema.parse(await c.req.json());
977
+ const fileName = sanitizeFileName(body.file_name);
978
+ const contentType = body.content_type?.trim() || inferAttachmentContentType(fileName);
979
+ if (!isAllowedAttachment(fileName, contentType)) {
980
+ return c.json({ error: `Unsupported preview media type: ${fileName}` }, 400);
981
+ }
982
+ const directory = sanitizeStorageSubpath(body.directory);
983
+ const storageKey = storage.developerScopedKey(customer.id, directory, randomUUID(), fileName);
984
+ const upload = await storage.createWriteUrl(storageKey, {
985
+ contentType,
986
+ publicRead: false,
987
+ expiresIn: 3600
988
+ });
989
+ return c.json({
990
+ file_name: fileName,
991
+ content_type: contentType,
992
+ storage_key: storageKey,
993
+ preview_media_url: buildAbsoluteUrl(c, `/template-media?key=${encodeURIComponent(storageKey)}`),
994
+ upload: {
995
+ method: upload.method,
996
+ url: upload.url,
997
+ headers: upload.headers,
998
+ expires_in_seconds: upload.expiresIn
999
+ }
1000
+ });
1001
+ }
1002
+ catch (error) {
1003
+ return c.json({ error: error instanceof Error ? error.message : "Unable to create preview upload URL." }, 400);
1004
+ }
1005
+ });
917
1006
  app.get(`${USER_PREFIX}/me/jobs`, (c) => {
918
1007
  const query = listJobsQuerySchema.parse({
919
1008
  template_id: c.req.query("template_id"),
@@ -969,8 +1058,8 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
969
1058
  slug_id: z.string().min(3).regex(/^[a-z0-9_]+$/i, "slug_id must contain only letters, numbers, and underscores."),
970
1059
  repo_url: z.string().url(),
971
1060
  branch: z.string().min(1).default("production"),
972
- template_module_path: z.string().min(1),
973
- skill_path: z.string().min(1).default("SKILL.md"),
1061
+ template_module_path: z.string().regex(/(^|\/)vidfarm_template_[^/]+\/src\/template\.ts$/i, "template_module_path must point at a template entrypoint inside a folder that starts with vidfarm_template_, for example templates/vidfarm_template_example/src/template.ts."),
1062
+ skill_path: z.string().min(1).optional(),
974
1063
  install_command: z.string().min(1).default("npm install"),
975
1064
  build_command: z.string().min(1).default("npm run build")
976
1065
  }).parse(await c.req.json());