@agentkeysapp/cli 0.1.2

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.
@@ -0,0 +1,3902 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const commander_1 = require("commander");
38
+ const readline = __importStar(require("readline"));
39
+ const program = new commander_1.Command();
40
+ // ─── Helpers ────────────────────────────────────────────────────────────────
41
+ const TAG_RE = /^[a-z0-9-]{1,50}$/;
42
+ function validateTag(tag) {
43
+ return TAG_RE.test(tag);
44
+ }
45
+ function getBaseUrl() {
46
+ return process.env.AGENTKEYS_API_URL ?? 'http://localhost:3000';
47
+ }
48
+ function getApiKey() {
49
+ const key = process.env.AGENTKEYS_API_KEY ?? '';
50
+ if (!key) {
51
+ console.error('✗ AGENTKEYS_API_KEY environment variable is not set.');
52
+ process.exit(3);
53
+ }
54
+ return key;
55
+ }
56
+ async function apiFetch(path, options = {}) {
57
+ const apiKey = getApiKey();
58
+ const url = `${getBaseUrl()}${path}`;
59
+ let res;
60
+ try {
61
+ res = await fetch(url, {
62
+ ...options,
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ 'x-agent-key': apiKey,
66
+ ...(options.headers ?? {}),
67
+ },
68
+ });
69
+ }
70
+ catch {
71
+ console.error('✗ Network error — could not reach AgentKeys API.');
72
+ console.error(' Check your connection or AGENTKEYS_API_URL setting.');
73
+ process.exit(1);
74
+ }
75
+ if (res.status === 429) {
76
+ console.error('⚡ Rate limited. Please wait 60 seconds and try again.');
77
+ process.exit(1);
78
+ }
79
+ if (res.status === 204) {
80
+ return { ok: true, status: 204, data: null, error: null };
81
+ }
82
+ const json = await res.json();
83
+ return {
84
+ ok: res.ok,
85
+ status: res.status,
86
+ data: json.data ?? null,
87
+ error: json.error ?? null,
88
+ };
89
+ }
90
+ function prompt(question) {
91
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
92
+ return new Promise(resolve => {
93
+ rl.question(question, answer => {
94
+ rl.close();
95
+ resolve(answer.trim());
96
+ });
97
+ });
98
+ }
99
+ async function confirm(question, defaultYes = false) {
100
+ const hint = defaultYes ? '[Y/n]' : '[y/N]';
101
+ const answer = await prompt(`${question} ${hint} `);
102
+ if (answer === '')
103
+ return defaultYes;
104
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
105
+ }
106
+ // ─── Profile Interests Add ──────────────────────────────────────────────────
107
+ program
108
+ .command('profile-interests-add')
109
+ .description('Add a single interest tag')
110
+ .argument('<tag>', 'Interest tag (lowercase, alphanumeric, hyphens only, max 50 chars)')
111
+ .option('--json', 'Output as JSON')
112
+ .action(async (tag, opts) => {
113
+ if (!validateTag(tag)) {
114
+ if (opts.json) {
115
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_TAG_FORMAT', message: `Invalid tag format: "${tag}"` } }));
116
+ }
117
+ else {
118
+ console.error(`✗ Invalid tag format: "${tag}"`);
119
+ console.error(' Tags must be lowercase, alphanumeric, and hyphens only (max 50 chars).');
120
+ console.error(' Example: trading-defi, my-custom-skill');
121
+ }
122
+ process.exit(2);
123
+ }
124
+ const result = await apiFetch('/api/agents/me/interests', { method: 'POST', body: JSON.stringify({ tag }) });
125
+ if (!result.ok) {
126
+ const code = result.error?.code;
127
+ if (opts.json) {
128
+ console.log(JSON.stringify({ success: false, error: result.error }));
129
+ }
130
+ else if (code === 'INTEREST_ALREADY_EXISTS') {
131
+ console.error(`✗ Interest '${tag}' is already in your list.`);
132
+ console.error(' Run `agentkeys profile interests` to see all current interests.');
133
+ }
134
+ else if (code === 'MAX_INTERESTS_EXCEEDED') {
135
+ console.error('✗ Maximum interests reached (20/20).');
136
+ console.error(' Remove one first: agentkeys profile interests remove <tag>');
137
+ }
138
+ else {
139
+ console.error(`✗ ${result.error?.message ?? 'Unknown error'}`);
140
+ }
141
+ process.exit(1);
142
+ }
143
+ if (opts.json) {
144
+ console.log(JSON.stringify({ success: true, data: result.data }));
145
+ }
146
+ else {
147
+ console.log(`✓ Added interest: ${tag}`);
148
+ console.log(` You now have ${result.data?.count ?? '?'} interests.`);
149
+ }
150
+ });
151
+ // ─── Profile Interests Remove ───────────────────────────────────────────────
152
+ program
153
+ .command('profile-interests-remove')
154
+ .description('Remove a single interest tag')
155
+ .argument('<tag>', 'Interest tag to remove')
156
+ .option('-y, --yes', 'Skip confirmation prompt')
157
+ .option('--json', 'Output as JSON')
158
+ .action(async (tag, opts) => {
159
+ if (!validateTag(tag)) {
160
+ if (opts.json) {
161
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_TAG_FORMAT', message: `Invalid tag format: "${tag}"` } }));
162
+ }
163
+ else {
164
+ console.error(`✗ Invalid tag format: "${tag}"`);
165
+ }
166
+ process.exit(2);
167
+ }
168
+ if (!opts.yes) {
169
+ const ok = await confirm(`Remove interest '${tag}'?`, false);
170
+ if (!ok) {
171
+ console.log('Aborted.');
172
+ process.exit(0);
173
+ }
174
+ }
175
+ const result = await apiFetch(`/api/agents/me/interests?tag=${encodeURIComponent(tag)}`, { method: 'DELETE' });
176
+ if (!result.ok) {
177
+ if (opts.json) {
178
+ console.log(JSON.stringify({ success: false, error: result.error }));
179
+ }
180
+ else if (result.error?.code === 'INTEREST_NOT_FOUND') {
181
+ console.error(`✗ Interest '${tag}' was not found in your list.`);
182
+ console.error(' Run `agentkeys profile interests` to see current interests.');
183
+ }
184
+ else {
185
+ console.error(`✗ ${result.error?.message ?? 'Unknown error'}`);
186
+ }
187
+ process.exit(1);
188
+ }
189
+ if (opts.json) {
190
+ console.log(JSON.stringify({ success: true, data: { removed_tag: tag, ...result.data } }));
191
+ }
192
+ else {
193
+ console.log(`✓ Removed interest: ${tag}`);
194
+ if (result.data?.count !== undefined) {
195
+ console.log(` You now have ${result.data.count} interests.`);
196
+ }
197
+ }
198
+ });
199
+ // ─── Profile Interests Clear ────────────────────────────────────────────────
200
+ program
201
+ .command('profile-interests-clear')
202
+ .description('Clear all interests')
203
+ .option('-y, --yes', 'Skip confirmation prompt')
204
+ .option('--json', 'Output as JSON')
205
+ .action(async (opts) => {
206
+ // Get current count first
207
+ const current = await apiFetch('/api/agents/me/interests');
208
+ if (!current.ok) {
209
+ console.error(`✗ ${current.error?.message ?? 'Failed to fetch interests'}`);
210
+ process.exit(1);
211
+ }
212
+ const count = current.data?.count ?? 0;
213
+ if (count === 0) {
214
+ if (opts.json) {
215
+ console.log(JSON.stringify({ success: true, data: { action: 'cleared', removed_count: 0 } }));
216
+ }
217
+ else {
218
+ console.log('No interests to clear.');
219
+ }
220
+ process.exit(0);
221
+ }
222
+ if (!opts.yes) {
223
+ const ok = await confirm(`Clear all ${count} interests?`, false);
224
+ if (!ok) {
225
+ console.log('Aborted.');
226
+ process.exit(0);
227
+ }
228
+ }
229
+ const result = await apiFetch('/api/agents/me/interests', { method: 'PUT', body: JSON.stringify({ tags: [] }) });
230
+ if (!result.ok) {
231
+ if (opts.json) {
232
+ console.log(JSON.stringify({ success: false, error: result.error }));
233
+ }
234
+ else {
235
+ console.error(`✗ ${result.error?.message ?? 'Failed to clear interests'}`);
236
+ }
237
+ process.exit(1);
238
+ }
239
+ if (opts.json) {
240
+ console.log(JSON.stringify({ success: true, data: { action: 'cleared', removed_count: count } }));
241
+ process.exit(0);
242
+ }
243
+ console.log(`✓ Cleared all interests${opts.yes ? ` (${count} removed)` : ''}.`);
244
+ if (!opts.yes) {
245
+ const reselect = await confirm('\nWould you like to re-select now?', true);
246
+ if (reselect) {
247
+ console.log('Run `agentkeys setup` to re-select your interests.');
248
+ }
249
+ else {
250
+ console.log("Run `agentkeys setup` whenever you're ready to re-select.");
251
+ }
252
+ }
253
+ });
254
+ // ─── Profile Interests (view) ────────────────────────────────────────────────
255
+ program
256
+ .command('profile-interests')
257
+ .description('View your current interest tags')
258
+ .option('--json', 'Output as JSON')
259
+ .action(async (opts) => {
260
+ const result = await apiFetch('/api/agents/me/interests');
261
+ if (!result.ok) {
262
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch interests'}`);
263
+ process.exit(1);
264
+ }
265
+ if (opts.json) {
266
+ console.log(JSON.stringify({ success: true, data: result.data }));
267
+ return;
268
+ }
269
+ const { interests, count } = result.data;
270
+ if (count === 0) {
271
+ console.log('No interests set yet.');
272
+ console.log('Run `agentkeys setup` to get started, or add tags directly:');
273
+ console.log(' agentkeys profile interests add trading-defi');
274
+ }
275
+ else {
276
+ console.log(`Your interests (${count}):`);
277
+ interests.forEach(tag => console.log(` • ${tag}`));
278
+ console.log();
279
+ console.log('Tip: Run `agentkeys pack browse --sort compatibility` to find matching packs.');
280
+ console.log(' Run `agentkeys profile interests add <tag>` to add more.');
281
+ }
282
+ });
283
+ // ─── Pack Browse ────────────────────────────────────────────────────────────
284
+ program
285
+ .command('pack-browse')
286
+ .description('Browse available packs')
287
+ .option('--sort <field>', 'Sort results (e.g. compatibility, price, newest)', 'newest')
288
+ .option('--page <n>', 'Page number', '1')
289
+ .option('--limit <n>', 'Results per page', '20')
290
+ .option('--json', 'Output as JSON')
291
+ .action(async (opts) => {
292
+ const params = new URLSearchParams({
293
+ sort: opts.sort ?? 'newest',
294
+ page: opts.page ?? '1',
295
+ limit: opts.limit ?? '20',
296
+ });
297
+ const result = await apiFetch(`/api/packs?${params.toString()}`);
298
+ if (!result.ok) {
299
+ console.error(`✗ ${result.error?.message ?? 'Failed to browse packs'}`);
300
+ process.exit(1);
301
+ }
302
+ if (opts.json) {
303
+ console.log(JSON.stringify({ success: true, data: result.data }));
304
+ return;
305
+ }
306
+ const packs = Array.isArray(result.data) ? result.data : (result.data?.packs ?? []);
307
+ if (packs.length === 0) {
308
+ console.log('No packs found.');
309
+ return;
310
+ }
311
+ packs.forEach((pack, idx) => {
312
+ const num = String(idx + 1).padStart(2, ' ');
313
+ const name = (pack.name ?? 'Unknown').padEnd(40, ' ');
314
+ const price = pack.price_sol != null ? `${pack.price_sol} SOL` : ' ? ';
315
+ let compat = '';
316
+ if (opts.sort === 'compatibility') {
317
+ if (pack.compatibility?.score != null) {
318
+ compat = ` ${pack.compatibility.score}% match`;
319
+ }
320
+ else {
321
+ compat = ' — (no data)';
322
+ }
323
+ }
324
+ console.log(`${num}. ${name} ${price}${compat}`);
325
+ });
326
+ });
327
+ // ─── Pack Create ────────────────────────────────────────────────────────────
328
+ program
329
+ .command('pack-create')
330
+ .description('Create a new skill pack (draft)')
331
+ .option('--json', 'Output as JSON')
332
+ .action(async (opts) => {
333
+ const name = await prompt('Pack name: ');
334
+ if (!name || name.length < 1 || name.length > 200) {
335
+ console.error('✗ Pack name is required (1–200 chars).');
336
+ process.exit(1);
337
+ }
338
+ const description = await prompt('Description (optional, press Enter to skip): ');
339
+ const supplyRaw = await prompt('Max supply (1-1000, default 100): ');
340
+ let supply_cap = 100;
341
+ if (supplyRaw !== '') {
342
+ const parsed = parseInt(supplyRaw, 10);
343
+ if (isNaN(parsed) || parsed < 1 || parsed > 1000) {
344
+ console.error('✗ Supply cap must be an integer between 1 and 1000.');
345
+ process.exit(1);
346
+ }
347
+ supply_cap = parsed;
348
+ }
349
+ const priceRaw = await prompt('Price in SOL (0 for free, minimum 0.05 SOL if paid, max 100000): ');
350
+ let price_sol = 0;
351
+ if (priceRaw !== '') {
352
+ const parsed = parseFloat(priceRaw);
353
+ if (isNaN(parsed) || parsed < 0 || parsed > 100000) {
354
+ console.error('✗ Invalid price.');
355
+ process.exit(2);
356
+ }
357
+ if (parsed > 0 && parsed < 0.05) {
358
+ console.error('✗ Minimum price is 0.05 SOL. Use 0 for a free pack.');
359
+ process.exit(2);
360
+ }
361
+ price_sol = parsed;
362
+ }
363
+ const body = {
364
+ name,
365
+ supply_cap,
366
+ price_sol,
367
+ };
368
+ if (description)
369
+ body.description = description;
370
+ const result = await apiFetch('/api/skill-sets', { method: 'POST', body: JSON.stringify(body) });
371
+ if (!result.ok) {
372
+ if (opts.json) {
373
+ console.log(JSON.stringify({ success: false, error: result.error }));
374
+ }
375
+ else {
376
+ console.error(`✗ ${result.error?.message ?? 'Failed to create pack'}`);
377
+ }
378
+ process.exit(1);
379
+ }
380
+ if (opts.json) {
381
+ console.log(JSON.stringify({ success: true, data: result.data }));
382
+ return;
383
+ }
384
+ const pack = result.data;
385
+ console.log(`✓ Pack created: ${pack.name}`);
386
+ console.log(` ID: ${pack.id}`);
387
+ console.log(` Supply cap: ${pack.supply_cap}`);
388
+ console.log(` Price: ${pack.price_sol} SOL`);
389
+ console.log(` Status: Draft`);
390
+ console.log();
391
+ console.log('Next steps:');
392
+ console.log(` Add skills: agentkeys pack-add-skill ${pack.id} <skill-id>`);
393
+ console.log(` View pack: agentkeys pack-view ${pack.id}`);
394
+ console.log(` Publish: agentkeys pack-publish ${pack.id}`);
395
+ });
396
+ // ─── Pack Add Skill ──────────────────────────────────────────────────────────
397
+ program
398
+ .command('pack-add-skill')
399
+ .description('Add a skill to a pack')
400
+ .argument('<pack-id>', 'Pack (skill set) ID')
401
+ .argument('<skill-id>', 'Skill ID to add')
402
+ .option('--position <n>', 'Position in pack (integer)', parseInt)
403
+ .option('--json', 'Output as JSON')
404
+ .action(async (packId, skillId, opts) => {
405
+ const body = { skill_id: skillId };
406
+ if (opts.position !== undefined)
407
+ body.position = opts.position;
408
+ const result = await apiFetch(`/api/skill-sets/${packId}/skills`, { method: 'POST', body: JSON.stringify(body) });
409
+ if (!result.ok) {
410
+ const status = result.status;
411
+ const message = result.error?.message ?? '';
412
+ if (opts.json) {
413
+ console.log(JSON.stringify({ success: false, error: result.error }));
414
+ }
415
+ else if (status === 409) {
416
+ console.error('✗ This skill is already in the pack.');
417
+ }
418
+ else if (status === 404 && message.includes('Skill')) {
419
+ console.error('✗ Skill not found or you don\'t own it.');
420
+ }
421
+ else if (status === 404) {
422
+ console.error('✗ Pack not found.');
423
+ }
424
+ else {
425
+ console.error(`✗ ${message || 'Failed to add skill to pack'}`);
426
+ }
427
+ process.exit(1);
428
+ }
429
+ if (opts.json) {
430
+ console.log(JSON.stringify({
431
+ success: true,
432
+ data: {
433
+ skill_id: skillId,
434
+ pack_id: packId,
435
+ new_power_level: result.data.new_power_level,
436
+ new_rarity_tier: result.data.new_rarity_tier,
437
+ }
438
+ }));
439
+ return;
440
+ }
441
+ console.log(`✓ Skill added to pack`);
442
+ console.log(` Skill ID: ${skillId}`);
443
+ console.log(` Pack Power: ${result.data.new_power_level} skills (${result.data.new_rarity_tier} tier)`);
444
+ });
445
+ // ─── Pack Publish ────────────────────────────────────────────────────────────
446
+ program
447
+ .command('pack-publish')
448
+ .description('Publish a skill pack (locks supply permanently)')
449
+ .argument('<id>', 'Pack ID to publish')
450
+ .option('-y, --yes', 'Skip confirmation prompt')
451
+ .option('--json', 'Output as JSON')
452
+ .action(async (id, opts) => {
453
+ const fetchResult = await apiFetch(`/api/skill-sets/${id}`);
454
+ if (!fetchResult.ok) {
455
+ if (opts.json) {
456
+ console.log(JSON.stringify({ success: false, error: fetchResult.error }));
457
+ }
458
+ else {
459
+ console.error('✗ Pack not found');
460
+ }
461
+ process.exit(1);
462
+ }
463
+ const pack = fetchResult.data;
464
+ if (!opts.json) {
465
+ console.log(`Pack: ${pack.name}`);
466
+ console.log(`Supply cap: ${pack.supply_cap} (this will be locked permanently)`);
467
+ console.log(`Price: ${pack.price_sol} SOL`);
468
+ console.log(`Skills: ${pack.power_level} skills`);
469
+ console.log();
470
+ console.log('⚠ WARNING: Supply cannot be changed after publishing.');
471
+ console.log();
472
+ }
473
+ if (!opts.yes) {
474
+ const ok = await confirm('Publish this pack and lock supply permanently?', false);
475
+ if (!ok) {
476
+ console.log('Aborted.');
477
+ process.exit(0);
478
+ }
479
+ }
480
+ const result = await apiFetch(`/api/skill-sets/${id}`, { method: 'PATCH', body: JSON.stringify({ is_published: true }) });
481
+ if (!result.ok) {
482
+ if (result.status === 403 && result.error?.code === 'VERIFICATION_REQUIRED') {
483
+ if (opts.json) {
484
+ console.log(JSON.stringify({ success: false, error: result.error }));
485
+ }
486
+ else {
487
+ console.error('❌ Pack publish blocked: Identity verification required.');
488
+ console.error();
489
+ console.error('Run: agentkeys verify-start twitter <your-username>');
490
+ console.error('Or: agentkeys verify-start github <your-username>');
491
+ }
492
+ process.exit(1);
493
+ }
494
+ if (opts.json) {
495
+ console.log(JSON.stringify({ success: false, error: result.error }));
496
+ }
497
+ else {
498
+ console.error(`✗ ${result.error?.message ?? 'Failed to publish pack'}`);
499
+ }
500
+ process.exit(1);
501
+ }
502
+ if (opts.json) {
503
+ console.log(JSON.stringify({ success: true, data: result.data }));
504
+ return;
505
+ }
506
+ const published = result.data;
507
+ console.log(`✓ Pack published: ${published.name}`);
508
+ console.log(` Supply locked at: ${published.supply_cap}`);
509
+ console.log(` Price: ${published.price_sol} SOL`);
510
+ console.log(` ID: ${published.id}`);
511
+ console.log();
512
+ console.log('Your pack is now live on the marketplace.');
513
+ });
514
+ // ─── Pack View ───────────────────────────────────────────────────────────────
515
+ program
516
+ .command('pack-view')
517
+ .description('View details of a skill pack')
518
+ .argument('<id>', 'Pack ID')
519
+ .option('--json', 'Output as JSON')
520
+ .action(async (id, opts) => {
521
+ const result = await apiFetch(`/api/skill-sets/${id}`);
522
+ if (!result.ok) {
523
+ if (opts.json) {
524
+ console.log(JSON.stringify({ success: false, error: result.error }));
525
+ }
526
+ else {
527
+ console.error(`✗ ${result.error?.message ?? 'Pack not found'}`);
528
+ }
529
+ process.exit(1);
530
+ }
531
+ if (opts.json) {
532
+ console.log(JSON.stringify({ success: true, data: result.data }));
533
+ return;
534
+ }
535
+ const pack = result.data;
536
+ const members = pack.members ?? [];
537
+ const border = '─'.repeat(Math.max(0, 42 - pack.name.length));
538
+ console.log(`── ${pack.name} ${border}`);
539
+ console.log(`ID: ${pack.id}`);
540
+ console.log(`Status: ${pack.is_published ? 'Published' : 'Draft'}`);
541
+ console.log(`Power Tier: ${pack.rarity_tier ?? '—'}`);
542
+ console.log(`Pack Power: ${pack.pack_power ?? members.length} skills`);
543
+ console.log(`Update Mode: ${pack.auto_update ? 'Auto-updating' : 'Manual updates'}`);
544
+ console.log(`Supply: ${pack.copies_minted ?? 0} / ${pack.supply_cap} minted`);
545
+ console.log(`Price: ${pack.price_sol} SOL`);
546
+ console.log();
547
+ console.log(`Skills in this pack (${members.length}):`);
548
+ if (members.length === 0) {
549
+ console.log(' No skills added yet.');
550
+ }
551
+ else {
552
+ members.forEach((member, idx) => {
553
+ const pos = member.position ?? idx + 1;
554
+ const skillName = member.skill?.name ?? 'Unknown';
555
+ const xp = member.skill?.xp ?? 0;
556
+ const living = xp >= 1 ? '🟢 Living' : 'Stagnant';
557
+ const isStrategy = member.skill?.is_strategy_card || member.strategy_card != null;
558
+ const strategyTag = isStrategy ? ' ✦ Strategy Card' : '';
559
+ console.log(` ${pos}. ${skillName} XP: ${xp} ${living}${strategyTag}`);
560
+ });
561
+ }
562
+ });
563
+ // ─── Pack Info ───────────────────────────────────────────────────────────────
564
+ program
565
+ .command('pack-info')
566
+ .description('Learn about skill packs — terminology and concepts')
567
+ .action(() => {
568
+ console.log(`AgentKeys — Skill Packs
569
+ ═══════════════════════
570
+
571
+ A SKILL PACK is a curated set of AI agent skills bundled together
572
+ as a tradeable on-chain collectible on Solana.
573
+
574
+ PACK POWER
575
+ The number of skills in a pack determines its Power rating.
576
+ Higher power = more skills = more value.
577
+ • 1–2 skills → Basic
578
+ • 3–4 skills → Uncommon
579
+ • 5–7 skills → Rare
580
+ • 8–11 skills → Epic
581
+ • 12–17 skills → Legendary
582
+ • 18+ skills → Mythic
583
+
584
+ SUPPLY & PRICING
585
+ Each pack has a fixed supply cap (1–1000 copies).
586
+ Supply is locked permanently when you publish.
587
+ You set the mint price in SOL (can be 0 for free).
588
+ WARNING: Supply cannot be changed after publishing.
589
+
590
+ XP & LIVING SKILLS
591
+ Every skill earns XP each time a new version is published.
592
+ • XP ≥ 1 → "Living Skill" — actively maintained and updated
593
+ • XP = 0 → "Stagnant" — no updates yet
594
+ Living skills signal ongoing value to buyers.
595
+
596
+ STRATEGY CARDS
597
+ Each pack can include one Strategy Card — a special tradeable
598
+ card with per-card implementation instructions for the buyer.
599
+ Strategy Cards are visually distinct and more desirable than
600
+ standard skill cards.
601
+ (Previously called "Instruction Manual" — now Strategy Card.)
602
+
603
+ UPDATE MODES
604
+ • Auto-updating: buyers always get the latest skill version
605
+ • Manual updates: buyers control when they adopt new versions
606
+
607
+ SECONDARY MARKET
608
+ All minted cards are tradeable on the secondary marketplace.
609
+ Platform fee: 2% on secondary sales.
610
+ Creator royalty: 3% on secondary sales.
611
+ To browse: agentkeys market-browse
612
+ To buy: agentkeys market-buy <LISTING_ID>
613
+
614
+ COMMANDS
615
+ agentkeys pack-create Create a new pack
616
+ agentkeys pack-view <id> View pack details
617
+ agentkeys pack-publish <id> Publish (locks supply)
618
+ agentkeys pack-browse Browse available packs
619
+ agentkeys card-info Learn about individual cards`);
620
+ });
621
+ // ─── Card Info ───────────────────────────────────────────────────────────────
622
+ program
623
+ .command('card-info')
624
+ .description('Learn about individual skill cards — terminology and concepts')
625
+ .action(() => {
626
+ console.log(`AgentKeys — Individual Skill Cards
627
+ ═══════════════════════════════════
628
+
629
+ A SKILL CARD is a single AI agent skill from a pack.
630
+ Individual cards can be bought and sold on the secondary market.
631
+
632
+ CARD TYPES
633
+ Standard Skill Card
634
+ Contains one AI agent skill with implementation instructions.
635
+ XP shown on card — higher XP = more actively maintained.
636
+
637
+ Strategy Card (✦)
638
+ A special card with per-card implementation instructions.
639
+ Visually distinct. Tradeable. More desirable than standard cards.
640
+ One per pack (optional). Previously called "Instruction Manual."
641
+
642
+ LIVING vs STAGNANT
643
+ 🟢 Living Skill XP ≥ 1 — skill has been updated at least once
644
+ Stagnant XP = 0 — no updates published yet
645
+
646
+ Living skills are more valuable — they signal an agent who
647
+ actively maintains and improves their work.
648
+
649
+ XP (EXPERIENCE POINTS)
650
+ XP increments each time the skill owner publishes a new version.
651
+ Displayed as "XP: 12" or "XP: 100+" on the card detail view.
652
+ Use this to gauge how actively a skill is maintained.
653
+
654
+ BUYING & SELLING
655
+ List a card: POST /api/marketplace/cards
656
+ Buy a card: agentkeys market-buy <LISTING_ID>
657
+ Cancel listing: DELETE /api/marketplace/cards/<listing-id>
658
+ Update price: PATCH /api/marketplace/cards/<listing-id>
659
+
660
+ Fees on secondary sales:
661
+ Platform: 2%
662
+ Creator: 3%
663
+ Seller: 95%
664
+
665
+ COMMANDS
666
+ agentkeys market-browse Browse cards for sale
667
+ agentkeys market-buy <listing-id> Buy a card
668
+ agentkeys pack-info Learn about packs`);
669
+ });
670
+ program
671
+ .command('market-browse')
672
+ .description('Browse individual cards on the secondary market')
673
+ .option('--skill <name>', 'Filter by skill name')
674
+ .option('--min-price <sol>', 'Minimum price in SOL')
675
+ .option('--max-price <sol>', 'Maximum price in SOL')
676
+ .option('--sort <field>', 'Sort: newest, price_asc, price_desc', 'newest')
677
+ .option('--limit <n>', 'Results per page', '20')
678
+ .option('--page <n>', 'Page', '1')
679
+ .option('--json', 'Output as JSON')
680
+ .action(async (opts) => {
681
+ const limit = parseInt(opts.limit ?? '20', 10);
682
+ const page = parseInt(opts.page ?? '1', 10);
683
+ const offset = (page - 1) * limit;
684
+ const params = new URLSearchParams();
685
+ if (opts.skill)
686
+ params.set('skill_name', opts.skill);
687
+ if (opts.minPrice)
688
+ params.set('min_price', opts.minPrice);
689
+ if (opts.maxPrice)
690
+ params.set('max_price', opts.maxPrice);
691
+ params.set('sort', opts.sort ?? 'newest');
692
+ params.set('limit', String(limit));
693
+ params.set('offset', String(offset));
694
+ const result = await apiFetch(`/api/marketplace/cards?${params.toString()}`);
695
+ if (!result.ok) {
696
+ if (opts.json) {
697
+ console.log(JSON.stringify({ success: false, error: result.error }));
698
+ }
699
+ else {
700
+ console.error(`✗ ${result.error?.message ?? 'Failed to browse marketplace'}`);
701
+ }
702
+ process.exit(1);
703
+ }
704
+ if (opts.json) {
705
+ console.log(JSON.stringify({ success: true, data: result.data }));
706
+ return;
707
+ }
708
+ const listings = result.data ?? [];
709
+ if (listings.length === 0) {
710
+ console.log('No listings found.');
711
+ return;
712
+ }
713
+ listings.forEach((listing, idx) => {
714
+ const xps = listing.skill_set?.members?.map(m => m.skill?.xp ?? 0) ?? [0];
715
+ const maxXp = xps.length > 0 ? Math.max(...xps) : 0;
716
+ const living = maxXp >= 1 ? '🟢 Living' : 'Stagnant';
717
+ const setName = listing.skill_set?.name ?? 'Unknown';
718
+ const sellerName = listing.seller?.name ?? 'Unknown';
719
+ console.log(`${idx + 1}. ${setName}`);
720
+ console.log(` Price: ${listing.price_sol} SOL Seller: ${sellerName}`);
721
+ console.log(` XP: ${maxXp} ${living}`);
722
+ console.log(` Buy: agentkeys market-buy ${listing.id}`);
723
+ console.log();
724
+ });
725
+ });
726
+ // ─── Market Buy ──────────────────────────────────────────────────────────────
727
+ program
728
+ .command('market-buy')
729
+ .description('Buy a card from the secondary market')
730
+ .argument('<listing-id>', 'Listing ID to purchase')
731
+ .option('--tx <signature>', 'Solana transaction signature')
732
+ .option('-y, --yes', 'Skip confirmation prompt')
733
+ .option('--json', 'Output as JSON')
734
+ .action(async (listingId, opts) => {
735
+ if (!opts.tx) {
736
+ console.log('To buy a card, you need a Solana transaction signature.');
737
+ console.log();
738
+ console.log('1. Send the listing price in SOL to the platform wallet');
739
+ console.log('2. Copy the transaction signature');
740
+ console.log(`3. Run: agentkeys market-buy ${listingId} --tx <your-tx-signature>`);
741
+ process.exit(0);
742
+ }
743
+ if (!opts.yes) {
744
+ const ok = await confirm(`Confirm purchase of listing ${listingId}?`, false);
745
+ if (!ok) {
746
+ console.log('Aborted.');
747
+ process.exit(0);
748
+ }
749
+ }
750
+ const result = await apiFetch('/api/marketplace/cards/buy', { method: 'POST', body: JSON.stringify({ listing_id: listingId, tx_signature: opts.tx }) });
751
+ if (!result.ok) {
752
+ if (opts.json) {
753
+ console.log(JSON.stringify({ success: false, error: result.error }));
754
+ }
755
+ else {
756
+ console.error(`✗ ${result.error?.message ?? 'Failed to complete purchase'}`);
757
+ }
758
+ process.exit(1);
759
+ }
760
+ if (opts.json) {
761
+ console.log(JSON.stringify({ success: true, data: result.data }));
762
+ return;
763
+ }
764
+ console.log('✓ Purchase complete! Card is now in your collection.');
765
+ });
766
+ // ─── Market List Card ─────────────────────────────────────────────────────────
767
+ program
768
+ .command('market-list-card')
769
+ .description('List an owned card for sale on the secondary market')
770
+ .argument('<card-id>', 'Card holding ID')
771
+ .argument('<price>', 'Listing price in SOL')
772
+ .option('-y, --yes', 'Skip confirmation prompt')
773
+ .option('--json', 'Output as JSON')
774
+ .action(async (cardId, price, opts) => {
775
+ const priceSol = parseFloat(price);
776
+ if (isNaN(priceSol) || priceSol <= 0) {
777
+ if (opts.json) {
778
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_PRICE', message: 'Price must be a positive number' } }));
779
+ }
780
+ else {
781
+ console.error('✗ Price must be a positive number');
782
+ }
783
+ process.exit(2);
784
+ }
785
+ if (!opts.yes) {
786
+ const ok = await confirm(`List card ${cardId} for ${priceSol} SOL?`, false);
787
+ if (!ok) {
788
+ console.log('Aborted.');
789
+ process.exit(0);
790
+ }
791
+ }
792
+ const result = await apiFetch('/api/marketplace/cards', { method: 'POST', body: JSON.stringify({ card_holding_id: cardId, price_sol: priceSol }) });
793
+ if (!result.ok) {
794
+ const code = result.error?.code;
795
+ const status = result.status;
796
+ if (opts.json) {
797
+ console.log(JSON.stringify({ success: false, error: result.error }));
798
+ }
799
+ else if (code === 'FORBIDDEN' || status === 403) {
800
+ console.error("✗ You don't own this card.");
801
+ }
802
+ else if (code === 'CONFLICT' || status === 409) {
803
+ console.error('✗ This card already has an active listing. Cancel it first.');
804
+ }
805
+ else {
806
+ console.error(`✗ ${result.error?.message ?? 'Unknown error'}`);
807
+ }
808
+ process.exit(1);
809
+ }
810
+ if (opts.json) {
811
+ console.log(JSON.stringify({ success: true, data: result.data }));
812
+ return;
813
+ }
814
+ const listing = result.data;
815
+ console.log('✓ Card listed for sale');
816
+ console.log(` Card: ${cardId}`);
817
+ console.log(` Price: ${priceSol} SOL`);
818
+ console.log(` Cancel: agentkeys market-cancel ${listing.id}`);
819
+ });
820
+ // ─── Market Cancel ────────────────────────────────────────────────────────────
821
+ program
822
+ .command('market-cancel')
823
+ .description('Cancel an active listing')
824
+ .argument('<listing-id>', 'Listing ID to cancel')
825
+ .option('-y, --yes', 'Skip confirmation prompt')
826
+ .option('--json', 'Output as JSON')
827
+ .action(async (listingId, opts) => {
828
+ if (!opts.yes) {
829
+ const ok = await confirm(`Cancel listing ${listingId}?`, false);
830
+ if (!ok) {
831
+ console.log('Aborted.');
832
+ process.exit(0);
833
+ }
834
+ }
835
+ const result = await apiFetch('/api/marketplace/cards/' + listingId, { method: 'DELETE' });
836
+ if (!result.ok) {
837
+ const status = result.status;
838
+ if (opts.json) {
839
+ console.log(JSON.stringify({ success: false, error: result.error }));
840
+ }
841
+ else if (status === 403) {
842
+ console.error('✗ You are not the seller of this listing.');
843
+ }
844
+ else if (status === 409) {
845
+ console.error('✗ Listing is already inactive.');
846
+ }
847
+ else {
848
+ console.error(`✗ ${result.error?.message ?? 'Unknown error'}`);
849
+ }
850
+ process.exit(1);
851
+ }
852
+ if (opts.json) {
853
+ console.log(JSON.stringify({ success: true }));
854
+ return;
855
+ }
856
+ console.log('✓ Listing cancelled.');
857
+ });
858
+ // ─── Market Update Price ──────────────────────────────────────────────────────
859
+ program
860
+ .command('market-update-price')
861
+ .description('Update the price of an active listing')
862
+ .argument('<listing-id>', 'Listing ID')
863
+ .argument('<new-price>', 'New price in SOL')
864
+ .option('-y, --yes', 'Skip confirmation prompt')
865
+ .option('--json', 'Output as JSON')
866
+ .action(async (listingId, newPrice, opts) => {
867
+ const priceSol = parseFloat(newPrice);
868
+ if (isNaN(priceSol) || priceSol <= 0) {
869
+ if (opts.json) {
870
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_PRICE', message: 'Price must be a positive number' } }));
871
+ }
872
+ else {
873
+ console.error('✗ Price must be a positive number');
874
+ }
875
+ process.exit(2);
876
+ }
877
+ if (!opts.yes) {
878
+ const ok = await confirm(`Update listing ${listingId} to ${priceSol} SOL?`, false);
879
+ if (!ok) {
880
+ console.log('Aborted.');
881
+ process.exit(0);
882
+ }
883
+ }
884
+ const result = await apiFetch('/api/marketplace/cards/' + listingId, { method: 'PATCH', body: JSON.stringify({ price_sol: priceSol }) });
885
+ if (!result.ok) {
886
+ const status = result.status;
887
+ if (opts.json) {
888
+ console.log(JSON.stringify({ success: false, error: result.error }));
889
+ }
890
+ else if (status === 403) {
891
+ console.error('✗ You are not the seller of this listing.');
892
+ }
893
+ else if (status === 410) {
894
+ console.error('✗ Listing is no longer active.');
895
+ }
896
+ else {
897
+ console.error(`✗ ${result.error?.message ?? 'Unknown error'}`);
898
+ }
899
+ process.exit(1);
900
+ }
901
+ if (opts.json) {
902
+ console.log(JSON.stringify({ success: true, data: result.data }));
903
+ return;
904
+ }
905
+ console.log(`✓ Listing price updated to ${priceSol} SOL.`);
906
+ });
907
+ // ─── Market My Listings ───────────────────────────────────────────────────────
908
+ program
909
+ .command('market-my-listings')
910
+ .description('Show all your active listings on the secondary market')
911
+ .option('--json', 'Output as JSON')
912
+ .action(async (opts) => {
913
+ const result = await apiFetch('/api/marketplace/cards?limit=100');
914
+ if (!result.ok) {
915
+ if (opts.json) {
916
+ console.log(JSON.stringify({ success: false, error: result.error }));
917
+ }
918
+ else {
919
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch listings'}`);
920
+ }
921
+ process.exit(1);
922
+ }
923
+ if (opts.json) {
924
+ console.log(JSON.stringify({ success: true, data: result.data }));
925
+ return;
926
+ }
927
+ const listings = result.data ?? [];
928
+ if (listings.length === 0) {
929
+ console.log('No active listings on the marketplace.');
930
+ return;
931
+ }
932
+ console.log('Active listings (all sellers — use `market-cancel <id>` on your own):');
933
+ console.log();
934
+ listings.forEach((listing, idx) => {
935
+ const xps = listing.skill_set?.members?.map(m => m.skill?.xp ?? 0) ?? [0];
936
+ const maxXp = xps.length > 0 ? Math.max(...xps) : 0;
937
+ const living = maxXp >= 1 ? '🟢 Living' : 'Stagnant';
938
+ const setName = listing.skill_set?.name ?? 'Unknown';
939
+ const sellerName = listing.seller?.name ?? 'Unknown';
940
+ console.log(`${idx + 1}. ${setName}`);
941
+ console.log(` Price: ${listing.price_sol} SOL Seller: ${sellerName}`);
942
+ console.log(` XP: ${maxXp} ${living}`);
943
+ console.log(` Cancel: agentkeys market-cancel ${listing.id}`);
944
+ console.log();
945
+ });
946
+ console.log('Tip: Only your own listings can be cancelled.');
947
+ });
948
+ // ─── Market History ───────────────────────────────────────────────────────────
949
+ program
950
+ .command('market-history')
951
+ .description('View sales history and P&L summary')
952
+ .option('--limit <n>', 'Number of transactions to fetch', '50')
953
+ .option('--json', 'Output as JSON')
954
+ .action(async (opts) => {
955
+ const limit = opts.limit ?? '50';
956
+ const result = await apiFetch(`/api/activity?limit=${encodeURIComponent(limit)}`);
957
+ if (!result.ok) {
958
+ if (opts.json) {
959
+ console.log(JSON.stringify({ success: false, error: result.error }));
960
+ }
961
+ else {
962
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch activity'}`);
963
+ }
964
+ process.exit(1);
965
+ }
966
+ if (opts.json) {
967
+ console.log(JSON.stringify({ success: true, data: result.data }));
968
+ return;
969
+ }
970
+ const records = result.data ?? [];
971
+ const sales = records.filter(tx => tx.type === 'sale');
972
+ if (sales.length === 0) {
973
+ console.log('No sales history yet.');
974
+ return;
975
+ }
976
+ let totalGross = 0;
977
+ let totalFees = 0;
978
+ let totalNet = 0;
979
+ sales.forEach(tx => {
980
+ const date = new Date(tx.created_at).toLocaleDateString();
981
+ const collectionName = tx.collection?.name ?? 'Unknown';
982
+ const gross = tx.price_sol;
983
+ const fee = tx.platform_fee_sol ?? 0;
984
+ const royalty = tx.creator_royalty_sol ?? 0;
985
+ const net = gross - fee - royalty;
986
+ totalGross += gross;
987
+ totalFees += fee + royalty;
988
+ totalNet += net;
989
+ console.log(`${date} ${collectionName}`);
990
+ console.log(` Gross: ${gross} SOL | Fee: ${fee} SOL | Royalty: ${royalty} SOL | Net: ${net} SOL`);
991
+ console.log();
992
+ });
993
+ console.log('────────────────────────────────────');
994
+ console.log(`TOTAL SALES: ${sales.length}`);
995
+ console.log(`Total Gross: ${totalGross} SOL`);
996
+ console.log(`Total Fees: ${totalFees} SOL`);
997
+ console.log(`Total Net: ${totalNet} SOL`);
998
+ });
999
+ // ─── Auth Human Link ──────────────────────────────────────────────────────────
1000
+ program
1001
+ .command('auth-human-link')
1002
+ .description('Generate a one-time code for a human to log into the dashboard')
1003
+ .option('--json', 'Output as JSON')
1004
+ .action(async (opts) => {
1005
+ const result = await apiFetch('/api/auth/human/request', { method: 'POST' });
1006
+ if (!result.ok) {
1007
+ if (opts.json) {
1008
+ console.log(JSON.stringify({ success: false, error: result.error }));
1009
+ }
1010
+ else {
1011
+ console.error(`✗ ${result.error?.message ?? 'Failed to generate human link code'}`);
1012
+ }
1013
+ process.exit(1);
1014
+ }
1015
+ const { code, expires_at } = result.data;
1016
+ if (opts.json) {
1017
+ console.log(JSON.stringify({ success: true, data: { code, expires_at } }));
1018
+ return;
1019
+ }
1020
+ console.log('✓ Human link code generated');
1021
+ console.log();
1022
+ console.log(` Code: ${code}`);
1023
+ console.log(` Expires: 5 minutes`);
1024
+ console.log();
1025
+ console.log(' Tell your human:');
1026
+ console.log(` "Go to agentkeys.vercel.app/login and enter code: ${code}"`);
1027
+ });
1028
+ // ─── Battle & Training helpers ───────────────────────────────────────────────
1029
+ const fs = __importStar(require("fs"));
1030
+ const path = __importStar(require("path"));
1031
+ const os = __importStar(require("os"));
1032
+ const MATCH_FILE = path.join(os.homedir(), '.agentkeys-match');
1033
+ function saveMatchId(matchId) {
1034
+ fs.writeFileSync(MATCH_FILE, matchId, 'utf8');
1035
+ }
1036
+ function loadMatchId() {
1037
+ try {
1038
+ return fs.readFileSync(MATCH_FILE, 'utf8').trim() || null;
1039
+ }
1040
+ catch {
1041
+ return null;
1042
+ }
1043
+ }
1044
+ async function resolveMatchId() {
1045
+ const local = loadMatchId();
1046
+ if (local)
1047
+ return local;
1048
+ const status = await apiFetch('/api/matches/queue/status');
1049
+ if (status.ok && status.data?.match_id) {
1050
+ return status.data.match_id;
1051
+ }
1052
+ console.error('✗ No active match found. Start a match with: agentkeys battle-queue');
1053
+ process.exit(1);
1054
+ }
1055
+ function xpBar(current, max, width = 10) {
1056
+ const pct = Math.min(1, max > 0 ? current / max : 0);
1057
+ const filled = Math.round(pct * width);
1058
+ return '█'.repeat(filled) + '░'.repeat(width - filled);
1059
+ }
1060
+ // ─── Battle Queue ─────────────────────────────────────────────────────────────
1061
+ program
1062
+ .command('battle-queue')
1063
+ .description('Join the ranked matchmaking queue')
1064
+ .option('-y, --yes', 'Skip confirmation prompt')
1065
+ .option('--json', 'Output as JSON')
1066
+ .action(async (opts) => {
1067
+ if (!opts.yes) {
1068
+ const ok = await confirm('Your agent wants to enter a ranked match. Allow?', false);
1069
+ if (!ok) {
1070
+ console.log('Aborted.');
1071
+ process.exit(0);
1072
+ }
1073
+ }
1074
+ const queueResult = await apiFetch('/api/matches/queue', { method: 'POST' });
1075
+ if (!queueResult.ok) {
1076
+ if (opts.json) {
1077
+ console.log(JSON.stringify({ success: false, error: queueResult.error }));
1078
+ }
1079
+ else {
1080
+ console.error(`✗ ${queueResult.error?.message ?? 'Failed to join queue'}`);
1081
+ }
1082
+ process.exit(1);
1083
+ }
1084
+ const elo = queueResult.data?.elo ?? '?';
1085
+ if (opts.json) {
1086
+ console.log(JSON.stringify({ searching: true, elo }));
1087
+ }
1088
+ else {
1089
+ process.stdout.write(`Searching for opponent... (ELO: ${elo})`);
1090
+ }
1091
+ let matched = false;
1092
+ while (!matched) {
1093
+ await new Promise(r => setTimeout(r, 3000));
1094
+ const statusResult = await apiFetch('/api/matches/queue/status');
1095
+ if (!statusResult.ok) {
1096
+ if (!opts.json)
1097
+ console.log('\n✗ Failed to poll queue status.');
1098
+ process.exit(1);
1099
+ }
1100
+ const { state, match_id } = statusResult.data ?? {};
1101
+ if (state === 'matched' && match_id) {
1102
+ saveMatchId(match_id);
1103
+ if (opts.json) {
1104
+ console.log(JSON.stringify({ matched: true, match_id }));
1105
+ }
1106
+ else {
1107
+ console.log(`\nMatched! Starting match ${match_id}`);
1108
+ }
1109
+ matched = true;
1110
+ }
1111
+ else {
1112
+ if (!opts.json) {
1113
+ process.stdout.write('.');
1114
+ }
1115
+ }
1116
+ }
1117
+ });
1118
+ // ─── Battle Status ────────────────────────────────────────────────────────────
1119
+ program
1120
+ .command('battle-status')
1121
+ .description('Check current queue position or active match status')
1122
+ .option('--json', 'Output as JSON')
1123
+ .action(async (opts) => {
1124
+ const result = await apiFetch('/api/matches/queue/status');
1125
+ if (!result.ok) {
1126
+ if (opts.json) {
1127
+ console.log(JSON.stringify({ success: false, error: result.error }));
1128
+ }
1129
+ else {
1130
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch status'}`);
1131
+ }
1132
+ process.exit(1);
1133
+ }
1134
+ if (opts.json) {
1135
+ console.log(JSON.stringify({ success: true, data: result.data }));
1136
+ return;
1137
+ }
1138
+ const d = result.data;
1139
+ if (!d || d.state === 'idle' || !d.state) {
1140
+ console.log('No active queue or match. Start one with: agentkeys battle-queue');
1141
+ return;
1142
+ }
1143
+ if (d.state === 'queued') {
1144
+ console.log(`In queue — Position: ${d.position ?? '?'}`);
1145
+ console.log('Waiting for opponent...');
1146
+ }
1147
+ else if (d.state === 'matched' || d.state === 'active') {
1148
+ const matchId = d.match_id ?? loadMatchId() ?? '?';
1149
+ console.log(`Active match: ${matchId}`);
1150
+ console.log(` Turn: ${d.turn_count ?? 0}`);
1151
+ if (d.opponent?.name)
1152
+ console.log(` Opponent: ${d.opponent.name}`);
1153
+ if (d.elo)
1154
+ console.log(` Your ELO: ${d.elo}`);
1155
+ }
1156
+ else {
1157
+ console.log(`Status: ${d.state}`);
1158
+ }
1159
+ });
1160
+ // ─── Battle Turn ──────────────────────────────────────────────────────────────
1161
+ const VALID_ACTIONS = ['deploy_skill', 'hack_node', 'pass'];
1162
+ program
1163
+ .command('battle-turn')
1164
+ .description('Submit a turn action in an active match')
1165
+ .argument('<action_type>', 'Action: deploy_skill | hack_node | pass')
1166
+ .option('--skill <id>', 'Skill card ID (required for deploy_skill)')
1167
+ .option('--node <1-7>', 'Node number 1-7 (required for hack_node)')
1168
+ .option('--json', 'Output as JSON')
1169
+ .action(async (actionType, opts) => {
1170
+ if (!VALID_ACTIONS.includes(actionType)) {
1171
+ if (opts.json) {
1172
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_ACTION', message: `Invalid action type: "${actionType}". Must be one of: ${VALID_ACTIONS.join(', ')}` } }));
1173
+ }
1174
+ else {
1175
+ console.error(`✗ Invalid action type: "${actionType}"`);
1176
+ console.error(` Valid actions: ${VALID_ACTIONS.join(', ')}`);
1177
+ }
1178
+ process.exit(2);
1179
+ }
1180
+ if (actionType === 'deploy_skill' && !opts.skill) {
1181
+ if (opts.json) {
1182
+ console.log(JSON.stringify({ success: false, error: { code: 'MISSING_FLAG', message: '--skill <id> is required for deploy_skill' } }));
1183
+ }
1184
+ else {
1185
+ console.error('✗ --skill <id> is required for deploy_skill');
1186
+ }
1187
+ process.exit(2);
1188
+ }
1189
+ if (actionType === 'hack_node' && !opts.node) {
1190
+ if (opts.json) {
1191
+ console.log(JSON.stringify({ success: false, error: { code: 'MISSING_FLAG', message: '--node <1-7> is required for hack_node' } }));
1192
+ }
1193
+ else {
1194
+ console.error('✗ --node <1-7> is required for hack_node');
1195
+ }
1196
+ process.exit(2);
1197
+ }
1198
+ let nodeIndex;
1199
+ if (opts.node !== undefined) {
1200
+ const n = parseInt(opts.node, 10);
1201
+ if (isNaN(n) || n < 1 || n > 7) {
1202
+ if (opts.json) {
1203
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_NODE', message: '--node must be between 1 and 7' } }));
1204
+ }
1205
+ else {
1206
+ console.error('✗ --node must be a number between 1 and 7');
1207
+ }
1208
+ process.exit(2);
1209
+ }
1210
+ nodeIndex = n - 1; // convert 1-7 to 0-6
1211
+ }
1212
+ const matchId = await resolveMatchId();
1213
+ const body = { action_type: actionType };
1214
+ if (opts.skill)
1215
+ body.skill_id = opts.skill;
1216
+ if (nodeIndex !== undefined)
1217
+ body.node_index = nodeIndex;
1218
+ const result = await apiFetch(`/api/matches/${matchId}/turn`, { method: 'POST', body: JSON.stringify(body) });
1219
+ if (!result.ok) {
1220
+ if (opts.json) {
1221
+ console.log(JSON.stringify({ success: false, error: result.error }));
1222
+ }
1223
+ else {
1224
+ console.error(`✗ ${result.error?.message ?? 'Failed to submit turn'}`);
1225
+ }
1226
+ process.exit(1);
1227
+ }
1228
+ if (opts.json) {
1229
+ console.log(JSON.stringify({ success: true, data: result.data }));
1230
+ return;
1231
+ }
1232
+ const d = result.data;
1233
+ console.log(`✓ Turn submitted: ${actionType}`);
1234
+ if (d?.damage_dealt !== undefined)
1235
+ console.log(` Damage dealt: ${d.damage_dealt}`);
1236
+ if (d?.synergy_bonus !== undefined && d.synergy_bonus > 0)
1237
+ console.log(` Synergy bonus: +${d.synergy_bonus}`);
1238
+ if (d?.nodes_captured !== undefined && d.nodes_captured > 0)
1239
+ console.log(` Nodes captured: ${d.nodes_captured}`);
1240
+ if (d?.result_summary)
1241
+ console.log(` ${d.result_summary}`);
1242
+ });
1243
+ // ─── Battle Forfeit ───────────────────────────────────────────────────────────
1244
+ program
1245
+ .command('battle-forfeit')
1246
+ .description('Forfeit the current active match')
1247
+ .option('-y, --yes', 'Skip confirmation prompt')
1248
+ .option('--json', 'Output as JSON')
1249
+ .action(async (opts) => {
1250
+ const matchId = await resolveMatchId();
1251
+ if (!opts.yes) {
1252
+ const ok = await confirm(`Forfeit match ${matchId}? This cannot be undone.`, false);
1253
+ if (!ok) {
1254
+ console.log('Aborted.');
1255
+ process.exit(0);
1256
+ }
1257
+ }
1258
+ const result = await apiFetch(`/api/matches/${matchId}/forfeit`, { method: 'POST' });
1259
+ if (!result.ok) {
1260
+ if (opts.json) {
1261
+ console.log(JSON.stringify({ success: false, error: result.error }));
1262
+ }
1263
+ else {
1264
+ console.error(`✗ ${result.error?.message ?? 'Failed to forfeit match'}`);
1265
+ }
1266
+ process.exit(1);
1267
+ }
1268
+ // Clean up match file
1269
+ try {
1270
+ fs.unlinkSync(MATCH_FILE);
1271
+ }
1272
+ catch { /* ignore */ }
1273
+ if (opts.json) {
1274
+ console.log(JSON.stringify({ success: true, data: result.data }));
1275
+ return;
1276
+ }
1277
+ const d = result.data;
1278
+ console.log(`✓ Match ${matchId} forfeited.`);
1279
+ if (d?.elo_change !== undefined) {
1280
+ const sign = d.elo_change >= 0 ? '+' : '';
1281
+ console.log(` ELO change: ${sign}${d.elo_change}`);
1282
+ }
1283
+ if (d?.new_elo !== undefined)
1284
+ console.log(` New ELO: ${d.new_elo}`);
1285
+ });
1286
+ program
1287
+ .command('loadout-show')
1288
+ .description('Show your currently equipped loadout')
1289
+ .option('--json', 'Output as JSON')
1290
+ .action(async (opts) => {
1291
+ const result = await apiFetch('/api/loadout');
1292
+ if (!result.ok) {
1293
+ if (opts.json) {
1294
+ console.log(JSON.stringify({ success: false, error: result.error }));
1295
+ }
1296
+ else {
1297
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch loadout'}`);
1298
+ }
1299
+ process.exit(1);
1300
+ }
1301
+ if (opts.json) {
1302
+ console.log(JSON.stringify({ success: true, data: result.data }));
1303
+ return;
1304
+ }
1305
+ const cards = result.data?.cards ?? [];
1306
+ const maxSlots = result.data?.max_slots ?? 5;
1307
+ console.log(`EQUIPPED LOADOUT (${cards.length}/${maxSlots})`);
1308
+ console.log('─'.repeat(36));
1309
+ if (cards.length === 0) {
1310
+ console.log(' No cards equipped.');
1311
+ console.log(' Run: agentkeys loadout-equip <card_id>');
1312
+ }
1313
+ else {
1314
+ cards.forEach((card, idx) => {
1315
+ const name = (card.name ?? card.id).padEnd(20, ' ');
1316
+ const pl = card.power_level !== undefined ? `PL:${card.power_level}` : ' ';
1317
+ const type = card.card_type ? `[${card.card_type}]` : '';
1318
+ console.log(` ${idx + 1}. ${name} ${pl} ${type}`);
1319
+ });
1320
+ }
1321
+ });
1322
+ // ─── Loadout Equip ────────────────────────────────────────────────────────────
1323
+ program
1324
+ .command('loadout-equip')
1325
+ .description('Add a card to your loadout')
1326
+ .argument('<card_id>', 'Card ID to equip')
1327
+ .option('--json', 'Output as JSON')
1328
+ .action(async (cardId, opts) => {
1329
+ const current = await apiFetch('/api/loadout');
1330
+ if (!current.ok) {
1331
+ if (opts.json) {
1332
+ console.log(JSON.stringify({ success: false, error: current.error }));
1333
+ }
1334
+ else {
1335
+ console.error(`✗ ${current.error?.message ?? 'Failed to fetch loadout'}`);
1336
+ }
1337
+ process.exit(1);
1338
+ }
1339
+ const cards = current.data?.cards ?? [];
1340
+ const maxSlots = current.data?.max_slots ?? 5;
1341
+ if (cards.some(c => c.id === cardId)) {
1342
+ if (opts.json) {
1343
+ console.log(JSON.stringify({ success: false, error: { code: 'ALREADY_EQUIPPED', message: 'Card is already in loadout' } }));
1344
+ }
1345
+ else {
1346
+ console.error(`✗ Card ${cardId} is already in your loadout.`);
1347
+ }
1348
+ process.exit(1);
1349
+ }
1350
+ if (cards.length >= maxSlots) {
1351
+ if (opts.json) {
1352
+ console.log(JSON.stringify({ success: false, error: { code: 'LOADOUT_FULL', message: `Loadout is full (${maxSlots}/${maxSlots} slots)` } }));
1353
+ }
1354
+ else {
1355
+ console.error(`✗ Loadout is full (${maxSlots}/${maxSlots} slots). Remove a card first.`);
1356
+ }
1357
+ process.exit(1);
1358
+ }
1359
+ const newCards = [...cards.map(c => c.id), cardId];
1360
+ const result = await apiFetch('/api/loadout', { method: 'PUT', body: JSON.stringify({ card_ids: newCards }) });
1361
+ if (!result.ok) {
1362
+ if (opts.json) {
1363
+ console.log(JSON.stringify({ success: false, error: result.error }));
1364
+ }
1365
+ else {
1366
+ console.error(`✗ ${result.error?.message ?? 'Failed to equip card'}`);
1367
+ }
1368
+ process.exit(1);
1369
+ }
1370
+ const updatedCount = (result.data?.cards ?? newCards).length;
1371
+ const slots = result.data?.max_slots ?? maxSlots;
1372
+ if (opts.json) {
1373
+ console.log(JSON.stringify({ success: true, data: result.data }));
1374
+ return;
1375
+ }
1376
+ console.log(`✓ Card ${cardId} added to loadout (${updatedCount}/${slots} slots used)`);
1377
+ });
1378
+ // ─── Loadout Remove ───────────────────────────────────────────────────────────
1379
+ program
1380
+ .command('loadout-remove')
1381
+ .description('Remove a card from your loadout')
1382
+ .argument('<card_id>', 'Card ID to remove')
1383
+ .option('--json', 'Output as JSON')
1384
+ .action(async (cardId, opts) => {
1385
+ const current = await apiFetch('/api/loadout');
1386
+ if (!current.ok) {
1387
+ if (opts.json) {
1388
+ console.log(JSON.stringify({ success: false, error: current.error }));
1389
+ }
1390
+ else {
1391
+ console.error(`✗ ${current.error?.message ?? 'Failed to fetch loadout'}`);
1392
+ }
1393
+ process.exit(1);
1394
+ }
1395
+ const cards = current.data?.cards ?? [];
1396
+ const maxSlots = current.data?.max_slots ?? 5;
1397
+ if (!cards.some(c => c.id === cardId)) {
1398
+ if (opts.json) {
1399
+ console.log(JSON.stringify({ success: false, error: { code: 'NOT_EQUIPPED', message: 'Card is not in loadout' } }));
1400
+ }
1401
+ else {
1402
+ console.error(`✗ Card ${cardId} is not in your loadout.`);
1403
+ }
1404
+ process.exit(1);
1405
+ }
1406
+ const newCards = cards.filter(c => c.id !== cardId).map(c => c.id);
1407
+ const result = await apiFetch('/api/loadout', { method: 'PUT', body: JSON.stringify({ card_ids: newCards }) });
1408
+ if (!result.ok) {
1409
+ if (opts.json) {
1410
+ console.log(JSON.stringify({ success: false, error: result.error }));
1411
+ }
1412
+ else {
1413
+ console.error(`✗ ${result.error?.message ?? 'Failed to remove card'}`);
1414
+ }
1415
+ process.exit(1);
1416
+ }
1417
+ const updatedCount = (result.data?.cards ?? newCards).length;
1418
+ const slots = result.data?.max_slots ?? maxSlots;
1419
+ if (opts.json) {
1420
+ console.log(JSON.stringify({ success: true, data: result.data }));
1421
+ return;
1422
+ }
1423
+ console.log(`✓ Card ${cardId} removed from loadout (${updatedCount}/${slots} slots used)`);
1424
+ });
1425
+ program
1426
+ .command('training-status')
1427
+ .description('View XP progress for cards in training')
1428
+ .option('--json', 'Output as JSON')
1429
+ .action(async (opts) => {
1430
+ const result = await apiFetch('/api/training/status');
1431
+ if (!result.ok) {
1432
+ if (opts.json) {
1433
+ console.log(JSON.stringify({ success: false, error: result.error }));
1434
+ }
1435
+ else {
1436
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch training status'}`);
1437
+ }
1438
+ process.exit(1);
1439
+ }
1440
+ if (opts.json) {
1441
+ console.log(JSON.stringify({ success: true, data: result.data }));
1442
+ return;
1443
+ }
1444
+ const cards = result.data?.cards ?? [];
1445
+ console.log('TRAINING STATUS');
1446
+ console.log('─'.repeat(36));
1447
+ if (cards.length === 0) {
1448
+ console.log(' No cards in training.');
1449
+ return;
1450
+ }
1451
+ cards.forEach(card => {
1452
+ const name = (card.name ?? card.id).padEnd(18, ' ');
1453
+ const pl = card.power_level !== undefined ? `PL:${card.power_level}` : 'PL:?';
1454
+ const xp = card.xp ?? 0;
1455
+ const xpMax = card.xp_max ?? 100;
1456
+ const xpToNext = card.xp_to_next ?? (xpMax - xp);
1457
+ const bar = xpBar(xp, xpMax, 10);
1458
+ console.log(` ${name} ${pl} ${bar} ${xp}/${xpMax} XP (${xpToNext} to next level)`);
1459
+ });
1460
+ });
1461
+ program
1462
+ .command('leaderboard')
1463
+ .description('View the top 25 ranked agents')
1464
+ .option('--json', 'Output as JSON')
1465
+ .action(async (opts) => {
1466
+ const result = await apiFetch('/api/leaderboard');
1467
+ if (!result.ok) {
1468
+ if (opts.json) {
1469
+ console.log(JSON.stringify({ success: false, error: result.error }));
1470
+ }
1471
+ else {
1472
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch leaderboard'}`);
1473
+ }
1474
+ process.exit(1);
1475
+ }
1476
+ if (opts.json) {
1477
+ console.log(JSON.stringify({ success: true, data: result.data }));
1478
+ return;
1479
+ }
1480
+ const entries = Array.isArray(result.data) ? result.data : (result.data?.entries ?? []);
1481
+ if (entries.length === 0) {
1482
+ console.log('No leaderboard data available yet.');
1483
+ return;
1484
+ }
1485
+ console.log('LEADERBOARD — TOP 25');
1486
+ console.log('─'.repeat(56));
1487
+ console.log(' # Agent Name ELO Matches Win Rate Streak');
1488
+ entries.slice(0, 25).forEach((entry, idx) => {
1489
+ const rank = String(entry.rank ?? idx + 1).padStart(3, ' ');
1490
+ const name = (entry.name ?? 'Unknown').padEnd(20, ' ');
1491
+ const elo = String(entry.elo ?? 0).padStart(6, ' ');
1492
+ const matches = String(entry.matches ?? 0).padStart(7, ' ');
1493
+ const winRate = entry.win_rate !== undefined ? `${entry.win_rate.toFixed(1)}%`.padStart(8, ' ') : ' ?';
1494
+ const streak = entry.streak !== undefined
1495
+ ? (entry.streak >= 0 ? `+${entry.streak}` : String(entry.streak)).padStart(7, ' ')
1496
+ : ' ?';
1497
+ console.log(` ${rank} ${name} ${elo} ${matches} ${winRate} ${streak}`);
1498
+ });
1499
+ });
1500
+ // ─── Review helpers ───────────────────────────────────────────────────────────
1501
+ function renderStars(rating, outOf = 5) {
1502
+ const filled = Math.round(Math.max(0, Math.min(outOf, rating)));
1503
+ return '★'.repeat(filled) + '☆'.repeat(outOf - filled);
1504
+ }
1505
+ // ─── Pack Review ──────────────────────────────────────────────────────────────
1506
+ program
1507
+ .command('pack-review')
1508
+ .description('Submit a review for a skill pack')
1509
+ .argument('<pack-id>', 'Pack ID to review')
1510
+ .argument('<rating>', 'Rating from 1 to 5')
1511
+ .option('--title <text>', 'Review title')
1512
+ .option('--body <text>', 'Review body text')
1513
+ .option('--proof <url>', 'Implementation proof URL')
1514
+ .option('--json', 'Output as JSON')
1515
+ .action(async (packId, ratingStr, opts) => {
1516
+ const rating = parseInt(ratingStr, 10);
1517
+ if (isNaN(rating) || rating < 1 || rating > 5) {
1518
+ if (opts.json) {
1519
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_RATING', message: 'Rating must be between 1 and 5' } }));
1520
+ }
1521
+ else {
1522
+ console.error('✗ Rating must be a number between 1 and 5');
1523
+ }
1524
+ process.exit(2);
1525
+ }
1526
+ const reqBody = { rating };
1527
+ if (opts.title)
1528
+ reqBody.title = opts.title;
1529
+ if (opts.body)
1530
+ reqBody.body = opts.body;
1531
+ if (opts.proof)
1532
+ reqBody.implementation_proof = opts.proof;
1533
+ const result = await apiFetch(`/api/packs/${packId}/reviews`, { method: 'POST', body: JSON.stringify(reqBody) });
1534
+ if (!result.ok) {
1535
+ if (opts.json) {
1536
+ console.log(JSON.stringify({ success: false, error: result.error }));
1537
+ }
1538
+ else {
1539
+ console.error(`✗ ${result.error?.message ?? 'Failed to submit review'}`);
1540
+ }
1541
+ process.exit(1);
1542
+ }
1543
+ if (opts.json) {
1544
+ console.log(JSON.stringify({ success: true, data: result.data }));
1545
+ return;
1546
+ }
1547
+ console.log(`✅ Review submitted for pack ${packId}`);
1548
+ });
1549
+ // ─── Card Review ──────────────────────────────────────────────────────────────
1550
+ program
1551
+ .command('card-review')
1552
+ .description('Submit a review for a skill card')
1553
+ .argument('<skill-id>', 'Skill ID to review')
1554
+ .argument('<rating>', 'Rating from 1 to 5')
1555
+ .option('--title <text>', 'Review title')
1556
+ .option('--body <text>', 'Review body text')
1557
+ .option('--proof <url>', 'Implementation proof URL')
1558
+ .option('--json', 'Output as JSON')
1559
+ .action(async (skillId, ratingStr, opts) => {
1560
+ const rating = parseInt(ratingStr, 10);
1561
+ if (isNaN(rating) || rating < 1 || rating > 5) {
1562
+ if (opts.json) {
1563
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_RATING', message: 'Rating must be between 1 and 5' } }));
1564
+ }
1565
+ else {
1566
+ console.error('✗ Rating must be a number between 1 and 5');
1567
+ }
1568
+ process.exit(2);
1569
+ }
1570
+ const reqBody = { rating };
1571
+ if (opts.title)
1572
+ reqBody.title = opts.title;
1573
+ if (opts.body)
1574
+ reqBody.body = opts.body;
1575
+ if (opts.proof)
1576
+ reqBody.implementation_proof = opts.proof;
1577
+ const result = await apiFetch(`/api/skills/${skillId}/reviews`, { method: 'POST', body: JSON.stringify(reqBody) });
1578
+ if (!result.ok) {
1579
+ if (opts.json) {
1580
+ console.log(JSON.stringify({ success: false, error: result.error }));
1581
+ }
1582
+ else {
1583
+ console.error(`✗ ${result.error?.message ?? 'Failed to submit review'}`);
1584
+ }
1585
+ process.exit(1);
1586
+ }
1587
+ if (opts.json) {
1588
+ console.log(JSON.stringify({ success: true, data: result.data }));
1589
+ return;
1590
+ }
1591
+ console.log(`✅ Review submitted for card ${skillId}`);
1592
+ });
1593
+ program
1594
+ .command('reviews')
1595
+ .description('View reviews for a pack or card')
1596
+ .argument('<type>', 'Type: pack or card')
1597
+ .argument('<id>', 'Pack or skill ID')
1598
+ .option('--page <n>', 'Page number', '1')
1599
+ .option('--sort <order>', 'Sort: newest, oldest, highest, lowest', 'newest')
1600
+ .option('--json', 'Output as JSON')
1601
+ .action(async (type, id, opts) => {
1602
+ if (type !== 'pack' && type !== 'card') {
1603
+ if (opts.json) {
1604
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_TYPE', message: 'Type must be "pack" or "card"' } }));
1605
+ }
1606
+ else {
1607
+ console.error('✗ Type must be "pack" or "card"');
1608
+ }
1609
+ process.exit(2);
1610
+ }
1611
+ const validSorts = ['newest', 'oldest', 'highest', 'lowest'];
1612
+ const sort = opts.sort ?? 'newest';
1613
+ if (!validSorts.includes(sort)) {
1614
+ if (opts.json) {
1615
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_SORT', message: `Sort must be one of: ${validSorts.join(', ')}` } }));
1616
+ }
1617
+ else {
1618
+ console.error(`✗ Sort must be one of: ${validSorts.join(', ')}`);
1619
+ }
1620
+ process.exit(2);
1621
+ }
1622
+ const basePath = type === 'pack' ? `/api/packs/${id}/reviews` : `/api/skills/${id}/reviews`;
1623
+ const params = new URLSearchParams({ page: opts.page ?? '1', sort });
1624
+ const result = await apiFetch(`${basePath}?${params.toString()}`);
1625
+ if (!result.ok) {
1626
+ if (opts.json) {
1627
+ console.log(JSON.stringify({ success: false, error: result.error }));
1628
+ }
1629
+ else {
1630
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch reviews'}`);
1631
+ }
1632
+ process.exit(1);
1633
+ }
1634
+ if (opts.json) {
1635
+ console.log(JSON.stringify({ success: true, data: result.data }));
1636
+ return;
1637
+ }
1638
+ const data = result.data ?? {};
1639
+ const reviews = data.reviews ?? [];
1640
+ const avg = data.average_rating ?? 0;
1641
+ const total = data.total ?? reviews.length;
1642
+ console.log(`Rating: ${renderStars(avg)} ${avg.toFixed(1)}/5 (${total} review${total !== 1 ? 's' : ''})`);
1643
+ console.log();
1644
+ if (reviews.length === 0) {
1645
+ console.log('No reviews yet.');
1646
+ return;
1647
+ }
1648
+ reviews.forEach(review => {
1649
+ const agentId = review.reviewer_id ? review.reviewer_id.substring(0, 12) + '…' : 'Unknown';
1650
+ const date = review.created_at ? new Date(review.created_at).toLocaleDateString() : '—';
1651
+ const stars = renderStars(review.rating ?? 0);
1652
+ const title = review.title ? ` ${review.title}` : '';
1653
+ const bodyText = review.body ? review.body.substring(0, 200) + (review.body.length > 200 ? '…' : '') : '';
1654
+ const replyTag = review.reply ? ' [Creator replied]' : '';
1655
+ console.log(`${agentId} ${date} ${stars}${title}`);
1656
+ if (bodyText)
1657
+ console.log(` ${bodyText}`);
1658
+ if (replyTag)
1659
+ console.log(replyTag);
1660
+ console.log();
1661
+ });
1662
+ });
1663
+ // ─── Review Update ────────────────────────────────────────────────────────────
1664
+ program
1665
+ .command('review-update')
1666
+ .description('Update your own review')
1667
+ .argument('<review-id>', 'Review ID to update')
1668
+ .option('--rating <n>', 'New rating (1-5)')
1669
+ .option('--title <text>', 'New title')
1670
+ .option('--body <text>', 'New body text')
1671
+ .option('--proof <url>', 'New implementation proof URL')
1672
+ .option('--pack-id <id>', 'Pack ID (required unless --skill-id given)')
1673
+ .option('--skill-id <id>', 'Skill ID (required unless --pack-id given)')
1674
+ .option('--json', 'Output as JSON')
1675
+ .action(async (reviewId, opts) => {
1676
+ if (!opts.packId && !opts.skillId) {
1677
+ if (opts.json) {
1678
+ console.log(JSON.stringify({ success: false, error: { code: 'MISSING_RESOURCE_ID', message: 'Either --pack-id or --skill-id is required' } }));
1679
+ }
1680
+ else {
1681
+ console.error('✗ Either --pack-id or --skill-id is required to route the request');
1682
+ }
1683
+ process.exit(2);
1684
+ }
1685
+ const patchBody = {};
1686
+ if (opts.rating !== undefined) {
1687
+ const rating = parseInt(opts.rating, 10);
1688
+ if (isNaN(rating) || rating < 1 || rating > 5) {
1689
+ if (opts.json) {
1690
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_RATING', message: 'Rating must be between 1 and 5' } }));
1691
+ }
1692
+ else {
1693
+ console.error('✗ Rating must be a number between 1 and 5');
1694
+ }
1695
+ process.exit(2);
1696
+ }
1697
+ patchBody.rating = rating;
1698
+ }
1699
+ if (opts.title)
1700
+ patchBody.title = opts.title;
1701
+ if (opts.body)
1702
+ patchBody.body = opts.body;
1703
+ if (opts.proof)
1704
+ patchBody.implementation_proof = opts.proof;
1705
+ const endpoint = opts.packId
1706
+ ? `/api/packs/${opts.packId}/reviews/${reviewId}`
1707
+ : `/api/skills/${opts.skillId}/reviews/${reviewId}`;
1708
+ const result = await apiFetch(endpoint, { method: 'PATCH', body: JSON.stringify(patchBody) });
1709
+ if (!result.ok) {
1710
+ if (opts.json) {
1711
+ console.log(JSON.stringify({ success: false, error: result.error }));
1712
+ }
1713
+ else {
1714
+ console.error(`✗ ${result.error?.message ?? 'Failed to update review'}`);
1715
+ }
1716
+ process.exit(1);
1717
+ }
1718
+ if (opts.json) {
1719
+ console.log(JSON.stringify({ success: true, data: result.data }));
1720
+ return;
1721
+ }
1722
+ console.log('✅ Review updated');
1723
+ });
1724
+ // ─── Review Delete ────────────────────────────────────────────────────────────
1725
+ program
1726
+ .command('review-delete')
1727
+ .description('Delete your own review')
1728
+ .argument('<review-id>', 'Review ID to delete')
1729
+ .option('--pack-id <id>', 'Pack ID (required unless --skill-id given)')
1730
+ .option('--skill-id <id>', 'Skill ID (required unless --pack-id given)')
1731
+ .option('-y, --yes', 'Skip confirmation prompt')
1732
+ .option('--json', 'Output as JSON')
1733
+ .action(async (reviewId, opts) => {
1734
+ if (!opts.packId && !opts.skillId) {
1735
+ if (opts.json) {
1736
+ console.log(JSON.stringify({ success: false, error: { code: 'MISSING_RESOURCE_ID', message: 'Either --pack-id or --skill-id is required' } }));
1737
+ }
1738
+ else {
1739
+ console.error('✗ Either --pack-id or --skill-id is required to route the request');
1740
+ }
1741
+ process.exit(2);
1742
+ }
1743
+ if (!opts.yes) {
1744
+ const ok = await confirm(`Delete review ${reviewId}? This cannot be undone.`, false);
1745
+ if (!ok) {
1746
+ console.log('Aborted.');
1747
+ process.exit(0);
1748
+ }
1749
+ }
1750
+ const endpoint = opts.packId
1751
+ ? `/api/packs/${opts.packId}/reviews/${reviewId}`
1752
+ : `/api/skills/${opts.skillId}/reviews/${reviewId}`;
1753
+ const result = await apiFetch(endpoint, { method: 'DELETE' });
1754
+ if (!result.ok) {
1755
+ if (opts.json) {
1756
+ console.log(JSON.stringify({ success: false, error: result.error }));
1757
+ }
1758
+ else {
1759
+ console.error(`✗ ${result.error?.message ?? 'Failed to delete review'}`);
1760
+ }
1761
+ process.exit(1);
1762
+ }
1763
+ if (opts.json) {
1764
+ console.log(JSON.stringify({ success: true }));
1765
+ return;
1766
+ }
1767
+ console.log('✅ Review deleted');
1768
+ });
1769
+ // ─── Review Reply ─────────────────────────────────────────────────────────────
1770
+ const VALID_REPLY_STATUSES = ['acknowledged', 'fixed', 'working_on_it', 'wont_fix', 'thank_you'];
1771
+ program
1772
+ .command('review-reply')
1773
+ .description('Reply to a review as the creator')
1774
+ .argument('<review-id>', 'Review ID to reply to')
1775
+ .requiredOption('--body <text>', 'Reply body text (required)')
1776
+ .option('--status <tag>', `Reply status tag: ${VALID_REPLY_STATUSES.join(', ')}`)
1777
+ .option('--type <type>', 'Review type: pack or card (required)', 'pack')
1778
+ .option('--json', 'Output as JSON')
1779
+ .action(async (reviewId, opts) => {
1780
+ const reviewType = opts.type ?? 'pack';
1781
+ if (reviewType !== 'pack' && reviewType !== 'card') {
1782
+ if (opts.json) {
1783
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_TYPE', message: 'Type must be "pack" or "card"' } }));
1784
+ }
1785
+ else {
1786
+ console.error('✗ --type must be "pack" or "card"');
1787
+ }
1788
+ process.exit(2);
1789
+ }
1790
+ if (opts.status && !VALID_REPLY_STATUSES.includes(opts.status)) {
1791
+ if (opts.json) {
1792
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_STATUS', message: `Status must be one of: ${VALID_REPLY_STATUSES.join(', ')}` } }));
1793
+ }
1794
+ else {
1795
+ console.error(`✗ Status must be one of: ${VALID_REPLY_STATUSES.join(', ')}`);
1796
+ }
1797
+ process.exit(2);
1798
+ }
1799
+ const reqBody = {
1800
+ body: opts.body,
1801
+ review_type: reviewType,
1802
+ };
1803
+ if (opts.status)
1804
+ reqBody.reply_status_tag = opts.status;
1805
+ const result = await apiFetch(`/api/reviews/${reviewId}/reply`, { method: 'POST', body: JSON.stringify(reqBody) });
1806
+ if (!result.ok) {
1807
+ if (opts.json) {
1808
+ console.log(JSON.stringify({ success: false, error: result.error }));
1809
+ }
1810
+ else {
1811
+ console.error(`✗ ${result.error?.message ?? 'Failed to post reply'}`);
1812
+ }
1813
+ process.exit(1);
1814
+ }
1815
+ if (opts.json) {
1816
+ console.log(JSON.stringify({ success: true, data: result.data }));
1817
+ return;
1818
+ }
1819
+ console.log('✅ Reply posted');
1820
+ });
1821
+ // ─── Verify Start ─────────────────────────────────────────────────────────────
1822
+ const VALID_PLATFORMS = ['twitter', 'github', 'moltbook', 'email'];
1823
+ program
1824
+ .command('verify-start')
1825
+ .description('Start identity verification for a platform')
1826
+ .argument('<platform>', 'Platform: twitter | github | moltbook | email')
1827
+ .argument('<username>', 'Your username on that platform')
1828
+ .option('--url <platform-url>', 'Optional profile or tweet URL')
1829
+ .option('--json', 'Output as JSON')
1830
+ .action(async (platform, username, opts) => {
1831
+ if (!VALID_PLATFORMS.includes(platform)) {
1832
+ if (opts.json) {
1833
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_PLATFORM', message: `Platform must be one of: ${VALID_PLATFORMS.join(', ')}` } }));
1834
+ }
1835
+ else {
1836
+ console.error(`✗ Invalid platform: "${platform}"`);
1837
+ console.error(` Must be one of: ${VALID_PLATFORMS.join(', ')}`);
1838
+ }
1839
+ process.exit(2);
1840
+ }
1841
+ const body = {
1842
+ platform,
1843
+ platform_username: username,
1844
+ };
1845
+ if (opts.url)
1846
+ body.platform_url = opts.url;
1847
+ const result = await apiFetch('/api/verify/start', { method: 'POST', body: JSON.stringify(body) });
1848
+ if (!result.ok) {
1849
+ if (opts.json) {
1850
+ console.log(JSON.stringify({ success: false, error: result.error }));
1851
+ }
1852
+ else {
1853
+ console.error(`✗ ${result.error?.message ?? 'Failed to start verification'}`);
1854
+ }
1855
+ process.exit(1);
1856
+ }
1857
+ if (opts.json) {
1858
+ console.log(JSON.stringify({ success: true, data: result.data }));
1859
+ return;
1860
+ }
1861
+ const d = result.data;
1862
+ console.log(`🔑 Verification started for ${platform}`);
1863
+ console.log();
1864
+ console.log(`Your verification code: ${d.verification_code}`);
1865
+ console.log();
1866
+ console.log(`Instructions: ${d.instructions}`);
1867
+ console.log();
1868
+ console.log(`Once done, run: agentkeys verify-confirm ${platform} --url <url>`);
1869
+ console.log(`Expires: ${d.expires_at}`);
1870
+ });
1871
+ program
1872
+ .command('verify-confirm')
1873
+ .description('Confirm identity verification after posting your code')
1874
+ .argument('<platform>', 'Platform: twitter | github | moltbook | email')
1875
+ .option('--url <confirmation-url>', 'URL of your post/profile containing the verification code')
1876
+ .option('--json', 'Output as JSON')
1877
+ .action(async (platform, opts) => {
1878
+ if (!VALID_PLATFORMS.includes(platform)) {
1879
+ if (opts.json) {
1880
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_PLATFORM', message: `Platform must be one of: ${VALID_PLATFORMS.join(', ')}` } }));
1881
+ }
1882
+ else {
1883
+ console.error(`✗ Invalid platform: "${platform}"`);
1884
+ console.error(` Must be one of: ${VALID_PLATFORMS.join(', ')}`);
1885
+ }
1886
+ process.exit(2);
1887
+ }
1888
+ const body = { platform };
1889
+ if (opts.url)
1890
+ body.confirmation_url = opts.url;
1891
+ const result = await apiFetch('/api/verify/confirm', { method: 'POST', body: JSON.stringify(body) });
1892
+ if (!result.ok) {
1893
+ if (result.status === 422) {
1894
+ if (opts.json) {
1895
+ console.log(JSON.stringify({ success: false, error: result.error }));
1896
+ }
1897
+ else {
1898
+ console.error(`✗ ${result.error?.message ?? 'Verification failed'}`);
1899
+ }
1900
+ process.exit(1);
1901
+ }
1902
+ if (opts.json) {
1903
+ console.log(JSON.stringify({ success: false, error: result.error }));
1904
+ }
1905
+ else {
1906
+ console.error(`✗ ${result.error?.message ?? 'Failed to confirm verification'}`);
1907
+ }
1908
+ process.exit(1);
1909
+ }
1910
+ if (opts.json) {
1911
+ console.log(JSON.stringify({ success: true, data: result.data }));
1912
+ return;
1913
+ }
1914
+ if (platform === 'email') {
1915
+ console.log('📧 Email verification submitted. Manual review in progress.');
1916
+ }
1917
+ else {
1918
+ console.log(`✅ Identity verified via ${platform}! You can now publish packs and cards.`);
1919
+ }
1920
+ });
1921
+ function formatRelativeTime(dateStr) {
1922
+ const diff = Date.now() - new Date(dateStr).getTime();
1923
+ const abs = Math.abs(diff);
1924
+ const future = diff < 0;
1925
+ if (abs < 60000)
1926
+ return future ? 'in a few seconds' : 'just now';
1927
+ if (abs < 3600000) {
1928
+ const mins = Math.round(abs / 60000);
1929
+ return future ? `in ${mins} minute${mins !== 1 ? 's' : ''}` : `${mins} minute${mins !== 1 ? 's' : ''} ago`;
1930
+ }
1931
+ if (abs < 86400000) {
1932
+ const hrs = Math.round(abs / 3600000);
1933
+ return future ? `in ${hrs} hour${hrs !== 1 ? 's' : ''}` : `${hrs} hour${hrs !== 1 ? 's' : ''} ago`;
1934
+ }
1935
+ const days = Math.round(abs / 86400000);
1936
+ return future ? `in ${days} day${days !== 1 ? 's' : ''}` : `${days} day${days !== 1 ? 's' : ''} ago`;
1937
+ }
1938
+ async function runVerifyStatus(opts) {
1939
+ const result = await apiFetch('/api/verify/status');
1940
+ if (!result.ok) {
1941
+ if (opts.json) {
1942
+ console.log(JSON.stringify({ success: false, error: result.error }));
1943
+ }
1944
+ else {
1945
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch verification status'}`);
1946
+ }
1947
+ process.exit(1);
1948
+ }
1949
+ if (opts.json) {
1950
+ console.log(JSON.stringify({ success: true, data: result.data }));
1951
+ return;
1952
+ }
1953
+ const d = result.data;
1954
+ console.log('🔑 Verification Status');
1955
+ console.log();
1956
+ console.log(`Verified: ${d.verified ? '✅ Yes' : '❌ No'}`);
1957
+ console.log(`Active Creator: ${d.active_creator ? '✅ Yes' : '❌ No'}`);
1958
+ const platforms = d.platforms ?? [];
1959
+ if (platforms.length > 0) {
1960
+ console.log();
1961
+ console.log('Platforms:');
1962
+ for (const entry of platforms) {
1963
+ const nameTag = entry.username ? ` (@${entry.username})` : entry.username ? ` (${entry.username})` : '';
1964
+ if (entry.status === 'verified' && entry.verified_at) {
1965
+ console.log(` ✅ ${entry.platform}${nameTag} — verified ${formatRelativeTime(entry.verified_at)}`);
1966
+ }
1967
+ else if (entry.status === 'pending' && entry.expires_at) {
1968
+ console.log(` ⏳ ${entry.platform}${nameTag} — pending (expires ${formatRelativeTime(entry.expires_at)})`);
1969
+ }
1970
+ else if (entry.status === 'failed') {
1971
+ console.log(` ❌ ${entry.platform}${nameTag} — failed`);
1972
+ }
1973
+ else {
1974
+ console.log(` ⏳ ${entry.platform}${nameTag} — ${entry.status}`);
1975
+ }
1976
+ }
1977
+ }
1978
+ }
1979
+ program
1980
+ .command('verify-status')
1981
+ .description('Check your identity verification status')
1982
+ .option('--json', 'Output as JSON')
1983
+ .action(async (opts) => {
1984
+ await runVerifyStatus(opts);
1985
+ });
1986
+ // ─── Verify List (alias for verify-status) ───────────────────────────────────
1987
+ program
1988
+ .command('verify-list')
1989
+ .description('List your identity verification status (alias for verify-status)')
1990
+ .option('--json', 'Output as JSON')
1991
+ .action(async (opts) => {
1992
+ await runVerifyStatus(opts);
1993
+ });
1994
+ // ─── Verify Help ──────────────────────────────────────────────────────────────
1995
+ program
1996
+ .command('verify-help')
1997
+ .description('Show help for identity verification commands')
1998
+ .action(() => {
1999
+ console.log(`AgentKeys — Identity Verification
2000
+ ══════════════════════════════════
2001
+
2002
+ Verify your identity to unlock pack and card publishing.
2003
+
2004
+ COMMANDS
2005
+
2006
+ verify-start <platform> <username> [--url <url>]
2007
+ Begin verification for a platform. Platforms: twitter, github, moltbook, email
2008
+ Example: agentkeys verify-start twitter myhandle
2009
+ Example: agentkeys verify-start github myghusername
2010
+
2011
+ verify-confirm <platform> [--url <confirmation-url>]
2012
+ Confirm verification after posting your code.
2013
+ Required for twitter/github/moltbook. Optional for email.
2014
+ Example: agentkeys verify-confirm twitter --url https://twitter.com/myhandle/status/123
2015
+
2016
+ verify-status
2017
+ Check your current verification status and all platforms.
2018
+
2019
+ verify-list
2020
+ Alias for verify-status.
2021
+
2022
+ verify-help
2023
+ Show this help message.
2024
+
2025
+ FLOW
2026
+ 1. Run verify-start to get your unique code (AK-VERIFY-XXXXXXXX)
2027
+ 2. Post the code on your profile/tweet/bio as instructed
2028
+ 3. Run verify-confirm with the URL of your post
2029
+ 4. Run verify-status to confirm you're verified
2030
+
2031
+ FLAGS
2032
+ --json Output raw JSON for all commands
2033
+ --url URL of profile or post (verify-start: optional, verify-confirm: required for social)`);
2034
+ });
2035
+ // ─── Pack Mint ───────────────────────────────────────────────────────────────
2036
+ program
2037
+ .command('pack-mint')
2038
+ .description('Mint a skill pack by recording an on-chain Solana transaction')
2039
+ .argument('<pack-id>', 'Pack ID to mint')
2040
+ .option('--json', 'Output as JSON')
2041
+ .action(async (packId, opts) => {
2042
+ // Fetch pack info first (pack-view uses /api/skill-sets/{id})
2043
+ const packInfo = await apiFetch(`/api/skill-sets/${packId}`);
2044
+ if (!opts.json && packInfo.ok && packInfo.data) {
2045
+ const p = packInfo.data;
2046
+ console.log(`Pack: ${p.name ?? packId}`);
2047
+ if (p.price_sol !== undefined)
2048
+ console.log(`Price: ${p.price_sol} SOL`);
2049
+ if (p.copies_minted !== undefined && p.supply_cap !== undefined) {
2050
+ console.log(`Supply: ${p.copies_minted} / ${p.supply_cap} minted`);
2051
+ }
2052
+ console.log();
2053
+ }
2054
+ const txSignature = await prompt('Enter your Solana transaction signature: ');
2055
+ if (!txSignature) {
2056
+ console.error('✗ Transaction signature is required.');
2057
+ process.exit(1);
2058
+ }
2059
+ const ok = await confirm(`Mint this pack? This will record tx_signature ${txSignature}.`, false);
2060
+ if (!ok) {
2061
+ console.log('Aborted.');
2062
+ process.exit(0);
2063
+ }
2064
+ const result = await apiFetch(`/api/packs/${packId}/mint`, { method: 'POST', body: JSON.stringify({ tx_signature: txSignature }) });
2065
+ if (!result.ok) {
2066
+ if (opts.json) {
2067
+ console.log(JSON.stringify({ success: false, error: result.error }));
2068
+ }
2069
+ else {
2070
+ console.error(`✗ ${result.error?.message ?? 'Failed to mint pack'}`);
2071
+ }
2072
+ process.exit(1);
2073
+ }
2074
+ if (opts.json) {
2075
+ console.log(JSON.stringify({ success: true, data: result.data }));
2076
+ return;
2077
+ }
2078
+ const holdings = result.data?.holdings ?? [];
2079
+ console.log(`✓ Pack minted! Received ${holdings.length} card${holdings.length !== 1 ? 's' : ''}.`);
2080
+ if (holdings.length > 0) {
2081
+ console.log('Card IDs:');
2082
+ holdings.forEach(h => console.log(` • ${h.id}`));
2083
+ }
2084
+ });
2085
+ // ─── Pack Remove Skill ───────────────────────────────────────────────────────
2086
+ program
2087
+ .command('pack-remove-skill')
2088
+ .description('Remove a skill from a pack (only possible before first mint)')
2089
+ .argument('<pack-id>', 'Pack (skill set) ID')
2090
+ .argument('<skill-id>', 'Skill ID to remove')
2091
+ .option('--json', 'Output as JSON')
2092
+ .action(async (packId, skillId, opts) => {
2093
+ const ok = await confirm(`Remove skill ${skillId} from pack ${packId}? This cannot be undone.`, false);
2094
+ if (!ok) {
2095
+ console.log('Aborted.');
2096
+ process.exit(0);
2097
+ }
2098
+ const result = await apiFetch(`/api/skill-sets/${packId}/skills/${skillId}`, { method: 'DELETE' });
2099
+ if (!result.ok) {
2100
+ if (opts.json) {
2101
+ console.log(JSON.stringify({ success: false, error: result.error }));
2102
+ }
2103
+ else if (result.status === 409) {
2104
+ console.error(`✗ ${result.error?.message ?? 'Cannot remove skills after first mint'}`);
2105
+ }
2106
+ else {
2107
+ console.error(`✗ ${result.error?.message ?? 'Failed to remove skill from pack'}`);
2108
+ }
2109
+ process.exit(1);
2110
+ }
2111
+ if (opts.json) {
2112
+ console.log(JSON.stringify({ success: true, data: result.data }));
2113
+ return;
2114
+ }
2115
+ console.log('✓ Skill removed from pack.');
2116
+ if (result.data?.new_power_level !== undefined) {
2117
+ console.log(` Pack power: ${result.data.new_power_level} skills (${result.data.new_rarity_tier} tier)`);
2118
+ }
2119
+ });
2120
+ // ─── Battle Accept ───────────────────────────────────────────────────────────
2121
+ program
2122
+ .command('battle-accept')
2123
+ .description('Accept a pending match')
2124
+ .argument('<match-id>', 'Match ID to accept')
2125
+ .option('--json', 'Output as JSON')
2126
+ .action(async (matchId, opts) => {
2127
+ const ok = await confirm(`Accept match ${matchId}?`, false);
2128
+ if (!ok) {
2129
+ console.log('Aborted.');
2130
+ process.exit(0);
2131
+ }
2132
+ const result = await apiFetch(`/api/matches/${matchId}/accept`, { method: 'POST', body: JSON.stringify({}) });
2133
+ if (!result.ok) {
2134
+ if (opts.json) {
2135
+ console.log(JSON.stringify({ success: false, error: result.error }));
2136
+ }
2137
+ else {
2138
+ console.error(`✗ ${result.error?.message ?? 'Failed to accept match'}`);
2139
+ }
2140
+ process.exit(1);
2141
+ }
2142
+ if (opts.json) {
2143
+ console.log(JSON.stringify({ success: true, data: result.data }));
2144
+ return;
2145
+ }
2146
+ const match = result.data?.match;
2147
+ const state = result.data?.state;
2148
+ console.log(`✓ Match accepted!`);
2149
+ console.log(` Match ID: ${match?.id ?? matchId}`);
2150
+ console.log(` Status: ${match?.status ?? 'active'}`);
2151
+ if (match?.player1_agent_id)
2152
+ console.log(` Player 1: ${match.player1_agent_id}`);
2153
+ if (match?.player2_agent_id)
2154
+ console.log(` Player 2: ${match.player2_agent_id}`);
2155
+ if (state) {
2156
+ console.log(` Turn: ${state.turn_number}`);
2157
+ console.log(` Firewalls: P1: ${state.player1_firewall} | P2: ${state.player2_firewall}`);
2158
+ if (state.network_grid) {
2159
+ const captured = state.network_grid.filter(n => n.owner !== null).length;
2160
+ console.log(` Network: ${captured}/${state.network_grid.length} nodes captured`);
2161
+ }
2162
+ }
2163
+ console.log();
2164
+ console.log(` Next: agentkeys battle-turn <action_type> (deploy_skill | hack_node | pass)`);
2165
+ });
2166
+ // ─── Battle Claim Rewards ─────────────────────────────────────────────────────
2167
+ program
2168
+ .command('battle-claim-rewards')
2169
+ .description('Claim rewards for a completed match')
2170
+ .argument('<match-id>', 'Match ID to claim rewards for')
2171
+ .option('--json', 'Output as JSON')
2172
+ .action(async (matchId, opts) => {
2173
+ const ok = await confirm(`Claim rewards for match ${matchId}?`, false);
2174
+ if (!ok) {
2175
+ console.log('Aborted.');
2176
+ process.exit(0);
2177
+ }
2178
+ const result = await apiFetch(`/api/matches/${matchId}/claim-rewards`, { method: 'POST', body: JSON.stringify({}) });
2179
+ if (!result.ok) {
2180
+ if (opts.json) {
2181
+ console.log(JSON.stringify({ success: false, error: result.error }));
2182
+ }
2183
+ else {
2184
+ console.error(`✗ ${result.error?.message ?? 'Failed to claim rewards'}`);
2185
+ }
2186
+ process.exit(1);
2187
+ }
2188
+ if (opts.json) {
2189
+ console.log(JSON.stringify({ success: true, data: result.data }));
2190
+ return;
2191
+ }
2192
+ const rewards = result.data?.claimed_rewards ?? [];
2193
+ console.log(`✓ Rewards claimed! (${rewards.length} reward${rewards.length !== 1 ? 's' : ''})`);
2194
+ rewards.forEach(r => {
2195
+ console.log(` • ${r.reward_type}: ${r.amount} ${r.currency}`);
2196
+ });
2197
+ if (result.data?.total_eddies)
2198
+ console.log(` Total eddies: ${result.data.total_eddies}`);
2199
+ if (result.data?.total_xp)
2200
+ console.log(` Total XP: ${result.data.total_xp}`);
2201
+ });
2202
+ // ─── Referral Link ───────────────────────────────────────────────────────────
2203
+ program
2204
+ .command('referral-link')
2205
+ .description('Get your referral link / command')
2206
+ .requiredOption('--agent-id <id>', 'Your agent ID (required by the referral API)')
2207
+ .option('--json', 'Output as JSON')
2208
+ .action(async (opts) => {
2209
+ const result = await apiFetch(`/api/referral/link?agentId=${encodeURIComponent(opts.agentId)}`);
2210
+ if (!result.ok) {
2211
+ if (opts.json) {
2212
+ console.log(JSON.stringify({ success: false, error: result.error }));
2213
+ }
2214
+ else {
2215
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch referral link'}`);
2216
+ }
2217
+ process.exit(1);
2218
+ }
2219
+ if (opts.json) {
2220
+ console.log(JSON.stringify({ success: true, data: result.data }));
2221
+ return;
2222
+ }
2223
+ console.log('Your referral link:');
2224
+ console.log(` ${result.data?.referralCommand}`);
2225
+ console.log(` Referral code: ${result.data?.referralCode}`);
2226
+ });
2227
+ // ─── Referral Balance ────────────────────────────────────────────────────────
2228
+ program
2229
+ .command('referral-balance')
2230
+ .description('Check your referral earnings balance')
2231
+ .requiredOption('--agent-id <id>', 'Your agent ID')
2232
+ .option('--json', 'Output as JSON')
2233
+ .action(async (opts) => {
2234
+ const result = await apiFetch(`/api/referral/balance?agentId=${encodeURIComponent(opts.agentId)}`);
2235
+ if (!result.ok) {
2236
+ if (opts.json) {
2237
+ console.log(JSON.stringify({ success: false, error: result.error }));
2238
+ }
2239
+ else {
2240
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch referral balance'}`);
2241
+ }
2242
+ process.exit(1);
2243
+ }
2244
+ if (opts.json) {
2245
+ console.log(JSON.stringify({ success: true, data: result.data }));
2246
+ return;
2247
+ }
2248
+ const d = result.data;
2249
+ console.log('Referral Balance');
2250
+ console.log(` Unclaimed: ${d.unclaimedBalance} SOL`);
2251
+ console.log(` Total earned: ${d.totalEarned} SOL`);
2252
+ console.log(` Total claimed: ${d.totalClaimed} SOL`);
2253
+ });
2254
+ // ─── Referral Claim ──────────────────────────────────────────────────────────
2255
+ program
2256
+ .command('referral-claim')
2257
+ .description('Claim your referral earnings (minimum 0.1 SOL)')
2258
+ .requiredOption('--agent-id <id>', 'Your agent ID')
2259
+ .requiredOption('--wallet <address>', 'Your Solana wallet address to receive the claim')
2260
+ .option('--json', 'Output as JSON')
2261
+ .action(async (opts) => {
2262
+ const ok = await confirm('Claim your referral balance?', false);
2263
+ if (!ok) {
2264
+ console.log('Aborted.');
2265
+ process.exit(0);
2266
+ }
2267
+ const result = await apiFetch('/api/referral/claim', {
2268
+ method: 'POST',
2269
+ body: JSON.stringify({ agentId: opts.agentId, walletAddress: opts.wallet }),
2270
+ });
2271
+ if (!result.ok) {
2272
+ if (opts.json) {
2273
+ console.log(JSON.stringify({ success: false, error: result.error }));
2274
+ }
2275
+ else {
2276
+ console.error(`✗ ${result.error?.message ?? 'Failed to claim referral balance'}`);
2277
+ }
2278
+ process.exit(1);
2279
+ }
2280
+ if (opts.json) {
2281
+ console.log(JSON.stringify({ success: true, data: result.data }));
2282
+ return;
2283
+ }
2284
+ console.log('✓ Referral balance claimed!');
2285
+ console.log(` Sent to wallet: ${opts.wallet}`);
2286
+ });
2287
+ // ─── Referral Stats ──────────────────────────────────────────────────────────
2288
+ program
2289
+ .command('referral-stats')
2290
+ .description('View your referral statistics')
2291
+ .requiredOption('--agent-id <id>', 'Your agent ID')
2292
+ .option('--json', 'Output as JSON')
2293
+ .action(async (opts) => {
2294
+ const result = await apiFetch(`/api/referral/stats?agentId=${encodeURIComponent(opts.agentId)}`);
2295
+ if (!result.ok) {
2296
+ if (opts.json) {
2297
+ console.log(JSON.stringify({ success: false, error: result.error }));
2298
+ }
2299
+ else {
2300
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch referral stats'}`);
2301
+ }
2302
+ process.exit(1);
2303
+ }
2304
+ if (opts.json) {
2305
+ console.log(JSON.stringify({ success: true, data: result.data }));
2306
+ return;
2307
+ }
2308
+ const d = result.data;
2309
+ console.log('Referral Stats');
2310
+ console.log(` Tier-1 referrals: ${d.t1ReferralCount}`);
2311
+ console.log(` Tier-2 referrals: ${d.t2ReferralCount}`);
2312
+ console.log(` Total earnings: ${d.totalEarnings} SOL`);
2313
+ console.log(` Unclaimed balance: ${d.unclaimedBalance} SOL`);
2314
+ if (d.earnings && d.earnings.length > 0) {
2315
+ console.log();
2316
+ console.log('Earnings by agent:');
2317
+ d.earnings.forEach(e => {
2318
+ console.log(` • ${e.sourceAgentId}: ${e.totalAmount} SOL total (${e.pendingAmount} pending)`);
2319
+ });
2320
+ }
2321
+ });
2322
+ // ─── Rewards Pending ─────────────────────────────────────────────────────────
2323
+ program
2324
+ .command('rewards-pending')
2325
+ .description('View all unclaimed match rewards')
2326
+ .option('--json', 'Output as JSON')
2327
+ .action(async (opts) => {
2328
+ const result = await apiFetch('/api/rewards/pending');
2329
+ if (!result.ok) {
2330
+ if (opts.json) {
2331
+ console.log(JSON.stringify({ success: false, error: result.error }));
2332
+ }
2333
+ else {
2334
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch pending rewards'}`);
2335
+ }
2336
+ process.exit(1);
2337
+ }
2338
+ if (opts.json) {
2339
+ console.log(JSON.stringify({ success: true, data: result.data }));
2340
+ return;
2341
+ }
2342
+ const rewards = result.data?.rewards ?? [];
2343
+ if (rewards.length === 0) {
2344
+ console.log('No pending rewards.');
2345
+ return;
2346
+ }
2347
+ console.log(`Pending Rewards (${rewards.length}):`);
2348
+ console.log('─'.repeat(56));
2349
+ rewards.forEach(r => {
2350
+ console.log(` Match: ${r.match_id}`);
2351
+ console.log(` ${r.reward_type}: ${r.amount} ${r.currency}`);
2352
+ });
2353
+ console.log();
2354
+ console.log('─'.repeat(56));
2355
+ if (result.data?.total_eddies)
2356
+ console.log(` Total eddies: ${result.data.total_eddies}`);
2357
+ if (result.data?.total_xp)
2358
+ console.log(` Total XP: ${result.data.total_xp}`);
2359
+ console.log();
2360
+ console.log('Claim rewards: agentkeys battle-claim-rewards <match-id>');
2361
+ });
2362
+ // ─── Skill Upload Art ─────────────────────────────────────────────────────────
2363
+ const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
2364
+ const IMAGE_MIME_TYPES = {
2365
+ '.jpg': 'image/jpeg',
2366
+ '.jpeg': 'image/jpeg',
2367
+ '.png': 'image/png',
2368
+ '.gif': 'image/gif',
2369
+ '.webp': 'image/webp',
2370
+ };
2371
+ program
2372
+ .command('skill-upload-art')
2373
+ .description('Upload card art for a skill / pack')
2374
+ .argument('<skill-id>', 'Skill set ID to upload art for')
2375
+ .argument('<file-path>', 'Path to the image file (jpg, png, gif, webp)')
2376
+ .option('--json', 'Output as JSON')
2377
+ .action(async (skillId, filePath, opts) => {
2378
+ const resolvedPath = path.resolve(filePath);
2379
+ const fileName = path.basename(resolvedPath);
2380
+ const ext = path.extname(fileName).toLowerCase();
2381
+ // Validate extension
2382
+ if (!IMAGE_EXTENSIONS.includes(ext)) {
2383
+ if (opts.json) {
2384
+ console.log(JSON.stringify({ success: false, error: { code: 'INVALID_FILE_TYPE', message: `File must be an image (${IMAGE_EXTENSIONS.join(', ')})` } }));
2385
+ }
2386
+ else {
2387
+ console.error(`✗ File must be an image. Allowed extensions: ${IMAGE_EXTENSIONS.join(', ')}`);
2388
+ }
2389
+ process.exit(2);
2390
+ }
2391
+ // Read file
2392
+ let fileBuffer;
2393
+ try {
2394
+ fileBuffer = fs.readFileSync(resolvedPath);
2395
+ }
2396
+ catch {
2397
+ if (opts.json) {
2398
+ console.log(JSON.stringify({ success: false, error: { code: 'FILE_NOT_FOUND', message: `File not found: ${filePath}` } }));
2399
+ }
2400
+ else {
2401
+ console.error(`✗ File not found: ${filePath}`);
2402
+ }
2403
+ process.exit(1);
2404
+ }
2405
+ // Warn if >10MB (server limit is 8MB, but we warn at 10MB)
2406
+ const TEN_MB = 10 * 1024 * 1024;
2407
+ if (fileBuffer.length > TEN_MB) {
2408
+ console.warn(`⚠ File is ${(fileBuffer.length / 1024 / 1024).toFixed(1)}MB — server limit is 8MB. Upload may fail.`);
2409
+ }
2410
+ const mimeType = IMAGE_MIME_TYPES[ext] ?? 'image/jpeg';
2411
+ if (!opts.json) {
2412
+ console.log(`Uploading ${fileName} for skill ${skillId}...`);
2413
+ }
2414
+ // Multipart upload — use fetch directly
2415
+ const formData = new FormData();
2416
+ formData.append('file', new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), fileName);
2417
+ formData.append('skill_set_id', skillId);
2418
+ const url = `${getBaseUrl()}/api/upload/card-art`;
2419
+ let res;
2420
+ try {
2421
+ res = await fetch(url, {
2422
+ method: 'POST',
2423
+ headers: { 'x-agent-key': getApiKey() },
2424
+ body: formData,
2425
+ });
2426
+ }
2427
+ catch {
2428
+ console.error('✗ Network error — could not reach AgentKeys API.');
2429
+ process.exit(1);
2430
+ }
2431
+ let json;
2432
+ try {
2433
+ json = await res.json();
2434
+ }
2435
+ catch {
2436
+ console.error('✗ Invalid response from server.');
2437
+ process.exit(1);
2438
+ }
2439
+ if (!res.ok) {
2440
+ const errMsg = json?.error?.message ?? 'Upload failed';
2441
+ if (opts.json) {
2442
+ console.log(JSON.stringify({ success: false, error: json?.error ?? { code: 'UPLOAD_ERROR', message: errMsg } }));
2443
+ }
2444
+ else {
2445
+ console.error(`✗ ${errMsg}`);
2446
+ }
2447
+ process.exit(1);
2448
+ }
2449
+ if (opts.json) {
2450
+ console.log(JSON.stringify({ success: true, data: json?.data }));
2451
+ return;
2452
+ }
2453
+ const artUrl = json?.data?.art_image_url;
2454
+ console.log('✓ Art uploaded successfully!');
2455
+ if (artUrl)
2456
+ console.log(` URL: ${artUrl}`);
2457
+ if (json?.data?.width)
2458
+ console.log(` Dimensions: ${json.data.width} × ${json.data.height}px`);
2459
+ });
2460
+ // ─── Claim ───────────────────────────────────────────────────────────────────
2461
+ program
2462
+ .command('claim')
2463
+ .description('View your claimable earnings balance and withdraw to your wallet')
2464
+ .option('--wallet <address>', 'Solana wallet address to receive funds')
2465
+ .option('--json', 'Output as JSON')
2466
+ .action(async (opts) => {
2467
+ // Step 1: Fetch current balance
2468
+ const balanceResult = await apiFetch('/api/claims/balance');
2469
+ if (!balanceResult.ok) {
2470
+ if (opts.json) {
2471
+ console.log(JSON.stringify({ success: false, error: balanceResult.error }));
2472
+ }
2473
+ else {
2474
+ console.error(`✗ ${balanceResult.error?.message ?? 'Failed to fetch balance'}`);
2475
+ }
2476
+ process.exit(1);
2477
+ }
2478
+ const balance = balanceResult.data;
2479
+ if (opts.json) {
2480
+ if (!opts.wallet) {
2481
+ // JSON mode without wallet = balance only
2482
+ console.log(JSON.stringify({ success: true, data: balance }));
2483
+ return;
2484
+ }
2485
+ }
2486
+ else {
2487
+ // Display balance
2488
+ console.log();
2489
+ console.log(' ── Your Earnings Balance ─────────────────────────');
2490
+ console.log(` Claimable: ${balance.total_pending_sol.toFixed(6)} SOL`);
2491
+ console.log();
2492
+ console.log(' Breakdown:');
2493
+ if (balance.by_type.mint_revenue > 0)
2494
+ console.log(` Pack sales: ${balance.by_type.mint_revenue.toFixed(6)} SOL`);
2495
+ if (balance.by_type.secondary_sale > 0)
2496
+ console.log(` Secondary sales: ${balance.by_type.secondary_sale.toFixed(6)} SOL`);
2497
+ if (balance.by_type.secondary_royalty > 0)
2498
+ console.log(` Creator royalties: ${balance.by_type.secondary_royalty.toFixed(6)} SOL`);
2499
+ if ((balance.by_type.referral_t1 ?? 0) + (balance.by_type.referral_t2 ?? 0) > 0)
2500
+ console.log(` Referrals: ${((balance.by_type.referral_t1 ?? 0) + (balance.by_type.referral_t2 ?? 0)).toFixed(6)} SOL`);
2501
+ console.log();
2502
+ console.log(` Total earned (lifetime): ${balance.total_earned_sol.toFixed(6)} SOL`);
2503
+ console.log(` Total claimed (lifetime): ${balance.total_claimed_sol.toFixed(6)} SOL`);
2504
+ if (balance.claim_history.length > 0) {
2505
+ console.log();
2506
+ console.log(' ── Claim History ─────────────────────────────────');
2507
+ balance.claim_history.slice(0, 5).forEach(claim => {
2508
+ const date = new Date(claim.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
2509
+ const statusIcon = claim.status === 'completed' ? '✓' : claim.status === 'failed' ? '✗' : '⏳';
2510
+ console.log(` ${statusIcon} ${parseFloat(claim.amount_sol.toString()).toFixed(6)} SOL ${date} ${claim.status}`);
2511
+ });
2512
+ }
2513
+ console.log();
2514
+ if (balance.total_pending_sol === 0) {
2515
+ console.log(' No claimable balance. Earn by selling packs, cards, or referring agents.');
2516
+ return;
2517
+ }
2518
+ if (balance.total_pending_sol < 0.05) {
2519
+ console.log(` Minimum claim is 0.05 SOL. Current balance: ${balance.total_pending_sol.toFixed(6)} SOL`);
2520
+ return;
2521
+ }
2522
+ }
2523
+ // Step 2: Get wallet address
2524
+ let walletAddress = opts.wallet;
2525
+ if (!walletAddress) {
2526
+ walletAddress = await prompt(` Wallet address to receive ${balance.total_pending_sol.toFixed(6)} SOL: `);
2527
+ if (!walletAddress?.trim()) {
2528
+ console.error('✗ Wallet address is required. Use --wallet <address>');
2529
+ process.exit(2);
2530
+ }
2531
+ }
2532
+ // Step 3: Confirm
2533
+ if (!opts.json) {
2534
+ const ok = await confirm(` Claim ${balance.total_pending_sol.toFixed(6)} SOL to ${walletAddress}?`, false);
2535
+ if (!ok) {
2536
+ console.log(' Aborted.');
2537
+ process.exit(0);
2538
+ }
2539
+ }
2540
+ // Step 4: Submit withdrawal
2541
+ const withdrawResult = await apiFetch('/api/claims/withdraw', {
2542
+ method: 'POST',
2543
+ body: JSON.stringify({ wallet_address: walletAddress }),
2544
+ });
2545
+ if (!withdrawResult.ok) {
2546
+ if (opts.json) {
2547
+ console.log(JSON.stringify({ success: false, error: withdrawResult.error }));
2548
+ }
2549
+ else {
2550
+ console.error(`✗ ${withdrawResult.error?.message ?? 'Failed to submit claim'}`);
2551
+ }
2552
+ process.exit(1);
2553
+ }
2554
+ if (opts.json) {
2555
+ console.log(JSON.stringify({ success: true, data: withdrawResult.data }));
2556
+ }
2557
+ else {
2558
+ console.log();
2559
+ console.log(` ✓ Claim submitted!`);
2560
+ console.log(` Amount: ${withdrawResult.data?.amount_sol.toFixed(6)} SOL`);
2561
+ console.log(` Wallet: ${withdrawResult.data?.wallet_address}`);
2562
+ console.log(` Status: ${withdrawResult.data?.status}`);
2563
+ console.log();
2564
+ console.log(` ${withdrawResult.data?.message}`);
2565
+ console.log();
2566
+ }
2567
+ });
2568
+ function getTierFromCount(count) {
2569
+ if (count <= 2)
2570
+ return { tier: 'basic', display: 'NODE' };
2571
+ if (count <= 4)
2572
+ return { tier: 'uncommon', display: 'CIRCUIT' };
2573
+ if (count <= 7)
2574
+ return { tier: 'rare', display: 'CORE' };
2575
+ if (count <= 11)
2576
+ return { tier: 'epic', display: 'NEXUS' };
2577
+ if (count <= 17)
2578
+ return { tier: 'legendary', display: 'ORACLE' };
2579
+ return { tier: 'mythic', display: 'PHANTOM' };
2580
+ }
2581
+ program
2582
+ .command('organize')
2583
+ .description('Interactive planning tool: map your capabilities to a suggested skill card structure and pack groupings. Runs locally — no API calls.')
2584
+ .option('--json', 'Output result as JSON')
2585
+ .action(async (opts) => {
2586
+ if (!opts.json) {
2587
+ console.log();
2588
+ console.log(' ╔══════════════════════════════════════════════╗');
2589
+ console.log(' ║ AgentKeys — Skill Pack Organizer ║');
2590
+ console.log(' ╚══════════════════════════════════════════════╝');
2591
+ console.log();
2592
+ console.log(" Let's figure out your skill pack structure.");
2593
+ console.log(' Answer a few questions about what you can do.');
2594
+ console.log();
2595
+ }
2596
+ // Q1: Domain
2597
+ const domain = await prompt(' 1. What is your primary domain?\n (e.g., trading, security, data analysis, creative, automation)\n > ');
2598
+ if (!domain) {
2599
+ console.error('✗ Domain is required.');
2600
+ process.exit(2);
2601
+ }
2602
+ // Q2: Capabilities
2603
+ const capsRaw = await prompt('\n 2. List your main capabilities (comma-separated):\n > ');
2604
+ const capabilities = capsRaw.split(',').map((s) => s.trim()).filter(Boolean);
2605
+ if (capabilities.length === 0) {
2606
+ console.error('✗ At least one capability is required.');
2607
+ process.exit(2);
2608
+ }
2609
+ // Q3: API-dependent capabilities
2610
+ const apiRaw = await prompt('\n 3. Do any of these require external API access?\n (comma-separated skill names, or "none")\n > ');
2611
+ const apiDependent = apiRaw.toLowerCase() === 'none' || apiRaw.trim() === ''
2612
+ ? []
2613
+ : apiRaw.split(',').map((s) => s.trim()).filter(Boolean);
2614
+ // Q4: Standalone capabilities
2615
+ const standaloneRaw = await prompt('\n 4. Which of these could work as a standalone tool?\n (comma-separated, or "all")\n > ');
2616
+ const standalone = standaloneRaw.toLowerCase() === 'all'
2617
+ ? [...capabilities]
2618
+ : standaloneRaw.split(',').map((s) => s.trim()).filter(Boolean);
2619
+ // Q5: Scope
2620
+ const scopeRaw = await prompt('\n 5. Rate your overall scope:\n [1] Narrow specialist [2] Moderate range [3] Wide generalist\n > ');
2621
+ const _scope = parseInt(scopeRaw.trim(), 10) || 2;
2622
+ // Q6: Strategy card
2623
+ const strategyRaw = await prompt('\n 6. Do you have a strategy or methodology tying your skills together?\n (Describe briefly, or press Enter to skip)\n > ');
2624
+ const strategyCard = strategyRaw.trim() || null;
2625
+ // Q7: Target audience
2626
+ const targetAudience = await prompt('\n 7. Who is your target audience?\n (e.g., DeFi traders, DevOps teams, content creators)\n > ');
2627
+ // ── Build suggestions ──────────────────────────────────────────────────
2628
+ const packName = `${domain.charAt(0).toUpperCase() + domain.slice(1)} Pack`;
2629
+ const totalSkills = capabilities.length;
2630
+ let packs;
2631
+ if (totalSkills <= 7) {
2632
+ const { tier, display } = getTierFromCount(totalSkills);
2633
+ packs = [{
2634
+ name: packName,
2635
+ suggestedTier: tier,
2636
+ displayTier: display,
2637
+ skillCount: totalSkills,
2638
+ skills: capabilities.map((name) => ({ name, apiDependent: apiDependent.includes(name) })),
2639
+ strategyCard: strategyCard ? { name: `${packName} Strategy`, description: strategyCard } : null,
2640
+ }];
2641
+ }
2642
+ else if (totalSkills <= 14) {
2643
+ const apiSkills = capabilities.filter((c) => apiDependent.includes(c));
2644
+ const localSkills = capabilities.filter((c) => !apiDependent.includes(c));
2645
+ const t1 = getTierFromCount(Math.max(apiSkills.length, 1));
2646
+ const t2 = getTierFromCount(Math.max(localSkills.length, 1));
2647
+ packs = [
2648
+ {
2649
+ name: `${domain.charAt(0).toUpperCase() + domain.slice(1)} Core Pack`,
2650
+ suggestedTier: t1.tier, displayTier: t1.display,
2651
+ skillCount: apiSkills.length,
2652
+ skills: apiSkills.map((name) => ({ name, apiDependent: true })),
2653
+ strategyCard: strategyCard ? { name: `${packName} Strategy`, description: strategyCard } : null,
2654
+ },
2655
+ {
2656
+ name: `${domain.charAt(0).toUpperCase() + domain.slice(1)} Tools Pack`,
2657
+ suggestedTier: t2.tier, displayTier: t2.display,
2658
+ skillCount: localSkills.length,
2659
+ skills: localSkills.map((name) => ({ name, apiDependent: false })),
2660
+ strategyCard: null,
2661
+ },
2662
+ ].filter((p) => p.skillCount > 0);
2663
+ }
2664
+ else {
2665
+ const third = Math.ceil(totalSkills / 3);
2666
+ const chunks = [capabilities.slice(0, third), capabilities.slice(third, third * 2), capabilities.slice(third * 2)];
2667
+ packs = chunks.filter((c) => c.length > 0).map((chunk, i) => {
2668
+ const { tier, display } = getTierFromCount(chunk.length);
2669
+ return {
2670
+ name: `${domain.charAt(0).toUpperCase() + domain.slice(1)} Pack ${i + 1}`,
2671
+ suggestedTier: tier, displayTier: display,
2672
+ skillCount: chunk.length,
2673
+ skills: chunk.map((name) => ({ name, apiDependent: apiDependent.includes(name) })),
2674
+ strategyCard: i === 0 && strategyCard ? { name: `${packName} Strategy`, description: strategyCard } : null,
2675
+ };
2676
+ });
2677
+ }
2678
+ const suggestion = {
2679
+ domain, capabilities, apiDependent, standalone, strategyCard, targetAudience, suggestedPacks: packs,
2680
+ };
2681
+ if (opts.json) {
2682
+ console.log(JSON.stringify({ success: true, data: suggestion }));
2683
+ return;
2684
+ }
2685
+ // ── Human-readable output ──────────────────────────────────────────────
2686
+ console.log();
2687
+ console.log(' ──────────────────────────────────────────────────────');
2688
+ console.log(' SUGGESTED STRUCTURE');
2689
+ console.log(' ──────────────────────────────────────────────────────');
2690
+ packs.forEach((pack, pi) => {
2691
+ console.log();
2692
+ if (packs.length > 1)
2693
+ console.log(` Pack ${pi + 1}: "${pack.name}"`);
2694
+ else
2695
+ console.log(` Pack: "${pack.name}"`);
2696
+ console.log(` Tier: ${pack.displayTier} (${pack.skillCount} skill${pack.skillCount !== 1 ? 's' : ''} → ${pack.suggestedTier})`);
2697
+ console.log(' Skills:');
2698
+ pack.skills.forEach((s, i) => {
2699
+ const note = s.apiDependent ? ' ← requires API access' : '';
2700
+ console.log(` ${i + 1}. ${s.name}${note}`);
2701
+ });
2702
+ if (pack.strategyCard) {
2703
+ console.log();
2704
+ console.log(` ✦ Strategy Card: "${pack.strategyCard.name}"`);
2705
+ console.log(` ${pack.strategyCard.description}`);
2706
+ }
2707
+ });
2708
+ console.log();
2709
+ console.log(` Target: ${targetAudience}`);
2710
+ console.log();
2711
+ console.log(' ──────────────────────────────────────────────────────');
2712
+ console.log(' CLI COMMANDS TO CREATE THIS');
2713
+ console.log(' ──────────────────────────────────────────────────────');
2714
+ packs.forEach((pack, pi) => {
2715
+ console.log();
2716
+ if (packs.length > 1)
2717
+ console.log(` # ── Pack ${pi + 1}: ${pack.name} ──`);
2718
+ console.log();
2719
+ console.log(' # Step 1: Create the pack (follow interactive prompts)');
2720
+ console.log(' agentkeys pack-create');
2721
+ console.log(` # When prompted: name="${pack.name}", supply=100`);
2722
+ console.log();
2723
+ console.log(' # Step 2: Add your skills to the pack');
2724
+ pack.skills.forEach((s) => {
2725
+ const slugGuess = s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
2726
+ console.log(` agentkeys pack-add-skill <PACK_ID> <${slugGuess}-skill-id>`);
2727
+ });
2728
+ if (pack.strategyCard) {
2729
+ console.log();
2730
+ console.log(' # Step 3: Upload card art (optional)');
2731
+ console.log(' agentkeys skill-upload-art <PACK_ID> ./card-art.png');
2732
+ }
2733
+ });
2734
+ console.log();
2735
+ console.log(' # Verify identity (required before publishing)');
2736
+ console.log(' agentkeys verify-start twitter <your-handle>');
2737
+ console.log(' agentkeys verify-confirm twitter --url <tweet-url>');
2738
+ console.log();
2739
+ console.log(' # Publish each pack (locks supply permanently)');
2740
+ packs.forEach((_pack, i) => {
2741
+ console.log(` agentkeys pack-publish <PACK_${i + 1}_ID> -y`);
2742
+ });
2743
+ console.log();
2744
+ console.log(' # Mint your first copy of each pack');
2745
+ packs.forEach((_pack, i) => {
2746
+ console.log(` agentkeys pack-mint <PACK_${i + 1}_ID>`);
2747
+ });
2748
+ console.log();
2749
+ const save = await confirm(' Save this structure to ./agentkeys-organize.json?', false);
2750
+ if (save) {
2751
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
2752
+ const outPath = './agentkeys-organize.json';
2753
+ fs.writeFileSync(outPath, JSON.stringify({ success: true, data: suggestion }, null, 2));
2754
+ console.log(` ✓ Saved to ${outPath}`);
2755
+ }
2756
+ console.log();
2757
+ });
2758
+ // ─── Wishlist Add ─────────────────────────────────────────────────────────────
2759
+ program
2760
+ .command('wishlist-add')
2761
+ .description('Add a pack or listing to your wishlist')
2762
+ .argument('<id>', 'Pack ID or listing ID to wishlist')
2763
+ .option('--type <type>', 'Item type: pack or listing (default: pack)', 'pack')
2764
+ .option('--json', 'Output as JSON')
2765
+ .action(async (itemId, opts) => {
2766
+ const item_type = opts.type ?? 'pack';
2767
+ if (!['pack', 'listing'].includes(item_type)) {
2768
+ console.error('✗ --type must be "pack" or "listing"');
2769
+ process.exit(1);
2770
+ }
2771
+ const result = await apiFetch('/api/wishlist', { method: 'POST', body: JSON.stringify({ item_type, item_id: itemId }) });
2772
+ if (!result.ok) {
2773
+ if (opts.json) {
2774
+ console.log(JSON.stringify({ success: false, error: result.error }));
2775
+ }
2776
+ else if (result.status === 409) {
2777
+ console.error(`✗ Already in your wishlist.`);
2778
+ }
2779
+ else if (result.status === 404) {
2780
+ console.error(`✗ ${item_type === 'pack' ? 'Pack' : 'Listing'} "${itemId}" not found.`);
2781
+ }
2782
+ else if (result.status === 400 && result.error?.message?.includes('full')) {
2783
+ console.error('✗ Wishlist is full (max 100). Remove some items first: agentkeys wishlist-remove <id>');
2784
+ }
2785
+ else {
2786
+ console.error(`✗ ${result.error?.message ?? 'Failed to add to wishlist'}`);
2787
+ }
2788
+ process.exit(1);
2789
+ }
2790
+ if (opts.json) {
2791
+ console.log(JSON.stringify({ success: true, data: result.data }));
2792
+ }
2793
+ else {
2794
+ console.log(`✓ Added ${item_type} to wishlist (row ID: ${result.data?.id})`);
2795
+ console.log(` Remove later: agentkeys wishlist-remove ${result.data?.id}`);
2796
+ }
2797
+ });
2798
+ // ─── Wishlist Remove ──────────────────────────────────────────────────────────
2799
+ program
2800
+ .command('wishlist-remove')
2801
+ .description('Remove an item from your wishlist by wishlist row ID')
2802
+ .argument('<id>', 'Wishlist row ID (from wishlist-view)')
2803
+ .option('-y, --yes', 'Skip confirmation prompt')
2804
+ .option('--json', 'Output as JSON')
2805
+ .action(async (id, opts) => {
2806
+ if (!opts.yes) {
2807
+ const ok = await confirm(`Remove item "${id}" from your wishlist?`, false);
2808
+ if (!ok) {
2809
+ console.log('Aborted.');
2810
+ process.exit(0);
2811
+ }
2812
+ }
2813
+ const result = await apiFetch(`/api/wishlist/${encodeURIComponent(id)}`, { method: 'DELETE' });
2814
+ if (!result.ok) {
2815
+ if (opts.json) {
2816
+ console.log(JSON.stringify({ success: false, error: result.error }));
2817
+ }
2818
+ else if (result.status === 404) {
2819
+ console.error(`✗ Item "${id}" not found in your wishlist.`);
2820
+ }
2821
+ else {
2822
+ console.error(`✗ ${result.error?.message ?? 'Failed to remove from wishlist'}`);
2823
+ }
2824
+ process.exit(1);
2825
+ }
2826
+ if (opts.json) {
2827
+ console.log(JSON.stringify({ success: true, data: result.data }));
2828
+ }
2829
+ else {
2830
+ console.log('✓ Removed from wishlist.');
2831
+ }
2832
+ });
2833
+ program
2834
+ .command('wishlist-view')
2835
+ .description('View all packs and listings in your wishlist')
2836
+ .option('--json', 'Output as JSON')
2837
+ .action(async (opts) => {
2838
+ const result = await apiFetch('/api/wishlist');
2839
+ if (!result.ok) {
2840
+ if (opts.json) {
2841
+ console.log(JSON.stringify({ success: false, error: result.error }));
2842
+ }
2843
+ else {
2844
+ console.error(`✗ ${result.error?.message ?? 'Failed to load wishlist'}`);
2845
+ }
2846
+ process.exit(1);
2847
+ }
2848
+ if (opts.json) {
2849
+ console.log(JSON.stringify({ success: true, data: result.data }));
2850
+ return;
2851
+ }
2852
+ const items = result.data?.items ?? [];
2853
+ if (items.length === 0) {
2854
+ console.log('\n Your wishlist is empty.');
2855
+ console.log(' Browse packs: agentkeys pack-browse');
2856
+ console.log(' Browse market: agentkeys market-browse');
2857
+ return;
2858
+ }
2859
+ console.log(`\n Wishlist (${items.length} item${items.length !== 1 ? 's' : ''})`);
2860
+ console.log(' ─────────────────────────────────────────────');
2861
+ items.forEach((item, i) => {
2862
+ const price = item.price_sol != null ? `${item.price_sol} SOL` : '—';
2863
+ const rarity = item.rarity_tier ?? '—';
2864
+ const status = item.item_type === 'listing'
2865
+ ? (item.is_sold_out ? ' [SOLD]' : item.is_active === false ? ' [INACTIVE]' : '')
2866
+ : '';
2867
+ console.log(`\n ${i + 1}. [${item.item_type.toUpperCase()}] ${item.name ?? item.item_id}${status}`);
2868
+ console.log(` Price: ${price} · Rarity: ${rarity}`);
2869
+ console.log(` Wishlist ID: ${item.id}`);
2870
+ });
2871
+ console.log();
2872
+ });
2873
+ // ─── Recommend ────────────────────────────────────────────────────────────────
2874
+ program
2875
+ .command('recommend')
2876
+ .description('Get personalized pack recommendations based on your agent profile and current loadout')
2877
+ .option('--limit <n>', 'Number of recommendations (default: 5)', '5')
2878
+ .option('--json', 'Output as JSON')
2879
+ .action(async (opts) => {
2880
+ const limit = Math.min(parseInt(opts.limit ?? '5'), 20);
2881
+ // Fetch agent profile + current loadout for cross-reference
2882
+ const [profileResult, packsResult] = await Promise.all([
2883
+ apiFetch('/api/agents/me'),
2884
+ apiFetch(`/api/skills?limit=50&sort=power_level_desc`),
2885
+ ]);
2886
+ if (!profileResult.ok) {
2887
+ if (opts.json) {
2888
+ console.log(JSON.stringify({ success: false, error: profileResult.error }));
2889
+ }
2890
+ else {
2891
+ console.error(`✗ ${profileResult.error?.message ?? 'Failed to fetch profile'}`);
2892
+ }
2893
+ process.exit(1);
2894
+ }
2895
+ const agent = profileResult.data;
2896
+ const packs = packsResult.data ?? [];
2897
+ if (opts.json) {
2898
+ console.log(JSON.stringify({ success: true, data: { agent_name: agent?.name, recommendations: packs } }));
2899
+ return;
2900
+ }
2901
+ console.log('\n ── Recommended Packs ─────────────────────────────');
2902
+ console.log(` Based on your profile: ${agent?.name ?? 'Agent'}`);
2903
+ console.log();
2904
+ if (!packs || (Array.isArray(packs) && packs.length === 0)) {
2905
+ console.log(' No recommendations available right now. Try browsing all packs:');
2906
+ console.log(' agentkeys pack-browse');
2907
+ return;
2908
+ }
2909
+ const items = Array.isArray(packs) ? packs.slice(0, limit) : [];
2910
+ items.forEach((pack, i) => {
2911
+ const price = pack.price_sol != null ? `${pack.price_sol} SOL` : '—';
2912
+ console.log(` ${i + 1}. ${pack.name ?? pack.id}`);
2913
+ console.log(` ${pack.description ?? ''}`);
2914
+ console.log(` Price: ${price} · Rarity: ${pack.rarity_tier ?? '—'}`);
2915
+ console.log(` Mint: agentkeys pack-mint ${pack.id}`);
2916
+ console.log();
2917
+ });
2918
+ });
2919
+ // ─── Wallet ──────────────────────────────────────────────────────────────────
2920
+ // OKX Agentic Wallet — TEE-protected, email+OTP setup.
2921
+ // No local keypair files. Wallet lives in OKX TEE, never extractable.
2922
+ program
2923
+ .command('wallet')
2924
+ .description('Show your OKX Agentic Wallet address, SOL balance, and deposit instructions')
2925
+ .option('--json', 'Output as JSON')
2926
+ .action(async (opts) => {
2927
+ const { Connection, PublicKey, LAMPORTS_PER_SOL } = await Promise.resolve().then(() => __importStar(require('@solana/web3.js')));
2928
+ // Fetch wallet address from the platform (stored post-OTP-verify)
2929
+ const profileResult = await apiFetch('/api/agents/me', { method: 'GET' });
2930
+ if (!profileResult.ok) {
2931
+ if (opts.json) {
2932
+ console.log(JSON.stringify({ success: false, error: profileResult.error }));
2933
+ }
2934
+ else {
2935
+ console.error(`✗ ${profileResult.error?.message ?? 'Failed to fetch profile'}`);
2936
+ }
2937
+ process.exit(1);
2938
+ }
2939
+ const walletAddress = profileResult.data?.wallet_address ?? null;
2940
+ if (!walletAddress) {
2941
+ if (opts.json) {
2942
+ console.log(JSON.stringify({
2943
+ success: false,
2944
+ error: {
2945
+ code: 'NO_WALLET',
2946
+ message: 'Wallet not yet configured. Run: agentkeys wallet-setup',
2947
+ },
2948
+ }));
2949
+ }
2950
+ else {
2951
+ console.error('✗ Wallet not yet configured.');
2952
+ console.error(' Run: agentkeys wallet-setup');
2953
+ }
2954
+ process.exit(1);
2955
+ }
2956
+ // Live SOL balance
2957
+ let balanceSol = null;
2958
+ const rpcUrl = process.env.SOLANA_RPC_URL ?? 'https://api.mainnet-beta.solana.com';
2959
+ try {
2960
+ const connection = new Connection(rpcUrl, 'confirmed');
2961
+ const pubkey = new PublicKey(walletAddress);
2962
+ const lamports = await connection.getBalance(pubkey);
2963
+ balanceSol = lamports / LAMPORTS_PER_SOL;
2964
+ }
2965
+ catch {
2966
+ // Non-fatal — balance display is informational
2967
+ }
2968
+ if (opts.json) {
2969
+ console.log(JSON.stringify({
2970
+ success: true,
2971
+ data: {
2972
+ wallet_address: walletAddress,
2973
+ balance_sol: balanceSol,
2974
+ deposit_address: walletAddress,
2975
+ },
2976
+ }));
2977
+ return;
2978
+ }
2979
+ console.log();
2980
+ console.log(' ── OKX Agentic Wallet ────────────────────────────');
2981
+ console.log(` Address: ${walletAddress}`);
2982
+ if (balanceSol !== null) {
2983
+ console.log(` Balance: ${balanceSol.toFixed(6)} SOL`);
2984
+ if (balanceSol === 0) {
2985
+ console.log();
2986
+ console.log(' ⚠ Wallet is empty. Deposit SOL to get started.');
2987
+ }
2988
+ }
2989
+ else {
2990
+ console.log(' Balance: (unavailable — RPC unreachable)');
2991
+ }
2992
+ console.log();
2993
+ console.log(' ── Security ──────────────────────────────────────');
2994
+ console.log(' Private key: stored in OKX TEE. Never exposed to anyone.');
2995
+ console.log(' Protected by: TEE + email OTP. No seed phrase needed.');
2996
+ console.log();
2997
+ console.log(' ── How to Deposit SOL ────────────────────────────');
2998
+ console.log(' 1. Copy your wallet address above');
2999
+ console.log(' 2. Send SOL from any Solana wallet (Phantom, OKX, Backpack)');
3000
+ console.log(' 3. Recipient address:');
3001
+ console.log();
3002
+ console.log(` ${walletAddress}`);
3003
+ console.log();
3004
+ console.log(' ── Explorer ──────────────────────────────────────');
3005
+ console.log(` https://solscan.io/account/${walletAddress}`);
3006
+ console.log();
3007
+ });
3008
+ // ─── Wallet Setup ─────────────────────────────────────────────────────────────
3009
+ program
3010
+ .command('wallet-setup')
3011
+ .description('Create your OKX Agentic Wallet — sends OTP to your email (TEE-protected, no seed phrase)')
3012
+ .option('--email <email>', 'Email address for OTP delivery')
3013
+ .option('--json', 'Output as JSON')
3014
+ .action(async (opts) => {
3015
+ let email = opts.email;
3016
+ if (!email) {
3017
+ email = await prompt(' Your email address (for wallet OTP): ');
3018
+ if (!email?.trim()) {
3019
+ console.error('✗ Email is required.');
3020
+ process.exit(1);
3021
+ }
3022
+ email = email.trim();
3023
+ }
3024
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
3025
+ console.error('✗ Invalid email address.');
3026
+ process.exit(1);
3027
+ }
3028
+ if (!opts.json) {
3029
+ console.log();
3030
+ console.log(' Sending OTP to', email, '...');
3031
+ }
3032
+ const result = await apiFetch('/api/wallet/setup', { method: 'POST', body: JSON.stringify({ email }) });
3033
+ if (!result.ok) {
3034
+ if (opts.json) {
3035
+ console.log(JSON.stringify({ success: false, error: result.error }));
3036
+ }
3037
+ else {
3038
+ console.error(`✗ ${result.error?.message ?? 'Failed to send OTP'}`);
3039
+ }
3040
+ process.exit(1);
3041
+ }
3042
+ if (opts.json) {
3043
+ console.log(JSON.stringify({ success: true, data: result.data }));
3044
+ return;
3045
+ }
3046
+ console.log();
3047
+ console.log(' ✓ OTP sent! Check your email.');
3048
+ console.log();
3049
+ console.log(' Next: run agentkeys wallet-setup-verify --email <email> --otp <code>');
3050
+ console.log();
3051
+ });
3052
+ // ─── Wallet Setup Verify ───────────────────────────────────────────────────────
3053
+ program
3054
+ .command('wallet-setup-verify')
3055
+ .description('Verify OTP and create your OKX Agentic Wallet (TEE-protected)')
3056
+ .option('--email <email>', 'Email address used in wallet-setup')
3057
+ .option('--otp <code>', 'OTP code from your email')
3058
+ .option('--json', 'Output as JSON')
3059
+ .action(async (opts) => {
3060
+ let email = opts.email;
3061
+ let otp = opts.otp;
3062
+ if (!email) {
3063
+ email = await prompt(' Email address: ');
3064
+ if (!email?.trim()) {
3065
+ console.error('✗ Email required.');
3066
+ process.exit(1);
3067
+ }
3068
+ email = email.trim();
3069
+ }
3070
+ if (!otp) {
3071
+ otp = await prompt(' OTP code from email: ');
3072
+ if (!otp?.trim()) {
3073
+ console.error('✗ OTP required.');
3074
+ process.exit(1);
3075
+ }
3076
+ otp = otp.trim();
3077
+ }
3078
+ if (!opts.json) {
3079
+ console.log();
3080
+ console.log(' Verifying OTP and creating wallet ...');
3081
+ }
3082
+ const result = await apiFetch('/api/wallet/setup/verify', { method: 'POST', body: JSON.stringify({ email, otp }) });
3083
+ if (!result.ok) {
3084
+ if (opts.json) {
3085
+ console.log(JSON.stringify({ success: false, error: result.error }));
3086
+ }
3087
+ else {
3088
+ console.error(`✗ ${result.error?.message ?? 'Verification failed'}`);
3089
+ }
3090
+ process.exit(1);
3091
+ }
3092
+ if (opts.json) {
3093
+ console.log(JSON.stringify({ success: true, data: result.data }));
3094
+ return;
3095
+ }
3096
+ console.log();
3097
+ console.log(' ✓ OKX Agentic Wallet created!');
3098
+ console.log();
3099
+ console.log(` Solana address: ${result.data?.wallet_address}`);
3100
+ if (result.data?.evm_address) {
3101
+ console.log(` EVM address: ${result.data.evm_address}`);
3102
+ }
3103
+ console.log();
3104
+ console.log(' Private key: stored in OKX TEE — never extractable.');
3105
+ console.log(' Your wallet is now linked to your agent profile.');
3106
+ console.log();
3107
+ console.log(" ── What's Next ───────────────────────────────────");
3108
+ console.log(' 1. Run: agentkeys wallet (view address + balance)');
3109
+ console.log(' 2. Run: agentkeys verify-start twitter <handle> (identity)');
3110
+ console.log(' 3. Run: agentkeys pack-create (build your first pack)');
3111
+ console.log();
3112
+ });
3113
+ // ─── Wallet Connect ───────────────────────────────────────────────────────────
3114
+ // ─── Spending Limit / Autonomous Buying ─────────────────────────────────────
3115
+ program
3116
+ .command('spending-limit')
3117
+ .description('View or set your autonomous buying spending limit (in SOL). Default: OFF (manual confirmation on all purchases).')
3118
+ .argument('[amount]', 'SOL limit per period. Pass "off" to disable autonomous buying. Omit to view current setting.')
3119
+ .option('--json', 'Output as JSON')
3120
+ .action(async (amount, opts) => {
3121
+ // View current setting
3122
+ if (!amount) {
3123
+ const result = await apiFetch('/api/agents/me/autonomous-settings');
3124
+ if (!result.ok) {
3125
+ if (opts?.json) {
3126
+ console.log(JSON.stringify({ success: false, error: result.error }));
3127
+ return;
3128
+ }
3129
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch settings'}`);
3130
+ process.exit(1);
3131
+ }
3132
+ const d = result.data;
3133
+ if (opts?.json) {
3134
+ console.log(JSON.stringify({ success: true, data: d }));
3135
+ return;
3136
+ }
3137
+ console.log();
3138
+ console.log(' ── Autonomous Buying Settings ──────────────────');
3139
+ console.log(` Status: ${d.autonomous_buying_enabled ? '✓ ENABLED' : '✗ DISABLED (manual confirmation on all purchases)'}`);
3140
+ if (d.autonomous_buying_enabled && d.spending_limit_sol != null) {
3141
+ console.log(` Limit: ${d.spending_limit_sol} SOL per purchase`);
3142
+ }
3143
+ console.log();
3144
+ console.log(' To enable: agentkeys spending-limit <SOL_AMOUNT>');
3145
+ console.log(' To disable: agentkeys spending-limit off');
3146
+ return;
3147
+ }
3148
+ // Update setting
3149
+ const isOff = amount.toLowerCase() === 'off' || amount === '0';
3150
+ const updates = {};
3151
+ if (isOff) {
3152
+ updates.autonomous_buying_enabled = false;
3153
+ updates.spending_limit_sol = null;
3154
+ }
3155
+ else {
3156
+ const num = parseFloat(amount);
3157
+ if (isNaN(num) || num <= 0) {
3158
+ console.error('✗ Amount must be a positive number in SOL, or "off" to disable.');
3159
+ process.exit(1);
3160
+ }
3161
+ updates.autonomous_buying_enabled = true;
3162
+ updates.spending_limit_sol = num;
3163
+ }
3164
+ const result = await apiFetch('/api/agents/me/autonomous-settings', {
3165
+ method: 'PATCH',
3166
+ body: JSON.stringify(updates),
3167
+ });
3168
+ if (!result.ok) {
3169
+ if (opts?.json) {
3170
+ console.log(JSON.stringify({ success: false, error: result.error }));
3171
+ return;
3172
+ }
3173
+ console.error(`✗ ${result.error?.message ?? 'Failed to update settings'}`);
3174
+ process.exit(1);
3175
+ }
3176
+ if (opts?.json) {
3177
+ console.log(JSON.stringify({ success: true, data: result.data }));
3178
+ return;
3179
+ }
3180
+ if (isOff) {
3181
+ console.log('✓ Autonomous buying DISABLED. All purchases require manual confirmation.');
3182
+ }
3183
+ else {
3184
+ console.log(`✓ Autonomous buying ENABLED with ${amount} SOL limit per purchase.`);
3185
+ console.log(' Your agent will buy automatically within this limit. No confirmation needed below the limit.');
3186
+ }
3187
+ });
3188
+ program
3189
+ .command('wallet-connect')
3190
+ .description('Connect an external Solana wallet (Phantom, OKX, Backpack) by providing your public key')
3191
+ .option('--address <address>', 'Your Solana wallet public key (base58)')
3192
+ .option('--json', 'Output as JSON')
3193
+ .action(async (opts) => {
3194
+ let walletAddress = opts.address;
3195
+ if (!walletAddress) {
3196
+ walletAddress = await prompt(' Enter your Solana wallet address (base58 public key): ');
3197
+ if (!walletAddress?.trim()) {
3198
+ console.error('✗ Wallet address is required.');
3199
+ process.exit(1);
3200
+ }
3201
+ walletAddress = walletAddress.trim();
3202
+ }
3203
+ // Basic base58 validation
3204
+ if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(walletAddress)) {
3205
+ console.error('✗ Invalid Solana address format.');
3206
+ process.exit(1);
3207
+ }
3208
+ const result = await apiFetch('/api/agents/me', { method: 'PATCH', body: JSON.stringify({ wallet_address: walletAddress }) });
3209
+ if (!result.ok) {
3210
+ if (opts.json) {
3211
+ console.log(JSON.stringify({ success: false, error: result.error }));
3212
+ }
3213
+ else {
3214
+ console.error(`✗ ${result.error?.message ?? 'Failed to connect wallet'}`);
3215
+ }
3216
+ process.exit(1);
3217
+ }
3218
+ if (opts.json) {
3219
+ console.log(JSON.stringify({ success: true, data: { wallet_address: walletAddress } }));
3220
+ }
3221
+ else {
3222
+ console.log();
3223
+ console.log(` ✓ Wallet connected: ${walletAddress}`);
3224
+ console.log();
3225
+ console.log(' Supported wallets: Phantom, OKX, Backpack, Solflare — any Solana-compatible wallet.');
3226
+ console.log(' Your public key is used for NFT transfers and SOL payouts.');
3227
+ console.log();
3228
+ }
3229
+ });
3230
+ // ─── Social Connect ───────────────────────────────────────────────────────────
3231
+ program
3232
+ .command('social-connect')
3233
+ .description('Connect a social account (twitter or github) to your agent profile')
3234
+ .argument('<platform>', 'Platform to connect: twitter | github')
3235
+ .option('--json', 'Output as JSON')
3236
+ .action(async (platform, opts) => {
3237
+ const validPlatforms = ['twitter', 'github'];
3238
+ const normalized = platform.toLowerCase().replace('x', 'twitter');
3239
+ if (!validPlatforms.includes(normalized)) {
3240
+ console.error(`✗ Unsupported platform. Choose: ${validPlatforms.join(', ')}`);
3241
+ process.exit(1);
3242
+ }
3243
+ // Kick off verify flow which handles OAuth
3244
+ const startResult = await apiFetch('/api/verify/start', { method: 'POST', body: JSON.stringify({ provider: normalized }) });
3245
+ if (!startResult.ok) {
3246
+ if (opts.json) {
3247
+ console.log(JSON.stringify({ success: false, error: startResult.error }));
3248
+ }
3249
+ else {
3250
+ console.error(`✗ ${startResult.error?.message ?? 'Failed to start social connect'}`);
3251
+ console.error(` Try: agentkeys verify-start ${normalized} <your-handle>`);
3252
+ }
3253
+ process.exit(1);
3254
+ }
3255
+ if (opts.json) {
3256
+ console.log(JSON.stringify({ success: true, data: startResult.data }));
3257
+ return;
3258
+ }
3259
+ console.log();
3260
+ console.log(` ── Connect ${normalized.charAt(0).toUpperCase() + normalized.slice(1)} ─────────────────────────`);
3261
+ if (startResult.data?.redirect_url) {
3262
+ console.log(` Visit this URL to authorize:`);
3263
+ console.log(` ${startResult.data.redirect_url}`);
3264
+ }
3265
+ else if (startResult.data?.instructions) {
3266
+ console.log(` ${startResult.data.instructions}`);
3267
+ }
3268
+ else {
3269
+ console.log(` Follow the verification flow:`);
3270
+ console.log(` agentkeys verify-start ${normalized} <your-handle>`);
3271
+ }
3272
+ console.log();
3273
+ });
3274
+ // ─── Got Skills? ──────────────────────────────────────────────────────────────
3275
+ program
3276
+ .command('got-skills')
3277
+ .description('Audit your capabilities and discover what skill packs you could list on AgentKeys')
3278
+ .option('--scan <scope>', 'What to scan: files | config | both | skip (default: prompt)')
3279
+ .option('--json', 'Output as JSON')
3280
+ .action(async (opts) => {
3281
+ const os = await Promise.resolve().then(() => __importStar(require('os')));
3282
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
3283
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
3284
+ let scope = opts.scan;
3285
+ if (!scope) {
3286
+ if (!opts.json) {
3287
+ console.log();
3288
+ console.log(' ── GotSkills? — Capability Audit ─────────────────');
3289
+ console.log();
3290
+ console.log(' This tool scans your environment (read-only) to identify');
3291
+ console.log(' capabilities you could package and sell as skill packs.');
3292
+ console.log();
3293
+ scope = await prompt(' What would you like me to scan? (files/config/both/skip): ') ?? 'skip';
3294
+ scope = scope.trim().toLowerCase();
3295
+ }
3296
+ else {
3297
+ scope = 'both';
3298
+ }
3299
+ }
3300
+ if (!['files', 'config', 'both', 'skip'].includes(scope)) {
3301
+ scope = 'skip';
3302
+ }
3303
+ if (scope === 'skip') {
3304
+ if (opts.json) {
3305
+ console.log(JSON.stringify({ success: true, data: { scope: 'skip', skills: [], message: 'Scan skipped by user.' } }));
3306
+ }
3307
+ else {
3308
+ console.log('\n Scan skipped. Run again when you\'re ready: agentkeys got-skills');
3309
+ }
3310
+ return;
3311
+ }
3312
+ // ── Read-only discovery ──────────────────────────────────────────────────
3313
+ const home = os.homedir();
3314
+ const discovered = [];
3315
+ if (scope === 'files' || scope === 'both') {
3316
+ // Check common project/tool directories
3317
+ const checks = [
3318
+ { path: path.join(home, '.agentkeys'), category: 'Blockchain', name: 'AgentKeys Config', value: 'medium' },
3319
+ { path: '/usr/bin/ffmpeg', category: 'Media Processing', name: 'FFmpeg Video/Audio Processing', value: 'high' },
3320
+ { path: '/usr/bin/python3', category: 'AI/ML', name: 'Python ML Runtime', value: 'medium' },
3321
+ { path: '/usr/local/bin/node', category: 'Development', name: 'Node.js Automation', value: 'medium' },
3322
+ { path: path.join(home, '.ssh'), category: 'Infrastructure', name: 'SSH Remote Access', value: 'high' },
3323
+ { path: path.join(home, '.kube'), category: 'Infrastructure', name: 'Kubernetes Orchestration', value: 'high' },
3324
+ { path: path.join(home, '.docker'), category: 'Infrastructure', name: 'Docker Container Management', value: 'medium' },
3325
+ { path: path.join(home, 'agents'), category: 'AI Agents', name: 'Multi-Agent Orchestration', value: 'high' },
3326
+ ];
3327
+ for (const check of checks) {
3328
+ try {
3329
+ if (fs.existsSync(check.path)) {
3330
+ discovered.push({
3331
+ category: check.category,
3332
+ name: check.name,
3333
+ evidence: check.path.replace(home, '~'),
3334
+ value: check.value,
3335
+ });
3336
+ }
3337
+ }
3338
+ catch {
3339
+ // Skip inaccessible paths
3340
+ }
3341
+ }
3342
+ }
3343
+ if (scope === 'config' || scope === 'both') {
3344
+ // Check environment for known tool/API integrations
3345
+ const envChecks = [
3346
+ { env: 'OPENAI_API_KEY', category: 'AI/ML', name: 'OpenAI API Integration', value: 'high' },
3347
+ { env: 'ANTHROPIC_API_KEY', category: 'AI/ML', name: 'Anthropic Claude Integration', value: 'high' },
3348
+ { env: 'SOLANA_RPC_URL', category: 'Blockchain', name: 'Solana On-Chain Access', value: 'high' },
3349
+ { env: 'AGENTKEYS_API_KEY', category: 'AgentKeys', name: 'AgentKeys Marketplace Agent', value: 'medium' },
3350
+ { env: 'GITHUB_TOKEN', category: 'Development', name: 'GitHub Automation', value: 'medium' },
3351
+ { env: 'DATABASE_URL', category: 'Infrastructure', name: 'Database Access Layer', value: 'medium' },
3352
+ { env: 'REDIS_URL', category: 'Infrastructure', name: 'Cache/Queue Management', value: 'low' },
3353
+ { env: 'STRIPE_SECRET_KEY', category: 'Payments', name: 'Stripe Payment Processing', value: 'high' },
3354
+ { env: 'TWITTER_API_KEY', category: 'Social', name: 'Twitter/X API Integration', value: 'medium' },
3355
+ { env: 'DISCORD_TOKEN', category: 'Social', name: 'Discord Bot Automation', value: 'medium' },
3356
+ { env: 'TELEGRAM_BOT_TOKEN', category: 'Social', name: 'Telegram Bot Integration', value: 'medium' },
3357
+ { env: 'AWS_ACCESS_KEY_ID', category: 'Cloud', name: 'AWS Cloud Services', value: 'high' },
3358
+ { env: 'GOOGLE_APPLICATION_CREDENTIALS', category: 'Cloud', name: 'Google Cloud Integration', value: 'high' },
3359
+ ];
3360
+ for (const check of envChecks) {
3361
+ if (process.env[check.env]) {
3362
+ // Only record that it exists — never log the value
3363
+ discovered.push({
3364
+ category: check.category,
3365
+ name: check.name,
3366
+ evidence: `$${check.env} is set`,
3367
+ value: check.value,
3368
+ });
3369
+ }
3370
+ }
3371
+ }
3372
+ // ── Generate report ───────────────────────────────────────────────────────
3373
+ // Deduplicate by name
3374
+ const unique = discovered.filter((d, i, arr) => arr.findIndex(x => x.name === d.name) === i);
3375
+ // Group into recommended pack bundles by category
3376
+ const byCategory = {};
3377
+ for (const item of unique) {
3378
+ if (!byCategory[item.category])
3379
+ byCategory[item.category] = [];
3380
+ byCategory[item.category].push(item);
3381
+ }
3382
+ const highValueCount = unique.filter(u => u.value === 'high').length;
3383
+ const estimatedRevenue = highValueCount * 2.5 + unique.filter(u => u.value === 'medium').length * 0.8;
3384
+ if (opts.json) {
3385
+ console.log(JSON.stringify({
3386
+ success: true,
3387
+ data: {
3388
+ scope,
3389
+ skills_found: unique.length,
3390
+ high_value: highValueCount,
3391
+ categories: byCategory,
3392
+ estimated_monthly_revenue_sol: estimatedRevenue.toFixed(2),
3393
+ },
3394
+ }));
3395
+ return;
3396
+ }
3397
+ console.log();
3398
+ console.log(' ── GotSkills? Audit Results ──────────────────────');
3399
+ console.log();
3400
+ if (unique.length === 0) {
3401
+ console.log(' No capabilities detected in the scanned scope.');
3402
+ console.log(' Try scanning more: agentkeys got-skills --scan both');
3403
+ return;
3404
+ }
3405
+ console.log(` Found ${unique.length} potential skill${unique.length !== 1 ? 's' : ''} across ${Object.keys(byCategory).length} categories.`);
3406
+ console.log();
3407
+ // Display by category
3408
+ for (const [category, skills] of Object.entries(byCategory)) {
3409
+ console.log(` ▸ ${category}`);
3410
+ for (const skill of skills) {
3411
+ const badge = skill.value === 'high' ? '🔥' : skill.value === 'medium' ? '⚡' : '○';
3412
+ console.log(` ${badge} ${skill.name}`);
3413
+ console.log(` Evidence: ${skill.evidence}`);
3414
+ }
3415
+ console.log();
3416
+ }
3417
+ // Recommended pack bundles
3418
+ console.log(' ── Recommended Pack Groupings ────────────────────');
3419
+ console.log();
3420
+ let packNum = 1;
3421
+ for (const [category, skills] of Object.entries(byCategory)) {
3422
+ if (skills.length > 0) {
3423
+ const suggestedName = `${category} Agent Pack`;
3424
+ const highVal = skills.filter(s => s.value === 'high').length;
3425
+ const suggestedPrice = highVal >= 2 ? '1.5' : highVal === 1 ? '0.8' : '0.3';
3426
+ console.log(` Pack ${packNum}: "${suggestedName}"`);
3427
+ console.log(` Skills: ${skills.map(s => s.name).join(', ')}`);
3428
+ console.log(` Suggested price: ${suggestedPrice} SOL`);
3429
+ console.log(` Create: agentkeys pack-create`);
3430
+ console.log();
3431
+ packNum++;
3432
+ }
3433
+ }
3434
+ // Revenue estimate
3435
+ console.log(' ── Revenue Estimate ──────────────────────────────');
3436
+ console.log();
3437
+ console.log(` If you listed all ${unique.length} skills and averaged 5 sales/month:`);
3438
+ console.log(` ~${estimatedRevenue.toFixed(2)} SOL/month (rough estimate)`);
3439
+ console.log();
3440
+ if (highValueCount > 0) {
3441
+ console.log(` 🔥 ${highValueCount} high-value skill${highValueCount !== 1 ? 's' : ''} detected — you could be leaving money on the table.`);
3442
+ console.log();
3443
+ }
3444
+ console.log(' ── Next Steps ────────────────────────────────────');
3445
+ console.log();
3446
+ console.log(' 1. agentkeys pack-create — create a new skill pack');
3447
+ console.log(' 2. agentkeys pack-add-skill — add skills to your pack');
3448
+ console.log(' 3. agentkeys pack-publish — publish and set price');
3449
+ console.log(' 4. agentkeys market-my-listings — view your live listings');
3450
+ console.log();
3451
+ console.log(' Nothing was saved. Run again anytime.');
3452
+ console.log();
3453
+ });
3454
+ // ─── Setup ───────────────────────────────────────────────────────────────────
3455
+ // OKX Agentic Wallet setup — guides through the full 4-step flow.
3456
+ program
3457
+ .command('setup')
3458
+ .description('Set up your AgentKeys identity and OKX Agentic Wallet (TEE-protected, no seed phrase)')
3459
+ .option('--json', 'Output as JSON')
3460
+ .action(async (opts) => {
3461
+ const hasApiKey = !!process.env.AGENTKEYS_API_KEY;
3462
+ if (!opts.json) {
3463
+ console.log();
3464
+ console.log(' ── AgentKeys Setup ───────────────────────────────');
3465
+ console.log();
3466
+ }
3467
+ if (!hasApiKey) {
3468
+ if (opts.json) {
3469
+ console.log(JSON.stringify({
3470
+ success: false,
3471
+ error: {
3472
+ code: 'NO_API_KEY',
3473
+ message: 'AGENTKEYS_API_KEY is not set. Register at agentkeys.app, then set the env var.',
3474
+ next_steps: [
3475
+ 'Register at https://agentkeys.app or via POST /api/agents/register',
3476
+ 'Set: export AGENTKEYS_API_KEY=<your-key>',
3477
+ 'Run: agentkeys wallet-setup --email <your@email.com>',
3478
+ ],
3479
+ },
3480
+ }));
3481
+ }
3482
+ else {
3483
+ console.log(' Step 1: Get your agent API key');
3484
+ console.log();
3485
+ console.log(' Register at: https://agentkeys.app');
3486
+ console.log(" Or via API: POST /api/agents/register { name: 'YourAgent' }");
3487
+ console.log();
3488
+ console.log(' Then set: export AGENTKEYS_API_KEY=<your-key>');
3489
+ console.log();
3490
+ console.log(' Once you have your API key, run:');
3491
+ console.log(' agentkeys wallet-setup --email <your@email.com>');
3492
+ console.log();
3493
+ }
3494
+ return;
3495
+ }
3496
+ const profileResult = await apiFetch('/api/agents/me', { method: 'GET' });
3497
+ if (!profileResult.ok) {
3498
+ if (opts.json) {
3499
+ console.log(JSON.stringify({ success: false, error: profileResult.error }));
3500
+ }
3501
+ else {
3502
+ console.error(`✗ ${profileResult.error?.message ?? 'Failed to fetch profile'}`);
3503
+ }
3504
+ process.exit(1);
3505
+ }
3506
+ const walletConfigured = profileResult.data?.wallet_configured ?? false;
3507
+ const walletAddress = profileResult.data?.wallet_address ?? null;
3508
+ if (walletConfigured && walletAddress) {
3509
+ if (opts.json) {
3510
+ console.log(JSON.stringify({
3511
+ success: true,
3512
+ data: {
3513
+ wallet_configured: true,
3514
+ wallet_address: walletAddress,
3515
+ message: 'Already set up. Your OKX Agentic Wallet is active.',
3516
+ },
3517
+ }));
3518
+ }
3519
+ else {
3520
+ console.log(' ✓ Already set up!');
3521
+ console.log();
3522
+ console.log(` Wallet: ${walletAddress}`);
3523
+ console.log();
3524
+ console.log(' ── Commands ──────────────────────────────────────');
3525
+ console.log(' agentkeys wallet — view balance');
3526
+ console.log(' agentkeys verify-start twitter — identity verification');
3527
+ console.log(' agentkeys pack-create — build your first pack');
3528
+ console.log();
3529
+ }
3530
+ return;
3531
+ }
3532
+ if (opts.json) {
3533
+ console.log(JSON.stringify({
3534
+ success: true,
3535
+ data: {
3536
+ wallet_configured: false,
3537
+ message: 'Wallet not yet configured.',
3538
+ next_steps: [
3539
+ 'Run: agentkeys wallet-setup --email <your@email.com>',
3540
+ 'Then: agentkeys wallet-setup-verify --email <email> --otp <code>',
3541
+ ],
3542
+ },
3543
+ }));
3544
+ }
3545
+ else {
3546
+ console.log(' ── Step 2: Create your OKX Agentic Wallet ────────');
3547
+ console.log();
3548
+ console.log(' Your wallet is TEE-protected — no seed phrase, no private key exposure.');
3549
+ console.log();
3550
+ console.log(' Run: agentkeys wallet-setup --email <your@email.com>');
3551
+ console.log(' Then: agentkeys wallet-setup-verify --email <email> --otp <code>');
3552
+ console.log();
3553
+ console.log(' ── What is OKX Agentic Wallet? ───────────────────');
3554
+ console.log(' Private keys are generated and stored inside OKX TEE.');
3555
+ console.log(' No one — not you, not OKX, not the platform — can extract them.');
3556
+ console.log(' Your wallet is bound to your email, recoverable via OTP.');
3557
+ console.log();
3558
+ }
3559
+ });
3560
+ // ─── Register ───────────────────────────────────────────────────────────────
3561
+ program
3562
+ .command('register')
3563
+ .description('Register a new agent and get your API key')
3564
+ .requiredOption('--name <name>', 'Agent name')
3565
+ .option('--bio <bio>', 'Optional agent bio')
3566
+ .option('--keypair <path>', 'Path to Solana keypair JSON file (array of bytes). If omitted, a new keypair is generated and saved to ~/.agentkeys/keypair.json')
3567
+ .option('--json', 'Output as JSON')
3568
+ .action(async (opts) => {
3569
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
3570
+ const os = await Promise.resolve().then(() => __importStar(require('os')));
3571
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
3572
+ const nacl = await Promise.resolve().then(() => __importStar(require('tweetnacl')));
3573
+ // Load or auto-generate keypair
3574
+ let keypairBytes;
3575
+ let keypairPath = null;
3576
+ if (opts.keypair) {
3577
+ // Load from provided path
3578
+ try {
3579
+ const raw = fs.readFileSync(opts.keypair, 'utf-8');
3580
+ const arr = JSON.parse(raw);
3581
+ keypairBytes = new Uint8Array(arr);
3582
+ }
3583
+ catch (e) {
3584
+ const msg = e instanceof Error ? e.message : String(e);
3585
+ if (opts.json) {
3586
+ console.log(JSON.stringify({ success: false, error: { code: 'KEYPAIR_LOAD_FAILED', message: msg } }));
3587
+ }
3588
+ else {
3589
+ console.error(`✗ Failed to load keypair from ${opts.keypair}: ${msg}`);
3590
+ }
3591
+ process.exit(1);
3592
+ }
3593
+ }
3594
+ else {
3595
+ // Auto-generate a new Ed25519 keypair and persist it
3596
+ const defaultDir = path.join(os.homedir(), '.agentkeys');
3597
+ keypairPath = path.join(defaultDir, 'keypair.json');
3598
+ if (fs.existsSync(keypairPath)) {
3599
+ // Reuse existing auto-generated keypair
3600
+ try {
3601
+ const raw = fs.readFileSync(keypairPath, 'utf-8');
3602
+ const arr = JSON.parse(raw);
3603
+ keypairBytes = new Uint8Array(arr);
3604
+ if (!opts.json)
3605
+ console.log(` ℹ️ Using existing keypair at ${keypairPath}`);
3606
+ }
3607
+ catch (e) {
3608
+ const msg = e instanceof Error ? e.message : String(e);
3609
+ if (opts.json) {
3610
+ console.log(JSON.stringify({ success: false, error: { code: 'KEYPAIR_LOAD_FAILED', message: msg } }));
3611
+ }
3612
+ else {
3613
+ console.error(`✗ Failed to load existing keypair from ${keypairPath}: ${msg}`);
3614
+ }
3615
+ process.exit(1);
3616
+ }
3617
+ }
3618
+ else {
3619
+ // Generate fresh keypair
3620
+ const kp = nacl.default.sign.keyPair();
3621
+ keypairBytes = kp.secretKey; // 64 bytes
3622
+ try {
3623
+ fs.mkdirSync(defaultDir, { recursive: true });
3624
+ fs.writeFileSync(keypairPath, JSON.stringify(Array.from(keypairBytes)), { mode: 0o600 });
3625
+ if (!opts.json)
3626
+ console.log(` ✓ Generated new keypair → ${keypairPath}`);
3627
+ }
3628
+ catch (e) {
3629
+ const msg = e instanceof Error ? e.message : String(e);
3630
+ if (opts.json) {
3631
+ console.log(JSON.stringify({ success: false, error: { code: 'KEYPAIR_SAVE_FAILED', message: msg } }));
3632
+ }
3633
+ else {
3634
+ console.error(`✗ Failed to save keypair: ${msg}`);
3635
+ }
3636
+ process.exit(1);
3637
+ }
3638
+ }
3639
+ }
3640
+ // Build Keypair + message
3641
+ const bs58Mod = await Promise.resolve().then(() => __importStar(require('bs58')));
3642
+ const bs58Encode = bs58Mod.default?.encode ?? bs58Mod.encode;
3643
+ const secretKey = keypairBytes.slice(0, 64);
3644
+ const publicKeyBytes = keypairBytes.slice(32, 64);
3645
+ const pubkey = bs58Encode(publicKeyBytes);
3646
+ const timestamp = Date.now().toString();
3647
+ const cryptoMod = await Promise.resolve().then(() => __importStar(require('crypto')));
3648
+ const nonce = cryptoMod.randomBytes(4).toString('hex');
3649
+ const message = `AgentKeys registration\nWallet: ${pubkey}\nTimestamp: ${timestamp}\nNonce: ${nonce}`;
3650
+ const msgBytes = new TextEncoder().encode(message);
3651
+ const sigBytes = nacl.default.sign.detached(msgBytes, secretKey);
3652
+ const signature = bs58Encode(sigBytes);
3653
+ // POST to register (no API key needed)
3654
+ const baseUrl = process.env.AGENTKEYS_API_URL ?? 'https://agentkeys.vercel.app';
3655
+ const body = {
3656
+ wallet_address: pubkey,
3657
+ signature,
3658
+ message,
3659
+ name: opts.name,
3660
+ };
3661
+ if (opts.bio)
3662
+ body.bio = opts.bio;
3663
+ let res;
3664
+ try {
3665
+ res = await fetch(`${baseUrl}/api/agents/register`, {
3666
+ method: 'POST',
3667
+ headers: { 'Content-Type': 'application/json' },
3668
+ body: JSON.stringify(body),
3669
+ });
3670
+ }
3671
+ catch (e) {
3672
+ const msg = e instanceof Error ? e.message : String(e);
3673
+ if (opts.json) {
3674
+ console.log(JSON.stringify({ success: false, error: { code: 'NETWORK_ERROR', message: msg } }));
3675
+ }
3676
+ else {
3677
+ console.error(`✗ Network error: ${msg}`);
3678
+ }
3679
+ process.exit(1);
3680
+ }
3681
+ const json = await res.json();
3682
+ if (!res.ok) {
3683
+ const errMsg = (json.error && typeof json.error === 'object' && 'message' in json.error)
3684
+ ? json.error.message
3685
+ : `HTTP ${res.status}`;
3686
+ if (opts.json) {
3687
+ console.log(JSON.stringify({ success: false, error: { code: 'REGISTER_FAILED', message: errMsg } }));
3688
+ }
3689
+ else {
3690
+ console.error(`✗ Registration failed (${res.status}): ${errMsg}`);
3691
+ }
3692
+ process.exit(1);
3693
+ }
3694
+ const agent = json.data?.agent;
3695
+ const apiKey = json.data?.api_key;
3696
+ if (!agent || !apiKey) {
3697
+ if (opts.json) {
3698
+ console.log(JSON.stringify({ success: false, error: { code: 'UNEXPECTED_RESPONSE', message: 'Missing agent or api_key in response' } }));
3699
+ }
3700
+ else {
3701
+ console.error('✗ Unexpected response from server');
3702
+ }
3703
+ process.exit(1);
3704
+ }
3705
+ if (opts.json) {
3706
+ console.log(JSON.stringify({ success: true, data: { id: agent.id, api_key: apiKey, name: agent.name } }));
3707
+ }
3708
+ else {
3709
+ console.log();
3710
+ console.log(' ✓ Agent registered successfully!');
3711
+ console.log();
3712
+ console.log(` Name: ${agent.name}`);
3713
+ console.log(` Agent ID: ${agent.id}`);
3714
+ console.log();
3715
+ console.log(' ┌─────────────────────────────────────────────────┐');
3716
+ console.log(' │ 🔑 YOUR API KEY — SAVE THIS NOW │');
3717
+ console.log(' │ │');
3718
+ console.log(` │ ${apiKey.padEnd(47)} │`);
3719
+ console.log(' │ │');
3720
+ console.log(' │ ⚠️ This will NOT be shown again. │');
3721
+ console.log(' └─────────────────────────────────────────────────┘');
3722
+ console.log();
3723
+ console.log(` export AGENTKEYS_API_KEY=${apiKey}`);
3724
+ console.log();
3725
+ console.log(' Next steps:');
3726
+ console.log(' agentkeys wallet-setup --email you@email.com');
3727
+ console.log(' agentkeys wallet');
3728
+ console.log();
3729
+ }
3730
+ });
3731
+ // ─── Status ──────────────────────────────────────────────────────────────────
3732
+ program
3733
+ .command('status')
3734
+ .description('Check your agent status — wallet, verification, and packs')
3735
+ .option('--json', 'Output as JSON')
3736
+ .action(async (opts) => {
3737
+ const apiKey = process.env.AGENTKEYS_API_KEY ?? '';
3738
+ if (!apiKey) {
3739
+ if (opts.json) {
3740
+ console.log(JSON.stringify({ success: false, error: { code: 'NO_API_KEY', message: 'AGENTKEYS_API_KEY is not set' } }));
3741
+ }
3742
+ else {
3743
+ console.error('✗ AGENTKEYS_API_KEY is not set.');
3744
+ console.error(' Register at: agentkeys.vercel.app');
3745
+ console.error(' Then: export AGENTKEYS_API_KEY=<your-key>');
3746
+ }
3747
+ process.exit(3);
3748
+ }
3749
+ const result = await apiFetch('/api/agents/me');
3750
+ if (!result.ok) {
3751
+ if (opts.json) {
3752
+ console.log(JSON.stringify({ success: false, error: result.error }));
3753
+ }
3754
+ else {
3755
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch agent status'}`);
3756
+ }
3757
+ process.exit(1);
3758
+ }
3759
+ if (opts.json) {
3760
+ console.log(JSON.stringify({ success: true, data: result.data }));
3761
+ return;
3762
+ }
3763
+ const agent = result.data;
3764
+ const walletAddr = agent.wallet_address;
3765
+ const walletDisplay = walletAddr
3766
+ ? `✓ ${walletAddr.slice(0, 8)}...${walletAddr.slice(-4)}`
3767
+ : '✗ not configured';
3768
+ const verified = agent.verification_status === 'verified';
3769
+ const baseUrl = process.env.AGENTKEYS_API_URL ?? 'https://agentkeys.vercel.app';
3770
+ console.log();
3771
+ console.log(' ── Agent Status ──────────────────────────────────');
3772
+ console.log(` Name: ${agent.name}`);
3773
+ console.log(` ID: ${agent.id}`);
3774
+ console.log(` Wallet: ${walletDisplay}`);
3775
+ console.log(` Verified: ${verified ? '✓' : '✗ unverified'}`);
3776
+ console.log(` Creator: ${agent.is_active_creator ? '✓ active' : '✗ inactive'}`);
3777
+ console.log(' ─────────────────────────────────────────────────');
3778
+ console.log(` API URL: ${baseUrl}`);
3779
+ console.log();
3780
+ if (!walletAddr) {
3781
+ console.log(' → Run: agentkeys wallet-setup --email <you@email.com>');
3782
+ }
3783
+ else if (!verified) {
3784
+ console.log(' → Run: agentkeys verify-start twitter <your-handle>');
3785
+ }
3786
+ else {
3787
+ console.log(' Ready. Run: agentkeys pack-create');
3788
+ }
3789
+ console.log();
3790
+ });
3791
+ // ─── Pack List ────────────────────────────────────────────────────────────────
3792
+ program
3793
+ .command('pack-list')
3794
+ .description('List your skill packs')
3795
+ .option('--limit <n>', 'Max results', '20')
3796
+ .option('--json', 'Output as JSON')
3797
+ .action(async (opts) => {
3798
+ const limit = parseInt(opts.limit ?? '20', 10);
3799
+ const result = await apiFetch(`/api/skill-sets?limit=${limit}`);
3800
+ if (!result.ok) {
3801
+ if (opts.json) {
3802
+ console.log(JSON.stringify({ success: false, error: result.error }));
3803
+ }
3804
+ else {
3805
+ console.error(`✗ ${result.error?.message ?? 'Failed to fetch packs'}`);
3806
+ }
3807
+ process.exit(1);
3808
+ }
3809
+ if (opts.json) {
3810
+ console.log(JSON.stringify({ success: true, data: result.data }));
3811
+ return;
3812
+ }
3813
+ const packs = Array.isArray(result.data) ? result.data : [];
3814
+ if (packs.length === 0) {
3815
+ console.log();
3816
+ console.log(' No packs yet.');
3817
+ console.log(' Create one: agentkeys pack-create');
3818
+ console.log();
3819
+ return;
3820
+ }
3821
+ console.log();
3822
+ console.log(` Your Packs (${packs.length})`);
3823
+ console.log(' ─────────────────────────────────────────────────────────────');
3824
+ packs.forEach((pack, i) => {
3825
+ const status = pack.is_published ? 'Published' : 'Draft';
3826
+ const supply = pack.supply_cap != null ? `${pack.copies_minted ?? 0}/${pack.supply_cap}` : '—';
3827
+ const price = pack.price_sol != null ? `${pack.price_sol} SOL` : '—';
3828
+ const tier = (pack.rarity_tier ?? '—').padEnd(10, ' ');
3829
+ const name = (pack.name ?? pack.id).padEnd(30, ' ');
3830
+ const shortId = pack.id.slice(0, 8) + '…';
3831
+ console.log(` ${String(i + 1).padStart(2)}. ${name} [${status}] ${tier} ${pack.power_level ?? 0} skills ${supply} minted ${price}`);
3832
+ console.log(` ID: ${shortId}`);
3833
+ });
3834
+ console.log();
3835
+ });
3836
+ // ─── Skill Create ─────────────────────────────────────────────────────────────
3837
+ program
3838
+ .command('skill-create')
3839
+ .description('Create a new skill')
3840
+ .option('--name <name>', 'Skill name (skip prompt)')
3841
+ .option('--slug <slug>', 'Skill slug (auto-generated if omitted)')
3842
+ .option('--description <desc>', 'Skill description (skip prompt)')
3843
+ .option('--json', 'Output as JSON')
3844
+ .action(async (opts) => {
3845
+ let name = opts.name;
3846
+ let slug = opts.slug;
3847
+ let description = opts.description;
3848
+ if (!name) {
3849
+ name = await prompt('Skill name: ');
3850
+ if (!name?.trim()) {
3851
+ console.error('✗ Skill name is required.');
3852
+ process.exit(1);
3853
+ }
3854
+ name = name.trim();
3855
+ }
3856
+ const autoSlug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
3857
+ if (!slug) {
3858
+ const inputSlug = await prompt(`Slug (default: ${autoSlug}): `);
3859
+ slug = inputSlug.trim() || autoSlug;
3860
+ }
3861
+ if (!description) {
3862
+ const inputDesc = await prompt('Description (optional, press Enter to skip): ');
3863
+ description = inputDesc.trim() || undefined;
3864
+ }
3865
+ const body = { name, slug };
3866
+ if (description)
3867
+ body.description = description;
3868
+ // content_md is optional — API will generate a placeholder if omitted
3869
+ const result = await apiFetch('/api/skills', { method: 'POST', body: JSON.stringify(body) });
3870
+ if (!result.ok) {
3871
+ const code = result.error?.code;
3872
+ if (opts.json) {
3873
+ console.log(JSON.stringify({ success: false, error: result.error }));
3874
+ }
3875
+ else if (result.status === 409 || code === 'CONFLICT') {
3876
+ console.error(`✗ Slug "${slug}" is already taken.`);
3877
+ console.error(` Try: agentkeys skill-create --slug ${slug}-2`);
3878
+ }
3879
+ else if (result.status === 403 || code === 'VERIFICATION_REQUIRED') {
3880
+ console.error('✗ Verification required to create skills.');
3881
+ console.error(' Run: agentkeys verify-start twitter <your-handle>');
3882
+ }
3883
+ else {
3884
+ console.error(`✗ ${result.error?.message ?? 'Failed to create skill'}`);
3885
+ }
3886
+ process.exit(1);
3887
+ }
3888
+ if (opts.json) {
3889
+ console.log(JSON.stringify({ success: true, data: result.data }));
3890
+ return;
3891
+ }
3892
+ const skill = result.data;
3893
+ console.log(`✓ Skill created: ${skill.name}`);
3894
+ console.log(` ID: ${skill.id}`);
3895
+ console.log(` Slug: ${skill.slug}`);
3896
+ console.log();
3897
+ console.log('Next steps:');
3898
+ console.log(` Add to a pack: agentkeys pack-add-skill <pack-id> ${skill.id}`);
3899
+ console.log(` View packs: agentkeys pack-list`);
3900
+ });
3901
+ program.parse(process.argv);
3902
+ //# sourceMappingURL=agentkeys.js.map