miaw-core 1.9.1 → 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 (123) hide show
  1. package/.env.example +78 -0
  2. package/CHANGELOG.md +367 -0
  3. package/README.md +116 -26
  4. package/bin/miaw-cli.ts +100 -9
  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 +8 -5
  14. package/dist/cli/commands/commands-index.d.ts.map +1 -1
  15. package/dist/cli/commands/commands-index.js +12 -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 +15 -5
  26. package/dist/cli/commands/index.d.ts.map +1 -1
  27. package/dist/cli/commands/index.js +491 -46
  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/proxy.d.ts +59 -0
  38. package/dist/cli/commands/proxy.d.ts.map +1 -0
  39. package/dist/cli/commands/proxy.js +418 -0
  40. package/dist/cli/commands/proxy.js.map +1 -0
  41. package/dist/cli/commands/send.d.ts +12 -0
  42. package/dist/cli/commands/send.d.ts.map +1 -1
  43. package/dist/cli/commands/send.js +44 -0
  44. package/dist/cli/commands/send.js.map +1 -1
  45. package/dist/cli/repl.d.ts +8 -6
  46. package/dist/cli/repl.d.ts.map +1 -1
  47. package/dist/cli/repl.js +501 -81
  48. package/dist/cli/repl.js.map +1 -1
  49. package/dist/cli/utils/client-cache.d.ts +8 -0
  50. package/dist/cli/utils/client-cache.d.ts.map +1 -1
  51. package/dist/cli/utils/client-cache.js +66 -7
  52. package/dist/cli/utils/client-cache.js.map +1 -1
  53. package/dist/cli/utils/instance-config.d.ts +89 -0
  54. package/dist/cli/utils/instance-config.d.ts.map +1 -0
  55. package/dist/cli/utils/instance-config.js +224 -0
  56. package/dist/cli/utils/instance-config.js.map +1 -0
  57. package/dist/cli/utils/instance-registry.d.ts +7 -0
  58. package/dist/cli/utils/instance-registry.d.ts.map +1 -1
  59. package/dist/cli/utils/instance-registry.js +2 -0
  60. package/dist/cli/utils/instance-registry.js.map +1 -1
  61. package/dist/cli/utils/parse-args.d.ts +41 -0
  62. package/dist/cli/utils/parse-args.d.ts.map +1 -0
  63. package/dist/cli/utils/parse-args.js +84 -0
  64. package/dist/cli/utils/parse-args.js.map +1 -0
  65. package/dist/cli/utils/proxy-config.d.ts +46 -0
  66. package/dist/cli/utils/proxy-config.d.ts.map +1 -0
  67. package/dist/cli/utils/proxy-config.js +93 -0
  68. package/dist/cli/utils/proxy-config.js.map +1 -0
  69. package/dist/cli/utils/proxy-resolver.d.ts +91 -0
  70. package/dist/cli/utils/proxy-resolver.d.ts.map +1 -0
  71. package/dist/cli/utils/proxy-resolver.js +180 -0
  72. package/dist/cli/utils/proxy-resolver.js.map +1 -0
  73. package/dist/cli/utils/session.d.ts +24 -1
  74. package/dist/cli/utils/session.d.ts.map +1 -1
  75. package/dist/cli/utils/session.js +5 -0
  76. package/dist/cli/utils/session.js.map +1 -1
  77. package/dist/client/MiawClient.d.ts +333 -9
  78. package/dist/client/MiawClient.d.ts.map +1 -1
  79. package/dist/client/MiawClient.js +874 -30
  80. package/dist/client/MiawClient.js.map +1 -1
  81. package/dist/index.d.ts +10 -3
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +9 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/types/index.d.ts +234 -2
  86. package/dist/types/index.d.ts.map +1 -1
  87. package/dist/types/index.js +22 -0
  88. package/dist/types/index.js.map +1 -1
  89. package/dist/utils/browser-presets.d.ts +51 -0
  90. package/dist/utils/browser-presets.d.ts.map +1 -0
  91. package/dist/utils/browser-presets.js +53 -0
  92. package/dist/utils/browser-presets.js.map +1 -0
  93. package/dist/utils/proxy-agent.d.ts +47 -11
  94. package/dist/utils/proxy-agent.d.ts.map +1 -1
  95. package/dist/utils/proxy-agent.js +65 -12
  96. package/dist/utils/proxy-agent.js.map +1 -1
  97. package/dist/utils/proxy-loader.d.ts +114 -0
  98. package/dist/utils/proxy-loader.d.ts.map +1 -0
  99. package/dist/utils/proxy-loader.js +379 -0
  100. package/dist/utils/proxy-loader.js.map +1 -0
  101. package/dist/utils/proxy-rotator.d.ts +132 -0
  102. package/dist/utils/proxy-rotator.d.ts.map +1 -0
  103. package/dist/utils/proxy-rotator.js +268 -0
  104. package/dist/utils/proxy-rotator.js.map +1 -0
  105. package/docs/API_STABILITY_REVIEW.md +10 -0
  106. package/docs/BAILEYS_MIGRATION_v7.md +10 -1
  107. package/docs/BAILEYS_VS_MIAW_COMPARISON.md +69 -48
  108. package/docs/CLI-ANALYSIS.md +10 -0
  109. package/docs/CLI.md +381 -4
  110. package/docs/CLI_INTEGRATION_TEST_PLAN.md +58 -24
  111. package/docs/CODE_REVIEW_REPORT.md +10 -0
  112. package/docs/DEFERRED_FEATURES.md +34 -38
  113. package/docs/DEPLOYMENT_INSTANCE_PROXY.md +168 -0
  114. package/docs/DEPLOYMENT_PROXY.md +151 -0
  115. package/docs/DEPLOYMENT_V1.12.0.md +163 -0
  116. package/docs/FOLLOW_UPS.md +320 -0
  117. package/docs/MIGRATION.md +147 -2
  118. package/docs/PROXY.md +706 -0
  119. package/docs/TEST_COVERAGE_ANALYSIS.md +29 -5
  120. package/docs/USAGE.md +340 -59
  121. package/package.json +8 -4
  122. package/sessions-cli/.cli_history +2 -0
  123. package/docs/PROXY_SUPPORT_PLAN.md +0 -1442
