@flowapt/flowiq-cli 0.10.0 → 0.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.
- package/README.md +123 -2
- package/TEAM-GUIDE.md +23 -2
- package/package.json +1 -1
- package/src/batch-f.test.mjs +63 -0
- package/src/batch-g.test.mjs +35 -0
- package/src/commands/agent-test.js +44 -3
- package/src/commands/agents.js +31 -0
- package/src/commands/broadcast.js +19 -0
- package/src/commands/contacts.js +43 -2
- package/src/commands/links.js +63 -20
- package/src/commands/members.js +108 -0
- package/src/commands/messages.js +69 -3
- package/src/commands/notes.js +63 -0
- package/src/commands/org.js +77 -10
- package/src/commands/plans.js +50 -0
- package/src/commands/products.js +58 -0
- package/src/commands/prompts.js +46 -1
- package/src/commands/segments.js +47 -14
- package/src/commands/store-api.js +11 -3
- package/src/commands/tag.js +89 -8
- package/src/commands/templates.js +66 -5
- package/src/index.js +232 -9
package/src/index.js
CHANGED
|
@@ -41,6 +41,9 @@ import * as broadcastCmd from "./commands/broadcast.js";
|
|
|
41
41
|
import * as segmentsCmd from "./commands/segments.js";
|
|
42
42
|
import * as tagCmd from "./commands/tag.js";
|
|
43
43
|
import * as linksCmd from "./commands/links.js";
|
|
44
|
+
import * as membersCmd from "./commands/members.js";
|
|
45
|
+
import * as notesCmd from "./commands/notes.js";
|
|
46
|
+
import * as productsCmd from "./commands/products.js";
|
|
44
47
|
import * as keywordsCmd from "./commands/keywords.js";
|
|
45
48
|
import * as guideCmd from "./commands/guide.js";
|
|
46
49
|
import * as auditCmd from "./commands/audit.js";
|
|
@@ -116,7 +119,8 @@ export function run(argv) {
|
|
|
116
119
|
.action((orgId, auditId, opts) => promptsCmd.restore(orgId, auditId, opts));
|
|
117
120
|
prompts.command("push <slug-or-path>")
|
|
118
121
|
.description("Apply a local JSON file's prompt_sections back to the agent it was pulled from (regenerates system_prompt server-side)")
|
|
119
|
-
.
|
|
122
|
+
.option("--dry-run", "print what would change (added / removed / changed sections against the live agent) and write nothing")
|
|
123
|
+
.action((id, opts) => promptsCmd.push(id, opts));
|
|
120
124
|
|
|
121
125
|
// questionnaires
|
|
122
126
|
const qs = program.command("questionnaires")
|
|
@@ -144,9 +148,20 @@ export function run(argv) {
|
|
|
144
148
|
.option("--until <when>", "upper bound")
|
|
145
149
|
.option("--sender-type <list>", "comma list, e.g. bot-whatsapp or user-whatsapp,user-web (default: conversation rows only)")
|
|
146
150
|
.option("--include-templates", "also match template / broadcast payloads")
|
|
151
|
+
.option("--tool-calls", "search inside tool-call rows' request / response instead of message text (what a tool was asked and answered)")
|
|
147
152
|
.option("--limit <n>", "max rows (default 200, max 1000)")
|
|
148
153
|
.option("--json", "raw JSON (rows carry contact_id)")
|
|
149
154
|
.action((orgId, text, opts) => messagesCmd.search(orgId, text, opts));
|
|
155
|
+
msgs.command("purge <organization_id>")
|
|
156
|
+
.description("DELETE messages org-wide by pattern: email subject regex, text, sender types, time window (5,000 a run). Dry run by default; --confirm asks you to type the org id")
|
|
157
|
+
.option("--subject-regex <re>", "case-insensitive regex on the email subject (more_data.subject), e.g. '^Automatic reply'")
|
|
158
|
+
.option("--text <substr>", "message text contains this")
|
|
159
|
+
.option("--sender-type <list>", "comma list, e.g. user-email")
|
|
160
|
+
.option("--since <when>", "only rows newer than this (30d | YYYY-MM-DD)")
|
|
161
|
+
.option("--before <when>", "only rows older than this")
|
|
162
|
+
.option("--confirm", "actually delete (typed confirmation follows)")
|
|
163
|
+
.option("--json", "raw JSON")
|
|
164
|
+
.action((orgId, opts) => messagesCmd.purge(orgId, opts));
|
|
150
165
|
msgs.command("failures <organization_id>")
|
|
151
166
|
.description("What failed to send and why (Meta error codes by reason / media / contact), plus sends Meta accepted and never confirmed")
|
|
152
167
|
.option("--since <when>", "7d (default) | 24h | 30d | YYYY-MM-DD")
|
|
@@ -158,6 +173,13 @@ export function run(argv) {
|
|
|
158
173
|
.option("--until <when>", "upper bound (default now)")
|
|
159
174
|
.option("--json", "raw JSON")
|
|
160
175
|
.action((orgId, opts) => messagesCmd.stats(orgId, opts));
|
|
176
|
+
msgs.command("delete <contact_id>")
|
|
177
|
+
.description("DELETE a contact's messages (all, older than --before, or only --sender-type). Dry run by default; --confirm asks you to type the contact id")
|
|
178
|
+
.option("--before <when>", "only rows older than this: 30d | 2w | YYYY-MM-DD | ISO time")
|
|
179
|
+
.option("--sender-type <list>", "only these sender types, e.g. tool-call,steer")
|
|
180
|
+
.option("--confirm", "actually delete (typed confirmation follows)")
|
|
181
|
+
.option("--json", "raw JSON")
|
|
182
|
+
.action((contactId, opts) => messagesCmd.remove(contactId, opts));
|
|
161
183
|
|
|
162
184
|
// fine-tuning (agents.fine_tuning text + agents.fine_tuning_entries jsonb)
|
|
163
185
|
const ft = program.command("fine-tuning")
|
|
@@ -428,10 +450,23 @@ export function run(argv) {
|
|
|
428
450
|
// org (read-only org summary for the prompt-builder skill, creds stripped)
|
|
429
451
|
const org = program.command("org").description("Org info (read), create a new organization, read/set its feature flags");
|
|
430
452
|
org.command("list [search]")
|
|
431
|
-
.description("List organizations with their UUIDs — the lookup every other command needs (filter by name/slug)")
|
|
453
|
+
.description("List organizations with their UUIDs — the lookup every other command needs (filter by name/slug, or find by WhatsApp number)")
|
|
432
454
|
.option("--all", "include inactive organizations")
|
|
455
|
+
.option("--number <digits>", "find the org by its WhatsApp number (any format), Meta phone id or WABA id")
|
|
433
456
|
.option("--json", "raw JSON")
|
|
434
457
|
.action((search, opts) => orgCmd.list(search, opts));
|
|
458
|
+
org.command("agent <organization_id> <on|off>")
|
|
459
|
+
.description("The agent master switch (wati_webhook_status): off = no customer gets the AI. Dry-run unless --commit; ON without test mode needs --yes")
|
|
460
|
+
.option("--commit", "write the change")
|
|
461
|
+
.option("--yes", "confirm switching ON for every customer (vert.active not false, no allowlist)")
|
|
462
|
+
.option("--json", "raw JSON")
|
|
463
|
+
.action((orgId, state, opts) => orgCmd.agentSwitch(orgId, state, opts));
|
|
464
|
+
org.command("park <organization_id>")
|
|
465
|
+
.description("Park an org (organizations.inactive = true: hidden from censuses and crons; nothing deleted). --unpark reverses it. Needs --confirm")
|
|
466
|
+
.option("--unpark", "set inactive = false")
|
|
467
|
+
.option("--confirm", "write the change")
|
|
468
|
+
.option("--json", "raw JSON")
|
|
469
|
+
.action((orgId, opts) => orgCmd.park(orgId, opts));
|
|
435
470
|
org.command("create")
|
|
436
471
|
.description("Create a NEW organization (org row + admin membership; owner defaults to you)")
|
|
437
472
|
.requiredOption("--name <name>", "organization name")
|
|
@@ -460,9 +495,13 @@ export function run(argv) {
|
|
|
460
495
|
.option("--json", "raw JSON")
|
|
461
496
|
.action((orgId, opts) => orgCmd.health(orgId, opts));
|
|
462
497
|
org.command("automations <organization_id>")
|
|
463
|
-
.description("Automated order messages (automated_messages_config): each type, its template and whether that template is approved")
|
|
498
|
+
.description("Automated order messages (automated_messages_config): each type, its template and whether that template is approved; --set changes a type's template or mapping")
|
|
464
499
|
.option("--enable [type]", "switch a type on (or the whole feature with no type); dry run unless --commit")
|
|
465
500
|
.option("--disable [type]", "switch a type off (or the whole feature)")
|
|
501
|
+
.option("--set <type>", "change this type: with --template / --body / --buttons")
|
|
502
|
+
.option("--template <name>", "with --set: the template name to send (must exist in FlowIQ's templates; approval is checked and warned)")
|
|
503
|
+
.option("--body <map>", "with --set: body param mapping, param1=full_name,param2=order_number (\"none\" clears)")
|
|
504
|
+
.option("--buttons <map>", "with --set: button param mapping, param1=short_code (\"none\" clears)")
|
|
466
505
|
.option("--commit", "write the change")
|
|
467
506
|
.option("--json", "raw JSON")
|
|
468
507
|
.action((orgId, opts) => orgCmd.automations(orgId, opts));
|
|
@@ -558,6 +597,13 @@ export function run(argv) {
|
|
|
558
597
|
.description("List the org's agents (★ = active); use an id as --agent <id> elsewhere")
|
|
559
598
|
.option("--json", "print the raw JSON payload")
|
|
560
599
|
.action((orgId, opts) => agentsCmd.list(orgId, opts));
|
|
600
|
+
agent.command("flags")
|
|
601
|
+
.description("Cross-org census of every active agent: model, reasoning, prompt switch, tool gates (product_lookup, tickets, postal, …)")
|
|
602
|
+
.option("--tool <flag[=bool]>", "only agents where this tool flag is on (or =false: off)")
|
|
603
|
+
.option("--model <substr>", "only agents whose model contains this")
|
|
604
|
+
.option("--all", "include inactive orgs")
|
|
605
|
+
.option("--json", "raw JSON")
|
|
606
|
+
.action((opts) => agentsCmd.flags(opts));
|
|
561
607
|
agent.command("create <organization_id>")
|
|
562
608
|
.description("Create a new agent (optionally seed its prompt + set it active)")
|
|
563
609
|
.requiredOption("--name <name>", "agent name")
|
|
@@ -663,6 +709,27 @@ export function run(argv) {
|
|
|
663
709
|
.option("--json", "print the raw response")
|
|
664
710
|
.option("--out <file>", "also write the full plan JSON to a file")
|
|
665
711
|
.action((planId, opts) => plansCmd.show(planId, opts));
|
|
712
|
+
plans.command("create <organization_id>")
|
|
713
|
+
.description("File a plan on the client's Planning board (the MCP submit_plan row; staff are exempt from the client lead time). Dry run unless --commit")
|
|
714
|
+
.requiredOption("--topic <text>", "campaign name (under 120 chars)")
|
|
715
|
+
.requiredOption("--type <t>", "utility | marketing")
|
|
716
|
+
.requiredOption("--date <YYYY-MM-DD>", "send date (SAST)")
|
|
717
|
+
.option("--time <HH:MM>", "send time SAST (omit = any time that day)")
|
|
718
|
+
.option("--product <text>", "product or collection it is about")
|
|
719
|
+
.option("--audience <text>", "who it goes to, in words (omit = the whole opted-in list)")
|
|
720
|
+
.option("--line-1 <text>", "utility: first sentence after \"Hi [Name],\"")
|
|
721
|
+
.option("--line-2 <text>", "utility: second paragraph")
|
|
722
|
+
.option("--follow-up <text>", "utility: the message VIEW MORE sends")
|
|
723
|
+
.option("--landing <url>", "utility: the https:// page CLICK HERE opens")
|
|
724
|
+
.option("--message <text>", "marketing: the full first message (≤ 1,024 chars)")
|
|
725
|
+
.option("--button <spec>", "marketing button, repeatable: quick_reply:TEXT | url:TEXT=https://… | phone:TEXT=+27… | copy_code:CODE", (v, acc) => (acc || []).concat([v]), [])
|
|
726
|
+
.option("--reply <text=follow-up>", "marketing: the message a quick_reply button sends (repeatable)", (v, acc) => (acc || []).concat([v]), [])
|
|
727
|
+
.option("--graphic <brief>", "have Flowapt design the image: describe it")
|
|
728
|
+
.option("--media <url>", "use your own public https:// image or video instead")
|
|
729
|
+
.option("--draft", "save as a draft (nobody alerted) instead of filing for review")
|
|
730
|
+
.option("--commit", "file it")
|
|
731
|
+
.option("--json", "raw JSON")
|
|
732
|
+
.action((orgId, opts) => plansCmd.create(orgId, opts));
|
|
666
733
|
plans.command("status <plan_id>")
|
|
667
734
|
.description("Change a plan's status (dry run unless --commit); approved/rejected also record you as the reviewer")
|
|
668
735
|
.requiredOption("--to <status>", "draft | pending_review | approved | rejected | scheduled | sent | cancelled")
|
|
@@ -679,9 +746,29 @@ export function run(argv) {
|
|
|
679
746
|
.option("--limit <n>", "max matches (default 20, max 100)").option("--json", "raw JSON")
|
|
680
747
|
.action((orgId, opts) => contactsCmd.find(orgId, opts));
|
|
681
748
|
contacts.command("show <contact_id>")
|
|
682
|
-
.description("One contact's record + inbox state (
|
|
683
|
-
.
|
|
749
|
+
.description("One contact's record + inbox state (the org is resolved from the id; --org to pin it)")
|
|
750
|
+
.option("--org <organization_id>", "the contact's org (optional since 0.12.0)").option("--json", "raw JSON")
|
|
684
751
|
.action((contactId, opts) => contactsCmd.show(contactId, opts));
|
|
752
|
+
contacts.command("add <organization_id>")
|
|
753
|
+
.description("Create one contact by hand (a tester, a team member for a test send). Dry run unless --commit")
|
|
754
|
+
.requiredOption("--whatsapp-id <digits>", "the number with country code, e.g. 27637186304")
|
|
755
|
+
.option("--name <name>", "full_name")
|
|
756
|
+
.option("--email <email>", "email")
|
|
757
|
+
.option("--no-broadcast", "allow_broadcast false (default: true, like the inbox)")
|
|
758
|
+
.option("--ai-off", "bot_status false (default: on)")
|
|
759
|
+
.option("--tag <csv>", "tags to start with")
|
|
760
|
+
.option("--commit", "create it")
|
|
761
|
+
.option("--json", "raw JSON")
|
|
762
|
+
.action((orgId, opts) => contactsCmd.add(orgId, opts));
|
|
763
|
+
contacts.command("ai <on|off> <organization_id>")
|
|
764
|
+
.description("Switch the AI agent on or off for every contact on a tag (or an id list), writing the agent-on/off row each chat shows. Dry run unless --commit")
|
|
765
|
+
.option("--tag <tag>", "every contact carrying this tag")
|
|
766
|
+
.option("--contact <id>", "contact UUID (repeatable)", (v, acc) => (acc || []).concat([v]), [])
|
|
767
|
+
.option("--ids-file <path>", "file of contact UUIDs")
|
|
768
|
+
.option("--reason <text>", "noted on the agent-on/off row and the audit")
|
|
769
|
+
.option("--commit", "write bot_status + the marker rows")
|
|
770
|
+
.option("--json", "raw JSON")
|
|
771
|
+
.action((state, orgId, opts) => contactsCmd.ai(state, orgId, opts));
|
|
685
772
|
contacts.command("census <organization_id>")
|
|
686
773
|
.description("Archived chats that still carry unread messages or where the customer wrote last")
|
|
687
774
|
.option("--json", "raw JSON")
|
|
@@ -851,7 +938,8 @@ export function run(argv) {
|
|
|
851
938
|
.description("Send one message, print the reply + any tool calls")
|
|
852
939
|
.option("--agent <id>", "test a specific (non-active) agent")
|
|
853
940
|
.option("--clear", "clear the conversation before the run")
|
|
854
|
-
.option("--
|
|
941
|
+
.option("--tools", "also print each tool's arguments and result (what the agent asked and got back)")
|
|
942
|
+
.option("--sender <name>", "the name the agent sees: renames a SYNTHETIC test contact (a real person's contact keeps their name)")
|
|
855
943
|
.option("--image <url>", "attach a publicly-fetchable image URL (text is the caption)")
|
|
856
944
|
.option("--timeout <ms>", "per-turn HTTP timeout (default 120000)")
|
|
857
945
|
.option("--json", "print raw JSON instead of the transcript")
|
|
@@ -860,6 +948,7 @@ export function run(argv) {
|
|
|
860
948
|
test.command("scenario <organization_id> <file>")
|
|
861
949
|
.description("Run a scenario pack (JSON: {scenarios:[{id,title,turns:[{text,expect?,expectNot?}]}]})")
|
|
862
950
|
.option("--agent <id>", "test a specific agent")
|
|
951
|
+
.option("--tools", "also print each tool's arguments and result")
|
|
863
952
|
.option("--no-clear-between", "do NOT clear between scenarios (one long thread)")
|
|
864
953
|
.option("--force", "go ahead even though the test contact had real (non-web) messages in the last 6 h")
|
|
865
954
|
.option("--sender <name>", "senderName")
|
|
@@ -875,6 +964,24 @@ export function run(argv) {
|
|
|
875
964
|
.option("--timeout <ms>", "per-turn HTTP timeout")
|
|
876
965
|
.option("--json", "print raw JSON results")
|
|
877
966
|
.action((orgId, opts) => testCmd.qa(orgId, opts));
|
|
967
|
+
test.command("seed <organization_id>")
|
|
968
|
+
.description("Copy a real chat onto a NEW test contact (test_seed_*, tag test-contact, no broadcasts, AI off) to reproduce an agent or Coworker answer. Dry run unless --commit")
|
|
969
|
+
.requiredOption("--from <contact_id>", "the real contact whose thread is copied")
|
|
970
|
+
.option("--until <time>", "copy rows up to this SAST time, e.g. \"2026-10-01 09:08\" or an ISO time")
|
|
971
|
+
.option("--rows <n>", "newest rows to copy (default 60, max 500)")
|
|
972
|
+
.option("--name <name>", "the seeded contact's display name (default: \"<name> (seeded test)\")")
|
|
973
|
+
.option("--with-tools", "also copy tool-call rows")
|
|
974
|
+
.option("--copy-attributes", "copy the contact's attributes (dog name, weight, …)")
|
|
975
|
+
.option("--ai-on", "leave the agent ON for the seeded contact (default off)")
|
|
976
|
+
.option("--agent <id>", "scope to a specific agent (audit only)")
|
|
977
|
+
.option("--commit", "create it")
|
|
978
|
+
.option("--json", "raw JSON")
|
|
979
|
+
.action((orgId, opts) => testCmd.seed(orgId, opts));
|
|
980
|
+
test.command("unseed <organization_id> <contact_id>")
|
|
981
|
+
.description("DELETE a seeded test contact (test_seed_* only) and its copied rows")
|
|
982
|
+
.option("--confirm", "actually delete")
|
|
983
|
+
.option("--json", "raw JSON")
|
|
984
|
+
.action((orgId, contactId, opts) => testCmd.unseed(orgId, contactId, opts));
|
|
878
985
|
test.command("clear <organization_id>")
|
|
879
986
|
.description("Clear the test conversation (hide history via memory_cutoff)")
|
|
880
987
|
.option("--agent <id>", "target a specific agent's test contact")
|
|
@@ -1006,6 +1113,7 @@ export function run(argv) {
|
|
|
1006
1113
|
.option("--all", "include fired / cancelled / failed rows too")
|
|
1007
1114
|
.option("--status <s>", "filter to one status (pending|request|completed|cancelled|failed)")
|
|
1008
1115
|
.option("--limit <n>", "max rows (default 25, max 200)")
|
|
1116
|
+
.option("--detail", "also print each row's body / button values and header media (what the queued send will say)")
|
|
1009
1117
|
.option("--json", "raw JSON")
|
|
1010
1118
|
.action((orgId, opts) => broadcastCmd.scheduledList(orgId, opts));
|
|
1011
1119
|
scheduled.command("approve <organization_id> <queue_id>")
|
|
@@ -1118,15 +1226,25 @@ export function run(argv) {
|
|
|
1118
1226
|
.option("--raw-campaign", "use --campaign exactly as typed, bypassing the Date_Campaign normalisation")
|
|
1119
1227
|
.option("--new-campaign", "confirm the tag is a NEW campaign even though a differently spelled one with the same name already runs")
|
|
1120
1228
|
.option("--domain <host>", "short domain: chatcart.io (default) | linklnk.io | yapi.store")
|
|
1229
|
+
.option("--slug <slug>", "readable code for ONE url (3-48 chars a-z 0-9 -); a taken slug falls back to a generated code and says so")
|
|
1121
1230
|
.option("--commit", "actually mint the links (omit = dry-run preview)")
|
|
1122
1231
|
.option("--json", "raw JSON output")
|
|
1123
1232
|
.action((orgId, opts) => linksCmd.shorten(orgId, opts));
|
|
1124
1233
|
links.command("list <organization_id>")
|
|
1125
|
-
.description("List the org's short links, newest first")
|
|
1234
|
+
.description("List the org's short links, newest first; look one up by code or by destination")
|
|
1126
1235
|
.option("--campaign <value>", "only links carrying this exact utm_campaign")
|
|
1127
|
-
.option("--
|
|
1236
|
+
.option("--code <code>", "one link by its code (the part after the slash)")
|
|
1237
|
+
.option("--to <substr>", "only links whose destination contains this text")
|
|
1238
|
+
.option("--limit <n>", "max rows (default 25, max 500)")
|
|
1239
|
+
.option("--all", "walk every page (500 a call) instead of the first")
|
|
1128
1240
|
.option("--json", "raw JSON output")
|
|
1129
1241
|
.action((orgId, opts) => linksCmd.list(orgId, opts));
|
|
1242
|
+
links.command("repoint <organization_id> <code>")
|
|
1243
|
+
.description("Move where a live short code sends people (the code, its clicks and every message carrying it stay). Dry-run unless --commit")
|
|
1244
|
+
.requiredOption("--to <url>", "the new destination page")
|
|
1245
|
+
.option("--commit", "write the change (audited with the old destination; undo is printed)")
|
|
1246
|
+
.option("--json", "raw JSON output")
|
|
1247
|
+
.action((orgId, code, opts) => linksCmd.repoint(orgId, code, opts));
|
|
1130
1248
|
|
|
1131
1249
|
// tag (the Advanced Tagging dialog in the terminal — ONE named tag on a matched set; dry-run default)
|
|
1132
1250
|
const tag = program.command("tag")
|
|
@@ -1136,11 +1254,28 @@ export function run(argv) {
|
|
|
1136
1254
|
.option("--field <name>", "field to search", "order_history")
|
|
1137
1255
|
.option("--any <csv>", "match ANY of these comma-separated keywords (OR)")
|
|
1138
1256
|
.option("--all <csv>", "match ALL of these comma-separated keywords (AND)")
|
|
1257
|
+
.option("--exact", "match the WHOLE value (whatsapp_id / phone_number / email / full_name), not a substring")
|
|
1139
1258
|
.option("--case-sensitive", "case-sensitive match")
|
|
1140
1259
|
.option("--tag <name>", "tag to apply (required with --commit)")
|
|
1141
1260
|
.option("--commit", "write the tag (omit = dry-run count + sample)")
|
|
1142
1261
|
.option("--yes", "skip the type-the-tag confirm on large (>2000) writes")
|
|
1143
1262
|
.action((orgId, opts) => tagCmd.field(orgId, opts));
|
|
1263
|
+
tag.command("ids <organization_id>")
|
|
1264
|
+
.description("Apply one tag to an explicit list of contact ids (append-only, idempotent; ids outside the org are ignored)")
|
|
1265
|
+
.option("--ids <csv>", "contact UUIDs, comma-separated")
|
|
1266
|
+
.option("--ids-file <path>", "file of contact UUIDs (one per line or comma-separated)")
|
|
1267
|
+
.option("--tag <name>", "tag to apply (required with --commit)")
|
|
1268
|
+
.option("--commit", "write the tag (omit = dry-run count + sample)")
|
|
1269
|
+
.option("--yes", "skip the type-the-tag confirm on large (>2000) writes")
|
|
1270
|
+
.option("--json", "raw JSON")
|
|
1271
|
+
.action((orgId, opts) => tagCmd.ids(orgId, opts));
|
|
1272
|
+
tag.command("holders <organization_id> <tag>")
|
|
1273
|
+
.description("Who carries a tag: name, number, id and the flags that keep them off a broadcast (read-only)")
|
|
1274
|
+
.option("--limit <n>", "rows (default 200, max 1000)")
|
|
1275
|
+
.option("--all", "walk every page")
|
|
1276
|
+
.option("--csv", "CSV to stdout (id, name, number, email, allow_broadcast, archived, blocked)")
|
|
1277
|
+
.option("--json", "raw JSON")
|
|
1278
|
+
.action((orgId, tagName, opts) => tagCmd.holders(orgId, tagName, opts));
|
|
1144
1279
|
tag.command("cohort <organization_id>")
|
|
1145
1280
|
.description("Tag a ranked cohort (top-N). --type top_spenders|frequent_buyers|recent_buyers|high_value_low_frequency")
|
|
1146
1281
|
.option("--type <t>", "cohort type", "top_spenders")
|
|
@@ -1194,10 +1329,96 @@ export function run(argv) {
|
|
|
1194
1329
|
.option("--json", "raw JSON")
|
|
1195
1330
|
.action((orgId, opts) => tagCmd.list(orgId, opts));
|
|
1196
1331
|
tag.command("remove <organization_id> <tags>")
|
|
1197
|
-
.description("Remove tag(s) (comma-separated) from ALL contacts in the org. Destructive — needs --confirm")
|
|
1332
|
+
.description("Remove tag(s) (comma-separated) from ALL contacts in the org, or only from --contacts. Destructive — needs --confirm")
|
|
1333
|
+
.option("--contacts <csv>", "only these contact UUIDs lose the tag(s)")
|
|
1334
|
+
.option("--contacts-file <path>", "file of contact UUIDs that lose the tag(s)")
|
|
1198
1335
|
.option("--confirm", "actually remove (omit = dry-run)")
|
|
1199
1336
|
.action((orgId, tags, opts) => tagCmd.remove(orgId, tags, opts));
|
|
1200
1337
|
|
|
1338
|
+
// members / users (organization_members + profiles + auth.users)
|
|
1339
|
+
const members = program.command("members").description("An org's people: list, add an existing account, change a role, rename, remove");
|
|
1340
|
+
members.command("list <organization_id>")
|
|
1341
|
+
.description("Members with role, name, email, last sign-in; pending invitations")
|
|
1342
|
+
.option("--json", "raw JSON")
|
|
1343
|
+
.action((orgId, opts) => membersCmd.list(orgId, opts));
|
|
1344
|
+
members.command("invite <organization_id> <email>")
|
|
1345
|
+
.description("Invite someone who has NO account yet (an invitations row they accept at sign-up; FlowIQ sends no email)")
|
|
1346
|
+
.option("--role <role>", "admin | member | viewer", "member")
|
|
1347
|
+
.option("--dry-run", "show what would be written")
|
|
1348
|
+
.option("--json", "raw JSON")
|
|
1349
|
+
.action((orgId, email, opts) => membersCmd.invite(orgId, email, opts));
|
|
1350
|
+
members.command("add <organization_id> <email>")
|
|
1351
|
+
.description("Add an EXISTING account to the org (someone with no account must be invited from the app)")
|
|
1352
|
+
.option("--role <role>", "admin | member | viewer", "member")
|
|
1353
|
+
.option("--json", "raw JSON")
|
|
1354
|
+
.action((orgId, email, opts) => membersCmd.add(orgId, email, opts));
|
|
1355
|
+
members.command("role <organization_id> <email> <role>")
|
|
1356
|
+
.description("Change a member's role: admin | member | viewer (the last admin cannot be demoted)")
|
|
1357
|
+
.option("--json", "raw JSON")
|
|
1358
|
+
.action((orgId, email, role, opts) => membersCmd.role(orgId, email, role, opts));
|
|
1359
|
+
members.command("rename <email> <full_name>")
|
|
1360
|
+
.description("Set the person's display name (profiles.full_name, shown in every org)")
|
|
1361
|
+
.option("--json", "raw JSON")
|
|
1362
|
+
.action((email, name, opts) => membersCmd.rename(email, name, opts));
|
|
1363
|
+
members.command("remove <organization_id> <email>")
|
|
1364
|
+
.description("Remove a member from the org (dry-run unless --confirm; the owner and the last admin are refused)")
|
|
1365
|
+
.option("--confirm", "actually remove")
|
|
1366
|
+
.option("--json", "raw JSON")
|
|
1367
|
+
.action((orgId, email, opts) => membersCmd.remove(orgId, email, opts));
|
|
1368
|
+
const users = program.command("users").description("One user across orgs");
|
|
1369
|
+
users.command("lookup <email>")
|
|
1370
|
+
.description("The account behind an email: sign-in method, confirmation, last sign-in, every org and role")
|
|
1371
|
+
.option("--json", "raw JSON")
|
|
1372
|
+
.action((email, opts) => membersCmd.lookup(email, opts));
|
|
1373
|
+
|
|
1374
|
+
// steer / notes (internal rows in a contact's chat)
|
|
1375
|
+
const steer = program.command("steer").description("Supervisor steers for the agent on one contact: list, add, clear");
|
|
1376
|
+
steer.command("list <contact_id>")
|
|
1377
|
+
.description("Every steer on the contact (mode, consumed, active)")
|
|
1378
|
+
.option("--limit <n>", "rows (default 50)")
|
|
1379
|
+
.option("--json", "raw JSON")
|
|
1380
|
+
.action((contactId, opts) => notesCmd.steerList(contactId, opts));
|
|
1381
|
+
steer.command("add <contact_id> <text...>")
|
|
1382
|
+
.description("Add a steer the agent applies on EVERY reply (ongoing), or --once for the next reply only")
|
|
1383
|
+
.option("--once", "next reply only")
|
|
1384
|
+
.option("--json", "raw JSON")
|
|
1385
|
+
.action((contactId, text, opts) => notesCmd.steerAdd(contactId, text, opts));
|
|
1386
|
+
steer.command("clear <contact_id>")
|
|
1387
|
+
.description("Delete the contact's steers (all, or --id); the inbox has no delete for an ongoing steer. Dry-run unless --confirm")
|
|
1388
|
+
.option("--id <steer_id>", "only this steer (repeatable)", (v, acc) => (acc || []).concat([v]), [])
|
|
1389
|
+
.option("--confirm", "actually delete")
|
|
1390
|
+
.option("--json", "raw JSON")
|
|
1391
|
+
.action((contactId, opts) => notesCmd.steerClear(contactId, opts));
|
|
1392
|
+
const notes = program.command("notes").description("Internal notes on a contact (the Customer panel's sticky notes)");
|
|
1393
|
+
notes.command("list <contact_id>")
|
|
1394
|
+
.description("Every internal note on the contact")
|
|
1395
|
+
.option("--limit <n>", "rows (default 50)")
|
|
1396
|
+
.option("--json", "raw JSON")
|
|
1397
|
+
.action((contactId, opts) => notesCmd.notesList(contactId, opts));
|
|
1398
|
+
notes.command("add <contact_id> <text...>")
|
|
1399
|
+
.description("Add an internal note (team-only, never sent to the customer)")
|
|
1400
|
+
.option("--json", "raw JSON")
|
|
1401
|
+
.action((contactId, text, opts) => notesCmd.notesAdd(contactId, text, opts));
|
|
1402
|
+
|
|
1403
|
+
// products (the synced catalogue)
|
|
1404
|
+
const products = program.command("products").description("The synced catalogue as the agent sees it: status, a search replay, a one-org reconcile");
|
|
1405
|
+
products.command("status <organization_id>")
|
|
1406
|
+
.description("Row counts by status, embeddings (missing = the agent cannot find them), images, prices, stock, the last syncs")
|
|
1407
|
+
.option("--json", "raw JSON")
|
|
1408
|
+
.action((orgId, opts) => productsCmd.status(orgId, opts));
|
|
1409
|
+
products.command("search <organization_id> <text...>")
|
|
1410
|
+
.description("Replay the agent's product_lookup match (fuzzy_match_products) for a phrase")
|
|
1411
|
+
.option("--limit <n>", "matches (default 8, max 50)")
|
|
1412
|
+
.option("--min-similarity <x>", "pg_trgm threshold (default 0.15)")
|
|
1413
|
+
.option("--json", "raw JSON")
|
|
1414
|
+
.action((orgId, text, opts) => productsCmd.search(orgId, text, opts));
|
|
1415
|
+
products.command("reconcile <organization_id>")
|
|
1416
|
+
.description("Trigger products-reconcile-scheduler for this org only (re-sync + re-embed). Dry-run unless --confirm")
|
|
1417
|
+
.option("--platform <p>", "shopify | woo (default: the org's connected store)")
|
|
1418
|
+
.option("--confirm", "actually trigger")
|
|
1419
|
+
.option("--json", "raw JSON")
|
|
1420
|
+
.action((orgId, opts) => productsCmd.reconcile(orgId, opts));
|
|
1421
|
+
|
|
1201
1422
|
// keywords (org auto-reply engine: keywords + keyword_actions; id-keyed diff push)
|
|
1202
1423
|
const keywords = program.command("keywords")
|
|
1203
1424
|
.alias("kw")
|
|
@@ -1222,6 +1443,7 @@ export function run(argv) {
|
|
|
1222
1443
|
shopify.command("get <organization_id> <path>")
|
|
1223
1444
|
.description('GET any Shopify Admin REST endpoint, e.g. `get <org> orders.json --q status=any` (".json" optional)')
|
|
1224
1445
|
.option("--q <k=v>", "query parameter (repeatable), e.g. --q status=any --q limit=250", storeApiCmd.collectQuery, {})
|
|
1446
|
+
.option("--fields <csv>", "return only these fields per record (Shopify REST `fields`); the way past the 3.5 MB cap on big lists")
|
|
1225
1447
|
.option("--all", "follow Link rel=next and return every page (bounded by --max-pages and a size/time cap)")
|
|
1226
1448
|
.option("--max-pages <n>", "page limit when using --all (default 10, cap 50)")
|
|
1227
1449
|
.option("--api-version <ver>", "Shopify Admin API version (default 2024-01)")
|
|
@@ -1257,6 +1479,7 @@ export function run(argv) {
|
|
|
1257
1479
|
woo.command("get <organization_id> <path>")
|
|
1258
1480
|
.description("GET any WooCommerce REST endpoint, e.g. `get <org> orders --q status=completed`")
|
|
1259
1481
|
.option("--q <k=v>", "query parameter (repeatable), e.g. --q status=completed --q per_page=100", storeApiCmd.collectQuery, {})
|
|
1482
|
+
.option("--fields <csv>", "return only these fields per record (Woo `_fields`), e.g. id,name,permalink,status; the way past the 3.5 MB cap on a big catalogue")
|
|
1260
1483
|
.option("--namespace <ns>", "REST namespace (default wc/v3; also wc-analytics, wp/v2)")
|
|
1261
1484
|
.option("--all", "page through every result (bounded by --max-pages and a size/time cap)")
|
|
1262
1485
|
.option("--max-pages <n>", "page limit when using --all (default 10, cap 50)")
|