@tinycloud/cli 1.0.0-beta.17 → 1.0.0-beta.19

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",
@@ -16907,6 +16938,23 @@ async function throwKvError(error, spaceUri, profileName) {
16907
16938
  if (hosted) throw hosted;
16908
16939
  throw new CLIError(error.code, error.message, ExitCode.ERROR);
16909
16940
  }
16941
+ function isByteCount(value) {
16942
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
16943
+ }
16944
+ function storageQuotaError(error) {
16945
+ if (error.code !== "STORAGE_QUOTA_EXCEEDED") return void 0;
16946
+ const { usedBytes, limitBytes } = typeof error.meta === "object" && error.meta !== null ? error.meta : {};
16947
+ const sizes = isByteCount(usedBytes) && isByteCount(limitBytes) ? ` (${formatBytes(usedBytes)} used of ${formatBytes(limitBytes)} limit)` : "";
16948
+ return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was written`, ExitCode.ERROR);
16949
+ }
16950
+ function assertAddressableKey(key) {
16951
+ for (let i = 0; i < key.length; i++) {
16952
+ const code2 = key.charCodeAt(i);
16953
+ if (code2 <= 32 || code2 === 127) {
16954
+ throw new CLIError("USAGE_ERROR", "KV keys cannot contain spaces or control characters; use a URL-safe key", ExitCode.USAGE_ERROR);
16955
+ }
16956
+ }
16957
+ }
16910
16958
  async function readStdin2() {
16911
16959
  const chunks = [];
16912
16960
  for await (const chunk of process.stdin) {
@@ -16923,6 +16971,7 @@ function registerKvCommand(program) {
16923
16971
  const kv = program.command("kv").description("Key-value store operations");
16924
16972
  kv.command("get <key>").description("Get a value by key").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
16925
16973
  try {
16974
+ assertAddressableKey(key);
16926
16975
  const globalOpts = cmd.optsWithGlobals();
16927
16976
  const ctx = await ProfileManager.resolveContext(globalOpts);
16928
16977
  const node = await ensureAuthenticated(ctx);
@@ -16967,6 +17016,7 @@ function registerKvCommand(program) {
16967
17016
  });
16968
17017
  kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
16969
17018
  try {
17019
+ assertAddressableKey(key);
16970
17020
  const globalOpts = cmd.optsWithGlobals();
16971
17021
  const ctx = await ProfileManager.resolveContext(globalOpts);
16972
17022
  const node = await ensureAuthenticated(ctx);
@@ -16992,6 +17042,8 @@ function registerKvCommand(program) {
16992
17042
  const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
16993
17043
  const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
16994
17044
  if (!result.ok) {
17045
+ const quota = storageQuotaError(result.error);
17046
+ if (quota) throw quota;
16995
17047
  await throwKvError(result.error, spaceUri, ctx.profile);
16996
17048
  }
16997
17049
  outputJson({ key, written: true });
@@ -17001,6 +17053,7 @@ function registerKvCommand(program) {
17001
17053
  });
17002
17054
  kv.command("delete <key>").description("Delete a key").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
17003
17055
  try {
17056
+ assertAddressableKey(key);
17004
17057
  const globalOpts = cmd.optsWithGlobals();
17005
17058
  const ctx = await ProfileManager.resolveContext(globalOpts);
17006
17059
  const node = await ensureAuthenticated(ctx);
@@ -17016,6 +17069,7 @@ function registerKvCommand(program) {
17016
17069
  });
17017
17070
  kv.command("list").description("List keys").option("--prefix <prefix>", "Filter by key prefix").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (options, cmd) => {
17018
17071
  try {
17072
+ if (options.prefix) assertAddressableKey(options.prefix);
17019
17073
  const globalOpts = cmd.optsWithGlobals();
17020
17074
  const ctx = await ProfileManager.resolveContext(globalOpts);
17021
17075
  const node = await ensureAuthenticated(ctx);
@@ -17051,6 +17105,7 @@ function registerKvCommand(program) {
17051
17105
  });
17052
17106
  kv.command("head <key>").description("Get metadata for a key (no body)").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
17053
17107
  try {
17108
+ assertAddressableKey(key);
17054
17109
  const globalOpts = cmd.optsWithGlobals();
17055
17110
  const ctx = await ProfileManager.resolveContext(globalOpts);
17056
17111
  const node = await ensureAuthenticated(ctx);
@@ -27204,6 +27259,7 @@ function parseNativeShareUrl(value) {
27204
27259
  }
27205
27260
 
27206
27261
  // src/commands/share.ts
27262
+ init_formatter();
27207
27263
  init_errors();
27208
27264
 
27209
27265
  // src/share/output.ts
@@ -27403,7 +27459,7 @@ function shareCliError(error) {
27403
27459
  }
27404
27460
  if (failure.kind === "scope-denied") {
27405
27461
  const requiredAction = failure.requiredAction === void 0 ? "" : ` (${failure.requiredAction})`;
27406
- const renew = localKey ? `renew it with ${loginHint} using the required capability` : `request the builtin:share-publishing scope with ${loginHint}`;
27462
+ const renew = localKey ? `renew it with ${loginHint} using the required capability` : `verify the session includes the builtin:share-publishing scope; if it does not, request it with ${loginHint}`;
27407
27463
  return new CLIError("PERMISSION_DENIED", `the session lacks ${failure.capability} authority${requiredAction}; ${renew}`, 5);
27408
27464
  }
27409
27465
  if (failure.kind === "lifetime-exceeds-session") {
@@ -27428,6 +27484,13 @@ function shareCliError(error) {
27428
27484
  if (failure.kind === "registry-rejected") {
27429
27485
  return new CLIError("REGISTRY_REJECTED", "the TinyCloud location registry rejected this session's location record, so nothing was shared; retrying will not help. Log in again, and report the problem if it persists", 6);
27430
27486
  }
27487
+ if (failure.kind === "storage-quota-exceeded") {
27488
+ const sizes = failure.usedBytes === void 0 || failure.limitBytes === void 0 ? "" : ` (${formatBytes(failure.usedBytes)} used of ${formatBytes(failure.limitBytes)} limit)`;
27489
+ return new CLIError("STORAGE_QUOTA_EXCEEDED", `storage quota exceeded${sizes}; nothing was shared`, 4);
27490
+ }
27491
+ if (failure.kind === "upload-failed") {
27492
+ return new CLIError("UPLOAD_FAILED", "share source upload failed; nothing was shared", 4);
27493
+ }
27431
27494
  }
27432
27495
  if (error instanceof ShareNotifyError) {
27433
27496
  return new CLIError("INVALID_ARGUMENT", error.message, 2);