package/.env.example ADDED
@@ -0,0 +1,78 @@
1
+ # miaw-core / miaw-cli runtime configuration
2
+ #
3
+ # Copy to .env and edit. Every value here is optional — each has a default and
4
+ # each can also be supplied as a CLI flag. Flags win over environment variables.
5
+ #
6
+ # cp .env.example .env
7
+ #
8
+ # .env is gitignored. Prefer this file over CLI flags for anything containing a
9
+ # password: flags land in your shell history and in the process list, where any
10
+ # other user on the machine can read them.
11
+
12
+ # ─── Instance ────────────────────────────────────────────────────────────────
13
+
14
+ # Instance ID. Sessions are isolated per instance. (flag: --instance-id)
15
+ MIAW_INSTANCE_ID=default
16
+
17
+ # Where session credentials are stored, as {MIAW_SESSION_PATH}/{MIAW_INSTANCE_ID}/
18
+ # (flag: --session-path)
19
+ MIAW_SESSION_PATH=./sessions-cli
20
+
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.
34
+
35
+ # A single proxy for this instance. (flag: --proxy)
36
+ # Schemes: http, https, socks4, socks4a, socks5, socks5h
37
+ #
38
+ # Prefer the h/a variants (socks5h, socks4a): plain socks5/socks4 resolve DNS
39
+ # locally, so the destination hostname leaks to your resolver even though the
40
+ # connection itself is tunnelled.
41
+ #
42
+ # Passwords containing @ : / ? # or % must be percent-encoded here
43
+ # ("p@ss#1" becomes "p%40ss%231"), or use the ProxyConfig object form in code.
44
+ #
45
+ # MIAW_PROXY=socks5h://user:password@proxy.example.com:1080
46
+
47
+ # A proxy list file instead of a single proxy — TXT (one per line) or JSON
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.
57
+ #
58
+ # See examples/proxies.example.txt for the format.
59
+ #
60
+ # MIAW_PROXY_FILE=./proxies.txt
61
+
62
+ # How a proxy is picked from MIAW_PROXY_FILE. (flag: --proxy-strategy)
63
+ # deterministic same instanceId always maps to the same proxy [default]
64
+ # round-robin next in the list, wrapping
65
+ # random uniform random
66
+ # weighted random, biased by each entry's weight=
67
+ #
68
+ # Leave this as deterministic for anything with a persisted WhatsApp session.
69
+ # WhatsApp treats a session's source IP as a trust signal, so a session whose
70
+ # egress IP jumps between runs looks like an account takeover. Rotation is for
71
+ # spreading instances across proxies, never for rotating a live session's IP.
72
+ #
73
+ # MIAW_PROXY_STRATEGY=deterministic
74
+
75
+ # ─── Logging ─────────────────────────────────────────────────────────────────
76
+
77
+ # Verbose Baileys logging. (flag: --debug)
78
+ # DEBUG=false
package/CHANGELOG.md CHANGED
@@ -5,6 +5,330 @@ 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
+
239
+ ## [1.10.0] - 2026-07-29
240
+
241
+ **Proxy files, rotation, and CLI diagnostics** - builds on the v1.3.0 proxy core.
242
+ All additive; no breaking changes.
243
+
244
+ ### Added
245
+
246
+ - **`socks5h://` and `socks4a://` support.** These resolve DNS *at the proxy*;
247
+ plain `socks5://` / `socks4://` resolve locally, so the destination hostname still
248
+ leaks to your DNS resolver even though the connection is tunnelled. Both variants
249
+ were previously rejected as unsupported protocols despite the underlying agent
250
+ supporting them.
251
+ - **Proxy list files** - `loadProxyList()` / `loadProxyListSync()` / `parseProxyList()`
252
+ read TXT (one per line, `#`/`;` comments, optional `weight=` and `label=` tokens,
253
+ scheme-less `host:port[:user:pass]` vendor forms) or JSON (array of strings and/or
254
+ objects, or a `{ "proxies": [...] }` wrapper). Format is auto-detected by extension
255
+ then by content. `validateProxyList()` partitions a list; `watchProxyList()` hot-reloads
256
+ a file.
257
+ - **`ProxyRotator`** - `round-robin`, `random`, `weighted`, and `deterministic`
258
+ strategies, plus `ProxyRotator.fromFile()`, `setProxies()`, `getStats()`, and `close()`.
259
+ It returns a `ProxyPoolEntry` you pass to `new MiawClient({ proxy })`, so `getProxyInfo()`
260
+ and proxy validation keep working for rotated instances.
261
+ - **`maskProxyUrl()`** - exported credential masking; `getProxyInfo()` now uses it.
262
+ - **CLI**: `proxy list` (alias `ls`), `proxy test <url>`, `proxy test-all`. These need
263
+ **no WhatsApp connection** - they probe the proxy through the same agent Baileys uses,
264
+ so you can validate a proxy before spending a pairing attempt on it. `proxy test-all`
265
+ exits non-zero if any proxy fails, so it can gate a deploy.
266
+ - **CLI global flags**: `--proxy-file <path>`, `--proxy-strategy <strategy>`
267
+ (default `deterministic`; `instance` accepted as an alias). Env fallbacks:
268
+ `MIAW_PROXY`, `MIAW_PROXY_FILE`, `MIAW_PROXY_STRATEGY`.
269
+ - **[.env.example](./.env.example)** - documents every `MIAW_*` runtime variable,
270
+ including `MIAW_INSTANCE_ID` and `MIAW_SESSION_PATH`, which the CLI has always
271
+ read but which were never written down anywhere.
272
+ - **[docs/DEPLOYMENT_PROXY.md](./docs/DEPLOYMENT_PROXY.md)** - operational notes for
273
+ deploying with proxies: why `instanceId` must be stable across rollouts, treating
274
+ proxy files as secrets, ConfigMap-safe hot reload, egress firewall rules, and the
275
+ sticky-session requirement.
276
+ - **[docs/PROXY.md](./docs/PROXY.md)** - dedicated proxy guide covering configuration,
277
+ proxy files, rotation, CLI diagnostics, troubleshooting, security, and provider
278
+ selection. Replaces the internal `docs/PROXY_SUPPORT_PLAN.md`, which is removed.
279
+ - 97 new tests (42 loader, 33 rotator, 22 CLI). The CLI proxy tests are the first in
280
+ `tests/integration/cli/` that run without a WhatsApp session.
281
+
282
+ ### Fixed
283
+
284
+ - **Media uploads through a proxy were broken and always had been** (since v1.3.0).
285
+ `fetchAgent` was set to an undici `ProxyAgent`, but Baileys declares it as an
286
+ `https.Agent` and its Node upload path hands it to `https.request({ agent })`, which
287
+ cannot use a Dispatcher. Every `sendImage`/`sendVideo`/`sendDocument` through a proxy
288
+ failed with "Media upload failed on all hosts". `fetchAgent` is now the same
289
+ `http.Agent` as `wsAgent`. **Uploads are consequently proxied on SOCKS too**, which
290
+ the previous documentation said was impossible.
291
+ - **Media downloads were never proxied.** Baileys downloads with
292
+ `fetch(url, { dispatcher })` and never wires a proxy into that path, and
293
+ `downloadMedia()` passed no options — so every download revealed the real IP
294
+ regardless of proxy settings. It now passes an undici Dispatcher explicitly.
295
+ This works for HTTP/HTTPS proxies; SOCKS downloads remain direct because undici
296
+ has no SOCKS transport.
297
+ - `proxy test` reported a **407 Proxy Authentication Required as success**. A 407 comes
298
+ from the proxy itself and means the tunnel was refused, so nothing ever reached
299
+ WhatsApp - but the probe counted any HTTP status as reachable. `proxy test-all` would
300
+ therefore green-light a proxy list with wrong credentials, defeating the pre-flight
301
+ check it exists to provide. It now fails with `EPROXYAUTH` and a non-zero exit code.
302
+ - Proxy credentials leaked in two places: `MiawClient` interpolated the raw proxy URL,
303
+ password included, into its "Invalid proxy configuration" error (which lands in logs
304
+ and stack traces), and the CLI printed the raw `--proxy` URL to the REPL startup
305
+ banner. Both are masked now.
306
+
307
+ ### Notes
308
+
309
+ - `--proxy-strategy` defaults to `deterministic` so a given `instanceId` keeps a stable
310
+ egress IP across runs. **Rotation is for distributing instances across proxies, never
311
+ for rotating a live session's IP** - WhatsApp treats a session's source IP as a trust
312
+ signal, and churning it looks like account takeover.
313
+ - Deterministic selection uses rendezvous hashing rather than `hash % length`, so adding
314
+ or removing a proxy remaps only the instances that were on it. Under modulo, one edit
315
+ to your proxy file would change the egress IP of every live session at once.
316
+ - SOCKS proxies still route media over a direct connection: Node's `fetch()` requires an
317
+ undici dispatcher and undici has no SOCKS transport. `proxy list` and `proxy test`
318
+ report this per proxy. See [docs/PROXY.md](./docs/PROXY.md).
319
+
320
+ ## [1.9.2] - 2026-07-09
321
+
322
+ **CommonJS-consumer fix** - Let `require()` resolve the package.
323
+
324
+ ### Fixed
325
+
326
+ - The `exports` map only declared the `import` condition, so CommonJS /
327
+ `require()` resolution (e.g. `ts-node` compiling to CJS) failed with
328
+ `ERR_PACKAGE_PATH_NOT_EXPORTED`. Added a `default` condition pointing at the
329
+ same ESM build; on Node >=22.12 `require(esm)` now resolves it. The package
330
+ stays ESM-only — no separate CJS build was added.
331
+
8
332
  ## [1.9.1] - 2026-07-07
