@thirdfy/agent-cli 0.1.22 → 0.1.23

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 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.1.23] - 2026-05-02
8
+
9
+ **npm:** [`@thirdfy/agent-cli@0.1.23`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.23)
10
+ **Git tag:** `v0.1.23`
11
+
12
+ ### Added
13
+
14
+ - Provider discovery hints for `earn`, `morpho`, `curve`, and generic `prediction` so agents can filter action catalogs by domain without hard-coded protocol-specific flows.
15
+ - Curve/Base 4pool write hints surfaced through the actions provider filter for earn flows.
16
+
17
+ ### Fixed
18
+
19
+ - **Provider filter:** Case-insensitive matching for earn-related action keys when filtering catalogs by provider.
20
+ - **Self custody:** `preflight` and self-exec paths reject offchain venue order actions before `build-tx`, aligned with venue execution guardrails.
21
+
7
22
  ## [0.1.22] - 2026-04-28
8
23
 
9
24
  **npm:** [`@thirdfy/agent-cli@0.1.22`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.22)
@@ -374,6 +374,212 @@ function shouldApplySwapAmountContract(actionName) {
374
374
  return action === 'swap' || /(^|:|-)swap$/.test(action);
375
375
  }
376
376
 
377
+ function getTradingProviderHint(providerId) {
378
+ const provider = String(providerId || '').trim().toLowerCase();
379
+ const manifestHint = getCachedProviderHintFromCapabilities(provider);
380
+ if (manifestHint) return manifestHint;
381
+ if (provider === 'trading') {
382
+ return {
383
+ provider,
384
+ category: 'onchain_spot',
385
+ canonicalWriteAction: 'swap',
386
+ credentialUx: 'Use normal Thirdfy login/delegation; do not use this provider for Hyperliquid or Polymarket.',
387
+ };
388
+ }
389
+ if (provider === 'hyperliquid') {
390
+ return {
391
+ provider,
392
+ category: 'leveraged_perps',
393
+ canonicalWriteAction: 'place_hyperliquid_perps_order',
394
+ readActions: [
395
+ 'get_hyperliquid_perps_meta',
396
+ 'get_hyperliquid_all_mids',
397
+ 'get_hyperliquid_l2_book',
398
+ 'get_hyperliquid_candles',
399
+ 'get_hyperliquid_user_state',
400
+ 'get_hyperliquid_open_orders',
401
+ 'get_hyperliquid_onboarding_plan',
402
+ 'get_hyperliquid_referral_status',
403
+ 'get_hyperliquid_builder_fee_status',
404
+ ],
405
+ orderManagementActions: [
406
+ 'place_hyperliquid_perps_order',
407
+ 'get_hyperliquid_open_orders',
408
+ ],
409
+ setupActions: [
410
+ 'get_hyperliquid_setup_status',
411
+ 'get_hyperliquid_onboarding_plan',
412
+ 'prepare_hyperliquid_onboarding',
413
+ 'get_bridge_quote',
414
+ 'execute_bridge',
415
+ 'approve_hyperliquid_agent',
416
+ 'get_hyperliquid_builder_fee_status',
417
+ 'approve_hyperliquid_builder_fee',
418
+ 'set_hyperliquid_referrer',
419
+ 'deposit_hyperliquid_bridge',
420
+ 'deposit_hyperliquid_staking',
421
+ 'claim_hyperliquid_testnet_faucet',
422
+ ],
423
+ statusActions: ['get_hyperliquid_setup_status', 'get_hyperliquid_builder_fee_status', 'get_hyperliquid_referral_status'],
424
+ credentialType: 'hyperliquid_api_wallet',
425
+ credentialUx:
426
+ 'Thirdfy manages Hyperliquid API-wallet setup for normal users. Delegated writes require encrypted privateKey-backed hyperliquid_api_wallet credentials; walletAddress-only records are rejected until Privy walletId-backed Hyperliquid signing ships.',
427
+ recommendedFundingChainId: 42161,
428
+ supportedFundingSourceChains: [
429
+ { chainId: 42161, name: 'Arbitrum One', path: 'native USDC -> Bridge2' },
430
+ { chainId: 8453, name: 'Base', path: 'Li.Fi -> Arbitrum native USDC -> Bridge2' },
431
+ { chainId: 1, name: 'Ethereum', path: 'Li.Fi -> Arbitrum native USDC -> Bridge2' },
432
+ { chainId: 137, name: 'Polygon', path: 'Li.Fi -> Arbitrum native USDC -> Bridge2' },
433
+ ],
434
+ setupBlockers: [
435
+ 'HYPERLIQUID_SOURCE_FUNDS_REQUIRED',
436
+ 'HYPERLIQUID_ARBITRUM_USDC_REQUIRED',
437
+ 'HYPERLIQUID_BRIDGE2_DEPOSIT_REQUIRED',
438
+ 'HYPERLIQUID_API_WALLET_APPROVAL_REQUIRED',
439
+ 'HYPERLIQUID_BUILDER_APPROVAL_REQUIRED',
440
+ 'HYPERLIQUID_POLICY_REQUIRED',
441
+ 'HYPERLIQUID_CREDITS_REQUIRED',
442
+ 'HYPERLIQUID_API_WALLET_SIGNER_REQUIRED',
443
+ ],
444
+ sourceBridgeProvider: 'lifi',
445
+ funding:
446
+ 'Best path: Base/Ethereum/Polygon USDC -> Li.Fi bridge to native Arbitrum USDC -> Hyperliquid Bridge2. If the user already has native Circle USDC on Arbitrum One (42161), skip Li.Fi and call deposit_hyperliquid_bridge. Minimum 5 USDC; Bridge2 credits the sender/source wallet.',
447
+ laneCompatibility:
448
+ 'Use self only for setup signatures or Arbitrum funding transactions. Perps orders should use thirdfy, hybrid, or agent_wallet.',
449
+ setupExample:
450
+ 'actions --provider hyperliquid -> get_hyperliquid_onboarding_plan -> prepare_hyperliquid_onboarding -> get_bridge_quote/execute_bridge if source is Base -> deposit_hyperliquid_bridge on 42161 -> approve_hyperliquid_agent -> approve_hyperliquid_builder_fee if required -> get_hyperliquid_setup_status',
451
+ example:
452
+ 'thirdfy-agent actions --provider hyperliquid && thirdfy-agent preflight --action place_hyperliquid_perps_order --params \'{"coin":"ETH","isBuy":true,"size":0.01,"limitPx":3500,"orderType":"market"}\'',
453
+ };
454
+ }
455
+ if (provider === 'polymarket') {
456
+ return {
457
+ provider,
458
+ category: 'prediction_market',
459
+ canonicalWriteAction: 'place_polymarket_order',
460
+ readActions: [
461
+ 'get_polymarket_markets',
462
+ 'get_polymarket_market',
463
+ 'get_polymarket_order_book',
464
+ 'get_polymarket_prices',
465
+ 'get_polymarket_price_history',
466
+ 'get_polymarket_setup_status',
467
+ 'get_polymarket_order',
468
+ 'get_polymarket_user_orders',
469
+ ],
470
+ orderManagementActions: [
471
+ 'place_polymarket_order',
472
+ 'get_polymarket_order',
473
+ 'get_polymarket_user_orders',
474
+ 'cancel_polymarket_order',
475
+ ],
476
+ setupActions: [
477
+ 'get_polymarket_setup_status',
478
+ 'agent_wallet_setup:ensure_agent_wallet_safe',
479
+ 'agent_wallet_setup:deploy_safe_and_set_approvals',
480
+ 'agent_wallet_setup:create_or_derive_clob_credentials',
481
+ 'browser_setup:add_privy_signer',
482
+ 'browser_setup:create_or_derive_clob_credentials',
483
+ 'browser_setup:deploy_safe_and_set_approvals',
484
+ ],
485
+ statusActions: ['get_polymarket_setup_status'],
486
+ credentialType: 'polymarket_clob',
487
+ credentialUx:
488
+ 'Autonomous agents use an agent-owned Privy wallet/Safe first: Thirdfy creates the agent wallet, deploys/approves the Safe, and stores encrypted CLOB credentials once Polymarket key creation is available. User-owned browser setup remains optional for interactive capital.',
489
+ funding:
490
+ 'Fund the agent-owned Polymarket Safe with Polygon USDC/pUSD collateral after Safe deployment and approvals.',
491
+ laneCompatibility:
492
+ 'Use agent_wallet as the preferred autonomous-agent path. Use self only for user-owned setup/funding signatures. CLOB orders should use thirdfy, hybrid, or agent_wallet after polymarket_clob credentials are stored.',
493
+ setupBlockers: [
494
+ 'POLYMARKET_AGENT_WALLET_SAFE_REQUIRED',
495
+ 'POLYMARKET_CLOB_CREDENTIALS_REQUIRED',
496
+ 'POLYMARKET_SAFE_DEPLOYMENT_REQUIRED',
497
+ 'POLYMARKET_SAFE_APPROVALS_REQUIRED',
498
+ 'POLYMARKET_USDC_REQUIRED',
499
+ 'POLYMARKET_CREDITS_REQUIRED',
500
+ 'POLYMARKET_RELAYER_UNAVAILABLE',
501
+ 'POLYMARKET_CLOB_KEY_CREATION_BLOCKED_UNTIL_POLYMARKET_FIX',
502
+ ],
503
+ example:
504
+ 'thirdfy-agent actions --provider polymarket && thirdfy-agent preflight --action place_polymarket_order --params \'{"tokenID":"...","price":0.42,"size":10,"side":"BUY"}\'',
505
+ };
506
+ }
507
+ if (provider === 'prediction') {
508
+ return {
509
+ provider,
510
+ category: 'prediction',
511
+ canonicalWriteAction: 'place_prediction_order',
512
+ readActions: ['get_prediction_markets', 'get_prediction_market', 'get_prediction_order_book'],
513
+ orderManagementActions: ['place_prediction_order', 'cancel_prediction_order'],
514
+ compatibilityProvider: 'polymarket',
515
+ };
516
+ }
517
+ if (provider === 'earn') {
518
+ return {
519
+ provider,
520
+ category: 'earn',
521
+ canonicalReadAction: 'get_earn_opportunities',
522
+ canonicalWriteAction: 'deposit_earn_position',
523
+ readActions: ['get_earn_opportunities', 'get_earn_provider', 'get_earn_position'],
524
+ orderManagementActions: ['deposit_earn_position', 'withdraw_earn_position'],
525
+ laneCompatibility:
526
+ 'Generic earn writes use deposit_earn_position / withdraw_earn_position on normal Thirdfy policy/delegation rails; routing selects Morpho vault calldata or the validated Curve Base 4pool adapter on Base (8453) per opportunity.',
527
+ };
528
+ }
529
+ if (provider === 'morpho') {
530
+ return {
531
+ provider,
532
+ category: 'earn',
533
+ canonicalReadAction: 'get_morpho_vaults',
534
+ canonicalWriteAction: 'deposit_earn_position',
535
+ readActions: ['get_morpho_vaults', 'get_earn_provider', 'get_earn_position'],
536
+ orderManagementActions: ['deposit_earn_position', 'withdraw_earn_position'],
537
+ supportedChains: [8453, 84532],
538
+ laneCompatibility: 'Morpho deposits require ERC-20 approvals to the vault/market routes surfaced by the catalog before deposit_earn_position.',
539
+ example:
540
+ 'thirdfy-agent actions --provider morpho && thirdfy-agent run --action deposit_earn_position --params \'{"providerId":"morpho","vaultAddress":"0x...","tokenAddress":"0x...","amount":"10","chainId":8453}\'',
541
+ };
542
+ }
543
+ if (provider === 'curve') {
544
+ return {
545
+ provider,
546
+ category: 'earn',
547
+ canonicalReadAction: 'get_curve_pools',
548
+ canonicalWriteAction: 'deposit_earn_position',
549
+ readActions: ['get_curve_pools', 'get_earn_opportunities', 'get_earn_provider', 'get_earn_position'],
550
+ orderManagementActions: ['deposit_earn_position', 'withdraw_earn_position'],
551
+ supportedChains: [8453],
552
+ laneCompatibility:
553
+ 'Curve EVM writes are limited to the validated Base mainnet 4pool adapter; other pools remain discovery-only until per-pool calldata is validated.',
554
+ };
555
+ }
556
+ return null;
557
+ }
558
+
559
+ function getCachedProviderHintFromCapabilities(_provider) {
560
+ return null;
561
+ }
562
+
563
+ const OFFCHAIN_VENUE_ORDER_ACTIONS = new Set([
564
+ 'place_hyperliquid_perps_order',
565
+ 'place-hyperliquid-perps-order',
566
+ 'place_polymarket_order',
567
+ 'place-polymarket-order',
568
+ ]);
569
+
570
+ function enforceOffchainVenueLaneCompatibility(action, runMode) {
571
+ const normalizedAction = String(action || '').trim().toLowerCase();
572
+ if (runMode !== 'self' || !OFFCHAIN_VENUE_ORDER_ACTIONS.has(normalizedAction)) return;
573
+ throw createCliError(
574
+ 'OFFCHAIN_VENUE_ORDER_SELF_UNSUPPORTED',
575
+ 'Polymarket CLOB and Hyperliquid perps orders are offchain signed API actions, not EVM build-tx actions. Use self for setup/funding signatures, then run venue orders with --run-mode thirdfy, --run-mode hybrid, or --run-mode agent_wallet.',
576
+ {
577
+ recommendedRunModes: ['thirdfy', 'hybrid', 'agent_wallet'],
578
+ setupOnlyRunMode: 'self',
579
+ }
580
+ );
581
+ }
582
+
377
583
  function createCliError(code, message, payload = {}) {
378
584
  const error = new Error(message);
379
585
  error.payload = {
@@ -630,6 +836,7 @@ async function commandTrackAction(ctx, flags, capabilities) {
630
836
  async function commandActions(ctx, flags, capabilities) {
631
837
  const policyAware = Boolean(flags.agentApiKey);
632
838
  const actions = applyActionFilters(await getCachedActionsCatalog(ctx, flags), flags);
839
+ const providerHint = getTradingProviderHint(flags.provider);
633
840
  const effectiveRunMode = getEffectiveRunMode(ctx, flags);
634
841
  const chainIdFilter = Number(flags.chainId || 0) || undefined;
635
842
  const chainSupport = resolveChainSupport(capabilities, chainIdFilter, effectiveRunMode);
@@ -637,7 +844,7 @@ async function commandActions(ctx, flags, capabilities) {
637
844
  success: true,
638
845
  code: 'OK',
639
846
  message: 'Actions loaded',
640
- data: { actions },
847
+ data: { actions, ...(providerHint ? { providerHint } : {}) },
641
848
  meta: {
642
849
  apiBase: ctx.apiBase,
643
850
  policyAware,
@@ -653,6 +860,7 @@ async function commandPreflight(ctx, flags, capabilities) {
653
860
  const resolved = await resolveActionSelection(ctx, flags);
654
861
  prepareActionParamsForFlags(flags, resolved.resolvedAction);
655
862
  const runMode = getEffectiveRunMode(ctx, flags);
863
+ enforceOffchainVenueLaneCompatibility(resolved.resolvedAction, runMode);
656
864
  const hybridWalletMode = runMode === 'hybrid' ? normalizeHybridWalletMode(flags.hybridWalletMode) : null;
657
865
  enforceChainCompatibility(capabilities, flags, runMode);
658
866
  const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved, { hybridWalletMode });
@@ -687,6 +895,7 @@ async function commandRun(ctx, flags, capabilities) {
687
895
  const resolved = await resolveActionSelection(ctx, flags);
688
896
  prepareActionParamsForFlags(flags, resolved.resolvedAction);
689
897
  const runMode = getEffectiveRunMode(ctx, flags);
898
+ enforceOffchainVenueLaneCompatibility(resolved.resolvedAction, runMode);
690
899
  const hybridWalletMode = runMode === 'hybrid' ? normalizeHybridWalletMode(flags.hybridWalletMode) : null;
691
900
  enforceChainCompatibility(capabilities, flags, runMode);
692
901
  const skipPreflight = Boolean(flags.skipPreflight);
@@ -812,6 +1021,7 @@ async function commandWalletSign(ctx, flags, capabilities) {
812
1021
  const normalizedFlags = { ...flags, runMode: 'self' };
813
1022
  const resolved = await resolveActionSelection(ctx, normalizedFlags);
814
1023
  prepareActionParamsForFlags(normalizedFlags, resolved.resolvedAction);
1024
+ enforceOffchainVenueLaneCompatibility(resolved.resolvedAction, 'self');
815
1025
  enforceChainCompatibility(capabilities, normalizedFlags, 'self');
816
1026
  const selfResult = await executeSelfRun(ctx, normalizedFlags, resolved, {
817
1027
  skipPreflight: Boolean(normalizedFlags.skipPreflight),
@@ -1682,6 +1892,7 @@ async function commandPrompt(ctx, flags, capabilities, promptTokens) {
1682
1892
  async function commandSelfExec(ctx, flags, capabilities) {
1683
1893
  const resolved = await resolveActionSelection(ctx, flags);
1684
1894
  prepareActionParamsForFlags(flags, resolved.resolvedAction);
1895
+ enforceOffchainVenueLaneCompatibility(resolved.resolvedAction, 'self');
1685
1896
  enforceChainCompatibility(capabilities, flags, 'self');
1686
1897
  const selfResult = await executeSelfRun(ctx, flags, resolved, {
1687
1898
  skipPreflight: Boolean(flags.skipPreflight),
@@ -2969,11 +3180,22 @@ function applyActionFilters(actions, flags) {
2969
3180
  }
2970
3181
  if (flags.provider) {
2971
3182
  const expectedProvider = String(flags.provider).trim().toLowerCase();
2972
- filtered = filtered.filter((a) => String(a.provider || '').trim().toLowerCase() === expectedProvider);
3183
+ filtered = filtered.filter((a) => {
3184
+ const provider = getActionProvider(a);
3185
+ const actionKey = getActionKey(a).replace(/-/g, '_').toLowerCase();
3186
+ if (provider === expectedProvider) return true;
3187
+ if (expectedProvider === 'earn') return provider === 'morpho' || provider === 'curve' || actionKey.includes('_earn_');
3188
+ if (expectedProvider === 'prediction') return provider === 'polymarket' || actionKey.includes('_prediction_');
3189
+ return false;
3190
+ });
2973
3191
  }
2974
3192
  return filtered;
2975
3193
  }
2976
3194
 
3195
+ function getActionProvider(action) {
3196
+ return String(action?.provider || action?.providerId || action?.provider_id || action?.catalog || '').trim().toLowerCase();
3197
+ }
3198
+
2977
3199
  async function resolveActionSelection(ctx, flags) {
2978
3200
  const requestedAction = requireFlag(flags, 'action', 'Missing --action');
2979
3201
  const requestedLower = requestedAction.toLowerCase();
@@ -3009,10 +3231,11 @@ async function resolveActionSelection(ctx, flags) {
3009
3231
  return { requestedAction, resolvedAction: exactAlias[0].key, resolvedActionMeta: exactAlias[0].action || null };
3010
3232
  }
3011
3233
  if (exactAlias.length > 1) {
3234
+ const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
3012
3235
  throw new Error(
3013
3236
  `Ambiguous --action "${requestedAction}". Alias matches multiple actions: ${formatActionList(
3014
3237
  exactAlias.map((v) => v.key)
3015
- )}.`
3238
+ )}.${providerHint ? ` Provider ${providerHint.provider} expects ${providerHint.canonicalWriteAction}.` : ''}`
3016
3239
  );
3017
3240
  }
3018
3241
 
@@ -3025,14 +3248,18 @@ async function resolveActionSelection(ctx, flags) {
3025
3248
  return { requestedAction, resolvedAction: fuzzy[0].key, resolvedActionMeta: fuzzy[0].action || null };
3026
3249
  }
3027
3250
  if (fuzzy.length > 1) {
3251
+ const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
3028
3252
  throw new Error(
3029
3253
  `Ambiguous --action "${requestedAction}". Did you mean one of: ${formatActionList(
3030
3254
  fuzzy.slice(0, 10).map((v) => v.key)
3031
- )}?`
3255
+ )}?${providerHint ? ` Provider ${providerHint.provider} expects ${providerHint.canonicalWriteAction}.` : ''}`
3032
3256
  );
3033
3257
  }
3258
+ const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
3034
3259
  throw new Error(
3035
- `Unknown --action "${requestedAction}". No compatible action found in current catalog/provider scope.`
3260
+ `Unknown --action "${requestedAction}". No compatible action found in current catalog/provider scope.${
3261
+ providerHint ? ` Provider ${providerHint.provider} expects ${providerHint.canonicalWriteAction}; run actions --provider ${providerHint.provider} first.` : ''
3262
+ }`
3036
3263
  );
3037
3264
  }
3038
3265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {