@maxceem/agw 0.1.8 → 0.1.10

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.
Files changed (3) hide show
  1. package/README.md +48 -9
  2. package/dist/agw.mjs +1004 -218
  3. package/package.json +13 -2
package/dist/agw.mjs CHANGED
@@ -6,10 +6,10 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // <define:__AGW_MANIFEST__>
9
- var define_AGW_MANIFEST_default = { version: "0.1.8", wrangler: "4.96.0", upgradeFrom: ["0.1.8", "0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.1.8/gateway-0.1.8.tar.gz", sha256: "a4123a9186809038e7613f80d299637075eee47016b7aa309fd82f3a6a978ee9", manifest: "f6b6451ba84b7409c5f1e239e57319aa2e92bb54739bb07f27171d652c68b326" } };
9
+ var define_AGW_MANIFEST_default = { version: "0.1.10", wrangler: "4.96.0", upgradeFrom: ["0.1.10", "0.1.9", "0.1.8", "0.1.7", "0.1.6", "0.1.5", "0.1.4", "0.1.3", "0.1.2", "0.1.1", "0.1.0"], release: { url: "https://github.com/maxceem/app-ai-gateway/releases/download/v0.1.10/gateway-0.1.10.tar.gz", sha256: "39a8b65f05d666e323a08db70c90e345dd3eec698cbf7491b288dd5b9f864efa", manifest: "5f58bcdb8dee3dd4ca72f6da3a52ca086f2f7529cd39bd0e331a72779b8fd506" } };
10
10
 
11
11
  // src/main.ts
12
- import { Writable } from "node:stream";
12
+ import { Writable as Writable2 } from "node:stream";
13
13
 
14
14
  // ../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
15
15
  var external_exports = {};
@@ -15018,7 +15018,7 @@ var ErrorResponseSchema = external_exports.object({
15018
15018
  code: external_exports.string(),
15019
15019
  message: external_exports.string(),
15020
15020
  data: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).optional().meta({
15021
- description: "Machine-readable facts about this rejection, present only where the code alone is not actionable. A billing_request_quota_exceeded rejection carries periodId, periodStart, periodEnd, limit, used, and resetAt; an app_rate_limited or app_budget_exhausted rejection carries scope, either user or app."
15021
+ description: "Machine-readable facts about this rejection, present only where the code alone is not actionable. A billing_request_quota_exceeded rejection carries periodId, periodStart, periodEnd, limit, used, and resetAt; a billing_plan_limit_reached rejection carries limit and used; an app_rate_limited or app_budget_exhausted rejection carries scope, either user or app; a rate_limited rejection carries scope naming the endpoint policy that refused, plus limit, windowSeconds, retryAfterSeconds and resetAt."
15022
15022
  })
15023
15023
  })
15024
15024
  }).meta({ id: "ErrorResponse" });
@@ -15751,12 +15751,27 @@ var CliErrorDetailsSchema = external_exports.object({
15751
15751
  providerGatewayId: external_exports.string().optional(),
15752
15752
  revoked: external_exports.boolean().optional(),
15753
15753
  storagePath: external_exports.string().optional(),
15754
+ /** Which ceiling refused the request, for the codes that name one. */
15755
+ scope: external_exports.string().optional(),
15756
+ limit: external_exports.number().optional(),
15757
+ used: external_exports.number().optional(),
15758
+ windowSeconds: external_exports.number().optional(),
15759
+ retryAfterSeconds: external_exports.number().optional(),
15760
+ resetAt: external_exports.string().optional(),
15754
15761
  id: external_exports.string().optional(),
15755
15762
  state: external_exports.string().optional(),
15756
15763
  expiresAt: external_exports.string().optional(),
15757
15764
  url: external_exports.string().optional(),
15758
15765
  /** The tail of a failed subprocess's own output, for failures it explains. */
15759
- output: external_exports.string().optional()
15766
+ output: external_exports.string().optional(),
15767
+ /**
15768
+ * What Cloudflare itself said about a refused API call: each error's message
15769
+ * and its code. Carried because the things that refuse a deployment — an
15770
+ * account ceiling, a permission a token lacks — are named only there, and
15771
+ * wrangler reports an API refusal it aggregates as the bare sentence that a
15772
+ * request failed.
15773
+ */
15774
+ apiErrors: external_exports.string().optional()
15760
15775
  });
15761
15776
 
15762
15777
  // src/apps.ts
