@thirdfy/agent-cli 0.4.0 → 0.4.2

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 (64) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +5 -8
  3. package/package.json +3 -3
  4. package/scripts/validate-cli-contract-schemas.mjs +1 -0
  5. package/src/cli/help.mjs +1 -1
  6. package/src/cli/manifest.mjs +8 -1
  7. package/src/cli/options/index.mjs +11 -11
  8. package/src/cli/program.mjs +1 -1
  9. package/src/commands/{agent.mjs → account/agent.mjs} +5 -5
  10. package/src/commands/{credits.mjs → account/credits.mjs} +73 -17
  11. package/src/commands/{wallet.mjs → account/wallet.mjs} +136 -21
  12. package/src/commands/{walletControl.mjs → account/walletControl.mjs} +3 -3
  13. package/src/commands/discovery.mjs +9 -9
  14. package/src/commands/execute.mjs +2 -2
  15. package/src/commands/index.mjs +9 -9
  16. package/src/commands/{bootstrap.mjs → onboarding/bootstrap.mjs} +8 -8
  17. package/src/commands/{consoleMandate.mjs → onboarding/consoleMandate.mjs} +2 -2
  18. package/src/commands/{credentials.mjs → onboarding/credentials.mjs} +6 -6
  19. package/src/commands/{delegation.mjs → onboarding/delegation.mjs} +5 -5
  20. package/src/commands/{avantis.mjs → venues/avantis.mjs} +1 -1
  21. package/src/commands/{condor.mjs → venues/condor.mjs} +4 -4
  22. package/src/commands/{hummingbot.mjs → venues/hummingbot.mjs} +4 -4
  23. package/src/commands/{hyperliquid.mjs → venues/hyperliquid.mjs} +1 -1
  24. package/src/commands/{lighter.mjs → venues/lighter.mjs} +1 -1
  25. package/src/commands/{polymarket.mjs → venues/polymarket.mjs} +4 -4
  26. package/src/commands/{venue.mjs → venues/venue.mjs} +3 -3
  27. package/src/contracts/cli/schemas/wallet-profile.response.schema.json +127 -0
  28. package/src/core/context.mjs +20 -0
  29. package/src/core/earnclawAttribution.mjs +1 -1
  30. package/src/core/{actionTimeouts.mjs → execution/actionTimeouts.mjs} +2 -2
  31. package/src/core/{walletExecuteChainId.mjs → execution/walletExecuteChainId.mjs} +1 -1
  32. package/src/core/http.mjs +1 -1
  33. package/src/runtime/execution/hostedRuntimeWriteGate.mjs +1 -1
  34. package/src/runtime/execution/payloads.mjs +4 -4
  35. package/src/runtime/execution/runners/agentWalletLane.mjs +109 -0
  36. package/src/runtime/execution/runners/createExecutionRunners.mjs +315 -0
  37. package/src/runtime/execution/runners/fallbackHints.mjs +54 -0
  38. package/src/runtime/execution/runners/hybridLane.mjs +88 -0
  39. package/src/runtime/execution/runners/managedPreflight.mjs +253 -0
  40. package/src/runtime/execution/runners/managedResponse.mjs +119 -0
  41. package/src/runtime/execution/runners/selfLane.mjs +232 -0
  42. package/src/runtime/execution/runners/thirdfyLane.mjs +97 -0
  43. package/src/runtime/execution/svmLane.mjs +1 -1
  44. package/src/runtime/handlers.mjs +17 -16
  45. package/src/runtime/main.mjs +1 -1
  46. package/src/runtime/risk/notional.mjs +1 -1
  47. package/src/runtime/validation/actionParamsGate.mjs +1 -1
  48. package/src/runtime/execution/runners.mjs +0 -1134
  49. /package/src/cli/options/{agent.mjs → account/agent.mjs} +0 -0
  50. /package/src/cli/options/{credits.mjs → account/credits.mjs} +0 -0
  51. /package/src/cli/options/{wallet.mjs → account/wallet.mjs} +0 -0
  52. /package/src/cli/options/{walletControl.mjs → account/walletControl.mjs} +0 -0
  53. /package/src/cli/options/{bootstrap.mjs → onboarding/bootstrap.mjs} +0 -0
  54. /package/src/cli/options/{credentials.mjs → onboarding/credentials.mjs} +0 -0
  55. /package/src/cli/options/{delegation.mjs → onboarding/delegation.mjs} +0 -0
  56. /package/src/cli/options/{condor.mjs → venues/condor.mjs} +0 -0
  57. /package/src/cli/options/{hummingbot.mjs → venues/hummingbot.mjs} +0 -0
  58. /package/src/cli/options/{polymarket.mjs → venues/polymarket.mjs} +0 -0
  59. /package/src/cli/options/{venue.mjs → venues/venue.mjs} +0 -0
  60. /package/src/core/{executionContext.mjs → execution/executionContext.mjs} +0 -0
  61. /package/src/core/{fundingTokenIn.mjs → execution/fundingTokenIn.mjs} +0 -0
  62. /package/src/core/{hostedRuntimeIdempotency.mjs → execution/hostedRuntimeIdempotency.mjs} +0 -0
  63. /package/src/core/{readOnlyActions.mjs → execution/readOnlyActions.mjs} +0 -0
  64. /package/src/core/{signedTx.mjs → execution/signedTx.mjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.4.2] - 2026-10-14
