@thirdfy/agent-cli 0.1.9 → 0.1.11
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 +16 -0
- package/README.md +27 -0
- package/bin/thirdfy-agent.mjs +322 -13
- package/package.json +5 -2
- package/src/utils/cli/intentNormalization.cjs +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.1.11] - 2026-04-16
|
|
8
|
+
|
|
9
|
+
**npm:** [`@thirdfy/agent-cli@0.1.11`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.11)
|
|
10
|
+
**Git tag:** `v0.1.11`
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Self identity bootstrap during login:** `thirdfy-agent login` can now auto-bootstrap scoped self identity when `--agent-key` is provided, and persist the returned `agentApiKey` without manual env injection.
|
|
15
|
+
- **Custody mode profile contract:** Added persisted `custodyMode` (`managed` or `external`) across `profile init`, `profile use`, and `whoami`/config UX.
|
|
16
|
+
- **Self custody validation E2E:** Added `e2e:self:managed-custody-validation` and wired it into production certification flow.
|
|
17
|
+
|
|
18
|
+
## [0.1.9] - 2026-04-08
|
|
19
|
+
|
|
20
|
+
**npm:** [`@thirdfy/agent-cli@0.1.9`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.9)
|
|
21
|
+
**Git tag:** [`v0.1.9`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.9)
|
|
22
|
+
|
|
7
23
|
### Fixed
|
|
8
24
|
|
|
9
25
|
- **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
|
|
@@ -76,9 +81,13 @@ Execution-only workflows can run with only `AGENT_API_KEY` after onboarding is c
|
|
|
76
81
|
# Store auth defaults once
|
|
77
82
|
thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-api-key "$AGENT_API_KEY" --json
|
|
78
83
|
|
|
84
|
+
# Bootstrap scoped self identity + custody mode in one step
|
|
85
|
+
thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --run-mode self --custody-mode external --wallet-address "0xYOUR_AGENT_KEY" --json
|
|
86
|
+
|
|
79
87
|
# Inspect config
|
|
80
88
|
thirdfy-agent whoami --json
|
|
81
89
|
thirdfy-agent config set --key runMode --value self --json
|
|
90
|
+
thirdfy-agent config set --key custodyMode --value external --json
|
|
82
91
|
|
|
83
92
|
# Local-only logout (default)
|
|
84
93
|
thirdfy-agent logout --json
|
|
@@ -131,6 +140,9 @@ Auth expectations by mode:
|
|
|
131
140
|
- `self`: requires execution identity (`agentApiKey`) today; keyless self lane is blocked with deterministic `SELF_OPEN_DISABLED`.
|
|
132
141
|
- `hybrid`: requires execution identity and governed mirror semantics.
|
|
133
142
|
- `thirdfy`: requires execution identity and delegated governance path.
|
|
143
|
+
- custody mode defaults are profile-aware:
|
|
144
|
+
- `managed` default for `thirdfy`
|
|
145
|
+
- `external` default for `self` and `hybrid`
|
|
134
146
|
|
|
135
147
|
Selection precedence:
|
|
136
148
|
|
|
@@ -215,6 +227,14 @@ Jeff-friendly aliases map directly to core commands:
|
|
|
215
227
|
- `thirdfy-agent jeff status --intent-id ...` -> alias of `intent-status`
|
|
216
228
|
- `thirdfy-agent prompt "<text>"` -> captures prompt intent and suggests deterministic execution commands
|
|
217
229
|
|
|
230
|
+
## Provider-specific workflows
|
|
231
|
+
|
|
232
|
+
Provider and venue guides are kept outside `README` so this page stays stable as integrations grow.
|
|
233
|
+
|
|
234
|
+
- Provider index: [`docs/providers/index.md`](./docs/providers/index.md)
|
|
235
|
+
- Bitfinex runbook: [`docs/providers/bitfinex.md`](./docs/providers/bitfinex.md)
|
|
236
|
+
- DogeOS runbook: [`docs/providers/dogeos.md`](./docs/providers/dogeos.md)
|
|
237
|
+
|
|
218
238
|
## Command groups
|
|
219
239
|
|
|
220
240
|
- Discovery: `catalogs list`, `actions`
|
|
@@ -226,6 +246,12 @@ Jeff-friendly aliases map directly to core commands:
|
|
|
226
246
|
- Delegation execution helpers: `delegation balance`, `delegation redeem`
|
|
227
247
|
- Account: `credits balance`
|
|
228
248
|
|
|
249
|
+
## Where to find what is supported
|
|
250
|
+
|
|
251
|
+
- Full command groups and usage map: [`docs/command-reference.md`](./docs/command-reference.md)
|
|
252
|
+
- Live action inventory in your environment: [`docs/action-inventory-and-compatibility.md`](./docs/action-inventory-and-compatibility.md)
|
|
253
|
+
- Provider runbooks and provider status: [`docs/providers/index.md`](./docs/providers/index.md)
|
|
254
|
+
|
|
229
255
|
## Delegation lifecycle (canonical)
|
|
230
256
|
|
|
231
257
|
Use `create` + `activate` as the default delegated execution lifecycle:
|
|
@@ -325,6 +351,7 @@ thirdfy-agent managed run --agent-api-key "$AGENT_API_KEY" --run-mode thirdfy --
|
|
|
325
351
|
npm run validate:cli-contract-schemas
|
|
326
352
|
npm test
|
|
327
353
|
npm run smoke:cli
|
|
354
|
+
npm run e2e:self:managed-custody-validation
|
|
328
355
|
npm run e2e:managed:delegation:parity
|
|
329
356
|
```
|
|
330
357
|
|
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,7 +18,13 @@ 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'];
|
|
27
|
+
const CUSTODY_MODES = ['managed', 'external'];
|
|
22
28
|
const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
|
|
23
29
|
const PROFILE_DEFAULTS = {
|
|
24
30
|
personal: 'self',
|
|
@@ -52,6 +58,7 @@ main().catch((error) => {
|
|
|
52
58
|
const payload = error?.payload && typeof error.payload === 'object' ? error.payload : {};
|
|
53
59
|
const blockedReason = String(payload.blockedReason || payload.error || '').trim() || undefined;
|
|
54
60
|
const blockedStage = String(payload.blockedStage || '').trim() || undefined;
|
|
61
|
+
const onboardingHint = buildTopLevelOnboardingHint(payload);
|
|
55
62
|
printEnvelope({
|
|
56
63
|
success: false,
|
|
57
64
|
code: statusCode ? 'API_REQUEST_FAILED' : 'CLI_UNHANDLED_ERROR',
|
|
@@ -60,6 +67,7 @@ main().catch((error) => {
|
|
|
60
67
|
statusCode,
|
|
61
68
|
blockedReason,
|
|
62
69
|
blockedStage,
|
|
70
|
+
onboardingHint,
|
|
63
71
|
payload,
|
|
64
72
|
},
|
|
65
73
|
meta: { version: CLI_VERSION, apiBase: context.apiBase },
|
|
@@ -67,10 +75,27 @@ main().catch((error) => {
|
|
|
67
75
|
process.exit(1);
|
|
68
76
|
});
|
|
69
77
|
|
|
78
|
+
function buildTopLevelOnboardingHint(payload) {
|
|
79
|
+
const reason = String(payload?.blockedReason || payload?.error || '').trim();
|
|
80
|
+
if (!reason) return null;
|
|
81
|
+
if (
|
|
82
|
+
['CEX_CREDENTIAL_NOT_FOUND', 'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT', 'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED'].includes(
|
|
83
|
+
reason
|
|
84
|
+
)
|
|
85
|
+
) {
|
|
86
|
+
return {
|
|
87
|
+
type: 'bitfinex_credentials',
|
|
88
|
+
command: buildBitfinexOnboardingCommand(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
async function main() {
|
|
71
95
|
const profileState = resolveProfileState(globalFlags);
|
|
72
96
|
context.profile = profileState.profile;
|
|
73
97
|
context.runMode = profileState.runMode;
|
|
98
|
+
context.custodyMode = profileState.custodyMode;
|
|
74
99
|
context.profileConfigPath = profileState.configPath;
|
|
75
100
|
|
|
76
101
|
if (globalFlags.version) {
|
|
@@ -518,10 +543,15 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
518
543
|
const runMode = getEffectiveRunMode(ctx, flags);
|
|
519
544
|
enforceChainCompatibility(capabilities, flags, runMode);
|
|
520
545
|
const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved);
|
|
521
|
-
const normalized = backendResult.normalized
|
|
546
|
+
const normalized = applyExecutionFallbackHints(backendResult.normalized, {
|
|
547
|
+
flags,
|
|
548
|
+
runMode,
|
|
549
|
+
resolvedAction: resolved.resolvedAction,
|
|
550
|
+
});
|
|
551
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
522
552
|
printEnvelope({
|
|
523
553
|
success: normalized.success,
|
|
524
|
-
code: normalized.success ? 'PREFLIGHT_OK' : 'PREFLIGHT_FAILED',
|
|
554
|
+
code: normalized.success ? 'PREFLIGHT_OK' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'PREFLIGHT_FAILED',
|
|
525
555
|
message: backendResult.message,
|
|
526
556
|
data: normalized,
|
|
527
557
|
meta: {
|
|
@@ -548,7 +578,11 @@ async function commandRun(ctx, flags, capabilities) {
|
|
|
548
578
|
const backendResult = await runExecutionByMode(runMode, ctx, flags, resolved, {
|
|
549
579
|
skipPreflight,
|
|
550
580
|
});
|
|
551
|
-
const normalized = backendResult.normalized
|
|
581
|
+
const normalized = applyExecutionFallbackHints(backendResult.normalized, {
|
|
582
|
+
flags,
|
|
583
|
+
runMode,
|
|
584
|
+
resolvedAction: resolved.resolvedAction,
|
|
585
|
+
});
|
|
552
586
|
if (runMode === 'self' && parseBooleanFlag(flags.broadcast, false) && normalized?.success) {
|
|
553
587
|
const executed = await performSelfBroadcast(ctx, flags, resolved.resolvedAction, normalized);
|
|
554
588
|
printEnvelope({
|
|
@@ -925,8 +959,9 @@ async function commandDelegationRedeem(ctx, flags, capabilities) {
|
|
|
925
959
|
|
|
926
960
|
async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
927
961
|
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for credentials upsert');
|
|
962
|
+
const venue = requireFlag(flags, 'venue', 'Missing --venue');
|
|
928
963
|
const payload = {
|
|
929
|
-
venue
|
|
964
|
+
venue,
|
|
930
965
|
apiKey: requireFlag(flags, 'apiKey', 'Missing --api-key'),
|
|
931
966
|
apiSecret: requireFlag(flags, 'apiSecret', 'Missing --api-secret'),
|
|
932
967
|
credentialType: String(flags.credentialType || 'api_key'),
|
|
@@ -937,6 +972,19 @@ async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
|
937
972
|
if (flags.policy) payload.policy = parseJsonFlag(flags, 'policy');
|
|
938
973
|
if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
|
|
939
974
|
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
975
|
+
const probeEnabled =
|
|
976
|
+
String(venue).trim().toLowerCase() === 'bitfinex' &&
|
|
977
|
+
!parseBooleanFlag(flags.skipPermissionsProbe, false) &&
|
|
978
|
+
parseBooleanFlag(flags.verifyPermissions, true);
|
|
979
|
+
let permissionProbe = null;
|
|
980
|
+
if (probeEnabled) {
|
|
981
|
+
permissionProbe = await probeBitfinexPermissions({
|
|
982
|
+
apiKey: payload.apiKey,
|
|
983
|
+
apiSecret: payload.apiSecret,
|
|
984
|
+
timeoutMs: ctx.timeoutMs,
|
|
985
|
+
baseUrl: String(flags.bitfinexBaseUrl || process.env.BITFINEX_BASE_URL || DEFAULT_BITFINEX_BASE_URL),
|
|
986
|
+
});
|
|
987
|
+
}
|
|
940
988
|
|
|
941
989
|
const response = await requestWithRouteFallback(ctx, {
|
|
942
990
|
method: 'POST',
|
|
@@ -953,6 +1001,7 @@ async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
|
953
1001
|
data: response,
|
|
954
1002
|
meta: {
|
|
955
1003
|
apiBase: ctx.apiBase,
|
|
1004
|
+
permissionProbe,
|
|
956
1005
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
957
1006
|
},
|
|
958
1007
|
});
|
|
@@ -1354,13 +1403,16 @@ async function commandProfileInit(ctx, flags) {
|
|
|
1354
1403
|
const current = loadProfileConfig();
|
|
1355
1404
|
const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
|
|
1356
1405
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
1406
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode, runMode);
|
|
1357
1407
|
persistProfileConfig({
|
|
1358
1408
|
...current,
|
|
1359
1409
|
profile,
|
|
1360
1410
|
runMode,
|
|
1411
|
+
custodyMode,
|
|
1361
1412
|
});
|
|
1362
1413
|
ctx.profile = profile;
|
|
1363
1414
|
ctx.runMode = runMode;
|
|
1415
|
+
ctx.custodyMode = custodyMode;
|
|
1364
1416
|
printEnvelope({
|
|
1365
1417
|
success: true,
|
|
1366
1418
|
code: 'PROFILE_INITIALIZED',
|
|
@@ -1368,6 +1420,7 @@ async function commandProfileInit(ctx, flags) {
|
|
|
1368
1420
|
data: {
|
|
1369
1421
|
profile,
|
|
1370
1422
|
runMode,
|
|
1423
|
+
custodyMode,
|
|
1371
1424
|
configPath: getProfileConfigPath(),
|
|
1372
1425
|
},
|
|
1373
1426
|
meta: { apiBase: ctx.apiBase },
|
|
@@ -1379,13 +1432,16 @@ async function commandProfileUse(ctx, flags) {
|
|
|
1379
1432
|
const profile = normalizeProfileName(flags.profile || flags.name || current.profile || 'personal');
|
|
1380
1433
|
// `profile use` intentionally re-applies profile defaults unless caller pins a mode.
|
|
1381
1434
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
1435
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode || current.custodyMode, runMode);
|
|
1382
1436
|
persistProfileConfig({
|
|
1383
1437
|
...current,
|
|
1384
1438
|
profile,
|
|
1385
1439
|
runMode,
|
|
1440
|
+
custodyMode,
|
|
1386
1441
|
});
|
|
1387
1442
|
ctx.profile = profile;
|
|
1388
1443
|
ctx.runMode = runMode;
|
|
1444
|
+
ctx.custodyMode = custodyMode;
|
|
1389
1445
|
printEnvelope({
|
|
1390
1446
|
success: true,
|
|
1391
1447
|
code: 'PROFILE_UPDATED',
|
|
@@ -1393,6 +1449,7 @@ async function commandProfileUse(ctx, flags) {
|
|
|
1393
1449
|
data: {
|
|
1394
1450
|
profile,
|
|
1395
1451
|
runMode,
|
|
1452
|
+
custodyMode,
|
|
1396
1453
|
defaults: PROFILE_DEFAULTS,
|
|
1397
1454
|
configPath: getProfileConfigPath(),
|
|
1398
1455
|
},
|
|
@@ -1409,6 +1466,7 @@ async function commandProfileShow(ctx, _flags) {
|
|
|
1409
1466
|
data: {
|
|
1410
1467
|
profile: ctx.profile || config.profile || 'personal',
|
|
1411
1468
|
runMode: ctx.runMode || config.runMode || 'thirdfy',
|
|
1469
|
+
custodyMode: ctx.custodyMode || config.custodyMode || defaultCustodyModeForRunMode(ctx.runMode || config.runMode || 'thirdfy'),
|
|
1412
1470
|
profileDefaults: PROFILE_DEFAULTS,
|
|
1413
1471
|
configPath: getProfileConfigPath(),
|
|
1414
1472
|
config,
|
|
@@ -1431,6 +1489,10 @@ async function commandConfigSet(ctx, flags) {
|
|
|
1431
1489
|
if (canonicalKey === 'runmode' && hasValue) {
|
|
1432
1490
|
normalizeRunMode(value);
|
|
1433
1491
|
}
|
|
1492
|
+
if (canonicalKey === 'custodymode' && hasValue) {
|
|
1493
|
+
const runModeHint = normalizeRunMode(flags.runMode || loadProfileConfig().runMode || 'thirdfy');
|
|
1494
|
+
normalizeCustodyMode(value, runModeHint);
|
|
1495
|
+
}
|
|
1434
1496
|
|
|
1435
1497
|
const current = loadProfileConfig();
|
|
1436
1498
|
const next = setNestedConfigValue(current, key, value);
|
|
@@ -1452,6 +1514,11 @@ async function commandLogin(ctx, flags) {
|
|
|
1452
1514
|
const current = loadProfileConfig();
|
|
1453
1515
|
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
1454
1516
|
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
1517
|
+
const loginRunMode = normalizeRunMode(flags.runMode || current.runMode || process.env.THIRDFY_RUN_MODE || 'thirdfy');
|
|
1518
|
+
const custodyMode = normalizeCustodyMode(
|
|
1519
|
+
flags.custodyMode || current.custodyMode || process.env.THIRDFY_CUSTODY_MODE,
|
|
1520
|
+
loginRunMode
|
|
1521
|
+
);
|
|
1455
1522
|
let loginSource = 'token';
|
|
1456
1523
|
|
|
1457
1524
|
if (!authToken && !ownerSessionToken) {
|
|
@@ -1482,8 +1549,40 @@ async function commandLogin(ctx, flags) {
|
|
|
1482
1549
|
);
|
|
1483
1550
|
}
|
|
1484
1551
|
|
|
1552
|
+
let bootstrapAgentApiKey = '';
|
|
1553
|
+
let bootstrapPerformed = false;
|
|
1554
|
+
const requestedAgentApiKey = String(flags.agentApiKey || '').trim();
|
|
1555
|
+
const bootstrapAgentKey = String(flags.agentKey || '').trim();
|
|
1556
|
+
if (!requestedAgentApiKey && bootstrapAgentKey) {
|
|
1557
|
+
const bootstrapHeaders = ownerSessionToken
|
|
1558
|
+
? { 'x-owner-session-token': ownerSessionToken }
|
|
1559
|
+
: authToken
|
|
1560
|
+
? { Authorization: `Bearer ${authToken}` }
|
|
1561
|
+
: null;
|
|
1562
|
+
if (bootstrapHeaders) {
|
|
1563
|
+
const bootstrapResponse = await requestWithRouteFallback(ctx, {
|
|
1564
|
+
method: 'POST',
|
|
1565
|
+
routes: ['/api/v1/agent/onboarding/me/bootstrap-self'],
|
|
1566
|
+
headers: bootstrapHeaders,
|
|
1567
|
+
body: {
|
|
1568
|
+
agentKey: bootstrapAgentKey,
|
|
1569
|
+
runMode: loginRunMode,
|
|
1570
|
+
custodyMode,
|
|
1571
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
1572
|
+
rotate: parseBooleanFlag(flags.rotateAgentKey, false),
|
|
1573
|
+
},
|
|
1574
|
+
});
|
|
1575
|
+
bootstrapAgentApiKey = String(
|
|
1576
|
+
bootstrapResponse?.agentApiKey || bootstrapResponse?.data?.agentApiKey || ''
|
|
1577
|
+
).trim();
|
|
1578
|
+
bootstrapPerformed = true;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1485
1582
|
const next = {
|
|
1486
1583
|
...current,
|
|
1584
|
+
runMode: loginRunMode,
|
|
1585
|
+
custodyMode,
|
|
1487
1586
|
auth: {
|
|
1488
1587
|
...(current.auth && typeof current.auth === 'object' ? current.auth : {}),
|
|
1489
1588
|
lastLoginAt: new Date().toISOString(),
|
|
@@ -1500,7 +1599,12 @@ async function commandLogin(ctx, flags) {
|
|
|
1500
1599
|
if (flags.agentApiKey) {
|
|
1501
1600
|
next.agent = {
|
|
1502
1601
|
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
1503
|
-
apiKey:
|
|
1602
|
+
apiKey: requestedAgentApiKey,
|
|
1603
|
+
};
|
|
1604
|
+
} else if (bootstrapAgentApiKey) {
|
|
1605
|
+
next.agent = {
|
|
1606
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
1607
|
+
apiKey: bootstrapAgentApiKey,
|
|
1504
1608
|
};
|
|
1505
1609
|
}
|
|
1506
1610
|
persistProfileConfig(next);
|
|
@@ -1511,7 +1615,10 @@ async function commandLogin(ctx, flags) {
|
|
|
1511
1615
|
data: {
|
|
1512
1616
|
hasAuthToken: Boolean(authToken),
|
|
1513
1617
|
hasOwnerSessionToken: Boolean(ownerSessionToken),
|
|
1514
|
-
hasAgentApiKey: Boolean(
|
|
1618
|
+
hasAgentApiKey: Boolean(requestedAgentApiKey || bootstrapAgentApiKey),
|
|
1619
|
+
bootstrapPerformed,
|
|
1620
|
+
runMode: loginRunMode,
|
|
1621
|
+
custodyMode,
|
|
1515
1622
|
configPath: getProfileConfigPath(),
|
|
1516
1623
|
},
|
|
1517
1624
|
meta: { apiBase: ctx.apiBase },
|
|
@@ -1575,9 +1682,14 @@ function resolveProfileState(flags) {
|
|
|
1575
1682
|
PROFILE_DEFAULTS[profile] ||
|
|
1576
1683
|
'thirdfy'
|
|
1577
1684
|
);
|
|
1685
|
+
const custodyMode = normalizeCustodyMode(
|
|
1686
|
+
flags.custodyMode || process.env.THIRDFY_CUSTODY_MODE || persisted.custodyMode,
|
|
1687
|
+
runMode
|
|
1688
|
+
);
|
|
1578
1689
|
return {
|
|
1579
1690
|
profile,
|
|
1580
1691
|
runMode,
|
|
1692
|
+
custodyMode,
|
|
1581
1693
|
configPath: getProfileConfigPath(),
|
|
1582
1694
|
};
|
|
1583
1695
|
}
|
|
@@ -1595,6 +1707,18 @@ function normalizeRunMode(value) {
|
|
|
1595
1707
|
throw new Error(`Unsupported --run-mode "${value}". Supported: ${RUN_MODES.join(', ')}`);
|
|
1596
1708
|
}
|
|
1597
1709
|
|
|
1710
|
+
function defaultCustodyModeForRunMode(runMode) {
|
|
1711
|
+
return runMode === 'thirdfy' ? 'managed' : 'external';
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function normalizeCustodyMode(value, runModeHint = 'thirdfy') {
|
|
1715
|
+
const fallback = defaultCustodyModeForRunMode(runModeHint);
|
|
1716
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
1717
|
+
if (!normalized) return fallback;
|
|
1718
|
+
if (CUSTODY_MODES.includes(normalized)) return normalized;
|
|
1719
|
+
throw new Error(`Unsupported custody mode "${value}". Supported: ${CUSTODY_MODES.join(', ')}`);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1598
1722
|
function normalizeManagedRunMode(value) {
|
|
1599
1723
|
const mode = normalizeRunMode(value || 'thirdfy');
|
|
1600
1724
|
if (mode === 'self') {
|
|
@@ -1711,6 +1835,66 @@ function enforceChainCompatibility(capabilities, flags, runMode) {
|
|
|
1711
1835
|
}
|
|
1712
1836
|
}
|
|
1713
1837
|
|
|
1838
|
+
function shouldUseBuildTxExecution(runMode, resolved) {
|
|
1839
|
+
if (runMode !== 'self' && runMode !== 'hybrid') return false;
|
|
1840
|
+
const meta = resolved?.resolvedActionMeta || null;
|
|
1841
|
+
if (!meta || typeof meta !== 'object') return true;
|
|
1842
|
+
if (meta.supportsBuildTx === false) return false;
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
function buildBitfinexOnboardingCommand() {
|
|
1847
|
+
return [
|
|
1848
|
+
'thirdfy-agent credentials upsert',
|
|
1849
|
+
'--auth-token "$THIRDFY_AUTH_TOKEN"',
|
|
1850
|
+
'--venue bitfinex',
|
|
1851
|
+
'--api-key "<BITFINEX_API_KEY>"',
|
|
1852
|
+
'--api-secret "<BITFINEX_API_SECRET>"',
|
|
1853
|
+
'--verify-permissions',
|
|
1854
|
+
'--json',
|
|
1855
|
+
].join(' ');
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedAction }) {
|
|
1859
|
+
if (!normalized || typeof normalized !== 'object') return normalized;
|
|
1860
|
+
const next = { ...normalized };
|
|
1861
|
+
const reasons = new Set();
|
|
1862
|
+
const topBlockedReason = String(next.blockedReason || '').trim();
|
|
1863
|
+
if (topBlockedReason) reasons.add(topBlockedReason);
|
|
1864
|
+
const blockedByReason = next.blockedByReason && typeof next.blockedByReason === 'object' ? next.blockedByReason : {};
|
|
1865
|
+
for (const key of Object.keys(blockedByReason)) {
|
|
1866
|
+
if (String(key || '').trim()) reasons.add(String(key).trim());
|
|
1867
|
+
}
|
|
1868
|
+
const resolvedActionLower = String(resolvedAction || '').trim().toLowerCase();
|
|
1869
|
+
const looksBitfinexAction = resolvedActionLower.includes('bitfinex');
|
|
1870
|
+
const cexCredentialMissingSignals = new Set([
|
|
1871
|
+
'CEX_CREDENTIAL_NOT_FOUND',
|
|
1872
|
+
'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT',
|
|
1873
|
+
'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED',
|
|
1874
|
+
]);
|
|
1875
|
+
const hasMissingCredentialSignal = Array.from(reasons).some((code) => cexCredentialMissingSignals.has(code));
|
|
1876
|
+
if (looksBitfinexAction && hasMissingCredentialSignal) {
|
|
1877
|
+
next.onboardingHint = {
|
|
1878
|
+
type: 'bitfinex_credentials',
|
|
1879
|
+
message:
|
|
1880
|
+
'Bitfinex credentials are missing for this delegated account. Onboard credentials first, then retry the same command.',
|
|
1881
|
+
command: buildBitfinexOnboardingCommand(),
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
if (
|
|
1885
|
+
looksBitfinexAction &&
|
|
1886
|
+
runMode !== 'thirdfy' &&
|
|
1887
|
+
(topBlockedReason === 'SELF_MODE_REQUIRES_BUILD_TX' || topBlockedReason === 'HYBRID_MODE_REQUIRES_BUILD_TX')
|
|
1888
|
+
) {
|
|
1889
|
+
next.onboardingHint = {
|
|
1890
|
+
...(next.onboardingHint || {}),
|
|
1891
|
+
runMode: 'thirdfy',
|
|
1892
|
+
runModeMessage: 'Bitfinex actions require delegated execute-intent rail. Re-run with --run-mode thirdfy.',
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
return next;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1714
1898
|
function shouldUseBuildTxPreflight(runMode, resolved) {
|
|
1715
1899
|
if (runMode !== 'self') return false;
|
|
1716
1900
|
const meta = resolved?.resolvedActionMeta || null;
|
|
@@ -1725,7 +1909,7 @@ async function runPreflightByMode(runMode, ctx, flags, resolved) {
|
|
|
1725
1909
|
const normalized = await executeSelfRun(
|
|
1726
1910
|
ctx,
|
|
1727
1911
|
flags,
|
|
1728
|
-
|
|
1912
|
+
resolved,
|
|
1729
1913
|
{ skipPreflight: route === 'build_tx' }
|
|
1730
1914
|
);
|
|
1731
1915
|
const blockedByGovernance = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
@@ -1843,6 +2027,7 @@ async function executeThirdfyRun(ctx, flags, resolved, options) {
|
|
|
1843
2027
|
|
|
1844
2028
|
async function executeSelfRun(ctx, flags, resolved, options) {
|
|
1845
2029
|
const skipPreflight = Boolean(options?.skipPreflight);
|
|
2030
|
+
const blockedReasonCode = String(options?.blockedReasonCode || 'SELF_MODE_REQUIRES_BUILD_TX');
|
|
1846
2031
|
if (!skipPreflight) {
|
|
1847
2032
|
const preflightPayload = buildIntentPayload(flags, {
|
|
1848
2033
|
validationOnly: true,
|
|
@@ -1861,6 +2046,17 @@ async function executeSelfRun(ctx, flags, resolved, options) {
|
|
|
1861
2046
|
return preflightNormalized;
|
|
1862
2047
|
}
|
|
1863
2048
|
}
|
|
2049
|
+
if (!shouldUseBuildTxExecution(options?.runMode || 'self', resolved)) {
|
|
2050
|
+
const normalized = normalizeIntentResponse({
|
|
2051
|
+
success: false,
|
|
2052
|
+
status: 'failed',
|
|
2053
|
+
blockedReason: blockedReasonCode,
|
|
2054
|
+
blockedStage: 'routing',
|
|
2055
|
+
error: 'Selected action does not support build-tx self lane. Use --run-mode thirdfy for delegated CEX execution.',
|
|
2056
|
+
});
|
|
2057
|
+
normalized.preflightBlocked = true;
|
|
2058
|
+
return normalized;
|
|
2059
|
+
}
|
|
1864
2060
|
const buildTxPayload = buildBuildTxPayload(flags, resolved.resolvedAction);
|
|
1865
2061
|
const buildTx = await apiPost(ctx, '/api/v1/agent/build-tx', buildTxPayload);
|
|
1866
2062
|
return normalizeIntentResponse({
|
|
@@ -1873,7 +2069,11 @@ async function executeSelfRun(ctx, flags, resolved, options) {
|
|
|
1873
2069
|
}
|
|
1874
2070
|
|
|
1875
2071
|
async function executeHybridRun(ctx, flags, resolved, options) {
|
|
1876
|
-
const selfResult = await executeSelfRun(ctx, flags, resolved,
|
|
2072
|
+
const selfResult = await executeSelfRun(ctx, flags, resolved, {
|
|
2073
|
+
...(options || {}),
|
|
2074
|
+
runMode: 'hybrid',
|
|
2075
|
+
blockedReasonCode: 'HYBRID_MODE_REQUIRES_BUILD_TX',
|
|
2076
|
+
});
|
|
1877
2077
|
if (!selfResult.success) return selfResult;
|
|
1878
2078
|
|
|
1879
2079
|
const mirrorPayload = buildIntentPayload(flags, {
|
|
@@ -2454,6 +2654,114 @@ function safeJsonParse(text) {
|
|
|
2454
2654
|
}
|
|
2455
2655
|
}
|
|
2456
2656
|
|
|
2657
|
+
function parseBitfinexPermissionRows(rawData) {
|
|
2658
|
+
const rows = Array.isArray(rawData) ? rawData : [];
|
|
2659
|
+
const parsed = {};
|
|
2660
|
+
for (const row of rows) {
|
|
2661
|
+
if (!Array.isArray(row) || row.length < 3) continue;
|
|
2662
|
+
const scope = String(row[0] || '').trim().toLowerCase();
|
|
2663
|
+
if (!scope) continue;
|
|
2664
|
+
const read = Number(row[1]) === 1 ? 1 : 0;
|
|
2665
|
+
const write = Number(row[2]) === 1 ? 1 : 0;
|
|
2666
|
+
parsed[scope] = { read, write };
|
|
2667
|
+
}
|
|
2668
|
+
return parsed;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
function getMissingBitfinexScopes(scopePermissions) {
|
|
2672
|
+
const missing = [];
|
|
2673
|
+
for (const [scope, required] of Object.entries(BITFINEX_REQUIRED_SCOPE_PERMISSIONS)) {
|
|
2674
|
+
const actual = scopePermissions[scope] || { read: 0, write: 0 };
|
|
2675
|
+
if (actual.read < required.read || actual.write < required.write) {
|
|
2676
|
+
missing.push({
|
|
2677
|
+
scope,
|
|
2678
|
+
required,
|
|
2679
|
+
actual,
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return missing;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
async function probeBitfinexPermissions({ apiKey, apiSecret, timeoutMs, baseUrl }) {
|
|
2687
|
+
const normalizedBaseUrl = String(baseUrl || DEFAULT_BITFINEX_BASE_URL).trim().replace(/\/+$/, '');
|
|
2688
|
+
const apiPath = '/v2/auth/r/permissions';
|
|
2689
|
+
const nonce = Date.now().toString();
|
|
2690
|
+
const payload = '{}';
|
|
2691
|
+
const signature = createHmac('sha384', String(apiSecret || ''))
|
|
2692
|
+
.update(`/api${apiPath}${nonce}${payload}`)
|
|
2693
|
+
.digest('hex');
|
|
2694
|
+
const controller = new AbortController();
|
|
2695
|
+
const timer = setTimeout(() => controller.abort(), Number(timeoutMs || DEFAULT_TIMEOUT_MS));
|
|
2696
|
+
try {
|
|
2697
|
+
const response = await fetch(`${normalizedBaseUrl}${apiPath}`, {
|
|
2698
|
+
method: 'POST',
|
|
2699
|
+
headers: {
|
|
2700
|
+
'content-type': 'application/json',
|
|
2701
|
+
'bfx-apikey': String(apiKey || ''),
|
|
2702
|
+
'bfx-nonce': nonce,
|
|
2703
|
+
'bfx-signature': signature,
|
|
2704
|
+
},
|
|
2705
|
+
body: payload,
|
|
2706
|
+
signal: controller.signal,
|
|
2707
|
+
});
|
|
2708
|
+
const text = await response.text();
|
|
2709
|
+
const parsed = text ? safeJsonParse(text) : {};
|
|
2710
|
+
const bitfinexApiError =
|
|
2711
|
+
Array.isArray(parsed) && String(parsed[0] || '').trim().toLowerCase() === 'error'
|
|
2712
|
+
? {
|
|
2713
|
+
code: Number(parsed[1]) || null,
|
|
2714
|
+
message: String(parsed[2] || 'Unknown Bitfinex API error'),
|
|
2715
|
+
}
|
|
2716
|
+
: parsed && typeof parsed === 'object' && String(parsed.event || '').trim().toLowerCase() === 'error'
|
|
2717
|
+
? {
|
|
2718
|
+
code: Number(parsed.code) || null,
|
|
2719
|
+
message: String(parsed.msg || parsed.message || 'Unknown Bitfinex API error'),
|
|
2720
|
+
}
|
|
2721
|
+
: null;
|
|
2722
|
+
if (!response.ok) {
|
|
2723
|
+
throw createCliError(
|
|
2724
|
+
'BITFINEX_PERMISSIONS_PROBE_FAILED',
|
|
2725
|
+
`Bitfinex permissions probe failed with HTTP ${response.status}. Verify API key/secret and key validity before onboarding.`,
|
|
2726
|
+
{ statusCode: response.status }
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
if (bitfinexApiError) {
|
|
2730
|
+
throw createCliError(
|
|
2731
|
+
'BITFINEX_PERMISSIONS_PROBE_FAILED',
|
|
2732
|
+
`Bitfinex permissions probe failed: ${bitfinexApiError.message}. Verify API key/secret and key validity before onboarding.`,
|
|
2733
|
+
{ statusCode: response.status, bitfinexApiError }
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
const scopePermissions = parseBitfinexPermissionRows(parsed);
|
|
2737
|
+
const missingScopes = getMissingBitfinexScopes(scopePermissions);
|
|
2738
|
+
if (missingScopes.length > 0) {
|
|
2739
|
+
throw createCliError(
|
|
2740
|
+
'BITFINEX_PERMISSION_SCOPE_MISSING',
|
|
2741
|
+
`Bitfinex key is missing required scopes (${missingScopes
|
|
2742
|
+
.map((entry) => entry.scope)
|
|
2743
|
+
.join(', ')}). Enable funding+wallets read/write and retry onboarding.`,
|
|
2744
|
+
{ missingScopes, scopePermissions }
|
|
2745
|
+
);
|
|
2746
|
+
}
|
|
2747
|
+
return {
|
|
2748
|
+
venue: 'bitfinex',
|
|
2749
|
+
checked: true,
|
|
2750
|
+
scopes: scopePermissions,
|
|
2751
|
+
missingScopes: [],
|
|
2752
|
+
};
|
|
2753
|
+
} catch (error) {
|
|
2754
|
+
if (error?.payload) throw error;
|
|
2755
|
+
throw createCliError(
|
|
2756
|
+
'BITFINEX_PERMISSIONS_PROBE_FAILED',
|
|
2757
|
+
`Could not verify Bitfinex key permissions. ${String(error?.message || 'Unknown probe error')}`,
|
|
2758
|
+
{}
|
|
2759
|
+
);
|
|
2760
|
+
} finally {
|
|
2761
|
+
clearTimeout(timer);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2457
2765
|
function printEnvelope(payload) {
|
|
2458
2766
|
if (jsonMode) {
|
|
2459
2767
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
@@ -2469,11 +2777,11 @@ function printHelp() {
|
|
|
2469
2777
|
thirdfy-agent ${CLI_VERSION}
|
|
2470
2778
|
|
|
2471
2779
|
Core commands:
|
|
2472
|
-
thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--json]
|
|
2473
|
-
thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid] [--json]
|
|
2780
|
+
thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--custody-mode managed|external] [--json]
|
|
2781
|
+
thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid] [--custody-mode managed|external] [--json]
|
|
2474
2782
|
thirdfy-agent profile show [--json]
|
|
2475
2783
|
thirdfy-agent config set --key <path> --value <value> [--json]
|
|
2476
|
-
thirdfy-agent login [--auth-token <token> | --owner-session-token <token>] [--agent-api-key <key>] [--json]
|
|
2784
|
+
thirdfy-agent login [--auth-token <token> | --owner-session-token <token>] [--agent-api-key <key>] [--agent-key <key>] [--run-mode <mode>] [--custody-mode managed|external] [--wallet-address <addr>] [--json]
|
|
2477
2785
|
thirdfy-agent logout [--all] [--clear-agent-key] [--json]
|
|
2478
2786
|
thirdfy-agent whoami [--json]
|
|
2479
2787
|
thirdfy-agent catalogs list [--json]
|
|
@@ -2491,7 +2799,7 @@ Core commands:
|
|
|
2491
2799
|
thirdfy-agent managed wallet init [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
|
|
2492
2800
|
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
2801
|
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]
|
|
2802
|
+
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
2803
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
|
2496
2804
|
thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
|
|
2497
2805
|
thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
|
|
@@ -2513,6 +2821,7 @@ Core commands:
|
|
|
2513
2821
|
Global flags:
|
|
2514
2822
|
--api-base <url> default: https://api.thirdfy.com
|
|
2515
2823
|
--run-mode <mode> thirdfy | self | hybrid
|
|
2824
|
+
--custody-mode <mode> managed | external
|
|
2516
2825
|
--profile <name> personal | builder | network
|
|
2517
2826
|
--env <file> load env vars from file
|
|
2518
2827
|
--timeout <ms> request timeout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thirdfy/agent-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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",
|
|
@@ -26,11 +27,12 @@
|
|
|
26
27
|
"e2e:delegation:lifecycle": "node ./scripts/e2e/delegation-lifecycle.mjs",
|
|
27
28
|
"e2e:delegation:commands": "node ./scripts/e2e/delegation-command-parity.mjs",
|
|
28
29
|
"e2e:self:delegation:parity": "node ./scripts/e2e/self-delegation-parity.mjs",
|
|
30
|
+
"e2e:self:managed-custody-validation": "node ./scripts/e2e/self-managed-custody-validation.mjs",
|
|
29
31
|
"e2e:managed:delegation:parity": "node ./scripts/e2e/managed-custodial-lifecycle.mjs",
|
|
30
32
|
"e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
|
|
31
33
|
"e2e:report:last": "node ./scripts/e2e/report-last.mjs",
|
|
32
34
|
"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"
|
|
35
|
+
"prepublishOnly": "npm run validate:public-docs && npm run validate:cli-contract-schemas && npm run test && npm run smoke:cli"
|
|
34
36
|
},
|
|
35
37
|
"keywords": [
|
|
36
38
|
"thirdfy",
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
},
|
|
53
55
|
"homepage": "https://github.com/thirdfy/agent-cli#readme",
|
|
54
56
|
"dependencies": {
|
|
57
|
+
"@open-wallet-standard/core": "^1.3.0",
|
|
55
58
|
"ethers": "^6.16.0"
|
|
56
59
|
}
|
|
57
60
|
}
|
|
@@ -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,
|