@thirdfy/agent-cli 0.4.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.
- package/CHANGELOG.md +15 -0
- package/README.md +6 -8
- package/package.json +1 -1
- package/scripts/validate-cli-contract-schemas.mjs +1 -0
- package/src/cli/help.mjs +1 -1
- package/src/cli/manifest.mjs +8 -1
- package/src/commands/credits.mjs +71 -13
- package/src/commands/discovery.mjs +9 -9
- package/src/commands/wallet.mjs +129 -10
- package/src/contracts/cli/schemas/wallet-profile.response.schema.json +127 -0
- package/src/core/context.mjs +20 -0
- package/src/runtime/handlers.mjs +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ 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
|
+
|
|
7
22
|
## [0.4.0] - 2026-10-14
|
|
8
23
|
|
|
9
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.
|
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,12 @@ Run without global install:
|
|
|
40
40
|
npx @thirdfy/agent-cli --help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## What's new in v0.4.
|
|
43
|
+
## What's new in v0.4.1
|
|
44
44
|
|
|
45
|
-
- **`
|
|
46
|
-
-
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
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
|
+
- **`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.
|
|
51
49
|
- Older versions: see [CHANGELOG.md](./CHANGELOG.md).
|
|
52
50
|
|
|
53
51
|
## Quick start
|
package/package.json
CHANGED
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 <
|
|
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
|
package/src/cli/manifest.mjs
CHANGED
|
@@ -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'],
|
package/src/commands/credits.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { requireFlag, usageError } from '../core/args.mjs';
|
|
2
2
|
import { apiGet, apiPost } from '../core/http.mjs';
|
|
3
|
+
import { loadProfileConfig, resolveCreditsLedger } from '../core/context.mjs';
|
|
4
|
+
import { createCliError } from '../core/envelope.mjs';
|
|
3
5
|
import { toAvantisSpelling } from '../core/venueBrand.mjs';
|
|
4
6
|
import {
|
|
5
7
|
formatCreditsEstimateSummary,
|
|
@@ -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 the key's agent is known: --agent-key, or the
|
|
155
|
+
* configured agent when the key also comes from env or config. Credits only.
|
|
156
|
+
*/
|
|
146
157
|
async function commandPricingQuote(ctx, flags) {
|
|
147
|
-
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for pricing quote');
|
|
148
158
|
const action = requireFlag(flags, 'action', 'Missing --action for pricing quote');
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
// Veranta names price as their Avantis spelling, which every API deployment knows.
|
|
160
|
+
const body = { actionKey: toAvantisSpelling(action), chainId: Number(flags.chainId || 8453) };
|
|
161
|
+
const { ledger: requestedLedger, agentApiKey, agentApiKeyExplicit } = resolveCreditsLedger(flags);
|
|
162
|
+
const flagAgentKey = String(flags.agentKey || '').trim();
|
|
163
|
+
const ownerHeaders = () =>
|
|
164
|
+
buildOwnerAuthHeaders(flags, 'Missing --owner-session-token or --auth-token (or THIRDFY_OWNER_SESSION_TOKEN / THIRDFY_AUTH_TOKEN), or an agent key, for pricing quote');
|
|
165
|
+
const ownerQuote = (agentKey) =>
|
|
166
|
+
apiPost(ctx, '/api/v1/credits/pricing/quote', { ...body, ...(agentKey ? { agentKey } : {}) }, ownerHeaders());
|
|
167
|
+
let ledger = requestedLedger;
|
|
168
|
+
let quotedAgentKey = flagAgentKey || null;
|
|
169
|
+
const warnings = [];
|
|
170
|
+
let response;
|
|
171
|
+
if (ledger === 'agent') {
|
|
172
|
+
try {
|
|
173
|
+
response = await apiPost(ctx, '/api/v1/agent/credits/quote', body, { 'x-agent-api-key': agentApiKey });
|
|
174
|
+
quotedAgentKey = response?.agentKey || null;
|
|
175
|
+
if (flagAgentKey && quotedAgentKey && flagAgentKey.toLowerCase() !== String(quotedAgentKey).toLowerCase()) {
|
|
176
|
+
warnings.push(`--agent-key ${flagAgentKey} is not the agent of this API key; the quote is for ${quotedAgentKey}.`);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (Number(error?.statusCode) !== 404) throw error;
|
|
180
|
+
// Owner auth is needed for the older owner route, and the agent must be the key's own.
|
|
181
|
+
let hasOwner = true;
|
|
182
|
+
try {
|
|
183
|
+
ownerHeaders();
|
|
184
|
+
} catch {
|
|
185
|
+
hasOwner = false;
|
|
186
|
+
}
|
|
187
|
+
if (!hasOwner) {
|
|
188
|
+
throw createCliError(
|
|
189
|
+
'OWNER_AUTH_REQUIRED',
|
|
190
|
+
'This Thirdfy API has no agent quote route yet: pass --owner-session-token or --auth-token for an owner quote.',
|
|
191
|
+
{ blockedStage: 'auth' },
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const pairedAgentKey = flagAgentKey || (agentApiKeyExplicit ? '' : String(process.env.THIRDFY_AGENT_KEY || loadProfileConfig()?.agent?.agentKey || '').trim());
|
|
195
|
+
if (!pairedAgentKey) {
|
|
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 = pairedAgentKey;
|
|
204
|
+
response = await ownerQuote(pairedAgentKey);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
response = await ownerQuote(flagAgentKey);
|
|
208
|
+
}
|
|
159
209
|
printEnvelope({
|
|
160
210
|
success: true,
|
|
161
211
|
code: 'OK',
|
|
@@ -163,10 +213,18 @@ 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: {
|
|
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
|
|
|
226
|
+
|
|
227
|
+
|
|
170
228
|
return {
|
|
171
229
|
commandCreditsBuy,
|
|
172
230
|
commandCreditsTransactions,
|
|
@@ -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,
|
|
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, getAuthToken, 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
|
|
193
|
-
const
|
|
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(
|
|
199
|
-
|
|
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({
|
package/src/commands/wallet.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { DEFAULT_CHAIN_ID, chainGasNote, isSvmChainId, parseChainIdFlag, resolve
|
|
|
5
5
|
import { apiGet } from '../core/http.mjs';
|
|
6
6
|
import { getEffectiveRunMode } from '../core/runMode.mjs';
|
|
7
7
|
import { readWalletControl } from '../runtime/execution/walletControl.mjs';
|
|
8
|
+
import { blockedResultFields } from '../core/errorEnvelope.mjs';
|
|
8
9
|
import { assertSvmChainRunByApi } from '../runtime/execution/svmLane.mjs';
|
|
9
10
|
import { applySvmLane } from '../runtime/execution/svmLane.mjs';
|
|
10
11
|
import { readSignedTxChainId } from '../core/signedTx.mjs';
|
|
@@ -181,6 +182,132 @@ function resolveReadinessChain(flags) {
|
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
184
|
|
|
185
|
+
/**
|
|
186
|
+
* The agent's execution wallet on a chain, read with its own key (`GET /agent/execution-wallet`, as
|
|
187
|
+
* thirdfy-mcp agentWalletBootstrap): the API answer, the chain's gas note, wallet control (API 4.0),
|
|
188
|
+
* whether it holds the funding token, and whether it can sign (`ready`). Used by `readiness` and
|
|
189
|
+
* `wallet profile`.
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* The agent an agent API key acts for. With an explicit --agent-api-key, only --agent-key or
|
|
193
|
+
* --user-did name it: an agent from env or config may belong to another key. Otherwise the env or
|
|
194
|
+
* config agent. Throws AGENT_IDENTITY_UNAVAILABLE when no agent can be named, as thirdfy-mcp does
|
|
195
|
+
* for a key it cannot tie to an agent; it never guesses.
|
|
196
|
+
*/
|
|
197
|
+
function resolveAgentIdentity(flags) {
|
|
198
|
+
const explicitKey = Boolean(String(flags.agentApiKey || '').trim());
|
|
199
|
+
const agentKey = explicitKey ? String(flags.agentKey || '').trim().toLowerCase() : String(resolveAgentKey(flags) || '').trim();
|
|
200
|
+
const explicitDid = String(flags.userDid || '').trim();
|
|
201
|
+
const userDid = explicitKey
|
|
202
|
+
? explicitDid || (agentKey ? `did:wallet:${agentKey}` : '')
|
|
203
|
+
: resolveEffectiveUserDid(flags, { runMode: 'agent_wallet' });
|
|
204
|
+
if (!userDid) {
|
|
205
|
+
throw createCliError(
|
|
206
|
+
'AGENT_IDENTITY_UNAVAILABLE',
|
|
207
|
+
explicitKey
|
|
208
|
+
? 'With --agent-api-key, pass --agent-key (the agent that key belongs to) so its wallet can be named.'
|
|
209
|
+
: 'No agent to name the wallet: pass --agent-key, or set THIRDFY_AGENT_KEY or config agent.agentKey.',
|
|
210
|
+
{ blockedStage: 'auth' },
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return { agentKey: agentKey || null, userDid };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function readAgentExecutionWallet(ctx, flags, agentApiKey, chainId) {
|
|
217
|
+
const { agentKey, userDid } = resolveAgentIdentity(flags);
|
|
218
|
+
const query = new URLSearchParams({ chainId: String(chainId), runMode: 'agent_wallet' });
|
|
219
|
+
if (userDid) query.set('userDid', userDid);
|
|
220
|
+
const data = await apiGet(ctx, `/api/v1/agent/execution-wallet?${query.toString()}`, { 'x-agent-api-key': agentApiKey });
|
|
221
|
+
// The API says per chain whether it sponsors the fee (Solana `gasSponsored`); that answer wins.
|
|
222
|
+
const gas = chainGasNote(chainId, { gasSponsored: data?.gasSponsored });
|
|
223
|
+
const control = readWalletControl(data, { svm: isSvmChainId(chainId) });
|
|
224
|
+
const raw = String(data?.fundingTokenBalance?.raw ?? '').trim();
|
|
225
|
+
const fundingReady = /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(raw) ? BigInt(raw) > 0n : undefined;
|
|
226
|
+
const ready = Boolean(data?.executionWalletAddress) && data?.success !== false && !control.walletControlBlocked;
|
|
227
|
+
return { agentKey, userDid, data, gas, control, fundingReady, ready };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `wallet profile` (thirdfy-mcp `getWalletProfile`). With owner auth: the owner's live onboarding
|
|
232
|
+
* profile (`GET /agent/onboarding/cli/status`: linked wallets, EVM/Solana readiness). With
|
|
233
|
+
* --agent-api-key, or with no owner auth but an agent key in env or config: the agent's own
|
|
234
|
+
* execution wallet on --chain-id (Base by default), with `ready`, `walletControl` and `nextAction`.
|
|
235
|
+
* `wallet list` reads only the local profile config; this asks the API.
|
|
236
|
+
*/
|
|
237
|
+
async function commandWalletProfile(ctx, flags, capabilities) {
|
|
238
|
+
const meta = { apiBase: ctx.apiBase, capabilitiesVersion: capabilities?.contractVersion || null };
|
|
239
|
+
const ownerHeaders = (() => {
|
|
240
|
+
try {
|
|
241
|
+
return buildOwnerAuthHeaders(flags, 'no owner auth');
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
})();
|
|
246
|
+
const agentApiKey = findAgentApiKey(flags);
|
|
247
|
+
// An owner flag the caller passed wins (as thirdfy-mcp), then an explicit --agent-api-key, then
|
|
248
|
+
// owner auth from env or config, then an agent key from env or config.
|
|
249
|
+
const explicitOwner = Boolean(String(flags.ownerSessionToken || flags.authToken || '').trim());
|
|
250
|
+
const explicitAgent = Boolean(String(flags.agentApiKey || '').trim());
|
|
251
|
+
const agentPath = !explicitOwner && (explicitAgent || (!ownerHeaders && Boolean(agentApiKey)));
|
|
252
|
+
if (!agentPath) {
|
|
253
|
+
if (!ownerHeaders) {
|
|
254
|
+
throw createCliError(
|
|
255
|
+
'OWNER_AUTH_REQUIRED',
|
|
256
|
+
'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.',
|
|
257
|
+
{ blockedStage: 'auth' },
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const profile = await apiGet(ctx, '/api/v1/agent/onboarding/cli/status', ownerHeaders);
|
|
261
|
+
printEnvelope({
|
|
262
|
+
success: true,
|
|
263
|
+
code: 'WALLET_PROFILE_OK',
|
|
264
|
+
message: 'Owner wallet profile loaded',
|
|
265
|
+
data: { auth: 'owner', source: '/api/v1/agent/onboarding/cli/status', profile },
|
|
266
|
+
meta,
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const chain = resolveReadinessChain(flags);
|
|
271
|
+
assertSvmChainRunByApi(chain.chainId);
|
|
272
|
+
const { agentKey, userDid, data, gas, control, fundingReady, ready } = await readAgentExecutionWallet(ctx, flags, agentApiKey, chain.chainId);
|
|
273
|
+
// An API answer of success:false is a failed call, as thirdfy-mcp lifts it to the top level.
|
|
274
|
+
const apiFailed = data?.success === false;
|
|
275
|
+
const failure = apiFailed
|
|
276
|
+
? {
|
|
277
|
+
...blockedResultFields(typeof data?.blockedReason === 'string' && data.blockedReason ? data.blockedReason : 'WALLET_PROFILE_FAILED'),
|
|
278
|
+
...(data?.error ? { error: String(data.error) } : {}),
|
|
279
|
+
}
|
|
280
|
+
: {};
|
|
281
|
+
printEnvelope({
|
|
282
|
+
success: !apiFailed,
|
|
283
|
+
code: apiFailed ? 'WALLET_PROFILE_FAILED' : 'WALLET_PROFILE_OK',
|
|
284
|
+
message: apiFailed
|
|
285
|
+
? `Agent execution wallet read failed${data?.error ? `: ${data.error}` : ''}`
|
|
286
|
+
: ready ? 'Agent execution wallet loaded (ready)' : control.userMessage || 'Agent execution wallet loaded (not ready)',
|
|
287
|
+
data: {
|
|
288
|
+
...failure,
|
|
289
|
+
auth: 'agent_api_key',
|
|
290
|
+
source: '/api/v1/agent/execution-wallet',
|
|
291
|
+
agentKey,
|
|
292
|
+
chainId: chain.chainId,
|
|
293
|
+
...(chain.chainIdAssumed ? { chainIdAssumed: true, chainIdNote: chain.chainIdNote } : {}),
|
|
294
|
+
ready,
|
|
295
|
+
...(fundingReady !== undefined ? { fundingReady } : {}),
|
|
296
|
+
...(gas ? { gas } : {}),
|
|
297
|
+
...(control.walletControl ? { walletControl: control.walletControl } : {}),
|
|
298
|
+
...(control.nextAction ? { nextAction: control.nextAction, userMessage: control.userMessage } : {}),
|
|
299
|
+
executionWallet: {
|
|
300
|
+
executionWalletAddress: data?.executionWalletAddress || null,
|
|
301
|
+
userDid: userDid || data?.userDid || null,
|
|
302
|
+
data,
|
|
303
|
+
},
|
|
304
|
+
note: "This is the agent's execution wallet. The owner's onboarding profile needs --owner-session-token or --auth-token.",
|
|
305
|
+
},
|
|
306
|
+
meta,
|
|
307
|
+
});
|
|
308
|
+
if (apiFailed) await exitAfterOutput(1);
|
|
309
|
+
}
|
|
310
|
+
|
|
184
311
|
/**
|
|
185
312
|
* `readiness`: one verdict on whether the agent can act, as thirdfy-mcp `getSelfReadiness`.
|
|
186
313
|
* - agent_wallet: the agent's execution wallet on the chain (`GET /agent/execution-wallet` with the
|
|
@@ -226,16 +353,7 @@ async function commandReadiness(ctx, flags, capabilities) {
|
|
|
226
353
|
{ blockedStage: 'auth', runMode, chainId: chain.chainId },
|
|
227
354
|
);
|
|
228
355
|
}
|
|
229
|
-
const userDid =
|
|
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;
|
|
356
|
+
const { userDid, data, gas, control, fundingReady, ready } = await readAgentExecutionWallet(ctx, flags, agentApiKey, chain.chainId);
|
|
239
357
|
printEnvelope({
|
|
240
358
|
success: true,
|
|
241
359
|
code: ready ? 'READY' : 'NOT_READY',
|
|
@@ -264,6 +382,7 @@ async function commandReadiness(ctx, flags, capabilities) {
|
|
|
264
382
|
|
|
265
383
|
return {
|
|
266
384
|
commandReadiness,
|
|
385
|
+
commandWalletProfile,
|
|
267
386
|
commandWalletExecute,
|
|
268
387
|
commandWalletSign,
|
|
269
388
|
commandWalletSubmit,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "CLI response data: wallet profile (thirdfy-mcp getWalletProfile parity)",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"auth",
|
|
7
|
+
"source"
|
|
8
|
+
],
|
|
9
|
+
"properties": {
|
|
10
|
+
"auth": {
|
|
11
|
+
"enum": [
|
|
12
|
+
"owner",
|
|
13
|
+
"agent_api_key"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"source": {
|
|
17
|
+
"enum": [
|
|
18
|
+
"/api/v1/agent/onboarding/cli/status",
|
|
19
|
+
"/api/v1/agent/execution-wallet"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"profile": {
|
|
23
|
+
"type": "object"
|
|
24
|
+
},
|
|
25
|
+
"agentKey": {
|
|
26
|
+
"type": [
|
|
27
|
+
"string",
|
|
28
|
+
"null"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"chainId": {
|
|
32
|
+
"type": "integer"
|
|
33
|
+
},
|
|
34
|
+
"chainIdAssumed": {
|
|
35
|
+
"enum": [
|
|
36
|
+
true
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"chainIdNote": {
|
|
40
|
+
"type": "string"
|
|
41
|
+
},
|
|
42
|
+
"ready": {
|
|
43
|
+
"type": "boolean"
|
|
44
|
+
},
|
|
45
|
+
"fundingReady": {
|
|
46
|
+
"type": "boolean"
|
|
47
|
+
},
|
|
48
|
+
"gas": {
|
|
49
|
+
"type": "object"
|
|
50
|
+
},
|
|
51
|
+
"walletControl": {
|
|
52
|
+
"type": "object"
|
|
53
|
+
},
|
|
54
|
+
"nextAction": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"userMessage": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"executionWallet": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": [
|
|
63
|
+
"executionWalletAddress"
|
|
64
|
+
],
|
|
65
|
+
"properties": {
|
|
66
|
+
"executionWalletAddress": {
|
|
67
|
+
"type": [
|
|
68
|
+
"string",
|
|
69
|
+
"null"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"userDid": {
|
|
73
|
+
"type": [
|
|
74
|
+
"string",
|
|
75
|
+
"null"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"data": {
|
|
79
|
+
"type": "object"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"additionalProperties": true
|
|
83
|
+
},
|
|
84
|
+
"note": {
|
|
85
|
+
"type": "string"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"additionalProperties": true,
|
|
89
|
+
"oneOf": [
|
|
90
|
+
{
|
|
91
|
+
"properties": {
|
|
92
|
+
"auth": {
|
|
93
|
+
"enum": [
|
|
94
|
+
"owner"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"source": {
|
|
98
|
+
"enum": [
|
|
99
|
+
"/api/v1/agent/onboarding/cli/status"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"required": [
|
|
104
|
+
"profile"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"properties": {
|
|
109
|
+
"auth": {
|
|
110
|
+
"enum": [
|
|
111
|
+
"agent_api_key"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"source": {
|
|
115
|
+
"enum": [
|
|
116
|
+
"/api/v1/agent/execution-wallet"
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"required": [
|
|
121
|
+
"executionWallet",
|
|
122
|
+
"ready",
|
|
123
|
+
"chainId"
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
package/src/core/context.mjs
CHANGED
|
@@ -132,6 +132,26 @@ export function findAgentApiKey(flags = {}) {
|
|
|
132
132
|
return String(loadProfileConfig()?.agent?.apiKey || '').trim();
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Whose credits a credits read is about. Same precedence as `wallet profile`: an owner credential
|
|
137
|
+
* passed as a flag (--owner-session-token or --auth-token), then an explicit --agent-api-key, then
|
|
138
|
+
* owner auth from env or config, then an agent key from env or config. Returns `{ ledger, agentApiKey,
|
|
139
|
+
* agentApiKeyExplicit }`; `ledger` is `agent` or `owner`.
|
|
140
|
+
*/
|
|
141
|
+
export function resolveCreditsLedger(flags = {}) {
|
|
142
|
+
const config = loadProfileConfig();
|
|
143
|
+
const explicitOwner = Boolean(String(flags.ownerSessionToken || flags.authToken || '').trim());
|
|
144
|
+
const agentApiKeyExplicit = Boolean(String(flags.agentApiKey || '').trim());
|
|
145
|
+
const storedOwner = Boolean(
|
|
146
|
+
String(
|
|
147
|
+
process.env.THIRDFY_OWNER_SESSION_TOKEN || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.ownerSessionToken || config?.auth?.authToken || '',
|
|
148
|
+
).trim(),
|
|
149
|
+
);
|
|
150
|
+
const agentApiKey = findAgentApiKey(flags);
|
|
151
|
+
const agent = !explicitOwner && (agentApiKeyExplicit || (!storedOwner && Boolean(agentApiKey)));
|
|
152
|
+
return { ledger: agent ? 'agent' : 'owner', agentApiKey, agentApiKeyExplicit };
|
|
153
|
+
}
|
|
154
|
+
|
|
135
155
|
/**
|
|
136
156
|
* The actions list for this caller, cached per API base and key: the agent's own list
|
|
137
157
|
* (`GET /api/v1/agent/actions`) whenever an agent key is set (flag, env or config, `findAgentApiKey`),
|
package/src/runtime/handlers.mjs
CHANGED
|
@@ -67,6 +67,7 @@ export function createRuntimeHandlers(deps) {
|
|
|
67
67
|
getProviderHintForActions,
|
|
68
68
|
resolveChainSupport,
|
|
69
69
|
getAuthToken,
|
|
70
|
+
buildOwnerAuthHeaders,
|
|
70
71
|
resolveAgentApiKey,
|
|
71
72
|
});
|
|
72
73
|
|
|
@@ -75,6 +76,7 @@ export function createRuntimeHandlers(deps) {
|
|
|
75
76
|
const __credits = createCreditsCommands({
|
|
76
77
|
printEnvelope,
|
|
77
78
|
getAuthToken,
|
|
79
|
+
buildOwnerAuthHeaders,
|
|
78
80
|
// __wallet is created further down; resolve it when the command runs.
|
|
79
81
|
commandWalletExecute: (...args) => __wallet.commandWalletExecute(...args),
|
|
80
82
|
});
|