@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/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 import_commander8 = require("commander");
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.2.2";
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
- activeChatbotId: cfg.activeChatbotId ?? "(not set)",
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("<id-or-name>", "Chatbot UUID or exact name").description("Set the default chatbot for this CLI (shortcut for `config use-chatbot`)").action(async (target) => {
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) return chatbots[0].id;
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 import("fs");
691
- const { Writable } = await import("stream");
692
- const { Readable } = await import("stream");
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/login.ts
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 import_commander4.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(
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 import_commander5 = require("commander");
1353
+ var import_commander7 = require("commander");
1110
1354
  function logoutCommand() {
1111
- return new import_commander5.Command("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
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 import_commander6 = require("commander");
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 import_commander6.Command("org").description(
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 import_commander7 = require("commander");
1513
+ var import_commander9 = require("commander");
1270
1514
  async function client3(globals) {
1271
1515
  const cfg = await resolveConfig({
1272
1516
  apiKeyFlag: globals.apiKey ?? null,
@@ -1277,6 +1521,15 @@ async function client3(globals) {
1277
1521
  }
1278
1522
  return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
1279
1523
  }
1524
+ function requireChatbotId(cmd) {
1525
+ const globals = cmd.optsWithGlobals();
1526
+ if (!globals.chatbot) {
1527
+ fatal(
1528
+ "--chatbot <id> is required. Pass it inline or set a default with `openbat use <id>`."
1529
+ );
1530
+ }
1531
+ return globals.chatbot;
1532
+ }
1280
1533
  function surfacePlaintext(plaintext, label) {
1281
1534
  process.stderr.write(
1282
1535
  `
@@ -1290,7 +1543,7 @@ function surfacePlaintext(plaintext, label) {
1290
1543
  );
1291
1544
  }
1292
1545
  function chatbotsCommand() {
1293
- const cmd = new import_commander7.Command("chatbots").description(
1546
+ const cmd = new import_commander9.Command("chatbots").description(
1294
1547
  "List, create, delete chatbots in the current scope"
1295
1548
  );
1296
1549
  cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
@@ -1338,24 +1591,26 @@ function chatbotsCommand() {
1338
1591
  return cmd;
1339
1592
  }
1340
1593
  function webhooksCommand() {
1341
- const cmd = new import_commander7.Command("webhooks").description("Manage webhooks for a chatbot");
1342
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
1594
+ const cmd = new import_commander9.Command("webhooks").description("Manage webhooks for a chatbot");
1595
+ cmd.command("list").action(async function() {
1343
1596
  try {
1597
+ const chatbotId = requireChatbotId(this);
1344
1598
  const globals = this.optsWithGlobals();
1345
1599
  const c = await client3(globals);
1346
1600
  const result = await c.get(
1347
- `/api/v1/chatbots/${opts.chatbot}/webhooks`
1601
+ `/api/v1/chatbots/${chatbotId}/webhooks`
1348
1602
  );
1349
1603
  emit(result.webhooks, { json: !!globals.json });
1350
1604
  } catch (err) {
1351
1605
  fatal(err instanceof Error ? err.message : String(err));
1352
1606
  }
1353
1607
  });
1354
- cmd.command("create").requiredOption("--chatbot <id>", "Chatbot id").requiredOption("--name <name>").requiredOption("--url <url>").option("--type <type>", "discord | slack | custom", "custom").action(async function(opts) {
1608
+ cmd.command("create").requiredOption("--name <name>").requiredOption("--url <url>").option("--type <type>", "discord | slack | custom", "custom").action(async function(opts) {
1355
1609
  try {
1610
+ const chatbotId = requireChatbotId(this);
1356
1611
  const globals = this.optsWithGlobals();
1357
1612
  const c = await client3(globals);
1358
- const result = await c.post(`/api/v1/chatbots/${opts.chatbot}/webhooks`, {
1613
+ const result = await c.post(`/api/v1/chatbots/${chatbotId}/webhooks`, {
1359
1614
  name: opts.name,
1360
1615
  url: opts.url,
1361
1616
  type: opts.type
@@ -1366,11 +1621,12 @@ function webhooksCommand() {
1366
1621
  fatal(err instanceof Error ? err.message : String(err));
1367
1622
  }
1368
1623
  });
1369
- cmd.command("delete").requiredOption("--chatbot <id>", "Chatbot id").requiredOption("--webhook <id>", "Webhook id").action(async function(opts) {
1624
+ cmd.command("delete").requiredOption("--webhook <id>", "Webhook id").action(async function(opts) {
1370
1625
  try {
1626
+ const chatbotId = requireChatbotId(this);
1371
1627
  const globals = this.optsWithGlobals();
1372
1628
  const c = await client3(globals);
1373
- await c.delete(`/api/v1/chatbots/${opts.chatbot}/webhooks/${opts.webhook}`);
1629
+ await c.delete(`/api/v1/chatbots/${chatbotId}/webhooks/${opts.webhook}`);
1374
1630
  emit({ ok: true, deleted: opts.webhook }, { json: !!globals.json });
1375
1631
  } catch (err) {
1376
1632
  fatal(err instanceof Error ? err.message : String(err));
@@ -1379,16 +1635,17 @@ function webhooksCommand() {
1379
1635
  return cmd;
1380
1636
  }
1381
1637
  function settingsCommand() {
1382
- const cmd = new import_commander7.Command("settings").description(
1638
+ const cmd = new import_commander9.Command("settings").description(
1383
1639
  "Manage chatbot settings + per-chatbot keys"
1384
1640
  );
1385
1641
  const keys = cmd.command("keys").description("Manage API keys for a chatbot");
1386
- keys.command("rotate-ingest").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
1642
+ keys.command("rotate-ingest").action(async function() {
1387
1643
  try {
1644
+ const chatbotId = requireChatbotId(this);
1388
1645
  const globals = this.optsWithGlobals();
1389
1646
  const c = await client3(globals);
1390
1647
  const result = await c.post(
1391
- `/api/v1/chatbots/${opts.chatbot}/keys/ingest/rotate`,
1648
+ `/api/v1/chatbots/${chatbotId}/keys/ingest/rotate`,
1392
1649
  {}
1393
1650
  );
1394
1651
  surfacePlaintext(result.plaintext, "New ingest key (ob_live_*)");
@@ -1397,12 +1654,13 @@ function settingsCommand() {
1397
1654
  fatal(err instanceof Error ? err.message : String(err));
1398
1655
  }
1399
1656
  });
1400
- keys.command("generate-read").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
1657
+ keys.command("generate-read").action(async function() {
1401
1658
  try {
1659
+ const chatbotId = requireChatbotId(this);
1402
1660
  const globals = this.optsWithGlobals();
1403
1661
  const c = await client3(globals);
1404
1662
  const result = await c.post(
1405
- `/api/v1/chatbots/${opts.chatbot}/keys/read`,
1663
+ `/api/v1/chatbots/${chatbotId}/keys/read`,
1406
1664
  {}
1407
1665
  );
1408
1666
  surfacePlaintext(result.plaintext, "New read key (ob_read_*)");
@@ -1411,11 +1669,12 @@ function settingsCommand() {
1411
1669
  fatal(err instanceof Error ? err.message : String(err));
1412
1670
  }
1413
1671
  });
1414
- keys.command("generate-admin").requiredOption("--chatbot <id>", "Chatbot id").requiredOption("--name <name>", "Human-friendly name (e.g. 'CI key')").option("--expires-in-days <n>", "Auto-expire after N days").action(async function(opts) {
1672
+ 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) {
1415
1673
  try {
1674
+ const chatbotId = requireChatbotId(this);
1416
1675
  const globals = this.optsWithGlobals();
1417
1676
  const c = await client3(globals);
1418
- const result = await c.post(`/api/v1/chatbots/${opts.chatbot}/admin-keys`, {
1677
+ const result = await c.post(`/api/v1/chatbots/${chatbotId}/admin-keys`, {
1419
1678
  name: opts.name,
1420
1679
  expiresInDays: opts.expiresInDays ? Number(opts.expiresInDays) : void 0
1421
1680
  });
@@ -1425,30 +1684,33 @@ function settingsCommand() {
1425
1684
  fatal(err instanceof Error ? err.message : String(err));
1426
1685
  }
1427
1686
  });
1428
- keys.command("list-admin").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
1687
+ keys.command("list-admin").action(async function() {
1429
1688
  try {
1689
+ const chatbotId = requireChatbotId(this);
1430
1690
  const globals = this.optsWithGlobals();
1431
1691
  const c = await client3(globals);
1432
1692
  const result = await c.get(
1433
- `/api/v1/chatbots/${opts.chatbot}/admin-keys`
1693
+ `/api/v1/chatbots/${chatbotId}/admin-keys`
1434
1694
  );
1435
1695
  emit(result.keys, { json: !!globals.json });
1436
1696
  } catch (err) {
1437
1697
  fatal(err instanceof Error ? err.message : String(err));
1438
1698
  }
1439
1699
  });
1440
- keys.command("revoke-admin").requiredOption("--chatbot <id>", "Chatbot id").requiredOption("--key <keyId>", "Admin key id").action(async function(opts) {
1700
+ keys.command("revoke-admin").requiredOption("--key <keyId>", "Admin key id").action(async function(opts) {
1441
1701
  try {
1702
+ const chatbotId = requireChatbotId(this);
1442
1703
  const globals = this.optsWithGlobals();
1443
1704
  const c = await client3(globals);
1444
- await c.delete(`/api/v1/chatbots/${opts.chatbot}/admin-keys/${opts.key}`);
1705
+ await c.delete(`/api/v1/chatbots/${chatbotId}/admin-keys/${opts.key}`);
1445
1706
  emit({ ok: true, revoked: opts.key }, { json: !!globals.json });
1446
1707
  } catch (err) {
1447
1708
  fatal(err instanceof Error ? err.message : String(err));
1448
1709
  }
1449
1710
  });
1450
- cmd.command("update").description("Patch a chatbot's settings JSONB").requiredOption("--chatbot <id>", "Chatbot id").option("--description <text>").option("--website-url <url>").option("--language <code>").action(async function(opts) {
1711
+ cmd.command("update").description("Patch a chatbot's settings JSONB").option("--description <text>").option("--website-url <url>").option("--language <code>").action(async function(opts) {
1451
1712
  try {
1713
+ const chatbotId = requireChatbotId(this);
1452
1714
  const settings = {};
1453
1715
  if (opts.description) settings.description = opts.description;
1454
1716
  if (opts.websiteUrl) settings.website_url = opts.websiteUrl;
@@ -1458,7 +1720,7 @@ function settingsCommand() {
1458
1720
  }
1459
1721
  const globals = this.optsWithGlobals();
1460
1722
  const c = await client3(globals);
1461
- await c.patch(`/api/v1/chatbots/${opts.chatbot}/settings`, { settings });
1723
+ await c.patch(`/api/v1/chatbots/${chatbotId}/settings`, { settings });
1462
1724
  emit({ ok: true }, { json: !!globals.json });
1463
1725
  } catch (err) {
1464
1726
  fatal(err instanceof Error ? err.message : String(err));
@@ -1467,20 +1729,21 @@ function settingsCommand() {
1467
1729
  return cmd;
1468
1730
  }
1469
1731
  function workflowsCommand() {
1470
- const cmd = new import_commander7.Command("workflows").description("Manage workflows");
1471
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
1732
+ const cmd = new import_commander9.Command("workflows").description("Manage workflows");
1733
+ cmd.command("list").action(async function() {
1472
1734
  try {
1735
+ const chatbotId = requireChatbotId(this);
1473
1736
  const globals = this.optsWithGlobals();
1474
1737
  const c = await client3(globals);
1475
1738
  const result = await c.get(
1476
- `/api/v1/chatbots/${opts.chatbot}/workflows`
1739
+ `/api/v1/chatbots/${chatbotId}/workflows`
1477
1740
  );
1478
1741
  emit(result.workflows, { json: !!globals.json });
1479
1742
  } catch (err) {
1480
1743
  fatal(err instanceof Error ? err.message : String(err));
1481
1744
  }
1482
1745
  });
1483
- cmd.command("create").description("Create a workflow from a built-in template").requiredOption("--chatbot <id>", "Chatbot id").requiredOption("--name <name>").requiredOption(
1746
+ cmd.command("create").description("Create a workflow from a built-in template").requiredOption("--name <name>").requiredOption(
1484
1747
  "--template <name>",
1485
1748
  "flag-to-webhook | outcome-to-webhook | sentiment-drop-to-webhook"
1486
1749
  ).requiredOption(
@@ -1488,9 +1751,10 @@ function workflowsCommand() {
1488
1751
  "Flag value / outcome value / sentiment threshold"
1489
1752
  ).requiredOption("--webhook <id>", "Webhook id to fire").option("--message <tpl>", "Message template (supports {{user.id}}, etc.)").action(async function(opts) {
1490
1753
  try {
1754
+ const chatbotId = requireChatbotId(this);
1491
1755
  const globals = this.optsWithGlobals();
1492
1756
  const c = await client3(globals);
1493
- const result = await c.post(`/api/v1/chatbots/${opts.chatbot}/workflows`, {
1757
+ const result = await c.post(`/api/v1/chatbots/${chatbotId}/workflows`, {
1494
1758
  name: opts.name,
1495
1759
  template: opts.template,
1496
1760
  triggerValue: opts.triggerValue,
@@ -1505,24 +1769,26 @@ function workflowsCommand() {
1505
1769
  return cmd;
1506
1770
  }
1507
1771
  function reportsCommand() {
1508
- const cmd = new import_commander7.Command("reports").description("Manage AI reports");
1509
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
1772
+ const cmd = new import_commander9.Command("reports").description("Manage AI reports");
1773
+ cmd.command("list").action(async function() {
1510
1774
  try {
1775
+ const chatbotId = requireChatbotId(this);
1511
1776
  const globals = this.optsWithGlobals();
1512
1777
  const c = await client3(globals);
1513
1778
  const result = await c.get(
1514
- `/api/v1/chatbots/${opts.chatbot}/reports`
1779
+ `/api/v1/chatbots/${chatbotId}/reports`
1515
1780
  );
1516
1781
  emit(result.reports, { json: !!globals.json });
1517
1782
  } catch (err) {
1518
1783
  fatal(err instanceof Error ? err.message : String(err));
1519
1784
  }
1520
1785
  });
1521
- cmd.command("create").description("Create a new AI report; returns the org-private dashboard URL").requiredOption("--chatbot <id>", "Chatbot id").option("--name <name>", "Report name", "Untitled Report").action(async function(opts) {
1786
+ 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) {
1522
1787
  try {
1788
+ const chatbotId = requireChatbotId(this);
1523
1789
  const globals = this.optsWithGlobals();
1524
1790
  const c = await client3(globals);
1525
- const result = await c.post(`/api/v1/chatbots/${opts.chatbot}/reports`, { name: opts.name });
1791
+ const result = await c.post(`/api/v1/chatbots/${chatbotId}/reports`, { name: opts.name });
1526
1792
  process.stderr.write(
1527
1793
  `
1528
1794
  Created report. View it (org members only):
@@ -1541,31 +1807,33 @@ Created report. View it (org members only):
1541
1807
  return cmd;
1542
1808
  }
1543
1809
  function analysisCommand() {
1544
- const cmd = new import_commander7.Command("analysis").description("Manage analysis definitions");
1545
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
1810
+ const cmd = new import_commander9.Command("analysis").description("Manage analysis definitions");
1811
+ cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
1546
1812
  try {
1813
+ const chatbotId = requireChatbotId(this);
1547
1814
  const globals = this.optsWithGlobals();
1548
1815
  const c = await client3(globals);
1549
1816
  const qs = new URLSearchParams();
1550
1817
  if (opts.type) qs.set("type", opts.type);
1551
1818
  if (opts.pending) qs.set("pending", "true");
1552
1819
  const result = await c.get(
1553
- `/api/v1/chatbots/${opts.chatbot}/analysis-definitions${qs.size ? `?${qs}` : ""}`
1820
+ `/api/v1/chatbots/${chatbotId}/analysis-definitions${qs.size ? `?${qs}` : ""}`
1554
1821
  );
1555
1822
  emit(result.definitions, { json: !!globals.json });
1556
1823
  } catch (err) {
1557
1824
  fatal(err instanceof Error ? err.message : String(err));
1558
1825
  }
1559
1826
  });
1560
- cmd.command("add").requiredOption("--chatbot <id>", "Chatbot id").requiredOption(
1827
+ cmd.command("add").requiredOption(
1561
1828
  "--type <t>",
1562
1829
  "intent | flag | assistant_outcome | assistant_issue"
1563
1830
  ).requiredOption("--name <slug>", "snake_case slug").requiredOption("--display-name <text>").requiredOption("--description <text>").action(async function(opts) {
1564
1831
  try {
1832
+ const chatbotId = requireChatbotId(this);
1565
1833
  const globals = this.optsWithGlobals();
1566
1834
  const c = await client3(globals);
1567
1835
  const result = await c.post(
1568
- `/api/v1/chatbots/${opts.chatbot}/analysis-definitions`,
1836
+ `/api/v1/chatbots/${chatbotId}/analysis-definitions`,
1569
1837
  {
1570
1838
  analysisType: opts.type,
1571
1839
  name: opts.name,
@@ -1581,11 +1849,12 @@ function analysisCommand() {
1581
1849
  return cmd;
1582
1850
  }
1583
1851
  function usersCommand() {
1584
- const cmd = new import_commander7.Command("users").description(
1852
+ const cmd = new import_commander9.Command("users").description(
1585
1853
  "List external users (chatbot customers) with health metrics"
1586
1854
  );
1587
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").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) {
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) {
1588
1856
  try {
1857
+ const chatbotId = requireChatbotId(this);
1589
1858
  const globals = this.optsWithGlobals();
1590
1859
  const c = await client3(globals);
1591
1860
  const qs = new URLSearchParams();
@@ -1600,7 +1869,7 @@ function usersCommand() {
1600
1869
  if (opts.search) qs.set("search", opts.search);
1601
1870
  qs.set("limit", opts.limit);
1602
1871
  const result = await c.get(
1603
- `/api/v1/chatbots/${opts.chatbot}/external-users?${qs}`
1872
+ `/api/v1/chatbots/${chatbotId}/external-users?${qs}`
1604
1873
  );
1605
1874
  emit(result.users, { json: !!globals.json });
1606
1875
  process.stderr.write(`
@@ -1613,7 +1882,7 @@ Total: ${result.total}
1613
1882
  return cmd;
1614
1883
  }
1615
1884
  function sdkCommand() {
1616
- const cmd = new import_commander7.Command("sdk").description(
1885
+ const cmd = new import_commander9.Command("sdk").description(
1617
1886
  "Help install and verify the OpenBat SDK in a target project"
1618
1887
  );
1619
1888
  cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
@@ -1675,18 +1944,22 @@ function sdkCommand() {
1675
1944
  const out = opts.framework === "vercel-ai-sdk" ? snippetWrapper : snippetNext;
1676
1945
  process.stdout.write(out);
1677
1946
  });
1678
- cmd.command("verify").description("Check whether any event has arrived for the chatbot yet").requiredOption("--chatbot <id>", "Chatbot id").option("--timeout <n>", "Seconds to wait (default: 60)", "60").action(async function(opts) {
1947
+ 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) {
1679
1948
  try {
1680
1949
  const globals = this.optsWithGlobals();
1950
+ const chatbotId = globals.chatbot;
1951
+ if (!chatbotId) {
1952
+ fatal("--chatbot <id> is required (also accepts the persisted active chatbot).");
1953
+ }
1681
1954
  const c = await client3(globals);
1682
1955
  const timeoutSec = Number(opts.timeout);
1683
1956
  const deadline = Date.now() + timeoutSec * 1e3;
1684
1957
  const params = new URLSearchParams({
1685
- chatbotId: opts.chatbot,
1958
+ chatbotId,
1686
1959
  limit: "1"
1687
1960
  });
1688
1961
  process.stderr.write(
1689
- `Waiting for first event on chatbot ${opts.chatbot} (timeout ${timeoutSec}s)\u2026
1962
+ `Waiting for first event on chatbot ${chatbotId} (timeout ${timeoutSec}s)\u2026
1690
1963
  `
1691
1964
  );
1692
1965
  let lastTick = Date.now();
@@ -1719,10 +1992,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
1719
1992
  }
1720
1993
 
1721
1994
  // src/index.ts
1722
- var program = new import_commander8.Command();
1995
+ var program = new import_commander10.Command();
1723
1996
  program.name("openbat").description(
1724
1997
  "Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
1725
- ).version("0.2.2").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
1998
+ ).version("0.3.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
1726
1999
  "--base-url <url>",
1727
2000
  "Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
1728
2001
  ).option(
@@ -1741,6 +2014,8 @@ program.addCommand(orgCommand());
1741
2014
  program.addCommand(chatbotCommand());
1742
2015
  program.addCommand(chatbotsCommand());
1743
2016
  program.addCommand(conversationsCommand());
2017
+ program.addCommand(reviewCommand());
2018
+ program.addCommand(promptsCommand());
1744
2019
  program.addCommand(usersCommand());
1745
2020
  program.addCommand(settingsCommand());
1746
2021
  program.addCommand(webhooksCommand());