terminalmarket 0.7.3 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,20 @@ The official command-line interface for [TerminalMarket](https://terminalmarket.
4
4
 
5
5
  ## Installation
6
6
 
7
+ ### npm (requires Node.js)
8
+
7
9
  ```bash
8
10
  npm install -g terminalmarket
9
11
  ```
10
12
 
13
+ ### Standalone binary (no Node.js needed)
14
+
15
+ ```bash
16
+ curl -fsSL https://terminalmarket.app/install.sh | sh
17
+ ```
18
+
19
+ This installs `tm` into `~/.local/bin`.
20
+
11
21
  ## Usage
12
22
 
13
23
  ```bash
@@ -19,22 +29,25 @@ tm <command> [options]
19
29
  ### Authentication
20
30
 
21
31
  ```bash
22
- tm register <email> <password> # Create a new account
23
- tm login <email> <password> # Login to your account
32
+ tm register <email> [password] # Create a new account
33
+ tm login <email> [password] # Login to your account
24
34
  tm logout # Logout
25
35
  tm whoami # Show current user info
26
36
  tm me # Alias for whoami
37
+ tm auth github # Login with GitHub (opens browser)
38
+ tm github # Shortcut for GitHub auth
27
39
  ```
28
40
 
29
41
  ### Profile
30
42
 
31
43
  ```bash
32
44
  tm profile # View your profile
33
- tm profile name "John Doe" # Update your name
34
- tm profile phone "+1234567890" # Update phone
35
- tm profile address "123 Main St" # Update address
36
- tm profile city "Berlin" # Update city
37
- tm profile country "DE" # Update country
45
+ tm profile view # View your profile
46
+ tm profile set name "John Doe" # Update your name
47
+ tm profile set phone "+1234567890" # Update phone
48
+ tm profile set address "123 Main" # Update address
49
+ tm profile set city "Berlin" # Update city
50
+ tm profile set country "DE" # Update country
38
51
  ```
39
52
 
40
53
  ### Shopping
@@ -87,32 +100,29 @@ tm credits # Check credits (shortcut)
87
100
  tm topup <amount> # Add credits (shortcut)
88
101
  ```
89
102
 
90
- Example:
103
+ ### Aliases & Rewards
104
+
91
105
  ```bash
92
- tm ai list # See available models
93
- tm ai topup 10 # Add $10 credits
94
- tm ai run text-rewrite "Fix this text" # Run AI model
95
- tm ai credits # Check remaining balance
106
+ tm alias list # List your aliases
107
+ tm alias add <name> <command> # Create alias
108
+ tm alias remove <name> # Remove alias
109
+ tm aliases # Shortcut for alias list
110
+
111
+ tm reward list # List reward rules
112
+ tm reward add <product> <pushes> # Auto-order after N pushes
113
+ tm reward remove <id> # Remove reward rule
114
+ tm rewards # Shortcut for reward list
96
115
  ```
97
116
 
98
- ### Service Types
99
-
100
- Products have different service types:
101
- - Global — SaaS, digital products, worldwide delivery
102
- - National — Country-wide delivery/services
103
- - Local — City-specific services (food delivery, coworking, etc.)
104
-
105
117
  ### Categories & Offers
106
118
 
107
119
  ```bash
108
120
  tm categories # List all categories
109
121
  tm category <slug> # List products in category
110
122
  tm offers # List all offers
111
- tm offers --product <id> # Filter by product
112
- tm offers --seller <id> # Filter by seller
113
123
  ```
114
124
 
115
- Available categories include:
125
+ Available categories:
116
126
  - `coffee` — Specialty coffee for developers
117
127
  - `lunch` — Meal subscriptions & delivery
118
128
  - `snacks` — Healthy snacks & energy packs
@@ -134,6 +144,7 @@ tm config set api <url> # Set API endpoint
134
144
  ```bash
135
145
  tm about # About TerminalMarket
136
146
  tm help # Show help
147
+ tm help <command> # Help for specific command
137
148
  tm --version # Show version
138
149
  ```
139
150
 
@@ -152,49 +163,11 @@ tm review 1 5 "Great coffee, fast delivery!"
152
163
 
153
164
  # View your order history
154
165
  tm orders
155
- ```
156
166
 
157
- ## Seller Tiers
158
-
159
- | Tier | Price | Products | Commission | Features |
160
- |------|-------|----------|------------|----------|
161
- | Free | $0/mo | 5 | 5% | Basic analytics |
162
- | Basic | $29/mo | 50 | 4% | Priority support |
163
- | Premium | $99/mo | 1000 | 2.5% | Stripe Connect, Terminal Checkout |
164
-
165
- ## API Endpoints Used
166
-
167
- ### Public
168
- - `GET /api/products` — List products
169
- - `GET /api/products/:id` — Get product details
170
- - `GET /api/products/slug/:slug` — Get product by slug
171
- - `GET /api/products/category/:category` — Products by category
172
- - `GET /api/products/search` — Search products
173
- - `GET /api/categories` — List categories
174
- - `GET /api/sellers` — List sellers
175
- - `GET /api/sellers/:slug` — Get seller details
176
- - `GET /api/offers` — List offers
177
- - `GET /api/stores/:id/reviews` — Get store reviews
178
- - `GET /api/stores/:id/rating` — Get store rating
179
-
180
- ### Authenticated
181
- - `POST /api/auth/register` — Create account
182
- - `POST /api/auth/login` — Login
183
- - `POST /api/auth/logout` — Logout
184
- - `GET /api/auth/status` — Check auth status
185
- - `PATCH /api/profile` — Update profile
186
- - `GET /api/cart` — Get cart
187
- - `POST /api/cart/add` — Add to cart
188
- - `POST /api/cart/remove` — Remove from cart
189
- - `POST /api/cart/clear` — Clear cart
190
- - `GET /api/orders` — Get orders
191
- - `POST /api/stores/:id/reviews` — Leave review
192
- - `GET /api/credits` — Get AI credits balance
193
- - `POST /api/credits/topup` — Create Stripe checkout for credits
194
- - `POST /api/ai/run/:model` — Run AI model
195
- - `GET /api/ai/history` — Get AI usage history
196
- - `POST /api/clicks` — Track clicks
197
- - `POST /api/intents` — Create purchase intent
167
+ # Use AI services
168
+ tm ai topup 10
169
+ tm ai run text-rewrite "Fix this text"
170
+ ```
198
171
 
199
172
  ## Configuration
200
173
 
@@ -204,6 +177,20 @@ The CLI stores configuration in `~/.config/terminalmarket/config.json`:
204
177
  - `sessionCookie`: Session cookie for authentication
205
178
  - `user`: Cached user info
206
179
 
180
+ ## Building Binaries
181
+
182
+ See [INSTALL_BINARIES.md](./INSTALL_BINARIES.md) for instructions on building standalone binaries.
183
+
184
+ ```bash
185
+ npm ci
186
+ npm run build:bin
187
+ ```
188
+
189
+ This produces binaries in `dist/`:
190
+ - `tm-linux-x64`
191
+ - `tm-macos-x64`
192
+ - `tm-macos-arm64`
193
+
207
194
  ## License
208
195
 
209
196
  MIT
package/bin/tm.js CHANGED
@@ -4,15 +4,29 @@ import { Command } from "commander";
4
4
  import chalk from "chalk";
5
5
  import open from "open";
6
6
  import readline from "readline";
7
+ import { readFileSync } from "fs";
8
+ import { fileURLToPath } from "url";
9
+ import { dirname, join } from "path";
7
10
 
8
11
  import { apiGet, apiPost, apiDelete, apiPatch } from "../src/api.js";
9
- import { getApiBase, setApiBase, getUser, setUser, clearUser, clearSession } from "../src/config.js";
12
+ import { getApiBase, setApiBase, getUser, setUser, clearUser, clearSession, isFirstRun, markFirstRunComplete, setLocation, getLocation } from "../src/config.js";
10
13
  import {
11
14
  printTable, pickProductFields, pickSellerFields, pickOfferFields, containsQuery, formatStars,
12
15
  printHeader, printDivider, printSuccess, printError, printWarning, printInfo, printField, printEmpty,
13
16
  printProductCard, printCart, printOrders, printStoreCard, printSellers, printReviews, printAIModels, printCredits
14
17
  } from "../src/format.js";
18
+ import { showWelcome, showBox, showError, showSuccess, showStatusBar, showNextSteps, createSpinner, stopSpinner } from "../src/ui.js";
15
19
 
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+ const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
23
+ const VERSION = pkg.version;
24
+
25
+ if (isFirstRun() && process.argv.length <= 2) {
26
+ showWelcome(VERSION);
27
+ markFirstRunComplete();
28
+ process.exit(0);
29
+ }
16
30
  // Helper for hidden password input
17
31
  function askPassword(prompt = "Password: ") {
18
32
  return new Promise((resolve) => {
@@ -63,7 +77,7 @@ const program = new Command();
63
77
  program
64
78
  .name("tm")
65
79
  .description("TerminalMarket CLI — marketplace for developers")
66
- .version("0.7.0");
80
+ .version(VERSION);
67
81
 
68
82
  // -----------------
69
83
  // config
@@ -903,61 +917,6 @@ program
903
917
  }
904
918
  });
905
919
 
906
- // -----------------
907
- // where command (location search)
908
- // -----------------
909
- program
910
- .command("where <city>")
911
- .description("Find products and sellers in a city")
912
- .option("-c, --country <country>", "Filter by country")
913
- .action(async (city, opts) => {
914
- try {
915
- console.log(chalk.bold(`Services in ${city}`));
916
- console.log("");
917
-
918
- // Search products by city
919
- const params = new URLSearchParams();
920
- params.set("city", city);
921
- if (opts.country) params.set("country", opts.country);
922
-
923
- const products = await apiGet(`/products?${params.toString()}`);
924
- const localProducts = (products || []).filter(p =>
925
- p.serviceType === "local" &&
926
- p.serviceCity?.toLowerCase() === city.toLowerCase()
927
- );
928
-
929
- if (localProducts.length > 0) {
930
- console.log(chalk.cyan("Products:"));
931
- localProducts.forEach(p => {
932
- console.log(` ${p.name} - $${p.price} (${p.category})`);
933
- });
934
- console.log("");
935
- }
936
-
937
- // Search sellers by city
938
- const sellers = await apiGet("/sellers");
939
- const localSellers = (sellers || []).filter(s =>
940
- s.serviceType === "local" &&
941
- s.baseCity?.toLowerCase() === city.toLowerCase()
942
- );
943
-
944
- if (localSellers.length > 0) {
945
- console.log(chalk.cyan("Sellers:"));
946
- localSellers.forEach(s => {
947
- console.log(` ${s.name} (${s.slug})`);
948
- });
949
- }
950
-
951
- if (localProducts.length === 0 && localSellers.length === 0) {
952
- console.log(chalk.yellow(`No local services found in ${city}.`));
953
- console.log(chalk.dim("Try: tm products --city <city>"));
954
- }
955
- } catch (e) {
956
- console.error(chalk.red(e?.message || String(e)));
957
- process.exitCode = 1;
958
- }
959
- });
960
-
961
920
  // -----------------
962
921
  // categories
963
922
  // -----------------
@@ -995,6 +954,7 @@ program
995
954
  .option("--city <city>", "Filter by city (for local services)")
996
955
  .option("--country <country>", "Filter by country")
997
956
  .action(async (opts) => {
957
+ const spinner = createSpinner("Fetching products...");
998
958
  try {
999
959
  const limit = Math.max(1, Math.min(200, Number.parseInt(opts.limit, 10) || 20));
1000
960
 
@@ -1015,6 +975,8 @@ program
1015
975
  }
1016
976
 
1017
977
  const products = await apiGet(url);
978
+ stopSpinner(true, `Found ${products.length} products`);
979
+
1018
980
  const rows = (products || []).slice(0, limit).map(pickProductFields);
1019
981
  printTable(rows, [
1020
982
  { key: "id", title: "id" },
@@ -1024,7 +986,13 @@ program
1024
986
  { key: "category", title: "category" },
1025
987
  { key: "serviceType", title: "type" },
1026
988
  ]);
989
+
990
+ showNextSteps([
991
+ { cmd: "tm view <id>", desc: "view product details" },
992
+ { cmd: "tm add <id>", desc: "add to cart" }
993
+ ]);
1027
994
  } catch (e) {
995
+ stopSpinner(false, "Failed to load products");
1028
996
  console.error(chalk.red(e?.message || String(e)));
1029
997
  process.exitCode = 1;
1030
998
  }
@@ -1438,8 +1406,14 @@ program
1438
1406
  console.log();
1439
1407
  console.log(chalk.dim(' ─────────────────────────────────────────────'));
1440
1408
  console.log();
1409
+ console.log(chalk.white(' Install:'));
1410
+ console.log();
1411
+ console.log(` ${chalk.dim('npm:')} ${chalk.green('npm i -g terminalmarket')}`);
1412
+ console.log(` ${chalk.dim('curl:')} ${chalk.cyan('curl -fsSL https://terminalmarket.app/install.sh | sh')}`);
1413
+ console.log();
1414
+ console.log(chalk.dim(' ─────────────────────────────────────────────'));
1415
+ console.log();
1441
1416
  console.log(` ${chalk.dim('Website:')} ${chalk.cyan('https://terminalmarket.app')}`);
1442
- console.log(` ${chalk.dim('Install:')} ${chalk.green('npm i -g terminalmarket')}`);
1443
1417
  console.log(` ${chalk.dim('Version:')} ${chalk.white('0.7.2')}`);
1444
1418
  console.log();
1445
1419
  });
