@darksol/terminal 0.9.2 → 0.10.0

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.
@@ -10,6 +10,7 @@ import { homedir } from 'os';
10
10
  import { spawn } from 'child_process';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { getConfiguredModel, getModelSelectionMeta, getProviderDefaultModel } from '../llm/models.js';
13
+ import { pokerNewGame, pokerAction, pokerStatus, pokerHistory } from '../services/poker.js';
13
14
 
14
15
  // ══════════════════════════════════════════════════
15
16
  // CHAT LOG PERSISTENCE
@@ -207,6 +208,38 @@ export async function handleMenuSelect(id, value, item, ws) {
207
208
  ].map(i => ({ ...i, meta: { provider: value } })));
208
209
  return {};
209
210
 
211
+ case 'poker_mode':
212
+ if (value === 'back') return {};
213
+ if (value === 'status') return await cmdPoker(['status'], ws);
214
+ if (value === 'history') return await cmdPoker(['history'], ws);
215
+ return await startPokerWebGame(value === 'real' ? 'real' : 'free', ws);
216
+
217
+ case 'poker_action': {
218
+ const gameId = item?.meta?.gameId || pokerSessions.get(ws);
219
+ if (!gameId) {
220
+ ws.sendLine(` ${ANSI.red}No active poker game${ANSI.reset}`);
221
+ ws.sendLine('');
222
+ return {};
223
+ }
224
+
225
+ const before = pokerStatus(gameId);
226
+ const status = await pokerAction(gameId, value);
227
+ pokerSessions.set(ws, status.id);
228
+ await renderPokerState(status, ws, { previous: before });
229
+ if (status.street !== 'finished' && status.currentActor === 'player') {
230
+ sendPokerActionMenu(status, ws);
231
+ } else if (status.street === 'finished') {
232
+ sendPokerPostHandMenu(status, ws);
233
+ }
234
+ return {};
235
+ }
236
+
237
+ case 'poker_post_hand':
238
+ if (value === 'again-free') return await startPokerWebGame('free', ws);
239
+ if (value === 'again-real') return await startPokerWebGame('real', ws);
240
+ if (value === 'history') return await cmdPoker(['history'], ws);
241
+ return {};
242
+
210
243
  case 'cards_amount':
211
244
  if (value === 'back') return {};
212
245
  // Store provider+amount, ask for email