8
+
9
+ A cleanup release: two small `wallet profile` / `pricing quote` fixes from the 0.4.1 review, and source files moved into domain folders. Commands answer as in 0.4.1, apart from the two fixes.
10
+
11
+ ### Fixed
12
+
13
+ - **`wallet profile` and `readiness` report the agent key in lowercase** whether it came from `--agent-key` or from env/config. Before, a checksummed config key was echoed as stored, so one agent could appear with two spellings.
14
+ - **`pricing quote` on an API without the agent quote route** names the agent only from `--agent-key`. The branch that took the configured agent could not be reached (a stored owner token already puts the quote on the owner ledger) and is removed.
15
+
16
+ ### Changed
17
+
18
+ - **Source files moved into domain subfolders (no behavior change).** The `thirdfy-agent` command is unchanged. Code that deep-imports package files must use the new paths:
19
+ - `src/runtime/execution/runners.mjs` → `src/runtime/execution/runners/` (one file per lane; `createExecutionRunners` is in `createExecutionRunners.mjs`).
20
+ - `src/core/{actionTimeouts,executionContext,fundingTokenIn,hostedRuntimeIdempotency,readOnlyActions,signedTx,walletExecuteChainId}.mjs` → `src/core/execution/`.
21
+ - `src/commands/{avantis,condor,hummingbot,hyperliquid,lighter,polymarket,venue}.mjs` → `src/commands/venues/`; `{bootstrap,consoleMandate,credentials,delegation}.mjs` → `src/commands/onboarding/`; `{agent,credits,wallet,walletControl}.mjs` → `src/commands/account/`.
22
+ - `src/cli/options/` uses the same `venues/`, `onboarding/` and `account/` subfolders.
23
+ - `src/core/index.mjs` and `src/commands/index.mjs` export the same names as before.
24
+ - `wallet profile` uses the same owner/agent precedence code as the credits commands (`resolveCreditsLedger`) instead of its own copy. Same behavior.
25
+ - `validate:provider-parity` names the files it looked in when it finds no MCP provider-hints file.
26
+
27
+ ## [0.4.1] - 2026-10-14
28
+
29
+ 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.
30
+
31
+ ### Added
32
+
33
+ - **`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.
34
+ - **`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`.
35
+
36
+ ### Fixed
37
+
38
+ - **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.
39
+ - **`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`.
40
+ - **`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.
41
+
7
42
  ## [0.4.0] - 2026-10-14
8
43
 
9
44
  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.
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,14 +40,11 @@ Run without global install:
40
40
  npx @thirdfy/agent-cli --help
