@thirdfy/agent-cli 0.3.0 → 0.4.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 (66) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +59 -19
  3. package/package.json +2 -2
  4. package/scripts/validate-cli-contract-schemas.mjs +6 -0
  5. package/src/cli/errors.mjs +58 -8
  6. package/src/cli/help.mjs +1 -1
  7. package/src/cli/manifest.mjs +62 -6
  8. package/src/cli/options/bootstrap.mjs +15 -3
  9. package/src/cli/options/chains.mjs +5 -0
  10. package/src/cli/options/credits.mjs +9 -0
  11. package/src/cli/options/data.mjs +13 -1
  12. package/src/cli/options/index.mjs +10 -4
  13. package/src/cli/options/walletControl.mjs +8 -0
  14. package/src/commands/bootstrap.mjs +44 -5
  15. package/src/commands/chat.mjs +3 -0
  16. package/src/commands/credits.mjs +109 -16
  17. package/src/commands/delegation.mjs +3 -1
  18. package/src/commands/discovery.mjs +156 -13
  19. package/src/commands/doctor.mjs +5 -2
  20. package/src/commands/execute.mjs +96 -3
  21. package/src/commands/prompt.mjs +5 -3
  22. package/src/commands/wallet.mjs +238 -3
  23. package/src/commands/walletControl.mjs +66 -0
  24. package/src/contracts/cli/schemas/actions.response.schema.json +45 -0
  25. package/src/contracts/cli/schemas/chains-non-evm.response.schema.json +76 -0
  26. package/src/contracts/cli/schemas/chains.response.schema.json +151 -0
  27. package/src/contracts/cli/schemas/error.response.schema.json +61 -0
  28. package/src/contracts/cli/schemas/preview.response.schema.json +179 -0
  29. package/src/contracts/cli/schemas/readiness.response.schema.json +94 -0
  30. package/src/contracts/cli/schemas/wallet-profile.response.schema.json +127 -0
  31. package/src/core/args.mjs +10 -2
  32. package/src/core/chainRegistry.mjs +223 -1
  33. package/src/core/context.mjs +57 -14
  34. package/src/core/errorEnvelope.mjs +209 -0
  35. package/src/core/fundingTokenIn.mjs +24 -1
  36. package/src/core/http.mjs +6 -1
  37. package/src/core/readOnlyActions.mjs +10 -0
  38. package/src/core/walletExecuteChainId.mjs +25 -0
  39. package/src/lib/creditsUsdSummary.mjs +48 -4
  40. package/src/runtime/actions/actionList.mjs +87 -0
  41. package/src/runtime/actions/selection.mjs +17 -22
  42. package/src/runtime/execution/actionPreflight.mjs +50 -14
  43. package/src/runtime/execution/payloads.mjs +36 -28
  44. package/src/runtime/execution/runners.mjs +75 -21
  45. package/src/runtime/execution/svmLane.mjs +106 -0
  46. package/src/runtime/execution/walletControl.mjs +104 -0
  47. package/src/runtime/handlers.mjs +18 -2
  48. package/src/runtime/index.mjs +1 -1
  49. package/src/runtime/main.mjs +21 -5
  50. package/src/runtime/providerHints/apps.mjs +36 -0
  51. package/src/runtime/providerHints/catalogHint.mjs +55 -0
  52. package/src/runtime/providerHints/cex.mjs +130 -0
  53. package/src/runtime/providerHints/data.mjs +144 -0
  54. package/src/runtime/providerHints/dispatch.mjs +177 -0
  55. package/src/runtime/providerHints/dogeos.mjs +38 -0
  56. package/src/runtime/providerHints/earn.mjs +201 -0
  57. package/src/runtime/providerHints/perps.mjs +346 -0
  58. package/src/runtime/providerHints/prediction.mjs +128 -0
  59. package/src/runtime/providerHints/solana.mjs +85 -0
  60. package/src/runtime/providerHints/trading.mjs +78 -0
  61. package/src/runtime/providerHints/walletControl.mjs +39 -0
  62. package/src/runtime/risk/notional.mjs +59 -0
  63. package/src/runtime/risk/orderPreview.mjs +154 -0
  64. package/src/runtime/risk/riskRefusal.mjs +74 -0
  65. package/src/runtime/validation/actionParamsGate.mjs +2 -0
  66. package/src/runtime/providerHints.mjs +0 -1017
