terminalmarket 0.6.5 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bin/tm.js +160 -249
  2. package/package.json +1 -1
  3. package/src/format.js +454 -9
package/bin/tm.js CHANGED
@@ -7,7 +7,11 @@ import readline from "readline";
7
7
 
8
8
  import { apiGet, apiPost, apiDelete, apiPatch } from "../src/api.js";
9
9
  import { getApiBase, setApiBase, getUser, setUser, clearUser, clearSession } from "../src/config.js";
10
- import { printTable, pickProductFields, pickSellerFields, pickOfferFields, containsQuery, formatStars } from "../src/format.js";
10
+ import {
11
+ printTable, pickProductFields, pickSellerFields, pickOfferFields, containsQuery, formatStars,
12
+ printHeader, printDivider, printSuccess, printError, printWarning, printInfo, printField, printEmpty,
13
+ printProductCard, printCart, printOrders, printStoreCard, printSellers, printReviews, printAIModels, printCredits
14
+ } from "../src/format.js";
11
15
 
12
16
  // Helper for hidden password input
13
17
  function askPassword(prompt = "Password: ") {
@@ -59,7 +63,7 @@ const program = new Command();
59
63
  program
60
64
  .name("tm")
61
65
  .description("TerminalMarket CLI — marketplace for developers")
62
- .version("0.6.3");
66
+ .version("0.7.0");
63
67
 
64
68
  // -----------------
65
69
  // config
@@ -289,28 +293,16 @@ cart
289
293
  try {
290
294
  const cartData = await apiGet("/cart");
291
295
 
292
- if (!cartData.items || cartData.items.length === 0) {
293
- console.log(chalk.yellow("Your cart is empty."));
294
- console.log(chalk.dim("Add items with: tm cart add <product-id>"));
295
- return;
296
- }
297
-
298
- console.log(chalk.bold("Your Cart"));
299
- console.log("");
300
-
301
296
  let total = 0;
302
- cartData.items.forEach((item, i) => {
303
- const subtotal = (item.price || 0) * (item.quantity || 1);
304
- total += subtotal;
305
- console.log(`${i + 1}. ${item.name || `Product #${item.productId}`}`);
306
- console.log(` ${chalk.dim("qty:")} ${item.quantity} ${chalk.dim("price:")} $${item.price} ${chalk.dim("subtotal:")} $${subtotal.toFixed(2)}`);
307
- });
297
+ if (cartData.items?.length) {
298
+ cartData.items.forEach((item) => {
299
+ total += (item.price || 0) * (item.quantity || 1);
300
+ });
301
+ }
308
302
 
309
- console.log("");
310
- console.log(chalk.bold(`Total: $${total.toFixed(2)}`));
311
- console.log(chalk.dim("Checkout with: tm checkout"));
303
+ printCart(cartData.items || [], total);
312
304
  } catch (e) {
313
- console.error(chalk.red(e?.message || String(e)));
305
+ printError(e?.message || String(e));
314
306
  process.exitCode = 1;
315
307
  }
316
308
  });
@@ -323,9 +315,9 @@ cart
323
315
  try {
324
316
  const quantity = parseInt(opts.quantity) || 1;
325
317
  await apiPost("/cart/add", { productId: parseInt(productId), quantity });
326
- console.log(chalk.green(`Added to cart (qty: ${quantity})`));
318
+ printSuccess(`Added to cart (qty: ${quantity})`);
327
319
  } catch (e) {
328
- console.error(chalk.red(e?.message || String(e)));
320
+ printError(e?.message || String(e));
329
321
  process.exitCode = 1;
330
322
  }
331
323
  });
@@ -336,9 +328,9 @@ cart
336
328
  .action(async (productId) => {
337
329
  try {
338
330
  await apiPost("/cart/remove", { productId: parseInt(productId) });
339
- console.log(chalk.green("Removed from cart."));
331
+ printSuccess("Removed from cart");
340
332
  } catch (e) {
341
- console.error(chalk.red(e?.message || String(e)));
333
+ printError(e?.message || String(e));
342
334
  process.exitCode = 1;
343
335
  }
344
336
  });
@@ -349,9 +341,9 @@ cart
349
341
  .action(async () => {
350
342
  try {
351
343
  await apiPost("/cart/clear", {});
352
- console.log(chalk.green("Cart cleared."));
344
+ printSuccess("Cart cleared");
353
345
  } catch (e) {
354
- console.error(chalk.red(e?.message || String(e)));
346
+ printError(e?.message || String(e));
355
347
  process.exitCode = 1;
356
348
  }
357
349
  });
@@ -421,28 +413,9 @@ program
421
413
  try {
422
414
  const orders = await apiGet("/orders");
423
415
  const limit = parseInt(opts.limit) || 10;
424
-
425
- if (!orders || orders.length === 0) {
426
- console.log(chalk.yellow("No orders yet."));
427
- return;
428
- }
429
-
430
- console.log(chalk.bold("Order History"));
431
- console.log("");
432
-
433
- orders.slice(0, limit).forEach((order) => {
434
- const date = new Date(order.createdAt).toLocaleDateString();
435
- const statusColor = order.status === "delivered" ? chalk.green :
436
- order.status === "shipped" ? chalk.cyan :
437
- order.status === "paid" ? chalk.blue :
438
- chalk.yellow;
439
-
440
- console.log(`${chalk.bold(order.orderNumber || `#${order.id}`)} - ${date}`);
441
- console.log(` ${chalk.dim("status:")} ${statusColor(order.status)} ${chalk.dim("total:")} $${order.total || 0}`);
442
- console.log("");
443
- });
416
+ printOrders((orders || []).slice(0, limit));
444
417
  } catch (e) {
445
- console.error(chalk.red(e?.message || String(e)));
418
+ printError(e?.message || String(e));
446
419
  process.exitCode = 1;
447
420
  }
448
421
  });
