miaw-core 1.10.0 → 1.12.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 (106) hide show
  1. package/.env.example +21 -2
  2. package/CHANGELOG.md +231 -0
  3. package/README.md +105 -27
  4. package/bin/miaw-cli.ts +68 -23
  5. package/dist/cli/commands/call.d.ts +16 -0
  6. package/dist/cli/commands/call.d.ts.map +1 -0
  7. package/dist/cli/commands/call.js +29 -0
  8. package/dist/cli/commands/call.js.map +1 -0
  9. package/dist/cli/commands/chat.d.ts +14 -0
  10. package/dist/cli/commands/chat.d.ts.map +1 -1
  11. package/dist/cli/commands/chat.js +48 -0
  12. package/dist/cli/commands/chat.js.map +1 -1
  13. package/dist/cli/commands/commands-index.d.ts +7 -5
  14. package/dist/cli/commands/commands-index.d.ts.map +1 -1
  15. package/dist/cli/commands/commands-index.js +11 -5
  16. package/dist/cli/commands/commands-index.js.map +1 -1
  17. package/dist/cli/commands/community.d.ts +28 -0
  18. package/dist/cli/commands/community.d.ts.map +1 -1
  19. package/dist/cli/commands/community.js +83 -0
  20. package/dist/cli/commands/community.js.map +1 -1
  21. package/dist/cli/commands/group.d.ts +49 -0
  22. package/dist/cli/commands/group.d.ts.map +1 -1
  23. package/dist/cli/commands/group.js +151 -0
  24. package/dist/cli/commands/group.js.map +1 -1
  25. package/dist/cli/commands/index.d.ts +11 -7
  26. package/dist/cli/commands/index.d.ts.map +1 -1
  27. package/dist/cli/commands/index.js +453 -51
  28. package/dist/cli/commands/index.js.map +1 -1
  29. package/dist/cli/commands/instance.d.ts +46 -7
  30. package/dist/cli/commands/instance.d.ts.map +1 -1
  31. package/dist/cli/commands/instance.js +224 -34
  32. package/dist/cli/commands/instance.js.map +1 -1
  33. package/dist/cli/commands/privacy.d.ts +83 -0
  34. package/dist/cli/commands/privacy.d.ts.map +1 -0
  35. package/dist/cli/commands/privacy.js +167 -0
  36. package/dist/cli/commands/privacy.js.map +1 -0
  37. package/dist/cli/commands/send.d.ts +12 -0
  38. package/dist/cli/commands/send.d.ts.map +1 -1
  39. package/dist/cli/commands/send.js +44 -0
  40. package/dist/cli/commands/send.js.map +1 -1
  41. package/dist/cli/repl.d.ts +8 -10
  42. package/dist/cli/repl.d.ts.map +1 -1
  43. package/dist/cli/repl.js +464 -84
  44. package/dist/cli/repl.js.map +1 -1
  45. package/dist/cli/utils/client-cache.d.ts +8 -0
  46. package/dist/cli/utils/client-cache.d.ts.map +1 -1
  47. package/dist/cli/utils/client-cache.js +66 -7
  48. package/dist/cli/utils/client-cache.js.map +1 -1
  49. package/dist/cli/utils/instance-config.d.ts +89 -0
  50. package/dist/cli/utils/instance-config.d.ts.map +1 -0
  51. package/dist/cli/utils/instance-config.js +224 -0
  52. package/dist/cli/utils/instance-config.js.map +1 -0
  53. package/dist/cli/utils/instance-registry.d.ts +7 -0
  54. package/dist/cli/utils/instance-registry.d.ts.map +1 -1
  55. package/dist/cli/utils/instance-registry.js +2 -0
  56. package/dist/cli/utils/instance-registry.js.map +1 -1
  57. package/dist/cli/utils/parse-args.d.ts +41 -0
  58. package/dist/cli/utils/parse-args.d.ts.map +1 -0
  59. package/dist/cli/utils/parse-args.js +84 -0
  60. package/dist/cli/utils/parse-args.js.map +1 -0
  61. package/dist/cli/utils/proxy-resolver.d.ts +91 -0
  62. package/dist/cli/utils/proxy-resolver.d.ts.map +1 -0
  63. package/dist/cli/utils/proxy-resolver.js +180 -0
  64. package/dist/cli/utils/proxy-resolver.js.map +1 -0
  65. package/dist/cli/utils/session.d.ts +24 -1
  66. package/dist/cli/utils/session.d.ts.map +1 -1
  67. package/dist/cli/utils/session.js +5 -0
  68. package/dist/cli/utils/session.js.map +1 -1
  69. package/dist/client/MiawClient.d.ts +331 -9
  70. package/dist/client/MiawClient.d.ts.map +1 -1
  71. package/dist/client/MiawClient.js +853 -23
  72. package/dist/client/MiawClient.js.map +1 -1
  73. package/dist/index.d.ts +5 -2
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +4 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/types/index.d.ts +234 -2
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js +22 -0
  80. package/dist/types/index.js.map +1 -1
  81. package/dist/utils/browser-presets.d.ts +51 -0
  82. package/dist/utils/browser-presets.d.ts.map +1 -0
  83. package/dist/utils/browser-presets.js +53 -0
  84. package/dist/utils/browser-presets.js.map +1 -0
  85. package/dist/utils/proxy-rotator.d.ts +7 -1
  86. package/dist/utils/proxy-rotator.d.ts.map +1 -1
  87. package/dist/utils/proxy-rotator.js +0 -0
  88. package/dist/utils/proxy-rotator.js.map +1 -1
  89. package/docs/API_STABILITY_REVIEW.md +10 -0
  90. package/docs/BAILEYS_MIGRATION_v7.md +10 -1
  91. package/docs/BAILEYS_VS_MIAW_COMPARISON.md +66 -47
  92. package/docs/CLI-ANALYSIS.md +10 -0
  93. package/docs/CLI.md +223 -3
  94. package/docs/CLI_INTEGRATION_TEST_PLAN.md +58 -24
  95. package/docs/CODE_REVIEW_REPORT.md +10 -0
  96. package/docs/DEFERRED_FEATURES.md +29 -41
  97. package/docs/DEPLOYMENT_INSTANCE_PROXY.md +168 -0
  98. package/docs/DEPLOYMENT_PROXY.md +6 -0
  99. package/docs/DEPLOYMENT_V1.12.0.md +163 -0
  100. package/docs/FOLLOW_UPS.md +320 -0
  101. package/docs/MIGRATION.md +147 -2
  102. package/docs/PROXY.md +112 -23
  103. package/docs/TEST_COVERAGE_ANALYSIS.md +29 -5
  104. package/docs/USAGE.md +320 -3
  105. package/package.json +6 -3
  106. package/sessions-cli/.cli_history +2 -0
