@thirdfy/agent-cli 0.1.11 → 0.1.12

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.
Files changed (2) hide show
  1. package/bin/thirdfy-agent.mjs +218 -26
  2. package/package.json +1 -1
@@ -214,6 +214,14 @@ async function main() {
214
214
  case 'developer bootstrap':
215
215
  await commandDeveloperBootstrap(context, subFlags, capabilities);
216
216
  return;
217
+ case 'bootstrap begin':
218
+ case 'onboarding begin':
219
+ await commandBootstrapBegin(context, subFlags, capabilities);
220
+ return;
221
+ case 'bootstrap complete':
222
+ case 'onboarding complete':
223
+ await commandBootstrapComplete(context, subFlags, capabilities);
224
+ return;
217
225
  default:
218
226
  break;
219
227
  }
@@ -1091,11 +1099,11 @@ async function commandDeveloperBootstrap(ctx, flags, capabilities) {
1091
1099
  if (!challengeId) {
1092
1100
  throw new Error('Missing --challenge-id for wallet bootstrap. Run `agent auth challenge` first.');
1093
1101
  }
1094
- const verify = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', {
1102
+ const verify = await verifyOwnerWalletChallenge(ctx, {
1103
+ ...flags,
1095
1104
  challengeId,
1096
1105
  signature,
1097
1106
  agentKey,
1098
- ...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
1099
1107
  });
1100
1108
  ownerSessionToken = String(verify?.ownerSessionToken || verify?.data?.ownerSessionToken || '').trim();
1101
1109
  usedWalletFlow = true;
@@ -1129,23 +1137,30 @@ async function commandDeveloperBootstrap(ctx, flags, capabilities) {
1129
1137
  },
1130
1138
  };
1131
1139
  persistProfileConfig(next);
1132
- printEnvelope({
1133
- success: true,
1134
- code: 'DEVELOPER_BOOTSTRAP_OK',
1135
- message: 'Developer bootstrap completed',
1136
- data: {
1137
- registration: registerResponse,
1138
- persisted: {
1139
- ownerSessionTokenSaved: Boolean(ownerSessionToken),
1140
- source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
1141
- configPath: getProfileConfigPath(),
1140
+ if (!flags.__suppressOutput) {
1141
+ printEnvelope({
1142
+ success: true,
1143
+ code: 'DEVELOPER_BOOTSTRAP_OK',
1144
+ message: 'Developer bootstrap completed',
1145
+ data: {
1146
+ registration: registerResponse,
1147
+ persisted: {
1148
+ ownerSessionTokenSaved: Boolean(ownerSessionToken),
1149
+ source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
1150
+ configPath: getProfileConfigPath(),
1151
+ },
1142
1152
  },
1143
- },
1144
- meta: {
1145
- apiBase: ctx.apiBase,
1146
- capabilitiesVersion: capabilities?.contractVersion || null,
1147
- },
1148
- });
1153
+ meta: {
1154
+ apiBase: ctx.apiBase,
1155
+ capabilitiesVersion: capabilities?.contractVersion || null,
1156
+ },
1157
+ });
1158
+ }
1159
+ return {
1160
+ registration: registerResponse,
1161
+ ownerSessionTokenSaved: Boolean(ownerSessionToken),
1162
+ source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
1163
+ };
1149
1164
  }
1150
1165
 
1151
1166
  async function commandAgentKeyRotate(ctx, flags, capabilities) {
@@ -1188,14 +1203,21 @@ async function commandAgentKeyRevoke(ctx, flags, capabilities) {
1188
1203
  });
1189
1204
  }
1190
1205
 
