levix-bot 2.0.1 → 2.1.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 CHANGED
@@ -14,8 +14,13 @@ levix
14
14
  The npm package is named `levix-bot`; the installed CLI command stays `levix`.
15
15
 
16
16
  On a desktop that opens the panel in your browser by itself. Pick a password,
17
- scan the QR, done — see [SETUP.md](SETUP.md) for the longer version, Docker,
18
- and running it as a service.
17
+ go to **Connection**, press **Start session** and scan the QR — see
18
+ [SETUP.md](SETUP.md) for the longer version, Docker, and running it as a
19
+ service.
20
+
21
+ Starting Levix does not connect it to WhatsApp. The panel, the database and the
22
+ commands come up on their own, and the WhatsApp session is started from the
23
+ Connection screen when you want it.
19
24
 
20
25
  ```
21
26
  levix start Levix with the web panel
@@ -41,7 +46,9 @@ warnings with auto-kick, rules and notes, plus the usual `!group kick`,
41
46
  **An AI agent, not a chat box** — `!gemini` can search the web, open a page and
42
47
  read it, save something to its long-term memory and hand out bot roles, across
43
48
  several tool rounds, narrating the whole run inside a single message it keeps
44
- editing. Its personality is a Markdown file you can edit from the panel.
49
+ editing. Gemini's own Google Search is available to it too, so questions about
50
+ current things get grounded answers with their sources listed. Its personality
51
+ is a Markdown file you can edit from the panel.
45
52
 
46
53
  **Long-term memory** — "remember that…" writes to `memory/global.md` or a
47
54
  per-chat file. Plain Markdown, hand-editable, injected into every prompt.
@@ -49,9 +56,20 @@ per-chat file. Plain Markdown, hand-editable, injected into every prompt.
49
56
  **Scheduled messages** — one-off (`!schedule`) or recurring (`!autoschedule`),
50
57
  stored in the database and listed in the panel.
51
58
 
59
+ **Optional proxy** — Settings → WhatsApp proxy routes the WhatsApp connection
60
+ through an HTTP, HTTPS or SOCKS5 proxy, including the media it sends and
61
+ receives. Nothing else changes: the control panel and the AI still connect
62
+ directly.
63
+
64
+ **A connection you control** — the panel starts, stops and unlinks the
65
+ WhatsApp session, and shows what it is actually doing: waiting for a scan,
66
+ connected, reconnecting (5s, 10s, 15s, 20s, 25s), or stopped. Reconnects happen
67
+ in the bot whether or not a browser is open, and a WhatsApp connection that will
68
+ not come back never takes the panel down with it.
69
+
52
70
  **A panel you can also do without** — `levix headless` runs the bot with no
53
- Express, no socket.io and no port open at all; an unpaired install prints its
54
- QR straight to the terminal.
71
+ Express, no socket.io and no port open at all; having no screen to press Start
72
+ on, it connects by itself and prints its QR straight to the terminal.
55
73
 
56
74
  **A domain, without a takeover** — `levix domain bot.example.com` looks at what
57
75
  the server already runs and works with it: it adds one nginx or Caddy site and
package/SETUP.md CHANGED
@@ -40,10 +40,15 @@ The terminal prints something like:
40
40
  Data: ~/.levix
41
41
 
42
42
  First run — that link asks you to pick a password.
43
- Opening it from another machine also needs this code: 43CB5162
43
+ Opening it from another machine also needs this code:
44
+
45
+ [Setup] Setup code: 43CB5162
44
46
 
45
47
  Opening Levix in your browser...
46
48
 
49
+ Not linked to WhatsApp yet? Open the panel, go to Connection and
50
+ press Start session.
51
+
47
52
  Press Ctrl+C to stop.
48
53
  ```
49
54
 
@@ -51,8 +56,9 @@ The terminal prints something like:
51
56
  2. Choose a password. (Opening the page from a *different* machine also asks
52
57
  for the setup code above — that's what stops a stranger from claiming your
53
58
  bot before you do.)
54
- 3. You land on the control panel. Go to **Connection** and scan the QR with
55
- WhatsApp → Settings → Linked devices → Link a device.
59
+ 3. You land on the control panel. Go to **Connection**, press **Start session**,
60
+ and scan the QR with WhatsApp → Settings → Linked devices → Link a device.
61
+ Levix does not connect to WhatsApp until you ask it to.
56
62
  4. Send `!ping` in any chat. The bot answers.
