@agent-wechat/cli 0.14.0 → 0.15.1
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 +6 -0
- package/dist/cli.js +70 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -90,6 +90,12 @@ Send options:
|
|
|
90
90
|
|
|
91
91
|
Media options:
|
|
92
92
|
- `--output <path>` — save to a specific file path
|
|
93
|
+
- `--full` — require an original-resolution image
|
|
94
|
+
- `--thumbnail` — save the cached preview
|
|
95
|
+
- `--best` — explicitly choose the default best-available image behavior
|
|
96
|
+
|
|
97
|
+
Media retrieval does not open a chat. Use `wx chats open <chatId>` separately
|
|
98
|
+
if WeChat needs to cache a regular photo's standard-size copy.
|
|
93
99
|
|
|
94
100
|
### Sessions
|
|
95
101
|
|
package/dist/cli.js
CHANGED
|
@@ -3582,6 +3582,23 @@ var WeChatClient = class {
|
|
|
3582
3582
|
async sendMessage(params) {
|
|
3583
3583
|
return this.post("/api/messages/send", params);
|
|
3584
3584
|
}
|
|
3585
|
+
async createVoiceJob(chatId, audio, idempotencyKey) {
|
|
3586
|
+
const form = new FormData();
|
|
3587
|
+
form.set("chatId", chatId);
|
|
3588
|
+
form.set("audio", new Blob([new Uint8Array(audio)]), "voice-audio");
|
|
3589
|
+
const headers = { ...this.headers, "Idempotency-Key": idempotencyKey };
|
|
3590
|
+
delete headers["Content-Type"];
|
|
3591
|
+
const res = await fetch(`${this.base}/api/messages/voice`, { method: "POST", headers, body: form });
|
|
3592
|
+
if (!res.ok)
|
|
3593
|
+
throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
|
|
3594
|
+
return res.json();
|
|
3595
|
+
}
|
|
3596
|
+
async getVoiceJob(jobId) {
|
|
3597
|
+
return this.get(`/api/messages/voice/${encodeURIComponent(jobId)}`);
|
|
3598
|
+
}
|
|
3599
|
+
async cancelVoiceJob(jobId) {
|
|
3600
|
+
return this.post(`/api/messages/voice/${encodeURIComponent(jobId)}/cancel`);
|
|
3601
|
+
}
|
|
3585
3602
|
// ---- Debug ----
|
|
3586
3603
|
async screenshot() {
|
|
3587
3604
|
return this.get("/api/debug/screenshot");
|
|
@@ -7912,7 +7929,7 @@ import qrTerminal from "qrcode-terminal";
|
|
|
7912
7929
|
import os from "os";
|
|
7913
7930
|
import path from "path";
|
|
7914
7931
|
import { fileURLToPath } from "url";
|
|
7915
|
-
var VERSION = "0.
|
|
7932
|
+
var VERSION = "0.15.1";
|
|
7916
7933
|
var CONTAINER_NAME = "agent-wechat";
|
|
7917
7934
|
var GHCR_IMAGE = "ghcr.io/thisnick/agent-wechat";
|
|
7918
7935
|
var DEFAULT_PORT = 6174;
|
|
@@ -8058,14 +8075,28 @@ var messagesCmd = program2.command("messages").description("Message commands");
|
|
|
8058
8075
|
messagesCmd.command("list <chatId>").description("List messages for a chat").option("-l, --limit <number>", "Maximum number of messages", "50").option("-o, --offset <number>", "Skip first N messages", "0").option("-j, --json", "Output as JSON").action(async (chatId, opts) => {
|
|
8059
8076
|
await cmdMessages(getClient(), chatId, parseInt(opts.limit, 10), parseInt(opts.offset, 10), opts.json ?? false);
|
|
8060
8077
|
});
|
|
8061
|
-
messagesCmd.command("media <chatId> <localId>").description("Save a media attachment (
|
|
8062
|
-
|
|
8078
|
+
messagesCmd.command("media <chatId> <localId>").description("Save a media attachment (best available image, file, or voice)").option("--full", "Require the original-resolution image").option("--thumbnail", "Retrieve an image thumbnail instead of full resolution").option("--best", "Retrieve the best available image (default)").option("-o, --output <path>", "Output file path").action(async (chatId, localIdStr, opts) => {
|
|
8079
|
+
if (Number(!!opts.full) + Number(!!opts.thumbnail) + Number(!!opts.best) > 1) {
|
|
8080
|
+
console.error("Choose only one of --full, --thumbnail, or --best.");
|
|
8081
|
+
process.exit(1);
|
|
8082
|
+
}
|
|
8083
|
+
const quality = opts.full ? "full" : opts.thumbnail ? "thumbnail" : "best";
|
|
8084
|
+
await cmdMedia(getClient(), chatId, parseInt(localIdStr, 10), opts.output, quality);
|
|
8063
8085
|
});
|
|
8064
|
-
messagesCmd.command("send <chatId>").description("Send a message to a chat").option("--text <text>", "Text message to send").option("--image <path>", "Image file to send").option("--file <path>", "File to send").action(async (chatId, opts) => {
|
|
8065
|
-
if (!opts.text && !opts.image && !opts.file) {
|
|
8066
|
-
console.error("Must provide --text, --image, or --
|
|
8086
|
+
messagesCmd.command("send <chatId>").description("Send a message to a chat").option("--text <text>", "Text message to send").option("--image <path>", "Image file to send").option("--file <path>", "File to send").option("--voice <path>", "Record audio as one or more voice notes").option("--detach", "Return after the voice job is queued").action(async (chatId, opts) => {
|
|
8087
|
+
if (!opts.text && !opts.image && !opts.file && !opts.voice) {
|
|
8088
|
+
console.error("Must provide --text, --image, --file, or --voice");
|
|
8067
8089
|
process.exit(1);
|
|
8068
8090
|
}
|
|
8091
|
+
if (opts.voice) {
|
|
8092
|
+
if (opts.text || opts.image || opts.file) throw new Error("--voice must be sent on its own");
|
|
8093
|
+
const audio = fs.readFileSync(opts.voice);
|
|
8094
|
+
const job = await getClient().createVoiceJob(chatId, audio, randomBytes(16).toString("hex"));
|
|
8095
|
+
console.log(`Voice job: ${job.jobId}`);
|
|
8096
|
+
if (!opts.detach) await waitVoiceJob(getClient(), job.jobId);
|
|
8097
|
+
return;
|
|
8098
|
+
}
|
|
8099
|
+
if (opts.detach) throw new Error("--detach requires --voice");
|
|
8069
8100
|
let image;
|
|
8070
8101
|
if (opts.image) {
|
|
8071
8102
|
if (!fs.existsSync(opts.image)) {
|
|
@@ -8088,6 +8119,34 @@ messagesCmd.command("send <chatId>").description("Send a message to a chat").opt
|
|
|
8088
8119
|
}
|
|
8089
8120
|
await cmdSend(getClient(), chatId, opts.text, image, file);
|
|
8090
8121
|
});
|
|
8122
|
+
var voiceCmd = messagesCmd.command("voice").description("Inspect or cancel voice jobs");
|
|
8123
|
+
voiceCmd.command("status <jobId>").action(async (jobId) => {
|
|
8124
|
+
console.log(JSON.stringify(await getClient().getVoiceJob(jobId), null, 2));
|
|
8125
|
+
});
|
|
8126
|
+
voiceCmd.command("cancel <jobId>").action(async (jobId) => {
|
|
8127
|
+
const job = await getClient().cancelVoiceJob(jobId);
|
|
8128
|
+
console.log(`Cancellation requested for ${job.jobId}`);
|
|
8129
|
+
});
|
|
8130
|
+
async function waitVoiceJob(client, jobId) {
|
|
8131
|
+
let lastProgress = "";
|
|
8132
|
+
for (; ; ) {
|
|
8133
|
+
const job = await client.getVoiceJob(jobId);
|
|
8134
|
+
const verified = job.chunks.filter((chunk) => chunk.status === "verified").length;
|
|
8135
|
+
const progress = `${job.status}: ${verified}/${job.chunks.length} notes verified`;
|
|
8136
|
+
if (progress !== lastProgress) {
|
|
8137
|
+
console.log(progress);
|
|
8138
|
+
lastProgress = progress;
|
|
8139
|
+
}
|
|
8140
|
+
if (job.status === "completed") {
|
|
8141
|
+
console.log(`Message IDs: ${job.chunks.map((chunk) => chunk.messageId).join(", ")}`);
|
|
8142
|
+
return;
|
|
8143
|
+
}
|
|
8144
|
+
if (["failed", "cancelled", "needs_review"].includes(job.status)) {
|
|
8145
|
+
throw new Error(`${job.status}: ${job.error ?? "No further details"}. Job ${jobId}; do not resend the whole recording.`);
|
|
8146
|
+
}
|
|
8147
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
8148
|
+
}
|
|
8149
|
+
}
|
|
8091
8150
|
program2.command("update").description("Update the agent-server binary in the running container to match CLI version").action(cmdUpdate);
|
|
8092
8151
|
program2.command("screenshot").description("Save screenshot to file").argument("[file]", "Output file path", "screenshot.png").action(async (file) => {
|
|
8093
8152
|
await cmdScreenshot(getClient(), file);
|
|
@@ -8336,8 +8395,8 @@ async function cmdMessages(client, chatId, limit = 50, offset = 0, json = false)
|
|
|
8336
8395
|
console.log(`
|
|
8337
8396
|
${messages.length} message(s) shown.`);
|
|
8338
8397
|
}
|
|
8339
|
-
async function cmdMedia(client, chatId, localId, outputPath,
|
|
8340
|
-
const result = await client.getMedia(chatId, localId,
|
|
8398
|
+
async function cmdMedia(client, chatId, localId, outputPath, quality = "best") {
|
|
8399
|
+
const result = await client.getMedia(chatId, localId, quality);
|
|
8341
8400
|
if (result.type === "unsupported") {
|
|
8342
8401
|
console.error("No media found for this message (unsupported type or not found).");
|
|
8343
8402
|
process.exit(1);
|
|
@@ -8350,9 +8409,10 @@ async function cmdMedia(client, chatId, localId, outputPath, thumbnail = false)
|
|
|
8350
8409
|
if (result.data) {
|
|
8351
8410
|
const buffer = Buffer.from(result.data, "base64");
|
|
8352
8411
|
fs.writeFileSync(outFile, buffer);
|
|
8353
|
-
|
|
8412
|
+
const variant = result.type === "image" && result.quality ? ` (${result.quality})` : "";
|
|
8413
|
+
console.log(`Saved ${result.type}${variant} to ${outFile} (${buffer.length} bytes)`);
|
|
8354
8414
|
} else if (result.type === "image") {
|
|
8355
|
-
console.error(`Requested image ${
|
|
8415
|
+
console.error(`Requested image ${quality} is not yet available.${quality === "full" ? " Regular phone sends may have no Original copy; try without --full." : ""}`);
|
|
8356
8416
|
process.exit(1);
|
|
8357
8417
|
} else if (result.type === "video") {
|
|
8358
8418
|
console.error("Video not yet downloaded by WeChat. Try playing the video in the app first.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-wechat/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"wx": "./dist/cli.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@types/node": "^22",
|
|
18
18
|
"@types/qrcode-terminal": "^0.12.2",
|
|
19
19
|
"typescript": "^5.4.5",
|
|
20
|
-
"@agent-wechat/shared": "0.
|
|
20
|
+
"@agent-wechat/shared": "0.3.1"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public",
|