@darksol/terminal 0.10.0 → 0.11.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.
@@ -11,6 +11,7 @@ import { spawn } from 'child_process';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { getConfiguredModel, getModelSelectionMeta, getProviderDefaultModel } from '../llm/models.js';
13
13
  import { pokerNewGame, pokerAction, pokerStatus, pokerHistory } from '../services/poker.js';
14
+ import { sendBrowserCommand, installPlaywrightBrowsers } from '../services/browser.js';
14
15
 
15
16
  // ══════════════════════════════════════════════════
16
17
  // CHAT LOG PERSISTENCE
@@ -917,6 +918,8 @@ export async function handleCommand(cmd, ws) {
917
918
  return await cmdCasino(args, ws);
918
919
  case 'poker':
919
920
  return await cmdPoker(args, ws);
921
+ case 'browser':
922
+ return await cmdBrowser(args, ws);
920
923
  case 'facilitator':
921
924
  return await cmdFacilitator(args, ws);
922
925
  case 'send':
@@ -940,7 +943,7 @@ export async function handleCommand(cmd, ws) {
940
943
  return await cmdChatLogs(args, ws);
941
944
  default: {
942
945
  // Fuzzy: if it looks like natural language, route to AI
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;
946
+ 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|browser|screenshot|click)\b/i;
944
947
  if (nlKeywords.test(cmd)) {
945
948
  return await cmdAI(cmd.split(/\s+/), ws);
946
949
  }
@@ -954,6 +957,137 @@ export async function handleCommand(cmd, ws) {
954
957
  // ══════════════════════════════════════════════════
955
958
  // PRICE
956
959
  // ══════════════════════════════════════════════════
960
+ async function cmdBrowser(args, ws) {
961
+ const sub = args[0]?.toLowerCase() || 'status';
962
+
963
+ if (sub === 'help') {
964
+ ws.sendLine(`${ANSI.gold} ◆ BROWSER AUTOMATION${ANSI.reset}`);
965
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
966
+ ws.sendLine('');
967
+ ws.sendLine(` ${ANSI.green}browser status${ANSI.reset} ${ANSI.dim}Show browser state${ANSI.reset}`);
968
+ ws.sendLine(` ${ANSI.green}browser navigate https://...${ANSI.reset} ${ANSI.dim}Navigate active page${ANSI.reset}`);
969
+ ws.sendLine(` ${ANSI.green}browser screenshot${ANSI.reset} ${ANSI.dim}Save + refresh browser panel${ANSI.reset}`);
970
+ ws.sendLine(` ${ANSI.green}browser click <selector>${ANSI.reset} ${ANSI.dim}Click an element${ANSI.reset}`);
971
+ ws.sendLine(` ${ANSI.green}browser type <selector> <text>${ANSI.reset} ${ANSI.dim}Type text into an input${ANSI.reset}`);
972
+ ws.sendLine(` ${ANSI.green}browser eval <js>${ANSI.reset} ${ANSI.dim}Run JavaScript in page context${ANSI.reset}`);
973
+ ws.sendLine(` ${ANSI.green}browser close${ANSI.reset} ${ANSI.dim}Close the browser service${ANSI.reset}`);
974
+ ws.sendLine('');
975
+ return {};
976
+ }
977
+
978
+ if (sub === 'install') {
979
+ try {
980
+ await installPlaywrightBrowsers();
981
+ ws.sendLine(` ${ANSI.green}✓ Playwright Chromium install complete${ANSI.reset}`);
982
+ ws.sendLine('');
983
+ } catch (err) {
984
+ ws.sendLine(` ${ANSI.red}✗ ${err.message}${ANSI.reset}`);
985
+ ws.sendLine('');
986
+ }
987
+ return {};
988
+ }
989
+
990
+ try {
991
+ if (sub === 'status') {
992
+ const status = await sendBrowserCommand('status');
993
+ ws.sendLine(`${ANSI.gold} ◆ BROWSER STATUS${ANSI.reset}`);
994
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
995
+ ws.sendLine(` ${ANSI.darkGold}Running${ANSI.reset} ${status.running ? ANSI.green + 'yes' : ANSI.dim + 'no'}${ANSI.reset}`);
996
+ ws.sendLine(` ${ANSI.darkGold}Type${ANSI.reset} ${ANSI.white}${status.browserType || 'chromium'}${ANSI.reset}`);
997
+ ws.sendLine(` ${ANSI.darkGold}Profile${ANSI.reset} ${ANSI.white}${status.profile || 'default'}${ANSI.reset}`);
998
+ ws.sendLine(` ${ANSI.darkGold}Mode${ANSI.reset} ${ANSI.white}${status.headed ? 'headed' : 'headless'}${ANSI.reset}`);
999
+ ws.sendLine(` ${ANSI.darkGold}URL${ANSI.reset} ${status.url ? ANSI.white + status.url : ANSI.dim + '(blank)'}${ANSI.reset}`);
1000
+ ws.sendLine(` ${ANSI.darkGold}Title${ANSI.reset} ${status.title ? ANSI.white + status.title : ANSI.dim + '(none)'}${ANSI.reset}`);
1001
+ ws.sendLine(` ${ANSI.darkGold}Pages${ANSI.reset} ${ANSI.white}${status.pageCount || 0}${ANSI.reset}`);
1002
+ ws.sendLine('');
1003
+ return {};
1004
+ }
1005
+
1006
+ if (sub === 'navigate') {
1007
+ const url = args[1];
1008
+ if (!url) {
1009
+ ws.sendLine(` ${ANSI.dim}Usage: browser navigate <url>${ANSI.reset}`);
1010
+ ws.sendLine('');
1011
+ return {};
1012
+ }
1013
+ const status = await sendBrowserCommand('navigate', { url });
1014
+ ws.sendLine(` ${ANSI.green}✓ Navigated to ${status.url}${ANSI.reset}`);
1015
+ ws.sendLine('');
1016
+ ws.send(JSON.stringify({ type: 'browser:refresh' }));
1017
+ return {};
1018
+ }
1019
+
1020
+ if (sub === 'screenshot') {
1021
+ const result = await sendBrowserCommand('screenshot', {});
1022
+ ws.sendLine(` ${ANSI.green}✓ Screenshot saved${ANSI.reset}`);
1023
+ ws.sendLine(` ${ANSI.dim}${result.path}${ANSI.reset}`);
1024
+ ws.sendLine('');
1025
+ ws.send(JSON.stringify({ type: 'browser:refresh' }));
1026
+ return {};
1027
+ }
1028
+
1029
+ if (sub === 'click') {
1030
+ const selector = args[1];
1031
+ if (!selector) {
1032
+ ws.sendLine(` ${ANSI.dim}Usage: browser click <selector>${ANSI.reset}`);
1033
+ ws.sendLine('');
1034
+ return {};
1035
+ }
1036
+ await sendBrowserCommand('click', { selector });
1037
+ ws.sendLine(` ${ANSI.green}✓ Clicked ${selector}${ANSI.reset}`);
1038
+ ws.sendLine('');
1039
+ return {};
1040
+ }
1041
+
1042
+ if (sub === 'type') {
1043
+ const selector = args[1];
1044
+ const text = args.slice(2).join(' ');
1045
+ if (!selector || !text) {
1046
+ ws.sendLine(` ${ANSI.dim}Usage: browser type <selector> <text>${ANSI.reset}`);
1047
+ ws.sendLine('');
1048
+ return {};
1049
+ }
1050
+ await sendBrowserCommand('type', { selector, text });
1051
+ ws.sendLine(` ${ANSI.green}✓ Typed into ${selector}${ANSI.reset}`);
1052
+ ws.sendLine('');
1053
+ return {};
1054
+ }
1055
+
1056
+ if (sub === 'eval') {
1057
+ const expression = args.slice(1).join(' ');
1058
+ if (!expression) {
1059
+ ws.sendLine(` ${ANSI.dim}Usage: browser eval <js>${ANSI.reset}`);
1060
+ ws.sendLine('');
1061
+ return {};
1062
+ }
1063
+ const result = await sendBrowserCommand('eval', { expression });
1064
+ ws.sendLine(`${ANSI.gold} ◆ BROWSER EVAL${ANSI.reset}`);
1065
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1066
+ ws.sendLine(` ${ANSI.white}${String(result.formatted)}${ANSI.reset}`);
1067
+ ws.sendLine('');
1068
+ return {};
1069
+ }
1070
+
1071
+ if (sub === 'close') {
1072
+ await sendBrowserCommand('close');
1073
+ ws.sendLine(` ${ANSI.green}✓ Browser closed${ANSI.reset}`);
1074
+ ws.sendLine('');
1075
+ ws.send(JSON.stringify({ type: 'browser:refresh' }));
1076
+ return {};
1077
+ }
1078
+
1079
+ ws.sendLine(` ${ANSI.red}✗ Unknown browser subcommand: ${sub}${ANSI.reset}`);
1080
+ ws.sendLine(` ${ANSI.dim}Try: browser help${ANSI.reset}`);
1081
+ ws.sendLine('');
1082
+ } catch (err) {
1083
+ ws.sendLine(` ${ANSI.red}✗ ${err.message}${ANSI.reset}`);
1084
+ ws.sendLine(` ${ANSI.dim}Start it with: darksol browser launch${ANSI.reset}`);
1085
+ ws.sendLine('');
1086
+ }
1087
+
1088
+ return {};
1089
+ }
1090
+
957
1091
  async function cmdPrice(tokens, ws) {
958
1092
  if (!tokens.length) {
959
1093
  return { output: ` ${ANSI.dim}Usage: price ETH AERO VIRTUAL${ANSI.reset}\r\n` };
package/src/web/server.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { createServer } from 'http';
2
2
  import { WebSocketServer } from 'ws';
3
- import { readFileSync } from 'fs';
3
+ import { existsSync, readFileSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
6
  import open from 'open';
7
7
  import { theme } from '../ui/theme.js';
8
8
  import { getRecentMemories } from '../memory/index.js';
9
9
  import { getSoul, hasSoul } from '../soul/index.js';
10
+ import { getBrowserScreenshotPath, sendBrowserCommand } from '../services/browser.js';
10
11
  import { createRequire } from 'module';
11
12
  const require = createRequire(import.meta.url);
12
13
  const { version: PKG_VERSION } = require('../../package.json');
@@ -36,7 +37,7 @@ export async function startWebShell(opts = {}) {
36
37
  const css = readFileSync(join(__dirname, 'terminal.css'), 'utf-8');
37
38
  const js = readFileSync(join(__dirname, 'terminal.js'), 'utf-8');
38
39
 
39
- const server = createServer((req, res) => {
40
+ const server = createServer(async (req, res) => {
40
41
  try {
41
42
  const pathname = req.url.split('?')[0];
42
43
 
@@ -52,6 +53,24 @@ export async function startWebShell(opts = {}) {
52
53
  } else if (pathname === '/health') {
53
54
  res.writeHead(200, { 'Content-Type': 'application/json' });
54
55
  res.end(JSON.stringify({ status: 'ok', version: PKG_VERSION }));
56
+ } else if (pathname === '/browser/status') {
57
+ try {
58
+ const status = await sendBrowserCommand('status');
59
+ res.writeHead(200, { 'Content-Type': 'application/json' });
60
+ res.end(JSON.stringify(status));
61
+ } catch {
62
+ res.writeHead(200, { 'Content-Type': 'application/json' });
63
+ res.end(JSON.stringify({ running: false }));
64
+ }
65
+ } else if (pathname === '/browser/screenshot') {
66
+ const screenshotPath = getBrowserScreenshotPath();
67
+ if (!existsSync(screenshotPath)) {
68
+ res.writeHead(404);
69
+ res.end('No screenshot available');
70
+ return;
71
+ }
72
+ res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'no-store' });
73
+ res.end(readFileSync(screenshotPath));
55
74
  } else {
56
75
  res.writeHead(404);
57
76
  res.end('Not found');