@@ -486,27 +459,9 @@ program
486
459
  .action(async (storeId) => {
487
460
  try {
488
461
  const data = await apiGet(`/stores/${storeId}/reviews`);
489
-
490
- if (data.reviewCount === 0) {
491
- console.log(chalk.yellow(`No reviews yet for store #${storeId}.`));
492
- console.log(chalk.dim(`Be the first: tm review ${storeId} <rating> [comment]`));
493
- return;
494
- }
495
-
496
- console.log(chalk.bold(`Store Reviews`));
497
- console.log(`${formatStars(Math.round(data.averageRating))} ${data.averageRating.toFixed(1)}/5 (${data.reviewCount} reviews)`);
498
- console.log("");
499
-
500
- data.reviews.forEach((review) => {
501
- const date = new Date(review.createdAt).toLocaleDateString();
502
- console.log(`${formatStars(review.rating)} by ${review.userName} (${date})`);
503
- if (review.comment) {
504
- console.log(` "${review.comment}"`);
505
- }
506
- console.log("");
507
- });
462
+ printReviews(data.reviews || [], data.averageRating);
508
463
  } catch (e) {
509
- console.error(chalk.red(e?.message || String(e)));
464
+ printError(e?.message || String(e));
510
465
  process.exitCode = 1;
511
466
  }
512
467
  });
@@ -522,31 +477,21 @@ program
522
477
  const store = await apiGet(`/stores/${storeId}`);
523
478
 
524
479
  if (!store) {
525
- console.error(chalk.red("Store not found."));
480
+ printError("Store not found");
526
481
  return;
527
482
  }
528
483
 
529
- console.log(chalk.bold(store.name));
530
- if (store.verified) console.log(chalk.green("✓ Verified"));
531
- console.log("");
532
-
533
- if (store.description) console.log(store.description);
534
- console.log("");
535
-
536
- console.log(`${chalk.dim("id:")} ${store.id}`);
537
- if (store.slug) console.log(`${chalk.dim("slug:")} ${store.slug}`);
538
-
484
+ // Get rating
539
485
  try {
540
486
  const rating = await apiGet(`/stores/${storeId}/rating`);
541
487
  if (rating.count > 0) {
542
- console.log(`${chalk.dim("rating:")} ${formatStars(Math.round(rating.average))} ${rating.average.toFixed(1)}/5 (${rating.count} reviews)`);
488
+ store.rating = rating.average;
543
489
  }
544
490
  } catch {}
545
491
 
546
- if (store.website) console.log(`${chalk.dim("website:")} ${store.website}`);
547
- if (store.supportEmail) console.log(`${chalk.dim("support:")} ${store.supportEmail}`);
492
+ printStoreCard(store);
548
493
  } catch (e) {
549
- console.error(chalk.red(e?.message || String(e)));
494
+ printError(e?.message || String(e));
550
495
  process.exitCode = 1;
551
496
  }
552
497
  });
@@ -567,40 +512,17 @@ ai
567
512
  const data = await apiGet("/ai/models");
568
513
  const { models, categories } = data;
569
514
 
570
- if (!models || models.length === 0) {
571
- console.log(chalk.yellow("No AI models available yet."));
572
- return;
573
- }
574
-
575
- console.log(chalk.bold("Available AI Models"));
576
- console.log("");
577
-
515
+ // Add category names to models
578
516
  const catMap = new Map((categories || []).map(c => [c.id, c]));
517
+ const modelsWithCat = (models || []).map(m => ({
518
+ ...m,
519
+ categoryName: catMap.get(m.categoryId)?.name || 'Other',
520
+ creditsPerRun: '$' + parseFloat(m.pricePerRun).toFixed(4)
521
+ }));
579
522
 
580
- // Group by category
581
- const grouped = {};
582
- for (const model of models) {
583
- const cat = catMap.get(model.categoryId);
584
- const catName = cat ? cat.name : "Other";
585
- if (!grouped[catName]) grouped[catName] = [];
586
- grouped[catName].push(model);
587
- }
588
-
589
- for (const [catName, catModels] of Object.entries(grouped)) {
590
- console.log(chalk.cyan.bold(`${catName}:`));
591
- for (const model of catModels) {
592
- const price = parseFloat(model.pricePerRun).toFixed(4);
593
- console.log(` ${chalk.white(model.slug.padEnd(25))} $${price} ${chalk.dim(model.outputType)}`);
594
- if (model.description) {
595
- console.log(` ${chalk.dim(model.description)}`);
596
- }
597
- }
598
- console.log("");
599
- }
600
-
601
- console.log(chalk.dim("Run: tm ai run <model> <input>"));
523
+ printAIModels(modelsWithCat, categories);
602
524
  } catch (e) {
603
- console.error(chalk.red(e?.message || String(e)));
525
+ printError(e?.message || String(e));
604
526
  process.exitCode = 1;
605
527
  }
606
528
  });
@@ -649,18 +571,20 @@ ai
649
571
  try {
650
572
  const credits = await apiGet("/credits");
651
573
 
652
- console.log(chalk.bold("AI Credits"));
653
- console.log("");
654
- console.log(`${chalk.dim("balance:")} $${parseFloat(credits.balance).toFixed(4)}`);
655
- console.log(`${chalk.dim("purchased:")} $${parseFloat(credits.totalPurchased).toFixed(2)}`);
656
- console.log(`${chalk.dim("spent:")} $${parseFloat(credits.totalSpent).toFixed(4)}`);
657
- console.log("");
658
- console.log(chalk.dim("Top up: tm ai topup <amount>"));
574
+ console.log();
575
+ console.log(chalk.cyan.bold(' 💳 AI Credits'));
576
+ console.log();
577
+ console.log(` ${chalk.white('Balance:')} ${chalk.green.bold('$' + parseFloat(credits.balance).toFixed(4))}`);
578
+ console.log(` ${chalk.dim('Purchased:')} $${parseFloat(credits.totalPurchased).toFixed(2)}`);
579
+ console.log(` ${chalk.dim('Spent:')} $${parseFloat(credits.totalSpent).toFixed(4)}`);
580
+ console.log();
581
+ console.log(chalk.dim(' 💡 tm ai topup <amount> — add more credits'));
582
+ console.log();
659
583
  } catch (e) {
660
584
  if (e?.message?.includes("401") || e?.message?.includes("Login")) {
661
- console.log(chalk.yellow("Please login first: tm login <email> <password>"));
585
+ printWarning("Please login first: tm login <email>");
662
586
  } else {
663
- console.error(chalk.red(e?.message || String(e)));
587
+ printError(e?.message || String(e));
664
588
  process.exitCode = 1;
665
589
  }
666
590
  }
@@ -1216,48 +1140,8 @@ program
1216
1140
  return;
1217
1141
  }
1218
1142
 
