@phenx-inc/ctlsurf 0.3.14 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/out/headless/index.mjs +57 -8
  2. package/out/headless/index.mjs.map +2 -2
  3. package/out/main/index.js +56 -7
  4. package/out/preload/index.js +6 -0
  5. package/out/renderer/assets/{cssMode-G_SDogBL.js → cssMode-D5dPwEy5.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-BzEus0h2.js → freemarker2-c5jJjQ9s.js} +1 -1
  7. package/out/renderer/assets/{handlebars-Et995f6O.js → handlebars-BTbmOxx9.js} +1 -1
  8. package/out/renderer/assets/{html-D4wgKxPD.js → html-3cIIQcxO.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-DSxpefzL.js → htmlMode-DYbpW1yY.js} +3 -3
  10. package/out/renderer/assets/{index-AQ346NMi.css → index-6KvOnYL1.css} +18 -0
  11. package/out/renderer/assets/{index-ByJTqkiQ.js → index-D2MUZin7.js} +36 -23
  12. package/out/renderer/assets/{javascript-CzLoo8aq.js → javascript-CDuCMm-6.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-BrwPy7fY.js → jsonMode-COLqbq0s.js} +3 -3
  14. package/out/renderer/assets/{liquid-BsfPf6YG.js → liquid-BFcqZizB.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-CxLZ421s.js → lspLanguageFeatures-CbkEcL-z.js} +1 -1
  16. package/out/renderer/assets/{mdx-CPvHIsAR.js → mdx-DyK93oEE.js} +1 -1
  17. package/out/renderer/assets/{python-Dr7dCUjG.js → python-D4lCwSVr.js} +1 -1
  18. package/out/renderer/assets/{razor-a7zjD7Y3.js → razor-DdkE9XVt.js} +1 -1
  19. package/out/renderer/assets/{tsMode-B7KLV2X6.js → tsMode-BrQ4Fsc-.js} +1 -1
  20. package/out/renderer/assets/{typescript-Cjuzf37q.js → typescript-BakbYMnC.js} +1 -1
  21. package/out/renderer/assets/{xml-Yz9xINtk.js → xml-DHDW9Xhp.js} +1 -1
  22. package/out/renderer/assets/{yaml-DtKnp5J0.js → yaml-1Ayv_J3q.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/src/main/agents.ts +36 -1
  26. package/src/main/headless.ts +5 -3
  27. package/src/main/index.ts +4 -2
  28. package/src/main/orchestrator.ts +29 -0
  29. package/src/main/workerWs.ts +8 -6
  30. package/src/preload/index.ts +7 -0
  31. package/src/renderer/App.tsx +19 -1
  32. package/src/renderer/styles.css +18 -0
@@ -5471,7 +5471,7 @@ var require_package = __commonJS({
5471
5471
  "package.json"(exports, module) {
5472
5472
  module.exports = {
5473
5473
  name: "@phenx-inc/ctlsurf",
5474
- version: "0.3.14",
5474
+ version: "0.3.16",
5475
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
5476
  main: "out/main/index.js",
5477
5477
  bin: {
@@ -5619,10 +5619,27 @@ var PtyManager = class {
5619
5619
  };
5620
5620
 
5621
5621
  // src/main/agents.ts
5622
+ import { accessSync, constants } from "fs";
5623
+ import { join, delimiter } from "path";
5622
5624
  function getShellCommand() {
5623
5625
  if (process.platform === "win32") return "powershell.exe";
5624
5626
  return process.env.SHELL || "/bin/zsh";
5625
5627
  }
5628
+ function isCommandAvailable(command) {
5629
+ const dirs = (process.env.PATH || "").split(delimiter).filter(Boolean);
5630
+ const isWin = process.platform === "win32";
5631
+ const exts = isWin ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
5632
+ for (const dir of dirs) {
5633
+ for (const ext of exts) {
5634
+ try {
5635
+ accessSync(join(dir, command + ext), isWin ? constants.F_OK : constants.X_OK);
5636
+ return true;
5637
+ } catch {
5638
+ }
5639
+ }
5640
+ }
5641
+ return false;
5642
+ }
5626
5643
  function getBuiltinAgents() {
5627
5644
  return [
5628
5645
  {
@@ -5649,6 +5666,12 @@ function getBuiltinAgents() {
5649
5666
  }
5650
5667
  ];
5651
5668
  }
5669
+ function getAvailableAgents() {
5670
+ const all = getBuiltinAgents();
5671
+ const coding = all.filter((a) => isCodingAgent(a) && isCommandAvailable(a.command));
5672
+ const shell = all.filter((a) => !isCodingAgent(a));
5673
+ return [...coding, ...shell];
5674
+ }
5652
5675
  function isCodingAgent(agent) {
5653
5676
  return agent.id !== "shell";
5654
5677
  }
@@ -6087,11 +6110,9 @@ var WorkerWsClient = class {
6087
6110
  workerId = null;
6088
6111
  _status = "disconnected";
6089
6112
  shouldReconnect = false;
6090
- fingerprint;
6091
6113
  constructor(events, baseUrl) {
6092
6114
  this.events = events;
6093
6115
  this.baseUrl = baseUrl || "wss://app.ctlsurf.com";
6094
- this.fingerprint = this.generateFingerprint();
6095
6116
  }
6096
6117
  get status() {
6097
6118
  return this._status;
@@ -6105,8 +6126,12 @@ var WorkerWsClient = class {
6105
6126
  setBaseUrl(url) {
6106
6127
  this.baseUrl = url;
6107
6128
  }
6108
- generateFingerprint() {
6109
- const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}`;
6129
+ // Per-directory fingerprint: each working directory is a distinct worker, so
6130
+ // multiple instances on the same machine (one per project) don't collide as a
6131
+ // single worker server-side. cwd is included so the same folder maps to the
6132
+ // same worker across restarts.
6133
+ generateFingerprint(cwd) {
6134
+ const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}:${cwd}`;
6110
6135
  return crypto.createHash("sha256").update(data).digest("hex").slice(0, 32);
6111
6136
  }
6112
6137
  setStatus(status) {
@@ -6116,7 +6141,8 @@ var WorkerWsClient = class {
6116
6141
  }
6117
6142
  }
6118
6143
  connect(registration) {
6119
- this.registration = { ...registration, fingerprint: this.fingerprint };
6144
+ const fingerprint = this.generateFingerprint(registration.cwd);
6145
+ this.registration = { ...registration, fingerprint };
6120
6146
  this.shouldReconnect = true;
6121
6147
  this.doConnect();
6122
6148
  }
@@ -6857,6 +6883,7 @@ var Orchestrator = class {
6857
6883
  };
6858
6884
  noProjectPollTimer = null;
6859
6885
  noProjectPollCwd = null;
6886
+ currentProjectName = null;
6860
6887
  constructor(settingsDir, events) {
6861
6888
  this.settingsDir = settingsDir;
6862
6889
  this.events = events;
@@ -6881,12 +6908,14 @@ var Orchestrator = class {
6881
6908
  log(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`);
6882
6909
  events.onWorkerRegistered(data);
6883
6910
  if (!data.folder_id) {
6911
+ this.setProjectName(null);
6884
6912
  events.onWorkerStatus("no_project");
6885
6913
  if (this.currentCwd && data.status !== "pending_approval") {
6886
6914
  this.startNoProjectPolling(this.currentCwd);
6887
6915
  }
6888
6916
  } else {
6889
6917
  this.stopNoProjectPolling();
6918
+ this.resolveProjectName(data.folder_id);
6890
6919
  }
6891
6920
  },
6892
6921
  onTerminalInput: (data) => {
@@ -6921,6 +6950,26 @@ var Orchestrator = class {
6921
6950
  get cwd() {
6922
6951
  return this.currentCwd;
6923
6952
  }
6953
+ // Name of the connected ctlsurf project (folder) for the desktop header.
6954
+ get projectName() {
6955
+ return this.currentProjectName;
6956
+ }
6957
+ setProjectName(name) {
6958
+ if (this.currentProjectName === name) return;
6959
+ this.currentProjectName = name;
6960
+ this.events.onProjectChanged?.(name);
6961
+ }
6962
+ // Resolve the connected folder's human-readable name. Best-effort: a failed
6963
+ // lookup just leaves the project name unset rather than blocking anything.
6964
+ async resolveProjectName(folderId) {
6965
+ try {
6966
+ const folder = await this.ctlsurfApi.getFolder(folderId);
6967
+ const name = folder?.name ?? folder?.title;
6968
+ this.setProjectName(typeof name === "string" && name ? name : null);
6969
+ } catch (err) {
6970
+ log(`[worker-ws] Failed to resolve project name for folder ${folderId}: ${err}`);
6971
+ }
6972
+ }
6924
6973
  get agent() {
6925
6974
  return this.currentAgent;
6926
6975
  }
@@ -7711,7 +7760,7 @@ async function main() {
7711
7760
  const settingsDir = getSettingsDir(false);
7712
7761
  await checkVersionAndNotify();
7713
7762
  const tui = new Tui();
7714
- const agents = getBuiltinAgents();
7763
+ const agents = getAvailableAgents();
7715
7764
  const orchestrator = new Orchestrator(settingsDir, {
7716
7765
  onPtyData: (_tabId, data) => {
7717
7766
  tui.writePtyData(data);
@@ -7740,7 +7789,7 @@ async function main() {
7740
7789
  let agent;
7741
7790
  let trackTimeOverride;
7742
7791
  if (args.agent) {
7743
- const found = agents.find((a) => a.id === args.agent);
7792
+ const found = getBuiltinAgents().find((a) => a.id === args.agent);
7744
7793
  agent = found || {
7745
7794
  id: args.agent,
7746
7795
  name: args.agent,