@openbat/cli 0.2.3 → 0.4.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 +47 -6
- package/dist/api-client.js +1 -1
- package/dist/api-client.mjs +1 -1
- package/dist/{chunk-HBSCN3HV.mjs → chunk-RA36VFZI.mjs} +1 -1
- package/dist/index.js +373 -25
- package/dist/index.mjs +373 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @openbat/cli
|
|
2
2
|
|
|
3
3
|
Command-line tool for managing OpenBat chatbots end-to-end — read
|
|
4
|
-
analytics + conversations,
|
|
4
|
+
analytics + conversations, pull daily eval digests (`review`), manage the
|
|
5
|
+
live system prompt (`prompts`), manage settings + keys + webhooks + workflows
|
|
5
6
|
+ reports, run experiments, and help install the SDK in a target app.
|
|
7
|
+
All commands operate on one **pinned chatbot** (`openbat use <id>`).
|
|
6
8
|
|
|
7
9
|
> **Companion docs**: [`@openbat/mcp`](../mcp/README.md) (same surface
|
|
8
10
|
> over MCP), [`lib/openbat-tools/`](../../lib/openbat-tools/README.md)
|
|
@@ -63,6 +65,25 @@ rejects it with a helpful error.
|
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
68
|
+
## Pin to one chatbot (active-chatbot scope)
|
|
69
|
+
|
|
70
|
+
`ob_read_*` / `ob_admin_*` keys are already scoped to one chatbot server-side.
|
|
71
|
+
A `ob_pat_*` reaches many — pin one so every command stays on it:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
openbat use <id|name> # persists ~/.openbatrc.activeChatbotId
|
|
75
|
+
openbat use # no arg → shows the current pin + reachable options
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Once pinned, every data command (`conversations`, `analytics`, `review`,
|
|
79
|
+
`prompts`, …) targets that chatbot and prints a `→ chatbot: <name> (<id>)`
|
|
80
|
+
banner to **stderr** so scope is never in doubt (and `--json | jq` stays
|
|
81
|
+
clean). Active-chatbot resolution order: `--chatbot <id|name>` flag →
|
|
82
|
+
`$OPENBAT_CHATBOT_ID` → `~/.openbatrc`. The MCP server reads the same pin and
|
|
83
|
+
**hard-locks** to it (see [`@openbat/mcp`](../mcp/README.md)).
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
66
87
|
## Command tree
|
|
67
88
|
|
|
68
89
|
```
|
|
@@ -70,7 +91,13 @@ openbat
|
|
|
70
91
|
├── config
|
|
71
92
|
│ ├── set-key Store / replace the key in ~/.openbatrc
|
|
72
93
|
│ ├── set-url <baseUrl> Override the API base URL
|
|
73
|
-
│
|
|
94
|
+
│ ├── show Print the resolved config (key prefix + active chatbot)
|
|
95
|
+
│ ├── use-chatbot <id|name> Pin the active chatbot (persists to ~/.openbatrc)
|
|
96
|
+
│ └── clear-chatbot Forget the pinned chatbot
|
|
97
|
+
│
|
|
98
|
+
├── use [id|name] [any kind]
|
|
99
|
+
│ └── Pin the active chatbot (shortcut for `config use-chatbot`).
|
|
100
|
+
│ Omit the arg to print the current pin + the reachable list.
|
|
74
101
|
│
|
|
75
102
|
├── auth [any kind]
|
|
76
103
|
│ ├── whoami Show kind, chatbots in scope, orgs (PAT only)
|
|
@@ -96,7 +123,11 @@ openbat
|
|
|
96
123
|
│
|
|
97
124
|
├── conversations [any kind]
|
|
98
125
|
│ ├── list [--days N] [--from ISO] [--to ISO] [--limit N]
|
|
99
|
-
│ └── show <id>
|
|
126
|
+
│ └── show <id> Messages + ALL analyses (issues/outcomes/flags/intents + reasoning)
|
|
127
|
+
│
|
|
128
|
+
├── review [--since 45m|6h|7d] [any kind]
|
|
129
|
+
│ └── Daily eval digest: outcome/sentiment deltas + top issues/flags/intents
|
|
130
|
+
│ with representative conversation pointers. Default window 24h.
|
|
100
131
|
│
|
|
101
132
|
├── users [any kind]
|
|
102
133
|
│ └── list --chatbot ID [--days] [--search] External users + health
|
|
@@ -124,6 +155,12 @@ openbat
|
|
|
124
155
|
│ ├── list --chatbot ID
|
|
125
156
|
│ └── create --chatbot ID [--name "..."] Returns org-private dashboard URL
|
|
126
157
|
│
|
|
158
|
+
├── prompts manage the LIVE published prompt
|
|
159
|
+
│ ├── list [any kind] versions + active + kill-switch
|
|
160
|
+
│ ├── publish --file PATH | --text "..." [admin/pat] create a version + set it LIVE
|
|
161
|
+
│ ├── activate <versionId> [admin/pat] roll back/forward to a version
|
|
162
|
+
│ └── kill-switch --on | --off [admin/pat] emergency fallback toggle
|
|
163
|
+
│
|
|
127
164
|
├── analysis [admin or pat]
|
|
128
165
|
│ ├── list --chatbot ID [--type] [--pending]
|
|
129
166
|
│ └── add --chatbot ID --type intent|flag|assistant_outcome|assistant_issue
|
|
@@ -272,6 +309,9 @@ Per-tool buckets keyed by credential. The strict ones to know about:
|
|
|
272
309
|
| Create backtest | 10 per hour per PAT |
|
|
273
310
|
| Invite org member | 20 per hour per PAT |
|
|
274
311
|
| Chat with an AI report | 30 per minute per credential |
|
|
312
|
+
| Daily review digest (`review`) | 30 per minute per credential |
|
|
313
|
+
| Publish prompt | 20 per hour per credential |
|
|
314
|
+
| Activate prompt / toggle kill switch | 30 per hour per credential |
|
|
275
315
|
| Generic write | 60 per minute per credential |
|
|
276
316
|
| Generic read | 600 per minute per credential |
|
|
277
317
|
| Export | 30 per hour per credential |
|
|
@@ -290,11 +330,12 @@ install the OpenBat agent skill bundle:
|
|
|
290
330
|
npx skills add openbat-dev/agent-skills
|
|
291
331
|
```
|
|
292
332
|
|
|
293
|
-
Drops
|
|
333
|
+
Drops the `SKILL.md` bundle into your project's `.claude/skills/` (or the
|
|
294
334
|
equivalent for Cursor / Copilot / Gemini CLI / Codex / OpenCode / Amp).
|
|
295
335
|
The bundle covers chatbot onboarding, key management, webhooks,
|
|
296
|
-
workflows, AI reports, SDK install,
|
|
297
|
-
|
|
336
|
+
workflows, AI reports, SDK install, the daily eval→fix loop
|
|
337
|
+
(`openbat-optimize`), and safety patterns — written specifically for the
|
|
338
|
+
four-kind auth ladder this CLI exposes.
|
|
298
339
|
|
|
299
340
|
Source + docs: https://github.com/openbat-dev/agent-skills.
|
|
300
341
|
|
package/dist/api-client.js
CHANGED
|
@@ -32,7 +32,7 @@ __export(api_client_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(api_client_exports);
|
|
34
34
|
var import_node_url = require("url");
|
|
35
|
-
var CLI_VERSION = "0.
|
|
35
|
+
var CLI_VERSION = "0.4.0";
|
|
36
36
|
var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
|
|
37
37
|
function redact(s) {
|
|
38
38
|
return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
|
package/dist/api-client.mjs
CHANGED
|
@@ -9,7 +9,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
9
9
|
|
|
10
10
|
// src/api-client.ts
|
|
11
11
|
import { URL } from "url";
|
|
12
|
-
var CLI_VERSION = "0.
|
|
12
|
+
var CLI_VERSION = "0.4.0";
|
|
13
13
|
var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
|
|
14
14
|
function redact(s) {
|
|
15
15
|
return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
32
32
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
35
|
-
var
|
|
35
|
+
var import_commander11 = require("commander");
|
|
36
36
|
|
|
37
37
|
// src/commands/config.ts
|
|
38
38
|
var import_commander = require("commander");
|
|
@@ -163,7 +163,7 @@ function configPath() {
|
|
|
163
163
|
|
|
164
164
|
// src/api-client.ts
|
|
165
165
|
var import_node_url = require("url");
|
|
166
|
-
var CLI_VERSION = "0.
|
|
166
|
+
var CLI_VERSION = "0.4.0";
|
|
167
167
|
var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
|
|
168
168
|
function redact(s) {
|
|
169
169
|
return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
|
|
@@ -433,12 +433,24 @@ function configCommand() {
|
|
|
433
433
|
cmd.command("show").description("Show the current resolved config (key prefix only)").action(async () => {
|
|
434
434
|
const cfg = await resolveConfig({});
|
|
435
435
|
const keyDisplay = cfg.apiKey ? `${cfg.apiKey.slice(0, 16)}\u2026<hidden>` : "(not set)";
|
|
436
|
+
let activeChatbot = cfg.activeChatbotId ?? "(not set)";
|
|
437
|
+
if (cfg.apiKey && cfg.activeChatbotId) {
|
|
438
|
+
try {
|
|
439
|
+
const api = new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
440
|
+
const list = await api.get("/api/v1/chatbots");
|
|
441
|
+
const hit = (list.chatbots ?? []).find(
|
|
442
|
+
(c) => c.id === cfg.activeChatbotId
|
|
443
|
+
);
|
|
444
|
+
if (hit) activeChatbot = `${hit.name} (${hit.id})`;
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
}
|
|
436
448
|
emit({
|
|
437
449
|
apiKey: keyDisplay,
|
|
438
450
|
apiKeySource: cfg.apiKeySource,
|
|
439
451
|
baseUrl: cfg.baseUrl,
|
|
440
452
|
baseUrlSource: cfg.baseUrlSource,
|
|
441
|
-
|
|
453
|
+
activeChatbot,
|
|
442
454
|
activeChatbotIdSource: cfg.activeChatbotIdSource,
|
|
443
455
|
configFile: configPath()
|
|
444
456
|
});
|
|
@@ -493,9 +505,46 @@ async function resolveTargetChatbotId(target) {
|
|
|
493
505
|
}
|
|
494
506
|
return matches[0].id;
|
|
495
507
|
}
|
|
508
|
+
async function showCurrentAndOptions() {
|
|
509
|
+
const cfg = await resolveConfig({});
|
|
510
|
+
if (!cfg.apiKey) {
|
|
511
|
+
fatal("No API key configured. Run `openbat config set-key` first.");
|
|
512
|
+
}
|
|
513
|
+
const api = new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
514
|
+
const list = await api.get(
|
|
515
|
+
"/api/v1/chatbots"
|
|
516
|
+
);
|
|
517
|
+
const chatbots = list.chatbots ?? [];
|
|
518
|
+
if (cfg.activeChatbotId) {
|
|
519
|
+
const hit = chatbots.find((c) => c.id === cfg.activeChatbotId);
|
|
520
|
+
process.stdout.write(
|
|
521
|
+
hit ? `Active chatbot: ${hit.name} (${hit.id})
|
|
522
|
+
|
|
523
|
+
` : `Active chatbot: ${cfg.activeChatbotId} (not in reachable set)
|
|
524
|
+
|
|
525
|
+
`
|
|
526
|
+
);
|
|
527
|
+
} else {
|
|
528
|
+
process.stdout.write("No active chatbot set.\n\n");
|
|
529
|
+
}
|
|
530
|
+
if (chatbots.length === 0) {
|
|
531
|
+
process.stdout.write("No chatbots reachable by this key.\n");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
process.stdout.write("Reachable chatbots \u2014 pin one with `openbat use <id>`:\n");
|
|
535
|
+
for (const c of chatbots) {
|
|
536
|
+
const marker = c.id === cfg.activeChatbotId ? "\u25CF" : "\u25CB";
|
|
537
|
+
process.stdout.write(` ${marker} ${c.name} ${c.id}
|
|
538
|
+
`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
496
541
|
function useCommand() {
|
|
497
|
-
return new import_commander.Command("use").argument("
|
|
542
|
+
return new import_commander.Command("use").argument("[id-or-name]", "Chatbot UUID or exact name (omit to show current + options)").description("Set the active chatbot for this CLI (omit the arg to see current + options)").action(async (target) => {
|
|
498
543
|
try {
|
|
544
|
+
if (!target) {
|
|
545
|
+
await showCurrentAndOptions();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
499
548
|
const id = await resolveTargetChatbotId(target);
|
|
500
549
|
await setActiveChatbotId(id);
|
|
501
550
|
process.stdout.write(`Active chatbot saved: ${id}
|
|
@@ -525,6 +574,10 @@ async function client(globals) {
|
|
|
525
574
|
};
|
|
526
575
|
}
|
|
527
576
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
577
|
+
function announceScope(name, id) {
|
|
578
|
+
process.stderr.write(`\u2192 chatbot: ${name} (${id})
|
|
579
|
+
`);
|
|
580
|
+
}
|
|
528
581
|
async function resolveChatbotId(api, chatbotFlag) {
|
|
529
582
|
const list = await api.get(
|
|
530
583
|
"/api/v1/chatbots"
|
|
@@ -543,6 +596,7 @@ async function resolveChatbotId(api, chatbotFlag) {
|
|
|
543
596
|
`Chatbot ${chatbotFlag} is not reachable by this API key. Run \`openbat chatbot list\` to see what is.`
|
|
544
597
|
);
|
|
545
598
|
}
|
|
599
|
+
announceScope(hit.name, hit.id);
|
|
546
600
|
return hit.id;
|
|
547
601
|
}
|
|
548
602
|
const lower = chatbotFlag.toLowerCase();
|
|
@@ -557,9 +611,13 @@ async function resolveChatbotId(api, chatbotFlag) {
|
|
|
557
611
|
`Multiple chatbots named "${chatbotFlag}". Use the UUID instead \u2014 list them with \`openbat chatbot list\`.`
|
|
558
612
|
);
|
|
559
613
|
}
|
|
614
|
+
announceScope(matches[0].name, matches[0].id);
|
|
560
615
|
return matches[0].id;
|
|
561
616
|
}
|
|
562
|
-
if (chatbots.length === 1)
|
|
617
|
+
if (chatbots.length === 1) {
|
|
618
|
+
announceScope(chatbots[0].name, chatbots[0].id);
|
|
619
|
+
return chatbots[0].id;
|
|
620
|
+
}
|
|
563
621
|
const lines = chatbots.map((c) => ` \u2022 ${c.name} ${c.id}`).join("\n");
|
|
564
622
|
fatal(
|
|
565
623
|
`This API key targets multiple chatbots. Pick one with \`openbat use <id>\` (persistent) or \`--chatbot <id>\` (one-off):
|
|
@@ -687,9 +745,10 @@ function exportCommand() {
|
|
|
687
745
|
`/api/v1/export/${id}?format=${format}`
|
|
688
746
|
);
|
|
689
747
|
if (opts.out) {
|
|
690
|
-
const { createWriteStream } = await
|
|
691
|
-
|
|
692
|
-
|
|
748
|
+
const [{ createWriteStream }, { Writable, Readable }] = await Promise.all([
|
|
749
|
+
import("fs"),
|
|
750
|
+
import("stream")
|
|
751
|
+
]);
|
|
693
752
|
const file = createWriteStream(opts.out);
|
|
694
753
|
const nodeReadable = Readable.fromWeb(
|
|
695
754
|
body
|
|
@@ -724,6 +783,13 @@ function detectKind(apiKey) {
|
|
|
724
783
|
if (apiKey.startsWith("ob_pat_")) return "pat";
|
|
725
784
|
return "unknown";
|
|
726
785
|
}
|
|
786
|
+
function activeChatbotDisplay(activeId, chatbots) {
|
|
787
|
+
if (!activeId) {
|
|
788
|
+
return chatbots.length > 1 ? `(not pinned \u2014 ${chatbots.length} reachable; run \`openbat use <id>\`)` : "(not pinned)";
|
|
789
|
+
}
|
|
790
|
+
const hit = chatbots.find((c) => c.id === activeId);
|
|
791
|
+
return hit ? `${hit.name} (${hit.id})` : `${activeId} (not in reachable set)`;
|
|
792
|
+
}
|
|
727
793
|
function authCommand() {
|
|
728
794
|
const cmd = new import_commander3.Command("auth").description(
|
|
729
795
|
"Inspect the current credential's scope and audit history"
|
|
@@ -747,6 +813,10 @@ function authCommand() {
|
|
|
747
813
|
{
|
|
748
814
|
kind,
|
|
749
815
|
keyPrefix: `${cfg.apiKey.slice(0, 16)}\u2026<hidden>`,
|
|
816
|
+
activeChatbot: activeChatbotDisplay(
|
|
817
|
+
cfg.activeChatbotId,
|
|
818
|
+
chatbots.chatbots
|
|
819
|
+
),
|
|
750
820
|
orgs: orgs.orgs,
|
|
751
821
|
chatbots: chatbots.chatbots
|
|
752
822
|
},
|
|
@@ -760,6 +830,10 @@ function authCommand() {
|
|
|
760
830
|
{
|
|
761
831
|
kind,
|
|
762
832
|
keyPrefix: `${cfg.apiKey.slice(0, 16)}\u2026<hidden>`,
|
|
833
|
+
activeChatbot: activeChatbotDisplay(
|
|
834
|
+
cfg.activeChatbotId,
|
|
835
|
+
chatbots.chatbots
|
|
836
|
+
),
|
|
763
837
|
chatbots: chatbots.chatbots
|
|
764
838
|
},
|
|
765
839
|
{ json: !!globals.json }
|
|
@@ -772,8 +846,279 @@ function authCommand() {
|
|
|
772
846
|
return cmd;
|
|
773
847
|
}
|
|
774
848
|
|
|
775
|
-
// src/commands/
|
|
849
|
+
// src/commands/review.ts
|
|
776
850
|
var import_commander4 = require("commander");
|
|
851
|
+
function parseSinceToMinutes(s) {
|
|
852
|
+
const match = /^(\d+)\s*(m|h|d)?$/.exec(s.trim().toLowerCase());
|
|
853
|
+
if (!match) {
|
|
854
|
+
fatal(`Invalid --since "${s}". Use e.g. 45m, 6h, or 7d.`);
|
|
855
|
+
}
|
|
856
|
+
const n = Number(match[1]);
|
|
857
|
+
const unit = match[2] ?? "m";
|
|
858
|
+
const minutes = unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n;
|
|
859
|
+
if (minutes < 1) fatal("--since must be at least 1 minute.");
|
|
860
|
+
if (minutes > 43200) fatal("--since cannot exceed 30 days (43200m / 720h / 30d).");
|
|
861
|
+
return minutes;
|
|
862
|
+
}
|
|
863
|
+
var arrow = (t) => t === "up" ? "\u25B2" : t === "down" ? "\u25BC" : "=";
|
|
864
|
+
var pctDelta = (d) => d === null ? "(new)" : `${arrow(d >= 0 ? "up" : "down")} ${d >= 0 ? "+" : ""}${d}%`;
|
|
865
|
+
var ptsDelta = (d) => `${arrow(d >= 0 ? "up" : "down")} ${d >= 0 ? "+" : ""}${d} pts`;
|
|
866
|
+
function renderReview(r) {
|
|
867
|
+
const L = [];
|
|
868
|
+
const h = r.headline;
|
|
869
|
+
L.push(`OpenBat review \xB7 last ${r.window.since} \xB7 vs prior ${r.window.since}`);
|
|
870
|
+
L.push("");
|
|
871
|
+
L.push(` Conversations ${h.conversations.value} ${pctDelta(h.conversations.delta_pct)}`);
|
|
872
|
+
L.push(` Messages ${h.messages.value}`);
|
|
873
|
+
L.push(` Resolved ${h.outcomes.resolved.pct}% ${ptsDelta(h.outcomes.resolved.delta_pts)}`);
|
|
874
|
+
L.push(` Partial ${h.outcomes.partially_resolved.pct}% ${ptsDelta(h.outcomes.partially_resolved.delta_pts)}`);
|
|
875
|
+
L.push(` Failed ${h.outcomes.failed.pct}% ${ptsDelta(h.outcomes.failed.delta_pts)}`);
|
|
876
|
+
const sent = h.avg_sentiment.value ?? "\u2014";
|
|
877
|
+
const sentDelta = h.avg_sentiment.delta === null ? "" : ` ${arrow(h.avg_sentiment.delta >= 0 ? "up" : "down")} ${h.avg_sentiment.delta >= 0 ? "+" : ""}${h.avg_sentiment.delta}`;
|
|
878
|
+
L.push(` Avg sentiment ${sent}${sentDelta}`);
|
|
879
|
+
L.push(` Flagged ${h.flagged}`);
|
|
880
|
+
if (r.clusters.issues.length) {
|
|
881
|
+
L.push("");
|
|
882
|
+
L.push("Top issues");
|
|
883
|
+
for (const i of r.clusters.issues) {
|
|
884
|
+
const ans = i.answer_available.true + i.answer_available.false;
|
|
885
|
+
const ansStr = ans > 0 ? ` answer_available ${i.answer_available.true}/${ans}` : "";
|
|
886
|
+
L.push(
|
|
887
|
+
` ${i.type} ${i.count} ${arrow(i.trend)} high ${i.severity.high} \xB7 med ${i.severity.medium} \xB7 low ${i.severity.low}${ansStr}`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (r.clusters.flags.length) {
|
|
892
|
+
L.push("");
|
|
893
|
+
L.push(`Top flags ${r.clusters.flags.map((f) => `${f.value} ${f.count} ${arrow(f.trend)}`).join(" \xB7 ")}`);
|
|
894
|
+
}
|
|
895
|
+
if (r.clusters.intents.length) {
|
|
896
|
+
L.push(`Top intents ${r.clusters.intents.map((i) => `${i.value} ${i.count} ${arrow(i.trend)}`).join(" \xB7 ")}`);
|
|
897
|
+
}
|
|
898
|
+
const reps = [
|
|
899
|
+
...r.clusters.issues.flatMap((i) => i.representatives),
|
|
900
|
+
...r.clusters.failedOutcomes
|
|
901
|
+
];
|
|
902
|
+
if (reps.length) {
|
|
903
|
+
L.push("");
|
|
904
|
+
L.push("Representative failures");
|
|
905
|
+
for (const rep of reps.slice(0, 8)) {
|
|
906
|
+
const tag = rep.type ? `[${rep.type} \xB7 ${rep.severity ?? "?"} \xB7 answer_available=${rep.answer_available ?? "?"} \xB7 source=${rep.verification_source ?? "?"}]` : `[failed outcome]`;
|
|
907
|
+
L.push(` ${tag}`);
|
|
908
|
+
L.push(` conv ${rep.conversationId ?? "?"} msg ${rep.messageId}`);
|
|
909
|
+
if (rep.reasoning) L.push(` why: ${rep.reasoning}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
L.push("");
|
|
913
|
+
L.push("Drill in: openbat conversations show <id> \xB7 Fix: openbat-optimize skill");
|
|
914
|
+
return L.join("\n");
|
|
915
|
+
}
|
|
916
|
+
function reviewCommand() {
|
|
917
|
+
return new import_commander4.Command("review").description(
|
|
918
|
+
"Daily eval digest for the active chatbot \u2014 flags, issues, outcomes + reasonings"
|
|
919
|
+
).option(
|
|
920
|
+
"--since <duration>",
|
|
921
|
+
"Look-back window: 45m, 6h, 7d (max 30d)",
|
|
922
|
+
"24h"
|
|
923
|
+
).action(async function(opts) {
|
|
924
|
+
try {
|
|
925
|
+
const windowMinutes = parseSinceToMinutes(opts.since);
|
|
926
|
+
const globals = this.optsWithGlobals();
|
|
927
|
+
const { api, chatbotFlag } = await client(globals);
|
|
928
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
929
|
+
const params = new URLSearchParams({
|
|
930
|
+
windowMinutes: String(windowMinutes)
|
|
931
|
+
});
|
|
932
|
+
const result = await api.get(
|
|
933
|
+
`/api/v1/chatbots/${id}/review?${params}`
|
|
934
|
+
);
|
|
935
|
+
if (this.optsWithGlobals().json) {
|
|
936
|
+
emit(result, { json: true });
|
|
937
|
+
} else {
|
|
938
|
+
process.stdout.write(renderReview(result) + "\n");
|
|
939
|
+
}
|
|
940
|
+
} catch (err) {
|
|
941
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/commands/prompts.ts
|
|
947
|
+
var import_commander5 = require("commander");
|
|
948
|
+
var import_node_fs2 = require("fs");
|
|
949
|
+
async function waitForActive(api, chatbotId, versionId) {
|
|
950
|
+
const deadlineMs = Date.now() + 15e3;
|
|
951
|
+
let attempt = 0;
|
|
952
|
+
while (Date.now() < deadlineMs) {
|
|
953
|
+
const res = await api.get(
|
|
954
|
+
`/api/v1/chatbots/${chatbotId}/prompts/active`
|
|
955
|
+
);
|
|
956
|
+
if (res.activeVersionId === versionId) {
|
|
957
|
+
process.stderr.write(
|
|
958
|
+
`
|
|
959
|
+
Confirmed live (server): active version is ${versionId}. Live SDK processes converge within their ~60s fetch-cache TTL.
|
|
960
|
+
`
|
|
961
|
+
);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
attempt++;
|
|
965
|
+
await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 2e3)));
|
|
966
|
+
}
|
|
967
|
+
process.stderr.write(
|
|
968
|
+
"\nPublished, but timed out (15s) waiting for server confirmation \u2014 the active pointer may still be propagating.\n"
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
function promptsCommand() {
|
|
972
|
+
const cmd = new import_commander5.Command("prompts").description(
|
|
973
|
+
"Manage the live published system prompt (list / publish / activate / kill-switch)"
|
|
974
|
+
);
|
|
975
|
+
cmd.command("list").description("List system-prompt versions + live controls (active version, kill switch)").action(async function() {
|
|
976
|
+
try {
|
|
977
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
978
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
979
|
+
const result = await api.get(
|
|
980
|
+
`/api/v1/chatbots/${id}/prompts`
|
|
981
|
+
);
|
|
982
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
983
|
+
} catch (err) {
|
|
984
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
cmd.command("publish").description("Publish a template as the LIVE prompt (from --file or --text)").option("--file <path>", "Read the template text from a file").option("--text <text>", "Inline template text (prefer --file for long prompts)").option("--wait", "Block until the server confirms the new version is the active published prompt").action(async function(opts) {
|
|
988
|
+
try {
|
|
989
|
+
let templateText = opts.text;
|
|
990
|
+
if (opts.file) templateText = await import_node_fs2.promises.readFile(opts.file, "utf8");
|
|
991
|
+
if (!templateText || !templateText.trim()) {
|
|
992
|
+
fatal("Provide the prompt text via --file <path> or --text <text>.");
|
|
993
|
+
}
|
|
994
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
995
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
996
|
+
const result = await api.post(
|
|
997
|
+
`/api/v1/chatbots/${id}/prompts`,
|
|
998
|
+
{ templateText }
|
|
999
|
+
);
|
|
1000
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1001
|
+
if (opts.wait && result.versionId) {
|
|
1002
|
+
await waitForActive(api, id, result.versionId);
|
|
1003
|
+
} else {
|
|
1004
|
+
process.stderr.write(
|
|
1005
|
+
"\nPublished. Live within ~60s for chatbots that fetch their prompt from OpenBat.\n"
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
cmd.command("activate <versionId>").description("Point the live prompt at an existing version id (roll back/forward)").action(async function(versionId) {
|
|
1013
|
+
try {
|
|
1014
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
1015
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
1016
|
+
const result = await api.post(
|
|
1017
|
+
`/api/v1/chatbots/${id}/prompts/activate`,
|
|
1018
|
+
{ versionId }
|
|
1019
|
+
);
|
|
1020
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
cmd.command("kill-switch").description("Toggle the remote prompt kill switch (--on / --off)").option("--on", "Enable: SDK falls back to its hardcoded prompt").option("--off", "Disable: SDK serves the active published prompt again").action(async function(opts) {
|
|
1026
|
+
try {
|
|
1027
|
+
if (!!opts.on === !!opts.off) {
|
|
1028
|
+
fatal("Pass exactly one of --on or --off.");
|
|
1029
|
+
}
|
|
1030
|
+
const enabled = !!opts.on;
|
|
1031
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
1032
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
1033
|
+
const result = await api.post(
|
|
1034
|
+
`/api/v1/chatbots/${id}/prompts/kill-switch`,
|
|
1035
|
+
{ enabled }
|
|
1036
|
+
);
|
|
1037
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
cmd.command("show <versionId>").description("Show a version's full template text (list shows only hashes)").action(async function(versionId) {
|
|
1043
|
+
try {
|
|
1044
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
1045
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
1046
|
+
const result = await api.get(
|
|
1047
|
+
`/api/v1/chatbots/${id}/prompts/${versionId}`
|
|
1048
|
+
);
|
|
1049
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
cmd.command("active").description("Show what the server resolves to RIGHT NOW (active version, kill switch)").action(async function() {
|
|
1055
|
+
try {
|
|
1056
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
1057
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
1058
|
+
const result = await api.get(
|
|
1059
|
+
`/api/v1/chatbots/${id}/prompts/active`
|
|
1060
|
+
);
|
|
1061
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
return cmd;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/commands/backtests.ts
|
|
1070
|
+
var import_commander6 = require("commander");
|
|
1071
|
+
function backtestsCommand() {
|
|
1072
|
+
const cmd = new import_commander6.Command("backtests").description(
|
|
1073
|
+
"Retest a candidate prompt against flagged conversations (create + poll status)"
|
|
1074
|
+
);
|
|
1075
|
+
cmd.command("create").description("Replay flagged conversations against a candidate prompt version").requiredOption("--name <name>", "Backtest name").requiredOption(
|
|
1076
|
+
"--candidate-prompt <versionId>",
|
|
1077
|
+
"Candidate prompt version id (from `openbat prompts list`)"
|
|
1078
|
+
).requiredOption(
|
|
1079
|
+
"--flags <csv>",
|
|
1080
|
+
"Comma-separated flag values to sample (e.g. churn_risk,billing_issue)"
|
|
1081
|
+
).option("--sample-size <n>", "How many flagged messages to replay (1-500)", "50").option("--from <iso>", "Only sample flags on/after this ISO timestamp").option("--to <iso>", "Only sample flags on/before this ISO timestamp").action(async function(opts) {
|
|
1082
|
+
try {
|
|
1083
|
+
const flagValues = opts.flags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1084
|
+
if (flagValues.length === 0) {
|
|
1085
|
+
fatal("Provide at least one flag via --flags <csv>.");
|
|
1086
|
+
}
|
|
1087
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
1088
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
1089
|
+
const result = await api.post(
|
|
1090
|
+
`/api/v1/chatbots/${id}/backtests`,
|
|
1091
|
+
{
|
|
1092
|
+
name: opts.name,
|
|
1093
|
+
candidatePromptId: opts.candidatePrompt,
|
|
1094
|
+
flagValues,
|
|
1095
|
+
sampleSize: Number(opts.sampleSize),
|
|
1096
|
+
timeRangeStart: opts.from,
|
|
1097
|
+
timeRangeEnd: opts.to
|
|
1098
|
+
}
|
|
1099
|
+
);
|
|
1100
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1101
|
+
process.stderr.write("\nStarted. Poll progress with: openbat backtests status <id>\n");
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
cmd.command("status <backtestId>").description("Show a backtest's progress + verdict tally").action(async function(backtestId) {
|
|
1107
|
+
try {
|
|
1108
|
+
const { api } = await client(this.optsWithGlobals());
|
|
1109
|
+
const result = await api.get(
|
|
1110
|
+
`/api/v1/backtests/${encodeURIComponent(backtestId)}/status`
|
|
1111
|
+
);
|
|
1112
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
return cmd;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/commands/login.ts
|
|
1121
|
+
var import_commander7 = require("commander");
|
|
777
1122
|
var import_node_os2 = __toESM(require("os"));
|
|
778
1123
|
var import_node_readline = __toESM(require("readline"));
|
|
779
1124
|
|
|
@@ -916,7 +1261,7 @@ async function runLoopbackServer(opts) {
|
|
|
916
1261
|
// src/commands/login.ts
|
|
917
1262
|
var HOSTNAME = import_node_os2.default.hostname();
|
|
918
1263
|
function loginCommand() {
|
|
919
|
-
return new
|
|
1264
|
+
return new import_commander7.Command("login").description("Sign in via browser and install an API key on this device").option("--device", "Use the device-code flow (for SSH / headless machines)").option(
|
|
920
1265
|
"--use <key>",
|
|
921
1266
|
"Skip the browser; install a PAT plaintext you already have"
|
|
922
1267
|
).action(async function(opts) {
|
|
@@ -1106,9 +1451,9 @@ You have ${chatbots.length} chatbots. Pick one later with \`openbat use <id>\`.
|
|
|
1106
1451
|
}
|
|
1107
1452
|
|
|
1108
1453
|
// src/commands/logout.ts
|
|
1109
|
-
var
|
|
1454
|
+
var import_commander8 = require("commander");
|
|
1110
1455
|
function logoutCommand() {
|
|
1111
|
-
return new
|
|
1456
|
+
return new import_commander8.Command("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
|
|
1112
1457
|
try {
|
|
1113
1458
|
const globals = this.optsWithGlobals();
|
|
1114
1459
|
const cfg = await resolveConfig({
|
|
@@ -1143,7 +1488,7 @@ function logoutCommand() {
|
|
|
1143
1488
|
}
|
|
1144
1489
|
|
|
1145
1490
|
// src/commands/org.ts
|
|
1146
|
-
var
|
|
1491
|
+
var import_commander9 = require("commander");
|
|
1147
1492
|
async function client2(globals) {
|
|
1148
1493
|
const cfg = await resolveConfig({
|
|
1149
1494
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -1158,7 +1503,7 @@ async function client2(globals) {
|
|
|
1158
1503
|
return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
1159
1504
|
}
|
|
1160
1505
|
function orgCommand() {
|
|
1161
|
-
const cmd = new
|
|
1506
|
+
const cmd = new import_commander9.Command("org").description(
|
|
1162
1507
|
"Manage the OpenBat tenant org (rename, members, invitations). Requires PAT."
|
|
1163
1508
|
);
|
|
1164
1509
|
cmd.command("list").description("List orgs the current PAT's user belongs to").action(async function() {
|
|
@@ -1266,7 +1611,7 @@ function orgCommand() {
|
|
|
1266
1611
|
}
|
|
1267
1612
|
|
|
1268
1613
|
// src/commands/write.ts
|
|
1269
|
-
var
|
|
1614
|
+
var import_commander10 = require("commander");
|
|
1270
1615
|
async function client3(globals) {
|
|
1271
1616
|
const cfg = await resolveConfig({
|
|
1272
1617
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -1299,7 +1644,7 @@ function surfacePlaintext(plaintext, label) {
|
|
|
1299
1644
|
);
|
|
1300
1645
|
}
|
|
1301
1646
|
function chatbotsCommand() {
|
|
1302
|
-
const cmd = new
|
|
1647
|
+
const cmd = new import_commander10.Command("chatbots").description(
|
|
1303
1648
|
"List, create, delete chatbots in the current scope"
|
|
1304
1649
|
);
|
|
1305
1650
|
cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
|
|
@@ -1347,7 +1692,7 @@ function chatbotsCommand() {
|
|
|
1347
1692
|
return cmd;
|
|
1348
1693
|
}
|
|
1349
1694
|
function webhooksCommand() {
|
|
1350
|
-
const cmd = new
|
|
1695
|
+
const cmd = new import_commander10.Command("webhooks").description("Manage webhooks for a chatbot");
|
|
1351
1696
|
cmd.command("list").action(async function() {
|
|
1352
1697
|
try {
|
|
1353
1698
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1391,7 +1736,7 @@ function webhooksCommand() {
|
|
|
1391
1736
|
return cmd;
|
|
1392
1737
|
}
|
|
1393
1738
|
function settingsCommand() {
|
|
1394
|
-
const cmd = new
|
|
1739
|
+
const cmd = new import_commander10.Command("settings").description(
|
|
1395
1740
|
"Manage chatbot settings + per-chatbot keys"
|
|
1396
1741
|
);
|
|
1397
1742
|
const keys = cmd.command("keys").description("Manage API keys for a chatbot");
|
|
@@ -1485,7 +1830,7 @@ function settingsCommand() {
|
|
|
1485
1830
|
return cmd;
|
|
1486
1831
|
}
|
|
1487
1832
|
function workflowsCommand() {
|
|
1488
|
-
const cmd = new
|
|
1833
|
+
const cmd = new import_commander10.Command("workflows").description("Manage workflows");
|
|
1489
1834
|
cmd.command("list").action(async function() {
|
|
1490
1835
|
try {
|
|
1491
1836
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1525,7 +1870,7 @@ function workflowsCommand() {
|
|
|
1525
1870
|
return cmd;
|
|
1526
1871
|
}
|
|
1527
1872
|
function reportsCommand() {
|
|
1528
|
-
const cmd = new
|
|
1873
|
+
const cmd = new import_commander10.Command("reports").description("Manage AI reports");
|
|
1529
1874
|
cmd.command("list").action(async function() {
|
|
1530
1875
|
try {
|
|
1531
1876
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1563,7 +1908,7 @@ Created report. View it (org members only):
|
|
|
1563
1908
|
return cmd;
|
|
1564
1909
|
}
|
|
1565
1910
|
function analysisCommand() {
|
|
1566
|
-
const cmd = new
|
|
1911
|
+
const cmd = new import_commander10.Command("analysis").description("Manage analysis definitions");
|
|
1567
1912
|
cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
|
|
1568
1913
|
try {
|
|
1569
1914
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1605,7 +1950,7 @@ function analysisCommand() {
|
|
|
1605
1950
|
return cmd;
|
|
1606
1951
|
}
|
|
1607
1952
|
function usersCommand() {
|
|
1608
|
-
const cmd = new
|
|
1953
|
+
const cmd = new import_commander10.Command("users").description(
|
|
1609
1954
|
"List external users (chatbot customers) with health metrics"
|
|
1610
1955
|
);
|
|
1611
1956
|
cmd.command("list").option("--from <iso>").option("--to <iso>").option("--days <n>", "Convenience: last N days").option("--search <q>").option("--limit <n>", "Page size", "20").action(async function(opts) {
|
|
@@ -1638,7 +1983,7 @@ Total: ${result.total}
|
|
|
1638
1983
|
return cmd;
|
|
1639
1984
|
}
|
|
1640
1985
|
function sdkCommand() {
|
|
1641
|
-
const cmd = new
|
|
1986
|
+
const cmd = new import_commander10.Command("sdk").description(
|
|
1642
1987
|
"Help install and verify the OpenBat SDK in a target project"
|
|
1643
1988
|
);
|
|
1644
1989
|
cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
|
|
@@ -1748,10 +2093,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
|
|
|
1748
2093
|
}
|
|
1749
2094
|
|
|
1750
2095
|
// src/index.ts
|
|
1751
|
-
var program = new
|
|
2096
|
+
var program = new import_commander11.Command();
|
|
1752
2097
|
program.name("openbat").description(
|
|
1753
2098
|
"Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
|
|
1754
|
-
).version("0.
|
|
2099
|
+
).version("0.4.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
|
|
1755
2100
|
"--base-url <url>",
|
|
1756
2101
|
"Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
|
|
1757
2102
|
).option(
|
|
@@ -1770,6 +2115,9 @@ program.addCommand(orgCommand());
|
|
|
1770
2115
|
program.addCommand(chatbotCommand());
|
|
1771
2116
|
program.addCommand(chatbotsCommand());
|
|
1772
2117
|
program.addCommand(conversationsCommand());
|
|
2118
|
+
program.addCommand(reviewCommand());
|
|
2119
|
+
program.addCommand(promptsCommand());
|
|
2120
|
+
program.addCommand(backtestsCommand());
|
|
1773
2121
|
program.addCommand(usersCommand());
|
|
1774
2122
|
program.addCommand(settingsCommand());
|
|
1775
2123
|
program.addCommand(webhooksCommand());
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ApiClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-RA36VFZI.mjs";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command11 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/config.ts
|
|
10
10
|
import { Command } from "commander";
|
|
@@ -255,12 +255,24 @@ function configCommand() {
|
|
|
255
255
|
cmd.command("show").description("Show the current resolved config (key prefix only)").action(async () => {
|
|
256
256
|
const cfg = await resolveConfig({});
|
|
257
257
|
const keyDisplay = cfg.apiKey ? `${cfg.apiKey.slice(0, 16)}\u2026<hidden>` : "(not set)";
|
|
258
|
+
let activeChatbot = cfg.activeChatbotId ?? "(not set)";
|
|
259
|
+
if (cfg.apiKey && cfg.activeChatbotId) {
|
|
260
|
+
try {
|
|
261
|
+
const api = new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
262
|
+
const list = await api.get("/api/v1/chatbots");
|
|
263
|
+
const hit = (list.chatbots ?? []).find(
|
|
264
|
+
(c) => c.id === cfg.activeChatbotId
|
|
265
|
+
);
|
|
266
|
+
if (hit) activeChatbot = `${hit.name} (${hit.id})`;
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
258
270
|
emit({
|
|
259
271
|
apiKey: keyDisplay,
|
|
260
272
|
apiKeySource: cfg.apiKeySource,
|
|
261
273
|
baseUrl: cfg.baseUrl,
|
|
262
274
|
baseUrlSource: cfg.baseUrlSource,
|
|
263
|
-
|
|
275
|
+
activeChatbot,
|
|
264
276
|
activeChatbotIdSource: cfg.activeChatbotIdSource,
|
|
265
277
|
configFile: configPath()
|
|
266
278
|
});
|
|
@@ -315,9 +327,46 @@ async function resolveTargetChatbotId(target) {
|
|
|
315
327
|
}
|
|
316
328
|
return matches[0].id;
|
|
317
329
|
}
|
|
330
|
+
async function showCurrentAndOptions() {
|
|
331
|
+
const cfg = await resolveConfig({});
|
|
332
|
+
if (!cfg.apiKey) {
|
|
333
|
+
fatal("No API key configured. Run `openbat config set-key` first.");
|
|
334
|
+
}
|
|
335
|
+
const api = new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
336
|
+
const list = await api.get(
|
|
337
|
+
"/api/v1/chatbots"
|
|
338
|
+
);
|
|
339
|
+
const chatbots = list.chatbots ?? [];
|
|
340
|
+
if (cfg.activeChatbotId) {
|
|
341
|
+
const hit = chatbots.find((c) => c.id === cfg.activeChatbotId);
|
|
342
|
+
process.stdout.write(
|
|
343
|
+
hit ? `Active chatbot: ${hit.name} (${hit.id})
|
|
344
|
+
|
|
345
|
+
` : `Active chatbot: ${cfg.activeChatbotId} (not in reachable set)
|
|
346
|
+
|
|
347
|
+
`
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
process.stdout.write("No active chatbot set.\n\n");
|
|
351
|
+
}
|
|
352
|
+
if (chatbots.length === 0) {
|
|
353
|
+
process.stdout.write("No chatbots reachable by this key.\n");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
process.stdout.write("Reachable chatbots \u2014 pin one with `openbat use <id>`:\n");
|
|
357
|
+
for (const c of chatbots) {
|
|
358
|
+
const marker = c.id === cfg.activeChatbotId ? "\u25CF" : "\u25CB";
|
|
359
|
+
process.stdout.write(` ${marker} ${c.name} ${c.id}
|
|
360
|
+
`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
318
363
|
function useCommand() {
|
|
319
|
-
return new Command("use").argument("
|
|
364
|
+
return new Command("use").argument("[id-or-name]", "Chatbot UUID or exact name (omit to show current + options)").description("Set the active chatbot for this CLI (omit the arg to see current + options)").action(async (target) => {
|
|
320
365
|
try {
|
|
366
|
+
if (!target) {
|
|
367
|
+
await showCurrentAndOptions();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
321
370
|
const id = await resolveTargetChatbotId(target);
|
|
322
371
|
await setActiveChatbotId(id);
|
|
323
372
|
process.stdout.write(`Active chatbot saved: ${id}
|
|
@@ -347,6 +396,10 @@ async function client(globals) {
|
|
|
347
396
|
};
|
|
348
397
|
}
|
|
349
398
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
399
|
+
function announceScope(name, id) {
|
|
400
|
+
process.stderr.write(`\u2192 chatbot: ${name} (${id})
|
|
401
|
+
`);
|
|
402
|
+
}
|
|
350
403
|
async function resolveChatbotId(api, chatbotFlag) {
|
|
351
404
|
const list = await api.get(
|
|
352
405
|
"/api/v1/chatbots"
|
|
@@ -365,6 +418,7 @@ async function resolveChatbotId(api, chatbotFlag) {
|
|
|
365
418
|
`Chatbot ${chatbotFlag} is not reachable by this API key. Run \`openbat chatbot list\` to see what is.`
|
|
366
419
|
);
|
|
367
420
|
}
|
|
421
|
+
announceScope(hit.name, hit.id);
|
|
368
422
|
return hit.id;
|
|
369
423
|
}
|
|
370
424
|
const lower = chatbotFlag.toLowerCase();
|
|
@@ -379,9 +433,13 @@ async function resolveChatbotId(api, chatbotFlag) {
|
|
|
379
433
|
`Multiple chatbots named "${chatbotFlag}". Use the UUID instead \u2014 list them with \`openbat chatbot list\`.`
|
|
380
434
|
);
|
|
381
435
|
}
|
|
436
|
+
announceScope(matches[0].name, matches[0].id);
|
|
382
437
|
return matches[0].id;
|
|
383
438
|
}
|
|
384
|
-
if (chatbots.length === 1)
|
|
439
|
+
if (chatbots.length === 1) {
|
|
440
|
+
announceScope(chatbots[0].name, chatbots[0].id);
|
|
441
|
+
return chatbots[0].id;
|
|
442
|
+
}
|
|
385
443
|
const lines = chatbots.map((c) => ` \u2022 ${c.name} ${c.id}`).join("\n");
|
|
386
444
|
fatal(
|
|
387
445
|
`This API key targets multiple chatbots. Pick one with \`openbat use <id>\` (persistent) or \`--chatbot <id>\` (one-off):
|
|
@@ -509,9 +567,10 @@ function exportCommand() {
|
|
|
509
567
|
`/api/v1/export/${id}?format=${format}`
|
|
510
568
|
);
|
|
511
569
|
if (opts.out) {
|
|
512
|
-
const { createWriteStream } = await
|
|
513
|
-
|
|
514
|
-
|
|
570
|
+
const [{ createWriteStream }, { Writable, Readable }] = await Promise.all([
|
|
571
|
+
import("fs"),
|
|
572
|
+
import("stream")
|
|
573
|
+
]);
|
|
515
574
|
const file = createWriteStream(opts.out);
|
|
516
575
|
const nodeReadable = Readable.fromWeb(
|
|
517
576
|
body
|
|
@@ -546,6 +605,13 @@ function detectKind(apiKey) {
|
|
|
546
605
|
if (apiKey.startsWith("ob_pat_")) return "pat";
|
|
547
606
|
return "unknown";
|
|
548
607
|
}
|
|
608
|
+
function activeChatbotDisplay(activeId, chatbots) {
|
|
609
|
+
if (!activeId) {
|
|
610
|
+
return chatbots.length > 1 ? `(not pinned \u2014 ${chatbots.length} reachable; run \`openbat use <id>\`)` : "(not pinned)";
|
|
611
|
+
}
|
|
612
|
+
const hit = chatbots.find((c) => c.id === activeId);
|
|
613
|
+
return hit ? `${hit.name} (${hit.id})` : `${activeId} (not in reachable set)`;
|
|
614
|
+
}
|
|
549
615
|
function authCommand() {
|
|
550
616
|
const cmd = new Command3("auth").description(
|
|
551
617
|
"Inspect the current credential's scope and audit history"
|
|
@@ -569,6 +635,10 @@ function authCommand() {
|
|
|
569
635
|
{
|
|
570
636
|
kind,
|
|
571
637
|
keyPrefix: `${cfg.apiKey.slice(0, 16)}\u2026<hidden>`,
|
|
638
|
+
activeChatbot: activeChatbotDisplay(
|
|
639
|
+
cfg.activeChatbotId,
|
|
640
|
+
chatbots.chatbots
|
|
641
|
+
),
|
|
572
642
|
orgs: orgs.orgs,
|
|
573
643
|
chatbots: chatbots.chatbots
|
|
574
644
|
},
|
|
@@ -582,6 +652,10 @@ function authCommand() {
|
|
|
582
652
|
{
|
|
583
653
|
kind,
|
|
584
654
|
keyPrefix: `${cfg.apiKey.slice(0, 16)}\u2026<hidden>`,
|
|
655
|
+
activeChatbot: activeChatbotDisplay(
|
|
656
|
+
cfg.activeChatbotId,
|
|
657
|
+
chatbots.chatbots
|
|
658
|
+
),
|
|
585
659
|
chatbots: chatbots.chatbots
|
|
586
660
|
},
|
|
587
661
|
{ json: !!globals.json }
|
|
@@ -594,8 +668,279 @@ function authCommand() {
|
|
|
594
668
|
return cmd;
|
|
595
669
|
}
|
|
596
670
|
|
|
597
|
-
// src/commands/
|
|
671
|
+
// src/commands/review.ts
|
|
598
672
|
import { Command as Command4 } from "commander";
|
|
673
|
+
function parseSinceToMinutes(s) {
|
|
674
|
+
const match = /^(\d+)\s*(m|h|d)?$/.exec(s.trim().toLowerCase());
|
|
675
|
+
if (!match) {
|
|
676
|
+
fatal(`Invalid --since "${s}". Use e.g. 45m, 6h, or 7d.`);
|
|
677
|
+
}
|
|
678
|
+
const n = Number(match[1]);
|
|
679
|
+
const unit = match[2] ?? "m";
|
|
680
|
+
const minutes = unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n;
|
|
681
|
+
if (minutes < 1) fatal("--since must be at least 1 minute.");
|
|
682
|
+
if (minutes > 43200) fatal("--since cannot exceed 30 days (43200m / 720h / 30d).");
|
|
683
|
+
return minutes;
|
|
684
|
+
}
|
|
685
|
+
var arrow = (t) => t === "up" ? "\u25B2" : t === "down" ? "\u25BC" : "=";
|
|
686
|
+
var pctDelta = (d) => d === null ? "(new)" : `${arrow(d >= 0 ? "up" : "down")} ${d >= 0 ? "+" : ""}${d}%`;
|
|
687
|
+
var ptsDelta = (d) => `${arrow(d >= 0 ? "up" : "down")} ${d >= 0 ? "+" : ""}${d} pts`;
|
|
688
|
+
function renderReview(r) {
|
|
689
|
+
const L = [];
|
|
690
|
+
const h = r.headline;
|
|
691
|
+
L.push(`OpenBat review \xB7 last ${r.window.since} \xB7 vs prior ${r.window.since}`);
|
|
692
|
+
L.push("");
|
|
693
|
+
L.push(` Conversations ${h.conversations.value} ${pctDelta(h.conversations.delta_pct)}`);
|
|
694
|
+
L.push(` Messages ${h.messages.value}`);
|
|
695
|
+
L.push(` Resolved ${h.outcomes.resolved.pct}% ${ptsDelta(h.outcomes.resolved.delta_pts)}`);
|
|
696
|
+
L.push(` Partial ${h.outcomes.partially_resolved.pct}% ${ptsDelta(h.outcomes.partially_resolved.delta_pts)}`);
|
|
697
|
+
L.push(` Failed ${h.outcomes.failed.pct}% ${ptsDelta(h.outcomes.failed.delta_pts)}`);
|
|
698
|
+
const sent = h.avg_sentiment.value ?? "\u2014";
|
|
699
|
+
const sentDelta = h.avg_sentiment.delta === null ? "" : ` ${arrow(h.avg_sentiment.delta >= 0 ? "up" : "down")} ${h.avg_sentiment.delta >= 0 ? "+" : ""}${h.avg_sentiment.delta}`;
|
|
700
|
+
L.push(` Avg sentiment ${sent}${sentDelta}`);
|
|
701
|
+
L.push(` Flagged ${h.flagged}`);
|
|
702
|
+
if (r.clusters.issues.length) {
|
|
703
|
+
L.push("");
|
|
704
|
+
L.push("Top issues");
|
|
705
|
+
for (const i of r.clusters.issues) {
|
|
706
|
+
const ans = i.answer_available.true + i.answer_available.false;
|
|
707
|
+
const ansStr = ans > 0 ? ` answer_available ${i.answer_available.true}/${ans}` : "";
|
|
708
|
+
L.push(
|
|
709
|
+
` ${i.type} ${i.count} ${arrow(i.trend)} high ${i.severity.high} \xB7 med ${i.severity.medium} \xB7 low ${i.severity.low}${ansStr}`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (r.clusters.flags.length) {
|
|
714
|
+
L.push("");
|
|
715
|
+
L.push(`Top flags ${r.clusters.flags.map((f) => `${f.value} ${f.count} ${arrow(f.trend)}`).join(" \xB7 ")}`);
|
|
716
|
+
}
|
|
717
|
+
if (r.clusters.intents.length) {
|
|
718
|
+
L.push(`Top intents ${r.clusters.intents.map((i) => `${i.value} ${i.count} ${arrow(i.trend)}`).join(" \xB7 ")}`);
|
|
719
|
+
}
|
|
720
|
+
const reps = [
|
|
721
|
+
...r.clusters.issues.flatMap((i) => i.representatives),
|
|
722
|
+
...r.clusters.failedOutcomes
|
|
723
|
+
];
|
|
724
|
+
if (reps.length) {
|
|
725
|
+
L.push("");
|
|
726
|
+
L.push("Representative failures");
|
|
727
|
+
for (const rep of reps.slice(0, 8)) {
|
|
728
|
+
const tag = rep.type ? `[${rep.type} \xB7 ${rep.severity ?? "?"} \xB7 answer_available=${rep.answer_available ?? "?"} \xB7 source=${rep.verification_source ?? "?"}]` : `[failed outcome]`;
|
|
729
|
+
L.push(` ${tag}`);
|
|
730
|
+
L.push(` conv ${rep.conversationId ?? "?"} msg ${rep.messageId}`);
|
|
731
|
+
if (rep.reasoning) L.push(` why: ${rep.reasoning}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
L.push("");
|
|
735
|
+
L.push("Drill in: openbat conversations show <id> \xB7 Fix: openbat-optimize skill");
|
|
736
|
+
return L.join("\n");
|
|
737
|
+
}
|
|
738
|
+
function reviewCommand() {
|
|
739
|
+
return new Command4("review").description(
|
|
740
|
+
"Daily eval digest for the active chatbot \u2014 flags, issues, outcomes + reasonings"
|
|
741
|
+
).option(
|
|
742
|
+
"--since <duration>",
|
|
743
|
+
"Look-back window: 45m, 6h, 7d (max 30d)",
|
|
744
|
+
"24h"
|
|
745
|
+
).action(async function(opts) {
|
|
746
|
+
try {
|
|
747
|
+
const windowMinutes = parseSinceToMinutes(opts.since);
|
|
748
|
+
const globals = this.optsWithGlobals();
|
|
749
|
+
const { api, chatbotFlag } = await client(globals);
|
|
750
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
751
|
+
const params = new URLSearchParams({
|
|
752
|
+
windowMinutes: String(windowMinutes)
|
|
753
|
+
});
|
|
754
|
+
const result = await api.get(
|
|
755
|
+
`/api/v1/chatbots/${id}/review?${params}`
|
|
756
|
+
);
|
|
757
|
+
if (this.optsWithGlobals().json) {
|
|
758
|
+
emit(result, { json: true });
|
|
759
|
+
} else {
|
|
760
|
+
process.stdout.write(renderReview(result) + "\n");
|
|
761
|
+
}
|
|
762
|
+
} catch (err) {
|
|
763
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/commands/prompts.ts
|
|
769
|
+
import { Command as Command5 } from "commander";
|
|
770
|
+
import { promises as fs2 } from "fs";
|
|
771
|
+
async function waitForActive(api, chatbotId, versionId) {
|
|
772
|
+
const deadlineMs = Date.now() + 15e3;
|
|
773
|
+
let attempt = 0;
|
|
774
|
+
while (Date.now() < deadlineMs) {
|
|
775
|
+
const res = await api.get(
|
|
776
|
+
`/api/v1/chatbots/${chatbotId}/prompts/active`
|
|
777
|
+
);
|
|
778
|
+
if (res.activeVersionId === versionId) {
|
|
779
|
+
process.stderr.write(
|
|
780
|
+
`
|
|
781
|
+
Confirmed live (server): active version is ${versionId}. Live SDK processes converge within their ~60s fetch-cache TTL.
|
|
782
|
+
`
|
|
783
|
+
);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
attempt++;
|
|
787
|
+
await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 2e3)));
|
|
788
|
+
}
|
|
789
|
+
process.stderr.write(
|
|
790
|
+
"\nPublished, but timed out (15s) waiting for server confirmation \u2014 the active pointer may still be propagating.\n"
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
function promptsCommand() {
|
|
794
|
+
const cmd = new Command5("prompts").description(
|
|
795
|
+
"Manage the live published system prompt (list / publish / activate / kill-switch)"
|
|
796
|
+
);
|
|
797
|
+
cmd.command("list").description("List system-prompt versions + live controls (active version, kill switch)").action(async function() {
|
|
798
|
+
try {
|
|
799
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
800
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
801
|
+
const result = await api.get(
|
|
802
|
+
`/api/v1/chatbots/${id}/prompts`
|
|
803
|
+
);
|
|
804
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
805
|
+
} catch (err) {
|
|
806
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
cmd.command("publish").description("Publish a template as the LIVE prompt (from --file or --text)").option("--file <path>", "Read the template text from a file").option("--text <text>", "Inline template text (prefer --file for long prompts)").option("--wait", "Block until the server confirms the new version is the active published prompt").action(async function(opts) {
|
|
810
|
+
try {
|
|
811
|
+
let templateText = opts.text;
|
|
812
|
+
if (opts.file) templateText = await fs2.readFile(opts.file, "utf8");
|
|
813
|
+
if (!templateText || !templateText.trim()) {
|
|
814
|
+
fatal("Provide the prompt text via --file <path> or --text <text>.");
|
|
815
|
+
}
|
|
816
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
817
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
818
|
+
const result = await api.post(
|
|
819
|
+
`/api/v1/chatbots/${id}/prompts`,
|
|
820
|
+
{ templateText }
|
|
821
|
+
);
|
|
822
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
823
|
+
if (opts.wait && result.versionId) {
|
|
824
|
+
await waitForActive(api, id, result.versionId);
|
|
825
|
+
} else {
|
|
826
|
+
process.stderr.write(
|
|
827
|
+
"\nPublished. Live within ~60s for chatbots that fetch their prompt from OpenBat.\n"
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
} catch (err) {
|
|
831
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
cmd.command("activate <versionId>").description("Point the live prompt at an existing version id (roll back/forward)").action(async function(versionId) {
|
|
835
|
+
try {
|
|
836
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
837
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
838
|
+
const result = await api.post(
|
|
839
|
+
`/api/v1/chatbots/${id}/prompts/activate`,
|
|
840
|
+
{ versionId }
|
|
841
|
+
);
|
|
842
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
843
|
+
} catch (err) {
|
|
844
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
cmd.command("kill-switch").description("Toggle the remote prompt kill switch (--on / --off)").option("--on", "Enable: SDK falls back to its hardcoded prompt").option("--off", "Disable: SDK serves the active published prompt again").action(async function(opts) {
|
|
848
|
+
try {
|
|
849
|
+
if (!!opts.on === !!opts.off) {
|
|
850
|
+
fatal("Pass exactly one of --on or --off.");
|
|
851
|
+
}
|
|
852
|
+
const enabled = !!opts.on;
|
|
853
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
854
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
855
|
+
const result = await api.post(
|
|
856
|
+
`/api/v1/chatbots/${id}/prompts/kill-switch`,
|
|
857
|
+
{ enabled }
|
|
858
|
+
);
|
|
859
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
860
|
+
} catch (err) {
|
|
861
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
cmd.command("show <versionId>").description("Show a version's full template text (list shows only hashes)").action(async function(versionId) {
|
|
865
|
+
try {
|
|
866
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
867
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
868
|
+
const result = await api.get(
|
|
869
|
+
`/api/v1/chatbots/${id}/prompts/${versionId}`
|
|
870
|
+
);
|
|
871
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
872
|
+
} catch (err) {
|
|
873
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
cmd.command("active").description("Show what the server resolves to RIGHT NOW (active version, kill switch)").action(async function() {
|
|
877
|
+
try {
|
|
878
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
879
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
880
|
+
const result = await api.get(
|
|
881
|
+
`/api/v1/chatbots/${id}/prompts/active`
|
|
882
|
+
);
|
|
883
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
884
|
+
} catch (err) {
|
|
885
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
return cmd;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/commands/backtests.ts
|
|
892
|
+
import { Command as Command6 } from "commander";
|
|
893
|
+
function backtestsCommand() {
|
|
894
|
+
const cmd = new Command6("backtests").description(
|
|
895
|
+
"Retest a candidate prompt against flagged conversations (create + poll status)"
|
|
896
|
+
);
|
|
897
|
+
cmd.command("create").description("Replay flagged conversations against a candidate prompt version").requiredOption("--name <name>", "Backtest name").requiredOption(
|
|
898
|
+
"--candidate-prompt <versionId>",
|
|
899
|
+
"Candidate prompt version id (from `openbat prompts list`)"
|
|
900
|
+
).requiredOption(
|
|
901
|
+
"--flags <csv>",
|
|
902
|
+
"Comma-separated flag values to sample (e.g. churn_risk,billing_issue)"
|
|
903
|
+
).option("--sample-size <n>", "How many flagged messages to replay (1-500)", "50").option("--from <iso>", "Only sample flags on/after this ISO timestamp").option("--to <iso>", "Only sample flags on/before this ISO timestamp").action(async function(opts) {
|
|
904
|
+
try {
|
|
905
|
+
const flagValues = opts.flags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
906
|
+
if (flagValues.length === 0) {
|
|
907
|
+
fatal("Provide at least one flag via --flags <csv>.");
|
|
908
|
+
}
|
|
909
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
910
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
911
|
+
const result = await api.post(
|
|
912
|
+
`/api/v1/chatbots/${id}/backtests`,
|
|
913
|
+
{
|
|
914
|
+
name: opts.name,
|
|
915
|
+
candidatePromptId: opts.candidatePrompt,
|
|
916
|
+
flagValues,
|
|
917
|
+
sampleSize: Number(opts.sampleSize),
|
|
918
|
+
timeRangeStart: opts.from,
|
|
919
|
+
timeRangeEnd: opts.to
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
923
|
+
process.stderr.write("\nStarted. Poll progress with: openbat backtests status <id>\n");
|
|
924
|
+
} catch (err) {
|
|
925
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
cmd.command("status <backtestId>").description("Show a backtest's progress + verdict tally").action(async function(backtestId) {
|
|
929
|
+
try {
|
|
930
|
+
const { api } = await client(this.optsWithGlobals());
|
|
931
|
+
const result = await api.get(
|
|
932
|
+
`/api/v1/backtests/${encodeURIComponent(backtestId)}/status`
|
|
933
|
+
);
|
|
934
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
935
|
+
} catch (err) {
|
|
936
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
return cmd;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/commands/login.ts
|
|
943
|
+
import { Command as Command7 } from "commander";
|
|
599
944
|
import os from "os";
|
|
600
945
|
import readline from "readline";
|
|
601
946
|
|
|
@@ -738,7 +1083,7 @@ async function runLoopbackServer(opts) {
|
|
|
738
1083
|
// src/commands/login.ts
|
|
739
1084
|
var HOSTNAME = os.hostname();
|
|
740
1085
|
function loginCommand() {
|
|
741
|
-
return new
|
|
1086
|
+
return new Command7("login").description("Sign in via browser and install an API key on this device").option("--device", "Use the device-code flow (for SSH / headless machines)").option(
|
|
742
1087
|
"--use <key>",
|
|
743
1088
|
"Skip the browser; install a PAT plaintext you already have"
|
|
744
1089
|
).action(async function(opts) {
|
|
@@ -928,9 +1273,9 @@ You have ${chatbots.length} chatbots. Pick one later with \`openbat use <id>\`.
|
|
|
928
1273
|
}
|
|
929
1274
|
|
|
930
1275
|
// src/commands/logout.ts
|
|
931
|
-
import { Command as
|
|
1276
|
+
import { Command as Command8 } from "commander";
|
|
932
1277
|
function logoutCommand() {
|
|
933
|
-
return new
|
|
1278
|
+
return new Command8("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
|
|
934
1279
|
try {
|
|
935
1280
|
const globals = this.optsWithGlobals();
|
|
936
1281
|
const cfg = await resolveConfig({
|
|
@@ -965,7 +1310,7 @@ function logoutCommand() {
|
|
|
965
1310
|
}
|
|
966
1311
|
|
|
967
1312
|
// src/commands/org.ts
|
|
968
|
-
import { Command as
|
|
1313
|
+
import { Command as Command9 } from "commander";
|
|
969
1314
|
async function client2(globals) {
|
|
970
1315
|
const cfg = await resolveConfig({
|
|
971
1316
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -980,7 +1325,7 @@ async function client2(globals) {
|
|
|
980
1325
|
return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
981
1326
|
}
|
|
982
1327
|
function orgCommand() {
|
|
983
|
-
const cmd = new
|
|
1328
|
+
const cmd = new Command9("org").description(
|
|
984
1329
|
"Manage the OpenBat tenant org (rename, members, invitations). Requires PAT."
|
|
985
1330
|
);
|
|
986
1331
|
cmd.command("list").description("List orgs the current PAT's user belongs to").action(async function() {
|
|
@@ -1088,7 +1433,7 @@ function orgCommand() {
|
|
|
1088
1433
|
}
|
|
1089
1434
|
|
|
1090
1435
|
// src/commands/write.ts
|
|
1091
|
-
import { Command as
|
|
1436
|
+
import { Command as Command10 } from "commander";
|
|
1092
1437
|
async function client3(globals) {
|
|
1093
1438
|
const cfg = await resolveConfig({
|
|
1094
1439
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -1121,7 +1466,7 @@ function surfacePlaintext(plaintext, label) {
|
|
|
1121
1466
|
);
|
|
1122
1467
|
}
|
|
1123
1468
|
function chatbotsCommand() {
|
|
1124
|
-
const cmd = new
|
|
1469
|
+
const cmd = new Command10("chatbots").description(
|
|
1125
1470
|
"List, create, delete chatbots in the current scope"
|
|
1126
1471
|
);
|
|
1127
1472
|
cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
|
|
@@ -1169,7 +1514,7 @@ function chatbotsCommand() {
|
|
|
1169
1514
|
return cmd;
|
|
1170
1515
|
}
|
|
1171
1516
|
function webhooksCommand() {
|
|
1172
|
-
const cmd = new
|
|
1517
|
+
const cmd = new Command10("webhooks").description("Manage webhooks for a chatbot");
|
|
1173
1518
|
cmd.command("list").action(async function() {
|
|
1174
1519
|
try {
|
|
1175
1520
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1213,7 +1558,7 @@ function webhooksCommand() {
|
|
|
1213
1558
|
return cmd;
|
|
1214
1559
|
}
|
|
1215
1560
|
function settingsCommand() {
|
|
1216
|
-
const cmd = new
|
|
1561
|
+
const cmd = new Command10("settings").description(
|
|
1217
1562
|
"Manage chatbot settings + per-chatbot keys"
|
|
1218
1563
|
);
|
|
1219
1564
|
const keys = cmd.command("keys").description("Manage API keys for a chatbot");
|
|
@@ -1307,7 +1652,7 @@ function settingsCommand() {
|
|
|
1307
1652
|
return cmd;
|
|
1308
1653
|
}
|
|
1309
1654
|
function workflowsCommand() {
|
|
1310
|
-
const cmd = new
|
|
1655
|
+
const cmd = new Command10("workflows").description("Manage workflows");
|
|
1311
1656
|
cmd.command("list").action(async function() {
|
|
1312
1657
|
try {
|
|
1313
1658
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1347,7 +1692,7 @@ function workflowsCommand() {
|
|
|
1347
1692
|
return cmd;
|
|
1348
1693
|
}
|
|
1349
1694
|
function reportsCommand() {
|
|
1350
|
-
const cmd = new
|
|
1695
|
+
const cmd = new Command10("reports").description("Manage AI reports");
|
|
1351
1696
|
cmd.command("list").action(async function() {
|
|
1352
1697
|
try {
|
|
1353
1698
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1385,7 +1730,7 @@ Created report. View it (org members only):
|
|
|
1385
1730
|
return cmd;
|
|
1386
1731
|
}
|
|
1387
1732
|
function analysisCommand() {
|
|
1388
|
-
const cmd = new
|
|
1733
|
+
const cmd = new Command10("analysis").description("Manage analysis definitions");
|
|
1389
1734
|
cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
|
|
1390
1735
|
try {
|
|
1391
1736
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1427,7 +1772,7 @@ function analysisCommand() {
|
|
|
1427
1772
|
return cmd;
|
|
1428
1773
|
}
|
|
1429
1774
|
function usersCommand() {
|
|
1430
|
-
const cmd = new
|
|
1775
|
+
const cmd = new Command10("users").description(
|
|
1431
1776
|
"List external users (chatbot customers) with health metrics"
|
|
1432
1777
|
);
|
|
1433
1778
|
cmd.command("list").option("--from <iso>").option("--to <iso>").option("--days <n>", "Convenience: last N days").option("--search <q>").option("--limit <n>", "Page size", "20").action(async function(opts) {
|
|
@@ -1460,7 +1805,7 @@ Total: ${result.total}
|
|
|
1460
1805
|
return cmd;
|
|
1461
1806
|
}
|
|
1462
1807
|
function sdkCommand() {
|
|
1463
|
-
const cmd = new
|
|
1808
|
+
const cmd = new Command10("sdk").description(
|
|
1464
1809
|
"Help install and verify the OpenBat SDK in a target project"
|
|
1465
1810
|
);
|
|
1466
1811
|
cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
|
|
@@ -1570,10 +1915,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
|
|
|
1570
1915
|
}
|
|
1571
1916
|
|
|
1572
1917
|
// src/index.ts
|
|
1573
|
-
var program = new
|
|
1918
|
+
var program = new Command11();
|
|
1574
1919
|
program.name("openbat").description(
|
|
1575
1920
|
"Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
|
|
1576
|
-
).version("0.
|
|
1921
|
+
).version("0.4.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
|
|
1577
1922
|
"--base-url <url>",
|
|
1578
1923
|
"Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
|
|
1579
1924
|
).option(
|
|
@@ -1592,6 +1937,9 @@ program.addCommand(orgCommand());
|
|
|
1592
1937
|
program.addCommand(chatbotCommand());
|
|
1593
1938
|
program.addCommand(chatbotsCommand());
|
|
1594
1939
|
program.addCommand(conversationsCommand());
|
|
1940
|
+
program.addCommand(reviewCommand());
|
|
1941
|
+
program.addCommand(promptsCommand());
|
|
1942
|
+
program.addCommand(backtestsCommand());
|
|
1595
1943
|
program.addCommand(usersCommand());
|
|
1596
1944
|
program.addCommand(settingsCommand());
|
|
1597
1945
|
program.addCommand(webhooksCommand());
|