@tinycloud/cli 1.1.0-beta.13 → 1.1.0-beta.15

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.
@@ -165,8 +165,14 @@ import ora from "ora";
165
165
  function outputJson(data) {
166
166
  process.stdout.write(JSON.stringify(data, null, 2) + "\n");
167
167
  }
168
- function outputError(code3, message, hint, meta) {
169
- if (isInteractive()) {
168
+ function warningLine(warnings) {
169
+ const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
170
+ const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
171
+ return `${subject} not used (${which}). Approve access again when a command needs it; the old grant expires on its own.`;
172
+ }
173
+ function outputError(code3, message, hint, details = {}) {
174
+ const { meta, warnings = [] } = details;
175
+ if (!shouldOutputJson()) {
170
176
  process.stderr.write(
171
177
  `${theme.error("\u2717")} ${theme.label(code3)}: ${message}
172
178
  `
@@ -177,20 +183,40 @@ function outputError(code3, message, hint, meta) {
177
183
  `);
178
184
  }
179
185
  }
186
+ if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
187
+ `);
180
188
  } else {
181
189
  const payload = {
182
190
  error: { code: code3, message }
183
191
  };
184
192
  if (hint) payload.error.hint = hint;
185
193
  if (meta) payload.error.meta = meta;
194
+ if (warnings.length > 0) payload.error.warnings = warnings;
186
195
  process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
187
196
  }
188
197
  }
198
+ function outputWarnings(warnings) {
199
+ if (warnings.length === 0) return;
200
+ process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings })}
201
+ ` : `${theme.warn("!")} ${warningLine(warnings)}
202
+ `);
203
+ }
204
+ function operationWarnings(value) {
205
+ if (!Array.isArray(value)) return [];
206
+ return value.flatMap((warning) => {
207
+ if (warning === null || typeof warning !== "object") return [];
208
+ const { code: code3, reason, grantCid } = warning;
209
+ if (typeof code3 !== "string" || !WARNING_CODE.test(code3) || typeof reason !== "string" || !WARNING_REASON.test(reason)) {
210
+ return [];
211
+ }
212
+ return [{ code: code3, reason, ...typeof grantCid === "string" && DELEGATION_CID.test(grantCid) ? { grantCid } : {} }];
213
+ });
214
+ }
189
215
  function isInteractive() {
190
216
  return Boolean(process.stdout.isTTY);
191
217
  }
192
218
  async function withSpinner(label, fn) {
193
- if (!isInteractive()) {
219
+ if (shouldOutputJson()) {
194
220
  return fn();
195
221
  }
196
222
  const spinner = ora(label).start();
@@ -204,7 +230,7 @@ async function withSpinner(label, fn) {
204
230
  }
205
231
  }
206
232
  function shouldOutputJson() {
207
- return !isInteractive() || process.argv.includes("--json");
233
+ return !isInteractive() || (globalThis[JSON_REQUESTED] ?? process.argv.includes("--json"));
208
234
  }
209
235
  function formatField(label, value) {
210
236
  if (value === null || value === void 0) return ` ${theme.label(label + ":")} ${theme.muted("\u2014")}`;
@@ -253,10 +279,15 @@ function formatTimeAgo(date) {
253
279
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
254
280
  return `${Math.floor(seconds / 86400)}d ago`;
255
281
  }
282
+ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
256
283
  var init_formatter = __esm({
257
284
  "src/output/formatter.ts"() {
258
285
  "use strict";
259
286
  init_theme();
287
+ WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
288
+ WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
289
+ DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
290
+ JSON_REQUESTED = /* @__PURE__ */ Symbol.for("tinycloud.cli.jsonOutputRequested");
260
291
  }
261
292
  });
262
293
 
@@ -361,11 +392,12 @@ function cliErrorFromService(error, message = error.message) {
361
392
  return missingPrivateKeyError();
362
393
  }
363
394
  const missingCapability = validatedCapabilityOf({ ...error, meta }) !== void 0;
364
- const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability;
395
+ const denied = verdict === "forbidden" || verdict === "unauthenticated" && missingCapability || verdict === void 0 && error.code === "PERMISSION_DENIED";
396
+ const unauthenticated = !denied && (verdict === "unauthenticated" || verdict === void 0 && error.code === "AUTH_REQUIRED");
365
397
  return new CLIError(
366
- denied ? "PERMISSION_DENIED" : verdict === "unauthenticated" ? "AUTH_REQUIRED" : error.code,
398
+ denied ? "PERMISSION_DENIED" : unauthenticated ? "AUTH_REQUIRED" : error.code,
367
399
  message,
368
- denied ? ExitCode.PERMISSION_DENIED : verdict === "unauthenticated" ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
400
+ denied ? ExitCode.PERMISSION_DENIED : unauthenticated ? ExitCode.AUTH_REQUIRED : ExitCode.ERROR,
369
401
  meta
370
402
  );
371
403
  }
@@ -437,7 +469,10 @@ function handleError(error) {
437
469
  meta.resource = capability.resource;
438
470
  meta.requiredAction = capability.requiredAction;
439
471
  }
440
- outputError(cliError.code, cliError.message, hint, Object.keys(meta).length ? meta : void 0);
472
+ outputError(cliError.code, cliError.message, hint, {
473
+ ...Object.keys(meta).length ? { meta } : {},
474
+ warnings: operationWarnings(cliError.metadata?.warnings)
475
+ });
441
476
  process.exit(cliError.exitCode);
442
477
  }
443
478
  function buildAuthHint(error) {
@@ -1942,6 +1977,8 @@ var CAPABILITIES = [
1942
1977
  { urn: "tinycloud.kv/put", service: "tinycloud.kv", status: "active" },
1943
1978
  { urn: "tinycloud.kv/del", service: "tinycloud.kv", status: "active" },
1944
1979
  { urn: "tinycloud.kv/delete", service: "tinycloud.kv", status: "deprecated-alias", aliasOf: "tinycloud.kv/del" },
1980
+ { urn: "tinycloud.kv/sync", service: "tinycloud.kv", status: "active" },
1981
+ { urn: "tinycloud.kv/retain", service: "tinycloud.kv", status: "active" },
1945
1982
  { urn: "tinycloud.sql/read", service: "tinycloud.sql", status: "active" },
1946
1983
  { urn: "tinycloud.sql/select", service: "tinycloud.sql", status: "deprecated-alias", aliasOf: "tinycloud.sql/read" },
1947
1984
  { urn: "tinycloud.sql/write", service: "tinycloud.sql", status: "active" },
@@ -6521,6 +6558,10 @@ var ErrorCodes = {
6521
6558
  AUTH_EXPIRED: "AUTH_EXPIRED",
6522
6559
  AUTH_REQUIRED: "AUTH_REQUIRED",
6523
6560
  AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
6561
+ /** HTTP 401: the delegation the invocation cites was revoked. */
6562
+ AUTH_DELEGATION_REVOKED: "AUTH_DELEGATION_REVOKED",
6563
+ /** HTTP 401: a delegation the cited one rests on was revoked. */
6564
+ AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
6524
6565
  NETWORK_ERROR: "NETWORK_ERROR",
6525
6566
  TIMEOUT: "TIMEOUT",
6526
6567
  ABORTED: "ABORTED",
@@ -6532,6 +6573,10 @@ var ErrorCodes = {
6532
6573
  KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
6533
6574
  KV_CONFLICT: "KV_CONFLICT",
6534
6575
  KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
6576
+ /** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */
6577
+ KV_SYNC_RESET_REQUIRED: "KV_SYNC_RESET_REQUIRED",
6578
+ /** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */
6579
+ KV_RETENTION_GRANT_REFUSED: "KV_RETENTION_GRANT_REFUSED",
6535
6580
  // SQL-specific errors
6536
6581
  SQL_ERROR: "SQL_ERROR",
6537
6582
  SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
@@ -7592,6 +7637,30 @@ var PrefixedKVService = class _PrefixedKVService {
7592
7637
  removePrefix
7593
7638
  });
7594
7639
  }
7640
+ /**
7641
+ * Read the change feed for everything under this prefix. A view with an
7642
+ * empty prefix (`""` or `"/"`) is refused: the node does not serve a
7643
+ * whole-space feed.
7644
+ */
7645
+ async changes(options) {
7646
+ if (this._prefix.replace(/\/+$/, "") === "") {
7647
+ return err(serviceError(
7648
+ ErrorCodes.INVALID_INPUT,
7649
+ "KV changes needs a non-empty prefix; this view has none",
7650
+ "kv"
7651
+ ));
7652
+ }
7653
+ const syncPrefix = `${this._prefix}/`;
7654
+ const response = await this._kv.changes({ ...options, prefix: syncPrefix });
7655
+ if (!response.ok) return response;
7656
+ return ok({
7657
+ ...response.data,
7658
+ changes: response.data.changes.map((change) => ({
7659
+ ...change,
7660
+ key: change.key.startsWith(syncPrefix) ? change.key.slice(syncPrefix.length) : change.key
7661
+ }))
7662
+ });
7663
+ }
7595
7664
  /**
7596
7665
  * Delete a key.
7597
7666
  */
@@ -7642,9 +7711,16 @@ var KVAction = {
7642
7711
  PUT: "tinycloud.kv/put",
7643
7712
  LIST: "tinycloud.kv/list",
7644
7713
  DELETE: "tinycloud.kv/del",
7645
- HEAD: "tinycloud.kv/metadata"
7714
+ HEAD: "tinycloud.kv/metadata",
7715
+ /** The change feed; never implied by `*` or `tinycloud.kv/*`. */
7716
+ SYNC: "tinycloud.kv/sync",
7717
+ /** Retention attestation; presented through `retentionGrant`, never invoked or implied. */
7718
+ RETAIN: "tinycloud.kv/retain"
7646
7719
  };
