@flowapt/flowiq-cli 0.10.0 → 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 +75 -2
- package/TEAM-GUIDE.md +13 -1
- package/package.json +1 -1
- 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/links.js +63 -20
- package/src/commands/members.js +95 -0
- package/src/commands/messages.js +39 -0
- package/src/commands/notes.js +63 -0
- package/src/commands/org.js +55 -6
- package/src/commands/products.js +58 -0
- package/src/commands/prompts.js +46 -1
- package/src/commands/segments.js +47 -14
- package/src/commands/tag.js +77 -7
- package/src/commands/templates.js +66 -5
- package/src/index.js +147 -6
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")
|
|
@@ -158,6 +162,13 @@ export function run(argv) {
|
|
|
158
162
|
.option("--until <when>", "upper bound (default now)")
|
|
159
163
|
.option("--json", "raw JSON")
|
|
160
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));
|
|
161
172
|
|
|
162
173
|
// fine-tuning (agents.fine_tuning text + agents.fine_tuning_entries jsonb)
|
|
163
174
|
const ft = program.command("fine-tuning")
|
|
@@ -428,10 +439,23 @@ export function run(argv) {
|
|
|
428
439
|
// org (read-only org summary for the prompt-builder skill, creds stripped)
|
|
429
440
|
const org = program.command("org").description("Org info (read), create a new organization, read/set its feature flags");
|
|
430
441
|
org.command("list [search]")
|
|
431
|
-
.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)")
|
|
432
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")
|
|
433
445
|
.option("--json", "raw JSON")
|
|
434
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));
|
|
435
459
|
org.command("create")
|
|
436
460
|
.description("Create a NEW organization (org row + admin membership; owner defaults to you)")
|
|
437
461
|
.requiredOption("--name <name>", "organization name")
|
|
@@ -558,6 +582,13 @@ export function run(argv) {
|
|
|
558
582
|
.description("List the org's agents (★ = active); use an id as --agent <id> elsewhere")
|
|
559
583
|
.option("--json", "print the raw JSON payload")
|
|
560
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));
|
|
561
592
|
agent.command("create <organization_id>")
|
|
562
593
|
.description("Create a new agent (optionally seed its prompt + set it active)")
|
|
563
594
|
.requiredOption("--name <name>", "agent name")
|
|
@@ -851,7 +882,8 @@ export function run(argv) {
|
|
|
851
882
|
.description("Send one message, print the reply + any tool calls")
|
|
852
883
|
.option("--agent <id>", "test a specific (non-active) agent")
|
|
853
884
|
.option("--clear", "clear the conversation before the run")
|
|
854
|
-
.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)")
|
|
855
887
|
.option("--image <url>", "attach a publicly-fetchable image URL (text is the caption)")
|
|
856
888
|
.option("--timeout <ms>", "per-turn HTTP timeout (default 120000)")
|
|
857
889
|
.option("--json", "print raw JSON instead of the transcript")
|
|
@@ -860,6 +892,7 @@ export function run(argv) {
|
|
|
860
892
|
test.command("scenario <organization_id> <file>")
|
|
861
893
|
.description("Run a scenario pack (JSON: {scenarios:[{id,title,turns:[{text,expect?,expectNot?}]}]})")
|
|
862
894
|
.option("--agent <id>", "test a specific agent")
|
|
895
|
+
.option("--tools", "also print each tool's arguments and result")
|
|
863
896
|
.option("--no-clear-between", "do NOT clear between scenarios (one long thread)")
|
|
864
897
|
.option("--force", "go ahead even though the test contact had real (non-web) messages in the last 6 h")
|
|
865
898
|
.option("--sender <name>", "senderName")
|
|
@@ -1006,6 +1039,7 @@ export function run(argv) {
|
|
|
1006
1039
|
.option("--all", "include fired / cancelled / failed rows too")
|
|
1007
1040
|
.option("--status <s>", "filter to one status (pending|request|completed|cancelled|failed)")
|
|
1008
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)")
|
|
1009
1043
|
.option("--json", "raw JSON")
|
|
1010
1044
|
.action((orgId, opts) => broadcastCmd.scheduledList(orgId, opts));
|
|
1011
1045
|
scheduled.command("approve <organization_id> <queue_id>")
|
|
@@ -1118,15 +1152,25 @@ export function run(argv) {
|
|
|
1118
1152
|
.option("--raw-campaign", "use --campaign exactly as typed, bypassing the Date_Campaign normalisation")
|
|
1119
1153
|
.option("--new-campaign", "confirm the tag is a NEW campaign even though a differently spelled one with the same name already runs")
|
|
1120
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")
|
|
1121
1156
|
.option("--commit", "actually mint the links (omit = dry-run preview)")
|
|
1122
1157
|
.option("--json", "raw JSON output")
|
|
1123
1158
|
.action((orgId, opts) => linksCmd.shorten(orgId, opts));
|
|
1124
1159
|
links.command("list <organization_id>")
|
|
1125
|
-
.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")
|
|
1126
1161
|
.option("--campaign <value>", "only links carrying this exact utm_campaign")
|
|
1127
|
-
.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")
|
|
1128
1166
|
.option("--json", "raw JSON output")
|
|
1129
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));
|
|
1130
1174
|
|
|
1131
1175
|
// tag (the Advanced Tagging dialog in the terminal — ONE named tag on a matched set; dry-run default)
|
|
1132
1176
|
const tag = program.command("tag")
|
|
@@ -1136,11 +1180,28 @@ export function run(argv) {
|
|
|
1136
1180
|
.option("--field <name>", "field to search", "order_history")
|
|
1137
1181
|
.option("--any <csv>", "match ANY of these comma-separated keywords (OR)")
|
|
1138
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")
|
|
1139
1184
|
.option("--case-sensitive", "case-sensitive match")
|
|
1140
1185
|
.option("--tag <name>", "tag to apply (required with --commit)")
|
|
1141
1186
|
.option("--commit", "write the tag (omit = dry-run count + sample)")
|
|
1142
1187
|
.option("--yes", "skip the type-the-tag confirm on large (>2000) writes")
|
|
1143
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));
|
|
1144
1205
|
tag.command("cohort <organization_id>")
|
|
1145
1206
|
.description("Tag a ranked cohort (top-N). --type top_spenders|frequent_buyers|recent_buyers|high_value_low_frequency")
|
|
1146
1207
|
.option("--type <t>", "cohort type", "top_spenders")
|
|
@@ -1194,10 +1255,90 @@ export function run(argv) {
|
|
|
1194
1255
|
.option("--json", "raw JSON")
|
|
1195
1256
|
.action((orgId, opts) => tagCmd.list(orgId, opts));
|
|
1196
1257
|
tag.command("remove <organization_id> <tags>")
|
|
1197
|
-
.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)")
|
|
1198
1261
|
.option("--confirm", "actually remove (omit = dry-run)")
|
|
1199
1262
|
.action((orgId, tags, opts) => tagCmd.remove(orgId, tags, opts));
|
|
1200
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
|
+
|
|
1201
1342
|
// keywords (org auto-reply engine: keywords + keyword_actions; id-keyed diff push)
|
|
1202
1343
|
const keywords = program.command("keywords")
|
|
1203
1344
|
.alias("kw")
|