@phenx-inc/ctlsurf 0.3.6 → 0.3.8

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 (35) hide show
  1. package/out/headless/index.mjs +276 -56
  2. package/out/headless/index.mjs.map +3 -3
  3. package/out/main/index.js +152 -94
  4. package/out/preload/index.js +3 -0
  5. package/out/renderer/assets/{cssMode-DQW-brNd.js → cssMode-CYoo4t9f.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-DxgOckH2.js → freemarker2--UQnPZsn.js} +1 -1
  7. package/out/renderer/assets/{handlebars-BX1Wpk_3.js → handlebars-DVDrmX0C.js} +1 -1
  8. package/out/renderer/assets/{html-t-KXioI0.js → html-D1-cXoLy.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-Dya7iUjr.js → htmlMode-f5nBuprq.js} +3 -3
  10. package/out/renderer/assets/{index-D6JBcQ20.css → index-65hyKM_8.css} +16 -0
  11. package/out/renderer/assets/{index-DNqZidnO.js → index-D23nru43.js} +64 -23
  12. package/out/renderer/assets/{javascript-DZzW2adn.js → javascript-CcarFzBL.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-D_Wv7XH8.js → jsonMode-BvF-xK9U.js} +3 -3
  14. package/out/renderer/assets/{liquid-BJAHAm2T.js → liquid-CHLtUKl2.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-BgMd-KJk.js → lspLanguageFeatures-B9aNeatS.js} +1 -1
  16. package/out/renderer/assets/{mdx-B6Zod3ry.js → mdx-HGDrkifZ.js} +1 -1
  17. package/out/renderer/assets/{python-Cgt13-KH.js → python-B_dPzjJ6.js} +1 -1
  18. package/out/renderer/assets/{razor-BcwFJGYS.js → razor-CHheM4ot.js} +1 -1
  19. package/out/renderer/assets/{tsMode-BTjzM6fl.js → tsMode-CdC3i1gG.js} +1 -1
  20. package/out/renderer/assets/{typescript-DZYDQEUb.js → typescript-BX6guVRK.js} +1 -1
  21. package/out/renderer/assets/{xml-CloiUoIW.js → xml-CpS-pOPE.js} +1 -1
  22. package/out/renderer/assets/{yaml-CdKdpE-z.js → yaml-Du0AjOHW.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/src/main/bridge.ts +9 -3
  26. package/src/main/headless.ts +35 -2
  27. package/src/main/index.ts +10 -39
  28. package/src/main/orchestrator.ts +29 -1
  29. package/src/main/timeTracker.ts +74 -45
  30. package/src/main/tui.ts +20 -8
  31. package/src/main/updateCheck.ts +40 -0
  32. package/src/preload/index.ts +6 -0
  33. package/src/renderer/App.tsx +2 -0
  34. package/src/renderer/components/AgentPicker.tsx +38 -3
  35. package/src/renderer/styles.css +16 -0
@@ -5466,6 +5466,88 @@ var require_electron = __commonJS({
5466
5466
  }
5467
5467
  });
5468
5468
 
