axauth 1.0.0 → 1.2.0

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 (39) hide show
  1. package/README.md +201 -84
  2. package/dist/auth/adapter.d.ts +51 -17
  3. package/dist/auth/agents/{claude-code-storage.d.ts → claude-storage.d.ts} +13 -5
  4. package/dist/auth/agents/{claude-code-storage.js → claude-storage.js} +51 -3
  5. package/dist/auth/agents/{claude-code.js → claude.js} +29 -61
  6. package/dist/auth/agents/codex-config.js +1 -1
  7. package/dist/auth/agents/codex.js +23 -12
  8. package/dist/auth/agents/copilot-storage.js +1 -2
  9. package/dist/auth/agents/copilot.js +22 -9
  10. package/dist/auth/agents/gemini-auth-check.d.ts +3 -3
  11. package/dist/auth/agents/gemini-auth-check.js +32 -45
  12. package/dist/auth/agents/gemini-install.d.ts +11 -0
  13. package/dist/auth/agents/gemini-install.js +115 -0
  14. package/dist/auth/agents/gemini-storage.d.ts +2 -4
  15. package/dist/auth/agents/gemini-storage.js +4 -12
  16. package/dist/auth/agents/gemini.js +13 -86
  17. package/dist/auth/agents/opencode-schema.d.ts +24 -0
  18. package/dist/auth/agents/opencode-schema.js +42 -0
  19. package/dist/auth/agents/opencode.d.ts +4 -0
  20. package/dist/auth/agents/opencode.js +49 -13
  21. package/dist/auth/registry.d.ts +45 -8
  22. package/dist/auth/registry.js +91 -8
  23. package/dist/auth/resolve-config-directory.d.ts +11 -7
  24. package/dist/auth/resolve-config-directory.js +12 -8
  25. package/dist/auth/types.d.ts +26 -8
  26. package/dist/auth/types.js +20 -1
  27. package/dist/auth/validate-directories.d.ts +25 -0
  28. package/dist/auth/validate-directories.js +29 -0
  29. package/dist/cli.js +19 -4
  30. package/dist/commands/auth.d.ts +5 -11
  31. package/dist/commands/auth.js +15 -74
  32. package/dist/commands/install-credentials.d.ts +16 -0
  33. package/dist/commands/install-credentials.js +77 -0
  34. package/dist/commands/validate-agent.d.ts +14 -0
  35. package/dist/commands/validate-agent.js +22 -0
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +1 -1
  38. package/package.json +5 -4
  39. /package/dist/auth/agents/{claude-code.d.ts → claude.d.ts} +0 -0
@@ -3,9 +3,11 @@
3
3
  *
4
4
  * Supports OAuth via keychain (macOS) or file, and API key via env var.
5
5
  */
6
+ import path from "node:path";
6
7
  import { isMacOS } from "../keychain.js";
7
- import { deleteFileCreds, deleteKeychainCreds, getDefaultCredsFilePath, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, } from "./claude-code-storage.js";
8
- const AGENT_ID = "claude";
8
+ import { resolveCustomDirectory } from "../validate-directories.js";
9
+ import { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, } from "./claude-storage.js";
10
+ const CREDS_FILE_NAME = ".credentials.json";
9
11
  /** Claude Code authentication adapter */
10
12
  const claudeCodeAdapter = {
11
13
  agentId: AGENT_ID,
@@ -16,60 +18,20 @@ const claudeCodeAdapter = {
16
18
  installApiKey: false,
17
19
  },
18
20
  checkAuth() {
19
- if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
20
- return { agentId: AGENT_ID, authenticated: true, method: "OAuth (env)" };
21
- }
22
- const keychainCreds = loadKeychainCreds();
23
- if (keychainCreds) {
24
- const subType = keychainCreds.subscriptionType;
25
- return {
26
- agentId: AGENT_ID,
27
- authenticated: true,
28
- method: `OAuth (${subType ?? "keychain"})`,
29
- };
30
- }
31
- if (loadFileCreds()) {
32
- return { agentId: AGENT_ID, authenticated: true, method: "OAuth (file)" };
33
- }
34
- if (process.env.ANTHROPIC_API_KEY) {
35
- return { agentId: AGENT_ID, authenticated: true, method: "API key" };
36
- }
37
- return { agentId: AGENT_ID, authenticated: false };
21
+ const result = checkAuth();
22
+ return { agentId: AGENT_ID, ...result };
38
23
  },
