@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.
- package/out/headless/index.mjs +57 -8
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +56 -7
- package/out/preload/index.js +6 -0
- package/out/renderer/assets/{cssMode-G_SDogBL.js → cssMode-D5dPwEy5.js} +3 -3
- package/out/renderer/assets/{freemarker2-BzEus0h2.js → freemarker2-c5jJjQ9s.js} +1 -1
- package/out/renderer/assets/{handlebars-Et995f6O.js → handlebars-BTbmOxx9.js} +1 -1
- package/out/renderer/assets/{html-D4wgKxPD.js → html-3cIIQcxO.js} +1 -1
- package/out/renderer/assets/{htmlMode-DSxpefzL.js → htmlMode-DYbpW1yY.js} +3 -3
- package/out/renderer/assets/{index-AQ346NMi.css → index-6KvOnYL1.css} +18 -0
- package/out/renderer/assets/{index-ByJTqkiQ.js → index-D2MUZin7.js} +36 -23
- package/out/renderer/assets/{javascript-CzLoo8aq.js → javascript-CDuCMm-6.js} +2 -2
- package/out/renderer/assets/{jsonMode-BrwPy7fY.js → jsonMode-COLqbq0s.js} +3 -3
- package/out/renderer/assets/{liquid-BsfPf6YG.js → liquid-BFcqZizB.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-CxLZ421s.js → lspLanguageFeatures-CbkEcL-z.js} +1 -1
- package/out/renderer/assets/{mdx-CPvHIsAR.js → mdx-DyK93oEE.js} +1 -1
- package/out/renderer/assets/{python-Dr7dCUjG.js → python-D4lCwSVr.js} +1 -1
- package/out/renderer/assets/{razor-a7zjD7Y3.js → razor-DdkE9XVt.js} +1 -1
- package/out/renderer/assets/{tsMode-B7KLV2X6.js → tsMode-BrQ4Fsc-.js} +1 -1
- package/out/renderer/assets/{typescript-Cjuzf37q.js → typescript-BakbYMnC.js} +1 -1
- package/out/renderer/assets/{xml-Yz9xINtk.js → xml-DHDW9Xhp.js} +1 -1
- package/out/renderer/assets/{yaml-DtKnp5J0.js → yaml-1Ayv_J3q.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/agents.ts +36 -1
- package/src/main/headless.ts +5 -3
- package/src/main/index.ts +4 -2
- package/src/main/orchestrator.ts +29 -0
- package/src/main/workerWs.ts +8 -6
- package/src/preload/index.ts +7 -0
- package/src/renderer/App.tsx +19 -1
- package/src/renderer/styles.css +18 -0
package/src/main/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ function log(...args: unknown[]): void {
|
|
|
21
21
|
try { console.log(...args) } catch { /* EPIPE safe */ }
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
import { AgentConfig, getDefaultAgent,
|
|
24
|
+
import { AgentConfig, getDefaultAgent, getAvailableAgents } from './agents'
|
|
25
25
|
import { getSettingsDir } from './settingsDir'
|
|
26
26
|
import { Orchestrator } from './orchestrator'
|
|
27
27
|
|
|
@@ -105,6 +105,7 @@ const orchestrator = new Orchestrator(
|
|
|
105
105
|
onWorkerStatus: (status) => mainWindow?.webContents.send('worker:status', status),
|
|
106
106
|
onWorkerMessage: (message) => mainWindow?.webContents.send('worker:message', message),
|
|
107
107
|
onWorkerRegistered: (data) => mainWindow?.webContents.send('worker:registered', data),
|
|
108
|
+
onProjectChanged: (name) => mainWindow?.webContents.send('app:projectChanged', name),
|
|
108
109
|
onCwdChanged: () => {
|
|
109
110
|
mainWindow?.webContents.send('app:cwdChanged')
|
|
110
111
|
updateProjectBadge(orchestrator.cwd)
|
|
@@ -165,11 +166,12 @@ ipcMain.handle('pty:setActiveTab', (_event, tabId: string) => {
|
|
|
165
166
|
orchestrator.setActiveTab(tabId)
|
|
166
167
|
})
|
|
167
168
|
|
|
168
|
-
ipcMain.handle('agents:list', () =>
|
|
169
|
+
ipcMain.handle('agents:list', () => getAvailableAgents())
|
|
169
170
|
ipcMain.handle('agents:default', () => getDefaultAgent())
|
|
170
171
|
|
|
171
172
|
ipcMain.handle('app:homePath', () => app.getPath('home'))
|
|
172
173
|
ipcMain.handle('app:cwd', () => process.env.CTLSURF_WORKER_CWD || process.cwd())
|
|
174
|
+
ipcMain.handle('app:projectName', () => orchestrator.projectName)
|
|
173
175
|
|
|
174
176
|
ipcMain.handle('app:browseCwd', async () => {
|
|
175
177
|
if (!mainWindow) return null
|
package/src/main/orchestrator.ts
CHANGED
|
@@ -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
|
}
|
package/src/main/workerWs.ts
CHANGED
|
@@ -47,13 +47,10 @@ export class WorkerWsClient {
|
|
|
47
47
|
private workerId: string | null = null
|
|
48
48
|
private _status: WorkerWsStatus = 'disconnected'
|
|
49
49
|
private shouldReconnect = false
|
|
50
|
-
private fingerprint: string
|
|
51
50
|
|
|
52
51
|
constructor(events: WorkerWsEvents, baseUrl?: string) {
|
|
53
52
|
this.events = events
|
|
54
53
|
this.baseUrl = baseUrl || 'wss://app.ctlsurf.com'
|
|
55
|
-
// Generate a stable machine fingerprint
|
|
56
|
-
this.fingerprint = this.generateFingerprint()
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
get status(): WorkerWsStatus {
|
|
@@ -72,8 +69,12 @@ export class WorkerWsClient {
|
|
|
72
69
|
this.baseUrl = url
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
// Per-directory fingerprint: each working directory is a distinct worker, so
|
|
73
|
+
// multiple instances on the same machine (one per project) don't collide as a
|
|
74
|
+
// single worker server-side. cwd is included so the same folder maps to the
|
|
75
|
+
// same worker across restarts.
|
|
76
|
+
private generateFingerprint(cwd: string): string {
|
|
77
|
+
const data = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}:${cwd}`
|
|
77
78
|
return crypto.createHash('sha256').update(data).digest('hex').slice(0, 32)
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -85,7 +86,8 @@ export class WorkerWsClient {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
connect(registration: WorkerRegistration): void {
|
|
88
|
-
|
|
89
|
+
const fingerprint = this.generateFingerprint(registration.cwd)
|
|
90
|
+
this.registration = { ...registration, fingerprint }
|
|
89
91
|
this.shouldReconnect = true
|
|
90
92
|
this.doConnect()
|
|
91
93
|
}
|
package/src/preload/index.ts
CHANGED
|
@@ -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> =>
|
package/src/renderer/App.tsx
CHANGED
|
@@ -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">
|
|
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
|
package/src/renderer/styles.css
CHANGED
|
@@ -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 {
|