@autohq/cli 0.1.106 → 0.1.108

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.
@@ -20843,7 +20843,8 @@ var SecretAesGcmAuthTagSchema = external_exports.string().trim().regex(/^[A-Za-z
20843
20843
  var SecretEnvValueSchema = external_exports.union([
20844
20844
  external_exports.string(),
20845
20845
  external_exports.object({
20846
- $secret: ResourceNameSchema
20846
+ $secret: ResourceNameSchema,
20847
+ optional: external_exports.boolean().optional()
20847
20848
  })
20848
20849
  ]);
20849
20850
  var SecretEnvSchema = external_exports.record(
@@ -26205,7 +26206,7 @@ Object.assign(lookup, {
26205
26206
  // package.json
26206
26207
  var package_default = {
26207
26208
  name: "@autohq/cli",
26208
- version: "0.1.106",
26209
+ version: "0.1.108",
26209
26210
  license: "SEE LICENSE IN README.md",
26210
26211
  publishConfig: {
26211
26212
  access: "public"
@@ -26229,6 +26230,7 @@ var package_default = {
26229
26230
  "dev:tui": "node ../../scripts/cli-tui-dev-watch.mjs",
26230
26231
  "skill:generate": "tsx scripts/generate-skill-content.ts",
26231
26232
  test: "npm run test:unit",
26233
+ "test:mutation": "stryker run",
26232
26234
  "test:unit": 'tsx --test "test/**/*.unit.test.ts"',
26233
26235
  typecheck: "tsc --noEmit"
26234
26236
  },
@@ -26249,6 +26251,7 @@ var package_default = {
26249
26251
  devDependencies: {
26250
26252
  "@auto/protocol": "*",
26251
26253
  "@auto/schemas": "*",
26254
+ "@stryker-mutator/core": "^9.6.1",
26252
26255
  "@types/react": "^19",
26253
26256
  tsup: "^8.5.1"
26254
26257
  }
package/dist/index.js CHANGED
@@ -16531,7 +16531,8 @@ var init_secrets = __esm({
16531
16531
  SecretEnvValueSchema = external_exports.union([
16532
16532
  external_exports.string(),
16533
16533
  external_exports.object({
16534
- $secret: ResourceNameSchema
16534
+ $secret: ResourceNameSchema,
16535
+ optional: external_exports.boolean().optional()
16535
16536
  })
16536
16537
  ]);
16537
16538
  SecretEnvSchema = external_exports.record(
@@ -18511,7 +18512,7 @@ var init_package = __esm({
18511
18512
  "package.json"() {
18512
18513
  package_default = {
18513
18514
  name: "@autohq/cli",
18514
- version: "0.1.106",
18515
+ version: "0.1.108",
18515
18516
  license: "SEE LICENSE IN README.md",
18516
18517
  publishConfig: {
18517
18518
  access: "public"
@@ -18535,6 +18536,7 @@ var init_package = __esm({
18535
18536
  "dev:tui": "node ../../scripts/cli-tui-dev-watch.mjs",
18536
18537
  "skill:generate": "tsx scripts/generate-skill-content.ts",
18537
18538
  test: "npm run test:unit",
18539
+ "test:mutation": "stryker run",
18538
18540
  "test:unit": 'tsx --test "test/**/*.unit.test.ts"',
18539
18541
  typecheck: "tsc --noEmit"
18540
18542
  },
@@ -18555,6 +18557,7 @@ var init_package = __esm({
18555
18557
  devDependencies: {
18556
18558
  "@auto/protocol": "*",
18557
18559
  "@auto/schemas": "*",
18560
+ "@stryker-mutator/core": "^9.6.1",
18558
18561
  "@types/react": "^19",
18559
18562
  tsup: "^8.5.1"
18560
18563
  }
@@ -18597,18 +18600,36 @@ var init_tokens = __esm({
18597
18600
  // src/lib/config/path.ts
18598
18601
  import { createHash } from "crypto";
18599
18602
  import { homedir as homedir2 } from "os";
18600
- import { join, normalize } from "path";
18603
+ import { basename, dirname as dirname2, join, normalize } from "path";
18601
18604
  function defaultConfigPath() {
18602
18605
  return process.env.AUTO_CLI_CONFIG ?? join(homedir2(), ".auto", "config.yaml");
18603
18606
  }
18607
+ function isProfilePath(path2) {
18608
+ return basename(dirname2(path2)) === PROFILES_DIR_NAME;
18609
+ }
18604
18610
  function profilesDir(configPath = defaultConfigPath()) {
18611
+ if (isProfilePath(configPath)) return dirname2(configPath);
18605
18612
  return normalize(join(configPath, "..", PROFILES_DIR_NAME));
18606
18613
  }
18607
- 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) {
18608
18629
  const key = `${input.userEmail.toLowerCase()}
18609
18630
  ${serverHost(input.serverUrl)}`;
18610
18631
  const hash2 = createHash("sha256").update(key).digest("hex").slice(0, 8);
18611
- return `${slug(input.userEmail)}--${slug(serverHost(input.serverUrl))}-${hash2}.yaml`;
18632
+ return `${slug(input.userEmail)}--${slug(serverHost(input.serverUrl))}-${hash2}`;
18612
18633
  }
18613
18634
  function serverHost(serverUrl) {
18614
18635
  try {
@@ -18620,62 +18641,129 @@ function serverHost(serverUrl) {
18620
18641
  function slug(value) {
18621
18642
  return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "_");
18622
18643
  }
18623
- var PROFILES_DIR_NAME;
18644
+ var PROFILES_DIR_NAME, PROFILE_NAME_PATTERN;
18624
18645
  var init_path = __esm({
18625
18646
  "src/lib/config/path.ts"() {
18626
18647
  "use strict";
18627
18648
  PROFILES_DIR_NAME = "profiles";
18649
+ PROFILE_NAME_PATTERN = /^[a-z0-9._-]+$/;
18628
18650
  }
18629
18651
  });
18630
18652
 
18631
18653
  // src/lib/config/file.ts
18632
18654
  import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
18633
- import { basename, dirname as dirname2, join as join2 } from "path";
18655
+ import { dirname as dirname3 } from "path";
18634
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) {
18635
18732
  try {
18636
18733
  const text = readFileSync2(path2, "utf8");
18637
18734
  const config2 = {};
18735
+ let currentProfile;
18638
18736
  for (const line of text.split(/\r?\n/)) {
18639
18737
  const match = /^([A-Za-z0-9_]+):\s*(.*)$/.exec(line.trim());
18640
18738
  if (!match) continue;
18739
+ if (match[1] === CURRENT_PROFILE_KEY) {
18740
+ currentProfile = match[2] || void 0;
18741
+ continue;
18742
+ }
18641
18743
  const key = CONFIG_KEYS.find((candidate) => candidate === match[1]);
18642
18744
  if (key) config2[key] = match[2] ?? "";
18643
18745
  }
18644
- return config2;
18746
+ return { config: config2, currentProfile };
18645
18747
  } catch (err) {
18646
- if (err.code === "ENOENT") return {};
18748
+ if (err.code === "ENOENT") {
18749
+ return { config: {} };
18750
+ }
18647
18751
  throw err;
18648
18752
  }
18649
18753
  }
18650
- function writeConfig(config2, path2 = defaultConfigPath()) {
18651
- writeConfigFile(config2, path2);
18652
- if (config2.userEmail && config2.serverUrl && basename(dirname2(path2)) !== PROFILES_DIR_NAME) {
18653
- writeConfigFile(
18654
- config2,
18655
- join2(
18656
- dirname2(path2),
18657
- PROFILES_DIR_NAME,
18658
- profileFileName({
18659
- userEmail: config2.userEmail,
18660
- serverUrl: config2.serverUrl
18661
- })
18662
- )
18663
- );
18664
- }
18665
- }
18666
18754
  function writeConfigFile(config2, path2) {
18667
- mkdirSync2(dirname2(path2), { recursive: true });
18668
18755
  const lines = CONFIG_KEYS.filter((key) => config2[key]).map(
18669
18756
  (key) => `${key}: ${config2[key]}`
18670
18757
  );
18671
- writeFileSync2(path2, `${lines.join("\n")}
18672
- `, {
18673
- encoding: "utf8",
18674
- mode: 384
18675
- });
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 });
18676
18764
  chmodSync(path2, 384);
18677
18765
  }
18678
- var CONFIG_KEYS;
18766
+ var CONFIG_KEYS, CURRENT_PROFILE_KEY;
18679
18767
  var init_file = __esm({
18680
18768
  "src/lib/config/file.ts"() {
18681
18769
  "use strict";
@@ -18692,6 +18780,7 @@ var init_file = __esm({
18692
18780
  "accessTokenOrganizationId",
18693
18781
  "accessTokenProjectId"
18694
18782
  ];
18783
+ CURRENT_PROFILE_KEY = "currentProfile";
18695
18784
  }
18696
18785
  });
18697
18786
 
@@ -21115,10 +21204,10 @@ import {
21115
21204
  } from "fs";
21116
21205
  import {
21117
21206
  basename as basename2,
21118
- dirname as dirname3,
21207
+ dirname as dirname4,
21119
21208
  extname,
21120
21209
  isAbsolute,
21121
- join as join3,
21210
+ join as join2,
21122
21211
  resolve
21123
21212
  } from "path";
21124
21213
  import { parseAllDocuments as parseYamlDocuments } from "yaml";
@@ -21134,7 +21223,7 @@ function readProjectApplyRequest(options) {
21134
21223
  );
21135
21224
  return { ...request, assets: assets2 };
21136
21225
  }
21137
- const directory = options.directory ?? join3(process.cwd(), ".auto");
21226
+ const directory = options.directory ?? join2(process.cwd(), ".auto");
21138
21227
  const files = applyFiles(directory);
21139
21228
  if (files.length === 0) {
21140
21229
  throw new Error(`No resource files found in ${directory}`);
@@ -21197,7 +21286,7 @@ function applyFiles(root) {
21197
21286
  const files = [];
21198
21287
  for (const kind of APPLY_RESOURCE_ORDER) {
21199
21288
  const directory = APPLY_DIRECTORIES[kind];
21200
- const path2 = join3(root, directory);
21289
+ const path2 = join2(root, directory);
21201
21290
  let entries;
21202
21291
  try {
21203
21292
  entries = readdirSync(path2, { withFileTypes: true });
@@ -21341,15 +21430,15 @@ function validateSessionAvatarAsset(input) {
21341
21430
  }
21342
21431
  function applyProjectRoot(directory) {
21343
21432
  const resolved = resolve(directory);
21344
- return basename2(resolved) === ".auto" ? dirname3(resolved) : resolved;
21433
+ return basename2(resolved) === ".auto" ? dirname4(resolved) : resolved;
21345
21434
  }
21346
21435
  function applyFileProjectRoot(file2) {
21347
- let dir = dirname3(resolve(file2));
21436
+ let dir = dirname4(resolve(file2));
21348
21437
  while (true) {
21349
21438
  if (basename2(dir) === ".auto") {
21350
- return dirname3(dir);
21439
+ return dirname4(dir);
21351
21440
  }
21352
- const parent = dirname3(dir);
21441
+ const parent = dirname4(dir);
21353
21442
  if (parent === dir) {
21354
21443
  return process.cwd();
21355
21444
  }
@@ -21383,7 +21472,7 @@ function isRecord(value) {
21383
21472
  function resourceApplyFiles(directory, entries) {
21384
21473
  const files = [];
21385
21474
  for (const entry of entries) {
21386
- const path2 = join3(directory, entry.name);
21475
+ const path2 = join2(directory, entry.name);
21387
21476
  if (entry.isDirectory()) {
21388
21477
  files.push(
21389
21478
  ...resourceApplyFiles(path2, readdirSync(path2, { withFileTypes: true }))
@@ -21579,7 +21668,7 @@ var init_actions = __esm({
21579
21668
 
21580
21669
  // src/lib/config/profiles.ts
21581
21670
  import { readdirSync as readdirSync2 } from "fs";
21582
- import { join as join4 } from "path";
21671
+ import { join as join3 } from "path";
21583
21672
  function listProfiles(configPath = defaultConfigPath()) {
21584
21673
  const dir = profilesDir(configPath);
21585
21674
  let entries;
@@ -21590,8 +21679,12 @@ function listProfiles(configPath = defaultConfigPath()) {
21590
21679
  throw err;
21591
21680
  }
21592
21681
  return entries.filter((entry) => entry.endsWith(".yaml")).sort().map((entry) => {
21593
- const path2 = join4(dir, entry);
21594
- 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
+ };
21595
21688
  }).filter((profile) => profile.config.userEmail);
21596
21689
  }
21597
21690
  function findAccountProfile(input) {
@@ -21630,6 +21723,9 @@ var init_pkce = __esm({
21630
21723
  // src/commands/auth/login.ts
21631
21724
  async function login(input) {
21632
21725
  const style = input.style ?? plainStyle;
21726
+ if (input.options.profile !== void 0) {
21727
+ assertValidProfileName(input.options.profile);
21728
+ }
21633
21729
  const serverUrl = resolveApiBaseUrl({ explicit: input.options.apiUrl });
21634
21730
  if (input.options.device) {
21635
21731
  const device = await postJson(
@@ -21673,6 +21769,7 @@ async function login(input) {
21673
21769
  serverUrl,
21674
21770
  fetch: input.fetch,
21675
21771
  configPath: input.configPath,
21772
+ profileName: input.options.profile,
21676
21773
  writeOutput: input.writeOutput,
21677
21774
  style
21678
21775
  });
@@ -21718,6 +21815,7 @@ async function login(input) {
21718
21815
  serverUrl,
21719
21816
  fetch: input.fetch,
21720
21817
  configPath: input.configPath,
21818
+ profileName: input.options.profile,
21721
21819
  writeOutput: input.writeOutput,
21722
21820
  style
21723
21821
  });
@@ -21738,6 +21836,7 @@ async function login(input) {
21738
21836
  serverUrl,
21739
21837
  fetch: input.fetch,
21740
21838
  configPath: input.configPath,
21839
+ profileName: input.options.profile,
21741
21840
  writeOutput: input.writeOutput,
21742
21841
  style
21743
21842
  });
@@ -21805,13 +21904,28 @@ async function finishLogin(input) {
21805
21904
  );
21806
21905
  }
21807
21906
  }
21808
- writeConfig(config2, input.configPath);
21907
+ persistLogin(config2, input);
21809
21908
  input.writeOutput(
21810
21909
  input.style.success(
21811
21910
  token2.user ? `Logged in as ${token2.user.email}.` : "Logged in."
21812
21911
  )
21813
21912
  );
21814
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
+ }
21815
21929
  async function sleep(ms) {
21816
21930
  await new Promise((resolve2) => setTimeout(resolve2, ms));
21817
21931
  }
@@ -21823,6 +21937,7 @@ var init_login = __esm({
21823
21937
  init_tokens();
21824
21938
  init_browser();
21825
21939
  init_file();
21940
+ init_path();
21826
21941
  init_profiles2();
21827
21942
  init_loopback();
21828
21943
  init_style();
@@ -21874,9 +21989,9 @@ var init_resources3 = __esm({
21874
21989
 
21875
21990
  // src/commands/edit/actions.ts
21876
21991
  import { spawn as spawn2 } from "child_process";
21877
- 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";
21878
21993
  import { tmpdir } from "os";
21879
- import { join as join5 } from "path";
21994
+ import { join as join4 } from "path";
21880
21995
  import { parseAllDocuments as parseYamlDocuments2, stringify as stringify2 } from "yaml";
21881
21996
  async function editResource(input) {
21882
21997
  const reference = parseProjectResourceReference(input.resource);
@@ -21891,8 +22006,8 @@ async function editResource(input) {
21891
22006
  const document = editableResourceDocument(reference.kind, current);
21892
22007
  const source = `${stringify2(document).trimEnd()}
21893
22008
  `;
21894
- const tempRoot = mkdtempSync(join5(tmpdir(), "auto-edit-"));
21895
- 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`);
21896
22011
  writeFileSync3(filePath, source, "utf8");
21897
22012
  let removeTempFile = false;
21898
22013
  try {
@@ -21929,7 +22044,7 @@ async function editResource(input) {
21929
22044
  Edited file retained at ${filePath}`);
21930
22045
  } finally {
21931
22046
  if (removeTempFile) {
21932
- rmSync(tempRoot, { recursive: true, force: true });
22047
+ rmSync2(tempRoot, { recursive: true, force: true });
21933
22048
  }
21934
22049
  }
21935
22050
  }
@@ -26972,6 +27087,7 @@ async function launch(opts) {
26972
27087
  const apiClient = createApiClient({
26973
27088
  env: process.env,
26974
27089
  fetch,
27090
+ configPath: opts.configPath,
26975
27091
  writeError: (e) => process.stderr.write(`${e}
26976
27092
  `)
26977
27093
  });
@@ -27164,6 +27280,7 @@ var init_launcher = __esm({
27164
27280
  });
27165
27281
 
27166
27282
  // src/cli/program.ts
27283
+ import { existsSync as existsSync4 } from "fs";
27167
27284
  import { Command, Option as Option3 } from "commander";
27168
27285
 
27169
27286
  // src/commands/agent-bridge/git-credentials.ts
@@ -29135,7 +29252,9 @@ init_base_url();
29135
29252
  init_login();
29136
29253
 
29137
29254
  // src/commands/auth/profile.ts
29255
+ import { existsSync as existsSync2, rmSync } from "fs";
29138
29256
  init_file();
29257
+ init_path();
29139
29258
  init_profiles2();
29140
29259
  async function handleAuthStatus(context) {
29141
29260
  const result = await fetchAuthStatus(context);
@@ -29161,43 +29280,58 @@ function logout(context) {
29161
29280
  );
29162
29281
  context.writeOutput(context.io.style.success("Logged out."));
29163
29282
  }
29164
- function switchAccount(context, accountEmail, options = {}) {
29283
+ function listAccounts(context) {
29165
29284
  const profiles = listProfiles(context.configPath);
29166
29285
  if (profiles.length === 0) {
29167
29286
  throw new Error("No stored accounts. Run `auto auth login` first.");
29168
29287
  }
29169
- const active = readConfig(context.configPath);
29170
- if (!accountEmail) {
29171
- for (const profile of profiles) {
29172
- context.writeOutput(
29173
- accountLine(profile.config, active, context.io.style)
29174
- );
29175
- }
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);
29176
29297
  return;
29177
29298
  }
29178
- const matches = profiles.map((profile) => profile.config).filter(
29179
- (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)
29180
29311
  );
29181
29312
  if (matches.length === 0) {
29182
29313
  throw new Error(
29183
- `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.`
29184
29315
  );
29185
29316
  }
29186
- 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
+ );
29187
29321
  if (!match) {
29188
29322
  throw new Error(
29189
- `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(
29190
29324
  ", "
29191
- )}. Pick one with \`auto auth switch ${accountEmail} --server <url>\`.`
29325
+ )}. Pick one with \`auto auth switch ${accountRef} --server <url>\`.`
29192
29326
  );
29193
29327
  }
29194
- writeConfig(match, context.configPath);
29328
+ setCurrentProfile(match.name, context.configPath);
29195
29329
  context.writeOutput(
29196
29330
  context.io.style.success(
29197
- `Switched to ${match.userEmail} (${match.serverUrl}).`
29331
+ `Switched to ${match.config.userEmail} (${match.config.serverUrl}).`
29198
29332
  )
29199
29333
  );
29200
- if (!match.refreshToken) {
29334
+ if (!match.config.refreshToken) {
29201
29335
  context.writeOutput(
29202
29336
  context.io.style.warn(
29203
29337
  "This account has no stored credentials; run `auto auth login`."
@@ -29205,13 +29339,43 @@ function switchAccount(context, accountEmail, options = {}) {
29205
29339
  );
29206
29340
  }
29207
29341
  }
29208
- function accountLine(config2, active, style) {
29209
- 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) {
29210
29373
  return [
29211
- config2.userEmail,
29212
- style.dim(`server=${config2.serverUrl ?? "(unset)"}`),
29213
- config2.refreshToken ? void 0 : style.warn("logged_out"),
29214
- 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
29215
29379
  ].filter(Boolean).join(" ");
29216
29380
  }
29217
29381
  async function fetchAuthStatus(context) {
@@ -29377,6 +29541,7 @@ function registerAuthCommands(program, context) {
29377
29541
  await login({
29378
29542
  options: {
29379
29543
  ...options,
29544
+ profile: globalOptions.profile,
29380
29545
  apiUrl: resolveApiBaseUrl({
29381
29546
  explicit: [options.apiUrl, globalOptions.apiUrl],
29382
29547
  env: context.env
@@ -29399,11 +29564,13 @@ function registerAuthCommands(program, context) {
29399
29564
  await handleWhoami(context);
29400
29565
  });
29401
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));
29402
29568
  auth.command("switch").description(
29403
29569
  "Switch the active account to a stored profile, or list stored accounts."
29404
- ).argument("[email]", "Email of a stored account").option("--server <url>", "Auto web server URL of the stored account").action(
29405
- (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)
29406
29572
  );
29573
+ auth.command("remove").description("Remove a stored account profile.").argument("<name>", "Profile name to remove").action((name) => removeProfile(context, name));
29407
29574
  }
29408
29575
 
29409
29576
  // src/lib/stdio/readline.ts
@@ -32204,9 +32371,9 @@ function withApiBaseUrl2(context, commandOptions) {
32204
32371
  // src/commands/sessions/connect.ts
32205
32372
  init_resources2();
32206
32373
  init_browser();
32207
- 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";
32208
32375
  import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
32209
- import { join as join6 } from "path";
32376
+ import { join as join5 } from "path";
32210
32377
  var POLL_INTERVAL_MS2 = 2e3;
32211
32378
  var POLL_TIMEOUT_MS2 = 5 * 6e4;
32212
32379
  var SLACK_APPS_URL = "https://api.slack.com/apps";
@@ -32377,7 +32544,7 @@ async function promptForIconUploads(input, options) {
32377
32544
  }
32378
32545
  }
32379
32546
  function stagedLocationLabel(stagedPath) {
32380
- return stagedPath.startsWith(`${join6(homedir3(), "Downloads")}/`) ? "Downloads" : "the printed path";
32547
+ return stagedPath.startsWith(`${join5(homedir3(), "Downloads")}/`) ? "Downloads" : "the printed path";
32381
32548
  }
32382
32549
  async function stageAvatarImage(input) {
32383
32550
  try {
@@ -32389,9 +32556,9 @@ async function stageAvatarImage(input) {
32389
32556
  }
32390
32557
  const contentType = response.headers.get("content-type") ?? "";
32391
32558
  const extension = contentType.includes("jpeg") ? ".jpg" : ".png";
32392
- const downloads = join6(homedir3(), "Downloads");
32393
- const directory = existsSync2(downloads) ? downloads : mkdtempSync2(join6(tmpdir2(), "auto-avatar-"));
32394
- 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}`);
32395
32562
  writeFileSync4(path2, Buffer.from(await response.arrayBuffer()));
32396
32563
  return path2;
32397
32564
  } catch {
@@ -32534,6 +32701,10 @@ function registerToolCommands(program, context) {
32534
32701
  });
32535
32702
  }
32536
32703
 
32704
+ // src/cli/program.ts
32705
+ init_path();
32706
+ init_profiles2();
32707
+
32537
32708
  // src/lib/output/iostreams.ts
32538
32709
  init_style();
32539
32710
  function createIOStreams(flags) {
@@ -32627,9 +32798,12 @@ function createProgram(options = {}) {
32627
32798
  "stream-json",
32628
32799
  "tui"
32629
32800
  ])
32630
- ).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
+ );
32631
32805
  program.configureHelp({ showGlobalOptions: true });
32632
- program.hook("preAction", () => {
32806
+ program.hook("preAction", (_thisCommand, actionCommand) => {
32633
32807
  if (!options.io) {
32634
32808
  const g = program.opts();
32635
32809
  context.io = createIOStreams({
@@ -32639,15 +32813,34 @@ function createProgram(options = {}) {
32639
32813
  writeError: options.writeError
32640
32814
  });
32641
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
+ }
32642
32832
  });
32643
32833
  const launchTui = async () => {
32644
32834
  const g = program.opts();
32645
32835
  if (options.launchTui) {
32646
- await options.launchTui({ apiUrl: g.apiUrl });
32836
+ await options.launchTui({
32837
+ apiUrl: g.apiUrl,
32838
+ configPath: context.configPath
32839
+ });
32647
32840
  return;
32648
32841
  }
32649
32842
  const { launch: launch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
32650
- await launch2({ apiUrl: g.apiUrl });
32843
+ await launch2({ apiUrl: g.apiUrl, configPath: context.configPath });
32651
32844
  process.exit(0);
32652
32845
  };
32653
32846
  program.action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autohq/cli",
3
- "version": "0.1.106",
3
+ "version": "0.1.108",
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
  }