41
41
  ```
42
42
 
43
- ## What's new in v0.4.0
43
+ ## What's new in v0.4.2
44
44
 
45
- - **`preview`:** asks whether Thirdfy would sign a write before anything is sent: the verdict, the order and risk preview, the risk-policy refusal and the `run` command to place it.
46
- - **New commands:** `readiness` (can the agent act on this chain), `chains` (chains, CAIP-2 ids and lanes from the API), `credits buy --usd <n>`, and `actions --search/--limit/--offset/--view` for one page of the catalog.
47
- - **Error fields on every failure:** `errorCode`, `retryable`, `retryAfterSeconds`, `nextAction` and `hint`, the same as thirdfy-mcp. A write that got a 5xx is no longer sent twice.
48
- - **Credits in credits only, and who pays next:** `credits balance` and `pricing quote` show no USD price per credit or request. With an agent key, `credits balance` reads the agent's own ledger: its free requests left today (20 per UTC day), the owner-credits fallback, and a note on who pays next.
49
- - **Provider hints at MCP parity:** DogeOS, Solana and provider-app hints, perps trade recipes, token lookup and wallet-control notes. Solana fees are sponsored.
50
- - **Behavior changes:** `--action` matches only an action, alias or Veranta/Avantis twin (no partial names), and is checked against the agent's own allowed actions; `--confirm-writes` goes only into the confirmation keys an action's schema lists.
45
+ - **One spelling per agent:** `wallet profile` and `readiness` report the agent key in lowercase, whether it came from `--agent-key` or from your config.
46
+ - **`pricing quote` on an older API** (no agent quote route) names the agent only from `--agent-key`.
47
+ - **Reorganized source** (no behavior change): source files moved into domain folders. If your code imports files from inside the package, see [CHANGELOG.md](./CHANGELOG.md) for the new paths; the `thirdfy-agent` command is unchanged.
51
48
  - Older versions: see [CHANGELOG.md](./CHANGELOG.md).
52
49
 
53
50
  ## Quick start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,8 +22,8 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "scripts": {
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 && node --check ./src/runtime/providerHints/*.mjs && node --check ./src/runtime/risk/*.mjs",
25
+ "test": "node --test test/*/*.test.cjs test/*/*/*.test.cjs",
26
+ "lint": "node --check ./bin/thirdfy-agent.mjs && node --check ./src/core/*.mjs && node --check ./src/core/execution/*.mjs && node --check ./src/commands/*.mjs && node --check ./src/commands/venues/*.mjs && node --check ./src/commands/onboarding/*.mjs && node --check ./src/commands/account/*.mjs && node --check ./src/commands/signals/*.mjs && node --check ./src/cli/*.mjs && node --check ./src/cli/options/*.mjs && node --check ./src/cli/options/venues/*.mjs && node --check ./src/cli/options/onboarding/*.mjs && node --check ./src/cli/options/account/*.mjs && node --check ./src/runtime/*.mjs && node --check ./src/runtime/execution/*.mjs && node --check ./src/runtime/execution/runners/*.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",
@@ -15,6 +15,7 @@ const REQUIRED_FILES = [
15
15
  'chains-non-evm.response.schema.json',
16
16
  'readiness.response.schema.json',
17
17
  'error.response.schema.json',
18
+ 'wallet-profile.response.schema.json',
18
19
  ];
19
20
 
20
21
  const REQUIRED_KEYS = ['$schema', 'title', 'type'];
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
@@ -80,7 +80,14 @@ export const CLI_MANIFEST = [
80
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'],
@@ -1,24 +1,24 @@
1
1
  import { attachGlobalOptions } from './global.mjs';
2
2
  import { attachAuthOptions } from './auth.mjs';
3
3
  import { attachExecutionOptions } from './execution.mjs';
4
- import { attachDelegationOptions } from './delegation.mjs';
4
+ import { attachDelegationOptions } from './onboarding/delegation.mjs';
5
5
  import { attachTrackingOptions } from './tracking.mjs';
6
- import { attachWalletOptions } from './wallet.mjs';
7
- import { attachBootstrapOptions, attachOnboardingHelpOptions } from './bootstrap.mjs';
8
- import { attachCredentialsOptions } from './credentials.mjs';
6
+ import { attachWalletOptions } from './account/wallet.mjs';
7
+ import { attachBootstrapOptions, attachOnboardingHelpOptions } from './onboarding/bootstrap.mjs';
8
+ import { attachCredentialsOptions } from './onboarding/credentials.mjs';
9
9
  import { attachAnalyticsOptions } from './analytics.mjs';
10
- import { attachPolymarketOptions } from './polymarket.mjs';
11
- import { attachHummingbotOptions } from './hummingbot.mjs';
12
- import { attachCondorOptions } from './condor.mjs';
13
- import { attachAgentRegisterOptions } from './agent.mjs';
10
+ import { attachPolymarketOptions } from './venues/polymarket.mjs';
11
+ import { attachHummingbotOptions } from './venues/hummingbot.mjs';
12
+ import { attachCondorOptions } from './venues/condor.mjs';
13
+ import { attachAgentRegisterOptions } from './account/agent.mjs';
14
14
  import { attachChainListOptions, attachChainOptions } from './chains.mjs';
15
- import { attachCreditsBuyOptions, attachCreditsOptions } from './credits.mjs';
15
+ import { attachCreditsBuyOptions, attachCreditsOptions } from './account/credits.mjs';
16
16
  import { attachChatOptions } from './chat.mjs';
17
- import { attachVenueOptions } from './venue.mjs';
17
+ import { attachVenueOptions } from './venues/venue.mjs';
18
18
  import { attachBrainOptions } from './brain.mjs';
19
19
  import { attachCertificationOptions } from './certification.mjs';
20
20
  import { attachActionListOptions, attachDataCategoryOptions, attachSignalsOptions } from './data.mjs';
21
- import { attachWalletControlOptions } from './walletControl.mjs';
21
+ import { attachWalletControlOptions } from './account/walletControl.mjs';
22
22
 
23
23
  const BUNDLES = {
24
24
  auth: attachAuthOptions,
@@ -3,4 +3,4 @@ import { buildCliProgram } from './register.mjs';
3
3
  export { buildCliProgram };
4
4
  export { collectRootFlags, collectLegacyFlags } from './flags.mjs';
5
5
  export { configureCommanderErrors, handleRootOnlyFlows, collectVersionFlagsFromArgv } from './errors.mjs';
6
- export { normalizeLegacyShortFlags } from './options/bootstrap.mjs';
6
+ export { normalizeLegacyShortFlags } from './options/onboarding/bootstrap.mjs';
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from 'module';
2
- import { requireFlag, parseJsonFlag } from '../core/args.mjs';
3
- import { apiPost, safeJsonParse } from '../core/http.mjs';
4
- import { createCliError } from '../core/envelope.mjs';
5
- import { DEFAULT_TIMEOUT_MS } from '../core/constants.mjs';
2
+ import { requireFlag, parseJsonFlag } from '../../core/args.mjs';
3
+ import { apiPost, safeJsonParse } from '../../core/http.mjs';
4
+ import { createCliError } from '../../core/envelope.mjs';
5
+ import { DEFAULT_TIMEOUT_MS } from '../../core/constants.mjs';
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
- const { version: CLI_VERSION } = require('../../package.json');
8
+ const { version: CLI_VERSION } = require('../../../package.json');
9
9
  export function createAgentCommands({ printEnvelope, extractAgentApiKey, extractAgentKey, buildOwnerAuthHeaders }) {
10
10
  async function commandAgentRegister(ctx, flags, capabilities) {
11
11
  const ownerHeaders = buildOwnerAuthHeaders(
@@ -1,11 +1,13 @@
1
- import { requireFlag, usageError } from '../core/args.mjs';
2
- import { apiGet, apiPost } from '../core/http.mjs';
3
- import { toAvantisSpelling } from '../core/venueBrand.mjs';
1
+ import { requireFlag, usageError } from '../../core/args.mjs';
2
+ import { apiGet, apiPost } from '../../core/http.mjs';
3
+ import { resolveCreditsLedger } from '../../core/context.mjs';
4
+ import { createCliError } from '../../core/envelope.mjs';
5
+ import { toAvantisSpelling } from '../../core/venueBrand.mjs';
4
6
  import {
5
7
  formatCreditsEstimateSummary,
6
8
  formatCreditsModelsSummary,
7
9
  stripUsdFields,
8
- } from '../lib/creditsUsdSummary.mjs';
10
+ } from '../../lib/creditsUsdSummary.mjs';
9
11
 
10
12
  function resolveToolCallsEstimate(flags) {
11
13
  const raw = flags.toolCalls ?? flags.toolCallsList;
@@ -36,7 +38,7 @@ function resolveToolCallsEstimate(flags) {
36
38
  /** Thirdfy credits are bought on Base with USDC from the agent's wallet (thirdfy-api-v2 `buy-agent-credits`). */
37
39
  const CREDITS_PURCHASE_CHAIN_ID = 8453;
38
40
 
39
- export function createCreditsCommands({ printEnvelope, getAuthToken, commandWalletExecute }) {
41
+ export function createCreditsCommands({ printEnvelope, getAuthToken, buildOwnerAuthHeaders, commandWalletExecute }) {
40
42
  /**
41
43
  * `credits buy --usd <n>`: buy the agent's Thirdfy credits with USDC from its execution wallet on
42
44
  * Base (gas sponsored), as thirdfy-mcp's `walletExecute buy-agent-credits` does. A shortcut for
@@ -143,19 +145,67 @@ export function createCreditsCommands({ printEnvelope, getAuthToken, commandWall
143
145
  });
144
146
  }
145
147
 
148
+ /**
149
+ * `pricing quote --action <id>`: the credits one catalog action costs, and `wouldBePaidBy`
150
+ * (agent_credits | free_request | owner_credits | none) when the API can read the balances
151
+ * (thirdfy-api-v2#887). The ledger follows `resolveCreditsLedger`. Agent: `POST /agent/credits/quote`
152
+ * with the agent's key. Owner (session token or auth token): `/credits/pricing/quote`, for one of the
153
+ * owner's agents with --agent-key. An API without the agent route (404, before #887) is quoted
154
+ * through the owner route when owner auth is set and --agent-key names the key's agent. Credits only.
155
+ */
146
156
  async function commandPricingQuote(ctx, flags) {
147
- const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for pricing quote');
148
157
  const action = requireFlag(flags, 'action', 'Missing --action for pricing quote');
149
- const response = await apiPost(
150
- ctx,
151
- '/api/v1/credits/pricing/quote',
152
- {
153
- // Veranta names price as their Avantis spelling, which every API deployment knows.
154
- actionKey: toAvantisSpelling(action),
155
- chainId: Number(flags.chainId || 8453),
156
- },
157
- { Authorization: `Bearer ${authToken}` },
158
- );
158
+ // Veranta names price as their Avantis spelling, which every API deployment knows.
159
+ const body = { actionKey: toAvantisSpelling(action), chainId: Number(flags.chainId || 8453) };
160
+ const { ledger: requestedLedger, agentApiKey } = resolveCreditsLedger(flags);
161
+ const flagAgentKey = String(flags.agentKey || '').trim();
162
+ const ownerHeaders = () =>
163
+ buildOwnerAuthHeaders(flags, 'Missing --owner-session-token or --auth-token (or THIRDFY_OWNER_SESSION_TOKEN / THIRDFY_AUTH_TOKEN), or an agent key, for pricing quote');
164
+ const ownerQuote = (agentKey) =>
165
+ apiPost(ctx, '/api/v1/credits/pricing/quote', { ...body, ...(agentKey ? { agentKey } : {}) }, ownerHeaders());
166
+ let ledger = requestedLedger;
167
+ let quotedAgentKey = flagAgentKey || null;
168
+ const warnings = [];
169
+ let response;
170
+ if (ledger === 'agent') {
171
+ try {
172
+ response = await apiPost(ctx, '/api/v1/agent/credits/quote', body, { 'x-agent-api-key': agentApiKey });
173
+ quotedAgentKey = response?.agentKey || null;
174
+ if (flagAgentKey && quotedAgentKey && flagAgentKey.toLowerCase() !== String(quotedAgentKey).toLowerCase()) {
175
+ warnings.push(`--agent-key ${flagAgentKey} is not the agent of this API key; the quote is for ${quotedAgentKey}.`);
176
+ }
177
+ } catch (error) {
178
+ if (Number(error?.statusCode) !== 404) throw error;
179
+ // Owner auth is needed for the older owner route, and the agent must be the key's own.
180
+ let hasOwner = true;
181
+ try {
182
+ ownerHeaders();
183
+ } catch {
184
+ hasOwner = false;
185
+ }
186
+ if (!hasOwner) {
187
+ throw createCliError(
188
+ 'OWNER_AUTH_REQUIRED',
189
+ 'This Thirdfy API has no agent quote route yet: pass --owner-session-token or --auth-token for an owner quote.',
190
+ { blockedStage: 'auth' },
191
+ );
192
+ }
193
+ // Only --agent-key can name the key's agent here. The agent ledger without --agent-api-key means
194
+ // no owner auth is stored, and then the owner check above has already refused.
195
+ if (!flagAgentKey) {
196
+ throw createCliError(
197
+ 'AGENT_IDENTITY_UNAVAILABLE',
198
+ 'This Thirdfy API has no agent quote route yet; to quote through the owner route, pass --agent-key (the agent of this API key).',
199
+ { blockedStage: 'auth' },
200
+ );
201
+ }
202
+ ledger = 'owner';
203
+ quotedAgentKey = flagAgentKey;
204
+ response = await ownerQuote(flagAgentKey);
205
+ }
206
+ } else {
207
+ response = await ownerQuote(flagAgentKey);
208
+ }
159
209
  printEnvelope({
160
210
  success: true,
161
211
  code: 'OK',
@@ -163,7 +213,13 @@ export function createCreditsCommands({ printEnvelope, getAuthToken, commandWall
163
213
  message: response?.wouldBePaidBy ? `Action pricing quote generated; would be paid by ${response.wouldBePaidBy}` : 'Action pricing quote generated',
164
214
  // An action's cost is in credits only, never dollars (thirdfy-mcp quoteActionPricing).
165
215
  data: stripUsdFields(response),
166
- meta: { apiBase: ctx.apiBase, action },
216
+ meta: {
217
+ apiBase: ctx.apiBase,
218
+ action,
219
+ ledger,
220
+ ...(quotedAgentKey ? { agentKey: quotedAgentKey } : {}),
221
+ ...(warnings.length ? { warnings } : {}),
222
+ },
167
223
  });
168
224
  }
169
225
 
@@ -1,14 +1,14 @@
1
- import { requireFlag, usageError } from '../core/args.mjs';
2
- import { createCliError, exitAfterOutput } from '../core/envelope.mjs';
3
- import { findAgentApiKey, loadProfileConfig } from '../core/context.mjs';
4
- import { DEFAULT_CHAIN_ID, chainGasNote, isSvmChainId, parseChainIdFlag, resolveChainRpcUrl } from '../core/chainRegistry.mjs';
5
- import { apiGet } from '../core/http.mjs';
6
- import { getEffectiveRunMode } from '../core/runMode.mjs';
7
- import { readWalletControl } from '../runtime/execution/walletControl.mjs';
8
- import { assertSvmChainRunByApi } from '../runtime/execution/svmLane.mjs';
9
- import { applySvmLane } from '../runtime/execution/svmLane.mjs';
10
- import { readSignedTxChainId } from '../core/signedTx.mjs';
11
- import { runNotionalWarnings } from '../runtime/risk/notional.mjs';
1
+ import { requireFlag, usageError } from '../../core/args.mjs';
2
+ import { createCliError, exitAfterOutput } from '../../core/envelope.mjs';
3
+ import { findAgentApiKey, loadProfileConfig, resolveCreditsLedger } from '../../core/context.mjs';
4
+ import { DEFAULT_CHAIN_ID, chainGasNote, isSvmChainId, parseChainIdFlag, resolveChainRpcUrl } from '../../core/chainRegistry.mjs';
5
+ import { apiGet } from '../../core/http.mjs';
6
+ import { getEffectiveRunMode } from '../../core/runMode.mjs';
7
+ import { readWalletControl } from '../../runtime/execution/walletControl.mjs';
8
+ import { blockedResultFields } from '../../core/errorEnvelope.mjs';
9
+ import { applySvmLane, assertSvmChainRunByApi } from '../../runtime/execution/svmLane.mjs';
10
+ import { readSignedTxChainId } from '../../core/execution/signedTx.mjs';
11
+ import { runNotionalWarnings } from '../../runtime/risk/notional.mjs';
12
12
 
13
13
  export function createWalletCommands(deps) {
14
14
  const {
@@ -181,6 +181,129 @@ function resolveReadinessChain(flags) {
181
181
  };
182
182
  }
183
183
 
184
+ /**
185
+ * The agent an agent API key acts for. With an explicit --agent-api-key, only --agent-key or
186
+ * --user-did name it: an agent from env or config may belong to another key. Otherwise the env or
187
+ * config agent. Throws AGENT_IDENTITY_UNAVAILABLE when no agent can be named, as thirdfy-mcp does
188
+ * for a key it cannot tie to an agent; it never guesses.
189
+ */
190
+ function resolveAgentIdentity(flags) {
191
+ const explicitKey = Boolean(String(flags.agentApiKey || '').trim());
192
+ const agentKey = String((explicitKey ? flags.agentKey : resolveAgentKey(flags)) || '').trim().toLowerCase();
193
+ const explicitDid = String(flags.userDid || '').trim();
194
+ const userDid = explicitKey
195
+ ? explicitDid || (agentKey ? `did:wallet:${agentKey}` : '')
196
+ : resolveEffectiveUserDid(flags, { runMode: 'agent_wallet' });
197
+ if (!userDid) {
198
+ throw createCliError(
199
+ 'AGENT_IDENTITY_UNAVAILABLE',
200
+ explicitKey
201
+ ? 'With --agent-api-key, pass --agent-key (the agent that key belongs to) so its wallet can be named.'
202
+ : 'No agent to name the wallet: pass --agent-key, or set THIRDFY_AGENT_KEY or config agent.agentKey.',
203
+ { blockedStage: 'auth' },
204
+ );
205
+ }
206
+ return { agentKey: agentKey || null, userDid };
207
+ }
208
+
209
+ /**
210
+ * The agent's execution wallet on a chain, read with its own key (`GET /agent/execution-wallet`, as
211
+ * thirdfy-mcp agentWalletBootstrap): the API answer, the chain's gas note, wallet control (API 4.0),
212
+ * whether it holds the funding token, and whether it can sign (`ready`). Used by `readiness` and
213
+ * `wallet profile`.
214
+ */
215
+ async function readAgentExecutionWallet(ctx, flags, agentApiKey, chainId) {
216
+ const { agentKey, userDid } = resolveAgentIdentity(flags);
217
+ const query = new URLSearchParams({ chainId: String(chainId), runMode: 'agent_wallet' });
218
+ if (userDid) query.set('userDid', userDid);
219
+ const data = await apiGet(ctx, `/api/v1/agent/execution-wallet?${query.toString()}`, { 'x-agent-api-key': agentApiKey });
220
+ // The API says per chain whether it sponsors the fee (Solana `gasSponsored`); that answer wins.
221
+ const gas = chainGasNote(chainId, { gasSponsored: data?.gasSponsored });
222
+ const control = readWalletControl(data, { svm: isSvmChainId(chainId) });
223
+ const raw = String(data?.fundingTokenBalance?.raw ?? '').trim();
224
+ const fundingReady = /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(raw) ? BigInt(raw) > 0n : undefined;
225
+ const ready = Boolean(data?.executionWalletAddress) && data?.success !== false && !control.walletControlBlocked;
226
+ return { agentKey, userDid, data, gas, control, fundingReady, ready };
227
+ }
228
+
229
+ /**
230
+ * `wallet profile` (thirdfy-mcp `getWalletProfile`). With owner auth: the owner's live onboarding
231
+ * profile (`GET /agent/onboarding/cli/status`: linked wallets, EVM/Solana readiness). With
232
+ * --agent-api-key, or with no owner auth but an agent key in env or config: the agent's own
233
+ * execution wallet on --chain-id (Base by default), with `ready`, `walletControl` and `nextAction`.
234
+ * `wallet list` reads only the local profile config; this asks the API.
235
+ */
236
+ async function commandWalletProfile(ctx, flags, capabilities) {
237
+ const meta = { apiBase: ctx.apiBase, capabilitiesVersion: capabilities?.contractVersion || null };
238
+ // Same precedence as the credits commands (as thirdfy-mcp): an owner flag the caller passed, then an
239
+ // explicit --agent-api-key, then owner auth from env or config, then an agent key from env or config.
240
+ const { ledger, agentApiKey } = resolveCreditsLedger(flags);
241
+ if (ledger !== 'agent') {
242
+ const ownerHeaders = (() => {
243
+ try {
244
+ return buildOwnerAuthHeaders(flags, 'no owner auth');
245
+ } catch {
246
+ return null;
247
+ }
248
+ })();
249
+ if (!ownerHeaders) {
250
+ throw createCliError(
251
+ 'OWNER_AUTH_REQUIRED',
252
+ 'wallet profile needs owner auth (--owner-session-token or --auth-token) for the owner profile, or an agent key for the agent\'s execution wallet.',
253
+ { blockedStage: 'auth' },
254
+ );
255
+ }
256
+ const profile = await apiGet(ctx, '/api/v1/agent/onboarding/cli/status', ownerHeaders);
257
+ printEnvelope({
258
+ success: true,
259
+ code: 'WALLET_PROFILE_OK',
260
+ message: 'Owner wallet profile loaded',
261
+ data: { auth: 'owner', source: '/api/v1/agent/onboarding/cli/status', profile },
262
+ meta,
263
+ });
264
+ return;
265
+ }
266
+ const chain = resolveReadinessChain(flags);
267
+ assertSvmChainRunByApi(chain.chainId);
268
+ const { agentKey, userDid, data, gas, control, fundingReady, ready } = await readAgentExecutionWallet(ctx, flags, agentApiKey, chain.chainId);
269
+ // An API answer of success:false is a failed call, as thirdfy-mcp lifts it to the top level.
270
+ const apiFailed = data?.success === false;
271
+ const failure = apiFailed
272
+ ? {
273
+ ...blockedResultFields(typeof data?.blockedReason === 'string' && data.blockedReason ? data.blockedReason : 'WALLET_PROFILE_FAILED'),
274
+ ...(data?.error ? { error: String(data.error) } : {}),
275
+ }
276
+ : {};
277
+ printEnvelope({
278
+ success: !apiFailed,
279
+ code: apiFailed ? 'WALLET_PROFILE_FAILED' : 'WALLET_PROFILE_OK',
280
+ message: apiFailed
281
+ ? `Agent execution wallet read failed${data?.error ? `: ${data.error}` : ''}`
282
+ : ready ? 'Agent execution wallet loaded (ready)' : control.userMessage || 'Agent execution wallet loaded (not ready)',
283
+ data: {
284
+ ...failure,
285
+ auth: 'agent_api_key',
286
+ source: '/api/v1/agent/execution-wallet',
287
+ agentKey,
288
+ chainId: chain.chainId,
289
+ ...(chain.chainIdAssumed ? { chainIdAssumed: true, chainIdNote: chain.chainIdNote } : {}),
290
+ ready,
291
+ ...(fundingReady !== undefined ? { fundingReady } : {}),
292
+ ...(gas ? { gas } : {}),
293
+ ...(control.walletControl ? { walletControl: control.walletControl } : {}),
294
+ ...(control.nextAction ? { nextAction: control.nextAction, userMessage: control.userMessage } : {}),
295
+ executionWallet: {
296
+ executionWalletAddress: data?.executionWalletAddress || null,
297
+ userDid: userDid || data?.userDid || null,
298
+ data,
299
+ },
300
+ note: "This is the agent's execution wallet. The owner's onboarding profile needs --owner-session-token or --auth-token.",
301
+ },
302
+ meta,
303
+ });
304
+ if (apiFailed) await exitAfterOutput(1);
305
+ }
306
+
184
307
  /**
185
308
  * `readiness`: one verdict on whether the agent can act, as thirdfy-mcp `getSelfReadiness`.
186
309
  * - agent_wallet: the agent's execution wallet on the chain (`GET /agent/execution-wallet` with the
@@ -226,16 +349,7 @@ async function commandReadiness(ctx, flags, capabilities) {
226
349
  { blockedStage: 'auth', runMode, chainId: chain.chainId },
227
350
  );
228
351
  }
229
- const userDid = resolveEffectiveUserDid(flags, { runMode: 'agent_wallet' });
230
- const query = new URLSearchParams({ chainId: String(chain.chainId), runMode: 'agent_wallet' });
231
- if (userDid) query.set('userDid', userDid);
232
- const data = await apiGet(ctx, `/api/v1/agent/execution-wallet?${query.toString()}`, { 'x-agent-api-key': agentApiKey });
233
- // The API says per chain whether it sponsors the fee (Solana `gasSponsored`); that answer wins.
234
- const gas = chainGasNote(chain.chainId, { gasSponsored: data?.gasSponsored });
235
- const control = readWalletControl(data, { svm: isSvmChainId(chain.chainId) });
236
- const raw = String(data?.fundingTokenBalance?.raw ?? '').trim();
237
- const fundingReady = /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(raw) ? BigInt(raw) > 0n : undefined;
238
- const ready = Boolean(data?.executionWalletAddress) && data?.success !== false && !control.walletControlBlocked;
352
+ const { userDid, data, gas, control, fundingReady, ready } = await readAgentExecutionWallet(ctx, flags, agentApiKey, chain.chainId);
239
353
  printEnvelope({
240
354
  success: true,
241
355
  code: ready ? 'READY' : 'NOT_READY',
@@ -264,6 +378,7 @@ async function commandReadiness(ctx, flags, capabilities) {
264
378
 
265
379
  return {
266
380
  commandReadiness,
381
+ commandWalletProfile,
267
382
  commandWalletExecute,
268
383
  commandWalletSign,
269
384
  commandWalletSubmit,
@@ -1,6 +1,6 @@
1
- import { loadProfileConfig } from '../core/context.mjs';
2
- import { parseJsonFlag, requireFlag } from '../core/args.mjs';
3
- import { apiGet, apiPost } from '../core/http.mjs';
1
+ import { loadProfileConfig } from '../../core/context.mjs';
2
+ import { parseJsonFlag, requireFlag } from '../../core/args.mjs';
3
+ import { apiGet, apiPost } from '../../core/http.mjs';
4
4
 
5
5
  /**
6
6
  * The owner's side of API 4.0 wallet control (`/api/v1/agent/:agentKey/wallet-control/*`), for a
@@ -1,6 +1,6 @@
1
1
  import { requireFlag, parseJsonFlag } from '../core/args.mjs';
2
2
  import { apiGet, apiPost } from '../core/http.mjs';
3
- import { extractActions, findAgentApiKey, getCachedActionsCatalogSnapshot, loadProfileConfig } from '../core/context.mjs';
3
+ import { extractActions, getCachedActionsCatalogSnapshot, resolveCreditsLedger } from '../core/context.mjs';
4
4
  import { getEffectiveRunMode } from '../core/runMode.mjs';
5
5
  import { creditsFreeQuotaNote, formatCreditsBalanceSummary, stripUsdFields } from '../lib/creditsUsdSummary.mjs';
6
6
  import { filterActionsByChain } from '../runtime/actions/chainFilter.mjs';
@@ -52,7 +52,7 @@ function solanaExecutionContract() {
52
52
  'Solana execution runs only with --run-mode agent_wallet on chain 101 (103 devnet where a provider supports it). self, hybrid and thirdfy stay EVM-only. Check `actions --chain-id 101` (supportedChains) for each action\'s chains.',
53
53
  };
54
54
  }
55
- export function createDiscoveryCommands({ printEnvelope, applyActionFilters, getProviderHintForActions, resolveChainSupport, getAuthToken, resolveAgentApiKey }) {
55
+ export function createDiscoveryCommands({ printEnvelope, applyActionFilters, getProviderHintForActions, resolveChainSupport, buildOwnerAuthHeaders, resolveAgentApiKey }) {
56
56
  async function commandCatalogsList(ctx, flags, capabilities) {
57
57
  const response = await apiGet(ctx, '/api/v1/agent/actions/catalog');
58
58
  const actions = extractActions(response);
@@ -189,15 +189,15 @@ async function commandChains(ctx, flags, capabilities) {
189
189
  * thirdfy-mcp getCreditsBalance. Credits only, never dollars.
190
190
  */
