@tinycloud/cli 1.0.0-beta.18 → 1.0.0-beta.20

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.
@@ -14594,6 +14594,34 @@ function errorDescription(value) {
14594
14594
  const description = value?.errorDescription ?? value?.error_description;
14595
14595
  return typeof description === "string" && description.length > 0 ? description : void 0;
14596
14596
  }
14597
+ function retryAfterSeconds(response) {
14598
+ const value = response.headers.get("retry-after");
14599
+ if (!value) return void 0;
14600
+ let seconds;
14601
+ if (/^\d+$/.test(value)) {
14602
+ seconds = Number(value);
14603
+ if (!Number.isSafeInteger(seconds)) return void 0;
14604
+ } else {
14605
+ if (!/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (?:0[1-9]|[12]\d|3[01]) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} (?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d GMT$/.test(value)) {
14606
+ return void 0;
14607
+ }
14608
+ const retryAt = Date.parse(value);
14609
+ if (!Number.isFinite(retryAt) || new Date(retryAt).toUTCString() !== value) return void 0;
14610
+ seconds = Math.ceil((retryAt - Date.now()) / 1e3);
14611
+ }
14612
+ return seconds > 0 && seconds <= 600 ? seconds : void 0;
14613
+ }
14614
+ function deviceAuthRateLimit(response) {
14615
+ const retrySeconds = retryAfterSeconds(response);
14616
+ return new CLIError(
14617
+ "DEVICE_AUTH_RATE_LIMITED",
14618
+ "OpenKey rate limited device sign-in requests from this network",
14619
+ ExitCode.ERROR,
14620
+ {
14621
+ hint: `OpenKey allows 5 device sign-in requests per 10 minutes from this network; wait up to 10 minutes, then retry once. The limit is shared by every agent on this network.${retrySeconds !== void 0 ? ` Retry after ${retrySeconds} seconds.` : ""}`
14622
+ }
14623
+ );
14624
+ }
14597
14625
  function publicSessionJwk(value) {
14598
14626
  const publicJwk = publicJwkForDelegation(value);
14599
14627
  const record = publicJwk;
@@ -14774,6 +14802,9 @@ async function acquireDeviceDelegation(input) {
14774
14802
  if (!startResponse.ok) {
14775
14803
  const code2 = errorCode(startValue);
14776
14804
  const description = errorDescription(startValue);
14805
+ if (startResponse.status === 429 && code2 === "rate_limited") {
14806
+ throw deviceAuthRateLimit(startResponse);
14807
+ }
14777
14808
  if (code2 === "invalid_scope") {
14778
14809
  throw new CLIError(
14779
14810
  "SCOPE_REJECTED",
@@ -20353,6 +20384,17 @@ var __export2 = (target, all) => {
20353
20384
  for (var name in all)
20354
20385
  __defProp2(target, name, { get: all[name], enumerable: true });
20355
20386
  };
20387
+ var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
20388
+ function hasUnsafeFilenameCodePoint(value) {
20389
+ return UNSAFE_FILENAME_CODE_POINT.test(value);
20390
+ }
20391
+ function canonicalShareFilename(value) {
20392
+ const canonical = value.normalize("NFC");
20393
+ if (canonical.length === 0 || canonical === "." || canonical === ".." || canonical.includes("/") || canonical.includes("\\") || hasUnsafeFilenameCodePoint(canonical)) {
20394
+ throw new TypeError("share filename is unsafe");
20395
+ }
20396
+ return canonical;
20397
+ }
20356
20398
  var external_exports2 = {};
20357
20399
  __export2(external_exports2, {
20358
20400
  BRAND: () => BRAND2,
@@ -26488,8 +26530,17 @@ function normalizeShareTarget(target) {
26488
26530
  function targetAuthorizationMethod(target) {
26489
26531
  return authorizationMethodForTarget(target);
26490
26532
  }
26533
+ function publishFilename(value) {
26534
+ try {
26535
+ return canonicalShareFilename(value);
26536
+ } catch {
26537
+ throw new SharePublishError("invalid-argument", hasUnsafeFilenameCodePoint(value) ? "filename contains control or invisible characters" : "filename must be one safe path segment");
26538
+ }
26539
+ }
26491
26540
  async function publishTargetShare(input) {
26492
26541
  const target = normalizeShareTarget(input.target);
26542
+ const filename = publishFilename(input.filename);
26543
+ const files = input.files?.map((file) => ({ ...file, filename: publishFilename(file.filename) }));
26493
26544
  if (input.targetAdapter === void 0) {
26494
26545
  if (target.kind === "bearer") throw new SharePublishError("authority-required", "native bearer publication requires an authenticated TinyCloud node");
26495
26546
  return {
@@ -26515,9 +26566,6 @@ async function publishTargetShare(input) {
26515
26566
  return bytes3;
26516
26567
  })();
26517
26568
  if (source.byteLength === 0) throw new SharePublishError("invalid-argument", "share content is empty");
26518
- if (!input.filename || input.filename === "." || input.filename === ".." || /[/\\\u0000-\u001f\u007f]/.test(input.filename)) {
26519
- throw new SharePublishError("invalid-argument", "filename must be one safe path segment");
26520
- }
26521
26569
  if (target.kind === "bearer" && input.allowBinary !== true) {
26522
26570
  try {
26523
26571
  new TextDecoder("utf-8", { fatal: true }).decode(source);
@@ -26525,10 +26573,10 @@ async function publishTargetShare(input) {
26525
26573
  throw new SharePublishError("invalid-argument", "Markdown input must be valid UTF-8");
26526
26574
  }
26527
26575
  }
26528
- const files = input.files === void 0 || input.files.length === 0 ? [{ bytes: source }] : input.files;
26576
+ const publishFiles = files === void 0 || files.length === 0 ? [{ bytes: source }] : files;
26529
26577
  const limit = input.maxBytes ?? SHARE_CONTENT_LIMIT;
26530
26578
  let totalBytes = 0;
26531
- for (const file of files) {
26579
+ for (const file of publishFiles) {
26532
26580
  totalBytes += file.bytes.byteLength;
26533
26581
  if (!Number.isSafeInteger(totalBytes) || totalBytes > limit) {
26534
26582
  throw new SharePublishError("max-bytes-exceeded", "share publication exceeds the combined byte limit");
@@ -26539,8 +26587,8 @@ async function publishTargetShare(input) {
26539
26587
  if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= nowMs2) throw new SharePublishError("invalid-argument", "expiresAt must be a valid future time");
26540
26588
  return input.targetAdapter.publish({
26541
26589
  source,
26542
- filename: input.filename,
26543
- ...input.files === void 0 ? {} : { files: input.files },
26590
+ filename,
26591
+ ...files === void 0 ? {} : { files },
26544
26592
  ...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
26545
26593
  ...input.resourceKind === void 0 ? {} : { resourceKind: input.resourceKind },
26546
26594
  ...input.actions === void 0 ? {} : { actions: input.actions },
@@ -27271,6 +27319,7 @@ import { constants } from "fs";
27271
27319
  import { lstat, mkdir as mkdir3, mkdtemp, open as open3, readFile as readFile9, realpath, stat as stat2, link, rename as rename2, rm as rm3, unlink } from "fs/promises";
27272
27320
  import { randomBytes as randomBytes4 } from "crypto";
27273
27321
  import { basename as basename2, join as join6, resolve as resolve2, sep } from "path";
27322
+ init_errors();
27274
27323
  var MAX_SHARE_STDIN_BYTES = 100 * 1024 * 1024;
27275
27324
  var MAX_SHARE_URL_BYTES = 64 * 1024;
27276
27325
  async function readBoundedStdin(limit = MAX_SHARE_STDIN_BYTES) {
@@ -27292,21 +27341,28 @@ async function readBoundedUrlStdin() {
27292
27341
  return value;
27293
27342
  }
27294
27343
  function safeFilename(value) {
27295
- if (value.length === 0 || value === "." || value === ".." || /[/\\\u0000-\u001f\u007f]/.test(value)) throw new Error("UNSAFE_FILENAME");
27296
- return value;
27344
+ return shareFilename(value);
27297
27345
  }
27298
27346
  function shareFilename(value) {
27299
- return safeFilename(value);
27347
+ try {
27348
+ return canonicalShareFilename(value);
27349
+ } catch {
27350
+ throw new CLIError(
27351
+ "UNSAFE_FILENAME",
27352
+ hasUnsafeFilenameCodePoint(value) ? "filename contains control or invisible characters" : "filename must be one safe path segment",
27353
+ 8
27354
+ );
27355
+ }
27356
+ }
27357
+ function shareInputFilename(input, name) {
27358
+ return shareFilename(name ?? (input === "-" ? "stdin.md" : basename2(resolve2(input))));
27300
27359
  }
27301
27360
  async function readShareInput(input, name, limit = MAX_SHARE_STDIN_BYTES) {
27302
- if (input === "-") {
27303
- const bytes2 = await readBoundedStdin(limit);
27304
- return { bytes: bytes2, filename: shareFilename(name ?? "stdin.md") };
27305
- }
27361
+ const filename = shareInputFilename(input, name);
27362
+ if (input === "-") return { bytes: await readBoundedStdin(limit), filename };
27306
27363
  const path = resolve2(input);
27307
27364
  const info = await stat2(path);
27308
27365
  if (!info.isFile() || info.size > limit) throw new Error("MAX_BYTES_EXCEEDED");
27309
- const filename = shareFilename(name ?? basename2(path));
27310
27366
  const bytes = new Uint8Array(await readFile9(path));
27311
27367
  if (bytes.byteLength > limit) throw new Error("MAX_BYTES_EXCEEDED");
27312
27368
  return { bytes, filename };
@@ -27551,7 +27607,9 @@ function registerShareCommand(program) {
27551
27607
  const json = jsonOutput(options, command);
27552
27608
  const maxBytes = byteLimit(options.maxBytes);
27553
27609
  if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
27554
- const inputs = await Promise.all(files.map((file) => readShareInput(file, files.length === 1 ? options.name : void 0, maxBytes)));
27610
+ const name = files.length === 1 ? options.name : void 0;
27611
+ for (const file of files) shareInputFilename(file, name);
27612
+ const inputs = await Promise.all(files.map((file) => readShareInput(file, name, maxBytes)));
27555
27613
  assertAggregateInputLimit(inputs, maxBytes);
27556
27614
  const target = parseShareTarget(options.to);
27557
27615
  const actions = requestedActions(options.action);