@trydecember/cli 0.4.4 → 0.4.5

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.
Files changed (2) hide show
  1. package/dist/december.js +1935 -276
  2. package/package.json +1 -1
package/dist/december.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@trydecember/cli",
68
- version: "0.4.4",
68
+ version: "0.4.5",
69
69
  description: "a coding agent for terminal and cloud.",
70
70
  homepage: "https://trydecember.com",
71
71
  repository: {
@@ -84297,7 +84297,7 @@ var require_websocket = __commonJS(function(exports, module) {
84297
84297
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
84298
84298
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
84299
84299
 
84300
- class WebSocket extends EventEmitter {
84300
+ class WebSocket2 extends EventEmitter {
84301
84301
  constructor(address, protocols, options) {
84302
84302
  super();
84303
84303
  this._binaryType = BINARY_TYPES[0];
@@ -84310,7 +84310,7 @@ var require_websocket = __commonJS(function(exports, module) {
84310
84310
  this._extensions = {};
84311
84311
  this._paused = false;
84312
84312
  this._protocol = "";
84313
- this._readyState = WebSocket.CONNECTING;
84313
+ this._readyState = WebSocket2.CONNECTING;
84314
84314
  this._receiver = null;
84315
84315
  this._sender = null;
84316
84316
  this._socket = null;
@@ -84412,12 +84412,12 @@ var require_websocket = __commonJS(function(exports, module) {
84412
84412
  socket.on("data", socketOnData);
84413
84413
  socket.on("end", socketOnEnd);
84414
84414
  socket.on("error", socketOnError);
84415
- this._readyState = WebSocket.OPEN;
84415
+ this._readyState = WebSocket2.OPEN;
84416
84416
  this.emit("open");
84417
84417
  }
84418
84418
  emitClose() {
84419
84419
  if (!this._socket) {
84420
- this._readyState = WebSocket.CLOSED;
84420
+ this._readyState = WebSocket2.CLOSED;
84421
84421
  this.emit("close", this._closeCode, this._closeMessage);
84422
84422
  return;
84423
84423
  }
@@ -84425,24 +84425,24 @@ var require_websocket = __commonJS(function(exports, module) {
84425
84425
  this._extensions[PerMessageDeflate.extensionName].cleanup();
84426
84426
  }
84427
84427
  this._receiver.removeAllListeners();
84428
- this._readyState = WebSocket.CLOSED;
84428
+ this._readyState = WebSocket2.CLOSED;
84429
84429
  this.emit("close", this._closeCode, this._closeMessage);
84430
84430
  }
84431
84431
  close(code, data) {
84432
- if (this.readyState === WebSocket.CLOSED)
84432
+ if (this.readyState === WebSocket2.CLOSED)
84433
84433
  return;
84434
- if (this.readyState === WebSocket.CONNECTING) {
84434
+ if (this.readyState === WebSocket2.CONNECTING) {
84435
84435
  const msg = "WebSocket was closed before the connection was established";
84436
84436
  abortHandshake(this, this._req, msg);
84437
84437
  return;
84438
84438
  }
84439
- if (this.readyState === WebSocket.CLOSING) {
84439
+ if (this.readyState === WebSocket2.CLOSING) {
84440
84440
  if (this._closeFrameSent && (this._closeFrameReceived || this._receiver._writableState.errorEmitted)) {
84441
84441
  this._socket.end();
84442
84442
  }
84443
84443
  return;
84444
84444
  }
84445
- this._readyState = WebSocket.CLOSING;
84445
+ this._readyState = WebSocket2.CLOSING;
84446
84446
  this._sender.close(code, data, !this._isServer, (err) => {
84447
84447
  if (err)
84448
84448
  return;
@@ -84454,14 +84454,14 @@ var require_websocket = __commonJS(function(exports, module) {
84454
84454
  setCloseTimer(this);
84455
84455
  }
84456
84456
  pause() {
84457
- if (this.readyState === WebSocket.CONNECTING || this.readyState === WebSocket.CLOSED) {
84457
+ if (this.readyState === WebSocket2.CONNECTING || this.readyState === WebSocket2.CLOSED) {
84458
84458
  return;
84459
84459
  }
84460
84460
  this._paused = true;
84461
84461
  this._socket.pause();
84462
84462
  }
84463
84463
  ping(data, mask, cb) {
84464
- if (this.readyState === WebSocket.CONNECTING) {
84464
+ if (this.readyState === WebSocket2.CONNECTING) {
84465
84465
  throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
84466
84466
  }
84467
84467
  if (typeof data === "function") {
@@ -84473,7 +84473,7 @@ var require_websocket = __commonJS(function(exports, module) {
84473
84473
  }
84474
84474
  if (typeof data === "number")
84475
84475
  data = data.toString();
84476
- if (this.readyState !== WebSocket.OPEN) {
84476
+ if (this.readyState !== WebSocket2.OPEN) {
84477
84477
  sendAfterClose(this, data, cb);
84478
84478
  return;
84479
84479
  }
@@ -84482,7 +84482,7 @@ var require_websocket = __commonJS(function(exports, module) {
84482
84482
  this._sender.ping(data || EMPTY_BUFFER, mask, cb);
84483
84483
  }
84484
84484
  pong(data, mask, cb) {
84485
- if (this.readyState === WebSocket.CONNECTING) {
84485
+ if (this.readyState === WebSocket2.CONNECTING) {
84486
84486
  throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
84487
84487
  }
84488
84488
  if (typeof data === "function") {
@@ -84494,7 +84494,7 @@ var require_websocket = __commonJS(function(exports, module) {
84494
84494
  }
84495
84495
  if (typeof data === "number")
84496
84496
  data = data.toString();
84497
- if (this.readyState !== WebSocket.OPEN) {
84497
+ if (this.readyState !== WebSocket2.OPEN) {
84498
84498
  sendAfterClose(this, data, cb);
84499
84499
  return;
84500
84500
  }
@@ -84503,7 +84503,7 @@ var require_websocket = __commonJS(function(exports, module) {
84503
84503
  this._sender.pong(data || EMPTY_BUFFER, mask, cb);
84504
84504
  }
84505
84505
  resume() {
84506
- if (this.readyState === WebSocket.CONNECTING || this.readyState === WebSocket.CLOSED) {
84506
+ if (this.readyState === WebSocket2.CONNECTING || this.readyState === WebSocket2.CLOSED) {
84507
84507
  return;
84508
84508
  }
84509
84509
  this._paused = false;
@@ -84511,7 +84511,7 @@ var require_websocket = __commonJS(function(exports, module) {
84511
84511
  this._socket.resume();
84512
84512
  }
84513
84513
  send(data, options, cb) {
84514
- if (this.readyState === WebSocket.CONNECTING) {
84514
+ if (this.readyState === WebSocket2.CONNECTING) {
84515
84515
  throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
84516
84516
  }
84517
84517
  if (typeof options === "function") {
@@ -84520,7 +84520,7 @@ var require_websocket = __commonJS(function(exports, module) {
84520
84520
  }
84521
84521
  if (typeof data === "number")
84522
84522
  data = data.toString();
84523
- if (this.readyState !== WebSocket.OPEN) {
84523
+ if (this.readyState !== WebSocket2.OPEN) {
84524
84524
  sendAfterClose(this, data, cb);
84525
84525
  return;
84526
84526
  }
@@ -84537,48 +84537,48 @@ var require_websocket = __commonJS(function(exports, module) {
84537
84537
  this._sender.send(data || EMPTY_BUFFER, opts, cb);
84538
84538
  }
84539
84539
  terminate() {
84540
- if (this.readyState === WebSocket.CLOSED)
84540
+ if (this.readyState === WebSocket2.CLOSED)
84541
84541
  return;
84542
- if (this.readyState === WebSocket.CONNECTING) {
84542
+ if (this.readyState === WebSocket2.CONNECTING) {
84543
84543
  const msg = "WebSocket was closed before the connection was established";
84544
84544
  abortHandshake(this, this._req, msg);
84545
84545
  return;
84546
84546
  }
84547
84547
  if (this._socket) {
84548
- this._readyState = WebSocket.CLOSING;
84548
+ this._readyState = WebSocket2.CLOSING;
84549
84549
  this._socket.destroy();
84550
84550
  }
84551
84551
  }
84552
84552
  }
84553
- Object.defineProperty(WebSocket, "CONNECTING", {
84553
+ Object.defineProperty(WebSocket2, "CONNECTING", {
84554
84554
  enumerable: true,
84555
84555
  value: readyStates.indexOf("CONNECTING")
84556
84556
  });
84557
- Object.defineProperty(WebSocket.prototype, "CONNECTING", {
84557
+ Object.defineProperty(WebSocket2.prototype, "CONNECTING", {
84558
84558
  enumerable: true,
84559
84559
  value: readyStates.indexOf("CONNECTING")
84560
84560
  });
84561
- Object.defineProperty(WebSocket, "OPEN", {
84561
+ Object.defineProperty(WebSocket2, "OPEN", {
84562
84562
  enumerable: true,
84563
84563
  value: readyStates.indexOf("OPEN")
84564
84564
  });
84565
- Object.defineProperty(WebSocket.prototype, "OPEN", {
84565
+ Object.defineProperty(WebSocket2.prototype, "OPEN", {
84566
84566
  enumerable: true,
84567
84567
  value: readyStates.indexOf("OPEN")
84568
84568
  });
84569
- Object.defineProperty(WebSocket, "CLOSING", {
84569
+ Object.defineProperty(WebSocket2, "CLOSING", {
84570
84570
  enumerable: true,
84571
84571
  value: readyStates.indexOf("CLOSING")
84572
84572
  });
84573
- Object.defineProperty(WebSocket.prototype, "CLOSING", {
84573
+ Object.defineProperty(WebSocket2.prototype, "CLOSING", {
84574
84574
  enumerable: true,
84575
84575
  value: readyStates.indexOf("CLOSING")
84576
84576
  });
84577
- Object.defineProperty(WebSocket, "CLOSED", {
84577
+ Object.defineProperty(WebSocket2, "CLOSED", {
84578
84578
  enumerable: true,
84579
84579
  value: readyStates.indexOf("CLOSED")
84580
84580
  });
84581
- Object.defineProperty(WebSocket.prototype, "CLOSED", {
84581
+ Object.defineProperty(WebSocket2.prototype, "CLOSED", {
84582
84582
  enumerable: true,
84583
84583
  value: readyStates.indexOf("CLOSED")
84584
84584
  });
@@ -84591,10 +84591,10 @@ var require_websocket = __commonJS(function(exports, module) {
84591
84591
  "readyState",
84592
84592
  "url"
84593
84593
  ].forEach((property) => {
84594
- Object.defineProperty(WebSocket.prototype, property, { enumerable: true });
84594
+ Object.defineProperty(WebSocket2.prototype, property, { enumerable: true });
84595
84595
  });
84596
84596
  ["open", "error", "close", "message"].forEach((method) => {
84597
- Object.defineProperty(WebSocket.prototype, `on${method}`, {
84597
+ Object.defineProperty(WebSocket2.prototype, `on${method}`, {
84598
84598
  enumerable: true,
84599
84599
  get() {
84600
84600
  for (const listener of this.listeners(method)) {
@@ -84618,9 +84618,9 @@ var require_websocket = __commonJS(function(exports, module) {
84618
84618
  }
84619
84619
  });
84620
84620
  });
84621
- WebSocket.prototype.addEventListener = addEventListener;
84622
- WebSocket.prototype.removeEventListener = removeEventListener;
84623
- module.exports = WebSocket;
84621
+ WebSocket2.prototype.addEventListener = addEventListener;
84622
+ WebSocket2.prototype.removeEventListener = removeEventListener;
84623
+ module.exports = WebSocket2;
84624
84624
  function initAsClient(websocket, address, protocols, options) {
84625
84625
  const opts = {
84626
84626
  allowSynchronousEvents: true,
@@ -84804,7 +84804,7 @@ var require_websocket = __commonJS(function(exports, module) {
84804
84804
  });
84805
84805
  req.on("upgrade", (res, socket, head) => {
84806
84806
  websocket.emit("upgrade", res);
84807
- if (websocket.readyState !== WebSocket.CONNECTING)
84807
+ if (websocket.readyState !== WebSocket2.CONNECTING)
84808
84808
  return;
84809
84809
  req = websocket._req = null;
84810
84810
  const upgrade = res.headers.upgrade;
@@ -84880,7 +84880,7 @@ var require_websocket = __commonJS(function(exports, module) {
84880
84880
  }
84881
84881
  }
84882
84882
  function emitErrorAndClose(websocket, err) {
84883
- websocket._readyState = WebSocket.CLOSING;
84883
+ websocket._readyState = WebSocket2.CLOSING;
84884
84884
  websocket._errorEmitted = true;
84885
84885
  websocket.emit("error", err);
84886
84886
  websocket.emitClose();
@@ -84897,7 +84897,7 @@ var require_websocket = __commonJS(function(exports, module) {
84897
84897
  return tls.connect(options);
84898
84898
  }
84899
84899
  function abortHandshake(websocket, stream, message) {
84900
- websocket._readyState = WebSocket.CLOSING;
84900
+ websocket._readyState = WebSocket2.CLOSING;
84901
84901
  const err = new Error(message);
84902
84902
  Error.captureStackTrace(err, abortHandshake);
84903
84903
  if (stream.setHeader) {
@@ -84977,10 +84977,10 @@ var require_websocket = __commonJS(function(exports, module) {
84977
84977
  }
84978
84978
  function senderOnError(err) {
84979
84979
  const websocket = this[kWebSocket];
84980
- if (websocket.readyState === WebSocket.CLOSED)
84980
+ if (websocket.readyState === WebSocket2.CLOSED)
84981
84981
  return;
84982
- if (websocket.readyState === WebSocket.OPEN) {
84983
- websocket._readyState = WebSocket.CLOSING;
84982
+ if (websocket.readyState === WebSocket2.OPEN) {
84983
+ websocket._readyState = WebSocket2.CLOSING;
84984
84984
  setCloseTimer(websocket);
84985
84985
  }
84986
84986
  this._socket.end();
@@ -84997,7 +84997,7 @@ var require_websocket = __commonJS(function(exports, module) {
84997
84997
  this.removeListener("close", socketOnClose);
84998
84998
  this.removeListener("data", socketOnData);
84999
84999
  this.removeListener("end", socketOnEnd);
85000
- websocket._readyState = WebSocket.CLOSING;
85000
+ websocket._readyState = WebSocket2.CLOSING;
85001
85001
  if (!this._readableState.endEmitted && !websocket._closeFrameReceived && !websocket._receiver._writableState.errorEmitted && this._readableState.length !== 0) {
85002
85002
  const chunk = this.read(this._readableState.length);
85003
85003
  websocket._receiver.write(chunk);
@@ -85019,7 +85019,7 @@ var require_websocket = __commonJS(function(exports, module) {
85019
85019
  }
85020
85020
  function socketOnEnd() {
85021
85021
  const websocket = this[kWebSocket];
85022
- websocket._readyState = WebSocket.CLOSING;
85022
+ websocket._readyState = WebSocket2.CLOSING;
85023
85023
  websocket._receiver.end();
85024
85024
  this.end();
85025
85025
  }
@@ -85028,7 +85028,7 @@ var require_websocket = __commonJS(function(exports, module) {
85028
85028
  this.removeListener("error", socketOnError);
85029
85029
  this.on("error", NOOP);
85030
85030
  if (websocket) {
85031
- websocket._readyState = WebSocket.CLOSING;
85031
+ websocket._readyState = WebSocket2.CLOSING;
85032
85032
  this.destroy();
85033
85033
  }
85034
85034
  }
@@ -85036,7 +85036,7 @@ var require_websocket = __commonJS(function(exports, module) {
85036
85036
 
85037
85037
  // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/stream.js
85038
85038
  var require_stream = __commonJS(function(exports, module) {
85039
- var WebSocket = require_websocket();
85039
+ var WebSocket2 = require_websocket();
85040
85040
  var { Duplex } = __require("stream");
85041
85041
  function emitClose(stream) {
85042
85042
  stream.emit("close");
@@ -85191,7 +85191,7 @@ var require_websocket_server = __commonJS(function(exports, module) {
85191
85191
  var extension = require_extension();
85192
85192
  var PerMessageDeflate = require_permessage_deflate();
85193
85193
  var subprotocol = require_subprotocol();
85194
- var WebSocket = require_websocket();
85194
+ var WebSocket2 = require_websocket();
85195
85195
  var { CLOSE_TIMEOUT, GUID, kWebSocket } = require_constants2();
85196
85196
  var keyRegex = /^[+/0-9A-Za-z]{22}==$/;
85197
85197
  var RUNNING = 0;
@@ -85219,7 +85219,7 @@ var require_websocket_server = __commonJS(function(exports, module) {
85219
85219
  host: null,
85220
85220
  path: null,
85221
85221
  port: null,
85222
- WebSocket,
85222
+ WebSocket: WebSocket2,
85223
85223
  ...options
85224
85224
  };
85225
85225
  if (options.port == null && !options.server && !options.noServer || options.port != null && (options.server || options.noServer) || options.server && options.noServer) {
@@ -115360,20 +115360,28 @@ Examples:`);
115360
115360
  console.log("No API key provided. Operation cancelled.");
115361
115361
  return;
115362
115362
  }
115363
- await Promise.resolve().then(() => init_models5());
115364
- const defaultModel = getDefaultModelForProvider(provider);
115363
+ const isSTTProvider = provider.toLowerCase() === "deepgram";
115365
115364
  const config = await loadConfig();
115366
115365
  config.providers = config.providers || {};
115367
115366
  config.providers[provider] = finalKey;
115368
- config.activeProvider = provider;
115369
- config.activeModel = defaultModel;
115370
- config.authPriority = "byok";
115371
- await saveConfig(config);
115372
- await Promise.resolve().then(() => init_models5());
115373
- fetchLiveProviderModels(provider, finalKey).catch(() => {});
115374
- console.log(`
115367
+ if (!isSTTProvider) {
115368
+ await Promise.resolve().then(() => init_models5());
115369
+ const defaultModel = getDefaultModelForProvider(provider);
115370
+ config.activeProvider = provider;
115371
+ config.activeModel = defaultModel;
115372
+ config.authPriority = "byok";
115373
+ await saveConfig(config);
115374
+ await Promise.resolve().then(() => init_models5());
115375
+ fetchLiveProviderModels(provider, finalKey).catch(() => {});
115376
+ console.log(`
115375
115377
  ${GREEN}Successfully saved API key for ${provider.toUpperCase()} (Model: ${defaultModel})!${RESET}
115376
115378
  `);
115379
+ } else {
115380
+ await saveConfig(config);
115381
+ console.log(`
115382
+ ${GREEN}Successfully saved API key for DEEPGRAM (Live Voice Dictation)!${RESET}
115383
+ `);
115384
+ }
115377
115385
  }
115378
115386
  async function handleLoginCommand(options) {
115379
115387
  const targetProvider = (options?.provider || "december").toLowerCase().trim();
@@ -119419,7 +119427,7 @@ var init_browser = __esm(() => {
119419
119427
  });
119420
119428
  BrowserTool = {
119421
119429
  name: "browser",
119422
- description: "Use a basic HTTP client to fetch the HTML content of a URL. Strips out scripts and styles to return clean text. It also exposes a VNC stream for UI inspection.",
119430
+ description: "Use an interactive browser engine to navigate to a URL, execute client-side JavaScript, extract rendered page content, and capture console or network errors.",
119423
119431
  inputSchema: browserSchema,
119424
119432
  execute: async ({ url }, context) => {
119425
119433
  try {
@@ -119431,6 +119439,20 @@ var init_browser = __esm(() => {
119431
119439
  return `Failed to fetch URL: ${result.error}`;
119432
119440
  }
119433
119441
  let output = truncateOutput(result.text, 25000, 100).text;
119442
+ if (result.consoleErrors && result.consoleErrors.length > 0) {
119443
+ output += `
119444
+
119445
+ [CONSOLE ERRORS]
119446
+ ` + result.consoleErrors.map((err) => `- ${err}`).join(`
119447
+ `);
119448
+ }
119449
+ if (result.networkErrors && result.networkErrors.length > 0) {
119450
+ output += `
119451
+
119452
+ [FAILED NETWORK REQUESTS]
119453
+ ` + result.networkErrors.map((err) => `- ${err}`).join(`
119454
+ `);
119455
+ }
119434
119456
  if (result.vncUrl) {
119435
119457
  output += `
119436
119458
 
@@ -119444,6 +119466,89 @@ var init_browser = __esm(() => {
119444
119466
  };
119445
119467
  });
119446
119468
 
119469
+ // ../../packages/tools/src/browser_action.ts
119470
+ var browserActionSchema, BrowserActionTool;
119471
+ var init_browser_action = __esm(() => {
119472
+ init_src();
119473
+ init_esm2();
119474
+ browserActionSchema = Type2.Object({
119475
+ action: Type2.Union([
119476
+ Type2.Literal("navigate"),
119477
+ Type2.Literal("click"),
119478
+ Type2.Literal("type"),
119479
+ Type2.Literal("screenshot"),
119480
+ Type2.Literal("get_console_errors")
119481
+ ], { description: "The interactive browser action to execute" }),
119482
+ url: Type2.Optional(Type2.String({ description: 'URL to navigate to (required for action: "navigate")' })),
119483
+ selector: Type2.Optional(Type2.String({ description: 'CSS selector or text target (for action: "click" or "type")' })),
119484
+ text: Type2.Optional(Type2.String({ description: 'Text to input (required for action: "type")' })),
119485
+ fullPage: Type2.Optional(Type2.Boolean({ description: 'Capture full scrollable page (for action: "screenshot")' }))
119486
+ });
119487
+ BrowserActionTool = {
119488
+ name: "browser_action",
119489
+ description: "Interact with a headless browser session. Supports navigating to URLs, clicking elements, typing into inputs, capturing screenshots, and inspecting console or network errors on localhost web apps.",
119490
+ inputSchema: browserActionSchema,
119491
+ execute: async (input, context) => {
119492
+ try {
119493
+ if (!context.operations.browser) {
119494
+ return `Failed to execute browser action: Browser operations are not supported in this environment.`;
119495
+ }
119496
+ if (!context.operations.browser.action) {
119497
+ if (input.action === "navigate" && input.url) {
119498
+ const navResult = await context.operations.browser.navigate(input.url);
119499
+ if (navResult.error) {
119500
+ return `Failed to navigate: ${navResult.error}`;
119501
+ }
119502
+ let out = truncateOutput(navResult.text, 25000, 100).text;
119503
+ if (navResult.consoleErrors && navResult.consoleErrors.length > 0) {
119504
+ out += `
119505
+
119506
+ [CONSOLE ERRORS]
119507
+ ` + navResult.consoleErrors.map((err) => `- ${err}`).join(`
119508
+ `);
119509
+ }
119510
+ if (navResult.networkErrors && navResult.networkErrors.length > 0) {
119511
+ out += `
119512
+
119513
+ [FAILED NETWORK REQUESTS]
119514
+ ` + navResult.networkErrors.map((err) => `- ${err}`).join(`
119515
+ `);
119516
+ }
119517
+ return out;
119518
+ }
119519
+ return `Browser interactive actions ('click', 'type', 'screenshot') require Chrome or Chromium to be installed.`;
119520
+ }
119521
+ const result = await context.operations.browser.action(input);
119522
+ if (result.error) {
119523
+ return `Failed to execute action "${input.action}": ${result.error}`;
119524
+ }
119525
+ let response = result.output || result.text || `Browser action "${input.action}" completed successfully.`;
119526
+ if (result.screenshotPath) {
119527
+ response += `
119528
+ [Screenshot saved: ${result.screenshotPath}]`;
119529
+ }
119530
+ if (result.consoleErrors && result.consoleErrors.length > 0) {
119531
+ response += `
119532
+
119533
+ [CONSOLE ERRORS]
119534
+ ` + result.consoleErrors.map((err) => `- ${err}`).join(`
119535
+ `);
119536
+ }
119537
+ if (result.networkErrors && result.networkErrors.length > 0) {
119538
+ response += `
119539
+
119540
+ [FAILED NETWORK REQUESTS]
119541
+ ` + result.networkErrors.map((err) => `- ${err}`).join(`
119542
+ `);
119543
+ }
119544
+ return response;
119545
+ } catch (error) {
119546
+ return `Failed to execute browser action: ${error.message}`;
119547
+ }
119548
+ }
119549
+ };
119550
+ });
119551
+
119447
119552
  // ../../packages/tools/src/code_intelligence.ts
119448
119553
  function formatDiagnostics(diags, path) {
119449
119554
  const target = path ? ` for ${path}` : "";
@@ -160892,7 +160997,7 @@ var require_websocket2 = __commonJS(function(exports, module) {
160892
160997
  return null;
160893
160998
  }
160894
160999
 
160895
- class WebSocket extends EventTarget {
161000
+ class WebSocket2 extends EventTarget {
160896
161001
  #events = {
160897
161002
  open: null,
160898
161003
  error: null,
@@ -160968,11 +161073,11 @@ var require_websocket2 = __commonJS(function(exports, module) {
160968
161073
  this.#url = new URL(urlRecord.href);
160969
161074
  const client = environmentSettingsObject.settingsObject;
160970
161075
  this.#handler.controller = establishWebSocketConnection(urlRecord, protocols, client, this.#handler, options);
160971
- this.#handler.readyState = WebSocket.CONNECTING;
161076
+ this.#handler.readyState = WebSocket2.CONNECTING;
160972
161077
  this.#binaryType = "blob";
160973
161078
  }
160974
161079
  close(code = undefined, reason = undefined) {
160975
- webidl.brandCheck(this, WebSocket);
161080
+ webidl.brandCheck(this, WebSocket2);
160976
161081
  const prefix = "WebSocket.close";
160977
161082
  if (code !== undefined) {
160978
161083
  code = webidl.converters["unsigned short"](code, prefix, "code", webidl.attributes.Clamp);
@@ -160985,7 +161090,7 @@ var require_websocket2 = __commonJS(function(exports, module) {
160985
161090
  closeWebSocketConnection(this.#handler, code, reason, true);
160986
161091
  }
160987
161092
  send(data) {
160988
- webidl.brandCheck(this, WebSocket);
161093
+ webidl.brandCheck(this, WebSocket2);
160989
161094
  const prefix = "WebSocket.send";
160990
161095
  webidl.argumentLengthCheck(arguments, 1, prefix);
160991
161096
  data = webidl.converters.WebSocketSendData(data, prefix, "data");
@@ -161019,31 +161124,31 @@ var require_websocket2 = __commonJS(function(exports, module) {
161019
161124
  }
161020
161125
  }
161021
161126
  get readyState() {
161022
- webidl.brandCheck(this, WebSocket);
161127
+ webidl.brandCheck(this, WebSocket2);
161023
161128
  return this.#handler.readyState;
161024
161129
  }
161025
161130
  get bufferedAmount() {
161026
- webidl.brandCheck(this, WebSocket);
161131
+ webidl.brandCheck(this, WebSocket2);
161027
161132
  return this.#bufferedAmount;
161028
161133
  }
161029
161134
  get url() {
161030
- webidl.brandCheck(this, WebSocket);
161135
+ webidl.brandCheck(this, WebSocket2);
161031
161136
  return URLSerializer(this.#url);
161032
161137
  }
161033
161138
  get extensions() {
161034
- webidl.brandCheck(this, WebSocket);
161139
+ webidl.brandCheck(this, WebSocket2);
161035
161140
  return this.#extensions;
161036
161141
  }
161037
161142
  get protocol() {
161038
- webidl.brandCheck(this, WebSocket);
161143
+ webidl.brandCheck(this, WebSocket2);
161039
161144
  return this.#protocol;
161040
161145
  }
161041
161146
  get onopen() {
161042
- webidl.brandCheck(this, WebSocket);
161147
+ webidl.brandCheck(this, WebSocket2);
161043
161148
  return this.#events.open;
161044
161149
  }
161045
161150
  set onopen(fn) {
161046
- webidl.brandCheck(this, WebSocket);
161151
+ webidl.brandCheck(this, WebSocket2);
161047
161152
  if (this.#events.open) {
161048
161153
  this.removeEventListener("open", this.#events.open);
161049
161154
  }
@@ -161056,11 +161161,11 @@ var require_websocket2 = __commonJS(function(exports, module) {
161056
161161
  }
161057
161162
  }
161058
161163
  get onerror() {
161059
- webidl.brandCheck(this, WebSocket);
161164
+ webidl.brandCheck(this, WebSocket2);
161060
161165
  return this.#events.error;
161061
161166
  }
161062
161167
  set onerror(fn) {
161063
- webidl.brandCheck(this, WebSocket);
161168
+ webidl.brandCheck(this, WebSocket2);
161064
161169
  if (this.#events.error) {
161065
161170
  this.removeEventListener("error", this.#events.error);
161066
161171
  }
@@ -161073,11 +161178,11 @@ var require_websocket2 = __commonJS(function(exports, module) {
161073
161178
  }
161074
161179
  }
161075
161180
  get onclose() {
161076
- webidl.brandCheck(this, WebSocket);
161181
+ webidl.brandCheck(this, WebSocket2);
161077
161182
  return this.#events.close;
161078
161183
  }
161079
161184
  set onclose(fn) {
161080
- webidl.brandCheck(this, WebSocket);
161185
+ webidl.brandCheck(this, WebSocket2);
161081
161186
  if (this.#events.close) {
161082
161187
  this.removeEventListener("close", this.#events.close);
161083
161188
  }
@@ -161090,11 +161195,11 @@ var require_websocket2 = __commonJS(function(exports, module) {
161090
161195
  }
161091
161196
  }
161092
161197
  get onmessage() {
161093
- webidl.brandCheck(this, WebSocket);
161198
+ webidl.brandCheck(this, WebSocket2);
161094
161199
  return this.#events.message;
161095
161200
  }
161096
161201
  set onmessage(fn) {
161097
- webidl.brandCheck(this, WebSocket);
161202
+ webidl.brandCheck(this, WebSocket2);
161098
161203
  if (this.#events.message) {
161099
161204
  this.removeEventListener("message", this.#events.message);
161100
161205
  }
@@ -161107,11 +161212,11 @@ var require_websocket2 = __commonJS(function(exports, module) {
161107
161212
  }
161108
161213
  }
161109
161214
  get binaryType() {
161110
- webidl.brandCheck(this, WebSocket);
161215
+ webidl.brandCheck(this, WebSocket2);
161111
161216
  return this.#binaryType;
161112
161217
  }
161113
161218
  set binaryType(type) {
161114
- webidl.brandCheck(this, WebSocket);
161219
+ webidl.brandCheck(this, WebSocket2);
161115
161220
  if (type !== "blob" && type !== "arraybuffer") {
161116
161221
  this.#binaryType = "blob";
161117
161222
  } else {
@@ -161227,13 +161332,13 @@ var require_websocket2 = __commonJS(function(exports, module) {
161227
161332
  }
161228
161333
  }
161229
161334
  }
161230
- var { ping } = WebSocket;
161231
- Reflect.deleteProperty(WebSocket, "ping");
161232
- WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING;
161233
- WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN;
161234
- WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING;
161235
- WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED;
161236
- Object.defineProperties(WebSocket.prototype, {
161335
+ var { ping } = WebSocket2;
161336
+ Reflect.deleteProperty(WebSocket2, "ping");
161337
+ WebSocket2.CONNECTING = WebSocket2.prototype.CONNECTING = states.CONNECTING;
161338
+ WebSocket2.OPEN = WebSocket2.prototype.OPEN = states.OPEN;
161339
+ WebSocket2.CLOSING = WebSocket2.prototype.CLOSING = states.CLOSING;
161340
+ WebSocket2.CLOSED = WebSocket2.prototype.CLOSED = states.CLOSED;
161341
+ Object.defineProperties(WebSocket2.prototype, {
161237
161342
  CONNECTING: staticPropertyDescriptors,
161238
161343
  OPEN: staticPropertyDescriptors,
161239
161344
  CLOSING: staticPropertyDescriptors,
@@ -161257,7 +161362,7 @@ var require_websocket2 = __commonJS(function(exports, module) {
161257
161362
  configurable: true
161258
161363
  }
161259
161364
  });
161260
- Object.defineProperties(WebSocket, {
161365
+ Object.defineProperties(WebSocket2, {
161261
161366
  CONNECTING: staticPropertyDescriptors,
161262
161367
  OPEN: staticPropertyDescriptors,
161263
161368
  CLOSING: staticPropertyDescriptors,
@@ -161304,7 +161409,7 @@ var require_websocket2 = __commonJS(function(exports, module) {
161304
161409
  return webidl.converters.USVString(V);
161305
161410
  };
161306
161411
  module.exports = {
161307
- WebSocket,
161412
+ WebSocket: WebSocket2,
161308
161413
  ping
161309
161414
  };
161310
161415
  });
@@ -162189,7 +162294,7 @@ function makeDispatcher(fn) {
162189
162294
  }, handler);
162190
162295
  };
162191
162296
  }
162192
- var __filename = "/home/runner/work/december/december/node_modules/.bun/undici@7.28.0/node_modules/undici/index.js", Client, Dispatcher, Pool, BalancedPool, RoundRobinPool, Agent, ProxyAgent, Socks5ProxyAgent, EnvHttpProxyAgent, RetryAgent, H2CClient, errors2, util3, InvalidArgumentError, api, buildConnector, MockClient, MockCallHistory, MockCallHistoryLog, MockAgent, MockPool, SnapshotAgent, mockErrors, RetryHandler, getGlobalDispatcher, setGlobalDispatcher, DecoratorHandler, RedirectHandler, $interceptors, $cacheStores, SqliteCacheStore, fetchImpl, $Headers, $Response, $Request, $FormData, setGlobalOrigin, getGlobalOrigin, CacheStorage, kConstruct, $caches, deleteCookie, getCookies, getSetCookies, setCookie, parseCookie, parseMIMEType, serializeAMimeType, CloseEvent, ErrorEvent, MessageEvent, WebSocket2, ping, $WebSocketStream, $WebSocketError, $request, $stream, $pipeline, $connect, $upgrade, EventSource;
162297
+ var __filename = "/home/runner/work/december/december/node_modules/.bun/undici@7.28.0/node_modules/undici/index.js", Client, Dispatcher, Pool, BalancedPool, RoundRobinPool, Agent, ProxyAgent, Socks5ProxyAgent, EnvHttpProxyAgent, RetryAgent, H2CClient, errors2, util3, InvalidArgumentError, api, buildConnector, MockClient, MockCallHistory, MockCallHistoryLog, MockAgent, MockPool, SnapshotAgent, mockErrors, RetryHandler, getGlobalDispatcher, setGlobalDispatcher, DecoratorHandler, RedirectHandler, $interceptors, $cacheStores, SqliteCacheStore, fetchImpl, $Headers, $Response, $Request, $FormData, setGlobalOrigin, getGlobalOrigin, CacheStorage, kConstruct, $caches, deleteCookie, getCookies, getSetCookies, setCookie, parseCookie, parseMIMEType, serializeAMimeType, CloseEvent, ErrorEvent, MessageEvent, WebSocket3, ping, $WebSocketStream, $WebSocketError, $request, $stream, $pipeline, $connect, $upgrade, EventSource;
162193
162298
  var init_undici = __esm(() => {
162194
162299
  Client = require_client();
162195
162300
  Dispatcher = require_dispatcher();
@@ -162245,7 +162350,7 @@ var init_undici = __esm(() => {
162245
162350
  ({ deleteCookie, getCookies, getSetCookies, setCookie, parseCookie } = require_cookies());
162246
162351
  ({ parseMIMEType, serializeAMimeType } = require_data_url());
162247
162352
  ({ CloseEvent, ErrorEvent, MessageEvent } = require_events());
162248
- ({ WebSocket: WebSocket2, ping } = require_websocket2());
162353
+ ({ WebSocket: WebSocket3, ping } = require_websocket2());
162249
162354
  $WebSocketStream = require_websocketstream().WebSocketStream;
162250
162355
  $WebSocketError = require_websocketerror().WebSocketError;
162251
162356
  $request = makeDispatcher(api.request);
@@ -162835,6 +162940,7 @@ var init_src4 = __esm(() => {
162835
162940
  init_ask_question();
162836
162941
  init_bash();
162837
162942
  init_browser();
162943
+ init_browser_action();
162838
162944
  init_code_intelligence();
162839
162945
  init_deferred_registry();
162840
162946
  init_edit();
@@ -162862,6 +162968,7 @@ var init_src4 = __esm(() => {
162862
162968
  init_manage_task();
162863
162969
  init_manage_memory();
162864
162970
  init_browser();
162971
+ init_browser_action();
162865
162972
  init_web_search();
162866
162973
  init_diff_preview();
162867
162974
  init_deferred_registry();
@@ -162881,11 +162988,26 @@ var init_src4 = __esm(() => {
162881
162988
  CodeIntelligenceTool,
162882
162989
  ManageMemoryTool
162883
162990
  ];
162884
- DEFERRED_TOOLS = [ManageTaskTool, BrowserTool, WebSearchTool];
162991
+ DEFERRED_TOOLS = [ManageTaskTool, BrowserTool, BrowserActionTool, WebSearchTool];
162885
162992
  defaultDeferredRegistry.register(BrowserTool, {
162886
162993
  category: "web",
162887
162994
  keywords: ["browser", "render", "html", "scrape", "page", "url", "browse", "webpage"]
162888
162995
  });
162996
+ defaultDeferredRegistry.register(BrowserActionTool, {
162997
+ category: "web",
162998
+ keywords: [
162999
+ "browser_action",
163000
+ "click",
163001
+ "type",
163002
+ "screenshot",
163003
+ "dom",
163004
+ "test",
163005
+ "input",
163006
+ "form",
163007
+ "ui",
163008
+ "localhost"
163009
+ ]
163010
+ });
162889
163011
  defaultDeferredRegistry.register(WebSearchTool, {
162890
163012
  category: "web",
162891
163013
  keywords: ["search", "google", "duckduckgo", "tavily", "internet", "docs", "websearch"]
@@ -169706,13 +169828,210 @@ var init_headless_runner = __esm(() => {
169706
169828
  };
169707
169829
  });
169708
169830
 
169831
+ // src/utils/workspace-trust.ts
169832
+ import { execSync as execSync2 } from "node:child_process";
169833
+ import fs25 from "node:fs";
169834
+ import os13 from "node:os";
169835
+ import path26 from "node:path";
169836
+ import readline3 from "node:readline";
169837
+ function resolveWorkspaceRoot(targetDir = process.cwd()) {
169838
+ const resolved = path26.resolve(targetDir);
169839
+ try {
169840
+ const stdout = execSync2("git rev-parse --show-toplevel", {
169841
+ cwd: resolved,
169842
+ stdio: ["ignore", "pipe", "ignore"],
169843
+ encoding: "utf8"
169844
+ }).trim();
169845
+ if (stdout && fs25.existsSync(stdout)) {
169846
+ return path26.resolve(stdout);
169847
+ }
169848
+ } catch {}
169849
+ let current = resolved;
169850
+ const homeDir = path26.resolve(os13.homedir());
169851
+ while (true) {
169852
+ if (fs25.existsSync(path26.join(current, ".git"))) {
169853
+ return current;
169854
+ }
169855
+ const parent = path26.dirname(current);
169856
+ if (parent === current || current === homeDir) {
169857
+ break;
169858
+ }
169859
+ current = parent;
169860
+ }
169861
+ return resolved;
169862
+ }
169863
+ function isWorkspaceTrusted(workspaceRoot, config) {
169864
+ const canonicalRoot = path26.resolve(workspaceRoot);
169865
+ const homeDir = path26.resolve(os13.homedir());
169866
+ const trustedList = (config.trustedWorkspaces || []).map((p) => path26.resolve(p));
169867
+ for (const trustedPath of trustedList) {
169868
+ if (canonicalRoot === trustedPath) {
169869
+ return true;
169870
+ }
169871
+ if (trustedPath !== "/" && trustedPath !== homeDir) {
169872
+ const rel = path26.relative(trustedPath, canonicalRoot);
169873
+ if (!rel.startsWith("..") && !path26.isAbsolute(rel)) {
169874
+ return true;
169875
+ }
169876
+ }
169877
+ }
169878
+ return false;
169879
+ }
169880
+ async function addTrustedWorkspace(workspaceRoot) {
169881
+ const config = await loadConfig();
169882
+ const canonical = path26.resolve(workspaceRoot);
169883
+ const existing = (config.trustedWorkspaces || []).map((p) => path26.resolve(p));
169884
+ if (!existing.includes(canonical)) {
169885
+ config.trustedWorkspaces = [...existing, canonical];
169886
+ await saveConfig(config);
169887
+ }
169888
+ }
169889
+ async function promptWorkspaceTrust(workspaceRoot) {
169890
+ if (!process.stdin.isTTY) {
169891
+ return false;
169892
+ }
169893
+ const homeDir = path26.resolve(os13.homedir());
169894
+ const isHomeOrRoot = workspaceRoot === homeDir || workspaceRoot === "/";
169895
+ const BRAND = "\x1B[38;2;137;180;248m";
169896
+ const TEXT = "\x1B[38;2;244;244;245m";
169897
+ const MUTED = "\x1B[38;2;170;170;170m";
169898
+ const GRAY = "\x1B[38;2;119;119;119m";
169899
+ const YELLOW = "\x1B[38;2;253;214;99m";
169900
+ const BOLD = "\x1B[1m";
169901
+ const RESET = "\x1B[0m";
169902
+ const options = isHomeOrRoot ? [
169903
+ { label: "No, exit", value: false },
169904
+ { label: "Yes, trust folder anyway", value: true }
169905
+ ] : [
169906
+ { label: "Yes, trust folder", value: true },
169907
+ { label: "No, exit", value: false }
169908
+ ];
169909
+ return new Promise((resolve) => {
169910
+ let selectedIndex = 0;
169911
+ let renderedLineCount = 0;
169912
+ const wasRaw = process.stdin.isRaw;
169913
+ if (typeof process.stdin.setRawMode === "function") {
169914
+ process.stdin.setRawMode(true);
169915
+ }
169916
+ process.stdin.resume();
169917
+ readline3.emitKeypressEvents(process.stdin);
169918
+ const cleanup = () => {
169919
+ process.stdin.removeListener("keypress", onKeypress);
169920
+ if (typeof process.stdin.setRawMode === "function" && !wasRaw) {
169921
+ process.stdin.setRawMode(false);
169922
+ }
169923
+ process.stdin.pause();
169924
+ process.stdout.write("\x1B[?25h");
169925
+ };
169926
+ const clearRenderedLines = () => {
169927
+ if (renderedLineCount > 0) {
169928
+ readline3.cursorTo(process.stdout, 0);
169929
+ readline3.moveCursor(process.stdout, 0, -renderedLineCount);
169930
+ readline3.clearScreenDown(process.stdout);
169931
+ }
169932
+ };
169933
+ const render = () => {
169934
+ clearRenderedLines();
169935
+ const lines = [];
169936
+ lines.push("");
169937
+ lines.push(` ${BOLD}${TEXT}Trust workspace?${RESET}`);
169938
+ lines.push("");
169939
+ lines.push(` ${GRAY}Directory${RESET} ${BOLD}${BRAND}${workspaceRoot}${RESET}`);
169940
+ if (isHomeOrRoot) {
169941
+ lines.push(` ${YELLOW}Warning${RESET} ${YELLOW}Home or system root directory. Gives access to all personal files.${RESET}`);
169942
+ } else {
169943
+ lines.push(` ${GRAY}Permission${RESET} ${TEXT}Read, edit, and run commands${RESET}`);
169944
+ }
169945
+ lines.push("");
169946
+ for (let i = 0;i < options.length; i++) {
169947
+ const opt = options[i];
169948
+ if (i === selectedIndex) {
169949
+ lines.push(` ${BRAND}❭${RESET} ${BOLD}${TEXT}${opt.label}${RESET}`);
169950
+ } else {
169951
+ lines.push(` ${MUTED}${opt.label}${RESET}`);
169952
+ }
169953
+ }
169954
+ lines.push("");
169955
+ lines.push(` ${BRAND}Enter${RESET} ${MUTED}confirm${RESET} ${MUTED}·${RESET} ${BRAND}Esc${RESET} ${MUTED}cancel${RESET} ${MUTED}·${RESET} ${BRAND}↑↓${RESET} ${MUTED}navigate${RESET}`);
169956
+ lines.push("");
169957
+ renderedLineCount = lines.length;
169958
+ process.stdout.write(lines.join(`
169959
+ `));
169960
+ };
169961
+ const onKeypress = (str, key) => {
169962
+ if (!key && !str)
169963
+ return;
169964
+ const lowerStr = (str || "").toLowerCase();
169965
+ const keyName = key?.name;
169966
+ if (lowerStr === "y" || keyName === "y") {
169967
+ cleanup();
169968
+ clearRenderedLines();
169969
+ resolve(true);
169970
+ return;
169971
+ }
169972
+ if (lowerStr === "n" || keyName === "n") {
169973
+ cleanup();
169974
+ clearRenderedLines();
169975
+ resolve(false);
169976
+ return;
169977
+ }
169978
+ if (keyName === "up" || keyName === "k") {
169979
+ selectedIndex = Math.max(0, selectedIndex - 1);
169980
+ render();
169981
+ } else if (keyName === "down" || keyName === "j" || keyName === "tab") {
169982
+ selectedIndex = Math.min(options.length - 1, selectedIndex + 1);
169983
+ render();
169984
+ } else if (keyName === "return" || keyName === "enter") {
169985
+ cleanup();
169986
+ clearRenderedLines();
169987
+ resolve(options[selectedIndex].value);
169988
+ } else if (keyName === "escape" || key?.ctrl && keyName === "c") {
169989
+ cleanup();
169990
+ clearRenderedLines();
169991
+ resolve(false);
169992
+ }
169993
+ };
169994
+ process.stdout.write("\x1B[?25l");
169995
+ process.stdin.on("keypress", onKeypress);
169996
+ render();
169997
+ });
169998
+ }
169999
+ async function ensureWorkspaceTrust(options = {}) {
170000
+ const workspaceRoot = resolveWorkspaceRoot(options.targetDir || process.cwd());
170001
+ const config = await loadConfig();
170002
+ if (isWorkspaceTrusted(workspaceRoot, config)) {
170003
+ return;
170004
+ }
170005
+ const isNonInteractive = Boolean(options.isHeadless) || !process.stdin.isTTY || Boolean(process.env.NON_INTERACTIVE) || Boolean(process.env.CI);
170006
+ if (isNonInteractive) {
170007
+ const RED = "\x1B[38;2;252;165;165m";
170008
+ const RESET = "\x1B[0m";
170009
+ console.error(`
170010
+ ${RED}Error:${RESET} Workspace "${workspaceRoot}" is not trusted.`);
170011
+ console.error(`Please run "december" interactively first to review and trust this workspace.
170012
+ `);
170013
+ process.exit(1);
170014
+ }
170015
+ const trusted = await promptWorkspaceTrust(workspaceRoot);
170016
+ if (!trusted) {
170017
+ console.log(`
170018
+ Workspace untrusted. Exiting.
170019
+ `);
170020
+ process.exit(0);
170021
+ }
170022
+ await addTrustedWorkspace(workspaceRoot);
170023
+ }
170024
+ var init_workspace_trust = __esm(() => {
170025
+ init_config();
170026
+ });
170027
+
169709
170028
  // src/file-session-repository.ts
169710
170029
  var exports_file_session_repository = {};
169711
170030
  __export(exports_file_session_repository, {
169712
170031
  FileSessionRepository: () => FileSessionRepository
169713
170032
  });
169714
- import fs25 from "node:fs/promises";
169715
- import path26 from "node:path";
170033
+ import fs26 from "node:fs/promises";
170034
+ import path27 from "node:path";
169716
170035
 
169717
170036
  class FileSessionRepository {
169718
170037
  sessionDir;
@@ -169722,22 +170041,22 @@ class FileSessionRepository {
169722
170041
  if (sessionDir) {
169723
170042
  this.sessionDir = sessionDir;
169724
170043
  } else {
169725
- this.sessionDir = path26.join(getConfigDir(), "sessions");
169726
- const legacyDir = path26.join(getLegacyConfigDir(), "sessions");
170044
+ this.sessionDir = path27.join(getConfigDir(), "sessions");
170045
+ const legacyDir = path27.join(getLegacyConfigDir(), "sessions");
169727
170046
  if (legacyDir !== this.sessionDir) {
169728
170047
  this.legacySessionDir = legacyDir;
169729
170048
  }
169730
170049
  }
169731
170050
  }
169732
170051
  async saveContext(sessionId, messages) {
169733
- await fs25.mkdir(this.sessionDir, { recursive: true });
169734
- const historyPath = path26.join(this.sessionDir, `${sessionId}.jsonl`);
170052
+ await fs26.mkdir(this.sessionDir, { recursive: true });
170053
+ const historyPath = path27.join(this.sessionDir, `${sessionId}.jsonl`);
169735
170054
  let existing = this.sessionCache[sessionId];
169736
170055
  if (!existing) {
169737
170056
  existing = {};
169738
170057
  this.sessionCache[sessionId] = existing;
169739
170058
  try {
169740
- const data = await fs25.readFile(historyPath, "utf-8");
170059
+ const data = await fs26.readFile(historyPath, "utf-8");
169741
170060
  const lines = data.split(`
169742
170061
  `).filter((l) => l.trim().length > 0);
169743
170062
  for (const l of lines) {
@@ -169761,19 +170080,19 @@ class FileSessionRepository {
169761
170080
  }
169762
170081
  }
169763
170082
  if (appendContent) {
169764
- await fs25.appendFile(historyPath, appendContent, "utf-8");
170083
+ await fs26.appendFile(historyPath, appendContent, "utf-8");
169765
170084
  }
169766
170085
  }
169767
170086
  async loadContext(sessionId) {
169768
- let historyPath = path26.join(this.sessionDir, `${sessionId}.jsonl`);
170087
+ let historyPath = path27.join(this.sessionDir, `${sessionId}.jsonl`);
169769
170088
  try {
169770
170089
  let data;
169771
170090
  try {
169772
- data = await fs25.readFile(historyPath, "utf-8");
170091
+ data = await fs26.readFile(historyPath, "utf-8");
169773
170092
  } catch (err) {
169774
170093
  if (this.legacySessionDir) {
169775
- const legacyPath = path26.join(this.legacySessionDir, `${sessionId}.jsonl`);
169776
- data = await fs25.readFile(legacyPath, "utf-8");
170094
+ const legacyPath = path27.join(this.legacySessionDir, `${sessionId}.jsonl`);
170095
+ data = await fs26.readFile(legacyPath, "utf-8");
169777
170096
  historyPath = legacyPath;
169778
170097
  } else {
169779
170098
  throw err;
@@ -169814,7 +170133,7 @@ class FileSessionRepository {
169814
170133
  if (msgs.length < lines.length) {
169815
170134
  const compacted = msgs.map((m) => JSON.stringify(m)).join(`
169816
170135
  `);
169817
- await fs25.writeFile(historyPath, compacted, "utf-8").catch(() => {});
170136
+ await fs26.writeFile(historyPath, compacted, "utf-8").catch(() => {});
169818
170137
  }
169819
170138
  const cacheObj = {};
169820
170139
  for (const msg of msgs) {
@@ -169828,12 +170147,12 @@ class FileSessionRepository {
169828
170147
  }
169829
170148
  async listSessions() {
169830
170149
  try {
169831
- await fs25.mkdir(this.sessionDir, { recursive: true });
170150
+ await fs26.mkdir(this.sessionDir, { recursive: true });
169832
170151
  const sessions = [];
169833
170152
  const seenIds = new Set;
169834
170153
  const processDirectory = async (dirPath) => {
169835
170154
  try {
169836
- const dirFiles = await fs25.readdir(dirPath);
170155
+ const dirFiles = await fs26.readdir(dirPath);
169837
170156
  for (const file of dirFiles) {
169838
170157
  if (!file.endsWith(".jsonl"))
169839
170158
  continue;
@@ -169841,10 +170160,10 @@ class FileSessionRepository {
169841
170160
  if (seenIds.has(id))
169842
170161
  continue;
169843
170162
  seenIds.add(id);
169844
- const filePath = path26.join(dirPath, file);
170163
+ const filePath = path27.join(dirPath, file);
169845
170164
  try {
169846
- const stat = await fs25.stat(filePath);
169847
- const data = await fs25.readFile(filePath, "utf-8");
170165
+ const stat = await fs26.stat(filePath);
170166
+ const data = await fs26.readFile(filePath, "utf-8");
169848
170167
  const lines = data.split(`
169849
170168
  `).filter((l) => l.trim().length > 0);
169850
170169
  let preview = "";
@@ -169878,20 +170197,20 @@ class FileSessionRepository {
169878
170197
  }
169879
170198
  }
169880
170199
  async deleteSession(sessionId) {
169881
- const historyPath = path26.join(this.sessionDir, `${sessionId}.jsonl`);
170200
+ const historyPath = path27.join(this.sessionDir, `${sessionId}.jsonl`);
169882
170201
  try {
169883
- await fs25.unlink(historyPath);
170202
+ await fs26.unlink(historyPath);
169884
170203
  } catch (e) {}
169885
170204
  if (this.legacySessionDir) {
169886
170205
  try {
169887
- await fs25.unlink(path26.join(this.legacySessionDir, `${sessionId}.jsonl`));
170206
+ await fs26.unlink(path27.join(this.legacySessionDir, `${sessionId}.jsonl`));
169888
170207
  } catch {}
169889
170208
  }
169890
170209
  }
169891
170210
  async renameSession(oldSessionId, newSessionId) {
169892
- const oldPath = path26.join(this.sessionDir, `${oldSessionId}.jsonl`);
169893
- const newPath = path26.join(this.sessionDir, `${newSessionId}.jsonl`);
169894
- await fs25.rename(oldPath, newPath);
170211
+ const oldPath = path27.join(this.sessionDir, `${oldSessionId}.jsonl`);
170212
+ const newPath = path27.join(this.sessionDir, `${newSessionId}.jsonl`);
170213
+ await fs26.rename(oldPath, newPath);
169895
170214
  }
169896
170215
  }
169897
170216
  var init_file_session_repository = __esm(() => {
@@ -175518,6 +175837,614 @@ var init_task_manager = __esm(() => {
175518
175837
  }
175519
175838
  });
175520
175839
 
175840
+ // src/utils/browser/detector.ts
175841
+ import { execSync as execSync3 } from "node:child_process";
175842
+ import fs27 from "node:fs/promises";
175843
+ import os14 from "node:os";
175844
+ import path28 from "node:path";
175845
+ async function isExecutable(filePath) {
175846
+ try {
175847
+ await fs27.access(filePath, fs27.constants.X_OK);
175848
+ const stats = await fs27.stat(filePath);
175849
+ return stats.isFile();
175850
+ } catch {
175851
+ return false;
175852
+ }
175853
+ }
175854
+ async function findSystemChrome() {
175855
+ if (cachedChromePath !== undefined) {
175856
+ return cachedChromePath;
175857
+ }
175858
+ const envVars = ["CHROME_PATH", "CHROME_BIN", "CHROMIUM_PATH", "PUPPETEER_EXECUTABLE_PATH"];
175859
+ for (const envVar of envVars) {
175860
+ const val = process.env[envVar];
175861
+ if (val && await isExecutable(val)) {
175862
+ cachedChromePath = val;
175863
+ return val;
175864
+ }
175865
+ }
175866
+ const platform = os14.platform();
175867
+ const candidates = [];
175868
+ if (platform === "linux") {
175869
+ candidates.push("/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/snap/bin/chromium", "/usr/bin/brave-browser", "/usr/bin/microsoft-edge", "/usr/bin/microsoft-edge-stable");
175870
+ } else if (platform === "darwin") {
175871
+ const home = os14.homedir();
175872
+ candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", path28.join(home, "Applications/Google Chrome.app/Contents/MacOS/Google Chrome"), path28.join(home, "Applications/Chromium.app/Contents/MacOS/Chromium"));
175873
+ } else if (platform === "win32") {
175874
+ const localAppData = process.env.LOCALAPPDATA || "";
175875
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
175876
+ const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
175877
+ candidates.push(path28.join(programFiles, "Google\\Chrome\\Application\\chrome.exe"), path28.join(programFilesX86, "Google\\Chrome\\Application\\chrome.exe"), path28.join(localAppData, "Google\\Chrome\\Application\\chrome.exe"), path28.join(programFiles, "Microsoft\\Edge\\Application\\msedge.exe"), path28.join(programFilesX86, "Microsoft\\Edge\\Application\\msedge.exe"), path28.join(localAppData, "BraveSoftware\\Brave-Browser\\Application\\brave.exe"));
175878
+ }
175879
+ for (const candidate of candidates) {
175880
+ if (await isExecutable(candidate)) {
175881
+ cachedChromePath = candidate;
175882
+ return candidate;
175883
+ }
175884
+ }
175885
+ const binaryNames = platform === "win32" ? ["chrome.exe", "msedge.exe", "brave.exe"] : [
175886
+ "google-chrome",
175887
+ "google-chrome-stable",
175888
+ "chromium",
175889
+ "chromium-browser",
175890
+ "brave-browser",
175891
+ "microsoft-edge"
175892
+ ];
175893
+ const whichCmd = platform === "win32" ? "where" : "which";
175894
+ for (const bin of binaryNames) {
175895
+ try {
175896
+ const out = execSync3(`${whichCmd} ${bin}`, {
175897
+ stdio: ["ignore", "pipe", "ignore"],
175898
+ encoding: "utf-8"
175899
+ }).trim();
175900
+ const firstLine = out.split(`
175901
+ `)[0]?.trim();
175902
+ if (firstLine && await isExecutable(firstLine)) {
175903
+ cachedChromePath = firstLine;
175904
+ return firstLine;
175905
+ }
175906
+ } catch {}
175907
+ }
175908
+ cachedChromePath = null;
175909
+ return null;
175910
+ }
175911
+ var cachedChromePath = undefined;
175912
+ var init_detector = () => {};
175913
+
175914
+ // src/utils/browser/cdp-client.ts
175915
+ import { spawn as spawn4 } from "node:child_process";
175916
+ import fs28 from "node:fs/promises";
175917
+ import os15 from "node:os";
175918
+ import path29 from "node:path";
175919
+
175920
+ class CdpBrowserSession {
175921
+ options;
175922
+ chromeProc = null;
175923
+ ws = null;
175924
+ nextId = 1;
175925
+ pendingCommands = new Map;
175926
+ sessionId = null;
175927
+ tmpProfileDir = null;
175928
+ consoleErrors = [];
175929
+ networkErrors = [];
175930
+ isLoaded = false;
175931
+ constructor(options) {
175932
+ this.options = options;
175933
+ }
175934
+ async launch() {
175935
+ this.tmpProfileDir = path29.join(os15.tmpdir(), `december-chrome-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
175936
+ await fs28.mkdir(this.tmpProfileDir, { recursive: true });
175937
+ const isHeadless = this.options.headless ?? (process.env.DECEMBER_HEADED !== "1" && process.env.HEADED !== "1");
175938
+ const args = [
175939
+ ...isHeadless ? ["--headless=new"] : [],
175940
+ "--disable-gpu",
175941
+ "--no-sandbox",
175942
+ "--disable-dev-shm-usage",
175943
+ "--remote-debugging-port=0",
175944
+ "--hide-scrollbars",
175945
+ "--window-size=1280,800",
175946
+ `--user-data-dir=${this.tmpProfileDir}`,
175947
+ "about:blank"
175948
+ ];
175949
+ const proc = spawn4(this.options.chromePath, args, {
175950
+ stdio: ["ignore", "pipe", "pipe"]
175951
+ });
175952
+ this.chromeProc = proc;
175953
+ const wsUrl = await new Promise((resolve, reject) => {
175954
+ let buffer = "";
175955
+ const timeout = setTimeout(() => {
175956
+ reject(new Error("Timeout waiting for Chrome DevTools WebSocket URL."));
175957
+ }, this.options.timeoutMs || 15000);
175958
+ const onData = (chunk) => {
175959
+ buffer += chunk.toString();
175960
+ const match = buffer.match(/DevTools listening on (ws:\/\/[^\s]+)/);
175961
+ if (match && match[1]) {
175962
+ clearTimeout(timeout);
175963
+ proc.stderr?.off("data", onData);
175964
+ resolve(match[1]);
175965
+ }
175966
+ };
175967
+ proc.stderr?.on("data", onData);
175968
+ proc.on("error", (err) => {
175969
+ clearTimeout(timeout);
175970
+ reject(err);
175971
+ });
175972
+ proc.on("exit", (code) => {
175973
+ clearTimeout(timeout);
175974
+ reject(new Error(`Chrome exited prematurely with code ${code}.`));
175975
+ });
175976
+ });
175977
+ await this.connectWs(wsUrl);
175978
+ const createTargetRes = await this.sendCommand("Target.createTarget", {
175979
+ url: "about:blank"
175980
+ });
175981
+ const targetId = createTargetRes.targetId;
175982
+ const attachRes = await this.sendCommand("Target.attachToTarget", {
175983
+ targetId,
175984
+ flatten: true
175985
+ });
175986
+ this.sessionId = attachRes.sessionId;
175987
+ await this.sendSessionCommand("Page.enable");
175988
+ await this.sendSessionCommand("Runtime.enable");
175989
+ await this.sendSessionCommand("Network.enable");
175990
+ }
175991
+ async connectWs(wsUrl) {
175992
+ return new Promise((resolve, reject) => {
175993
+ const ws = new WebSocket(wsUrl);
175994
+ this.ws = ws;
175995
+ ws.onopen = () => resolve();
175996
+ ws.onerror = (err) => reject(err);
175997
+ ws.onmessage = (event) => {
175998
+ try {
175999
+ const msg = JSON.parse(event.data.toString());
176000
+ if (typeof msg.id === "number") {
176001
+ const pending = this.pendingCommands.get(msg.id);
176002
+ if (pending) {
176003
+ this.pendingCommands.delete(msg.id);
176004
+ if (msg.error) {
176005
+ pending.reject(new Error(msg.error.message || "CDP Error"));
176006
+ } else {
176007
+ pending.resolve(msg.result);
176008
+ }
176009
+ }
176010
+ return;
176011
+ }
176012
+ this.handleEvent(msg);
176013
+ } catch {}
176014
+ };
176015
+ });
176016
+ }
176017
+ handleEvent(msg) {
176018
+ const method = msg.method;
176019
+ if (method === "Page.loadEventFired" || method === "Page.domContentEventFired") {
176020
+ this.isLoaded = true;
176021
+ } else if (method === "Runtime.exceptionThrown") {
176022
+ const details = msg.params?.exceptionDetails;
176023
+ const desc = details?.exception?.description || details?.text || "Unknown runtime exception";
176024
+ this.consoleErrors.push(`Exception: ${desc}`);
176025
+ } else if (method === "Runtime.consoleAPICalled") {
176026
+ const type = msg.params?.type;
176027
+ if (type === "error" || type === "warning") {
176028
+ const args = (msg.params?.args || []).map((a) => a.value ?? a.description ?? "").join(" ");
176029
+ this.consoleErrors.push(`[console.${type}] ${args}`);
176030
+ }
176031
+ } else if (method === "Network.responseReceived") {
176032
+ const res = msg.params?.response;
176033
+ if (res && typeof res.status === "number" && res.status >= 400) {
176034
+ this.networkErrors.push(`HTTP ${res.status} ${res.statusText || ""} - ${res.url}`);
176035
+ }
176036
+ } else if (method === "Network.loadingFailed") {
176037
+ const err = msg.params;
176038
+ if (err) {
176039
+ this.networkErrors.push(`Network failure: ${err.errorText || "Unknown error"} (${err.type || "request"})`);
176040
+ }
176041
+ }
176042
+ }
176043
+ sendCommand(method, params) {
176044
+ return new Promise((resolve, reject) => {
176045
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
176046
+ return reject(new Error("WebSocket is not open"));
176047
+ }
176048
+ const id = this.nextId++;
176049
+ this.pendingCommands.set(id, { resolve, reject });
176050
+ this.ws.send(JSON.stringify({ id, method, params }));
176051
+ });
176052
+ }
176053
+ sendSessionCommand(method, params) {
176054
+ return new Promise((resolve, reject) => {
176055
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
176056
+ return reject(new Error("WebSocket is not open"));
176057
+ }
176058
+ const id = this.nextId++;
176059
+ this.pendingCommands.set(id, { resolve, reject });
176060
+ const payload = { id, method, params };
176061
+ if (this.sessionId) {
176062
+ payload.sessionId = this.sessionId;
176063
+ }
176064
+ this.ws.send(JSON.stringify(payload));
176065
+ });
176066
+ }
176067
+ async navigate(url, timeoutMs = 12000) {
176068
+ this.consoleErrors = [];
176069
+ this.networkErrors = [];
176070
+ this.isLoaded = false;
176071
+ await this.sendSessionCommand("Page.navigate", { url });
176072
+ const startTime = Date.now();
176073
+ while (!this.isLoaded && Date.now() - startTime < timeoutMs) {
176074
+ await new Promise((r) => setTimeout(r, 100));
176075
+ }
176076
+ await new Promise((r) => setTimeout(r, 300));
176077
+ const evalRes = await this.sendSessionCommand("Runtime.evaluate", {
176078
+ expression: '(function() { return document.body ? (document.body.innerText || document.documentElement.innerText || "") : ""; })()',
176079
+ returnByValue: true
176080
+ });
176081
+ const text = evalRes?.result?.value || "";
176082
+ return {
176083
+ text: text.trim(),
176084
+ consoleErrors: [...this.consoleErrors],
176085
+ networkErrors: [...this.networkErrors]
176086
+ };
176087
+ }
176088
+ async click(selector) {
176089
+ const expression = `
176090
+ (function(sel) {
176091
+ var el = document.querySelector(sel);
176092
+ if (!el) {
176093
+ var buttons = Array.from(document.querySelectorAll('button, a, input[type=button], input[type=submit], [role=button]'));
176094
+ el = buttons.find(function(b) {
176095
+ return (b.textContent || '').trim().toLowerCase() === sel.trim().toLowerCase();
176096
+ });
176097
+ }
176098
+ if (!el) {
176099
+ return { success: false, error: 'Could not find element matching: ' + sel };
176100
+ }
176101
+ el.scrollIntoView({ behavior: 'instant', block: 'center' });
176102
+ el.click();
176103
+ return {
176104
+ success: true,
176105
+ tagName: el.tagName,
176106
+ text: (el.textContent || '').trim().slice(0, 50)
176107
+ };
176108
+ })(${JSON.stringify(selector)})
176109
+ `;
176110
+ const evalRes = await this.sendSessionCommand("Runtime.evaluate", {
176111
+ expression,
176112
+ returnByValue: true
176113
+ });
176114
+ const result = evalRes?.result?.value;
176115
+ if (!result || !result.success) {
176116
+ return {
176117
+ success: false,
176118
+ output: "",
176119
+ error: result?.error || `Element "${selector}" not found.`,
176120
+ consoleErrors: [...this.consoleErrors],
176121
+ networkErrors: [...this.networkErrors]
176122
+ };
176123
+ }
176124
+ await new Promise((r) => setTimeout(r, 300));
176125
+ return {
176126
+ success: true,
176127
+ output: `Clicked <${result.tagName}>: "${result.text || selector}"`,
176128
+ consoleErrors: [...this.consoleErrors],
176129
+ networkErrors: [...this.networkErrors]
176130
+ };
176131
+ }
176132
+ async type(selector, text) {
176133
+ const expression = `
176134
+ (function(sel, val) {
176135
+ var el = document.querySelector(sel);
176136
+ if (!el) {
176137
+ var inputs = Array.from(document.querySelectorAll('input, textarea'));
176138
+ el = inputs.find(function(i) {
176139
+ return i.name === sel || i.id === sel || i.placeholder === sel;
176140
+ });
176141
+ }
176142
+ if (!el) {
176143
+ return { success: false, error: 'Could not find input element matching: ' + sel };
176144
+ }
176145
+ el.focus();
176146
+ el.value = val;
176147
+ el.dispatchEvent(new Event('input', { bubbles: true }));
176148
+ el.dispatchEvent(new Event('change', { bubbles: true }));
176149
+ return { success: true, tagName: el.tagName, value: el.value };
176150
+ })(${JSON.stringify(selector)}, ${JSON.stringify(text)})
176151
+ `;
176152
+ const evalRes = await this.sendSessionCommand("Runtime.evaluate", {
176153
+ expression,
176154
+ returnByValue: true
176155
+ });
176156
+ const result = evalRes?.result?.value;
176157
+ if (!result || !result.success) {
176158
+ return {
176159
+ success: false,
176160
+ output: "",
176161
+ error: result?.error || `Input element "${selector}" not found.`,
176162
+ consoleErrors: [...this.consoleErrors],
176163
+ networkErrors: [...this.networkErrors]
176164
+ };
176165
+ }
176166
+ return {
176167
+ success: true,
176168
+ output: `Typed "${text}" into <${result.tagName}> (${selector})`,
176169
+ consoleErrors: [...this.consoleErrors],
176170
+ networkErrors: [...this.networkErrors]
176171
+ };
176172
+ }
176173
+ async screenshot(fullPage = false, outputPath) {
176174
+ const params = { format: "png" };
176175
+ if (fullPage) {
176176
+ params.captureBeyondViewport = true;
176177
+ }
176178
+ const res = await this.sendSessionCommand("Page.captureScreenshot", params);
176179
+ const base64Data = res?.data;
176180
+ if (!base64Data) {
176181
+ return {
176182
+ success: false,
176183
+ output: "",
176184
+ error: "Failed to capture screenshot data from Chrome."
176185
+ };
176186
+ }
176187
+ const targetFile = outputPath || path29.join(os15.tmpdir(), `december-screenshot-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.png`);
176188
+ await fs28.writeFile(targetFile, Buffer.from(base64Data, "base64"));
176189
+ return {
176190
+ success: true,
176191
+ output: `Screenshot captured successfully.`,
176192
+ screenshotPath: targetFile,
176193
+ screenshotBase64: base64Data,
176194
+ consoleErrors: [...this.consoleErrors],
176195
+ networkErrors: [...this.networkErrors]
176196
+ };
176197
+ }
176198
+ async getConsoleErrors() {
176199
+ return {
176200
+ consoleErrors: [...this.consoleErrors],
176201
+ networkErrors: [...this.networkErrors]
176202
+ };
176203
+ }
176204
+ async close() {
176205
+ if (this.ws) {
176206
+ try {
176207
+ this.ws.close();
176208
+ } catch {}
176209
+ this.ws = null;
176210
+ }
176211
+ if (this.chromeProc) {
176212
+ try {
176213
+ this.chromeProc.kill("SIGTERM");
176214
+ setTimeout(() => {
176215
+ if (this.chromeProc && !this.chromeProc.killed) {
176216
+ this.chromeProc.kill("SIGKILL");
176217
+ }
176218
+ }, 1000);
176219
+ } catch {}
176220
+ this.chromeProc = null;
176221
+ }
176222
+ if (this.tmpProfileDir) {
176223
+ try {
176224
+ await fs28.rm(this.tmpProfileDir, { recursive: true, force: true });
176225
+ } catch {}
176226
+ this.tmpProfileDir = null;
176227
+ }
176228
+ }
176229
+ }
176230
+ var init_cdp_client = () => {};
176231
+
176232
+ // src/utils/browser/browser-runner.ts
176233
+ class BrowserRunner {
176234
+ activeSession = null;
176235
+ cleanupRegistered = false;
176236
+ registerCleanup() {
176237
+ if (this.cleanupRegistered)
176238
+ return;
176239
+ this.cleanupRegistered = true;
176240
+ const cleanup = () => {
176241
+ if (this.activeSession) {
176242
+ this.activeSession.close().catch(() => {});
176243
+ this.activeSession = null;
176244
+ }
176245
+ };
176246
+ process.once("exit", cleanup);
176247
+ process.once("SIGINT", cleanup);
176248
+ process.once("SIGTERM", cleanup);
176249
+ }
176250
+ async getSession() {
176251
+ if (this.activeSession) {
176252
+ return this.activeSession;
176253
+ }
176254
+ const chromePath = await findSystemChrome();
176255
+ if (!chromePath) {
176256
+ return null;
176257
+ }
176258
+ try {
176259
+ await Promise.resolve().then(() => init_config());
176260
+ const config = await loadConfig().catch(() => null);
176261
+ const hasDisplay = process.platform !== "linux" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
176262
+ const isVisible = hasDisplay && config?.browserVisible !== false && process.env.HEADED !== "0" && process.env.DECEMBER_HEADED !== "0";
176263
+ const session = new CdpBrowserSession({
176264
+ chromePath,
176265
+ headless: !isVisible
176266
+ });
176267
+ await session.launch();
176268
+ this.activeSession = session;
176269
+ this.registerCleanup();
176270
+ return session;
176271
+ } catch {
176272
+ return null;
176273
+ }
176274
+ }
176275
+ async staticFallbackFetch(url) {
176276
+ try {
176277
+ const res = await fetch(url, {
176278
+ headers: {
176279
+ "User-Agent": "Mozilla/5.0 (compatible; DecemberAgent/1.0)"
176280
+ }
176281
+ });
176282
+ const html = await res.text();
176283
+ if (!res.ok) {
176284
+ return {
176285
+ text: "",
176286
+ error: `HTTP Error (${res.status}): ${html}`,
176287
+ networkErrors: [`HTTP ${res.status} - ${url}`]
176288
+ };
176289
+ }
176290
+ const cleanText = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
176291
+ const notice = `
176292
+
176293
+ [Note: Local Chrome/Chromium not detected. Displaying static HTML text. Install Chrome or Chromium for client-side JavaScript rendering and interactive actions.]`;
176294
+ return {
176295
+ text: cleanText + notice,
176296
+ consoleErrors: [],
176297
+ networkErrors: []
176298
+ };
176299
+ } catch (err) {
176300
+ return {
176301
+ text: "",
176302
+ error: err.message,
176303
+ networkErrors: [`Network request failed: ${err.message}`]
176304
+ };
176305
+ }
176306
+ }
176307
+ navigate = async (url) => {
176308
+ const session = await this.getSession();
176309
+ if (session) {
176310
+ try {
176311
+ return await session.navigate(url);
176312
+ } catch {}
176313
+ }
176314
+ return this.staticFallbackFetch(url);
176315
+ };
176316
+ action = async (params) => {
176317
+ const { action, url, selector, text, fullPage } = params;
176318
+ if (action === "navigate") {
176319
+ if (!url) {
176320
+ return {
176321
+ success: false,
176322
+ action: "navigate",
176323
+ error: 'Action "navigate" requires a valid "url" parameter.'
176324
+ };
176325
+ }
176326
+ const navResult = await this.navigate(url);
176327
+ if (navResult.error) {
176328
+ return {
176329
+ success: false,
176330
+ action: "navigate",
176331
+ error: navResult.error,
176332
+ networkErrors: navResult.networkErrors
176333
+ };
176334
+ }
176335
+ return {
176336
+ success: true,
176337
+ action: "navigate",
176338
+ text: navResult.text,
176339
+ consoleErrors: navResult.consoleErrors,
176340
+ networkErrors: navResult.networkErrors
176341
+ };
176342
+ }
176343
+ if (action === "get_console_errors") {
176344
+ if (this.activeSession) {
176345
+ const errors = await this.activeSession.getConsoleErrors();
176346
+ const count = errors.consoleErrors.length + errors.networkErrors.length;
176347
+ return {
176348
+ success: true,
176349
+ action: "get_console_errors",
176350
+ output: count > 0 ? `Captured ${errors.consoleErrors.length} console error(s) and ${errors.networkErrors.length} network error(s).` : "No console or network errors detected.",
176351
+ consoleErrors: errors.consoleErrors,
176352
+ networkErrors: errors.networkErrors
176353
+ };
176354
+ }
176355
+ return {
176356
+ success: true,
176357
+ action: "get_console_errors",
176358
+ output: "No active browser session running."
176359
+ };
176360
+ }
176361
+ const session = await this.getSession();
176362
+ if (!session) {
176363
+ return {
176364
+ success: false,
176365
+ action,
176366
+ error: `Interactive browser action "${action}" requires Google Chrome or Chromium. Please install Chrome or Chromium, or set the CHROME_PATH environment variable.`
176367
+ };
176368
+ }
176369
+ switch (action) {
176370
+ case "click": {
176371
+ if (!selector) {
176372
+ return {
176373
+ success: false,
176374
+ action: "click",
176375
+ error: 'Action "click" requires a "selector" parameter.'
176376
+ };
176377
+ }
176378
+ const res = await session.click(selector);
176379
+ return {
176380
+ success: res.success,
176381
+ action: "click",
176382
+ output: res.output,
176383
+ error: res.error,
176384
+ consoleErrors: res.consoleErrors,
176385
+ networkErrors: res.networkErrors
176386
+ };
176387
+ }
176388
+ case "type": {
176389
+ if (!selector) {
176390
+ return {
176391
+ success: false,
176392
+ action: "type",
176393
+ error: 'Action "type" requires a "selector" parameter.'
176394
+ };
176395
+ }
176396
+ const res = await session.type(selector, text || "");
176397
+ return {
176398
+ success: res.success,
176399
+ action: "type",
176400
+ output: res.output,
176401
+ error: res.error,
176402
+ consoleErrors: res.consoleErrors,
176403
+ networkErrors: res.networkErrors
176404
+ };
176405
+ }
176406
+ case "screenshot": {
176407
+ const res = await session.screenshot(Boolean(fullPage));
176408
+ return {
176409
+ success: res.success,
176410
+ action: "screenshot",
176411
+ output: res.output,
176412
+ screenshotPath: res.screenshotPath,
176413
+ screenshotBase64: res.screenshotBase64,
176414
+ error: res.error,
176415
+ consoleErrors: res.consoleErrors,
176416
+ networkErrors: res.networkErrors
176417
+ };
176418
+ }
176419
+ default:
176420
+ return {
176421
+ success: false,
176422
+ action,
176423
+ error: `Unsupported browser action: ${action}`
176424
+ };
176425
+ }
176426
+ };
176427
+ close = async () => {
176428
+ if (this.activeSession) {
176429
+ await this.activeSession.close();
176430
+ this.activeSession = null;
176431
+ }
176432
+ };
176433
+ }
176434
+ var browserRunner;
176435
+ var init_browser_runner = __esm(() => {
176436
+ init_cdp_client();
176437
+ init_detector();
176438
+ browserRunner = new BrowserRunner;
176439
+ });
176440
+
176441
+ // src/utils/browser/index.ts
176442
+ var init_browser2 = __esm(() => {
176443
+ init_detector();
176444
+ init_cdp_client();
176445
+ init_browser_runner();
176446
+ });
176447
+
175521
176448
  // src/local-operations.ts
175522
176449
  var exports_local_operations = {};
175523
176450
  __export(exports_local_operations, {
@@ -175525,22 +176452,22 @@ __export(exports_local_operations, {
175525
176452
  localOperations: () => localOperations,
175526
176453
  setActiveScopeDir: () => setActiveScopeDir
175527
176454
  });
175528
- import { exec as exec4, spawn as spawn4 } from "node:child_process";
176455
+ import { exec as exec4, spawn as spawn5 } from "node:child_process";
175529
176456
  import nodeFs from "node:fs";
175530
- import fs26 from "node:fs/promises";
175531
- import path27 from "node:path";
176457
+ import fs29 from "node:fs/promises";
176458
+ import path30 from "node:path";
175532
176459
  import { promisify as promisify4 } from "node:util";
175533
176460
  function setActiveScopeDir(dir) {
175534
176461
  activeScopeDir = dir;
175535
176462
  }
175536
176463
  function getScopedCwd() {
175537
176464
  if (activeScopeDir) {
175538
- return path27.resolve(process.cwd(), activeScopeDir);
176465
+ return path30.resolve(process.cwd(), activeScopeDir);
175539
176466
  }
175540
176467
  return process.cwd();
175541
176468
  }
175542
176469
  function isTypeScriptProject(workspaceDir) {
175543
- return nodeFs.existsSync(path27.join(workspaceDir, "tsconfig.json")) || nodeFs.existsSync(path27.join(workspaceDir, "package.json"));
176470
+ return nodeFs.existsSync(path30.join(workspaceDir, "tsconfig.json")) || nodeFs.existsSync(path30.join(workspaceDir, "package.json"));
175544
176471
  }
175545
176472
  async function getOrLaunchLspClient(customWorkspaceDir) {
175546
176473
  const workspaceDir = customWorkspaceDir || getScopedCwd();
@@ -175575,6 +176502,7 @@ var init_local_operations = __esm(() => {
175575
176502
  init_src();
175576
176503
  init_src4();
175577
176504
  init_task_manager();
176505
+ init_browser2();
175578
176506
  import_fast_glob = __toESM(require_out4(), 1);
175579
176507
  execAsync2 = promisify4(exec4);
175580
176508
  localBashOps = createLocalBashOperations();
@@ -175587,7 +176515,7 @@ var init_local_operations = __esm(() => {
175587
176515
  const fallbackTimeoutMs = actualOptions.waitMsBeforeAsync ?? DEFAULT_BG_TIMEOUT_MS;
175588
176516
  const serverDelayMs = actualOptions.serverReadyDelayMs ?? DEFAULT_SERVER_READY_DELAY_MS;
175589
176517
  return new Promise((resolve, reject) => {
175590
- const child = spawn4(command, {
176518
+ const child = spawn5(command, {
175591
176519
  cwd: targetCwd,
175592
176520
  detached: process.platform !== "win32",
175593
176521
  env: actualOptions.env ?? process.env,
@@ -175717,19 +176645,19 @@ Failed to start process: ${err?.message || err}
175717
176645
  fs: {
175718
176646
  readFile: async (filepath) => {
175719
176647
  const root = getScopedCwd();
175720
- const absolutePath = path27.isAbsolute(filepath) ? filepath : path27.resolve(root, filepath);
175721
- return fs26.readFile(absolutePath, "utf8");
176648
+ const absolutePath = path30.isAbsolute(filepath) ? filepath : path30.resolve(root, filepath);
176649
+ return fs29.readFile(absolutePath, "utf8");
175722
176650
  },
175723
176651
  writeFile: async (filepath, content) => {
175724
176652
  const root = getScopedCwd();
175725
- const absolutePath = path27.isAbsolute(filepath) ? filepath : path27.resolve(root, filepath);
175726
- await fs26.mkdir(path27.dirname(absolutePath), { recursive: true });
175727
- await fs26.writeFile(absolutePath, content, "utf8");
176653
+ const absolutePath = path30.isAbsolute(filepath) ? filepath : path30.resolve(root, filepath);
176654
+ await fs29.mkdir(path30.dirname(absolutePath), { recursive: true });
176655
+ await fs29.writeFile(absolutePath, content, "utf8");
175728
176656
  },
175729
176657
  readdir: async (dirPath) => {
175730
176658
  const root = getScopedCwd();
175731
- const absolutePath = path27.isAbsolute(dirPath) ? dirPath : path27.resolve(root, dirPath);
175732
- const entries = await fs26.readdir(absolutePath, { withFileTypes: true });
176659
+ const absolutePath = path30.isAbsolute(dirPath) ? dirPath : path30.resolve(root, dirPath);
176660
+ const entries = await fs29.readdir(absolutePath, { withFileTypes: true });
175733
176661
  const ignores = getWorkspaceIgnores(root);
175734
176662
  return entries.filter((entry) => !isPathIgnored(entry.name, ignores)).map((entry) => {
175735
176663
  const type = entry.isDirectory() ? "DIR " : "FILE";
@@ -175738,14 +176666,14 @@ Failed to start process: ${err?.message || err}
175738
176666
  },
175739
176667
  mkdir: async (dirPath, options) => {
175740
176668
  const root = getScopedCwd();
175741
- const absolutePath = path27.isAbsolute(dirPath) ? dirPath : path27.resolve(root, dirPath);
175742
- await fs26.mkdir(absolutePath, options);
176669
+ const absolutePath = path30.isAbsolute(dirPath) ? dirPath : path30.resolve(root, dirPath);
176670
+ await fs29.mkdir(absolutePath, options);
175743
176671
  },
175744
176672
  exists: async (filepath) => {
175745
176673
  const root = getScopedCwd();
175746
- const absolutePath = path27.isAbsolute(filepath) ? filepath : path27.resolve(root, filepath);
176674
+ const absolutePath = path30.isAbsolute(filepath) ? filepath : path30.resolve(root, filepath);
175747
176675
  try {
175748
- await fs26.access(absolutePath);
176676
+ await fs29.access(absolutePath);
175749
176677
  return true;
175750
176678
  } catch {
175751
176679
  return false;
@@ -175755,7 +176683,7 @@ Failed to start process: ${err?.message || err}
175755
176683
  search: {
175756
176684
  find: async (dirPath, query) => {
175757
176685
  const root = getScopedCwd();
175758
- const targetDir = path27.isAbsolute(dirPath) ? dirPath : path27.resolve(root, dirPath);
176686
+ const targetDir = path30.isAbsolute(dirPath) ? dirPath : path30.resolve(root, dirPath);
175759
176687
  const ignores = getWorkspaceIgnores(root);
175760
176688
  const files = await import_fast_glob.default([query], {
175761
176689
  cwd: targetDir,
@@ -175767,7 +176695,7 @@ Failed to start process: ${err?.message || err}
175767
176695
  },
175768
176696
  grep: async (dirPath, query) => {
175769
176697
  const root = getScopedCwd();
175770
- const targetDir = path27.isAbsolute(dirPath) ? dirPath : path27.resolve(root, dirPath);
176698
+ const targetDir = path30.isAbsolute(dirPath) ? dirPath : path30.resolve(root, dirPath);
175771
176699
  const ignores = getWorkspaceIgnores(root);
175772
176700
  try {
175773
176701
  const escapedQuery = query.replace(/"/g, "\\\"");
@@ -175781,7 +176709,7 @@ Failed to start process: ${err?.message || err}
175781
176709
  if (!line.trim())
175782
176710
  return false;
175783
176711
  const rawPath = line.split(":")[0];
175784
- const relPath = path27.isAbsolute(rawPath) ? path27.relative(root, rawPath) : rawPath;
176712
+ const relPath = path30.isAbsolute(rawPath) ? path30.relative(root, rawPath) : rawPath;
175785
176713
  return !isPathIgnored(relPath, ignores);
175786
176714
  });
175787
176715
  return filtered.join(`
@@ -175806,23 +176734,9 @@ Failed to start process: ${err?.message || err}
175806
176734
  }
175807
176735
  },
175808
176736
  browser: {
175809
- navigate: async (url) => {
175810
- try {
175811
- const res = await fetch(url, {
175812
- headers: {
175813
- "User-Agent": "Mozilla/5.0 (compatible; DecemberAgent/1.0)"
175814
- }
175815
- });
175816
- const html = await res.text();
175817
- if (!res.ok) {
175818
- return { text: "", error: `HTTP Error (${res.status}): ${html}` };
175819
- }
175820
- const cleanText = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
175821
- return { text: cleanText };
175822
- } catch (error) {
175823
- return { text: "", error: error.message };
175824
- }
175825
- }
176737
+ navigate: async (url) => browserRunner.navigate(url),
176738
+ action: async (params) => browserRunner.action(params),
176739
+ close: async () => browserRunner.close()
175826
176740
  },
175827
176741
  diagnostics: {
175828
176742
  getDiagnostics: async (filePath) => {
@@ -177515,7 +178429,7 @@ var init_environment = __esm(() => {
177515
178429
 
177516
178430
  // ../../node_modules/.bun/ansi-escapes@7.3.0/node_modules/ansi-escapes/base.js
177517
178431
  import process2 from "node:process";
177518
- import os13 from "node:os";
178432
+ import os16 from "node:os";
177519
178433
  var ESC = "\x1B[", SEP = ";", isTerminalApp, isWindows2, isTmux, cwdFunction, cursorTo = (x, y) => {
177520
178434
  if (typeof x !== "number") {
177521
178435
  throw new TypeError("The `x` argument is required");
@@ -177537,7 +178451,7 @@ var ESC = "\x1B[", SEP = ";", isTerminalApp, isWindows2, isTmux, cwdFunction, cu
177537
178451
  if (isBrowser || !isWindows2) {
177538
178452
  return false;
177539
178453
  }
177540
- const parts = os13.release().split(".");
178454
+ const parts = os16.release().split(".");
177541
178455
  const major = Number(parts[0]);
177542
178456
  const build = Number(parts[2] ?? 0);
177543
178457
  if (major < 10) {
@@ -180224,7 +181138,7 @@ var init_wrap_ansi = __esm(() => {
180224
181138
  // ../../node_modules/.bun/terminal-size@4.0.1/node_modules/terminal-size/index.js
180225
181139
  import process3 from "node:process";
180226
181140
  import { execFileSync } from "node:child_process";
180227
- import fs27 from "node:fs";
181141
+ import fs30 from "node:fs";
180228
181142
  import tty from "node:tty";
180229
181143
  function terminalSize() {
180230
181144
  const { env, stdout, stderr } = process3;
@@ -180272,7 +181186,7 @@ var defaultColumns = 80, defaultRows = 24, exec6 = (command, arguments_, { shell
180272
181186
  return true;
180273
181187
  }
180274
181188
  try {
180275
- const statContents = fs27.readFileSync("/proc/self/stat", "utf8");
181189
+ const statContents = fs30.readFileSync("/proc/self/stat", "utf8");
180276
181190
  const closingParenthesisIndex = statContents.lastIndexOf(") ");
180277
181191
  if (closingParenthesisIndex === -1) {
180278
181192
  return false;
@@ -180292,8 +181206,8 @@ var defaultColumns = 80, defaultRows = 24, exec6 = (command, arguments_, { shell
180292
181206
  }
180293
181207
  }, devTty = () => {
180294
181208
  try {
180295
- const flags = process3.platform === "darwin" ? fs27.constants.O_EVTONLY | fs27.constants.O_NONBLOCK : fs27.constants.O_NONBLOCK;
180296
- const { columns, rows } = tty.WriteStream(fs27.openSync("/dev/tty", flags));
181209
+ const flags = process3.platform === "darwin" ? fs30.constants.O_EVTONLY | fs30.constants.O_NONBLOCK : fs30.constants.O_NONBLOCK;
181210
+ const { columns, rows } = tty.WriteStream(fs30.openSync("/dev/tty", flags));
180297
181211
  return { columns, rows };
180298
181212
  } catch {}
180299
181213
  }, tput = () => {
@@ -210285,7 +211199,7 @@ var init_ansi_styles2 = __esm(() => {
210285
211199
 
210286
211200
  // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
210287
211201
  import process5 from "node:process";
210288
- import os14 from "node:os";
211202
+ import os17 from "node:os";
210289
211203
  import tty2 from "node:tty";
210290
211204
  function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process5.argv) {
210291
211205
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
@@ -210343,7 +211257,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
210343
211257
  return min;
210344
211258
  }
210345
211259
  if (process5.platform === "win32") {
210346
- const osRelease = os14.release().split(".");
211260
+ const osRelease = os17.release().split(".");
210347
211261
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
210348
211262
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
210349
211263
  }
@@ -212495,7 +213409,7 @@ var init_Text = __esm(() => {
212495
213409
  });
212496
213410
 
212497
213411
  // ../../node_modules/.bun/ink@7.1.0+90ffd46201e3176f/node_modules/ink/build/components/ErrorOverview.js
212498
- import * as fs28 from "node:fs";
213412
+ import * as fs31 from "node:fs";
212499
213413
  import { cwd } from "node:process";
212500
213414
  function ErrorOverview({ error }) {
212501
213415
  const stack = error.stack ? error.stack.split(`
@@ -212505,8 +213419,8 @@ function ErrorOverview({ error }) {
212505
213419
  let excerpt;
212506
213420
  let lineWidth = 0;
212507
213421
  const stackLineCounts = new Map;
212508
- if (filePath && origin?.line && fs28.existsSync(filePath)) {
212509
- const sourceCode = fs28.readFileSync(filePath, "utf8");
213422
+ if (filePath && origin?.line && fs31.existsSync(filePath)) {
213423
+ const sourceCode = fs31.readFileSync(filePath, "utf8");
212510
213424
  excerpt = dist_default4(sourceCode, origin.line);
212511
213425
  if (excerpt) {
212512
213426
  for (const { line } of excerpt) {
@@ -213937,7 +214851,7 @@ var init_render_to_string = __esm(async () => {
213937
214851
  });
213938
214852
 
213939
214853
  // ../../node_modules/.bun/ink@7.1.0+90ffd46201e3176f/node_modules/ink/build/components/Static.js
213940
- function Static15(props) {
214854
+ function Static16(props) {
213941
214855
  const { items, children: render, style: customStyle } = props;
213942
214856
  const [index, setIndex] = import_react17.useState(0);
213943
214857
  const itemsToRender = import_react17.useMemo(() => {
@@ -215180,9 +216094,9 @@ var init_paste_manager = __esm(() => {
215180
216094
  });
215181
216095
 
215182
216096
  // ../../packages/tui/src/utils/prompt-history.ts
215183
- import fs29 from "node:fs";
215184
- import os15 from "node:os";
215185
- import path28 from "node:path";
216097
+ import fs32 from "node:fs";
216098
+ import os18 from "node:os";
216099
+ import path31 from "node:path";
215186
216100
 
215187
216101
  class PromptHistory {
215188
216102
  historyPath;
@@ -215193,17 +216107,17 @@ class PromptHistory {
215193
216107
  if (customPath) {
215194
216108
  this.historyPath = customPath;
215195
216109
  } else {
215196
- const home = process.env.HOME || process.env.USERPROFILE || os15.homedir();
215197
- const canonicalPath = path28.join(home, ".december", "history");
215198
- const legacyPath = path28.join(home, ".config", "december", "history");
215199
- this.historyPath = fs29.existsSync(canonicalPath) || !fs29.existsSync(legacyPath) ? canonicalPath : legacyPath;
216110
+ const home = process.env.HOME || process.env.USERPROFILE || os18.homedir();
216111
+ const canonicalPath = path31.join(home, ".december", "history");
216112
+ const legacyPath = path31.join(home, ".config", "december", "history");
216113
+ this.historyPath = fs32.existsSync(canonicalPath) || !fs32.existsSync(legacyPath) ? canonicalPath : legacyPath;
215200
216114
  }
215201
216115
  this.load();
215202
216116
  }
215203
216117
  load() {
215204
216118
  try {
215205
- if (fs29.existsSync(this.historyPath)) {
215206
- const raw = fs29.readFileSync(this.historyPath, "utf8");
216119
+ if (fs32.existsSync(this.historyPath)) {
216120
+ const raw = fs32.readFileSync(this.historyPath, "utf8");
215207
216121
  this.entries = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
215208
216122
  }
215209
216123
  } catch {
@@ -215219,11 +216133,11 @@ class PromptHistory {
215219
216133
  this.cursor = this.entries.length;
215220
216134
  this.currentDraft = "";
215221
216135
  try {
215222
- const dir = path28.dirname(this.historyPath);
215223
- if (!fs29.existsSync(dir)) {
215224
- fs29.mkdirSync(dir, { recursive: true });
216136
+ const dir = path31.dirname(this.historyPath);
216137
+ if (!fs32.existsSync(dir)) {
216138
+ fs32.mkdirSync(dir, { recursive: true });
215225
216139
  }
215226
- fs29.appendFileSync(this.historyPath, trimmed + `
216140
+ fs32.appendFileSync(this.historyPath, trimmed + `
215227
216141
  `, "utf8");
215228
216142
  } catch {}
215229
216143
  }
@@ -215262,11 +216176,11 @@ var init_prompt_history = __esm(() => {
215262
216176
 
215263
216177
  // ../../packages/tui/src/utils/clipboard.ts
215264
216178
  import { spawnSync } from "child_process";
215265
- import * as os16 from "os";
216179
+ import * as os19 from "os";
215266
216180
  function writeToClipboard(text) {
215267
- const platform = os16.platform();
216181
+ const platform = os19.platform();
215268
216182
  try {
215269
- const isWsl = platform === "linux" && (!!process.env.WSL_DISTRO_NAME || os16.release().toLowerCase().includes("microsoft"));
216183
+ const isWsl = platform === "linux" && (!!process.env.WSL_DISTRO_NAME || os19.release().toLowerCase().includes("microsoft"));
215270
216184
  if (platform === "darwin") {
215271
216185
  spawnSync("pbcopy", { input: text });
215272
216186
  } else if (platform === "win32") {
@@ -215287,16 +216201,16 @@ var init_clipboard = () => {};
215287
216201
 
215288
216202
  // ../../packages/tui/src/utils/handoff.ts
215289
216203
  import { exec as exec7 } from "node:child_process";
215290
- import fs30 from "node:fs";
215291
- import path29 from "node:path";
216204
+ import fs33 from "node:fs";
216205
+ import path32 from "node:path";
215292
216206
  import { promisify as promisify5 } from "node:util";
215293
216207
  function getHandoffExcludes(baseDir = process.cwd()) {
215294
216208
  const excludes = [...MANDATORY_HANDOFF_EXCLUDES];
215295
216209
  const readIgnoreFile = (fileName) => {
215296
- const filePath = path29.join(baseDir, fileName);
216210
+ const filePath = path32.join(baseDir, fileName);
215297
216211
  try {
215298
- if (fs30.existsSync(filePath)) {
215299
- const lines = fs30.readFileSync(filePath, "utf8").split(`
216212
+ if (fs33.existsSync(filePath)) {
216213
+ const lines = fs33.readFileSync(filePath, "utf8").split(`
215300
216214
  `);
215301
216215
  for (const line of lines) {
215302
216216
  const trimmed = line.trim();
@@ -215365,9 +216279,9 @@ var init_handoff = __esm(() => {
215365
216279
  });
215366
216280
 
215367
216281
  // ../../packages/tui/src/components/command-menu/commands.tsx
215368
- import fs31 from "node:fs";
215369
- import os17 from "node:os";
215370
- import path30 from "node:path";
216282
+ import fs34 from "node:fs";
216283
+ import os20 from "node:os";
216284
+ import path33 from "node:path";
215371
216285
  var COMMANDS;
215372
216286
  var init_commands2 = __esm(() => {
215373
216287
  init_clipboard();
@@ -215470,14 +216384,14 @@ var init_commands2 = __esm(() => {
215470
216384
  action: async (ctx) => {
215471
216385
  const archivePath = ".december-handoff.tar.gz";
215472
216386
  try {
215473
- const home = process.env.HOME || process.env.USERPROFILE || os17.homedir();
215474
- const canonicalConfigPath = path30.join(home, ".december", "config.json");
215475
- const legacyConfigPath = path30.join(home, ".config", "december", "config.json");
215476
- const configPath = fs31.existsSync(canonicalConfigPath) || !fs31.existsSync(legacyConfigPath) ? canonicalConfigPath : legacyConfigPath;
216387
+ const home = process.env.HOME || process.env.USERPROFILE || os20.homedir();
216388
+ const canonicalConfigPath = path33.join(home, ".december", "config.json");
216389
+ const legacyConfigPath = path33.join(home, ".config", "december", "config.json");
216390
+ const configPath = fs34.existsSync(canonicalConfigPath) || !fs34.existsSync(legacyConfigPath) ? canonicalConfigPath : legacyConfigPath;
215477
216391
  let config = {};
215478
216392
  try {
215479
- if (fs31.existsSync(configPath)) {
215480
- config = JSON.parse(fs31.readFileSync(configPath, "utf8"));
216393
+ if (fs34.existsSync(configPath)) {
216394
+ config = JSON.parse(fs34.readFileSync(configPath, "utf8"));
215481
216395
  }
215482
216396
  } catch {}
215483
216397
  if (!config.decemberToken) {
@@ -215512,7 +216426,7 @@ var init_commands2 = __esm(() => {
215512
216426
  headers: { Authorization: `Bearer ${config.decemberToken}` }
215513
216427
  });
215514
216428
  if (!urlRes.ok) {
215515
- await fs31.promises.unlink(archivePath).catch(() => {});
216429
+ await fs34.promises.unlink(archivePath).catch(() => {});
215516
216430
  if (urlRes.status === 401) {
215517
216431
  ctx.toast.show({
215518
216432
  variant: "error",
@@ -215533,7 +216447,7 @@ var init_commands2 = __esm(() => {
215533
216447
  const urlJson = await urlRes.json();
215534
216448
  const { uploadUrl, objectKey } = urlJson.data || urlJson;
215535
216449
  ctx.toast.show({ variant: "info", message: "Uploading to MinIO..." });
215536
- const fileData = await fs31.promises.readFile(archivePath);
216450
+ const fileData = await fs34.promises.readFile(archivePath);
215537
216451
  const uploadRes = await fetch(uploadUrl, {
215538
216452
  method: "PUT",
215539
216453
  body: fileData
@@ -215557,11 +216471,11 @@ var init_commands2 = __esm(() => {
215557
216471
  const errJson = await sessionRes.json().catch(() => ({}));
215558
216472
  throw new Error(errJson.message || await sessionRes.text());
215559
216473
  }
215560
- await fs31.promises.unlink(archivePath).catch(() => {});
216474
+ await fs34.promises.unlink(archivePath).catch(() => {});
215561
216475
  ctx.toast.show({ variant: "success", message: "Handoff complete! Exiting in 3s." });
215562
216476
  setTimeout(() => ctx.exit(), 3000);
215563
216477
  } catch (e) {
215564
- await fs31.promises.unlink(archivePath).catch(() => {});
216478
+ await fs34.promises.unlink(archivePath).catch(() => {});
215565
216479
  ctx.toast.show({ variant: "error", message: `Handoff failed: ${e.message}` });
215566
216480
  }
215567
216481
  }
@@ -215633,6 +216547,12 @@ var init_commands2 = __esm(() => {
215633
216547
  description: "View quota usage",
215634
216548
  value: "/usage",
215635
216549
  action: (ctx) => {}
216550
+ },
216551
+ {
216552
+ name: "voice",
216553
+ description: "Dictate a prompt using your microphone (Deepgram)",
216554
+ value: "/voice",
216555
+ action: (ctx) => {}
215636
216556
  }
215637
216557
  ];
215638
216558
  });
@@ -216065,6 +216985,7 @@ function TextArea({
216065
216985
  onScrollUp,
216066
216986
  onScrollDown,
216067
216987
  placeholder = "",
216988
+ placeholderColor,
216068
216989
  focus = true,
216069
216990
  disableHistoryNav = false
216070
216991
  }) {
@@ -216318,7 +217239,7 @@ function TextArea({
216318
217239
  });
216319
217240
  if (!value && placeholder) {
216320
217241
  return /* @__PURE__ */ jsx_runtime7.jsxs(Text3, {
216321
- color: THEME.colors.subtle,
217242
+ color: placeholderColor || THEME.colors.subtle,
216322
217243
  wrap: "wrap",
216323
217244
  children: [
216324
217245
  focus ? /* @__PURE__ */ jsx_runtime7.jsx(Text3, {
@@ -216543,9 +217464,13 @@ var init_input_bar = __esm(async () => {
216543
217464
  queuedPrompts,
216544
217465
  tasks,
216545
217466
  onScrollUp,
216546
- onScrollDown
217467
+ onScrollDown,
217468
+ onStartVoice
216547
217469
  }) {
216548
217470
  const [value, setValue] = import_react42.useState("");
217471
+ const [isVoiceActive, setIsVoiceActive] = import_react42.useState(false);
217472
+ const [isTranscribing, setIsTranscribing] = import_react42.useState(false);
217473
+ const voiceSessionRef = import_react42.useRef(null);
216549
217474
  const toast = useToast();
216550
217475
  const dialog = useDialog();
216551
217476
  const columns = useTerminalColumns();
@@ -216553,6 +217478,71 @@ var init_input_bar = __esm(async () => {
216553
217478
  const [allWorkspaceFiles, setAllWorkspaceFiles] = import_react42.useState([]);
216554
217479
  const activeToast = toasts && toasts.length > 0 ? toasts[toasts.length - 1] : toast.currentToast ? { message: toast.currentToast.message, variant: toast.currentToast.variant } : null;
216555
217480
  const handleContentChangeRef = import_react42.useRef(() => {});
217481
+ const handleStopVoice = import_react42.useCallback(async (sendPrompt = false) => {
217482
+ const session = voiceSessionRef.current;
217483
+ if (!session) {
217484
+ setIsVoiceActive(false);
217485
+ setIsTranscribing(false);
217486
+ return;
217487
+ }
217488
+ voiceSessionRef.current = null;
217489
+ setIsVoiceActive(false);
217490
+ if (sendPrompt) {
217491
+ setIsTranscribing(true);
217492
+ try {
217493
+ const finalTranscript = await session.stop();
217494
+ const promptToSend = (finalTranscript || value).trim();
217495
+ setValue("");
217496
+ handleContentChangeRef.current("");
217497
+ if (promptToSend) {
217498
+ onSubmit(promptToSend);
217499
+ }
217500
+ } finally {
217501
+ setIsTranscribing(false);
217502
+ }
217503
+ } else {
217504
+ session.cancel();
217505
+ setValue("");
217506
+ handleContentChangeRef.current("");
217507
+ setIsTranscribing(false);
217508
+ }
217509
+ }, [onSubmit, value]);
217510
+ const handleStartVoice = import_react42.useCallback(async () => {
217511
+ if (!onStartVoice) {
217512
+ onSubmit("/voice");
217513
+ return;
217514
+ }
217515
+ setIsVoiceActive(true);
217516
+ setValue("");
217517
+ handleContentChangeRef.current("");
217518
+ setIsTranscribing(false);
217519
+ try {
217520
+ const session = await onStartVoice({
217521
+ onTranscript: (text) => {
217522
+ setValue(text);
217523
+ handleContentChangeRef.current(text);
217524
+ },
217525
+ onError: (err) => {
217526
+ setIsVoiceActive(false);
217527
+ setIsTranscribing(false);
217528
+ voiceSessionRef.current = null;
217529
+ toast.show({
217530
+ variant: "error",
217531
+ message: err.message || "Voice dictation error."
217532
+ });
217533
+ }
217534
+ });
217535
+ if (!session) {
217536
+ setIsVoiceActive(false);
217537
+ setIsTranscribing(false);
217538
+ return;
217539
+ }
217540
+ voiceSessionRef.current = session;
217541
+ } catch {
217542
+ setIsVoiceActive(false);
217543
+ setIsTranscribing(false);
217544
+ }
217545
+ }, [onStartVoice, onSubmit, toast]);
216556
217546
  const handleAutocompleteCommand = import_react42.useCallback((completedText) => {
216557
217547
  setValue(completedText);
216558
217548
  handleContentChangeRef.current(completedText);
@@ -216618,7 +217608,10 @@ var init_input_bar = __esm(async () => {
216618
217608
  handleContentChange,
216619
217609
  grillMode,
216620
217610
  onSubmit,
216621
- onCopy
217611
+ onCopy,
217612
+ isVoiceActive,
217613
+ isTranscribing,
217614
+ handleStopVoice
216622
217615
  });
216623
217616
  stateRef.current = {
216624
217617
  showFileMenu,
@@ -216628,11 +217621,26 @@ var init_input_bar = __esm(async () => {
216628
217621
  handleContentChange,
216629
217622
  grillMode,
216630
217623
  onSubmit,
216631
- onCopy
217624
+ onCopy,
217625
+ isVoiceActive,
217626
+ isTranscribing,
217627
+ handleStopVoice
216632
217628
  };
216633
217629
  const isCtrlW = import_react42.useRef(false);
216634
217630
  use_input_default((input, key) => {
216635
217631
  const state = stateRef.current;
217632
+ if (key.escape) {
217633
+ if (state.isVoiceActive || state.isTranscribing || voiceSessionRef.current) {
217634
+ state.handleStopVoice(false);
217635
+ return;
217636
+ }
217637
+ if (state.showFileMenu && state.matchingFiles.length > 0) {
217638
+ const nextVal = state.value.replace(/@\S*$/, "");
217639
+ setValue(nextVal);
217640
+ state.handleContentChange(nextVal);
217641
+ return;
217642
+ }
217643
+ }
216636
217644
  if (state.showFileMenu && state.matchingFiles.length > 0) {
216637
217645
  if (key.upArrow) {
216638
217646
  setSelectedFileIndex((prev) => Math.max(0, prev - 1));
@@ -216652,12 +217660,6 @@ var init_input_bar = __esm(async () => {
216652
217660
  }
216653
217661
  return;
216654
217662
  }
216655
- if (key.escape) {
216656
- const nextVal = state.value.replace(/@\S*$/, "");
216657
- setValue(nextVal);
216658
- state.handleContentChange(nextVal);
216659
- return;
216660
- }
216661
217663
  }
216662
217664
  if (key.ctrl && input === "w") {
216663
217665
  isCtrlW.current = true;
@@ -216748,6 +217750,12 @@ var init_input_bar = __esm(async () => {
216748
217750
  "/handoff",
216749
217751
  "/docs"
216750
217752
  ];
217753
+ if (command.value === "/voice") {
217754
+ setValue("");
217755
+ handleContentChange("");
217756
+ handleStartVoice();
217757
+ return;
217758
+ }
216751
217759
  if (forwardCommands.includes(command.value) || !command.action) {
216752
217760
  if (hasArguments) {
216753
217761
  onSubmit(currentValue);
@@ -216766,10 +217774,24 @@ var init_input_bar = __esm(async () => {
216766
217774
  onUpdateSuccess
216767
217775
  });
216768
217776
  }
216769
- }, [toast, dialog, agent, resetChat, onUpdateSuccess, handleContentChange, onSubmit, value]);
217777
+ }, [
217778
+ toast,
217779
+ dialog,
217780
+ agent,
217781
+ resetChat,
217782
+ onUpdateSuccess,
217783
+ handleContentChange,
217784
+ onSubmit,
217785
+ value,
217786
+ handleStartVoice
217787
+ ]);
216770
217788
  const handleSubmit = import_react42.useCallback(async (text) => {
216771
217789
  if (disabled)
216772
217790
  return;
217791
+ if (isVoiceActive || isTranscribing) {
217792
+ await handleStopVoice(true);
217793
+ return;
217794
+ }
216773
217795
  if (showCommandMenu) {
216774
217796
  const command = resolveCommand(selectedIndex);
216775
217797
  handleCommand(command);
@@ -216781,6 +217803,12 @@ var init_input_bar = __esm(async () => {
216781
217803
  const rawTrimmed = text.trim();
216782
217804
  if (rawTrimmed.length === 0)
216783
217805
  return;
217806
+ if (rawTrimmed === "/voice") {
217807
+ setValue("");
217808
+ handleContentChange("");
217809
+ handleStartVoice();
217810
+ return;
217811
+ }
216784
217812
  const trimmed = expandPastes(rawTrimmed);
216785
217813
  defaultPromptHistory.append(trimmed);
216786
217814
  defaultPromptHistory.resetCursor();
@@ -216939,7 +217967,8 @@ var init_input_bar = __esm(async () => {
216939
217967
  onHistoryDown: handleHistoryDown,
216940
217968
  onScrollUp,
216941
217969
  onScrollDown,
216942
- placeholder: grillMode ? "" : planRefineMode ? "Enter feedback to refine plan (or Esc to cancel)..." : interactivePlanGoalMode ? "Enter your goal (e.g. add dark mode) or Esc to cancel..." : placeholder,
217970
+ placeholder: isTranscribing ? "Transcribing speech..." : isVoiceActive ? "Listening..." : grillMode ? "" : planRefineMode ? "Enter feedback to refine plan (or Esc to cancel)..." : interactivePlanGoalMode ? "Enter your goal (e.g. add dark mode) or Esc to cancel..." : placeholder,
217971
+ placeholderColor: isVoiceActive ? THEME.colors.brand : undefined,
216943
217972
  focus: !disabled && !dialog.isOpen,
216944
217973
  disableHistoryNav: showCommandMenu || showFileMenu || showShortcutsMenu
216945
217974
  })
@@ -217254,7 +218283,7 @@ var init_scroll_viewport = __esm(async () => {
217254
218283
  });
217255
218284
 
217256
218285
  // ../../packages/tui/src/utils/terminal-link.ts
217257
- import path31 from "node:path";
218286
+ import path34 from "node:path";
217258
218287
  function terminalLink(text, uri) {
217259
218288
  if (!uri)
217260
218289
  return text;
@@ -217263,7 +218292,7 @@ function terminalLink(text, uri) {
217263
218292
  function fileLink(displayPath, targetPath, line, col) {
217264
218293
  if (!targetPath)
217265
218294
  return displayPath;
217266
- const absPath = path31.isAbsolute(targetPath) ? targetPath : path31.resolve(process.cwd(), targetPath);
218295
+ const absPath = path34.isAbsolute(targetPath) ? targetPath : path34.resolve(process.cwd(), targetPath);
217267
218296
  const lineFragment = line ? `#L${line}${col ? `:${col}` : ""}` : "";
217268
218297
  const uri = `file://${absPath}${lineFragment}`;
217269
218298
  return terminalLink(displayPath, uri);
@@ -217272,9 +218301,9 @@ function toRelativePath(filePath, cwd = process.cwd()) {
217272
218301
  if (!filePath)
217273
218302
  return "";
217274
218303
  const trimmed = filePath.trim();
217275
- if (!path31.isAbsolute(trimmed))
218304
+ if (!path34.isAbsolute(trimmed))
217276
218305
  return trimmed;
217277
- const rel = path31.relative(cwd, trimmed);
218306
+ const rel = path34.relative(cwd, trimmed);
217278
218307
  if (!rel || rel.startsWith("..")) {
217279
218308
  return trimmed;
217280
218309
  }
@@ -263642,27 +264671,27 @@ ${r}`;
263642
264671
  });
263643
264672
 
263644
264673
  // ../../packages/tui/src/components/header.tsx
263645
- import fs32 from "node:fs";
264674
+ import fs35 from "node:fs";
263646
264675
  import { homedir } from "node:os";
263647
- import path32 from "node:path";
264676
+ import path35 from "node:path";
263648
264677
  function getGitBranch(workspaceRoot = process.cwd()) {
263649
264678
  if (cachedBranch !== undefined) {
263650
264679
  return cachedBranch;
263651
264680
  }
263652
264681
  try {
263653
- const gitPath = path32.resolve(workspaceRoot, ".git");
263654
- if (fs32.existsSync(gitPath)) {
263655
- const stat = fs32.statSync(gitPath);
263656
- let headPath = path32.resolve(gitPath, "HEAD");
264682
+ const gitPath = path35.resolve(workspaceRoot, ".git");
264683
+ if (fs35.existsSync(gitPath)) {
264684
+ const stat = fs35.statSync(gitPath);
264685
+ let headPath = path35.resolve(gitPath, "HEAD");
263657
264686
  if (stat.isFile()) {
263658
- const content = fs32.readFileSync(gitPath, "utf-8").trim();
264687
+ const content = fs35.readFileSync(gitPath, "utf-8").trim();
263659
264688
  if (content.startsWith("gitdir:")) {
263660
264689
  const gitDir = content.slice(7).trim();
263661
- headPath = path32.resolve(workspaceRoot, gitDir, "HEAD");
264690
+ headPath = path35.resolve(workspaceRoot, gitDir, "HEAD");
263662
264691
  }
263663
264692
  }
263664
- if (fs32.existsSync(headPath)) {
263665
- const headContent = fs32.readFileSync(headPath, "utf-8").trim();
264693
+ if (fs35.existsSync(headPath)) {
264694
+ const headContent = fs35.readFileSync(headPath, "utf-8").trim();
263666
264695
  if (headContent.startsWith("ref: refs/heads/")) {
263667
264696
  cachedBranch = headContent.replace("ref: refs/heads/", "").trim();
263668
264697
  return cachedBranch;
@@ -263936,7 +264965,7 @@ var init_message_list = __esm(async () => {
263936
264965
  flexDirection: "column",
263937
264966
  width: "100%",
263938
264967
  children: [
263939
- staticMessages.length > 0 && /* @__PURE__ */ jsx_runtime20.jsx(Static15, {
264968
+ staticMessages.length > 0 && /* @__PURE__ */ jsx_runtime20.jsx(Static16, {
263940
264969
  items: staticMessages,
263941
264970
  style: { width: "100%" },
263942
264971
  children: (msg, index) => renderSingleMessage(msg, index, staticMessages, cliVersion, latestVersion, userEmail, expandCommands, announcement)
@@ -264504,6 +265533,7 @@ function ByokKeyMenu(props) {
264504
265533
  const { selectedProvider, apiKey, setApiKey, handleKeySubmit, isStreaming, authError } = props;
264505
265534
  const formattedProvider = formatProviderName2(selectedProvider);
264506
265535
  const keyUrl = selectedProvider ? PROVIDER_KEY_URLS[selectedProvider] : undefined;
265536
+ const keyNote = selectedProvider ? PROVIDER_KEY_NOTES[selectedProvider.toLowerCase().trim()] : undefined;
264507
265537
  return /* @__PURE__ */ jsx_runtime25.jsxs(Box_default, {
264508
265538
  flexDirection: "column",
264509
265539
  paddingX: THEME.padding.paddingX,
@@ -264528,7 +265558,8 @@ function ByokKeyMenu(props) {
264528
265558
  /* @__PURE__ */ jsx_runtime25.jsx(Text3, {
264529
265559
  color: THEME.colors.brand,
264530
265560
  children: keyUrl
264531
- })
265561
+ }),
265562
+ keyNote
264532
265563
  ]
264533
265564
  })
264534
265565
  ]
@@ -264573,7 +265604,7 @@ function ByokKeyMenu(props) {
264573
265604
  ]
264574
265605
  });
264575
265606
  }
264576
- var jsx_runtime25, PROVIDER_NAMES, PROVIDER_KEY_URLS;
265607
+ var jsx_runtime25, PROVIDER_NAMES, PROVIDER_KEY_URLS, PROVIDER_KEY_NOTES;
264577
265608
  var init_byok_key_menu = __esm(async () => {
264578
265609
  init_theme();
264579
265610
  await __promiseAll([
@@ -264619,6 +265650,7 @@ var init_byok_key_menu = __esm(async () => {
264619
265650
  december: "December Cloud",
264620
265651
  december_proxy: "December Cloud",
264621
265652
  deepseek: "DeepSeek",
265653
+ deepgram: "Deepgram",
264622
265654
  fireworks: "Fireworks AI",
264623
265655
  google: "Google",
264624
265656
  gemini: "Google AI Studio",
@@ -264737,6 +265769,7 @@ var init_byok_key_menu = __esm(async () => {
264737
265769
  deepinfra: "https://deepinfra.com/dash/api_keys",
264738
265770
  deepseek: "https://platform.deepseek.com/api_keys",
264739
265771
  digitalocean: "https://cloud.digitalocean.com/account/api/tokens",
265772
+ deepgram: "https://console.deepgram.com",
264740
265773
  dinference: "https://dinference.com/",
264741
265774
  drun: "https://d.run/",
264742
265775
  ebcloud: "https://ebcloud.com/",
@@ -264910,6 +265943,9 @@ var init_byok_key_menu = __esm(async () => {
264910
265943
  zhipu: "https://open.bigmodel.cn/usercenter/apikeys",
264911
265944
  zhipuai: "https://open.bigmodel.cn/usercenter/apikeys"
264912
265945
  };
265946
+ PROVIDER_KEY_NOTES = {
265947
+ deepgram: " ($200 free credit on signup)"
265948
+ };
264913
265949
  });
264914
265950
 
264915
265951
  // ../../packages/tui/src/components/menus/context-select-menu.tsx
@@ -266835,6 +267871,7 @@ function SettingsMainMenu(props) {
266835
267871
  settingsPathGuard = true,
266836
267872
  settingsSteeringMode = "all",
266837
267873
  settingsFollowUpMode = "all",
267874
+ settingsBrowserVisible = true,
266838
267875
  handleSettingsMainSelect
266839
267876
  } = props;
266840
267877
  const providerKey = selectedProvider || activeProvider || "";
@@ -266873,6 +267910,10 @@ function SettingsMainMenu(props) {
266873
267910
  {
266874
267911
  label: `Follow-Up Mode [${settingsFollowUpMode}]`,
266875
267912
  value: "followUpMode"
267913
+ },
267914
+ {
267915
+ label: `Browser Window [${settingsBrowserVisible !== false ? "on" : "off"}]`,
267916
+ value: "browserVisible"
266876
267917
  }
266877
267918
  ];
266878
267919
  return /* @__PURE__ */ jsx_runtime34.jsxs(Box_default, {
@@ -269025,25 +270066,25 @@ var init_global_shortcuts = __esm(async () => {
269025
270066
 
269026
270067
  // ../../packages/tui/src/utils/pager.ts
269027
270068
  import * as cp3 from "node:child_process";
269028
- import fs33 from "node:fs";
269029
- import os18 from "node:os";
269030
- import path33 from "node:path";
270069
+ import fs36 from "node:fs";
270070
+ import os21 from "node:os";
270071
+ import path36 from "node:path";
269031
270072
  function openPlanInPager(planText, title = "december-plan") {
269032
270073
  if (Boolean(process.env.BUN_TEST) || Boolean(process.env.VITEST)) {
269033
270074
  return Promise.resolve();
269034
270075
  }
269035
270076
  return new Promise((resolve) => {
269036
270077
  try {
269037
- const tmpFile = path33.join(os18.tmpdir(), `${title}-${Date.now()}.md`);
269038
- fs33.writeFileSync(tmpFile, planText, "utf8");
269039
- const pager = process.env.PAGER || process.env.EDITOR || (os18.platform() === "win32" ? "notepad" : "less -R");
270078
+ const tmpFile = path36.join(os21.tmpdir(), `${title}-${Date.now()}.md`);
270079
+ fs36.writeFileSync(tmpFile, planText, "utf8");
270080
+ const pager = process.env.PAGER || process.env.EDITOR || (os21.platform() === "win32" ? "notepad" : "less -R");
269040
270081
  const child = cp3.spawn(pager, [tmpFile], {
269041
270082
  stdio: "inherit",
269042
270083
  shell: true
269043
270084
  });
269044
270085
  child.on("exit", () => {
269045
270086
  try {
269046
- fs33.unlinkSync(tmpFile);
270087
+ fs36.unlinkSync(tmpFile);
269047
270088
  } catch {}
269048
270089
  resolve();
269049
270090
  });
@@ -269203,6 +270244,7 @@ function ChatApp({
269203
270244
  onSubmit: handleFormSubmit,
269204
270245
  onScrollUp: handleScrollUp,
269205
270246
  onScrollDown: handleScrollDown,
270247
+ onStartVoice: session.onStartVoice,
269206
270248
  disabled: authMode !== "none" || Boolean(workflowUI) && !session.customInputMode,
269207
270249
  onCopy: () => {
269208
270250
  Promise.resolve().then(() => (init_clipboard(), {})).then((cb) => {
@@ -269579,6 +270621,8 @@ var init_store = __esm(() => {
269579
270621
  setSettingsSteeringMode: (settingsSteeringMode) => set({ settingsSteeringMode }),
269580
270622
  settingsFollowUpMode: "all",
269581
270623
  setSettingsFollowUpMode: (settingsFollowUpMode) => set({ settingsFollowUpMode }),
270624
+ settingsBrowserVisible: true,
270625
+ setSettingsBrowserVisible: (settingsBrowserVisible) => set({ settingsBrowserVisible }),
269582
270626
  tasksData: [],
269583
270627
  setTasksData: (tasksData) => set({ tasksData }),
269584
270628
  taskSelectedIndex: 0,
@@ -269612,9 +270656,9 @@ var init_store = __esm(() => {
269612
270656
  });
269613
270657
 
269614
270658
  // src/store/interceptors.ts
269615
- import { execSync as execSync2 } from "node:child_process";
269616
- import fs34 from "node:fs/promises";
269617
- import path34 from "node:path";
270659
+ import { execSync as execSync4 } from "node:child_process";
270660
+ import fs37 from "node:fs/promises";
270661
+ import path37 from "node:path";
269618
270662
  async function computeToolCallDiff(toolCall) {
269619
270663
  if (toolCall.input?.diff) {
269620
270664
  return toolCall.input.diff;
@@ -269623,11 +270667,11 @@ async function computeToolCallDiff(toolCall) {
269623
270667
  if (!targetPath)
269624
270668
  return;
269625
270669
  const cwd = process.cwd();
269626
- const resolved = path34.resolve(cwd, targetPath);
269627
- const relative = path34.relative(cwd, resolved);
270670
+ const resolved = path37.resolve(cwd, targetPath);
270671
+ const relative = path37.relative(cwd, resolved);
269628
270672
  let oldContent = "";
269629
270673
  try {
269630
- oldContent = await fs34.readFile(resolved, "utf-8");
270674
+ oldContent = await fs37.readFile(resolved, "utf-8");
269631
270675
  } catch {}
269632
270676
  if (toolCall.name === "write_to_file" || toolCall.name === "write") {
269633
270677
  const newContent = toolCall.input?.CodeContent ?? toolCall.input?.content ?? "";
@@ -269691,9 +270735,9 @@ function setupAgentInterceptors(agent, storeState) {
269691
270735
  ];
269692
270736
  if (fileTools.includes(toolCall.name)) {
269693
270737
  const cwd = process.cwd();
269694
- const resolved = path34.resolve(cwd, targetPath);
269695
- const relative = path34.relative(cwd, resolved);
269696
- const isOutside = relative.startsWith("..") || path34.isAbsolute(relative);
270738
+ const resolved = path37.resolve(cwd, targetPath);
270739
+ const relative = path37.relative(cwd, resolved);
270740
+ const isOutside = relative.startsWith("..") || path37.isAbsolute(relative);
269697
270741
  if (!config.nonWorkspaceAccess && isOutside) {
269698
270742
  return {
269699
270743
  block: true,
@@ -269757,10 +270801,10 @@ function setupAgentInterceptors(agent, storeState) {
269757
270801
  const targetPath = toolCall.input?.TargetFile || toolCall.input?.AbsolutePath || toolCall.input?.filePath || toolCall.input?.path;
269758
270802
  if (targetPath) {
269759
270803
  const cwd = process.cwd();
269760
- const resolved = path34.resolve(cwd, targetPath);
269761
- const relative = path34.relative(cwd, resolved);
270804
+ const resolved = path37.resolve(cwd, targetPath);
270805
+ const relative = path37.relative(cwd, resolved);
269762
270806
  try {
269763
- execSync2(`git ls-files --error-unmatch "${relative}"`, {
270807
+ execSync4(`git ls-files --error-unmatch "${relative}"`, {
269764
270808
  cwd,
269765
270809
  stdio: "pipe"
269766
270810
  });
@@ -269785,7 +270829,7 @@ var init_interceptors = __esm(() => {
269785
270829
  });
269786
270830
 
269787
270831
  // src/utils/direct-shell.ts
269788
- import { spawn as spawn6 } from "node:child_process";
270832
+ import { spawn as spawn7 } from "node:child_process";
269789
270833
  function startDirectCommand(command, options = {}) {
269790
270834
  const { timeoutMs = 20000, serverReadyDelayMs = 2500, onData, onBackground } = options;
269791
270835
  let output = "";
@@ -269795,7 +270839,7 @@ function startDirectCommand(command, options = {}) {
269795
270839
  const isWindows = process.platform === "win32";
269796
270840
  const shellCmd = isWindows ? "cmd.exe" : "/bin/bash";
269797
270841
  const shellArgs = isWindows ? ["/d", "/s", "/c", command] : ["-c", command];
269798
- const child = spawn6(shellCmd, shellArgs, {
270842
+ const child = spawn7(shellCmd, shellArgs, {
269799
270843
  cwd: process.cwd(),
269800
270844
  env: process.env,
269801
270845
  stdio: ["pipe", "pipe", "pipe"],
@@ -269947,15 +270991,15 @@ var init_direct_shell = __esm(() => {
269947
270991
  });
269948
270992
 
269949
270993
  // src/utils/usage-tracker.ts
269950
- import fs35 from "node:fs/promises";
269951
- import path35 from "node:path";
270994
+ import fs38 from "node:fs/promises";
270995
+ import path38 from "node:path";
269952
270996
  function getUsageFilePath() {
269953
- return path35.join(getConfigDir(), "usage.json");
270997
+ return path38.join(getConfigDir(), "usage.json");
269954
270998
  }
269955
270999
  async function loadUsageLedger() {
269956
271000
  const filePath = getUsageFilePath();
269957
271001
  try {
269958
- const raw = await fs35.readFile(filePath, "utf-8");
271002
+ const raw = await fs38.readFile(filePath, "utf-8");
269959
271003
  const parsed = JSON.parse(raw);
269960
271004
  if (parsed && typeof parsed === "object" && parsed.version === 1 && parsed.days) {
269961
271005
  return parsed;
@@ -269968,8 +271012,8 @@ async function loadUsageLedger() {
269968
271012
  }
269969
271013
  async function saveUsageLedger(ledger) {
269970
271014
  const configDir = getConfigDir();
269971
- const filePath = path35.join(configDir, "usage.json");
269972
- const tmpPath = path35.join(configDir, `usage.json.tmp.${Date.now()}`);
271015
+ const filePath = path38.join(configDir, "usage.json");
271016
+ const tmpPath = path38.join(configDir, `usage.json.tmp.${Date.now()}`);
269973
271017
  const cutoffDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
269974
271018
  const prunedDays = {};
269975
271019
  for (const [dateKey, dayRecord] of Object.entries(ledger.days)) {
@@ -269979,12 +271023,12 @@ async function saveUsageLedger(ledger) {
269979
271023
  }
269980
271024
  ledger.days = prunedDays;
269981
271025
  try {
269982
- await fs35.mkdir(configDir, { recursive: true });
269983
- await fs35.writeFile(tmpPath, JSON.stringify(ledger, null, 2), "utf-8");
269984
- await fs35.rename(tmpPath, filePath);
271026
+ await fs38.mkdir(configDir, { recursive: true });
271027
+ await fs38.writeFile(tmpPath, JSON.stringify(ledger, null, 2), "utf-8");
271028
+ await fs38.rename(tmpPath, filePath);
269985
271029
  } catch {
269986
271030
  try {
269987
- await fs35.unlink(tmpPath);
271031
+ await fs38.unlink(tmpPath);
269988
271032
  } catch {}
269989
271033
  }
269990
271034
  }
@@ -271421,14 +272465,14 @@ function extractJsonArray(rawText) {
271421
272465
  }
271422
272466
 
271423
272467
  // src/utils/project-context.ts
271424
- import fs36 from "node:fs";
271425
- import path36 from "node:path";
272468
+ import fs39 from "node:fs";
272469
+ import path39 from "node:path";
271426
272470
  function getProjectContext(workspaceDir = process.cwd()) {
271427
272471
  const parts = [];
271428
272472
  try {
271429
- const pkgPath = path36.join(workspaceDir, "package.json");
271430
- if (fs36.existsSync(pkgPath)) {
271431
- const pkg = JSON.parse(fs36.readFileSync(pkgPath, "utf8"));
272473
+ const pkgPath = path39.join(workspaceDir, "package.json");
272474
+ if (fs39.existsSync(pkgPath)) {
272475
+ const pkg = JSON.parse(fs39.readFileSync(pkgPath, "utf8"));
271432
272476
  const deps = Object.keys(pkg.dependencies || {}).slice(0, 15).join(", ");
271433
272477
  const devDeps = Object.keys(pkg.devDependencies || {}).slice(0, 10).join(", ");
271434
272478
  parts.push(`Project: ${pkg.name || "Unnamed"}`);
@@ -271437,13 +272481,13 @@ function getProjectContext(workspaceDir = process.cwd()) {
271437
272481
  if (devDeps)
271438
272482
  parts.push(`DevDependencies: ${devDeps}`);
271439
272483
  }
271440
- const agentsPath = path36.join(workspaceDir, "AGENTS.md");
271441
- if (fs36.existsSync(agentsPath)) {
271442
- const content = fs36.readFileSync(agentsPath, "utf8");
272484
+ const agentsPath = path39.join(workspaceDir, "AGENTS.md");
272485
+ if (fs39.existsSync(agentsPath)) {
272486
+ const content = fs39.readFileSync(agentsPath, "utf8");
271443
272487
  parts.push(`Guidelines (from AGENTS.md):
271444
272488
  ${content.slice(0, 500)}`);
271445
272489
  }
271446
- const entries = fs36.readdirSync(workspaceDir, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.isDirectory() ? `${e.name}/` : e.name).slice(0, 20);
272490
+ const entries = fs39.readdirSync(workspaceDir, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.isDirectory() ? `${e.name}/` : e.name).slice(0, 20);
271447
272491
  if (entries.length > 0) {
271448
272492
  parts.push(`Workspace layout: ${entries.join(", ")}`);
271449
272493
  }
@@ -271598,14 +272642,14 @@ async function fetchProviderBalance(provider, apiKey, timeoutMs = 2000) {
271598
272642
  }
271599
272643
 
271600
272644
  // src/utils/formatters.ts
271601
- import path37 from "node:path";
272645
+ import path40 from "node:path";
271602
272646
  function toRelativePath2(filePath, cwd = process.cwd()) {
271603
272647
  if (!filePath)
271604
272648
  return "";
271605
272649
  const trimmed = filePath.trim();
271606
- if (!path37.isAbsolute(trimmed))
272650
+ if (!path40.isAbsolute(trimmed))
271607
272651
  return trimmed;
271608
- const rel = path37.relative(cwd, trimmed);
272652
+ const rel = path40.relative(cwd, trimmed);
271609
272653
  if (!rel || rel.startsWith("..")) {
271610
272654
  return trimmed;
271611
272655
  }
@@ -272328,6 +273372,30 @@ ${formatSubscriptionCard(bundle, targetModel)}`
272328
273372
  if (!trimmedKey)
272329
273373
  return;
272330
273374
  const selectedProvider = useCliStore.getState().selectedProvider;
273375
+ if (selectedProvider === "deepgram") {
273376
+ setIsStreaming(true);
273377
+ setAuthError(null);
273378
+ try {
273379
+ const res = await fetch("https://api.deepgram.com/v1/projects", {
273380
+ headers: { Authorization: `Token ${trimmedKey}` }
273381
+ });
273382
+ if (!res.ok) {
273383
+ throw new Error("Invalid Deepgram API key. Please check your key from console.deepgram.com.");
273384
+ }
273385
+ const config = await loadConfig();
273386
+ config.providers = config.providers || {};
273387
+ config.providers.deepgram = trimmedKey;
273388
+ await saveConfig(config);
273389
+ setApiKey("");
273390
+ setAuthMode("none");
273391
+ addToast("Saved Deepgram API key for voice dictation.", "success");
273392
+ } catch (err) {
273393
+ setAuthError(err.message || "Failed to verify Deepgram API key.");
273394
+ } finally {
273395
+ setIsStreaming(false);
273396
+ }
273397
+ return;
273398
+ }
272331
273399
  setIsStreaming(true);
272332
273400
  setAuthError(null);
272333
273401
  let testProvider;
@@ -272684,6 +273752,8 @@ function useSettingsHandlers() {
272684
273752
  setSettingsSteeringMode,
272685
273753
  settingsFollowUpMode,
272686
273754
  setSettingsFollowUpMode,
273755
+ settingsBrowserVisible,
273756
+ setSettingsBrowserVisible,
272687
273757
  settingsAuthPriority,
272688
273758
  setSettingsAuthPriority,
272689
273759
  setAuthMethod,
@@ -272694,7 +273764,7 @@ function useSettingsHandlers() {
272694
273764
  setOllamaModels,
272695
273765
  agent,
272696
273766
  addToast
272697
- } = useCliStore();
273767
+ } = useCliStore.getState();
272698
273768
  const handleSettingsMainSelect = async (item) => {
272699
273769
  const config = await loadConfig();
272700
273770
  let updated = false;
@@ -272802,6 +273872,15 @@ function useSettingsHandlers() {
272802
273872
  updated = true;
272803
273873
  break;
272804
273874
  }
273875
+ case "browserVisible": {
273876
+ const current = useCliStore.getState().settingsBrowserVisible;
273877
+ const nextVal = !(current !== false);
273878
+ config.browserVisible = nextVal;
273879
+ setSettingsBrowserVisible(nextVal);
273880
+ addToast(`Browser window set to ${nextVal ? "visible (on)" : "headless (off)"}`);
273881
+ updated = true;
273882
+ break;
273883
+ }
272805
273884
  case "authPriority": {
272806
273885
  const priorities = [
272807
273886
  "subscription",
@@ -272858,6 +273937,503 @@ var init_use_settings_handlers = __esm(() => {
272858
273937
  init_provider_factory();
272859
273938
  });
272860
273939
 
273940
+ // src/utils/audio/detector.ts
273941
+ import { execSync as execSync5 } from "node:child_process";
273942
+ function checkBinary(name) {
273943
+ try {
273944
+ const isWindows = process.platform === "win32";
273945
+ const checkCmd = isWindows ? `where ${name}` : `which ${name}`;
273946
+ const stdout = execSync5(checkCmd, {
273947
+ stdio: ["ignore", "pipe", "ignore"],
273948
+ encoding: "utf8"
273949
+ }).trim();
273950
+ return stdout ? stdout.split(`
273951
+ `)[0].trim() : null;
273952
+ } catch {
273953
+ return null;
273954
+ }
273955
+ }
273956
+ function detectAudioBinary() {
273957
+ const ffmpegPath = checkBinary("ffmpeg");
273958
+ if (ffmpegPath) {
273959
+ return { available: true, binary: "ffmpeg", path: ffmpegPath };
273960
+ }
273961
+ const soxPath = checkBinary("rec") || checkBinary("sox");
273962
+ if (soxPath) {
273963
+ return { available: true, binary: "sox", path: soxPath };
273964
+ }
273965
+ if (process.platform === "linux") {
273966
+ const arecordPath = checkBinary("arecord");
273967
+ if (arecordPath) {
273968
+ return { available: true, binary: "arecord", path: arecordPath };
273969
+ }
273970
+ }
273971
+ return {
273972
+ available: false,
273973
+ binary: null,
273974
+ error: process.platform === "darwin" ? 'No audio recording binary found. Please install ffmpeg or sox (e.g. "brew install ffmpeg").' : process.platform === "linux" ? 'No audio recording binary found. Please install ffmpeg, sox, or alsa-utils (e.g. "sudo apt install ffmpeg").' : "No audio recording binary found. Please install ffmpeg or sox in your system PATH."
273975
+ };
273976
+ }
273977
+ var init_detector2 = () => {};
273978
+
273979
+ // src/utils/audio/recorder.ts
273980
+ import { spawn as spawn8 } from "node:child_process";
273981
+ import { EventEmitter as EventEmitter4 } from "node:events";
273982
+ var AudioRecorder;
273983
+ var init_recorder = __esm(() => {
273984
+ init_detector2();
273985
+ AudioRecorder = class AudioRecorder extends EventEmitter4 {
273986
+ process = null;
273987
+ isRecording = false;
273988
+ start(options = {}) {
273989
+ if (this.isRecording)
273990
+ return true;
273991
+ const detection = detectAudioBinary();
273992
+ if (!detection.available || !detection.binary) {
273993
+ this.emit("error", new Error(detection.error || "Audio recording binary not found."));
273994
+ return false;
273995
+ }
273996
+ const sampleRate = options.sampleRate ?? 16000;
273997
+ const channels = options.channels ?? 1;
273998
+ try {
273999
+ this.process = this.spawnRecorderProcess(detection.binary, sampleRate, channels);
274000
+ this.isRecording = true;
274001
+ this.process.stdout.on("data", (chunk) => {
274002
+ this.emit("data", chunk);
274003
+ });
274004
+ this.process.stderr.on("data", (data) => {
274005
+ const msg = data.toString();
274006
+ if (msg.includes("No such device") || msg.includes("Device or resource busy")) {
274007
+ this.emit("error", new Error(msg.trim()));
274008
+ }
274009
+ });
274010
+ this.process.on("error", (err) => {
274011
+ this.emit("error", err);
274012
+ this.stop();
274013
+ });
274014
+ this.process.on("close", (code) => {
274015
+ this.isRecording = false;
274016
+ this.process = null;
274017
+ this.emit("close", code);
274018
+ });
274019
+ return true;
274020
+ } catch (err) {
274021
+ const error = err instanceof Error ? err : new Error(String(err));
274022
+ this.emit("error", error);
274023
+ this.stop();
274024
+ return false;
274025
+ }
274026
+ }
274027
+ spawnRecorderProcess(binary, sampleRate, channels) {
274028
+ if (binary === "ffmpeg") {
274029
+ const isLinux = process.platform === "linux";
274030
+ const isMac = process.platform === "darwin";
274031
+ const isWindows = process.platform === "win32";
274032
+ const args = [
274033
+ "-loglevel",
274034
+ "error",
274035
+ "-fflags",
274036
+ "nobuffer",
274037
+ "-flags",
274038
+ "low_delay"
274039
+ ];
274040
+ if (isLinux) {
274041
+ args.push("-f", "pulse", "-i", "default");
274042
+ } else if (isMac) {
274043
+ args.push("-f", "avfoundation", "-i", ":default");
274044
+ } else if (isWindows) {
274045
+ args.push("-f", "dshow", "-i", "audio=default");
274046
+ }
274047
+ args.push("-ac", channels.toString(), "-ar", sampleRate.toString(), "-f", "s16le", "-flush_packets", "1", "-");
274048
+ return spawn8("ffmpeg", args);
274049
+ }
274050
+ if (binary === "sox") {
274051
+ const detection = detectAudioBinary();
274052
+ const binaryName = detection.path?.endsWith("rec") ? "rec" : "sox";
274053
+ const args = binaryName === "rec" ? [
274054
+ "-q",
274055
+ "-c",
274056
+ channels.toString(),
274057
+ "-r",
274058
+ sampleRate.toString(),
274059
+ "-b",
274060
+ "16",
274061
+ "-e",
274062
+ "signed",
274063
+ "-t",
274064
+ "raw",
274065
+ "-"
274066
+ ] : [
274067
+ "-d",
274068
+ "-q",
274069
+ "-c",
274070
+ channels.toString(),
274071
+ "-r",
274072
+ sampleRate.toString(),
274073
+ "-b",
274074
+ "16",
274075
+ "-e",
274076
+ "signed",
274077
+ "-t",
274078
+ "raw",
274079
+ "-"
274080
+ ];
274081
+ return spawn8(binaryName, args);
274082
+ }
274083
+ return spawn8("arecord", [
274084
+ "-q",
274085
+ "-c",
274086
+ channels.toString(),
274087
+ "-r",
274088
+ sampleRate.toString(),
274089
+ "-f",
274090
+ "S16_LE",
274091
+ "-t",
274092
+ "raw"
274093
+ ]);
274094
+ }
274095
+ stop() {
274096
+ if (!this.isRecording && !this.process)
274097
+ return;
274098
+ this.isRecording = false;
274099
+ if (this.process) {
274100
+ try {
274101
+ this.process.kill("SIGTERM");
274102
+ } catch {}
274103
+ this.process = null;
274104
+ }
274105
+ }
274106
+ get recording() {
274107
+ return this.isRecording;
274108
+ }
274109
+ };
274110
+ });
274111
+
274112
+ // src/utils/audio/stt.ts
274113
+ import dns from "node:dns";
274114
+ async function resolveSTTKey() {
274115
+ const envDeepgram = process.env.DEEPGRAM_API_KEY?.trim();
274116
+ if (envDeepgram)
274117
+ return { provider: "deepgram", key: envDeepgram };
274118
+ const config = await loadConfig();
274119
+ const configDeepgram = config.providers?.deepgram?.trim();
274120
+ if (configDeepgram)
274121
+ return { provider: "deepgram", key: configDeepgram };
274122
+ return { provider: null, key: null };
274123
+ }
274124
+ function appendTranscript(base, next) {
274125
+ const trimmedBase = base.trim();
274126
+ const trimmedNext = next.trim();
274127
+ if (!trimmedBase)
274128
+ return trimmedNext;
274129
+ if (!trimmedNext)
274130
+ return trimmedBase;
274131
+ if (/^[.,!?;:]/.test(trimmedNext)) {
274132
+ return `${trimmedBase}${trimmedNext}`;
274133
+ }
274134
+ const baseWords = trimmedBase.split(/\s+/);
274135
+ const nextWords = trimmedNext.split(/\s+/);
274136
+ const maxOverlap = Math.min(3, baseWords.length, nextWords.length);
274137
+ let overlapCount = 0;
274138
+ const cleanWord = (w) => w.toLowerCase().replace(/[^a-z0-9]/g, "");
274139
+ for (let count = maxOverlap;count >= 1; count--) {
274140
+ const baseSlice = baseWords.slice(baseWords.length - count).map(cleanWord);
274141
+ const nextSlice = nextWords.slice(0, count).map(cleanWord);
274142
+ if (baseSlice.length === nextSlice.length && baseSlice.every((w, i) => w.length > 0 && w === nextSlice[i])) {
274143
+ overlapCount = count;
274144
+ break;
274145
+ }
274146
+ }
274147
+ if (overlapCount > 0) {
274148
+ const remainingNextWords = nextWords.slice(overlapCount);
274149
+ if (remainingNextWords.length === 0) {
274150
+ return trimmedBase;
274151
+ }
274152
+ return `${trimmedBase} ${remainingNextWords.join(" ")}`;
274153
+ }
274154
+ return `${trimmedBase} ${trimmedNext}`;
274155
+ }
274156
+
274157
+ class STTSession {
274158
+ key = null;
274159
+ ws = null;
274160
+ finalTranscript = "";
274161
+ interimTranscript = "";
274162
+ pendingChunks = [];
274163
+ heartbeatTimer = null;
274164
+ onTranscript;
274165
+ onError;
274166
+ isClosed = false;
274167
+ constructor(options) {
274168
+ this.onTranscript = options.onTranscript;
274169
+ this.onError = options.onError;
274170
+ }
274171
+ async init() {
274172
+ const resolved = await resolveSTTKey();
274173
+ if (!resolved.key) {
274174
+ this.onError(new Error("No Deepgram API key found. Set DEEPGRAM_API_KEY or configure via BYOK key menu."));
274175
+ return false;
274176
+ }
274177
+ this.key = resolved.key;
274178
+ return this.initDeepgram(this.key);
274179
+ }
274180
+ initDeepgram(key) {
274181
+ return new Promise((resolve) => {
274182
+ try {
274183
+ const url = "wss://api.deepgram.com/v1/listen?model=nova-2&smart_format=true&interim_results=true&endpointing=300&encoding=linear16&sample_rate=16000&channels=1";
274184
+ this.ws = new WebSocket(url, ["token", key]);
274185
+ let isHandshakeComplete = false;
274186
+ const handshakeTimeout = setTimeout(() => {
274187
+ if (!isHandshakeComplete) {
274188
+ isHandshakeComplete = true;
274189
+ try {
274190
+ if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
274191
+ this.ws.close();
274192
+ }
274193
+ } catch {}
274194
+ this.ws = null;
274195
+ this.onError(new Error("Deepgram WebSocket connection timed out. Check your internet connection."));
274196
+ resolve(false);
274197
+ }
274198
+ }, 1e4);
274199
+ this.ws.onopen = () => {
274200
+ if (isHandshakeComplete)
274201
+ return;
274202
+ isHandshakeComplete = true;
274203
+ clearTimeout(handshakeTimeout);
274204
+ this.startHeartbeat();
274205
+ this.flushPendingChunks();
274206
+ resolve(true);
274207
+ };
274208
+ this.ws.onmessage = (event) => {
274209
+ if (this.isClosed)
274210
+ return;
274211
+ try {
274212
+ const data = typeof event.data === "string" ? JSON.parse(event.data) : null;
274213
+ if (!data || data.type !== "Results")
274214
+ return;
274215
+ const alt = data.channel?.alternatives?.[0];
274216
+ const transcript = alt?.transcript?.trim();
274217
+ if (!transcript) {
274218
+ if (data.is_final) {
274219
+ this.interimTranscript = "";
274220
+ }
274221
+ return;
274222
+ }
274223
+ if (data.is_final) {
274224
+ this.finalTranscript = appendTranscript(this.finalTranscript, transcript);
274225
+ this.interimTranscript = "";
274226
+ this.onTranscript(this.finalTranscript, true);
274227
+ } else {
274228
+ this.interimTranscript = transcript;
274229
+ const fullText = appendTranscript(this.finalTranscript, this.interimTranscript);
274230
+ this.onTranscript(fullText, false);
274231
+ }
274232
+ } catch {}
274233
+ };
274234
+ this.ws.onerror = (_err) => {
274235
+ if (!isHandshakeComplete) {
274236
+ isHandshakeComplete = true;
274237
+ clearTimeout(handshakeTimeout);
274238
+ this.onError(new Error("Deepgram WebSocket connection error."));
274239
+ resolve(false);
274240
+ return;
274241
+ }
274242
+ if (!this.isClosed) {
274243
+ this.onError(new Error("Deepgram WebSocket connection error."));
274244
+ }
274245
+ };
274246
+ this.ws.onclose = () => {
274247
+ this.stopHeartbeat();
274248
+ };
274249
+ } catch (err) {
274250
+ const error = err instanceof Error ? err : new Error(String(err));
274251
+ this.onError(error);
274252
+ resolve(false);
274253
+ }
274254
+ });
274255
+ }
274256
+ sendAudioChunk(chunk) {
274257
+ if (this.isClosed)
274258
+ return;
274259
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
274260
+ this.ws.send(chunk);
274261
+ } else if (!this.ws || this.ws.readyState === WebSocket.CONNECTING) {
274262
+ this.pendingChunks.push(chunk);
274263
+ }
274264
+ }
274265
+ flushPendingChunks() {
274266
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
274267
+ return;
274268
+ while (this.pendingChunks.length > 0) {
274269
+ const chunk = this.pendingChunks.shift();
274270
+ if (chunk) {
274271
+ try {
274272
+ this.ws.send(chunk);
274273
+ } catch {}
274274
+ }
274275
+ }
274276
+ }
274277
+ startHeartbeat() {
274278
+ this.stopHeartbeat();
274279
+ this.heartbeatTimer = setInterval(() => {
274280
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
274281
+ try {
274282
+ this.ws.send(JSON.stringify({ type: "KeepAlive" }));
274283
+ } catch {}
274284
+ }
274285
+ }, 4000);
274286
+ }
274287
+ stopHeartbeat() {
274288
+ if (this.heartbeatTimer) {
274289
+ clearInterval(this.heartbeatTimer);
274290
+ this.heartbeatTimer = null;
274291
+ }
274292
+ }
274293
+ async finish() {
274294
+ this.isClosed = true;
274295
+ this.stopHeartbeat();
274296
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
274297
+ try {
274298
+ this.ws.send(JSON.stringify({ type: "CloseStream" }));
274299
+ } catch {}
274300
+ await new Promise((resolve) => setTimeout(resolve, 150));
274301
+ try {
274302
+ if (this.ws.readyState === WebSocket.OPEN) {
274303
+ this.ws.close();
274304
+ }
274305
+ } catch {}
274306
+ this.ws = null;
274307
+ } else if (this.ws) {
274308
+ try {
274309
+ this.ws.close();
274310
+ } catch {}
274311
+ this.ws = null;
274312
+ }
274313
+ this.pendingChunks = [];
274314
+ const fullText = appendTranscript(this.finalTranscript, this.interimTranscript);
274315
+ return fullText.trim();
274316
+ }
274317
+ cancel() {
274318
+ this.isClosed = true;
274319
+ this.stopHeartbeat();
274320
+ this.pendingChunks = [];
274321
+ if (this.ws) {
274322
+ try {
274323
+ this.ws.close();
274324
+ } catch {}
274325
+ this.ws = null;
274326
+ }
274327
+ this.finalTranscript = "";
274328
+ this.interimTranscript = "";
274329
+ }
274330
+ }
274331
+ var init_stt = __esm(() => {
274332
+ init_config();
274333
+ try {
274334
+ dns.setDefaultResultOrder?.("ipv4first");
274335
+ } catch {}
274336
+ });
274337
+
274338
+ // src/utils/audio/index.ts
274339
+ async function isVoiceConfigured() {
274340
+ const binaryDetection = detectAudioBinary();
274341
+ const sttConfig = await resolveSTTKey();
274342
+ if (!binaryDetection.available) {
274343
+ return {
274344
+ available: false,
274345
+ hasBinary: false,
274346
+ hasKey: Boolean(sttConfig.key),
274347
+ error: binaryDetection.error
274348
+ };
274349
+ }
274350
+ if (!sttConfig.key) {
274351
+ return {
274352
+ available: false,
274353
+ hasBinary: true,
274354
+ hasKey: false,
274355
+ binary: binaryDetection.binary,
274356
+ error: "Deepgram API key not configured."
274357
+ };
274358
+ }
274359
+ return {
274360
+ available: true,
274361
+ hasBinary: true,
274362
+ hasKey: true,
274363
+ binary: binaryDetection.binary,
274364
+ provider: "deepgram"
274365
+ };
274366
+ }
274367
+ function getVoiceSetupNotice(status) {
274368
+ if (!status.hasBinary) {
274369
+ return [
274370
+ "Voice dictation requires an audio recording tool.",
274371
+ "",
274372
+ "Install:",
274373
+ " • macOS: brew install ffmpeg",
274374
+ " • Linux: sudo apt install ffmpeg"
274375
+ ].join(`
274376
+ `);
274377
+ }
274378
+ return [
274379
+ "Voice dictation requires a Deepgram API key for real-time live streaming.",
274380
+ "",
274381
+ "Get a free key ($200 credit) at https://console.deepgram.com",
274382
+ "",
274383
+ "Configure key:",
274384
+ " december key deepgram <key>"
274385
+ ].join(`
274386
+ `);
274387
+ }
274388
+ async function startVoiceDictation(options) {
274389
+ const configCheck = await isVoiceConfigured();
274390
+ if (!configCheck.available) {
274391
+ options.onError(new Error(configCheck.error || "Voice dictation is not available."));
274392
+ return null;
274393
+ }
274394
+ const stt = new STTSession({
274395
+ onTranscript: options.onTranscript,
274396
+ onError: options.onError
274397
+ });
274398
+ const initialized = await stt.init();
274399
+ if (!initialized) {
274400
+ return null;
274401
+ }
274402
+ const recorder = new AudioRecorder;
274403
+ recorder.on("data", (chunk) => {
274404
+ stt.sendAudioChunk(chunk);
274405
+ });
274406
+ recorder.on("error", (err) => {
274407
+ options.onError(err);
274408
+ stt.cancel();
274409
+ });
274410
+ const started = recorder.start();
274411
+ if (!started) {
274412
+ stt.cancel();
274413
+ return null;
274414
+ }
274415
+ return {
274416
+ stop: async () => {
274417
+ recorder.stop();
274418
+ return await stt.finish();
274419
+ },
274420
+ cancel: () => {
274421
+ recorder.stop();
274422
+ stt.cancel();
274423
+ },
274424
+ get isRecording() {
274425
+ return recorder.recording;
274426
+ }
274427
+ };
274428
+ }
274429
+ var init_audio2 = __esm(() => {
274430
+ init_detector2();
274431
+ init_recorder();
274432
+ init_stt();
274433
+ init_detector2();
274434
+ init_recorder();
274435
+ });
274436
+
272861
274437
  // src/hooks/use-agent-session.ts
272862
274438
  var exports_use_agent_session = {};
272863
274439
  __export(exports_use_agent_session, {
@@ -272993,7 +274569,9 @@ function useAgentSession({
272993
274569
  settingsSteeringMode,
272994
274570
  setSettingsSteeringMode,
272995
274571
  settingsFollowUpMode,
272996
- setSettingsFollowUpMode
274572
+ setSettingsFollowUpMode,
274573
+ settingsBrowserVisible,
274574
+ setSettingsBrowserVisible
272997
274575
  } = state;
272998
274576
  const [expandCommands, setExpandCommands] = import_react71.useState(false);
272999
274577
  const toggleExpandCommands = import_react71.useCallback(() => {
@@ -274046,6 +275624,36 @@ ${decStatus}
274046
275624
  }
274047
275625
  return;
274048
275626
  }
275627
+ if (text.trim() === "/voice") {
275628
+ await Promise.resolve().then(() => init_audio2());
275629
+ const status = await isVoiceConfigured();
275630
+ if (!status.available) {
275631
+ const userMsg = {
275632
+ id: getNextMsgId(),
275633
+ role: "user",
275634
+ displayText: "/voice",
275635
+ text: "/voice"
275636
+ };
275637
+ const noticeMsg = {
275638
+ id: getNextMsgId(),
275639
+ role: "assistant",
275640
+ blocks: [
275641
+ {
275642
+ type: "text",
275643
+ content: getVoiceSetupNotice(status)
275644
+ }
275645
+ ]
275646
+ };
275647
+ setStaticMessages((prev) => [
275648
+ ...prev,
275649
+ ...useCliStore.getState().activeMessages,
275650
+ userMsg,
275651
+ noticeMsg
275652
+ ]);
275653
+ setActiveMessages([]);
275654
+ return;
275655
+ }
275656
+ }
274049
275657
  if (text.trim() === "/copy") {
274050
275658
  try {
274051
275659
  await init_src7();
@@ -274433,6 +276041,7 @@ ${decStatus}
274433
276041
  setSettingsThinkingLevel(config.thinkingLevel ?? "auto");
274434
276042
  setSettingsSteeringMode(config.steeringMode ?? "all");
274435
276043
  setSettingsFollowUpMode(config.followUpMode ?? "all");
276044
+ setSettingsBrowserVisible(config.browserVisible !== false);
274436
276045
  setAuthMode("settings_main");
274437
276046
  });
274438
276047
  return;
@@ -274837,7 +276446,46 @@ If it doesn't open automatically, please click here:
274837
276446
  setStaticMessages
274838
276447
  ]);
274839
276448
  const handleContextSelect = () => {};
276449
+ const handleStartVoice = import_react71.useCallback(async (callbacks) => {
276450
+ await Promise.resolve().then(() => init_audio2());
276451
+ const status = await isVoiceConfigured();
276452
+ if (!status.hasBinary) {
276453
+ const userMsg = {
276454
+ id: getNextMsgId(),
276455
+ role: "user",
276456
+ displayText: "/voice",
276457
+ text: "/voice"
276458
+ };
276459
+ const noticeMsg = {
276460
+ id: getNextMsgId(),
276461
+ role: "assistant",
276462
+ blocks: [
276463
+ {
276464
+ type: "text",
276465
+ content: getVoiceSetupNotice(status)
276466
+ }
276467
+ ]
276468
+ };
276469
+ setStaticMessages((prev) => [
276470
+ ...prev,
276471
+ ...useCliStore.getState().activeMessages,
276472
+ userMsg,
276473
+ noticeMsg
276474
+ ]);
276475
+ setActiveMessages([]);
276476
+ return null;
276477
+ }
276478
+ if (!status.hasKey) {
276479
+ useCliStore.getState().setSelectedProvider("deepgram");
276480
+ useCliStore.getState().setApiKey("");
276481
+ useCliStore.getState().setAuthError(null);
276482
+ useCliStore.getState().setAuthMode("byok_key");
276483
+ return null;
276484
+ }
276485
+ return await startVoiceDictation(callbacks);
276486
+ }, [setStaticMessages, setActiveMessages]);
274840
276487
  return {
276488
+ onStartVoice: handleStartVoice,
274841
276489
  currentPlannedPrompt,
274842
276490
  setCurrentPlannedPrompt,
274843
276491
  currentPlanText,
@@ -274932,6 +276580,8 @@ If it doesn't open automatically, please click here:
274932
276580
  setSettingsSteeringMode,
274933
276581
  settingsFollowUpMode,
274934
276582
  setSettingsFollowUpMode,
276583
+ settingsBrowserVisible,
276584
+ setSettingsBrowserVisible,
274935
276585
  handleSubmit,
274936
276586
  sessionRepository,
274937
276587
  handleSettingsMainSelect,
@@ -275008,7 +276658,7 @@ var init_use_agent_session = __esm(async () => {
275008
276658
 
275009
276659
  // src/index.ts
275010
276660
  init_package();
275011
- import path38 from "node:path";
276661
+ import path41 from "node:path";
275012
276662
 
275013
276663
  // src/args.ts
275014
276664
  import { parseArgs } from "node:util";
@@ -275146,12 +276796,11 @@ Options:
275146
276796
  }
275147
276797
  // src/constants/release-highlights.json
275148
276798
  var release_highlights_default = {
275149
- version: "0.4.4",
275150
- title: "What's New in v0.4.4",
276799
+ version: "0.4.5",
276800
+ title: "What's New in v0.4.5",
275151
276801
  bullets: [
275152
- "*(agent)* Passive workspace memory ingestion and manage_memory tool",
275153
- "*(agent)* Lsp client integration for definition and reference navigation",
275154
- "*(tools)* Prefer non-import lines when resolving symbol definition in code_intelligence"
276802
+ "*(cli,tui)* Add voice input dictation and workspace trust verification",
276803
+ "*(tools,cli)* Interactive cdp browser engine and browser_action tool (#506, #509)"
275155
276804
  ],
275156
276805
  url: "https://trydecember.com/docs/changelog"
275157
276806
  };
@@ -275161,6 +276810,7 @@ init_env_sanitizer();
275161
276810
  init_env_sanitizer();
275162
276811
  init_commands();
275163
276812
  init_headless_runner();
276813
+ init_workspace_trust();
275164
276814
  purgeProjectEnvApiKeys();
275165
276815
  async function readPipedStdin() {
275166
276816
  if (process.stdin.isTTY)
@@ -275279,6 +276929,11 @@ To define project-specific instructions, create an AGENTS.md file in your projec
275279
276929
  }
275280
276930
  parsedArgs.prompt = question;
275281
276931
  }
276932
+ await Promise.resolve().then(() => init_workspace_trust());
276933
+ await ensureWorkspaceTrust({
276934
+ targetDir: parsedArgs.scope,
276935
+ isHeadless: Boolean(parsedArgs.prompt)
276936
+ });
275282
276937
  if (parsedArgs.prompt) {
275283
276938
  const [
275284
276939
  { AgentHarness },
@@ -275352,7 +277007,7 @@ To define project-specific instructions, create an AGENTS.md file in your projec
275352
277007
  sessionRepository,
275353
277008
  sessionId,
275354
277009
  runtime: "cli",
275355
- workspaceDir: parsedArgs.scope ? path38.resolve(process.cwd(), parsedArgs.scope) : process.cwd(),
277010
+ workspaceDir: parsedArgs.scope ? path41.resolve(process.cwd(), parsedArgs.scope) : process.cwd(),
275356
277011
  thinkingLevel: config.thinkingLevel || "auto",
275357
277012
  steeringMode: config.steeringMode || "all",
275358
277013
  followUpMode: config.followUpMode || "all"
@@ -275456,7 +277111,7 @@ Executing Headless Task: "${parsedArgs.prompt}"
275456
277111
  sessionRepository,
275457
277112
  sessionId,
275458
277113
  runtime: "cli",
275459
- workspaceDir: parsedArgs.scope ? path38.resolve(process.cwd(), parsedArgs.scope) : process.cwd(),
277114
+ workspaceDir: parsedArgs.scope ? path41.resolve(process.cwd(), parsedArgs.scope) : process.cwd(),
275460
277115
  thinkingLevel: config.thinkingLevel || "auto",
275461
277116
  steeringMode: config.steeringMode || "all",
275462
277117
  followUpMode: config.followUpMode || "all"
@@ -275541,6 +277196,8 @@ Executing Headless Task: "${parsedArgs.prompt}"
275541
277196
  }
275542
277197
  main().catch(console.error);
275543
277198
  export {
277199
+ addTrustedWorkspace,
277200
+ ensureWorkspaceTrust,
275544
277201
  getHelpText,
275545
277202
  handleAuthCommand,
275546
277203
  handleDoctorCommand,
@@ -275550,9 +277207,11 @@ export {
275550
277207
  handleLogoutCommand,
275551
277208
  handleSwitchCommand,
275552
277209
  handleUpdateCommand,
277210
+ isWorkspaceTrusted,
275553
277211
  parseCliArgs,
275554
277212
  purgeProjectEnvApiKeys,
275555
277213
  readPipedStdin,
277214
+ resolveWorkspaceRoot,
275556
277215
  restoreConsole,
275557
277216
  runHeadlessTask,
275558
277217
  suppressConsole