39
24
  extractRawCredentials() {
40
- if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
41
- return {
42
- agent: AGENT_ID,
43
- type: "oauth",
44
- data: { accessToken: process.env.CLAUDE_CODE_OAUTH_TOKEN },
45
- };
46
- }
47
- const keychainCreds = loadKeychainCreds();
48
- if (keychainCreds) {
49
- return {
50
- agent: AGENT_ID,
51
- type: "oauth",
52
- data: { ...keychainCreds, _source: "keychain" },
53
- };
54
- }
55
- const fileCreds = loadFileCreds();
56
- if (fileCreds) {
57
- return {
58
- agent: AGENT_ID,
59
- type: "oauth",
60
- data: { ...fileCreds, _source: "file" },
61
- };
62
- }
63
- if (process.env.ANTHROPIC_API_KEY) {
64
- return {
65
- agent: AGENT_ID,
66
- type: "api-key",
67
- data: { apiKey: process.env.ANTHROPIC_API_KEY },
68
- };
69
- }
70
- return undefined;
25
+ const result = extractRawCredentials();
26
+ if (!result)
27
+ return undefined;
28
+ return { agent: AGENT_ID, ...result };
71
29
  },
72
30
  installCredentials(creds, options) {
31
+ // Resolve custom directory with validation
32
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
33
+ if (!resolved.ok)
34
+ return resolved.error;
73
35
  if (creds.type === "api-key") {
74
36
  return {
75
37
  ok: false,
@@ -77,17 +39,18 @@ const claudeCodeAdapter = {
77
39
  };
78
40
  }
79
41
  const { _source, ...oauthData } = creds.data;
80
- // Custom path forces file storage (keychain only for default location)
81
- if (options?.path) {
82
- if (saveFileCreds(oauthData, options.path)) {
42
+ // Custom directory forces file storage (keychain only for default location)
43
+ if (resolved.customDir) {
44
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
45
+ if (saveFileCreds(oauthData, targetPath)) {
83
46
  return {
84
47
  ok: true,
85
- message: `Installed credentials to ${options.path}`,
48
+ message: `Installed credentials to ${targetPath}`,
86
49
  };
87
50
  }
88
51
  return {
89
52
  ok: false,
90
- message: `Failed to install credentials to ${options.path}`,
53
+ message: `Failed to install credentials to ${targetPath}`,
91
54
  };
92
55
  }
93
56
  // Default location: use storage option or _source marker
@@ -114,10 +77,15 @@ const claudeCodeAdapter = {
114
77
  };
115
78
  },
116
79
  removeCredentials(options) {
117
- // Custom path: only remove that specific file (no keychain)
118
- if (options?.path) {
119
- if (deleteFileCreds(options.path)) {
120
- return { ok: true, message: `Removed ${options.path}` };
80
+ // Resolve custom directory with validation
81
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
82
+ if (!resolved.ok)
83
+ return resolved.error;
84
+ // Custom directory: only remove that specific file (no keychain)
85
+ if (resolved.customDir) {
86
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
87
+ if (deleteFileCreds(targetPath)) {
88
+ return { ok: true, message: `Removed ${targetPath}` };
121
89
  }
122
90
  return {
123
91
  ok: true,
@@ -8,7 +8,7 @@ import { codexConfigReader } from "axconfig";
8
8
  import { getResolvedConfigDirectory } from "../resolve-config-directory.js";
9
9
  /** Get the codex home directory */
10
10
  function getCodexHome() {
11
- return getResolvedConfigDirectory(codexConfigReader);
11
+ return getResolvedConfigDirectory("codex");
12
12
  }
13
13
  /** Get auth file path */
14
14
  function getAuthFilePath() {
@@ -6,10 +6,12 @@
6
6
  import path from "node:path";
7
7
  import { ensureDirectory } from "../file-storage.js";
8
8
  import { isMacOS } from "../keychain.js";
9
+ import { resolveCustomDirectory } from "../validate-directories.js";
9
10
  import { checkAuth, extractRawCredentials } from "./codex-auth-check.js";
10
11
  import { getAuthFilePath, getCodexHome, updateConfigStorage, } from "./codex-config.js";
11
12
  import { buildAuthContent, deleteFileCreds, deleteKeychainCreds, saveFileCreds, saveKeychainCreds, } from "./codex-storage.js";
12
13
  const AGENT_ID = "codex";
14
+ const CREDS_FILE_NAME = "auth.json";
13
15
  /** Codex authentication adapter */
14
16
  const codexAdapter = {
15
17
  agentId: AGENT_ID,
@@ -22,25 +24,29 @@ const codexAdapter = {
22
24
  checkAuth,
23
25
  extractRawCredentials,
24
26
  installCredentials(creds, options) {
27
+ // Resolve custom directory with validation
28
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
29
+ if (!resolved.ok)
30
+ return resolved.error;
25
31
  const authContent = buildAuthContent(creds.type, creds.data);
26
- // Custom path forces file storage (keychain only for default location)
27
- if (options?.path) {
28
- const parentDirectory = path.dirname(options.path);
29
- if (!ensureDirectory(parentDirectory)) {
32
+ // Custom directory forces file storage (keychain only for default location)
33
+ if (resolved.customDir) {
34
+ if (!ensureDirectory(resolved.customDir)) {
30
35
  return {
31
36
  ok: false,
32
- message: `Failed to create directory ${parentDirectory}`,
37
+ message: `Failed to create directory ${resolved.customDir}`,
33
38
  };
34
39
  }
35
- if (saveFileCreds(authContent, options.path)) {
40
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
41
+ if (saveFileCreds(authContent, targetPath)) {
36
42
  return {
37
43
  ok: true,
38
- message: `Installed credentials to ${options.path}`,
44
+ message: `Installed credentials to ${targetPath}`,
39
45
  };
40
46
  }
41
47
  return {
42
48
  ok: false,
43
- message: `Failed to install credentials to ${options.path}`,
49
+ message: `Failed to install credentials to ${targetPath}`,
44
50
  };
45
51
  }
46
52
  // Default location: use storage option or _source marker
@@ -72,10 +78,15 @@ const codexAdapter = {
72
78
  return { ok: false, message: "Failed to install credentials to file" };
73
79
  },
74
80
  removeCredentials(options) {
75
- // Custom path: only remove that specific file (no keychain)
76
- if (options?.path) {
77
- if (deleteFileCreds(options.path)) {
78
- return { ok: true, message: `Removed ${options.path}` };
81
+ // Resolve custom directory with validation
82
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
83
+ if (!resolved.ok)
84
+ return resolved.error;
85
+ // Custom directory: only remove that specific file (no keychain)
86
+ if (resolved.customDir) {
87
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
88
+ if (deleteFileCreds(targetPath)) {
89
+ return { ok: true, message: `Removed ${targetPath}` };
79
90
  }
80
91
  return {
81
92
  ok: true,
@@ -7,7 +7,6 @@
7
7
  */
8
8
  import { existsSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
9
9
  import path from "node:path";
10
- import { copilotConfigReader } from "axconfig";
11
10
  import { ensureDirectory, loadJsonFile, saveJsonFile, } from "../file-storage.js";
12
11
  import { deleteFromKeychain, isMacOS, loadFromKeychain, saveToKeychain, } from "../keychain.js";
13
12
  import { getResolvedConfigDirectory } from "../resolve-config-directory.js";
@@ -15,7 +14,7 @@ const KEYCHAIN_SERVICE = "copilot-cli";
15
14
  const DEFAULT_HOST = "https://github.com";
16
15
  /** Get the copilot config directory */
17
16
  function getConfigDirectory() {
18
- return getResolvedConfigDirectory(copilotConfigReader);
17
+ return getResolvedConfigDirectory("copilot");
19
18
  }
20
19
  /** Get the default config file path */
21
20
  function getConfigFilePath() {
@@ -4,11 +4,14 @@
4
4
  * Supports OAuth via keychain (macOS) or file, and GitHub token via env var.
5
5
  * Also supports GitHub CLI (`gh auth login`) as a fallback.
6
6
  */
7
+ import path from "node:path";
7
8
  import { ensureDirectory } from "../file-storage.js";
8
9
  import { isMacOS } from "../keychain.js";
10
+ import { resolveCustomDirectory } from "../validate-directories.js";
9
11
  import { findFirstAvailableToken } from "./copilot-auth-check.js";
10
12
  import { deleteFileToken, deleteKeychainToken, deleteTokenFromPath, getConfigDirectory, getConfigFilePath, saveFileToken, saveKeychainToken, saveTokenToPath, } from "./copilot-storage.js";
11
13
  const AGENT_ID = "copilot";
14
+ const CREDS_FILE_NAME = "token.json";
12
15
  /** Copilot CLI authentication adapter */
13
16
  const copilotAdapter = {
14
17
  agentId: AGENT_ID,
@@ -46,6 +49,10 @@ const copilotAdapter = {
46
49
  };
47
50
  },
48
51
  installCredentials(creds, options) {
52
+ // Resolve custom directory with validation
53
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
54
+ if (!resolved.ok)
55
+ return resolved.error;
49
56
  if (creds.type === "api-key") {
50
57
  return {
51
58
  ok: false,
@@ -58,17 +65,18 @@ const copilotAdapter = {
58
65
  if (!token) {
59
66
  return { ok: false, message: "No access token found in credentials" };
60
67
  }
61
- // Custom path forces file storage
62
- if (options?.path) {
63
- if (saveTokenToPath(token, options.path)) {
68
+ // Custom directory forces file storage
69
+ if (resolved.customDir) {
70
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
71
+ if (saveTokenToPath(token, targetPath)) {
64
72
  return {
65
73
  ok: true,
66
- message: `Installed credentials to ${options.path}`,
74
+ message: `Installed credentials to ${targetPath}`,
67
75
  };
68
76
  }
69
77
  return {
70
78
  ok: false,
71
- message: `Failed to install credentials to ${options.path}`,
79
+ message: `Failed to install credentials to ${targetPath}`,
72
80
  };
73
81
  }
74
82
  // Default location: use storage option or _source marker
@@ -102,10 +110,15 @@ const copilotAdapter = {
102
110
  return { ok: false, message: "Failed to install credentials to file" };
103
111
  },
104
112
  removeCredentials(options) {
105
- // Custom path: only remove that specific file
106
- if (options?.path) {
107
- if (deleteTokenFromPath(options.path)) {
108
- return { ok: true, message: `Removed ${options.path}` };
113
+ // Resolve custom directory with validation
114
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
115
+ if (!resolved.ok)
116
+ return resolved.error;
117
+ // Custom directory: only remove that specific file
118
+ if (resolved.customDir) {
119
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
120
+ if (deleteTokenFromPath(targetPath)) {
121
+ return { ok: true, message: `Removed ${targetPath}` };
109
122
  }
110
123
  return {
111
124
  ok: true,
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Gemini CLI authentication checking.
3
3
  *
4
- * Determines auth status based on configured auth type and available credentials.
4
+ * Uses priority-ordered credential discovery: env keychain file.
5
5
  */
6
6
  /** Credential source for auth method display */
7
- type CredentialSource = "keychain" | "file" | "api-key" | "vertex-ai" | "adc";
7
+ type CredentialSource = "keychain" | "file" | "api-key" | "vertex-ai";
8
8
  /** Result of finding available credentials */
9
9
  interface CredentialResult {
10
10
  source: CredentialSource;
11
11
  method: string;
12
12
  data?: Record<string, unknown>;
13
13
  }
14
- /** Find credentials based on configured auth type */
14
+ /** Find credentials using priority-ordered discovery: env keychain → file */
15
15
  declare function findCredentials(): CredentialResult | undefined;
16
16
  export { findCredentials };
@@ -1,51 +1,25 @@
1
1
  /**
2
2
  * Gemini CLI authentication checking.
3
3
  *
4
- * Determines auth status based on configured auth type and available credentials.
4
+ * Uses priority-ordered credential discovery: env keychain file.
5
5
  */
6
6
  import { existsSync } from "node:fs";
7
- import { getAuthType, getDefaultOAuthPath, loadKeychainCreds, loadOAuthCreds, } from "./gemini-storage.js";
8
- /** Check OAuth credentials (keychain first, then file) */
9
- function checkOAuthCredentials() {
10
- const keychainCreds = loadKeychainCreds();
11
- if (keychainCreds) {
12
- return {
13
- source: "keychain",
14
- method: "Google OAuth (keychain)",
15
- data: keychainCreds,
16
- };
17
- }
18
- if (existsSync(getDefaultOAuthPath())) {
19
- const fileCreds = loadOAuthCreds();
20
- if (fileCreds) {
21
- return {
22
- source: "file",
23
- method: "Google OAuth (file)",
24
- data: fileCreds,
25
- };
26
- }
27
- }
28
- return undefined;
29
- }
30
- /** Check API key credentials */
31
- function checkApiKeyCredentials() {
7
+ import { getDefaultOAuthPath, loadKeychainCreds, loadOAuthCreds, } from "./gemini-storage.js";
8
+ /** Check environment variable credentials (API key and Vertex AI) */
9
+ function checkEnvironmentAuth() {
32
10
  if (process.env.GEMINI_API_KEY) {
33
11
  return {
34
12
  source: "api-key",
35
- method: "API key",
13
+ method: "API key (env)",
36
14
  data: { apiKey: process.env.GEMINI_API_KEY },
37
15
  };
38
16
  }
39
- return undefined;
40
- }
41
- /** Check Vertex AI credentials */
42
- function checkVertexAiCredentials() {
43
17
  const hasVertexEnvironment = (process.env.GOOGLE_CLOUD_PROJECT && process.env.GOOGLE_CLOUD_LOCATION) ||
44
18
  process.env.GOOGLE_API_KEY;
45
19
  if (hasVertexEnvironment) {
46
20
  return {
47
21
  source: "vertex-ai",
48
- method: "Vertex AI",
22
+ method: "Vertex AI (env)",
49
23
  data: process.env.GOOGLE_API_KEY
50
24
  ? { apiKey: process.env.GOOGLE_API_KEY }
51
25
  : undefined,
@@ -53,21 +27,34 @@ function checkVertexAiCredentials() {
53
27
  }
54
28
  return undefined;
55
29
  }
56
- /** Find credentials based on configured auth type */
57
- function findCredentials() {
58
- const authType = getAuthType();
59
- if (authType === "oauth-personal") {
60
- return checkOAuthCredentials();
61
- }
62
- if (authType === "gemini-api-key") {
63
- return checkApiKeyCredentials();
64
- }
65
- if (authType === "vertex-ai") {
66
- return checkVertexAiCredentials();
30
+ /** Check keychain for OAuth credentials */
31
+ function checkKeychainAuth() {
32
+ const keychainCreds = loadKeychainCreds();
33
+ if (keychainCreds) {
34
+ return {
35
+ source: "keychain",
36
+ method: "Google OAuth (keychain)",
37
+ data: keychainCreds,
38
+ };
67
39
  }
68
- if (authType === "compute-default-credentials") {
69
- return { source: "adc", method: "Compute ADC" };
40
+ return undefined;
41
+ }
42
+ /** Check file for OAuth credentials */
43
+ function checkFileAuth() {
44
+ if (existsSync(getDefaultOAuthPath())) {
45
+ const fileCreds = loadOAuthCreds();
46
+ if (fileCreds) {
47
+ return {
48
+ source: "file",
49
+ method: "Google OAuth (file)",
50
+ data: fileCreds,
51
+ };
52
+ }
70
53
  }
71
54
  return undefined;
72
55
  }
56
+ /** Find credentials using priority-ordered discovery: env → keychain → file */
57
+ function findCredentials() {
58
+ return checkEnvironmentAuth() ?? checkKeychainAuth() ?? checkFileAuth();
59
+ }
73
60
  export { findCredentials };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Gemini credential installation operations.
3
+ *
4
+ * Extracted from gemini.ts to reduce file complexity.
5
+ */
6
+ import type { InstallOptions, OperationResult, RemoveOptions } from "../adapter.js";
7
+ /** Install OAuth credentials to storage */
8
+ declare function installOAuthCredentials(oauthData: Record<string, unknown>, source: string | undefined, options?: InstallOptions): OperationResult;
9
+ /** Remove credentials from storage */
10
+ declare function removeGeminiCredentials(options?: RemoveOptions): OperationResult;
11
+ export { installOAuthCredentials, removeGeminiCredentials };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Gemini credential installation operations.
3
+ *
4
+ * Extracted from gemini.ts to reduce file complexity.
5
+ */
6
+ import path from "node:path";
7
+ import { ensureDirectory } from "../file-storage.js";
8
+ import { isMacOS } from "../keychain.js";
9
+ import { resolveCustomDirectory } from "../validate-directories.js";
10
+ import { clearAuthTypeFromSettings, deleteKeychainCreds, deleteOAuthCreds, getDefaultOAuthPath, saveKeychainCreds, saveOAuthCreds, setAuthTypeInSettings, } from "./gemini-storage.js";
11
+ const AGENT_ID = "gemini";
12
+ const CREDS_FILE_NAME = "oauth_creds.json";
13
+ /** Install OAuth credentials to storage */
14
+ function installOAuthCredentials(oauthData, source, options) {
15
+ // Resolve custom directory with validation
16
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
17
+ if (!resolved.ok)
18
+ return resolved.error;
19
+ // Custom directory forces file storage
20
+ if (resolved.customDir) {
21
+ return installToCustomDirectory(oauthData, resolved.customDir);
22
+ }
23
+ // Default location: use storage option or _source marker
24
+ const targetStorage = options?.storage ?? (source === "keychain" ? "keychain" : "file");
25
+ if (targetStorage === "keychain") {
26
+ return installToKeychain(oauthData);
27
+ }
28
+ return installToDefaultFile(oauthData);
29
+ }
30
+ /** Install to custom directory */
31
+ function installToCustomDirectory(oauthData, customDirectory) {
32
+ if (!ensureDirectory(customDirectory)) {
33
+ return {
34
+ ok: false,
35
+ message: `Failed to create directory ${customDirectory}`,
36
+ };
37
+ }
38
+ const targetPath = path.join(customDirectory, CREDS_FILE_NAME);
39
+ if (!saveOAuthCreds(oauthData, targetPath)) {
40
+ return {
41
+ ok: false,
42
+ message: `Failed to install credentials to ${targetPath}`,
43
+ };
44
+ }
45
+ if (!setAuthTypeInSettings("oauth-personal", customDirectory)) {
46
+ return { ok: false, message: "Failed to update settings" };
47
+ }
48
+ return { ok: true, message: `Installed credentials to ${targetPath}` };
49
+ }
50
+ /** Install to macOS Keychain */
51
+ function installToKeychain(oauthData) {
52
+ if (!isMacOS()) {
53
+ return {
54
+ ok: false,
55
+ message: "Keychain storage is only available on macOS",
56
+ };
57
+ }
58
+ if (!saveKeychainCreds(oauthData)) {
59
+ return { ok: false, message: "Failed to save to macOS Keychain" };
60
+ }
61
+ if (!setAuthTypeInSettings("oauth-personal")) {
62
+ return { ok: false, message: "Failed to update settings" };
63
+ }
64
+ return { ok: true, message: "Installed credentials to macOS Keychain" };
65
+ }
66
+ /** Install to default file location */
67
+ function installToDefaultFile(oauthData) {
68
+ const targetPath = getDefaultOAuthPath();
69
+ const targetDirectory = path.dirname(targetPath);
70
+ if (!ensureDirectory(targetDirectory)) {
71
+ return {
72
+ ok: false,
73
+ message: `Failed to create directory ${targetDirectory}`,
74
+ };
75
+ }
76
+ if (!saveOAuthCreds(oauthData, targetPath)) {
77
+ return { ok: false, message: "Failed to write OAuth credentials" };
78
+ }
79
+ if (!setAuthTypeInSettings("oauth-personal")) {
80
+ return { ok: false, message: "Failed to update settings" };
81
+ }
82
+ return { ok: true, message: `Installed credentials to ${targetPath}` };
83
+ }
84
+ /** Remove credentials from storage */
85
+ function removeGeminiCredentials(options) {
86
+ // Resolve custom directory with validation
87
+ const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
88
+ if (!resolved.ok)
89
+ return resolved.error;
90
+ // Custom directory: only remove that specific file
91
+ if (resolved.customDir) {
92
+ const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
93
+ if (deleteOAuthCreds(targetPath)) {
94
+ return { ok: true, message: `Removed ${targetPath}` };
95
+ }
96
+ return { ok: true, message: "No credentials file found at specified path" };
97
+ }
98
+ // Default location: remove from keychain, file, and clear settings
99
+ const removedFrom = [];
100
+ if (deleteKeychainCreds()) {
101
+ removedFrom.push("macOS Keychain");
102
+ }
103
+ if (deleteOAuthCreds()) {
104
+ removedFrom.push(getDefaultOAuthPath());
105
+ }
106
+ // Always clear auth type setting to avoid stale configuration
107
+ if (clearAuthTypeFromSettings()) {
108
+ removedFrom.push("auth type from settings");
109
+ }
110
+ if (removedFrom.length === 0) {
111
+ return { ok: true, message: "No credentials found" };
112
+ }
113
+ return { ok: true, message: `Removed from ${removedFrom.join(" and ")}` };
114
+ }
115
+ export { installOAuthCredentials, removeGeminiCredentials };
@@ -3,12 +3,10 @@
3
3
  */
4
4
  /** Get default OAuth credentials file path */
5
5
  declare function getDefaultOAuthPath(): string;
6
- /** Get current auth type from settings */
7
- declare function getAuthType(): string | undefined;
8
6
  /** Clear auth type from settings */
9
7
  declare function clearAuthTypeFromSettings(): boolean;
10
8
  /** Set auth type in settings */
11
- declare function setAuthTypeInSettings(authType: string): boolean;
9
+ declare function setAuthTypeInSettings(authType: string, configDirectory?: string): boolean;
12
10
  /** Load OAuth credentials from file */
13
11
  declare function loadOAuthCreds(): Record<string, unknown> | undefined;
14
12
  /** Load OAuth credentials from keychain */
@@ -21,4 +19,4 @@ declare function deleteKeychainCreds(): boolean;
21
19
  declare function saveOAuthCreds(data: Record<string, unknown>, filePath?: string): boolean;
22
20
  /** Delete OAuth credentials file */
23
21
  declare function deleteOAuthCreds(filePath?: string): boolean;
24
- export { clearAuthTypeFromSettings, deleteKeychainCreds, deleteOAuthCreds, getAuthType, getDefaultOAuthPath, loadKeychainCreds, loadOAuthCreds, saveKeychainCreds, saveOAuthCreds, setAuthTypeInSettings, };
22
+ export { clearAuthTypeFromSettings, deleteKeychainCreds, deleteOAuthCreds, getDefaultOAuthPath, loadKeychainCreds, loadOAuthCreds, saveKeychainCreds, saveOAuthCreds, setAuthTypeInSettings, };
@@ -10,28 +10,20 @@ const KEYCHAIN_SERVICE = "gemini-cli-oauth";
10
10
  const KEYCHAIN_ACCOUNT = "main-account";
11
11
  /** Get the Gemini config directory */
12
12
  function getConfigDirectory() {
13
- return getResolvedConfigDirectory(geminiConfigReader);
13
+ return getResolvedConfigDirectory("gemini");
14
14
  }
15
15
  /** Get default OAuth credentials file path */
16
16
  function getDefaultOAuthPath() {
17
17
  return path.join(getConfigDirectory(), "oauth_creds.json");
18
18
  }
19
- /** Get current auth type from settings */
20
- function getAuthType() {
21
- const result = geminiConfigReader.readRaw(getConfigDirectory(), "security.auth.selectedType");
22
- if (result.ok && typeof result.value === "string") {
23
- return result.value;
24
- }
25
- return undefined;
26
- }
27
19
  /** Clear auth type from settings */
28
20
  function clearAuthTypeFromSettings() {
29
21
  const result = geminiConfigReader.deleteRaw(getConfigDirectory(), "security.auth.selectedType");
30
22
  return result.ok && result.deleted;
31
23
  }
32
24
  /** Set auth type in settings */
33
- function setAuthTypeInSettings(authType) {
34
- const result = geminiConfigReader.writeRaw(getConfigDirectory(), "security.auth.selectedType", authType);
25
+ function setAuthTypeInSettings(authType, configDirectory) {
26
+ const result = geminiConfigReader.writeRaw(configDirectory ?? getConfigDirectory(), "security.auth.selectedType", authType);
35
27
  return result.ok;
36
28
  }
37
29
  /** Load OAuth credentials from file */
@@ -66,4 +58,4 @@ function saveOAuthCreds(data, filePath) {
66
58
  function deleteOAuthCreds(filePath) {
67
59
  return deleteFile(filePath ?? getDefaultOAuthPath());
68
60
  }
69
- export { clearAuthTypeFromSettings, deleteKeychainCreds, deleteOAuthCreds, getAuthType, getDefaultOAuthPath, loadKeychainCreds, loadOAuthCreds, saveKeychainCreds, saveOAuthCreds, setAuthTypeInSettings, };
61
+ export { clearAuthTypeFromSettings, deleteKeychainCreds, deleteOAuthCreds, getDefaultOAuthPath, loadKeychainCreds, loadOAuthCreds, saveKeychainCreds, saveOAuthCreds, setAuthTypeInSettings, };