@@ -16035,6 +16050,11 @@ function validate(schema, value) {
16035
16050
  // src/input.ts
16036
16051
  import { createInterface } from "node:readline/promises";
16037
16052
  import { stdin, stderr } from "node:process";
16053
+ var MASK_LIMIT = 64;
16054
+ function maskDelta(shown, length) {
16055
+ const target = Math.min(length, MASK_LIMIT);
16056
+ return target > shown ? "*".repeat(target - shown) : "\b \b".repeat(shown - target);
16057
+ }
16038
16058
  async function prompt(label, flags, { hidden = false, defaultValue } = {}) {
16039
16059
  if (flags["no-input"] || !stdin.isTTY || !stderr.isTTY)
16040
16060
  fail(
@@ -16058,6 +16078,12 @@ async function prompt(label, flags, { hidden = false, defaultValue } = {}) {
16058
16078
  stdin.resume();
16059
16079
  return new Promise((resolve3, reject2) => {
16060
16080
  let value = "";
16081
+ let shown = 0;
16082
+ function mask() {
16083
+ const delta = maskDelta(shown, value.length);
16084
+ if (delta) stderr.write(delta);
16085
+ shown = Math.min(value.length, MASK_LIMIT);
16086
+ }
16061
16087
  function done(error51) {
16062
16088
  stdin.off("data", onData);
16063
16089
  stdin.setRawMode(wasRaw);
@@ -16078,9 +16104,13 @@ async function prompt(label, flags, { hidden = false, defaultValue } = {}) {
16078
16104
  }
16079
16105
  if (char === "\x7F" || char === "\b") {
16080
16106
  value = value.slice(0, -1);
16107
+ mask();
16081
16108
  continue;
16082
16109
  }
16083
- if (char >= " ") value += char;
16110
+ if (char >= " ") {
16111
+ value += char;
16112
+ mask();
16113
+ }
16084
16114
  if (value.length > 16384) {
16085
16115
  done(new Error("input too long"));
16086
16116
  return;
@@ -16223,7 +16253,10 @@ function parse3(argv) {
16223
16253
  if (!match || !types[match[1]])
16224
16254
  fail(
16225
16255
  "unknown_flag",
16226
- `Unknown flag ${token.split("=")[0]} for ${command}.`
16256
+ `Unknown flag ${token.split("=")[0]} for ${command}.`,
16257
+ // Resources are addressed positionally throughout, so the one mistake
16258
+ // worth answering with more than the help text is naming one as a flag.
16259
+ spec.args && ["id", "app", "app-id"].includes(match?.[1] ?? "") ? `Pass the identifier as an argument: agw ${command}${" <id>".repeat(spec.args)}.` : void 0
16227
16260
  );
16228
16261
  const name = match[1];
16229
16262
  const inline = match[2];
@@ -16240,9 +16273,9 @@ function parse3(argv) {
16240
16273
  fail("missing_value", `--${name} requires a value.`);
16241
16274
  if (type === "multiple") {
16242
16275
  const existing = collected[name];
16243
- const list = Array.isArray(existing) ? existing : [];
16244
- list.push(value);
16245
- collected[name] = list;
16276
+ const list2 = Array.isArray(existing) ? existing : [];
16277
+ list2.push(value);
16278
+ collected[name] = list2;
16246
16279
  } else collected[name] = value;
16247
16280
  }
16248
16281
  }
@@ -16935,14 +16968,137 @@ var StateStore = class {
16935
16968
  }
16936
16969
  };
16937
16970
 
16971
+ // ../src/shared/first-request.ts
16972
+ var PROVIDER_PLACEHOLDER = "PROVIDER_SLUG";
16973
+ var MODEL_PLACEHOLDER = "MODEL";
16974
+ var BODY_PLACEHOLDER = "REQUEST_BODY";
16975
+ var EXAMPLE_GAP_NOTES = {
16976
+ provider: `No provider is configured yet, so ${PROVIDER_PLACEHOLDER} stands in for one. Add a provider key, then use its slug.`,
16977
+ model: `No priced model was found for this provider, so ${MODEL_PLACEHOLDER} stands in for one. Use a model the provider serves.`,
16978
+ body: `This app allows no path whose request shape is known here, so ${BODY_PLACEHOLDER} stands in for the body the provider documents.`
16979
+ };
16980
+ function examplePath(type, { gatewayRouted = false, model } = {}) {
16981
+ if (type === "openai") return "v1/responses";
16982
+ if (type === "anthropic") return "v1/messages";
16983
+ if (type === "gemini")
16984
+ return model ? `v1beta/models/${model}:generateContent` : void 0;
16985
+ if (gatewayRouted) return "v1/chat/completions";
16986
+ if (type === "groq") return "openai/v1/chat/completions";
16987
+ if (type === "fireworks") return "inference/v1/chat/completions";
16988
+ if (["deepseek", "perplexity", "bytedance"].includes(type)) return "chat/completions";
16989
+ return "v1/chat/completions";
16990
+ }
16991
+ function bodyFor(path, model) {
16992
+ const messages = [{ role: "user", content: "Say hello." }];
16993
+ if (path.endsWith("responses")) return { model, input: "Say hello." };
16994
+ if (path.endsWith("chat/completions")) return { model, messages };
16995
+ if (path.endsWith("messages")) return { model, max_tokens: 128, messages };
16996
+ if (path.endsWith(":generateContent"))
16997
+ return { contents: [{ parts: [{ text: "Say hello." }] }] };
16998
+ return null;
16999
+ }
17000
+ function modelsFor(provider, policy2, prices) {
17001
+ if (policy2?.allowed_models?.length) return policy2.allowed_models;
17002
+ return [
17003
+ ...Object.keys(provider.pricing ?? {}),
17004
+ ...Object.entries(prices[provider.type] ?? {}).filter(([, price]) => price.output !== void 0).map(([model]) => model)
17005
+ ];
17006
+ }
17007
+ function firstRequest(routing, providers, prices) {
17008
+ let fallback;
17009
+ for (const provider of providers) {
17010
+ if (provider.status !== "active") continue;
17011
+ const policy2 = routing?.providers?.[provider.slug];
17012
+ if (routing?.providerMode === "selected" && !policy2) continue;
17013
+ const models = modelsFor(provider, policy2, prices);
17014
+ const model = models[0] ?? MODEL_PLACEHOLDER;
17015
+ const gaps = models.length ? [] : ["model"];
17016
+ const fallbackPath = examplePath(provider.type, {
17017
+ gatewayRouted: Boolean(provider.providerGatewayId),
17018
+ model
17019
+ });
17020
+ const paths = policy2?.allowed_paths?.length ? policy2.allowed_paths : fallbackPath ? [fallbackPath] : [];
17021
+ for (const entry of paths) {
17022
+ const path = typeof entry === "string" ? entry : entry.path;
17023
+ if (path.includes("*")) continue;
17024
+ const fixed = typeof entry !== "string" ? entry.fixed_model : void 0;
17025
+ const body = bodyFor(path, fixed ?? model);
17026
+ const example = {
17027
+ target: { provider: provider.slug, path },
17028
+ body,
17029
+ anthropic: path.endsWith("messages"),
17030
+ // A fixed model is the app's own, so a catalog that has none of its
17031
+ // own to offer is not a gap on a path that never carries one.
17032
+ gaps: body && fixed ? [] : gaps
17033
+ };
17034
+ if (body) return example;
17035
+ fallback ??= { ...example, gaps: [...example.gaps, "body"] };
17036
+ }
17037
+ }
17038
+ return fallback ?? {
17039
+ target: { provider: PROVIDER_PLACEHOLDER, path: "v1/chat/completions" },
17040
+ body: bodyFor("v1/chat/completions", MODEL_PLACEHOLDER),
17041
+ anthropic: false,
17042
+ gaps: ["provider", "model"]
17043
+ };
17044
+ }
17045
+ function exampleNotes(example) {
17046
+ return example.gaps.map((gap) => EXAMPLE_GAP_NOTES[gap]);
17047
+ }
17048
+ function exampleUrl(baseUrl, appId, target) {
17049
+ const origin2 = baseUrl.replace(/\/+$/, "");
17050
+ const app2 = `${origin2}/v1/apps/${encodeURIComponent(appId)}`;
17051
+ return "endpoint" in target ? `${app2}/endpoints/${encodeURIComponent(target.endpoint)}` : `${app2}/proxy/${encodeURIComponent(target.provider)}/${target.path}`;
17052
+ }
17053
+ function shellQuote(value) {
17054
+ return `'${value.replaceAll("'", "'\\''")}'`;
17055
+ }
17056
+ function commented(notes2, marker) {
17057
+ return (notes2 ?? []).map((note) => `${marker} ${note}
17058
+ `).join("");
17059
+ }
17060
+ function curlSnippet(options) {
17061
+ const { baseUrl, appId, example, notes: notes2, keyExpression = "$APP_AI_GATEWAY_KEY" } = options;
17062
+ const body = example.body ? JSON.stringify(example.body) : BODY_PLACEHOLDER;
17063
+ return commented(notes2, "#") + `curl --fail-with-body ${shellQuote(exampleUrl(baseUrl, appId, example.target))} \\
17064
+ -H "Authorization: Bearer ${keyExpression}" \\
17065
+ -H 'Content-Type: application/json' \\
17066
+ ` + (example.anthropic ? " -H 'anthropic-version: 2023-06-01' \\\n" : "") + ` -d ${shellQuote(body)}
17067
+ `;
17068
+ }
17069
+ function swiftSnippet(options) {
17070
+ const { baseUrl, appId, example, notes: notes2, authMode = ".appAttestInstall" } = options;
17071
+ const target = "endpoint" in example.target ? `endpointSlug: ${JSON.stringify(example.target.endpoint)}` : `provider: ${JSON.stringify(example.target.provider)},
17072
+ providerPath: ${JSON.stringify(example.target.path)}`;
17073
+ const body = example.body ? `request.httpBody = Data(${JSON.stringify(JSON.stringify(example.body))}.utf8)` : `// Supply the provider-native JSON body.
17074
+ request.httpBody = Data(${JSON.stringify(BODY_PLACEHOLDER)}.utf8)`;
17075
+ return commented(notes2, "//") + `import Foundation
17076
+ import AppAIGateway
17077
+
17078
+ let gateway = AppAIGatewayClient(
17079
+ appID: ${JSON.stringify(appId)},
17080
+ baseURL: URL(string: ${JSON.stringify(baseUrl.replace(/\/+$/, ""))})!,
17081
+ authMode: ${authMode}
17082
+ )
17083
+
17084
+ var request = try await gateway.authorizedRequest(
17085
+ ${target}
17086
+ )
17087
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
17088
+ ${example.anthropic ? 'request.setValue("2023-06-01", forHTTPHeaderField: "anthropic-version")\n' : ""}${body}
17089
+ let (data, _) = try await URLSession.shared.data(for: request)
17090
+ print(String(decoding: data, as: UTF8.self))
17091
+ `;
17092
+ }
17093
+
16938
17094
  // src/apps.ts
16939
17095
  var unlimited = () => ({
16940
17096
  requests: { per_minute: null, per_day: null },
16941
17097
  spending: { monthly_usd: null }
16942
17098
  });
16943
17099
  var policy = () => ({ allowed_paths: [], allowed_models: [] });
16944
- function documentOf(app) {
16945
- return localApp({ name: app.name, config: app.config, status: app.status });
17100
+ function documentOf(app2) {
17101
+ return localApp({ name: app2.name, config: app2.config, status: app2.status });
16946
17102
  }
16947
17103
  function localApp(value) {
16948
17104
  const doc = validate(AppWriteSchema, value);
@@ -17143,14 +17299,14 @@ async function appCommand(ctx, command, args, flags) {
17143
17299
  };
17144
17300
  const output = action === "add" && doc2.config.authentication.type === "api_key" ? await ctx.keyOutput(operations.createApp.path(), doc2, flags["key-output"]) : null;
17145
17301
  try {
17146
- let app;
17302
+ let app2;
17147
17303
  let resolved;
17148
17304
  let configError;
17149
17305
  let key;
17150
17306
  if (action === "add") {
17151
17307
  await ctx.bootstrap();
17152
17308
  const created = await ctx.create("createApp", [], doc2);
17153
- ({ app, resolved, config_error: configError } = created.data);
17309
+ ({ app: app2, resolved, config_error: configError } = created.data);
17154
17310
  if (output) {
17155
17311
  if (created.keyMetadata) key = created.keyMetadata;
17156
17312
  else {
@@ -17162,7 +17318,7 @@ async function appCommand(ctx, command, args, flags) {
17162
17318
  "Inspect the app key list before retrying.",
17163
17319
  3
17164
17320
  );
17165
- key = await saveKey(ctx, minted, output, app.id);
17321
+ key = await saveKey(ctx, minted, output, app2.id);
17166
17322
  }
17167
17323
  await created.keyStored?.(key);
17168
17324
  }
@@ -17171,23 +17327,25 @@ async function appCommand(ctx, command, args, flags) {
17171
17327
  const updated = await ctx.call("updateApp", [appId2], {
17172
17328
  body: { ...doc2, revision }
17173
17329
  });
17174
- ({ app, resolved, config_error: configError } = updated.data);
17330
+ ({ app: app2, resolved, config_error: configError } = updated.data);
17175
17331
  }
17176
17332
  let snippet;
17177
- if (doc2.config.authentication.type === "apple_app_attest") {
17178
- try {
17179
- const example = await appCommand(ctx, "app snippet", [app.id], {});
17180
- if ("snippet" in example) snippet = example.snippet;
17181
- } catch {
17182
- }
17333
+ try {
17334
+ const example = await appCommand(ctx, "app snippet", [app2.id], {});
17335
+ if ("snippet" in example) snippet = example.snippet;
17336
+ } catch {
17183
17337
  }
17338
+ if (snippet && key)
17339
+ snippet = `export APP_AI_GATEWAY_KEY="$(cat ${shellQuote(key.storagePath)})"
17340
+
17341
+ ${snippet}`;
17184
17342
  return {
17185
17343
  ...snippet ? { snippet } : {},
17186
- app,
17344
+ app: app2,
17187
17345
  resolved,
17188
17346
  config_error: configError,
17189
17347
  ...key ? { applicationKey: key } : {},
17190
- guidance: doc2.config.authentication.type === "apple_app_attest" ? `Add https://github.com/maxceem/app-ai-gateway-swift from 1.0.0, enable App Attest, and test on a supported physical device. Run agw app snippet ${app.id} --provider <slug> for integration code. Production releases should use production-only App Attest.` : "Store the generated key on your server; never embed it in a mobile application."
17348
+ guidance: doc2.config.authentication.type === "apple_app_attest" ? `Add https://github.com/maxceem/app-ai-gateway-swift from 1.0.0, enable App Attest, and test on a supported physical device. Run agw app snippet ${app2.id} --provider <slug> for integration code. Production releases should use production-only App Attest.` : `Store the generated key on your server; never embed it in a mobile application. Run agw app snippet ${app2.id} for this request again.`
17191
17349
  };
17192
17350
  } finally {
17193
17351
  await output?.cancel();
@@ -17196,8 +17354,8 @@ async function appCommand(ctx, command, args, flags) {
17196
17354
  if (action.startsWith("key ")) {
17197
17355
  const sub = action.slice(4);
17198
17356
  const appId2 = args[0] ?? "";
17199
- const { data: app } = await ctx.call("getApp", [appId2]);
17200
- if (documentOf(app.app).config.authentication.type !== "api_key")
17357
+ const { data: app2 } = await ctx.call("getApp", [appId2]);
17358
+ if (documentOf(app2.app).config.authentication.type !== "api_key")
17201
17359
  fail(
17202
17360
  "invalid_input",
17203
17361
  "Only server applications support application keys."
@@ -17274,62 +17432,75 @@ async function appCommand(ctx, command, args, flags) {
17274
17432
  };
17275
17433
  }
17276
17434
  if (action === "snippet") {
17277
- if (flags.language && flags.language !== "swift")
17278
- fail("invalid_input", "Only --language swift is supported.");
17279
- if (doc.config.authentication.type !== "apple_app_attest")
17435
+ const ios = doc.config.authentication.type === "apple_app_attest";
17436
+ const language = flags.language ?? (ios ? "swift" : "curl");
17437
+ if (!["swift", "curl"].includes(language))
17438
+ fail("invalid_input", "--language must be swift or curl.");
17439
+ if (language === "swift" !== ios)
17280
17440
  fail(
17281
17441
  "unsupported_snippet",
17282
- "Swift snippets are for iOS applications.",
17283
- "Use your server HTTP client with the stored application key."
17442
+ ios ? "iOS applications authenticate with App Attest, which curl cannot perform." : "Swift snippets are for iOS applications.",
17443
+ `Run agw app snippet ${appId} --language ${ios ? "swift" : "curl"}.`
17284
17444
  );
17285
- let target;
17445
+ const notes2 = [];
17446
+ let example;
17286
17447
  if (flags.endpoint) {
17287
- if (!doc.config.endpoints?.[flags.endpoint])
17448
+ const endpoint = doc.config.endpoints?.[flags.endpoint];
17449
+ if (!endpoint)
17288
17450
  fail("endpoint_not_found", "Choose an existing named endpoint.");
17289
- target = `endpointSlug: ${JSON.stringify(flags.endpoint)}`;
17451
+ const responses = endpoint.api_style === "responses";
17452
+ example = {
17453
+ target: { endpoint: flags.endpoint },
17454
+ body: responses ? { input: "Say hello." } : null,
17455
+ anthropic: false,
17456
+ gaps: responses ? [] : ["body"]
17457
+ };
17290
17458
  } else {
17459
+ const routing = {
17460
+ providerMode: doc.config.routing.providers.mode,
17461
+ providers: doc.config.routing.providers.selected
17462
+ };
17291
17463
  const { data: all } = await ctx.call("listProviders", []);
17292
- let providers = all.providers.filter(
17293
- (p2) => p2.status === "active" && (doc.config.routing.providers.mode === "all" || Object.hasOwn(doc.config.routing.providers.selected ?? {}, p2.slug))
17464
+ const reachable = all.providers.filter(
17465
+ (p) => p.status === "active" && (routing.providerMode === "all" || Object.hasOwn(routing.providers ?? {}, p.slug))
17294
17466
  );
17295
17467
  const requested = typeof flags.provider === "string" ? flags.provider : void 0;
17296
- if (requested) providers = providers.filter((p2) => p2.slug === requested);
17297
- const p = providers[0];
17298
- if (providers.length !== 1 || !p)
17468
+ if (requested && !reachable.some((p) => p.slug === requested))
17299
17469
  fail(
17300
- "provider_required",
17301
- "Choose one available provider slug using --provider, or use --endpoint."
17470
+ all.providers.some((p) => p.slug === requested) ? "provider_unavailable" : "provider_not_found",
17471
+ all.providers.some((p) => p.slug === requested) ? "That provider is disabled or outside this app's proxy policy." : "No provider has that slug.",
17472
+ "Run agw provider list for the slugs, and agw app show <id> for the policy."
17302
17473
  );
17303
- const { data: capabilities } = await ctx.publicCall("getCliCapabilities", []);
17304
- const cap = capabilities.providers.find((c) => c.type === p.type);
17305
- const path = cap?.defaultPath;
17306
- if (!path)
17307
- fail(
17308
- "provider_path_required",
17309
- "This provider has no default snippet path in deployment capabilities.",
17310
- "Configure and select a named endpoint."
17474
+ const { data: catalog } = await ctx.call("listModelPrices", []);
17475
+ const providers = requested ? reachable.filter((p) => p.slug === requested) : reachable;
17476
+ example = firstRequest(routing, providers, catalog.prices);
17477
+ if (!requested && reachable.length > 1)
17478
+ notes2.push(`This app can reach ${reachable.length} providers. Add --provider <slug> for a different one.`);
17479
+ }
17480
+ notes2.push(...exampleNotes(example));
17481
+ const clientUrl = ctx.active?.deployment?.apiUrl ?? ctx.url;
17482
+ let snippet;
17483
+ if (ios) {
17484
+ const endUser = doc.config.authentication.type === "apple_app_attest" ? doc.config.authentication.end_user : void 0;
17485
+ const issuer = endUser?.source === "issuer";
17486
+ if (issuer)
17487
+ notes2.push(
17488
+ "Replace yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh) with your configured issuer integration."
17311
17489
  );
17312
- target = `provider: ${JSON.stringify(p.slug)},
17313
- providerPath: ${JSON.stringify(path)}`;
17314
- }
17315
- const issuer = doc.config.authentication.end_user.source === "issuer";
17316
- const authMode = issuer ? ".appAttest(issuerTokenProvider: { forceRefresh in\n // Return a fresh signed token from your configured identity SDK.\n try await yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh)\n })" : ".appAttestInstall";
17317
- const snippet = `// Swift package: https://github.com/maxceem/app-ai-gateway-swift (from: 1.0.0)
17318
- // Enable App Attest and test on a supported physical device.
17319
- import Foundation
17320
- import AppAIGateway
17321
-
17322
- let gateway = AppAIGatewayClient(
17323
- appID: ${JSON.stringify(appId)},
17324
- baseURL: URL(string: ${JSON.stringify(ctx.url)})!,
17325
- authMode: ${authMode}
17326
- )
17327
-
17328
- var request = try await gateway.authorizedRequest(
17329
- ${target}
17330
- )
17331
- // Supply the provider-native JSON body, then send request with URLSession.
17332
- `;
17490
+ snippet = swiftSnippet({
17491
+ baseUrl: clientUrl,
17492
+ appId,
17493
+ example,
17494
+ authMode: issuer ? ".appAttest(issuerTokenProvider: { forceRefresh in\n // Return a fresh signed token from your configured identity SDK.\n try await yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh)\n })" : ".appAttestInstall",
17495
+ notes: [
17496
+ "Swift package: https://github.com/maxceem/app-ai-gateway-swift (from: 1.0.0)",
17497
+ "Enable App Attest and test on a supported physical device.",
17498
+ ...notes2
17499
+ ]
17500
+ });
17501
+ } else {
17502
+ snippet = curlSnippet({ baseUrl: clientUrl, appId, example, notes: notes2 });
17503
+ }
17333
17504
  if (flags.output) {
17334
17505
  const output = await reserveOutput(flags.output);
17335
17506
  try {
@@ -17339,13 +17510,7 @@ var request = try await gateway.authorizedRequest(
17339
17510
  await output.cancel();
17340
17511
  }
17341
17512
  }
17342
- return {
17343
- language: "swift",
17344
- snippet,
17345
- ...issuer ? {
17346
- requires: "Replace yourIdentitySDK.currentIDToken(forceRefresh: forceRefresh) with your configured issuer integration."
17347
- } : {}
17348
- };
17513
+ return { language: ios ? "swift" : "shell", snippet, notes: notes2 };
17349
17514
  }
17350
17515
  fail("unknown_command", "Unknown command.");
17351
17516
  }
@@ -17376,8 +17541,8 @@ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429]);
17376
17541
  var RECEIPT_BOUND_STATUS = /* @__PURE__ */ new Set([409, 410]);
17377
17542
  function definitiveRefusal(error51) {
17378
17543
  if (!(error51 instanceof CliError)) return false;
17379
- const status = error51.details?.status;
17380
- return typeof status === "number" && status >= 400 && status < 500 && !RETRYABLE_STATUS.has(status) && !RECEIPT_BOUND_STATUS.has(status);
17544
+ const status2 = error51.details?.status;
17545
+ return typeof status2 === "number" && status2 >= 400 && status2 < 500 && !RETRYABLE_STATUS.has(status2) && !RECEIPT_BOUND_STATUS.has(status2);
17381
17546
  }
17382
17547
  var Context = class {
17383
17548
  store;
@@ -17477,7 +17642,7 @@ var Context = class {
17477
17642
  (entry) => entry.requestHash === requestHash && (entry.accountId === null || entry.accountId === this.active?.account?.id)
17478
17643
  ),
17479
17644
  (state) => {
17480
- const mutation = {
17645
+ const mutation2 = {
17481
17646
  id: randomToken(),
17482
17647
  proof: randomToken(),
17483
17648
  requestHash,
@@ -17486,27 +17651,27 @@ var Context = class {
17486
17651
  path,
17487
17652
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
17488
17653
  };
17489
- (state.mutations ??= {})[mutation.id] = mutation;
17490
- return mutation;
17654
+ (state.mutations ??= {})[mutation2.id] = mutation2;
17655
+ return mutation2;
17491
17656
  }
17492
17657
  );
17493
17658
  }
17494
17659
  async keyOutput(path, body, requestedPath) {
17495
- const mutation = await this.prepareCreate(path, body);
17496
- const chosen = requestedPath ? resolve2(requestedPath) : mutation.output?.path;
17497
- if (mutation.completedAt && chosen !== mutation.output?.path)
17660
+ const mutation2 = await this.prepareCreate(path, body);
17661
+ const chosen = requestedPath ? resolve2(requestedPath) : mutation2.output?.path;
17662
+ if (mutation2.completedAt && chosen !== mutation2.output?.path)
17498
17663
  fail(
17499
17664
  "output_changed",
17500
17665
  "A completed key creation cannot move its output.",
17501
17666
  "Copy the protected existing file, or create a replacement with a new key name.",
17502
17667
  4
17503
17668
  );
17504
- const existing = chosen && chosen === mutation.output?.path ? mutation.output : void 0;
17669
+ const existing = chosen && chosen === mutation2.output?.path ? mutation2.output : void 0;
17505
17670
  const output = await this.store.keyOutput(chosen, {
17506
17671
  reservation: existing,
17507
17672
  retain: true
17508
17673
  });
17509
- if (mutation.completedAt && mutation.keyMetadata?.contentHash !== await output.contentHash()) {
17674
+ if (mutation2.completedAt && mutation2.keyMetadata?.contentHash !== await output.contentHash()) {
17510
17675
  await output.cancel();
17511
17676
  fail(
17512
17677
  "output_changed",
@@ -17515,15 +17680,15 @@ var Context = class {
17515
17680
  4
17516
17681
  );
17517
17682
  }
17518
- mutation.output = output.reservation;
17683
+ mutation2.output = output.reservation;
17519
17684
  await this.save();
17520
17685
  return {
17521
17686
  ...output,
17522
- recoveryAvailable: () => Boolean(mutation.response),
17523
- mutation,
17687
+ recoveryAvailable: () => Boolean(mutation2.response),
17688
+ mutation: mutation2,
17524
17689
  cancel: async () => {
17525
17690
  await output.cancel();
17526
- if (!this.state.mutations?.[mutation.id])
17691
+ if (!this.state.mutations?.[mutation2.id])
17527
17692
  await releaseOutput(output.reservation);
17528
17693
  }
17529
17694
  };
@@ -17544,32 +17709,32 @@ var Context = class {
17544
17709
  );
17545
17710
  const descriptor = operations[name];
17546
17711
  const path = descriptor.path(...params);
17547
- const mutation = await this.prepareCreate(path, body);
17548
- if (mutation.accountId === null) {
17549
- mutation.accountId = this.active.account?.id ?? null;
17712
+ const mutation2 = await this.prepareCreate(path, body);
17713
+ if (mutation2.accountId === null) {
17714
+ mutation2.accountId = this.active.account?.id ?? null;
17550
17715
  await this.save();
17551
17716
  }
17552
- if (mutation.failure)
17717
+ if (mutation2.failure)
17553
17718
  fail(
17554
17719
  "key_storage_failed",
17555
17720
  "This creation ended with a revoked or unverified key.",
17556
- `Inspect agw app key list ${mutation.failure.appId}; use a new key name to replace it.`,
17721
+ `Inspect agw app key list ${mutation2.failure.appId}; use a new key name to replace it.`,
17557
17722
  4,
17558
- mutation.failure
17723
+ mutation2.failure
17559
17724
  );
17560
- if (mutation.completedAt) {
17561
- this.deliveredMutations.add(mutation.id);
17725
+ if (mutation2.completedAt) {
17726
+ this.deliveredMutations.add(mutation2.id);
17562
17727
  return {
17563
17728
  // Parsed against the recorded shape, not the live one: this copy never
17564
17729
  // held the plaintext, so the live schema would reject it — and a state
17565
17730
  // file that somehow grew one is stripped again on the way out.
17566
- data: this.parseRecorded(name, mutation.result),
17567
- keyMetadata: mutation.keyMetadata,
17731
+ data: this.parseRecorded(name, mutation2.result),
17732
+ keyMetadata: mutation2.keyMetadata,
17568
17733
  complete: async () => {
17569
17734
  }
17570
17735
  };
17571
17736
  }
17572
- if (!mutation.response && Date.now() - Date.parse(mutation.createdAt) > 90 * 24 * 60 * 60 * 1e3)
17737
+ if (!mutation2.response && Date.now() - Date.parse(mutation2.createdAt) > 90 * 24 * 60 * 60 * 1e3)
17573
17738
  fail(
17574
17739
  "resource_retry_expired",
17575
17740
  "This unfinished creation is older than the supported 90-day retry window.",
@@ -17577,8 +17742,8 @@ var Context = class {
17577
17742
  4
17578
17743
  );
17579
17744
  let data;
17580
- if (mutation.response !== void 0) {
17581
- const recovered = this.parse(name, mutation.response);
17745
+ if (mutation2.response !== void 0) {
17746
+ const recovered = this.parse(name, mutation2.response);
17582
17747
  const keyRecord = recoveredKey(name, recovered);
17583
17748
  if (keyRecord) {
17584
17749
  const appId = recoveredAppId(name, recovered, params);
@@ -17586,15 +17751,15 @@ var Context = class {
17586
17751
  if (!existing.keys.some(
17587
17752
  (key) => key.id === keyRecord.id && key.status === "active"
17588
17753
  )) {
17589
- mutation.failure = { appId, keyId: keyRecord.id, revoked: true };
17590
- delete mutation.response;
17754
+ mutation2.failure = { appId, keyId: keyRecord.id, revoked: true };
17755
+ delete mutation2.response;
17591
17756
  await this.save();
17592
17757
  fail(
17593
17758
  "resource_key_unavailable",
17594
17759
  "The recovered application key was revoked or removed.",
17595
17760
  "Create a replacement key with a new name; the previous creation will not be repeated.",
17596
17761
  4,
17597
- mutation.failure
17762
+ mutation2.failure
17598
17763
  );
17599
17764
  }
17600
17765
  }
@@ -17603,29 +17768,29 @@ var Context = class {
17603
17768
  const response = await this.call(name, params, {
17604
17769
  body,
17605
17770
  headers: {
17606
- "Idempotency-Key": mutation.id,
17607
- "X-Idempotency-Proof": mutation.proof
17771
+ "Idempotency-Key": mutation2.id,
17772
+ "X-Idempotency-Proof": mutation2.proof
17608
17773
  }
17609
17774
  }).catch(async (error51) => {
17610
- await this.discard(mutation, error51);
17775
+ await this.discard(mutation2, error51);
17611
17776
  throw error51;
17612
17777
  });
17613
17778
  data = response.data;
17614
- mutation.response = data;
17779
+ mutation2.response = data;
17615
17780
  await this.save();
17616
17781
  }
17617
17782
  return {
17618
17783
  data,
17619
17784
  keyStored: async (metadata) => {
17620
- mutation.keyMetadata = metadata;
17785
+ mutation2.keyMetadata = metadata;
17621
17786
  await this.save();
17622
17787
  },
17623
17788
  complete: async () => {
17624
- mutation.result = recordedResultFor[name].parse(data);
17625
- mutation.completedAt = (/* @__PURE__ */ new Date()).toISOString();
17626
- delete mutation.response;
17789
+ mutation2.result = recordedResultFor[name].parse(data);
17790
+ mutation2.completedAt = (/* @__PURE__ */ new Date()).toISOString();
17791
+ delete mutation2.response;
17627
17792
  await this.save();
17628
- this.deliveredMutations.add(mutation.id);
17793
+ this.deliveredMutations.add(mutation2.id);
17629
17794
  }
17630
17795
  };
17631
17796
  }
@@ -17637,9 +17802,9 @@ var Context = class {
17637
17802
  * different failure. The worst a failed write leaves behind is the record
17638
17803
  * this would have removed, which is where the CLI stood before.
17639
17804
  */
17640
- async discard(mutation, error51) {
17805
+ async discard(mutation2, error51) {
17641
17806
  if (!definitiveRefusal(error51)) return;
17642
- delete this.state.mutations?.[mutation.id];
17807
+ delete this.state.mutations?.[mutation2.id];
17643
17808
  await this.save().catch(() => {
17644
17809
  });
17645
17810
  }
@@ -17764,12 +17929,12 @@ var Context = class {
17764
17929
  };
17765
17930
  await this.save();
17766
17931
  }
17767
- const operation2 = this.state.operations[localId];
17932
+ const operation3 = this.state.operations[localId];
17768
17933
  const { data: capabilities } = await this.publicCall("getCliCapabilities", [], {
17769
17934
  url: target
17770
17935
  });
17771
17936
  const { data } = await this.publicCall("createCliOperation", [], {
17772
- body: { kind, payload, pollToken: operation2.pollToken },
17937
+ body: { kind, payload, pollToken: operation3.pollToken },
17773
17938
  url: target,
17774
17939
  ...authenticated ? { key: this.active?.credential } : {}
17775
17940
  });
@@ -17782,7 +17947,7 @@ var Context = class {
17782
17947
  "invalid_response",
17783
17948
  "The handoff URL is not on this deployment\u2019s trusted console origin."
17784
17949
  );
17785
- this.state.operations[data.id] = { ...operation2, phase: "pending" };
17950
+ this.state.operations[data.id] = { ...operation3, phase: "pending" };
17786
17951
  delete this.state.operations[localId];
17787
17952
  await this.save();
17788
17953
  if (!this.flags["no-open"]) await openBrowser(data.url);
@@ -17796,15 +17961,15 @@ Operation: ${data.id}
17796
17961
  return this.wait(data.id, 300);
17797
17962
  }
17798
17963
  async poll(id) {
17799
- const operation2 = this.state.operations[id];
17800
- if (!operation2)
17964
+ const operation3 = this.state.operations[id];
17965
+ if (!operation3)
17801
17966
  fail(
17802
17967
  "operation_unknown",
17803
17968
  "This operation has no locally stored polling authorization.",
17804
17969
  "Resume it from the machine that initiated the operation.",
17805
17970
  4
17806
17971
  );
17807
- if (this.active && this.active.url !== operation2.url)
17972
+ if (this.active && this.active.url !== operation3.url)
17808
17973
  fail(
17809
17974
  "operation_context",
17810
17975
  "This operation belongs to another selected deployment.",
@@ -17812,18 +17977,18 @@ Operation: ${data.id}
17812
17977
  4
17813
17978
  );
17814
17979
  const { data } = await this.publicCall("pollCliOperation", [id], {
17815
- key: operation2.pollToken,
17816
- url: operation2.url
17980
+ key: operation3.pollToken,
17981
+ url: operation3.url
17817
17982
  });
17818
- if (data.state === "completed" && !operation2.completed && operation2.kind === "claim") {
17819
- if ((this.state.generation ?? 0) !== operation2.generation)
17983
+ if (data.state === "completed" && !operation3.completed && operation3.kind === "claim") {
17984
+ if ((this.state.generation ?? 0) !== operation3.generation)
17820
17985
  fail(
17821
17986
  "operation_context",
17822
17987
  "The active connection changed after this operation began.",
17823
17988
  "Start a new login for the intended connection.",
17824
17989
  4
17825
17990
  );
17826
- operation2.completed = true;
17991
+ operation3.completed = true;
17827
17992
  if (this.active && data.account) this.active.account = data.account;
17828
17993
  await this.save();
17829
17994
  }
@@ -17885,9 +18050,11 @@ async function openBrowser(url2) {
17885
18050
 
17886
18051
  // src/deployment.ts
17887
18052
  import { spawn as spawn2 } from "node:child_process";
18053
+ import { stripVTControlCharacters } from "node:util";
17888
18054
  import { createRequire as createRequire2 } from "node:module";
17889
18055
  import { randomUUID } from "node:crypto";
17890
18056
  import { cp, writeFile as writeFile2, unlink as unlink2 } from "node:fs/promises";
18057
+ import { tmpdir } from "node:os";
17891
18058
  import { join as join3 } from "node:path";
17892
18059
  import { setTimeout as delay3 } from "node:timers/promises";
17893
18060
 
@@ -18133,9 +18300,11 @@ async function release(requested = VERSION, base = dirname(fileURLToPath(import.
18133
18300
 
18134
18301
  // src/deployment.ts
18135
18302
  var require2 = createRequire2(import.meta.url);
18136
- var OUTPUT_TAIL = 2e3;
18303
+ var OUTPUT_TAIL = 5e3;
18137
18304
  function wranglerFailure(args, code, output, redact = []) {
18138
- const tail = redact.filter((value) => value.length >= 8).reduce((text2, value) => text2.split(value).join("[redacted]"), output).trim().slice(-OUTPUT_TAIL);
18305
+ const tail = stripVTControlCharacters(
18306
+ redact.filter((value) => value.length >= 8).reduce((text2, value) => text2.split(value).join("[redacted]"), output)
18307
+ ).trim().slice(-OUTPUT_TAIL);
18139
18308
  return new CliError(
18140
18309
  "wrangler_failed",
18141
18310
  `wrangler ${args[0] ?? ""} failed${code === null ? "" : ` (exit ${code})`}.`,
@@ -18144,16 +18313,21 @@ function wranglerFailure(args, code, output, redact = []) {
18144
18313
  tail ? { output: tail } : void 0
18145
18314
  );
18146
18315
  }
18316
+ function wranglerEnvironment({
18317
+ interactive = false
18318
+ } = {}) {
18319
+ return {
18320
+ ...process.env,
18321
+ WRANGLER_SEND_METRICS: "false",
18322
+ WRANGLER_LOG: "log",
18323
+ CI: interactive ? "" : "true"
18324
+ };
18325
+ }
18147
18326
  async function runWrangler(args, { cwd, input, interactive = false, redact } = {}) {
18148
18327
  return new Promise((resolve3, reject2) => {
18149
18328
  const child = spawn2(process.execPath, [require2.resolve("wrangler/bin/wrangler.js"), ...args], {
18150
18329
  ...cwd === void 0 ? {} : { cwd },
18151
- env: {
18152
- ...process.env,
18153
- WRANGLER_SEND_METRICS: "false",
18154
- WRANGLER_LOG: "error",
18155
- CI: interactive ? "" : "true"
18156
- },
18330
+ env: wranglerEnvironment({ interactive }),
18157
18331
  stdio: [input ? "pipe" : interactive ? "inherit" : "ignore", "pipe", "pipe"]
18158
18332
  });
18159
18333
  let output = "";
@@ -18184,6 +18358,24 @@ async function runWrangler(args, { cwd, input, interactive = false, redact } = {
18184
18358
  });
18185
18359
  });
18186
18360
  }
18361
+ function usable(auth) {
18362
+ return auth.type === "api_key" ? Boolean(auth.key && auth.email) : Boolean(auth.token);
18363
+ }
18364
+ function environmentCredentials() {
18365
+ const env = process.env;
18366
+ const token = env.CLOUDFLARE_API_TOKEN ?? env.CF_API_TOKEN;
18367
+ const key = env.CLOUDFLARE_API_KEY ?? env.CF_API_KEY;
18368
+ const email3 = env.CLOUDFLARE_EMAIL ?? env.CF_EMAIL;
18369
+ return [token, key && email3 ? key : void 0].filter(
18370
+ (value) => Boolean(value)
18371
+ );
18372
+ }
18373
+ var API_ERROR_TAIL = 500;
18374
+ function apiErrors(data) {
18375
+ return (data.errors ?? []).map(
18376
+ (error51) => [error51.message, error51.code === void 0 ? "" : `[code: ${error51.code}]`].filter(Boolean).join(" ")
18377
+ ).filter(Boolean).join("; ").slice(0, API_ERROR_TAIL);
18378
+ }
18187
18379
  var Cloudflare = class {
18188
18380
  run;
18189
18381
  fetch;
@@ -18192,22 +18384,62 @@ var Cloudflare = class {
18192
18384
  this.run = run;
18193
18385
  this.fetch = fetchImpl;
18194
18386
  }
18195
- async authenticate(flags) {
18387
+ /**
18388
+ * The authorization wrangler holds, or undefined when it holds none.
18389
+ *
18390
+ * Only one outcome may be read as "logged out": wrangler exiting non-zero
18391
+ * with nothing in the environment for it to have used. A run that failed
18392
+ * while credentials were set failed at something else, a run that could not
18393
+ * start failed at nothing to do with authorization, and output that will not
18394
+ * parse means this CLI and the wrangler it drives disagree about the
18395
+ * command. Answering any of those with a login would hide the cause behind a
18396
+ * message about something the caller had already done.
18397
+ */
18398
+ async readAuth() {
18399
+ const credentials = environmentCredentials();
18400
+ let reported2;
18401
+ try {
18402
+ reported2 = await this.run(["auth", "token", "--json"], {
18403
+ cwd: tmpdir(),
18404
+ redact: credentials
18405
+ });
18406
+ } catch (error51) {
18407
+ if (credentials.length > 0 || !(error51 instanceof CliError) || error51.code !== "wrangler_failed")
18408
+ throw error51;
18409
+ return void 0;
18410
+ }
18411
+ let auth;
18196
18412
  try {
18197
- this.auth = JSON.parse(await this.run(["auth", "token", "--json"]));
18413
+ auth = JSON.parse(reported2);
18198
18414
  } catch {
18199
- if (flags["no-input"] || flags["dry-run"])
18200
- fail(
18201
- "cloudflare_auth_required",
18202
- "Cloudflare authentication is required.",
18203
- "Set CLOUDFLARE_API_TOKEN securely, or run setup interactively to authorize Cloudflare.",
18204
- 4
18205
- );
18206
- await this.run(["login"], { interactive: true });
18207
- this.auth = JSON.parse(await this.run(["auth", "token", "--json"]));
18415
+ fail(
18416
+ "cloudflare_auth_unreadable",
18417
+ "Wrangler did not report the Cloudflare authorization it holds.",
18418
+ "Reinstall the CLI so the wrangler it bundles is the one it expects, then retry.",
18419
+ 3
18420
+ );
18208
18421
  }
18209
- if (!this.auth?.token && this.auth?.type !== "api_key")
18210
- fail("cloudflare_auth_required", "No Cloudflare authorization is available.");
18422
+ return usable(auth) ? auth : void 0;
18423
+ }
18424
+ async authenticate(flags) {
18425
+ this.auth = await this.readAuth();
18426
+ if (this.auth) return;
18427
+ if (flags["no-input"] || flags["dry-run"])
18428
+ fail(
18429
+ "cloudflare_auth_required",
18430
+ "Cloudflare authentication is required.",
18431
+ "Set CLOUDFLARE_API_TOKEN securely, or run setup interactively to authorize Cloudflare.",
18432
+ 4
18433
+ );
18434
+ await this.run(["login"], { interactive: true, cwd: tmpdir() });
18435
+ this.auth = await this.readAuth();
18436
+ if (!this.auth)
18437
+ fail(
18438
+ "cloudflare_auth_required",
18439
+ "Cloudflare authorization was not completed.",
18440
+ "Read the wrangler output above, complete the browser login, then retry the same command.",
18441
+ 4
18442
+ );
18211
18443
  }
18212
18444
  authHeaders() {
18213
18445
  const auth = this.auth;
@@ -18243,13 +18475,16 @@ var Cloudflare = class {
18243
18475
  } catch {
18244
18476
  fail("cloudflare_error", "Cloudflare returned an invalid response.");
18245
18477
  }
18246
- if (!response.ok || !data.success)
18478
+ if (!response.ok || !data.success) {
18479
+ const reported2 = apiErrors(data);
18247
18480
  fail(
18248
18481
  "cloudflare_error",
18249
- `Cloudflare rejected the request (HTTP ${response.status}).`,
18482
+ `Cloudflare rejected the request (HTTP ${response.status})${reported2 ? `: ${reported2}` : ""}`,
18250
18483
  "Check Cloudflare permissions and the deployment resource IDs; retry the same command.",
18251
- 3
18484
+ 3,
18485
+ reported2 ? { apiErrors: reported2 } : void 0
18252
18486
  );
18487
+ }
18253
18488
  return data;
18254
18489
  }
18255
18490
  async all(path) {
@@ -18761,60 +18996,570 @@ async function deploymentCommand(ctx, command, flags, cf = new Cloudflare(), loa
18761
18996
  }
18762
18997
  }
18763
18998
 
18999
+ // src/style.ts
19000
+ import { Writable } from "node:stream";
19001
+ import { styleText } from "node:util";
19002
+ var STATES = /* @__PURE__ */ new Map([
19003
+ ["active", "green"],
19004
+ ["ready", "green"],
19005
+ ["completed", "green"],
19006
+ ["yes", "green"],
19007
+ ["pending", "yellow"],
19008
+ ["failed", "red"],
19009
+ ["expired", "red"],
19010
+ ["blocked", "red"],
19011
+ ["inactive", "red"],
19012
+ ["disabled", "red"],
19013
+ ["revoked", "red"],
19014
+ ["no", "red"]
19015
+ ]);
19016
+ function colorEnabled(sink, env = process.env) {
19017
+ if (env["NO_COLOR"]) return false;
19018
+ if (!(sink instanceof Writable)) return false;
19019
+ if (env["FORCE_COLOR"] && env["FORCE_COLOR"] !== "0") return true;
19020
+ if (!("isTTY" in sink && sink.isTTY === true)) return false;
19021
+ return styleText("dim", "?", { stream: sink, validateStream: true }) !== "?";
19022
+ }
19023
+ var plain = style(false);
19024
+ var styleFor = (sink) => style(colorEnabled(sink));
19025
+ function style(enabled) {
19026
+ const paint = (format, text2) => enabled ? styleText(format, text2, { validateStream: false }) : text2;
19027
+ const self = {
19028
+ enabled,
19029
+ headline: (text2) => paint("bold", text2),
19030
+ dim: (text2) => paint("dim", text2),
19031
+ alert: (text2) => paint("red", text2),
19032
+ warn: (text2) => paint("yellow", text2),
19033
+ state: (value) => {
19034
+ const tone = STATES.get(value.toLowerCase());
19035
+ return tone ? paint(tone, value) : value;
19036
+ },
19037
+ code: (text2, language) => {
19038
+ const rule = `\u2500\u2500 ${language} \u2500\u2500`;
19039
+ return [
19040
+ self.dim(rule),
19041
+ ...text2.split("\n").map((line) => self.dim(line)),
19042
+ self.dim("\u2500".repeat(rule.length))
19043
+ ];
19044
+ },
19045
+ json: (value) => JSON.stringify(value, null, 2).split("\n").map((line) => jsonLine(line, paint)).join("\n")
19046
+ };
19047
+ return self;
19048
+ }
19049
+ function jsonLine(line, paint) {
19050
+ const match = /^(\s*)(?:("(?:[^"\\]|\\.)*")(: ))?(.*?)(,?)$/.exec(line);
19051
+ if (!match) return line;
19052
+ const [, indent = "", key, colon, value = "", comma = ""] = match;
19053
+ const painted = value === "" ? "" : /^[[\]{}]+$/.test(value) ? paint("dim", value) : value.startsWith('"') ? value : paint("green", value);
19054
+ return indent + (key && colon ? paint("bold", key) + paint("dim", colon) : "") + painted + (comma ? paint("dim", comma) : "");
19055
+ }
19056
+
18764
19057
  // src/human.ts
18765
- function humanResult(command, result, context) {
18766
- const lines = [];
18767
- if (command === "app snippet" && "snippet" in result && typeof result.snippet === "string")
18768
- return result.snippet;
18769
- if ("state" in result && result.state === "pending" && "url" in result) {
18770
- lines.push(`Waiting for browser handoff (${result.id}).`, result.url);
18771
- lines.push(`Resume: agw operation wait ${result.id}`);
18772
- } else if ("guidance" in result && "app" in result) {
18773
- lines.push(
18774
- `${command === "app add" ? "Created" : "Updated"} ${result.app.name}`,
18775
- `Gateway: ${context.url}`,
18776
- `App ID: ${result.app.id}`,
18777
- ""
18778
- );
18779
- const parsed = AppConfigSchema.safeParse(result.app.config);
18780
- if (parsed.success) {
18781
- const config2 = parsed.data;
18782
- if (config2.authentication.type === "apple_app_attest") {
18783
- const endUser = config2.authentication.end_user;
18784
- lines.push(
18785
- `App Attest: ${(config2.authentication.app_attest.environments ?? ["production"]).join(" + ")}`,
18786
- `User identity: ${endUser.source === "app_install" ? "per installation; sign-in not required" : "verified issuer; integrate your sign-in SDK"}`,
18787
- `Paid subscription check: ${endUser.source === "issuer" && endUser.issuer.required_claims.length ? "configured claim requirements" : "off"}`
18788
- );
18789
- } else lines.push("Authentication: server application API key");
18790
- const limits = config2.limits?.per_user;
18791
- lines.push(
18792
- `Per-user limits: ${limits?.requests.per_minute ?? "unlimited"} requests/minute, ${limits?.requests.per_day ?? "unlimited"}/day`
18793
- );
18794
- }
18795
- if (result.applicationKey)
18796
- lines.push(`Key saved: ${result.applicationKey.storagePath}`);
18797
- if (result.guidance) lines.push("", result.guidance);
18798
- if ("snippet" in result && result.snippet) lines.push("", result.snippet);
18799
- } else if (command === "provider add" || command === "provider-gateway add") {
18800
- const stored = "provider" in result ? result.provider : "gateway" in result ? result.gateway : void 0;
18801
- const label = stored?.name ?? stored?.type ?? "connection";
18802
- const id = stored?.id ?? ("id" in result ? result.id : void 0);
18803
- lines.push(
18804
- `Stored ${label} (${id}).`,
18805
- `Gateway: ${context.url}`,
18806
- "Credential stored; no upstream probe or inference was performed."
18807
- );
18808
- } else if ("output" in result && typeof result.output === "string") {
18809
- lines.push(`Saved: ${result.output}`);
18810
- } else lines.push(JSON.stringify(result, null, 2));
18811
- if (context.accountId) lines.push(`Account: ${context.accountId}`);
19058
+ function kv(entries, style2) {
19059
+ const rows = [];
19060
+ for (const [key, value] of entries)
19061
+ if (value !== void 0 && value !== null) rows.push([key, String(value)]);
19062
+ const width = Math.max(0, ...rows.map(([key]) => key.length + 1));
19063
+ return rows.map(
19064
+ ([key, value]) => style2.dim(`${key}:`.padEnd(width + 1)) + style2.state(value)
19065
+ );
19066
+ }
19067
+ function table(headers, rows, style2) {
19068
+ const widths = headers.map(
19069
+ (header, column) => Math.max(header.length, ...rows.map((row) => (row[column] ?? "").length))
19070
+ );
19071
+ const line = (cells, tone) => cells.map(
19072
+ (cell, column) => tone(cell) + (column === widths.length - 1 ? "" : " ".repeat((widths[column] ?? 0) + 2 - cell.length))
19073
+ ).join("").trimEnd();
19074
+ return [
19075
+ line(headers, (cell) => style2.dim(cell)),
19076
+ ...rows.map((row) => line(row, (cell) => style2.state(cell)))
19077
+ ];
19078
+ }
19079
+ var yesNo = (value) => value ? "yes" : "no";
19080
+ function humanResult(command, result, context, style2 = plain) {
19081
+ if (command === "app snippet" && "snippet" in result && typeof result.snippet === "string") {
19082
+ if (!style2.enabled) return result.snippet;
19083
+ const language = "language" in result ? result.language : "code";
19084
+ return style2.code(result.snippet, language).join("\n") + "\n";
19085
+ }
19086
+ const lines = render(command, result, context, style2);
18812
19087
  if ("trial" in result && result.trial)
18813
19088
  lines.push(`Free access ends: ${result.trial.endsAt}`);
18814
19089
  if ("account" in result && result.account?.expiresAt)
18815
19090
  lines.push(`Recovery deadline: ${result.account.expiresAt}`);
18816
19091
  return lines.join("\n") + "\n";
18817
19092
  }
19093
+ function render(command, result, context, style2) {
19094
+ if ("state" in result && result.state === "pending" && "url" in result)
19095
+ return [
19096
+ style2.headline(`Waiting for browser handoff (${result.id}).`),
19097
+ result.url,
19098
+ `Resume: agw operation wait ${result.id}`
19099
+ ];
19100
+ switch (command) {
19101
+ case "account status":
19102
+ case "account login":
19103
+ case "account claim":
19104
+ case "deployment connect":
19105
+ case "deployment status":
19106
+ return status(command, result, context, style2);
19107
+ case "provider types":
19108
+ case "provider-gateway types":
19109
+ case "provider list":
19110
+ case "provider-gateway list":
19111
+ case "app list":
19112
+ case "app key list":
19113
+ return list(command, result, style2);
19114
+ case "provider show":
19115
+ case "provider-gateway show":
19116
+ case "app show":
19117
+ return show(command, result, style2);
19118
+ case "provider update":
19119
+ case "provider-gateway update":
19120
+ case "provider rotate-key":
19121
+ case "provider-gateway rotate-key":
19122
+ case "provider add":
19123
+ case "provider-gateway add":
19124
+ case "provider remove":
19125
+ case "provider-gateway remove":
19126
+ case "app remove":
19127
+ case "app key add":
19128
+ case "app key revoke":
19129
+ case "account logout":
19130
+ return mutation(command, result, context, style2);
19131
+ case "usage show":
19132
+ case "usage breakdown":
19133
+ return usage(command, result, style2);
19134
+ case "operation status":
19135
+ case "operation wait":
19136
+ return operation2(result, style2);
19137
+ case "app add":
19138
+ case "app update":
19139
+ case "app validate":
19140
+ case "app check":
19141
+ case "app snippet":
19142
+ return app(command, result, context, style2);
19143
+ case "deployment setup":
19144
+ case "deployment update":
19145
+ case "deployment domain":
19146
+ return deployment(result, style2);
19147
+ }
19148
+ }
19149
+ function status(command, result, context, style2) {
19150
+ const did = command === "account login" ? [style2.headline(`Signed in to ${context.url}.`)] : command === "deployment connect" ? [style2.headline(`Connected to ${context.url}.`)] : [];
19151
+ if ("billing" in result)
19152
+ return [
19153
+ ...did,
19154
+ ...kv(
19155
+ [
19156
+ ["Gateway", context.url],
19157
+ ["Account", `${result.account.name} (${result.account.id})`],
19158
+ ["Claimed", yesNo(result.account.claimed)],
19159
+ ["Deployment", result.deployment.mode === "cloud" ? "cloud" : "self-hosted"],
19160
+ ["Console", result.deployment.consoleOrigin]
19161
+ ],
19162
+ style2
19163
+ )
19164
+ ];
19165
+ if ("protocolVersion" in result)
19166
+ return [
19167
+ ...did,
19168
+ ...kv(
19169
+ [
19170
+ ["Gateway", result.url],
19171
+ ["Connected", yesNo(result.connected)],
19172
+ ["Authenticated", yesNo(result.authenticated)],
19173
+ ["Version", result.serverVersion],
19174
+ ["Deployment", result.deployment.mode === "cloud" ? "cloud" : "self-hosted"],
19175
+ ["Console", result.consoleOrigin],
19176
+ ["Provider types", result.providers.length],
19177
+ ["Provider gateway types", result.providerGateways.length]
19178
+ ],
19179
+ style2
19180
+ )
19181
+ ];
19182
+ if ("state" in result) return [...did, ...operation2(result, style2)];
19183
+ return [...did, style2.json(result)];
19184
+ }
19185
+ function list(command, result, style2) {
19186
+ if (command === "provider types" || command === "provider-gateway types") {
19187
+ if (!Array.isArray(result)) return [style2.json(result)];
19188
+ const types = result;
19189
+ if (!types.length) return ["No types."];
19190
+ return table(
19191
+ ["TYPE", "NAME", "BASE URL"],
19192
+ types.map((entry) => [entry.type, entry.name, "baseUrl" in entry ? entry.baseUrl : ""]),
19193
+ style2
19194
+ );
19195
+ }
19196
+ if (command === "provider list") {
19197
+ if (!("providers" in result) || "appId" in result || "protocolVersion" in result)
19198
+ return [style2.json(result)];
19199
+ if (!result.providers.length) return ["No providers."];
19200
+ return table(
19201
+ ["ID", "SLUG", "TYPE", "NAME", "STATUS", "CREATED"],
19202
+ result.providers.map((provider) => [
19203
+ provider.id,
19204
+ provider.slug,
19205
+ provider.type,
19206
+ provider.name,
19207
+ provider.status,
19208
+ provider.createdAt
19209
+ ]),
19210
+ style2
19211
+ );
19212
+ }
19213
+ if (command === "provider-gateway list" && "gateways" in result) {
19214
+ if (!result.gateways.length) return ["No provider gateways."];
19215
+ return table(
19216
+ ["ID", "TYPE", "NAME", "STATUS", "PROVIDERS", "CREATED"],
19217
+ result.gateways.map((gateway) => [
19218
+ gateway.id,
19219
+ gateway.type,
19220
+ gateway.name,
19221
+ gateway.status,
19222
+ String(gateway.providerCount),
19223
+ gateway.createdAt
19224
+ ]),
19225
+ style2
19226
+ );
19227
+ }
19228
+ if (command === "app list" && "apps" in result && !("totals" in result)) {
19229
+ if (!result.apps.length) return ["No apps."];
19230
+ return table(
19231
+ ["ID", "NAME", "AUTH", "STATUS", "PROVIDERS", "CREATED"],
19232
+ result.apps.map((entry) => [
19233
+ entry.id,
19234
+ entry.name,
19235
+ entry.authentication_type,
19236
+ entry.status,
19237
+ String(entry.providers.length),
19238
+ entry.created_at
19239
+ ]),
19240
+ style2
19241
+ );
19242
+ }
19243
+ if (command === "app key list" && "keys" in result && "app_id" in result) {
19244
+ if (!result.keys.length) return [`No keys for ${result.app_id}.`];
19245
+ return table(
19246
+ ["ID", "NAME", "PREFIX", "STATUS", "CREATED", "LAST USED"],
19247
+ result.keys.map((key) => [
19248
+ key.id,
19249
+ key.name,
19250
+ key.key_prefix,
19251
+ key.status,
19252
+ key.created_at,
19253
+ key.last_used_at ?? "never"
19254
+ ]),
19255
+ style2
19256
+ );
19257
+ }
19258
+ return [style2.json(result)];
19259
+ }
19260
+ function show(command, result, style2) {
19261
+ if (command === "app show" && "app" in result && !("guidance" in result)) {
19262
+ const lines = [style2.headline(`${result.app.name} (${result.app.id})`)];
19263
+ if (result.config_error)
19264
+ lines.push(style2.alert(`Configuration error: ${result.config_error}`));
19265
+ return [...lines, "", style2.json(result)];
19266
+ }
19267
+ if ("slug" in result)
19268
+ return [
19269
+ style2.headline(`${result.name} (${result.id}), type ${result.type}, slug ${result.slug}`),
19270
+ "",
19271
+ style2.json(result)
19272
+ ];
19273
+ if ("referencedCount" in result)
19274
+ return [
19275
+ style2.headline(`${result.name} (${result.id}), type ${result.type}`),
19276
+ "",
19277
+ style2.json(result)
19278
+ ];
19279
+ return [style2.json(result)];
19280
+ }
19281
+ function mutation(command, result, context, style2) {
19282
+ if ("loggedOut" in result)
19283
+ return [
19284
+ style2.headline(`Signed out of ${context.url}.`) + " The stored credential was removed."
19285
+ ];
19286
+ if ("deleted" in result) {
19287
+ if ("provider_id" in result)
19288
+ return [style2.headline(`Removed provider ${result.provider_id}.`)];
19289
+ if ("provider_gateway_id" in result)
19290
+ return [style2.headline(`Removed provider gateway ${result.provider_gateway_id}.`)];
19291
+ return [
19292
+ style2.headline(`Removed app ${result.app_id}.`) + ` ${result.removed_users} users removed; usage events retained.`
19293
+ ];
19294
+ }
19295
+ if ("key" in result)
19296
+ return [
19297
+ style2.headline(
19298
+ `Revoked key ${result.key.name} (${result.key.id}) for app ${result.app_id}.`
19299
+ )
19300
+ ];
19301
+ if ("applicationKey" in result && "appId" in result)
19302
+ return [
19303
+ style2.headline(
19304
+ `Created key ${result.applicationKey.name} (${result.applicationKey.id}) for app ${result.appId}.`
19305
+ ),
19306
+ ...kv([["Key saved", result.applicationKey.storagePath]], style2)
19307
+ ];
19308
+ if ("provider" in result) {
19309
+ const added = command === "provider add";
19310
+ return [
19311
+ style2.headline(
19312
+ `${added ? "Stored" : "Updated"} provider ${result.provider.name} (${result.provider.id}).`
19313
+ ),
19314
+ ...kv(
19315
+ [
19316
+ ["Gateway", context.url],
19317
+ ["Status", result.provider.status],
19318
+ ["Key hint", result.provider.secretHint]
19319
+ ],
19320
+ style2
19321
+ ),
19322
+ ...added ? ["Credential stored; no upstream probe or inference was performed."] : []
19323
+ ];
19324
+ }
19325
+ if ("gateway" in result) {
19326
+ const added = command === "provider-gateway add";
19327
+ return [
19328
+ style2.headline(
19329
+ `${added ? "Stored" : "Updated"} provider gateway ${result.gateway.name} (${result.gateway.id}).`
19330
+ ),
19331
+ ...kv(
19332
+ [
19333
+ ["Gateway", context.url],
19334
+ ["Status", result.gateway.status],
19335
+ ["Token hint", result.gateway.secretHint]
19336
+ ],
19337
+ style2
19338
+ ),
19339
+ ...added ? ["Credential stored; no upstream probe or inference was performed."] : []
19340
+ ];
19341
+ }
19342
+ return [style2.json(result)];
19343
+ }
19344
+ function usage(command, result, style2) {
19345
+ if (command === "usage breakdown" && "rows" in result) {
19346
+ const head = kv(
19347
+ [
19348
+ ["App", result.app_id],
19349
+ ["Grouped by", result.by],
19350
+ ["Period", `${result.from} to ${result.to}`]
19351
+ ],
19352
+ style2
19353
+ );
19354
+ const rows = result.rows.length ? table(
19355
+ ["KEY", "REQUESTS", "INPUT", "OUTPUT", "COST USD", "ERRORS", "BLOCKED"],
19356
+ result.rows.map((row) => [
19357
+ row.key ?? "(none)",
19358
+ String(row.requests),
19359
+ String(row.input_tokens),
19360
+ String(row.output_tokens),
19361
+ row.cost_usd.toFixed(4),
19362
+ String(row.errors),
19363
+ String(row.blocked)
19364
+ ]),
19365
+ style2
19366
+ ) : ["No usage in this period."];
19367
+ return [...head, "", ...rows, "", result.coverage.source];
19368
+ }
19369
+ if ("totals" in result)
19370
+ return [
19371
+ ...kv(
19372
+ [
19373
+ ["Month", result.month],
19374
+ ["Requests", result.totals.requests],
19375
+ ["Input tokens", result.totals.input_tokens],
19376
+ ["Output tokens", result.totals.output_tokens],
19377
+ ["Cost USD", result.totals.cost_usd.toFixed(4)],
19378
+ ["Errors", result.totals.errors],
19379
+ ["Blocked", result.totals.blocked]
19380
+ ],
19381
+ style2
19382
+ ),
19383
+ "",
19384
+ // The totals are the sum of these rows, so the apps they came from are
19385
+ // printed rather than counted: a month is read to find which app spent it.
19386
+ ...result.apps.length ? table(
19387
+ ["APP", "REQUESTS", "INPUT", "OUTPUT", "COST USD", "ERRORS", "BLOCKED"],
19388
+ result.apps.map((entry) => [
19389
+ entry.deleted ? `${entry.appId} (deleted)` : entry.appId,
19390
+ String(entry.requests),
19391
+ String(entry.input_tokens),
19392
+ String(entry.output_tokens),
19393
+ entry.cost_usd.toFixed(4),
19394
+ String(entry.errors),
19395
+ String(entry.blocked)
19396
+ ]),
19397
+ style2
19398
+ ) : ["No app usage recorded."],
19399
+ "",
19400
+ result.coverage.historicalAttribution
19401
+ ];
19402
+ if ("requests" in result)
19403
+ return kv(
19404
+ [
19405
+ ["App", result.app_id],
19406
+ ["Month", result.month],
19407
+ ["Requests", result.requests],
19408
+ ["Input tokens", result.input_tokens],
19409
+ ["Cached input tokens", result.cached_input_tokens],
19410
+ ["Cache write tokens", result.cache_write_tokens],
19411
+ ["Output tokens", result.output_tokens],
19412
+ ["Cost USD", result.cost_usd.toFixed(4)]
19413
+ ],
19414
+ style2
19415
+ );
19416
+ return [style2.json(result)];
19417
+ }
19418
+ function operation2(result, style2) {
19419
+ if (!("state" in result)) return [style2.json(result)];
19420
+ const stored = "result" in result ? result.result : void 0;
19421
+ return kv(
19422
+ [
19423
+ ["Operation", result.id],
19424
+ ["State", result.state],
19425
+ ["Expires", result.expiresAt],
19426
+ ["Account", stored?.accountId],
19427
+ ["Provider", stored?.provider && `${stored.provider.name} (${stored.provider.id})`],
19428
+ ["Provider gateway", stored?.gateway && `${stored.gateway.name} (${stored.gateway.id})`]
19429
+ ],
19430
+ style2
19431
+ );
19432
+ }
19433
+ function app(command, result, context, style2) {
19434
+ if ("output" in result) return [style2.headline(`Saved: ${result.output}`)];
19435
+ if ("ready" in result)
19436
+ return [
19437
+ style2.headline(
19438
+ `${result.appId} is ${result.ready ? "ready to serve requests" : "not ready to serve requests"}.`
19439
+ ),
19440
+ ...kv(
19441
+ [
19442
+ ["Status", result.status],
19443
+ ["Providers", result.providers.length],
19444
+ ["Active providers", result.providers.filter((p) => p.status === "active").length]
19445
+ ],
19446
+ style2
19447
+ ),
19448
+ ...notes([
19449
+ ...result.validation.remote ? [] : result.validation.skipped,
19450
+ ...result.limitations
19451
+ ])
19452
+ ];
19453
+ if ("definition" in result) {
19454
+ const dryRun = "dryRun" in result;
19455
+ return [
19456
+ style2.headline(
19457
+ `${dryRun ? "Dry run: " : ""}${result.definition.name} is valid${result.validation.remote ? " here and on the gateway" : " locally"}.`
19458
+ ),
19459
+ ...kv(
19460
+ [
19461
+ ["Gateway", context.url],
19462
+ ["Status", result.definition.status],
19463
+ ["Authentication", result.definition.config.authentication.type],
19464
+ ["App ID", result.validation.remote ? result.validation.app_id : void 0]
19465
+ ],
19466
+ style2
19467
+ ),
19468
+ ...notes(result.validation.remote ? [] : result.validation.skipped),
19469
+ ...dryRun ? ["", "Nothing was written."] : []
19470
+ ];
19471
+ }
19472
+ if ("guidance" in result && "app" in result)
19473
+ return written(command, result, context, style2);
19474
+ return [style2.json(result)];
19475
+ }
19476
+ var notes = (values) => values.length ? ["", ...values.map((value) => `- ${value}`)] : [];
19477
+ function written(command, result, context, style2) {
19478
+ const parsed = AppConfigSchema.safeParse(result.app.config);
19479
+ const details = [];
19480
+ let language = "shell";
19481
+ if (parsed.success) {
19482
+ const config2 = parsed.data;
19483
+ if (config2.authentication.type === "apple_app_attest") {
19484
+ language = "swift";
19485
+ const endUser = config2.authentication.end_user;
19486
+ details.push(
19487
+ [
19488
+ "App Attest",
19489
+ (config2.authentication.app_attest.environments ?? ["production"]).join(" + ")
19490
+ ],
19491
+ [
19492
+ "User identity",
19493
+ endUser.source === "app_install" ? "per installation; sign-in not required" : "verified issuer; integrate your sign-in SDK"
19494
+ ],
19495
+ [
19496
+ "Paid subscription check",
19497
+ endUser.source === "issuer" && endUser.issuer.required_claims.length ? "configured claim requirements" : "off"
19498
+ ]
19499
+ );
19500
+ } else details.push(["Authentication", "server application API key"]);
19501
+ const limits = config2.limits?.per_user;
19502
+ details.push([
19503
+ "Per-user limits",
19504
+ `${limits?.requests.per_minute ?? "unlimited"} requests/minute, ${limits?.requests.per_day ?? "unlimited"}/day`
19505
+ ]);
19506
+ }
19507
+ if (result.applicationKey)
19508
+ details.push(["Key saved", result.applicationKey.storagePath]);
19509
+ return [
19510
+ style2.headline(
19511
+ `${command === "app add" ? "Created" : "Updated"} app ${result.app.name} (${result.app.id}).`
19512
+ ),
19513
+ ...kv([["Gateway", context.url], ...details], style2),
19514
+ ...result.guidance ? ["", result.guidance] : [],
19515
+ ..."snippet" in result && result.snippet ? ["", ...style2.code(result.snippet, language)] : []
19516
+ ];
19517
+ }
19518
+ function deployment(result, style2) {
19519
+ if ("hostname" in result)
19520
+ return kv(
19521
+ [
19522
+ [result.dryRun ? "Would attach domain" : "Domain", result.hostname],
19523
+ ["Worker", result.worker],
19524
+ ["Cloudflare account", result.accountId],
19525
+ ["Zone", result.zoneId],
19526
+ ["Previous URL", result.previousUrl],
19527
+ ["URL", result.url],
19528
+ ["Note", result.note]
19529
+ ],
19530
+ style2
19531
+ );
19532
+ if ("fromVersion" in result)
19533
+ return kv(
19534
+ [
19535
+ [result.dryRun ? "Would update worker" : "Worker", result.worker],
19536
+ ["Cloudflare account", result.accountId],
19537
+ ["Database", result.databaseId],
19538
+ ["From version", result.fromVersion],
19539
+ ["To version", result.toVersion],
19540
+ ["Updated", result.updated ? "yes" : void 0]
19541
+ ],
19542
+ style2
19543
+ );
19544
+ if ("installed" in result || "resources" in result)
19545
+ return kv(
19546
+ [
19547
+ [result.dryRun ? "Would install worker" : "Worker", result.name],
19548
+ ["Cloudflare account", result.accountId],
19549
+ ["URL", result.url],
19550
+ ["Version", result.version],
19551
+ ["Domain", result.domain],
19552
+ ["Pending domain", result.pendingDomain],
19553
+ ["Deployment", result.deploymentId],
19554
+ ["Resources", result.resources?.join(", ")],
19555
+ ["Backup", result.backup],
19556
+ ["Installed", result.installed ? "yes" : void 0],
19557
+ ["Note", result.note]
19558
+ ],
19559
+ style2
19560
+ );
19561
+ return [style2.json(result)];
19562
+ }
18818
19563
 
18819
19564
  // src/transport.ts
18820
19565
  var MESSAGE_LIMIT = 300;
@@ -18833,6 +19578,27 @@ function reported(value, sent) {
18833
19578
  }
18834
19579
  var RECOVERABLE_RESOURCE_CODES = ["resource_receipt_expired", "resource_key_unavailable"];
18835
19580
  var RECOVERABLE_RESOURCE_FIELDS = ["appId", "keyId", "providerId", "providerGatewayId"];
19581
+ var LIMIT_CODES = [
19582
+ "rate_limited",
19583
+ "app_rate_limited",
19584
+ "app_budget_exhausted",
19585
+ "billing_request_quota_exceeded",
19586
+ "billing_plan_limit_reached"
19587
+ ];
19588
+ var LIMIT_TEXT_FIELDS = ["scope", "resetAt"];
19589
+ var LIMIT_NUMBER_FIELDS = ["limit", "used", "windowSeconds", "retryAfterSeconds"];
19590
+ function nextActionFor(status2, code, details) {
19591
+ if (status2 === 401) return "Run agw account login.";
19592
+ if (code === "billing_plan_limit_reached")
19593
+ return "Delete one you no longer need, or move to a plan whose limits are higher.";
19594
+ if (status2 === 429) {
19595
+ const seconds = details.retryAfterSeconds;
19596
+ if (seconds === void 0)
19597
+ return "Wait for the limit named in the message to reset, then run the same command again.";
19598
+ return seconds >= 3600 && details.resetAt !== void 0 ? `Wait until ${details.resetAt}, then run the same command again.` : `Wait ${seconds} seconds, then run the same command again.`;
19599
+ }
19600
+ return "Review command configuration and account status.";
19601
+ }
18836
19602
  var Transport = class {
18837
19603
  fetch;
18838
19604
  constructor(fetchImpl = fetch) {
@@ -18887,18 +19653,27 @@ var Transport = class {
18887
19653
  const wire = data ?? {};
18888
19654
  const code = typeof wire.error?.code === "string" ? wire.error.code : typeof wire.code === "string" ? wire.code : `http_${response.status}`;
18889
19655
  const details = { status: response.status };
18890
- if (RECOVERABLE_RESOURCE_CODES.includes(code)) {
18891
- const reported2 = wire.error?.data ?? {};
19656
+ const facts = wire.error?.data ?? {};
19657
+ if (RECOVERABLE_RESOURCE_CODES.includes(code))
18892
19658
  for (const field of RECOVERABLE_RESOURCE_FIELDS) {
18893
- const value = reported2[field];
19659
+ const value = facts[field];
18894
19660
  if (typeof value === "string") details[field] = value;
18895
19661
  }
19662
+ if (LIMIT_CODES.includes(code)) {
19663
+ for (const field of LIMIT_TEXT_FIELDS) {
19664
+ const value = facts[field];
19665
+ if (typeof value === "string") details[field] = value;
19666
+ }
19667
+ for (const field of LIMIT_NUMBER_FIELDS) {
19668
+ const value = facts[field];
19669
+ if (typeof value === "number") details[field] = value;
19670
+ }
18896
19671
  }
18897
19672
  const explanation = reported(wire.error?.message ?? wire.message, body);
18898
19673
  fail(
18899
19674
  code,
18900
19675
  `Deployment rejected the request (HTTP ${response.status})` + (explanation ? `: ${explanation}` : "."),
18901
- response.status === 401 ? "Run agw account login." : "Review command configuration and account status.",
19676
+ nextActionFor(response.status, code, details),
18902
19677
  3,
18903
19678
  details
18904
19679
  );
@@ -18934,7 +19709,7 @@ async function usageCommand(ctx, command, flags) {
18934
19709
  const m = month(flags.month ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7));
18935
19710
  return flags.app ? (await ctx.call("getAppUsage", [flags.app, { month: m }])).data : (await ctx.call("getCliUsage", [{ month: m }])).data;
18936
19711
  }
18937
- const app = await required2(flags, "app");
19712
+ const app2 = await required2(flags, "app");
18938
19713
  const by = flags.by ?? "model";
18939
19714
  if (!["provider", "model", "status"].includes(by))
18940
19715
  fail("invalid_input", "--by must be provider, model or status.");
@@ -18946,7 +19721,7 @@ async function usageCommand(ctx, command, flags) {
18946
19721
  if (from > to) fail("invalid_input", "--from must not be later than --to.");
18947
19722
  const limit = positive(flags.limit ?? 50, 200);
18948
19723
  return {
18949
- ...(await ctx.call("getAppUsageBreakdown", [app, { from, to, by, limit }])).data,
19724
+ ...(await ctx.call("getAppUsageBreakdown", [app2, { from, to, by, limit }])).data,
18950
19725
  coverage: {
18951
19726
  from,
18952
19727
  to,
@@ -19000,7 +19775,8 @@ async function execute(parsed, ctx) {
19000
19775
  async function main(argv = process.argv.slice(2), {
19001
19776
  store = new StateStore(),
19002
19777
  transport = new Transport(),
19003
- stdout = process.stdout
19778
+ stdout = process.stdout,
19779
+ stderr: stderr2 = process.stderr
19004
19780
  } = {}) {
19005
19781
  const json2 = argv.includes("--json");
19006
19782
  try {
@@ -19024,8 +19800,8 @@ async function main(argv = process.argv.slice(2), {
19024
19800
  };
19025
19801
  const rendered = ctx.onboarding ? { ...result, ...ctx.onboarding } : result;
19026
19802
  const output = { schemaVersion: 1, ok: true, context, result: rendered };
19027
- const text2 = json2 ? JSON.stringify(output) + "\n" : humanResult(parsed.command, rendered, context);
19028
- if (stdout instanceof Writable) {
19803
+ const text2 = json2 ? JSON.stringify(output) + "\n" : humanResult(parsed.command, rendered, context, styleFor(stdout));
19804
+ if (stdout instanceof Writable2) {
19029
19805
  await new Promise(
19030
19806
  (resolve3, reject2) => stdout.write(
19031
19807
  text2,
@@ -19047,17 +19823,27 @@ async function main(argv = process.argv.slice(2), {
19047
19823
  4
19048
19824
  );
19049
19825
  const details = e.details ? CliErrorDetailsSchema.safeParse(e.details) : void 0;
19050
- const output = {
19051
- schemaVersion: 1,
19052
- ok: false,
19053
- error: {
19054
- code: e.code,
19055
- message: e.message,
19056
- nextAction: e.nextAction,
19057
- ...details?.success ? { details: details.data } : {}
19058
- }
19826
+ const reported2 = {
19827
+ code: e.code,
19828
+ message: e.message,
19829
+ nextAction: e.nextAction,
19830
+ ...details?.success ? { details: details.data } : {}
19059
19831
  };
19060
- stdout.write(JSON.stringify(output, null, json2 ? void 0 : 2) + "\n");
19832
+ if (json2) {
19833
+ stdout.write(
19834
+ JSON.stringify({ schemaVersion: 1, ok: false, error: reported2 }) + "\n"
19835
+ );
19836
+ return e.exitCode;
19837
+ }
19838
+ const style2 = styleFor(stderr2);
19839
+ const lines = [
19840
+ style2.alert(`Error: ${e.message}`),
19841
+ style2.warn(`Next: ${e.nextAction}`),
19842
+ style2.dim(`Code: ${e.code}`)
19843
+ ];
19844
+ for (const [key, value] of Object.entries(reported2.details ?? {}))
19845
+ lines.push(` ${style2.dim(`${key}:`)} ${String(value)}`);
19846
+ stderr2.write(lines.join("\n") + "\n");
19061
19847
  return e.exitCode;
19062
19848
  }
19063
19849
  }