omnitrade-mcp 0.3.1 → 0.4.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.
Files changed (3) hide show
  1. package/dist/cli.js +271 -154
  2. package/dist/index.js +1 -1
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -5,159 +5,331 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
5
5
  import { homedir } from "os";
6
6
  import { join } from "path";
7
7
  import * as readline from "readline";
8
- var VERSION = "0.3.1";
8
+ var VERSION = "0.4.0";
9
9
  var CONFIG_PATH = join(homedir(), ".omnitrade", "config.json");
10
- var COLORS = {
10
+ var c = {
11
11
  reset: "\x1B[0m",
12
- bright: "\x1B[1m",
12
+ bold: "\x1B[1m",
13
13
  dim: "\x1B[2m",
14
- cyan: "\x1B[36m",
15
- green: "\x1B[32m",
16
- yellow: "\x1B[33m",
17
- red: "\x1B[31m",
18
- magenta: "\x1B[35m",
19
- blue: "\x1B[34m",
20
- white: "\x1B[37m",
21
- bgBlue: "\x1B[44m",
22
- bgMagenta: "\x1B[45m"
14
+ italic: "\x1B[3m",
15
+ // Subtle colors
16
+ white: "\x1B[97m",
17
+ gray: "\x1B[90m",
18
+ blue: "\x1B[38;5;75m",
19
+ // Soft blue
20
+ cyan: "\x1B[38;5;80m",
21
+ // Soft cyan
22
+ green: "\x1B[38;5;114m",
23
+ // Soft green
24
+ yellow: "\x1B[38;5;222m",
25
+ // Soft yellow
26
+ purple: "\x1B[38;5;141m",
27
+ // Soft purple
28
+ orange: "\x1B[38;5;215m",
29
+ // Soft orange
30
+ red: "\x1B[38;5;203m"
31
+ // Soft red
23
32
  };
24
- var c = COLORS;
25
33
  function printLogo() {
26
34
  console.log(`
27
- ${c.cyan}${c.bright}
28
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
29
- \u2551 \u2551
30
- \u2551 ${c.white}\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557${c.cyan} \u2551
31
- \u2551 ${c.white}\u2588\u2588\u2554\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2588\u2588\u2554\u2550\u255D\u2588\u2588\u2554\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u255D${c.cyan} \u2551
32
- \u2551 ${c.white}\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2557${c.cyan} \u2551
33
- \u2551 ${c.white}\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D${c.cyan} \u2551
34
- \u2551 ${c.white}\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557${c.cyan} \u2551
35
- \u2551 ${c.white}\u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u255D${c.cyan} \u2551
36
- \u2551 \u2551
37
- \u2551 ${c.magenta}\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557${c.cyan} \u2551
38
- \u2551 ${c.magenta}\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557${c.cyan} ${c.dim}One AI. 107 Exchanges.${c.cyan} \u2551
39
- \u2551 ${c.magenta}\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D${c.cyan} ${c.dim}Natural Language Trading.${c.cyan} \u2551
40
- \u2551 ${c.magenta}\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D${c.cyan} \u2551
41
- \u2551 ${c.magenta}\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551${c.cyan} ${c.dim}v${VERSION}${c.cyan} \u2551
42
- \u2551 ${c.magenta}\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D${c.cyan} ${c.dim}by Connectry Labs${c.cyan} \u2551
43
- \u2551 \u2551
44
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
35
+ ${c.blue}${c.bold}
36
+ \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E
37
+ \u2502 \u2502
38
+ \u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557${c.purple}\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557${c.blue}\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
39
+ \u2502 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551${c.purple}\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D${c.blue}\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2502
40
+ \u2502 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551${c.purple} \u2588\u2588\u2551 ${c.blue}\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
41
+ \u2502 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551${c.purple} \u2588\u2588\u2551 ${c.blue}\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2502
42
+ \u2502 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551${c.purple} \u2588\u2588\u2551 ${c.blue}\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
43
+ \u2502 \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D${c.purple} \u255A\u2550\u255D ${c.blue}\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u2502
44
+ \u2502 \u2502
45
+ \u2502 ${c.gray}\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501${c.blue} \u2502
46
+ \u2502 \u2502
47
+ \u2502 ${c.white}One AI. 107 Exchanges. Natural Language Trading.${c.blue} \u2502
48
+ \u2502 \u2502
49
+ \u2502 ${c.gray}v${VERSION} by Connectry Labs${c.blue} \u2502
50
+ \u2502 \u2502
51
+ \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F
45
52
  ${c.reset}`);
46
53
  }
54
+ function printCompactLogo() {
55
+ console.log(`
56
+ ${c.blue}${c.bold} \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E
57
+ \u2502 ${c.white}OmniTrade${c.purple} MCP${c.blue} ${c.gray}\xB7 One AI. 107 Exchanges.${c.blue} \u2502
58
+ \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${c.reset}
59
+ `);
60
+ }
47
61
  function printHelp() {
48
62
  printLogo();
49
63
  console.log(`
50
- ${c.bright}${c.white}USAGE${c.reset}
51
- ${c.cyan}omnitrade-mcp${c.reset} ${c.dim}<command>${c.reset}
64
+ ${c.white}${c.bold} COMMANDS${c.reset}
65
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
52
66
 
53
- ${c.bright}${c.white}COMMANDS${c.reset}
54
- ${c.green}start${c.reset} Start the MCP server (default)
55
- ${c.green}init${c.reset} Interactive setup wizard
56
- ${c.green}config${c.reset} Show current configuration
57
- ${c.green}test${c.reset} Test exchange connections
58
- ${c.green}exchanges${c.reset} List all 107 supported exchanges
59
- ${c.green}help${c.reset} Show this help message
60
- ${c.green}version${c.reset} Show version
67
+ ${c.green}setup${c.reset} Guided setup wizard ${c.dim}(recommended for first use)${c.reset}
68
+ ${c.blue}start${c.reset} Start the MCP server for Claude Desktop
69
+ ${c.blue}test${c.reset} Test your exchange connections
70
+ ${c.blue}config${c.reset} View current configuration
71
+ ${c.blue}exchanges${c.reset} List all 107 supported exchanges
72
+ ${c.blue}help${c.reset} Show this help message
61
73
 
62
- ${c.bright}${c.white}EXAMPLES${c.reset}
63
- ${c.dim}# First time setup${c.reset}
64
- ${c.cyan}omnitrade-mcp init${c.reset}
74
+ ${c.white}${c.bold} QUICK START${c.reset}
75
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
65
76
 
66
- ${c.dim}# Start the server (for Claude Desktop)${c.reset}
67
- ${c.cyan}omnitrade-mcp start${c.reset}
77
+ ${c.yellow}$${c.reset} ${c.cyan}omnitrade setup${c.reset} ${c.dim}Run the guided setup wizard${c.reset}
68
78
 
69
- ${c.dim}# Test your connections${c.reset}
70
- ${c.cyan}omnitrade-mcp test${c.reset}
79
+ ${c.white}${c.bold} LINKS${c.reset}
80
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
71
81
 
72
- ${c.bright}${c.white}CONFIGURATION${c.reset}
73
- Config file: ${c.yellow}~/.omnitrade/config.json${c.reset}
82
+ ${c.dim}Documentation${c.reset} ${c.blue}https://github.com/Connectry-io/omnitrade-mcp${c.reset}
83
+ ${c.dim}Issues${c.reset} ${c.blue}https://github.com/Connectry-io/omnitrade-mcp/issues${c.reset}
74
84
 
75
- ${c.bright}${c.white}DOCUMENTATION${c.reset}
76
- ${c.blue}https://github.com/Connectry-io/omnitrade-mcp${c.reset}
77
-
78
- ${c.bright}${c.white}SUPPORT${c.reset}
79
- ${c.blue}https://github.com/Connectry-io/omnitrade-mcp/issues${c.reset}
80
85
  `);
81
86
  }
82
- function printVersion() {
83
- console.log(`${c.cyan}omnitrade-mcp${c.reset} v${VERSION}`);
87
+ async function runSetupWizard() {
88
+ printLogo();
89
+ const rl = readline.createInterface({
90
+ input: process.stdin,
91
+ output: process.stdout
92
+ });
93
+ const question = (q) => new Promise((resolve) => rl.question(q, resolve));
94
+ const clear = () => console.log("\x1B[2J\x1B[H");
95
+ console.log(`
96
+ ${c.white}${c.bold} WELCOME TO OMNITRADE${c.reset}
97
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
98
+
99
+ This wizard will help you connect your first cryptocurrency exchange
100
+ to Claude. It takes about ${c.green}2 minutes${c.reset}.
101
+
102
+ ${c.white}${c.bold} WHAT YOU'LL NEED${c.reset}
103
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
104
+
105
+ ${c.cyan}1.${c.reset} An account on a cryptocurrency exchange ${c.dim}(Binance, Coinbase, etc.)${c.reset}
106
+ ${c.cyan}2.${c.reset} API keys from that exchange ${c.dim}(we'll show you how)${c.reset}
107
+ ${c.cyan}3.${c.reset} Claude Desktop installed ${c.dim}(download from claude.ai)${c.reset}
108
+
109
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
110
+ `);
111
+ await question(` ${c.dim}Press Enter to continue...${c.reset}`);
112
+ console.log(`
113
+ ${c.white}${c.bold} STEP 1 OF 4: CHOOSE YOUR EXCHANGE${c.reset}
114
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
115
+
116
+ ${c.dim}Popular exchanges:${c.reset}
117
+
118
+ ${c.cyan}[1]${c.reset} Binance ${c.dim}Largest exchange, global${c.reset}
119
+ ${c.cyan}[2]${c.reset} Coinbase ${c.dim}US-based, beginner friendly${c.reset}
120
+ ${c.cyan}[3]${c.reset} Kraken ${c.dim}Security focused, EU/US${c.reset}
121
+ ${c.cyan}[4]${c.reset} Bybit ${c.dim}Derivatives, Asia${c.reset}
122
+ ${c.cyan}[5]${c.reset} OKX ${c.dim}Full-featured, global${c.reset}
123
+ ${c.cyan}[6]${c.reset} KuCoin ${c.dim}Altcoin variety${c.reset}
124
+ ${c.cyan}[7]${c.reset} Other ${c.dim}Enter exchange name manually${c.reset}
125
+
126
+ `);
127
+ const exchangeChoice = await question(` ${c.yellow}?${c.reset} Select exchange ${c.dim}[1-7]${c.reset}: `);
128
+ const exchangeMap = {
129
+ "1": "binance",
130
+ "2": "coinbase",
131
+ "3": "kraken",
132
+ "4": "bybit",
133
+ "5": "okx",
134
+ "6": "kucoin"
135
+ };
136
+ let exchange = exchangeMap[exchangeChoice.trim()];
137
+ if (!exchange) {
138
+ exchange = await question(` ${c.yellow}?${c.reset} Enter exchange name: `);
139
+ }
140
+ exchange = exchange.toLowerCase().trim();
141
+ console.log(`
142
+ ${c.white}${c.bold} STEP 2 OF 4: GET YOUR API KEYS${c.reset}
143
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
144
+
145
+ ${c.dim}You need to create API keys on ${c.white}${exchange}${c.dim}. Here's how:${c.reset}
146
+ `);
147
+ if (exchange === "binance") {
148
+ console.log(`
149
+ ${c.cyan}1.${c.reset} Go to ${c.blue}https://testnet.binance.vision/${c.reset} ${c.dim}(for testnet)${c.reset}
150
+ Or ${c.blue}https://www.binance.com/en/my/settings/api-management${c.reset} ${c.dim}(real)${c.reset}
151
+ ${c.cyan}2.${c.reset} Click "${c.white}Generate HMAC_SHA256 Key${c.reset}"
152
+ ${c.cyan}3.${c.reset} Enable permissions: ${c.green}\u2713 Read${c.reset} ${c.green}\u2713 Trade${c.reset} ${c.red}\u2717 Withdraw${c.reset}
153
+ ${c.cyan}4.${c.reset} Copy the ${c.white}API Key${c.reset} and ${c.white}Secret Key${c.reset}
154
+ `);
155
+ } else if (exchange === "coinbase") {
156
+ console.log(`
157
+ ${c.cyan}1.${c.reset} Go to ${c.blue}https://portal.cdp.coinbase.com/${c.reset}
158
+ ${c.cyan}2.${c.reset} Create a new project
159
+ ${c.cyan}3.${c.reset} Generate API credentials
160
+ ${c.cyan}4.${c.reset} Copy ${c.white}API Key${c.reset}, ${c.white}Secret${c.reset}, and ${c.white}Passphrase${c.reset}
161
+ `);
162
+ } else if (exchange === "kraken") {
163
+ console.log(`
164
+ ${c.cyan}1.${c.reset} Go to ${c.blue}https://www.kraken.com/u/security/api${c.reset}
165
+ ${c.cyan}2.${c.reset} Click "Generate new key"
166
+ ${c.cyan}3.${c.reset} Enable: ${c.green}\u2713 Query${c.reset} ${c.green}\u2713 Trade${c.reset} ${c.red}\u2717 Withdraw${c.reset}
167
+ ${c.cyan}4.${c.reset} Copy the ${c.white}API Key${c.reset} and ${c.white}Private Key${c.reset}
168
+ `);
169
+ } else {
170
+ console.log(`
171
+ ${c.cyan}1.${c.reset} Log in to ${c.white}${exchange}${c.reset}
172
+ ${c.cyan}2.${c.reset} Go to API settings ${c.dim}(usually in Settings or Security)${c.reset}
173
+ ${c.cyan}3.${c.reset} Create a new API key
174
+ ${c.cyan}4.${c.reset} Enable: ${c.green}\u2713 Read${c.reset} ${c.green}\u2713 Trade${c.reset} ${c.red}\u2717 Withdraw${c.reset} ${c.dim}(never enable withdraw!)${c.reset}
175
+ ${c.cyan}5.${c.reset} Copy the ${c.white}API Key${c.reset} and ${c.white}Secret${c.reset}
176
+ `);
177
+ }
178
+ console.log(`
179
+ ${c.orange} \u26A0 SECURITY TIP: Never enable withdrawal permissions!${c.reset}
180
+ `);
181
+ await question(` ${c.dim}Press Enter when you have your API keys ready...${c.reset}`);
182
+ console.log(`
183
+ ${c.white}${c.bold} STEP 3 OF 4: ENTER YOUR API KEYS${c.reset}
184
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
185
+
186
+ ${c.dim}Your keys are stored locally at ~/.omnitrade/config.json${c.reset}
187
+ ${c.dim}They never leave your machine.${c.reset}
188
+
189
+ `);
190
+ const apiKey = await question(` ${c.yellow}?${c.reset} API Key: `);
191
+ const secret = await question(` ${c.yellow}?${c.reset} Secret Key: `);
192
+ let password = "";
193
+ if (["coinbase", "kucoin", "okx"].includes(exchange)) {
194
+ password = await question(` ${c.yellow}?${c.reset} Passphrase ${c.dim}(required for ${exchange})${c.reset}: `);
195
+ }
196
+ const testnetAnswer = await question(` ${c.yellow}?${c.reset} Use testnet/sandbox mode? ${c.dim}(recommended)${c.reset} [Y/n]: `);
197
+ const testnet = testnetAnswer.toLowerCase() !== "n";
198
+ rl.close();
199
+ const config = {
200
+ exchanges: {
201
+ [exchange]: {
202
+ apiKey: apiKey.trim(),
203
+ secret: secret.trim(),
204
+ ...password.trim() ? { password: password.trim() } : {},
205
+ testnet
206
+ }
207
+ },
208
+ security: {
209
+ maxOrderSize: 100,
210
+ confirmTrades: true
211
+ }
212
+ };
213
+ const configDir = join(homedir(), ".omnitrade");
214
+ if (!existsSync(configDir)) {
215
+ mkdirSync(configDir, { recursive: true });
216
+ }
217
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
218
+ try {
219
+ const { chmodSync } = await import("fs");
220
+ chmodSync(CONFIG_PATH, 384);
221
+ } catch {
222
+ }
223
+ console.log(`
224
+ ${c.green}${c.bold} \u2713 CONFIGURATION SAVED${c.reset}
225
+
226
+ ${c.white}${c.bold} STEP 4 OF 4: CONNECT TO CLAUDE DESKTOP${c.reset}
227
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
228
+
229
+ ${c.dim}Add OmniTrade to Claude Desktop:${c.reset}
230
+
231
+ ${c.cyan}1.${c.reset} Open this file in a text editor:
232
+ ${c.blue}~/Library/Application Support/Claude/claude_desktop_config.json${c.reset}
233
+
234
+ ${c.cyan}2.${c.reset} Add this configuration:
235
+
236
+ ${c.gray}{
237
+ "mcpServers": {
238
+ "omnitrade": {
239
+ "command": "omnitrade",
240
+ "args": ["start"]
241
+ }
242
+ }
243
+ }${c.reset}
244
+
245
+ ${c.cyan}3.${c.reset} Save the file and ${c.white}restart Claude Desktop${c.reset}
246
+
247
+ ${c.cyan}4.${c.reset} Start chatting! Try asking:
248
+ ${c.dim}"What's my balance on ${exchange}?"${c.reset}
249
+ ${c.dim}"Show me the price of Bitcoin"${c.reset}
250
+ ${c.dim}"Buy $10 of ETH"${c.reset}
251
+
252
+ ${c.gray} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
253
+
254
+ ${c.white}${c.bold} NEXT STEPS${c.reset}
255
+
256
+ ${c.cyan}\u2022${c.reset} Test your connection: ${c.yellow}omnitrade test${c.reset}
257
+ ${c.cyan}\u2022${c.reset} View your config: ${c.yellow}omnitrade config${c.reset}
258
+ ${c.cyan}\u2022${c.reset} Add more exchanges: ${c.yellow}omnitrade setup${c.reset} ${c.dim}(run again)${c.reset}
259
+ ${c.cyan}\u2022${c.reset} Get help: ${c.yellow}omnitrade help${c.reset}
260
+
261
+ ${c.green}${c.bold} \u2713 Setup complete! You're ready to trade with AI.${c.reset}
262
+
263
+ `);
84
264
  }
85
265
  async function showExchanges() {
86
- printLogo();
266
+ printCompactLogo();
87
267
  const ccxt = await import("ccxt");
88
268
  const exchanges = ccxt.default.exchanges;
89
- console.log(`${c.bright}${c.white}SUPPORTED EXCHANGES (${exchanges.length})${c.reset}
269
+ console.log(`${c.white}${c.bold} SUPPORTED EXCHANGES${c.reset} ${c.dim}(${exchanges.length} total)${c.reset}
90
270
  `);
91
271
  const tier1 = ["binance", "bybit", "okx", "gate", "kucoin", "bitget", "htx", "mexc", "cryptocom", "bitmex", "woo", "coinex", "bitmart", "bingx"];
92
- console.log(`${c.green}${c.bright}\u2605 TIER 1 - CERTIFIED${c.reset}`);
93
- console.log(` ${tier1.filter((e) => exchanges.includes(e)).join(", ")}`);
94
272
  const tier2 = ["coinbase", "kraken", "bitstamp", "gemini", "bitfinex", "poloniex", "deribit", "upbit", "bithumb", "bitvavo", "phemex", "ascendex", "lbank"];
273
+ console.log(` ${c.green}\u2605 TIER 1 - CERTIFIED${c.reset}`);
274
+ console.log(` ${c.dim}${tier1.filter((e) => exchanges.includes(e)).join(", ")}${c.reset}`);
95
275
  console.log(`
96
- ${c.yellow}${c.bright}\u2605 TIER 2 - MAJOR${c.reset}`);
97
- console.log(` ${tier2.filter((e) => exchanges.includes(e)).join(", ")}`);
276
+ ${c.yellow}\u2605 TIER 2 - MAJOR${c.reset}`);
277
+ console.log(` ${c.dim}${tier2.filter((e) => exchanges.includes(e)).join(", ")}${c.reset}`);
98
278
  const others = exchanges.filter((e) => !tier1.includes(e) && !tier2.includes(e));
99
279
  console.log(`
100
- ${c.dim}\u2605 ALL OTHERS (${others.length})${c.reset}`);
101
- const cols = 6;
280
+ ${c.gray}\u2605 ALL OTHERS (${others.length})${c.reset}`);
281
+ const cols = 8;
102
282
  for (let i = 0; i < others.length; i += cols) {
103
283
  const row = others.slice(i, i + cols);
104
- console.log(` ${c.dim}${row.map((e) => e.padEnd(15)).join("")}${c.reset}`);
284
+ console.log(` ${c.dim}${row.map((e) => e.padEnd(12)).join("")}${c.reset}`);
105
285
  }
106
- console.log(`
107
- ${c.bright}Total: ${c.cyan}${exchanges.length}${c.reset} exchanges supported
108
- `);
286
+ console.log("");
109
287
  }
110
288
  async function showConfig() {
111
- printLogo();
112
- console.log(`${c.bright}${c.white}CONFIGURATION${c.reset}
289
+ printCompactLogo();
290
+ console.log(`${c.white}${c.bold} CONFIGURATION${c.reset}
113
291
  `);
114
- console.log(`${c.dim}Location:${c.reset} ${c.yellow}${CONFIG_PATH}${c.reset}
292
+ console.log(` ${c.dim}Location:${c.reset} ${c.blue}${CONFIG_PATH}${c.reset}
115
293
  `);
116
294
  if (!existsSync(CONFIG_PATH)) {
117
- console.log(`${c.red}\u2717 Config file not found${c.reset}`);
295
+ console.log(` ${c.red}\u2717 No configuration found${c.reset}`);
118
296
  console.log(`
119
- Run ${c.cyan}omnitrade-mcp init${c.reset} to create one.
297
+ Run ${c.cyan}omnitrade setup${c.reset} to get started.
120
298
  `);
121
299
  return;
122
300
  }
123
301
  try {
124
302
  const config = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
125
303
  const exchanges = Object.keys(config.exchanges || {});
126
- console.log(`${c.green}\u2713 Config loaded${c.reset}
304
+ console.log(` ${c.green}\u2713 Config loaded${c.reset}
127
305
  `);
128
- console.log(`${c.bright}Exchanges configured:${c.reset}`);
306
+ console.log(` ${c.white}${c.bold}Exchanges:${c.reset}`);
129
307
  for (const ex of exchanges) {
130
308
  const cfg = config.exchanges[ex];
131
- const mode = cfg.testnet ? `${c.yellow}testnet${c.reset}` : `${c.green}production${c.reset}`;
132
- console.log(` ${c.cyan}\u2022${c.reset} ${ex} (${mode})`);
309
+ const mode = cfg.testnet ? `${c.yellow}testnet${c.reset}` : `${c.green}live${c.reset}`;
310
+ console.log(` ${c.cyan}\u2022${c.reset} ${ex} (${mode})`);
133
311
  }
134
312
  if (config.security) {
135
313
  console.log(`
136
- ${c.bright}Security settings:${c.reset}`);
314
+ ${c.white}${c.bold}Security:${c.reset}`);
137
315
  if (config.security.maxOrderSize) {
138
- console.log(` ${c.cyan}\u2022${c.reset} Max order size: $${config.security.maxOrderSize}`);
139
- }
140
- if (config.security.allowedPairs) {
141
- console.log(` ${c.cyan}\u2022${c.reset} Allowed pairs: ${config.security.allowedPairs.join(", ")}`);
142
- }
143
- if (config.security.testnetOnly) {
144
- console.log(` ${c.cyan}\u2022${c.reset} Testnet only: ${c.yellow}enabled${c.reset}`);
316
+ console.log(` ${c.cyan}\u2022${c.reset} Max order: $${config.security.maxOrderSize}`);
145
317
  }
146
318
  }
147
319
  console.log("");
148
320
  } catch (error) {
149
- console.log(`${c.red}\u2717 Error reading config:${c.reset} ${error.message}
321
+ console.log(` ${c.red}\u2717 Error:${c.reset} ${error.message}
150
322
  `);
151
323
  }
152
324
  }
153
325
  async function testConnections() {
154
- printLogo();
155
- console.log(`${c.bright}${c.white}TESTING CONNECTIONS${c.reset}
326
+ printCompactLogo();
327
+ console.log(`${c.white}${c.bold} TESTING CONNECTIONS${c.reset}
156
328
  `);
157
329
  if (!existsSync(CONFIG_PATH)) {
158
- console.log(`${c.red}\u2717 Config file not found${c.reset}`);
330
+ console.log(` ${c.red}\u2717 No configuration found${c.reset}`);
159
331
  console.log(`
160
- Run ${c.cyan}omnitrade-mcp init${c.reset} to create one.
332
+ Run ${c.cyan}omnitrade setup${c.reset} to get started.
161
333
  `);
162
334
  return;
163
335
  }
@@ -194,66 +366,15 @@ async function testConnections() {
194
366
  }
195
367
  console.log("");
196
368
  }
197
- async function initConfig() {
198
- printLogo();
199
- console.log(`${c.bright}${c.white}SETUP WIZARD${c.reset}
200
- `);
201
- const rl = readline.createInterface({
202
- input: process.stdin,
203
- output: process.stdout
204
- });
205
- const question = (q) => new Promise((resolve) => rl.question(q, resolve));
206
- console.log(`${c.dim}Let's set up your first exchange connection.${c.reset}
207
- `);
208
- const exchange = await question(`${c.cyan}?${c.reset} Exchange name (e.g., binance, coinbase, kraken): `);
209
- if (!exchange.trim()) {
210
- console.log(`${c.red}\u2717 Exchange name required${c.reset}`);
211
- rl.close();
212
- return;
213
- }
214
- const apiKey = await question(`${c.cyan}?${c.reset} API Key: `);
215
- const secret = await question(`${c.cyan}?${c.reset} Secret Key: `);
216
- const password = await question(`${c.cyan}?${c.reset} Passphrase (press Enter if none): `);
217
- const testnetAnswer = await question(`${c.cyan}?${c.reset} Use testnet/sandbox? (Y/n): `);
218
- const testnet = testnetAnswer.toLowerCase() !== "n";
219
- rl.close();
220
- const config = {
221
- exchanges: {
222
- [exchange.toLowerCase().trim()]: {
223
- apiKey: apiKey.trim(),
224
- secret: secret.trim(),
225
- ...password.trim() ? { password: password.trim() } : {},
226
- testnet
227
- }
228
- },
229
- security: {
230
- maxOrderSize: 100,
231
- confirmTrades: true
232
- }
233
- };
234
- const configDir = join(homedir(), ".omnitrade");
235
- if (!existsSync(configDir)) {
236
- mkdirSync(configDir, { recursive: true });
237
- }
238
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
239
- try {
240
- const { chmodSync } = await import("fs");
241
- chmodSync(CONFIG_PATH, 384);
242
- } catch {
243
- }
244
- console.log(`
245
- ${c.green}\u2713 Configuration saved to ${CONFIG_PATH}${c.reset}`);
246
- console.log(`
247
- ${c.bright}Next steps:${c.reset}`);
248
- console.log(` 1. Test connection: ${c.cyan}omnitrade-mcp test${c.reset}`);
249
- console.log(` 2. Add to Claude: ${c.cyan}Add "omnitrade-mcp" to Claude Desktop config${c.reset}`);
250
- console.log(` 3. Start trading: ${c.cyan}Ask Claude "What's my balance?"${c.reset}
251
- `);
252
- }
253
369
  async function main() {
254
370
  const args = process.argv.slice(2);
255
- const command = args[0] || "start";
371
+ const command = args[0] || "help";
256
372
  switch (command) {
373
+ case "setup":
374
+ case "init":
375
+ case "configure":
376
+ await runSetupWizard();
377
+ break;
257
378
  case "help":
258
379
  case "--help":
259
380
  case "-h":
@@ -262,7 +383,7 @@ async function main() {
262
383
  case "version":
263
384
  case "--version":
264
385
  case "-v":
265
- printVersion();
386
+ console.log(`omnitrade v${VERSION}`);
266
387
  break;
267
388
  case "exchanges":
268
389
  case "list":
@@ -275,10 +396,6 @@ async function main() {
275
396
  case "test":
276
397
  await testConnections();
277
398
  break;
278
- case "init":
279
- case "setup":
280
- await initConfig();
281
- break;
282
399
  case "start":
283
400
  case "serve":
284
401
  case "run":
@@ -286,7 +403,7 @@ async function main() {
286
403
  break;
287
404
  default:
288
405
  console.log(`${c.red}Unknown command: ${command}${c.reset}`);
289
- console.log(`Run ${c.cyan}omnitrade-mcp help${c.reset} for usage.
406
+ console.log(`Run ${c.cyan}omnitrade help${c.reset} for usage.
290
407
  `);
291
408
  process.exit(1);
292
409
  }
package/dist/index.js CHANGED
@@ -911,7 +911,7 @@ function registerArbitrageTools(server, exchangeManager) {
911
911
  }
912
912
 
913
913
  // src/index.ts
914
- var VERSION = "0.3.1";
914
+ var VERSION = "0.4.0";
915
915
  function showBanner() {
916
916
  console.error(`
917
917
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "omnitrade-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Multi-exchange AI trading via MCP. 107 exchanges. One AI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
+ "omnitrade": "dist/cli.js",
8
9
  "omnitrade-mcp": "dist/cli.js"
9
10
  },
10
11
  "scripts": {