opensteer 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/auth.cjs CHANGED
@@ -94,6 +94,65 @@ function toNonEmptyString(value) {
94
94
  return normalized.length ? normalized : void 0;
95
95
  }
96
96
 
97
+ // src/cloud/credential-selection.ts
98
+ function selectCloudCredential(options) {
99
+ const apiKey = normalizeNonEmptyString(options.apiKey);
100
+ const accessToken = normalizeNonEmptyString(options.accessToken);
101
+ if (apiKey) {
102
+ if (options.authScheme === "bearer") {
103
+ return {
104
+ apiKey,
105
+ authScheme: "bearer",
106
+ kind: "access-token",
107
+ token: apiKey,
108
+ compatibilityBearerApiKey: true
109
+ };
110
+ }
111
+ return {
112
+ apiKey,
113
+ authScheme: "api-key",
114
+ kind: "api-key",
115
+ token: apiKey
116
+ };
117
+ }
118
+ if (accessToken) {
119
+ return {
120
+ accessToken,
121
+ authScheme: "bearer",
122
+ kind: "access-token",
123
+ token: accessToken
124
+ };
125
+ }
126
+ return null;
127
+ }
128
+ function selectCloudCredentialByPrecedence(layers, authScheme) {
129
+ for (const layer of layers) {
130
+ const hasApiKey = layer.hasApiKey ?? Object.prototype.hasOwnProperty.call(layer, "apiKey");
131
+ const hasAccessToken = layer.hasAccessToken ?? Object.prototype.hasOwnProperty.call(layer, "accessToken");
132
+ if (!hasApiKey && !hasAccessToken) {
133
+ continue;
134
+ }
135
+ return {
136
+ source: layer.source,
137
+ apiKey: layer.apiKey,
138
+ accessToken: layer.accessToken,
139
+ hasApiKey,
140
+ hasAccessToken,
141
+ credential: selectCloudCredential({
142
+ apiKey: layer.apiKey,
143
+ accessToken: layer.accessToken,
144
+ authScheme: layer.authScheme ?? authScheme
145
+ })
146
+ };
147
+ }
148
+ return null;
149
+ }
150
+ function normalizeNonEmptyString(value) {
151
+ if (typeof value !== "string") return void 0;
152
+ const normalized = value.trim();
153
+ return normalized.length ? normalized : void 0;
154
+ }
155
+
97
156
  // src/config.ts
