@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.
Files changed (27) hide show
  1. package/out/headless/index.mjs +4 -2
  2. package/out/headless/index.mjs.map +2 -2
  3. package/out/main/index.js +3678 -4
  4. package/out/renderer/assets/{cssMode-DL0XItGB.js → cssMode-CY6x0qXW.js} +3 -3
  5. package/out/renderer/assets/{freemarker2-CrOEuDcF.js → freemarker2-BXSW9BAX.js} +1 -1
  6. package/out/renderer/assets/{handlebars-D4QYaBof.js → handlebars-BYUZ1IOs.js} +1 -1
  7. package/out/renderer/assets/{html-B2Dqk2ai.js → html-DPocQM4t.js} +1 -1
  8. package/out/renderer/assets/{htmlMode-CdZ0Prhd.js → htmlMode-CsPinKYA.js} +3 -3
  9. package/out/renderer/assets/{index-pZmE1QXB.js → index-Bml7oDn9.js} +84 -36
  10. package/out/renderer/assets/{index-CJ6RsQWP.css → index-DK9wLFFm.css} +146 -0
  11. package/out/renderer/assets/{javascript-CK8zNQXj.js → javascript-_HVGB-lj.js} +2 -2
  12. package/out/renderer/assets/{jsonMode-Cewaellc.js → jsonMode-JbrRQBOU.js} +3 -3
  13. package/out/renderer/assets/{liquid-Bd3GPNs2.js → liquid-B7izKdqo.js} +1 -1
  14. package/out/renderer/assets/{lspLanguageFeatures-DSDH7BnA.js → lspLanguageFeatures-DzxH499X.js} +1 -1
  15. package/out/renderer/assets/{mdx-CCPVCrXC.js → mdx-CmvUeYLw.js} +1 -1
  16. package/out/renderer/assets/{python-34jOtlcC.js → python-DJqYTFoi.js} +1 -1
  17. package/out/renderer/assets/{razor-DXRw694z.js → razor-CGEA5nUK.js} +1 -1
  18. package/out/renderer/assets/{tsMode-CmND5_wB.js → tsMode-CN0FOHMy.js} +1 -1
  19. package/out/renderer/assets/{typescript-BNNI0Euv.js → typescript-CIn-DSfY.js} +1 -1
  20. package/out/renderer/assets/{xml-CgdndrNB.js → xml-C5t3U2jS.js} +1 -1
  21. package/out/renderer/assets/{yaml-DNWPIf1s.js → yaml-n-Jb6xf1.js} +1 -1
  22. package/out/renderer/index.html +2 -2
  23. package/package.json +6 -4
  24. package/src/main/workerWs.ts +6 -2
  25. package/src/renderer/App.tsx +38 -12
  26. package/src/renderer/components/AgentPicker.tsx +49 -0
  27. package/src/renderer/styles.css +146 -0
@@ -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 WebSocket(url)
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 === WebSocket.OPEN) {
279
+ if (this.ws && this.ws.readyState === WS.OPEN) {
276
280
  this.ws.send(JSON.stringify(data))
277
281
  }
278
282
  }
@@ -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: [33.33, 33.33, 33.34],
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">&#x1F4C2;</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
+ }
@@ -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;