@thirdfy/agent-cli 0.1.9 → 0.1.10
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 +5 -0
- package/README.md +19 -0
- package/bin/thirdfy-agent.mjs +239 -8
- package/package.json +3 -2
- package/src/utils/cli/intentNormalization.cjs +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.1.9] - 2026-04-08
|
|
8
|
+
|
|
9
|
+
**npm:** [`@thirdfy/agent-cli@0.1.9`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.9)
|
|
10
|
+
**Git tag:** [`v0.1.9`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.9)
|
|
11
|
+
|
|
7
12
|
### Fixed
|
|
8
13
|
|
|
9
14
|
- **Self preflight routing for swap-like actions:** `preflight --run-mode self` now selects the validation route by action capability. When an action advertises `supportsExecuteIntent=false` and `supportsBuildTx=true`, preflight uses `build-tx` directly instead of forcing execute-intent validation.
|
package/README.md
CHANGED
|
@@ -16,8 +16,13 @@ If you want the full developer docs, start here:
|
|
|
16
16
|
- [API Reference home](https://docs.thirdfy.com/api)
|
|
17
17
|
- [LLM docs map (llms.txt)](https://docs.thirdfy.com/llms.txt)
|
|
18
18
|
- [LLM full API snapshot (llms-full.txt)](https://docs.thirdfy.com/llms-full.txt)
|
|
19
|
+
- [Provider docs index (repo)](./docs/providers/index.md)
|
|
20
|
+
- [Action inventory and compatibility](./docs/action-inventory-and-compatibility.md)
|
|
21
|
+
- [Command reference](./docs/command-reference.md)
|
|
19
22
|
- [DogeOS quickstart (repo)](./docs/dogeos-quickstart.md)
|
|
23
|
+
- [Bitfinex CEX workflow (repo)](./docs/bitfinex-cex-workflow.md)
|
|
20
24
|
- [Action onboarding standard](./docs/action-onboarding-standard.md)
|
|
25
|
+
- [Provider docs governance model](./docs/provider-docs-governance.md)
|
|
21
26
|
- [Skill integration RFC](./docs/skill-integration-rfc.md)
|
|
22
27
|
|
|
23
28
|
## Install
|
|
@@ -215,6 +220,14 @@ Jeff-friendly aliases map directly to core commands:
|
|
|
215
220
|
- `thirdfy-agent jeff status --intent-id ...` -> alias of `intent-status`
|
|
216
221
|
- `thirdfy-agent prompt "<text>"` -> captures prompt intent and suggests deterministic execution commands
|
|
217
222
|
|
|
223
|
+
## Provider-specific workflows
|
|
224
|
+
|
|
225
|
+
Provider and venue guides are kept outside `README` so this page stays stable as integrations grow.
|
|
226
|
+
|
|
227
|
+
- Provider index: [`docs/providers/index.md`](./docs/providers/index.md)
|
|
228
|
+
- Bitfinex runbook: [`docs/providers/bitfinex.md`](./docs/providers/bitfinex.md)
|
|
229
|
+
- DogeOS runbook: [`docs/providers/dogeos.md`](./docs/providers/dogeos.md)
|
|
230
|
+
|
|
218
231
|
## Command groups
|
|
219
232
|
|
|
220
233
|
- Discovery: `catalogs list`, `actions`
|
|
@@ -226,6 +239,12 @@ Jeff-friendly aliases map directly to core commands:
|
|
|
226
239
|
- Delegation execution helpers: `delegation balance`, `delegation redeem`
|
|
227
240
|
- Account: `credits balance`
|
|
228
241
|
|
|
242
|
+
## Where to find what is supported
|
|
243
|
+
|
|
244
|
+
- Full command groups and usage map: [`docs/command-reference.md`](./docs/command-reference.md)
|
|
245
|
+
- Live action inventory in your environment: [`docs/action-inventory-and-compatibility.md`](./docs/action-inventory-and-compatibility.md)
|
|
246
|
+
- Provider runbooks and provider status: [`docs/providers/index.md`](./docs/providers/index.md)
|
|
247
|
+
|
|
229
248
|
## Delegation lifecycle (canonical)
|
|
230
249
|
|
|
231
250
|
Use `create` + `activate` as the default delegated execution lifecycle:
|
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { randomUUID } from 'crypto';
|
|
3
|
+
import { createHmac, randomUUID } from 'crypto';
|
|
4
4
|
import { spawnSync } from 'child_process';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import os from 'os';
|
|
@@ -18,6 +18,11 @@ const {
|
|
|
18
18
|
const DEFAULT_API_BASE = 'https://api.thirdfy.com';
|
|
19
19
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
20
20
|
const DEFAULT_CATALOG_CACHE_TTL_MS = 60_000;
|
|
21
|
+
const DEFAULT_BITFINEX_BASE_URL = 'https://api.bitfinex.com';
|
|
22
|
+
const BITFINEX_REQUIRED_SCOPE_PERMISSIONS = {
|
|
23
|
+
funding: { read: 1, write: 1 },
|
|
24
|
+
wallets: { read: 1, write: 1 },
|
|
25
|
+
};
|
|
21
26
|
const RUN_MODES = ['thirdfy', 'self', 'hybrid'];
|
|
22
27
|
const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
|
|
23
28
|
const PROFILE_DEFAULTS = {
|
|
@@ -52,6 +57,7 @@ main().catch((error) => {
|
|
|
52
57
|
const payload = error?.payload && typeof error.payload === 'object' ? error.payload : {};
|
|
53
58
|
const blockedReason = String(payload.blockedReason || payload.error || '').trim() || undefined;
|
|
54
59
|
const blockedStage = String(payload.blockedStage || '').trim() || undefined;
|
|
60
|
+
const onboardingHint = buildTopLevelOnboardingHint(payload);
|
|
55
61
|
printEnvelope({
|
|
56
62
|
success: false,
|
|
57
63
|
code: statusCode ? 'API_REQUEST_FAILED' : 'CLI_UNHANDLED_ERROR',
|
|
@@ -60,6 +66,7 @@ main().catch((error) => {
|
|
|
60
66
|
statusCode,
|
|
61
67
|
blockedReason,
|
|
62
68
|
blockedStage,
|
|
69
|
+
onboardingHint,
|
|
63
70
|
payload,
|
|
64
71
|
},
|
|
65
72
|
meta: { version: CLI_VERSION, apiBase: context.apiBase },
|
|
@@ -67,6 +74,22 @@ main().catch((error) => {
|
|
|
67
74
|
process.exit(1);
|
|
68
75
|
});
|
|
69
76
|
|
|
77
|
+
function buildTopLevelOnboardingHint(payload) {
|
|
78
|
+
const reason = String(payload?.blockedReason || payload?.error || '').trim();
|
|
79
|
+
if (!reason) return null;
|
|
80
|
+
if (
|
|
81
|
+
['CEX_CREDENTIAL_NOT_FOUND', 'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT', 'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED'].includes(
|
|
82
|
+
reason
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
return {
|
|
86
|
+
type: 'bitfinex_credentials',
|
|
87
|
+
command: buildBitfinexOnboardingCommand(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
70
93
|
async function main() {
|
|
71
94
|
const profileState = resolveProfileState(globalFlags);
|
|
72
95
|
context.profile = profileState.profile;
|
|
@@ -518,10 +541,15 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
518
541
|
const runMode = getEffectiveRunMode(ctx, flags);
|
|
519
542
|
enforceChainCompatibility(capabilities, flags, runMode);
|
|
520
543
|
const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved);
|
|
521
|
-
const normalized = backendResult.normalized
|
|
544
|
+
const normalized = applyExecutionFallbackHints(backendResult.normalized, {
|
|
545
|
+
flags,
|
|
546
|
+
runMode,
|
|
547
|
+
resolvedAction: resolved.resolvedAction,
|
|
548
|
+
});
|
|
549
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
522
550
|
printEnvelope({
|
|
523
551
|
success: normalized.success,
|
|
524
|
-
code: normalized.success ? 'PREFLIGHT_OK' : 'PREFLIGHT_FAILED',
|
|
552
|
+
code: normalized.success ? 'PREFLIGHT_OK' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'PREFLIGHT_FAILED',
|
|
525
553
|
message: backendResult.message,
|
|
526
554
|
data: normalized,
|
|
527
555
|
meta: {
|
|
@@ -548,7 +576,11 @@ async function commandRun(ctx, flags, capabilities) {
|
|
|
548
576
|
const backendResult = await runExecutionByMode(runMode, ctx, flags, resolved, {
|
|
549
577
|
skipPreflight,
|
|
550
578
|
});
|
|
551
|
-
const normalized = backendResult.normalized
|
|
579
|
+
const normalized = applyExecutionFallbackHints(backendResult.normalized, {
|
|
580
|
+
flags,
|
|
581
|
+
runMode,
|
|
582
|
+
resolvedAction: resolved.resolvedAction,
|
|
583
|
+
});
|
|
552
584
|
if (runMode === 'self' && parseBooleanFlag(flags.broadcast, false) && normalized?.success) {
|
|
553
585
|
const executed = await performSelfBroadcast(ctx, flags, resolved.resolvedAction, normalized);
|
|
554
586
|
printEnvelope({
|
|
@@ -925,8 +957,9 @@ async function commandDelegationRedeem(ctx, flags, capabilities) {
|
|
|
925
957
|
|
|
926
958
|
async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
927
959
|
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for credentials upsert');
|
|
960
|
+
const venue = requireFlag(flags, 'venue', 'Missing --venue');
|
|
928
961
|
const payload = {
|
|
929
|
-
venue
|
|
962
|
+
venue,
|
|
930
963
|
apiKey: requireFlag(flags, 'apiKey', 'Missing --api-key'),
|
|
931
964
|
apiSecret: requireFlag(flags, 'apiSecret', 'Missing --api-secret'),
|
|
932
965
|
credentialType: String(flags.credentialType || 'api_key'),
|
|
@@ -937,6 +970,19 @@ async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
|
937
970
|
if (flags.policy) payload.policy = parseJsonFlag(flags, 'policy');
|
|
938
971
|
if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
|
|
939
972
|
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
973
|
+
const probeEnabled =
|
|
974
|
+
String(venue).trim().toLowerCase() === 'bitfinex' &&
|
|
975
|
+
!parseBooleanFlag(flags.skipPermissionsProbe, false) &&
|
|
976
|
+
parseBooleanFlag(flags.verifyPermissions, true);
|
|
977
|
+
let permissionProbe = null;
|
|
978
|
+
if (probeEnabled) {
|
|
979
|
+
permissionProbe = await probeBitfinexPermissions({
|
|
980
|
+
apiKey: payload.apiKey,
|
|
981
|
+
apiSecret: payload.apiSecret,
|
|
982
|
+
timeoutMs: ctx.timeoutMs,
|
|
983
|
+
baseUrl: String(flags.bitfinexBaseUrl || process.env.BITFINEX_BASE_URL || DEFAULT_BITFINEX_BASE_URL),
|
|
984
|
+
});
|
|
985
|
+
}
|
|
940
986
|
|
|
941
987
|
const response = await requestWithRouteFallback(ctx, {
|
|
942
988
|
method: 'POST',
|
|
@@ -953,6 +999,7 @@ async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
|
953
999
|
data: response,
|
|
954
1000
|
meta: {
|
|
955
1001
|
apiBase: ctx.apiBase,
|
|
1002
|
+
permissionProbe,
|
|
956
1003
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
957
1004
|
},
|
|
958
1005
|
});
|
|
@@ -1711,6 +1758,66 @@ function enforceChainCompatibility(capabilities, flags, runMode) {
|
|
|
1711
1758
|
}
|
|
1712
1759
|
}
|
|
1713
1760
|
|
|
1761
|
+
function shouldUseBuildTxExecution(runMode, resolved) {
|
|
1762
|
+
if (runMode !== 'self' && runMode !== 'hybrid') return false;
|
|
1763
|
+
const meta = resolved?.resolvedActionMeta || null;
|
|
1764
|
+
if (!meta || typeof meta !== 'object') return true;
|
|
1765
|
+
if (meta.supportsBuildTx === false) return false;
|
|
1766
|
+
return true;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
function buildBitfinexOnboardingCommand() {
|
|
1770
|
+
return [
|
|
1771
|
+
'thirdfy-agent credentials upsert',
|
|
1772
|
+
'--auth-token "$THIRDFY_AUTH_TOKEN"',
|
|
1773
|
+
'--venue bitfinex',
|
|
1774
|
+
'--api-key "<BITFINEX_API_KEY>"',
|
|
1775
|
+
'--api-secret "<BITFINEX_API_SECRET>"',
|
|
1776
|
+
'--verify-permissions',
|
|
1777
|
+
'--json',
|
|
1778
|
+
].join(' ');
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedAction }) {
|
|
1782
|
+
if (!normalized || typeof normalized !== 'object') return normalized;
|
|
1783
|
+
const next = { ...normalized };
|
|
1784
|
+
const reasons = new Set();
|
|
1785
|
+
const topBlockedReason = String(next.blockedReason || '').trim();
|
|
1786
|
+
if (topBlockedReason) reasons.add(topBlockedReason);
|
|
1787
|
+
const blockedByReason = next.blockedByReason && typeof next.blockedByReason === 'object' ? next.blockedByReason : {};
|
|
1788
|
+
for (const key of Object.keys(blockedByReason)) {
|
|
1789
|
+
if (String(key || '').trim()) reasons.add(String(key).trim());
|
|
1790
|
+
}
|
|
1791
|
+
const resolvedActionLower = String(resolvedAction || '').trim().toLowerCase();
|
|
1792
|
+
const looksBitfinexAction = resolvedActionLower.includes('bitfinex');
|
|
1793
|
+
const cexCredentialMissingSignals = new Set([
|
|
1794
|
+
'CEX_CREDENTIAL_NOT_FOUND',
|
|
1795
|
+
'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT',
|
|
1796
|
+
'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED',
|
|
1797
|
+
]);
|
|
1798
|
+
const hasMissingCredentialSignal = Array.from(reasons).some((code) => cexCredentialMissingSignals.has(code));
|
|
1799
|
+
if (looksBitfinexAction && hasMissingCredentialSignal) {
|
|
1800
|
+
next.onboardingHint = {
|
|
1801
|
+
type: 'bitfinex_credentials',
|
|
1802
|
+
message:
|
|
1803
|
+
'Bitfinex credentials are missing for this delegated account. Onboard credentials first, then retry the same command.',
|
|
1804
|
+
command: buildBitfinexOnboardingCommand(),
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
if (
|
|
1808
|
+
looksBitfinexAction &&
|
|
1809
|
+
runMode !== 'thirdfy' &&
|
|
1810
|
+
(topBlockedReason === 'SELF_MODE_REQUIRES_BUILD_TX' || topBlockedReason === 'HYBRID_MODE_REQUIRES_BUILD_TX')
|
|
1811
|
+
) {
|
|
1812
|
+
next.onboardingHint = {
|
|
1813
|
+
...(next.onboardingHint || {}),
|
|
1814
|
+
runMode: 'thirdfy',
|
|
1815
|
+
runModeMessage: 'Bitfinex actions require delegated execute-intent rail. Re-run with --run-mode thirdfy.',
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
return next;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1714
1821
|
function shouldUseBuildTxPreflight(runMode, resolved) {
|
|
1715
1822
|
if (runMode !== 'self') return false;
|
|
1716
1823
|
const meta = resolved?.resolvedActionMeta || null;
|
|
@@ -1725,7 +1832,7 @@ async function runPreflightByMode(runMode, ctx, flags, resolved) {
|
|
|
1725
1832
|
const normalized = await executeSelfRun(
|
|
1726
1833
|
ctx,
|
|
1727
1834
|
flags,
|
|
1728
|
-
|
|
1835
|
+
resolved,
|
|
1729
1836
|
{ skipPreflight: route === 'build_tx' }
|
|
1730
1837
|
);
|
|
1731
1838
|
const blockedByGovernance = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
@@ -1843,6 +1950,7 @@ async function executeThirdfyRun(ctx, flags, resolved, options) {
|
|
|
1843
1950
|
|
|
1844
1951
|
async function executeSelfRun(ctx, flags, resolved, options) {
|
|
1845
1952
|
const skipPreflight = Boolean(options?.skipPreflight);
|
|
1953
|
+
const blockedReasonCode = String(options?.blockedReasonCode || 'SELF_MODE_REQUIRES_BUILD_TX');
|
|
1846
1954
|
if (!skipPreflight) {
|
|
1847
1955
|
const preflightPayload = buildIntentPayload(flags, {
|
|
1848
1956
|
validationOnly: true,
|
|
@@ -1861,6 +1969,17 @@ async function executeSelfRun(ctx, flags, resolved, options) {
|
|
|
1861
1969
|
return preflightNormalized;
|
|
1862
1970
|
}
|
|
1863
1971
|
}
|
|
1972
|
+
if (!shouldUseBuildTxExecution(options?.runMode || 'self', resolved)) {
|
|
1973
|
+
const normalized = normalizeIntentResponse({
|
|
1974
|
+
success: false,
|
|
1975
|
+
status: 'failed',
|
|
1976
|
+
blockedReason: blockedReasonCode,
|
|
1977
|
+
blockedStage: 'routing',
|
|
1978
|
+
error: 'Selected action does not support build-tx self lane. Use --run-mode thirdfy for delegated CEX execution.',
|
|
1979
|
+
});
|
|
1980
|
+
normalized.preflightBlocked = true;
|
|
1981
|
+
return normalized;
|
|
1982
|
+
}
|
|
1864
1983
|
const buildTxPayload = buildBuildTxPayload(flags, resolved.resolvedAction);
|
|
1865
1984
|
const buildTx = await apiPost(ctx, '/api/v1/agent/build-tx', buildTxPayload);
|
|
1866
1985
|
return normalizeIntentResponse({
|
|
@@ -1873,7 +1992,11 @@ async function executeSelfRun(ctx, flags, resolved, options) {
|
|
|
1873
1992
|
}
|
|
1874
1993
|
|
|
1875
1994
|
async function executeHybridRun(ctx, flags, resolved, options) {
|
|
1876
|
-
const selfResult = await executeSelfRun(ctx, flags, resolved,
|
|
1995
|
+
const selfResult = await executeSelfRun(ctx, flags, resolved, {
|
|
1996
|
+
...(options || {}),
|
|
1997
|
+
runMode: 'hybrid',
|
|
1998
|
+
blockedReasonCode: 'HYBRID_MODE_REQUIRES_BUILD_TX',
|
|
1999
|
+
});
|
|
1877
2000
|
if (!selfResult.success) return selfResult;
|
|
1878
2001
|
|
|
1879
2002
|
const mirrorPayload = buildIntentPayload(flags, {
|
|
@@ -2454,6 +2577,114 @@ function safeJsonParse(text) {
|
|
|
2454
2577
|
}
|
|
2455
2578
|
}
|
|
2456
2579
|
|
|
2580
|
+
function parseBitfinexPermissionRows(rawData) {
|
|
2581
|
+
const rows = Array.isArray(rawData) ? rawData : [];
|
|
2582
|
+
const parsed = {};
|
|
2583
|
+
for (const row of rows) {
|
|
2584
|
+
if (!Array.isArray(row) || row.length < 3) continue;
|
|
2585
|
+
const scope = String(row[0] || '').trim().toLowerCase();
|
|
2586
|
+
if (!scope) continue;
|
|
2587
|
+
const read = Number(row[1]) === 1 ? 1 : 0;
|
|
2588
|
+
const write = Number(row[2]) === 1 ? 1 : 0;
|
|
2589
|
+
parsed[scope] = { read, write };
|
|
2590
|
+
}
|
|
2591
|
+
return parsed;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
function getMissingBitfinexScopes(scopePermissions) {
|
|
2595
|
+
const missing = [];
|
|
2596
|
+
for (const [scope, required] of Object.entries(BITFINEX_REQUIRED_SCOPE_PERMISSIONS)) {
|
|
2597
|
+
const actual = scopePermissions[scope] || { read: 0, write: 0 };
|
|
2598
|
+
if (actual.read < required.read || actual.write < required.write) {
|
|
2599
|
+
missing.push({
|
|
2600
|
+
scope,
|
|
2601
|
+
required,
|
|
2602
|
+
actual,
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return missing;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
async function probeBitfinexPermissions({ apiKey, apiSecret, timeoutMs, baseUrl }) {
|
|
2610
|
+
const normalizedBaseUrl = String(baseUrl || DEFAULT_BITFINEX_BASE_URL).trim().replace(/\/+$/, '');
|
|
2611
|
+
const apiPath = '/v2/auth/r/permissions';
|
|
2612
|
+
const nonce = Date.now().toString();
|
|
2613
|
+
const payload = '{}';
|
|
2614
|
+
const signature = createHmac('sha384', String(apiSecret || ''))
|
|
2615
|
+
.update(`/api${apiPath}${nonce}${payload}`)
|
|
2616
|
+
.digest('hex');
|
|
2617
|
+
const controller = new AbortController();
|
|
2618
|
+
const timer = setTimeout(() => controller.abort(), Number(timeoutMs || DEFAULT_TIMEOUT_MS));
|
|
2619
|
+
try {
|
|
2620
|
+
const response = await fetch(`${normalizedBaseUrl}${apiPath}`, {
|
|
2621
|
+
method: 'POST',
|
|
2622
|
+
headers: {
|
|
2623
|
+
'content-type': 'application/json',
|
|
2624
|
+
'bfx-apikey': String(apiKey || ''),
|
|
2625
|
+
'bfx-nonce': nonce,
|
|
2626
|
+
'bfx-signature': signature,
|
|
2627
|
+
},
|
|
2628
|
+
body: payload,
|
|
2629
|
+
signal: controller.signal,
|
|
2630
|
+
});
|
|
2631
|
+
const text = await response.text();
|
|
2632
|
+
const parsed = text ? safeJsonParse(text) : {};
|
|
2633
|
+
const bitfinexApiError =
|
|
2634
|
+
Array.isArray(parsed) && String(parsed[0] || '').trim().toLowerCase() === 'error'
|
|
2635
|
+
? {
|
|
2636
|
+
code: Number(parsed[1]) || null,
|
|
2637
|
+
message: String(parsed[2] || 'Unknown Bitfinex API error'),
|
|
2638
|
+
}
|
|
2639
|
+
: parsed && typeof parsed === 'object' && String(parsed.event || '').trim().toLowerCase() === 'error'
|
|
2640
|
+
? {
|
|
2641
|
+
code: Number(parsed.code) || null,
|
|
2642
|
+
message: String(parsed.msg || parsed.message || 'Unknown Bitfinex API error'),
|
|
2643
|
+
}
|
|
2644
|
+
: null;
|
|
2645
|
+
if (!response.ok) {
|
|
2646
|
+
throw createCliError(
|
|
2647
|
+
'BITFINEX_PERMISSIONS_PROBE_FAILED',
|
|
2648
|
+
`Bitfinex permissions probe failed with HTTP ${response.status}. Verify API key/secret and key validity before onboarding.`,
|
|
2649
|
+
{ statusCode: response.status }
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
if (bitfinexApiError) {
|
|
2653
|
+
throw createCliError(
|
|
2654
|
+
'BITFINEX_PERMISSIONS_PROBE_FAILED',
|
|
2655
|
+
`Bitfinex permissions probe failed: ${bitfinexApiError.message}. Verify API key/secret and key validity before onboarding.`,
|
|
2656
|
+
{ statusCode: response.status, bitfinexApiError }
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
const scopePermissions = parseBitfinexPermissionRows(parsed);
|
|
2660
|
+
const missingScopes = getMissingBitfinexScopes(scopePermissions);
|
|
2661
|
+
if (missingScopes.length > 0) {
|
|
2662
|
+
throw createCliError(
|
|
2663
|
+
'BITFINEX_PERMISSION_SCOPE_MISSING',
|
|
2664
|
+
`Bitfinex key is missing required scopes (${missingScopes
|
|
2665
|
+
.map((entry) => entry.scope)
|
|
2666
|
+
.join(', ')}). Enable funding+wallets read/write and retry onboarding.`,
|
|
2667
|
+
{ missingScopes, scopePermissions }
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
return {
|
|
2671
|
+
venue: 'bitfinex',
|
|
2672
|
+
checked: true,
|
|
2673
|
+
scopes: scopePermissions,
|
|
2674
|
+
missingScopes: [],
|
|
2675
|
+
};
|
|
2676
|
+
} catch (error) {
|
|
2677
|
+
if (error?.payload) throw error;
|
|
2678
|
+
throw createCliError(
|
|
2679
|
+
'BITFINEX_PERMISSIONS_PROBE_FAILED',
|
|
2680
|
+
`Could not verify Bitfinex key permissions. ${String(error?.message || 'Unknown probe error')}`,
|
|
2681
|
+
{}
|
|
2682
|
+
);
|
|
2683
|
+
} finally {
|
|
2684
|
+
clearTimeout(timer);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2457
2688
|
function printEnvelope(payload) {
|
|
2458
2689
|
if (jsonMode) {
|
|
2459
2690
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
@@ -2491,7 +2722,7 @@ Core commands:
|
|
|
2491
2722
|
thirdfy-agent managed wallet init [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
|
|
2492
2723
|
thirdfy-agent managed wallet grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
|
|
2493
2724
|
thirdfy-agent managed run --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--idempotency-key <key>] [--json]
|
|
2494
|
-
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
|
|
2725
|
+
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--verify-permissions] [--skip-permissions-probe] [--bitfinex-base-url <url>] [--json]
|
|
2495
2726
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
|
2496
2727
|
thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
|
|
2497
2728
|
thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thirdfy/agent-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"test": "node --test \"test/**/*.test.cjs\"",
|
|
20
20
|
"lint": "node --check ./bin/thirdfy-agent.mjs",
|
|
21
|
+
"validate:public-docs": "node ./scripts/validate-public-docs.mjs",
|
|
21
22
|
"validate:cli-contract-schemas": "node ./scripts/validate-cli-contract-schemas.mjs",
|
|
22
23
|
"validate:actions": "node ./scripts/e2e/validate-actions.mjs",
|
|
23
24
|
"e2e:base:simulate": "node ./scripts/e2e/base-simulate.mjs",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
|
|
31
32
|
"e2e:report:last": "node ./scripts/e2e/report-last.mjs",
|
|
32
33
|
"smoke:cli": "node ./bin/thirdfy-agent.mjs --help && node ./bin/thirdfy-agent.mjs --version --json",
|
|
33
|
-
"prepublishOnly": "npm run validate:cli-contract-schemas && npm run test && npm run smoke:cli"
|
|
34
|
+
"prepublishOnly": "npm run validate:public-docs && npm run validate:cli-contract-schemas && npm run test && npm run smoke:cli"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
37
|
"thirdfy",
|
|
@@ -33,6 +33,7 @@ function normalizeIntentResponse(response) {
|
|
|
33
33
|
const topLevelGovernanceSignal = String(response?.governanceSignal || '').trim() || null;
|
|
34
34
|
const topLevelDelegationSignal = String(response?.delegationSignal || '').trim() || null;
|
|
35
35
|
const policyEvaluated = response?.policyEvaluated === true;
|
|
36
|
+
const preflightBlocked = response?.preflightBlocked === true;
|
|
36
37
|
return {
|
|
37
38
|
success,
|
|
38
39
|
status: response?.status || (success ? 'queued' : 'failed'),
|
|
@@ -48,6 +49,7 @@ function normalizeIntentResponse(response) {
|
|
|
48
49
|
governanceSignal: topLevelGovernanceSignal,
|
|
49
50
|
delegationSignal: topLevelDelegationSignal,
|
|
50
51
|
policyEvaluated,
|
|
52
|
+
preflightBlocked,
|
|
51
53
|
error: response?.error || null,
|
|
52
54
|
mode: response?.mode || null,
|
|
53
55
|
unsignedTx: response?.unsignedTx || null,
|