@phenx-inc/ctlsurf 0.3.7 → 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 (34) hide show
  1. package/out/headless/index.mjs +209 -13
  2. package/out/headless/index.mjs.map +3 -3
  3. package/out/main/index.js +85 -51
  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/tui.ts +20 -8
  30. package/src/main/updateCheck.ts +40 -0
  31. package/src/preload/index.ts +6 -0
  32. package/src/renderer/App.tsx +2 -0
  33. package/src/renderer/components/AgentPicker.tsx +38 -3
  34. 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,
@@ -6547,7 +6634,8 @@ var Orchestrator = class {
6547
6634
  currentCwd = null;
6548
6635
  settings = {
6549
6636
  activeProfile: "production",
6550
- profiles: { ...DEFAULT_PROFILES }
6637
+ profiles: { ...DEFAULT_PROFILES },
6638
+ logChat: false
6551
6639
  };
6552
6640
  constructor(settingsDir, events) {
6553
6641
  this.settingsDir = settingsDir;
@@ -6582,6 +6670,21 @@ var Orchestrator = class {
6582
6670
  }
6583
6671
  });
6584
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
+ }
6585
6688
  }
6586
6689
  // ─── Settings ───────────────────────────────────
6587
6690
  getActiveProfile() {
@@ -6629,7 +6732,8 @@ var Orchestrator = class {
6629
6732
  baseUrl: raw.ctlsurfBaseUrl || "https://app.ctlsurf.com",
6630
6733
  dataspacePageId: raw.ctlsurfDataspacePageId || ""
6631
6734
  }
6632
- }
6735
+ },
6736
+ logChat: !!raw.logChat
6633
6737
  };
6634
6738
  this.saveSettings();
6635
6739
  log3("[settings] Migrated legacy settings to profiles");
@@ -6638,15 +6742,20 @@ var Orchestrator = class {
6638
6742
  if (!this.settings.profiles.production) {
6639
6743
  this.settings.profiles.production = { ...DEFAULT_PROFILES.production };
6640
6744
  }
6745
+ if (this.settings.logChat === void 0) {
6746
+ this.settings.logChat = false;
6747
+ }
6641
6748
  }
6642
6749
  }
6643
6750
  } catch {
6644
6751
  this.settings = {
6645
6752
  activeProfile: "production",
6646
- profiles: { ...DEFAULT_PROFILES }
6753
+ profiles: { ...DEFAULT_PROFILES },
6754
+ logChat: false
6647
6755
  };
6648
6756
  }
6649
6757
  this.applyProfile(this.getActiveProfile());
6758
+ this.bridge.setLoggingEnabled(!!this.settings.logChat);
6650
6759
  }
6651
6760
  saveSettings() {
6652
6761
  const settingsPath = path.join(this.settingsDir, "settings.json");
@@ -6776,7 +6885,9 @@ var Orchestrator = class {
6776
6885
  const t = this.tabs.get(tabId);
6777
6886
  if (t?.termStreamTimer) clearTimeout(t.termStreamTimer);
6778
6887
  });
6779
- this.bridge.startSession();
6888
+ if (this.settings.logChat) {
6889
+ this.bridge.startSession();
6890
+ }
6780
6891
  const profile = this.getActiveProfile();
6781
6892
  const shouldTrack = opts?.trackTime !== void 0 ? opts.trackTime : profile.trackTime !== false;
