@funnelchat-hq/cli 0.1.1 → 0.2.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 (3) hide show
  1. package/README.md +138 -15
  2. package/dist/cli.js +588 -132
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,8 +1,32 @@
1
1
  # @funnelchat-hq/cli
2
2
 
3
- The Funnelchat CLI to **export your conversations and your contacts** through the public API, and check your numbers, your templates and your MACs, with your integrator key. It writes files you can verify, never overwrites one that already exists and, if it gets interrupted, picks up where it left off.
3
+ The Funnelchat CLI. **Connect a WhatsApp number, send a message and watch it get read**, in three commands, without writing code or a webhook:
4
4
 
5
5
  ```bash
6
+ npm install --global @funnelchat-hq/cli
7
+
8
+ funnelchat login # 1. paste your API key; it is not shown
9
+ funnelchat connect # 2. open the link it prints and scan the QR with WhatsApp
10
+ funnelchat send 5491122334455 "Hello from Funnelchat" # 3. and watch it: queued → sent → delivered → read
11
+ ```
12
+
13
+ ```
14
+ Sending to 5491122334455 from Support (5491100000002)…
15
+ queued
16
+ sent after 1 s
17
+ delivered after 2 s
18
+ read after 14 s
19
+ Message 98765 to 5491122334455: read 14 s after sending.
20
+ ```
21
+
22
+ The phone does not need to be a contact yet: the send creates it. Send it to your own phone to see it arrive.
23
+
24
+ **Handing it to an AI agent?** Agents are good at running commands. Two things help them: `funnelchat connect --no-wait` prints the link and ends at once (the agent shows it to you, and `funnelchat numbers list` says when the number is connected), and `--json` on any command gives one JSON document to read.
25
+
26
+ The same key also **follows your WhatsApp groups** (`funnelchat groups`), **exports your conversations and your contacts** to files you can verify, and checks your numbers, templates and MACs:
27
+
28
+ ```bash
29
+ funnelchat groups # the groups of your number, as WhatsApp has them
6
30
  funnelchat contacts export --output-dir ./my-contacts
7
31
  funnelchat conversations export --since 2026-10-01 --output-dir ./october
8
32
  ```
@@ -11,7 +35,8 @@ funnelchat conversations export --since 2026-10-01 --output-dir ./october
11
35
 
12
36
  ## What it does and what it does not
13
37
 
14
- - **It only reads.** It sends no messages and creates or changes nothing in your account: every call it makes is a `GET`.
38
+ - **It writes only when you ask it to.** `connect` creates a connect link, `send` sends a message (and creates the contact if the phone is new) and `groups follow` imports a group into your account. Every other command only reads: each call it makes is a `GET`.
39
+ - **A write is sent once.** The CLI never repeats it on its own; if the network fails halfway, it says the write may have arrived, so you can check before repeating it.
15
40
  - **It exports by paging through the public API**, because the API has no server-side export. A large account takes a while: the CLI goes at a low pace on purpose, so it does not slow down your panel.
16
41
  - **It never stores or shows your key** anywhere except your configuration file, if you use `funnelchat login`.
17
42
 
@@ -26,7 +51,7 @@ funnelchat --version
26
51
 
27
52
  Or without installing it: `npx @funnelchat-hq/cli --version`.
28
53
 
29
- ### A minimal example
54
+ ### An export example
30
55
 
