@ardennguyen/zalo-agent-cli 1.0.7

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 (52) hide show
  1. package/DISCLAIMER.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +205 -0
  4. package/package.json +64 -0
  5. package/src/cli.test.js +98 -0
  6. package/src/commands/account.js +178 -0
  7. package/src/commands/auto-reply.js +83 -0
  8. package/src/commands/catalog.js +143 -0
  9. package/src/commands/conv.js +264 -0
  10. package/src/commands/friend.js +384 -0
  11. package/src/commands/group.js +606 -0
  12. package/src/commands/label.js +39 -0
  13. package/src/commands/listen.js +293 -0
  14. package/src/commands/login.js +165 -0
  15. package/src/commands/mcp.js +219 -0
  16. package/src/commands/msg.js +608 -0
  17. package/src/commands/oa-init.js +503 -0
  18. package/src/commands/oa-listen.js +215 -0
  19. package/src/commands/oa.js +657 -0
  20. package/src/commands/poll.js +159 -0
  21. package/src/commands/profile.js +274 -0
  22. package/src/commands/quick-msg.js +55 -0
  23. package/src/commands/reminder.js +195 -0
  24. package/src/core/accounts.js +93 -0
  25. package/src/core/credentials.js +63 -0
  26. package/src/core/oa-client.js +407 -0
  27. package/src/core/zalo-client.js +201 -0
  28. package/src/index.js +95 -0
  29. package/src/mcp/mcp-config.js +81 -0
  30. package/src/mcp/mcp-http-transport.js +84 -0
  31. package/src/mcp/mcp-server.js +40 -0
  32. package/src/mcp/mcp-tools.js +353 -0
  33. package/src/mcp/media-downloader.js +191 -0
  34. package/src/mcp/media-downloader.test.js +45 -0
  35. package/src/mcp/message-buffer.js +127 -0
  36. package/src/mcp/message-buffer.test.js +308 -0
  37. package/src/mcp/notifier.js +141 -0
  38. package/src/mcp/notifier.test.js +269 -0
  39. package/src/mcp/thread-filter.js +79 -0
  40. package/src/mcp/thread-filter.test.js +206 -0
  41. package/src/mcp/thread-name-cache.js +162 -0
  42. package/src/mcp/thread-name-cache.test.js +139 -0
  43. package/src/utils/bank-helpers.js +233 -0
  44. package/src/utils/bank-helpers.test.js +66 -0
  45. package/src/utils/extract-message-text.js +21 -0
  46. package/src/utils/output.js +21 -0
  47. package/src/utils/proxy-helpers.js +14 -0
  48. package/src/utils/proxy-helpers.test.js +36 -0
  49. package/src/utils/qr-display.js +91 -0
  50. package/src/utils/qr-display.test.js +97 -0
  51. package/src/utils/qr-http-server.js +190 -0
  52. package/src/utils/update-check.js +56 -0
