@phenx-inc/ctlsurf 0.1.2 → 0.1.3
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 +4 -2
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +3678 -4
- package/out/renderer/assets/{cssMode-DL0XItGB.js → cssMode-CY6x0qXW.js} +3 -3
- package/out/renderer/assets/{freemarker2-CrOEuDcF.js → freemarker2-BXSW9BAX.js} +1 -1
- package/out/renderer/assets/{handlebars-D4QYaBof.js → handlebars-BYUZ1IOs.js} +1 -1
- package/out/renderer/assets/{html-B2Dqk2ai.js → html-DPocQM4t.js} +1 -1
- package/out/renderer/assets/{htmlMode-CdZ0Prhd.js → htmlMode-CsPinKYA.js} +3 -3
- package/out/renderer/assets/{index-pZmE1QXB.js → index-Bml7oDn9.js} +84 -36
- package/out/renderer/assets/{index-CJ6RsQWP.css → index-DK9wLFFm.css} +146 -0
- package/out/renderer/assets/{javascript-CK8zNQXj.js → javascript-_HVGB-lj.js} +2 -2
- package/out/renderer/assets/{jsonMode-Cewaellc.js → jsonMode-JbrRQBOU.js} +3 -3
- package/out/renderer/assets/{liquid-Bd3GPNs2.js → liquid-B7izKdqo.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DSDH7BnA.js → lspLanguageFeatures-DzxH499X.js} +1 -1
- package/out/renderer/assets/{mdx-CCPVCrXC.js → mdx-CmvUeYLw.js} +1 -1
- package/out/renderer/assets/{python-34jOtlcC.js → python-DJqYTFoi.js} +1 -1
- package/out/renderer/assets/{razor-DXRw694z.js → razor-CGEA5nUK.js} +1 -1
- package/out/renderer/assets/{tsMode-CmND5_wB.js → tsMode-CN0FOHMy.js} +1 -1
- package/out/renderer/assets/{typescript-BNNI0Euv.js → typescript-CIn-DSfY.js} +1 -1
- package/out/renderer/assets/{xml-CgdndrNB.js → xml-C5t3U2jS.js} +1 -1
- package/out/renderer/assets/{yaml-DNWPIf1s.js → yaml-n-Jb6xf1.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +6 -4
- package/src/main/workerWs.ts +6 -2
- package/src/renderer/App.tsx +38 -12
- package/src/renderer/components/AgentPicker.tsx +49 -0
- package/src/renderer/styles.css +146 -0
package/src/main/workerWs.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import os from 'os'
|
|
2
2
|
import crypto from 'crypto'
|
|
3
|
+
import WsModule from 'ws'
|
|
4
|
+
|
|
5
|
+
// Use native WebSocket if available (Node 22+), otherwise fall back to ws package
|
|
6
|
+
const WS: typeof WebSocket = typeof WebSocket !== 'undefined' ? WebSocket : WsModule as any
|
|
3
7
|
|
|
4
8
|
function log(...args: unknown[]): void {
|
|
5
9
|
try { console.log(...args) } catch { /* EPIPE safe */ }
|
|
@@ -169,7 +173,7 @@ export class WorkerWsClient {
|
|
|
169
173
|
log(`[worker-ws] Connecting to ${url.replace(/token=.*/, 'token=***')}...`)
|
|
170
174
|
|
|
171
175
|
try {
|
|
172
|
-
this.ws = new
|
|
176
|
+
this.ws = new WS(url) as unknown as WebSocket
|
|
173
177
|
} catch (err) {
|
|
174
178
|
log('[worker-ws] Failed to create WebSocket:', err)
|
|
175
179
|
this.scheduleReconnect()
|
|
@@ -272,7 +276,7 @@ export class WorkerWsClient {
|
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
private send(data: Record<string, unknown>): void {
|
|
275
|
-
if (this.ws && this.ws.readyState ===
|
|
279
|
+
if (this.ws && this.ws.readyState === WS.OPEN) {
|
|
276
280
|
this.ws.send(JSON.stringify(data))
|
|
277
281
|
}
|
|
278
282
|
}
|
package/src/renderer/App.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'
|
|
|
2
2
|
import { TerminalPanel } from './components/TerminalPanel'
|
|
3
3
|
import { CtlsurfPanel } from './components/CtlsurfPanel'
|
|
4
4
|
import { EditorPanel } from './components/EditorPanel'
|
|
5
|
+
import { AgentPicker } from './components/AgentPicker'
|
|
5
6
|
import {
|
|
6
7
|
PaneLayout,
|
|
7
8
|
type LayoutNode,
|
|
@@ -63,11 +64,10 @@ const DEFAULT_LAYOUT: LayoutNode = {
|
|
|
63
64
|
type: 'split',
|
|
64
65
|
direction: 'horizontal',
|
|
65
66
|
children: [
|
|
66
|
-
{ type: 'leaf', paneId: 'editor' },
|
|
67
67
|
{ type: 'leaf', paneId: 'terminal' },
|
|
68
68
|
{ type: 'leaf', paneId: 'ctlsurf' },
|
|
69
69
|
],
|
|
70
|
-
sizes: [
|
|
70
|
+
sizes: [50, 50],
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const ALL_PANE_IDS = ['editor', 'terminal', 'ctlsurf']
|
|
@@ -86,14 +86,18 @@ export default function App() {
|
|
|
86
86
|
// Track which panes are in the layout tree
|
|
87
87
|
const visiblePaneIds = findPaneIds(layout)
|
|
88
88
|
|
|
89
|
+
const [showAgentPicker, setShowAgentPicker] = useState(true)
|
|
90
|
+
|
|
89
91
|
useEffect(() => {
|
|
90
92
|
async function init() {
|
|
91
93
|
const list = await window.worker.listAgents()
|
|
92
|
-
const defaultAgent = await window.worker.getDefaultAgent()
|
|
93
94
|
setAgents(list)
|
|
94
|
-
setSelectedAgent(defaultAgent)
|
|
95
95
|
const status = await window.worker.getWorkerStatus()
|
|
96
96
|
setWsStatus(status)
|
|
97
|
+
// Load initial cwd for the picker
|
|
98
|
+
const initialCwd = await window.worker.getCwd().catch(() => window.worker.getHomePath())
|
|
99
|
+
setCwd(initialCwd)
|
|
100
|
+
cwdRef.current = initialCwd
|
|
97
101
|
}
|
|
98
102
|
init()
|
|
99
103
|
}, [])
|
|
@@ -232,6 +236,18 @@ export default function App() {
|
|
|
232
236
|
<button className="titlebar-btn" onClick={() => setShowSettings(true)} title="Settings">
|
|
233
237
|
Settings
|
|
234
238
|
</button>
|
|
239
|
+
<span className="titlebar-separator" />
|
|
240
|
+
{agents.map(a => (
|
|
241
|
+
<button
|
|
242
|
+
key={a.id}
|
|
243
|
+
className={`titlebar-agent-btn ${selectedAgent?.id === a.id ? 'active' : ''}`}
|
|
244
|
+
onClick={() => handleAgentChange(a.id)}
|
|
245
|
+
title={a.description}
|
|
246
|
+
>
|
|
247
|
+
{a.name}
|
|
248
|
+
</button>
|
|
249
|
+
))}
|
|
250
|
+
<span className="titlebar-separator" />
|
|
235
251
|
{ALL_PANE_IDS.map(id => (
|
|
236
252
|
<button
|
|
237
253
|
key={id}
|
|
@@ -242,14 +258,6 @@ export default function App() {
|
|
|
242
258
|
{id === 'editor' ? 'Editor' : id === 'terminal' ? 'Terminal' : 'ctlsurf'}
|
|
243
259
|
</button>
|
|
244
260
|
))}
|
|
245
|
-
<select
|
|
246
|
-
value={selectedAgent?.id || ''}
|
|
247
|
-
onChange={(e) => handleAgentChange(e.target.value)}
|
|
248
|
-
>
|
|
249
|
-
{agents.map(a => (
|
|
250
|
-
<option key={a.id} value={a.id}>{a.name}</option>
|
|
251
|
-
))}
|
|
252
|
-
</select>
|
|
253
261
|
</div>
|
|
254
262
|
</div>
|
|
255
263
|
|
|
@@ -270,6 +278,24 @@ export default function App() {
|
|
|
270
278
|
|
|
271
279
|
<StatusBar wsStatus={wsStatus} cwd={cwd} onChangeCwd={handleChangeCwd} />
|
|
272
280
|
<SettingsDialog open={showSettings} onClose={() => setShowSettings(false)} />
|
|
281
|
+
|
|
282
|
+
{showAgentPicker && agents.length > 0 && (
|
|
283
|
+
<AgentPicker
|
|
284
|
+
agents={agents}
|
|
285
|
+
cwd={cwd || ''}
|
|
286
|
+
onSelect={(agent) => {
|
|
287
|
+
setSelectedAgent(agent)
|
|
288
|
+
setShowAgentPicker(false)
|
|
289
|
+
}}
|
|
290
|
+
onChangeCwd={async () => {
|
|
291
|
+
const newCwd = await window.worker.browseCwd()
|
|
292
|
+
if (newCwd) {
|
|
293
|
+
setCwd(newCwd)
|
|
294
|
+
cwdRef.current = newCwd
|
|
295
|
+
}
|
|
296
|
+
}}
|
|
297
|
+
/>
|
|
298
|
+
)}
|
|
273
299
|
</div>
|
|
274
300
|
)
|
|
275
301
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
interface AgentConfig {
|
|
2
|
+
id: string
|
|
3
|
+
name: string
|
|
4
|
+
command: string
|
|
5
|
+
args: string[]
|
|
6
|
+
description: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface AgentPickerProps {
|
|
10
|
+
agents: AgentConfig[]
|
|
11
|
+
cwd: string
|
|
12
|
+
onSelect: (agent: AgentConfig) => void
|
|
13
|
+
onChangeCwd: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AgentPicker({ agents, cwd, onSelect, onChangeCwd }: AgentPickerProps) {
|
|
17
|
+
const home = typeof window !== 'undefined' ? '' : ''
|
|
18
|
+
// Shorten home dir for display
|
|
19
|
+
const displayPath = cwd.replace(/^\/Users\/[^/]+/, '~')
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="agent-picker-overlay">
|
|
23
|
+
<div className="agent-picker-modal">
|
|
24
|
+
<div className="agent-picker-brand">ctlsurf</div>
|
|
25
|
+
|
|
26
|
+
<div className="agent-picker-cwd" onClick={onChangeCwd} title="Click to change directory">
|
|
27
|
+
<span className="agent-picker-cwd-icon">📂</span>
|
|
28
|
+
<span className="agent-picker-cwd-path">{displayPath}</span>
|
|
29
|
+
<span className="agent-picker-cwd-change">change</span>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div className="agent-picker-title">SELECT AGENT</div>
|
|
33
|
+
<div className="agent-picker-list">
|
|
34
|
+
{agents.map(a => (
|
|
35
|
+
<button
|
|
36
|
+
key={a.id}
|
|
37
|
+
className="agent-picker-item"
|
|
38
|
+
onClick={() => onSelect(a)}
|
|
39
|
+
>
|
|
40
|
+
<span className="agent-picker-name">{a.name}</span>
|
|
41
|
+
<span className="agent-picker-desc">{a.description}</span>
|
|
42
|
+
</button>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
<div className="agent-picker-hint">Select an agent to start</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
package/src/renderer/styles.css
CHANGED
|
@@ -82,6 +82,36 @@ html, body, #root {
|
|
|
82
82
|
background: #1f2335;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
.titlebar-separator {
|
|
86
|
+
width: 1px;
|
|
87
|
+
height: 16px;
|
|
88
|
+
background: #3b3d57;
|
|
89
|
+
margin: 0 4px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.titlebar-agent-btn {
|
|
93
|
+
background: #2a2b3d;
|
|
94
|
+
color: #565f89;
|
|
95
|
+
border: 1px solid #3b3d57;
|
|
96
|
+
border-radius: 4px;
|
|
97
|
+
padding: 2px 12px;
|
|
98
|
+
font-size: 11px;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition: all 0.15s;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.titlebar-agent-btn:hover {
|
|
104
|
+
color: #a9b1d6;
|
|
105
|
+
border-color: #565f89;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.titlebar-agent-btn.active {
|
|
109
|
+
color: #1a1b26;
|
|
110
|
+
background: #7aa2f7;
|
|
111
|
+
border-color: #7aa2f7;
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
/* Main content area */
|
|
86
116
|
.main-content {
|
|
87
117
|
flex: 1;
|
|
@@ -530,6 +560,122 @@ html, body, #root {
|
|
|
530
560
|
font-size: 13px;
|
|
531
561
|
}
|
|
532
562
|
|
|
563
|
+
/* Agent picker modal */
|
|
564
|
+
.agent-picker-overlay {
|
|
565
|
+
position: fixed;
|
|
566
|
+
inset: 0;
|
|
567
|
+
background: rgba(0, 0, 0, 0.7);
|
|
568
|
+
display: flex;
|
|
569
|
+
align-items: center;
|
|
570
|
+
justify-content: center;
|
|
571
|
+
z-index: 200;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.agent-picker-modal {
|
|
575
|
+
background: #1f2335;
|
|
576
|
+
border: 1px solid #3b3d57;
|
|
577
|
+
border-radius: 12px;
|
|
578
|
+
padding: 32px 40px;
|
|
579
|
+
min-width: 360px;
|
|
580
|
+
text-align: center;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.agent-picker-brand {
|
|
584
|
+
font-size: 22px;
|
|
585
|
+
font-weight: 700;
|
|
586
|
+
color: #7aa2f7;
|
|
587
|
+
margin-bottom: 20px;
|
|
588
|
+
letter-spacing: 0.5px;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.agent-picker-cwd {
|
|
592
|
+
display: flex;
|
|
593
|
+
align-items: center;
|
|
594
|
+
justify-content: center;
|
|
595
|
+
gap: 8px;
|
|
596
|
+
padding: 10px 16px;
|
|
597
|
+
margin-bottom: 20px;
|
|
598
|
+
background: #16161e;
|
|
599
|
+
border: 1px solid #2a2b3d;
|
|
600
|
+
border-radius: 8px;
|
|
601
|
+
cursor: pointer;
|
|
602
|
+
transition: border-color 0.15s;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.agent-picker-cwd:hover {
|
|
606
|
+
border-color: #7aa2f7;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.agent-picker-cwd-icon {
|
|
610
|
+
font-size: 14px;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.agent-picker-cwd-path {
|
|
614
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
615
|
+
font-size: 12px;
|
|
616
|
+
color: #a9b1d6;
|
|
617
|
+
flex: 1;
|
|
618
|
+
text-align: left;
|
|
619
|
+
overflow: hidden;
|
|
620
|
+
text-overflow: ellipsis;
|
|
621
|
+
white-space: nowrap;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.agent-picker-cwd-change {
|
|
625
|
+
font-size: 11px;
|
|
626
|
+
color: #7aa2f7;
|
|
627
|
+
flex-shrink: 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.agent-picker-title {
|
|
631
|
+
font-size: 11px;
|
|
632
|
+
color: #565f89;
|
|
633
|
+
margin-bottom: 12px;
|
|
634
|
+
text-transform: uppercase;
|
|
635
|
+
letter-spacing: 1px;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.agent-picker-list {
|
|
639
|
+
display: flex;
|
|
640
|
+
flex-direction: column;
|
|
641
|
+
gap: 8px;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.agent-picker-item {
|
|
645
|
+
display: flex;
|
|
646
|
+
align-items: center;
|
|
647
|
+
justify-content: space-between;
|
|
648
|
+
padding: 14px 18px;
|
|
649
|
+
background: #16161e;
|
|
650
|
+
border: 1px solid #2a2b3d;
|
|
651
|
+
border-radius: 8px;
|
|
652
|
+
cursor: pointer;
|
|
653
|
+
transition: all 0.15s;
|
|
654
|
+
text-align: left;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.agent-picker-item:hover {
|
|
658
|
+
border-color: #7aa2f7;
|
|
659
|
+
background: #1a1b2e;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.agent-picker-name {
|
|
663
|
+
font-size: 14px;
|
|
664
|
+
font-weight: 600;
|
|
665
|
+
color: #c0caf5;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.agent-picker-desc {
|
|
669
|
+
font-size: 11px;
|
|
670
|
+
color: #565f89;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.agent-picker-hint {
|
|
674
|
+
margin-top: 20px;
|
|
675
|
+
font-size: 11px;
|
|
676
|
+
color: #414868;
|
|
677
|
+
}
|
|
678
|
+
|
|
533
679
|
/* Settings dialog */
|
|
534
680
|
.settings-overlay {
|
|
535
681
|
position: fixed;
|