31
56
  ```bash
32
57
  funnelchat login # paste your key; it is not shown
@@ -54,9 +79,15 @@ The CLI uses an API key of your account: `<id>|fc_live_...`, complete, as the **
54
79
 
55
80
  | Command | Permission |
56
81
  |---|---|
82
+ | `connect` | `accounts:operate` (WhatsApp numbers → Create, connect and delete) and `accounts:read` to wait for the number |
83
+ | `send` | `conversations:send`; to a phone that is not a contact yet, `conversations:write` (it creates the contact); `conversations:read` to watch the status; `accounts:read` to pick the number without `--device` |
84
+ | `groups` | `communities:read`; `accounts:read` without `--device` |
85
+ | `groups follow` | `communities:operate` (Groups and campaigns → Manage groups); `accounts:read` without `--device` |
57
86
  | `contacts export`, `conversations export`, `templates`, `mac status` | `conversations:read` |
58
87
  | `whoami`, `numbers` | `accounts:read` |
59
88
 
89
+ For the first use, a key with **WhatsApp numbers** (view and connect) and **Contacts and conversations** (view, edit and send) is enough.
90
+
60
91
  There are two ways to give it the key:
61
92
 
62
93
  **With an environment variable**, for scripts and servers:
@@ -105,6 +136,94 @@ FUNNELCHAT_API_KEY='1|fc_live_sandbox' funnelchat --base-url http://127.0.0.1:40
105
136
 
106
137
  Every command takes `--json` for use from scripts and `--help` to see all its options.
107
138
 
139
+ ### `funnelchat connect`
140
+
141
+ Connects a WhatsApp number to your account by QR code. It creates a connect link (`POST /api/v1/connect-links` with `type: "qr"`), prints it, and waits until the number is connected.
142
+
143
+ ```bash
144
+ funnelchat connect
145
+ ```
146
+
147
+ ```
148
+ Open this link, tap the button and scan the QR code with WhatsApp on the phone you are connecting (Linked devices → Link a device):
149
+
150
+ https://…
151
+
152
+ It works once and expires at 2026-11-05T21:00:00+00:00; after tapping the button there are 10 minutes to scan the QR.
153
+ Whoever has the link can connect a number to your account: do not share it.
154
+
155
+ Waiting for the number to connect… (Ctrl+C to stop)
156
+ Connected: Support (5491100000002), id 4.
157
+ Send a first message: funnelchat send <phone> "Hello from Funnelchat"
158
+ ```
159
+
160
+ | Option | What it does |
161
+ |---|---|
162
+ | `--no-wait` | Prints the link and ends, without waiting. For scripts and agents: `funnelchat numbers list` then shows when the number is connected. |
163
+ | `--timeout <seconds>` | How long to wait for the number. By default, 900 (15 minutes). |
164
+
165
+ - **The link** is a page on Funnelchat's domain. Opening it does not use it; tapping its button creates the number and shows the QR, which renews itself every 5 seconds. Treat it as a secret: it comes only in this answer, and whoever has it can connect a number to your account.
166
+ - **How it knows:** it lists your numbers before creating the link and then every 5 seconds (`GET /api/v1/devices`), and stops at the first one that was not connected before. Each listing counts toward the daily cap of accounts (about 180 calls in 15 minutes, of 10,000 a day).
167
+ - **With `--json`**, the link goes to stderr as soon as it exists, and stdout gets one document at the end, with the link and the number.
168
+ - **No QR in the terminal:** the page shows the QR that WhatsApp scans.
169
+ - If nobody scans the QR within 10 minutes of tapping the button, the link fails and the number it created is deleted: run `funnelchat connect` again. If the CLI stops waiting before (`--timeout`, exit code `8`, or Ctrl+C), the link still works until it expires.
170
+
171
+ ### `funnelchat send <phone> <text>`
172
+
173
+ Sends a text message to a phone and shows its status live, until it is `read`, it `failed` or `--timeout` ends.
174
+
175
+ ```bash
176
+ funnelchat send 5491122334455 "Hello from Funnelchat"
177
+ funnelchat send +54 9 11 2233-4455 "Hello" --device 4 --timeout 300
178
+ ```
179
+
180
+ | Option | What it does |
181
+ |---|---|
182
+ | `--device <id>` | The number it sends from. By default, the only connected number of your account; with several, the CLI asks for this. |
183
+ | `--timeout <seconds>` | How long to watch the status. By default, 90. `0` sends and does not watch. |
184
+
185
+ - **The phone**, with its country code. Spaces, dashes, dots, parentheses and a leading `+` are dropped. It does not need to be a contact yet: the send creates it (`POST /api/v1/message/send-message`, with `device_id`, `phone` and `text`).
186
+ - **The status:** `queued` (in line, not sent yet), `sent`, `delivered` and `read`, or `failed` with the reason. The CLI reads it with `GET /api/v1/contacts/{contact}/messages/{message}`, 1 second after each change and then less and less often (2 s, 4 s… up to 10 s). If the recipient turned read receipts off, it stays `delivered`.
187
+ - **Exit codes:** `0` when it is read, or still on its way when `--timeout` ends; `6` when it failed (`code` `message_failed`, with the reason in `--json`). Ctrl+C stops watching (`130`); the message was already sent.
188
+ - **On an official number** (WhatsApp Cloud API), free text only goes out within 24 hours of the contact's last message, so the first message to a new contact has to be a template: the API answers `422 window_closed` and nothing is sent. A number connected by QR has no such window.
189
+ - **Rejections say what happened in plain words,** and that nothing was sent when the API guarantees it: `window_closed`, `recipient_mismatch`, `antiblock_*`, `device_unavailable`, `403` (the permission missing) and `429` (how long to wait). A send is never repeated on its own.
190
+ - **With `--json`**, one document with the message, every status with the seconds it took, and `outcome`: `read`, `timeout`, `unknown` (the provider never confirmed it: it will not change) or `not_watched`.
191
+
192
+ ### `funnelchat groups`
193
+
194
+ The WhatsApp groups of a number connected by QR, as WhatsApp has them, and which ones you follow.
195
+
196
+ ```bash
197
+ funnelchat groups
198
+ funnelchat groups --device 4
199
+ ```
200
+
201
+ ```
202
+ uid name followed
203
+ 120363000000000001@g.us Launch Nov yes
204
+ 120363000000000002@g.us Clients VIP no
205
+ ```
206
+
207
+ - **Live:** `GET /api/v1/devices/{device}/groups` reads them from WhatsApp, and reuses the list for up to a minute per number: a group just created can take that long to show.
208
+ - **The number:** `--device`, or the only number of your account connected by QR.
209
+
210
+ ### `funnelchat groups follow <group>`
211
+
212
+ Follows a group: imports it to your account, as importing it from the panel does. From then on, who joins and who leaves arrives as the `group_participant.joined` and `group_participant.left` webhook events.
213
+
214
+ ```bash
215
+ funnelchat groups follow 120363000000000002@g.us
216
+ ```
217
+
218
+ ```
219
+ Following "Clients VIP" (120363000000000002@g.us).
220
+ Who joins and who leaves arrives as the group_participant.joined and group_participant.left webhook events.
221
+ ```
222
+
223
+ - `<group>` is the `uid` that `funnelchat groups` shows. A group already followed says so, and nothing changes.
224
+ - **Only numbers connected by QR** (`422 provider_unsupported` otherwise), and up to 5 groups per number per minute (`429 import_rate_limited`). If the number is not an admin of the group, the CLI warns you: it can follow it, but not send messages to it or manage it.
225
+ - `POST /api/v1/groups/{group}/import` with `device_id`; it needs `communities:operate` (Groups and campaigns → Manage groups).
226
+
108
227
  ### `funnelchat whoami`
109
228
 
110
229
  Which account the key belongs to, without showing the key.
@@ -269,8 +388,8 @@ MACs in the period: 120 of 1,000 (12 %) · 880 left
269
388
  {
270
389
  "manifest_version": 1,
271
390
  "kind": "conversations",
272
- "generator": { "name": "@funnelchat-hq/cli", "version": "0.1.1" },
273
- "api": { "base_url": "https://api.funnelchat.com", "spec": { "commit": "0ef4abb…", "sha256": "8bbbae5…" }, "routes": [ … ] },
391
+ "generator": { "name": "@funnelchat-hq/cli", "version": "0.2.0" },
392
+ "api": { "base_url": "https://api.funnelchat.com", "spec": { "commit": "99928866…", "sha256": "0ffaad2…" }, "routes": [ … ] },
274
393
  "started_at": "2026-10-16T12:00:00.000Z",
275
394
  "finished_at": "2026-10-16T12:41:10.000Z",
276
395
  "runs": 2,
@@ -318,8 +437,9 @@ funnelchat contacts export --output-dir ./contacts --resume
318
437
 
319
438
  - **Low pace:** at most 1 call per second, retries included. Change it with `--rate` (greater than 0 and up to 10; `0.5` is one every two seconds) or `FUNNELCHAT_RATE`. The API's per-minute limits are counted per user and your panel shares them: raise it carefully.
320
439
  - **`429` and `503`:** the CLI waits exactly what `Retry-After` says and retries. If it asks for more than 60 seconds (`--max-wait` changes that), it stops with code `5` and tells you when it resets.
321
- - **Server and network errors:** it retries with exponential backoff (1 s, 2 s, 4 s… up to 30 s), up to 4 times; also a `502 origin_unreachable` from the API's entry point. **It only retries reads**; the CLI does nothing else. A `500 edge_misconfigured` is not retried: it is a configuration problem on our side and waiting does not fix it.
322
- - **The daily cap:** accounts and communities allow up to 10,000 calls per day per key, each on its own; conversations has no daily cap. The contacts and conversations exports use only conversations; `whoami` and `numbers` use accounts. If the cap is reached, the CLI stops with a clear message and the reset time, and the export can be resumed.
440
+ - **Server and network errors:** it retries with exponential backoff (1 s, 2 s, 4 s… up to 30 s), up to 4 times; also a `502 origin_unreachable` from the API's entry point. **It only retries reads**: a write (`connect`, `send`, `groups follow`) is sent once, and a `429` on it says how long to wait. A `500 edge_misconfigured` is not retried: it is a configuration problem on our side and waiting does not fix it.
441
+ - **Waiting without hurry:** `connect` lists your numbers every 5 seconds; `send` reads the message 1 second after each change and then less and less often, up to every 10 seconds.
442
+ - **The daily cap:** accounts and communities allow up to 10,000 calls per day per key, each on its own; conversations has no daily cap. The contacts and conversations exports and the message of `send` use only conversations; `whoami`, `numbers`, `connect` and picking the number without `--device` use accounts; `groups` uses communities. If the cap is reached, the CLI stops with a clear message and the reset time, and the export can be resumed.
323
443
 
324
444
  ## For scripts
325
445
 
@@ -345,8 +465,9 @@ The CLI asks the API for its messages in Spanish (`Accept-Language: es`), so the
345
465
  | `3` | No key, a key the API rejects (`401`) or a key without the permission the route needs (`403`). |
346
466
  | `4` | Not found in your account (`404`), or the route does not exist in the API (`404 route_not_found`). |
347
467
  | `5` | An API limit the CLI does not wait for on its own: the daily cap or a long `Retry-After`. It can be resumed. |
348
- | `6` | The API failed (`5xx`, `422`…) or could not be reached, after the retries. |
468
+ | `6` | The API failed (`5xx`, `422`…) or could not be reached, after the retries; or the message `send` watched failed. |
349
469
  | `7` | The output already exists: the CLI never overwrites a file. |
470
+ | `8` | `connect` waited `--timeout` and no number connected. |
350
471
  | `130` | Interrupted with Ctrl+C. An export can be resumed. |
351
472
 
352
473
  ## What the API does not allow yet
@@ -368,7 +489,7 @@ The CLI uses only routes that exist in the public spec (`spec/routes-used.json`
368
489
 
369
490
  ## Where the spec comes from
370
491
 
371
- `spec/funnelchat.json` is the portal's unified snapshot (developers-portal, branch `feat/openapi-unificada` @ `0ef4abb`, merged into `main` on 2026-10-21), built from the domains' snapshots of 2026-10-13. On 2026-10-22 every command was checked against the code in production (conversations `v1.18.10`, accounts `v1.4.24`, api-edge `v0.3.0`): the routes and parameters the CLI uses did not change.
492
+ `spec/funnelchat.json` is the portal's full unified spec (developers-portal `main` @ `99928866`, deployed as v0.6.3 on 2026-11-05: `generated/specs/funnelchat-completa.json`, which carries every operation, not only the ones the portal shows), built from the snapshots of accounts `v1.5.97` (`558332bcf`), conversations `v1.19.21` (`f2937a713`) and communities `54643bbd` (the app code of `v2.35.7`). `spec/procedencia.json` says so, with the file's `sha256`. Every route, parameter and body field the CLI uses is in it: `spec/pending.json` stays empty.
372
493
 
373
494
  ## Development
374
495
 
@@ -381,7 +502,7 @@ npm run build
381
502
 
382
503
  To develop you need Node 20 or later (for the test tools); the CLI it builds runs on Node 18.
383
504
 
384
- **The tests** use simulated answers, with the same shapes as the API (pages by number and by cursor). They cover paging, resuming, `429`s, that the key never shows up in any output or any file, that no file is overwritten, and the manifest's SHA-256.
505
+ **The tests** use simulated answers, with the same shapes as the API (pages by number and by cursor). They cover paging, resuming, `429`s, that the key never shows up in any output or any file, that no file is overwritten, the manifest's SHA-256, and the first use: `connect` waiting for the number, and `send` watching the status until it is read.
385
506
 
386
507
  **End to end against the sandbox.** With an already built checkout of [funnelchat-sandbox](https://github.com/Funnelchat20/funnelchat-sandbox) (`npm install && npm run build` in its folder), this test starts it in the same process, on a free port, and runs the CLI against it. It does not touch the sandbox's repository:
387
508
 
@@ -391,17 +512,19 @@ FUNNELCHAT_SANDBOX_DIR=../funnelchat-sandbox npm run test:e2e
391
512
 
392
513
  Without that variable, `npm test` skips it.
393
514
 
394
- **The spec.** It is copied and regenerated with a single script, which records where it came from in `spec/procedencia.json` and checks that every route and every parameter the CLI uses exists:
515
+ **The spec.** It is copied and regenerated with a single script, which records where it came from in `spec/procedencia.json` and checks that every route, every parameter and every body field the CLI uses exists:
395
516
 
396
517
  ```bash