package/.env.example CHANGED
@@ -19,6 +19,18 @@ MIAW_INSTANCE_ID=default
19
19
  MIAW_SESSION_PATH=./sessions-cli
20
20
 
21
21
  # ─── Proxy (all optional; added in v1.10.0) ──────────────────────────────────
22
+ #
23
+ # Precedence, highest first (v1.11.0):
24
+ # 1. MIAW_PROXY / --proxy always wins; warns when it shadows a pin
25
+ # 2. a pin in <MIAW_SESSION_PATH>/instances.json, per instance
26
+ # 3. MIAW_PROXY_FILE + strategy hashed on the TARGET instanceId
27
+ # 4. direct
28
+ #
29
+ # The pin store is written by `miaw-cli instance set-proxy <id> ...`, not by
30
+ # env vars - it is per-instance state, so it belongs with the sessions rather
31
+ # than with process config. It holds credentials when a URL is pinned, so it is
32
+ # written 0600 and must live on the same persistent, secret-grade volume as the
33
+ # session directories. See docs/DEPLOYMENT_INSTANCE_PROXY.md.
22
34
 
23
35
  # A single proxy for this instance. (flag: --proxy)
24
36
  # Schemes: http, https, socks4, socks4a, socks5, socks5h
@@ -33,8 +45,15 @@ MIAW_SESSION_PATH=./sessions-cli
33
45
  # MIAW_PROXY=socks5h://user:password@proxy.example.com:1080
34
46
 
35
47
  # A proxy list file instead of a single proxy — TXT (one per line) or JSON
36
- # array. One proxy is selected per process using MIAW_PROXY_STRATEGY.
37
- # Ignored when MIAW_PROXY is also set. (flag: --proxy-file)
48
+ # array. A proxy is selected per instance using MIAW_PROXY_STRATEGY, keyed on
49
+ # the instance the command targets (v1.11.0; before that, once per process
50
+ # against the startup instance).
51
+ # Ignored when MIAW_PROXY is also set, and outranked by a per-instance pin.
52
+ # (flag: --proxy-file)
53
+ #
54
+ # Also required when an instance is pinned by label (`instance set-proxy <id>
55
+ # --label eu`): the label names an entry in THIS file. A label pin that cannot
56
+ # be resolved refuses to connect rather than silently going direct.
38
57
  #
39
58
  # See examples/proxies.example.txt for the format.
40
59
  #
