@flowapt/flowiq-cli 0.9.9 → 0.11.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 +209 -0
- package/TEAM-GUIDE.md +25 -0
- package/package.json +1 -1
- package/src/batch-cde.test.mjs +73 -0
- package/src/batch-f.test.mjs +63 -0
- package/src/commands/agent-test.js +15 -3
- package/src/commands/agents.js +31 -0
- package/src/commands/broadcast.js +9 -0
- package/src/commands/contacts.js +173 -0
- package/src/commands/flows.js +82 -0
- package/src/commands/links.js +63 -20
- package/src/commands/members.js +95 -0
- package/src/commands/messages.js +106 -0
- package/src/commands/notes.js +63 -0
- package/src/commands/org.js +190 -5
- package/src/commands/products.js +58 -0
- package/src/commands/prompts.js +46 -1
- package/src/commands/rules.js +67 -0
- package/src/commands/segments.js +47 -14
- package/src/commands/tag.js +77 -7
- package/src/commands/templates.js +66 -5
- package/src/commands/tickets.js +69 -0
- package/src/commands/widgets.js +97 -0
- package/src/index.js +286 -6
package/src/index.js
CHANGED
|
@@ -28,6 +28,11 @@ import * as agentsCmd from "./commands/agents.js";
|
|
|
28
28
|
import * as agentUpdatesCmd from "./commands/agent-updates.js";
|
|
29
29
|
import * as plansCmd from "./commands/plans.js";
|
|
30
30
|
import * as gapsCmd from "./commands/gaps.js";
|
|
31
|
+
import * as contactsCmd from "./commands/contacts.js";
|
|
32
|
+
import * as widgetsCmd from "./commands/widgets.js";
|
|
33
|
+
import * as ticketsCmd from "./commands/tickets.js";
|
|
34
|
+
import * as flowsCmd from "./commands/flows.js";
|
|
35
|
+
import * as rulesCmd from "./commands/rules.js";
|
|
31
36
|
import * as exportCmd from "./commands/export.js";
|
|
32
37
|
import * as testCmd from "./commands/agent-test.js";
|
|
33
38
|
import * as knowledgeCmd from "./commands/knowledge.js";
|
|
@@ -36,6 +41,9 @@ import * as broadcastCmd from "./commands/broadcast.js";
|
|
|
36
41
|
import * as segmentsCmd from "./commands/segments.js";
|
|
37
42
|
import * as tagCmd from "./commands/tag.js";
|
|
38
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";
|
|
39
47
|
import * as keywordsCmd from "./commands/keywords.js";
|
|
40
48
|
import * as guideCmd from "./commands/guide.js";
|
|
41
49
|
import * as auditCmd from "./commands/audit.js";
|
|
@@ -111,7 +119,8 @@ export function run(argv) {
|
|
|
111
119
|
.action((orgId, auditId, opts) => promptsCmd.restore(orgId, auditId, opts));
|
|
112
120
|
prompts.command("push <slug-or-path>")
|
|
113
121
|
.description("Apply a local JSON file's prompt_sections back to the agent it was pulled from (regenerates system_prompt server-side)")
|
|
114
|
-
.
|
|
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));
|
|
115
124
|
|
|
116
125
|
// questionnaires
|
|
117
126
|
const qs = program.command("questionnaires")
|
|
@@ -133,6 +142,33 @@ export function run(argv) {
|
|
|
133
142
|
.description("Pull last N user-message windows of conversation for a contact (default N=25, max 200)")
|
|
134
143
|
.option("--count <n>", "number of most-recent user-* messages to anchor on", "25")
|
|
135
144
|
.action((contactId, opts) => messagesCmd.pull(contactId, opts));
|
|
145
|
+
msgs.command("search <organization_id> <text...>")
|
|
146
|
+
.description("Org-wide text search: did the agent (or anyone) say X to anyone? Template sends excluded unless asked")
|
|
147
|
+
.option("--since <when>", "24h (default) | 7d | 2w | YYYY-MM-DD")
|
|
148
|
+
.option("--until <when>", "upper bound")
|
|
149
|
+
.option("--sender-type <list>", "comma list, e.g. bot-whatsapp or user-whatsapp,user-web (default: conversation rows only)")
|
|
150
|
+
.option("--include-templates", "also match template / broadcast payloads")
|
|
151
|
+
.option("--limit <n>", "max rows (default 200, max 1000)")
|
|
152
|
+
.option("--json", "raw JSON (rows carry contact_id)")
|
|
153
|
+
.action((orgId, text, opts) => messagesCmd.search(orgId, text, opts));
|
|
154
|
+
msgs.command("failures <organization_id>")
|
|
155
|
+
.description("What failed to send and why (Meta error codes by reason / media / contact), plus sends Meta accepted and never confirmed")
|
|
156
|
+
.option("--since <when>", "7d (default) | 24h | 30d | YYYY-MM-DD")
|
|
157
|
+
.option("--json", "raw JSON")
|
|
158
|
+
.action((orgId, opts) => messagesCmd.failures(orgId, opts));
|
|
159
|
+
msgs.command("stats <organization_id>")
|
|
160
|
+
.description("Monthly WhatsApp volume: inbound, outbound, free-form (in-window) vs template, bot vs human")
|
|
161
|
+
.option("--since <when>", "6m (default) | 30d | YYYY-MM-DD")
|
|
162
|
+
.option("--until <when>", "upper bound (default now)")
|
|
163
|
+
.option("--json", "raw JSON")
|
|
164
|
+
.action((orgId, opts) => messagesCmd.stats(orgId, opts));
|
|
165
|
+
msgs.command("delete <contact_id>")
|
|
166
|
+
.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")
|
|
167
|
+
.option("--before <when>", "only rows older than this: 30d | 2w | YYYY-MM-DD | ISO time")
|
|
168
|
+
.option("--sender-type <list>", "only these sender types, e.g. tool-call,steer")
|
|
169
|
+
.option("--confirm", "actually delete (typed confirmation follows)")
|
|
170
|
+
.option("--json", "raw JSON")
|
|
171
|
+
.action((contactId, opts) => messagesCmd.remove(contactId, opts));
|
|
136
172
|
|
|
137
173
|
// fine-tuning (agents.fine_tuning text + agents.fine_tuning_entries jsonb)
|
|
138
174
|
const ft = program.command("fine-tuning")
|
|
@@ -403,10 +439,23 @@ export function run(argv) {
|
|
|
403
439
|
// org (read-only org summary for the prompt-builder skill, creds stripped)
|
|
404
440
|
const org = program.command("org").description("Org info (read), create a new organization, read/set its feature flags");
|
|
405
441
|
org.command("list [search]")
|
|
406
|
-
.description("List organizations with their UUIDs — the lookup every other command needs (filter by name/slug)")
|
|
442
|
+
.description("List organizations with their UUIDs — the lookup every other command needs (filter by name/slug, or find by WhatsApp number)")
|
|
407
443
|
.option("--all", "include inactive organizations")
|
|
444
|
+
.option("--number <digits>", "find the org by its WhatsApp number (any format), Meta phone id or WABA id")
|
|
408
445
|
.option("--json", "raw JSON")
|
|
409
446
|
.action((search, opts) => orgCmd.list(search, opts));
|
|
447
|
+
org.command("agent <organization_id> <on|off>")
|
|
448
|
+
.description("The agent master switch (wati_webhook_status): off = no customer gets the AI. Dry-run unless --commit; ON without test mode needs --yes")
|
|
449
|
+
.option("--commit", "write the change")
|
|
450
|
+
.option("--yes", "confirm switching ON for every customer (vert.active not false, no allowlist)")
|
|
451
|
+
.option("--json", "raw JSON")
|
|
452
|
+
.action((orgId, state, opts) => orgCmd.agentSwitch(orgId, state, opts));
|
|
453
|
+
org.command("park <organization_id>")
|
|
454
|
+
.description("Park an org (organizations.inactive = true: hidden from censuses and crons; nothing deleted). --unpark reverses it. Needs --confirm")
|
|
455
|
+
.option("--unpark", "set inactive = false")
|
|
456
|
+
.option("--confirm", "write the change")
|
|
457
|
+
.option("--json", "raw JSON")
|
|
458
|
+
.action((orgId, opts) => orgCmd.park(orgId, opts));
|
|
410
459
|
org.command("create")
|
|
411
460
|
.description("Create a NEW organization (org row + admin membership; owner defaults to you)")
|
|
412
461
|
.requiredOption("--name <name>", "organization name")
|
|
@@ -418,6 +467,36 @@ export function run(argv) {
|
|
|
418
467
|
.description("Show the org's platform, storefront url + active agent (raw creds stripped)")
|
|
419
468
|
.option("--json", "print the raw JSON payload")
|
|
420
469
|
.action((orgId, opts) => orgCmd.info(orgId, opts));
|
|
470
|
+
org.command("channels [organization_id]")
|
|
471
|
+
.description("Channel connections: WhatsApp (provider, master switch), Messenger, Instagram, TikTok, email, Meta Ads; --all = every org")
|
|
472
|
+
.option("--all", "census across every active org (no organization_id needed)")
|
|
473
|
+
.option("--inactive", "with --all: include inactive orgs")
|
|
474
|
+
.option("--json", "raw JSON")
|
|
475
|
+
.action((orgId, opts) => orgCmd.channels(orgId, opts));
|
|
476
|
+
org.command("whatsapp [organization_id]")
|
|
477
|
+
.description("WhatsApp number health straight from Meta: status, quality, limit tier, name status, webhook subscription; --all = every Meta org")
|
|
478
|
+
.option("--all", "census across every active Meta org")
|
|
479
|
+
.option("--inactive", "with --all: include inactive orgs")
|
|
480
|
+
.option("--json", "raw JSON")
|
|
481
|
+
.action((orgId, opts) => orgCmd.whatsapp(orgId, opts));
|
|
482
|
+
org.command("health <organization_id>")
|
|
483
|
+
.description("Is this client's agent ready? Master switch, keys (probed), agent, catalogue + embeddings, store webhooks + order sync, traffic")
|
|
484
|
+
.option("--json", "raw JSON")
|
|
485
|
+
.action((orgId, opts) => orgCmd.health(orgId, opts));
|
|
486
|
+
org.command("automations <organization_id>")
|
|
487
|
+
.description("Automated order messages (automated_messages_config): each type, its template and whether that template is approved")
|
|
488
|
+
.option("--enable [type]", "switch a type on (or the whole feature with no type); dry run unless --commit")
|
|
489
|
+
.option("--disable [type]", "switch a type off (or the whole feature)")
|
|
490
|
+
.option("--commit", "write the change")
|
|
491
|
+
.option("--json", "raw JSON")
|
|
492
|
+
.action((orgId, opts) => orgCmd.automations(orgId, opts));
|
|
493
|
+
org.command("abandoned <organization_id>")
|
|
494
|
+
.description("Abandoned-cart recovery config per sequence (template, delay, cart filter, discount) + the last 30 days of sends")
|
|
495
|
+
.option("--enable [sequence]", "switch a sequence on (or the whole feature with no number); dry run unless --commit")
|
|
496
|
+
.option("--disable [sequence]", "switch a sequence off (or the whole feature)")
|
|
497
|
+
.option("--commit", "write the change")
|
|
498
|
+
.option("--json", "raw JSON")
|
|
499
|
+
.action((orgId, opts) => orgCmd.abandoned(orgId, opts));
|
|
421
500
|
|
|
422
501
|
// org flags — organizations.feature_flags (the Settings → Profile switches).
|
|
423
502
|
// Registry-allowlisted writes, credentials never read or written, dangerous
|
|
@@ -503,6 +582,13 @@ export function run(argv) {
|
|
|
503
582
|
.description("List the org's agents (★ = active); use an id as --agent <id> elsewhere")
|
|
504
583
|
.option("--json", "print the raw JSON payload")
|
|
505
584
|
.action((orgId, opts) => agentsCmd.list(orgId, opts));
|
|
585
|
+
agent.command("flags")
|
|
586
|
+
.description("Cross-org census of every active agent: model, reasoning, prompt switch, tool gates (product_lookup, tickets, postal, …)")
|
|
587
|
+
.option("--tool <flag[=bool]>", "only agents where this tool flag is on (or =false: off)")
|
|
588
|
+
.option("--model <substr>", "only agents whose model contains this")
|
|
589
|
+
.option("--all", "include inactive orgs")
|
|
590
|
+
.option("--json", "raw JSON")
|
|
591
|
+
.action((opts) => agentsCmd.flags(opts));
|
|
506
592
|
agent.command("create <organization_id>")
|
|
507
593
|
.description("Create a new agent (optionally seed its prompt + set it active)")
|
|
508
594
|
.requiredOption("--name <name>", "agent name")
|
|
@@ -616,6 +702,90 @@ export function run(argv) {
|
|
|
616
702
|
.option("--commit", "apply the change (default is a dry run)")
|
|
617
703
|
.action((planId, opts) => plansCmd.status(planId, opts));
|
|
618
704
|
|
|
705
|
+
// contacts (1 Oct 2026): the contact record from the terminal
|
|
706
|
+
const contacts = program.command("contacts").description("Find a contact (incl. archived / blocked) with its inbox state, lint numbers, merge, opt out / in, fix a number, export");
|
|
707
|
+
contacts.command("find <organization_id>")
|
|
708
|
+
.description("Find contacts by email / phone / name / id, archived and blocked included, with team, assignee, latest message and open ticket")
|
|
709
|
+
.option("--email <substr>", "email contains").option("--phone <digits>", "number contains (digits only)").option("--name <substr>", "name contains").option("--id <uuid>", "one contact by id")
|
|
710
|
+
.option("--limit <n>", "max matches (default 20, max 100)").option("--json", "raw JSON")
|
|
711
|
+
.action((orgId, opts) => contactsCmd.find(orgId, opts));
|
|
712
|
+
contacts.command("show <contact_id>")
|
|
713
|
+
.description("One contact's record + inbox state (needs --org)")
|
|
714
|
+
.requiredOption("--org <organization_id>", "the contact's org").option("--json", "raw JSON")
|
|
715
|
+
.action((contactId, opts) => contactsCmd.show(contactId, opts));
|
|
716
|
+
contacts.command("census <organization_id>")
|
|
717
|
+
.description("Archived chats that still carry unread messages or where the customer wrote last")
|
|
718
|
+
.option("--json", "raw JSON")
|
|
719
|
+
.action((orgId, opts) => contactsCmd.census(orgId, opts));
|
|
720
|
+
contacts.command("lint <organization_id>")
|
|
721
|
+
.description("Malformed WhatsApp numbers (doubled country code, kept trunk 0, bad length) with the repair and whether a twin contact holds it")
|
|
722
|
+
.option("--json", "raw JSON")
|
|
723
|
+
.action((orgId, opts) => contactsCmd.lint(orgId, opts));
|
|
724
|
+
contacts.command("merge <organization_id> <keep_id> <remove_id>")
|
|
725
|
+
.description("Merge <remove_id> into <keep_id> (messages, tickets, orders move; the app's merge_contacts_v2). Dry run unless --commit")
|
|
726
|
+
.option("--commit", "do it").option("--json", "raw JSON")
|
|
727
|
+
.action((orgId, keep, remove, opts) => contactsCmd.merge(orgId, keep, remove, opts));
|
|
728
|
+
contacts.command("optout <organization_id>")
|
|
729
|
+
.description("Opt contacts OUT of broadcasts (allow_broadcast=false + the inbox marker). Dry run unless --commit")
|
|
730
|
+
.option("--contact <id>", "contact id (repeatable)", (v, a) => (a || []).concat(v), []).option("--ids-file <file>", "file of contact ids (whitespace / comma separated)")
|
|
731
|
+
.option("--reason <text>", "recorded on the marker and the audit row").option("--commit", "write").option("--json", "raw JSON")
|
|
732
|
+
.action((orgId, opts) => contactsCmd.optout(orgId, opts));
|
|
733
|
+
contacts.command("optin <organization_id>")
|
|
734
|
+
.description("Opt contacts back IN to broadcasts (they will receive sends again). Dry run unless --commit --confirm")
|
|
735
|
+
.option("--contact <id>", "contact id (repeatable)", (v, a) => (a || []).concat(v), []).option("--ids-file <file>", "file of contact ids")
|
|
736
|
+
.option("--reason <text>", "recorded on the marker and the audit row").option("--commit", "write").option("--confirm", "required with --commit").option("--json", "raw JSON")
|
|
737
|
+
.action((orgId, opts) => contactsCmd.optin(orgId, opts));
|
|
738
|
+
contacts.command("set <organization_id> <contact_id>")
|
|
739
|
+
.description("Repair a contact's WhatsApp number (refused when another contact holds it: merge instead). Dry run unless --commit")
|
|
740
|
+
.option("--whatsapp-id <digits>", "the corrected number, country code first").option("--commit", "write").option("--json", "raw JSON")
|
|
741
|
+
.action((orgId, contactId, opts) => contactsCmd.set(orgId, contactId, opts));
|
|
742
|
+
contacts.command("export <organization_id>")
|
|
743
|
+
.description("Export the org's contacts to CSV (Excel opens it) → .flowiq/contacts/<org>.csv")
|
|
744
|
+
.option("--subscribers", "only opted-in, not archived, not blocked").option("--out <file>", "write here instead").option("--json", "write JSON instead of CSV")
|
|
745
|
+
.action((orgId, opts) => contactsCmd.exportContacts(orgId, opts));
|
|
746
|
+
|
|
747
|
+
// widgets (1 Oct 2026): web chat widgets
|
|
748
|
+
const widgets = program.command("widgets").description("Web chat widgets: list, show, pull / push the config (id-keyed patch), embed code, set the active one");
|
|
749
|
+
widgets.command("list <organization_id>").description("The org's widgets").option("--json", "raw JSON").action((orgId, opts) => widgetsCmd.list(orgId, opts));
|
|
750
|
+
widgets.command("show <widget_id>").description("One widget's settings").option("--json", "raw JSON").action((id, opts) => widgetsCmd.show(id, opts));
|
|
751
|
+
widgets.command("pull <widget_id>").description("Write the full config to .flowiq/widgets/<org>-<id>.json for editing").option("--out <file>", "write here instead").action((id, opts) => widgetsCmd.pull(id, opts));
|
|
752
|
+
widgets.command("push <file>").description("Push a pulled file back: only differing fields, jsonb bags merged at their top level (null removes a key). Dry run unless --commit")
|
|
753
|
+
.option("--commit", "write").option("--json", "raw JSON").action((file, opts) => widgetsCmd.push(file, opts));
|
|
754
|
+
widgets.command("embed <organization_id> [widget_id]").description("The <script> snippet a client pastes (widget id optional: the org-only embed resolves to the active widget)").option("--json", "raw JSON").action((orgId, wid, opts) => widgetsCmd.embed(orgId, wid, opts));
|
|
755
|
+
widgets.command("activate <widget_id>").description("Make this the widget an org-only embed resolves to").option("--json", "raw JSON").action((id, opts) => widgetsCmd.activate(id, opts));
|
|
756
|
+
|
|
757
|
+
// tickets (1 Oct 2026): escalation tickets
|
|
758
|
+
const tickets = program.command("tickets").description("Escalation tickets: list open / stale ones, show one, change its status");
|
|
759
|
+
tickets.command("list <organization_id>")
|
|
760
|
+
.description("Tickets newest first (default open + in_progress)")
|
|
761
|
+
.option("--status <list>", "open,in_progress (default) | resolved | closed | all").option("--contact <id>", "one contact's tickets")
|
|
762
|
+
.option("--older-than <age>", "only tickets opened longer ago than 48h / 3d / 1w").option("--limit <n>", "max rows (default 100, max 500)").option("--json", "raw JSON")
|
|
763
|
+
.action((orgId, opts) => ticketsCmd.list(orgId, opts));
|
|
764
|
+
tickets.command("show <ticket_id>").description("One ticket in full").option("--json", "raw JSON").action((id, opts) => ticketsCmd.show(id, opts));
|
|
765
|
+
tickets.command("status <ticket_id>")
|
|
766
|
+
.description("Change a ticket's status the way the app does (stamps, history; honours reenable_bot_on_ticket_resolve). Dry run unless --commit")
|
|
767
|
+
.requiredOption("--set <status>", "open | in_progress | resolved | closed").option("--note <text>", "resolution / reopen note").option("--commit", "write").option("--json", "raw JSON")
|
|
768
|
+
.action((id, opts) => ticketsCmd.status(id, opts));
|
|
769
|
+
|
|
770
|
+
// flows (1 Oct 2026): the Flows automation engine
|
|
771
|
+
const flows = program.command("flows").description("Flows: list (trigger, templates, param-key lint), show, execution status + failures, enable / disable");
|
|
772
|
+
flows.command("list <organization_id>").description("Every flow with trigger, templates, stats and a lint of its body_parameters keys").option("--json", "raw JSON").action((orgId, opts) => flowsCmd.list(orgId, opts));
|
|
773
|
+
flows.command("show <flow_id>").description("One flow with its full configuration").option("--json", "raw JSON").action((id, opts) => flowsCmd.show(id, opts));
|
|
774
|
+
flows.command("status <organization_id>")
|
|
775
|
+
.description("Execution funnel per status and per flow, with the verbatim error rollup")
|
|
776
|
+
.option("--since <when>", "14d (default) | 7d | 2w | YYYY-MM-DD").option("--flow <id>", "one flow").option("--failed", "also list the failed executions").option("--json", "raw JSON")
|
|
777
|
+
.action((orgId, opts) => flowsCmd.status(orgId, opts));
|
|
778
|
+
flows.command("enable <flow_id>").description("Enable a flow (arms real sends: needs --confirm)").option("--confirm", "required").option("--json", "raw JSON").action((id, opts) => flowsCmd.enable(id, opts));
|
|
779
|
+
flows.command("disable <flow_id>").description("Disable a flow").option("--json", "raw JSON").action((id, opts) => flowsCmd.disable(id, opts));
|
|
780
|
+
|
|
781
|
+
// rules (1 Oct 2026): the Automations rules engine (custom_notification_rules)
|
|
782
|
+
const rules = program.command("rules").description("Automation rules (Shoppers → Rules): list, show, what each has queued, enable / disable");
|
|
783
|
+
rules.command("list <organization_id>").description("Every rule: trigger, template, delay / quiet hours, conditions").option("--trigger <type>", "tag_added | shopify_order_stage | external_webhook | instagram_comment").option("--json", "raw JSON").action((orgId, opts) => rulesCmd.list(orgId, opts));
|
|
784
|
+
rules.command("show <rule_id>").description("One rule in full").option("--json", "raw JSON").action((id, opts) => rulesCmd.show(id, opts));
|
|
785
|
+
rules.command("results <rule_id>").description("What the rule queued (api_request_queue): status counts, errors, recent sends").option("--since <when>", "30d (default) | 7d | YYYY-MM-DD").option("--json", "raw JSON").action((id, opts) => rulesCmd.results(id, opts));
|
|
786
|
+
rules.command("enable <rule_id>").description("Enable a rule (arms real template sends: needs --confirm)").option("--confirm", "required").option("--json", "raw JSON").action((id, opts) => rulesCmd.enable(id, opts));
|
|
787
|
+
rules.command("disable <rule_id>").description("Disable a rule").option("--json", "raw JSON").action((id, opts) => rulesCmd.disable(id, opts));
|
|
788
|
+
|
|
619
789
|
// gaps (CLI gap reports + feature requests, filed by anyone incl. a Claude session)
|
|
620
790
|
const gaps = program.command("gaps")
|
|
621
791
|
.alias("requests")
|
|
@@ -712,7 +882,8 @@ export function run(argv) {
|
|
|
712
882
|
.description("Send one message, print the reply + any tool calls")
|
|
713
883
|
.option("--agent <id>", "test a specific (non-active) agent")
|
|
714
884
|
.option("--clear", "clear the conversation before the run")
|
|
715
|
-
.option("--
|
|
885
|
+
.option("--tools", "also print each tool's arguments and result (what the agent asked and got back)")
|
|
886
|
+
.option("--sender <name>", "the name the agent sees: renames a SYNTHETIC test contact (a real person's contact keeps their name)")
|
|
716
887
|
.option("--image <url>", "attach a publicly-fetchable image URL (text is the caption)")
|
|
717
888
|
.option("--timeout <ms>", "per-turn HTTP timeout (default 120000)")
|
|
718
889
|
.option("--json", "print raw JSON instead of the transcript")
|
|
@@ -721,6 +892,7 @@ export function run(argv) {
|
|
|
721
892
|
test.command("scenario <organization_id> <file>")
|
|
722
893
|
.description("Run a scenario pack (JSON: {scenarios:[{id,title,turns:[{text,expect?,expectNot?}]}]})")
|
|
723
894
|
.option("--agent <id>", "test a specific agent")
|
|
895
|
+
.option("--tools", "also print each tool's arguments and result")
|
|
724
896
|
.option("--no-clear-between", "do NOT clear between scenarios (one long thread)")
|
|
725
897
|
.option("--force", "go ahead even though the test contact had real (non-web) messages in the last 6 h")
|
|
726
898
|
.option("--sender <name>", "senderName")
|
|
@@ -867,6 +1039,7 @@ export function run(argv) {
|
|
|
867
1039
|
.option("--all", "include fired / cancelled / failed rows too")
|
|
868
1040
|
.option("--status <s>", "filter to one status (pending|request|completed|cancelled|failed)")
|
|
869
1041
|
.option("--limit <n>", "max rows (default 25, max 200)")
|
|
1042
|
+
.option("--detail", "also print each row's body / button values and header media (what the queued send will say)")
|
|
870
1043
|
.option("--json", "raw JSON")
|
|
871
1044
|
.action((orgId, opts) => broadcastCmd.scheduledList(orgId, opts));
|
|
872
1045
|
scheduled.command("approve <organization_id> <queue_id>")
|
|
@@ -979,15 +1152,25 @@ export function run(argv) {
|
|
|
979
1152
|
.option("--raw-campaign", "use --campaign exactly as typed, bypassing the Date_Campaign normalisation")
|
|
980
1153
|
.option("--new-campaign", "confirm the tag is a NEW campaign even though a differently spelled one with the same name already runs")
|
|
981
1154
|
.option("--domain <host>", "short domain: chatcart.io (default) | linklnk.io | yapi.store")
|
|
1155
|
+
.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")
|
|
982
1156
|
.option("--commit", "actually mint the links (omit = dry-run preview)")
|
|
983
1157
|
.option("--json", "raw JSON output")
|
|
984
1158
|
.action((orgId, opts) => linksCmd.shorten(orgId, opts));
|
|
985
1159
|
links.command("list <organization_id>")
|
|
986
|
-
.description("List the org's short links, newest first")
|
|
1160
|
+
.description("List the org's short links, newest first; look one up by code or by destination")
|
|
987
1161
|
.option("--campaign <value>", "only links carrying this exact utm_campaign")
|
|
988
|
-
.option("--
|
|
1162
|
+
.option("--code <code>", "one link by its code (the part after the slash)")
|
|
1163
|
+
.option("--to <substr>", "only links whose destination contains this text")
|
|
1164
|
+
.option("--limit <n>", "max rows (default 25, max 500)")
|
|
1165
|
+
.option("--all", "walk every page (500 a call) instead of the first")
|
|
989
1166
|
.option("--json", "raw JSON output")
|
|
990
1167
|
.action((orgId, opts) => linksCmd.list(orgId, opts));
|
|
1168
|
+
links.command("repoint <organization_id> <code>")
|
|
1169
|
+
.description("Move where a live short code sends people (the code, its clicks and every message carrying it stay). Dry-run unless --commit")
|
|
1170
|
+
.requiredOption("--to <url>", "the new destination page")
|
|
1171
|
+
.option("--commit", "write the change (audited with the old destination; undo is printed)")
|
|
1172
|
+
.option("--json", "raw JSON output")
|
|
1173
|
+
.action((orgId, code, opts) => linksCmd.repoint(orgId, code, opts));
|
|
991
1174
|
|
|
992
1175
|
// tag (the Advanced Tagging dialog in the terminal — ONE named tag on a matched set; dry-run default)
|
|
993
1176
|
const tag = program.command("tag")
|
|
@@ -997,11 +1180,28 @@ export function run(argv) {
|
|
|
997
1180
|
.option("--field <name>", "field to search", "order_history")
|
|
998
1181
|
.option("--any <csv>", "match ANY of these comma-separated keywords (OR)")
|
|
999
1182
|
.option("--all <csv>", "match ALL of these comma-separated keywords (AND)")
|
|
1183
|
+
.option("--exact", "match the WHOLE value (whatsapp_id / phone_number / email / full_name), not a substring")
|
|
1000
1184
|
.option("--case-sensitive", "case-sensitive match")
|
|
1001
1185
|
.option("--tag <name>", "tag to apply (required with --commit)")
|
|
1002
1186
|
.option("--commit", "write the tag (omit = dry-run count + sample)")
|
|
1003
1187
|
.option("--yes", "skip the type-the-tag confirm on large (>2000) writes")
|
|
1004
1188
|
.action((orgId, opts) => tagCmd.field(orgId, opts));
|
|
1189
|
+
tag.command("ids <organization_id>")
|
|
1190
|
+
.description("Apply one tag to an explicit list of contact ids (append-only, idempotent; ids outside the org are ignored)")
|
|
1191
|
+
.option("--ids <csv>", "contact UUIDs, comma-separated")
|
|
1192
|
+
.option("--ids-file <path>", "file of contact UUIDs (one per line or comma-separated)")
|
|
1193
|
+
.option("--tag <name>", "tag to apply (required with --commit)")
|
|
1194
|
+
.option("--commit", "write the tag (omit = dry-run count + sample)")
|
|
1195
|
+
.option("--yes", "skip the type-the-tag confirm on large (>2000) writes")
|
|
1196
|
+
.option("--json", "raw JSON")
|
|
1197
|
+
.action((orgId, opts) => tagCmd.ids(orgId, opts));
|
|
1198
|
+
tag.command("holders <organization_id> <tag>")
|
|
1199
|
+
.description("Who carries a tag: name, number, id and the flags that keep them off a broadcast (read-only)")
|
|
1200
|
+
.option("--limit <n>", "rows (default 200, max 1000)")
|
|
1201
|
+
.option("--all", "walk every page")
|
|
1202
|
+
.option("--csv", "CSV to stdout (id, name, number, email, allow_broadcast, archived, blocked)")
|
|
1203
|
+
.option("--json", "raw JSON")
|
|
1204
|
+
.action((orgId, tagName, opts) => tagCmd.holders(orgId, tagName, opts));
|
|
1005
1205
|
tag.command("cohort <organization_id>")
|
|
1006
1206
|
.description("Tag a ranked cohort (top-N). --type top_spenders|frequent_buyers|recent_buyers|high_value_low_frequency")
|
|
1007
1207
|
.option("--type <t>", "cohort type", "top_spenders")
|
|
@@ -1055,10 +1255,90 @@ export function run(argv) {
|
|
|
1055
1255
|
.option("--json", "raw JSON")
|
|
1056
1256
|
.action((orgId, opts) => tagCmd.list(orgId, opts));
|
|
1057
1257
|
tag.command("remove <organization_id> <tags>")
|
|
1058
|
-
.description("Remove tag(s) (comma-separated) from ALL contacts in the org. Destructive — needs --confirm")
|
|
1258
|
+
.description("Remove tag(s) (comma-separated) from ALL contacts in the org, or only from --contacts. Destructive — needs --confirm")
|
|
1259
|
+
.option("--contacts <csv>", "only these contact UUIDs lose the tag(s)")
|
|
1260
|
+
.option("--contacts-file <path>", "file of contact UUIDs that lose the tag(s)")
|
|
1059
1261
|
.option("--confirm", "actually remove (omit = dry-run)")
|
|
1060
1262
|
.action((orgId, tags, opts) => tagCmd.remove(orgId, tags, opts));
|
|
1061
1263
|
|
|
1264
|
+
// members / users (organization_members + profiles + auth.users)
|
|
1265
|
+
const members = program.command("members").description("An org's people: list, add an existing account, change a role, rename, remove");
|
|
1266
|
+
members.command("list <organization_id>")
|
|
1267
|
+
.description("Members with role, name, email, last sign-in; pending invitations")
|
|
1268
|
+
.option("--json", "raw JSON")
|
|
1269
|
+
.action((orgId, opts) => membersCmd.list(orgId, opts));
|
|
1270
|
+
members.command("add <organization_id> <email>")
|
|
1271
|
+
.description("Add an EXISTING account to the org (someone with no account must be invited from the app)")
|
|
1272
|
+
.option("--role <role>", "admin | member | viewer", "member")
|
|
1273
|
+
.option("--json", "raw JSON")
|
|
1274
|
+
.action((orgId, email, opts) => membersCmd.add(orgId, email, opts));
|
|
1275
|
+
members.command("role <organization_id> <email> <role>")
|
|
1276
|
+
.description("Change a member's role: admin | member | viewer (the last admin cannot be demoted)")
|
|
1277
|
+
.option("--json", "raw JSON")
|
|
1278
|
+
.action((orgId, email, role, opts) => membersCmd.role(orgId, email, role, opts));
|
|
1279
|
+
members.command("rename <email> <full_name>")
|
|
1280
|
+
.description("Set the person's display name (profiles.full_name, shown in every org)")
|
|
1281
|
+
.option("--json", "raw JSON")
|
|
1282
|
+
.action((email, name, opts) => membersCmd.rename(email, name, opts));
|
|
1283
|
+
members.command("remove <organization_id> <email>")
|
|
1284
|
+
.description("Remove a member from the org (dry-run unless --confirm; the owner and the last admin are refused)")
|
|
1285
|
+
.option("--confirm", "actually remove")
|
|
1286
|
+
.option("--json", "raw JSON")
|
|
1287
|
+
.action((orgId, email, opts) => membersCmd.remove(orgId, email, opts));
|
|
1288
|
+
const users = program.command("users").description("One user across orgs");
|
|
1289
|
+
users.command("lookup <email>")
|
|
1290
|
+
.description("The account behind an email: sign-in method, confirmation, last sign-in, every org and role")
|
|
1291
|
+
.option("--json", "raw JSON")
|
|
1292
|
+
.action((email, opts) => membersCmd.lookup(email, opts));
|
|
1293
|
+
|
|
1294
|
+
// steer / notes (internal rows in a contact's chat)
|
|
1295
|
+
const steer = program.command("steer").description("Supervisor steers for the agent on one contact: list, add, clear");
|
|
1296
|
+
steer.command("list <contact_id>")
|
|
1297
|
+
.description("Every steer on the contact (mode, consumed, active)")
|
|
1298
|
+
.option("--limit <n>", "rows (default 50)")
|
|
1299
|
+
.option("--json", "raw JSON")
|
|
1300
|
+
.action((contactId, opts) => notesCmd.steerList(contactId, opts));
|
|
1301
|
+
steer.command("add <contact_id> <text...>")
|
|
1302
|
+
.description("Add a steer the agent applies on EVERY reply (ongoing), or --once for the next reply only")
|
|
1303
|
+
.option("--once", "next reply only")
|
|
1304
|
+
.option("--json", "raw JSON")
|
|
1305
|
+
.action((contactId, text, opts) => notesCmd.steerAdd(contactId, text, opts));
|
|
1306
|
+
steer.command("clear <contact_id>")
|
|
1307
|
+
.description("Delete the contact's steers (all, or --id); the inbox has no delete for an ongoing steer. Dry-run unless --confirm")
|
|
1308
|
+
.option("--id <steer_id>", "only this steer (repeatable)", (v, acc) => (acc || []).concat([v]), [])
|
|
1309
|
+
.option("--confirm", "actually delete")
|
|
1310
|
+
.option("--json", "raw JSON")
|
|
1311
|
+
.action((contactId, opts) => notesCmd.steerClear(contactId, opts));
|
|
1312
|
+
const notes = program.command("notes").description("Internal notes on a contact (the Customer panel's sticky notes)");
|
|
1313
|
+
notes.command("list <contact_id>")
|
|
1314
|
+
.description("Every internal note on the contact")
|
|
1315
|
+
.option("--limit <n>", "rows (default 50)")
|
|
1316
|
+
.option("--json", "raw JSON")
|
|
1317
|
+
.action((contactId, opts) => notesCmd.notesList(contactId, opts));
|
|
1318
|
+
notes.command("add <contact_id> <text...>")
|
|
1319
|
+
.description("Add an internal note (team-only, never sent to the customer)")
|
|
1320
|
+
.option("--json", "raw JSON")
|
|
1321
|
+
.action((contactId, text, opts) => notesCmd.notesAdd(contactId, text, opts));
|
|
1322
|
+
|
|
1323
|
+
// products (the synced catalogue)
|
|
1324
|
+
const products = program.command("products").description("The synced catalogue as the agent sees it: status, a search replay, a one-org reconcile");
|
|
1325
|
+
products.command("status <organization_id>")
|
|
1326
|
+
.description("Row counts by status, embeddings (missing = the agent cannot find them), images, prices, stock, the last syncs")
|
|
1327
|
+
.option("--json", "raw JSON")
|
|
1328
|
+
.action((orgId, opts) => productsCmd.status(orgId, opts));
|
|
1329
|
+
products.command("search <organization_id> <text...>")
|
|
1330
|
+
.description("Replay the agent's product_lookup match (fuzzy_match_products) for a phrase")
|
|
1331
|
+
.option("--limit <n>", "matches (default 8, max 50)")
|
|
1332
|
+
.option("--min-similarity <x>", "pg_trgm threshold (default 0.15)")
|
|
1333
|
+
.option("--json", "raw JSON")
|
|
1334
|
+
.action((orgId, text, opts) => productsCmd.search(orgId, text, opts));
|
|
1335
|
+
products.command("reconcile <organization_id>")
|
|
1336
|
+
.description("Trigger products-reconcile-scheduler for this org only (re-sync + re-embed). Dry-run unless --confirm")
|
|
1337
|
+
.option("--platform <p>", "shopify | woo (default: the org's connected store)")
|
|
1338
|
+
.option("--confirm", "actually trigger")
|
|
1339
|
+
.option("--json", "raw JSON")
|
|
1340
|
+
.action((orgId, opts) => productsCmd.reconcile(orgId, opts));
|
|
1341
|
+
|
|
1062
1342
|
// keywords (org auto-reply engine: keywords + keyword_actions; id-keyed diff push)
|
|
1063
1343
|
const keywords = program.command("keywords")
|
|
1064
1344
|
.alias("kw")
|