7647
7720
  var MAX_KV_BATCH_READ_ITEMS = 100;
7721
+ function isJsonObject(value) {
7722
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7723
+ }
7648
7724
  function encodeKvBatchPartName(path) {
7649
7725
  return encodeURIComponent(path).replace(
7650
7726
  /[!'()*]/g,
@@ -8563,6 +8639,169 @@ var KVService = class extends BaseService {
8563
8639
  }
8564
8640
  });
8565
8641
  }
8642
+ /**
8643
+ * Read one page of the `tinycloud.kv/sync` change feed for a prefix.
8644
+ */
8645
+ async changes(options) {
8646
+ return this.withTelemetry("changes", options?.prefix, async () => {
8647
+ if (!this.requireAuth()) {
8648
+ return err(authRequiredError("kv"));
8649
+ }
8650
+ if (typeof options?.prefix !== "string" || options.prefix.length === 0) {
8651
+ return err(serviceError(
8652
+ ErrorCodes.INVALID_INPUT,
8653
+ "KV changes prefix must be a non-empty string",
8654
+ "kv"
8655
+ ));
8656
+ }
8657
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1e3)) {
8658
+ return err(serviceError(
8659
+ ErrorCodes.INVALID_INPUT,
8660
+ "KV changes limit must be an integer from 1 through 1000",
8661
+ "kv"
8662
+ ));
8663
+ }
8664
+ const headers = {};
8665
+ if (options.limit !== void 0) headers["x-tinycloud-limit"] = String(options.limit);
8666
+ if (options.cursor !== void 0) headers["x-tinycloud-cursor"] = options.cursor;
8667
+ if (options.retentionGrant !== void 0) {
8668
+ headers["x-tinycloud-retention-grant"] = options.retentionGrant;
8669
+ }
8670
+ const request = this.createRequestSignal(options.signal, options.timeout);
8671
+ try {
8672
+ const response = await this.invokeOperation(
8673
+ options.prefix,
8674
+ KVAction.SYNC,
8675
+ void 0,
8676
+ request.signal,
8677
+ headers
8678
+ );
8679
+ if (!response.ok) {
8680
+ return await this.changesFailure(response, options.prefix, request.signal);
8681
+ }
8682
+ const text = await response.text();
8683
+ let body;
8684
+ try {
8685
+ body = JSON.parse(text);
8686
+ } catch {
8687
+ body = void 0;
8688
+ }
8689
+ const page = this.normalizeChangesResponse(body);
8690
+ if (!page) {
8691
+ return err(serviceError(
8692
+ ErrorCodes.NETWORK_ERROR,
8693
+ "KV changes response was not a tinycloud.kv/sync page",
8694
+ "kv",
8695
+ { meta: { status: response.status, statusText: response.statusText } }
8696
+ ));
8697
+ }
8698
+ return ok(page);
8699
+ } catch (error) {
8700
+ return err(wrapError2("kv", error));
8701
+ } finally {
8702
+ request.dispose();
8703
+ }
8704
+ });
8705
+ }
8706
+ /**
8707
+ * Map a failed `kv/sync` response. 410 and the retention 403 carry
8708
+ * `{"error":{"code","reason"}}`; a revoked grant is a 401 whose text names
8709
+ * `delegation-revoked` or `delegation-ancestor-revoked`. A body read that
8710
+ * fails because the request was cancelled or timed out rethrows, so the
8711
+ * caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
8712
+ */
8713
+ async changesFailure(response, prefix, signal) {
8714
+ if (response.status === 404) {
8715
+ return this.classifyNotFound(response, prefix);
8716
+ }
8717
+ let errorText;
8718
+ try {
8719
+ errorText = await response.text();
8720
+ } catch (error) {
8721
+ if (signal.aborted) throw error;
8722
+ errorText = "";
8723
+ }
8724
+ const meta = { status: response.status, statusText: response.statusText };
8725
+ let reason;
8726
+ let bodyCode;
8727
+ try {
8728
+ const body = JSON.parse(errorText);
8729
+ const error = isJsonObject(body) ? body.error : void 0;
8730
+ if (isJsonObject(error)) {
8731
+ if (typeof error.code === "string") bodyCode = error.code;
8732
+ if (typeof error.reason === "string") reason = error.reason;
8733
+ }
8734
+ } catch {
8735
+ }
8736
+ const context = `Failed to read KV changes for ${JSON.stringify(prefix)}`;
8737
+ if (response.status === 410) {
8738
+ return err(serviceError(
8739
+ ErrorCodes.KV_SYNC_RESET_REQUIRED,
8740
+ `${context}: the node requires a reset (${reason ?? "reason not given"}); discard sync state and restart without a cursor`,
8741
+ "kv",
8742
+ { meta: { ...meta, reason } }
8743
+ ));
8744
+ }
8745
+ if (response.status === 403 && bodyCode === "RETENTION_GRANT_REFUSED") {
8746
+ return err(serviceError(
8747
+ ErrorCodes.KV_RETENTION_GRANT_REFUSED,
8748
+ `${context}: the retention grant was refused (${reason ?? "reason not given"})`,
8749
+ "kv",
8750
+ { meta: { ...meta, reason } }
8751
+ ));
8752
+ }
8753
+ if (response.status === 401) {
8754
+ const revoked = /\bdelegation-ancestor-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_ANCESTOR_REVOKED : /\bdelegation-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_REVOKED : void 0;
8755
+ if (revoked !== void 0) {
8756
+ return err(serviceError(revoked, `${context}: 401 - ${errorText}`, "kv", { meta }));
8757
+ }
8758
+ }
8759
+ if (response.status === 401 || response.status === 403) {
8760
+ return this.authorizationFailure(context, response, errorText, [prefix], KVAction.SYNC);
8761
+ }
8762
+ return err(serviceError(
8763
+ response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR,
8764
+ `${context}: ${response.status} - ${errorText}`,
8765
+ "kv",
8766
+ { meta }
8767
+ ));
8768
+ }
8769
+ normalizeChangesResponse(data) {
8770
+ if (!isJsonObject(data)) return void 0;
8771
+ const { changes, more, cursor, source, authority } = data;
8772
+ if (!Array.isArray(changes) || typeof more !== "boolean" || typeof cursor !== "string" || !isJsonObject(source) || typeof source.nodeDid !== "string" || typeof source.space !== "string" || typeof source.prefix !== "string" || !isJsonObject(authority)) {
8773
+ return void 0;
8774
+ }
8775
+ const { notBefore, expiresAt, retainUntil } = authority;
8776
+ if (notBefore !== null && typeof notBefore !== "string" || expiresAt !== null && typeof expiresAt !== "string" || retainUntil !== null && typeof retainUntil !== "string") {
8777
+ return void 0;
8778
+ }
8779
+ const parsed = [];
8780
+ for (const change of changes) {
8781
+ if (!isJsonObject(change) || typeof change.key !== "string") return void 0;
8782
+ if (change.deleted === true) {
8783
+ parsed.push({ key: change.key, deleted: true });
8784
+ continue;
8785
+ }
8786
+ const { metadata } = change;
8787
+ if (change.deleted !== false || typeof change.etag !== "string" || !isJsonObject(metadata) || !Object.values(metadata).every((value) => typeof value === "string")) {
8788
+ return void 0;
8789
+ }
8790
+ parsed.push({
8791
+ key: change.key,
8792
+ deleted: false,
8793
+ etag: change.etag,
8794
+ metadata
8795
+ });
8796
+ }
8797
+ return {
8798
+ changes: parsed,
8799
+ more,
8800
+ cursor,
8801
+ source: { nodeDid: source.nodeDid, space: source.space, prefix: source.prefix },
8802
+ authority: { notBefore, expiresAt, retainUntil }
8803
+ };
8804
+ }
8566
8805
  /**
8567
8806
  * Delete a key.
8568
8807
  */
