@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 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
 
@@ -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: String(flags.agentApiKey).trim(),
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(flags.agentApiKey),
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.10",
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
  }