AEON AgentOS Skill — AI Image Generation (x402)
AEON AgentOS is a platform-level execution system for AI Agents. It connects skill invocation, task execution, authorized payment and settlement networks — enabling Agents to not only understand user intent, but to call different skills within the user's authorization scope and complete real-world purchases, content generation, data calls, subscriptions, bookings and transaction execution via wallet and Card payment capabilities.
Currently open skill: AI image generation — generate images from a text prompt, paying per request with USDT on BSC via the x402 HTTP payment protocol.
⚡ Gas Model: BSC USDT does not support EIP-3009. The session key must perform a one-time
approveauthorization (on-chain tx) before the first generation; the actual USDT transfer is executed by the server.
- Prepare (prepare): Single WalletConnect session — funds session key with USDT (and 0.0003 BNB if a fresh approve is needed), then session key broadcasts
ERC20.approve(facilitator, MaxUint256). After this, all subsequent generations are gasless. Also the canonical way to add more USDT later via--topup-amount <n>.- Generate (x402): Pure EIP-712 signature → server submits transfer (server pays gas) → image returned. As a safety net, if
preparewas skipped or funds ran out,create-imagefalls back into the same funding flow.- Withdraw (withdraw): Local wallet sends ERC20 transfer + BNB directly on-chain, requires BNB for gas.
- Gas top-up (gas): Transfers BNB only (used when withdraw reports "No BNB for gas" or additional BNB is needed).
Opening Line (Required)
Whenever entering this skill for the first time, output this opening line:
Let me check the environment first.
Then immediately proceed to "Step 1: Pre-check".
Command Overview
All operations use the global command agentos.
📦 One-time installation:
npm install -g @aeon-ai-pay/agentos@latestUsing the global command instead of
npxavoids 4-5 second cold-start delays. Upgrade:npm update -g @aeon-ai-pay/agentos.
agentos setup --check # Pre-check / auto-create wallet
agentos setup --show # Show configuration
agentos prepare [--topup-amount <usdt>] # Pre-flight (≥1 USDT + facilitator approve); also adds more funds (≥5 USDT) when --topup-amount is supplied
agentos create-image --prompt "<text>" # Generate AI image (x402-paid)
agentos wallet # Check local wallet balance
agentos gas [--amount <bnb>] # Top up BNB for local wallet (WalletConnect, for withdraw)
agentos clean # Uninstall skill, clear cache
agentos withdraw [--to <addr>] [--amount <usdt>] # Withdraw funds
Config is stored in ~/.agentos/config.json (file permissions 600).
Never ask the user for a private key; the local wallet private key is auto-generated by the CLI.
Step 1: Pre-check (Auto Wallet Initialization)
Regardless of user intent, always run first:
agentos setup --check
If agentos is not found (exit code 127 / "command not found"):
- Output to user exactly:
> Installing agentos... - Run (foreground, wait for completion):
npm install -g @aeon-ai-pay/agentos@latest - Re-run
agentos setup --checkand continue normally from the output templates below.
This is a one-time installation — no user confirmation needed, no error to surface.
Auto-Upgrade Handling (applies to every command)
The CLI does a self-version check on every invocation. When it detects a newer version on npm, it synchronously upgrades itself in the foreground (logs [update] @aeon-ai-pay/agentos X → Y, upgrading (foreground)... to stderr), then exits with code 2 and a stderr JSON:
{ "error": "Upgraded ... Rerun the previous command.", "code": "UPDATE_APPLIED", "from": "X", "to": "Y" }
Whenever any agentos command exits with "code": "UPDATE_APPLIED":
- Treat it as a no-op success — the upgrade itself fixed nothing, but also broke nothing.
- Rerun the same command verbatim with the same arguments. The new version is now active.
- Do not relay the upgrade message to the user unless they explicitly asked about updates — surface the result of the rerun instead.
⚠️ Do not keep retrying past one rerun. If the rerun also exits with
UPDATE_APPLIED(extremely unlikely), stop and surface the error.
CLI behavior:
- Reads
~/.agentos/config.json - If
privateKeyis missing → generates a new private key locally withviem.generatePrivateKey()and saves it - Returns JSON:
{ ready, created, mode, address, mainWallet, serviceUrl }
💰 Pricing: per-call USDT is decided by the x402 server (returned in the 402 response). Two thresholds — keep them apart:
- Low-balance threshold = 1 USDT.
prepareonly triggers a funding flow when the session key has < 1 USDT (≈ 50 image generations of headroom at current pricing). Above that, the wallet is treated as ready and the user is not asked to refund.- Top-up minimum = 5 USDT. When a top-up does happen, the user picks from whole-USDT tiers
5/20/50(or a custom value ≥ 5). A single funding lasts a long time; the CLI never asks for "just enough" decimals like0.0104. If a future per-call price ever exceeds 5 USDT, the floor automatically rises to cover it (e.g. an 8 USDT call would offer the20/50tiers only, plus custom ≥ 8).
Output Templates
Always output a progress line first:
> Pre-check in progress...
Branch A: Wallet already exists (ready: true, created: false)
0x0...{last4} Ready. Tell me what image you'd like to generate.
Branch B: Auto-created this time (ready: true, created: true)
Auto-creating your designated wallet...
0x0...{last4} Ready. Tell me what image you'd like to generate.
{last4}is the last 4 characters of the returnedaddress- Pre-check is offline — no on-chain balance queries, no server calls
Edge Cases
| User Question | Response |
|---|---|
| "What's my wallet address?" | Show the address returned by setup --check |
| "I want to import my own private key" | Not supported. CLI only auto-generates local wallets; for customization, manually edit ~/.agentos/config.json |
| "Can I recover my wallet?" | No. The private key is stored locally only; back up the config file before withdrawing funds |
Step 1.5: Pre-flight (Balance Verification + Pre-Authorize)
Always run between Step 1 (setup) and Step 2 (image generation). This step verifies the wallet is funded before the user is asked for a prompt, so the funding QR scan happens up front rather than mid-generation.
agentos prepare
CLI behavior:
- Reads the session key balance and the USDT allowance to the x402 facilitator.
- If balance ≥ 1 USDT and the facilitator is already approved (allowance > 0) → exit
0immediately with{ "ready": true, ... }. No QR, no user interaction. - Otherwise → trigger a single WalletConnect session and, in one user-confirmed flow:
- Transfer the chosen amount of USDT from the main wallet to the session key (only if balance < 1 USDT). The user picks an amount ≥ 5 USDT (tiers
5/20/50or custom). - Transfer 0.0003 BNB for approve gas (only if a fresh approve is needed and the session key has no BNB).
- Session key broadcasts
ERC20.approve(facilitator, MaxUint256)once → confirmed →{ "ready": true, "approveTx": "0x..." }on stdout.
- Transfer the chosen amount of USDT from the main wallet to the session key (only if balance < 1 USDT). The user picks an amount ≥ 5 USDT (tiers
1.5.A — Already prepared (silent)
When the CLI exits ready without doing any funding (balance.topup === null AND approveTx === null in the stdout JSON), it has only verified state. Proceed to Step 2 silently — do not surface anything to the user.
1.5.D — Funding completed (show summary)
When the CLI exits ready after actually funding/approving (i.e. topup !== null OR approveTx !== null in the stdout JSON), display the result to the user using the verbatim template below so the WalletConnect work the user just did is acknowledged in plain text.
Stdout JSON (agentos prepare success after funding):
{
"ready": true,
"address": "0x1e175b01Fa8e06a8541E3f96C304D5D569933b4b",
"initialUsdt": "0",
"usdt": "5",
"bnb": "0.0003",
"allowance": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"topup": "5",
"approveTx": "0xabc...def"
}
Display template (verbatim, variable substitution only):
✅ Wallet prepared
💸 Top-up {initialUsdt} → {usdt} USDT (+{topup})
🔓 Approve {approveTx}
🏦 Balance {usdt} USDT
🔗 Address 0x0...{last4}
Rules:
- Title
✅ Wallet preparedon its own line, then one blank line, then the rows. - Each row: emoji + single space + label padded with spaces to the longest label width here (
Approve/Balance/Address= 7 chars) + two spaces + value. SoTop-up(6 chars) gets one extra padding space before the two-space gap; the others get zero extra padding. - The Top-up row appears only when
balance.topup !== null. Render{initialUsdt} → {usdt} USDT (+{topup})using→(U+2192) with single spaces and a literal+before{topup}. Skip the row entirely whentopup === null. - The Approve row appears only when
approveTx !== null(a fresh approval was broadcast this run). Skip the row entirely whenapproveTx === null. - The Balance row always renders the post-funding USDT balance (
balance.usdt). - The Address row always renders the session-key address truncated to
0x0...{last4}(last 4 hex chars). - If only
approveTxwas performed (no top-up): omit the Top-up row, keep Approve/Balance/Address. - If only top-up was performed (no fresh approve, e.g. user added more funds via
--topup-amount): omit the Approve row, keep Top-up/Balance/Address. - After displaying the summary, proceed to Step 2 (ask the user for the prompt) — do not idle.
1.5.B — Headless top-up required (agent path)
CLI exits with stderr JSON { "code": "TOPUP_REQUIRED", "minTopup": 5, "currentBalance": "...", "address": "...", "presets": [5, 20, 50], "hint": "..." }. Handle exactly the same as Case B.3 in Step 2.2 — show the user the suggested tiers, accept the user's reply as a literal USDT amount, then rerun agentos prepare --topup-amount <usdt>.
1.5.C — Funding rejected / timed out
If WalletConnect signing times out or the user rejects, relay the error and ask whether to retry. Do not auto-retry.
Step 2: Generate Image (with Auto Top-up When Insufficient)
Trigger: User wants to generate / create / draw / render an image.
2.0 Prompt Confirmation
- The user must provide a non-empty prompt describing the image they want.
- If the user has not yet supplied a prompt, ask (verbatim):
What image would you like me to generate? Describe it in a sentence or two.
- Once the user gives a prompt, execute immediately — no second confirmation needed. Proceed to 2.1.
- Actual deduction per generation is decided by the x402 server (returned in the 402 response). Step 1.5 (
agentos prepare) is supposed to have already brought the wallet to a "ready" state (balance ≥ 1 USDT + facilitator approved), so most calls flow straight through. As a safety net,create-imagere-checks balance internally and falls back into the same funding flow if the wallet somehow ended up short — using the same tiered top-up (5/20/50USDT or custom), with the floor raised torequiredUsdtif the per-call price ever exceeds 5 USDT.
2.1 Execute Generation
agentos create-image --prompt "<text>" \
[--aspect-ratio 16:9] \
[--output-format png] \
[--model replicate/black-forest-labs/flux-schnell] \
[--topup-amount <usdt>]
CLI executes the following steps internally:
- Validate prompt and wallet config
- Serialize
{ model, inputs: { prompt, aspect_ratio, output_format } }to JSON, URL-encode it as thebodyquery param - Fetch payment requirements via
GET /open/ai/x402/skillBoss/create?body=<encoded-json>(exact USDT amount via 402 response) - Check allowance → if insufficient and local wallet has no BNB, mark BNB needed
- Check USDT balance → if insufficient (rare, since Step 1.5 should already have funded), decide top-up amount with the same rules
prepareuses:- Floor =
max(5, requiredUsdt)USDT — never a "just enough" decimal - If
--topup-amount <usdt>is supplied (must be ≥ floor) → use it - Else if a TTY is attached (user runs CLI directly) → interactively prompt the user to pick a tier ≥ floor or a custom amount ≥ floor
- Else (headless / agent invocation) → exit with
{ "code": "TOPUP_REQUIRED", "minTopup": <floor>, ... }so the caller can ask the user and rerun with--topup-amount
- Floor =
- If top-up or BNB needed → initiate WalletConnect funding (opens QR page, waits for user to confirm in wallet app, 5-minute timeout)
approveauthorization (on-chain tx, costs small amount of BNB, only on first use or when allowance insufficient)- EIP-712 signature (gasless) → re-send the same
GETURL withPAYMENT-SIGNATUREheader → server submits transfer and returns generated image URLs - CLI downloads each
data.images[].urlto~/agentos-images/(override with--output <dir>) and reads format/size/dimensions
Defaults: aspect-ratio=16:9, output-format=png, model=replicate/black-forest-labs/flux-schnell. Only --prompt is required; pass other flags only when the user explicitly asks for a different aspect ratio / format / model.
Agent invocation pattern: an agent (no TTY) typically runs create-image without --topup-amount first. If the wallet is short, the CLI exits before opening WalletConnect with a TOPUP_REQUIRED JSON on stderr (see Case B.3). The agent must relay the choice to the user, then rerun the same create-image invocation with --topup-amount <usdt> appended.
Output first line:
> Generating image...
⚠️ The create-image command includes an interactive WalletConnect flow (when balance insufficient); it must run in foreground synchronously:
- Do not use
run_in_background: true - Do not kill the process before the user finishes scanning
🔧 If
prepareorcreate-imagewas accidentally run in background and killed: The user's on-chain transaction may already have been sent (USDT actually arrived in local wallet). In this case, do not force another top-up. Instead:
- Run
agentos walletto confirm USDT has arrived- If arrived, re-run the original
agentos create-image --prompt "<text>"(no--topup-amount)- If not arrived (user didn't actually scan), re-run the same command in foreground
2.2 Scenario Branches
Case A: Successful Generation
On HTTP 200, the server returns JSON shaped as:
{
"transaction": "0x...",
"data": { "images": [ { "url": "https://assets.skillboss.co/....png" } ] }
}
The CLI automatically downloads every data.images[].url, then reads each file's format/dimensions/size and emits:
{
"success": true,
"prompt": "<original>",
"aspectRatio": "16:9",
"outputFormat": "png",
"model": "replicate/black-forest-labs/flux-schnell",
"transaction": "0x...",
"images": [
{
"url": "https://...png",
"localPath": "/Users/<user>/agentos-images/<file>.png",
"format": "png",
"width": 1344,
"height": 768,
"sizeBytes": 1016287,
"sizeHuman": "992.4 KB"
}
],
"balance": {
"initial": "0.05",
"before": "5.05",
"after": "4.95",
"charged": 0.1,
"topup": "5"
},
"data": { /* full server payload */ },
"paymentResponse": { "txHash": "0x..." }
}
Display to the user as a key-value list (no fixed-width box, so long paths / tx hashes never overflow). Use this exact form (verbatim, variable substitution only):
✅ Generated
🧩 Powered by Skillboss
📁 Path {localPath}
🎨 Format {format}
📐 Dimensions {width} × {height}
💾 Size {sizeHuman}
🔗 Tx {transaction}
💸 Top-up {initial} → {before} USDT (+{topup})
💰 Charged {before} → {after} USDT (−{charged})
Rules:
- Title
✅ Generatedon its own line, then one blank line, then the rows. - Base layout is 6 fixed rows (Path / Format / Dimensions / Size / Tx, plus the trailing Charged row showing the on-chain settlement). The Top-up row appears only when
balance.topupis non-null (i.e. the user funded the wallet during this call); skip it entirely whenbalance.topupisnull. - Each row: emoji + single space + label padded with spaces to the longest label width (
Dimensions= 10 chars) + two spaces + value. This keeps values visually aligned in monospace fonts. {format}: uppercase the CLI's lowercase value (e.g.png→PNG).{width} × {height}: render with×(U+00D7) and single spaces around it.{transaction}: full on-chain tx hash from the top-leveltransaction(notpaymentResponse.txHash).- Top-up row (only when
balance.topup != null): shows the wallet balance moving frombalance.initialtobalance.beforebecause the user transferred inbalance.topupUSDT. Render as{initial} → {before} USDT (+{topup}). The arrow is U+2192 surrounded by single spaces; the+sign before{topup}is literal. - Charged row: shows the on-chain x402 settlement, where the wallet moves from
balance.before(pre-charge balance) tobalance.after(post-charge balance), the deducted amount isbalance.charged. Render as{before} → {after} USDT (−{charged}). The minus sign is U+2212 (or a regular-); both produce a clear "outflow" reading. Verify visually:before − chargedshould approximately equalafter(small RPC rounding is fine). - If
balance.afterisnull(post-payment balance query failed), render the Charged row as{before} → ? USDT (−{charged})instead. - Multiple images: render one block per image, separated by a blank line; the
Top-up(if any) andChargedrows appear once at the end (not per-image). - Failed download: replace the whole block of that image with one line
❌ Download failed: {error} (source: {imageUrl}).
Cases B / B.1 / B.2 — Funding-flow errors (timeout / rejection / insufficient after funding)
These three branches are short relay-only paths. Full handling rules (the verbatim error string emitted by the CLI and the "do not auto-retry" rule) live in references/error-handling.md. One-line summary for routing:
| Case | Trigger | Action |
|---|---|---|
| B | "Payment approval timed out. Please try again." (5-minute WalletConnect timeout) |
Relay verbatim, ask if retry. Do not auto-retry. |
| B.1 | "Payment approval was rejected. Please try again if you'd like to proceed." (user dismissed in wallet app) |
Relay verbatim. Do not auto-retry. |
| B.2 | "Still insufficient USDT after funding" (main wallet sent less than expected) |
Relay; user can rerun agentos prepare --topup-amount <usdt>. |
Case B.3: Top-up Required (Agent Must Ask User to Choose Amount)
In headless / agent mode (no TTY attached), when the session key cannot pay the call and --topup-amount was not supplied, the CLI exits before opening any WalletConnect QR — emitting on stderr:
{
"error": "USDT balance is below the 1 USDT low-balance threshold; a top-up of ≥ 5 USDT is required. Choose an amount and rerun with --topup-amount <usdt>.",
"code": "TOPUP_REQUIRED",
"threshold": 1,
"minTopup": 5,
"required": 0.1,
"currentBalance": "0.0099",
"address": "0x...",
"presets": [5, 20, 50],
"hint": "Rerun: agentos create-image --prompt \"<text>\" --topup-amount <usdt>"
}
The same JSON shape is also emitted by agentos prepare in Step 1.5.B — handle both the same way.
Action:
Detect
code === "TOPUP_REQUIRED"in stderr.Show the user (verbatim copy template — plain text, no numbered options):
💸 Top up required (minimum {minTopup} USDT, current balance {currentBalance}). Reply with the USDT amount you want to top up. Suggested: {presets joined by " / "} USDT, or any custom amount ≥ {minTopup}.{presets joined by " / "}is thepresetsarray joined by/(e.g.5 / 20 / 50). Ifpresetsis empty (extremely high per-call price), drop the "Suggested" line and ask only for a custom amount ≥minTopup.- Never render the suggestions as numbered/bulleted options like
1) 5 USDT,2) 20 USDT. The user's reply is always the literal USDT amount, never a menu index. If the user replies "1", that means 1 USDT, not "the first preset".
Parse the user's reply as a USDT amount (a positive number). The only validation the agent should do is:
- It parses as a positive number.
- It is ≥
minTopupfrom the CLI JSON. If below, ask again quoting the floor — do not silently bump up.
Rerun the same command that emitted
TOPUP_REQUIRED, with--topup-amount <usdt>appended:# if the request came from prepare (Step 1.5) agentos prepare --topup-amount <usdt> # if the request came from create-image (Step 2.1, fallback path) agentos create-image --prompt "<text>" --topup-amount <usdt>For
create-image, carry over any other flags from the original call (--aspect-ratio,--output-format,--model,--output).From here, the CLI proceeds with the WalletConnect QR flow normally — fall through to Case A on success, Case B on signature timeout/rejection, etc.
⚠️ Do not silently substitute a default top-up amount, auto-pick a preset, or interpret a numeric reply as a 1-based index into the suggestion list.
Cases C / D — Server failure / Empty prompt
Both are short relay-only paths. Full handling in references/error-handling.md:
| Case | Trigger | Action |
|---|---|---|
| C | success: false with non-2xx HTTP status (network / server failure) |
Show raw error, suggest retry or check serviceUrl. |
| D | "Missing --prompt. Provide a non-empty image prompt." |
Ask user for a prompt; never invent one. |
See also:
references/create-image.md— field-level documentation forcreate-imageoutput, CLI flags, pricing model.references/error-handling.md— full treatment of every non-success branch above.
Step 3: Wallet Management
Trigger: User wants to check balance / add more funds / withdraw funds.
| Sub-flow | Command | Where to look |
|---|---|---|
| 3.1 Check local wallet balance | agentos wallet |
references/wallet-ops.md § 1 |
3.2 Add more USDT (any time after prepare) |
agentos prepare --topup-amount <usdt> |
references/wallet-ops.md § 2 — also Step 1.5 above for first-time prepare |
3.3 Withdraw to main wallet (verbatim > Reclaiming funds... template, edge cases) |
agentos withdraw [--to <addr>] [--amount <n>] |
references/wallet-ops.md § 3 |
| 3.4 Top up BNB only (pre-withdraw gas) | agentos gas [--amount <bnb>] |
references/wallet-ops.md § 4 |
The verbatim > Reclaiming funds... template (with To: main wallet (0x0...{last4}) / Status: completed lines) is reproduced inside references/wallet-ops.md; the corresponding entries in this file's Copy Consistency Constraints section remain the regression guardrail.
Decision Routing Overview
| User Intent | Entry Command |
|---|---|
| Any first entry / uncertain state | setup --check |
| View current config / wallet address | setup --show |
| Pre-flight before generation (≥1 USDT + facilitator approve) | prepare |
| Generate AI image | create-image --prompt "<text>" |
| Session key USDT insufficient, top up | prepare --topup-amount <n> |
| Check local wallet balance | wallet |
| Withdraw funds to main wallet | withdraw [--to <addr>] [--amount <n>] |
| Top up BNB for local wallet (pre-withdraw) | gas [--amount <bnb>] |
| Learn about x402 protocol | Read x402-protocol |
Copy Consistency Constraints (Required Reading)
The following key phrases and line-level output templates must be verbatim — no rewording, translation, character additions/removals (including punctuation, spaces, > prefix, and casing):
Line-Level Templates (must be exact)
| Step | Template First Line |
|---|---|
| Pre-check | > Pre-check in progress... |
| Auto-create wallet | Auto-creating your designated wallet... |
| Wallet ready | 0x0...{last4} Ready. Tell me what image you'd like to generate. |
| Generate image | > Generating image... |
| Generation success header | ✅ Generated (followed by blank line + 6 fixed rows + optional Top-up row when balance.topup != null; see Case A) |
| Prepare success header | ✅ Wallet prepared (followed by blank line + Balance/Address rows + optional Top-up/Approve rows; see 1.5.D) |
| Signature timeout | Payment approval timed out. Please try again. |
| Signature rejected | Payment approval was rejected. Please try again if you'd like to proceed. |
| Funding flow | > Funding flow triggered... |
| Withdraw funds | > Reclaiming funds... |
| Withdraw target line | To: main wallet (0x0...{last4}) |
| Withdraw status line | Status: completed |
Key Phrases (must be preserved as-is)
Payment approval timed out. Please try again.Payment approval was rejected. Please try again if you'd like to proceed.Prompt,Image,Tx,Charged,Top-up,Approve,Balance,Address,USDTFrom,To,Amount,Status,completedmain wallet(literal text in the withdraw target line)
Variable Mapping
The full Variable Mapping table — every {placeholder} used in any verbatim template above and the JSON field it resolves to — lives in references/copy-constraints.md. Keep that file open whenever you're rendering one of the ✅ / 💸 / 🏦 lines.
Quick orientation only:
{last4}— last 4 chars of the EVMaddress(always rendered as0x0...{last4}){transaction}— top-leveltransactionfromcreate-image, neverpaymentResponse.txHash{charged}/{topup}/{before}/{after}/{initial}— fields underbalance.*increate-image{initialUsdt}/{approveTx}—prepare-only top-level fields{minTopup}/{currentBalance}/{presets}— fields inside theTOPUP_REQUIREDstderr JSON
Prohibited Deviations
- ❌ Translate to other languages
- ❌ Change casing (e.g., "Generating Image")
- ❌ Abbreviate
- ❌ Add extra decorations (e.g., emoji, bold,
✅) - ❌ Split or merge lines
- ❌ Use synonyms (e.g., replace
insufficientwithnot enough)
Global Prohibited Behaviors
- Never ask the user for a private key; the local wallet is auto-generated by the CLI
- Never execute
create-imageorprepare --topup-amountwithout the user supplying / confirming the input (prompt or amount) - Never log or display the full private key; addresses are displayed as
0x0...last4format - Never skip
setup --checkand directly execute other commands - Never run
prepare/create-image/gas/ any command with a WalletConnect flow in the background (must run in foreground synchronously). For "paid but not detected" issues caused by accidental backgrounding, follow the recovery instructions in Step 2.1 - Do not auto-retry after funding/signature failure; relay the error to the user and stop
- Do not invent prompt content for the user; if no prompt is given, ask