@darksol/terminal 0.5.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "DARKSOL Terminal — unified CLI for all DARKSOL services. Market intel, trading, oracle, casino, and more.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -274,13 +274,20 @@ export function cli(argv) {
274
274
  cards
275
275
  .command('order')
276
276
  .description('Order a prepaid card')
277
- .requiredOption('-p, --provider <name>', 'Card provider')
277
+ .requiredOption('-p, --provider <name>', 'Card provider (swype/mpc/reward)')
278
278
  .requiredOption('-a, --amount <usd>', 'Card amount in USD')
279
- .action((opts) => cardsOrder(opts.provider, parseFloat(opts.amount)));
279
+ .requiredOption('-e, --email <address>', 'Delivery email for card activation link')
280
+ .option('-t, --ticker <coin>', 'Payment crypto (default: usdc)')
281
+ .option('-n, --network <net>', 'Payment network (default: base)')
282
+ .action((opts) => cardsOrder(opts.provider, parseFloat(opts.amount), {
283
+ email: opts.email,
284
+ ticker: opts.ticker,
285
+ network: opts.network,
286
+ }));
280
287
 
281
288
  cards
282
- .command('status <orderId>')
283
- .description('Check order status')
289
+ .command('status <tradeId>')
290
+ .description('Check order status by trade ID')
284
291
  .action((id) => cardsStatus(id));
285
292
 
286
293
  // ═══════════════════════════════════════
@@ -4,27 +4,39 @@ import { theme } from '../ui/theme.js';
4
4
  import { spinner, kvDisplay, error, info, table } from '../ui/components.js';
5
5
  import { showSection } from '../ui/banner.js';
6
6
 
7
- const getURL = () => getServiceURL('cards') || 'https://acp.darksol.net/cards';
7
+ const BASE = () => getServiceURL('cards') || 'https://acp.darksol.net';
8
8
 
