opensteer 0.6.2 → 0.6.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.
Files changed (33) hide show
  1. package/bin/opensteer.mjs +94 -8
  2. package/dist/{browser-profile-client-DK9qa_Dj.d.cts → browser-profile-client-D6PuRefA.d.cts} +1 -1
  3. package/dist/{browser-profile-client-CaL-mwqs.d.ts → browser-profile-client-OUHaODro.d.ts} +1 -1
  4. package/dist/{chunk-7RMY26CM.js → chunk-54KNQTOL.js} +172 -55
  5. package/dist/{chunk-WJI7TGBQ.js → chunk-6B6LOYU3.js} +1 -1
  6. package/dist/{chunk-F2VDVOJO.js → chunk-G6V2DJRN.js} +451 -609
  7. package/dist/chunk-K5CL76MG.js +81 -0
  8. package/dist/{chunk-WDRMHPWL.js → chunk-KPPOTU3D.js} +159 -180
  9. package/dist/cli/auth.cjs +186 -95
  10. package/dist/cli/auth.d.cts +1 -1
  11. package/dist/cli/auth.d.ts +1 -1
  12. package/dist/cli/auth.js +2 -2
  13. package/dist/cli/local-profile.cjs +197 -0
  14. package/dist/cli/local-profile.d.cts +18 -0
  15. package/dist/cli/local-profile.d.ts +18 -0
  16. package/dist/cli/local-profile.js +97 -0
  17. package/dist/cli/profile.cjs +1747 -1279
  18. package/dist/cli/profile.d.cts +2 -2
  19. package/dist/cli/profile.d.ts +2 -2
  20. package/dist/cli/profile.js +469 -7
  21. package/dist/cli/server.cjs +759 -257
  22. package/dist/cli/server.js +69 -16
  23. package/dist/index.cjs +688 -238
  24. package/dist/index.d.cts +7 -5
  25. package/dist/index.d.ts +7 -5
  26. package/dist/index.js +4 -3
  27. package/dist/{types-BxiRblC7.d.cts → types-BWItZPl_.d.cts} +31 -13
  28. package/dist/{types-BxiRblC7.d.ts → types-BWItZPl_.d.ts} +31 -13
  29. package/package.json +2 -2
  30. package/skills/opensteer/SKILL.md +34 -14
  31. package/skills/opensteer/references/cli-reference.md +1 -1
  32. package/skills/opensteer/references/examples.md +5 -3
  33. package/skills/opensteer/references/sdk-reference.md +16 -14
