@tinycloud/cli 1.1.0-beta.8 → 1.1.0-beta.9

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,7 +72,7 @@ var init_theme = __esm({
72
72
 
73
73
  // src/output/formatter.ts
74
74
  import ora from "ora";
75
- function outputError(code3, message, hint) {
75
+ function outputError(code3, message, hint, meta) {
76
76
  if (isInteractive()) {
77
77
  process.stderr.write(
78
78
  `${theme.error("\u2717")} ${theme.label(code3)}: ${message}
@@ -89,6 +89,7 @@ function outputError(code3, message, hint) {
89
89
  error: { code: code3, message }
90
90
  };
91
91
  if (hint) payload.error.hint = hint;
92
+ if (meta) payload.error.meta = meta;
92
93
  process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
93
94
  }
94
95
  }
@@ -111,23 +112,23 @@ var init_formatter = __esm({
111
112
  import { readFileSync, readdirSync } from "fs";
112
113
  import { join } from "path";
113
114
  import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
115
+ import { authorizationVerdictOf, parseCapabilityResource, SERVICE_LONG_TO_SHORT, validatedCapabilityOf } from "@tinycloud/sdk-core";
114
116
  function setActiveProfileName(name) {
115
117
  activeProfileName = name;
116
118
  }
117
119
  function wrapError(error) {
120
+ if (error instanceof CLIError) return error;
118
121
  const message = error instanceof Error ? error.message : String(error);
119
- if (message.includes("Missing private key parameter in JWK")) {
120
- const profileName = activeProfileName ?? process.env.TC_PROFILE ?? DEFAULT_PROFILE;
121
- return new CLIError(
122
- "AUTH_REQUIRED",
123
- `Profile "${profileName}" cannot restore its session because its private key material is missing.`,
124
- ExitCode.AUTH_REQUIRED,
125
- {
126
- hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey`
127
- }
128
- );
122
+ const verdict = authorizationVerdictOf(error);
123
+ if (verdict === "unauthenticated") {
124
+ return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
125
+ }
126
+ if (verdict === "forbidden") {
127
+ return new CLIError("PERMISSION_DENIED", message, ExitCode.PERMISSION_DENIED);
128
+ }
129
+ if (verdict === void 0 && message.includes("Missing private key parameter in JWK")) {
130
+ return missingPrivateKeyError();
129
131
  }
130
- if (error instanceof CLIError) return error;
131
132
  if (error instanceof ProfileLockTimeoutError) {
132
133
  return new CLIError(
133
134
  "PROFILE_LOCK_TIMEOUT",
@@ -139,13 +140,13 @@ function wrapError(error) {
139
140
  if (error instanceof ProfileDeletedError) {
140
141
  return new CLIError("PROFILE_NOT_FOUND", message);
141
142
  }
142
- if (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired")) {
143
+ if (verdict === void 0 && (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired"))) {
143
144
  return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
144
145
  }
145
146
  if (message.includes("NOT_FOUND") || message.includes("KV_NOT_FOUND")) {
146
147
  return new CLIError("NOT_FOUND", message, ExitCode.NOT_FOUND);
147
148
  }
148
- if (message.includes("PERMISSION_DENIED")) {
149
+ if (verdict === void 0 && message.includes("PERMISSION_DENIED")) {
149
150
  return new CLIError("PERMISSION_DENIED", message, ExitCode.PERMISSION_DENIED);
150
151
  }
151
152
  if (message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("fetch failed")) {
@@ -153,39 +154,51 @@ function wrapError(error) {
153
154
  }
154
155
  return new CLIError("ERROR", message, ExitCode.ERROR);
155
156
  }
157
+ function missingPrivateKeyError() {
158
+ const profileName = activeProfileName ?? process.env.TC_PROFILE ?? DEFAULT_PROFILE;
159
+ return new CLIError(
160
+ "AUTH_REQUIRED",
161
+ `Profile "${profileName}" cannot restore its session because its private key material is missing.`,
162
+ ExitCode.AUTH_REQUIRED,
163
+ { hint: `Sign in again with: tc --profile ${profileName} auth login --method openkey` }
164
+ );
165
+ }
156
166
  function handleError(error) {
157
167
  const cliError = wrapError(error);
158
168
  const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
159
169
  const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
160
- outputError(cliError.code, cliError.message, hint);
170
+ const authMeta = cliError.code === "AUTH_REQUIRED" || cliError.code === "PERMISSION_DENIED" ? cliError.metadata : void 0;
171
+ const meta = {};
172
+ if (cliError.status !== void 0 && authMeta !== void 0) meta.status = cliError.status;
173
+ const capability = validatedCapabilityOf({ meta: authMeta });
174
+ if (capability) {
175
+ meta.resource = capability.resource;
176
+ meta.requiredAction = capability.requiredAction;
177
+ }
178
+ outputError(cliError.code, cliError.message, hint, Object.keys(meta).length ? meta : void 0);
161
179
  process.exit(cliError.exitCode);
162
180
  }
163
181
  function buildAuthHint(error) {
164
- const resource = error.metadata?.resource;
165
- const requiredAction = error.metadata?.requiredAction;
166
- if (typeof resource !== "string" || typeof requiredAction !== "string") {
167
- return void 0;
168
- }
169
- const spec = capSpecFromAuthMeta(resource, requiredAction);
182
+ const capability = validatedCapabilityOf({ meta: error.metadata });
183
+ if (!capability) return void 0;
184
+ const spec = capSpecFromAuthMeta(capability.resource, capability.requiredAction);
170
185
  if (!spec) return void 0;
171
186
  return [
172
187
  "The active session is missing a TinyCloud capability.",
173
- `Request it with: tc auth request --cap "${spec}"`,
188
+ `Request it with: tc auth request --cap '${spec.replaceAll("'", "'\\''")}'`,
174
189
  "Then retry the original command."
175
190
  ].join("\n");
176
191
  }
177
192
  function capSpecFromAuthMeta(resource, action) {
178
- const slash = resource.indexOf("/");
179
- if (slash <= 0 || slash === resource.length - 1) return void 0;
180
- const spaceUri = resource.slice(0, slash);
181
- const rest = resource.slice(slash + 1);
182
- const nextSlash = rest.indexOf("/");
183
- if (nextSlash <= 0) return void 0;
184
- const serviceShort = rest.slice(0, nextSlash);
185
- const path = rest.slice(nextSlash + 1);
186
- const actionName = action.includes("/") ? action.slice(action.indexOf("/") + 1) : action;
187
- const spaceName = spaceUri.startsWith("tinycloud:") ? spaceUri.slice(spaceUri.lastIndexOf(":") + 1) : spaceUri;
188
- return `tinycloud.${serviceShort}:${spaceName}:${path}:${actionName}`;
193
+ const slash = action.indexOf("/");
194
+ if (slash < 0) return void 0;
195
+ const longService = action.slice(0, slash);
196
+ const serviceShort = SERVICE_LONG_TO_SHORT[longService];
197
+ if (!serviceShort) return void 0;
198
+ const parsed = parseCapabilityResource(resource, serviceShort);
199
+ if (!parsed) return void 0;
200
+ const spaceName = parsed.space.slice(parsed.space.lastIndexOf(":") + 1);
201
+ return `${longService}:${spaceName}:${parsed.path}:${action.slice(slash + 1)}`;
189
202
  }
190
203
  function buildNetworkHint() {
191
204
  const readHost = (name) => {
@@ -239,7 +252,9 @@ var init_errors = __esm({
239
252
  this.exitCode = exitCode;
240
253
  this.metadata = metadata;
241
254
  this.name = "CLIError";
255
+ if (typeof metadata?.status === "number" && Number.isInteger(metadata.status) && metadata.status >= 400 && metadata.status <= 599) this.status = metadata.status;
242
256
  }
257
+ status;
243
258
  };
244
259
  }
245
260
  });
@@ -15019,7 +15034,6 @@ async function notifyShare(input) {
15019
15034
  };
15020
15035
  }
15021
15036
  let attempts = 0;
15022
- let lastError;
15023
15037
  while (attempts < attemptsLimit) {
15024
15038
  if (input.signal?.aborted) throw new ShareNotifyError("share delivery was cancelled");
15025
15039
  attempts += 1;
@@ -15046,10 +15060,19 @@ async function notifyShare(input) {
15046
15060
  reason: "delivery-window-expired"
15047
15061
  };
15048
15062
  }
15049
- lastError = error;
15063
+ if (error !== null && typeof error === "object" && "status" in error && (error.status === 401 || error.status === 403)) {
15064
+ return {
15065
+ protocol: "tinycloud-share",
15066
+ version: 1,
15067
+ shareId: input.shareId,
15068
+ state: "partial-failure",
15069
+ idempotencyKey,
15070
+ attempts,
15071
+ retryable: false
15072
+ };
15073
+ }
15050
15074
  }
15051
15075
  }
15052
- void lastError;
15053
15076
  return { protocol: "tinycloud-share", version: 1, shareId: input.shareId, state: "partial-failure", idempotencyKey, attempts, retryable: true };
15054
15077
  }
15055
15078
  async function defaultIdempotencyKey(shareId, recipient) {