package/CHANGELOG.md CHANGED
@@ -4,6 +4,131 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.4.1] - 2026-10-14
8
+
9
+ The last MCP tool with no CLI command (`wallet profile`), who pays for an action (`pricing quote`), owner session tokens for credits, and a parity check that follows thirdfy-mcp's new file layout.
10
+
11
+ ### Added
12
+
13
+ - **`pricing quote` says who would pay** (`wouldBePaidBy`: `agent_credits`, `free_request`, `owner_credits` or `none`; thirdfy-api-v2#887). With an agent key it uses the agent's own quote (`POST /api/v1/agent/credits/quote`, `meta.agentKey` names the agent); with owner auth, `/credits/pricing/quote`, for one of the owner's agents with `--agent-key`. On an API without the agent route (404), it quotes through the owner route only when owner auth is set and the key's agent is known (`--agent-key`, or the configured agent when the key also comes from config); otherwise `OWNER_AUTH_REQUIRED` or `AGENT_IDENTITY_UNAVAILABLE`, never another agent's quote.
14
+ - **`wallet profile`** (thirdfy-mcp `getWalletProfile` parity; it was the last MCP tool with no CLI command). With owner auth: the owner's live onboarding profile (`GET /api/v1/agent/onboarding/cli/status`). With `--agent-api-key`, or with only an agent key in env/config: the agent's execution wallet on `--chain-id` (Base by default, said with `chainIdAssumed`), with `ready`, `fundingReady`, `walletControl`, `nextAction` and the chain's gas note. `wallet list` still reads only the local config. `readiness` and `wallet profile` share one execution-wallet read. An owner flag you pass wins over `--agent-api-key`, as in the MCP; an execution-wallet answer of `success: false` is `WALLET_PROFILE_FAILED`. Schema: `wallet-profile.response.schema.json`; the MCP tool map now has 65 of 67 tools with a CLI command (`health` and `listTools` do not apply). Tests: `test/wallet-profile.test.cjs`.
15
+
16
+ ### Fixed
17
+
18
+ - **Owner session tokens work for `credits balance` and `pricing quote`.** Both read only `--auth-token`; they now take `--owner-session-token` (flag, env or config) too. The agent or owner ledger follows one precedence for both, the same as `wallet profile`: an owner flag you pass, then `--agent-api-key`, then owner auth from env/config, then an agent key from env/config.
19
+ - **`validate:provider-parity` follows thirdfy-mcp's provider split** (thirdfy-mcp#262). It finds the MCP checkout by `src/tools/definitions.ts` and reads `apiProviderIds` from `src/tools/discovery/providerActions/providerIds.ts`. The static hint map in `src/tools/providers/providerHints.ts` spreads per-family maps (`...PERPS_PROVIDER_HINTS`, …): each spread is followed through its import, and its keys are read by importing the MCP module with Node type stripping. A spread it cannot resolve, or one with no keys, fails the check. The older single-file layout still works. Before this, the check failed with "No apiProviderIds list found" against MCP `develop`.
20
+ - **`readiness` (and `wallet profile`) could name a different agent than the API key's.** With an explicit `--agent-api-key`, the agent came from env or config; it now comes only from `--agent-key` or `--user-did`, and without them the command answers `AGENT_IDENTITY_UNAVAILABLE` instead of guessing, as thirdfy-mcp does.
21
+
22
+ ## [0.4.0] - 2026-10-14
23
+
24
+ Data parity with thirdfy-mcp `develop` (`6143bf6`): the CLI answers with the same data the MCP gives an agent. Minor version: new commands, and `--action` no longer resolves to an action whose name merely contains it.
25
+
26
+ ### Added
27
+
28
+ - **Parity guards (Phase 4 of the MCP parity plan):**
29
+ - Response schemas for `preview`, `chains`, `chains --non-evm`, `readiness` and the error fields (`src/contracts/cli/schemas/`), checked by `validate:cli-contract-schemas`. `test/contract-schemas.test.cjs` runs the commands against a stub API and checks their output against the schemas, failures included.
30
+ - `src/contracts/cli/mcp-tool-map.json`: each of the 67 thirdfy-mcp tools, by its real name (`brain_sections`, ...), and its CLI command, or `null` and the reason (`health`, `listTools`, and the `getWalletProfile` gap). `validate:provider-parity` now also fails when an MCP tool is missing from the map or the map lists a tool the MCP dropped; `test/mcp-tool-map.test.cjs` checks every mapped command exists.
31
+ - `.cursor/rules/cli-mcp-parity.mdc` covers response fields, discovery hints and user-facing copy, not only execution routing.
32
+ - Synced with thirdfy-mcp `develop` `6143bf6`: the trading `tokenLookup` hint names every chain `search_tokens` covers and asks for confirmation on `high`, `medium` or `unknown` risk (MCP #248); `preview`'s `order` uses the venue preview's `notionalUsd` when the API sent no `amountUsd` (MCP #249); the credits refusal hint names `credits buy --usd`.
33
+
34
+ - **`readiness`** (thirdfy-mcp `getSelfReadiness` parity): on `agent_wallet`, the agent's execution wallet on the chain with its own key (`ready`, `walletControl`, `nextAction` and `userMessage` at the top, `fundingReady`, the chain's gas note; `READY` / `NOT_READY`); on `self` / `hybrid` / `thirdfy`, the delegation check with owner auth. Base is assumed without `--chain-id`, and the answer says so; a bad `--chain-id` is `INVALID_ARGUMENTS`. `READY` means the wallet can sign; `fundingReady` says whether it holds the funding token. A Solana chain the API does not run is refused, as `run` refuses it. Checked live against thirdfy-mcp `getSelfReadiness` on Base and DogeOS: the same verdict, wallet, wallet control and gas.
35
+ - **`credits buy --usd <n>`**: buys the agent's Thirdfy credits with USDC on Base through `wallet execute --action buy-agent-credits` (thirdfy-mcp #236). The notional check, risk and credits refusal fields of `wallet execute` apply. `--tx-hash` resumes a purchase whose payment was already sent; `--chain-id` picks Base Sepolia on test APIs. It takes only its own options (no `--action` or `--params`).
36
+ - **`help onboarding --path <id>`** returns one onboarding path with `selectedPath` (thirdfy-mcp `describeOnboardingPath`); the `wallet_sign` path lists the whole flow for an agent with its own key: challenge, sign, `agent auth verify`, `agent register --name`.
37
+ - Tests: `test/readiness-credits-onboarding.test.cjs`.
38
+
39
+ - **`actions` paging** (thirdfy-mcp `getAgentActions` / `actionListFilter` / `actionListView` parity): `--search`, `--limit` (page size up to 100 concise or 25 detailed), `--offset` and `--view concise|detailed` return one page with `pagination { total, matched, offset, limit, returned, hasMore }`, `view` and a `hint` for the next `--offset`. Concise entries keep only the fields an agent needs to pick an action. Without these flags `actions` prints the whole list as before. A bad value is `INVALID_ARGUMENTS`, before any API call. Tests: `test/actions-paging.test.cjs`.
40
+
41
+ - **`chains`: chains from the API** (thirdfy-mcp `getChainCapabilities` / `getNonEvmCapabilities` parity). The CLI reads the chain list in the API's capabilities (`chainFamily`, `caip2`, `supportedExecutionLanes`) when it negotiates them, as thirdfy-mcp `parseApiChains` does; the built-in table in `src/core/chainRegistry.mjs` is the fallback (`chainRegistrySource: api | default`). `chains` reports EVM, Solana and catalog chain ids, CAIP-2 ids, execution lanes, each chain's lanes and the Solana contract; `chains --non-evm` reports the Solana contract and whether execution is enabled.
42
+ - On an API that lists no Solana chain, a Solana action fails with `SOLANA_NOT_ENABLED` (`nextAction: use_evm_chain`) before any wallet call, and the Solana provider hints carry an `availability` note.
43
+ - Checked live against thirdfy-mcp `getChainCapabilities`: the chain ids, catalog chains, Solana ids, CAIP-2 map and source match. The CLI's execution lanes are the run modes the API lists, and its default lane is the profile's.
44
+ - Tests: `test/chain-registry-api.test.cjs`.
45
+
46
+ - **Provider hints at thirdfy-mcp parity** (`actions --provider <id>` → `data.providerHint`, the guidance of thirdfy-mcp `getProviderActions`, re-read at thirdfy-mcp `develop` `ebad427`):
47
+ - New hints: DogeOS (`dogeos`, `dogeos-barkswap`, `dogeos-laika`, `dogeos-ecosystem`, `barkswap`, `laika`: testnet, gas in DOGE, native-DOGE swaps and two-hop routes, wrap/unwrap, `LAIKA_TOKEN_MIGRATED`, launch with name and symbol only), the seven Solana families, and the `openmarket` / `openmarket-metrics` provider apps.
48
+ - `tradeRecipes` for Hyperliquid (one-call open with `leverage`, `takeProfitPx`, `stopLossPx`; `HL_TP_SL_LEG_REJECTED`; `update_hyperliquid_isolated_margin`) and Veranta (`update_veranta_margin`), `tokenLookup` for `trading` (`search_tokens`, `get_token_metadata`), and `walletControlNote` where wallet control changes what a provider can do.
49
+ - A provider with no built-in hint (provider apps, CEX venues, any catalog id: `managed-wallet`, `allora`, `governance-v2`, `openclaw`, `wasabi`, `binance`, `kraken`, `okx`, `coinbase`, `thirdfy`, `lifi`) gets a hint built from its catalog rows (`source: actions_catalog_manifest`), as the MCP builds it. Any provider id works with `--provider`.
50
+ - The existing 30 hints take the MCP's current copy: Hyperliquid funding from Base, Arbitrum, Ethereum or Polygon with sponsored gas, withdraw via `withdraw_hyperliquid_cctp_to_base`, Polymarket readiness fields and EU geoblock blockers, Bitfinex spot actions and credential profiles, Hummingbot and Condor blockers. MCP tool names are written as CLI commands (`thirdfy-agent run`, `thirdfy-agent preview`); the CLI keeps its shell-quoted examples.
51
+ - `validate:provider-parity` checks every MCP static hint (45) and `KNOWN_GAPS` is empty. Tests: `test/provider-hints-dispatch.test.cjs`, `test/output-copy.test.cjs`.
52
+ - **Copy test** (`test/output-copy.test.cjs`, the rules of thirdfy-mcp `tests/mcp/modelFacingCopy.test.ts`): no buy-gas advice, Solana never called user-pays, no "Console" in agent-facing hints, and provider hints name CLI commands, not MCP tools.
53
+
54
+ - **`preview`: would Thirdfy sign this write?** The same verdict as thirdfy-mcp `previewOrder` (`src/tools/risk/previewOrder.ts`), from the action-aware execution-wallet preflight, on `agent_wallet`. Nothing is signed, billed or reserved. `data` carries `wouldSign`, `blockedReason` / `blockedStage` / `reason`, `riskRefusal` with a one-line `message`, `amountUsd`, `orderPreview` (`previewKind` `risk` or `spend`), non-blocking `warnings` (`HL_STOP_PAST_LIQUIDATION`), a `note`, `gas`, `venueSetup` (when `runsWithOrder`) and, for an order Thirdfy would sign, `order`: the `run` arguments, one idempotency key and the `command` argv. A blocked order is the answer (exit 0, `code: PREVIEW_BLOCKED`).
55
+ - On a chain whose wallet pays gas (DogeOS 6281971), a wallet that cannot pay it gives `wouldSign: false`, `INSUFFICIENT_NATIVE_GAS`, `blockedStage: gas`, `nextAction: fund_native_gas` and the faucet message. Solana and sponsored EVM chains are never blocked on gas.
56
+ - A value write Thirdfy cannot size (no `--estimated-amount-usd`, no amount from the API) fails with `ESTIMATED_AMOUNT_USD_REQUIRED` / `fix_request`; a read fails with `PREVIEW_ORDER_WRITE_ONLY`.
57
+ - Code: `src/runtime/risk/` (`orderPreview.mjs`, `riskRefusal.mjs`, `notional.mjs`), `chainGasNote` in `src/core/chainRegistry.mjs`. Tests: `test/order-preview.test.cjs`.
58
+ - **Risk-policy refusals are lifted to the top level** on `run`, `wallet execute` and `agent run` (thirdfy-mcp `liftRiskRefusalFields`): `riskRefusal`, `blockedReason`, `blockedStage: risk_policy`, `retryable` (true only for `POLICY_STATE_UNREAD`) and `message`, e.g. `refused by risk policy: maxLeverage 3, order 5`.
59
+ - **`preflight` keeps `orderPreview`, `riskRefusal` and `venueSetup`** from the action preflight. A `risk_policy` or `venue_rules` refusal is `status: blocked`, and an action preflight the API answers 2xx with `success: false` is a block too (it read as ready).
60
+ - **Notional warning** (thirdfy-mcp `NOTIONAL_WARNING`): a write sent by `run` (`agent_wallet`, `thirdfy`), `wallet execute` or `agent run` without `--estimated-amount-usd`, whose catalog row does not say `behavior.requiresEstimatedAmountUsd: false`, gets `meta.warnings`. `THIRDFY_NOTIONAL_ESTIMATE_MODE=refuse` stops it before anything is sent with `ESTIMATED_AMOUNT_USD_REQUIRED`.
61
+
62
+ - **Error fields, as thirdfy-mcp sends them** (`src/core/errorEnvelope.mjs`, ported from thirdfy-mcp `src/mcp/errors/toolErrorEnvelope.ts` and `src/wallet/creditsRefusal.ts`). Every failed command's JSON `data` now also carries `errorCode`, `retryable`, `retryAfterSeconds` (from `Retry-After` or the body, only when retryable), `nextAction` and `hint`. Existing fields (`statusCode`, `blockedReason`, `blockedStage`, `payload`, …) are unchanged, and wallet-control fields still win.
63
+ - `errorCode` is the API's code (`blockedReason` first, then `errorCode`, `error`, `code`), else one for the status (`UNAUTHORIZED`, `RATE_LIMITED`, `UPSTREAM_ERROR`, …), `UPSTREAM_TIMEOUT` / `UPSTREAM_UNREACHABLE` when the API never answered, or the CLI's own code.
64
+ - A write that got a 5xx with no API code is `retryable: false`, `nextAction: reread_state_before_retry`: it may have executed.
65
+ - **Credits refusal:** HTTP 402 `DAILY_FREE_LIMIT_EXCEEDED` / `INSUFFICIENT_CREDITS` / `CREDITS_EXHAUSTED` is `nextAction: add_credits`, not retryable, with a hint naming `credits balance`. `WRITE_OUTCOME_UNKNOWN: CREDITS_CLAIM_PENDING` says the payment may have been sent and not to buy again.
66
+ - **Empty managed read** (`MANAGED_READ_EMPTY_EXECUTE_INTENT`, a read whose execute call timed out): `retryable: true`, `retryAfterSeconds: 3`, `nextAction: retry_later`, as in thirdfy-mcp.
67
+ - Blocked preflights (`INSUFFICIENT_NATIVE_GAS` → `fund_native_gas`, `INSUFFICIENT_FUNDS`, `EXECUTION_WALLET_MISMATCH` → `fix_request`) carry the same fields.
68
+ - A missing or malformed flag (`requireFlag`, `parseJsonFlag`) is `INVALID_ARGUMENTS` / `fix_request`; an API-blocked `preflight` (409 venue, 403 policy, 429 limits) carries the same fields.
69
+ - `docs/command-reference.md` "Error fields" lists every `nextAction`; a test fails when the CLI sends one it does not list. Tests: `test/error-envelope.test.cjs`.
70
+
71
+ - **`wallet-control state|request|submit`**: the owner's actions on a user-owned agent wallet (Thirdfy API 4.0 `/api/v1/agent/:agentKey/wallet-control/*`), with owner auth only (`--owner-session-token` or a Privy `--auth-token`). `request --action update_policy` returns the unsigned Privy request(s); the owner signs `signatureInput` with their own key; `submit --action update_policy --signature-input <json> --signature <sig>` relays it. This is how an owner answers `AGENT_POLICY_UPDATE_REQUIRED` from the CLI (also `stop_agent`, `resume_agent`, `change_payout --payout-address`, `export --recipient-public-key`). The CLI never signs these. `--agent-key` defaults to the configured agent.
72
+
73
+ ### Changed
74
+
75
+ - **Credits are shown in credits only, never as a dollar price per credit or per request** (thirdfy-mcp #252). `credits balance` and `pricing quote` drop every USD field the API adds (`availableUsd`, `retailUsdPerCredit`, `chargedUsd`, `estimatedTotalUsd`, `displayCurrency`, `pricingCurrency`, at any depth), and the balance summary reads `N credits`. Buying credits keeps its USD amount (`credits buy --usd`): that is a purchase, not a request price. `models list` / `models estimate` (chat model pricing) are unchanged, as in the MCP.
76
+ - **Free daily requests and the owner-credits fallback** (thirdfy-api-v2#884, thirdfy-mcp #253). Billing order for the agent's own runs: its credits, then its free requests (20 per UTC day), then the owner's credits, then a refusal.
77
+ - `credits balance` reads the agent's own ledger (`/agent/credits/balance`) with `--agent-api-key`, or with only an agent key in env/config: `freeDailyLimit`, `freeRequestsUsedToday`, `freeRequestsRemainingToday`, `ownerCreditsFallback`, and a `note` saying who pays next. Owner auth still reads the owner's balance.
78
+ - `pricing quote` says `wouldBePaidBy` when the API sends it.
79
+ - The credits refusal hint (`add_credits`) says the agent's credits, today's free requests and the owner's credits are all used, and names `credits buy --usd`.
80
+ - **Solana gas copy follows the API's sponsored fees.** Solana is no longer a user-pays chain in `src/core/chainRegistry.mjs` (no "about 0.003 SOL"); the `INSUFFICIENT_NATIVE_GAS` text, `doctor` gas notes and the `login email` Solana reminder say Thirdfy sponsors network fees, the wallet pays only rent for new token accounts and SOL the action spends, and to give the user the base58 address. The README and command reference no longer say to fund SOL for network fees.
81
+ - **No "Console" in agent-facing hints:** `AGENT_SIGNER_REMOVED_BY_OWNER` and `AGENT_POLICY_UPDATE_REQUIRED` say "their Thirdfy account", as thirdfy-mcp does.
82
+ - `venue setup` / `venue fund` descriptions: setup also runs by itself with the first order; funding steps are writes sent with `run`. `prompt` suggests `actions` → `preview` → `run` on `agent_wallet`.
83
+ - Provider hint `category` follows the MCP for `bitfinex` (`cex_funding_and_spot`), `hummingbot` and `condor` (`cex_executor`); Bitfinex `credentialType` is `api_key`.
84
+
85
+ - **Provider hints moved into `src/runtime/providerHints/`** (move only, no change in output). The 1,017-line `src/runtime/providerHints.mjs` is now one file per provider family plus `dispatch.mjs`. `createProviderHints` is still exported from `src/runtime/index.mjs`. A deep import of `src/runtime/providerHints.mjs` must change to `src/runtime/providerHints/dispatch.mjs`; the package has no `exports` map, so that path could be imported. `validate:provider-parity` reads the provider ids from `STATIC_HINT_PROVIDER_IDS`. Tests: `test/provider-hints-dispatch.test.cjs`.
86
+ - **`--confirm-writes` follows the action's schema** (thirdfy-mcp 0.1.43 `injectSchemaConfirmationIntoParams`). It is copied into each confirmation key the catalog `paramsSchema` lists, and only those: `params.confirmWrites` on Veranta writes, `params.confirm` on the Hyperliquid and Polymarket bridge and capital hops (the CLI never sent `confirm`). A schema that lists neither, such as one that refuses unknown keys, gets nothing (the CLI always added `confirmWrites`); a value already in `--params` wins, and `params.confirm` no longer turns into `confirmWrites`.
87
+ - **`dogeos_prepare_trade` and `dogeos_prepare_token_launch` are reads** (planning steps; thirdfy-api-v2#867, thirdfy-mcp #239), as in the API and the MCP.
88
+ - **The API gets the action name you asked for**, as thirdfy-mcp sends it. `run`, `preflight`, `wallet execute` and `agent run` sent the catalog row's key (`place-hyperliquid-perps-order` for `--action place_hyperliquid_perps_order`, or the row an alias pointed to); they now send `--action` as written, and the CLI's own rules (schema, amounts, chain) still use the catalog row. A Veranta name that matched only an Avantis row is still sent under the Avantis key. The API accepts every spelling, so results don't change; its logs show the name you used. Tests: `test/api-action-name.test.cjs`.
89
+ - **The catalog's `behavior.readOnly` decides read vs write** for a resolved action when the row has it, either way; the name rules and `supportsExecute` metadata are the fallback (`isReadOnlyResolvedAction` in `src/core/readOnlyActions.mjs`, used by `run`, `preflight`, the params gate and action timeouts). Provider app actions are named freely, so only the catalog knows `partner_vault_apy` is a read. Same rule as thirdfy-mcp `isReadOnlyCatalogAction`. Tests: `test/read-only-behavior-precedence.test.cjs`.
90
+ - **Provider app writes (Thirdfy API manifest writes)**, matching thirdfy-mcp (thirdfy/thirdfy-mcp#199):
91
+ - `run` uses the catalog row's `defaultChainId` when no `--chain-id` or `params.chainId` is given (the API sends it for single-chain provider app writes), so a DogeOS-only write is preflighted on DogeOS. Rows without it keep the Base default (and Solana-only rows 101). `inferChainIdFromCatalogRow` in `src/core/walletExecuteChainId.mjs`.
92
+ - The funding preflight checks the token named by the row's `behavior.spend.tokenParam` before the name fallbacks.
93
+ - `AGENT_POLICY_UPDATE_REQUIRED` is a wallet-control refusal: not retryable, with a hint, and `ownerAction` (the owner's `update_policy` request path) in the error envelope `data`.
94
+
95
+ ### Fixed
96
+
97
+ - **A risk refusal on `run` reported `errorCode: FORBIDDEN`** (the HTTP status) when the API nested the refusal under `data`; `errorCode` is now the refusal (`RISK_MAX_LEVERAGE`), as `blockedReason` already was. Found by the new contract test.
98
+ - **`prompt` crashed** with `getEffectiveRunMode is not defined` (a missing import).
99
+ - **An action the agent may not run reached the API when its key came from env or config.** The params gate resolved `--action` against the agent's own policy list (`GET /api/v1/agent/actions`) only with `--agent-api-key`; with `THIRDFY_AGENT_API_KEY` or config `agent.apiKey` it used the public catalog, so `swap` passed and the API refused it with `ACTION_NOT_ALLOWED`. It now uses the same key execution uses, and `run`, `preflight` and `wallet execute` stop such an action as `PARAMS_SCHEMA_INVALID` / `blockedReason: UNKNOWN_ACTION` with `suggestions` before any API write, as thirdfy-mcp does.
100
+ - **`actions` listed the public catalog when the agent key came from env or config.** It used the agent's own list only with `--agent-api-key`, so it showed actions (`swap`, `buy-agent-credits`) that `run` and `preflight` then refused as `UNKNOWN_ACTION`. The lookup now lives in the catalog loader (`getCachedActionsCatalogSnapshot` in `src/core/context.mjs`: flag, `THIRDFY_AGENT_API_KEY`, config), so `actions`, the params gate and `doctor certify` all read the list execution is checked against, as thirdfy-mcp `getAgentActions` does. `meta.policyAware` says a key was sent; the new `meta.policyScoped` says the API answered with the agent policy's allowlist (an unrestricted key gets the whole catalog). A stale key in config now fails `actions` with the API's 401, as `run` does.
101
+ - **`--help` printed the help twice**, and a subcommand's help was followed by the whole root help. A command group run without its subcommand (`thirdfy-agent doctor`) now exits 1, and in `--json` mode answers `INVALID_ARGUMENTS` on stdout, with `command` and `subcommands`, instead of printing nothing. The bare `thirdfy-agent` and `thirdfy-agent help` are help requests and still exit 0.
102
+ - **`--ni` was refused** (`unknown option '--ni'`) although the README and the onboarding help use it. It now means `--not-interactive`, like `-ni`.
103
+ - **Command-line errors carry the error fields.** `UNKNOWN_COMMAND` and `CLI_ARG_ERROR` envelopes now have `errorCode`, `retryable: false` and `nextAction: fix_request`, and `meta.apiBase` is the API base in use (`--api-base`, `--api-base=`, or `THIRDFY_API_BASE`; it was always production). A missing flag (`run` without `--action`) is `code: INVALID_ARGUMENTS` at the top of the envelope, not `CLI_UNHANDLED_ERROR`.
104
+ - **`actions --help` listed about 30 `run` flags** (`--broadcast`, `--skip-preflight`, `--confirm-writes`, …). `actions` now takes only its filters: `--provider`, `--chain-id`, `--catalog`, `--data-category`, plus the auth options.
105
+ - **The README named an env var the CLI never reads** (`AGENT_API_KEY`). It now uses `THIRDFY_AGENT_API_KEY`, which the CLI reads after `--agent-api-key` and before config `agent.apiKey`.
106
+ - **`--action` no longer runs a different action whose name contains it.** A name that is not an action, alias or Veranta/Avantis twin used to resolve to the one catalog action containing it (`swap` → `execute_optimal_swap`), or fail as "Ambiguous" when several did. It is now `UNKNOWN_ACTION` with those actions in `suggestions`, as in thirdfy-mcp.
107
+ - **A write that got a 502, 503 or 504 was sent a second time.** `requestJson` resent any request once on those statuses, writes included, although the first one may have executed. It now resends only requests that are safe to resend (GETs, `validation_only`, calls marked `writeAction: false`), as for timeouts; a write's 5xx comes back with `nextAction: reread_state_before_retry`. thirdfy-mcp never resends a write either.
108
+ - **`validate:provider-parity` checked nothing against thirdfy-mcp.** It read `../thirdfy-mcp/src/tools/getTradingProviderActions.ts`, which moved to `src/tools/discovery/`, and a missing file only printed a warning, so the release preflight passed while the CLI lacked hints for 20 MCP providers. It now finds thirdfy-mcp at `THIRDFY_MCP_DIR`, next to this repo, or next to the main checkout when run from a git worktree, and fails when it is not found (`SKIP_MCP_PARITY=1` skips on purpose). It compares against the MCP's whole `apiProviderIds` list instead of 15 fixed ids. The 20 providers without a CLI hint are listed in `KNOWN_GAPS`; the check fails when a new MCP provider has no hint, or when a listed gap gets one (so the list only shrinks).
109
+
110
+ ## [0.3.2] - 2026-10-10
111
+
112
+ ### Added
113
+
114
+ - **Thirdfy API 4.0 wallet control** (Privy owner/signer split; `PRIVY_WALLET_CONTROL_MODE` off / enforced / user_owned / per_template), shown the way thirdfy-mcp 0.1.19 shows it. Shared code: `src/runtime/execution/walletControl.mjs`.
115
+ - **Refusals.** When the API refuses a write before signing (`AGENT_SIGNER_REMOVED_BY_OWNER`, `AGENT_WALLET_CONTROL_CONTRADICTORY`, `AGENT_WALLET_CONTROL_UNAVAILABLE`, `WITHDRAW_DESTINATION_NOT_OWNER`), the error envelope's `data` carries `walletControlCode`, `retryable`, `hint`, and the API's `walletControl`, `nextAction` and `userMessage` lifted from the response body. `AGENT_SIGNER_REMOVED_BY_OWNER` is not retryable: the owner must re-enable the agent. The API body stays under `data.payload`, and other errors are unchanged.
116
+ - **Preflight.** The managed `agent_wallet` preflight (`preflight`, `run`) reports the API's `walletControl`. When `signerActive` is false it adds `walletControlBlocked`, `walletControlBlocker` (from `walletControl.blockers`) and `nextAction` (`owner_reenable_agent_signer`, `retry_later` or `contact_support`). Solana checks are exempt, as in the API's gate. The API still refuses the write itself.
117
+ - **`login` bootstrap.** New `--owner-payout-address <addr>` (where exits from a controlled wallet may pay) and `--allowed-actions <a,b,...>` (the agent's action template from catalog names). Both go to `bootstrap-self`; the output shows the API's `ownerPayout` and `actionTemplate`, including any `ownerAction` the owner must sign.
118
+ - Parity with thirdfy-mcp 0.1.19 (thirdfy/thirdfy-mcp#179). Tests: `test/wallet-control-parity.test.cjs`.
119
+
120
+ ## [0.3.1] - 2026-10-10
121
+
122
+ ### Added
123
+
124
+ - **Solana on the `agent_wallet` lane.** Chains 101 (mainnet, `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) and 103 (devnet) are in `src/core/chainRegistry.mjs` as family `svm`. A catalog row that runs only on Solana (`supportedChains: [101]`) defaults to chain 101 when no `--chain-id` or `params.chainId` is given; other rows keep the Base 8453 default. On a Solana chain a run mode from the profile, env or config becomes `agent_wallet`; an explicit `--run-mode self|hybrid|thirdfy` (and `wallet sign`, `self-exec`, `wallet submit --chain-id 101`) fails with `SVM_REQUIRES_AGENT_WALLET` before any wallet lookup or execution, so a Solana run never reaches EVM signing. Funding preflight sends the spent mint as `tokenIn` (`inputMint` on `jupiter_swap`, `mint` on `solana_transfer`, Solana USDC on `deposit_polymarket_from_solana` and `deposit_hyperliquid_from_solana`). When the run mode or chain was switched for Solana, `run` output `meta` says so (`runModeDefaultedForSvm`, `chainIdInferred`). `chat --network sol-mainnet|sol-devnet` maps to 101/103. Docs: "Solana (agent_wallet)" in README, `docs/command-reference.md`, `docs/action-inventory-and-compatibility.md` and `llms.txt`. Pin: `test/solana-agent-wallet.test.cjs`.
125
+
126
+ ### Fixed
127
+
128
+ - **`agent_wallet` lowercased the execution wallet address.** The preflight lowercased `executionWalletAddress`, which corrupts a case-sensitive Solana base58 address. The CLI now keeps the address exactly as the API returns it. Expected-wallet checks (`--wallet-address`, read fast path) compare 0x addresses case-insensitively and base58 addresses exactly. EVM addresses now print in the API's case (checksummed) instead of lowercase.
129
+ - **`login email` failed when the agent had only a Solana execution wallet.** It accepted only 0x `executionWallets`; it now accepts base58 Solana addresses too and prints a `solanaFundingReminder` with the address to fund with SOL and SPL tokens.
130
+ - **Native gas fallback text is chain-aware.** When the API reports `gasReady: false` without a `nativeGasWarning.message`, a Solana write now says the wallet needs SOL for network fees (about 0.003 SOL) instead of "0 native gas". The API message still wins when present.
131
+
7
132
  ## [0.3.0] - 2026-10-10
8
133
 
9
134
  ### Removed
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  `@thirdfy/agent-cli` is the standalone command-line product for Thirdfy agent operations.
4
4
  Use it to:
5
5
 
6
- - onboard with **Privy email OTP** (`login email`), onboarding help (`help onboarding`), auth diagnostics (`doctor auth`), and wallet discovery (`wallet list`)
6
+ - onboard with **Privy email OTP** (`login email`), onboarding help (`help onboarding`), auth diagnostics (`doctor auth`), and wallet discovery (`wallet list` for the local profile, `wallet profile` for the live one)
7
7
  - onboard and manage agent keys
8
8
  - discover allowed actions (including policy-aware discovery)
9
9
  - run governance preflight checks
@@ -40,35 +40,46 @@ Run without global install:
40
40
  npx @thirdfy/agent-cli --help
41
41
  ```
42
42
 
43
- ## What's new in v0.3.0
43
+ ## What's new in v0.4.1
44
44
 
45
- - **Removed: the `jeff` command group.** Jeff v1 is deprecated and Thirdfy API 4.0.0 no longer serves `jeff_*` tools. Replace `jeff trade` with `run`, `jeff preflight` with `preflight`, and `jeff status` with `intent-status` (same flags).
46
- - See [CHANGELOG.md](./CHANGELOG.md) and the release notes for older versions.
45
+ - **`wallet profile`:** the live wallet profile from the API. With owner auth, the owner's onboarding profile; with an agent key, the agent's execution wallet with `ready`, wallet control and the next step. Every thirdfy-mcp tool that applies to a CLI now has a command.
46
+ - **`pricing quote` says who would pay:** the agent's credits, a free request, the owner's credits, or nobody (`wouldBePaidBy`), in credits only.
47
+ - **Owner session tokens** work for `credits balance` and `pricing quote`, and both pick the agent or owner ledger the same way as `wallet profile`.
48
+ - **Safer agent identity:** with an explicit `--agent-api-key`, `readiness`, `wallet profile` and `pricing quote` name the agent from `--agent-key`, never a configured agent that may belong to another key.
49
+ - Older versions: see [CHANGELOG.md](./CHANGELOG.md).
47
50
 
48
51
  ## Quick start
49
52
 
50
53
  Hosted solo agents use profile `personal` and `--run-mode agent_wallet` (Path A). Fund the managed **execution wallet**, complete venue trading setup from the catalog when needed, then preflight and run. MetaMask Gator / `delegation *` is not required on this path.
51
54
 
52
55
  ```bash
53
- export AGENT_API_KEY="..."
56
+ export THIRDFY_AGENT_API_KEY="..."
54
57
  export THIRDFY_AUTH_TOKEN="..."
55
58
  export THIRDFY_OWNER_SESSION_TOKEN="..."
56
59
 
57
60
  thirdfy-agent profile init --profile personal --json
58
- thirdfy-agent actions --agent-api-key "$AGENT_API_KEY" --json
61
+ thirdfy-agent actions --agent-api-key "$THIRDFY_AGENT_API_KEY" --json
59
62
  thirdfy-agent wallet list --json
60
63
  # Fund the printed execution wallet address for your target chain (not the owner login wallet).
61
64
 
62
65
  thirdfy-agent preflight \
63
- --agent-api-key "$AGENT_API_KEY" \
66
+ --agent-api-key "$THIRDFY_AGENT_API_KEY" \
64
67
  --run-mode agent_wallet \
65
68
  --action swap \
66
69
  --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' \
67
70
  --estimated-amount-usd 25 \
68
71
  --json
69
72
 
70
- thirdfy-agent run \
73
+ # Would Thirdfy sign it? Verdict, order and risk preview, and the run command. Nothing is sent.
74
+ thirdfy-agent preview \
71
75
  --agent-api-key "$AGENT_API_KEY" \
76
+ --action swap \
77
+ --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' \
78
+ --estimated-amount-usd 25 \
79
+ --json
80
+
81
+ thirdfy-agent run \
82
+ --agent-api-key "$THIRDFY_AGENT_API_KEY" \
72
83
  --run-mode agent_wallet \
73
84
  --action swap \
74
85
  --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' \
@@ -91,7 +102,7 @@ npm run e2e:local-api:smoke
91
102
 
92
103
  ## Auth model
93
104
 
94
- - `AGENT_API_KEY`: execution identity for `preflight`, `run`, `self-exec`, and policy-aware discovery.
105
+ - `THIRDFY_AGENT_API_KEY` (or `--agent-api-key`, or `agent.apiKey` in config): execution identity for `preflight`, `run`, `self-exec`, and policy-aware discovery (`actions` lists the agent's own actions when a key is set).
95
106
  - `THIRDFY_AUTH_TOKEN`: owner/account auth for onboarding and delegation commands.
96
107
  - `THIRDFY_OWNER_SESSION_TOKEN`: wallet-sign owner session for onboarding and owner operations.
97
108
 
@@ -101,7 +112,7 @@ CLI precedence now supports login/config defaults:
101
112
  2. environment variable
102
113
  3. `~/.thirdfy/config.json` (from `thirdfy-agent login` / `config set`)
103
114
 
104
- Execution-only workflows can run with only `AGENT_API_KEY` after onboarding is complete.
115
+ Execution-only workflows can run with only `THIRDFY_AGENT_API_KEY` after onboarding is complete.
105
116
 
106
117
  ### Login / logout / config UX
107
118
 
@@ -114,7 +125,7 @@ thirdfy-agent wallet list --json
114
125
  thirdfy-agent doctor auth --json
115
126
 
116
127
  # Store auth defaults once
117
- thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-api-key "$AGENT_API_KEY" --json
128
+ thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-api-key "$THIRDFY_AGENT_API_KEY" --json
118
129
 
119
130
  # Bootstrap scoped self identity + custody mode in one step
120
131
  thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --run-mode self --custody-mode external --wallet-address "0xYOUR_AGENT_KEY" --json
@@ -220,6 +231,33 @@ thirdfy-agent whoami --json
220
231
 
221
232
  `profile use --profile <name>` resets run mode to that profile default unless `--run-mode` is explicitly provided.
222
233
 
234
+ ### Solana (agent_wallet)
235
+
236
+ Solana actions run only with `--run-mode agent_wallet`, on the managed Solana agent wallet that Thirdfy holds for the agent. `self` and `hybrid` sign EVM transactions only, and `thirdfy` fan-out does not run on Solana. Chain ids: `101` (mainnet, `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) and `103` (devnet).
237
+
238
+ ```bash
239
+ # 1. Log in (or bootstrap). executionWallets lists a base58 Solana address next to any EVM one.
240
+ thirdfy-agent login email you@example.com --code <otp> --accept-terms --json
241
+
242
+ # 2. Look up the Solana execution wallet (read-only).
243
+ thirdfy-agent preflight --chain-id 101 --run-mode agent_wallet --action get_solana_balances --params '{}' --json
244
+
245
+ # 3. Fund the printed base58 executionWalletAddress with the token you trade (USDC). Give the user this base58 address, not the EVM one.
246
+ # Thirdfy sponsors Solana network fees; keep a little SOL only for rent on new token accounts (about 0.002 SOL each).
247
+
248
+ # 4. Quote, then swap with Jupiter.
249
+ thirdfy-agent run --action get_jupiter_quote --params '{"inputMint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","outputMint":"So11111111111111111111111111111111111111112","amount":"5"}' --json
250
+ thirdfy-agent run --action jupiter_swap --params '{"inputMint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","outputMint":"So11111111111111111111111111111111111111112","amount":"5"}' --estimated-amount-usd 5 --json
251
+ ```
252
+
253
+ - An action whose catalog row runs only on Solana (`supportedChains: [101]`) defaults to chain `101` when you pass no `--chain-id`, and a run mode that came from your profile or config switches to `agent_wallet`. An explicit `--run-mode self|hybrid|thirdfy` on Solana fails with `SVM_REQUIRES_AGENT_WALLET` before any wallet lookup or execution.
254
+ - Solana addresses are base58 and case-sensitive. The CLI prints them exactly as the API returns them, and `--wallet-address` must match exactly.
255
+ - Network fees are sponsored. A write stops before execution with `INSUFFICIENT_NATIVE_GAS` when the API reports `gasReady: false`: the wallet is short of the SOL it needs for rent on new token accounts or that the action spends. Ask for SOL only then.
256
+ - Funding checks read the spent mint: `inputMint` on `jupiter_swap`, `mint` on an SPL `solana_transfer`.
257
+ - Providers: Jupiter swaps (`get_jupiter_quote`, `jupiter_swap`), Solana wallet (`get_solana_balances`, `solana_transfer`), Jupiter prediction markets (`get_jupiter_prediction_events`, `get_jupiter_prediction_market`, `get_jupiter_prediction_positions`, `place_jupiter_prediction_order`, `close_jupiter_prediction_position`, `claim_jupiter_prediction_payout`), Jupiter Lend (`get_jupiter_lend_vaults`, `get_jupiter_lend_positions`, `jupiter_lend_deposit`, `jupiter_lend_withdraw`), native staking (`get_solana_staking_info`, `get_solana_staking_positions`, `solana_stake`, `solana_unstake`, `solana_withdraw_stake`), Kamino vaults (`get_kamino_vaults`, `get_kamino_vault_positions`, `kamino_vault_deposit`, `kamino_vault_withdraw`), and Meteora liquidity (`get_meteora_pools`, `get_meteora_positions`, `open_meteora_position`, `remove_meteora_liquidity`, `claim_meteora_fees`). Run `thirdfy-agent actions --chain-id 101 --json` for the live list and each action's params.
258
+ - Bridges into Solana keep an EVM source chain: pass `toChain: 101` and a base58 `toAddress` to `execute_bridge`.
259
+ - Portfolio: `thirdfy-agent analytics portfolio --agent-key <key> --chain-type svm --network-id sol-mainnet --json`.
260
+
223
261
  ## Framework Guidance
224
262
 
225
263
  Use the same execution identities across frameworks, but choose the rail by who signs and who the agent acts for:
@@ -236,7 +274,7 @@ Use `--broadcast` with `run --run-mode self` to keep one deterministic command f
236
274
 
237
275
  ```bash
238
276
  thirdfy-agent run \
239
- --agent-api-key "$AGENT_API_KEY" \
277
+ --agent-api-key "$THIRDFY_AGENT_API_KEY" \
240
278
  --run-mode self \
241
279
  --broadcast \
242
280
  --action swap \
@@ -250,7 +288,7 @@ Hybrid managed-wallet variant (agent_wallet + Thirdfy mirror):
250
288
 
251
289
  ```bash
252
290
  thirdfy-agent run \
253
- --agent-api-key "$AGENT_API_KEY" \
291
+ --agent-api-key "$THIRDFY_AGENT_API_KEY" \
254
292
  --run-mode hybrid \
255
293
  --hybrid-wallet-mode agent_wallet \
256
294
  --action swap \
@@ -292,11 +330,11 @@ Examples:
292
330
 
293
331
  ```bash
294
332
  # Raw/base units
295
- thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap \
333
+ thirdfy-agent run --agent-api-key "$THIRDFY_AGENT_API_KEY" --action swap \
296
334
  --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6,"chainId":8453}' --json
297
335
 
298
336
  # Human units + decimals
299
- thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap \
337
+ thirdfy-agent run --agent-api-key "$THIRDFY_AGENT_API_KEY" --action swap \
300
338
  --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInHuman":"1.5","tokenInDecimals":6,"chainId":8453}' --json
301
339
  ```
302
340
 
@@ -315,14 +353,16 @@ Provider and venue guides are kept outside `README` so this page stays stable as
315
353
  ## Command groups
316
354
 
317
355
  - Discovery: `catalogs list`, `signals` (data provider apps and their signals: fields with units, freshness, roles, credits, attribution; `--provider`, `--role`), `actions` (optional `--provider`, `--chain-id`, `--data-category`; see [`docs/command-reference.md`](./docs/command-reference.md) for trading vs earn vs prediction providers and case-insensitive matching)
318
- - Execution: `preflight`, `run`, `intent-status`
356
+ - Discovery: `actions`, `chains` (chain ids, CAIP-2 and lanes from the API)
357
+ - Readiness and credits: `readiness` (can the agent act on a chain), `credits balance`, `credits buy --usd <n>`
358
+ - Execution: `preflight`, `preview`, `run`, `intent-status`
319
359
  - Polymarket: `polymarket status`, `polymarket setup`, `polymarket dry-run`
320
360
  - Venue tooling: `venue readiness`, `venue setup`, `venue fund` (`polymarket`, `hyperliquid`, `lighter`, `veranta`)
321
361
  - Hummingbot: `hummingbot status`, `connectors`, `executor-schema`, `executor-create`, `executor-stop`, `gateway-networks`, `gateway-connectors`
322
362
  - Condor Pattern A: `condor policy-status`, `condor preflight`
323
363
  - Managed signer execution: `wallet execute`, `wallet sign`, `wallet submit`, `agent run`
324
364
  - Profiles: `profile init`, `profile use`, `profile show`, `whoami`
325
- - Onboarding: `login email`, `help onboarding`, `doctor auth`, `wallet list`, `bootstrap begin`, `bootstrap complete`, `onboarding begin`, `onboarding complete`, `agent auth challenge`, `agent auth verify`, `agent register`, `agent key rotate`, `agent key revoke`, `developer bootstrap`
365
+ - Onboarding: `login email`, `help onboarding`, `doctor auth`, `wallet list`, `bootstrap begin`, `bootstrap complete`, `onboarding begin`, `onboarding complete`, `agent auth challenge`, `agent auth verify`, `agent register`, `agent key rotate`, `agent key revoke`, `wallet-control state|request|submit` (owner), `developer bootstrap`
326
366
  - User wallet authorization (Gator): `delegation create`, `delegation activate`, `delegation status`, `credentials upsert`, `credentials status`
327
367
  - Gator operations: `delegation show`, `delegation inspect`, `delegation revoke`
328
368
  - Gator execution helpers: `delegation balance`, `delegation redeem` (daily-cap caveats via `--max-usd-per-day`)
@@ -392,7 +432,7 @@ thirdfy-agent delegation balance \
392
432
 
393
433
  thirdfy-agent delegation redeem \
394
434
  --run-mode thirdfy \
395
- --agent-api-key "$AGENT_API_KEY" \
435
+ --agent-api-key "$THIRDFY_AGENT_API_KEY" \
396
436
  --action swap \
397
437
  --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' \
398
438
  --estimated-amount-usd 5 \
@@ -430,7 +470,7 @@ thirdfy-agent managed wallet init --auth-token "$THIRDFY_AUTH_TOKEN" --json
430
470
  # or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
431
471
  thirdfy-agent managed wallet grant --auth-token "$THIRDFY_AUTH_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --token-address "0x..." --max-usd-per-day 250 --json
432
472
  # or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
433
- thirdfy-agent agent run --agent-api-key "$AGENT_API_KEY" --signer-method fanout_intent --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' --json
473
+ thirdfy-agent agent run --agent-api-key "$THIRDFY_AGENT_API_KEY" --signer-method fanout_intent --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' --json
434
474
  ```
435
475
 
436
476
  ### Signer command model (managed + BYOW)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "test": "node --test test/*.test.cjs",
26
- "lint": "node --check ./bin/thirdfy-agent.mjs && node --check ./src/core/*.mjs && node --check ./src/commands/*.mjs && node --check ./src/commands/signals/*.mjs && node --check ./src/cli/*.mjs && node --check ./src/cli/options/*.mjs && node --check ./src/runtime/*.mjs && node --check ./src/runtime/execution/*.mjs && node --check ./src/runtime/actions/*.mjs && node --check ./src/runtime/validation/*.mjs && node --check ./src/runtime/certification/*.mjs",
26
+ "lint": "node --check ./bin/thirdfy-agent.mjs && node --check ./src/core/*.mjs && node --check ./src/commands/*.mjs && node --check ./src/commands/signals/*.mjs && node --check ./src/cli/*.mjs && node --check ./src/cli/options/*.mjs && node --check ./src/runtime/*.mjs && node --check ./src/runtime/execution/*.mjs && node --check ./src/runtime/actions/*.mjs && node --check ./src/runtime/validation/*.mjs && node --check ./src/runtime/certification/*.mjs && node --check ./src/runtime/providerHints/*.mjs && node --check ./src/runtime/risk/*.mjs",
27
27
  "validate:cli-manifest-handlers": "node ./scripts/validate-cli-manifest-handlers.mjs",
28
28
  "validate:runtime-boundaries": "node ./scripts/validate-runtime-boundaries.mjs",
29
29
  "validate:npm-pack": "node ./scripts/validate-npm-pack.mjs",
@@ -10,6 +10,12 @@ const REQUIRED_FILES = [
10
10
  'execute-intent.response.schema.json',
11
11
  'intent-status.response.schema.json',
12
12
  'signals.response.schema.json',
13
+ 'preview.response.schema.json',
14
+ 'chains.response.schema.json',
15
+ 'chains-non-evm.response.schema.json',
16
+ 'readiness.response.schema.json',
17
+ 'error.response.schema.json',
18
+ 'wallet-profile.response.schema.json',
13
19
  ];
14
20
 
15
21
  const REQUIRED_KEYS = ['$schema', 'title', 'type'];
@@ -12,16 +12,64 @@ function resolveJsonMode(program, getJsonMode) {
12
12
  }
13
13
  }
14
14
 
15
+ /**
16
+ * The command group named on the command line, followed down from the root (`['venue']` for
17
+ * `thirdfy-agent venue --json`), and the subcommands it has.
18
+ */
19
+ function namedGroup(program, argv) {
20
+ const path = [];
21
+ let current = program;
22
+ for (const arg of argv.slice(2)) {
23
+ const next = current.commands.find((cmd) => cmd.name() === arg || cmd.aliases().includes(arg));
24
+ if (!next) continue;
25
+ path.push(next.name());
26
+ current = next;
27
+ }
28
+ return { path, subcommands: current.commands.map((cmd) => cmd.name()) };
29
+ }
30
+
31
+ /**
32
+ * True when Commander stopped because a command group ran without its subcommand (`thirdfy-agent
33
+ * doctor`): an error, exit 1. The bare root and the `help` group (Commander's own help is off so
34
+ * `help onboarding` works) are help requests and exit 0, as before.
35
+ */
36
+ export function isMissingSubcommand(program, err, argv = process.argv) {
37
+ if (err?.code !== 'commander.help' || Number(err.exitCode ?? 0) !== 1) return false;
38
+ const { path } = namedGroup(program, argv);
39
+ return path.length > 0 && path[0] !== 'help';
40
+ }
41
+
15
42
  export function configureCommanderErrors(program, deps) {
16
- const { getJsonMode, printEnvelope, CLI_VERSION, getApiBase, collectRootFlags } = deps;
43
+ const { getJsonMode, printEnvelope, CLI_VERSION, getApiBase } = deps;
44
+
45
+ // The error fields every failed command carries (src/core/errorEnvelope.mjs): a bad command line is
46
+ // fixed by the caller, never retried.
47
+ const argErrorFields = (errorCode) => ({ errorCode, retryable: false, nextAction: 'fix_request' });
48
+ // Commander fails before the context is built, so resolve the API base as the context would:
49
+ // `--api-base`, then THIRDFY_API_BASE, then the default.
50
+ const meta = () => ({
51
+ version: CLI_VERSION,
52
+ apiBase: String(collectVersionFlagsFromArgv(process.argv).apiBase || process.env.THIRDFY_API_BASE || getApiBase()).replace(/\/+$/, ''),
53
+ });
17
54
 
18
55
  program.exitOverride((err) => {
19
56
  const jsonMode = resolveJsonMode(program, getJsonMode);
20
57
 
58
+ // Commander has already printed the help when it raises these; printing it again showed it twice
59
+ // (and the root help after a subcommand's).
21
60
  if (err.code === 'commander.help' || err.code === 'commander.helpDisplayed') {
22
- const rootFlags = typeof collectRootFlags === 'function' ? collectRootFlags(program) : {};
23
- if (!rootFlags.version) {
24
- program.outputHelp();
61
+ // A group run without its subcommand (`doctor`) is an error (exit 1), not a help request: in
62
+ // --json mode answer with an envelope on stdout; the help itself went to stderr.
63
+ if (jsonMode && isMissingSubcommand(program, err)) {
64
+ const { path, subcommands } = namedGroup(program, process.argv);
65
+ const command = `thirdfy-agent ${path.join(' ')}`;
66
+ printEnvelope({
67
+ success: false,
68
+ code: 'INVALID_ARGUMENTS',
69
+ message: `\`${command}\` needs a subcommand: ${subcommands.join(', ')}.`,
70
+ data: { ...argErrorFields('INVALID_ARGUMENTS'), command, subcommands },
71
+ meta: meta(),
72
+ });
25
73
  }
26
74
  throw err;
27
75
  }
@@ -32,8 +80,8 @@ export function configureCommanderErrors(program, deps) {
32
80
  success: false,
33
81
  code: 'UNKNOWN_COMMAND',
34
82
  message: err.message || 'Unknown command',
35
- data: {},
36
- meta: { version: CLI_VERSION, apiBase: getApiBase() },
83
+ data: { ...argErrorFields('UNKNOWN_COMMAND'), hint: 'Run `thirdfy-agent --help` to list commands.' },
84
+ meta: meta(),
37
85
  });
38
86
  }
39
87
  throw err;
@@ -52,8 +100,8 @@ export function configureCommanderErrors(program, deps) {
52
100
  success: false,
53
101
  code: 'CLI_ARG_ERROR',
54
102
  message: err.message || 'Invalid CLI arguments',
55
- data: { commanderCode: err.code },
56
- meta: { version: CLI_VERSION, apiBase: getApiBase() },
103
+ data: { commanderCode: err.code, ...argErrorFields('INVALID_ARGUMENTS') },
104
+ meta: meta(),
57
105
  });
58
106
  }
