@thirdfy/agent-cli 0.1.10 → 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 +11 -0
- package/README.md +8 -0
- package/bin/thirdfy-agent.mjs +83 -5
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ 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
|
+
|
|
7
18
|
## [0.1.9] - 2026-04-08
|
|
8
19
|
|
|
9
20
|
**npm:** [`@thirdfy/agent-cli@0.1.9`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.9)
|
package/README.md
CHANGED
|
@@ -81,9 +81,13 @@ Execution-only workflows can run with only `AGENT_API_KEY` after onboarding is c
|
|
|
81
81
|
# Store auth defaults once
|
|
82
82
|
thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-api-key "$AGENT_API_KEY" --json
|
|
83
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
|
+
|
|
84
87
|
# Inspect config
|
|
85
88
|
thirdfy-agent whoami --json
|
|
86
89
|
thirdfy-agent config set --key runMode --value self --json
|
|
90
|
+
thirdfy-agent config set --key custodyMode --value external --json
|
|
87
91
|
|
|
88
92
|
# Local-only logout (default)
|
|
89
93
|
thirdfy-agent logout --json
|
|
@@ -136,6 +140,9 @@ Auth expectations by mode:
|
|
|
136
140
|
- `self`: requires execution identity (`agentApiKey`) today; keyless self lane is blocked with deterministic `SELF_OPEN_DISABLED`.
|
|
137
141
|
- `hybrid`: requires execution identity and governed mirror semantics.
|
|
138
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`
|
|
139
146
|
|
|
140
147
|
Selection precedence:
|
|
141
148
|
|
|
@@ -344,6 +351,7 @@ thirdfy-agent managed run --agent-api-key "$AGENT_API_KEY" --run-mode thirdfy --
|
|
|
344
351
|
npm run validate:cli-contract-schemas
|
|
345
352
|
npm test
|
|
346
353
|
npm run smoke:cli
|
|
354
|
+
npm run e2e:self:managed-custody-validation
|
|
347
355
|
npm run e2e:managed:delegation:parity
|
|
348
356
|
```
|
|
349
357
|
|
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -24,6 +24,7 @@ const BITFINEX_REQUIRED_SCOPE_PERMISSIONS = {
|
|
|
24
24
|
wallets: { read: 1, write: 1 },
|
|
25
25
|
};
|
|
26
26
|
const RUN_MODES = ['thirdfy', 'self', 'hybrid'];
|
|
27
|
+
const CUSTODY_MODES = ['managed', 'external'];
|
|
27
28
|
const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
|
|
28
29
|
const PROFILE_DEFAULTS = {
|
|
29
30
|
personal: 'self',
|
|
@@ -94,6 +95,7 @@ async function main() {
|
|
|
94
95
|
const profileState = resolveProfileState(globalFlags);
|
|
95
96
|
context.profile = profileState.profile;
|
|
96
97
|
context.runMode = profileState.runMode;
|
|
98
|
+
context.custodyMode = profileState.custodyMode;
|
|
97
99
|
context.profileConfigPath = profileState.configPath;
|
|
98
100
|
|
|
99
101
|
if (globalFlags.version) {
|
|
@@ -1401,13 +1403,16 @@ async function commandProfileInit(ctx, flags) {
|
|
|
1401
1403
|
const current = loadProfileConfig();
|
|
1402
1404
|
const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
|
|
1403
1405
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
1406
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode, runMode);
|
|
1404
1407
|
persistProfileConfig({
|
|
1405
1408
|
...current,
|
|
1406
1409
|
profile,
|
|
1407
1410
|
runMode,
|
|
1411
|
+
custodyMode,
|
|
1408
1412
|
});
|
|
1409
1413
|
ctx.profile = profile;
|
|
1410
1414
|
ctx.runMode = runMode;
|
|
1415
|
+
ctx.custodyMode = custodyMode;
|
|
1411
1416
|
printEnvelope({
|
|
1412
1417
|
success: true,
|
|
1413
1418
|
code: 'PROFILE_INITIALIZED',
|
|
@@ -1415,6 +1420,7 @@ async function commandProfileInit(ctx, flags) {
|
|
|
1415
1420
|
data: {
|
|
1416
1421
|
profile,
|
|
1417
1422
|
runMode,
|
|
1423
|
+
custodyMode,
|
|
1418
1424
|
configPath: getProfileConfigPath(),
|
|
1419
1425
|
},
|
|
1420
1426
|
meta: { apiBase: ctx.apiBase },
|
|
@@ -1426,13 +1432,16 @@ async function commandProfileUse(ctx, flags) {
|
|
|
1426
1432
|
const profile = normalizeProfileName(flags.profile || flags.name || current.profile || 'personal');
|
|
1427
1433
|
// `profile use` intentionally re-applies profile defaults unless caller pins a mode.
|
|
1428
1434
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
1435
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode || current.custodyMode, runMode);
|
|
1429
1436
|
persistProfileConfig({
|
|
1430
1437
|
...current,
|
|
1431
1438
|
profile,
|
|
1432
1439
|
runMode,
|
|
1440
|
+
custodyMode,
|
|
1433
1441
|
});
|
|
1434
1442
|
ctx.profile = profile;
|
|
1435
1443
|
ctx.runMode = runMode;
|
|
1444
|
+
ctx.custodyMode = custodyMode;
|
|
1436
1445
|
printEnvelope({
|
|
1437
1446
|
success: true,
|
|
1438
1447
|
code: 'PROFILE_UPDATED',
|
|
@@ -1440,6 +1449,7 @@ async function commandProfileUse(ctx, flags) {
|
|
|
1440
1449
|
data: {
|
|
1441
1450
|
profile,
|
|
1442
1451
|
runMode,
|
|
1452
|
+
custodyMode,
|
|
1443
1453
|
defaults: PROFILE_DEFAULTS,
|
|
1444
1454
|
configPath: getProfileConfigPath(),
|
|
1445
1455
|
},
|
|
@@ -1456,6 +1466,7 @@ async function commandProfileShow(ctx, _flags) {
|
|
|
1456
1466
|
data: {
|
|
1457
1467
|
profile: ctx.profile || config.profile || 'personal',
|
|
1458
1468
|
runMode: ctx.runMode || config.runMode || 'thirdfy',
|
|
1469
|
+
custodyMode: ctx.custodyMode || config.custodyMode || defaultCustodyModeForRunMode(ctx.runMode || config.runMode || 'thirdfy'),
|
|
1459
1470
|
profileDefaults: PROFILE_DEFAULTS,
|
|
1460
1471
|
configPath: getProfileConfigPath(),
|
|
1461
1472
|
config,
|
|
@@ -1478,6 +1489,10 @@ async function commandConfigSet(ctx, flags) {
|
|
|
1478
1489
|
if (canonicalKey === 'runmode' && hasValue) {
|
|
1479
1490
|
normalizeRunMode(value);
|
|
1480
1491
|
}
|
|
1492
|
+
if (canonicalKey === 'custodymode' && hasValue) {
|
|
1493
|
+
const runModeHint = normalizeRunMode(flags.runMode || loadProfileConfig().runMode || 'thirdfy');
|
|
1494
|
+
normalizeCustodyMode(value, runModeHint);
|
|
1495
|
+
}
|
|
1481
1496
|
|
|
1482
1497
|
const current = loadProfileConfig();
|
|
1483
1498
|
const next = setNestedConfigValue(current, key, value);
|
|
@@ -1499,6 +1514,11 @@ async function commandLogin(ctx, flags) {
|
|
|
1499
1514
|
const current = loadProfileConfig();
|
|
1500
1515
|
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
1501
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
|
+
);
|
|
1502
1522
|
let loginSource = 'token';
|
|
1503
1523
|
|
|
1504
1524
|
if (!authToken && !ownerSessionToken) {
|
|
@@ -1529,8 +1549,40 @@ async function commandLogin(ctx, flags) {
|
|
|
1529
1549
|
);
|
|
1530
1550
|
}
|
|
1531
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
|
+
|
|
1532
1582
|
const next = {
|
|
1533
1583
|
...current,
|
|
1584
|
+
runMode: loginRunMode,
|
|
1585
|
+
custodyMode,
|
|
1534
1586
|
auth: {
|
|
1535
1587
|
...(current.auth && typeof current.auth === 'object' ? current.auth : {}),
|
|
1536
1588
|
lastLoginAt: new Date().toISOString(),
|
|
@@ -1547,7 +1599,12 @@ async function commandLogin(ctx, flags) {
|
|
|
1547
1599
|
if (flags.agentApiKey) {
|
|
1548
1600
|
next.agent = {
|
|
1549
1601
|
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
1550
|
-
apiKey:
|
|
1602
|
+
apiKey: requestedAgentApiKey,
|
|
1603
|
+
};
|
|
1604
|
+
} else if (bootstrapAgentApiKey) {
|
|
1605
|
+
next.agent = {
|
|
1606
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
1607
|
+
apiKey: bootstrapAgentApiKey,
|
|
1551
1608
|
};
|
|
1552
1609
|
}
|
|
1553
1610
|
persistProfileConfig(next);
|
|
@@ -1558,7 +1615,10 @@ async function commandLogin(ctx, flags) {
|
|
|
1558
1615
|
data: {
|
|
1559
1616
|
hasAuthToken: Boolean(authToken),
|
|
1560
1617
|
hasOwnerSessionToken: Boolean(ownerSessionToken),
|
|
1561
|
-
hasAgentApiKey: Boolean(
|
|
1618
|
+
hasAgentApiKey: Boolean(requestedAgentApiKey || bootstrapAgentApiKey),
|
|
1619
|
+
bootstrapPerformed,
|
|
1620
|
+
runMode: loginRunMode,
|
|
1621
|
+
custodyMode,
|
|
1562
1622
|
configPath: getProfileConfigPath(),
|
|
1563
1623
|
},
|
|
1564
1624
|
meta: { apiBase: ctx.apiBase },
|
|
@@ -1622,9 +1682,14 @@ function resolveProfileState(flags) {
|
|
|
1622
1682
|
PROFILE_DEFAULTS[profile] ||
|
|
1623
1683
|
'thirdfy'
|
|
1624
1684
|
);
|
|
1685
|
+
const custodyMode = normalizeCustodyMode(
|
|
1686
|
+
flags.custodyMode || process.env.THIRDFY_CUSTODY_MODE || persisted.custodyMode,
|
|
1687
|
+
runMode
|
|
1688
|
+
);
|
|
1625
1689
|
return {
|
|
1626
1690
|
profile,
|
|
1627
1691
|
runMode,
|
|
1692
|
+
custodyMode,
|
|
1628
1693
|
configPath: getProfileConfigPath(),
|
|
1629
1694
|
};
|
|
1630
1695
|
}
|
|
@@ -1642,6 +1707,18 @@ function normalizeRunMode(value) {
|
|
|
1642
1707
|
throw new Error(`Unsupported --run-mode "${value}". Supported: ${RUN_MODES.join(', ')}`);
|
|
1643
1708
|
}
|
|
1644
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
|
+
|
|
1645
1722
|
function normalizeManagedRunMode(value) {
|
|
1646
1723
|
const mode = normalizeRunMode(value || 'thirdfy');
|
|
1647
1724
|
if (mode === 'self') {
|
|
@@ -2700,11 +2777,11 @@ function printHelp() {
|
|
|
2700
2777
|
thirdfy-agent ${CLI_VERSION}
|
|
2701
2778
|
|
|
2702
2779
|
Core commands:
|
|
2703
|
-
thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--json]
|
|
2704
|
-
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]
|
|
2705
2782
|
thirdfy-agent profile show [--json]
|
|
2706
2783
|
thirdfy-agent config set --key <path> --value <value> [--json]
|
|
2707
|
-
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]
|
|
2708
2785
|
thirdfy-agent logout [--all] [--clear-agent-key] [--json]
|
|
2709
2786
|
thirdfy-agent whoami [--json]
|
|
2710
2787
|
thirdfy-agent catalogs list [--json]
|
|
@@ -2744,6 +2821,7 @@ Core commands:
|
|
|
2744
2821
|
Global flags:
|
|
2745
2822
|
--api-base <url> default: https://api.thirdfy.com
|
|
2746
2823
|
--run-mode <mode> thirdfy | self | hybrid
|
|
2824
|
+
--custody-mode <mode> managed | external
|
|
2747
2825
|
--profile <name> personal | builder | network
|
|
2748
2826
|
--env <file> load env vars from file
|
|
2749
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": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"e2e:delegation:lifecycle": "node ./scripts/e2e/delegation-lifecycle.mjs",
|
|
28
28
|
"e2e:delegation:commands": "node ./scripts/e2e/delegation-command-parity.mjs",
|
|
29
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",
|
|
30
31
|
"e2e:managed:delegation:parity": "node ./scripts/e2e/managed-custodial-lifecycle.mjs",
|
|
31
32
|
"e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
|
|
32
33
|
"e2e:report:last": "node ./scripts/e2e/report-last.mjs",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
},
|
|
54
55
|
"homepage": "https://github.com/thirdfy/agent-cli#readme",
|
|
55
56
|
"dependencies": {
|
|
57
|
+
"@open-wallet-standard/core": "^1.3.0",
|
|
56
58
|
"ethers": "^6.16.0"
|
|
57
59
|
}
|
|
58
60
|
}
|