@bnbagent/studio-cli 0.0.6-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/DISCLAIMER.md +48 -0
  2. package/LICENSE +201 -0
  3. package/dist/_agentcoreName-DZDWEYD3.js +7 -0
  4. package/dist/_twak-5XQMOFUC.js +25 -0
  5. package/dist/bag.js +19358 -0
  6. package/dist/chunk-7RAKL4AS.js +172 -0
  7. package/dist/chunk-M3ODFCA7.js +1053 -0
  8. package/dist/chunk-U7IDQ3K5.js +14 -0
  9. package/dist/deployCli-N6TPN6XA.js +40 -0
  10. package/package.json +64 -0
  11. package/recipes/agent/code/{{PKG}}/signing.ts.tmpl +287 -0
  12. package/recipes/agent/recipe.toml +35 -0
  13. package/recipes/providers/pieverse-llm/recipe.toml +16 -0
  14. package/recipes/providers/pieverse-llm/skills/funding-pieverse-llm.md +203 -0
  15. package/recipes/runtimes/agentcore/code/{{PKG}}/.dockerignore.tmpl +8 -0
  16. package/recipes/runtimes/agentcore/code/{{PKG}}/Dockerfile.tmpl +50 -0
  17. package/recipes/runtimes/agentcore/code/{{PKG}}/agentCard.ts.tmpl +135 -0
  18. package/recipes/runtimes/agentcore/code/{{PKG}}/dualMain.ts.tmpl +402 -0
  19. package/recipes/runtimes/agentcore/code/{{PKG}}/executor.ts.tmpl +147 -0
  20. package/recipes/runtimes/agentcore/code/{{PKG}}/main.ts.tmpl +344 -0
  21. package/recipes/runtimes/agentcore/code/{{PKG}}/mcpMain.ts.tmpl +677 -0
  22. package/recipes/runtimes/agentcore/code/{{PKG}}/model.ts.tmpl +117 -0
  23. package/recipes/runtimes/agentcore/code/{{PKG}}/sellerCore.ts.tmpl +503 -0
  24. package/recipes/runtimes/agentcore/code/{{PKG}}/tools.ts.tmpl +157 -0
  25. package/recipes/runtimes/agentcore/recipe.toml +97 -0
  26. package/recipes/runtimes/azure-foundry/code/{{PKG}}/.dockerignore.tmpl +8 -0
  27. package/recipes/runtimes/azure-foundry/code/{{PKG}}/Dockerfile.tmpl +47 -0
  28. package/recipes/runtimes/azure-foundry/code/{{PKG}}/agentCard.ts.tmpl +131 -0
  29. package/recipes/runtimes/azure-foundry/code/{{PKG}}/executor.ts.tmpl +504 -0
  30. package/recipes/runtimes/azure-foundry/code/{{PKG}}/foundryMain.ts.tmpl +300 -0
  31. package/recipes/runtimes/azure-foundry/code/{{PKG}}/main.ts.tmpl +196 -0
  32. package/recipes/runtimes/azure-foundry/code/{{PKG}}/mcpMain.ts.tmpl +562 -0
  33. package/recipes/runtimes/azure-foundry/code/{{PKG}}/model.ts.tmpl +117 -0
  34. package/recipes/runtimes/azure-foundry/code/{{PKG}}/tools.ts.tmpl +157 -0
  35. package/recipes/runtimes/azure-foundry/recipe.toml +88 -0
  36. package/recipes/tools-chain/code/{{PKG}}/chainTools.ts.tmpl +166 -0
  37. package/recipes/tools-chain/recipe.toml +11 -0
  38. package/recipes/wallet/recipe.toml +20 -0
  39. package/recipes/x402-buyer/code/{{PKG}}/x402Buyer.ts.tmpl +175 -0
  40. package/recipes/x402-buyer/recipe.toml +15 -0
  41. package/skills/bnbagent-studio.md +107 -0
  42. package/skills/references/bnbagent-studio-adding-to-project.md +241 -0
  43. package/skills/references/bnbagent-studio-buying-from-bazaar.md +169 -0
  44. package/skills/references/bnbagent-studio-buying-via-8183.md +222 -0
  45. package/skills/references/bnbagent-studio-extending-signing.md +227 -0
  46. package/skills/references/bnbagent-studio-operating.md +211 -0
  47. package/skills/references/bnbagent-studio-scaffolding-agent.md +536 -0
  48. package/skills/references/bnbagent-studio-selling-via-8183.md +271 -0
  49. package/skills/references/bnbagent-studio-selling-via-b402.md +194 -0
  50. package/skills/references/bnbagent-studio-use-aws-agentcore.md +208 -0
  51. package/skills/references/bnbagent-studio-use-azure-foundry.md +164 -0
  52. package/skills/references/bnbagent-studio-use-bnb-trial.md +92 -0
  53. package/skills/references/bnbagent-studio-using-altana-wallet.md +68 -0
  54. package/skills/references/bnbagent-studio-using-twak-wallet.md +260 -0
  55. package/skills/references/bnbagent-studio-wiring-llm-tools.md +338 -0
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/_agentcoreName.ts
4
+ var AGENTCORE_NAME_RE = /^[A-Za-z][A-Za-z0-9]{0,22}$/;
5
+ function validateAgentcoreName(name) {
6
+ if (AGENTCORE_NAME_RE.test(name)) {
7
+ return null;
8
+ }
9
+ return `agentcore.json name '${name}' is invalid: the agentcore CLI requires a name that starts with a letter, contains only ASCII letters/digits (no '_', '-', '.'), and is at most 23 characters. The agentcore CLI's own error for this can be misleading ('No agentcore project found') \u2014 fix the 'name' field in agentcore/agentcore.json.`;
10
+ }
11
+
12
+ export {
13
+ validateAgentcoreName
14
+ };
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BNB_PLATFORM_API_URL,
4
+ BNB_PLATFORM_API_URL_ENV,
5
+ DEPLOY_CLI_PACKAGE,
6
+ DEPLOY_CLI_VERSION,
7
+ LEGACY_BNB_PLATFORM_API_URL_ENV,
8
+ bnbEnv,
9
+ bnbPlatformApiUrl,
10
+ buildDeploySpec,
11
+ deployCommand,
12
+ deployEndpointValue,
13
+ providerPassthrough,
14
+ renderSecretEnvFile,
15
+ runDeployCliJson,
16
+ runDeployCliStream,
17
+ runPlatformAccountCommand,
18
+ trialFromDeployCliJson,
19
+ withDeployFiles
20
+ } from "./chunk-M3ODFCA7.js";
21
+ import "./chunk-7RAKL4AS.js";
22
+ export {
23
+ BNB_PLATFORM_API_URL,
24
+ BNB_PLATFORM_API_URL_ENV,
25
+ DEPLOY_CLI_PACKAGE,
26
+ DEPLOY_CLI_VERSION,
27
+ LEGACY_BNB_PLATFORM_API_URL_ENV,
28
+ bnbEnv,
29
+ bnbPlatformApiUrl,
30
+ buildDeploySpec,
31
+ deployCommand,
32
+ deployEndpointValue,
33
+ providerPassthrough,
34
+ renderSecretEnvFile,
35
+ runDeployCliJson,
36
+ runDeployCliStream,
37
+ runPlatformAccountCommand,
38
+ trialFromDeployCliJson,
39
+ withDeployFiles
40
+ };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@bnbagent/studio-cli",
3
+ "version": "0.0.6-alpha.1",
4
+ "description": "The `bag` CLI: scaffold, run, deploy, and monetize a single seller agent on BNB Chain (ERC-8004 identity, ERC-8183 commerce, x402 payments).",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/bnb-chain/bnbagent-studio.git",
9
+ "directory": "packages/studio-cli"
10
+ },
11
+ "type": "module",
12
+ "engines": {
13
+ "node": ">=22"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "skills",
21
+ "recipes",
22
+ "DISCLAIMER.md"
23
+ ],
24
+ "bin": {
25
+ "bag": "./dist/bag.js"
26
+ },
27
+ "dependencies": {
28
+ "@bnbagent/sdk": "0.5.0-alpha.1",
29
+ "ai": "^7.0.29",
30
+ "archiver": "^8.0.0",
31
+ "commander": "^15.0.0",
32
+ "esbuild": "^0.28.0",
33
+ "execa": "^9.5.0",
34
+ "picocolors": "^1.1.0",
35
+ "proper-lockfile": "^4.1.2",
36
+ "smol-toml": "^1.3.0",
37
+ "tar": "^7.4.0",
38
+ "viem": "^2.54.0",
39
+ "yaml": "^2.9.0",
40
+ "@bnbagent/studio-runtime": "0.0.6-alpha.1"
41
+ },
42
+ "devDependencies": {
43
+ "@a2a-js/sdk": "^0.3.14",
44
+ "@ai-sdk/openai": "^4.0.15",
45
+ "@aws-sdk/client-secrets-manager": "^3.600.0",
46
+ "@modelcontextprotocol/sdk": "^1.29.0",
47
+ "@types/express": "^5.0.6",
48
+ "@types/node": "^22.0.0",
49
+ "@types/proper-lockfile": "^4.1.4",
50
+ "express": "^5.2.1",
51
+ "tsup": "^8.0.0",
52
+ "tsx": "^4.19.0",
53
+ "typescript": "^5.5.0",
54
+ "vitest": "^2.0.0",
55
+ "zod": "^3.25.76"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup",
59
+ "test": "vitest run",
60
+ "lint": "biome check src tests",
61
+ "typecheck": "tsc --noEmit",
62
+ "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
63
+ }
64
+ }
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Deterministic signing — the Agent is the SOLE key-holder/signer.
3
+ *
4
+ * Every on-chain WRITE the Agent performs lives here as FIXED code:
5
+ *
6
+ * signQuote(...) EIP-191 sign the (clamped) negotiated offer
7
+ * submitResult(...) build manifest → upload → on-chain `submit`
8
+ * settle(...) claim payment after the dispute window
9
+ *
10
+ * These functions are NEVER registered as LLM-callable tools (`tools.ts` holds
11
+ * only read-only tools). The price is a FIXED list price from studio.toml
12
+ * (`listPrice()`, clamped by the entrypoint BEFORE it reaches here) — the LLM
13
+ * only produces the work text and never moves money or sets a price.
14
+ *
15
+ * The key is loaded by `@bnbagent/studio-runtime/wallet` `getWallet()` (local
16
+ * keystore, unlocked by `WALLET_PASSWORD`). It is injected into the AgentCore
17
+ * runtime via the secret store, never bundled into the code package.
18
+ *
19
+ * You own this file — edit the pricing clamp source / manifest shape if your
20
+ * domain needs it, but keep these ops OUT of the LLM tool list.
21
+ */
22
+
23
+ import {
24
+ JobDescription,
25
+ NegotiationHandler,
26
+ type NegotiationResult,
27
+ type QuoteSigner,
28
+ } from "@bnbagent/sdk/erc8183";
29
+ import { getAddress as deployedAddresses } from "@bnbagent/sdk/networks";
30
+ import {
31
+ loadStudioToml,
32
+ type TomlTable,
33
+ } from "@bnbagent/studio-runtime/config";
34
+ import {
35
+ get8183Client,
36
+ settleWorkflow,
37
+ type SubmitResult,
38
+ submitWorkflow,
39
+ type Verdict,
40
+ verifySignedJob as verifySignedJobCore,
41
+ } from "@bnbagent/studio-runtime/erc8183";
42
+ import { getNetwork } from "@bnbagent/studio-runtime/networks";
43
+ import { getWallet } from "@bnbagent/studio-runtime/wallet";
44
+
45
+ const MAX_UINT256 = (1n << 256n) - 1n;
46
+
47
+ // ── test seams (mirror the studio-runtime `_set*` convention) ────────────────
48
+ type StudioTomlLoader = () => TomlTable;
49
+ const defaultTomlLoader: StudioTomlLoader = () => loadStudioToml();
50
+ let tomlLoader: StudioTomlLoader = defaultTomlLoader;
51
+
52
+ /** Test seam: replace the studio.toml loader. Pass null to restore. */
53
+ export function _setStudioTomlLoader(loader: StudioTomlLoader | null): void {
54
+ tomlLoader = loader ?? defaultTomlLoader;
55
+ handler = null; // config feeds the cached handler — rebuild it
56
+ }
57
+
58
+ /** The narrow NegotiationHandler surface signQuote drives (test-fakeable). */
59
+ export interface NegotiationHandlerLike {
60
+ negotiate(
61
+ request: Record<string, unknown>,
62
+ opts?: { price?: string; estimatedCompletionSeconds?: number },
63
+ ): Promise<NegotiationResult> | NegotiationResult;
64
+ }
65
+
66
+ let handler: NegotiationHandlerLike | null = null;
67
+
68
+ type RuntimeWallet = ReturnType<typeof getWallet>;
69
+ type SessionQuoteWallet = RuntimeWallet & {
70
+ sessionQuoteSigner(): QuoteSigner;
71
+ };
72
+
73
+ /** Select the narrow quote-only authority when the wallet exposes one. */
74
+ export function negotiationSignerOptions(
75
+ wallet: RuntimeWallet,
76
+ ): { quoteSigner: QuoteSigner } | { walletProvider: RuntimeWallet } {
77
+ const candidate = wallet as Partial<SessionQuoteWallet>;
78
+ return typeof candidate.sessionQuoteSigner === "function"
79
+ ? { quoteSigner: candidate.sessionQuoteSigner.call(wallet) }
80
+ : { walletProvider: wallet };
81
+ }
82
+
83
+ /** Test seam: replace the cached NegotiationHandler. Pass null to restore. */
84
+ export function _setNegotiationHandler(h: NegotiationHandlerLike | null): void {
85
+ handler = h;
86
+ }
87
+
88
+ // ── config readers ────────────────────────────────────────────────────────────
89
+
90
+ /** Read `[payments.erc8183]` from studio.toml ({} when absent). */
91
+ function erc8183Cfg(): Record<string, unknown> {
92
+ let cfg: TomlTable;
93
+ try {
94
+ cfg = tomlLoader();
95
+ } catch {
96
+ // No studio.toml here; use the safe empty-config fallback.
97
+ cfg = {};
98
+ }
99
+ const payments = (cfg.payments ?? {}) as Record<string, unknown>;
100
+ return (payments.erc8183 ?? {}) as Record<string, unknown>;
101
+ }
102
+
103
+ /** studio.toml `[network].default` (best-effort; binds the quote signature). */
104
+ function defaultNetworkName(): string {
105
+ let cfg: TomlTable;
106
+ try {
107
+ cfg = tomlLoader();
108
+ } catch {
109
+ cfg = {};
110
+ }
111
+ return String(((cfg.network ?? {}) as TomlTable).default ?? "bsc-testnet");
112
+ }
113
+
114
+ /**
115
+ * Return `[minPrice, maxPrice]` in raw wei from studio.toml.
116
+ *
117
+ * These are the clamp bounds applied to the configured list price BEFORE
118
+ * signing. `min_price`/`max_price` are raw uint256 strings in
119
+ * `[payments.erc8183]`.
120
+ */
121
+ export function priceBounds(): [bigint, bigint] {
122
+ const cfg = erc8183Cfg();
123
+ // TODO: if min/max are absent the bounds default to (0, +inf) — i.e. NO
124
+ // clamp. Set [payments.erc8183].min_price / max_price in studio.toml to
125
+ // enforce a real floor/ceiling (strongly recommended for production).
126
+ // The scaffold ships max_price = "" (an empty string, not absent), so treat
127
+ // empty/whitespace the same as missing → fall back to the default bound.
128
+ const raw = (key: string, dflt: bigint): bigint => {
129
+ const s = String(cfg[key] ?? "").trim();
130
+ return s ? BigInt(s) : dflt;
131
+ };
132
+ return [raw("min_price", 0n), raw("max_price", MAX_UINT256)];
133
+ }
134
+
135
+ /**
136
+ * Return the seller's list price in raw wei from studio.toml.
137
+ *
138
+ * Reads `[payments.erc8183].price` — the deterministic asking price every
139
+ * quote uses (rule-based pricing; no LLM in the quote path). Empty/absent → 0.
140
+ * Edit `price` in studio.toml to change what you charge. The value is still
141
+ * clamped to `[minPrice, maxPrice]` by {@link clampPrice} before signing.
142
+ */
143
+ export function listPrice(): bigint {
144
+ const s = String(erc8183Cfg().price ?? "").trim();
145
+ return s ? BigInt(s) : 0n;
146
+ }
147
+
148
+ /** Clamp a proposed price into `[minPrice, maxPrice]`. */
149
+ export function clampPrice(proposedWei: bigint): bigint {
150
+ const [lo, hi] = priceBounds();
151
+ const capped = proposedWei < hi ? proposedWei : hi;
152
+ return capped > lo ? capped : lo;
153
+ }
154
+
155
+ /**
156
+ * Return the process-wide NegotiationHandler (lazy, cached).
157
+ *
158
+ * The handler's chainId + verifyingContract are stable per process, so we
159
+ * build it once. The per-request clamped price is passed via
160
+ * `negotiate(..., { price })` (see {@link signQuote}), so the
161
+ * construction-time `servicePrice` is a placeholder that is always
162
+ * overridden.
163
+ *
164
+ * chainId + verifyingContract bind provider_sig to this chain/contract
165
+ * (prevents cross-chain replay). The runtime client facade does not expose
166
+ * them, so
167
+ * they come from the SDK's on-chain address registry for the configured
168
+ * network — the same addresses the live client is constructed from.
169
+ */
170
+ function getHandler(): NegotiationHandlerLike {
171
+ if (handler === null) {
172
+ const cfg = erc8183Cfg();
173
+ const currency = String(cfg.currency ?? ""); // the Agent owns the currency now
174
+ const ttl = Number(cfg.quote_ttl_seconds ?? 900);
175
+ const est = Number(cfg.default_estimated_completion_seconds ?? 600);
176
+ const network = getNetwork(defaultNetworkName());
177
+ const wallet = getWallet();
178
+ handler = new NegotiationHandler({
179
+ servicePrice: "0", // placeholder — overridden per call via price=
180
+ currency,
181
+ estimatedCompletionSeconds: est,
182
+ ...negotiationSignerOptions(wallet),
183
+ quoteTtlSeconds: ttl,
184
+ chainId: network.chainId,
185
+ verifyingContract: deployedAddresses(network.chainId).commerceProxy,
186
+ });
187
+ }
188
+ return handler;
189
+ }
190
+
191
+ /**
192
+ * Negotiate + EIP-191-sign a quote at `clampedPriceWei`; return the SDK
193
+ * envelope.
194
+ *
195
+ * Reuses a process-wide NegotiationHandler (cached — its chainId +
196
+ * verifyingContract are stable per process) and overrides the price for this
197
+ * request via `negotiate(..., { price: String(clampedPriceWei) })`.
198
+ *
199
+ * Returns the SDK's `NegotiationResult.toDict()` envelope **verbatim** — the
200
+ * exact wire structure a buyer parses and feeds to `buildJobDescription` to
201
+ * anchor on-chain (see docs/design/erc8183-reference.md §2). On accept it
202
+ * carries `response.terms.price`/`currency`, `quote_expires_at`,
203
+ * `negotiation_hash`, `response_hash`, `provider_sig`, `chain_id`,
204
+ * `verifying_contract`; on reject it carries `response.reason_code` /
205
+ * `reason` (empty hash + sig). We do NOT invent a custom shape.
206
+ */
207
+ export async function signQuote(
208
+ request: Record<string, unknown>,
209
+ clampedPriceWei: bigint,
210
+ ): Promise<Record<string, unknown>> {
211
+ const cfg = erc8183Cfg();
212
+ const est = Number(cfg.default_estimated_completion_seconds ?? 600);
213
+
214
+ const result = await getHandler().negotiate(request, {
215
+ price: String(clampedPriceWei),
216
+ estimatedCompletionSeconds: est,
217
+ });
218
+
219
+ // NegotiationHandler signs non-fatally: if signMessage failed it returns an
220
+ // accepted result WITHOUT provider_sig. Never relay an unsigned "accepted".
221
+ if (result.accepted && (!result.negotiationHash || !result.providerSig)) {
222
+ throw new Error(
223
+ "quote accepted but provider_sig is missing (wallet sign failed); " +
224
+ "refusing to relay an unsigned offer",
225
+ );
226
+ }
227
+
228
+ return result.toDict();
229
+ }
230
+
231
+ /**
232
+ * Verify funded `jobId` carries the quote THIS agent signed.
233
+ *
234
+ * Thin wrapper over `@bnbagent/studio-runtime/erc8183` `verifySignedJob` with
235
+ * `expectedSigner` = our own wallet address. Returns a `Verdict` `{ ok,
236
+ * reason, permanent }`: `ok` → safe to work; otherwise `permanent`
237
+ * distinguishes a job to skip-forever (record + tell the client) from a
238
+ * transient retry.
239
+ */
240
+ export async function verifySignedJob(jobId: number): Promise<Verdict> {
241
+ return verifySignedJobCore(jobId, getWallet().address);
242
+ }
243
+
244
+ /**
245
+ * Return the on-chain `JobDescription` for `jobId` (`null` if unstructured).
246
+ *
247
+ * The task + terms the buyer ANCHORED ON-CHAIN — and that this agent's
248
+ * `provider_sig` covers — are the authoritative work spec. The work hook
249
+ * reads the task from HERE (the on-chain job description), so the Agent
250
+ * delivers exactly the deal it signed.
251
+ * Returns `null` for legacy/plain-text descriptions (caller falls back).
252
+ */
253
+ export async function jobSpec(jobId: number): Promise<JobDescription | null> {
254
+ const client = await get8183Client();
255
+ const job = await client.getJob(BigInt(jobId));
256
+ return JobDescription.fromStr(job.description);
257
+ }
258
+
259
+ /**
260
+ * Sign + broadcast the on-chain `submit` for `jobId`.
261
+ *
262
+ * Delegates to `@bnbagent/studio-runtime/erc8183` `submitWorkflow`, which
263
+ * re-verifies the job is genuinely FUNDED + assigned to us (via the SDK's
264
+ * `ERC8183JobOps.verifyJob`), builds the `DeliverableManifest`, uploads it
265
+ * to storage, and calls on-chain `submit` — all `auditedOp`-wrapped.
266
+ * Returns the `SubmitResult` (`.submitTx` + `.deliverableUrl`);
267
+ * `deliverableUrl` is published on-chain by the submit, so the buyer fetches
268
+ * the canonical manifest from storage without an on-chain log scan.
269
+ */
270
+ export async function submitResult(
271
+ jobId: number,
272
+ responseContent: string,
273
+ metadata?: Record<string, unknown> | null,
274
+ ): Promise<SubmitResult> {
275
+ return submitWorkflow(jobId, responseContent, { metadata: metadata ?? null });
276
+ }
277
+
278
+ /**
279
+ * Sign + broadcast `settle` (claim payment) for `jobId`.
280
+ *
281
+ * Delegates to `@bnbagent/studio-runtime/erc8183` `settleWorkflow` with the
282
+ * default `approve` action → SDK `router.settle(jobId)`, `auditedOp`-wrapped.
283
+ * Returns the settle tx hash.
284
+ */
285
+ export async function settle(jobId: number): Promise<string> {
286
+ return settleWorkflow(jobId, { action: "approve" });
287
+ }
@@ -0,0 +1,35 @@
1
+ [recipe]
2
+ name = "agent"
3
+ description = "Agent — protocol-neutral signing entrypoints (signQuote / submitResult / settle). The Agent is the SOLE key-holder/signer; this recipe emits the fixed signing.ts glue that the LLM never calls. Runtime choice (AgentCore / Azure Foundry) lives in `runtimes/<runtime>/` (which also carries the chain tools + model factory — decision D9: no separate frameworks recipe in the TS line)."
4
+ status = "signing-only" # only signing.ts here; runtime/provider code lives in their own recipes
5
+
6
+ [dependencies]
7
+ node = [
8
+ # signing.ts imports only from @bnbagent/studio-runtime (erc8183 + wallet +
9
+ # config + networks subpaths) and the @bnbagent/sdk protocol layer
10
+ # (NegotiationHandler / JobDescription) — protocol-level glue, framework-
11
+ # neutral. The agent project's serving deps (@a2a-js/sdk / MCP SDK / ai)
12
+ # come from the runtimes/<R>/ recipe selected at `bag init` time.
13
+ "@bnbagent/studio-runtime",
14
+ "@bnbagent/sdk@0.5.0-alpha.1",
15
+ ]
16
+
17
+ [env]
18
+ # WALLET_PASSWORD unlocks the local keystore. The agent is the SOLE signer:
19
+ # it uses the key for quote-sign + submit + settle (signing.ts, fixed code,
20
+ # NOT LLM tools) AND automatic budget-gated Pieverse LLM-credit auto-renew.
21
+ # The keystore is injected via the AgentCore secret store, NEVER bundled.
22
+ WALLET_PASSWORD = "wallet keystore password (the agent is the sole on-chain signer)"
23
+ PIEVERSE_LLM_API_KEY = "set by `bag llm activate` for the default Pieverse provider"
24
+ # OPENROUTER_API_KEY = "set if [llm].provider=openrouter (legacy)"
25
+ # STORAGE_API_URL / STORAGE_API_KEY: IPFS upload endpoint + write key — the
26
+ # Agent pins deliverables at submit ([storage].kind=ipfs). Any pinning service
27
+ # works (use its upload/pin endpoint + key/JWT), as does a self-hosted node's
28
+ # /api/v0/add (usually keyless). Agent-only secrets — inject into the AgentCore
29
+ # secret store at deploy, like WALLET_PASSWORD.
30
+ STORAGE_API_URL = "IPFS upload endpoint (required when [storage].kind=ipfs)"
31
+ STORAGE_API_KEY = "pinning-service write key/JWT (hosted services; self-hosted nodes usually need none)"
32
+ # RPC_URL: dedicated BSC RPC for the deployed Agent (SDK var). Set it to avoid
33
+ # the rate-limited public BSC RPC default; when set it is pushed with the
34
+ # runtime secrets on deploy. The STUDIO_BSC_RPC* vars are CLI-side only.
35
+ # RPC_URL = "https://your-dedicated-bsc-rpc"
@@ -0,0 +1,16 @@
1
+ [recipe]
2
+ name = "providers/pieverse-llm"
3
+ description = "Provider-specific assets for Pieverse LLM: emits `funding-pieverse-llm` skill into the project's `.claude/skills/` so Claude Code knows how to handle Pieverse credit / topup / paid-model decisions."
4
+ status = "v0.0.1"
5
+
6
+ # This is a "provider" recipe — non-code: it emits documentation/skill
7
+ # artifacts that travel with the chosen LLM provider. There is no `code/`
8
+ # directory; the init flow's provider-skill emitter reads `skills/*.md`
9
+ # and writes each to `<project>/.claude/skills/<stem>/SKILL.md`.
10
+ #
11
+ # Future on-chain providers (e.g. Bittensor, Aether) can follow the same
12
+ # layout: drop `skills/<name>.md` files here under their own provider dir
13
+ # and they'll be emitted at `bag init --llm-provider <name>` time.
14
+
15
+ [skills]
16
+ emit = ["funding-pieverse-llm"]
@@ -0,0 +1,203 @@
1
+ ---
2
+ name: funding-pieverse-llm
3
+ description: When the user is dealing with Pieverse LLM funding — switching to a paid model, hitting InsufficientCreditsError / PieverseAccountBalanceExhaustedError, topping up the wallet for paid LLM usage, or asking "how do I pay for the LLM" / "why is my agent stuck on LLM". Covers the zero-deposit default + the paid-upgrade path.
4
+ ---
5
+
6
+ # funding-pieverse-llm
7
+
8
+ Procedure for **Pieverse LLM funding decisions** in a bnbagent-studio project.
9
+ Audience: Claude Code helping a user who has already run `bag init` + `bag llm activate`.
10
+
11
+ **Different from**: `-operating` (general ops) — this skill is specifically for the
12
+ Pieverse credit lifecycle, paid-model upgrade, and topup troubleshooting.
13
+
14
+ ## Mental model (zero-deposit default)
15
+
16
+ bnbagent-studio v0.0.x defaults to **zero-deposit Pieverse activation**:
17
+
18
+ - `bag init` + `bag llm activate` together create a Pieverse account + API key
19
+ with **$0 initial allocation**. No wallet funding required.
20
+ - Default model is **`auto/free`** (Pieverse's $0/token tier). The agent runs
21
+ unmetered against it.
22
+ - The user **only needs to fund their wallet** when they want to:
23
+ 1. Switch to a **paid model** (e.g. `auto/paid`, `claude-haiku-4.5`,
24
+ `anthropic/claude-sonnet-4.6`, etc.)
25
+ 2. Run `bag erc8004 register` (needs **testnet tBNB** for gas)
26
+ 3. `bag deploy` to AgentCore
27
+
28
+ **Three independent balances**, on the same EOA address but different chains/assets:
29
+
30
+ | Balance | Chain | Purpose | When needed |
31
+ |---|---|---|---|
32
+ | testnet tBNB | BSC testnet (97) | gas for ERC-8004 / ERC-8183 / deploy txs | register / deploy |
33
+ | testnet U | BSC testnet (97) | ERC-8183 job settlement | when this agent buys/sells via 8183 |
34
+ | mainnet U | BSC mainnet (56) | Pieverse paid LLM credits | switching to paid LLM model |
35
+
36
+ Use `bag wallet balance --all` to see all three.
37
+
38
+ ## Quick triage decision tree
39
+
40
+ | User said... | Run first |
41
+ |---|---|
42
+ | "how do I switch to claude/opus/sonnet/paid model" | Section A below |
43
+ | `InsufficientCreditsError` during a negotiate or job delivery | Section B below |
44
+ | `PieverseAccountBalanceExhaustedError` | Section B below |
45
+ | `PieverseColdStartTopupBlockedError` | Section C below |
46
+ | "I want auto-renew without being asked" / "turn auto-renew on/off" | Section D below (`bag llm auto-renew status`) |
47
+ | "how much U / credit do I have" | `bag llm usage` (no password) + `bag llm status` (key credit) + `bag wallet balance --all` |
48
+ | "why is gas failing" | testnet tBNB ≠ mainnet U; check `bag wallet balance --all` |
49
+
50
+ ## A. Switch from `auto/free` to a paid model
51
+
52
+ ```bash
53
+ # 1. fund the wallet (mainnet U)
54
+ # The wallet address is the same EOA that signs ERC-8004 txs; on mainnet
55
+ # only the U balance matters here.
56
+ bag wallet show
57
+ # Then either:
58
+ # - swap on PancakeSwap → https://pancakeswap.finance/swap
59
+ # ?outputCurrency=0xcE24439F2D9C6a2289F741120FE202248B666666
60
+ # - transfer U from another wallet to the address above
61
+ bag wallet balance --all # confirm mainnet U > 0
62
+
63
+ # 2. allocate paid credits into the API key
64
+ bag llm topup --amount 1 # start small ($1 covers many calls on auto/paid)
65
+
66
+ # 3. change the default model in studio.toml
67
+ bag config set llm.model auto/paid # or anthropic/claude-haiku-4.5 etc.
68
+
69
+ # 4. verify
70
+ bag llm status # should show non-zero key_credit
71
+ bag llm test # one-shot smoke check
72
+ ```
73
+
74
+ **Do NOT** silently fallback to `auto/free` when a paid call fails — see the
75
+ zero-deposit hard-fail rule. The user explicitly chose a paid model; surface the failure.
76
+
77
+ ## B. InsufficientCreditsError / PieverseAccountBalanceExhaustedError
78
+
79
+ The agent's API key ran out of credit (or never had any). Recovery:
80
+
81
+ ```bash
82
+ # Diagnose
83
+ bag llm status # key_credit (per-key balance); Account Balance has no API endpoint
84
+ bag llm usage --days 7 # see what was spent
85
+
86
+ # If account balance > 0 but key balance = 0: allocate from account → key
87
+ bag llm allocate --amount 1
88
+
89
+ # If account balance = 0: topup from mainnet U first
90
+ bag wallet balance --all # check mainnet U
91
+ bag llm topup --amount 1 # spends mainnet U via x402
92
+ bag llm allocate --amount 1 # then allocate
93
+ ```
94
+
95
+ Never edit `studio.toml [llm].model = auto/free` to "fix" a paid model's
96
+ funding error — that's silently changing agent behavior. Either fund or
97
+ explicitly downgrade, and tell the user which you did.
98
+
99
+ ## C. PieverseColdStartTopupBlockedError
100
+
101
+ The auto-topup hook refuses to fund during the first 60 seconds after process
102
+ start. This is a **safety feature** (闸门 5) — it prevents boot-loop
103
+ scenarios from burning the user's monthly budget.
104
+
105
+ Recovery:
106
+ - Wait 60s, then retry the LLM call (auto-topup will engage).
107
+ - Or topup manually: `bag llm topup --amount N` (CLI is not subject to the
108
+ cold-start gate; only the in-process auto-topup hook is).
109
+ - **Never** advise the user to set `PIEVERSE_BUDGET_COLD_START_SECONDS=0` to
110
+ work around this — that env var is for CI/e2e only. If the user is hitting
111
+ this repeatedly, it indicates the agent is restarting too often; investigate
112
+ why.
113
+
114
+ ## D. Auto-renew: two independent tiers + their switches
115
+
116
+ Auto-renew is a **two-tier ladder**, each with its own on/off switch. Inspect
117
+ both at once:
118
+
119
+ ```bash
120
+ bag llm auto-renew status # prints the llm tier AND the wallet tier
121
+ ```
122
+
123
+ **Tier 1 — `llm` (allocate from Pieverse Account Balance; no wallet spend).**
124
+ Default **on**. Before each LLM call the agent tops the API key up from credit
125
+ already parked in the Account Balance when it dips below `min_balance_usd`.
126
+
127
+ ```bash
128
+ bag llm auto-renew llm off # disable: agent runs as a plain model, no credit hook
129
+ bag llm auto-renew llm on # re-enable (writes [llm.auto_renew].enabled)
130
+ ```
131
+
132
+ **Tier 2 — `wallet` (spend wallet U via x402 to refill the Account Balance).**
133
+ Default **off** (opt-in; the agent will not autonomously spend the wallet until
134
+ this is on). Turning it on records a consent stamp — required before any
135
+ autonomous spend.
136
+
137
+ ```bash
138
+ bag llm auto-renew wallet on # opt in (= bag budget enable; records acknowledged_at)
139
+ bag llm auto-renew wallet off # opt out (= bag budget disable)
140
+ ```
141
+
142
+ `bag llm auto-renew wallet on` is exactly `bag budget enable` and accepts the
143
+ same cap overrides via that command:
144
+
145
+ ```bash
146
+ bag budget show # current state (default: disabled)
147
+ bag budget enable # default caps: $1/topup, $3/day, $15/month
148
+ # Or customize the caps:
149
+ bag budget enable \
150
+ --max-per-topup-usd 0.5 \
151
+ --max-per-day-usd 2 \
152
+ --max-per-month-usd 10
153
+ ```
154
+
155
+ Once enabled, the agent will:
156
+ - Wait at least 60s after process start before any topup (cold-start gate)
157
+ - Try to topup_x402 from mainnet U when the API key drops below
158
+ `min_balance_usd` (default $0.2)
159
+ - Stop and raise on cap-exceeded; not retry on failure (exponential backoff
160
+ applies — next LLM call may retry)
161
+ - Log every topup attempt to `~/.bnbagent-studio/<project>/logs/topup.jsonl`
162
+ and print a red-text notice to stdout
163
+
164
+ `WALLET_PASSWORD` env must be present in the runtime — without it the gate
165
+ falls back to allocate-only mode (no wallet spend, no error).
166
+
167
+ ## E. Protocol-level details (for advanced cases)
168
+
169
+ When the user needs to understand the Pieverse protocol itself (raw SIWE,
170
+ x402 v2 envelope, deposit methods schema, signing typed data), point them
171
+ to Pieverse's official skill markdown — it is the authoritative reference:
172
+
173
+ - https://llm.pieverse.io/ai-gateway/skill.md (overview)
174
+ - https://llm.pieverse.io/ai-gateway/references/siwe-format.md (SIWE login)
175
+ - https://llm.pieverse.io/ai-gateway/references/x402-payment.md (topup mechanics)
176
+ - https://llm.pieverse.io/ai-gateway/references/api-reference.md (key/usage)
177
+
178
+ Do **not** re-implement what these documents specify. studio's `bag llm` /
179
+ `bag wallet` commands already wrap these flows.
180
+
181
+ ## Read-only inspection (no password needed)
182
+
183
+ The CLI commands that only need the `sk-pv-...` API key (no wallet signature)
184
+ work without prompting for `WALLET_PASSWORD`:
185
+
186
+ ```bash
187
+ bag llm usage [--days 7] # per-key usage, no session
188
+ bag llm test --message "hi" # one-shot LLM smoke (uses key only)
189
+ ```
190
+
191
+ Anything that needs the session_token (balance / deposits / key management)
192
+ will require `WALLET_PASSWORD` to be set in the environment or interactively.
193
+
194
+ ## Hard rules
195
+
196
+ - Wallet private keys are never sent to Pieverse, never logged, and never put in
197
+ error messages. (Deploying to the managed platform is the one case a key leaves
198
+ your machine — it is sent to the operator to sign; self-deploy keeps it local.)
199
+ - `session_token` is memory-only per Pieverse spec — never persist to disk.
200
+ - When in doubt about a topup amount, suggest **smaller** values. Starting at
201
+ $0.5 – $1 is far better than $5+ for a user's first paid run.
202
+ - If the user has not opted into `bag budget enable`, never trigger an
203
+ auto-topup on their behalf — surface the error and let them decide.
@@ -0,0 +1,8 @@
1
+ # Defense-in-depth: secrets and local state must never enter the image.
2
+ # (.env.local is also stashed out of the packaging window by `bag deploy`;
3
+ # wallet material reaches the runtime ONLY via Secrets Manager injection.)
4
+ node_modules/
5
+ dist/
6
+ .env.local
7
+ .studio/
8
+ *.tsbuildinfo