1219
- console.log(chalk.bold(p.name || `Product ${productIdOrSlug}`));
1220
- console.log("");
1221
-
1222
- if (p.shortDescription) {
1223
- console.log(chalk.italic(p.shortDescription));
1224
- console.log("");
1225
- }
1226
-
1227
- if (p.description) {
1228
- console.log(p.description);
1229
- console.log("");
1230
- }
1231
-
1232
- console.log(`${chalk.dim("id:")} ${p.productId || p.id}`);
1233
- if (p.slug) console.log(`${chalk.dim("slug:")} ${p.slug}`);
1234
- if (p.category) console.log(`${chalk.dim("category:")} ${p.category}`);
1235
- if (p.price) console.log(`${chalk.dim("price:")} $${p.price}`);
1236
-
1237
- const serviceType = p.serviceType || "global";
1238
- const typeLabel = serviceType === "global" ? "🌍 Global" :
1239
- serviceType === "national" ? "🏳️ National" : "📍 Local";
1240
- console.log(`${chalk.dim("serviceType:")} ${typeLabel}`);
1241
-
1242
- if (serviceType === "local" && p.serviceCity) {
1243
- console.log(`${chalk.dim("city:")} ${p.serviceCity}`);
1244
- }
1245
- if ((serviceType === "national" || serviceType === "local") && p.serviceCountry) {
1246
- console.log(`${chalk.dim("country:")} ${p.serviceCountry}`);
1247
- }
1248
-
1249
- // Show image URL if available
1250
- const imageUrl = p.imageUrl || p.image;
1251
- if (imageUrl) {
1252
- console.log("");
1253
- console.log(`${chalk.dim("image:")} ${imageUrl}`);
1254
- console.log(chalk.dim(`Use: tm view ${p.slug || p.id} --image`));
1255
- }
1256
-
1257
- if (p.buyUrl) console.log(`${chalk.dim("buyUrl:")} ${p.buyUrl}`);
1258
- if (p.subscriptionAvailable) console.log(`${chalk.dim("subscription:")} ${chalk.green("available")}`);
1259
- if (p.tags && p.tags.length > 0) console.log(`${chalk.dim("tags:")} ${p.tags.join(", ")}`);
1260
- if (p.storeId) console.log(`${chalk.dim("storeId:")} ${p.storeId}`);
1143
+ // Print beautiful product card
1144
+ printProductCard(p);
1261
1145
 
1262
1146
  try {
1263
1147
  const offers = await apiGet(`/products/${encodeURIComponent(p.productId || p.id)}/offers`);
@@ -1433,15 +1317,9 @@ program
1433
1317
  );
1434
1318
  }
1435
1319
 
1436
- const rows = (sellers || []).slice(0, limit).map(pickSellerFields);
1437
- printTable(rows, [
1438
- { key: "slug", title: "slug" },
1439
- { key: "name", title: "name" },
1440
- { key: "serviceType", title: "type" },
1441
- { key: "verified", title: "verified" },
1442
- ]);
1320
+ printSellers((sellers || []).slice(0, limit));
1443
1321
  } catch (e) {
1444
- console.error(chalk.red(e?.message || String(e)));
1322
+ printError(e?.message || String(e));
1445
1323
  process.exitCode = 1;
1446
1324
  }
1447
1325
  });
@@ -1454,47 +1332,14 @@ program
1454
1332
  const seller = await apiGet(`/sellers/${encodeURIComponent(slug)}`);
1455
1333
 
1456
1334
  if (!seller) {
1457
- console.error(chalk.red("Seller not found"));
1335
+ printError("Seller not found");
1458
1336
  process.exitCode = 1;
1459
1337
  return;
1460
1338
  }
1461
1339
 
1462
- console.log(chalk.bold(seller.name));
1463
- if (seller.verified) {
1464
- console.log(chalk.green("✓ Verified Seller"));
1465
- }
1466
- console.log("");
1467
-
1468
- if (seller.description) console.log(seller.description);
1469
- console.log("");
1470
-
1471
- console.log(`${chalk.dim("slug:")} ${seller.slug}`);
1472
-
1473
- const serviceType = seller.serviceType || "global";
1474
- const typeLabel = serviceType === "global" ? "🌍 Global (SaaS/Digital)" :
1475
- serviceType === "national" ? "🏳️ National" : "📍 Local";
1476
- console.log(`${chalk.dim("serviceType:")} ${typeLabel}`);
1477
-
1478
- if (serviceType === "local" && seller.baseCity) {
1479
- console.log(`${chalk.dim("city:")} ${seller.baseCity}`);
1480
- }
1481
- if ((serviceType === "national" || serviceType === "local") && seller.baseCountry) {
1482
- console.log(`${chalk.dim("country:")} ${seller.baseCountry}`);
1483
- }
1484
-
1485
- if (seller.website) console.log(`${chalk.dim("website:")} ${seller.website}`);
1486
- if (seller.supportEmail) console.log(`${chalk.dim("support:")} ${seller.supportEmail}`);
1487
- if (seller.badges && seller.badges.length > 0) {
1488
- console.log(`${chalk.dim("badges:")} ${seller.badges.join(", ")}`);
1489
- }
1490
- if (seller.categories && seller.categories.length > 0) {
1491
- console.log(`${chalk.dim("categories:")} ${seller.categories.join(", ")}`);
1492
- }
1493
- if (seller.shippingPolicy) console.log(`${chalk.dim("shippingPolicy:")} ${seller.shippingPolicy}`);
1494
- if (seller.returnPolicy) console.log(`${chalk.dim("returnPolicy:")} ${seller.returnPolicy}`);
1495
-
1340
+ printStoreCard(seller);
1496
1341
  } catch (e) {
1497
- console.error(chalk.red(e?.message || String(e)));
1342
+ printError(e?.message || String(e));
1498
1343
  process.exitCode = 1;
1499
1344
  }
1500
1345
  });
@@ -1540,45 +1385,75 @@ program
1540
1385
  .command("about")
1541
1386
  .description("About TerminalMarket")
