cutsio 0.2.5 → 0.2.6

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 (2) hide show
  1. package/dist/index.js +213 -177
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -366,6 +366,195 @@ function resolveSelection(manifest, selection) {
366
366
  return { videos: [...selected.values()], errors };
367
367
  }
368
368
 
369
+ // src/ui/format.ts
370
+ function formatBytes(bytes) {
371
+ const units = ["B", "KB", "MB", "GB", "TB"];
372
+ let value = bytes;
373
+ let unit = 0;
374
+ while (value >= 1024 && unit < units.length - 1) {
375
+ value /= 1024;
376
+ unit += 1;
377
+ }
378
+ return `${value.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
379
+ }
380
+
381
+ // src/ui/theme.ts
382
+ var colorEnabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
383
+ function paint(code) {
384
+ return colorEnabled ? (text) => `\x1B[${code}m${text}\x1B[0m` : (text) => text;
385
+ }
386
+ var color = {
387
+ dim: paint(2),
388
+ bold: paint(1),
389
+ red: paint(31),
390
+ green: paint(32),
391
+ yellow: paint(33),
392
+ blue: paint(34),
393
+ magenta: paint(35),
394
+ cyan: paint(36)
395
+ };
396
+ var isInteractive = colorEnabled;
397
+ var animationsEnabled = colorEnabled && process.env.CUTSIO_NO_ANIM !== "1";
398
+ function sleep2(ms) {
399
+ return new Promise((resolve) => setTimeout(resolve, ms));
400
+ }
401
+
402
+ // src/ui/progress.ts
403
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
404
+ var BAR_WIDTH = 22;
405
+ var ProgressView = class {
406
+ active = /* @__PURE__ */ new Map();
407
+ interactive = isInteractive;
408
+ statusLine = null;
409
+ renderedLines = 0;
410
+ frame = 0;
411
+ timer = null;
412
+ dirty = false;
413
+ constructor() {
414
+ if (!this.interactive) return;
415
+ this.timer = setInterval(() => {
416
+ if (this.active.size === 0 && !this.statusLine) return;
417
+ this.frame += 1;
418
+ this.render();
419
+ }, 120);
420
+ this.timer.unref();
421
+ }
422
+ status(text) {
423
+ if (!this.interactive) return;
424
+ this.statusLine = text;
425
+ this.dirty = true;
426
+ this.render();
427
+ }
428
+ /** Print a line above the live area without tearing it. */
429
+ log(message) {
430
+ if (!this.interactive) {
431
+ console.log(message);
432
+ return;
433
+ }
434
+ this.clear();
435
+ process.stdout.write(`${message}
436
+ `);
437
+ this.render();
438
+ }
439
+ error(message) {
440
+ if (!this.interactive) {
441
+ console.error(message);
442
+ return;
443
+ }
444
+ this.clear();
445
+ process.stderr.write(`${message}
446
+ `);
447
+ this.render();
448
+ }
449
+ start(id, label) {
450
+ if (!this.interactive) {
451
+ console.log(` fetch ${label}`);
452
+ return;
453
+ }
454
+ const now = Date.now();
455
+ this.active.set(id, {
456
+ label,
457
+ bytes: 0,
458
+ total: null,
459
+ lastBytes: 0,
460
+ lastAt: now,
461
+ speed: 0
462
+ });
463
+ this.dirty = true;
464
+ this.render();
465
+ }
466
+ progress(id, bytes, total) {
467
+ if (!this.interactive) return;
468
+ const item = this.active.get(id);
469
+ if (!item) return;
470
+ const now = Date.now();
471
+ const elapsed = (now - item.lastAt) / 1e3;
472
+ if (elapsed >= 0.3) {
473
+ const delta = bytes - item.lastBytes;
474
+ if (delta >= 0) item.speed = delta / elapsed;
475
+ item.lastBytes = bytes;
476
+ item.lastAt = now;
477
+ }
478
+ item.bytes = bytes;
479
+ if (total !== null) item.total = total;
480
+ this.dirty = true;
481
+ }
482
+ finish(id, ok, text) {
483
+ if (!this.interactive) {
484
+ if (ok) console.log(` done ${text}`);
485
+ else console.error(` FAILED ${text}`);
486
+ return;
487
+ }
488
+ this.clear();
489
+ const line = ok ? ` ${color.green("\u2713")} ${text}` : ` ${color.red("\u2717")} ${text}`;
490
+ (ok ? process.stdout : process.stderr).write(`${line}
491
+ `);
492
+ this.active.delete(id);
493
+ this.render();
494
+ }
495
+ stop() {
496
+ if (this.timer) clearInterval(this.timer);
497
+ this.timer = null;
498
+ if (!this.interactive) return;
499
+ this.clear();
500
+ this.active.clear();
501
+ this.statusLine = null;
502
+ }
503
+ /** Run `work` while a spinner status shows; cleared on exit, plain line when piped. */
504
+ async withStatus(text, work) {
505
+ if (!this.interactive) {
506
+ console.log(text);
507
+ try {
508
+ return await work();
509
+ } finally {
510
+ this.stop();
511
+ }
512
+ }
513
+ this.status(text);
514
+ try {
515
+ return await work();
516
+ } finally {
517
+ this.statusLine = null;
518
+ this.render();
519
+ }
520
+ }
521
+ clear() {
522
+ if (this.renderedLines === 0) return;
523
+ process.stdout.write(`\x1B[${this.renderedLines}A\x1B[J`);
524
+ this.renderedLines = 0;
525
+ }
526
+ render() {
527
+ if (!this.interactive) return;
528
+ if (this.active.size === 0 && !this.statusLine) {
529
+ this.clear();
530
+ return;
531
+ }
532
+ const lines = [];
533
+ if (this.statusLine) {
534
+ const spinner = FRAMES[this.frame % FRAMES.length] ?? FRAMES[0];
535
+ lines.push(` ${color.cyan(spinner ?? "")} ${color.dim(this.statusLine)}`);
536
+ }
537
+ for (const item of this.active.values()) {
538
+ lines.push(` ${color.bold(item.label)}`);
539
+ lines.push(` ${this.bar(item)}`);
540
+ }
541
+ this.clear();
542
+ process.stdout.write(`${lines.join("\n")}
543
+ `);
544
+ this.renderedLines = lines.length;
545
+ this.dirty = false;
546
+ }
547
+ bar(item) {
548
+ const ratio = item.total && item.total > 0 ? Math.min(1, item.bytes / item.total) : null;
549
+ const filled = ratio === null ? 0 : Math.round(ratio * BAR_WIDTH);
550
+ const track = color.cyan("\u2588".repeat(filled)) + color.dim("\u2591".repeat(BAR_WIDTH - filled));
551
+ const percent = ratio === null ? " ? " : `${Math.floor(ratio * 100)}%`.padStart(4);
552
+ const size = item.total ? `${formatBytes(item.bytes)} / ${formatBytes(item.total)}` : formatBytes(item.bytes);
553
+ const speed = item.speed > 0 ? `${formatBytes(item.speed)}/s` : "";
554
+ return `${track} ${percent} ${size} ${color.dim(speed)}`.trimEnd();
555
+ }
556
+ };
557
+
369
558
  // src/commands/list.ts
370
559
  async function listCommand(flags) {
371
560
  const { apiKey, migrated } = await resolveApiKey();
@@ -375,12 +564,15 @@ async function listCommand(flags) {
375
564
  return 1;
376
565
  }
377
566
  const client = new CutsioClient(resolveApiUrl(flags.url), apiKey);
567
+ const view = new ProgressView();
378
568
  let manifest;
379
569
  try {
380
- manifest = await client.fetchManifest();
570
+ manifest = await view.withStatus("Fetching workspace\u2026", () => client.fetchManifest());
381
571
  } catch (error) {
382
572
  console.error(error instanceof Error ? error.message : String(error));
383
573
  return 1;
574
+ } finally {
575
+ view.stop();
384
576
  }
385
577
  if (flags.json === "true") {
386
578
  console.log(JSON.stringify(manifest, null, 2));
@@ -462,7 +654,16 @@ async function loginCommand(flags) {
462
654
  continue;
463
655
  }
464
656
  try {
465
- const who = await new CutsioClient(apiUrl, apiKey).whoami();
657
+ const view = new ProgressView();
658
+ let who;
659
+ try {
660
+ who = await view.withStatus(
661
+ "Verifying key\u2026",
662
+ () => new CutsioClient(apiUrl, apiKey).whoami()
663
+ );
664
+ } finally {
665
+ view.stop();
666
+ }
466
667
  if (!who.orgId) {
467
668
  console.error("That key is not attached to an organization. Create an organization API key.\n");
468
669
  continue;
@@ -870,7 +1071,7 @@ async function sync(client, options) {
870
1071
  }
871
1072
  break;
872
1073
  }
873
- await sleep2(Math.min(Math.max(serverDelayMs, prepareDelayMs), MAX_PREPARE_DELAY_MS));
1074
+ await sleep3(Math.min(Math.max(serverDelayMs, prepareDelayMs), MAX_PREPARE_DELAY_MS));
874
1075
  prepareDelayMs = Math.min(prepareDelayMs * 2, MAX_PREPARE_DELAY_MS);
875
1076
  }
876
1077
  return { downloaded, unchanged, skipped, failed };
@@ -890,180 +1091,10 @@ async function runPool(items, concurrency, worker) {
890
1091
  })
891
1092
  );
892
1093
  }
893
- function sleep2(ms) {
894
- return new Promise((resolve) => setTimeout(resolve, ms));
895
- }
896
-
897
- // src/ui/format.ts
898
- function formatBytes(bytes) {
899
- const units = ["B", "KB", "MB", "GB", "TB"];
900
- let value = bytes;
901
- let unit = 0;
902
- while (value >= 1024 && unit < units.length - 1) {
903
- value /= 1024;
904
- unit += 1;
905
- }
906
- return `${value.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
907
- }
908
-
909
- // src/ui/theme.ts
910
- var colorEnabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
911
- function paint(code) {
912
- return colorEnabled ? (text) => `\x1B[${code}m${text}\x1B[0m` : (text) => text;
913
- }
914
- var color = {
915
- dim: paint(2),
916
- bold: paint(1),
917
- red: paint(31),
918
- green: paint(32),
919
- yellow: paint(33),
920
- blue: paint(34),
921
- magenta: paint(35),
922
- cyan: paint(36)
923
- };
924
- var isInteractive = colorEnabled;
925
- var animationsEnabled = colorEnabled && process.env.CUTSIO_NO_ANIM !== "1";
926
1094
  function sleep3(ms) {
927
1095
  return new Promise((resolve) => setTimeout(resolve, ms));
928
1096
  }
929
1097
 
930
- // src/ui/progress.ts
931
- var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
932
- var BAR_WIDTH = 22;
933
- var ProgressView = class {
934
- active = /* @__PURE__ */ new Map();
935
- interactive = isInteractive;
936
- statusLine = null;
937
- renderedLines = 0;
938
- frame = 0;
939
- timer = null;
940
- dirty = false;
941
- constructor() {
942
- if (!this.interactive) return;
943
- this.timer = setInterval(() => {
944
- if (this.active.size === 0) return;
945
- this.frame += 1;
946
- this.render();
947
- }, 120);
948
- this.timer.unref();
949
- }
950
- status(text) {
951
- if (!this.interactive) return;
952
- this.statusLine = text;
953
- this.dirty = true;
954
- }
955
- /** Print a line above the live area without tearing it. */
956
- log(message) {
957
- if (!this.interactive) {
958
- console.log(message);
959
- return;
960
- }
961
- this.clear();
962
- process.stdout.write(`${message}
963
- `);
964
- this.render();
965
- }
966
- error(message) {
967
- if (!this.interactive) {
968
- console.error(message);
969
- return;
970
- }
971
- this.clear();
972
- process.stderr.write(`${message}
973
- `);
974
- this.render();
975
- }
976
- start(id, label) {
977
- if (!this.interactive) {
978
- console.log(` fetch ${label}`);
979
- return;
980
- }
981
- const now = Date.now();
982
- this.active.set(id, {
983
- label,
984
- bytes: 0,
985
- total: null,
986
- lastBytes: 0,
987
- lastAt: now,
988
- speed: 0
989
- });
990
- this.dirty = true;
991
- this.render();
992
- }
993
- progress(id, bytes, total) {
994
- if (!this.interactive) return;
995
- const item = this.active.get(id);
996
- if (!item) return;
997
- const now = Date.now();
998
- const elapsed = (now - item.lastAt) / 1e3;
999
- if (elapsed >= 0.3) {
1000
- const delta = bytes - item.lastBytes;
1001
- if (delta >= 0) item.speed = delta / elapsed;
1002
- item.lastBytes = bytes;
1003
- item.lastAt = now;
1004
- }
1005
- item.bytes = bytes;
1006
- if (total !== null) item.total = total;
1007
- this.dirty = true;
1008
- }
1009
- finish(id, ok, text) {
1010
- if (!this.interactive) {
1011
- if (ok) console.log(` done ${text}`);
1012
- else console.error(` FAILED ${text}`);
1013
- return;
1014
- }
1015
- this.clear();
1016
- const line = ok ? ` ${color.green("\u2713")} ${text}` : ` ${color.red("\u2717")} ${text}`;
1017
- (ok ? process.stdout : process.stderr).write(`${line}
1018
- `);
1019
- this.active.delete(id);
1020
- this.render();
1021
- }
1022
- stop() {
1023
- if (this.timer) clearInterval(this.timer);
1024
- this.timer = null;
1025
- if (!this.interactive) return;
1026
- this.clear();
1027
- this.active.clear();
1028
- this.statusLine = null;
1029
- }
1030
- clear() {
1031
- if (this.renderedLines === 0) return;
1032
- process.stdout.write(`\x1B[${this.renderedLines}A\x1B[J`);
1033
- this.renderedLines = 0;
1034
- }
1035
- render() {
1036
- if (!this.interactive) return;
1037
- if (this.active.size === 0 && !this.statusLine) {
1038
- this.clear();
1039
- return;
1040
- }
1041
- const lines = [];
1042
- if (this.statusLine) {
1043
- const spinner = FRAMES[this.frame % FRAMES.length] ?? FRAMES[0];
1044
- lines.push(` ${color.cyan(spinner ?? "")} ${color.dim(this.statusLine)}`);
1045
- }
1046
- for (const item of this.active.values()) {
1047
- lines.push(` ${color.bold(item.label)}`);
1048
- lines.push(` ${this.bar(item)}`);
1049
- }
1050
- this.clear();
1051
- process.stdout.write(`${lines.join("\n")}
1052
- `);
1053
- this.renderedLines = lines.length;
1054
- this.dirty = false;
1055
- }
1056
- bar(item) {
1057
- const ratio = item.total && item.total > 0 ? Math.min(1, item.bytes / item.total) : null;
1058
- const filled = ratio === null ? 0 : Math.round(ratio * BAR_WIDTH);
1059
- const track = color.cyan("\u2588".repeat(filled)) + color.dim("\u2591".repeat(BAR_WIDTH - filled));
1060
- const percent = ratio === null ? " ? " : `${Math.floor(ratio * 100)}%`.padStart(4);
1061
- const size = item.total ? `${formatBytes(item.bytes)} / ${formatBytes(item.total)}` : formatBytes(item.bytes);
1062
- const speed = item.speed > 0 ? `${formatBytes(item.speed)}/s` : "";
1063
- return `${track} ${percent} ${size} ${color.dim(speed)}`.trimEnd();
1064
- }
1065
- };
1066
-
1067
1098
  // src/commands/selection-prompt.ts
1068
1099
  import { stdin as stdin2, stdout as stdout2 } from "node:process";
1069
1100
  import { createInterface } from "node:readline/promises";
@@ -1192,7 +1223,7 @@ async function syncCommand(flags) {
1192
1223
  try {
1193
1224
  if (flags.interactive === "true") {
1194
1225
  view.stop();
1195
- manifest = await client.fetchManifest();
1226
+ manifest = await view.withStatus("Fetching workspace\u2026", () => client.fetchManifest());
1196
1227
  const prompted = await promptSelection(manifest);
1197
1228
  if (!prompted) {
1198
1229
  console.log("Cancelled. Nothing downloaded.");
@@ -1273,12 +1304,12 @@ async function revealBanner() {
1273
1304
  for (let index = 0; index < WORDMARK.length; index += 1) {
1274
1305
  process.stdout.write(`${shade(WORDMARK[index] ?? "", index)}
1275
1306
  `);
1276
- if (animationsEnabled) await sleep3(38);
1307
+ if (animationsEnabled) await sleep2(38);
1277
1308
  }
1278
1309
  process.stdout.write(`${color.dim(" mezzanine sync for your Mac")}
1279
1310
 
1280
1311
  `);
1281
- if (animationsEnabled) await sleep3(60);
1312
+ if (animationsEnabled) await sleep2(60);
1282
1313
  }
1283
1314
 
1284
1315
  // src/commands/welcome.ts
@@ -1300,12 +1331,17 @@ async function whoamiCommand(flags) {
1300
1331
  return 1;
1301
1332
  }
1302
1333
  const client = new CutsioClient(resolveApiUrl(flags.url), apiKey);
1334
+ const view = new ProgressView();
1303
1335
  try {
1304
- console.log(JSON.stringify(await client.whoami(), null, 2));
1336
+ console.log(
1337
+ JSON.stringify(await view.withStatus("Checking key\u2026", () => client.whoami()), null, 2)
1338
+ );
1305
1339
  return 0;
1306
1340
  } catch (error) {
1307
1341
  console.error(error instanceof Error ? error.message : String(error));
1308
1342
  return 1;
1343
+ } finally {
1344
+ view.stop();
1309
1345
  }
1310
1346
  }
1311
1347
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cutsio",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Sync Cutsio mezzanine masters to a local folder",
5
5
  "homepage": "https://cutsio.com",
6
6
  "type": "module",