package/DISCLAIMER.md ADDED
@@ -0,0 +1,27 @@
1
+ # Disclaimer
2
+
3
+ ## ⚠️ Cảnh báo quan trọng / Important Warning
4
+
5
+ **Tool này sử dụng [zca-js](https://github.com/AKAspanion/zca-js) — một thư viện gọi API Zalo không chính thức. Zalo không hỗ trợ và tài khoản của bạn có thể bị khóa (ban) khi sử dụng.**
6
+
7
+ **This tool uses [zca-js](https://github.com/AKAspanion/zca-js) — an unofficial Zalo API library. Zalo does not support this and your account may be banned.**
8
+
9
+ ---
10
+
11
+ This is an **unofficial** project and is **not affiliated with, endorsed by, or connected to Zalo or VNG Corporation** in any way.
12
+
13
+ This tool is provided for **research, educational, and personal automation purposes only**. By using this tool, you acknowledge and accept that:
14
+
15
+ - **Your Zalo account may be permanently banned or restricted** — Zalo actively detects and blocks unofficial API usage
16
+ - This tool relies on **reverse-engineered, undocumented APIs** via the zca-js library, which can break at any time
17
+ - There is **no guarantee of stability, compatibility, or continued functionality**
18
+
19
+ Users are solely responsible for ensuring their use of this tool complies with:
20
+
21
+ - Zalo's Terms of Service
22
+ - Applicable local and international laws and regulations
23
+ - Data privacy requirements
24
+
25
+ The authors and contributors of this project accept **no responsibility or liability** for any misuse, account restrictions, bans, or legal consequences arising from the use of this software.
26
+
27
+ **Sử dụng trên tinh thần tự chịu trách nhiệm. Use at your own risk.**
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ <p align="center">
2
+ <img src="assets/mascot.png" width="120" alt="zalo-agent-cli mascot" />
3
+ </p>
4
+
5
+ # zalo-agent-cli
6
+
7
+ Công cụ CLI tự động hóa Zalo — đa tài khoản, proxy, chuyển khoản ngân hàng, thanh toán QR.
8
+ Xây dựng trên [zca-js](https://github.com/RFS-ADRENO/zca-js).
9
+
10
+ **[Tiếng Việt](#bắt-đầu-nhanh)** | **[English](#english)**
11
+
12
+ > [!WARNING]
13
+ > Tool này sử dụng API Zalo **không chính thức** ([zca-js](https://github.com/RFS-ADRENO/zca-js)). Zalo không hỗ trợ và **tài khoản của bạn có thể bị khóa hoặc ban**. Tự chịu trách nhiệm. Không liên kết với Zalo hay VNG. Xem [DISCLAIMER.md](DISCLAIMER.md).
14
+
15
+ > [!TIP]
16
+ > **AI Agent Skill** — Dùng với OpenClaw, Claude Code, hoặc bất kỳ agent nào hỗ trợ SKILL.md:
17
+ > ```bash
18
+ > clawhub install zalo-agent # OpenClaw (từ ClawHub registry)
19
+ > cp -r skill/ ~/.claude/skills/zalo-agent/ # Claude Code
20
+ > ```
21
+ > 15+ nhóm lệnh · listen mode + webhook · 55+ ngân hàng VN · đa tài khoản + proxy
22
+ > Xem [skill/SKILL.md](skill/SKILL.md) · [Eval scenarios](skill/evals/)
23
+
24
+ > [!NOTE]
25
+ > **Zalo Official Account (OA)** — Zalo OA API v3.0 chính thức:
26
+ > ```bash
27
+ > zalo-agent oa init # Setup wizard (interactive)
28
+ > zalo-agent oa init --app-id <ID> --secret <KEY> --skip-webhook # Non-interactive (AI agent)
29
+ > zalo-agent oa whoami # Xem thông tin OA
30
+ > zalo-agent oa msg text <user-id> "Xin chào" # Gửi tin nhắn
31
+ > zalo-agent oa listen -p 3000 # Webhook listener
32
+ > ```
33
+ > OAuth login · gửi tin nhắn · quản lý follower · tag · webhook listener · VPS support
34
+ > Xem [docs/official-account.md](docs/official-account.md)
35
+
36
+ > [!TIP]
37
+ > **MCP Server (AI Agent Integration)** — Model Context Protocol cho Claude Code và các MCP client:
38
+ > ```bash
39
+ > zalo-agent mcp start # stdio (local Claude Code)
40
+ > zalo-agent mcp start --http 3847 --auth your-secret # HTTP (VPS)
41
+ > ```
42
+ > 4 tools: get_messages · send_message · list_threads · mark_read
43
+ > Auto-reconnect · thread filter · noise reduction · group notifications
44
+ > Xem [MCP Guide](skill/references/mcp-guide.md)
45
+
46
+ ---
47
+
48
+ ## Cài đặt
49
+
50
+ ```bash
51
+ npm install -g @ardennguyen/zalo-agent-cli
52
+ ```
53
+
54
+ ## Bắt đầu nhanh
55
+
56
+ ### 1. Đăng nhập
57
+
58
+ ```bash
59
+ zalo-agent login
60
+ ```
61
+
62
+ Quét QR bằng **Zalo app > Quét mã QR**. Thông tin tự động lưu.
63
+
64
+ ### 2. Tìm bạn bè
65
+
66
+ ```bash
67
+ zalo-agent friend search "Phúc"
68
+ ```
69
+
70
+ ### 3. Lắng nghe tin nhắn (lấy thread ID)
71
+
72
+ ```bash
73
+ zalo-agent listen
74
+ ```
75
+
76
+ Mỗi tin nhắn đến sẽ hiện `threadId`. Dùng `--json` để lấy dạng JSON.
77
+
78
+ ### 4. Gửi tin nhắn
79
+
80
+ ```bash
81
+ # Gửi cho cá nhân
82
+ zalo-agent msg send <THREAD_ID> "Xin chào!"
83
+
84
+ # Gửi vào nhóm
85
+ zalo-agent msg send <THREAD_ID> "Xin chào nhóm!" -t 1
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Danh sách lệnh
91
+
92
+ Tất cả lệnh hỗ trợ `--json`. Tài liệu đầy đủ: **[Wiki](https://github.com/ardennguyen/zalo-agent-cli/wiki)**
93
+
94
+ | Nhóm lệnh | Mô tả | Docs |
95
+ |------------|--------|------|
96
+ | `msg` | Gửi tin nhắn, hình, file, voice, video, sticker, link, thẻ chuyển khoản, QR | [Tin nhắn](https://github.com/ardennguyen/zalo-agent-cli/wiki/Tin-Nh%E1%BA%AFn) |
97
+ | `friend` | Danh sách, tìm, thêm, xóa, chặn, biệt danh, gợi ý | [Bạn bè](https://github.com/ardennguyen/zalo-agent-cli/wiki/B%E1%BA%A1n-B%C3%A8) |
98
+ | `group` | Tạo, đổi tên, thành viên, cài đặt, link, ghi chú, lời mời | [Nhóm & Cộng đồng](https://github.com/ardennguyen/zalo-agent-cli/wiki/Nh%C3%B3m) |
99
+ | `conv` | Tắt thông báo, ghim, lưu trữ, ẩn hội thoại, tự xóa | [Hội thoại](https://github.com/ardennguyen/zalo-agent-cli/wiki/H%E1%BB%99i-Tho%E1%BA%A1i) |
100
+ | `profile` | Xem/cập nhật hồ sơ, ảnh đại diện, quyền riêng tư | [Hồ sơ](https://github.com/ardennguyen/zalo-agent-cli/wiki/H%E1%BB%93-S%C6%A1) |
101
+ | `poll` | Tạo, bỏ phiếu, đóng khảo sát | [Khảo sát](https://github.com/ardennguyen/zalo-agent-cli/wiki/Kh%E1%BA%A3o-S%C3%A1t) |
102
+ | `reminder` | Tạo, sửa, xóa nhắc nhở | [Nhắc nhở](https://github.com/ardennguyen/zalo-agent-cli/wiki/Nh%E1%BA%AFc-Nh%E1%BB%9F) |
103
+ | `auto-reply` | Quản lý trả lời tự động | [Trả lời tự động](https://github.com/ardennguyen/zalo-agent-cli/wiki/Tr%E1%BA%A3-L%E1%BB%9Di-T%E1%BB%B1-%C4%90%E1%BB%99ng) |
104
+ | `quick-msg` | Tin nhắn nhanh đã lưu | [Tin nhắn nhanh](https://github.com/ardennguyen/zalo-agent-cli/wiki/Tin-Nh%E1%BA%AFn-Nhanh) |
105
+ | `label` | Nhãn hội thoại | [Nhãn](https://github.com/ardennguyen/zalo-agent-cli/wiki/Nh%C3%A3n) |
106
+ | `catalog` | zBusiness — danh mục sản phẩm | [zBusiness](https://github.com/ardennguyen/zalo-agent-cli/wiki/zBusiness) |
107
+ | `listen` | Lắng nghe tin nhắn real-time, webhook, lưu JSONL | [Lắng nghe](https://github.com/ardennguyen/zalo-agent-cli/wiki/L%E1%BA%AFng-Nghe) |
108
+ | `account` | Đa tài khoản & proxy | [Tài khoản](https://github.com/ardennguyen/zalo-agent-cli/wiki/T%C3%A0i-Kho%E1%BA%A3n) |
109
+ | **`oa`** | **Zalo Official Account API v3.0 — OAuth, tin nhắn, follower, tag, webhook** | **[Official Account](https://github.com/ardennguyen/zalo-agent-cli/wiki/Official-Account)** |
110
+
111
+ Xem thêm: [Đa tài khoản & Proxy](https://github.com/ardennguyen/zalo-agent-cli/wiki/%C4%90a-T%C3%A0i-Kho%E1%BA%A3n-&-Proxy) · [Cài đặt VPS](https://github.com/ardennguyen/zalo-agent-cli/wiki/C%C3%A0i-%C4%90%E1%BA%B7t-VPS) · [Thẻ chuyển khoản & QR](https://github.com/ardennguyen/zalo-agent-cli/wiki/Th%E1%BA%BB-Chuy%E1%BB%83n-Kho%E1%BA%A3n-&-QR) · [Official Account](https://github.com/ardennguyen/zalo-agent-cli/wiki/Official-Account)
112
+
113
+ ---
114
+
115
+ ## Tính năng
116
+
117
+ - Đăng nhập QR qua HTTP server tự động (browser + terminal)
118
+ - Đa tài khoản với proxy riêng biệt (1:1)
119
+ - 90+ lệnh phủ hết tính năng Zalo
120
+ - **Zalo Official Account (OA) API v3.0** — OAuth login, gửi tin nhắn, quản lý follower, webhook listener
121
+ - Thẻ chuyển khoản (55+ ngân hàng VN) & QR VietQR
122
+ - Lắng nghe real-time với webhook & lưu JSONL local
123
+ - Output `--json` cho mọi lệnh — scripting & AI agents
124
+ - Credentials mã hóa tại chỗ (quyền 0600)
125
+ - **Dual mode**: interactive (human) + non-interactive (AI agents, CI/CD)
126
+
127
+ ---
128
+
129
+
130
+
131
+ ---
132
+
133
+ ## English
134
+
135
+ CLI tool for Zalo automation — multi-account, proxy support, bank transfers, QR payments.
136
+
137
+ > [!TIP]
138
+ > **AI Agent Skill** — Use with OpenClaw, Claude Code, or any SKILL.md-compatible agent:
139
+ > ```bash
140
+ > clawhub install zalo-agent # OpenClaw (from ClawHub registry)
141
+ > cp -r skill/ ~/.claude/skills/zalo-agent/ # Claude Code
142
+ > ```
143
+ > 15+ command groups · listen mode + webhook · 55+ VN banks · multi-account + proxy
144
+ > See [skill/SKILL.md](skill/SKILL.md) · [Eval scenarios](skill/evals/)
145
+
146
+ > [!NOTE]
147
+ > **Zalo Official Account (OA)** — Official Zalo OA API v3.0:
148
+ > ```bash
149
+ > zalo-agent oa init # Setup wizard (interactive)
150
+ > zalo-agent oa init --app-id <ID> --secret <KEY> --skip-webhook # Non-interactive (AI agent)
151
+ > zalo-agent oa whoami # OA profile
152
+ > zalo-agent oa msg text <user-id> "Hello" # Send message
153
+ > zalo-agent oa listen -p 3000 # Webhook listener
154
+ > ```
155
+ > OAuth login · messaging · follower management · tags · webhook listener · VPS support
156
+ > See [docs/official-account.md](docs/official-account.md)
157
+
158
+ > [!TIP]
159
+ > **MCP Server (AI Agent Integration)** — Model Context Protocol support for Claude Code and MCP clients:
160
+ > ```bash
161
+ > zalo-agent mcp start # stdio (local Claude Code)
162
+ > zalo-agent mcp start --http 3847 --auth your-secret # HTTP (VPS)
163
+ > ```
164
+ > 4 tools: get_messages · send_message · list_threads · mark_read
165
+ > Auto-reconnect · thread filter · noise reduction · group notifications
166
+ > See [MCP Guide](skill/references/mcp-guide.md)
167
+
168
+ ### Quick Start
169
+
170
+ ```bash
171
+ npm install -g @ardennguyen/zalo-agent-cli
172
+ zalo-agent login # 1. Login via QR
173
+ zalo-agent friend search "Name" # 2. Find a friend
174
+ zalo-agent listen # 3. Listen for threadId
175
+ zalo-agent msg send <THREAD_ID> "Hello!" # 4. Send a message
176
+ ```
177
+
178
+ ### Commands
179
+
180
+ Full docs: **[Wiki](https://github.com/ardennguyen/zalo-agent-cli/wiki)**
181
+
182
+ | Group | Description | Docs |
183
+ |-------|-------------|------|
184
+ | `msg` | Text, images, files, voice, video, stickers, links, bank cards, QR | [Messages](https://github.com/ardennguyen/zalo-agent-cli/wiki/Messages) |
185
+ | `friend` | List, find, add, remove, block, alias, recommendations | [Friends](https://github.com/ardennguyen/zalo-agent-cli/wiki/Friends) |
186
+ | `group` | Create, rename, members, settings, links, notes, invites | [Groups](https://github.com/ardennguyen/zalo-agent-cli/wiki/Groups) |
187
+ | `conv` | Mute, pin, archive, hidden, auto-delete | [Conversations](https://github.com/ardennguyen/zalo-agent-cli/wiki/Conversations) |
188
+ | `profile` | Profile, avatar gallery, privacy | [Profile](https://github.com/ardennguyen/zalo-agent-cli/wiki/Profile) |
189
+ | `poll` | Create, vote, lock polls | [Polls](https://github.com/ardennguyen/zalo-agent-cli/wiki/Polls) |
190
+ | `reminder` | Create, edit, remove reminders | [Reminders](https://github.com/ardennguyen/zalo-agent-cli/wiki/Reminders) |
191
+ | `auto-reply` | Auto-reply rules | [Auto-Reply](https://github.com/ardennguyen/zalo-agent-cli/wiki/Auto-Reply) |
192
+ | `quick-msg` | Saved quick messages | [Quick Messages](https://github.com/ardennguyen/zalo-agent-cli/wiki/Quick-Messages) |
193
+ | `label` | Conversation labels | [Labels](https://github.com/ardennguyen/zalo-agent-cli/wiki/Labels) |
194
+ | `catalog` | zBusiness catalogs & products | [Catalog](https://github.com/ardennguyen/zalo-agent-cli/wiki/Catalog) |
195
+ | `listen` | Real-time listener, webhook, JSONL | [Listener](https://github.com/ardennguyen/zalo-agent-cli/wiki/Listener) |
196
+ | `account` | Multi-account & proxy | [Accounts](https://github.com/ardennguyen/zalo-agent-cli/wiki/Accounts) |
197
+ | **`oa`** | **Zalo Official Account API v3.0 — OAuth, messaging, followers, webhook** | **[Official Account](https://github.com/ardennguyen/zalo-agent-cli/wiki/Official-Account)** |
198
+
199
+
200
+
201
+ ---
202
+
203
+ ## License
204
+
205
+ [MIT](LICENSE) · See [DISCLAIMER.md](DISCLAIMER.md) for full terms.
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@ardennguyen/zalo-agent-cli",
3
+ "version": "1.0.7",
4
+ "description": "CLI tool for Zalo automation — multi-account, proxy, bank transfers, QR payments, Official Account API v3.0",
5
+ "type": "module",
6
+ "bin": {
7
+ "zalo-agent": "src/index.js"
8
+ },
9
+ "main": "./src/index.js",
10
+ "files": [
11
+ "src/",
12
+ "LICENSE",
13
+ "DISCLAIMER.md"
14
+ ],
15
+ "scripts": {
16
+ "test": "node --test src/*.test.js src/**/*.test.js",
17
+ "lint": "eslint src/",
18
+ "lint:fix": "eslint src/ --fix",
19
+ "format": "prettier --write src/",
20
+ "format:check": "prettier --check src/",
21
+ "release": "npm version patch -m 'release: v%s' && git push origin main --tags",
22
+ "prepublishOnly": "npm run lint && npm test"
23
+ },
24
+ "keywords": [
25
+ "zalo",
26
+ "cli",
27
+ "automation",
28
+ "messaging",
29
+ "vietnam",
30
+ "proxy",
31
+ "vietqr",
32
+ "zca-js",
33
+ "official-account",
34
+ "zalo-oa",
35
+ "webhook"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/ardennguyen/zalo-agent-cli"
40
+ },
41
+ "author": "ardennguyen",
42
+ "license": "MIT",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=22"
48
+ },
49
+ "dependencies": {
50
+ "@modelcontextprotocol/sdk": "^1.29.0",
51
+ "chalk": "^5.6.2",
52
+ "commander": "^15.0.0",
53
+ "express": "^5.2.1",
54
+ "https-proxy-agent": "^9.1.0",
55
+ "node-fetch": "^3.3.2",
56
+ "undici": "^8.5.0",
57
+ "zca-js": "^2.1.2",
58
+ "zod": "^4.4.3"
59
+ },
60
+ "devDependencies": {
61
+ "eslint": "^10.0.3",
62
+ "prettier": "^3.8.1"
63
+ }
64
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * CLI interface tests — verify command parsing, help output, and basic behavior
3
+ * without requiring a Zalo session.
4
+ */
5
+
6
+ import { describe, it } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { execFileSync } from "child_process";
9
+ import { resolve } from "path";
10
+
11
+ const CLI = resolve(import.meta.dirname, "index.js");
12
+
13
+ function run(...args) {
14
+ return execFileSync("node", [CLI, ...args], {
15
+ encoding: "utf-8",
16
+ timeout: 10000,
17
+ env: { ...process.env, HOME: "/tmp/zalo-agent-cli-test-home" },
18
+ });
19
+ }
20
+
21
+ describe("CLI interface", () => {
22
+ it("--version outputs 1.0.0", () => {
23
+ const out = run("--version");
24
+ assert.match(out.trim(), /^\d+\.\d+\.\d+$/);
25
+ });
26
+
27
+ it("--help lists all command groups", () => {
28
+ const out = run("--help");
29
+ assert.match(out, /login/);
30
+ assert.match(out, /msg/);
31
+ assert.match(out, /friend/);
32
+ assert.match(out, /group/);
33
+ assert.match(out, /conv/);
34
+ assert.match(out, /account/);
35
+ });
36
+
37
+ it("msg --help lists all subcommands", () => {
38
+ const out = run("msg", "--help");
39
+ assert.match(out, /send /);
40
+ assert.match(out, /send-image/);
41
+ assert.match(out, /send-file/);
42
+ assert.match(out, /send-card/);
43
+ assert.match(out, /send-bank/);
44
+ assert.match(out, /send-qr-transfer/);
45
+ assert.match(out, /sticker/);
46
+ assert.match(out, /react/);
47
+ assert.match(out, /delete/);
48
+ assert.match(out, /forward/);
49
+ });
50
+
51
+ it("friend --help lists subcommands", () => {
52
+ const out = run("friend", "--help");
53
+ assert.match(out, /list/);
54
+ assert.match(out, /find/);
55
+ assert.match(out, /info/);
56
+ assert.match(out, /block/);
57
+ });
58
+
59
+ it("group --help lists subcommands", () => {
60
+ const out = run("group", "--help");
61
+ assert.match(out, /create/);
62
+ assert.match(out, /members/);
63
+ assert.match(out, /rename/);
64
+ });
65
+
66
+ it("conv --help lists subcommands", () => {
67
+ const out = run("conv", "--help");
68
+ assert.match(out, /mute/);
69
+ assert.match(out, /pinned/);
70
+ assert.match(out, /archived/);
71
+ });
72
+
73
+ it("account --help lists subcommands", () => {
74
+ const out = run("account", "--help");
75
+ assert.match(out, /login/);
76
+ assert.match(out, /switch/);
77
+ assert.match(out, /export/);
78
+ assert.match(out, /remove/);
79
+ });
80
+
81
+ it("login --help shows all flags", () => {
82
+ const out = run("login", "--help");
83
+ assert.match(out, /--proxy/);
84
+ assert.match(out, /--credentials/);
85
+ assert.match(out, /--qr-url/);
86
+ assert.match(out, /--qr-port/);
87
+ });
88
+
89
+ it("logout --help shows --purge", () => {
90
+ const out = run("logout", "--help");
91
+ assert.match(out, /--purge/);
92
+ });
93
+
94
+ it("account list on clean state shows no accounts", () => {
95
+ const out = run("account", "list");
96
+ assert.match(out, /No accounts/);
97
+ });
98
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Account commands — multi-account management with per-account proxy.
3
+ * Includes export for headless/CI credential transfer.
4
+ */
5
+
6
+ import { writeFileSync, chmodSync } from "fs";
7
+ import { resolve } from "path";
8
+ import { loginWithQR, loginWithCredentials, extractCredentials, clearSession } from "../core/zalo-client.js";
9
+ import { saveCredentials, loadCredentials } from "../core/credentials.js";
10
+ import { listAccounts, getActive, setActive, addAccount, removeAccount, getAccount } from "../core/accounts.js";
11
+ import { maskProxy } from "../utils/proxy-helpers.js";
12
+ import { displayQR, getQRPath } from "../utils/qr-display.js";
13
+ import { startQrServer } from "../utils/qr-http-server.js";
14
+ import { success, error, info, warning, output } from "../utils/output.js";
15
+
16
+ export function registerAccountCommands(program) {
17
+ const account = program.command("account").description("Manage multiple Zalo accounts with proxy");
18
+
19
+ account
20
+ .command("list")
21
+ .description("List all registered accounts")
22
+ .action(() => {
23
+ const accounts = listAccounts();
24
+ const safe = accounts.map((a) => ({ ...a, proxy: maskProxy(a.proxy) }));
25
+ output(safe, program.opts().json, () => {
26
+ if (!accounts.length) {
27
+ info("No accounts. Use: zalo-agent account login");
28
+ return;
29
+ }
30
+ console.log(` ${"Active".padEnd(8)} ${"Owner ID".padEnd(22)} ${"Name".padEnd(20)} Proxy`);
31
+ console.log(` ${"─".repeat(75)}`);
32
+ for (const a of accounts) {
33
+ const marker = a.active ? " ★" : " ";
34
+ console.log(
35
+ ` ${marker.padEnd(8)} ${a.ownId.padEnd(22)} ${(a.name || "").padEnd(20)} ${maskProxy(a.proxy)}`,
36
+ );
37
+ }
38
+ });
39
+ });
40
+
41
+ account
42
+ .command("login")
43
+ .description("Login a new Zalo account via QR code")
44
+ .option("-p, --proxy <url>", "Dedicated proxy URL for this account")
45
+ .option("-n, --name <label>", "Friendly label", "")
46
+ .option("--qr-url", "Start local HTTP server to view QR in browser (for VPS/headless)")
47
+ .action(async (opts) => {
48
+ if (opts.proxy) info(`Using proxy: ${maskProxy(opts.proxy)}`);
49
+ info("Generating QR code... Scan with Zalo mobile app.");
50
+
51
+ let qrServer = null;
52
+ try {
53
+ const { ownId } = await loginWithQR(opts.proxy, (event) => {
54
+ displayQR(event);
55
+ if (!qrServer) {
56
+ qrServer = startQrServer(getQRPath());
57
+ }
58
+ });
59
+
60
+ // Fetch display name from Zalo profile
61
+ let displayName = opts.name || "";
62
+ try {
63
+ const { getApi } = await import("../core/zalo-client.js");
64
+ const accountInfo = await getApi().fetchAccountInfo();
65
+ displayName = accountInfo?.profile?.displayName || displayName || ownId;
66
+ } catch {}
67
+
68
+ const creds = extractCredentials();
69
+ saveCredentials(ownId, creds);
70
+ addAccount(ownId, displayName, opts.proxy);
71
+ success(
72
+ `Account logged in: ${displayName} (${ownId})${opts.proxy ? ` via ${maskProxy(opts.proxy)}` : ""}`,
73
+ );
74
+ } catch (e) {
75
+ error(`Login failed: ${e.message}`);
76
+ } finally {
77
+ if (qrServer) qrServer.close();
78
+ }
79
+ });
80
+
81
+ account
82
+ .command("switch <ownerId>")
83
+ .description("Switch active account (restarts connection with account proxy)")
84
+ .action(async (ownerId) => {
85
+ let acc = getAccount(ownerId);
86
+ if (!acc) {
87
+ const all = listAccounts();
88
+ const matches = all.filter((a) => a.ownId.includes(ownerId) || (a.name || "").includes(ownerId));
89
+ if (matches.length === 1) {
90
+ ownerId = matches[0].ownId;
91
+ acc = matches[0];
92
+ } else {
93
+ error(`Account not found: ${ownerId}`);
94
+ return;
95
+ }
96
+ }
97
+
98
+ const creds = loadCredentials(ownerId);
99
+ if (!creds) {
100
+ error(`No credentials for ${ownerId}. Re-login needed.`);
101
+ return;
102
+ }
103
+
104
+ info(`Switching to ${ownerId} (${acc?.name || ""})`);
105
+ if (acc?.proxy) info(`Proxy: ${maskProxy(acc.proxy)}`);
106
+
107
+ clearSession();
108
+ try {
109
+ await loginWithCredentials(creds, acc?.proxy || null);
110
+ setActive(ownerId);
111
+ success(`Switched to ${ownerId}`);
112
+ } catch (e) {
113
+ error(`Switch failed: ${e.message}`);
114
+ }
115
+ });
116
+
117
+ account
118
+ .command("remove <ownerId>")
119
+ .description("Remove account and delete its credentials")
120
+ .action((ownerId) => {
121
+ if (removeAccount(ownerId)) {
122
+ success(`Account ${ownerId} removed`);
123
+ } else {
124
+ error(`Account not found: ${ownerId}`);
125
+ }
126
+ });
127
+
128
+ account
129
+ .command("info")
130
+ .description("Show currently active account")
131
+ .action(() => {
132
+ const active = getActive();
133
+ const safe = active ? { ...active, proxy: maskProxy(active.proxy) } : null;
134
+ output(safe, program.opts().json, () => {
135
+ if (!active) {
136
+ info("No active account.");
137
+ return;
138
+ }
139
+ console.log(` Owner ID: ${active.ownId}`);
140
+ console.log(` Name: ${active.name || "-"}`);
141
+ console.log(` Proxy: ${maskProxy(active.proxy)}`);
142
+ console.log(` Active: yes`);
143
+ });
144
+ });
145
+
146
+ account
147
+ .command("export [ownerId]")
148
+ .description("Export account credentials for transfer to another machine")
149
+ .option("-o, --output <path>", "Output file path", "./zalo-creds.json")
150
+ .action((ownerId, opts) => {
151
+ const acc = ownerId ? getAccount(ownerId) : getActive();
152
+ if (!acc) {
153
+ error("No account found to export.");
154
+ return;
155
+ }
156
+
157
+ const creds = loadCredentials(acc.ownId);
158
+ if (!creds) {
159
+ error(`No credentials for ${acc.ownId}.`);
160
+ return;
161
+ }
162
+
163
+ const exportData = {
164
+ ...creds,
165
+ proxy: acc.proxy || null,
166
+ ownId: acc.ownId,
167
+ name: acc.name || "",
168
+ };
169
+
170
+ const outPath = resolve(opts.output);
171
+ writeFileSync(outPath, JSON.stringify(exportData, null, 2), "utf-8");
172
+ chmodSync(outPath, 0o600);
173
+
174
+ success(`Exported to ${outPath}`);
175
+ warning("This file contains login credentials. Keep it secure and do not commit to git.");
176
+ info(`Import on another machine: zalo-agent login --credentials ${opts.output}`);
177
+ });
178
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Auto-reply commands — list, create, update, delete auto-reply messages.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, output } from "../utils/output.js";
7
+
8
+ export function registerAutoReplyCommands(program) {
9
+ const ar = program.command("auto-reply").description("Manage auto-reply messages");
10
+
11
+ ar.command("list")
12
+ .description("List all auto-reply rules")
13
+ .action(async () => {
14
+ try {
15
+ const result = await getApi().getAutoReplyList();
16
+ output(result, program.opts().json);
17
+ } catch (e) {
18
+ error(`Get auto-reply list failed: ${e.message}`);
19
+ }
20
+ });
21
+
22
+ ar.command("create <content>")
23
+ .description("Create an auto-reply rule")
24
+ .option("--enable", "Enable the rule (default: true)", true)
25
+ .option("--no-enable", "Create disabled")
26
+ .option("--start <ms>", "Start time (epoch ms)", parseInt, 0)
27
+ .option("--end <ms>", "End time (epoch ms)", parseInt, 0)
28
+ .option("--scope <n>", "Scope: 0=all, 1=friends, 2=strangers", parseInt, 0)
29
+ .option("--uids <ids...>", "Specific user IDs to auto-reply to")
30
+ .action(async (content, opts) => {
31
+ try {
32
+ const payload = {
33
+ content,
34
+ isEnable: opts.enable,
35
+ startTime: opts.start,
36
+ endTime: opts.end,
37
+ scope: opts.scope,
38
+ };
39
+ if (opts.uids) payload.uids = opts.uids;
40
+ const result = await getApi().createAutoReply(payload);
41
+ output(result, program.opts().json, () => success("Auto-reply created"));
42
+ } catch (e) {
43
+ error(`Create auto-reply failed: ${e.message}`);
44
+ }
45
+ });
46
+
47
+ ar.command("update <id> <content>")
48
+ .description("Update an auto-reply rule")
49
+ .option("--enable", "Enable the rule")
50
+ .option("--no-enable", "Disable the rule")
51
+ .option("--start <ms>", "Start time (epoch ms)", parseInt, 0)
52
+ .option("--end <ms>", "End time (epoch ms)", parseInt, 0)
53
+ .option("--scope <n>", "Scope: 0=all, 1=friends, 2=strangers", parseInt, 0)
54
+ .option("--uids <ids...>", "Specific user IDs")
55
+ .action(async (id, content, opts) => {
56
+ try {
57
+ const payload = {
58
+ id: Number(id),
59
+ content,
60
+ isEnable: opts.enable ?? true,
61
+ startTime: opts.start,
62
+ endTime: opts.end,
63
+ scope: opts.scope,
64
+ };
65
+ if (opts.uids) payload.uids = opts.uids;
66
+ const result = await getApi().updateAutoReply(payload);
67
+ output(result, program.opts().json, () => success(`Auto-reply ${id} updated`));
68
+ } catch (e) {
69
+ error(`Update auto-reply failed: ${e.message}`);
70
+ }
71
+ });
72
+
73
+ ar.command("delete <id>")
74
+ .description("Delete an auto-reply rule")
75
+ .action(async (id) => {
76
+ try {
77
+ const result = await getApi().deleteAutoReply(Number(id));
78
+ output(result, program.opts().json, () => success(`Auto-reply ${id} deleted`));
79
+ } catch (e) {
80
+ error(`Delete auto-reply failed: ${e.message}`);
81
+ }
82
+ });
83
+ }