@flowapt/flowiq-cli 0.1.7 → 0.1.8
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 +31 -0
- package/package.json +1 -1
- package/src/commands/agent-config.js +4 -3
- package/src/commands/agents.js +76 -0
- package/src/commands/fine-tuning.js +4 -3
- package/src/commands/prompts.js +4 -3
- package/src/commands/questionnaires.js +4 -3
- package/src/index.js +28 -12
package/README.md
CHANGED
|
@@ -252,6 +252,37 @@ credentials are stripped server-side).
|
|
|
252
252
|
flowiq org info <organization_id> # platform, storefront url, active agent
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
+
### Agents — `flowiq agent list|create <org>`
|
|
256
|
+
|
|
257
|
+
List an org's agents (to discover ids) and create a new one.
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
flowiq agent list <organization_id> # ★ marks the active agent; shows each id
|
|
261
|
+
flowiq agent create <organization_id> --name "Zara" \
|
|
262
|
+
--system-prompt-file ./zara-prompt.txt --make-active
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`create` inserts a new agent (`created_by` = your staff-key identity), optionally
|
|
266
|
+
seeds its prompt into a single "Main" section, and — with `--make-active` — sets
|
|
267
|
+
it as the org's `active_whatsapp_agent`. New agents start on the app default
|
|
268
|
+
model; set the house model afterwards with `agent config … --model gpt-5.4-mini
|
|
269
|
+
--use-settings-prompt`.
|
|
270
|
+
|
|
271
|
+
### Targeting a specific agent — `--agent <id>`
|
|
272
|
+
|
|
273
|
+
By default `prompts`, `questionnaires`, `fine-tuning`, and `agent config` all act
|
|
274
|
+
on the org's **active** agent. To target a **different** agent, pass `--agent <id>`
|
|
275
|
+
(get ids from `agent list`):
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
flowiq prompts pull <org> --agent <agent_id> # pull a NON-active agent's prompt
|
|
279
|
+
flowiq prompts push <slug> # pushes back to that same agent (id is in the file)
|
|
280
|
+
flowiq agent config <org> --agent <agent_id> --model gpt-5.4-mini
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`pull` records the agent id in the JSON, so `push` always writes back to the exact
|
|
284
|
+
agent you pulled from (never "whatever is active now").
|
|
285
|
+
|
|
255
286
|
### Agent config — `flowiq agent config <organization_id> [flags]`
|
|
256
287
|
|
|
257
288
|
Set an allowlisted set of the active agent's config fields (the prompt-builder
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowapt/flowiq-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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": {
|
|
@@ -37,6 +37,7 @@ export async function config(orgId, opts = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const body = { organization_id: orgId };
|
|
40
|
+
if (opts.agent !== undefined) body.agent_id = opts.agent;
|
|
40
41
|
const settings = {};
|
|
41
42
|
if (opts.useSettingsPrompt !== undefined) settings.use_settings_prompt = parseBool(opts.useSettingsPrompt, "--use-settings-prompt");
|
|
42
43
|
if (opts.model !== undefined) settings.model = opts.model;
|
|
@@ -65,12 +66,12 @@ export async function config(orgId, opts = {}) {
|
|
|
65
66
|
if (!hasWrite) {
|
|
66
67
|
let resp;
|
|
67
68
|
try {
|
|
68
|
-
resp = await http.get("agent-config", { organization_id: orgId });
|
|
69
|
+
resp = await http.get("agent-config", { organization_id: orgId, agent_id: opts.agent });
|
|
69
70
|
} catch (e) {
|
|
70
71
|
console.error(`Lookup failed: ${e.message}`);
|
|
71
72
|
process.exit(1);
|
|
72
73
|
}
|
|
73
|
-
console.log(`${resp.organization_name} → agent ${resp.agent_id}`);
|
|
74
|
+
console.log(`${resp.organization_name} → agent ${resp.agent_id}${resp.is_active === false ? " [NON-active]" : ""}`);
|
|
74
75
|
printSnapshot("current", resp.current);
|
|
75
76
|
console.log("\n(pass --use-settings-prompt / --model / --rename / --tool / --discount to change)");
|
|
76
77
|
return;
|
|
@@ -85,7 +86,7 @@ export async function config(orgId, opts = {}) {
|
|
|
85
86
|
process.exit(1);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
console.log(`Updated agent ${resp.agent_id} (${resp.organization_name})`);
|
|
89
|
+
console.log(`Updated agent ${resp.agent_id} (${resp.organization_name})${resp.is_active === false ? " [NON-active]" : ""}`);
|
|
89
90
|
console.log(` changed: ${resp.changed.join(", ") || "(nothing)"}`);
|
|
90
91
|
for (const k of resp.changed) {
|
|
91
92
|
const b = resp.before?.[k];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// `flowiq agent list <org>` / `flowiq agent create <org> --name … [flags]`.
|
|
2
|
+
// List an org's agents (to discover ids for --agent targeting) and create a
|
|
3
|
+
// new agent via /cli/agents.
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { http } from "../http.js";
|
|
8
|
+
|
|
9
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
|
+
|
|
11
|
+
export async function list(orgId, opts = {}) {
|
|
12
|
+
if (!UUID_RE.test(orgId)) {
|
|
13
|
+
console.error(`Error: "${orgId}" is not a valid organization UUID.`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
let resp;
|
|
17
|
+
try {
|
|
18
|
+
resp = await http.get("agents", { organization_id: orgId });
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.error(`List failed: ${e.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
if (opts.json) { console.log(JSON.stringify(resp, null, 2)); return; }
|
|
24
|
+
|
|
25
|
+
const agents = resp.agents || [];
|
|
26
|
+
console.log(`${resp.organization_name} (${resp.organization_id}) — ${agents.length} agent${agents.length === 1 ? "" : "s"}:`);
|
|
27
|
+
if (!agents.length) { console.log(" (none — create one with `agent create`)"); return; }
|
|
28
|
+
for (const a of agents) {
|
|
29
|
+
const created = a.created_at ? new Date(a.created_at).toISOString().slice(0, 10) : "?";
|
|
30
|
+
console.log(` ${a.is_active ? "★" : " "} ${a.id} ${created} ${a.name}`);
|
|
31
|
+
}
|
|
32
|
+
console.log("\n★ = active agent. Pass any id as --agent <id> to prompts/questionnaires/fine-tuning/agent config.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function create(orgId, opts = {}) {
|
|
36
|
+
if (!UUID_RE.test(orgId)) {
|
|
37
|
+
console.error(`Error: "${orgId}" is not a valid organization UUID.`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (!opts.name || !String(opts.name).trim()) {
|
|
41
|
+
console.error("Error: --name <name> is required.");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let systemPrompt;
|
|
46
|
+
if (opts.systemPromptFile) {
|
|
47
|
+
let fp = opts.systemPromptFile;
|
|
48
|
+
if (!path.isAbsolute(fp)) fp = path.resolve(fp);
|
|
49
|
+
try { systemPrompt = await fs.readFile(fp, "utf8"); }
|
|
50
|
+
catch (e) { console.error(`Cannot read --system-prompt-file ${fp}: ${e.message}`); process.exit(1); }
|
|
51
|
+
} else if (opts.systemPrompt !== undefined) {
|
|
52
|
+
systemPrompt = opts.systemPrompt;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let resp;
|
|
56
|
+
try {
|
|
57
|
+
resp = await http.post("agents", {
|
|
58
|
+
organization_id: orgId,
|
|
59
|
+
name: opts.name,
|
|
60
|
+
system_prompt: systemPrompt,
|
|
61
|
+
make_active: !!opts.makeActive,
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error(`Create failed: ${e.message}`);
|
|
65
|
+
if (e.body?.error) console.error(` ${e.body.error}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`Created agent "${resp.agent_name}" (${resp.agent_id})`);
|
|
70
|
+
console.log(` org: ${resp.organization_name} (${resp.organization_id})`);
|
|
71
|
+
console.log(` active: ${resp.is_active ? "yes (set as the org's active agent)" : "no"}`);
|
|
72
|
+
console.log(` seeded prompt: ${resp.seeded_prompt ? "yes (single \"Main\" section)" : "no (default settings)"}`);
|
|
73
|
+
if (resp.warning) console.log(` ⚠ ${resp.warning}`);
|
|
74
|
+
console.log("\nNext: `agent config " + orgId + " --agent " + resp.agent_id + " --use-settings-prompt --model gpt-5.4-mini`");
|
|
75
|
+
console.log(" then `prompts pull " + orgId + " --agent " + resp.agent_id + "` to author the full prompt.");
|
|
76
|
+
}
|
|
@@ -53,14 +53,14 @@ function countEntries(entries) {
|
|
|
53
53
|
return { total: entries.length, pending, resolved, byCategory };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export async function pull(orgId) {
|
|
56
|
+
export async function pull(orgId, opts = {}) {
|
|
57
57
|
if (!UUID_RE.test(orgId)) {
|
|
58
58
|
console.error(`Error: "${orgId}" is not a valid organization UUID.`);
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
let resp;
|
|
62
62
|
try {
|
|
63
|
-
resp = await http.get("fine-tuning", { organization_id: orgId });
|
|
63
|
+
resp = await http.get("fine-tuning", { organization_id: orgId, agent_id: opts.agent });
|
|
64
64
|
} catch (e) {
|
|
65
65
|
console.error(`Pull failed: ${e.message}`);
|
|
66
66
|
process.exit(1);
|
|
@@ -75,7 +75,7 @@ export async function pull(orgId) {
|
|
|
75
75
|
const stats = countEntries(resp.fine_tuning_entries);
|
|
76
76
|
console.log(`${overwriting ? "Overwrote" : "Wrote"} ${filePath}`);
|
|
77
77
|
console.log(` org: ${resp.organization_name} (${resp.organization_id})`);
|
|
78
|
-
console.log(` agent: ${resp.agent_name} (${resp.agent_id})`);
|
|
78
|
+
console.log(` agent: ${resp.agent_name} (${resp.agent_id})${resp.is_active === false ? " [NON-active]" : ""}`);
|
|
79
79
|
console.log(` instructions: ${resp.fine_tuning.length} chars`);
|
|
80
80
|
console.log(` entries: ${stats.total} (${stats.pending} pending, ${stats.resolved} resolved)`);
|
|
81
81
|
if (stats.total > 0) {
|
|
@@ -118,6 +118,7 @@ export async function push(identifier) {
|
|
|
118
118
|
organization_id: data.organization_id,
|
|
119
119
|
fine_tuning: data.fine_tuning,
|
|
120
120
|
fine_tuning_entries: data.fine_tuning_entries ?? [],
|
|
121
|
+
agent_id: data.agent_id, // push back to the agent this file was pulled from
|
|
121
122
|
});
|
|
122
123
|
} catch (e) {
|
|
123
124
|
console.error(`Push failed: ${e.message}`);
|
package/src/commands/prompts.js
CHANGED
|
@@ -40,14 +40,14 @@ async function resolvePushPath(identifier) {
|
|
|
40
40
|
throw new Error(`File not found. Tried:\n ${candidates.join("\n ")}`);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export async function pull(orgId) {
|
|
43
|
+
export async function pull(orgId, opts = {}) {
|
|
44
44
|
if (!UUID_RE.test(orgId)) {
|
|
45
45
|
console.error(`Error: "${orgId}" is not a valid organization UUID.`);
|
|
46
46
|
process.exit(1);
|
|
47
47
|
}
|
|
48
48
|
let resp;
|
|
49
49
|
try {
|
|
50
|
-
resp = await http.get("prompts", { organization_id: orgId });
|
|
50
|
+
resp = await http.get("prompts", { organization_id: orgId, agent_id: opts.agent });
|
|
51
51
|
} catch (e) {
|
|
52
52
|
console.error(`Pull failed: ${e.message}`);
|
|
53
53
|
process.exit(1);
|
|
@@ -62,7 +62,7 @@ export async function pull(orgId) {
|
|
|
62
62
|
const totalChars = resp.prompt_sections.reduce((a, s) => a + (s.content?.length || 0), 0);
|
|
63
63
|
console.log(`${overwriting ? "Overwrote" : "Wrote"} ${filePath}`);
|
|
64
64
|
console.log(` org: ${resp.organization_name} (${resp.organization_id})`);
|
|
65
|
-
console.log(` agent: ${resp.agent_name} (${resp.agent_id})`);
|
|
65
|
+
console.log(` agent: ${resp.agent_name} (${resp.agent_id})${resp.is_active === false ? " [NON-active]" : ""}`);
|
|
66
66
|
console.log(` sections: ${resp.prompt_sections.length}`);
|
|
67
67
|
console.log(` content: ${totalChars.toLocaleString()} chars`);
|
|
68
68
|
if (resp.bootstrapped) {
|
|
@@ -105,6 +105,7 @@ export async function push(identifier) {
|
|
|
105
105
|
resp = await http.post("prompts", {
|
|
106
106
|
organization_id: data.organization_id,
|
|
107
107
|
prompt_sections: data.prompt_sections,
|
|
108
|
+
agent_id: data.agent_id, // push back to the agent this file was pulled from
|
|
108
109
|
});
|
|
109
110
|
} catch (e) {
|
|
110
111
|
console.error(`Push failed: ${e.message}`);
|
|
@@ -53,14 +53,14 @@ function countQuestions(sections) {
|
|
|
53
53
|
return { total, answered };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export async function pull(orgId) {
|
|
56
|
+
export async function pull(orgId, opts = {}) {
|
|
57
57
|
if (!UUID_RE.test(orgId)) {
|
|
58
58
|
console.error(`Error: "${orgId}" is not a valid organization UUID.`);
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
let resp;
|
|
62
62
|
try {
|
|
63
|
-
resp = await http.get("questionnaires", { organization_id: orgId });
|
|
63
|
+
resp = await http.get("questionnaires", { organization_id: orgId, agent_id: opts.agent });
|
|
64
64
|
} catch (e) {
|
|
65
65
|
console.error(`Pull failed: ${e.message}`);
|
|
66
66
|
process.exit(1);
|
|
@@ -75,7 +75,7 @@ export async function pull(orgId) {
|
|
|
75
75
|
const { total, answered } = countQuestions(resp.questionnaire?.sections);
|
|
76
76
|
console.log(`${overwriting ? "Overwrote" : "Wrote"} ${filePath}`);
|
|
77
77
|
console.log(` org: ${resp.organization_name} (${resp.organization_id})`);
|
|
78
|
-
console.log(` agent: ${resp.agent_name} (${resp.agent_id})`);
|
|
78
|
+
console.log(` agent: ${resp.agent_name} (${resp.agent_id})${resp.is_active === false ? " [NON-active]" : ""}`);
|
|
79
79
|
console.log(` sections: ${resp.questionnaire?.sections?.length ?? 0}`);
|
|
80
80
|
console.log(` questions: ${answered}/${total} answered`);
|
|
81
81
|
if (resp.default_used) {
|
|
@@ -114,6 +114,7 @@ export async function push(identifier) {
|
|
|
114
114
|
resp = await http.post("questionnaires", {
|
|
115
115
|
organization_id: data.organization_id,
|
|
116
116
|
questionnaire: data.questionnaire,
|
|
117
|
+
agent_id: data.agent_id, // push back to the agent this file was pulled from
|
|
117
118
|
});
|
|
118
119
|
} catch (e) {
|
|
119
120
|
console.error(`Push failed: ${e.message}`);
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import * as pinboardCmd from "./commands/pinboard.js";
|
|
|
18
18
|
import * as templatesCmd from "./commands/templates.js";
|
|
19
19
|
import * as orgCmd from "./commands/org.js";
|
|
20
20
|
import * as agentConfigCmd from "./commands/agent-config.js";
|
|
21
|
+
import * as agentsCmd from "./commands/agents.js";
|
|
21
22
|
import * as agentUpdatesCmd from "./commands/agent-updates.js";
|
|
22
23
|
import * as exportCmd from "./commands/export.js";
|
|
23
24
|
import * as testCmd from "./commands/agent-test.js";
|
|
@@ -48,10 +49,11 @@ export function run(argv) {
|
|
|
48
49
|
// prompts
|
|
49
50
|
const prompts = program.command("prompts").description("Round-trip an org's active WhatsApp agent system prompt");
|
|
50
51
|
prompts.command("pull <organization_id>")
|
|
51
|
-
.description("Fetch
|
|
52
|
-
.
|
|
52
|
+
.description("Fetch an agent's prompt_sections into a local JSON file (default: active agent)")
|
|
53
|
+
.option("--agent <id>", "target a specific agent instead of the org's active one")
|
|
54
|
+
.action((orgId, opts) => promptsCmd.pull(orgId, opts));
|
|
53
55
|
prompts.command("push <slug-or-path>")
|
|
54
|
-
.description("Apply a local JSON file's prompt_sections to the agent (regenerates system_prompt server-side)")
|
|
56
|
+
.description("Apply a local JSON file's prompt_sections back to the agent it was pulled from (regenerates system_prompt server-side)")
|
|
55
57
|
.action((id) => promptsCmd.push(id));
|
|
56
58
|
|
|
57
59
|
// questionnaires
|
|
@@ -59,10 +61,11 @@ export function run(argv) {
|
|
|
59
61
|
.alias("q")
|
|
60
62
|
.description("Round-trip an org's active WhatsApp agent's questionnaire answers (agents.questionnaire JSONB)");
|
|
61
63
|
qs.command("pull <organization_id>")
|
|
62
|
-
.description("Fetch
|
|
63
|
-
.
|
|
64
|
+
.description("Fetch an agent's questionnaire into a local JSON file (default: active agent; seeds DEFAULT_QUESTIONNAIRE if empty)")
|
|
65
|
+
.option("--agent <id>", "target a specific agent instead of the org's active one")
|
|
66
|
+
.action((orgId, opts) => questionnairesCmd.pull(orgId, opts));
|
|
64
67
|
qs.command("push <slug-or-path>")
|
|
65
|
-
.description("Apply a local JSON file's questionnaire to the agent")
|
|
68
|
+
.description("Apply a local JSON file's questionnaire back to the agent it was pulled from")
|
|
66
69
|
.action((id) => questionnairesCmd.push(id));
|
|
67
70
|
|
|
68
71
|
// messages (read-only: pull a contact's helpdesk_messages history)
|
|
@@ -79,10 +82,11 @@ export function run(argv) {
|
|
|
79
82
|
.alias("ft")
|
|
80
83
|
.description("Round-trip the active agent's fine-tuning instructions + Q&A entries");
|
|
81
84
|
ft.command("pull <organization_id>")
|
|
82
|
-
.description("Fetch
|
|
83
|
-
.
|
|
85
|
+
.description("Fetch an agent's fine_tuning + fine_tuning_entries into a local JSON file (default: active agent)")
|
|
86
|
+
.option("--agent <id>", "target a specific agent instead of the org's active one")
|
|
87
|
+
.action((orgId, opts) => fineTuningCmd.pull(orgId, opts));
|
|
84
88
|
ft.command("push <slug-or-path>")
|
|
85
|
-
.description("Apply a local JSON file's fine_tuning + fine_tuning_entries to the agent")
|
|
89
|
+
.description("Apply a local JSON file's fine_tuning + fine_tuning_entries back to the agent it was pulled from")
|
|
86
90
|
.action((id) => fineTuningCmd.push(id));
|
|
87
91
|
|
|
88
92
|
// messaging-webhooks (outbound notifications via meta_webhooks table)
|
|
@@ -184,10 +188,22 @@ export function run(argv) {
|
|
|
184
188
|
.option("--json", "print the raw JSON payload")
|
|
185
189
|
.action((orgId, opts) => orgCmd.info(orgId, opts));
|
|
186
190
|
|
|
187
|
-
// agent (
|
|
188
|
-
const agent = program.command("agent").description("
|
|
191
|
+
// agent (list / create / config — config defaults to the active agent, or --agent <id>)
|
|
192
|
+
const agent = program.command("agent").description("List, create, and configure an org's agents");
|
|
193
|
+
agent.command("list <organization_id>")
|
|
194
|
+
.description("List the org's agents (★ = active); use an id as --agent <id> elsewhere")
|
|
195
|
+
.option("--json", "print the raw JSON payload")
|
|
196
|
+
.action((orgId, opts) => agentsCmd.list(orgId, opts));
|
|
197
|
+
agent.command("create <organization_id>")
|
|
198
|
+
.description("Create a new agent (optionally seed its prompt + set it active)")
|
|
199
|
+
.requiredOption("--name <name>", "agent name")
|
|
200
|
+
.option("--system-prompt <text>", "seed the prompt (single \"Main\" section)")
|
|
201
|
+
.option("--system-prompt-file <path>", "seed the prompt from a file")
|
|
202
|
+
.option("--make-active", "set the new agent as the org's active_whatsapp_agent")
|
|
203
|
+
.action((orgId, opts) => agentsCmd.create(orgId, opts));
|
|
189
204
|
agent.command("config <organization_id>")
|
|
190
|
-
.description("Set allowlisted agent config (no flags = show current)")
|
|
205
|
+
.description("Set allowlisted agent config (no write flags = show current; defaults to the active agent)")
|
|
206
|
+
.option("--agent <id>", "target a specific agent instead of the org's active one")
|
|
191
207
|
.option("--use-settings-prompt [bool]", "settings.use_settings_prompt (true if bare)")
|
|
192
208
|
.option("--model <model>", "settings.model (e.g. gpt-5.4-mini)")
|
|
193
209
|
.option("--rename <name>", "rename the agent")
|