@thirdfy/agent-cli 0.4.2 → 0.5.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 (35) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +4 -6
  3. package/package.json +4 -2
  4. package/src/cli/help.mjs +2 -1
  5. package/src/cli/manifest.mjs +4 -4
  6. package/src/cli/options/index.mjs +2 -0
  7. package/src/cli/options/onboarding/credentials.mjs +0 -4
  8. package/src/cli/options/onboarding/oauthLogin.mjs +8 -0
  9. package/src/commands/account/credits.mjs +9 -7
  10. package/src/commands/discovery.mjs +6 -5
  11. package/src/commands/doctor.mjs +5 -5
  12. package/src/commands/execute.mjs +2 -11
  13. package/src/commands/onboarding/bootstrap.mjs +35 -0
  14. package/src/commands/onboarding/credentials.mjs +2 -25
  15. package/src/commands/onboarding/oauthLogin.mjs +284 -0
  16. package/src/core/chainRegistry.mjs +11 -0
  17. package/src/core/constants.mjs +0 -19
  18. package/src/core/context.mjs +16 -6
  19. package/src/core/errorEnvelope.mjs +6 -0
  20. package/src/core/http.mjs +47 -2
  21. package/src/core/oauth/authServer.mjs +192 -0
  22. package/src/core/oauth/pkce.mjs +17 -0
  23. package/src/core/oauth/session.mjs +117 -0
  24. package/src/lib/creditsUsdSummary.mjs +128 -29
  25. package/src/runtime/actions/selection.mjs +0 -1
  26. package/src/runtime/execution/payloads.mjs +6 -0
  27. package/src/runtime/execution/runners/createExecutionRunners.mjs +0 -3
  28. package/src/runtime/execution/runners/selfLane.mjs +6 -5
  29. package/src/runtime/handlers.mjs +1 -4
  30. package/src/runtime/main.mjs +0 -10
  31. package/src/runtime/owsSigning.mjs +17 -16
  32. package/src/runtime/providerHints/cex.mjs +1 -52
  33. package/src/runtime/bitfinexProbe.mjs +0 -121
  34. package/src/runtime/execution/runners/fallbackHints.mjs +0 -54
  35. package/src/runtime/onboardingHints.mjs +0 -54
@@ -1,59 +1,8 @@
1
1
  /**
2
- * Static provider hints. Centralized exchanges and their executors: Bitfinex, Hummingbot and Condor.
2
+ * Static provider hints. Centralized exchange executors: Hummingbot and Condor.
3
3
  * Each entry builds the hint for one provider id; `dispatch.mjs` picks the entry.
4
4
  */
