@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.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiClient
4
- } from "./chunk-MCDJLQI2.mjs";
4
+ } from "./chunk-KIY62R3O.mjs";
5
5
 
6
6
  // src/index.ts
7
- import { Command as Command8 } from "commander";
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
- activeChatbotId: cfg.activeChatbotId ?? "(not set)",
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("<id-or-name>", "Chatbot UUID or exact name").description("Set the default chatbot for this CLI (shortcut for `config use-chatbot`)").action(async (target) => {
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) return chatbots[0].id;
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 import("fs");
513
- const { Writable } = await import("stream");
514
- const { Readable } = await import("stream");
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/login.ts
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 Command4("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(
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 Command5 } from "commander";
1175
+ import { Command as Command7 } from "commander";
932
1176
  function logoutCommand() {
933
- return new Command5("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
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 Command6 } from "commander";
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 Command6("org").description(
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 Command7 } from "commander";
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 Command7("chatbots").description(
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 Command7("webhooks").description("Manage webhooks for a chatbot");
1164
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
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/${opts.chatbot}/webhooks`
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("--chatbot <id>", "Chatbot id").requiredOption("--name <name>").requiredOption("--url <url>").option("--type <type>", "discord | slack | custom", "custom").action(async function(opts) {
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/${opts.chatbot}/webhooks`, {
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("--chatbot <id>", "Chatbot id").requiredOption("--webhook <id>", "Webhook id").action(async function(opts) {
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/${opts.chatbot}/webhooks/${opts.webhook}`);
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 Command7("settings").description(
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").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
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/${opts.chatbot}/keys/ingest/rotate`,
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").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
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/${opts.chatbot}/keys/read`,
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("--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) {
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/${opts.chatbot}/admin-keys`, {
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").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
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/${opts.chatbot}/admin-keys`
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("--chatbot <id>", "Chatbot id").requiredOption("--key <keyId>", "Admin key id").action(async function(opts) {
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/${opts.chatbot}/admin-keys/${opts.key}`);
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").requiredOption("--chatbot <id>", "Chatbot id").option("--description <text>").option("--website-url <url>").option("--language <code>").action(async function(opts) {
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/${opts.chatbot}/settings`, { settings });
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 Command7("workflows").description("Manage workflows");
1293
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
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/${opts.chatbot}/workflows`
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("--chatbot <id>", "Chatbot id").requiredOption("--name <name>").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/${opts.chatbot}/workflows`, {
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 Command7("reports").description("Manage AI reports");
1331
- cmd.command("list").requiredOption("--chatbot <id>", "Chatbot id").action(async function(opts) {
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/${opts.chatbot}/reports`
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").requiredOption("--chatbot <id>", "Chatbot id").option("--name <name>", "Report name", "Untitled Report").action(async function(opts) {
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/${opts.chatbot}/reports`, { name: opts.name });
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 Command7("analysis").description("Manage analysis definitions");
1367
- 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) {
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/${opts.chatbot}/analysis-definitions${qs.size ? `?${qs}` : ""}`
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("--chatbot <id>", "Chatbot id").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/${opts.chatbot}/analysis-definitions`,
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 Command7("users").description(
1674
+ const cmd = new Command9("users").description(
1407
1675
  "List external users (chatbot customers) with health metrics"
1408
1676
  );
1409
- 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) {
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/${opts.chatbot}/external-users?${qs}`
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 Command7("sdk").description(
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").requiredOption("--chatbot <id>", "Chatbot id").option("--timeout <n>", "Seconds to wait (default: 60)", "60").action(async function(opts) {
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: opts.chatbot,
1780
+ chatbotId,
1508
1781
  limit: "1"
1509
1782
  });
1510
1783
  process.stderr.write(
1511
- `Waiting for first event on chatbot ${opts.chatbot} (timeout ${timeoutSec}s)\u2026
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 Command8();
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.2.2").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
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());