@thirdfy/agent-cli 0.2.86 → 0.4.0

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 (65) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/README.md +61 -25
  3. package/package.json +2 -2
  4. package/scripts/validate-cli-contract-schemas.mjs +5 -0
  5. package/src/cli/errors.mjs +58 -8
  6. package/src/cli/help.mjs +0 -3
  7. package/src/cli/manifest.mjs +54 -27
  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 +39 -4
  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 -4
  22. package/src/commands/wallet.mjs +119 -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/core/args.mjs +10 -2
  31. package/src/core/chainRegistry.mjs +223 -1
  32. package/src/core/context.mjs +37 -14
  33. package/src/core/errorEnvelope.mjs +209 -0
  34. package/src/core/fundingTokenIn.mjs +24 -1
  35. package/src/core/http.mjs +6 -1
  36. package/src/core/readOnlyActions.mjs +10 -0
  37. package/src/core/walletExecuteChainId.mjs +25 -0
  38. package/src/lib/creditsUsdSummary.mjs +48 -4
  39. package/src/runtime/actions/actionList.mjs +87 -0
  40. package/src/runtime/actions/selection.mjs +17 -22
  41. package/src/runtime/execution/actionPreflight.mjs +50 -14
  42. package/src/runtime/execution/payloads.mjs +36 -28
  43. package/src/runtime/execution/runners.mjs +75 -21
  44. package/src/runtime/execution/svmLane.mjs +106 -0
  45. package/src/runtime/execution/walletControl.mjs +104 -0
  46. package/src/runtime/handlers.mjs +16 -2
  47. package/src/runtime/index.mjs +1 -1
  48. package/src/runtime/main.mjs +21 -5
  49. package/src/runtime/providerHints/apps.mjs +36 -0
  50. package/src/runtime/providerHints/catalogHint.mjs +55 -0
  51. package/src/runtime/providerHints/cex.mjs +130 -0
  52. package/src/runtime/providerHints/data.mjs +144 -0
  53. package/src/runtime/providerHints/dispatch.mjs +177 -0
  54. package/src/runtime/providerHints/dogeos.mjs +38 -0
  55. package/src/runtime/providerHints/earn.mjs +201 -0
  56. package/src/runtime/providerHints/perps.mjs +346 -0
  57. package/src/runtime/providerHints/prediction.mjs +128 -0
  58. package/src/runtime/providerHints/solana.mjs +85 -0
  59. package/src/runtime/providerHints/trading.mjs +78 -0
  60. package/src/runtime/providerHints/walletControl.mjs +39 -0
  61. package/src/runtime/risk/notional.mjs +59 -0
  62. package/src/runtime/risk/orderPreview.mjs +154 -0
  63. package/src/runtime/risk/riskRefusal.mjs +74 -0
  64. package/src/runtime/validation/actionParamsGate.mjs +2 -0
  65. package/src/runtime/providerHints.mjs +0 -1017
@@ -1,6 +1,9 @@
1
1
  import { createRequire } from 'module';
2
2
  import { exitAfterOutput, printEnvelope as corePrintEnvelope } from '../core/envelope.mjs';
3
3
  import { negotiateCapabilities } from '../core/http.mjs';
