@tinycloud/cli 1.1.0 → 1.2.0-beta.0

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.
package/dist/index.js CHANGED
@@ -72,12 +72,51 @@ var init_theme = __esm({
72
72
  }
73
73
  });
74
74
 
75
+ // src/output/warnings.ts
76
+ function warningKey(warning) {
77
+ return JSON.stringify([warning.code, warning.reason, warning.grantCid ?? null]);
78
+ }
79
+ function mergeWarnings(first, second) {
80
+ const merged = [...first];
81
+ const counts = /* @__PURE__ */ new Map();
82
+ for (const warning of first) {
83
+ const key = warningKey(warning);
84
+ counts.set(key, (counts.get(key) ?? 0) + 1);
85
+ }
86
+ const seen = /* @__PURE__ */ new Map();
87
+ for (const warning of second) {
88
+ const key = warningKey(warning);
89
+ const occurrence = (seen.get(key) ?? 0) + 1;
90
+ seen.set(key, occurrence);
91
+ if (occurrence > (counts.get(key) ?? 0)) merged.push(warning);
92
+ }
93
+ return merged;
94
+ }
95
+ function recordWarnings(warnings) {
96
+ if (warnings.length === 0) return;
97
+ const holder = globalThis;
98
+ holder[PENDING_WARNINGS] = mergeWarnings(holder[PENDING_WARNINGS] ?? [], warnings);
99
+ }
100
+ function drainWarnings() {
101
+ const holder = globalThis;
102
+ const warnings = holder[PENDING_WARNINGS] ?? [];
103
+ delete holder[PENDING_WARNINGS];
104
+ return warnings;
105
+ }
106
+ var PENDING_WARNINGS;
107
+ var init_warnings = __esm({
108
+ "src/output/warnings.ts"() {
109
+ "use strict";
110
+ PENDING_WARNINGS = /* @__PURE__ */ Symbol.for("tinycloud.cli.pendingWarnings.v1");
111
+ }
112
+ });
113
+
75
114
  // src/output/formatter.ts
76
115
  import ora from "ora";
77
116
  function warningLine(warnings) {
78
117
  const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
79
118
  const subject = warnings.length === 1 ? "A stored grant was" : `${warnings.length} stored grants were`;
80
- return `${subject} not used (${which}). Approve access again when a command needs it; the old grant expires on its own.`;
119
+ return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with tc auth grants remove <cid>.`;
81
120
  }
82
121
  function outputError(code3, message, hint, details = {}) {
83
122
  const { meta, warnings = [] } = details;
@@ -92,7 +131,7 @@ function outputError(code3, message, hint, details = {}) {
92
131
  `);
93
132
  }
94
133
  }
