@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.
Files changed (135) hide show
  1. package/README.md +9 -3
  2. package/dist/adapter/adapter.d.ts +4 -2
  3. package/dist/adapter/adapter.d.ts.map +1 -1
  4. package/dist/adapter/adapter.js +20 -406
  5. package/dist/adapter/adapter.js.map +1 -1
  6. package/dist/adapter/channels.d.ts.map +1 -1
  7. package/dist/adapter/channels.js.map +1 -1
  8. package/dist/adapter/comments.d.ts +5 -0
  9. package/dist/adapter/comments.d.ts.map +1 -0
  10. package/dist/adapter/comments.js +14 -0
  11. package/dist/adapter/comments.js.map +1 -0
  12. package/dist/adapter/discovery.d.ts +5 -0
  13. package/dist/adapter/discovery.d.ts.map +1 -0
  14. package/dist/adapter/discovery.js +28 -0
  15. package/dist/adapter/discovery.js.map +1 -0
  16. package/dist/adapter/errors.d.ts +1 -1
  17. package/dist/adapter/errors.d.ts.map +1 -1
  18. package/dist/adapter/errors.js +4 -2
  19. package/dist/adapter/errors.js.map +1 -1
  20. package/dist/adapter/groups.d.ts.map +1 -1
  21. package/dist/adapter/groups.js +1 -1
  22. package/dist/adapter/groups.js.map +1 -1
  23. package/dist/adapter/listen.d.ts +11 -0
  24. package/dist/adapter/listen.d.ts.map +1 -0
  25. package/dist/adapter/listen.js +264 -0
  26. package/dist/adapter/listen.js.map +1 -0
  27. package/dist/adapter/map.d.ts +1 -1
  28. package/dist/adapter/map.d.ts.map +1 -1
  29. package/dist/adapter/map.js +13 -22
  30. package/dist/adapter/map.js.map +1 -1
  31. package/dist/adapter/polls.d.ts +2 -1
  32. package/dist/adapter/polls.d.ts.map +1 -1
  33. package/dist/adapter/polls.js +14 -3
  34. package/dist/adapter/polls.js.map +1 -1
  35. package/dist/adapter/privacy.d.ts.map +1 -1
  36. package/dist/adapter/privacy.js +26 -11
  37. package/dist/adapter/privacy.js.map +1 -1
  38. package/dist/adapter/sends.d.ts +14 -0
  39. package/dist/adapter/sends.d.ts.map +1 -0
  40. package/dist/adapter/sends.js +156 -0
  41. package/dist/adapter/sends.js.map +1 -0
  42. package/dist/adapter/settings.js +4 -1
  43. package/dist/adapter/settings.js.map +1 -1
  44. package/dist/app.d.ts +8 -0
  45. package/dist/app.d.ts.map +1 -1
  46. package/dist/app.js +20 -1
  47. package/dist/app.js.map +1 -1
  48. package/dist/bot/adapter.d.ts +18 -0
  49. package/dist/bot/adapter.d.ts.map +1 -0
  50. package/dist/bot/adapter.js +74 -0
  51. package/dist/bot/adapter.js.map +1 -0
  52. package/dist/bot/generated/definitions.d.ts +3 -0
  53. package/dist/bot/generated/definitions.d.ts.map +1 -0
  54. package/dist/bot/generated/definitions.js +9254 -0
  55. package/dist/bot/generated/definitions.js.map +1 -0
  56. package/dist/bot/generated/manifest.d.ts +3 -0
  57. package/dist/bot/generated/manifest.d.ts.map +1 -0
  58. package/dist/bot/generated/manifest.js +2614 -0
  59. package/dist/bot/generated/manifest.js.map +1 -0
  60. package/dist/bot/generated/schemas.d.ts +803 -0
  61. package/dist/bot/generated/schemas.d.ts.map +1 -0
  62. package/dist/bot/generated/schemas.js +3052 -0
  63. package/dist/bot/generated/schemas.js.map +1 -0
  64. package/dist/bot/generated/types.d.ts +3439 -0
  65. package/dist/bot/generated/types.d.ts.map +1 -0
  66. package/dist/bot/generated/types.js +5 -0
  67. package/dist/bot/generated/types.js.map +1 -0
  68. package/dist/bot/graph.d.ts +53 -0
  69. package/dist/bot/graph.d.ts.map +1 -0
  70. package/dist/bot/graph.js +141 -0
  71. package/dist/bot/graph.js.map +1 -0
  72. package/dist/bot/messaging.d.ts +30 -0
  73. package/dist/bot/messaging.d.ts.map +1 -0
  74. package/dist/bot/messaging.js +124 -0
  75. package/dist/bot/messaging.js.map +1 -0
  76. package/dist/bot/mime.d.ts +3 -0
  77. package/dist/bot/mime.d.ts.map +1 -0
  78. package/dist/bot/mime.js +20 -0
  79. package/dist/bot/mime.js.map +1 -0
  80. package/dist/bot/webhook.d.ts +13 -0
  81. package/dist/bot/webhook.d.ts.map +1 -0
  82. package/dist/bot/webhook.js +148 -0
  83. package/dist/bot/webhook.js.map +1 -0
  84. package/dist/commands/bot-templates.d.ts +9 -0
  85. package/dist/commands/bot-templates.d.ts.map +1 -0
  86. package/dist/commands/bot-templates.js +141 -0
  87. package/dist/commands/bot-templates.js.map +1 -0
  88. package/dist/commands/bot.d.ts +14 -0
  89. package/dist/commands/bot.d.ts.map +1 -0
  90. package/dist/commands/bot.js +208 -0
  91. package/dist/commands/bot.js.map +1 -0
  92. package/dist/commands/session.d.ts +0 -44
  93. package/dist/commands/session.d.ts.map +1 -1
  94. package/dist/commands/session.js +15 -141
  95. package/dist/commands/session.js.map +1 -1
  96. package/dist/commands/setup.d.ts.map +1 -1
  97. package/dist/commands/setup.js +9 -8
  98. package/dist/commands/setup.js.map +1 -1
  99. package/dist/commands/update.d.ts +2 -1
  100. package/dist/commands/update.d.ts.map +1 -1
  101. package/dist/commands/update.js.map +1 -1
  102. package/dist/diagnose.d.ts +6 -41
  103. package/dist/diagnose.d.ts.map +1 -1
  104. package/dist/diagnose.js.map +1 -1
  105. package/dist/engine/channels.d.ts +1 -0
  106. package/dist/engine/channels.d.ts.map +1 -1
  107. package/dist/engine/contract.d.ts +32 -2
  108. package/dist/engine/contract.d.ts.map +1 -1
  109. package/dist/engine/contract.js +9 -1
  110. package/dist/engine/contract.js.map +1 -1
  111. package/dist/engine/discovery.d.ts +34 -0
  112. package/dist/engine/discovery.d.ts.map +1 -0
  113. package/dist/engine/discovery.js +22 -0
  114. package/dist/engine/discovery.js.map +1 -0
  115. package/dist/engine/events.d.ts +1 -1
  116. package/dist/engine/events.d.ts.map +1 -1
  117. package/dist/engine/events.js.map +1 -1
  118. package/dist/engine/go-engine.d.ts.map +1 -1
  119. package/dist/engine/go-engine.js +4 -0
  120. package/dist/engine/go-engine.js.map +1 -1
  121. package/dist/messenger.d.ts +4 -1
  122. package/dist/messenger.d.ts.map +1 -1
  123. package/dist/messenger.js +8 -10
  124. package/dist/messenger.js.map +1 -1
  125. package/dist/program.d.ts.map +1 -1
  126. package/dist/program.js +8 -2
  127. package/dist/program.js.map +1 -1
  128. package/dist/session.d.ts +56 -0
  129. package/dist/session.d.ts.map +1 -0
  130. package/dist/session.js +136 -0
  131. package/dist/session.js.map +1 -0
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/package.json +16 -11
  135. package/skills/whatsapp-cli/SKILL.md +16 -13