4
+ import { describeCliError } from '../core/errorEnvelope.mjs';
5
+ import { applyApiChainRegistry } from '../core/chainRegistry.mjs';
6
+ import { riskRefusalFields } from './risk/riskRefusal.mjs';
4
7
  import {
5
8
  buildCliProgram,
6
9
  collectRootFlags,
@@ -8,11 +11,13 @@ import {
8
11
  collectVersionFlagsFromArgv,
9
12
  normalizeLegacyShortFlags,
10
13
  } from '../cli/program.mjs';
14
+ import { isMissingSubcommand } from '../cli/errors.mjs';
11
15
  import { createInitialRuntimeContext, applyFlagsToRuntimeContext } from './context.mjs';
12
- import { createProviderHints } from './providerHints.mjs';
16
+ import { createProviderHints } from './providerHints/dispatch.mjs';
13
17
  import { createRuntimeHandlers } from './handlers.mjs';
14
18
  import { isCommanderCliError } from './errors.mjs';
15
19
  import { createOnboardingHints } from './onboardingHints.mjs';
20
+ import { walletControlErrorFields } from './execution/walletControl.mjs';
16
21
  import { createAgentExtract } from './agentExtract.mjs';
17
22
  import { createAuthHelpers } from './authHelpers.mjs';
18
23
  import { createExecutionAmounts } from './execution/amounts.mjs';
@@ -49,13 +54,15 @@ export function createThirdfyAgentRuntime() {
49
54
  if (ctx.negotiatedCapabilitiesCache) return ctx.negotiatedCapabilitiesCache;
50
55
  const capabilities = await negotiateCapabilities(ctx);
51
56
  ctx.negotiatedCapabilitiesCache = capabilities;
57
+ // The API's chain list (chain family, CAIP-2) replaces the built-in defaults.
58
+ applyApiChainRegistry(capabilities);
52
59
  return capabilities;
53
60
  }
54
61
 
55
62
  const providerHints = createProviderHints({
56
63
  getNegotiatedCapabilitiesCache: () => context.negotiatedCapabilitiesCache,
57
64
  });
58
- const { getTradingProviderHint, formatProviderAmbiguousActionHint, formatProviderUnknownActionHint } =
65
+ const { getTradingProviderHint, getProviderHintForActions, formatProviderAmbiguousActionHint, formatProviderUnknownActionHint } =
59
66
  providerHints;
60
67
 
61
68
  const { extractAgentApiKey, extractAgentKey, maskSecret } = createAgentExtract();
@@ -106,7 +113,7 @@ export function createThirdfyAgentRuntime() {
106
113
  buildOwnerAuthHeaders: auth.buildOwnerAuthHeaders,
107
114
  probeBitfinexPermissions: bitfinex.probeBitfinexPermissions,
108
115
  applyActionFilters: actions.applyActionFilters,
109
- getTradingProviderHint,
116
+ getProviderHintForActions,
110
117
  resolveChainSupport: lanes.resolveChainSupport,
111
118
  resolveAgentApiKey: payloads.resolveAgentApiKey,
112
119
  getPreparedParams: amounts.getPreparedParams,
@@ -124,6 +131,7 @@ export function createThirdfyAgentRuntime() {
124
131
  performSelfBroadcast: runners.performSelfBroadcast,
125
132
  resolveManagedExecutionPreflight: runners.resolveManagedExecutionPreflight,
126
133
  resolveEffectiveUserDid: payloads.resolveEffectiveUserDid,
134
+ resolveAgentKey: payloads.resolveAgentKey,
127
135
  loadEthers: ows.loadEthers,
128
136
  resolveOwsBinary: ows.resolveOwsBinary,
129
137
  CLI_VERSION,
@@ -170,7 +178,8 @@ export function createThirdfyAgentRuntime() {
170
178
  await handleRootOnlyFlows(cliProgram, rootFlowDeps);
171
179
  return;
172
180
  }
173
- if (error.code === 'commander.help' || error.code === 'commander.helpDisplayed') {
181
+ // Help exits 0; a command group run without its subcommand (`thirdfy-agent doctor`) fails.
182
+ if ((error.code === 'commander.help' || error.code === 'commander.helpDisplayed') && !isMissingSubcommand(cliProgram, error)) {
174
183
  return;
175
184
  }
176
185
  const exitCode = Number(error.exitCode ?? 1);
@@ -189,7 +198,8 @@ export function createThirdfyAgentRuntime() {
189
198
  const onboardingHint = onboarding.buildTopLevelOnboardingHint(payload);
190
199
  printEnvelope({
191
200
  success: false,
192
- code: statusCode ? 'API_REQUEST_FAILED' : clientCode || 'CLI_UNHANDLED_ERROR',
201
+ // A missing or malformed flag is INVALID_ARGUMENTS at the top too, not an unhandled error.
202
+ code: statusCode ? 'API_REQUEST_FAILED' : clientCode || (error?.usage === true ? 'INVALID_ARGUMENTS' : 'CLI_UNHANDLED_ERROR'),
193
203
  message: error?.message || 'Unhandled CLI error',
194
204
  data: {
195
205
  statusCode,
@@ -200,6 +210,12 @@ export function createThirdfyAgentRuntime() {
200
210
  resolvedAction: payload.resolvedAction || null,
201
211
  requestedAction: payload.requestedAction || null,
202
212
  onboardingHint,
213
+ // errorCode, retryable, retryAfterSeconds, nextAction, hint: as thirdfy-mcp's error envelope.
214
+ ...describeCliError(error),
215
+ // API 4.0 wallet-control refusals: walletControl, nextAction, userMessage, retryable, hint.
216
+ ...walletControlErrorFields(payload),
217
+ // Risk-policy refusals: riskRefusal, blockedReason, blockedStage, retryable and a one-line message.
218
+ ...riskRefusalFields(payload),
203
219
  payload,
204
220
  },
205
221
  meta: { version: CLI_VERSION, apiBase: context.apiBase },
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Static provider hints. Thirdfy internal apps: Bermuda and Arc.
3
+ * Each entry builds the hint for one provider id; `dispatch.mjs` picks the entry.
4
+ */
5
+ export const APPS_PROVIDER_HINTS = Object.freeze({
6
+ bermuda: ({ provider }) => ({
7
+ provider,
8
+ category: 'internal',
9
+ canonicalReadAction: 'get_bermuda_setup_status',
10
+ canonicalWriteAction: 'bermuda_deposit',
11
+ readActions: ['get_bermuda_setup_status', 'get_bermuda_balance'],
12
+ orderManagementActions: [
13
+ 'bermuda_deposit',
14
+ 'bermuda_transfer',
15
+ 'bermuda_withdraw',
16
+ 'bermuda_stealth_earn',
17
+ 'bermuda_stealth_exit',
18
+ 'bermuda_stealth_swap',
19
+ 'bermuda_sweep_burners',
20
+ ],
21
+ supportedChains: [84532],
22
+ laneCompatibility: 'Base Sepolia shielded accounts (USDC, WETH, MetaMorpho vault shares). Catalog is flag-gated on the API host. Deposit is sent from the execution wallet; every other write is proven and relayed, so the execution wallet never appears: transfer, withdraw, private vault entry/exit (stealth earn/exit), private swap (stealth swap, best of Thirdfy routes and Uniswap v3, fresh burner per trade) and burner sweep. The regular swap takes private: true for the same private swap. Writes are capped at 5 USD; the sweep has no cap. Private writes bill the write_private credit tier; any privacy fee is reported as privacyFee (0 at launch). Responses include privacyNotes.',
23
+ example: 'thirdfy-agent run --action get_bermuda_setup_status --params \'{"chainId":84532}\' --provider bermuda --json',
24
+ }),
25
+ arc: ({ provider }) => ({
26
+ provider,
27
+ category: 'internal',
28
+ canonicalReadAction: 'get_arc_job',
29
+ canonicalWriteAction: 'create_arc_job',
30
+ readActions: ['get_arc_job'],
31
+ orderManagementActions: ['create_arc_job', 'set_arc_job_budget', 'fund_arc_job', 'submit_arc_job', 'complete_arc_job'],
32
+ supportedChains: [5042002, 5042],
33
+ laneCompatibility: 'Arc ERC-8183 job escrow. Testnet 5042002 is live. Mainnet 5042 cash is ready. Jobs writes on 5042 need the API jobs contract. Use Privy agent_wallet. USDC pays gas. No Uniswap, Li.Fi, or Yield.xyz on Arc yet.',
34
+ example: 'thirdfy-agent run --action get_arc_job --params \'{"chainId":5042002,"jobId":1}\' --provider arc --json',
35
+ }),
36
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A provider hint built from the provider's catalog rows, for a provider with no static hint and no
3
+ * capabilities manifest: provider apps (`openclaw`, `thirdfy`, ...), the CEX venues (`binance`,
4
+ * `okx`, ...) and any provider id the catalog serves. Same rules as thirdfy-mcp `buildProviderHint`
5
+ * (`src/tools/discovery/getTradingProviderActions.ts`) for a provider without a fallback hint.
6
+ */
7
+
8
+ const WRITE_VERB_SEGMENTS = new Set(['swap', 'execute', 'deposit', 'withdraw', 'place', 'cancel', 'approve', 'stake', 'unstake', 'claim']);
9
+
10
+ const rowAction = (row) =>
11
+ String(row?.action || row?.actionKey || row?.id || '')
12
+ .trim()
13
+ .replace(/-/g, '_')
14
+ .toLowerCase();
15
+
16
+ /** The catalog row's own verdict (`behavior.readOnly`), when it has one. */
17
+ const rowReadOnly = (row) => (typeof row?.behavior?.readOnly === 'boolean' ? row.behavior.readOnly : undefined);
18
+
19
+ /** A write by the row's behavior; by its name only when the row has none. */
20
+ function isWriteRow(row) {
21
+ const key = rowAction(row);
22
+ if (!key) return false;
23
+ const readOnly = rowReadOnly(row);
24
+ if (readOnly !== undefined) return !readOnly;
25
+ if (key.startsWith('get_')) return false;
26
+ if (row?.supportsExecute === true || row?.supportsExecuteIntent === true) return true;
27
+ return key.split('_').some((segment) => WRITE_VERB_SEGMENTS.has(segment));
28
+ }
29
+
30
+ const unique = (values) => Array.from(new Set(values.filter(Boolean)));
31
+
32
+ /** The hint for `provider` from its catalog rows; null when there are none. */
33
+ export function buildCatalogProviderHint(provider, rows) {
34
+ const actions = Array.isArray(rows) ? rows : [];
35
+ if (!provider || !actions.length) return null;
36
+ const readActions = unique(actions.filter((row) => rowReadOnly(row) ?? /^get_/.test(rowAction(row))).map(rowAction));
37
+ const writeActions = unique(actions.filter(isWriteRow).map(rowAction));
38
+ const categories = unique(
39
+ actions.map((row) => String(row?.category || row?.domain || row?.providerDomain || '').trim().toLowerCase()),
40
+ );
41
+ const supportedChains = unique(
42
+ actions.flatMap((row) => {
43
+ const value = row?.supportedChains || row?.supportedChainIds || row?.chainIds || row?.chains || [];
44
+ return Array.isArray(value) ? value.map(Number).filter((id) => Number.isInteger(id) && id > 0) : [];
45
+ }),
46
+ ).sort((a, b) => a - b);
47
+ return {
48
+ provider,
49
+ ...(categories.length ? { category: categories[0], categories } : {}),
50
+ ...(readActions.length ? { readActions } : {}),
51
+ ...(writeActions.length ? { orderManagementActions: writeActions, canonicalWriteAction: writeActions[0] } : {}),
52
+ ...(supportedChains.length ? { supportedChains } : {}),
53
+ source: 'actions_catalog_manifest',
54
+ };
55
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Static provider hints. Centralized exchanges and their executors: Bitfinex, Hummingbot and Condor.
3
+ * Each entry builds the hint for one provider id; `dispatch.mjs` picks the entry.
4
+ */
5
+ export const CEX_PROVIDER_HINTS = Object.freeze({
6
+ bitfinex: ({ provider }) => ({
7
+ provider,
8
+ category: 'cex_funding_and_spot',
9
+ canonicalReadAction: 'get_bitfinex_funding_ticker',
10
+ canonicalWriteAction: 'submit_bitfinex_funding_offer',
11
+ readActions: [
12
+ 'get_bitfinex_funding_stats_hist',
13
+ 'get_bitfinex_funding_ticker',
14
+ 'get_bitfinex_funding_book',
15
+ 'get_bitfinex_wallets',
16
+ 'list_bitfinex_funding_offers',
17
+ 'list_bitfinex_funding_offers_hist',
18
+ 'list_bitfinex_funding_credits',
19
+ 'list_bitfinex_funding_loans',
20
+ 'get_bitfinex_funding_info',
21
+ 'get_bitfinex_ledgers_hist',
22
+ 'get_bitfinex_orders',
23
+ ],
24
+ orderManagementActions: [
25
+ 'submit_bitfinex_funding_offer',
26
+ 'cancel_bitfinex_funding_offer',
27
+ 'transfer_bitfinex_between_wallets',
28
+ 'get_bitfinex_orders',
29
+ 'submit_bitfinex_order',
30
+ 'update_bitfinex_order',
31
+ 'cancel_bitfinex_order',
32
+ ],
33
+ credentialType: 'api_key',
34
+ credentialRef: 'bitfinex:funding-primary',
35
+ credentialUx: 'Bitfinex credentials are owned by Thirdfy API encrypted delegation storage. CLI commands pass credentialRef only; never apiKey/apiSecret.',
36
+ laneCompatibility: 'Use `thirdfy-agent actions --provider bitfinex` for discovery, then `thirdfy-agent run --run-mode thirdfy` with credentialRef in params. The CLI does not store, log, or sign raw exchange credentials.',
37
+ example: 'thirdfy-agent actions --provider bitfinex && thirdfy-agent run --run-mode thirdfy --action submit_bitfinex_funding_offer --params \'{"symbol":"fUSD","amount":"100","rate":"0.0002","period":2,"credentialRef":"bitfinex:funding-primary"}\'',
38
+ credentialProfiles: [
39
+ {
40
+ id: 'bitfinex_funding_v1',
41
+ credentialRef: 'bitfinex:funding-primary',
42
+ required: ['funding.read', 'funding.write', 'wallets.read'],
43
+ },
44
+ {
45
+ id: 'bitfinex_spot_v1',
46
+ credentialRef: 'bitfinex:spot-primary',
47
+ required: ['orders.read', 'orders.write', 'wallets.read'],
48
+ },
49
+ ],
50
+ setupBlockers: [
51
+ 'CEX_CREDENTIAL_NOT_FOUND',
52
+ 'BITFINEX_CREDENTIALS_NOT_IN_THIRDFY_DELEGATION_CONTEXT',
53
+ 'BITFINEX_PERMISSION_SCOPE_MISSING',
54
+ 'CEX_POLICY_ACTION_NOT_ALLOWED',
55
+ ],
56
+ }),
57
+ hummingbot: ({ provider }) => ({
58
+ provider,
59
+ category: 'cex_executor',
60
+ canonicalWriteAction: 'hummingbot_executor_create',
61
+ readActions: [
62
+ 'get_hummingbot_connectors',
63
+ 'get_hummingbot_executor_schema',
64
+ 'get_hummingbot_gateway_networks',
65
+ 'get_hummingbot_gateway_connectors',
66
+ ],
67
+ orderManagementActions: ['hummingbot_executor_create', 'hummingbot_executor_stop'],
68
+ setupActions: [
69
+ 'get_hummingbot_connectors',
70
+ 'get_hummingbot_gateway_networks',
71
+ 'get_hummingbot_gateway_connectors',
72
+ ],
73
+ laneCompatibility: 'Prefer Thirdfy /api/v1/agent/hummingbot/* governed routes for writes. Gateway discovery uses catalog reads get_hummingbot_gateway_networks and get_hummingbot_gateway_connectors (runMode=thirdfy).',
74
+ example: 'thirdfy-agent hummingbot gateway-networks && thirdfy-agent hummingbot executor-stop --executor-id <id>',
75
+ canonicalReadAction: 'get_hummingbot_connectors',
76
+ credentialType: 'api_key',
77
+ credentialProfiles: [
78
+ {
79
+ id: 'binance_perpetual_v1',
80
+ credentialRef: 'binance_perpetual:primary',
81
+ required: ['trade.read', 'trade.write'],
82
+ },
83
+ {
84
+ id: 'okx_perpetual_v1',
85
+ credentialRef: 'okx_perpetual:primary',
86
+ required: ['trade.read', 'trade.write'],
87
+ },
88
+ ],
89
+ credentialUx: 'Hummingbot connector credentials sync from Thirdfy CEX delegation upsert into the org HB API account namespace. The CLI passes orgId and credentialRef only.',
90
+ setupBlockers: [
91
+ 'HUMMINGBOT_INSTANCE_NOT_CONFIGURED',
92
+ 'HUMMINGBOT_CREDENTIAL_NOT_FOUND',
93
+ 'HUMMINGBOT_GOVERNANCE_DENIED',
94
+ 'HUMMINGBOT_CONNECTOR_REQUIRED',
95
+ ],
96
+ }),
97
+ condor: ({ provider }) => ({
98
+ provider,
99
+ category: 'cex_executor',
100
+ canonicalWriteAction: 'condor_executor_create',
101
+ readActions: ['get_condor_policy_status'],
102
+ orderManagementActions: ['condor_executor_create', 'condor_executor_stop'],
103
+ setupActions: ['get_condor_policy_status'],
104
+ laneCompatibility: 'Use `thirdfy-agent run --run-mode thirdfy` for get_condor_policy_status, condor_executor_create, and condor_executor_stop. Preflight reservations use governance action condor.executor.create (CLI condor preflight). Do not bypass Pattern A with raw HB writes from Condor runtimes.',
105
+ example: 'thirdfy-agent condor policy-status && thirdfy-agent condor preflight --estimated-amount-usd 25 --idempotency-key <key>',
106
+ canonicalReadAction: 'get_condor_policy_status',
107
+ credentialType: 'api_key',
108
+ credentialProfiles: [
109
+ {
110
+ id: 'okx_perpetual_v1',
111
+ credentialRef: 'okx_perpetual:primary',
112
+ required: ['trade.read', 'trade.write'],
113
+ },
114
+ {
115
+ id: 'binance_perpetual_v1',
116
+ credentialRef: 'binance_perpetual:primary',
117
+ required: ['trade.read', 'trade.write'],
118
+ },
119
+ ],
120
+ credentialUx: 'Condor Pattern A uses Thirdfy policy and day-cap reservations. The CLI passes orgId/runtimeId only; exchange keys stay in Hummingbot.',
121
+ setupBlockers: [
122
+ 'PATTERN_A_REQUIRED',
123
+ 'CREDITS_EXHAUSTED',
124
+ 'CONNECTOR_NOT_ALLOWED',
125
+ 'NOTIONAL_CAP',
126
+ 'DAILY_NOTIONAL_CAP',
127
+ 'ACTION_NOT_SUPPORTED',
128
+ ],
129
+ }),
130
+ });
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Static provider hints. Read-only data and research providers, including the OpenMarket provider apps.
3
+ * Each entry builds the hint for one provider id; `dispatch.mjs` picks the entry.
4
+ */
5
+ export const DATA_PROVIDER_HINTS = Object.freeze({
6
+ fomo: ({ provider }) => ({
7
+ provider,
8
+ category: 'smart_money_intel',
9
+ canonicalReadAction: 'get_fomo_alerts',
10
+ readActions: [
11
+ 'get_fomo_search',
12
+ 'get_fomo_user',
13
+ 'get_fomo_balances',
14
+ 'get_fomo_leaderboard',
15
+ 'get_fomo_alerts',
16
+ ],
17
+ supportedChains: [],
18
+ laneCompatibility: 'Fomo API (fomoapi.io) uses string chain slugs (robinhood, solana, base, bsc, eth), not numeric chainId. Unofficial integration; not the Fomo consumer app. Read-only handle intel and app alerts via Thirdfy API. Pace calls (~1 req/s).',
19
+ example: 'thirdfy-agent run --action get_fomo_alerts --params \'{"chain":"robinhood","type":"buy","limit":5}\' --provider fomo --json',
20
+ }),
21
+ gmgn: ({ provider }) => ({
22
+ provider,
23
+ category: 'smart_money_intel',
24
+ canonicalReadAction: 'get_gmgn_kol_trades',
25
+ readActions: [
26
+ 'get_gmgn_token_info',
27
+ 'get_gmgn_token_security',
28
+ 'get_gmgn_token_pool',
29
+ 'get_gmgn_token_holders',
30
+ 'get_gmgn_token_traders',
31
+ 'get_gmgn_token_kline',
32
+ 'get_gmgn_trending',
33
+ 'get_gmgn_trenches',
34
+ 'get_gmgn_hot_searches',
35
+ 'get_gmgn_wallet_activity',
36
+ 'get_gmgn_wallet_stats',
37
+ 'get_gmgn_wallet_token_balance',
38
+ 'get_gmgn_created_tokens',
39
+ 'get_gmgn_kol_trades',
40
+ 'get_gmgn_smartmoney_trades',
41
+ ],
42
+ supportedChains: [],
43
+ laneCompatibility: 'GMGN uses string chain slugs (sol, base, bsc, eth, robinhood), not numeric chainId. Read-only evidence via Thirdfy API. Pace calls (~1 req/s).',
44
+ example: 'thirdfy-agent run --action get_gmgn_kol_trades --params \'{"chain":"sol","limit":5}\' --provider gmgn --json',
45
+ }),
46
+ nansen: ({ provider }) => ({
47
+ provider,
48
+ category: 'research_intel',
49
+ canonicalReadAction: 'get_nansen_perp_screener',
50
+ readActions: [
51
+ 'get_nansen_perp_screener',
52
+ 'get_nansen_position_intelligence',
53
+ 'get_nansen_address_perp_positions',
54
+ 'get_nansen_address_perp_trades',
55
+ 'get_nansen_address_balances',
56
+ 'get_nansen_address_pnl_summary',
57
+ 'get_nansen_flow_intelligence',
58
+ 'get_nansen_agent_fast',
59
+ 'get_nansen_agent_expert',
60
+ ],
61
+ supportedChains: [],
62
+ laneCompatibility: 'Nansen HL/TGM reads and premium research agent via Thirdfy API. Fast and expert research cost more than screener reads. Pace outbound starts (~1 req/s).',
63
+ example: 'thirdfy-agent run --action get_nansen_perp_screener --params \'{"date":{"from":"2026-08-27T00:00:00.000Z","to":"2026-08-28T00:00:00.000Z"},"pagination":{"page":1,"per_page":5}}\' --provider nansen --json',
64
+ }),
65
+ cmc: ({ provider }) => ({
66
+ provider,
67
+ category: 'research_intel',
68
+ canonicalReadAction: 'get_cmc_quotes',
69
+ readActions: [
70
+ 'get_cmc_search',
71
+ 'get_cmc_quotes',
72
+ 'get_cmc_info',
73
+ 'get_cmc_global_metrics',
74
+ 'get_cmc_fear_greed',
75
+ 'get_cmc_metrics',
76
+ 'get_cmc_technicals',
77
+ 'get_cmc_marketcap_technicals',
78
+ 'get_cmc_derivatives_metrics',
79
+ 'get_cmc_news',
80
+ 'get_cmc_search_info',
81
+ 'get_cmc_narratives',
82
+ 'get_cmc_upcoming_events',
83
+ 'get_cmc_skill',
84
+ 'get_cmc_daily_market_overview',
85
+ 'get_cmc_btc_etf_demand',
86
+ 'get_cmc_perp_contract_analysis',
87
+ 'get_cmc_market_regime',
88
+ 'get_cmc_derivatives_crowding',
89
+ ],
90
+ supportedChains: [],
91
+ laneCompatibility: 'CoinMarketCap quotes, global metrics, and composed Skill Hub briefs via Thirdfy API. Skill actions may take up to 180s. Composed briefs cost more than cheap reads.',
92
+ example: 'thirdfy-agent run --action get_cmc_quotes --params \'{"symbol":"BTC","convert":"USD"}\' --provider cmc --json',
93
+ }),
94
+ 'market-data': ({ provider }) => ({
95
+ provider,
96
+ category: 'market_data',
97
+ canonicalReadAction: 'get_trending_tokens',
98
+ readActions: [
99
+ 'get_trending_tokens',
100
+ 'get_new_token_launches',
101
+ 'get_token_market_data',
102
+ 'get_chain_market_overview',
103
+ ],
104
+ supportedChains: [8453, 84532, 4663],
105
+ laneCompatibility: 'Read-only market data via API-owned actions. Robinhood Chain uses CoinGecko Onchain network id robinhood.',
106
+ }),
107
+ openmarket: ({ provider }) => ({
108
+ provider,
109
+ category: 'market_data',
110
+ canonicalReadAction: 'get_openmarket_candles',
111
+ readActions: [
112
+ 'get_openmarket_markets',
113
+ 'get_openmarket_candles',
114
+ 'get_openmarket_open_interest',
115
+ 'get_openmarket_funding',
116
+ 'get_openmarket_liquidations',
117
+ 'get_openmarket_order_flow',
118
+ 'get_openmarket_prediction_markets',
119
+ 'get_openmarket_hl_perps_snapshot',
120
+ 'get_openmarket_btc_funding',
121
+ 'get_openmarket_hl_funding',
122
+ 'get_openmarket_hl_open_interest',
123
+ 'get_openmarket_polymarket_top_markets',
124
+ ],
125
+ supportedChains: [],
126
+ laneCompatibility: 'Read-only provider app (no wallet). Series reads take exchange (venue id), symbol (the venue\'s own symbol: BTC on HYPERLIQUID_FUTURES, BTCUSDT on BINANCE_FUTURES), interval and period in seconds (at most 601200). Find symbols with get_openmarket_markets.',
127
+ example: 'thirdfy-agent run --action get_openmarket_candles --params \'{"exchange":"HYPERLIQUID_FUTURES","symbol":"ETH","interval":"HOUR","period":21600}\' --json',
128
+ }),
129
+ 'openmarket-metrics': ({ provider }) => ({
130
+ provider,
131
+ category: 'market_data',
132
+ canonicalReadAction: 'get_openmarket_metrics_indicators',
133
+ readActions: [
134
+ 'get_openmarket_metrics_indicators',
135
+ 'get_openmarket_metrics_volume_profile',
136
+ 'get_openmarket_metrics_screen',
137
+ 'get_openmarket_metrics_market_screen',
138
+ 'get_openmarket_metrics_symbol_resolve',
139
+ ],
140
+ supportedChains: [],
141
+ laneCompatibility: 'Read-only provider app (no wallet). Indicators warm up on about 4x their period over 167h of history: use FIFTEEN_MINUTES or HOUR bars; a value that cannot be computed returns ok false with detail.reason.',
142
+ example: 'thirdfy-agent run --action get_openmarket_metrics_indicators --params \'{"selector":{"symbol":"BTC","exchange":"HYPERLIQUID_FUTURES","interval":"FIFTEEN_MINUTES"},"queries":[{"metric":"rsi","params":{"period":14}}]}\' --json',
143
+ }),
144
+ });
@@ -0,0 +1,177 @@
1
+ import { normalizeVenueProviderId } from '../../core/venueBrand.mjs';
2
+ import { configuredSvmChainIds } from '../../core/chainRegistry.mjs';
3
+ import { PERPS_PROVIDER_HINTS, verantaNamingFields } from './perps.mjs';
4
+ import { PREDICTION_PROVIDER_HINTS } from './prediction.mjs';
5
+ import { TRADING_PROVIDER_HINTS } from './trading.mjs';
6
+ import { EARN_PROVIDER_HINTS } from './earn.mjs';
7
+ import { CEX_PROVIDER_HINTS } from './cex.mjs';
8
+ import { DATA_PROVIDER_HINTS } from './data.mjs';
9
+ import { APPS_PROVIDER_HINTS } from './apps.mjs';
10
+ import { SOLANA_PROVIDER_HINTS } from './solana.mjs';
11
+ import { DOGEOS_PROVIDER_HINTS } from './dogeos.mjs';
12
+ import { walletControlProviderNote } from './walletControl.mjs';
13
+ import { buildCatalogProviderHint } from './catalogHint.mjs';
14
+
15
+ /** Every static provider hint, by provider id, from the family files in this folder. */
16
+ const STATIC_PROVIDER_HINTS = Object.freeze({
17
+ ...PERPS_PROVIDER_HINTS,
18
+ ...PREDICTION_PROVIDER_HINTS,
19
+ ...TRADING_PROVIDER_HINTS,
20
+ ...EARN_PROVIDER_HINTS,
21
+ ...CEX_PROVIDER_HINTS,
22
+ ...DATA_PROVIDER_HINTS,
23
+ ...APPS_PROVIDER_HINTS,
24
+ ...SOLANA_PROVIDER_HINTS,
25
+ ...DOGEOS_PROVIDER_HINTS,
26
+ });
27
+
28
+ /** Provider ids with a static hint (read by `scripts/validate-provider-parity.mjs`). */
29
+ export const STATIC_HINT_PROVIDER_IDS = Object.freeze(Object.keys(STATIC_PROVIDER_HINTS));
30
+
31
+ /**
32
+ * Providers whose static hint is built on top of the capabilities manifest's hint instead of being
33
+ * replaced by it. Every other provider answers with the manifest's hint when the API sends one.
34
+ */
35
+ const MERGES_MANIFEST_HINT = new Set(['hyperliquid']);
36
+
37
+ /**
38
+ * `walletControlNote` for the hint's provider when one applies (thirdfy-mcp `walletControlProviderNote`),
39
+ * and, for a Solana provider on an API that lists no svm chain, an `availability` note so the agent
40
+ * does not try a chain that is refused (thirdfy-mcp `providerHintFor`).
41
+ */
42
+ function withWalletControlNote(hint) {
43
+ const note = walletControlProviderNote(hint.provider, hint);
44
+ const solanaOff = Object.prototype.hasOwnProperty.call(SOLANA_PROVIDER_HINTS, hint.provider) && configuredSvmChainIds().length === 0;
45
+ if (!note && !solanaOff) return hint;
46
+ return {
47
+ ...hint,
48
+ ...(note ? { walletControlNote: note } : {}),
49
+ ...(solanaOff
50
+ ? { availability: 'Solana execution is not enabled on this Thirdfy API (its capabilities list no svm chain); chain 101 is refused with SOLANA_NOT_ENABLED.' }
51
+ : {}),
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Static and capabilities-backed provider discovery hints for CLI actions filtering.
57
+ */
58
+ export function createProviderHints({ getNegotiatedCapabilitiesCache }) {
59
+ function getTradingProviderHint(providerId) {
60
+ // `veranta` / `veranta-vault` resolve to the internal `avantis` / `avantis-vault` hints.
61
+ const provider = normalizeVenueProviderId(providerId || '');
62
+ const manifestHint = getCachedProviderHintFromCapabilities(provider);
63
+ const setupHints =
64
+ manifestHint?.setupHints && typeof manifestHint.setupHints === 'object' ? manifestHint.setupHints : {};
65
+ const buildHint = Object.prototype.hasOwnProperty.call(STATIC_PROVIDER_HINTS, provider)
66
+ ? STATIC_PROVIDER_HINTS[provider]
67
+ : null;
68
+ if (buildHint && MERGES_MANIFEST_HINT.has(provider)) return withWalletControlNote(buildHint({ provider, manifestHint, setupHints }));
69
+ if (manifestHint) return withWalletControlNote(manifestHint);
70
+ return buildHint ? withWalletControlNote(buildHint({ provider, manifestHint, setupHints })) : null;
71
+ }
72
+
73
+ /**
74
+ * The hint for `actions --provider`: the static or manifest hint, else one built from the
75
+ * provider's catalog rows (provider apps, CEX venues, any id the catalog serves), as thirdfy-mcp
76
+ * `getProviderActions` answers it.
77
+ */
78
+ function getProviderHintForActions(providerId, rows) {
79
+ const hint = getTradingProviderHint(providerId);
80
+ if (hint || !providerId) return hint;
81
+ const catalogHint = buildCatalogProviderHint(normalizeVenueProviderId(providerId), rows);
82
+ return catalogHint ? withWalletControlNote(catalogHint) : null;
83
+ }
84
+
85
+ function getCachedProviderHintFromCapabilities(provider) {
86
+ const normalizedProvider = normalizeVenueProviderId(provider || '');
87
+ if (!normalizedProvider) return null;
88
+ const manifests = Array.isArray(getNegotiatedCapabilitiesCache()?.providers?.manifests)
89
+ ? getNegotiatedCapabilitiesCache().providers.manifests
90
+ : [];
91
+ const match = manifests.find((entry) => normalizeVenueProviderId(entry?.providerId || '') === normalizedProvider);
92
+ if (!match) return null;
93
+ const canonicalWriteActions = {
94
+ trading: 'swap',
95
+ hyperliquid: 'place_hyperliquid_perps_order',
96
+ lighter: 'place_lighter_perps_order',
97
+ polymarket: 'place_polymarket_order',
98
+ prediction: 'place_prediction_order',
99
+ earn: 'deposit_earn_position',
100
+ morpho: 'deposit_earn_position',
101
+ aave: 'deposit_earn_position',
102
+ aegis: 'deposit_aegis_lender',
103
+ basedbid: 'basedbid_create_lbp',
104
+ bermuda: 'bermuda_deposit',
105
+ arc: 'create_arc_job',
106
+ curve: 'deposit_earn_position',
107
+ uniswap: 'mint_uniswap_position',
108
+ ratehopper: 'setup_ratehopper_cash_lp',
109
+ 'vaults-fyi': 'deposit_earn_position',
110
+ 'yield-xyz': 'deposit_earn_position',
111
+ };
112
+ const canonicalReadActions = {
113
+ earn: 'get_earning_opportunities',
114
+ morpho: 'get_morpho_vaults',
115
+ aave: 'get_aave_markets',
116
+ aegis: 'get_aegis_pools',
117
+ curve: 'get_curve_pools',
118
+ uniswap: 'get_uniswap_pools',
119
+ ratehopper: 'get_ratehopper_agents',
120
+ 'vaults-fyi': 'get-vaults-fyi-vaults',
121
+ 'yield-xyz': 'get_yield_xyz_opportunities',
122
+ 'market-data': 'get_trending_tokens',
123
+ fomo: 'get_fomo_alerts',
124
+ gmgn: 'get_gmgn_kol_trades',
125
+ nansen: 'get_nansen_perp_screener',
126
+ basedbid: 'basedbid_get_tokens',
127
+ bermuda: 'get_bermuda_setup_status',
128
+ arc: 'get_arc_job',
129
+ cmc: 'get_cmc_quotes',
130
+ prediction: 'get_prediction_markets',
131
+ };
132
+ return {
133
+ provider: normalizedProvider,
134
+ category: match.category || match.domain || match.providerKind || null,
135
+ ...(canonicalReadActions[normalizedProvider] ? { canonicalReadAction: canonicalReadActions[normalizedProvider] } : {}),
136
+ ...(canonicalWriteActions[normalizedProvider] ? { canonicalWriteAction: canonicalWriteActions[normalizedProvider] } : {}),
137
+ providerKind: match.providerKind || null,
138
+ capabilities: Array.isArray(match.capabilities) ? match.capabilities : [],
139
+ supportedChains: Array.isArray(match.supportedChains) ? match.supportedChains : [],
140
+ custodyMode: match.custodyMode || null,
141
+ description: match.description || null,
142
+ source: 'cli_capabilities_manifest',
143
+ ...verantaNamingFields(normalizedProvider),
144
+ ...(match.setupHints && typeof match.setupHints === 'object' ? { setupHints: match.setupHints } : {}),
145
+ };
146
+ }
147
+
148
+ // Veranta hints carry `providerAliases`; show the public name in messages (`--provider veranta`).
149
+ function providerLabelForHint(providerHint) {
150
+ const alias = Array.isArray(providerHint?.providerAliases) ? providerHint.providerAliases[0] : null;
151
+ return alias || providerHint.provider;
152
+ }
153
+
154
+ function formatProviderAmbiguousActionHint(providerHint) {
155
+ if (!providerHint) return '';
156
+ const p = providerLabelForHint(providerHint);
157
+ const w = providerHint.canonicalWriteAction;
158
+ if (w) return ` Provider ${p} expects ${w}.`;
159
+ return ` Provider ${p} did not declare a canonical write action in capabilities; run actions --provider ${p} first.`;
160
+ }
161
+
162
+ function formatProviderUnknownActionHint(providerHint) {
163
+ if (!providerHint) return '';
164
+ const p = providerLabelForHint(providerHint);
165
+ const w = providerHint.canonicalWriteAction;
166
+ if (w) return ` Provider ${p} expects ${w}; run actions --provider ${p} first.`;
167
+ return ` Provider ${p} did not declare a canonical write action in capabilities; run actions --provider ${p} first.`;
168
+ }
169
+
170
+ return {
171
+ getTradingProviderHint,
172
+ getProviderHintForActions,
173
+ getCachedProviderHintFromCapabilities,
174
+ formatProviderAmbiguousActionHint,
175
+ formatProviderUnknownActionHint,
176
+ };
177
+ }