opensteer 0.6.1 → 0.6.2

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/README.md CHANGED
@@ -182,11 +182,10 @@ opensteer auth logout
182
182
  `--no-browser` on remote shells, containers, or CI and paste the printed URL
183
183
  into a browser manually. In `--json` mode, login prompts go to stderr and the
184
184
  final JSON result stays on stdout.
185
- - Saved machine logins remain scoped per resolved cloud host (`baseUrl` +
186
- `siteUrl`). The CLI also remembers the last selected cloud host, so
187
- `opensteer auth status`, `opensteer auth logout`, and other cloud commands
188
- reuse it by default unless `--base-url`, `--site-url`, or env vars select a
189
- different host.
185
+ - Saved machine logins remain scoped per resolved cloud API host (`baseUrl`).
186
+ The CLI also remembers the last selected cloud host, so `opensteer auth
187
+ status`, `opensteer auth logout`, and other cloud commands reuse it by
188
+ default unless `--base-url` or env vars select a different host.
190
189
 
191
190
  - `OPENSTEER_BASE_URL` overrides the default cloud host
192
191
  - `OPENSTEER_ACCESS_TOKEN` provides bearer auth for cloud commands
package/bin/opensteer.mjs CHANGED
@@ -1149,7 +1149,6 @@ Environment:
1149
1149
  OPENSTEER_API_KEY Cloud API key credential
1150
1150
  OPENSTEER_ACCESS_TOKEN Cloud bearer access token credential
1151
1151
  OPENSTEER_BASE_URL Override cloud control-plane base URL
1152
- OPENSTEER_CLOUD_SITE_URL Override cloud site URL for device login endpoints
1153
1152
  OPENSTEER_AUTH_SCHEME Cloud auth scheme: api-key (default) or bearer
1154
1153
  OPENSTEER_REMOTE_ANNOUNCE Cloud session announcement policy: always (default), off, tty
