@thirdfy/agent-cli 0.1.6 → 0.1.7
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 +105 -5
- package/bin/thirdfy-agent.mjs +959 -19
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -42,8 +42,8 @@ export THIRDFY_OWNER_SESSION_TOKEN="..."
|
|
|
42
42
|
|
|
43
43
|
thirdfy-agent actions --agent-api-key "$AGENT_API_KEY" --json
|
|
44
44
|
thirdfy-agent profile init --profile personal --json
|
|
45
|
-
thirdfy-agent preflight --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","
|
|
46
|
-
thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","
|
|
45
|
+
thirdfy-agent preflight --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --estimated-amount-usd 25 --json
|
|
46
|
+
thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --estimated-amount-usd 25 --json
|
|
47
47
|
thirdfy-agent intent-status --intent-id "<intentId>" --json
|
|
48
48
|
```
|
|
49
49
|
|
|
@@ -51,12 +51,37 @@ By default, the CLI targets production (`https://api.thirdfy.com`). Use `--api-b
|
|
|
51
51
|
|
|
52
52
|
## Auth model
|
|
53
53
|
|
|
54
|
-
- `AGENT_API_KEY`: execution identity
|
|
55
|
-
- `THIRDFY_AUTH_TOKEN`:
|
|
56
|
-
- `THIRDFY_OWNER_SESSION_TOKEN`: wallet-sign owner
|
|
54
|
+
- `AGENT_API_KEY`: execution identity for `preflight`, `run`, `self-exec`, and policy-aware discovery.
|
|
55
|
+
- `THIRDFY_AUTH_TOKEN`: owner/account auth for onboarding and delegation commands.
|
|
56
|
+
- `THIRDFY_OWNER_SESSION_TOKEN`: wallet-sign owner session for onboarding and owner operations.
|
|
57
|
+
|
|
58
|
+
CLI precedence now supports login/config defaults:
|
|
59
|
+
|
|
60
|
+
1. explicit flag
|
|
61
|
+
2. environment variable
|
|
62
|
+
3. `~/.thirdfy/config.json` (from `thirdfy-agent login` / `config set`)
|
|
57
63
|
|
|
58
64
|
Execution-only workflows can run with only `AGENT_API_KEY` after onboarding is complete.
|
|
59
65
|
|
|
66
|
+
### Login / logout / config UX
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Store auth defaults once
|
|
70
|
+
thirdfy-agent login --auth-token "$THIRDFY_AUTH_TOKEN" --agent-api-key "$AGENT_API_KEY" --json
|
|
71
|
+
|
|
72
|
+
# Inspect config
|
|
73
|
+
thirdfy-agent whoami --json
|
|
74
|
+
thirdfy-agent config set --key runMode --value self --json
|
|
75
|
+
|
|
76
|
+
# Local-only logout (default)
|
|
77
|
+
thirdfy-agent logout --json
|
|
78
|
+
|
|
79
|
+
# Local clear + server owner-session revoke (if present)
|
|
80
|
+
thirdfy-agent logout --all --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`logout --all` is provider-agnostic: it attempts owner-session revocation when a session token exists, then always clears local credentials.
|
|
84
|
+
|
|
60
85
|
### Wallet-sign onboarding path
|
|
61
86
|
|
|
62
87
|
```bash
|
|
@@ -85,6 +110,12 @@ thirdfy-agent agent register --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN
|
|
|
85
110
|
- `--run-mode self` -> self-custody rail (`build-tx` flow; default for `profile=personal`)
|
|
86
111
|
- `--run-mode hybrid` -> self-custody + governance mirror metadata (default for `profile=builder`)
|
|
87
112
|
|
|
113
|
+
Auth expectations by mode:
|
|
114
|
+
|
|
115
|
+
- `self`: requires execution identity (`agentApiKey`) today; keyless self lane is blocked with deterministic `SELF_OPEN_DISABLED`.
|
|
116
|
+
- `hybrid`: requires execution identity and governed mirror semantics.
|
|
117
|
+
- `thirdfy`: requires execution identity and delegated governance path.
|
|
118
|
+
|
|
88
119
|
Selection precedence:
|
|
89
120
|
|
|
90
121
|
1. `--run-mode`
|
|
@@ -102,6 +133,28 @@ thirdfy-agent whoami --json
|
|
|
102
133
|
|
|
103
134
|
`profile use --profile <name>` resets run mode to that profile default unless `--run-mode` is explicitly provided.
|
|
104
135
|
|
|
136
|
+
## Swap amount contract (agent-safe)
|
|
137
|
+
|
|
138
|
+
For `--action swap`, CLI enforces an explicit amount unit contract:
|
|
139
|
+
|
|
140
|
+
- Provide exactly one of:
|
|
141
|
+
- `amountInRaw` (base units integer string), or
|
|
142
|
+
- `amountInHuman` + `tokenInDecimals` (CLI converts to `amountInRaw`)
|
|
143
|
+
- Legacy `amountIn` is accepted only as raw integer compatibility input.
|
|
144
|
+
- Ambiguous payloads (both/none) fail with `AMOUNT_UNIT_AMBIGUOUS`.
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Raw/base units
|
|
150
|
+
thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap \
|
|
151
|
+
--params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","chainId":8453}' --json
|
|
152
|
+
|
|
153
|
+
# Human units + decimals
|
|
154
|
+
thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap \
|
|
155
|
+
--params '{"tokenIn":"0x...","tokenOut":"0x...","amountInHuman":"1.5","tokenInDecimals":6,"chainId":8453}' --json
|
|
156
|
+
```
|
|
157
|
+
|
|
105
158
|
## Jeff shorthand rail
|
|
106
159
|
|
|
107
160
|
Jeff-friendly aliases map directly to core commands:
|
|
@@ -118,6 +171,8 @@ Jeff-friendly aliases map directly to core commands:
|
|
|
118
171
|
- Profiles: `profile init`, `profile use`, `profile show`, `whoami`
|
|
119
172
|
- Onboarding: `agent auth challenge`, `agent auth verify`, `agent register`, `agent key rotate`, `agent key revoke`
|
|
120
173
|
- Governance readiness: `delegation create`, `delegation activate`, `delegation status`, `credentials upsert`, `credentials status`
|
|
174
|
+
- Delegation operations: `delegation show`, `delegation inspect`, `delegation revoke`
|
|
175
|
+
- Delegation execution helpers: `delegation balance`, `delegation redeem`
|
|
121
176
|
- Account: `credits balance`
|
|
122
177
|
|
|
123
178
|
## Delegation lifecycle (canonical)
|
|
@@ -149,8 +204,53 @@ thirdfy-agent delegation status \
|
|
|
149
204
|
--agent-key "0xYOUR_AGENT_KEY" \
|
|
150
205
|
--verify \
|
|
151
206
|
--json
|
|
207
|
+
|
|
208
|
+
thirdfy-agent delegation inspect \
|
|
209
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
210
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
211
|
+
--verify \
|
|
212
|
+
--json
|
|
213
|
+
|
|
214
|
+
thirdfy-agent delegation revoke \
|
|
215
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
216
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
217
|
+
--reason "user_revoked" \
|
|
218
|
+
--json
|
|
219
|
+
|
|
220
|
+
thirdfy-agent delegation balance \
|
|
221
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
222
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
223
|
+
--json
|
|
224
|
+
|
|
225
|
+
thirdfy-agent delegation redeem \
|
|
226
|
+
--run-mode thirdfy \
|
|
227
|
+
--agent-api-key "$AGENT_API_KEY" \
|
|
228
|
+
--action swap \
|
|
229
|
+
--params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' \
|
|
230
|
+
--estimated-amount-usd 5 \
|
|
231
|
+
--json
|
|
152
232
|
```
|
|
153
233
|
|
|
234
|
+
### MetaMask custodial-compatible lifecycle (new)
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
thirdfy-agent delegation init-custodial \
|
|
238
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
239
|
+
--chain-id 8453 \
|
|
240
|
+
--json
|
|
241
|
+
|
|
242
|
+
thirdfy-agent delegation custodial-grant \
|
|
243
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
244
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
245
|
+
--token-address "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" \
|
|
246
|
+
--max-usd-per-day 250 \
|
|
247
|
+
--json
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The CLI keeps compatibility with legacy `delegation create` + `delegation activate` during migration.
|
|
251
|
+
|
|
252
|
+
`delegation show` is a direct alias to `delegation status` for gator-style operator ergonomics.
|
|
253
|
+
|
|
154
254
|
## More docs
|
|
155
255
|
|
|
156
256
|
- OpenClaw CLI onboarding flow:
|
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import os from 'os';
|
|
6
7
|
import path from 'path';
|
|
@@ -105,6 +106,15 @@ async function main() {
|
|
|
105
106
|
case 'config get':
|
|
106
107
|
await commandProfileShow(context, subFlags);
|
|
107
108
|
return;
|
|
109
|
+
case 'config set':
|
|
110
|
+
await commandConfigSet(context, subFlags);
|
|
111
|
+
return;
|
|
112
|
+
case 'login':
|
|
113
|
+
await commandLogin(context, subFlags);
|
|
114
|
+
return;
|
|
115
|
+
case 'logout':
|
|
116
|
+
await commandLogout(context, subFlags);
|
|
117
|
+
return;
|
|
108
118
|
case 'whoami':
|
|
109
119
|
await commandProfileShow(context, subFlags);
|
|
110
120
|
return;
|
|
@@ -133,12 +143,33 @@ async function main() {
|
|
|
133
143
|
case 'delegation create':
|
|
134
144
|
await commandDelegationCreate(context, subFlags, capabilities);
|
|
135
145
|
return;
|
|
146
|
+
case 'delegation init-custodial':
|
|
147
|
+
await commandDelegationInitCustodial(context, subFlags, capabilities);
|
|
148
|
+
return;
|
|
149
|
+
case 'delegation custodial-grant':
|
|
150
|
+
await commandDelegationCustodialGrant(context, subFlags, capabilities);
|
|
151
|
+
return;
|
|
136
152
|
case 'delegation activate':
|
|
137
153
|
await commandDelegationActivate(context, subFlags, capabilities);
|
|
138
154
|
return;
|
|
139
155
|
case 'delegation status':
|
|
140
156
|
await commandDelegationStatus(context, subFlags, capabilities);
|
|
141
157
|
return;
|
|
158
|
+
case 'delegation show':
|
|
159
|
+
await commandDelegationStatus(context, subFlags, capabilities);
|
|
160
|
+
return;
|
|
161
|
+
case 'delegation inspect':
|
|
162
|
+
await commandDelegationInspect(context, subFlags, capabilities);
|
|
163
|
+
return;
|
|
164
|
+
case 'delegation balance':
|
|
165
|
+
await commandDelegationBalance(context, subFlags, capabilities);
|
|
166
|
+
return;
|
|
167
|
+
case 'delegation revoke':
|
|
168
|
+
await commandDelegationRevoke(context, subFlags, capabilities);
|
|
169
|
+
return;
|
|
170
|
+
case 'delegation redeem':
|
|
171
|
+
await commandDelegationRedeem(context, subFlags, capabilities);
|
|
172
|
+
return;
|
|
142
173
|
case 'credentials upsert':
|
|
143
174
|
await commandCredentialsUpsert(context, subFlags, capabilities);
|
|
144
175
|
return;
|
|
@@ -148,6 +179,9 @@ async function main() {
|
|
|
148
179
|
case 'agent register':
|
|
149
180
|
await commandAgentRegister(context, subFlags, capabilities);
|
|
150
181
|
return;
|
|
182
|
+
case 'developer bootstrap':
|
|
183
|
+
await commandDeveloperBootstrap(context, subFlags, capabilities);
|
|
184
|
+
return;
|
|
151
185
|
default:
|
|
152
186
|
break;
|
|
153
187
|
}
|
|
@@ -182,6 +216,9 @@ async function main() {
|
|
|
182
216
|
case 'run':
|
|
183
217
|
await commandRun(context, subFlags, capabilities);
|
|
184
218
|
return;
|
|
219
|
+
case 'self-exec':
|
|
220
|
+
await commandSelfExec(context, subFlags, capabilities);
|
|
221
|
+
return;
|
|
185
222
|
case 'intent-status':
|
|
186
223
|
await commandIntentStatus(context, subFlags, capabilities);
|
|
187
224
|
return;
|
|
@@ -256,6 +293,162 @@ function parseJsonFlag(flags, key) {
|
|
|
256
293
|
}
|
|
257
294
|
}
|
|
258
295
|
|
|
296
|
+
function shouldApplySwapAmountContract(actionName) {
|
|
297
|
+
const action = String(actionName || '').trim().toLowerCase();
|
|
298
|
+
return action === 'swap' || /(^|:|-)swap$/.test(action);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function createCliError(code, message, payload = {}) {
|
|
302
|
+
const error = new Error(message);
|
|
303
|
+
error.payload = {
|
|
304
|
+
error: code,
|
|
305
|
+
blockedReason: code,
|
|
306
|
+
blockedStage: 'client_validation',
|
|
307
|
+
...payload,
|
|
308
|
+
};
|
|
309
|
+
return error;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function normalizeIntegerRawAmount(input, code = 'AMOUNT_UNIT_INVALID') {
|
|
313
|
+
const raw = String(input ?? '').trim();
|
|
314
|
+
if (!/^\d+$/.test(raw)) {
|
|
315
|
+
throw createCliError(code, 'amountInRaw must be an integer base-unit string (example: "1000000").');
|
|
316
|
+
}
|
|
317
|
+
return raw;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function decimalToRawUnits(amountHuman, decimals) {
|
|
321
|
+
const s = String(amountHuman ?? '').trim();
|
|
322
|
+
if (!/^\d+(\.\d+)?$/.test(s)) {
|
|
323
|
+
throw createCliError('AMOUNT_UNIT_INVALID', 'amountInHuman must be a decimal string (example: "100.5").');
|
|
324
|
+
}
|
|
325
|
+
const [whole, frac = ''] = s.split('.');
|
|
326
|
+
if (decimals === 0) {
|
|
327
|
+
if (frac && /[1-9]/.test(frac)) {
|
|
328
|
+
throw createCliError(
|
|
329
|
+
'AMOUNT_PRECISION_EXCESS',
|
|
330
|
+
'amountInHuman has fractional digits but tokenInDecimals is 0 (example: use "2" not "2.5").'
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
} else if (frac.length > decimals && /[1-9]/.test(frac.slice(decimals))) {
|
|
334
|
+
throw createCliError(
|
|
335
|
+
'AMOUNT_PRECISION_EXCESS',
|
|
336
|
+
`amountInHuman has more than ${decimals} fractional digit(s) for tokenInDecimals; round or use amountInRaw.`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const fracNorm = (frac + '0'.repeat(decimals)).slice(0, decimals);
|
|
340
|
+
const wholePart = BigInt(whole || '0') * 10n ** BigInt(decimals);
|
|
341
|
+
const fracPart = BigInt(fracNorm || '0');
|
|
342
|
+
return (wholePart + fracPart).toString();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeSwapAmountContract(params) {
|
|
346
|
+
const hasRaw = params.amountInRaw !== undefined && params.amountInRaw !== null && String(params.amountInRaw).trim() !== '';
|
|
347
|
+
const hasHuman = params.amountInHuman !== undefined && params.amountInHuman !== null && String(params.amountInHuman).trim() !== '';
|
|
348
|
+
const hasLegacy = params.amountIn !== undefined && params.amountIn !== null && String(params.amountIn).trim() !== '';
|
|
349
|
+
const populated = [hasRaw, hasHuman, hasLegacy].filter(Boolean).length;
|
|
350
|
+
if (populated === 0) {
|
|
351
|
+
throw createCliError(
|
|
352
|
+
'AMOUNT_UNIT_MISSING',
|
|
353
|
+
'Missing swap amount: provide one of amountInRaw, amountInHuman, or legacy amountIn.'
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (populated > 1) {
|
|
357
|
+
throw createCliError(
|
|
358
|
+
'AMOUNT_UNIT_AMBIGUOUS',
|
|
359
|
+
'Provide exactly one of amountInRaw, amountInHuman, or legacy amountIn.'
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (hasRaw) {
|
|
364
|
+
const amountInRaw = normalizeIntegerRawAmount(params.amountInRaw);
|
|
365
|
+
return {
|
|
366
|
+
params: {
|
|
367
|
+
...params,
|
|
368
|
+
amountInRaw,
|
|
369
|
+
amountIn: amountInRaw,
|
|
370
|
+
},
|
|
371
|
+
swapInput: {
|
|
372
|
+
amountInRaw,
|
|
373
|
+
amountInHuman: null,
|
|
374
|
+
tokenInDecimals: null,
|
|
375
|
+
unitSource: 'raw',
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (hasLegacy) {
|
|
381
|
+
const amountInRaw = normalizeIntegerRawAmount(params.amountIn, 'AMOUNT_UNIT_INVALID');
|
|
382
|
+
return {
|
|
383
|
+
params: {
|
|
384
|
+
...params,
|
|
385
|
+
amountInRaw,
|
|
386
|
+
amountIn: amountInRaw,
|
|
387
|
+
},
|
|
388
|
+
swapInput: {
|
|
389
|
+
amountInRaw,
|
|
390
|
+
amountInHuman: null,
|
|
391
|
+
tokenInDecimals: null,
|
|
392
|
+
unitSource: 'legacy_raw',
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const hasDecimals =
|
|
398
|
+
params.tokenInDecimals !== undefined &&
|
|
399
|
+
params.tokenInDecimals !== null &&
|
|
400
|
+
String(params.tokenInDecimals).trim() !== '';
|
|
401
|
+
if (!hasDecimals) {
|
|
402
|
+
throw createCliError(
|
|
403
|
+
'AMOUNT_UNIT_DECIMALS_REQUIRED',
|
|
404
|
+
'amountInHuman requires tokenInDecimals (integer 0-36).'
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const decimals = Number(params.tokenInDecimals);
|
|
408
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
409
|
+
throw createCliError(
|
|
410
|
+
'AMOUNT_UNIT_DECIMALS_REQUIRED',
|
|
411
|
+
'amountInHuman requires tokenInDecimals (integer 0-36).'
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const amountInHuman = String(params.amountInHuman).trim();
|
|
415
|
+
const amountInRaw = decimalToRawUnits(amountInHuman, decimals);
|
|
416
|
+
return {
|
|
417
|
+
params: {
|
|
418
|
+
...params,
|
|
419
|
+
amountInHuman,
|
|
420
|
+
amountInRaw,
|
|
421
|
+
amountIn: amountInRaw,
|
|
422
|
+
tokenInDecimals: decimals,
|
|
423
|
+
},
|
|
424
|
+
swapInput: {
|
|
425
|
+
amountInRaw,
|
|
426
|
+
amountInHuman,
|
|
427
|
+
tokenInDecimals: decimals,
|
|
428
|
+
unitSource: 'human',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function prepareActionParamsForFlags(flags, resolvedAction) {
|
|
434
|
+
const parsedParams = parseJsonFlag(flags, 'params');
|
|
435
|
+
if (!shouldApplySwapAmountContract(resolvedAction)) {
|
|
436
|
+
flags.__preparedParams = parsedParams;
|
|
437
|
+
flags.__swapInput = null;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const normalized = normalizeSwapAmountContract(parsedParams);
|
|
441
|
+
flags.__preparedParams = normalized.params;
|
|
442
|
+
flags.__swapInput = normalized.swapInput;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getPreparedParams(flags) {
|
|
446
|
+
if (flags.__preparedParams && typeof flags.__preparedParams === 'object') {
|
|
447
|
+
return flags.__preparedParams;
|
|
448
|
+
}
|
|
449
|
+
return parseJsonFlag(flags, 'params');
|
|
450
|
+
}
|
|
451
|
+
|
|
259
452
|
async function commandCatalogsList(ctx, flags, capabilities) {
|
|
260
453
|
const response = await apiGet(ctx, '/api/v1/agent/actions/catalog');
|
|
261
454
|
const actions = extractActions(response);
|
|
@@ -306,6 +499,7 @@ async function commandActions(ctx, flags, capabilities) {
|
|
|
306
499
|
|
|
307
500
|
async function commandPreflight(ctx, flags, capabilities) {
|
|
308
501
|
const resolved = await resolveActionSelection(ctx, flags);
|
|
502
|
+
prepareActionParamsForFlags(flags, resolved.resolvedAction);
|
|
309
503
|
const runMode = getEffectiveRunMode(ctx, flags);
|
|
310
504
|
enforceChainCompatibility(capabilities, flags, runMode);
|
|
311
505
|
const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved.resolvedAction);
|
|
@@ -322,6 +516,7 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
322
516
|
resolvedAction: resolved.resolvedAction,
|
|
323
517
|
runMode,
|
|
324
518
|
profile: ctx.profile || null,
|
|
519
|
+
swapInput: flags.__swapInput || null,
|
|
325
520
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
326
521
|
},
|
|
327
522
|
});
|
|
@@ -330,6 +525,7 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
330
525
|
|
|
331
526
|
async function commandRun(ctx, flags, capabilities) {
|
|
332
527
|
const resolved = await resolveActionSelection(ctx, flags);
|
|
528
|
+
prepareActionParamsForFlags(flags, resolved.resolvedAction);
|
|
333
529
|
const runMode = getEffectiveRunMode(ctx, flags);
|
|
334
530
|
enforceChainCompatibility(capabilities, flags, runMode);
|
|
335
531
|
const skipPreflight = Boolean(flags.skipPreflight);
|
|
@@ -350,6 +546,7 @@ async function commandRun(ctx, flags, capabilities) {
|
|
|
350
546
|
runMode,
|
|
351
547
|
profile: ctx.profile || null,
|
|
352
548
|
skippedPreflight: skipPreflight,
|
|
549
|
+
swapInput: flags.__swapInput || null,
|
|
353
550
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
354
551
|
},
|
|
355
552
|
});
|
|
@@ -392,12 +589,15 @@ async function commandCreditsBalance(ctx, flags, capabilities) {
|
|
|
392
589
|
}
|
|
393
590
|
|
|
394
591
|
async function commandDelegationCreate(ctx, flags, capabilities) {
|
|
395
|
-
const
|
|
592
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
593
|
+
flags,
|
|
594
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation create'
|
|
595
|
+
);
|
|
396
596
|
let sessionAccountAddress = String(flags.sessionAccountAddress || '').trim();
|
|
397
597
|
if (!sessionAccountAddress) {
|
|
398
598
|
try {
|
|
399
599
|
const executorAddressResponse = await apiGet(ctx, '/api/v1/agent/delegation/executor-address', {
|
|
400
|
-
|
|
600
|
+
...authHeaders,
|
|
401
601
|
});
|
|
402
602
|
sessionAccountAddress = String(executorAddressResponse?.data?.executorAddress || '').trim();
|
|
403
603
|
} catch (error) {
|
|
@@ -419,7 +619,7 @@ async function commandDelegationCreate(ctx, flags, capabilities) {
|
|
|
419
619
|
expirySeconds: Number(flags.expirySeconds || 86_400),
|
|
420
620
|
};
|
|
421
621
|
const response = await apiPost(ctx, '/api/v1/agent/delegation/create', payload, {
|
|
422
|
-
|
|
622
|
+
...authHeaders,
|
|
423
623
|
});
|
|
424
624
|
const success = Boolean(response?.success !== false);
|
|
425
625
|
printEnvelope({
|
|
@@ -434,8 +634,74 @@ async function commandDelegationCreate(ctx, flags, capabilities) {
|
|
|
434
634
|
});
|
|
435
635
|
}
|
|
436
636
|
|
|
637
|
+
async function commandDelegationInitCustodial(ctx, flags, capabilities) {
|
|
638
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
639
|
+
flags,
|
|
640
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation init-custodial'
|
|
641
|
+
);
|
|
642
|
+
const payload = {
|
|
643
|
+
chainId: Number(flags.chainId || 8453),
|
|
644
|
+
};
|
|
645
|
+
const response = await requestWithRouteFallback(ctx, {
|
|
646
|
+
method: 'POST',
|
|
647
|
+
routes: ['/api/v1/agent/delegation/init-custodial'],
|
|
648
|
+
body: payload,
|
|
649
|
+
headers: {
|
|
650
|
+
...authHeaders,
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
const success = Boolean(response?.success !== false);
|
|
654
|
+
printEnvelope({
|
|
655
|
+
success,
|
|
656
|
+
code: success ? 'DELEGATION_CUSTODIAL_INITIALIZED' : 'DELEGATION_CUSTODIAL_INIT_FAILED',
|
|
657
|
+
message: response?.message || (success ? 'Custodial wallet initialized' : 'Custodial wallet init failed'),
|
|
658
|
+
data: response,
|
|
659
|
+
meta: {
|
|
660
|
+
apiBase: ctx.apiBase,
|
|
661
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function commandDelegationCustodialGrant(ctx, flags, capabilities) {
|
|
667
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
668
|
+
flags,
|
|
669
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation custodial-grant'
|
|
670
|
+
);
|
|
671
|
+
const payload = {
|
|
672
|
+
chainId: Number(flags.chainId || 8453),
|
|
673
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
674
|
+
tokenAddress: requireFlag(flags, 'tokenAddress', 'Missing --token-address'),
|
|
675
|
+
maxUsdPerDay: Number(requireFlag(flags, 'maxUsdPerDay', 'Missing --max-usd-per-day')),
|
|
676
|
+
periodDuration: Number(flags.periodDuration || 86_400),
|
|
677
|
+
expirySeconds: Number(flags.expirySeconds || 86_400),
|
|
678
|
+
};
|
|
679
|
+
const response = await requestWithRouteFallback(ctx, {
|
|
680
|
+
method: 'POST',
|
|
681
|
+
routes: ['/api/v1/agent/delegation/custodial-grant'],
|
|
682
|
+
body: payload,
|
|
683
|
+
headers: {
|
|
684
|
+
...authHeaders,
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
const success = Boolean(response?.success !== false);
|
|
688
|
+
printEnvelope({
|
|
689
|
+
success,
|
|
690
|
+
code: success ? 'DELEGATION_CUSTODIAL_GRANTED' : 'DELEGATION_CUSTODIAL_GRANT_FAILED',
|
|
691
|
+
message: response?.message || (success ? 'Custodial delegation granted' : 'Custodial delegation grant failed'),
|
|
692
|
+
data: response,
|
|
693
|
+
meta: {
|
|
694
|
+
apiBase: ctx.apiBase,
|
|
695
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
437
700
|
async function commandDelegationActivate(ctx, flags, capabilities) {
|
|
438
|
-
const
|
|
701
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
702
|
+
flags,
|
|
703
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation activate'
|
|
704
|
+
);
|
|
439
705
|
const payload = {
|
|
440
706
|
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
441
707
|
walletAddress: requireFlag(flags, 'walletAddress', 'Missing --wallet-address'),
|
|
@@ -454,7 +720,7 @@ async function commandDelegationActivate(ctx, flags, capabilities) {
|
|
|
454
720
|
if (flags.permissionData) payload.permissionData = parseJsonFlag(flags, 'permissionData');
|
|
455
721
|
if (flags.modelAConstraints) payload.modelAConstraints = parseJsonFlag(flags, 'modelAConstraints');
|
|
456
722
|
const response = await apiPost(ctx, '/api/v1/agent/delegation/activate', payload, {
|
|
457
|
-
|
|
723
|
+
...authHeaders,
|
|
458
724
|
});
|
|
459
725
|
const success = Boolean(response?.success !== false);
|
|
460
726
|
printEnvelope({
|
|
@@ -470,7 +736,10 @@ async function commandDelegationActivate(ctx, flags, capabilities) {
|
|
|
470
736
|
}
|
|
471
737
|
|
|
472
738
|
async function commandDelegationStatus(ctx, flags, capabilities) {
|
|
473
|
-
const
|
|
739
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
740
|
+
flags,
|
|
741
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation status'
|
|
742
|
+
);
|
|
474
743
|
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
475
744
|
const chainId = Number(flags.chainId || 8453);
|
|
476
745
|
const verify = Boolean(flags.verify);
|
|
@@ -478,7 +747,7 @@ async function commandDelegationStatus(ctx, flags, capabilities) {
|
|
|
478
747
|
? `/api/v1/agent/delegation/verify?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`
|
|
479
748
|
: `/api/v1/agent/delegation/check?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
480
749
|
const response = await apiGet(ctx, route, {
|
|
481
|
-
|
|
750
|
+
...authHeaders,
|
|
482
751
|
});
|
|
483
752
|
const normalized = normalizeDelegationStatus(response);
|
|
484
753
|
printEnvelope({
|
|
@@ -495,6 +764,129 @@ async function commandDelegationStatus(ctx, flags, capabilities) {
|
|
|
495
764
|
});
|
|
496
765
|
}
|
|
497
766
|
|
|
767
|
+
async function commandDelegationInspect(ctx, flags, capabilities) {
|
|
768
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
769
|
+
flags,
|
|
770
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation inspect'
|
|
771
|
+
);
|
|
772
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
773
|
+
const chainId = Number(flags.chainId || 8453);
|
|
774
|
+
const includeOnchain = Boolean(flags.verify || flags.includeOnchain || flags.onchain);
|
|
775
|
+
const checkRoute = `/api/v1/agent/delegation/check?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
776
|
+
const checkResponse = await apiGet(ctx, checkRoute, {
|
|
777
|
+
...authHeaders,
|
|
778
|
+
});
|
|
779
|
+
const checkNormalized = normalizeDelegationStatus(checkResponse);
|
|
780
|
+
|
|
781
|
+
let verifyNormalized = null;
|
|
782
|
+
if (includeOnchain) {
|
|
783
|
+
const verifyRoute = `/api/v1/agent/delegation/verify?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
784
|
+
const verifyResponse = await apiGet(ctx, verifyRoute, {
|
|
785
|
+
...authHeaders,
|
|
786
|
+
});
|
|
787
|
+
verifyNormalized = normalizeDelegationStatus(verifyResponse);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
printEnvelope({
|
|
791
|
+
success: true,
|
|
792
|
+
code: 'DELEGATION_INSPECT_OK',
|
|
793
|
+
message: includeOnchain ? 'Delegation inspect loaded (with onchain verification)' : 'Delegation inspect loaded',
|
|
794
|
+
data: {
|
|
795
|
+
check: checkNormalized,
|
|
796
|
+
verify: verifyNormalized,
|
|
797
|
+
state: verifyNormalized?.state || checkNormalized.state,
|
|
798
|
+
rawStatus: verifyNormalized?.rawStatus || checkNormalized.rawStatus,
|
|
799
|
+
},
|
|
800
|
+
meta: {
|
|
801
|
+
apiBase: ctx.apiBase,
|
|
802
|
+
includeOnchain,
|
|
803
|
+
governanceState: verifyNormalized?.state || checkNormalized.state || null,
|
|
804
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async function commandDelegationRevoke(ctx, flags, capabilities) {
|
|
810
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
811
|
+
flags,
|
|
812
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation revoke'
|
|
813
|
+
);
|
|
814
|
+
const payload = {
|
|
815
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
816
|
+
reason: String(flags.reason || 'user_revoked').trim(),
|
|
817
|
+
chainId: Number(flags.chainId || 8453),
|
|
818
|
+
};
|
|
819
|
+
const response = await requestWithRouteFallback(ctx, {
|
|
820
|
+
method: 'POST',
|
|
821
|
+
routes: ['/api/v1/agent/delegation/revoke'],
|
|
822
|
+
body: payload,
|
|
823
|
+
headers: {
|
|
824
|
+
...authHeaders,
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
const success = Boolean(response?.success !== false);
|
|
828
|
+
printEnvelope({
|
|
829
|
+
success,
|
|
830
|
+
code: success ? 'DELEGATION_REVOKED' : 'DELEGATION_REVOKE_FAILED',
|
|
831
|
+
message: response?.message || (success ? 'Delegation revoked' : 'Delegation revoke failed'),
|
|
832
|
+
data: response,
|
|
833
|
+
meta: {
|
|
834
|
+
apiBase: ctx.apiBase,
|
|
835
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function commandDelegationBalance(ctx, flags, capabilities) {
|
|
841
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
842
|
+
flags,
|
|
843
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation balance'
|
|
844
|
+
);
|
|
845
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
846
|
+
const chainId = Number(flags.chainId || 8453);
|
|
847
|
+
const route = `/api/v1/agent/delegation/verify?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
848
|
+
const response = await apiGet(ctx, route, {
|
|
849
|
+
...authHeaders,
|
|
850
|
+
});
|
|
851
|
+
const normalized = normalizeDelegationStatus(response);
|
|
852
|
+
const verifyData = normalized?.data?.onChainVerification || {};
|
|
853
|
+
printEnvelope({
|
|
854
|
+
success: true,
|
|
855
|
+
code: 'DELEGATION_BALANCE_OK',
|
|
856
|
+
message: 'Delegation allowance balance loaded',
|
|
857
|
+
data: {
|
|
858
|
+
state: normalized.state,
|
|
859
|
+
rawStatus: normalized.rawStatus,
|
|
860
|
+
delegatedWalletAddress: normalized?.data?.delegatedWalletAddress || null,
|
|
861
|
+
periodAvailableUsd: verifyData?.periodAvailableUsd ?? null,
|
|
862
|
+
periodAvailableBaseUnits: verifyData?.periodAvailableBaseUnits ?? null,
|
|
863
|
+
verified: verifyData?.verified ?? null,
|
|
864
|
+
isRevoked: verifyData?.isRevoked ?? null,
|
|
865
|
+
onChainVerification: verifyData || null,
|
|
866
|
+
},
|
|
867
|
+
meta: {
|
|
868
|
+
apiBase: ctx.apiBase,
|
|
869
|
+
governanceState: normalized.state || null,
|
|
870
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
871
|
+
},
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async function commandDelegationRedeem(ctx, flags, capabilities) {
|
|
876
|
+
const selectedRunMode = getEffectiveRunMode(ctx, flags);
|
|
877
|
+
if (!['thirdfy', 'hybrid'].includes(selectedRunMode)) {
|
|
878
|
+
throw createCliError('DELEGATION_REDEEM_MODE_INVALID', 'delegation redeem supports only --run-mode thirdfy|hybrid');
|
|
879
|
+
}
|
|
880
|
+
const delegatedFlags = {
|
|
881
|
+
...flags,
|
|
882
|
+
runMode: selectedRunMode,
|
|
883
|
+
};
|
|
884
|
+
if (!delegatedFlags.estimatedAmountUsd) {
|
|
885
|
+
delegatedFlags.estimatedAmountUsd = '5';
|
|
886
|
+
}
|
|
887
|
+
await commandRun(ctx, delegatedFlags, capabilities);
|
|
888
|
+
}
|
|
889
|
+
|
|
498
890
|
async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
499
891
|
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for credentials upsert');
|
|
500
892
|
const payload = {
|
|
@@ -587,11 +979,83 @@ async function commandAgentRegister(ctx, flags, capabilities) {
|
|
|
587
979
|
? '/api/v1/agent/onboarding/register-with-wallet'
|
|
588
980
|
: '/api/v1/agent/onboarding/register-with-privy';
|
|
589
981
|
const response = await apiPost(ctx, route, payload, ownerHeaders);
|
|
982
|
+
if (!flags.__suppressOutput) {
|
|
983
|
+
printEnvelope({
|
|
984
|
+
success: true,
|
|
985
|
+
code: 'AGENT_REGISTERED',
|
|
986
|
+
message: 'Agent registered',
|
|
987
|
+
data: response,
|
|
988
|
+
meta: {
|
|
989
|
+
apiBase: ctx.apiBase,
|
|
990
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
return response;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function commandDeveloperBootstrap(ctx, flags, capabilities) {
|
|
998
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
999
|
+
const name = requireFlag(flags, 'name', 'Missing --name');
|
|
1000
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
1001
|
+
let usedWalletFlow = false;
|
|
1002
|
+
const challengeId = String(flags.challengeId || '').trim();
|
|
1003
|
+
const signature = String(flags.signature || '').trim();
|
|
1004
|
+
|
|
1005
|
+
if (!ownerSessionToken && signature) {
|
|
1006
|
+
if (!challengeId) {
|
|
1007
|
+
throw new Error('Missing --challenge-id for wallet bootstrap. Run `agent auth challenge` first.');
|
|
1008
|
+
}
|
|
1009
|
+
const verify = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', {
|
|
1010
|
+
challengeId,
|
|
1011
|
+
signature,
|
|
1012
|
+
agentKey,
|
|
1013
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
1014
|
+
});
|
|
1015
|
+
ownerSessionToken = String(verify?.ownerSessionToken || verify?.data?.ownerSessionToken || '').trim();
|
|
1016
|
+
usedWalletFlow = true;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const registerFlags = {
|
|
1020
|
+
...flags,
|
|
1021
|
+
agentKey,
|
|
1022
|
+
name,
|
|
1023
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
1024
|
+
};
|
|
1025
|
+
const registerResponse = await commandAgentRegister(
|
|
1026
|
+
ctx,
|
|
1027
|
+
{ ...registerFlags, __suppressOutput: true },
|
|
1028
|
+
capabilities
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
const config = loadProfileConfig();
|
|
1032
|
+
const next = {
|
|
1033
|
+
...config,
|
|
1034
|
+
auth: {
|
|
1035
|
+
...(config.auth && typeof config.auth === 'object' ? config.auth : {}),
|
|
1036
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
1037
|
+
lastLoginAt: new Date().toISOString(),
|
|
1038
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
1039
|
+
},
|
|
1040
|
+
developer: {
|
|
1041
|
+
...(config.developer && typeof config.developer === 'object' ? config.developer : {}),
|
|
1042
|
+
agentKey,
|
|
1043
|
+
bootstrapCompletedAt: new Date().toISOString(),
|
|
1044
|
+
},
|
|
1045
|
+
};
|
|
1046
|
+
persistProfileConfig(next);
|
|
590
1047
|
printEnvelope({
|
|
591
1048
|
success: true,
|
|
592
|
-
code: '
|
|
593
|
-
message: '
|
|
594
|
-
data:
|
|
1049
|
+
code: 'DEVELOPER_BOOTSTRAP_OK',
|
|
1050
|
+
message: 'Developer bootstrap completed',
|
|
1051
|
+
data: {
|
|
1052
|
+
registration: registerResponse,
|
|
1053
|
+
persisted: {
|
|
1054
|
+
ownerSessionTokenSaved: Boolean(ownerSessionToken),
|
|
1055
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
1056
|
+
configPath: getProfileConfigPath(),
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
595
1059
|
meta: {
|
|
596
1060
|
apiBase: ctx.apiBase,
|
|
597
1061
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
@@ -706,10 +1170,140 @@ async function commandPrompt(ctx, flags, capabilities, promptTokens) {
|
|
|
706
1170
|
});
|
|
707
1171
|
}
|
|
708
1172
|
|
|
1173
|
+
async function commandSelfExec(ctx, flags, capabilities) {
|
|
1174
|
+
const resolved = await resolveActionSelection(ctx, flags);
|
|
1175
|
+
prepareActionParamsForFlags(flags, resolved.resolvedAction);
|
|
1176
|
+
enforceChainCompatibility(capabilities, flags, 'self');
|
|
1177
|
+
const selfResult = await executeSelfRun(ctx, flags, resolved, {
|
|
1178
|
+
skipPreflight: Boolean(flags.skipPreflight),
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
if (!selfResult?.success) {
|
|
1182
|
+
const blocked = Boolean(selfResult?.preflightBlocked);
|
|
1183
|
+
printEnvelope({
|
|
1184
|
+
success: false,
|
|
1185
|
+
code: blocked ? 'PREFLIGHT_BLOCKED' : 'SELF_EXECUTION_FAILED',
|
|
1186
|
+
message: blocked ? 'Preflight blocked self execution' : 'Self execution preparation failed',
|
|
1187
|
+
data: selfResult || {},
|
|
1188
|
+
meta: {
|
|
1189
|
+
apiBase: ctx.apiBase,
|
|
1190
|
+
runMode: 'self',
|
|
1191
|
+
resolvedAction: resolved.resolvedAction,
|
|
1192
|
+
},
|
|
1193
|
+
});
|
|
1194
|
+
process.exit(1);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const txd = resolveUnsignedTx(selfResult);
|
|
1198
|
+
if (!txd?.to || !txd?.data) {
|
|
1199
|
+
printEnvelope({
|
|
1200
|
+
success: false,
|
|
1201
|
+
code: 'SELF_UNSIGNED_TX_MISSING',
|
|
1202
|
+
message: 'Self execution did not return a valid unsigned transaction',
|
|
1203
|
+
data: selfResult || {},
|
|
1204
|
+
meta: {
|
|
1205
|
+
apiBase: ctx.apiBase,
|
|
1206
|
+
runMode: 'self',
|
|
1207
|
+
resolvedAction: resolved.resolvedAction,
|
|
1208
|
+
},
|
|
1209
|
+
});
|
|
1210
|
+
process.exit(1);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const chainId = Number(txd.chainId || flags.chainId || 8453);
|
|
1214
|
+
const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
|
|
1215
|
+
const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
|
|
1216
|
+
const rpcUrl = String(flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org').trim();
|
|
1217
|
+
if (!walletAddress) {
|
|
1218
|
+
throw createCliError('SIGNING_FAILED', 'Missing --wallet-address (or MARKET_MAKER_WALLET_ADDRESS) for self-exec signing.');
|
|
1219
|
+
}
|
|
1220
|
+
if (!owsWalletName) {
|
|
1221
|
+
throw createCliError('SIGNING_FAILED', 'Missing --ows-wallet-name (or OWS_WALLET_NAME) for self-exec signing.');
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const params = getPreparedParams(flags);
|
|
1225
|
+
const effectTokenIn = String(flags.effectTokenIn || params.tokenIn || '').trim();
|
|
1226
|
+
const effectTokenOut = String(flags.effectTokenOut || params.tokenOut || '').trim();
|
|
1227
|
+
|
|
1228
|
+
let balancesBefore = null;
|
|
1229
|
+
if (effectTokenIn && effectTokenOut) {
|
|
1230
|
+
balancesBefore = await readEffectBalances(rpcUrl, walletAddress, effectTokenIn, effectTokenOut);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
let submission;
|
|
1234
|
+
try {
|
|
1235
|
+
submission = await signAndBroadcastWithOws({
|
|
1236
|
+
txd,
|
|
1237
|
+
chainId,
|
|
1238
|
+
walletAddress,
|
|
1239
|
+
owsWalletName,
|
|
1240
|
+
rpcUrl,
|
|
1241
|
+
});
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
throw createCliError('SIGNING_FAILED', error?.message || 'OWS signing failed');
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
let receipt;
|
|
1247
|
+
try {
|
|
1248
|
+
receipt = await submission.sent.wait();
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
throw createCliError('BROADCAST_FAILED', error?.message || 'Broadcast wait failed');
|
|
1251
|
+
}
|
|
1252
|
+
const status = Number(receipt?.status ?? 0);
|
|
1253
|
+
if (status !== 1) {
|
|
1254
|
+
throw createCliError('BROADCAST_FAILED', 'Transaction receipt status is not success', {
|
|
1255
|
+
receiptStatus: receipt?.status ?? null,
|
|
1256
|
+
txHash: submission.sent.hash,
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
let effectCheck = { checked: false, hasEffect: null, inDeltaRaw: null, outDeltaRaw: null };
|
|
1261
|
+
if (balancesBefore) {
|
|
1262
|
+
const balancesAfter = await readEffectBalances(rpcUrl, walletAddress, effectTokenIn, effectTokenOut);
|
|
1263
|
+
const inDeltaRaw = (BigInt(balancesBefore.tokenIn) - BigInt(balancesAfter.tokenIn)).toString();
|
|
1264
|
+
const outDeltaRaw = (BigInt(balancesAfter.tokenOut) - BigInt(balancesBefore.tokenOut)).toString();
|
|
1265
|
+
const hasEffect = BigInt(inDeltaRaw) > 0n || BigInt(outDeltaRaw) > 0n;
|
|
1266
|
+
effectCheck = {
|
|
1267
|
+
checked: true,
|
|
1268
|
+
hasEffect,
|
|
1269
|
+
inDeltaRaw,
|
|
1270
|
+
outDeltaRaw,
|
|
1271
|
+
};
|
|
1272
|
+
if (!hasEffect) {
|
|
1273
|
+
throw createCliError('EFFECT_CHECK_FAILED', 'Transaction mined but did not produce observable token effect.', {
|
|
1274
|
+
txHash: submission.sent.hash,
|
|
1275
|
+
effectCheck,
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
printEnvelope({
|
|
1281
|
+
success: true,
|
|
1282
|
+
code: 'SELF_EXECUTED',
|
|
1283
|
+
message: 'Self-custody execution completed',
|
|
1284
|
+
data: {
|
|
1285
|
+
txHash: submission.sent.hash,
|
|
1286
|
+
chainId,
|
|
1287
|
+
blockNumber: receipt?.blockNumber ?? null,
|
|
1288
|
+
status: receipt?.status ?? null,
|
|
1289
|
+
unsignedTx: txd,
|
|
1290
|
+
effectCheck,
|
|
1291
|
+
},
|
|
1292
|
+
meta: {
|
|
1293
|
+
apiBase: ctx.apiBase,
|
|
1294
|
+
runMode: 'self',
|
|
1295
|
+
resolvedAction: resolved.resolvedAction,
|
|
1296
|
+
swapInput: flags.__swapInput || null,
|
|
1297
|
+
},
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
709
1301
|
async function commandProfileInit(ctx, flags) {
|
|
1302
|
+
const current = loadProfileConfig();
|
|
710
1303
|
const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
|
|
711
1304
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
712
1305
|
persistProfileConfig({
|
|
1306
|
+
...current,
|
|
713
1307
|
profile,
|
|
714
1308
|
runMode,
|
|
715
1309
|
});
|
|
@@ -734,6 +1328,7 @@ async function commandProfileUse(ctx, flags) {
|
|
|
734
1328
|
// `profile use` intentionally re-applies profile defaults unless caller pins a mode.
|
|
735
1329
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
736
1330
|
persistProfileConfig({
|
|
1331
|
+
...current,
|
|
737
1332
|
profile,
|
|
738
1333
|
runMode,
|
|
739
1334
|
});
|
|
@@ -770,6 +1365,154 @@ async function commandProfileShow(ctx, _flags) {
|
|
|
770
1365
|
});
|
|
771
1366
|
}
|
|
772
1367
|
|
|
1368
|
+
async function commandConfigSet(ctx, flags) {
|
|
1369
|
+
const key = String(flags.key || '').trim();
|
|
1370
|
+
const value = flags.value;
|
|
1371
|
+
if (!key) {
|
|
1372
|
+
throw new Error('Missing --key for config set');
|
|
1373
|
+
}
|
|
1374
|
+
const canonicalKey = key.toLowerCase();
|
|
1375
|
+
const hasValue = !(value === undefined || String(value).trim() === '');
|
|
1376
|
+
if (canonicalKey === 'profile' && hasValue) {
|
|
1377
|
+
normalizeProfileName(value);
|
|
1378
|
+
}
|
|
1379
|
+
if (canonicalKey === 'runmode' && hasValue) {
|
|
1380
|
+
normalizeRunMode(value);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const current = loadProfileConfig();
|
|
1384
|
+
const next = setNestedConfigValue(current, key, value);
|
|
1385
|
+
persistProfileConfig(next);
|
|
1386
|
+
printEnvelope({
|
|
1387
|
+
success: true,
|
|
1388
|
+
code: 'CONFIG_UPDATED',
|
|
1389
|
+
message: 'Configuration updated',
|
|
1390
|
+
data: {
|
|
1391
|
+
key,
|
|
1392
|
+
value: value === undefined ? null : String(value),
|
|
1393
|
+
configPath: getProfileConfigPath(),
|
|
1394
|
+
},
|
|
1395
|
+
meta: { apiBase: ctx.apiBase },
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
async function commandLogin(ctx, flags) {
|
|
1400
|
+
const current = loadProfileConfig();
|
|
1401
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
1402
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
1403
|
+
let loginSource = 'token';
|
|
1404
|
+
|
|
1405
|
+
if (!authToken && !ownerSessionToken) {
|
|
1406
|
+
const challengeId = String(flags.challengeId || '').trim();
|
|
1407
|
+
const signature = String(flags.signature || '').trim();
|
|
1408
|
+
const agentKey = String(flags.agentKey || '').trim();
|
|
1409
|
+
if (challengeId && signature && agentKey) {
|
|
1410
|
+
const verifyResponse = await requestWithRouteFallback(ctx, {
|
|
1411
|
+
method: 'POST',
|
|
1412
|
+
routes: ['/api/v1/agent/onboarding/wallet/verify'],
|
|
1413
|
+
body: {
|
|
1414
|
+
challengeId,
|
|
1415
|
+
signature,
|
|
1416
|
+
agentKey,
|
|
1417
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
ownerSessionToken = String(
|
|
1421
|
+
verifyResponse?.ownerSessionToken || verifyResponse?.data?.ownerSessionToken || ''
|
|
1422
|
+
).trim();
|
|
1423
|
+
loginSource = 'wallet-verify';
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (!authToken && !ownerSessionToken) {
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
'Missing login credentials: provide --auth-token, --owner-session-token, or challenge verify inputs (--challenge-id/--signature/--agent-key).'
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const next = {
|
|
1434
|
+
...current,
|
|
1435
|
+
auth: {
|
|
1436
|
+
...(current.auth && typeof current.auth === 'object' ? current.auth : {}),
|
|
1437
|
+
lastLoginAt: new Date().toISOString(),
|
|
1438
|
+
source: loginSource,
|
|
1439
|
+
},
|
|
1440
|
+
};
|
|
1441
|
+
if (authToken) {
|
|
1442
|
+
next.auth.authToken = authToken;
|
|
1443
|
+
delete next.auth.ownerSessionToken;
|
|
1444
|
+
} else if (ownerSessionToken) {
|
|
1445
|
+
next.auth.ownerSessionToken = ownerSessionToken;
|
|
1446
|
+
delete next.auth.authToken;
|
|
1447
|
+
}
|
|
1448
|
+
if (flags.agentApiKey) {
|
|
1449
|
+
next.agent = {
|
|
1450
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
1451
|
+
apiKey: String(flags.agentApiKey).trim(),
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
persistProfileConfig(next);
|
|
1455
|
+
printEnvelope({
|
|
1456
|
+
success: true,
|
|
1457
|
+
code: 'LOGIN_OK',
|
|
1458
|
+
message: 'Login credentials stored',
|
|
1459
|
+
data: {
|
|
1460
|
+
hasAuthToken: Boolean(authToken),
|
|
1461
|
+
hasOwnerSessionToken: Boolean(ownerSessionToken),
|
|
1462
|
+
hasAgentApiKey: Boolean(flags.agentApiKey),
|
|
1463
|
+
configPath: getProfileConfigPath(),
|
|
1464
|
+
},
|
|
1465
|
+
meta: { apiBase: ctx.apiBase },
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
async function commandLogout(ctx, flags) {
|
|
1470
|
+
const current = loadProfileConfig();
|
|
1471
|
+
const localOnly = flags.all ? false : true;
|
|
1472
|
+
const auth = current.auth && typeof current.auth === 'object' ? current.auth : {};
|
|
1473
|
+
const ownerSessionToken = String(auth.ownerSessionToken || '').trim();
|
|
1474
|
+
let revokeAttempted = false;
|
|
1475
|
+
let revokeSuccess = false;
|
|
1476
|
+
let revokeError = null;
|
|
1477
|
+
|
|
1478
|
+
if (!localOnly && ownerSessionToken) {
|
|
1479
|
+
revokeAttempted = true;
|
|
1480
|
+
try {
|
|
1481
|
+
await requestWithRouteFallback(ctx, {
|
|
1482
|
+
method: 'POST',
|
|
1483
|
+
routes: ['/api/v1/agent/onboarding/wallet/logout', '/api/v1/agent/onboarding/wallet/revoke-session'],
|
|
1484
|
+
body: {},
|
|
1485
|
+
headers: {
|
|
1486
|
+
'x-owner-session-token': ownerSessionToken,
|
|
1487
|
+
},
|
|
1488
|
+
});
|
|
1489
|
+
revokeSuccess = true;
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
revokeError = error?.message || 'OWNER_SESSION_REVOKE_FAILED';
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const next = { ...current };
|
|
1496
|
+
delete next.auth;
|
|
1497
|
+
if (flags.clearAgentKey) {
|
|
1498
|
+
delete next.agent;
|
|
1499
|
+
}
|
|
1500
|
+
persistProfileConfig(next);
|
|
1501
|
+
printEnvelope({
|
|
1502
|
+
success: true,
|
|
1503
|
+
code: 'LOGOUT_OK',
|
|
1504
|
+
message: 'Local credentials cleared',
|
|
1505
|
+
data: {
|
|
1506
|
+
localOnly,
|
|
1507
|
+
revokeAttempted,
|
|
1508
|
+
revokeSuccess,
|
|
1509
|
+
revokeError,
|
|
1510
|
+
configPath: getProfileConfigPath(),
|
|
1511
|
+
},
|
|
1512
|
+
meta: { apiBase: ctx.apiBase },
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
|
|
773
1516
|
function resolveProfileState(flags) {
|
|
774
1517
|
const persisted = loadProfileConfig();
|
|
775
1518
|
const profile = normalizeProfileName(flags.profile || process.env.THIRDFY_PROFILE || persisted.profile || 'personal');
|
|
@@ -818,8 +1561,60 @@ function loadProfileConfig() {
|
|
|
818
1561
|
function persistProfileConfig(config) {
|
|
819
1562
|
const configPath = getProfileConfigPath();
|
|
820
1563
|
const dirPath = path.dirname(configPath);
|
|
821
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
822
|
-
|
|
1564
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
1565
|
+
const payload = `${JSON.stringify(config, null, 2)}\n`;
|
|
1566
|
+
fs.writeFileSync(configPath, payload, { encoding: 'utf8', mode: 0o600 });
|
|
1567
|
+
try {
|
|
1568
|
+
fs.chmodSync(configPath, 0o600);
|
|
1569
|
+
} catch {
|
|
1570
|
+
/* chmod unsupported or restricted (e.g. some Windows FS) */
|
|
1571
|
+
}
|
|
1572
|
+
try {
|
|
1573
|
+
fs.chmodSync(dirPath, 0o700);
|
|
1574
|
+
} catch {
|
|
1575
|
+
/* ignore */
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function clonePlainConfigTree(input) {
|
|
1580
|
+
const obj = input && typeof input === 'object' && !Array.isArray(input) ? input : {};
|
|
1581
|
+
try {
|
|
1582
|
+
return structuredClone(obj);
|
|
1583
|
+
} catch {
|
|
1584
|
+
return JSON.parse(JSON.stringify(obj));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function setNestedConfigValue(config, keyPath, value) {
|
|
1589
|
+
const next = clonePlainConfigTree(config || {});
|
|
1590
|
+
const parts = String(keyPath || '')
|
|
1591
|
+
.split('.')
|
|
1592
|
+
.map((v) => v.trim())
|
|
1593
|
+
.filter(Boolean);
|
|
1594
|
+
const blocked = new Set(['__proto__', 'prototype', 'constructor']);
|
|
1595
|
+
if (parts.some((part) => blocked.has(part))) {
|
|
1596
|
+
throw createCliError(
|
|
1597
|
+
'CONFIG_KEY_INVALID',
|
|
1598
|
+
'Invalid config key path: reserved segments (__proto__, prototype, constructor) are not allowed.'
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
if (!parts.length) return next;
|
|
1602
|
+
let cursor = next;
|
|
1603
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
1604
|
+
const key = parts[i];
|
|
1605
|
+
const existing = cursor[key];
|
|
1606
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
1607
|
+
cursor[key] = {};
|
|
1608
|
+
}
|
|
1609
|
+
cursor = cursor[key];
|
|
1610
|
+
}
|
|
1611
|
+
const finalKey = parts[parts.length - 1];
|
|
1612
|
+
if (value === undefined || String(value).trim() === '') {
|
|
1613
|
+
delete cursor[finalKey];
|
|
1614
|
+
} else {
|
|
1615
|
+
cursor[finalKey] = String(value);
|
|
1616
|
+
}
|
|
1617
|
+
return next;
|
|
823
1618
|
}
|
|
824
1619
|
|
|
825
1620
|
function getEffectiveRunMode(ctx, flags) {
|
|
@@ -1030,10 +1825,11 @@ async function executeHybridRun(ctx, flags, resolved, options) {
|
|
|
1030
1825
|
}
|
|
1031
1826
|
|
|
1032
1827
|
function buildBuildTxPayload(flags, resolvedAction) {
|
|
1828
|
+
const runMode = String(flags.runMode || '').trim().toLowerCase() || 'self';
|
|
1033
1829
|
const payload = {
|
|
1034
|
-
agentApiKey:
|
|
1830
|
+
agentApiKey: resolveAgentApiKey(flags, runMode),
|
|
1035
1831
|
action: String(resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
|
|
1036
|
-
params:
|
|
1832
|
+
params: getPreparedParams(flags),
|
|
1037
1833
|
};
|
|
1038
1834
|
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
1039
1835
|
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
@@ -1041,10 +1837,11 @@ function buildBuildTxPayload(flags, resolvedAction) {
|
|
|
1041
1837
|
}
|
|
1042
1838
|
|
|
1043
1839
|
function buildIntentPayload(flags, options) {
|
|
1840
|
+
const runMode = String(options.runMode || flags.runMode || '').trim().toLowerCase() || 'thirdfy';
|
|
1044
1841
|
const payload = {
|
|
1045
|
-
agentApiKey:
|
|
1842
|
+
agentApiKey: resolveAgentApiKey(flags, runMode),
|
|
1046
1843
|
action: String(options.resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
|
|
1047
|
-
params:
|
|
1844
|
+
params: getPreparedParams(flags),
|
|
1048
1845
|
};
|
|
1049
1846
|
if (flags.catalog) payload.catalog = String(flags.catalog).trim();
|
|
1050
1847
|
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
@@ -1065,6 +1862,22 @@ function buildIntentPayload(flags, options) {
|
|
|
1065
1862
|
return payload;
|
|
1066
1863
|
}
|
|
1067
1864
|
|
|
1865
|
+
function resolveAgentApiKey(flags, runMode) {
|
|
1866
|
+
const explicit = String(flags.agentApiKey || '').trim();
|
|
1867
|
+
if (explicit) return explicit;
|
|
1868
|
+
const envKey = String(process.env.THIRDFY_AGENT_API_KEY || '').trim();
|
|
1869
|
+
if (envKey) return envKey;
|
|
1870
|
+
const config = loadProfileConfig();
|
|
1871
|
+
const configKey = String(config?.agent?.apiKey || '').trim();
|
|
1872
|
+
if (configKey) return configKey;
|
|
1873
|
+
if (runMode === 'self') {
|
|
1874
|
+
throw new Error(
|
|
1875
|
+
'SELF_OPEN_DISABLED: self mode without agent identity is not enabled on this API yet. Set --agent-api-key, THIRDFY_AGENT_API_KEY, or login/config default.'
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
throw new Error('Missing --agent-api-key (or THIRDFY_AGENT_API_KEY / config.agent.apiKey)');
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1068
1881
|
function getActionKey(action) {
|
|
1069
1882
|
return String(action?.action || action?.id || action?.key || action?.name || '')
|
|
1070
1883
|
.trim();
|
|
@@ -1188,14 +2001,18 @@ async function resolveActionSelection(ctx, flags) {
|
|
|
1188
2001
|
}
|
|
1189
2002
|
|
|
1190
2003
|
function getAuthToken(flags, missingMessage) {
|
|
1191
|
-
const
|
|
2004
|
+
const config = loadProfileConfig();
|
|
2005
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
1192
2006
|
if (!authToken) throw new Error(missingMessage);
|
|
1193
2007
|
return authToken;
|
|
1194
2008
|
}
|
|
1195
2009
|
|
|
1196
2010
|
function buildOwnerAuthHeaders(flags, missingMessage) {
|
|
1197
|
-
const
|
|
1198
|
-
const
|
|
2011
|
+
const config = loadProfileConfig();
|
|
2012
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
2013
|
+
const ownerSessionToken = String(
|
|
2014
|
+
flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || ''
|
|
2015
|
+
).trim();
|
|
1199
2016
|
if (!authToken && !ownerSessionToken) {
|
|
1200
2017
|
throw new Error(missingMessage);
|
|
1201
2018
|
}
|
|
@@ -1224,6 +2041,116 @@ async function negotiateCapabilities(ctx) {
|
|
|
1224
2041
|
}
|
|
1225
2042
|
}
|
|
1226
2043
|
|
|
2044
|
+
function resolveUnsignedTx(result) {
|
|
2045
|
+
if (result?.unsignedTx && typeof result.unsignedTx === 'object') return result.unsignedTx;
|
|
2046
|
+
if (result?.data?.unsignedTx && typeof result.data.unsignedTx === 'object') return result.data.unsignedTx;
|
|
2047
|
+
if (result?.details?.unsignedTx && typeof result.details.unsignedTx === 'object') return result.details.unsignedTx;
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
async function loadEthers() {
|
|
2052
|
+
const candidates = ['ethers', path.join(process.cwd(), 'node_modules', 'ethers')];
|
|
2053
|
+
for (const candidate of candidates) {
|
|
2054
|
+
try {
|
|
2055
|
+
return require(candidate);
|
|
2056
|
+
} catch {
|
|
2057
|
+
// continue
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
throw new Error('Could not load ethers dependency for self-exec');
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function resolveOwsBinary() {
|
|
2064
|
+
const explicit = String(process.env.OWS_CLI_PATH || '').trim();
|
|
2065
|
+
if (explicit && fs.existsSync(explicit)) return explicit;
|
|
2066
|
+
const localBin = path.join(process.cwd(), 'node_modules', '.bin', 'ows');
|
|
2067
|
+
if (fs.existsSync(localBin)) return localBin;
|
|
2068
|
+
const which = spawnSync('which', ['ows'], { encoding: 'utf-8' });
|
|
2069
|
+
const discovered = String(which.stdout || '').trim();
|
|
2070
|
+
if (discovered) return discovered;
|
|
2071
|
+
throw new Error('ows CLI not found. Install it or set OWS_CLI_PATH.');
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function signUnsignedTxWithOws(unsignedHex, walletName, chainId) {
|
|
2075
|
+
const passphrase = String(process.env.OWS_PASSPHRASE || process.env.OWS_API_KEY || '').trim();
|
|
2076
|
+
if (!passphrase) {
|
|
2077
|
+
throw new Error('Set OWS_PASSPHRASE (or OWS_API_KEY) for self-exec signing.');
|
|
2078
|
+
}
|
|
2079
|
+
const ows = resolveOwsBinary();
|
|
2080
|
+
const txHex = String(unsignedHex).startsWith('0x') ? String(unsignedHex).slice(2) : String(unsignedHex);
|
|
2081
|
+
const res = spawnSync(
|
|
2082
|
+
ows,
|
|
2083
|
+
['sign', 'tx', '--wallet', walletName, '--chain', `eip155:${chainId}`, '--tx', txHex, '--json'],
|
|
2084
|
+
{ env: { ...process.env, OWS_PASSPHRASE: passphrase }, encoding: 'utf-8' }
|
|
2085
|
+
);
|
|
2086
|
+
const out = String(res.stdout || '').trim();
|
|
2087
|
+
if (res.status !== 0 || !out) {
|
|
2088
|
+
throw new Error(`ows sign tx failed: ${String(res.stderr || out || res.status).slice(0, 300)}`);
|
|
2089
|
+
}
|
|
2090
|
+
let parsed;
|
|
2091
|
+
try {
|
|
2092
|
+
parsed = JSON.parse(out);
|
|
2093
|
+
} catch {
|
|
2094
|
+
throw new Error(`ows sign tx returned non-json payload: ${out.slice(0, 200)}`);
|
|
2095
|
+
}
|
|
2096
|
+
const signature = String(parsed.signature || '').replace(/^0x/i, '');
|
|
2097
|
+
if (signature.length < 128) {
|
|
2098
|
+
throw new Error('Invalid OWS signature payload');
|
|
2099
|
+
}
|
|
2100
|
+
return {
|
|
2101
|
+
recoveryId: Number(parsed.recovery_id),
|
|
2102
|
+
r: `0x${signature.slice(0, 64)}`,
|
|
2103
|
+
s: `0x${signature.slice(64, 128)}`,
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
async function signAndBroadcastWithOws({ txd, chainId, walletAddress, owsWalletName, rpcUrl }) {
|
|
2108
|
+
const ethers = await loadEthers();
|
|
2109
|
+
const { JsonRpcProvider, Transaction, Signature } = ethers;
|
|
2110
|
+
const provider = new JsonRpcProvider(rpcUrl, chainId);
|
|
2111
|
+
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
|
|
2112
|
+
const fee = await provider.getFeeData();
|
|
2113
|
+
const txFields = {
|
|
2114
|
+
type: 2,
|
|
2115
|
+
chainId,
|
|
2116
|
+
nonce,
|
|
2117
|
+
to: String(txd.to).toLowerCase(),
|
|
2118
|
+
data: txd.data,
|
|
2119
|
+
value: txd.value ? BigInt(txd.value) : 0n,
|
|
2120
|
+
gasLimit: txd.gas ? BigInt(txd.gas) : txd.gasLimit ? BigInt(txd.gasLimit) : 500000n,
|
|
2121
|
+
maxFeePerGas: fee.maxFeePerGas ?? fee.gasPrice ?? 2_000_000_000n,
|
|
2122
|
+
maxPriorityFeePerGas: fee.maxPriorityFeePerGas ?? 1_000_000_000n,
|
|
2123
|
+
};
|
|
2124
|
+
const unsigned = Transaction.from(txFields);
|
|
2125
|
+
const sigParts = signUnsignedTxWithOws(unsigned.unsignedSerialized, owsWalletName, chainId);
|
|
2126
|
+
const sig = Signature.from({ r: sigParts.r, s: sigParts.s, yParity: sigParts.recoveryId });
|
|
2127
|
+
const tx = Transaction.from(unsigned.unsignedSerialized);
|
|
2128
|
+
tx.signature = sig;
|
|
2129
|
+
const sent = await provider.broadcastTransaction(tx.serialized);
|
|
2130
|
+
return { sent };
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
async function readTokenBalanceRaw(rpcUrl, walletAddress, tokenAddress) {
|
|
2134
|
+
const ethers = await loadEthers();
|
|
2135
|
+
const { JsonRpcProvider, Interface } = ethers;
|
|
2136
|
+
const provider = new JsonRpcProvider(rpcUrl);
|
|
2137
|
+
const erc20 = new Interface(['function balanceOf(address owner) view returns (uint256)']);
|
|
2138
|
+
const response = await provider.call({
|
|
2139
|
+
to: tokenAddress,
|
|
2140
|
+
data: erc20.encodeFunctionData('balanceOf', [walletAddress]),
|
|
2141
|
+
});
|
|
2142
|
+
const decoded = erc20.decodeFunctionResult('balanceOf', response);
|
|
2143
|
+
return BigInt(decoded[0].toString()).toString();
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
async function readEffectBalances(rpcUrl, walletAddress, tokenIn, tokenOut) {
|
|
2147
|
+
const [inBal, outBal] = await Promise.all([
|
|
2148
|
+
readTokenBalanceRaw(rpcUrl, walletAddress, tokenIn),
|
|
2149
|
+
readTokenBalanceRaw(rpcUrl, walletAddress, tokenOut),
|
|
2150
|
+
]);
|
|
2151
|
+
return { tokenIn: inBal, tokenOut: outBal };
|
|
2152
|
+
}
|
|
2153
|
+
|
|
1227
2154
|
async function apiGet(ctx, route, headers = {}) {
|
|
1228
2155
|
return requestJson(ctx, route, { method: 'GET', headers });
|
|
1229
2156
|
}
|
|
@@ -1308,21 +2235,33 @@ Core commands:
|
|
|
1308
2235
|
thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--json]
|
|
1309
2236
|
thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid] [--json]
|
|
1310
2237
|
thirdfy-agent profile show [--json]
|
|
2238
|
+
thirdfy-agent config set --key <path> --value <value> [--json]
|
|
2239
|
+
thirdfy-agent login [--auth-token <token> | --owner-session-token <token>] [--agent-api-key <key>] [--json]
|
|
2240
|
+
thirdfy-agent logout [--all] [--clear-agent-key] [--json]
|
|
1311
2241
|
thirdfy-agent whoami [--json]
|
|
1312
2242
|
thirdfy-agent catalogs list [--json]
|
|
1313
2243
|
thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
|
|
1314
2244
|
thirdfy-agent delegation create --auth-token <token> --agent-key <key> --owner-address <addr> --token-address <addr> --max-usd-per-day <usd> [--session-account-address <addr>] [--json]
|
|
2245
|
+
thirdfy-agent delegation init-custodial --auth-token <token> [--chain-id <id>] [--json]
|
|
2246
|
+
thirdfy-agent delegation custodial-grant --auth-token <token> --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
|
|
1315
2247
|
thirdfy-agent delegation activate --auth-token <token> --agent-key <key> --wallet-address <addr> --session-account-address <addr> --delegation-manager <addr> --delegation <json> --signature <hex> [--json]
|
|
1316
2248
|
thirdfy-agent delegation status --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2249
|
+
thirdfy-agent delegation show --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2250
|
+
thirdfy-agent delegation inspect --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2251
|
+
thirdfy-agent delegation balance --auth-token <token> --agent-key <key> [--chain-id <id>] [--json]
|
|
2252
|
+
thirdfy-agent delegation revoke --auth-token <token> --agent-key <key> [--reason <text>] [--chain-id <id>] [--json]
|
|
2253
|
+
thirdfy-agent delegation redeem --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--estimated-amount-usd <usd>] [--json]
|
|
1317
2254
|
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
|
|
1318
2255
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
|
1319
2256
|
thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
|
|
1320
2257
|
thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
|
|
2258
|
+
thirdfy-agent developer bootstrap --agent-key <key> --name <name> [--owner-session-token <token> | --challenge-id <id> --signature <hex>] [--json]
|
|
1321
2259
|
thirdfy-agent agent register --agent-key <key> --name <name> [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1322
2260
|
thirdfy-agent agent key rotate [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1323
2261
|
thirdfy-agent agent key revoke [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1324
2262
|
thirdfy-agent preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1325
2263
|
thirdfy-agent run --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--idempotency-key <key>] [--json]
|
|
2264
|
+
thirdfy-agent self-exec --agent-api-key <key> --action <id> --params <json> --wallet-address <addr> --ows-wallet-name <name> [--rpc-url <url>] [--json]
|
|
1326
2265
|
thirdfy-agent jeff preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1327
2266
|
thirdfy-agent jeff trade --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1328
2267
|
thirdfy-agent jeff status --intent-id <id> [--json]
|
|
@@ -1336,6 +2275,7 @@ Global flags:
|
|
|
1336
2275
|
--profile <name> personal | builder | network
|
|
1337
2276
|
--env <file> load env vars from file
|
|
1338
2277
|
--timeout <ms> request timeout
|
|
2278
|
+
--params <json> for swap: exactly one of {amountInRaw} or {amountInHuman + tokenInDecimals}
|
|
1339
2279
|
--owner-session-token <token> owner session token (or THIRDFY_OWNER_SESSION_TOKEN)
|
|
1340
2280
|
--json stable machine-readable output
|
|
1341
2281
|
--verbose print adapter diagnostics
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thirdfy/agent-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"e2e:base:real": "node ./scripts/e2e/base-realtx.mjs",
|
|
24
24
|
"e2e:delegation:discovery": "node ./scripts/e2e/delegation-discovery.mjs",
|
|
25
25
|
"e2e:delegation:lifecycle": "node ./scripts/e2e/delegation-lifecycle.mjs",
|
|
26
|
+
"e2e:delegation:commands": "node ./scripts/e2e/delegation-command-parity.mjs",
|
|
27
|
+
"e2e:self:delegation:parity": "node ./scripts/e2e/self-delegation-parity.mjs",
|
|
26
28
|
"e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
|
|
27
29
|
"e2e:report:last": "node ./scripts/e2e/report-last.mjs",
|
|
28
30
|
"smoke:cli": "node ./bin/thirdfy-agent.mjs --help && node ./bin/thirdfy-agent.mjs --version --json",
|