@autohq/cli 0.1.87 → 0.1.88

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 (2) hide show
  1. package/dist/index.js +252 -167
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -18158,20 +18158,42 @@ var init_active_project = __esm({
18158
18158
  });
18159
18159
 
18160
18160
  // src/lib/config/path.ts
18161
+ import { createHash } from "crypto";
18161
18162
  import { homedir } from "os";
18162
- import { join } from "path";
18163
+ import { join, normalize } from "path";
18163
18164
  function defaultConfigPath() {
18164
18165
  return process.env.AUTO_CLI_CONFIG ?? join(homedir(), ".auto", "config.yaml");
18165
18166
  }
18167
+ function profilesDir(configPath = defaultConfigPath()) {
18168
+ return normalize(join(configPath, "..", PROFILES_DIR_NAME));
18169
+ }
18170
+ function profileFileName(input) {
18171
+ const key = `${input.userEmail.toLowerCase()}
18172
+ ${serverHost(input.serverUrl)}`;
18173
+ const hash2 = createHash("sha256").update(key).digest("hex").slice(0, 8);
18174
+ return `${slug(input.userEmail)}--${slug(serverHost(input.serverUrl))}-${hash2}.yaml`;
18175
+ }
18176
+ function serverHost(serverUrl) {
18177
+ try {
18178
+ return new URL(serverUrl).host;
18179
+ } catch {
18180
+ return serverUrl;
18181
+ }
18182
+ }
18183
+ function slug(value) {
18184
+ return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "_");
18185
+ }
18186
+ var PROFILES_DIR_NAME;
18166
18187
  var init_path = __esm({
18167
18188
  "src/lib/config/path.ts"() {
18168
18189
  "use strict";
18190
+ PROFILES_DIR_NAME = "profiles";
18169
18191
  }
18170
18192
  });
18171
18193
 
18172
18194
  // src/lib/config/file.ts
18173
18195
  import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
