opensteer 0.8.3 → 0.8.4

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/dist/cli/bin.js CHANGED
@@ -1,12 +1,75 @@
1
1
  #!/usr/bin/env node
2
- import { assertExecutionModeSupportsEngine, createOpensteerSemanticRuntime, OpensteerBrowserManager, dispatchSemanticOperation, resolveOpensteerEngineName, discoverLocalCdpBrowsers, inspectCdpEndpoint, resolveFilesystemWorkspacePath, hasPersistedCloudSession, resolveOpensteerExecutionMode } from '../chunk-JK4NMMM2.js';
2
+ import { assertProviderSupportsEngine, createOpensteerSemanticRuntime, OpensteerBrowserManager, dispatchSemanticOperation, pathExists, resolveOpensteerEngineName, normalizeOpensteerProviderKind, discoverLocalCdpBrowsers, inspectCdpEndpoint, resolveOpensteerRuntimeConfig, resolveOpensteerProvider, resolveFilesystemWorkspacePath, readPersistedLocalBrowserSessionRecord, readPersistedCloudSessionRecord, OpensteerCloudClient, isProcessRunning } from '../chunk-C7GWMSTV.js';
3
3
  import process2 from 'process';
4
+ import { readFile } from 'fs/promises';
5
+ import path2 from 'path';
4
6
  import { spawn } from 'child_process';
5
7
  import { existsSync } from 'fs';
6
- import path from 'path';
7
8
  import { createRequire } from 'module';
8
9
  import { fileURLToPath } from 'url';
9
10
 
