@thirdfy/agent-cli 0.1.2 → 0.1.4
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/README.md +64 -4
- package/bin/thirdfy-agent.mjs +534 -64
- package/package.json +1 -1
- package/src/utils/cli/intentNormalization.cjs +6 -0
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Use it to:
|
|
|
8
8
|
- run governance preflight checks
|
|
9
9
|
- queue execute-intent requests with idempotency
|
|
10
10
|
- poll intent status with stable JSON output for automation
|
|
11
|
+
- choose execution topology with one switch: `thirdfy`, `self`, or `hybrid`
|
|
12
|
+
- configure profile-driven defaults (`personal`, `builder`, `network`)
|
|
11
13
|
|
|
12
14
|
If you want the full developer docs, start here:
|
|
13
15
|
- [Thirdfy Agent CLI docs](https://docs.thirdfy.com/api/thirdfy-agent-cli)
|
|
@@ -34,25 +36,83 @@ npx @thirdfy/agent-cli --help
|
|
|
34
36
|
export THIRDFY_API_BASE="https://api.thirdfy.com"
|
|
35
37
|
export AGENT_API_KEY="..."
|
|
36
38
|
export THIRDFY_AUTH_TOKEN="..."
|
|
39
|
+
export THIRDFY_OWNER_SESSION_TOKEN="..."
|
|
37
40
|
|
|
38
41
|
thirdfy-agent actions --api-base "$THIRDFY_API_BASE" --agent-api-key "$AGENT_API_KEY" --json
|
|
42
|
+
thirdfy-agent profile init --profile personal --json
|
|
39
43
|
thirdfy-agent preflight --api-base "$THIRDFY_API_BASE" --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountIn":"1000000"}' --estimated-amount-usd 25 --json
|
|
40
|
-
thirdfy-agent run --api-base "$THIRDFY_API_BASE" --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountIn":"1000000"}' --estimated-amount-usd 25 --
|
|
44
|
+
thirdfy-agent run --api-base "$THIRDFY_API_BASE" --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountIn":"1000000"}' --estimated-amount-usd 25 --json
|
|
41
45
|
thirdfy-agent intent-status --api-base "$THIRDFY_API_BASE" --intent-id "<intentId>" --json
|
|
42
46
|
```
|
|
43
47
|
|
|
44
48
|
## Auth model
|
|
45
49
|
|
|
46
50
|
- `AGENT_API_KEY`: execution identity and policy-aware action access (`actions --agent-api-key`, `preflight`, `run`)
|
|
47
|
-
- `THIRDFY_AUTH_TOKEN`:
|
|
51
|
+
- `THIRDFY_AUTH_TOKEN`: Privy-native owner/account auth (`agent register`, `agent key *`, `delegation *`, `credentials *`, `credits balance`)
|
|
52
|
+
- `THIRDFY_OWNER_SESSION_TOKEN`: wallet-sign owner auth session (`agent register`, `agent key *`)
|
|
48
53
|
|
|
49
54
|
Execution-only workflows can run with only `AGENT_API_KEY` after onboarding is complete.
|
|
50
55
|
|
|
56
|
+
### Wallet-sign onboarding path
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
thirdfy-agent agent auth challenge --api-base "$THIRDFY_API_BASE" --agent-key "0xYOUR_AGENT_KEY" --json
|
|
60
|
+
# Sign challenge.message with your wallet, then:
|
|
61
|
+
thirdfy-agent agent auth verify --api-base "$THIRDFY_API_BASE" --challenge-id "<challengeId>" --signature "0x..." --agent-key "0xYOUR_AGENT_KEY" --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use the returned session token:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
thirdfy-agent agent register --api-base "$THIRDFY_API_BASE" --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --name "my-agent" --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Idempotency behavior (`run`)
|
|
71
|
+
|
|
72
|
+
- `run` auto-generates `idempotencyKey` by default when not provided.
|
|
73
|
+
- For deterministic retries across workers/restarts, pass your own stable `--idempotency-key`.
|
|
74
|
+
- `preflight` does not force idempotency keys.
|
|
75
|
+
|
|
76
|
+
## Run modes and profiles
|
|
77
|
+
|
|
78
|
+
`thirdfy-agent` supports one command surface with three run modes:
|
|
79
|
+
|
|
80
|
+
- `--run-mode thirdfy` -> Thirdfy-governed execute-intent rail (default for `profile=network`)
|
|
81
|
+
- `--run-mode self` -> self-custody rail (`build-tx` flow; default for `profile=personal`)
|
|
82
|
+
- `--run-mode hybrid` -> self-custody + governance mirror metadata (default for `profile=builder`)
|
|
83
|
+
|
|
84
|
+
Selection precedence:
|
|
85
|
+
|
|
86
|
+
1. `--run-mode`
|
|
87
|
+
2. `THIRDFY_RUN_MODE`
|
|
88
|
+
3. saved profile config (`~/.thirdfy/config.json`)
|
|
89
|
+
4. profile default
|
|
90
|
+
|
|
91
|
+
Profile commands:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
thirdfy-agent profile init --profile personal --json
|
|
95
|
+
thirdfy-agent profile use --profile builder --run-mode hybrid --json
|
|
96
|
+
thirdfy-agent whoami --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`profile use --profile <name>` resets run mode to that profile default unless `--run-mode` is explicitly provided.
|
|
100
|
+
|
|
101
|
+
## Jeff shorthand rail
|
|
102
|
+
|
|
103
|
+
Jeff-friendly aliases map directly to core commands:
|
|
104
|
+
|
|
105
|
+
- `thirdfy-agent jeff preflight ...` -> alias of `preflight`
|
|
106
|
+
- `thirdfy-agent jeff trade ...` -> alias of `run`
|
|
107
|
+
- `thirdfy-agent jeff status --intent-id ...` -> alias of `intent-status`
|
|
108
|
+
- `thirdfy-agent prompt "<text>"` -> captures prompt intent and suggests deterministic execution commands
|
|
109
|
+
|
|
51
110
|
## Command groups
|
|
52
111
|
|
|
53
112
|
- Discovery: `catalogs list`, `actions`
|
|
54
|
-
- Execution: `preflight`, `run`, `intent-status`
|
|
55
|
-
-
|
|
113
|
+
- Execution: `preflight`, `run`, `intent-status`, `jeff preflight`, `jeff trade`, `jeff status`
|
|
114
|
+
- Profiles: `profile init`, `profile use`, `profile show`, `whoami`
|
|
115
|
+
- Onboarding: `agent auth challenge`, `agent auth verify`, `agent register`, `agent key rotate`, `agent key revoke`
|
|
56
116
|
- Governance readiness: `delegation upsert`, `delegation status`, `credentials upsert`, `credentials status`
|
|
57
117
|
- Account: `credits balance`
|
|
58
118
|
|
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import fs from 'fs';
|
|
5
|
+
import os from 'os';
|
|
5
6
|
import path from 'path';
|
|
6
7
|
import { createRequire } from 'module';
|
|
7
8
|
|
|
@@ -16,6 +17,12 @@ const {
|
|
|
16
17
|
const DEFAULT_API_BASE = 'https://api.thirdfy.com';
|
|
17
18
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
18
19
|
const DEFAULT_CATALOG_CACHE_TTL_MS = 60_000;
|
|
20
|
+
const RUN_MODES = ['thirdfy', 'self', 'hybrid'];
|
|
21
|
+
const PROFILE_DEFAULTS = {
|
|
22
|
+
personal: 'self',
|
|
23
|
+
builder: 'hybrid',
|
|
24
|
+
network: 'thirdfy',
|
|
25
|
+
};
|
|
19
26
|
const catalogCache = new Map();
|
|
20
27
|
|
|
21
28
|
const rawArgv = process.argv.slice(2);
|
|
@@ -59,6 +66,11 @@ main().catch((error) => {
|
|
|
59
66
|
});
|
|
60
67
|
|
|
61
68
|
async function main() {
|
|
69
|
+
const profileState = resolveProfileState(globalFlags);
|
|
70
|
+
context.profile = profileState.profile;
|
|
71
|
+
context.runMode = profileState.runMode;
|
|
72
|
+
context.profileConfigPath = profileState.configPath;
|
|
73
|
+
|
|
62
74
|
if (globalFlags.version) {
|
|
63
75
|
printEnvelope({
|
|
64
76
|
success: true,
|
|
@@ -75,12 +87,43 @@ async function main() {
|
|
|
75
87
|
return;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
const capabilities = await negotiateCapabilities(context);
|
|
79
90
|
const commandKey = parsed.positionals.slice(0, 2).join(' ');
|
|
80
91
|
const commandKey3 = parsed.positionals.slice(0, 3).join(' ');
|
|
81
92
|
const subFlags = parsed.flags;
|
|
82
93
|
|
|
94
|
+
// Pure local profile/config commands should work offline.
|
|
95
|
+
switch (commandKey) {
|
|
96
|
+
case 'profile init':
|
|
97
|
+
await commandProfileInit(context, subFlags);
|
|
98
|
+
return;
|
|
99
|
+
case 'profile use':
|
|
100
|
+
await commandProfileUse(context, subFlags);
|
|
101
|
+
return;
|
|
102
|
+
case 'profile show':
|
|
103
|
+
await commandProfileShow(context, subFlags);
|
|
104
|
+
return;
|
|
105
|
+
case 'config get':
|
|
106
|
+
await commandProfileShow(context, subFlags);
|
|
107
|
+
return;
|
|
108
|
+
case 'whoami':
|
|
109
|
+
await commandProfileShow(context, subFlags);
|
|
110
|
+
return;
|
|
111
|
+
default:
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const capabilities = await negotiateCapabilities(context);
|
|
116
|
+
|
|
83
117
|
switch (commandKey) {
|
|
118
|
+
case 'jeff trade':
|
|
119
|
+
await commandRun(context, subFlags, capabilities);
|
|
120
|
+
return;
|
|
121
|
+
case 'jeff preflight':
|
|
122
|
+
await commandPreflight(context, subFlags, capabilities);
|
|
123
|
+
return;
|
|
124
|
+
case 'jeff status':
|
|
125
|
+
await commandIntentStatus(context, subFlags, capabilities);
|
|
126
|
+
return;
|
|
84
127
|
case 'catalogs list':
|
|
85
128
|
await commandCatalogsList(context, subFlags, capabilities);
|
|
86
129
|
return;
|
|
@@ -113,9 +156,20 @@ async function main() {
|
|
|
113
156
|
await commandAgentKeyRevoke(context, subFlags, capabilities);
|
|
114
157
|
return;
|
|
115
158
|
}
|
|
159
|
+
if (commandKey3 === 'agent auth challenge') {
|
|
160
|
+
await commandAgentAuthChallenge(context, subFlags, capabilities);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (commandKey3 === 'agent auth verify') {
|
|
164
|
+
await commandAgentAuthVerify(context, subFlags, capabilities);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
116
167
|
|
|
117
168
|
const rootCommand = parsed.positionals[0];
|
|
118
169
|
switch (rootCommand) {
|
|
170
|
+
case 'prompt':
|
|
171
|
+
await commandPrompt(context, subFlags, capabilities, parsed.positionals.slice(1));
|
|
172
|
+
return;
|
|
119
173
|
case 'actions':
|
|
120
174
|
await commandActions(context, subFlags, capabilities);
|
|
121
175
|
return;
|
|
@@ -228,6 +282,9 @@ async function commandCatalogsList(ctx, flags, capabilities) {
|
|
|
228
282
|
async function commandActions(ctx, flags, capabilities) {
|
|
229
283
|
const policyAware = Boolean(flags.agentApiKey);
|
|
230
284
|
const actions = applyActionFilters(await getCachedActionsCatalog(ctx, flags), flags);
|
|
285
|
+
const effectiveRunMode = getEffectiveRunMode(ctx, flags);
|
|
286
|
+
const chainIdFilter = Number(flags.chainId || 0) || undefined;
|
|
287
|
+
const chainSupport = resolveChainSupport(capabilities, chainIdFilter, effectiveRunMode);
|
|
231
288
|
printEnvelope({
|
|
232
289
|
success: true,
|
|
233
290
|
code: 'OK',
|
|
@@ -236,6 +293,9 @@ async function commandActions(ctx, flags, capabilities) {
|
|
|
236
293
|
meta: {
|
|
237
294
|
apiBase: ctx.apiBase,
|
|
238
295
|
policyAware,
|
|
296
|
+
runMode: effectiveRunMode,
|
|
297
|
+
profile: ctx.profile || null,
|
|
298
|
+
chainCompatibility: chainSupport,
|
|
239
299
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
240
300
|
},
|
|
241
301
|
});
|
|
@@ -243,23 +303,22 @@ async function commandActions(ctx, flags, capabilities) {
|
|
|
243
303
|
|
|
244
304
|
async function commandPreflight(ctx, flags, capabilities) {
|
|
245
305
|
const resolved = await resolveActionSelection(ctx, flags);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
const response = await apiPost(ctx, '/api/v1/agent/execute-intent', payload);
|
|
252
|
-
const normalized = normalizeIntentResponse(response);
|
|
306
|
+
const runMode = getEffectiveRunMode(ctx, flags);
|
|
307
|
+
enforceChainCompatibility(capabilities, flags, runMode);
|
|
308
|
+
const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved.resolvedAction);
|
|
309
|
+
const normalized = backendResult.normalized;
|
|
253
310
|
printEnvelope({
|
|
254
311
|
success: normalized.success,
|
|
255
312
|
code: normalized.success ? 'PREFLIGHT_OK' : 'PREFLIGHT_FAILED',
|
|
256
|
-
message:
|
|
313
|
+
message: backendResult.message,
|
|
257
314
|
data: normalized,
|
|
258
315
|
meta: {
|
|
259
316
|
apiBase: ctx.apiBase,
|
|
260
317
|
catalog: flags.catalog || null,
|
|
261
318
|
requestedAction: resolved.requestedAction,
|
|
262
319
|
resolvedAction: resolved.resolvedAction,
|
|
320
|
+
runMode,
|
|
321
|
+
profile: ctx.profile || null,
|
|
263
322
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
264
323
|
},
|
|
265
324
|
});
|
|
@@ -268,52 +327,25 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
268
327
|
|
|
269
328
|
async function commandRun(ctx, flags, capabilities) {
|
|
270
329
|
const resolved = await resolveActionSelection(ctx, flags);
|
|
330
|
+
const runMode = getEffectiveRunMode(ctx, flags);
|
|
331
|
+
enforceChainCompatibility(capabilities, flags, runMode);
|
|
271
332
|
const skipPreflight = Boolean(flags.skipPreflight);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
validationOnly: true,
|
|
275
|
-
forceIdempotency: false,
|
|
276
|
-
resolvedAction: resolved.resolvedAction,
|
|
277
|
-
});
|
|
278
|
-
const preflight = await apiPost(ctx, '/api/v1/agent/execute-intent', preflightPayload);
|
|
279
|
-
const preflightNormalized = normalizeIntentResponse(preflight);
|
|
280
|
-
const blockedCount = preflightNormalized.blocked || 0;
|
|
281
|
-
if (!preflightNormalized.success || blockedCount > 0) {
|
|
282
|
-
printEnvelope({
|
|
283
|
-
success: false,
|
|
284
|
-
code: 'PREFLIGHT_BLOCKED',
|
|
285
|
-
message: 'Preflight blocked execution',
|
|
286
|
-
data: preflightNormalized,
|
|
287
|
-
meta: {
|
|
288
|
-
apiBase: ctx.apiBase,
|
|
289
|
-
catalog: flags.catalog || null,
|
|
290
|
-
requestedAction: resolved.requestedAction,
|
|
291
|
-
resolvedAction: resolved.resolvedAction,
|
|
292
|
-
skippedPreflight: false,
|
|
293
|
-
},
|
|
294
|
-
});
|
|
295
|
-
process.exit(1);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const payload = buildIntentPayload(flags, {
|
|
301
|
-
validationOnly: false,
|
|
302
|
-
forceIdempotency: true,
|
|
303
|
-
resolvedAction: resolved.resolvedAction,
|
|
333
|
+
const backendResult = await runExecutionByMode(runMode, ctx, flags, resolved, {
|
|
334
|
+
skipPreflight,
|
|
304
335
|
});
|
|
305
|
-
const
|
|
306
|
-
const normalized = normalizeIntentResponse(response);
|
|
336
|
+
const normalized = backendResult.normalized;
|
|
307
337
|
printEnvelope({
|
|
308
338
|
success: normalized.success,
|
|
309
|
-
code:
|
|
310
|
-
message:
|
|
339
|
+
code: backendResult.code,
|
|
340
|
+
message: backendResult.message,
|
|
311
341
|
data: normalized,
|
|
312
342
|
meta: {
|
|
313
343
|
apiBase: ctx.apiBase,
|
|
314
344
|
requestedAction: resolved.requestedAction,
|
|
315
345
|
resolvedAction: resolved.resolvedAction,
|
|
316
|
-
idempotencyKey:
|
|
346
|
+
idempotencyKey: backendResult.idempotencyKey || null,
|
|
347
|
+
runMode,
|
|
348
|
+
profile: ctx.profile || null,
|
|
317
349
|
skippedPreflight: skipPreflight,
|
|
318
350
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
319
351
|
},
|
|
@@ -483,7 +515,10 @@ async function commandCredentialsStatus(ctx, flags, capabilities) {
|
|
|
483
515
|
}
|
|
484
516
|
|
|
485
517
|
async function commandAgentRegister(ctx, flags, capabilities) {
|
|
486
|
-
const
|
|
518
|
+
const ownerHeaders = buildOwnerAuthHeaders(
|
|
519
|
+
flags,
|
|
520
|
+
'Missing owner auth for agent register: provide --auth-token (THIRDFY_AUTH_TOKEN) or --owner-session-token'
|
|
521
|
+
);
|
|
487
522
|
const payload = {
|
|
488
523
|
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
489
524
|
name: requireFlag(flags, 'name', 'Missing --name'),
|
|
@@ -501,9 +536,10 @@ async function commandAgentRegister(ctx, flags, capabilities) {
|
|
|
501
536
|
if (flags.strategy) payload.strategy = parseJsonFlag(flags, 'strategy');
|
|
502
537
|
if (flags.apiKeyEnvelopePublicKey) payload.apiKeyEnvelopePublicKey = String(flags.apiKeyEnvelopePublicKey);
|
|
503
538
|
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
539
|
+
const route = ownerHeaders['x-owner-session-token']
|
|
540
|
+
? '/api/v1/agent/onboarding/register-with-wallet'
|
|
541
|
+
: '/api/v1/agent/onboarding/register-with-privy';
|
|
542
|
+
const response = await apiPost(ctx, route, payload, ownerHeaders);
|
|
507
543
|
printEnvelope({
|
|
508
544
|
success: true,
|
|
509
545
|
code: 'AGENT_REGISTERED',
|
|
@@ -517,12 +553,13 @@ async function commandAgentRegister(ctx, flags, capabilities) {
|
|
|
517
553
|
}
|
|
518
554
|
|
|
519
555
|
async function commandAgentKeyRotate(ctx, flags, capabilities) {
|
|
520
|
-
const
|
|
556
|
+
const ownerHeaders = buildOwnerAuthHeaders(
|
|
557
|
+
flags,
|
|
558
|
+
'Missing owner auth for agent key rotate: provide --auth-token (THIRDFY_AUTH_TOKEN) or --owner-session-token'
|
|
559
|
+
);
|
|
521
560
|
const payload = {};
|
|
522
561
|
if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
|
|
523
|
-
const response = await apiPost(ctx, '/api/v1/agent/onboarding/me/api-key/rotate', payload,
|
|
524
|
-
Authorization: `Bearer ${authToken}`,
|
|
525
|
-
});
|
|
562
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/me/api-key/rotate', payload, ownerHeaders);
|
|
526
563
|
printEnvelope({
|
|
527
564
|
success: true,
|
|
528
565
|
code: 'AGENT_KEY_ROTATED',
|
|
@@ -536,12 +573,13 @@ async function commandAgentKeyRotate(ctx, flags, capabilities) {
|
|
|
536
573
|
}
|
|
537
574
|
|
|
538
575
|
async function commandAgentKeyRevoke(ctx, flags, capabilities) {
|
|
539
|
-
const
|
|
576
|
+
const ownerHeaders = buildOwnerAuthHeaders(
|
|
577
|
+
flags,
|
|
578
|
+
'Missing owner auth for agent key revoke: provide --auth-token (THIRDFY_AUTH_TOKEN) or --owner-session-token'
|
|
579
|
+
);
|
|
540
580
|
const payload = {};
|
|
541
581
|
if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
|
|
542
|
-
const response = await apiPost(ctx, '/api/v1/agent/onboarding/me/api-key/revoke', payload,
|
|
543
|
-
Authorization: `Bearer ${authToken}`,
|
|
544
|
-
});
|
|
582
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/me/api-key/revoke', payload, ownerHeaders);
|
|
545
583
|
printEnvelope({
|
|
546
584
|
success: true,
|
|
547
585
|
code: 'AGENT_KEY_REVOKED',
|
|
@@ -554,6 +592,407 @@ async function commandAgentKeyRevoke(ctx, flags, capabilities) {
|
|
|
554
592
|
});
|
|
555
593
|
}
|
|
556
594
|
|
|
595
|
+
async function commandAgentAuthChallenge(ctx, flags, capabilities) {
|
|
596
|
+
const payload = {
|
|
597
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
598
|
+
chainId: Number(flags.chainId || 8453),
|
|
599
|
+
};
|
|
600
|
+
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
601
|
+
|
|
602
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/challenge', payload);
|
|
603
|
+
printEnvelope({
|
|
604
|
+
success: true,
|
|
605
|
+
code: 'OWNER_CHALLENGE_CREATED',
|
|
606
|
+
message: 'Wallet challenge created',
|
|
607
|
+
data: response,
|
|
608
|
+
meta: {
|
|
609
|
+
apiBase: ctx.apiBase,
|
|
610
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function commandAgentAuthVerify(ctx, flags, capabilities) {
|
|
616
|
+
const payload = {
|
|
617
|
+
challengeId: requireFlag(flags, 'challengeId', 'Missing --challenge-id'),
|
|
618
|
+
signature: requireFlag(flags, 'signature', 'Missing --signature'),
|
|
619
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
620
|
+
};
|
|
621
|
+
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
622
|
+
|
|
623
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', payload);
|
|
624
|
+
printEnvelope({
|
|
625
|
+
success: true,
|
|
626
|
+
code: 'OWNER_VERIFIED',
|
|
627
|
+
message: 'Wallet challenge verified',
|
|
628
|
+
data: response,
|
|
629
|
+
meta: {
|
|
630
|
+
apiBase: ctx.apiBase,
|
|
631
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function commandPrompt(ctx, flags, capabilities, promptTokens) {
|
|
637
|
+
const prompt = String(flags.prompt || promptTokens.join(' ') || '').trim();
|
|
638
|
+
if (!prompt) {
|
|
639
|
+
throw new Error('Missing prompt text. Use: thirdfy-agent prompt "<text>"');
|
|
640
|
+
}
|
|
641
|
+
// Jeff rail: deterministic wrapper around existing run contract.
|
|
642
|
+
printEnvelope({
|
|
643
|
+
success: true,
|
|
644
|
+
code: 'PROMPT_RECEIVED',
|
|
645
|
+
message: 'Prompt captured; execute with `jeff trade` or `run` using --run-mode.',
|
|
646
|
+
data: {
|
|
647
|
+
prompt,
|
|
648
|
+
suggestedCommands: [
|
|
649
|
+
'thirdfy-agent jeff trade --run-mode self --agent-api-key ... --action ... --params ...',
|
|
650
|
+
'thirdfy-agent jeff trade --run-mode hybrid --agent-api-key ... --action ... --params ...',
|
|
651
|
+
],
|
|
652
|
+
},
|
|
653
|
+
meta: {
|
|
654
|
+
apiBase: ctx.apiBase,
|
|
655
|
+
profile: ctx.profile || null,
|
|
656
|
+
runMode: getEffectiveRunMode(ctx, flags),
|
|
657
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async function commandProfileInit(ctx, flags) {
|
|
663
|
+
const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
|
|
664
|
+
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
665
|
+
persistProfileConfig({
|
|
666
|
+
profile,
|
|
667
|
+
runMode,
|
|
668
|
+
});
|
|
669
|
+
ctx.profile = profile;
|
|
670
|
+
ctx.runMode = runMode;
|
|
671
|
+
printEnvelope({
|
|
672
|
+
success: true,
|
|
673
|
+
code: 'PROFILE_INITIALIZED',
|
|
674
|
+
message: 'Profile initialized',
|
|
675
|
+
data: {
|
|
676
|
+
profile,
|
|
677
|
+
runMode,
|
|
678
|
+
configPath: getProfileConfigPath(),
|
|
679
|
+
},
|
|
680
|
+
meta: { apiBase: ctx.apiBase },
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async function commandProfileUse(ctx, flags) {
|
|
685
|
+
const current = loadProfileConfig();
|
|
686
|
+
const profile = normalizeProfileName(flags.profile || flags.name || current.profile || 'personal');
|
|
687
|
+
// `profile use` intentionally re-applies profile defaults unless caller pins a mode.
|
|
688
|
+
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
689
|
+
persistProfileConfig({
|
|
690
|
+
profile,
|
|
691
|
+
runMode,
|
|
692
|
+
});
|
|
693
|
+
ctx.profile = profile;
|
|
694
|
+
ctx.runMode = runMode;
|
|
695
|
+
printEnvelope({
|
|
696
|
+
success: true,
|
|
697
|
+
code: 'PROFILE_UPDATED',
|
|
698
|
+
message: 'Profile updated',
|
|
699
|
+
data: {
|
|
700
|
+
profile,
|
|
701
|
+
runMode,
|
|
702
|
+
defaults: PROFILE_DEFAULTS,
|
|
703
|
+
configPath: getProfileConfigPath(),
|
|
704
|
+
},
|
|
705
|
+
meta: { apiBase: ctx.apiBase },
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function commandProfileShow(ctx, _flags) {
|
|
710
|
+
const config = loadProfileConfig();
|
|
711
|
+
printEnvelope({
|
|
712
|
+
success: true,
|
|
713
|
+
code: 'PROFILE_OK',
|
|
714
|
+
message: 'Profile configuration loaded',
|
|
715
|
+
data: {
|
|
716
|
+
profile: ctx.profile || config.profile || 'personal',
|
|
717
|
+
runMode: ctx.runMode || config.runMode || 'thirdfy',
|
|
718
|
+
profileDefaults: PROFILE_DEFAULTS,
|
|
719
|
+
configPath: getProfileConfigPath(),
|
|
720
|
+
config,
|
|
721
|
+
},
|
|
722
|
+
meta: { apiBase: ctx.apiBase },
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function resolveProfileState(flags) {
|
|
727
|
+
const persisted = loadProfileConfig();
|
|
728
|
+
const profile = normalizeProfileName(flags.profile || process.env.THIRDFY_PROFILE || persisted.profile || 'personal');
|
|
729
|
+
const runMode = normalizeRunMode(
|
|
730
|
+
flags.runMode ||
|
|
731
|
+
process.env.THIRDFY_RUN_MODE ||
|
|
732
|
+
persisted.runMode ||
|
|
733
|
+
PROFILE_DEFAULTS[profile] ||
|
|
734
|
+
'thirdfy'
|
|
735
|
+
);
|
|
736
|
+
return {
|
|
737
|
+
profile,
|
|
738
|
+
runMode,
|
|
739
|
+
configPath: getProfileConfigPath(),
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function normalizeProfileName(value) {
|
|
744
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
745
|
+
if (normalized in PROFILE_DEFAULTS) return normalized;
|
|
746
|
+
throw new Error(`Unsupported profile "${value}". Supported: ${Object.keys(PROFILE_DEFAULTS).join(', ')}`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function normalizeRunMode(value) {
|
|
750
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
751
|
+
if (!normalized) return 'thirdfy';
|
|
752
|
+
if (RUN_MODES.includes(normalized)) return normalized;
|
|
753
|
+
throw new Error(`Unsupported --run-mode "${value}". Supported: ${RUN_MODES.join(', ')}`);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function getProfileConfigPath() {
|
|
757
|
+
return path.join(os.homedir(), '.thirdfy', 'config.json');
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function loadProfileConfig() {
|
|
761
|
+
try {
|
|
762
|
+
const raw = fs.readFileSync(getProfileConfigPath(), 'utf8');
|
|
763
|
+
const parsed = JSON.parse(raw);
|
|
764
|
+
if (!parsed || typeof parsed !== 'object') return {};
|
|
765
|
+
return parsed;
|
|
766
|
+
} catch {
|
|
767
|
+
return {};
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function persistProfileConfig(config) {
|
|
772
|
+
const configPath = getProfileConfigPath();
|
|
773
|
+
const dirPath = path.dirname(configPath);
|
|
774
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
775
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function getEffectiveRunMode(ctx, flags) {
|
|
779
|
+
if (flags.runMode) return normalizeRunMode(flags.runMode);
|
|
780
|
+
return normalizeRunMode(ctx.runMode || 'thirdfy');
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function resolveChainSupport(capabilities, chainId, runMode) {
|
|
784
|
+
const chains = Array.isArray(capabilities?.chains) ? capabilities.chains : [];
|
|
785
|
+
if (!chainId) return { chainId: null, supported: null };
|
|
786
|
+
const match = chains.find((entry) => Number(entry.chainId) === Number(chainId));
|
|
787
|
+
if (!match) {
|
|
788
|
+
return { chainId, supported: false, reason: 'chain_not_advertised_by_api' };
|
|
789
|
+
}
|
|
790
|
+
const lanes = Array.isArray(match.supportedExecutionLanes) ? match.supportedExecutionLanes : [];
|
|
791
|
+
return {
|
|
792
|
+
chainId,
|
|
793
|
+
supported: lanes.includes(runMode),
|
|
794
|
+
runMode,
|
|
795
|
+
chainKey: match.chainKey || null,
|
|
796
|
+
displayName: match.displayName || null,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function enforceChainCompatibility(capabilities, flags, runMode) {
|
|
801
|
+
const chainId = Number(flags.chainId || 0) || undefined;
|
|
802
|
+
if (!chainId) return;
|
|
803
|
+
if (!capabilities || !Array.isArray(capabilities.chains)) return;
|
|
804
|
+
const support = resolveChainSupport(capabilities, chainId, runMode);
|
|
805
|
+
if (support?.supported === false) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`Chain ${chainId} is not advertised as compatible with run-mode=${runMode}. Check \`thirdfy-agent actions --chain-id ${chainId}\`.`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async function runPreflightByMode(runMode, ctx, flags, resolvedAction) {
|
|
813
|
+
if (runMode === 'self') {
|
|
814
|
+
const normalized = await executeSelfRun(
|
|
815
|
+
ctx,
|
|
816
|
+
flags,
|
|
817
|
+
{ resolvedAction },
|
|
818
|
+
{ skipPreflight: false }
|
|
819
|
+
);
|
|
820
|
+
const blockedByGovernance = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
821
|
+
return {
|
|
822
|
+
message: blockedByGovernance
|
|
823
|
+
? 'Self preflight blocked by governance'
|
|
824
|
+
: normalized.success
|
|
825
|
+
? 'Self preflight completed (unsigned transaction ready)'
|
|
826
|
+
: 'Self preflight failed',
|
|
827
|
+
normalized,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
const payload = buildIntentPayload(flags, {
|
|
831
|
+
validationOnly: true,
|
|
832
|
+
forceIdempotency: false,
|
|
833
|
+
resolvedAction,
|
|
834
|
+
runMode,
|
|
835
|
+
mirrorOnly: runMode === 'hybrid',
|
|
836
|
+
});
|
|
837
|
+
const response = await apiPost(ctx, '/api/v1/agent/execute-intent', payload);
|
|
838
|
+
const normalized = normalizeIntentResponse(response);
|
|
839
|
+
return {
|
|
840
|
+
message: normalized.success
|
|
841
|
+
? runMode === 'hybrid'
|
|
842
|
+
? 'Hybrid mirror preflight completed'
|
|
843
|
+
: 'Preflight completed'
|
|
844
|
+
: runMode === 'hybrid'
|
|
845
|
+
? 'Hybrid mirror preflight failed'
|
|
846
|
+
: 'Preflight failed',
|
|
847
|
+
normalized,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
|
|
852
|
+
if (runMode === 'thirdfy') {
|
|
853
|
+
const normalized = await executeThirdfyRun(ctx, flags, resolved, options);
|
|
854
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
855
|
+
return {
|
|
856
|
+
code: normalized.success ? 'RUN_QUEUED' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'RUN_FAILED',
|
|
857
|
+
message: normalized.success
|
|
858
|
+
? 'Execution intent queued'
|
|
859
|
+
: preflightBlocked
|
|
860
|
+
? 'Preflight blocked execution'
|
|
861
|
+
: 'Execution failed',
|
|
862
|
+
idempotencyKey: normalized.idempotencyKey || null,
|
|
863
|
+
normalized,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
if (runMode === 'self') {
|
|
867
|
+
const normalized = await executeSelfRun(ctx, flags, resolved, options);
|
|
868
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
869
|
+
return {
|
|
870
|
+
code: normalized.success
|
|
871
|
+
? 'SELF_UNSIGNED_TX_READY'
|
|
872
|
+
: preflightBlocked
|
|
873
|
+
? 'PREFLIGHT_BLOCKED'
|
|
874
|
+
: 'SELF_EXECUTION_FAILED',
|
|
875
|
+
message: normalized.success
|
|
876
|
+
? 'Unsigned transaction prepared for self-custody execution'
|
|
877
|
+
: preflightBlocked
|
|
878
|
+
? 'Preflight blocked self execution'
|
|
879
|
+
: 'Self-custody execution failed',
|
|
880
|
+
idempotencyKey: null,
|
|
881
|
+
normalized,
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const normalized = await executeHybridRun(ctx, flags, resolved, options);
|
|
885
|
+
const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
|
|
886
|
+
return {
|
|
887
|
+
code: normalized.success ? 'HYBRID_READY' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'HYBRID_FAILED',
|
|
888
|
+
message: normalized.success
|
|
889
|
+
? 'Hybrid execution prepared (self tx + mirror preflight)'
|
|
890
|
+
: preflightBlocked
|
|
891
|
+
? 'Preflight blocked hybrid execution'
|
|
892
|
+
: 'Hybrid execution failed',
|
|
893
|
+
idempotencyKey: normalized.idempotencyKey || null,
|
|
894
|
+
normalized,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
async function executeThirdfyRun(ctx, flags, resolved, options) {
|
|
899
|
+
const skipPreflight = Boolean(options?.skipPreflight);
|
|
900
|
+
if (!skipPreflight) {
|
|
901
|
+
const preflightPayload = buildIntentPayload(flags, {
|
|
902
|
+
validationOnly: true,
|
|
903
|
+
forceIdempotency: false,
|
|
904
|
+
resolvedAction: resolved.resolvedAction,
|
|
905
|
+
runMode: 'thirdfy',
|
|
906
|
+
mirrorOnly: false,
|
|
907
|
+
});
|
|
908
|
+
const preflight = await apiPost(ctx, '/api/v1/agent/execute-intent', preflightPayload);
|
|
909
|
+
const preflightNormalized = normalizeIntentResponse(preflight);
|
|
910
|
+
const blockedCount = preflightNormalized.blocked || 0;
|
|
911
|
+
if (!preflightNormalized.success || blockedCount > 0) {
|
|
912
|
+
preflightNormalized.success = false;
|
|
913
|
+
preflightNormalized.error = preflightNormalized.error || 'Preflight blocked execution';
|
|
914
|
+
preflightNormalized.preflightBlocked = true;
|
|
915
|
+
return preflightNormalized;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
const payload = buildIntentPayload(flags, {
|
|
919
|
+
validationOnly: false,
|
|
920
|
+
forceIdempotency: true,
|
|
921
|
+
resolvedAction: resolved.resolvedAction,
|
|
922
|
+
runMode: 'thirdfy',
|
|
923
|
+
mirrorOnly: false,
|
|
924
|
+
});
|
|
925
|
+
const response = await apiPost(ctx, '/api/v1/agent/execute-intent', payload);
|
|
926
|
+
const normalized = normalizeIntentResponse(response);
|
|
927
|
+
normalized.idempotencyKey = payload.idempotencyKey;
|
|
928
|
+
return normalized;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async function executeSelfRun(ctx, flags, resolved, options) {
|
|
932
|
+
const skipPreflight = Boolean(options?.skipPreflight);
|
|
933
|
+
if (!skipPreflight) {
|
|
934
|
+
const preflightPayload = buildIntentPayload(flags, {
|
|
935
|
+
validationOnly: true,
|
|
936
|
+
forceIdempotency: false,
|
|
937
|
+
resolvedAction: resolved.resolvedAction,
|
|
938
|
+
runMode: 'self',
|
|
939
|
+
mirrorOnly: false,
|
|
940
|
+
});
|
|
941
|
+
const preflight = await apiPost(ctx, '/api/v1/agent/execute-intent', preflightPayload);
|
|
942
|
+
const preflightNormalized = normalizeIntentResponse(preflight);
|
|
943
|
+
const blockedCount = preflightNormalized.blocked || 0;
|
|
944
|
+
if (!preflightNormalized.success || blockedCount > 0) {
|
|
945
|
+
preflightNormalized.success = false;
|
|
946
|
+
preflightNormalized.error = preflightNormalized.error || 'Preflight blocked self execution';
|
|
947
|
+
preflightNormalized.preflightBlocked = true;
|
|
948
|
+
return preflightNormalized;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const buildTxPayload = buildBuildTxPayload(flags, resolved.resolvedAction);
|
|
952
|
+
const buildTx = await apiPost(ctx, '/api/v1/agent/build-tx', buildTxPayload);
|
|
953
|
+
return normalizeIntentResponse({
|
|
954
|
+
...buildTx,
|
|
955
|
+
status: buildTx?.status || 'ready',
|
|
956
|
+
mode: buildTx?.mode || 'self',
|
|
957
|
+
unsignedTx: buildTx?.unsignedTx || null,
|
|
958
|
+
estimatedGas: buildTx?.estimatedGas || null,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function executeHybridRun(ctx, flags, resolved, options) {
|
|
963
|
+
const selfResult = await executeSelfRun(ctx, flags, resolved, options);
|
|
964
|
+
if (!selfResult.success) return selfResult;
|
|
965
|
+
|
|
966
|
+
const mirrorPayload = buildIntentPayload(flags, {
|
|
967
|
+
validationOnly: true,
|
|
968
|
+
forceIdempotency: true,
|
|
969
|
+
resolvedAction: resolved.resolvedAction,
|
|
970
|
+
runMode: 'hybrid',
|
|
971
|
+
mirrorOnly: true,
|
|
972
|
+
});
|
|
973
|
+
const mirrorResponse = await apiPost(ctx, '/api/v1/agent/execute-intent', mirrorPayload);
|
|
974
|
+
const mirrorNormalized = normalizeIntentResponse(mirrorResponse);
|
|
975
|
+
return normalizeIntentResponse({
|
|
976
|
+
success: Boolean(selfResult.success && mirrorNormalized.success),
|
|
977
|
+
status: mirrorNormalized.status || 'ready',
|
|
978
|
+
mode: 'hybrid',
|
|
979
|
+
idempotencyKey: mirrorPayload.idempotencyKey,
|
|
980
|
+
self: selfResult,
|
|
981
|
+
mirror: mirrorNormalized,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function buildBuildTxPayload(flags, resolvedAction) {
|
|
986
|
+
const payload = {
|
|
987
|
+
agentApiKey: requireFlag(flags, 'agentApiKey', 'Missing --agent-api-key'),
|
|
988
|
+
action: String(resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
|
|
989
|
+
params: parseJsonFlag(flags, 'params'),
|
|
990
|
+
};
|
|
991
|
+
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
992
|
+
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
993
|
+
return payload;
|
|
994
|
+
}
|
|
995
|
+
|
|
557
996
|
function buildIntentPayload(flags, options) {
|
|
558
997
|
const payload = {
|
|
559
998
|
agentApiKey: requireFlag(flags, 'agentApiKey', 'Missing --agent-api-key'),
|
|
@@ -565,6 +1004,12 @@ function buildIntentPayload(flags, options) {
|
|
|
565
1004
|
if (flags.estimatedAmountUsd) payload.estimatedAmountUsd = Number(flags.estimatedAmountUsd);
|
|
566
1005
|
if (flags.userDid) payload.userDid = String(flags.userDid);
|
|
567
1006
|
if (options.validationOnly) payload.executionLane = 'validation_only';
|
|
1007
|
+
if (options.runMode) {
|
|
1008
|
+
payload.executionStrategy = {
|
|
1009
|
+
runMode: String(options.runMode),
|
|
1010
|
+
mirrorOnly: Boolean(options.mirrorOnly),
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
568
1013
|
if (options.forceIdempotency) {
|
|
569
1014
|
payload.idempotencyKey = String(flags.idempotencyKey || `cli:${payload.action}:${Date.now()}:${randomUUID()}`);
|
|
570
1015
|
} else if (flags.idempotencyKey) {
|
|
@@ -687,6 +1132,18 @@ function getAuthToken(flags, missingMessage) {
|
|
|
687
1132
|
return authToken;
|
|
688
1133
|
}
|
|
689
1134
|
|
|
1135
|
+
function buildOwnerAuthHeaders(flags, missingMessage) {
|
|
1136
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
1137
|
+
const ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
1138
|
+
if (!authToken && !ownerSessionToken) {
|
|
1139
|
+
throw new Error(missingMessage);
|
|
1140
|
+
}
|
|
1141
|
+
if (authToken) {
|
|
1142
|
+
return { Authorization: `Bearer ${authToken}` };
|
|
1143
|
+
}
|
|
1144
|
+
return { 'x-owner-session-token': ownerSessionToken };
|
|
1145
|
+
}
|
|
1146
|
+
|
|
690
1147
|
function extractActions(response) {
|
|
691
1148
|
if (Array.isArray(response)) return response;
|
|
692
1149
|
if (Array.isArray(response.actions)) return response.actions;
|
|
@@ -787,24 +1244,37 @@ function printHelp() {
|
|
|
787
1244
|
thirdfy-agent ${CLI_VERSION}
|
|
788
1245
|
|
|
789
1246
|
Core commands:
|
|
1247
|
+
thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--json]
|
|
1248
|
+
thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid] [--json]
|
|
1249
|
+
thirdfy-agent profile show [--json]
|
|
1250
|
+
thirdfy-agent whoami [--json]
|
|
790
1251
|
thirdfy-agent catalogs list [--json]
|
|
791
|
-
thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--json]
|
|
1252
|
+
thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
|
|
792
1253
|
thirdfy-agent delegation upsert --user-did <did> --agent-key <key> --wallet-address <addr> [--json]
|
|
793
1254
|
thirdfy-agent delegation status --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
794
1255
|
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
|
|
795
1256
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
|
796
|
-
thirdfy-agent agent
|
|
797
|
-
thirdfy-agent agent
|
|
798
|
-
thirdfy-agent agent key
|
|
799
|
-
thirdfy-agent
|
|
800
|
-
thirdfy-agent
|
|
1257
|
+
thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
|
|
1258
|
+
thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
|
|
1259
|
+
thirdfy-agent agent register --agent-key <key> --name <name> [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1260
|
+
thirdfy-agent agent key rotate [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1261
|
+
thirdfy-agent agent key revoke [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1262
|
+
thirdfy-agent preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1263
|
+
thirdfy-agent run --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--idempotency-key <key>] [--json]
|
|
1264
|
+
thirdfy-agent jeff preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1265
|
+
thirdfy-agent jeff trade --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1266
|
+
thirdfy-agent jeff status --intent-id <id> [--json]
|
|
1267
|
+
thirdfy-agent prompt "<natural language prompt>" [--json]
|
|
801
1268
|
thirdfy-agent intent-status --intent-id <id> [--json]
|
|
802
1269
|
thirdfy-agent credits balance --auth-token <token> [--json]
|
|
803
1270
|
|
|
804
1271
|
Global flags:
|
|
805
1272
|
--api-base <url> default: https://api.thirdfy.com
|
|
1273
|
+
--run-mode <mode> thirdfy | self | hybrid
|
|
1274
|
+
--profile <name> personal | builder | network
|
|
806
1275
|
--env <file> load env vars from file
|
|
807
1276
|
--timeout <ms> request timeout
|
|
1277
|
+
--owner-session-token <token> owner session token (or THIRDFY_OWNER_SESSION_TOKEN)
|
|
808
1278
|
--json stable machine-readable output
|
|
809
1279
|
--verbose print adapter diagnostics
|
|
810
1280
|
--version print version
|
package/package.json
CHANGED
|
@@ -30,6 +30,12 @@ function normalizeIntentResponse(response) {
|
|
|
30
30
|
results,
|
|
31
31
|
blockedByReason: summarizeBlockedReasons(results),
|
|
32
32
|
error: response?.error || null,
|
|
33
|
+
mode: response?.mode || null,
|
|
34
|
+
unsignedTx: response?.unsignedTx || null,
|
|
35
|
+
estimatedGas: response?.estimatedGas || null,
|
|
36
|
+
idempotencyKey: response?.idempotencyKey || null,
|
|
37
|
+
mirror: response?.mirror || null,
|
|
38
|
+
self: response?.self || null,
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
41
|
|