@thirdfy/agent-cli 0.1.4 → 0.1.6
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 +44 -9
- package/bin/thirdfy-agent.mjs +89 -27
- package/package.json +8 -1
- package/src/utils/cli/intentNormalization.cjs +20 -1
package/README.md
CHANGED
|
@@ -16,6 +16,9 @@ If you want the full developer docs, start here:
|
|
|
16
16
|
- [API Reference home](https://docs.thirdfy.com/api)
|
|
17
17
|
- [LLM docs map (llms.txt)](https://docs.thirdfy.com/llms.txt)
|
|
18
18
|
- [LLM full API snapshot (llms-full.txt)](https://docs.thirdfy.com/llms-full.txt)
|
|
19
|
+
- [DogeOS quickstart (repo)](./docs/dogeos-quickstart.md)
|
|
20
|
+
- [Action onboarding standard](./docs/action-onboarding-standard.md)
|
|
21
|
+
- [Skill integration RFC](./docs/skill-integration-rfc.md)
|
|
19
22
|
|
|
20
23
|
## Install
|
|
21
24
|
|
|
@@ -33,18 +36,19 @@ npx @thirdfy/agent-cli --help
|
|
|
33
36
|
## Quick start
|
|
34
37
|
|
|
35
38
|
```bash
|
|
36
|
-
export THIRDFY_API_BASE="https://api.thirdfy.com"
|
|
37
39
|
export AGENT_API_KEY="..."
|
|
38
40
|
export THIRDFY_AUTH_TOKEN="..."
|
|
39
41
|
export THIRDFY_OWNER_SESSION_TOKEN="..."
|
|
40
42
|
|
|
41
|
-
thirdfy-agent actions --
|
|
43
|
+
thirdfy-agent actions --agent-api-key "$AGENT_API_KEY" --json
|
|
42
44
|
thirdfy-agent profile init --profile personal --json
|
|
43
|
-
thirdfy-agent preflight --
|
|
44
|
-
thirdfy-agent run --
|
|
45
|
-
thirdfy-agent intent-status --
|
|
45
|
+
thirdfy-agent preflight --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountIn":"1000000"}' --estimated-amount-usd 25 --json
|
|
46
|
+
thirdfy-agent run --agent-api-key "$AGENT_API_KEY" --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountIn":"1000000"}' --estimated-amount-usd 25 --json
|
|
47
|
+
thirdfy-agent intent-status --intent-id "<intentId>" --json
|
|
46
48
|
```
|
|
47
49
|
|
|
50
|
+
By default, the CLI targets production (`https://api.thirdfy.com`). Use `--api-base` (or `THIRDFY_API_BASE`) only for staging/dev/custom deployments.
|
|
51
|
+
|
|
48
52
|
## Auth model
|
|
49
53
|
|
|
50
54
|
- `AGENT_API_KEY`: execution identity and policy-aware action access (`actions --agent-api-key`, `preflight`, `run`)
|
|
@@ -56,15 +60,15 @@ Execution-only workflows can run with only `AGENT_API_KEY` after onboarding is c
|
|
|
56
60
|
### Wallet-sign onboarding path
|
|
57
61
|
|
|
58
62
|
```bash
|
|
59
|
-
thirdfy-agent agent auth challenge --
|
|
63
|
+
thirdfy-agent agent auth challenge --agent-key "0xYOUR_AGENT_KEY" --json
|
|
60
64
|
# Sign challenge.message with your wallet, then:
|
|
61
|
-
thirdfy-agent agent auth verify --
|
|
65
|
+
thirdfy-agent agent auth verify --challenge-id "<challengeId>" --signature "0x..." --agent-key "0xYOUR_AGENT_KEY" --json
|
|
62
66
|
```
|
|
63
67
|
|
|
64
68
|
Use the returned session token:
|
|
65
69
|
|
|
66
70
|
```bash
|
|
67
|
-
thirdfy-agent agent register --
|
|
71
|
+
thirdfy-agent agent register --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --name "my-agent" --json
|
|
68
72
|
```
|
|
69
73
|
|
|
70
74
|
### Idempotency behavior (`run`)
|
|
@@ -113,9 +117,40 @@ Jeff-friendly aliases map directly to core commands:
|
|
|
113
117
|
- Execution: `preflight`, `run`, `intent-status`, `jeff preflight`, `jeff trade`, `jeff status`
|
|
114
118
|
- Profiles: `profile init`, `profile use`, `profile show`, `whoami`
|
|
115
119
|
- Onboarding: `agent auth challenge`, `agent auth verify`, `agent register`, `agent key rotate`, `agent key revoke`
|
|
116
|
-
- Governance readiness: `delegation
|
|
120
|
+
- Governance readiness: `delegation create`, `delegation activate`, `delegation status`, `credentials upsert`, `credentials status`
|
|
117
121
|
- Account: `credits balance`
|
|
118
122
|
|
|
123
|
+
## Delegation lifecycle (canonical)
|
|
124
|
+
|
|
125
|
+
Use `create` + `activate` as the default delegated execution lifecycle:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
thirdfy-agent delegation create \
|
|
129
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
130
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
131
|
+
--owner-address "0xOWNER_WALLET" \
|
|
132
|
+
--token-address "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" \
|
|
133
|
+
--max-usd-per-day 250 \
|
|
134
|
+
--json
|
|
135
|
+
|
|
136
|
+
# Sign the returned `data.delegation` payload in your wallet, then:
|
|
137
|
+
thirdfy-agent delegation activate \
|
|
138
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
139
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
140
|
+
--wallet-address "0xOWNER_WALLET" \
|
|
141
|
+
--session-account-address "0xSESSION_ACCOUNT" \
|
|
142
|
+
--delegation-manager "0xDELEGATION_MANAGER" \
|
|
143
|
+
--delegation '{"delegator":"0x...","delegate":"0x...","authority":"0x...","caveats":[],"salt":"0x...","signature":"0x"}' \
|
|
144
|
+
--signature "0xSIGNED_DELEGATION" \
|
|
145
|
+
--json
|
|
146
|
+
|
|
147
|
+
thirdfy-agent delegation status \
|
|
148
|
+
--auth-token "$THIRDFY_AUTH_TOKEN" \
|
|
149
|
+
--agent-key "0xYOUR_AGENT_KEY" \
|
|
150
|
+
--verify \
|
|
151
|
+
--json
|
|
152
|
+
```
|
|
153
|
+
|
|
119
154
|
## More docs
|
|
120
155
|
|
|
121
156
|
- OpenClaw CLI onboarding flow:
|
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -130,8 +130,11 @@ async function main() {
|
|
|
130
130
|
case 'credits balance':
|
|
131
131
|
await commandCreditsBalance(context, subFlags, capabilities);
|
|
132
132
|
return;
|
|
133
|
-
case 'delegation
|
|
134
|
-
await
|
|
133
|
+
case 'delegation create':
|
|
134
|
+
await commandDelegationCreate(context, subFlags, capabilities);
|
|
135
|
+
return;
|
|
136
|
+
case 'delegation activate':
|
|
137
|
+
await commandDelegationActivate(context, subFlags, capabilities);
|
|
135
138
|
return;
|
|
136
139
|
case 'delegation status':
|
|
137
140
|
await commandDelegationStatus(context, subFlags, capabilities);
|
|
@@ -388,32 +391,76 @@ async function commandCreditsBalance(ctx, flags, capabilities) {
|
|
|
388
391
|
});
|
|
389
392
|
}
|
|
390
393
|
|
|
391
|
-
async function
|
|
394
|
+
async function commandDelegationCreate(ctx, flags, capabilities) {
|
|
395
|
+
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for delegation create');
|
|
396
|
+
let sessionAccountAddress = String(flags.sessionAccountAddress || '').trim();
|
|
397
|
+
if (!sessionAccountAddress) {
|
|
398
|
+
try {
|
|
399
|
+
const executorAddressResponse = await apiGet(ctx, '/api/v1/agent/delegation/executor-address', {
|
|
400
|
+
Authorization: `Bearer ${authToken}`,
|
|
401
|
+
});
|
|
402
|
+
sessionAccountAddress = String(executorAddressResponse?.data?.executorAddress || '').trim();
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (ctx.verbose) {
|
|
405
|
+
process.stderr.write(
|
|
406
|
+
`delegation create: executor-address discovery failed, falling back to --session-account-address (${error?.message || 'unknown error'})\n`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const payload = {
|
|
412
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
413
|
+
ownerAddress: String(flags.ownerAddress || flags.walletAddress || '').trim() || requireFlag(flags, 'ownerAddress', 'Missing --owner-address'),
|
|
414
|
+
sessionAccountAddress: sessionAccountAddress || requireFlag(flags, 'sessionAccountAddress', 'Missing --session-account-address'),
|
|
415
|
+
chainId: Number(flags.chainId || 8453),
|
|
416
|
+
tokenAddress: requireFlag(flags, 'tokenAddress', 'Missing --token-address'),
|
|
417
|
+
maxUsdPerDay: Number(requireFlag(flags, 'maxUsdPerDay', 'Missing --max-usd-per-day')),
|
|
418
|
+
periodDuration: Number(flags.periodDuration || 86_400),
|
|
419
|
+
expirySeconds: Number(flags.expirySeconds || 86_400),
|
|
420
|
+
};
|
|
421
|
+
const response = await apiPost(ctx, '/api/v1/agent/delegation/create', payload, {
|
|
422
|
+
Authorization: `Bearer ${authToken}`,
|
|
423
|
+
});
|
|
424
|
+
const success = Boolean(response?.success !== false);
|
|
425
|
+
printEnvelope({
|
|
426
|
+
success,
|
|
427
|
+
code: success ? 'DELEGATION_CREATED' : 'DELEGATION_CREATE_FAILED',
|
|
428
|
+
message: response?.message || (success ? 'Delegation payload created' : 'Delegation create failed'),
|
|
429
|
+
data: response,
|
|
430
|
+
meta: {
|
|
431
|
+
apiBase: ctx.apiBase,
|
|
432
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function commandDelegationActivate(ctx, flags, capabilities) {
|
|
438
|
+
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for delegation activate');
|
|
392
439
|
const payload = {
|
|
393
|
-
userDid: requireFlag(flags, 'userDid', 'Missing --user-did'),
|
|
394
440
|
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
395
441
|
walletAddress: requireFlag(flags, 'walletAddress', 'Missing --wallet-address'),
|
|
442
|
+
sessionAccountAddress: requireFlag(flags, 'sessionAccountAddress', 'Missing --session-account-address'),
|
|
443
|
+
sessionAccountType: String(flags.sessionAccountType || 'smart').trim(),
|
|
396
444
|
chainId: Number(flags.chainId || 8453),
|
|
397
|
-
|
|
445
|
+
delegationManager: requireFlag(flags, 'delegationManager', 'Missing --delegation-manager'),
|
|
446
|
+
signature: requireFlag(flags, 'signature', 'Missing --signature'),
|
|
398
447
|
};
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (flags.permissionsContext) payload.permissionsContext = String(flags.permissionsContext).trim();
|
|
402
|
-
if (flags.delegationManager) payload.delegationManager = String(flags.delegationManager).trim();
|
|
448
|
+
const delegation = parseJsonFlag(flags, 'delegation');
|
|
449
|
+
payload.delegation = delegation;
|
|
403
450
|
if (flags.permissionsExpiry) payload.permissionsExpiry = String(flags.permissionsExpiry).trim();
|
|
404
|
-
if (flags.
|
|
405
|
-
if (flags.
|
|
451
|
+
if (flags.permissionsContext) payload.permissionsContext = String(flags.permissionsContext).trim();
|
|
452
|
+
if (flags.permission) payload.permission = parseJsonFlag(flags, 'permission');
|
|
453
|
+
if (flags.permissionType) payload.permissionType = String(flags.permissionType).trim();
|
|
454
|
+
if (flags.permissionData) payload.permissionData = parseJsonFlag(flags, 'permissionData');
|
|
406
455
|
if (flags.modelAConstraints) payload.modelAConstraints = parseJsonFlag(flags, 'modelAConstraints');
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
method: 'POST',
|
|
410
|
-
routes: ['/api/v1/agent/delegation/upsert', '/api/v1/agent/agent/delegation/upsert'],
|
|
411
|
-
body: payload,
|
|
456
|
+
const response = await apiPost(ctx, '/api/v1/agent/delegation/activate', payload, {
|
|
457
|
+
Authorization: `Bearer ${authToken}`,
|
|
412
458
|
});
|
|
459
|
+
const success = Boolean(response?.success !== false);
|
|
413
460
|
printEnvelope({
|
|
414
|
-
success
|
|
415
|
-
code:
|
|
416
|
-
message: response?.message || 'Delegation
|
|
461
|
+
success,
|
|
462
|
+
code: success ? 'DELEGATION_ACTIVATED' : 'DELEGATION_ACTIVATE_FAILED',
|
|
463
|
+
message: response?.message || (success ? 'Delegation activated' : 'Delegation activate failed'),
|
|
417
464
|
data: response,
|
|
418
465
|
meta: {
|
|
419
466
|
apiBase: ctx.apiBase,
|
|
@@ -1094,15 +1141,29 @@ async function resolveActionSelection(ctx, flags) {
|
|
|
1094
1141
|
return { requestedAction, resolvedAction: requestedAction };
|
|
1095
1142
|
}
|
|
1096
1143
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
);
|
|
1100
|
-
if (
|
|
1101
|
-
return { requestedAction, resolvedAction:
|
|
1144
|
+
// Prefer direct action id matches over alias matches.
|
|
1145
|
+
// This keeps UX simple for commands like `--action swap` when another action aliases to "swap".
|
|
1146
|
+
const exactKey = keyed.filter((entry) => entry.key.toLowerCase() === requestedLower);
|
|
1147
|
+
if (exactKey.length === 1) {
|
|
1148
|
+
return { requestedAction, resolvedAction: exactKey[0].key };
|
|
1149
|
+
}
|
|
1150
|
+
if (exactKey.length > 1) {
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
`Ambiguous --action "${requestedAction}". Multiple exact matches found: ${formatActionList(
|
|
1153
|
+
exactKey.map((v) => v.key)
|
|
1154
|
+
)}.`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const exactAlias = keyed.filter((entry) => entry.aliases.some((a) => a.toLowerCase() === requestedLower));
|
|
1159
|
+
if (exactAlias.length === 1) {
|
|
1160
|
+
return { requestedAction, resolvedAction: exactAlias[0].key };
|
|
1102
1161
|
}
|
|
1103
|
-
if (
|
|
1162
|
+
if (exactAlias.length > 1) {
|
|
1104
1163
|
throw new Error(
|
|
1105
|
-
`Ambiguous --action "${requestedAction}".
|
|
1164
|
+
`Ambiguous --action "${requestedAction}". Alias matches multiple actions: ${formatActionList(
|
|
1165
|
+
exactAlias.map((v) => v.key)
|
|
1166
|
+
)}.`
|
|
1106
1167
|
);
|
|
1107
1168
|
}
|
|
1108
1169
|
|
|
@@ -1250,7 +1311,8 @@ Core commands:
|
|
|
1250
1311
|
thirdfy-agent whoami [--json]
|
|
1251
1312
|
thirdfy-agent catalogs list [--json]
|
|
1252
1313
|
thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
|
|
1253
|
-
thirdfy-agent delegation
|
|
1314
|
+
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]
|
|
1315
|
+
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]
|
|
1254
1316
|
thirdfy-agent delegation status --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
1255
1317
|
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
|
|
1256
1318
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thirdfy/agent-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
"test": "node --test \"test/**/*.test.cjs\"",
|
|
19
19
|
"lint": "node --check ./bin/thirdfy-agent.mjs",
|
|
20
20
|
"validate:cli-contract-schemas": "node ./scripts/validate-cli-contract-schemas.mjs",
|
|
21
|
+
"validate:actions": "node ./scripts/e2e/validate-actions.mjs",
|
|
22
|
+
"e2e:base:simulate": "node ./scripts/e2e/base-simulate.mjs",
|
|
23
|
+
"e2e:base:real": "node ./scripts/e2e/base-realtx.mjs",
|
|
24
|
+
"e2e:delegation:discovery": "node ./scripts/e2e/delegation-discovery.mjs",
|
|
25
|
+
"e2e:delegation:lifecycle": "node ./scripts/e2e/delegation-lifecycle.mjs",
|
|
26
|
+
"e2e:delegation:prod:certify": "node ./scripts/e2e/prod-delegation-certify.mjs",
|
|
27
|
+
"e2e:report:last": "node ./scripts/e2e/report-last.mjs",
|
|
21
28
|
"smoke:cli": "node ./bin/thirdfy-agent.mjs --help && node ./bin/thirdfy-agent.mjs --version --json",
|
|
22
29
|
"prepublishOnly": "npm run validate:cli-contract-schemas && npm run test && npm run smoke:cli"
|
|
23
30
|
},
|
|
@@ -19,6 +19,20 @@ function normalizeIntentResponse(response) {
|
|
|
19
19
|
const executed = Number(response?.executed || 0);
|
|
20
20
|
const total = Number(response?.total || results.length || 0);
|
|
21
21
|
const success = Boolean(response?.success);
|
|
22
|
+
const topLevelBlockedReason = String(response?.blockedReason || '').trim() || null;
|
|
23
|
+
const topLevelBlockedStage = String(response?.blockedStage || '').trim() || null;
|
|
24
|
+
const blockedByReasonFromResponse =
|
|
25
|
+
response?.blockedByReason && typeof response.blockedByReason === 'object' ? response.blockedByReason : null;
|
|
26
|
+
const groupedBlocked = summarizeBlockedReasons(results);
|
|
27
|
+
if (topLevelBlockedReason && !groupedBlocked[topLevelBlockedReason]) {
|
|
28
|
+
groupedBlocked[topLevelBlockedReason] = 1;
|
|
29
|
+
}
|
|
30
|
+
const mergedBlockedByReason = blockedByReasonFromResponse
|
|
31
|
+
? { ...groupedBlocked, ...blockedByReasonFromResponse }
|
|
32
|
+
: groupedBlocked;
|
|
33
|
+
const topLevelGovernanceSignal = String(response?.governanceSignal || '').trim() || null;
|
|
34
|
+
const topLevelDelegationSignal = String(response?.delegationSignal || '').trim() || null;
|
|
35
|
+
const policyEvaluated = response?.policyEvaluated === true;
|
|
22
36
|
return {
|
|
23
37
|
success,
|
|
24
38
|
status: response?.status || (success ? 'queued' : 'failed'),
|
|
@@ -28,7 +42,12 @@ function normalizeIntentResponse(response) {
|
|
|
28
42
|
blocked,
|
|
29
43
|
failed,
|
|
30
44
|
results,
|
|
31
|
-
blockedByReason:
|
|
45
|
+
blockedByReason: mergedBlockedByReason,
|
|
46
|
+
blockedReason: topLevelBlockedReason,
|
|
47
|
+
blockedStage: topLevelBlockedStage,
|
|
48
|
+
governanceSignal: topLevelGovernanceSignal,
|
|
49
|
+
delegationSignal: topLevelDelegationSignal,
|
|
50
|
+
policyEvaluated,
|
|
32
51
|
error: response?.error || null,
|
|
33
52
|
mode: response?.mode || null,
|
|
34
53
|
unsignedTx: response?.unsignedTx || null,
|