@thirdfy/agent-cli 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/README.md +50 -0
- package/bin/thirdfy-agent.mjs +312 -82
- package/package.json +7 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@thirdfy/agent-cli` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.8] - 2026-04-07
|
|
8
|
+
|
|
9
|
+
**npm:** [`@thirdfy/agent-cli@0.1.8`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.8)
|
|
10
|
+
**Git tag:** `v0.1.8`
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Managed custodial lifecycle validation:** Added CLI E2E path for managed wallet delegation parity (`e2e:managed:delegation:parity`) and integrated coverage into production certification flow.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Self execution diagnostics:** `doctor self` is now routed as an offline/local command and no longer depends on capabilities negotiation.
|
|
19
|
+
- **Managed wallet command routing:** Removed thin wrapper indirection and wired `managed wallet init/grant` directly to delegation custodial handlers.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Effect check payload safety:** Hardened relaxed-mode effect-check fallback so warning payload shape remains deterministic in edge paths.
|
|
24
|
+
- **Managed wallet integration test contract:** Validation-only detection now correctly uses `executionLane === 'validation_only'`, with explicit auth env isolation for deterministic header assertions.
|
|
25
|
+
|
|
26
|
+
## [0.1.7] - 2026-04-05
|
|
27
|
+
|
|
28
|
+
**npm:** [`@thirdfy/agent-cli@0.1.7`](https://www.npmjs.com/package/@thirdfy/agent-cli/v/0.1.7)
|
|
29
|
+
**Git tag:** [`v0.1.7`](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.7)
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Login and local config:** `thirdfy-agent login`, `logout` (including `logout --all` for local clear plus owner-session revoke when applicable), `whoami`, and `config set` / `config show` with defaults persisted under `~/.thirdfy/config.json`. Credential precedence remains: CLI flags, then environment variables, then saved config (see README).
|
|
34
|
+
- **Custodial delegation (MetaMask-compatible path):** `delegation init-custodial` and `delegation custodial-grant` alongside the existing `delegation create` / `delegation activate` lifecycle.
|
|
35
|
+
- **Delegation operator ergonomics:** `delegation show` as an alias for `delegation status`; expanded delegation inspection and parity-focused behavior for automation.
|
|
36
|
+
- **E2E / certification:** Delegation parity and production certification scripts (`e2e:self:delegation:parity`, `e2e:delegation:prod:certify`) and related gates documented in internal runbooks.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **`delegation redeem`:** Respects the effective run mode (flag / env / profile) when building the execution path.
|
|
41
|
+
- **Swap amount handling:** Clearer separation between *missing* and *ambiguous* amount-unit errors; stricter validation of human-form amounts (including excess precision) and decimals handling for `amountInHuman` flows.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Config and bootstrap:** Safer bootstrap output, tighter validation, preserved config semantics on upgrade paths, and `config set` rejects prototype-path keys to avoid confusing or unsafe key shapes.
|
|
46
|
+
- **Login / token handling:** Stricter verification of token shape after login and safer config cloning for delegation-related routing.
|
|
47
|
+
- **E2E:** Session-auth paths keep a dev-mock wallet fallback where intended so local and CI simulations stay stable.
|
|
48
|
+
|
|
49
|
+
### Tooling
|
|
50
|
+
|
|
51
|
+
- **Release workflow:** When a version is already published to npm, CI skips `npm publish` so tag-based runs can still create or align GitHub releases without duplicate-publish failures.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## [0.1.6] - 2026-03-30
|
|
56
|
+
|
|
57
|
+
Prior release; see [git compare `v0.1.6...v0.1.7`](https://github.com/thirdfy/agent-cli/compare/v0.1.6...v0.1.7) for the full commit list.
|
package/README.md
CHANGED
|
@@ -133,6 +133,41 @@ thirdfy-agent whoami --json
|
|
|
133
133
|
|
|
134
134
|
`profile use --profile <name>` resets run mode to that profile default unless `--run-mode` is explicitly provided.
|
|
135
135
|
|
|
136
|
+
### One-command self execution (BYOW)
|
|
137
|
+
|
|
138
|
+
Use `--broadcast` with `run --run-mode self` to keep one deterministic command for build/sign/send:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
thirdfy-agent run \
|
|
142
|
+
--agent-api-key "$AGENT_API_KEY" \
|
|
143
|
+
--run-mode self \
|
|
144
|
+
--broadcast \
|
|
145
|
+
--action swap \
|
|
146
|
+
--params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","chainId":8453}' \
|
|
147
|
+
--json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The default `run --run-mode self` behavior is still unsigned-only (advanced users can sign externally).
|
|
151
|
+
|
|
152
|
+
Self lane effect-check controls:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# strict: fail if no observable token delta
|
|
156
|
+
thirdfy-agent self-exec ... --effect-check strict --json
|
|
157
|
+
|
|
158
|
+
# relaxed (default): retry/read balances, warn on mismatch, do not false-fail the tx
|
|
159
|
+
thirdfy-agent self-exec ... --effect-check relaxed --json
|
|
160
|
+
|
|
161
|
+
# off: skip post-trade token-delta verification
|
|
162
|
+
thirdfy-agent self-exec ... --effect-check off --json
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Signer diagnostics:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
thirdfy-agent doctor self --json
|
|
169
|
+
```
|
|
170
|
+
|
|
136
171
|
## Swap amount contract (agent-safe)
|
|
137
172
|
|
|
138
173
|
For `--action swap`, CLI enforces an explicit amount unit contract:
|
|
@@ -251,6 +286,16 @@ The CLI keeps compatibility with legacy `delegation create` + `delegation activa
|
|
|
251
286
|
|
|
252
287
|
`delegation show` is a direct alias to `delegation status` for gator-style operator ergonomics.
|
|
253
288
|
|
|
289
|
+
Managed easy-wallet command group:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
thirdfy-agent managed wallet init --auth-token "$THIRDFY_AUTH_TOKEN" --json
|
|
293
|
+
# or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
|
|
294
|
+
thirdfy-agent managed wallet grant --auth-token "$THIRDFY_AUTH_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --token-address "0x..." --max-usd-per-day 250 --json
|
|
295
|
+
# or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
|
|
296
|
+
thirdfy-agent managed run --agent-api-key "$AGENT_API_KEY" --run-mode thirdfy --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --json
|
|
297
|
+
```
|
|
298
|
+
|
|
254
299
|
## More docs
|
|
255
300
|
|
|
256
301
|
- OpenClaw CLI onboarding flow:
|
|
@@ -264,11 +309,16 @@ The CLI keeps compatibility with legacy `delegation create` + `delegation activa
|
|
|
264
309
|
npm run validate:cli-contract-schemas
|
|
265
310
|
npm test
|
|
266
311
|
npm run smoke:cli
|
|
312
|
+
npm run e2e:managed:delegation:parity
|
|
267
313
|
```
|
|
268
314
|
|
|
269
315
|
## Release
|
|
270
316
|
|
|
317
|
+
Version history and user-facing notes: [CHANGELOG.md](./CHANGELOG.md) (for example [0.1.7](https://github.com/thirdfy/agent-cli/releases/tag/v0.1.7) — login/config UX, custodial delegation helpers, swap and config hardening).
|
|
318
|
+
|
|
271
319
|
```bash
|
|
272
320
|
npm version patch
|
|
273
321
|
npm publish --access public
|
|
274
322
|
```
|
|
323
|
+
|
|
324
|
+
After publishing, push the matching tag (for example `v0.1.7`) so the [Release workflow](.github/workflows/release.yml) can create a GitHub release. If the package version is already on npm, the workflow skips republishing and still creates the release when triggered by a tag.
|
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -19,6 +19,7 @@ const DEFAULT_API_BASE = 'https://api.thirdfy.com';
|
|
|
19
19
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
20
20
|
const DEFAULT_CATALOG_CACHE_TTL_MS = 60_000;
|
|
21
21
|
const RUN_MODES = ['thirdfy', 'self', 'hybrid'];
|
|
22
|
+
const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
|
|
22
23
|
const PROFILE_DEFAULTS = {
|
|
23
24
|
personal: 'self',
|
|
24
25
|
builder: 'hybrid',
|
|
@@ -118,6 +119,9 @@ async function main() {
|
|
|
118
119
|
case 'whoami':
|
|
119
120
|
await commandProfileShow(context, subFlags);
|
|
120
121
|
return;
|
|
122
|
+
case 'doctor self':
|
|
123
|
+
await commandDoctorSelf(context, subFlags);
|
|
124
|
+
return;
|
|
121
125
|
default:
|
|
122
126
|
break;
|
|
123
127
|
}
|
|
@@ -125,6 +129,9 @@ async function main() {
|
|
|
125
129
|
const capabilities = await negotiateCapabilities(context);
|
|
126
130
|
|
|
127
131
|
switch (commandKey) {
|
|
132
|
+
case 'managed run':
|
|
133
|
+
await commandManagedRun(context, subFlags, capabilities);
|
|
134
|
+
return;
|
|
128
135
|
case 'jeff trade':
|
|
129
136
|
await commandRun(context, subFlags, capabilities);
|
|
130
137
|
return;
|
|
@@ -201,6 +208,14 @@ async function main() {
|
|
|
201
208
|
await commandAgentAuthVerify(context, subFlags, capabilities);
|
|
202
209
|
return;
|
|
203
210
|
}
|
|
211
|
+
if (commandKey3 === 'managed wallet init') {
|
|
212
|
+
await commandDelegationInitCustodial(context, subFlags, capabilities);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (commandKey3 === 'managed wallet grant') {
|
|
216
|
+
await commandDelegationCustodialGrant(context, subFlags, capabilities);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
204
219
|
|
|
205
220
|
const rootCommand = parsed.positionals[0];
|
|
206
221
|
switch (rootCommand) {
|
|
@@ -533,6 +548,26 @@ async function commandRun(ctx, flags, capabilities) {
|
|
|
533
548
|
skipPreflight,
|
|
534
549
|
});
|
|
535
550
|
const normalized = backendResult.normalized;
|
|
551
|
+
if (runMode === 'self' && parseBooleanFlag(flags.broadcast, false) && normalized?.success) {
|
|
552
|
+
const executed = await performSelfBroadcast(ctx, flags, resolved.resolvedAction, normalized);
|
|
553
|
+
printEnvelope({
|
|
554
|
+
success: true,
|
|
555
|
+
code: 'SELF_EXECUTED',
|
|
556
|
+
message: 'Self-custody execution completed',
|
|
557
|
+
data: executed,
|
|
558
|
+
meta: {
|
|
559
|
+
apiBase: ctx.apiBase,
|
|
560
|
+
requestedAction: resolved.requestedAction,
|
|
561
|
+
resolvedAction: resolved.resolvedAction,
|
|
562
|
+
runMode,
|
|
563
|
+
profile: ctx.profile || null,
|
|
564
|
+
skippedPreflight: skipPreflight,
|
|
565
|
+
swapInput: flags.__swapInput || null,
|
|
566
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
536
571
|
printEnvelope({
|
|
537
572
|
success: normalized.success,
|
|
538
573
|
code: backendResult.code,
|
|
@@ -1194,108 +1229,124 @@ async function commandSelfExec(ctx, flags, capabilities) {
|
|
|
1194
1229
|
process.exit(1);
|
|
1195
1230
|
}
|
|
1196
1231
|
|
|
1197
|
-
const
|
|
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
|
-
}
|
|
1232
|
+
const executed = await performSelfBroadcast(ctx, flags, resolved.resolvedAction, selfResult);
|
|
1212
1233
|
|
|
1213
|
-
|
|
1234
|
+
printEnvelope({
|
|
1235
|
+
success: true,
|
|
1236
|
+
code: 'SELF_EXECUTED',
|
|
1237
|
+
message: 'Self-custody execution completed',
|
|
1238
|
+
data: executed,
|
|
1239
|
+
meta: {
|
|
1240
|
+
apiBase: ctx.apiBase,
|
|
1241
|
+
runMode: 'self',
|
|
1242
|
+
resolvedAction: resolved.resolvedAction,
|
|
1243
|
+
swapInput: flags.__swapInput || null,
|
|
1244
|
+
},
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
async function commandDoctorSelf(ctx, flags) {
|
|
1214
1249
|
const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
|
|
1215
1250
|
const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
|
|
1216
|
-
const rpcUrl = String(
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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();
|
|
1251
|
+
const rpcUrl = String(
|
|
1252
|
+
flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org'
|
|
1253
|
+
).trim();
|
|
1254
|
+
const passphrasePresent = Boolean(String(process.env.OWS_PASSPHRASE || process.env.OWS_API_KEY || '').trim());
|
|
1255
|
+
const checks = [];
|
|
1227
1256
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1257
|
+
checks.push({
|
|
1258
|
+
name: 'wallet-address',
|
|
1259
|
+
ok: Boolean(walletAddress),
|
|
1260
|
+
detail: walletAddress || 'missing --wallet-address / MARKET_MAKER_WALLET_ADDRESS',
|
|
1261
|
+
});
|
|
1262
|
+
checks.push({
|
|
1263
|
+
name: 'ows-wallet-name',
|
|
1264
|
+
ok: Boolean(owsWalletName),
|
|
1265
|
+
detail: owsWalletName || 'missing --ows-wallet-name / OWS_WALLET_NAME',
|
|
1266
|
+
});
|
|
1267
|
+
checks.push({
|
|
1268
|
+
name: 'ows-passphrase',
|
|
1269
|
+
ok: passphrasePresent,
|
|
1270
|
+
detail: passphrasePresent ? 'present' : 'missing OWS_PASSPHRASE (or OWS_API_KEY fallback)',
|
|
1271
|
+
});
|
|
1232
1272
|
|
|
1233
|
-
let submission;
|
|
1234
1273
|
try {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
chainId,
|
|
1238
|
-
walletAddress,
|
|
1239
|
-
owsWalletName,
|
|
1240
|
-
rpcUrl,
|
|
1241
|
-
});
|
|
1274
|
+
resolveOwsBinary();
|
|
1275
|
+
checks.push({ name: 'ows-binary', ok: true, detail: 'found' });
|
|
1242
1276
|
} catch (error) {
|
|
1243
|
-
|
|
1277
|
+
checks.push({ name: 'ows-binary', ok: false, detail: error?.message || 'not found' });
|
|
1244
1278
|
}
|
|
1245
1279
|
|
|
1246
|
-
let receipt;
|
|
1247
1280
|
try {
|
|
1248
|
-
|
|
1281
|
+
await loadEthers();
|
|
1282
|
+
checks.push({ name: 'ethers-runtime', ok: true, detail: 'loaded' });
|
|
1249
1283
|
} catch (error) {
|
|
1250
|
-
|
|
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
|
-
});
|
|
1284
|
+
checks.push({ name: 'ethers-runtime', ok: false, detail: error?.message || 'missing ethers dependency' });
|
|
1258
1285
|
}
|
|
1259
1286
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
const
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
-
}
|
|
1287
|
+
try {
|
|
1288
|
+
const ethers = await loadEthers();
|
|
1289
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
1290
|
+
const block = await provider.getBlockNumber();
|
|
1291
|
+
checks.push({ name: 'rpc-connectivity', ok: Number.isFinite(block), detail: `latestBlock=${block}` });
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
checks.push({ name: 'rpc-connectivity', ok: false, detail: error?.message || 'rpc check failed' });
|
|
1278
1294
|
}
|
|
1279
1295
|
|
|
1296
|
+
const ok = checks.every((entry) => entry.ok);
|
|
1280
1297
|
printEnvelope({
|
|
1281
|
-
success:
|
|
1282
|
-
code: '
|
|
1283
|
-
message: 'Self
|
|
1298
|
+
success: ok,
|
|
1299
|
+
code: ok ? 'DOCTOR_SELF_OK' : 'DOCTOR_SELF_FAILED',
|
|
1300
|
+
message: ok ? 'Self lane signer diagnostics passed' : 'Self lane signer diagnostics found blocking issues',
|
|
1284
1301
|
data: {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
effectCheck,
|
|
1302
|
+
checks,
|
|
1303
|
+
hints: [
|
|
1304
|
+
'For one-command self execution use: thirdfy-agent run --run-mode self --broadcast ...',
|
|
1305
|
+
'Set --effect-check strict|relaxed|off to control post-trade effect validation behavior.',
|
|
1306
|
+
],
|
|
1291
1307
|
},
|
|
1292
1308
|
meta: {
|
|
1293
1309
|
apiBase: ctx.apiBase,
|
|
1294
1310
|
runMode: 'self',
|
|
1311
|
+
rpcUrl,
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
if (!ok) process.exit(1);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
async function commandManagedRun(ctx, flags, capabilities) {
|
|
1318
|
+
const mode = normalizeManagedRunMode(flags.runMode || 'thirdfy');
|
|
1319
|
+
const managedFlags = { ...flags, runMode: mode };
|
|
1320
|
+
const resolved = await resolveActionSelection(ctx, managedFlags);
|
|
1321
|
+
prepareActionParamsForFlags(managedFlags, resolved.resolvedAction);
|
|
1322
|
+
enforceChainCompatibility(capabilities, managedFlags, mode);
|
|
1323
|
+
const skipPreflight = Boolean(managedFlags.skipPreflight);
|
|
1324
|
+
const backendResult = await runExecutionByMode(mode, ctx, managedFlags, resolved, {
|
|
1325
|
+
skipPreflight,
|
|
1326
|
+
});
|
|
1327
|
+
const normalized = backendResult.normalized;
|
|
1328
|
+
printEnvelope({
|
|
1329
|
+
success: normalized.success,
|
|
1330
|
+
code: normalized.success ? 'MANAGED_RUN_QUEUED' : backendResult.code,
|
|
1331
|
+
message: normalized.success
|
|
1332
|
+
? mode === 'hybrid'
|
|
1333
|
+
? 'Managed easy-wallet hybrid execution prepared'
|
|
1334
|
+
: 'Managed easy-wallet execution queued'
|
|
1335
|
+
: backendResult.message,
|
|
1336
|
+
data: normalized,
|
|
1337
|
+
meta: {
|
|
1338
|
+
apiBase: ctx.apiBase,
|
|
1339
|
+
requestedAction: resolved.requestedAction,
|
|
1295
1340
|
resolvedAction: resolved.resolvedAction,
|
|
1296
|
-
|
|
1341
|
+
lane: 'managed_easy_wallet',
|
|
1342
|
+
runMode: mode,
|
|
1343
|
+
skippedPreflight: skipPreflight,
|
|
1344
|
+
profile: ctx.profile || null,
|
|
1345
|
+
swapInput: managedFlags.__swapInput || null,
|
|
1346
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
1297
1347
|
},
|
|
1298
1348
|
});
|
|
1349
|
+
if (!normalized.success) process.exit(1);
|
|
1299
1350
|
}
|
|
1300
1351
|
|
|
1301
1352
|
async function commandProfileInit(ctx, flags) {
|
|
@@ -1543,6 +1594,14 @@ function normalizeRunMode(value) {
|
|
|
1543
1594
|
throw new Error(`Unsupported --run-mode "${value}". Supported: ${RUN_MODES.join(', ')}`);
|
|
1544
1595
|
}
|
|
1545
1596
|
|
|
1597
|
+
function normalizeManagedRunMode(value) {
|
|
1598
|
+
const mode = normalizeRunMode(value || 'thirdfy');
|
|
1599
|
+
if (mode === 'self') {
|
|
1600
|
+
throw new Error('Managed lane does not support run-mode=self. Use thirdfy or hybrid.');
|
|
1601
|
+
}
|
|
1602
|
+
return mode;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1546
1605
|
function getProfileConfigPath() {
|
|
1547
1606
|
return path.join(os.homedir(), '.thirdfy', 'config.json');
|
|
1548
1607
|
}
|
|
@@ -2048,6 +2107,171 @@ function resolveUnsignedTx(result) {
|
|
|
2048
2107
|
return null;
|
|
2049
2108
|
}
|
|
2050
2109
|
|
|
2110
|
+
function parseBooleanFlag(value, defaultValue = false) {
|
|
2111
|
+
if (value === undefined || value === null) return defaultValue;
|
|
2112
|
+
if (typeof value === 'boolean') return value;
|
|
2113
|
+
const normalized = String(value).trim().toLowerCase();
|
|
2114
|
+
if (!normalized) return defaultValue;
|
|
2115
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
2116
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
2117
|
+
return defaultValue;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
function normalizeEffectCheckMode(value) {
|
|
2121
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
2122
|
+
if (!normalized) return 'relaxed';
|
|
2123
|
+
if (EFFECT_CHECK_MODES.includes(normalized)) return normalized;
|
|
2124
|
+
throw createCliError(
|
|
2125
|
+
'EFFECT_CHECK_MODE_INVALID',
|
|
2126
|
+
`Unsupported --effect-check "${value}". Supported: ${EFFECT_CHECK_MODES.join(', ')}.`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
async function sleepMs(ms) {
|
|
2131
|
+
const delay = Number(ms || 0);
|
|
2132
|
+
if (!Number.isFinite(delay) || delay <= 0) return;
|
|
2133
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
async function resolveEffectCheckResult({
|
|
2137
|
+
mode,
|
|
2138
|
+
rpcUrl,
|
|
2139
|
+
walletAddress,
|
|
2140
|
+
tokenIn,
|
|
2141
|
+
tokenOut,
|
|
2142
|
+
balancesBefore,
|
|
2143
|
+
retries,
|
|
2144
|
+
waitMs,
|
|
2145
|
+
}) {
|
|
2146
|
+
const baseResult = {
|
|
2147
|
+
checked: true,
|
|
2148
|
+
mode,
|
|
2149
|
+
hasEffect: false,
|
|
2150
|
+
inDeltaRaw: '0',
|
|
2151
|
+
outDeltaRaw: '0',
|
|
2152
|
+
attempts: 0,
|
|
2153
|
+
retries: Math.max(0, Number(retries || 0)),
|
|
2154
|
+
};
|
|
2155
|
+
if (mode === 'off') {
|
|
2156
|
+
return { checked: false, mode, hasEffect: null, inDeltaRaw: null, outDeltaRaw: null, reason: 'disabled' };
|
|
2157
|
+
}
|
|
2158
|
+
if (!tokenIn || !tokenOut || !balancesBefore) {
|
|
2159
|
+
return { checked: false, mode, hasEffect: null, inDeltaRaw: null, outDeltaRaw: null, reason: 'token_pair_missing' };
|
|
2160
|
+
}
|
|
2161
|
+
const maxAttempts = Math.max(1, Number(retries || 0) + 1);
|
|
2162
|
+
let last = null;
|
|
2163
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2164
|
+
const balancesAfter = await readEffectBalances(rpcUrl, walletAddress, tokenIn, tokenOut);
|
|
2165
|
+
const inDeltaRaw = (BigInt(balancesBefore.tokenIn) - BigInt(balancesAfter.tokenIn)).toString();
|
|
2166
|
+
const outDeltaRaw = (BigInt(balancesAfter.tokenOut) - BigInt(balancesBefore.tokenOut)).toString();
|
|
2167
|
+
const hasEffect = BigInt(inDeltaRaw) > 0n || BigInt(outDeltaRaw) > 0n;
|
|
2168
|
+
last = {
|
|
2169
|
+
checked: true,
|
|
2170
|
+
mode,
|
|
2171
|
+
hasEffect,
|
|
2172
|
+
inDeltaRaw,
|
|
2173
|
+
outDeltaRaw,
|
|
2174
|
+
attempts: attempt,
|
|
2175
|
+
retries: maxAttempts - 1,
|
|
2176
|
+
};
|
|
2177
|
+
if (hasEffect) return last;
|
|
2178
|
+
if (attempt < maxAttempts) await sleepMs(waitMs);
|
|
2179
|
+
}
|
|
2180
|
+
return {
|
|
2181
|
+
...(last || baseResult),
|
|
2182
|
+
warning: 'No balance delta observed after retries; treating as non-fatal in relaxed mode.',
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
async function performSelfBroadcast(ctx, flags, resolvedAction, selfResult) {
|
|
2187
|
+
const txd = resolveUnsignedTx(selfResult);
|
|
2188
|
+
if (!txd?.to || !txd?.data) {
|
|
2189
|
+
throw createCliError('SELF_UNSIGNED_TX_MISSING', 'Self execution did not return a valid unsigned transaction');
|
|
2190
|
+
}
|
|
2191
|
+
const chainId = Number(txd.chainId || flags.chainId || 8453);
|
|
2192
|
+
const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
|
|
2193
|
+
const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
|
|
2194
|
+
const rpcUrl = String(
|
|
2195
|
+
flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org'
|
|
2196
|
+
).trim();
|
|
2197
|
+
if (!walletAddress) {
|
|
2198
|
+
throw createCliError('SIGNING_FAILED', 'Missing --wallet-address (or MARKET_MAKER_WALLET_ADDRESS) for self-exec signing.');
|
|
2199
|
+
}
|
|
2200
|
+
if (!owsWalletName) {
|
|
2201
|
+
throw createCliError('SIGNING_FAILED', 'Missing --ows-wallet-name (or OWS_WALLET_NAME) for self-exec signing.');
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
const params = getPreparedParams(flags);
|
|
2205
|
+
const effectTokenIn = String(flags.effectTokenIn || params.tokenIn || '').trim();
|
|
2206
|
+
const effectTokenOut = String(flags.effectTokenOut || params.tokenOut || '').trim();
|
|
2207
|
+
const effectMode = normalizeEffectCheckMode(flags.effectCheck || process.env.THIRDFY_EFFECT_CHECK || 'relaxed');
|
|
2208
|
+
const effectRetries = Number(flags.effectCheckRetries || process.env.THIRDFY_EFFECT_CHECK_RETRIES || 2);
|
|
2209
|
+
const effectWaitMs = Number(flags.effectCheckWaitMs || process.env.THIRDFY_EFFECT_CHECK_WAIT_MS || 1200);
|
|
2210
|
+
|
|
2211
|
+
let balancesBefore = null;
|
|
2212
|
+
if (effectMode !== 'off' && effectTokenIn && effectTokenOut) {
|
|
2213
|
+
balancesBefore = await readEffectBalances(rpcUrl, walletAddress, effectTokenIn, effectTokenOut);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
let submission;
|
|
2217
|
+
try {
|
|
2218
|
+
submission = await signAndBroadcastWithOws({
|
|
2219
|
+
txd,
|
|
2220
|
+
chainId,
|
|
2221
|
+
walletAddress,
|
|
2222
|
+
owsWalletName,
|
|
2223
|
+
rpcUrl,
|
|
2224
|
+
});
|
|
2225
|
+
} catch (error) {
|
|
2226
|
+
throw createCliError('SIGNING_FAILED', error?.message || 'OWS signing failed');
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
let receipt;
|
|
2230
|
+
try {
|
|
2231
|
+
receipt = await submission.sent.wait();
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
throw createCliError('BROADCAST_FAILED', error?.message || 'Broadcast wait failed');
|
|
2234
|
+
}
|
|
2235
|
+
const status = Number(receipt?.status ?? 0);
|
|
2236
|
+
if (status !== 1) {
|
|
2237
|
+
throw createCliError('BROADCAST_FAILED', 'Transaction receipt status is not success', {
|
|
2238
|
+
receiptStatus: receipt?.status ?? null,
|
|
2239
|
+
txHash: submission.sent.hash,
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
const effectCheck = await resolveEffectCheckResult({
|
|
2244
|
+
mode: effectMode,
|
|
2245
|
+
rpcUrl,
|
|
2246
|
+
walletAddress,
|
|
2247
|
+
tokenIn: effectTokenIn,
|
|
2248
|
+
tokenOut: effectTokenOut,
|
|
2249
|
+
balancesBefore,
|
|
2250
|
+
retries: effectRetries,
|
|
2251
|
+
waitMs: effectWaitMs,
|
|
2252
|
+
});
|
|
2253
|
+
if (effectCheck.checked && !effectCheck.hasEffect && effectMode === 'strict') {
|
|
2254
|
+
throw createCliError('EFFECT_CHECK_FAILED', 'Transaction mined but did not produce observable token effect.', {
|
|
2255
|
+
txHash: submission.sent.hash,
|
|
2256
|
+
effectCheck,
|
|
2257
|
+
mode: effectMode,
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
return {
|
|
2262
|
+
txHash: submission.sent.hash,
|
|
2263
|
+
chainId,
|
|
2264
|
+
blockNumber: receipt?.blockNumber ?? null,
|
|
2265
|
+
status: receipt?.status ?? null,
|
|
2266
|
+
unsignedTx: txd,
|
|
2267
|
+
effectCheck,
|
|
2268
|
+
lane: 'self',
|
|
2269
|
+
signer: 'ows',
|
|
2270
|
+
resolvedAction,
|
|
2271
|
+
apiBase: ctx.apiBase,
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2051
2275
|
async function loadEthers() {
|
|
2052
2276
|
const candidates = ['ethers', path.join(process.cwd(), 'node_modules', 'ethers')];
|
|
2053
2277
|
for (const candidate of candidates) {
|
|
@@ -2242,8 +2466,8 @@ Core commands:
|
|
|
2242
2466
|
thirdfy-agent catalogs list [--json]
|
|
2243
2467
|
thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
|
|
2244
2468
|
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]
|
|
2469
|
+
thirdfy-agent delegation init-custodial [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
|
|
2470
|
+
thirdfy-agent delegation custodial-grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
|
|
2247
2471
|
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]
|
|
2248
2472
|
thirdfy-agent delegation status --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2249
2473
|
thirdfy-agent delegation show --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
@@ -2251,6 +2475,9 @@ Core commands:
|
|
|
2251
2475
|
thirdfy-agent delegation balance --auth-token <token> --agent-key <key> [--chain-id <id>] [--json]
|
|
2252
2476
|
thirdfy-agent delegation revoke --auth-token <token> --agent-key <key> [--reason <text>] [--chain-id <id>] [--json]
|
|
2253
2477
|
thirdfy-agent delegation redeem --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--estimated-amount-usd <usd>] [--json]
|
|
2478
|
+
thirdfy-agent managed wallet init [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
|
|
2479
|
+
thirdfy-agent managed wallet grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
|
|
2480
|
+
thirdfy-agent managed run --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--idempotency-key <key>] [--json]
|
|
2254
2481
|
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
|
|
2255
2482
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
|
2256
2483
|
thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
|
|
@@ -2260,8 +2487,9 @@ Core commands:
|
|
|
2260
2487
|
thirdfy-agent agent key rotate [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
2261
2488
|
thirdfy-agent agent key revoke [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
2262
2489
|
thirdfy-agent preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
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]
|
|
2490
|
+
thirdfy-agent run --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--broadcast] [--idempotency-key <key>] [--json]
|
|
2491
|
+
thirdfy-agent self-exec --agent-api-key <key> --action <id> --params <json> --wallet-address <addr> --ows-wallet-name <name> [--rpc-url <url>] [--effect-check strict|relaxed|off] [--json]
|
|
2492
|
+
thirdfy-agent doctor self [--wallet-address <addr>] [--ows-wallet-name <name>] [--rpc-url <url>] [--json]
|
|
2265
2493
|
thirdfy-agent jeff preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
2266
2494
|
thirdfy-agent jeff trade --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
2267
2495
|
thirdfy-agent jeff status --intent-id <id> [--json]
|
|
@@ -2276,6 +2504,8 @@ Global flags:
|
|
|
2276
2504
|
--env <file> load env vars from file
|
|
2277
2505
|
--timeout <ms> request timeout
|
|
2278
2506
|
--params <json> for swap: exactly one of {amountInRaw} or {amountInHuman + tokenInDecimals}
|
|
2507
|
+
--broadcast with run --run-mode self, sign and broadcast using OWS
|
|
2508
|
+
--effect-check <mode> self-exec check mode: strict | relaxed | off (default: relaxed)
|
|
2279
2509
|
--owner-session-token <token> owner session token (or THIRDFY_OWNER_SESSION_TOKEN)
|
|
2280
2510
|
--json stable machine-readable output
|
|
2281
2511
|
--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.8",
|
|
4
4
|
"description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"src/contracts/cli/schemas/",
|
|
13
13
|
"scripts/validate-cli-contract-schemas.mjs",
|
|
14
14
|
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
"e2e:delegation:lifecycle": "node ./scripts/e2e/delegation-lifecycle.mjs",
|
|
26
27
|
"e2e:delegation:commands": "node ./scripts/e2e/delegation-command-parity.mjs",
|
|
27
28
|
"e2e:self:delegation:parity": "node ./scripts/e2e/self-delegation-parity.mjs",
|
|
29
|
+
"e2e:managed:delegation:parity": "node ./scripts/e2e/managed-custodial-lifecycle.mjs",
|
|
28
30
|
"e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
|
|
29
31
|
"e2e:report:last": "node ./scripts/e2e/report-last.mjs",
|
|
30
32
|
"smoke:cli": "node ./bin/thirdfy-agent.mjs --help && node ./bin/thirdfy-agent.mjs --version --json",
|
|
@@ -48,5 +50,8 @@
|
|
|
48
50
|
"bugs": {
|
|
49
51
|
"url": "https://github.com/thirdfy/agent-cli/issues"
|
|
50
52
|
},
|
|
51
|
-
"homepage": "https://github.com/thirdfy/agent-cli#readme"
|
|
53
|
+
"homepage": "https://github.com/thirdfy/agent-cli#readme",
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"ethers": "^6.16.0"
|
|
56
|
+
}
|
|
52
57
|
}
|