@openbat/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@ __export(api_client_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(api_client_exports);
34
34
  var import_node_url = require("url");
35
- var CLI_VERSION = "0.3.0";
35
+ var CLI_VERSION = "0.4.0";
36
36
  var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
37
37
  function redact(s) {
38
38
  return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ApiClient
3
- } from "./chunk-KIY62R3O.mjs";
3
+ } from "./chunk-RA36VFZI.mjs";
4
4
  export {
5
5
  ApiClient
6
6
  };
@@ -9,7 +9,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
9
9
 
10
10
  // src/api-client.ts
11
11
  import { URL } from "url";
12
- var CLI_VERSION = "0.3.0";
12
+ var CLI_VERSION = "0.4.0";
13
13
  var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
14
14
  function redact(s) {
15
15
  return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
32
32
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
33
33
 
34
34
  // src/index.ts
35
- var import_commander10 = require("commander");
35
+ var import_commander11 = require("commander");
36
36
 
37
37
  // src/commands/config.ts
38
38
  var import_commander = require("commander");
@@ -163,7 +163,7 @@ function configPath() {
163
163
 
164
164
  // src/api-client.ts
165
165
  var import_node_url = require("url");
166
- var CLI_VERSION = "0.3.0";
166
+ var CLI_VERSION = "0.4.0";
167
167
  var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
168
168
  function redact(s) {
169
169
  return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
@@ -946,6 +946,28 @@ function reviewCommand() {
946
946
  // src/commands/prompts.ts
947
947
  var import_commander5 = require("commander");
948
948
  var import_node_fs2 = require("fs");
949
+ async function waitForActive(api, chatbotId, versionId) {
950
+ const deadlineMs = Date.now() + 15e3;
951
+ let attempt = 0;
952
+ while (Date.now() < deadlineMs) {
953
+ const res = await api.get(
954
+ `/api/v1/chatbots/${chatbotId}/prompts/active`
955
+ );
956
+ if (res.activeVersionId === versionId) {
957
+ process.stderr.write(
958
+ `
959
+ Confirmed live (server): active version is ${versionId}. Live SDK processes converge within their ~60s fetch-cache TTL.
960
+ `
961
+ );
962
+ return;
963
+ }
964
+ attempt++;
965
+ await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 2e3)));
966
+ }
967
+ process.stderr.write(
968
+ "\nPublished, but timed out (15s) waiting for server confirmation \u2014 the active pointer may still be propagating.\n"
969
+ );
970
+ }
949
971
  function promptsCommand() {
950
972
  const cmd = new import_commander5.Command("prompts").description(
951
973
  "Manage the live published system prompt (list / publish / activate / kill-switch)"
@@ -962,7 +984,7 @@ function promptsCommand() {
962
984
  fatal(err instanceof Error ? err.message : String(err));
963
985
  }
964
986
  });
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) {
987
+ cmd.command("publish").description("Publish a template as the LIVE prompt (from --file or --text)").option("--file <path>", "Read the template text from a file").option("--text <text>", "Inline template text (prefer --file for long prompts)").option("--wait", "Block until the server confirms the new version is the active published prompt").action(async function(opts) {
966
988
  try {
967
989
  let templateText = opts.text;
968
990
  if (opts.file) templateText = await import_node_fs2.promises.readFile(opts.file, "utf8");
@@ -976,9 +998,13 @@ function promptsCommand() {
976
998
  { templateText }
977
999
  );
978
1000
  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
- );
1001
+ if (opts.wait && result.versionId) {
1002
+ await waitForActive(api, id, result.versionId);
1003
+ } else {
1004
+ process.stderr.write(
1005
+ "\nPublished. Live within ~60s for chatbots that fetch their prompt from OpenBat.\n"
1006
+ );
1007
+ }
982
1008
  } catch (err) {
983
1009
  fatal(err instanceof Error ? err.message : String(err));
984
1010
  }
@@ -1013,11 +1039,86 @@ function promptsCommand() {
1013
1039
  fatal(err instanceof Error ? err.message : String(err));
1014
1040
  }
1015
1041
  });
