opensteer 0.4.4 → 0.4.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Breaking: CLI runtime routing now uses `--session`/`OPENSTEER_SESSION` instead
6
+ of `--name`/cwd/active-session fallback.
7
+ - Breaking: non-interactive CLI calls now require explicit runtime identity via
8
+ `--session`, `OPENSTEER_SESSION`, or `OPENSTEER_CLIENT_ID`.
9
+ - Added `OPENSTEER_CLIENT_ID` support for stable client-scoped default session
10
+ binding.
11
+ - CLI `--name` is now selector-cache namespace only and no longer controls
12
+ daemon/browser routing.
13
+ - Added per-session daemon startup locking + stale-lock recovery and ping-based
14
+ health checks to remove startup races across concurrent commands.
15
+ - Added strict in-daemon request serialization for session commands, while
16
+ keeping `ping` out of the queue for reliable liveness checks.
5
17
  - Breaking: removed legacy `ai` config from `OpensteerConfig`; use top-level `model` instead.
6
18
  - Breaking: `OPENSTEER_AI_MODEL` is no longer supported; use `OPENSTEER_MODEL`.
7
19
  - Breaking: `OPENSTEER_RUNTIME` is no longer supported; use `OPENSTEER_MODE`.
package/README.md CHANGED
@@ -20,6 +20,31 @@ npm install opensteer playwright
20
20
  pnpm add opensteer playwright
21
21
  ```
22
22
 
23
+ ## CLI Session Routing
24
+
25
+ OpenSteer CLI now separates runtime routing from selector caching:
26
+
27
+ - Runtime routing: `--session` or `OPENSTEER_SESSION`
28
+ - Selector cache namespace: `--name` or `OPENSTEER_NAME` (used on `open`)
29
+
30
+ If neither `--session` nor `OPENSTEER_SESSION` is set:
31
+
32
+ - In an interactive terminal, OpenSteer creates/reuses a terminal-scoped default session.
33
+ - In non-interactive environments (agents/CI), it fails fast unless you set
34
+ `OPENSTEER_SESSION` or `OPENSTEER_CLIENT_ID`.
35
+
36
+ Example:
37
+
38
+ ```bash
39
+ export OPENSTEER_SESSION=agent-a
40
+ opensteer open https://example.com --name product-scraper
41
+ opensteer snapshot
42
+ opensteer click 3
43
+ opensteer status
44
+ ```
45
+
46
+ `opensteer status` reports `resolvedSession`, `sessionSource`, `resolvedName`, and `nameSource`.
47
+
23
48
  ## Quickstart
24
49
 
25
50
  ```ts
package/bin/opensteer.mjs CHANGED
@@ -2,10 +2,18 @@
2
2
 
3
3
  import { createHash } from 'crypto'
4
4
  import { spawn } from 'child_process'
5
- import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'fs'
5
+ import {
6
+ closeSync,
7
+ existsSync,
8
+ openSync,
9
+ readFileSync,
10
+ readdirSync,
11
+ unlinkSync,
12
+ writeFileSync,
13
+ } from 'fs'
6
14
  import { connect } from 'net'
7
15
  import { tmpdir } from 'os'
8
- import { basename, dirname, join } from 'path'
16
+ import { dirname, join } from 'path'
9
17
  import { fileURLToPath } from 'url'
10
18
 
11
19
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -14,10 +22,16 @@ const SERVER_SCRIPT = join(__dirname, '..', 'dist', 'cli', 'server.js')
14
22
  const CONNECT_TIMEOUT = 15000
15
23
  const POLL_INTERVAL = 100
16
24
  const RESPONSE_TIMEOUT = 120000
25
+ const HEALTH_TIMEOUT = 1500
17
26
  const RUNTIME_PREFIX = 'opensteer-'
18
27
  const SOCKET_SUFFIX = '.sock'
19
28
  const PID_SUFFIX = '.pid'
29
+ const LOCK_SUFFIX = '.lock'
30
+ const CLIENT_BINDING_PREFIX = `${RUNTIME_PREFIX}client-`
31
+ const CLIENT_BINDING_SUFFIX = '.session'
20
32
  const CLOSE_ALL_REQUEST = { id: 1, command: 'close', args: {} }
33
+ const PING_REQUEST = { id: 1, command: 'ping', args: {} }
34
+ const SESSION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/
21
35
 
