@flowapt/flowiq-cli 0.9.6 → 0.9.8
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 +152 -10
- package/TEAM-GUIDE.md +28 -9
- package/package.json +1 -1
- package/src/agent-config.test.mjs +39 -0
- package/src/batch-a.test.mjs +36 -0
- package/src/commands/agent-config.js +141 -9
- package/src/commands/agent-test.js +23 -9
- package/src/commands/agent-updates.js +4 -3
- package/src/commands/audit.js +8 -1
- package/src/commands/broadcast.js +18 -2
- package/src/commands/doctor.js +13 -4
- package/src/commands/gaps.js +346 -0
- package/src/commands/keywords.js +43 -2
- package/src/commands/links.js +8 -0
- package/src/commands/prompts.js +25 -3
- package/src/commands/store-api.js +53 -6
- package/src/commands/templates.js +48 -5
- package/src/gaps.test.mjs +48 -0
- package/src/http.js +34 -8
- package/src/index.js +107 -9
package/README.md
CHANGED
|
@@ -77,9 +77,13 @@ Round-trips an org's active WhatsApp agent's system prompt
|
|
|
77
77
|
flowiq prompts pull <organization_id>
|
|
78
78
|
# edit ./.flowiq/prompts/<slug>.json — modify section titles/content
|
|
79
79
|
flowiq prompts push <slug>
|
|
80
|
+
flowiq prompts pull <organization_id> --out /tmp/check.json # a scratch copy; the working file is untouched (0.9.8)
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
Notes:
|
|
84
|
+
- **A new section needs no id (0.9.8).** Add `{ "title": …, "content": … }` and push:
|
|
85
|
+
the CLI writes a fresh uuid into the local file first, says which sections got
|
|
86
|
+
one, then pushes, so the next pull keeps the same id.
|
|
83
87
|
- `system_prompt` is regenerated server-side from `prompt_sections` on
|
|
84
88
|
push using the `## SECTION: <title>` format.
|
|
85
89
|
- Sections support optional `hidden: boolean` + `channels: string[]` (`web`, `whatsapp`, `messenger`, `instagram`, `email`; empty = every channel)
|
|
@@ -348,12 +352,17 @@ flowiq bc route <org_id> <broadcastId> --clear --commit # switc
|
|
|
348
352
|
the **full broadcastId** per row + template, status, recipient count and SAST
|
|
349
353
|
created time — the discovery step `status` / `retry` need (previously the id
|
|
350
354
|
existed only in the send output or the DB). `--limit <n>` (default 25, max 200),
|
|
351
|
-
`--template <substr>` (case-insensitive contains), `--since <date>`,
|
|
352
|
-
|
|
355
|
+
`--template <substr>` (case-insensitive contains), `--since <date>`,
|
|
356
|
+
`--min-recipients <n>` (0.9.8: `2` hides one-recipient traffic like popup welcomes
|
|
357
|
+
and cart reminders, which otherwise fill the cap and hide campaign sends), `--json`.
|
|
358
|
+
When more rows match than are shown it prints `N of M shown` and says so. Read-only. NOTE: plain `bc list` (no org) still lists your **local campaign
|
|
353
359
|
files** — the remote verb is named after `pinboard list-remote`.
|
|
354
360
|
- **`status <org> <broadcastId>` (v0.3.5)**: live delivery **funnel** for a
|
|
355
361
|
broadcast by its id — `accepted` → `delivered` → `read`, plus `pending` and
|
|
356
|
-
`failed`, with percentages, from `helpdesk_messages`.
|
|
362
|
+
`failed`, with percentages, from `helpdesk_messages`. Since 0.9.8 it also prints
|
|
363
|
+
what the send said (`sent with:` header / body / button slot values) and, for a
|
|
364
|
+
button short code, the link's destination with its UTM tags and click count
|
|
365
|
+
(`payload` in `--json`). Built for the `--python`
|
|
357
366
|
fire-and-forget engine (which returns a `broadcastId` but has no CLI status
|
|
358
367
|
log), but works for any broadcast id (dashboard sends included).
|
|
359
368
|
**`--failures` (v0.3.7)** additionally lists each failed recipient + the Meta
|
|
@@ -654,6 +663,14 @@ flowiq doctor --json # same, machine-readable; exits 1 when something needs fi
|
|
|
654
663
|
✓ server contract 0.6.8 or newer · build 1f0fd48c
|
|
655
664
|
```
|
|
656
665
|
|
|
666
|
+
**A Vercel security checkpoint is named as one (0.9.8).** When Vercel's bot
|
|
667
|
+
protection challenges this network, every command (and `doctor`) now says so:
|
|
668
|
+
"Vercel is challenging this network… not your key". Wait a few minutes or switch
|
|
669
|
+
network; do not re-login and do not narrow the request. Only a JSON 401/403 from
|
|
670
|
+
the API means the key itself was refused. A timeout on a WRITE now says the server
|
|
671
|
+
may have finished it (check before retrying); `templates create` names the exact
|
|
672
|
+
`templates status --live` command to check with.
|
|
673
|
+
|
|
657
674
|
**Three ways this install tells you it is stale**, so nobody — person or AI
|
|
658
675
|
assistant — has to remember to check:
|
|
659
676
|
|
|
@@ -745,6 +762,12 @@ Spring Promotion is **`9Sep_SpringPromotion`**.
|
|
|
745
762
|
|
|
746
763
|
- The resolved tag is **printed before anything is minted**, and `shorten` is
|
|
747
764
|
dry-run by default, so you always see it first.
|
|
765
|
+
- **A running campaign never forks (0.9.8).** Before minting, the org's tags from
|
|
766
|
+
the last 120 days are checked: when one normalises to the same tag but is
|
|
767
|
+
spelled differently (`5Sept_BraaiDayOffer` running, you asked for
|
|
768
|
+
`5Sep_BraaiDayOffer`), the dry run prints `⚠ CAMPAIGN FORK` and `--commit` is
|
|
769
|
+
refused. Reuse the running tag with `--campaign 5Sept_BraaiDayOffer --raw-campaign`,
|
|
770
|
+
or pass `--new-campaign` when it really is a separate campaign.
|
|
748
771
|
- `--date` takes `9Sep`, `2026-09-24` or `24/9/2026` — use it whenever the send
|
|
749
772
|
is not today.
|
|
750
773
|
- `--content` is the **per-link** tag (which button, which product), so it gets
|
|
@@ -797,6 +820,17 @@ flowiq kw push <slug> # id-keyed upsert (never touches keywor
|
|
|
797
820
|
flowiq kw push <slug> --prune # ALSO deletes DB keywords missing from the file (dry-run first!)
|
|
798
821
|
```
|
|
799
822
|
|
|
823
|
+
- **Runtime order and collisions (0.9.8).** `kw pull` lists ACTIVE rows in the
|
|
824
|
+
order the runtime tries them (by `type`, first match answers), then the inactive
|
|
825
|
+
ones, and prints `⚠ TRIGGER COLLISION` when two active rows match the same
|
|
826
|
+
message: which row wins, and whether `allowed_tags` lets the other one answer
|
|
827
|
+
some contacts. `kw push` warns the same way (a dry run predicts the order).
|
|
828
|
+
- **Schedule labels.** The keyword-scheduler cron switches `active` on at
|
|
829
|
+
`start_date` and off at `end_date` (every 30 min); the runtime reads `active`
|
|
830
|
+
only. So `kw pull` shows `starts … SAST` on a scheduled inactive row, and a loud
|
|
831
|
+
`⚠ LIVE BEFORE ITS START` on an active row whose start is still ahead (it is
|
|
832
|
+
answering now), or `⚠ PAST ITS END` on one not yet switched off.
|
|
833
|
+
|
|
800
834
|
- **Identity = the `id`s.** Keep an id to update that row; drop the id to
|
|
801
835
|
create a new one; remove an *action* from a kept keyword to delete it.
|
|
802
836
|
A file id that doesn't exist for the org aborts (stale file → re-pull).
|
|
@@ -915,7 +949,11 @@ flowiq audit show <audit_id> --out entry.json # dump the whole entry
|
|
|
915
949
|
```
|
|
916
950
|
|
|
917
951
|
Filters: `--endpoint --action --status --agent --target --user --since --until
|
|
918
|
-
--limit` (default 50, max 200) `--json`.
|
|
952
|
+
--before --limit` (default 50, max 200) `--json`. `--since` / `--until` take a date
|
|
953
|
+
(SAST day start), an ISO time, or a window like `2d` / `12h` / `2w` (0.9.8). A
|
|
954
|
+
`--limit` above 200 is clamped (it used to fall back to 50 silently); when more
|
|
955
|
+
entries match than are shown, the header reads `200 of 1,044 matching entries`
|
|
956
|
+
and the footer prints the `--before <created_at>` that pages further back.
|
|
919
957
|
|
|
920
958
|
**What gets logged.** Every command that CHANGES something writes a row —
|
|
921
959
|
including two that were missed until 4 Aug 2026 and are now covered:
|
|
@@ -1273,10 +1311,13 @@ flowiq report deck pull <org_id> 2026-08 # → ./.flowiq/repor
|
|
|
1273
1311
|
predates the current build logic, unless it is approved, sent or held).
|
|
1274
1312
|
- Send fees are estimated at the category each template had on its send day. A send whose category
|
|
1275
1313
|
cannot be known shows no fee and is left out of the broadcast return (`totals.unpriced_sends`).
|
|
1276
|
-
- `inputs.json` shape: `{ "changes": { "<update_id>": { "hidden": false, "tag": "client_raised|flowapt_shipped", "title": "…", "description": "…" } }, "changes_reviewed": true, "action_points": { "<title>": "done|in_progress|waiting_on_you|ongoing|dropped" }, "waiting_on_you": [{ "title": "…", "unlocks": "…" }], "recipients_confirmed": true }`.
|
|
1314
|
+
- `inputs.json` shape: `{ "changes": { "<update_id>": { "hidden": false, "tag": "client_raised|flowapt_shipped", "title": "…", "description": "…" } }, "changes_reviewed": true, "action_points": { "<title>": "done|in_progress|waiting_on_you|ongoing|dropped" }, "waiting_on_you": [{ "title": "…", "unlocks": "…" }], "recipients_confirmed": true, "hidden": { "tile:tail": true, "pill:new_contacts": true } }`.
|
|
1315
|
+
`hidden` (29 Sep 2026) takes any tile, pill, card or block off the slides and the PDF for that month only: the keys are what the deck page's rail lists (`tile:…`, `pill:<metric>`, `card:…`, `block:…`, `box:…`, `strip:…`); a metric's pill hides everywhere that metric shows. The copy is not rewritten when something is hidden.
|
|
1277
1316
|
Free text (milestone, the four plan fields) is edited in the app; it is stored as overrides that survive `narrate`.
|
|
1278
1317
|
- `send --client` needs an approved deck AND `report.email.recipients` in the org's reporting config (Control center → Report delivery); it marks the deck `sent`. `--test-to` never changes status. Fees on the slides are the org's actual Meta billing when the token can read it, otherwise the rate-card estimate, and the footnote says which.
|
|
1279
1318
|
- Non-store orgs need `config.deck.outcome` (`source: handover | ticket_status | tag | keyword`, labels) — the revenue slides become outcome slides. Every verb except `status` and `pull` is audited.
|
|
1319
|
+
- A month with a Customer Insights report gets the "What your customers asked" slide after slide 7 (29 Sep 2026): `build` takes the one `org_insights` export-insights row whose window covers the most of the month (top questions, trending topics, complaints or product mentions, sentiment, each as "N customers"), stores it under `deck.insights`, and the copy carries `insights_title` + `insights_read`. `status --json` shows the window under `insights` (null = no report, no slide). The timing slide's next-step card is now written by the narrator (`next_step_title` / `next_step_body`) against next month's South African retail calendar (public holidays, Mother's / Father's Day, Black Friday, payday, month-end) and edited in place like every other block.
|
|
1320
|
+
- Cart funnel and buckets (29 Sep 2026): "messaged" counts reminders WhatsApp reports delivered (the old send-request count rides along as `messaged_requested`); a Woo cart reminder now matches its order, so Woo recoveries are no longer zero; our own cart link is a recovery, another tool's abandoned-cart email is not; on Woo an agent-built order is a conversation, not a campaign; chat-to-order counts buyers who chatted, any bucket. `status` refuses approval when the agent replied to nobody in the month (no live channel).
|
|
1280
1321
|
- Store orgs get slide 8, "Store health": cart abandonment (online checkouts started, bought, abandoned), first-time vs returning buyers and opt-in growth, each with a twelve-month strip and measured on the online store checkout only (till and other channels are named in the footer). `status --json` carries the headline figures under `store`; `config.deck.channel_labels {"<source_name>": "Label"}` renames a sales channel on the slide.
|
|
1281
1322
|
|
|
1282
1323
|
### Insights — `flowiq insights status|enable|disable|run` (v0.7.0)
|
|
@@ -1384,7 +1425,8 @@ create call, including the ones that failed and why.
|
|
|
1384
1425
|
|
|
1385
1426
|
```bash
|
|
1386
1427
|
flowiq templates pull <organization_id> # → ./.flowiq/templates/<slug>.json (Meta-side truth)
|
|
1387
|
-
flowiq templates status <organization_id> --name booking #
|
|
1428
|
+
flowiq templates status <organization_id> --name booking # FlowIQ's record (webhook-kept status)
|
|
1429
|
+
flowiq templates status <organization_id> --live --name booking --watch # straight from Meta, re-checked every 15 s until decided (0.9.8)
|
|
1388
1430
|
flowiq templates show <organization_id> heritage_day_v2 # render ONE row, drafts included
|
|
1389
1431
|
flowiq templates create <organization_id> --request-file req.json
|
|
1390
1432
|
flowiq templates attempts <organization_id> --failed # why a submit was refused (v0.8.1)
|
|
@@ -1537,6 +1579,14 @@ Looking up stock for "1 litre Oil Pourer" — the calls being made:
|
|
|
1537
1579
|
`stock` prints **"not tracked"** where Shopify returns `available: null` — that
|
|
1538
1580
|
means no inventory record exists at that location, which is not the same as zero.
|
|
1539
1581
|
|
|
1582
|
+
**No exact title? It asks Shopify's product search (0.9.8).** The first call is an
|
|
1583
|
+
exact `products.json?title=`; when that finds nothing, the recipe runs Shopify's
|
|
1584
|
+
own product search over the WHOLE catalogue (it used to scan only the first 250
|
|
1585
|
+
products and then say "Nothing matched" on a 3,594-product store). Titles holding
|
|
1586
|
+
every word you typed are kept; if none do, Shopify's closest matches are shown and
|
|
1587
|
+
labelled as such. Search syntax passes through: `"sku:5702018068984"`,
|
|
1588
|
+
`"handle:up-scaled-darth-vader"`. Per-location levels cover the first 50 variants.
|
|
1589
|
+
|
|
1540
1590
|
#### Counting things — read `precision` before you trust a number
|
|
1541
1591
|
|
|
1542
1592
|
Shopify's `*Count` fields return `{count, precision}`. **`precision: "AT_LEAST"`
|
|
@@ -1709,6 +1759,18 @@ flowiq agent config <organization_id> --model gpt-6-luna --reasoning-effort high
|
|
|
1709
1759
|
flowiq agent config <organization_id> --model claude-sonnet-5 --reasoning-effort medium # Claude (needs the org's Claude key; warns if missing)
|
|
1710
1760
|
flowiq agent config <organization_id> --disable-base-tool get_product_info
|
|
1711
1761
|
flowiq agent config <organization_id> --enable-base-tool get_product_info
|
|
1762
|
+
# 0.9.8: the fields that used to need SQL
|
|
1763
|
+
flowiq agent config <org> --tool-instruction send_whatsapp_message=@rules.txt # appended to that tool's description ("tool=" removes)
|
|
1764
|
+
flowiq agent config <org> --shorten-links web=on --shorten-links whatsapp=default
|
|
1765
|
+
flowiq agent config <org> --email-from-name "Litehouse Support" --email-signature-name "Laia (AI)" --email-signature-title "Customer care"
|
|
1766
|
+
flowiq agent config <org> --email-request-to a@shop.co.za,b@shop.co.za --tool email_request_tool=true
|
|
1767
|
+
flowiq agent config <org> --send-success-rule "image=Stay silent after your last image." --send-success-rule "document="
|
|
1768
|
+
flowiq agent config <org> --after-escalation "Tell the customer the team replies here; never repeat what you already sent."
|
|
1769
|
+
flowiq agent config <org> --knowledge enabled=true,match_count=8,min_similarity=0.3
|
|
1770
|
+
flowiq agent config <org> --human-notify @human-notify.json # merged into human_notify; null deletes a key
|
|
1771
|
+
flowiq agent config <org> --order-notify '{"enabled":false}'
|
|
1772
|
+
flowiq agent config <org> --tool silent_option=true
|
|
1773
|
+
flowiq agent config <org> --full # show mode without truncating long values (also --json)
|
|
1712
1774
|
```
|
|
1713
1775
|
|
|
1714
1776
|
Settable: `settings.use_settings_prompt`, `settings.model`,
|
|
@@ -1717,10 +1779,26 @@ pairs with reasoning models like `gpt-5.6-luna`; `max` is GPT-6 and Claude only)
|
|
|
1717
1779
|
the tool-flag columns (`woo_order_build`, `woo_tip_field`, `woo_order_note_field`,
|
|
1718
1780
|
`view_cart_tool`, `restock_tool`, `block_tool_status`, `postal_code_tool_status`,
|
|
1719
1781
|
`shopify_products_web_chat`, `ticket_tool_status`, `product_lookup`,
|
|
1720
|
-
`collapse_product_variants`, `email_request_tool`),
|
|
1782
|
+
`collapse_product_variants`, `email_request_tool`, `silent_option`),
|
|
1721
1783
|
`discount.enabled`, and the `flowiq test` contact
|
|
1722
|
-
(`settings.test_contact_number` / `settings.test_contact_name`).
|
|
1723
|
-
|
|
1784
|
+
(`settings.test_contact_number` / `settings.test_contact_name`). Since 0.9.8 also:
|
|
1785
|
+
|
|
1786
|
+
| Flag | Stored in | Runtime reader |
|
|
1787
|
+
|---|---|---|
|
|
1788
|
+
| `--tool-instruction <tool>=<text\|@file>` (repeatable; `tool=` removes) | `settings.tool_instructions` | appended to that tool's description (`tool-instructions.ts`); a name with no such tool is skipped and logged |
|
|
1789
|
+
| `--shorten-links <channel>=on\|off\|default` (repeatable) | `disable_url_shortening` | channels whatsapp / web / messenger / instagram / tiktok / email / call; `default` = on for WhatsApp only |
|
|
1790
|
+
| `--email-from-name` / `--email-signature-name` / `--email-signature-title` (`""` clears) | `settings.email_*` | the From name and signature on the agent's email replies |
|
|
1791
|
+
| `--email-request-to a,b` (`""` clears) / `--email-request-subject-prefix` | `settings.email_request` | `email_request_to_team`; warns while `email_request_tool` is off |
|
|
1792
|
+
| `--send-success-rule <when>=<text\|@file>` (repeatable; `when=` removes) / `--send-success <bool>` | `send_message_success_instructions` | one rule per `when` (media / image / video / document / audio / text / always), appended to the send tool's "sent" reply |
|
|
1793
|
+
| `--after-escalation <text\|@file>` (`""` clears) | `human_notify.success_instructions` | the NEXT STEP appended to the handover tool's reply |
|
|
1794
|
+
| `--knowledge enabled=…,match_count=…,min_similarity=…` (`default` clears a key) | `knowledge_config` | `search_knowledge` (match_count 1-20, min_similarity 0-1) |
|
|
1795
|
+
| `--human-notify` / `--order-notify <json\|@file.json>` | `human_notify` / `order_notify` | top-level merge, `null` deletes a key; unknown keys and wrong types are refused |
|
|
1796
|
+
|
|
1797
|
+
Show mode (no write flags) prints every one of these, collapses long values to one
|
|
1798
|
+
line (`--full` prints them whole, `--json` gives the raw response) and lists what
|
|
1799
|
+
is settable. Anything else is rejected; every change is reported before → after.
|
|
1800
|
+
Not settable here: `additional_config` (use `flowiq knowledge`) and
|
|
1801
|
+
`additional_tools` (use `flowiq ct`).
|
|
1724
1802
|
|
|
1725
1803
|
`--disable-base-tool` / `--enable-base-tool` can be repeated. They manage only the
|
|
1726
1804
|
allowlisted discovery/commerce built-ins; opt-out, human handover and channel-send
|
|
@@ -1745,7 +1823,7 @@ addresses in `agents.settings.email_request.to`, from flowiq@flowapt.com with Re
|
|
|
1745
1823
|
set to the customer, and leaves an internal note on the thread. No ticket, no bot-off,
|
|
1746
1824
|
no staff WhatsApp — built for businesses that want requests in an inbox rather than a
|
|
1747
1825
|
human escalation (GIB Financial Services). The recipient list is set in the Tool
|
|
1748
|
-
Library (Agents → Tool Library → Email Request to Team)
|
|
1826
|
+
Library (Agents → Tool Library → Email Request to Team) or with `--email-request-to` (0.9.8).
|
|
1749
1827
|
|
|
1750
1828
|
**`--tool collapse_product_variants=true` (added 4 Aug 2026).** Not a tool toggle —
|
|
1751
1829
|
it changes what `get_product_info` RETURNS. OFF (the default) the result cap counts
|
|
@@ -1795,8 +1873,14 @@ flowiq au pull <organization_id> --before 20 --after 10 --no-files
|
|
|
1795
1873
|
|
|
1796
1874
|
flowiq au resolve <update_id> --note "We updated the agent so it no longer …"
|
|
1797
1875
|
flowiq au resolve <update_id> --status declined --internal "duplicate of …"
|
|
1876
|
+
flowiq au resolve <update_id> --status in_progress --note "Fixed; waiting to see it on a real order" # 0.9.8
|
|
1798
1877
|
```
|
|
1799
1878
|
|
|
1879
|
+
- **`--status in_progress`** puts a ticket in progress without closing it: no
|
|
1880
|
+
resolution stamp (an old one is cleared, as the app's reopen does), the note is
|
|
1881
|
+
optional and an earlier client note is kept when you pass none. Use it when the
|
|
1882
|
+
fix shipped but cannot be proven yet.
|
|
1883
|
+
|
|
1800
1884
|
- **`--note` is what the client reads** (rendered as `FlowIQ: <note>` in their
|
|
1801
1885
|
console) and is **required** when resolving; `--internal` is staff-only.
|
|
1802
1886
|
- The typical flow is `prompts push → flowiq test → au resolve` — resolve is
|
|
@@ -1859,6 +1943,54 @@ flowiq plans status <plan_id> --to sent --commit
|
|
|
1859
1943
|
- Editing a plan's copy, buttons or links is not in the CLI yet; do that in
|
|
1860
1944
|
the app.
|
|
1861
1945
|
|
|
1946
|
+
### CLI gaps + requests — `flowiq gaps report|list|show|hit|note|status` (alias `requests`) (v0.9.7)
|
|
1947
|
+
|
|
1948
|
+
Found something the CLI cannot do, does wrong, or should do? File it here, from
|
|
1949
|
+
the terminal, the moment you hit it. This is the shared list of what the CLI
|
|
1950
|
+
is missing: anyone with a staff key can add to it, and so can a Claude session
|
|
1951
|
+
driving the CLI for you (it is marked `claude` automatically).
|
|
1952
|
+
|
|
1953
|
+
```bash
|
|
1954
|
+
flowiq gaps report "agent config cannot set tool_instructions" --command "agent config" \
|
|
1955
|
+
--error "error: unknown option '--tool-instruction'" --workaround "SQL on agents.settings" \
|
|
1956
|
+
--detail "Needed to add a per-tool instruction for Clara's get_product_info" --org <uuid>
|
|
1957
|
+
flowiq gaps report "links shorten should take a readable --slug" --kind idea --command "links shorten"
|
|
1958
|
+
flowiq gaps report "…" --dry-run # only check whether it is already on the list
|
|
1959
|
+
flowiq gaps list # open requests, most-hit first
|
|
1960
|
+
flowiq gaps list --command "bc" --status all --search slug
|
|
1961
|
+
flowiq gaps list --mine
|
|
1962
|
+
flowiq gaps show 12 # detail, verbatim error, every hit / note / status change
|
|
1963
|
+
flowiq gaps hit 12 --note "same on Barkyn IT while copying the escalation config"
|
|
1964
|
+
flowiq gaps note 12 "also needs --agent"
|
|
1965
|
+
flowiq gaps status 12 --to done --version 0.9.8 --note "agent config --tool-instruction name=@file.txt" # maintainers
|
|
1966
|
+
flowiq gaps status 14 --to duplicate --of 12 # hits move to #12
|
|
1967
|
+
```
|
|
1968
|
+
|
|
1969
|
+
- **`report`** takes a one-line title plus whatever context you have:
|
|
1970
|
+
`--command` (the command it concerns, or the one you expected to exist),
|
|
1971
|
+
`--kind gap|bug|idea` (default gap), `--error` (paste it verbatim),
|
|
1972
|
+
`--workaround` (what you did instead), `--detail` / `--detail-file`, `--org`,
|
|
1973
|
+
`--priority low|normal|high` (high = it blocked the job).
|
|
1974
|
+
- **Duplicates are caught before they are filed.** A report that closely
|
|
1975
|
+
matches an OPEN request is refused and names it; add yourself to that one
|
|
1976
|
+
with `hit` instead, or pass `--new` if yours is genuinely different.
|
|
1977
|
+
`--dry-run` shows the similar requests and files nothing.
|
|
1978
|
+
- **`hit`** is "this bit me too": it adds your case and counts it. `list` is
|
|
1979
|
+
ordered by hits, so the requests that cost the team most get built first.
|
|
1980
|
+
`note` adds context without counting.
|
|
1981
|
+
- **`status`** (open / planned / in_progress / done / declined / duplicate) is
|
|
1982
|
+
for the CLI maintainers (Matt and Gidon). You can withdraw your OWN request
|
|
1983
|
+
with `--to declined` or `--to duplicate --of <n>`. `done` and `declined` need
|
|
1984
|
+
`--note`: closing a request **emails everyone who reported or hit it** (from
|
|
1985
|
+
FlowIQ (CLI), reply-to the person who closed it), with the version to update
|
|
1986
|
+
to. `--no-notify` closes quietly.
|
|
1987
|
+
- **A command or flag that does not exist** now ends its error with the exact
|
|
1988
|
+
`flowiq gaps report …` line to file it, `--command` and `--error` filled in.
|
|
1989
|
+
- **In the app:** the Changelog dialog has a **CLI requests** tab (super admins) showing the same list, filterable by status and kind, with each request's detail and history. It is read only; reporting and closing stay in the CLI.
|
|
1990
|
+
- Every report / hit / note / status change is audited
|
|
1991
|
+
(`flowiq audit --endpoint gaps`). A report or hit from anyone but Matt also
|
|
1992
|
+
emails him through the standing CLI-write alert.
|
|
1993
|
+
|
|
1862
1994
|
### Chat export — `flowiq export chats <organization_id> [--out <path>]`
|
|
1863
1995
|
|
|
1864
1996
|
Full chat history → TXT, byte-identical to the in-app "Export Settings TXT"
|
|
@@ -1903,6 +2035,16 @@ messages (`memory_cutoff`) AND sets aside the agent's remembered tool results
|
|
|
1903
2035
|
(`more_data.tool_usage_history`, which the agent reads as TOOL USAGE HISTORY) under
|
|
1904
2036
|
`more_data.tool_usage_history_cleared`, so a cleared test really starts fresh. Since 0.9.6;
|
|
1905
2037
|
before that earlier tool results and escalation reasons leaked into "cleared" tests.
|
|
2038
|
+
`test clear` prints the PREVIOUS `memory_cutoff` so a clear can be undone (0.9.8).
|
|
2039
|
+
|
|
2040
|
+
**Real-customer guard (0.9.8).** The configured test contact can be a real person
|
|
2041
|
+
(a client's reviewer, a dormant customer). When it had real (non-web) inbound in
|
|
2042
|
+
the last 6 h, `send`, `scenario`, `qa` and `clear` are refused with the latest
|
|
2043
|
+
message time; `--force` overrides. A test contact that has ever messaged on a real
|
|
2044
|
+
channel gets a warning on every run. Point the agent at a fake number with
|
|
2045
|
+
`flowiq agent config <org> --test-contact-number 27000000001`. A test contact the
|
|
2046
|
+
CLI creates is born with `allow_broadcast=false` and the `test-contact` tag, so it
|
|
2047
|
+
never lands on a broadcast list (stress contacts too).
|
|
1906
2048
|
|
|
1907
2049
|
### Guide — `flowiq guide`
|
|
1908
2050
|
|
package/TEAM-GUIDE.md
CHANGED
|
@@ -70,19 +70,25 @@ When you see it, run `npm i -g @flowapt/flowiq-cli` — a stale version also mea
|
|
|
70
70
|
goes back to the agent you pulled — never "whatever is active now".
|
|
71
71
|
5. Run the CLI from the repo root when you can — `./.flowiq/` is gitignored
|
|
72
72
|
there.
|
|
73
|
+
6. **Hit a wall? File it.** Whenever the CLI cannot do something and you (or
|
|
74
|
+
your Claude session) fell back to the dashboard, SQL or a script, file it:
|
|
75
|
+
`flowiq gaps report "<what you needed>" --command "<topic>" --error "<the
|
|
76
|
+
exact error>"`. Already on the list? `flowiq gaps hit <number> --note "…"`
|
|
77
|
+
instead. The most-hit requests get built first, and you are emailed when
|
|
78
|
+
yours ships. Tell your Claude session to do this too.
|
|
73
79
|
|
|
74
80
|
## Everyday tasks
|
|
75
81
|
|
|
76
82
|
| I want to… | Run |
|
|
77
83
|
|---|---|
|
|
78
|
-
| Edit a client agent's system prompt | `flowiq prompts pull <org_id>` → edit the JSON → `flowiq prompts push <slug>` |
|
|
84
|
+
| Edit a client agent's system prompt | `flowiq prompts pull <org_id>` → edit the JSON → `flowiq prompts push <slug>` (a new section needs no id; the push adds one) |
|
|
79
85
|
| Edit the questionnaire answers | `flowiq q pull <org_id>` → edit → `flowiq q push <slug>` |
|
|
80
86
|
| Edit fine-tuning instructions / Q&A | `flowiq ft pull <org_id>` → edit → `flowiq ft push <slug>` |
|
|
81
87
|
| Edit the text knowledge sources (get_more_answers playbooks) | `flowiq kn pull <org_id>` → edit `sources[]` → `flowiq kn push <slug>` |
|
|
82
88
|
| Edit the custom tools (API-call tools) | `flowiq ct pull <org_id>` → edit `tools[]` → `flowiq ct push <slug> --dry-run` → `flowiq ct push <slug>` |
|
|
83
89
|
| Turn one custom tool on/off | `flowiq ct enable\|disable <org_id> <tool_name>` |
|
|
84
90
|
| Give a custom tool a friendly name on the inbox tool-activity card | add `"display_name": "Checked loyalty points"` to that tool in `tools[]` → `flowiq ct push` (≤80 chars, never shown to the AI; empty = derived from the tool name) |
|
|
85
|
-
| 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
|
|
91
|
+
| 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>`. `kw pull` flags two active keywords answering the same word (and which one wins) and a keyword that is live before its start date |
|
|
86
92
|
| 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 |
|
|
87
93
|
| 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 |
|
|
88
94
|
| Make a keyword/button **hand the chat to a team or person** (assign in the inbox + email/WhatsApp them) | keyword action `{"type":"assign_chat","team_id":"…","assignee_user_id":"…","notify_member":true}` — see *Keywords* in `flowiq guide --reference`. Also in the dashboard (action type "Assign Chat") |
|
|
@@ -98,7 +104,7 @@ When you see it, run `npm i -g @flowapt/flowiq-cli` — a stale version also mea
|
|
|
98
104
|
| See one client's broadcast plans | `flowiq plans list <org_id>` (open plans) or `flowiq plans list <org_id> --status all --since 2026-09-01` |
|
|
99
105
|
| "What did clients submit today?" | `flowiq plans list --created-since 2026-09-22` (v0.9.1) — `--since` filters the SEND date, `--created-since` / `--touched-since` filter when the plan itself was created or last changed |
|
|
100
106
|
| Mark a broadcast plan approved, scheduled or sent | `flowiq plans status <plan_id> --to sent` (a dry run), then add `--commit`. Rejecting needs `--comment "..."`, which the client reads |
|
|
101
|
-
| What's the stock on a product, per branch? | `flowiq shopify stock <org_id> "olive oil"` — shows each location by NAME, and says "not tracked" rather than a confusing 0 |
|
|
107
|
+
| What's the stock on a product, per branch? | `flowiq shopify stock <org_id> "olive oil"` — shows each location by NAME, and says "not tracked" rather than a confusing 0. Part of the title is enough (it searches the whole catalogue); `"sku:…"` works too |
|
|
102
108
|
| Has this order shipped? What's the tracking? | `flowiq shopify order <org_id> '#14728'` — status, courier, tracking number + link |
|
|
103
109
|
| **Size a customer cohort** | Use `customers(first:250, query:…)` and paginate — **NOT `customersCount(query:…)`, which Shopify ignores the filter on** and answers 10,000 every time. Any count showing `precision: AT_LEAST` is a cap, not a total; the CLI warns you. |
|
|
104
110
|
| A big `--all` failed with a timeout | It's transient, not a limit — retry, or narrow it with `--max-pages` / a smaller `--q limit`. The message now says so. |
|
|
@@ -114,13 +120,22 @@ When you see it, run `npm i -g @flowapt/flowiq-cli` — a stale version also mea
|
|
|
114
120
|
| Remove a conflicting built-in tool from one agent | `flowiq agent config <org_id> --disable-base-tool get_product_info` (repeatable; safety/delivery tools cannot be disabled) |
|
|
115
121
|
| Agent says an in-stock product "isn't showing" | `flowiq agent config <org_id> --tool collapse_product_variants=true` — the search cap counts VARIANT rows until this is on |
|
|
116
122
|
| Agent can't quote ANY price / "I can't pull the live menu" | `flowiq agent config <org_id> --tool product_lookup=true` — name-based fuzzy lookup that works without embeddings or a live OpenAI key (semantic `get_product_info` needs both) |
|
|
117
|
-
| Client wants leads/requests emailed to their team instead of a human escalation | `flowiq agent config <org_id> --tool email_request_tool=true
|
|
123
|
+
| Client wants leads/requests emailed to their team instead of a human escalation | `flowiq agent config <org_id> --tool email_request_tool=true --email-request-to a@shop.co.za,b@shop.co.za` |
|
|
124
|
+
| Add extra rules to one tool (appended to its description) | `flowiq agent config <org_id> --tool-instruction send_whatsapp_message=@rules.txt` (`tool=` removes it) |
|
|
125
|
+
| Turn link shortening on/off per channel | `flowiq agent config <org_id> --shorten-links web=on` (`off`, or `default` = on for WhatsApp only) |
|
|
126
|
+
| Set the agent's email From name / signature | `flowiq agent config <org_id> --email-from-name "Shop Support" --email-signature-name "Laia (AI)" --email-signature-title "Customer care"` |
|
|
127
|
+
| Tell the agent what to do right after it sends an image / after a handover | `flowiq agent config <org_id> --send-success-rule "image=…"` / `--after-escalation "…"` |
|
|
128
|
+
| Knowledge search settings, human_notify / order_notify keys | `flowiq agent config <org_id> --knowledge enabled=true,match_count=8` · `--human-notify @file.json` · `--order-notify '{"enabled":false}'` (merged; `null` deletes a key) |
|
|
129
|
+
| See EVERYTHING an agent is configured with | `flowiq agent config <org_id>` (`--full` for long values, `--json` raw) |
|
|
118
130
|
| Talk to the live agent safely (no real WhatsApp ever sent) | `flowiq test send <org_id> "hi, do you sell X?"` |
|
|
119
|
-
| Start a test chat from scratch (agent forgets the earlier test messages AND its earlier tool results) | `flowiq test clear <org_id>` (or `flowiq test send <org_id> "…" --clear`) |
|
|
131
|
+
| Start a test chat from scratch (agent forgets the earlier test messages AND its earlier tool results) | `flowiq test clear <org_id>` (or `flowiq test send <org_id> "…" --clear`). It prints the previous `memory_cutoff`, and refuses when the test contact is a real customer who wrote in the last 6 h (`--force` overrides; better: use a fake test number) |
|
|
120
132
|
| **Tell the team what shipped** (the FlowIQ team update email) | `flowiq updates draft` → enrich the JSON (screenshots via `flowiq updates asset`, a *For the team* note per headline) → `flowiq updates preview <file> --open` → `flowiq updates send <file> --test` → `flowiq updates send <file> --yes` |
|
|
121
133
|
| Can I use `flowiq updates`? | Yes: `status`, `uncovered`, `draft`, `preview` and `send --test` (a copy to yourself) work for everyone. Only the real `send --yes` to the whole team is Matt's; anyone else gets a 403 there |
|
|
122
134
|
| Which changelog rows has nobody emailed the team about yet? | `flowiq updates status` / `flowiq updates uncovered` — anything left 20h+ goes out automatically at 08:30 SAST as a plain digest |
|
|
123
135
|
| Set which contact `flowiq test` uses (use a FAKE number!) | `flowiq agent config <org_id> --test-contact-number 27000000001 --test-contact-name "QA Bot"` |
|
|
136
|
+
| **The CLI can't do something I need** (or does it wrong, or should do more) | `flowiq gaps report "<command> cannot <thing>" --command "<command>" --error "<exact error>" --workaround "<what you did instead>"`. If it is already on the list you are told its number: `flowiq gaps hit <number> --note "…"` |
|
|
137
|
+
| See what the team has asked the CLI to do | `flowiq gaps list` (open, most-hit first) · `flowiq gaps list --mine` · `flowiq gaps show <number>` · in the app: Changelog → **CLI requests** |
|
|
138
|
+
| Close a request (Matt / Gidon) | `flowiq gaps status <number> --to done --version 0.9.8 --note "what shipped"` — emails everyone who reported or hit it |
|
|
124
139
|
> **Publishing the CLI (maintainers only):** publish from a clean clone, never
|
|
125
140
|
> from your working tree — `npm publish` packs whatever is on disk. A
|
|
126
141
|
> `prepublishOnly` check now stops you if `cli/` differs from `origin/main`.
|
|
@@ -156,7 +171,7 @@ Getting this right is what makes a campaign's clicks and revenue group together
|
|
|
156
171
|
in reporting — a tag typed a different way each time splits one campaign into
|
|
157
172
|
several.
|
|
158
173
|
|
|
159
|
-
| **Make short links for a campaign** (with UTM tracking) | `flowiq links shorten <org_id> --url "https://shop.co.za/product/x" --campaign "Spring Promotion"` (dry run) → `… --commit`. **Just type the campaign in plain words** — the CLI names it for you as `9Sep_SpringPromotion` and prints it before minting. `utm_source`/`utm_medium` are set for you |
|
|
174
|
+
| **Make short links for a campaign** (with UTM tracking) | `flowiq links shorten <org_id> --url "https://shop.co.za/product/x" --campaign "Spring Promotion"` (dry run) → `… --commit`. **Just type the campaign in plain words** — the CLI names it for you as `9Sep_SpringPromotion` and prints it before minting. `utm_source`/`utm_medium` are set for you. If the campaign already runs under another spelling (`5Sept_…` vs `5Sep_…`) it refuses and tells you to reuse the running tag (`--raw-campaign`) or confirm with `--new-campaign` |
|
|
160
175
|
| A campaign going out on a later date | Add `--date`: `--campaign "Heritage Day" --date 24/9/2026` → `24Sep_HeritageDay`. Without it you get today's date |
|
|
161
176
|
| A second link in the same send (e.g. a VIEW MORE button) | Same `--campaign`, different `--content`: `--content ViewMore`. Content is the per-link tag and gets no date |
|
|
162
177
|
| Short links for every URL in a message | `flowiq links shorten <org_id> --file ./message.txt --campaign "Spring Promotion" --commit` |
|
|
@@ -183,19 +198,21 @@ several.
|
|
|
183
198
|
| Send a broadcast whose link button should open a page on the store, tracked | Paste the real link: `flowiq bc send <org_id> --tag <batch-tag> --template <name> --button param1="https://shop.co.za/collections/spring?ref=ig"` (dry run shows `→ linklnk.io/<code>`, the campaign tag and anything replaced) → `… --commit` mints the code and sends. `--link-campaign "Spring Promotion"` names the tag (default: the template name); `--keep-link` sends your link untouched with no FlowIQ tags (clicks counted, the sale will not show as WhatsApp) |
|
|
184
199
|
| Send a template with TWO link buttons (e.g. one per product) and track clicks per batch | Mint two short links per batch with the same campaign and a different `--content` (e.g. `--content CycleHarmony_b01` and `--content NeuroShyft_b01`), then `flowiq bc send <org_id> --tag <batch-tag> --template <name> --button param1=<first-code> --button param2=<second-code>`. `param1` is the first link button on the template, `param2` the second. The dry run prints both final URLs and stops if either is missing. Tag mode only (v0.7.5). |
|
|
185
200
|
| 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.** |
|
|
186
|
-
| 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) |
|
|
187
|
-
| Check how a broadcast is landing (accepted → delivered → read, plus failed/pending) | `flowiq bc status <org_id> <broadcastId>`
|
|
201
|
+
| 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; `--min-recipients 2` hides welcomes and cart reminders) |
|
|
202
|
+
| Check how a broadcast is landing (accepted → delivered → read, plus failed/pending) and what it said | `flowiq bc status <org_id> <broadcastId>` — also prints the slot values, the button code and the link's destination (UTM tag) |
|
|
188
203
|
| See WHO failed on a broadcast + why (Meta error) | `flowiq bc status <org_id> <broadcastId> --failures` |
|
|
189
204
|
| Re-send a broadcast to only the ones that failed | `flowiq bc retry <org_id> <broadcastId>` (dry-run) → `… --commit` |
|
|
190
205
|
| Make EVERY reply to a broadcast land with a team or a person (client asks "all replies to the Spa team") | Add `--route-team "Spa"` (and/or `--route-member kim@client.com`) to the `bc send`. The first reply from each recipient within 72 hours (`--route-window 48h` to change) assigns the chat and notifies them, typed replies and button taps alike. The AI agent and keywords still answer as normal, so nothing is muted. The dry run shows `Replies → team Spa …`; a wrong team name stops the run and lists the org's teams. Works with `--csv` and `--at` too. |
|
|
191
206
|
| Add, check or remove that routing on a broadcast that already went out | `flowiq bc route <org_id> <broadcastId>` shows it and how many chats it has routed · `… --team "Spa" --commit` sets it · `… --clear --commit` removes it. Only replies from now on are affected. |
|
|
192
207
|
| **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` |
|
|
193
|
-
| **See who changed what, and when** | `flowiq audit <org_id>` — add `--endpoint prompts`, `--user <name>`, `--since
|
|
208
|
+
| **See who changed what, and when** | `flowiq audit <org_id>` — add `--endpoint prompts`, `--user <name>`, `--since 2d` (or a date) to narrow; `N of M` in the header means there is more (`--before` pages back) |
|
|
194
209
|
| See exactly what a change looked like (before → after) | `flowiq audit show <audit_id> --content` (or `--out entry.json`) |
|
|
195
210
|
| Know what the log does and doesn't keep | Anything that CHANGES something is logged — including `flowiq test` (it creates the test contact and clears conversations) and `flowiq auth refresh`. Reads and dry-runs are not. The log never stores the content itself: chats, store data and agent replies are recorded as "who read what", never copied. |
|
|
196
211
|
| Read a contact's chat | `flowiq m pull <contact_id>` then open the JSON. Ask for more than exists (`--count 100`) and you get the entire history — it says "that is ALL of them" when there is nothing older |
|
|
197
212
|
| Export an org's full chat history | `flowiq export chats <org_id>`. If it stops mid-way it says which page failed and nothing is written, so just re-run it (a 502/503/504 page retries on its own) |
|
|
198
213
|
| Check / create WhatsApp templates | `flowiq tpl pull <org_id>` / `flowiq tpl create <org_id> --request-file req.json` |
|
|
214
|
+
| Is my new template approved yet? | `flowiq tpl status <org_id> --live --name <name> --watch` — straight from Meta every 15 s until decided, with the rejection reason. If a create timed out, run this before retrying (it may already exist) |
|
|
215
|
+
| A command says "Vercel is challenging this network" | Not your key: wait a few minutes or switch network (hotspot). Don't log in again |
|
|
199
216
|
| **Read a template you have not submitted yet (a DRAFT)** | `flowiq tpl show <org_id> <template_name>` — the ONLY way to see a draft from the terminal (`pull` reads Meta, and a draft never gets there). Renders the message as the customer will read it, every carousel card, and a **Checks** list of the things Meta would bounce it for |
|
|
200
217
|
| Check a carousel before submitting it | `flowiq tpl show <org_id> <name>` and read **Checks**. It catches a card link using the wrong `{{n}}`, a missing greeting example, a bold/italic marker against a space (WhatsApp shows the literal `_`), wrong card counts and over-length text. It cannot know a card names a product you do not stock — read the card text against the shop yourself |
|
|
201
218
|
| **A template would not submit and you want to know why** | `flowiq tpl attempts <org_id> --failed` — the submission ledger. Every create call since 21 Sep 2026, from the dashboard or the CLI, with the reason a refused one was refused (e.g. `HEADER format is DOCUMENT but the media at file_url is video/mp4`). A failed submit writes no template row and never reaches Meta, so this is the ONLY place it shows |
|
|
@@ -205,6 +222,7 @@ several.
|
|
|
205
222
|
| See EVERY client's open change requests at once | `flowiq au all` (grouped by org; `--since 14d`, `--priority urgent,high`, `--search <text>`, `--status all`, `--summary`, `--json`) |
|
|
206
223
|
| See one client's pending change requests, with the chat around each | `flowiq au pull <org_id>` |
|
|
207
224
|
| Close a client's change request (after verifying the fix!) | `flowiq au resolve <update_id> --note "what changed"` — the client reads the note |
|
|
225
|
+
| Fix shipped but not provable yet | `flowiq au resolve <update_id> --status in_progress --note "…"` (stays open, no resolution stamp) |
|
|
208
226
|
| Check an org's platform + active agent | `flowiq org info <org_id>` |
|
|
209
227
|
| **See every Settings → Profile switch on an org** | `flowiq org flags show <org_id>` (credentials are never shown) |
|
|
210
228
|
| **Which orgs have a flag on / off** (insights, MCP member access, a debounce…) | `flowiq org flags list --key export_insights.enabled --off` · `--key mcp_member_access.enabled --on` |
|
|
@@ -221,6 +239,7 @@ several.
|
|
|
221
239
|
| Generate a client's monthly deck (metrics + copy) after the month has frozen | `flowiq report deck generate <org_id> 2026-08` — then review it at /reporting/deck |
|
|
222
240
|
| See whether a client deck can be approved, and what is still missing | `flowiq report deck status <org_id> 2026-08` |
|
|
223
241
|
| Send yourself a test copy of a client deck (PDF from flowiq@flowapt.com) | `flowiq report deck send <org_id> 2026-08 --test-to you@flowapt.com` — status untouched |
|
|
242
|
+
| Take a tile, pill or card off a client deck (and its PDF) for one month | On `/reporting/deck` open **Inputs** (the rail beside the slides) → Show or hide; or `flowiq report deck inputs <org_id> 2026-08 --file inputs.json` with `{ "hidden": { "tile:tail": true } }` |
|
|
224
243
|
| Approve a client deck / send it to the client | `flowiq report deck approve <org_id> 2026-08` then `… send … --client` (or let the 09:00 SAST schedule send it on the 5th) |
|
|
225
244
|
| Re-read this guide / the full command reference | `flowiq guide` / `flowiq guide --reference` |
|
|
226
245
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowapt/flowiq-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
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": {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// node --test src/agent-config.test.mjs
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { writeFileSync, mkdtempSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { splitPair, parseShorten, parseKnowledge, parseJsonObject, readValue } from "./commands/agent-config.js";
|
|
8
|
+
|
|
9
|
+
test("splitPair keeps '=' inside the value", () => {
|
|
10
|
+
assert.deepEqual(splitPair("send_whatsapp_message=use a=b style", "x"), ["send_whatsapp_message", "use a=b style"]);
|
|
11
|
+
assert.deepEqual(splitPair("document=", "x"), ["document", ""]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("parseShorten maps on/off/default", () => {
|
|
15
|
+
assert.equal(parseShorten("on", "x"), true);
|
|
16
|
+
assert.equal(parseShorten("OFF", "x"), false);
|
|
17
|
+
assert.equal(parseShorten("default", "x"), null);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("parseKnowledge types each pair and clears on default", () => {
|
|
21
|
+
assert.deepEqual(parseKnowledge("enabled=true, match_count=8,min_similarity=0.35"), { enabled: true, match_count: 8, min_similarity: 0.35 });
|
|
22
|
+
assert.deepEqual(parseKnowledge("min_similarity=default"), { min_similarity: null });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("readValue reads @file and drops one trailing newline", () => {
|
|
26
|
+
const dir = mkdtempSync(join(tmpdir(), "flowiq-ac-"));
|
|
27
|
+
const f = join(dir, "t.txt");
|
|
28
|
+
writeFileSync(f, "line one\nline two\n");
|
|
29
|
+
assert.equal(readValue(`@${f}`, "x"), "line one\nline two");
|
|
30
|
+
assert.equal(readValue("plain text", "x"), "plain text");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("parseJsonObject accepts inline JSON and @file", () => {
|
|
34
|
+
assert.deepEqual(parseJsonObject('{"enabled":false,"template_name":null}', "x"), { enabled: false, template_name: null });
|
|
35
|
+
const dir = mkdtempSync(join(tmpdir(), "flowiq-ac-"));
|
|
36
|
+
const f = join(dir, "hn.json");
|
|
37
|
+
writeFileSync(f, '{"whatsapp_ids":["27000000000"]}');
|
|
38
|
+
assert.deepEqual(parseJsonObject(`@${f}`, "x"), { whatsapp_ids: ["27000000000"] });
|
|
39
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// node --test src/batch-a.test.mjs
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { mintSectionIds } from "./commands/prompts.js";
|
|
5
|
+
import { scheduleLabel } from "./commands/keywords.js";
|
|
6
|
+
import { describeError, isVercelChallenge } from "./http.js";
|
|
7
|
+
|
|
8
|
+
test("mintSectionIds gives only id-less sections a uuid and names them", () => {
|
|
9
|
+
const secs = [{ id: "keep-me", title: "A", content: "" }, { title: "New one", content: "" }, { id: " ", title: "Blank", content: "" }];
|
|
10
|
+
const touched = mintSectionIds(secs);
|
|
11
|
+
assert.deepEqual(touched, ["New one", "Blank"]);
|
|
12
|
+
assert.equal(secs[0].id, "keep-me");
|
|
13
|
+
assert.match(secs[1].id, /^[0-9a-f-]{36}$/);
|
|
14
|
+
assert.notEqual(secs[1].id, secs[2].id);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("scheduleLabel flags an active row before its start and past its end", () => {
|
|
18
|
+
const now = Date.parse("2026-09-30T20:00:00Z");
|
|
19
|
+
assert.match(scheduleLabel({ active: true, start_date: "2026-09-30T22:00:00Z" }, now), /LIVE BEFORE ITS START/);
|
|
20
|
+
assert.match(scheduleLabel({ active: false, start_date: "2026-09-30T22:00:00Z" }, now), /starts 2026-10-01 00:00 SAST/);
|
|
21
|
+
assert.match(scheduleLabel({ active: true, end_date: "2026-09-29T10:00:00Z" }, now), /PAST ITS END/);
|
|
22
|
+
assert.equal(scheduleLabel({ active: true }, now), "");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("describeError: HTML 'timeout' text is not a timeout; a POST timeout says check first", () => {
|
|
26
|
+
assert.doesNotMatch(describeError({ _raw: "<html>setTimeout(…) timed out</html>" }, "GET", "hours", 403, { json: false }), /TIMEOUT/);
|
|
27
|
+
assert.match(describeError({ error: "x" }, "POST", "meta-templates", 504), /may still have finished the write/);
|
|
28
|
+
assert.match(describeError({ error: "x" }, "GET", "store-api", 504), /--max-pages/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("isVercelChallenge reads the header or the checkpoint page", () => {
|
|
32
|
+
const h = (v) => ({ get: (k) => (k === "x-vercel-mitigated" ? v : null) });
|
|
33
|
+
assert.equal(isVercelChallenge(403, h("challenge"), ""), true);
|
|
34
|
+
assert.equal(isVercelChallenge(403, h(null), "<title>Vercel Security Checkpoint</title>"), true);
|
|
35
|
+
assert.equal(isVercelChallenge(403, h(null), '{"error":"Forbidden"}'), false);
|
|
36
|
+
});
|