@openbat/cli 0.2.3 → 0.3.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-KIY62R3O.mjs} +1 -1
- package/dist/index.js +271 -25
- package/dist/index.mjs +271 -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.3.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.3.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_commander10 = 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.3.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,178 @@ 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
|
+
function promptsCommand() {
|
|
950
|
+
const cmd = new import_commander5.Command("prompts").description(
|
|
951
|
+
"Manage the live published system prompt (list / publish / activate / kill-switch)"
|
|
952
|
+
);
|
|
953
|
+
cmd.command("list").description("List system-prompt versions + live controls (active version, kill switch)").action(async function() {
|
|
954
|
+
try {
|
|
955
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
956
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
957
|
+
const result = await api.get(
|
|
958
|
+
`/api/v1/chatbots/${id}/prompts`
|
|
959
|
+
);
|
|
960
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
961
|
+
} catch (err) {
|
|
962
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
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)").action(async function(opts) {
|
|
966
|
+
try {
|
|
967
|
+
let templateText = opts.text;
|
|
968
|
+
if (opts.file) templateText = await import_node_fs2.promises.readFile(opts.file, "utf8");
|
|
969
|
+
if (!templateText || !templateText.trim()) {
|
|
970
|
+
fatal("Provide the prompt text via --file <path> or --text <text>.");
|
|
971
|
+
}
|
|
972
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
973
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
974
|
+
const result = await api.post(
|
|
975
|
+
`/api/v1/chatbots/${id}/prompts`,
|
|
976
|
+
{ templateText }
|
|
977
|
+
);
|
|
978
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
979
|
+
process.stderr.write(
|
|
980
|
+
"\nPublished. Live within ~60s for chatbots that fetch their prompt from OpenBat.\n"
|
|
981
|
+
);
|
|
982
|
+
} catch (err) {
|
|
983
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
cmd.command("activate <versionId>").description("Point the live prompt at an existing version id (roll back/forward)").action(async function(versionId) {
|
|
987
|
+
try {
|
|
988
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
989
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
990
|
+
const result = await api.post(
|
|
991
|
+
`/api/v1/chatbots/${id}/prompts/activate`,
|
|
992
|
+
{ versionId }
|
|
993
|
+
);
|
|
994
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
995
|
+
} catch (err) {
|
|
996
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
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) {
|
|
1000
|
+
try {
|
|
1001
|
+
if (!!opts.on === !!opts.off) {
|
|
1002
|
+
fatal("Pass exactly one of --on or --off.");
|
|
1003
|
+
}
|
|
1004
|
+
const enabled = !!opts.on;
|
|
1005
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
1006
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
1007
|
+
const result = await api.post(
|
|
1008
|
+
`/api/v1/chatbots/${id}/prompts/kill-switch`,
|
|
1009
|
+
{ enabled }
|
|
1010
|
+
);
|
|
1011
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
return cmd;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/commands/login.ts
|
|
1020
|
+
var import_commander6 = require("commander");
|
|
777
1021
|
var import_node_os2 = __toESM(require("os"));
|
|
778
1022
|
var import_node_readline = __toESM(require("readline"));
|
|
779
1023
|
|
|
@@ -916,7 +1160,7 @@ async function runLoopbackServer(opts) {
|
|
|
916
1160
|
// src/commands/login.ts
|
|
917
1161
|
var HOSTNAME = import_node_os2.default.hostname();
|
|
918
1162
|
function loginCommand() {
|
|
919
|
-
return new
|
|
1163
|
+
return new import_commander6.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
1164
|
"--use <key>",
|
|
921
1165
|
"Skip the browser; install a PAT plaintext you already have"
|
|
922
1166
|
).action(async function(opts) {
|
|
@@ -1106,9 +1350,9 @@ You have ${chatbots.length} chatbots. Pick one later with \`openbat use <id>\`.
|
|
|
1106
1350
|
}
|
|
1107
1351
|
|
|
1108
1352
|
// src/commands/logout.ts
|
|
1109
|
-
var
|
|
1353
|
+
var import_commander7 = require("commander");
|
|
1110
1354
|
function logoutCommand() {
|
|
1111
|
-
return new
|
|
1355
|
+
return new import_commander7.Command("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
|
|
1112
1356
|
try {
|
|
1113
1357
|
const globals = this.optsWithGlobals();
|
|
1114
1358
|
const cfg = await resolveConfig({
|
|
@@ -1143,7 +1387,7 @@ function logoutCommand() {
|
|
|
1143
1387
|
}
|
|
1144
1388
|
|
|
1145
1389
|
// src/commands/org.ts
|
|
1146
|
-
var
|
|
1390
|
+
var import_commander8 = require("commander");
|
|
1147
1391
|
async function client2(globals) {
|
|
1148
1392
|
const cfg = await resolveConfig({
|
|
1149
1393
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -1158,7 +1402,7 @@ async function client2(globals) {
|
|
|
1158
1402
|
return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
1159
1403
|
}
|
|
1160
1404
|
function orgCommand() {
|
|
1161
|
-
const cmd = new
|
|
1405
|
+
const cmd = new import_commander8.Command("org").description(
|
|
1162
1406
|
"Manage the OpenBat tenant org (rename, members, invitations). Requires PAT."
|
|
1163
1407
|
);
|
|
1164
1408
|
cmd.command("list").description("List orgs the current PAT's user belongs to").action(async function() {
|
|
@@ -1266,7 +1510,7 @@ function orgCommand() {
|
|
|
1266
1510
|
}
|
|
1267
1511
|
|
|
1268
1512
|
// src/commands/write.ts
|
|
1269
|
-
var
|
|
1513
|
+
var import_commander9 = require("commander");
|
|
1270
1514
|
async function client3(globals) {
|
|
1271
1515
|
const cfg = await resolveConfig({
|
|
1272
1516
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -1299,7 +1543,7 @@ function surfacePlaintext(plaintext, label) {
|
|
|
1299
1543
|
);
|
|
1300
1544
|
}
|
|
1301
1545
|
function chatbotsCommand() {
|
|
1302
|
-
const cmd = new
|
|
1546
|
+
const cmd = new import_commander9.Command("chatbots").description(
|
|
1303
1547
|
"List, create, delete chatbots in the current scope"
|
|
1304
1548
|
);
|
|
1305
1549
|
cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
|
|
@@ -1347,7 +1591,7 @@ function chatbotsCommand() {
|
|
|
1347
1591
|
return cmd;
|
|
1348
1592
|
}
|
|
1349
1593
|
function webhooksCommand() {
|
|
1350
|
-
const cmd = new
|
|
1594
|
+
const cmd = new import_commander9.Command("webhooks").description("Manage webhooks for a chatbot");
|
|
1351
1595
|
cmd.command("list").action(async function() {
|
|
1352
1596
|
try {
|
|
1353
1597
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1391,7 +1635,7 @@ function webhooksCommand() {
|
|
|
1391
1635
|
return cmd;
|
|
1392
1636
|
}
|
|
1393
1637
|
function settingsCommand() {
|
|
1394
|
-
const cmd = new
|
|
1638
|
+
const cmd = new import_commander9.Command("settings").description(
|
|
1395
1639
|
"Manage chatbot settings + per-chatbot keys"
|
|
1396
1640
|
);
|
|
1397
1641
|
const keys = cmd.command("keys").description("Manage API keys for a chatbot");
|
|
@@ -1485,7 +1729,7 @@ function settingsCommand() {
|
|
|
1485
1729
|
return cmd;
|
|
1486
1730
|
}
|
|
1487
1731
|
function workflowsCommand() {
|
|
1488
|
-
const cmd = new
|
|
1732
|
+
const cmd = new import_commander9.Command("workflows").description("Manage workflows");
|
|
1489
1733
|
cmd.command("list").action(async function() {
|
|
1490
1734
|
try {
|
|
1491
1735
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1525,7 +1769,7 @@ function workflowsCommand() {
|
|
|
1525
1769
|
return cmd;
|
|
1526
1770
|
}
|
|
1527
1771
|
function reportsCommand() {
|
|
1528
|
-
const cmd = new
|
|
1772
|
+
const cmd = new import_commander9.Command("reports").description("Manage AI reports");
|
|
1529
1773
|
cmd.command("list").action(async function() {
|
|
1530
1774
|
try {
|
|
1531
1775
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1563,7 +1807,7 @@ Created report. View it (org members only):
|
|
|
1563
1807
|
return cmd;
|
|
1564
1808
|
}
|
|
1565
1809
|
function analysisCommand() {
|
|
1566
|
-
const cmd = new
|
|
1810
|
+
const cmd = new import_commander9.Command("analysis").description("Manage analysis definitions");
|
|
1567
1811
|
cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
|
|
1568
1812
|
try {
|
|
1569
1813
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1605,7 +1849,7 @@ function analysisCommand() {
|
|
|
1605
1849
|
return cmd;
|
|
1606
1850
|
}
|
|
1607
1851
|
function usersCommand() {
|
|
1608
|
-
const cmd = new
|
|
1852
|
+
const cmd = new import_commander9.Command("users").description(
|
|
1609
1853
|
"List external users (chatbot customers) with health metrics"
|
|
1610
1854
|
);
|
|
1611
1855
|
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 +1882,7 @@ Total: ${result.total}
|
|
|
1638
1882
|
return cmd;
|
|
1639
1883
|
}
|
|
1640
1884
|
function sdkCommand() {
|
|
1641
|
-
const cmd = new
|
|
1885
|
+
const cmd = new import_commander9.Command("sdk").description(
|
|
1642
1886
|
"Help install and verify the OpenBat SDK in a target project"
|
|
1643
1887
|
);
|
|
1644
1888
|
cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
|
|
@@ -1748,10 +1992,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
|
|
|
1748
1992
|
}
|
|
1749
1993
|
|
|
1750
1994
|
// src/index.ts
|
|
1751
|
-
var program = new
|
|
1995
|
+
var program = new import_commander10.Command();
|
|
1752
1996
|
program.name("openbat").description(
|
|
1753
1997
|
"Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
|
|
1754
|
-
).version("0.
|
|
1998
|
+
).version("0.3.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
|
|
1755
1999
|
"--base-url <url>",
|
|
1756
2000
|
"Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
|
|
1757
2001
|
).option(
|
|
@@ -1770,6 +2014,8 @@ program.addCommand(orgCommand());
|
|
|
1770
2014
|
program.addCommand(chatbotCommand());
|
|
1771
2015
|
program.addCommand(chatbotsCommand());
|
|
1772
2016
|
program.addCommand(conversationsCommand());
|
|
2017
|
+
program.addCommand(reviewCommand());
|
|
2018
|
+
program.addCommand(promptsCommand());
|
|
1773
2019
|
program.addCommand(usersCommand());
|
|
1774
2020
|
program.addCommand(settingsCommand());
|
|
1775
2021
|
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-KIY62R3O.mjs";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command10 } 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,178 @@ 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
|
+
function promptsCommand() {
|
|
772
|
+
const cmd = new Command5("prompts").description(
|
|
773
|
+
"Manage the live published system prompt (list / publish / activate / kill-switch)"
|
|
774
|
+
);
|
|
775
|
+
cmd.command("list").description("List system-prompt versions + live controls (active version, kill switch)").action(async function() {
|
|
776
|
+
try {
|
|
777
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
778
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
779
|
+
const result = await api.get(
|
|
780
|
+
`/api/v1/chatbots/${id}/prompts`
|
|
781
|
+
);
|
|
782
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
783
|
+
} catch (err) {
|
|
784
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
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)").action(async function(opts) {
|
|
788
|
+
try {
|
|
789
|
+
let templateText = opts.text;
|
|
790
|
+
if (opts.file) templateText = await fs2.readFile(opts.file, "utf8");
|
|
791
|
+
if (!templateText || !templateText.trim()) {
|
|
792
|
+
fatal("Provide the prompt text via --file <path> or --text <text>.");
|
|
793
|
+
}
|
|
794
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
795
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
796
|
+
const result = await api.post(
|
|
797
|
+
`/api/v1/chatbots/${id}/prompts`,
|
|
798
|
+
{ templateText }
|
|
799
|
+
);
|
|
800
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
801
|
+
process.stderr.write(
|
|
802
|
+
"\nPublished. Live within ~60s for chatbots that fetch their prompt from OpenBat.\n"
|
|
803
|
+
);
|
|
804
|
+
} catch (err) {
|
|
805
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
cmd.command("activate <versionId>").description("Point the live prompt at an existing version id (roll back/forward)").action(async function(versionId) {
|
|
809
|
+
try {
|
|
810
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
811
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
812
|
+
const result = await api.post(
|
|
813
|
+
`/api/v1/chatbots/${id}/prompts/activate`,
|
|
814
|
+
{ versionId }
|
|
815
|
+
);
|
|
816
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
817
|
+
} catch (err) {
|
|
818
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
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) {
|
|
822
|
+
try {
|
|
823
|
+
if (!!opts.on === !!opts.off) {
|
|
824
|
+
fatal("Pass exactly one of --on or --off.");
|
|
825
|
+
}
|
|
826
|
+
const enabled = !!opts.on;
|
|
827
|
+
const { api, chatbotFlag } = await client(this.optsWithGlobals());
|
|
828
|
+
const id = await resolveChatbotId(api, chatbotFlag);
|
|
829
|
+
const result = await api.post(
|
|
830
|
+
`/api/v1/chatbots/${id}/prompts/kill-switch`,
|
|
831
|
+
{ enabled }
|
|
832
|
+
);
|
|
833
|
+
emit(result, { json: !!this.optsWithGlobals().json });
|
|
834
|
+
} catch (err) {
|
|
835
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
return cmd;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/commands/login.ts
|
|
842
|
+
import { Command as Command6 } from "commander";
|
|
599
843
|
import os from "os";
|
|
600
844
|
import readline from "readline";
|
|
601
845
|
|
|
@@ -738,7 +982,7 @@ async function runLoopbackServer(opts) {
|
|
|
738
982
|
// src/commands/login.ts
|
|
739
983
|
var HOSTNAME = os.hostname();
|
|
740
984
|
function loginCommand() {
|
|
741
|
-
return new
|
|
985
|
+
return new Command6("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
986
|
"--use <key>",
|
|
743
987
|
"Skip the browser; install a PAT plaintext you already have"
|
|
744
988
|
).action(async function(opts) {
|
|
@@ -928,9 +1172,9 @@ You have ${chatbots.length} chatbots. Pick one later with \`openbat use <id>\`.
|
|
|
928
1172
|
}
|
|
929
1173
|
|
|
930
1174
|
// src/commands/logout.ts
|
|
931
|
-
import { Command as
|
|
1175
|
+
import { Command as Command7 } from "commander";
|
|
932
1176
|
function logoutCommand() {
|
|
933
|
-
return new
|
|
1177
|
+
return new Command7("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
|
|
934
1178
|
try {
|
|
935
1179
|
const globals = this.optsWithGlobals();
|
|
936
1180
|
const cfg = await resolveConfig({
|
|
@@ -965,7 +1209,7 @@ function logoutCommand() {
|
|
|
965
1209
|
}
|
|
966
1210
|
|
|
967
1211
|
// src/commands/org.ts
|
|
968
|
-
import { Command as
|
|
1212
|
+
import { Command as Command8 } from "commander";
|
|
969
1213
|
async function client2(globals) {
|
|
970
1214
|
const cfg = await resolveConfig({
|
|
971
1215
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -980,7 +1224,7 @@ async function client2(globals) {
|
|
|
980
1224
|
return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
981
1225
|
}
|
|
982
1226
|
function orgCommand() {
|
|
983
|
-
const cmd = new
|
|
1227
|
+
const cmd = new Command8("org").description(
|
|
984
1228
|
"Manage the OpenBat tenant org (rename, members, invitations). Requires PAT."
|
|
985
1229
|
);
|
|
986
1230
|
cmd.command("list").description("List orgs the current PAT's user belongs to").action(async function() {
|
|
@@ -1088,7 +1332,7 @@ function orgCommand() {
|
|
|
1088
1332
|
}
|
|
1089
1333
|
|
|
1090
1334
|
// src/commands/write.ts
|
|
1091
|
-
import { Command as
|
|
1335
|
+
import { Command as Command9 } from "commander";
|
|
1092
1336
|
async function client3(globals) {
|
|
1093
1337
|
const cfg = await resolveConfig({
|
|
1094
1338
|
apiKeyFlag: globals.apiKey ?? null,
|
|
@@ -1121,7 +1365,7 @@ function surfacePlaintext(plaintext, label) {
|
|
|
1121
1365
|
);
|
|
1122
1366
|
}
|
|
1123
1367
|
function chatbotsCommand() {
|
|
1124
|
-
const cmd = new
|
|
1368
|
+
const cmd = new Command9("chatbots").description(
|
|
1125
1369
|
"List, create, delete chatbots in the current scope"
|
|
1126
1370
|
);
|
|
1127
1371
|
cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
|
|
@@ -1169,7 +1413,7 @@ function chatbotsCommand() {
|
|
|
1169
1413
|
return cmd;
|
|
1170
1414
|
}
|
|
1171
1415
|
function webhooksCommand() {
|
|
1172
|
-
const cmd = new
|
|
1416
|
+
const cmd = new Command9("webhooks").description("Manage webhooks for a chatbot");
|
|
1173
1417
|
cmd.command("list").action(async function() {
|
|
1174
1418
|
try {
|
|
1175
1419
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1213,7 +1457,7 @@ function webhooksCommand() {
|
|
|
1213
1457
|
return cmd;
|
|
1214
1458
|
}
|
|
1215
1459
|
function settingsCommand() {
|
|
1216
|
-
const cmd = new
|
|
1460
|
+
const cmd = new Command9("settings").description(
|
|
1217
1461
|
"Manage chatbot settings + per-chatbot keys"
|
|
1218
1462
|
);
|
|
1219
1463
|
const keys = cmd.command("keys").description("Manage API keys for a chatbot");
|
|
@@ -1307,7 +1551,7 @@ function settingsCommand() {
|
|
|
1307
1551
|
return cmd;
|
|
1308
1552
|
}
|
|
1309
1553
|
function workflowsCommand() {
|
|
1310
|
-
const cmd = new
|
|
1554
|
+
const cmd = new Command9("workflows").description("Manage workflows");
|
|
1311
1555
|
cmd.command("list").action(async function() {
|
|
1312
1556
|
try {
|
|
1313
1557
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1347,7 +1591,7 @@ function workflowsCommand() {
|
|
|
1347
1591
|
return cmd;
|
|
1348
1592
|
}
|
|
1349
1593
|
function reportsCommand() {
|
|
1350
|
-
const cmd = new
|
|
1594
|
+
const cmd = new Command9("reports").description("Manage AI reports");
|
|
1351
1595
|
cmd.command("list").action(async function() {
|
|
1352
1596
|
try {
|
|
1353
1597
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1385,7 +1629,7 @@ Created report. View it (org members only):
|
|
|
1385
1629
|
return cmd;
|
|
1386
1630
|
}
|
|
1387
1631
|
function analysisCommand() {
|
|
1388
|
-
const cmd = new
|
|
1632
|
+
const cmd = new Command9("analysis").description("Manage analysis definitions");
|
|
1389
1633
|
cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
|
|
1390
1634
|
try {
|
|
1391
1635
|
const chatbotId = requireChatbotId(this);
|
|
@@ -1427,7 +1671,7 @@ function analysisCommand() {
|
|
|
1427
1671
|
return cmd;
|
|
1428
1672
|
}
|
|
1429
1673
|
function usersCommand() {
|
|
1430
|
-
const cmd = new
|
|
1674
|
+
const cmd = new Command9("users").description(
|
|
1431
1675
|
"List external users (chatbot customers) with health metrics"
|
|
1432
1676
|
);
|
|
1433
1677
|
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 +1704,7 @@ Total: ${result.total}
|
|
|
1460
1704
|
return cmd;
|
|
1461
1705
|
}
|
|
1462
1706
|
function sdkCommand() {
|
|
1463
|
-
const cmd = new
|
|
1707
|
+
const cmd = new Command9("sdk").description(
|
|
1464
1708
|
"Help install and verify the OpenBat SDK in a target project"
|
|
1465
1709
|
);
|
|
1466
1710
|
cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
|
|
@@ -1570,10 +1814,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
|
|
|
1570
1814
|
}
|
|
1571
1815
|
|
|
1572
1816
|
// src/index.ts
|
|
1573
|
-
var program = new
|
|
1817
|
+
var program = new Command10();
|
|
1574
1818
|
program.name("openbat").description(
|
|
1575
1819
|
"Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
|
|
1576
|
-
).version("0.
|
|
1820
|
+
).version("0.3.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
|
|
1577
1821
|
"--base-url <url>",
|
|
1578
1822
|
"Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
|
|
1579
1823
|
).option(
|
|
@@ -1592,6 +1836,8 @@ program.addCommand(orgCommand());
|
|
|
1592
1836
|
program.addCommand(chatbotCommand());
|
|
1593
1837
|
program.addCommand(chatbotsCommand());
|
|
1594
1838
|
program.addCommand(conversationsCommand());
|
|
1839
|
+
program.addCommand(reviewCommand());
|
|
1840
|
+
program.addCommand(promptsCommand());
|
|
1595
1841
|
program.addCommand(usersCommand());
|
|
1596
1842
|
program.addCommand(settingsCommand());
|
|
1597
1843
|
program.addCommand(webhooksCommand());
|