@@ -0,0 +1,81 @@
1
+ // src/browser/chrome.ts
2
+ import { homedir, platform } from "os";
3
+ import { join } from "path";
4
+ import { existsSync, readFileSync } from "fs";
5
+ function detectChromePaths() {
6
+ const os = platform();
7
+ if (os === "darwin") {
8
+ const executable2 = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
9
+ return {
10
+ executable: existsSync(executable2) ? executable2 : null,
11
+ defaultUserDataDir: join(
12
+ homedir(),
13
+ "Library",
14
+ "Application Support",
15
+ "Google",
16
+ "Chrome"
17
+ )
18
+ };
19
+ }
20
+ if (os === "win32") {
21
+ const executable2 = join(
22
+ process.env.PROGRAMFILES || "C:\\Program Files",
23
+ "Google",
24
+ "Chrome",
25
+ "Application",
26
+ "chrome.exe"
27
+ );
28
+ return {
29
+ executable: existsSync(executable2) ? executable2 : null,
30
+ defaultUserDataDir: join(
31
+ process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local"),
32
+ "Google",
33
+ "Chrome",
34
+ "User Data"
35
+ )
36
+ };
37
+ }
38
+ const executable = "/usr/bin/google-chrome";
39
+ return {
40
+ executable: existsSync(executable) ? executable : null,
41
+ defaultUserDataDir: join(homedir(), ".config", "google-chrome")
42
+ };
43
+ }
44
+ function expandHome(p) {
45
+ if (p.startsWith("~/") || p === "~") {
46
+ return join(homedir(), p.slice(1));
47
+ }
48
+ return p;
49
+ }
50
+ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDataDir) {
51
+ const resolvedUserDataDir = expandHome(userDataDir);
52
+ const localStatePath = join(resolvedUserDataDir, "Local State");
53
+ if (!existsSync(localStatePath)) {
54
+ return [];
55
+ }
56
+ try {
57
+ const raw = JSON.parse(readFileSync(localStatePath, "utf-8"));
58
+ const infoCache = raw && typeof raw === "object" && !Array.isArray(raw) && raw.profile && typeof raw.profile === "object" && !Array.isArray(raw.profile) ? raw.profile.info_cache : void 0;
59
+ if (!infoCache || typeof infoCache !== "object") {
60
+ return [];
61
+ }
62
+ return Object.entries(infoCache).map(([directory, info]) => {
63
+ const record = info && typeof info === "object" && !Array.isArray(info) ? info : {};
64
+ const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : directory;
65
+ return {
66
+ directory,
67
+ name
68
+ };
69
+ }).filter((profile) => profile.directory.trim().length > 0).sort(
70
+ (left, right) => left.directory.localeCompare(right.directory)
71
+ );
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+
77
+ export {
78
+ detectChromePaths,
79
+ expandHome,
80
+ listLocalChromeProfiles
81
+ };
@@ -261,14 +261,76 @@ import fs from "fs";
261
261
  import path2 from "path";
262
262
  import { fileURLToPath } from "url";
263
263
  import { parse as parseDotenv } from "dotenv";
264
+
265
+ // src/cloud/credential-selection.ts
266
+ function selectCloudCredential(options) {
267
+ const apiKey = normalizeNonEmptyString(options.apiKey);
268
+ const accessToken = normalizeNonEmptyString(options.accessToken);
269
+ if (apiKey) {
270
+ if (options.authScheme === "bearer") {
271
+ return {
272
+ apiKey,
273
+ authScheme: "bearer",
274
+ kind: "access-token",
275
+ token: apiKey,
276
+ compatibilityBearerApiKey: true
277
+ };
278
+ }
279
+ return {
280
+ apiKey,
281
+ authScheme: "api-key",
282
+ kind: "api-key",
283
+ token: apiKey
284
+ };
285
+ }
286
+ if (accessToken) {
287
+ return {
288
+ accessToken,
289
+ authScheme: "bearer",
290
+ kind: "access-token",
291
+ token: accessToken
292
+ };
293
+ }
294
+ return null;
295
+ }
296
+ function selectCloudCredentialByPrecedence(layers, authScheme) {
297
+ for (const layer of layers) {
298
+ const hasApiKey = layer.hasApiKey ?? Object.prototype.hasOwnProperty.call(layer, "apiKey");
299
+ const hasAccessToken = layer.hasAccessToken ?? Object.prototype.hasOwnProperty.call(layer, "accessToken");
300
+ if (!hasApiKey && !hasAccessToken) {
301
+ continue;
302
+ }
303
+ return {
304
+ source: layer.source,
305
+ apiKey: layer.apiKey,
306
+ accessToken: layer.accessToken,
307
+ hasApiKey,
308
+ hasAccessToken,
309
+ credential: selectCloudCredential({
310
+ apiKey: layer.apiKey,
311
+ accessToken: layer.accessToken,
312
+ authScheme: layer.authScheme ?? authScheme
313
+ })
314
+ };
315
+ }
316
+ return null;
317
+ }
318
+ function normalizeNonEmptyString(value) {
319
+ if (typeof value !== "string") return void 0;
320
+ const normalized = value.trim();
321
+ return normalized.length ? normalized : void 0;
322
+ }
323
+
324
+ // src/config.ts
264
325
  var DEFAULT_CONFIG = {
265
326
  browser: {
266
327
  headless: false,
267
328
  executablePath: void 0,
268
329
  slowMo: 0,
269
- connectUrl: void 0,
270
- channel: void 0,
271
- profileDir: void 0
330
+ mode: void 0,
331
+ cdpUrl: void 0,
332
+ userDataDir: void 0,
333
+ profileDirectory: void 0
272
334
  },
273
335
  storage: {
274
336
  rootDir: process.cwd()
@@ -552,11 +614,6 @@ function normalizeCloudOptions(value) {
552
614
  }
553
615
  return value;
554
616
  }
555
- function normalizeNonEmptyString(value) {
556
- if (typeof value !== "string") return void 0;
557
- const normalized = value.trim();
558
- return normalized.length ? normalized : void 0;
559
- }
560
617
  function parseCloudEnabled(value, source) {
561
618
  if (value == null) return void 0;
562
619
  if (typeof value === "boolean") return value;
@@ -565,6 +622,18 @@ function parseCloudEnabled(value, source) {
565
622
  `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
566
623
  );
567
624
  }
625
+ function resolveCloudCredentialFields(selectedLayer) {
626
+ const credential = selectedLayer?.credential;
627
+ if (!credential) return {};
628
+ if (credential.kind === "api-key" || credential.compatibilityBearerApiKey === true && selectedLayer?.source !== "env") {
629
+ return {
630
+ apiKey: credential.token
631
+ };
632
+ }
633
+ return {
634
+ accessToken: credential.token
635
+ };
636
+ }
568
637
  function resolveCloudSelection(config, env = process.env) {
569
638
  const configCloud = parseCloudEnabled(config.cloud, "cloud");
570
639
  if (configCloud !== void 0) {
@@ -601,7 +670,12 @@ function resolveConfigWithEnv(input = {}, options = {}) {
601
670
  });
602
671
  assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
603
672
  assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
673
+ const fileCloudOptions = normalizeCloudOptions(fileConfig.cloud);
674
+ const fileHasCloudApiKey = hasOwn(fileCloudOptions, "apiKey");
675
+ const fileHasCloudAccessToken = hasOwn(fileCloudOptions, "accessToken");
604
676
  const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
677
+ assertNoRemovedBrowserConfig(input.browser, "Opensteer constructor config");
678
+ assertNoRemovedBrowserConfig(fileConfig.browser, ".opensteer/config.json");
605
679
  const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
606
680
  const env = resolveEnv(envRootDir, {
607
681
  debug: debugHint,
@@ -617,14 +691,30 @@ function resolveConfigWithEnv(input = {}, options = {}) {
617
691
  "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
618
692
  );
619
693
  }
694
+ if (env.OPENSTEER_CONNECT_URL != null) {
695
+ throw new Error(
696
+ "OPENSTEER_CONNECT_URL is no longer supported. Use OPENSTEER_CDP_URL instead."
697
+ );
698
+ }
699
+ if (env.OPENSTEER_CHANNEL != null) {
700
+ throw new Error(
701
+ "OPENSTEER_CHANNEL is no longer supported. Use OPENSTEER_BROWSER plus OPENSTEER_BROWSER_PATH when needed."
702
+ );
703
+ }
704
+ if (env.OPENSTEER_PROFILE_DIR != null) {
705
+ throw new Error(
706
+ "OPENSTEER_PROFILE_DIR is no longer supported. Use OPENSTEER_USER_DATA_DIR and OPENSTEER_PROFILE_DIRECTORY instead."
707
+ );
708
+ }
620
709
  const envConfig = {
621
710
  browser: {
622
711
  headless: parseBool(env.OPENSTEER_HEADLESS),
623
712
  executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
624
713
  slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
625
- connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
626
- channel: env.OPENSTEER_CHANNEL || void 0,
627
- profileDir: env.OPENSTEER_PROFILE_DIR || void 0
714
+ mode: env.OPENSTEER_BROWSER === "real" || env.OPENSTEER_BROWSER === "chromium" ? env.OPENSTEER_BROWSER : void 0,
715
+ cdpUrl: env.OPENSTEER_CDP_URL || void 0,
716
+ userDataDir: env.OPENSTEER_USER_DATA_DIR || void 0,
717
+ profileDirectory: env.OPENSTEER_PROFILE_DIRECTORY || void 0
628
718
  },
629
719
  cursor: {
630
720
  enabled: parseBool(env.OPENSTEER_CURSOR)
@@ -635,17 +725,38 @@ function resolveConfigWithEnv(input = {}, options = {}) {
635
725
  const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
636
726
  const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
637
727
  const resolved = mergeDeep(mergedWithEnv, input);
728
+ const browserHeadlessExplicit = input.browser?.headless !== void 0 || fileConfig.browser?.headless !== void 0 || envConfig.browser?.headless !== void 0;
729
+ if (!browserHeadlessExplicit && resolved.browser?.mode === "real") {
730
+ resolved.browser = {
731
+ ...resolved.browser,
732
+ headless: true
733
+ };
734
+ }
735
+ function assertNoRemovedBrowserConfig(value, source) {
736
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
737
+ return;
738
+ }
739
+ const record = value;
740
+ if (record.connectUrl !== void 0) {
741
+ throw new Error(
742
+ `${source}.browser.connectUrl is no longer supported. Use browser.cdpUrl instead.`
743
+ );
744
+ }
745
+ if (record.channel !== void 0) {
746
+ throw new Error(
747
+ `${source}.browser.channel is no longer supported. Use browser.mode plus browser.executablePath instead.`
748
+ );
749
+ }
750
+ if (record.profileDir !== void 0) {
751
+ throw new Error(
752
+ `${source}.browser.profileDir is no longer supported. Use browser.userDataDir and browser.profileDirectory instead.`
753
+ );
754
+ }
755
+ }
638
756
  const envApiKey = resolveOpensteerApiKey(env);
639
757
  const envAccessTokenRaw = resolveOpensteerAccessToken(env);
640
758
  const envBaseUrl = resolveOpensteerBaseUrl(env);
641
759
  const envAuthScheme = resolveOpensteerAuthScheme(env);
642
- if (envApiKey && envAccessTokenRaw) {
643
- throw new Error(
644
- "OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
645
- );
646
- }
647
- const envAccessToken = envAccessTokenRaw || (envAuthScheme === "bearer" ? envApiKey : void 0);
648
- const envApiCredential = envAuthScheme === "bearer" && !envAccessTokenRaw ? void 0 : envApiKey;
649
760
  const envCloudProfileId = resolveOpensteerCloudProfileId(env);
650
761
  const envCloudProfileReuseIfActive = resolveOpensteerCloudProfileReuseIfActive(env);
651
762
  const envCloudAnnounce = parseCloudAnnounce(
@@ -674,11 +785,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
674
785
  const inputHasCloudBaseUrl = Boolean(
675
786
  inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
676
787
  );
677
- if (normalizeNonEmptyString(inputCloudOptions?.apiKey) && normalizeNonEmptyString(inputCloudOptions?.accessToken)) {
678
- throw new Error(
679
- "cloud.apiKey and cloud.accessToken are mutually exclusive. Set only one."
680
- );
681
- }
682
788
  const cloudSelection = resolveCloudSelection({
683
789
  cloud: resolved.cloud
684
790
  }, env);
@@ -689,11 +795,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
689
795
  accessToken: resolvedCloudAccessTokenRaw,
690
796
  ...resolvedCloudRest
691
797
  } = resolvedCloud;
692
- if (normalizeNonEmptyString(resolvedCloudApiKeyRaw) && normalizeNonEmptyString(resolvedCloudAccessTokenRaw)) {
693
- throw new Error(
694
- "Cloud config cannot include both apiKey and accessToken at the same time."
695
- );
696
- }
697
798
  const resolvedCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
698
799
  resolvedCloud.browserProfile,
699
800
  "resolved.cloud.browserProfile"
@@ -705,25 +806,40 @@ function resolveConfigWithEnv(input = {}, options = {}) {
705
806
  const browserProfile = inputCloudBrowserProfile ?? envCloudBrowserProfile ?? resolvedCloudBrowserProfile;
706
807
  let authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
707
808
  const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
708
- const credentialOverriddenByInput = inputHasCloudApiKey || inputHasCloudAccessToken;
709
- let apiKey = normalizeNonEmptyString(resolvedCloudApiKeyRaw);
710
- let accessToken = normalizeNonEmptyString(resolvedCloudAccessTokenRaw);
711
- if (!credentialOverriddenByInput) {
712
- if (envAccessToken) {
713
- accessToken = envAccessToken;
714
- apiKey = void 0;
715
- } else if (envApiCredential) {
716
- apiKey = envApiCredential;
717
- accessToken = void 0;
718
- }
719
- }
809
+ const selectedCredentialLayer = selectCloudCredentialByPrecedence(
810
+ [
811
+ {
812
+ source: "input",
813
+ apiKey: inputCloudOptions?.apiKey,
814
+ accessToken: inputCloudOptions?.accessToken,
815
+ hasApiKey: inputHasCloudApiKey,
816
+ hasAccessToken: inputHasCloudAccessToken
817
+ },
818
+ {
819
+ source: "env",
820
+ apiKey: envApiKey,
821
+ accessToken: envAccessTokenRaw,
822
+ hasApiKey: envApiKey !== void 0,
823
+ hasAccessToken: envAccessTokenRaw !== void 0
824
+ },
825
+ {
826
+ source: "file",
827
+ apiKey: fileCloudOptions?.apiKey,
828
+ accessToken: fileCloudOptions?.accessToken,
829
+ hasApiKey: fileHasCloudApiKey,
830
+ hasAccessToken: fileHasCloudAccessToken
831
+ }
832
+ ],
833
+ authScheme
834
+ );
835
+ const { apiKey, accessToken } = resolveCloudCredentialFields(selectedCredentialLayer);
720
836
  if (accessToken) {
721
837
  authScheme = "bearer";
722
838
  }
723
839
  resolved.cloud = {
724
840
  ...resolvedCloudRest,
725
- ...inputHasCloudApiKey ? { apiKey: resolvedCloudApiKeyRaw } : apiKey ? { apiKey } : {},
726
- ...inputHasCloudAccessToken ? { accessToken: resolvedCloudAccessTokenRaw } : accessToken ? { accessToken } : {},
841
+ ...apiKey ? { apiKey } : selectedCredentialLayer?.hasApiKey && !accessToken ? { apiKey: selectedCredentialLayer.apiKey } : {},
842
+ ...accessToken ? { accessToken } : selectedCredentialLayer?.hasAccessToken && !apiKey ? { accessToken: selectedCredentialLayer.accessToken } : {},
727
843
  authScheme,
728
844
  announce,
729
845
  ...browserProfile ? { browserProfile } : {}
@@ -1159,147 +1275,10 @@ function readCloudActionDescription(payload) {
1159
1275
  return normalized.length ? normalized : void 0;
1160
1276
  }
1161
1277
 
1162
- // src/auth/keychain-store.ts
1163
- import { spawnSync } from "child_process";
1164
- function commandExists(command) {
1165
- const result = spawnSync(command, ["--help"], {
1166
- encoding: "utf8",
1167
- stdio: "ignore"
1168
- });
1169
- return result.error == null;
1170
- }
1171
- function commandFailed(result) {
1172
- return typeof result.status === "number" && result.status !== 0;
1173
- }
1174
- function sanitizeCommandArgs(command, args) {
1175
- if (command !== "security") {
1176
- return args;
1177
- }
1178
- const sanitized = [];
1179
- for (let index = 0; index < args.length; index += 1) {
1180
- const value = args[index];
1181
- sanitized.push(value);
1182
- if (value === "-w" && index + 1 < args.length) {
1183
- sanitized.push("[REDACTED]");
1184
- index += 1;
1185
- }
1186
- }
1187
- return sanitized;
1188
- }
1189
- function buildCommandError(command, args, result) {
1190
- const stderr = typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim() : `Command "${command}" failed with status ${String(result.status)}.`;
1191
- const sanitizedArgs = sanitizeCommandArgs(command, args);
1192
- return new Error(
1193
- [
1194
- `Unable to persist credential via ${command}.`,
1195
- `${command} ${sanitizedArgs.join(" ")}`,
1196
- stderr
1197
- ].join(" ")
1198
- );
1199
- }
1200
- function createMacosSecurityStore() {
1201
- return {
1202
- backend: "macos-security",
1203
- get(service, account) {
1204
- const result = spawnSync(
1205
- "security",
1206
- ["find-generic-password", "-s", service, "-a", account, "-w"],
1207
- { encoding: "utf8" }
1208
- );
1209
- if (commandFailed(result)) {
1210
- return null;
1211
- }
1212
- const secret = result.stdout.trim();
1213
- return secret.length ? secret : null;
1214
- },
1215
- set(service, account, secret) {
1216
- const args = [
1217
- "add-generic-password",
1218
- "-U",
1219
- "-s",
1220
- service,
1221
- "-a",
1222
- account,
1223
- "-w",
1224
- secret
1225
- ];
1226
- const result = spawnSync("security", args, { encoding: "utf8" });
1227
- if (commandFailed(result)) {
1228
- throw buildCommandError("security", args, result);
1229
- }
1230
- },
1231
- delete(service, account) {
1232
- const args = ["delete-generic-password", "-s", service, "-a", account];
1233
- const result = spawnSync("security", args, { encoding: "utf8" });
1234
- if (commandFailed(result)) {
1235
- return;
1236
- }
1237
- }
1238
- };
1239
- }
1240
- function createLinuxSecretToolStore() {
1241
- return {
1242
- backend: "linux-secret-tool",
1243
- get(service, account) {
1244
- const result = spawnSync(
1245
- "secret-tool",
1246
- ["lookup", "service", service, "account", account],
1247
- {
1248
- encoding: "utf8"
1249
- }
1250
- );
1251
- if (commandFailed(result)) {
1252
- return null;
1253
- }
1254
- const secret = result.stdout.trim();
1255
- return secret.length ? secret : null;
1256
- },
1257
- set(service, account, secret) {
1258
- const args = [
1259
- "store",
1260
- "--label",
1261
- "Opensteer CLI",
1262
- "service",
1263
- service,
1264
- "account",
1265
- account
1266
- ];
1267
- const result = spawnSync("secret-tool", args, {
1268
- encoding: "utf8",
1269
- input: secret
1270
- });
1271
- if (commandFailed(result)) {
1272
- throw buildCommandError("secret-tool", args, result);
1273
- }
1274
- },
1275
- delete(service, account) {
1276
- const args = ["clear", "service", service, "account", account];
1277
- spawnSync("secret-tool", args, {
1278
- encoding: "utf8"
1279
- });
1280
- }
1281
- };
1282
- }
1283
- function createKeychainStore() {
1284
- if (process.platform === "darwin") {
1285
- if (!commandExists("security")) {
1286
- return null;
1287
- }
1288
- return createMacosSecurityStore();
1289
- }
1290
- if (process.platform === "linux") {
1291
- if (!commandExists("secret-tool")) {
1292
- return null;
1293
- }
1294
- return createLinuxSecretToolStore();
1295
- }
1296
- return null;
1297
- }
1298
-
1299
1278
  export {
1300
- createKeychainStore,
1301
1279
  extractErrorMessage,
1302
1280
  normalizeError,
1281
+ selectCloudCredential,
1303
1282
  normalizeNamespace,
1304
1283
  resolveNamespaceDir,
1305
1284
  resolveCloudSelection,