1542
1387
  .action(() => {
1543
- console.log(chalk.bold("TerminalMarket"));
1544
- console.log("The marketplace for developers who prefer the command line.\n");
1545
- console.log("We connect developers with premium services: coffee subscriptions,");
1546
- console.log("healthy snacks, coworking spaces, productivity tools, and more.\n");
1547
- console.log(chalk.dim("Website: https://terminalmarket.app"));
1548
- console.log(chalk.dim("CLI: npm i -g terminalmarket"));
1388
+ console.log();
1389
+ console.log(chalk.green.bold(' ╔══════════════════════════════════════════════╗'));
1390
+ console.log(chalk.green.bold(' ║') + chalk.white.bold(' TERMINAL MARKET') + chalk.green.bold(' ║'));
1391
+ console.log(chalk.green.bold(' ║') + chalk.dim(' The marketplace for developers') + chalk.green.bold(' ║'));
1392
+ console.log(chalk.green.bold(' ╚══════════════════════════════════════════════╝'));
1393
+ console.log();
1394
+ console.log(chalk.white(' We connect developers with premium services:'));
1395
+ console.log();
1396
+ console.log(chalk.cyan(' ☕') + chalk.white(' Coffee subscriptions'));
1397
+ console.log(chalk.cyan(' 🥗') + chalk.white(' Healthy snacks & lunch'));
1398
+ console.log(chalk.cyan(' 💆') + chalk.white(' Health & wellness services'));
1399
+ console.log(chalk.cyan(' 🏢') + chalk.white(' Coworking spaces'));
1400
+ console.log(chalk.cyan(' 🤖') + chalk.white(' AI services & tools'));
1401
+ console.log(chalk.cyan(' ⚡') + chalk.white(' Productivity tools'));
1402
+ console.log();
1403
+ console.log(chalk.dim(' ─────────────────────────────────────────────'));
1404
+ console.log();
1405
+ console.log(` ${chalk.dim('Website:')} ${chalk.cyan('https://terminalmarket.app')}`);
1406
+ console.log(` ${chalk.dim('Install:')} ${chalk.green('npm i -g terminalmarket')}`);
1407
+ console.log(` ${chalk.dim('Version:')} ${chalk.white('0.7.0')}`);
1408
+ console.log();
1549
1409
  });
1550
1410
 
1551
1411
  // -----------------
1552
1412
  // help
1553
1413
  // -----------------
1554
1414
 
1415
+ // Command groups for organized help
1416
+ const commandGroups = {
1417
+ 'Authentication': ['login', 'logout', 'register', 'auth', 'github', 'whoami', 'profile'],
1418
+ 'Shopping': ['products', 'search', 'view', 'open', 'buy', 'categories', 'category'],
1419
+ 'Cart & Orders': ['cart', 'add', 'checkout', 'orders'],
1420
+ 'Stores': ['sellers', 'seller', 'store', 'reviews', 'review', 'where'],
1421
+ 'AI Services': ['ai', 'credits', 'topup'],
1422
+ 'Personalization': ['alias', 'aliases', 'reward', 'rewards'],
1423
+ 'System': ['config', 'help', 'about', 'offers']
1424
+ };
1425
+
1555
1426
  // Custom help formatter
