@thirdfy/agent-cli 0.1.25 → 0.1.28

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,9 +4,53 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.28] - 2026-05-05
8
+
9
+ ### Added
10
+
11
+ - `thirdfy-agent analytics portfolio` for API-owned agent portfolio NAV, principal, PnL, ROI, APR, holdings, and confidence reads.
12
+ - `thirdfy-agent analytics leaderboard` for category/network/window-ranked agent performance across `trading`, `yield`, and `prediction`.
13
+ - **`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.
14
+
15
+ ### Fixed
16
+
17
+ - `release:npm`: `npm publish` now uses a short-lived `NPM_CONFIG_USERCONFIG` file with `//registry.npmjs.org/:_authToken=…` when a token is present, so npm 10+ authenticates reliably (Bearer env alone was not enough in this repo’s CLI runs).
18
+ - `release:npm -- --publish`: when both `NODE_AUTH_TOKEN` and `NPM_TOKEN` are set but `NODE_AUTH_TOKEN` is much shorter than `NPM_TOKEN` (common placeholder), use `NPM_TOKEN` for the publish subprocess and print a warning.
19
+
20
+ ### Changed
21
+
22
+ - Refactored portfolio-related read commands to share query-param and `meta` construction (`applySharedPortfolioListingFiltersToQuery`, `buildAgentKeyPortfolioSummaryQuery`, `metaPortfolioListingFilters`) so telemetry summary and portfolio summary stay aligned.
23
+ - Documented portfolio analytics commands as read-only CLI surfaces over Thirdfy API accounting.
24
+ - **CI:** Release workflow no longer requests `id-token: write` (npm publish uses `secrets.NPM_TOKEN` only).
25
+
26
+ ## [0.1.27] - 2026-05-05
27
+
28
+ **npm:** [`@thirdfy/agent-cli@0.1.27`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.27)
29
+ **Git tag:** [`v0.1.27`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.27)
30
+
31
+ ### Added
32
+
33
+ - Maintainer [docs/releasing.md](./docs/releasing.md) runbook for npm + GitHub Actions, `.env.example` template, and `npm run release:npm` (`scripts/release-npm.mjs`) with dry-run default and stricter checks for `--publish`.
34
+
35
+ ### Fixed
36
+
37
+ - **Bugbot (PR #24):** `release:npm` maps `NPM_TOKEN` → `NODE_AUTH_TOKEN` for the publish subprocess; `npm publish` uses `--ignore-scripts` after explicit checks so `prepublishOnly` does not run twice. **Release** GitHub Action uses the same `npm publish --ignore-scripts` after its validation steps.
38
+
39
+ ## [0.1.26] - 2026-05-05
40
+
41
+ **Git tag:** [`v0.1.26`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.26) — provider parity merged to `main`; not published to the npm registry (use **0.1.27** for the first npm build that includes PR #23 plus release tooling from PR #24).
42
+
43
+ ### Fixed
44
+
45
+ - **Provider discovery parity:** Align `actions --provider` filtering with MCP/API provider taxonomy for provider-less spot, Hyperliquid/perps, bridge, DogeOS, prediction, and earn catalog rows.
46
+ - **Metadata-first discovery:** Prefer API-supplied `providerId`, `domain`, and `category` for provider filtering, keeping action-name inference as a legacy fallback only.
47
+ - **Bugbot:** `actions --provider` key fallback now matches provider suffix patterns (e.g. `execute_bridge` for `--provider bridge`).
48
+ - **Bugbot:** After metadata mismatch, hyperliquid/polymarket/bridge/DogeOS branches use action-key matching only (removed redundant `provider ===` checks and dead final equality).
49
+
7
50
  ## [0.1.25] - 2026-05-04
8
51
 
9
- **npm:** `@thirdfy/agent-cli@0.1.25` (publish after merge)
52
+ **npm:** [`@thirdfy/agent-cli@0.1.25`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.25)
53
+ **Git tag:** [`v0.1.25`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.25)
10
54
 
11
55
  ### Changed
12
56
 
package/README.md CHANGED
@@ -40,7 +40,9 @@ 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 earn/prediction provider discovery, case-insensitive `actions --provider` filtering, self-lane guards for offchain venue orders, and npm-facing README refresh (**v0.1.24**).
43
+ Version history and highlights: [CHANGELOG.md](./CHANGELOG.md). Recent CLI releases document provider discovery parity, metadata-first `actions --provider` filtering, earn deposit guards, portfolio analytics commands, and README refresh through **v0.1.28**.
44
+
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.
44
46
 
45
47
  ## Quick start
46
48
 
@@ -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) {
@@ -808,21 +816,82 @@ async function commandCatalogsList(ctx, flags, capabilities) {
808
816
  });
809
817
  }
