@thirdfy/agent-cli 0.2.18 → 0.2.21
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 +18 -0
- package/README.md +5 -2
- package/package.json +1 -1
- package/src/cli/manifest.mjs +102 -0
- package/src/cli/options/brain.mjs +10 -0
- package/src/cli/options/chat.mjs +9 -0
- package/src/cli/options/index.mjs +6 -0
- package/src/cli/options/venue.mjs +12 -0
- package/src/cli/register.mjs +10 -0
- package/src/commands/bootstrap.mjs +46 -1
- package/src/commands/brain.mjs +135 -0
- package/src/commands/chat.mjs +404 -0
- package/src/commands/consoleMandate.mjs +125 -0
- package/src/commands/venue.mjs +110 -0
- package/src/runtime/handlers.mjs +32 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.21] - 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `console-mandate` command group: `status`, `draft`, `activate`, `revoke` for Console delegated execution (daily cap + expiry). Uses `/api/v1/console/delegation/*` and is separate from certified-agent `delegation *` commands. Pairs with Thirdfy API **v3.10.2+**.
|
|
12
|
+
|
|
13
|
+
## [0.2.20] - 2026-07-04
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `brain` command group: `sections`, `suggest`, `preflight`, `graduate` for Console Brain read/preflight-first strategy lanes (pairs with Thirdfy API **v3.10.0** Brain routes).
|
|
18
|
+
|
|
19
|
+
## [0.2.19] - 2026-07-04
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Release packaging and docs alignment (no Brain commands in this tag; use **v0.2.20** for `brain`).
|
|
24
|
+
|
|
7
25
|
## [0.2.18] - 2026-07-02
|
|
8
26
|
|
|
9
27
|
### Changed
|
package/README.md
CHANGED
|
@@ -40,9 +40,11 @@ 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.21
|
|
44
44
|
|
|
45
|
-
-
|
|
45
|
+
- **Console mandate:** `thirdfy-agent console-mandate status|draft|activate|revoke` for Console delegated execution with daily cap and expiry.
|
|
46
|
+
- Uses the same `/api/v1/console/delegation/*` routes as Console and Account. Separate from certified-agent `delegation *` commands.
|
|
47
|
+
- Pairs with Thirdfy API **v3.10.2+**. See [`docs/command-reference.md`](./docs/command-reference.md).
|
|
46
48
|
|
|
47
49
|
Older versions: see [CHANGELOG.md](./CHANGELOG.md) and [GitHub Releases](https://github.com/thirdfy/agent-cli/releases).
|
|
48
50
|
|
|
@@ -312,6 +314,7 @@ Provider and venue guides are kept outside `README` so this page stays stable as
|
|
|
312
314
|
- Delegation operations: `delegation show`, `delegation inspect`, `delegation revoke`
|
|
313
315
|
- Delegation execution helpers: `delegation balance`, `delegation redeem`
|
|
314
316
|
- Account: `credits balance`
|
|
317
|
+
- Console Brain: `brain sections`, `brain suggest`, `brain preflight`, `brain graduate` (read/preflight-first; see [`docs/command-reference.md`](./docs/command-reference.md))
|
|
315
318
|
|
|
316
319
|
## CLI routing (Commander + runtime)
|
|
317
320
|
|
package/package.json
CHANGED
package/src/cli/manifest.mjs
CHANGED
|
@@ -92,6 +92,30 @@ export const CLI_MANIFEST = [
|
|
|
92
92
|
options: ['auth', 'execution', 'polymarket'],
|
|
93
93
|
description: 'Polymarket dry-run preflight',
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
path: ['venue', 'readiness'],
|
|
97
|
+
handler: 'commandVenueReadiness',
|
|
98
|
+
options: ['auth', 'venue'],
|
|
99
|
+
description: 'Normalized venue readiness (polymarket, hyperliquid, lighter)',
|
|
100
|
+
args: { query: true },
|
|
101
|
+
tier: 'online',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: ['venue', 'setup'],
|
|
105
|
+
handler: 'commandVenueSetup',
|
|
106
|
+
options: ['auth', 'venue'],
|
|
107
|
+
description: 'Prepare or complete venue onboarding via shared API contract',
|
|
108
|
+
args: { query: true },
|
|
109
|
+
tier: 'online',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
path: ['venue', 'fund'],
|
|
113
|
+
handler: 'commandVenueFund',
|
|
114
|
+
options: ['auth', 'venue'],
|
|
115
|
+
description: 'Return venue funding plan (does not move funds implicitly)',
|
|
116
|
+
args: { query: true },
|
|
117
|
+
tier: 'online',
|
|
118
|
+
},
|
|
95
119
|
{
|
|
96
120
|
path: ['hummingbot', 'status'],
|
|
97
121
|
handler: 'commandHummingbotStatus',
|
|
@@ -196,6 +220,56 @@ export const CLI_MANIFEST = [
|
|
|
196
220
|
options: ['auth', 'execution'],
|
|
197
221
|
description: 'Quote action-tier credits for a catalog action',
|
|
198
222
|
},
|
|
223
|
+
{
|
|
224
|
+
path: ['chat', 'send'],
|
|
225
|
+
handler: 'commandChatSend',
|
|
226
|
+
tier: 'online',
|
|
227
|
+
options: ['auth', 'chat', 'chains'],
|
|
228
|
+
description: 'Send a Console chat message (streaming)',
|
|
229
|
+
args: { message: '[message...]' },
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
path: ['chat', 'threads'],
|
|
233
|
+
handler: 'commandChatThreads',
|
|
234
|
+
tier: 'online',
|
|
235
|
+
options: ['auth'],
|
|
236
|
+
description: 'List Console chat threads',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
path: ['chat', 'models'],
|
|
240
|
+
handler: 'commandChatModels',
|
|
241
|
+
tier: 'online',
|
|
242
|
+
options: ['auth'],
|
|
243
|
+
description: 'List Console chat models from catalog',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
path: ['brain', 'sections'],
|
|
247
|
+
handler: 'commandBrainSections',
|
|
248
|
+
tier: 'online',
|
|
249
|
+
options: ['auth', 'chains', 'brain'],
|
|
250
|
+
description: 'Read Console Brain sections for trading, yield, and prediction',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
path: ['brain', 'suggest'],
|
|
254
|
+
handler: 'commandBrainSuggest',
|
|
255
|
+
tier: 'online',
|
|
256
|
+
options: ['auth', 'chains', 'brain'],
|
|
257
|
+
description: 'Return a structured Brain strategy draft from current signals',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
path: ['brain', 'preflight'],
|
|
261
|
+
handler: 'commandBrainPreflight',
|
|
262
|
+
tier: 'online',
|
|
263
|
+
options: ['auth', 'chains', 'brain'],
|
|
264
|
+
description: 'Run read-only Brain preflight checks for a strategy',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
path: ['brain', 'graduate'],
|
|
268
|
+
handler: 'commandBrainGraduate',
|
|
269
|
+
tier: 'online',
|
|
270
|
+
options: ['auth', 'brain'],
|
|
271
|
+
description: 'Prepare an EarnClaw handoff proposal without deploying',
|
|
272
|
+
},
|
|
199
273
|
{
|
|
200
274
|
path: ['data', 'summary'],
|
|
201
275
|
handler: 'commandDataSummary',
|
|
@@ -303,6 +377,34 @@ export const CLI_MANIFEST = [
|
|
|
303
377
|
options: ['auth', 'execution'],
|
|
304
378
|
description: 'Redeem via delegation',
|
|
305
379
|
},
|
|
380
|
+
{
|
|
381
|
+
path: ['console-mandate', 'status'],
|
|
382
|
+
handler: 'commandConsoleMandateStatus',
|
|
383
|
+
tier: 'online',
|
|
384
|
+
options: ['auth', 'delegation'],
|
|
385
|
+
description: 'Console delegated execution status',
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
path: ['console-mandate', 'draft'],
|
|
389
|
+
handler: 'commandConsoleMandateDraft',
|
|
390
|
+
tier: 'online',
|
|
391
|
+
options: ['auth', 'delegation'],
|
|
392
|
+
description: 'Create Console mandate draft',
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
path: ['console-mandate', 'activate'],
|
|
396
|
+
handler: 'commandConsoleMandateActivate',
|
|
397
|
+
tier: 'online',
|
|
398
|
+
options: ['auth', 'delegation'],
|
|
399
|
+
description: 'Activate signed Console mandate',
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
path: ['console-mandate', 'revoke'],
|
|
403
|
+
handler: 'commandConsoleMandateRevoke',
|
|
404
|
+
tier: 'online',
|
|
405
|
+
options: ['auth', 'delegation'],
|
|
406
|
+
description: 'Revoke Console mandate',
|
|
407
|
+
},
|
|
306
408
|
{
|
|
307
409
|
path: ['credentials', 'upsert'],
|
|
308
410
|
handler: 'commandCredentialsUpsert',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function attachBrainOptions(cmd) {
|
|
2
|
+
return cmd
|
|
3
|
+
.option('--strategy <id>', 'Brain strategy id (default base-usdc-yield)')
|
|
4
|
+
.option('--strategy-id <id>', 'alias for --strategy')
|
|
5
|
+
.option('--section <lane>', 'yield | trading | prediction')
|
|
6
|
+
.option('--capital-usd <amount>', 'optional capital size for strategy draft')
|
|
7
|
+
.option('--risk <level>', 'low | medium | high')
|
|
8
|
+
.option('--target <name>', 'handoff target (earnclaw)')
|
|
9
|
+
.option('--include-degraded', 'include degraded Brain sections', false);
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function attachChatOptions(cmd) {
|
|
2
|
+
return cmd
|
|
3
|
+
.option('--network <id>', 'network id (for example base-mainnet)')
|
|
4
|
+
.option('--model <key>', 'chat model key from catalog')
|
|
5
|
+
.option('--thread <id>', 'existing chat thread id')
|
|
6
|
+
.option('--continue', 'continue the last persisted thread')
|
|
7
|
+
.option('--persona <mode>', 'chat persona mode', 'thirdfy')
|
|
8
|
+
.option('--tool-domains <list>', 'comma-separated tool pack ids (prediction, earn, perps_hl, ...)');
|
|
9
|
+
}
|
|
@@ -12,6 +12,9 @@ import { attachHummingbotOptions } from './hummingbot.mjs';
|
|
|
12
12
|
import { attachAgentRegisterOptions } from './agent.mjs';
|
|
13
13
|
import { attachChainOptions } from './chains.mjs';
|
|
14
14
|
import { attachCreditsOptions } from './credits.mjs';
|
|
15
|
+
import { attachChatOptions } from './chat.mjs';
|
|
16
|
+
import { attachVenueOptions } from './venue.mjs';
|
|
17
|
+
import { attachBrainOptions } from './brain.mjs';
|
|
15
18
|
|
|
16
19
|
const BUNDLES = {
|
|
17
20
|
auth: attachAuthOptions,
|
|
@@ -27,6 +30,9 @@ const BUNDLES = {
|
|
|
27
30
|
agent: attachAgentRegisterOptions,
|
|
28
31
|
chains: attachChainOptions,
|
|
29
32
|
credits: attachCreditsOptions,
|
|
33
|
+
chat: attachChatOptions,
|
|
34
|
+
venue: attachVenueOptions,
|
|
35
|
+
brain: attachBrainOptions,
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
/** @param {import('commander').Command} cmd */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function attachVenueOptions(cmd) {
|
|
2
|
+
return cmd
|
|
3
|
+
.option('--amount <usd>', 'Funding amount in USD')
|
|
4
|
+
.option('--amount-usd <usd>', 'Alias for --amount')
|
|
5
|
+
.option('--main-wallet-address <address>', 'Hyperliquid main wallet address')
|
|
6
|
+
.option('--api-wallet-address <address>', 'Hyperliquid API wallet address')
|
|
7
|
+
.option('--l1-address <address>', 'Lighter L1 address')
|
|
8
|
+
.option('--include-geoblock', 'Include Polymarket geoblock probes', true)
|
|
9
|
+
.option('--no-include-geoblock', 'Skip Polymarket geoblock probes')
|
|
10
|
+
.option('--complete', 'Run complete onboarding writes (setup command only)')
|
|
11
|
+
.option('--confirm-writes', 'Confirm onboarding writes');
|
|
12
|
+
}
|
package/src/cli/register.mjs
CHANGED
|
@@ -50,6 +50,9 @@ function registerManifestEntry(program, entry, nodeByPath, deps) {
|
|
|
50
50
|
if (entry.args?.query) {
|
|
51
51
|
leaf.argument(entry.args.query);
|
|
52
52
|
}
|
|
53
|
+
if (entry.args?.message) {
|
|
54
|
+
leaf.argument(entry.args.message);
|
|
55
|
+
}
|
|
53
56
|
|
|
54
57
|
const handler = handlers[entry.handler];
|
|
55
58
|
if (!handler) {
|
|
@@ -62,6 +65,11 @@ function registerManifestEntry(program, entry, nodeByPath, deps) {
|
|
|
62
65
|
|
|
63
66
|
if (entry.args?.address) {
|
|
64
67
|
extra.address = actionArgs[0];
|
|
68
|
+
} else if (entry.args?.message) {
|
|
69
|
+
const messageArg = actionArgs[0];
|
|
70
|
+
extra.message = Array.isArray(messageArg)
|
|
71
|
+
? messageArg.join(' ')
|
|
72
|
+
: String(messageArg || '').trim();
|
|
65
73
|
} else if (entry.args?.query) {
|
|
66
74
|
const queryArg = actionArgs[0];
|
|
67
75
|
extra.query = Array.isArray(queryArg) ? queryArg : queryArg !== undefined ? [queryArg] : [];
|
|
@@ -78,6 +86,8 @@ function registerManifestEntry(program, entry, nodeByPath, deps) {
|
|
|
78
86
|
} else if (entry.handler === 'commandPrompt') {
|
|
79
87
|
const tokens = Array.isArray(extra.query) ? extra.query : [];
|
|
80
88
|
await handler(ctx, flags, capabilities, tokens);
|
|
89
|
+
} else if (entry.handler === 'commandChatSend') {
|
|
90
|
+
await handler(ctx, flags, capabilities, extra.message);
|
|
81
91
|
} else {
|
|
82
92
|
await handler(ctx, flags, capabilities);
|
|
83
93
|
}
|
|
@@ -26,6 +26,8 @@ export function createBootstrapCommands(deps) {
|
|
|
26
26
|
printEnvelope,
|
|
27
27
|
extractAgentApiKey,
|
|
28
28
|
extractAgentKey,
|
|
29
|
+
maskSecret,
|
|
30
|
+
buildOwnerAuthHeaders,
|
|
29
31
|
buildOnboardingHelp,
|
|
30
32
|
buildWalletChallengePayload,
|
|
31
33
|
issueBootstrapToken,
|
|
@@ -162,12 +164,47 @@ async function commandHelpOnboarding(ctx, _flags) {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
|
|
165
|
-
async function commandWhoami(ctx,
|
|
167
|
+
async function commandWhoami(ctx, flags) {
|
|
166
168
|
const config = loadProfileConfig();
|
|
167
169
|
const auth = config.auth && typeof config.auth === 'object' ? config.auth : {};
|
|
168
170
|
const agent = config.agent && typeof config.agent === 'object' ? config.agent : {};
|
|
169
171
|
const wallets = config.wallets && typeof config.wallets === 'object' ? config.wallets : {};
|
|
172
|
+
const chat = config.chat && typeof config.chat === 'object' ? config.chat : {};
|
|
170
173
|
const runMode = ctx.runMode || config.runMode || 'agent_wallet';
|
|
174
|
+
|
|
175
|
+
let mandateSummary = null;
|
|
176
|
+
let mandateSummaryError = null;
|
|
177
|
+
const hasPrivyAuth = Boolean(
|
|
178
|
+
auth.authToken ||
|
|
179
|
+
auth.ownerSessionToken ||
|
|
180
|
+
process.env.THIRDFY_AUTH_TOKEN ||
|
|
181
|
+
process.env.THIRDFY_OWNER_SESSION_TOKEN ||
|
|
182
|
+
flags.authToken,
|
|
183
|
+
);
|
|
184
|
+
const hasAgentApiKey = Boolean(agent.apiKey || process.env.THIRDFY_AGENT_API_KEY);
|
|
185
|
+
|
|
186
|
+
if (hasPrivyAuth) {
|
|
187
|
+
try {
|
|
188
|
+
const headers = buildOwnerAuthHeaders(
|
|
189
|
+
flags,
|
|
190
|
+
'Missing credentials for mandate summary (auth token or owner session required).',
|
|
191
|
+
);
|
|
192
|
+
const chainId = Number(chat.chainId || flags.chainId || 8453);
|
|
193
|
+
const query = new URLSearchParams({ chainId: String(chainId) });
|
|
194
|
+
const walletAddress = String(
|
|
195
|
+
wallets.primaryEvmWallet || flags.walletAddress || '',
|
|
196
|
+
).trim();
|
|
197
|
+
if (walletAddress) query.set('walletAddress', walletAddress);
|
|
198
|
+
mandateSummary = await apiGet(
|
|
199
|
+
ctx,
|
|
200
|
+
`/api/v1/console/mandate-summary?${query.toString()}`,
|
|
201
|
+
headers,
|
|
202
|
+
);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
mandateSummaryError = error?.message || 'MANDATE_SUMMARY_FAILED';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
171
208
|
printEnvelope({
|
|
172
209
|
success: true,
|
|
173
210
|
code: 'WHOAMI_OK',
|
|
@@ -188,6 +225,14 @@ async function commandWhoami(ctx, _flags) {
|
|
|
188
225
|
agentApiKey: maskSecret(agent.apiKey),
|
|
189
226
|
agentKey: agent.agentKey || null,
|
|
190
227
|
},
|
|
228
|
+
chat: {
|
|
229
|
+
lastThreadId: chat.lastThreadId || null,
|
|
230
|
+
defaultChatModel: chat.defaultChatModel || null,
|
|
231
|
+
chainId: chat.chainId || null,
|
|
232
|
+
},
|
|
233
|
+
mandateSummary,
|
|
234
|
+
mandateSummaryError,
|
|
235
|
+
mandateSummarySkipped: !hasPrivyAuth && hasAgentApiKey ? 'requires_privy_auth' : null,
|
|
191
236
|
wallets: {
|
|
192
237
|
userDid: wallets.userDid || config.identity?.userDid || null,
|
|
193
238
|
primaryEvmWallet: wallets.primaryEvmWallet || null,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { loadProfileConfig } from '../core/context.mjs';
|
|
2
|
+
import { apiGet } from '../core/http.mjs';
|
|
3
|
+
|
|
4
|
+
function buildAuthHeaders(flags) {
|
|
5
|
+
const config = loadProfileConfig();
|
|
6
|
+
const explicitOwner = String(flags.ownerSessionToken || '').trim();
|
|
7
|
+
const explicitAuth = String(flags.authToken || '').trim();
|
|
8
|
+
const ownerSession = String(
|
|
9
|
+
process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || '',
|
|
10
|
+
).trim();
|
|
11
|
+
const authToken = String(process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
12
|
+
|
|
13
|
+
if (explicitOwner || ownerSession) {
|
|
14
|
+
return { 'x-owner-session-token': explicitOwner || ownerSession };
|
|
15
|
+
}
|
|
16
|
+
const token = explicitAuth || authToken;
|
|
17
|
+
if (token) {
|
|
18
|
+
return { Authorization: `Bearer ${token}` };
|
|
19
|
+
}
|
|
20
|
+
throw new Error(
|
|
21
|
+
'Missing credentials: provide --auth-token, THIRDFY_AUTH_TOKEN, or --owner-session-token.',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveChainId(flags, config) {
|
|
26
|
+
if (flags.chainId) {
|
|
27
|
+
const parsed = Number(flags.chainId);
|
|
28
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
29
|
+
}
|
|
30
|
+
const fromConfig = Number(config?.chat?.chainId || 0);
|
|
31
|
+
if (Number.isFinite(fromConfig) && fromConfig > 0) return fromConfig;
|
|
32
|
+
return 8453;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveWalletAddress(flags, config) {
|
|
36
|
+
const explicit = String(flags.walletAddress || '').trim();
|
|
37
|
+
if (explicit) return explicit;
|
|
38
|
+
const wallets = config?.wallets && typeof config.wallets === 'object' ? config.wallets : {};
|
|
39
|
+
return String(wallets.primaryEvmWallet || '').trim() || undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildQuery(params) {
|
|
43
|
+
const search = new URLSearchParams();
|
|
44
|
+
for (const [key, value] of Object.entries(params)) {
|
|
45
|
+
if (value == null || value === '') continue;
|
|
46
|
+
search.set(key, String(value));
|
|
47
|
+
}
|
|
48
|
+
const query = search.toString();
|
|
49
|
+
return query ? `?${query}` : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function printBrainEnvelope(printEnvelope, flags, code, message, data) {
|
|
53
|
+
printEnvelope(
|
|
54
|
+
{
|
|
55
|
+
success: true,
|
|
56
|
+
code,
|
|
57
|
+
message,
|
|
58
|
+
data,
|
|
59
|
+
meta: { readOnly: true },
|
|
60
|
+
},
|
|
61
|
+
flags,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createBrainCommands({ printEnvelope }) {
|
|
66
|
+
async function commandBrainSections(ctx, flags) {
|
|
67
|
+
const config = loadProfileConfig();
|
|
68
|
+
const chainId = resolveChainId(flags, config);
|
|
69
|
+
const walletAddress = resolveWalletAddress(flags, config);
|
|
70
|
+
const query = buildQuery({
|
|
71
|
+
chainId,
|
|
72
|
+
walletAddress,
|
|
73
|
+
includeDegraded: flags.includeDegraded === false ? 'false' : 'true',
|
|
74
|
+
});
|
|
75
|
+
const data = await apiGet(ctx, `/api/v1/console/brain-sections${query}`, buildAuthHeaders(flags));
|
|
76
|
+
printBrainEnvelope(printEnvelope, flags, 'OK', 'Brain sections loaded', data);
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function commandBrainSuggest(ctx, flags) {
|
|
81
|
+
const config = loadProfileConfig();
|
|
82
|
+
const chainId = resolveChainId(flags, config);
|
|
83
|
+
const walletAddress = resolveWalletAddress(flags, config);
|
|
84
|
+
const strategyId = String(flags.strategy || flags.strategyId || '').trim();
|
|
85
|
+
const section = String(flags.section || '').trim();
|
|
86
|
+
const queryParams = {
|
|
87
|
+
capitalUsd: flags.capitalUsd,
|
|
88
|
+
risk: flags.risk,
|
|
89
|
+
chainId,
|
|
90
|
+
walletAddress,
|
|
91
|
+
};
|
|
92
|
+
if (strategyId) {
|
|
93
|
+
queryParams.strategyId = strategyId;
|
|
94
|
+
} else if (section) {
|
|
95
|
+
queryParams.section = section;
|
|
96
|
+
} else {
|
|
97
|
+
queryParams.strategyId = 'base-usdc-yield';
|
|
98
|
+
}
|
|
99
|
+
const query = buildQuery(queryParams);
|
|
100
|
+
const data = await apiGet(ctx, `/api/v1/console/brain/suggest${query}`, buildAuthHeaders(flags));
|
|
101
|
+
printBrainEnvelope(printEnvelope, flags, 'OK', 'Brain strategy draft loaded', data);
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function commandBrainPreflight(ctx, flags) {
|
|
106
|
+
const config = loadProfileConfig();
|
|
107
|
+
const chainId = resolveChainId(flags, config);
|
|
108
|
+
const walletAddress = resolveWalletAddress(flags, config);
|
|
109
|
+
const query = buildQuery({
|
|
110
|
+
strategyId: flags.strategy || flags.strategyId || 'base-usdc-yield',
|
|
111
|
+
chainId,
|
|
112
|
+
walletAddress,
|
|
113
|
+
});
|
|
114
|
+
const data = await apiGet(ctx, `/api/v1/console/brain/preflight${query}`, buildAuthHeaders(flags));
|
|
115
|
+
printBrainEnvelope(printEnvelope, flags, 'OK', 'Brain preflight loaded', data);
|
|
116
|
+
return data;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function commandBrainGraduate(ctx, flags) {
|
|
120
|
+
const query = buildQuery({
|
|
121
|
+
strategyId: flags.strategy || flags.strategyId || 'base-usdc-yield',
|
|
122
|
+
target: flags.target || 'earnclaw',
|
|
123
|
+
});
|
|
124
|
+
const data = await apiGet(ctx, `/api/v1/console/brain/graduate${query}`, buildAuthHeaders(flags));
|
|
125
|
+
printBrainEnvelope(printEnvelope, flags, 'OK', 'Brain EarnClaw handoff proposal loaded', data);
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
commandBrainSections,
|
|
131
|
+
commandBrainSuggest,
|
|
132
|
+
commandBrainPreflight,
|
|
133
|
+
commandBrainGraduate,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { parseBooleanFlag } from '../core/runMode.mjs';
|
|
2
|
+
import { loadProfileConfig, persistProfileConfig } from '../core/context.mjs';
|
|
3
|
+
import { apiGet, safeJsonParse } from '../core/http.mjs';
|
|
4
|
+
|
|
5
|
+
const NETWORK_CHAIN_IDS = {
|
|
6
|
+
'base-mainnet': 8453,
|
|
7
|
+
'base-sepolia': 84532,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CHAT_CHAIN_ID = 8453;
|
|
11
|
+
const CHAT_STREAM_TIMEOUT_MS = 180_000;
|
|
12
|
+
|
|
13
|
+
function resolveChatChainId(flags, config) {
|
|
14
|
+
if (flags.chainId) {
|
|
15
|
+
const parsed = Number(flags.chainId);
|
|
16
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
17
|
+
}
|
|
18
|
+
const network = String(flags.network || '').trim().toLowerCase();
|
|
19
|
+
if (network && NETWORK_CHAIN_IDS[network]) return NETWORK_CHAIN_IDS[network];
|
|
20
|
+
const fromConfig = Number(config?.chat?.chainId || 0);
|
|
21
|
+
if (Number.isFinite(fromConfig) && fromConfig > 0) return fromConfig;
|
|
22
|
+
return DEFAULT_CHAT_CHAIN_ID;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildChatAuthHeaders(flags) {
|
|
26
|
+
const config = loadProfileConfig();
|
|
27
|
+
const explicitOwner = String(flags.ownerSessionToken || '').trim();
|
|
28
|
+
const explicitAuth = String(flags.authToken || '').trim();
|
|
29
|
+
const ownerSession = String(
|
|
30
|
+
process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || '',
|
|
31
|
+
).trim();
|
|
32
|
+
const authToken = String(process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
33
|
+
|
|
34
|
+
if (explicitOwner || ownerSession) {
|
|
35
|
+
return { 'x-owner-session-token': explicitOwner || ownerSession };
|
|
36
|
+
}
|
|
37
|
+
const token = explicitAuth || authToken;
|
|
38
|
+
if (token) {
|
|
39
|
+
return { Authorization: `Bearer ${token}` };
|
|
40
|
+
}
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Missing chat credentials: provide --auth-token, THIRDFY_AUTH_TOKEN, or --owner-session-token.',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildChatRequestHeaders(flags, modelKey) {
|
|
47
|
+
const headers = {
|
|
48
|
+
'content-type': 'application/json',
|
|
49
|
+
Accept: 'text/plain; charset=utf-8',
|
|
50
|
+
...buildChatAuthHeaders(flags),
|
|
51
|
+
};
|
|
52
|
+
if (modelKey) {
|
|
53
|
+
headers['x-thirdfy-chat-model'] = modelKey;
|
|
54
|
+
}
|
|
55
|
+
const persona = String(flags.persona || 'thirdfy').trim();
|
|
56
|
+
if (persona) {
|
|
57
|
+
headers['x-thirdfy-chat-persona'] = persona;
|
|
58
|
+
}
|
|
59
|
+
return headers;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveChatModelKey(flags, config) {
|
|
63
|
+
return (
|
|
64
|
+
String(flags.model || '').trim() ||
|
|
65
|
+
String(config?.chat?.defaultChatModel || '').trim() ||
|
|
66
|
+
''
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveChatThreadId(flags, config) {
|
|
71
|
+
const explicit = String(flags.thread || flags.threadId || '').trim();
|
|
72
|
+
if (explicit) return explicit;
|
|
73
|
+
if (parseBooleanFlag(flags.continue, false)) {
|
|
74
|
+
return String(config?.chat?.lastThreadId || '').trim() || undefined;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveUserAddress(flags, config) {
|
|
80
|
+
const explicit = String(flags.walletAddress || '').trim();
|
|
81
|
+
if (explicit) return explicit;
|
|
82
|
+
const wallets = config?.wallets && typeof config.wallets === 'object' ? config.wallets : {};
|
|
83
|
+
return String(wallets.primaryEvmWallet || '').trim() || undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseToolDomains(flags) {
|
|
87
|
+
const raw = String(flags.toolDomains || '').trim();
|
|
88
|
+
if (!raw) return undefined;
|
|
89
|
+
const domains = raw
|
|
90
|
+
.split(',')
|
|
91
|
+
.map((entry) => entry.trim())
|
|
92
|
+
.filter(Boolean);
|
|
93
|
+
return domains.length ? domains : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseDataStreamLine(line) {
|
|
97
|
+
const trimmed = line.trim();
|
|
98
|
+
if (!trimmed) return null;
|
|
99
|
+
|
|
100
|
+
if (trimmed.startsWith('data: ')) {
|
|
101
|
+
try {
|
|
102
|
+
return safeJsonParse(trimmed.slice(6));
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const colonIndex = trimmed.indexOf(':');
|
|
109
|
+
if (colonIndex <= 0) return null;
|
|
110
|
+
const prefix = trimmed.slice(0, colonIndex);
|
|
111
|
+
const payload = trimmed.slice(colonIndex + 1);
|
|
112
|
+
|
|
113
|
+
if (prefix === '0') {
|
|
114
|
+
try {
|
|
115
|
+
return { type: 'text', value: String(JSON.parse(payload)) };
|
|
116
|
+
} catch {
|
|
117
|
+
return { type: 'text', value: payload };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (prefix === '2') {
|
|
122
|
+
try {
|
|
123
|
+
const data = JSON.parse(payload);
|
|
124
|
+
return { type: 'data', value: Array.isArray(data) ? data : [data] };
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (prefix === '3') {
|
|
131
|
+
try {
|
|
132
|
+
return { type: 'error', value: JSON.parse(payload) };
|
|
133
|
+
} catch {
|
|
134
|
+
return { type: 'error', value: payload };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function extractThreadMetaFromDataItems(items) {
|
|
142
|
+
if (!Array.isArray(items)) return null;
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
if (item && typeof item === 'object' && item.type === 'thread_meta' && item.threadId) {
|
|
145
|
+
return item;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseThreadMetaHeader(raw) {
|
|
152
|
+
if (!raw) return null;
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(raw);
|
|
155
|
+
if (parsed && typeof parsed === 'object' && parsed.type === 'thread_meta') {
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
if (parsed && typeof parsed === 'object' && parsed.threadId) {
|
|
159
|
+
return { type: 'thread_meta', ...parsed };
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function requestChatStream(ctx, body, headers) {
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timeoutMs = Math.max(Number(ctx.timeoutMs || 0), CHAT_STREAM_TIMEOUT_MS);
|
|
170
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(`${ctx.apiBase}/api/v1/chat/stream`, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers,
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
signal: controller.signal,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
const text = await response.text().catch(() => '');
|
|
182
|
+
const data = text ? safeJsonParse(text) : {};
|
|
183
|
+
const error = new Error(data?.error || data?.message || `HTTP ${response.status}`);
|
|
184
|
+
error.statusCode = response.status;
|
|
185
|
+
error.payload = data;
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let threadMeta =
|
|
190
|
+
parseThreadMetaHeader(response.headers.get('X-Thirdfy-Chat-Thread-Meta')) || null;
|
|
191
|
+
let threadId = response.headers.get('X-Thirdfy-Chat-Thread-Id') || undefined;
|
|
192
|
+
let content = '';
|
|
193
|
+
let streamError = null;
|
|
194
|
+
const toolCalls = [];
|
|
195
|
+
|
|
196
|
+
if (response.body) {
|
|
197
|
+
const reader = response.body.getReader();
|
|
198
|
+
const decoder = new TextDecoder();
|
|
199
|
+
let lineBuffer = '';
|
|
200
|
+
|
|
201
|
+
while (true) {
|
|
202
|
+
const { done, value } = await reader.read();
|
|
203
|
+
if (done) break;
|
|
204
|
+
lineBuffer += decoder.decode(value, { stream: true });
|
|
205
|
+
const lines = lineBuffer.split('\n');
|
|
206
|
+
lineBuffer = lines.pop() ?? '';
|
|
207
|
+
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
const parsedLine = parseDataStreamLine(line);
|
|
210
|
+
if (!parsedLine) continue;
|
|
211
|
+
|
|
212
|
+
if (parsedLine.type === 'text') {
|
|
213
|
+
content += parsedLine.value;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (parsedLine.type === 'error') {
|
|
218
|
+
streamError = String(parsedLine.value || 'stream error');
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (parsedLine.type === 'data') {
|
|
223
|
+
const meta = extractThreadMetaFromDataItems(parsedLine.value);
|
|
224
|
+
if (meta) {
|
|
225
|
+
threadMeta = meta;
|
|
226
|
+
if (meta.threadId) threadId = String(meta.threadId);
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (parsedLine.type === 'content' || parsedLine.type === 'text-delta') {
|
|
232
|
+
content += String(parsedLine.content || parsedLine.textDelta || parsedLine.text || '');
|
|
233
|
+
}
|
|
234
|
+
if (parsedLine.type === 'tool-call' && parsedLine.toolName) {
|
|
235
|
+
toolCalls.push(String(parsedLine.toolName));
|
|
236
|
+
}
|
|
237
|
+
if (parsedLine.type === 'data' && Array.isArray(parsedLine.data)) {
|
|
238
|
+
const meta = extractThreadMetaFromDataItems(parsedLine.data);
|
|
239
|
+
if (meta) {
|
|
240
|
+
threadMeta = meta;
|
|
241
|
+
if (meta.threadId) threadId = String(meta.threadId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (parsedLine.type === 'error') {
|
|
245
|
+
streamError = String(parsedLine.error || parsedLine.message || 'stream error');
|
|
246
|
+
}
|
|
247
|
+
if (parsedLine.type === 'done') {
|
|
248
|
+
const meta = parsedLine.thread_meta || parsedLine.threadMeta;
|
|
249
|
+
if (meta && typeof meta === 'object') {
|
|
250
|
+
threadMeta = meta;
|
|
251
|
+
if (meta.threadId) threadId = String(meta.threadId);
|
|
252
|
+
}
|
|
253
|
+
if (parsedLine.threadId) threadId = String(parsedLine.threadId);
|
|
254
|
+
}
|
|
255
|
+
const debug = parsedLine.debug;
|
|
256
|
+
if (debug && Array.isArray(debug.toolCallsList)) {
|
|
257
|
+
for (const item of debug.toolCallsList) toolCalls.push(String(item));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const tail = lineBuffer.trim();
|
|
263
|
+
if (tail) {
|
|
264
|
+
const parsedLine = parseDataStreamLine(tail);
|
|
265
|
+
if (parsedLine?.type === 'text') content += parsedLine.value;
|
|
266
|
+
if (parsedLine?.type === 'data') {
|
|
267
|
+
const meta = extractThreadMetaFromDataItems(parsedLine.value);
|
|
268
|
+
if (meta) {
|
|
269
|
+
threadMeta = meta;
|
|
270
|
+
if (meta.threadId) threadId = String(meta.threadId);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (streamError) {
|
|
277
|
+
const error = new Error(streamError);
|
|
278
|
+
error.statusCode = 502;
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
content,
|
|
284
|
+
threadId: threadId || threadMeta?.threadId || body.threadId,
|
|
285
|
+
threadMeta,
|
|
286
|
+
toolCalls,
|
|
287
|
+
};
|
|
288
|
+
} finally {
|
|
289
|
+
clearTimeout(timeout);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function persistChatPreferences(config, { threadId, modelKey, chainId }) {
|
|
294
|
+
const next = {
|
|
295
|
+
...config,
|
|
296
|
+
chat: {
|
|
297
|
+
...(config.chat && typeof config.chat === 'object' ? config.chat : {}),
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
if (threadId) next.chat.lastThreadId = String(threadId);
|
|
301
|
+
if (modelKey) next.chat.defaultChatModel = String(modelKey);
|
|
302
|
+
if (chainId) next.chat.chainId = Number(chainId);
|
|
303
|
+
persistProfileConfig(next);
|
|
304
|
+
return next.chat;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function createChatCommands({ printEnvelope }) {
|
|
308
|
+
async function commandChatSend(ctx, flags, _capabilities, messageArg) {
|
|
309
|
+
const message = String(messageArg || flags.message || '').trim();
|
|
310
|
+
if (!message) {
|
|
311
|
+
throw new Error('Missing message. Use: thirdfy-agent chat send "<message>"');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const config = loadProfileConfig();
|
|
315
|
+
const chainId = resolveChatChainId(flags, config);
|
|
316
|
+
const modelKey = resolveChatModelKey(flags, config);
|
|
317
|
+
const threadId = resolveChatThreadId(flags, config);
|
|
318
|
+
const userAddress = resolveUserAddress(flags, config);
|
|
319
|
+
const toolDomains = parseToolDomains(flags);
|
|
320
|
+
|
|
321
|
+
const body = {
|
|
322
|
+
messages: [{ role: 'user', content: message }],
|
|
323
|
+
chainId,
|
|
324
|
+
personaMode: String(flags.persona || 'thirdfy').trim() || 'thirdfy',
|
|
325
|
+
...(threadId ? { threadId } : {}),
|
|
326
|
+
...(modelKey ? { modelKey } : {}),
|
|
327
|
+
...(userAddress ? { userAddress } : {}),
|
|
328
|
+
...(toolDomains ? { toolDomains } : {}),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const headers = buildChatRequestHeaders(flags, modelKey);
|
|
332
|
+
const stream = await requestChatStream(ctx, body, headers);
|
|
333
|
+
|
|
334
|
+
const resolvedThreadId = stream.threadId || threadId;
|
|
335
|
+
const resolvedModelKey = stream.threadMeta?.modelKey || modelKey;
|
|
336
|
+
const persisted = persistChatPreferences(config, {
|
|
337
|
+
threadId: resolvedThreadId,
|
|
338
|
+
modelKey: resolvedModelKey,
|
|
339
|
+
chainId,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (!flags.json && stream.content) {
|
|
343
|
+
process.stdout.write(stream.content);
|
|
344
|
+
if (!stream.content.endsWith('\n')) process.stdout.write('\n');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const threadLabel = resolvedThreadId ? ` (thread ${resolvedThreadId})` : '';
|
|
348
|
+
printEnvelope({
|
|
349
|
+
success: true,
|
|
350
|
+
code: 'CHAT_SEND_OK',
|
|
351
|
+
message: flags.json ? 'Chat response completed' : `Chat response completed${threadLabel}`,
|
|
352
|
+
data: {
|
|
353
|
+
...(flags.json ? { content: stream.content } : {}),
|
|
354
|
+
threadId: resolvedThreadId || null,
|
|
355
|
+
threadMeta: stream.threadMeta || null,
|
|
356
|
+
toolCalls: stream.toolCalls,
|
|
357
|
+
modelKey: resolvedModelKey || null,
|
|
358
|
+
chainId,
|
|
359
|
+
persisted,
|
|
360
|
+
},
|
|
361
|
+
meta: {
|
|
362
|
+
apiBase: ctx.apiBase,
|
|
363
|
+
continued: parseBooleanFlag(flags.continue, false),
|
|
364
|
+
network: flags.network || null,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function commandChatThreads(ctx, flags) {
|
|
370
|
+
const headers = buildChatAuthHeaders(flags);
|
|
371
|
+
const query = new URLSearchParams();
|
|
372
|
+
if (flags.limit) query.set('limit', String(flags.limit));
|
|
373
|
+
if (flags.status) query.set('status', String(flags.status));
|
|
374
|
+
const route = query.size
|
|
375
|
+
? `/api/v1/chat/threads?${query.toString()}`
|
|
376
|
+
: '/api/v1/chat/threads';
|
|
377
|
+
const response = await apiGet(ctx, route, headers);
|
|
378
|
+
printEnvelope({
|
|
379
|
+
success: true,
|
|
380
|
+
code: 'CHAT_THREADS_OK',
|
|
381
|
+
message: 'Chat threads loaded',
|
|
382
|
+
data: response,
|
|
383
|
+
meta: { apiBase: ctx.apiBase },
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function commandChatModels(ctx, flags) {
|
|
388
|
+
const headers = buildChatAuthHeaders(flags);
|
|
389
|
+
const response = await apiGet(ctx, '/api/v1/chat/models', headers);
|
|
390
|
+
printEnvelope({
|
|
391
|
+
success: true,
|
|
392
|
+
code: 'CHAT_MODELS_OK',
|
|
393
|
+
message: 'Chat models loaded',
|
|
394
|
+
data: response,
|
|
395
|
+
meta: { apiBase: ctx.apiBase },
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
commandChatSend,
|
|
401
|
+
commandChatThreads,
|
|
402
|
+
commandChatModels,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { requireFlag } from '../core/args.mjs';
|
|
2
|
+
import { apiGet, apiPost } from '../core/http.mjs';
|
|
3
|
+
|
|
4
|
+
export function createConsoleMandateCommands(deps) {
|
|
5
|
+
const { buildOwnerAuthHeaders, printEnvelope } = deps;
|
|
6
|
+
|
|
7
|
+
async function commandConsoleMandateStatus(ctx, flags) {
|
|
8
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
9
|
+
flags,
|
|
10
|
+
'Missing --auth-token/--owner-session-token for console mandate status',
|
|
11
|
+
);
|
|
12
|
+
const chainId = Number(flags.chainId || 8453);
|
|
13
|
+
const walletAddress = String(flags.walletAddress || flags.ownerAddress || '').trim();
|
|
14
|
+
const query = new URLSearchParams({ chainId: String(chainId) });
|
|
15
|
+
if (walletAddress) query.set('walletAddress', walletAddress);
|
|
16
|
+
const response = await apiGet(ctx, `/api/v1/console/delegation/status?${query.toString()}`, {
|
|
17
|
+
...authHeaders,
|
|
18
|
+
headers: {
|
|
19
|
+
...(authHeaders.headers || {}),
|
|
20
|
+
'x-thirdfy-surface': 'cli',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
printEnvelope({
|
|
24
|
+
success: Boolean(response?.success !== false),
|
|
25
|
+
code: 'CONSOLE_MANDATE_STATUS',
|
|
26
|
+
message: 'Console delegated execution status',
|
|
27
|
+
data: response?.data || response,
|
|
28
|
+
meta: { apiBase: ctx.apiBase },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function commandConsoleMandateDraft(ctx, flags) {
|
|
33
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
34
|
+
flags,
|
|
35
|
+
'Missing --auth-token/--owner-session-token for console mandate draft',
|
|
36
|
+
);
|
|
37
|
+
const payload = {
|
|
38
|
+
ownerAddress: requireFlag(flags, 'ownerAddress', 'Missing --owner-address'),
|
|
39
|
+
chainId: Number(flags.chainId || 8453),
|
|
40
|
+
maxUsdPerDay: Number(requireFlag(flags, 'maxUsdPerDay', 'Missing --max-usd-per-day')),
|
|
41
|
+
expirySeconds: Number(flags.expirySeconds || 30 * 24 * 60 * 60),
|
|
42
|
+
tokenAddress: flags.tokenAddress,
|
|
43
|
+
periodDuration: Number(flags.periodDuration || 86_400),
|
|
44
|
+
};
|
|
45
|
+
const response = await apiPost(ctx, '/api/v1/console/delegation/draft', payload, {
|
|
46
|
+
...authHeaders,
|
|
47
|
+
headers: {
|
|
48
|
+
...(authHeaders.headers || {}),
|
|
49
|
+
'x-thirdfy-surface': 'cli',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
printEnvelope({
|
|
53
|
+
success: Boolean(response?.success !== false),
|
|
54
|
+
code: 'CONSOLE_MANDATE_DRAFT',
|
|
55
|
+
message: 'Console mandate draft created',
|
|
56
|
+
data: response?.data || response,
|
|
57
|
+
meta: { apiBase: ctx.apiBase },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function commandConsoleMandateActivate(ctx, flags) {
|
|
62
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
63
|
+
flags,
|
|
64
|
+
'Missing --auth-token/--owner-session-token for console mandate activate',
|
|
65
|
+
);
|
|
66
|
+
const payload = {
|
|
67
|
+
walletAddress: requireFlag(flags, 'walletAddress', 'Missing --wallet-address'),
|
|
68
|
+
chainId: Number(flags.chainId || 8453),
|
|
69
|
+
delegationManager: requireFlag(flags, 'delegationManager', 'Missing --delegation-manager'),
|
|
70
|
+
delegation: JSON.parse(requireFlag(flags, 'delegation', 'Missing --delegation JSON')),
|
|
71
|
+
signature: requireFlag(flags, 'signature', 'Missing --signature'),
|
|
72
|
+
permissionsExpiry: flags.permissionsExpiry,
|
|
73
|
+
maxUsdPerDay: Number(requireFlag(flags, 'maxUsdPerDay', 'Missing --max-usd-per-day')),
|
|
74
|
+
periodDuration: Number(flags.periodDuration || 86_400),
|
|
75
|
+
tokenAddress: flags.tokenAddress,
|
|
76
|
+
};
|
|
77
|
+
const response = await apiPost(ctx, '/api/v1/console/delegation/activate', payload, {
|
|
78
|
+
...authHeaders,
|
|
79
|
+
headers: {
|
|
80
|
+
...(authHeaders.headers || {}),
|
|
81
|
+
'x-thirdfy-surface': 'cli',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
printEnvelope({
|
|
85
|
+
success: Boolean(response?.success !== false),
|
|
86
|
+
code: 'CONSOLE_MANDATE_ACTIVATED',
|
|
87
|
+
message: 'Console mandate activated',
|
|
88
|
+
data: response?.data || response,
|
|
89
|
+
meta: { apiBase: ctx.apiBase },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function commandConsoleMandateRevoke(ctx, flags) {
|
|
94
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
95
|
+
flags,
|
|
96
|
+
'Missing --auth-token/--owner-session-token for console mandate revoke',
|
|
97
|
+
);
|
|
98
|
+
const payload = {
|
|
99
|
+
walletAddress: flags.walletAddress,
|
|
100
|
+
chainId: Number(flags.chainId || 8453),
|
|
101
|
+
reason: flags.reason || 'cli_revoked',
|
|
102
|
+
};
|
|
103
|
+
const response = await apiPost(ctx, '/api/v1/console/delegation/revoke', payload, {
|
|
104
|
+
...authHeaders,
|
|
105
|
+
headers: {
|
|
106
|
+
...(authHeaders.headers || {}),
|
|
107
|
+
'x-thirdfy-surface': 'cli',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
printEnvelope({
|
|
111
|
+
success: Boolean(response?.success !== false),
|
|
112
|
+
code: 'CONSOLE_MANDATE_REVOKED',
|
|
113
|
+
message: 'Console mandate revoked',
|
|
114
|
+
data: response?.data || response,
|
|
115
|
+
meta: { apiBase: ctx.apiBase },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
commandConsoleMandateStatus,
|
|
121
|
+
commandConsoleMandateDraft,
|
|
122
|
+
commandConsoleMandateActivate,
|
|
123
|
+
commandConsoleMandateRevoke,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { apiGet, apiPost } from '../core/http.mjs';
|
|
2
|
+
import { loadProfileConfig } from '../core/context.mjs';
|
|
3
|
+
|
|
4
|
+
const VENUES = new Set(['polymarket', 'hyperliquid', 'lighter']);
|
|
5
|
+
|
|
6
|
+
function resolveVenue(flags) {
|
|
7
|
+
const fromQuery = Array.isArray(flags.query) ? flags.query[0] : flags.query;
|
|
8
|
+
const venue = String(flags.venue || fromQuery || '').trim().toLowerCase();
|
|
9
|
+
if (!VENUES.has(venue)) {
|
|
10
|
+
throw new Error(`Unsupported venue "${venue}". Use polymarket, hyperliquid, or lighter.`);
|
|
11
|
+
}
|
|
12
|
+
return venue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function extractAgentKey(value) {
|
|
16
|
+
if (!value || typeof value !== 'object') return '';
|
|
17
|
+
const candidates = [
|
|
18
|
+
value.agentKey,
|
|
19
|
+
value.data?.agentKey,
|
|
20
|
+
value.bootstrap?.agentKey,
|
|
21
|
+
value.registration?.agentKey,
|
|
22
|
+
value.registration?.registration?.agentKey,
|
|
23
|
+
value.data?.registration?.agentKey,
|
|
24
|
+
value.data?.registration?.registration?.agentKey,
|
|
25
|
+
value.owner?.creatorWallet,
|
|
26
|
+
value.data?.owner?.creatorWallet,
|
|
27
|
+
];
|
|
28
|
+
return String(candidates.find((candidate) => String(candidate || '').trim()) || '').trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveVenueAgentKey(flags) {
|
|
32
|
+
const fromFlags = String(flags.agentKey || '').trim();
|
|
33
|
+
if (fromFlags) return fromFlags;
|
|
34
|
+
const config = loadProfileConfig();
|
|
35
|
+
const fromAgent = String(config?.agent?.agentKey || '').trim();
|
|
36
|
+
if (fromAgent) return fromAgent;
|
|
37
|
+
const fromConfig = extractAgentKey(config);
|
|
38
|
+
if (fromConfig) return fromConfig;
|
|
39
|
+
throw new Error('Missing --agent-key (or config.agent.agentKey). Venue tooling uses --agent-api-key only for authentication.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildAgentQuery(agentKey, flags) {
|
|
43
|
+
const query = new URLSearchParams({ agentKey });
|
|
44
|
+
if (flags.mainWalletAddress) query.set('mainWalletAddress', String(flags.mainWalletAddress));
|
|
45
|
+
if (flags.apiWalletAddress) query.set('apiWalletAddress', String(flags.apiWalletAddress));
|
|
46
|
+
if (flags.l1Address) query.set('l1Address', String(flags.l1Address));
|
|
47
|
+
if (flags.amountUsd) query.set('amountUsd', String(flags.amountUsd));
|
|
48
|
+
if (flags.includeGeoblock === false) query.set('includeGeoblock', 'false');
|
|
49
|
+
return query;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createVenueCommands({ printEnvelope, resolveAgentApiKey }) {
|
|
53
|
+
function buildAuthHeaders(flags) {
|
|
54
|
+
const agentApiKey = resolveAgentApiKey(flags, flags.runMode || 'agent_wallet');
|
|
55
|
+
return { 'x-agent-api-key': agentApiKey };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function commandVenueReadiness(ctx, flags) {
|
|
59
|
+
const venue = resolveVenue(flags);
|
|
60
|
+
const agentKey = resolveVenueAgentKey(flags);
|
|
61
|
+
const query = buildAgentQuery(agentKey, flags);
|
|
62
|
+
query.set('venue', venue);
|
|
63
|
+
const response = await apiGet(ctx, `/api/v1/agent/venue/readiness?${query.toString()}`, buildAuthHeaders(flags));
|
|
64
|
+
printEnvelope(response);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function commandVenueSetup(ctx, flags) {
|
|
68
|
+
const venue = resolveVenue(flags);
|
|
69
|
+
const agentKey = resolveVenueAgentKey(flags);
|
|
70
|
+
const complete = Boolean(flags.complete);
|
|
71
|
+
const confirmWrites = Boolean(flags.confirmWrites);
|
|
72
|
+
const body = {
|
|
73
|
+
venue,
|
|
74
|
+
agentKey,
|
|
75
|
+
dryRun: complete ? false : !confirmWrites,
|
|
76
|
+
confirmWrites,
|
|
77
|
+
...(flags.mainWalletAddress ? { mainWalletAddress: String(flags.mainWalletAddress) } : {}),
|
|
78
|
+
...(flags.apiWalletAddress ? { apiWalletAddress: String(flags.apiWalletAddress) } : {}),
|
|
79
|
+
...(flags.l1Address ? { l1Address: String(flags.l1Address) } : {}),
|
|
80
|
+
...(flags.amountUsd ? { amountUsd: Number(flags.amountUsd) } : {}),
|
|
81
|
+
};
|
|
82
|
+
const path = flags.complete ? '/api/v1/agent/venue/complete' : '/api/v1/agent/venue/prepare';
|
|
83
|
+
const response = await apiPost(ctx, path, body, buildAuthHeaders(flags));
|
|
84
|
+
printEnvelope(response);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function commandVenueFund(ctx, flags) {
|
|
88
|
+
const venue = resolveVenue(flags);
|
|
89
|
+
const agentKey = resolveVenueAgentKey(flags);
|
|
90
|
+
const amountUsd = Number(flags.amount || flags.amountUsd || 0);
|
|
91
|
+
if (!Number.isFinite(amountUsd) || amountUsd <= 0) {
|
|
92
|
+
throw new Error('Provide --amount with a positive USD value.');
|
|
93
|
+
}
|
|
94
|
+
const response = await apiPost(ctx, '/api/v1/agent/venue/fund', {
|
|
95
|
+
venue,
|
|
96
|
+
agentKey,
|
|
97
|
+
amountUsd,
|
|
98
|
+
...(flags.mainWalletAddress ? { mainWalletAddress: String(flags.mainWalletAddress) } : {}),
|
|
99
|
+
...(flags.apiWalletAddress ? { apiWalletAddress: String(flags.apiWalletAddress) } : {}),
|
|
100
|
+
...(flags.l1Address ? { l1Address: String(flags.l1Address) } : {}),
|
|
101
|
+
}, buildAuthHeaders(flags));
|
|
102
|
+
printEnvelope(response);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
commandVenueReadiness,
|
|
107
|
+
commandVenueSetup,
|
|
108
|
+
commandVenueFund,
|
|
109
|
+
};
|
|
110
|
+
}
|
package/src/runtime/handlers.mjs
CHANGED
|
@@ -6,14 +6,18 @@ import { createLighterHelpers } from '../commands/lighter.mjs';
|
|
|
6
6
|
import { createExecuteCommands } from '../commands/execute.mjs';
|
|
7
7
|
import { createWalletCommands } from '../commands/wallet.mjs';
|
|
8
8
|
import { createDelegationCommands } from '../commands/delegation.mjs';
|
|
9
|
+
import { createConsoleMandateCommands } from '../commands/consoleMandate.mjs';
|
|
9
10
|
import { createBootstrapCommands } from '../commands/bootstrap.mjs';
|
|
10
11
|
import { createDiscoveryCommands } from '../commands/discovery.mjs';
|
|
11
12
|
import { createCreditsCommands } from '../commands/credits.mjs';
|
|
13
|
+
import { createChatCommands } from '../commands/chat.mjs';
|
|
14
|
+
import { createBrainCommands } from '../commands/brain.mjs';
|
|
12
15
|
import { createTelemetryCommands } from '../commands/telemetry.mjs';
|
|
13
16
|
import { createCredentialsCommands } from '../commands/credentials.mjs';
|
|
14
17
|
import { createAgentCommands } from '../commands/agent.mjs';
|
|
15
18
|
import { createDoctorCommands } from '../commands/doctor.mjs';
|
|
16
19
|
import { createPromptCommands } from '../commands/prompt.mjs';
|
|
20
|
+
import { createVenueCommands } from '../commands/venue.mjs';
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Wire command-module factories and local handlers for Commander manifest registration.
|
|
@@ -62,6 +66,14 @@ export function createRuntimeHandlers(deps) {
|
|
|
62
66
|
getAuthToken,
|
|
63
67
|
});
|
|
64
68
|
|
|
69
|
+
const __chat = createChatCommands({
|
|
70
|
+
printEnvelope,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const __brain = createBrainCommands({
|
|
74
|
+
printEnvelope,
|
|
75
|
+
});
|
|
76
|
+
|
|
65
77
|
const __telemetry = createTelemetryCommands({
|
|
66
78
|
printEnvelope,
|
|
67
79
|
resolveAgentApiKey,
|
|
@@ -94,6 +106,7 @@ export function createRuntimeHandlers(deps) {
|
|
|
94
106
|
});
|
|
95
107
|
|
|
96
108
|
const __prompt = createPromptCommands({ printEnvelope });
|
|
109
|
+
const __venue = createVenueCommands({ printEnvelope, resolveAgentApiKey });
|
|
97
110
|
|
|
98
111
|
const __hyperliquid = createHyperliquidHelpers({
|
|
99
112
|
getPreparedParams,
|
|
@@ -144,6 +157,11 @@ export function createRuntimeHandlers(deps) {
|
|
|
144
157
|
commandRun: __execute.commandRun,
|
|
145
158
|
});
|
|
146
159
|
|
|
160
|
+
const __consoleMandate = createConsoleMandateCommands({
|
|
161
|
+
buildOwnerAuthHeaders,
|
|
162
|
+
printEnvelope,
|
|
163
|
+
});
|
|
164
|
+
|
|
147
165
|
const __polymarket = createPolymarketCommands({
|
|
148
166
|
commandPreflight: __execute.commandPreflight,
|
|
149
167
|
printEnvelope,
|
|
@@ -157,6 +175,8 @@ export function createRuntimeHandlers(deps) {
|
|
|
157
175
|
printEnvelope,
|
|
158
176
|
extractAgentApiKey,
|
|
159
177
|
extractAgentKey,
|
|
178
|
+
maskSecret,
|
|
179
|
+
buildOwnerAuthHeaders,
|
|
160
180
|
buildOnboardingHelp: __doctor.buildOnboardingHelp,
|
|
161
181
|
buildWalletChallengePayload: __agent.buildWalletChallengePayload,
|
|
162
182
|
issueBootstrapToken: __agent.issueBootstrapToken,
|
|
@@ -170,6 +190,7 @@ export function createRuntimeHandlers(deps) {
|
|
|
170
190
|
...__execute,
|
|
171
191
|
...__wallet,
|
|
172
192
|
...__delegation,
|
|
193
|
+
...__consoleMandate,
|
|
173
194
|
...__polymarket,
|
|
174
195
|
...__hummingbot,
|
|
175
196
|
...__bootstrap,
|
|
@@ -179,6 +200,7 @@ export function createRuntimeHandlers(deps) {
|
|
|
179
200
|
...__agent,
|
|
180
201
|
...__doctor,
|
|
181
202
|
...__prompt,
|
|
203
|
+
...__venue,
|
|
182
204
|
commandDoctorSelf: __doctor.commandDoctorSelf,
|
|
183
205
|
commandDoctorAuth: __doctor.commandDoctorAuth,
|
|
184
206
|
commandManagedSetup: __doctor.commandManagedSetup,
|
|
@@ -190,6 +212,13 @@ export function createRuntimeHandlers(deps) {
|
|
|
190
212
|
commandModelsList: __credits.commandModelsList,
|
|
191
213
|
commandModelsEstimate: __credits.commandModelsEstimate,
|
|
192
214
|
commandPricingQuote: __credits.commandPricingQuote,
|
|
215
|
+
commandChatSend: __chat.commandChatSend,
|
|
216
|
+
commandChatThreads: __chat.commandChatThreads,
|
|
217
|
+
commandChatModels: __chat.commandChatModels,
|
|
218
|
+
commandBrainSections: __brain.commandBrainSections,
|
|
219
|
+
commandBrainSuggest: __brain.commandBrainSuggest,
|
|
220
|
+
commandBrainPreflight: __brain.commandBrainPreflight,
|
|
221
|
+
commandBrainGraduate: __brain.commandBrainGraduate,
|
|
193
222
|
commandDataSummary: __telemetry.commandDataSummary,
|
|
194
223
|
commandTrackList: __telemetry.commandTrackList,
|
|
195
224
|
commandTrackAction: __telemetry.commandTrackAction,
|
|
@@ -203,5 +232,8 @@ export function createRuntimeHandlers(deps) {
|
|
|
203
232
|
commandAnalyticsPortfolio: __telemetry.commandAnalyticsPortfolio,
|
|
204
233
|
commandAnalyticsLeaderboard: __telemetry.commandAnalyticsLeaderboard,
|
|
205
234
|
commandPrompt: __prompt.commandPrompt,
|
|
235
|
+
commandVenueReadiness: __venue.commandVenueReadiness,
|
|
236
|
+
commandVenueSetup: __venue.commandVenueSetup,
|
|
237
|
+
commandVenueFund: __venue.commandVenueFund,
|
|
206
238
|
};
|
|
207
239
|
}
|