opensteer 0.6.13 → 0.7.1

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-7QTHGTGN.js +32589 -0
  3. package/dist/chunk-7QTHGTGN.js.map +1 -0
  4. package/dist/cli/bin.cjs +38219 -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 +31327 -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 +58 -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
@@ -1,977 +0,0 @@
1
- import {
2
- Opensteer
3
- } from "../chunk-HBTSQ2V4.js";
4
- import {
5
- normalizeError,
6
- resolveCloudSelection,
7
- resolveConfigWithEnv
8
- } from "../chunk-2ES46WCO.js";
9
- import "../chunk-K5CL76MG.js";
10
- import "../chunk-3H5RRIMZ.js";
11
-
12
- // src/cli/server.ts
13
- import { createServer } from "net";
14
- import { writeFileSync, unlinkSync, existsSync } from "fs";
15
-
16
- // src/cli/paths.ts
17
- import { tmpdir } from "os";
18
- import { join } from "path";
19
- function prefix(session2) {
20
- return `opensteer-${session2}`;
21
- }
22
- function getSocketPath(session2) {
23
- return join(tmpdir(), `${prefix(session2)}.sock`);
24
- }
25
- function getPidPath(session2) {
26
- return join(tmpdir(), `${prefix(session2)}.pid`);
27
- }
28
- function getMetadataPath(session2) {
29
- return join(tmpdir(), `${prefix(session2)}.meta.json`);
30
- }
31
-
32
- // src/cli/commands.ts
33
- import { writeFile } from "fs/promises";
34
- var commands = {
35
- async navigate(opensteer, args) {
36
- const url = args.url;
37
- if (!url) throw new Error("Missing required argument: url");
38
- await opensteer.goto(url, {
39
- timeout: args.timeout,
40
- settleMs: args.settleMs,
41
- waitUntil: args.waitUntil
42
- });
43
- return { url: opensteer.page.url() };
44
- },
45
- async back(opensteer) {
46
- await opensteer.page.goBack();
47
- return { url: opensteer.page.url() };
48
- },
49
- async forward(opensteer) {
50
- await opensteer.page.goForward();
51
- return { url: opensteer.page.url() };
52
- },
53
- async reload(opensteer) {
54
- await opensteer.page.reload();
55
- return { url: opensteer.page.url() };
56
- },
57
- async snapshot(opensteer, args) {
58
- const mode = args.mode || "action";
59
- const html = await opensteer.snapshot({ mode });
60
- return { html };
61
- },
62
- async state(opensteer) {
63
- return await opensteer.state();
64
- },
65
- async cursor(opensteer, args) {
66
- const mode = typeof args.mode === "string" ? args.mode : "status";
67
- if (mode === "on") {
68
- opensteer.setCursorEnabled(true);
69
- } else if (mode === "off") {
70
- opensteer.setCursorEnabled(false);
71
- } else if (mode !== "status") {
72
- throw new Error(
73
- `Invalid cursor mode "${String(mode)}". Use "on", "off", or "status".`
74
- );
75
- }
76
- return {
77
- cursor: opensteer.getCursorState()
78
- };
79
- },
80
- async screenshot(opensteer, args) {
81
- const file = args.file || "screenshot.png";
82
- const type = file.endsWith(".jpg") || file.endsWith(".jpeg") ? "jpeg" : "png";
83
- const buffer = await opensteer.screenshot({
84
- fullPage: args.fullPage,
85
- type
86
- });
87
- await writeFile(file, buffer);
88
- return { file };
89
- },
90
- async click(opensteer, args) {
91
- return await opensteer.click({
92
- element: args.element,
93
- selector: args.selector,
94
- description: args.description,
95
- button: args.button,
96
- clickCount: args.clickCount
97
- });
98
- },
99
- async dblclick(opensteer, args) {
100
- return await opensteer.dblclick({
101
- element: args.element,
102
- selector: args.selector,
103
- description: args.description
104
- });
105
- },
106
- async rightclick(opensteer, args) {
107
- return await opensteer.rightclick({
108
- element: args.element,
109
- selector: args.selector,
110
- description: args.description
111
- });
112
- },
113
- async hover(opensteer, args) {
114
- return await opensteer.hover({
115
- element: args.element,
116
- selector: args.selector,
117
- description: args.description
118
- });
119
- },
120
- async input(opensteer, args) {
121
- const text = args.text;
122
- if (text == null) throw new Error("Missing required argument: text");
123
- return await opensteer.input({
124
- element: args.element,
125
- selector: args.selector,
126
- description: args.description,
127
- text,
128
- clear: args.clear,
129
- pressEnter: args.pressEnter
130
- });
131
- },
132
- async select(opensteer, args) {
133
- return await opensteer.select({
134
- element: args.element,
135
- selector: args.selector,
136
- description: args.description,
137
- value: args.value,
138
- label: args.label,
139
- index: args.index
140
- });
141
- },
142
- async scroll(opensteer, args) {
143
- return await opensteer.scroll({
144
- element: args.element,
145
- selector: args.selector,
146
- description: args.description,
147
- direction: args.direction,
148
- amount: args.amount
149
- });
150
- },
151
- async press(opensteer, args) {
152
- const key = args.key;
153
- if (!key) throw new Error("Missing required argument: key");
154
- await opensteer.pressKey(key);
155
- return { key };
156
- },
157
- async type(opensteer, args) {
158
- const text = args.text;
159
- if (text == null) throw new Error("Missing required argument: text");
160
- await opensteer.type(text);
161
- return { text };
162
- },
163
- async "get-text"(opensteer, args) {
164
- const text = await opensteer.getElementText({
165
- element: args.element,
166
- selector: args.selector,
167
- description: args.description
168
- });
169
- return { text };
170
- },
171
- async "get-value"(opensteer, args) {
172
- const value = await opensteer.getElementValue({
173
- element: args.element,
174
- selector: args.selector,
175
- description: args.description
176
- });
177
- return { value };
178
- },
179
- async "get-attrs"(opensteer, args) {
180
- const attributes = await opensteer.getElementAttributes({
181
- element: args.element,
182
- selector: args.selector,
183
- description: args.description
184
- });
185
- return { attributes };
186
- },
187
- async "get-html"(opensteer, args) {
188
- const html = await opensteer.getHtml(args.selector);
189
- return { html };
190
- },
191
- async tabs(opensteer) {
192
- const tabs = await opensteer.tabs();
193
- return { tabs };
194
- },
195
- async "tab-new"(opensteer, args) {
196
- return await opensteer.newTab(args.url);
197
- },
198
- async "tab-switch"(opensteer, args) {
199
- const index = args.index;
200
- if (index == null) throw new Error("Missing required argument: index");
201
- await opensteer.switchTab(index);
202
- return { index };
203
- },
204
- async "tab-close"(opensteer, args) {
205
- await opensteer.closeTab(args.index);
206
- return {};
207
- },
208
- async cookies(opensteer, args) {
209
- const cookies = await opensteer.getCookies(args.url);
210
- return { cookies };
211
- },
212
- async "cookie-set"(opensteer, args) {
213
- await opensteer.setCookie({
214
- name: args.name,
215
- value: args.value,
216
- url: args.url,
217
- domain: args.domain,
218
- path: args.path,
219
- expires: args.expires,
220
- httpOnly: args.httpOnly,
221
- secure: args.secure,
222
- sameSite: args.sameSite
223
- });
224
- return {};
225
- },
226
- async "cookies-clear"(opensteer) {
227
- await opensteer.clearCookies();
228
- return {};
229
- },
230
- async "cookies-export"(opensteer, args) {
231
- const file = args.file;
232
- if (!file) throw new Error("Missing required argument: file");
233
- await opensteer.exportCookies(file, args.url);
234
- return { file };
235
- },
236
- async "cookies-import"(opensteer, args) {
237
- const file = args.file;
238
- if (!file) throw new Error("Missing required argument: file");
239
- await opensteer.importCookies(file);
240
- return { file };
241
- },
242
- async eval(opensteer, args) {
243
- const expression = args.expression;
244
- if (!expression)
245
- throw new Error("Missing required argument: expression");
246
- const result = await opensteer.page.evaluate(expression);
247
- return { result };
248
- },
249
- async "wait-for"(opensteer, args) {
250
- const text = args.text;
251
- if (!text) throw new Error("Missing required argument: text");
252
- await opensteer.waitForText(text, {
253
- timeout: args.timeout
254
- });
255
- return { text };
256
- },
257
- async "wait-selector"(opensteer, args) {
258
- const selector = args.selector;
259
- if (!selector) throw new Error("Missing required argument: selector");
260
- await opensteer.page.waitForSelector(selector, {
261
- timeout: args.timeout ?? 3e4
262
- });
263
- return { selector };
264
- },
265
- async extract(opensteer, args) {
266
- const schema = args.schema;
267
- const data = await opensteer.extract({
268
- schema,
269
- description: args.description,
270
- prompt: args.prompt
271
- });
272
- return { data };
273
- }
274
- };
275
- function getCommandHandler(name) {
276
- return commands[name];
277
- }
278
-
279
- // src/cli/cloud-profile-binding.ts
280
- function normalizeCloudProfileBinding(value) {
281
- if (!value) {
282
- return null;
283
- }
284
- const profileId = typeof value.profileId === "string" ? value.profileId.trim() : "";
285
- if (!profileId) {
286
- return null;
287
- }
288
- return {
289
- profileId,
290
- reuseIfActive: typeof value.reuseIfActive === "boolean" ? value.reuseIfActive : void 0
291
- };
292
- }
293
- function resolveConfiguredCloudProfileBinding(config) {
294
- if (!isCloudConfigured(config)) {
295
- return null;
296
- }
297
- return normalizeCloudProfileBinding(config.cloud.browserProfile);
298
- }
299
- function resolveSessionCloudProfileBinding(config, requested) {
300
- if (!isCloudConfigured(config)) {
301
- return null;
302
- }
303
- return requested ?? resolveConfiguredCloudProfileBinding(config);
304
- }
305
- function assertCompatibleCloudProfileBinding(sessionId, active, requested) {
306
- if (!requested) {
307
- return;
308
- }
309
- if (!active) {
310
- throw new Error(
311
- [
312
- `Session '${sessionId}' is already running without a bound cloud browser profile.`,
313
- "Cloud browser profile selection only applies when the session is first opened.",
314
- "Close this session or use a different --session to target another profile."
315
- ].join(" ")
316
- );
317
- }
318
- if (active.profileId === requested.profileId && active.reuseIfActive === requested.reuseIfActive) {
319
- return;
320
- }
321
- throw new Error(
322
- [
323
- `Session '${sessionId}' is already bound to cloud browser profile ${formatCloudProfileBinding(active)}.`,
324
- `Requested ${formatCloudProfileBinding(requested)} does not match.`,
325
- "Use the same cloud profile for this session, or start a different --session."
326
- ].join(" ")
327
- );
328
- }
329
- function formatCloudProfileBinding(binding) {
330
- if (binding.reuseIfActive === void 0) {
331
- return `'${binding.profileId}'`;
332
- }
333
- return `'${binding.profileId}' (reuseIfActive=${String(
334
- binding.reuseIfActive
335
- )})`;
336
- }
337
- function isCloudConfigured(config) {
338
- return Boolean(
339
- config.cloud && typeof config.cloud === "object" && !Array.isArray(config.cloud)
340
- );
341
- }
342
-
343
- // src/cli/open-cloud-auth.ts
344
- function normalizeCliOpenCloudAuth(value) {
345
- if (value == null) {
346
- return null;
347
- }
348
- if (!value || typeof value !== "object" || Array.isArray(value)) {
349
- throw new Error("Invalid open request cloud auth payload.");
350
- }
351
- const record = value;
352
- const apiKey = normalizeNonEmptyString(record.apiKey);
353
- const accessToken = normalizeNonEmptyString(record.accessToken);
354
- const baseUrl = normalizeNonEmptyString(record.baseUrl);
355
- const authScheme = normalizeAuthScheme(record.authScheme);
356
- if (!baseUrl) {
357
- throw new Error("Open request cloud auth payload is missing baseUrl.");
358
- }
359
- if ((apiKey ? 1 : 0) + (accessToken ? 1 : 0) !== 1) {
360
- throw new Error(
361
- "Open request cloud auth payload must include exactly one credential."
362
- );
363
- }
364
- if (accessToken && authScheme !== "bearer") {
365
- throw new Error(
366
- 'Open request cloud auth payload must use authScheme "bearer" with accessToken.'
367
- );
368
- }
369
- return {
370
- ...apiKey ? { apiKey } : {},
371
- ...accessToken ? { accessToken } : {},
372
- baseUrl,
373
- authScheme
374
- };
375
- }
376
- function buildServerOpenConfig(options) {
377
- const config = {
378
- name: options.name,
379
- storage: {
380
- rootDir: options.scopeDir
381
- },
382
- cursor: {
383
- enabled: options.cursorEnabled
384
- },
385
- browser: {
386
- headless: options.headless,
387
- mode: options.mode,
388
- cdpUrl: options.cdpUrl,
389
- userDataDir: options.userDataDir,
390
- profileDirectory: options.profileDirectory,
391
- executablePath: options.executablePath
392
- }
393
- };
394
- if (!options.cloudAuth) {
395
- return config;
396
- }
397
- const resolved = resolveConfigWithEnv(
398
- {
399
- storage: {
400
- rootDir: options.scopeDir
401
- }
402
- },
403
- {
404
- env: options.env
405
- }
406
- );
407
- const cloudSelection = resolveCloudSelection(
408
- {
409
- cloud: resolved.config.cloud
410
- },
411
- resolved.env
412
- );
413
- if (!cloudSelection.cloud) {
414
- return config;
415
- }
416
- config.cloud = toOpensteerCloudOptions(options.cloudAuth);
417
- return config;
418
- }
419
- function toOpensteerCloudOptions(auth) {
420
- return {
421
- ...auth.apiKey ? { apiKey: auth.apiKey } : {},
422
- ...auth.accessToken ? { accessToken: auth.accessToken } : {},
423
- baseUrl: auth.baseUrl,
424
- authScheme: auth.authScheme
425
- };
426
- }
427
- function normalizeNonEmptyString(value) {
428
- if (typeof value !== "string") {
429
- return void 0;
430
- }
431
- const trimmed = value.trim();
432
- return trimmed.length > 0 ? trimmed : void 0;
433
- }
434
- function normalizeAuthScheme(value) {
435
- if (value === "api-key" || value === "bearer") {
436
- return value;
437
- }
438
- throw new Error(
439
- 'Open request cloud auth payload must use authScheme "api-key" or "bearer".'
440
- );
441
- }
442
-
443
- // src/cli/open-browser-config.ts
444
- function resolveCliBrowserRequestConfig(options) {
445
- const mode = options.browser ?? (options.profileDirectory || options.userDataDir || options.executablePath ? "real" : void 0);
446
- return {
447
- mode,
448
- headless: options.headless ?? (mode === "real" ? true : void 0),
449
- cdpUrl: options.cdpUrl,
450
- profileDirectory: options.profileDirectory,
451
- userDataDir: options.userDataDir,
452
- executablePath: options.executablePath
453
- };
454
- }
455
-
456
- // src/cli/server.ts
457
- var instance = null;
458
- var launchPromise = null;
459
- var selectorNamespace = null;
460
- var cloudProfileBinding = null;
461
- var cloudAuthOverride = null;
462
- var cursorEnabledPreference = readCursorPreferenceFromEnv();
463
- var requestQueue = Promise.resolve();
464
- var shuttingDown = false;
465
- function sanitizeNamespace(value) {
466
- const trimmed = String(value || "").trim();
467
- if (!trimmed || trimmed === "." || trimmed === "..") {
468
- return "default";
469
- }
470
- const replaced = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_");
471
- const collapsed = replaced.replace(/_+/g, "_");
472
- const bounded = collapsed.replace(/^_+|_+$/g, "");
473
- return bounded || "default";
474
- }
475
- function invalidateInstance() {
476
- if (!instance) return;
477
- instance.close().catch(() => {
478
- });
479
- instance = null;
480
- cloudProfileBinding = null;
481
- }
482
- function normalizeCursorFlag(value) {
483
- if (value === void 0 || value === null) {
484
- return null;
485
- }
486
- if (typeof value === "boolean") {
487
- return value;
488
- }
489
- if (typeof value === "number") {
490
- if (value === 1) return true;
491
- if (value === 0) return false;
492
- }
493
- throw new Error(
494
- '--cursor must be a boolean value ("true" or "false").'
495
- );
496
- }
497
- function readCursorPreferenceFromEnv() {
498
- const value = process.env.OPENSTEER_CURSOR;
499
- if (typeof value !== "string") {
500
- return null;
501
- }
502
- const normalized = value.trim().toLowerCase();
503
- if (normalized === "true" || normalized === "1") {
504
- return true;
505
- }
506
- if (normalized === "false" || normalized === "0") {
507
- return false;
508
- }
509
- return null;
510
- }
511
- function attachLifecycleListeners(inst) {
512
- try {
513
- inst.page.on("close", invalidateInstance);
514
- inst.context.on("close", invalidateInstance);
515
- } catch {
516
- }
517
- }
518
- var sessionEnv = process.env.OPENSTEER_SESSION?.trim();
519
- if (!sessionEnv) {
520
- process.stderr.write("Missing OPENSTEER_SESSION environment variable.\n");
521
- process.exit(1);
522
- }
523
- var session = sessionEnv;
524
- var logicalSession = process.env.OPENSTEER_LOGICAL_SESSION?.trim() || session;
525
- var scopeDir = process.env.OPENSTEER_SCOPE_DIR?.trim() || process.cwd();
526
- var socketPath = getSocketPath(session);
527
- var pidPath = getPidPath(session);
528
- function cleanup() {
529
- try {
530
- unlinkSync(socketPath);
531
- } catch {
532
- }
533
- try {
534
- unlinkSync(pidPath);
535
- } catch {
536
- }
537
- try {
538
- unlinkSync(getMetadataPath(session));
539
- } catch {
540
- }
541
- }
542
- function beginShutdown() {
543
- if (shuttingDown) return;
544
- shuttingDown = true;
545
- cleanup();
546
- server.close(() => {
547
- process.exit(0);
548
- });
549
- setTimeout(() => {
550
- process.exit(0);
551
- }, 250).unref();
552
- }
553
- function sendResponse(socket, response) {
554
- try {
555
- socket.write(JSON.stringify(response) + "\n");
556
- } catch {
557
- }
558
- }
559
- function enqueueRequest(request, socket) {
560
- if (request.command === "ping") {
561
- void handleRequest(request, socket);
562
- return;
563
- }
564
- requestQueue = requestQueue.then(() => handleRequest(request, socket)).catch((error) => {
565
- sendResponse(
566
- socket,
567
- buildErrorResponse(
568
- request.id,
569
- error,
570
- "Unexpected server error while handling request.",
571
- "CLI_INTERNAL_ERROR"
572
- )
573
- );
574
- });
575
- }
576
- async function handleRequest(request, socket) {
577
- const { id, command, args } = request;
578
- if (command === "ping" && shuttingDown) {
579
- sendResponse(socket, {
580
- id,
581
- ok: false,
582
- error: `Session '${logicalSession}' is shutting down.`,
583
- errorInfo: {
584
- message: `Session '${logicalSession}' is shutting down.`,
585
- code: "SESSION_SHUTTING_DOWN",
586
- details: {
587
- session: logicalSession,
588
- runtimeSession: session,
589
- scopeDir
590
- }
591
- }
592
- });
593
- return;
594
- }
595
- if (shuttingDown) {
596
- sendResponse(socket, {
597
- id,
598
- ok: false,
599
- error: `Session '${logicalSession}' is shutting down. Retry your command.`,
600
- errorInfo: {
601
- message: `Session '${logicalSession}' is shutting down. Retry your command.`,
602
- code: "SESSION_SHUTTING_DOWN",
603
- details: {
604
- session: logicalSession,
605
- runtimeSession: session,
606
- scopeDir
607
- }
608
- }
609
- });
610
- return;
611
- }
612
- if (command === "open") {
613
- try {
614
- const url = args.url;
615
- const headless = args.headless;
616
- const browser = args.browser === "real" || args.browser === "chromium" ? args.browser : args.browser === void 0 ? void 0 : null;
617
- const cdpUrl = args["cdp-url"];
618
- const profileDirectory = args.profile;
619
- const userDataDir = args["user-data-dir"];
620
- const executablePath = args["browser-path"];
621
- const cloudProfileId = typeof args["cloud-profile-id"] === "string" ? args["cloud-profile-id"].trim() : void 0;
622
- const cloudProfileReuseIfActive = typeof args["cloud-profile-reuse-if-active"] === "boolean" ? args["cloud-profile-reuse-if-active"] : void 0;
623
- const requestedCloudProfileBinding = normalizeCloudProfileBinding({
624
- profileId: cloudProfileId,
625
- reuseIfActive: cloudProfileReuseIfActive
626
- });
627
- const requestedCloudAuth = normalizeCliOpenCloudAuth(
628
- args["cloud-auth"]
629
- );
630
- if (cloudProfileReuseIfActive !== void 0 && !cloudProfileId) {
631
- throw new Error(
632
- "--cloud-profile-reuse-if-active requires --cloud-profile-id."
633
- );
634
- }
635
- if (browser === null) {
636
- throw new Error(
637
- '--browser must be either "chromium" or "real".'
638
- );
639
- }
640
- if (browser === "chromium" && (profileDirectory || userDataDir || executablePath)) {
641
- throw new Error(
642
- "--profile, --user-data-dir, and --browser-path require --browser real."
643
- );
644
- }
645
- if (cdpUrl && browser === "real") {
646
- throw new Error(
647
- "--cdp-url cannot be combined with --browser real."
648
- );
649
- }
650
- const requestedCursor = normalizeCursorFlag(args.cursor);
651
- const requestedName = typeof args.name === "string" && args.name.trim().length > 0 ? sanitizeNamespace(args.name) : null;
652
- if (requestedCursor !== null) {
653
- cursorEnabledPreference = requestedCursor;
654
- }
655
- const effectiveCursorEnabled = cursorEnabledPreference !== null ? cursorEnabledPreference : true;
656
- if (selectorNamespace && requestedName && requestedName !== selectorNamespace) {
657
- sendResponse(socket, {
658
- id,
659
- ok: false,
660
- error: `Session '${logicalSession}' is already bound to selector namespace '${selectorNamespace}'. Requested '${requestedName}' does not match. Use the same --name for this session or start a different --session.`,
661
- errorInfo: {
662
- message: `Session '${logicalSession}' is already bound to selector namespace '${selectorNamespace}'. Requested '${requestedName}' does not match. Use the same --name for this session or start a different --session.`,
663
- code: "SESSION_NAMESPACE_MISMATCH",
664
- details: {
665
- session: logicalSession,
666
- runtimeSession: session,
667
- scopeDir,
668
- activeNamespace: selectorNamespace,
669
- requestedNamespace: requestedName
670
- }
671
- }
672
- });
673
- return;
674
- }
675
- if (!selectorNamespace) {
676
- selectorNamespace = requestedName ?? logicalSession;
677
- }
678
- const activeNamespace = selectorNamespace ?? logicalSession;
679
- if (requestedCloudAuth) {
680
- cloudAuthOverride = requestedCloudAuth;
681
- }
682
- if (instance && !launchPromise) {
683
- try {
684
- if (instance.page.isClosed()) {
685
- invalidateInstance();
686
- }
687
- } catch {
688
- invalidateInstance();
689
- }
690
- }
691
- const requestedBrowserConfig = resolveCliBrowserRequestConfig({
692
- browser: browser ?? void 0,
693
- headless,
694
- cdpUrl,
695
- profileDirectory,
696
- userDataDir,
697
- executablePath
698
- });
699
- if (instance && !launchPromise) {
700
- assertCompatibleCloudProfileBinding(
701
- logicalSession,
702
- cloudProfileBinding,
703
- requestedCloudProfileBinding
704
- );
705
- const existingBrowserConfig = instance.getConfig().browser || {};
706
- const existingBrowserRecord = existingBrowserConfig;
707
- const mismatch = Object.entries(requestedBrowserConfig).find(
708
- ([key, value]) => value !== void 0 && existingBrowserRecord[key] !== value
709
- );
710
- if (mismatch) {
711
- const [key, value] = mismatch;
712
- throw new Error(
713
- `Session '${logicalSession}' is already bound to browser setting "${key}"=${JSON.stringify(existingBrowserRecord[key])}. Requested ${JSON.stringify(value)} does not match. Use the same browser flags for this session or start a different --session.`
714
- );
715
- }
716
- }
717
- let shouldLaunchInitialUrl = false;
718
- if (!instance) {
719
- instance = new Opensteer(
720
- buildServerOpenConfig({
721
- scopeDir,
722
- name: activeNamespace,
723
- cursorEnabled: effectiveCursorEnabled,
724
- ...requestedBrowserConfig,
725
- cloudAuth: cloudAuthOverride
726
- })
727
- );
728
- const resolvedBrowserConfig = instance.getConfig().browser || {};
729
- shouldLaunchInitialUrl = Boolean(url) && resolvedBrowserConfig.mode === "real" && !resolvedBrowserConfig.cdpUrl;
730
- const nextCloudProfileBinding = resolveSessionCloudProfileBinding(
731
- instance.getConfig(),
732
- requestedCloudProfileBinding
733
- );
734
- if (requestedCloudProfileBinding && !nextCloudProfileBinding) {
735
- instance = null;
736
- throw new Error(
737
- "--cloud-profile-id can only be used when cloud mode is enabled for this session."
738
- );
739
- }
740
- launchPromise = instance.launch({
741
- initialUrl: shouldLaunchInitialUrl ? url : void 0,
742
- ...requestedBrowserConfig,
743
- cloudBrowserProfile: cloudProfileId ? {
744
- profileId: cloudProfileId,
745
- reuseIfActive: cloudProfileReuseIfActive
746
- } : void 0,
747
- timeout: cdpUrl ? 12e4 : 3e4
748
- });
749
- try {
750
- await launchPromise;
751
- attachLifecycleListeners(instance);
752
- cloudProfileBinding = nextCloudProfileBinding;
753
- } catch (err) {
754
- instance = null;
755
- cloudProfileBinding = null;
756
- throw err;
757
- } finally {
758
- launchPromise = null;
759
- }
760
- } else if (launchPromise) {
761
- await launchPromise;
762
- } else if (requestedCursor !== null) {
763
- instance.setCursorEnabled(requestedCursor);
764
- }
765
- if (url && !shouldLaunchInitialUrl) {
766
- await instance.goto(url);
767
- }
768
- sendResponse(socket, {
769
- id,
770
- ok: true,
771
- result: {
772
- url: instance.page.url(),
773
- session: logicalSession,
774
- logicalSession,
775
- runtimeSession: session,
776
- scopeDir,
777
- name: activeNamespace,
778
- cursor: instance.getCursorState(),
779
- cloudSessionId: instance.getCloudSessionId() ?? void 0,
780
- cloudSessionUrl: instance.getCloudSessionUrl() ?? void 0
781
- }
782
- });
783
- } catch (err) {
784
- sendResponse(
785
- socket,
786
- buildErrorResponse(id, err, "Failed to open browser session.")
787
- );
788
- }
789
- return;
790
- }
791
- if (command === "cursor") {
792
- try {
793
- const mode = typeof args.mode === "string" ? args.mode : "status";
794
- if (mode === "on") {
795
- cursorEnabledPreference = true;
796
- instance?.setCursorEnabled(true);
797
- } else if (mode === "off") {
798
- cursorEnabledPreference = false;
799
- instance?.setCursorEnabled(false);
800
- } else if (mode !== "status") {
801
- throw new Error(
802
- `Invalid cursor mode "${mode}". Use "on", "off", or "status".`
803
- );
804
- }
805
- const defaultEnabled = cursorEnabledPreference !== null ? cursorEnabledPreference : true;
806
- const cursor = instance ? instance.getCursorState() : {
807
- enabled: defaultEnabled,
808
- active: false,
809
- reason: "session_not_open"
810
- };
811
- sendResponse(socket, {
812
- id,
813
- ok: true,
814
- result: {
815
- cursor
816
- }
817
- });
818
- } catch (err) {
819
- sendResponse(
820
- socket,
821
- buildErrorResponse(id, err, "Failed to update cursor mode.")
822
- );
823
- }
824
- return;
825
- }
826
- if (command === "close") {
827
- try {
828
- if (instance) {
829
- await instance.close();
830
- instance = null;
831
- }
832
- sendResponse(socket, {
833
- id,
834
- ok: true,
835
- result: { sessionClosed: true }
836
- });
837
- } catch (err) {
838
- sendResponse(
839
- socket,
840
- buildErrorResponse(id, err, "Failed to close browser session.")
841
- );
842
- }
843
- beginShutdown();
844
- return;
845
- }
846
- if (command === "ping") {
847
- sendResponse(socket, { id, ok: true, result: { pong: true } });
848
- return;
849
- }
850
- if (!instance) {
851
- sendResponse(socket, {
852
- id,
853
- ok: false,
854
- error: `No browser session in session '${logicalSession}' (scope '${scopeDir}'). Call 'opensteer open --session ${logicalSession}' first, or use 'opensteer sessions' to list active sessions.`,
855
- errorInfo: {
856
- message: `No browser session in session '${logicalSession}' (scope '${scopeDir}'). Call 'opensteer open --session ${logicalSession}' first, or use 'opensteer sessions' to list active sessions.`,
857
- code: "SESSION_NOT_OPEN",
858
- details: {
859
- session: logicalSession,
860
- runtimeSession: session,
861
- scopeDir
862
- }
863
- }
864
- });
865
- return;
866
- }
867
- const handler = getCommandHandler(command);
868
- if (!handler) {
869
- sendResponse(socket, {
870
- id,
871
- ok: false,
872
- error: `Unknown command: ${command}`,
873
- errorInfo: {
874
- message: `Unknown command: ${command}`,
875
- code: "UNKNOWN_COMMAND",
876
- details: {
877
- command
878
- }
879
- }
880
- });
881
- return;
882
- }
883
- try {
884
- const result = await handler(instance, args);
885
- sendResponse(socket, { id, ok: true, result });
886
- } catch (err) {
887
- sendResponse(
888
- socket,
889
- buildErrorResponse(id, err, `Command "${command}" failed.`, void 0, {
890
- command
891
- })
892
- );
893
- }
894
- }
895
- if (existsSync(socketPath)) {
896
- unlinkSync(socketPath);
897
- }
898
- var server = createServer((socket) => {
899
- let buffer = "";
900
- socket.on("data", (chunk) => {
901
- buffer += chunk.toString();
902
- const lines = buffer.split("\n");
903
- buffer = lines.pop() || "";
904
- for (const line of lines) {
905
- if (!line.trim()) continue;
906
- try {
907
- const request = JSON.parse(line);
908
- enqueueRequest(request, socket);
909
- } catch {
910
- sendResponse(socket, {
911
- id: 0,
912
- ok: false,
913
- error: "Invalid JSON request",
914
- errorInfo: {
915
- message: "Invalid JSON request",
916
- code: "INVALID_JSON_REQUEST"
917
- }
918
- });
919
- }
920
- }
921
- });
922
- socket.on("error", () => {
923
- });
924
- });
925
- server.listen(socketPath, () => {
926
- writeFileSync(pidPath, String(process.pid));
927
- if (process.send) {
928
- process.send("ready");
929
- }
930
- });
931
- server.on("error", (err) => {
932
- console.error("Server error:", err.message);
933
- cleanup();
934
- process.exit(1);
935
- });
936
- async function shutdown() {
937
- if (shuttingDown) return;
938
- shuttingDown = true;
939
- if (instance) {
940
- try {
941
- await instance.close();
942
- } catch {
943
- }
944
- instance = null;
945
- }
946
- cleanup();
947
- server.close(() => {
948
- process.exit(0);
949
- });
950
- setTimeout(() => {
951
- process.exit(0);
952
- }, 250).unref();
953
- }
954
- process.on("SIGTERM", shutdown);
955
- process.on("SIGINT", shutdown);
956
- function buildErrorResponse(id, error, fallbackMessage, fallbackCode, details) {
957
- const normalized = normalizeError(error, fallbackMessage);
958
- let mergedDetails;
959
- if (normalized.details || details) {
960
- mergedDetails = {
961
- ...normalized.details || {},
962
- ...details || {}
963
- };
964
- }
965
- return {
966
- id,
967
- ok: false,
968
- error: normalized.message,
969
- errorInfo: {
970
- message: normalized.message,
971
- ...normalized.code || fallbackCode ? { code: normalized.code || fallbackCode } : {},
972
- ...normalized.name ? { name: normalized.name } : {},
973
- ...mergedDetails ? { details: mergedDetails } : {},
974
- ...normalized.cause ? { cause: normalized.cause } : {}
975
- }
976
- };
977
- }