5
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
6
  hummingbot: ({ provider }) => ({
58
7
  provider,
59
8
  category: 'cex_executor',
@@ -1,121 +0,0 @@
1
- import { createHmac } from 'crypto';
2
- import { DEFAULT_BITFINEX_BASE_URL, BITFINEX_PERMISSION_PROFILES } from '../core/constants.mjs';
3
- import { createCliError } from '../core/envelope.mjs';
4
- import { requestJson, safeJsonParse } from '../core/http.mjs';
5
- export function createBitfinexProbe() {
6
- function parseBitfinexPermissionRows(rawData) {
7
- const rows = Array.isArray(rawData) ? rawData : [];
8
- const parsed = {};
9
- for (const row of rows) {
10
- if (!Array.isArray(row) || row.length < 3) continue;
11
- const scope = String(row[0] || '').trim().toLowerCase();
12
- if (!scope) continue;
13
- const read = Number(row[1]) === 1 ? 1 : 0;
14
- const write = Number(row[2]) === 1 ? 1 : 0;
15
- parsed[scope] = { read, write };
16
- }
17
- return parsed;
18
- }
19
-
20
- function getMissingBitfinexScopes(scopePermissions, permissionProfile = 'funding') {
21
- const profile = BITFINEX_PERMISSION_PROFILES[permissionProfile] || BITFINEX_PERMISSION_PROFILES.funding;
22
- const missing = [];
23
- for (const [scope, required] of Object.entries(profile.required)) {
24
- const actual = scopePermissions[scope] || { read: 0, write: 0 };
25
- if (actual.read < required.read || actual.write < required.write) {
26
- missing.push({
27
- scope,
28
- required,
29
- actual,
30
- });
31
- }
32
- }
33
- return missing;
34
- }
35
-
36
- async function probeBitfinexPermissions({ apiKey, apiSecret, permissionProfile = 'funding', timeoutMs, baseUrl }) {
37
- const normalizedBaseUrl = String(baseUrl || DEFAULT_BITFINEX_BASE_URL).trim().replace(/\/+$/, '');
38
- const apiPath = '/v2/auth/r/permissions';
39
- const nonce = Date.now().toString();
40
- const payload = '{}';
41
- const signature = createHmac('sha384', String(apiSecret || ''))
42
- .update(`/api${apiPath}${nonce}${payload}`)
43
- .digest('hex');
44
- const controller = new AbortController();
45
- const timer = setTimeout(() => controller.abort(), Number(timeoutMs || DEFAULT_TIMEOUT_MS));
46
- try {
47
- const response = await fetch(`${normalizedBaseUrl}${apiPath}`, {
48
- method: 'POST',
49
- headers: {
50
- 'content-type': 'application/json',
51
- 'bfx-apikey': String(apiKey || ''),
52
- 'bfx-nonce': nonce,
53
- 'bfx-signature': signature,
54
- },
55
- body: payload,
56
- signal: controller.signal,
57
- });
58
- const text = await response.text();
59
- const parsed = text ? safeJsonParse(text) : {};
60
- const bitfinexApiError =
61
- Array.isArray(parsed) && String(parsed[0] || '').trim().toLowerCase() === 'error'
62
- ? {
63
- code: Number(parsed[1]) || null,
64
- message: String(parsed[2] || 'Unknown Bitfinex API error'),
65
- }
66
- : parsed && typeof parsed === 'object' && String(parsed.event || '').trim().toLowerCase() === 'error'
67
- ? {
68
- code: Number(parsed.code) || null,
69
- message: String(parsed.msg || parsed.message || 'Unknown Bitfinex API error'),
70
- }
71
- : null;
72
- if (!response.ok) {
73
- throw createCliError(
74
- 'BITFINEX_PERMISSIONS_PROBE_FAILED',
75
- `Bitfinex permissions probe failed with HTTP ${response.status}. Verify API key/secret and key validity before onboarding.`,
76
- { statusCode: response.status }
77
- );
78
- }
79
- if (bitfinexApiError) {
80
- throw createCliError(
81
- 'BITFINEX_PERMISSIONS_PROBE_FAILED',
82
- `Bitfinex permissions probe failed: ${bitfinexApiError.message}. Verify API key/secret and key validity before onboarding.`,
83
- { statusCode: response.status, bitfinexApiError }
84
- );
85
- }
86
- const scopePermissions = parseBitfinexPermissionRows(parsed);
87
- const missingScopes = getMissingBitfinexScopes(scopePermissions, permissionProfile);
88
- if (missingScopes.length > 0) {
89
- throw createCliError(
90
- 'BITFINEX_PERMISSION_SCOPE_MISSING',
91
- `Bitfinex key is missing required scopes (${missingScopes
92
- .map((entry) => entry.scope)
93
- .join(', ')}). Enable the ${permissionProfile} profile scopes and retry onboarding.`,
94
- { missingScopes, scopePermissions, permissionProfile }
95
- );
96
- }
97
- return {
98
- venue: 'bitfinex',
99
- permissionProfile,
100
- checked: true,
101
- scopes: scopePermissions,
102
- missingScopes: [],
103
- };
104
- } catch (error) {
105
- if (error?.payload) throw error;
106
- throw createCliError(
107
- 'BITFINEX_PERMISSIONS_PROBE_FAILED',
108
- `Could not verify Bitfinex key permissions. ${String(error?.message || 'Unknown probe error')}`,
109
- {}
110
- );
111
- } finally {
112
- clearTimeout(timer);
113
- }
114
- }
115
-
116
- return {
117
- parseBitfinexPermissionRows,
118
- getMissingBitfinexScopes,
119
- probeBitfinexPermissions,
120
- };
121
- }
@@ -1,54 +0,0 @@
1
- /** Hints added to a failed execution answer (Bitfinex onboarding and similar next steps). */
2
- export function createFallbackHints(deps) {
3
- const {
4
- buildBitfinexOnboardingCommand,
5
- inferBitfinexPermissionProfileFromAction,
6
- } = deps;
7
-
8
- function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedAction }) {
9
- if (!normalized || typeof normalized !== 'object') return normalized;
10
- const next = { ...normalized };
11
- const reasons = new Set();
12
- const topBlockedReason = String(next.blockedReason || '').trim();
13
- if (topBlockedReason) reasons.add(topBlockedReason);
14
- const blockedByReason = next.blockedByReason && typeof next.blockedByReason === 'object' ? next.blockedByReason : {};
15
- for (const key of Object.keys(blockedByReason)) {
16
- if (String(key || '').trim()) reasons.add(String(key).trim());
17
- }
18
- const resolvedActionLower = String(resolvedAction || '').trim().toLowerCase();
19
- const looksBitfinexAction = resolvedActionLower.includes('bitfinex');
20
- const permissionProfile = inferBitfinexPermissionProfileFromAction(resolvedActionLower);
21
- const cexCredentialMissingSignals = new Set([
22
- 'CEX_CREDENTIAL_NOT_FOUND',
23
- 'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT',
24
- 'BITFINEX_CREDENTIALS_NOT_IN_THIRDFY_DELEGATION_CONTEXT',
25
- 'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED',
26
- ]);
27
- const hasMissingCredentialSignal = Array.from(reasons).some((code) => cexCredentialMissingSignals.has(code));
28
- if (looksBitfinexAction && hasMissingCredentialSignal) {
29
- next.onboardingHint = {
30
- type: 'bitfinex_credentials',
31
- message:
32
- 'Bitfinex credentials are missing for this delegated account. Onboard credentials first, then retry the same command.',
33
- command: buildBitfinexOnboardingCommand(permissionProfile),
34
- };
35
- }
36
- if (
37
- looksBitfinexAction &&
38
- runMode !== 'thirdfy' &&
39
- runMode !== 'agent_wallet' &&
40
- (topBlockedReason === 'SELF_MODE_REQUIRES_BUILD_TX' || topBlockedReason === 'HYBRID_MODE_REQUIRES_BUILD_TX')
41
- ) {
42
- next.onboardingHint = {
43
- ...(next.onboardingHint || {}),
44
- runMode: 'thirdfy',
45
- runModeMessage: 'Bitfinex actions require delegated execute-intent rail. Re-run with --run-mode thirdfy.',
46
- };
47
- }
48
- return next;
49
- }
50
-
51
- return {
52
- applyExecutionFallbackHints,
53
- };
54
- }
@@ -1,54 +0,0 @@
1
- import { BITFINEX_PERMISSION_PROFILES } from '../core/constants.mjs';
2
- export function createOnboardingHints() {
3
- function buildBitfinexOnboardingCommand(permissionProfile = 'funding') {
4
- const profile = BITFINEX_PERMISSION_PROFILES[permissionProfile] || BITFINEX_PERMISSION_PROFILES.funding;
5
- return [
6
- 'thirdfy-agent credentials upsert',
7
- '--auth-token "$THIRDFY_AUTH_TOKEN"',
8
- '--venue bitfinex',
9
- `--permission-profile ${profile.id}`,
10
- `--credential-ref ${profile.credentialRef}`,
11
- '--api-key "<BITFINEX_API_KEY>"',
12
- '--api-secret "<BITFINEX_API_SECRET>"',
13
- '--verify-permissions',
14
- '--json',
15
- ].join(' ');
16
- }
17
-
18
- function inferBitfinexPermissionProfileFromAction(actionName) {
19
- const action = String(actionName || '')
20
- .trim()
21
- .toLowerCase()
22
- .replace(/[_\s]+/g, '-');
23
- if (!action.includes('bitfinex')) return 'funding';
24
- return action.includes('bitfinex-order') ? 'spot' : 'funding';
25
- }
26
-
27
- function buildTopLevelOnboardingHint(payload) {
28
- const reason = String(payload?.blockedReason || payload?.error || '').trim();
29
- const permissionProfile = inferBitfinexPermissionProfileFromAction(
30
- payload?.resolvedAction || payload?.action || payload?.requestedAction
31
- );
32
- if (!reason) return null;
33
- if (
34
- [
35
- 'CEX_CREDENTIAL_NOT_FOUND',
36
- 'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT',
37
- 'BITFINEX_CREDENTIALS_NOT_IN_THIRDFY_DELEGATION_CONTEXT',
38
- 'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED',
39
- ].includes(reason)
40
- ) {
41
- return {
42
- type: 'bitfinex_credentials',
43
- command: buildBitfinexOnboardingCommand(permissionProfile),
44
- };
45
- }
46
- return null;
47
- }
48
-
49
- return {
50
- buildBitfinexOnboardingCommand,
51
- inferBitfinexPermissionProfileFromAction,
52
- buildTopLevelOnboardingHint,
53
- };
54
- }