@thirdfy/agent-cli 0.2.77 → 0.2.79
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 +27 -0
- package/README.md +8 -8
- package/package.json +2 -2
- package/scripts/validate-cli-contract-schemas.mjs +1 -0
- package/src/cli/manifest.mjs +9 -2
- package/src/cli/options/data.mjs +14 -0
- package/src/cli/options/index.mjs +3 -0
- package/src/commands/avantis.mjs +7 -4
- package/src/commands/condor.mjs +6 -5
- package/src/commands/credits.mjs +3 -1
- package/src/commands/doctor.mjs +5 -4
- package/src/commands/execute.mjs +28 -22
- package/src/commands/hummingbot.mjs +7 -6
- package/src/commands/polymarket.mjs +2 -1
- package/src/commands/signals/command.mjs +39 -0
- package/src/commands/signals/signalRows.mjs +153 -0
- package/src/commands/signals/typedReading.mjs +55 -0
- package/src/commands/telemetry.mjs +5 -1
- package/src/commands/venue.mjs +5 -2
- package/src/commands/wallet.mjs +3 -3
- package/src/contracts/cli/schemas/signals.response.schema.json +78 -0
- package/src/core/actionTimeouts.mjs +4 -5
- package/src/core/envelope.mjs +46 -7
- package/src/core/venueBrand.mjs +76 -0
- package/src/runtime/actions/selection.mjs +66 -18
- package/src/runtime/certification/executionCertify.mjs +3 -1
- package/src/runtime/certification/venueActionSchemas.mjs +5 -3
- package/src/runtime/execution/lanes.mjs +4 -2
- package/src/runtime/execution/payloads.mjs +3 -4
- package/src/runtime/execution/runners.mjs +1 -1
- package/src/runtime/handlers.mjs +4 -0
- package/src/runtime/main.mjs +4 -4
- package/src/runtime/providerHints.mjs +92 -65
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@thirdfy/agent-cli` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## [0.2.79] - 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`thirdfy-agent signals [--provider <id>] [--role direction|filter|context] [--json]`** lists the data provider apps (CoinMarketCap, Nansen, Allora, ...) and the signals they publish, from the Thirdfy action catalog's app manifests (`apps[]` and each read's `signals[]`, Thirdfy API thirdfy-api-v2#682): id, title, what it measures, fields with paths, types and units, freshness (cadence, max age, horizon), roles, rules, the read that returns it, credits per call with the billing tier, attribution and whether values may be displayed. Human mode prints one block per signal. An unknown `--role` fails with `SIGNAL_ROLE_INVALID`. An API without app manifests lists nothing. Pin: `test/signals-command.test.cjs`.
|
|
12
|
+
- **`thirdfy-agent actions --data-category <category[,category]>`** keeps only data reads of those categories (`market_data`, `signal`, `research`, `wallet`), case-insensitive.
|
|
13
|
+
- **`run` shows a data read's typed reading first in human mode**: `typed`, `parseIssues`, `freshness` and `attribution` (Thirdfy API typed results, thirdfy-api-v2#681), then the full result. `--json` is unchanged. Pin: `test/typed-reading-output.test.cjs`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Large output is no longer cut when it goes to a pipe.** The envelope was written with `fs.writeSync(1)`, which ignored a partial write: once Node has opened `process.stdout` (or the reader side made the shared pipe non-blocking), a pipe is non-blocking and `writeSync` writes only what fits in the pipe buffer (64 KiB on macOS). That is how `actions --data-category signal,research --json` piped to another program was once cut at 65,514 characters. Output now goes through `process.stdout.write`, and every command that exits with a status (failed `run` / `preflight` / `wallet` / `doctor` checks, missing flags in `condor`, `hummingbot`, `polymarket`, Commander errors, unhandled errors) sets `process.exitCode` and exits only after stdout and stderr have drained (`exitAfterOutput` in `src/core/envelope.mjs`). A reader that stops reading cannot hold the CLI open: after 10 s (`THIRDFY_AGENT_EXIT_DRAIN_TIMEOUT_MS`) it exits with the intended code anyway. Exit codes are unchanged. Pin: `test/output-drain.test.cjs` (over 1 MB of JSON through a slow pipe reader, blocking and non-blocking stdout, the exit path, and a reader that never reads).
|
|
18
|
+
|
|
19
|
+
## [0.2.78] - 2026-09-24
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Avantis is now Veranta.** The Base perps venue rebranded (ticker AVNT unchanged). Help, errors, provider hints and docs say Veranta. Provider hints for `avantis` / `avantis-vault` list Veranta action names (`place_veranta_perps_order`, `deposit_veranta_vault`, ...) and carry `displayName: "Veranta"` and `providerAliases`.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **Veranta names work against every API deployment.** A Veranta action name (`*_veranta_*`) resolves to the catalog row the API in hand has: the Veranta row on an API with Veranta names, the Avantis row on one without. Avantis names keep working, including against a catalog that lists them only as aliases of Veranta rows. With no catalog to check, a Veranta name is sent in the Avantis spelling, which every deployment accepts. `pricing quote` does the same.
|
|
28
|
+
- **`veranta` / `veranta-vault` provider aliases.** `--provider veranta` lists the same actions as `--provider avantis` (and `veranta-vault` as `avantis-vault`). `venue readiness|setup|fund veranta` calls the API with the `avantis` venue id. `avantis` is still accepted everywhere.
|
|
29
|
+
- **Hardcoded venue lists recognise both spellings:** the offchain-venue lane guard (`OFFCHAIN_VENUE_ORDER_SELF_UNSUPPORTED`), the 180s long timeout for onboarding and market open/close, the `agent_wallet` `traderAddress` default, the `complete_*_onboarding` `estimatedAmountUsd` default, and `certify` venue fixtures (a live Veranta row replaces the Avantis fixture; the schema digest is spelling-independent).
|
|
30
|
+
- Error codes and internal identifiers are unchanged (`avantis_delegate`, `AVANTIS_*` setup blockers, provider ids `avantis` / `avantis-vault`). Pin: `test/veranta-rebrand.test.cjs`.
|
|
31
|
+
|
|
5
32
|
## [0.2.77] - 2026-09-24
|
|
6
33
|
|
|
7
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -40,13 +40,13 @@ Run without global install:
|
|
|
40
40
|
npx @thirdfy/agent-cli --help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## What's new in v0.2.
|
|
43
|
+
## What's new in v0.2.79
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
-
|
|
45
|
+
- **`thirdfy-agent signals`** lists the data provider apps (CoinMarketCap, Nansen, Allora, ...) and the signals they publish: what each measures, its fields and units, freshness, roles, the read that returns it, credits per call and attribution. Filter with `--provider <id>` and `--role direction|filter|context`; add `--json` for the envelope. No key is needed to browse.
|
|
46
|
+
- **`thirdfy-agent actions --data-category <category[,category]>`** keeps only data reads of those categories (`market_data`, `signal`, `research`, `wallet`).
|
|
47
|
+
- **`run` shows a data read's typed reading first** in human mode (`typed`, `parseIssues`, `freshness`, `attribution`), then the full result. `--json` is unchanged.
|
|
48
|
+
- Large `--json` output piped to another program is no longer cut at the pipe buffer size. Exit codes are unchanged.
|
|
49
|
+
- Signals and typed readings need Thirdfy API 3.17.0 or later; an older API lists no signals.
|
|
50
50
|
- See [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases for older versions.
|
|
51
51
|
|
|
52
52
|
## Quick start
|
|
@@ -323,10 +323,10 @@ Provider and venue guides are kept outside `README` so this page stays stable as
|
|
|
323
323
|
|
|
324
324
|
## Command groups
|
|
325
325
|
|
|
326
|
-
- Discovery: `catalogs list`, `actions` (optional `--provider`, `--chain-id`; see [`docs/command-reference.md`](./docs/command-reference.md) for trading vs earn vs prediction providers and case-insensitive matching)
|
|
326
|
+
- Discovery: `catalogs list`, `signals` (data provider apps and their signals: fields with units, freshness, roles, credits, attribution; `--provider`, `--role`), `actions` (optional `--provider`, `--chain-id`, `--data-category`; see [`docs/command-reference.md`](./docs/command-reference.md) for trading vs earn vs prediction providers and case-insensitive matching)
|
|
327
327
|
- Execution: `preflight`, `run`, `intent-status`, `jeff preflight`, `jeff trade`, `jeff status`
|
|
328
328
|
- Polymarket: `polymarket status`, `polymarket setup`, `polymarket dry-run`
|
|
329
|
-
- Venue tooling: `venue readiness`, `venue setup`, `venue fund` (`polymarket`, `hyperliquid`, `lighter`, `
|
|
329
|
+
- Venue tooling: `venue readiness`, `venue setup`, `venue fund` (`polymarket`, `hyperliquid`, `lighter`, `veranta`)
|
|
330
330
|
- Hummingbot: `hummingbot status`, `connectors`, `executor-schema`, `executor-create`, `executor-stop`, `gateway-networks`, `gateway-connectors`
|
|
331
331
|
- Condor Pattern A: `condor policy-status`, `condor preflight`
|
|
332
332
|
- Managed signer execution: `wallet execute`, `wallet sign`, `wallet submit`, `agent run`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thirdfy/agent-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.79",
|
|
4
4
|
"description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "node --test test/*.test.cjs",
|
|
26
|
-
"lint": "node --check ./bin/thirdfy-agent.mjs && node --check ./src/core/*.mjs && node --check ./src/commands/*.mjs && node --check ./src/cli/*.mjs && node --check ./src/cli/options/*.mjs && node --check ./src/runtime/*.mjs && node --check ./src/runtime/execution/*.mjs && node --check ./src/runtime/actions/*.mjs && node --check ./src/runtime/validation/*.mjs && node --check ./src/runtime/certification/*.mjs",
|
|
26
|
+
"lint": "node --check ./bin/thirdfy-agent.mjs && node --check ./src/core/*.mjs && node --check ./src/commands/*.mjs && node --check ./src/commands/signals/*.mjs && node --check ./src/cli/*.mjs && node --check ./src/cli/options/*.mjs && node --check ./src/runtime/*.mjs && node --check ./src/runtime/execution/*.mjs && node --check ./src/runtime/actions/*.mjs && node --check ./src/runtime/validation/*.mjs && node --check ./src/runtime/certification/*.mjs",
|
|
27
27
|
"validate:cli-manifest-handlers": "node ./scripts/validate-cli-manifest-handlers.mjs",
|
|
28
28
|
"validate:runtime-boundaries": "node ./scripts/validate-runtime-boundaries.mjs",
|
|
29
29
|
"validate:npm-pack": "node ./scripts/validate-npm-pack.mjs",
|
package/src/cli/manifest.mjs
CHANGED
|
@@ -103,7 +103,7 @@ export const CLI_MANIFEST = [
|
|
|
103
103
|
path: ['venue', 'readiness'],
|
|
104
104
|
handler: 'commandVenueReadiness',
|
|
105
105
|
options: ['auth', 'venue'],
|
|
106
|
-
description: 'Normalized venue readiness (polymarket, hyperliquid, lighter,
|
|
106
|
+
description: 'Normalized venue readiness (polymarket, hyperliquid, lighter, veranta)',
|
|
107
107
|
args: { query: '<venue>' },
|
|
108
108
|
tier: 'online',
|
|
109
109
|
},
|
|
@@ -622,9 +622,16 @@ export const CLI_MANIFEST = [
|
|
|
622
622
|
path: ['actions'],
|
|
623
623
|
handler: 'commandActions',
|
|
624
624
|
tier: 'online',
|
|
625
|
-
options: ['auth', 'execution'],
|
|
625
|
+
options: ['auth', 'execution', 'dataCategory'],
|
|
626
626
|
description: 'List allowed actions',
|
|
627
627
|
},
|
|
628
|
+
{
|
|
629
|
+
path: ['signals'],
|
|
630
|
+
handler: 'commandSignals',
|
|
631
|
+
tier: 'online',
|
|
632
|
+
options: ['signals'],
|
|
633
|
+
description: 'List data provider apps and the signals they publish (fields, units, freshness, roles, credits)',
|
|
634
|
+
},
|
|
628
635
|
{
|
|
629
636
|
path: ['preflight'],
|
|
630
637
|
handler: 'commandPreflight',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Options of the data commands: `signals`, and the data-category filter of `actions`. */
|
|
2
|
+
|
|
3
|
+
export function attachSignalsOptions(cmd) {
|
|
4
|
+
return cmd
|
|
5
|
+
.option('--provider <id>', 'only this provider app (cmc, nansen, allora, ...)')
|
|
6
|
+
.option('--role <role>', 'only signals a strategy may give this role: direction|filter|context');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function attachDataCategoryOptions(cmd) {
|
|
10
|
+
return cmd.option(
|
|
11
|
+
'--data-category <category>',
|
|
12
|
+
'only data reads of this category (market_data, signal, research, wallet); comma-separated for several',
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -17,6 +17,7 @@ import { attachChatOptions } from './chat.mjs';
|
|
|
17
17
|
import { attachVenueOptions } from './venue.mjs';
|
|
18
18
|
import { attachBrainOptions } from './brain.mjs';
|
|
19
19
|
import { attachCertificationOptions } from './certification.mjs';
|
|
20
|
+
import { attachDataCategoryOptions, attachSignalsOptions } from './data.mjs';
|
|
20
21
|
|
|
21
22
|
const BUNDLES = {
|
|
22
23
|
auth: attachAuthOptions,
|
|
@@ -37,6 +38,8 @@ const BUNDLES = {
|
|
|
37
38
|
venue: attachVenueOptions,
|
|
38
39
|
brain: attachBrainOptions,
|
|
39
40
|
certification: attachCertificationOptions,
|
|
41
|
+
signals: attachSignalsOptions,
|
|
42
|
+
dataCategory: attachDataCategoryOptions,
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
/** @param {import('commander').Command} cmd */
|
package/src/commands/avantis.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { toLegacyVenueActionKey } from '../core/venueBrand.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Veranta (formerly Avantis) helpers. Names keep the Avantis spelling (internal identifiers); every
|
|
5
|
+
* check accepts both the `*_avantis_*` and `*_veranta_*` action spellings.
|
|
6
|
+
*/
|
|
1
7
|
export function createAvantisHelpers(deps) {
|
|
2
8
|
const { getPreparedParams, resolveManagedExecutionPreflight } = deps;
|
|
3
9
|
|
|
4
10
|
function avantisDefaultAddressParamForAction(action) {
|
|
5
|
-
const canonical =
|
|
6
|
-
.trim()
|
|
7
|
-
.toLowerCase()
|
|
8
|
-
.replace(/_/g, '-');
|
|
11
|
+
const canonical = toLegacyVenueActionKey(action).replace(/_/g, '-');
|
|
9
12
|
if (
|
|
10
13
|
canonical === 'get-avantis-open-orders' ||
|
|
11
14
|
canonical === 'get-avantis-positions' ||
|
package/src/commands/condor.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiPost } from '../core/http.mjs';
|
|
2
|
+
import { exitAfterOutput } from '../core/envelope.mjs';
|
|
2
3
|
import { parseJsonFlag } from '../core/args.mjs';
|
|
3
4
|
import { negotiateCapabilities } from '../core/http.mjs';
|
|
4
5
|
import { resolveConfigAgentKey } from './polymarket.mjs';
|
|
@@ -27,7 +28,7 @@ function failMissingIdentity(printEnvelope, ctx, message) {
|
|
|
27
28
|
data: {},
|
|
28
29
|
meta: { apiBase: ctx.apiBase },
|
|
29
30
|
});
|
|
30
|
-
|
|
31
|
+
return exitAfterOutput(1);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export function createCondorCommands({ commandRun, printEnvelope }) {
|
|
@@ -35,7 +36,7 @@ export function createCondorCommands({ commandRun, printEnvelope }) {
|
|
|
35
36
|
const agentApiKey = agentApiKeyFromFlags(flags, ctx);
|
|
36
37
|
const userDid = userDidFromFlags(flags);
|
|
37
38
|
if (!agentApiKey || !userDid) {
|
|
38
|
-
failMissingIdentity(
|
|
39
|
+
await failMissingIdentity(
|
|
39
40
|
printEnvelope,
|
|
40
41
|
ctx,
|
|
41
42
|
'Provide --agent-api-key and --user-did for Condor policy status'
|
|
@@ -60,7 +61,7 @@ export function createCondorCommands({ commandRun, printEnvelope }) {
|
|
|
60
61
|
const idempotencyKey = String(flags.idempotencyKey || flags.idempotency_key || '').trim();
|
|
61
62
|
const estimatedAmountUsd = Number(flags.estimatedAmountUsd || flags.estimated_amount_usd || 0);
|
|
62
63
|
if (!agentApiKey || !userDid) {
|
|
63
|
-
failMissingIdentity(
|
|
64
|
+
await failMissingIdentity(
|
|
64
65
|
printEnvelope,
|
|
65
66
|
ctx,
|
|
66
67
|
'Provide --agent-api-key and --user-did for Condor preflight'
|
|
@@ -74,7 +75,7 @@ export function createCondorCommands({ commandRun, printEnvelope }) {
|
|
|
74
75
|
data: {},
|
|
75
76
|
meta: { apiBase: ctx.apiBase },
|
|
76
77
|
});
|
|
77
|
-
|
|
78
|
+
await exitAfterOutput(1);
|
|
78
79
|
}
|
|
79
80
|
if (!Number.isFinite(estimatedAmountUsd) || estimatedAmountUsd <= 0) {
|
|
80
81
|
printEnvelope({
|
|
@@ -84,7 +85,7 @@ export function createCondorCommands({ commandRun, printEnvelope }) {
|
|
|
84
85
|
data: {},
|
|
85
86
|
meta: { apiBase: ctx.apiBase },
|
|
86
87
|
});
|
|
87
|
-
|
|
88
|
+
await exitAfterOutput(1);
|
|
88
89
|
}
|
|
89
90
|
const params = parseJsonFlag(flags, 'params') || {};
|
|
90
91
|
const orgId = orgIdFromFlags(flags);
|
package/src/commands/credits.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { requireFlag } from '../core/args.mjs';
|
|
2
2
|
import { apiGet, apiPost } from '../core/http.mjs';
|
|
3
|
+
import { toAvantisSpelling } from '../core/venueBrand.mjs';
|
|
3
4
|
import {
|
|
4
5
|
formatCreditsEstimateSummary,
|
|
5
6
|
formatCreditsModelsSummary,
|
|
@@ -117,7 +118,8 @@ export function createCreditsCommands({ printEnvelope, getAuthToken }) {
|
|
|
117
118
|
ctx,
|
|
118
119
|
'/api/v1/credits/pricing/quote',
|
|
119
120
|
{
|
|
120
|
-
|
|
121
|
+
// Veranta names price as their Avantis spelling, which every API deployment knows.
|
|
122
|
+
actionKey: toAvantisSpelling(action),
|
|
121
123
|
chainId: Number(flags.chainId || 8453),
|
|
122
124
|
},
|
|
123
125
|
{ Authorization: `Bearer ${authToken}` },
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { requireFlag } from '../core/args.mjs';
|
|
|
3
3
|
import { apiGet, requestWithRouteFallback } from '../core/http.mjs';
|
|
4
4
|
import { loadProfileConfig, getProfileConfigPath, getCachedActionsCatalog } from '../core/context.mjs';
|
|
5
5
|
import { DEFAULT_API_BASE, PROFILE_DEFAULTS } from '../core/constants.mjs';
|
|
6
|
+
import { exitAfterOutput } from '../core/envelope.mjs';
|
|
6
7
|
import { normalizeRunMode, normalizeManagedRunMode } from '../core/runMode.mjs';
|
|
7
8
|
import { buildExecutionCertificationReport } from '../runtime/certification/executionCertify.mjs';
|
|
8
9
|
|
|
@@ -87,7 +88,7 @@ async function commandDoctorSelf(ctx, flags) {
|
|
|
87
88
|
rpcUrl,
|
|
88
89
|
},
|
|
89
90
|
});
|
|
90
|
-
if (!ok)
|
|
91
|
+
if (!ok) await exitAfterOutput(1);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
function buildOnboardingHelp(ctx) {
|
|
@@ -233,7 +234,7 @@ async function commandDoctorAuth(ctx, _flags) {
|
|
|
233
234
|
},
|
|
234
235
|
meta: { apiBase: ctx.apiBase },
|
|
235
236
|
});
|
|
236
|
-
if (!ok)
|
|
237
|
+
if (!ok) await exitAfterOutput(1);
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
async function commandManagedSetup(ctx, flags, capabilities) {
|
|
@@ -310,7 +311,7 @@ async function commandManagedSetup(ctx, flags, capabilities) {
|
|
|
310
311
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
311
312
|
},
|
|
312
313
|
});
|
|
313
|
-
if (!success)
|
|
314
|
+
if (!success) await exitAfterOutput(1);
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
async function commandDoctorCertifyExecution(ctx, flags) {
|
|
@@ -361,7 +362,7 @@ async function commandDoctorCertifyExecution(ctx, flags) {
|
|
|
361
362
|
cliVersion: CLI_VERSION,
|
|
362
363
|
},
|
|
363
364
|
});
|
|
364
|
-
if (!report.ok)
|
|
365
|
+
if (!report.ok) await exitAfterOutput(1);
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
return {
|
package/src/commands/execute.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { requireFlag } from '../core/args.mjs';
|
|
2
|
-
import { createCliError } from '../core/envelope.mjs';
|
|
2
|
+
import { createCliError, exitAfterOutput } from '../core/envelope.mjs';
|
|
3
3
|
import {
|
|
4
4
|
getEffectiveRunMode,
|
|
5
5
|
normalizeHybridWalletMode,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
parseBooleanFlag,
|
|
8
8
|
} from '../core/runMode.mjs';
|
|
9
9
|
import { apiGet } from '../core/http.mjs';
|
|
10
|
+
import { findTypedReading, formatTypedReadingLead } from './signals/typedReading.mjs';
|
|
10
11
|
import { createRequire } from 'module';
|
|
11
12
|
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
@@ -84,7 +85,7 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
84
85
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
85
86
|
},
|
|
86
87
|
});
|
|
87
|
-
if (!normalized.success)
|
|
88
|
+
if (!normalized.success) await exitAfterOutput(1);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
|
|
@@ -144,25 +145,30 @@ async function commandRun(ctx, flags, capabilities) {
|
|
|
144
145
|
},
|
|
145
146
|
}
|
|
146
147
|
: normalized;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
148
|
+
// A data read's typed reading goes first in human output; --json keeps the envelope unchanged.
|
|
149
|
+
const typedReading = normalized?.success ? findTypedReading(data) : null;
|
|
150
|
+
printEnvelope(
|
|
151
|
+
{
|
|
152
|
+
success: normalized.success,
|
|
153
|
+
code: backendResult.code,
|
|
154
|
+
message: backendResult.message,
|
|
155
|
+
data,
|
|
156
|
+
meta: {
|
|
157
|
+
apiBase: ctx.apiBase,
|
|
158
|
+
requestedAction: resolved.requestedAction,
|
|
159
|
+
resolvedAction: resolved.resolvedAction,
|
|
160
|
+
idempotencyKey: backendResult.idempotencyKey || null,
|
|
161
|
+
runMode,
|
|
162
|
+
hybridWalletMode,
|
|
163
|
+
profile: ctx.profile || null,
|
|
164
|
+
skippedPreflight: skipPreflight,
|
|
165
|
+
swapInput: flags.__swapInput || null,
|
|
166
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
167
|
+
},
|
|
163
168
|
},
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
typedReading ? { humanLead: formatTypedReadingLead(typedReading) } : {},
|
|
170
|
+
);
|
|
171
|
+
if (!normalized.success) await exitAfterOutput(1);
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
|
|
@@ -224,7 +230,7 @@ async function commandIntentStatus(ctx, flags, capabilities) {
|
|
|
224
230
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
225
231
|
},
|
|
226
232
|
});
|
|
227
|
-
if (!normalized.success)
|
|
233
|
+
if (!normalized.success) await exitAfterOutput(1);
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
|
|
@@ -251,7 +257,7 @@ async function commandSelfExec(ctx, flags, capabilities) {
|
|
|
251
257
|
resolvedAction: resolved.resolvedAction,
|
|
252
258
|
},
|
|
253
259
|
});
|
|
254
|
-
|
|
260
|
+
await exitAfterOutput(1);
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
const executed = await performSelfBroadcast(ctx, flags, resolved.resolvedAction, selfResult);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiGet, apiPost } from '../core/http.mjs';
|
|
2
|
+
import { exitAfterOutput } from '../core/envelope.mjs';
|
|
2
3
|
import { parseJsonFlag } from '../core/args.mjs';
|
|
3
4
|
import { negotiateCapabilities } from '../core/http.mjs';
|
|
4
5
|
import { resolveConfigAgentKey } from './polymarket.mjs';
|
|
@@ -27,7 +28,7 @@ function failMissingIdentity(printEnvelope, ctx, message) {
|
|
|
27
28
|
data: {},
|
|
28
29
|
meta: { apiBase: ctx.apiBase },
|
|
29
30
|
});
|
|
30
|
-
|
|
31
|
+
return exitAfterOutput(1);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export function createHummingbotCommands({ printEnvelope, commandRun }) {
|
|
@@ -63,7 +64,7 @@ export function createHummingbotCommands({ printEnvelope, commandRun }) {
|
|
|
63
64
|
const userDid = userDidFromFlags(flags);
|
|
64
65
|
const params = parseJsonFlag(flags, 'params') || {};
|
|
65
66
|
if (!agentApiKey || !userDid) {
|
|
66
|
-
failMissingIdentity(
|
|
67
|
+
await failMissingIdentity(
|
|
67
68
|
printEnvelope,
|
|
68
69
|
ctx,
|
|
69
70
|
'Provide --agent-api-key and --user-did for governed executor create'
|
|
@@ -97,7 +98,7 @@ export function createHummingbotCommands({ printEnvelope, commandRun }) {
|
|
|
97
98
|
const executorId = String(flags.executorId || flags.executor_id || '').trim();
|
|
98
99
|
const params = parseJsonFlag(flags, 'params') || {};
|
|
99
100
|
if (!agentApiKey || !userDid) {
|
|
100
|
-
failMissingIdentity(printEnvelope, ctx, 'Provide --agent-api-key and --user-did for governed executor stop');
|
|
101
|
+
await failMissingIdentity(printEnvelope, ctx, 'Provide --agent-api-key and --user-did for governed executor stop');
|
|
101
102
|
}
|
|
102
103
|
if (!executorId) {
|
|
103
104
|
printEnvelope({
|
|
@@ -107,7 +108,7 @@ export function createHummingbotCommands({ printEnvelope, commandRun }) {
|
|
|
107
108
|
data: {},
|
|
108
109
|
meta: { apiBase: ctx.apiBase, orgId },
|
|
109
110
|
});
|
|
110
|
-
|
|
111
|
+
await exitAfterOutput(1);
|
|
111
112
|
}
|
|
112
113
|
const body = {
|
|
113
114
|
...params,
|
|
@@ -135,12 +136,12 @@ export function createHummingbotCommands({ printEnvelope, commandRun }) {
|
|
|
135
136
|
data: {},
|
|
136
137
|
meta: { apiBase: ctx.apiBase },
|
|
137
138
|
});
|
|
138
|
-
|
|
139
|
+
await exitAfterOutput(1);
|
|
139
140
|
}
|
|
140
141
|
const agentApiKey = agentApiKeyFromFlags(flags, ctx);
|
|
141
142
|
const userDid = userDidFromFlags(flags);
|
|
142
143
|
if (!agentApiKey || !userDid) {
|
|
143
|
-
failMissingIdentity(
|
|
144
|
+
await failMissingIdentity(
|
|
144
145
|
printEnvelope,
|
|
145
146
|
ctx,
|
|
146
147
|
'Provide --agent-api-key and --user-did for Gateway discovery reads'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadProfileConfig } from '../core/context.mjs';
|
|
2
2
|
import { negotiateCapabilities } from '../core/http.mjs';
|
|
3
3
|
import { parseBooleanFlag } from '../core/runMode.mjs';
|
|
4
|
+
import { exitAfterOutput } from '../core/envelope.mjs';
|
|
4
5
|
|
|
5
6
|
function extractAgentKey(value) {
|
|
6
7
|
if (!value || typeof value !== 'object') return '';
|
|
@@ -74,7 +75,7 @@ export function createPolymarketCommands({ commandRun, commandPreflight, printEn
|
|
|
74
75
|
data: {},
|
|
75
76
|
meta: { apiBase: ctx.apiBase },
|
|
76
77
|
});
|
|
77
|
-
|
|
78
|
+
await exitAfterOutput(1);
|
|
78
79
|
}
|
|
79
80
|
const capabilities = await negotiateCapabilities(ctx);
|
|
80
81
|
if (!flags.runMode) flags.runMode = 'agent_wallet';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { apiGet } from '../../core/http.mjs';
|
|
2
|
+
import { createCliError } from '../../core/envelope.mjs';
|
|
3
|
+
import { SIGNAL_ROLES, buildSignalsListing, formatSignalsListing } from './signalRows.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `thirdfy-agent signals`: the provider apps and signals the Thirdfy action catalog publishes (what
|
|
7
|
+
* each signal measures, fields with units, freshness, roles, credits, attribution). Reads the public
|
|
8
|
+
* catalog, which carries `apps[]`; no key needed.
|
|
9
|
+
*/
|
|
10
|
+
export function createSignalsCommands({ printEnvelope }) {
|
|
11
|
+
async function commandSignals(ctx, flags, capabilities) {
|
|
12
|
+
const role = String(flags.role || '').trim().toLowerCase();
|
|
13
|
+
if (role && !SIGNAL_ROLES.includes(role)) {
|
|
14
|
+
throw createCliError('SIGNAL_ROLE_INVALID', `--role must be one of ${SIGNAL_ROLES.join(', ')}`, {
|
|
15
|
+
role: flags.role,
|
|
16
|
+
allowedRoles: SIGNAL_ROLES,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const provider = String(flags.provider || '').trim();
|
|
20
|
+
const response = await apiGet(ctx, '/api/v1/agent/actions/catalog');
|
|
21
|
+
const listing = buildSignalsListing(response, { provider, role });
|
|
22
|
+
printEnvelope(
|
|
23
|
+
{
|
|
24
|
+
success: true,
|
|
25
|
+
code: 'OK',
|
|
26
|
+
message: `Signals loaded: ${listing.signals.length} signal(s) from ${listing.apps.length} app(s)`,
|
|
27
|
+
data: listing,
|
|
28
|
+
meta: {
|
|
29
|
+
apiBase: ctx.apiBase,
|
|
30
|
+
filters: { provider: provider || null, role: role || null },
|
|
31
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{ humanLead: formatSignalsListing(listing), humanData: false },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { commandSignals };
|
|
39
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signals listing, built from the Thirdfy action catalog (provider app manifests, thirdfy-app.json
|
|
3
|
+
* v1): `apps[]` at the top level and `signals[]` on each data read. Pure: no HTTP, no printing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The roles a strategy may give a signal (manifest v1). */
|
|
7
|
+
export const SIGNAL_ROLES = ['direction', 'filter', 'context'];
|
|
8
|
+
|
|
9
|
+
const text = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
10
|
+
const lower = (value) => text(value).toLowerCase();
|
|
11
|
+
const isRecord = (value) => Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
12
|
+
const snake = (action) => lower(action).replace(/-/g, '_');
|
|
13
|
+
|
|
14
|
+
function extractCatalogActions(response) {
|
|
15
|
+
if (Array.isArray(response)) return response;
|
|
16
|
+
if (Array.isArray(response?.actions)) return response.actions;
|
|
17
|
+
if (Array.isArray(response?.data?.actions)) return response.data.actions;
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractApps(response) {
|
|
22
|
+
if (Array.isArray(response?.apps)) return response.apps;
|
|
23
|
+
if (Array.isArray(response?.data?.apps)) return response.data.apps;
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 900 -> "15m", 3600 -> "1h", 45 -> "45s", 129600 -> "1.5d"; "" when absent. */
|
|
28
|
+
export function formatDuration(seconds) {
|
|
29
|
+
const value = Number(seconds);
|
|
30
|
+
if (!Number.isFinite(value) || value <= 0) return '';
|
|
31
|
+
for (const [size, unit] of [[86400, 'd'], [3600, 'h'], [60, 'm']]) {
|
|
32
|
+
if (value >= size) return `${Number((value / size).toFixed(1))}${unit}`;
|
|
33
|
+
}
|
|
34
|
+
return `${Number(value.toFixed(1))}s`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fieldRows(fields) {
|
|
38
|
+
if (!isRecord(fields)) return [];
|
|
39
|
+
return Object.entries(fields).map(([name, field]) => {
|
|
40
|
+
const spec = isRecord(field) ? field : {};
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
path: text(spec.path) || null,
|
|
44
|
+
type: text(spec.type) || null,
|
|
45
|
+
unit: text(spec.unit) || null,
|
|
46
|
+
...(Array.isArray(spec.range) ? { range: spec.range } : {}),
|
|
47
|
+
...(Array.isArray(spec.values) ? { values: spec.values } : {}),
|
|
48
|
+
...(text(spec.description) ? { description: text(spec.description) } : {}),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function appSummary(app) {
|
|
54
|
+
const redistribution = isRecord(app.redistribution) ? app.redistribution : {};
|
|
55
|
+
return {
|
|
56
|
+
appId: text(app.appId),
|
|
57
|
+
title: text(app.title) || text(app.appId),
|
|
58
|
+
kind: text(app.kind) || null,
|
|
59
|
+
category: text(app.category) || null,
|
|
60
|
+
summary: text(app.summary) || null,
|
|
61
|
+
manifestVersion: Number.isFinite(Number(app.manifestVersion)) ? Number(app.manifestVersion) : null,
|
|
62
|
+
attribution: text(redistribution.attribution) || null,
|
|
63
|
+
displayValues: typeof redistribution.displayValues === 'boolean' ? redistribution.displayValues : null,
|
|
64
|
+
...(text(app.docsUrl) ? { docsUrl: text(app.docsUrl) } : {}),
|
|
65
|
+
signalIds: Array.isArray(app.signalIds) ? app.signalIds.map(text).filter(Boolean) : [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Apps and signals from one catalog response, filtered by provider (app id or the action's
|
|
71
|
+
* provider id) and role. A catalog without manifest fields (an older API) gives empty lists.
|
|
72
|
+
*/
|
|
73
|
+
export function buildSignalsListing(response, { provider, role } = {}) {
|
|
74
|
+
const apps = new Map(extractApps(response).filter((app) => isRecord(app) && text(app.appId)).map((app) => [lower(app.appId), app]));
|
|
75
|
+
const wantedProvider = lower(provider);
|
|
76
|
+
const wantedRole = lower(role);
|
|
77
|
+
const signals = [];
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
for (const action of extractCatalogActions(response)) {
|
|
80
|
+
if (!isRecord(action) || !Array.isArray(action.signals) || action.enabled === false) continue;
|
|
81
|
+
const appId = lower(action.app?.appId) || lower(action.providerId);
|
|
82
|
+
if (wantedProvider && appId !== wantedProvider && lower(action.providerId) !== wantedProvider) continue;
|
|
83
|
+
const app = apps.get(appId);
|
|
84
|
+
const redistribution = isRecord(app?.redistribution) ? app.redistribution : {};
|
|
85
|
+
for (const signal of action.signals) {
|
|
86
|
+
const id = text(signal?.id);
|
|
87
|
+
if (!id || seen.has(id)) continue;
|
|
88
|
+
const roles = Array.isArray(signal.roles) ? signal.roles.map(lower).filter(Boolean) : [];
|
|
89
|
+
if (wantedRole && !roles.includes(wantedRole)) continue;
|
|
90
|
+
seen.add(id);
|
|
91
|
+
signals.push({
|
|
92
|
+
id,
|
|
93
|
+
title: text(signal.title) || id,
|
|
94
|
+
summary: text(signal.summary) || null,
|
|
95
|
+
measures: text(signal.measures) || null,
|
|
96
|
+
scope: text(signal.scope) || null,
|
|
97
|
+
universe: signal.universe ?? null,
|
|
98
|
+
fields: fieldRows(signal.fields),
|
|
99
|
+
asOfPath: text(signal.asOf?.path) || null,
|
|
100
|
+
freshness: {
|
|
101
|
+
cadenceSec: Number.isFinite(Number(signal.cadenceSec)) ? Number(signal.cadenceSec) : null,
|
|
102
|
+
freshnessSec: Number.isFinite(Number(signal.freshnessSec)) ? Number(signal.freshnessSec) : null,
|
|
103
|
+
horizonHours: Number.isFinite(Number(signal.horizonHours)) && signal.horizonHours !== null ? Number(signal.horizonHours) : null,
|
|
104
|
+
},
|
|
105
|
+
roles,
|
|
106
|
+
rules: Array.isArray(signal.rules) ? signal.rules.map(text).filter(Boolean) : [],
|
|
107
|
+
action: snake(action.action),
|
|
108
|
+
dataCategory: text(action.dataCategory) || null,
|
|
109
|
+
billingTier: text(action.billingTier) || null,
|
|
110
|
+
credits: typeof action.credits === 'number' && Number.isFinite(action.credits) ? action.credits : null,
|
|
111
|
+
appId: appId || null,
|
|
112
|
+
manifestVersion: Number.isFinite(Number(action.app?.manifestVersion)) ? Number(action.app.manifestVersion) : null,
|
|
113
|
+
attribution: text(signal.attribution) || text(redistribution.attribution) || null,
|
|
114
|
+
displayValues: typeof redistribution.displayValues === 'boolean' ? redistribution.displayValues : null,
|
|
115
|
+
...(text(signal.docsUrl) ? { docsUrl: text(signal.docsUrl) } : {}),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const listedAppIds = new Set(signals.map((signal) => signal.appId).filter(Boolean));
|
|
120
|
+
// With a filter, only the apps that still have a signal listed.
|
|
121
|
+
const filtered = Boolean(wantedProvider || wantedRole);
|
|
122
|
+
const appRows = [...apps.values()].filter((app) => !filtered || listedAppIds.has(lower(app.appId))).map(appSummary);
|
|
123
|
+
return { apps: appRows, signals };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function fieldLabel(field) {
|
|
127
|
+
const details = [];
|
|
128
|
+
if (field.unit) details.push(field.unit);
|
|
129
|
+
if (Array.isArray(field.range) && field.range.length === 2) details.push(`${field.range[0]}-${field.range[1]}`);
|
|
130
|
+
if (!details.length && field.type) details.push(field.type);
|
|
131
|
+
return details.length ? `${field.name} (${details.join(', ')})` : field.name;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Human lines: one per signal, then the credit lines to show. */
|
|
135
|
+
export function formatSignalsListing({ apps, signals }) {
|
|
136
|
+
if (!signals.length) return 'No signals in the catalog for this filter.';
|
|
137
|
+
const lines = signals.map((signal) => {
|
|
138
|
+
const timing = [
|
|
139
|
+
signal.freshness.cadenceSec ? `every ${formatDuration(signal.freshness.cadenceSec)}` : '',
|
|
140
|
+
signal.freshness.freshnessSec ? `fresh ${formatDuration(signal.freshness.freshnessSec)}` : '',
|
|
141
|
+
signal.freshness.horizonHours ? `horizon ${formatDuration(signal.freshness.horizonHours * 3600)}` : '',
|
|
142
|
+
].filter(Boolean);
|
|
143
|
+
const cost = signal.credits !== null ? `${signal.credits} credits` : '';
|
|
144
|
+
return [
|
|
145
|
+
`${signal.id} ${signal.title}`,
|
|
146
|
+
` measures ${signal.measures || '?'} (${signal.scope || 'market'}); roles ${signal.roles.join(', ') || '-'}; ${[...timing, cost].filter(Boolean).join('; ')}`,
|
|
147
|
+
` fields ${signal.fields.map(fieldLabel).join(', ') || '-'}; action ${signal.action}`,
|
|
148
|
+
...(signal.attribution ? [` ${signal.attribution}`] : []),
|
|
149
|
+
].join('\n');
|
|
150
|
+
});
|
|
151
|
+
const appLine = apps.length ? `Apps: ${apps.map((app) => `${app.appId} (${app.title})`).join(', ')}` : '';
|
|
152
|
+
return [appLine, ...lines].filter(Boolean).join('\n');
|
|
153
|
+
}
|