package/CHANGELOG.md CHANGED
@@ -5,6 +5,237 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.12.0] - 2026-09-02
9
+
10
+ **Baileys 7.0.0-rc14, and the deferred feature backlog cleared.** All additive;
11
+ no breaking changes.
12
+
13
+ ### Changed
14
+
15
+ - **Operator notes:** [docs/DEPLOYMENT_V1.12.0.md](docs/DEPLOYMENT_V1.12.0.md) —
16
+ no env, migration or state-format changes; rollback does not require re-pairing.
17
+ - **Upgraded `@whiskeysockets/baileys` from `7.0.0-rc13` to `7.0.0-rc14`** (pinned
18
+ exactly, as before). Four substantive upstream commits:
19
+ - WhatsApp Web version `2.3000.1035194821` → `2.3000.1043857760`. Low impact —
20
+ `resolveWAVersion()` already prefers the live version from `web.whatsapp.com`,
21
+ so the bundled constant is only the third-tier fallback — but a fresher
22
+ fallback is still worth having.
23
+ - **`profilePictureUrl` fix.** The privacy `tctoken` now nests under the
24
+ `picture` query node and carries a `t` attribute, matching WA Web. This
25
+ affects `getContactInfo()`, `getProfilePicture()` and `getChatInfo()` when
26
+ the target has profile-picture privacy enabled.
27
+ - New `Browsers.android()` identity (see below).
28
+ - An internal `Long` type import fix.
29
+
30
+ No public API changed; miaw-core's whole Baileys surface — including the
31
+ `updateBussinesProfile` typo, `signalRepository.lidMapping`, the hand-built
32
+ `remove-companion-device` node, `messaging-history.set` `lidPnMappings`, the
33
+ `resyncAppState` collection names, and the dispatcher passed into
34
+ `downloadMediaMessage` — is byte-identical between the two releases.
35
+
36
+ ### Added
37
+
38
+ - **`BrowserPresets`** — `macOS`, `windows`, `ubuntu` and `android` browser
39
+ identities for the `browser` option, which was previously undocumented outside
40
+ its JSDoc. The default is unchanged (`macOS("Chrome")`).
41
+ - **Android connection identity.** `BrowserPresets.android()` negotiates as an
42
+ Android client rather than a web one. Baileys reports this is required to
43
+ **receive view-once media**, which a web session is never sent
44
+ ([#2201](https://github.com/WhiskeySockets/Baileys/pull/2201)); miaw-core
45
+ already normalizes view-once messages, so if that holds,
46
+ `message.media.viewOnce` and `downloadMedia()` work with no further change.
47
+ The handshake is verified here; the view-once receipt itself is upstream's
48
+ claim and is **not** confirmed end-to-end — see `tests/verify-viewonce.ts` to
49
+ check it on your own account. Baileys marks the identity experimental; prefer
50
+ a dedicated `instanceId` over switching an established session.
51
+ - **Group & community administration.** `setGroupAnnounceOnly`,
52
+ `setGroupRestrictInfo`, `setGroupMemberAddMode`, `setGroupJoinApproval`,
53
+ `setGroupEphemeral`, `getGroupJoinRequests`, `approveGroupJoinRequests`,
54
+ `rejectGroupJoinRequests`, and the eight `setCommunity*` /
55
+ `*CommunityJoinRequests` equivalents.
56
+ - **Privacy settings.** `getPrivacySettings()` plus `setLastSeenPrivacy`,
57
+ `setOnlinePrivacy`, `setProfilePicturePrivacy`, `setStatusPrivacy`,
58
+ `setReadReceiptsPrivacy`, `setGroupAddPrivacy`, `setMessagesPrivacy`,
59
+ `setCallPrivacy`, `setDefaultDisappearingMode` and `setLinkPreviewsDisabled`.
60
+ - **Blocklist.** `blockContact()`, `unblockContact()`, `getBlocklist()`,
61
+ `isBlocked()`.
62
+ - **Calls.** A new `call` event carrying a normalized `MiawCall`, plus
63
+ `rejectCall()` and `createCallLink()`. A single call fires the event several
64
+ times as it progresses; `status === "offer"` is the only stage at which
65
+ `rejectCall()` still works.
66
+ - **Disappearing messages.** `setChatEphemeral()` for 1:1 chats alongside the
67
+ group and community variants, with an `EphemeralDuration` constant for the four
68
+ durations the WhatsApp UI offers.
69
+ - **Group invite cards.** `sendGroupInvite()` sends the rich "join my group" card
70
+ rather than a pasted link. Its payload type is `GroupInviteMessage` — distinct
71
+ from the existing `GroupInviteInfo`, which is the *preview* returned by
72
+ `getGroupInviteInfo()` before joining.
73
+ - **Pin-in-chat.** `pinMessage()` / `unpinMessage()`, with a `PinDuration`
74
+ constant. WhatsApp accepts only 24h, 7d and 30d here — a different set from the
75
+ disappearing-message durations.
76
+ - **CLI:** `group|community announce|restrict|add-mode|approval|ephemeral`,
77
+ `group|community requests list|approve|reject`, `privacy
78
+ show|set|disappearing|link-previews`, `block list|add|remove`, `call link`,
79
+ `chat ephemeral|pin-message|unpin-message`, and `send group-invite`. All with
80
+ tab completion and `help` topics.
81
+ - **`tests/unit/baileys-export-surface.test.ts`** — imports Baileys unmocked and
82
+ asserts every symbol the two import sites destructure still exists. Seventeen
83
+ unit suites replace the module wholesale with hand-written factories, so
84
+ without this a renamed or removed upstream export stays invisible until
85
+ runtime.
86
+
87
+ ### Fixed
88
+
89
+ - **`community invite-link` was advertised but never worked.** The subcommand
90
+ list printed by `community` has named it since v1.9.0, but the dispatch
91
+ switch had no `case` for it, so it fell through to the unknown-command
92
+ branch and always errored. `group invite-link` has existed since v1.4.
93
+ - **Five `help` topics tab-completed and then failed.** `help chat|story|
94
+ community|call|business` were offered by completion but had no case in
95
+ `showReplHelp()`, printing "Unknown help topic". The completion tree, the
96
+ dispatch and two "available topics" lines were four hand-maintained copies
97
+ of the same list; they are now all derived from one `HELP_TOPIC_HANDLERS`
98
+ map, and the five missing help screens were written.
99
+ - **`test:manual:build` never worked and has been removed.** It ran
100
+ `node dist/tests/interactive-test.js`, but `tsconfig.json` sets
101
+ `rootDir: ./src` and excludes `tests/`, so `dist/tests/` is never emitted.
102
+ - **`sendGroupInvite()` failed for any group without a profile picture.** Baileys
103
+ embeds a thumbnail on the invite card by calling `getProfilePicUrl(groupJid)`
104
+ and does not guard it; WhatsApp answers `item-not-found` for a pictureless
105
+ group, which throws and aborts the whole send. Since most groups have no
106
+ picture, the unguarded path failed more often than it worked. `sendGroupInvite`
107
+ now passes its own guarded hook — Baileys spreads caller options last, so it
108
+ wins — and sends the card without a thumbnail rather than not at all. Found by
109
+ live testing; it is invisible to unit tests because the mock never throws.
110
+ - **Omitting a disappearing-message duration silently disabled it.**
111
+ `miaw-cli group ephemeral <jid>` with no duration turned disappearing messages
112
+ **off** for the whole group instead of printing a usage line — `Number("")` is
113
+ `0`, and `0` is a valid duration meaning "off", so the missing argument parsed
114
+ as a successful request to disable. Same for `community ephemeral`,
115
+ `chat ephemeral`, and `privacy disappearing` (which changed an account-wide
116
+ default). Found in review of this release, before any of it shipped.
117
+ - **`--force` could swallow the following token.** It was missing from
118
+ `BOOLEAN_FLAGS` in `bin/miaw-cli.ts`, the same class of bug that `--json` hit
119
+ previously.
120
+ - **CLI docs gap.** `chat` commands were entirely undocumented in
121
+ [CLI.md](docs/CLI.md) despite shipping in v1.7.0; they now have a section.
122
+
123
+ ### Verified live
124
+
125
+ Against a real WhatsApp connection (Baileys rc14):
126
+
127
+ - **`profilePictureUrl`** — the upstream tctoken-nesting fix. 2 of 5 real
128
+ contacts resolved to live URLs; the rest correctly returned `null`.
129
+ - **`getPrivacySettings()`** — WhatsApp returned 16 categories; the 8 with
130
+ Baileys setters map onto typed fields and the other 8 survive under `raw`.
131
+ - **All 19 v1.12.0 write paths** — every group setting toggled and restored with
132
+ a read-back confirming the change landed, plus 1:1 ephemeral, privacy
133
+ round-trips, `sendGroupInvite`, pin/unpin, and `createCallLink`. 19/19.
134
+ - **`BrowserPresets.android()`** — WhatsApp issued a QR rather than a 428, so
135
+ the `Platform.ANDROID` handshake is accepted. End-to-end view-once *receipt*
136
+ is not yet confirmed; see [FOLLOW_UPS.md](docs/FOLLOW_UPS.md) section 7.
137
+ - **No regressions from rc14.** The CLI integration suite has 15 pre-existing
138
+ failures, unrelated to this release — running the same six suites against
139
+ rc13 produced a byte-identical failure set. They are catalogued in
140
+ [FOLLOW_UPS.md](docs/FOLLOW_UPS.md) section 9.
141
+
142
+ ### Internal
143
+
144
+ - `jest.config.js` sets `maxWorkers: 1`. The live-connection suites share a
145
+ single WhatsApp session, and forking one worker per core for a full run is
146
+ memory-hungry; `test:cli` already passed `--runInBand` for the first reason.
147
+ - CLI integration teardown moved from `10-business-commands.test.ts` to the new
148
+ `13-privacy-call-commands.test.ts`. Files run in name order and 13 is now the
149
+ last one needing a connection (11 and 12 are deliberately offline), so
150
+ disconnecting at 10 would pull the socket out from under it.
151
+ - 615 unit tests across 29 suites (up from 417), 105 CLI router tests (up from
152
+ 26), 278 CLI integration tests across 13 files (up from 238).
153
+ - New `tests/unit/cli-help-consistency.test.ts` and a dispatchability sweep in
154
+ `01-command-router.test.ts`, both verified to fail against the previous code.
155
+ - The manual runner grew from 82 to 96 entries, and gained a `community` group:
156
+ all 19 community methods, v1.9.0's included, had been unreachable from it.
157
+
158
+ ## [1.11.0] - 2026-09-02
159
+
160
+ ### Added
161
+
162
+ - **Per-instance proxy pins (CLI).** `miaw-cli instance set-proxy <id>` persists a
163
+ proxy assignment to `<session-path>/instances.json`, so
164
+ `miaw-cli --instance-id bot-3 <command>` uses bot-3's proxy with no flags.
165
+ Four sources: a positional URL, `--label <name>` (selects a `label=` entry from
166
+ the proxy file and stores **no credentials**), `--from-env <VAR>`, and
167
+ `--from-file` (materializes the current deterministic selection).
168
+ `instance unset-proxy <id>` removes it.
169
+ - `instance ls` gains a Proxy column (masked) and lists instances that are pinned
170
+ but not yet created as `[not created]` — pinning before `instance create` is
171
+ supported, so the pairing itself comes from the final egress IP.
172
+ - **`MiawClient.setProxy(proxy)`** — stages a proxy for the **next** `connect()`,
173
+ returning `{ success, proxy, reconnectRequired, error }`. It never touches a
174
+ live socket, validates eagerly, and is refused when the client was constructed
175
+ with a custom `agent`/`fetchAgent`.
176
+ - `getProxyInfo()` gains `active` (is the open socket using this config?) and
177
+ `pending` (what it is using instead). The `{ url, protocol }` shape and the
178
+ `null`-for-custom-agent contract are unchanged.
179
+ - New exported types `SetProxyResult` and `ProxyInfo`.
180
+ - A complete, runnable dead-proxy failover recipe in docs/PROXY.md and
181
+ examples/10-proxy-rotation.ts, reusing a single client.
182
+ - `docs/DEPLOYMENT_INSTANCE_PROXY.md` — operational notes for the new
183
+ persistent pin store, and `docs/FOLLOW_UPS.md` for deferred findings.
184
+
185
+ ### Behavior changes
186
+
187
+ - **`ProxyRotator` now defaults to `deterministic`, not `round-robin`.** The docs
188
+ and CLAUDE.md have always said deterministic is the default; only the CLI
189
+ actually applied it. The mismatch was silent and severe: following the docs
190
+ (`new ProxyRotator(urls)` then `next(id)`) gave round-robin, which hands a
191
+ long-lived session a different egress IP on each call — read by WhatsApp as
192
+ account takeover. Code changed rather than docs, so the default fails safe.
193
+ `next()` with no `instanceId` on a defaulted rotator now throws a message
194
+ naming the fix; pass `{ strategy: "round-robin" }` explicitly to restore the
195
+ old behavior. Checked against the sibling `miaw-api`, which constructs with an
196
+ explicit strategy and always calls `next(instanceId)`: unaffected.
197
+
198
+ ### Fixed
199
+
200
+ - **The CLI silently dropped the proxy.** `instance create`/`connect`/`disconnect`/
201
+ `logout` built clients from `{instanceId, sessionPath}` only, discarding both
202
+ `proxy` and `debug`. Since the client cache key ignored the proxy, REPL
203
+ `connect <id>` cached a proxy-less client that every later command reused —
204
+ traffic went direct with no indication.
205
+ - **REPL `use <id>` carried the previous instance's proxy** to the next one: it
206
+ mutated `config.instanceId` in place without re-resolving.
207
+ - **Proxy selection ignored the target instance.** `--proxy-file` was resolved once
208
+ per process against the *startup* instance id, so `instance connect bot-3` got
209
+ another instance's egress IP. Resolution is now per target.
210
+ - `instance create` called `createClient()` directly, bypassing the cache, so the
211
+ freshly-paired live client was never registered and the next command built a
212
+ second `MiawClient` for the same account.
213
+ - An explicit `disconnect()` emitted `disconnected` twice — once as `"unknown"`
214
+ from the `connection.update{close}` that Baileys' `end()` fires, then once as
215
+ `"intentional"`.
216
+ - Console-filter reference leak: the filter was acquired only in the constructor
217
+ but released on every `disconnect()`/`logout()`, so a reconnect cycle lost this
218
+ client's reference and repeated cycles un-filtered other live clients' output.
219
+ - Proxy transports leaked: `connect()` built fresh agents on every call, auto-
220
+ reconnects included, and never released the old ones.
221
+ - `.cli_history` was written 0644 with every line verbatim, so an
222
+ `instance set-proxy` command turned it into a plaintext credential store. Now
223
+ redacted at entry and written 0600.
224
+ - **Global boolean flags swallowed the command name.** The arg parser assumed
225
+ every `--flag` took a value, so `miaw-cli --json instance ls` parsed as
226
+ `json="instance"` and failed with "Unknown command: ls" — even though
227
+ `--json` is documented as a global option. Value-less flags (`--json`,
228
+ `--debug`, `--ip`, `--from-file`, `--help`, `--version`) are now recognised
229
+ as booleans in any position.
230
+ - **`src/utils/proxy-rotator.ts` contained a literal NUL byte**, which made
231
+ `file(1)` classify the source as binary and caused `grep` to skip it in any
232
+ repo-wide search. Replaced with the equivalent escape sequence; verified
233
+ runtime-identical, so no instance is remapped to a different proxy.
234
+ - **Docs: `fetchAgent` is an `http.Agent`, not an undici Dispatcher.** The JSDoc
235
+ and three doc sites claimed otherwise; the PROXY.md "Custom agents" snippet was
236
+ copy-pasteable and would silently break every media upload through the proxy.
237
+ Also corrects the `createProxyAgents` API-table row and two broken anchors.
238
+
8
239
  ## [1.10.0] - 2026-07-29
9
240
 
10
241
  **Proxy files, rotation, and CLI diagnostics** - builds on the v1.3.0 proxy core.
package/README.md CHANGED
@@ -10,7 +10,7 @@ Miaw Core abstracts away the complexity of Baileys, providing a clean, simple AP
10
10
 
11
11
  | Aspect | Baileys (Direct) | Miaw Core |
12
12
  | ---------------------- | ----------------------------- | ---------------------------------- |
13
- | **Learning Curve** | Steep - 150+ methods to learn | Gentle - 92 focused methods |
13
+ | **Learning Curve** | Steep - 150+ methods to learn | Gentle - ~200 focused methods |
14
14
  | **Setup Code** | 50-100 lines boilerplate | 10 lines to start |
15
15
  | **Session Management** | Manual auth state handling | Automatic file-based persistence |
16
16
  | **Reconnection** | DIY implementation | Built-in with configurable retries |
@@ -30,6 +30,7 @@ For a detailed comparison, see [Baileys vs Miaw Core Comparison](./docs/BAILEYS_
30
30
  - **[Usage Guide](./docs/USAGE.md)** - Complete guide for all current features
31
31
  - **[Proxy Guide](./docs/PROXY.md)** - Proxies, proxy list files, and rotation strategies
32
32
  - **[Proxy Deployment Notes](./docs/DEPLOYMENT_PROXY.md)** - Operational guidance for deploying with proxies
33
+ - **[Per-Instance Proxy Deployment](./docs/DEPLOYMENT_INSTANCE_PROXY.md)** - What v1.11.0 changes for operators
33
34
  - **[LID Resolution](./docs/LID_RESOLUTION.md)** - Working with privacy-masked (`@lid`) JIDs
34
35
  - **[Baileys Comparison](./docs/BAILEYS_VS_MIAW_COMPARISON.md)** - Feature comparison with raw Baileys
35
36
  - **[Migration Guide](./docs/MIGRATION.md)** - Upgrading between versions
@@ -44,7 +45,7 @@ For a detailed comparison, see [Baileys vs Miaw Core Comparison](./docs/BAILEYS_
44
45
  - **Auto-Reconnection** - Handles connection drops and reconnects automatically
45
46
  - **Session Management** - File-based session storage with automatic persistence
46
47
  - **Multiple Instances** - Run multiple WhatsApp connections in a single process
47
- - **Proxy Support** - HTTP/HTTPS/SOCKS proxies, proxy list files, and per-instance rotation
48
+ - **Proxy Support** - HTTP/HTTPS/SOCKS proxies, proxy list files, and per-instance proxies (assigned or pinned)
48
49
  - **TypeScript Support** - Full type definitions for excellent IDE experience
49
50
  - **Event-Driven** - Easy-to-use event system for messages and connection states
50
51
  - **Normalized Messages** - Simplified message format, no more complex Baileys structures
@@ -132,7 +133,7 @@ For more examples and detailed usage, see the [Usage Guide](./docs/USAGE.md).
132
133
 
133
134
  ### Manual Interactive Testing
134
135
 
135
- Test all 92 API methods interactively:
136
+ Drive the API interactively against a live connection:
136
137
 
137
138
  ```bash
138
139
  # Show available test groups
@@ -147,17 +148,34 @@ npm run test:manual business # Business features only
147
148
 
148
149
  **Available test groups:**
149
150
 
150
- | Group | Description | Methods |
151
- | ------------ | ------------------------------- | ------- |
152
- | `core` | Connection, lifecycle | 6 |
153
- | `get` | Fetch contacts, groups, chats | 6 |
154
- | `messaging` | Send/receive, reactions, edit | 12 |
155
- | `contacts` | Check numbers, contact info | 7 |
156
- | `group` | Create, manage participants | 13 |
157
- | `profile` | Update picture, name, status | 4 |
158
- | `business` | Labels, catalog (Business only) | 10 |
159
- | `newsletter` | Channels, subscriptions | 6 |
160
- | `ux` | Typing, presence, read receipts | 5 |
151
+ | Group | Description |
152
+ | ------------ | ----------------------------------------------- |
153
+ | `core` | Connection, lifecycle |
154
+ | `get` | Fetch contacts, groups, chats |
155
+ | `messaging` | Send/receive, reactions, edit, pin, invites |
156
+ | `contacts` | Check numbers, contact info |
157
+ | `group` | Create, manage participants, admin settings |
158
+ | `community` | Communities, linked groups, admin, join requests|
159
+ | `profile` | Update picture, name, status |
160
+ | `business` | Labels, catalog (Business only) |
161
+ | `newsletter` | Channels, subscriptions |
162
+ | `ux` | Typing, presence, read receipts |
163
+ | `privacy` | Privacy settings, block / unblock |
164
+ | `calls` | Call event, reject, call links |
165
+
166
+ Run `npm run test:manual` with no argument for this list with a live entry count
167
+ per group — it is generated from the runner's own category map, so it cannot go
168
+ stale.
169
+
170
+ **Unattended mode:**
171
+
172
+ ```bash
173
+ npm run test:manual:auto # every group, no prompts, non-zero exit on failure
174
+ npm run test:manual group -- --auto # one group
175
+ ```
176
+
177
+ Prompts resolve to their `.env.test` defaults. Entries needing a human are
178
+ skipped; add `--destructive` to include the ones that change real state.
161
179
 
162
180
  **Features:**
163
181
 
@@ -165,25 +183,36 @@ npm run test:manual business # Business features only
165
183
  - Tracks test results with pass/fail/skip status
166
184
  - Generates summary report with timestamps
167
185
  - Pre-loads test configuration from `.env.test`
168
-
169
- See [Test Coverage Analysis](./docs/TEST_COVERAGE_ANALYSIS.md) for detailed coverage report.
186
+ - Warns when `.env.test` is configured in a way that silently invalidates
187
+ results (e.g. both contact numbers set to the bot's own number)
170
188
 
171
189
  ### Unit & Integration Tests
172
190
 
173
191
  ```bash
174
- # Run all tests
192
+ # Unit suites only - the fast gate (615 tests, no connection needed)
193
+ npm run test:unit
194
+
195
+ # Everything, including the live-connection suites
175
196
  npm test
176
197
 
177
- # Run in watch mode
178
- npm run test:watch
198
+ # CLI command tests against a real connection (13 files)
199
+ npm run test:cli
200
+
201
+ # Type-check src/ and tests/ together
202
+ npm run typecheck
179
203
 
180
- # Generate coverage report
204
+ # Watch mode / coverage
205
+ npm run test:watch
181
206
  npm run test:coverage
182
207
  ```
183
208
 
184
- ## Current Capabilities (v1.10.0)
209
+ > Jest runs serially (`maxWorkers: 1`): the live suites share one WhatsApp
210
+ > session, so a parallel run would have them fighting over it. For the same
211
+ > reason, never run two test commands at once.
212
+
213
+ ## Current Capabilities (v1.12.0)
185
214
 
186
- Built on **Baileys v7.0.0-rc13** - the latest WhatsApp Web protocol implementation.
215
+ Built on **Baileys v7.0.0-rc14** - the latest WhatsApp Web protocol implementation.
187
216
 
188
217
  ### Core Features
189
218
 
@@ -200,11 +229,19 @@ Built on **Baileys v7.0.0-rc13** - the latest WhatsApp Web protocol implementati
200
229
  ### Networking
201
230
 
202
231
  - ✅ HTTP / HTTPS / SOCKS4 / SOCKS5 proxies, per instance
232
+ - ✅ Per-instance proxy pins that persist — `miaw-cli instance set-proxy <id> ...`, then no flags needed
233
+ - ✅ `client.setProxy()` — stage a proxy for the next connect (never rotates a live session's IP)
203
234
  - ✅ Proxy list files (TXT / JSON) with validation and hot reload
204
235
  - ✅ Rotation strategies (round-robin, random, weighted, deterministic-per-instance)
205
236
  - ✅ `miaw-cli proxy test` connectivity diagnostics — no WhatsApp connection needed
206
237
  - ⚠️ SOCKS proxies: media *downloads* use a direct connection ([details](./docs/PROXY.md#️-socks-media-downloads-use-a-direct-connection))
207
238
 
239
+ ### Connection Identity
240
+
241
+ - ✅ Configurable browser identity via `BrowserPresets` (macOS / Windows / Ubuntu / Android)
242
+ - ✅ Android identity — Baileys reports this is required to **receive view-once media** ([details](./docs/USAGE.md#receiving-view-once-messages-android-identity))
243
+ - ✅ Live WhatsApp Web version negotiation (avoids stale-version 428 rejections)
244
+
208
245
  ### Advanced Messaging
209
246
 
210
247
  - ✅ Reply/quote messages
@@ -212,6 +249,10 @@ Built on **Baileys v7.0.0-rc13** - the latest WhatsApp Web protocol implementati
212
249
  - ✅ Delete messages
213
250
  - ✅ Message reactions
214
251
  - ✅ Forward messages
252
+ - ✅ Location, contact cards, polls and stickers
253
+ - ✅ Group invite cards — `sendGroupInvite()`
254
+ - ✅ Pin / unpin a message in a chat — `pinMessage()` / `unpinMessage()`
255
+ - ✅ Disappearing messages per chat — `setChatEphemeral()`
215
256
 
216
257
  ### Group Management
217
258
 
@@ -220,6 +261,39 @@ Built on **Baileys v7.0.0-rc13** - the latest WhatsApp Web protocol implementati
220
261
  - ✅ Promote/demote admins
221
262
  - ✅ Group invite links
222
263
  - ✅ Update group settings
264
+ - ✅ Announce-only and info-edit locks
265
+ - ✅ Member-add mode (admins only / all members)
266
+ - ✅ Join approval, plus listing / approving / rejecting join requests
267
+ - ✅ Group-wide disappearing messages
268
+
269
+ ### Communities
270
+
271
+ - ✅ Create communities, link and unlink groups
272
+ - ✅ Community members: add, remove, promote, demote
273
+ - ✅ Community invite links
274
+ - ✅ The same admin surface as groups — announce, restrict, add-mode,
275
+ join approval, join requests, disappearing messages
276
+
277
+ ### Privacy & Blocklist
278
+
279
+ - ✅ Read all privacy settings, including categories Baileys has no setter for
280
+ (preserved under `raw`)
281
+ - ✅ Eight setters — last seen, online, profile picture, status, read receipts,
282
+ group add, messages, calls
283
+ - ✅ Default disappearing mode for new chats, and link-preview control
284
+ - ✅ Block / unblock, list the blocklist, check whether a contact is blocked
285
+
286
+ ### Calls
287
+
288
+ - ✅ `call` event for incoming calls (offer / accept / reject / timeout)
289
+ - ✅ Reject an incoming call — `rejectCall()`
290
+ - ✅ Create shareable call links — `createCallLink()`
291
+
292
+ ### Chat Management
293
+
294
+ - ✅ Archive, pin, mute, mark read/unread
295
+ - ✅ Clear or delete a chat
296
+ - ✅ Star / unstar messages
223
297
 
224
298
  ### Profile Management
225
299
 
@@ -256,16 +330,20 @@ Miaw Core handles these Baileys complexities for you:
256
330
 
257
331
  ```text
258
332
  miaw-core/
333
+ ├── bin/
334
+ │ └── miaw-cli.ts # CLI entry point (one-shot + REPL)
259
335
  ├── src/
260
336
  │ ├── client/ # Main MiawClient class
261
337
  │ ├── handlers/ # Auth and message handlers
262
338
  │ ├── types/ # TypeScript definitions
263
- │ ├── utils/ # Storage utilities
339
+ │ ├── constants/ # Shared constant tables
340
+ │ ├── utils/ # Proxy agents, rotation, browser presets
341
+ │ ├── cli/ # CLI subsystem (commands + utils)
264
342
  │ └── index.ts # Public API exports
265
343
  ├── docs/ # Documentation
266
344
  ├── examples/ # Usage examples
267
- ├── tests/ # Unit and integration tests
268
- └── README.md # This file
345
+ ├── tests/ # Unit, integration and CLI integration tests
346
+ └── README.md # This file
269
347
  ```
270
348
 
271
349
  ## Contributing
@@ -290,8 +368,8 @@ MIT
290
368
 
291
369
  ## Credits
292
370
 
293
- Built on top of [@whiskeysockets/baileys](https://github.com/WhiskeySockets/Baileys) v7.0.0-rc13
371
+ Built on top of [@whiskeysockets/baileys](https://github.com/WhiskeySockets/Baileys) v7.0.0-rc14
294
372
 
295
373
  ---
296
374
 
297
- **Version:** 1.4.1 | **Baileys:** 7.0.0-rc13 | **Status:** Stable | **Updated:** 2026-06-26
375
+ **Version:** 1.12.0 | **Baileys:** 7.0.0-rc14 | **Status:** Stable | **Updated:** 2026-09-02
package/bin/miaw-cli.ts CHANGED
@@ -17,10 +17,15 @@ import { runRepl } from "../src/cli/repl.js";
17
17
  import { runCommand } from "../src/cli/commands/index.js";
18
18
  import { initializeCLICleanup } from "../src/cli/utils/cleanup.js";
19
19
  import { getErrorMessage } from "../src/utils/type-guards.js";
20
+ import {
21
+ configFromResolved,
22
+ describeProxySource,
23
+ resolveProxyForInstance,
24
+ type ProxyResolutionBase,
25
+ } from "../src/cli/utils/proxy-resolver.js";
20
26
  import { maskProxyUrl } from "../src/utils/proxy-agent.js";
21
27
  import {
22
28
  parseProxyStrategy,
23
- selectProxyForInstance,
24
29
  } from "../src/cli/utils/proxy-config.js";
25
30
 
26
31
  // Initialize CLI cleanup handlers for graceful shutdown
@@ -38,6 +43,12 @@ const DEFAULT_PROXY = process.env.MIAW_PROXY || undefined;
38
43
  const DEFAULT_PROXY_FILE = process.env.MIAW_PROXY_FILE || undefined;
39
44
  const DEFAULT_PROXY_STRATEGY_ENV = process.env.MIAW_PROXY_STRATEGY || undefined;
40
45
 
46
+ /**
47
+ * Global flags that never take a value. Listed explicitly because the parser is
48
+ * positional: without this an `--ip`-style flag consumes the command name.
49
+ */
50
+ const BOOLEAN_FLAGS = new Set(["json", "debug", "help", "version", "ip", "from-file", "force"]);
51
+
41
52
  /**
42
53
  * Parse CLI arguments
43
54
  */
@@ -53,6 +64,14 @@ function parseArgs(args: string[]): {
53
64
  const arg = args[i];
54
65
  if (arg.startsWith("--")) {
55
66
  const flagName = arg.slice(2);
67
+ // Value-less flags must never swallow the next token. `--json instance ls`
68
+ // otherwise parsed as json="instance", leaving the command empty and
69
+ // reporting "Unknown command: ls" - even though --json is documented as a
70
+ // global option, i.e. valid before the command.
71
+ if (BOOLEAN_FLAGS.has(flagName)) {
72
+ flags[flagName] = true;
73
+ continue;
74
+ }
56
75
  const nextArg = args[i + 1];
57
76
  if (nextArg && !nextArg.startsWith("--")) {
58
77
  flags[flagName] = nextArg;
@@ -95,7 +114,8 @@ GLOBAL FLAGS:
95
114
  --debug Enable verbose logging
96
115
 
97
116
  COMMANDS:
98
- instance Manage instances (ls, status, create, delete, connect, disconnect, logout)
117
+ instance Manage instances (ls, status, create, delete, connect, disconnect,
118
+ logout, set-proxy, unset-proxy)
99
119
  get Fetch data (profile, contacts, groups, chats, messages, labels)
100
120
  load Load older messages from history
101
121
  send Send messages (text, image, document)
@@ -118,6 +138,14 @@ EXAMPLES:
118
138
  miaw-cli contact add 6281234567890 "John Doe"
119
139
  miaw-cli proxy test socks5://proxy.example.com:1080
120
140
  miaw-cli --instance-id bot-3 --proxy-file ./proxies.txt get groups
141
+ miaw-cli instance set-proxy bot-3 --label eu Pin a proxy, no credentials stored
142
+ miaw-cli --instance-id bot-3 get groups Uses bot-3's pinned proxy, no flags
143
+
144
+ PROXY PRECEDENCE (highest first):
145
+ --proxy / MIAW_PROXY > pinned in <session-path>/instances.json
146
+ > --proxy-file > direct
147
+ A pin applies on the instance's NEXT connect. Never change a live session's
148
+ egress IP - WhatsApp reads that as account takeover.
121
149
 
122
150
  REPL MODE:
123
151
  Run 'miaw-cli' without arguments to start interactive mode.
@@ -161,38 +189,55 @@ async function main() {
161
189
  process.exit(1);
162
190
  }
163
191
 
164
- // Precedence: an explicit --proxy always wins; otherwise a --proxy-file
165
- // selects one proxy for this process. The default strategy is
166
- // deterministic so a given instanceId keeps a stable egress IP across
167
- // invocations - round-robin here would silently rotate a live session's IP.
168
- let proxyUrl = explicitProxy;
169
- if (explicitProxy && proxyFile) {
170
- console.log("⚠️ --proxy overrides --proxy-file");
171
- } else if (!explicitProxy && proxyFile) {
172
- try {
173
- proxyUrl = await selectProxyForInstance(proxyFile, proxyStrategy, instanceId);
174
- } catch (error: unknown) {
175
- console.error(`❌ Failed to select a proxy from ${proxyFile}: ${getErrorMessage(error)}`);
176
- process.exit(1);
177
- }
178
- }
179
-
180
- // Create client configuration
181
- const clientConfig = {
182
- instanceId,
192
+ // Precedence lives in one place - see src/cli/utils/proxy-resolver.ts:
193
+ // --proxy > pin in instances.json > --proxy-file > direct
194
+ // Selection is keyed on this instanceId, and commands targeting a *different*
195
+ // instance re-resolve rather than reusing what the process started with.
196
+ const proxyBase: ProxyResolutionBase = {
183
197
  sessionPath,
184
198
  debug: debugMode,
185
- ...(proxyUrl && { proxy: proxyUrl }),
199
+ ...(explicitProxy && { explicitProxy }),
186
200
  ...(proxyFile && { proxyFile }),
187
201
  proxyStrategy,
188
202
  };
189
203
 
204
+ // A failure here is reported but does NOT exit. A bad pin would otherwise
205
+ // lock the operator out of `instance unset-proxy` and `instance ls` - the
206
+ // commands that repair it. The config instead carries `proxyError`, and
207
+ // createClient() refuses to build a client, so nothing connects direct.
208
+ let clientConfig;
209
+ let resolvedProxy;
210
+ try {
211
+ // Resolve once, then build the config from that result: resolving twice
212
+ // would print every override warning twice.
213
+ resolvedProxy = await resolveProxyForInstance(proxyBase, instanceId);
214
+ clientConfig = configFromResolved(proxyBase, instanceId, resolvedProxy);
215
+ } catch (error: unknown) {
216
+ const reason = getErrorMessage(error);
217
+ console.error(`❌ ${reason}`);
218
+ console.error(
219
+ ` Commands needing a connection will refuse until this is fixed; ` +
220
+ `"instance unset-proxy ${instanceId}" removes the pin.`
221
+ );
222
+ resolvedProxy = { source: "none" as const };
223
+ clientConfig = {
224
+ ...configFromResolved(proxyBase, instanceId, resolvedProxy),
225
+ proxyError: reason,
226
+ };
227
+ }
228
+
229
+ const proxyUrl = clientConfig.proxy;
230
+
190
231
  // No command provided - start REPL
191
232
  if (!command) {
192
233
  console.log(`\n🚀 Starting miaw-cli REPL...`);
193
234
  console.log(`📂 Instance: ${instanceId}`);
194
235
  console.log(`📂 Session: ${sessionPath}`);
195
- if (proxyUrl) console.log(`🌐 Proxy: ${maskProxyUrl(proxyUrl)}`);
236
+ if (proxyUrl) {
237
+ console.log(
238
+ `🌐 Proxy: ${maskProxyUrl(proxyUrl)} (${describeProxySource(resolvedProxy.source)})`
239
+ );
240
+ }
196
241
  console.log(`🔧 Debug: ${debugMode ? "ON" : "OFF"}\n`);
197
242
 
198
243
  try {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Call Commands (v1.12.0)
3
+ *
4
+ * Create shareable call links. Rejecting a call is an event-driven operation —
5
+ * it only works while a call is ringing — so it belongs in a bot's `call`
6
+ * handler rather than on the CLI.
7
+ */
8
+ import { MiawClient } from "../../index.js";
9
+ /**
10
+ * Create a shareable call link
11
+ */
12
+ export declare function cmdCallLink(client: MiawClient, args: {
13
+ type: "audio" | "video";
14
+ startTime?: number;
15
+ }): Promise<boolean>;
16
+ //# sourceMappingURL=call.d.ts.map