@thirdfy/agent-cli 0.1.24 → 0.1.27

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,60 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+
9
+ - **`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
+
11
+ ### Fixed
12
+
13
+ - `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).
14
+ - `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.
15
+
16
+ ### Changed
17
+
18
+ - **CI:** Release workflow no longer requests `id-token: write` (npm publish uses `secrets.NPM_TOKEN` only).
19
+
20
+ ## [0.1.27] - 2026-05-05
21
+
22
+ **npm:** [`@thirdfy/agent-cli@0.1.27`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.27)
23
+ **Git tag:** [`v0.1.27`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.27)
24
+
25
+ ### Added
26
+
27
+ - 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`.
28
+
29
+ ### Fixed
30
+
31
+ - **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.
32
+
33
+ ## [0.1.26] - 2026-05-05
34
+
35
+ **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).
36
+
37
+ ### Fixed
38
+
39
+ - **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.
40
+ - **Metadata-first discovery:** Prefer API-supplied `providerId`, `domain`, and `category` for provider filtering, keeping action-name inference as a legacy fallback only.
41
+ - **Bugbot:** `actions --provider` key fallback now matches provider suffix patterns (e.g. `execute_bridge` for `--provider bridge`).
42
+ - **Bugbot:** After metadata mismatch, hyperliquid/polymarket/bridge/DogeOS branches use action-key matching only (removed redundant `provider ===` checks and dead final equality).
43
+
44
+ ## [0.1.25] - 2026-05-04
45
+
46
+ **npm:** [`@thirdfy/agent-cli@0.1.25`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.25)
47
+ **Git tag:** [`v0.1.25`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.25)
48
+
49
+ ### Changed
50
+
51
+ - **Capabilities negotiation:** Cache negotiated Thirdfy CLI capabilities and derive provider hints from the live API response to reduce static hint drift.
52
+
53
+ ### Fixed
54
+
55
+ - **Managed wallet payloads:** Align swap and related `params` with human-readable amount fields expected by the Thirdfy API when using `amountInHuman`.
56
+
57
+ ### Added
58
+
59
+ - **Tests:** `test/managed-wallet-payload.test.cjs` for managed-wallet payload shape.
60
+
7
61
  ## [0.1.24] - 2026-05-02
8
62
 
9
63
  **npm:** [`@thirdfy/agent-cli@0.1.24`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.24)
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, and README refresh through **v0.1.27**.
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
 
@@ -27,6 +27,7 @@ const RUN_MODES = ['thirdfy', 'self', 'hybrid', 'agent_wallet'];
27
27
  const HYBRID_WALLET_MODES = ['self', 'agent_wallet'];
28
28
  const CUSTODY_MODES = ['managed', 'external'];
29
29
  const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