810
818
 
811
- async function commandDataSummary(ctx, flags, capabilities) {
819
+ /** Query params shared by telemetry summary and portfolio summary (`agentKey` + scope filters). */
820
+ function applySharedPortfolioListingFiltersToQuery(query, flags) {
821
+ if (flags.chainId) query.set('chainId', String(flags.chainId));
822
+ if (flags.network) query.set('networkId', String(flags.network));
823
+ if (flags.networkId) query.set('networkId', String(flags.networkId));
824
+ if (flags.chainType) query.set('chainType', String(flags.chainType));
825
+ if (flags.category) query.set('category', String(flags.category));
826
+ }
827
+
828
+ function buildAgentKeyPortfolioSummaryQuery(flags, { includeLimit = false } = {}) {
812
829
  const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
813
830
  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));
831
+ applySharedPortfolioListingFiltersToQuery(query, flags);
832
+ if (includeLimit && flags.limit) query.set('limit', String(flags.limit));
833
+ return { agentKey, query };
834
+ }
835
+
836
+ /** `meta` fields that echo portfolio scope filters (keep in sync with query construction). */
837
+ function metaPortfolioListingFilters(ctx, flags, capabilities, agentKey) {
838
+ return {
839
+ apiBase: ctx.apiBase,
840
+ agentKey,
841
+ chainId: flags.chainId ? Number(flags.chainId) : null,
842
+ networkId: flags.networkId || flags.network || null,
843
+ chainType: flags.chainType || null,
844
+ category: flags.category || null,
845
+ capabilitiesVersion: capabilities?.contractVersion || null,
846
+ };
847
+ }
848
+
849
+ async function commandDataSummary(ctx, flags, capabilities) {
850
+ const { agentKey, query } = buildAgentKeyPortfolioSummaryQuery(flags, { includeLimit: true });
816
851
  const response = await apiGet(ctx, `/api/v1/agent/telemetry/summary?${query.toString()}`);
817
852
  printEnvelope({
818
853
  success: true,
819
854
  code: 'DATA_SUMMARY_OK',
820
855
  message: 'Agent data summary loaded',
821
856
  data: response,
857
+ meta: metaPortfolioListingFilters(ctx, flags, capabilities, agentKey),
858
+ });
859
+ }
860
+
861
+ async function commandAnalyticsPortfolio(ctx, flags, capabilities) {
862
+ const { agentKey, query } = buildAgentKeyPortfolioSummaryQuery(flags);
863
+ const response = await apiGet(ctx, `/api/v1/agent/portfolio/summary?${query.toString()}`);
864
+ printEnvelope({
865
+ success: true,
866
+ code: 'ANALYTICS_PORTFOLIO_OK',
867
+ message: 'Agent portfolio summary loaded',
868
+ data: response,
869
+ meta: metaPortfolioListingFilters(ctx, flags, capabilities, agentKey),
870
+ });
871
+ }
872
+
873
+ async function commandAnalyticsLeaderboard(ctx, flags, capabilities) {
874
+ const query = new URLSearchParams();
875
+ const window = flags.window || '7d';
876
+ const metric = flags.metric || 'roiPct';
877
+ applySharedPortfolioListingFiltersToQuery(query, flags);
878
+ query.set('window', String(window));
879
+ query.set('metric', String(metric));
880
+ if (flags.limit) query.set('limit', String(flags.limit));
881
+ const response = await apiGet(ctx, `/api/v1/agent/portfolio/leaderboard?${query.toString()}`);
882
+ printEnvelope({
883
+ success: true,
884
+ code: 'ANALYTICS_LEADERBOARD_OK',
885
+ message: 'Agent portfolio leaderboard loaded',
886
+ data: response,
822
887
  meta: {
823
888
  apiBase: ctx.apiBase,
824
- agentKey,
825
- chainId: flags.chainId ? Number(flags.chainId) : null,
889
+ networkId: flags.networkId || flags.network || null,
890
+ chainType: flags.chainType || null,
891
+ category: flags.category || null,
892
+ window,
893
+ metric,
894
+ capabilitiesVersion: capabilities?.contractVersion || null,
826
895
  },
827
896
  });
828
897
  }
@@ -3228,6 +3297,172 @@ function formatActionList(items) {
3228
3297
  .join(', ');
3229
3298
  }
3230
3299
 