5469
+ // package.json
5470
+ var require_package = __commonJS({
5471
+ "package.json"(exports, module) {
5472
+ module.exports = {
5473
+ name: "@phenx-inc/ctlsurf",
5474
+ version: "0.3.8",
5475
+ 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",
5476
+ main: "out/main/index.js",
5477
+ bin: {
5478
+ ctlsurf: "./bin/ctlsurf-worker.js",
5479
+ "ctlsurf-worker": "./bin/ctlsurf-worker.js"
5480
+ },
5481
+ files: [
5482
+ "bin/",
5483
+ "out/",
5484
+ "resources/",
5485
+ "scripts/",
5486
+ "src/",
5487
+ "package.json",
5488
+ "electron-vite.config.ts",
5489
+ "tsconfig*.json"
5490
+ ],
5491
+ scripts: {
5492
+ dev: "electron-vite dev",
5493
+ "dev:terminal": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --external:ws --sourcemap && node out/headless/index.mjs",
5494
+ build: "electron-vite build && npm run build:headless",
5495
+ "build:headless": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --external:ws --sourcemap",
5496
+ prepublishOnly: "npm run build",
5497
+ preview: "electron-vite preview",
5498
+ package: "electron-builder",
5499
+ postinstall: "node-gyp rebuild --directory=node_modules/node-pty 2>/dev/null || true"
5500
+ },
5501
+ keywords: [
5502
+ "ctlsurf",
5503
+ "terminal",
5504
+ "tui",
5505
+ "coding-agent",
5506
+ "claude-code",
5507
+ "codex",
5508
+ "electron",
5509
+ "node-pty",
5510
+ "xterm"
5511
+ ],
5512
+ license: "MIT",
5513
+ engines: {
5514
+ node: ">=18"
5515
+ },
5516
+ dependencies: {
5517
+ "@monaco-editor/react": "^4.7.0",
5518
+ "@xterm/addon-fit": "^0.10.0",
5519
+ "@xterm/addon-serialize": "^0.14.0",
5520
+ "@xterm/addon-web-links": "^0.11.0",
5521
+ "@xterm/headless": "^6.0.0",
5522
+ "@xterm/xterm": "^5.5.0",
5523
+ "electron-store": "^10.0.0",
5524
+ esbuild: "^0.27.4",
5525
+ "monaco-editor": "^0.55.1",
5526
+ "node-pty": "^1.0.0",
5527
+ ws: "^8.20.0"
5528
+ },
5529
+ devDependencies: {
5530
+ "@electron/rebuild": "^4.0.3",
5531
+ "@types/node": "^22.15.0",
5532
+ "@types/react": "^19.1.0",
5533
+ "@types/react-dom": "^19.1.0",
5534
+ "@types/ws": "^8.18.1",
5535
+ "@vitejs/plugin-react": "^4.5.2",
5536
+ electron: "^35.0.0",
5537
+ "electron-builder": "^25.1.8",
5538
+ "electron-vite": "^3.1.0",
5539
+ react: "^19.1.0",
5540
+ "react-dom": "^19.1.0",
5541
+ typescript: "^5.8.3"
5542
+ },
5543
+ optionalDependencies: {
5544
+ bufferutil: "^4.1.0",
5545
+ "utf-8-validate": "^6.0.6"
5546
+ }
5547
+ };
5548
+ }
5549
+ });
5550
+
5469
5551
  // src/main/orchestrator.ts
5470
5552
  import path from "path";
5471
5553
  import fs from "fs";
