@autohq/cli 0.1.107 → 0.1.109

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
@@ -44,6 +44,24 @@ auto tui # interactive dashboard
44
44
 
45
45
  Run `auto --help` for the full command tree.
46
46
 
47
+ ## Accounts and profiles
48
+
49
+ Each signed-in account is stored as a named profile; the active one is a
50
+ pointer in `~/.auto/config.yaml`. Logging in to a second account never
51
+ discards the first.
52
+
53
+ ```sh
54
+ auto auth login --profile staging # sign in under a chosen profile name
55
+ auto auth list # list stored accounts
56
+ auto auth switch staging # make a profile (or email) active
57
+ auto auth remove staging # delete a stored profile
58
+ auto --profile staging runs list # use a profile for one invocation
59
+ ```
60
+
61
+ `AUTO_PROFILE=<name>` pins a whole shell session to a profile without
62
+ moving the active pointer, so different terminals can act as different
63
+ accounts at the same time.
64
+
47
65
  ## License
48
66
 
49
67
  Copyright Fractal Works. All rights reserved.
@@ -26206,7 +26206,7 @@ Object.assign(lookup, {
26206
26206
  // package.json
26207
26207
  var package_default = {
26208
26208
  name: "@autohq/cli",
26209
- version: "0.1.107",
26209
+ version: "0.1.109",
26210
26210
  license: "SEE LICENSE IN README.md",
26211
26211
  publishConfig: {
26212
26212
  access: "public"
@@ -26230,6 +26230,7 @@ var package_default = {
26230
26230
  "dev:tui": "node ../../scripts/cli-tui-dev-watch.mjs",
26231
26231
  "skill:generate": "tsx scripts/generate-skill-content.ts",
26232
26232
  test: "npm run test:unit",
26233
+ "test:mutation": "stryker run",
26233
26234
  "test:unit": 'tsx --test "test/**/*.unit.test.ts"',
26234
26235
  typecheck: "tsc --noEmit"
26235
26236
  },
@@ -26250,6 +26251,7 @@ var package_default = {
26250
26251
  devDependencies: {
26251
26252
  "@auto/protocol": "*",
26252
26253
  "@auto/schemas": "*",
26254
+ "@stryker-mutator/core": "^9.6.1",
26253
26255
  "@types/react": "^19",
26254
26256
  tsup: "^8.5.1"
26255
26257
  }
package/dist/index.js CHANGED
@@ -18512,7 +18512,7 @@ var init_package = __esm({
18512
18512
  "package.json"() {
18513
18513
  package_default = {
18514
18514
  name: "@autohq/cli",
18515
- version: "0.1.107",
18515
+ version: "0.1.109",
18516
18516
  license: "SEE LICENSE IN README.md",
18517
18517
  publishConfig: {
18518
18518
  access: "public"
@@ -18536,6 +18536,7 @@ var init_package = __esm({
18536
18536
  "dev:tui": "node ../../scripts/cli-tui-dev-watch.mjs",
18537
18537
  "skill:generate": "tsx scripts/generate-skill-content.ts",
18538
18538
  test: "npm run test:unit",
18539
+ "test:mutation": "stryker run",
18539
18540
  "test:unit": 'tsx --test "test/**/*.unit.test.ts"',
18540
18541
  typecheck: "tsc --noEmit"
18541
18542
  },
@@ -18556,6 +18557,7 @@ var init_package = __esm({
18556
18557
  devDependencies: {
18557
18558
  "@auto/protocol": "*",
18558
18559
  "@auto/schemas": "*",
18560
+ "@stryker-mutator/core": "^9.6.1",
18559
18561
  "@types/react": "^19",
18560
18562
  tsup: "^8.5.1"
18561
18563
  }
@@ -18598,18 +18600,36 @@ var init_tokens = __esm({
18598
18600
  // src/lib/config/path.ts
18599
18601
  import { createHash } from "crypto";
18600
18602
  import { homedir as homedir2 } from "os";
18601
- import { join, normalize } from "path";
18603
+ import { basename, dirname as dirname2, join, normalize } from "path";
18602
18604
  function defaultConfigPath() {
18603
18605
  return process.env.AUTO_CLI_CONFIG ?? join(homedir2(), ".auto", "config.yaml");
18604
18606
  }
18607
+ function isProfilePath(path2) {
18608
+ return basename(dirname2(path2)) === PROFILES_DIR_NAME;
18609
+ }
18605
18610
  function profilesDir(configPath = defaultConfigPath()) {
18611
+ if (isProfilePath(configPath)) return dirname2(configPath);
18606
18612
  return normalize(join(configPath, "..", PROFILES_DIR_NAME));
18607
18613
  }
18608
- function profileFileName(input) {
18614
+ function profileFilePath(configPath, name) {
18615
+ return join(profilesDir(configPath), `${name}.yaml`);
18616
+ }
18617
+ function profileNameFromPath(path2) {
18618
+ return basename(path2).replace(/\.yaml$/, "");
18619
+ }
18620
+ function assertValidProfileName(name) {
18621
+ if (!PROFILE_NAME_PATTERN.test(name)) {
18622
+ throw new Error(
18623
+ `Invalid profile name "${name}". Profile names use lowercase letters, digits, dots, dashes, and underscores.`
18624
+ );
18625
+ }
18626
+ return name;
18627
+ }
18628
+ function derivedProfileName(input) {
18609
18629
  const key = `${input.userEmail.toLowerCase()}
18610
18630
  ${serverHost(input.serverUrl)}`;
18611
18631
  const hash2 = createHash("sha256").update(key).digest("hex").slice(0, 8);
18612
- return `${slug(input.userEmail)}--${slug(serverHost(input.serverUrl))}-${hash2}.yaml`;
18632
+ return `${slug(input.userEmail)}--${slug(serverHost(input.serverUrl))}-${hash2}`;
18613
18633
  }
18614
18634
  function serverHost(serverUrl) {
18615
18635
  try {
@@ -18621,62 +18641,129 @@ function serverHost(serverUrl) {
18621
18641
  function slug(value) {
18622
18642
  return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "_");
18623
18643
  }
18624
- var PROFILES_DIR_NAME;
18644
+ var PROFILES_DIR_NAME, PROFILE_NAME_PATTERN;
18625
18645
  var init_path = __esm({
18626
18646
  "src/lib/config/path.ts"() {
18627
18647
  "use strict";
18628
18648
  PROFILES_DIR_NAME = "profiles";
18649
+ PROFILE_NAME_PATTERN = /^[a-z0-9._-]+$/;
18629
18650
  }
18630
18651
  });
18631
18652
 
18632
18653
  // src/lib/config/file.ts
18633
18654
  import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
18634
- import { basename, dirname as dirname2, join as join2 } from "path";
18655
+ import { dirname as dirname3 } from "path";
18635
18656
  function readConfig(path2 = defaultConfigPath()) {
18657
+ const raw = readRawConfigFile(path2);
18658
+ if (isProfilePath(path2) || !raw.currentProfile) return raw.config;
18659
+ return readRawConfigFile(profileFilePath(path2, raw.currentProfile)).config;
18660
+ }
18661
+ function currentProfileName(path2 = defaultConfigPath()) {
18662
+ if (isProfilePath(path2)) return profileNameFromPath(path2);
18663
+ return readRawConfigFile(path2).currentProfile;
18664
+ }
18665
+ function writeConfig(config2, path2 = defaultConfigPath()) {
18666
+ if (isProfilePath(path2)) {
18667
+ writeConfigFile(config2, path2);
18668
+ return;
18669
+ }
18670
+ const raw = readRawConfigFile(path2);
18671
+ if (raw.currentProfile) {
18672
+ const targetPath = profileFilePath(path2, raw.currentProfile);
18673
+ const target = readRawConfigFile(targetPath).config;
18674
+ if (hasAccountIdentity(config2) && !sameAccount(config2, target)) {
18675
+ setCurrentProfile(saveProfile({ config: config2, configPath: path2 }), path2);
18676
+ return;
18677
+ }
18678
+ writeConfigFile(config2, targetPath);
18679
+ return;
18680
+ }
18681
+ if (hasAccountIdentity(config2)) {
18682
+ setCurrentProfile(saveProfile({ config: config2, configPath: path2 }), path2);
18683
+ return;
18684
+ }
18685
+ writeConfigFile(config2, path2);
18686
+ }
18687
+ function saveProfile(input) {
18688
+ const configPath = input.configPath ?? defaultConfigPath();
18689
+ const name = resolveProfileName(input);
18690
+ writeConfigFile(input.config, profileFilePath(configPath, name));
18691
+ return name;
18692
+ }
18693
+ function setCurrentProfile(name, path2 = defaultConfigPath()) {
18694
+ if (isProfilePath(path2)) {
18695
+ throw new Error(
18696
+ "Cannot change the active profile while pinned to a profile."
18697
+ );
18698
+ }
18699
+ writeFile2(`${CURRENT_PROFILE_KEY}: ${assertValidProfileName(name)}
18700
+ `, path2);
18701
+ }
18702
+ function clearCurrentProfile(path2 = defaultConfigPath()) {
18703
+ if (isProfilePath(path2)) {
18704
+ throw new Error(
18705
+ "Cannot change the active profile while pinned to a profile."
18706
+ );
18707
+ }
18708
+ writeFile2("", path2);
18709
+ }
18710
+ function resolveProfileName(input) {
18711
+ if (input.name !== void 0) return assertValidProfileName(input.name);
18712
+ if (!input.config.userEmail || !input.config.serverUrl) {
18713
+ throw new Error(
18714
+ "A profile name is required for a config without a signed-in account."
18715
+ );
18716
+ }
18717
+ return derivedProfileName({
18718
+ userEmail: input.config.userEmail,
18719
+ serverUrl: input.config.serverUrl
18720
+ });
18721
+ }
18722
+ function hasAccountIdentity(config2) {
18723
+ return Boolean(config2.userEmail && config2.serverUrl);
18724
+ }
18725
+ function sameAccount(next, current) {
18726
+ if (!current.userEmail) return true;
18727
+ if (next.serverUrl !== current.serverUrl) return false;
18728
+ if (next.userId && current.userId) return next.userId === current.userId;
18729
+ return next.userEmail?.toLowerCase() === current.userEmail.toLowerCase();
18730
+ }
18731
+ function readRawConfigFile(path2) {
18636
18732
  try {
18637
18733
  const text = readFileSync2(path2, "utf8");
18638
18734
  const config2 = {};
18735
+ let currentProfile;
18639
18736
  for (const line of text.split(/\r?\n/)) {
18640
18737
  const match = /^([A-Za-z0-9_]+):\s*(.*)$/.exec(line.trim());
18641
18738
  if (!match) continue;
18739
+ if (match[1] === CURRENT_PROFILE_KEY) {
18740
+ currentProfile = match[2] || void 0;
18741
+ continue;
18742
+ }
18642
18743
  const key = CONFIG_KEYS.find((candidate) => candidate === match[1]);
18643
18744
  if (key) config2[key] = match[2] ?? "";
18644
18745
  }
18645
- return config2;
18746
+ return { config: config2, currentProfile };
18646
18747
  } catch (err) {
18647
- if (err.code === "ENOENT") return {};
18748
+ if (err.code === "ENOENT") {
18749
+ return { config: {} };
18750
+ }
18648
18751
  throw err;
18649
18752
  }
18650
18753
  }
18651
- function writeConfig(config2, path2 = defaultConfigPath()) {
18652
- writeConfigFile(config2, path2);
18653
- if (config2.userEmail && config2.serverUrl && basename(dirname2(path2)) !== PROFILES_DIR_NAME) {
18654
- writeConfigFile(
18655
- config2,
18656
- join2(
18657
- dirname2(path2),
18658
- PROFILES_DIR_NAME,
18659
- profileFileName({
18660
- userEmail: config2.userEmail,
18661
- serverUrl: config2.serverUrl
18662
- })
18663
- )
18664
- );
18665
- }
18666
- }
18667
18754
  function writeConfigFile(config2, path2) {
18668
- mkdirSync2(dirname2(path2), { recursive: true });
18669
18755
  const lines = CONFIG_KEYS.filter((key) => config2[key]).map(
18670
18756
  (key) => `${key}: ${config2[key]}`
18671
18757
  );
18672
- writeFileSync2(path2, `${lines.join("\n")}
18673
- `, {
18674
- encoding: "utf8",
18675
- mode: 384
18676
- });
18758
+ writeFile2(`${lines.join("\n")}
18759
+ `, path2);
18760
+ }
18761
+ function writeFile2(content, path2) {
18762
+ mkdirSync2(dirname3(path2), { recursive: true });
18763
+ writeFileSync2(path2, content, { encoding: "utf8", mode: 384 });
18677
18764
  chmodSync(path2, 384);
18678
18765
  }
18679
- var CONFIG_KEYS;
18766
+ var CONFIG_KEYS, CURRENT_PROFILE_KEY;
18680
18767
  var init_file = __esm({
18681
18768
  "src/lib/config/file.ts"() {
18682
18769
  "use strict";
@@ -18693,6 +18780,7 @@ var init_file = __esm({
18693
18780
  "accessTokenOrganizationId",
18694
18781
  "accessTokenProjectId"
18695
18782
  ];
18783
+ CURRENT_PROFILE_KEY = "currentProfile";
18696
18784
  }
18697
18785
  });
18698
18786
 
@@ -21116,10 +21204,10 @@ import {
21116
21204
  } from "fs";
21117
21205
  import {
21118
21206
  basename as basename2,
21119
- dirname as dirname3,
21207
+ dirname as dirname4,
21120
21208
  extname,
21121
21209
  isAbsolute,
21122
- join as join3,
21210
+ join as join2,
21123
21211
  resolve
21124
21212
  } from "path";
21125
21213
  import { parseAllDocuments as parseYamlDocuments } from "yaml";
@@ -21135,7 +21223,7 @@ function readProjectApplyRequest(options) {
21135
21223
  );
21136
21224
  return { ...request, assets: assets2 };
21137
21225
  }
21138
- const directory = options.directory ?? join3(process.cwd(), ".auto");
21226
+ const directory = options.directory ?? join2(process.cwd(), ".auto");
21139
21227
  const files = applyFiles(directory);
21140
21228
  if (files.length === 0) {
21141
21229
  throw new Error(`No resource files found in ${directory}`);
@@ -21198,7 +21286,7 @@ function applyFiles(root) {
21198
21286
  const files = [];
21199
21287
  for (const kind of APPLY_RESOURCE_ORDER) {
21200
21288
  const directory = APPLY_DIRECTORIES[kind];
21201
- const path2 = join3(root, directory);
21289
+ const path2 = join2(root, directory);
21202
21290
  let entries;
21203
21291
  try {
21204
21292
  entries = readdirSync(path2, { withFileTypes: true });
@@ -21342,15 +21430,15 @@ function validateSessionAvatarAsset(input) {
21342
21430
  }
21343
21431
  function applyProjectRoot(directory) {
21344
21432
  const resolved = resolve(directory);
21345
- return basename2(resolved) === ".auto" ? dirname3(resolved) : resolved;
21433
+ return basename2(resolved) === ".auto" ? dirname4(resolved) : resolved;
21346
21434
  }
21347
21435
  function applyFileProjectRoot(file2) {
21348
- let dir = dirname3(resolve(file2));
21436
+ let dir = dirname4(resolve(file2));
21349
21437
  while (true) {
21350
21438
  if (basename2(dir) === ".auto") {
21351
- return dirname3(dir);
21439
+ return dirname4(dir);
21352
21440
  }
21353
- const parent = dirname3(dir);
21441
+ const parent = dirname4(dir);
21354
21442
  if (parent === dir) {
21355
21443
  return process.cwd();
21356
21444
  }
@@ -21384,7 +21472,7 @@ function isRecord(value) {
21384
21472
  function resourceApplyFiles(directory, entries) {
21385
21473
  const files = [];
21386
21474
  for (const entry of entries) {
21387
- const path2 = join3(directory, entry.name);
21475
+ const path2 = join2(directory, entry.name);
21388
21476
  if (entry.isDirectory()) {
21389
21477
  files.push(
21390
21478
  ...resourceApplyFiles(path2, readdirSync(path2, { withFileTypes: true }))
@@ -21580,7 +21668,7 @@ var init_actions = __esm({
21580
21668
 
21581
21669
  // src/lib/config/profiles.ts
21582
21670
  import { readdirSync as readdirSync2 } from "fs";
21583
- import { join as join4 } from "path";
21671
+ import { join as join3 } from "path";
21584
21672
  function listProfiles(configPath = defaultConfigPath()) {
21585
21673
  const dir = profilesDir(configPath);
21586
21674
  let entries;
@@ -21591,8 +21679,12 @@ function listProfiles(configPath = defaultConfigPath()) {
21591
21679
  throw err;
21592
21680
  }
21593
21681
  return entries.filter((entry) => entry.endsWith(".yaml")).sort().map((entry) => {
21594
- const path2 = join4(dir, entry);
21595
- return { path: path2, config: readConfig(path2) };
21682
+ const path2 = join3(dir, entry);
21683
+ return {
21684
+ name: profileNameFromPath(path2),
21685
+ path: path2,
21686
+ config: readConfig(path2)
21687
+ };
21596
21688
  }).filter((profile) => profile.config.userEmail);
21597
21689
  }
21598
21690
  function findAccountProfile(input) {
@@ -21631,6 +21723,9 @@ var init_pkce = __esm({
21631
21723
  // src/commands/auth/login.ts
21632
21724
  async function login(input) {
21633
21725
  const style = input.style ?? plainStyle;
21726
+ if (input.options.profile !== void 0) {
21727
+ assertValidProfileName(input.options.profile);
21728
+ }
21634
21729
  const serverUrl = resolveApiBaseUrl({ explicit: input.options.apiUrl });
21635
21730
  if (input.options.device) {
21636
21731
  const device = await postJson(
@@ -21674,6 +21769,7 @@ async function login(input) {
21674
21769
  serverUrl,
21675
21770
  fetch: input.fetch,
21676
21771
  configPath: input.configPath,
21772
+ profileName: input.options.profile,
21677
21773
  writeOutput: input.writeOutput,
21678
21774
  style
21679
21775
  });
@@ -21719,6 +21815,7 @@ async function login(input) {
21719
21815
  serverUrl,
21720
21816
  fetch: input.fetch,
21721
21817
  configPath: input.configPath,
21818
+ profileName: input.options.profile,
21722
21819
  writeOutput: input.writeOutput,
21723
21820
  style
21724
21821
  });
@@ -21739,6 +21836,7 @@ async function login(input) {
21739
21836
  serverUrl,
21740
21837
  fetch: input.fetch,
21741
21838
  configPath: input.configPath,
21839
+ profileName: input.options.profile,
21742
21840
  writeOutput: input.writeOutput,
21743
21841
  style
21744
21842
  });
@@ -21806,13 +21904,28 @@ async function finishLogin(input) {
21806
21904
  );
21807
21905
  }
21808
21906
  }
21809
- writeConfig(config2, input.configPath);
21907
+ persistLogin(config2, input);
21810
21908
  input.writeOutput(
21811
21909
  input.style.success(
21812
21910
  token2.user ? `Logged in as ${token2.user.email}.` : "Logged in."
21813
21911
  )
21814
21912
  );
21815
21913
  }
21914
+ function persistLogin(config2, input) {
21915
+ if (!input.token.user) {
21916
+ writeConfig(config2, input.configPath);
21917
+ return;
21918
+ }
21919
+ const pinned = input.configPath !== void 0 && isProfilePath(input.configPath);
21920
+ const name = saveProfile({
21921
+ config: config2,
21922
+ name: input.profileName ?? (pinned && input.configPath ? profileNameFromPath(input.configPath) : void 0),
21923
+ configPath: input.configPath
21924
+ });
21925
+ if (!pinned) {
21926
+ setCurrentProfile(name, input.configPath);
21927
+ }
21928
+ }
21816
21929
  async function sleep(ms) {
21817
21930
  await new Promise((resolve2) => setTimeout(resolve2, ms));
21818
21931
  }
@@ -21824,6 +21937,7 @@ var init_login = __esm({
21824
21937
  init_tokens();
21825
21938
  init_browser();
21826
21939
  init_file();
21940
+ init_path();
21827
21941
  init_profiles2();
21828
21942
  init_loopback();
21829
21943
  init_style();
@@ -21875,9 +21989,9 @@ var init_resources3 = __esm({
21875
21989
 
21876
21990
  // src/commands/edit/actions.ts
21877
21991
  import { spawn as spawn2 } from "child_process";
21878
- import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
21992
+ import { mkdtempSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
21879
21993
  import { tmpdir } from "os";
21880
- import { join as join5 } from "path";
21994
+ import { join as join4 } from "path";
21881
21995
  import { parseAllDocuments as parseYamlDocuments2, stringify as stringify2 } from "yaml";
21882
21996
  async function editResource(input) {
21883
21997
  const reference = parseProjectResourceReference(input.resource);
@@ -21892,8 +22006,8 @@ async function editResource(input) {
21892
22006
  const document = editableResourceDocument(reference.kind, current);
21893
22007
  const source = `${stringify2(document).trimEnd()}
21894
22008
  `;
21895
- const tempRoot = mkdtempSync(join5(tmpdir(), "auto-edit-"));
21896
- const filePath = join5(tempRoot, `${reference.kind}-${reference.name}.yaml`);
22009
+ const tempRoot = mkdtempSync(join4(tmpdir(), "auto-edit-"));
22010
+ const filePath = join4(tempRoot, `${reference.kind}-${reference.name}.yaml`);
21897
22011
  writeFileSync3(filePath, source, "utf8");
21898
22012
  let removeTempFile = false;
21899
22013
  try {
@@ -21930,7 +22044,7 @@ async function editResource(input) {
21930
22044
  Edited file retained at ${filePath}`);
21931
22045
  } finally {
21932
22046
  if (removeTempFile) {
21933
- rmSync(tempRoot, { recursive: true, force: true });
22047
+ rmSync2(tempRoot, { recursive: true, force: true });
21934
22048
  }
21935
22049
  }
21936
22050
  }
@@ -26973,6 +27087,7 @@ async function launch(opts) {
26973
27087
  const apiClient = createApiClient({
26974
27088
  env: process.env,
26975
27089
  fetch,
27090
+ configPath: opts.configPath,
26976
27091
  writeError: (e) => process.stderr.write(`${e}
26977
27092
  `)
26978
27093
  });
@@ -27165,6 +27280,7 @@ var init_launcher = __esm({
27165
27280
  });
27166
27281
 
27167
27282
  // src/cli/program.ts
27283
+ import { existsSync as existsSync4 } from "fs";
27168
27284
  import { Command, Option as Option3 } from "commander";
27169
27285
 
27170
27286
  // src/commands/agent-bridge/git-credentials.ts
@@ -29136,7 +29252,9 @@ init_base_url();
29136
29252
  init_login();
29137
29253
 
29138
29254
  // src/commands/auth/profile.ts
29255
+ import { existsSync as existsSync2, rmSync } from "fs";
29139
29256
  init_file();
29257
+ init_path();
29140
29258
  init_profiles2();
29141
29259
  async function handleAuthStatus(context) {
29142
29260
  const result = await fetchAuthStatus(context);
@@ -29162,43 +29280,58 @@ function logout(context) {
29162
29280
  );
29163
29281
  context.writeOutput(context.io.style.success("Logged out."));
29164
29282
  }
29165
- function switchAccount(context, accountEmail, options = {}) {
29283
+ function listAccounts(context) {
29166
29284
  const profiles = listProfiles(context.configPath);
29167
29285
  if (profiles.length === 0) {
29168
29286
  throw new Error("No stored accounts. Run `auto auth login` first.");
29169
29287
  }
29170
- const active = readConfig(context.configPath);
29171
- if (!accountEmail) {
29172
- for (const profile of profiles) {
29173
- context.writeOutput(
29174
- accountLine(profile.config, active, context.io.style)
29175
- );
29176
- }
29288
+ for (const profile of profiles) {
29289
+ context.writeOutput(
29290
+ accountLine(profile, activeProfileName(context), context.io.style)
29291
+ );
29292
+ }
29293
+ }
29294
+ function switchAccount(context, accountRef, options = {}) {
29295
+ if (!accountRef) {
29296
+ listAccounts(context);
29177
29297
  return;
29178
29298
  }
29179
- const matches = profiles.map((profile) => profile.config).filter(
29180
- (config2) => config2.userEmail?.toLowerCase() === accountEmail.toLowerCase() && (!options.server || config2.serverUrl === options.server)
29299
+ if (context.pinnedProfile) {
29300
+ throw new Error(
29301
+ "Cannot switch the active account while pinned to a profile via --profile or AUTO_PROFILE."
29302
+ );
29303
+ }
29304
+ const profiles = listProfiles(context.configPath);
29305
+ if (profiles.length === 0) {
29306
+ throw new Error("No stored accounts. Run `auto auth login` first.");
29307
+ }
29308
+ const byName = profiles.find((profile) => profile.name === accountRef);
29309
+ const matches = byName ? [byName] : profiles.filter(
29310
+ (profile) => profile.config.userEmail?.toLowerCase() === accountRef.toLowerCase() && (!options.server || profile.config.serverUrl === options.server)
29181
29311
  );
29182
29312
  if (matches.length === 0) {
29183
29313
  throw new Error(
29184
- `No stored account for ${accountEmail}. Run \`auto auth login\` to add it.`
29314
+ `No stored account for ${accountRef}. Run \`auto auth login\` to add it.`
29185
29315
  );
29186
29316
  }
29187
- const match = matches.length === 1 ? matches[0] : matches.find((config2) => config2.serverUrl === active.serverUrl);
29317
+ const active = readConfig(context.configPath);
29318
+ const match = matches.length === 1 ? matches[0] : matches.find(
29319
+ (profile) => profile.config.serverUrl === active.serverUrl
29320
+ );
29188
29321
  if (!match) {
29189
29322
  throw new Error(
29190
- `Multiple servers have a stored account for ${accountEmail}: ${matches.map((config2) => config2.serverUrl).join(
29323
+ `Multiple servers have a stored account for ${accountRef}: ${matches.map((profile) => profile.config.serverUrl).join(
29191
29324
  ", "
29192
- )}. Pick one with \`auto auth switch ${accountEmail} --server <url>\`.`
29325
+ )}. Pick one with \`auto auth switch ${accountRef} --server <url>\`.`
29193
29326
  );
29194
29327
  }
29195
- writeConfig(match, context.configPath);
29328
+ setCurrentProfile(match.name, context.configPath);
29196
29329
  context.writeOutput(
29197
29330
  context.io.style.success(
29198
- `Switched to ${match.userEmail} (${match.serverUrl}).`
29331
+ `Switched to ${match.config.userEmail} (${match.config.serverUrl}).`
29199
29332
  )
29200
29333
  );
29201
- if (!match.refreshToken) {
29334
+ if (!match.config.refreshToken) {
29202
29335
  context.writeOutput(
29203
29336
  context.io.style.warn(
29204
29337
  "This account has no stored credentials; run `auto auth login`."
@@ -29206,13 +29339,43 @@ function switchAccount(context, accountEmail, options = {}) {
29206
29339
  );
29207
29340
  }
29208
29341
  }
29209
- function accountLine(config2, active, style) {
29210
- const isActive = Boolean(config2.userEmail) && config2.userEmail === active.userEmail && config2.serverUrl === active.serverUrl;
29342
+ function removeProfile(context, name) {
29343
+ assertValidProfileName(name);
29344
+ if (context.pinnedProfile === name) {
29345
+ throw new Error(`Cannot remove profile "${name}" while pinned to it.`);
29346
+ }
29347
+ const configPath = context.configPath ?? defaultConfigPath();
29348
+ const path2 = profileFilePath(configPath, name);
29349
+ if (!existsSync2(path2)) {
29350
+ const names = listProfiles(context.configPath).map(
29351
+ (profile) => profile.name
29352
+ );
29353
+ throw new Error(
29354
+ `No stored profile "${name}". Stored profiles: ${names.join(", ") || "(none)"}.`
29355
+ );
29356
+ }
29357
+ if (currentProfileName(context.configPath) === name) {
29358
+ clearCurrentProfile(context.configPath);
29359
+ }
29360
+ rmSync(path2);
29361
+ context.writeOutput(context.io.style.success(`Removed profile "${name}".`));
29362
+ }
29363
+ function activeProfileName(context) {
29364
+ const name = currentProfileName(context.configPath);
29365
+ if (name) return name;
29366
+ const active = readConfig(context.configPath);
29367
+ if (!active.userEmail) return void 0;
29368
+ return listProfiles(context.configPath).find(
29369
+ (profile) => profile.config.userEmail === active.userEmail && profile.config.serverUrl === active.serverUrl
29370
+ )?.name;
29371
+ }
29372
+ function accountLine(profile, activeName, style) {
29211
29373
  return [
29212
- config2.userEmail,
29213
- style.dim(`server=${config2.serverUrl ?? "(unset)"}`),
29214
- config2.refreshToken ? void 0 : style.warn("logged_out"),
29215
- isActive ? style.success("(active)") : void 0
29374
+ profile.name,
29375
+ profile.config.userEmail,
29376
+ style.dim(`server=${profile.config.serverUrl ?? "(unset)"}`),
29377
+ profile.config.refreshToken ? void 0 : style.warn("logged_out"),
29378
+ profile.name === activeName ? style.success("(active)") : void 0
29216
29379
  ].filter(Boolean).join(" ");
29217
29380
  }
29218
29381
  async function fetchAuthStatus(context) {
@@ -29378,6 +29541,7 @@ function registerAuthCommands(program, context) {
29378
29541
  await login({
29379
29542
  options: {
29380
29543
  ...options,
29544
+ profile: globalOptions.profile,
29381
29545
  apiUrl: resolveApiBaseUrl({
29382
29546
  explicit: [options.apiUrl, globalOptions.apiUrl],
29383
29547
  env: context.env
@@ -29400,11 +29564,13 @@ function registerAuthCommands(program, context) {
29400
29564
  await handleWhoami(context);
29401
29565
  });
29402
29566
  auth.command("logout").description("Remove the local user refresh token.").action(() => logout(context));
29567
+ auth.command("list").description("List stored account profiles.").action(() => listAccounts(context));
29403
29568
  auth.command("switch").description(
29404
29569
  "Switch the active account to a stored profile, or list stored accounts."
29405
- ).argument("[email]", "Email of a stored account").option("--server <url>", "Auto web server URL of the stored account").action(
29406
- (email3, options) => switchAccount(context, email3, options)
29570
+ ).argument("[account]", "Profile name or email of a stored account").option("--server <url>", "Auto web server URL of the stored account").action(
29571
+ (account, options) => switchAccount(context, account, options)
29407
29572
  );
29573
+ auth.command("remove").description("Remove a stored account profile.").argument("<name>", "Profile name to remove").action((name) => removeProfile(context, name));
29408
29574
  }
29409
29575
 
29410
29576
  // src/lib/stdio/readline.ts
@@ -32205,9 +32371,9 @@ function withApiBaseUrl2(context, commandOptions) {
32205
32371
  // src/commands/sessions/connect.ts
32206
32372
  init_resources2();
32207
32373
  init_browser();
32208
- import { existsSync as existsSync2, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync4 } from "fs";
32374
+ import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync4 } from "fs";
32209
32375
  import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
32210
- import { join as join6 } from "path";
32376
+ import { join as join5 } from "path";
32211
32377
  var POLL_INTERVAL_MS2 = 2e3;
32212
32378
  var POLL_TIMEOUT_MS2 = 5 * 6e4;
32213
32379
  var SLACK_APPS_URL = "https://api.slack.com/apps";
@@ -32378,7 +32544,7 @@ async function promptForIconUploads(input, options) {
32378
32544
  }
32379
32545
  }
32380
32546
  function stagedLocationLabel(stagedPath) {
32381
- return stagedPath.startsWith(`${join6(homedir3(), "Downloads")}/`) ? "Downloads" : "the printed path";
32547
+ return stagedPath.startsWith(`${join5(homedir3(), "Downloads")}/`) ? "Downloads" : "the printed path";
32382
32548
  }
32383
32549
  async function stageAvatarImage(input) {
32384
32550
  try {
@@ -32390,9 +32556,9 @@ async function stageAvatarImage(input) {
32390
32556
  }
32391
32557
  const contentType = response.headers.get("content-type") ?? "";
32392
32558
  const extension = contentType.includes("jpeg") ? ".jpg" : ".png";
32393
- const downloads = join6(homedir3(), "Downloads");
32394
- const directory = existsSync2(downloads) ? downloads : mkdtempSync2(join6(tmpdir2(), "auto-avatar-"));
32395
- const path2 = join6(directory, `${input.session}-avatar${extension}`);
32559
+ const downloads = join5(homedir3(), "Downloads");
32560
+ const directory = existsSync3(downloads) ? downloads : mkdtempSync2(join5(tmpdir2(), "auto-avatar-"));
32561
+ const path2 = join5(directory, `${input.session}-avatar${extension}`);
32396
32562
  writeFileSync4(path2, Buffer.from(await response.arrayBuffer()));
32397
32563
  return path2;
32398
32564
  } catch {
@@ -32535,6 +32701,10 @@ function registerToolCommands(program, context) {
32535
32701
  });
32536
32702
  }
32537
32703
 
32704
+ // src/cli/program.ts
32705
+ init_path();
32706
+ init_profiles2();
32707
+
32538
32708
  // src/lib/output/iostreams.ts
32539
32709
  init_style();
32540
32710
  function createIOStreams(flags) {
@@ -32628,9 +32798,12 @@ function createProgram(options = {}) {
32628
32798
  "stream-json",
32629
32799
  "tui"
32630
32800
  ])
32631
- ).option("--no-color", "disable color output").option("--api-url <url>", "override API base URL");
32801
+ ).option("--no-color", "disable color output").option("--api-url <url>", "override API base URL").option(
32802
+ "--profile <name>",
32803
+ "use this stored profile for the invocation (or set AUTO_PROFILE); for `auth login`, the profile name to store the login under"
32804
+ );
32632
32805
  program.configureHelp({ showGlobalOptions: true });
32633
- program.hook("preAction", () => {
32806
+ program.hook("preAction", (_thisCommand, actionCommand) => {
32634
32807
  if (!options.io) {
32635
32808
  const g = program.opts();
32636
32809
  context.io = createIOStreams({
@@ -32640,15 +32813,34 @@ function createProgram(options = {}) {
32640
32813
  writeError: options.writeError
32641
32814
  });
32642
32815
  }
32816
+ const isLogin = actionCommand.name() === "login" && actionCommand.parent?.name() === "auth";
32817
+ const flagPin = isLogin ? void 0 : program.opts().profile;
32818
+ const pin = flagPin ?? context.env.AUTO_PROFILE;
32819
+ if (pin && context.pinnedProfile !== pin) {
32820
+ assertValidProfileName(pin);
32821
+ const base = options.configPath ?? defaultConfigPath();
32822
+ const pinnedPath = profileFilePath(base, pin);
32823
+ if (!existsSync4(pinnedPath)) {
32824
+ const names = listProfiles(base).map((profile) => profile.name);
32825
+ throw new Error(
32826
+ `No stored profile "${pin}". Stored profiles: ${names.join(", ") || "(none)"}.`
32827
+ );
32828
+ }
32829
+ context.configPath = pinnedPath;
32830
+ context.pinnedProfile = pin;
32831
+ }
32643
32832
  });
32644
32833
  const launchTui = async () => {
32645
32834
  const g = program.opts();
32646
32835
  if (options.launchTui) {
32647
- await options.launchTui({ apiUrl: g.apiUrl });
32836
+ await options.launchTui({
32837
+ apiUrl: g.apiUrl,
32838
+ configPath: context.configPath
32839
+ });
32648
32840
  return;
32649
32841
  }
32650
32842
  const { launch: launch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
32651
- await launch2({ apiUrl: g.apiUrl });
32843
+ await launch2({ apiUrl: g.apiUrl, configPath: context.configPath });
32652
32844
  process.exit(0);
32653
32845
  };
32654
32846
  program.action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autohq/cli",
3
- "version": "0.1.107",
3
+ "version": "0.1.109",
4
4
  "license": "SEE LICENSE IN README.md",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,6 +24,7 @@
24
24
  "dev:tui": "node ../../scripts/cli-tui-dev-watch.mjs",
25
25
  "skill:generate": "tsx scripts/generate-skill-content.ts",
26
26
  "test": "npm run test:unit",
27
+ "test:mutation": "stryker run",
27
28
  "test:unit": "tsx --test \"test/**/*.unit.test.ts\"",
28
29
  "typecheck": "tsc --noEmit"
29
30
  },
@@ -44,6 +45,7 @@
44
45
  "devDependencies": {
45
46
  "@auto/protocol": "*",
46
47
  "@auto/schemas": "*",
48
+ "@stryker-mutator/core": "^9.6.1",
47
49
  "@types/react": "^19",
48
50
  "tsup": "^8.5.1"
49
51
  }