6782
6893
  if (shouldTrack) {
@@ -7034,8 +7145,9 @@ var Tui = class {
7034
7145
  return new Promise((resolve) => {
7035
7146
  let selected = 0;
7036
7147
  let trackTime = options.initialTrackTime;
7148
+ let logChat = options.initialLogChat;
7037
7149
  const modalWidth = 44;
7038
- const modalHeight = agents.length + 4 + 2;
7150
+ const modalHeight = agents.length + 4 + 3;
7039
7151
  const startCol = Math.max(1, Math.floor((this.cols - modalWidth) / 2));
7040
7152
  const startRow = Math.max(1, Math.floor((this.rows - modalHeight) / 2));
7041
7153
  this.write(`${CSI}?1049h`);
@@ -7079,9 +7191,16 @@ var Tui = class {
7079
7191
  const trackContentLen = 2 + 3 + 1 + "Track time".length;
7080
7192
  const trackPad = " ".repeat(Math.max(0, modalWidth - 2 - trackContentLen));
7081
7193
  this.write(`${CSI}${trackRow};${startCol}H${BG_MODAL}${FG_DIM}\u2502${RESET}${BG_MODAL}${trackContent}${trackPad}${FG_DIM}\u2502${RESET}`);
7082
- 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;
7083
7202
  this.write(`${CSI}${botRow};${startCol}H${BG_MODAL}${FG_DIM}${botBorder}${RESET}`);
7084
- 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";
7085
7204
  const hintCol = Math.max(1, Math.floor((this.cols - hint.length) / 2));
7086
7205
  this.write(`${CSI}${botRow + 2};${hintCol}H${FG_DIM}${hint}${RESET}`);
7087
7206
  };
@@ -7098,12 +7217,15 @@ var Tui = class {
7098
7217
  } else if (key === "\x1B[B" || key === "j") {
7099
7218
  selected = (selected + 1) % agents.length;
7100
7219
  drawModal();
7101
- } else if (key === "t" || key === "T" || key === " ") {
7220
+ } else if (key === "t" || key === "T") {
7102
7221
  trackTime = !trackTime;
7103
7222
  drawModal();
7223
+ } else if (key === "l" || key === "L") {
7224
+ logChat = !logChat;
7225
+ drawModal();
7104
7226
  } else if (key === "\r" || key === "\n") {
7105
7227
  cleanup();
7106
- resolve({ agentIdx: selected, trackTime });
7228
+ resolve({ agentIdx: selected, trackTime, logChat });
7107
7229
  } else if (key === "q" || key === "\x1B" || key === "") {
7108
7230
  cleanup();
7109
7231
  this.write(`${CSI}?25h`);
@@ -7170,6 +7292,50 @@ var Tui = class {
7170
7292
  }
7171
7293
  };
7172
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
+
7173
7339
  // src/main/headless.ts
7174
7340
  process.stdout?.on?.("error", () => {
7175
7341
  });
@@ -7182,6 +7348,14 @@ process.on("uncaughtException", (err) => {
7182
7348
  } catch {
7183
7349
  }
7184
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
+ }
7185
7359
  function parseArgs(argv) {
7186
7360
  const args = {
7187
7361
  agent: null,
@@ -7222,9 +7396,27 @@ function parseArgs(argv) {
7222
7396
  }
7223
7397
  return args;
7224
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
+ }
7225
7416
  async function main() {
7226
7417
  const args = parseArgs(process.argv.slice(2));
7227
7418
  const settingsDir = getSettingsDir(false);
7419
+ await checkVersionAndNotify();
7228
7420
  const tui = new Tui();
7229
7421
  const agents = getBuiltinAgents();
7230
7422
  const orchestrator = new Orchestrator(settingsDir, {
@@ -7265,9 +7457,13 @@ async function main() {
7265
7457
  };
7266
7458
  } else {
7267
7459
  const initialTrackTime = orchestrator.getActiveProfile().trackTime !== false;
7268
- const picked = await tui.showAgentPicker(agents, { initialTrackTime });
7460
+ const initialLogChat = orchestrator.logChatEnabled;
7461
+ const picked = await tui.showAgentPicker(agents, { initialTrackTime, initialLogChat });
7269
7462
  agent = agents[picked.agentIdx];
7270
7463
  trackTimeOverride = picked.trackTime;
7464
+ if (picked.logChat !== orchestrator.logChatEnabled) {
7465
+ orchestrator.setLogChat(picked.logChat);
7466
+ }
7271
7467
  }
7272
7468
  tui.update({
7273
7469
  agentName: agent.name,