1155
1154
  `)
@@ -100,13 +100,10 @@ import { createHash } from "crypto";
100
100
  import fs from "fs";
101
101
  import os from "os";
102
102
  import path from "path";
103
- var METADATA_VERSION = 1;
104
- var ACTIVE_TARGET_VERSION = 1;
103
+ var METADATA_VERSION = 2;
104
+ var ACTIVE_TARGET_VERSION = 2;
105
105
  var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
106
106
  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
107
  var ACTIVE_TARGET_FILE_NAME = "cli-target.json";
111
108
  var MachineCredentialStore = class {
112
109
  authDir;
@@ -121,8 +118,11 @@ var MachineCredentialStore = class {
121
118
  this.warn = options.warn ?? (() => void 0);
122
119
  }
123
120
  readCloudCredential(target) {
124
- const slot = resolveCredentialSlot(this.authDir, target);
125
- return this.readCredentialSlot(slot, target) ?? this.readAndMigrateLegacyCredential(target);
121
+ const normalizedTarget = normalizeCloudCredentialTarget(target);
122
+ return this.readCredentialSlot(
123
+ resolveCredentialSlot(this.authDir, normalizedTarget),
124
+ normalizedTarget
125
+ );
126
126
  }
127
127
  writeCloudCredential(args) {
128
128
  const accessToken = args.accessToken.trim();
@@ -130,12 +130,8 @@ var MachineCredentialStore = class {
130
130
  if (!accessToken || !refreshToken2) {
131
131
  throw new Error("Cannot persist empty machine credential secrets.");
132
132
  }
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
- });
133
+ const baseUrl = normalizeCredentialUrl(args.baseUrl);
134
+ const slot = resolveCredentialSlot(this.authDir, { baseUrl });
139
135
  ensureDirectory(this.authDir);
140
136
  const secretPayload = {
141
137
  accessToken,
@@ -162,7 +158,6 @@ var MachineCredentialStore = class {
162
158
  version: METADATA_VERSION,
163
159
  secretBackend,
164
160
  baseUrl,
165
- siteUrl,
166
161
  scope: args.scope,
167
162
  obtainedAt: args.obtainedAt,
168
163
  expiresAt: args.expiresAt,
@@ -174,23 +169,18 @@ var MachineCredentialStore = class {
174
169
  return readActiveCloudTargetMetadata(resolveActiveTargetPath(this.authDir));
175
170
  }
176
171
  writeActiveCloudTarget(target) {
177
- const baseUrl = normalizeCredentialUrl(target.baseUrl, "baseUrl");
178
- const siteUrl = normalizeCredentialUrl(target.siteUrl, "siteUrl");
172
+ const baseUrl = normalizeCredentialUrl(target.baseUrl);
179
173
  ensureDirectory(this.authDir);
180
174
  writeJsonFile(resolveActiveTargetPath(this.authDir), {
181
175
  version: ACTIVE_TARGET_VERSION,
182
176
  baseUrl,
183
- siteUrl,
184
177
  updatedAt: Date.now()
185
178
  });
186
179
  }
187
180
  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
- }
181
+ this.clearCredentialSlot(
182
+ resolveCredentialSlot(this.authDir, normalizeCloudCredentialTarget(target))
183
+ );
194
184
  }
195
185
  readCredentialSlot(slot, target) {
196
186
  const metadata = readMetadata(slot.metadataPath);
@@ -206,7 +196,6 @@ var MachineCredentialStore = class {
206
196
  }
207
197
  return {
208
198
  baseUrl: metadata.baseUrl,
209
- siteUrl: metadata.siteUrl,
210
199
  scope: metadata.scope,
211
200
  accessToken: secret.accessToken,
212
201
  refreshToken: secret.refreshToken,
@@ -214,16 +203,6 @@ var MachineCredentialStore = class {
214
203
  expiresAt: metadata.expiresAt
215
204
  };
216
205
  }
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
206
  readSecret(slot, backend) {
228
207
  if (backend === "keychain" && this.keychain) {
229
208
  try {
@@ -264,9 +243,8 @@ function createMachineCredentialStore(options = {}) {
264
243
  return new MachineCredentialStore(options);
265
244
  }
266
245
  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);
246
+ const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
247
+ const storageKey = createHash("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
270
248
  return {
271
249
  keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
272
250
  metadataPath: path.join(authDir, `cli-login.${storageKey}.json`),
@@ -276,26 +254,24 @@ function resolveCredentialSlot(authDir, target) {
276
254
  )
277
255
  };
278
256
  }
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
257
  function resolveActiveTargetPath(authDir) {
287
258
  return path.join(authDir, ACTIVE_TARGET_FILE_NAME);
288
259
  }
289
260
  function matchesCredentialTarget(value, target) {
290
- return normalizeCredentialUrl(value.baseUrl, "baseUrl") === normalizeCredentialUrl(target.baseUrl, "baseUrl") && normalizeCredentialUrl(value.siteUrl, "siteUrl") === normalizeCredentialUrl(target.siteUrl, "siteUrl");
261
+ return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
291
262
  }
292
- function normalizeCredentialUrl(value, field) {
263
+ function normalizeCredentialUrl(value) {
293
264
  const normalized = stripTrailingSlashes(value.trim());
294
265
  if (!normalized) {
295
- throw new Error(`Cannot persist machine credential without ${field}.`);
266
+ throw new Error("Cannot persist machine credential without baseUrl.");
296
267
  }
297
268
  return normalized;
298
269
  }
270
+ function normalizeCloudCredentialTarget(target) {
271
+ return {
272
+ baseUrl: normalizeCredentialUrl(target.baseUrl)
273
+ };
274
+ }
299
275
  function resolveConfigDir(appName, env) {
300
276
  if (process.platform === "win32") {
301
277
  const appData = env.APPDATA?.trim() || path.join(os.homedir(), "AppData", "Roaming");
@@ -334,7 +310,6 @@ function readMetadata(filePath) {
334
310
  return null;
335
311
  }
336
312
  if (typeof parsed.baseUrl !== "string" || !parsed.baseUrl.trim()) return null;
337
- if (typeof parsed.siteUrl !== "string" || !parsed.siteUrl.trim()) return null;
338
313
  if (!Array.isArray(parsed.scope)) return null;
339
314
  if (typeof parsed.obtainedAt !== "number") return null;
340
315
  if (typeof parsed.expiresAt !== "number") return null;
@@ -343,7 +318,6 @@ function readMetadata(filePath) {
343
318
  version: parsed.version,
344
319
  secretBackend: parsed.secretBackend,
345
320
  baseUrl: parsed.baseUrl,
346
- siteUrl: parsed.siteUrl,
347
321
  scope: parsed.scope.filter(
348
322
  (value) => typeof value === "string"
349
323
  ),
@@ -368,12 +342,8 @@ function readActiveCloudTargetMetadata(filePath) {
368
342
  if (typeof parsed.baseUrl !== "string" || !parsed.baseUrl.trim()) {
369
343
  return null;
370
344
  }
371
- if (typeof parsed.siteUrl !== "string" || !parsed.siteUrl.trim()) {
372
- return null;
373
- }
374
345
  return {
375
- baseUrl: parsed.baseUrl,
376
- siteUrl: parsed.siteUrl
346
+ baseUrl: parsed.baseUrl
377
347
  };
378
348
  } catch {
379
349
  return null;
@@ -398,14 +368,13 @@ function readSecretFile(filePath) {
398
368
  return null;
399
369
  }
400
370
  try {
401
- const raw = fs.readFileSync(filePath, "utf8");
402
- return parseSecretPayload(raw);
371
+ return parseSecretPayload(fs.readFileSync(filePath, "utf8"));
403
372
  } catch {
404
373
  return null;
405
374
  }
406
375
  }
407
- function writeJsonFile(filePath, payload, options = {}) {
408
- fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), {
376
+ function writeJsonFile(filePath, value, options = {}) {
377
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2), {
409
378
  encoding: "utf8",
410
379
  mode: options.mode ?? 384
411
380
  });
@@ -436,11 +405,12 @@ Commands:
436
405
 
437
406
  Options:
438
407
  --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
408
  --json JSON output (login prompts go to stderr)
441
409
  --no-browser Do not auto-open your default browser during login
442
410
  -h, --help Show this help
443
411
  `;