1191
- async function commandAgentAuthChallenge(ctx, flags, capabilities) {
1206
+ function buildWalletChallengePayload(flags) {
1192
1207
  const payload = {
1193
1208
  agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
1194
1209
  chainId: Number(flags.chainId || 8453),
1195
1210
  };
1196
1211
  if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
1212
+ return payload;
1213
+ }
1197
1214
 
1198
- const response = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/challenge', payload);
1215
+ async function commandAgentAuthChallenge(ctx, flags, capabilities) {
1216
+ const response = await apiPost(
1217
+ ctx,
1218
+ '/api/v1/agent/onboarding/wallet/challenge',
1219
+ buildWalletChallengePayload(flags)
1220
+ );
1199
1221
  printEnvelope({
1200
1222
  success: true,
1201
1223
  code: 'OWNER_CHALLENGE_CREATED',
@@ -1209,21 +1231,186 @@ async function commandAgentAuthChallenge(ctx, flags, capabilities) {
1209
1231
  }
1210
1232
 
1211
1233
  async function commandAgentAuthVerify(ctx, flags, capabilities) {
1234
+ const response = await verifyOwnerWalletChallenge(ctx, flags);
1235
+ printEnvelope({
1236
+ success: true,
1237
+ code: 'OWNER_VERIFIED',
1238
+ message: 'Wallet challenge verified',
1239
+ data: response,
1240
+ meta: {
1241
+ apiBase: ctx.apiBase,
1242
+ capabilitiesVersion: capabilities?.contractVersion || null,
1243
+ },
1244
+ });
1245
+ }
1246
+
1247
+ async function verifyOwnerWalletChallenge(ctx, flags) {
1212
1248
  const payload = {
1213
1249
  challengeId: requireFlag(flags, 'challengeId', 'Missing --challenge-id'),
1214
1250
  signature: requireFlag(flags, 'signature', 'Missing --signature'),
1215
1251
  agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
1216
1252
  };
1217
1253
  if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
1254
+ return apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', payload);
1255
+ }
1256
+
1257
+ function resolveMcpBase(flags) {
1258
+ return String(flags.mcpBase || process.env.THIRDFY_MCP_BASE_URL || 'https://mcp.thirdfy.com').trim().replace(/\/+$/, '');
1259
+ }
1260
+
1261
+ async function issueBootstrapToken(ctx, flags, payload) {
1262
+ const mcpBase = resolveMcpBase(flags);
1263
+ const controller = new AbortController();
1264
+ const timeout = setTimeout(() => controller.abort(), Number(ctx.timeoutMs || DEFAULT_TIMEOUT_MS));
1265
+ let response;
1266
+ let text = '';
1267
+ try {
1268
+ response = await fetch(`${mcpBase}/auth/bootstrap/issue`, {
1269
+ method: 'POST',
1270
+ headers: {
1271
+ 'content-type': 'application/json',
1272
+ 'x-cli-version': CLI_VERSION,
1273
+ },
1274
+ body: JSON.stringify(payload),
1275
+ signal: controller.signal,
1276
+ });
1277
+ text = await response.text();
1278
+ } catch (error) {
1279
+ if (error?.name === 'AbortError') {
1280
+ throw createCliError('TIMEOUT', `Request timed out after ${ctx.timeoutMs}ms`, {
1281
+ routes: ['/auth/bootstrap/issue'],
1282
+ });
1283
+ }
1284
+ throw error;
1285
+ } finally {
1286
+ clearTimeout(timeout);
1287
+ }
1288
+ const data = text ? safeJsonParse(text) : {};
1289
+ if (!response.ok) {
1290
+ const message = data?.error || data?.message || 'Bootstrap token issuance failed';
1291
+ throw createCliError('BOOTSTRAP_TOKEN_ISSUE_FAILED', message, data);
1292
+ }
1293
+ return {
1294
+ mcpBase,
1295
+ data,
1296
+ };
1297
+ }
1218
1298
 
1219
- const response = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', payload);
1299
+ async function commandBootstrapBegin(ctx, flags, capabilities) {
1300
+ const challenge = await apiPost(
1301
+ ctx,
1302
+ '/api/v1/agent/onboarding/wallet/challenge',
1303
+ buildWalletChallengePayload(flags)
1304
+ );
1220
1305
  printEnvelope({
1221
1306
  success: true,
1222
- code: 'OWNER_VERIFIED',
1223
- message: 'Wallet challenge verified',
1224
- data: response,
1307
+ code: 'BOOTSTRAP_BEGIN_OK',
1308
+ message: 'Bootstrap challenge created',
1309
+ data: {
1310
+ challenge,
1311
+ nextStep:
1312
+ 'Run `thirdfy-agent bootstrap complete --agent-key <key> --challenge-id <id> --signature <hex> [--name <agent-name>]`',
1313
+ },
1314
+ meta: {
1315
+ apiBase: ctx.apiBase,
1316
+ capabilitiesVersion: capabilities?.contractVersion || null,
1317
+ },
1318
+ });
1319
+ }
1320
+
1321
+ async function commandBootstrapComplete(ctx, flags, capabilities) {
1322
+ const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
1323
+ const lane = normalizeRunMode(flags.lane || flags.runMode || 'self', flags.lane ? '--lane' : '--run-mode');
1324
+ const proofType = String(flags.proofType || '').trim() || 'session_token';
1325
+ if (proofType !== 'session_token' && proofType !== 'wallet_signature') {
1326
+ throw new Error('Invalid --proof-type. Supported values: session_token (preferred) or wallet_signature (challenge exchange only).');
1327
+ }
1328
+ let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
1329
+ const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
1330
+ let challengeId = String(flags.challengeId || '').trim();
1331
+ let signature = String(flags.signature || '').trim();
1332
+ if (proofType === 'wallet_signature' && (!challengeId || !signature)) {
1333
+ throw new Error('wallet_signature bootstrap is not supported directly. Provide --owner-session-token or --auth-token.');
1334
+ }
1335
+ if (!ownerSessionToken && !authToken && challengeId && signature) {
1336
+ const verify = await verifyOwnerWalletChallenge(ctx, {
1337
+ ...flags,
1338
+ agentKey,
1339
+ challengeId,
1340
+ signature,
1341
+ });
1342
+ ownerSessionToken = String(verify?.ownerSessionToken || verify?.data?.ownerSessionToken || '').trim();
1343
+ if (!ownerSessionToken) {
1344
+ throw new Error('Wallet verify did not return owner session token required for bootstrap registration.');
1345
+ }
1346
+ }
1347
+ if (ownerSessionToken || authToken) {
1348
+ // Avoid replaying a single-use challenge across bootstrap and register calls.
1349
+ challengeId = '';
1350
+ signature = '';
1351
+ }
1352
+ if (!ownerSessionToken && !authToken) {
1353
+ throw new Error(
1354
+ 'Bootstrap currently supports session_token proof only. Provide --owner-session-token/--auth-token, or --challenge-id + --signature to exchange into a session token.'
1355
+ );
1356
+ }
1357
+
1358
+ const bootstrapPayload = {
1359
+ sub: agentKey,
1360
+ lane,
1361
+ scope: ['onboarding:bootstrap'],
1362
+ proof_type: 'session_token',
1363
+ ...(ownerSessionToken ? { ownerSessionToken } : {}),
1364
+ ...(authToken ? { authToken } : {}),
1365
+ agentKey,
1366
+ };
1367
+ const issued = await issueBootstrapToken(ctx, flags, bootstrapPayload);
1368
+ const bootstrapToken = String(issued.data?.token || '').trim();
1369
+
1370
+ const config = loadProfileConfig();
1371
+ const next = {
1372
+ ...config,
1373
+ auth: {
1374
+ ...(config.auth && typeof config.auth === 'object' ? config.auth : {}),
1375
+ bootstrapToken,
1376
+ bootstrapTokenIssuedAt: new Date().toISOString(),
1377
+ bootstrapLane: lane,
1378
+ },
1379
+ };
1380
+ persistProfileConfig(next);
1381
+
1382
+ let registration = null;
1383
+ if (flags.name) {
1384
+ registration = await commandDeveloperBootstrap(
1385
+ ctx,
1386
+ {
1387
+ ...flags,
1388
+ agentKey,
1389
+ name: String(flags.name).trim(),
1390
+ challengeId,
1391
+ signature,
1392
+ ...(ownerSessionToken ? { ownerSessionToken } : {}),
1393
+ __suppressOutput: true,
1394
+ },
1395
+ capabilities
1396
+ );
1397
+ }
1398
+
1399
+ printEnvelope({
1400
+ success: true,
1401
+ code: 'BOOTSTRAP_COMPLETE_OK',
1402
+ message: 'Bootstrap token issued',
1403
+ data: {
1404
+ bootstrap: issued.data,
1405
+ registration,
1406
+ persisted: {
1407
+ configPath: getProfileConfigPath(),
1408
+ hasBootstrapToken: Boolean(bootstrapToken),
1409
+ },
1410
+ },
1225
1411
  meta: {
1226
1412
  apiBase: ctx.apiBase,
1413
+ mcpBase: issued.mcpBase,
1227
1414
  capabilitiesVersion: capabilities?.contractVersion || null,
1228
1415
  },
1229
1416
  });
@@ -1700,11 +1887,11 @@ function normalizeProfileName(value) {
1700
1887
  throw new Error(`Unsupported profile "${value}". Supported: ${Object.keys(PROFILE_DEFAULTS).join(', ')}`);
1701
1888
  }
1702
1889
 
1703
- function normalizeRunMode(value) {
1890
+ function normalizeRunMode(value, flagName = '--run-mode') {
1704
1891
  const normalized = String(value || '').trim().toLowerCase();
1705
1892
  if (!normalized) return 'thirdfy';
1706
1893
  if (RUN_MODES.includes(normalized)) return normalized;
1707
- throw new Error(`Unsupported --run-mode "${value}". Supported: ${RUN_MODES.join(', ')}`);
1894
+ throw new Error(`Unsupported ${flagName} "${value}". Supported: ${RUN_MODES.join(', ')}`);
1708
1895
  }
1709
1896
 
1710
1897
  function defaultCustodyModeForRunMode(runMode) {
@@ -2803,6 +2990,10 @@ Core commands:
2803
2990
  thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
2804
2991
  thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
2805
2992
  thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
2993
+ thirdfy-agent bootstrap begin --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
2994
+ thirdfy-agent bootstrap complete --agent-key <key> [--proof-type session_token] [--lane self|hybrid|thirdfy] [--owner-session-token <token> | --auth-token <token> | --challenge-id <id> --signature <hex>] [--name <agent-name>] [--mcp-base <url>] [--json]
2995
+ thirdfy-agent onboarding begin ... # alias of bootstrap begin
2996
+ thirdfy-agent onboarding complete ... # alias of bootstrap complete
2806
2997
  thirdfy-agent developer bootstrap --agent-key <key> --name <name> [--owner-session-token <token> | --challenge-id <id> --signature <hex>] [--json]
2807
2998
  thirdfy-agent agent register --agent-key <key> --name <name> [--auth-token <token> | --owner-session-token <token>] [--json]
2808
2999
  thirdfy-agent agent key rotate [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
@@ -2820,6 +3011,7 @@ Core commands:
2820
3011
 
2821
3012
  Global flags:
2822
3013
  --api-base <url> default: https://api.thirdfy.com
3014
+ --mcp-base <url> default: https://mcp.thirdfy.com (bootstrap token issuance)
2823
3015
  --run-mode <mode> thirdfy | self | hybrid
2824
3016
  --custody-mode <mode> managed | external
2825
3017
  --profile <name> personal | builder | network
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {