@phenx-inc/ctlsurf 0.16.1 → 0.17.1

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 (37) hide show
  1. package/bin/ctlsurf-worker.js +39 -6
  2. package/out/headless/index.mjs +549 -116
  3. package/out/headless/index.mjs.map +4 -4
  4. package/out/main/index.js +416 -38
  5. package/out/preload/index.js +3 -0
  6. package/out/renderer/assets/{cssMode-DwHD6JSn.js → cssMode-BtQxI2Eb.js} +3 -3
  7. package/out/renderer/assets/{freemarker2-Na9-bXYw.js → freemarker2-DRW5dmY5.js} +1 -1
  8. package/out/renderer/assets/{handlebars-D7gzKLS8.js → handlebars-CFLF3cP7.js} +1 -1
  9. package/out/renderer/assets/{html-DDK7xN27.js → html-DtnBOkwp.js} +1 -1
  10. package/out/renderer/assets/{htmlMode-BfbLv-NB.js → htmlMode-DMVysDCg.js} +3 -3
  11. package/out/renderer/assets/{index-DID1LlMU.css → index-CEjwKjtd.css} +45 -0
  12. package/out/renderer/assets/{index-HoDaBul3.js → index-Dm70hk5s.js} +252 -26
  13. package/out/renderer/assets/{javascript-DJjWOxID.js → javascript-ChlyA1sf.js} +2 -2
  14. package/out/renderer/assets/{jsonMode-BileWbig.js → jsonMode-D1_oHzeN.js} +3 -3
  15. package/out/renderer/assets/{liquid-DvNU_dI9.js → liquid-D4ftdy1G.js} +1 -1
  16. package/out/renderer/assets/{lspLanguageFeatures-Bb27EY8c.js → lspLanguageFeatures-Ckqu5ubG.js} +1 -1
  17. package/out/renderer/assets/{mdx-CLkNPVgD.js → mdx-B9wsVYSC.js} +1 -1
  18. package/out/renderer/assets/{python-DOJdL4kC.js → python-e23tD6IS.js} +1 -1
  19. package/out/renderer/assets/{razor-CSr8UlJc.js → razor-CrDyXj3d.js} +1 -1
  20. package/out/renderer/assets/{tsMode-sbsAvk21.js → tsMode-BwGC5o4O.js} +1 -1
  21. package/out/renderer/assets/{typescript-r5QB4yRW.js → typescript-Do-98d_O.js} +1 -1
  22. package/out/renderer/assets/{xml-CYhKh3Pi.js → xml-Dpdu5R3m.js} +1 -1
  23. package/out/renderer/assets/{yaml-B8DsoaYW.js → yaml-B0Zt7LCJ.js} +1 -1
  24. package/out/renderer/index.html +2 -2
  25. package/package.json +1 -1
  26. package/scripts/extract-electron.js +105 -0
  27. package/src/main/headless.ts +12 -0
  28. package/src/main/index.ts +8 -0
  29. package/src/main/monitor.ts +540 -0
  30. package/src/main/orchestrator.ts +10 -0
  31. package/src/main/pty.ts +5 -0
  32. package/src/main/tui.ts +37 -3
  33. package/src/preload/index.ts +4 -0
  34. package/src/renderer/App.tsx +9 -1
  35. package/src/renderer/components/Resources.tsx +217 -0
  36. package/src/renderer/components/StatusBar.tsx +6 -1
  37. package/src/renderer/styles.css +45 -0
@@ -6386,15 +6386,15 @@ function redactSensitive(body) {
6386
6386
  }
6387
6387
  return null;
6388
6388
  }