9
333
 
10
334
  **Message receipts** - Observe delivery/read/played status of messages you send.
@@ -328,6 +652,49 @@ unchanged.
328
652
 
329
653
  ---
330
654
 
655
+ ## [1.3.0] - 2026-03-13
656
+
657
+ **Proxy support** - route a WhatsApp connection through a proxy, per instance.
658
+
659
+ _Backfilled: this release shipped in commit `8def495` but was never recorded here._
660
+
661
+ ### Added
662
+
663
+ - `proxy` option on `MiawClientOptions`, accepting a URL string or a `ProxyConfig`
664
+ object (`{ url, username?, password? }`). Supports HTTP, HTTPS, SOCKS4, and SOCKS5.
665
+ - `createProxyAgents()` and `validateProxyConfig()` in `src/utils/proxy-agent.ts`.
666
+ Dual-agent approach: `https-proxy-agent` / `socks-proxy-agent` for the WebSocket
667
+ transport, `undici`'s `ProxyAgent` for media upload/download.
668
+ - `agent` and `fetchAgent` escape-hatch options for supplying custom transports.
669
+ - `MiawClient.getProxyInfo()` for inspecting the active proxy.
670
+ - `--proxy <url>` CLI flag.
671
+
672
+ ### Notes
673
+
674
+ - SOCKS proxies tunnel the WebSocket but not media transfers: Node's `fetch()` needs an
675
+ undici dispatcher and undici has no SOCKS transport, so media falls back to a direct
676
+ connection.
677
+
678
+ ## [1.2.0] - 2026-01-20
679
+
680
+ **Code quality release** - configurable timeouts, validation utilities, and type safety.
681
+
682
+ _Backfilled: this release shipped in commit `2e922d2` but was never recorded here._
683
+
684
+ ### Added
685
+
686
+ - Configurable timeouts and exported `TIMEOUTS` / `THRESHOLDS` constants.
687
+ - Validation utilities and type-guard helpers (`isError`, `getErrorMessage`,
688
+ `isBaileysMessage`, `isBaileysMessageUpsert`).
689
+ - Custom logger support.
690
+ - CLI: `send video`, `send audio`, `media download`; topic-specific help sub-commands
691
+ and expanded autocomplete.
692
+
693
+ ### Changed
694
+
695
+ - Type-safety improvements throughout; 14 of 15 issues from the code review report
696
+ resolved. No breaking changes.
697
+
331
698
  ## [1.1.0] - 2026-01-02
332
699
 
333
700
  **Baileys v7 Migration** - ESM-only release with improved session stability