3300
+ function normalizeProviderId(value) {
3301
+ return String(value || '').trim().toLowerCase();
3302
+ }
3303
+
3304
+ function rowActionKey(action) {
3305
+ return getActionKey(action)
3306
+ .replace(/-/g, '_')
3307
+ .toLowerCase();
3308
+ }
3309
+
3310
+ function canonicalActionKey(action) {
3311
+ return getActionKey(action)
3312
+ .replace(/_/g, '-')
3313
+ .toLowerCase();
3314
+ }
3315
+
3316
+ function inferActionProvider(action) {
3317
+ // API catalog metadata (`providerId`, `domain`, `category`) is the source of truth.
3318
+ // This fallback only keeps older catalog rows / older API deployments discoverable.
3319
+ const key = canonicalActionKey(action);
3320
+ const knownMap = {
3321
+ swap: 'trading',
3322
+ 'execute-optimal-swap': 'trading',
3323
+ 'get-best-trading-quote': 'trading',
3324
+ 'dogeos-barkswap-get-quote': 'dogeos-barkswap',
3325
+ 'dogeos-barkswap-execute-swap': 'dogeos-barkswap',
3326
+ 'dogeos-get-ecosystem-overview': 'dogeos-ecosystem',
3327
+ 'dogeos-get-wallet-actionability': 'dogeos-ecosystem',
3328
+ 'dogeos-prepare-trade': 'dogeos-ecosystem',
3329
+ 'dogeos-prepare-token-launch': 'dogeos-ecosystem',
3330
+ 'dogeos-get-chain-insights': 'dogeos-ecosystem',
3331
+ 'dogeos-get-trending-tokens': 'dogeos-ecosystem',
3332
+ 'dogeos-get-new-tokens': 'dogeos-ecosystem',
3333
+ 'dogeos-get-early-bird-tokens': 'dogeos-ecosystem',
3334
+ 'dogeos-get-only-moon-tokens': 'dogeos-ecosystem',
3335
+ 'dogeos-get-big-wins-tokens': 'dogeos-ecosystem',
3336
+ 'dogeos-get-related-tokens': 'dogeos-ecosystem',
3337
+ 'dogeos-laika-read-contract': 'dogeos-laika',
3338
+ 'dogeos-laika-execute-raw': 'dogeos-laika',
3339
+ 'dogeos-laika-launch-token': 'dogeos-laika',
3340
+ 'dogeos-laika-buy-token': 'dogeos-laika',
3341
+ 'dogeos-laika-claim': 'dogeos-laika',
3342
+ 'dogeos-laika-token-state': 'dogeos-laika',
3343
+ 'get-governance-v2-overview': 'governance-v2',
3344
+ 'get-governance-v2-timeseries': 'governance-v2',
3345
+ 'get-governance-v2-breakdowns': 'governance-v2',
3346
+ 'get-hyperliquid-perps-meta': 'hyperliquid',
3347
+ 'get-hyperliquid-all-mids': 'hyperliquid',
3348
+ 'get-hyperliquid-l2-book': 'hyperliquid',
3349
+ 'get-hyperliquid-candles': 'hyperliquid',
3350
+ 'get-hyperliquid-user-state': 'hyperliquid',
3351
+ 'get-hyperliquid-open-orders': 'hyperliquid',
3352
+ 'get-hyperliquid-setup-status': 'hyperliquid',
3353
+ 'get-hyperliquid-onboarding-plan': 'hyperliquid',
3354
+ 'prepare-hyperliquid-onboarding': 'hyperliquid',
3355
+ 'claim-hyperliquid-testnet-faucet': 'hyperliquid',
3356
+ 'get-hyperliquid-builder-fee-status': 'hyperliquid',
3357
+ 'approve-hyperliquid-agent': 'hyperliquid',
3358
+ 'approve-hyperliquid-builder-fee': 'hyperliquid',
3359
+ 'register-hyperliquid-referrer': 'hyperliquid',
3360
+ 'set-hyperliquid-referrer': 'hyperliquid',
3361
+ 'get-hyperliquid-referral-status': 'hyperliquid',
3362
+ 'deposit-hyperliquid-staking': 'hyperliquid',
3363
+ 'deposit-hyperliquid-bridge': 'hyperliquid',
3364
+ 'place-hyperliquid-perps-order': 'hyperliquid',
3365
+ 'get-perps-markets': 'hyperliquid',
3366
+ 'get-perps-account': 'hyperliquid',
3367
+ 'get-perps-position': 'hyperliquid',
3368
+ 'place-perps-order': 'hyperliquid',
3369
+ 'cancel-perps-order': 'hyperliquid',
3370
+ 'get-earn-opportunities': 'earn',
3371
+ 'get-earn-provider': 'earn',
3372
+ 'get-earn-position': 'earn',
3373
+ 'deposit-earn-position': 'earn',
3374
+ 'withdraw-earn-position': 'earn',
3375
+ 'get-morpho-vaults': 'morpho',
3376
+ 'deposit-morpho-vault': 'morpho',
3377
+ 'withdraw-morpho-vault': 'morpho',
3378
+ 'get-curve-pools': 'curve',
3379
+ 'get-curve-pool-details': 'curve',
3380
+ 'deposit-curve-pool': 'curve',
3381
+ 'withdraw-curve-pool': 'curve',
3382
+ 'get-prediction-markets': 'polymarket',
3383
+ 'get-prediction-market': 'polymarket',
3384
+ 'get-prediction-order-book': 'polymarket',
3385
+ 'get-prediction-position': 'polymarket',
3386
+ 'place-prediction-order': 'polymarket',
3387
+ 'cancel-prediction-order': 'polymarket',
3388
+ 'get-polymarket-markets': 'polymarket',
3389
+ 'get-polymarket-market': 'polymarket',
3390
+ 'get-polymarket-setup-status': 'polymarket',
3391
+ 'get-polymarket-order': 'polymarket',
3392
+ 'place-polymarket-order': 'polymarket',
3393
+ };
3394
+ if (knownMap[key]) return knownMap[key];
3395
+ if (key.includes('hyperliquid')) return 'hyperliquid';
3396
+ if (key.includes('polymarket') || key.includes('prediction')) return 'polymarket';
3397
+ if (key.includes('morpho')) return 'morpho';
3398
+ if (key.includes('curve')) return 'curve';
3399
+ if (key.includes('earn')) return 'earn';
3400
+ if (key.includes('bridge')) return 'bridge';
3401
+ if (key.includes('dogeos-barkswap')) return 'dogeos-barkswap';
3402
+ if (key.includes('dogeos-laika')) return 'dogeos-laika';
3403
+ if (key.includes('dogeos')) return 'dogeos-ecosystem';
3404
+ return '';
3405
+ }
3406
+
3407
+ function getActionProviderWithInference(action) {
3408
+ return getActionProvider(action) || inferActionProvider(action);
3409
+ }
3410
+
3411
+ function isEarnCatalogAction(action) {
3412
+ const actionKey = rowActionKey(action);
3413
+ return (
3414
+ actionKey.includes('_earn_') ||
3415
+ normalizeProviderId(action?.domain) === 'earn' ||
3416
+ normalizeProviderId(action?.category) === 'earn'
3417
+ );
3418
+ }
3419
+
3420
+ function isSpotTradingCatalogAction(action) {
3421
+ const actionKey = rowActionKey(action);
3422
+ return actionKey === 'swap' || actionKey === 'execute_optimal_swap' || actionKey === 'get_best_trading_quote';
3423
+ }
3424
+
3425
+ function isProviderActionKey(action, provider) {
3426
+ const actionKey = rowActionKey(action);
3427
+ const normalizedProvider = normalizeProviderId(provider).replace(/-/g, '_');
3428
+ if (!actionKey || !normalizedProvider) return false;
3429
+ return (
3430
+ actionKey === normalizedProvider ||
3431
+ actionKey.startsWith(`${normalizedProvider}_`) ||
3432
+ actionKey.includes(`_${normalizedProvider}_`) ||
3433
+ actionKey.endsWith(`_${normalizedProvider}`)
3434
+ );
3435
+ }
3436
+
3437
+ function actionMatchesProvider(action, requestedProvider, allActions) {
3438
+ const provider = getActionProviderWithInference(action);
3439
+ const actionKey = rowActionKey(action);
3440
+ if (provider === requestedProvider) return true;
3441
+ if (requestedProvider === 'trading') return isSpotTradingCatalogAction(action);
3442
+ // Here resolved metadata already disagrees with the filter; fall back to action-key shape only.
3443
+ if (requestedProvider === 'hyperliquid') return isProviderActionKey(action, 'hyperliquid');
3444
+ if (requestedProvider === 'polymarket') return isProviderActionKey(action, 'polymarket');
3445
+ if (requestedProvider === 'bridge') return isProviderActionKey(action, 'bridge');
3446
+ if (requestedProvider.startsWith('dogeos')) return isProviderActionKey(action, requestedProvider);
3447
+ if (requestedProvider === 'earn') {
3448
+ return provider === 'earn' || provider === 'morpho' || provider === 'curve' || isEarnCatalogAction(action);
3449
+ }
3450
+ if (requestedProvider === 'morpho' || requestedProvider === 'curve') {
3451
+ const hasProtocolSpecificEarnRows = allActions.some((candidate) => getActionProviderWithInference(candidate) === requestedProvider);
3452
+ return isProviderActionKey(action, requestedProvider) || (!hasProtocolSpecificEarnRows && isEarnCatalogAction(action));
3453
+ }
3454
+ if (requestedProvider === 'prediction') {
3455
+ return (
3456
+ provider === 'polymarket' ||
3457
+ provider === 'prediction' ||
3458
+ actionKey.includes('_prediction_') ||
3459
+ normalizeProviderId(action?.domain) === 'prediction' ||
3460
+ normalizeProviderId(action?.category) === 'prediction'
3461
+ );
3462
+ }
3463
+ return isProviderActionKey(action, requestedProvider);
3464
+ }
3465
+
3231
3466
  function applyActionFilters(actions, flags) {
3232
3467
  let filtered = actions;
3233
3468
  if (flags.catalog) {
@@ -3235,21 +3470,14 @@ function applyActionFilters(actions, flags) {
3235
3470
  filtered = filtered.filter((a) => String(a.catalog || a.catalogId || 'default').trim().toLowerCase() === expectedCatalog);
3236
3471
  }
3237
3472
  if (flags.provider) {
3238
- const expectedProvider = String(flags.provider).trim().toLowerCase();
3239
- filtered = filtered.filter((a) => {
3240
- const provider = getActionProvider(a);
3241
- const actionKey = getActionKey(a).replace(/-/g, '_').toLowerCase();
3242
- if (provider === expectedProvider) return true;
3243
- if (expectedProvider === 'earn') return provider === 'morpho' || provider === 'curve' || actionKey.includes('_earn_');
3244
- if (expectedProvider === 'prediction') return provider === 'polymarket' || actionKey.includes('_prediction_');
3245
- return false;
3246
- });
3473
+ const expectedProvider = normalizeProviderId(flags.provider);
3474
+ filtered = filtered.filter((a) => actionMatchesProvider(a, expectedProvider, filtered));
3247
3475
  }
3248
3476
  return filtered;
3249
3477
  }
3250
3478
 
3251
3479
  function getActionProvider(action) {
3252
- return String(action?.provider || action?.providerId || action?.provider_id || action?.catalog || '').trim().toLowerCase();
3480
+ return normalizeProviderId(action?.provider || action?.providerId || action?.provider_id || action?.catalog || '');
3253
3481
  }
3254
3482
 
3255
3483
  async function resolveActionSelection(ctx, flags) {
@@ -3850,7 +4078,9 @@ Core commands:
3850
4078
  thirdfy-agent whoami [--json]
3851
4079
  thirdfy-agent catalogs list [--json]
3852
4080
  thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
3853
- thirdfy-agent data summary --agent-key <key> [--chain-id <id>] [--limit <n>] [--json]
4081
+ thirdfy-agent data summary --agent-key <key> [--chain-id <id>] [--network <id>] [--category trading|yield|prediction] [--limit <n>] [--json]
4082
+ thirdfy-agent analytics portfolio --agent-key <key> [--network base-mainnet] [--category trading|yield|prediction] [--json]
4083
+ thirdfy-agent analytics leaderboard [--category trading|yield|prediction] [--network base-mainnet] [--window 24h|7d|30d|all] [--metric roiPct|pnlUsd|aprPct|currentValueUsd] [--json]
3854
4084
  thirdfy-agent track actions --agent-key <key> [--limit <n>] [--json]
3855
4085
  thirdfy-agent track events --agent-key <key> [--limit <n>] [--json]
3856
4086
  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]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.28",
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,8 @@
18
18
  "scripts": {
19
19
  "test": "node --test \"test/**/*.test.cjs\"",
20
20
  "lint": "node --check ./bin/thirdfy-agent.mjs",
21
+ "release:npm": "node ./scripts/release-npm.mjs",
22
+ "publish:npm:local": "bash ./scripts/local-npm-publish.sh",
21
23
  "validate:public-docs": "node ./scripts/validate-public-docs.mjs",
22
24
  "validate:cli-contract-schemas": "node ./scripts/validate-cli-contract-schemas.mjs",
23
25
  "validate:actions": "node ./scripts/e2e/validate-actions.mjs",