9
9
  export async function cardsCatalog() {
10
10
  const spin = spinner('Loading card catalog...').start();
11
11
  try {
12
- const data = await fetchJSON(`${getURL()}/api/cards/catalog`);
12
+ const data = await fetchJSON(`${BASE()}/api/cards/catalog`);
13
13
  spin.succeed('Catalog loaded');
14
14
 
15
15
  showSection('PREPAID CARDS');
16
16
  const cards = data.providers || data;
17
17
  if (Array.isArray(cards)) {
18
18
  const rows = cards.map(c => [
19
- theme.gold(c.name),
20
- c.network || 'Visa/MC',
21
- c.denominations?.join(', ') || 'Various',
19
+ theme.gold(c.name || c.id),
20
+ c.brand || c.network || 'Visa/MC',
21
+ c.currency || 'USD',
22
22
  c.region || 'Global',
23
23
  ]);
24
- table(['Provider', 'Network', 'Amounts', 'Region'], rows);
24
+ table(['Provider', 'Brand', 'Currency', 'Region'], rows);
25
25
  } else {
26
26
  kvDisplay(Object.entries(cards).map(([k, v]) => [k, String(v)]));
27
27
  }
28
+
29
+ // Show pricing tiers if available
30
+ if (data.pricing?.tiers) {
31
+ console.log('');
32
+ showSection('PRICING');
33
+ const tRows = data.pricing.tiers.map(t => [
34
+ `$${t.denomination}`,
35
+ `$${t.youPay}`,
36
+ ]);
37
+ table(['Card Value', 'You Pay'], tRows);
38
+ console.log(theme.dim(` ${data.pricing.markup || '3% service fee'} + ${data.pricing.issuanceFee || '~3% provider fee'}`));
39
+ }
28
40
  } catch (err) {
29
41
  spin.fail('Catalog failed');
30
42
  error(err.message);
@@ -32,33 +44,68 @@ export async function cardsCatalog() {
32
44
  }
33
45
  }
34
46
 
35
- export async function cardsOrder(provider, amount) {
47
+ export async function cardsOrder(provider, amount, opts = {}) {
48
+ if (!opts.email) {
49
+ error('Email is required for card delivery. Use --email <address>');
50
+ info('Example: darksol cards order -p swype -a 100 --email you@example.com');
51
+ return;
52
+ }
53
+
36
54
  const spin = spinner('Processing card order...').start();
37
55
  try {
38
- const data = await fetchJSON(`${getURL()}/api/cards/order`, {
56
+ const body = {
57
+ provider,
58
+ amount: Number(amount),
59
+ email: opts.email,
60
+ };
61
+ // Optional: custom crypto + network
62
+ if (opts.ticker) body.tickerFrom = opts.ticker;
63
+ if (opts.network) body.networkFrom = opts.network;
64
+
65
+ const data = await fetchJSON(`${BASE()}/api/cards/order`, {
39
66
  method: 'POST',
40
67
  headers: { 'Content-Type': 'application/json' },
41
- body: JSON.stringify({ provider, amount }),
68
+ body: JSON.stringify(body),
42
69
  });
43
70
  spin.succeed('Order placed');
44
71
 
45
- showSection('CARD ORDER');
46
- kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
72
+ if (data.order) {
73
+ showSection('CARD ORDER');
74
+ kvDisplay([
75
+ ['Trade ID', theme.gold(data.order.tradeId)],
76
+ ['Status', data.order.status],
77
+ ['Card', `$${data.order.cardAmount} ${data.order.currency} ${data.order.brand}`],
78
+ ['Provider', data.order.provider],
79
+ ['Pay', `${data.order.amountCrypto} ${data.order.ticker?.toUpperCase()} (${data.order.network})`],
80
+ ['To Address', data.order.paymentAddress],
81
+ ...(data.order.paymentMemo ? [['Memo', data.order.paymentMemo]] : []),
82
+ ['Delivery', opts.email],
83
+ ]);
84
+ console.log('');
85
+ console.log(theme.accent(` ${data.order.message}`));
86
+ console.log('');
87
+ info(`Check status: darksol cards status ${data.order.tradeId}`);
88
+ } else {
89
+ kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
90
+ }
47
91
  } catch (err) {
48
92
  spin.fail('Order failed');
49
93
  error(err.message);
50
- info('The cards order API may not be live yet. Check: https://acp.darksol.net/cards');
51
94
  }
52
95
  }
53
96
 
54
- export async function cardsStatus(orderId) {
97
+ export async function cardsStatus(tradeId) {
55
98
  const spin = spinner('Checking order...').start();
56
99
  try {
57
- const data = await fetchJSON(`${getURL()}/api/cards/status?orderId=${orderId}`);
100
+ const data = await fetchJSON(`${BASE()}/api/cards/status?tradeId=${tradeId}`);
58
101
  spin.succeed('Status loaded');
59
102
 
60
- showSection(`CARD ORDER — ${orderId}`);
61
- kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
103
+ showSection(`CARD ORDER — ${tradeId}`);
104
+ if (data.order) {
105
+ kvDisplay(Object.entries(data.order).map(([k, v]) => [k, String(v)]));
106
+ } else {
107
+ kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
108
+ }
62
109
  } catch (err) {
63
110
  spin.fail('Status check failed');
64
111
  error(err.message);
@@ -173,6 +173,51 @@ export async function handleMenuSelect(id, value, item, ws) {
173
173
  });
174
174
  return {};
175
175
 
176
+ case 'cards_action':
177
+ if (value === 'order') {
178
+ ws.sendMenu('cards_provider', '◆ Select Provider', [
179
+ { value: 'swype', label: 'Swype', desc: 'Mastercard · Global' },
180
+ { value: 'mpc', label: 'MPC', desc: 'Mastercard · US Only' },
181
+ { value: 'reward', label: 'Reward', desc: 'Visa · US Only' },
182
+ { value: 'back', label: '← Back', desc: '' },
183
+ ]);
184
+ return {};
185
+ }
186
+ if (value === 'status') {
187
+ ws.sendPrompt('cards_status_id', 'Trade ID:', {});
188
+ return {};
189
+ }
190
+ return {};
191
+
192
+ case 'cards_provider':
193
+ if (value === 'back') return {};
194
+ // Store provider, ask for amount
195
+ ws.sendMenu('cards_amount', `◆ Card Amount (${value})`, [
196
+ { value: '10', label: '$10', desc: 'Pay ~$10.60' },
197
+ { value: '25', label: '$25', desc: 'Pay ~$26.50' },
198
+ { value: '50', label: '$50', desc: 'Pay ~$53' },
199
+ { value: '100', label: '$100', desc: 'Pay ~$106' },
200
+ { value: '250', label: '$250', desc: 'Pay ~$265' },
201
+ { value: '500', label: '$500', desc: 'Pay ~$530' },
202
+ { value: '1000', label: '$1,000', desc: 'Pay ~$1,060' },
203
+ { value: 'back', label: '← Back', desc: '' },
204
+ ].map(i => ({ ...i, meta: { provider: value } })));
205
+ return {};
206
+
207
+ case 'cards_amount':
208
+ if (value === 'back') return {};
209
+ // Store provider+amount, ask for email
210
+ ws.sendPrompt('cards_email', 'Delivery email (card activation link will be sent here):', {
211
+ provider: item?.meta?.provider || 'swype',
212
+ amount: value,
213
+ });
214
+ return {};
215
+
216
+ case 'cards_crypto':
217
+ if (value === 'back') return {};
218
+ // Execute the order
219
+ return await executeCardOrder(item?.meta || {}, ws);
220
+
176
221
  case 'agent_action':
177
222
  if (value === 'start') {
178
223
  const { listWallets } = await import('../wallet/keystore.js');
@@ -275,6 +320,31 @@ export async function handlePromptResponse(id, value, meta, ws) {
275
320
  return {};
276
321
  }
277
322
 
323
+ if (id === 'cards_status_id') {
324
+ if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
325
+ return await showCardStatus(value.trim(), ws);
326
+ }
327
+
328
+ if (id === 'cards_email') {
329
+ if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
330
+ const provider = meta.provider || 'swype';
331
+ const amount = meta.amount || '100';
332
+ const email = value.trim();
333
+
334
+ // Ask for crypto selection
335
+ ws.sendMenu('cards_crypto', `◆ Pay With (${provider} $${amount} → ${email})`, [
336
+ { value: 'usdc_base', label: 'USDC on Base', desc: 'Default · fast & cheap', meta: { provider, amount, email, ticker: 'usdc', network: 'base' } },
337
+ { value: 'usdc_eth', label: 'USDC on Ethereum', desc: 'Higher fees', meta: { provider, amount, email, ticker: 'usdc', network: 'eth' } },
338
+ { value: 'usdt_trc20', label: 'USDT on Tron', desc: 'TRC-20', meta: { provider, amount, email, ticker: 'usdt', network: 'trc20' } },
339
+ { value: 'btc', label: 'Bitcoin', desc: 'BTC mainnet', meta: { provider, amount, email, ticker: 'btc', network: 'mainnet' } },
340
+ { value: 'eth', label: 'Ethereum', desc: 'ETH mainnet', meta: { provider, amount, email, ticker: 'eth', network: 'eth' } },
341
+ { value: 'sol', label: 'Solana', desc: 'SOL', meta: { provider, amount, email, ticker: 'sol', network: 'sol' } },
342
+ { value: 'xmr', label: 'Monero', desc: 'XMR', meta: { provider, amount, email, ticker: 'xmr', network: 'xmr' } },
343
+ { value: 'default', label: 'Default (USDC/Base)', desc: 'Let API choose', meta: { provider, amount, email } },
344
+ ]);
345
+ return {};
346
+ }
347
+
278
348
  if (id === 'agent_signer_password') {
279
349
  const wallet = meta.wallet;
280
350
  if (!wallet || !value) {
@@ -359,6 +429,8 @@ export async function handleCommand(cmd, ws) {
359
429
  return await cmdConfig(ws);
360
430
  case 'oracle':
361
431
  return await cmdOracle(args, ws);
432
+ case 'cards':
433
+ return await cmdCards(args, ws);
362
434
  case 'casino':
363
435
  return await cmdCasino(args, ws);
364
436
  case 'facilitator':
@@ -911,6 +983,140 @@ async function cmdHistory(args, ws) {
911
983
  // ══════════════════════════════════════════════════
912
984
  // SERVICE COMMANDS (thin wrappers)
913
985
  // ══════════════════════════════════════════════════
986
+ // ══════════════════════════════════════════════════
987
+ // CARDS (interactive ordering)
988
+ // ══════════════════════════════════════════════════
989
+ const CARDS_API = 'https://acp.darksol.net/api/cards';
990
+
991
+ async function cmdCards(args, ws) {
992
+ const sub = (args[0] || '').toLowerCase();
993
+
994
+ if (sub === 'status' && args[1]) {
995
+ return await showCardStatus(args[1], ws);
996
+ }
997
+
998
+ // Show catalog + order menu
999
+ ws.sendLine(`${ANSI.gold} ◆ PREPAID CARDS${ANSI.reset}`);
1000
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1001
+
1002
+ try {
1003
+ const resp = await fetch(`${CARDS_API}/catalog`);
1004
+ const data = await resp.json();
1005
+
1006
+ if (data.providers) {
1007
+ for (const p of data.providers) {
1008
+ ws.sendLine(` ${ANSI.gold}${p.name}${ANSI.reset} ${ANSI.dim}${p.brand} · ${p.region}${ANSI.reset}`);
1009
+ }
1010
+ }
1011
+ if (data.pricing?.tiers) {
1012
+ ws.sendLine('');
1013
+ ws.sendLine(` ${ANSI.darkGold}Pricing${ANSI.reset} ${ANSI.dim}${data.pricing.markup} + ${data.pricing.issuanceFee}${ANSI.reset}`);
1014
+ const tierStr = data.pricing.tiers.map(t => `$${t.denomination}→$${t.youPay}`).join(' ');
1015
+ ws.sendLine(` ${ANSI.dim}${tierStr}${ANSI.reset}`);
1016
+ }
1017
+ ws.sendLine('');
1018
+ } catch {
1019
+ ws.sendLine(` ${ANSI.dim}Could not load catalog${ANSI.reset}`);
1020
+ ws.sendLine('');
1021
+ }
1022
+
1023
+ ws.sendMenu('cards_action', '◆ Prepaid Cards', [
1024
+ { value: 'order', label: '💳 Order Card', desc: 'Start a new order' },
1025
+ { value: 'status', label: '🔍 Check Status', desc: 'Track existing order' },
1026
+ { value: 'back', label: '← Back', desc: '' },
1027
+ ]);
1028
+ return {};
1029
+ }
1030
+
1031
+ async function showCardStatus(tradeId, ws) {
1032
+ try {
1033
+ const resp = await fetch(`${CARDS_API}/status?tradeId=${tradeId}`);
1034
+ const ct = resp.headers.get('content-type') || '';
1035
+ if (!ct.includes('json')) {
1036
+ ws.sendLine(` ${ANSI.red}✗ Invalid response from status endpoint${ANSI.reset}`);
1037
+ return {};
1038
+ }
1039
+ const data = await resp.json();
1040
+ ws.sendLine(`${ANSI.gold} ◆ ORDER STATUS — ${tradeId}${ANSI.reset}`);
1041
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1042
+ const order = data.order || data;
1043
+ for (const [k, v] of Object.entries(order)) {
1044
+ if (v !== null && v !== undefined) {
1045
+ ws.sendLine(` ${ANSI.darkGold}${k}${ANSI.reset} ${ANSI.white}${v}${ANSI.reset}`);
1046
+ }
1047
+ }
1048
+ ws.sendLine('');
1049
+ } catch (err) {
1050
+ ws.sendLine(` ${ANSI.red}✗ ${err.message}${ANSI.reset}`);
1051
+ ws.sendLine('');
1052
+ }
1053
+ return {};
1054
+ }
1055
+
1056
+ async function executeCardOrder(orderMeta, ws) {
1057
+ const { provider, amount, email, ticker, network } = orderMeta;
1058
+ ws.sendLine(` ${ANSI.dim}Placing order...${ANSI.reset}`);
1059
+
1060
+ try {
1061
+ const body = { provider: provider || 'swype', amount: Number(amount), email };
1062
+ if (ticker) body.tickerFrom = ticker;
1063
+ if (network) body.networkFrom = network;
1064
+
1065
+ const resp = await fetch(`${CARDS_API}/order`, {
1066
+ method: 'POST',
1067
+ headers: { 'Content-Type': 'application/json' },
1068
+ body: JSON.stringify(body),
1069
+ });
1070
+ const ct = resp.headers.get('content-type') || '';
1071
+ if (!ct.includes('json')) {
1072
+ const text = await resp.text();
1073
+ ws.sendLine(` ${ANSI.red}✗ API returned non-JSON: ${text.substring(0, 80)}${ANSI.reset}`);
1074
+ ws.sendLine('');
1075
+ return {};
1076
+ }
1077
+ const data = await resp.json();
1078
+
1079
+ if (!data.success || !data.order) {
1080
+ ws.sendLine(` ${ANSI.red}✗ Order failed: ${data.error || JSON.stringify(data)}${ANSI.reset}`);
1081
+ ws.sendLine('');
1082
+ return {};
1083
+ }
1084
+
1085
+ const o = data.order;
1086
+ ws.sendLine('');
1087
+ ws.sendLine(`${ANSI.gold} ◆ ORDER PLACED${ANSI.reset}`);
1088
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1089
+ ws.sendLine(` ${ANSI.darkGold}Trade ID${ANSI.reset} ${ANSI.gold}${o.tradeId}${ANSI.reset}`);
1090
+ ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${ANSI.white}${o.status}${ANSI.reset}`);
1091
+ ws.sendLine(` ${ANSI.darkGold}Card${ANSI.reset} ${ANSI.white}$${o.cardAmount} ${o.currency} ${o.brand}${ANSI.reset}`);
1092
+ ws.sendLine(` ${ANSI.darkGold}Provider${ANSI.reset} ${ANSI.white}${o.provider}${ANSI.reset}`);
1093
+ ws.sendLine(` ${ANSI.darkGold}Delivery${ANSI.reset} ${ANSI.white}${email}${ANSI.reset}`);
1094
+ ws.sendLine('');
1095
+ ws.sendLine(` ${ANSI.green}PAY EXACTLY:${ANSI.reset}`);
1096
+ ws.sendLine(` ${ANSI.gold}${o.amountCrypto} ${(o.ticker || '').toUpperCase()}${ANSI.reset} ${ANSI.dim}(${o.network})${ANSI.reset}`);
1097
+ ws.sendLine('');
1098
+ ws.sendLine(` ${ANSI.darkGold}To Address:${ANSI.reset}`);
1099
+ const addr = o.paymentAddress;
1100
+ ws.sendLine(` ${ANSI.dim}┌${'─'.repeat(addr.length + 4)}┐${ANSI.reset}`);
1101
+ ws.sendLine(` ${ANSI.dim}│ ${ANSI.gold}${addr}${ANSI.dim} │${ANSI.reset}`);
1102
+ ws.sendLine(` ${ANSI.dim}└${'─'.repeat(addr.length + 4)}┘${ANSI.reset}`);
1103
+ if (o.paymentMemo) {
1104
+ ws.sendLine(` ${ANSI.darkGold}Memo:${ANSI.reset} ${ANSI.white}${o.paymentMemo}${ANSI.reset}`);
1105
+ }
1106
+ ws.sendLine('');
1107
+ ws.sendLine(` ${ANSI.dim}${o.message}${ANSI.reset}`);
1108
+ ws.sendLine(` ${ANSI.dim}Check status: cards status ${o.tradeId}${ANSI.reset}`);
1109
+ ws.sendLine('');
1110
+ } catch (err) {
1111
+ ws.sendLine(` ${ANSI.red}✗ ${err.message}${ANSI.reset}`);
1112
+ ws.sendLine('');
1113
+ }
1114
+ return {};
1115
+ }
1116
+
1117
+ // ══════════════════════════════════════════════════
1118
+ // ORACLE
1119
+ // ══════════════════════════════════════════════════
914
1120
  async function cmdOracle(args, ws) {
915
1121
  try {
916
1122
  const resp = await fetch('https://acp.darksol.net/oracle');
package/src/web/server.js CHANGED
@@ -169,6 +169,7 @@ export async function startWebShell(opts = {}) {
169
169
  { value: 'portfolio', label: '📊 Portfolio', desc: 'Multi-chain balances' },
170
170
  { value: 'market', label: '📈 Market', desc: 'Price + liquidity intel' },
171
171
  { value: 'mail', label: '📧 Mail', desc: 'AgentMail status/inbox' },
172
+ { value: 'cards', label: '💳 Cards', desc: 'Order prepaid Visa/MC' },
172
173
  { value: 'oracle', label: '🎲 Oracle', desc: 'Randomness service' },
173
174
  { value: 'casino', label: '🎰 Casino', desc: 'Service status' },
174
175
  { value: 'facilitator', label: '💸 Facilitator', desc: 'x402 health' },
@@ -308,6 +309,7 @@ function getHelp() {
308
309
  ['', ''],
309
310
  ['', `${gold}SERVICES${reset}`],
310
311
  ['market <token>', 'Market intel & data'],
312
+ ['cards', 'Order prepaid Visa/MC cards'],
311
313
  ['mail status', 'AgentMail status'],
312
314
  ['mail inbox', 'Check email inbox'],
313
315
  ['oracle roll', 'On-chain random oracle'],
@@ -16,7 +16,7 @@ const A = {
16
16
 
17
17
  const COMMANDS = [
18
18
  'ai', 'price', 'watch', 'gas', 'portfolio', 'history', 'market',
19
- 'wallet', 'send', 'receive', 'agent', 'mail', 'keys', 'oracle', 'casino',
19
+ 'wallet', 'send', 'receive', 'agent', 'cards', 'mail', 'keys', 'oracle', 'casino',
20
20
  'facilitator', 'config', 'logs', 'help', 'clear', 'banner', 'exit',
21
21
  ];
22
22