opensteer 0.6.13 → 0.7.0

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 (62) hide show
  1. package/README.md +256 -184
  2. package/dist/chunk-PQYA6IX2.js +32571 -0
  3. package/dist/chunk-PQYA6IX2.js.map +1 -0
  4. package/dist/cli/bin.cjs +38201 -0
  5. package/dist/cli/bin.cjs.map +1 -0
  6. package/dist/cli/bin.d.cts +1 -0
  7. package/dist/cli/bin.d.ts +1 -0
  8. package/dist/cli/bin.js +5612 -0
  9. package/dist/cli/bin.js.map +1 -0
  10. package/dist/index.cjs +31309 -16009
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +4440 -670
  13. package/dist/index.d.ts +4440 -670
  14. package/dist/index.js +438 -378
  15. package/dist/index.js.map +1 -0
  16. package/package.json +56 -62
  17. package/skills/README.md +21 -20
  18. package/skills/opensteer/SKILL.md +60 -194
  19. package/skills/opensteer/references/cli-reference.md +69 -113
  20. package/skills/opensteer/references/request-workflow.md +81 -0
  21. package/skills/opensteer/references/sdk-reference.md +101 -154
  22. package/CHANGELOG.md +0 -75
  23. package/bin/opensteer.mjs +0 -1423
  24. package/dist/browser-profile-client-CGXc0-P9.d.cts +0 -228
  25. package/dist/browser-profile-client-DHLzMf-K.d.ts +0 -228
  26. package/dist/chunk-2ES46WCO.js +0 -1437
  27. package/dist/chunk-3H5RRIMZ.js +0 -69
  28. package/dist/chunk-AVXUMEDG.js +0 -62
  29. package/dist/chunk-DN3GI5CH.js +0 -57
  30. package/dist/chunk-FAHE5DB2.js +0 -190
  31. package/dist/chunk-HBTSQ2V4.js +0 -15259
  32. package/dist/chunk-K5CL76MG.js +0 -81
  33. package/dist/chunk-U724TBY6.js +0 -1262
  34. package/dist/chunk-ZRCFF546.js +0 -77
  35. package/dist/cli/auth.cjs +0 -2022
  36. package/dist/cli/auth.d.cts +0 -114
  37. package/dist/cli/auth.d.ts +0 -114
  38. package/dist/cli/auth.js +0 -15
  39. package/dist/cli/local-profile.cjs +0 -197
  40. package/dist/cli/local-profile.d.cts +0 -18
  41. package/dist/cli/local-profile.d.ts +0 -18
  42. package/dist/cli/local-profile.js +0 -97
  43. package/dist/cli/profile.cjs +0 -18548
  44. package/dist/cli/profile.d.cts +0 -79
  45. package/dist/cli/profile.d.ts +0 -79
  46. package/dist/cli/profile.js +0 -1328
  47. package/dist/cli/server.cjs +0 -17232
  48. package/dist/cli/server.d.cts +0 -2
  49. package/dist/cli/server.d.ts +0 -2
  50. package/dist/cli/server.js +0 -977
  51. package/dist/cli/skills-installer.cjs +0 -230
  52. package/dist/cli/skills-installer.d.cts +0 -28
  53. package/dist/cli/skills-installer.d.ts +0 -28
  54. package/dist/cli/skills-installer.js +0 -201
  55. package/dist/extractor-4Q3TFZJB.js +0 -8
  56. package/dist/resolver-MGN64KCP.js +0 -7
  57. package/dist/types-Cr10igF3.d.cts +0 -345
  58. package/dist/types-Cr10igF3.d.ts +0 -345
  59. package/skills/electron/SKILL.md +0 -87
  60. package/skills/electron/references/opensteer-electron-recipes.md +0 -88
  61. package/skills/electron/references/opensteer-electron-workflow.md +0 -85
  62. package/skills/opensteer/references/examples.md +0 -118