@@ -1456,7 +1430,7 @@ const commandGroups = {
1456
1430
  'Stores': ['sellers', 'seller', 'store', 'reviews', 'review', 'where'],
1457
1431
  'AI Services': ['ai', 'credits', 'topup'],
1458
1432
  'Personalization': ['alias', 'aliases', 'reward', 'rewards'],
1459
- 'System': ['config', 'help', 'about', 'offers']
1433
+ 'System': ['start', 'config', 'help', 'about', 'offers']
1460
1434
  };
1461
1435
 
1462
1436
  // Custom help formatter
@@ -1527,7 +1501,7 @@ function showHelp(commandName = null) {
1527
1501
 
1528
1502
  console.log();
1529
1503
  console.log(chalk.green.bold(' ╔' + line + '╗'));
1530
- console.log(chalk.green.bold(' ║') + chalk.white.bold(pad(' TerminalMarket CLI', W - 8)) + chalk.dim(' v0.7.2 ') + chalk.green.bold('║'));
1504
+ console.log(chalk.green.bold(' ║') + chalk.white.bold(pad(' TerminalMarket CLI', W - 8)) + chalk.dim(` v${VERSION} `) + chalk.green.bold('║'));
1531
1505
  console.log(chalk.green.bold(' ║') + chalk.dim(pad(' Marketplace for developers', W)) + chalk.green.bold('║'));
1532
1506
  console.log(chalk.green.bold(' ╚' + line + '╝'));
1533
1507
  console.log();
@@ -1598,6 +1572,122 @@ function showHelp(commandName = null) {
1598
1572
  console.log();
1599
1573
  }
1600
1574
 
1575
+ program
1576
+ .command("where [city]")
1577
+ .description("Set or view your location (for local services)")
1578
+ .action(async (city) => {
1579
+ if (city) {
1580
+ setLocation(city);
1581
+ showSuccess(`Location set to ${city}`);
1582
+ showNextSteps([
1583
+ { cmd: "tm products", desc: "browse products in " + city },
1584
+ { cmd: "tm search lunch", desc: "find lunch options" }
1585
+ ]);
1586
+ } else {
1587
+ const location = getLocation();
1588
+ if (location?.city) {
1589
+ console.log();
1590
+ console.log(chalk.green(" 📍 Location: ") + chalk.white.bold(location.city));
1591
+ console.log();
1592
+ console.log(chalk.dim(" 💡 tm where <city> — change location"));
1593
+ console.log();
1594
+ } else {
1595
+ console.log();
1596
+ console.log(chalk.dim(" 📍 Location not set"));
1597
+ console.log();
1598
+ console.log(chalk.dim(" 💡 Set location for local services:"));
1599
+ console.log(chalk.cyan(" tm where berlin"));
1600
+ console.log(chalk.cyan(" tm where prague"));
1601
+ console.log();
1602
+ }
1603
+ }
1604
+ });
1605
+
1606
+ program
1607
+ .command("start")
1608
+ .alias("tour")
1609
+ .description("Interactive onboarding tour")
1610
+ .action(async () => {
1611
+ const inquirer = await import("inquirer").then(m => m.default);
1612
+
1613
+ console.log();
1614
+ console.log(chalk.green.bold(" Welcome to TerminalMarket! 🚀"));
1615
+ console.log(chalk.dim(" Let's get you started with a quick tour."));
1616
+ console.log();
1617
+
1618
+ const { city } = await inquirer.prompt([
1619
+ {
1620
+ type: "input",
1621
+ name: "city",
1622
+ message: "What city are you in?",
1623
+ default: "Berlin"
1624
+ }
1625
+ ]);
1626
+
1627
+ setLocation(city);
1628
+ console.log(chalk.green(` ✓ Location set to ${city}`));
1629
+ console.log();
1630
+
1631
+ const { action } = await inquirer.prompt([
1632
+ {
1633
+ type: "list",
1634
+ name: "action",
1635
+ message: "What would you like to explore?",
1636
+ choices: [
1637
+ { name: "🍽 Food & Drinks", value: "food" },
1638
+ { name: "🤖 AI Services", value: "ai" },
1639
+ { name: "🏢 Coworking Spaces", value: "coworking" },
1640
+ { name: "🛠 Developer Tools", value: "digital" },
1641
+ { name: "📦 Browse all products", value: "all" }
1642
+ ]
1643
+ }
1644
+ ]);
1645
+
1646
+ console.log();
1647
+
1648
+ const spinner = createSpinner("Fetching products...");
1649
+
1650
+ try {
1651
+ let products;
1652
+ if (action === "all") {
1653
+ products = await apiGet("/products");
1654
+ } else if (action === "ai") {
1655
+ stopSpinner(true, "AI models");
1656
+ const models = await apiGet("/ai/models");
1657
+ printAIModels(models);
1658
+ showNextSteps([
1659
+ { cmd: "tm ai topup 10", desc: "add $10 credits" },
1660
+ { cmd: "tm ai run <model> <prompt>", desc: "run an AI model" }
1661
+ ]);
1662
+ return;
1663
+ } else {
1664
+ products = await apiGet(`/products/category/${action}`);
1665
+ }
1666
+
1667
+ stopSpinner(true, `Found ${products.length} products`);
1668
+
1669
+ if (products.length > 0) {
1670
+ const rows = products.slice(0, 5).map(pickProductFields);
1671
+ printTable(rows, [
1672
+ { key: "id", title: "ID" },
1673
+ { key: "name", title: "Name" },
1674
+ { key: "price", title: "Price" },
1675
+ { key: "category", title: "Category" }
1676
+ ]);
1677
+ }
1678
+
1679
+ showNextSteps([
1680
+ { cmd: "tm view <id>", desc: "view product details" },
1681
+ { cmd: "tm add <id>", desc: "add to cart" },
1682
+ { cmd: "tm search <query>", desc: "search products" }
1683
+ ]);
1684
+
1685
+ } catch (e) {
1686
+ stopSpinner(false, "Failed to load");
1687
+ printError(e?.message || String(e));
1688
+ }
1689
+ });
1690
+
1601
1691
  program
1602
1692
  .command("help [command]")
1603
1693
  .description("Show help for a command")
@@ -1608,5 +1698,6 @@ program
1608
1698
  program.parse(process.argv);
1609
1699
 
1610
1700
  if (!process.argv.slice(2).length) {
1701
+ showStatusBar();
1611
1702
  showHelp();
1612
1703
  }
package/package.json CHANGED
@@ -1,34 +1,66 @@
1
1
  {
2
2
  "name": "terminalmarket",
3
- "version": "0.7.3",
4
- "description": "TerminalMarket CLI — marketplace for developers (client for terminalmarket.app)",
3
+ "version": "0.8.1",
4
+ "description": "TerminalMarket CLI — a curated marketplace for developers & founders (client for terminalmarket.app)",
5
5
  "bin": {
6
- "tm": "./bin/tm.js"
6
+ "tm": "bin/tm.js"
7
7
  },
8
8
  "type": "module",
9
9
  "keywords": [
10
10
  "cli",
11
- "marketplace",
12
11
  "terminal",
12
+ "marketplace",
13
13
  "developers",
14
+ "founders",
14
15
  "food",
15
16
  "subscription",
16
17
  "saas",
17
- "coworking"
18
+ "coworking",
19
+ "tools",
20
+ "telegram",
21
+ "discord"
18
22
  ],
19
23
  "author": "TerminalMarket",
20
24
  "license": "MIT",
21
25
  "repository": {
22
26
  "type": "git",
23
- "url": "https://github.com/terminalmarket/cli"
27
+ "url": "git+https://github.com/terminalmarket/cli.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/terminalmarket/cli/issues"
24
31
  },
25
32
  "homepage": "https://terminalmarket.app",
33
+ "files": [
34
+ "bin/",
35
+ "src/",
36
+ "dist/",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "scripts": {
41
+ "bundle:cli": "esbuild bin/tm.js --bundle --platform=node --format=cjs --outfile=dist/tm.cjs",
42
+ "build:bin": "npm run bundle:cli && pkg dist/tm.cjs --targets node18-linux-x64,node18-macos-x64,node18-macos-arm64,node18-win-x64 --output dist/tm",
43
+ "build:bin:linux": "npm run bundle:cli && pkg dist/tm.cjs --targets node18-linux-x64 --output dist/tm",
44
+ "build:bin:mac": "npm run bundle:cli && pkg dist/tm.cjs --targets node18-macos-x64,node18-macos-arm64 --output dist/tm",
45
+ "build:bin:win": "npm run bundle:cli && pkg dist/tm.cjs --targets node18-win-x64 --output dist/tm",
46
+ "clean": "rm -rf dist",
47
+ "prepack": "npm run clean",
48
+ "test:smoke": "node bin/tm.js --help"
49
+ },
50
+ "devDependencies": {
51
+ "esbuild": "^0.25.0",
52
+ "pkg": "^5.8.1"
53
+ },
26
54
  "dependencies": {
55
+ "boxen": "^7.1.1",
27
56
  "chalk": "^5.3.0",
57
+ "cli-table3": "^0.6.5",
28
58
  "commander": "^12.1.0",
29
59
  "conf": "^12.0.0",
60
+ "inquirer": "^9.2.15",
30
61
  "node-fetch": "^3.3.2",
31
- "open": "^9.1.0"
62
+ "open": "^9.1.0",
63
+ "ora": "^8.0.1"
32
64
  },
33
65
  "engines": {
34
66
  "node": ">=18.0.0"
package/src/config.js CHANGED
@@ -36,3 +36,19 @@ export function setUser(user) {
36
36
  export function clearUser() {
37
37
  conf.delete("user");
38
38
  }
39
+
40
+ export function isFirstRun() {
41
+ return !conf.get("firstRunComplete", false);
42
+ }
43
+
44
+ export function markFirstRunComplete() {
45
+ conf.set("firstRunComplete", true);
46
+ }
47
+
48
+ export function getLocation() {
49
+ return conf.get("location", null);
50
+ }
51
+
52
+ export function setLocation(city, country = null) {
53
+ conf.set("location", { city, country });
54
+ }
package/src/ui.js ADDED
@@ -0,0 +1,186 @@
1
+ import chalk from "chalk";
2
+ import boxen from "boxen";
3
+ import ora from "ora";
4
+ import Table from "cli-table3";
5
+ import { getUser, getLocation } from "./config.js";
6
+
7
+ let currentSpinner = null;
8
+
9
+ export function createSpinner(text) {
10
+ if (currentSpinner) {
11
+ currentSpinner.stop();
12
+ }
13
+ currentSpinner = ora({
14
+ text,
15
+ color: "green",
16
+ spinner: "dots"
17
+ }).start();
18
+ return currentSpinner;
19
+ }
20
+
21
+ export function stopSpinner(success = true, text = null) {
22
+ if (currentSpinner) {
23
+ if (success) {
24
+ currentSpinner.succeed(text);
25
+ } else {
26
+ currentSpinner.fail(text);
27
+ }
28
+ currentSpinner = null;
29
+ }
30
+ }
31
+
32
+ export function showWelcome(version) {
33
+ const content = `${chalk.green.bold("TerminalMarket CLI")} ${chalk.dim(`v${version}`)}
34
+ ${chalk.dim("A curated marketplace for developers & founders")}
35
+
36
+ ${chalk.white("Try one of these:")}
37
+ ${chalk.cyan("tm products")} ${chalk.dim("— browse products")}
38
+ ${chalk.cyan("tm search lunch")} ${chalk.dim("— search for lunch deals")}
39
+ ${chalk.cyan("tm categories")} ${chalk.dim("— explore categories")}
40
+ ${chalk.cyan("tm ai list")} ${chalk.dim("— browse AI models")}
41
+ ${chalk.cyan("tm start")} ${chalk.dim("— interactive tour")}
42
+
43
+ ${chalk.dim("Tip: this is a real marketplace — products open real checkout pages.")}`;
44
+
45
+ console.log();
46
+ console.log(boxen(content, {
47
+ padding: 1,
48
+ margin: 0,
49
+ borderStyle: "round",
50
+ borderColor: "green"
51
+ }));
52
+ console.log();
53
+ }
54
+
55
+ export function showBox(title, content, options = {}) {
56
+ const { borderColor = "green", padding = 1 } = options;
57
+
58
+ const text = title
59
+ ? `${chalk.bold(title)}\n\n${content}`
60
+ : content;
61
+
62
+ console.log();
63
+ console.log(boxen(text, {
64
+ padding,
65
+ borderStyle: "round",
66
+ borderColor
67
+ }));
68
+ console.log();
69
+ }
70
+
71
+ export function showError(message, hint = null) {
72
+ let content = chalk.red.bold("Error: ") + chalk.white(message);
73
+ if (hint) {
74
+ content += "\n\n" + chalk.dim("💡 " + hint);
75
+ }
76
+
77
+ console.log();
78
+ console.log(boxen(content, {
79
+ padding: 1,
80
+ borderStyle: "round",
81
+ borderColor: "red"
82
+ }));
83
+ console.log();
84
+ }
85
+
86
+ export function showSuccess(message) {
87
+ console.log();
88
+ console.log(boxen(chalk.green("✓ ") + chalk.white(message), {
89
+ padding: 1,
90
+ borderStyle: "round",
91
+ borderColor: "green"
92
+ }));
93
+ console.log();
94
+ }
95
+
96
+ export function showStatusBar() {
97
+ const user = getUser();
98
+ const location = getLocation();
99
+
100
+ const parts = [];
101
+
102
+ if (location?.city) {
103
+ parts.push(chalk.cyan("📍 " + location.city));
104
+ }
105
+
106
+ if (user) {
107
+ parts.push(chalk.magenta("👤 " + (user.username || user.email?.split("@")[0] || "User")));
108
+ } else {
109
+ parts.push(chalk.dim("👤 Guest"));
110
+ }
111
+
112
+ if (parts.length > 0) {
113
+ console.log(chalk.dim(" " + parts.join(" │ ")));
114
+ console.log();
115
+ }
116
+ }
117
+
118
+ export function showNextSteps(steps) {
119
+ console.log();
120
+ console.log(chalk.dim(" Next:"));
121
+ steps.forEach(step => {
122
+ console.log(chalk.dim(" → ") + chalk.cyan(step.cmd) + chalk.dim(" — " + step.desc));
123
+ });
124
+ console.log();
125
+ }
126
+
127
+ export function createTable(headers, options = {}) {
128
+ const { compact = false } = options;
129
+
130
+ return new Table({
131
+ head: headers.map(h => chalk.cyan.bold(h)),
132
+ style: {
133
+ head: [],
134
+ border: ["dim"],
135
+ compact
136
+ },
137
+ chars: compact ? {
138
+ "top": "", "top-mid": "", "top-left": "", "top-right": "",
139
+ "bottom": "", "bottom-mid": "", "bottom-left": "", "bottom-right": "",
140
+ "left": " ", "left-mid": "", "mid": "", "mid-mid": "",
141
+ "right": "", "right-mid": "", "middle": " │ "
142
+ } : undefined
143
+ });
144
+ }
145
+
146
+ export function printTableData(data, columns) {
147
+ if (!data?.length) {
148
+ console.log(chalk.dim(" No results found."));
149
+ return;
150
+ }
151
+
152
+ const headers = columns.map(c => c.title);
153
+ const table = createTable(headers, { compact: true });
154
+
155
+ data.forEach(row => {
156
+ const cells = columns.map(col => {
157
+ let value = row[col.key] ?? "";
158
+
159
+ if (col.key === "price" || col.key === "total") {
160
+ value = chalk.green(value);
161
+ } else if (col.key === "name" || col.key === "title") {
162
+ value = chalk.white.bold(value);
163
+ } else if (col.key === "id") {
164
+ value = chalk.dim(value);
165
+ } else if (col.key === "status") {
166
+ const status = String(value).toLowerCase();
167
+ if (status === "delivered" || status === "active") {
168
+ value = chalk.green(value);
169
+ } else if (status === "pending") {
170
+ value = chalk.yellow(value);
171
+ } else if (status === "cancelled") {
172
+ value = chalk.red(value);
173
+ }
174
+ }
175
+
176
+ return value;
177
+ });
178
+
179
+ table.push(cells);
180
+ });
181
+
182
+ console.log();
183
+ console.log(table.toString());
184
+ console.log();
185
+ console.log(chalk.dim(` Showing ${data.length} result${data.length !== 1 ? "s" : ""}`));
186
+ }