@thirdfy/agent-cli 0.1.9 → 0.1.10

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,11 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.9] - 2026-04-08
8
+
9
+ **npm:** [`@thirdfy/agent-cli@0.1.9`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.9)
10
+ **Git tag:** [`v0.1.9`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.9)
11
+
7
12
  ### Fixed
8
13
 
9
14
  - **Self preflight routing for swap-like actions:** `preflight --run-mode self` now selects the validation route by action capability. When an action advertises `supportsExecuteIntent=false` and `supportsBuildTx=true`, preflight uses `build-tx` directly instead of forcing execute-intent validation.
package/README.md CHANGED
@@ -16,8 +16,13 @@ If you want the full developer docs, start here:
16
16
  - [API Reference home](https://docs.thirdfy.com/api)
17
17
  - [LLM docs map (llms.txt)](https://docs.thirdfy.com/llms.txt)
18
18
  - [LLM full API snapshot (llms-full.txt)](https://docs.thirdfy.com/llms-full.txt)
19
+ - [Provider docs index (repo)](./docs/providers/index.md)
20
+ - [Action inventory and compatibility](./docs/action-inventory-and-compatibility.md)
21
+ - [Command reference](./docs/command-reference.md)
19
22
  - [DogeOS quickstart (repo)](./docs/dogeos-quickstart.md)
23
+ - [Bitfinex CEX workflow (repo)](./docs/bitfinex-cex-workflow.md)
20
24
  - [Action onboarding standard](./docs/action-onboarding-standard.md)
25
+ - [Provider docs governance model](./docs/provider-docs-governance.md)
21
26
  - [Skill integration RFC](./docs/skill-integration-rfc.md)
22
27
 
23
28
  ## Install
@@ -215,6 +220,14 @@ Jeff-friendly aliases map directly to core commands:
215
220
  - `thirdfy-agent jeff status --intent-id ...` -> alias of `intent-status`
216
221
  - `thirdfy-agent prompt "<text>"` -> captures prompt intent and suggests deterministic execution commands
217
222
 
223
+ ## Provider-specific workflows
224
+
225
+ Provider and venue guides are kept outside `README` so this page stays stable as integrations grow.
226
+
227
+ - Provider index: [`docs/providers/index.md`](./docs/providers/index.md)
228
+ - Bitfinex runbook: [`docs/providers/bitfinex.md`](./docs/providers/bitfinex.md)
229
+ - DogeOS runbook: [`docs/providers/dogeos.md`](./docs/providers/dogeos.md)
230
+
218
231
  ## Command groups
219
232
 
220
233
  - Discovery: `catalogs list`, `actions`
@@ -226,6 +239,12 @@ Jeff-friendly aliases map directly to core commands:
226
239
  - Delegation execution helpers: `delegation balance`, `delegation redeem`
227
240
  - Account: `credits balance`
228
241
 
242
+ ## Where to find what is supported
243
+
244
+ - Full command groups and usage map: [`docs/command-reference.md`](./docs/command-reference.md)
245
+ - Live action inventory in your environment: [`docs/action-inventory-and-compatibility.md`](./docs/action-inventory-and-compatibility.md)
246
+ - Provider runbooks and provider status: [`docs/providers/index.md`](./docs/providers/index.md)
247
+
229
248
  ## Delegation lifecycle (canonical)
230
249
 
231
250
  Use `create` + `activate` as the default delegated execution lifecycle:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { randomUUID } from 'crypto';
3
+ import { createHmac, randomUUID } from 'crypto';
4
4
  import { spawnSync } from 'child_process';
5
5
  import fs from 'fs';
6
6
  import os from 'os';
@@ -18,6 +18,11 @@ const {
18
18
  const DEFAULT_API_BASE = 'https://api.thirdfy.com';
19
19
  const DEFAULT_TIMEOUT_MS = 30000;
20
20
  const DEFAULT_CATALOG_CACHE_TTL_MS = 60_000;
21
+ const DEFAULT_BITFINEX_BASE_URL = 'https://api.bitfinex.com';
22
+ const BITFINEX_REQUIRED_SCOPE_PERMISSIONS = {
23
+ funding: { read: 1, write: 1 },
24
+ wallets: { read: 1, write: 1 },
25
+ };
21
26
  const RUN_MODES = ['thirdfy', 'self', 'hybrid'];
22
27
  const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
23
28
  const PROFILE_DEFAULTS = {
@@ -52,6 +57,7 @@ main().catch((error) => {
52
57
  const payload = error?.payload && typeof error.payload === 'object' ? error.payload : {};
53
58
  const blockedReason = String(payload.blockedReason || payload.error || '').trim() || undefined;
54
59
  const blockedStage = String(payload.blockedStage || '').trim() || undefined;
60
+ const onboardingHint = buildTopLevelOnboardingHint(payload);
55
61
  printEnvelope({
56
62
  success: false,
57
63
  code: statusCode ? 'API_REQUEST_FAILED' : 'CLI_UNHANDLED_ERROR',
@@ -60,6 +66,7 @@ main().catch((error) => {
60
66
  statusCode,
61
67
  blockedReason,
62
68
  blockedStage,
69
+ onboardingHint,
63
70
  payload,
64
71
  },
65
72
  meta: { version: CLI_VERSION, apiBase: context.apiBase },
@@ -67,6 +74,22 @@ main().catch((error) => {
67
74
  process.exit(1);
68
75
  });
69
76
 
77
+ function buildTopLevelOnboardingHint(payload) {
78
+ const reason = String(payload?.blockedReason || payload?.error || '').trim();
79
+ if (!reason) return null;
80
+ if (
81
+ ['CEX_CREDENTIAL_NOT_FOUND', 'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT', 'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED'].includes(
82
+ reason
83
+ )
84
+ ) {
85
+ return {
86
+ type: 'bitfinex_credentials',
87
+ command: buildBitfinexOnboardingCommand(),
88
+ };
89
+ }
90
+ return null;
91
+ }
92
+
70
93
  async function main() {
71
94
  const profileState = resolveProfileState(globalFlags);
72
95
  context.profile = profileState.profile;
@@ -518,10 +541,15 @@ async function commandPreflight(ctx, flags, capabilities) {
518
541
  const runMode = getEffectiveRunMode(ctx, flags);
519
542
  enforceChainCompatibility(capabilities, flags, runMode);
520
543
  const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved);
521
- const normalized = backendResult.normalized;
544
+ const normalized = applyExecutionFallbackHints(backendResult.normalized, {
545
+ flags,
546
+ runMode,
547
+ resolvedAction: resolved.resolvedAction,
548
+ });
549
+ const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
522
550
  printEnvelope({
523
551
  success: normalized.success,
524
- code: normalized.success ? 'PREFLIGHT_OK' : 'PREFLIGHT_FAILED',
552
+ code: normalized.success ? 'PREFLIGHT_OK' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'PREFLIGHT_FAILED',
525
553
  message: backendResult.message,
526
554
  data: normalized,
527
555
  meta: {
@@ -548,7 +576,11 @@ async function commandRun(ctx, flags, capabilities) {
548
576
  const backendResult = await runExecutionByMode(runMode, ctx, flags, resolved, {
549
577
  skipPreflight,
550
578
  });
551
- const normalized = backendResult.normalized;
579
+ const normalized = applyExecutionFallbackHints(backendResult.normalized, {
580
+ flags,
581
+ runMode,
582
+ resolvedAction: resolved.resolvedAction,
583
+ });
552
584
  if (runMode === 'self' && parseBooleanFlag(flags.broadcast, false) && normalized?.success) {
553
585
  const executed = await performSelfBroadcast(ctx, flags, resolved.resolvedAction, normalized);
554
586
  printEnvelope({
@@ -925,8 +957,9 @@ async function commandDelegationRedeem(ctx, flags, capabilities) {
925
957
 
926
958
  async function commandCredentialsUpsert(ctx, flags, capabilities) {
927
959
  const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for credentials upsert');
960
+ const venue = requireFlag(flags, 'venue', 'Missing --venue');
928
961
  const payload = {
929
- venue: requireFlag(flags, 'venue', 'Missing --venue'),
962
+ venue,
930
963
  apiKey: requireFlag(flags, 'apiKey', 'Missing --api-key'),
931
964
  apiSecret: requireFlag(flags, 'apiSecret', 'Missing --api-secret'),
932
965
  credentialType: String(flags.credentialType || 'api_key'),
@@ -937,6 +970,19 @@ async function commandCredentialsUpsert(ctx, flags, capabilities) {
937
970
  if (flags.policy) payload.policy = parseJsonFlag(flags, 'policy');
938
971
  if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
939
972
  if (flags.chainId) payload.chainId = Number(flags.chainId);
973
+ const probeEnabled =
974
+ String(venue).trim().toLowerCase() === 'bitfinex' &&
975
+ !parseBooleanFlag(flags.skipPermissionsProbe, false) &&
976
+ parseBooleanFlag(flags.verifyPermissions, true);
977
+ let permissionProbe = null;
978
+ if (probeEnabled) {
979
+ permissionProbe = await probeBitfinexPermissions({
980
+ apiKey: payload.apiKey,
981
+ apiSecret: payload.apiSecret,
982
+ timeoutMs: ctx.timeoutMs,
983
+ baseUrl: String(flags.bitfinexBaseUrl || process.env.BITFINEX_BASE_URL || DEFAULT_BITFINEX_BASE_URL),
984
+ });
985
+ }
940
986
 
941
987
  const response = await requestWithRouteFallback(ctx, {
942
988
  method: 'POST',
@@ -953,6 +999,7 @@ async function commandCredentialsUpsert(ctx, flags, capabilities) {
953
999
  data: response,
954
1000
  meta: {
955
1001
  apiBase: ctx.apiBase,
1002
+ permissionProbe,
956
1003
  capabilitiesVersion: capabilities?.contractVersion || null,
957
1004
  },
958
1005
  });
@@ -1711,6 +1758,66 @@ function enforceChainCompatibility(capabilities, flags, runMode) {
1711
1758
  }
1712
1759
  }
1713
1760
 
1761
+ function shouldUseBuildTxExecution(runMode, resolved) {
1762
+ if (runMode !== 'self' && runMode !== 'hybrid') return false;
1763
+ const meta = resolved?.resolvedActionMeta || null;
1764
+ if (!meta || typeof meta !== 'object') return true;
1765
+ if (meta.supportsBuildTx === false) return false;
1766
+ return true;
1767
+ }
1768
+
1769
+ function buildBitfinexOnboardingCommand() {
1770
+ return [
1771
+ 'thirdfy-agent credentials upsert',
1772
+ '--auth-token "$THIRDFY_AUTH_TOKEN"',
1773
+ '--venue bitfinex',
1774
+ '--api-key "<BITFINEX_API_KEY>"',
1775
+ '--api-secret "<BITFINEX_API_SECRET>"',
1776
+ '--verify-permissions',
1777
+ '--json',
1778
+ ].join(' ');
1779
+ }
1780
+
1781
+ function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedAction }) {
1782
+ if (!normalized || typeof normalized !== 'object') return normalized;
1783
+ const next = { ...normalized };
1784
+ const reasons = new Set();
1785
+ const topBlockedReason = String(next.blockedReason || '').trim();
1786
+ if (topBlockedReason) reasons.add(topBlockedReason);
1787
+ const blockedByReason = next.blockedByReason && typeof next.blockedByReason === 'object' ? next.blockedByReason : {};
1788
+ for (const key of Object.keys(blockedByReason)) {
1789
+ if (String(key || '').trim()) reasons.add(String(key).trim());
1790
+ }
1791
+ const resolvedActionLower = String(resolvedAction || '').trim().toLowerCase();
1792
+ const looksBitfinexAction = resolvedActionLower.includes('bitfinex');
1793
+ const cexCredentialMissingSignals = new Set([
1794
+ 'CEX_CREDENTIAL_NOT_FOUND',
1795
+ 'BITFINEX_CREDENTIALS_NOT_IN_CONTEXT',
1796
+ 'BITFINEX_ENV_CREDENTIALS_NOT_CONFIGURED',
1797
+ ]);
1798
+ const hasMissingCredentialSignal = Array.from(reasons).some((code) => cexCredentialMissingSignals.has(code));
1799
+ if (looksBitfinexAction && hasMissingCredentialSignal) {
1800
+ next.onboardingHint = {
1801
+ type: 'bitfinex_credentials',
1802
+ message:
1803
+ 'Bitfinex credentials are missing for this delegated account. Onboard credentials first, then retry the same command.',
1804
+ command: buildBitfinexOnboardingCommand(),
1805
+ };
1806
+ }
1807
+ if (
1808
+ looksBitfinexAction &&
1809
+ runMode !== 'thirdfy' &&
1810
+ (topBlockedReason === 'SELF_MODE_REQUIRES_BUILD_TX' || topBlockedReason === 'HYBRID_MODE_REQUIRES_BUILD_TX')
1811
+ ) {
1812
+ next.onboardingHint = {
1813
+ ...(next.onboardingHint || {}),
1814
+ runMode: 'thirdfy',
1815
+ runModeMessage: 'Bitfinex actions require delegated execute-intent rail. Re-run with --run-mode thirdfy.',
1816
+ };
1817
+ }
1818
+ return next;
1819
+ }
1820
+
1714
1821
  function shouldUseBuildTxPreflight(runMode, resolved) {
1715
1822
  if (runMode !== 'self') return false;
1716
1823
  const meta = resolved?.resolvedActionMeta || null;
@@ -1725,7 +1832,7 @@ async function runPreflightByMode(runMode, ctx, flags, resolved) {
1725
1832
  const normalized = await executeSelfRun(
1726
1833
  ctx,
1727
1834
  flags,
1728
- { resolvedAction },
1835
+ resolved,
1729
1836
  { skipPreflight: route === 'build_tx' }
1730
1837
  );
1731
1838
  const blockedByGovernance = !normalized.success && Boolean(normalized.preflightBlocked);
@@ -1843,6 +1950,7 @@ async function executeThirdfyRun(ctx, flags, resolved, options) {
1843
1950
 
1844
1951
  async function executeSelfRun(ctx, flags, resolved, options) {
1845
1952
  const skipPreflight = Boolean(options?.skipPreflight);
1953
+ const blockedReasonCode = String(options?.blockedReasonCode || 'SELF_MODE_REQUIRES_BUILD_TX');
1846
1954
  if (!skipPreflight) {
1847
1955
  const preflightPayload = buildIntentPayload(flags, {
1848
1956
  validationOnly: true,
@@ -1861,6 +1969,17 @@ async function executeSelfRun(ctx, flags, resolved, options) {
1861
1969
  return preflightNormalized;
1862
1970
  }
1863
1971
  }
1972
+ if (!shouldUseBuildTxExecution(options?.runMode || 'self', resolved)) {
1973
+ const normalized = normalizeIntentResponse({
1974
+ success: false,
1975
+ status: 'failed',
1976
+ blockedReason: blockedReasonCode,
1977
+ blockedStage: 'routing',
1978
+ error: 'Selected action does not support build-tx self lane. Use --run-mode thirdfy for delegated CEX execution.',
1979
+ });
1980
+ normalized.preflightBlocked = true;
1981
+ return normalized;
1982
+ }
1864
1983
  const buildTxPayload = buildBuildTxPayload(flags, resolved.resolvedAction);
1865
1984
  const buildTx = await apiPost(ctx, '/api/v1/agent/build-tx', buildTxPayload);
1866
1985
  return normalizeIntentResponse({
@@ -1873,7 +1992,11 @@ async function executeSelfRun(ctx, flags, resolved, options) {
1873
1992
  }
1874
1993
 
1875
1994
  async function executeHybridRun(ctx, flags, resolved, options) {
1876
- const selfResult = await executeSelfRun(ctx, flags, resolved, options);
1995
+ const selfResult = await executeSelfRun(ctx, flags, resolved, {
1996
+ ...(options || {}),
1997
+ runMode: 'hybrid',
1998
+ blockedReasonCode: 'HYBRID_MODE_REQUIRES_BUILD_TX',
1999
+ });
1877
2000
  if (!selfResult.success) return selfResult;
1878
2001
 
1879
2002
  const mirrorPayload = buildIntentPayload(flags, {
@@ -2454,6 +2577,114 @@ function safeJsonParse(text) {
2454
2577
  }
2455
2578
  }
2456
2579
 
2580
+ function parseBitfinexPermissionRows(rawData) {
2581
+ const rows = Array.isArray(rawData) ? rawData : [];
2582
+ const parsed = {};
2583
+ for (const row of rows) {
2584
+ if (!Array.isArray(row) || row.length < 3) continue;
2585
+ const scope = String(row[0] || '').trim().toLowerCase();
2586
+ if (!scope) continue;
2587
+ const read = Number(row[1]) === 1 ? 1 : 0;
2588
+ const write = Number(row[2]) === 1 ? 1 : 0;
2589
+ parsed[scope] = { read, write };
2590
+ }
2591
+ return parsed;
2592
+ }
2593
+
2594
+ function getMissingBitfinexScopes(scopePermissions) {
2595
+ const missing = [];
2596
+ for (const [scope, required] of Object.entries(BITFINEX_REQUIRED_SCOPE_PERMISSIONS)) {
2597
+ const actual = scopePermissions[scope] || { read: 0, write: 0 };
2598
+ if (actual.read < required.read || actual.write < required.write) {
2599
+ missing.push({
2600
+ scope,
2601
+ required,
2602
+ actual,
2603
+ });
2604
+ }
2605
+ }
2606
+ return missing;
2607
+ }
2608
+
2609
+ async function probeBitfinexPermissions({ apiKey, apiSecret, timeoutMs, baseUrl }) {
2610
+ const normalizedBaseUrl = String(baseUrl || DEFAULT_BITFINEX_BASE_URL).trim().replace(/\/+$/, '');
2611
+ const apiPath = '/v2/auth/r/permissions';
2612
+ const nonce = Date.now().toString();
2613
+ const payload = '{}';
2614
+ const signature = createHmac('sha384', String(apiSecret || ''))
2615
+ .update(`/api${apiPath}${nonce}${payload}`)
2616
+ .digest('hex');
2617
+ const controller = new AbortController();
2618
+ const timer = setTimeout(() => controller.abort(), Number(timeoutMs || DEFAULT_TIMEOUT_MS));
2619
+ try {
2620
+ const response = await fetch(`${normalizedBaseUrl}${apiPath}`, {
2621
+ method: 'POST',
2622
+ headers: {
2623
+ 'content-type': 'application/json',
2624
+ 'bfx-apikey': String(apiKey || ''),
2625
+ 'bfx-nonce': nonce,
2626
+ 'bfx-signature': signature,
2627
+ },
2628
+ body: payload,
2629
+ signal: controller.signal,
2630
+ });
2631
+ const text = await response.text();
2632
+ const parsed = text ? safeJsonParse(text) : {};
2633
+ const bitfinexApiError =
2634
+ Array.isArray(parsed) && String(parsed[0] || '').trim().toLowerCase() === 'error'
2635
+ ? {
2636
+ code: Number(parsed[1]) || null,
2637
+ message: String(parsed[2] || 'Unknown Bitfinex API error'),
2638
+ }
2639
+ : parsed && typeof parsed === 'object' && String(parsed.event || '').trim().toLowerCase() === 'error'
2640
+ ? {
2641
+ code: Number(parsed.code) || null,
2642
+ message: String(parsed.msg || parsed.message || 'Unknown Bitfinex API error'),
2643
+ }
2644
+ : null;
2645
+ if (!response.ok) {
2646
+ throw createCliError(
2647
+ 'BITFINEX_PERMISSIONS_PROBE_FAILED',
2648
+ `Bitfinex permissions probe failed with HTTP ${response.status}. Verify API key/secret and key validity before onboarding.`,
2649
+ { statusCode: response.status }
2650
+ );
2651
+ }
2652
+ if (bitfinexApiError) {
2653
+ throw createCliError(
2654
+ 'BITFINEX_PERMISSIONS_PROBE_FAILED',
2655
+ `Bitfinex permissions probe failed: ${bitfinexApiError.message}. Verify API key/secret and key validity before onboarding.`,
2656
+ { statusCode: response.status, bitfinexApiError }
2657
+ );
2658
+ }
2659
+ const scopePermissions = parseBitfinexPermissionRows(parsed);
2660
+ const missingScopes = getMissingBitfinexScopes(scopePermissions);
2661
+ if (missingScopes.length > 0) {
2662
+ throw createCliError(
2663
+ 'BITFINEX_PERMISSION_SCOPE_MISSING',
2664
+ `Bitfinex key is missing required scopes (${missingScopes
2665
+ .map((entry) => entry.scope)
2666
+ .join(', ')}). Enable funding+wallets read/write and retry onboarding.`,
2667
+ { missingScopes, scopePermissions }
2668
+ );
2669
+ }
2670
+ return {
2671
+ venue: 'bitfinex',
2672
+ checked: true,
2673
+ scopes: scopePermissions,
2674
+ missingScopes: [],
2675
+ };
2676
+ } catch (error) {
2677
+ if (error?.payload) throw error;
2678
+ throw createCliError(
2679
+ 'BITFINEX_PERMISSIONS_PROBE_FAILED',
2680
+ `Could not verify Bitfinex key permissions. ${String(error?.message || 'Unknown probe error')}`,
2681
+ {}
2682
+ );
2683
+ } finally {
2684
+ clearTimeout(timer);
2685
+ }
2686
+ }
2687
+
2457
2688
  function printEnvelope(payload) {
2458
2689
  if (jsonMode) {
2459
2690
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
@@ -2491,7 +2722,7 @@ Core commands:
2491
2722
  thirdfy-agent managed wallet init [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
2492
2723
  thirdfy-agent managed wallet grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
2493
2724
  thirdfy-agent managed run --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--idempotency-key <key>] [--json]
2494
- thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
2725
+ thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--verify-permissions] [--skip-permissions-probe] [--bitfinex-base-url <url>] [--json]
2495
2726
  thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
2496
2727
  thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
2497
2728
  thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
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,7 @@
18
18
  "scripts": {
19
19
  "test": "node --test \"test/**/*.test.cjs\"",
20
20
  "lint": "node --check ./bin/thirdfy-agent.mjs",
21
+ "validate:public-docs": "node ./scripts/validate-public-docs.mjs",
21
22
  "validate:cli-contract-schemas": "node ./scripts/validate-cli-contract-schemas.mjs",
22
23
  "validate:actions": "node ./scripts/e2e/validate-actions.mjs",
23
24
  "e2e:base:simulate": "node ./scripts/e2e/base-simulate.mjs",
@@ -30,7 +31,7 @@
30
31
  "e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
31
32
  "e2e:report:last": "node ./scripts/e2e/report-last.mjs",
32
33
  "smoke:cli": "node ./bin/thirdfy-agent.mjs --help && node ./bin/thirdfy-agent.mjs --version --json",
33
- "prepublishOnly": "npm run validate:cli-contract-schemas && npm run test && npm run smoke:cli"
34
+ "prepublishOnly": "npm run validate:public-docs && npm run validate:cli-contract-schemas && npm run test && npm run smoke:cli"
34
35
  },
35
36
  "keywords": [
36
37
  "thirdfy",
@@ -33,6 +33,7 @@ function normalizeIntentResponse(response) {
33
33
  const topLevelGovernanceSignal = String(response?.governanceSignal || '').trim() || null;
34
34
  const topLevelDelegationSignal = String(response?.delegationSignal || '').trim() || null;
35
35
  const policyEvaluated = response?.policyEvaluated === true;
36
+ const preflightBlocked = response?.preflightBlocked === true;
36
37
  return {
37
38
  success,
38
39
  status: response?.status || (success ? 'queued' : 'failed'),
@@ -48,6 +49,7 @@ function normalizeIntentResponse(response) {
48
49
  governanceSignal: topLevelGovernanceSignal,
49
50
  delegationSignal: topLevelDelegationSignal,
50
51
  policyEvaluated,
52
+ preflightBlocked,
51
53
  error: response?.error || null,
52
54
  mode: response?.mode || null,
53
55
  unsignedTx: response?.unsignedTx || null,