@beastmode-develeap/beastmode 0.1.109 → 0.1.111

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
@@ -5005,6 +5005,18 @@ function getBoardRoutes(factoryDir) {
5005
5005
  return proxyToBoard(boardUrl, "GET", "/api/pipeline-config", void 0, scopedQuery(query));
5006
5006
  }
5007
5007
  },
5008
+ // ── Telemetry status (Gap 15: opt-in anonymous telemetry) ──
5009
+ // Direct passthrough proxy — matches the backend route path verbatim
5010
+ // so the Settings UI can fetch /api/telemetry/status through the UI
5011
+ // server at http://ui:8080. Same pattern as /api/pipeline-config.
5012
+ {
5013
+ method: "GET",
5014
+ pattern: "/api/telemetry/status",
5015
+ handler: async (_body, _params, query) => {
5016
+ const boardUrl = getBoardUrl2(factoryDir);
5017
+ return proxyToBoard(boardUrl, "GET", "/api/telemetry/status", void 0, scopedQuery(query));
5018
+ }
5019
+ },
5008
5020
  // ── Board Items (proxy to existing board at :8080) ──
5009
5021
  // Every proxy forwards the `board` query param so the Python server
5010
5022
  // can route to the correct per-project SQLite database. `"all"` is
@@ -7502,7 +7514,10 @@ var initCommand = new Command("init").description("Create a new BeastMode factor
7502
7514
  ).option(
7503
7515
  "--ghcr-pull-token-file <path>",
7504
7516
  "Read GHCR pull token from file (safer than --ghcr-pull-token)"
7505
- ).option("--board-password <password>", "Board UI password (avoids interactive prompt)").option("--board-password-file <path>", "Read board password from file").action(async (name, opts) => {
7517
+ ).option("--board-password <password>", "Board UI password (avoids interactive prompt)").option("--board-password-file <path>", "Read board password from file").option(
7518
+ "--telemetry",
7519
+ "Enable opt-in anonymous telemetry non-interactively (default off, including with --yes). See docs/telemetry.md."
7520
+ ).action(async (name, opts) => {
7506
7521
  try {
7507
7522
  await runInit(name, opts);
7508
7523
  } catch (err) {
@@ -7553,7 +7568,7 @@ async function runInit(name, opts) {
7553
7568
  await runImageModeInit(name, opts);
7554
7569
  return;
7555
7570
  }
7556
- const totalSteps = 5;
7571
+ const totalSteps = 6;
7557
7572
  header("BeastMode \u2014 Dark Factory Init");
7558
7573
  info("Creating your Custom Dark Factory\n");
7559
7574
  const factoryName = name || basename4(resolve5("."));
@@ -7766,7 +7781,40 @@ async function runInit(name, opts) {
7766
7781
  } else if (uiPassword) {
7767
7782
  success("Board UI password set");
7768
7783
  }
7769
- step(5, totalSteps, "Boot");
7784
+ step(5, totalSteps, "Telemetry");
7785
+ let telemetryEnabled = false;
7786
+ let telemetrySentryDsn = "";
7787
+ if (opts.telemetry) {
7788
+ telemetryEnabled = true;
7789
+ } else if (!opts.yes) {
7790
+ const answer = await inquirer.prompt([
7791
+ {
7792
+ type: "confirm",
7793
+ name: "enabled",
7794
+ message: "Help improve BeastMode with anonymous telemetry? (pipeline metrics only \u2014 no code, no PII)",
7795
+ default: false
7796
+ }
7797
+ ]);
7798
+ telemetryEnabled = !!answer.enabled;
7799
+ if (telemetryEnabled) {
7800
+ const sentryAnswer = await inquirer.prompt([
7801
+ {
7802
+ type: "input",
7803
+ name: "dsn",
7804
+ message: "Sentry DSN for error reporting (leave empty to skip):",
7805
+ default: ""
7806
+ }
7807
+ ]);
7808
+ telemetrySentryDsn = (sentryAnswer.dsn || "").trim();
7809
+ }
7810
+ }
7811
+ if (telemetryEnabled) {
7812
+ success("Telemetry enabled \u2014 anonymous pipeline metrics will be collected");
7813
+ info("You can disable anytime: set telemetry.enabled=false in config/beastmode.daemon.json");
7814
+ } else {
7815
+ info("Telemetry disabled \u2014 no data will be collected");
7816
+ }
7817
+ step(6, totalSteps, "Boot");
7770
7818
  const projectName = basename4(projectPath);
7771
7819
  const actions = scaffoldFactory(factoryName, config, {
7772
7820
  name: projectName,
@@ -7791,6 +7839,12 @@ async function runInit(name, opts) {
7791
7839
  if (uiPassword) {
7792
7840
  collectedSecrets.push(`BEASTMODE_UI_PASSWORD=${uiPassword}`);
7793
7841
  }
7842
+ if (telemetryEnabled) {
7843
+ collectedSecrets.push(`BEASTMODE_TELEMETRY_ENABLED=true`);
7844
+ if (telemetrySentryDsn) {
7845
+ collectedSecrets.push(`BEASTMODE_TELEMETRY_SENTRY_DSN=${telemetrySentryDsn}`);
7846
+ }
7847
+ }
7794
7848
  if (collectedSecrets.length > 0) {
7795
7849
  const secretsPath = resolve5(factoryName, ".beastmode", "secrets.env.local");
7796
7850
  const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + collectedSecrets.join("\n") + "\n";
@@ -7811,7 +7865,7 @@ async function runInit(name, opts) {
7811
7865
  console.log();
7812
7866
  }
7813
7867
  async function runImageModeInit(name, opts) {
7814
- const totalSteps = 4;
7868
+ const totalSteps = 5;
7815
7869
  header("BeastMode \u2014 Factory Init (Image Mode)");
7816
7870
  info("Setting up BeastMode from pre-built Docker images\n");
7817
7871
  const factoryName = name || ".";
@@ -7937,7 +7991,40 @@ async function runImageModeInit(name, opts) {
7937
7991
  } else {
7938
7992
  success("Authenticated to ghcr.io");
7939
7993
  }
7940
- step(3, totalSteps, "Generate");
7994
+ step(3, totalSteps, "Telemetry");
7995
+ let telemetryEnabled = false;
7996
+ let telemetrySentryDsn = "";
7997
+ if (opts.telemetry) {
7998
+ telemetryEnabled = true;
7999
+ } else if (!opts.yes) {
8000
+ const answer = await inquirer.prompt([
8001
+ {
8002
+ type: "confirm",
8003
+ name: "enabled",
8004
+ message: "Help improve BeastMode with anonymous telemetry? (pipeline metrics only \u2014 no code, no PII)",
8005
+ default: false
8006
+ }
8007
+ ]);
8008
+ telemetryEnabled = !!answer.enabled;
8009
+ if (telemetryEnabled) {
8010
+ const sentryAnswer = await inquirer.prompt([
8011
+ {
8012
+ type: "input",
8013
+ name: "dsn",
8014
+ message: "Sentry DSN for error reporting (leave empty to skip):",
8015
+ default: ""
8016
+ }
8017
+ ]);
8018
+ telemetrySentryDsn = (sentryAnswer.dsn || "").trim();
8019
+ }
8020
+ }
8021
+ if (telemetryEnabled) {
8022
+ success("Telemetry enabled \u2014 anonymous pipeline metrics will be collected");
8023
+ info("You can disable anytime: remove BEASTMODE_TELEMETRY_ENABLED from .env");
8024
+ } else {
8025
+ info("Telemetry disabled \u2014 no data will be collected");
8026
+ }
8027
+ step(4, totalSteps, "Generate");
7941
8028
  mkdirSync12(targetDir, { recursive: true });
7942
8029
  const composeContent = generateComposeYaml("latest");
7943
8030
  writeFileSync13(join15(targetDir, "docker-compose.yml"), composeContent, "utf-8");
@@ -7978,6 +8065,14 @@ async function runImageModeInit(name, opts) {
7978
8065
  "# (git remote get-url origin) doesn't work for your setup.",
7979
8066
  "# The daemon handles this automatically in >99% of cases.",
7980
8067
  "# PROJECT_REPO=owner/repo",
8068
+ "",
8069
+ "# \u2500\u2500 Telemetry (Gap 15) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
8070
+ "# Opt-in anonymous pipeline metrics. Default: off. Collects",
8071
+ "# durations, satisfaction scores, iteration counts, error classes.",
8072
+ "# Never collects code, file paths, task descriptions, or PII.",
8073
+ "# See docs/telemetry.md for the full privacy contract.",
8074
+ `BEASTMODE_TELEMETRY_ENABLED=${telemetryEnabled ? "true" : "false"}`,
8075
+ `BEASTMODE_TELEMETRY_SENTRY_DSN=${telemetrySentryDsn}`,
7981
8076
  ""
7982
8077
  ];
7983
8078
  writeFileSync13(join15(targetDir, ".env"), envLines.join("\n"), "utf-8");
@@ -7985,7 +8080,7 @@ async function runImageModeInit(name, opts) {
7985
8080
  for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
7986
8081
  mkdirSync12(join15(targetDir, dir), { recursive: true });
7987
8082
  }
7988
- step(4, totalSteps, "Pull Images");
8083
+ step(5, totalSteps, "Pull Images");
7989
8084
  try {
7990
8085
  const { execSync: exec } = await import("child_process");
7991
8086
  exec("docker compose pull", {