22
36
  function getVersion() {
23
37
  try {
@@ -84,59 +98,162 @@ function sanitizeNamespace(value) {
84
98
  return bounded || 'default'
85
99
  }
86
100
 
87
- function getActiveNamespacePath() {
88
- const hash = createHash('md5').update(process.cwd()).digest('hex').slice(0, 16)
89
- return join(tmpdir(), `${RUNTIME_PREFIX}active-${hash}`)
101
+ function isValidSessionId(value) {
102
+ return SESSION_ID_PATTERN.test(value)
90
103
  }
91
104
 
92
- function readActiveNamespace() {
105
+ function validateSessionId(rawValue, label) {
106
+ const value = String(rawValue ?? '').trim()
107
+ if (!value) {
108
+ throw new Error(`${label} cannot be empty.`)
109
+ }
110
+ if (!isValidSessionId(value)) {
111
+ throw new Error(
112
+ `${label} "${value}" is invalid. Use only letters, numbers, underscores, and hyphens.`
113
+ )
114
+ }
115
+ return value
116
+ }
117
+
118
+ function resolveName(flags, session) {
119
+ if (flags.name !== undefined) {
120
+ if (flags.name === true) {
121
+ throw new Error('--name requires a namespace value.')
122
+ }
123
+ const raw = String(flags.name).trim()
124
+ if (raw.length > 0) {
125
+ return { name: sanitizeNamespace(raw), source: 'flag' }
126
+ }
127
+ }
128
+
129
+ if (
130
+ typeof process.env.OPENSTEER_NAME === 'string' &&
131
+ process.env.OPENSTEER_NAME.trim().length > 0
132
+ ) {
133
+ return {
134
+ name: sanitizeNamespace(process.env.OPENSTEER_NAME),
135
+ source: 'env',
136
+ }
137
+ }
138
+
139
+ return { name: sanitizeNamespace(session), source: 'session' }
140
+ }
141
+
142
+ function hashKey(value) {
143
+ return createHash('sha256').update(value).digest('hex')
144
+ }
145
+
146
+ function getClientBindingPath(clientKey) {
147
+ return join(
148
+ tmpdir(),
149
+ `${CLIENT_BINDING_PREFIX}${hashKey(clientKey).slice(0, 24)}${CLIENT_BINDING_SUFFIX}`
150
+ )
151
+ }
152
+
153
+ function readClientBinding(clientKey) {
154
+ const bindingPath = getClientBindingPath(clientKey)
155
+ if (!existsSync(bindingPath)) {
156
+ return null
157
+ }
158
+
93
159
  try {
94
- const filePath = getActiveNamespacePath()
95
- if (!existsSync(filePath)) return null
96
- const ns = readFileSync(filePath, 'utf-8').trim()
97
- return ns || null
160
+ const rawSession = readFileSync(bindingPath, 'utf-8').trim()
161
+ if (!rawSession) {
162
+ unlinkSync(bindingPath)
163
+ return null
164
+ }
165
+ if (!isValidSessionId(rawSession)) {
166
+ unlinkSync(bindingPath)
167
+ return null
168
+ }
169
+ return rawSession
98
170
  } catch {
99
171
  return null
100
172
  }
101
173
  }
102
174
 
103
- function writeActiveNamespace(namespace) {
175
+ function writeClientBinding(clientKey, session) {
104
176
  try {
105
- writeFileSync(getActiveNamespacePath(), namespace)
177
+ writeFileSync(getClientBindingPath(clientKey), session)
106
178
  } catch { /* best-effort */ }
107
179
  }
108
180
 
109
- function resolveNamespace(flags) {
110
- if (flags.name !== undefined && String(flags.name).trim().length > 0) {
111
- return { namespace: sanitizeNamespace(String(flags.name)), source: 'flag' }
181
+ function createDefaultSessionId(prefix, clientKey) {
182
+ return `${prefix}-${hashKey(clientKey).slice(0, 12)}`
183
+ }
184
+
185
+ function isInteractiveTerminal() {
186
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY)
187
+ }
188
+
189
+ function resolveSession(flags) {
190
+ if (flags.session !== undefined) {
191
+ if (flags.session === true) {
192
+ throw new Error('--session requires a session id value.')
193
+ }
194
+
195
+ return {
196
+ session: validateSessionId(flags.session, 'Session id'),
197
+ source: 'flag',
198
+ }
112
199
  }
113
200
 
114
201
  if (
115
- typeof process.env.OPENSTEER_NAME === 'string' &&
116
- process.env.OPENSTEER_NAME.trim().length > 0
202
+ typeof process.env.OPENSTEER_SESSION === 'string' &&
203
+ process.env.OPENSTEER_SESSION.trim().length > 0
117
204
  ) {
118
- return { namespace: sanitizeNamespace(process.env.OPENSTEER_NAME), source: 'env' }
205
+ return {
206
+ session: validateSessionId(
207
+ process.env.OPENSTEER_SESSION,
208
+ 'OPENSTEER_SESSION'
209
+ ),
210
+ source: 'env',
211
+ }
119
212
  }
120
213
 
121
- const active = readActiveNamespace()
122
- if (active && isServerRunning(active)) {
123
- return { namespace: active, source: 'active' }
214
+ if (
215
+ typeof process.env.OPENSTEER_CLIENT_ID === 'string' &&
216
+ process.env.OPENSTEER_CLIENT_ID.trim().length > 0
217
+ ) {
218
+ const clientId = process.env.OPENSTEER_CLIENT_ID.trim()
219
+ const clientKey = `client:${process.cwd()}:${clientId}`
220
+ const bound = readClientBinding(clientKey)
221
+ if (bound) {
222
+ return { session: bound, source: 'client_binding' }
223
+ }
224
+
225
+ const created = createDefaultSessionId('client', clientKey)
226
+ writeClientBinding(clientKey, created)
227
+ return { session: created, source: 'client_binding' }
124
228
  }
125
229
 
126
- const cwdBase = basename(process.cwd())
127
- if (cwdBase && cwdBase !== '.' && cwdBase !== '/') {
128
- return { namespace: sanitizeNamespace(cwdBase), source: 'cwd' }
230
+ if (isInteractiveTerminal()) {
231
+ const ttyKey = `tty:${process.cwd()}:${process.ppid}`
232
+ const bound = readClientBinding(ttyKey)
233
+ if (bound) {
234
+ return { session: bound, source: 'tty_default' }
235
+ }
236
+
237
+ const created = createDefaultSessionId('tty', ttyKey)
238
+ writeClientBinding(ttyKey, created)
239
+ return { session: created, source: 'tty_default' }
129
240
  }
130
241
 
131
- return { namespace: 'default', source: 'default' }
242
+ throw new Error(
243
+ 'No session resolved for this non-interactive command. Set OPENSTEER_SESSION or OPENSTEER_CLIENT_ID, or pass --session <id>.'
244
+ )
132
245
  }
133
246
 
134
- function getSocketPath(namespace) {
135
- return join(tmpdir(), `${RUNTIME_PREFIX}${namespace}${SOCKET_SUFFIX}`)
247
+ function getSocketPath(session) {
248
+ return join(tmpdir(), `${RUNTIME_PREFIX}${session}${SOCKET_SUFFIX}`)
136
249
  }
137
250
 
138
- function getPidPath(namespace) {
139
- return join(tmpdir(), `${RUNTIME_PREFIX}${namespace}${PID_SUFFIX}`)
251
+ function getPidPath(session) {
252
+ return join(tmpdir(), `${RUNTIME_PREFIX}${session}${PID_SUFFIX}`)
253
+ }
254
+
255
+ function getLockPath(session) {
256
+ return join(tmpdir(), `${RUNTIME_PREFIX}${session}${LOCK_SUFFIX}`)
140
257
  }
141
258
 
142
259
  function buildRequest(command, flags, positional) {
@@ -270,66 +387,36 @@ function isPidAlive(pid) {
270
387
  }
271
388
  }
272
389
 
273
- function cleanStaleFiles(namespace) {
274
- try {
275
- unlinkSync(getSocketPath(namespace))
276
- } catch { }
277
- try {
278
- unlinkSync(getPidPath(namespace))
279
- } catch { }
280
- }
390
+ function cleanStaleFiles(session, options = {}) {
391
+ const removeSocket = options.removeSocket !== false
392
+ const removePid = options.removePid !== false
281
393
 
282
- function isServerRunning(namespace) {
283
- const pidPath = getPidPath(namespace)
284
- const pid = readPid(pidPath)
285
- if (!pid) {
286
- cleanStaleFiles(namespace)
287
- return false
394
+ if (removeSocket) {
395
+ try {
396
+ unlinkSync(getSocketPath(session))
397
+ } catch { }
288
398
  }
289
399
 
290
- if (!isPidAlive(pid)) {
291
- cleanStaleFiles(namespace)
292
- return false
400
+ if (removePid) {
401
+ try {
402
+ unlinkSync(getPidPath(session))
403
+ } catch { }
293
404
  }
294
-
295
- return existsSync(getSocketPath(namespace))
296
405
  }
297
406
 
298
- function startServer(namespace) {
407
+ function startServer(session) {
299
408
  const child = spawn('node', [SERVER_SCRIPT], {
300
409
  detached: true,
301
410
  stdio: ['ignore', 'ignore', 'ignore'],
302
411
  env: {
303
412
  ...process.env,
304
- OPENSTEER_NAME: namespace,
413
+ OPENSTEER_SESSION: session,
305
414
  },
306
415
  })
307
416
  child.unref()
308
417
  }
309
418
 
310
- function waitForSocket(socketPath, timeout) {
311
- return new Promise((resolve, reject) => {
312
- const start = Date.now()
313
-
314
- function poll() {
315
- if (Date.now() - start > timeout) {
316
- reject(new Error('Timed out waiting for server to start'))
317
- return
318
- }
319
-
320
- if (existsSync(socketPath)) {
321
- resolve()
322
- return
323
- }
324
-
325
- setTimeout(poll, POLL_INTERVAL)
326
- }
327
-
328
- poll()
329
- })
330
- }
331
-
332
- function sendCommand(socketPath, request) {
419
+ function sendCommand(socketPath, request, timeoutMs = RESPONSE_TIMEOUT) {
333
420
  return new Promise((resolve, reject) => {
334
421
  const socket = connect(socketPath)
335
422
  let buffer = ''
@@ -341,7 +428,7 @@ function sendCommand(socketPath, request) {
341
428
  socket.destroy()
342
429
  reject(new Error('Response timeout'))
343
430
  }
344
- }, RESPONSE_TIMEOUT)
431
+ }, timeoutMs)
345
432
 
346
433
  socket.on('connect', () => {
347
434
  socket.write(JSON.stringify(request) + '\n')
@@ -381,6 +468,166 @@ function sendCommand(socketPath, request) {
381
468
  })
382
469
  }
383
470
 
471
+ async function pingServer(session) {
472
+ const socketPath = getSocketPath(session)
473
+ if (!existsSync(socketPath)) return false
474
+
475
+ try {
476
+ const response = await sendCommand(socketPath, PING_REQUEST, HEALTH_TIMEOUT)
477
+ return Boolean(response?.ok && response?.result?.pong)
478
+ } catch {
479
+ return false
480
+ }
481
+ }
482
+
483
+ async function isServerHealthy(session) {
484
+ const pid = readPid(getPidPath(session))
485
+ const pidAlive = pid ? isPidAlive(pid) : false
486
+ const socketExists = existsSync(getSocketPath(session))
487
+
488
+ if (pid && !pidAlive) {
489
+ cleanStaleFiles(session)
490
+ return false
491
+ }
492
+
493
+ if (!socketExists) {
494
+ return false
495
+ }
496
+
497
+ const healthy = await pingServer(session)
498
+ if (healthy) {
499
+ return true
500
+ }
501
+
502
+ if (!pid) {
503
+ cleanStaleFiles(session, { removeSocket: true, removePid: false })
504
+ }
505
+
506
+ return false
507
+ }
508
+
509
+ function acquireStartLock(session) {
510
+ const lockPath = getLockPath(session)
511
+
512
+ try {
513
+ const fd = openSync(lockPath, 'wx')
514
+ writeFileSync(
515
+ fd,
516
+ JSON.stringify({
517
+ pid: process.pid,
518
+ createdAt: Date.now(),
519
+ })
520
+ )
521
+ closeSync(fd)
522
+ return true
523
+ } catch {
524
+ return false
525
+ }
526
+ }
527
+
528
+ function releaseStartLock(session) {
529
+ try {
530
+ unlinkSync(getLockPath(session))
531
+ } catch { /* best-effort */ }
532
+ }
533
+
534
+ function recoverStaleStartLock(session) {
535
+ const lockPath = getLockPath(session)
536
+ if (!existsSync(lockPath)) {
537
+ return false
538
+ }
539
+
540
+ try {
541
+ const raw = readFileSync(lockPath, 'utf-8')
542
+ const parsed = JSON.parse(raw)
543
+ const pid =
544
+ parsed && Number.isInteger(parsed.pid) && parsed.pid > 0
545
+ ? parsed.pid
546
+ : null
547
+
548
+ if (!pid || !isPidAlive(pid)) {
549
+ unlinkSync(lockPath)
550
+ return true
551
+ }
552
+
553
+ return false
554
+ } catch {
555
+ try {
556
+ unlinkSync(lockPath)
557
+ return true
558
+ } catch {
559
+ return false
560
+ }
561
+ }
562
+ }
563
+
564
+ async function sleep(ms) {
565
+ await new Promise((resolve) => setTimeout(resolve, ms))
566
+ }
567
+
568
+ async function waitForServerReady(session, timeout) {
569
+ const start = Date.now()
570
+
571
+ while (Date.now() - start <= timeout) {
572
+ if (await isServerHealthy(session)) {
573
+ return
574
+ }
575
+ await sleep(POLL_INTERVAL)
576
+ }
577
+
578
+ throw new Error(`Timed out waiting for server '${session}' to become healthy.`)
579
+ }
580
+
581
+ async function ensureServer(session) {
582
+ if (await isServerHealthy(session)) {
583
+ return
584
+ }
585
+
586
+ if (!existsSync(SERVER_SCRIPT)) {
587
+ throw new Error(
588
+ `Server script not found: ${SERVER_SCRIPT}. Run the build script first.`
589
+ )
590
+ }
591
+
592
+ const deadline = Date.now() + CONNECT_TIMEOUT
593
+
594
+ while (Date.now() < deadline) {
595
+ if (await isServerHealthy(session)) {
596
+ return
597
+ }
598
+
599
+ const existingPid = readPid(getPidPath(session))
600
+ if (existingPid && isPidAlive(existingPid)) {
601
+ await sleep(POLL_INTERVAL)
602
+ continue
603
+ }
604
+
605
+ recoverStaleStartLock(session)
606
+
607
+ if (acquireStartLock(session)) {
608
+ try {
609
+ if (!(await isServerHealthy(session))) {
610
+ startServer(session)
611
+ }
612
+
613
+ await waitForServerReady(
614
+ session,
615
+ Math.max(500, deadline - Date.now())
616
+ )
617
+ return
618
+ } finally {
619
+ releaseStartLock(session)
620
+ }
621
+ }
622
+
623
+ await sleep(POLL_INTERVAL)
624
+ }
625
+
626
+ throw new Error(
627
+ `Failed to start server for session '${session}'. Check that the build is complete.`
628
+ )
629
+ }
630
+
384
631
  function listSessions() {
385
632
  const sessions = []
386
633
  const entries = readdirSync(tmpdir())
@@ -465,11 +712,11 @@ Navigation:
465
712
  forward Go forward
466
713
  reload Reload page
467
714
  close Close browser and server
468
- close --all Close all active namespace-scoped servers
715
+ close --all Close all active session-scoped servers
469
716
 
470
717
  Sessions:
471
- sessions List active namespace-scoped sessions
472
- status Show resolved namespace and session state
718
+ sessions List active session-scoped daemons
719
+ status Show resolved session/name and session state
473
720
 
474
721
  Observation:
475
722
  snapshot [--mode action] Get page snapshot
@@ -515,7 +762,8 @@ Utility:
515
762
  extract <schema-json> Extract structured data
516
763
 
517
764
  Global Flags:
518
- --name <namespace> Session namespace (default: CWD basename or OPENSTEER_NAME)
765
+ --session <id> Runtime session id for daemon/browser routing
766
+ --name <namespace> Selector namespace for cache storage on 'open'
519
767
  --headless Launch browser in headless mode
520
768
  --connect-url <url> Connect to a running browser (e.g. http://localhost:9222)
521
769
  --channel <browser> Use installed browser (chrome, chrome-beta, msedge)
@@ -527,7 +775,9 @@ Global Flags:
527
775
  --version, -v Show version
528
776
 
529
777
  Environment:
530
- OPENSTEER_NAME Default session namespace when --name is omitted
778
+ OPENSTEER_SESSION Runtime session id (equivalent to --session)
779
+ OPENSTEER_CLIENT_ID Stable client identity for default session binding
780
+ OPENSTEER_NAME Default selector namespace for 'open' when --name is omitted
531
781
  OPENSTEER_MODE Runtime mode: "local" (default) or "remote"
532
782
  OPENSTEER_API_KEY Required when remote mode is selected
533
783
  OPENSTEER_BASE_URL Override remote control-plane base URL
@@ -536,26 +786,12 @@ Environment:
536
786
 
537
787
  async function main() {
538
788
  const { command, flags, positional } = parseArgs(process.argv)
539
- const { namespace, source: namespaceSource } = resolveNamespace(flags)
540
- const socketPath = getSocketPath(namespace)
541
789
 
542
790
  if (command === 'sessions') {
543
791
  output({ ok: true, sessions: listSessions() })
544
792
  return
545
793
  }
546
794
 
547
- if (command === 'status') {
548
- output({
549
- ok: true,
550
- namespace,
551
- namespaceSource,
552
- serverRunning: isServerRunning(namespace),
553
- socketPath,
554
- sessions: listSessions(),
555
- })
556
- return
557
- }
558
-
559
795
  if (command === 'close' && flags.all === true) {
560
796
  try {
561
797
  const closed = await closeAllSessions()
@@ -566,36 +802,66 @@ async function main() {
566
802
  return
567
803
  }
568
804
 
805
+ let resolvedSession
806
+ let resolvedName
807
+ try {
808
+ resolvedSession = resolveSession(flags)
809
+ resolvedName = resolveName(flags, resolvedSession.session)
810
+ } catch (err) {
811
+ error(err instanceof Error ? err.message : 'Failed to resolve session')
812
+ }
813
+
814
+ const session = resolvedSession.session
815
+ const sessionSource = resolvedSession.source
816
+ const name = resolvedName.name
817
+ const nameSource = resolvedName.source
818
+ const socketPath = getSocketPath(session)
819
+
820
+ if (command === 'status') {
821
+ output({
822
+ ok: true,
823
+ resolvedSession: session,
824
+ sessionSource,
825
+ resolvedName: name,
826
+ nameSource,
827
+ serverRunning: await isServerHealthy(session),
828
+ socketPath,
829
+ sessions: listSessions(),
830
+ })
831
+ return
832
+ }
833
+
569
834
  delete flags.name
835
+ delete flags.session
570
836
  delete flags.all
837
+
571
838
  const request = buildRequest(command, flags, positional)
839
+ if (command === 'open') {
840
+ request.args.name = name
841
+ }
572
842
 
573
- if (!isServerRunning(namespace)) {
843
+ if (!(await isServerHealthy(session))) {
574
844
  if (command !== 'open') {
575
845
  error(
576
- `No server running for namespace '${namespace}' (resolved from ${namespaceSource}). Run 'opensteer open' first or use 'opensteer sessions' to see active sessions.`
846
+ `No server running for session '${session}' (resolved from ${sessionSource}). Run 'opensteer open' first or use 'opensteer sessions' to see active sessions.`
577
847
  )
578
848
  }
579
- if (!existsSync(SERVER_SCRIPT)) {
849
+
850
+ try {
851
+ await ensureServer(session)
852
+ } catch (err) {
580
853
  error(
581
- `Server script not found: ${SERVER_SCRIPT}. Run the build script first.`
854
+ err instanceof Error
855
+ ? err.message
856
+ : 'Failed to start server. Check that the build is complete.'
582
857
  )
583
858
  }
584
- startServer(namespace)
585
- try {
586
- await waitForSocket(socketPath, CONNECT_TIMEOUT)
587
- } catch {
588
- error('Failed to start server. Check that the build is complete.')
589
- }
590
859
  }
591
860
 
592
861
  try {
593
862
  const response = await sendCommand(socketPath, request)
594
863
 
595
864
  if (response.ok) {
596
- if (command === 'open') {
597
- writeActiveNamespace(namespace)
598
- }
599
865
  output({ ok: true, ...response.result })
600
866
  } else {
601
867
  process.stderr.write(