@@ -882,6 +915,8 @@ export async function handleCommand(cmd, ws) {
882
915
  return await cmdCards(args, ws);
883
916
  case 'casino':
884
917
  return await cmdCasino(args, ws);
918
+ case 'poker':
919
+ return await cmdPoker(args, ws);
885
920
  case 'facilitator':
886
921
  return await cmdFacilitator(args, ws);
887
922
  case 'send':
@@ -905,7 +940,7 @@ export async function handleCommand(cmd, ws) {
905
940
  return await cmdChatLogs(args, ws);
906
941
  default: {
907
942
  // Fuzzy: if it looks like natural language, route to AI
908
- const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca|order|card|prepaid|visa|mastercard|bet|coinflip|flip|dice|slots|hilo|gamble|play|casino|bridge|cross-chain|crosschain)\b/i;
943
+ const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca|order|card|prepaid|visa|mastercard|bet|coinflip|flip|dice|slots|hilo|gamble|play|casino|poker|holdem|bridge|cross-chain|crosschain)\b/i;
909
944
  if (nlKeywords.test(cmd)) {
910
945
  return await cmdAI(cmd.split(/\s+/), ws);
911
946
  }
@@ -1778,6 +1813,222 @@ async function cmdOracle(args, ws) {
1778
1813
  return {};
1779
1814
  }
1780
1815
 
1816
+ function pokerColor(card, text) {
1817
+ if (card === '??') return `${ANSI.dim}${text}${ANSI.reset}`;
1818
+ return card[1] === 'h' || card[1] === 'd'
1819
+ ? `${ANSI.red}${text}${ANSI.reset}`
1820
+ : `${ANSI.white}${text}${ANSI.reset}`;
1821
+ }
1822
+
1823
+ function pokerCardRows(cards, hidden = false) {
1824
+ const suitMap = { s: '♠', h: '♥', d: '♦', c: '♣' };
1825
+ const source = hidden ? ['??', '??'] : cards;
1826
+ const rows = ['', '', '', '', ''];
1827
+
1828
+ for (const card of source) {
1829
+ if (card === '??') {
1830
+ rows[0] += `${ANSI.dim}┌─────┐${ANSI.reset} `;
1831
+ rows[1] += `${ANSI.dim}│░░░░░│${ANSI.reset} `;
1832
+ rows[2] += `${ANSI.dim}│░░▓░░│${ANSI.reset} `;
1833
+ rows[3] += `${ANSI.dim}│░░░░░│${ANSI.reset} `;
1834
+ rows[4] += `${ANSI.dim}└─────┘${ANSI.reset} `;
1835
+ continue;
1836
+ }
1837
+
1838
+ const rank = card[0] === 'T' ? '10' : card[0];
1839
+ const suit = suitMap[card[1]];
1840
+ rows[0] += `${ANSI.dim}┌─────┐${ANSI.reset} `;
1841
+ rows[1] += `${ANSI.dim}│${ANSI.reset}${pokerColor(card, rank.padEnd(2, ' '))}${ANSI.dim} │${ANSI.reset} `;
1842
+ rows[2] += `${ANSI.dim}│ ${ANSI.reset}${pokerColor(card, suit)}${ANSI.dim} │${ANSI.reset} `;
1843
+ rows[3] += `${ANSI.dim}│ ${ANSI.reset}${pokerColor(card, rank.padStart(2, ' '))}${ANSI.dim}│${ANSI.reset} `;
1844
+ rows[4] += `${ANSI.dim}└─────┘${ANSI.reset} `;
1845
+ }
1846
+
1847
+ return rows;
1848
+ }
1849
+
1850
+ function sendPokerCards(label, cards, ws, hidden = false) {
1851
+ ws.sendLine(` ${ANSI.darkGold}${label}${ANSI.reset}`);
1852
+ for (const row of pokerCardRows(cards, hidden)) {
1853
+ ws.sendLine(` ${row}`);
1854
+ }
1855
+ }
1856
+
1857
+ function sendPokerActionMenu(status, ws) {
1858
+ ws.sendMenu('poker_action', `◆ Poker Actions (${status.street})`, status.availableActions.map((action) => ({
1859
+ value: action,
1860
+ label: action,
1861
+ desc: action === 'all-in' ? 'Commit the rest of your stack' : '',
1862
+ meta: { gameId: status.id },
1863
+ })));
1864
+ }
1865
+
1866
+ function sendPokerPostHandMenu(status, ws) {
1867
+ ws.sendMenu('poker_post_hand', '◆ Next Hand', [
1868
+ { value: 'again-free', label: 'Play Free', desc: 'Start another free hand', meta: { gameId: status.id } },
1869
+ { value: 'again-real', label: 'Play Real', desc: 'Start another $1 USDC hand', meta: { gameId: status.id } },
1870
+ { value: 'history', label: 'History', desc: 'Recent poker hands', meta: { gameId: status.id } },
1871
+ { value: 'back', label: '← Back', desc: '', meta: { gameId: status.id } },
1872
+ ]);
1873
+ }
1874
+
1875
+ async function renderPokerState(status, ws, opts = {}) {
1876
+ const previous = opts.previous || null;
1877
+ const header = status.mode === 'real'
1878
+ ? `${ANSI.gold} ◆ GTO POKER ARENA${ANSI.reset} ${ANSI.dim}REAL MODE · $${status.buyInUsdc} in / $${status.payoutUsdc} out${ANSI.reset}`
1879
+ : `${ANSI.gold} ◆ GTO POKER ARENA${ANSI.reset} ${ANSI.dim}FREE MODE${ANSI.reset}`;
1880
+
1881
+ if (!previous) {
1882
+ ws.sendLine(header);
1883
+ ws.sendLine(` ${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1884
+ ws.sendLine(` ${ANSI.dim}Dealing cards...${ANSI.reset}`);
1885
+ ws.sendLine('');
1886
+ await sleep(120);
1887
+ sendPokerCards('House', ['??'], ws, true);
1888
+ ws.sendLine('');
1889
+ await sleep(120);
1890
+ sendPokerCards('You', [status.player.hole[0]], ws);
1891
+ ws.sendLine('');
1892
+ await sleep(120);
1893
+ }
1894
+
1895
+ if (previous && status.community.length > previous.community.length) {
1896
+ const label = status.community.length === 3 ? 'Flop' : status.community.length === 4 ? 'Turn' : 'River';
1897
+ ws.sendLine(` ${ANSI.dim}${label} coming in...${ANSI.reset}`);
1898
+ ws.sendLine('');
1899
+ await sleep(140);
1900
+ }
1901
+
1902
+ if (previous && previous.house.holeHidden && !status.house.holeHidden) {
1903
+ ws.sendLine(` ${ANSI.gold} ◆ SHOWDOWN${ANSI.reset}`);
1904
+ ws.sendLine(` ${ANSI.dim}Revealing the house cards...${ANSI.reset}`);
1905
+ ws.sendLine('');
1906
+ await sleep(180);
1907
+ }
1908
+
1909
+ ws.sendLine(header);
1910
+ ws.sendLine(` ${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1911
+ ws.sendLine(` ${ANSI.darkGold}Street${ANSI.reset} ${ANSI.white}${status.street.toUpperCase()}${ANSI.reset}`);
1912
+ ws.sendLine(` ${ANSI.darkGold}Pot${ANSI.reset} ${ANSI.white}${status.pot} chips${ANSI.reset}`);
1913
+ ws.sendLine(` ${ANSI.darkGold}Current Bet${ANSI.reset} ${ANSI.white}${status.currentBet} chips${ANSI.reset}`);
1914
+ ws.sendLine(` ${ANSI.darkGold}Your Stack${ANSI.reset} ${ANSI.white}${status.player.stack} chips${ANSI.reset}`);
1915
+ ws.sendLine(` ${ANSI.darkGold}House Stack${ANSI.reset} ${ANSI.white}${status.house.stack} chips${ANSI.reset}`);
1916
+ ws.sendLine('');
1917
+
1918
+ sendPokerCards('House', status.house.hole, ws, status.house.holeHidden);
1919
+ ws.sendLine('');
1920
+ ws.sendLine(` ${ANSI.darkGold}Board${ANSI.reset}`);
1921
+ if (status.community.length) {
1922
+ for (const row of pokerCardRows(status.community)) {
1923
+ ws.sendLine(` ${row}`);
1924
+ }
1925
+ } else {
1926
+ ws.sendLine(` ${ANSI.dim}No community cards yet${ANSI.reset}`);
1927
+ }
1928
+ ws.sendLine('');
1929
+ sendPokerCards('You', status.player.hole, ws);
1930
+ ws.sendLine('');
1931
+
1932
+ if (status.street === 'finished') {
1933
+ const outcome = status.winner === 'player'
1934
+ ? `${ANSI.green}YOU WIN${ANSI.reset}`
1935
+ : status.winner === 'house'
1936
+ ? `${ANSI.red}HOUSE WINS${ANSI.reset}`
1937
+ : `${ANSI.gold}PUSH${ANSI.reset}`;
1938
+ ws.sendLine(` ${ANSI.darkGold}Result${ANSI.reset} ${outcome}`);
1939
+ ws.sendLine(` ${ANSI.darkGold}Summary${ANSI.reset} ${ANSI.white}${status.summary || '-'}${ANSI.reset}`);
1940
+ ws.sendLine(` ${ANSI.darkGold}Your Hand${ANSI.reset} ${ANSI.white}${status.player.hand?.name || '-'}${ANSI.reset}`);
1941
+ ws.sendLine(` ${ANSI.darkGold}House Hand${ANSI.reset} ${ANSI.white}${status.house.hand?.name || '-'}${ANSI.reset}`);
1942
+ ws.sendLine(` ${ANSI.darkGold}Payout${ANSI.reset} ${ANSI.white}${status.mode === 'real' && status.winner === 'player' ? `$${status.payoutUsdc} USDC` : status.mode === 'real' ? '$0 USDC' : 'fun only'}${ANSI.reset}`);
1943
+ if (status.payment?.payoutTxHash) {
1944
+ ws.sendLine(` ${ANSI.darkGold}Payout TX${ANSI.reset} ${ANSI.white}${status.payment.payoutTxHash.slice(0, 18)}...${ANSI.reset}`);
1945
+ }
1946
+ if (status.payment?.payoutError) {
1947
+ ws.sendLine(` ${ANSI.darkGold}Payout${ANSI.reset} ${ANSI.red}${status.payment.payoutError}${ANSI.reset}`);
1948
+ }
1949
+ ws.sendLine('');
1950
+ return {};
1951
+ }
1952
+
1953
+ if (status.house.lastAction) {
1954
+ ws.sendLine(` ${ANSI.dim}House last action: ${status.house.lastAction}${ANSI.reset}`);
1955
+ }
1956
+ ws.sendLine(` ${ANSI.dim}Available actions: ${status.availableActions.join(', ')}${ANSI.reset}`);
1957
+ ws.sendLine('');
1958
+ return {};
1959
+ }
1960
+
1961
+ async function startPokerWebGame(mode, ws) {
1962
+ const status = await pokerNewGame({ mode });
1963
+ pokerSessions.set(ws, status.id);
1964
+ await renderPokerState(status, ws);
1965
+ if (status.street !== 'finished' && status.currentActor === 'player') {
1966
+ sendPokerActionMenu(status, ws);
1967
+ } else if (status.street === 'finished') {
1968
+ sendPokerPostHandMenu(status, ws);
1969
+ }
1970
+ return {};
1971
+ }
1972
+
1973
+ async function cmdPoker(args, ws) {
1974
+ const sub = (args[0] || '').toLowerCase();
1975
+
1976
+ if (sub === 'free' || sub === 'real') {
1977
+ return await startPokerWebGame(sub, ws);
1978
+ }
1979
+
1980
+ if (sub === 'status') {
1981
+ const status = pokerStatus(pokerSessions.get(ws));
1982
+ if (!status) {
1983
+ ws.sendLine(` ${ANSI.dim}No active poker game${ANSI.reset}`);
1984
+ ws.sendLine('');
1985
+ return {};
1986
+ }
1987
+ await renderPokerState(status, ws);
1988
+ if (status.street !== 'finished' && status.currentActor === 'player') {
1989
+ sendPokerActionMenu(status, ws);
1990
+ } else if (status.street === 'finished') {
1991
+ sendPokerPostHandMenu(status, ws);
1992
+ }
1993
+ return {};
1994
+ }
1995
+
1996
+ if (sub === 'history') {
1997
+ const items = pokerHistory();
1998
+ ws.sendLine(`${ANSI.gold} ◆ POKER HISTORY${ANSI.reset}`);
1999
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
2000
+ if (!items.length) {
2001
+ ws.sendLine(` ${ANSI.dim}No poker hands played yet${ANSI.reset}`);
2002
+ ws.sendLine('');
2003
+ return {};
2004
+ }
2005
+ for (const item of items.slice(0, 10)) {
2006
+ const verdict = item.winner === 'player'
2007
+ ? `${ANSI.green}W${ANSI.reset}`
2008
+ : item.winner === 'house'
2009
+ ? `${ANSI.red}L${ANSI.reset}`
2010
+ : `${ANSI.gold}P${ANSI.reset}`;
2011
+ ws.sendLine(` ${verdict} ${ANSI.white}${item.mode.toUpperCase().padEnd(5)}${ANSI.reset} ${item.summary}`);
2012
+ }
2013
+ ws.sendLine('');
2014
+ return {};
2015
+ }
2016
+
2017
+ ws.sendLine(`${ANSI.gold} ◆ GTO POKER ARENA${ANSI.reset}`);
2018
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
2019
+ ws.sendLine(` ${ANSI.white}Heads-up Texas Hold'em versus the house.${ANSI.reset}`);
2020
+ ws.sendLine(` ${ANSI.dim}Free mode skips payment checks. Real mode uses x402 with a $1 USDC buy-in.${ANSI.reset}`);
2021
+ ws.sendLine('');
2022
+ ws.sendMenu('poker_mode', '◆ Choose Poker Mode', [
2023
+ { value: 'free', label: 'Free Mode', desc: 'Play for fun' },
2024
+ { value: 'real', label: 'Real Mode', desc: '$1 buy-in · $2 payout on win' },
2025
+ { value: 'status', label: 'Status', desc: 'Show current hand' },
2026
+ { value: 'history', label: 'History', desc: 'Recent hands' },
2027
+ { value: 'back', label: '← Back', desc: '' },
2028
+ ]);
2029
+ return {};
2030
+ }
2031
+
1781
2032
  async function cmdCasino(args, ws) {
1782
2033
  ws.sendLine(`${ANSI.gold} ◆ THE CLAWSINO 🎰${ANSI.reset}`);
1783
2034
  ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
@@ -1919,6 +2170,7 @@ function saveSelectedModel(model, provider = getConfig('llm.provider') || 'opena
1919
2170
 
1920
2171
  // Persistent chat engine per WebSocket connection
1921
2172
  const chatEngines = new WeakMap();
2173
+ const pokerSessions = new WeakMap();
1922
2174
 
1923
2175
  async function cmdAI(args, ws) {
1924
2176
  const input = args.join(' ').trim();
package/src/web/server.js CHANGED
@@ -15,11 +15,11 @@ const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = dirname(__filename);
16
16
 
17
17
  // ══════════════════════════════════════════════════
18
- // DARKSOL WEB SHELL Terminal in the browser
18
+ // DARKSOL WEB SHELL - Terminal in the browser
19
19
  // ══════════════════════════════════════════════════
20
20
 
21
21
  /**
22
- * Command handler registry maps command strings to async functions
22
+ * Command handler registry - maps command strings to async functions
23
23
  * that return { output: string } with ANSI stripped for web
24
24
  */
25
25
  import { handleCommand, getAIStatus } from './commands.js';
@@ -174,7 +174,7 @@ export async function startWebShell(opts = {}) {
174
174
  ws.send(JSON.stringify({
175
175
  type: 'menu',
176
176
  id: 'main_menu',
177
- title: '◆ Help Menu Select Command',
177
+ title: '◆ Help Menu - Select Command',
178
178
  items: [
179
179
  { value: 'ai', label: '🧠 AI Chat', desc: 'Natural language assistant' },
180
180
  { value: 'wallet', label: '👛 Wallet', desc: 'Picker + balance + actions' },
@@ -185,6 +185,7 @@ export async function startWebShell(opts = {}) {
185
185
  { value: 'portfolio', label: '📊 Portfolio', desc: 'Multi-chain balances' },
186
186
  { value: 'trade', label: '🔄 Trade', desc: 'Swap / snipe click-through flows' },
187
187
  { value: 'market', label: '📈 Market', desc: 'Price + liquidity intel' },
188
+ { value: 'poker', label: '🃏 Poker', desc: 'Heads-up holdem arena' },
188
189
  { value: 'mail', label: '📧 Mail', desc: 'AgentMail status/inbox' },
189
190
  { value: 'cards', label: '💳 Cards', desc: 'Order prepaid Visa/MC' },
190
191
  { value: 'oracle', label: '🎲 Oracle', desc: 'Randomness service' },
@@ -286,7 +287,7 @@ function getBanner() {
286
287
  `${gold} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚══════╝${reset}`,
287
288
  '',
288
289
  `${dim} ╔══════════════════════════════════════════════════════════╗${reset}`,
289
- `${dim} ║${reset} ${gold}${white} DARKSOL TERMINAL${reset}${dim} ${reset}${dim}Ghost in the machine with teeth${reset}${dim} ║${reset}`,
290
+ `${dim} ║${reset} ${gold}${white} DARKSOL TERMINAL${reset}${dim} - ${reset}${dim}Ghost in the machine with teeth${reset}${dim} ║${reset}`,
290
291
  `${dim} ║${reset}${dim} v${PKG_VERSION}${' '.repeat(Math.max(0, 52 - PKG_VERSION.length))}${reset}${gold}🌑${reset}${dim} ║${reset}`,
291
292
  `${dim} ╚══════════════════════════════════════════════════════════╝${reset}`,
292
293
  '',
@@ -332,6 +333,7 @@ function getHelp() {
332
333
  ['mail inbox', 'Check email inbox'],
333
334
  ['oracle roll', 'On-chain random oracle'],
334
335
  ['casino status', 'Casino status'],
336
+ ['poker', 'GTO Poker Arena'],
335
337
  ['config', 'Show configuration'],
336
338
  ['', ''],
337
339
  ['', `${gold}GENERAL${reset}`],
@@ -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', 'cards', 'mail', 'keys', 'oracle', 'casino',
19
+ 'wallet', 'send', 'receive', 'agent', 'cards', 'mail', 'keys', 'oracle', 'casino', 'poker',
20
20
  'facilitator', 'config', 'logs', 'help', 'clear', 'banner', 'exit',
21
21
  ];
22
22