package/bin/opensteer.mjs DELETED
@@ -1,1423 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { createHash } from 'crypto'
4
- import { spawn } from 'child_process'
5
- import {
6
- closeSync,
7
- existsSync,
8
- openSync,
9
- realpathSync,
10
- readFileSync,
11
- readdirSync,
12
- unlinkSync,
13
- writeFileSync,
14
- } from 'fs'
15
- import { connect } from 'net'
16
- import { tmpdir } from 'os'
17
- import { dirname, join } from 'path'
18
- import { fileURLToPath, pathToFileURL } from 'url'
19
-
20
- const __dirname = dirname(fileURLToPath(import.meta.url))
21
- const SERVER_SCRIPT = join(__dirname, '..', 'dist', 'cli', 'server.js')
22
- const SKILLS_INSTALLER_SCRIPT = join(
23
- __dirname,
24
- '..',
25
- 'dist',
26
- 'cli',
27
- 'skills-installer.js'
28
- )
29
- const PROFILE_CLI_SCRIPT = join(__dirname, '..', 'dist', 'cli', 'profile.js')
30
- const LOCAL_PROFILE_CLI_SCRIPT = join(
31
- __dirname,
32
- '..',
33
- 'dist',
34
- 'cli',
35
- 'local-profile.js'
36
- )
37
- const AUTH_CLI_SCRIPT = join(__dirname, '..', 'dist', 'cli', 'auth.js')
38
- const SKILLS_HELP_TEXT = `Usage: opensteer skills <install|add> [options]
39
-
40
- Installs the first-party Opensteer skill using the upstream "skills" CLI.
41
-
42
- Commands:
43
- install Install the opensteer skill
44
- add Alias for install
45
-
46
- Supported Options:
47
- -a, --agent <agents...> Target specific agent(s)
48
- -g, --global Install globally
49
- -y, --yes Skip confirmations
50
- --copy Copy files instead of symlinking
51
- --all Install to all agents
52
- -h, --help Show this help
53
-
54
- Examples:
55
- opensteer skills install
56
- opensteer skills add --agent codex --global --yes
57
- opensteer skills install --all --yes
58
- `
59
- const PROFILE_HELP_TEXT = `Usage: opensteer profile <command> [options]
60
-
61
- Manage cloud browser profiles and sync local cookie state into cloud profiles.
62
-
63
- Commands:
64
- list
65
- create --name <name>
66
- sync
67
-
68
- Run "opensteer profile --help" after building for full command details.
69
- `
70
- const LOCAL_PROFILE_HELP_TEXT = `Usage: opensteer local-profile <command> [options]
71
-
72
- Inspect local Chrome profiles for real-browser mode.
73
-
74
- Commands:
75
- list
76
-
77
- Run "opensteer local-profile --help" after building for full command details.
78
- `
79
- const AUTH_HELP_TEXT = `Usage: opensteer auth <command> [options]
80
-
81
- Authenticate Opensteer CLI with Opensteer Cloud.
82
-
83
- Commands:
84
- login
85
- status
86
- logout
87
-
88
- Run "opensteer auth --help" after building for full command details.
89
- `
90
-
91
- const CONNECT_TIMEOUT = 15000
92
- const POLL_INTERVAL = 100
93
- const RESPONSE_TIMEOUT = 120000
94
- const HEALTH_TIMEOUT = 1500
95
- const RUNTIME_PREFIX = 'opensteer-'
96
- const SOCKET_SUFFIX = '.sock'
97
- const PID_SUFFIX = '.pid'
98
- const LOCK_SUFFIX = '.lock'
99
- const METADATA_SUFFIX = '.meta.json'
100
- const CLIENT_BINDING_PREFIX = `${RUNTIME_PREFIX}client-`
101
- const CLIENT_BINDING_SUFFIX = '.session'
102
- const CLOSE_ALL_REQUEST = { id: 1, command: 'close', args: {} }
103
- const PING_REQUEST = { id: 1, command: 'ping', args: {} }
104
- const SESSION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/
105
- const RUNTIME_SESSION_PREFIX = 'sc-'
106
- const BOOLEAN_FLAGS = new Set(['all', 'headless', 'headed', 'json'])
107
-
108
- function getVersion() {
109
- try {
110
- const pkgPath = join(__dirname, '..', 'package.json')
111
- return JSON.parse(readFileSync(pkgPath, 'utf-8')).version
112
- } catch {
113
- return 'unknown'
114
- }
115
- }
116
-
117
- function parseArgs(argv) {
118
- const args = argv.slice(2)
119
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
120
- printHelp()
121
- process.exit(0)
122
- }
123
-
124
- if (args[0] === '--version' || args[0] === '-v') {
125
- console.log(getVersion())
126
- process.exit(0)
127
- }
128
-
129
- const command = args[0]
130
- const flags = {}
131
- const positional = []
132
-
133
- for (let i = 1; i < args.length; i++) {
134
- const arg = args[i]
135
- if (arg.startsWith('--')) {
136
- const key = arg.slice(2)
137
- const next = args[i + 1]
138
- if (
139
- BOOLEAN_FLAGS.has(key) &&
140
- next !== undefined &&
141
- next !== 'true' &&
142
- next !== 'false'
143
- ) {
144
- flags[key] = true
145
- } else if (next !== undefined && !next.startsWith('--')) {
146
- flags[key] = parseValue(next)
147
- i++
148
- } else {
149
- flags[key] = true
150
- }
151
- } else {
152
- positional.push(arg)
153
- }
154
- }
155
-
156
- return { command, flags, positional }
157
- }
158
-
159
- function parseValue(str) {
160
- if (str === 'true') return true
161
- if (str === 'false') return false
162
- const num = Number(str)
163
- if (!Number.isNaN(num) && str.trim() !== '') return num
164
- return str
165
- }
166
-
167
- function sanitizeNamespace(value) {
168
- const trimmed = String(value || '').trim()
169
- if (!trimmed || trimmed === '.' || trimmed === '..') {
170
- return 'default'
171
- }
172
-
173
- const replaced = trimmed.replace(/[^a-zA-Z0-9_-]+/g, '_')
174
- const collapsed = replaced.replace(/_+/g, '_')
175
- const bounded = collapsed.replace(/^_+|_+$/g, '')
176
-
177
- return bounded || 'default'
178
- }
179
-
180
- function isValidSessionId(value) {
181
- return SESSION_ID_PATTERN.test(value)
182
- }
183
-
184
- function validateSessionId(rawValue, label) {
185
- const value = String(rawValue ?? '').trim()
186
- if (!value) {
187
- throw new Error(`${label} cannot be empty.`)
188
- }
189
- if (!isValidSessionId(value)) {
190
- throw new Error(
191
- `${label} "${value}" is invalid. Use only letters, numbers, underscores, and hyphens.`
192
- )
193
- }
194
- return value
195
- }
196
-
197
- function resolveName(flags, session) {
198
- if (flags.name !== undefined) {
199
- if (flags.name === true) {
200
- throw new Error('--name requires a namespace value.')
201
- }
202
- const raw = String(flags.name).trim()
203
- if (raw.length > 0) {
204
- return { name: sanitizeNamespace(raw), source: 'flag' }
205
- }
206
- }
207
-
208
- if (
209
- typeof process.env.OPENSTEER_NAME === 'string' &&
210
- process.env.OPENSTEER_NAME.trim().length > 0
211
- ) {
212
- return {
213
- name: sanitizeNamespace(process.env.OPENSTEER_NAME),
214
- source: 'env',
215
- }
216
- }
217
-
218
- return { name: sanitizeNamespace(session), source: 'session' }
219
- }
220
-
221
- function hashKey(value) {
222
- return createHash('sha256').update(value).digest('hex')
223
- }
224
-
225
- function getClientBindingPath(clientKey) {
226
- return join(
227
- tmpdir(),
228
- `${CLIENT_BINDING_PREFIX}${hashKey(clientKey).slice(0, 24)}${CLIENT_BINDING_SUFFIX}`
229
- )
230
- }
231
-
232
- function readClientBinding(clientKey) {
233
- const bindingPath = getClientBindingPath(clientKey)
234
- if (!existsSync(bindingPath)) {
235
- return null
236
- }
237
-
238
- try {
239
- const rawSession = readFileSync(bindingPath, 'utf-8').trim()
240
- if (!rawSession) {
241
- unlinkSync(bindingPath)
242
- return null
243
- }
244
- if (!isValidSessionId(rawSession)) {
245
- unlinkSync(bindingPath)
246
- return null
247
- }
248
- return rawSession
249
- } catch {
250
- return null
251
- }
252
- }
253
-
254
- function writeClientBinding(clientKey, session) {
255
- try {
256
- writeFileSync(getClientBindingPath(clientKey), session)
257
- } catch { /* best-effort */ }
258
- }
259
-
260
- function createDefaultSessionId(prefix, clientKey) {
261
- return `${prefix}-${hashKey(clientKey).slice(0, 12)}`
262
- }
263
-
264
- function isInteractiveTerminal() {
265
- return Boolean(process.stdin.isTTY && process.stdout.isTTY)
266
- }
267
-
268
- function resolveScopeDir() {
269
- const cwd = process.cwd()
270
- try {
271
- return realpathSync(cwd)
272
- } catch {
273
- return cwd
274
- }
275
- }
276
-
277
- function buildRuntimeSession(scopeDir, logicalSession) {
278
- return `${RUNTIME_SESSION_PREFIX}${hashKey(`${scopeDir}:${logicalSession}`).slice(0, 24)}`
279
- }
280
-
281
- function resolveSession(flags, scopeDir) {
282
- if (flags.session !== undefined) {
283
- if (flags.session === true) {
284
- throw new Error('--session requires a session id value.')
285
- }
286
-
287
- return {
288
- session: validateSessionId(flags.session, 'Session id'),
289
- source: 'flag',
290
- }
291
- }
292
-
293
- if (
294
- typeof process.env.OPENSTEER_SESSION === 'string' &&
295
- process.env.OPENSTEER_SESSION.trim().length > 0
296
- ) {
297
- return {
298
- session: validateSessionId(
299
- process.env.OPENSTEER_SESSION,
300
- 'OPENSTEER_SESSION'
301
- ),
302
- source: 'env',
303
- }
304
- }
305
-
306
- if (
307
- typeof process.env.OPENSTEER_CLIENT_ID === 'string' &&
308
- process.env.OPENSTEER_CLIENT_ID.trim().length > 0
309
- ) {
310
- const clientId = process.env.OPENSTEER_CLIENT_ID.trim()
311
- const clientKey = `client:${scopeDir}:${clientId}`
312
- const bound = readClientBinding(clientKey)
313
- if (bound) {
314
- return { session: bound, source: 'client_binding' }
315
- }
316
-
317
- const created = createDefaultSessionId('client', clientKey)
318
- writeClientBinding(clientKey, created)
319
- return { session: created, source: 'client_binding' }
320
- }
321
-
322
- if (isInteractiveTerminal()) {
323
- const ttyKey = `tty:${scopeDir}:${process.ppid}`
324
- const bound = readClientBinding(ttyKey)
325
- if (bound) {
326
- return { session: bound, source: 'tty_default' }
327
- }
328
-
329
- const created = createDefaultSessionId('tty', ttyKey)
330
- writeClientBinding(ttyKey, created)
331
- return { session: created, source: 'tty_default' }
332
- }
333
-
334
- throw new Error(
335
- 'No session resolved for this non-interactive command. Set OPENSTEER_SESSION or OPENSTEER_CLIENT_ID, or pass --session <id>.'
336
- )
337
- }
338
-
339
- function getSocketPath(session) {
340
- return join(tmpdir(), `${RUNTIME_PREFIX}${session}${SOCKET_SUFFIX}`)
341
- }
342
-
343
- function getPidPath(session) {
344
- return join(tmpdir(), `${RUNTIME_PREFIX}${session}${PID_SUFFIX}`)
345
- }
346
-
347
- function getLockPath(session) {
348
- return join(tmpdir(), `${RUNTIME_PREFIX}${session}${LOCK_SUFFIX}`)
349
- }
350
-
351
- function getMetadataPath(session) {
352
- return join(tmpdir(), `${RUNTIME_PREFIX}${session}${METADATA_SUFFIX}`)
353
- }
354
-
355
- function buildRequest(command, flags, positional) {
356
- const id = 1
357
- const globalFlags = {}
358
- for (const key of [
359
- 'headless',
360
- 'json',
361
- 'browser',
362
- 'profile',
363
- 'cdp-url',
364
- 'user-data-dir',
365
- 'browser-path',
366
- 'cloud-profile-id',
367
- 'cloud-profile-reuse-if-active',
368
- 'cursor',
369
- ]) {
370
- if (key in flags) {
371
- globalFlags[key] = flags[key]
372
- delete flags[key]
373
- }
374
- }
375
-
376
- const args = { ...globalFlags, ...flags }
377
- if ('headed' in flags) {
378
- const headed = flags.headed === false ? false : Boolean(flags.headed)
379
- args.headless = args.headless ?? !headed
380
- delete args.headed
381
- }
382
-
383
- switch (command) {
384
- case 'open':
385
- case 'navigate':
386
- args.url = positional[0] || args.url
387
- break
388
-
389
- case 'click':
390
- case 'dblclick':
391
- case 'rightclick':
392
- case 'hover':
393
- case 'select':
394
- case 'scroll':
395
- case 'get-text':
396
- case 'get-value':
397
- case 'get-attrs':
398
- if (positional[0] !== undefined && args.element === undefined) {
399
- args.element = Number(positional[0])
400
- }
401
- break
402
-
403
- case 'input': {
404
- // input 12 "text" or input "text" --element 12
405
- if (positional.length >= 2) {
406
- const first = Number(positional[0])
407
- if (!Number.isNaN(first)) {
408
- args.element = args.element ?? first
409
- args.text = args.text ?? positional[1]
410
- } else {
411
- args.text = args.text ?? positional[0]
412
- args.element = args.element ?? Number(positional[1])
413
- }
414
- } else if (positional.length === 1) {
415
- args.text = args.text ?? positional[0]
416
- }
417
- break
418
- }
419
-
420
- case 'press':
421
- args.key = positional[0] || args.key
422
- break
423
-
424
- case 'type':
425
- args.text = positional[0] || args.text
426
- break
427
-
428
- case 'get-html':
429
- if (positional[0] && !args.selector) {
430
- args.selector = positional[0]
431
- }
432
- break
433
-
434
- case 'tab-new':
435
- args.url = positional[0] || args.url
436
- break
437
-
438
- case 'tab-switch':
439
- case 'tab-close':
440
- args.index =
441
- positional[0] !== undefined ? Number(positional[0]) : args.index
442
- break
443
-
444
- case 'cookies-export':
445
- case 'cookies-import':
446
- case 'screenshot':
447
- args.file = positional[0] || args.file
448
- break
449
-
450
- case 'eval':
451
- args.expression = positional[0] || args.expression
452
- break
453
-
454
- case 'wait-for':
455
- args.text = positional[0] || args.text
456
- break
457
-
458
- case 'wait-selector':
459
- args.selector = positional[0] || args.selector
460
- break
461
-
462
- case 'extract':
463
- if (positional[0] && !args.schema) {
464
- try {
465
- args.schema = JSON.parse(positional[0])
466
- } catch {
467
- error(`Invalid JSON schema: ${positional[0]}`)
468
- }
469
- }
470
- break
471
-
472
- case 'snapshot':
473
- args.mode = positional[0] || args.mode
474
- break
475
-
476
- case 'cursor':
477
- args.mode = positional[0] || args.mode || 'status'
478
- break
479
- }
480
-
481
- return { id, command, args }
482
- }
483
-
484
- function readPid(pidPath) {
485
- if (!existsSync(pidPath)) {
486
- return null
487
- }
488
-
489
- const parsed = Number.parseInt(readFileSync(pidPath, 'utf-8').trim(), 10)
490
- if (!Number.isInteger(parsed) || parsed <= 0) {
491
- return null
492
- }
493
-
494
- return parsed
495
- }
496
-
497
- function isPidAlive(pid) {
498
- try {
499
- process.kill(pid, 0)
500
- return true
501
- } catch {
502
- return false
503
- }
504
- }
505
-
506
- function cleanStaleFiles(session, options = {}) {
507
- const removeSocket = options.removeSocket !== false
508
- const removePid = options.removePid !== false
509
-
510
- if (removeSocket) {
511
- try {
512
- unlinkSync(getSocketPath(session))
513
- } catch { }
514
- }
515
-
516
- if (removePid) {
517
- try {
518
- unlinkSync(getPidPath(session))
519
- } catch { }
520
- }
521
-
522
- try {
523
- unlinkSync(getMetadataPath(session))
524
- } catch { }
525
- }
526
-
527
- function startServer(runtimeSession, logicalSession, scopeDir) {
528
- const child = spawn('node', [SERVER_SCRIPT], {
529
- detached: true,
530
- stdio: ['ignore', 'ignore', 'ignore'],
531
- env: {
532
- ...process.env,
533
- OPENSTEER_SESSION: runtimeSession,
534
- OPENSTEER_LOGICAL_SESSION: logicalSession,
535
- OPENSTEER_SCOPE_DIR: scopeDir,
536
- },
537
- })
538
- child.unref()
539
- }
540
-
541
- function readMetadata(session) {
542
- const metadataPath = getMetadataPath(session)
543
- if (!existsSync(metadataPath)) {
544
- return null
545
- }
546
-
547
- try {
548
- const raw = JSON.parse(readFileSync(metadataPath, 'utf-8'))
549
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
550
- return null
551
- }
552
-
553
- if (
554
- typeof raw.logicalSession !== 'string' ||
555
- !raw.logicalSession.trim() ||
556
- typeof raw.scopeDir !== 'string' ||
557
- !raw.scopeDir.trim() ||
558
- typeof raw.runtimeSession !== 'string' ||
559
- !raw.runtimeSession.trim()
560
- ) {
561
- return null
562
- }
563
-
564
- return {
565
- logicalSession: raw.logicalSession.trim(),
566
- scopeDir: raw.scopeDir,
567
- runtimeSession: raw.runtimeSession.trim(),
568
- createdAt:
569
- typeof raw.createdAt === 'number' ? raw.createdAt : undefined,
570
- updatedAt:
571
- typeof raw.updatedAt === 'number' ? raw.updatedAt : undefined,
572
- }
573
- } catch {
574
- return null
575
- }
576
- }
577
-
578
- function writeMetadata(runtimeSession, logicalSession, scopeDir) {
579
- const metadataPath = getMetadataPath(runtimeSession)
580
- const existing = readMetadata(runtimeSession)
581
- const now = Date.now()
582
- const payload = {
583
- runtimeSession,
584
- logicalSession,
585
- scopeDir,
586
- createdAt: existing?.createdAt ?? now,
587
- updatedAt: now,
588
- }
589
-
590
- try {
591
- writeFileSync(metadataPath, JSON.stringify(payload, null, 2))
592
- } catch { }
593
- }
594
-
595
- function sendCommand(socketPath, request, timeoutMs = RESPONSE_TIMEOUT) {
596
- return new Promise((resolve, reject) => {
597
- const socket = connect(socketPath)
598
- let buffer = ''
599
- let settled = false
600
-
601
- const timer = setTimeout(() => {
602
- if (!settled) {
603
- settled = true
604
- socket.destroy()
605
- reject(new Error('Response timeout'))
606
- }
607
- }, timeoutMs)
608
-
609
- socket.on('connect', () => {
610
- socket.write(JSON.stringify(request) + '\n')
611
- })
612
-
613
- socket.on('data', (chunk) => {
614
- buffer += chunk.toString()
615
- const idx = buffer.indexOf('\n')
616
- if (idx !== -1) {
617
- const line = buffer.slice(0, idx)
618
- clearTimeout(timer)
619
- settled = true
620
- socket.end()
621
- try {
622
- resolve(JSON.parse(line))
623
- } catch {
624
- reject(new Error('Invalid JSON response from server'))
625
- }
626
- }
627
- })
628
-
629
- socket.on('error', (err) => {
630
- if (!settled) {
631
- clearTimeout(timer)
632
- settled = true
633
- reject(err)
634
- }
635
- })
636
-
637
- socket.on('close', () => {
638
- if (!settled) {
639
- clearTimeout(timer)
640
- settled = true
641
- reject(new Error('Connection closed before response'))
642
- }
643
- })
644
- })
645
- }
646
-
647
- async function pingServer(session) {
648
- const socketPath = getSocketPath(session)
649
- if (!existsSync(socketPath)) return false
650
-
651
- try {
652
- const response = await sendCommand(socketPath, PING_REQUEST, HEALTH_TIMEOUT)
653
- return Boolean(response?.ok && response?.result?.pong)
654
- } catch {
655
- return false
656
- }
657
- }
658
-
659
- async function isServerHealthy(session) {
660
- const pid = readPid(getPidPath(session))
661
- const pidAlive = pid ? isPidAlive(pid) : false
662
- const socketExists = existsSync(getSocketPath(session))
663
-
664
- if (pid && !pidAlive) {
665
- cleanStaleFiles(session)
666
- return false
667
- }
668
-
669
- if (!socketExists) {
670
- return false
671
- }
672
-
673
- const healthy = await pingServer(session)
674
- if (healthy) {
675
- return true
676
- }
677
-
678
- if (!pid) {
679
- cleanStaleFiles(session, { removeSocket: true, removePid: false })
680
- }
681
-
682
- return false
683
- }
684
-
685
- function acquireStartLock(session) {
686
- const lockPath = getLockPath(session)
687
-
688
- try {
689
- const fd = openSync(lockPath, 'wx')
690
- writeFileSync(
691
- fd,
692
- JSON.stringify({
693
- pid: process.pid,
694
- createdAt: Date.now(),
695
- })
696
- )
697
- closeSync(fd)
698
- return true
699
- } catch {
700
- return false
701
- }
702
- }
703
-
704
- function releaseStartLock(session) {
705
- try {
706
- unlinkSync(getLockPath(session))
707
- } catch { /* best-effort */ }
708
- }
709
-
710
- function recoverStaleStartLock(session) {
711
- const lockPath = getLockPath(session)
712
- if (!existsSync(lockPath)) {
713
- return false
714
- }
715
-
716
- try {
717
- const raw = readFileSync(lockPath, 'utf-8')
718
- const parsed = JSON.parse(raw)
719
- const pid =
720
- parsed && Number.isInteger(parsed.pid) && parsed.pid > 0
721
- ? parsed.pid
722
- : null
723
-
724
- if (!pid || !isPidAlive(pid)) {
725
- unlinkSync(lockPath)
726
- return true
727
- }
728
-
729
- return false
730
- } catch {
731
- try {
732
- unlinkSync(lockPath)
733
- return true
734
- } catch {
735
- return false
736
- }
737
- }
738
- }
739
-
740
- async function sleep(ms) {
741
- await new Promise((resolve) => setTimeout(resolve, ms))
742
- }
743
-
744
- async function waitForServerReady(session, timeout) {
745
- const start = Date.now()
746
-
747
- while (Date.now() - start <= timeout) {
748
- if (await isServerHealthy(session)) {
749
- return
750
- }
751
- await sleep(POLL_INTERVAL)
752
- }
753
-
754
- throw new Error(`Timed out waiting for server '${session}' to become healthy.`)
755
- }
756
-
757
- async function ensureServer(context) {
758
- const runtimeSession = context.runtimeSession
759
- if (await isServerHealthy(runtimeSession)) {
760
- writeMetadata(
761
- runtimeSession,
762
- context.logicalSession,
763
- context.scopeDir
764
- )
765
- return
766
- }
767
-
768
- if (!existsSync(SERVER_SCRIPT)) {
769
- throw new Error(
770
- `Server script not found: ${SERVER_SCRIPT}. Run the build script first.`
771
- )
772
- }
773
-
774
- const deadline = Date.now() + CONNECT_TIMEOUT
775
-
776
- while (Date.now() < deadline) {
777
- if (await isServerHealthy(runtimeSession)) {
778
- writeMetadata(
779
- runtimeSession,
780
- context.logicalSession,
781
- context.scopeDir
782
- )
783
- return
784
- }
785
-
786
- const existingPid = readPid(getPidPath(runtimeSession))
787
- if (existingPid && isPidAlive(existingPid)) {
788
- await sleep(POLL_INTERVAL)
789
- continue
790
- }
791
-
792
- recoverStaleStartLock(runtimeSession)
793
-
794
- if (acquireStartLock(runtimeSession)) {
795
- try {
796
- if (!(await isServerHealthy(runtimeSession))) {
797
- startServer(
798
- runtimeSession,
799
- context.logicalSession,
800
- context.scopeDir
801
- )
802
- }
803
-
804
- await waitForServerReady(
805
- runtimeSession,
806
- Math.max(500, deadline - Date.now())
807
- )
808
- writeMetadata(
809
- runtimeSession,
810
- context.logicalSession,
811
- context.scopeDir
812
- )
813
- return
814
- } finally {
815
- releaseStartLock(runtimeSession)
816
- }
817
- }
818
-
819
- await sleep(POLL_INTERVAL)
820
- }
821
-
822
- throw new Error(
823
- `Failed to start server for session '${context.logicalSession}' in cwd scope '${context.scopeDir}' within ${CONNECT_TIMEOUT}ms.`
824
- )
825
- }
826
-
827
- function listSessions() {
828
- const sessions = []
829
- const entries = readdirSync(tmpdir())
830
-
831
- for (const entry of entries) {
832
- if (!entry.startsWith(RUNTIME_PREFIX) || !entry.endsWith(PID_SUFFIX)) {
833
- continue
834
- }
835
-
836
- const runtimeSession = entry.slice(
837
- RUNTIME_PREFIX.length,
838
- entry.length - PID_SUFFIX.length
839
- )
840
- if (!runtimeSession) {
841
- continue
842
- }
843
-
844
- const pid = readPid(join(tmpdir(), entry))
845
- if (!pid || !isPidAlive(pid)) {
846
- cleanStaleFiles(runtimeSession)
847
- continue
848
- }
849
-
850
- const metadata = readMetadata(runtimeSession)
851
- sessions.push({
852
- name: metadata?.logicalSession || runtimeSession,
853
- logicalSession: metadata?.logicalSession || runtimeSession,
854
- runtimeSession,
855
- scopeDir: metadata?.scopeDir || null,
856
- pid,
857
- })
858
- }
859
-
860
- sessions.sort((a, b) => {
861
- const scopeA = a.scopeDir || ''
862
- const scopeB = b.scopeDir || ''
863
- if (scopeA !== scopeB) {
864
- return scopeA.localeCompare(scopeB)
865
- }
866
-
867
- return a.logicalSession.localeCompare(b.logicalSession)
868
- })
869
- return sessions
870
- }
871
-
872
- async function closeAllSessions() {
873
- const sessions = listSessions()
874
- const closed = []
875
- const failures = []
876
-
877
- for (const session of sessions) {
878
- const socketPath = getSocketPath(session.runtimeSession)
879
- if (!existsSync(socketPath)) {
880
- cleanStaleFiles(session.runtimeSession)
881
- continue
882
- }
883
-
884
- try {
885
- const response = await sendCommand(socketPath, CLOSE_ALL_REQUEST)
886
- if (response && response.ok === true) {
887
- closed.push(session)
888
- } else {
889
- failures.push(
890
- `${session.logicalSession} (${session.scopeDir || 'unknown scope'}): ${response?.error || 'unknown close error'}`
891
- )
892
- }
893
- } catch (err) {
894
- failures.push(
895
- `${session.logicalSession} (${session.scopeDir || 'unknown scope'}): ${err instanceof Error ? err.message : String(err)}`
896
- )
897
- }
898
- }
899
-
900
- if (failures.length > 0) {
901
- throw new Error(`Failed to close sessions: ${failures.join('; ')}`)
902
- }
903
-
904
- return closed
905
- }
906
-
907
- function output(data) {
908
- process.stdout.write(JSON.stringify(data) + '\n')
909
- }
910
-
911
- function error(msg) {
912
- process.stderr.write(JSON.stringify({ ok: false, error: msg }) + '\n')
913
- process.exit(1)
914
- }
915
-
916
- function normalizeFailedResponse(response) {
917
- const info = toObject(response?.errorInfo)
918
-
919
- let message = 'Request failed.'
920
- if (typeof info?.message === 'string' && info.message.trim()) {
921
- message = info.message.trim()
922
- } else if (typeof response?.error === 'string' && response.error.trim()) {
923
- message = response.error.trim()
924
- }
925
-
926
- return {
927
- ok: false,
928
- error: message,
929
- ...(info && typeof info.code === 'string' && info.code.trim()
930
- ? { code: info.code.trim() }
931
- : {}),
932
- ...(toObject(info?.details)
933
- ? { details: info.details }
934
- : {}),
935
- ...(info ? { errorInfo: info } : {}),
936
- }
937
- }
938
-
939
- function formatTransportFailure(error, context) {
940
- const message = error instanceof Error ? error.message : String(error)
941
- return `${context}: ${message}`
942
- }
943
-
944
- function toObject(value) {
945
- if (!value || typeof value !== 'object' || Array.isArray(value)) return null
946
- return value
947
- }
948
-
949
- function isSkillsHelpRequest(args) {
950
- if (args.length === 0) return true
951
-
952
- const [subcommand, ...rest] = args
953
- if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
954
- return true
955
- }
956
-
957
- if (subcommand !== 'install' && subcommand !== 'add') {
958
- return false
959
- }
960
-
961
- return rest.includes('--help') || rest.includes('-h')
962
- }
963
-
964
- function printSkillsHelp() {
965
- process.stdout.write(SKILLS_HELP_TEXT)
966
- }
967
-
968
- async function runSkillsSubcommand(args) {
969
- if (isSkillsHelpRequest(args)) {
970
- printSkillsHelp()
971
- return
972
- }
973
-
974
- if (!existsSync(SKILLS_INSTALLER_SCRIPT)) {
975
- throw new Error(
976
- `Skills installer module not found: ${SKILLS_INSTALLER_SCRIPT}. Run the build script first.`
977
- )
978
- }
979
-
980
- const moduleUrl = pathToFileURL(SKILLS_INSTALLER_SCRIPT).href
981
- const { runOpensteerSkillsInstaller } = await import(moduleUrl)
982
-
983
- const exitCode = await runOpensteerSkillsInstaller(args)
984
- if (exitCode !== 0) {
985
- process.exit(exitCode)
986
- }
987
- }
988
-
989
- async function runProfileSubcommand(args) {
990
- if (isProfileHelpRequest(args)) {
991
- process.stdout.write(PROFILE_HELP_TEXT)
992
- return
993
- }
994
-
995
- if (!existsSync(PROFILE_CLI_SCRIPT)) {
996
- throw new Error(
997
- `Profile CLI module was not found at "${PROFILE_CLI_SCRIPT}". Run "npm run build" to generate dist artifacts.`
998
- )
999
- }
1000
-
1001
- const moduleUrl = pathToFileURL(PROFILE_CLI_SCRIPT).href
1002
- const { runOpensteerProfileCli } = await import(moduleUrl)
1003
- const exitCode = await runOpensteerProfileCli(args)
1004
- if (exitCode !== 0) {
1005
- process.exit(exitCode)
1006
- }
1007
- }
1008
-
1009
- async function runLocalProfileSubcommand(args) {
1010
- if (
1011
- args.length === 0 ||
1012
- args.includes('--help') ||
1013
- args.includes('-h')
1014
- ) {
1015
- process.stdout.write(LOCAL_PROFILE_HELP_TEXT)
1016
- return
1017
- }
1018
-
1019
- if (!existsSync(LOCAL_PROFILE_CLI_SCRIPT)) {
1020
- throw new Error(
1021
- `Local profile CLI module was not found at "${LOCAL_PROFILE_CLI_SCRIPT}". Run "npm run build" to generate dist artifacts.`
1022
- )
1023
- }
1024
-
1025
- const moduleUrl = pathToFileURL(LOCAL_PROFILE_CLI_SCRIPT).href
1026
- const { runOpensteerLocalProfileCli } = await import(moduleUrl)
1027
- const exitCode = await runOpensteerLocalProfileCli(args)
1028
- if (exitCode !== 0) {
1029
- process.exit(exitCode)
1030
- }
1031
- }
1032
-
1033
- async function runAuthSubcommand(args) {
1034
- if (isAuthHelpRequest(args)) {
1035
- process.stdout.write(AUTH_HELP_TEXT)
1036
- return
1037
- }
1038
-
1039
- if (!existsSync(AUTH_CLI_SCRIPT)) {
1040
- throw new Error(
1041
- `Auth CLI module was not found at "${AUTH_CLI_SCRIPT}". Run "npm run build" to generate dist artifacts.`
1042
- )
1043
- }
1044
-
1045
- const moduleUrl = pathToFileURL(AUTH_CLI_SCRIPT).href
1046
- const { runOpensteerAuthCli } = await import(moduleUrl)
1047
- const exitCode = await runOpensteerAuthCli(args)
1048
- if (exitCode !== 0) {
1049
- process.exit(exitCode)
1050
- }
1051
- }
1052
-
1053
- function isAuthHelpRequest(args) {
1054
- if (args.length === 0) return true
1055
- const [subcommand, ...rest] = args
1056
- if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
1057
- return true
1058
- }
1059
- return rest.includes('--help') || rest.includes('-h')
1060
- }
1061
-
1062
- async function ensureOpenCloudCredentials(flags, scopeDir) {
1063
- if (!existsSync(AUTH_CLI_SCRIPT)) {
1064
- throw new Error(
1065
- `Auth CLI module was not found at "${AUTH_CLI_SCRIPT}". Run "npm run build" to generate dist artifacts.`
1066
- )
1067
- }
1068
-
1069
- const apiKeyFlag =
1070
- typeof flags['api-key'] === 'string' ? flags['api-key'] : undefined
1071
- const accessTokenFlag =
1072
- typeof flags['access-token'] === 'string'
1073
- ? flags['access-token']
1074
- : undefined
1075
-
1076
- const moduleUrl = pathToFileURL(AUTH_CLI_SCRIPT).href
1077
- const { ensureCloudCredentialsForOpenCommand } = await import(moduleUrl)
1078
- return await ensureCloudCredentialsForOpenCommand({
1079
- scopeDir,
1080
- env: process.env,
1081
- apiKeyFlag,
1082
- accessTokenFlag,
1083
- interactive: isInteractiveTerminal(),
1084
- writeProgress: (message) => process.stderr.write(message),
1085
- writeStderr: (message) => process.stderr.write(message),
1086
- })
1087
- }
1088
-
1089
- function isProfileHelpRequest(args) {
1090
- if (args.length === 0) return true
1091
- const [subcommand, ...rest] = args
1092
- if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
1093
- return true
1094
- }
1095
- return rest.includes('--help') || rest.includes('-h')
1096
- }
1097
-
1098
- function buildOpenCloudAuthPayload(auth) {
1099
- if (!auth) {
1100
- return null
1101
- }
1102
-
1103
- return {
1104
- ...(auth.kind === 'access-token'
1105
- ? { accessToken: auth.token }
1106
- : { apiKey: auth.token }),
1107
- baseUrl: auth.baseUrl,
1108
- authScheme: auth.authScheme,
1109
- }
1110
- }
1111
-
1112
- function printHelp() {
1113
- console.log(`Usage: opensteer <command> [options]
1114
-
1115
- Navigation:
1116
- open <url> Open browser and navigate to URL
1117
- navigate <url> Navigate and wait for visual stability
1118
- back Go back
1119
- forward Go forward
1120
- reload Reload page
1121
- close Close browser and server
1122
- close --all Close all active session-scoped servers
1123
-
1124
- Sessions:
1125
- sessions List active session-scoped daemons
1126
- status Show resolved logical/runtime session and session state
1127
-
1128
- Observation:
1129
- snapshot [--mode action] Get page snapshot
1130
- state Get page URL, title, and snapshot
1131
- cursor [on|off|status] Configure/query cursor preview mode
1132
- screenshot [file] Take screenshot
1133
-
1134
- Actions:
1135
- click [element] Click element
1136
- dblclick [element] Double-click element
1137
- rightclick [element] Right-click element
1138
- hover [element] Hover over element
1139
- input [element] <text> Input text into element
1140
- select [element] Select option from dropdown
1141
- scroll [element] Scroll page or element
1142
-
1143
- Keyboard:
1144
- press <key> Press key
1145
- type <text> Type text into focused element
1146
-
1147
- Element Info:
1148
- get-text [element] Get element text
1149
- get-value [element] Get element value
1150
- get-attrs [element] Get element attributes
1151
- get-html [selector] Get page or element HTML
1152
-
1153
- Tabs:
1154
- tabs List tabs
1155
- tab-new [url] Open new tab
1156
- tab-switch <index> Switch to tab
1157
- tab-close [index] Close tab
1158
-
1159
- Cookies:
1160
- cookies [--url] Get cookies
1161
- cookie-set Set cookie (--name, --value, ...)
1162
- cookies-clear Clear all cookies
1163
- cookies-export <file> Export cookies to file
1164
- cookies-import <file> Import cookies from file
1165
-
1166
- Utility:
1167
- eval <expression> Evaluate JavaScript
1168
- wait-for <text> Wait for text to appear
1169
- wait-selector <selector> Wait for selector
1170
- extract <schema-json> Extract structured data
1171
-
1172
- Skills:
1173
- skills install [options] Install Opensteer skill pack for supported agents
1174
- skills add [options] Alias for "skills install"
1175
- skills --help Show skills installer help
1176
- profile <command> Manage cloud browser profiles and cookie sync
1177
- local-profile <command> Inspect local Chrome profiles for real-browser mode
1178
- auth <command> Manage cloud login credentials (login/status/logout)
1179
- login Alias for "auth login"
1180
- logout Alias for "auth logout"
1181
-
1182
- Global Flags:
1183
- --session <id> Logical session id (scoped by canonical cwd)
1184
- --name <namespace> Selector namespace for cache storage on 'open'
1185
- --headless Launch chromium mode in headless mode
1186
- --browser <mode> Browser mode: chromium or real
1187
- --profile <name> Browser profile directory name for real-browser mode
1188
- --headed Launch real-browser mode with a visible window
1189
- --cdp-url <url> Connect to a running browser (e.g. http://localhost:9222)
1190
- --user-data-dir <path> Browser user-data root for real-browser mode
1191
- --browser-path <path> Override Chrome executable path for real-browser mode
1192
- --cloud-profile-id <id> Launch cloud session with a specific browser profile
1193
- --cloud-profile-reuse-if-active <true|false>
1194
- Reuse active cloud session for that browser profile
1195
- --cursor <true|false> Enable/disable cursor preview for the session
1196
- --element <N> Target element by counter
1197
- --selector <css> Target element by CSS selector
1198
- --description <text> Description for selector persistence
1199
- --help Show this help
1200
- --version, -v Show version
1201
-
1202
- Environment:
1203
- OPENSTEER_SESSION Logical session id (equivalent to --session)
1204
- OPENSTEER_CLIENT_ID Stable client identity for default session binding
1205
- OPENSTEER_NAME Default selector namespace for 'open' when --name is omitted
1206
- OPENSTEER_CURSOR Default cursor enablement (SDK + CLI session bootstrap)
1207
- OPENSTEER_MODE Runtime routing: "local" (default) or "cloud"
1208
- OPENSTEER_API_KEY Cloud API key credential
1209
- OPENSTEER_ACCESS_TOKEN Cloud bearer access token credential
1210
- OPENSTEER_BASE_URL Override cloud control-plane base URL
1211
- OPENSTEER_AUTH_SCHEME Cloud auth scheme: api-key (default) or bearer
1212
- OPENSTEER_REMOTE_ANNOUNCE Cloud session announcement policy: always (default), off, tty
1213
- OPENSTEER_BROWSER Local browser mode: chromium or real
1214
- OPENSTEER_CDP_URL Connect to a running browser (e.g. http://localhost:9222)
1215
- OPENSTEER_USER_DATA_DIR Browser user-data root for real-browser mode
1216
- OPENSTEER_PROFILE_DIRECTORY Browser profile directory for real-browser mode
1217
- `)
1218
- }
1219
-
1220
- async function main() {
1221
- const rawArgs = process.argv.slice(2)
1222
- if (rawArgs[0] === 'skills') {
1223
- try {
1224
- await runSkillsSubcommand(rawArgs.slice(1))
1225
- } catch (err) {
1226
- const message =
1227
- err instanceof Error ? err.message : 'Failed to run skills command'
1228
- process.stderr.write(`${message}\n`)
1229
- process.exit(1)
1230
- }
1231
- return
1232
- }
1233
- if (rawArgs[0] === 'auth') {
1234
- try {
1235
- await runAuthSubcommand(rawArgs.slice(1))
1236
- } catch (err) {
1237
- const message =
1238
- err instanceof Error ? err.message : 'Failed to run auth command'
1239
- process.stderr.write(`${message}\n`)
1240
- process.exit(1)
1241
- }
1242
- return
1243
- }
1244
- if (rawArgs[0] === 'login') {
1245
- try {
1246
- await runAuthSubcommand(['login', ...rawArgs.slice(1)])
1247
- } catch (err) {
1248
- const message =
1249
- err instanceof Error ? err.message : 'Failed to run login command'
1250
- process.stderr.write(`${message}\n`)
1251
- process.exit(1)
1252
- }
1253
- return
1254
- }
1255
- if (rawArgs[0] === 'logout') {
1256
- try {
1257
- await runAuthSubcommand(['logout', ...rawArgs.slice(1)])
1258
- } catch (err) {
1259
- const message =
1260
- err instanceof Error ? err.message : 'Failed to run logout command'
1261
- process.stderr.write(`${message}\n`)
1262
- process.exit(1)
1263
- }
1264
- return
1265
- }
1266
- if (rawArgs[0] === 'profile') {
1267
- try {
1268
- await runProfileSubcommand(rawArgs.slice(1))
1269
- } catch (err) {
1270
- const message =
1271
- err instanceof Error ? err.message : 'Failed to run profile command'
1272
- process.stderr.write(`${message}\n`)
1273
- process.exit(1)
1274
- }
1275
- return
1276
- }
1277
- if (rawArgs[0] === 'local-profile') {
1278
- try {
1279
- await runLocalProfileSubcommand(rawArgs.slice(1))
1280
- } catch (err) {
1281
- const message =
1282
- err instanceof Error
1283
- ? err.message
1284
- : 'Failed to run local-profile command'
1285
- process.stderr.write(`${message}\n`)
1286
- process.exit(1)
1287
- }
1288
- return
1289
- }
1290
-
1291
- const scopeDir = resolveScopeDir()
1292
-
1293
- const { command, flags, positional } = parseArgs(process.argv)
1294
-
1295
- if (
1296
- flags['connect-url'] !== undefined ||
1297
- flags.channel !== undefined ||
1298
- flags['profile-dir'] !== undefined
1299
- ) {
1300
- error(
1301
- '--connect-url, --channel, and --profile-dir are no longer supported. Use --cdp-url, --browser real, --profile, --user-data-dir, and --browser-path instead.'
1302
- )
1303
- }
1304
-
1305
- if (command === 'sessions') {
1306
- output({ ok: true, sessions: listSessions() })
1307
- return
1308
- }
1309
-
1310
- if (command === 'close' && flags.all === true) {
1311
- try {
1312
- const closed = await closeAllSessions()
1313
- output({ ok: true, closed })
1314
- } catch (err) {
1315
- error(err instanceof Error ? err.message : 'Failed to close sessions')
1316
- }
1317
- return
1318
- }
1319
-
1320
- let openCloudAuth = null
1321
- if (command === 'open') {
1322
- try {
1323
- openCloudAuth = await ensureOpenCloudCredentials(flags, scopeDir)
1324
- } catch (err) {
1325
- error(
1326
- err instanceof Error
1327
- ? err.message
1328
- : 'Failed to resolve cloud authentication for open command.'
1329
- )
1330
- }
1331
- }
1332
-
1333
- let resolvedSession
1334
- let resolvedName
1335
- try {
1336
- resolvedSession = resolveSession(flags, scopeDir)
1337
- resolvedName = resolveName(flags, resolvedSession.session)
1338
- } catch (err) {
1339
- error(err instanceof Error ? err.message : 'Failed to resolve session')
1340
- }
1341
-
1342
- const logicalSession = resolvedSession.session
1343
- const runtimeSession = buildRuntimeSession(scopeDir, logicalSession)
1344
- const sessionSource = resolvedSession.source
1345
- const name = resolvedName.name
1346
- const nameSource = resolvedName.source
1347
- const socketPath = getSocketPath(runtimeSession)
1348
- const routingContext = {
1349
- logicalSession,
1350
- runtimeSession,
1351
- scopeDir,
1352
- }
1353
-
1354
- if (command === 'status') {
1355
- output({
1356
- ok: true,
1357
- resolvedSession: logicalSession,
1358
- logicalSession,
1359
- runtimeSession,
1360
- scopeDir,
1361
- sessionSource,
1362
- resolvedName: name,
1363
- nameSource,
1364
- serverRunning: await isServerHealthy(runtimeSession),
1365
- socketPath,
1366
- sessions: listSessions(),
1367
- })
1368
- return
1369
- }
1370
-
1371
- delete flags.name
1372
- delete flags.session
1373
- delete flags.all
1374
- delete flags['api-key']
1375
- delete flags['access-token']
1376
-
1377
- const request = buildRequest(command, flags, positional)
1378
- if (command === 'open') {
1379
- request.args.name = name
1380
- const cloudAuthPayload = buildOpenCloudAuthPayload(openCloudAuth)
1381
- if (cloudAuthPayload) {
1382
- request.args['cloud-auth'] = cloudAuthPayload
1383
- }
1384
- }
1385
-
1386
- if (!(await isServerHealthy(runtimeSession))) {
1387
- if (command !== 'open') {
1388
- error(
1389
- `No server running for session '${logicalSession}' in cwd scope '${scopeDir}' (resolved from ${sessionSource}). Run 'opensteer open' first or use 'opensteer sessions' to see active sessions.`
1390
- )
1391
- }
1392
-
1393
- try {
1394
- await ensureServer(routingContext)
1395
- } catch (err) {
1396
- error(
1397
- err instanceof Error
1398
- ? err.message
1399
- : `Failed to start server for session '${logicalSession}' in cwd scope '${scopeDir}'.`
1400
- )
1401
- }
1402
- }
1403
-
1404
- try {
1405
- const response = await sendCommand(socketPath, request)
1406
-
1407
- if (response.ok) {
1408
- output({ ok: true, ...response.result })
1409
- } else {
1410
- process.stderr.write(JSON.stringify(normalizeFailedResponse(response)) + '\n')
1411
- process.exit(1)
1412
- }
1413
- } catch (err) {
1414
- error(
1415
- formatTransportFailure(
1416
- err,
1417
- `Failed to run '${command}' for session '${logicalSession}' in cwd scope '${scopeDir}'`
1418
- )
1419
- )
1420
- }
1421
- }
1422
-
1423
- main()