@thirdfy/agent-cli 0.1.48 → 0.2.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 +51 -0
- package/README.md +16 -1
- package/bin/thirdfy-agent.mjs +3 -5106
- package/package.json +17 -4
- package/scripts/validate-npm-pack.mjs +43 -0
- package/src/cli/errors.mjs +103 -0
- package/src/cli/flags.mjs +66 -0
- package/src/cli/help.mjs +89 -0
- package/src/cli/manifest.mjs +422 -0
- package/src/cli/options/agent.mjs +9 -0
- package/src/cli/options/analytics.mjs +5 -0
- package/src/cli/options/auth.mjs +10 -0
- package/src/cli/options/bootstrap.mjs +19 -0
- package/src/cli/options/chains.mjs +3 -0
- package/src/cli/options/credentials.mjs +15 -0
- package/src/cli/options/delegation.mjs +22 -0
- package/src/cli/options/execution.mjs +24 -0
- package/src/cli/options/global.mjs +16 -0
- package/src/cli/options/index.mjs +37 -0
- package/src/cli/options/polymarket.mjs +3 -0
- package/src/cli/options/tracking.mjs +18 -0
- package/src/cli/options/wallet.mjs +7 -0
- package/src/cli/program.mjs +5 -0
- package/src/cli/register.mjs +118 -0
- package/src/commands/agent.mjs +194 -0
- package/src/commands/bootstrap.mjs +762 -0
- package/src/commands/credentials.mjs +102 -0
- package/src/commands/delegation.mjs +355 -0
- package/src/commands/discovery.mjs +77 -0
- package/src/commands/doctor.mjs +320 -0
- package/src/commands/execute.mjs +255 -0
- package/src/commands/hyperliquid.mjs +51 -0
- package/src/commands/index.mjs +6 -0
- package/src/commands/polymarket.mjs +89 -0
- package/src/commands/prompt.mjs +33 -0
- package/src/commands/telemetry.mjs +146 -0
- package/src/commands/wallet.mjs +129 -0
- package/src/core/args.mjs +68 -0
- package/src/core/constants.mjs +31 -0
- package/src/core/context.mjs +145 -0
- package/src/core/envelope.mjs +20 -0
- package/src/core/http.mjs +77 -0
- package/src/core/index.mjs +6 -0
- package/src/core/runMode.mjs +72 -0
- package/src/runtime/actions/selection.mjs +339 -0
- package/src/runtime/agentExtract.mjs +52 -0
- package/src/runtime/authHelpers.mjs +53 -0
- package/src/runtime/bitfinexProbe.mjs +121 -0
- package/src/runtime/context.mjs +28 -0
- package/src/runtime/createThirdfyAgentRuntime.mjs +2 -0
- package/src/runtime/errors.mjs +3 -0
- package/src/runtime/execution/amounts.mjs +193 -0
- package/src/runtime/execution/lanes.mjs +75 -0
- package/src/runtime/execution/payloads.mjs +148 -0
- package/src/runtime/execution/runners.mjs +702 -0
- package/src/runtime/handlers.mjs +181 -0
- package/src/runtime/index.mjs +4 -0
- package/src/runtime/main.mjs +204 -0
- package/src/runtime/onboardingHints.mjs +54 -0
- package/src/runtime/owsSigning.mjs +118 -0
- package/src/runtime/providerHints.mjs +414 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export async function apiGet(ctx, route, headers = {}) {
|
|
2
|
+
return requestJson(ctx, route, { method: 'GET', headers });
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function apiPost(ctx, route, body, headers = {}) {
|
|
6
|
+
return requestJson(ctx, route, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'content-type': 'application/json',
|
|
10
|
+
...headers,
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify(body),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function requestJson(ctx, route, init) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timeout = setTimeout(() => controller.abort(), ctx.timeoutMs);
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${ctx.apiBase}${route}`, {
|
|
21
|
+
...init,
|
|
22
|
+
signal: controller.signal,
|
|
23
|
+
});
|
|
24
|
+
const text = await response.text();
|
|
25
|
+
const data = text ? safeJsonParse(text) : {};
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const error = new Error(data?.error || data?.message || `HTTP ${response.status}`);
|
|
28
|
+
error.statusCode = response.status;
|
|
29
|
+
error.payload = data;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
} finally {
|
|
34
|
+
clearTimeout(timeout);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function requestWithRouteFallback(ctx, options) {
|
|
39
|
+
const routes = Array.isArray(options.routes) ? options.routes : [];
|
|
40
|
+
let lastError = null;
|
|
41
|
+
for (const route of routes) {
|
|
42
|
+
try {
|
|
43
|
+
if (options.method === 'GET') {
|
|
44
|
+
return await apiGet(ctx, route, options.headers || {});
|
|
45
|
+
}
|
|
46
|
+
if (options.method === 'POST') {
|
|
47
|
+
return await apiPost(ctx, route, options.body || {}, options.headers || {});
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unsupported method for fallback request: ${options.method}`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
const statusCode = Number(error?.statusCode || 0);
|
|
53
|
+
if (statusCode !== 404) throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw lastError || new Error('No route variants succeeded');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function safeJsonParse(text) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(text);
|
|
62
|
+
} catch {
|
|
63
|
+
return { raw: text };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function negotiateCapabilities(ctx) {
|
|
68
|
+
try {
|
|
69
|
+
const response = await apiGet(ctx, '/api/v1/agent/cli/capabilities');
|
|
70
|
+
return response;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (ctx.verbose) {
|
|
73
|
+
process.stderr.write(`capabilities negotiation skipped: ${error.message}\n`);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CUSTODY_MODES,
|
|
3
|
+
EFFECT_CHECK_MODES,
|
|
4
|
+
HYBRID_WALLET_MODES,
|
|
5
|
+
PROFILE_DEFAULTS,
|
|
6
|
+
RUN_MODES,
|
|
7
|
+
} from './constants.mjs';
|
|
8
|
+
import { createCliError } from './envelope.mjs';
|
|
9
|
+
|
|
10
|
+
export function normalizeProfileName(value) {
|
|
11
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
12
|
+
if (normalized in PROFILE_DEFAULTS) return normalized;
|
|
13
|
+
throw new Error(`Unsupported profile "${value}". Supported: ${Object.keys(PROFILE_DEFAULTS).join(', ')}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeRunMode(value, flagName = '--run-mode') {
|
|
17
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
18
|
+
if (!normalized) return 'thirdfy';
|
|
19
|
+
if (RUN_MODES.includes(normalized)) return normalized;
|
|
20
|
+
throw new Error(`Unsupported ${flagName} "${value}". Supported: ${RUN_MODES.join(', ')}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defaultCustodyModeForRunMode(runMode) {
|
|
24
|
+
return runMode === 'thirdfy' || runMode === 'agent_wallet' ? 'managed' : 'external';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function normalizeCustodyMode(value, runModeHint = 'thirdfy') {
|
|
28
|
+
const fallback = defaultCustodyModeForRunMode(runModeHint);
|
|
29
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
30
|
+
if (!normalized) return fallback;
|
|
31
|
+
if (CUSTODY_MODES.includes(normalized)) return normalized;
|
|
32
|
+
throw new Error(`Unsupported custody mode "${value}". Supported: ${CUSTODY_MODES.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeManagedRunMode(value) {
|
|
36
|
+
const mode = normalizeRunMode(value || 'thirdfy');
|
|
37
|
+
if (mode === 'self') {
|
|
38
|
+
throw new Error('Managed lane does not support run-mode=self. Use thirdfy, hybrid, or agent_wallet.');
|
|
39
|
+
}
|
|
40
|
+
return mode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function normalizeHybridWalletMode(value) {
|
|
44
|
+
const normalized = String(value || '').trim().toLowerCase() || 'self';
|
|
45
|
+
if (HYBRID_WALLET_MODES.includes(normalized)) return normalized;
|
|
46
|
+
throw new Error(`Unsupported --hybrid-wallet-mode "${value}". Supported: ${HYBRID_WALLET_MODES.join(', ')}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getEffectiveRunMode(ctx, flags) {
|
|
50
|
+
if (flags.runMode) return normalizeRunMode(flags.runMode);
|
|
51
|
+
return normalizeRunMode(ctx.runMode || 'thirdfy');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseBooleanFlag(value, defaultValue = false) {
|
|
55
|
+
if (value === undefined || value === null) return defaultValue;
|
|
56
|
+
if (typeof value === 'boolean') return value;
|
|
57
|
+
const normalized = String(value).trim().toLowerCase();
|
|
58
|
+
if (!normalized) return defaultValue;
|
|
59
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
60
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
61
|
+
return defaultValue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeEffectCheckMode(value) {
|
|
65
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
66
|
+
if (!normalized) return 'relaxed';
|
|
67
|
+
if (EFFECT_CHECK_MODES.includes(normalized)) return normalized;
|
|
68
|
+
throw createCliError(
|
|
69
|
+
'EFFECT_CHECK_MODE_INVALID',
|
|
70
|
+
`Unsupported --effect-check "${value}". Supported: ${EFFECT_CHECK_MODES.join(', ')}.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { requireFlag } from '../../core/args.mjs';
|
|
2
|
+
import { getCachedActionsCatalog } from '../../core/context.mjs';
|
|
3
|
+
export function createActionSelection({ getTradingProviderHint, formatProviderAmbiguousActionHint, formatProviderUnknownActionHint }) {
|
|
4
|
+
function getActionKey(action) {
|
|
5
|
+
return String(action?.action || action?.id || action?.key || action?.name || '')
|
|
6
|
+
.trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getActionAliases(action) {
|
|
10
|
+
if (!Array.isArray(action?.aliases)) return [];
|
|
11
|
+
return action.aliases
|
|
12
|
+
.map((v) => String(v || '').trim())
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function formatActionList(items) {
|
|
17
|
+
return items
|
|
18
|
+
.map((v) => `\`${v}\``)
|
|
19
|
+
.join(', ');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeProviderId(value) {
|
|
23
|
+
return String(value || '').trim().toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function rowActionKey(action) {
|
|
27
|
+
return getActionKey(action)
|
|
28
|
+
.replace(/-/g, '_')
|
|
29
|
+
.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function canonicalActionKey(action) {
|
|
33
|
+
return getActionKey(action)
|
|
34
|
+
.replace(/_/g, '-')
|
|
35
|
+
.toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inferActionProvider(action) {
|
|
39
|
+
// API catalog metadata (`providerId`, `domain`, `category`) is the source of truth.
|
|
40
|
+
// This fallback only keeps older catalog rows / older API deployments discoverable.
|
|
41
|
+
const key = canonicalActionKey(action);
|
|
42
|
+
const knownMap = {
|
|
43
|
+
swap: 'trading',
|
|
44
|
+
'execute-optimal-swap': 'trading',
|
|
45
|
+
'get-best-trading-quote': 'trading',
|
|
46
|
+
'dogeos-barkswap-get-quote': 'dogeos-barkswap',
|
|
47
|
+
'dogeos-barkswap-execute-swap': 'dogeos-barkswap',
|
|
48
|
+
'dogeos-get-ecosystem-overview': 'dogeos-ecosystem',
|
|
49
|
+
'dogeos-get-wallet-actionability': 'dogeos-ecosystem',
|
|
50
|
+
'dogeos-prepare-trade': 'dogeos-ecosystem',
|
|
51
|
+
'dogeos-prepare-token-launch': 'dogeos-ecosystem',
|
|
52
|
+
'dogeos-get-chain-insights': 'dogeos-ecosystem',
|
|
53
|
+
'dogeos-get-trending-tokens': 'dogeos-ecosystem',
|
|
54
|
+
'dogeos-get-new-tokens': 'dogeos-ecosystem',
|
|
55
|
+
'dogeos-get-early-bird-tokens': 'dogeos-ecosystem',
|
|
56
|
+
'dogeos-get-only-moon-tokens': 'dogeos-ecosystem',
|
|
57
|
+
'dogeos-get-big-wins-tokens': 'dogeos-ecosystem',
|
|
58
|
+
'dogeos-get-related-tokens': 'dogeos-ecosystem',
|
|
59
|
+
'dogeos-laika-read-contract': 'dogeos-laika',
|
|
60
|
+
'dogeos-laika-execute-raw': 'dogeos-laika',
|
|
61
|
+
'dogeos-laika-launch-token': 'dogeos-laika',
|
|
62
|
+
'dogeos-laika-buy-token': 'dogeos-laika',
|
|
63
|
+
'dogeos-laika-claim': 'dogeos-laika',
|
|
64
|
+
'dogeos-laika-token-state': 'dogeos-laika',
|
|
65
|
+
'get-governance-v2-overview': 'governance-v2',
|
|
66
|
+
'get-governance-v2-timeseries': 'governance-v2',
|
|
67
|
+
'get-governance-v2-breakdowns': 'governance-v2',
|
|
68
|
+
'get-hyperliquid-perps-meta': 'hyperliquid',
|
|
69
|
+
'get-hyperliquid-all-mids': 'hyperliquid',
|
|
70
|
+
'search-hyperliquid-markets': 'hyperliquid',
|
|
71
|
+
'get-hyperliquid-l2-book': 'hyperliquid',
|
|
72
|
+
'get-hyperliquid-candles': 'hyperliquid',
|
|
73
|
+
'get-hyperliquid-user-state': 'hyperliquid',
|
|
74
|
+
'get-hyperliquid-open-orders': 'hyperliquid',
|
|
75
|
+
'get-hyperliquid-setup-status': 'hyperliquid',
|
|
76
|
+
'get-hyperliquid-funding-status': 'hyperliquid',
|
|
77
|
+
'get-hyperliquid-onboarding-plan': 'hyperliquid',
|
|
78
|
+
'prepare-hyperliquid-onboarding': 'hyperliquid',
|
|
79
|
+
'claim-hyperliquid-testnet-faucet': 'hyperliquid',
|
|
80
|
+
'get-hyperliquid-builder-fee-status': 'hyperliquid',
|
|
81
|
+
'approve-hyperliquid-agent': 'hyperliquid',
|
|
82
|
+
'approve-hyperliquid-builder-fee': 'hyperliquid',
|
|
83
|
+
'register-hyperliquid-referrer': 'hyperliquid',
|
|
84
|
+
'set-hyperliquid-referrer': 'hyperliquid',
|
|
85
|
+
'get-hyperliquid-referral-status': 'hyperliquid',
|
|
86
|
+
'deposit-hyperliquid-staking': 'hyperliquid',
|
|
87
|
+
'deposit-hyperliquid-bridge': 'hyperliquid',
|
|
88
|
+
'place-hyperliquid-perps-order': 'hyperliquid',
|
|
89
|
+
'cancel-hyperliquid-perps-order': 'hyperliquid',
|
|
90
|
+
'get-perps-markets': 'hyperliquid',
|
|
91
|
+
'get-perps-account': 'hyperliquid',
|
|
92
|
+
'get-perps-position': 'hyperliquid',
|
|
93
|
+
'place-perps-order': 'hyperliquid',
|
|
94
|
+
'cancel-perps-order': 'hyperliquid',
|
|
95
|
+
'get-earn-opportunities': 'earn',
|
|
96
|
+
'get-earn-provider': 'earn',
|
|
97
|
+
'get-earn-position': 'earn',
|
|
98
|
+
'deposit-earn-position': 'earn',
|
|
99
|
+
'withdraw-earn-position': 'earn',
|
|
100
|
+
'get-capyfi-markets': 'capyfi',
|
|
101
|
+
'get-fx-supported-assets': 'fx',
|
|
102
|
+
'quote-fx-swap': 'fx',
|
|
103
|
+
'get-monerium-profile-status': 'fx',
|
|
104
|
+
'get-morpho-vaults': 'morpho',
|
|
105
|
+
'deposit-morpho-vault': 'morpho',
|
|
106
|
+
'withdraw-morpho-vault': 'morpho',
|
|
107
|
+
'get-curve-pools': 'curve',
|
|
108
|
+
'get-curve-pool-details': 'curve',
|
|
109
|
+
'deposit-curve-pool': 'curve',
|
|
110
|
+
'withdraw-curve-pool': 'curve',
|
|
111
|
+
'get-aegis-pools': 'aegis',
|
|
112
|
+
'get-aegis-pool-state': 'aegis',
|
|
113
|
+
'get-aegis-vault-state': 'aegis',
|
|
114
|
+
'deposit-aegis-lender': 'aegis',
|
|
115
|
+
'redeem-aegis-lender': 'aegis',
|
|
116
|
+
'open-aegis-vault-and-borrow': 'aegis',
|
|
117
|
+
'repay-aegis-vault': 'aegis',
|
|
118
|
+
'stabilize-aegis-vault': 'aegis',
|
|
119
|
+
'get-vaults-fyi-vaults': 'vaults-fyi',
|
|
120
|
+
'get-vaults-fyi-opportunities': 'vaults-fyi',
|
|
121
|
+
'get-yield-xyz-opportunities': 'yield-xyz',
|
|
122
|
+
'get-prediction-markets': 'polymarket',
|
|
123
|
+
'get-prediction-market': 'polymarket',
|
|
124
|
+
'get-prediction-events': 'polymarket',
|
|
125
|
+
'get-prediction-tags': 'polymarket',
|
|
126
|
+
'search-prediction-markets': 'polymarket',
|
|
127
|
+
'get-prediction-order-book': 'polymarket',
|
|
128
|
+
'get-prediction-position': 'polymarket',
|
|
129
|
+
'place-prediction-order': 'polymarket',
|
|
130
|
+
'cancel-prediction-order': 'polymarket',
|
|
131
|
+
'get-polymarket-markets': 'polymarket',
|
|
132
|
+
'get-polymarket-market': 'polymarket',
|
|
133
|
+
'get-polymarket-events': 'polymarket',
|
|
134
|
+
'get-polymarket-event': 'polymarket',
|
|
135
|
+
'get-polymarket-tags': 'polymarket',
|
|
136
|
+
'search-polymarket': 'polymarket',
|
|
137
|
+
'get-polymarket-order-book': 'polymarket',
|
|
138
|
+
'get-polymarket-prices': 'polymarket',
|
|
139
|
+
'get-polymarket-setup-status': 'polymarket',
|
|
140
|
+
'get-polymarket-order': 'polymarket',
|
|
141
|
+
'get-polymarket-user-orders': 'polymarket',
|
|
142
|
+
'cancel-polymarket-order': 'polymarket',
|
|
143
|
+
'get-polymarket-price-history': 'polymarket',
|
|
144
|
+
'place-polymarket-order': 'polymarket',
|
|
145
|
+
};
|
|
146
|
+
if (knownMap[key]) return knownMap[key];
|
|
147
|
+
if (key.includes('hyperliquid')) return 'hyperliquid';
|
|
148
|
+
if (key.includes('bitfinex')) return 'bitfinex';
|
|
149
|
+
if (key.includes('polymarket') || key.includes('prediction')) return 'polymarket';
|
|
150
|
+
if (key.includes('vaults-fyi')) return 'vaults-fyi';
|
|
151
|
+
if (key.includes('yield-xyz')) return 'yield-xyz';
|
|
152
|
+
if (key.includes('capyfi')) return 'capyfi';
|
|
153
|
+
if (key.includes('fx') || key.includes('monerium')) return 'fx';
|
|
154
|
+
if (key.includes('morpho')) return 'morpho';
|
|
155
|
+
if (key.includes('aegis')) return 'aegis';
|
|
156
|
+
if (key.includes('curve')) return 'curve';
|
|
157
|
+
if (key.includes('earn') || key.includes('yield') || key.includes('vault')) return 'earn';
|
|
158
|
+
if (key.includes('bridge')) return 'bridge';
|
|
159
|
+
if (key.includes('dogeos-barkswap')) return 'dogeos-barkswap';
|
|
160
|
+
if (key.includes('dogeos-laika')) return 'dogeos-laika';
|
|
161
|
+
if (key.includes('dogeos')) return 'dogeos-ecosystem';
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getActionProviderWithInference(action) {
|
|
166
|
+
return getActionProvider(action) || inferActionProvider(action);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isEarnCatalogAction(action) {
|
|
170
|
+
const actionKey = rowActionKey(action);
|
|
171
|
+
return (
|
|
172
|
+
actionKey.includes('_earn_') ||
|
|
173
|
+
normalizeProviderId(action?.domain) === 'earn' ||
|
|
174
|
+
normalizeProviderId(action?.category) === 'earn'
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isGenericEarnWriteAction(action) {
|
|
179
|
+
const actionKey = rowActionKey(action);
|
|
180
|
+
return actionKey === 'deposit_earn_position' || actionKey === 'withdraw_earn_position';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isGenericEarnWriteForProvider(action, requestedProvider) {
|
|
184
|
+
if (!isGenericEarnWriteAction(action)) return false;
|
|
185
|
+
const provider = getActionProviderWithInference(action);
|
|
186
|
+
if (!provider) return true;
|
|
187
|
+
if (provider === requestedProvider) return true;
|
|
188
|
+
if (provider === 'earn' && requestedProvider === 'earn') return true;
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isSpotTradingCatalogAction(action) {
|
|
193
|
+
const actionKey = rowActionKey(action);
|
|
194
|
+
return actionKey === 'swap' || actionKey === 'execute_optimal_swap' || actionKey === 'get_best_trading_quote';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isProviderActionKey(action, provider) {
|
|
198
|
+
const actionKey = rowActionKey(action);
|
|
199
|
+
const normalizedProvider = normalizeProviderId(provider).replace(/-/g, '_');
|
|
200
|
+
if (!actionKey || !normalizedProvider) return false;
|
|
201
|
+
return (
|
|
202
|
+
actionKey === normalizedProvider ||
|
|
203
|
+
actionKey.startsWith(`${normalizedProvider}_`) ||
|
|
204
|
+
actionKey.includes(`_${normalizedProvider}_`) ||
|
|
205
|
+
actionKey.endsWith(`_${normalizedProvider}`)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function actionMatchesProvider(action, requestedProvider, allActions) {
|
|
210
|
+
const provider = getActionProviderWithInference(action);
|
|
211
|
+
const actionKey = rowActionKey(action);
|
|
212
|
+
if (provider === requestedProvider) return true;
|
|
213
|
+
if (requestedProvider === 'trading') return isSpotTradingCatalogAction(action);
|
|
214
|
+
// Here resolved metadata already disagrees with the filter; fall back to action-key shape only.
|
|
215
|
+
if (requestedProvider === 'hyperliquid') return isProviderActionKey(action, 'hyperliquid');
|
|
216
|
+
if (requestedProvider === 'polymarket') return isProviderActionKey(action, 'polymarket');
|
|
217
|
+
if (requestedProvider === 'bridge') {
|
|
218
|
+
// Hyperliquid Bridge2 setup actions are owned by Hyperliquid, not the cross-chain bridge provider.
|
|
219
|
+
if (provider === 'hyperliquid') return false;
|
|
220
|
+
return isProviderActionKey(action, 'bridge');
|
|
221
|
+
}
|
|
222
|
+
if (requestedProvider.startsWith('dogeos')) return isProviderActionKey(action, requestedProvider);
|
|
223
|
+
if (requestedProvider === 'earn') {
|
|
224
|
+
return provider === 'earn' || provider === 'morpho' || provider === 'aegis' || provider === 'curve' || provider === 'capyfi' || provider === 'vaults-fyi' || provider === 'yield-xyz' || isEarnCatalogAction(action);
|
|
225
|
+
}
|
|
226
|
+
if (requestedProvider === 'morpho' || requestedProvider === 'aegis' || requestedProvider === 'curve' || requestedProvider === 'capyfi' || requestedProvider === 'vaults-fyi' || requestedProvider === 'yield-xyz') {
|
|
227
|
+
const hasProtocolSpecificEarnRows = allActions.some((candidate) => getActionProviderWithInference(candidate) === requestedProvider);
|
|
228
|
+
return (
|
|
229
|
+
isProviderActionKey(action, requestedProvider) ||
|
|
230
|
+
isGenericEarnWriteForProvider(action, requestedProvider) ||
|
|
231
|
+
(!hasProtocolSpecificEarnRows && isEarnCatalogAction(action))
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (requestedProvider === 'prediction') {
|
|
235
|
+
return (
|
|
236
|
+
provider === 'polymarket' ||
|
|
237
|
+
provider === 'prediction' ||
|
|
238
|
+
actionKey.includes('_prediction_') ||
|
|
239
|
+
normalizeProviderId(action?.domain) === 'prediction' ||
|
|
240
|
+
normalizeProviderId(action?.category) === 'prediction'
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return isProviderActionKey(action, requestedProvider);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function applyActionFilters(actions, flags) {
|
|
247
|
+
let filtered = actions;
|
|
248
|
+
if (flags.catalog) {
|
|
249
|
+
const expectedCatalog = String(flags.catalog).trim().toLowerCase();
|
|
250
|
+
filtered = filtered.filter((a) => String(a.catalog || a.catalogId || 'default').trim().toLowerCase() === expectedCatalog);
|
|
251
|
+
}
|
|
252
|
+
if (flags.provider) {
|
|
253
|
+
const expectedProvider = normalizeProviderId(flags.provider);
|
|
254
|
+
filtered = filtered.filter((a) => actionMatchesProvider(a, expectedProvider, filtered));
|
|
255
|
+
}
|
|
256
|
+
return filtered;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getActionProvider(action) {
|
|
260
|
+
return normalizeProviderId(action?.provider || action?.providerId || action?.provider_id || action?.catalog || '');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function resolveActionSelection(ctx, flags) {
|
|
264
|
+
const requestedAction = requireFlag(flags, 'action', 'Missing --action');
|
|
265
|
+
const requestedLower = requestedAction.toLowerCase();
|
|
266
|
+
const actions = applyActionFilters(await getCachedActionsCatalog(ctx, flags), flags);
|
|
267
|
+
const keyed = actions
|
|
268
|
+
.map((action) => ({
|
|
269
|
+
key: getActionKey(action),
|
|
270
|
+
aliases: getActionAliases(action),
|
|
271
|
+
action,
|
|
272
|
+
}))
|
|
273
|
+
.filter((entry) => entry.key);
|
|
274
|
+
|
|
275
|
+
if (!keyed.length) {
|
|
276
|
+
return { requestedAction, resolvedAction: requestedAction, resolvedActionMeta: null };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Prefer direct action id matches over alias matches.
|
|
280
|
+
// This keeps UX simple for commands like `--action swap` when another action aliases to "swap".
|
|
281
|
+
const exactKey = keyed.filter((entry) => entry.key.toLowerCase() === requestedLower);
|
|
282
|
+
if (exactKey.length === 1) {
|
|
283
|
+
return { requestedAction, resolvedAction: exactKey[0].key, resolvedActionMeta: exactKey[0].action || null };
|
|
284
|
+
}
|
|
285
|
+
if (exactKey.length > 1) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Ambiguous --action "${requestedAction}". Multiple exact matches found: ${formatActionList(
|
|
288
|
+
exactKey.map((v) => v.key)
|
|
289
|
+
)}.`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const exactAlias = keyed.filter((entry) => entry.aliases.some((a) => a.toLowerCase() === requestedLower));
|
|
294
|
+
if (exactAlias.length === 1) {
|
|
295
|
+
return { requestedAction, resolvedAction: exactAlias[0].key, resolvedActionMeta: exactAlias[0].action || null };
|
|
296
|
+
}
|
|
297
|
+
if (exactAlias.length > 1) {
|
|
298
|
+
const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Ambiguous --action "${requestedAction}". Alias matches multiple actions: ${formatActionList(
|
|
301
|
+
exactAlias.map((v) => v.key)
|
|
302
|
+
)}.${formatProviderAmbiguousActionHint(providerHint)}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const fuzzy = keyed.filter(
|
|
307
|
+
(entry) =>
|
|
308
|
+
entry.key.toLowerCase().includes(requestedLower) ||
|
|
309
|
+
entry.aliases.some((a) => a.toLowerCase().includes(requestedLower))
|
|
310
|
+
);
|
|
311
|
+
if (fuzzy.length === 1) {
|
|
312
|
+
return { requestedAction, resolvedAction: fuzzy[0].key, resolvedActionMeta: fuzzy[0].action || null };
|
|
313
|
+
}
|
|
314
|
+
if (fuzzy.length > 1) {
|
|
315
|
+
const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Ambiguous --action "${requestedAction}". Did you mean one of: ${formatActionList(
|
|
318
|
+
fuzzy.slice(0, 10).map((v) => v.key)
|
|
319
|
+
)}?${formatProviderAmbiguousActionHint(providerHint)}`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
|
|
323
|
+
throw new Error(
|
|
324
|
+
`Unknown --action "${requestedAction}". No compatible action found in current catalog/provider scope.${formatProviderUnknownActionHint(
|
|
325
|
+
providerHint
|
|
326
|
+
)}`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
getActionKey,
|
|
332
|
+
getActionAliases,
|
|
333
|
+
formatActionList,
|
|
334
|
+
normalizeProviderId,
|
|
335
|
+
applyActionFilters,
|
|
336
|
+
getActionProvider,
|
|
337
|
+
resolveActionSelection,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function createAgentExtract() {
|
|
2
|
+
function extractAgentApiKey(value) {
|
|
3
|
+
if (!value || typeof value !== 'object') return '';
|
|
4
|
+
const candidates = [
|
|
5
|
+
value.agentApiKey,
|
|
6
|
+
value.apiKey,
|
|
7
|
+
value.data?.agentApiKey,
|
|
8
|
+
value.data?.apiKey,
|
|
9
|
+
value.bootstrap?.agentApiKey,
|
|
10
|
+
value.bootstrap?.apiKey,
|
|
11
|
+
value.registration?.agentApiKey,
|
|
12
|
+
value.registration?.apiKey,
|
|
13
|
+
value.registration?.registration?.agentApiKey,
|
|
14
|
+
value.registration?.registration?.apiKey,
|
|
15
|
+
value.data?.registration?.agentApiKey,
|
|
16
|
+
value.data?.registration?.apiKey,
|
|
17
|
+
value.data?.registration?.registration?.agentApiKey,
|
|
18
|
+
value.data?.registration?.registration?.apiKey,
|
|
19
|
+
];
|
|
20
|
+
return String(candidates.find((candidate) => String(candidate || '').trim()) || '').trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function extractAgentKey(value) {
|
|
24
|
+
if (!value || typeof value !== 'object') return '';
|
|
25
|
+
const candidates = [
|
|
26
|
+
value.agentKey,
|
|
27
|
+
value.data?.agentKey,
|
|
28
|
+
value.bootstrap?.agentKey,
|
|
29
|
+
value.registration?.agentKey,
|
|
30
|
+
value.registration?.registration?.agentKey,
|
|
31
|
+
value.data?.registration?.agentKey,
|
|
32
|
+
value.data?.registration?.registration?.agentKey,
|
|
33
|
+
value.owner?.creatorWallet,
|
|
34
|
+
value.data?.owner?.creatorWallet,
|
|
35
|
+
];
|
|
36
|
+
return String(candidates.find((candidate) => String(candidate || '').trim()) || '').trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function maskSecret(value) {
|
|
40
|
+
const raw = String(value || '').trim();
|
|
41
|
+
if (!raw) return null;
|
|
42
|
+
if (raw.length <= 12) return '***';
|
|
43
|
+
if (raw.length <= 20) return `${raw.slice(0, 4)}***${raw.slice(-2)}`;
|
|
44
|
+
return `${raw.slice(0, 6)}...${raw.slice(-4)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
extractAgentApiKey,
|
|
49
|
+
extractAgentKey,
|
|
50
|
+
maskSecret,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { loadProfileConfig } from '../core/context.mjs';
|
|
2
|
+
export function createAuthHelpers() {
|
|
3
|
+
function getAuthToken(flags, missingMessage) {
|
|
4
|
+
const config = loadProfileConfig();
|
|
5
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
6
|
+
if (!authToken) throw new Error(missingMessage);
|
|
7
|
+
return authToken;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function buildOwnerAuthHeaders(flags, missingMessage) {
|
|
11
|
+
const config = loadProfileConfig();
|
|
12
|
+
const explicitOwnerSessionToken = String(flags.ownerSessionToken || '').trim();
|
|
13
|
+
const explicitAuthToken = String(flags.authToken || '').trim();
|
|
14
|
+
const ownerSessionToken = String(process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || '').trim();
|
|
15
|
+
const authToken = String(process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
16
|
+
|
|
17
|
+
// Prefer explicit flags first; then prefer owner-session over bearer from env/config
|
|
18
|
+
// to keep owner operations on least-privilege credentials by default.
|
|
19
|
+
if (explicitOwnerSessionToken) {
|
|
20
|
+
return { 'x-owner-session-token': explicitOwnerSessionToken };
|
|
21
|
+
}
|
|
22
|
+
if (explicitAuthToken) {
|
|
23
|
+
return { Authorization: `Bearer ${explicitAuthToken}` };
|
|
24
|
+
}
|
|
25
|
+
if (ownerSessionToken) {
|
|
26
|
+
return { 'x-owner-session-token': ownerSessionToken };
|
|
27
|
+
}
|
|
28
|
+
if (authToken) {
|
|
29
|
+
return { Authorization: `Bearer ${authToken}` };
|
|
30
|
+
}
|
|
31
|
+
throw new Error(missingMessage);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveUnsignedTx(result) {
|
|
35
|
+
if (result?.unsignedTx && typeof result.unsignedTx === 'object') return result.unsignedTx;
|
|
36
|
+
if (result?.data?.unsignedTx && typeof result.data.unsignedTx === 'object') return result.data.unsignedTx;
|
|
37
|
+
if (result?.details?.unsignedTx && typeof result.details.unsignedTx === 'object') return result.details.unsignedTx;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function sleepMs(ms) {
|
|
42
|
+
const delay = Number(ms || 0);
|
|
43
|
+
if (!Number.isFinite(delay) || delay <= 0) return;
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
getAuthToken,
|
|
49
|
+
buildOwnerAuthHeaders,
|
|
50
|
+
resolveUnsignedTx,
|
|
51
|
+
sleepMs,
|
|
52
|
+
};
|
|
53
|
+
}
|