@thirdfy/agent-cli 0.1.27 → 0.1.31

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,8 +4,32 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.31] - 2026-05-07
8
+
9
+ ### Fixed
10
+
11
+ - **Swap amounts (self / build-tx / execute-intent):** When using `amountInHuman` + `tokenInDecimals`, prepared params now set canonical `amountIn` to the **human decimal** string (matching `amountInRaw` normalization). Previously `amountIn` incorrectly carried base-unit raw integers, which could mis-size swaps outside the managed-wallet execute path.
12
+
13
+ ## [0.1.30] - 2026-05-07
14
+
15
+ ### Fixed
16
+
17
+ - **Swap amounts:** `amountInRaw` now requires `tokenInDecimals`; without it the CLI errors instead of sending base units as a human `amountIn` (catastrophic mis-size risk).
18
+ - **Managed wallet payload:** Legacy `amountIn` is treated as a **human decimal** string (API contract); base units require `amountInRaw` + `tokenInDecimals`.
19
+ - **Managed wallet payload:** `legacy_human` path strips `tokenInDecimals` from the execute payload for consistency.
20
+
21
+ ## [0.1.29] - 2026-05-07
22
+
23
+ ### Fixed
24
+
25
+ - **Managed wallet swaps:** Normalize `amountInRaw` / `amountInHuman` into the single human `amountIn` field required by Thirdfy managed-wallet swap execution, preventing ambiguous amount payloads for `agent_wallet` agents.
26
+
27
+ ## [0.1.28] - 2026-05-05
28
+
7
29
  ### Added
8
30
 
31
+ - `thirdfy-agent analytics portfolio` for API-owned agent portfolio NAV, principal, PnL, ROI, APR, holdings, and confidence reads.
32
+ - `thirdfy-agent analytics leaderboard` for category/network/window-ranked agent performance across `trading`, `yield`, and `prediction`.
9
33
  - **`npm run publish:npm:local`** (`scripts/local-npm-publish.sh`): loads gitignored `.env`, exports `NODE_AUTH_TOKEN` from `NPM_TOKEN`, then runs `release:npm` via **node** (`scripts/release-npm.mjs`, `--dry-run` or `--publish`) so the outer `npm run` does not strip registry auth before the release script runs. Documented in `docs/releasing.md` Path B.
10
34
 
11
35
  ### Fixed
@@ -15,6 +39,8 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
15
39
 
16
40
  ### Changed
17
41
 
42
+ - Refactored portfolio-related read commands to share query-param and `meta` construction (`applySharedPortfolioListingFiltersToQuery`, `buildAgentKeyPortfolioSummaryQuery`, `metaPortfolioListingFilters`) so telemetry summary and portfolio summary stay aligned.
43
+ - Documented portfolio analytics commands as read-only CLI surfaces over Thirdfy API accounting.
18
44
  - **CI:** Release workflow no longer requests `id-token: write` (npm publish uses `secrets.NPM_TOKEN` only).
19
45
 
20
46
  ## [0.1.27] - 2026-05-05
package/README.md CHANGED
@@ -40,7 +40,7 @@ npx @thirdfy/agent-cli --help
40
40
 
41
41
  ## Release notes
42
42
 
43
- Version history and highlights: [CHANGELOG.md](./CHANGELOG.md). Recent CLI releases document provider discovery parity, metadata-first `actions --provider` filtering, earn deposit guards, and README refresh through **v0.1.27**.
43
+ Version history and highlights: [CHANGELOG.md](./CHANGELOG.md). Highlights through **v0.1.31** include provider discovery parity, portfolio analytics, managed-wallet swap normalization, and **self/build-tx** swap human-decimal `amountIn` parity when using `amountInHuman` + `tokenInDecimals`.
44
44
 
45
45
  **Maintainers — npm:** follow [docs/releasing.md](./docs/releasing.md). From a clone with gitignored `.env`, use `npm run publish:npm:local -- --dry-run` then `npm run publish:npm:local`. Or run `npm run release:npm` after exporting tokens in the shell. Never commit npm tokens — use [.env.example](./.env.example) as a template only.
46
46
 