@@ -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.4.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.4.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.4.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.49.0",
61
- "@wirecat/cli-core": "0.385.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.4.0",
67
- "@wirecat/whatsapp-engine-darwin-x64": "0.4.0",
68
- "@wirecat/whatsapp-engine-linux-arm64": "0.4.0",
69
- "@wirecat/whatsapp-engine-linux-x64": "0.4.0",
70
- "@wirecat/whatsapp-engine-win32-arm64": "0.4.0",
71
- "@wirecat/whatsapp-engine-win32-x64": "0.4.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.2",
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 refresh` (a group's title and description, for
36
- `tags auto`), group admin
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 link show|reset`, `chats members list|fetch|add|remove`,
39
- `chats admins add|remove`, `chats requests list|accept|decline`), `contacts profile|block|unblock|lookup|sync`, `account privacy show|set`,
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` sends a read receipt only and may not clear a
54
- `chats mark-unread`.
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-time` takes 24h, 7d, 90d or off, nothing else. A chat or person named by name is looked up
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 schema messages list --json` describes one command's schemas and effects. Use `--json --no-input` for
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` and
149
- `--mark-read` on `messages list`, `inbox` and `review` send a read receipt, and the other side sees it: only when
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 build --chat 120363000000000001@g.us --json` finds the threads in a
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 alias set|rm` is a local name, `contacts notes add|list|show|edit|remove` private notes;
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.