397
- npm run generate -- --spec ../developers-portal/out/specs/funnelchat.json # a new snapshot
398
- npm run generate # regenerate
399
- npm run generate:check # is it up to date?
518
+ npm run generate -- --spec ../developers-portal/generated/specs/funnelchat-completa.json # a new spec
519
+ npm run generate # regenerate
520
+ npm run generate:check # is it up to date?
400
521
  ```
401
522
 
523
+ The portal's full spec is a build output git ignores, so a copy of it says where it came from on the command line, as funnelchat-node does: `--expect-sha256 <hex>`, `--source repository=… --source branch=… --source commit=… --source path=…` and `--domain <area>=<sha>@<branch>@<snapshot_at>` for each area. `spec/procedencia.json` marks those fields as declared.
524
+
402
525
  `spec/pending.json` is for a route that is not in the snapshot yet; today it is empty. When a new snapshot has a route from there, the generation says it can be deleted.
403
526
 
404
- **Publishing.** Push a `vX.Y.Z` tag on `main` that matches the version in `package.json`, and GitHub Actions publishes it to npm with provenance (`.github/workflows/publish.yml`).
527
+ **Publishing.** Push a `vX.Y.Z` tag on `main` that matches the version in `package.json`; GitHub Actions publishes it through npm Trusted Publishing (`.github/workflows/publish.yml`).
405
528
 
406
529
  ## License
407
530