98
157
  var DEFAULT_CONFIG = {
99
158
  browser: {
@@ -386,11 +445,6 @@ function normalizeCloudOptions(value) {
386
445
  }
387
446
  return value;
388
447
  }
389
- function normalizeNonEmptyString(value) {
390
- if (typeof value !== "string") return void 0;
391
- const normalized = value.trim();
392
- return normalized.length ? normalized : void 0;
393
- }
394
448
  function parseCloudEnabled(value, source) {
395
449
  if (value == null) return void 0;
396
450
  if (typeof value === "boolean") return value;
@@ -399,6 +453,18 @@ function parseCloudEnabled(value, source) {
399
453
  `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
400
454
  );
401
455
  }
456
+ function resolveCloudCredentialFields(selectedLayer) {
457
+ const credential = selectedLayer?.credential;
458
+ if (!credential) return {};
459
+ if (credential.kind === "api-key" || credential.compatibilityBearerApiKey === true && selectedLayer?.source !== "env") {
460
+ return {
461
+ apiKey: credential.token
462
+ };
463
+ }
464
+ return {
465
+ accessToken: credential.token
466
+ };
467
+ }
402
468
  function resolveCloudSelection(config, env = process.env) {
403
469
  const configCloud = parseCloudEnabled(config.cloud, "cloud");
404
470
  if (configCloud !== void 0) {
@@ -435,6 +501,9 @@ function resolveConfigWithEnv(input = {}, options = {}) {
435
501
  });
436
502
  assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
437
503
  assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
504
+ const fileCloudOptions = normalizeCloudOptions(fileConfig.cloud);
505
+ const fileHasCloudApiKey = hasOwn(fileCloudOptions, "apiKey");
506
+ const fileHasCloudAccessToken = hasOwn(fileCloudOptions, "accessToken");
438
507
  const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
439
508
  const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
440
509
  const env = resolveEnv(envRootDir, {
@@ -473,13 +542,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
473
542
  const envAccessTokenRaw = resolveOpensteerAccessToken(env);
474
543
  const envBaseUrl = resolveOpensteerBaseUrl(env);
475
544
  const envAuthScheme = resolveOpensteerAuthScheme(env);
476
- if (envApiKey && envAccessTokenRaw) {
477
- throw new Error(
478
- "OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
479
- );
480
- }
481
- const envAccessToken = envAccessTokenRaw || (envAuthScheme === "bearer" ? envApiKey : void 0);
482
- const envApiCredential = envAuthScheme === "bearer" && !envAccessTokenRaw ? void 0 : envApiKey;
483
545
  const envCloudProfileId = resolveOpensteerCloudProfileId(env);
484
546
  const envCloudProfileReuseIfActive = resolveOpensteerCloudProfileReuseIfActive(env);
485
547
  const envCloudAnnounce = parseCloudAnnounce(
@@ -508,11 +570,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
508
570
  const inputHasCloudBaseUrl = Boolean(
509
571
  inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
510
572
  );
511
- if (normalizeNonEmptyString(inputCloudOptions?.apiKey) && normalizeNonEmptyString(inputCloudOptions?.accessToken)) {
512
- throw new Error(
513
- "cloud.apiKey and cloud.accessToken are mutually exclusive. Set only one."
514
- );
515
- }
516
573
  const cloudSelection = resolveCloudSelection({
517
574
  cloud: resolved.cloud
518
575
  }, env);
@@ -523,11 +580,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
523
580
  accessToken: resolvedCloudAccessTokenRaw,
524
581
  ...resolvedCloudRest
525
582
  } = resolvedCloud;
526
- if (normalizeNonEmptyString(resolvedCloudApiKeyRaw) && normalizeNonEmptyString(resolvedCloudAccessTokenRaw)) {
527
- throw new Error(
528
- "Cloud config cannot include both apiKey and accessToken at the same time."
529
- );
530
- }
531
583
  const resolvedCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
532
584
  resolvedCloud.browserProfile,
533
585
  "resolved.cloud.browserProfile"
@@ -539,25 +591,40 @@ function resolveConfigWithEnv(input = {}, options = {}) {
539
591
  const browserProfile = inputCloudBrowserProfile ?? envCloudBrowserProfile ?? resolvedCloudBrowserProfile;
540
592
  let authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
541
593
  const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
542
- const credentialOverriddenByInput = inputHasCloudApiKey || inputHasCloudAccessToken;
543
- let apiKey = normalizeNonEmptyString(resolvedCloudApiKeyRaw);
544
- let accessToken = normalizeNonEmptyString(resolvedCloudAccessTokenRaw);
545
- if (!credentialOverriddenByInput) {
546
- if (envAccessToken) {
547
- accessToken = envAccessToken;
548
- apiKey = void 0;
549
- } else if (envApiCredential) {
550
- apiKey = envApiCredential;
551
- accessToken = void 0;
552
- }
553
- }
594
+ const selectedCredentialLayer = selectCloudCredentialByPrecedence(
595
+ [
596
+ {
597
+ source: "input",
598
+ apiKey: inputCloudOptions?.apiKey,
599
+ accessToken: inputCloudOptions?.accessToken,
600
+ hasApiKey: inputHasCloudApiKey,
601
+ hasAccessToken: inputHasCloudAccessToken
602
+ },
603
+ {
604
+ source: "env",
605
+ apiKey: envApiKey,
606
+ accessToken: envAccessTokenRaw,
607
+ hasApiKey: envApiKey !== void 0,
608
+ hasAccessToken: envAccessTokenRaw !== void 0
609
+ },
610
+ {
611
+ source: "file",
612
+ apiKey: fileCloudOptions?.apiKey,
613
+ accessToken: fileCloudOptions?.accessToken,
614
+ hasApiKey: fileHasCloudApiKey,
615
+ hasAccessToken: fileHasCloudAccessToken
616
+ }
617
+ ],
618
+ authScheme
619
+ );
620
+ const { apiKey, accessToken } = resolveCloudCredentialFields(selectedCredentialLayer);
554
621
  if (accessToken) {
555
622
  authScheme = "bearer";
556
623
  }
557
624
  resolved.cloud = {
558
625
  ...resolvedCloudRest,
559
- ...inputHasCloudApiKey ? { apiKey: resolvedCloudApiKeyRaw } : apiKey ? { apiKey } : {},
560
- ...inputHasCloudAccessToken ? { accessToken: resolvedCloudAccessTokenRaw } : accessToken ? { accessToken } : {},
626
+ ...apiKey ? { apiKey } : selectedCredentialLayer?.hasApiKey && !accessToken ? { apiKey: selectedCredentialLayer.apiKey } : {},
627
+ ...accessToken ? { accessToken } : selectedCredentialLayer?.hasAccessToken && !apiKey ? { accessToken: selectedCredentialLayer.accessToken } : {},
561
628
  authScheme,
562
629
  announce,
563
630
  ...browserProfile ? { browserProfile } : {}
@@ -577,59 +644,21 @@ function resolveConfigWithEnv(input = {}, options = {}) {
577
644
 
578
645
  // src/auth/credential-resolver.ts
579
646
  function resolveCloudCredential(options) {
580
- const flagApiKey = normalizeToken(options.apiKeyFlag);
581
- const flagAccessToken = normalizeToken(options.accessTokenFlag);
582
- if (flagApiKey && flagAccessToken) {
583
- throw new Error("--api-key and --access-token are mutually exclusive.");
584
- }
585
- if (flagAccessToken) {
586
- return {
587
- kind: "access-token",
588
- source: "flag",
589
- token: flagAccessToken,
590
- authScheme: "bearer"
591
- };
592
- }
593
- if (flagApiKey) {
594
- return {
595
- kind: "api-key",
596
- source: "flag",
597
- token: flagApiKey,
598
- authScheme: "api-key"
599
- };
647
+ const flagCredential = selectCloudCredential({
648
+ apiKey: options.apiKeyFlag,
649
+ accessToken: options.accessTokenFlag
650
+ });
651
+ if (flagCredential) {
652
+ return toResolvedCloudCredential("flag", flagCredential);
600
653
  }
601
654
  const envAuthScheme = parseEnvAuthScheme(options.env.OPENSTEER_AUTH_SCHEME);
602
- const envApiKey = normalizeToken(options.env.OPENSTEER_API_KEY);
603
- const envAccessToken = normalizeToken(options.env.OPENSTEER_ACCESS_TOKEN);
604
- if (envApiKey && envAccessToken) {
605
- throw new Error(
606
- "OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
607
- );
608
- }
609
- if (envAccessToken) {
610
- return {
611
- kind: "access-token",
612
- source: "env",
613
- token: envAccessToken,
614
- authScheme: "bearer"
615
- };
616
- }
617
- if (envApiKey) {
618
- if (envAuthScheme === "bearer") {
619
- return {
620
- kind: "access-token",
621
- source: "env",
622
- token: envApiKey,
623
- authScheme: "bearer",
624
- compatibilityBearerApiKey: true
625
- };
626
- }
627
- return {
628
- kind: "api-key",
629
- source: "env",
630
- token: envApiKey,
631
- authScheme: envAuthScheme ?? "api-key"
632
- };
655
+ const envCredential = selectCloudCredential({
656
+ apiKey: options.env.OPENSTEER_API_KEY,
657
+ accessToken: options.env.OPENSTEER_ACCESS_TOKEN,
658
+ authScheme: envAuthScheme
659
+ });
660
+ if (envCredential) {
661
+ return toResolvedCloudCredential("env", envCredential);
633
662
  }
634
663
  return null;
635
664
  }
@@ -659,6 +688,23 @@ function normalizeToken(value) {
659
688
  const normalized = value.trim();
660
689
  return normalized.length ? normalized : void 0;
661
690
  }
691
+ function toResolvedCloudCredential(source, credential) {
692
+ if (credential.compatibilityBearerApiKey) {
693
+ return {
694
+ kind: credential.kind,
695
+ source,
696
+ token: credential.token,
697
+ authScheme: credential.authScheme,
698
+ compatibilityBearerApiKey: true
699
+ };
700
+ }
701
+ return {
702
+ kind: credential.kind,
703
+ source,
704
+ token: credential.token,
705
+ authScheme: credential.authScheme
706
+ };
707
+ }
662
708
 
663
709
  // src/auth/machine-credential-store.ts
664
710
  var import_node_crypto = require("crypto");
@@ -804,13 +850,10 @@ function createKeychainStore() {
804
850
  }
805
851
 
806
852
  // src/auth/machine-credential-store.ts
807
- var METADATA_VERSION = 1;
808
- var ACTIVE_TARGET_VERSION = 1;
853
+ var METADATA_VERSION = 2;
854
+ var ACTIVE_TARGET_VERSION = 2;
809
855
  var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
810
856
  var KEYCHAIN_ACCOUNT_PREFIX = "machine:";
811
- var LEGACY_KEYCHAIN_ACCOUNT = "machine";
812
- var LEGACY_METADATA_FILE_NAME = "cli-login.json";
813
- var LEGACY_FALLBACK_SECRET_FILE_NAME = "cli-login.secret.json";
814
857
  var ACTIVE_TARGET_FILE_NAME = "cli-target.json";
815
858
  var MachineCredentialStore = class {
816
859
  authDir;
@@ -825,8 +868,11 @@ var MachineCredentialStore = class {
825
868
  this.warn = options.warn ?? (() => void 0);
826
869
  }
827
870
  readCloudCredential(target) {
828
- const slot = resolveCredentialSlot(this.authDir, target);
829
- return this.readCredentialSlot(slot, target) ?? this.readAndMigrateLegacyCredential(target);
871
+ const normalizedTarget = normalizeCloudCredentialTarget(target);
872
+ return this.readCredentialSlot(
873
+ resolveCredentialSlot(this.authDir, normalizedTarget),
874
+ normalizedTarget
875
+ );
830
876
  }
831
877
  writeCloudCredential(args) {
832
878
  const accessToken = args.accessToken.trim();
@@ -834,12 +880,8 @@ var MachineCredentialStore = class {
834
880
  if (!accessToken || !refreshToken2) {
835
881
  throw new Error("Cannot persist empty machine credential secrets.");
836
882
  }
837
- const baseUrl = normalizeCredentialUrl(args.baseUrl, "baseUrl");
838
- const siteUrl = normalizeCredentialUrl(args.siteUrl, "siteUrl");
839
- const slot = resolveCredentialSlot(this.authDir, {
840
- baseUrl,
841
- siteUrl
842
- });
883
+ const baseUrl = normalizeCredentialUrl(args.baseUrl);
884
+ const slot = resolveCredentialSlot(this.authDir, { baseUrl });
843
885
  ensureDirectory(this.authDir);
844
886
  const secretPayload = {
845
887
  accessToken,
@@ -866,7 +908,6 @@ var MachineCredentialStore = class {
866
908
  version: METADATA_VERSION,
867
909
  secretBackend,
868
910
  baseUrl,
869
- siteUrl,
870
911
  scope: args.scope,
871
912
  obtainedAt: args.obtainedAt,
872
913
  expiresAt: args.expiresAt,
@@ -878,23 +919,18 @@ var MachineCredentialStore = class {
878
919
  return readActiveCloudTargetMetadata(resolveActiveTargetPath(this.authDir));
879
920
  }
880
921
  writeActiveCloudTarget(target) {
881
- const baseUrl = normalizeCredentialUrl(target.baseUrl, "baseUrl");
882
- const siteUrl = normalizeCredentialUrl(target.siteUrl, "siteUrl");
922
+ const baseUrl = normalizeCredentialUrl(target.baseUrl);
883
923
  ensureDirectory(this.authDir);
884
924
  writeJsonFile(resolveActiveTargetPath(this.authDir), {
885
925
  version: ACTIVE_TARGET_VERSION,
886
926
  baseUrl,
887
- siteUrl,
888
927
  updatedAt: Date.now()
889
928
  });
890
929
  }
891
930
  clearCloudCredential(target) {
892
- this.clearCredentialSlot(resolveCredentialSlot(this.authDir, target));
893
- const legacySlot = resolveLegacyCredentialSlot(this.authDir);
894
- const legacyMetadata = readMetadata(legacySlot.metadataPath);
895
- if (legacyMetadata && matchesCredentialTarget(legacyMetadata, target)) {
896
- this.clearCredentialSlot(legacySlot);
897
- }
931
+ this.clearCredentialSlot(
932
+ resolveCredentialSlot(this.authDir, normalizeCloudCredentialTarget(target))
933
+ );
898
934
  }
899
935
  readCredentialSlot(slot, target) {
900
936
  const metadata = readMetadata(slot.metadataPath);
@@ -910,7 +946,6 @@ var MachineCredentialStore = class {
910
946
  }
911
947
  return {
912
948
  baseUrl: metadata.baseUrl,
913
- siteUrl: metadata.siteUrl,
914
949
  scope: metadata.scope,
915
950
  accessToken: secret.accessToken,
916
951
  refreshToken: secret.refreshToken,
@@ -918,16 +953,6 @@ var MachineCredentialStore = class {
918
953
  expiresAt: metadata.expiresAt
919
954
  };
920
955
  }
921
- readAndMigrateLegacyCredential(target) {
922
- const legacySlot = resolveLegacyCredentialSlot(this.authDir);
923
- const legacyCredential = this.readCredentialSlot(legacySlot, target);
924
- if (!legacyCredential) {
925
- return null;
926
- }
927
- this.writeCloudCredential(legacyCredential);
928
- this.clearCredentialSlot(legacySlot);
929
- return legacyCredential;
930
- }
931
956
  readSecret(slot, backend) {
932
957
  if (backend === "keychain" && this.keychain) {
933
958
  try {
@@ -968,9 +993,8 @@ function createMachineCredentialStore(options = {}) {
968
993
  return new MachineCredentialStore(options);
969
994
  }
970
995
  function resolveCredentialSlot(authDir, target) {
971
- const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl, "baseUrl");
972
- const normalizedSiteUrl = normalizeCredentialUrl(target.siteUrl, "siteUrl");
973
- const storageKey = (0, import_node_crypto.createHash)("sha256").update(`${normalizedBaseUrl}\0${normalizedSiteUrl}`).digest("hex").slice(0, 24);
996
+ const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
997
+ const storageKey = (0, import_node_crypto.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
974
998
  return {
975
999
  keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
976
1000
  metadataPath: import_node_path.default.join(authDir, `cli-login.${storageKey}.json`),
@@ -980,26 +1004,24 @@ function resolveCredentialSlot(authDir, target) {
980
1004
  )
981
1005
  };
982
1006
  }
983
- function resolveLegacyCredentialSlot(authDir) {
984
- return {
985
- keychainAccount: LEGACY_KEYCHAIN_ACCOUNT,
986
- metadataPath: import_node_path.default.join(authDir, LEGACY_METADATA_FILE_NAME),
987
- fallbackSecretPath: import_node_path.default.join(authDir, LEGACY_FALLBACK_SECRET_FILE_NAME)
988
- };
989
- }
990
1007
  function resolveActiveTargetPath(authDir) {
991
1008
  return import_node_path.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
992
1009
  }
993
1010
  function matchesCredentialTarget(value, target) {
994
- return normalizeCredentialUrl(value.baseUrl, "baseUrl") === normalizeCredentialUrl(target.baseUrl, "baseUrl") && normalizeCredentialUrl(value.siteUrl, "siteUrl") === normalizeCredentialUrl(target.siteUrl, "siteUrl");
1011
+ return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
995
1012
  }
996
- function normalizeCredentialUrl(value, field) {
1013
+ function normalizeCredentialUrl(value) {
997
1014
  const normalized = stripTrailingSlashes(value.trim());
998
1015
  if (!normalized) {
999
- throw new Error(`Cannot persist machine credential without ${field}.`);
1016
+ throw new Error("Cannot persist machine credential without baseUrl.");
1000
1017
  }
1001
1018
  return normalized;
1002
1019
  }
1020
+ function normalizeCloudCredentialTarget(target) {
1021
+ return {
1022
+ baseUrl: normalizeCredentialUrl(target.baseUrl)
1023
+ };
1024
+ }
1003
1025
  function resolveConfigDir(appName, env) {
1004
1026
  if (process.platform === "win32") {
1005
1027
  const appData = env.APPDATA?.trim() || import_node_path.default.join(import_node_os.default.homedir(), "AppData", "Roaming");
@@ -1038,7 +1060,6 @@ function readMetadata(filePath) {
1038
1060
  return null;
1039
1061
  }
1040
1062
  if (typeof parsed.baseUrl !== "string" || !parsed.baseUrl.trim()) return null;
1041
- if (typeof parsed.siteUrl !== "string" || !parsed.siteUrl.trim()) return null;
1042
1063
  if (!Array.isArray(parsed.scope)) return null;
1043
1064
  if (typeof parsed.obtainedAt !== "number") return null;
1044
1065
  if (typeof parsed.expiresAt !== "number") return null;
@@ -1047,7 +1068,6 @@ function readMetadata(filePath) {
1047
1068
  version: parsed.version,
1048
1069
  secretBackend: parsed.secretBackend,
1049
1070
  baseUrl: parsed.baseUrl,
1050
- siteUrl: parsed.siteUrl,
1051
1071
  scope: parsed.scope.filter(
1052
1072
  (value) => typeof value === "string"
1053
1073
  ),
@@ -1072,12 +1092,8 @@ function readActiveCloudTargetMetadata(filePath) {
1072
1092
  if (typeof parsed.baseUrl !== "string" || !parsed.baseUrl.trim()) {
1073
1093
  return null;
1074
1094
  }
1075
- if (typeof parsed.siteUrl !== "string" || !parsed.siteUrl.trim()) {
1076
- return null;
1077
- }
1078
1095
  return {
1079
- baseUrl: parsed.baseUrl,
1080
- siteUrl: parsed.siteUrl
1096
+ baseUrl: parsed.baseUrl
1081
1097
  };
1082
1098
  } catch {
1083
1099
  return null;
@@ -1102,14 +1118,13 @@ function readSecretFile(filePath) {
1102
1118
  return null;
1103
1119
  }
1104
1120
  try {
1105
- const raw = import_node_fs.default.readFileSync(filePath, "utf8");
1106
- return parseSecretPayload(raw);
1121
+ return parseSecretPayload(import_node_fs.default.readFileSync(filePath, "utf8"));
1107
1122
  } catch {
1108
1123
  return null;
1109
1124
  }
1110
1125
  }
1111
- function writeJsonFile(filePath, payload, options = {}) {
1112
- import_node_fs.default.writeFileSync(filePath, JSON.stringify(payload, null, 2), {
1126
+ function writeJsonFile(filePath, value, options = {}) {
1127
+ import_node_fs.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
1113
1128
  encoding: "utf8",
1114
1129
  mode: options.mode ?? 384
1115
1130
  });
@@ -1140,11 +1155,12 @@ Commands:
1140
1155
 
1141
1156
  Options:
1142
1157
  --base-url <url> Cloud API base URL (defaults to env or the last selected host)
1143
- --site-url <url> Cloud site URL for browser/device auth (defaults to env or the last selected host)
1144
1158
  --json JSON output (login prompts go to stderr)
1145
1159
  --no-browser Do not auto-open your default browser during login
1146
1160
  -h, --help Show this help
1147
1161
  `;
1162
+ var DEFAULT_AUTH_SITE_URL = "https://opensteer.com";
1163
+ var INTERNAL_AUTH_SITE_URL_ENV = "OPENSTEER_INTERNAL_AUTH_SITE_URL";
1148
1164
  function createDefaultDeps() {
1149
1165
  const env = process.env;
1150
1166
  return {
@@ -1196,13 +1212,6 @@ function parseAuthCommonArgs(rawArgs) {
1196
1212
  i = value.nextIndex;
1197
1213
  continue;
1198
1214
  }
1199
- if (arg === "--site-url") {
1200
- const value = readFlagValue(rawArgs, i, "--site-url");
1201
- if (!value.ok) return { args, error: value.error };
1202
- args.siteUrl = value.value;
1203
- i = value.nextIndex;
1204
- continue;
1205
- }
1206
1215
  return {
1207
1216
  args,
1208
1217
  error: `Unsupported option "${arg}".`
@@ -1272,16 +1281,19 @@ function resolveBaseUrl(provided, env) {
1272
1281
  assertSecureUrl(baseUrl, "--base-url");
1273
1282
  return baseUrl;
1274
1283
  }
1275
- function resolveSiteUrl(provided, baseUrl, env) {
1276
- const siteUrl = normalizeCloudBaseUrl(
1277
- (provided || env.OPENSTEER_CLOUD_SITE_URL || deriveSiteUrlFromBaseUrl(baseUrl)).trim()
1284
+ function resolveAuthSiteUrl(env) {
1285
+ const authSiteUrl = normalizeCloudBaseUrl(
1286
+ (env[INTERNAL_AUTH_SITE_URL_ENV] || DEFAULT_AUTH_SITE_URL).trim()
1278
1287
  );
1279
- assertSecureUrl(siteUrl, "--site-url");
1280
- return siteUrl;
1288
+ assertSecureUrl(
1289
+ authSiteUrl,
1290
+ `environment variable ${INTERNAL_AUTH_SITE_URL_ENV}`
1291
+ );
1292
+ return authSiteUrl;
1281
1293
  }
1282
- function hasExplicitCloudTargetSelection(providedBaseUrl, providedSiteUrl, env) {
1294
+ function hasExplicitCloudTargetSelection(providedBaseUrl, env) {
1283
1295
  return Boolean(
1284
- providedBaseUrl?.trim() || providedSiteUrl?.trim() || env.OPENSTEER_BASE_URL?.trim() || env.OPENSTEER_CLOUD_SITE_URL?.trim()
1296
+ providedBaseUrl?.trim() || env.OPENSTEER_BASE_URL?.trim()
1285
1297
  );
1286
1298
  }
1287
1299
  function readRememberedCloudTarget(store) {
@@ -1291,57 +1303,21 @@ function readRememberedCloudTarget(store) {
1291
1303
  }
1292
1304
  try {
1293
1305
  const baseUrl = normalizeCloudBaseUrl(activeTarget.baseUrl);
1294
- const siteUrl = normalizeCloudBaseUrl(activeTarget.siteUrl);
1295
1306
  assertSecureUrl(baseUrl, "--base-url");
1296
- assertSecureUrl(siteUrl, "--site-url");
1297
- return {
1298
- baseUrl,
1299
- siteUrl
1300
- };
1307
+ return { baseUrl };
1301
1308
  } catch {
1302
1309
  return null;
1303
1310
  }
1304
1311
  }
1305
- function resolveCloudTarget(args, env, store) {
1306
- if (!hasExplicitCloudTargetSelection(args.baseUrl, args.siteUrl, env)) {
1312
+ function resolveCloudTarget(args, env, store, options = {}) {
1313
+ if (options.allowRememberedTarget !== false && !hasExplicitCloudTargetSelection(args.baseUrl, env)) {
1307
1314
  const rememberedTarget = readRememberedCloudTarget(store);
1308
1315
  if (rememberedTarget) {
1309
1316
  return rememberedTarget;
1310
1317
  }
1311
1318
  }
1312
1319
  const baseUrl = resolveBaseUrl(args.baseUrl, env);
1313
- const siteUrl = resolveSiteUrl(args.siteUrl, baseUrl, env);
1314
- return {
1315
- baseUrl,
1316
- siteUrl
1317
- };
1318
- }
1319
- function deriveSiteUrlFromBaseUrl(baseUrl) {
1320
- let parsed;
1321
- try {
1322
- parsed = new URL(baseUrl);
1323
- } catch {
1324
- return "https://opensteer.com";
1325
- }
1326
- const hostname = parsed.hostname.toLowerCase();
1327
- if (hostname.startsWith("api.")) {
1328
- parsed.hostname = hostname.slice("api.".length);
1329
- parsed.pathname = "";
1330
- parsed.search = "";
1331
- parsed.hash = "";
1332
- return normalizeCloudBaseUrl(parsed.toString());
1333
- }
1334
- if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
1335
- parsed.port = "3001";
1336
- parsed.pathname = "";
1337
- parsed.search = "";
1338
- parsed.hash = "";
1339
- return normalizeCloudBaseUrl(parsed.toString());
1340
- }
1341
- parsed.pathname = "";
1342
- parsed.search = "";
1343
- parsed.hash = "";
1344
- return normalizeCloudBaseUrl(parsed.toString());
1320
+ return { baseUrl };
1345
1321
  }
1346
1322
  function assertSecureUrl(value, flag) {
1347
1323
  let parsed;
@@ -1416,10 +1392,10 @@ function parseCliOauthError(error) {
1416
1392
  interval: typeof root.interval === "number" ? root.interval : void 0
1417
1393
  };
1418
1394
  }
1419
- async function startDeviceAuthorization(siteUrl, fetchFn) {
1395
+ async function startDeviceAuthorization(authSiteUrl, fetchFn) {
1420
1396
  const response = await postJson(
1421
1397
  fetchFn,
1422
- `${siteUrl}/api/cli-auth/device/start`,
1398
+ `${authSiteUrl}/api/cli-auth/device/start`,
1423
1399
  {
1424
1400
  scope: ["cloud:browser"]
1425
1401
  }
@@ -1429,24 +1405,24 @@ async function startDeviceAuthorization(siteUrl, fetchFn) {
1429
1405
  }
1430
1406
  return response;
1431
1407
  }
1432
- async function pollDeviceToken(siteUrl, deviceCode, fetchFn) {
1408
+ async function pollDeviceToken(authSiteUrl, deviceCode, fetchFn) {
1433
1409
  return await postJson(
1434
1410
  fetchFn,
1435
- `${siteUrl}/api/cli-auth/device/token`,
1411
+ `${authSiteUrl}/api/cli-auth/device/token`,
1436
1412
  {
1437
1413
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1438
1414
  device_code: deviceCode
1439
1415
  }
1440
1416
  );
1441
1417
  }
1442
- async function refreshToken(siteUrl, refreshTokenValue, fetchFn) {
1443
- return await postJson(fetchFn, `${siteUrl}/api/cli-auth/token`, {
1418
+ async function refreshToken(authSiteUrl, refreshTokenValue, fetchFn) {
1419
+ return await postJson(fetchFn, `${authSiteUrl}/api/cli-auth/token`, {
1444
1420
  grant_type: "refresh_token",
1445
1421
  refresh_token: refreshTokenValue
1446
1422
  });
1447
1423
  }
1448
- async function revokeToken(siteUrl, refreshTokenValue, fetchFn) {
1449
- await postJson(fetchFn, `${siteUrl}/api/cli-auth/revoke`, {
1424
+ async function revokeToken(authSiteUrl, refreshTokenValue, fetchFn) {
1425
+ await postJson(fetchFn, `${authSiteUrl}/api/cli-auth/revoke`, {
1450
1426
  token: refreshTokenValue
1451
1427
  });
1452
1428
  }
@@ -1463,7 +1439,7 @@ async function openDefaultBrowser(url) {
1463
1439
  }
1464
1440
  }
1465
1441
  async function runDeviceLoginFlow(args) {
1466
- const start = await startDeviceAuthorization(args.siteUrl, args.fetchFn);
1442
+ const start = await startDeviceAuthorization(args.authSiteUrl, args.fetchFn);
1467
1443
  if (args.openBrowser) {
1468
1444
  args.writeProgress(
1469
1445
  "Opening your default browser for Opensteer CLI authentication.\n"
@@ -1508,7 +1484,7 @@ ${start.verification_uri_complete}
1508
1484
  await args.sleep(pollIntervalMs);
1509
1485
  try {
1510
1486
  const tokenPayload = await pollDeviceToken(
1511
- args.siteUrl,
1487
+ args.authSiteUrl,
1512
1488
  start.device_code,
1513
1489
  args.fetchFn
1514
1490
  );
@@ -1556,7 +1532,7 @@ ${start.verification_uri_complete}
1556
1532
  }
1557
1533
  async function refreshSavedCredential(saved, deps) {
1558
1534
  const tokenPayload = await refreshToken(
1559
- saved.siteUrl,
1535
+ resolveAuthSiteUrl(deps.env),
1560
1536
  saved.refreshToken,
1561
1537
  deps.fetchFn
1562
1538
  );
@@ -1569,7 +1545,6 @@ async function refreshSavedCredential(saved, deps) {
1569
1545
  };
1570
1546
  deps.store.writeCloudCredential({
1571
1547
  baseUrl: saved.baseUrl,
1572
- siteUrl: saved.siteUrl,
1573
1548
  scope: updated.scope,
1574
1549
  accessToken: updated.accessToken,
1575
1550
  refreshToken: updated.refreshToken,
@@ -1598,8 +1573,7 @@ async function ensureSavedCredentialIsFresh(saved, deps) {
1598
1573
  const oauth = parseCliOauthError(error.body);
1599
1574
  if (oauth?.error === "invalid_grant" || oauth?.error === "expired_token") {
1600
1575
  deps.store.clearCloudCredential({
1601
- baseUrl: saved.baseUrl,
1602
- siteUrl: saved.siteUrl
1576
+ baseUrl: saved.baseUrl
1603
1577
  });
1604
1578
  return null;
1605
1579
  }
@@ -1699,7 +1673,7 @@ async function ensureCloudCredentialsForCommand(options) {
1699
1673
  `);
1700
1674
  }
1701
1675
  });
1702
- const { baseUrl, siteUrl } = resolveCloudTarget(options, env, store);
1676
+ const { baseUrl } = resolveCloudTarget(options, env, store);
1703
1677
  const initialCredential = resolveCloudCredential({
1704
1678
  env,
1705
1679
  apiKeyFlag: options.apiKeyFlag,
@@ -1707,11 +1681,9 @@ async function ensureCloudCredentialsForCommand(options) {
1707
1681
  });
1708
1682
  let credential = initialCredential;
1709
1683
  if (!credential) {
1710
- const saved = store.readCloudCredential({
1711
- baseUrl,
1712
- siteUrl
1713
- });
1684
+ const saved = store.readCloudCredential({ baseUrl });
1714
1685
  const freshSaved = saved ? await ensureSavedCredentialIsFresh(saved, {
1686
+ env,
1715
1687
  fetchFn,
1716
1688
  store,
1717
1689
  now,
@@ -1729,7 +1701,7 @@ async function ensureCloudCredentialsForCommand(options) {
1729
1701
  if (!credential) {
1730
1702
  if (options.autoLoginIfNeeded && (options.interactive ?? false)) {
1731
1703
  const loggedIn = await runDeviceLoginFlow({
1732
- siteUrl,
1704
+ authSiteUrl: resolveAuthSiteUrl(env),
1733
1705
  fetchFn,
1734
1706
  writeProgress,
1735
1707
  openExternalUrl,
@@ -1739,7 +1711,6 @@ async function ensureCloudCredentialsForCommand(options) {
1739
1711
  });
1740
1712
  store.writeCloudCredential({
1741
1713
  baseUrl,
1742
- siteUrl,
1743
1714
  scope: loggedIn.scope,
1744
1715
  accessToken: loggedIn.accessToken,
1745
1716
  refreshToken: loggedIn.refreshToken,
@@ -1757,28 +1728,25 @@ async function ensureCloudCredentialsForCommand(options) {
1757
1728
  throw new Error(toAuthMissingMessage(options.commandName));
1758
1729
  }
1759
1730
  }
1760
- store.writeActiveCloudTarget({
1761
- baseUrl,
1762
- siteUrl
1763
- });
1731
+ store.writeActiveCloudTarget({ baseUrl });
1764
1732
  applyCloudCredentialToEnv(env, credential);
1765
1733
  env.OPENSTEER_BASE_URL = baseUrl;
1766
- env.OPENSTEER_CLOUD_SITE_URL = siteUrl;
1767
1734
  return {
1768
1735
  token: credential.token,
1769
1736
  authScheme: credential.authScheme,
1770
1737
  source: credential.source,
1771
1738
  kind: credential.kind,
1772
- baseUrl,
1773
- siteUrl
1739
+ baseUrl
1774
1740
  };
1775
1741
  }
1776
1742
  async function runLogin(args, deps) {
1777
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
1743
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store, {
1744
+ allowRememberedTarget: false
1745
+ });
1778
1746
  const writeProgress = args.json ? deps.writeStderr : deps.writeStdout;
1779
1747
  const browserOpenMode = describeBrowserOpenMode(args, deps);
1780
1748
  const login = await runDeviceLoginFlow({
1781
- siteUrl,
1749
+ authSiteUrl: resolveAuthSiteUrl(deps.env),
1782
1750
  fetchFn: deps.fetchFn,
1783
1751
  writeProgress,
1784
1752
  openExternalUrl: deps.openExternalUrl,
@@ -1789,22 +1757,17 @@ async function runLogin(args, deps) {
1789
1757
  });
1790
1758
  deps.store.writeCloudCredential({
1791
1759
  baseUrl,
1792
- siteUrl,
1793
1760
  scope: login.scope,
1794
1761
  accessToken: login.accessToken,
1795
1762
  refreshToken: login.refreshToken,
1796
1763
  obtainedAt: deps.now(),
1797
1764
  expiresAt: login.expiresAt
1798
1765
  });
1799
- deps.store.writeActiveCloudTarget({
1800
- baseUrl,
1801
- siteUrl
1802
- });
1766
+ deps.store.writeActiveCloudTarget({ baseUrl });
1803
1767
  if (args.json) {
1804
1768
  writeJsonLine(deps, {
1805
1769
  loggedIn: true,
1806
1770
  baseUrl,
1807
- siteUrl,
1808
1771
  expiresAt: login.expiresAt,
1809
1772
  scope: login.scope,
1810
1773
  authSource: "device"
@@ -1812,33 +1775,20 @@ async function runLogin(args, deps) {
1812
1775
  return 0;
1813
1776
  }
1814
1777
  writeHumanLine(deps, "Opensteer CLI login successful.");
1815
- writeHumanLine(deps, ` Site URL: ${siteUrl}`);
1816
- writeHumanLine(deps, ` API Base URL: ${baseUrl}`);
1817
- writeHumanLine(deps, ` Expires At: ${new Date(login.expiresAt).toISOString()}`);
1818
1778
  return 0;
1819
1779
  }
1820
1780
  async function runStatus(args, deps) {
1821
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
1822
- deps.store.writeActiveCloudTarget({
1823
- baseUrl,
1824
- siteUrl
1825
- });
1826
- const saved = deps.store.readCloudCredential({
1827
- baseUrl,
1828
- siteUrl
1829
- });
1781
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store);
1782
+ deps.store.writeActiveCloudTarget({ baseUrl });
1783
+ const saved = deps.store.readCloudCredential({ baseUrl });
1830
1784
  if (!saved) {
1831
1785
  if (args.json) {
1832
1786
  writeJsonLine(deps, {
1833
1787
  loggedIn: false,
1834
- baseUrl,
1835
- siteUrl
1788
+ baseUrl
1836
1789
  });
1837
1790
  } else {
1838
- writeHumanLine(
1839
- deps,
1840
- `Opensteer CLI is not logged in for ${siteUrl}.`
1841
- );
1791
+ writeHumanLine(deps, `Opensteer CLI is not logged in for ${baseUrl}.`);
1842
1792
  }
1843
1793
  return 0;
1844
1794
  }
@@ -1849,7 +1799,6 @@ async function runStatus(args, deps) {
1849
1799
  loggedIn: true,
1850
1800
  expired,
1851
1801
  baseUrl: saved.baseUrl,
1852
- siteUrl: saved.siteUrl,
1853
1802
  expiresAt: saved.expiresAt,
1854
1803
  scope: saved.scope
1855
1804
  });
@@ -1859,43 +1808,33 @@ async function runStatus(args, deps) {
1859
1808
  deps,
1860
1809
  expired ? "Opensteer CLI has a saved login, but the access token is expired." : "Opensteer CLI is logged in."
1861
1810
  );
1862
- writeHumanLine(deps, ` Site URL: ${saved.siteUrl}`);
1863
1811
  writeHumanLine(deps, ` API Base URL: ${saved.baseUrl}`);
1864
1812
  writeHumanLine(deps, ` Expires At: ${new Date(saved.expiresAt).toISOString()}`);
1865
1813
  return 0;
1866
1814
  }
1867
1815
  async function runLogout(args, deps) {
1868
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
1869
- deps.store.writeActiveCloudTarget({
1870
- baseUrl,
1871
- siteUrl
1872
- });
1873
- const saved = deps.store.readCloudCredential({
1874
- baseUrl,
1875
- siteUrl
1876
- });
1816
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store);
1817
+ deps.store.writeActiveCloudTarget({ baseUrl });
1818
+ const saved = deps.store.readCloudCredential({ baseUrl });
1877
1819
  if (saved) {
1878
1820
  try {
1879
- await revokeToken(saved.siteUrl, saved.refreshToken, deps.fetchFn);
1821
+ await revokeToken(
1822
+ resolveAuthSiteUrl(deps.env),
1823
+ saved.refreshToken,
1824
+ deps.fetchFn
1825
+ );
1880
1826
  } catch {
1881
1827
  }
1882
1828
  }
1883
- deps.store.clearCloudCredential({
1884
- baseUrl,
1885
- siteUrl
1886
- });
1829
+ deps.store.clearCloudCredential({ baseUrl });
1887
1830
  if (args.json) {
1888
1831
  writeJsonLine(deps, {
1889
1832
  loggedOut: true,
1890
- baseUrl,
1891
- siteUrl
1833
+ baseUrl
1892
1834
  });
1893
1835
  return 0;
1894
1836
  }
1895
- writeHumanLine(
1896
- deps,
1897
- `Opensteer CLI login removed for ${siteUrl}.`
1898
- );
1837
+ writeHumanLine(deps, `Opensteer CLI login removed for ${baseUrl}.`);
1899
1838
  return 0;
1900
1839
  }
1901
1840
  async function runOpensteerAuthCli(rawArgs, overrideDeps = {}) {