59
107
  throw err;
@@ -74,6 +122,8 @@ export function collectVersionFlagsFromArgv(argv = process.argv) {
74
122
  if (argv.includes('--json')) flags.json = true;
75
123
  const apiIdx = argv.indexOf('--api-base');
76
124
  if (apiIdx >= 0 && argv[apiIdx + 1]) flags.apiBase = argv[apiIdx + 1];
125
+ const apiInline = argv.find((arg) => arg.startsWith('--api-base='));
126
+ if (!flags.apiBase && apiInline) flags.apiBase = apiInline.slice('--api-base='.length);
77
127
  const envIdx = argv.indexOf('--env');
78
128
  if (envIdx >= 0 && argv[envIdx + 1]) flags.env = argv[envIdx + 1];
79
129
  return flags;
package/src/cli/help.mjs CHANGED
@@ -79,7 +79,7 @@ Core commands:
79
79
  thirdfy-agent credits usage --auth-token <token> [--range 7d|30d|90d] [--json]
80
80
  thirdfy-agent models list --auth-token <token> [--json]
81
81
  thirdfy-agent models estimate --auth-token <token> --model <key> [--input-tokens <n>] [--output-tokens <n>] [--json]
82
- thirdfy-agent pricing quote --auth-token <token> --action <action-key> [--chain-id <id>] [--json]
82
+ thirdfy-agent pricing quote --action <action-key> [--owner-session-token <t> | --auth-token <t> [--agent-key <0x...>] | --agent-api-key <key>] [--chain-id <id>] [--json]
83
83
 
84
84
  Global flags:
85
85
  --api-base <url> default: https://api.thirdfy.com
@@ -71,16 +71,23 @@ export const CLI_MANIFEST = [
71
71
  {
72
72
  path: ['help', 'onboarding'],
73
73
  handler: 'commandHelpOnboarding',
74
- options: ['chains'],
74
+ options: ['chains', 'onboardingHelp'],
75
75
  description: 'Structured onboarding help (JSON envelope)',
76
76
  },
77
77
  {
78
78
  path: ['onboarding', 'help'],
79
79
  handler: 'commandHelpOnboarding',
80
- options: ['chains'],
80
+ options: ['chains', 'onboardingHelp'],
81
81
  description: 'Structured onboarding help (JSON envelope)',
82
82
  },
83
- { path: ['wallet', 'list'], handler: 'commandWalletList', description: 'List configured wallets' },
83
+ { path: ['wallet', 'list'], handler: 'commandWalletList', description: 'List configured wallets (local profile config only; `wallet profile` asks the API)' },
84
+ {
85
+ path: ['wallet', 'profile'],
86
+ handler: 'commandWalletProfile',
87
+ tier: 'online',
88
+ options: ['auth', 'chains'],
89
+ description: "Live wallet profile from the API: the owner's onboarding profile with owner auth, or the agent's execution wallet with an agent key",
90
+ },
84
91
  // polymarket (offline wrappers)
85
92
  {
86
93
  path: ['polymarket', 'status'],
@@ -112,7 +119,7 @@ export const CLI_MANIFEST = [
112
119
  path: ['venue', 'setup'],
113
120
  handler: 'commandVenueSetup',
114
121
  options: ['auth', 'venue'],
115
- description: 'Prepare or complete venue onboarding via shared API contract',
122
+ description: 'Prepare or complete venue onboarding. Thirdfy also runs the one-time setup by itself with the first order (no funds move)',
116
123
  args: { query: '<venue>' },
117
124
  tier: 'online',
118
125
  },
@@ -120,7 +127,7 @@ export const CLI_MANIFEST = [
120
127
  path: ['venue', 'fund'],
121
128
  handler: 'commandVenueFund',
122
129
  options: ['auth', 'venue'],
123
- description: 'Return venue funding plan (does not move funds implicitly)',
130
+ description: 'Return the venue funding plan. Nothing moves here: each funding step is a write you send with `run` and confirm',
124
131
  args: { query: '<venue>' },
125
132
  tier: 'online',
126
133
  },
@@ -207,6 +214,13 @@ export const CLI_MANIFEST = [
207
214
  options: ['auth'],
208
215
  description: 'Credits balance',
209
216
  },
217
+ {
218
+ path: ['credits', 'buy'],
219
+ handler: 'commandCreditsBuy',
220
+ tier: 'online',
221
+ options: ['auth', 'creditsBuy'],
222
+ description: "Buy the agent's Thirdfy credits with USDC from its wallet on Base (gas sponsored): --usd <whole dollars>",
223
+ },
210
224
  {
211
225
  path: ['credits', 'transactions'],
212
226
  handler: 'commandCreditsTransactions',
@@ -519,6 +533,27 @@ export const CLI_MANIFEST = [
519
533
  options: ['auth'],
520
534
  description: 'Rotate agent API key',
521
535
  },
536
+ {
537
+ path: ['wallet-control', 'state'],
538
+ handler: 'commandWalletControlState',
539
+ tier: 'online',
540
+ options: ['auth'],
541
+ description: 'Owner: read the user-owned agent wallet state',
542
+ },
543
+ {
544
+ path: ['wallet-control', 'request'],
545
+ handler: 'commandWalletControlRequest',
546
+ tier: 'online',
547
+ options: ['auth', 'walletControl'],
548
+ description: 'Owner: get the unsigned request for an owner action (update_policy, stop_agent, resume_agent, change_payout, export)',
549
+ },
550
+ {
551
+ path: ['wallet-control', 'submit'],
552
+ handler: 'commandWalletControlSubmit',
553
+ tier: 'online',
554
+ options: ['auth', 'walletControl'],
555
+ description: 'Owner: relay the signed owner request',
556
+ },
522
557
  {
523
558
  path: ['agent', 'key', 'revoke'],
524
559
  handler: 'commandAgentKeyRevoke',
@@ -602,7 +637,7 @@ export const CLI_MANIFEST = [
602
637
  path: ['actions'],
603
638
  handler: 'commandActions',
604
639
  tier: 'online',
605
- options: ['auth', 'execution', 'dataCategory'],
640
+ options: ['auth', 'actionList', 'dataCategory'],
606
641
  description: 'List allowed actions',
607
642
  },
608
643
  {
@@ -612,6 +647,20 @@ export const CLI_MANIFEST = [
612
647
  options: ['signals'],
613
648
  description: 'List data provider apps and the signals they publish (fields, units, freshness, roles, credits)',
614
649
  },
650
+ {
651
+ path: ['readiness'],
652
+ handler: 'commandReadiness',
653
+ tier: 'online',
654
+ options: ['auth', 'chains'],
655
+ description: 'Can the agent act? agent_wallet: its execution wallet, funding, gas and wallet control; self/hybrid/thirdfy: the delegation check',
656
+ },
657
+ {
658
+ path: ['chains'],
659
+ handler: 'commandChains',
660
+ tier: 'online',
661
+ options: ['chainList'],
662
+ description: 'Chains Thirdfy acts on (from the API): chain ids, CAIP-2 ids, execution lanes and the Solana contract',
663
+ },
615
664
  {
616
665
  path: ['preflight'],
617
666
  handler: 'commandPreflight',
@@ -619,6 +668,13 @@ export const CLI_MANIFEST = [
619
668
  options: ['auth', 'execution'],
620
669
  description: 'Governance preflight',
621
670
  },
671
+ {
672
+ path: ['preview'],
673
+ handler: 'commandPreview',
674
+ tier: 'online',
675
+ options: ['auth', 'execution'],
676
+ description: 'Preview a write (agent_wallet): would Thirdfy sign it, order and risk preview, and the run command. Nothing is sent',
677
+ },
622
678
  { path: ['run'], handler: 'commandRun', tier: 'online', options: ['auth', 'execution'], description: 'Execute intent' },
623
679
  {
624
680
  path: ['self-exec'],