@openbat/cli 0.2.2 → 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-MCDJLQI2.mjs → chunk-KIY62R3O.mjs} +1 -1
- package/dist/index.js +335 -60
- package/dist/index.mjs +335 -60
- package/package.json +5 -2
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,
|
|
@@ -1099,6 +1343,15 @@ async function client3(globals) {
|
|
|
1099
1343
|
}
|
|
1100
1344
|
return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
1101
1345
|
}
|
|
1346
|
+
function requireChatbotId(cmd) {
|
|
1347
|
+
const globals = cmd.optsWithGlobals();
|
|
1348
|
+
if (!globals.chatbot) {
|
|
1349
|
+
fatal(
|
|
1350
|
+
"--chatbot <id> is required. Pass it inline or set a default with `openbat use <id>`."
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
return globals.chatbot;
|
|
1354
|
+
}
|
|
1102
1355
|
function surfacePlaintext(plaintext, label) {
|
|
1103
1356
|
process.stderr.write(
|
|
1104
1357
|
`
|
|
@@ -1112,7 +1365,7 @@ function surfacePlaintext(plaintext, label) {
|
|
|
1112
1365
|
);
|
|
1113
1366
|
}
|
|
1114
1367
|
function chatbotsCommand() {
|
|
1115
|
-
const cmd = new
|
|
1368
|
+
const cmd = new Command9("chatbots").description(
|
|
1116
1369
|
"List, create, delete chatbots in the current scope"
|
|
1117
1370
|
);
|
|
1118
1371
|
cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
|
|
@@ -1160,24 +1413,26 @@ function chatbotsCommand() {
|
|
|
1160
1413
|
return cmd;
|
|
1161
1414
|
}
|
|
1162
1415
|
function webhooksCommand() {
|
|
1163
|
-
const cmd = new
|
|
1164
|
-
cmd.command("list").
|
|
1416
|
+
const cmd = new Command9("webhooks").description("Manage webhooks for a chatbot");
|
|
1417
|
+
cmd.command("list").action(async function() {
|
|
1165
1418
|
try {
|
|
1419
|
+
const chatbotId = requireChatbotId(this);
|
|
1166
1420
|
const globals = this.optsWithGlobals();
|
|
1167
1421
|
const c = await client3(globals);
|
|
1168
1422
|
const result = await c.get(
|
|
1169
|
-
`/api/v1/chatbots/${
|
|
1423
|
+
`/api/v1/chatbots/${chatbotId}/webhooks`
|
|
1170
1424
|
);
|
|
1171
1425
|
emit(result.webhooks, { json: !!globals.json });
|
|
1172
1426
|
} catch (err) {
|
|
1173
1427
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1174
1428
|
}
|
|
1175
1429
|
});
|
|
1176
|
-
cmd.command("create").requiredOption("--
|
|
1430
|
+
cmd.command("create").requiredOption("--name <name>").requiredOption("--url <url>").option("--type <type>", "discord | slack | custom", "custom").action(async function(opts) {
|
|
1177
1431
|
try {
|
|
1432
|
+
const chatbotId = requireChatbotId(this);
|
|
1178
1433
|
const globals = this.optsWithGlobals();
|
|
1179
1434
|
const c = await client3(globals);
|
|
1180
|
-
const result = await c.post(`/api/v1/chatbots/${
|
|
1435
|
+
const result = await c.post(`/api/v1/chatbots/${chatbotId}/webhooks`, {
|
|
1181
1436
|
name: opts.name,
|
|
1182
1437
|
url: opts.url,
|
|
1183
1438
|
type: opts.type
|
|
@@ -1188,11 +1443,12 @@ function webhooksCommand() {
|
|
|
1188
1443
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1189
1444
|
}
|
|
1190
1445
|
});
|
|
1191
|
-
cmd.command("delete").requiredOption("--
|
|
1446
|
+
cmd.command("delete").requiredOption("--webhook <id>", "Webhook id").action(async function(opts) {
|
|
1192
1447
|
try {
|
|
1448
|
+
const chatbotId = requireChatbotId(this);
|
|
1193
1449
|
const globals = this.optsWithGlobals();
|
|
1194
1450
|
const c = await client3(globals);
|
|
1195
|
-
await c.delete(`/api/v1/chatbots/${
|
|
1451
|
+
await c.delete(`/api/v1/chatbots/${chatbotId}/webhooks/${opts.webhook}`);
|
|
1196
1452
|
emit({ ok: true, deleted: opts.webhook }, { json: !!globals.json });
|
|
1197
1453
|
} catch (err) {
|
|
1198
1454
|
fatal(err instanceof Error ? err.message : String(err));
|
|
@@ -1201,16 +1457,17 @@ function webhooksCommand() {
|
|
|
1201
1457
|
return cmd;
|
|
1202
1458
|
}
|
|
1203
1459
|
function settingsCommand() {
|
|
1204
|
-
const cmd = new
|
|
1460
|
+
const cmd = new Command9("settings").description(
|
|
1205
1461
|
"Manage chatbot settings + per-chatbot keys"
|
|
1206
1462
|
);
|
|
1207
1463
|
const keys = cmd.command("keys").description("Manage API keys for a chatbot");
|
|
1208
|
-
keys.command("rotate-ingest").
|
|
1464
|
+
keys.command("rotate-ingest").action(async function() {
|
|
1209
1465
|
try {
|
|
1466
|
+
const chatbotId = requireChatbotId(this);
|
|
1210
1467
|
const globals = this.optsWithGlobals();
|
|
1211
1468
|
const c = await client3(globals);
|
|
1212
1469
|
const result = await c.post(
|
|
1213
|
-
`/api/v1/chatbots/${
|
|
1470
|
+
`/api/v1/chatbots/${chatbotId}/keys/ingest/rotate`,
|
|
1214
1471
|
{}
|
|
1215
1472
|
);
|
|
1216
1473
|
surfacePlaintext(result.plaintext, "New ingest key (ob_live_*)");
|
|
@@ -1219,12 +1476,13 @@ function settingsCommand() {
|
|
|
1219
1476
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1220
1477
|
}
|
|
1221
1478
|
});
|
|
1222
|
-
keys.command("generate-read").
|
|
1479
|
+
keys.command("generate-read").action(async function() {
|
|
1223
1480
|
try {
|
|
1481
|
+
const chatbotId = requireChatbotId(this);
|
|
1224
1482
|
const globals = this.optsWithGlobals();
|
|
1225
1483
|
const c = await client3(globals);
|
|
1226
1484
|
const result = await c.post(
|
|
1227
|
-
`/api/v1/chatbots/${
|
|
1485
|
+
`/api/v1/chatbots/${chatbotId}/keys/read`,
|
|
1228
1486
|
{}
|
|
1229
1487
|
);
|
|
1230
1488
|
surfacePlaintext(result.plaintext, "New read key (ob_read_*)");
|
|
@@ -1233,11 +1491,12 @@ function settingsCommand() {
|
|
|
1233
1491
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1234
1492
|
}
|
|
1235
1493
|
});
|
|
1236
|
-
keys.command("generate-admin").requiredOption("--
|
|
1494
|
+
keys.command("generate-admin").requiredOption("--name <name>", "Human-friendly name (e.g. 'CI key')").option("--expires-in-days <n>", "Auto-expire after N days").action(async function(opts) {
|
|
1237
1495
|
try {
|
|
1496
|
+
const chatbotId = requireChatbotId(this);
|
|
1238
1497
|
const globals = this.optsWithGlobals();
|
|
1239
1498
|
const c = await client3(globals);
|
|
1240
|
-
const result = await c.post(`/api/v1/chatbots/${
|
|
1499
|
+
const result = await c.post(`/api/v1/chatbots/${chatbotId}/admin-keys`, {
|
|
1241
1500
|
name: opts.name,
|
|
1242
1501
|
expiresInDays: opts.expiresInDays ? Number(opts.expiresInDays) : void 0
|
|
1243
1502
|
});
|
|
@@ -1247,30 +1506,33 @@ function settingsCommand() {
|
|
|
1247
1506
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1248
1507
|
}
|
|
1249
1508
|
});
|
|
1250
|
-
keys.command("list-admin").
|
|
1509
|
+
keys.command("list-admin").action(async function() {
|
|
1251
1510
|
try {
|
|
1511
|
+
const chatbotId = requireChatbotId(this);
|
|
1252
1512
|
const globals = this.optsWithGlobals();
|
|
1253
1513
|
const c = await client3(globals);
|
|
1254
1514
|
const result = await c.get(
|
|
1255
|
-
`/api/v1/chatbots/${
|
|
1515
|
+
`/api/v1/chatbots/${chatbotId}/admin-keys`
|
|
1256
1516
|
);
|
|
1257
1517
|
emit(result.keys, { json: !!globals.json });
|
|
1258
1518
|
} catch (err) {
|
|
1259
1519
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1260
1520
|
}
|
|
1261
1521
|
});
|
|
1262
|
-
keys.command("revoke-admin").requiredOption("--
|
|
1522
|
+
keys.command("revoke-admin").requiredOption("--key <keyId>", "Admin key id").action(async function(opts) {
|
|
1263
1523
|
try {
|
|
1524
|
+
const chatbotId = requireChatbotId(this);
|
|
1264
1525
|
const globals = this.optsWithGlobals();
|
|
1265
1526
|
const c = await client3(globals);
|
|
1266
|
-
await c.delete(`/api/v1/chatbots/${
|
|
1527
|
+
await c.delete(`/api/v1/chatbots/${chatbotId}/admin-keys/${opts.key}`);
|
|
1267
1528
|
emit({ ok: true, revoked: opts.key }, { json: !!globals.json });
|
|
1268
1529
|
} catch (err) {
|
|
1269
1530
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1270
1531
|
}
|
|
1271
1532
|
});
|
|
1272
|
-
cmd.command("update").description("Patch a chatbot's settings JSONB").
|
|
1533
|
+
cmd.command("update").description("Patch a chatbot's settings JSONB").option("--description <text>").option("--website-url <url>").option("--language <code>").action(async function(opts) {
|
|
1273
1534
|
try {
|
|
1535
|
+
const chatbotId = requireChatbotId(this);
|
|
1274
1536
|
const settings = {};
|
|
1275
1537
|
if (opts.description) settings.description = opts.description;
|
|
1276
1538
|
if (opts.websiteUrl) settings.website_url = opts.websiteUrl;
|
|
@@ -1280,7 +1542,7 @@ function settingsCommand() {
|
|
|
1280
1542
|
}
|
|
1281
1543
|
const globals = this.optsWithGlobals();
|
|
1282
1544
|
const c = await client3(globals);
|
|
1283
|
-
await c.patch(`/api/v1/chatbots/${
|
|
1545
|
+
await c.patch(`/api/v1/chatbots/${chatbotId}/settings`, { settings });
|
|
1284
1546
|
emit({ ok: true }, { json: !!globals.json });
|
|
1285
1547
|
} catch (err) {
|
|
1286
1548
|
fatal(err instanceof Error ? err.message : String(err));
|
|
@@ -1289,20 +1551,21 @@ function settingsCommand() {
|
|
|
1289
1551
|
return cmd;
|
|
1290
1552
|
}
|
|
1291
1553
|
function workflowsCommand() {
|
|
1292
|
-
const cmd = new
|
|
1293
|
-
cmd.command("list").
|
|
1554
|
+
const cmd = new Command9("workflows").description("Manage workflows");
|
|
1555
|
+
cmd.command("list").action(async function() {
|
|
1294
1556
|
try {
|
|
1557
|
+
const chatbotId = requireChatbotId(this);
|
|
1295
1558
|
const globals = this.optsWithGlobals();
|
|
1296
1559
|
const c = await client3(globals);
|
|
1297
1560
|
const result = await c.get(
|
|
1298
|
-
`/api/v1/chatbots/${
|
|
1561
|
+
`/api/v1/chatbots/${chatbotId}/workflows`
|
|
1299
1562
|
);
|
|
1300
1563
|
emit(result.workflows, { json: !!globals.json });
|
|
1301
1564
|
} catch (err) {
|
|
1302
1565
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1303
1566
|
}
|
|
1304
1567
|
});
|
|
1305
|
-
cmd.command("create").description("Create a workflow from a built-in template").requiredOption("--
|
|
1568
|
+
cmd.command("create").description("Create a workflow from a built-in template").requiredOption("--name <name>").requiredOption(
|
|
1306
1569
|
"--template <name>",
|
|
1307
1570
|
"flag-to-webhook | outcome-to-webhook | sentiment-drop-to-webhook"
|
|
1308
1571
|
).requiredOption(
|
|
@@ -1310,9 +1573,10 @@ function workflowsCommand() {
|
|
|
1310
1573
|
"Flag value / outcome value / sentiment threshold"
|
|
1311
1574
|
).requiredOption("--webhook <id>", "Webhook id to fire").option("--message <tpl>", "Message template (supports {{user.id}}, etc.)").action(async function(opts) {
|
|
1312
1575
|
try {
|
|
1576
|
+
const chatbotId = requireChatbotId(this);
|
|
1313
1577
|
const globals = this.optsWithGlobals();
|
|
1314
1578
|
const c = await client3(globals);
|
|
1315
|
-
const result = await c.post(`/api/v1/chatbots/${
|
|
1579
|
+
const result = await c.post(`/api/v1/chatbots/${chatbotId}/workflows`, {
|
|
1316
1580
|
name: opts.name,
|
|
1317
1581
|
template: opts.template,
|
|
1318
1582
|
triggerValue: opts.triggerValue,
|
|
@@ -1327,24 +1591,26 @@ function workflowsCommand() {
|
|
|
1327
1591
|
return cmd;
|
|
1328
1592
|
}
|
|
1329
1593
|
function reportsCommand() {
|
|
1330
|
-
const cmd = new
|
|
1331
|
-
cmd.command("list").
|
|
1594
|
+
const cmd = new Command9("reports").description("Manage AI reports");
|
|
1595
|
+
cmd.command("list").action(async function() {
|
|
1332
1596
|
try {
|
|
1597
|
+
const chatbotId = requireChatbotId(this);
|
|
1333
1598
|
const globals = this.optsWithGlobals();
|
|
1334
1599
|
const c = await client3(globals);
|
|
1335
1600
|
const result = await c.get(
|
|
1336
|
-
`/api/v1/chatbots/${
|
|
1601
|
+
`/api/v1/chatbots/${chatbotId}/reports`
|
|
1337
1602
|
);
|
|
1338
1603
|
emit(result.reports, { json: !!globals.json });
|
|
1339
1604
|
} catch (err) {
|
|
1340
1605
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1341
1606
|
}
|
|
1342
1607
|
});
|
|
1343
|
-
cmd.command("create").description("Create a new AI report; returns the org-private dashboard URL").
|
|
1608
|
+
cmd.command("create").description("Create a new AI report; returns the org-private dashboard URL").option("--name <name>", "Report name", "Untitled Report").action(async function(opts) {
|
|
1344
1609
|
try {
|
|
1610
|
+
const chatbotId = requireChatbotId(this);
|
|
1345
1611
|
const globals = this.optsWithGlobals();
|
|
1346
1612
|
const c = await client3(globals);
|
|
1347
|
-
const result = await c.post(`/api/v1/chatbots/${
|
|
1613
|
+
const result = await c.post(`/api/v1/chatbots/${chatbotId}/reports`, { name: opts.name });
|
|
1348
1614
|
process.stderr.write(
|
|
1349
1615
|
`
|
|
1350
1616
|
Created report. View it (org members only):
|
|
@@ -1363,31 +1629,33 @@ Created report. View it (org members only):
|
|
|
1363
1629
|
return cmd;
|
|
1364
1630
|
}
|
|
1365
1631
|
function analysisCommand() {
|
|
1366
|
-
const cmd = new
|
|
1367
|
-
cmd.command("list").
|
|
1632
|
+
const cmd = new Command9("analysis").description("Manage analysis definitions");
|
|
1633
|
+
cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
|
|
1368
1634
|
try {
|
|
1635
|
+
const chatbotId = requireChatbotId(this);
|
|
1369
1636
|
const globals = this.optsWithGlobals();
|
|
1370
1637
|
const c = await client3(globals);
|
|
1371
1638
|
const qs = new URLSearchParams();
|
|
1372
1639
|
if (opts.type) qs.set("type", opts.type);
|
|
1373
1640
|
if (opts.pending) qs.set("pending", "true");
|
|
1374
1641
|
const result = await c.get(
|
|
1375
|
-
`/api/v1/chatbots/${
|
|
1642
|
+
`/api/v1/chatbots/${chatbotId}/analysis-definitions${qs.size ? `?${qs}` : ""}`
|
|
1376
1643
|
);
|
|
1377
1644
|
emit(result.definitions, { json: !!globals.json });
|
|
1378
1645
|
} catch (err) {
|
|
1379
1646
|
fatal(err instanceof Error ? err.message : String(err));
|
|
1380
1647
|
}
|
|
1381
1648
|
});
|
|
1382
|
-
cmd.command("add").requiredOption(
|
|
1649
|
+
cmd.command("add").requiredOption(
|
|
1383
1650
|
"--type <t>",
|
|
1384
1651
|
"intent | flag | assistant_outcome | assistant_issue"
|
|
1385
1652
|
).requiredOption("--name <slug>", "snake_case slug").requiredOption("--display-name <text>").requiredOption("--description <text>").action(async function(opts) {
|
|
1386
1653
|
try {
|
|
1654
|
+
const chatbotId = requireChatbotId(this);
|
|
1387
1655
|
const globals = this.optsWithGlobals();
|
|
1388
1656
|
const c = await client3(globals);
|
|
1389
1657
|
const result = await c.post(
|
|
1390
|
-
`/api/v1/chatbots/${
|
|
1658
|
+
`/api/v1/chatbots/${chatbotId}/analysis-definitions`,
|
|
1391
1659
|
{
|
|
1392
1660
|
analysisType: opts.type,
|
|
1393
1661
|
name: opts.name,
|
|
@@ -1403,11 +1671,12 @@ function analysisCommand() {
|
|
|
1403
1671
|
return cmd;
|
|
1404
1672
|
}
|
|
1405
1673
|
function usersCommand() {
|
|
1406
|
-
const cmd = new
|
|
1674
|
+
const cmd = new Command9("users").description(
|
|
1407
1675
|
"List external users (chatbot customers) with health metrics"
|
|
1408
1676
|
);
|
|
1409
|
-
cmd.command("list").
|
|
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) {
|
|
1410
1678
|
try {
|
|
1679
|
+
const chatbotId = requireChatbotId(this);
|
|
1411
1680
|
const globals = this.optsWithGlobals();
|
|
1412
1681
|
const c = await client3(globals);
|
|
1413
1682
|
const qs = new URLSearchParams();
|
|
@@ -1422,7 +1691,7 @@ function usersCommand() {
|
|
|
1422
1691
|
if (opts.search) qs.set("search", opts.search);
|
|
1423
1692
|
qs.set("limit", opts.limit);
|
|
1424
1693
|
const result = await c.get(
|
|
1425
|
-
`/api/v1/chatbots/${
|
|
1694
|
+
`/api/v1/chatbots/${chatbotId}/external-users?${qs}`
|
|
1426
1695
|
);
|
|
1427
1696
|
emit(result.users, { json: !!globals.json });
|
|
1428
1697
|
process.stderr.write(`
|
|
@@ -1435,7 +1704,7 @@ Total: ${result.total}
|
|
|
1435
1704
|
return cmd;
|
|
1436
1705
|
}
|
|
1437
1706
|
function sdkCommand() {
|
|
1438
|
-
const cmd = new
|
|
1707
|
+
const cmd = new Command9("sdk").description(
|
|
1439
1708
|
"Help install and verify the OpenBat SDK in a target project"
|
|
1440
1709
|
);
|
|
1441
1710
|
cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
|
|
@@ -1497,18 +1766,22 @@ function sdkCommand() {
|
|
|
1497
1766
|
const out = opts.framework === "vercel-ai-sdk" ? snippetWrapper : snippetNext;
|
|
1498
1767
|
process.stdout.write(out);
|
|
1499
1768
|
});
|
|
1500
|
-
cmd.command("verify").description("Check whether any event has arrived for the chatbot yet").
|
|
1769
|
+
cmd.command("verify").description("Check whether any event has arrived for the chatbot yet").option("--timeout <n>", "Seconds to wait (default: 60)", "60").action(async function(opts) {
|
|
1501
1770
|
try {
|
|
1502
1771
|
const globals = this.optsWithGlobals();
|
|
1772
|
+
const chatbotId = globals.chatbot;
|
|
1773
|
+
if (!chatbotId) {
|
|
1774
|
+
fatal("--chatbot <id> is required (also accepts the persisted active chatbot).");
|
|
1775
|
+
}
|
|
1503
1776
|
const c = await client3(globals);
|
|
1504
1777
|
const timeoutSec = Number(opts.timeout);
|
|
1505
1778
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
1506
1779
|
const params = new URLSearchParams({
|
|
1507
|
-
chatbotId
|
|
1780
|
+
chatbotId,
|
|
1508
1781
|
limit: "1"
|
|
1509
1782
|
});
|
|
1510
1783
|
process.stderr.write(
|
|
1511
|
-
`Waiting for first event on chatbot ${
|
|
1784
|
+
`Waiting for first event on chatbot ${chatbotId} (timeout ${timeoutSec}s)\u2026
|
|
1512
1785
|
`
|
|
1513
1786
|
);
|
|
1514
1787
|
let lastTick = Date.now();
|
|
@@ -1541,10 +1814,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
|
|
|
1541
1814
|
}
|
|
1542
1815
|
|
|
1543
1816
|
// src/index.ts
|
|
1544
|
-
var program = new
|
|
1817
|
+
var program = new Command10();
|
|
1545
1818
|
program.name("openbat").description(
|
|
1546
1819
|
"Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
|
|
1547
|
-
).version("0.
|
|
1820
|
+
).version("0.3.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
|
|
1548
1821
|
"--base-url <url>",
|
|
1549
1822
|
"Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
|
|
1550
1823
|
).option(
|
|
@@ -1563,6 +1836,8 @@ program.addCommand(orgCommand());
|
|
|
1563
1836
|
program.addCommand(chatbotCommand());
|
|
1564
1837
|
program.addCommand(chatbotsCommand());
|
|
1565
1838
|
program.addCommand(conversationsCommand());
|
|
1839
|
+
program.addCommand(reviewCommand());
|
|
1840
|
+
program.addCommand(promptsCommand());
|
|
1566
1841
|
program.addCommand(usersCommand());
|
|
1567
1842
|
program.addCommand(settingsCommand());
|
|
1568
1843
|
program.addCommand(webhooksCommand());
|