6389
- async function checkCredentialsFileSafety(path12, onWarn = (m) => console.warn(`anthropic-sdk: ${m}`)) {
6389
+ async function checkCredentialsFileSafety(path13, onWarn = (m) => console.warn(`anthropic-sdk: ${m}`)) {
6390
6390
  if (typeof process === "undefined" || process.platform === "win32")
6391
6391
  return;
6392
- const fs8 = await import("node:fs");
6393
- let resolved = path12;
6392
+ const fs9 = await import("node:fs");
6393
+ let resolved = path13;
6394
6394
  let st;
6395
6395
  try {
6396
- resolved = await fs8.promises.realpath(path12);
6397
- st = await fs8.promises.stat(resolved);
6396
+ resolved = await fs9.promises.realpath(path13);
6397
+ st = await fs9.promises.stat(resolved);
6398
6398
  } catch {
6399
6399
  return;
6400
6400
  }
@@ -6410,27 +6410,27 @@ async function checkCredentialsFileSafety(path12, onWarn = (m) => console.warn(`
6410
6410
  }
6411
6411
  }
6412
6412
  async function writeCredentialsFileAtomic(targetPath, data) {
6413
- const fs8 = await import("node:fs");
6414
- const path12 = await import("node:path");
6415
- const dir = path12.dirname(targetPath);
6416
- await fs8.promises.mkdir(dir, { recursive: true, mode: 448 });
6413
+ const fs9 = await import("node:fs");
6414
+ const path13 = await import("node:path");
6415
+ const dir = path13.dirname(targetPath);
6416
+ await fs9.promises.mkdir(dir, { recursive: true, mode: 448 });
6417
6417
  const tmpPath = `${targetPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
6418
6418
  try {
6419
- const fh = await fs8.promises.open(tmpPath, "w", 384);
6419
+ const fh = await fs9.promises.open(tmpPath, "w", 384);
6420
6420
  try {
6421
6421
  await fh.writeFile(JSON.stringify(data, null, 2));
6422
6422
  await fh.sync();
6423
6423
  } finally {
6424
6424
  await fh.close();
6425
6425
  }
6426
- await fs8.promises.rename(tmpPath, targetPath);
6426
+ await fs9.promises.rename(tmpPath, targetPath);
6427
6427
  } catch (err) {
6428
- await fs8.promises.unlink(tmpPath).catch(() => {
6428
+ await fs9.promises.unlink(tmpPath).catch(() => {
6429
6429
  });
6430
6430
  throw err;
6431
6431
  }
6432
6432
  try {
6433
- const dirFh = await fs8.promises.open(dir, "r");
6433
+ const dirFh = await fs9.promises.open(dir, "r");
6434
6434
  try {
6435
6435
  await dirFh.sync();
6436
6436
  } finally {
@@ -6794,12 +6794,12 @@ var init_credentials = __esm({
6794
6794
  return null;
6795
6795
  }
6796
6796
  validateProfileName(profileName);
6797
- const fs8 = await import("node:fs");
6798
- const path12 = await import("node:path");
6799
- const configPath = path12.join(rootConfigPath, "configs", `${profileName}.json`);
6797
+ const fs9 = await import("node:fs");
6798
+ const path13 = await import("node:path");
6799
+ const configPath = path13.join(rootConfigPath, "configs", `${profileName}.json`);
6800
6800
  let configRaw;
6801
6801
  try {
6802
- configRaw = await fs8.promises.readFile(configPath, "utf-8");
6802
+ configRaw = await fs9.promises.readFile(configPath, "utf-8");
6803
6803
  } catch (err) {
6804
6804
  if (err?.code !== "ENOENT") {
6805
6805
  throw new Error(`failed to read config file ${configPath}: ${err}`);
@@ -6880,37 +6880,37 @@ var init_credentials = __esm({
6880
6880
  return null;
6881
6881
  }
6882
6882
  validateProfileName(profileName);
6883
- const path12 = await import("node:path");
6884
- return path12.join(rootConfigPath, "credentials", `${profileName}.json`);
6883
+ const path13 = await import("node:path");
6884
+ return path13.join(rootConfigPath, "credentials", `${profileName}.json`);
6885
6885
  };
6886
6886
  getRootConfigPath = async () => {
6887
6887
  if (!supportsLocalConfigFiles()) {
6888
6888
  return null;
6889
6889
  }
6890
- const path12 = await import("node:path");
6890
+ const path13 = await import("node:path");
6891
6891
  const configDir = readEnv("ANTHROPIC_CONFIG_DIR");
6892
6892
  if (configDir) {
6893
6893
  return configDir;
6894
6894
  }
6895
- const os6 = getPlatformHeaders()["X-Stainless-OS"];
6896
- if (os6 === "Windows") {
6895
+ const os7 = getPlatformHeaders()["X-Stainless-OS"];
6896
+ if (os7 === "Windows") {
6897
6897
  const appData = readEnv("APPDATA");
6898
6898
  if (appData) {
6899
- return path12.join(appData, "Anthropic");
6899
+ return path13.join(appData, "Anthropic");
6900
6900
  }
6901
6901
  const userProfile = readEnv("USERPROFILE");
6902
6902
  if (userProfile) {
6903
- return path12.join(userProfile, "AppData", "Roaming", "Anthropic");
6903
+ return path13.join(userProfile, "AppData", "Roaming", "Anthropic");
6904
6904
  }
6905
6905
  return null;
6906
6906
  }
6907
6907
  const xdgConfigHome = readEnv("XDG_CONFIG_HOME");
6908
6908
  if (xdgConfigHome) {
6909
- return path12.join(xdgConfigHome, "anthropic");
6909
+ return path13.join(xdgConfigHome, "anthropic");
6910
6910
  }
6911
6911
  const home = readEnv("HOME");
6912
6912
  if (home) {
6913
- return path12.join(home, ".config", "anthropic");
6913
+ return path13.join(home, ".config", "anthropic");
6914
6914
  }
6915
6915
  return null;
6916
6916
  };
@@ -6927,11 +6927,11 @@ var init_credentials = __esm({
6927
6927
  if (profileName) {
6928
6928
  return profileName;
6929
6929
  }
6930
- const fs8 = await import("node:fs");
6931
- const path12 = await import("node:path");
6932
- const filePath = path12.join(rootConfigPath, "active_config");
6930
+ const fs9 = await import("node:fs");
6931
+ const path13 = await import("node:path");
6932
+ const filePath = path13.join(rootConfigPath, "active_config");
6933
6933
  try {
6934
- return (await fs8.promises.readFile(filePath, "utf-8")).trim() || "default";
6934
+ return (await fs9.promises.readFile(filePath, "utf-8")).trim() || "default";
6935
6935
  } catch (err) {
6936
6936
  if (err?.code !== "ENOENT") {
6937
6937
  throw new Error(`failed to read ${filePath}: ${err}`);
@@ -6943,21 +6943,21 @@ var init_credentials = __esm({
6943
6943
  });
6944
6944
 
6945
6945
  // node_modules/@anthropic-ai/sdk/lib/credentials/identity-token.mjs
6946
- function identityTokenFromFile(path12) {
6947
- if (!path12) {
6946
+ function identityTokenFromFile(path13) {
6947
+ if (!path13) {
6948
6948
  throw new AnthropicError("Identity token file path is empty");
6949
6949
  }
6950
6950
  return async () => {
6951
- const fs8 = await import("node:fs");
6951
+ const fs9 = await import("node:fs");
6952
6952
  let content;
6953
6953
  try {
6954
- content = await fs8.promises.readFile(path12, "utf-8");
6954
+ content = await fs9.promises.readFile(path13, "utf-8");
6955
6955
  } catch (err) {
6956
- throw new AnthropicError(`Failed to read identity token file at ${path12}: ${err}`);
6956
+ throw new AnthropicError(`Failed to read identity token file at ${path13}: ${err}`);
6957
6957
  }
6958
6958
  const token = content.trim();
6959
6959
  if (!token) {
6960
- throw new AnthropicError(`Identity token file at ${path12} is empty`);
6960
+ throw new AnthropicError(`Identity token file at ${path13} is empty`);
6961
6961
  }
6962
6962
  return token;
6963
6963
  };
@@ -7042,11 +7042,11 @@ var init_oidc_federation = __esm({
7042
7042
  // node_modules/@anthropic-ai/sdk/lib/credentials/user-oauth.mjs
7043
7043
  function userOAuthProvider(config) {
7044
7044
  return async (opts) => {
7045
- const fs8 = await import("node:fs");
7045
+ const fs9 = await import("node:fs");
7046
7046
  await checkCredentialsFileSafety(config.credentialsPath, config.onSafetyWarning);
7047
7047
  let raw;
7048
7048
  try {
7049
- raw = await fs8.promises.readFile(config.credentialsPath, "utf-8");
7049
+ raw = await fs9.promises.readFile(config.credentialsPath, "utf-8");
7050
7050
  } catch (err) {
7051
7051
  throw new WorkloadIdentityError(`Credentials file not found at ${config.credentialsPath}: ${err}`);
7052
7052
  }
@@ -7218,11 +7218,11 @@ function resolveIdentityTokenProvider(auth) {
7218
7218
  }
7219
7219
  function cachedExchangeProvider(exchange, credentialsPath, onCacheWriteError, onSafetyWarning) {
7220
7220
  return async (opts) => {
7221
- const fs8 = await import("node:fs");
7221
+ const fs9 = await import("node:fs");
7222
7222
  await checkCredentialsFileSafety(credentialsPath, onSafetyWarning);
7223
7223
  let existing;
7224
7224
  try {
7225
- const raw = await fs8.promises.readFile(credentialsPath, "utf-8");
7225
+ const raw = await fs9.promises.readFile(credentialsPath, "utf-8");
7226
7226
  existing = JSON.parse(raw);
7227
7227
  const token = existing?.["access_token"];
7228
7228
  if (token && !opts?.forceRefresh) {
@@ -8282,12 +8282,12 @@ var init_path = __esm({
8282
8282
  "node_modules/@anthropic-ai/sdk/internal/utils/path.mjs"() {
8283
8283
  init_error();
8284
8284
  EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
8285
- createPathTagFunction = (pathEncoder = encodeURIPath) => function path12(statics, ...params) {
8285
+ createPathTagFunction = (pathEncoder = encodeURIPath) => function path13(statics, ...params) {
8286
8286
  if (statics.length === 1)
8287
8287
  return statics[0];
8288
8288
  let postPath = false;
8289
8289
  const invalidSegments = [];
8290
- const path13 = statics.reduce((previousValue, currentValue, index) => {
8290
+ const path14 = statics.reduce((previousValue, currentValue, index) => {
8291
8291
  if (/[?#]/.test(currentValue)) {
8292
8292
  postPath = true;
8293
8293
  }
@@ -8304,7 +8304,7 @@ var init_path = __esm({
8304
8304
  }
8305
8305
  return previousValue + currentValue + (index === params.length ? "" : encoded);
8306
8306
  }, "");
8307
- const pathOnly = path13.split(/[?#]/, 1)[0];
8307
+ const pathOnly = path14.split(/[?#]/, 1)[0];
8308
8308
  const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
8309
8309
  let match;
8310
8310
  while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
@@ -8325,10 +8325,10 @@ var init_path = __esm({
8325
8325
  }, "");
8326
8326
  throw new AnthropicError(`Path parameters result in path with invalid segments:
8327
8327
  ${invalidSegments.map((e) => e.error).join("\n")}
8328
- ${path13}
8328
+ ${path14}
8329
8329
  ${underline}`);
8330
8330
  }
8331
- return path13;
8331
+ return path14;
8332
8332
  };
8333
8333
  path5 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
8334
8334
  }
@@ -9706,15 +9706,15 @@ var require_dist = __commonJS({
9706
9706
  return `v1,${expectedSignature}`;
9707
9707
  }
9708
9708
  verifyTimestamp(timestampHeader) {
9709
- const now = Math.floor(Date.now() / 1e3);
9709
+ const now2 = Math.floor(Date.now() / 1e3);
9710
9710
  const timestamp = parseInt(timestampHeader, 10);
9711
9711
  if (isNaN(timestamp)) {
9712
9712
  throw new WebhookVerificationError("Invalid Signature Headers");
9713
9713
  }
9714
- if (now - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) {
9714
+ if (now2 - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) {
9715
9715
  throw new WebhookVerificationError("Message timestamp too old");
9716
9716
  }
9717
- if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {
9717
+ if (timestamp > now2 + WEBHOOK_TOLERANCE_IN_SECONDS) {
9718
9718
  throw new WebhookVerificationError("Message timestamp too new");
9719
9719
  }
9720
9720
  return new Date(timestamp * 1e3);
@@ -16818,9 +16818,9 @@ var init_client = __esm({
16818
16818
  makeStatusError(status, error, message, headers) {
16819
16819
  return APIError.generate(status, error, message, headers);
16820
16820
  }
16821
- buildURL(path12, query, defaultBaseURL) {
16821
+ buildURL(path13, query, defaultBaseURL) {
16822
16822
  const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
16823
- const url = isAbsoluteURL(path12) ? new URL(path12) : new URL(baseURL + (baseURL.endsWith("/") && path12.startsWith("/") ? path12.slice(1) : path12));
16823
+ const url = isAbsoluteURL(path13) ? new URL(path13) : new URL(baseURL + (baseURL.endsWith("/") && path13.startsWith("/") ? path13.slice(1) : path13));
16824
16824
  const defaultQuery = this.defaultQuery();
16825
16825
  const pathQuery = Object.fromEntries(url.searchParams);
16826
16826
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
@@ -16892,24 +16892,24 @@ var init_client = __esm({
16892
16892
  backendMiddleware() {
16893
16893
  return [];
16894
16894
  }
16895
- get(path12, opts) {
16896
- return this.methodRequest("get", path12, opts);
16895
+ get(path13, opts) {
16896
+ return this.methodRequest("get", path13, opts);
16897
16897
  }
16898
- post(path12, opts) {
16899
- return this.methodRequest("post", path12, opts);
16898
+ post(path13, opts) {
16899
+ return this.methodRequest("post", path13, opts);
16900
16900
  }
16901
- patch(path12, opts) {
16902
- return this.methodRequest("patch", path12, opts);
16901
+ patch(path13, opts) {
16902
+ return this.methodRequest("patch", path13, opts);
16903
16903
  }
16904
- put(path12, opts) {
16905
- return this.methodRequest("put", path12, opts);
16904
+ put(path13, opts) {
16905
+ return this.methodRequest("put", path13, opts);
16906
16906
  }
16907
- delete(path12, opts) {
16908
- return this.methodRequest("delete", path12, opts);
16907
+ delete(path13, opts) {
16908
+ return this.methodRequest("delete", path13, opts);
16909
16909
  }
16910
- methodRequest(method, path12, opts) {
16910
+ methodRequest(method, path13, opts) {
16911
16911
  return this.request(Promise.resolve(opts).then((opts2) => {
16912
- return { method, path: path12, ...opts2 };
16912
+ return { method, path: path13, ...opts2 };
16913
16913
  }));
16914
16914
  }
16915
16915
  request(options, remainingRetries = null) {
@@ -17023,8 +17023,8 @@ var init_client = __esm({
17023
17023
  }));
17024
17024
  return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
17025
17025
  }
17026
- getAPIList(path12, Page2, opts) {
17027
- return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path12, ...opts2 })) : { method: "get", path: path12, ...opts });
17026
+ getAPIList(path13, Page2, opts) {
17027
+ return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path13, ...opts2 })) : { method: "get", path: path13, ...opts });
17028
17028
  }
17029
17029
  requestAPIList(Page2, options) {
17030
17030
  const request = this.makeRequest(options, null, void 0);
@@ -17140,14 +17140,14 @@ var init_client = __esm({
17140
17140
  }
17141
17141
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
17142
17142
  const options = { ...inputOptions };
17143
- const { method, path: path12, query, defaultBaseURL } = options;
17143
+ const { method, path: path13, query, defaultBaseURL } = options;
17144
17144
  if (this._authState.resolution) {
17145
17145
  await this._authState.resolution;
17146
17146
  }
17147
17147
  if (!this._baseURLIsExplicit && this._authState.baseURL && this.baseURL !== this._authState.baseURL) {
17148
17148
  this.baseURL = this._authState.baseURL;
17149
17149
  }
17150
- const url = this.buildURL(path12, query, defaultBaseURL);
17150
+ const url = this.buildURL(path13, query, defaultBaseURL);
17151
17151
  if ("timeout" in options)
17152
17152
  validatePositiveInteger("timeout", options.timeout);
17153
17153
  options.timeout = options.timeout ?? this.timeout;
@@ -17288,19 +17288,19 @@ var init_sdk = __esm({
17288
17288
  // node_modules/electron/index.js
17289
17289
  var require_electron = __commonJS({
17290
17290
  "node_modules/electron/index.js"(exports, module) {
17291
- var fs8 = __require("fs");
17292
- var path12 = __require("path");
17293
- var pathFile = path12.join(__dirname, "path.txt");
17291
+ var fs9 = __require("fs");
17292
+ var path13 = __require("path");
17293
+ var pathFile = path13.join(__dirname, "path.txt");
17294
17294
  function getElectronPath() {
17295
17295
  let executablePath;
17296
- if (fs8.existsSync(pathFile)) {
17297
- executablePath = fs8.readFileSync(pathFile, "utf-8");
17296
+ if (fs9.existsSync(pathFile)) {
17297
+ executablePath = fs9.readFileSync(pathFile, "utf-8");
17298
17298
  }
17299
17299
  if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
17300
- return path12.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || "electron");
17300
+ return path13.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || "electron");
17301
17301
  }
17302
17302
  if (executablePath) {
17303
- return path12.join(__dirname, "dist", executablePath);
17303
+ return path13.join(__dirname, "dist", executablePath);
17304
17304
  } else {
17305
17305
  throw new Error("Electron failed to install correctly, please delete node_modules/electron and try installing again");
17306
17306
  }
@@ -17314,7 +17314,7 @@ var require_package = __commonJS({
17314
17314
  "package.json"(exports, module) {
17315
17315
  module.exports = {
17316
17316
  name: "@phenx-inc/ctlsurf",
17317
- version: "0.16.1",
17317
+ version: "0.17.1",
17318
17318
  description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
17319
17319
  main: "out/main/index.js",
17320
17320
  bin: {
@@ -17452,6 +17452,10 @@ var PtyManager = class {
17452
17452
  this.process = null;
17453
17453
  });
17454
17454
  }
17455
+ /** OS pid of the spawned agent (or ssh), null once it has exited. */
17456
+ get pid() {
17457
+ return this.process?.pid ?? null;
17458
+ }
17455
17459
  write(data) {
17456
17460
  this.process?.write(data);
17457
17461
  }
@@ -17564,8 +17568,8 @@ var CtlsurfApi = class {
17564
17568
  }
17565
17569
  return h;
17566
17570
  }
17567
- async request(method, path12, body) {
17568
- const url = `${this.baseUrl}${path12}`;
17571
+ async request(method, path13, body) {
17572
+ const url = `${this.baseUrl}${path13}`;
17569
17573
  const opts = {
17570
17574
  method,
17571
17575
  headers: this.headers()
@@ -17576,7 +17580,7 @@ var CtlsurfApi = class {
17576
17580
  const res = await fetch(url, opts);
17577
17581
  if (!res.ok) {
17578
17582
  const text = await res.text();
17579
- throw new Error(`ctlsurf API ${method} ${path12}: ${res.status} ${text}`);
17583
+ throw new Error(`ctlsurf API ${method} ${path13}: ${res.status} ${text}`);
17580
17584
  }
17581
17585
  return res.json();
17582
17586
  }
@@ -17747,16 +17751,16 @@ var ConversationBridge = class {
17747
17751
  this.outputFlushTimer = null;
17748
17752
  if (this.outputBuffer.length === 0) return;
17749
17753
  const candidate = this.terminalCapture.currentCandidate();
17750
- const now = Date.now();
17754
+ const now2 = Date.now();
17751
17755
  if (candidate.length === 0) {
17752
17756
  this.outputBuffer = "";
17753
17757
  return;
17754
17758
  }
17755
17759
  if (candidate !== this.pendingCandidate) {
17756
17760
  this.pendingCandidate = candidate;
17757
- this.pendingCandidateChangedAt = now;
17761
+ this.pendingCandidateChangedAt = now2;
17758
17762
  }
17759
- if (now - this.pendingCandidateChangedAt >= OUTPUT_STABLE_MS) {
17763
+ if (now2 - this.pendingCandidateChangedAt >= OUTPUT_STABLE_MS) {
17760
17764
  this.flush();
17761
17765
  return;
17762
17766
  }
@@ -18271,8 +18275,8 @@ function mostRecentClaudeFile(cwd, root) {
18271
18275
  }
18272
18276
  function recentCodexRollouts(root) {
18273
18277
  const dirs = /* @__PURE__ */ new Set();
18274
- const now = Date.now();
18275
- for (const ms of [now, now - 24 * 60 * 60 * 1e3]) {
18278
+ const now2 = Date.now();
18279
+ for (const ms of [now2, now2 - 24 * 60 * 60 * 1e3]) {
18276
18280
  const d = new Date(ms);
18277
18281
  const yyyy = String(d.getFullYear());
18278
18282
  const mm = String(d.getMonth() + 1).padStart(2, "0");
@@ -18386,9 +18390,9 @@ var AgentStateMonitor = class {
18386
18390
  tabs = /* @__PURE__ */ new Map();
18387
18391
  timer = null;
18388
18392
  lastAnyAwaiting = false;
18389
- constructor(sink, now = Date.now) {
18393
+ constructor(sink, now2 = Date.now) {
18390
18394
  this.sink = sink;
18391
- this.now = now;
18395
+ this.now = now2;
18392
18396
  }
18393
18397
  start() {
18394
18398
  if (this.timer) return;
@@ -18404,14 +18408,14 @@ var AgentStateMonitor = class {
18404
18408
  }
18405
18409
  /** Register a tab so its raw PTY output can be classified. */
18406
18410
  trackTab(tabId, agentId, cwd) {
18407
- const now = this.now();
18411
+ const now2 = this.now();
18408
18412
  this.tabs.set(tabId, {
18409
18413
  agentId,
18410
18414
  cwd,
18411
18415
  state: "idle",
18412
18416
  everBusy: false,
18413
18417
  lastBusyAt: 0,
18414
- lastOutputAt: now,
18418
+ lastOutputAt: now2,
18415
18419
  lastDecisionAt: 0,
18416
18420
  hookState: null,
18417
18421
  hookAt: 0,
@@ -18425,16 +18429,16 @@ var AgentStateMonitor = class {
18425
18429
  feed(tabId, data) {
18426
18430
  const rec = this.tabs.get(tabId);
18427
18431
  if (!rec) return;
18428
- const now = this.now();
18429
- rec.lastOutputAt = now;
18432
+ const now2 = this.now();
18433
+ rec.lastOutputAt = now2;
18430
18434
  const text = rec.tail + stripAnsi2(data);
18431
18435
  rec.tail = text.length > TAIL_KEEP_CHARS ? text.slice(-TAIL_KEEP_CHARS) : text;
18432
18436
  if (hasBusyMarker(text)) {
18433
- rec.lastBusyAt = now;
18437
+ rec.lastBusyAt = now2;
18434
18438
  rec.everBusy = true;
18435
18439
  }
18436
18440
  if (hasApprovalMarker(text)) {
18437
- rec.lastDecisionAt = now;
18441
+ rec.lastDecisionAt = now2;
18438
18442
  }
18439
18443
  }
18440
18444
  /**
@@ -18451,9 +18455,9 @@ var AgentStateMonitor = class {
18451
18455
  if (!/[\r\n]/.test(data)) return;
18452
18456
  const rec = this.tabs.get(tabId);
18453
18457
  if (!rec) return;
18454
- const now = this.now();
18458
+ const now2 = this.now();
18455
18459
  rec.everBusy = false;
18456
- rec.lastBusyAt = now;
18460
+ rec.lastBusyAt = now2;
18457
18461
  rec.lastDecisionAt = 0;
18458
18462
  rec.hookState = null;
18459
18463
  this.setState(rec, tabId, "busy");
@@ -18467,28 +18471,28 @@ var AgentStateMonitor = class {
18467
18471
  */
18468
18472
  applyHookEvent(cwd, kind) {
18469
18473
  const target = stripTrailingSep2(cwd);
18470
- const now = this.now();
18474
+ const now2 = this.now();
18471
18475
  for (const [tabId, rec] of this.tabs) {
18472
18476
  if (stripTrailingSep2(rec.cwd) !== target) continue;
18473
18477
  rec.hookState = kind;
18474
- rec.hookAt = now;
18478
+ rec.hookAt = now2;
18475
18479
  if (kind === "busy") {
18476
18480
  rec.everBusy = false;
18477
- rec.lastBusyAt = now;
18481
+ rec.lastBusyAt = now2;
18478
18482
  }
18479
18483
  this.setState(rec, tabId, kind);
18480
18484
  }
18481
18485
  }
18482
18486
  tick() {
18483
- const now = this.now();
18487
+ const now2 = this.now();
18484
18488
  for (const [tabId, rec] of this.tabs) {
18485
- if (rec.hookState && now - rec.hookAt < HOOK_AUTHORITY_MS) {
18489
+ if (rec.hookState && now2 - rec.hookAt < HOOK_AUTHORITY_MS) {
18486
18490
  this.setState(rec, tabId, rec.hookState);
18487
18491
  continue;
18488
18492
  }
18489
- const busyRecently = rec.lastBusyAt > 0 && now - rec.lastBusyAt < BUSY_GRACE_MS;
18490
- const settled = now - rec.lastOutputAt >= SETTLE_MS;
18491
- const decisionRecent = rec.lastDecisionAt > 0 && now - rec.lastDecisionAt < DECISION_TTL_MS;
18493
+ const busyRecently = rec.lastBusyAt > 0 && now2 - rec.lastBusyAt < BUSY_GRACE_MS;
18494
+ const settled = now2 - rec.lastOutputAt >= SETTLE_MS;
18495
+ const decisionRecent = rec.lastDecisionAt > 0 && now2 - rec.lastDecisionAt < DECISION_TTL_MS;
18492
18496
  let next = null;
18493
18497
  if (busyRecently) {
18494
18498
  next = "busy";
@@ -18909,16 +18913,16 @@ var TimeTracker = class {
18909
18913
  recordActivity(tabId) {
18910
18914
  const s = this.sessions.get(tabId);
18911
18915
  if (!s || s.ended) return;
18912
- const now = Date.now();
18913
- if (!isSameLocalDay(s.startedAt, now)) {
18916
+ const now2 = Date.now();
18917
+ if (!isSameLocalDay(s.startedAt, now2)) {
18914
18918
  void this.rollover(tabId);
18915
18919
  return;
18916
18920
  }
18917
- const delta = now - s.lastActivity;
18921
+ const delta = now2 - s.lastActivity;
18918
18922
  if (delta < s.idleTimeoutMs) {
18919
18923
  s.activeMs += delta;
18920
18924
  }
18921
- s.lastActivity = now;
18925
+ s.lastActivity = now2;
18922
18926
  }
18923
18927
  async rollover(tabId) {
18924
18928
  if (this.rollingOver.has(tabId)) return;
@@ -19562,10 +19566,10 @@ var SpanTracker = class {
19562
19566
  lastAccountAt = 0;
19563
19567
  activityMs = { agent: 0, human: 0, idle: 0 };
19564
19568
  started = false;
19565
- constructor(session, sink, now = Date.now) {
19569
+ constructor(session, sink, now2 = Date.now) {
19566
19570
  this.session = session;
19567
19571
  this.sink = sink;
19568
- this.now = now;
19572
+ this.now = now2;
19569
19573
  }
19570
19574
  /** Begin attribution at session start. */
19571
19575
  begin() {
@@ -20020,10 +20024,10 @@ var ObserverBudget = class {
20020
20024
  capUsd;
20021
20025
  date;
20022
20026
  spentUsd = 0;
20023
- constructor(dir, capUsd, now = () => /* @__PURE__ */ new Date()) {
20027
+ constructor(dir, capUsd, now2 = () => /* @__PURE__ */ new Date()) {
20024
20028
  this.filePath = path4.join(dir, "budget.json");
20025
20029
  this.capUsd = capUsd;
20026
- this.date = localDay(now());
20030
+ this.date = localDay(now2());
20027
20031
  try {
20028
20032
  const raw = JSON.parse(fs3.readFileSync(this.filePath, "utf-8"));
20029
20033
  if (raw?.date === this.date && typeof raw.spentUsd === "number") {
@@ -20032,8 +20036,8 @@ var ObserverBudget = class {
20032
20036
  } catch {
20033
20037
  }
20034
20038
  }
20035
- addUsage(model, usage, now = () => /* @__PURE__ */ new Date()) {
20036
- const today = localDay(now());
20039
+ addUsage(model, usage, now2 = () => /* @__PURE__ */ new Date()) {
20040
+ const today = localDay(now2());
20037
20041
  if (today !== this.date) {
20038
20042
  this.date = today;
20039
20043
  this.spentUsd = 0;
@@ -20045,8 +20049,8 @@ var ObserverBudget = class {
20045
20049
  } catch {
20046
20050
  }
20047
20051
  }
20048
- overCap(now = () => /* @__PURE__ */ new Date()) {
20049
- if (localDay(now()) !== this.date) return false;
20052
+ overCap(now2 = () => /* @__PURE__ */ new Date()) {
20053
+ if (localDay(now2()) !== this.date) return false;
20050
20054
  return this.spentUsd >= this.capUsd;
20051
20055
  }
20052
20056
  get spent() {
@@ -21234,6 +21238,15 @@ var Orchestrator = class {
21234
21238
  getTabIds() {
21235
21239
  return [...this.tabs.keys()];
21236
21240
  }
21241
+ // Every open tab's agent and PTY pid, for the resource monitor (monitor.ts).
21242
+ getTabProcesses() {
21243
+ return [...this.tabs].map(([tabId, tab]) => ({
21244
+ tabId,
21245
+ agent: tab.agent,
21246
+ pid: tab.ptyManager.pid,
21247
+ active: tabId === this.activeTabId
21248
+ }));
21249
+ }
21237
21250
  // ─── Tracking control (active tab) ──────────────
21238
21251
  isActiveTabTracking() {
21239
21252
  if (!this.activeTabId) return false;
@@ -21439,6 +21452,7 @@ var FG_TITLE = `${CSI}38;2;192;202;245m`;
21439
21452
  var BG_MODAL = `${CSI}48;2;31;35;53m`;
21440
21453
  var BG_SELECTED = `${CSI}48;2;42;43;61m`;
21441
21454
  var RESET = `${CSI}0m`;
21455
+ var TITLE_QUIET_MS = 50;
21442
21456
  var Tui = class {
21443
21457
  rows = 0;
21444
21458
  cols = 0;
@@ -21447,8 +21461,13 @@ var Tui = class {
21447
21461
  cwd: "",
21448
21462
  wsStatus: "disconnected",
21449
21463
  workerId: null,
21450
- mode: "terminal"
21464
+ mode: "terminal",
21465
+ resources: ""
21451
21466
  };
21467
+ lastPtyWrite = 0;
21468
+ pendingTitle = null;
21469
+ lastTitle = "";
21470
+ titleTimer = null;
21452
21471
  constructor() {
21453
21472
  this.rows = process.stdout.rows || 24;
21454
21473
  this.cols = process.stdout.columns || 80;
@@ -21464,6 +21483,8 @@ var Tui = class {
21464
21483
  * Restore terminal to normal state
21465
21484
  */
21466
21485
  destroy() {
21486
+ if (this.titleTimer) clearTimeout(this.titleTimer);
21487
+ this.titleTimer = null;
21467
21488
  this.write(`${CSI}?25h`);
21468
21489
  }
21469
21490
  /**
@@ -21493,19 +21514,35 @@ var Tui = class {
21493
21514
  * Write PTY output — full passthrough.
21494
21515
  */
21495
21516
  writePtyData(data) {
21517
+ this.lastPtyWrite = Date.now();
21496
21518
  this.write(data);
21497
21519
  }
21498
21520
  /**
21499
21521
  * Update the terminal window/tab title via OSC escape sequence.
21500
21522
  */
21501
21523
  setTerminalTitle(title) {
21502
- this.write(`${ESC}]0;${title}\x07`);
21524
+ this.pendingTitle = title;
21525
+ this.flushTitle();
21526
+ }
21527
+ flushTitle() {
21528
+ if (this.pendingTitle === null || this.titleTimer) return;
21529
+ const wait = TITLE_QUIET_MS - (Date.now() - this.lastPtyWrite);
21530
+ if (wait > 0) {
21531
+ this.titleTimer = setTimeout(() => {
21532
+ this.titleTimer = null;
21533
+ this.flushTitle();
21534
+ }, wait);
21535
+ return;
21536
+ }
21537
+ if (this.pendingTitle !== this.lastTitle) this.write(`${ESC}]0;${this.pendingTitle}\x07`);
21538
+ this.lastTitle = this.pendingTitle;
21539
+ this.pendingTitle = null;
21503
21540
  }
21504
21541
  /**
21505
21542
  * Build a title string from current state for the terminal tab.
21506
21543
  */
21507
21544
  updateTerminalTitle() {
21508
- const { agentName, wsStatus, cwd } = this.state;
21545
+ const { agentName, wsStatus, cwd, resources } = this.state;
21509
21546
  const displayCwd = this.shortenPath(cwd);
21510
21547
  const statusIcon = {
21511
21548
  connected: "\u25CF",
@@ -21521,7 +21558,9 @@ var Tui = class {
21521
21558
  pending_approval: "Pending",
21522
21559
  no_project: "No Project"
21523
21560
  }[wsStatus] || wsStatus;
21524
- this.setTerminalTitle(`ctlsurf \xB7 ${agentName} \xB7 ${statusIcon} ${statusLabel} \xB7 ${displayCwd}`);
21561
+ this.setTerminalTitle(
21562
+ `ctlsurf \xB7 ${agentName} \xB7 ${statusIcon} ${statusLabel} \xB7 ${displayCwd}` + (resources ? ` \xB7 ${resources}` : "")
21563
+ );
21525
21564
  }
21526
21565
  /**
21527
21566
  * Show an interactive agent picker modal.
@@ -21696,6 +21735,392 @@ var Tui = class {
21696
21735
  }
21697
21736
  };
21698
21737
 
21738
+ // src/main/monitor.ts
21739
+ import { execFile as execFile2 } from "child_process";
21740
+ import fs8 from "fs";
21741
+ import os6 from "os";
21742
+ import path12 from "path";
21743
+ var FAST_MS = 3e3;
21744
+ var SLOW_MS = 1e4;
21745
+ var IDLE_STOP_MS = 12e4;
21746
+ var HISTORY_MS = 6e5;
21747
+ var FAST_HOLD_MS = 15e3;
21748
+ var USAGE_SLOW_MS = 3e5;
21749
+ var USAGE_FAST_MS = 6e4;
21750
+ var CODEX_TAIL_BYTES = 512 * 1024;
21751
+ function now() {
21752
+ return (/* @__PURE__ */ new Date()).toISOString();
21753
+ }
21754
+ function round(x, digits = 1) {
21755
+ const f = 10 ** digits;
21756
+ return Math.round(x * f) / f;
21757
+ }
21758
+ function run(cmd, args, opts = {}) {
21759
+ return new Promise((resolve4, reject) => {
21760
+ execFile2(cmd, args, { timeout: opts.timeout ?? 5e3, cwd: opts.cwd, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
21761
+ if (err && !stdout) reject(err);
21762
+ else resolve4(stdout);
21763
+ });
21764
+ });
21765
+ }
21766
+ function serviceFor(agent) {
21767
+ const cmd = isRemoteAgent(agent) ? agent.args.slice(2).join(" ") : path12.basename(agent.command);
21768
+ if (/\bcodex\b/.test(cmd)) return "codex";
21769
+ if (/\bclaude\b/.test(cmd)) return "claude";
21770
+ return null;
21771
+ }
21772
+ function cpuTimes() {
21773
+ let idle = 0;
21774
+ let total = 0;
21775
+ for (const c of os6.cpus()) {
21776
+ const t = c.times;
21777
+ idle += t.idle;
21778
+ total += t.user + t.nice + t.sys + t.idle + t.irq;
21779
+ }
21780
+ return { idle, total };
21781
+ }
21782
+ async function memory() {
21783
+ const total = os6.totalmem();
21784
+ let avail = os6.freemem();
21785
+ try {
21786
+ if (process.platform === "darwin") {
21787
+ const out = await run("vm_stat", []);
21788
+ const page = Number(/page size of (\d+) bytes/.exec(out)?.[1]) || 4096;
21789
+ const pages = (name) => Number(new RegExp(`Pages ${name}:\\s+(\\d+)`).exec(out)?.[1] ?? 0);
21790
+ avail = (pages("free") + pages("inactive") + pages("speculative")) * page;
21791
+ } else if (process.platform === "linux") {
21792
+ const m = /MemAvailable:\s+(\d+) kB/.exec(await fs8.promises.readFile("/proc/meminfo", "utf-8"));
21793
+ if (m) avail = Number(m[1]) * 1024;
21794
+ }
21795
+ } catch {
21796
+ }
21797
+ avail = Math.min(avail, total);
21798
+ return { total, used: total - avail, pct: round(100 * (total - avail) / total) };
21799
+ }
21800
+ async function diskUsage() {
21801
+ if (typeof fs8.promises.statfs !== "function") return null;
21802
+ try {
21803
+ const s = await fs8.promises.statfs(os6.homedir());
21804
+ return { total: s.blocks * s.bsize, free: s.bavail * s.bsize };
21805
+ } catch {
21806
+ return null;
21807
+ }
21808
+ }
21809
+ async function appleGpu() {
21810
+ if (process.platform !== "darwin") return null;
21811
+ try {
21812
+ const out = await run("ioreg", ["-r", "-d", "1", "-w", "0", "-c", "IOAccelerator"], { timeout: 3e3 });
21813
+ const m = /"Device Utilization %"=(\d+)/.exec(out);
21814
+ return m ? Number(m[1]) : null;
21815
+ } catch {
21816
+ return null;
21817
+ }
21818
+ }
21819
+ async function processTable() {
21820
+ const table = /* @__PURE__ */ new Map();
21821
+ if (process.platform === "win32") return table;
21822
+ try {
21823
+ const out = await run("ps", ["-A", "-o", "pid=,ppid=,pcpu=,rss="]);
21824
+ for (const line of out.split("\n")) {
21825
+ const [pid, ppid, cpu, rss] = line.trim().split(/\s+/).map(Number);
21826
+ if (pid) table.set(pid, { ppid, cpu: cpu || 0, rss: (rss || 0) * 1024 });
21827
+ }
21828
+ } catch {
21829
+ }
21830
+ return table;
21831
+ }
21832
+ function agentProcess(tab, table) {
21833
+ const base = {
21834
+ tabId: tab.tabId,
21835
+ name: tab.agent.name,
21836
+ active: tab.active,
21837
+ remote: isRemoteAgent(tab.agent),
21838
+ service: serviceFor(tab.agent),
21839
+ pid: tab.pid
21840
+ };
21841
+ if (tab.pid == null || !table.has(tab.pid)) return { ...base, cpu: null, mem: null, procs: 0 };
21842
+ const children = /* @__PURE__ */ new Map();
21843
+ for (const [pid, row] of table) {
21844
+ const list = children.get(row.ppid);
21845
+ if (list) list.push(pid);
21846
+ else children.set(row.ppid, [pid]);
21847
+ }
21848
+ let cpu = 0;
21849
+ let mem = 0;
21850
+ let procs = 0;
21851
+ const queue = [tab.pid];
21852
+ while (queue.length) {
21853
+ const pid = queue.pop();
21854
+ const row = table.get(pid);
21855
+ if (!row) continue;
21856
+ cpu += row.cpu;
21857
+ mem += row.rss;
21858
+ procs++;
21859
+ queue.push(...children.get(pid) ?? []);
21860
+ }
21861
+ return { ...base, cpu: round(cpu), mem, procs };
21862
+ }
21863
+ function parseClaudeUsage(text) {
21864
+ const windows = [];
21865
+ for (const line of text.split("\n")) {
21866
+ let m = /^\s*Current session:\s*([\d.]+)% used(?:\s*·\s*resets (.+))?/.exec(line);
21867
+ if (m) {
21868
+ windows.push({ label: "session (5h)", short: "5h", pct: Number(m[1]), resets: m[2]?.trim() || null });
21869
+ continue;
21870
+ }
21871
+ m = /^\s*Current week \((.+?)\):\s*([\d.]+)% used(?:\s*·\s*resets (.+))?/.exec(line);
21872
+ if (m) {
21873
+ const all = m[1].trim().toLowerCase() === "all models";
21874
+ windows.push({
21875
+ label: all ? "week, all models" : `week, ${m[1].trim()}`,
21876
+ short: all ? "wk" : null,
21877
+ pct: Number(m[2]),
21878
+ resets: m[3]?.trim() || null
21879
+ });
21880
+ }
21881
+ }
21882
+ return windows;
21883
+ }
21884
+ async function claudeUsage() {
21885
+ const source = "claude /usage";
21886
+ let text = "";
21887
+ try {
21888
+ const out = await run(
21889
+ "claude",
21890
+ ["-p", "/usage", "--output-format", "json", "--no-session-persistence"],
21891
+ { cwd: os6.tmpdir(), timeout: 4e4 }
21892
+ );
21893
+ text = String(JSON.parse(out || "{}").result ?? "");
21894
+ } catch (err) {
21895
+ const msg = err.code === "ENOENT" ? "claude not found on PATH" : String(err);
21896
+ return { ok: false, windows: [], source, ts: now(), error: msg.slice(0, 200) };
21897
+ }
21898
+ const windows = parseClaudeUsage(text);
21899
+ return windows.length ? { ok: true, windows, source, ts: now() } : { ok: false, windows: [], source, ts: now(), error: text.trim().slice(0, 200) || "no usage reported" };
21900
+ }
21901
+ function findKey(obj, key) {
21902
+ if (Array.isArray(obj)) {
21903
+ for (const v of obj) {
21904
+ const found = findKey(v, key);
21905
+ if (found) return found;
21906
+ }
21907
+ } else if (obj && typeof obj === "object") {
21908
+ const rec = obj;
21909
+ if (rec[key] && typeof rec[key] === "object") return rec[key];
21910
+ for (const v of Object.values(rec)) {
21911
+ const found = findKey(v, key);
21912
+ if (found) return found;
21913
+ }
21914
+ }
21915
+ return null;
21916
+ }
21917
+ function codexWindow(minutes) {
21918
+ const m = Number(minutes);
21919
+ if (!Number.isFinite(m)) return { label: "window", short: "win" };
21920
+ if (m === 300) return { label: "5h", short: "5h" };
21921
+ if (m === 1440) return { label: "day", short: "day" };
21922
+ if (m === 10080) return { label: "week", short: "wk" };
21923
+ const s = m < 1440 ? `${Math.floor(m / 60)}h` : `${Math.floor(m / 1440)}d`;
21924
+ return { label: s, short: s };
21925
+ }
21926
+ async function readTail(file, bytes) {
21927
+ const fh = await fs8.promises.open(file, "r");
21928
+ try {
21929
+ const { size } = await fh.stat();
21930
+ const start = Math.max(0, size - bytes);
21931
+ const buf = Buffer.alloc(size - start);
21932
+ await fh.read(buf, 0, buf.length, start);
21933
+ return buf.toString("utf-8");
21934
+ } finally {
21935
+ await fh.close();
21936
+ }
21937
+ }
21938
+ async function codexSessionFiles(root, limit2) {
21939
+ const sortedDirs = async (dir) => (await fs8.promises.readdir(dir, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory()).map((d) => path12.join(dir, d.name)).sort().reverse();
21940
+ const days = [];
21941
+ for (const y of await sortedDirs(root)) {
21942
+ for (const m of await sortedDirs(y)) {
21943
+ days.push(...await sortedDirs(m));
21944
+ if (days.length >= 14) break;
21945
+ }
21946
+ if (days.length >= 14) break;
21947
+ }
21948
+ const files = [];
21949
+ for (const day of days.slice(0, 14)) {
21950
+ for (const name of await fs8.promises.readdir(day).catch(() => [])) {
21951
+ if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
21952
+ const file = path12.join(day, name);
21953
+ try {
21954
+ files.push({ file, mtime: (await fs8.promises.stat(file)).mtimeMs });
21955
+ } catch {
21956
+ }
21957
+ }
21958
+ }
21959
+ return files.sort((a, b) => b.mtime - a.mtime).slice(0, limit2).map((f) => f.file);
21960
+ }
21961
+ async function codexUsage(home = os6.homedir()) {
21962
+ const source = "your most recent Codex session";
21963
+ for (const file of await codexSessionFiles(path12.join(home, ".codex", "sessions"), 5)) {
21964
+ let lines;
21965
+ try {
21966
+ lines = (await readTail(file, CODEX_TAIL_BYTES)).split("\n");
21967
+ } catch {
21968
+ continue;
21969
+ }
21970
+ for (let i = lines.length - 1; i >= 0; i--) {
21971
+ if (!lines[i].includes('"rate_limits"')) continue;
21972
+ let rl;
21973
+ try {
21974
+ rl = findKey(JSON.parse(lines[i]), "rate_limits");
21975
+ } catch {
21976
+ continue;
21977
+ }
21978
+ if (!rl) continue;
21979
+ const windows = [];
21980
+ for (const key of ["primary", "secondary"]) {
21981
+ const w = rl[key];
21982
+ if (!w || typeof w !== "object" || w.used_percent == null) continue;
21983
+ const { label, short } = codexWindow(w.window_minutes);
21984
+ const resets = typeof w.resets_at === "number" ? new Date(w.resets_at * 1e3).toLocaleString(void 0, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : null;
21985
+ windows.push({ label, short, pct: Number(w.used_percent), resets });
21986
+ }
21987
+ if (windows.length) {
21988
+ const ts = new Date((await fs8.promises.stat(file)).mtimeMs).toISOString();
21989
+ return { ok: true, windows, plan: rl.plan_type ?? null, source, ts };
21990
+ }
21991
+ }
21992
+ }
21993
+ return { ok: false, windows: [], source, ts: now(), error: "no Codex session with usage yet" };
21994
+ }
21995
+ var FETCH_USAGE = {
21996
+ claude: claudeUsage,
21997
+ codex: () => codexUsage()
21998
+ };
21999
+ var SERVICE_NAME = { claude: "Claude", codex: "Codex" };
22000
+ var ResourceMonitor = class {
22001
+ constructor(tabs) {
22002
+ this.tabs = tabs;
22003
+ }
22004
+ machine = { ok: false, ts: now(), error: "sampling\u2026" };
22005
+ history = [];
22006
+ agents = [];
22007
+ usage = {};
22008
+ usageAt = {};
22009
+ usageBusy = /* @__PURE__ */ new Set();
22010
+ prevCpu = cpuTimes();
22011
+ lastAsk = 0;
22012
+ fastUntil = 0;
22013
+ timer = null;
22014
+ inflight = null;
22015
+ /** Latest sample plus 10 minutes of history. `fast` = the panel is open:
22016
+ * sample every ~3 s and refresh quota every minute. */
22017
+ async snapshot(fast = false) {
22018
+ const t = Date.now();
22019
+ this.lastAsk = t;
22020
+ const speedUp = fast && t >= this.fastUntil;
22021
+ if (fast) this.fastUntil = t + FAST_HOLD_MS;
22022
+ if (!this.timer && !this.inflight || speedUp) await this.tick();
22023
+ else this.refreshUsage(this.tabs());
22024
+ return this.build(fast);
22025
+ }
22026
+ stop() {
22027
+ if (this.timer) clearTimeout(this.timer);
22028
+ this.timer = null;
22029
+ this.lastAsk = 0;
22030
+ }
22031
+ tick() {
22032
+ if (!this.inflight) {
22033
+ if (this.timer) clearTimeout(this.timer);
22034
+ this.timer = null;
22035
+ this.inflight = this.sample().catch((err) => log(`[monitor] sample failed: ${err}`)).finally(() => {
22036
+ this.inflight = null;
22037
+ if (Date.now() - this.lastAsk >= IDLE_STOP_MS) return;
22038
+ this.timer = setTimeout(() => {
22039
+ void this.tick();
22040
+ }, Date.now() < this.fastUntil ? FAST_MS : SLOW_MS);
22041
+ this.timer.unref?.();
22042
+ });
22043
+ }
22044
+ return this.inflight;
22045
+ }
22046
+ async sample() {
22047
+ const tabs = this.tabs();
22048
+ this.refreshUsage(tabs);
22049
+ try {
22050
+ const [mem, disk, gpu, table] = await Promise.all([memory(), diskUsage(), appleGpu(), processTable()]);
22051
+ const cur = cpuTimes();
22052
+ const dTotal = cur.total - this.prevCpu.total;
22053
+ const cpuPct = dTotal > 0 ? round(100 * (1 - (cur.idle - this.prevCpu.idle) / dTotal)) : null;
22054
+ this.prevCpu = cur;
22055
+ this.machine = {
22056
+ ok: true,
22057
+ ts: now(),
22058
+ cpu: { pct: cpuPct, cores: os6.cpus().length, load: os6.loadavg().map((x) => round(x, 2)) },
22059
+ mem,
22060
+ disk,
22061
+ gpu
22062
+ };
22063
+ this.agents = tabs.map((tab) => agentProcess(tab, table));
22064
+ const t = Date.now();
22065
+ this.history.push({ t, cpu: cpuPct, mem: mem.pct, gpu });
22066
+ while (this.history.length && this.history[0].t < t - HISTORY_MS) this.history.shift();
22067
+ } catch (err) {
22068
+ this.machine = { ok: false, ts: now(), error: String(err).slice(0, 200) };
22069
+ }
22070
+ }
22071
+ refreshUsage(tabs) {
22072
+ const every = Date.now() < this.fastUntil ? USAGE_FAST_MS : USAGE_SLOW_MS;
22073
+ for (const s of this.servicesInUse(tabs)) {
22074
+ if (this.usageBusy.has(s) || Date.now() - (this.usageAt[s] ?? 0) < every) continue;
22075
+ this.usageBusy.add(s);
22076
+ FETCH_USAGE[s]().catch((err) => ({ ok: false, windows: [], source: s, ts: now(), error: String(err).slice(0, 200) })).then((u) => {
22077
+ this.usage[s] = u;
22078
+ this.usageAt[s] = Date.now();
22079
+ if (!u.ok) log(`[monitor] ${s} usage unavailable: ${u.error}`);
22080
+ }).finally(() => this.usageBusy.delete(s));
22081
+ }
22082
+ }
22083
+ servicesInUse(tabs) {
22084
+ return [...new Set(tabs.map((t) => serviceFor(t.agent)).filter((s) => s !== null))];
22085
+ }
22086
+ build(fast) {
22087
+ const tabs = this.tabs();
22088
+ const active = tabs.find((t) => t.active);
22089
+ const usage = {};
22090
+ for (const s of this.servicesInUse(tabs)) {
22091
+ if (this.usage[s]) usage[s] = this.usage[s];
22092
+ }
22093
+ return {
22094
+ machine: {
22095
+ ...this.machine,
22096
+ label: process.platform === "darwin" ? "This Mac" : os6.hostname(),
22097
+ history: [...this.history]
22098
+ },
22099
+ agents: this.agents,
22100
+ activeService: active ? serviceFor(active.agent) : null,
22101
+ usage,
22102
+ interval: fast ? FAST_MS : SLOW_MS
22103
+ };
22104
+ }
22105
+ };
22106
+ function summarizeSnapshot(s) {
22107
+ const parts = [];
22108
+ const m = s.machine;
22109
+ if (m.ok) {
22110
+ const res = [
22111
+ m.cpu?.pct != null ? `CPU ${Math.round(m.cpu.pct)}%` : "",
22112
+ m.mem ? `RAM ${Math.round(m.mem.pct)}%` : ""
22113
+ ].filter(Boolean).join(" ");
22114
+ if (res) parts.push(res);
22115
+ }
22116
+ const u = s.activeService ? s.usage[s.activeService] : void 0;
22117
+ if (s.activeService && u?.ok) {
22118
+ const wins = u.windows.filter((w) => w.short).map((w) => `${w.short} ${Math.round(w.pct)}%`);
22119
+ if (wins.length) parts.push(`${SERVICE_NAME[s.activeService]} ${wins.join(" ")}`);
22120
+ }
22121
+ return parts.join(" \xB7 ");
22122
+ }
22123
+
21699
22124
  // src/main/updateCheck.ts
21700
22125
  import https from "https";
21701
22126
  var NPM_PACKAGE = "@phenx-inc/ctlsurf";
@@ -21880,6 +22305,13 @@ async function main() {
21880
22305
  const ptySize = tui.getPtySize();
21881
22306
  await orchestrator.spawnAgent(HEADLESS_TAB, agent, args.cwd, { trackTime: trackTimeOverride });
21882
22307
  orchestrator.resizePty(HEADLESS_TAB, ptySize.cols, ptySize.rows);
22308
+ const monitor = new ResourceMonitor(() => orchestrator.getTabProcesses());
22309
+ const refreshResources = () => {
22310
+ monitor.snapshot().then((snap) => tui.update({ resources: summarizeSnapshot(snap) })).catch(() => {
22311
+ });
22312
+ };
22313
+ refreshResources();
22314
+ setInterval(refreshResources, 1e4).unref();
21883
22315
  if (isCodingAgent(agent)) {
21884
22316
  setTimeout(() => {
21885
22317
  orchestrator.writePty(HEADLESS_TAB, "hello\r");
@@ -21908,6 +22340,7 @@ async function main() {
21908
22340
  if (process.stdin.isTTY) {
21909
22341
  process.stdin.setRawMode(false);
21910
22342
  }
22343
+ monitor.stop();
21911
22344
  tui.destroy();
21912
22345
  await orchestrator.shutdown();
21913
22346
  process.exit(0);