412
+ var DEFAULT_AUTH_SITE_URL = "https://opensteer.com";
413
+ var INTERNAL_AUTH_SITE_URL_ENV = "OPENSTEER_INTERNAL_AUTH_SITE_URL";
444
414
  function createDefaultDeps() {
445
415
  const env = process.env;
446
416
  return {
@@ -492,13 +462,6 @@ function parseAuthCommonArgs(rawArgs) {
492
462
  i = value.nextIndex;
493
463
  continue;
494
464
  }
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
465
  return {
503
466
  args,
504
467
  error: `Unsupported option "${arg}".`
@@ -568,16 +531,19 @@ function resolveBaseUrl(provided, env) {
568
531
  assertSecureUrl(baseUrl, "--base-url");
569
532
  return baseUrl;
570
533
  }
571
- function resolveSiteUrl(provided, baseUrl, env) {
572
- const siteUrl = normalizeCloudBaseUrl(
573
- (provided || env.OPENSTEER_CLOUD_SITE_URL || deriveSiteUrlFromBaseUrl(baseUrl)).trim()
534
+ function resolveAuthSiteUrl(env) {
535
+ const authSiteUrl = normalizeCloudBaseUrl(
536
+ (env[INTERNAL_AUTH_SITE_URL_ENV] || DEFAULT_AUTH_SITE_URL).trim()
537
+ );
538
+ assertSecureUrl(
539
+ authSiteUrl,
540
+ `environment variable ${INTERNAL_AUTH_SITE_URL_ENV}`
574
541
  );
575
- assertSecureUrl(siteUrl, "--site-url");
576
- return siteUrl;
542
+ return authSiteUrl;
577
543
  }
578
- function hasExplicitCloudTargetSelection(providedBaseUrl, providedSiteUrl, env) {
544
+ function hasExplicitCloudTargetSelection(providedBaseUrl, env) {
579
545
  return Boolean(
580
- providedBaseUrl?.trim() || providedSiteUrl?.trim() || env.OPENSTEER_BASE_URL?.trim() || env.OPENSTEER_CLOUD_SITE_URL?.trim()
546
+ providedBaseUrl?.trim() || env.OPENSTEER_BASE_URL?.trim()
581
547
  );
582
548
  }
583
549
  function readRememberedCloudTarget(store) {
@@ -587,57 +553,21 @@ function readRememberedCloudTarget(store) {
587
553
  }
588
554
  try {
589
555
  const baseUrl = normalizeCloudBaseUrl(activeTarget.baseUrl);
590
- const siteUrl = normalizeCloudBaseUrl(activeTarget.siteUrl);
591
556
  assertSecureUrl(baseUrl, "--base-url");
592
- assertSecureUrl(siteUrl, "--site-url");
593
- return {
594
- baseUrl,
595
- siteUrl
596
- };
557
+ return { baseUrl };
597
558
  } catch {
598
559
  return null;
599
560
  }
600
561
  }
601
- function resolveCloudTarget(args, env, store) {
602
- if (!hasExplicitCloudTargetSelection(args.baseUrl, args.siteUrl, env)) {
562
+ function resolveCloudTarget(args, env, store, options = {}) {
563
+ if (options.allowRememberedTarget !== false && !hasExplicitCloudTargetSelection(args.baseUrl, env)) {
603
564
  const rememberedTarget = readRememberedCloudTarget(store);
604
565
  if (rememberedTarget) {
605
566
  return rememberedTarget;
606
567
  }
607
568
  }
608
569
  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());
570
+ return { baseUrl };
641
571
  }
642
572
  function assertSecureUrl(value, flag) {
643
573
  let parsed;
@@ -712,10 +642,10 @@ function parseCliOauthError(error) {
712
642
  interval: typeof root.interval === "number" ? root.interval : void 0
713
643
  };
714
644
  }
715
- async function startDeviceAuthorization(siteUrl, fetchFn) {
645
+ async function startDeviceAuthorization(authSiteUrl, fetchFn) {
716
646
  const response = await postJson(
717
647
  fetchFn,
718
- `${siteUrl}/api/cli-auth/device/start`,
648
+ `${authSiteUrl}/api/cli-auth/device/start`,
719
649
  {
720
650
  scope: ["cloud:browser"]
721
651
  }
@@ -725,24 +655,24 @@ async function startDeviceAuthorization(siteUrl, fetchFn) {
725
655
  }
726
656
  return response;
727
657
  }
728
- async function pollDeviceToken(siteUrl, deviceCode, fetchFn) {
658
+ async function pollDeviceToken(authSiteUrl, deviceCode, fetchFn) {
729
659
  return await postJson(
730
660
  fetchFn,
731
- `${siteUrl}/api/cli-auth/device/token`,
661
+ `${authSiteUrl}/api/cli-auth/device/token`,
732
662
  {
733
663
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
734
664
  device_code: deviceCode
735
665
  }
736
666
  );
737
667
  }
738
- async function refreshToken(siteUrl, refreshTokenValue, fetchFn) {
739
- return await postJson(fetchFn, `${siteUrl}/api/cli-auth/token`, {
668
+ async function refreshToken(authSiteUrl, refreshTokenValue, fetchFn) {
669
+ return await postJson(fetchFn, `${authSiteUrl}/api/cli-auth/token`, {
740
670
  grant_type: "refresh_token",
741
671
  refresh_token: refreshTokenValue
742
672
  });
743
673
  }
744
- async function revokeToken(siteUrl, refreshTokenValue, fetchFn) {
745
- await postJson(fetchFn, `${siteUrl}/api/cli-auth/revoke`, {
674
+ async function revokeToken(authSiteUrl, refreshTokenValue, fetchFn) {
675
+ await postJson(fetchFn, `${authSiteUrl}/api/cli-auth/revoke`, {
746
676
  token: refreshTokenValue
747
677
  });
748
678
  }
@@ -759,7 +689,7 @@ async function openDefaultBrowser(url) {
759
689
  }
760
690
  }
761
691
  async function runDeviceLoginFlow(args) {
762
- const start = await startDeviceAuthorization(args.siteUrl, args.fetchFn);
692
+ const start = await startDeviceAuthorization(args.authSiteUrl, args.fetchFn);
763
693
  if (args.openBrowser) {
764
694
  args.writeProgress(
765
695
  "Opening your default browser for Opensteer CLI authentication.\n"
@@ -804,7 +734,7 @@ ${start.verification_uri_complete}
804
734
  await args.sleep(pollIntervalMs);
805
735
  try {
806
736
  const tokenPayload = await pollDeviceToken(
807
- args.siteUrl,
737
+ args.authSiteUrl,
808
738
  start.device_code,
809
739
  args.fetchFn
810
740
  );
@@ -852,7 +782,7 @@ ${start.verification_uri_complete}
852
782
  }
853
783
  async function refreshSavedCredential(saved, deps) {
854
784
  const tokenPayload = await refreshToken(
855
- saved.siteUrl,
785
+ resolveAuthSiteUrl(deps.env),
856
786
  saved.refreshToken,
857
787
  deps.fetchFn
858
788
  );
@@ -865,7 +795,6 @@ async function refreshSavedCredential(saved, deps) {
865
795
  };
866
796
  deps.store.writeCloudCredential({
867
797
  baseUrl: saved.baseUrl,
868
- siteUrl: saved.siteUrl,
869
798
  scope: updated.scope,
870
799
  accessToken: updated.accessToken,
871
800
  refreshToken: updated.refreshToken,
@@ -894,8 +823,7 @@ async function ensureSavedCredentialIsFresh(saved, deps) {
894
823
  const oauth = parseCliOauthError(error.body);
895
824
  if (oauth?.error === "invalid_grant" || oauth?.error === "expired_token") {
896
825
  deps.store.clearCloudCredential({
897
- baseUrl: saved.baseUrl,
898
- siteUrl: saved.siteUrl
826
+ baseUrl: saved.baseUrl
899
827
  });
900
828
  return null;
901
829
  }
@@ -995,7 +923,7 @@ async function ensureCloudCredentialsForCommand(options) {
995
923
  `);
996
924
  }
997
925
  });
998
- const { baseUrl, siteUrl } = resolveCloudTarget(options, env, store);
926
+ const { baseUrl } = resolveCloudTarget(options, env, store);
999
927
  const initialCredential = resolveCloudCredential({
1000
928
  env,
1001
929
  apiKeyFlag: options.apiKeyFlag,
@@ -1003,11 +931,9 @@ async function ensureCloudCredentialsForCommand(options) {
1003
931
  });
1004
932
  let credential = initialCredential;
1005
933
  if (!credential) {
1006
- const saved = store.readCloudCredential({
1007
- baseUrl,
1008
- siteUrl
1009
- });
934
+ const saved = store.readCloudCredential({ baseUrl });
1010
935
  const freshSaved = saved ? await ensureSavedCredentialIsFresh(saved, {
936
+ env,
1011
937
  fetchFn,
1012
938
  store,
1013
939
  now,
@@ -1025,7 +951,7 @@ async function ensureCloudCredentialsForCommand(options) {
1025
951
  if (!credential) {
1026
952
  if (options.autoLoginIfNeeded && (options.interactive ?? false)) {
1027
953
  const loggedIn = await runDeviceLoginFlow({
1028
- siteUrl,
954
+ authSiteUrl: resolveAuthSiteUrl(env),
1029
955
  fetchFn,
1030
956
  writeProgress,
1031
957
  openExternalUrl,
@@ -1035,7 +961,6 @@ async function ensureCloudCredentialsForCommand(options) {
1035
961
  });
1036
962
  store.writeCloudCredential({
1037
963
  baseUrl,
1038
- siteUrl,
1039
964
  scope: loggedIn.scope,
1040
965
  accessToken: loggedIn.accessToken,
1041
966
  refreshToken: loggedIn.refreshToken,
@@ -1053,28 +978,25 @@ async function ensureCloudCredentialsForCommand(options) {
1053
978
  throw new Error(toAuthMissingMessage(options.commandName));
1054
979
  }
1055
980
  }
1056
- store.writeActiveCloudTarget({
1057
- baseUrl,
1058
- siteUrl
1059
- });
981
+ store.writeActiveCloudTarget({ baseUrl });
1060
982
  applyCloudCredentialToEnv(env, credential);
1061
983
  env.OPENSTEER_BASE_URL = baseUrl;
1062
- env.OPENSTEER_CLOUD_SITE_URL = siteUrl;
1063
984
  return {
1064
985
  token: credential.token,
1065
986
  authScheme: credential.authScheme,
1066
987
  source: credential.source,
1067
988
  kind: credential.kind,
1068
- baseUrl,
1069
- siteUrl
989
+ baseUrl
1070
990
  };
1071
991
  }
1072
992
  async function runLogin(args, deps) {
1073
- const { baseUrl, siteUrl } = resolveCloudTarget(args, deps.env, deps.store);
993
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store, {
994
+ allowRememberedTarget: false
995
+ });
1074
996
  const writeProgress = args.json ? deps.writeStderr : deps.writeStdout;
1075
997
  const browserOpenMode = describeBrowserOpenMode(args, deps);
1076
998
  const login = await runDeviceLoginFlow({
1077
- siteUrl,
999
+ authSiteUrl: resolveAuthSiteUrl(deps.env),
1078
1000
  fetchFn: deps.fetchFn,
1079
1001
  writeProgress,
1080
1002
  openExternalUrl: deps.openExternalUrl,
@@ -1085,22 +1007,17 @@ async function runLogin(args, deps) {
1085
1007
  });
1086
1008
  deps.store.writeCloudCredential({
1087
1009
  baseUrl,
1088
- siteUrl,
1089
1010
  scope: login.scope,
1090
1011
  accessToken: login.accessToken,
1091
1012
  refreshToken: login.refreshToken,
1092
1013
  obtainedAt: deps.now(),
1093
1014
  expiresAt: login.expiresAt
1094
1015
  });
1095
- deps.store.writeActiveCloudTarget({
1096
- baseUrl,
1097
- siteUrl
1098
- });
1016
+ deps.store.writeActiveCloudTarget({ baseUrl });
1099
1017
  if (args.json) {
1100
1018
  writeJsonLine(deps, {
1101
1019
  loggedIn: true,
1102
1020
  baseUrl,
1103
- siteUrl,
1104
1021
  expiresAt: login.expiresAt,
1105
1022
  scope: login.scope,
1106
1023
  authSource: "device"
@@ -1108,33 +1025,22 @@ async function runLogin(args, deps) {
1108
1025
  return 0;
1109
1026
  }
1110
1027
  writeHumanLine(deps, "Opensteer CLI login successful.");
1111
- writeHumanLine(deps, ` Site URL: ${siteUrl}`);
1112
1028
  writeHumanLine(deps, ` API Base URL: ${baseUrl}`);
1113
1029
  writeHumanLine(deps, ` Expires At: ${new Date(login.expiresAt).toISOString()}`);
1114
1030
  return 0;
1115
1031
  }
1116
1032
  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
- });
1033
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store);
1034
+ deps.store.writeActiveCloudTarget({ baseUrl });
1035
+ const saved = deps.store.readCloudCredential({ baseUrl });
1126
1036
  if (!saved) {
1127
1037
  if (args.json) {
1128
1038
  writeJsonLine(deps, {
1129
1039
  loggedIn: false,
1130
- baseUrl,
1131
- siteUrl
1040
+ baseUrl
1132
1041
  });
1133
1042
  } else {
1134
- writeHumanLine(
1135
- deps,
1136
- `Opensteer CLI is not logged in for ${siteUrl}.`
1137
- );
1043
+ writeHumanLine(deps, `Opensteer CLI is not logged in for ${baseUrl}.`);
1138
1044
  }
1139
1045
  return 0;
1140
1046
  }
@@ -1145,7 +1051,6 @@ async function runStatus(args, deps) {
1145
1051
  loggedIn: true,
1146
1052
  expired,
1147
1053
  baseUrl: saved.baseUrl,
1148
- siteUrl: saved.siteUrl,
1149
1054
  expiresAt: saved.expiresAt,
1150
1055
  scope: saved.scope
1151
1056
  });
@@ -1155,43 +1060,33 @@ async function runStatus(args, deps) {
1155
1060
  deps,
1156
1061
  expired ? "Opensteer CLI has a saved login, but the access token is expired." : "Opensteer CLI is logged in."
1157
1062
  );
1158
- writeHumanLine(deps, ` Site URL: ${saved.siteUrl}`);
1159
1063
  writeHumanLine(deps, ` API Base URL: ${saved.baseUrl}`);
1160
1064
  writeHumanLine(deps, ` Expires At: ${new Date(saved.expiresAt).toISOString()}`);
1161
1065
  return 0;
1162
1066
  }
1163
1067
  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
- });
1068
+ const { baseUrl } = resolveCloudTarget(args, deps.env, deps.store);
1069
+ deps.store.writeActiveCloudTarget({ baseUrl });
1070
+ const saved = deps.store.readCloudCredential({ baseUrl });
1173
1071
  if (saved) {
1174
1072
  try {
1175
- await revokeToken(saved.siteUrl, saved.refreshToken, deps.fetchFn);
1073
+ await revokeToken(
1074
+ resolveAuthSiteUrl(deps.env),
1075
+ saved.refreshToken,
1076
+ deps.fetchFn
1077
+ );
1176
1078
  } catch {
1177
1079
  }
1178
1080
  }
1179
- deps.store.clearCloudCredential({
1180
- baseUrl,
1181
- siteUrl
1182
- });
1081
+ deps.store.clearCloudCredential({ baseUrl });
1183
1082
  if (args.json) {
1184
1083
  writeJsonLine(deps, {
1185
1084
  loggedOut: true,
1186
- baseUrl,
1187
- siteUrl
1085
+ baseUrl
1188
1086
  });
1189
1087
  return 0;
1190
1088
  }
1191
- writeHumanLine(
1192
- deps,
1193
- `Opensteer CLI login removed for ${siteUrl}.`
1194
- );
1089
+ writeHumanLine(deps, `Opensteer CLI login removed for ${baseUrl}.`);
1195
1090
  return 0;
1196
1091
  }
1197
1092
  async function runOpensteerAuthCli(rawArgs, overrideDeps = {}) {