1042
+ cmd.command("show <versionId>").description("Show a version's full template text (list shows only hashes)").action(async function(versionId) {
1043
+ try {
1044
+ const { api, chatbotFlag } = await client(this.optsWithGlobals());
1045
+ const id = await resolveChatbotId(api, chatbotFlag);
1046
+ const result = await api.get(
1047
+ `/api/v1/chatbots/${id}/prompts/${versionId}`
1048
+ );
1049
+ emit(result, { json: !!this.optsWithGlobals().json });
1050
+ } catch (err) {
1051
+ fatal(err instanceof Error ? err.message : String(err));
1052
+ }
1053
+ });
1054
+ cmd.command("active").description("Show what the server resolves to RIGHT NOW (active version, kill switch)").action(async function() {
1055
+ try {
1056
+ const { api, chatbotFlag } = await client(this.optsWithGlobals());
1057
+ const id = await resolveChatbotId(api, chatbotFlag);
1058
+ const result = await api.get(
1059
+ `/api/v1/chatbots/${id}/prompts/active`
1060
+ );
1061
+ emit(result, { json: !!this.optsWithGlobals().json });
1062
+ } catch (err) {
1063
+ fatal(err instanceof Error ? err.message : String(err));
1064
+ }
1065
+ });
1016
1066
  return cmd;
1017
1067
  }
1018
1068
 
1019
- // src/commands/login.ts
1069
+ // src/commands/backtests.ts
1020
1070
  var import_commander6 = require("commander");
1071
+ function backtestsCommand() {
1072
+ const cmd = new import_commander6.Command("backtests").description(
1073
+ "Retest a candidate prompt against flagged conversations (create + poll status)"
1074
+ );
1075
+ cmd.command("create").description("Replay flagged conversations against a candidate prompt version").requiredOption("--name <name>", "Backtest name").requiredOption(
1076
+ "--candidate-prompt <versionId>",
1077
+ "Candidate prompt version id (from `openbat prompts list`)"
1078
+ ).requiredOption(
1079
+ "--flags <csv>",
1080
+ "Comma-separated flag values to sample (e.g. churn_risk,billing_issue)"
1081
+ ).option("--sample-size <n>", "How many flagged messages to replay (1-500)", "50").option("--from <iso>", "Only sample flags on/after this ISO timestamp").option("--to <iso>", "Only sample flags on/before this ISO timestamp").action(async function(opts) {
1082
+ try {
1083
+ const flagValues = opts.flags.split(",").map((s) => s.trim()).filter(Boolean);
1084
+ if (flagValues.length === 0) {
1085
+ fatal("Provide at least one flag via --flags <csv>.");
1086
+ }
1087
+ const { api, chatbotFlag } = await client(this.optsWithGlobals());
1088
+ const id = await resolveChatbotId(api, chatbotFlag);
1089
+ const result = await api.post(
1090
+ `/api/v1/chatbots/${id}/backtests`,
1091
+ {
1092
+ name: opts.name,
1093
+ candidatePromptId: opts.candidatePrompt,
1094
+ flagValues,
1095
+ sampleSize: Number(opts.sampleSize),
1096
+ timeRangeStart: opts.from,
1097
+ timeRangeEnd: opts.to
1098
+ }
1099
+ );
1100
+ emit(result, { json: !!this.optsWithGlobals().json });
1101
+ process.stderr.write("\nStarted. Poll progress with: openbat backtests status <id>\n");
1102
+ } catch (err) {
1103
+ fatal(err instanceof Error ? err.message : String(err));
1104
+ }
1105
+ });
1106
+ cmd.command("status <backtestId>").description("Show a backtest's progress + verdict tally").action(async function(backtestId) {
1107
+ try {
1108
+ const { api } = await client(this.optsWithGlobals());
1109
+ const result = await api.get(
1110
+ `/api/v1/backtests/${encodeURIComponent(backtestId)}/status`
1111
+ );
1112
+ emit(result, { json: !!this.optsWithGlobals().json });
1113
+ } catch (err) {
1114
+ fatal(err instanceof Error ? err.message : String(err));
1115
+ }
1116
+ });
1117
+ return cmd;
1118
+ }
1119
+
1120
+ // src/commands/login.ts
1121
+ var import_commander7 = require("commander");
1021
1122
  var import_node_os2 = __toESM(require("os"));
