@claw-lab/wxclawbot-cli 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lroolle
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.en.md ADDED
@@ -0,0 +1,161 @@
1
+ # @claw-lab/wxclawbot-cli
2
+
3
+ [简体中文](./README.md) | [npm](https://www.npmjs.com/package/@claw-lab/wxclawbot-cli) | [GitHub](https://github.com/lroolle/wxclawbot-cli) | [ClawHub](https://clawhub.ai/lroolle/wxclawbot-send)
4
+
5
+ Let your AI agent proactively send WeChat messages. Text, images, video, files -- whatever you need.
6
+
7
+ ## Why This Exists
8
+
9
+ WeChat bots can only reply. They can't initiate. That's like having a secretary who only talks when spoken to and otherwise sits there staring at the wall.
10
+
11
+ > "Currently doesn't support proactively sending you messages on a schedule"
12
+ >
13
+ > -- [Lobster's WeChat Integration Tutorial](https://mp.weixin.qq.com/s/nYDQ1obQEHe1WavGpNzasQ)
14
+
15
+ **Now it does.**
16
+
17
+ ## Install
18
+
19
+ Send this to your OpenClaw agent (Lobster). That's the whole install process:
20
+
21
+ ```
22
+ Install a skill so you can proactively WeChat me: npm install -g @claw-lab/wxclawbot-cli
23
+
24
+ npm: https://www.npmjs.com/package/@claw-lab/wxclawbot-cli
25
+ Source: https://github.com/lroolle/wxclawbot-cli
26
+
27
+ Let me know when it's done.
28
+ ```
29
+
30
+ Copy. Paste. Send. The agent handles installation, config, scheduling -- all of it.
31
+
32
+ You don't touch the CLI. You don't write cron jobs. You don't need the technical docs below.
33
+
34
+ ## What You Can Do With It
35
+
36
+ You're not "running commands." You're talking to your agent. These are prompts you send directly:
37
+
38
+ **Stay-alive reminders:**
39
+
40
+ > Remind me to drink water every 45 minutes, don't be polite about it
41
+
42
+ > Every hour of sitting, tell me to get up and move. Be aggressive.
43
+
44
+ > If I'm still chatting with you after 1 AM, yell at me to go to sleep
45
+
46
+ > Friday 5:55 PM -- remind me to stop working. Do not accept new tasks.
47
+
48
+ > Weekdays 11:15 -- remind me to order lunch before delivery queues explode
49
+
50
+ **DevOps alerts:**
51
+
52
+ > If CI breaks, WeChat me immediately with whose commit blew it up
53
+
54
+ > PR sitting unreviewed for 24 hours? Ping me.
55
+
56
+ > Notify me on every deploy -- success or failure, I want to know
57
+
58
+ > Production error rate over 1%? Alert me via WeChat, right now
59
+
60
+ **Business ops:**
61
+
62
+ > Every morning at 9, send me yesterday's key metrics summary
63
+
64
+ > Ticket exceeds SLA by 4 hours -- auto-escalate and notify me
65
+
66
+ > Suspicious login detected? Alert me immediately
67
+
68
+ > Server disk over 90%? WeChat me
69
+
70
+ You talk. The agent works. That's the whole idea.
71
+
72
+ ---
73
+
74
+ Everything below is technical reference for agents, scripts, and developers. Normal humans can stop here.
75
+
76
+ ## CLI Reference
77
+
78
+ ```bash
79
+ wxclawbot send --text "message" --json
80
+ wxclawbot send --file ./photo.jpg --json
81
+ wxclawbot send --file ./report.pdf --text "See attached" --json
82
+ wxclawbot send --to "user@im.wechat" --text "Hello" --json
83
+ echo "report ready" | wxclawbot send --json
84
+ wxclawbot send --text "test" --dry-run
85
+ ```
86
+
87
+ | Flag | Description |
88
+ |------|-------------|
89
+ | `--text <msg>` | Message text. `"-"` to explicitly read stdin |
90
+ | `--file <path>` | Local file path or URL (image/video/file) |
91
+ | `--to <userId>` | Target user ID. Default: bound user from account |
92
+ | `--account <id>` | Account ID. Default: first available |
93
+ | `--json` | JSON output. **Always use this.** |
94
+ | `--dry-run` | Preview without sending |
95
+
96
+ Media type auto-detected by extension:
97
+
98
+ - Image: .png .jpg .jpeg .gif .webp .bmp
99
+ - Video: .mp4 .mov .webm .mkv .avi
100
+ - File: everything else
101
+
102
+ ## Output Format
103
+
104
+ ```json
105
+ {"ok":true,"to":"user@im.wechat","clientId":"..."}
106
+ {"ok":false,"error":"ret=-2 (rate limited, try again later)"}
107
+ ```
108
+
109
+ Exit codes: 0 = CLI ran, 1 = failure. Note: exit 0 means the CLI executed, not that the message was delivered. Check the `ok` field.
110
+
111
+ ## Error Codes
112
+
113
+ | ret | meaning | action |
114
+ |-----|---------|--------|
115
+ | -2 | rate limited | wait 5-10s, retry. Don't tight-loop this. |
116
+ | -14 | session expired | re-login via openclaw |
117
+
118
+ Rate limit: **~7 msgs / 5 min** per bot account, server-side, shared across all clients on the same token.
119
+
120
+ ## Accounts
121
+
122
+ ```bash
123
+ wxclawbot accounts --json
124
+ ```
125
+
126
+ Auto-discovers from `~/.openclaw/openclaw-weixin/accounts/`. Env override:
127
+
128
+ ```bash
129
+ export WXCLAW_TOKEN="bot@im.bot:your-token"
130
+ export WXCLAW_BASE_URL="https://ilinkai.weixin.qq.com"
131
+ ```
132
+
133
+ ## Programmatic API
134
+
135
+ ```typescript
136
+ import { WxClawClient } from "@claw-lab/wxclawbot-cli";
137
+ import { resolveAccount } from "@claw-lab/wxclawbot-cli/accounts";
138
+
139
+ const account = resolveAccount();
140
+ const client = new WxClawClient({
141
+ baseUrl: account.baseUrl,
142
+ token: account.token,
143
+ botId: account.botId,
144
+ });
145
+
146
+ await client.sendText("user@im.wechat", "Hello");
147
+ await client.sendFile("user@im.wechat", "./photo.jpg", { text: "Check this" });
148
+ ```
149
+
150
+ See [references/programmatic-api.md](references/programmatic-api.md) for full details.
151
+
152
+ ## Links
153
+
154
+ - [npm](https://www.npmjs.com/package/@claw-lab/wxclawbot-cli)
155
+ - [GitHub](https://github.com/lroolle/wxclawbot-cli)
156
+ - [ClawHub](https://clawhub.ai/lroolle/wxclawbot-send) -- `clawhub install wxclawbot-send`
157
+ - [WeChat Integration Tutorial](https://mp.weixin.qq.com/s/nYDQ1obQEHe1WavGpNzasQ) -- start here for the basics
158
+
159
+ ## License
160
+
161
+ MIT
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # @claw-lab/wxclawbot-cli
2
+
3
+ [English](./README.en.md) | [npm](https://www.npmjs.com/package/@claw-lab/wxclawbot-cli) | [GitHub](https://github.com/lroolle/wxclawbot-cli) | [ClawHub](https://clawhub.ai/lroolle/wxclawbot-send)
4
+
5
+ 让你的 AI agent 主动给微信用户发消息。文本、图片、视频、文件,想发就发。
6
+
7
+ ## 为什么做这个
8
+
9
+ > 7)现在还不支持主动定时给你发消息
10
+ > 也就是说,它不会像小秘书一样,自己跳出来提醒你。
11
+ >
12
+ > -- [龙虾接入微信教程](https://mp.weixin.qq.com/s/nYDQ1obQEHe1WavGpNzasQ)
13
+
14
+ **现在可以了。**
15
+
16
+ ## 安装
17
+
18
+ 把下面这段直接发给你的龙虾就行:
19
+
20
+ ```
21
+ 装个技能让你能主动给我发微信,先装 CLI:npm install -g @claw-lab/wxclawbot-cli
22
+
23
+ npm 包:https://www.npmjs.com/package/@claw-lab/wxclawbot-cli
24
+ 源码:https://github.com/lroolle/wxclawbot-cli
25
+
26
+ 装好了告诉我。
27
+ ```
28
+
29
+ 没了。复制,粘贴,发送。agent 自己搞定安装、配置、定时任务。
30
+
31
+ 你不需要碰命令行。你不需要写 cron。你不需要看下面的技术文档。
32
+
33
+ ## 使用场景
34
+
35
+ 你不是在"用 CLI",你是在跟 agent 说话。以下是你可以直接发给龙虾的提示词:
36
+
37
+ **活着提醒系列:**
38
+
39
+ > 每 45 分钟提醒我喝水,别客气直接骂
40
+
41
+ > 每坐满 1 小时提醒我站起来,语气凶一点
42
+
43
+ > 凌晨 1 点还在跟你聊天直接骂我去睡觉
44
+
45
+ > 周五 5:55 提醒我下班,不要接新需求
46
+
47
+ > 工作日 11:15 提醒我点外卖,不然配送排到下午两点
48
+
49
+ **搬砖告警系列:**
50
+
51
+ > CI 挂了第一时间微信通知我,告诉我是谁的 commit 炸的
52
+
53
+ > 有 PR 超过 24 小时没人 review 就催我
54
+
55
+ > 每次部署完成通知我,成功失败都要说
56
+
57
+ > 生产环境错误率超过 1% 立刻告警
58
+
59
+ **老板视角系列:**
60
+
61
+ > 每天早上 9 点把昨天的核心指标发给我
62
+
63
+ > 工单超 SLA 4 小时自动升级通知我
64
+
65
+ > 检测到异常登录立刻告警
66
+
67
+ > 服务器磁盘超 90% 微信通知我
68
+
69
+ 你说人话,agent 干活。就这么简单。
70
+
71
+ ---
72
+
73
+ 以下是给 agent、脚本和开发者看的技术文档。普通用户到这里就可以关掉了。
74
+
75
+ ## CLI 参考
76
+
77
+ ```bash
78
+ wxclawbot send --text "消息内容" --json
79
+ wxclawbot send --file ./photo.jpg --json
80
+ wxclawbot send --file ./report.pdf --text "请查收" --json
81
+ wxclawbot send --to "user@im.wechat" --text "你好" --json
82
+ echo "日报已生成" | wxclawbot send --json
83
+ wxclawbot send --text "test" --dry-run
84
+ ```
85
+
86
+ | 参数 | 说明 |
87
+ |------|------|
88
+ | `--text <msg>` | 消息文本。`"-"` 显式读 stdin |
89
+ | `--file <path>` | 本地文件或 URL(图片 / 视频 / 文件) |
90
+ | `--to <userId>` | 目标用户 ID。默认:账号绑定用户 |
91
+ | `--account <id>` | 指定账号。默认:第一个可用的 |
92
+ | `--json` | JSON 格式输出。**始终带上** |
93
+ | `--dry-run` | 预览,不发送 |
94
+
95
+ 媒体类型按扩展名自动识别:
96
+
97
+ - 图片:.png .jpg .jpeg .gif .webp .bmp
98
+ - 视频:.mp4 .mov .webm .mkv .avi
99
+ - 文件:其他所有
100
+
101
+ ## 输出格式
102
+
103
+ ```json
104
+ {"ok":true,"to":"user@im.wechat","clientId":"..."}
105
+ {"ok":false,"error":"ret=-2 (rate limited, try again later)"}
106
+ ```
107
+
108
+ 退出码:0 = CLI 执行成功,1 = 失败。注意:exit 0 只代表 CLI 跑完了,不代表消息送达。看 `ok` 字段。
109
+
110
+ ## 错误码
111
+
112
+ | ret | 含义 | 处理 |
113
+ |-----|------|------|
114
+ | -2 | 频率限制 | 等 5-10 秒重试,别搞紧循环 |
115
+ | -14 | 会话过期 | 通过 openclaw 重新登录 |
116
+
117
+ 频率限制:每个机器人账号约 **7 条 / 5 分钟**,服务端限制,所有客户端共享。
118
+
119
+ ## 账号
120
+
121
+ ```bash
122
+ wxclawbot accounts --json
123
+ ```
124
+
125
+ 自动从 `~/.openclaw/openclaw-weixin/accounts/` 发现。环境变量覆盖:
126
+
127
+ ```bash
128
+ export WXCLAW_TOKEN="bot@im.bot:your-token"
129
+ export WXCLAW_BASE_URL="https://ilinkai.weixin.qq.com"
130
+ ```
131
+
132
+ ## 编程接口
133
+
134
+ ```typescript
135
+ import { WxClawClient } from "@claw-lab/wxclawbot-cli";
136
+ import { resolveAccount } from "@claw-lab/wxclawbot-cli/accounts";
137
+
138
+ const account = resolveAccount();
139
+ const client = new WxClawClient({
140
+ baseUrl: account.baseUrl,
141
+ token: account.token,
142
+ botId: account.botId,
143
+ });
144
+
145
+ await client.sendText("user@im.wechat", "你好");
146
+ await client.sendFile("user@im.wechat", "./photo.jpg", { text: "请查收" });
147
+ ```
148
+
149
+ 详见 [references/programmatic-api.md](references/programmatic-api.md)。
150
+
151
+ ## 相关链接
152
+
153
+ - [npm](https://www.npmjs.com/package/@claw-lab/wxclawbot-cli)
154
+ - [GitHub](https://github.com/lroolle/wxclawbot-cli)
155
+ - [ClawHub](https://clawhub.ai/lroolle/wxclawbot-send) -- `clawhub install wxclawbot-send`
156
+ - [龙虾接入微信教程](https://mp.weixin.qq.com/s/nYDQ1obQEHe1WavGpNzasQ) -- 先看这个搞定基础接入
157
+
158
+ ## 开源许可
159
+
160
+ MIT
@@ -0,0 +1,9 @@
1
+ import type { AccountData, ResolvedAccount } from "./types.js";
2
+ export declare function listAccountIds(): string[];
3
+ export declare function loadAccountData(accountId: string): AccountData | null;
4
+ export declare function resolveAccount(accountId?: string): ResolvedAccount | null;
5
+ export declare function listAccounts(): Array<{
6
+ id: string;
7
+ configured: boolean;
8
+ baseUrl: string;
9
+ }>;
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ const DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
5
+ function extractBotId(token) {
6
+ const colonIdx = token.indexOf(":");
7
+ return colonIdx > 0 ? token.substring(0, colonIdx) : "";
8
+ }
9
+ function stateDir() {
10
+ return (process.env.OPENCLAW_STATE_DIR?.trim() ||
11
+ process.env.CLAWDBOT_STATE_DIR?.trim() ||
12
+ path.join(os.homedir(), ".openclaw"));
13
+ }
14
+ function weixinStateDir() {
15
+ return path.join(stateDir(), "openclaw-weixin");
16
+ }
17
+ function accountsDir() {
18
+ return path.join(weixinStateDir(), "accounts");
19
+ }
20
+ function indexPath() {
21
+ return path.join(weixinStateDir(), "accounts.json");
22
+ }
23
+ export function listAccountIds() {
24
+ try {
25
+ const raw = fs.readFileSync(indexPath(), "utf-8");
26
+ const parsed = JSON.parse(raw);
27
+ if (Array.isArray(parsed)) {
28
+ const ids = parsed.filter((id) => typeof id === "string" && id !== "");
29
+ if (ids.length > 0)
30
+ return ids;
31
+ }
32
+ }
33
+ catch {
34
+ // fall through to directory scan
35
+ }
36
+ return scanAccountsDir();
37
+ }
38
+ function scanAccountsDir() {
39
+ try {
40
+ const dir = accountsDir();
41
+ return fs
42
+ .readdirSync(dir)
43
+ .filter((f) => f.endsWith(".json") && !f.endsWith(".sync.json"))
44
+ .map((f) => f.replace(/\.json$/, ""));
45
+ }
46
+ catch {
47
+ return [];
48
+ }
49
+ }
50
+ export function loadAccountData(accountId) {
51
+ if (accountId.includes("/") ||
52
+ accountId.includes("\\") ||
53
+ accountId.includes("..")) {
54
+ return null;
55
+ }
56
+ const filePath = path.join(accountsDir(), `${accountId}.json`);
57
+ try {
58
+ const raw = fs.readFileSync(filePath, "utf-8");
59
+ return JSON.parse(raw);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ function resolveBotId(data) {
66
+ return data.userId?.trim() || extractBotId(data.token ?? "");
67
+ }
68
+ export function resolveAccount(accountId) {
69
+ const envToken = process.env.WXCLAW_TOKEN?.trim();
70
+ const envBaseUrl = process.env.WXCLAW_BASE_URL?.trim();
71
+ if (envToken && !accountId) {
72
+ return {
73
+ id: "env",
74
+ token: envToken,
75
+ baseUrl: envBaseUrl || DEFAULT_BASE_URL,
76
+ botId: extractBotId(envToken),
77
+ };
78
+ }
79
+ const ids = listAccountIds();
80
+ const targetId = accountId || ids[0];
81
+ if (!targetId)
82
+ return null;
83
+ const data = loadAccountData(targetId);
84
+ if (!data?.token)
85
+ return null;
86
+ return {
87
+ id: targetId,
88
+ token: data.token,
89
+ baseUrl: data.baseUrl?.trim() || DEFAULT_BASE_URL,
90
+ botId: resolveBotId(data),
91
+ defaultTo: data.userId?.trim() || undefined,
92
+ };
93
+ }
94
+ export function listAccounts() {
95
+ const ids = listAccountIds();
96
+ return ids.map((id) => {
97
+ const data = loadAccountData(id);
98
+ return {
99
+ id,
100
+ configured: Boolean(data?.token),
101
+ baseUrl: data?.baseUrl?.trim() || DEFAULT_BASE_URL,
102
+ };
103
+ });
104
+ }
105
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAEzD,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,QAAQ;IACf,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE;QACtC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE;QACtC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CACrC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,UAAU,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,eAAe,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CACvB,CAAC,EAAW,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE,CACnE,CAAC;YACF,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC;QACjC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;IACnC,CAAC;IACD,OAAO,eAAe,EAAE,CAAC;AAC3B,CAAC;AAED,SAAS,eAAe;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;QAC1B,OAAO,EAAE;aACN,WAAW,CAAC,GAAG,CAAC;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;aAC/D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,IACE,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QACvB,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;QACxB,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EACxB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAiB;IACrC,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAkB;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAClD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;IAEvD,IAAI,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;QAC3B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,QAAQ;YACf,OAAO,EAAE,UAAU,IAAI,gBAAgB;YACvC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC;SAC9B,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,KAAK;QAAE,OAAO,IAAI,CAAC;IAE9B,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,gBAAgB;QACjD,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC;QACzB,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,SAAS;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY;IAK1B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QACpB,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;QACjC,OAAO;YACL,EAAE;YACF,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;YAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,gBAAgB;SACnD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { Command } from "commander";
4
+ import { listAccounts, resolveAccount } from "./accounts.js";
5
+ import { WxClawClient } from "./client.js";
6
+ import { VERSION } from "./version.js";
7
+ function readStdin() {
8
+ return readFileSync(0, "utf-8").trim();
9
+ }
10
+ function fail(msg, json) {
11
+ if (json) {
12
+ console.log(JSON.stringify({ ok: false, error: msg }));
13
+ }
14
+ else {
15
+ console.error(msg);
16
+ }
17
+ process.exit(1);
18
+ }
19
+ const program = new Command();
20
+ program
21
+ .name("wxclawbot")
22
+ .description("WeixinClawBot CLI - proactive messaging")
23
+ .version(VERSION);
24
+ program
25
+ .command("send")
26
+ .description("Send a message to a WeChat user")
27
+ .option("--to <userId>", "target user ID (default: bound user from account)")
28
+ .option("--text <message>", 'message text (use "-" to read from stdin)')
29
+ .option("--file <path>", "file or URL to send (image/video/file)")
30
+ .option("--account <id>", "account ID (default: first available)")
31
+ .option("--json", "output result as JSON")
32
+ .option("--dry-run", "preview without sending")
33
+ .argument("[text...]", "message text (alternative to --text)")
34
+ .action(async (args, opts) => {
35
+ let text = opts.text;
36
+ if (text === "-") {
37
+ text = readStdin();
38
+ }
39
+ else if (!text && !opts.file && args.length > 0) {
40
+ text = args.join(" ");
41
+ }
42
+ else if (!text && !opts.file) {
43
+ if (!process.stdin.isTTY) {
44
+ text = readStdin();
45
+ }
46
+ }
47
+ if (!text && !opts.file) {
48
+ fail("no message. use --text, --file, positional args, or pipe via stdin.", opts.json);
49
+ }
50
+ const account = resolveAccount(opts.account);
51
+ if (!account) {
52
+ fail("no account found. login via openclaw first, or set WXCLAW_TOKEN env var.", opts.json);
53
+ }
54
+ const to = opts.to || account.defaultTo;
55
+ if (!to) {
56
+ fail("no --to specified and no default user bound to this account.", opts.json);
57
+ }
58
+ const client = new WxClawClient({
59
+ baseUrl: account.baseUrl,
60
+ token: account.token,
61
+ botId: account.botId,
62
+ });
63
+ try {
64
+ let result;
65
+ if (opts.file) {
66
+ result = await client.sendFile(to, opts.file, {
67
+ text,
68
+ dryRun: opts.dryRun,
69
+ });
70
+ }
71
+ else {
72
+ result = await client.sendText(to, text, {
73
+ dryRun: opts.dryRun,
74
+ });
75
+ }
76
+ if (opts.json) {
77
+ console.log(JSON.stringify(result));
78
+ }
79
+ else if (result.ok) {
80
+ const prefix = opts.dryRun ? "[dry-run] would send" : "sent";
81
+ const what = opts.file ? `file ${opts.file}` : "text";
82
+ console.log(`${prefix} ${what} to ${to}`);
83
+ }
84
+ else {
85
+ console.error(`send failed: ${result.error}`);
86
+ process.exit(1);
87
+ }
88
+ }
89
+ catch (err) {
90
+ const msg = err instanceof Error ? err.message : String(err);
91
+ fail(`send failed: ${msg}`, opts.json);
92
+ }
93
+ });
94
+ program
95
+ .command("accounts")
96
+ .description("List available OpenClaw WeChat accounts")
97
+ .option("--json", "output as JSON")
98
+ .action((opts) => {
99
+ const accounts = listAccounts();
100
+ if (opts.json) {
101
+ console.log(JSON.stringify(accounts));
102
+ return;
103
+ }
104
+ if (accounts.length === 0) {
105
+ console.log("no accounts found. login via openclaw first.");
106
+ return;
107
+ }
108
+ for (const a of accounts) {
109
+ const status = a.configured ? "ok" : "no token";
110
+ console.log(` ${a.id} [${status}] ${a.baseUrl}`);
111
+ }
112
+ });
113
+ program.parse();
114
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface SendResult {
2
+ ok: boolean;
3
+ to: string;
4
+ clientId: string;
5
+ error?: string;
6
+ }
7
+ export declare class WxClawClient {
8
+ private baseUrl;
9
+ private token;
10
+ private botId;
11
+ constructor(opts: {
12
+ baseUrl: string;
13
+ token: string;
14
+ botId?: string;
15
+ });
16
+ sendText(to: string, text: string, opts?: {
17
+ dryRun?: boolean;
18
+ }): Promise<SendResult>;
19
+ sendFile(to: string, filePath: string, opts?: {
20
+ text?: string;
21
+ dryRun?: boolean;
22
+ }): Promise<SendResult>;
23
+ private sendItems;
24
+ private post;
25
+ }
package/dist/client.js ADDED
@@ -0,0 +1,114 @@
1
+ import crypto from "node:crypto";
2
+ import { readFileOrUrl, uploadFile } from "./media.js";
3
+ import { ItemType, MessageState, MessageType, } from "./types.js";
4
+ import { VERSION } from "./version.js";
5
+ const SEND_TIMEOUT_MS = 15_000;
6
+ const KNOWN_ERRORS = {
7
+ [-2]: "rate limited, try again later",
8
+ [-14]: "session expired, re-login via openclaw",
9
+ };
10
+ function randomUIN() {
11
+ const n = crypto.randomBytes(4).readUInt32BE(0);
12
+ return Buffer.from(String(n), "utf-8").toString("base64");
13
+ }
14
+ export class WxClawClient {
15
+ baseUrl;
16
+ token;
17
+ botId;
18
+ constructor(opts) {
19
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
20
+ this.token = opts.token;
21
+ this.botId = opts.botId ?? "";
22
+ }
23
+ async sendText(to, text, opts) {
24
+ const clientId = crypto.randomUUID();
25
+ if (opts?.dryRun) {
26
+ return { ok: true, to, clientId };
27
+ }
28
+ const items = [
29
+ { type: ItemType.TEXT, text_item: { text } },
30
+ ];
31
+ return this.sendItems(to, items, clientId);
32
+ }
33
+ async sendFile(to, filePath, opts) {
34
+ const clientId = crypto.randomUUID();
35
+ if (opts?.dryRun) {
36
+ return { ok: true, to, clientId };
37
+ }
38
+ const { data, name } = await readFileOrUrl(filePath);
39
+ const { item } = await uploadFile({
40
+ data,
41
+ filePath: name,
42
+ toUserId: to,
43
+ baseUrl: this.baseUrl,
44
+ token: this.token,
45
+ });
46
+ const items = [];
47
+ if (opts?.text) {
48
+ items.push({ type: ItemType.TEXT, text_item: { text: opts.text } });
49
+ }
50
+ items.push(item);
51
+ return this.sendItems(to, items, clientId);
52
+ }
53
+ async sendItems(to, items, clientId) {
54
+ const req = {
55
+ msg: {
56
+ from_user_id: this.botId,
57
+ to_user_id: to,
58
+ client_id: clientId,
59
+ message_type: MessageType.BOT,
60
+ message_state: MessageState.FINISH,
61
+ item_list: items,
62
+ },
63
+ base_info: { channel_version: VERSION },
64
+ };
65
+ const resp = await this.post("/ilink/bot/sendmessage", req);
66
+ const ret = typeof resp.ret === "number" ? resp.ret : 0;
67
+ if (ret !== 0) {
68
+ const hint = KNOWN_ERRORS[ret] ?? "";
69
+ const detail = hint ? ` (${hint})` : "";
70
+ return { ok: false, to, clientId, error: `ret=${ret}${detail}` };
71
+ }
72
+ return { ok: true, to, clientId };
73
+ }
74
+ async post(endpoint, body) {
75
+ const url = `${this.baseUrl}${endpoint}`;
76
+ const bodyStr = JSON.stringify(body);
77
+ const controller = new AbortController();
78
+ const timer = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
79
+ try {
80
+ const res = await fetch(url, {
81
+ method: "POST",
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ AuthorizationType: "ilink_bot_token",
85
+ Authorization: `Bearer ${this.token}`,
86
+ "X-WECHAT-UIN": randomUIN(),
87
+ },
88
+ body: bodyStr,
89
+ signal: controller.signal,
90
+ });
91
+ clearTimeout(timer);
92
+ const raw = await res.text();
93
+ if (!res.ok) {
94
+ const truncated = raw.length > 200 ? raw.slice(0, 200) + "..." : raw;
95
+ throw new Error(`HTTP ${res.status}: ${truncated}`);
96
+ }
97
+ try {
98
+ return JSON.parse(raw);
99
+ }
100
+ catch {
101
+ const truncated = raw.length > 200 ? raw.slice(0, 200) + "..." : raw;
102
+ throw new Error(`invalid JSON response (HTTP ${res.status}): ${truncated}`);
103
+ }
104
+ }
105
+ catch (err) {
106
+ clearTimeout(timer);
107
+ if (err instanceof Error && err.name === "AbortError") {
108
+ throw new Error(`request timeout after ${SEND_TIMEOUT_MS}ms`);
109
+ }
110
+ throw err;
111
+ }
112
+ }
113
+ }
114
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type MessageItem } from "./types.js";
2
+ export declare function aesEcbPaddedSize(plaintextSize: number): number;
3
+ export declare function encryptAesEcb(plaintext: Buffer, key: Buffer): Buffer;
4
+ export declare function classifyMedia(filePath: string): {
5
+ cdnType: number;
6
+ itemType: number;
7
+ };
8
+ export declare function uploadFile(opts: {
9
+ data: Buffer;
10
+ filePath: string;
11
+ toUserId: string;
12
+ baseUrl: string;
13
+ token: string;
14
+ cdnBaseUrl?: string;
15
+ }): Promise<{
16
+ item: MessageItem;
17
+ }>;
18
+ export declare function readFileOrUrl(filePath: string): Promise<{
19
+ data: Buffer;
20
+ name: string;
21
+ }>;
package/dist/media.js ADDED
@@ -0,0 +1,195 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { CDNMediaType, ItemType, } from "./types.js";
5
+ import { VERSION } from "./version.js";
6
+ const CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
7
+ const API_TIMEOUT_MS = 15_000;
8
+ const CDN_UPLOAD_TIMEOUT_MS = 60_000;
9
+ const CDN_MAX_RETRIES = 3;
10
+ const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
11
+ const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
12
+ const VIDEO_EXTS = new Set([".mp4", ".mov", ".webm", ".mkv", ".avi"]);
13
+ // PKCS7 padded ciphertext size for AES-128-ECB (16-byte blocks).
14
+ // Matches openclaw-weixin's formula. Note: weclaw Go uses a slightly
15
+ // different formula that over-reports by 16 bytes at block boundaries,
16
+ // but the server accepts both.
17
+ export function aesEcbPaddedSize(plaintextSize) {
18
+ return Math.ceil((plaintextSize + 1) / 16) * 16;
19
+ }
20
+ export function encryptAesEcb(plaintext, key) {
21
+ const cipher = crypto.createCipheriv("aes-128-ecb", key, null);
22
+ return Buffer.concat([cipher.update(plaintext), cipher.final()]);
23
+ }
24
+ export function classifyMedia(filePath) {
25
+ const ext = path.extname(filePath).toLowerCase();
26
+ if (IMAGE_EXTS.has(ext)) {
27
+ return { cdnType: CDNMediaType.IMAGE, itemType: ItemType.IMAGE };
28
+ }
29
+ if (VIDEO_EXTS.has(ext)) {
30
+ return { cdnType: CDNMediaType.VIDEO, itemType: ItemType.VIDEO };
31
+ }
32
+ return { cdnType: CDNMediaType.FILE, itemType: ItemType.FILE };
33
+ }
34
+ function filenameFromPath(filePath) {
35
+ return path.basename(filePath) || "file";
36
+ }
37
+ export async function uploadFile(opts) {
38
+ const { data, filePath, toUserId, baseUrl, token } = opts;
39
+ const cdnBaseUrl = opts.cdnBaseUrl || CDN_BASE_URL;
40
+ const { cdnType, itemType } = classifyMedia(filePath);
41
+ const uploaded = await uploadToCdn({
42
+ data,
43
+ toUserId,
44
+ cdnMediaType: cdnType,
45
+ baseUrl,
46
+ token,
47
+ cdnBaseUrl,
48
+ });
49
+ const media = {
50
+ encrypt_query_param: uploaded.downloadParam,
51
+ aes_key: Buffer.from(uploaded.aesKeyHex).toString("base64"),
52
+ encrypt_type: 1,
53
+ };
54
+ let item;
55
+ switch (itemType) {
56
+ case ItemType.IMAGE:
57
+ item = {
58
+ type: ItemType.IMAGE,
59
+ image_item: { media, mid_size: uploaded.cipherSize },
60
+ };
61
+ break;
62
+ case ItemType.VIDEO:
63
+ item = {
64
+ type: ItemType.VIDEO,
65
+ video_item: { media, video_size: uploaded.cipherSize },
66
+ };
67
+ break;
68
+ default:
69
+ item = {
70
+ type: ItemType.FILE,
71
+ file_item: {
72
+ media,
73
+ file_name: filenameFromPath(filePath),
74
+ len: String(uploaded.fileSize),
75
+ },
76
+ };
77
+ }
78
+ return { item };
79
+ }
80
+ async function fetchWithTimeout(url, init, timeoutMs) {
81
+ const controller = new AbortController();
82
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
83
+ try {
84
+ const res = await fetch(url, { ...init, signal: controller.signal });
85
+ return res;
86
+ }
87
+ finally {
88
+ clearTimeout(timer);
89
+ }
90
+ }
91
+ async function uploadToCdn(opts) {
92
+ const { data, toUserId, cdnMediaType, baseUrl, token, cdnBaseUrl } = opts;
93
+ const filekey = crypto.randomBytes(16).toString("hex");
94
+ const aeskey = crypto.randomBytes(16);
95
+ const aesKeyHex = aeskey.toString("hex");
96
+ const rawMd5 = crypto.createHash("md5").update(data).digest("hex");
97
+ const cipherSize = aesEcbPaddedSize(data.length);
98
+ const body = {
99
+ filekey,
100
+ media_type: cdnMediaType,
101
+ to_user_id: toUserId,
102
+ rawsize: data.length,
103
+ rawfilemd5: rawMd5,
104
+ filesize: cipherSize,
105
+ no_need_thumb: true,
106
+ aeskey: aesKeyHex,
107
+ base_info: { channel_version: VERSION },
108
+ };
109
+ const uin = crypto.randomBytes(4).readUInt32BE(0);
110
+ const uinHeader = Buffer.from(String(uin), "utf-8").toString("base64");
111
+ const uploadRes = await fetchWithTimeout(`${baseUrl}/ilink/bot/getuploadurl`, {
112
+ method: "POST",
113
+ headers: {
114
+ "Content-Type": "application/json",
115
+ AuthorizationType: "ilink_bot_token",
116
+ Authorization: `Bearer ${token}`,
117
+ "X-WECHAT-UIN": uinHeader,
118
+ },
119
+ body: JSON.stringify(body),
120
+ }, API_TIMEOUT_MS);
121
+ const uploadRespRaw = await uploadRes.text();
122
+ if (!uploadRes.ok) {
123
+ throw new Error(`getuploadurl HTTP ${uploadRes.status}: ${uploadRespRaw.slice(0, 200)}`);
124
+ }
125
+ let uploadResp;
126
+ try {
127
+ uploadResp = JSON.parse(uploadRespRaw);
128
+ }
129
+ catch {
130
+ throw new Error(`getuploadurl invalid JSON: ${uploadRespRaw.slice(0, 200)}`);
131
+ }
132
+ if ((uploadResp.ret ?? 0) !== 0 || !uploadResp.upload_param) {
133
+ throw new Error(`getuploadurl failed: ret=${uploadResp.ret} errmsg=${uploadResp.errmsg ?? "no upload_param"}`);
134
+ }
135
+ const ciphertext = encryptAesEcb(data, aeskey);
136
+ const cdnUrl = `${cdnBaseUrl}/upload?encrypted_query_param=${encodeURIComponent(uploadResp.upload_param)}&filekey=${encodeURIComponent(filekey)}`;
137
+ let downloadParam;
138
+ let lastError;
139
+ for (let attempt = 1; attempt <= CDN_MAX_RETRIES; attempt++) {
140
+ try {
141
+ const cdnRes = await fetchWithTimeout(cdnUrl, {
142
+ method: "POST",
143
+ headers: { "Content-Type": "application/octet-stream" },
144
+ body: new Uint8Array(ciphertext),
145
+ }, CDN_UPLOAD_TIMEOUT_MS);
146
+ if (cdnRes.status >= 400 && cdnRes.status < 500) {
147
+ throw new Error(`CDN upload client error ${cdnRes.status}`);
148
+ }
149
+ if (cdnRes.status !== 200) {
150
+ throw new Error(`CDN upload server error ${cdnRes.status}`);
151
+ }
152
+ downloadParam = cdnRes.headers.get("x-encrypted-param") ?? undefined;
153
+ if (!downloadParam) {
154
+ throw new Error("CDN response missing x-encrypted-param header");
155
+ }
156
+ break;
157
+ }
158
+ catch (err) {
159
+ lastError = err;
160
+ if (err instanceof Error && err.message.includes("client error"))
161
+ throw err;
162
+ if (attempt >= CDN_MAX_RETRIES)
163
+ throw err;
164
+ await new Promise((r) => setTimeout(r, 1000 * attempt));
165
+ }
166
+ }
167
+ if (!downloadParam) {
168
+ throw lastError instanceof Error
169
+ ? lastError
170
+ : new Error(`CDN upload failed after ${CDN_MAX_RETRIES} attempts`);
171
+ }
172
+ return { downloadParam, aesKeyHex, fileSize: data.length, cipherSize };
173
+ }
174
+ export async function readFileOrUrl(filePath) {
175
+ if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
176
+ const res = await fetchWithTimeout(filePath, {}, CDN_UPLOAD_TIMEOUT_MS);
177
+ if (!res.ok) {
178
+ throw new Error(`download ${filePath}: HTTP ${res.status}`);
179
+ }
180
+ const buf = Buffer.from(await res.arrayBuffer());
181
+ if (buf.length > MAX_FILE_SIZE) {
182
+ throw new Error(`file too large: ${buf.length} bytes (max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
183
+ }
184
+ const urlPath = new URL(filePath).pathname;
185
+ const name = path.basename(urlPath) || "file";
186
+ return { data: buf, name };
187
+ }
188
+ const stat = fs.statSync(filePath);
189
+ if (stat.size > MAX_FILE_SIZE) {
190
+ throw new Error(`file too large: ${stat.size} bytes (max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
191
+ }
192
+ const data = fs.readFileSync(filePath);
193
+ return { data, name: filePath };
194
+ }
195
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ export declare const MessageType: {
2
+ readonly USER: 1;
3
+ readonly BOT: 2;
4
+ };
5
+ export declare const MessageState: {
6
+ readonly NEW: 0;
7
+ readonly GENERATING: 1;
8
+ readonly FINISH: 2;
9
+ };
10
+ export declare const ItemType: {
11
+ readonly TEXT: 1;
12
+ readonly IMAGE: 2;
13
+ readonly VOICE: 3;
14
+ readonly FILE: 4;
15
+ readonly VIDEO: 5;
16
+ };
17
+ export declare const CDNMediaType: {
18
+ readonly IMAGE: 1;
19
+ readonly VIDEO: 2;
20
+ readonly FILE: 3;
21
+ };
22
+ type ValueOf<T> = T[keyof T];
23
+ export interface TextItem {
24
+ text: string;
25
+ }
26
+ export interface MediaInfo {
27
+ encrypt_query_param: string;
28
+ aes_key: string;
29
+ encrypt_type: number;
30
+ }
31
+ export interface ImageItem {
32
+ media: MediaInfo;
33
+ mid_size: number;
34
+ }
35
+ export interface VideoItem {
36
+ media: MediaInfo;
37
+ video_size: number;
38
+ }
39
+ export interface FileItem {
40
+ media: MediaInfo;
41
+ file_name: string;
42
+ len: string;
43
+ }
44
+ export interface MessageItem {
45
+ type: ValueOf<typeof ItemType>;
46
+ text_item?: TextItem;
47
+ image_item?: ImageItem;
48
+ video_item?: VideoItem;
49
+ file_item?: FileItem;
50
+ }
51
+ export interface SendMsg {
52
+ from_user_id: string;
53
+ to_user_id: string;
54
+ client_id: string;
55
+ message_type: ValueOf<typeof MessageType>;
56
+ message_state: ValueOf<typeof MessageState>;
57
+ item_list: MessageItem[];
58
+ context_token?: string;
59
+ }
60
+ export interface SendMessageReq {
61
+ msg: SendMsg;
62
+ base_info?: {
63
+ channel_version?: string;
64
+ };
65
+ }
66
+ export interface GetUploadUrlReq {
67
+ filekey: string;
68
+ media_type: ValueOf<typeof CDNMediaType>;
69
+ to_user_id: string;
70
+ rawsize: number;
71
+ rawfilemd5: string;
72
+ filesize: number;
73
+ no_need_thumb: boolean;
74
+ aeskey: string;
75
+ base_info?: {
76
+ channel_version?: string;
77
+ };
78
+ }
79
+ export interface GetUploadUrlResp {
80
+ ret?: number;
81
+ errmsg?: string;
82
+ upload_param?: string;
83
+ }
84
+ export interface AccountData {
85
+ token?: string;
86
+ savedAt?: string;
87
+ baseUrl?: string;
88
+ userId?: string;
89
+ }
90
+ export interface ResolvedAccount {
91
+ id: string;
92
+ token: string;
93
+ baseUrl: string;
94
+ botId: string;
95
+ defaultTo?: string;
96
+ }
97
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,22 @@
1
+ export const MessageType = {
2
+ USER: 1,
3
+ BOT: 2,
4
+ };
5
+ export const MessageState = {
6
+ NEW: 0,
7
+ GENERATING: 1,
8
+ FINISH: 2,
9
+ };
10
+ export const ItemType = {
11
+ TEXT: 1,
12
+ IMAGE: 2,
13
+ VOICE: 3,
14
+ FILE: 4,
15
+ VIDEO: 5,
16
+ };
17
+ export const CDNMediaType = {
18
+ IMAGE: 1,
19
+ VIDEO: 2,
20
+ FILE: 3,
21
+ };
22
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,CAAC;IACP,GAAG,EAAE,CAAC;CACE,CAAC;AAEX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,GAAG,EAAE,CAAC;IACN,UAAU,EAAE,CAAC;IACb,MAAM,EAAE,CAAC;CACD,CAAC;AAEX,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACA,CAAC;AAEX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;CACC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const VERSION: string;
@@ -0,0 +1,5 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ const pkg = require("../package.json");
4
+ export const VERSION = pkg.version;
5
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;AAE9D,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@claw-lab/wxclawbot-cli",
3
+ "version": "0.4.0",
4
+ "description": "WeixinClawBot CLI - send text, images, video, and files to WeChat users",
5
+ "type": "module",
6
+ "bin": {
7
+ "wxclawbot": "dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": "./dist/client.js",
11
+ "./accounts": "./dist/accounts.js",
12
+ "./media": "./dist/media.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "node --import tsx src/cli.ts",
17
+ "start": "node dist/cli.js",
18
+ "test": "node --import tsx --test src/accounts.test.ts src/media.test.ts",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "files": [
25
+ "dist/",
26
+ "LICENSE",
27
+ "README.md"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/lroolle/wxclawbot-cli.git"
32
+ },
33
+ "keywords": [
34
+ "wechat",
35
+ "weixin",
36
+ "openclaw",
37
+ "clawbot",
38
+ "weixinclawbot",
39
+ "cli",
40
+ "proactive-messaging"
41
+ ],
42
+ "dependencies": {
43
+ "commander": "^13.1.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.5.0",
47
+ "tsx": "^4.21.0",
48
+ "typescript": "^5.9.3"
49
+ },
50
+ "license": "MIT"
51
+ }