1556
1427
  function showHelp(commandName = null) {
1557
1428
  if (commandName) {
1558
1429
  // Show detailed help for specific command
1559
1430
  const cmd = program.commands.find(c => c.name() === commandName);
1560
1431
  if (!cmd) {
1561
- console.log(chalk.red(`Unknown command: ${commandName}`));
1432
+ console.log(chalk.red(`✗ Unknown command: ${commandName}`));
1562
1433
  console.log(chalk.dim(`Run 'tm help' to see all commands.`));
1563
1434
  return;
1564
1435
  }
1565
1436
 
1566
1437
  console.log();
1567
- console.log(chalk.green.bold(`tm ${cmd.name()}`), chalk.dim(`- ${cmd.description()}`));
1438
+ console.log(chalk.cyan('━'.repeat(50)));
1439
+ console.log(chalk.green.bold(` tm ${cmd.name()}`));
1440
+ console.log(chalk.white(` ${cmd.description()}`));
1441
+ console.log(chalk.cyan('━'.repeat(50)));
1568
1442
  console.log();
1569
1443
 
1570
1444
  // Show usage
1571
1445
  const args = cmd.registeredArguments || [];
1572
- const argsStr = args.map(a => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(' ');
1573
- console.log(chalk.yellow('Usage:'));
1574
- console.log(` tm ${cmd.name()}${argsStr ? ' ' + argsStr : ''} [options]`);
1446
+ const argsStr = args.map(a => a.required ? chalk.yellow(`<${a.name()}>`) : chalk.dim(`[${a.name()}]`)).join(' ');
1447
+ console.log(chalk.magenta.bold('Usage:'));
1448
+ console.log(` ${chalk.green('tm')} ${chalk.cyan(cmd.name())}${argsStr ? ' ' + argsStr : ''} ${chalk.dim('[options]')}`);
1575
1449
  console.log();
1576
1450
 
1577
1451
  // Show arguments
1578
1452
  if (args.length > 0) {
1579
- console.log(chalk.yellow('Arguments:'));
1453
+ console.log(chalk.magenta.bold('Arguments:'));
1580
1454
  args.forEach(a => {
1581
- console.log(` ${a.name().padEnd(15)} ${a.description || (a.required ? '(required)' : '(optional)')}`);
1455
+ const req = a.required ? chalk.yellow('(required)') : chalk.dim('(optional)');
1456
+ console.log(` ${chalk.cyan(a.name().padEnd(15))} ${req}`);
1582
1457
  });
1583
1458
  console.log();
1584
1459
  }
@@ -1586,61 +1461,97 @@ function showHelp(commandName = null) {
1586
1461
  // Show options
1587
1462
  const opts = cmd.options;
1588
1463
  if (opts.length > 0) {
1589
- console.log(chalk.yellow('Options:'));
1464
+ console.log(chalk.magenta.bold('Options:'));
1590
1465
  opts.forEach(o => {
1591
- const flags = o.flags.padEnd(25);
1592
- console.log(` ${flags} ${o.description}`);
1466
+ const flags = chalk.yellow(o.flags.padEnd(28));
1467
+ console.log(` ${flags} ${chalk.white(o.description)}`);
1593
1468
  });
1594
1469
  console.log();
1595
1470
  }
1596
1471
 
1597
1472
  // Show subcommands if any
1598
1473
  if (cmd.commands && cmd.commands.length > 0) {
1599
- console.log(chalk.yellow('Subcommands:'));
1474
+ console.log(chalk.magenta.bold('Subcommands:'));
1600
1475
  cmd.commands.sort((a, b) => a.name().localeCompare(b.name())).forEach(sub => {
1601
- const subArgs = (sub.registeredArguments || []).map(a => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(' ');
1602
- console.log(` ${(sub.name() + ' ' + subArgs).padEnd(25)} ${sub.description()}`);
1476
+ const subArgs = (sub.registeredArguments || []).map(a => a.required ? chalk.yellow(`<${a.name()}>`) : chalk.dim(`[${a.name()}]`)).join(' ');
1477
+ const cmdPart = chalk.cyan(sub.name()) + (subArgs ? ' ' + subArgs : '');
1478
+ console.log(` ${cmdPart.padEnd(40)} ${chalk.white(sub.description())}`);
1603
1479
  });
1604
1480
  console.log();
1605
- console.log(chalk.dim(`Run 'tm ${cmd.name()} <subcommand> help' for more details.`));
1481
+ console.log(chalk.dim(` 💡 Run 'tm ${cmd.name()} <subcommand> help' for more details.`));
1606
1482
  }
1483
+ console.log();
1607
1484
  return;
1608
1485
  }
1609
1486
 
1610
- // Show all commands
1487
+ // Show all commands grouped
1611
1488
  console.log();
1612
- console.log(chalk.green.bold('TerminalMarket CLI') + chalk.dim(' v0.6.3'));
1613
- console.log(chalk.dim('Marketplace for developers'));
1489
+ console.log(chalk.green.bold(' ╔════════════════════════════════════════╗'));
1490
+ console.log(chalk.green.bold(' ║') + chalk.white.bold(' TerminalMarket CLI ') + chalk.dim('v0.7.0') + chalk.green.bold(' ║'));
1491
+ console.log(chalk.green.bold(' ║') + chalk.dim(' Marketplace for developers') + chalk.green.bold(' ║'));
1492
+ console.log(chalk.green.bold(' ╚════════════════════════════════════════╝'));
1614
1493
  console.log();
1615
- console.log(chalk.yellow('Usage:'), 'tm <command> [options]');
1494
+ console.log(chalk.magenta.bold('Usage:'), chalk.green('tm'), chalk.cyan('<command>'), chalk.dim('[options]'));
1616
1495
  console.log();
1617
- console.log(chalk.yellow('Commands:'));
1618
1496
 
1619
- // Collect all commands with their args
1620
- const commands = [];
1497
+ // Collect all commands
1498
+ const allCommands = {};
1621
1499
  program.commands.forEach(cmd => {
1622
1500
  const args = (cmd.registeredArguments || []).map(a => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(' ');
1623
- commands.push({
1501
+ allCommands[cmd.name()] = {
1624
1502
  name: cmd.name(),
1625
1503
  args,
1626
1504
  desc: cmd.description(),
1627
1505
  hasSubcommands: cmd.commands && cmd.commands.length > 0
1628
- });
1506
+ };
1629
1507
  });
1630
1508
 
1631
- // Sort alphabetically
1632
- commands.sort((a, b) => a.name.localeCompare(b.name));
1509
+ // Display by groups
1510
+ const groupColors = {
1511
+ 'Authentication': chalk.blue,
1512
+ 'Shopping': chalk.green,
1513
+ 'Cart & Orders': chalk.yellow,
1514
+ 'Stores': chalk.magenta,
1515
+ 'AI Services': chalk.cyan,
1516
+ 'Personalization': chalk.white,
1517
+ 'System': chalk.gray
1518
+ };
1633
1519
 
1634
- // Display
1635
- commands.forEach(c => {
1636
- const cmdStr = c.args ? `${c.name} ${c.args}` : c.name;
1637
- const suffix = c.hasSubcommands ? chalk.dim(' (has subcommands)') : '';
1638
- console.log(` ${cmdStr.padEnd(30)} ${c.desc}${suffix}`);
1639
- });
1520
+ const groupIcons = {
1521
+ 'Authentication': '🔐',
1522
+ 'Shopping': '🛒',
1523
+ 'Cart & Orders': '📦',
1524
+ 'Stores': '🏪',
1525
+ 'AI Services': '🤖',
1526
+ 'Personalization': '⚙️',
1527
+ 'System': '💻'
1528
+ };
1640
1529
 
1641
- console.log();
1642
- console.log(chalk.dim(`Run 'tm <command> help' for detailed help on a command.`));
1643
- console.log(chalk.dim(`Run 'tm <command> <subcommand> help' for subcommand help.`));
1530
+ for (const [group, cmdNames] of Object.entries(commandGroups)) {
1531
+ const color = groupColors[group] || chalk.white;
1532
+ const icon = groupIcons[group] || '•';
1533
+
1534
+ console.log(color.bold(`${icon} ${group}`));
1535
+
1536
+ // Sort commands in group
1537
+ const groupCmds = cmdNames
1538
+ .filter(name => allCommands[name])
1539
+ .map(name => allCommands[name])
1540
+ .sort((a, b) => a.name.localeCompare(b.name));
1541
+
1542
+ groupCmds.forEach(c => {
1543
+ const cmdName = chalk.cyan(c.name);
1544
+ const cmdArgs = c.args ? chalk.yellow(` ${c.args}`) : '';
1545
+ const cmdStr = (c.name + (c.args ? ' ' + c.args : '')).padEnd(28);
1546
+ const suffix = c.hasSubcommands ? chalk.dim(' ⊕') : '';
1547
+ console.log(` ${cmdName}${cmdArgs}${' '.repeat(Math.max(0, 28 - c.name.length - (c.args?.length || 0)))} ${chalk.dim(c.desc)}${suffix}`);
1548
+ });
1549
+ console.log();
1550
+ }
1551
+
1552
+ console.log(chalk.cyan('━'.repeat(50)));
1553
+ console.log(chalk.dim(` 💡 Run '`) + chalk.cyan('tm help <command>') + chalk.dim(`' for detailed help`));
1554
+ console.log(chalk.dim(` ⊕ = has subcommands`));
1644
1555
  console.log();
1645
1556
  }
1646
1557
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalmarket",
3
- "version": "0.6.5",
3
+ "version": "0.7.1",
4
4
  "description": "TerminalMarket CLI — marketplace for developers (client for terminalmarket.app)",
5
5
  "bin": {
6
6
  "tm": "./bin/tm.js"
package/src/format.js CHANGED
@@ -1,21 +1,466 @@
1
1
  import chalk from "chalk";
2
2
 
3
- export function printTable(rows, columns) {
3
+ // Box drawing characters
4
+ const BOX = {
5
+ topLeft: '┌',
6
+ topRight: '┐',
7
+ bottomLeft: '└',
8
+ bottomRight: '┘',
9
+ horizontal: '─',
10
+ vertical: '│',
11
+ leftT: '├',
12
+ rightT: '┤',
13
+ topT: '┬',
14
+ bottomT: '┴',
15
+ cross: '┼',
16
+ };
17
+
18
+ // Print a beautiful box header
19
+ export function printHeader(title, subtitle = null) {
20
+ const width = 50;
21
+ const line = BOX.horizontal.repeat(width - 2);
22
+
23
+ console.log();
24
+ console.log(chalk.green(`${BOX.topLeft}${line}${BOX.topRight}`));
25
+
26
+ const titlePadded = title.padStart(Math.floor((width - 2 + title.length) / 2)).padEnd(width - 2);
27
+ console.log(chalk.green(BOX.vertical) + chalk.white.bold(titlePadded) + chalk.green(BOX.vertical));
28
+
29
+ if (subtitle) {
30
+ const subPadded = subtitle.padStart(Math.floor((width - 2 + subtitle.length) / 2)).padEnd(width - 2);
31
+ console.log(chalk.green(BOX.vertical) + chalk.dim(subPadded) + chalk.green(BOX.vertical));
32
+ }
33
+
34
+ console.log(chalk.green(`${BOX.bottomLeft}${line}${BOX.bottomRight}`));
35
+ console.log();
36
+ }
37
+
38
+ // Print a divider line
39
+ export function printDivider(char = '─', color = chalk.dim) {
40
+ console.log(color(char.repeat(50)));
41
+ }
42
+
43
+ // Print success message
44
+ export function printSuccess(message) {
45
+ console.log(chalk.green('✓ ') + chalk.white(message));
46
+ }
47
+
48
+ // Print error message
49
+ export function printError(message) {
50
+ console.log(chalk.red('✗ ') + chalk.white(message));
51
+ }
52
+
53
+ // Print warning message
54
+ export function printWarning(message) {
55
+ console.log(chalk.yellow('⚠ ') + chalk.white(message));
56
+ }
57
+
58
+ // Print info message
59
+ export function printInfo(message) {
60
+ console.log(chalk.cyan('ℹ ') + chalk.white(message));
61
+ }
62
+
63
+ // Print a key-value pair
64
+ export function printField(label, value, labelColor = chalk.dim) {
65
+ console.log(` ${labelColor(label + ':')} ${chalk.white(value)}`);
66
+ }
67
+
68
+ // Print empty state
69
+ export function printEmpty(message, hint = null) {
70
+ console.log();
71
+ console.log(chalk.dim(' ' + message));
72
+ if (hint) {
73
+ console.log(chalk.dim(' 💡 ' + hint));
74
+ }
75
+ console.log();
76
+ }
77
+
78
+ // Beautiful table with borders
79
+ export function printTable(rows, columns, options = {}) {
80
+ const { title, showIndex = false, compact = false } = options;
81
+
4
82
  if (!rows?.length) {
5
- console.log(chalk.gray("No results."));
83
+ printEmpty("No results found.", "Try a different search or filter.");
6
84
  return;
7
85
  }
86
+
87
+ // Calculate column widths
8
88
  const widths = {};
9
89
  for (const col of columns) {
10
- widths[col.key] = Math.max(col.title.length, ...rows.map(r => String(r[col.key] ?? "").length));
90
+ widths[col.key] = Math.max(
91
+ col.title.length,
92
+ ...rows.map(r => String(r[col.key] ?? "").length)
93
+ );
94
+ }
95
+
96
+ if (showIndex) {
97
+ widths._index = Math.max(1, String(rows.length).length);
98
+ }
99
+
100
+ // Build header
101
+ const headerParts = [];
102
+ if (showIndex) {
103
+ headerParts.push(chalk.dim('#'.padEnd(widths._index)));
104
+ }
105
+ for (const col of columns) {
106
+ headerParts.push(chalk.cyan.bold(col.title.padEnd(widths[col.key])));
107
+ }
108
+
109
+ // Print title if provided
110
+ if (title) {
111
+ console.log();
112
+ console.log(chalk.green.bold(` ${title}`));
113
+ console.log();
114
+ }
115
+
116
+ // Print header
117
+ console.log(' ' + headerParts.join(' '));
118
+
119
+ // Print separator
120
+ const sepParts = [];
121
+ if (showIndex) {
122
+ sepParts.push(chalk.dim('─'.repeat(widths._index)));
123
+ }
124
+ for (const col of columns) {
125
+ sepParts.push(chalk.dim('─'.repeat(widths[col.key])));
126
+ }
127
+ console.log(' ' + sepParts.join('──'));
128
+
129
+ // Print rows
130
+ rows.forEach((r, index) => {
131
+ const rowParts = [];
132
+ if (showIndex) {
133
+ rowParts.push(chalk.dim(String(index + 1).padEnd(widths._index)));
134
+ }
135
+
136
+ for (const col of columns) {
137
+ let value = String(r[col.key] ?? "").padEnd(widths[col.key]);
138
+
139
+ // Apply color based on column type
140
+ if (col.key === 'price' || col.key === 'total') {
141
+ value = chalk.green(value);
142
+ } else if (col.key === 'status') {
143
+ const status = r[col.key]?.toLowerCase() || '';
144
+ if (status === 'delivered' || status === 'active' || status === 'paid') {
145
+ value = chalk.green(value);
146
+ } else if (status === 'shipped' || status === 'processing') {
147
+ value = chalk.cyan(value);
148
+ } else if (status === 'pending') {
149
+ value = chalk.yellow(value);
150
+ } else if (status === 'cancelled' || status === 'suspended') {
151
+ value = chalk.red(value);
152
+ } else {
153
+ value = chalk.white(value);
154
+ }
155
+ } else if (col.key === 'verified') {
156
+ value = r[col.key] === '✓' ? chalk.green(value) : chalk.dim(value);
157
+ } else if (col.key === 'name' || col.key === 'title') {
158
+ value = chalk.white.bold(value);
159
+ } else if (col.key === 'id' || col.key === 'slug') {
160
+ value = chalk.dim(value);
161
+ } else if (col.key === 'category') {
162
+ value = chalk.magenta(value);
163
+ } else {
164
+ value = chalk.white(value);
165
+ }
166
+
167
+ rowParts.push(value);
168
+ }
169
+
170
+ console.log(' ' + rowParts.join(' '));
171
+ });
172
+
173
+ // Print footer with count
174
+ console.log();
175
+ console.log(chalk.dim(` Showing ${rows.length} result${rows.length !== 1 ? 's' : ''}`));
176
+ }
177
+
178
+ // Print product card
179
+ export function printProductCard(p) {
180
+ const width = 50;
181
+ const line = '─'.repeat(width);
182
+
183
+ console.log();
184
+ console.log(chalk.cyan(line));
185
+ console.log();
186
+
187
+ // Name
188
+ console.log(chalk.white.bold(' ' + (p.name || 'Unknown Product')));
189
+
190
+ // Short description
191
+ if (p.shortDescription) {
192
+ console.log(chalk.dim(' ' + p.shortDescription));
193
+ }
194
+ console.log();
195
+
196
+ // Price (big and prominent)
197
+ if (p.price) {
198
+ console.log(chalk.green.bold(` $${p.price}`));
199
+ console.log();
200
+ }
201
+
202
+ // Details
203
+ if (p.description) {
204
+ console.log(chalk.white(' ' + p.description));
205
+ console.log();
206
+ }
207
+
208
+ // Meta info
209
+ console.log(chalk.dim(' ─────────────────────────────'));
210
+
211
+ printField('ID', p.productId || p.id || '-');
212
+ if (p.slug) printField('Slug', p.slug);
213
+ if (p.category) printField('Category', p.category);
214
+
215
+ const serviceType = p.serviceType || 'global';
216
+ const typeLabel = serviceType === 'global' ? '🌍 Global' :
217
+ serviceType === 'national' ? '🏳️ National' : '📍 Local';
218
+ printField('Type', typeLabel);
219
+
220
+ if (serviceType === 'local' && p.serviceCity) {
221
+ printField('City', p.serviceCity);
222
+ }
223
+ if ((serviceType === 'local' || serviceType === 'national') && p.serviceCountry) {
224
+ printField('Country', p.serviceCountry);
225
+ }
226
+
227
+ if (p.storeId || p.sellerId) {
228
+ printField('Store', p.storeId || p.sellerId);
229
+ }
230
+
231
+ console.log();
232
+ console.log(chalk.cyan(line));
233
+
234
+ // Actions hint
235
+ console.log();
236
+ console.log(chalk.dim(' 💡 Quick actions:'));
237
+ console.log(chalk.dim(' tm add ' + (p.productId || p.id)) + chalk.dim(' — add to cart'));
238
+ console.log(chalk.dim(' tm buy ' + (p.slug || p.productId || p.id)) + chalk.dim(' — buy directly'));
239
+ console.log();
240
+ }
241
+
242
+ // Print cart with totals
243
+ export function printCart(items, total) {
244
+ if (!items?.length) {
245
+ printEmpty("Your cart is empty.", "Add items with: tm add <product-id>");
246
+ return;
11
247
  }
12
- const header = columns.map(c => chalk.bold(String(c.title).padEnd(widths[c.key]))).join(" ");
13
- console.log(header);
14
- console.log(columns.map(c => "-".repeat(widths[c.key])).join(" "));
15
- for (const r of rows) {
16
- const line = columns.map(c => String(r[c.key] ?? "").padEnd(widths[c.key])).join(" ");
17
- console.log(line);
248
+
249
+ console.log();
250
+ console.log(chalk.green.bold(' 🛒 Your Cart'));
251
+ console.log();
252
+
253
+ items.forEach((item, i) => {
254
+ const subtotal = (item.price || 0) * (item.quantity || 1);
255
+
256
+ console.log(chalk.white.bold(` ${i + 1}. ${item.name || `Product #${item.productId}`}`));
257
+ console.log(` ${chalk.dim('Qty:')} ${chalk.cyan(item.quantity)} ${chalk.dim('×')} ${chalk.green('$' + item.price)} ${chalk.dim('=')} ${chalk.green.bold('$' + subtotal.toFixed(2))}`);
258
+ console.log();
259
+ });
260
+
261
+ console.log(chalk.dim(' ─────────────────────────────────────────'));
262
+ console.log();
263
+ console.log(` ${chalk.white('Total:')} ${chalk.green.bold('$' + total.toFixed(2))}`);
264
+ console.log();
265
+ console.log(chalk.dim(' 💡 tm checkout — proceed to payment'));
266
+ console.log();
267
+ }
268
+
269
+ // Print order history
270
+ export function printOrders(orders) {
271
+ if (!orders?.length) {
272
+ printEmpty("No orders yet.", "Start shopping with: tm products");
273
+ return;
274
+ }
275
+
276
+ console.log();
277
+ console.log(chalk.green.bold(' 📦 Order History'));
278
+ console.log();
279
+
280
+ orders.forEach((order) => {
281
+ const date = new Date(order.createdAt).toLocaleDateString();
282
+ const status = order.status?.toLowerCase() || 'pending';
283
+
284
+ // Status with color and icon
285
+ let statusDisplay;
286
+ if (status === 'delivered') {
287
+ statusDisplay = chalk.green('✓ Delivered');
288
+ } else if (status === 'shipped') {
289
+ statusDisplay = chalk.cyan('📦 Shipped');
290
+ } else if (status === 'paid') {
291
+ statusDisplay = chalk.blue('💳 Paid');
292
+ } else if (status === 'processing') {
293
+ statusDisplay = chalk.yellow('⏳ Processing');
294
+ } else if (status === 'cancelled') {
295
+ statusDisplay = chalk.red('✗ Cancelled');
296
+ } else {
297
+ statusDisplay = chalk.dim('○ ' + status);
298
+ }
299
+
300
+ console.log(chalk.white.bold(` ${order.orderNumber || '#' + order.id}`));
301
+ console.log(` ${chalk.dim('Date:')} ${date} ${chalk.dim('Total:')} ${chalk.green('$' + (order.total || 0))}`);
302
+ console.log(` ${statusDisplay}`);
303
+ console.log();
304
+ });
305
+ }
306
+
307
+ // Print store/seller card
308
+ export function printStoreCard(s) {
309
+ const width = 50;
310
+ const line = '─'.repeat(width);
311
+
312
+ console.log();
313
+ console.log(chalk.magenta(line));
314
+ console.log();
315
+
316
+ // Name with verified badge
317
+ const verified = s.verified ? chalk.green(' ✓') : '';
318
+ console.log(chalk.white.bold(' 🏪 ' + (s.name || s.storeName || 'Unknown Store')) + verified);
319
+
320
+ if (s.description || s.storeDescription) {
321
+ console.log(chalk.dim(' ' + (s.description || s.storeDescription)));
322
+ }
323
+ console.log();
324
+
325
+ // Rating if available
326
+ if (s.rating || s.averageRating) {
327
+ const rating = s.rating || s.averageRating;
328
+ const stars = formatStars(rating);
329
+ console.log(` ${chalk.yellow(stars)} ${chalk.dim(`(${rating.toFixed(1)})`)}`);
330
+ console.log();
331
+ }
332
+
333
+ // Details
334
+ console.log(chalk.dim(' ─────────────────────────────'));
335
+
336
+ if (s.id) printField('ID', s.id);
337
+ if (s.slug) printField('Slug', s.slug);
338
+
339
+ const serviceType = s.serviceType || 'global';
340
+ const typeLabel = serviceType === 'global' ? '🌍 Global' :
341
+ serviceType === 'national' ? '🏳️ National' : '📍 Local';
342
+ printField('Type', typeLabel);
343
+
344
+ if (s.baseCity) printField('City', s.baseCity);
345
+ if (s.baseCountry) printField('Country', s.baseCountry);
346
+
347
+ if (s.categories?.length) {
348
+ printField('Categories', s.categories.join(', '));
18
349
  }
350
+
351
+ console.log();
352
+ console.log(chalk.magenta(line));
353
+
354
+ // Actions hint
355
+ console.log();
356
+ console.log(chalk.dim(' 💡 tm products --store ' + s.id) + chalk.dim(' — view products'));
357
+ console.log(chalk.dim(' 💡 tm reviews ' + s.id) + chalk.dim(' — see reviews'));
358
+ console.log();
359
+ }
360
+
361
+ // Print sellers list
362
+ export function printSellers(sellers) {
363
+ if (!sellers?.length) {
364
+ printEmpty("No stores found.", "Try adjusting your filters.");
365
+ return;
366
+ }
367
+
368
+ console.log();
369
+ console.log(chalk.green.bold(' 🏪 Stores'));
370
+ console.log();
371
+
372
+ sellers.forEach((s) => {
373
+ const verified = s.verified ? chalk.green(' ✓') : '';
374
+ const serviceType = s.serviceType || 'global';
375
+ const typeIcon = serviceType === 'global' ? '🌍' :
376
+ serviceType === 'national' ? '🏳️' : '📍';
377
+
378
+ console.log(chalk.white.bold(' ' + (s.name || s.storeName)) + verified);
379
+ console.log(` ${chalk.dim('Slug:')} ${chalk.cyan(s.slug)} ${chalk.dim('Type:')} ${typeIcon} ${serviceType}`);
380
+
381
+ if (s.baseCity || s.baseCountry) {
382
+ const location = [s.baseCity, s.baseCountry].filter(Boolean).join(', ');
383
+ console.log(` ${chalk.dim('Location:')} ${location}`);
384
+ }
385
+ console.log();
386
+ });
387
+
388
+ console.log(chalk.dim(` Showing ${sellers.length} store${sellers.length !== 1 ? 's' : ''}`));
389
+ console.log();
390
+ }
391
+
392
+ // Print reviews
393
+ export function printReviews(reviews, averageRating) {
394
+ console.log();
395
+
396
+ if (averageRating !== undefined) {
397
+ console.log(chalk.green.bold(' ⭐ Store Rating'));
398
+ console.log(` ${chalk.yellow(formatStars(averageRating))} ${chalk.dim(`(${averageRating.toFixed(1)} average)`)}`);
399
+ console.log();
400
+ }
401
+
402
+ if (!reviews?.length) {
403
+ printEmpty("No reviews yet.", "Be the first to review: tm review <store-id> <1-5>");
404
+ return;
405
+ }
406
+
407
+ console.log(chalk.green.bold(' 📝 Reviews'));
408
+ console.log();
409
+
410
+ reviews.forEach((r) => {
411
+ const stars = formatStars(r.rating);
412
+ const date = new Date(r.createdAt).toLocaleDateString();
413
+
414
+ console.log(` ${chalk.yellow(stars)} ${chalk.dim('— ' + date)}`);
415
+ if (r.comment) {
416
+ console.log(chalk.white(' "' + r.comment + '"'));
417
+ }
418
+ console.log();
419
+ });
420
+ }
421
+
422
+ // Print AI models list
423
+ export function printAIModels(models, categories = []) {
424
+ if (!models?.length) {
425
+ printEmpty("No AI models available.", "Check back later for new models.");
426
+ return;
427
+ }
428
+
429
+ console.log();
430
+ console.log(chalk.cyan.bold(' 🤖 AI Models'));
431
+ console.log();
432
+
433
+ // Group by category if categories provided
434
+ const grouped = {};
435
+ models.forEach(m => {
436
+ const cat = m.categoryName || 'Other';
437
+ if (!grouped[cat]) grouped[cat] = [];
438
+ grouped[cat].push(m);
439
+ });
440
+
441
+ for (const [category, catModels] of Object.entries(grouped)) {
442
+ console.log(chalk.magenta.bold(` ${category}`));
443
+ console.log();
444
+
445
+ catModels.forEach(m => {
446
+ console.log(chalk.white.bold(` ${m.name}`));
447
+ console.log(` ${chalk.dim(m.description || 'No description')}`);
448
+ console.log(` ${chalk.dim('Provider:')} ${m.provider} ${chalk.dim('Cost:')} ${chalk.green(m.creditsPerRun + ' credits')}`);
449
+ console.log(` ${chalk.dim('Run:')} ${chalk.cyan('tm ai run ' + m.slug)}`);
450
+ console.log();
451
+ });
452
+ }
453
+ }
454
+
455
+ // Print credits balance
456
+ export function printCredits(balance) {
457
+ console.log();
458
+ console.log(chalk.cyan.bold(' 💳 AI Credits'));
459
+ console.log();
460
+ console.log(` ${chalk.white('Balance:')} ${chalk.green.bold(balance + ' credits')}`);
461
+ console.log();
462
+ console.log(chalk.dim(' 💡 tm ai topup <amount> — add more credits'));
463
+ console.log();
19
464
  }
20
465
 
21
466
  export function pickProductFields(p) {