@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
package/out/main/index.js CHANGED
@@ -59,6 +59,21 @@ function getShellCommand() {
59
59
  if (process.platform === "win32") return "powershell.exe";
60
60
  return process.env.SHELL || "/bin/zsh";
61
61
  }
62
+ function isCommandAvailable(command) {
63
+ const dirs = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
64
+ const isWin = process.platform === "win32";
65
+ const exts = isWin ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
66
+ for (const dir of dirs) {
67
+ for (const ext of exts) {
68
+ try {
69
+ fs.accessSync(path.join(dir, command + ext), isWin ? fs.constants.F_OK : fs.constants.X_OK);
70
+ return true;
71
+ } catch {
72
+ }
73
+ }
74
+ }
75
+ return false;
76
+ }
62
77
  function getBuiltinAgents() {
63
78
  return [
64
79
  {
@@ -85,8 +100,14 @@ function getBuiltinAgents() {
85
100
  }
86
101
  ];
87
102
  }
103
+ function getAvailableAgents() {
104
+ const all = getBuiltinAgents();
105
+ const coding = all.filter((a) => isCodingAgent(a) && isCommandAvailable(a.command));
106
+ const shell = all.filter((a) => !isCodingAgent(a));
107
+ return [...coding, ...shell];
108
+ }
88
109
  function getDefaultAgent() {
89
- return getBuiltinAgents()[0];
110
+ return getAvailableAgents()[0];
90
111
  }
91
112
  function isCodingAgent(agent) {
92
113
  return agent.id !== "shell";
@@ -9932,11 +9953,9 @@ class WorkerWsClient {
9932
9953
  workerId = null;
9933
9954
  _status = "disconnected";
9934
9955
  shouldReconnect = false;
9935
- fingerprint;
9936
9956
  constructor(events, baseUrl) {
9937
9957
  this.events = events;
9938
9958
  this.baseUrl = baseUrl || "wss://app.ctlsurf.com";
9939
- this.fingerprint = this.generateFingerprint();
9940
9959
  }
9941
9960
  get status() {
9942
9961
  return this._status;
@@ -9950,8 +9969,12 @@ class WorkerWsClient {
9950
9969
  setBaseUrl(url) {
9951
9970
  this.baseUrl = url;
9952
9971
  }
9953
- generateFingerprint() {
9954
- const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}`;
9972
+ // Per-directory fingerprint: each working directory is a distinct worker, so
9973
+ // multiple instances on the same machine (one per project) don't collide as a
9974
+ // single worker server-side. cwd is included so the same folder maps to the
9975
+ // same worker across restarts.
9976
+ generateFingerprint(cwd) {
9977
+ const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}:${cwd}`;
9955
9978
  return require$$1.createHash("sha256").update(data).digest("hex").slice(0, 32);
9956
9979
  }
9957
9980
  setStatus(status) {
@@ -9961,7 +9984,8 @@ class WorkerWsClient {
9961
9984
  }
9962
9985
  }
9963
9986
  connect(registration) {
9964
- this.registration = { ...registration, fingerprint: this.fingerprint };
9987
+ const fingerprint = this.generateFingerprint(registration.cwd);
9988
+ this.registration = { ...registration, fingerprint };
9965
9989
  this.shouldReconnect = true;
9966
9990
  this.doConnect();
9967
9991
  }
@@ -10694,6 +10718,7 @@ class Orchestrator {
10694
10718
  };
10695
10719
  noProjectPollTimer = null;
10696
10720
  noProjectPollCwd = null;
10721
+ currentProjectName = null;
10697
10722
  constructor(settingsDir, events) {
10698
10723
  this.settingsDir = settingsDir;
10699
10724
  this.events = events;
@@ -10718,12 +10743,14 @@ class Orchestrator {
10718
10743
  log$3(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`);
10719
10744
  events.onWorkerRegistered(data);
10720
10745
  if (!data.folder_id) {
10746
+ this.setProjectName(null);
10721
10747
  events.onWorkerStatus("no_project");
10722
10748
  if (this.currentCwd && data.status !== "pending_approval") {
10723
10749
  this.startNoProjectPolling(this.currentCwd);
10724
10750
  }
10725
10751
  } else {
10726
10752
  this.stopNoProjectPolling();
10753
+ this.resolveProjectName(data.folder_id);
10727
10754
  }
10728
10755
  },
10729
10756
  onTerminalInput: (data) => {
@@ -10758,6 +10785,26 @@ class Orchestrator {
10758
10785
  get cwd() {
10759
10786
  return this.currentCwd;
10760
10787
  }
10788
+ // Name of the connected ctlsurf project (folder) for the desktop header.
10789
+ get projectName() {
10790
+ return this.currentProjectName;
10791
+ }
10792
+ setProjectName(name) {
10793
+ if (this.currentProjectName === name) return;
10794
+ this.currentProjectName = name;
10795
+ this.events.onProjectChanged?.(name);
10796
+ }
10797
+ // Resolve the connected folder's human-readable name. Best-effort: a failed
10798
+ // lookup just leaves the project name unset rather than blocking anything.
10799
+ async resolveProjectName(folderId) {
10800
+ try {
10801
+ const folder = await this.ctlsurfApi.getFolder(folderId);
10802
+ const name = folder?.name ?? folder?.title;
10803
+ this.setProjectName(typeof name === "string" && name ? name : null);
10804
+ } catch (err) {
10805
+ log$3(`[worker-ws] Failed to resolve project name for folder ${folderId}: ${err}`);
10806
+ }
10807
+ }
10761
10808
  get agent() {
10762
10809
  return this.currentAgent;
10763
10810
  }
@@ -11222,6 +11269,7 @@ const orchestrator = new Orchestrator(
11222
11269
  onWorkerStatus: (status) => mainWindow?.webContents.send("worker:status", status),
11223
11270
  onWorkerMessage: (message) => mainWindow?.webContents.send("worker:message", message),
11224
11271
  onWorkerRegistered: (data) => mainWindow?.webContents.send("worker:registered", data),
11272
+ onProjectChanged: (name) => mainWindow?.webContents.send("app:projectChanged", name),
11225
11273
  onCwdChanged: () => {
11226
11274
  mainWindow?.webContents.send("app:cwdChanged");
11227
11275
  updateProjectBadge(orchestrator.cwd);
@@ -11269,10 +11317,11 @@ electron.ipcMain.handle("pty:kill", async (_event, tabId) => {
11269
11317
  electron.ipcMain.handle("pty:setActiveTab", (_event, tabId) => {
11270
11318
  orchestrator.setActiveTab(tabId);
11271
11319
  });
11272
- electron.ipcMain.handle("agents:list", () => getBuiltinAgents());
11320
+ electron.ipcMain.handle("agents:list", () => getAvailableAgents());
11273
11321
  electron.ipcMain.handle("agents:default", () => getDefaultAgent());
11274
11322
  electron.ipcMain.handle("app:homePath", () => electron.app.getPath("home"));
11275
11323
  electron.ipcMain.handle("app:cwd", () => process.env.CTLSURF_WORKER_CWD || process.cwd());
11324
+ electron.ipcMain.handle("app:projectName", () => orchestrator.projectName);
11276
11325
  electron.ipcMain.handle("app:browseCwd", async () => {
11277
11326
  if (!mainWindow) return null;
11278
11327
  const result = await electron.dialog.showOpenDialog(mainWindow, {
@@ -24,6 +24,12 @@ const api = {
24
24
  // App info
25
25
  getHomePath: () => electron.ipcRenderer.invoke("app:homePath"),
26
26
  getCwd: () => electron.ipcRenderer.invoke("app:cwd"),
27
+ getProjectName: () => electron.ipcRenderer.invoke("app:projectName"),
28
+ onProjectChanged: (callback) => {
29
+ const listener = (_event, name) => callback(name);
30
+ electron.ipcRenderer.on("app:projectChanged", listener);
31
+ return () => electron.ipcRenderer.removeListener("app:projectChanged", listener);
32
+ },
27
33
  browseCwd: () => electron.ipcRenderer.invoke("app:browseCwd"),
28
34
  getVersion: () => electron.ipcRenderer.invoke("app:getVersion"),
29
35
  getUpdateInfo: () => electron.ipcRenderer.invoke("app:getUpdateInfo"),
@@ -1,6 +1,6 @@
1
- import { c as createWebWorker, l as languages } from "./index-ByJTqkiQ.js";
2
- import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-CxLZ421s.js";
3
- import { h, i, j, t, k } from "./lspLanguageFeatures-CxLZ421s.js";
1
+ import { c as createWebWorker, l as languages } from "./index-D2MUZin7.js";
2
+ import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-CbkEcL-z.js";
3
+ import { h, i, j, t, k } from "./lspLanguageFeatures-CbkEcL-z.js";
4
4
  const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
5
5
  class WorkerManager {
6
6
  constructor(defaults) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "assign",
4
4
  "flush",
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,6 +1,6 @@
1
- import { c as createWebWorker, l as languages } from "./index-ByJTqkiQ.js";
2
- import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-CxLZ421s.js";
3
- import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-CxLZ421s.js";
1
+ import { c as createWebWorker, l as languages } from "./index-D2MUZin7.js";
2
+ import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-CbkEcL-z.js";
3
+ import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-CbkEcL-z.js";
4
4
  const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
5
5
  class WorkerManager {
6
6
  constructor(defaults) {
@@ -7422,6 +7422,24 @@ html, body, #root {
7422
7422
  .titlebar-title {
7423
7423
  font-weight: 600;
7424
7424
  color: #c0caf5;
7425
+ display: flex;
7426
+ align-items: center;
7427
+ gap: 6px;
7428
+ min-width: 0;
7429
+ }
7430
+
7431
+ .titlebar-title-sep {
7432
+ color: #565f89;
7433
+ font-weight: 400;
7434
+ }
7435
+
7436
+ .titlebar-project {
7437
+ color: #7aa2f7;
7438
+ font-weight: 600;
7439
+ white-space: nowrap;
7440
+ overflow: hidden;
7441
+ text-overflow: ellipsis;
7442
+ max-width: 280px;
7425
7443
  }
7426
7444
 
7427
7445
  .titlebar-controls {
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-G_SDogBL.js","./lspLanguageFeatures-CxLZ421s.js","./htmlMode-DSxpefzL.js","./jsonMode-BrwPy7fY.js","./javascript-CzLoo8aq.js","./typescript-Cjuzf37q.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-D5dPwEy5.js","./lspLanguageFeatures-CbkEcL-z.js","./htmlMode-DYbpW1yY.js","./jsonMode-COLqbq0s.js","./javascript-CDuCMm-6.js","./typescript-BakbYMnC.js"])))=>i.map(i=>d[i]);
2
2
  function getDefaultExportFromCjs(x) {
3
3
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
4
  }
@@ -206558,7 +206558,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
206558
206558
  modeConfigurationDefault$2
206559
206559
  );
206560
206560
  function getMode$3() {
206561
- return __vitePreload(() => import("./cssMode-G_SDogBL.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
206561
+ return __vitePreload(() => import("./cssMode-D5dPwEy5.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
206562
206562
  }
206563
206563
  languages.onLanguage("less", () => {
206564
206564
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -206663,7 +206663,7 @@ const razorLanguageService = registerHTMLLanguageService(
206663
206663
  );
206664
206664
  const razorDefaults = razorLanguageService.defaults;
206665
206665
  function getMode$2() {
206666
- return __vitePreload(() => import("./htmlMode-DSxpefzL.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
206666
+ return __vitePreload(() => import("./htmlMode-DYbpW1yY.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
206667
206667
  }
206668
206668
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
206669
206669
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -206747,7 +206747,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
206747
206747
  );
206748
206748
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
206749
206749
  function getMode$1() {
206750
- return __vitePreload(() => import("./jsonMode-BrwPy7fY.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
206750
+ return __vitePreload(() => import("./jsonMode-COLqbq0s.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
206751
206751
  }
206752
206752
  languages.register({
206753
206753
  id: "json",
@@ -206993,7 +206993,7 @@ const getJavaScriptWorker = () => {
206993
206993
  return getMode().then((mode) => mode.getJavaScriptWorker());
206994
206994
  };
206995
206995
  function getMode() {
206996
- return __vitePreload(() => import("./tsMode-B7KLV2X6.js"), true ? [] : void 0, import.meta.url);
206996
+ return __vitePreload(() => import("./tsMode-BrQ4Fsc-.js"), true ? [] : void 0, import.meta.url);
206997
206997
  }
206998
206998
  languages.onLanguage("typescript", () => {
206999
206999
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -207188,49 +207188,49 @@ registerLanguage({
207188
207188
  extensions: [".ftl", ".ftlh", ".ftlx"],
207189
207189
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
207190
207190
  loader: () => {
207191
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207191
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207192
207192
  }
207193
207193
  });
207194
207194
  registerLanguage({
207195
207195
  id: "freemarker2.tag-angle.interpolation-dollar",
207196
207196
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
207197
207197
  loader: () => {
207198
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207198
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207199
207199
  }
207200
207200
  });
207201
207201
  registerLanguage({
207202
207202
  id: "freemarker2.tag-bracket.interpolation-dollar",
207203
207203
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
207204
207204
  loader: () => {
207205
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207205
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207206
207206
  }
207207
207207
  });
207208
207208
  registerLanguage({
207209
207209
  id: "freemarker2.tag-angle.interpolation-bracket",
207210
207210
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
207211
207211
  loader: () => {
207212
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207212
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207213
207213
  }
207214
207214
  });
207215
207215
  registerLanguage({
207216
207216
  id: "freemarker2.tag-bracket.interpolation-bracket",
207217
207217
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
207218
207218
  loader: () => {
207219
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207219
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207220
207220
  }
207221
207221
  });
207222
207222
  registerLanguage({
207223
207223
  id: "freemarker2.tag-auto.interpolation-dollar",
207224
207224
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
207225
207225
  loader: () => {
207226
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207226
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207227
207227
  }
207228
207228
  });
207229
207229
  registerLanguage({
207230
207230
  id: "freemarker2.tag-auto.interpolation-bracket",
207231
207231
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
207232
207232
  loader: () => {
207233
- return __vitePreload(() => import("./freemarker2-BzEus0h2.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207233
+ return __vitePreload(() => import("./freemarker2-c5jJjQ9s.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207234
207234
  }
207235
207235
  });
207236
207236
  registerLanguage({
@@ -207251,7 +207251,7 @@ registerLanguage({
207251
207251
  extensions: [".handlebars", ".hbs"],
207252
207252
  aliases: ["Handlebars", "handlebars", "hbs"],
207253
207253
  mimetypes: ["text/x-handlebars-template"],
207254
- loader: () => __vitePreload(() => import("./handlebars-Et995f6O.js"), true ? [] : void 0, import.meta.url)
207254
+ loader: () => __vitePreload(() => import("./handlebars-BTbmOxx9.js"), true ? [] : void 0, import.meta.url)
207255
207255
  });
207256
207256
  registerLanguage({
207257
207257
  id: "hcl",
@@ -207264,7 +207264,7 @@ registerLanguage({
207264
207264
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
207265
207265
  aliases: ["HTML", "htm", "html", "xhtml"],
207266
207266
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
207267
- loader: () => __vitePreload(() => import("./html-D4wgKxPD.js"), true ? [] : void 0, import.meta.url)
207267
+ loader: () => __vitePreload(() => import("./html-3cIIQcxO.js"), true ? [] : void 0, import.meta.url)
207268
207268
  });
207269
207269
  registerLanguage({
207270
207270
  id: "ini",
@@ -207287,7 +207287,7 @@ registerLanguage({
207287
207287
  filenames: ["jakefile"],
207288
207288
  aliases: ["JavaScript", "javascript", "js"],
207289
207289
  mimetypes: ["text/javascript"],
207290
- loader: () => __vitePreload(() => import("./javascript-CzLoo8aq.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207290
+ loader: () => __vitePreload(() => import("./javascript-CDuCMm-6.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207291
207291
  });
207292
207292
  registerLanguage({
207293
207293
  id: "julia",
@@ -207326,7 +207326,7 @@ registerLanguage({
207326
207326
  extensions: [".liquid", ".html.liquid"],
207327
207327
  aliases: ["Liquid", "liquid"],
207328
207328
  mimetypes: ["application/liquid"],
207329
- loader: () => __vitePreload(() => import("./liquid-BsfPf6YG.js"), true ? [] : void 0, import.meta.url)
207329
+ loader: () => __vitePreload(() => import("./liquid-BFcqZizB.js"), true ? [] : void 0, import.meta.url)
207330
207330
  });
207331
207331
  registerLanguage({
207332
207332
  id: "m3",
@@ -207344,7 +207344,7 @@ registerLanguage({
207344
207344
  id: "mdx",
207345
207345
  extensions: [".mdx"],
207346
207346
  aliases: ["MDX", "mdx"],
207347
- loader: () => __vitePreload(() => import("./mdx-CPvHIsAR.js"), true ? [] : void 0, import.meta.url)
207347
+ loader: () => __vitePreload(() => import("./mdx-DyK93oEE.js"), true ? [] : void 0, import.meta.url)
207348
207348
  });
207349
207349
  registerLanguage({
207350
207350
  id: "mips",
@@ -207443,7 +207443,7 @@ registerLanguage({
207443
207443
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
207444
207444
  aliases: ["Python", "py"],
207445
207445
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
207446
- loader: () => __vitePreload(() => import("./python-Dr7dCUjG.js"), true ? [] : void 0, import.meta.url)
207446
+ loader: () => __vitePreload(() => import("./python-D4lCwSVr.js"), true ? [] : void 0, import.meta.url)
207447
207447
  });
207448
207448
  registerLanguage({
207449
207449
  id: "qsharp",
@@ -207462,7 +207462,7 @@ registerLanguage({
207462
207462
  extensions: [".cshtml"],
207463
207463
  aliases: ["Razor", "razor"],
207464
207464
  mimetypes: ["text/x-cshtml"],
207465
- loader: () => __vitePreload(() => import("./razor-a7zjD7Y3.js"), true ? [] : void 0, import.meta.url)
207465
+ loader: () => __vitePreload(() => import("./razor-DdkE9XVt.js"), true ? [] : void 0, import.meta.url)
207466
207466
  });
207467
207467
  registerLanguage({
207468
207468
  id: "redis",
@@ -207595,7 +207595,7 @@ registerLanguage({
207595
207595
  aliases: ["TypeScript", "ts", "typescript"],
207596
207596
  mimetypes: ["text/typescript"],
207597
207597
  loader: () => {
207598
- return __vitePreload(() => import("./typescript-Cjuzf37q.js"), true ? [] : void 0, import.meta.url);
207598
+ return __vitePreload(() => import("./typescript-BakbYMnC.js"), true ? [] : void 0, import.meta.url);
207599
207599
  }
207600
207600
  });
207601
207601
  registerLanguage({
@@ -207640,14 +207640,14 @@ registerLanguage({
207640
207640
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
207641
207641
  aliases: ["XML", "xml"],
207642
207642
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
207643
- loader: () => __vitePreload(() => import("./xml-Yz9xINtk.js"), true ? [] : void 0, import.meta.url)
207643
+ loader: () => __vitePreload(() => import("./xml-DHDW9Xhp.js"), true ? [] : void 0, import.meta.url)
207644
207644
  });
207645
207645
  registerLanguage({
207646
207646
  id: "yaml",
207647
207647
  extensions: [".yaml", ".yml"],
207648
207648
  aliases: ["YAML", "yaml", "YML", "yml"],
207649
207649
  mimetypes: ["application/x-yaml", "text/x-yaml"],
207650
- loader: () => __vitePreload(() => import("./yaml-DtKnp5J0.js"), true ? [] : void 0, import.meta.url)
207650
+ loader: () => __vitePreload(() => import("./yaml-1Ayv_J3q.js"), true ? [] : void 0, import.meta.url)
207651
207651
  });
207652
207652
  var __defProp = Object.defineProperty;
207653
207653
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -212039,6 +212039,7 @@ function App() {
212039
212039
  const [showSettings, setShowSettings] = reactExports.useState(false);
212040
212040
  const [wsStatus, setWsStatus] = reactExports.useState("disconnected");
212041
212041
  const [cwd2, setCwd] = reactExports.useState(null);
212042
+ const [projectName, setProjectName] = reactExports.useState(null);
212042
212043
  const [updateInfo, setUpdateInfo] = reactExports.useState(null);
212043
212044
  const [tabs, setTabs] = reactExports.useState(() => {
212044
212045
  const id = nextTabId();
@@ -212059,9 +212060,15 @@ function App() {
212059
212060
  const initialCwd = await window.worker.getCwd().catch(() => window.worker.getHomePath());
212060
212061
  setCwd(initialCwd);
212061
212062
  cwdRef.current = initialCwd;
212063
+ window.worker.getProjectName().then(setProjectName).catch(() => {
212064
+ });
212062
212065
  }
212063
212066
  init2();
212064
212067
  }, []);
212068
+ reactExports.useEffect(() => {
212069
+ const unsub = window.worker.onProjectChanged((name) => setProjectName(name));
212070
+ return unsub;
212071
+ }, []);
212065
212072
  reactExports.useEffect(() => {
212066
212073
  const unsub = window.worker.onWorkerStatus((status2) => setWsStatus(status2));
212067
212074
  return unsub;
@@ -212305,7 +212312,13 @@ function App() {
212305
212312
  ];
212306
212313
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "app", children: [
212307
212314
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "titlebar", children: [
212308
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-title", children: "ctlsurf-worker" }),
212315
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "titlebar-title", children: [
212316
+ "ctlsurf",
212317
+ projectName && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
212318
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-title-sep", children: "·" }),
212319
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-project", title: projectName, children: projectName })
212320
+ ] })
212321
+ ] }),
212309
212322
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "titlebar-controls", children: [
212310
212323
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "titlebar-btn", onClick: () => setShowSettings(true), title: "Settings", children: "Settings" }),
212311
212324
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -1,5 +1,5 @@
1
- import { conf as conf$1, language as language$1 } from "./typescript-Cjuzf37q.js";
2
- import "./index-ByJTqkiQ.js";
1
+ import { conf as conf$1, language as language$1 } from "./typescript-BakbYMnC.js";
2
+ import "./index-D2MUZin7.js";
3
3
  const conf = conf$1;
4
4
  const language = {
5
5
  // Set defaultToken to invalid to see what you do not tokenize yet
@@ -1,6 +1,6 @@
1
- import { c as createWebWorker, l as languages, e as editor } from "./index-ByJTqkiQ.js";
2
- import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-CxLZ421s.js";
3
- import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-CxLZ421s.js";
1
+ import { c as createWebWorker, l as languages, e as editor } from "./index-D2MUZin7.js";
2
+ import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-CbkEcL-z.js";
3
+ import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-CbkEcL-z.js";
4
4
  const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
5
5
  class WorkerManager {
6
6
  constructor(defaults) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-ByJTqkiQ.js";
1
+ import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-D2MUZin7.js";
2
2
  var DocumentUri;
3
3
  (function(DocumentUri2) {
4
4
  function is(value) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["{/*", "*/}"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#",
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-ByJTqkiQ.js";
1
+ import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-D2MUZin7.js";
2
2
  class WorkerManager {
3
3
  constructor(_modeId, _defaults) {
4
4
  this._modeId = _modeId;
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const conf = {
3
3
  wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
4
4
  comments: {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["<!--", "-->"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-ByJTqkiQ.js";
1
+ import { l as languages } from "./index-D2MUZin7.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#"
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>ctlsurf-worker</title>
7
- <script type="module" crossorigin src="./assets/index-ByJTqkiQ.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-AQ346NMi.css">
7
+ <script type="module" crossorigin src="./assets/index-D2MUZin7.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-6KvOnYL1.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
5
5
  "main": "out/main/index.js",
6
6
  "bin": {
@@ -1,3 +1,6 @@
1
+ import { accessSync, constants } from 'fs'
2
+ import { join, delimiter } from 'path'
3
+
1
4
  export interface AgentConfig {
2
5
  id: string
3
6
  name: string
@@ -11,6 +14,27 @@ function getShellCommand(): string {
11
14
  return process.env.SHELL || '/bin/zsh'
12
15
  }
13
16
 
17
+ // Resolve a bare command name against PATH (like `which`/`where`), so we only
18
+ // offer coding agents whose CLI is actually installed. Both launch modes
19
+ // inherit the user's shell PATH (the `ctlsurf` launcher runs under their
20
+ // shell), so process.env.PATH is the right thing to scan.
21
+ function isCommandAvailable(command: string): boolean {
22
+ const dirs = (process.env.PATH || '').split(delimiter).filter(Boolean)
23
+ const isWin = process.platform === 'win32'
24
+ const exts = isWin
25
+ ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
26
+ : ['']
27
+ for (const dir of dirs) {
28
+ for (const ext of exts) {
29
+ try {
30
+ accessSync(join(dir, command + ext), isWin ? constants.F_OK : constants.X_OK)
31
+ return true
32
+ } catch { /* not in this dir */ }
33
+ }
34
+ }
35
+ return false
36
+ }
37
+
14
38
  export function getBuiltinAgents(): AgentConfig[] {
15
39
  return [
16
40
  {
@@ -37,8 +61,19 @@ export function getBuiltinAgents(): AgentConfig[] {
37
61
  ]
38
62
  }
39
63
 
64
+ // Builtin agents filtered to those actually usable: coding agents only when
65
+ // their CLI is installed (on PATH), with the shell always offered last. If no
66
+ // coding agent is installed the list collapses to just the shell.
67
+ export function getAvailableAgents(): AgentConfig[] {
68
+ const all = getBuiltinAgents()
69
+ const coding = all.filter(a => isCodingAgent(a) && isCommandAvailable(a.command))
70
+ const shell = all.filter(a => !isCodingAgent(a))
71
+ return [...coding, ...shell]
72
+ }
73
+
40
74
  export function getDefaultAgent(): AgentConfig {
41
- return getBuiltinAgents()[0]
75
+ // First available coding agent, or the shell when none are installed.
76
+ return getAvailableAgents()[0]
42
77
  }
43
78
 
44
79
  export function isCodingAgent(agent: AgentConfig): boolean {
@@ -29,7 +29,7 @@ setSilent(true)
29
29
 
30
30
  import { Orchestrator } from './orchestrator'
31
31
  import { getSettingsDir } from './settingsDir'
32
- import { getBuiltinAgents, isCodingAgent, type AgentConfig } from './agents'
32
+ import { getBuiltinAgents, getAvailableAgents, isCodingAgent, type AgentConfig } from './agents'
33
33
  import { Tui } from './tui'
34
34
  import { fetchLatestNpmVersion, compareSemver, NPM_PACKAGE } from './updateCheck'
35
35
 
@@ -103,7 +103,9 @@ async function main() {
103
103
  await checkVersionAndNotify()
104
104
 
105
105
  const tui = new Tui()
106
- const agents = getBuiltinAgents()
106
+ // Picker only offers installed coding agents (+ shell, last). Explicit
107
+ // --agent still resolves against the full builtin list below.
108
+ const agents = getAvailableAgents()
107
109
 
108
110
  // ─── Orchestrator (loaded early so picker can read profile defaults) ──
109
111
 
@@ -140,7 +142,7 @@ async function main() {
140
142
  let trackTimeOverride: boolean | undefined
141
143
 
142
144
  if (args.agent) {
143
- const found = agents.find(a => a.id === args.agent)
145
+ const found = getBuiltinAgents().find(a => a.id === args.agent)
144
146
  agent = found || {
145
147
  id: args.agent,
146
148
  name: args.agent,