@@ -53,8 +53,8 @@ export THIRDFY_OWNER_SESSION_TOKEN="..."
53
53
 
54
54
  thirdfy-agent actions --agent-api-key "$AGENT_API_KEY" --json
55
55
  thirdfy-agent profile init --profile personal --json
56
- thirdfy-agent preflight --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --estimated-amount-usd 25 --json
57
- thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --estimated-amount-usd 25 --json
56
+ thirdfy-agent preflight --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' --estimated-amount-usd 25 --json
57
+ thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' --estimated-amount-usd 25 --json
58
58
  thirdfy-agent intent-status --intent-id "<intentId>" --json
59
59
  ```
60
60
 
@@ -189,7 +189,7 @@ thirdfy-agent run \
189
189
  --run-mode self \
190
190
  --broadcast \
191
191
  --action swap \
192
- --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","chainId":8453}' \
192
+ --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6,"chainId":8453}' \
193
193
  --json
194
194
  ```
195
195
 
@@ -203,7 +203,7 @@ thirdfy-agent run \
203
203
  --run-mode hybrid \
204
204
  --hybrid-wallet-mode agent_wallet \
205
205
  --action swap \
206
- --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","chainId":8453}' \
206
+ --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6,"chainId":8453}' \
207
207
  --estimated-amount-usd 25 \
208
208
  --json
209
209
  ```
@@ -232,7 +232,7 @@ thirdfy-agent doctor self --json
232
232
  For `--action swap`, CLI enforces an explicit amount unit contract:
233
233
 
234
234
  - Provide exactly one of:
235
- - `amountInRaw` (base units integer string), or
235
+ - `amountInRaw` + `tokenInDecimals` (base units integer string), or
236
236
  - `amountInHuman` + `tokenInDecimals` (CLI converts to `amountInRaw`)
237
237
  - Legacy `amountIn` is accepted only as raw integer compatibility input.
238
238
  - Ambiguous payloads (both/none) fail with `AMOUNT_UNIT_AMBIGUOUS`.
@@ -242,7 +242,7 @@ Examples:
242
242
  ```bash
243
243
  # Raw/base units
244
244
  thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap \
245
- --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","chainId":8453}' --json
245
+ --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6,"chainId":8453}' --json
246
246
 
247
247
  # Human units + decimals
248
248
  thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap \
@@ -335,7 +335,7 @@ thirdfy-agent delegation redeem \
335
335
  --run-mode thirdfy \
336
336
  --agent-api-key "$AGENT_API_KEY" \
337
337
  --action swap \
338
- --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' \
338
+ --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' \
339
339
  --estimated-amount-usd 5 \
340
340
  --json
341
341
  ```
@@ -369,7 +369,7 @@ thirdfy-agent managed wallet init --auth-token "$THIRDFY_AUTH_TOKEN" --json
369
369
  # or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
370
370
  thirdfy-agent managed wallet grant --auth-token "$THIRDFY_AUTH_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --token-address "0x..." --max-usd-per-day 250 --json
371
371
  # or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
372
- thirdfy-agent agent run --agent-api-key "$AGENT_API_KEY" --signer-method fanout_intent --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --json
372
+ thirdfy-agent agent run --agent-api-key "$AGENT_API_KEY" --signer-method fanout_intent --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","tokenInDecimals":6}' --json
373
373
  ```
374
374
 
375
375
  ### Signer command model (managed + BYOW)
@@ -279,6 +279,14 @@ async function main() {
279
279
  await commandWalletSubmit(context, subFlags, capabilities);
280
280
  return;
281
281
  }
282
+ if (commandKey3 === 'analytics portfolio') {
283
+ await commandAnalyticsPortfolio(context, subFlags, capabilities);
284
+ return;
285
+ }
286
+ if (commandKey3 === 'analytics leaderboard') {
287
+ await commandAnalyticsLeaderboard(context, subFlags, capabilities);
288
+ return;
289
+ }
282
290
 
283
291
  const rootCommand = parsed.positionals[0];