1022
1123
  var import_node_readline = __toESM(require("readline"));
1023
1124
 
@@ -1160,7 +1261,7 @@ async function runLoopbackServer(opts) {
1160
1261
  // src/commands/login.ts
1161
1262
  var HOSTNAME = import_node_os2.default.hostname();
1162
1263
  function loginCommand() {
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(
1264
+ return new import_commander7.Command("login").description("Sign in via browser and install an API key on this device").option("--device", "Use the device-code flow (for SSH / headless machines)").option(
1164
1265
  "--use <key>",
1165
1266
  "Skip the browser; install a PAT plaintext you already have"
1166
1267
  ).action(async function(opts) {
@@ -1350,9 +1451,9 @@ You have ${chatbots.length} chatbots. Pick one later with \`openbat use <id>\`.
1350
1451
  }
1351
1452
 
1352
1453
  // src/commands/logout.ts
1353
- var import_commander7 = require("commander");
1454
+ var import_commander8 = require("commander");
1354
1455
  function logoutCommand() {
1355
- return new import_commander7.Command("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
1456
+ return new import_commander8.Command("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
1356
1457
  try {
1357
1458
  const globals = this.optsWithGlobals();
1358
1459
  const cfg = await resolveConfig({
@@ -1387,7 +1488,7 @@ function logoutCommand() {
1387
1488
  }
1388
1489
 
1389
1490
  // src/commands/org.ts
1390
- var import_commander8 = require("commander");
1491
+ var import_commander9 = require("commander");
1391
1492
  async function client2(globals) {
1392
1493
  const cfg = await resolveConfig({
1393
1494
  apiKeyFlag: globals.apiKey ?? null,
@@ -1402,7 +1503,7 @@ async function client2(globals) {
1402
1503
  return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
1403
1504
  }
1404
1505
  function orgCommand() {
1405
- const cmd = new import_commander8.Command("org").description(
1506
+ const cmd = new import_commander9.Command("org").description(
1406
1507
  "Manage the OpenBat tenant org (rename, members, invitations). Requires PAT."
1407
1508
  );
1408
1509
  cmd.command("list").description("List orgs the current PAT's user belongs to").action(async function() {
@@ -1510,7 +1611,7 @@ function orgCommand() {
1510
1611
  }
1511
1612
 
1512
1613
  // src/commands/write.ts
1513
- var import_commander9 = require("commander");
1614
+ var import_commander10 = require("commander");
1514
1615
  async function client3(globals) {
1515
1616
  const cfg = await resolveConfig({
1516
1617
  apiKeyFlag: globals.apiKey ?? null,
@@ -1543,7 +1644,7 @@ function surfacePlaintext(plaintext, label) {
1543
1644
  );
1544
1645
  }
1545
1646
  function chatbotsCommand() {
1546
- const cmd = new import_commander9.Command("chatbots").description(
1647
+ const cmd = new import_commander10.Command("chatbots").description(
1547
1648
  "List, create, delete chatbots in the current scope"
1548
1649
  );
1549
1650
  cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
@@ -1591,7 +1692,7 @@ function chatbotsCommand() {
1591
1692
  return cmd;
1592
1693
  }
1593
1694
  function webhooksCommand() {
1594
- const cmd = new import_commander9.Command("webhooks").description("Manage webhooks for a chatbot");
1695
+ const cmd = new import_commander10.Command("webhooks").description("Manage webhooks for a chatbot");
1595
1696
  cmd.command("list").action(async function() {
1596
1697
  try {
1597
1698
  const chatbotId = requireChatbotId(this);
@@ -1635,7 +1736,7 @@ function webhooksCommand() {
1635
1736
  return cmd;
1636
1737
  }
1637
1738
  function settingsCommand() {
1638
- const cmd = new import_commander9.Command("settings").description(
1739
+ const cmd = new import_commander10.Command("settings").description(
1639
1740
  "Manage chatbot settings + per-chatbot keys"
1640
1741
  );
1641
1742
  const keys = cmd.command("keys").description("Manage API keys for a chatbot");
@@ -1729,7 +1830,7 @@ function settingsCommand() {
1729
1830
  return cmd;
1730
1831
  }
1731
1832
  function workflowsCommand() {
1732
- const cmd = new import_commander9.Command("workflows").description("Manage workflows");
1833
+ const cmd = new import_commander10.Command("workflows").description("Manage workflows");
1733
1834
  cmd.command("list").action(async function() {
1734
1835
  try {
1735
1836
  const chatbotId = requireChatbotId(this);
@@ -1769,7 +1870,7 @@ function workflowsCommand() {
1769
1870
  return cmd;
1770
1871
  }
1771
1872
  function reportsCommand() {
1772
- const cmd = new import_commander9.Command("reports").description("Manage AI reports");
1873
+ const cmd = new import_commander10.Command("reports").description("Manage AI reports");
1773
1874
  cmd.command("list").action(async function() {
1774
1875
  try {
1775
1876
  const chatbotId = requireChatbotId(this);
@@ -1807,7 +1908,7 @@ Created report. View it (org members only):
1807
1908
  return cmd;
1808
1909
  }
1809
1910
  function analysisCommand() {
1810
- const cmd = new import_commander9.Command("analysis").description("Manage analysis definitions");
1911
+ const cmd = new import_commander10.Command("analysis").description("Manage analysis definitions");
1811
1912
  cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
1812
1913
  try {
1813
1914
  const chatbotId = requireChatbotId(this);
@@ -1849,7 +1950,7 @@ function analysisCommand() {
1849
1950
  return cmd;
1850
1951
  }
1851
1952
  function usersCommand() {
1852
- const cmd = new import_commander9.Command("users").description(
1953
+ const cmd = new import_commander10.Command("users").description(
1853
1954
  "List external users (chatbot customers) with health metrics"
1854
1955
  );
1855
1956
  cmd.command("list").option("--from <iso>").option("--to <iso>").option("--days <n>", "Convenience: last N days").option("--search <q>").option("--limit <n>", "Page size", "20").action(async function(opts) {
@@ -1882,7 +1983,7 @@ Total: ${result.total}
1882
1983
  return cmd;
1883
1984
  }
1884
1985
  function sdkCommand() {
1885
- const cmd = new import_commander9.Command("sdk").description(
1986
+ const cmd = new import_commander10.Command("sdk").description(
1886
1987
  "Help install and verify the OpenBat SDK in a target project"
1887
1988
  );
1888
1989
  cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
@@ -1992,10 +2093,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
1992
2093
  }
1993
2094
 
1994
2095
  // src/index.ts
1995
- var program = new import_commander10.Command();
2096
+ var program = new import_commander11.Command();
1996
2097
  program.name("openbat").description(
1997
2098
  "Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
1998
- ).version("0.3.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
2099
+ ).version("0.4.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
1999
2100
  "--base-url <url>",
2000
2101
  "Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
2001
2102
  ).option(
@@ -2016,6 +2117,7 @@ program.addCommand(chatbotsCommand());
2016
2117
  program.addCommand(conversationsCommand());
2017
2118
  program.addCommand(reviewCommand());
2018
2119
  program.addCommand(promptsCommand());
2120
+ program.addCommand(backtestsCommand());
2019
2121
  program.addCommand(usersCommand());
2020
2122
  program.addCommand(settingsCommand());
2021
2123
  program.addCommand(webhooksCommand());
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiClient
4
- } from "./chunk-KIY62R3O.mjs";
4
+ } from "./chunk-RA36VFZI.mjs";
5
5
 
6
6
  // src/index.ts
7
- import { Command as Command10 } from "commander";
7
+ import { Command as Command11 } from "commander";
8
8
 
9
9
  // src/commands/config.ts
10
10
  import { Command } from "commander";
@@ -768,6 +768,28 @@ function reviewCommand() {
768
768
  // src/commands/prompts.ts
769
769
  import { Command as Command5 } from "commander";
770
770
  import { promises as fs2 } from "fs";
771
+ async function waitForActive(api, chatbotId, versionId) {
772
+ const deadlineMs = Date.now() + 15e3;
773
+ let attempt = 0;
774
+ while (Date.now() < deadlineMs) {
775
+ const res = await api.get(
776
+ `/api/v1/chatbots/${chatbotId}/prompts/active`
777
+ );
778
+ if (res.activeVersionId === versionId) {
779
+ process.stderr.write(
780
+ `
781
+ Confirmed live (server): active version is ${versionId}. Live SDK processes converge within their ~60s fetch-cache TTL.
782
+ `
783
+ );
784
+ return;
785
+ }
786
+ attempt++;
787
+ await new Promise((r) => setTimeout(r, Math.min(500 * attempt, 2e3)));
788
+ }
789
+ process.stderr.write(
790
+ "\nPublished, but timed out (15s) waiting for server confirmation \u2014 the active pointer may still be propagating.\n"
791
+ );
792
+ }
771
793
  function promptsCommand() {
772
794
  const cmd = new Command5("prompts").description(
773
795
  "Manage the live published system prompt (list / publish / activate / kill-switch)"
@@ -784,7 +806,7 @@ function promptsCommand() {
784
806
  fatal(err instanceof Error ? err.message : String(err));
785
807
  }
786
808
  });
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) {
809
+ cmd.command("publish").description("Publish a template as the LIVE prompt (from --file or --text)").option("--file <path>", "Read the template text from a file").option("--text <text>", "Inline template text (prefer --file for long prompts)").option("--wait", "Block until the server confirms the new version is the active published prompt").action(async function(opts) {
788
810
  try {
789
811
  let templateText = opts.text;
790
812
  if (opts.file) templateText = await fs2.readFile(opts.file, "utf8");
@@ -798,9 +820,13 @@ function promptsCommand() {
798
820
  { templateText }
799
821
  );
800
822
  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
- );
823
+ if (opts.wait && result.versionId) {
824
+ await waitForActive(api, id, result.versionId);
825
+ } else {
826
+ process.stderr.write(
827
+ "\nPublished. Live within ~60s for chatbots that fetch their prompt from OpenBat.\n"
828
+ );
829
+ }
804
830
  } catch (err) {
805
831
  fatal(err instanceof Error ? err.message : String(err));
806
832
  }
@@ -835,11 +861,86 @@ function promptsCommand() {
835
861
  fatal(err instanceof Error ? err.message : String(err));
836
862
  }
837
863
  });
864
+ cmd.command("show <versionId>").description("Show a version's full template text (list shows only hashes)").action(async function(versionId) {
865
+ try {
866
+ const { api, chatbotFlag } = await client(this.optsWithGlobals());
867
+ const id = await resolveChatbotId(api, chatbotFlag);
868
+ const result = await api.get(
869
+ `/api/v1/chatbots/${id}/prompts/${versionId}`
870
+ );
871
+ emit(result, { json: !!this.optsWithGlobals().json });
872
+ } catch (err) {
873
+ fatal(err instanceof Error ? err.message : String(err));
874
+ }
875
+ });
876
+ cmd.command("active").description("Show what the server resolves to RIGHT NOW (active version, kill switch)").action(async function() {
877
+ try {
878
+ const { api, chatbotFlag } = await client(this.optsWithGlobals());
879
+ const id = await resolveChatbotId(api, chatbotFlag);
880
+ const result = await api.get(
881
+ `/api/v1/chatbots/${id}/prompts/active`
882
+ );
883
+ emit(result, { json: !!this.optsWithGlobals().json });
884
+ } catch (err) {
885
+ fatal(err instanceof Error ? err.message : String(err));
886
+ }
887
+ });
838
888
  return cmd;
839
889
  }
840
890
 
841
- // src/commands/login.ts
891
+ // src/commands/backtests.ts
842
892
  import { Command as Command6 } from "commander";
893
+ function backtestsCommand() {
894
+ const cmd = new Command6("backtests").description(
895
+ "Retest a candidate prompt against flagged conversations (create + poll status)"
896
+ );
897
+ cmd.command("create").description("Replay flagged conversations against a candidate prompt version").requiredOption("--name <name>", "Backtest name").requiredOption(
898
+ "--candidate-prompt <versionId>",
899
+ "Candidate prompt version id (from `openbat prompts list`)"
900
+ ).requiredOption(
901
+ "--flags <csv>",
902
+ "Comma-separated flag values to sample (e.g. churn_risk,billing_issue)"
903
+ ).option("--sample-size <n>", "How many flagged messages to replay (1-500)", "50").option("--from <iso>", "Only sample flags on/after this ISO timestamp").option("--to <iso>", "Only sample flags on/before this ISO timestamp").action(async function(opts) {
904
+ try {
905
+ const flagValues = opts.flags.split(",").map((s) => s.trim()).filter(Boolean);
906
+ if (flagValues.length === 0) {
907
+ fatal("Provide at least one flag via --flags <csv>.");
908
+ }
909
+ const { api, chatbotFlag } = await client(this.optsWithGlobals());
910
+ const id = await resolveChatbotId(api, chatbotFlag);
911
+ const result = await api.post(
912
+ `/api/v1/chatbots/${id}/backtests`,
913
+ {
914
+ name: opts.name,
915
+ candidatePromptId: opts.candidatePrompt,
916
+ flagValues,
917
+ sampleSize: Number(opts.sampleSize),
918
+ timeRangeStart: opts.from,
919
+ timeRangeEnd: opts.to
920
+ }
921
+ );
922
+ emit(result, { json: !!this.optsWithGlobals().json });
923
+ process.stderr.write("\nStarted. Poll progress with: openbat backtests status <id>\n");
924
+ } catch (err) {
925
+ fatal(err instanceof Error ? err.message : String(err));
926
+ }
927
+ });
928
+ cmd.command("status <backtestId>").description("Show a backtest's progress + verdict tally").action(async function(backtestId) {
929
+ try {
930
+ const { api } = await client(this.optsWithGlobals());
931
+ const result = await api.get(
932
+ `/api/v1/backtests/${encodeURIComponent(backtestId)}/status`
933
+ );
934
+ emit(result, { json: !!this.optsWithGlobals().json });
935
+ } catch (err) {
936
+ fatal(err instanceof Error ? err.message : String(err));
937
+ }
938
+ });
939
+ return cmd;
940
+ }
941
+
942
+ // src/commands/login.ts
943
+ import { Command as Command7 } from "commander";
843
944
  import os from "os";
844
945
  import readline from "readline";
845
946
 
@@ -982,7 +1083,7 @@ async function runLoopbackServer(opts) {
982
1083
  // src/commands/login.ts
983
1084
  var HOSTNAME = os.hostname();
984
1085
  function loginCommand() {
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(
1086
+ return new Command7("login").description("Sign in via browser and install an API key on this device").option("--device", "Use the device-code flow (for SSH / headless machines)").option(
986
1087
  "--use <key>",
987
1088
  "Skip the browser; install a PAT plaintext you already have"
988
1089
  ).action(async function(opts) {
@@ -1172,9 +1273,9 @@ You have ${chatbots.length} chatbots. Pick one later with \`openbat use <id>\`.
1172
1273
  }
1173
1274
 
1174
1275
  // src/commands/logout.ts
1175
- import { Command as Command7 } from "commander";
1276
+ import { Command as Command8 } from "commander";
1176
1277
  function logoutCommand() {
1177
- return new Command7("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
1278
+ return new Command8("logout").description("Revoke the stored API key and clear ~/.openbatrc").action(async function() {
1178
1279
  try {
1179
1280
  const globals = this.optsWithGlobals();
1180
1281
  const cfg = await resolveConfig({
@@ -1209,7 +1310,7 @@ function logoutCommand() {
1209
1310
  }
1210
1311
 
1211
1312
  // src/commands/org.ts
1212
- import { Command as Command8 } from "commander";
1313
+ import { Command as Command9 } from "commander";
1213
1314
  async function client2(globals) {
1214
1315
  const cfg = await resolveConfig({
1215
1316
  apiKeyFlag: globals.apiKey ?? null,
@@ -1224,7 +1325,7 @@ async function client2(globals) {
1224
1325
  return new ApiClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
1225
1326
  }
1226
1327
  function orgCommand() {
1227
- const cmd = new Command8("org").description(
1328
+ const cmd = new Command9("org").description(
1228
1329
  "Manage the OpenBat tenant org (rename, members, invitations). Requires PAT."
1229
1330
  );
1230
1331
  cmd.command("list").description("List orgs the current PAT's user belongs to").action(async function() {
@@ -1332,7 +1433,7 @@ function orgCommand() {
1332
1433
  }
1333
1434
 
1334
1435
  // src/commands/write.ts
1335
- import { Command as Command9 } from "commander";
1436
+ import { Command as Command10 } from "commander";
1336
1437
  async function client3(globals) {
1337
1438
  const cfg = await resolveConfig({
1338
1439
  apiKeyFlag: globals.apiKey ?? null,
@@ -1365,7 +1466,7 @@ function surfacePlaintext(plaintext, label) {
1365
1466
  );
1366
1467
  }
1367
1468
  function chatbotsCommand() {
1368
- const cmd = new Command9("chatbots").description(
1469
+ const cmd = new Command10("chatbots").description(
1369
1470
  "List, create, delete chatbots in the current scope"
1370
1471
  );
1371
1472
  cmd.command("list").description("List every chatbot the credential can reach").action(async function() {
@@ -1413,7 +1514,7 @@ function chatbotsCommand() {
1413
1514
  return cmd;
1414
1515
  }
1415
1516
  function webhooksCommand() {
1416
- const cmd = new Command9("webhooks").description("Manage webhooks for a chatbot");
1517
+ const cmd = new Command10("webhooks").description("Manage webhooks for a chatbot");
1417
1518
  cmd.command("list").action(async function() {
1418
1519
  try {
1419
1520
  const chatbotId = requireChatbotId(this);
@@ -1457,7 +1558,7 @@ function webhooksCommand() {
1457
1558
  return cmd;
1458
1559
  }
1459
1560
  function settingsCommand() {
1460
- const cmd = new Command9("settings").description(
1561
+ const cmd = new Command10("settings").description(
1461
1562
  "Manage chatbot settings + per-chatbot keys"
1462
1563
  );
1463
1564
  const keys = cmd.command("keys").description("Manage API keys for a chatbot");
@@ -1551,7 +1652,7 @@ function settingsCommand() {
1551
1652
  return cmd;
1552
1653
  }
1553
1654
  function workflowsCommand() {
1554
- const cmd = new Command9("workflows").description("Manage workflows");
1655
+ const cmd = new Command10("workflows").description("Manage workflows");
1555
1656
  cmd.command("list").action(async function() {
1556
1657
  try {
1557
1658
  const chatbotId = requireChatbotId(this);
@@ -1591,7 +1692,7 @@ function workflowsCommand() {
1591
1692
  return cmd;
1592
1693
  }
1593
1694
  function reportsCommand() {
1594
- const cmd = new Command9("reports").description("Manage AI reports");
1695
+ const cmd = new Command10("reports").description("Manage AI reports");
1595
1696
  cmd.command("list").action(async function() {
1596
1697
  try {
1597
1698
  const chatbotId = requireChatbotId(this);
@@ -1629,7 +1730,7 @@ Created report. View it (org members only):
1629
1730
  return cmd;
1630
1731
  }
1631
1732
  function analysisCommand() {
1632
- const cmd = new Command9("analysis").description("Manage analysis definitions");
1733
+ const cmd = new Command10("analysis").description("Manage analysis definitions");
1633
1734
  cmd.command("list").option("--type <t>", "intent | flag | assistant_outcome | assistant_issue").option("--pending", "Include pending suggestions").action(async function(opts) {
1634
1735
  try {
1635
1736
  const chatbotId = requireChatbotId(this);
@@ -1671,7 +1772,7 @@ function analysisCommand() {
1671
1772
  return cmd;
1672
1773
  }
1673
1774
  function usersCommand() {
1674
- const cmd = new Command9("users").description(
1775
+ const cmd = new Command10("users").description(
1675
1776
  "List external users (chatbot customers) with health metrics"
1676
1777
  );
1677
1778
  cmd.command("list").option("--from <iso>").option("--to <iso>").option("--days <n>", "Convenience: last N days").option("--search <q>").option("--limit <n>", "Page size", "20").action(async function(opts) {
@@ -1704,7 +1805,7 @@ Total: ${result.total}
1704
1805
  return cmd;
1705
1806
  }
1706
1807
  function sdkCommand() {
1707
- const cmd = new Command9("sdk").description(
1808
+ const cmd = new Command10("sdk").description(
1708
1809
  "Help install and verify the OpenBat SDK in a target project"
1709
1810
  );
1710
1811
  cmd.command("install-instructions").description("Print markdown the calling agent can follow").option(
@@ -1814,10 +1915,10 @@ Timed out after ${timeoutSec}s \u2014 no events yet. Confirm OPENBAT_API_KEY and
1814
1915
  }
1815
1916
 
1816
1917
  // src/index.ts
1817
- var program = new Command10();
1918
+ var program = new Command11();
1818
1919
  program.name("openbat").description(
1819
1920
  "Query OpenBat chatbot data \u2014 conversations, sentiment, analytics, exports."
1820
- ).version("0.3.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
1921
+ ).version("0.4.0").option("--api-key <key>", "Override the stored Read API key (footgun \u2014 leaks into shell history)").option(
1821
1922
  "--base-url <url>",
1822
1923
  "Override the OpenBat API base URL (defaults to ~/.openbatrc or https://openbat.dev)"
1823
1924
  ).option(
@@ -1838,6 +1939,7 @@ program.addCommand(chatbotsCommand());
1838
1939
  program.addCommand(conversationsCommand());
1839
1940
  program.addCommand(reviewCommand());
1840
1941
  program.addCommand(promptsCommand());
1942
+ program.addCommand(backtestsCommand());
1841
1943
  program.addCommand(usersCommand());
1842
1944
  program.addCommand(settingsCommand());
1843
1945
  program.addCommand(webhooksCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openbat/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Command-line tool for querying OpenBat chatbot data — conversations, sentiment, analytics, exports",
5
5
  "bin": {
6
6
  "openbat": "bin/openbat"