@phenx-inc/ctlsurf 0.3.14 → 0.3.15

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 (31) hide show
  1. package/out/headless/index.mjs +49 -3
  2. package/out/headless/index.mjs.map +2 -2
  3. package/out/main/index.js +48 -2
  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/preload/index.ts +7 -0
  30. package/src/renderer/App.tsx +19 -1
  31. package/src/renderer/styles.css +18 -0
@@ -40,6 +40,9 @@ export interface OrchestratorEvents {
40
40
  onWorkerMessage: (message: IncomingMessage) => void
41
41
  onWorkerRegistered: (data: { worker_id: string; folder_id: string | null; status: string }) => void
42
42
  onCwdChanged: () => void
43
+ // Human-readable name of the connected ctlsurf project (folder), or null
44
+ // when no project is connected. Optional — only the desktop header uses it.
45
+ onProjectChanged?: (name: string | null) => void
43
46
  }
44
47
 
45
48
  interface TabState {
@@ -89,6 +92,7 @@ export class Orchestrator {
89
92
  }
90
93
  private noProjectPollTimer: ReturnType<typeof setInterval> | null = null
91
94
  private noProjectPollCwd: string | null = null
95
+ private currentProjectName: string | null = null
92
96
 
93
97
  constructor(settingsDir: string, events: OrchestratorEvents) {
94
98
  this.settingsDir = settingsDir
@@ -116,12 +120,14 @@ export class Orchestrator {
116
120
  log(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`)
117
121
  events.onWorkerRegistered(data)
118
122
  if (!data.folder_id) {
123
+ this.setProjectName(null)
119
124
  events.onWorkerStatus('no_project')
120
125
  if (this.currentCwd && data.status !== 'pending_approval') {
121
126
  this.startNoProjectPolling(this.currentCwd)
122
127
  }
123
128
  } else {
124
129
  this.stopNoProjectPolling()
130
+ this.resolveProjectName(data.folder_id)
125
131
  }
126
132
  },
127
133
  onTerminalInput: (data: string) => {
@@ -165,6 +171,29 @@ export class Orchestrator {
165
171
  return this.currentCwd
166
172
  }
167
173
 
174
+ // Name of the connected ctlsurf project (folder) for the desktop header.
175
+ get projectName(): string | null {
176
+ return this.currentProjectName
177
+ }
178
+
179
+ private setProjectName(name: string | null): void {
180
+ if (this.currentProjectName === name) return
181
+ this.currentProjectName = name
182
+ this.events.onProjectChanged?.(name)
183
+ }
184
+
185
+ // Resolve the connected folder's human-readable name. Best-effort: a failed
186
+ // lookup just leaves the project name unset rather than blocking anything.
187
+ private async resolveProjectName(folderId: string): Promise<void> {
188
+ try {
189
+ const folder = await this.ctlsurfApi.getFolder(folderId)
190
+ const name = folder?.name ?? folder?.title
191
+ this.setProjectName(typeof name === 'string' && name ? name : null)
192
+ } catch (err) {
193
+ log(`[worker-ws] Failed to resolve project name for folder ${folderId}: ${err}`)
194
+ }
195
+ }
196
+
168
197
  get agent(): AgentConfig | null {
169
198
  return this.currentAgent
170
199
  }
@@ -44,6 +44,13 @@ const api = {
44
44
  ipcRenderer.invoke('app:homePath'),
45
45
  getCwd: (): Promise<string> =>
46
46
  ipcRenderer.invoke('app:cwd'),
47
+ getProjectName: (): Promise<string | null> =>
48
+ ipcRenderer.invoke('app:projectName'),
49
+ onProjectChanged: (callback: (name: string | null) => void) => {
50
+ const listener = (_event: Electron.IpcRendererEvent, name: string | null) => callback(name)
51
+ ipcRenderer.on('app:projectChanged', listener)
52
+ return () => ipcRenderer.removeListener('app:projectChanged', listener)
53
+ },
47
54
  browseCwd: (): Promise<string | null> =>
48
55
  ipcRenderer.invoke('app:browseCwd'),
49
56
  getVersion: (): Promise<string> =>
@@ -36,6 +36,8 @@ declare global {
36
36
  getDefaultAgent: () => Promise<AgentConfig>
37
37
  getHomePath: () => Promise<string>
38
38
  getCwd: () => Promise<string>
39
+ getProjectName: () => Promise<string | null>
40
+ onProjectChanged: (callback: (name: string | null) => void) => () => void
39
41
  browseCwd: () => Promise<string | null>
40
42
  getSetting: (key: string) => Promise<string | null>
41
43
  setSetting: (key: string, value: string) => Promise<{ ok: boolean }>
@@ -112,6 +114,7 @@ export default function App() {
112
114
  const [showSettings, setShowSettings] = useState(false)
113
115
  const [wsStatus, setWsStatus] = useState('disconnected')
114
116
  const [cwd, setCwd] = useState<string | null>(null)
117
+ const [projectName, setProjectName] = useState<string | null>(null)
115
118
  const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null)
116
119
 
117
120
  // Multi-tab state
@@ -140,10 +143,17 @@ export default function App() {
140
143
  const initialCwd = await window.worker.getCwd().catch(() => window.worker.getHomePath())
141
144
  setCwd(initialCwd)
142
145
  cwdRef.current = initialCwd
146
+ // Connected ctlsurf project name for the header (null until a worker registers)
147
+ window.worker.getProjectName().then(setProjectName).catch(() => { /* ignore */ })
143
148
  }
144
149
  init()
145
150
  }, [])
146
151
 
152
+ useEffect(() => {
153
+ const unsub = window.worker.onProjectChanged((name) => setProjectName(name))
154
+ return unsub
155
+ }, [])
156
+
147
157
  useEffect(() => {
148
158
  const unsub = window.worker.onWorkerStatus((status) => setWsStatus(status))
149
159
  return unsub
@@ -404,7 +414,15 @@ export default function App() {
404
414
  return (
405
415
  <div className="app">
406
416
  <div className="titlebar">
407
- <span className="titlebar-title">ctlsurf-worker</span>
417
+ <span className="titlebar-title">
418
+ ctlsurf
419
+ {projectName && (
420
+ <>
421
+ <span className="titlebar-title-sep">·</span>
422
+ <span className="titlebar-project" title={projectName}>{projectName}</span>
423
+ </>
424
+ )}
425
+ </span>
408
426
  <div className="titlebar-controls">
409
427
  <button className="titlebar-btn" onClick={() => setShowSettings(true)} title="Settings">
410
428
  Settings
@@ -36,6 +36,24 @@ html, body, #root {
36
36
  .titlebar-title {
37
37
  font-weight: 600;
38
38
  color: #c0caf5;
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 6px;
42
+ min-width: 0;
43
+ }
44
+
45
+ .titlebar-title-sep {
46
+ color: #565f89;
47
+ font-weight: 400;
48
+ }
49
+
50
+ .titlebar-project {
51
+ color: #7aa2f7;
52
+ font-weight: 600;
53
+ white-space: nowrap;
54
+ overflow: hidden;
55
+ text-overflow: ellipsis;
56
+ max-width: 280px;
39
57
  }
40
58
 
41
59
  .titlebar-controls {