@@ -5682,6 +5764,7 @@ var import_addon_serialize = __toESM(require_addon_serialize());
5682
5764
  var ConversationBridge = class {
5683
5765
  wsClient = null;
5684
5766
  sessionActive = false;
5767
+ loggingEnabled = false;
5685
5768
  inputBuffer = "";
5686
5769
  outputBuffer = "";
5687
5770
  outputFlushTimer = null;
@@ -5691,7 +5774,11 @@ var ConversationBridge = class {
5691
5774
  setWsClient(ws) {
5692
5775
  this.wsClient = ws;
5693
5776
  }
5777
+ setLoggingEnabled(enabled) {
5778
+ this.loggingEnabled = enabled;
5779
+ }
5694
5780
  startSession() {
5781
+ if (!this.loggingEnabled) return;
5695
5782
  this.clearOutputTimers();
5696
5783
  this.outputBuffer = "";
5697
5784
  this.inputBuffer = "";
@@ -5700,13 +5787,13 @@ var ConversationBridge = class {
5700
5787
  console.log("[bridge] Session started");
5701
5788
  }
5702
5789
  feedOutput(data) {
5703
- if (!this.sessionActive) return;
5790
+ if (!this.sessionActive || !this.loggingEnabled) return;
5704
5791
  this.outputBuffer += data;
5705
5792
  this.terminalCapture.write(data);
5706
5793
  this.scheduleOutputFlush();
5707
5794
  }
5708
5795
  feedInput(data) {
5709
- if (!this.sessionActive) return;
5796
+ if (!this.sessionActive || !this.loggingEnabled) return;
5710
5797
  this.inputBuffer += data;
5711
5798
  if (data.includes("\r") || data.includes("\n")) {
5712
5799
  const cleaned = cleanInput(this.inputBuffer);
@@ -5728,7 +5815,7 @@ var ConversationBridge = class {
5728
5815
  this.sendEntry("terminal_output", cleaned);
5729
5816
  }
5730
5817
  sendEntry(type, content) {
5731
- if (!this.wsClient) return;
5818
+ if (!this.wsClient || !this.loggingEnabled) return;
5732
5819
  this.wsClient.sendChatLog({
5733
5820
  ts: (/* @__PURE__ */ new Date()).toISOString(),
5734
5821
  type,
@@ -6271,55 +6358,68 @@ var TimeTracker = class {
6271
6358
  if (this.sessions.has(tabId)) {
6272
6359
  await this.endSession(tabId);
6273
6360
  }
6274
- try {
6275
- const blockId = await this.ensureDatastore(cwd);
6276
- if (!blockId) {
6277
- log2(`No "${AGENT_DATASTORE_PAGE_TITLE}" page found for ${cwd} \u2014 tracking disabled for this session`);
6278
- return;
6361
+ const startedAt = Date.now();
6362
+ const state = {
6363
+ blockId: null,
6364
+ rowId: null,
6365
+ sessionUuid: randomUUID(),
6366
+ cwd,
6367
+ agentName,
6368
+ idleTimeoutMin,
6369
+ startedAt,
6370
+ lastActivity: startedAt,
6371
+ activeMs: 0,
6372
+ idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
6373
+ firstCheckpointTimer: null,
6374
+ checkpointTimer: null,
6375
+ ended: false
6376
+ };
6377
+ this.sessions.set(tabId, state);
6378
+ await this.tryResolve(tabId);
6379
+ state.firstCheckpointTimer = setTimeout(() => {
6380
+ void this.checkpoint(tabId);
6381
+ const live = this.sessions.get(tabId);
6382
+ if (live && !live.ended) {
6383
+ live.checkpointTimer = setInterval(() => {
6384
+ void this.checkpoint(tabId);
6385
+ }, CHECKPOINT_INTERVAL_MS);
6279
6386
  }
6280
- const startedAt = Date.now();
6281
- const sessionUuid = randomUUID();
6387
+ }, FIRST_CHECKPOINT_DELAY_MS);
6388
+ const pending = !state.blockId || !state.rowId;
6389
+ log2(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}${pending ? " (pending datastore \u2014 will retry on each checkpoint)" : ""}`);
6390
+ }
6391
+ /** Attempts to locate (or create) the datastore + add the session row.
6392
+ * Returns true once the session is resolved (blockId + rowId set).
6393
+ * Safe to call repeatedly: re-running while pending will keep retrying;
6394
+ * once resolved it's a no-op. */
6395
+ async tryResolve(tabId) {
6396
+ const s = this.sessions.get(tabId);
6397
+ if (!s) return false;
6398
+ if (s.blockId && s.rowId) return true;
6399
+ try {
6400
+ const blockId = await this.ensureDatastore(s.cwd);
6401
+ if (!blockId) return false;
6282
6402
  const row = await this.api.addRow(blockId, {
6283
- Started: formatStarted(startedAt),
6284
- "Active Time": 0,
6285
- "Last Updated": new Date(startedAt).toISOString(),
6286
- Agent: agentName,
6403
+ Started: formatStarted(s.startedAt),
6404
+ "Active Time": Math.round(s.activeMs / 6e4),
6405
+ "Last Updated": (/* @__PURE__ */ new Date()).toISOString(),
6406
+ Agent: s.agentName,
6287
6407
  Worker: os2.hostname(),
6288
- Session: sessionUuid,
6408
+ Session: s.sessionUuid,
6289
6409
  Notes: ""
6290
6410
  });
6291
6411
  const rowId = row?.id;
6292
6412
  if (!rowId) {
6293
- log2("addRow returned no id; aborting tracking", row);
6294
- return;
6413
+ log2(`addRow returned no id for tab=${tabId}; will retry on next checkpoint`);
6414
+ return false;
6295
6415
  }
6296
- const state = {
6297
- blockId,
6298
- rowId,
6299
- cwd,
6300
- agentName,
6301
- idleTimeoutMin,
6302
- startedAt,
6303
- lastActivity: startedAt,
6304
- activeMs: 0,
6305
- idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
6306
- firstCheckpointTimer: null,
6307
- checkpointTimer: null,
6308
- ended: false
6309
- };
6310
- state.firstCheckpointTimer = setTimeout(() => {
6311
- void this.checkpoint(tabId);
6312
- const live = this.sessions.get(tabId);
6313
- if (live && !live.ended) {
6314
- live.checkpointTimer = setInterval(() => {
6315
- void this.checkpoint(tabId);
6316
- }, CHECKPOINT_INTERVAL_MS);
6317
- }
6318
- }, FIRST_CHECKPOINT_DELAY_MS);
6319
- this.sessions.set(tabId, state);
6320
- log2(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}`);
6416
+ s.blockId = blockId;
6417
+ s.rowId = rowId;
6418
+ log2(`Resolved datastore for tab=${tabId} (cwd=${s.cwd})`);
6419
+ return true;
6321
6420
  } catch (err) {
6322
- log2(`startSession failed: ${err?.message || err}`);
6421
+ log2(`tryResolve failed for tab=${tabId}: ${err?.message || err}`);
6422
+ return false;
6323
6423
  }
6324
6424
  }
6325
6425
  isTracking(tabId) {
@@ -6359,14 +6459,21 @@ var TimeTracker = class {
6359
6459
  async endSession(tabId) {
6360
6460
  const s = this.sessions.get(tabId);
6361
6461
  if (!s || s.ended) return;
6362
- s.ended = true;
6363
6462
  if (s.firstCheckpointTimer) clearTimeout(s.firstCheckpointTimer);
6364
6463
  if (s.checkpointTimer) clearInterval(s.checkpointTimer);
6365
6464
  try {
6366
- await this.writeRow(s, Date.now());
6465
+ if (!s.blockId || !s.rowId) {
6466
+ await this.tryResolve(tabId);
6467
+ }
6468
+ if (s.blockId && s.rowId) {
6469
+ await this.writeRow(s, Date.now());
6470
+ } else {
6471
+ log2(`endSession for tab=${tabId}: never resolved datastore; ${Math.round(s.activeMs / 6e4)}min not recorded`);
6472
+ }
6367
6473
  } catch (err) {
6368
6474
  log2(`endSession write failed: ${err?.message || err}`);
6369
6475
  }
6476
+ s.ended = true;
6370
6477
  this.sessions.delete(tabId);
6371
6478
  }
6372
6479
  async endAll() {
@@ -6376,13 +6483,16 @@ var TimeTracker = class {
6376
6483
  async checkpoint(tabId) {
6377
6484
  const s = this.sessions.get(tabId);
6378
6485
  if (!s || s.ended) return;
6486
+ if (!s.blockId || !s.rowId) {
6487
+ if (!await this.tryResolve(tabId)) return;
6488
+ }
6379
6489
  try {
6380
6490
  await this.writeRow(s, Date.now());
6381
6491
  } catch (err) {
6382
6492
  log2(`checkpoint failed: ${err?.message || err}; retrying in 2s`);
6383
6493
  setTimeout(() => {
6384
6494
  const live = this.sessions.get(tabId);
6385
- if (!live || live.ended) return;
6495
+ if (!live || live.ended || !live.blockId || !live.rowId) return;
6386
6496
  this.writeRow(live, Date.now()).catch((err2) => {
6387
6497
  log2(`checkpoint retry failed: ${err2?.message || err2}`);
6388
6498
  });
@@ -6390,6 +6500,7 @@ var TimeTracker = class {
6390
6500
  }
6391
6501
  }
6392
6502
  async writeRow(s, _endTimeMs) {
6503
+ if (!s.blockId || !s.rowId) return;
6393
6504
  const activeMin = Math.round(s.activeMs / 6e4);
6394
6505
  await this.api.updateRow(s.blockId, s.rowId, {
6395
6506
  "Active Time": activeMin,
@@ -6523,7 +6634,8 @@ var Orchestrator = class {
6523
6634
  currentCwd = null;
6524
6635
  settings = {
6525
6636
  activeProfile: "production",
6526
- profiles: { ...DEFAULT_PROFILES }
6637
+ profiles: { ...DEFAULT_PROFILES },
6638
+ logChat: false
6527
6639
  };
6528
6640
  constructor(settingsDir, events) {
6529
6641
  this.settingsDir = settingsDir;
@@ -6558,6 +6670,21 @@ var Orchestrator = class {
6558
6670
  }
6559
6671
  });
6560
6672
  this.bridge.setWsClient(this.workerWs);
6673
+ this.bridge.setLoggingEnabled(!!this.settings.logChat);
6674
+ }
6675
+ // ─── Chat logging ───────────────────────────────
6676
+ get logChatEnabled() {
6677
+ return !!this.settings.logChat;
6678
+ }
6679
+ setLogChat(enabled) {
6680
+ this.settings.logChat = enabled;
6681
+ this.saveSettings();
6682
+ this.bridge.setLoggingEnabled(enabled);
6683
+ if (!enabled) {
6684
+ this.bridge.endSession();
6685
+ } else if (this.activeTabId) {
6686
+ this.bridge.startSession();
6687
+ }
6561
6688
  }
6562
6689
  // ─── Settings ───────────────────────────────────
6563
6690
  getActiveProfile() {
@@ -6605,7 +6732,8 @@ var Orchestrator = class {
6605
6732
  baseUrl: raw.ctlsurfBaseUrl || "https://app.ctlsurf.com",
6606
6733
  dataspacePageId: raw.ctlsurfDataspacePageId || ""
6607
6734
  }
6608
- }
6735
+ },
6736
+ logChat: !!raw.logChat
6609
6737
  };
6610
6738
  this.saveSettings();
6611
6739
  log3("[settings] Migrated legacy settings to profiles");
@@ -6614,15 +6742,20 @@ var Orchestrator = class {
6614
6742
  if (!this.settings.profiles.production) {
6615
6743
  this.settings.profiles.production = { ...DEFAULT_PROFILES.production };
6616
6744
  }
6745
+ if (this.settings.logChat === void 0) {
6746
+ this.settings.logChat = false;
6747
+ }
6617
6748
  }
6618
6749
  }
6619
6750
  } catch {
6620
6751
  this.settings = {
6621
6752
  activeProfile: "production",
6622
- profiles: { ...DEFAULT_PROFILES }
6753
+ profiles: { ...DEFAULT_PROFILES },
6754
+ logChat: false
6623
6755
  };
6624
6756
  }
6625
6757
  this.applyProfile(this.getActiveProfile());
6758
+ this.bridge.setLoggingEnabled(!!this.settings.logChat);
6626
6759
  }
6627
6760
  saveSettings() {
6628
6761
  const settingsPath = path.join(this.settingsDir, "settings.json");
@@ -6752,7 +6885,9 @@ var Orchestrator = class {
6752
6885
  const t = this.tabs.get(tabId);
6753
6886
  if (t?.termStreamTimer) clearTimeout(t.termStreamTimer);
6754
6887
  });
6755
- this.bridge.startSession();
6888
+ if (this.settings.logChat) {
6889
+ this.bridge.startSession();
6890
+ }
6756
6891
  const profile = this.getActiveProfile();
6757
6892
  const shouldTrack = opts?.trackTime !== void 0 ? opts.trackTime : profile.trackTime !== false;
6758
6893
  if (shouldTrack) {
@@ -7010,8 +7145,9 @@ var Tui = class {
7010
7145
  return new Promise((resolve) => {
7011
7146
  let selected = 0;
7012
7147
  let trackTime = options.initialTrackTime;
7148
+ let logChat = options.initialLogChat;
7013
7149
  const modalWidth = 44;
7014
- const modalHeight = agents.length + 4 + 2;
7150
+ const modalHeight = agents.length + 4 + 3;
7015
7151
  const startCol = Math.max(1, Math.floor((this.cols - modalWidth) / 2));
7016
7152
  const startRow = Math.max(1, Math.floor((this.rows - modalHeight) / 2));
7017
7153
  this.write(`${CSI}?1049h`);
@@ -7055,9 +7191,16 @@ var Tui = class {
7055
7191
  const trackContentLen = 2 + 3 + 1 + "Track time".length;
7056
7192
  const trackPad = " ".repeat(Math.max(0, modalWidth - 2 - trackContentLen));
7057
7193
  this.write(`${CSI}${trackRow};${startCol}H${BG_MODAL}${FG_DIM}\u2502${RESET}${BG_MODAL}${trackContent}${trackPad}${FG_DIM}\u2502${RESET}`);
7058
- const botRow = trackRow + 1;
7194
+ const logRow = trackRow + 1;
7195
+ const logCheckbox = logChat ? `${FG_GREEN}[\u2713]${RESET}${BG_MODAL}` : `${FG_DIM}[ ]${RESET}${BG_MODAL}`;
7196
+ const logLabelFg = logChat ? FG_WHITE : FG_DIM;
7197
+ const logContent = ` ${logCheckbox} ${logLabelFg}Log chat${RESET}${BG_MODAL}`;
7198
+ const logContentLen = 2 + 3 + 1 + "Log chat".length;
7199
+ const logPad = " ".repeat(Math.max(0, modalWidth - 2 - logContentLen));
7200
+ this.write(`${CSI}${logRow};${startCol}H${BG_MODAL}${FG_DIM}\u2502${RESET}${BG_MODAL}${logContent}${logPad}${FG_DIM}\u2502${RESET}`);
7201
+ const botRow = logRow + 1;
7059
7202
  this.write(`${CSI}${botRow};${startCol}H${BG_MODAL}${FG_DIM}${botBorder}${RESET}`);
7060
- const hint = "\u2191\u2193 navigate \xB7 Enter select \xB7 t track \xB7 q quit";
7203
+ const hint = "\u2191\u2193 nav \xB7 Enter \xB7 t track \xB7 l log \xB7 q quit";
7061
7204
  const hintCol = Math.max(1, Math.floor((this.cols - hint.length) / 2));
7062
7205
  this.write(`${CSI}${botRow + 2};${hintCol}H${FG_DIM}${hint}${RESET}`);
7063
7206
  };
@@ -7074,12 +7217,15 @@ var Tui = class {
7074
7217
  } else if (key === "\x1B[B" || key === "j") {
7075
7218
  selected = (selected + 1) % agents.length;
7076
7219
  drawModal();
7077
- } else if (key === "t" || key === "T" || key === " ") {
7220
+ } else if (key === "t" || key === "T") {
7078
7221
  trackTime = !trackTime;
7079
7222
  drawModal();
7223
+ } else if (key === "l" || key === "L") {
7224
+ logChat = !logChat;
7225
+ drawModal();
7080
7226
  } else if (key === "\r" || key === "\n") {
7081
7227
  cleanup();
7082
- resolve({ agentIdx: selected, trackTime });
7228
+ resolve({ agentIdx: selected, trackTime, logChat });
7083
7229
  } else if (key === "q" || key === "\x1B" || key === "") {
7084
7230
  cleanup();
7085
7231
  this.write(`${CSI}?25h`);
@@ -7146,6 +7292,50 @@ var Tui = class {
7146
7292
  }
7147
7293
  };
7148
7294
 
7295
+ // src/main/updateCheck.ts
7296
+ import https from "https";
7297
+ var NPM_PACKAGE = "@phenx-inc/ctlsurf";
7298
+ function compareSemver(a, b) {
7299
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
7300
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
7301
+ for (let i = 0; i < 3; i++) {
7302
+ const ai = pa[i] || 0;
7303
+ const bi = pb[i] || 0;
7304
+ if (ai !== bi) return ai - bi;
7305
+ }
7306
+ return 0;
7307
+ }
7308
+ function fetchLatestNpmVersion(timeoutMs = 8e3) {
7309
+ return new Promise((resolve) => {
7310
+ const url = `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE)}/latest`;
7311
+ const req = https.get(url, { headers: { "Accept": "application/json" } }, (res) => {
7312
+ if (res.statusCode !== 200) {
7313
+ res.resume();
7314
+ resolve(null);
7315
+ return;
7316
+ }
7317
+ let body = "";
7318
+ res.setEncoding("utf8");
7319
+ res.on("data", (chunk) => {
7320
+ body += chunk;
7321
+ });
7322
+ res.on("end", () => {
7323
+ try {
7324
+ const json = JSON.parse(body);
7325
+ resolve(typeof json?.version === "string" ? json.version : null);
7326
+ } catch {
7327
+ resolve(null);
7328
+ }
7329
+ });
7330
+ });
7331
+ req.on("error", () => resolve(null));
7332
+ req.setTimeout(timeoutMs, () => {
7333
+ req.destroy();
7334
+ resolve(null);
7335
+ });
7336
+ });
7337
+ }
7338
+
7149
7339
  // src/main/headless.ts
7150
7340
  process.stdout?.on?.("error", () => {
7151
7341
  });
@@ -7158,6 +7348,14 @@ process.on("uncaughtException", (err) => {
7158
7348
  } catch {
7159
7349
  }
7160
7350
  });
7351
+ function getCurrentVersion() {
7352
+ try {
7353
+ const pkg = require_package();
7354
+ return typeof pkg?.version === "string" ? pkg.version : "0.0.0";
7355
+ } catch {
7356
+ return "0.0.0";
7357
+ }
7358
+ }
7161
7359
  function parseArgs(argv) {
7162
7360
  const args = {
7163
7361
  agent: null,
@@ -7198,9 +7396,27 @@ function parseArgs(argv) {
7198
7396
  }
7199
7397
  return args;
7200
7398
  }
7399
+ async function checkVersionAndNotify() {
7400
+ const current = getCurrentVersion();
7401
+ const latest = await fetchLatestNpmVersion(3e3);
7402
+ if (!latest) return;
7403
+ if (compareSemver(latest, current) <= 0) return;
7404
+ const Y = "\x1B[33m";
7405
+ const G = "\x1B[32m";
7406
+ const D = "\x1B[90m";
7407
+ const R = "\x1B[0m";
7408
+ process.stdout.write(
7409
+ `
7410
+ ${Y}A new version of ctlsurf is available${R} ${D}(${current} \u2192 ${latest})${R}
7411
+ Update: ${G}npm i -g ${NPM_PACKAGE}${R}
7412
+
7413
+ `
7414
+ );
7415
+ }
7201
7416
  async function main() {
7202
7417
  const args = parseArgs(process.argv.slice(2));
7203
7418
  const settingsDir = getSettingsDir(false);
7419
+ await checkVersionAndNotify();
7204
7420
  const tui = new Tui();
7205
7421
  const agents = getBuiltinAgents();
7206
7422
  const orchestrator = new Orchestrator(settingsDir, {
@@ -7241,9 +7457,13 @@ async function main() {
7241
7457
  };
7242
7458
  } else {
7243
7459
  const initialTrackTime = orchestrator.getActiveProfile().trackTime !== false;
7244
- const picked = await tui.showAgentPicker(agents, { initialTrackTime });
7460
+ const initialLogChat = orchestrator.logChatEnabled;
7461
+ const picked = await tui.showAgentPicker(agents, { initialTrackTime, initialLogChat });
7245
7462
  agent = agents[picked.agentIdx];
7246
7463
  trackTimeOverride = picked.trackTime;
7464
+ if (picked.logChat !== orchestrator.logChatEnabled) {
7465
+ orchestrator.setLogChat(picked.logChat);
7466
+ }
7247
7467
  }
7248
7468
  tui.update({
7249
7469
  agentName: agent.name,