@flowapt/flowiq-cli 0.1.5 → 0.1.7
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 +167 -3
- package/bin/flowiq.js +0 -0
- package/package.json +4 -3
- package/src/commands/agent-config.js +95 -0
- package/src/commands/agent-test.js +232 -0
- package/src/commands/agent-updates.js +186 -0
- package/src/commands/export.js +131 -0
- package/src/commands/flowmod.js +130 -0
- package/src/commands/groups.js +329 -0
- package/src/commands/org.js +33 -0
- package/src/commands/pinboard.js +174 -0
- package/src/commands/templates.js +156 -0
- package/src/data/scenarios.qa.json +41 -0
- package/src/index.js +171 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Generic agent smoke pack (org-agnostic)",
|
|
3
|
+
"note": "Universal FlowIQ-agent behaviours. Assertion-light on purpose — replies are dumped for inspection since exact copy varies per org. Safe to run against any org via `qa <org_id>`. For org-specific assertions, author a pack and run it with `scenario <org_id> <file>`.",
|
|
4
|
+
"scenarios": [
|
|
5
|
+
{
|
|
6
|
+
"id": "greeting",
|
|
7
|
+
"title": "Greeting / warmth",
|
|
8
|
+
"turns": [
|
|
9
|
+
{ "text": "Hi there, how are you doing today?", "expectNot": ["👉"] }
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "off-topic",
|
|
14
|
+
"title": "Off-topic request (should decline in one message)",
|
|
15
|
+
"turns": [
|
|
16
|
+
{ "text": "What's the weather like today?" }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "product-basic",
|
|
21
|
+
"title": "Basic product question",
|
|
22
|
+
"turns": [
|
|
23
|
+
{ "text": "What are your best-selling products?" }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "human-handover",
|
|
28
|
+
"title": "Request a human",
|
|
29
|
+
"turns": [
|
|
30
|
+
{ "text": "This isn't helping. Can I please speak to a real person?" }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "rating-reply",
|
|
35
|
+
"title": "End-of-chat rating reply",
|
|
36
|
+
"turns": [
|
|
37
|
+
{ "text": "I'd rate this chat a 3 out of 5." }
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,15 @@ import * as messagesCmd from "./commands/messages.js";
|
|
|
12
12
|
import * as fineTuningCmd from "./commands/fine-tuning.js";
|
|
13
13
|
import * as messagingWebhooksCmd from "./commands/messaging-webhooks.js";
|
|
14
14
|
import * as webhooksCmd from "./commands/webhooks.js";
|
|
15
|
+
import * as flowmodCmd from "./commands/flowmod.js";
|
|
16
|
+
import * as groupsCmd from "./commands/groups.js";
|
|
17
|
+
import * as pinboardCmd from "./commands/pinboard.js";
|
|
18
|
+
import * as templatesCmd from "./commands/templates.js";
|
|
19
|
+
import * as orgCmd from "./commands/org.js";
|
|
20
|
+
import * as agentConfigCmd from "./commands/agent-config.js";
|
|
21
|
+
import * as agentUpdatesCmd from "./commands/agent-updates.js";
|
|
22
|
+
import * as exportCmd from "./commands/export.js";
|
|
23
|
+
import * as testCmd from "./commands/agent-test.js";
|
|
15
24
|
|
|
16
25
|
// Read version from package.json so it stays in sync with the published npm
|
|
17
26
|
// version automatically (single source of truth — bumping package.json on each
|
|
@@ -103,5 +112,167 @@ export function run(argv) {
|
|
|
103
112
|
.description("WooCommerce-only: flip a webhook back to status=active (WC auto-disables after delivery failures)")
|
|
104
113
|
.action((id, webhookId) => webhooksCmd.reconnect(id, webhookId));
|
|
105
114
|
|
|
115
|
+
// flowmod (FlowMod master-group prompts + config: flowmod_master_groups + flowmod_prompts)
|
|
116
|
+
const flowmod = program.command("flowmod")
|
|
117
|
+
.alias("fm")
|
|
118
|
+
.description("Round-trip a FlowMod org's master-group prompts + config (flowmod_master_groups + flowmod_prompts)");
|
|
119
|
+
flowmod.command("pull <organization_id>")
|
|
120
|
+
.description("Fetch all master_groups + their flowmod_prompts into a local JSON file")
|
|
121
|
+
.action((orgId) => flowmodCmd.pull(orgId));
|
|
122
|
+
flowmod.command("push <slug-or-path>")
|
|
123
|
+
.description("Apply a local JSON file's master_group + prompt edits (text, config, pre_filter_prompt, signatures)")
|
|
124
|
+
.action((id) => flowmodCmd.push(id));
|
|
125
|
+
|
|
126
|
+
// groups (read-only: pull WhatsApp GROUP chat history from the FlowMod Evolution DB)
|
|
127
|
+
const groups = program.command("groups")
|
|
128
|
+
.alias("grp")
|
|
129
|
+
.description("Read WhatsApp group chat history from the FlowMod Evolution DB (set FLOWMOD_EVO_DB_URL env). Read-only.");
|
|
130
|
+
groups.command("list")
|
|
131
|
+
.description("List an Evolution instance's group chats with message counts + last activity")
|
|
132
|
+
.option("--instance <name>", "Evolution instance name", "flowapt")
|
|
133
|
+
.action((opts) => groupsCmd.list(opts));
|
|
134
|
+
groups.command("pull <name-or-jid>")
|
|
135
|
+
.description("Pull a group's messages in a time window into a local JSON file + print a timeline")
|
|
136
|
+
.option("--instance <name>", "Evolution instance name", "flowapt")
|
|
137
|
+
.option("--last <window>", "rolling window back from now, e.g. 48h, 7d, 2w, 90m", "48h")
|
|
138
|
+
.option("--since <date>", "window start (SAST), YYYY-MM-DD or \"YYYY-MM-DD HH:MM\" (overrides --last)")
|
|
139
|
+
.option("--until <date>", "window end (SAST), defaults to now")
|
|
140
|
+
.option("--limit <n>", "max messages to keep (most-recent in window)", String(1000))
|
|
141
|
+
.option("--json", "print the full JSON payload to stdout instead of the timeline")
|
|
142
|
+
.action((nameOrJid, opts) => groupsCmd.pull(nameOrJid, opts));
|
|
143
|
+
|
|
144
|
+
// pinboard (one flowapt_tasks row: Pin Board team task board)
|
|
145
|
+
const pinboard = program.command("pinboard")
|
|
146
|
+
.alias("pin")
|
|
147
|
+
.description("Round-trip a single Pin Board task (flowapt_tasks row)");
|
|
148
|
+
pinboard.command("pull <task_id>")
|
|
149
|
+
.description("Fetch one task into a local JSON file")
|
|
150
|
+
.action((taskId) => pinboardCmd.pull(taskId));
|
|
151
|
+
pinboard.command("push <slug-or-path>")
|
|
152
|
+
.description("Apply a local JSON file's editable fields back to the task row (stamps updated_at)")
|
|
153
|
+
.action((id) => pinboardCmd.push(id));
|
|
154
|
+
pinboard.command("list")
|
|
155
|
+
.description("List local task JSON files")
|
|
156
|
+
.action(() => pinboardCmd.list());
|
|
157
|
+
pinboard.command("list-remote [status]")
|
|
158
|
+
.description("List every task in the DB (optionally filter: open | in_progress | done) to find an id")
|
|
159
|
+
.action((status) => pinboardCmd.listRemote(status));
|
|
160
|
+
|
|
161
|
+
// templates (read WhatsApp templates from Meta; create via /cli/meta-templates)
|
|
162
|
+
const templates = program.command("templates")
|
|
163
|
+
.alias("tpl")
|
|
164
|
+
.description("Read an org's WhatsApp templates from Meta (pull/list); create + submit to Meta (create/status)");
|
|
165
|
+
templates.command("pull <organization_id>")
|
|
166
|
+
.description("Fetch every live WhatsApp template from Meta into a local JSON snapshot")
|
|
167
|
+
.action((orgId) => templatesCmd.pull(orgId));
|
|
168
|
+
templates.command("list")
|
|
169
|
+
.description("List local template snapshots")
|
|
170
|
+
.action(() => templatesCmd.list());
|
|
171
|
+
templates.command("create [organization_id]")
|
|
172
|
+
.description("Submit a new WhatsApp template to Meta via the create-meta-template edge fn")
|
|
173
|
+
.requiredOption("--request-file <path>", "JSON file with template_request (+ optional template_data/media_header/cards_media)")
|
|
174
|
+
.action((orgId, opts) => templatesCmd.create(orgId, opts));
|
|
175
|
+
templates.command("status <organization_id>")
|
|
176
|
+
.description("List the org's template rows (name + status) to poll Meta approval")
|
|
177
|
+
.option("--name <substr>", "filter by template name substring")
|
|
178
|
+
.action((orgId, opts) => templatesCmd.status(orgId, opts));
|
|
179
|
+
|
|
180
|
+
// org (read-only org summary for the prompt-builder skill, creds stripped)
|
|
181
|
+
const org = program.command("org").description("Read-only org info (platform detect + active agent)");
|
|
182
|
+
org.command("info <organization_id>")
|
|
183
|
+
.description("Show the org's platform, storefront url + active agent (raw creds stripped)")
|
|
184
|
+
.option("--json", "print the raw JSON payload")
|
|
185
|
+
.action((orgId, opts) => orgCmd.info(orgId, opts));
|
|
186
|
+
|
|
187
|
+
// agent (agent-level config writes: the prompt-builder skill's Step 5)
|
|
188
|
+
const agent = program.command("agent").description("Configure the org's active agent");
|
|
189
|
+
agent.command("config <organization_id>")
|
|
190
|
+
.description("Set allowlisted agent config (no flags = show current)")
|
|
191
|
+
.option("--use-settings-prompt [bool]", "settings.use_settings_prompt (true if bare)")
|
|
192
|
+
.option("--model <model>", "settings.model (e.g. gpt-5.4-mini)")
|
|
193
|
+
.option("--rename <name>", "rename the agent")
|
|
194
|
+
.option("--tool <flag=bool>", "toggle a tool flag (repeatable): woo_order_build/woo_tip_field/woo_order_note_field/view_cart_tool/restock_tool/block_tool_status/postal_code_tool_status/shopify_products_web_chat", agentConfigCmd.collectTool, [])
|
|
195
|
+
.option("--discount [bool]", "agent.discount.enabled (true if bare)")
|
|
196
|
+
.action((orgId, opts) => agentConfigCmd.config(orgId, opts));
|
|
197
|
+
|
|
198
|
+
// agent-updates (pending client change-requests + chat context; read-only)
|
|
199
|
+
const au = program.command("agent-updates")
|
|
200
|
+
.alias("au")
|
|
201
|
+
.description("Pull pending agent_updates + the chat context around each (read-only)");
|
|
202
|
+
au.command("pull <organization_id>")
|
|
203
|
+
.description("Fetch updates + linked contact + trigger-message window; downloads attachments locally")
|
|
204
|
+
.option("--status <status>", "pending (default) | resolved | in_progress | all")
|
|
205
|
+
.option("--titles <list>", "only updates whose title contains any of these (comma-separated, ci substring)")
|
|
206
|
+
.option("--before <n>", "context messages before the trigger (default 12)")
|
|
207
|
+
.option("--after <n>", "context messages after the trigger (default 8)")
|
|
208
|
+
.option("--recent <n>", "fallback window when no linked message but a contact exists (default 20)")
|
|
209
|
+
.option("--no-files", "do NOT download attachments (default: download is ON)")
|
|
210
|
+
.option("--max-mb <n>", "skip attachments larger than N MB (default 25)")
|
|
211
|
+
.action((orgId, opts) => agentUpdatesCmd.pull(orgId, opts));
|
|
212
|
+
au.command("list")
|
|
213
|
+
.description("List local agent-updates snapshots")
|
|
214
|
+
.action(() => agentUpdatesCmd.list());
|
|
215
|
+
|
|
216
|
+
// export (full chat history → TXT, byte-identical to the in-app export)
|
|
217
|
+
const exp = program.command("export").description("Export data to local files");
|
|
218
|
+
exp.command("chats <organization_id>")
|
|
219
|
+
.description("Export the org's full chat history to a TXT file (keyset-paginated, byte-matches the in-app export)")
|
|
220
|
+
.option("--out <path>", "output path (default .flowiq/exports/<slug>-chats.txt)")
|
|
221
|
+
.action((orgId, opts) => exportCmd.chats(orgId, opts));
|
|
222
|
+
|
|
223
|
+
// test (drive the org's LIVE agent through the web/test channel; no real WhatsApp)
|
|
224
|
+
const test = program.command("test").description("Test an org's live agent via the web/test channel (never sends a real WhatsApp)");
|
|
225
|
+
test.command("resolve <organization_id>")
|
|
226
|
+
.description("Print the resolved agent + test contact + endpoint")
|
|
227
|
+
.option("--agent <id>", "test a specific (non-active) agent in the org")
|
|
228
|
+
.action((orgId, opts) => testCmd.resolve(orgId, opts));
|
|
229
|
+
test.command("send <organization_id> <message...>")
|
|
230
|
+
.description("Send one message, print the reply + any tool calls")
|
|
231
|
+
.option("--agent <id>", "test a specific (non-active) agent")
|
|
232
|
+
.option("--clear", "clear the conversation before the run")
|
|
233
|
+
.option("--sender <name>", "senderName in the payload (default the test contact's name)")
|
|
234
|
+
.option("--image <url>", "attach a publicly-fetchable image URL (text is the caption)")
|
|
235
|
+
.option("--timeout <ms>", "per-turn HTTP timeout (default 120000)")
|
|
236
|
+
.option("--json", "print raw JSON instead of the transcript")
|
|
237
|
+
.action((orgId, message, opts) => testCmd.send(orgId, message, opts));
|
|
238
|
+
test.command("scenario <organization_id> <file>")
|
|
239
|
+
.description("Run a scenario pack (JSON: {scenarios:[{id,title,turns:[{text,expect?,expectNot?}]}]})")
|
|
240
|
+
.option("--agent <id>", "test a specific agent")
|
|
241
|
+
.option("--no-clear-between", "do NOT clear between scenarios (one long thread)")
|
|
242
|
+
.option("--sender <name>", "senderName")
|
|
243
|
+
.option("--timeout <ms>", "per-turn HTTP timeout (default 120000)")
|
|
244
|
+
.option("--json", "print raw JSON results")
|
|
245
|
+
.action((orgId, file, opts) => testCmd.scenario(orgId, file, opts));
|
|
246
|
+
test.command("qa <organization_id>")
|
|
247
|
+
.description("Run the bundled generic smoke pack")
|
|
248
|
+
.option("--agent <id>", "test a specific agent")
|
|
249
|
+
.option("--no-clear-between", "do NOT clear between scenarios")
|
|
250
|
+
.option("--sender <name>", "senderName")
|
|
251
|
+
.option("--timeout <ms>", "per-turn HTTP timeout")
|
|
252
|
+
.option("--json", "print raw JSON results")
|
|
253
|
+
.action((orgId, opts) => testCmd.qa(orgId, opts));
|
|
254
|
+
test.command("clear <organization_id>")
|
|
255
|
+
.description("Clear the test conversation (hide history via memory_cutoff)")
|
|
256
|
+
.option("--agent <id>", "target a specific agent's test contact")
|
|
257
|
+
.action((orgId, opts) => testCmd.clear(orgId, opts));
|
|
258
|
+
test.command("stress <organization_id> [pack]")
|
|
259
|
+
.description("Run an adversarial pack in parallel, one isolated contact per scenario (phase 2)")
|
|
260
|
+
.option("--agent <id>", "test a specific agent")
|
|
261
|
+
.option("--concurrency <n>", "parallel scenarios (default 6)")
|
|
262
|
+
.option("--timeout <ms>", "per-turn HTTP timeout")
|
|
263
|
+
.option("--out <path>", "results file (default .flowiq/agent-test/stress-results.json)")
|
|
264
|
+
.option("--contact <id>", "use a fixed real contact instead of isolated ones (forces concurrency 1)")
|
|
265
|
+
.action((orgId, pack, opts) => testCmd.stress(orgId, pack, opts));
|
|
266
|
+
test.command("cleanup <organization_id>")
|
|
267
|
+
.description("DELETE the stress test contacts + their messages/sessions")
|
|
268
|
+
.option("--agent <id>", "target a specific agent's stress contacts")
|
|
269
|
+
.option("--confirm", "required — confirms the destructive delete")
|
|
270
|
+
.action((orgId, opts) => testCmd.cleanup(orgId, opts));
|
|
271
|
+
test.command("extract <organization_id>")
|
|
272
|
+
.description("Rebuild full stress transcripts from the DB (for an LLM judge)")
|
|
273
|
+
.option("--agent <id>", "target a specific agent's stress contacts")
|
|
274
|
+
.option("--out <path>", "output file (default .flowiq/agent-test/judge_input.json)")
|
|
275
|
+
.action((orgId, opts) => testCmd.extract(orgId, opts));
|
|
276
|
+
|
|
106
277
|
program.parseAsync(argv);
|
|
107
278
|
}
|