57
63
 
58
64
  Levix only opens a browser when there is one to open: over SSH, under systemd,
@@ -76,7 +82,7 @@ docker compose up -d
76
82
  Then open <http://localhost:3001>. The setup code is in the logs:
77
83
 
78
84
  ```bash
79
- docker compose logs levix | grep -A3 "Setup code"
85
+ docker compose logs levix | grep -F '[Setup] Setup code:' | tail -1
80
86
  ```
81
87
 
82
88
  Everything the bot owns lives in the `levix-data` volume.
@@ -166,6 +172,13 @@ Closing the terminal stops the bot. Pick one:
166
172
  curl -fsSL https://levix.leviro.net/install.sh | bash
167
173
  ```
168
174
 
175
+ Opening the panel from your laptop counts as another machine, so it asks for
176
+ the setup code. Print it with:
177
+
178
+ ```bash
179
+ journalctl -u levix --no-pager | grep -F '[Setup] Setup code:' | tail -1
180
+ ```
181
+
169
182
  Read the script first if you like — it is short, and it explains each step.
170
183
 
171
184
  That always installs the newest stable Levix. To stay on one version — the same
@@ -175,6 +188,13 @@ script, pinned — use its own URL:
175
188
  curl -fsSL https://levix.leviro.net/install/v2.0.0.sh | bash
176
189
  ```
177
190
 
191
+ **Behind a proxy** — if this server reaches the internet through a proxy, the
192
+ panel has a **WhatsApp proxy** section under Settings: protocol (HTTP, HTTPS or
193
+ SOCKS5), host, port, and a username and password if yours needs them. It applies
194
+ to the WhatsApp connection only — the panel itself and the AI keep connecting
195
+ directly. Saving it never interrupts a working session; the Connection screen
196
+ offers **Reconnect to apply** when a change is waiting.
197
+
178
198
  **Your own machine** — `pm2` works well:
179
199
 
