@wirecat/whatsapp-cli 0.4.0 → 0.7.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 +9 -3
- package/dist/adapter/adapter.d.ts +4 -2
- package/dist/adapter/adapter.d.ts.map +1 -1
- package/dist/adapter/adapter.js +20 -406
- package/dist/adapter/adapter.js.map +1 -1
- package/dist/adapter/channels.d.ts.map +1 -1
- package/dist/adapter/channels.js.map +1 -1
- package/dist/adapter/comments.d.ts +5 -0
- package/dist/adapter/comments.d.ts.map +1 -0
- package/dist/adapter/comments.js +14 -0
- package/dist/adapter/comments.js.map +1 -0
- package/dist/adapter/discovery.d.ts +5 -0
- package/dist/adapter/discovery.d.ts.map +1 -0
- package/dist/adapter/discovery.js +28 -0
- package/dist/adapter/discovery.js.map +1 -0
- package/dist/adapter/errors.d.ts +1 -1
- package/dist/adapter/errors.d.ts.map +1 -1
- package/dist/adapter/errors.js +4 -2
- package/dist/adapter/errors.js.map +1 -1
- package/dist/adapter/groups.d.ts.map +1 -1
- package/dist/adapter/groups.js +1 -1
- package/dist/adapter/groups.js.map +1 -1
- package/dist/adapter/listen.d.ts +11 -0
- package/dist/adapter/listen.d.ts.map +1 -0
- package/dist/adapter/listen.js +264 -0
- package/dist/adapter/listen.js.map +1 -0
- package/dist/adapter/map.d.ts +1 -1
- package/dist/adapter/map.d.ts.map +1 -1
- package/dist/adapter/map.js +13 -22
- package/dist/adapter/map.js.map +1 -1
- package/dist/adapter/polls.d.ts +2 -1
- package/dist/adapter/polls.d.ts.map +1 -1
- package/dist/adapter/polls.js +14 -3
- package/dist/adapter/polls.js.map +1 -1
- package/dist/adapter/privacy.d.ts.map +1 -1
- package/dist/adapter/privacy.js +26 -11
- package/dist/adapter/privacy.js.map +1 -1
- package/dist/adapter/sends.d.ts +14 -0
- package/dist/adapter/sends.d.ts.map +1 -0
- package/dist/adapter/sends.js +156 -0
- package/dist/adapter/sends.js.map +1 -0
- package/dist/adapter/settings.js +4 -1
- package/dist/adapter/settings.js.map +1 -1
- package/dist/app.d.ts +8 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +20 -1
- package/dist/app.js.map +1 -1
- package/dist/bot/adapter.d.ts +18 -0
- package/dist/bot/adapter.d.ts.map +1 -0
- package/dist/bot/adapter.js +74 -0
- package/dist/bot/adapter.js.map +1 -0
- package/dist/bot/generated/definitions.d.ts +3 -0
- package/dist/bot/generated/definitions.d.ts.map +1 -0
- package/dist/bot/generated/definitions.js +9254 -0
- package/dist/bot/generated/definitions.js.map +1 -0
- package/dist/bot/generated/manifest.d.ts +3 -0
- package/dist/bot/generated/manifest.d.ts.map +1 -0
- package/dist/bot/generated/manifest.js +2614 -0
- package/dist/bot/generated/manifest.js.map +1 -0
- package/dist/bot/generated/schemas.d.ts +803 -0
- package/dist/bot/generated/schemas.d.ts.map +1 -0
- package/dist/bot/generated/schemas.js +3052 -0
- package/dist/bot/generated/schemas.js.map +1 -0
- package/dist/bot/generated/types.d.ts +3439 -0
- package/dist/bot/generated/types.d.ts.map +1 -0
- package/dist/bot/generated/types.js +5 -0
- package/dist/bot/generated/types.js.map +1 -0
- package/dist/bot/graph.d.ts +53 -0
- package/dist/bot/graph.d.ts.map +1 -0
- package/dist/bot/graph.js +141 -0
- package/dist/bot/graph.js.map +1 -0
- package/dist/bot/messaging.d.ts +30 -0
- package/dist/bot/messaging.d.ts.map +1 -0
- package/dist/bot/messaging.js +124 -0
- package/dist/bot/messaging.js.map +1 -0
- package/dist/bot/mime.d.ts +3 -0
- package/dist/bot/mime.d.ts.map +1 -0
- package/dist/bot/mime.js +20 -0
- package/dist/bot/mime.js.map +1 -0
- package/dist/bot/webhook.d.ts +13 -0
- package/dist/bot/webhook.d.ts.map +1 -0
- package/dist/bot/webhook.js +148 -0
- package/dist/bot/webhook.js.map +1 -0
- package/dist/commands/bot-templates.d.ts +9 -0
- package/dist/commands/bot-templates.d.ts.map +1 -0
- package/dist/commands/bot-templates.js +141 -0
- package/dist/commands/bot-templates.js.map +1 -0
- package/dist/commands/bot.d.ts +14 -0
- package/dist/commands/bot.d.ts.map +1 -0
- package/dist/commands/bot.js +208 -0
- package/dist/commands/bot.js.map +1 -0
- package/dist/commands/session.d.ts +0 -44
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +15 -141
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +9 -8
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/update.d.ts +2 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/diagnose.d.ts +6 -41
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js.map +1 -1
- package/dist/engine/channels.d.ts +1 -0
- package/dist/engine/channels.d.ts.map +1 -1
- package/dist/engine/contract.d.ts +32 -2
- package/dist/engine/contract.d.ts.map +1 -1
- package/dist/engine/contract.js +9 -1
- package/dist/engine/contract.js.map +1 -1
- package/dist/engine/discovery.d.ts +34 -0
- package/dist/engine/discovery.d.ts.map +1 -0
- package/dist/engine/discovery.js +22 -0
- package/dist/engine/discovery.js.map +1 -0
- package/dist/engine/events.d.ts +1 -1
- package/dist/engine/events.d.ts.map +1 -1
- package/dist/engine/events.js.map +1 -1
- package/dist/engine/go-engine.d.ts.map +1 -1
- package/dist/engine/go-engine.js +4 -0
- package/dist/engine/go-engine.js.map +1 -1
- package/dist/messenger.d.ts +4 -1
- package/dist/messenger.d.ts.map +1 -1
- package/dist/messenger.js +8 -10
- package/dist/messenger.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +8 -2
- package/dist/program.js.map +1 -1
- package/dist/session.d.ts +56 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +136 -0
- package/dist/session.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +16 -11
- package/skills/whatsapp-cli/SKILL.md +16 -13
package/dist/session.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { CliError, indent } from "@wirecat/cli-base";
|
|
3
|
+
import { atTerminal, loginQr, readSecret } from "@wirecat/cli-core";
|
|
4
|
+
import { asFirstWord, environmentOf, secretInputOf, } from "@wirecat/cli-core/cli";
|
|
5
|
+
import { linkDevice } from "./messenger.js";
|
|
6
|
+
import { privateStore } from "./paths.js";
|
|
7
|
+
/**
|
|
8
|
+
* Shown once, before the code that links the device. It ignores `--quiet` the way the QR code does: it belongs to
|
|
9
|
+
* the prompt the person is answering, and after the link it is too late to read it.
|
|
10
|
+
*/
|
|
11
|
+
export const BAN_WARNING = [
|
|
12
|
+
"Before you link: wa is not an official WhatsApp app, and WhatsApp may ban an account that uses one.",
|
|
13
|
+
"It also bans accounts that send in bulk or write to people who do not know them.",
|
|
14
|
+
"wa writes only when a command asks, sends at most 30 messages an hour by default, and holds every",
|
|
15
|
+
"write when WhatsApp says to slow down or restricts the account. Never send in bulk or to strangers.",
|
|
16
|
+
].join("\n");
|
|
17
|
+
/** The phone pushes its history right after linking, once; how long to keep receiving it. */
|
|
18
|
+
export const HISTORY_WAIT = { firstMs: 120_000, quietMs: 45_000, slowMs: 20_000 };
|
|
19
|
+
/**
|
|
20
|
+
* Saves what the phone pushes until it goes quiet or `signal` aborts. Live messages that arrive meanwhile are saved
|
|
21
|
+
* too, through the same connection.
|
|
22
|
+
*/
|
|
23
|
+
export const receiveHistory = async (adapter, wait, onSlow, signal) => {
|
|
24
|
+
const received = { chats: 0, messages: 0, people: 0 };
|
|
25
|
+
const stop = new AbortController();
|
|
26
|
+
const end = () => stop.abort();
|
|
27
|
+
let timer = setTimeout(end, wait.firstMs);
|
|
28
|
+
const slow = wait.slowMs === undefined ? undefined : setTimeout(onSlow, wait.slowMs);
|
|
29
|
+
signal?.addEventListener("abort", end, { once: true });
|
|
30
|
+
try {
|
|
31
|
+
await Promise.all([
|
|
32
|
+
adapter.feed?.((batch) => {
|
|
33
|
+
received.chats += batch.chats?.length ?? 0;
|
|
34
|
+
received.messages += batch.messages?.length ?? 0;
|
|
35
|
+
received.people += batch.people?.length ?? 0;
|
|
36
|
+
clearTimeout(slow);
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
timer = setTimeout(end, wait.quietMs);
|
|
39
|
+
}, stop.signal),
|
|
40
|
+
adapter.watch?.(() => { }, stop.signal),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
clearTimeout(slow);
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
signal?.removeEventListener("abort", end);
|
|
47
|
+
}
|
|
48
|
+
return received;
|
|
49
|
+
};
|
|
50
|
+
/** Links the profile's device unless it is linked already, then keeps the history the phone pushes once. */
|
|
51
|
+
export const startSession = async (command, context, { method, command: typed = "wa session start", signal, indent: pad = 0, stopEndsWait = false }) => {
|
|
52
|
+
const input = context.stdin;
|
|
53
|
+
const terminal = atTerminal(input);
|
|
54
|
+
const unanswerable = () => new CliError("validation_error", `${typed} asks questions — run it in a terminal`, {
|
|
55
|
+
reason: "input.required",
|
|
56
|
+
});
|
|
57
|
+
// A device that is linked already asks nothing, so only a profile without one is refused before the engine starts.
|
|
58
|
+
if (!terminal && method === "phone" && !existsSync(privateStore(context.profile, context.env)))
|
|
59
|
+
throw unanswerable();
|
|
60
|
+
const qr = loginQr({
|
|
61
|
+
diagnostic: context.streams.diagnostic,
|
|
62
|
+
note: context.renderer.note,
|
|
63
|
+
indent: pad,
|
|
64
|
+
terminal,
|
|
65
|
+
json: context.format !== "pretty",
|
|
66
|
+
});
|
|
67
|
+
const environment = environmentOf(command);
|
|
68
|
+
const linked = await linkDevice(command, context, {
|
|
69
|
+
method,
|
|
70
|
+
beforeLink: () => {
|
|
71
|
+
signal?.throwIfAborted();
|
|
72
|
+
if (!terminal && method === "phone")
|
|
73
|
+
throw unanswerable();
|
|
74
|
+
context.streams.diagnostic(indent(BAN_WARNING, pad));
|
|
75
|
+
},
|
|
76
|
+
phone: async () => {
|
|
77
|
+
if (!terminal)
|
|
78
|
+
throw new CliError("validation_error", `${typed} phone needs a terminal to ask for the number`, {
|
|
79
|
+
reason: "input.required",
|
|
80
|
+
});
|
|
81
|
+
const number = await readSecret(`${" ".repeat(pad)}phone number, international format: `, {
|
|
82
|
+
...secretInputOf(command),
|
|
83
|
+
echo: true,
|
|
84
|
+
...(signal === undefined ? {} : { signal }),
|
|
85
|
+
});
|
|
86
|
+
// WhatsApp reads the leading digits as the country code, so a number typed without one pairs someone else's.
|
|
87
|
+
if (!number.trim().startsWith("+"))
|
|
88
|
+
throw new CliError("validation_error", "write the phone number with + and the country code, such as +34", {
|
|
89
|
+
param: "phone",
|
|
90
|
+
reason: "input.invalid",
|
|
91
|
+
});
|
|
92
|
+
return number;
|
|
93
|
+
},
|
|
94
|
+
onCode: ({ login, code }) => {
|
|
95
|
+
if (login === "pair") {
|
|
96
|
+
context.renderer.note(`pairing code: ${code}\n` +
|
|
97
|
+
"On the phone: tap the WhatsApp notification, or Settings > Linked devices > Link a device >\n" +
|
|
98
|
+
"Link with phone number instead, and type the code. It expires in about two minutes.");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
qr.show(code, "scan in WhatsApp → Settings → Linked devices → Link a device");
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
signal?.throwIfAborted();
|
|
105
|
+
const session = { profile: context.profile, account: linked.account, alreadyLinked: linked.alreadyLinked };
|
|
106
|
+
if (!linked.alreadyLinked) {
|
|
107
|
+
context.renderer.note("receiving history from the phone; keep WhatsApp open on it. Ctrl+C stops — `serve` picks up the rest.");
|
|
108
|
+
const stop = stopEndsWait ? environment.untilStopped?.() : environment.signal;
|
|
109
|
+
const ended = AbortSignal.any([signal, stop].filter((one) => one !== undefined));
|
|
110
|
+
session.received = await context.withMessenger((adapter) => receiveHistory(adapter, environment.historyWait ?? HISTORY_WAIT, () => context.renderer.note("still waiting for the phone's history — keep WhatsApp open on the phone; it can take a minute"), ended), { listen: true });
|
|
111
|
+
}
|
|
112
|
+
return session;
|
|
113
|
+
};
|
|
114
|
+
export const endSession = async (context) => {
|
|
115
|
+
const store = privateStore(context.profile, context.env);
|
|
116
|
+
if (!existsSync(store)) {
|
|
117
|
+
return { profile: context.profile, ended: false };
|
|
118
|
+
}
|
|
119
|
+
let dropped = false;
|
|
120
|
+
try {
|
|
121
|
+
await context.withMessenger((adapter) => adapter.logout());
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error instanceof CliError && error.details.reason === "device.in-use")
|
|
125
|
+
throw new CliError("provider_unavailable", `${error.message} — stop it first (\`wa ${asFirstWord(context.profile)}server stop\` for serve, or Ctrl+C ` +
|
|
126
|
+
"where it runs), then end the session", { ...error.details, sent: false });
|
|
127
|
+
// The phone may have removed the device already; the keys here are then worth nothing, and still go.
|
|
128
|
+
if (!(error instanceof CliError && error.code === "authentication_error"))
|
|
129
|
+
throw error;
|
|
130
|
+
dropped = true;
|
|
131
|
+
}
|
|
132
|
+
for (const suffix of ["", "-wal", "-shm", ".lock"])
|
|
133
|
+
rmSync(`${store}${suffix}`, { force: true });
|
|
134
|
+
return { profile: context.profile, ended: true, ...(dropped ? { alreadyDropped: true } : {}) };
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.7.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.7.0";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/whatsapp-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A personal WhatsApp account from the terminal, as a linked device — built for agents and scripts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"test:matrix": "vitest run >/dev/null && pnpm build >/dev/null && cli-dev test-matrix --program dist/program.js --untested scripts/test-matrix-untested.ts --name wa --check",
|
|
35
35
|
"test:slow": "vitest run --reporter=json --outputFile=coverage/tests.json >/dev/null; cli-dev slow-tests",
|
|
36
36
|
"engine:test": "cd engine && unformatted=$(\"$(go env GOROOT)/bin/gofmt\" -l .) && { test -z \"$unformatted\" || { echo \"not gofmt-formatted: $unformatted\" >&2; exit 1; }; } && go vet ./... && go test ./...",
|
|
37
|
+
"bot:spec:sync": "node --experimental-strip-types scripts/bot/sync-spec.ts",
|
|
38
|
+
"bot:generate": "node --experimental-strip-types scripts/bot/generate.ts",
|
|
39
|
+
"bot:generate:check": "node --experimental-strip-types scripts/bot/generate.ts --check",
|
|
37
40
|
"generate": "pnpm build && node --experimental-strip-types scripts/commands.ts",
|
|
38
41
|
"smoke:bun": "bun run scripts/smoke.ts",
|
|
39
42
|
"release:check": "pnpm build && node --experimental-strip-types scripts/release-check.ts",
|
|
@@ -50,6 +53,7 @@
|
|
|
50
53
|
"docs:lint": "markdownlint-cli2",
|
|
51
54
|
"docs:spell": "cspell lint --config cspell.json --no-progress README.md",
|
|
52
55
|
"ci:quick": "pnpm typecheck && pnpm build && pnpm lint:commands",
|
|
56
|
+
"contracts:upgrade": "pnpm --dir contracts/previous install --frozen-lockfile --ignore-scripts && cli-contract contracts/upgrade.json",
|
|
53
57
|
"contracts:check": "cli-contract contracts/plan.json",
|
|
54
58
|
"security:no-leak": "cli-testing security no-leak . contracts/no-leak.json",
|
|
55
59
|
"lint:commands": "cli-dev lint --cli dist/bin/wa.js",
|
|
@@ -57,18 +61,18 @@
|
|
|
57
61
|
"engine:packages": "node scripts/engine-packages.mjs build"
|
|
58
62
|
},
|
|
59
63
|
"dependencies": {
|
|
60
|
-
"@wirecat/cli-base": "0.
|
|
61
|
-
"@wirecat/cli-core": "0.
|
|
64
|
+
"@wirecat/cli-base": "0.50.2",
|
|
65
|
+
"@wirecat/cli-core": "0.406.0",
|
|
62
66
|
"commander": "15.0.0",
|
|
63
67
|
"valibot": "1.5.0"
|
|
64
68
|
},
|
|
65
69
|
"optionalDependencies": {
|
|
66
|
-
"@wirecat/whatsapp-engine-darwin-arm64": "0.
|
|
67
|
-
"@wirecat/whatsapp-engine-darwin-x64": "0.
|
|
68
|
-
"@wirecat/whatsapp-engine-linux-arm64": "0.
|
|
69
|
-
"@wirecat/whatsapp-engine-linux-x64": "0.
|
|
70
|
-
"@wirecat/whatsapp-engine-win32-arm64": "0.
|
|
71
|
-
"@wirecat/whatsapp-engine-win32-x64": "0.
|
|
70
|
+
"@wirecat/whatsapp-engine-darwin-arm64": "0.7.0",
|
|
71
|
+
"@wirecat/whatsapp-engine-darwin-x64": "0.7.0",
|
|
72
|
+
"@wirecat/whatsapp-engine-linux-arm64": "0.7.0",
|
|
73
|
+
"@wirecat/whatsapp-engine-linux-x64": "0.7.0",
|
|
74
|
+
"@wirecat/whatsapp-engine-win32-arm64": "0.7.0",
|
|
75
|
+
"@wirecat/whatsapp-engine-win32-x64": "0.7.0"
|
|
72
76
|
},
|
|
73
77
|
"devDependencies": {
|
|
74
78
|
"@biomejs/biome": "2.5.15",
|
|
@@ -77,13 +81,14 @@
|
|
|
77
81
|
"@cspell/dict-ru_ru": "2.3.3",
|
|
78
82
|
"@types/node": "^22.20.4",
|
|
79
83
|
"@vitest/coverage-v8": "^5.0.3",
|
|
80
|
-
"@wirecat/cli-testing": "0.5.
|
|
84
|
+
"@wirecat/cli-testing": "0.5.3",
|
|
81
85
|
"cspell": "10.3.6",
|
|
82
86
|
"lefthook": "2.1.16",
|
|
83
87
|
"markdownlint-cli2": "0.23.3",
|
|
84
88
|
"rumdl": "^0.2.79",
|
|
85
89
|
"typescript": "^7.0.2",
|
|
86
|
-
"vitest": "^5.0.3"
|
|
90
|
+
"vitest": "^5.0.3",
|
|
91
|
+
"yaml": "^2.9.1"
|
|
87
92
|
},
|
|
88
93
|
"publishConfig": {
|
|
89
94
|
"access": "public"
|
|
@@ -32,11 +32,11 @@ account, older history of one chat (`messages sync`), sending text (with `--repl
|
|
|
32
32
|
`messages forward`, `messages transcribe --local`, `messages star|unstar`, `messages pin|unpin` (every pin tells the chat; `--expire-time 24h|7d|30d`), `reactions add|remove`,
|
|
33
33
|
`polls create|show|vote|voters`, `chats mark-read`, `chats mute|unmute`, `chats archive|unarchive|pin|unpin|mark-unread`,
|
|
34
34
|
`chats delete|clear` (they ask first; not over MCP),
|
|
35
|
-
`account update` (name, about and a square JPEG photo), `metadata
|
|
36
|
-
`tags
|
|
35
|
+
`account update` (name, about and a square JPEG photo), `metadata sync` (a group's title and description, for
|
|
36
|
+
`tags rebuild`), group admin
|
|
37
37
|
(`chats create|join|leave|update`, a group photo by `chats update --photo` (a square JPEG of 190 to 720 pixels a
|
|
38
|
-
side) or `--remove-photo`, `chats
|
|
39
|
-
`chats admins add|remove`, `chats requests list|
|
|
38
|
+
side) or `--remove-photo`, `chats links show|reset`, `chats members list|fetch|add|remove`,
|
|
39
|
+
`chats admins add|remove`, `chats requests list|confirm|reject`), `contacts info|block|unblock|lookup|sync`, `account privacy show|set`,
|
|
40
40
|
`doctor --online`, `serve` and `watch`. Everything that reads the local store works.
|
|
41
41
|
`account privacy set` has WhatsApp's settings only (last seen, online, photo, about, read receipts, groups, calls);
|
|
42
42
|
`--read-receipts off` also stops the owner seeing others' receipts, and `contacts_except` is shown, never set.
|
|
@@ -50,8 +50,8 @@ store: the joins, leaves, adds, removals, admin changes and titles the phone pus
|
|
|
50
50
|
`stats chats show` counts in `members`; one from before the link the phone did not push is missing. `chats members
|
|
51
51
|
history` shows who joined or left between member fetches. A group in a WhatsApp community names it in `parent_chat_id`, and the
|
|
52
52
|
community itself is a group with `provider_metadata.community: true`, in the store (`chats list`, `chats show`) and
|
|
53
|
-
in the answers of `chats create|join|update`. `chats mark-read`
|
|
54
|
-
|
|
53
|
+
in the answers of `chats create|join|update`. `chats mark-read` clears the unread mark up to the chosen message
|
|
54
|
+
and sends a read receipt.
|
|
55
55
|
A channel (`…@newsletter`) is followed with `chats join <whatsapp.com/channel link>` and left with `chats leave`;
|
|
56
56
|
`messages sync <channel>` reads its posts from WhatsApp's servers, and every post's sender is the channel.
|
|
57
57
|
`messages send <channel> <text>` posts, with one file (`--photo`, `--file`, `--voice`) or none, only where the owner
|
|
@@ -61,7 +61,7 @@ edit or deletion in a channel.
|
|
|
61
61
|
**Group changes are writes the members see**, and go through the profile's permissions like a send. `chats create`
|
|
62
62
|
and `members add` refuse anyone the owner never wrote to and has not saved — WhatsApp bans accounts that add
|
|
63
63
|
strangers; never work round it. `admins add` takes no `--can`: a WhatsApp admin has every right.
|
|
64
|
-
`chats update --disappear-
|
|
64
|
+
`chats update --disappear-after` takes 24h, 7d, 90d or off, nothing else. A chat or person named by name is looked up
|
|
65
65
|
in the store first (part of the name is enough; several matches are refused), then in WhatsApp's chat list; a group
|
|
66
66
|
the store does not hold yet needs its `…@g.us` id.
|
|
67
67
|
`contacts lookup` takes one number on stdin, at most one every 10 seconds per profile; never loop it.
|
|
@@ -109,7 +109,7 @@ the store does not hold yet needs its `…@g.us` id.
|
|
|
109
109
|
|
|
110
110
|
## Machine execution
|
|
111
111
|
|
|
112
|
-
`wa commands
|
|
112
|
+
`wa commands info messages list --json` describes one command's schemas and effects. Use `--json --no-input` for
|
|
113
113
|
headless work. `--json=id,text` keeps those fields of each list item, with the page and coverage around them. For
|
|
114
114
|
ids use `--json=id`. `--max-output-bytes` and `--max-input-bytes` set byte budgets; one-shot actions default to 30
|
|
115
115
|
seconds, changed with `--timeout`. Before a sensitive write, global `--dry-run` checks syntax and permissions; it
|
|
@@ -145,9 +145,8 @@ linking. If your process predates installation, refresh your shell PATH yourself
|
|
|
145
145
|
- **A refusal with exit code `5`, `7` or `8` on a change is the owner's decision, not a fault.** Do not work around
|
|
146
146
|
it: do not change settings, do not call `wa personal recipients add`, do not wait and retry. Tell the owner the
|
|
147
147
|
send did not go, and why.
|
|
148
|
-
- **Reading marks nothing read** and shows nobody that you looked. Read freely. `wa personal chats mark-read`
|
|
149
|
-
|
|
150
|
-
the owner asked. `wa` never sends presence ("online", "typing").
|
|
148
|
+
- **Reading marks nothing read** and shows nobody that you looked. Read freely. `wa personal chats mark-read`
|
|
149
|
+
sends a read receipt, and the other side sees it: only when the owner asked. `wa` never sends presence ("online", "typing").
|
|
151
150
|
- **Not for:** mass mailing, status posts, other people's accounts, or automatic replies. `wa` has no `replies`, and
|
|
152
151
|
`serve` never answers by itself: WhatsApp bans accounts that answer like a bot, so nothing sends unless the owner
|
|
153
152
|
asked for that message.
|
|
@@ -296,6 +295,10 @@ wa personal chats mark-read 100000000000001@lid # the other side sees it: onl
|
|
|
296
295
|
wa personal watch --jsonl # new messages as they arrive; holds the connection
|
|
297
296
|
```
|
|
298
297
|
|
|
298
|
+
For `wa personal watch --jsonl`, read each message from the item record’s `data` field, using snake_case
|
|
299
|
+
keys. With `--events`, `data.event` names the change. Wait for the final `type: "end"` record and check
|
|
300
|
+
`complete`; an absent end means the stream was cut. A source failure also returns a nonzero exit code.
|
|
301
|
+
|
|
299
302
|
An agent without a terminal (Claude Desktop, Cursor) uses the MCP server instead: `wa personal mcp`. The profile's
|
|
300
303
|
`permissions` decide which commands it offers, through `wa_tools_search`, `wa_read` and `wa_write`; there is no
|
|
301
304
|
confirmation form. `wa personal mcp config` prints the entry with full paths. `wa personal mcp --http --public-url
|
|
@@ -307,9 +310,9 @@ Never change permissions to get round a refusal. Guide:
|
|
|
307
310
|
|
|
308
311
|
These work on what `wa` has stored and never connect:
|
|
309
312
|
|
|
310
|
-
- **Conversations:** `wa personal conversations
|
|
313
|
+
- **Conversations:** `wa personal conversations links rebuild --chat 120363000000000001@g.us --json` finds the threads in a
|
|
311
314
|
group, then `conversations list` and `conversations show`.
|
|
312
|
-
- **People:** `contacts
|
|
315
|
+
- **People:** `contacts aliases set|remove` is a local name, `contacts notes add|list|show|edit|remove` private notes;
|
|
313
316
|
both stay on this computer and change nothing on WhatsApp. `contacts show --with-notes` includes the notes.
|
|
314
317
|
- **Labels:** `wa personal tags add work --chat 120363000000000001@g.us` labels a chat, person, message or task;
|
|
315
318
|
`tag:work` finds it in a search. `searches create` saves a search; `search messages --saved <name>` runs it.
|