@neta-art/cohub-cli 10.4.1 → 10.4.2

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.
@@ -6821,20 +6821,20 @@ const CAMERA_PROPERTIES = {
6821
6821
  y: number$1().finite(),
6822
6822
  width: number$1().finite().positive(),
6823
6823
  height: number$1().finite().positive()
6824
- }).strict()])
6824
+ }).strict()]).describe("Item id or world-space rect to fit with 48 px padding; holds until the next focus keyframe.")
6825
6825
  },
6826
6826
  zoom: {
6827
6827
  kind: "number",
6828
- schema: number$1().finite().positive(),
6828
+ schema: number$1().finite().positive().describe("Pixels per world unit; overrides the zoom computed by focus."),
6829
6829
  multiplicative: true
6830
6830
  },
6831
6831
  center: {
6832
6832
  kind: "vec2",
6833
- schema: BoardVec2Schema$1
6833
+ schema: BoardVec2Schema$1.describe("World-space viewport center; overrides focus positioning using the resolved zoom.")
6834
6834
  },
6835
6835
  shake: {
6836
6836
  kind: "number",
6837
- schema: number$1().finite().nonnegative()
6837
+ schema: number$1().finite().nonnegative().describe("Shake amplitude in screen pixels, applied after focus, zoom and center.")
6838
6838
  }
6839
6839
  };
