@flowapt/flowiq-cli 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -11
- package/TEAM-GUIDE.md +2 -1
- package/package.json +1 -1
- package/src/commands/broadcast.js +13 -4
package/README.md
CHANGED
|
@@ -227,13 +227,28 @@ flowiq bc resume <org_id> --campaign july-referrals --commit [--retry-failed]
|
|
|
227
227
|
`--template <substr>` (case-insensitive contains), `--since <date>`, `--json`.
|
|
228
228
|
Read-only. NOTE: plain `bc list` (no org) still lists your **local campaign
|
|
229
229
|
files** — the remote verb is named after `pinboard list-remote`.
|
|
230
|
-
- **`status <org> <broadcastId>` (v0.3.5)**: live delivery
|
|
231
|
-
by its id — `
|
|
232
|
-
`helpdesk_messages`. Built for the `--python`
|
|
233
|
-
returns a `broadcastId` but has no CLI status
|
|
234
|
-
id (dashboard sends included).
|
|
235
|
-
failed recipient + the Meta
|
|
236
|
-
by-reason rollup — makes a
|
|
230
|
+
- **`status <org> <broadcastId>` (v0.3.5)**: live delivery **funnel** for a
|
|
231
|
+
broadcast by its id — `accepted` → `delivered` → `read`, plus `pending` and
|
|
232
|
+
`failed`, with percentages, from `helpdesk_messages`. Built for the `--python`
|
|
233
|
+
fire-and-forget engine (which returns a `broadcastId` but has no CLI status
|
|
234
|
+
log), but works for any broadcast id (dashboard sends included).
|
|
235
|
+
**`--failures` (v0.3.7)** additionally lists each failed recipient + the Meta
|
|
236
|
+
error reason (`error_code` / `error_title`) with a by-reason rollup — makes a
|
|
237
|
+
fire-and-forget send fully auditable.
|
|
238
|
+
- **Delivery counting fixed 3 Aug 2026.** It previously counted
|
|
239
|
+
`message_status='delivered'`, a value present on **6 rows in the whole
|
|
240
|
+
table**, so *delivered always displayed 0* (the reached TOTAL was right; the
|
|
241
|
+
breakdown was not). Delivery now comes from the boolean receipt columns
|
|
242
|
+
(`delivered_receipt_received` / `read_receipt_received`) that the webhooks
|
|
243
|
+
flip — the same signal as the inbox ticks. **Booleans, not the
|
|
244
|
+
`delivered_at`/`read_at` timestamps**: those were added recently and are only
|
|
245
|
+
partially backfilled (2025: 323,325 delivered by boolean, **0** by
|
|
246
|
+
timestamp), and no row ever carries a timestamp without the boolean.
|
|
247
|
+
- The stages are **cumulative, not disjoint** (`accepted ⊇ delivered ⊇ read`) —
|
|
248
|
+
don't add them up. `delivered` counts read-without-a-delivered-receipt too
|
|
249
|
+
(~65k such rows exist: WhatsApp can skip straight to the read receipt).
|
|
250
|
+
- **`read` is a FLOOR, never exact** — recipients can disable read receipts in
|
|
251
|
+
WhatsApp. The JSON carries a `read_caveat` string saying so.
|
|
237
252
|
- **`retry <org> <broadcastId>` (v0.3.7)**: re-send a broadcast to **only its
|
|
238
253
|
failed recipients** — reconstructs the send from the `broadcasts` row (template +
|
|
239
254
|
params + header) and re-fires via the python engine, creating a NEW broadcast.
|
|
@@ -423,9 +438,10 @@ flowiq kw push <slug> --prune # ALSO deletes DB keywords missing from
|
|
|
423
438
|
collapses to 1 at runtime — rejected).
|
|
424
439
|
- `field:"attributes"` actions are warned (full jsonb replace; constant
|
|
425
440
|
values only) but applied — this CLI is their only safe editing surface.
|
|
426
|
-
- **Action types accepted** (all
|
|
441
|
+
- **Action types accepted** (all nine the runtime implements):
|
|
427
442
|
`send_message` · `update_contact_field` · `add_contact_tag` ·
|
|
428
|
-
`remove_contact_tag` · `set_agent` · `delay` · `combined
|
|
443
|
+
`remove_contact_tag` · `set_agent` · `delay` · `combined` ·
|
|
444
|
+
`update_ticket_status` · `renotify_ticket`.
|
|
429
445
|
Per-type rules: tag actions need a non-empty `tags[]` (or a single `tag`
|
|
430
446
|
string) or the runtime writes no tag at all; `set_agent.agent_id` must be
|
|
431
447
|
an agent UUID, or `null`/`""` to CLEAR the contact's binding (warned, since
|
|
@@ -436,6 +452,18 @@ flowiq kw push <slug> --prune # ALSO deletes DB keywords missing from
|
|
|
436
452
|
*(Before v0.3.9 only the first two + `combined` were accepted, so a
|
|
437
453
|
zero-edit pull→push failed for any org using tag / set_agent / delay
|
|
438
454
|
actions.)*
|
|
455
|
+
- **Ticket actions** (added 30 Jul 2026, for escalation follow-up buttons):
|
|
456
|
+
`update_ticket_status` changes the CONTACT's open/in_progress tickets —
|
|
457
|
+
`{ "type": "update_ticket_status", "status": "resolved", "scope": "all_open" }`.
|
|
458
|
+
`status` ∈ `open`/`in_progress`/`resolved`/`closed` (default `resolved`);
|
|
459
|
+
`scope` ∈ `all_open` (default) / `latest_open`. Appends the same
|
|
460
|
+
`data.status_history[]` audit entries as the agent's `manage_tickets` tool
|
|
461
|
+
(`by:"keyword"`). Canonical use: the follow-up template's **"Query solved"**
|
|
462
|
+
button resolving the escalation ticket. `renotify_ticket` re-fires the
|
|
463
|
+
human-needed team notifications for the contact's latest ticket via
|
|
464
|
+
ticket-tool `mode:"renotify"` (60s server-side rate limit; a resolved/closed
|
|
465
|
+
ticket is reopened first) — canonical use: the **"I still need help"**
|
|
466
|
+
button. Both send the action's `text` (if any) after the ticket work.
|
|
439
467
|
- `action_config.link_preview: false` disables WhatsApp's link-preview card
|
|
440
468
|
on that action's text send (absent/`true` = preview on, the default).
|
|
441
469
|
Passed through verbatim; also toggleable per action in the dashboard.
|
|
@@ -445,7 +473,7 @@ flowiq kw push <slug> --prune # ALSO deletes DB keywords missing from
|
|
|
445
473
|
```json
|
|
446
474
|
{ "type": "send_message", "when": { "field": "email", "op": "is_not_empty" } }
|
|
447
475
|
```
|
|
448
|
-
`field` ∈ `email` / `phone_number` / `full_name`
|
|
476
|
+
`field` ∈ `email` / `phone_number` / `full_name` (allowlist; `city` was removed 03 Aug 2026 — contacts has no such column, so a city condition could never evaluate).
|
|
449
477
|
`op` ∈ `is_empty` / `is_not_empty` / `equals` / `contains` — the last two
|
|
450
478
|
need a `value` and are case-insensitive. Rejected on push otherwise
|
|
451
479
|
(V-21/V-22), because the runtime SKIPS an action it can't evaluate.
|
|
@@ -739,7 +767,7 @@ flowiq agent config <organization_id> --test-contact-number 27000000001 --test-c
|
|
|
739
767
|
Settable: `settings.use_settings_prompt`, `settings.model`, agent `--rename`,
|
|
740
768
|
the tool-flag columns (`woo_order_build`, `woo_tip_field`, `woo_order_note_field`,
|
|
741
769
|
`view_cart_tool`, `restock_tool`, `block_tool_status`, `postal_code_tool_status`,
|
|
742
|
-
`shopify_products_web_chat`), `discount.enabled`, and the `flowiq test` contact
|
|
770
|
+
`shopify_products_web_chat`, `ticket_tool_status`), `discount.enabled`, and the `flowiq test` contact
|
|
743
771
|
(`settings.test_contact_number` / `settings.test_contact_name`). Anything else is
|
|
744
772
|
rejected; every change is reported before → after.
|
|
745
773
|
|
package/TEAM-GUIDE.md
CHANGED
|
@@ -79,6 +79,7 @@ When you see it, run `npm i -g @flowapt/flowiq-cli` — a stale version also mea
|
|
|
79
79
|
| Turn one custom tool on/off | `flowiq ct enable\|disable <org_id> <tool_name>` |
|
|
80
80
|
| Edit keyword auto-replies (incl. competition entry keywords, add/remove-tag, set-agent and delay actions) | `flowiq kw pull <org_id>` → edit → `flowiq kw push <slug> --dry-run` → `flowiq kw push <slug>` |
|
|
81
81
|
| Send a **different auto-reply depending on the contact** (e.g. "we already have your email" vs "send us your email") | add `"when": {"field":"email","op":"is_not_empty"}` to one action and `is_empty` to the other — see *Keywords* in `flowiq guide --reference`. CLI-only, no dashboard field yet |
|
|
82
|
+
| Make a follow-up button **close the customer's ticket** ("Query solved") or **re-alert the team** ("I still need help") | keyword action `{"type":"update_ticket_status","status":"resolved"}` / `{"type":"renotify_ticket"}` — see *Keywords* in `flowiq guide --reference`. CLI-only, no dashboard field yet |
|
|
82
83
|
| See an org's agents / create one | `flowiq agent list <org_id>` / `flowiq agent create <org_id> --name "…"` |
|
|
83
84
|
| Change agent model / tool flags | `flowiq agent config <org_id> --model … --tool view_cart_tool=true` |
|
|
84
85
|
| Talk to the live agent safely (no real WhatsApp ever sent) | `flowiq test send <org_id> "hi, do you sell X?"` |
|
|
@@ -95,7 +96,7 @@ When you see it, run `npm i -g @flowapt/flowiq-cli` — a stale version also mea
|
|
|
95
96
|
| Send a broadcast whose template has an IMAGE header | Same as above — the image is automatic (the template's own header image). Override with `--header-media <public-image-url>` if needed. |
|
|
96
97
|
| Send via the SAME engine as the dashboard's "Python" toggle | add `--python` to a `bc send --tag …` (fire-and-forget; python resolves the tag + sends + tracks; no CLI resume for this engine). **Any tag send over 10 recipients uses python automatically.** |
|
|
97
98
|
| Find a broadcast's id (don't have the `broadcastId`?) | `flowiq bc list-remote <org_id>` — the org's broadcasts newest-first with full ids (`--template <substr>` / `--since <date>` / `--limit <n>` to narrow) |
|
|
98
|
-
| Check how a broadcast is landing (read/
|
|
99
|
+
| Check how a broadcast is landing (accepted → delivered → read, plus failed/pending) | `flowiq bc status <org_id> <broadcastId>` (from the send output, or `bc list-remote`) |
|
|
99
100
|
| See WHO failed on a broadcast + why (Meta error) | `flowiq bc status <org_id> <broadcastId> --failures` |
|
|
100
101
|
| Re-send a broadcast to only the ones that failed | `flowiq bc retry <org_id> <broadcastId>` (dry-run) → `… --commit` |
|
|
101
102
|
| **Get an OLD version of a prompt back** | `flowiq prompts history <org_id>` (pick the version) → `flowiq prompts restore <org_id> <audit_id>` (dry-run) → `… --commit` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowapt/flowiq-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Command-line tool for FlowIQ staff: round-trip agent prompts, questionnaires, fine-tuning, pin-board tasks, webhooks, templates, agent-updates, chat exports, and live agent testing without ever touching service-role credentials.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -998,15 +998,24 @@ export async function status(orgId, broadcastId, opts = {}) {
|
|
|
998
998
|
catch (e) { console.error(`Status failed: ${e.message}`); if (e.body?.error) console.error(` ${e.body.error}`); process.exit(1); }
|
|
999
999
|
if (opts.json) { console.log(JSON.stringify(resp, null, 2)); return; }
|
|
1000
1000
|
const b = resp.broadcast, d = resp.delivery;
|
|
1001
|
-
|
|
1001
|
+
// Cumulative funnel (v0.4.0+). Falls back to the old disjoint fields when
|
|
1002
|
+
// talking to a server that predates the 3 Aug 2026 fix.
|
|
1003
|
+
const accepted = d.accepted ?? (d.read + d.delivered + d.sent);
|
|
1004
|
+
const delivered = d.delivered_total ?? d.delivered;
|
|
1005
|
+
const read = d.read_total ?? d.read;
|
|
1006
|
+
const pending = d.pending ?? d.other ?? 0;
|
|
1007
|
+
const pctOf = (n) => (accepted > 0 ? `${((n / accepted) * 100).toFixed(1)}%` : "–");
|
|
1002
1008
|
console.log(`Broadcast ${b.id} — ${resp.organization_name}`);
|
|
1003
1009
|
console.log(` template: ${b.template_name}${b.broadcast_name ? ` (${b.broadcast_name})` : ""}${b.media_url ? " · media header" : ""}`);
|
|
1004
1010
|
console.log(` status: ${b.status ?? "?"} · created ${b.created_at}`);
|
|
1005
1011
|
console.log(` recipients: ${b.total_recipients ?? "?"} · ${d.linked} message(s) linked`);
|
|
1006
|
-
console.log(` ✅
|
|
1007
|
-
console.log(`
|
|
1012
|
+
console.log(` ✅ accepted ${accepted}`);
|
|
1013
|
+
console.log(` 📬 delivered ${delivered} (${pctOf(delivered)} of accepted)`);
|
|
1014
|
+
console.log(` 👀 read ${read} (${pctOf(read)}) — floor only, recipients can disable read receipts`);
|
|
1015
|
+
console.log(` ⏳ pending ${pending} (accepted, no delivery receipt yet)`);
|
|
1016
|
+
console.log(` ❌ failed ${d.failed}${d.failed_pct != null ? ` (${d.failed_pct}% of linked)` : ""}`);
|
|
1008
1017
|
if (b.total_recipients) {
|
|
1009
|
-
console.log(` ${((
|
|
1018
|
+
console.log(` ${((accepted / b.total_recipients) * 100).toFixed(1)}% of ${b.total_recipients} recipients accepted${d.linked < b.total_recipients ? " (still sending?)" : ""}`);
|
|
1010
1019
|
}
|
|
1011
1020
|
if (opts.failures && resp.failures) {
|
|
1012
1021
|
console.log("");
|