@flowapt/flowiq-cli 0.3.1 → 0.3.2
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 +15 -5
- package/TEAM-GUIDE.md +1 -0
- package/package.json +1 -1
- package/src/commands/segments.js +21 -8
- package/src/index.js +2 -0
package/README.md
CHANGED
|
@@ -238,6 +238,14 @@ flowiq seg plan <org_id> --tag-prefix bcast --from-attribute allow_broadcast_tru
|
|
|
238
238
|
# as `tag attributes --filter`). --split N (2..100) replaces --batch-size;
|
|
239
239
|
# --seed makes the shuffle reproducible, --ordered keeps server order (no shuffle).
|
|
240
240
|
|
|
241
|
+
# Cohort from EXISTING TAGS → batched (v0.3.2 — combine tags, then split, no id file):
|
|
242
|
+
flowiq seg plan <org_id> --tag-prefix 18-july-clearance-bc \
|
|
243
|
+
--from-tag "9-jul-loyalty-list" --exclude "recent-campaign,opt-out" --batch-size 1000
|
|
244
|
+
# contacts carrying ANY of the --from-tag tags, MINUS anyone carrying any --exclude
|
|
245
|
+
# tag, minus archived/blocked/opted-out → sliced into 1000-sized batch tags
|
|
246
|
+
# (18-july-clearance-bc-batch-01, -02, …). --from-tag pairs with --split too.
|
|
247
|
+
# This is the "existing tags → batched send list" flow, entirely in the CLI.
|
|
248
|
+
|
|
241
249
|
# Cohort by ORDER-COUNT CRITERIA (v0.2.6 — server-resolved, no id file needed):
|
|
242
250
|
flowiq seg plan <org_id> --tag-prefix repeat-60d --min-orders 2 --window 60d
|
|
243
251
|
# "everyone with 2+ orders in the last 60 days" — counted from the captured
|
|
@@ -262,11 +270,13 @@ flowiq seg untag <org_id> repeat-60d --commit --confirm # ROLLBACK (its own ta
|
|
|
262
270
|
|
|
263
271
|
- **Cohort sources:** `--from-attribute <filter>` (a broadcast-permission
|
|
264
272
|
audience — `all_contacts` / `allow_broadcast_true` / `allow_broadcast_false`
|
|
265
|
-
/ `no_broadcast_permission`, resolved server-side
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
273
|
+
/ `no_broadcast_permission`, resolved server-side in one RPC call);
|
|
274
|
+
**`--from-tag "a,b" [--exclude "c,d"]`** (contacts carrying ANY of the include
|
|
275
|
+
tags minus anyone carrying an exclude tag — the "existing tags → batched send
|
|
276
|
+
list" source, v0.3.2); `--min-orders [--window]` (the windowed order count the
|
|
277
|
+
app's Advanced Tagging UI cannot express — its Min Orders filter is
|
|
278
|
+
lifetime-only); `--bought` (product line-items); or explicit **contact UUIDs**
|
|
279
|
+
(a snapshot JSON's `contact_ids[]` or a plain file). Exactly one per plan.
|
|
270
280
|
- **`--split N` = exactly N even cohorts** (2..100), an alternative to
|
|
271
281
|
`--batch-size` fixed chunks. It **shuffles by default** so the cohorts are
|
|
272
282
|
balanced (not skewed by contact age / signup order); `--seed S` makes the
|
package/TEAM-GUIDE.md
CHANGED
|
@@ -88,6 +88,7 @@ When you see it, run `npm i -g @flowapt/flowiq-cli` — a stale version also mea
|
|
|
88
88
|
| Advanced Tagging in the terminal (one named tag on a matched set) | `flowiq tag field\|cohort\|segment\|attributes\|messages <org_id> …` (dry-run) → add `--tag <name> --commit` |
|
|
89
89
|
| List / remove tags | `flowiq tag list <org_id>` · `flowiq tag remove <org_id> <tag> --confirm` |
|
|
90
90
|
| Split your whole broadcast list into N even cohorts (e.g. 3 for A/B/C or waves) | `flowiq seg plan <org_id> --tag-prefix bcast --from-attribute allow_broadcast_true --split 3` → `flowiq seg apply <org_id> bcast --commit` (makes `bcast-batch-01/02/03`, ~even, shuffled) |
|
|
91
|
+
| Combine existing tags → a batched send list (include some tags, drop others, split into batches of N) | `flowiq seg plan <org_id> --tag-prefix clearance-bc --from-tag "loyalty-list" --exclude "recent-campaign" --batch-size 1000` → `flowiq seg apply <org_id> clearance-bc --commit` (makes `clearance-bc-batch-01/02/…`) |
|
|
91
92
|
| Split a big id-list cohort into send-safe batch tags | `flowiq seg plan <org_id> --tag-prefix … --ids-file …` → `flowiq seg apply … --commit` |
|
|
92
93
|
| Send to one batch tag | `flowiq bc send <org_id> --tag <batch-tag> --template … --body param1="Hi {{first_name}}" --commit` |
|
|
93
94
|
| Read a contact's chat | `flowiq m pull <contact_id>` then open the JSON |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowapt/flowiq-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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": {
|
package/src/commands/segments.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// runbook). This command TAGS ONLY — it never sends. The send per tag is
|
|
4
4
|
// `flowiq broadcast send --tag <batch-tag>` (or the dashboard broadcaster).
|
|
5
5
|
//
|
|
6
|
-
// plan <org> --tag-prefix X (--from-
|
|
7
|
-
// | --min-orders N [--window 60d]
|
|
6
|
+
// plan <org> --tag-prefix X (--from-attribute F | --from-tag "a,b" [--exclude "c,d"]
|
|
7
|
+
// | --min-orders N [--window 60d] | --bought "P"
|
|
8
|
+
// | --from-segment file | --ids-file file) # exclusions → slice → plan file (no DB write)
|
|
8
9
|
// apply <org> <slug> [--commit] [--yes] # append the batch tags (dry-run default)
|
|
9
10
|
// list <org> [--prefix X] # VERIFY: tag → contact count
|
|
10
11
|
// untag <org> <slug> --commit --confirm # ROLLBACK: remove the plan's own tags
|
|
@@ -132,18 +133,20 @@ export async function plan(orgId, opts = {}) {
|
|
|
132
133
|
if (opts.seed !== undefined && !Number.isFinite(Number(opts.seed))) { console.error("Error: --seed must be a number."); process.exit(1); }
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
// Cohort source: a server-resolved criterion (--from-attribute / --
|
|
136
|
-
// / --bought) OR an explicit id list (--from-segment / --ids-file).
|
|
136
|
+
// Cohort source: a server-resolved criterion (--from-attribute / --from-tag /
|
|
137
|
+
// --min-orders / --bought) OR an explicit id list (--from-segment / --ids-file).
|
|
138
|
+
// Exactly one.
|
|
137
139
|
const orderMode = opts.minOrders !== undefined;
|
|
138
140
|
const productMode = opts.bought !== undefined;
|
|
139
141
|
const attributeMode = opts.fromAttribute !== undefined;
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
+
const tagMode = opts.fromTag !== undefined;
|
|
143
|
+
const criteriaMode = orderMode || productMode || attributeMode || tagMode;
|
|
144
|
+
const sourceCount = [orderMode, productMode, attributeMode, tagMode, !!opts.fromSegment, !!opts.idsFile].filter(Boolean).length;
|
|
142
145
|
if (sourceCount === 0) {
|
|
143
|
-
console.error("Error: a cohort source is required — one of --from-attribute / --min-orders / --bought / --from-segment / --ids-file."); process.exit(1);
|
|
146
|
+
console.error("Error: a cohort source is required — one of --from-attribute / --from-tag / --min-orders / --bought / --from-segment / --ids-file."); process.exit(1);
|
|
144
147
|
}
|
|
145
148
|
if (sourceCount > 1) {
|
|
146
|
-
console.error("Error: pick exactly ONE cohort source (--from-attribute / --min-orders / --bought / --from-segment / --ids-file)."); process.exit(1);
|
|
149
|
+
console.error("Error: pick exactly ONE cohort source (--from-attribute / --from-tag / --min-orders / --bought / --from-segment / --ids-file)."); process.exit(1);
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
let cohort;
|
|
@@ -157,6 +160,12 @@ export async function plan(orgId, opts = {}) {
|
|
|
157
160
|
}
|
|
158
161
|
cohortSpec = { attribute };
|
|
159
162
|
cohort = { ids: [], badUuids: [], sourceMeta: { type: "attribute_cohort", attribute } };
|
|
163
|
+
} else if (tagMode) {
|
|
164
|
+
const includeTags = String(opts.fromTag).split(",").map((s) => s.trim()).filter(Boolean);
|
|
165
|
+
if (!includeTags.length) { console.error("Error: --from-tag needs at least one tag (comma-separate several)."); process.exit(1); }
|
|
166
|
+
const excludeTags = opts.exclude ? String(opts.exclude).split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
167
|
+
cohortSpec = { include_tags: includeTags, exclude_tags: excludeTags };
|
|
168
|
+
cohort = { ids: [], badUuids: [], sourceMeta: { type: "tag_cohort", include_tags: includeTags, exclude_tags: excludeTags } };
|
|
160
169
|
} else {
|
|
161
170
|
let windowDays = null;
|
|
162
171
|
if (opts.window) {
|
|
@@ -209,6 +218,10 @@ export async function plan(orgId, opts = {}) {
|
|
|
209
218
|
no_broadcast_permission: "contacts with no broadcast permission set",
|
|
210
219
|
}[resp.cohort.attribute] || resp.cohort.attribute;
|
|
211
220
|
console.log(`Cohort: ${label} → ${resp.cohort.resolved_count} contact(s) (server-resolved).`);
|
|
221
|
+
} else if (resp.cohort.kind === "tag") {
|
|
222
|
+
const inc = resp.cohort.include_tags.join(", ");
|
|
223
|
+
const exc = resp.cohort.exclude_tags && resp.cohort.exclude_tags.length ? `, excluding [${resp.cohort.exclude_tags.join(", ")}]` : "";
|
|
224
|
+
console.log(`Cohort: contacts tagged any of [${inc}]${exc} → ${resp.cohort.resolved_count} contact(s) (server-resolved).`);
|
|
212
225
|
} else {
|
|
213
226
|
const w = resp.cohort.window_days ? `in the last ${resp.cohort.window_days} days` : "across all captured history";
|
|
214
227
|
if (resp.cohort.kind === "product") {
|
package/src/index.js
CHANGED
|
@@ -421,6 +421,8 @@ export function run(argv) {
|
|
|
421
421
|
.option("--from-segment <path>", "a segments snapshot JSON (uses its contact_ids[])")
|
|
422
422
|
.option("--ids-file <path>", "a plain newline/CSV file of contact UUIDs")
|
|
423
423
|
.option("--from-attribute <filter>", "SERVER-RESOLVED cohort: every contact matching a broadcast-permission filter (all_contacts | allow_broadcast_true | allow_broadcast_false | no_broadcast_permission)")
|
|
424
|
+
.option("--from-tag <csv>", "SERVER-RESOLVED cohort: contacts carrying ANY of these tag(s) (comma-separated)")
|
|
425
|
+
.option("--exclude <csv>", "with --from-tag: drop contacts carrying ANY of these tag(s)")
|
|
424
426
|
.option("--min-orders <n>", "SERVER-RESOLVED cohort: contacts with ≥ n captured orders (instead of an id file)")
|
|
425
427
|
.option("--bought <terms>", "SERVER-RESOLVED cohort: contacts who bought these product(s) (comma-separated name substrings) from real order line-items")
|
|
426
428
|
.option("--match <mode>", "with --bought: any (bought any listed product) | all (bought every one)", "any")
|