30
+ let negotiatedCapabilitiesCache = null;
30
31
  const PROFILE_DEFAULTS = {
31
32
  personal: 'self',
32
33
  builder: 'hybrid',
@@ -153,6 +154,7 @@ async function main() {
153
154
  }
154
155
 
155
156
  const capabilities = await negotiateCapabilities(context);
157
+ negotiatedCapabilitiesCache = capabilities;
156
158
 
157
159
  switch (commandKey) {
158
160
  case 'agent run':
@@ -556,8 +558,57 @@ function getTradingProviderHint(providerId) {
556
558
  return null;
557
559
  }
558
560
 
559
- function getCachedProviderHintFromCapabilities(_provider) {
560
- return null;
561
+ function getCachedProviderHintFromCapabilities(provider) {
562
+ const normalizedProvider = String(provider || '').trim().toLowerCase();
563
+ if (!normalizedProvider) return null;
564
+ const manifests = Array.isArray(negotiatedCapabilitiesCache?.providers?.manifests)
565
+ ? negotiatedCapabilitiesCache.providers.manifests
566
+ : [];
567
+ const match = manifests.find((entry) => String(entry?.providerId || '').trim().toLowerCase() === normalizedProvider);
568
+ if (!match) return null;
569
+ const canonicalWriteActions = {
570
+ trading: 'swap',
571
+ hyperliquid: 'place_hyperliquid_perps_order',
572
+ polymarket: 'place_polymarket_order',
573
+ prediction: 'place_prediction_order',
574
+ earn: 'deposit_earn_position',
575
+ morpho: 'deposit_earn_position',
576
+ curve: 'deposit_earn_position',
577
+ };
578
+ const canonicalReadActions = {
579
+ earn: 'get_earn_opportunities',
580
+ morpho: 'get_morpho_vaults',
581
+ curve: 'get_curve_pools',
582
+ prediction: 'get_prediction_markets',
583
+ };
584
+ return {
585
+ provider: normalizedProvider,
586
+ category: match.category || match.domain || match.providerKind || null,
587
+ ...(canonicalReadActions[normalizedProvider] ? { canonicalReadAction: canonicalReadActions[normalizedProvider] } : {}),
588
+ ...(canonicalWriteActions[normalizedProvider] ? { canonicalWriteAction: canonicalWriteActions[normalizedProvider] } : {}),
589
+ providerKind: match.providerKind || null,
590
+ capabilities: Array.isArray(match.capabilities) ? match.capabilities : [],
591
+ supportedChains: Array.isArray(match.supportedChains) ? match.supportedChains : [],
592
+ custodyMode: match.custodyMode || null,
593
+ description: match.description || null,
594
+ source: 'cli_capabilities_manifest',
595
+ };
596
+ }
597
+
598
+ function formatProviderAmbiguousActionHint(providerHint) {
599
+ if (!providerHint) return '';
600
+ const p = providerHint.provider;
601
+ const w = providerHint.canonicalWriteAction;
602
+ if (w) return ` Provider ${p} expects ${w}.`;
603
+ return ` Provider ${p} did not declare a canonical write action in capabilities; run actions --provider ${p} first.`;
604
+ }
605
+
606
+ function formatProviderUnknownActionHint(providerHint) {
607
+ if (!providerHint) return '';
608
+ const p = providerHint.provider;
609
+ const w = providerHint.canonicalWriteAction;
610
+ if (w) return ` Provider ${p} expects ${w}; run actions --provider ${p} first.`;
611
+ return ` Provider ${p} did not declare a canonical write action in capabilities; run actions --provider ${p} first.`;
561
612
  }
562
613
 
563
614
  const OFFCHAIN_VENUE_ORDER_ACTIONS = new Set([
@@ -2966,11 +3017,16 @@ function buildBuildTxPayload(flags, resolvedAction) {
2966
3017
 
2967
3018
  function buildManagedExecutePayload(flags, options) {
2968
3019
  const runMode = String(options.runMode || flags.runMode || 'agent_wallet').trim().toLowerCase();
3020
+ const params = getPreparedParams(flags);
3021
+ const managedParams = { ...params };
3022
+ if (flags.__swapInput?.unitSource === 'human' && flags.__swapInput.amountInHuman) {
3023
+ managedParams.amountIn = flags.__swapInput.amountInHuman;
3024
+ }
2969
3025
  const payload = {
2970
3026
  agentApiKey: resolveAgentApiKey(flags, runMode),
2971
3027
  action: String(options.resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
2972
3028
  userDid: String(flags.userDid || process.env.THIRDFY_USER_DID || '').trim(),
2973
- params: getPreparedParams(flags),
3029
+ params: managedParams,
2974
3030
  chainId: Number(flags.chainId || 8453),
2975
3031
  executionStrategy: {
2976
3032
  runMode,
@@ -3172,6 +3228,172 @@ function formatActionList(items) {
3172
3228
  .join(', ');
3173
3229
  }
3174
3230
 
3231
+ function normalizeProviderId(value) {
3232
+ return String(value || '').trim().toLowerCase();
3233
+ }
3234
+
3235
+ function rowActionKey(action) {
3236
+ return getActionKey(action)
3237
+ .replace(/-/g, '_')
3238
+ .toLowerCase();
3239
+ }
3240
+
3241
+ function canonicalActionKey(action) {
3242
+ return getActionKey(action)
3243
+ .replace(/_/g, '-')
3244
+ .toLowerCase();
3245
+ }
3246
+
3247
+ function inferActionProvider(action) {
3248
+ // API catalog metadata (`providerId`, `domain`, `category`) is the source of truth.
3249
+ // This fallback only keeps older catalog rows / older API deployments discoverable.
3250
+ const key = canonicalActionKey(action);
3251
+ const knownMap = {
3252
+ swap: 'trading',
3253
+ 'execute-optimal-swap': 'trading',
3254
+ 'get-best-trading-quote': 'trading',
3255
+ 'dogeos-barkswap-get-quote': 'dogeos-barkswap',
3256
+ 'dogeos-barkswap-execute-swap': 'dogeos-barkswap',
3257
+ 'dogeos-get-ecosystem-overview': 'dogeos-ecosystem',
3258
+ 'dogeos-get-wallet-actionability': 'dogeos-ecosystem',
3259
+ 'dogeos-prepare-trade': 'dogeos-ecosystem',
3260
+ 'dogeos-prepare-token-launch': 'dogeos-ecosystem',
3261
+ 'dogeos-get-chain-insights': 'dogeos-ecosystem',
3262
+ 'dogeos-get-trending-tokens': 'dogeos-ecosystem',
3263
+ 'dogeos-get-new-tokens': 'dogeos-ecosystem',
3264
+ 'dogeos-get-early-bird-tokens': 'dogeos-ecosystem',
3265
+ 'dogeos-get-only-moon-tokens': 'dogeos-ecosystem',
3266
+ 'dogeos-get-big-wins-tokens': 'dogeos-ecosystem',
3267
+ 'dogeos-get-related-tokens': 'dogeos-ecosystem',
3268
+ 'dogeos-laika-read-contract': 'dogeos-laika',
3269
+ 'dogeos-laika-execute-raw': 'dogeos-laika',
3270
+ 'dogeos-laika-launch-token': 'dogeos-laika',
3271
+ 'dogeos-laika-buy-token': 'dogeos-laika',
3272
+ 'dogeos-laika-claim': 'dogeos-laika',
3273
+ 'dogeos-laika-token-state': 'dogeos-laika',
3274
+ 'get-governance-v2-overview': 'governance-v2',
3275
+ 'get-governance-v2-timeseries': 'governance-v2',
3276
+ 'get-governance-v2-breakdowns': 'governance-v2',
3277
+ 'get-hyperliquid-perps-meta': 'hyperliquid',
3278
+ 'get-hyperliquid-all-mids': 'hyperliquid',
3279
+ 'get-hyperliquid-l2-book': 'hyperliquid',
3280
+ 'get-hyperliquid-candles': 'hyperliquid',
3281
+ 'get-hyperliquid-user-state': 'hyperliquid',
3282
+ 'get-hyperliquid-open-orders': 'hyperliquid',
3283
+ 'get-hyperliquid-setup-status': 'hyperliquid',
3284
+ 'get-hyperliquid-onboarding-plan': 'hyperliquid',
3285
+ 'prepare-hyperliquid-onboarding': 'hyperliquid',
3286
+ 'claim-hyperliquid-testnet-faucet': 'hyperliquid',
3287
+ 'get-hyperliquid-builder-fee-status': 'hyperliquid',
3288
+ 'approve-hyperliquid-agent': 'hyperliquid',
3289
+ 'approve-hyperliquid-builder-fee': 'hyperliquid',
3290
+ 'register-hyperliquid-referrer': 'hyperliquid',
3291
+ 'set-hyperliquid-referrer': 'hyperliquid',
3292
+ 'get-hyperliquid-referral-status': 'hyperliquid',
3293
+ 'deposit-hyperliquid-staking': 'hyperliquid',
3294
+ 'deposit-hyperliquid-bridge': 'hyperliquid',
3295
+ 'place-hyperliquid-perps-order': 'hyperliquid',
3296
+ 'get-perps-markets': 'hyperliquid',
3297
+ 'get-perps-account': 'hyperliquid',
3298
+ 'get-perps-position': 'hyperliquid',
3299
+ 'place-perps-order': 'hyperliquid',
3300
+ 'cancel-perps-order': 'hyperliquid',
3301
+ 'get-earn-opportunities': 'earn',
3302
+ 'get-earn-provider': 'earn',
3303
+ 'get-earn-position': 'earn',
3304
+ 'deposit-earn-position': 'earn',
3305
+ 'withdraw-earn-position': 'earn',
3306
+ 'get-morpho-vaults': 'morpho',
3307
+ 'deposit-morpho-vault': 'morpho',
3308
+ 'withdraw-morpho-vault': 'morpho',
3309
+ 'get-curve-pools': 'curve',
3310
+ 'get-curve-pool-details': 'curve',
3311
+ 'deposit-curve-pool': 'curve',
3312
+ 'withdraw-curve-pool': 'curve',
3313
+ 'get-prediction-markets': 'polymarket',
3314
+ 'get-prediction-market': 'polymarket',
3315
+ 'get-prediction-order-book': 'polymarket',
3316
+ 'get-prediction-position': 'polymarket',
3317
+ 'place-prediction-order': 'polymarket',
3318
+ 'cancel-prediction-order': 'polymarket',
3319
+ 'get-polymarket-markets': 'polymarket',
3320
+ 'get-polymarket-market': 'polymarket',
3321
+ 'get-polymarket-setup-status': 'polymarket',
3322
+ 'get-polymarket-order': 'polymarket',
3323
+ 'place-polymarket-order': 'polymarket',
3324
+ };
3325
+ if (knownMap[key]) return knownMap[key];
3326
+ if (key.includes('hyperliquid')) return 'hyperliquid';
3327
+ if (key.includes('polymarket') || key.includes('prediction')) return 'polymarket';
3328
+ if (key.includes('morpho')) return 'morpho';
3329
+ if (key.includes('curve')) return 'curve';
3330
+ if (key.includes('earn')) return 'earn';
3331
+ if (key.includes('bridge')) return 'bridge';
3332
+ if (key.includes('dogeos-barkswap')) return 'dogeos-barkswap';
3333
+ if (key.includes('dogeos-laika')) return 'dogeos-laika';
3334
+ if (key.includes('dogeos')) return 'dogeos-ecosystem';
3335
+ return '';
3336
+ }
3337
+
3338
+ function getActionProviderWithInference(action) {
3339
+ return getActionProvider(action) || inferActionProvider(action);
3340
+ }
3341
+
3342
+ function isEarnCatalogAction(action) {
3343
+ const actionKey = rowActionKey(action);
3344
+ return (
3345
+ actionKey.includes('_earn_') ||
3346
+ normalizeProviderId(action?.domain) === 'earn' ||
3347
+ normalizeProviderId(action?.category) === 'earn'
3348
+ );
3349
+ }
3350
+
3351
+ function isSpotTradingCatalogAction(action) {
3352
+ const actionKey = rowActionKey(action);
3353
+ return actionKey === 'swap' || actionKey === 'execute_optimal_swap' || actionKey === 'get_best_trading_quote';
3354
+ }
3355
+
3356
+ function isProviderActionKey(action, provider) {
3357
+ const actionKey = rowActionKey(action);
3358
+ const normalizedProvider = normalizeProviderId(provider).replace(/-/g, '_');
3359
+ if (!actionKey || !normalizedProvider) return false;
3360
+ return (
3361
+ actionKey === normalizedProvider ||
3362
+ actionKey.startsWith(`${normalizedProvider}_`) ||
3363
+ actionKey.includes(`_${normalizedProvider}_`) ||
3364
+ actionKey.endsWith(`_${normalizedProvider}`)
3365
+ );
3366
+ }
3367
+
3368
+ function actionMatchesProvider(action, requestedProvider, allActions) {
3369
+ const provider = getActionProviderWithInference(action);
3370
+ const actionKey = rowActionKey(action);
3371
+ if (provider === requestedProvider) return true;
3372
+ if (requestedProvider === 'trading') return isSpotTradingCatalogAction(action);
3373
+ // Here resolved metadata already disagrees with the filter; fall back to action-key shape only.
3374
+ if (requestedProvider === 'hyperliquid') return isProviderActionKey(action, 'hyperliquid');
3375
+ if (requestedProvider === 'polymarket') return isProviderActionKey(action, 'polymarket');
3376
+ if (requestedProvider === 'bridge') return isProviderActionKey(action, 'bridge');
3377
+ if (requestedProvider.startsWith('dogeos')) return isProviderActionKey(action, requestedProvider);
3378
+ if (requestedProvider === 'earn') {
3379
+ return provider === 'earn' || provider === 'morpho' || provider === 'curve' || isEarnCatalogAction(action);
3380
+ }
3381
+ if (requestedProvider === 'morpho' || requestedProvider === 'curve') {
3382
+ const hasProtocolSpecificEarnRows = allActions.some((candidate) => getActionProviderWithInference(candidate) === requestedProvider);
3383
+ return isProviderActionKey(action, requestedProvider) || (!hasProtocolSpecificEarnRows && isEarnCatalogAction(action));
3384
+ }
3385
+ if (requestedProvider === 'prediction') {
3386
+ return (
3387
+ provider === 'polymarket' ||
3388
+ provider === 'prediction' ||
3389
+ actionKey.includes('_prediction_') ||
3390
+ normalizeProviderId(action?.domain) === 'prediction' ||
3391
+ normalizeProviderId(action?.category) === 'prediction'
3392
+ );
3393
+ }
3394
+ return isProviderActionKey(action, requestedProvider);
3395
+ }
3396
+
3175
3397
  function applyActionFilters(actions, flags) {
3176
3398
  let filtered = actions;
3177
3399
  if (flags.catalog) {
@@ -3179,21 +3401,14 @@ function applyActionFilters(actions, flags) {
3179
3401
  filtered = filtered.filter((a) => String(a.catalog || a.catalogId || 'default').trim().toLowerCase() === expectedCatalog);
3180
3402
  }
3181
3403
  if (flags.provider) {
3182
- const expectedProvider = String(flags.provider).trim().toLowerCase();
3183
- filtered = filtered.filter((a) => {
3184
- const provider = getActionProvider(a);
3185
- const actionKey = getActionKey(a).replace(/-/g, '_').toLowerCase();
3186
- if (provider === expectedProvider) return true;
3187
- if (expectedProvider === 'earn') return provider === 'morpho' || provider === 'curve' || actionKey.includes('_earn_');
3188
- if (expectedProvider === 'prediction') return provider === 'polymarket' || actionKey.includes('_prediction_');
3189
- return false;
3190
- });
3404
+ const expectedProvider = normalizeProviderId(flags.provider);
3405
+ filtered = filtered.filter((a) => actionMatchesProvider(a, expectedProvider, filtered));
3191
3406
  }
3192
3407
  return filtered;
3193
3408
  }
3194
3409
 
3195
3410
  function getActionProvider(action) {
3196
- return String(action?.provider || action?.providerId || action?.provider_id || action?.catalog || '').trim().toLowerCase();
3411
+ return normalizeProviderId(action?.provider || action?.providerId || action?.provider_id || action?.catalog || '');
3197
3412
  }
3198
3413
 
3199
3414
  async function resolveActionSelection(ctx, flags) {
@@ -3235,7 +3450,7 @@ async function resolveActionSelection(ctx, flags) {
3235
3450
  throw new Error(
3236
3451
  `Ambiguous --action "${requestedAction}". Alias matches multiple actions: ${formatActionList(
3237
3452
  exactAlias.map((v) => v.key)
3238
- )}.${providerHint ? ` Provider ${providerHint.provider} expects ${providerHint.canonicalWriteAction}.` : ''}`
3453
+ )}.${formatProviderAmbiguousActionHint(providerHint)}`
3239
3454
  );
3240
3455
  }
3241
3456
 
@@ -3252,14 +3467,14 @@ async function resolveActionSelection(ctx, flags) {
3252
3467
  throw new Error(
3253
3468
  `Ambiguous --action "${requestedAction}". Did you mean one of: ${formatActionList(
3254
3469
  fuzzy.slice(0, 10).map((v) => v.key)
3255
- )}?${providerHint ? ` Provider ${providerHint.provider} expects ${providerHint.canonicalWriteAction}.` : ''}`
3470
+ )}?${formatProviderAmbiguousActionHint(providerHint)}`
3256
3471
  );
3257
3472
  }
3258
3473
  const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
3259
3474
  throw new Error(
3260
- `Unknown --action "${requestedAction}". No compatible action found in current catalog/provider scope.${
3261
- providerHint ? ` Provider ${providerHint.provider} expects ${providerHint.canonicalWriteAction}; run actions --provider ${providerHint.provider} first.` : ''
3262
- }`
3475
+ `Unknown --action "${requestedAction}". No compatible action found in current catalog/provider scope.${formatProviderUnknownActionHint(
3476
+ providerHint
3477
+ )}`
3263
3478
  );
3264
3479
  }
3265
3480
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.24",
3
+ "version": "0.1.27",
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",