@@ -13471,6 +13710,10 @@ function canonicalOwnerDid(did, label = "--owner") {
13471
13710
  }
13472
13711
  return `did:pkh:eip155:${match[1]}:${ensureEip55(match[2])}`;
13473
13712
  }
13713
+ function canonicalNetworkUrn(path) {
13714
+ const match = /^urn:tinycloud:encryption:(did:pkh:eip155:[1-9]\d*:0x[0-9a-fA-F]{40}):([^:]*)$/.exec(path);
13715
+ return match ? `urn:tinycloud:encryption:${canonicalOwnerDid(match[1])}:${match[2]}` : path;
13716
+ }
13474
13717
  function rawEncryptionOwnerMatches(path, ownerDid) {
13475
13718
  const match = /^urn:tinycloud:encryption:(did:pkh:eip155:.+):([a-z0-9][a-z0-9-]*)$/.exec(path);
13476
13719
  if (!match) return false;
@@ -13613,13 +13856,14 @@ async function replayAdditionalDelegations(node, profile, options) {
13613
13856
  for (const stored of entries) {
13614
13857
  const kind = storedDelegationKind(stored);
13615
13858
  if (kind === "compact" || kind === "signed-login") {
13616
- const installed = await replayStoredDelegation(activator, stored, {
13859
+ const replay = await replayStoredDelegation(activator, stored, {
13617
13860
  host: options.host,
13618
13861
  migrated,
13619
13862
  resolveSpace
13620
13863
  });
13621
- if (installed === void 0 && process.env.TC_DEBUG_REPLAY === "1") {
13622
- process.stderr.write("[replay] skipping a stored delegation refused by validation or its request binding\n");
13864
+ if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
13865
+ process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
13866
+ `);
13623
13867
  }
13624
13868
  continue;
13625
13869
  }
@@ -13649,6 +13893,14 @@ async function replayAdditionalDelegations(node, profile, options) {
13649
13893
  function storedAdditionalDelegation(delegation, permissions) {
13650
13894
  return { delegation, permissions };
13651
13895
  }
13896
+ async function cliGrantRecord(node, delegation, permissions, host) {
13897
+ const { bindCliGrant } = await import("@tinycloud/operations/delegation-binding");
13898
+ return bindCliGrant(
13899
+ node,
13900
+ storedAdditionalDelegation(delegation, permissions),
13901
+ host
13902
+ );
13903
+ }
13652
13904
  async function appendGrantHistory(profile, entry) {
13653
13905
  await ProfileManager.ensureProfileDir(profile);
13654
13906
  const line = JSON.stringify({
@@ -13803,7 +14055,8 @@ function permissionsFromDelegation(delegation) {
13803
14055
  }
13804
14056
  function compactPermission(permission) {
13805
14057
  const service = permission.service;
13806
- const space = permission.space.startsWith("tinycloud:") ? permission.space.slice(permission.space.lastIndexOf(":") + 1) : permission.space;
14058
+ const fullSpace = permission.space ?? (isRawEncryptionPermission(permission) ? ENCRYPTION_MANIFEST_SPACE2 : "");
14059
+ const space = fullSpace.startsWith("tinycloud:") ? fullSpace.slice(fullSpace.lastIndexOf(":") + 1) : fullSpace;
13807
14060
  const actions = permission.actions.map((action) => action.startsWith(`${service}/`) ? action.slice(service.length + 1) : action).join(",");
13808
14061
  return `${service}:${space}:${permission.path}:${actions}`;
13809
14062
  }
@@ -13899,6 +14152,35 @@ function serviceFromActions(actions) {
13899
14152
  return first.includes("/") ? first.slice(0, first.indexOf("/")) : "tinycloud.unknown";
13900
14153
  }
13901
14154
 
14155
+ // src/auth/session-expired.ts
14156
+ init_constants();
14157
+ init_errors();
14158
+ var SESSION_EXPIRED_REASON = "session_expired";
14159
+ function signInAgainHint(profileName, posture) {
14160
+ if (posture === "local-owner-key") {
14161
+ return `Sign in again with: tc --profile ${profileName} auth login --method local`;
14162
+ }
14163
+ if (posture === "delegate-session") {
14164
+ return `Have the owner approve a new scoped login: tc --profile ${profileName} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
14165
+ }
14166
+ return `Sign in again with: tc --profile ${profileName} auth login --method openkey`;
14167
+ }
14168
+ function sessionExpiredError(profileName, posture) {
14169
+ return new CLIError(
14170
+ "AUTH_REQUIRED",
14171
+ `The session for profile "${profileName}" has expired or is no longer valid.`,
14172
+ ExitCode.AUTH_REQUIRED,
14173
+ { hint: signInAgainHint(profileName, posture), reason: SESSION_EXPIRED_REASON }
14174
+ );
14175
+ }
14176
+ function withSignInHint(error, profileName, posture) {
14177
+ if (error.code !== "AUTH_REQUIRED") return error;
14178
+ return new CLIError(error.code, error.message, error.exitCode, {
14179
+ ...error.metadata,
14180
+ hint: signInAgainHint(profileName, posture)
14181
+ });
14182
+ }
14183
+
13902
14184
  // src/lib/sdk.ts
13903
14185
  function jwkHasPrivateParameter(jwk) {
13904
14186
  if (!jwk || typeof jwk !== "object") return false;
@@ -13925,6 +14207,16 @@ function signerJwkForProfile(profileName, sessionJwk, key) {
13925
14207
  }
13926
14208
  );
13927
14209
  }
14210
+ async function restoreProfileSession(node, profileName, profile, sessionData) {
14211
+ try {
14212
+ await node.restoreSession(sessionData);
14213
+ } catch (error) {
14214
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "AUTH_EXPIRED") {
14215
+ throw sessionExpiredError(profileName, profile === null ? void 0 : resolveProfilePosture(profile));
14216
+ }
14217
+ throw error;
14218
+ }
14219
+ }
13928
14220
  async function createSDKInstance(ctx, options) {
13929
14221
  const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
13930
14222
  const session = await ProfileManager.getSession(ctx.profile);
@@ -13944,7 +14236,7 @@ async function createSDKInstance(ctx, options) {
13944
14236
  });
13945
14237
  let restoredOwnSession2 = false;
13946
14238
  if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
13947
- await node2.restoreSession({
14239
+ await restoreProfileSession(node2, ctx.profile, profile, {
13948
14240
  delegationHeader: session.delegationHeader,
13949
14241
  delegationCid: session.delegationCid,
13950
14242
  spaceId: session.spaceId,
@@ -13974,7 +14266,7 @@ async function createSDKInstance(ctx, options) {
13974
14266
  if (options?.privateKey) {
13975
14267
  await node.signIn();
13976
14268
  } else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
13977
- await node.restoreSession({
14269
+ await restoreProfileSession(node, ctx.profile, profile, {
13978
14270
  delegationHeader: session.delegationHeader,
13979
14271
  delegationCid: session.delegationCid,
13980
14272
  spaceId: session.spaceId,
@@ -14556,6 +14848,41 @@ function validateDelegationCallbackPayload(value) {
14556
14848
  }
14557
14849
  return null;
14558
14850
  }
14851
+ async function delegationFromInput(input, did, options) {
14852
+ const trimmed = input.trim();
14853
+ let parsed;
14854
+ if (/^[a-z2-7]{4}-[a-z2-7]{4}$/i.test(trimmed)) {
14855
+ if (!options.jwk) throw new Error("A CLI session key is required to retrieve a delegation.");
14856
+ const apiHost = process.env.TC_OPENKEY_API_HOST ?? options.openkeyApiHost ?? (options.openkeyHost && options.openkeyHost !== DEFAULT_OPENKEY_HOST ? options.openkeyHost : DEFAULT_OPENKEY_DEVICE_API_HOST);
14857
+ const response = await fetch(`${apiHost.replace(/\/$/, "")}/api/delegation-codes/${trimmed.toLowerCase()}`, {
14858
+ redirect: "error"
14859
+ });
14860
+ if (!response.ok) {
14861
+ throw new Error(response.status === 404 ? "Delegation code not found or expired. Use the full code shown in OpenKey instead." : `Could not retrieve delegation code (HTTP ${response.status}).`);
14862
+ }
14863
+ const result = await response.json();
14864
+ parsed = result.delegation;
14865
+ const delegation = parsed;
14866
+ const jwk = delegation?.jwk;
14867
+ const localJwk = publicJwkForDelegation(options.jwk);
14868
+ if (typeof delegation?.verificationMethod !== "string" || delegation.verificationMethod.split("#")[0] !== did.split("#")[0] || !jwk || typeof jwk !== "object" || Array.isArray(jwk) || jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || typeof jwk.x !== "string" || jwk.x !== localJwk.x || [...PRIVATE_JWK_FIELDS].some((field) => field in jwk)) {
14869
+ throw new Error("Delegation is bound to a different CLI key.");
14870
+ }
14871
+ } else {
14872
+ try {
14873
+ parsed = JSON.parse(trimmed);
14874
+ } catch {
14875
+ try {
14876
+ parsed = JSON.parse(Buffer.from(trimmed, "base64").toString("utf-8"));
14877
+ } catch {
14878
+ throw new Error("Invalid delegation code. Expected JSON, base64-encoded JSON, or XXXX-XXXX.");
14879
+ }
14880
+ }
14881
+ }
14882
+ const invalid = validateDelegationCallbackPayload(parsed);
14883
+ if (invalid) throw new Error(`Invalid delegation code: ${invalid}`);
14884
+ return parsed;
14885
+ }
14559
14886
  function shouldOpenBrowser(options) {
14560
14887
  if (options.noPopup) return false;
14561
14888
  const env = process.env.TC_AUTH_NO_POPUP ?? process.env.TC_NO_POPUP;
@@ -14580,21 +14907,6 @@ async function callbackFlow(did, options = {}) {
14580
14907
  reject(result.error);
14581
14908
  }
14582
14909
  }
14583
- function parsePasteInput(input) {
14584
- const trimmed = input.trim();
14585
- let parsed;
14586
- try {
14587
- parsed = JSON.parse(trimmed);
14588
- } catch {
14589
- const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
14590
- parsed = JSON.parse(decoded);
14591
- }
14592
- const invalid = validateDelegationCallbackPayload(parsed);
14593
- if (invalid) {
14594
- throw new Error(`Invalid delegation code: ${invalid}`);
14595
- }
14596
- return parsed;
14597
- }
14598
14910
  const server = createServer((req, res) => {
14599
14911
  if (req.method === "POST" && req.url === "/callback") {
14600
14912
  let body = "";
@@ -14663,18 +14975,21 @@ async function callbackFlow(did, options = {}) {
14663
14975
  }
14664
14976
  if (process.stdin.isTTY) {
14665
14977
  console.error(`
14666
- If the browser can't connect back, paste the delegation code here:`);
14978
+ If the browser can't connect back, enter the short code or paste the full delegation code here:`);
14667
14979
  rl = createInterface({
14668
14980
  input: process.stdin,
14669
14981
  output: process.stderr
14670
14982
  });
14671
- rl.on("line", (input) => {
14672
- if (settled) return;
14983
+ let resolving = false;
14984
+ rl.on("line", async (input) => {
14985
+ if (settled || resolving) return;
14986
+ resolving = true;
14673
14987
  try {
14674
- const data = parsePasteInput(input);
14675
- settle({ data });
14676
- } catch {
14677
- console.error("Invalid delegation code. Expected JSON or base64-encoded JSON. Try again:");
14988
+ settle({ data: await delegationFromInput(input, did, options) });
14989
+ } catch (error) {
14990
+ console.error(error instanceof Error ? error.message : "Invalid delegation code. Try again:");
14991
+ } finally {
14992
+ resolving = false;
14678
14993
  }
14679
14994
  });
14680
14995
  }
@@ -14701,24 +15016,7 @@ Open this URL in a browser to authenticate:
14701
15016
  if (answered || input.trim() === "") return;
14702
15017
  answered = true;
14703
15018
  rl.close();
14704
- let parsed;
14705
- try {
14706
- parsed = JSON.parse(input.trim());
14707
- } catch {
14708
- try {
14709
- const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
14710
- parsed = JSON.parse(decoded);
14711
- } catch {
14712
- reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
14713
- return;
14714
- }
14715
- }
14716
- const invalid = validateDelegationCallbackPayload(parsed);
14717
- if (invalid) {
14718
- reject(new Error(`Invalid delegation code: ${invalid}`));
14719
- return;
14720
- }
14721
- resolve4(parsed);
15019
+ void delegationFromInput(input, did, options).then(resolve4, reject);
14722
15020
  });
14723
15021
  rl.on("close", () => {
14724
15022
  if (answered) return;
@@ -14733,7 +15031,7 @@ Open this URL in a browser to authenticate:
14733
15031
  }
14734
15032
  ));
14735
15033
  });
14736
- rl.setPrompt("Paste delegation code: ");
15034
+ rl.setPrompt("Enter short code or paste full delegation code: ");
14737
15035
  rl.prompt();
14738
15036
  });
14739
15037
  }
@@ -15017,7 +15315,13 @@ function validateLoginPermissions(permissions) {
15017
15315
  }
15018
15316
  }
15019
15317
  function scopedLoginPermissions(permissions) {
15020
- const space = permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "";
15318
+ return withCapabilitiesRead(permissions, permissions.find((p) => !isRawEncryptionPermission(p)).space ?? "");
15319
+ }
15320
+ function grantRequestPermissions(group, anchorSpace) {
15321
+ const request = group.map((p) => isRawEncryptionPermission(p) ? { ...p, space: ENCRYPTION_MANIFEST_SPACE2, path: canonicalNetworkUrn(p.path) } : p);
15322
+ return withCapabilitiesRead(request, request.find((p) => !isRawEncryptionPermission(p))?.space ?? anchorSpace);
15323
+ }
15324
+ function withCapabilitiesRead(permissions, space) {
15021
15325
  const hasRead = permissions.some((p) => p.service === "tinycloud.capabilities" && p.path === "" && p.actions.includes(CAPABILITIES_READ) && normalizePkhIdentifier(p.space ?? "") === normalizePkhIdentifier(space));
15022
15326
  return hasRead ? permissions : [{ service: "tinycloud.capabilities", space, path: "", actions: [CAPABILITIES_READ] }, ...permissions];
15023
15327
  }
@@ -15904,6 +16208,7 @@ function registerAuthCommand(program) {
15904
16208
  "Grant requested TinyCloud permissions from `tc auth request --grant`.",
15905
16209
  group
15906
16210
  );
16211
+ const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
15907
16212
  let delegationData;
15908
16213
  if (options.device) {
15909
16214
  const approval = await acquireDeviceDelegation({
@@ -15911,7 +16216,7 @@ function registerAuthCommand(program) {
15911
16216
  jwk: key,
15912
16217
  nodeOrigin: ctx.host,
15913
16218
  shareOrigin: DEFAULT_SHARE_ORIGIN,
15914
- permissions: group,
16219
+ permissions: request,
15915
16220
  expiry: parseRequestedExpiry(expiryOption ?? "7d"),
15916
16221
  reason,
15917
16222
  expectedOwner: pinnedOwner(profile),
@@ -15923,17 +16228,17 @@ function registerAuthCommand(program) {
15923
16228
  delegationData = await startAuthFlow(profile.did, {
15924
16229
  jwk: key,
15925
16230
  host: ctx.host,
15926
- permissions: group,
16231
+ permissions: request,
15927
16232
  reason,
15928
16233
  openkeyHost,
15929
16234
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
15930
16235
  noPopup: options.popup === false
15931
16236
  });
15932
16237
  }
15933
- const delegation = portableFromOpenKeyDelegation(delegationData, group, ctx.host, proof);
16238
+ const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
15934
16239
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
15935
16240
  }
15936
- await activateAndStoreOpenKeyGrants(ctx.profile, node, grants, options.manifest ? "manifest" : "cli");
16241
+ await activateAndStoreOpenKeyGrants(ctx.profile, ctx.host, node, grants, options.manifest ? "manifest" : "cli");
15937
16242
  const delegationCids2 = grants.map(({ delegation }) => delegation.cid);
15938
16243
  const expiry2 = grants.at(-1)?.delegation.expiry.toISOString();
15939
16244
  reportDeclined(declined);
@@ -15969,8 +16274,7 @@ function registerAuthCommand(program) {
15969
16274
  for (const delegation of delegations) {
15970
16275
  const covering = permissionsFromDelegation(delegation);
15971
16276
  localEffective.push(...covering);
15972
- const stored = storedAdditionalDelegation(delegation, covering);
15973
- await appendAdditionalDelegation(ctx.profile, stored);
16277
+ await appendAdditionalDelegation(ctx.profile, await cliGrantRecord(node, delegation, covering, ctx.host));
15974
16278
  delegationCids.push(delegation.cid);
15975
16279
  expiry = delegation.expiry.toISOString();
15976
16280
  await appendGrantHistory(ctx.profile, {
@@ -16392,6 +16696,8 @@ async function importRequestBoundDelegation(ctx, artifact) {
16392
16696
  { profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
16393
16697
  artifact
16394
16698
  );
16699
+ const warnings = operationWarnings(result.warnings);
16700
+ const metadata = warnings.length === 0 ? void 0 : { warnings };
16395
16701
  switch (result.status) {
16396
16702
  case "ok": {
16397
16703
  const output2 = result.output;
@@ -16404,22 +16710,25 @@ async function importRequestBoundDelegation(ctx, artifact) {
16404
16710
  permissions: output2.effectivePermissions,
16405
16711
  expiry: output2.expiry
16406
16712
  });
16713
+ outputWarnings(warnings);
16407
16714
  return;
16408
16715
  }
16409
16716
  case "authority_required":
16410
16717
  throw new CLIError(
16411
16718
  "AUTHORITY_REQUIRED",
16412
16719
  "The active session requires additional authority before importing this delegation.",
16413
- ExitCode.PERMISSION_DENIED
16720
+ ExitCode.PERMISSION_DENIED,
16721
+ metadata
16414
16722
  );
16415
16723
  case "setup_required":
16416
16724
  throw new CLIError(
16417
16725
  "SETUP_REQUIRED",
16418
16726
  "The active profile requires setup before importing this delegation.",
16419
- ExitCode.ERROR
16727
+ ExitCode.ERROR,
16728
+ metadata
16420
16729
  );
16421
16730
  case "error":
16422
- throw cliErrorFromService(result.error);
16731
+ throw withSignInHint(cliErrorFromService({ ...result.error, meta: metadata }), ctx.profile, result.context.posture);
16423
16732
  }
16424
16733
  }
16425
16734
  function isStoredDelegationLike(value) {
@@ -16444,9 +16753,10 @@ function normalizePortableDelegation(delegation) {
16444
16753
  }
16445
16754
  return { ...delegation, expiry };
16446
16755
  }
16447
- async function activateAndStoreOpenKeyGrants(profileName, node, grants, source) {
16448
- for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
16756
+ async function activateAndStoreOpenKeyGrants(profileName, host, node, grants, source) {
16449
16757
  if (grants.length === 0) return;
16758
+ const records = await Promise.all(grants.map(({ delegation, effective }) => cliGrantRecord(node, delegation, effective, host)));
16759
+ for (const { delegation } of grants) await node.useRuntimeDelegation(delegation);
16450
16760
  await ProfileManager.withLock(profileName, async () => {
16451
16761
  for (const { delegation, effective } of grants) {
16452
16762
  await appendGrantHistory(profileName, {
@@ -16456,10 +16766,7 @@ async function activateAndStoreOpenKeyGrants(profileName, node, grants, source)
16456
16766
  expiry: delegation.expiry.toISOString()
16457
16767
  });
16458
16768
  }
16459
- await appendAdditionalDelegations(
16460
- profileName,
16461
- grants.map(({ delegation, effective }) => storedAdditionalDelegation(delegation, effective))
16462
- );
16769
+ await appendAdditionalDelegations(profileName, records);
16463
16770
  });
16464
16771
  }
16465
16772
  async function ensureDelegationAuthority(params) {
@@ -16482,20 +16789,22 @@ async function ensureDelegationAuthority(params) {
16482
16789
  expectedOwner: pinnedOwner(params.profile),
16483
16790
  expiry: expiryCap
16484
16791
  };
16792
+ const anchorSpace = params.anchorSpace ?? params.profile.spaceId ?? params.profile.spaceName;
16485
16793
  const grants = [];
16486
16794
  for (const group of groupPermissionsBySpace(params.requested)) {
16795
+ const request = grantRequestPermissions(group, anchorSpace);
16487
16796
  const delegationData = await acquireOpenKey(params.profile.did, {
16488
16797
  jwk: key,
16489
16798
  host: params.ctx.host,
16490
- permissions: group,
16799
+ permissions: request,
16491
16800
  reason: permissionGrantReason(params.reason, group),
16492
16801
  openkeyHost,
16493
16802
  expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap)
16494
16803
  });
16495
- const delegation = portableFromOpenKeyDelegation(delegationData, group, params.ctx.host, proof);
16804
+ const delegation = portableFromOpenKeyDelegation(delegationData, request, params.ctx.host, proof);
16496
16805
  grants.push({ delegation, effective: permissionsFromDelegation(delegation) });
16497
16806
  }
16498
- await activateAndStoreOpenKeyGrants(params.ctx.profile, params.node, grants, "cli");
16807
+ await activateAndStoreOpenKeyGrants(params.ctx.profile, params.ctx.host, params.node, grants, "cli");
16499
16808
  return;
16500
16809
  }
16501
16810
  if (isInteractive()) {
@@ -16517,7 +16826,7 @@ async function ensureDelegationAuthority(params) {
16517
16826
  const covering = permissionsFromDelegation(delegation);
16518
16827
  await appendAdditionalDelegation(
16519
16828
  params.ctx.profile,
16520
- storedAdditionalDelegation(delegation, covering)
16829
+ await cliGrantRecord(params.node, delegation, covering, params.ctx.host)
16521
16830
  );
16522
16831
  await appendGrantHistory(params.ctx.profile, {
16523
16832
  addedCaps: covering,
@@ -16649,7 +16958,8 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16649
16958
  }
16650
16959
  const returnedSpace = data.spaceId;
16651
16960
  const effective = session.permissions;
16652
- const primary = effective.find((permission) => !isRawEncryptionPermission(permission)) ?? effective[0];
16961
+ const spaced = effective.filter((permission) => !isRawEncryptionPermission(permission));
16962
+ const primary = spaced.find((permission) => permission.service !== "tinycloud.capabilities") ?? spaced[0] ?? effective[0];
16653
16963
  const resources = effective.map((permission) => ({
16654
16964
  service: permission.service.slice("tinycloud.".length),
16655
16965
  space: isVerifiedRawEncryptionPermission(permission) ? "encryption" : returnedSpace,
@@ -16669,7 +16979,9 @@ function portableFromOpenKeyDelegation(data, requested, host, proof) {
16669
16979
  delegateDID: data.verificationMethod,
16670
16980
  ownerAddress: ownerParts[4],
16671
16981
  chainId: Number(ownerParts[3]),
16672
- host
16982
+ host,
16983
+ // Verified above; replay rebuilds the CACAO from it before trusting the grant.
16984
+ siweProof: { siwe: data.siwe, signature: data.signature }
16673
16985
  };
16674
16986
  }
16675
16987
  async function rotateAuthKey(profileName, host, options = {}) {
@@ -18394,6 +18706,7 @@ async function runSecretOperation(params) {
18394
18706
  reason: secretPermissionReason(params.action, params.name),
18395
18707
  yes: true,
18396
18708
  force: true,
18709
+ anchorSpace: params.space ?? SECRETS_SPACE3,
18397
18710
  openKeyAcquisition: params.openKeyAcquisition
18398
18711
  })
18399
18712
  );
@@ -18478,7 +18791,26 @@ async function runSecretOperationAttempt(label, operation) {
18478
18791
  throw error;
18479
18792
  }
18480
18793
  }
18794
+ function withOperationWarnings(error, warnings) {
18795
+ if (warnings === void 0 || warnings.length === 0) return error;
18796
+ const cliError = wrapError(error);
18797
+ cliError.metadata = { ...cliError.metadata, warnings };
18798
+ return cliError;
18799
+ }
18481
18800
  async function invokeCanonicalSecretGet(params) {
18801
+ const warnings = /* @__PURE__ */ new Map();
18802
+ const collect = (result) => {
18803
+ for (const warning of result.warnings ?? []) warnings.set(JSON.stringify(warning), warning);
18804
+ return result;
18805
+ };
18806
+ try {
18807
+ const result = await canonicalSecretGet(params, collect);
18808
+ return warnings.size === 0 ? result : { ...result, warnings: [...warnings.values()] };
18809
+ } catch (error) {
18810
+ throw withOperationWarnings(error, [...warnings.values()]);
18811
+ }
18812
+ }
18813
+ async function canonicalSecretGet(params, collect) {
18482
18814
  const auth = authOptions(params.options);
18483
18815
  let ownerNode;
18484
18816
  if (!auth?.privateKey) {
@@ -18501,10 +18833,10 @@ async function invokeCanonicalSecretGet(params) {
18501
18833
  ...params.scope === void 0 ? {} : { scope: params.scope },
18502
18834
  ...params.space === void 0 ? {} : { space: params.space }
18503
18835
  };
18504
- const invoke = () => withSpinner(
18836
+ const invoke = async () => collect(await withSpinner(
18505
18837
  params.label,
18506
18838
  () => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation2("tinycloud.secrets.get", 1, target, input)
18507
- );
18839
+ ));
18508
18840
  let first = await invoke();
18509
18841
  if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
18510
18842
  const profile2 = await ProfileManager.getProfile(params.ctx.profile);
@@ -18542,6 +18874,7 @@ async function invokeCanonicalSecretGet(params) {
18542
18874
  reason: secretPermissionReason("get", params.name),
18543
18875
  yes: true,
18544
18876
  force: true,
18877
+ anchorSpace: params.space ?? SECRETS_SPACE3,
18545
18878
  openKeyAcquisition: params.openKeyAcquisition
18546
18879
  })
18547
18880
  );
@@ -18553,7 +18886,7 @@ function throwCanonicalSecretGetError(result, name) {
18553
18886
  throw new CLIError(
18554
18887
  "PERMISSION_DENIED",
18555
18888
  "Permission denied while reading secret",
18556
- ExitCode.ERROR
18889
+ ExitCode.PERMISSION_DENIED
18557
18890
  );
18558
18891
  case "setup_required":
18559
18892
  throw new CLIError(
@@ -18575,11 +18908,10 @@ ${result.setup.message}`, setup: result.setup }
18575
18908
  if (result.error.code === "NODE_UNREACHABLE") {
18576
18909
  throw new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
18577
18910
  }
18578
- throw new CLIError(
18579
- result.error.code,
18580
- result.error.message,
18581
- result.error.code === "PERMISSION_HINT_INVALID" ? ExitCode.PERMISSION_DENIED : ExitCode.ERROR
18582
- );
18911
+ if (result.error.code === "PERMISSION_HINT_INVALID") {
18912
+ throw new CLIError(result.error.code, result.error.message, ExitCode.PERMISSION_DENIED);
18913
+ }
18914
+ throw withSignInHint(cliErrorFromService(result.error), result.context.profile, result.context.posture);
18583
18915
  case "ok":
18584
18916
  throw new Error("Expected a failed canonical secret result.");
18585
18917
  }
@@ -19195,19 +19527,26 @@ function registerSecretsCommand(program, openKeyAcquisition) {
19195
19527
  openKeyAcquisition
19196
19528
  });
19197
19529
  if (result.status !== "ok") {
19198
- throwCanonicalSecretGetError(result, name);
19530
+ try {
19531
+ throwCanonicalSecretGetError(result, name);
19532
+ } catch (error) {
19533
+ throw withOperationWarnings(error, result.warnings);
19534
+ }
19199
19535
  }
19200
19536
  const value = result.output.value;
19201
- if (options.output) {
19202
- await writeSecretFile(options.output, value);
19203
- outputJson({ name, written: options.output });
19204
- return;
19205
- }
19206
- if (options.raw || options.valueOnly) {
19207
- process.stdout.write(value);
19208
- return;
19537
+ try {
19538
+ if (options.output) {
19539
+ await writeSecretFile(options.output, value);
19540
+ outputJson({ name, written: options.output });
19541
+ } else if (options.raw || options.valueOnly) {
19542
+ process.stdout.write(value);
19543
+ } else {
19544
+ outputJson({ name, value });
19545
+ }
19546
+ } catch (error) {
19547
+ throw withOperationWarnings(error, result.warnings);
19209
19548
  }
19210
- outputJson({ name, value });
19549
+ outputWarnings(operationWarnings(result.warnings));
19211
19550
  } catch (error) {
19212
19551
  handleError(error);
19213
19552
  }