191
191
  async function commandCreditsBalance(ctx, flags, capabilities) {
192
- const config = loadProfileConfig();
193
- const ownerToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
194
- const agentApiKey = findAgentApiKey(flags);
195
- const agentLedger = Boolean(String(flags.agentApiKey || '').trim()) || (!ownerToken && Boolean(agentApiKey));
192
+ const { ledger, agentApiKey } = resolveCreditsLedger(flags);
193
+ const agentLedger = ledger === 'agent';
196
194
  const response = agentLedger
197
195
  ? await apiGet(ctx, '/api/v1/agent/credits/balance', { 'x-agent-api-key': agentApiKey })
198
- : await apiGet(ctx, '/api/v1/credits/balance', {
199
- Authorization: `Bearer ${getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN), or an agent key for the agent\'s own credits, for credits balance')}`,
200
- });
196
+ : await apiGet(
197
+ ctx,
198
+ '/api/v1/credits/balance',
199
+ buildOwnerAuthHeaders(flags, 'Missing --owner-session-token or --auth-token (or THIRDFY_OWNER_SESSION_TOKEN / THIRDFY_AUTH_TOKEN), or an agent key for the agent\'s own credits, for credits balance'),
200
+ );
201
201
  const summary = formatCreditsBalanceSummary(response);
202
202
  const note = creditsFreeQuotaNote(response);
203
203
  printEnvelope({
@@ -8,8 +8,8 @@ import {
8
8
  } from '../core/runMode.mjs';
9
9
  import { apiGet } from '../core/http.mjs';
10
10
  import { applySvmLane } from '../runtime/execution/svmLane.mjs';
11
- import { isReadOnlyResolvedAction } from '../core/readOnlyActions.mjs';
12
- import { resolveWalletExecuteChainId } from '../core/walletExecuteChainId.mjs';
11
+ import { isReadOnlyResolvedAction } from '../core/execution/readOnlyActions.mjs';
12
+ import { resolveWalletExecuteChainId } from '../core/execution/walletExecuteChainId.mjs';
13
13
  import { resolveExecuteEstimatedAmountUsd } from '../runtime/execution/payloads.mjs';
14
14
  import { assertPreviewSized, buildPreviewOrder, shapeOrderPreview } from '../runtime/risk/orderPreview.mjs';
15
15
  import { actionNameForApi } from '../runtime/actions/selection.mjs';
@@ -1,10 +1,10 @@
1
- export { createPolymarketCommands, resolveConfigAgentKey } from './polymarket.mjs';
2
- export { createHyperliquidHelpers } from './hyperliquid.mjs';
3
- export { createLighterHelpers } from './lighter.mjs';
4
- export { createAvantisHelpers } from './avantis.mjs';
5
- export { createHummingbotCommands } from './hummingbot.mjs';
6
- export { createCondorCommands } from './condor.mjs';
1
+ export { createPolymarketCommands, resolveConfigAgentKey } from './venues/polymarket.mjs';
2
+ export { createHyperliquidHelpers } from './venues/hyperliquid.mjs';
3
+ export { createLighterHelpers } from './venues/lighter.mjs';
4
+ export { createAvantisHelpers } from './venues/avantis.mjs';
5
+ export { createHummingbotCommands } from './venues/hummingbot.mjs';
6
+ export { createCondorCommands } from './venues/condor.mjs';
7
7
  export { createExecuteCommands } from './execute.mjs';
8
- export { createWalletCommands } from './wallet.mjs';
9
- export { createDelegationCommands } from './delegation.mjs';
10
- export { createBootstrapCommands } from './bootstrap.mjs';
8
+ export { createWalletCommands } from './account/wallet.mjs';
9
+ export { createDelegationCommands } from './onboarding/delegation.mjs';
10
+ export { createBootstrapCommands } from './onboarding/bootstrap.mjs';
@@ -1,26 +1,26 @@
1
- import { requireFlag, isNonInteractive, usageError } from '../core/args.mjs';
2
- import { createCliError } from '../core/envelope.mjs';
3
- import { PROFILE_DEFAULTS } from '../core/constants.mjs';
1
+ import { requireFlag, isNonInteractive, usageError } from '../../core/args.mjs';
2
+ import { createCliError } from '../../core/envelope.mjs';
3
+ import { PROFILE_DEFAULTS } from '../../core/constants.mjs';
4
4
  import {
5
5
  normalizeRunMode,
6
6
  normalizeProfileName,
7
7
  normalizeCustodyMode,
8
8
  defaultCustodyModeForRunMode,
9
9
  parseBooleanFlag,
10
- } from '../core/runMode.mjs';
10
+ } from '../../core/runMode.mjs';
11
11
  import {
12
12
  loadProfileConfig,
13
13
  persistProfileConfig,
14
14
  clonePlainConfigTree,
15
15
  setNestedConfigValue,
16
16
  getProfileConfigPath,
17
- } from '../core/context.mjs';
18
- import { apiGet, apiPost, requestWithRouteFallback } from '../core/http.mjs';
19
- import { isEvmAddress, isSolanaAddress } from '../core/chainRegistry.mjs';
17
+ } from '../../core/context.mjs';
18
+ import { apiGet, apiPost, requestWithRouteFallback } from '../../core/http.mjs';
19
+ import { isEvmAddress, isSolanaAddress } from '../../core/chainRegistry.mjs';
20
20
  import { createRequire } from 'module';
21
21
 
22
22
  const require = createRequire(import.meta.url);
23
- const { version: CLI_VERSION } = require('../../package.json');
23
+ const { version: CLI_VERSION } = require('../../../package.json');
24
24
 
25
25
  export function createBootstrapCommands(deps) {
26
26
  const {