@darksol/terminal 0.9.1 → 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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli.js +221 -24
- package/src/llm/engine.js +8 -9
- package/src/llm/models.js +67 -0
- package/src/services/poker.js +937 -0
- package/src/setup/wizard.js +61 -3
- package/src/web/commands.js +351 -5
- package/src/web/server.js +6 -4
- package/src/web/terminal.js +1 -1
package/src/setup/wizard.js
CHANGED
|
@@ -8,6 +8,7 @@ import { hasSoul, runSoulSetup } from '../soul/index.js';
|
|
|
8
8
|
import { createServer } from 'http';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import crypto from 'crypto';
|
|
11
|
+
import { getModelSelectionMeta } from '../llm/models.js';
|
|
11
12
|
|
|
12
13
|
// ══════════════════════════════════════════════════
|
|
13
14
|
// FIRST-RUN SETUP WIZARD
|
|
@@ -116,6 +117,8 @@ export async function runSetupWizard(opts = {}) {
|
|
|
116
117
|
* Setup a cloud provider (OpenAI, Anthropic, OpenRouter, MiniMax)
|
|
117
118
|
*/
|
|
118
119
|
async function setupCloudProvider(provider) {
|
|
120
|
+
await selectAndSaveModel(provider);
|
|
121
|
+
|
|
119
122
|
const supportsOAuth = ['openai', 'anthropic'].includes(provider);
|
|
120
123
|
const providerName = {
|
|
121
124
|
openai: 'OpenAI',
|
|
@@ -204,11 +207,11 @@ async function setupOllama() {
|
|
|
204
207
|
type: 'input',
|
|
205
208
|
name: 'model',
|
|
206
209
|
message: theme.gold('Default model:'),
|
|
207
|
-
default: '
|
|
210
|
+
default: getModelSelectionMeta('ollama').defaultModel,
|
|
211
|
+
validate: (v) => v.trim().length > 0 || 'Model is required',
|
|
208
212
|
}]);
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
setConfig('ollamaModel', model);
|
|
214
|
+
saveModelConfig(model.trim(), 'ollama');
|
|
212
215
|
setConfig('llm.provider', 'ollama');
|
|
213
216
|
setConfig('llmProvider', 'ollama');
|
|
214
217
|
|
|
@@ -216,6 +219,61 @@ async function setupOllama() {
|
|
|
216
219
|
info('Make sure Ollama is running: ollama serve');
|
|
217
220
|
}
|
|
218
221
|
|
|
222
|
+
async function selectAndSaveModel(provider) {
|
|
223
|
+
const meta = getModelSelectionMeta(provider);
|
|
224
|
+
if (!meta || meta.managed) return null;
|
|
225
|
+
|
|
226
|
+
if (meta.textInput) {
|
|
227
|
+
const { model } = await inquirer.prompt([{
|
|
228
|
+
type: 'input',
|
|
229
|
+
name: 'model',
|
|
230
|
+
message: theme.gold('Model:'),
|
|
231
|
+
default: meta.defaultModel,
|
|
232
|
+
validate: (v) => v.trim().length > 0 || 'Model is required',
|
|
233
|
+
}]);
|
|
234
|
+
saveModelConfig(model.trim(), provider);
|
|
235
|
+
return model.trim();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const choices = (meta.choices || []).map((choice) => ({
|
|
239
|
+
name: `${choice.value} - ${choice.desc}`,
|
|
240
|
+
value: choice.value,
|
|
241
|
+
}));
|
|
242
|
+
|
|
243
|
+
if (meta.allowCustom) {
|
|
244
|
+
choices.push({ name: 'Custom model string', value: '__custom__' });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { selectedModel } = await inquirer.prompt([{
|
|
248
|
+
type: 'list',
|
|
249
|
+
name: 'selectedModel',
|
|
250
|
+
message: theme.gold('Choose model:'),
|
|
251
|
+
choices,
|
|
252
|
+
default: meta.defaultModel,
|
|
253
|
+
}]);
|
|
254
|
+
|
|
255
|
+
if (selectedModel === '__custom__') {
|
|
256
|
+
const { customModel } = await inquirer.prompt([{
|
|
257
|
+
type: 'input',
|
|
258
|
+
name: 'customModel',
|
|
259
|
+
message: theme.gold('Custom model string:'),
|
|
260
|
+
validate: (v) => v.trim().length > 0 || 'Model is required',
|
|
261
|
+
}]);
|
|
262
|
+
saveModelConfig(customModel.trim(), provider);
|
|
263
|
+
return customModel.trim();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
saveModelConfig(selectedModel, provider);
|
|
267
|
+
return selectedModel;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function saveModelConfig(model, provider) {
|
|
271
|
+
setConfig('llm.model', model);
|
|
272
|
+
if (provider === 'ollama') {
|
|
273
|
+
setConfig('ollamaModel', model);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
219
277
|
/**
|
|
220
278
|
* Show instructions for getting API keys
|
|
221
279
|
*/
|
package/src/web/commands.js
CHANGED
|
@@ -9,6 +9,8 @@ import { join, dirname } from 'path';
|
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { spawn } from 'child_process';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import { getConfiguredModel, getModelSelectionMeta, getProviderDefaultModel } from '../llm/models.js';
|
|
13
|
+
import { pokerNewGame, pokerAction, pokerStatus, pokerHistory } from '../services/poker.js';
|
|
12
14
|
|
|
13
15
|
// ══════════════════════════════════════════════════
|
|
14
16
|
// CHAT LOG PERSISTENCE
|
|
@@ -206,6 +208,38 @@ export async function handleMenuSelect(id, value, item, ws) {
|
|
|
206
208
|
].map(i => ({ ...i, meta: { provider: value } })));
|
|
207
209
|
return {};
|
|
208
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
|
+
|
|
209
243
|
case 'cards_amount':
|
|
210
244
|
if (value === 'back') return {};
|
|
211
245
|
// Store provider+amount, ask for email
|
|
@@ -416,12 +450,32 @@ export async function handleMenuSelect(id, value, item, ws) {
|
|
|
416
450
|
})));
|
|
417
451
|
return {};
|
|
418
452
|
}
|
|
453
|
+
if (value === 'model') {
|
|
454
|
+
return showModelSelectionMenu(ws);
|
|
455
|
+
}
|
|
419
456
|
if (value === 'keys') {
|
|
420
457
|
return await handleCommand('keys', ws);
|
|
421
458
|
}
|
|
422
459
|
ws.sendLine('');
|
|
423
460
|
return {};
|
|
424
461
|
|
|
462
|
+
case 'config_model':
|
|
463
|
+
if (value === 'back') {
|
|
464
|
+
ws.sendLine('');
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
if (value === '__custom__') {
|
|
468
|
+
ws.sendPrompt('config_model_input', 'Model:', { provider: getConfig('llm.provider') || 'openai' });
|
|
469
|
+
return {};
|
|
470
|
+
}
|
|
471
|
+
saveSelectedModel(value);
|
|
472
|
+
chatEngines.delete(ws);
|
|
473
|
+
ws.sendLine('');
|
|
474
|
+
ws.sendLine(` ${ANSI.green}✓ Model set to ${value}${ANSI.reset}`);
|
|
475
|
+
ws.sendLine(` ${ANSI.dim}AI session refreshed.${ANSI.reset}`);
|
|
476
|
+
ws.sendLine('');
|
|
477
|
+
return {};
|
|
478
|
+
|
|
425
479
|
case 'main_menu':
|
|
426
480
|
if (value === 'back') {
|
|
427
481
|
ws.sendLine('');
|
|
@@ -429,7 +483,6 @@ export async function handleMenuSelect(id, value, item, ws) {
|
|
|
429
483
|
}
|
|
430
484
|
return await handleCommand(value, ws);
|
|
431
485
|
}
|
|
432
|
-
|
|
433
486
|
return {};
|
|
434
487
|
}
|
|
435
488
|
|
|
@@ -473,6 +526,23 @@ export async function handlePromptResponse(id, value, meta, ws) {
|
|
|
473
526
|
return {};
|
|
474
527
|
}
|
|
475
528
|
|
|
529
|
+
if (id === 'config_model_input') {
|
|
530
|
+
const provider = meta?.provider || getConfig('llm.provider') || 'openai';
|
|
531
|
+
const model = String(value || '').trim();
|
|
532
|
+
if (!model) {
|
|
533
|
+
ws.sendLine(` ${ANSI.red}✗ Model is required${ANSI.reset}`);
|
|
534
|
+
ws.sendLine('');
|
|
535
|
+
return {};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
saveSelectedModel(model, provider);
|
|
539
|
+
chatEngines.delete(ws);
|
|
540
|
+
ws.sendLine(` ${ANSI.green}✓ Model set to ${model}${ANSI.reset}`);
|
|
541
|
+
ws.sendLine(` ${ANSI.dim}AI session refreshed.${ANSI.reset}`);
|
|
542
|
+
ws.sendLine('');
|
|
543
|
+
return {};
|
|
544
|
+
}
|
|
545
|
+
|
|
476
546
|
if (id === 'cards_status_id') {
|
|
477
547
|
if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
|
|
478
548
|
return await showCardStatus(value.trim(), ws);
|
|
@@ -789,7 +859,9 @@ export function getAIStatus() {
|
|
|
789
859
|
|
|
790
860
|
if (connected.length > 0) {
|
|
791
861
|
const names = connected.map(p => SERVICES[p]?.name || p).join(', ');
|
|
792
|
-
|
|
862
|
+
const provider = getConfig('llm.provider') || connected[0];
|
|
863
|
+
const model = provider === 'bankr' ? 'gateway managed' : (getConfiguredModel(provider) || getProviderDefaultModel(provider) || 'default');
|
|
864
|
+
return ` ${green}● AI ready${reset} ${dim}(${names} | ${provider}/${model})${reset}\r\n ${dim}Type ${gold}ai <question>${dim} to start chatting. Chat logs saved to ~/.darksol/chat-logs/${reset}\r\n\r\n`;
|
|
793
865
|
}
|
|
794
866
|
|
|
795
867
|
return [
|
|
@@ -836,13 +908,15 @@ export async function handleCommand(cmd, ws) {
|
|
|
836
908
|
case 'mail':
|
|
837
909
|
return await cmdMail(args, ws);
|
|
838
910
|
case 'config':
|
|
839
|
-
return await cmdConfig(ws);
|
|
911
|
+
return await cmdConfig(args, ws);
|
|
840
912
|
case 'oracle':
|
|
841
913
|
return await cmdOracle(args, ws);
|
|
842
914
|
case 'cards':
|
|
843
915
|
return await cmdCards(args, ws);
|
|
844
916
|
case 'casino':
|
|
845
917
|
return await cmdCasino(args, ws);
|
|
918
|
+
case 'poker':
|
|
919
|
+
return await cmdPoker(args, ws);
|
|
846
920
|
case 'facilitator':
|
|
847
921
|
return await cmdFacilitator(args, ws);
|
|
848
922
|
case 'send':
|
|
@@ -866,7 +940,7 @@ export async function handleCommand(cmd, ws) {
|
|
|
866
940
|
return await cmdChatLogs(args, ws);
|
|
867
941
|
default: {
|
|
868
942
|
// Fuzzy: if it looks like natural language, route to AI
|
|
869
|
-
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;
|
|
870
944
|
if (nlKeywords.test(cmd)) {
|
|
871
945
|
return await cmdAI(cmd.split(/\s+/), ws);
|
|
872
946
|
}
|
|
@@ -1739,6 +1813,222 @@ async function cmdOracle(args, ws) {
|
|
|
1739
1813
|
return {};
|
|
1740
1814
|
}
|
|
1741
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
|
+
|
|
1742
2032
|
async function cmdCasino(args, ws) {
|
|
1743
2033
|
ws.sendLine(`${ANSI.gold} ◆ THE CLAWSINO 🎰${ANSI.reset}`);
|
|
1744
2034
|
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
@@ -1794,11 +2084,20 @@ async function cmdFacilitator(args, ws) {
|
|
|
1794
2084
|
return {};
|
|
1795
2085
|
}
|
|
1796
2086
|
|
|
1797
|
-
async function cmdConfig(ws) {
|
|
2087
|
+
async function cmdConfig(args, ws) {
|
|
2088
|
+
const sub = args[0]?.toLowerCase();
|
|
2089
|
+
if (sub === 'model') {
|
|
2090
|
+
return showModelSelectionMenu(ws);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
1798
2093
|
const chain = getConfig('chain') || 'base';
|
|
1799
2094
|
const wallet = getConfig('activeWallet') || '(none)';
|
|
1800
2095
|
const slippage = getConfig('slippage') || '0.5';
|
|
1801
2096
|
const email = getConfig('mailEmail') || '(none)';
|
|
2097
|
+
const provider = getConfig('llm.provider') || '(not set)';
|
|
2098
|
+
const model = provider === 'bankr'
|
|
2099
|
+
? 'gateway managed'
|
|
2100
|
+
: getConfiguredModel(provider === '(not set)' ? 'openai' : provider) || '(default)';
|
|
1802
2101
|
|
|
1803
2102
|
ws.sendLine(`${ANSI.gold} ◆ CONFIG${ANSI.reset}`);
|
|
1804
2103
|
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
@@ -1806,12 +2105,15 @@ async function cmdConfig(ws) {
|
|
|
1806
2105
|
ws.sendLine(` ${ANSI.darkGold}Wallet${ANSI.reset} ${ANSI.white}${wallet}${ANSI.reset}`);
|
|
1807
2106
|
ws.sendLine(` ${ANSI.darkGold}Slippage${ANSI.reset} ${ANSI.white}${slippage}%${ANSI.reset}`);
|
|
1808
2107
|
ws.sendLine(` ${ANSI.darkGold}Mail${ANSI.reset} ${ANSI.white}${email}${ANSI.reset}`);
|
|
2108
|
+
ws.sendLine(` ${ANSI.darkGold}LLM Provider${ANSI.reset} ${ANSI.white}${provider}${ANSI.reset}`);
|
|
2109
|
+
ws.sendLine(` ${ANSI.darkGold}LLM Model${ANSI.reset} ${ANSI.white}${model}${ANSI.reset}`);
|
|
1809
2110
|
ws.sendLine(` ${ANSI.darkGold}AI${ANSI.reset} ${hasAnyLLM() ? `${ANSI.green}● Ready${ANSI.reset}` : `${ANSI.dim}○ Not configured${ANSI.reset}`}`);
|
|
1810
2111
|
ws.sendLine('');
|
|
1811
2112
|
|
|
1812
2113
|
// Offer interactive config
|
|
1813
2114
|
ws.sendMenu('config_action', '◆ Configure', [
|
|
1814
2115
|
{ value: 'chain', label: '🔗 Change chain', desc: `Currently: ${chain}` },
|
|
2116
|
+
{ value: 'model', label: '🧠 Change model', desc: `Currently: ${model}` },
|
|
1815
2117
|
{ value: 'keys', label: '🔑 LLM / API keys', desc: '' },
|
|
1816
2118
|
{ value: 'back', label: '← Back', desc: '' },
|
|
1817
2119
|
]);
|
|
@@ -1819,12 +2121,56 @@ async function cmdConfig(ws) {
|
|
|
1819
2121
|
return {};
|
|
1820
2122
|
}
|
|
1821
2123
|
|
|
2124
|
+
/**
|
|
2125
|
+
* Show model selection menu for current provider
|
|
2126
|
+
*/
|
|
2127
|
+
function showModelSelectionMenu(ws) {
|
|
2128
|
+
const provider = getConfig('llm.provider') || 'openai';
|
|
2129
|
+
const meta = getModelSelectionMeta(provider);
|
|
2130
|
+
|
|
2131
|
+
if (meta.managed) {
|
|
2132
|
+
ws.sendLine(` ${ANSI.dim}Bankr selects the backing model automatically.${ANSI.reset}`);
|
|
2133
|
+
ws.sendLine('');
|
|
2134
|
+
return {};
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
if (meta.textInput) {
|
|
2138
|
+
ws.sendPrompt('config_model_input', 'Model:', { provider });
|
|
2139
|
+
return {};
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
const items = (meta.choices || []).map(choice => ({
|
|
2143
|
+
value: choice.value,
|
|
2144
|
+
label: choice.value,
|
|
2145
|
+
desc: choice.desc,
|
|
2146
|
+
}));
|
|
2147
|
+
|
|
2148
|
+
if (meta.allowCustom) {
|
|
2149
|
+
items.push({ value: '__custom__', label: 'Custom model', desc: 'Type any model string' });
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
items.push({ value: 'back', label: '← Back', desc: '' });
|
|
2153
|
+
ws.sendMenu('config_model', '🧠 Select Model', items);
|
|
2154
|
+
return {};
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* Save selected model to config
|
|
2159
|
+
*/
|
|
2160
|
+
function saveSelectedModel(model, provider = getConfig('llm.provider') || 'openai') {
|
|
2161
|
+
setConfig('llm.model', model);
|
|
2162
|
+
if (provider === 'ollama') {
|
|
2163
|
+
setConfig('ollamaModel', model);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
1822
2167
|
// ══════════════════════════════════════════════════
|
|
1823
2168
|
// AI CHAT — LLM-powered assistant in the web shell
|
|
1824
2169
|
// ══════════════════════════════════════════════════
|
|
1825
2170
|
|
|
1826
2171
|
// Persistent chat engine per WebSocket connection
|
|
1827
2172
|
const chatEngines = new WeakMap();
|
|
2173
|
+
const pokerSessions = new WeakMap();
|
|
1828
2174
|
|
|
1829
2175
|
async function cmdAI(args, ws) {
|
|
1830
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
|
|
18
|
+
// DARKSOL WEB SHELL - Terminal in the browser
|
|
19
19
|
// ══════════════════════════════════════════════════
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Command handler registry
|
|
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
|
|
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}
|
|
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}`],
|
package/src/web/terminal.js
CHANGED
|
@@ -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
|
|