@thirdfy/agent-cli 0.1.48 → 0.2.1
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 +51 -0
- package/README.md +16 -1
- package/bin/thirdfy-agent.mjs +3 -5106
- package/package.json +17 -4
- package/scripts/validate-npm-pack.mjs +43 -0
- package/src/cli/errors.mjs +103 -0
- package/src/cli/flags.mjs +66 -0
- package/src/cli/help.mjs +89 -0
- package/src/cli/manifest.mjs +422 -0
- package/src/cli/options/agent.mjs +9 -0
- package/src/cli/options/analytics.mjs +5 -0
- package/src/cli/options/auth.mjs +10 -0
- package/src/cli/options/bootstrap.mjs +19 -0
- package/src/cli/options/chains.mjs +3 -0
- package/src/cli/options/credentials.mjs +15 -0
- package/src/cli/options/delegation.mjs +22 -0
- package/src/cli/options/execution.mjs +24 -0
- package/src/cli/options/global.mjs +16 -0
- package/src/cli/options/index.mjs +37 -0
- package/src/cli/options/polymarket.mjs +3 -0
- package/src/cli/options/tracking.mjs +18 -0
- package/src/cli/options/wallet.mjs +7 -0
- package/src/cli/program.mjs +5 -0
- package/src/cli/register.mjs +118 -0
- package/src/commands/agent.mjs +194 -0
- package/src/commands/bootstrap.mjs +762 -0
- package/src/commands/credentials.mjs +102 -0
- package/src/commands/delegation.mjs +355 -0
- package/src/commands/discovery.mjs +77 -0
- package/src/commands/doctor.mjs +320 -0
- package/src/commands/execute.mjs +255 -0
- package/src/commands/hyperliquid.mjs +51 -0
- package/src/commands/index.mjs +6 -0
- package/src/commands/polymarket.mjs +89 -0
- package/src/commands/prompt.mjs +33 -0
- package/src/commands/telemetry.mjs +146 -0
- package/src/commands/wallet.mjs +129 -0
- package/src/core/args.mjs +68 -0
- package/src/core/constants.mjs +31 -0
- package/src/core/context.mjs +145 -0
- package/src/core/envelope.mjs +20 -0
- package/src/core/http.mjs +77 -0
- package/src/core/index.mjs +6 -0
- package/src/core/runMode.mjs +72 -0
- package/src/runtime/actions/selection.mjs +339 -0
- package/src/runtime/agentExtract.mjs +52 -0
- package/src/runtime/authHelpers.mjs +53 -0
- package/src/runtime/bitfinexProbe.mjs +121 -0
- package/src/runtime/context.mjs +28 -0
- package/src/runtime/createThirdfyAgentRuntime.mjs +2 -0
- package/src/runtime/errors.mjs +3 -0
- package/src/runtime/execution/amounts.mjs +193 -0
- package/src/runtime/execution/lanes.mjs +75 -0
- package/src/runtime/execution/payloads.mjs +148 -0
- package/src/runtime/execution/runners.mjs +702 -0
- package/src/runtime/handlers.mjs +181 -0
- package/src/runtime/index.mjs +4 -0
- package/src/runtime/main.mjs +204 -0
- package/src/runtime/onboardingHints.mjs +54 -0
- package/src/runtime/owsSigning.mjs +118 -0
- package/src/runtime/providerHints.mjs +414 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { CLI_MANIFEST } from './manifest.mjs';
|
|
3
|
+
import { attachGlobalOptions, attachOptionBundles } from './options/index.mjs';
|
|
4
|
+
import { collectLegacyFlags, collectRootFlags } from './flags.mjs';
|
|
5
|
+
import { configureCommanderErrors } from './errors.mjs';
|
|
6
|
+
|
|
7
|
+
function parentPathKey(pathSegments) {
|
|
8
|
+
return pathSegments.slice(0, -1).join(' ');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function fullPathKey(pathSegments) {
|
|
12
|
+
return pathSegments.join(' ');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ensureParentCommands(program, pathSegments, nodeByPath) {
|
|
16
|
+
let parent = program;
|
|
17
|
+
const built = [];
|
|
18
|
+
for (let i = 0; i < pathSegments.length - 1; i += 1) {
|
|
19
|
+
built.push(pathSegments[i]);
|
|
20
|
+
const key = built.join(' ');
|
|
21
|
+
if (!nodeByPath.has(key)) {
|
|
22
|
+
const sub = parent.command(pathSegments[i]);
|
|
23
|
+
nodeByPath.set(key, sub);
|
|
24
|
+
parent = sub;
|
|
25
|
+
} else {
|
|
26
|
+
parent = nodeByPath.get(key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return parent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function registerManifestEntry(program, entry, nodeByPath, deps) {
|
|
33
|
+
const { handlers, prepareContextFromFlags, negotiateCapabilities, getContext } = deps;
|
|
34
|
+
const path = entry.path;
|
|
35
|
+
const parent = ensureParentCommands(program, path, nodeByPath);
|
|
36
|
+
const leafName = path[path.length - 1];
|
|
37
|
+
const fullKey = fullPathKey(path);
|
|
38
|
+
|
|
39
|
+
if (nodeByPath.has(fullKey)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const leaf = parent.command(leafName);
|
|
44
|
+
if (entry.description) leaf.description(entry.description);
|
|
45
|
+
attachOptionBundles(leaf, entry.options || []);
|
|
46
|
+
|
|
47
|
+
if (entry.args?.address) {
|
|
48
|
+
leaf.argument(entry.args.address);
|
|
49
|
+
}
|
|
50
|
+
if (entry.args?.query) {
|
|
51
|
+
leaf.argument(entry.args.query);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const handler = handlers[entry.handler];
|
|
55
|
+
if (!handler) {
|
|
56
|
+
throw new Error(`Missing CLI handler: ${entry.handler} for ${fullKey}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
leaf.action(async (...actionArgs) => {
|
|
60
|
+
const command = actionArgs[actionArgs.length - 1];
|
|
61
|
+
const extra = {};
|
|
62
|
+
|
|
63
|
+
if (entry.args?.address) {
|
|
64
|
+
extra.address = actionArgs[0];
|
|
65
|
+
} else if (entry.args?.query) {
|
|
66
|
+
const queryArg = actionArgs[0];
|
|
67
|
+
extra.query = Array.isArray(queryArg) ? queryArg : queryArg !== undefined ? [queryArg] : [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const flags = collectLegacyFlags(program, command, extra);
|
|
71
|
+
const ctx = prepareContextFromFlags(flags);
|
|
72
|
+
|
|
73
|
+
if (entry.tier === 'online') {
|
|
74
|
+
const capabilities = await negotiateCapabilities(ctx);
|
|
75
|
+
ctx.negotiatedCapabilitiesCache = capabilities;
|
|
76
|
+
if (entry.trackKind) {
|
|
77
|
+
await handler(ctx, flags, capabilities, entry.trackKind);
|
|
78
|
+
} else if (entry.handler === 'commandPrompt') {
|
|
79
|
+
const tokens = Array.isArray(extra.query) ? extra.query : [];
|
|
80
|
+
await handler(ctx, flags, capabilities, tokens);
|
|
81
|
+
} else {
|
|
82
|
+
await handler(ctx, flags, capabilities);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (entry.trackKind) {
|
|
88
|
+
await handler(ctx, flags, null, entry.trackKind);
|
|
89
|
+
} else {
|
|
90
|
+
await handler(ctx, flags);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
nodeByPath.set(fullKey, leaf);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function buildCliProgram(deps) {
|
|
98
|
+
const program = new Command('thirdfy-agent');
|
|
99
|
+
program.name('thirdfy-agent');
|
|
100
|
+
attachGlobalOptions(program);
|
|
101
|
+
program.addHelpCommand(false);
|
|
102
|
+
program.showHelpAfterError(false);
|
|
103
|
+
|
|
104
|
+
configureCommanderErrors(program, {
|
|
105
|
+
getJsonMode: deps.getJsonMode,
|
|
106
|
+
printEnvelope: deps.printEnvelope,
|
|
107
|
+
CLI_VERSION: deps.CLI_VERSION,
|
|
108
|
+
getApiBase: () => deps.getContext().apiBase,
|
|
109
|
+
collectRootFlags,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const nodeByPath = new Map();
|
|
113
|
+
for (const entry of CLI_MANIFEST) {
|
|
114
|
+
registerManifestEntry(program, entry, nodeByPath, deps);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return program;
|
|
118
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { requireFlag, parseJsonFlag } from '../core/args.mjs';
|
|
3
|
+
import { apiPost, safeJsonParse } from '../core/http.mjs';
|
|
4
|
+
import { createCliError } from '../core/envelope.mjs';
|
|
5
|
+
import { DEFAULT_TIMEOUT_MS } from '../core/constants.mjs';
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { version: CLI_VERSION } = require('../../package.json');
|
|
9
|
+
export function createAgentCommands({ printEnvelope, extractAgentApiKey, extractAgentKey, buildOwnerAuthHeaders }) {
|
|
10
|
+
async function commandAgentRegister(ctx, flags, capabilities) {
|
|
11
|
+
const ownerHeaders = buildOwnerAuthHeaders(
|
|
12
|
+
flags,
|
|
13
|
+
'Missing owner auth for agent register: provide --auth-token (THIRDFY_AUTH_TOKEN) or --owner-session-token'
|
|
14
|
+
);
|
|
15
|
+
const payload = {
|
|
16
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
17
|
+
name: requireFlag(flags, 'name', 'Missing --name'),
|
|
18
|
+
};
|
|
19
|
+
if (flags.email) payload.email = String(flags.email);
|
|
20
|
+
if (flags.bio) payload.bio = String(flags.bio);
|
|
21
|
+
if (flags.website) payload.website = String(flags.website);
|
|
22
|
+
if (flags.github) payload.github = String(flags.github);
|
|
23
|
+
if (flags.tags) {
|
|
24
|
+
payload.tags = String(flags.tags)
|
|
25
|
+
.split(',')
|
|
26
|
+
.map((s) => s.trim())
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
if (flags.strategy) payload.strategy = parseJsonFlag(flags, 'strategy');
|
|
30
|
+
if (flags.apiKeyEnvelopePublicKey) payload.apiKeyEnvelopePublicKey = String(flags.apiKeyEnvelopePublicKey);
|
|
31
|
+
|
|
32
|
+
const route = ownerHeaders['x-owner-session-token']
|
|
33
|
+
? '/api/v1/agent/onboarding/register-with-wallet'
|
|
34
|
+
: '/api/v1/agent/onboarding/register-with-privy';
|
|
35
|
+
const response = await apiPost(ctx, route, payload, ownerHeaders);
|
|
36
|
+
if (!flags.__suppressOutput) {
|
|
37
|
+
printEnvelope({
|
|
38
|
+
success: true,
|
|
39
|
+
code: 'AGENT_REGISTERED',
|
|
40
|
+
message: 'Agent registered',
|
|
41
|
+
data: response,
|
|
42
|
+
meta: {
|
|
43
|
+
apiBase: ctx.apiBase,
|
|
44
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function commandAgentKeyRotate(ctx, flags, capabilities) {
|
|
52
|
+
const ownerHeaders = buildOwnerAuthHeaders(
|
|
53
|
+
flags,
|
|
54
|
+
'Missing owner auth for agent key rotate: provide --auth-token (THIRDFY_AUTH_TOKEN) or --owner-session-token'
|
|
55
|
+
);
|
|
56
|
+
const payload = {};
|
|
57
|
+
if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
|
|
58
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/me/api-key/rotate', payload, ownerHeaders);
|
|
59
|
+
printEnvelope({
|
|
60
|
+
success: true,
|
|
61
|
+
code: 'AGENT_KEY_ROTATED',
|
|
62
|
+
message: 'Agent API key rotated',
|
|
63
|
+
data: response,
|
|
64
|
+
meta: {
|
|
65
|
+
apiBase: ctx.apiBase,
|
|
66
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function commandAgentKeyRevoke(ctx, flags, capabilities) {
|
|
72
|
+
const ownerHeaders = buildOwnerAuthHeaders(
|
|
73
|
+
flags,
|
|
74
|
+
'Missing owner auth for agent key revoke: provide --auth-token (THIRDFY_AUTH_TOKEN) or --owner-session-token'
|
|
75
|
+
);
|
|
76
|
+
const payload = {};
|
|
77
|
+
if (flags.agentKey) payload.agentKey = String(flags.agentKey).trim();
|
|
78
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/me/api-key/revoke', payload, ownerHeaders);
|
|
79
|
+
printEnvelope({
|
|
80
|
+
success: true,
|
|
81
|
+
code: 'AGENT_KEY_REVOKED',
|
|
82
|
+
message: 'Agent API key revoked',
|
|
83
|
+
data: response,
|
|
84
|
+
meta: {
|
|
85
|
+
apiBase: ctx.apiBase,
|
|
86
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildWalletChallengePayload(flags) {
|
|
92
|
+
const payload = {
|
|
93
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
94
|
+
chainId: Number(flags.chainId || 8453),
|
|
95
|
+
};
|
|
96
|
+
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
97
|
+
return payload;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function commandAgentAuthChallenge(ctx, flags, capabilities) {
|
|
101
|
+
const response = await apiPost(
|
|
102
|
+
ctx,
|
|
103
|
+
'/api/v1/agent/onboarding/wallet/challenge',
|
|
104
|
+
buildWalletChallengePayload(flags)
|
|
105
|
+
);
|
|
106
|
+
printEnvelope({
|
|
107
|
+
success: true,
|
|
108
|
+
code: 'OWNER_CHALLENGE_CREATED',
|
|
109
|
+
message: 'Wallet challenge created',
|
|
110
|
+
data: response,
|
|
111
|
+
meta: {
|
|
112
|
+
apiBase: ctx.apiBase,
|
|
113
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function commandAgentAuthVerify(ctx, flags, capabilities) {
|
|
119
|
+
const response = await verifyOwnerWalletChallenge(ctx, flags);
|
|
120
|
+
printEnvelope({
|
|
121
|
+
success: true,
|
|
122
|
+
code: 'OWNER_VERIFIED',
|
|
123
|
+
message: 'Wallet challenge verified',
|
|
124
|
+
data: response,
|
|
125
|
+
meta: {
|
|
126
|
+
apiBase: ctx.apiBase,
|
|
127
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function verifyOwnerWalletChallenge(ctx, flags) {
|
|
133
|
+
const payload = {
|
|
134
|
+
challengeId: requireFlag(flags, 'challengeId', 'Missing --challenge-id'),
|
|
135
|
+
signature: requireFlag(flags, 'signature', 'Missing --signature'),
|
|
136
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
137
|
+
};
|
|
138
|
+
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
139
|
+
return apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', payload);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function resolveMcpBase(flags) {
|
|
143
|
+
return String(flags.mcpBase || process.env.THIRDFY_MCP_BASE_URL || 'https://mcp.thirdfy.com').trim().replace(/\/+$/, '');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function issueBootstrapToken(ctx, flags, payload) {
|
|
147
|
+
const mcpBase = resolveMcpBase(flags);
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timeout = setTimeout(() => controller.abort(), Number(ctx.timeoutMs || DEFAULT_TIMEOUT_MS));
|
|
150
|
+
let response;
|
|
151
|
+
let text = '';
|
|
152
|
+
try {
|
|
153
|
+
response = await fetch(`${mcpBase}/auth/bootstrap/issue`, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: {
|
|
156
|
+
'content-type': 'application/json',
|
|
157
|
+
'x-cli-version': CLI_VERSION,
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify(payload),
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
});
|
|
162
|
+
text = await response.text();
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error?.name === 'AbortError') {
|
|
165
|
+
throw createCliError('TIMEOUT', `Request timed out after ${ctx.timeoutMs}ms`, {
|
|
166
|
+
routes: ['/auth/bootstrap/issue'],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
} finally {
|
|
171
|
+
clearTimeout(timeout);
|
|
172
|
+
}
|
|
173
|
+
const data = text ? safeJsonParse(text) : {};
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
const message = data?.error || data?.message || 'Bootstrap token issuance failed';
|
|
176
|
+
throw createCliError('BOOTSTRAP_TOKEN_ISSUE_FAILED', message, data);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
mcpBase,
|
|
180
|
+
data,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
commandAgentRegister,
|
|
186
|
+
commandAgentKeyRotate,
|
|
187
|
+
commandAgentKeyRevoke,
|
|
188
|
+
buildWalletChallengePayload,
|
|
189
|
+
commandAgentAuthChallenge,
|
|
190
|
+
commandAgentAuthVerify,
|
|
191
|
+
verifyOwnerWalletChallenge,
|
|
192
|
+
issueBootstrapToken,
|
|
193
|
+
};
|
|
194
|
+
}
|