@bnbagent/studio-cli 0.0.6-alpha.1 → 0.0.6-alpha.3

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/bag.js CHANGED
@@ -2,6 +2,8 @@
2
2
  import {
3
3
  B402_RUNTIME_KEYS,
4
4
  CliExit,
5
+ DEFAULT_B402_PRICE_USD,
6
+ DEFAULT_B402_TESTNET_BASE_URL,
5
7
  act,
6
8
  agentcoreFlavor,
7
9
  b402Credentials,
@@ -18,7 +20,9 @@ import {
18
20
  hasA2aFace,
19
21
  hasMcpFace,
20
22
  hasX402Face,
23
+ isB402TestnetBaseUrl,
21
24
  nativeProtocolOf,
25
+ normalizeB402PriceUsd,
22
26
  normalizeProtocolFaces,
23
27
  printErr,
24
28
  printOut,
@@ -34,8 +38,11 @@ import {
34
38
  trialFromDeployCliJson,
35
39
  whichBin,
36
40
  withDeployFiles,
37
- x402DeploySummary
38
- } from "./chunk-M3ODFCA7.js";
41
+ x402DeploySummary,
42
+ x402SellerIsFree,
43
+ x402SellerPricingState,
44
+ x402SellerUsesB402
45
+ } from "./chunk-A7NAGZHR.js";
39
46
  import {
40
47
  TWAK_CLI_MIN_VERSION,
41
48
  TWAK_CLI_VERSION,
@@ -97,7 +104,7 @@ import {
97
104
  var CAMPAIGN_DOC_URL = "https://www.bnbchain.org/en/blog/bnb-agent-studio-is-live-on-bnb-chain-ai-agents-from-one-prompt";
98
105
  var CAMPAIGN_CHECK_TIMEOUT_MS = 6e3;
99
106
  async function fetchCampaignActive() {
100
- const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-N6TPN6XA.js");
107
+ const { bnbPlatformApiUrl: bnbPlatformApiUrl2 } = await import("./deployCli-264UE6KB.js");
101
108
  const controller = new AbortController();
102
109
  const timer = setTimeout(() => controller.abort(), CAMPAIGN_CHECK_TIMEOUT_MS);
103
110
  try {
@@ -2095,6 +2102,77 @@ function devPortInUse(port = 9e3, timeoutMs = 250) {
2095
2102
  });
2096
2103
  }
2097
2104
 
2105
+ // src/cli/_erc8183Config.ts
2106
+ var ERC8183_ADDRESS_OVERRIDE_KEYS = [
2107
+ "ERC8183_COMMERCE_ADDRESS",
2108
+ "ERC8183_ROUTER_ADDRESS",
2109
+ "ERC8183_POLICY_ADDRESS"
2110
+ ];
2111
+ var MAX_UINT256 = 2n ** 256n - 1n;
2112
+ function erc8183PricingState(pay) {
2113
+ const priceRaw = pay.price;
2114
+ const price = String(priceRaw ?? "").trim();
2115
+ if (!price) return { kind: "unset" };
2116
+ if (typeof priceRaw !== "string") {
2117
+ return { kind: "invalid", field: "price", value: price };
2118
+ }
2119
+ const minRaw = pay.min_price;
2120
+ const maxRaw = pay.max_price;
2121
+ const minPrice = String(minRaw ?? "").trim() || "0";
2122
+ const maxPrice = String(maxRaw ?? "").trim();
2123
+ if (minRaw !== void 0 && typeof minRaw !== "string") {
2124
+ return { kind: "invalid", field: "min_price", value: minPrice };
2125
+ }
2126
+ if (maxRaw !== void 0 && typeof maxRaw !== "string") {
2127
+ return { kind: "invalid", field: "max_price", value: maxPrice };
2128
+ }
2129
+ for (const [field, value] of [
2130
+ ["price", price],
2131
+ ["min_price", minPrice],
2132
+ ["max_price", maxPrice]
2133
+ ]) {
2134
+ if ((field !== "max_price" || value !== "") && !/^\d+$/.test(value)) {
2135
+ return { kind: "invalid", field, value };
2136
+ }
2137
+ }
2138
+ const list = BigInt(price);
2139
+ const min = BigInt(minPrice);
2140
+ const max = maxPrice ? BigInt(maxPrice) : MAX_UINT256;
2141
+ if (list > MAX_UINT256 || min > MAX_UINT256 || max > MAX_UINT256) {
2142
+ const [field, value] = list > MAX_UINT256 ? ["price", price] : min > MAX_UINT256 ? ["min_price", minPrice] : ["max_price", maxPrice];
2143
+ return { kind: "invalid", field, value };
2144
+ }
2145
+ const clampedToMax = list < max ? list : max;
2146
+ const effective = min > clampedToMax ? min : clampedToMax;
2147
+ const base = {
2148
+ listPrice: list,
2149
+ minPrice: min,
2150
+ maxPrice: max,
2151
+ effectivePrice: effective
2152
+ };
2153
+ if (list > 0n && effective === 0n) {
2154
+ return { kind: "clamped_to_zero", ...base };
2155
+ }
2156
+ return { kind: effective === 0n ? "free" : "paid", ...base };
2157
+ }
2158
+ function erc8183ContractOverrideState(env = process.env) {
2159
+ const present = ERC8183_ADDRESS_OVERRIDE_KEYS.filter(
2160
+ (key) => Boolean(env[key]?.trim())
2161
+ );
2162
+ const missing = ERC8183_ADDRESS_OVERRIDE_KEYS.filter(
2163
+ (key) => !env[key]?.trim()
2164
+ );
2165
+ const invalid = present.filter(
2166
+ (key) => !/^0x[0-9a-fA-F]{40}$/.test(env[key]?.trim() ?? "")
2167
+ );
2168
+ return {
2169
+ mode: present.length === 0 ? "canonical" : invalid.length > 0 ? "invalid" : missing.length === 0 ? "custom" : "partial",
2170
+ present: [...present],
2171
+ missing: [...missing],
2172
+ invalid
2173
+ };
2174
+ }
2175
+
2098
2176
  // src/cli/config.ts
2099
2177
  var MAX_QUOTE_TTL_SECONDS = NegotiationHandler.MAX_QUOTE_TTL_SECONDS;
2100
2178
  function registerConfig(program) {
@@ -2273,6 +2351,38 @@ function validateKnownKey(parts, value) {
2273
2351
  }
2274
2352
  return null;
2275
2353
  }
2354
+ function isErc8183AmountKey(parts) {
2355
+ return parts.length === 3 && parts[0] === "payments" && parts[1] === "erc8183" && ["price", "min_price", "max_price"].includes(parts[2]);
2356
+ }
2357
+ function isB402PriceKey(parts) {
2358
+ return parts.length === 3 && parts[0] === "payments" && parts[1] === "x402_seller" && parts[2] === "price_usd";
2359
+ }
2360
+ function coerceKnownValue(parts, rawValue, typeFlag) {
2361
+ if (!isErc8183AmountKey(parts) && !isB402PriceKey(parts)) {
2362
+ return coerceValue(rawValue, typeFlag);
2363
+ }
2364
+ if (isB402PriceKey(parts)) {
2365
+ if (typeFlag !== "auto" && typeFlag !== "string") {
2366
+ throw new Error(
2367
+ `${parts.join(".")} is stored as a decimal string; use --type string or omit --type.`
2368
+ );
2369
+ }
2370
+ return normalizeB402PriceUsd(rawValue);
2371
+ }
2372
+ if (typeFlag !== "auto" && typeFlag !== "string") {
2373
+ throw new Error(
2374
+ `${parts.join(".")} is stored as a decimal string; use --type string or omit --type.`
2375
+ );
2376
+ }
2377
+ const value = rawValue.trim();
2378
+ const isUnboundedMax = parts[2] === "max_price" && value === "";
2379
+ if (!isUnboundedMax && !/^\d+$/.test(value)) {
2380
+ throw new Error(
2381
+ `${parts.join(".")} must be a non-negative integer string in token base units; got ${JSON.stringify(rawValue)}.`
2382
+ );
2383
+ }
2384
+ return value;
2385
+ }
2276
2386
  function setDottedKey(text2, parts, value) {
2277
2387
  const key = parts[parts.length - 1];
2278
2388
  const section = parts.slice(0, -1).join(".");
@@ -2347,7 +2457,7 @@ async function cmdSet(key, rawValue, typeFlag, projectRoot) {
2347
2457
  }
2348
2458
  let value;
2349
2459
  try {
2350
- value = coerceValue(rawValue, typeFlag);
2460
+ value = coerceKnownValue(parts, rawValue, typeFlag);
2351
2461
  } catch (err) {
2352
2462
  printErr(`error: ${err instanceof Error ? err.message : String(err)}`);
2353
2463
  return 2;
@@ -2357,8 +2467,64 @@ async function cmdSet(key, rawValue, typeFlag, projectRoot) {
2357
2467
  printErr(`error: ${validationError}`);
2358
2468
  return 2;
2359
2469
  }
2360
- fs9.writeFileSync(tomlPath, setDottedKey(text2, parts, value), "utf-8");
2470
+ const updatedText = setDottedKey(text2, parts, value);
2471
+ fs9.writeFileSync(tomlPath, updatedText, "utf-8");
2361
2472
  printOut(`set ${key} = ${JSON.stringify(plainValue(value))}`);
2473
+ if (parts.length === 3 && parts[0] === "payments" && parts[1] === "erc8183" && parts[2] === "price") {
2474
+ const updated = parse4(updatedText);
2475
+ const payments = updated.payments !== null && typeof updated.payments === "object" && !Array.isArray(updated.payments) ? updated.payments : {};
2476
+ const erc = payments.erc8183 !== null && typeof payments.erc8183 === "object" && !Array.isArray(payments.erc8183) ? payments.erc8183 : {};
2477
+ const pricing = erc8183PricingState(erc);
2478
+ if (pricing.kind === "invalid" || pricing.kind === "unset") {
2479
+ printErr(
2480
+ "warning: price was saved, but existing min_price/max_price is invalid; run `bag doctor`."
2481
+ );
2482
+ }
2483
+ if (pricing.kind === "free") {
2484
+ printOut("pricing: FREE \u2014 buyers fund 0 token units; zero token escrow.");
2485
+ const contracts = erc8183ContractOverrideState();
2486
+ if (contracts.mode === "custom") {
2487
+ printOut(
2488
+ "ERC-8183 contracts: custom contract stack selected with all three address overrides."
2489
+ );
2490
+ } else if (contracts.mode === "partial") {
2491
+ printErr(
2492
+ `warning: ERC-8183 contract override is incomplete; set ${contracts.missing.join(", ")} before running a job.`
2493
+ );
2494
+ } else if (contracts.mode === "invalid") {
2495
+ printErr(
2496
+ `warning: invalid ERC-8183 address override(s): ${contracts.invalid.join(", ")}.`
2497
+ );
2498
+ } else {
2499
+ printErr(
2500
+ "warning: FREE requires a zero-price-compatible contract stack; set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together before deploy."
2501
+ );
2502
+ }
2503
+ } else if (pricing.kind === "clamped_to_zero") {
2504
+ printErr(
2505
+ "warning: PAID list price is clamped to 0; this is not an explicit FREE choice. Fix max_price or set price to 0."
2506
+ );
2507
+ } else if (pricing.kind === "paid") {
2508
+ printOut(
2509
+ `pricing: PAID \u2014 effective price ${pricing.effectivePrice} token base units after clamping.`
2510
+ );
2511
+ }
2512
+ }
2513
+ if (isB402PriceKey(parts)) {
2514
+ const pricing = x402SellerPricingState({ price_usd: value });
2515
+ if (pricing.kind === "free") {
2516
+ printOut(
2517
+ "x402 pricing: FREE \u2014 B402 verify/settle is bypassed; no payment or settlement audit."
2518
+ );
2519
+ printErr(
2520
+ "warning: /x402 is now an anonymous FREE endpoint. Confirm that unrestricted public access is intended."
2521
+ );
2522
+ } else if (pricing.kind === "paid") {
2523
+ printOut(
2524
+ `x402 pricing: PAID \u2014 $${pricing.priceUsd} per request through B402.`
2525
+ );
2526
+ }
2527
+ }
2362
2528
  if (parts[0] === "network") {
2363
2529
  if (await devPortInUse(8080)) {
2364
2530
  printErr(
@@ -3033,6 +3199,9 @@ function packageRoot() {
3033
3199
  }
3034
3200
  }
3035
3201
  function pnpmVersion() {
3202
+ if ("10.24.0") {
3203
+ return "10.24.0";
3204
+ }
3036
3205
  const file = path12.join(packageRoot(), "package.json");
3037
3206
  const pkg = JSON.parse(fs13.readFileSync(file, "utf-8"));
3038
3207
  const value = String(pkg.packageManager ?? "");
@@ -3061,6 +3230,46 @@ import * as fs14 from "fs";
3061
3230
  import * as path13 from "path";
3062
3231
  import { envLocalPath as envLocalPath7 } from "@bnbagent/studio-runtime/config";
3063
3232
  import { resolveTwakHome } from "@bnbagent/studio-runtime/wallet";
3233
+
3234
+ // src/cli/_twakContractTargets.ts
3235
+ import { resolveNetwork } from "@bnbagent/sdk";
3236
+ var CONTRACT_OVERRIDES = [
3237
+ ["ERC8004_REGISTRY_ADDRESS", "registryContract"],
3238
+ ["ERC8183_COMMERCE_ADDRESS", "commerceContract"],
3239
+ ["ERC8183_ROUTER_ADDRESS", "routerContract"],
3240
+ ["ERC8183_POLICY_ADDRESS", "policyContract"]
3241
+ ];
3242
+ function twakCustomContractOverrides(networkName, env = process.env) {
3243
+ let network;
3244
+ try {
3245
+ network = resolveNetwork(networkName);
3246
+ } catch {
3247
+ return [];
3248
+ }
3249
+ const erc8183 = erc8183ContractOverrideState(env);
3250
+ const out = [];
3251
+ for (const [envKey, field] of CONTRACT_OVERRIDES) {
3252
+ if (envKey.startsWith("ERC8183_") && erc8183.mode !== "custom") {
3253
+ continue;
3254
+ }
3255
+ const configured = env[envKey]?.trim() ?? "";
3256
+ if (!/^0x[0-9a-fA-F]{40}$/u.test(configured)) {
3257
+ continue;
3258
+ }
3259
+ const canonical = network[field];
3260
+ if (configured.toLowerCase() !== canonical.toLowerCase()) {
3261
+ out.push({ envKey, configured, canonical });
3262
+ }
3263
+ }
3264
+ return out;
3265
+ }
3266
+ function twakUnsupportedContractOverrides(networkName, env = process.env) {
3267
+ return twakCustomContractOverrides(networkName, env).filter(
3268
+ (item) => item.envKey.startsWith("ERC8183_")
3269
+ );
3270
+ }
3271
+
3272
+ // src/cli/_deploy/checks/twak.ts
3064
3273
  var AGENTCORE_DESCRIPTOR = path13.join("agentcore", "agentcore.json");
3065
3274
  function isTwak(data) {
3066
3275
  return tableOf2(data, "wallet").kind === "twak";
@@ -3165,6 +3374,26 @@ async function checkTwakCliPresent(root, _target) {
3165
3374
  }
3166
3375
  return [];
3167
3376
  }
3377
+ function checkTwakCustomContractsUnsupported(root, _target) {
3378
+ const data = loadAgentToml(root);
3379
+ if (!isTwak(data)) {
3380
+ return [];
3381
+ }
3382
+ const networkName = String(tableOf2(data, "network").default ?? "bsc-testnet");
3383
+ const overrides = twakUnsupportedContractOverrides(networkName);
3384
+ if (overrides.length === 0) {
3385
+ return [];
3386
+ }
3387
+ const keys = overrides.map((item) => item.envKey);
3388
+ return [
3389
+ {
3390
+ level: Level.CRITICAL,
3391
+ name: "twak_custom_contracts_unsupported",
3392
+ message: `wallet.kind='twak' cannot use the selected custom ERC-8183 targets (${keys.join(", ")}): twak v0.20.0 has no Commerce/Router/Policy address option and would otherwise execute against the wrong canonical contracts. Use wallet.kind='evm-local' for custom ERC-8183 contracts, or remove the overrides and use the canonical stack. ERC8004_REGISTRY_ADDRESS is supported.`,
3393
+ details: { network: networkName, override_keys: keys }
3394
+ }
3395
+ ];
3396
+ }
3168
3397
  async function checkTwakWalletExists(root, _target) {
3169
3398
  const data = loadAgentToml(root);
3170
3399
  if (!isTwak(data)) {
@@ -5197,7 +5426,9 @@ var LEGACY_SKILL_NAMES = [
5197
5426
  ];
5198
5427
  var ROUTER_SKILL_NAME = "bnbagent-studio";
5199
5428
  var META_FILENAME = ".bag-meta.json";
5200
- var HIDDEN_SKILL_NAMES = /* @__PURE__ */ new Set();
5429
+ var HIDDEN_SKILL_NAMES = /* @__PURE__ */ new Set([
5430
+ "bnbagent-studio-use-azure-foundry"
5431
+ ]);
5201
5432
  function isDir3(p) {
5202
5433
  try {
5203
5434
  return fs19.statSync(p).isDirectory();
@@ -5603,6 +5834,19 @@ function installSkills(opts) {
5603
5834
  };
5604
5835
  }
5605
5836
 
5837
+ // src/cli/utils/options.ts
5838
+ import { InvalidArgumentError } from "commander";
5839
+ function acceptChoices(option, accepted, advertised) {
5840
+ return option.argParser((value) => {
5841
+ if (!accepted.includes(value)) {
5842
+ throw new InvalidArgumentError(
5843
+ `Allowed choices are ${advertised.join(", ")}.`
5844
+ );
5845
+ }
5846
+ return value;
5847
+ });
5848
+ }
5849
+
5606
5850
  // src/cli/wallet.ts
5607
5851
  import * as fs21 from "fs";
5608
5852
  import * as path20 from "path";
@@ -6712,7 +6956,7 @@ var SUPPORTED_STORAGE_PROVIDERS = ["ipfs", "local"];
6712
6956
  var SUPPORTED_DESTINATIONS = ["self", "platform"];
6713
6957
  var RUNTIMES = {
6714
6958
  agentcore: { label: "AWS Bedrock AgentCore", hidden: false },
6715
- "azure-foundry": { label: "Azure AI Foundry", hidden: false }
6959
+ "azure-foundry": { label: "Azure AI Foundry", hidden: true }
6716
6960
  };
6717
6961
  var SUPPORTED_RUNTIMES = Object.keys(RUNTIMES);
6718
6962
  var APP_DIR = "app";
@@ -6789,7 +7033,7 @@ every intent to the right playbook via its references.
6789
7033
  lives in \`src/signing.ts\`.
6790
7034
  4. **The quote path is deterministic** (fixed list price, clamp + sign).
6791
7035
  Never put an LLM in the quote path.
6792
- 5. **Deploy with \`bag deploy --provider bnb|aws|azure\`.** Every deploy or
7036
+ 5. **Deploy with \`bag deploy --provider bnb|aws\`.** Every deploy or
6793
7037
  redeploy requires a visible provider choice. Studio runs its local business
6794
7038
  gates, then delegates cloud credentials, secrets, packaging and lifecycle
6795
7039
  calls to the pinned bnbagent-deploy CLI. Do not bypass this boundary with
@@ -6931,7 +7175,7 @@ function validateProjectName(basename16) {
6931
7175
  }
6932
7176
  if (safe !== basename16) {
6933
7177
  throw new Error(
6934
- `Project name '${basename16}' is not a valid AgentCore runtime name: the default runtime (AWS Bedrock AgentCore) requires ASCII letters and digits only ('-', '_', '.' and other characters are not allowed) \u2014 this is an AgentCore naming constraint, not a general bag restriction. Re-run with an alphanumeric name, e.g. \`bag init ${safe}\` \u2014 or, if this project is not for AgentCore, keep the original name on a runtime that allows it: \`bag init --runtime azure-foundry ${basename16}\`.`
7178
+ `Project name '${basename16}' is not a valid AgentCore runtime name: the default runtime (AWS Bedrock AgentCore) requires ASCII letters and digits only ('-', '_', '.' and other characters are not allowed) \u2014 this is an AgentCore naming constraint, not a general bag restriction. Re-run with an alphanumeric name, e.g. \`bag init ${safe}\`.`
6935
7179
  );
6936
7180
  }
6937
7181
  return basename16;
@@ -6944,21 +7188,15 @@ function registerInit(program) {
6944
7188
  "<name>",
6945
7189
  "Project directory name (created in cwd). Becomes the AgentCore runtime name: ASCII alphanumerics only, must start with a letter, \u226423 chars. Names with '-'/'_'/'.' are rejected (not auto-renamed)."
6946
7190
  ).addOption(
6947
- new Option2(
6948
- "--runtime <name>",
6949
- `Runtime / deploy target to scaffold for (choices: ${visibleRuntimes.join(", ")}); recorded in studio.toml [stack].runtime.`
6950
- ).default("agentcore").choices(SUPPORTED_RUNTIMES)
6951
- ).addOption(
6952
- new Option2(
6953
- "--azure-account <name>",
6954
- "azure-foundry only: Azure AI Foundry account (resource) name used by the delegated deploy."
7191
+ acceptChoices(
7192
+ new Option2(
7193
+ "--runtime <name>",
7194
+ `Runtime / deploy target to scaffold for (choices: ${visibleRuntimes.join(", ")}); recorded in studio.toml [stack].runtime.`
7195
+ ).default("agentcore"),
7196
+ SUPPORTED_RUNTIMES,
7197
+ visibleRuntimes
6955
7198
  )
6956
- ).addOption(
6957
- new Option2(
6958
- "--azure-project <name>",
6959
- "azure-foundry only: Azure AI Foundry project name used by the delegated deploy."
6960
- )
6961
- ).addOption(
7199
+ ).addOption(new Option2("--azure-account <name>").hideHelp()).addOption(new Option2("--azure-project <name>").hideHelp()).addOption(
6962
7200
  new Option2(
6963
7201
  "--destination <dest>",
6964
7202
  "Where the agent deploys; recorded in studio.toml [deploy].destination (default: platform while the trial campaign runs; self once it has ended, or when a non-agentcore --runtime or --network bsc-mainnet is passed). 'platform' is a 48h testnet trial on the BNB Chain managed platform (forces runtime=agentcore + bsc-testnet; a trial wallet key is transmitted to the operator \u2014 run `bag wallet new` for a throwaway). 'self' deploys to YOUR own cloud."
@@ -6983,6 +7221,12 @@ function registerInit(program) {
6983
7221
  "--rails <rails>",
6984
7222
  "Commerce rail(s) to scaffold: 8183 (default), b402, or both."
6985
7223
  ).choices(["8183", "b402", "both"])
7224
+ ).option(
7225
+ "--erc8183-price <base-units>",
7226
+ "ERC-8183 list price as a non-negative integer string in token base units. Use 0 for FREE. Default: 100000000000000000 (0.1 U)."
7227
+ ).option(
7228
+ "--b402-price <usd>",
7229
+ "B402/x402 per-request price as a non-negative decimal USD string. Use 0 for FREE passthrough (no B402 verify/settle). Default: 0.01."
6986
7230
  ).addOption(
6987
7231
  new Option2(
6988
7232
  "--llm-provider <provider>",
@@ -7099,6 +7343,45 @@ async function cmdInit(nameArg, opts) {
7099
7343
  opts.llmProviderSource,
7100
7344
  isTty
7101
7345
  );
7346
+ let rails = await resolveRails(opts.rails, isTty);
7347
+ if (hasX402Face(faces) && rails === "8183") {
7348
+ rails = "both";
7349
+ } else if (hasX402Face(faces) && rails !== "both") {
7350
+ rails = "b402";
7351
+ }
7352
+ if ((rails === "b402" || rails === "both") && !hasX402Face(faces)) {
7353
+ faces = normalizeProtocolFaces([...faces, "X402"]);
7354
+ }
7355
+ if (opts.erc8183Price !== void 0 && rails === "b402") {
7356
+ printErr(
7357
+ "error: --erc8183-price requires the 8183 rail; use --rails 8183 or --rails both."
7358
+ );
7359
+ return 2;
7360
+ }
7361
+ if (opts.b402Price !== void 0 && rails === "8183") {
7362
+ printErr(
7363
+ "error: --b402-price requires the b402 rail; use --rails b402 or --rails both."
7364
+ );
7365
+ return 2;
7366
+ }
7367
+ let erc8183Price = DEFAULT_ERC8183_PRICE;
7368
+ if (rails === "8183" || rails === "both") {
7369
+ try {
7370
+ erc8183Price = await resolveErc8183Price(opts.erc8183Price, isTty);
7371
+ } catch (exc) {
7372
+ printErr(`error: ${exc instanceof Error ? exc.message : exc}`);
7373
+ return 2;
7374
+ }
7375
+ }
7376
+ let b402Price = DEFAULT_B402_PRICE_USD;
7377
+ if (rails === "b402" || rails === "both") {
7378
+ try {
7379
+ b402Price = await resolveB402Price(opts.b402Price, isTty);
7380
+ } catch (exc) {
7381
+ printErr(`error: ${exc instanceof Error ? exc.message : exc}`);
7382
+ return 2;
7383
+ }
7384
+ }
7102
7385
  const enclosing = enclosingProjectRoot(path21.dirname(target));
7103
7386
  if (enclosing !== null) {
7104
7387
  printErr(
@@ -7196,16 +7479,7 @@ async function cmdInit(nameArg, opts) {
7196
7479
  }
7197
7480
  const budgetFlag = opts.enableAutoTopup ? "enable" : opts.autoTopup === false ? "disable" : null;
7198
7481
  const budgetChoice = walletKind2 === "altana" && llmProvider !== "pieverse-llm" && budgetFlag === null ? null : await resolveBudgetChoice(budgetFlag);
7199
- let rails = await resolveRails(opts.rails, isTty);
7200
- if (hasX402Face(faces) && rails === "8183") {
7201
- rails = "both";
7202
- } else if (hasX402Face(faces) && rails !== "both") {
7203
- rails = "b402";
7204
- }
7205
- if ((rails === "b402" || rails === "both") && !hasX402Face(faces)) {
7206
- faces = normalizeProtocolFaces([...faces, "X402"]);
7207
- }
7208
- if ((rails === "b402" || rails === "both") && !["evm-local", "twak"].includes(walletKind2)) {
7482
+ if ((rails === "b402" || rails === "both") && x402SellerPricingState({ price_usd: b402Price }).kind === "paid" && !["evm-local", "twak"].includes(walletKind2)) {
7209
7483
  printErr(
7210
7484
  `error: the b402 seller rail supports wallet.kind evm-local or twak only; '${walletKind2}' cannot act as the b402 payout wallet. Re-run with --rails 8183 or a supported --wallet-kind.`
7211
7485
  );
@@ -7220,6 +7494,8 @@ async function cmdInit(nameArg, opts) {
7220
7494
  runtime,
7221
7495
  faces,
7222
7496
  rails,
7497
+ erc8183Price,
7498
+ b402Price,
7223
7499
  budgetChoice,
7224
7500
  ides,
7225
7501
  storageProvider: opts.storageProvider,
@@ -7318,8 +7594,9 @@ async function cmdInit(nameArg, opts) {
7318
7594
  "info: X402-only publishes only the /x402 gateway face; the native AgentCore A2A protocol declaration is suppressed from discovery."
7319
7595
  );
7320
7596
  } else if (faces.length === 1 && hasX402Face(faces) && destination === "self") {
7597
+ const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
7321
7598
  printErr(
7322
- "warning: X402-only uses an A2A-native AgentCore process internally, but exposes no public A2A face; the seller stays dormant until the B402 credentials are filled, and a self-hosted AgentCore target needs your own HTTP front to reach /x402."
7599
+ free ? "warning: X402-only FREE mode uses an A2A-native AgentCore process internally, but exposes no public A2A face; a self-hosted AgentCore target still needs your own HTTP front to reach the anonymous /x402 passthrough." : "warning: X402-only uses an A2A-native AgentCore process internally, but exposes no public A2A face; the seller stays dormant until the B402 credentials are filled, and a self-hosted AgentCore target needs your own HTTP front to reach /x402."
7323
7600
  );
7324
7601
  }
7325
7602
  printConfigSummary(agentDir);
@@ -7379,9 +7656,10 @@ function derivePackaging(walletKind2, destination, platformArtifact2 = "zip", ru
7379
7656
  return "codezip";
7380
7657
  }
7381
7658
  function scaffold(target, name, o) {
7659
+ const packageManagerVersion = pnpmVersion();
7382
7660
  fs22.writeFileSync(
7383
7661
  path21.join(target, "package.json"),
7384
- renderWorkspacePackageJson(name)
7662
+ renderWorkspacePackageJson(name, packageManagerVersion)
7385
7663
  );
7386
7664
  fs22.writeFileSync(
7387
7665
  path21.join(target, "pnpm-workspace.yaml"),
@@ -7438,7 +7716,14 @@ function scaffold(target, name, o) {
7438
7716
  fs22.mkdirSync(path21.dirname(envLocal), { recursive: true });
7439
7717
  fs22.writeFileSync(
7440
7718
  envLocal,
7441
- renderAgentEnvLocal(o.provider, o.storageProvider, o.walletKind, o.rails)
7719
+ renderAgentEnvLocal(
7720
+ o.provider,
7721
+ o.storageProvider,
7722
+ o.walletKind,
7723
+ o.rails,
7724
+ o.b402Price,
7725
+ o.network
7726
+ )
7442
7727
  );
7443
7728
  fs22.writeFileSync(path21.join(agentRoot2, ".gitignore"), renderAgentGitignore());
7444
7729
  fs22.writeFileSync(
@@ -7458,7 +7743,7 @@ function scaffold(target, name, o) {
7458
7743
  const runtimeCtx = {
7459
7744
  PKG: AGENT_SRC,
7460
7745
  TWAK_CLI_VERSION,
7461
- PNPM_VERSION: pnpmVersion(),
7746
+ PNPM_VERSION: packageManagerVersion,
7462
7747
  DEPLOYMENT_TYPE: isContainer ? "container" : "direct_code_deploy",
7463
7748
  ENTRYPOINT: o.runtime === "azure-foundry" ? a2aEntry : `dist/${stem2}.js`,
7464
7749
  // Foundry Hosted Agents have a provider-level container contract that is
@@ -7547,11 +7832,12 @@ ${deployLine}
7547
7832
  In Claude Code / Cursor, type \`/bnbagent-studio\` \u2014 the skill drives every step.
7548
7833
  `;
7549
7834
  }
7550
- function renderWorkspacePackageJson(name) {
7835
+ function renderWorkspacePackageJson(name, packageManagerVersion) {
7551
7836
  return `${JSON.stringify(
7552
7837
  {
7553
7838
  name: `${name}-workspace`,
7554
- private: true
7839
+ private: true,
7840
+ packageManager: `pnpm@${packageManagerVersion}`
7555
7841
  // NOT a publishable package — the agent lives at app/agent (see
7556
7842
  // pnpm-workspace.yaml); this root only anchors the pnpm workspace.
7557
7843
  },
@@ -7755,14 +8041,14 @@ expected_recipient = ""
7755
8041
  allowed_hosts = ["llm.pieverse.io"]
7756
8042
  `;
7757
8043
  }
7758
- function renderX402SellerSection() {
8044
+ function renderX402SellerSection(priceUsd) {
7759
8045
  return `
7760
8046
  [payments.x402_seller]
7761
8047
  # Public seller rail mounted at /x402. Credentials stay in
7762
8048
  # <workspace>/.studio/.env.local and are issued per B402 environment.
7763
8049
  enabled = true
7764
- # Decimal USD string; "0" serves the work for free without a facilitator.
7765
- price_usd = "0.01"
8050
+ # Decimal USD string; "0" is explicit FREE passthrough and bypasses B402.
8051
+ price_usd = "${priceUsd}"
7766
8052
  # v1 supports United Stables (U) through EIP-3009 only.
7767
8053
  assets = ["U"]
7768
8054
  # Empty means the agent wallet receives payment.
@@ -7817,7 +8103,7 @@ function renderAgentStudioToml(name, o) {
7817
8103
  const model = o.model ?? PROVIDER_DEFAULT_MODEL[o.provider] ?? "";
7818
8104
  const pieverseSection = o.provider === "pieverse-llm" ? renderPieverseSections() : "";
7819
8105
  const x402Section = o.provider === "pieverse-llm" ? renderX402Section() : "";
7820
- const x402SellerSection = o.rails === "b402" || o.rails === "both" || hasX402Face(o.faces) ? renderX402SellerSection() : "";
8106
+ const x402SellerSection = o.rails === "b402" || o.rails === "both" || hasX402Face(o.faces) ? renderX402SellerSection(o.b402Price) : "";
7821
8107
  const budgetSection = renderBudgetSection(o.budgetChoice);
7822
8108
  const storageSection = renderAgentStorageSection(o.storageProvider);
7823
8109
  const azureSection = o.runtime === "azure-foundry" ? renderAzureSection(name, o.azureAccount, o.azureProject) : "";
@@ -7828,9 +8114,10 @@ function renderAgentStudioToml(name, o) {
7828
8114
  [payments.erc8183]
7829
8115
  # Read by the Agent: it quotes the FIXED \`price\`, CLAMPS it to [min,max],
7830
8116
  # freezes a short-TTL offer, and EIP-191 signs it \u2014 pricing is rule-based, the
7831
- # LLM never prices.
8117
+ # LLM never prices. price=0 is FREE and needs a zero-price-compatible
8118
+ # ERC-8183 stack selected with all three ERC8183_*_ADDRESS overrides.
7832
8119
  currency = "${currencyAddr}" # $U token address \u2014 prefilled from [network].default
7833
- price = "100000000000000000" # wei \u2014 your list price (0.1 U); what every quote charges
8120
+ price = "${o.erc8183Price}" # token base units; 0 = FREE (zero token escrow)
7834
8121
  min_price = "0" # wei \u2014 clamp floor
7835
8122
  max_price = "" # wei \u2014 clamp ceiling; SET before going live
7836
8123
  quote_ttl_seconds = 900 # seconds a signed quote stays valid; SDK caps at 900 (~15min work window)
@@ -7868,7 +8155,7 @@ ${pieverseSection}${erc8183Section}
7868
8155
  ${storageSection}
7869
8156
  ${x402Section}${x402SellerSection}${budgetSection}${azureSection}`;
7870
8157
  }
7871
- function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails) {
8158
+ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails, b402Price, network) {
7872
8159
  const keyEnv = PROVIDER_KEY_ENV[provider];
7873
8160
  const lines = [
7874
8161
  "# \u26A0\uFE0F SECRETS \u2014 do NOT paste this file into issue trackers, logs, or AI chats.",
@@ -7931,17 +8218,30 @@ function renderAgentEnvLocal(provider, storageProvider, walletKind2, rails) {
7931
8218
  );
7932
8219
  }
7933
8220
  if (rails === "b402" || rails === "both") {
8221
+ const free = x402SellerPricingState({ price_usd: b402Price }).kind === "free";
8222
+ const baseUrl = !free && network === "bsc-testnet" ? DEFAULT_B402_TESTNET_BASE_URL : "";
7934
8223
  lines.push(
7935
8224
  "",
7936
8225
  "# B402 merchant credentials (issued per environment after manual approval).",
7937
- "# All four absent = x402 rail dormant; partial = boot error naming the missing vars.",
7938
- "B402_BASE_URL=",
8226
+ free ? "# FREE price 0 does not use these credentials; leave them empty until switching to PAID." : "# PAID requires all four; all absent = dormant, partial = boot error.",
8227
+ `B402_BASE_URL=${baseUrl}`,
7939
8228
  "B402_CLIENT_ID=",
7940
8229
  "B402_ACCESS_TOKEN=",
7941
8230
  "B402_PRIVATE_KEY=",
7942
8231
  "# (alternative one-line PKCS#8 DER: B402_PRIVATE_KEY_B64=)"
7943
8232
  );
7944
8233
  }
8234
+ if (rails === "8183" || rails === "both") {
8235
+ lines.push(
8236
+ "",
8237
+ "# Optional ERC-8183 custom contract-stack override. Set all three",
8238
+ "# together; partial overrides can mix incompatible deployments.",
8239
+ "# Required for price=0 while canonical contracts reject zero funding.",
8240
+ "# ERC8183_COMMERCE_ADDRESS=",
8241
+ "# ERC8183_ROUTER_ADDRESS=",
8242
+ "# ERC8183_POLICY_ADDRESS="
8243
+ );
8244
+ }
7945
8245
  return `${lines.join("\n")}
7946
8246
  `;
7947
8247
  }
@@ -8183,6 +8483,69 @@ async function resolveRails(flagValue, isTty) {
8183
8483
  }
8184
8484
  return "8183";
8185
8485
  }
8486
+ var DEFAULT_ERC8183_PRICE = "100000000000000000";
8487
+ async function resolveErc8183Price(flagValue, isTty) {
8488
+ if (flagValue !== void 0) {
8489
+ const value = flagValue.trim();
8490
+ if (!/^\d+$/.test(value)) {
8491
+ throw new Error(
8492
+ `--erc8183-price must be a non-negative integer string in token base units; got ${JSON.stringify(flagValue)}.`
8493
+ );
8494
+ }
8495
+ return value;
8496
+ }
8497
+ if (!isTty) {
8498
+ return DEFAULT_ERC8183_PRICE;
8499
+ }
8500
+ const choice = (await promptUser(
8501
+ "ERC-8183 pricing [paid/free/custom, Enter = paid (0.1 U)]: "
8502
+ )).trim().toLowerCase();
8503
+ if (choice === "free") {
8504
+ return "0";
8505
+ }
8506
+ if (choice === "custom") {
8507
+ const custom = (await promptUser(
8508
+ "ERC-8183 list price in token base units (non-negative integer): "
8509
+ )).trim();
8510
+ if (!/^\d+$/.test(custom)) {
8511
+ throw new Error(
8512
+ `custom ERC-8183 price must be a non-negative integer string; got ${JSON.stringify(custom)}.`
8513
+ );
8514
+ }
8515
+ return custom;
8516
+ }
8517
+ if (choice !== "" && choice !== "paid") {
8518
+ printErr(`hint: unknown ERC-8183 pricing choice '${choice}' \u2014 using paid.`);
8519
+ }
8520
+ return DEFAULT_ERC8183_PRICE;
8521
+ }
8522
+ async function resolveB402Price(flagValue, isTty) {
8523
+ if (flagValue !== void 0) {
8524
+ return normalizeB402PriceUsd(flagValue);
8525
+ }
8526
+ if (!isTty) {
8527
+ return DEFAULT_B402_PRICE_USD;
8528
+ }
8529
+ const choice = (await promptUser(
8530
+ "B402/x402 pricing [paid/free/custom, Enter = paid ($0.01)]: "
8531
+ )).trim().toLowerCase();
8532
+ if (choice === "free") {
8533
+ return "0";
8534
+ }
8535
+ if (choice === "custom") {
8536
+ return normalizeB402PriceUsd(
8537
+ await promptUser(
8538
+ "B402/x402 per-request price in USD (non-negative decimal): "
8539
+ )
8540
+ );
8541
+ }
8542
+ if (choice !== "" && choice !== "paid") {
8543
+ printErr(
8544
+ `hint: unknown B402/x402 pricing choice '${choice}' \u2014 using paid.`
8545
+ );
8546
+ }
8547
+ return DEFAULT_B402_PRICE_USD;
8548
+ }
8186
8549
  async function promptPassword() {
8187
8550
  printOut("");
8188
8551
  printOut("Onboarding (TTY) \u2014 set a wallet password to finish in one go.");
@@ -8940,10 +9303,10 @@ function printNextSteps(name, provider, o = {}) {
8940
9303
  nextStep = 4;
8941
9304
  }
8942
9305
  printOut(
8943
- ` # ${nextStep}. set ERC-8183 pricing in studio.toml [payments.erc8183]`
9306
+ ` # ${nextStep}. review ERC-8183 pricing in studio.toml [payments.erc8183]`
8944
9307
  );
8945
9308
  printOut(
8946
- " # (currency = $U token; price = list price in wei; min/max_price clamp before sign)"
9309
+ " # (price was selected at init; `bag config set payments.erc8183.price 0` explicitly switches to FREE)"
8947
9310
  );
8948
9311
  nextStep += 1;
8949
9312
  printOut(` # ${nextStep}. verify + run the agent locally`);
@@ -9011,13 +9374,26 @@ function printConfigSummary(agentRoot2) {
9011
9374
  const shown = (v, unset = "<unset \u2014 fill before going live>") => v === void 0 || v === null || v === "" ? unset : String(v);
9012
9375
  const table = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
9013
9376
  const faces = stackFaces(table(agent.stack), table(agent.payments));
9377
+ const payments = table(agent.payments);
9378
+ const erc8183 = table(payments.erc8183);
9379
+ const x402Seller = table(payments.x402_seller);
9380
+ const hasErc8183 = Object.keys(erc8183).length > 0;
9381
+ const hasX402Seller = x402Seller.enabled === true;
9382
+ const x402Pricing = x402SellerPricingState(x402Seller);
9383
+ const isFreeX402 = hasX402Seller && x402Pricing.kind === "free";
9384
+ const onlyX402 = hasX402Seller && !hasErc8183;
9014
9385
  const network = shown(g(agent, "network", "default"), "?");
9015
9386
  const provider = shown(g(agent, "llm", "provider"), "?");
9016
9387
  const model = shown(g(agent, "llm", "model"), "?");
9017
9388
  const modelTag = isFreeModel(String(model)) ? "Free, $0/token" : "paid \u2014 fund + topup to use";
9018
9389
  const currency = g(agent, "payments", "erc8183", "currency");
9019
- const price = shown(g(agent, "payments", "erc8183", "price"), "0");
9390
+ const priceValue = g(agent, "payments", "erc8183", "price");
9391
+ const price = shown(priceValue);
9392
+ const minPrice = shown(g(agent, "payments", "erc8183", "min_price"), "0");
9020
9393
  const maxPrice = g(agent, "payments", "erc8183", "max_price");
9394
+ const pricingState = erc8183PricingState(erc8183);
9395
+ const isFreePrice = pricingState.kind === "free";
9396
+ const contractOverrides = erc8183ContractOverrideState();
9021
9397
  const storageKind2 = shown(g(agent, "storage", "kind"), "?");
9022
9398
  const storageDesc = storageKind2 === "ipfs" ? "IPFS (durable, public)" : "local disk (offline dev only)";
9023
9399
  const walletKind2 = shown(g(agent, "wallet", "kind"), "evm-local");
@@ -9030,16 +9406,41 @@ function printConfigSummary(agentRoot2) {
9030
9406
  printOut("Your agent's configuration (studio.toml, in plain language)");
9031
9407
  printOut(bar);
9032
9408
  printOut("");
9033
- printOut("Per task \u2014 how this seller earns $U:");
9034
9409
  printOut(
9035
- " buyer negotiates \u2192 the Agent (sole signer) QUOTES a signed price \u2192"
9036
- );
9037
- printOut(
9038
- " on payment the Agent FULFILLS (runs the LLM, stores the deliverable) \u2192"
9039
- );
9040
- printOut(
9041
- " after the dispute window, SETTLE releases the $U to your wallet."
9410
+ onlyX402 && isFreeX402 ? "Per request \u2014 how this FREE x402 seller delivers work:" : onlyX402 ? "Per request \u2014 how this x402 seller earns $U:" : isFreePrice ? "Per task \u2014 how this FREE seller delivers work:" : "Per task \u2014 how this seller earns $U:"
9042
9411
  );
9412
+ if (onlyX402 && isFreeX402) {
9413
+ printOut(
9414
+ " anonymous request reaches /x402 \u2192 B402 verify/settle is bypassed \u2192"
9415
+ );
9416
+ printOut(
9417
+ " the Agent runs the work and returns the result with no payment."
9418
+ );
9419
+ } else if (onlyX402) {
9420
+ printOut(
9421
+ " anonymous request receives a 402 challenge \u2192 B402 verifies and settles $U \u2192"
9422
+ );
9423
+ printOut(" the Agent runs the work and returns the paid result.");
9424
+ } else {
9425
+ printOut(
9426
+ " buyer negotiates \u2192 the Agent (sole signer) QUOTES a signed price \u2192"
9427
+ );
9428
+ if (isFreePrice) {
9429
+ printOut(
9430
+ " buyer funds 0 token units \u2192 the Agent FULFILLS (runs the LLM, stores the deliverable) \u2192"
9431
+ );
9432
+ printOut(
9433
+ " after the dispute window, SETTLE completes the job with no token payout."
9434
+ );
9435
+ } else {
9436
+ printOut(
9437
+ " on payment the Agent FULFILLS (runs the LLM, stores the deliverable) \u2192"
9438
+ );
9439
+ printOut(
9440
+ " after the dispute window, SETTLE releases the $U to your wallet."
9441
+ );
9442
+ }
9443
+ }
9043
9444
  printOut("");
9044
9445
  printOut("Agent (the value + the ONLY key-holder/signer)");
9045
9446
  printOut(` project : ${shown(g(agent, "project", "name"), "?")}`);
@@ -9086,19 +9487,35 @@ function printConfigSummary(agentRoot2) {
9086
9487
  const ar = autoRenew ? "on \u2014 tops the API key up from your Pieverse balance (no wallet spend)" : "off";
9087
9488
  printOut(` auto-alloc: ${ar}`);
9088
9489
  }
9089
- printOut(
9090
- ` pricing : list price ${weiToU(String(price))} per job; clamp ceiling ${shown(maxPrice)}`
9091
- );
9092
- printOut(
9093
- ` quote frozen ${shown(
9094
- g(agent, "payments", "erc8183", "quote_ttl_seconds"),
9095
- "?"
9096
- )}s; job timeout ${shown(
9097
- g(agent, "payments", "erc8183", "default_estimated_completion_seconds"),
9098
- "?"
9099
- )}s`
9100
- );
9101
- printOut(` currency ($U token): ${shown(currency)}`);
9490
+ if (hasErc8183) {
9491
+ if (isFreePrice) {
9492
+ printOut(
9493
+ ` pricing : FREE \u2014 ${weiToU(String(price))} per job; zero token escrow`
9494
+ );
9495
+ printOut(
9496
+ contractOverrides.mode === "custom" ? " contracts: custom stack selected (all three address overrides)" : " contracts: configure all three ERC8183_*_ADDRESS overrides before deploy"
9497
+ );
9498
+ } else {
9499
+ printOut(
9500
+ ` pricing : list price ${weiToU(String(price))} per job; clamp ceiling ${shown(maxPrice)}`
9501
+ );
9502
+ }
9503
+ printOut(
9504
+ ` quote frozen ${shown(
9505
+ g(agent, "payments", "erc8183", "quote_ttl_seconds"),
9506
+ "?"
9507
+ )}s; job timeout ${shown(
9508
+ g(agent, "payments", "erc8183", "default_estimated_completion_seconds"),
9509
+ "?"
9510
+ )}s`
9511
+ );
9512
+ printOut(` currency ($U token): ${shown(currency)}`);
9513
+ }
9514
+ if (hasX402Seller) {
9515
+ printOut(
9516
+ isFreeX402 ? " x402 : FREE \u2014 anonymous passthrough; B402 verify/settle is bypassed" : ` x402 : PAID \u2014 $${shown(x402Seller.price_usd, DEFAULT_B402_PRICE_USD)} per request through B402`
9517
+ );
9518
+ }
9102
9519
  printOut(
9103
9520
  ` storage : ${storageDesc} (options: local | ipfs \u2014 ipfs required to deploy)`
9104
9521
  );
@@ -9108,12 +9525,22 @@ function printConfigSummary(agentRoot2) {
9108
9525
  }
9109
9526
  printOut("");
9110
9527
  const todo = [];
9111
- if (!currency) {
9528
+ if (hasErc8183 && !currency) {
9112
9529
  todo.push("[payments.erc8183].currency \u2014 the $U token address");
9113
9530
  }
9114
- if (!maxPrice) {
9531
+ if (hasErc8183 && !maxPrice && !isFreePrice) {
9115
9532
  todo.push("[payments.erc8183].max_price \u2014 the price clamp ceiling");
9116
9533
  }
9534
+ if (hasErc8183 && isFreePrice && contractOverrides.mode !== "custom") {
9535
+ todo.push(
9536
+ "ERC8183_COMMERCE_ADDRESS / ROUTER_ADDRESS / POLICY_ADDRESS \u2014 select one zero-price-compatible custom contract stack"
9537
+ );
9538
+ }
9539
+ if (hasX402Seller && !isFreeX402) {
9540
+ todo.push(
9541
+ "B402_BASE_URL / CLIENT_ID / ACCESS_TOKEN / private key \u2014 required for PAID /x402"
9542
+ );
9543
+ }
9117
9544
  if (storageKind2 === "local") {
9118
9545
  todo.push(
9119
9546
  "[storage].kind \u2014 switch to 'ipfs' + set STORAGE_API_URL/STORAGE_API_KEY before deploy (local disk is NOT deployable)"
@@ -9392,8 +9819,13 @@ function runtimeEnvKeys(agentDir) {
9392
9819
  if (resolvable("RPC_URL")) {
9393
9820
  keys.push("RPC_URL");
9394
9821
  }
9822
+ if (commerceRails(cfg).erc8183) {
9823
+ for (const key of ERC8183_ADDRESS_OVERRIDE_KEYS) {
9824
+ if (resolvable(key)) keys.push(key);
9825
+ }
9826
+ }
9395
9827
  const runtime = String(tableOf7(cfg, "stack").runtime ?? "agentcore");
9396
- if (runtime === "agentcore" && commerceRails(cfg).x402) {
9828
+ if (runtime === "agentcore" && x402SellerUsesB402(cfg)) {
9397
9829
  for (const key of B402_RUNTIME_KEYS) {
9398
9830
  if (resolvable(key)) keys.push(key);
9399
9831
  }
@@ -9777,8 +10209,11 @@ Switch to Node \u226522 so it wins on PATH, then reopen the shell and retry. Ver
9777
10209
  );
9778
10210
  }
9779
10211
  if (hasX402Face(faces)) {
10212
+ const free = x402SellerIsFree(
10213
+ loadStudioToml10(path22.join(agentDir, "studio.toml"))
10214
+ );
9780
10215
  printOut(
9781
- `bag dev: x402 seller at http://localhost:${agentPort}/x402 (active when B402 credentials are configured)`
10216
+ free ? `bag dev: x402 seller at http://localhost:${agentPort}/x402 (FREE \u2014 anonymous passthrough; B402 bypassed)` : `bag dev: x402 seller at http://localhost:${agentPort}/x402 (PAID \u2014 active when B402 credentials are configured)`
9782
10217
  );
9783
10218
  }
9784
10219
  await launch();
@@ -9885,8 +10320,13 @@ function runtimeEnvKeys2(agentRoot2) {
9885
10320
  if (resolvable("RPC_URL")) {
9886
10321
  keys.push("RPC_URL");
9887
10322
  }
10323
+ if (commerceRails(cfg).erc8183) {
10324
+ for (const key of ERC8183_ADDRESS_OVERRIDE_KEYS) {
10325
+ if (resolvable(key)) keys.push(key);
10326
+ }
10327
+ }
9888
10328
  const runtime = String(tableOf2(cfg, "stack").runtime ?? "agentcore");
9889
- if (runtime === "agentcore" && commerceRails(cfg).x402) {
10329
+ if (runtime === "agentcore" && x402SellerUsesB402(cfg)) {
9890
10330
  for (const key of B402_RUNTIME_KEYS) {
9891
10331
  if (resolvable(key)) keys.push(key);
9892
10332
  }
@@ -10226,51 +10666,76 @@ async function checkPieverseKeyHash(root, _target) {
10226
10666
  }
10227
10667
  ];
10228
10668
  }
10229
- var MIN_PRICE_WEI = 1000000000n;
10230
- var MAX_UINT256 = 2n ** 256n - 1n;
10231
10669
  function erc8183RailChecks(cfg) {
10232
10670
  const c2 = tableOf2(tableOf2(cfg, "payments"), "erc8183");
10233
- const raw = (key, dflt) => {
10234
- const s = String(c2[key] ?? "").trim();
10235
- if (!s) {
10236
- return dflt;
10237
- }
10238
- try {
10239
- return BigInt(s);
10240
- } catch {
10241
- return dflt;
10242
- }
10243
- };
10244
10671
  const out = [];
10245
- const minPrice = raw("min_price", 0n);
10246
- const maxPrice = raw("max_price", MAX_UINT256);
10247
- const listPrice = raw("price", 0n);
10248
- const clampedToMax = listPrice < maxPrice ? listPrice : maxPrice;
10249
- const effective = minPrice > clampedToMax ? minPrice : clampedToMax;
10250
- if (effective < MIN_PRICE_WEI) {
10251
- const priceUnset = !String(c2.price ?? "").trim();
10252
- const clamped = listPrice >= MIN_PRICE_WEI && MIN_PRICE_WEI > effective;
10253
- let cause;
10254
- if (priceUnset) {
10255
- cause = "[payments.erc8183].price is unset, so every quote defaults to 0";
10256
- } else if (clamped) {
10257
- cause = `the list price (${listPrice} wei) is clamped to ${effective} wei by [payments.erc8183].max_price`;
10258
- } else {
10259
- cause = `the effective quote price is ${effective} wei`;
10260
- }
10672
+ const contracts = erc8183ContractOverrideState();
10673
+ if (contracts.mode === "partial") {
10674
+ out.push({
10675
+ level: Level.CRITICAL,
10676
+ name: "commerce_contract_override_incomplete",
10677
+ message: `The ERC-8183 contract override is incomplete. Set ${contracts.missing.join(", ")} so commerce, router, and policy come from one compatible contract stack.`,
10678
+ details: {
10679
+ present: contracts.present,
10680
+ missing: contracts.missing
10681
+ }
10682
+ });
10683
+ } else if (contracts.mode === "invalid") {
10684
+ out.push({
10685
+ level: Level.CRITICAL,
10686
+ name: "commerce_contract_override_invalid",
10687
+ message: `The following ERC-8183 contract overrides are not valid EVM addresses: ${contracts.invalid.join(", ")}.`,
10688
+ details: { invalid: contracts.invalid }
10689
+ });
10690
+ }
10691
+ const pricing = erc8183PricingState(c2);
10692
+ if (pricing.kind === "unset") {
10693
+ out.push({
10694
+ level: Level.CRITICAL,
10695
+ name: "commerce_price_unset",
10696
+ message: "[payments.erc8183].price is unset. Set it explicitly to 0 for a free job or to a positive integer amount in token base units.",
10697
+ fixCmd: "bag config set payments.erc8183.price 100000000000000000",
10698
+ details: {}
10699
+ });
10700
+ } else if (pricing.kind === "invalid") {
10261
10701
  out.push({
10262
10702
  level: Level.CRITICAL,
10263
- name: "commerce_price_below_min",
10264
- message: `${cause}; the agent would quote for free / below the contract minimum of ${MIN_PRICE_WEI} wei (1 gwei). v1 rejects price-0 / sub-gwei tasks, so no job can ever fund. Set [payments.erc8183].price (and check max_price) in studio.toml.`,
10265
- fixCmd: "bag config set payments.erc8183.price 1000000000",
10703
+ name: "commerce_price_invalid",
10704
+ message: `[payments.erc8183].${pricing.field} must be a non-negative uint256 decimal string; got ${JSON.stringify(pricing.value)}.`,
10705
+ fixCmd: "bag config set payments.erc8183.price 100000000000000000",
10266
10706
  details: {
10267
- effective_wei: String(effective),
10268
- list_price_wei: String(listPrice),
10269
- min_price_wei: String(minPrice),
10270
- max_price_wei: String(maxPrice),
10271
- min_required_wei: String(MIN_PRICE_WEI)
10707
+ configured_field: pricing.field,
10708
+ configured_value: pricing.value
10272
10709
  }
10273
10710
  });
10711
+ } else if (pricing.kind === "clamped_to_zero") {
10712
+ out.push({
10713
+ level: Level.CRITICAL,
10714
+ name: "commerce_price_clamped_to_zero",
10715
+ message: `The list price (${pricing.listPrice} token base units) is clamped to 0 by [payments.erc8183].max_price. Set price explicitly to 0 to opt into free jobs, or fix the clamp.`,
10716
+ details: {
10717
+ effective_wei: "0",
10718
+ list_price_wei: String(pricing.listPrice),
10719
+ min_price_wei: String(pricing.minPrice),
10720
+ max_price_wei: String(pricing.maxPrice)
10721
+ }
10722
+ });
10723
+ } else if (pricing.kind === "free") {
10724
+ if (contracts.mode === "canonical") {
10725
+ out.push({
10726
+ level: Level.CRITICAL,
10727
+ name: "commerce_zero_price_contract_unsupported",
10728
+ message: "ERC-8183 pricing is FREE (zero token escrow), but the canonical contract stack rejects zero funding. Set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together to a zero-price-compatible custom stack.",
10729
+ details: { effective_wei: "0", contract_profile: "canonical" }
10730
+ });
10731
+ } else if (contracts.mode === "custom") {
10732
+ out.push({
10733
+ level: Level.INFO,
10734
+ name: "commerce_zero_price_enabled",
10735
+ message: "ERC-8183 pricing is FREE \u2014 buyers fund 0 token units with zero token escrow; a complete custom contract stack is selected.",
10736
+ details: { effective_wei: "0", contract_profile: "custom" }
10737
+ });
10738
+ }
10274
10739
  }
10275
10740
  if (String(c2.currency ?? "").trim() === "") {
10276
10741
  out.push({
@@ -10306,15 +10771,24 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10306
10771
  const rails = commerceRails(cfg);
10307
10772
  const credentials = b402Credentials(agentRoot2);
10308
10773
  const out = [];
10774
+ const pricing = x402SellerPricingState(seller);
10775
+ const free = rails.x402 && pricing.kind === "free";
10776
+ const paid = rails.x402 && pricing.kind === "paid";
10309
10777
  if (sellerPresent) {
10310
- const price = seller.price_usd === void 0 ? "0.01" : typeof seller.price_usd === "string" ? seller.price_usd : "";
10311
- if (!/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(price)) {
10778
+ if (pricing.kind === "invalid") {
10312
10779
  out.push({
10313
10780
  level: Level.CRITICAL,
10314
10781
  name: "x402_price_invalid",
10315
10782
  message: '[payments.x402_seller].price_usd must be a non-negative decimal string; "0" is valid and enables the free passthrough.',
10316
10783
  details: {}
10317
10784
  });
10785
+ } else if (free) {
10786
+ out.push({
10787
+ level: Level.INFO,
10788
+ name: "x402_zero_price_enabled",
10789
+ message: "The x402 seller is explicitly FREE. /x402 is anonymous, and B402 verify/settle, token payment, and settlement audit are bypassed.",
10790
+ details: { price_usd: pricing.priceUsd, facilitator_bypassed: true }
10791
+ });
10318
10792
  }
10319
10793
  const assets = seller.assets ?? ["U"];
10320
10794
  if (!Array.isArray(assets) || assets.length !== 1 || assets[0] !== "U") {
@@ -10336,7 +10810,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10336
10810
  }
10337
10811
  }
10338
10812
  const walletKind2 = String(tableOf2(cfg, "wallet").kind ?? "evm-local");
10339
- if (rails.x402 && !["evm-local", "twak"].includes(walletKind2)) {
10813
+ if (paid && !["evm-local", "twak"].includes(walletKind2)) {
10340
10814
  out.push({
10341
10815
  level: Level.CRITICAL,
10342
10816
  name: "x402_wallet_kind_unsupported",
@@ -10344,7 +10818,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10344
10818
  details: { walletKind: walletKind2 }
10345
10819
  });
10346
10820
  }
10347
- if (credentials.any && !credentials.complete) {
10821
+ if (paid && credentials.any && !credentials.complete) {
10348
10822
  out.push({
10349
10823
  level: Level.CRITICAL,
10350
10824
  name: "x402_credentials_partial",
@@ -10352,7 +10826,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10352
10826
  details: { present: credentials.presentKeys }
10353
10827
  });
10354
10828
  }
10355
- if (credentials.bothPrivateKeyFormats) {
10829
+ if (paid && credentials.bothPrivateKeyFormats) {
10356
10830
  out.push({
10357
10831
  level: Level.WARNING,
10358
10832
  name: "x402_private_key_ambiguous",
@@ -10360,7 +10834,7 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10360
10834
  details: {}
10361
10835
  });
10362
10836
  }
10363
- if (rails.x402 && !credentials.any) {
10837
+ if (paid && !credentials.any) {
10364
10838
  out.push({
10365
10839
  level: rails.erc8183 ? Level.WARNING : Level.CRITICAL,
10366
10840
  name: "x402_credentials_missing",
@@ -10368,19 +10842,19 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10368
10842
  details: {}
10369
10843
  });
10370
10844
  }
10371
- if (!rails.x402 && credentials.any) {
10845
+ if ((!rails.x402 || free) && credentials.any) {
10372
10846
  out.push({
10373
10847
  level: Level.WARNING,
10374
10848
  name: "x402_credentials_unused",
10375
- message: "B402 credentials are configured but the x402 seller rail is absent or disabled; they will not be synchronized.",
10849
+ message: free ? "B402 credentials are configured but FREE x402 bypasses the facilitator; they will not be synchronized." : "B402 credentials are configured but the x402 seller rail is absent or disabled; they will not be synchronized.",
10376
10850
  details: { present: credentials.presentKeys }
10377
10851
  });
10378
10852
  }
10379
- if (credentials.baseUrl !== null) {
10853
+ if (paid && credentials.baseUrl !== null) {
10380
10854
  const network = String(
10381
10855
  tableOf2(cfg, "network").default ?? "bsc-mainnet"
10382
10856
  ).toLowerCase();
10383
- const testUrl = /sandbox|test/.test(credentials.baseUrl.toLowerCase());
10857
+ const testUrl = isB402TestnetBaseUrl(credentials.baseUrl);
10384
10858
  const testNetwork = network.includes("testnet");
10385
10859
  if (testUrl !== testNetwork) {
10386
10860
  out.push({
@@ -10393,12 +10867,12 @@ function x402SellerRailChecks(agentRoot2, cfg, target) {
10393
10867
  }
10394
10868
  const destination = String(tableOf2(cfg, "deploy").destination ?? "self");
10395
10869
  const stackRuntime2 = String(tableOf2(cfg, "stack").runtime ?? "agentcore");
10396
- if (rails.x402 && credentials.complete && destination !== "platform" && (target !== "agentcore" || stackRuntime2 !== "agentcore")) {
10870
+ if (rails.x402 && (free || credentials.complete) && destination !== "platform" && (target !== "agentcore" || stackRuntime2 !== "agentcore")) {
10397
10871
  const blocking = target !== "agentcore" ? target : stackRuntime2;
10398
10872
  out.push({
10399
10873
  level: Level.WARNING,
10400
10874
  name: "x402_forced_dormant_runtime",
10401
- message: `x402 seller credentials are complete, but the ${blocking} runtime has no x402 path; this target is forced dormant. Deploy to AgentCore (managed platform or self-hosted) to activate the rail.`,
10875
+ message: `x402 pricing is ready, but the ${blocking} runtime has no x402 path; this target is forced dormant. Deploy to AgentCore (managed platform or self-hosted) to activate the rail.`,
10402
10876
  details: { destination, target, stackRuntime: stackRuntime2 }
10403
10877
  });
10404
10878
  }
@@ -10613,6 +11087,7 @@ var allChecks = [
10613
11087
  // gates on the LEVEL, not on which module produced it.
10614
11088
  checkTwakRequiresContainer,
10615
11089
  checkTwakCliPresent,
11090
+ checkTwakCustomContractsUnsupported,
10616
11091
  checkTwakWalletExists,
10617
11092
  checkTwakPasswordEnvSet,
10618
11093
  checkTwakCredentialsAvailable,
@@ -11755,14 +12230,15 @@ async function checkWalletTbnbBalanceSufficient(root, _target) {
11755
12230
  return [];
11756
12231
  }
11757
12232
  const isTwak2 = tableOf2(data, "wallet").kind === "twak";
11758
- if (isTwak2 && netName === "bsc-testnet") {
12233
+ const customContracts = isTwak2 && twakCustomContractOverrides(netName).length > 0;
12234
+ if (isTwak2 && netName === "bsc-testnet" && !customContracts) {
11759
12235
  return [];
11760
12236
  }
11761
12237
  const bal = await readNativeBalanceRaw(address, netName);
11762
12238
  if (bal >= TBNB_MIN_WEI) {
11763
12239
  return [];
11764
12240
  }
11765
- const gasNote = isTwak2 ? " (twak: x402 topups are gasless and ERC-8004 registry writes are gas-sponsored, but mainnet ERC-8183 fund/settle self-pay gas)" : "";
12241
+ const gasNote = isTwak2 ? netName === "bsc-testnet" ? " (twak: custom contract sponsorship depends on the paymaster policy; keep fallback tBNB)" : " (twak: x402 topups are gasless and ERC-8004 registry writes are gas-sponsored, but mainnet ERC-8183 fund/settle self-pay gas)" : "";
11766
12242
  return [
11767
12243
  {
11768
12244
  level: Level.WARNING,
@@ -12243,6 +12719,7 @@ async function runPrepare(opts = {}) {
12243
12719
  checkLocalKeystoreExists,
12244
12720
  checkWalletPasswordEnvSet,
12245
12721
  checkLlmProviderKeySet,
12722
+ checkCommerceReady,
12246
12723
  ...azureFoundryChecks
12247
12724
  ],
12248
12725
  root,
@@ -12294,6 +12771,21 @@ var PROVIDER_LABELS = {
12294
12771
  aws: "AWS AgentCore",
12295
12772
  azure: "Azure Foundry"
12296
12773
  };
12774
+ var PROVIDER_MENU_ORDER = [
12775
+ "bnb",
12776
+ "aws",
12777
+ "azure"
12778
+ ];
12779
+ var PROVIDER_SHORT_LABELS = {
12780
+ bnb: "BNB",
12781
+ aws: "AWS",
12782
+ azure: "Azure"
12783
+ };
12784
+ function providerDigit(provider) {
12785
+ return PROVIDER_MENU_ORDER.indexOf(provider) + 1;
12786
+ }
12787
+ var ADVERTISED_PROVIDERS = ["bnb", "aws"];
12788
+ var ADVERTISED_PROVIDERS_HELP = ADVERTISED_PROVIDERS.join("|");
12297
12789
  function tableOf10(data, key) {
12298
12790
  const value = data[key];
12299
12791
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -12386,7 +12878,7 @@ function unavailableProvidersForProject(root) {
12386
12878
  return {};
12387
12879
  }
12388
12880
  if (runtime === "azure-foundry") {
12389
- const reason = "this project uses the Azure Foundry container adapter; scaffold an agentcore project before selecting BNB or AWS";
12881
+ const reason = "this project uses a different container adapter; scaffold an agentcore project before selecting BNB or AWS";
12390
12882
  return {
12391
12883
  bnb: reason,
12392
12884
  aws: reason,
@@ -12417,9 +12909,12 @@ function unavailableReason(opts, provider) {
12417
12909
  }
12418
12910
  return opts.unavailable?.[provider] ?? null;
12419
12911
  }
12912
+ function providerPromptHint() {
12913
+ return PROVIDER_MENU_ORDER.filter((p) => ADVERTISED_PROVIDERS.includes(p)).map((p) => `${providerDigit(p)}=${PROVIDER_SHORT_LABELS[p]}`).join(", ");
12914
+ }
12420
12915
  async function promptProvider(opts) {
12421
12916
  for (; ; ) {
12422
- const answer = (await opts.prompt("Provider [1=BNB, 2=AWS, 3=Azure]: ")).trim().toLowerCase();
12917
+ const answer = (await opts.prompt(`Provider [${providerPromptHint()}]: `)).trim().toLowerCase();
12423
12918
  const provider = answer === "1" || answer === "bnb" ? "bnb" : answer === "2" || answer === "aws" ? "aws" : answer === "3" || answer === "azure" ? "azure" : null;
12424
12919
  if (provider === null) {
12425
12920
  if (answer === "" || answer === "q" || answer === "cancel") {
@@ -13315,6 +13810,8 @@ function printAccessNextSteps(root, _invokeUrl) {
13315
13810
  const agentRoot2 = findSubProjectRoot15("agent", root) ?? root;
13316
13811
  const access = buyerAccess(agentRoot2);
13317
13812
  const x402Only = access.faces.length === 1 && hasX402Face(access.faces);
13813
+ const x402Free = x402SellerIsFree(loadAgentCfg(agentRoot2));
13814
+ const x402RouteLabel = x402Free ? "anonymous FREE route" : "anonymous paid route";
13318
13815
  emit("\nAccess your agent \u2014");
13319
13816
  if (access.agentId) {
13320
13817
  if (!x402Only) {
@@ -13332,7 +13829,7 @@ function printAccessNextSteps(root, _invokeUrl) {
13332
13829
  );
13333
13830
  }
13334
13831
  if (hasX402Face(access.faces)) {
13335
- emit(` x402 endpoint: ${access.x402Url} (anonymous paid route)`);
13832
+ emit(` x402 endpoint: ${access.x402Url} (${x402RouteLabel})`);
13336
13833
  }
13337
13834
  if (!x402Only) {
13338
13835
  emit(
@@ -13357,7 +13854,7 @@ function printAccessNextSteps(root, _invokeUrl) {
13357
13854
  );
13358
13855
  }
13359
13856
  if (hasX402Face(access.faces)) {
13360
- emit(` x402 endpoint: ${rtTmpl}/x402 (anonymous paid route)`);
13857
+ emit(` x402 endpoint: ${rtTmpl}/x402 (${x402RouteLabel})`);
13361
13858
  }
13362
13859
  if (x402Only) {
13363
13860
  emit(" protocol discovery is suppressed for this X402-only deployment.");
@@ -13388,7 +13885,9 @@ function printAgentClientPrompt(root, invokeUrl, status) {
13388
13885
  const tokenSteps = ` 1) run \`bag platform invoke-client new\` -> client_id + client_secret (secret shown ONCE).
13389
13886
  2) POST client_credentials to ${tokenUrl} with scope \`${scope}\` -> access_token.
13390
13887
  3) send \`Authorization: Bearer <token>\`.`;
13391
- const rails = commerceRails(loadAgentCfg(agentRoot2));
13888
+ const agentCfg = loadAgentCfg(agentRoot2);
13889
+ const rails = commerceRails(agentCfg);
13890
+ const x402Free = x402SellerIsFree(agentCfg);
13392
13891
  const x402Only = faces.length === 1 && hasX402Face(faces) || rails.x402 && !rails.erc8183;
13393
13892
  const x402Summary = x402DeploySummary(agentRoot2, "platform", access.x402Url);
13394
13893
  const endpoints = [];
@@ -13406,7 +13905,7 @@ function printAgentClientPrompt(root, invokeUrl, status) {
13406
13905
  }
13407
13906
  if (x402Only) {
13408
13907
  endpoints.push(
13409
- ` [X402] Anonymous paid endpoint: ${access.x402Url ?? `${rt}/x402`}
13908
+ ` [X402] Anonymous ${x402Free ? "FREE" : "paid"} endpoint: ${access.x402Url ?? `${rt}/x402`}
13410
13909
  Protocol discovery is suppressed.`
13411
13910
  );
13412
13911
  }
@@ -13454,7 +13953,7 @@ function printAgentClientPrompt(root, invokeUrl, status) {
13454
13953
  ] : [],
13455
13954
  "",
13456
13955
  "YOUR TASK",
13457
- x402Only ? "Follow the X402 SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint." : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
13956
+ x402Only ? x402Free ? "Call the anonymous FREE /x402 endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint." : "Follow the X402 SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint." : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
13458
13957
  CLIENT_PROMPT_RULE
13459
13958
  ];
13460
13959
  const text2 = lines.join("\n");
@@ -13641,9 +14140,11 @@ function isFile14(p) {
13641
14140
 
13642
14141
  // src/cli/_runtime/base.ts
13643
14142
  var DEPLOY_RUNTIME_TARGETS = ["agentcore", "azure-foundry"];
14143
+ var ADVERTISED_DEPLOY_RUNTIME_TARGETS = ["agentcore"];
13644
14144
 
13645
14145
  // src/cli/deploy.ts
13646
14146
  var TARGET_CHOICES = [...DEPLOY_RUNTIME_TARGETS];
14147
+ var ADVERTISED_TARGET_CHOICES = [...ADVERTISED_DEPLOY_RUNTIME_TARGETS];
13647
14148
  function isFile15(p) {
13648
14149
  try {
13649
14150
  return fs37.statSync(p).isFile();
@@ -13683,17 +14184,33 @@ function loadAgentCfg2(root) {
13683
14184
  function registerDeploy(program) {
13684
14185
  program.enablePositionalOptions();
13685
14186
  const p = program.command("deploy").description(
13686
- "Deploy the agent after explicitly selecting BNB, AWS, or Azure; lifecycle subcommands inspect recorded deployments."
13687
- );
13688
- const runtimeOption = () => new Option4(
13689
- "--runtime <name>",
13690
- "Deploy runtime to validate against (default: studio.toml [stack].runtime, else agentcore)."
13691
- ).choices(TARGET_CHOICES);
13692
- const targetAlias = () => new Option4("--target <name>", "Deprecated alias of --runtime.").choices(TARGET_CHOICES).hideHelp();
13693
- const providerOption = () => new Option4(
13694
- "--provider <provider>",
13695
- "Provider: bnb, aws, or azure. Required for non-interactive deploy; lifecycle commands need it only when multiple records exist."
13696
- ).choices(["bnb", "aws", "azure"]);
14187
+ "Deploy the agent after explicitly selecting BNB or AWS; lifecycle subcommands inspect recorded deployments."
14188
+ );
14189
+ const runtimeOption = () => acceptChoices(
14190
+ new Option4(
14191
+ "--runtime <name>",
14192
+ "Deploy runtime to validate against (default: studio.toml [stack].runtime, else agentcore)."
14193
+ ),
14194
+ TARGET_CHOICES,
14195
+ ADVERTISED_TARGET_CHOICES
14196
+ );
14197
+ const targetAlias = () => acceptChoices(
14198
+ new Option4(
14199
+ "--target <name>",
14200
+ "Deprecated alias of --runtime."
14201
+ ).hideHelp(),
14202
+ TARGET_CHOICES,
14203
+ ADVERTISED_TARGET_CHOICES
14204
+ );
14205
+ const providerOption = () => acceptChoices(
14206
+ new Option4(
14207
+ "--provider <provider>",
14208
+ "Provider: bnb or aws. Required for non-interactive deploy; lifecycle commands need it only when multiple records exist."
14209
+ ),
14210
+ // Unadvertised providers stay parseable (see ADVERTISED_PROVIDERS).
14211
+ PROVIDER_MENU_ORDER,
14212
+ ADVERTISED_PROVIDERS
14213
+ );
13697
14214
  p.addOption(providerOption()).option("--project-root <path>", "Override project root.").option("--skip-prepare", "Skip non-storage local readiness checks.").option(
13698
14215
  "--force-deploy-broken-storage",
13699
14216
  "DANGEROUS: bypass fatal deliverable-storage checks."
@@ -13702,7 +14219,7 @@ function registerDeploy(program) {
13702
14219
  ).option("--accept-risk", "Accept provider risk/terms disclosures.").option("--yes", "Confirm the selected deployment plan non-interactively.").option(
13703
14220
  "--allow-multiple",
13704
14221
  "Allow creating a deployment while another provider remains active."
13705
- ).option("--skip-smoke", "Skip Azure's delegated post-deploy smoke check.").action(act((opts) => cmdDeploy(opts, [])));
14222
+ ).option("--skip-smoke", "Skip the delegated post-deploy smoke check.").action(act((opts) => cmdDeploy(opts, [])));
13706
14223
  p.command("prepare").description("Run the deploy-readiness sweep on this project.").addOption(runtimeOption()).addOption(targetAlias()).option("--project-root <path>", "Override project root.").option("--json", "Emit JSON instead of a table.").option(
13707
14224
  "--ignore-warnings",
13708
14225
  "Exit 0 even if WARNING-level checks fire (still fails on BLOCKED/CRITICAL)."
@@ -13741,7 +14258,7 @@ function registerDeploy(program) {
13741
14258
  "Allow creating a deployment while another provider remains active."
13742
14259
  ).option(
13743
14260
  "--skip-smoke",
13744
- "(azure-foundry) Skip the post-deploy smoke check (omit `--smoke` from the delegated bnbagent-deploy invocation)."
14261
+ "Skip the post-deploy smoke check (omit `--smoke` from the delegated bnbagent-deploy invocation)."
13745
14262
  ).argument(
13746
14263
  "[agentcoreArgs...]",
13747
14264
  "Extra args passed straight through to `bnbagent-deploy deploy` (put after `--`)."
@@ -13799,7 +14316,7 @@ function registerDeploy(program) {
13799
14316
  (opts) => cmdDestroy(opts)
13800
14317
  )
13801
14318
  );
13802
- p.command("logs").description("Show the selected deployment's logs via bnbagent-deploy.").option("--project-root <path>", "Override project root.").addOption(providerOption()).option("--since <dur>", "Lower bound, e.g. 5m/2h/1d (default 10m).", "10m").option("--follow", "Stream new lines (poll ~3s).").option("--limit <n>", "Number of recent lines (Azure; default 50).").option("--session <id>", "Azure container session id.").addOption(
14319
+ p.command("logs").description("Show the selected deployment's logs via bnbagent-deploy.").option("--project-root <path>", "Override project root.").addOption(providerOption()).option("--since <dur>", "Lower bound, e.g. 5m/2h/1d (default 10m).", "10m").option("--follow", "Stream new lines (poll ~3s).").option("--limit <n>", "Number of recent lines (default 50).").option("--session <id>", "Container session id.").addOption(
13803
14320
  new Option4(
13804
14321
  "--job <id>",
13805
14322
  "Deprecated Studio direct-CloudWatch filter."
@@ -14251,14 +14768,18 @@ function renderProviderSelection(deployments, trial, unavailable) {
14251
14768
  }
14252
14769
  const remaining = formatTrialRemaining(trial.remainingSeconds);
14253
14770
  const bnbDetail = unavailable.bnb ? `unavailable \u2014 ${unavailable.bnb}` : trial.state === "expired" ? `unavailable \u2014 trial expired${trial.expiresAt ? ` ${trial.expiresAt}` : ""}` : trial.state === "active" ? `active${remaining ? ` \u2014 ${remaining} remaining` : ""}${trial.expiresAt ? ` \xB7 expires ${trial.expiresAt}` : ""}` : trial.state === "available" ? "available \u2014 free 48h testnet; starts on first successful deploy" : "login required to check trial eligibility";
14771
+ const detail = {
14772
+ bnb: bnbDetail,
14773
+ aws: unavailable.aws ? `unavailable \u2014 ${unavailable.aws}` : "deploy to your AWS account",
14774
+ azure: unavailable.azure ? `unavailable \u2014 ${unavailable.azure}` : "deploy to your Azure account"
14775
+ };
14254
14776
  printOut("Deployment providers");
14255
- printOut(` 1. BNB Chain Trial ${bnbDetail}`);
14256
- printOut(
14257
- ` 2. AWS AgentCore ${unavailable.aws ? `unavailable \u2014 ${unavailable.aws}` : "deploy to your AWS account"}`
14258
- );
14259
- printOut(
14260
- ` 3. Azure Foundry ${unavailable.azure ? `unavailable \u2014 ${unavailable.azure}` : "deploy to your Azure account"}`
14261
- );
14777
+ for (const provider of PROVIDER_MENU_ORDER) {
14778
+ if (!ADVERTISED_PROVIDERS.includes(provider)) continue;
14779
+ printOut(
14780
+ ` ${providerDigit(provider)}. ${PROVIDER_LABELS[provider].padEnd(18)}${detail[provider]}`
14781
+ );
14782
+ }
14262
14783
  }
14263
14784
  async function cmdDeploy(opts, deployArgs) {
14264
14785
  const rc = applyProjectRoot(opts.projectRoot);
@@ -14283,7 +14804,7 @@ async function cmdDeploy(opts, deployArgs) {
14283
14804
  });
14284
14805
  if (selection.kind === "selection_required") {
14285
14806
  printErr(
14286
- "error: deployment provider must be selected explicitly in a non-interactive terminal; pass --provider bnb|aws|azure --yes"
14807
+ `error: deployment provider must be selected explicitly in a non-interactive terminal; pass --provider ${ADVERTISED_PROVIDERS_HELP} --yes`
14287
14808
  );
14288
14809
  return selection.exitCode;
14289
14810
  }
@@ -14706,7 +15227,7 @@ async function printAccessNextSteps2(root, runtime, opts = {}) {
14706
15227
  return;
14707
15228
  }
14708
15229
  printOut(
14709
- " Azure Foundry buyer access is in Preview \u2014 see the bnbagent-studio-use-azure-foundry.md reference (skills/references/ in the bnbagent-studio repo) and docs/guides/foundry-a2a-access.md. The per-agent A2A card URL and OAuth scope are confirmed only after a live deploy."
15230
+ " Azure Foundry buyer access is in Preview \u2014 see docs/guides/foundry-a2a-access.md in the bnbagent-studio repo. The per-agent A2A card URL and OAuth scope are confirmed only after a live deploy."
14710
15231
  );
14711
15232
  }
14712
15233
  var CLIENT_PROMPT_RULE2 = "\u2500".repeat(62);
@@ -14731,7 +15252,9 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
14731
15252
  const tokenSteps = ` 1) token endpoint ${tokenUrl} (scope \`${scope}\`); ${credentialStep}.
14732
15253
  2) POST client_credentials -> access_token.
14733
15254
  3) send \`Authorization: Bearer <token>\` AND header \`X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: <stable id >= 33 chars>\`.`;
14734
- const rails = commerceRails(loadAgentCfg2(agentRoot2) ?? {});
15255
+ const agentCfg = loadAgentCfg2(agentRoot2) ?? {};
15256
+ const rails = commerceRails(agentCfg);
15257
+ const x402Free = x402SellerIsFree(agentCfg);
14735
15258
  const x402Only = rails.x402 && !rails.erc8183;
14736
15259
  const x402Summary = x402DeploySummary(agentRoot2, destination, null);
14737
15260
  const faces = agentFacesOf(agentRoot2);
@@ -14784,7 +15307,7 @@ async function printAgentClientPrompt2(root, destination, opts = {}) {
14784
15307
  ] : [],
14785
15308
  "",
14786
15309
  "YOUR TASK",
14787
- x402Only ? "Follow the X402 SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint." : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
15310
+ x402Only ? x402Free ? "Call the anonymous FREE /x402 endpoint directly. Do not create a payment or call the OAuth2 ERC-8183 endpoint." : "Follow the X402 SELLER activation or anonymous paid-request instructions above. Do not call the OAuth2 ERC-8183 endpoint." : "Write and run a client that (1) obtains a token, (2) calls negotiate with a sample task and prints the signed quote, (3) optionally funds a job on-chain (step 2 of the flow above) and calls notify_funded with the resulting job_id. Use the exact endpoint, headers, and message shapes above. If you don't have client_id/client_secret, ask me for them.",
14788
15311
  CLIENT_PROMPT_RULE2
14789
15312
  ];
14790
15313
  const text2 = lines.join("\n");
@@ -14978,7 +15501,9 @@ async function cmdStatus2(opts) {
14978
15501
  printOut(JSON.stringify({ deployments: [] }, null, 2));
14979
15502
  } else {
14980
15503
  printOut(`No recorded deployments${detail}.`);
14981
- printOut("Start one with `bag deploy --provider bnb|aws|azure`.");
15504
+ printOut(
15505
+ `Start one with \`bag deploy --provider ${ADVERTISED_PROVIDERS_HELP}\`.`
15506
+ );
14982
15507
  }
14983
15508
  return opts.provider ? 2 : 0;
14984
15509
  }
@@ -15135,13 +15660,13 @@ async function selectRecordedForLifecycle(root, requested, operation) {
15135
15660
  if (deployments.length === 1 && soleDeployment) return soleDeployment;
15136
15661
  if (deployments.length === 0) {
15137
15662
  printErr(
15138
- "error: no deployment is recorded; run `bag deploy --provider bnb|aws|azure` first"
15663
+ `error: no deployment is recorded; run \`bag deploy --provider ${ADVERTISED_PROVIDERS_HELP}\` first`
15139
15664
  );
15140
15665
  return 2;
15141
15666
  }
15142
15667
  if (!stdinIsTty()) {
15143
15668
  printErr(
15144
- `error: multiple deployments are active; non-interactive \`${operation}\` requires --provider bnb|aws|azure`
15669
+ `error: multiple deployments are active; non-interactive \`${operation}\` requires --provider ${ADVERTISED_PROVIDERS_HELP}`
15145
15670
  );
15146
15671
  return 2;
15147
15672
  }
@@ -15426,7 +15951,9 @@ async function cmdDoctor(opts) {
15426
15951
  checks.push(...await checkLlm(data, projectRoot));
15427
15952
  checks.push(...await checkNetwork(data, opts.network));
15428
15953
  checks.push(...checkCurrency(data));
15954
+ checks.push(...checkErc8183Pricing(data));
15429
15955
  checks.push(...checkPriceBounds(data));
15956
+ checks.push(...checkX402Seller(data, projectRoot));
15430
15957
  checks.push(...checkStorage(data));
15431
15958
  checks.push(...await checkBalances(projectRoot, data, opts.network));
15432
15959
  checks.push(...await checkPieverseMainnetU(projectRoot, data));
@@ -15592,7 +16119,7 @@ function anchoredKeystoreDir(projectRoot, walletCfg) {
15592
16119
  async function checkWallet(projectRoot, data) {
15593
16120
  const walletCfg = tableOf13(data, "wallet");
15594
16121
  if (walletCfg.kind === "twak") {
15595
- return checkWalletTwak(walletCfg, projectRoot);
16122
+ return checkWalletTwak(walletCfg, projectRoot, data);
15596
16123
  }
15597
16124
  if (walletCfg.kind === "altana") {
15598
16125
  return checkWalletAltana(walletCfg, projectRoot);
@@ -15737,7 +16264,7 @@ function checkWalletAltana(walletCfg, projectRoot) {
15737
16264
  }
15738
16265
  return out;
15739
16266
  }
15740
- async function checkWalletTwak(walletCfg, projectRoot) {
16267
+ async function checkWalletTwak(walletCfg, projectRoot, data = {}) {
15741
16268
  const out = [];
15742
16269
  if (await whichTwak() !== null) {
15743
16270
  const version = await twakInstalledVersion();
@@ -15762,6 +16289,15 @@ async function checkWalletTwak(walletCfg, projectRoot) {
15762
16289
  detail: `not on PATH \u2014 npm install -g @trustwallet/cli@${TWAK_CLI_VERSION}`
15763
16290
  });
15764
16291
  }
16292
+ const networkName = String(tableOf13(data, "network").default ?? "bsc-testnet");
16293
+ const unsupportedTargets = twakUnsupportedContractOverrides(networkName);
16294
+ if (unsupportedTargets.length > 0) {
16295
+ out.push({
16296
+ name: "[wallet] twak contract targets",
16297
+ status: FAIL,
16298
+ detail: `twak v0.20.0 cannot target custom ERC-8183 contracts (${unsupportedTargets.map((item) => item.envKey).join(", ")}) and would use canonical contracts instead. Use wallet.kind='evm-local' for custom ERC-8183 contracts, or remove the overrides. ERC8004_REGISTRY_ADDRESS is supported.`
16299
+ });
16300
+ }
15765
16301
  const walletFile = twakWalletFile(walletCfg, projectRoot);
15766
16302
  if (isFile16(walletFile)) {
15767
16303
  out.push({
@@ -16034,10 +16570,94 @@ function checkCurrency(data) {
16034
16570
  }
16035
16571
  return [];
16036
16572
  }
16573
+ function checkErc8183Pricing(data) {
16574
+ const payValue = tableOf13(data, "payments").erc8183;
16575
+ if (payValue === null || typeof payValue !== "object" || Array.isArray(payValue)) {
16576
+ return [];
16577
+ }
16578
+ const pay = payValue;
16579
+ const pricing = erc8183PricingState(pay);
16580
+ if (pricing.kind === "unset") {
16581
+ return [
16582
+ {
16583
+ name: "erc8183 pricing",
16584
+ status: FAIL,
16585
+ detail: "[payments.erc8183].price is unset \u2014 set it explicitly to 0 for FREE or to a positive integer string in token base units."
16586
+ }
16587
+ ];
16588
+ }
16589
+ if (pricing.kind === "invalid") {
16590
+ return [
16591
+ {
16592
+ name: "erc8183 pricing",
16593
+ status: FAIL,
16594
+ detail: `[payments.erc8183].${pricing.field} must be a non-negative uint256 decimal string; got ${JSON.stringify(pricing.value)}.`
16595
+ }
16596
+ ];
16597
+ }
16598
+ if (pricing.kind === "clamped_to_zero") {
16599
+ return [
16600
+ {
16601
+ name: "erc8183 pricing",
16602
+ status: FAIL,
16603
+ detail: `PAID list price ${pricing.listPrice} is clamped to 0 by max_price. Set price explicitly to 0 for FREE or fix the clamp.`
16604
+ }
16605
+ ];
16606
+ }
16607
+ const contracts = erc8183ContractOverrideState();
16608
+ const priceMode = pricing.kind === "free" ? "FREE \u2014 zero token escrow" : `PAID \u2014 effective list price ${pricing.effectivePrice} token base units`;
16609
+ if (contracts.mode === "partial") {
16610
+ return [
16611
+ {
16612
+ name: "erc8183 pricing",
16613
+ status: FAIL,
16614
+ detail: `${priceMode}, but the ERC-8183 contract override is incomplete. Set ${contracts.missing.join(", ")} so commerce, router, and policy come from one compatible stack.`
16615
+ }
16616
+ ];
16617
+ }
16618
+ if (contracts.mode === "invalid") {
16619
+ return [
16620
+ {
16621
+ name: "erc8183 pricing",
16622
+ status: FAIL,
16623
+ detail: `${priceMode}, but these contract overrides are not valid EVM addresses: ${contracts.invalid.join(", ")}.`
16624
+ }
16625
+ ];
16626
+ }
16627
+ if (pricing.kind === "paid") {
16628
+ return [
16629
+ {
16630
+ name: "erc8183 pricing",
16631
+ status: PASS,
16632
+ detail: `${priceMode}.`
16633
+ }
16634
+ ];
16635
+ }
16636
+ if (contracts.mode === "custom") {
16637
+ return [
16638
+ {
16639
+ name: "erc8183 pricing",
16640
+ status: PASS,
16641
+ detail: "FREE \u2014 zero token escrow; custom contract stack selected with all three ERC-8183 address overrides."
16642
+ }
16643
+ ];
16644
+ }
16645
+ return [
16646
+ {
16647
+ name: "erc8183 pricing",
16648
+ status: FAIL,
16649
+ detail: "FREE \u2014 zero token escrow, but the canonical ERC-8183 contracts reject zero funding. Set ERC8183_COMMERCE_ADDRESS, ERC8183_ROUTER_ADDRESS, and ERC8183_POLICY_ADDRESS together to a zero-price-compatible custom stack."
16650
+ }
16651
+ ];
16652
+ }
16037
16653
  function checkPriceBounds(data) {
16038
- const pay = tableOf13(tableOf13(data, "payments"), "erc8183");
16654
+ const payValue = tableOf13(data, "payments").erc8183;
16655
+ if (payValue === null || typeof payValue !== "object" || Array.isArray(payValue)) {
16656
+ return [];
16657
+ }
16658
+ const pay = payValue;
16039
16659
  const maxPrice = String(pay.max_price ?? "").trim();
16040
- if (!maxPrice || maxPrice === "0") {
16660
+ if (!maxPrice && erc8183PricingState(pay).kind !== "free") {
16041
16661
  return [
16042
16662
  {
16043
16663
  name: "erc8183 price bounds",
@@ -16048,6 +16668,84 @@ function checkPriceBounds(data) {
16048
16668
  }
16049
16669
  return [];
16050
16670
  }
16671
+ function checkX402Seller(data, agentRoot2) {
16672
+ const payments = tableOf13(data, "payments");
16673
+ const sellerValue = payments.x402_seller;
16674
+ if (sellerValue === null || typeof sellerValue !== "object" || Array.isArray(sellerValue)) {
16675
+ return [];
16676
+ }
16677
+ const seller = sellerValue;
16678
+ if (seller.enabled !== true) return [];
16679
+ const pricing = x402SellerPricingState(seller);
16680
+ if (pricing.kind === "invalid") {
16681
+ return [
16682
+ {
16683
+ name: "x402 pricing",
16684
+ status: FAIL,
16685
+ detail: "[payments.x402_seller].price_usd must be a non-negative decimal string."
16686
+ }
16687
+ ];
16688
+ }
16689
+ const runtime = String(tableOf13(data, "stack").runtime ?? "agentcore");
16690
+ const hasErc8183 = payments.erc8183 !== null && typeof payments.erc8183 === "object" && !Array.isArray(payments.erc8183);
16691
+ const fallbackStatus = hasErc8183 ? WARN : FAIL;
16692
+ if (pricing.kind === "free") {
16693
+ const out2 = [
16694
+ {
16695
+ name: "x402 pricing",
16696
+ status: PASS,
16697
+ detail: "FREE \u2014 /x402 is anonymous; B402 verify/settle, token payment, and settlement audit are bypassed."
16698
+ }
16699
+ ];
16700
+ const credentials2 = b402Credentials(agentRoot2);
16701
+ if (credentials2.any) {
16702
+ out2.push({
16703
+ name: "x402 credentials",
16704
+ status: INFO,
16705
+ detail: "configured B402 credentials are unused in FREE mode and will not be synchronized."
16706
+ });
16707
+ }
16708
+ if (runtime !== "agentcore") {
16709
+ out2.push({
16710
+ name: "x402 runtime",
16711
+ status: fallbackStatus,
16712
+ detail: `${runtime} has no /x402 route; FREE pricing does not remove that runtime limitation.`
16713
+ });
16714
+ }
16715
+ return out2;
16716
+ }
16717
+ const out = [
16718
+ {
16719
+ name: "x402 pricing",
16720
+ status: PASS,
16721
+ detail: `PAID \u2014 $${pricing.priceUsd} per request through B402.`
16722
+ }
16723
+ ];
16724
+ const walletKind2 = String(tableOf13(data, "wallet").kind ?? "evm-local");
16725
+ if (!["evm-local", "twak"].includes(walletKind2)) {
16726
+ out.push({
16727
+ name: "x402 payout wallet",
16728
+ status: FAIL,
16729
+ detail: `PAID B402 supports evm-local or twak, not ${walletKind2}.`
16730
+ });
16731
+ }
16732
+ const credentials = b402Credentials(agentRoot2);
16733
+ if (!credentials.complete) {
16734
+ out.push({
16735
+ name: "x402 credentials",
16736
+ status: credentials.any ? FAIL : fallbackStatus,
16737
+ detail: credentials.any ? "B402 credentials are partial; set BASE_URL, CLIENT_ID, ACCESS_TOKEN, and exactly one private-key form." : "PAID mode needs the four B402 merchant credentials; without them the rail starts dormant."
16738
+ });
16739
+ }
16740
+ if (runtime !== "agentcore") {
16741
+ out.push({
16742
+ name: "x402 runtime",
16743
+ status: fallbackStatus,
16744
+ detail: `${runtime} has no /x402 route; deploy this rail to AgentCore.`
16745
+ });
16746
+ }
16747
+ return out;
16748
+ }
16051
16749
  function checkStorage(data) {
16052
16750
  const kind = String(tableOf13(data, "storage").kind ?? "local").toLowerCase();
16053
16751
  if (kind === "ipfs") {
@@ -16650,7 +17348,7 @@ function loadAgentCfg3() {
16650
17348
  return null;
16651
17349
  }
16652
17350
  }
16653
- function resolveNetwork(override) {
17351
+ function resolveNetwork2(override) {
16654
17352
  if (override) {
16655
17353
  return override;
16656
17354
  }
@@ -16840,7 +17538,7 @@ async function cmdRegister2(opts) {
16840
17538
  return 2;
16841
17539
  }
16842
17540
  const wallet = walletRt6.getWallet();
16843
- const network = resolveNetwork(opts.network);
17541
+ const network = resolveNetwork2(opts.network);
16844
17542
  const gasErr = await precheckRegisterGas(wallet, network);
16845
17543
  if (gasErr !== null) {
16846
17544
  printErr(`error: ${gasErr}`);
@@ -16928,7 +17626,7 @@ async function cmdUpdateEndpoint(opts) {
16928
17626
  return 2;
16929
17627
  }
16930
17628
  const wallet = walletRt6.getWallet();
16931
- const network = resolveNetwork(opts.network);
17629
+ const network = resolveNetwork2(opts.network);
16932
17630
  const protocol = resolveProtocol(opts.protocol);
16933
17631
  let accessDescription = null;
16934
17632
  const agentRoot2 = findProjectRoot5();
@@ -16971,7 +17669,7 @@ async function cmdUpdateEndpoint(opts) {
16971
17669
  }
16972
17670
  async function cmdUpdateMetadata(opts) {
16973
17671
  const wallet = walletRt6.getWallet();
16974
- const network = resolveNetwork(opts.network);
17672
+ const network = resolveNetwork2(opts.network);
16975
17673
  let txHash;
16976
17674
  try {
16977
17675
  txHash = await setMetadata(wallet, opts.key, opts.value, {
@@ -17003,7 +17701,7 @@ async function cmdClearPending(opts) {
17003
17701
  }
17004
17702
  const pendingTx = identity.pending_tx ? String(identity.pending_tx) : null;
17005
17703
  if (pendingTx !== null && !opts.force) {
17006
- const network = resolveNetwork(opts.network);
17704
+ const network = resolveNetwork2(opts.network);
17007
17705
  let seen;
17008
17706
  try {
17009
17707
  seen = await readTransactionSeen(pendingTx, network);
@@ -17034,7 +17732,7 @@ async function cmdGetMetadata(opts) {
17034
17732
  let value;
17035
17733
  try {
17036
17734
  value = await getMetadata(wallet, opts.key, {
17037
- network: resolveNetwork(opts.network)
17735
+ network: resolveNetwork2(opts.network)
17038
17736
  });
17039
17737
  } catch (exc) {
17040
17738
  printOut(`error: ${errMsg4(exc)}`);
@@ -17047,7 +17745,7 @@ async function cmdShow6(networkArg) {
17047
17745
  const wallet = walletRt6.getWallet();
17048
17746
  let record;
17049
17747
  try {
17050
- record = await show2(wallet, { network: resolveNetwork(networkArg) });
17748
+ record = await show2(wallet, { network: resolveNetwork2(networkArg) });
17051
17749
  } catch (exc) {
17052
17750
  printOut(`error: ${errMsg4(exc)}`);
17053
17751
  return errName2(exc) === "NotRegisteredError" ? 2 : 1;
@@ -17064,7 +17762,7 @@ async function cmdResolve(agentId, networkArg) {
17064
17762
  try {
17065
17763
  uri = await resolve20(agentId, {
17066
17764
  wallet,
17067
- network: resolveNetwork(networkArg)
17765
+ network: resolveNetwork2(networkArg)
17068
17766
  });
17069
17767
  } catch (exc) {
17070
17768
  printOut(`error: ${errMsg4(exc)}`);
@@ -18576,7 +19274,10 @@ function registerX402(program) {
18576
19274
  return 1;
18577
19275
  })
18578
19276
  );
18579
- sell.command("init").description("Add the x402 seller config and missing B402 placeholders.").action(act(() => cmdSellInit()));
19277
+ sell.command("init").description("Add the x402 seller config and missing B402 placeholders.").option(
19278
+ "--price-usd <amount>",
19279
+ "Per-request decimal USD price; use 0 for FREE passthrough."
19280
+ ).action(act((opts) => cmdSellInit(opts.priceUsd)));
18580
19281
  sell.command("status").description("Show x402 seller config and B402 capability status.").option("--no-probe", "Skip the authenticated B402 /supported probe.").action(act((opts) => cmdSellStatus(opts.probe)));
18581
19282
  }
18582
19283
  function hostOf(url) {
@@ -18628,7 +19329,7 @@ function loadSellerConfig() {
18628
19329
  return null;
18629
19330
  }
18630
19331
  }
18631
- function resolveNetwork2(cfg, override) {
19332
+ function resolveNetwork3(cfg, override) {
18632
19333
  if (override) {
18633
19334
  return override;
18634
19335
  }
@@ -18661,39 +19362,88 @@ function resolveWallet2() {
18661
19362
  function printScopeError(host) {
18662
19363
  printErr(`error: ${SCOPE_NOTE.replace("{host}", host)}`);
18663
19364
  }
18664
- function cmdSellInit() {
19365
+ async function cmdSellInit(priceFlag) {
18665
19366
  const loaded = loadSellerConfig();
18666
19367
  if (loaded === null) return 1;
18667
19368
  const [root, cfg] = loaded;
19369
+ const tomlPath = path46.join(root, "studio.toml");
19370
+ const original = fs44.readFileSync(tomlPath, "utf8");
19371
+ const sellerExists = hasSection(original, "payments.x402_seller");
19372
+ let priceUsd;
19373
+ try {
19374
+ priceUsd = priceFlag !== void 0 ? normalizeB402PriceUsd(priceFlag) : sellerExists ? X402SellerPolicy.fromToml(cfg).priceUsd : await resolveSellInitPrice(stdinIsTty());
19375
+ } catch (exc) {
19376
+ printErr(`error: ${errMsg6(exc)}`);
19377
+ return 2;
19378
+ }
19379
+ const free = x402SellerPricingState({ price_usd: priceUsd }).kind === "free";
18668
19380
  const walletKind2 = String(tableOf15(cfg, "wallet").kind ?? "evm-local");
18669
- if (!["evm-local", "twak"].includes(walletKind2)) {
19381
+ if (!free && !["evm-local", "twak"].includes(walletKind2)) {
18670
19382
  printErr(
18671
19383
  `error: the b402 seller rail supports wallet.kind evm-local or twak only; '${walletKind2}' cannot act as the b402 payout wallet.`
18672
19384
  );
18673
19385
  return 1;
18674
19386
  }
18675
- const tomlPath = path46.join(root, "studio.toml");
18676
- const original = fs44.readFileSync(tomlPath, "utf8");
18677
- if (!hasSection(original, "payments.x402_seller")) {
19387
+ if (!sellerExists) {
18678
19388
  fs44.writeFileSync(
18679
19389
  tomlPath,
18680
19390
  updateSection(original, "payments.x402_seller", {
18681
19391
  enabled: true,
18682
- price_usd: "0.01",
19392
+ price_usd: priceUsd,
18683
19393
  assets: ["U"],
18684
19394
  pay_to: "",
18685
19395
  work_timeout_seconds: 60
18686
19396
  }),
18687
19397
  "utf8"
18688
19398
  );
19399
+ } else if (priceFlag !== void 0) {
19400
+ fs44.writeFileSync(
19401
+ tomlPath,
19402
+ updateSection(original, "payments.x402_seller", {
19403
+ price_usd: priceUsd
19404
+ }),
19405
+ "utf8"
19406
+ );
18689
19407
  }
18690
19408
  const envPath = envLocalPath17(root);
18691
19409
  for (const key of B402_ENV_KEYS) {
18692
- if (getEnvVar(envPath, key) === null) setEnvVar(envPath, key, "");
19410
+ const existing = getEnvVar(envPath, key);
19411
+ if (key === "B402_BASE_URL" && !free && resolveNetwork3(cfg).toLowerCase() === "bsc-testnet" && !process.env.B402_BASE_URL && !existing) {
19412
+ setEnvVar(envPath, key, DEFAULT_B402_TESTNET_BASE_URL);
19413
+ } else if (existing === null) {
19414
+ setEnvVar(envPath, key, "");
19415
+ }
19416
+ }
19417
+ printOut(
19418
+ free ? "\u2713 x402 seller config is ready in FREE mode; B402 credentials are not required." : "\u2713 x402 seller config and B402 placeholders are ready for PAID mode."
19419
+ );
19420
+ if (free) {
19421
+ printErr(
19422
+ "warning: /x402 is an anonymous FREE endpoint; B402 verify/settle and payment audit are bypassed."
19423
+ );
18693
19424
  }
18694
- printOut("\u2713 x402 seller config and B402 placeholders are ready.");
18695
19425
  return 0;
18696
19426
  }
19427
+ async function resolveSellInitPrice(isTty) {
19428
+ if (!isTty) return DEFAULT_B402_PRICE_USD;
19429
+ const choice = (await promptUser(
19430
+ "B402/x402 pricing [paid/free/custom, Enter = paid ($0.01)]: "
19431
+ )).trim().toLowerCase();
19432
+ if (choice === "free") return "0";
19433
+ if (choice === "custom") {
19434
+ return normalizeB402PriceUsd(
19435
+ await promptUser(
19436
+ "B402/x402 per-request price in USD (non-negative decimal): "
19437
+ )
19438
+ );
19439
+ }
19440
+ if (choice !== "" && choice !== "paid") {
19441
+ printErr(
19442
+ `hint: unknown B402/x402 pricing choice '${choice}' \u2014 using paid.`
19443
+ );
19444
+ }
19445
+ return DEFAULT_B402_PRICE_USD;
19446
+ }
18697
19447
  async function cmdSellStatus(probe) {
18698
19448
  const loaded = loadSellerConfig();
18699
19449
  if (loaded === null) return 1;
@@ -18714,9 +19464,13 @@ async function cmdSellStatus(probe) {
18714
19464
  };
18715
19465
  const privateKeySet = presence.B402_PRIVATE_KEY || presence.B402_PRIVATE_KEY_B64;
18716
19466
  const credentialsComplete = presence.B402_BASE_URL && presence.B402_CLIENT_ID && presence.B402_ACCESS_TOKEN && privateKeySet;
18717
- const state = !policy.enabled ? "disabled" : credentialsComplete ? "active-config" : "dormant";
19467
+ const free = x402SellerPricingState({ price_usd: policy.priceUsd }).kind === "free";
19468
+ const state = !policy.enabled ? "disabled" : free ? "free" : credentialsComplete ? "active-config" : "dormant";
18718
19469
  printOut(`Rail state: ${state}`);
18719
19470
  printOut(` price_usd: ${policy.priceUsd}`);
19471
+ printOut(
19472
+ ` Pricing: ${free ? "FREE \u2014 anonymous passthrough; no payment" : "PAID \u2014 B402 verify/settle required"}`
19473
+ );
18720
19474
  printOut(` assets: ${JSON.stringify(policy.assets)}`);
18721
19475
  printOut(` pay_to: ${policy.payTo ?? "(agent wallet)"}`);
18722
19476
  printOut(` work_timeout_seconds: ${policy.workTimeoutSeconds}`);
@@ -18729,11 +19483,15 @@ async function cmdSellStatus(probe) {
18729
19483
  printOut("B402 probe: skipped (--no-probe)");
18730
19484
  return 0;
18731
19485
  }
19486
+ if (policy.enabled && free) {
19487
+ printOut("B402 probe: skipped (FREE bypasses B402)");
19488
+ return 0;
19489
+ }
18732
19490
  if (!policy.enabled || !credentialsComplete) {
18733
19491
  printOut("B402 probe: skipped (rail disabled or credentials incomplete)");
18734
19492
  return 0;
18735
19493
  }
18736
- const networkName = resolveNetwork2(cfg);
19494
+ const networkName = resolveNetwork3(cfg);
18737
19495
  const network = caip2(getNetwork11(networkName).chainId);
18738
19496
  const token = SELLER_TOKENS[network];
18739
19497
  if (!token) {
@@ -18827,7 +19585,7 @@ async function cmdBuy2(url, opts) {
18827
19585
  printErr(`error: ${errMsg6(exc)}`);
18828
19586
  return 2;
18829
19587
  }
18830
- const networkName = resolveNetwork2(cfg, opts.network);
19588
+ const networkName = resolveNetwork3(cfg, opts.network);
18831
19589
  const wallet = resolveWallet2();
18832
19590
  if (wallet === null) {
18833
19591
  return 1;
@@ -19016,7 +19774,7 @@ async function cmdTrust(merchant, opts) {
19016
19774
  host = rec.domain;
19017
19775
  methodHint = rec.probeMethod;
19018
19776
  }
19019
- const networkName = opts.network || (rec ? rec.network : resolveNetwork2(cfg, null));
19777
+ const networkName = opts.network || (rec ? rec.network : resolveNetwork3(cfg, null));
19020
19778
  const probed = await probeChallenge(probeUrl, methodHint);
19021
19779
  if (probed === null) {
19022
19780
  return 1;
@@ -19162,7 +19920,7 @@ function buildProgram() {
19162
19920
  return program;
19163
19921
  }
19164
19922
  function cliVersion() {
19165
- return "0.0.6-alpha.1";
19923
+ return "0.0.6-alpha.3";
19166
19924
  }
19167
19925
 
19168
19926
  // src/cli/updateCheck.ts