11
+ var ENV_FILENAMES = [".env", ".env.local"];
12
+ async function loadCliEnvironment(cwd) {
13
+ const protectedKeys = new Set(Object.keys(process.env));
14
+ const directories = collectDirectories(cwd);
15
+ for (const directory of directories) {
16
+ for (const filename of ENV_FILENAMES) {
17
+ const filePath = path2.join(directory, filename);
18
+ if (!await pathExists(filePath)) {
19
+ continue;
20
+ }
21
+ const parsed = parseEnvFile(await readFile(filePath, "utf8"));
22
+ for (const [key, value] of Object.entries(parsed)) {
23
+ if (protectedKeys.has(key)) {
24
+ continue;
25
+ }
26
+ process.env[key] = value;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ function collectDirectories(cwd) {
32
+ const directories = [];
33
+ let current = path2.resolve(cwd);
34
+ for (; ; ) {
35
+ directories.unshift(current);
36
+ const parent = path2.dirname(current);
37
+ if (parent === current) {
38
+ return directories;
39
+ }
40
+ current = parent;
41
+ }
42
+ }
43
+ function parseEnvFile(contents) {
44
+ const parsed = {};
45
+ for (const rawLine of contents.split(/\r?\n/u)) {
46
+ const trimmed = rawLine.trim();
47
+ if (!trimmed || trimmed.startsWith("#")) {
48
+ continue;
49
+ }
50
+ const line = trimmed.startsWith("export ") ? trimmed.slice("export ".length) : trimmed;
51
+ const separatorIndex = line.indexOf("=");
52
+ if (separatorIndex <= 0) {
53
+ continue;
54
+ }
55
+ const key = line.slice(0, separatorIndex).trim();
56
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) {
57
+ continue;
58
+ }
59
+ const rawValue = line.slice(separatorIndex + 1).trim();
60
+ parsed[key] = parseEnvValue(rawValue);
61
+ }
62
+ return parsed;
63
+ }
64
+ function parseEnvValue(rawValue) {
65
+ if (rawValue.length >= 2 && rawValue.startsWith('"') && rawValue.endsWith('"')) {
66
+ return rawValue.slice(1, -1).replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"');
67
+ }
68
+ if (rawValue.length >= 2 && rawValue.startsWith("'") && rawValue.endsWith("'")) {
69
+ return rawValue.slice(1, -1);
70
+ }
71
+ return rawValue.replace(/\s+#.*$/u, "").trimEnd();
72
+ }
10
73
  function createOpensteerSkillsInvocation(input) {
11
74
  const cliArgs = ["add", input.skillSourcePath];
12
75
  if (input.options.all === true) {
@@ -40,22 +103,22 @@ function createOpensteerSkillsInvocation(input) {
40
103
  function resolveOpensteerSkillsCliPath() {
41
104
  const require2 = createRequire(import.meta.url);
42
105
  const skillsPackagePath = require2.resolve("skills/package.json");
43
- const skillsPackageDir = path.dirname(skillsPackagePath);
44
- const cliPath = path.join(skillsPackageDir, "bin", "cli.mjs");
106
+ const skillsPackageDir = path2.dirname(skillsPackagePath);
107
+ const cliPath = path2.join(skillsPackageDir, "bin", "cli.mjs");
45
108
  if (!existsSync(cliPath)) {
46
109
  throw new Error(`skills CLI entrypoint was not found at "${cliPath}".`);
47
110
  }
48
111
  return cliPath;
49
112
  }
50
113
  function resolveOpensteerSkillSourcePath() {
51
- let ancestor = path.dirname(fileURLToPath(import.meta.url));
114
+ let ancestor = path2.dirname(fileURLToPath(import.meta.url));
52
115
  for (let index = 0; index < 6; index += 1) {
53
- const candidate = path.join(ancestor, "skills");
54
- const skillManifest = path.join(candidate, "opensteer", "SKILL.md");
116
+ const candidate = path2.join(ancestor, "skills");
117
+ const skillManifest = path2.join(candidate, "opensteer", "SKILL.md");
55
118
  if (existsSync(skillManifest)) {
56
119
  return candidate;
57
120
  }
58
- ancestor = path.resolve(ancestor, "..");
121
+ ancestor = path2.resolve(ancestor, "..");
59
122
  }
60
123
  throw new Error("Unable to find the packaged Opensteer skill source directory.");
61
124
  }
@@ -97,6 +160,161 @@ function resolveSelectedSkills(options) {
97
160
  }
98
161
  return ["opensteer"];
99
162
  }
163
+ async function collectOpensteerStatus(input) {
164
+ const output = {
165
+ provider: {
166
+ current: input.provider.kind,
167
+ source: mapProviderSource(input.provider.source),
168
+ ...input.cloudConfig === void 0 ? {} : { cloudBaseUrl: input.cloudConfig.baseUrl }
169
+ },
170
+ ...input.workspace === void 0 ? {} : { workspace: input.workspace }
171
+ };
172
+ if (input.workspace === void 0) {
173
+ return output;
174
+ }
175
+ const rootPath = resolveFilesystemWorkspacePath({
176
+ rootDir: input.rootDir,
177
+ workspace: input.workspace
178
+ });
179
+ const localRecord = await readWorkspaceLocalRecord(rootPath);
180
+ const cloudRecord = await readWorkspaceCloudRecord(rootPath);
181
+ return {
182
+ ...output,
183
+ rootPath,
184
+ lanes: {
185
+ local: describeLocalLane(localRecord, input.provider.kind === "local"),
186
+ cloud: await describeCloudLane({
187
+ record: cloudRecord,
188
+ current: input.provider.kind === "cloud",
189
+ cloudConfig: input.cloudConfig
190
+ })
191
+ }
192
+ };
193
+ }
194
+ function renderOpensteerStatus(status) {
195
+ const lines = [
196
+ "Provider resolution",
197
+ ` current: ${status.provider.current}`,
198
+ ` source: ${status.provider.source}`
199
+ ];
200
+ if (status.provider.cloudBaseUrl !== void 0) {
201
+ lines.push(` control api: ${status.provider.cloudBaseUrl}`);
202
+ }
203
+ if (status.workspace !== void 0) {
204
+ lines.push(` workspace: ${status.workspace}`);
205
+ }
206
+ if (status.lanes === void 0) {
207
+ return `${lines.join("\n")}
208
+ `;
209
+ }
210
+ lines.push("", "Live sessions");
211
+ for (const lane of [status.lanes.local, status.lanes.cloud]) {
212
+ lines.push(
213
+ formatLaneRow({
214
+ marker: lane.current ? "*" : " ",
215
+ provider: lane.provider,
216
+ status: lane.status,
217
+ summary: lane.summary ?? "none",
218
+ detail: lane.detail
219
+ })
220
+ );
221
+ }
222
+ return `${lines.join("\n")}
223
+ `;
224
+ }
225
+ async function readWorkspaceLocalRecord(rootPath) {
226
+ if (!await pathExists(rootPath)) {
227
+ return void 0;
228
+ }
229
+ return readPersistedLocalBrowserSessionRecord(rootPath);
230
+ }
231
+ async function readWorkspaceCloudRecord(rootPath) {
232
+ if (!await pathExists(rootPath)) {
233
+ return void 0;
234
+ }
235
+ return readPersistedCloudSessionRecord(rootPath);
236
+ }
237
+ function describeLocalLane(record, current) {
238
+ if (record === void 0 || !isProcessRunning(record.pid)) {
239
+ return {
240
+ provider: "local",
241
+ status: "idle",
242
+ current,
243
+ summary: "none"
244
+ };
245
+ }
246
+ const browser = record.executablePath ? path2.basename(record.executablePath).replace(/\.[A-Za-z0-9]+$/u, "") : void 0;
247
+ return {
248
+ provider: "local",
249
+ status: "active",
250
+ current,
251
+ summary: `PID ${String(record.pid)}`,
252
+ detail: browser ?? record.engine,
253
+ pid: record.pid,
254
+ engine: record.engine,
255
+ ...browser === void 0 ? {} : { browser }
256
+ };
257
+ }
258
+ async function describeCloudLane(input) {
259
+ if (input.record === void 0) {
260
+ return {
261
+ provider: "cloud",
262
+ status: "idle",
263
+ current: input.current,
264
+ summary: "none"
265
+ };
266
+ }
267
+ const base = {
268
+ provider: "cloud",
269
+ status: "connected",
270
+ current: input.current,
271
+ summary: input.record.sessionId,
272
+ detail: input.record.baseUrl,
273
+ sessionId: input.record.sessionId,
274
+ baseUrl: input.record.baseUrl
275
+ };
276
+ if (input.cloudConfig === void 0) {
277
+ return base;
278
+ }
279
+ try {
280
+ const client = new OpensteerCloudClient(input.cloudConfig);
281
+ const session = await client.getSession(input.record.sessionId);
282
+ if (session.status === "closed") {
283
+ return {
284
+ ...base,
285
+ status: "closed",
286
+ ...session.region ?? session.runtimeRegion ? { region: session.region ?? session.runtimeRegion } : {}
287
+ };
288
+ }
289
+ if (session.status === "failed") {
290
+ return {
291
+ ...base,
292
+ status: "stale"
293
+ };
294
+ }
295
+ return {
296
+ ...base,
297
+ ...session.region ?? session.runtimeRegion ? { region: session.region ?? session.runtimeRegion } : {}
298
+ };
299
+ } catch {
300
+ return {
301
+ ...base,
302
+ status: "stale"
303
+ };
304
+ }
305
+ }
306
+ function mapProviderSource(source) {
307
+ if (source === "explicit") {
308
+ return "flag";
309
+ }
310
+ return source;
311
+ }
312
+ function formatLaneRow(input) {
313
+ const provider = input.provider.padEnd(7, " ");
314
+ const status = input.status.padEnd(9, " ");
315
+ const summary = input.summary.padEnd(16, " ");
316
+ return `${input.marker} ${provider} ${status} ${summary}${input.detail ?? ""}`.trimEnd();
317
+ }
100
318
 
101
319
  // src/cli/bin.ts
102
320
  var OPERATION_ALIASES = /* @__PURE__ */ new Map([
@@ -153,6 +371,7 @@ var OPERATION_ALIASES = /* @__PURE__ */ new Map([
153
371
  ["close", "session.close"]
154
372
  ]);
155
373
  async function main() {
374
+ await loadCliEnvironment(process2.cwd());
156
375
  const parsed = parseCommandLine(process2.argv.slice(2));
157
376
  if (parsed.help || parsed.command.length === 0) {
158
377
  printHelp();
@@ -177,6 +396,10 @@ async function main() {
177
396
  }
178
397
  return;
179
398
  }
399
+ if (parsed.command[0] === "status") {
400
+ await handleStatusCommand(parsed);
401
+ return;
402
+ }
180
403
  const operation = parsed.command[0] === "run" ? parsed.rest[0] : resolveOperation(parsed.command);
181
404
  if (!operation) {
182
405
  throw new Error(`Unknown command: ${parsed.command.join(" ")}`);
@@ -184,17 +407,14 @@ async function main() {
184
407
  if (parsed.options.workspace === void 0) {
185
408
  throw new Error('Stateful commands require "--workspace <id>".');
186
409
  }
187
- const mode = await resolveCliExecutionMode(parsed);
188
- assertExecutionModeSupportsEngine(mode, parsed.options.engineName);
189
- const cloudOptions = buildCliCloudOptions(parsed);
190
- if (mode !== "cloud" && (parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
191
- throw new Error("Cloud profile options require cloud mode.");
192
- }
410
+ const provider = resolveCliProvider(parsed);
411
+ assertProviderSupportsEngine(provider.kind, parsed.options.engineName);
412
+ assertCloudCliOptionsMatchProvider(parsed, provider.kind);
413
+ const runtimeProvider = buildCliRuntimeProvider(parsed, provider.kind);
193
414
  if (operation === "session.close") {
194
- if (mode === "cloud") {
415
+ if (provider.kind === "cloud") {
195
416
  const runtime2 = createOpensteerSemanticRuntime({
196
- mode,
197
- ...cloudOptions === void 0 ? {} : { cloud: cloudOptions },
417
+ ...runtimeProvider === void 0 ? {} : { provider: runtimeProvider },
198
418
  engine: parsed.options.engineName,
199
419
  runtimeOptions: {
200
420
  workspace: parsed.options.workspace,
@@ -223,8 +443,7 @@ async function main() {
223
443
  return;
224
444
  }
225
445
  const runtime = createOpensteerSemanticRuntime({
226
- mode,
227
- ...cloudOptions === void 0 ? {} : { cloud: cloudOptions },
446
+ ...runtimeProvider === void 0 ? {} : { provider: runtimeProvider },
228
447
  engine: parsed.options.engineName,
229
448
  runtimeOptions: {
230
449
  workspace: parsed.options.workspace,
@@ -480,10 +699,16 @@ function parseCommandLine(argv) {
480
699
  const contextJson = readJsonObject(rawOptions, "context-json");
481
700
  const inputJson = readJsonObject(rawOptions, "input-json");
482
701
  const schemaJson = readJsonObject(rawOptions, "schema-json");
483
- const local = readOptionalBoolean(rawOptions, "local");
484
- const cloud = readOptionalBoolean(rawOptions, "cloud");
702
+ const providerValue = readSingle(rawOptions, "provider");
703
+ const provider = providerValue === void 0 ? void 0 : normalizeOpensteerProviderKind(providerValue, "--provider");
704
+ const cloudBaseUrl = readSingle(rawOptions, "cloud-base-url");
705
+ const cloudApiKey = readSingle(rawOptions, "cloud-api-key");
485
706
  const cloudProfileId = readSingle(rawOptions, "cloud-profile-id");
486
- const cloudProfileReuseIfActive = readOptionalBoolean(rawOptions, "cloud-profile-reuse-if-active");
707
+ const cloudProfileReuseIfActive = readOptionalBoolean(
708
+ rawOptions,
709
+ "cloud-profile-reuse-if-active"
710
+ );
711
+ const json = readOptionalBoolean(rawOptions, "json");
487
712
  const agents = rawOptions.get("agent");
488
713
  const skills = rawOptions.get("skill");
489
714
  const global = readOptionalBoolean(rawOptions, "global");
@@ -494,10 +719,12 @@ function parseCommandLine(argv) {
494
719
  const options = {
495
720
  ...workspace === void 0 ? {} : { workspace },
496
721
  engineName,
497
- ...local === void 0 ? {} : { local },
498
- ...cloud === void 0 ? {} : { cloud },
722
+ ...provider === void 0 ? {} : { provider },
723
+ ...cloudBaseUrl === void 0 ? {} : { cloudBaseUrl },
724
+ ...cloudApiKey === void 0 ? {} : { cloudApiKey },
499
725
  ...cloudProfileId === void 0 ? {} : { cloudProfileId },
500
726
  ...cloudProfileReuseIfActive === void 0 ? {} : { cloudProfileReuseIfActive },
727
+ ...json === void 0 ? {} : { json },
501
728
  ...agents === void 0 ? {} : { agents },
502
729
  ...skills === void 0 ? {} : { skills },
503
730
  ...global === void 0 ? {} : { global },
@@ -529,41 +756,76 @@ function parseCommandLine(argv) {
529
756
  help
530
757
  };
531
758
  }
532
- async function resolveCliExecutionMode(parsed) {
533
- const environmentMode = process2.env.OPENSTEER_MODE;
534
- if (parsed.options.local === void 0 && parsed.options.cloud === void 0 && (environmentMode === void 0 || environmentMode.trim().length === 0) && parsed.options.workspace !== void 0) {
535
- const rootPath = resolveFilesystemWorkspacePath({
536
- rootDir: process2.cwd(),
537
- workspace: parsed.options.workspace
538
- });
539
- if (await hasPersistedCloudSession(rootPath)) {
540
- return "cloud";
541
- }
542
- }
543
- return resolveOpensteerExecutionMode({
544
- ...parsed.options.local === void 0 ? {} : { local: parsed.options.local },
545
- ...parsed.options.cloud === void 0 ? {} : { cloud: parsed.options.cloud },
546
- ...environmentMode === void 0 ? {} : { environment: environmentMode }
547
- });
548
- }
549
- function buildCliCloudOptions(parsed) {
759
+ function buildCliBrowserProfile(parsed) {
550
760
  if (parsed.options.cloudProfileReuseIfActive === true && parsed.options.cloudProfileId === void 0) {
551
- throw new Error(
552
- '"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".'
553
- );
761
+ throw new Error('"--cloud-profile-reuse-if-active" requires "--cloud-profile-id <id>".');
554
762
  }
555
- const browserProfile = parsed.options.cloudProfileId === void 0 ? void 0 : {
763
+ return parsed.options.cloudProfileId === void 0 ? void 0 : {
556
764
  profileId: parsed.options.cloudProfileId,
557
765
  ...parsed.options.cloudProfileReuseIfActive === true ? { reuseIfActive: true } : {}
558
766
  };
559
- if (browserProfile !== void 0) {
560
- return { browserProfile };
767
+ }
768
+ function buildCliExplicitProvider(parsed) {
769
+ if (parsed.options.provider === "local") {
770
+ return { kind: "local" };
561
771
  }
562
- if (parsed.options.cloud === true) {
563
- return true;
772
+ if (parsed.options.provider === "cloud") {
773
+ return { kind: "cloud" };
564
774
  }
565
775
  return void 0;
566
776
  }
777
+ function resolveCliProvider(parsed) {
778
+ const explicitProvider = buildCliExplicitProvider(parsed);
779
+ return resolveOpensteerProvider({
780
+ ...explicitProvider === void 0 ? {} : { provider: explicitProvider },
781
+ ...process2.env.OPENSTEER_PROVIDER === void 0 ? {} : { environmentProvider: process2.env.OPENSTEER_PROVIDER }
782
+ });
783
+ }
784
+ function buildCliRuntimeProvider(parsed, providerKind) {
785
+ const explicitProvider = buildCliExplicitProvider(parsed);
786
+ if (providerKind === "local") {
787
+ return explicitProvider?.kind === "local" ? explicitProvider : void 0;
788
+ }
789
+ const browserProfile = buildCliBrowserProfile(parsed);
790
+ const hasCloudOverrides = parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || browserProfile !== void 0;
791
+ if (!hasCloudOverrides && explicitProvider?.kind !== "cloud") {
792
+ return void 0;
793
+ }
794
+ return {
795
+ kind: "cloud",
796
+ ...parsed.options.cloudBaseUrl === void 0 ? {} : { baseUrl: parsed.options.cloudBaseUrl },
797
+ ...parsed.options.cloudApiKey === void 0 ? {} : { apiKey: parsed.options.cloudApiKey },
798
+ ...browserProfile === void 0 ? {} : { browserProfile }
799
+ };
800
+ }
801
+ function assertCloudCliOptionsMatchProvider(parsed, providerKind) {
802
+ if (providerKind !== "cloud" && (parsed.options.cloudBaseUrl !== void 0 || parsed.options.cloudApiKey !== void 0 || parsed.options.cloudProfileId !== void 0 || parsed.options.cloudProfileReuseIfActive === true)) {
803
+ throw new Error(
804
+ 'Cloud-specific options require provider=cloud. Set "--provider cloud" or OPENSTEER_PROVIDER=cloud.'
805
+ );
806
+ }
807
+ }
808
+ async function handleStatusCommand(parsed) {
809
+ const provider = resolveCliProvider(parsed);
810
+ assertCloudCliOptionsMatchProvider(parsed, provider.kind);
811
+ const runtimeProvider = buildCliRuntimeProvider(parsed, provider.kind);
812
+ const runtimeConfig = resolveOpensteerRuntimeConfig({
813
+ ...runtimeProvider === void 0 ? {} : { provider: runtimeProvider },
814
+ ...process2.env.OPENSTEER_PROVIDER === void 0 ? {} : { environmentProvider: process2.env.OPENSTEER_PROVIDER }
815
+ });
816
+ const status = await collectOpensteerStatus({
817
+ rootDir: process2.cwd(),
818
+ ...parsed.options.workspace === void 0 ? {} : { workspace: parsed.options.workspace },
819
+ provider,
820
+ ...runtimeConfig.cloud === void 0 ? {} : { cloudConfig: runtimeConfig.cloud }
821
+ });
822
+ if (parsed.options.json === true) {
823
+ process2.stdout.write(`${JSON.stringify(status, null, 2)}
824
+ `);
825
+ return;
826
+ }
827
+ process2.stdout.write(renderOpensteerStatus(status));
828
+ }
567
829
  function parseKeyValueList(values) {
568
830
  if (values === void 0 || values.length === 0) {
569
831
  return void 0;
@@ -633,6 +895,9 @@ function resolveCommandLength(tokens) {
633
895
  if (tokens[0] === "run") {
634
896
  return 1;
635
897
  }
898
+ if (tokens[0] === "status") {
899
+ return 1;
900
+ }
636
901
  for (let length = Math.min(3, tokens.length); length >= 1; length -= 1) {
637
902
  if (OPERATION_ALIASES.has(tokens.slice(0, length).join(" "))) {
638
903
  return length;
@@ -651,6 +916,7 @@ Usage:
651
916
  opensteer input --workspace <id> --text <value> (--element <n> | --selector <css> | --description <text>)
652
917
  opensteer extract --workspace <id> --description <text> [--schema-json <json>]
653
918
  opensteer close --workspace <id>
919
+ opensteer status [--workspace <id>] [--json]
654
920
 
655
921
  opensteer browser status --workspace <id>
656
922
  opensteer browser clone --workspace <id> --source-user-data-dir <path> [--source-profile-directory <name>]
@@ -664,10 +930,12 @@ Usage:
664
930
 
665
931
  Common options:
666
932
  --workspace <id>
667
- --local
668
- --cloud
933
+ --provider local|cloud
934
+ --cloud-base-url <url>
935
+ --cloud-api-key <key>
669
936
  --cloud-profile-id <id>
670
937
  --cloud-profile-reuse-if-active <true|false>
938
+ --json <true|false>
671
939
  --engine playwright|abp
672
940
  --browser temporary|persistent|attach
673
941
  --attach-endpoint <url>