18174
- import { dirname as dirname2 } from "path";
18196
+ import { basename, dirname as dirname2, join as join2 } from "path";
18175
18197
  function readConfig(path = defaultConfigPath()) {
18176
18198
  try {
18177
18199
  const text = readFileSync2(path, "utf8");
@@ -18179,21 +18201,8 @@ function readConfig(path = defaultConfigPath()) {
18179
18201
  for (const line of text.split(/\r?\n/)) {
18180
18202
  const match = /^([A-Za-z0-9_]+):\s*(.*)$/.exec(line.trim());
18181
18203
  if (!match) continue;
18182
- const value = match[2] ?? "";
18183
- if (match[1] === "serverUrl") config2.serverUrl = value;
18184
- if (match[1] === "organizationId") config2.organizationId = value;
18185
- if (match[1] === "projectId") config2.projectId = value;
18186
- if (match[1] === "refreshToken") config2.refreshToken = value;
18187
- if (match[1] === "accessToken") config2.accessToken = value;
18188
- if (match[1] === "accessTokenExpiresAt") {
18189
- config2.accessTokenExpiresAt = value;
18190
- }
18191
- if (match[1] === "accessTokenOrganizationId") {
18192
- config2.accessTokenOrganizationId = value;
18193
- }
18194
- if (match[1] === "accessTokenProjectId") {
18195
- config2.accessTokenProjectId = value;
18196
- }
18204
+ const key = CONFIG_KEYS.find((candidate) => candidate === match[1]);
18205
+ if (key) config2[key] = match[2] ?? "";
18197
18206
  }
18198
18207
  return config2;
18199
18208
  } catch (err) {
@@ -18202,17 +18211,26 @@ function readConfig(path = defaultConfigPath()) {
18202
18211
  }
18203
18212
  }
18204
18213
  function writeConfig(config2, path = defaultConfigPath()) {
18214
+ writeConfigFile(config2, path);
18215
+ if (config2.userEmail && config2.serverUrl && basename(dirname2(path)) !== PROFILES_DIR_NAME) {
18216
+ writeConfigFile(
18217
+ config2,
18218
+ join2(
18219
+ dirname2(path),
18220
+ PROFILES_DIR_NAME,
18221
+ profileFileName({
18222
+ userEmail: config2.userEmail,
18223
+ serverUrl: config2.serverUrl
18224
+ })
18225
+ )
18226
+ );
18227
+ }
18228
+ }
18229
+ function writeConfigFile(config2, path) {
18205
18230
  mkdirSync2(dirname2(path), { recursive: true });
18206
- const lines = [
18207
- ["serverUrl", config2.serverUrl],
18208
- ["organizationId", config2.organizationId],
18209
- ["projectId", config2.projectId],
18210
- ["refreshToken", config2.refreshToken],
18211
- ["accessToken", config2.accessToken],
18212
- ["accessTokenExpiresAt", config2.accessTokenExpiresAt],
18213
- ["accessTokenOrganizationId", config2.accessTokenOrganizationId],
18214
- ["accessTokenProjectId", config2.accessTokenProjectId]
18215
- ].filter(([, value]) => value).map(([key, value]) => `${key}: ${value}`);
18231
+ const lines = CONFIG_KEYS.filter((key) => config2[key]).map(
18232
+ (key) => `${key}: ${config2[key]}`
18233
+ );
18216
18234
  writeFileSync2(path, `${lines.join("\n")}
18217
18235
  `, {
18218
18236
  encoding: "utf8",
@@ -18220,10 +18238,23 @@ function writeConfig(config2, path = defaultConfigPath()) {
18220
18238
  });
18221
18239
  chmodSync(path, 384);
18222
18240
  }
18241
+ var CONFIG_KEYS;
18223
18242
  var init_file = __esm({
18224
18243
  "src/lib/config/file.ts"() {
18225
18244
  "use strict";
18226
18245
  init_path();
18246
+ CONFIG_KEYS = [
18247
+ "serverUrl",
18248
+ "userId",
18249
+ "userEmail",
18250
+ "organizationId",
18251
+ "projectId",
18252
+ "refreshToken",
18253
+ "accessToken",
18254
+ "accessTokenExpiresAt",
18255
+ "accessTokenOrganizationId",
18256
+ "accessTokenProjectId"
18257
+ ];
18227
18258
  }
18228
18259
  });
18229
18260
 
@@ -20213,7 +20244,7 @@ var init_connect = __esm({
20213
20244
  });
20214
20245
 
20215
20246
  // src/commands/apply/files.ts
20216
- import { createHash } from "crypto";
20247
+ import { createHash as createHash2 } from "crypto";
20217
20248
  import {
20218
20249
  readFileSync as readFileSync3,
20219
20250
  readdirSync,
@@ -20221,11 +20252,11 @@ import {
20221
20252
  statSync
20222
20253
  } from "fs";
20223
20254
  import {
20224
- basename,
20255
+ basename as basename2,
20225
20256
  dirname as dirname3,
20226
20257
  extname,
20227
20258
  isAbsolute,
20228
- join as join2,
20259
+ join as join3,
20229
20260
  resolve
20230
20261
  } from "path";
20231
20262
  import { parseAllDocuments as parseYamlDocuments } from "yaml";
@@ -20241,7 +20272,7 @@ function readProjectApplyRequest(options) {
20241
20272
  );
20242
20273
  return { ...request, assets: assets2 };
20243
20274
  }
20244
- const directory = options.directory ?? join2(process.cwd(), ".auto");
20275
+ const directory = options.directory ?? join3(process.cwd(), ".auto");
20245
20276
  const files = applyFiles(directory);
20246
20277
  if (files.length === 0) {
20247
20278
  throw new Error(`No resource files found in ${directory}`);
@@ -20304,7 +20335,7 @@ function applyFiles(root) {
20304
20335
  const files = [];
20305
20336
  for (const kind of APPLY_RESOURCE_ORDER) {
20306
20337
  const directory = APPLY_DIRECTORIES[kind];
20307
- const path = join2(root, directory);
20338
+ const path = join3(root, directory);
20308
20339
  let entries;
20309
20340
  try {
20310
20341
  entries = readdirSync(path, { withFileTypes: true });
@@ -20378,7 +20409,7 @@ function readApplyAssets(resources, projectRoot) {
20378
20409
  });
20379
20410
  const bytes = readFileSync3(resolvedPath);
20380
20411
  assets[avatarAsset] = {
20381
- sha256: createHash("sha256").update(bytes).digest("hex"),
20412
+ sha256: createHash2("sha256").update(bytes).digest("hex"),
20382
20413
  contentType: extname(resolvedPath).toLowerCase() === ".png" ? "image/png" : "image/jpeg",
20383
20414
  dataBase64: bytes.toString("base64")
20384
20415
  };
@@ -20442,12 +20473,12 @@ function validateSessionAvatarAsset(input) {
20442
20473
  }
20443
20474
  function applyProjectRoot(directory) {
20444
20475
  const resolved = resolve(directory);
20445
- return basename(resolved) === ".auto" ? dirname3(resolved) : resolved;
20476
+ return basename2(resolved) === ".auto" ? dirname3(resolved) : resolved;
20446
20477
  }
20447
20478
  function applyFileProjectRoot(file2) {
20448
20479
  let dir = dirname3(resolve(file2));
20449
20480
  while (true) {
20450
- if (basename(dir) === ".auto") {
20481
+ if (basename2(dir) === ".auto") {
20451
20482
  return dirname3(dir);
20452
20483
  }
20453
20484
  const parent = dirname3(dir);
@@ -20484,7 +20515,7 @@ function isRecord(value) {
20484
20515
  function resourceApplyFiles(directory, entries) {
20485
20516
  const files = [];
20486
20517
  for (const entry of entries) {
20487
- const path = join2(directory, entry.name);
20518
+ const path = join3(directory, entry.name);
20488
20519
  if (entry.isDirectory()) {
20489
20520
  files.push(
20490
20521
  ...resourceApplyFiles(path, readdirSync(path, { withFileTypes: true }))
@@ -20633,13 +20664,49 @@ var init_actions = __esm({
20633
20664
  }
20634
20665
  });
20635
20666
 
20667
+ // src/lib/config/profiles.ts
20668
+ import { readdirSync as readdirSync2 } from "fs";
20669
+ import { join as join4 } from "path";
20670
+ function listProfiles(configPath = defaultConfigPath()) {
20671
+ const dir = profilesDir(configPath);
20672
+ let entries;
20673
+ try {
20674
+ entries = readdirSync2(dir);
20675
+ } catch (err) {
20676
+ if (err.code === "ENOENT") return [];
20677
+ throw err;
20678
+ }
20679
+ return entries.filter((entry) => entry.endsWith(".yaml")).sort().map((entry) => {
20680
+ const path = join4(dir, entry);
20681
+ return { path, config: readConfig(path) };
20682
+ }).filter((profile) => profile.config.userEmail);
20683
+ }
20684
+ function findAccountProfile(input) {
20685
+ const candidates = listProfiles(input.configPath).map((profile) => profile.config).filter((config2) => config2.serverUrl === input.serverUrl);
20686
+ return candidates.find(
20687
+ (config2) => input.userId && config2.userId === input.userId
20688
+ ) ?? // Email is only a fallback for when a user id is missing on either side;
20689
+ // it must never override a known user-id mismatch (emails can be
20690
+ // reassigned to a different user).
20691
+ candidates.find(
20692
+ (config2) => input.userEmail && config2.userEmail?.toLowerCase() === input.userEmail.toLowerCase() && !(input.userId && config2.userId && config2.userId !== input.userId)
20693
+ );
20694
+ }
20695
+ var init_profiles2 = __esm({
20696
+ "src/lib/config/profiles.ts"() {
20697
+ "use strict";
20698
+ init_file();
20699
+ init_path();
20700
+ }
20701
+ });
20702
+
20636
20703
  // src/commands/auth/pkce.ts
20637
- import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
20704
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
20638
20705
  function pkceVerifier() {
20639
20706
  return randomBytes2(32).toString("base64url");
20640
20707
  }
20641
20708
  function pkceChallenge(verifier) {
20642
- return createHash2("sha256").update(verifier).digest("base64url");
20709
+ return createHash3("sha256").update(verifier).digest("base64url");
20643
20710
  }
20644
20711
  var init_pkce = __esm({
20645
20712
  "src/commands/auth/pkce.ts"() {
@@ -20650,7 +20717,6 @@ var init_pkce = __esm({
20650
20717
  // src/commands/auth/login.ts
20651
20718
  async function login(input) {
20652
20719
  const serverUrl = resolveApiBaseUrl({ explicit: input.options.apiUrl });
20653
- const previous = readConfig(input.configPath);
20654
20720
  if (input.options.device) {
20655
20721
  const device = await postJson(
20656
20722
  input.fetch,
@@ -20668,194 +20734,158 @@ async function login(input) {
20668
20734
  await sleep(Math.max(0, device.interval) * 1e3);
20669
20735
  }
20670
20736
  firstAttempt = false;
20737
+ let token3;
20671
20738
  try {
20672
- const token2 = await postJson(
20739
+ token3 = await postJson(
20673
20740
  input.fetch,
20674
20741
  `${serverUrl}/api/v1/auth/cli/token`,
20675
20742
  {
20676
20743
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
20677
- device_code: device.device_code,
20678
- ...previous.organizationId && previous.projectId ? {
20679
- organization_id: previous.organizationId,
20680
- project_id: previous.projectId
20681
- } : {}
20744
+ device_code: device.device_code
20682
20745
  }
20683
20746
  );
20684
- writeConfig(
20685
- {
20686
- ...previous,
20687
- serverUrl,
20688
- refreshToken: token2.refresh_token,
20689
- accessToken: token2.access_token,
20690
- accessTokenExpiresAt: token2.access_token ? accessTokenExpiresAt(token2) : void 0,
20691
- accessTokenOrganizationId: token2.access_token ? previous.organizationId : void 0,
20692
- accessTokenProjectId: token2.access_token ? previous.projectId : void 0
20693
- },
20694
- input.configPath
20695
- );
20696
- input.writeOutput("Logged in.");
20697
- return;
20698
20747
  } catch (error51) {
20699
20748
  if (error51 instanceof Error && error51.message === "authorization_pending") {
20700
20749
  continue;
20701
20750
  }
20702
20751
  throw error51;
20703
20752
  }
20753
+ await finishLogin({
20754
+ token: token3,
20755
+ serverUrl,
20756
+ fetch: input.fetch,
20757
+ configPath: input.configPath,
20758
+ writeOutput: input.writeOutput
20759
+ });
20760
+ return;
20704
20761
  }
20705
20762
  throw new Error("Device authorization expired before approval.");
20706
20763
  }
20707
20764
  const verifier = input.options.verifier ?? pkceVerifier();
20708
20765
  if (!input.options.code) {
20709
- const selectionDetails = await resolveLoginSelectionDetails({
20710
- configPath: input.configPath,
20711
- env: input.env ?? process.env,
20712
- fetch: input.fetch,
20713
- serverUrl,
20714
- previous,
20715
- writeError: input.writeError ?? (() => {
20716
- })
20717
- });
20718
20766
  const callback = await createOAuthLoopbackCallback({
20719
- successHtml: (result) => renderOAuthLoopbackPage({
20767
+ successHtml: () => renderOAuthLoopbackPage({
20720
20768
  status: "success",
20721
20769
  eyebrow: "Auto CLI",
20722
20770
  title: "Login authorized",
20723
20771
  message: "Auto received the browser authorization. The CLI will finish signing you in from your terminal.",
20724
- details: [
20725
- { label: "Server", value: serverUrl },
20726
- {
20727
- label: "Organization",
20728
- value: result.organizationName ?? selectionDetails.organization
20729
- },
20730
- {
20731
- label: "Project",
20732
- value: result.projectName ?? selectionDetails.project
20733
- }
20734
- ]
20772
+ details: [{ label: "Server", value: serverUrl }]
20735
20773
  }),
20736
20774
  failureHtml: () => renderOAuthLoopbackPage({
20737
20775
  status: "failure",
20738
20776
  eyebrow: "Auto CLI",
20739
20777
  title: "Login failed",
20740
20778
  message: "The browser authorization did not complete. Return to your terminal to retry or inspect the error.",
20741
- details: [
20742
- { label: "Server", value: serverUrl },
20743
- { label: "Organization", value: selectionDetails.organization },
20744
- { label: "Project", value: selectionDetails.project }
20745
- ]
20779
+ details: [{ label: "Server", value: serverUrl }]
20746
20780
  })
20747
20781
  });
20748
20782
  try {
20749
20783
  const authorizeUrl = new URL("/auth/cli", serverUrl);
20750
20784
  authorizeUrl.searchParams.set("pkce_challenge", pkceChallenge(verifier));
20751
20785
  authorizeUrl.searchParams.set("redirect_uri", callback.redirectUri);
20752
- if (previous.organizationId && previous.projectId) {
20753
- authorizeUrl.searchParams.set(
20754
- "organization_id",
20755
- previous.organizationId
20756
- );
20757
- authorizeUrl.searchParams.set("project_id", previous.projectId);
20758
- }
20759
20786
  input.writeOutput(`Opening ${authorizeUrl.toString()}`);
20760
20787
  input.writeOutput("Waiting for browser authorization...");
20761
20788
  openBrowser(authorizeUrl.toString());
20762
20789
  const { code } = await callback.result;
20763
- await exchangeCode({
20790
+ const token3 = await exchangeAuthorizationCode({
20764
20791
  code,
20765
- configPath: input.configPath,
20766
20792
  fetch: input.fetch,
20767
- previous,
20768
20793
  redirectUri: callback.redirectUri,
20769
20794
  serverUrl,
20770
20795
  verifier
20771
20796
  });
20772
- input.writeOutput("Logged in.");
20797
+ await finishLogin({
20798
+ token: token3,
20799
+ serverUrl,
20800
+ fetch: input.fetch,
20801
+ configPath: input.configPath,
20802
+ writeOutput: input.writeOutput
20803
+ });
20773
20804
  return;
20774
20805
  } finally {
20775
20806
  callback.close();
20776
20807
  }
20777
20808
  }
20778
- await exchangeCode({
20809
+ const token2 = await exchangeAuthorizationCode({
20779
20810
  code: input.options.code,
20780
- configPath: input.configPath,
20781
20811
  fetch: input.fetch,
20782
- previous,
20783
20812
  redirectUri: "http://127.0.0.1/callback",
20784
20813
  serverUrl,
20785
20814
  verifier
20786
20815
  });
20787
- input.writeOutput("Logged in.");
20816
+ await finishLogin({
20817
+ token: token2,
20818
+ serverUrl,
20819
+ fetch: input.fetch,
20820
+ configPath: input.configPath,
20821
+ writeOutput: input.writeOutput
20822
+ });
20788
20823
  }
20789
- async function exchangeCode(input) {
20790
- const token2 = await postJson(
20824
+ async function exchangeAuthorizationCode(input) {
20825
+ return postJson(
20791
20826
  input.fetch,
20792
20827
  `${input.serverUrl}/api/v1/auth/cli/token`,
20793
20828
  {
20794
20829
  grant_type: "authorization_code",
20795
20830
  code: input.code,
20796
20831
  code_verifier: input.verifier,
20797
- redirect_uri: input.redirectUri,
20798
- ...input.previous.organizationId && input.previous.projectId ? {
20799
- organization_id: input.previous.organizationId,
20800
- project_id: input.previous.projectId
20801
- } : {}
20832
+ redirect_uri: input.redirectUri
20802
20833
  }
20803
20834
  );
20804
- writeConfig(
20805
- {
20806
- ...input.previous,
20807
- serverUrl: input.serverUrl,
20808
- refreshToken: token2.refresh_token,
20809
- accessToken: token2.access_token,
20810
- accessTokenExpiresAt: token2.access_token ? accessTokenExpiresAt(token2) : void 0,
20811
- accessTokenOrganizationId: token2.access_token ? input.previous.organizationId : void 0,
20812
- accessTokenProjectId: token2.access_token ? input.previous.projectId : void 0
20813
- },
20814
- input.configPath
20815
- );
20816
20835
  }
20817
- async function resolveLoginSelectionDetails(input) {
20818
- const fallback = {
20819
- organization: input.previous.organizationId,
20820
- project: input.previous.projectId
20821
- };
20822
- if (!input.previous.organizationId) {
20823
- return fallback;
20824
- }
20825
- const client = createApiClient({
20836
+ async function finishLogin(input) {
20837
+ const { token: token2, serverUrl } = input;
20838
+ const previous = readConfig(input.configPath);
20839
+ const profile = token2.user ? findAccountProfile({
20826
20840
  configPath: input.configPath,
20827
- env: input.env,
20828
- fetch: input.fetch,
20829
- writeError: input.writeError
20830
- });
20831
- try {
20832
- if (input.previous.projectId) {
20833
- const projects = await client.listProjects({
20834
- apiBaseUrl: input.serverUrl
20835
- });
20836
- const project = projects.projects.find(
20837
- (candidate) => candidate.organizationId === input.previous.organizationId && candidate.projectId === input.previous.projectId
20841
+ serverUrl,
20842
+ userId: token2.user.id,
20843
+ userEmail: token2.user.email
20844
+ }) : void 0;
20845
+ const selectionSource = profile ?? previous;
20846
+ const selection = selectionSource.organizationId && selectionSource.projectId ? {
20847
+ organizationId: selectionSource.organizationId,
20848
+ projectId: selectionSource.projectId
20849
+ } : void 0;
20850
+ let config2 = {
20851
+ serverUrl,
20852
+ userId: token2.user?.id,
20853
+ userEmail: token2.user?.email,
20854
+ refreshToken: token2.refresh_token,
20855
+ accessToken: token2.access_token,
20856
+ accessTokenExpiresAt: token2.access_token ? accessTokenExpiresAt(token2) : void 0
20857
+ };
20858
+ if (selection) {
20859
+ try {
20860
+ const scoped = await postJson(
20861
+ input.fetch,
20862
+ `${serverUrl}/api/v1/auth/cli/token`,
20863
+ {
20864
+ grant_type: "refresh_token",
20865
+ refresh_token: token2.refresh_token,
20866
+ organization_id: selection.organizationId,
20867
+ project_id: selection.projectId
20868
+ }
20869
+ );
20870
+ config2 = {
20871
+ ...config2,
20872
+ ...selection,
20873
+ refreshToken: scoped.refresh_token,
20874
+ accessToken: scoped.access_token,
20875
+ accessTokenExpiresAt: scoped.access_token ? accessTokenExpiresAt(scoped) : void 0,
20876
+ accessTokenOrganizationId: scoped.access_token ? selection.organizationId : void 0,
20877
+ accessTokenProjectId: scoped.access_token ? selection.projectId : void 0
20878
+ };
20879
+ } catch {
20880
+ input.writeOutput(
20881
+ "The saved organization/project selection is not available for this account; run `auto orgs list` to pick a new one."
20838
20882
  );
20839
- if (project) {
20840
- return {
20841
- organization: project.organizationName,
20842
- project: project.projectName
20843
- };
20844
- }
20845
20883
  }
20846
- const organizations = await client.listOrganizations({
20847
- apiBaseUrl: input.serverUrl
20848
- });
20849
- const organization = organizations.organizations.find(
20850
- (candidate) => candidate.organizationId === input.previous.organizationId
20851
- );
20852
- return {
20853
- organization: organization?.organizationName ?? fallback.organization,
20854
- project: fallback.project
20855
- };
20856
- } catch {
20857
- return fallback;
20858
20884
  }
20885
+ writeConfig(config2, input.configPath);
20886
+ input.writeOutput(
20887
+ token2.user ? `Logged in as ${token2.user.email}.` : "Logged in."
20888
+ );
20859
20889
  }
20860
20890
  async function sleep(ms) {
20861
20891
  await new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -20864,11 +20894,11 @@ var init_login = __esm({
20864
20894
  "src/commands/auth/login.ts"() {
20865
20895
  "use strict";
20866
20896
  init_base_url();
20867
- init_client();
20868
20897
  init_http();
20869
20898
  init_tokens();
20870
20899
  init_browser();
20871
20900
  init_file();
20901
+ init_profiles2();
20872
20902
  init_loopback();
20873
20903
  init_pkce();
20874
20904
  }
@@ -20878,7 +20908,7 @@ var init_login = __esm({
20878
20908
  import { spawn as spawn2 } from "child_process";
20879
20909
  import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
20880
20910
  import { tmpdir } from "os";
20881
- import { join as join3 } from "path";
20911
+ import { join as join5 } from "path";
20882
20912
  import { parseAllDocuments as parseYamlDocuments2, stringify as stringify2 } from "yaml";
20883
20913
  async function editResource(input) {
20884
20914
  const reference = parseEditableResource(input.resource);
@@ -20893,8 +20923,8 @@ async function editResource(input) {
20893
20923
  const document = editableResourceDocument(reference.kind, current);
20894
20924
  const source = `${stringify2(document).trimEnd()}
20895
20925
  `;
20896
- const tempRoot = mkdtempSync(join3(tmpdir(), "auto-edit-"));
20897
- const filePath = join3(tempRoot, `${reference.kind}-${reference.name}.yaml`);
20926
+ const tempRoot = mkdtempSync(join5(tmpdir(), "auto-edit-"));
20927
+ const filePath = join5(tempRoot, `${reference.kind}-${reference.name}.yaml`);
20898
20928
  writeFileSync3(filePath, source, "utf8");
20899
20929
  let removeTempFile = false;
20900
20930
  try {
@@ -21153,7 +21183,7 @@ var init_package = __esm({
21153
21183
  "package.json"() {
21154
21184
  package_default = {
21155
21185
  name: "@autohq/cli",
21156
- version: "0.1.87",
21186
+ version: "0.1.88",
21157
21187
  license: "SEE LICENSE IN README.md",
21158
21188
  publishConfig: {
21159
21189
  access: "public"
@@ -28006,6 +28036,7 @@ init_login();
28006
28036
 
28007
28037
  // src/commands/auth/profile.ts
28008
28038
  init_file();
28039
+ init_profiles2();
28009
28040
  async function handleAuthStatus(context) {
28010
28041
  const result = await fetchAuthStatus(context);
28011
28042
  context.io.writeResult(result, formatAuthStatusText);
@@ -28030,12 +28061,58 @@ function logout(context) {
28030
28061
  );
28031
28062
  context.writeOutput("Logged out.");
28032
28063
  }
28064
+ function switchAccount(context, accountEmail, options = {}) {
28065
+ const profiles = listProfiles(context.configPath);
28066
+ if (profiles.length === 0) {
28067
+ throw new Error("No stored accounts. Run `auto auth login` first.");
28068
+ }
28069
+ const active = readConfig(context.configPath);
28070
+ if (!accountEmail) {
28071
+ for (const profile of profiles) {
28072
+ context.writeOutput(accountLine(profile.config, active));
28073
+ }
28074
+ return;
28075
+ }
28076
+ const matches = profiles.map((profile) => profile.config).filter(
28077
+ (config2) => config2.userEmail?.toLowerCase() === accountEmail.toLowerCase() && (!options.server || config2.serverUrl === options.server)
28078
+ );
28079
+ if (matches.length === 0) {
28080
+ throw new Error(
28081
+ `No stored account for ${accountEmail}. Run \`auto auth login\` to add it.`
28082
+ );
28083
+ }
28084
+ const match = matches.length === 1 ? matches[0] : matches.find((config2) => config2.serverUrl === active.serverUrl);
28085
+ if (!match) {
28086
+ throw new Error(
28087
+ `Multiple servers have a stored account for ${accountEmail}: ${matches.map((config2) => config2.serverUrl).join(
28088
+ ", "
28089
+ )}. Pick one with \`auto auth switch ${accountEmail} --server <url>\`.`
28090
+ );
28091
+ }
28092
+ writeConfig(match, context.configPath);
28093
+ context.writeOutput(`Switched to ${match.userEmail} (${match.serverUrl}).`);
28094
+ if (!match.refreshToken) {
28095
+ context.writeOutput(
28096
+ "This account has no stored credentials; run `auto auth login`."
28097
+ );
28098
+ }
28099
+ }
28100
+ function accountLine(config2, active) {
28101
+ const isActive = Boolean(config2.userEmail) && config2.userEmail === active.userEmail && config2.serverUrl === active.serverUrl;
28102
+ return [
28103
+ config2.userEmail,
28104
+ `server=${config2.serverUrl ?? "(unset)"}`,
28105
+ config2.refreshToken ? void 0 : "logged_out",
28106
+ isActive ? "(active)" : void 0
28107
+ ].filter(Boolean).join(" ");
28108
+ }
28033
28109
  async function fetchAuthStatus(context) {
28034
28110
  const config2 = readConfig(context.configPath);
28035
28111
  const client = createContextApiClient(context);
28036
28112
  const operator = client.getOperatorInfo();
28037
28113
  const base = {
28038
28114
  serverUrl: config2.serverUrl ?? context.env.AUTO_API_BASE_URL ?? null,
28115
+ account: config2.userEmail ?? null,
28039
28116
  organizationId: config2.organizationId ?? null,
28040
28117
  projectId: config2.projectId ?? null,
28041
28118
  authSource: operator.authType
@@ -28077,6 +28154,9 @@ async function fetchAuthStatus(context) {
28077
28154
  }
28078
28155
  function formatAuthStatusText(result, writeLine) {
28079
28156
  writeLine(`server: ${result.serverUrl ?? "(unset)"}`);
28157
+ if (result.account) {
28158
+ writeLine(`account: ${result.account}`);
28159
+ }
28080
28160
  writeLine(`organization: ${result.organizationId ?? "(unset)"}`);
28081
28161
  writeLine(`project: ${result.projectId ?? "(unset)"}`);
28082
28162
  writeLine(
@@ -28189,6 +28269,11 @@ function registerAuthCommands(program, context) {
28189
28269
  await handleWhoami(context);
28190
28270
  });
28191
28271
  auth.command("logout").description("Remove the local user refresh token.").action(() => logout(context));
28272
+ auth.command("switch").description(
28273
+ "Switch the active account to a stored profile, or list stored accounts."
28274
+ ).argument("[email]", "Email of a stored account").option("--server <url>", "Auto web server URL of the stored account").action(
28275
+ (email3, options) => switchAccount(context, email3, options)
28276
+ );
28192
28277
  }
28193
28278
 
28194
28279
  // src/lib/stdio/readline.ts
@@ -30745,7 +30830,7 @@ init_resources2();
30745
30830
  init_browser();
30746
30831
  import { existsSync as existsSync2, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync4 } from "fs";
30747
30832
  import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
30748
- import { join as join4 } from "path";
30833
+ import { join as join6 } from "path";
30749
30834
  var POLL_INTERVAL_MS = 2e3;
30750
30835
  var POLL_TIMEOUT_MS = 5 * 6e4;
30751
30836
  var SLACK_APPS_URL = "https://api.slack.com/apps";
@@ -30906,7 +30991,7 @@ async function promptForIconUploads(input, options) {
30906
30991
  }
30907
30992
  }
30908
30993
  function stagedLocationLabel(stagedPath) {
30909
- return stagedPath.startsWith(`${join4(homedir2(), "Downloads")}/`) ? "Downloads" : "the printed path";
30994
+ return stagedPath.startsWith(`${join6(homedir2(), "Downloads")}/`) ? "Downloads" : "the printed path";
30910
30995
  }
30911
30996
  async function stageAvatarImage(input) {
30912
30997
  try {
@@ -30918,9 +31003,9 @@ async function stageAvatarImage(input) {
30918
31003
  }
30919
31004
  const contentType = response.headers.get("content-type") ?? "";
30920
31005
  const extension = contentType.includes("jpeg") ? ".jpg" : ".png";
30921
- const downloads = join4(homedir2(), "Downloads");
30922
- const directory = existsSync2(downloads) ? downloads : mkdtempSync2(join4(tmpdir2(), "auto-avatar-"));
30923
- const path = join4(directory, `${input.session}-avatar${extension}`);
31006
+ const downloads = join6(homedir2(), "Downloads");
31007
+ const directory = existsSync2(downloads) ? downloads : mkdtempSync2(join6(tmpdir2(), "auto-avatar-"));
31008
+ const path = join6(directory, `${input.session}-avatar${extension}`);
30924
31009
  writeFileSync4(path, Buffer.from(await response.arrayBuffer()));
30925
31010
  return path;
30926
31011
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autohq/cli",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"