6840
6840
  const ANIMATION_PROPERTIES = { time: {
@@ -7338,6 +7338,86 @@ function parseBoardDocument(input) {
7338
7338
  } : result;
7339
7339
  }
7340
7340
  //#endregion
7341
+ //#region ../protocol/src/board.ts
7342
+ const BOARD_EXTENSION = ".board";
7343
+ const BOARD_MIME_TYPE = "application/json";
7344
+ object({
7345
+ kind: literal("cohub.board.manifest"),
7346
+ version: literal(1),
7347
+ boardId: string().uuid(),
7348
+ title: string().min(1).max(255)
7349
+ });
7350
+ const BOARD_READ_SECTIONS = ["items", "animations"];
7351
+ const rectSchema = object({
7352
+ x: number$1().finite(),
7353
+ y: number$1().finite(),
7354
+ width: number$1().finite().positive(),
7355
+ height: number$1().finite().positive()
7356
+ }).strict();
7357
+ object({
7358
+ only: array(_enum(BOARD_READ_SECTIONS)).optional(),
7359
+ rect: rectSchema.optional(),
7360
+ within: BoardIdSchema$1.optional(),
7361
+ items: array(string().min(1)).max(5e4).optional(),
7362
+ animations: array(string().min(1)).max(1e4).optional(),
7363
+ limit: number$1().int().min(1).max(5e4).optional(),
7364
+ cursor: string().min(1).optional()
7365
+ }).strict().refine((read) => !(read.cursor && (read.rect || read.within)), {
7366
+ path: ["cursor"],
7367
+ message: "cursor pages by id, so it cannot be combined with rect or within"
7368
+ });
7369
+ object({
7370
+ patch: BoardPatchSchema$1,
7371
+ replace: boolean().default(false),
7372
+ cascade: boolean().default(false),
7373
+ dryRun: boolean().default(false),
7374
+ baseVersion: number$1().int().nonnegative().optional(),
7375
+ mutationId: string().min(1).max(160).optional(),
7376
+ clientId: string().min(1).max(160).optional()
7377
+ }).strict();
7378
+ object({
7379
+ path: string().min(1),
7380
+ title: string().min(1).max(255).optional(),
7381
+ document: BoardPatchSchema$1.optional(),
7382
+ mutationId: string().max(160).optional()
7383
+ }).strict();
7384
+ object({
7385
+ before: number$1().int().positive().optional(),
7386
+ limit: number$1().int().min(1).max(500).default(200)
7387
+ }).strict();
7388
+ const commandId = string().min(1).max(160);
7389
+ discriminatedUnion("type", [
7390
+ object({
7391
+ commandId,
7392
+ type: literal("play"),
7393
+ animationId: string().min(1),
7394
+ position: number$1().finite().nonnegative().optional(),
7395
+ timeScale: number$1().finite().positive().max(4).optional(),
7396
+ seed: string().min(1).max(160).optional()
7397
+ }).strict(),
7398
+ object({
7399
+ commandId,
7400
+ type: literal("pause")
7401
+ }).strict(),
7402
+ object({
7403
+ commandId,
7404
+ type: literal("resume")
7405
+ }).strict(),
7406
+ object({
7407
+ commandId,
7408
+ type: literal("seek"),
7409
+ position: number$1().finite().nonnegative()
7410
+ }).strict(),
7411
+ object({
7412
+ commandId,
7413
+ type: literal("next")
7414
+ }).strict(),
7415
+ object({
7416
+ commandId,
7417
+ type: literal("stop")
7418
+ }).strict()
7419
+ ]);
7420
+ //#endregion
7341
7421
  //#region ../protocol/src/board-task.ts
7342
7422
  function artifactScore(artifact) {
7343
7423
  const kind = {
@@ -7374,6 +7454,117 @@ function taskArtifactPreviewUrl(artifact) {
7374
7454
  if (artifact?.type === "video" || artifact?.type === "audio") return artifact.previewUrl;
7375
7455
  }
7376
7456
  //#endregion
7457
+ //#region ../protocol/src/fs/mime.ts
7458
+ const MIME_BY_EXTENSION = {
7459
+ ".txt": "text/plain",
7460
+ ".md": "text/markdown",
7461
+ ".markdown": "text/markdown",
7462
+ ".json": "application/json",
7463
+ ".jsonl": "application/x-ndjson",
7464
+ ".csv": "text/csv",
7465
+ [BOARD_EXTENSION]: BOARD_MIME_TYPE,
7466
+ ".js": "text/javascript",
7467
+ ".mjs": "text/javascript",
7468
+ ".cjs": "text/javascript",
7469
+ ".ts": "text/typescript",
7470
+ ".tsx": "text/tsx",
7471
+ ".jsx": "text/jsx",
7472
+ ".svelte": "text/x-svelte",
7473
+ ".css": "text/css",
7474
+ ".scss": "text/x-scss",
7475
+ ".html": "text/html",
7476
+ ".htm": "text/html",
7477
+ ".xml": "application/xml",
7478
+ ".yaml": "application/yaml",
7479
+ ".yml": "application/yaml",
7480
+ ".toml": "application/toml",
7481
+ ".ini": "text/plain",
7482
+ ".env": "text/plain",
7483
+ ".sh": "text/x-shellscript",
7484
+ ".bash": "text/x-shellscript",
7485
+ ".py": "text/x-python",
7486
+ ".go": "text/x-go",
7487
+ ".rs": "text/x-rust",
7488
+ ".java": "text/x-java-source",
7489
+ ".c": "text/x-c",
7490
+ ".h": "text/x-c",
7491
+ ".cpp": "text/x-c++src",
7492
+ ".hpp": "text/x-c++hdr",
7493
+ ".sql": "application/sql",
7494
+ ".svg": "image/svg+xml",
7495
+ ".png": "image/png",
7496
+ ".jpg": "image/jpeg",
7497
+ ".jpeg": "image/jpeg",
7498
+ ".gif": "image/gif",
7499
+ ".webp": "image/webp",
7500
+ ".avif": "image/avif",
7501
+ ".bmp": "image/bmp",
7502
+ ".ico": "image/x-icon",
7503
+ ".mp4": "video/mp4",
7504
+ ".webm": "video/webm",
7505
+ ".mov": "video/quicktime",
7506
+ ".m4v": "video/x-m4v",
7507
+ ".ogv": "video/ogg",
7508
+ ".avi": "video/x-msvideo",
7509
+ ".mp3": "audio/mpeg",
7510
+ ".wav": "audio/wav",
7511
+ ".ogg": "audio/ogg",
7512
+ ".m4a": "audio/mp4",
7513
+ ".aac": "audio/aac",
7514
+ ".flac": "audio/flac",
7515
+ ".opus": "audio/ogg",
7516
+ ".glb": "model/gltf-binary",
7517
+ ".gltf": "model/gltf+json",
7518
+ ".stl": "model/stl",
7519
+ ".obj": "model/obj",
7520
+ ".3mf": "model/3mf",
7521
+ ".usdz": "model/vnd.usdz+zip",
7522
+ ".mtl": "text/plain",
7523
+ ".pdf": "application/pdf",
7524
+ ".wasm": "application/wasm",
7525
+ ".woff": "font/woff",
7526
+ ".woff2": "font/woff2",
7527
+ ".zip": "application/zip",
7528
+ ".gz": "application/gzip",
7529
+ ".tar": "application/x-tar",
7530
+ ".rar": "application/vnd.rar",
7531
+ ".7z": "application/x-7z-compressed",
7532
+ ".exe": "application/x-msdownload",
7533
+ ".dmg": "application/x-apple-diskimage",
7534
+ ".deb": "application/vnd.debian.binary-package",
7535
+ ".rpm": "application/x-rpm"
7536
+ };
7537
+ const MIME_BY_FILENAME = {
7538
+ dockerfile: "text/x-dockerfile",
7539
+ makefile: "text/x-makefile"
7540
+ };
7541
+ function mimeTypeForPath(path) {
7542
+ const name = (path.split(/[\\/]/).pop() ?? "").toLowerCase();
7543
+ const byName = Object.hasOwn(MIME_BY_FILENAME, name) ? MIME_BY_FILENAME[name] : void 0;
7544
+ if (byName) return byName;
7545
+ const dot = name.lastIndexOf(".");
7546
+ const byExtension = dot > 0 ? MIME_BY_EXTENSION[name.slice(dot)] : void 0;
7547
+ if (byExtension) return byExtension;
7548
+ return name.startsWith(".") ? "text/plain" : null;
7549
+ }
7550
+ function normalizeMime$1(mimeType) {
7551
+ if (!mimeType) return null;
7552
+ return mimeType.split(";")[0]?.trim().toLowerCase() || null;
7553
+ }
7554
+ const MODEL_FORMAT_BY_MIME = {
7555
+ "model/gltf-binary": "glb",
7556
+ "model/gltf+json": "gltf",
7557
+ "model/stl": "stl",
7558
+ "model/obj": "obj"
7559
+ };
7560
+ function modelFormatForPath(path, mimeType) {
7561
+ const format = (mime) => {
7562
+ const key = normalizeMime$1(mime);
7563
+ return key && Object.hasOwn(MODEL_FORMAT_BY_MIME, key) ? MODEL_FORMAT_BY_MIME[key] : void 0;
7564
+ };
7565
+ return format(mimeTypeForPath(path)) ?? format(mimeType) ?? null;
7566
+ }
7567
+ //#endregion
7377
7568
  //#region ../sdk/src/board/model/script-index.ts
7378
7569
  const scriptsByItems = /* @__PURE__ */ new WeakMap();
7379
7570
  function boardScriptItems(document) {
@@ -8470,7 +8661,6 @@ function boardSequence(document, chapters) {
8470
8661
  const chapterIndex = String(index).padStart(String(chapters.length).length, "0");
8471
8662
  for (const [id, track] of Object.entries(animation.tracks)) {
8472
8663
  if (document.animations[track.target]) throw new Error(`Chapter ${chapter.animation} contains nested clocks; compose its leaf animations instead.`);
8473
- if (chapter.frame && track.target === "camera" && track.property === "focus") continue;
8474
8664
  tracks[`chapter-${chapterIndex}-${ranks.get(id)}`] = {
8475
8665
  ...track,
8476
8666
  keyframes: track.keyframes.map((keyframe) => ({
@@ -8501,7 +8691,7 @@ function boardSequence(document, chapters) {
8501
8691
  }] : []
8502
8692
  ]
8503
8693
  };
8504
- tracks[`camera-${index}`] = {
8694
+ tracks[`chapter-${chapterIndex}--camera`] = {
8505
8695
  target: "camera",
8506
8696
  property: "focus",
8507
8697
  composite: "replace",
@@ -9192,6 +9382,14 @@ function getMediaResourceTitle(value) {
9192
9382
  return value.split("/").filter(Boolean).pop() || value;
9193
9383
  }
9194
9384
  }
9385
+ function boardModelFormat(src, mimeType) {
9386
+ const source = parseBoardMediaSource$1(src);
9387
+ if (!source) return null;
9388
+ return modelFormatForPath(source.kind === "url" ? source.value.split(/[?#]/, 1)[0] ?? "" : source.value, mimeType);
9389
+ }
9390
+ function isBoardModelItem(item) {
9391
+ return item?.type === "file" && boardModelFormat(item.props.src, item.props.snapshot?.mimeType) !== null;
9392
+ }
9195
9393
  //#endregion
9196
9394
  //#region ../sdk/src/board/model/file-snapshot.ts
9197
9395
  /**
@@ -27372,6 +27570,14 @@ const PIXEL_GLYPHS = {
27372
27570
  ".###..",
27373
27571
  ".###.."
27374
27572
  ],
27573
+ model: [
27574
+ "..#...",
27575
+ ".###..",
27576
+ "#####.",
27577
+ "#####.",
27578
+ ".###..",
27579
+ "..#..."
27580
+ ],
27375
27581
  file: [
27376
27582
  "####..",
27377
27583
  "#..##.",
@@ -27422,6 +27628,9 @@ function drawLineGlyph(graphics, glyph, color) {
27422
27628
  ]) graphics.moveTo(x, 6 - height / 2).lineTo(x, 6 + height / 2);
27423
27629
  graphics.stroke(line);
27424
27630
  return;
27631
+ case "model":
27632
+ traceCube(graphics, 6, 6, 4.5).stroke(line);
27633
+ return;
27425
27634
  case "file":
27426
27635
  graphics.poly([
27427
27636
  2.5,
@@ -27459,6 +27668,25 @@ function drawLineGlyph(graphics, glyph, color) {
27459
27668
  return;
27460
27669
  }
27461
27670
  }
27671
+ function traceCube(graphics, cx, cy, radius) {
27672
+ const dx = radius * .866;
27673
+ const dy = radius / 2;
27674
+ graphics.poly([
27675
+ cx,
27676
+ cy - radius,
27677
+ cx + dx,
27678
+ cy - dy,
27679
+ cx + dx,
27680
+ cy + dy,
27681
+ cx,
27682
+ cy + radius,
27683
+ cx - dx,
27684
+ cy + dy,
27685
+ cx - dx,
27686
+ cy - dy
27687
+ ]).moveTo(cx - dx, cy - dy).lineTo(cx, cy).lineTo(cx + dx, cy - dy).moveTo(cx, cy).lineTo(cx, cy + radius);
27688
+ return graphics;
27689
+ }
27462
27690
  function drawCardGlyph(graphics, glyph, color, pixel) {
27463
27691
  if (!pixel) {
27464
27692
  drawLineGlyph(graphics, glyph, color);
@@ -27957,9 +28185,28 @@ function drawMediaPlayKey(graphics, cx, cy, radius, context, solid = false) {
27957
28185
  alpha: .96
27958
28186
  });
27959
28187
  }
28188
+ function drawModelKey(graphics, cx, cy, radius, context) {
28189
+ if (context.style.corner === "pixel") {
28190
+ const size = Math.round(radius * 1.6);
28191
+ tracePixelRect(graphics, Math.round(cx - size / 2), Math.round(cy - size / 2), size, size, 2).fill({
28192
+ color: MEDIA_SCRIM,
28193
+ alpha: .62
28194
+ });
28195
+ } else graphics.circle(cx, cy, radius).fill({
28196
+ color: MEDIA_SCRIM,
28197
+ alpha: .45
28198
+ });
28199
+ traceCube(graphics, cx, cy, radius * .5).stroke({
28200
+ color: MEDIA_INK,
28201
+ width: Math.max(1, radius / 10),
28202
+ alpha: .96,
28203
+ join: "round",
28204
+ cap: "round"
28205
+ });
28206
+ }
27960
28207
  const GAP$1 = 12;
27961
28208
  const WAVE_HEIGHT = 16;
27962
- const partsByContainer$12 = /* @__PURE__ */ new WeakMap();
28209
+ const partsByContainer$13 = /* @__PURE__ */ new WeakMap();
27963
28210
  function audioKeyCenter(width, height) {
27964
28211
  const radius = Math.min(16, Math.max(8, height / 2 - 8), width / 4);
27965
28212
  return {
@@ -27968,8 +28215,8 @@ function audioKeyCenter(width, height) {
27968
28215
  radius
27969
28216
  };
27970
28217
  }
27971
- function sync$12(container, item, context) {
27972
- const parts = partsByContainer$12.get(container);
28218
+ function sync$13(container, item, context) {
28219
+ const parts = partsByContainer$13.get(container);
27973
28220
  if (!parts) return;
27974
28221
  positionShell(parts.root, item);
27975
28222
  const { width, height } = item.frame;
@@ -28040,7 +28287,7 @@ const audioCardRenderer = {
28040
28287
  roundPixels: true
28041
28288
  });
28042
28289
  root.addChild(plate, wave, title);
28043
- partsByContainer$12.set(root, {
28290
+ partsByContainer$13.set(root, {
28044
28291
  root,
28045
28292
  plate,
28046
28293
  wave,
@@ -28048,11 +28295,11 @@ const audioCardRenderer = {
28048
28295
  sig: "",
28049
28296
  resolution
28050
28297
  });
28051
- if (item.type === "audio") sync$12(root, item, context);
28298
+ if (item.type === "audio") sync$13(root, item, context);
28052
28299
  return root;
28053
28300
  },
28054
28301
  update: (container, item, context) => {
28055
- if (item.type === "audio") sync$12(container, item, context);
28302
+ if (item.type === "audio") sync$13(container, item, context);
28056
28303
  },
28057
28304
  renderFar: (graphics, item, context) => {
28058
28305
  drawFarPlate(graphics, item.frame, {
@@ -28062,6 +28309,127 @@ const audioCardRenderer = {
28062
28309
  accentAlpha: .72
28063
28310
  });
28064
28311
  },
28312
+ destroy: (container) => {
28313
+ partsByContainer$13.get(container)?.root.destroy({ children: true });
28314
+ partsByContainer$13.delete(container);
28315
+ }
28316
+ };
28317
+ //#endregion
28318
+ //#region ../sdk/src/board/render/renderers/model-card-renderer.ts
28319
+ const partsByContainer$12 = /* @__PURE__ */ new WeakMap();
28320
+ const INSET_RATIO = .06;
28321
+ const KEY_RADIUS = 14;
28322
+ const KEY_MARGIN = 10;
28323
+ function modelKeyCenter(width, height) {
28324
+ const radius = Math.max(8, Math.min(KEY_RADIUS, Math.min(width, height) * .12));
28325
+ const margin = Math.min(KEY_MARGIN, radius * .7);
28326
+ return {
28327
+ x: width - margin - radius,
28328
+ y: height - margin - radius,
28329
+ radius
28330
+ };
28331
+ }
28332
+ function sync$12(container, item, context) {
28333
+ const parts = partsByContainer$12.get(container);
28334
+ if (!parts) return;
28335
+ positionShell(parts.root, item);
28336
+ const { width, height } = item.frame;
28337
+ const { src, snapshot } = item.props;
28338
+ const meta = fileMetaLine(src, snapshot?.size);
28339
+ const title = snapshot?.title || fileStem(src);
28340
+ syncCardTag(parts.tag, {
28341
+ glyph: "model",
28342
+ text: `${title} · ${meta}`,
28343
+ width
28344
+ }, context);
28345
+ const key = context.assetKey(item);
28346
+ const texture = key ? context.getTexture(key) : null;
28347
+ const failed = Boolean(key && !texture && context.hasError(key));
28348
+ const detailed = boardCardDetailed(context.zoom);
28349
+ const elevation = cardElevation(context, item.id);
28350
+ const sig = [
28351
+ key ?? "",
28352
+ texture ? `${texture.width}x${texture.height}` : "none",
28353
+ failed,
28354
+ width,
28355
+ height,
28356
+ elevation,
28357
+ detailed,
28358
+ context.themeKey
28359
+ ].join("|");
28360
+ if (sig === parts.sig) return;
28361
+ parts.sig = sig;
28362
+ drawCardSurface(parts.plate.clear(), {
28363
+ x: 0,
28364
+ y: 0,
28365
+ width,
28366
+ height
28367
+ }, context.style, {
28368
+ fill: context.palette.surface,
28369
+ border: context.palette.border,
28370
+ borderAlpha: .85,
28371
+ elevation
28372
+ });
28373
+ if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
28374
+ parts.sprite.visible = Boolean(texture);
28375
+ if (texture) {
28376
+ const inset = Math.min(width, height) * INSET_RATIO;
28377
+ const fitted = containRect(width - inset * 2, height - inset * 2, texture.width, texture.height);
28378
+ parts.sprite.position.set(inset + fitted.x, inset + fitted.y);
28379
+ parts.sprite.width = fitted.width;
28380
+ parts.sprite.height = fitted.height;
28381
+ } else {
28382
+ const radius = Math.max(6, Math.min(28, Math.min(width, height) * .14));
28383
+ traceCube(parts.plate, width / 2, height / 2, radius).stroke({
28384
+ color: context.palette.muted,
28385
+ width: 1.5,
28386
+ alpha: failed ? .35 : .6,
28387
+ join: "round",
28388
+ cap: "round"
28389
+ });
28390
+ }
28391
+ parts.chrome.clear();
28392
+ if (detailed) {
28393
+ const center = modelKeyCenter(width, height);
28394
+ drawModelKey(parts.chrome, center.x, center.y, center.radius, context);
28395
+ }
28396
+ }
28397
+ const modelCardRenderer = {
28398
+ id: "model-card",
28399
+ canRender: (item) => isBoardModelItem(item),
28400
+ create: (item, context) => {
28401
+ const root = new Container();
28402
+ const plate = new Graphics();
28403
+ const sprite = new Sprite(Texture.EMPTY);
28404
+ const chrome = new Graphics();
28405
+ const tag = createCardTag();
28406
+ root.addChild(plate, sprite, chrome, tag.root);
28407
+ partsByContainer$12.set(root, {
28408
+ root,
28409
+ plate,
28410
+ sprite,
28411
+ chrome,
28412
+ tag,
28413
+ sig: ""
28414
+ });
28415
+ if (item.type === "file") sync$12(root, item, context);
28416
+ return root;
28417
+ },
28418
+ update: (container, item, context) => {
28419
+ if (item.type === "file") sync$12(container, item, context);
28420
+ },
28421
+ zoom: (container, zoom) => {
28422
+ const tag = partsByContainer$12.get(container)?.tag;
28423
+ if (tag) zoomCardTag(tag, zoom);
28424
+ },
28425
+ renderFar: (graphics, item, context) => {
28426
+ drawFarPlate(graphics, item.frame, {
28427
+ fill: context.palette.surface,
28428
+ fillAlpha: .96,
28429
+ accent: context.palette.legendary,
28430
+ accentAlpha: .45
28431
+ });
28432
+ },
28065
28433
  destroy: (container) => {
28066
28434
  partsByContainer$12.get(container)?.root.destroy({ children: true });
28067
28435
  partsByContainer$12.delete(container);
@@ -30212,6 +30580,7 @@ const builtinCardRenderers = [
30212
30580
  imageCardRenderer,
30213
30581
  videoCardRenderer,
30214
30582
  audioCardRenderer,
30583
+ modelCardRenderer,
30215
30584
  fileCardRenderer,
30216
30585
  taskCardRenderer,
30217
30586
  shapeCardRenderer,
@@ -1,7 +1,7 @@
1
1
  import { openAsBlob } from "node:fs";
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, extname, join } from "node:path";
4
- import { GenerationPolicyError, assertGenerationRequestAllowedByPolicy, parseGenerationPolicyFromEnv, } from "@neta-art/cohub";
4
+ import { GenerationPolicyError, assertGenerationRequestAllowedByPolicy, mimeTypeForPath, parseGenerationPolicyFromEnv, } from "@neta-art/cohub";
5
5
  import { probeMediaInfo } from "@neta-art/cohub/media";
6
6
  import { createClient } from "../client.js";
7
7
  import { resolveSpace } from "../space.js";
@@ -14,19 +14,6 @@ const rolesByMediaType = {
14
14
  audio: new Set(["reference_audio"]),
15
15
  };
16
16
  const mediaRoles = new Set([...frameMediaRoles, ...referenceMediaRoles]);
17
- const mimeByExt = {
18
- ".png": "image/png",
19
- ".jpg": "image/jpeg",
20
- ".jpeg": "image/jpeg",
21
- ".webp": "image/webp",
22
- ".gif": "image/gif",
23
- ".mp4": "video/mp4",
24
- ".webm": "video/webm",
25
- ".mov": "video/quicktime",
26
- ".mp3": "audio/mpeg",
27
- ".wav": "audio/wav",
28
- ".ogg": "audio/ogg",
29
- };
30
17
  function parseValue(value) {
31
18
  const trimmed = value.trim();
32
19
  if (trimmed === "true")
@@ -84,8 +71,8 @@ async function mediaContent({ type, value, role }, upload) {
84
71
  const block = (source) => ({ type, source, ...(role ? { meta: { role } } : {}) });
85
72
  if (/^https?:\/\//.test(value))
86
73
  return block({ type: "url", url: value });
87
- const mediaType = mimeByExt[extname(value).toLowerCase()];
88
- if (mediaType && upload) {
74
+ const mediaType = mimeTypeForPath(value);
75
+ if (mediaType && /^(image|video|audio)\//.test(mediaType) && upload) {
89
76
  try {
90
77
  return block({ type: "url", url: await upload(value, mediaType) });
91
78
  }
@@ -189,11 +176,13 @@ function slugOutputLabel(block) {
189
176
  return slugify(metaRole(block) ?? block.type);
190
177
  }
191
178
  const PROBE_TIMEOUT_MS = 3_000;
192
- /** Size, duration, and frames of URL outputs, probed in parallel; best effort. */
179
+ /** Size, duration, and frames of image, video, and audio URL outputs, probed in parallel; best effort. */
193
180
  async function probeOutputs(output) {
194
181
  const signal = AbortSignal.timeout(PROBE_TIMEOUT_MS);
195
182
  const probed = await Promise.all(output.map(async (block, index) => {
196
- if (block.type === "text" || block.source.type !== "url")
183
+ if (block.type !== "image" && block.type !== "video" && block.type !== "audio")
184
+ return null;
185
+ if (block.source.type !== "url")
197
186
  return null;
198
187
  const info = await probeMediaInfo(block.source.url, { type: block.type, signal });
199
188
  return Object.keys(info).length > 0 ? { index, ...info } : null;
@@ -1,4 +1,4 @@
1
- import type { CohubHttpClient } from "@neta-art/cohub";
1
+ import { type CohubHttpClient } from "@neta-art/cohub";
2
2
  import type { Command } from "commander";
3
3
  type LocalPublicFile = {
4
4
  id: string;
@@ -1,45 +1,12 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { lstat, readdir } from "node:fs/promises";
3
- import { basename, extname, relative, resolve } from "node:path";
3
+ import { basename, relative, resolve } from "node:path";
4
+ import { isTextMime, mimeTypeForPath, } from "@neta-art/cohub";
4
5
  import { createClient } from "../client.js";
5
6
  import { HttpPutError, putLocalFile } from "../http-put.js";
6
7
  import { error, handleHttp, json as outJson, jsonRequested } from "../output.js";
7
8
  import { resolveSpace } from "../space.js";
8
9
  const UPLOAD_CONCURRENCY = 4;
9
- const MIME_TYPES = {
10
- ".aac": "audio/aac",
11
- ".avi": "video/x-msvideo",
12
- ".avif": "image/avif",
13
- ".css": "text/css; charset=utf-8",
14
- ".csv": "text/csv; charset=utf-8",
15
- ".gif": "image/gif",
16
- ".htm": "text/html; charset=utf-8",
17
- ".html": "text/html; charset=utf-8",
18
- ".ico": "image/x-icon",
19
- ".jpeg": "image/jpeg",
20
- ".jpg": "image/jpeg",
21
- ".js": "text/javascript; charset=utf-8",
22
- ".json": "application/json; charset=utf-8",
23
- ".m4a": "audio/mp4",
24
- ".md": "text/markdown; charset=utf-8",
25
- ".mov": "video/quicktime",
26
- ".mp3": "audio/mpeg",
27
- ".mp4": "video/mp4",
28
- ".ogg": "audio/ogg",
29
- ".ogv": "video/ogg",
30
- ".pdf": "application/pdf",
31
- ".png": "image/png",
32
- ".svg": "image/svg+xml",
33
- ".txt": "text/plain; charset=utf-8",
34
- ".wasm": "application/wasm",
35
- ".wav": "audio/wav",
36
- ".webm": "video/webm",
37
- ".webp": "image/webp",
38
- ".woff": "font/woff",
39
- ".woff2": "font/woff2",
40
- ".xml": "application/xml; charset=utf-8",
41
- ".zip": "application/zip",
42
- };
43
10
  function hasControlCharacters(value) {
44
11
  return [...value].some((char) => {
45
12
  const code = char.charCodeAt(0);
@@ -65,8 +32,11 @@ function normalizePublicPath(input) {
65
32
  }
66
33
  return parts.join("/");
67
34
  }
68
- function mimeTypeForPath(path) {
69
- return MIME_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
35
+ function contentTypeForPath(path) {
36
+ const mimeType = mimeTypeForPath(path);
37
+ if (!mimeType)
38
+ return "application/octet-stream";
39
+ return isTextMime(mimeType) ? `${mimeType}; charset=utf-8` : mimeType;
70
40
  }
71
41
  async function walkDirectory(root, directory, destination) {
72
42
  const names = await readdir(directory);
@@ -90,7 +60,7 @@ async function walkDirectory(root, directory, destination) {
90
60
  localPath,
91
61
  publicPath,
92
62
  size: info.size,
93
- mimeType: mimeTypeForPath(localPath),
63
+ mimeType: contentTypeForPath(localPath),
94
64
  });
95
65
  }
96
66
  return files;
@@ -114,7 +84,7 @@ export async function collectPublicUpload(source, destination) {
114
84
  localPath,
115
85
  publicPath,
116
86
  size: info.size,
117
- mimeType: mimeTypeForPath(localPath),
87
+ mimeType: contentTypeForPath(localPath),
118
88
  }],
119
89
  };
120
90
  }
@@ -7,7 +7,7 @@ import { Readable, Transform } from "node:stream";
7
7
  import { pipeline } from "node:stream/promises";
8
8
  import { createGunzip } from "node:zlib";
9
9
  // Pin a global release tag after its helper artifacts are published.
10
- export const RTC_HELPER_VERSION = "v2.67.0";
10
+ export const RTC_HELPER_VERSION = "v2.68.0";
11
11
  const MAX_BINARY_BYTES = 128 * 1024 * 1024;
12
12
  async function digest(path) {
13
13
  const hash = createHash("sha256");
@@ -1,30 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { UPLOAD_MAX_BATCH_BYTES, UPLOAD_MAX_BATCH_FILES, } from "@neta-art/cohub";
2
+ import { batchUploadEntries } from "@neta-art/cohub";
3
3
  import { putLocalFile } from "../http-put.js";
4
4
  import { createLimiter } from "./limit.js";
5
5
  const PUT_CONCURRENCY = 4;
6
- function batches(entries) {
7
- const result = [];
8
- let batch = [];
9
- let bytes = 0;
10
- for (const entry of entries) {
11
- if (batch.length > 0 && (batch.length >= UPLOAD_MAX_BATCH_FILES || bytes + entry.size > UPLOAD_MAX_BATCH_BYTES)) {
12
- result.push(batch);
13
- batch = [];
14
- bytes = 0;
15
- }
16
- batch.push(entry);
17
- bytes += entry.size;
18
- }
19
- if (batch.length > 0)
20
- result.push(batch);
21
- return result;
22
- }
23
6
  export async function uploadLocalFiles(files, entries, options = {}) {
24
7
  const uploaded = [];
25
8
  const uploadIds = [];
26
9
  const limit = createLimiter(PUT_CONCURRENCY);
27
- for (const batch of batches(entries)) {
10
+ for (const batch of batchUploadEntries(entries, (entry) => entry.size)) {
28
11
  const byId = new Map(batch.map((entry) => [randomUUID(), entry]));
29
12
  const plan = await files.createUpload({
30
13
  destination: { kind: "workspace", targetDir: options.targetDir },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "10.4.1",
3
+ "version": "10.4.2",
4
4
  "description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -18,8 +18,8 @@
18
18
  "NOTICE"
19
19
  ],
20
20
  "dependencies": {
21
- "@neta-art/cohub": "11.3.1",
22
- "@neta-art/generation": "^0.1.35",
21
+ "@neta-art/cohub": "11.3.2",
22
+ "@neta-art/generation": "^0.1.38",
23
23
  "commander": "^15.0.0",
24
24
  "sharp": "^0.35.4",
25
25
  "ws": "^8.21.3"