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.
@@ -4,67 +4,30 @@ import {
4
4
  normalizeCloudBaseUrl,
5
5
  resolveCloudSelection,
6
6
  resolveConfigWithEnv,
7
+ selectCloudCredential,
7
8
  stripTrailingSlashes
8
- } from "./chunk-WDRMHPWL.js";
9
+ } from "./chunk-KE35RQOJ.js";
9
10
 
10
11
  // src/cli/auth.ts
11
12
  import open from "open";
12
13
 
13
14
  // src/auth/credential-resolver.ts
14
15
  function resolveCloudCredential(options) {
15
- const flagApiKey = normalizeToken(options.apiKeyFlag);
16
- const flagAccessToken = normalizeToken(options.accessTokenFlag);
17
- if (flagApiKey && flagAccessToken) {
18
- throw new Error("--api-key and --access-token are mutually exclusive.");
19
- }
20
- if (flagAccessToken) {
21
- return {
22
- kind: "access-token",
23
- source: "flag",
24
- token: flagAccessToken,
25
- authScheme: "bearer"
26
- };
27
- }
28
- if (flagApiKey) {
29
- return {
30
- kind: "api-key",
31
- source: "flag",
32
- token: flagApiKey,
33
- authScheme: "api-key"
34
- };
16
+ const flagCredential = selectCloudCredential({
17
+ apiKey: options.apiKeyFlag,
18
+ accessToken: options.accessTokenFlag
19
+ });
20
+ if (flagCredential) {
21
+ return toResolvedCloudCredential("flag", flagCredential);
35
22
  }
36
23
  const envAuthScheme = parseEnvAuthScheme(options.env.OPENSTEER_AUTH_SCHEME);
37
- const envApiKey = normalizeToken(options.env.OPENSTEER_API_KEY);
38
- const envAccessToken = normalizeToken(options.env.OPENSTEER_ACCESS_TOKEN);
39
- if (envApiKey && envAccessToken) {
40
- throw new Error(
41
- "OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
42
- );
43
- }
44
- if (envAccessToken) {
45
- return {
46
- kind: "access-token",
47
- source: "env",
48
- token: envAccessToken,
49
- authScheme: "bearer"
50
- };
51
- }
52
- if (envApiKey) {
53
- if (envAuthScheme === "bearer") {
54
- return {
55
- kind: "access-token",
56
- source: "env",
57
- token: envApiKey,
58
- authScheme: "bearer",
59
- compatibilityBearerApiKey: true
60
- };
61
- }
62
- return {
63
- kind: "api-key",
64
- source: "env",
65
- token: envApiKey,
66
- authScheme: envAuthScheme ?? "api-key"
67
- };
24
+ const envCredential = selectCloudCredential({
25
+ apiKey: options.env.OPENSTEER_API_KEY,
26
+ accessToken: options.env.OPENSTEER_ACCESS_TOKEN,
27
+ authScheme: envAuthScheme
28
+ });
29
+ if (envCredential) {
30
+ return toResolvedCloudCredential("env", envCredential);
68
31
  }
69
32
  return null;
70
33
  }
@@ -94,19 +57,33 @@ function normalizeToken(value) {
94
57
  const normalized = value.trim();
95
58
  return normalized.length ? normalized : void 0;
96
59
  }
60
+ function toResolvedCloudCredential(source, credential) {
61
+ if (credential.compatibilityBearerApiKey) {
62
+ return {
63
+ kind: credential.kind,
64
+ source,
65
+ token: credential.token,
66
+ authScheme: credential.authScheme,
67
+ compatibilityBearerApiKey: true
68
+ };
69
+ }
70
+ return {
71
+ kind: credential.kind,
72
+ source,
73
+ token: credential.token,
74
+ authScheme: credential.authScheme
75
+ };
76
+ }
97
77
 
98
78
  // src/auth/machine-credential-store.ts
99
79
  import { createHash } from "crypto";
100
80
  import fs from "fs";
101
81
  import os from "os";
102
82
  import path from "path";
103
- var METADATA_VERSION = 1;
104
- var ACTIVE_TARGET_VERSION = 1;
83
+ var METADATA_VERSION = 2;
84
+ var ACTIVE_TARGET_VERSION = 2;
105
85
  var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
106
86
  var KEYCHAIN_ACCOUNT_PREFIX = "machine:";
107
- var LEGACY_KEYCHAIN_ACCOUNT = "machine";
108
- var LEGACY_METADATA_FILE_NAME = "cli-login.json";
109
- var LEGACY_FALLBACK_SECRET_FILE_NAME = "cli-login.secret.json";
110
87
  var ACTIVE_TARGET_FILE_NAME = "cli-target.json";
111
88
  var MachineCredentialStore = class {
112
89
  authDir;
@@ -121,8 +98,11 @@ var MachineCredentialStore = class {
121
98
  this.warn = options.warn ?? (() => void 0);
122
99
  }
123
100
  readCloudCredential(target) {
124
- const slot = resolveCredentialSlot(this.authDir, target);
125
- return this.readCredentialSlot(slot, target) ?? this.readAndMigrateLegacyCredential(target);
101
+ const normalizedTarget = normalizeCloudCredentialTarget(target);
102
+ return this.readCredentialSlot(
103
+ resolveCredentialSlot(this.authDir, normalizedTarget),
104
+ normalizedTarget
105
+ );
126
106
  }
127
107
  writeCloudCredential(args) {
128
108
  const accessToken = args.accessToken.trim();
@@ -130,12 +110,8 @@ var MachineCredentialStore = class {
130
110
  if (!accessToken || !refreshToken2) {
131
111
  throw new Error("Cannot persist empty machine credential secrets.");
132
112
  }
133
- const baseUrl = normalizeCredentialUrl(args.baseUrl, "baseUrl");
134
- const siteUrl = normalizeCredentialUrl(args.siteUrl, "siteUrl");
135
- const slot = resolveCredentialSlot(this.authDir, {
136
- baseUrl,
137
- siteUrl
138
- });
113
+ const baseUrl = normalizeCredentialUrl(args.baseUrl);
114
+ const slot = resolveCredentialSlot(this.authDir, { baseUrl });
139
115
  ensureDirectory(this.authDir);
140
116
  const secretPayload = {
141
117
  accessToken,
@@ -162,7 +138,6 @@ var MachineCredentialStore = class {
162
138
  version: METADATA_VERSION,
163
139
  secretBackend,
164
140
  baseUrl,
165
- siteUrl,
166
141
  scope: args.scope,
167
142
  obtainedAt: args.obtainedAt,
168
143
  expiresAt: args.expiresAt,
@@ -174,23 +149,18 @@ var MachineCredentialStore = class {
174
149
  return readActiveCloudTargetMetadata(resolveActiveTargetPath(this.authDir));
175
150
  }
176
151
  writeActiveCloudTarget(target) {
177
- const baseUrl = normalizeCredentialUrl(target.baseUrl, "baseUrl");
178
- const siteUrl = normalizeCredentialUrl(target.siteUrl, "siteUrl");
152
+ const baseUrl = normalizeCredentialUrl(target.baseUrl);
179
153
  ensureDirectory(this.authDir);
180
154
  writeJsonFile(resolveActiveTargetPath(this.authDir), {
181
155
  version: ACTIVE_TARGET_VERSION,
182
156
  baseUrl,
183
- siteUrl,
184
157
  updatedAt: Date.now()
185
158
  });
186
159
  }
187
160
  clearCloudCredential(target) {
188
- this.clearCredentialSlot(resolveCredentialSlot(this.authDir, target));
189
- const legacySlot = resolveLegacyCredentialSlot(this.authDir);
190
- const legacyMetadata = readMetadata(legacySlot.metadataPath);
191
- if (legacyMetadata && matchesCredentialTarget(legacyMetadata, target)) {
192
- this.clearCredentialSlot(legacySlot);
193
- }
161
+ this.clearCredentialSlot(
162
+ resolveCredentialSlot(this.authDir, normalizeCloudCredentialTarget(target))
163
+ );
194
164
  }
195
165
  readCredentialSlot(slot, target) {
196
166
  const metadata = readMetadata(slot.metadataPath);
@@ -206,7 +176,6 @@ var MachineCredentialStore = class {
206
176
  }
207
177
  return {
208
178
  baseUrl: metadata.baseUrl,
209
- siteUrl: metadata.siteUrl,
210
179
  scope: metadata.scope,
211
180
  accessToken: secret.accessToken,
212
181
  refreshToken: secret.refreshToken,
@@ -214,16 +183,6 @@ var MachineCredentialStore = class {
214
183
  expiresAt: metadata.expiresAt
215
184
  };
216
185
  }
217
- readAndMigrateLegacyCredential(target) {
218
- const legacySlot = resolveLegacyCredentialSlot(this.authDir);
219
- const legacyCredential = this.readCredentialSlot(legacySlot, target);
220
- if (!legacyCredential) {
221
- return null;
222
- }
223
- this.writeCloudCredential(legacyCredential);
224
- this.clearCredentialSlot(legacySlot);
225
- return legacyCredential;
226
- }
227
186
  readSecret(slot, backend) {
228
187
  if (backend === "keychain" && this.keychain) {
229
188
  try {
@@ -264,9 +223,8 @@ function createMachineCredentialStore(options = {}) {
264
223
  return new MachineCredentialStore(options);
265
224
  }
266
225
  function resolveCredentialSlot(authDir, target) {
267
- const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl, "baseUrl");
268
- const normalizedSiteUrl = normalizeCredentialUrl(target.siteUrl, "siteUrl");
269
- const storageKey = createHash("sha256").update(`${normalizedBaseUrl}\0${normalizedSiteUrl}`).digest("hex").slice(0, 24);
226
+ const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
227
+ const storageKey = createHash("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
270
228
  return {
271
229
  keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
272
230
  metadataPath: path.join(authDir, `cli-login.${storageKey}.json`),
@@ -276,26 +234,24 @@ function resolveCredentialSlot(authDir, target) {
276
234
  )
277
235
  };
278
236
  }
279
- function resolveLegacyCredentialSlot(authDir) {
280
- return {
281
- keychainAccount: LEGACY_KEYCHAIN_ACCOUNT,
282
- metadataPath: path.join(authDir, LEGACY_METADATA_FILE_NAME),
283
- fallbackSecretPath: path.join(authDir, LEGACY_FALLBACK_SECRET_FILE_NAME)
284
- };
285
- }
286
237
  function resolveActiveTargetPath(authDir) {
287
238
  return path.join(authDir, ACTIVE_TARGET_FILE_NAME);
288
239
  }
289
240
  function matchesCredentialTarget(value, target) {
290
- return normalizeCredentialUrl(value.baseUrl, "baseUrl") === normalizeCredentialUrl(target.baseUrl, "baseUrl") && normalizeCredentialUrl(value.siteUrl, "siteUrl") === normalizeCredentialUrl(target.siteUrl, "siteUrl");
241
+ return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
291
242
  }
292
- function normalizeCredentialUrl(value, field) {
243
+ function normalizeCredentialUrl(value) {
293
244
  const normalized = stripTrailingSlashes(value.trim());
294
245
  if (!normalized) {
295
- throw new Error(`Cannot persist machine credential without ${field}.`);
246
+ throw new Error("Cannot persist machine credential without baseUrl.");
296
247
  }
297
248
  return normalized;
298
249
  }
250
+ function normalizeCloudCredentialTarget(target) {
251
+ return {
252
+ baseUrl: normalizeCredentialUrl(target.baseUrl)
253
+ };
254
+ }
299
255
  function resolveConfigDir(appName, env) {
300
256
  if (process.platform === "win32") {
301
257
  const appData = env.APPDATA?.trim() || path.join(os.homedir(), "AppData", "Roaming");
@@ -334,7 +290,6 @@ function readMetadata(filePath) {
334
290
  return null;
335
291
  }
336
292
  if (typeof parsed.baseUrl !== "string" || !parsed.baseUrl.trim()) return null;
337
- if (typeof parsed.siteUrl !== "string" || !parsed.siteUrl.trim()) return null;
338
293
  if (!Array.isArray(parsed.scope)) return null;
339
294
  if (typeof parsed.obtainedAt !== "number") return null;
340
295
  if (typeof parsed.expiresAt !== "number") return null;
@@ -343,7 +298,6 @@ function readMetadata(filePath) {
343
298
  version: parsed.version,
344
299
  secretBackend: parsed.secretBackend,
345
300
  baseUrl: parsed.baseUrl,
346
- siteUrl: parsed.siteUrl,
347
301
  scope: parsed.scope.filter(
348
302
  (value) => typeof value === "string"
349
303
  ),
@@ -368,12 +322,8 @@ function readActiveCloudTargetMetadata(filePath) {
368
322
  if (typeof parsed.baseUrl !== "string" || !parsed.baseUrl.trim()) {
369
323
  return null;
370
324
  }
371
- if (typeof parsed.siteUrl !== "string" || !parsed.siteUrl.trim()) {
372
- return null;
373
- }
374
325
  return {
375
- baseUrl: parsed.baseUrl,
376
- siteUrl: parsed.siteUrl
326
+ baseUrl: parsed.baseUrl
377
327
  };
378
328
  } catch {
379
329
  return null;
@@ -398,14 +348,13 @@ function readSecretFile(filePath) {
398
348
  return null;
399
349
  }
400
350
  try {
401
- const raw = fs.readFileSync(filePath, "utf8");
402
- return parseSecretPayload(raw);
351
+ return parseSecretPayload(fs.readFileSync(filePath, "utf8"));
403
352
  } catch {
404
353
  return null;
405
354
  }
406
355
  }
407
- function writeJsonFile(filePath, payload, options = {}) {
408
- fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), {
356
+ function writeJsonFile(filePath, value, options = {}) {
357
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2), {
409
358
  encoding: "utf8",
410
359
  mode: options.mode ?? 384
411
360
  });
@@ -436,11 +385,12 @@ Commands:
436
385
 
437
386
  Options:
438
387
  --base-url <url> Cloud API base URL (defaults to env or the last selected host)
439
- --site-url <url> Cloud site URL for browser/device auth (defaults to env or the last selected host)
440
388
  --json JSON output (login prompts go to stderr)
441
389
  --no-browser Do not auto-open your default browser during login
442
390
  -h, --help Show this help
443
391
  `;
392
+ var DEFAULT_AUTH_SITE_URL = "https://opensteer.com";
393
+ var INTERNAL_AUTH_SITE_URL_ENV = "OPENSTEER_INTERNAL_AUTH_SITE_URL";
444
394
  function createDefaultDeps() {
445
395
  const env = process.env;
446
396
  return {
@@ -492,13 +442,6 @@ function parseAuthCommonArgs(rawArgs) {
492
442
  i = value.nextIndex;
493
443
  continue;
494
444
  }
495
- if (arg === "--site-url") {
496
- const value = readFlagValue(rawArgs, i, "--site-url");
497
- if (!value.ok) return { args, error: value.error };
498
- args.siteUrl = value.value;
499
- i = value.nextIndex;
500
- continue;
501
- }
502
445
  return {
503
446
  args,
504
447
  error: `Unsupported option "${arg}".`
@@ -568,16 +511,19 @@ function resolveBaseUrl(provided, env) {
568
511
  assertSecureUrl(baseUrl, "--base-url");
569
512
  return baseUrl;
570
513
  }
571
- function resolveSiteUrl(provided, baseUrl, env) {
572
- const siteUrl = normalizeCloudBaseUrl(
573
- (provided || env.OPENSTEER_CLOUD_SITE_URL || deriveSiteUrlFromBaseUrl(baseUrl)).trim()
514
+ function resolveAuthSiteUrl(env) {
515
+ const authSiteUrl = normalizeCloudBaseUrl(
516
+ (env[INTERNAL_AUTH_SITE_URL_ENV] || DEFAULT_AUTH_SITE_URL).trim()
574
517
  );
575
- assertSecureUrl(siteUrl, "--site-url");
576
- return siteUrl;
518
+ assertSecureUrl(
519
+ authSiteUrl,
520
+ `environment variable ${INTERNAL_AUTH_SITE_URL_ENV}`
521
+ );
522
+ return authSiteUrl;
577
523
  }
578
- function hasExplicitCloudTargetSelection(providedBaseUrl, providedSiteUrl, env) {
524
+ function hasExplicitCloudTargetSelection(providedBaseUrl, env) {
579
525
  return Boolean(
580
- providedBaseUrl?.trim() || providedSiteUrl?.trim() || env.OPENSTEER_BASE_URL?.trim() || env.OPENSTEER_CLOUD_SITE_URL?.trim()
526
+ providedBaseUrl?.trim() || env.OPENSTEER_BASE_URL?.trim()
581
527
  );
582
528
  }
583
529
  function readRememberedCloudTarget(store) {
@@ -587,57 +533,21 @@ function readRememberedCloudTarget(store) {
587
533
  }
588
534
  try {
589
535
  const baseUrl = normalizeCloudBaseUrl(activeTarget.baseUrl);
590
- const siteUrl = normalizeCloudBaseUrl(activeTarget.siteUrl);
591
536
  assertSecureUrl(baseUrl, "--base-url");
592
- assertSecureUrl(siteUrl, "--site-url");
593
- return {
594
- baseUrl,
595
- siteUrl
596
- };
537
+ return { baseUrl };
597
538
  } catch {
598
539
  return null;
599
540
  }
600
541
  }
601
- function resolveCloudTarget(args, env, store) {
602
- if (!hasExplicitCloudTargetSelection(args.baseUrl, args.siteUrl, env)) {
542
+ function resolveCloudTarget(args, env, store, options = {}) {
543
+ if (options.allowRememberedTarget !== false && !hasExplicitCloudTargetSelection(args.baseUrl, env)) {
603
544
  const rememberedTarget = readRememberedCloudTarget(store);
604
545
  if (rememberedTarget) {
605
546
  return rememberedTarget;
606
547
  }
607
548
  }
608
549
  const baseUrl = resolveBaseUrl(args.baseUrl, env);
609
- const siteUrl = resolveSiteUrl(args.siteUrl, baseUrl, env);
610
- return {
611
- baseUrl,
612
- siteUrl
613
- };
614
- }
615
- function deriveSiteUrlFromBaseUrl(baseUrl) {
616
- let parsed;
617
- try {
618
- parsed = new URL(baseUrl);
619
- } catch {
620
- return "https://opensteer.com";
621
- }
622
- const hostname = parsed.hostname.toLowerCase();
623
- if (hostname.startsWith("api.")) {
624
- parsed.hostname = hostname.slice("api.".length);
625
- parsed.pathname = "";
626
- parsed.search = "";
627
- parsed.hash = "";
628
- return normalizeCloudBaseUrl(parsed.toString());
629
- }
630
- if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
631
- parsed.port = "3001";
632
- parsed.pathname = "";
633
- parsed.search = "";
634
- parsed.hash = "";
635
- return normalizeCloudBaseUrl(parsed.toString());
636
- }
637
- parsed.pathname = "";
638
- parsed.search = "";
639
- parsed.hash = "";
640
- return normalizeCloudBaseUrl(parsed.toString());
550
+ return { baseUrl };
641
551
  }
642
552
  function assertSecureUrl(value, flag) {
643
553
  let parsed;
@@ -712,10 +622,10 @@ function parseCliOauthError(error) {
712
622
  interval: typeof root.interval === "number" ? root.interval : void 0
713
623
  };
714
624
  }
715
- async function startDeviceAuthorization(siteUrl, fetchFn) {
625
+ async function startDeviceAuthorization(authSiteUrl, fetchFn) {
716
626
  const response = await postJson(
717
627
  fetchFn,
718
- `${siteUrl}/api/cli-auth/device/start`,
628
+ `${authSiteUrl}/api/cli-auth/device/start`,
719
629
  {
720
630
  scope: ["cloud:browser"]
721
631
  }
@@ -725,24 +635,24 @@ async function startDeviceAuthorization(siteUrl, fetchFn) {
725
635
  }
726
636
  return response;
727
637
  }
728
- async function pollDeviceToken(siteUrl, deviceCode, fetchFn) {
638
+ async function pollDeviceToken(authSiteUrl, deviceCode, fetchFn) {
729
639
  return await postJson(
730
640
  fetchFn,
731
- `${siteUrl}/api/cli-auth/device/token`,
641
+ `${authSiteUrl}/api/cli-auth/device/token`,
732
642
  {
733
643
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
734
644
  device_code: deviceCode
735
645
  }
736
646
  );
737
647
  }
738
- async function refreshToken(siteUrl, refreshTokenValue, fetchFn) {
739
- return await postJson(fetchFn, `${siteUrl}/api/cli-auth/token`, {
648
+ async function refreshToken(authSiteUrl, refreshTokenValue, fetchFn) {
649
+ return await postJson(fetchFn, `${authSiteUrl}/api/cli-auth/token`, {
740
650
  grant_type: "refresh_token",
741
651
  refresh_token: refreshTokenValue
742
652
  });
743
653
  }
744
- async function revokeToken(siteUrl, refreshTokenValue, fetchFn) {
745
- await postJson(fetchFn, `${siteUrl}/api/cli-auth/revoke`, {
654
+ async function revokeToken(authSiteUrl, refreshTokenValue, fetchFn) {
655
+ await postJson(fetchFn, `${authSiteUrl}/api/cli-auth/revoke`, {
746
656
  token: refreshTokenValue
747
657
  });
748
658
  }
@@ -759,7 +669,7 @@ async function openDefaultBrowser(url) {
759
669
  }
760
670
  }
761
671
  async function runDeviceLoginFlow(args) {
762
- const start = await startDeviceAuthorization(args.siteUrl, args.fetchFn);
672
+ const start = await startDeviceAuthorization(args.authSiteUrl, args.fetchFn);
763
673
  if (args.openBrowser) {
764
674
  args.writeProgress(
765
675
  "Opening your default browser for Opensteer CLI authentication.\n"
@@ -804,7 +714,7 @@ ${start.verification_uri_complete}
804
714
  await args.sleep(pollIntervalMs);
805
715
  try {
806
716
  const tokenPayload = await pollDeviceToken(
807
- args.siteUrl,
717
+ args.authSiteUrl,
808
718
  start.device_code,
809
719
  args.fetchFn
810
720
  );
@@ -852,7 +762,7 @@ ${start.verification_uri_complete}
852
762
  }
853
763
  async function refreshSavedCredential(saved, deps) {
854
764
  const tokenPayload = await refreshToken(
855
- saved.siteUrl,
765
+ resolveAuthSiteUrl(deps.env),
856
766
  saved.refreshToken,
857
767
  deps.fetchFn
858
768
  );
@@ -865,7 +775,6 @@ async function refreshSavedCredential(saved, deps) {
865
775
  };
866
776
  deps.store.writeCloudCredential({
867
777
  baseUrl: saved.baseUrl,
868
- siteUrl: saved.siteUrl,
869
778
  scope: updated.scope,
870
779
  accessToken: updated.accessToken,
871
780
  refreshToken: updated.refreshToken,
@@ -894,8 +803,7 @@ async function ensureSavedCredentialIsFresh(saved, deps) {
894
803
  const oauth = parseCliOauthError(error.body);
895
804
  if (oauth?.error === "invalid_grant" || oauth?.error === "expired_token") {
896
805
  deps.store.clearCloudCredential({
897
- baseUrl: saved.baseUrl,
898
- siteUrl: saved.siteUrl
806
+ baseUrl: saved.baseUrl
899
807
  });
900
808
  return null;
901
809
  }
@@ -995,7 +903,7 @@ async function ensureCloudCredentialsForCommand(options) {
995
903
  `);
996
904
  }
997
905
  });
998
- const { baseUrl, siteUrl } = resolveCloudTarget(options, env, store);
906
+ const { baseUrl } = resolveCloudTarget(options, env, store);
999
907
  const initialCredential = resolveCloudCredential({
1000
908
  env,
1001
909
  apiKeyFlag: options.apiKeyFlag,
@@ -1003,11 +911,9 @@ async function ensureCloudCredentialsForCommand(options) {
1003
911
  });
1004
912
  let credential = initialCredential;
1005
913
  if (!credential) {
1006
- const saved = store.readCloudCredential({
1007
- baseUrl,
1008
- siteUrl
1009
- });
914
+ const saved = store.readCloudCredential({ baseUrl });
1010
915
  const freshSaved = saved ? await ensureSavedCredentialIsFresh(saved, {
916
+ env,
1011
917
  fetchFn,
1012
918
  store,
1013
919
  now,
@@ -1025,7 +931,7 @@ async function ensureCloudCredentialsForCommand(options) {
1025
931
  if (!credential) {
1026
932
  if (options.autoLoginIfNeeded && (options.interactive ?? false)) {
1027
933
  const loggedIn = await runDeviceLoginFlow({
1028
- siteUrl,
934
+ authSiteUrl: resolveAuthSiteUrl(env),
1029
935
  fetchFn,
1030
936
  writeProgress,
1031
937
  openExternalUrl,
@@ -1035,7 +941,6 @@ async function ensureCloudCredentialsForCommand(options) {
1035
941
  });
1036
942
  store.writeCloudCredential({
1037
943
  baseUrl,
1038
- siteUrl,
1039
944
  scope: loggedIn.scope,
1040
945
  accessToken: loggedIn.accessToken,
1041
946
  refreshToken: loggedIn.refreshToken,
@@ -1053,28 +958,25 @@ async function ensureCloudCredentialsForCommand(options) {
1053
958
  throw new Error(toAuthMissingMessage(options.commandName));
1054
959
  }
1055
960
  }
1056
- store.writeActiveCloudTarget({
1057
- baseUrl,
1058
- siteUrl
1059
- });
961
+ store.writeActiveCloudTarget({ baseUrl });
1060
962
  applyCloudCredentialToEnv(env, credential);
1061
963
  env.OPENSTEER_BASE_URL = baseUrl;
1062
- env.OPENSTEER_CLOUD_SITE_URL = siteUrl;
1063
964
  return {
1064
965
  token: credential.token,
1065
966
  authScheme: credential.authScheme,
1066
967
  source: credential.source,
1067
968
  kind: credential.kind,
1068
- baseUrl,
1069
- siteUrl
969
+ baseUrl
1070
970
  };
1071
971
  }
1072
972
  async function runLogin(args, deps) {
1073
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
973
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store, {
974
+ allowRememberedTarget: false
975
+ });
1074
976
  const writeProgress = args.json ? deps.writeStderr : deps.writeStdout;
1075
977
  const browserOpenMode = describeBrowserOpenMode(args, deps);
1076
978
  const login = await runDeviceLoginFlow({
1077
- siteUrl,
979
+ authSiteUrl: resolveAuthSiteUrl(deps.env),
1078
980
  fetchFn: deps.fetchFn,
1079
981
  writeProgress,
1080
982
  openExternalUrl: deps.openExternalUrl,
@@ -1085,22 +987,17 @@ async function runLogin(args, deps) {
1085
987
  });
1086
988
  deps.store.writeCloudCredential({
1087
989
  baseUrl,
1088
- siteUrl,
1089
990
  scope: login.scope,
1090
991
  accessToken: login.accessToken,
1091
992
  refreshToken: login.refreshToken,
1092
993
  obtainedAt: deps.now(),
1093
994
  expiresAt: login.expiresAt
1094
995
  });
1095
- deps.store.writeActiveCloudTarget({
1096
- baseUrl,
1097
- siteUrl
1098
- });
996
+ deps.store.writeActiveCloudTarget({ baseUrl });
1099
997
  if (args.json) {
1100
998
  writeJsonLine(deps, {
1101
999
  loggedIn: true,
1102
1000
  baseUrl,
1103
- siteUrl,
1104
1001
  expiresAt: login.expiresAt,
1105
1002
  scope: login.scope,
1106
1003
  authSource: "device"
@@ -1108,33 +1005,20 @@ async function runLogin(args, deps) {
1108
1005
  return 0;
1109
1006
  }
1110
1007
  writeHumanLine(deps, "Opensteer CLI login successful.");
1111
- writeHumanLine(deps, ` Site URL: ${siteUrl}`);
1112
- writeHumanLine(deps, ` API Base URL: ${baseUrl}`);
1113
- writeHumanLine(deps, ` Expires At: ${new Date(login.expiresAt).toISOString()}`);
1114
1008
  return 0;
1115
1009
  }
1116
1010
  async function runStatus(args, deps) {
1117
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
1118
- deps.store.writeActiveCloudTarget({
1119
- baseUrl,
1120
- siteUrl
1121
- });
1122
- const saved = deps.store.readCloudCredential({
1123
- baseUrl,
1124
- siteUrl
1125
- });
1011
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store);
1012
+ deps.store.writeActiveCloudTarget({ baseUrl });
1013
+ const saved = deps.store.readCloudCredential({ baseUrl });
1126
1014
  if (!saved) {
1127
1015
  if (args.json) {
1128
1016
  writeJsonLine(deps, {
1129
1017
  loggedIn: false,
1130
- baseUrl,
1131
- siteUrl
1018
+ baseUrl
1132
1019
  });
1133
1020
  } else {
1134
- writeHumanLine(
1135
- deps,
1136
- `Opensteer CLI is not logged in for ${siteUrl}.`
1137
- );
1021
+ writeHumanLine(deps, `Opensteer CLI is not logged in for ${baseUrl}.`);
1138
1022
  }
1139
1023
  return 0;
1140
1024
  }
@@ -1145,7 +1029,6 @@ async function runStatus(args, deps) {
1145
1029
  loggedIn: true,
1146
1030
  expired,
1147
1031
  baseUrl: saved.baseUrl,
1148
- siteUrl: saved.siteUrl,
1149
1032
  expiresAt: saved.expiresAt,
1150
1033
  scope: saved.scope
1151
1034
  });
@@ -1155,43 +1038,33 @@ async function runStatus(args, deps) {
1155
1038
  deps,
1156
1039
  expired ? "Opensteer CLI has a saved login, but the access token is expired." : "Opensteer CLI is logged in."
1157
1040
  );
1158
- writeHumanLine(deps, ` Site URL: ${saved.siteUrl}`);
1159
1041
  writeHumanLine(deps, ` API Base URL: ${saved.baseUrl}`);
1160
1042
  writeHumanLine(deps, ` Expires At: ${new Date(saved.expiresAt).toISOString()}`);
1161
1043
  return 0;
1162
1044
  }
1163
1045
  async function runLogout(args, deps) {
1164
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
1165
- deps.store.writeActiveCloudTarget({
1166
- baseUrl,
1167
- siteUrl
1168
- });
1169
- const saved = deps.store.readCloudCredential({
1170
- baseUrl,
1171
- siteUrl
1172
- });
1046
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store);
1047
+ deps.store.writeActiveCloudTarget({ baseUrl });
1048
+ const saved = deps.store.readCloudCredential({ baseUrl });
1173
1049
  if (saved) {
1174
1050
  try {
1175
- await revokeToken(saved.siteUrl, saved.refreshToken, deps.fetchFn);
1051
+ await revokeToken(
1052
+ resolveAuthSiteUrl(deps.env),
1053
+ saved.refreshToken,
1054
+ deps.fetchFn
1055
+ );
1176
1056
  } catch {
1177
1057
  }
1178
1058
  }
1179
- deps.store.clearCloudCredential({
1180
- baseUrl,
1181
- siteUrl
1182
- });
1059
+ deps.store.clearCloudCredential({ baseUrl });
1183
1060
  if (args.json) {
1184
1061
  writeJsonLine(deps, {
1185
1062
  loggedOut: true,
1186
- baseUrl,
1187
- siteUrl
1063
+ baseUrl
1188
1064
  });
1189
1065
  return 0;
1190
1066
  }
1191
- writeHumanLine(
1192
- deps,
1193
- `Opensteer CLI login removed for ${siteUrl}.`
1194
- );
1067
+ writeHumanLine(deps, `Opensteer CLI login removed for ${baseUrl}.`);
1195
1068
  return 0;
1196
1069
  }
1197
1070
  async function runOpensteerAuthCli(rawArgs, overrideDeps = {}) {