95
- if (warnings.length > 0) process.stderr.write(` ${theme.warn(warningLine(warnings))}
134
+ if (warnings.length > 0) process.stderr.write(` ${theme.warn("!")} ${warningLine(warnings)}
96
135
  `);
97
136
  } else {
98
137
  const payload = {
@@ -104,6 +143,13 @@ function outputError(code3, message, hint, details = {}) {
104
143
  process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
105
144
  }
106
145
  }
146
+ function outputWarnings(warnings) {
147
+ const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
148
+ if (combined.length === 0) return;
149
+ process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: combined })}
150
+ ` : `${theme.warn("!")} ${warningLine(combined)}
151
+ `);
152
+ }
107
153
  function operationWarnings(value) {
108
154
  if (!Array.isArray(value)) return [];
109
155
  return value.flatMap((warning) => {
@@ -129,6 +175,7 @@ var init_formatter = __esm({
129
175
  "src/output/formatter.ts"() {
130
176
  "use strict";
131
177
  init_theme();
178
+ init_warnings();
132
179
  WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
133
180
  WARNING_REASON = /^[a-z][a-z_]{0,63}$/;
134
181
  DELEGATION_CID = /^bafkr4i[a-z2-7]{52}$/;
@@ -286,6 +333,9 @@ function handleError(error) {
286
333
  const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
287
334
  const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
288
335
  const meta = {};
336
+ if (cliError.code === "GRANT_REVOKED_LOCAL_REMOVE_FAILED" && typeof cliError.metadata?.causeCode === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(cliError.metadata.causeCode)) {
337
+ meta.causeCode = cliError.metadata.causeCode;
338
+ }
289
339
  if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
290
340
  const capability = validatedCapabilityOf({ meta: authMeta });
291
341
  if (capability) {
@@ -294,7 +344,7 @@ function handleError(error) {
294
344
  }
295
345
  outputError(cliError.code, cliError.message, hint, {
296
346
  ...Object.keys(meta).length ? { meta } : {},
297
- warnings: operationWarnings(cliError.metadata?.warnings)
347
+ warnings: mergeWarnings(drainWarnings(), operationWarnings(cliError.metadata?.warnings))
298
348
  });
299
349
  process.exit(cliError.exitCode);
300
350
  }
@@ -366,6 +416,7 @@ var init_errors = __esm({
366
416
  init_constants();
367
417
  init_formatter();
368
418
  init_storage();
419
+ init_warnings();
369
420
  CLIError = class extends Error {
370
421
  constructor(code3, message, exitCode = ExitCode.ERROR, metadata) {
371
422
  super(message);
@@ -5845,59 +5896,14 @@ import {
5845
5896
  withProfileLock as withProfileLock2,
5846
5897
  writeJsonAtomic
5847
5898
  } from "@tinycloud/operations/state";
5848
- async function loadAdditionalDelegations(profile) {
5849
- return readAdditionalDelegations(profile);
5850
- }
5851
5899
  async function replayAdditionalDelegations(node, profile, options) {
5852
- const {
5853
- operationSpaceResolver,
5854
- prepareStoredDelegationReplay,
5855
- replayStoredDelegation,
5856
- storedDelegationKind
5857
- } = await import("@tinycloud/operations/delegation-binding");
5858
- const activator = node;
5859
- const migrated = await prepareStoredDelegationReplay(profile, activator, {
5900
+ const { operationSpaceResolver, replayProfileDelegations } = await import("@tinycloud/operations/delegation-binding");
5901
+ const { warnings } = await replayProfileDelegations(profile, node, {
5860
5902
  host: options.host,
5861
- migrate: options.migrate
5903
+ migrate: options.migrate,
5904
+ resolveSpace: operationSpaceResolver(node, options.ownerSpace)
5862
5905
  });
5863
- const resolveSpace = operationSpaceResolver(node, options.ownerSpace);
5864
- const entries = await loadAdditionalDelegations(profile);
5865
- for (const stored of entries) {
5866
- const kind = storedDelegationKind(stored);
5867
- if (kind === "compact" || kind === "signed-login") {
5868
- const replay = await replayStoredDelegation(activator, stored, {
5869
- host: options.host,
5870
- migrated,
5871
- resolveSpace
5872
- });
5873
- if (replay.status === "skipped" && process.env.TC_DEBUG_REPLAY === "1") {
5874
- process.stderr.write(`[replay] skipping a stored delegation: ${replay.reason}
5875
- `);
5876
- }
5877
- continue;
5878
- }
5879
- if (kind === "refused") {
5880
- if (process.env.TC_DEBUG_REPLAY === "1") {
5881
- process.stderr.write("[replay] skipping a malformed stored record or one bound to a request it cannot be held to\n");
5882
- }
5883
- continue;
5884
- }
5885
- const entry = stored;
5886
- const expiry = entry.delegation.expiry instanceof Date ? entry.delegation.expiry : new Date(entry.delegation.expiry);
5887
- if (expiry.getTime() <= Date.now()) continue;
5888
- try {
5889
- await node.useRuntimeDelegation({
5890
- ...entry.delegation,
5891
- delegationHeader: { Authorization: entry.delegation.delegationHeader.Authorization },
5892
- expiry
5893
- });
5894
- } catch (err) {
5895
- if (process.env.TC_DEBUG_REPLAY === "1") {
5896
- process.stderr.write(`[replay] skipping ${entry.delegation.cid}: ${err.message}
5897
- `);
5898
- }
5899
- }
5900
- }
5906
+ return warnings;
5901
5907
  }
5902
5908
  var init_permissions = __esm({
5903
5909
  "src/lib/permissions.ts"() {
@@ -5922,6 +5928,7 @@ __export(sdk_exports, {
5922
5928
  createSDKInstance: () => createSDKInstance,
5923
5929
  ensureAuthenticated: () => ensureAuthenticated,
5924
5930
  jwkHasPrivateParameter: () => jwkHasPrivateParameter,
5931
+ restoreProfileSessionNode: () => restoreProfileSessionNode,
5925
5932
  selectSignerJwk: () => selectSignerJwk
5926
5933
  });
5927
5934
  import { TinyCloudNode } from "@tinycloud/node-sdk";
@@ -5960,6 +5967,44 @@ async function restoreProfileSession(node, profileName, profile, sessionData) {
5960
5967
  throw error;
5961
5968
  }
5962
5969
  }
5970
+ function persistedSessionData(profileName, profile, session, key) {
5971
+ return {
5972
+ delegationHeader: session.delegationHeader,
5973
+ delegationCid: session.delegationCid,
5974
+ spaceId: session.spaceId,
5975
+ jwk: signerJwkForProfile(profileName, session.jwk, key),
5976
+ verificationMethod: session.verificationMethod ?? profile.sessionDid ?? profile.did,
5977
+ address: session.address,
5978
+ chainId: session.chainId,
5979
+ siwe: session.siwe,
5980
+ signature: session.signature
5981
+ };
5982
+ }
5983
+ async function restoreProfileSessionNode(ctx) {
5984
+ const profile = await ProfileManager.getProfile(ctx.profile);
5985
+ const session = await ProfileManager.getSession(ctx.profile);
5986
+ if (!session) return { unavailable: "session_missing" };
5987
+ if (typeof session.delegationCid !== "string" || typeof session.spaceId !== "string" || session.delegationHeader === null || typeof session.delegationHeader !== "object" || !("Authorization" in session.delegationHeader) || typeof session.delegationHeader.Authorization !== "string") {
5988
+ return { unavailable: "session_invalid" };
5989
+ }
5990
+ if (typeof session.expiresAt === "string" && Date.parse(session.expiresAt) <= Date.now()) {
5991
+ return { unavailable: "session_expired" };
5992
+ }
5993
+ const key = await ProfileManager.getKey(ctx.profile);
5994
+ try {
5995
+ const node = new TinyCloudNode({
5996
+ host: ctx.host,
5997
+ ...profile.authMethod === "local" && profile.privateKey ? { privateKey: profile.privateKey } : {}
5998
+ });
5999
+ await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
6000
+ return { node };
6001
+ } catch (error) {
6002
+ if (error instanceof CLIError && error.metadata?.reason === "session_expired") {
6003
+ return { unavailable: "session_expired" };
6004
+ }
6005
+ return { unavailable: "session_invalid" };
6006
+ }
6007
+ }
5963
6008
  async function createSDKInstance(ctx, options) {
5964
6009
  const profile = options?.privateKey ? await ProfileManager.getProfile(ctx.profile).catch(() => null) : await ProfileManager.getProfile(ctx.profile);
5965
6010
  const session = await ProfileManager.getSession(ctx.profile);
@@ -5977,56 +6022,35 @@ async function createSDKInstance(ctx, options) {
5977
6022
  host: ctx.host,
5978
6023
  privateKey: effectivePrivateKey
5979
6024
  });
5980
- let restoredOwnSession2 = false;
5981
- if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
5982
- await restoreProfileSession(node2, ctx.profile, profile, {
5983
- delegationHeader: session.delegationHeader,
5984
- delegationCid: session.delegationCid,
5985
- spaceId: session.spaceId,
5986
- jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
5987
- verificationMethod: session.verificationMethod ?? profile?.sessionDid ?? profile?.did,
5988
- address: session.address,
5989
- chainId: session.chainId,
5990
- siwe: session.siwe,
5991
- signature: session.signature
5992
- });
5993
- restoredOwnSession2 = true;
6025
+ const restoredOwnSession = Boolean(session && session.delegationHeader && session.delegationCid && session.spaceId);
6026
+ if (restoredOwnSession) {
6027
+ await restoreProfileSession(node2, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
5994
6028
  } else {
5995
6029
  await node2.signIn();
5996
6030
  }
5997
- await replayAdditionalDelegations(node2, ctx.profile, {
5998
- host: ctx.host,
5999
- ownerSpace: profile.spaceId,
6000
- migrate: restoredOwnSession2 && options?.privateKey === void 0
6001
- });
6031
+ if (restoredOwnSession) {
6032
+ recordWarnings(await replayAdditionalDelegations(node2, ctx.profile, {
6033
+ host: ctx.host,
6034
+ ownerSpace: profile.spaceId,
6035
+ migrate: options?.privateKey === void 0
6036
+ }));
6037
+ }
6002
6038
  return node2;
6003
6039
  }
6004
6040
  const node = new TinyCloudNode({
6005
6041
  host: ctx.host,
6006
6042
  privateKey: options?.privateKey
6007
6043
  });
6008
- let restoredOwnSession = false;
6009
6044
  if (options?.privateKey) {
6010
6045
  await node.signIn();
6011
6046
  } else if (session && session.delegationHeader && session.delegationCid && session.spaceId) {
6012
- await restoreProfileSession(node, ctx.profile, profile, {
6013
- delegationHeader: session.delegationHeader,
6014
- delegationCid: session.delegationCid,
6015
- spaceId: session.spaceId,
6016
- jwk: signerJwkForProfile(ctx.profile, session.jwk, key),
6017
- verificationMethod: session.verificationMethod ?? profile?.did,
6018
- address: session.address,
6019
- chainId: session.chainId,
6020
- siwe: session.siwe,
6021
- signature: session.signature
6022
- });
6023
- restoredOwnSession = true;
6047
+ await restoreProfileSession(node, ctx.profile, profile, persistedSessionData(ctx.profile, profile, session, key));
6048
+ recordWarnings(await replayAdditionalDelegations(node, ctx.profile, {
6049
+ host: ctx.host,
6050
+ ownerSpace: profile?.spaceId,
6051
+ migrate: true
6052
+ }));
6024
6053
  }
6025
- await replayAdditionalDelegations(node, ctx.profile, {
6026
- host: ctx.host,
6027
- ownerSpace: profile?.spaceId,
6028
- migrate: restoredOwnSession
6029
- });
6030
6054
  return node;
6031
6055
  }
6032
6056
  async function bootstrapDelegatedSession(ctx, delegation) {
@@ -6154,6 +6178,7 @@ var init_sdk = __esm({
6154
6178
  init_constants();
6155
6179
  init_permissions();
6156
6180
  init_session_expired();
6181
+ init_warnings();
6157
6182
  BOOTSTRAP_PROFILE_FIELDS = ["sessionDid", "spaceId"];
6158
6183
  }
6159
6184
  });
@@ -18148,6 +18173,9 @@ program.hook("preAction", async (thisCommand) => {
18148
18173
  }
18149
18174
  }
18150
18175
  });
18176
+ program.hook("postAction", () => {
18177
+ outputWarnings([]);
18178
+ });
18151
18179
  configureShareCommandServices({
18152
18180
  targetAdapter: shareAuthority.targetAdapter,
18153
18181
  records: shareAuthority.records,