180
200
  ```bash
package/deploy/install.sh CHANGED
@@ -11,16 +11,20 @@
11
11
  # What it does, in order — read it before you pipe it into a shell, as you
12
12
  # should with any script that asks for sudo:
13
13
  #
14
- # 1. checks for Node 24+ and stops with instructions if it isn't there
14
+ # 1. makes sure Node 24+ is available (bootstraps it on Debian/Ubuntu)
15
15
  # 2. installs the `levix` command globally from the `levix-bot` npm package
16
16
  # 3. creates a `levix` system user and /var/lib/levix for its data
17
17
  # 4. installs the systemd unit, pointed at the levix it just installed
18
18
  # 5. enables it, starts it, and prints where to go next
19
19
  # 6. offers — but never assumes — to connect a domain
20
20
  #
21
- # It writes exactly three things outside npm's own prefix: the system user,
22
- # /var/lib/levix, and /etc/systemd/system/levix.service. It never touches an
23
- # existing installation's data, and it can be re-run to upgrade.
21
+ # On a machine that already has Node 24+, the installer does not touch the
22
+ # system package manager. On a fresh Debian/Ubuntu machine it also installs
23
+ # Node.js from NodeSource, which adds its signing key and APT source.
24
+ #
25
+ # Levix itself writes exactly three things outside npm's own prefix: the system
26
+ # user, /var/lib/levix, and /etc/systemd/system/levix.service. It never touches
27
+ # an existing installation's data, and it can be re-run to upgrade.
24
28
 
25
29
  set -euo pipefail
26
30
 
@@ -48,15 +52,97 @@ sudo_if_needed() {
48
52
 
49
53
  # --- 1. Node ---------------------------------------------------------------
50
54
 
51
- command -v node >/dev/null 2>&1 ||
52
- die "Node is not installed. Get Node ${MIN_NODE_MAJOR} LTS from https://nodejs.org"
55
+ node_major() {
56
+ if ! command -v node >/dev/null 2>&1; then
57
+ printf '0\n'
58
+ return
59
+ fi
53
60
 
54
- NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
55
- if [ "$NODE_MAJOR" -lt "$MIN_NODE_MAJOR" ]; then
56
- die "Node ${MIN_NODE_MAJOR}+ is required (found $(node -v)). Levix stores everything in SQLite, which Node only ships with itself from ${MIN_NODE_MAJOR} on."
61
+ local major
62
+ major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || true)"
63
+ case "$major" in
64
+ ''|*[!0-9]*) printf '0\n' ;;
65
+ *) printf '%s\n' "$major" ;;
66
+ esac
67
+ }
68
+
69
+ install_node24() {
70
+ [ -r /etc/os-release ] ||
71
+ die "Node ${MIN_NODE_MAJOR}+ is required. Automatic Node installation is supported on Debian and Ubuntu only."
72
+
73
+ # shellcheck disable=SC1091
74
+ . /etc/os-release
75
+ case "${ID:-}" in
76
+ debian|ubuntu) ;;
77
+ *)
78
+ die "Node ${MIN_NODE_MAJOR}+ is required. Automatic Node installation is supported on Debian and Ubuntu only (found ${ID:-unknown}). Install Node ${MIN_NODE_MAJOR}+ and run this installer again."
79
+ ;;
80
+ esac
81
+
82
+ command -v apt-get >/dev/null 2>&1 ||
83
+ die "This looks like ${ID}, but apt-get is unavailable. Install Node ${MIN_NODE_MAJOR}+ and run this installer again."
84
+
85
+ say "Installing Node ${MIN_NODE_MAJOR} from NodeSource…"
86
+
87
+ sudo_if_needed apt-get update
88
+ sudo_if_needed apt-get install -y ca-certificates curl
89
+ sudo_if_needed install -d -m 0755 /etc/apt/keyrings
90
+
91
+ local key_tmp repo_tmp
92
+ key_tmp="$(mktemp)"
93
+ repo_tmp="$(mktemp)"
94
+
95
+ # Download the signing key as data; do not execute a second remote setup
96
+ # script inside the Levix installer.
97
+ if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o "$key_tmp"; then
98
+ rm -f "$key_tmp" "$repo_tmp"
99
+ die "Could not download the NodeSource signing key."
100
+ fi
101
+
102
+ printf '%s\n' \
103
+ "deb [signed-by=/etc/apt/keyrings/nodesource.asc] https://deb.nodesource.com/node_${MIN_NODE_MAJOR}.x nodistro main" \
104
+ > "$repo_tmp"
105
+
106
+ sudo_if_needed install -m 0644 "$key_tmp" /etc/apt/keyrings/nodesource.asc
107
+ sudo_if_needed install -m 0644 "$repo_tmp" /etc/apt/sources.list.d/nodesource.list
108
+ rm -f "$key_tmp" "$repo_tmp"
109
+
110
+ sudo_if_needed apt-get update
111
+ sudo_if_needed apt-get install -y nodejs
112
+
113
+ # A pre-existing nvm/asdf installation can leave an older Node earlier in
114
+ # PATH even after the system package is upgraded. Prefer the freshly installed
115
+ # NodeSource binary for the rest of this installer when it is suitable.
116
+ if [ -x /usr/bin/node ]; then
117
+ local system_major
118
+ system_major="$(/usr/bin/node -p 'process.versions.node.split(".")[0]' 2>/dev/null || printf '0')"
119
+ case "$system_major" in
120
+ ''|*[!0-9]*) system_major=0 ;;
121
+ esac
122
+ if [ "$system_major" -ge "$MIN_NODE_MAJOR" ]; then
123
+ PATH="/usr/bin:$PATH"
124
+ export PATH
125
+ hash -r
126
+ fi
127
+ fi
128
+ }
129
+
130
+ NODE_MAJOR="$(node_major)"
131
+ if [ "$NODE_MAJOR" -lt "$MIN_NODE_MAJOR" ] || ! command -v npm >/dev/null 2>&1; then
132
+ if [ "$NODE_MAJOR" -eq 0 ]; then
133
+ say "Node ${MIN_NODE_MAJOR}+ was not found; bootstrapping it for you."
134
+ else
135
+ say "Node $(node -v) is too old; upgrading to Node ${MIN_NODE_MAJOR}."
136
+ fi
137
+ install_node24
138
+ NODE_MAJOR="$(node_major)"
57
139
  fi
58
140
 
59
- command -v npm >/dev/null 2>&1 || die "npm is not installed."
141
+ if [ "$NODE_MAJOR" -lt "$MIN_NODE_MAJOR" ]; then
142
+ die "Node ${MIN_NODE_MAJOR}+ is required, but the automatic installation did not provide it (found $(node -v 2>/dev/null || printf 'no Node'))."
143
+ fi
144
+ command -v npm >/dev/null 2>&1 ||
145
+ die "Node $(node -v) is installed, but npm is missing. Install npm and run this installer again."
60
146
  command -v systemctl >/dev/null 2>&1 ||
61
147
  die "This script installs a systemd service, and systemd isn't here. Run 'npm i -g ${PACKAGE}' and start 'levix' yourself."
62
148
 
@@ -151,7 +237,7 @@ cat <<EOF
151
237
  Open the panel and pick a password. Opening it from another machine also
152
238
  asks for a setup code — this prints it:
153
239
 
154
- journalctl -u ${SERVICE} -n 40 | grep -A3 'Setup code'
240
+ journalctl -u ${SERVICE} --no-pager | grep -F '[Setup] Setup code:' | tail -1
155
241
 
156
242
  Then scan the QR from the Connection screen with WhatsApp.
157
243
 
@@ -4,6 +4,9 @@
4
4
  # sudo systemctl enable --now levix
5
5
  # journalctl -u levix -f # the setup code is printed here on first run
6
6
  #
7
+ # The first-run setup code is one line on stdout, so the journal has it:
8
+ # journalctl -u levix --no-pager | grep -F '[Setup] Setup code:' | tail -1
9
+ #
7
10
  # deploy/install.sh installs this and rewrites ExecStart with the real path of
8
11
  # the `levix` command it just installed — `/usr/bin/env levix` only works when
9
12
  # npm's global bin is on systemd's own PATH, which it is not under nvm and not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "levix-bot",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "A self-hosted personal WhatsApp bot with an AI agent, group moderation and a web control panel.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -26,27 +26,32 @@
26
26
  "license": "MIT",
27
27
  "type": "module",
28
28
  "dependencies": {
29
- "@google/generative-ai": "^0.24.1",
29
+ "@google/genai": "^2.19.0",
30
30
  "@sefinek/google-tts-api": "^2.1.15",
31
31
  "@whiskeysockets/baileys": "7.0.0-rc14",
32
32
  "axios": "^1.9.0",
33
- "ejs": "^3.1.10",
33
+ "ejs": "^6.0.1",
34
34
  "express": "^5.1.0",
35
35
  "express-session": "^1.18.1",
36
36
  "ffmpeg-static": "^5.3.0",
37
+ "https-proxy-agent": "^9.1.0",
37
38
  "node-cache": "^5.1.2",
38
39
  "node-cron": "^4.1.0",
39
- "pino": "^9.7.0",
40
+ "pino": "^10.3.1",
40
41
  "pino-pretty": "^13.0.0",
41
42
  "qrcode": "^1.5.4",
42
43
  "qrcode-terminal": "^0.12.0",
43
- "socket.io": "^4.8.1"
44
+ "socket.io": "^4.8.1",
45
+ "socks": "^2.8.9",
46
+ "socks-proxy-agent": "^10.1.0",
47
+ "undici": "^8.10.0"
44
48
  },
45
49
  "allowScripts": {
46
50
  "@whiskeysockets/baileys": true,
47
51
  "esbuild": true,
48
52
  "ffmpeg-static": true,
49
- "protobufjs": true
53
+ "protobufjs": true,
54
+ "@google/genai": true
50
55
  },
51
56
  "repository": {
52
57
  "type": "git",
@@ -302,6 +302,15 @@ kbd {
302
302
  .pill.off .dot {
303
303
  background: var(--danger);
304
304
  }
305
+ /* Connecting / waiting for a scan / reconnecting: neither linked nor dead. */
306
+ .pill.warn {
307
+ color: var(--warn);
308
+ border-color: color-mix(in srgb, var(--warn) 40%, transparent);
309
+ }
310
+ .pill.warn .dot {
311
+ background: var(--warn);
312
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 22%, transparent);
313
+ }
305
314
 
306
315
  .btn {
307
316
  display: inline-flex;