@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,762 @@
|
|
|
1
|
+
import { requireFlag, isNonInteractive } from '../core/args.mjs';
|
|
2
|
+
import { createCliError } from '../core/envelope.mjs';
|
|
3
|
+
import { PROFILE_DEFAULTS } from '../core/constants.mjs';
|
|
4
|
+
import {
|
|
5
|
+
normalizeRunMode,
|
|
6
|
+
normalizeProfileName,
|
|
7
|
+
normalizeCustodyMode,
|
|
8
|
+
defaultCustodyModeForRunMode,
|
|
9
|
+
parseBooleanFlag,
|
|
10
|
+
} from '../core/runMode.mjs';
|
|
11
|
+
import {
|
|
12
|
+
loadProfileConfig,
|
|
13
|
+
persistProfileConfig,
|
|
14
|
+
clonePlainConfigTree,
|
|
15
|
+
setNestedConfigValue,
|
|
16
|
+
getProfileConfigPath,
|
|
17
|
+
} from '../core/context.mjs';
|
|
18
|
+
import { apiGet, apiPost, requestWithRouteFallback } from '../core/http.mjs';
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const { version: CLI_VERSION } = require('../../package.json');
|
|
23
|
+
|
|
24
|
+
export function createBootstrapCommands(deps) {
|
|
25
|
+
const {
|
|
26
|
+
printEnvelope,
|
|
27
|
+
extractAgentApiKey,
|
|
28
|
+
extractAgentKey,
|
|
29
|
+
buildOnboardingHelp,
|
|
30
|
+
buildWalletChallengePayload,
|
|
31
|
+
issueBootstrapToken,
|
|
32
|
+
verifyOwnerWalletChallenge,
|
|
33
|
+
resolveEffectiveUserDid,
|
|
34
|
+
negotiateCapabilities,
|
|
35
|
+
commandAgentRegister,
|
|
36
|
+
} = deps;
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async function commandProfileInit(ctx, flags) {
|
|
40
|
+
const current = loadProfileConfig();
|
|
41
|
+
const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
|
|
42
|
+
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
43
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode, runMode);
|
|
44
|
+
persistProfileConfig({
|
|
45
|
+
...current,
|
|
46
|
+
profile,
|
|
47
|
+
runMode,
|
|
48
|
+
custodyMode,
|
|
49
|
+
});
|
|
50
|
+
ctx.profile = profile;
|
|
51
|
+
ctx.runMode = runMode;
|
|
52
|
+
ctx.custodyMode = custodyMode;
|
|
53
|
+
printEnvelope({
|
|
54
|
+
success: true,
|
|
55
|
+
code: 'PROFILE_INITIALIZED',
|
|
56
|
+
message: 'Profile initialized',
|
|
57
|
+
data: {
|
|
58
|
+
profile,
|
|
59
|
+
runMode,
|
|
60
|
+
custodyMode,
|
|
61
|
+
configPath: getProfileConfigPath(),
|
|
62
|
+
},
|
|
63
|
+
meta: { apiBase: ctx.apiBase },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async function commandProfileUse(ctx, flags) {
|
|
69
|
+
const current = loadProfileConfig();
|
|
70
|
+
const profile = normalizeProfileName(flags.profile || flags.name || current.profile || 'personal');
|
|
71
|
+
// `profile use` intentionally re-applies profile defaults unless caller pins a mode.
|
|
72
|
+
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
73
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode || current.custodyMode, runMode);
|
|
74
|
+
persistProfileConfig({
|
|
75
|
+
...current,
|
|
76
|
+
profile,
|
|
77
|
+
runMode,
|
|
78
|
+
custodyMode,
|
|
79
|
+
});
|
|
80
|
+
ctx.profile = profile;
|
|
81
|
+
ctx.runMode = runMode;
|
|
82
|
+
ctx.custodyMode = custodyMode;
|
|
83
|
+
printEnvelope({
|
|
84
|
+
success: true,
|
|
85
|
+
code: 'PROFILE_UPDATED',
|
|
86
|
+
message: 'Profile updated',
|
|
87
|
+
data: {
|
|
88
|
+
profile,
|
|
89
|
+
runMode,
|
|
90
|
+
custodyMode,
|
|
91
|
+
defaults: PROFILE_DEFAULTS,
|
|
92
|
+
configPath: getProfileConfigPath(),
|
|
93
|
+
},
|
|
94
|
+
meta: { apiBase: ctx.apiBase },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async function commandProfileShow(ctx, _flags) {
|
|
100
|
+
const config = loadProfileConfig();
|
|
101
|
+
printEnvelope({
|
|
102
|
+
success: true,
|
|
103
|
+
code: 'PROFILE_OK',
|
|
104
|
+
message: 'Profile configuration loaded',
|
|
105
|
+
data: {
|
|
106
|
+
profile: ctx.profile || config.profile || 'personal',
|
|
107
|
+
runMode: ctx.runMode || config.runMode || 'agent_wallet',
|
|
108
|
+
custodyMode: ctx.custodyMode || config.custodyMode || defaultCustodyModeForRunMode(ctx.runMode || config.runMode || 'agent_wallet'),
|
|
109
|
+
profileDefaults: PROFILE_DEFAULTS,
|
|
110
|
+
configPath: getProfileConfigPath(),
|
|
111
|
+
config,
|
|
112
|
+
},
|
|
113
|
+
meta: { apiBase: ctx.apiBase },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async function commandConfigSet(ctx, flags) {
|
|
119
|
+
const key = String(flags.key || '').trim();
|
|
120
|
+
const value = flags.value;
|
|
121
|
+
if (!key) {
|
|
122
|
+
throw new Error('Missing --key for config set');
|
|
123
|
+
}
|
|
124
|
+
const canonicalKey = key.toLowerCase();
|
|
125
|
+
const hasValue = !(value === undefined || String(value).trim() === '');
|
|
126
|
+
if (canonicalKey === 'profile' && hasValue) {
|
|
127
|
+
normalizeProfileName(value);
|
|
128
|
+
}
|
|
129
|
+
if (canonicalKey === 'runmode' && hasValue) {
|
|
130
|
+
normalizeRunMode(value);
|
|
131
|
+
}
|
|
132
|
+
if (canonicalKey === 'custodymode' && hasValue) {
|
|
133
|
+
const runModeHint = normalizeRunMode(flags.runMode || loadProfileConfig().runMode || 'thirdfy');
|
|
134
|
+
normalizeCustodyMode(value, runModeHint);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const current = loadProfileConfig();
|
|
138
|
+
const next = setNestedConfigValue(current, key, value);
|
|
139
|
+
persistProfileConfig(next);
|
|
140
|
+
printEnvelope({
|
|
141
|
+
success: true,
|
|
142
|
+
code: 'CONFIG_UPDATED',
|
|
143
|
+
message: 'Configuration updated',
|
|
144
|
+
data: {
|
|
145
|
+
key,
|
|
146
|
+
value: value === undefined ? null : String(value),
|
|
147
|
+
configPath: getProfileConfigPath(),
|
|
148
|
+
},
|
|
149
|
+
meta: { apiBase: ctx.apiBase },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async function commandHelpOnboarding(ctx, _flags) {
|
|
155
|
+
printEnvelope({
|
|
156
|
+
success: true,
|
|
157
|
+
code: 'ONBOARDING_HELP_OK',
|
|
158
|
+
message: 'Thirdfy onboarding help',
|
|
159
|
+
data: buildOnboardingHelp(ctx),
|
|
160
|
+
meta: { apiBase: ctx.apiBase },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async function commandWhoami(ctx, _flags) {
|
|
166
|
+
const config = loadProfileConfig();
|
|
167
|
+
const auth = config.auth && typeof config.auth === 'object' ? config.auth : {};
|
|
168
|
+
const agent = config.agent && typeof config.agent === 'object' ? config.agent : {};
|
|
169
|
+
const wallets = config.wallets && typeof config.wallets === 'object' ? config.wallets : {};
|
|
170
|
+
const runMode = ctx.runMode || config.runMode || 'agent_wallet';
|
|
171
|
+
printEnvelope({
|
|
172
|
+
success: true,
|
|
173
|
+
code: 'WHOAMI_OK',
|
|
174
|
+
message: 'Thirdfy CLI identity loaded',
|
|
175
|
+
data: {
|
|
176
|
+
profile: ctx.profile || config.profile || 'personal',
|
|
177
|
+
runMode,
|
|
178
|
+
custodyMode: ctx.custodyMode || config.custodyMode || defaultCustodyModeForRunMode(runMode),
|
|
179
|
+
auth: {
|
|
180
|
+
source: auth.source || null,
|
|
181
|
+
hasAuthToken: Boolean(auth.authToken),
|
|
182
|
+
hasOwnerSessionToken: Boolean(auth.ownerSessionToken),
|
|
183
|
+
ownerSessionToken: maskSecret(auth.ownerSessionToken),
|
|
184
|
+
ownerSessionExpiresAt: auth.ownerSessionExpiresAt || null,
|
|
185
|
+
},
|
|
186
|
+
agent: {
|
|
187
|
+
hasAgentApiKey: Boolean(agent.apiKey),
|
|
188
|
+
agentApiKey: maskSecret(agent.apiKey),
|
|
189
|
+
agentKey: agent.agentKey || null,
|
|
190
|
+
},
|
|
191
|
+
wallets: {
|
|
192
|
+
userDid: wallets.userDid || config.identity?.userDid || null,
|
|
193
|
+
primaryEvmWallet: wallets.primaryEvmWallet || null,
|
|
194
|
+
evm: Array.isArray(wallets.evm) ? wallets.evm : [],
|
|
195
|
+
solana: Array.isArray(wallets.solana) ? wallets.solana : [],
|
|
196
|
+
executionWallets: wallets.executionWallets || config.executionWallets || {},
|
|
197
|
+
},
|
|
198
|
+
configPath: getProfileConfigPath(),
|
|
199
|
+
nextSteps: buildOnboardingHelp(ctx).paths[0].commands,
|
|
200
|
+
},
|
|
201
|
+
meta: { apiBase: ctx.apiBase },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
async function commandLogin(ctx, flags) {
|
|
207
|
+
const current = loadProfileConfig();
|
|
208
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
209
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
210
|
+
const loginRunMode = normalizeRunMode(flags.runMode || current.runMode || process.env.THIRDFY_RUN_MODE || 'agent_wallet');
|
|
211
|
+
const custodyMode = normalizeCustodyMode(
|
|
212
|
+
flags.custodyMode || current.custodyMode || process.env.THIRDFY_CUSTODY_MODE,
|
|
213
|
+
loginRunMode
|
|
214
|
+
);
|
|
215
|
+
let loginSource = 'token';
|
|
216
|
+
|
|
217
|
+
if (!authToken && !ownerSessionToken) {
|
|
218
|
+
const challengeId = String(flags.challengeId || '').trim();
|
|
219
|
+
const signature = String(flags.signature || '').trim();
|
|
220
|
+
const agentKey = String(flags.agentKey || '').trim();
|
|
221
|
+
if (challengeId && signature && agentKey) {
|
|
222
|
+
const verifyResponse = await requestWithRouteFallback(ctx, {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
routes: ['/api/v1/agent/onboarding/wallet/verify'],
|
|
225
|
+
body: {
|
|
226
|
+
challengeId,
|
|
227
|
+
signature,
|
|
228
|
+
agentKey,
|
|
229
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
ownerSessionToken = String(
|
|
233
|
+
verifyResponse?.ownerSessionToken || verifyResponse?.data?.ownerSessionToken || ''
|
|
234
|
+
).trim();
|
|
235
|
+
loginSource = 'wallet-verify';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!authToken && !ownerSessionToken) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
'Missing login credentials: provide --auth-token, --owner-session-token, or challenge verify inputs (--challenge-id/--signature/--agent-key).'
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let bootstrapAgentApiKey = '';
|
|
246
|
+
let bootstrapPerformed = false;
|
|
247
|
+
const requestedAgentApiKey = String(flags.agentApiKey || '').trim();
|
|
248
|
+
const bootstrapAgentKey = String(flags.agentKey || '').trim();
|
|
249
|
+
if (!requestedAgentApiKey && bootstrapAgentKey) {
|
|
250
|
+
const bootstrapHeaders = ownerSessionToken
|
|
251
|
+
? { 'x-owner-session-token': ownerSessionToken }
|
|
252
|
+
: authToken
|
|
253
|
+
? { Authorization: `Bearer ${authToken}` }
|
|
254
|
+
: null;
|
|
255
|
+
if (bootstrapHeaders) {
|
|
256
|
+
const bootstrapResponse = await requestWithRouteFallback(ctx, {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
routes: ['/api/v1/agent/onboarding/me/bootstrap-self'],
|
|
259
|
+
headers: bootstrapHeaders,
|
|
260
|
+
body: {
|
|
261
|
+
agentKey: bootstrapAgentKey,
|
|
262
|
+
runMode: loginRunMode,
|
|
263
|
+
custodyMode,
|
|
264
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
265
|
+
rotate: parseBooleanFlag(flags.rotateAgentKey, false),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
bootstrapAgentApiKey = String(
|
|
269
|
+
bootstrapResponse?.agentApiKey || bootstrapResponse?.data?.agentApiKey || ''
|
|
270
|
+
).trim();
|
|
271
|
+
bootstrapPerformed = true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const next = {
|
|
276
|
+
...current,
|
|
277
|
+
runMode: loginRunMode,
|
|
278
|
+
custodyMode,
|
|
279
|
+
auth: {
|
|
280
|
+
...(current.auth && typeof current.auth === 'object' ? current.auth : {}),
|
|
281
|
+
lastLoginAt: new Date().toISOString(),
|
|
282
|
+
source: loginSource,
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
if (authToken) {
|
|
286
|
+
next.auth.authToken = authToken;
|
|
287
|
+
delete next.auth.ownerSessionToken;
|
|
288
|
+
} else if (ownerSessionToken) {
|
|
289
|
+
next.auth.ownerSessionToken = ownerSessionToken;
|
|
290
|
+
delete next.auth.authToken;
|
|
291
|
+
}
|
|
292
|
+
if (flags.agentApiKey) {
|
|
293
|
+
next.agent = {
|
|
294
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
295
|
+
apiKey: requestedAgentApiKey,
|
|
296
|
+
};
|
|
297
|
+
} else if (bootstrapAgentApiKey) {
|
|
298
|
+
next.agent = {
|
|
299
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
300
|
+
apiKey: bootstrapAgentApiKey,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
persistProfileConfig(next);
|
|
304
|
+
printEnvelope({
|
|
305
|
+
success: true,
|
|
306
|
+
code: 'LOGIN_OK',
|
|
307
|
+
message: 'Login credentials stored',
|
|
308
|
+
data: {
|
|
309
|
+
hasAuthToken: Boolean(authToken),
|
|
310
|
+
hasOwnerSessionToken: Boolean(ownerSessionToken),
|
|
311
|
+
hasAgentApiKey: Boolean(requestedAgentApiKey || bootstrapAgentApiKey),
|
|
312
|
+
bootstrapPerformed,
|
|
313
|
+
runMode: loginRunMode,
|
|
314
|
+
custodyMode,
|
|
315
|
+
configPath: getProfileConfigPath(),
|
|
316
|
+
},
|
|
317
|
+
meta: { apiBase: ctx.apiBase },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
async function commandLoginEmail(ctx, flags) {
|
|
323
|
+
const current = loadProfileConfig();
|
|
324
|
+
const email = String(flags.address || flags.email || '').trim().toLowerCase();
|
|
325
|
+
if (!email) {
|
|
326
|
+
throw createCliError(
|
|
327
|
+
'LOGIN_EMAIL_REQUIRED',
|
|
328
|
+
isNonInteractive(flags)
|
|
329
|
+
? 'Missing email address for non-interactive login. Use: thirdfy-agent login email user@example.com [--code <otp> --accept-terms]'
|
|
330
|
+
: 'Missing email address. Use: thirdfy-agent login email user@example.com'
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
334
|
+
throw createCliError('LOGIN_EMAIL_INVALID', 'Provide a valid email address for Thirdfy email login.');
|
|
335
|
+
}
|
|
336
|
+
const code = String(flags.code || flags.otp || '').trim();
|
|
337
|
+
const keyName = String(flags.keyName || 'Thirdfy CLI').trim();
|
|
338
|
+
if (!code) {
|
|
339
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/cli/email/start', {
|
|
340
|
+
email,
|
|
341
|
+
keyName,
|
|
342
|
+
requestedCapabilities: {
|
|
343
|
+
walletApi: true,
|
|
344
|
+
agentApi: true,
|
|
345
|
+
evmWallet: true,
|
|
346
|
+
solanaWallet: true,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
const data = response?.data || response || {};
|
|
350
|
+
const next = {
|
|
351
|
+
...current,
|
|
352
|
+
login: {
|
|
353
|
+
...(current.login && typeof current.login === 'object' ? current.login : {}),
|
|
354
|
+
email: {
|
|
355
|
+
lastEmail: email,
|
|
356
|
+
lastAttemptId: data.loginAttemptId || null,
|
|
357
|
+
sentAt: new Date().toISOString(),
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
persistProfileConfig(next);
|
|
362
|
+
printEnvelope({
|
|
363
|
+
success: true,
|
|
364
|
+
code: 'LOGIN_EMAIL_CODE_SENT',
|
|
365
|
+
message: response?.message || 'Verification code sent',
|
|
366
|
+
data: {
|
|
367
|
+
email,
|
|
368
|
+
loginAttemptId: data.loginAttemptId || null,
|
|
369
|
+
expiresAt: data.expiresAt || null,
|
|
370
|
+
nextCommand: data.nextCommand || `thirdfy-agent login email ${email} --code <code> --accept-terms`,
|
|
371
|
+
configPath: getProfileConfigPath(),
|
|
372
|
+
},
|
|
373
|
+
meta: { apiBase: ctx.apiBase },
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!parseBooleanFlag(flags.acceptTerms, false)) {
|
|
379
|
+
throw createCliError(
|
|
380
|
+
'LOGIN_EMAIL_TERMS_REQUIRED',
|
|
381
|
+
'Email login requires --accept-terms to complete first-run onboarding.'
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const lastAttempt = current?.login?.email && typeof current.login.email === 'object' ? current.login.email : {};
|
|
386
|
+
const response = await apiPost(ctx, '/api/v1/agent/onboarding/cli/email/complete', {
|
|
387
|
+
email,
|
|
388
|
+
code,
|
|
389
|
+
loginAttemptId: String(flags.loginAttemptId || lastAttempt.lastAttemptId || '').trim() || undefined,
|
|
390
|
+
acceptTerms: true,
|
|
391
|
+
keyName,
|
|
392
|
+
agentKey: String(flags.agentKey || '').trim() || undefined,
|
|
393
|
+
runMode: normalizeRunMode(flags.runMode || current.runMode || process.env.THIRDFY_RUN_MODE || 'agent_wallet'),
|
|
394
|
+
custodyMode: normalizeCustodyMode(
|
|
395
|
+
flags.custodyMode || current.custodyMode || process.env.THIRDFY_CUSTODY_MODE,
|
|
396
|
+
normalizeRunMode(flags.runMode || current.runMode || process.env.THIRDFY_RUN_MODE || 'agent_wallet')
|
|
397
|
+
),
|
|
398
|
+
bootstrapAgent: !parseBooleanFlag(flags.noBootstrapAgent, false),
|
|
399
|
+
rotateAgentKey: parseBooleanFlag(flags.rotateAgentKey, false),
|
|
400
|
+
});
|
|
401
|
+
const data = response?.data || response || {};
|
|
402
|
+
const ownerSessionToken = String(data.ownerSessionToken || '').trim();
|
|
403
|
+
const bootstrap = data.bootstrap && typeof data.bootstrap === 'object' ? data.bootstrap : {};
|
|
404
|
+
const agentApiKey = extractAgentApiKey(data);
|
|
405
|
+
const agentKey = extractAgentKey(data);
|
|
406
|
+
const wallets = data.wallets && typeof data.wallets === 'object' ? data.wallets : {};
|
|
407
|
+
const executionWallets = data.executionWallets && typeof data.executionWallets === 'object' ? data.executionWallets : {};
|
|
408
|
+
const userDid = String(data.owner?.creatorDid || wallets.userDid || '').trim();
|
|
409
|
+
const primaryEvmWallet = String(wallets.primaryEvmWallet || data.owner?.creatorWallet || '').trim();
|
|
410
|
+
const persistedWallets = {
|
|
411
|
+
...wallets,
|
|
412
|
+
...(userDid ? { userDid } : {}),
|
|
413
|
+
...(primaryEvmWallet ? { primaryEvmWallet } : {}),
|
|
414
|
+
...(Object.keys(executionWallets).length ? { executionWallets } : {}),
|
|
415
|
+
};
|
|
416
|
+
const runMode = normalizeRunMode(flags.runMode || current.runMode || process.env.THIRDFY_RUN_MODE || 'agent_wallet');
|
|
417
|
+
const custodyMode = normalizeCustodyMode(flags.custodyMode || current.custodyMode || process.env.THIRDFY_CUSTODY_MODE, runMode);
|
|
418
|
+
if (!ownerSessionToken) {
|
|
419
|
+
throw createCliError('LOGIN_EMAIL_OWNER_SESSION_MISSING', 'Email onboarding completed without an owner session token.');
|
|
420
|
+
}
|
|
421
|
+
const next = {
|
|
422
|
+
...current,
|
|
423
|
+
runMode,
|
|
424
|
+
custodyMode,
|
|
425
|
+
auth: {
|
|
426
|
+
...(current.auth && typeof current.auth === 'object' ? current.auth : {}),
|
|
427
|
+
lastLoginAt: new Date().toISOString(),
|
|
428
|
+
source: 'email',
|
|
429
|
+
ownerSessionToken,
|
|
430
|
+
ownerSessionExpiresAt: data.ownerSessionExpiresAt || null,
|
|
431
|
+
},
|
|
432
|
+
wallets: persistedWallets,
|
|
433
|
+
login: {
|
|
434
|
+
...(current.login && typeof current.login === 'object' ? current.login : {}),
|
|
435
|
+
email: {
|
|
436
|
+
lastEmail: email,
|
|
437
|
+
completedAt: new Date().toISOString(),
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
delete next.auth.authToken;
|
|
442
|
+
if (agentApiKey || agentKey) {
|
|
443
|
+
next.agent = {
|
|
444
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
445
|
+
...(agentApiKey ? { apiKey: agentApiKey } : {}),
|
|
446
|
+
...(agentKey ? { agentKey } : {}),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
persistProfileConfig(next);
|
|
450
|
+
printEnvelope({
|
|
451
|
+
success: true,
|
|
452
|
+
code: 'LOGIN_EMAIL_OK',
|
|
453
|
+
message: response?.message || 'Email login completed',
|
|
454
|
+
data: {
|
|
455
|
+
email,
|
|
456
|
+
hasOwnerSessionToken: Boolean(ownerSessionToken),
|
|
457
|
+
hasAgentApiKey: Boolean(agentApiKey),
|
|
458
|
+
agentKey: agentKey || null,
|
|
459
|
+
evmWallets: Array.isArray(persistedWallets.evm) ? persistedWallets.evm : [],
|
|
460
|
+
solanaWallets: Array.isArray(persistedWallets.solana) ? persistedWallets.solana : [],
|
|
461
|
+
primaryEvmWallet: persistedWallets.primaryEvmWallet || null,
|
|
462
|
+
executionWallets:
|
|
463
|
+
persistedWallets.executionWallets && typeof persistedWallets.executionWallets === 'object'
|
|
464
|
+
? persistedWallets.executionWallets
|
|
465
|
+
: {},
|
|
466
|
+
ownerLinkedWallet: data.ownerLinkedWallet || null,
|
|
467
|
+
configPath: getProfileConfigPath(),
|
|
468
|
+
nextSteps: response?.nextSteps || [],
|
|
469
|
+
},
|
|
470
|
+
meta: { apiBase: ctx.apiBase },
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
async function commandLogout(ctx, flags) {
|
|
476
|
+
const current = loadProfileConfig();
|
|
477
|
+
const localOnly = flags.all ? false : true;
|
|
478
|
+
const auth = current.auth && typeof current.auth === 'object' ? current.auth : {};
|
|
479
|
+
const ownerSessionToken = String(auth.ownerSessionToken || '').trim();
|
|
480
|
+
let revokeAttempted = false;
|
|
481
|
+
let revokeSuccess = false;
|
|
482
|
+
let revokeError = null;
|
|
483
|
+
|
|
484
|
+
if (!localOnly && ownerSessionToken) {
|
|
485
|
+
revokeAttempted = true;
|
|
486
|
+
try {
|
|
487
|
+
await requestWithRouteFallback(ctx, {
|
|
488
|
+
method: 'POST',
|
|
489
|
+
routes: ['/api/v1/agent/onboarding/wallet/logout', '/api/v1/agent/onboarding/wallet/revoke-session'],
|
|
490
|
+
body: {},
|
|
491
|
+
headers: {
|
|
492
|
+
'x-owner-session-token': ownerSessionToken,
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
revokeSuccess = true;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
revokeError = error?.message || 'OWNER_SESSION_REVOKE_FAILED';
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const next = { ...current };
|
|
502
|
+
delete next.auth;
|
|
503
|
+
if (flags.clearAgentKey) {
|
|
504
|
+
delete next.agent;
|
|
505
|
+
}
|
|
506
|
+
persistProfileConfig(next);
|
|
507
|
+
printEnvelope({
|
|
508
|
+
success: true,
|
|
509
|
+
code: 'LOGOUT_OK',
|
|
510
|
+
message: 'Local credentials cleared',
|
|
511
|
+
data: {
|
|
512
|
+
localOnly,
|
|
513
|
+
revokeAttempted,
|
|
514
|
+
revokeSuccess,
|
|
515
|
+
revokeError,
|
|
516
|
+
configPath: getProfileConfigPath(),
|
|
517
|
+
},
|
|
518
|
+
meta: { apiBase: ctx.apiBase },
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
async function commandBootstrapBegin(ctx, flags, capabilities) {
|
|
524
|
+
const challenge = await apiPost(
|
|
525
|
+
ctx,
|
|
526
|
+
'/api/v1/agent/onboarding/wallet/challenge',
|
|
527
|
+
buildWalletChallengePayload(flags)
|
|
528
|
+
);
|
|
529
|
+
printEnvelope({
|
|
530
|
+
success: true,
|
|
531
|
+
code: 'BOOTSTRAP_BEGIN_OK',
|
|
532
|
+
message: 'Bootstrap challenge created',
|
|
533
|
+
data: {
|
|
534
|
+
challenge,
|
|
535
|
+
nextStep:
|
|
536
|
+
'Run `thirdfy-agent bootstrap complete --agent-key <key> --challenge-id <id> --signature <hex> [--name <agent-name>]`',
|
|
537
|
+
},
|
|
538
|
+
meta: {
|
|
539
|
+
apiBase: ctx.apiBase,
|
|
540
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
async function commandBootstrapComplete(ctx, flags, capabilities) {
|
|
547
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
548
|
+
const lane = normalizeRunMode(flags.lane || flags.runMode || 'self', flags.lane ? '--lane' : '--run-mode');
|
|
549
|
+
let proofType = String(flags.proofType || '').trim() || 'session_token';
|
|
550
|
+
if (proofType !== 'session_token' && proofType !== 'wallet_signature') {
|
|
551
|
+
throw new Error('Invalid --proof-type. Supported values: session_token (preferred) or wallet_signature.');
|
|
552
|
+
}
|
|
553
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
554
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
555
|
+
let challengeId = String(flags.challengeId || '').trim();
|
|
556
|
+
let signature = String(flags.signature || '').trim();
|
|
557
|
+
if (!String(flags.proofType || '').trim() && !ownerSessionToken && !authToken && challengeId && signature) {
|
|
558
|
+
proofType = 'wallet_signature';
|
|
559
|
+
}
|
|
560
|
+
if (proofType === 'wallet_signature' && (!challengeId || !signature)) {
|
|
561
|
+
throw new Error('wallet_signature proof requires --challenge-id and --signature.');
|
|
562
|
+
}
|
|
563
|
+
if (proofType === 'session_token' && !ownerSessionToken && !authToken && challengeId && signature) {
|
|
564
|
+
const verify = await verifyOwnerWalletChallenge(ctx, {
|
|
565
|
+
...flags,
|
|
566
|
+
agentKey,
|
|
567
|
+
challengeId,
|
|
568
|
+
signature,
|
|
569
|
+
});
|
|
570
|
+
ownerSessionToken = String(verify?.ownerSessionToken || verify?.data?.ownerSessionToken || '').trim();
|
|
571
|
+
if (!ownerSessionToken) {
|
|
572
|
+
throw new Error('Wallet verify did not return owner session token required for bootstrap registration.');
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (proofType === 'session_token' && (ownerSessionToken || authToken)) {
|
|
576
|
+
// Avoid replaying a single-use challenge across bootstrap and register calls.
|
|
577
|
+
challengeId = '';
|
|
578
|
+
signature = '';
|
|
579
|
+
}
|
|
580
|
+
if (proofType === 'session_token' && !ownerSessionToken && !authToken) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
'Bootstrap currently supports session_token proof only. Provide --owner-session-token/--auth-token, or --challenge-id + --signature to exchange into a session token.'
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const bootstrapPayload = {
|
|
587
|
+
sub: agentKey,
|
|
588
|
+
lane,
|
|
589
|
+
scope: ['onboarding:bootstrap'],
|
|
590
|
+
proof_type: proofType,
|
|
591
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
592
|
+
...(authToken ? { authToken } : {}),
|
|
593
|
+
...(challengeId ? { challengeId } : {}),
|
|
594
|
+
...(signature ? { signature } : {}),
|
|
595
|
+
agentKey,
|
|
596
|
+
};
|
|
597
|
+
const issued = await issueBootstrapToken(ctx, flags, bootstrapPayload);
|
|
598
|
+
const bootstrapToken = String(issued.data?.token || '').trim();
|
|
599
|
+
|
|
600
|
+
const config = loadProfileConfig();
|
|
601
|
+
const next = {
|
|
602
|
+
...config,
|
|
603
|
+
auth: {
|
|
604
|
+
...(config.auth && typeof config.auth === 'object' ? config.auth : {}),
|
|
605
|
+
bootstrapToken,
|
|
606
|
+
bootstrapTokenIssuedAt: new Date().toISOString(),
|
|
607
|
+
bootstrapLane: lane,
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
persistProfileConfig(next);
|
|
611
|
+
|
|
612
|
+
let registration = null;
|
|
613
|
+
if (flags.name) {
|
|
614
|
+
registration = await commandDeveloperBootstrap(
|
|
615
|
+
ctx,
|
|
616
|
+
{
|
|
617
|
+
...flags,
|
|
618
|
+
agentKey,
|
|
619
|
+
name: String(flags.name).trim(),
|
|
620
|
+
challengeId,
|
|
621
|
+
signature,
|
|
622
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
623
|
+
__suppressOutput: true,
|
|
624
|
+
},
|
|
625
|
+
capabilities
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const registrationAgentApiKey = extractAgentApiKey(registration || {});
|
|
629
|
+
const registrationAgentKey = extractAgentKey(registration || {}) || agentKey;
|
|
630
|
+
if (registrationAgentApiKey || registrationAgentKey) {
|
|
631
|
+
const latest = loadProfileConfig();
|
|
632
|
+
persistProfileConfig({
|
|
633
|
+
...latest,
|
|
634
|
+
agent: {
|
|
635
|
+
...(latest.agent && typeof latest.agent === 'object' ? latest.agent : {}),
|
|
636
|
+
...(registrationAgentApiKey ? { apiKey: registrationAgentApiKey } : {}),
|
|
637
|
+
...(registrationAgentKey ? { agentKey: registrationAgentKey } : {}),
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
printEnvelope({
|
|
643
|
+
success: true,
|
|
644
|
+
code: 'BOOTSTRAP_COMPLETE_OK',
|
|
645
|
+
message: 'Bootstrap token issued',
|
|
646
|
+
data: {
|
|
647
|
+
bootstrap: issued.data,
|
|
648
|
+
registration,
|
|
649
|
+
persisted: {
|
|
650
|
+
configPath: getProfileConfigPath(),
|
|
651
|
+
hasBootstrapToken: Boolean(bootstrapToken),
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
meta: {
|
|
655
|
+
apiBase: ctx.apiBase,
|
|
656
|
+
mcpBase: issued.mcpBase,
|
|
657
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
async function commandDeveloperBootstrap(ctx, flags, capabilities) {
|
|
664
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
665
|
+
const name = requireFlag(flags, 'name', 'Missing --name');
|
|
666
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
667
|
+
let usedWalletFlow = false;
|
|
668
|
+
const challengeId = String(flags.challengeId || '').trim();
|
|
669
|
+
const signature = String(flags.signature || '').trim();
|
|
670
|
+
|
|
671
|
+
if (!ownerSessionToken && signature) {
|
|
672
|
+
if (!challengeId) {
|
|
673
|
+
throw new Error('Missing --challenge-id for wallet bootstrap. Run `agent auth challenge` first.');
|
|
674
|
+
}
|
|
675
|
+
const verify = await verifyOwnerWalletChallenge(ctx, {
|
|
676
|
+
...flags,
|
|
677
|
+
challengeId,
|
|
678
|
+
signature,
|
|
679
|
+
agentKey,
|
|
680
|
+
});
|
|
681
|
+
ownerSessionToken = String(verify?.ownerSessionToken || verify?.data?.ownerSessionToken || '').trim();
|
|
682
|
+
usedWalletFlow = true;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const registerFlags = {
|
|
686
|
+
...flags,
|
|
687
|
+
agentKey,
|
|
688
|
+
name,
|
|
689
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
690
|
+
};
|
|
691
|
+
const registerResponse = await commandAgentRegister(
|
|
692
|
+
ctx,
|
|
693
|
+
{ ...registerFlags, __suppressOutput: true },
|
|
694
|
+
capabilities
|
|
695
|
+
);
|
|
696
|
+
const agentApiKey = extractAgentApiKey(registerResponse || {});
|
|
697
|
+
const responseAgentKey = extractAgentKey(registerResponse || {}) || agentKey;
|
|
698
|
+
|
|
699
|
+
const config = loadProfileConfig();
|
|
700
|
+
const next = {
|
|
701
|
+
...config,
|
|
702
|
+
auth: {
|
|
703
|
+
...(config.auth && typeof config.auth === 'object' ? config.auth : {}),
|
|
704
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
705
|
+
lastLoginAt: new Date().toISOString(),
|
|
706
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
707
|
+
},
|
|
708
|
+
developer: {
|
|
709
|
+
...(config.developer && typeof config.developer === 'object' ? config.developer : {}),
|
|
710
|
+
agentKey,
|
|
711
|
+
bootstrapCompletedAt: new Date().toISOString(),
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
if (agentApiKey || responseAgentKey) {
|
|
715
|
+
next.agent = {
|
|
716
|
+
...(config.agent && typeof config.agent === 'object' ? config.agent : {}),
|
|
717
|
+
...(agentApiKey ? { apiKey: agentApiKey } : {}),
|
|
718
|
+
...(responseAgentKey ? { agentKey: responseAgentKey } : {}),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
persistProfileConfig(next);
|
|
722
|
+
if (!flags.__suppressOutput) {
|
|
723
|
+
printEnvelope({
|
|
724
|
+
success: true,
|
|
725
|
+
code: 'DEVELOPER_BOOTSTRAP_OK',
|
|
726
|
+
message: 'Developer bootstrap completed',
|
|
727
|
+
data: {
|
|
728
|
+
registration: registerResponse,
|
|
729
|
+
persisted: {
|
|
730
|
+
ownerSessionTokenSaved: Boolean(ownerSessionToken),
|
|
731
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
732
|
+
configPath: getProfileConfigPath(),
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
meta: {
|
|
736
|
+
apiBase: ctx.apiBase,
|
|
737
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
registration: registerResponse,
|
|
743
|
+
ownerSessionTokenSaved: Boolean(ownerSessionToken),
|
|
744
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return {
|
|
749
|
+
commandProfileInit,
|
|
750
|
+
commandProfileUse,
|
|
751
|
+
commandProfileShow,
|
|
752
|
+
commandConfigSet,
|
|
753
|
+
commandHelpOnboarding,
|
|
754
|
+
commandWhoami,
|
|
755
|
+
commandLogin,
|
|
756
|
+
commandLoginEmail,
|
|
757
|
+
commandLogout,
|
|
758
|
+
commandBootstrapBegin,
|
|
759
|
+
commandBootstrapComplete,
|
|
760
|
+
commandDeveloperBootstrap,
|
|
761
|
+
};
|
|
762
|
+
}
|