@buildautomaton/cli 0.1.15 → 0.1.16

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
@@ -2240,7 +2240,7 @@ var require_websocket = __commonJS({
2240
2240
  var http = __require("http");
2241
2241
  var net = __require("net");
2242
2242
  var tls = __require("tls");
2243
- var { randomBytes, createHash: createHash2 } = __require("crypto");
2243
+ var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
2244
2244
  var { Duplex, Readable: Readable2 } = __require("stream");
2245
2245
  var { URL: URL2 } = __require("url");
2246
2246
  var PerMessageDeflate = require_permessage_deflate();
@@ -2770,7 +2770,7 @@ var require_websocket = __commonJS({
2770
2770
  }
2771
2771
  }
2772
2772
  const defaultPort = isSecure ? 443 : 80;
2773
- const key = randomBytes(16).toString("base64");
2773
+ const key = randomBytes2(16).toString("base64");
2774
2774
  const request = isSecure ? https2.request : http.request;
2775
2775
  const protocolSet = /* @__PURE__ */ new Set();
2776
2776
  let perMessageDeflate;
@@ -22787,8 +22787,8 @@ var PREVIEW_API_BASE_PATH = "/__preview";
22787
22787
  var PREVIEW_SECRET_HEADER = "X-Preview-Secret";
22788
22788
  var DEFAULT_PORT = 3e3;
22789
22789
  var DEFAULT_COMMAND = "npm run preview";
22790
- var PREVIEW_COMMAND_ENV = "BUILDAMATON_PREVIEW_COMMAND";
22791
- var PREVIEW_PORT_ENV = "BUILDAMATON_PREVIEW_PORT";
22790
+ var PREVIEW_COMMAND_ENV = "BUILDAUTOMATON_PREVIEW_COMMAND";
22791
+ var PREVIEW_PORT_ENV = "BUILDAUTOMATON_PREVIEW_PORT";
22792
22792
  var previewProcess = null;
22793
22793
  var previewPort = DEFAULT_PORT;
22794
22794
  var previewSecret = "";
@@ -22841,7 +22841,7 @@ var OPERATIONS = [
22841
22841
  var previewSkill = {
22842
22842
  id: "preview",
22843
22843
  name: "Preview",
22844
- description: "Start and manage a local preview server that implements the BuildAutomaton Preview Server API. Configure the command with BUILDAMATON_PREVIEW_COMMAND (default: npm run preview). The server receives PORT and PREVIEW_SECRET and must expose /__preview/status and /__preview/stop.",
22844
+ description: "Start and manage a local preview server that implements the BuildAutomaton Preview Server API. Configure the command with BUILDAUTOMATON_PREVIEW_COMMAND (default: npm run preview). The server receives PORT and PREVIEW_SECRET and must expose /__preview/status and /__preview/stop.",
22845
22845
  operations: OPERATIONS,
22846
22846
  async execute(operationId, params) {
22847
22847
  const command = getPreviewCommand();
@@ -23564,8 +23564,11 @@ function isLocalApiUrl(apiUrl) {
23564
23564
  return false;
23565
23565
  }
23566
23566
  }
23567
+ function appUrlForApiUrl(apiUrl) {
23568
+ return apiUrl && isLocalApiUrl(apiUrl) ? process.env.BUILDAUTOMATON_APP_URL ?? "http://localhost:3000" : process.env.BUILDAUTOMATON_APP_URL ?? "https://app.buildautomaton.com";
23569
+ }
23567
23570
  async function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiUrl, logFn = log) {
23568
- const appUrl = apiUrl && isLocalApiUrl(apiUrl) ? process.env.BUILDAUTOMATON_APP_URL ?? "http://localhost:3000" : process.env.BUILDAUTOMATON_APP_URL ?? "https://app.buildautomaton.com";
23571
+ const appUrl = appUrlForApiUrl(apiUrl);
23569
23572
  let connectCliUrl = `${appUrl.replace(/\/$/, "")}/bridges/connect?connectionId=${connectionId}`;
23570
23573
  if (initialWorkspaceId) {
23571
23574
  try {
@@ -28994,7 +28997,7 @@ async function createCursorAcpClient(options) {
28994
28997
  onRequest,
28995
28998
  onFileChange
28996
28999
  } = options;
28997
- const dbgFs = process.env.BUILDAMATON_DEBUG_ACP_FS === "1";
29000
+ const dbgFs = process.env.BUILDAUTOMATON_DEBUG_ACP_FS === "1";
28998
29001
  const isWindows = process.platform === "win32";
28999
29002
  const child = spawn4(command[0], command.slice(1), {
29000
29003
  cwd,
@@ -31541,7 +31544,7 @@ function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
31541
31544
  }
31542
31545
 
31543
31546
  // src/dev-servers/manager/dev-server-manager.ts
31544
- import { rm } from "node:fs/promises";
31547
+ import { rm as rm2 } from "node:fs/promises";
31545
31548
 
31546
31549
  // src/dev-servers/process/send-server-status.ts
31547
31550
  function sendDevServerStatus(getWs, serverId, status, options) {
@@ -32050,8 +32053,90 @@ var StreamTail = class {
32050
32053
  }
32051
32054
  };
32052
32055
 
32053
- // src/dev-servers/manager/dev-server-manager.ts
32056
+ // src/dev-servers/manager/dev-server-constants.ts
32054
32057
  var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
32058
+
32059
+ // src/dev-servers/manager/dev-server-firehose-messages.ts
32060
+ function buildFirehoseSnapshotMessage(params) {
32061
+ const payload = {
32062
+ type: "log_snapshot",
32063
+ serverId: params.serverId,
32064
+ viewerId: params.viewerId,
32065
+ stdoutTail: params.tails.stdout,
32066
+ stderrTail: params.tails.stderr
32067
+ };
32068
+ return params.e2ee ? params.e2ee.encryptFields(payload, ["stdoutTail", "stderrTail"]) : payload;
32069
+ }
32070
+ function buildFirehoseLogChunkMessage(params) {
32071
+ const payload = {
32072
+ type: "log_chunk",
32073
+ serverId: params.serverId,
32074
+ stream: params.stream,
32075
+ text: params.text
32076
+ };
32077
+ return params.e2ee ? params.e2ee.encryptFields(payload, ["text"]) : payload;
32078
+ }
32079
+
32080
+ // src/dev-servers/manager/dev-server-firehose-sink.ts
32081
+ var DevServerFirehoseSink = class {
32082
+ constructor(options) {
32083
+ this.options = options;
32084
+ }
32085
+ logViewerRefCountByServerId = /* @__PURE__ */ new Map();
32086
+ firehoseSend = null;
32087
+ attach(send) {
32088
+ this.firehoseSend = send;
32089
+ }
32090
+ detach() {
32091
+ this.firehoseSend = null;
32092
+ this.logViewerRefCountByServerId.clear();
32093
+ }
32094
+ openLogViewer(serverId, viewerId) {
32095
+ const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
32096
+ this.logViewerRefCountByServerId.set(serverId, next);
32097
+ this.sendSnapshot(serverId, viewerId);
32098
+ }
32099
+ closeLogViewer(serverId) {
32100
+ const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
32101
+ if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
32102
+ else this.logViewerRefCountByServerId.set(serverId, n);
32103
+ }
32104
+ pushLogChunk(serverId, stream, chunk) {
32105
+ if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
32106
+ if (!this.options.isPipedCaptureEnabled(serverId)) return;
32107
+ if (!this.firehoseSend) return;
32108
+ const text = chunk.toString("utf8");
32109
+ setImmediate(() => {
32110
+ if (!this.firehoseSend) return;
32111
+ this.firehoseSend(buildFirehoseLogChunkMessage({ serverId, stream, text, e2ee: this.options.e2ee }));
32112
+ });
32113
+ }
32114
+ sendSnapshot(serverId, viewerId) {
32115
+ const payload = buildFirehoseSnapshotMessage({
32116
+ serverId,
32117
+ viewerId,
32118
+ tails: this.options.getTails(serverId),
32119
+ e2ee: this.options.e2ee
32120
+ });
32121
+ setImmediate(() => {
32122
+ const send = this.firehoseSend;
32123
+ if (!send) return;
32124
+ send(payload);
32125
+ });
32126
+ }
32127
+ };
32128
+
32129
+ // src/dev-servers/manager/cleanup-merged-log-dir.ts
32130
+ import { rm } from "node:fs/promises";
32131
+ function cleanupMergedLogDirForServer(map2, serverId) {
32132
+ const mergedDir = map2.get(serverId);
32133
+ if (!mergedDir) return;
32134
+ map2.delete(serverId);
32135
+ void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32136
+ });
32137
+ }
32138
+
32139
+ // src/dev-servers/manager/dev-server-manager.ts
32055
32140
  var emptyTails = () => ({ stdout: [], stderr: [] });
32056
32141
  var DevServerManager = class {
32057
32142
  defsById = /* @__PURE__ */ new Map();
@@ -32059,66 +32144,36 @@ var DevServerManager = class {
32059
32144
  streamTailsByServerId = /* @__PURE__ */ new Map();
32060
32145
  spawnGenerationByServerId = /* @__PURE__ */ new Map();
32061
32146
  pipedCaptureByServerId = /* @__PURE__ */ new Map();
32062
- logViewerRefCountByServerId = /* @__PURE__ */ new Map();
32063
- firehoseSend = null;
32064
32147
  mergedLogPollByServerId = /* @__PURE__ */ new Map();
32065
32148
  mergedLogCleanupDirByServerId = /* @__PURE__ */ new Map();
32066
32149
  abortControllersByServerId = /* @__PURE__ */ new Map();
32067
32150
  getWs;
32068
32151
  log;
32069
32152
  getBridgeCwd;
32153
+ e2ee;
32154
+ firehoseSink;
32070
32155
  constructor(options) {
32071
32156
  this.getWs = options.getWs;
32072
32157
  this.log = options.log;
32073
32158
  this.getBridgeCwd = options.getBridgeCwd ?? (() => process.cwd());
32159
+ this.e2ee = options.e2ee;
32160
+ this.firehoseSink = new DevServerFirehoseSink({
32161
+ getTails: (serverId) => this.snapshotTails(serverId),
32162
+ isPipedCaptureEnabled: (serverId) => this.pipedCaptureByServerId.get(serverId) === true,
32163
+ e2ee: this.e2ee
32164
+ });
32074
32165
  }
32075
32166
  attachFirehose(send) {
32076
- this.firehoseSend = send;
32167
+ this.firehoseSink.attach(send);
32077
32168
  }
32078
32169
  detachFirehose() {
32079
- this.firehoseSend = null;
32080
- this.logViewerRefCountByServerId.clear();
32170
+ this.firehoseSink.detach();
32081
32171
  }
32082
32172
  handleFirehoseLogViewerOpen(serverId, _viewerId) {
32083
- const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
32084
- this.logViewerRefCountByServerId.set(serverId, next);
32085
- this.sendSnapshotToFirehose(serverId, _viewerId);
32173
+ this.firehoseSink.openLogViewer(serverId, _viewerId);
32086
32174
  }
32087
32175
  handleFirehoseLogViewerClose(serverId, _viewerId) {
32088
- const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
32089
- if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
32090
- else this.logViewerRefCountByServerId.set(serverId, n);
32091
- }
32092
- sendSnapshotToFirehose(serverId, viewerId) {
32093
- const tails = this.streamTailsByServerId.get(serverId);
32094
- const payload = {
32095
- type: "log_snapshot",
32096
- serverId,
32097
- viewerId,
32098
- stdoutTail: tails?.stdout.getTail() ?? [],
32099
- stderrTail: tails?.stderr.getTail() ?? []
32100
- };
32101
- setImmediate(() => {
32102
- const send = this.firehoseSend;
32103
- if (!send) return;
32104
- send(payload);
32105
- });
32106
- }
32107
- pushRemoteLogChunk(serverId, stream, chunk) {
32108
- if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
32109
- if (!this.pipedCaptureByServerId.get(serverId)) return;
32110
- const send = this.firehoseSend;
32111
- if (!send) return;
32112
- const text = chunk.toString("utf8");
32113
- setImmediate(() => {
32114
- if (!this.firehoseSend) return;
32115
- this.firehoseSend({
32116
- type: "log_chunk",
32117
- serverId,
32118
- stream,
32119
- text
32120
- });
32121
- });
32176
+ this.firehoseSink.closeLogViewer(serverId);
32122
32177
  }
32123
32178
  applyConfig(servers) {
32124
32179
  this.defsById.clear();
@@ -32171,12 +32226,7 @@ var DevServerManager = class {
32171
32226
  }
32172
32227
  this.clearTails(serverId);
32173
32228
  this.pipedCaptureByServerId.delete(serverId);
32174
- const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
32175
- if (mergedDir) {
32176
- this.mergedLogCleanupDirByServerId.delete(serverId);
32177
- void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32178
- });
32179
- }
32229
+ cleanupMergedLogDirForServer(this.mergedLogCleanupDirByServerId, serverId);
32180
32230
  this.sendStatus(serverId, "stopped", void 0, tails);
32181
32231
  }
32182
32232
  start(serverId) {
@@ -32259,7 +32309,7 @@ var DevServerManager = class {
32259
32309
  log: this.log,
32260
32310
  stdoutTail,
32261
32311
  stderrTail,
32262
- pushRemoteLogChunk: (sid, stream, chunk) => this.pushRemoteLogChunk(sid, stream, chunk),
32312
+ pushRemoteLogChunk: (sid, stream, chunk) => this.firehoseSink.pushLogChunk(sid, stream, chunk),
32263
32313
  sendStatus: (status, detail, tails) => this.sendStatus(serverId, status, detail, tails),
32264
32314
  setPollInterval: (iv) => {
32265
32315
  if (iv) this.mergedLogPollByServerId.set(serverId, iv);
@@ -32273,7 +32323,7 @@ var DevServerManager = class {
32273
32323
  this.mergedLogCleanupDirByServerId.delete(serverId);
32274
32324
  },
32275
32325
  rmMergedCleanupDir: (dir) => {
32276
- void rm(dir, { recursive: true, force: true }).catch(() => {
32326
+ void rm2(dir, { recursive: true, force: true }).catch(() => {
32277
32327
  });
32278
32328
  },
32279
32329
  clearTailBuffers: () => this.clearTails(serverId)
@@ -32310,12 +32360,7 @@ var DevServerManager = class {
32310
32360
  this.processes.delete(serverId);
32311
32361
  this.clearPoll(serverId);
32312
32362
  this.pipedCaptureByServerId.delete(serverId);
32313
- const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
32314
- if (mergedDir) {
32315
- this.mergedLogCleanupDirByServerId.delete(serverId);
32316
- void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32317
- });
32318
- }
32363
+ cleanupMergedLogDirForServer(this.mergedLogCleanupDirByServerId, serverId);
32319
32364
  const tails = this.snapshotTails(serverId);
32320
32365
  this.clearTails(serverId);
32321
32366
  this.sendStatus(serverId, "unknown", "Bridge closed before process exited", tails);
@@ -32775,7 +32820,7 @@ function reportGitRepos(getWs, log2) {
32775
32820
  var handleAuthToken = (msg, { log: log2 }) => {
32776
32821
  if (typeof msg.token !== "string") return;
32777
32822
  log2("Received auth token. Save it for future runs:");
32778
- log2(` export BUILDAMATON_AUTH_TOKEN="${msg.token}"`);
32823
+ log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
32779
32824
  };
32780
32825
 
32781
32826
  // src/bridge/routing/handlers/bridge-identified.ts
@@ -32895,21 +32940,28 @@ function handleBridgePrompt(msg, deps) {
32895
32940
  const sessionId = msg.sessionId;
32896
32941
  const runId = typeof msg.runId === "string" ? msg.runId : void 0;
32897
32942
  const promptId = typeof msg.id === "string" ? msg.id : void 0;
32943
+ const sendBridgeMessage = (message, encryptedFields = []) => {
32944
+ const s = getWs();
32945
+ if (!s) return false;
32946
+ const wire = deps.e2ee && encryptedFields.length > 0 ? deps.e2ee.encryptFields(message, encryptedFields) : message;
32947
+ sendWsMessage(s, wire);
32948
+ return true;
32949
+ };
32898
32950
  if (!promptText.trim()) {
32899
32951
  log2(
32900
32952
  `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
32901
32953
  );
32902
- const s = getWs();
32903
- if (s) {
32904
- sendWsMessage(s, {
32954
+ sendBridgeMessage(
32955
+ {
32905
32956
  type: "prompt_result",
32906
32957
  ...promptId ? { id: promptId } : {},
32907
32958
  ...sessionId ? { sessionId } : {},
32908
32959
  ...runId ? { runId } : {},
32909
32960
  success: false,
32910
32961
  error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
32911
- });
32912
- }
32962
+ },
32963
+ ["error"]
32964
+ );
32913
32965
  return;
32914
32966
  }
32915
32967
  const isNewSession = msg.isNewSession === true;
@@ -32918,8 +32970,7 @@ function handleBridgePrompt(msg, deps) {
32918
32970
  const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
32919
32971
  acpManager.logPromptReceivedFromBridge({ agentType, mode });
32920
32972
  const sendResult2 = (result) => {
32921
- const s = getWs();
32922
- if (s) sendWsMessage(s, result);
32973
+ sendBridgeMessage(result, result.type === "prompt_result" ? ["output", "error"] : []);
32923
32974
  };
32924
32975
  const sendSessionUpdate = (payload) => {
32925
32976
  const s = getWs();
@@ -32928,7 +32979,15 @@ function handleBridgePrompt(msg, deps) {
32928
32979
  return;
32929
32980
  }
32930
32981
  const p = payload;
32931
- sendWsMessage(s, payload);
32982
+ const wire = p.type === "session_update" && deps.e2ee ? deps.e2ee.encryptFields(payload, ["payload"]) : p.type === "session_file_change" && deps.e2ee ? deps.e2ee.encryptFields(payload, [
32983
+ "path",
32984
+ "oldText",
32985
+ "newText",
32986
+ "patchContent",
32987
+ "isDirectory",
32988
+ "directoryRemoved"
32989
+ ]) : payload;
32990
+ sendWsMessage(s, wire);
32932
32991
  };
32933
32992
  async function preambleAndPrompt(resolvedCwd) {
32934
32993
  const s = getWs();
@@ -33309,28 +33368,30 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
33309
33368
 
33310
33369
  // src/files/handle-file-browser-search.ts
33311
33370
  var SEARCH_LIMIT = 100;
33312
- function handleFileBrowserSearch(msg, socket) {
33371
+ function handleFileBrowserSearch(msg, socket, e2ee) {
33313
33372
  void (async () => {
33314
33373
  await yieldToEventLoop();
33315
33374
  const q = typeof msg.q === "string" ? msg.q : "";
33316
33375
  const cwd = getBridgeWorkspaceDirectory();
33317
33376
  const index = loadFileIndex(cwd);
33318
33377
  if (index === null) {
33319
- sendWsMessage(socket, {
33378
+ const payload2 = {
33320
33379
  type: "file_browser_search_response",
33321
33380
  id: msg.id,
33322
33381
  paths: [],
33323
33382
  indexReady: false
33324
- });
33383
+ };
33384
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
33325
33385
  return;
33326
33386
  }
33327
33387
  const results = await searchFileIndexAsync(index, q, SEARCH_LIMIT);
33328
- sendWsMessage(socket, {
33388
+ const payload = {
33329
33389
  type: "file_browser_search_response",
33330
33390
  id: msg.id,
33331
33391
  paths: results,
33332
33392
  indexReady: true
33333
- });
33393
+ };
33394
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
33334
33395
  })();
33335
33396
  }
33336
33397
  function triggerFileIndexBuild() {
@@ -33342,7 +33403,10 @@ function triggerFileIndexBuild() {
33342
33403
  }
33343
33404
 
33344
33405
  // src/files/handle-file-browser-request.ts
33345
- function handleFileBrowserRequest(msg, socket) {
33406
+ function sendFileBrowserMessage(socket, e2ee, payload) {
33407
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
33408
+ }
33409
+ function handleFileBrowserRequest(msg, socket, e2ee) {
33346
33410
  void (async () => {
33347
33411
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
33348
33412
  const op = msg.op === "read" ? "read" : "list";
@@ -33351,7 +33415,7 @@ function handleFileBrowserRequest(msg, socket) {
33351
33415
  if ("error" in result) {
33352
33416
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
33353
33417
  } else {
33354
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, entries: result.entries });
33418
+ sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
33355
33419
  if (reqPath === "." || reqPath === "") {
33356
33420
  triggerFileIndexBuild();
33357
33421
  }
@@ -33373,27 +33437,28 @@ function handleFileBrowserRequest(msg, socket) {
33373
33437
  size: result.size
33374
33438
  };
33375
33439
  if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
33376
- sendWsMessage(socket, payload);
33440
+ sendFileBrowserMessage(socket, e2ee, payload);
33377
33441
  }
33378
33442
  }
33379
33443
  })();
33380
33444
  }
33381
33445
 
33382
33446
  // src/bridge/routing/handlers/file-browser-messages.ts
33383
- function handleFileBrowserRequestMessage(msg, { getWs }) {
33447
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
33384
33448
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
33385
33449
  const socket = getWs();
33386
33450
  if (!socket) return;
33387
33451
  handleFileBrowserRequest(
33388
33452
  msg,
33389
- socket
33453
+ socket,
33454
+ e2ee
33390
33455
  );
33391
33456
  }
33392
- function handleFileBrowserSearchMessage(msg, { getWs }) {
33457
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
33393
33458
  if (typeof msg.id !== "string") return;
33394
33459
  const socket = getWs();
33395
33460
  if (!socket) return;
33396
- handleFileBrowserSearch(msg, socket);
33461
+ handleFileBrowserSearch(msg, socket, e2ee);
33397
33462
  }
33398
33463
 
33399
33464
  // src/bridge/routing/handlers/skill-layout-request.ts
@@ -33463,9 +33528,10 @@ var handleRefreshLocalSkills = (_msg, deps) => {
33463
33528
  };
33464
33529
 
33465
33530
  // src/bridge/routing/handlers/session-git-request.ts
33466
- function sendResult(ws, id, payload) {
33531
+ function sendResult(ws, id, payload, e2ee, encryptedFields = []) {
33467
33532
  if (!ws) return;
33468
- sendWsMessage(ws, { type: "session_git_result", id, ...payload });
33533
+ const message = { type: "session_git_result", id, ...payload };
33534
+ sendWsMessage(ws, e2ee && encryptedFields.length > 0 ? e2ee.encryptFields(message, encryptedFields) : message);
33469
33535
  }
33470
33536
  var handleSessionGitRequestMessage = (msg, deps) => {
33471
33537
  if (typeof msg.id !== "string") return;
@@ -33475,7 +33541,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
33475
33541
  return;
33476
33542
  void (async () => {
33477
33543
  const ws = deps.getWs();
33478
- const reply = (payload) => sendResult(ws, msg.id, payload);
33544
+ const reply = (payload, encryptedFields = []) => sendResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
33479
33545
  try {
33480
33546
  if (action === "status") {
33481
33547
  const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
@@ -33501,7 +33567,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
33501
33567
  reply({
33502
33568
  ok: true,
33503
33569
  repos
33504
- });
33570
+ }, ["repos"]);
33505
33571
  return;
33506
33572
  }
33507
33573
  if (action === "push") {
@@ -33603,8 +33669,15 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
33603
33669
 
33604
33670
  // src/bridge/routing/handlers/dev-server-control.ts
33605
33671
  var handleDevServerControl = (msg, deps) => {
33606
- const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
33607
- const action = msg.action === "start" || msg.action === "stop" ? msg.action : null;
33672
+ let wire = msg;
33673
+ try {
33674
+ wire = deps.e2ee ? deps.e2ee.decryptMessage(msg) : msg;
33675
+ } catch (e) {
33676
+ deps.log(`[E2EE] Could not decrypt dev server command: ${e instanceof Error ? e.message : String(e)}`);
33677
+ return;
33678
+ }
33679
+ const serverId = typeof wire.serverId === "string" ? wire.serverId : "";
33680
+ const action = wire.action === "start" || wire.action === "stop" ? wire.action : null;
33608
33681
  if (!serverId || !action) return;
33609
33682
  deps.devServerManager?.handleControl(serverId, action);
33610
33683
  };
@@ -33730,7 +33803,8 @@ function createMainBridgeWebSocketLifecycle(params) {
33730
33803
  messageDeps,
33731
33804
  tokens,
33732
33805
  persistTokens,
33733
- onAuthInvalid
33806
+ onAuthInvalid,
33807
+ e2ee
33734
33808
  } = params;
33735
33809
  let authRefreshInFlight = false;
33736
33810
  function handleOpen() {
@@ -33743,15 +33817,15 @@ function createMainBridgeWebSocketLifecycle(params) {
33743
33817
  }
33744
33818
  const socket = getWs();
33745
33819
  if (socket) {
33746
- sendWsMessage(socket, { type: "identify", role: "cli" });
33820
+ sendWsMessage(socket, { type: "identify", role: "cli", ...e2ee ? { e: e2ee.handshake } : {} });
33747
33821
  reportGitRepos(getWs, logFn);
33748
33822
  }
33749
33823
  if (justAuthenticated && socket) {
33750
33824
  logFn(
33751
33825
  "Save these for future runs (access token may rotate; refresh token is stored in ~/.buildautomaton/config.json when you use browser auth):"
33752
33826
  );
33753
- logFn(` export BUILDAMATON_AUTH_TOKEN="${tokens.accessToken}"`);
33754
- logFn(` export BUILDAMATON_WORKSPACE_ID="${workspaceId}"`);
33827
+ logFn(` export BUILDAUTOMATON_AUTH_TOKEN="${tokens.accessToken}"`);
33828
+ logFn(` export BUILDAUTOMATON_WORKSPACE_ID="${workspaceId}"`);
33755
33829
  }
33756
33830
  }
33757
33831
  function handleClose(code, reason) {
@@ -33833,6 +33907,100 @@ function createMainBridgeWebSocketLifecycle(params) {
33833
33907
  return { connect };
33834
33908
  }
33835
33909
 
33910
+ // ../e2ee/src/constants.ts
33911
+ var E2EE_NONCE_BYTES = 12;
33912
+
33913
+ // ../e2ee/src/types.ts
33914
+ function isE2eeEnvelope(value) {
33915
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
33916
+ const o = value;
33917
+ return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
33918
+ }
33919
+
33920
+ // ../e2ee/src/encoding.ts
33921
+ function base64UrlEncode(bytes) {
33922
+ let binary = "";
33923
+ for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
33924
+ const b64 = btoa(binary);
33925
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
33926
+ }
33927
+ function base64UrlDecode(value) {
33928
+ const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
33929
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
33930
+ const binary = atob(padded);
33931
+ const out = new Uint8Array(binary.length);
33932
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
33933
+ return out;
33934
+ }
33935
+
33936
+ // src/lib/e2ee/cli-e2ee-runtime.ts
33937
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
33938
+ function nonceFromCounter(prefix, counter) {
33939
+ const nonce = Buffer.alloc(E2EE_NONCE_BYTES);
33940
+ prefix.copy(nonce, 0);
33941
+ nonce.writeBigUInt64BE(counter, 4);
33942
+ return nonce;
33943
+ }
33944
+ function createCliE2eeRuntime(key) {
33945
+ const rawKey = Buffer.from(base64UrlDecode(key.k));
33946
+ const prefix = randomBytes(4);
33947
+ let counter = 0n;
33948
+ function nextNonce() {
33949
+ counter += 1n;
33950
+ return nonceFromCounter(prefix, counter);
33951
+ }
33952
+ function encryptObject(messageWithoutSensitiveFields, plaintext) {
33953
+ const nonce = nextNonce();
33954
+ const cipher = createCipheriv("aes-256-gcm", rawKey, nonce);
33955
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(plaintext), "utf8"), cipher.final()]);
33956
+ const tag = cipher.getAuthTag();
33957
+ return {
33958
+ k: key.id,
33959
+ n: base64UrlEncode(nonce),
33960
+ c: base64UrlEncode(Buffer.concat([ciphertext, tag]))
33961
+ };
33962
+ }
33963
+ return {
33964
+ keyId: key.id,
33965
+ handshake: { k: key.id, a: key.a },
33966
+ encryptFields(message, fields) {
33967
+ const plaintext = {};
33968
+ const stripped = { ...message };
33969
+ let hasPlaintext = false;
33970
+ for (const field of fields) {
33971
+ if (Object.prototype.hasOwnProperty.call(stripped, field) && stripped[field] !== void 0) {
33972
+ plaintext[field] = stripped[field];
33973
+ delete stripped[field];
33974
+ hasPlaintext = true;
33975
+ }
33976
+ }
33977
+ if (!hasPlaintext) return message;
33978
+ stripped.ee = encryptObject(stripped, plaintext);
33979
+ return stripped;
33980
+ },
33981
+ decryptMessage(message) {
33982
+ const envelope = message.ee;
33983
+ if (!isE2eeEnvelope(envelope)) return message;
33984
+ if (envelope.k !== key.id) throw new Error(`E2EE key mismatch: ${envelope.k}`);
33985
+ const sealed = Buffer.from(base64UrlDecode(envelope.c));
33986
+ if (sealed.length < 16) throw new Error("Invalid E2EE payload.");
33987
+ const ciphertext = sealed.subarray(0, sealed.length - 16);
33988
+ const tag = sealed.subarray(sealed.length - 16);
33989
+ const nonce = Buffer.from(base64UrlDecode(envelope.n));
33990
+ const decipher = createDecipheriv("aes-256-gcm", rawKey, nonce);
33991
+ decipher.setAuthTag(tag);
33992
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
33993
+ const parsed = JSON.parse(decrypted);
33994
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
33995
+ throw new Error("E2EE payload did not decode to an object.");
33996
+ }
33997
+ const merged = { ...message, ...parsed };
33998
+ delete merged.ee;
33999
+ return merged;
34000
+ }
34001
+ };
34002
+ }
34003
+
33836
34004
  // src/bridge/connection/create-bridge-connection.ts
33837
34005
  async function createBridgeConnection(options) {
33838
34006
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
@@ -33866,7 +34034,8 @@ async function createBridgeConnection(options) {
33866
34034
  function getWs() {
33867
34035
  return state.currentWs;
33868
34036
  }
33869
- const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
34037
+ const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
34038
+ const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory, e2ee });
33870
34039
  const onBridgeIdentified = createOnBridgeIdentified({
33871
34040
  sessionWorktreeManager,
33872
34041
  devServerManager,
@@ -33885,7 +34054,8 @@ async function createBridgeConnection(options) {
33885
34054
  onBridgeIdentified,
33886
34055
  sendLocalSkillsReport,
33887
34056
  reportAutoDetectedAgents,
33888
- devServerManager
34057
+ devServerManager,
34058
+ e2ee
33889
34059
  };
33890
34060
  const { connect } = createMainBridgeWebSocketLifecycle({
33891
34061
  state,
@@ -33897,7 +34067,8 @@ async function createBridgeConnection(options) {
33897
34067
  messageDeps,
33898
34068
  tokens,
33899
34069
  persistTokens,
33900
- onAuthInvalid
34070
+ onAuthInvalid,
34071
+ e2ee
33901
34072
  });
33902
34073
  connect();
33903
34074
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeWorkspaceDirectory());
@@ -33909,56 +34080,72 @@ async function createBridgeConnection(options) {
33909
34080
  };
33910
34081
  }
33911
34082
 
33912
- // src/run-bridge.ts
33913
- async function runBridge(options) {
33914
- installBridgeProcessResilience();
33915
- const { apiUrl, workspaceId, authToken, refreshToken, bridgeName, justAuthenticated, worktreesRootAbs } = options;
33916
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
33917
- const hasAuth = workspaceId && authToken;
33918
- if (!hasAuth) {
33919
- const handle2 = runPendingAuth({
33920
- apiUrl,
33921
- initialWorkspaceId: workspaceId,
33922
- preferredBridgeName: bridgeName,
33923
- log,
33924
- onAuth: (_auth) => {
33925
- }
33926
- });
33927
- const onSignal2 = (kind) => {
33928
- logImmediate(
33929
- kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
33930
- );
33931
- setImmediate(() => {
33932
- handle2.close();
33933
- process.exit(0);
33934
- });
34083
+ // src/e2e-certificates/key-command.ts
34084
+ import * as readline3 from "node:readline";
34085
+ function installE2eCertificateKeyCommand({
34086
+ log: log2,
34087
+ onOpenCertificate,
34088
+ onInterrupt
34089
+ }) {
34090
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
34091
+ log2("[E2EE] Press c to import the E2EE key in a browser when running in an interactive terminal.");
34092
+ return () => {
33935
34093
  };
33936
- const onSigInt2 = () => onSignal2("interrupt");
33937
- const onSigTerm2 = () => onSignal2("stop");
33938
- process.on("SIGINT", onSigInt2);
33939
- process.on("SIGTERM", onSigTerm2);
33940
- const auth = await handle2.authPromise;
33941
- process.off("SIGINT", onSigInt2);
33942
- process.off("SIGTERM", onSigTerm2);
33943
- handle2.close();
33944
- if (!auth) return;
33945
- writeConfigForApi(apiUrl, {
33946
- workspaceId: auth.workspaceId,
33947
- token: auth.token,
33948
- refreshToken: auth.refreshToken
33949
- });
33950
- await runBridge({
33951
- apiUrl,
33952
- workspaceId: auth.workspaceId,
33953
- authToken: auth.token,
33954
- refreshToken: auth.refreshToken,
33955
- firehoseServerUrl,
33956
- bridgeName,
33957
- justAuthenticated: true,
33958
- worktreesRootAbs
33959
- });
33960
- return;
33961
34094
  }
34095
+ readline3.emitKeypressEvents(process.stdin);
34096
+ process.stdin.setRawMode(true);
34097
+ process.stdin.resume();
34098
+ const onKeypress = (str, key) => {
34099
+ if (key?.ctrl && key.name === "c") {
34100
+ onInterrupt();
34101
+ return;
34102
+ }
34103
+ if (!key?.ctrl && !key?.meta && (key?.name === "c" || str === "c")) {
34104
+ onOpenCertificate();
34105
+ }
34106
+ };
34107
+ process.stdin.on("keypress", onKeypress);
34108
+ log2("[E2EE] Press c to import the active E2EE key into the browser.");
34109
+ return () => {
34110
+ process.stdin.off("keypress", onKeypress);
34111
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
34112
+ process.stdin.setRawMode(false);
34113
+ }
34114
+ };
34115
+ }
34116
+
34117
+ // src/e2e-certificates/open-import-url.ts
34118
+ async function openE2eCertificateImportUrl({
34119
+ apiUrl,
34120
+ workspaceId,
34121
+ certificate,
34122
+ log: log2
34123
+ }) {
34124
+ const appUrl = appUrlForApiUrl(apiUrl);
34125
+ const payload = encodeURIComponent(certificate.pemBundle);
34126
+ const url2 = `${appUrl.replace(/\/$/, "")}/w/${encodeURIComponent(workspaceId)}/settings/e2e-encryption?certificate=${payload}`;
34127
+ log2(`[E2EE] Opening browser to import key "${certificate.name}" (${certificate.id})...`);
34128
+ try {
34129
+ await open_default(url2, { wait: false });
34130
+ } catch {
34131
+ log2("[E2EE] Could not open browser. Open this URL manually:");
34132
+ log2(url2);
34133
+ }
34134
+ }
34135
+
34136
+ // src/run-bridge-connected.ts
34137
+ async function runConnectedBridge(options, restartWithoutAuth) {
34138
+ const {
34139
+ apiUrl,
34140
+ workspaceId,
34141
+ authToken,
34142
+ refreshToken,
34143
+ justAuthenticated,
34144
+ worktreesRootAbs,
34145
+ e2eCertificate
34146
+ } = options;
34147
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
34148
+ let cleanupKeyCommand;
33962
34149
  const handle = await createBridgeConnection({
33963
34150
  apiUrl,
33964
34151
  workspaceId,
@@ -33967,6 +34154,7 @@ async function runBridge(options) {
33967
34154
  firehoseServerUrl,
33968
34155
  justAuthenticated,
33969
34156
  worktreesRootAbs,
34157
+ e2eCertificate,
33970
34158
  log,
33971
34159
  persistTokens: (t) => {
33972
34160
  writeConfigForApi(apiUrl, {
@@ -33976,14 +34164,16 @@ async function runBridge(options) {
33976
34164
  });
33977
34165
  },
33978
34166
  onAuthInvalid: () => {
34167
+ cleanupKeyCommand?.();
33979
34168
  log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
33980
34169
  clearConfigForApi(apiUrl);
33981
34170
  void handle.close().then(() => {
33982
- void runBridge({ apiUrl, firehoseServerUrl, worktreesRootAbs });
34171
+ void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootAbs, e2eCertificate });
33983
34172
  });
33984
34173
  }
33985
34174
  });
33986
34175
  const onSignal = (kind) => {
34176
+ cleanupKeyCommand?.();
33987
34177
  logImmediate(
33988
34178
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
33989
34179
  );
@@ -33997,6 +34187,86 @@ async function runBridge(options) {
33997
34187
  const onSigTerm = () => onSignal("stop");
33998
34188
  process.on("SIGINT", onSigInt);
33999
34189
  process.on("SIGTERM", onSigTerm);
34190
+ if (e2eCertificate) {
34191
+ let openingCertificate = false;
34192
+ cleanupKeyCommand = installE2eCertificateKeyCommand({
34193
+ log,
34194
+ onInterrupt: onSigInt,
34195
+ onOpenCertificate: () => {
34196
+ if (openingCertificate) return;
34197
+ openingCertificate = true;
34198
+ void openE2eCertificateImportUrl({
34199
+ apiUrl,
34200
+ workspaceId,
34201
+ certificate: e2eCertificate,
34202
+ log
34203
+ }).finally(() => {
34204
+ openingCertificate = false;
34205
+ });
34206
+ }
34207
+ });
34208
+ }
34209
+ }
34210
+
34211
+ // src/run-bridge.ts
34212
+ async function runBridge(options) {
34213
+ installBridgeProcessResilience();
34214
+ const {
34215
+ apiUrl,
34216
+ workspaceId,
34217
+ authToken,
34218
+ bridgeName,
34219
+ worktreesRootAbs,
34220
+ e2eCertificate
34221
+ } = options;
34222
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
34223
+ const hasAuth = workspaceId && authToken;
34224
+ if (!hasAuth) {
34225
+ const handle = runPendingAuth({
34226
+ apiUrl,
34227
+ initialWorkspaceId: workspaceId,
34228
+ preferredBridgeName: bridgeName,
34229
+ log,
34230
+ onAuth: (_auth) => {
34231
+ }
34232
+ });
34233
+ const onSignal = (kind) => {
34234
+ logImmediate(
34235
+ kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
34236
+ );
34237
+ setImmediate(() => {
34238
+ handle.close();
34239
+ process.exit(0);
34240
+ });
34241
+ };
34242
+ const onSigInt = () => onSignal("interrupt");
34243
+ const onSigTerm = () => onSignal("stop");
34244
+ process.on("SIGINT", onSigInt);
34245
+ process.on("SIGTERM", onSigTerm);
34246
+ const auth = await handle.authPromise;
34247
+ process.off("SIGINT", onSigInt);
34248
+ process.off("SIGTERM", onSigTerm);
34249
+ handle.close();
34250
+ if (!auth) return;
34251
+ writeConfigForApi(apiUrl, {
34252
+ workspaceId: auth.workspaceId,
34253
+ token: auth.token,
34254
+ refreshToken: auth.refreshToken
34255
+ });
34256
+ await runBridge({
34257
+ apiUrl,
34258
+ workspaceId: auth.workspaceId,
34259
+ authToken: auth.token,
34260
+ refreshToken: auth.refreshToken,
34261
+ firehoseServerUrl,
34262
+ bridgeName,
34263
+ justAuthenticated: true,
34264
+ worktreesRootAbs,
34265
+ e2eCertificate
34266
+ });
34267
+ return;
34268
+ }
34269
+ await runConnectedBridge(options, runBridge);
34000
34270
  }
34001
34271
  export {
34002
34272
  callSkill,