284
292
  switch (rootCommand) {
@@ -675,6 +683,17 @@ function decimalToRawUnits(amountHuman, decimals) {
675
683
  return (wholePart + fracPart).toString();
676
684
  }
677
685
 
686
+ function rawUnitsToDecimalString(amountRaw, decimals) {
687
+ const raw = normalizeIntegerRawAmount(amountRaw);
688
+ const base = 10n ** BigInt(decimals);
689
+ const n = BigInt(raw);
690
+ const whole = n / base;
691
+ const frac = n % base;
692
+ if (decimals === 0 || frac === 0n) return whole.toString();
693
+ const fracText = frac.toString().padStart(decimals, '0').replace(/0+$/, '');
694
+ return `${whole.toString()}.${fracText}`;
695
+ }
696
+
678
697
  function normalizeSwapAmountContract(params) {
679
698
  const hasRaw = params.amountInRaw !== undefined && params.amountInRaw !== null && String(params.amountInRaw).trim() !== '';
680
699
  const hasHuman = params.amountInHuman !== undefined && params.amountInHuman !== null && String(params.amountInHuman).trim() !== '';
@@ -695,34 +714,58 @@ function normalizeSwapAmountContract(params) {
695
714
 
696
715
  if (hasRaw) {
697
716
  const amountInRaw = normalizeIntegerRawAmount(params.amountInRaw);
717
+ const hasDecimals =
718
+ params.tokenInDecimals !== undefined &&
719
+ params.tokenInDecimals !== null &&
720
+ String(params.tokenInDecimals).trim() !== '';
721
+ if (!hasDecimals) {
722
+ throw createCliError(
723
+ 'AMOUNT_UNIT_DECIMALS_REQUIRED',
724
+ 'amountInRaw requires tokenInDecimals (integer 0-36).'
725
+ );
726
+ }
727
+ const decimals = Number(params.tokenInDecimals);
728
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
729
+ throw createCliError(
730
+ 'AMOUNT_UNIT_DECIMALS_REQUIRED',
731
+ 'amountInRaw requires tokenInDecimals (integer 0-36).'
732
+ );
733
+ }
734
+ const amountInHuman = rawUnitsToDecimalString(amountInRaw, decimals);
698
735
  return {
699
736
  params: {
700
737
  ...params,
701
738
  amountInRaw,
702
- amountIn: amountInRaw,
739
+ amountIn: amountInHuman,
740
+ tokenInDecimals: decimals,
703
741
  },
704
742
  swapInput: {
705
743
  amountInRaw,
706
- amountInHuman: null,
707
- tokenInDecimals: null,
744
+ amountInHuman,
745
+ tokenInDecimals: decimals,
708
746
  unitSource: 'raw',
709
747
  },
710
748
  };
711
749
  }
712
750
 
713
751
  if (hasLegacy) {
714
- const amountInRaw = normalizeIntegerRawAmount(params.amountIn, 'AMOUNT_UNIT_INVALID');
752
+ const amountInHuman = String(params.amountIn).trim();
753
+ if (!/^\d+(\.\d+)?$/.test(amountInHuman)) {
754
+ throw createCliError(
755
+ 'AMOUNT_UNIT_INVALID',
756
+ 'Legacy swap amountIn must be a decimal token-unit string (example: "1.25"). For base units use amountInRaw with tokenInDecimals.'
757
+ );
758
+ }
715
759
  return {
716
760
  params: {
717
761
  ...params,
718
- amountInRaw,
719
- amountIn: amountInRaw,
762
+ amountIn: amountInHuman,
720
763
  },
721
764
  swapInput: {
722
- amountInRaw,
723
- amountInHuman: null,
765
+ amountInRaw: null,
766
+ amountInHuman,
724
767
  tokenInDecimals: null,
725
- unitSource: 'legacy_raw',
768
+ unitSource: 'legacy_human',
726
769
  },
727
770
  };
728
771
  }
@@ -751,7 +794,7 @@ function normalizeSwapAmountContract(params) {
751
794
  ...params,
752
795
  amountInHuman,
753
796
  amountInRaw,
754
- amountIn: amountInRaw,
797
+ amountIn: amountInHuman,
755
798
  tokenInDecimals: decimals,
756
799
  },
757
800
  swapInput: {
@@ -808,21 +851,82 @@ async function commandCatalogsList(ctx, flags, capabilities) {
808
851
  });
809
852
  }
810
853
 
811
- async function commandDataSummary(ctx, flags, capabilities) {
854
+ /** Query params shared by telemetry summary and portfolio summary (`agentKey` + scope filters). */
855
+ function applySharedPortfolioListingFiltersToQuery(query, flags) {
856
+ if (flags.chainId) query.set('chainId', String(flags.chainId));
857
+ if (flags.network) query.set('networkId', String(flags.network));
858
+ if (flags.networkId) query.set('networkId', String(flags.networkId));
859
+ if (flags.chainType) query.set('chainType', String(flags.chainType));
860
+ if (flags.category) query.set('category', String(flags.category));
861
+ }
862
+
863
+ function buildAgentKeyPortfolioSummaryQuery(flags, { includeLimit = false } = {}) {
812
864
  const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
813
865
  const query = new URLSearchParams({ agentKey });
814
- if (flags.chainId) query.set('chainId', String(flags.chainId));
815
- if (flags.limit) query.set('limit', String(flags.limit));
866
+ applySharedPortfolioListingFiltersToQuery(query, flags);
867
+ if (includeLimit && flags.limit) query.set('limit', String(flags.limit));
868
+ return { agentKey, query };
869
+ }
870
+
871
+ /** `meta` fields that echo portfolio scope filters (keep in sync with query construction). */
872
+ function metaPortfolioListingFilters(ctx, flags, capabilities, agentKey) {
873
+ return {
874
+ apiBase: ctx.apiBase,
875
+ agentKey,
876
+ chainId: flags.chainId ? Number(flags.chainId) : null,
877
+ networkId: flags.networkId || flags.network || null,
878
+ chainType: flags.chainType || null,
879
+ category: flags.category || null,
880
+ capabilitiesVersion: capabilities?.contractVersion || null,
881
+ };
882
+ }
883
+
884
+ async function commandDataSummary(ctx, flags, capabilities) {
885
+ const { agentKey, query } = buildAgentKeyPortfolioSummaryQuery(flags, { includeLimit: true });
816
886
  const response = await apiGet(ctx, `/api/v1/agent/telemetry/summary?${query.toString()}`);
817
887
  printEnvelope({
818
888
  success: true,
819
889
  code: 'DATA_SUMMARY_OK',
820
890
  message: 'Agent data summary loaded',
821
891
  data: response,
892
+ meta: metaPortfolioListingFilters(ctx, flags, capabilities, agentKey),
893
+ });
894
+ }
895
+
896
+ async function commandAnalyticsPortfolio(ctx, flags, capabilities) {
897
+ const { agentKey, query } = buildAgentKeyPortfolioSummaryQuery(flags);
898
+ const response = await apiGet(ctx, `/api/v1/agent/portfolio/summary?${query.toString()}`);
899
+ printEnvelope({
900
+ success: true,
901
+ code: 'ANALYTICS_PORTFOLIO_OK',
902
+ message: 'Agent portfolio summary loaded',
903
+ data: response,
904
+ meta: metaPortfolioListingFilters(ctx, flags, capabilities, agentKey),
905
+ });
906
+ }
907
+
908
+ async function commandAnalyticsLeaderboard(ctx, flags, capabilities) {
909
+ const query = new URLSearchParams();
910
+ const window = flags.window || '7d';
911
+ const metric = flags.metric || 'roiPct';
912
+ applySharedPortfolioListingFiltersToQuery(query, flags);
913
+ query.set('window', String(window));
914
+ query.set('metric', String(metric));
915
+ if (flags.limit) query.set('limit', String(flags.limit));
916
+ const response = await apiGet(ctx, `/api/v1/agent/portfolio/leaderboard?${query.toString()}`);
917
+ printEnvelope({
918
+ success: true,
919
+ code: 'ANALYTICS_LEADERBOARD_OK',
920
+ message: 'Agent portfolio leaderboard loaded',
921
+ data: response,
822
922
  meta: {
823
923
  apiBase: ctx.apiBase,
824
- agentKey,
825
- chainId: flags.chainId ? Number(flags.chainId) : null,
924
+ networkId: flags.networkId || flags.network || null,
925
+ chainType: flags.chainType || null,
926
+ category: flags.category || null,
927
+ window,
928
+ metric,
929
+ capabilitiesVersion: capabilities?.contractVersion || null,
826
930
  },
827
931
  });
828
932
  }
@@ -3020,6 +3124,26 @@ function buildManagedExecutePayload(flags, options) {
3020
3124
  const params = getPreparedParams(flags);
3021
3125
  const managedParams = { ...params };
3022
3126
  if (flags.__swapInput?.unitSource === 'human' && flags.__swapInput.amountInHuman) {
3127
+ delete managedParams.amountInRaw;
3128
+ delete managedParams.amountInHuman;
3129
+ delete managedParams.tokenInDecimals;
3130
+ managedParams.amountIn = flags.__swapInput.amountInHuman;
3131
+ } else if (flags.__swapInput?.unitSource === 'raw' && flags.__swapInput.amountInRaw) {
3132
+ const decimals = flags.__swapInput.tokenInDecimals;
3133
+ delete managedParams.amountInHuman;
3134
+ delete managedParams.amountInRaw;
3135
+ delete managedParams.tokenInDecimals;
3136
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
3137
+ throw createCliError(
3138
+ 'AMOUNT_UNIT_DECIMALS_REQUIRED',
3139
+ 'amountInRaw requires tokenInDecimals (integer 0-36).'
3140
+ );
3141
+ }
3142
+ managedParams.amountIn = rawUnitsToDecimalString(flags.__swapInput.amountInRaw, decimals);
3143
+ } else if (flags.__swapInput?.unitSource === 'legacy_human' && flags.__swapInput.amountInHuman) {
3144
+ delete managedParams.amountInRaw;
3145
+ delete managedParams.amountInHuman;
3146
+ delete managedParams.tokenInDecimals;
3023
3147
  managedParams.amountIn = flags.__swapInput.amountInHuman;
3024
3148
  }
3025
3149
  const payload = {
@@ -4009,7 +4133,9 @@ Core commands:
4009
4133
  thirdfy-agent whoami [--json]
4010
4134
  thirdfy-agent catalogs list [--json]
4011
4135
  thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
4012
- thirdfy-agent data summary --agent-key <key> [--chain-id <id>] [--limit <n>] [--json]
4136
+ thirdfy-agent data summary --agent-key <key> [--chain-id <id>] [--network <id>] [--category trading|yield|prediction] [--limit <n>] [--json]
4137
+ thirdfy-agent analytics portfolio --agent-key <key> [--network base-mainnet] [--category trading|yield|prediction] [--json]
4138
+ thirdfy-agent analytics leaderboard [--category trading|yield|prediction] [--network base-mainnet] [--window 24h|7d|30d|all] [--metric roiPct|pnlUsd|aprPct|currentValueUsd] [--json]
4013
4139
  thirdfy-agent track actions --agent-key <key> [--limit <n>] [--json]
4014
4140
  thirdfy-agent track events --agent-key <key> [--limit <n>] [--json]
4015
4141
  thirdfy-agent track action --agent-api-key <key> --action <name> [--category <name>] [--source agent|external_mcp|operator|thirdfy|custom] [--provider <id>] [--protocol <id>] [--namespace <name>] [--action-type supported|custom] [--status observed|planned|skipped|submitted|confirmed|failed|blocked] [--chain-id <id>] [--params <json>] [--json]
@@ -4062,7 +4188,7 @@ Global flags:
4062
4188
  --profile <name> personal | builder | network
4063
4189
  --env <file> load env vars from file
4064
4190
  --timeout <ms> request timeout
4065
- --params <json> for swap: exactly one of {amountInRaw} or {amountInHuman + tokenInDecimals}
4191
+ --params <json> for swap: amountInRaw + tokenInDecimals, or amountInHuman + tokenInDecimals, or legacy human amountIn (decimal string)
4066
4192
  --broadcast with run --run-mode self, sign and broadcast using OWS
4067
4193
  --user-did <did> required for managed wallet server execution paths
4068
4194
  --allow-wallet-mismatch bypass strict funded-wallet vs execution-wallet guard
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.31",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {