axauth 1.1.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.
- package/dist/auth/adapter.d.ts +29 -9
- package/dist/auth/agents/{claude-code-storage.d.ts → claude-storage.d.ts} +13 -5
- package/dist/auth/agents/{claude-code-storage.js → claude-storage.js} +50 -1
- package/dist/auth/agents/{claude-code.js → claude.js} +22 -58
- package/dist/auth/agents/codex.js +17 -8
- package/dist/auth/agents/copilot.js +15 -6
- package/dist/auth/agents/gemini-install.d.ts +11 -0
- package/dist/auth/agents/gemini-install.js +115 -0
- package/dist/auth/agents/gemini.js +3 -88
- package/dist/auth/agents/opencode.d.ts +4 -0
- package/dist/auth/agents/opencode.js +28 -11
- package/dist/auth/registry.js +1 -1
- package/dist/auth/validate-directories.d.ts +25 -0
- package/dist/auth/validate-directories.js +29 -0
- package/dist/cli.js +8 -6
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +4 -1
- package/dist/commands/install-credentials.d.ts +1 -0
- package/dist/commands/install-credentials.js +1 -0
- package/package.json +3 -3
- /package/dist/auth/agents/{claude-code.d.ts → claude.d.ts} +0 -0
package/dist/auth/adapter.d.ts
CHANGED
|
@@ -16,33 +16,53 @@ interface OperationResult {
|
|
|
16
16
|
/**
|
|
17
17
|
* Options for credential installation.
|
|
18
18
|
*
|
|
19
|
-
* When
|
|
20
|
-
*
|
|
19
|
+
* When custom directories are provided, credentials are always written as files
|
|
20
|
+
* (keychain is only for default location). The `storage` option is ignored.
|
|
21
21
|
*
|
|
22
|
-
* When
|
|
22
|
+
* When no directories are provided, `storage` controls where credentials go:
|
|
23
23
|
* - `"keychain"`: Store in macOS Keychain (if supported)
|
|
24
24
|
* - `"file"`: Store in agent's default file location
|
|
25
25
|
* - `undefined`: Use the `_source` marker in credentials, or default to file
|
|
26
|
+
*
|
|
27
|
+
* **Agents without separation** (Claude, Codex, Gemini, Copilot):
|
|
28
|
+
* - Config and data are stored in the same directory
|
|
29
|
+
* - `configDir` and `dataDir` are interchangeable (either works)
|
|
30
|
+
* - Providing different values for both will result in an error
|
|
31
|
+
*
|
|
32
|
+
* **Agents with separation** (OpenCode):
|
|
33
|
+
* - Config and data are stored in separate directories
|
|
34
|
+
* - `dataDir` controls where credentials go; `configDir` is for settings
|
|
35
|
+
* - If only `configDir` provided, credentials use default data location
|
|
36
|
+
* - If only `dataDir` provided, config uses default location
|
|
26
37
|
*/
|
|
27
38
|
interface InstallOptions {
|
|
28
|
-
/** Storage type for default location (ignored if configDir is set) */
|
|
39
|
+
/** Storage type for default location (ignored if configDir/dataDir is set) */
|
|
29
40
|
storage?: StorageType;
|
|
30
|
-
/** Custom config directory
|
|
41
|
+
/** Custom config directory for settings/preferences */
|
|
31
42
|
configDir?: string;
|
|
43
|
+
/** Custom data directory for credentials */
|
|
44
|
+
dataDir?: string;
|
|
32
45
|
}
|
|
33
46
|
/**
|
|
34
47
|
* Options for credential removal.
|
|
35
48
|
*
|
|
36
|
-
* When `
|
|
49
|
+
* When `dataDir` is provided, only the credentials file in that directory
|
|
37
50
|
* is removed. Keychain credentials are not affected (keychain is only for
|
|
38
51
|
* default location).
|
|
39
52
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
53
|
+
* **Agents without separation** (Claude, Codex, Gemini, Copilot):
|
|
54
|
+
* - `configDir` and `dataDir` are interchangeable
|
|
55
|
+
* - Either option specifies where to remove credentials from
|
|
56
|
+
*
|
|
57
|
+
* **Agents with separation** (OpenCode):
|
|
58
|
+
* - `dataDir` specifies where to remove credentials from
|
|
59
|
+
* - If only `configDir` provided, credentials are removed from default location
|
|
42
60
|
*/
|
|
43
61
|
interface RemoveOptions {
|
|
44
|
-
/** Custom config directory
|
|
62
|
+
/** Custom config directory for settings/preferences */
|
|
45
63
|
configDir?: string;
|
|
64
|
+
/** Custom data directory for credentials */
|
|
65
|
+
dataDir?: string;
|
|
46
66
|
}
|
|
47
67
|
/**
|
|
48
68
|
* Options for token extraction.
|
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/** Get default credentials file path */
|
|
5
5
|
declare function getDefaultCredsFilePath(): string;
|
|
6
|
-
/** Load OAuth credentials from keychain */
|
|
7
|
-
declare function loadKeychainCreds(): Record<string, unknown> | undefined;
|
|
8
|
-
/** Load OAuth credentials from file */
|
|
9
|
-
declare function loadFileCreds(): Record<string, unknown> | undefined;
|
|
10
6
|
/** Save credentials to keychain */
|
|
11
7
|
declare function saveKeychainCreds(oauthData: Record<string, unknown>): boolean;
|
|
12
8
|
/** Save credentials to file */
|
|
@@ -15,4 +11,16 @@ declare function saveFileCreds(oauthData: Record<string, unknown>, filePath: str
|
|
|
15
11
|
declare function deleteKeychainCreds(): boolean;
|
|
16
12
|
/** Delete credentials from file */
|
|
17
13
|
declare function deleteFileCreds(filePath?: string): boolean;
|
|
18
|
-
|
|
14
|
+
declare const AGENT_ID: "claude";
|
|
15
|
+
/** Check if Claude is authenticated and return status */
|
|
16
|
+
declare function checkAuth(): {
|
|
17
|
+
authenticated: boolean;
|
|
18
|
+
method?: string;
|
|
19
|
+
details?: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
/** Extract raw credentials from available sources */
|
|
22
|
+
declare function extractRawCredentials(): {
|
|
23
|
+
type: "oauth" | "api-key";
|
|
24
|
+
data: Record<string, unknown>;
|
|
25
|
+
} | undefined;
|
|
26
|
+
export { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, };
|
|
@@ -62,4 +62,53 @@ function deleteKeychainCreds() {
|
|
|
62
62
|
function deleteFileCreds(filePath) {
|
|
63
63
|
return deleteFile(filePath ?? getDefaultCredsFilePath());
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
const AGENT_ID = "claude";
|
|
66
|
+
/** Check if Claude is authenticated and return status */
|
|
67
|
+
function checkAuth() {
|
|
68
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
69
|
+
return { authenticated: true, method: "OAuth (env)" };
|
|
70
|
+
}
|
|
71
|
+
const keychainCreds = loadKeychainCreds();
|
|
72
|
+
if (keychainCreds) {
|
|
73
|
+
const subType = keychainCreds.subscriptionType;
|
|
74
|
+
return { authenticated: true, method: `OAuth (${subType ?? "keychain"})` };
|
|
75
|
+
}
|
|
76
|
+
if (loadFileCreds()) {
|
|
77
|
+
return { authenticated: true, method: "OAuth (file)" };
|
|
78
|
+
}
|
|
79
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
80
|
+
return { authenticated: true, method: "API key" };
|
|
81
|
+
}
|
|
82
|
+
return { authenticated: false };
|
|
83
|
+
}
|
|
84
|
+
/** Extract raw credentials from available sources */
|
|
85
|
+
function extractRawCredentials() {
|
|
86
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
87
|
+
return {
|
|
88
|
+
type: "oauth",
|
|
89
|
+
data: { accessToken: process.env.CLAUDE_CODE_OAUTH_TOKEN },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const keychainCreds = loadKeychainCreds();
|
|
93
|
+
if (keychainCreds) {
|
|
94
|
+
return {
|
|
95
|
+
type: "oauth",
|
|
96
|
+
data: { ...keychainCreds, _source: "keychain" },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const fileCreds = loadFileCreds();
|
|
100
|
+
if (fileCreds) {
|
|
101
|
+
return {
|
|
102
|
+
type: "oauth",
|
|
103
|
+
data: { ...fileCreds, _source: "file" },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
107
|
+
return {
|
|
108
|
+
type: "api-key",
|
|
109
|
+
data: { apiKey: process.env.ANTHROPIC_API_KEY },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
export { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, };
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { isMacOS } from "../keychain.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
9
|
+
import { AGENT_ID, checkAuth, deleteFileCreds, deleteKeychainCreds, extractRawCredentials, getDefaultCredsFilePath, saveFileCreds, saveKeychainCreds, } from "./claude-storage.js";
|
|
10
10
|
const CREDS_FILE_NAME = ".credentials.json";
|
|
11
11
|
/** Claude Code authentication adapter */
|
|
12
12
|
const claudeCodeAdapter = {
|
|
@@ -18,60 +18,20 @@ const claudeCodeAdapter = {
|
|
|
18
18
|
installApiKey: false,
|
|
19
19
|
},
|
|
20
20
|
checkAuth() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const keychainCreds = loadKeychainCreds();
|
|
25
|
-
if (keychainCreds) {
|
|
26
|
-
const subType = keychainCreds.subscriptionType;
|
|
27
|
-
return {
|
|
28
|
-
agentId: AGENT_ID,
|
|
29
|
-
authenticated: true,
|
|
30
|
-
method: `OAuth (${subType ?? "keychain"})`,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
if (loadFileCreds()) {
|
|
34
|
-
return { agentId: AGENT_ID, authenticated: true, method: "OAuth (file)" };
|
|
35
|
-
}
|
|
36
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
37
|
-
return { agentId: AGENT_ID, authenticated: true, method: "API key" };
|
|
38
|
-
}
|
|
39
|
-
return { agentId: AGENT_ID, authenticated: false };
|
|
21
|
+
const result = checkAuth();
|
|
22
|
+
return { agentId: AGENT_ID, ...result };
|
|
40
23
|
},
|
|
41
24
|
extractRawCredentials() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
data: { accessToken: process.env.CLAUDE_CODE_OAUTH_TOKEN },
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
const keychainCreds = loadKeychainCreds();
|
|
50
|
-
if (keychainCreds) {
|
|
51
|
-
return {
|
|
52
|
-
agent: AGENT_ID,
|
|
53
|
-
type: "oauth",
|
|
54
|
-
data: { ...keychainCreds, _source: "keychain" },
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
const fileCreds = loadFileCreds();
|
|
58
|
-
if (fileCreds) {
|
|
59
|
-
return {
|
|
60
|
-
agent: AGENT_ID,
|
|
61
|
-
type: "oauth",
|
|
62
|
-
data: { ...fileCreds, _source: "file" },
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
66
|
-
return {
|
|
67
|
-
agent: AGENT_ID,
|
|
68
|
-
type: "api-key",
|
|
69
|
-
data: { apiKey: process.env.ANTHROPIC_API_KEY },
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return undefined;
|
|
25
|
+
const result = extractRawCredentials();
|
|
26
|
+
if (!result)
|
|
27
|
+
return undefined;
|
|
28
|
+
return { agent: AGENT_ID, ...result };
|
|
73
29
|
},
|
|
74
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;
|
|
75
35
|
if (creds.type === "api-key") {
|
|
76
36
|
return {
|
|
77
37
|
ok: false,
|
|
@@ -79,9 +39,9 @@ const claudeCodeAdapter = {
|
|
|
79
39
|
};
|
|
80
40
|
}
|
|
81
41
|
const { _source, ...oauthData } = creds.data;
|
|
82
|
-
// Custom
|
|
83
|
-
if (
|
|
84
|
-
const targetPath = path.join(
|
|
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);
|
|
85
45
|
if (saveFileCreds(oauthData, targetPath)) {
|
|
86
46
|
return {
|
|
87
47
|
ok: true,
|
|
@@ -117,9 +77,13 @@ const claudeCodeAdapter = {
|
|
|
117
77
|
};
|
|
118
78
|
},
|
|
119
79
|
removeCredentials(options) {
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
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);
|
|
123
87
|
if (deleteFileCreds(targetPath)) {
|
|
124
88
|
return { ok: true, message: `Removed ${targetPath}` };
|
|
125
89
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
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";
|
|
@@ -23,16 +24,20 @@ const codexAdapter = {
|
|
|
23
24
|
checkAuth,
|
|
24
25
|
extractRawCredentials,
|
|
25
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;
|
|
26
31
|
const authContent = buildAuthContent(creds.type, creds.data);
|
|
27
|
-
// Custom
|
|
28
|
-
if (
|
|
29
|
-
if (!ensureDirectory(
|
|
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 ${
|
|
37
|
+
message: `Failed to create directory ${resolved.customDir}`,
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
|
-
const targetPath = path.join(
|
|
40
|
+
const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
|
|
36
41
|
if (saveFileCreds(authContent, targetPath)) {
|
|
37
42
|
return {
|
|
38
43
|
ok: true,
|
|
@@ -73,9 +78,13 @@ const codexAdapter = {
|
|
|
73
78
|
return { ok: false, message: "Failed to install credentials to file" };
|
|
74
79
|
},
|
|
75
80
|
removeCredentials(options) {
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
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);
|
|
79
88
|
if (deleteFileCreds(targetPath)) {
|
|
80
89
|
return { ok: true, message: `Removed ${targetPath}` };
|
|
81
90
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { ensureDirectory } from "../file-storage.js";
|
|
9
9
|
import { isMacOS } from "../keychain.js";
|
|
10
|
+
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
10
11
|
import { findFirstAvailableToken } from "./copilot-auth-check.js";
|
|
11
12
|
import { deleteFileToken, deleteKeychainToken, deleteTokenFromPath, getConfigDirectory, getConfigFilePath, saveFileToken, saveKeychainToken, saveTokenToPath, } from "./copilot-storage.js";
|
|
12
13
|
const AGENT_ID = "copilot";
|
|
@@ -48,6 +49,10 @@ const copilotAdapter = {
|
|
|
48
49
|
};
|
|
49
50
|
},
|
|
50
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;
|
|
51
56
|
if (creds.type === "api-key") {
|
|
52
57
|
return {
|
|
53
58
|
ok: false,
|
|
@@ -60,9 +65,9 @@ const copilotAdapter = {
|
|
|
60
65
|
if (!token) {
|
|
61
66
|
return { ok: false, message: "No access token found in credentials" };
|
|
62
67
|
}
|
|
63
|
-
// Custom
|
|
64
|
-
if (
|
|
65
|
-
const targetPath = path.join(
|
|
68
|
+
// Custom directory forces file storage
|
|
69
|
+
if (resolved.customDir) {
|
|
70
|
+
const targetPath = path.join(resolved.customDir, CREDS_FILE_NAME);
|
|
66
71
|
if (saveTokenToPath(token, targetPath)) {
|
|
67
72
|
return {
|
|
68
73
|
ok: true,
|
|
@@ -105,9 +110,13 @@ const copilotAdapter = {
|
|
|
105
110
|
return { ok: false, message: "Failed to install credentials to file" };
|
|
106
111
|
},
|
|
107
112
|
removeCredentials(options) {
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
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);
|
|
111
120
|
if (deleteTokenFromPath(targetPath)) {
|
|
112
121
|
return { ok: true, message: `Removed ${targetPath}` };
|
|
113
122
|
}
|
|
@@ -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,13 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Supports OAuth via keychain (macOS) or file. API key auth is env-var only.
|
|
5
5
|
*/
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { ensureDirectory } from "../file-storage.js";
|
|
8
|
-
import { isMacOS } from "../keychain.js";
|
|
9
6
|
import { findCredentials } from "./gemini-auth-check.js";
|
|
10
|
-
import {
|
|
7
|
+
import { installOAuthCredentials, removeGeminiCredentials, } from "./gemini-install.js";
|
|
11
8
|
const AGENT_ID = "gemini";
|
|
12
|
-
const CREDS_FILE_NAME = "oauth_creds.json";
|
|
13
9
|
/** Gemini authentication adapter */
|
|
14
10
|
const geminiAdapter = {
|
|
15
11
|
agentId: AGENT_ID,
|
|
@@ -60,91 +56,10 @@ const geminiAdapter = {
|
|
|
60
56
|
};
|
|
61
57
|
}
|
|
62
58
|
const { _source, ...oauthData } = creds.data;
|
|
63
|
-
|
|
64
|
-
if (options?.configDir) {
|
|
65
|
-
if (!ensureDirectory(options.configDir)) {
|
|
66
|
-
return {
|
|
67
|
-
ok: false,
|
|
68
|
-
message: `Failed to create directory ${options.configDir}`,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
const targetPath = path.join(options.configDir, CREDS_FILE_NAME);
|
|
72
|
-
if (saveOAuthCreds(oauthData, targetPath)) {
|
|
73
|
-
// Also set auth type in settings.json at the target directory
|
|
74
|
-
if (!setAuthTypeInSettings("oauth-personal", options.configDir)) {
|
|
75
|
-
return { ok: false, message: "Failed to update settings" };
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
ok: true,
|
|
79
|
-
message: `Installed credentials to ${targetPath}`,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
ok: false,
|
|
84
|
-
message: `Failed to install credentials to ${targetPath}`,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
// Default location: use storage option or _source marker
|
|
88
|
-
const targetStorage = options?.storage ?? (_source === "keychain" ? "keychain" : "file");
|
|
89
|
-
if (targetStorage === "keychain") {
|
|
90
|
-
if (!isMacOS()) {
|
|
91
|
-
return {
|
|
92
|
-
ok: false,
|
|
93
|
-
message: "Keychain storage is only available on macOS",
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
if (saveKeychainCreds(oauthData)) {
|
|
97
|
-
if (!setAuthTypeInSettings("oauth-personal")) {
|
|
98
|
-
return { ok: false, message: "Failed to update settings" };
|
|
99
|
-
}
|
|
100
|
-
return { ok: true, message: "Installed credentials to macOS Keychain" };
|
|
101
|
-
}
|
|
102
|
-
return { ok: false, message: "Failed to save to macOS Keychain" };
|
|
103
|
-
}
|
|
104
|
-
// File storage
|
|
105
|
-
const targetPath = getDefaultOAuthPath();
|
|
106
|
-
const targetDirectory = path.dirname(targetPath);
|
|
107
|
-
if (!ensureDirectory(targetDirectory)) {
|
|
108
|
-
return {
|
|
109
|
-
ok: false,
|
|
110
|
-
message: `Failed to create directory ${targetDirectory}`,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
if (!saveOAuthCreds(oauthData, targetPath)) {
|
|
114
|
-
return { ok: false, message: "Failed to write OAuth credentials" };
|
|
115
|
-
}
|
|
116
|
-
if (!setAuthTypeInSettings("oauth-personal")) {
|
|
117
|
-
return { ok: false, message: "Failed to update settings" };
|
|
118
|
-
}
|
|
119
|
-
return { ok: true, message: `Installed credentials to ${targetPath}` };
|
|
59
|
+
return installOAuthCredentials(oauthData, _source, options);
|
|
120
60
|
},
|
|
121
61
|
removeCredentials(options) {
|
|
122
|
-
|
|
123
|
-
if (options?.configDir) {
|
|
124
|
-
const targetPath = path.join(options.configDir, CREDS_FILE_NAME);
|
|
125
|
-
if (deleteOAuthCreds(targetPath)) {
|
|
126
|
-
return { ok: true, message: `Removed ${targetPath}` };
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
ok: true,
|
|
130
|
-
message: "No credentials file found at specified path",
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
// Default location: remove from keychain, file, and clear settings
|
|
134
|
-
const removedFrom = [];
|
|
135
|
-
if (deleteKeychainCreds()) {
|
|
136
|
-
removedFrom.push("macOS Keychain");
|
|
137
|
-
}
|
|
138
|
-
if (deleteOAuthCreds()) {
|
|
139
|
-
removedFrom.push(getDefaultOAuthPath());
|
|
140
|
-
}
|
|
141
|
-
if (clearAuthTypeFromSettings()) {
|
|
142
|
-
removedFrom.push("auth type from settings.json");
|
|
143
|
-
}
|
|
144
|
-
if (removedFrom.length === 0) {
|
|
145
|
-
return { ok: true, message: "No credentials found" };
|
|
146
|
-
}
|
|
147
|
-
return { ok: true, message: `Removed from ${removedFrom.join(", ")}` };
|
|
62
|
+
return removeGeminiCredentials(options);
|
|
148
63
|
},
|
|
149
64
|
getAccessToken(creds) {
|
|
150
65
|
const data = creds.data;
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* OpenCode auth adapter.
|
|
3
3
|
*
|
|
4
4
|
* Supports multi-provider auth via file only. No keychain or env var support.
|
|
5
|
+
*
|
|
6
|
+
* OpenCode follows XDG Base Directory Specification:
|
|
7
|
+
* - Config: ~/.config/opencode (XDG_CONFIG_HOME/opencode)
|
|
8
|
+
* - Data (auth): ~/.local/share/opencode (XDG_DATA_HOME/opencode)
|
|
5
9
|
*/
|
|
6
10
|
import type { AuthAdapter } from "../adapter.js";
|
|
7
11
|
/** OpenCode authentication adapter */
|
|
@@ -2,16 +2,28 @@
|
|
|
2
2
|
* OpenCode auth adapter.
|
|
3
3
|
*
|
|
4
4
|
* Supports multi-provider auth via file only. No keychain or env var support.
|
|
5
|
+
*
|
|
6
|
+
* OpenCode follows XDG Base Directory Specification:
|
|
7
|
+
* - Config: ~/.config/opencode (XDG_CONFIG_HOME/opencode)
|
|
8
|
+
* - Data (auth): ~/.local/share/opencode (XDG_DATA_HOME/opencode)
|
|
5
9
|
*/
|
|
6
10
|
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
11
|
import path from "node:path";
|
|
12
|
+
import { resolveAgentDataDirectory } from "axshared";
|
|
13
|
+
import { resolveCustomDirectory } from "../validate-directories.js";
|
|
9
14
|
import { extractTokenFromEntry, OpenCodeAuth } from "./opencode-schema.js";
|
|
10
15
|
const AGENT_ID = "opencode";
|
|
11
16
|
const CREDS_FILE_NAME = "auth.json";
|
|
17
|
+
/**
|
|
18
|
+
* Gets the default auth file path for OpenCode.
|
|
19
|
+
*
|
|
20
|
+
* Uses axshared's resolveAgentDataDirectory which respects:
|
|
21
|
+
* 1. OPENCODE_DATA_DIR environment variable
|
|
22
|
+
* 2. XDG_DATA_HOME/opencode (defaults to ~/.local/share/opencode)
|
|
23
|
+
*/
|
|
12
24
|
function getDefaultAuthFilePath() {
|
|
13
|
-
const
|
|
14
|
-
return path.join(
|
|
25
|
+
const dataDirectory = resolveAgentDataDirectory(AGENT_ID);
|
|
26
|
+
return path.join(dataDirectory, CREDS_FILE_NAME);
|
|
15
27
|
}
|
|
16
28
|
/** OpenCode authentication adapter */
|
|
17
29
|
const opencodeAdapter = {
|
|
@@ -71,11 +83,13 @@ const opencodeAdapter = {
|
|
|
71
83
|
return undefined;
|
|
72
84
|
},
|
|
73
85
|
installCredentials(creds, options) {
|
|
86
|
+
// Resolve custom directory with validation (OpenCode supports separation)
|
|
87
|
+
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
88
|
+
if (!resolved.ok)
|
|
89
|
+
return resolved.error;
|
|
74
90
|
// OpenCode only supports file storage (no keychain)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const targetPath = options?.configDir
|
|
78
|
-
? path.join(options.configDir, CREDS_FILE_NAME)
|
|
91
|
+
const targetPath = resolved.customDir
|
|
92
|
+
? path.join(resolved.customDir, CREDS_FILE_NAME)
|
|
79
93
|
: getDefaultAuthFilePath();
|
|
80
94
|
const targetDirectory = path.dirname(targetPath);
|
|
81
95
|
try {
|
|
@@ -100,10 +114,13 @@ const opencodeAdapter = {
|
|
|
100
114
|
}
|
|
101
115
|
},
|
|
102
116
|
removeCredentials(options) {
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
// Resolve custom directory with validation (OpenCode supports separation)
|
|
118
|
+
const resolved = resolveCustomDirectory(AGENT_ID, options?.configDir, options?.dataDir);
|
|
119
|
+
if (!resolved.ok)
|
|
120
|
+
return resolved.error;
|
|
121
|
+
// Use resolved custom directory or fall back to default
|
|
122
|
+
const targetPath = resolved.customDir
|
|
123
|
+
? path.join(resolved.customDir, CREDS_FILE_NAME)
|
|
107
124
|
: getDefaultAuthFilePath();
|
|
108
125
|
if (!existsSync(targetPath)) {
|
|
109
126
|
return { ok: true, message: "No credentials file found" };
|
package/dist/auth/registry.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* delegating to the appropriate adapter based on agent ID.
|
|
6
6
|
*/
|
|
7
7
|
import { fromBase64, tryDecrypt } from "../crypto.js";
|
|
8
|
-
import { claudeCodeAdapter } from "./agents/claude
|
|
8
|
+
import { claudeCodeAdapter } from "./agents/claude.js";
|
|
9
9
|
import { codexAdapter } from "./agents/codex.js";
|
|
10
10
|
import { copilotAdapter } from "./agents/copilot.js";
|
|
11
11
|
import { geminiAdapter } from "./agents/gemini.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory validation for auth adapters.
|
|
3
|
+
*
|
|
4
|
+
* Wraps axshared's resolveCustomDirectories for use in auth adapters.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentCli } from "axshared";
|
|
7
|
+
import type { OperationResult } from "./adapter.js";
|
|
8
|
+
/** Result of resolving custom directory with validation */
|
|
9
|
+
type ResolveResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
customDir: string | undefined;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: OperationResult;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Validate directory options and resolve the custom data directory to use.
|
|
18
|
+
*
|
|
19
|
+
* Wraps axshared's resolveCustomDirectories, adapting the result for auth
|
|
20
|
+
* adapters and printing warnings to stderr.
|
|
21
|
+
*
|
|
22
|
+
* @see resolveCustomDirectories in axshared for behavior details
|
|
23
|
+
*/
|
|
24
|
+
declare function resolveCustomDirectory(agentId: AgentCli, configDirectory: string | undefined, dataDirectory: string | undefined): ResolveResult;
|
|
25
|
+
export { resolveCustomDirectory };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory validation for auth adapters.
|
|
3
|
+
*
|
|
4
|
+
* Wraps axshared's resolveCustomDirectories for use in auth adapters.
|
|
5
|
+
*/
|
|
6
|
+
import { resolveCustomDirectories } from "axshared";
|
|
7
|
+
/**
|
|
8
|
+
* Validate directory options and resolve the custom data directory to use.
|
|
9
|
+
*
|
|
10
|
+
* Wraps axshared's resolveCustomDirectories, adapting the result for auth
|
|
11
|
+
* adapters and printing warnings to stderr.
|
|
12
|
+
*
|
|
13
|
+
* @see resolveCustomDirectories in axshared for behavior details
|
|
14
|
+
*/
|
|
15
|
+
function resolveCustomDirectory(agentId, configDirectory, dataDirectory) {
|
|
16
|
+
const result = resolveCustomDirectories(agentId, configDirectory, dataDirectory);
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: { ok: false, message: result.error },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Print warning to stderr if present
|
|
24
|
+
if (result.warning) {
|
|
25
|
+
console.error(`Warning: ${result.warning}`);
|
|
26
|
+
}
|
|
27
|
+
return { ok: true, customDir: result.dataDir };
|
|
28
|
+
}
|
|
29
|
+
export { resolveCustomDirectory };
|
package/dist/cli.js
CHANGED
|
@@ -61,12 +61,13 @@ program
|
|
|
61
61
|
.command("remove-credentials")
|
|
62
62
|
.description("Remove credentials from storage")
|
|
63
63
|
.requiredOption("-a, --agent <agent>", `Agent to remove credentials from (${AGENT_CLIS.join(", ")})`)
|
|
64
|
-
.option("--config-dir <dir>", "Custom config directory (
|
|
65
|
-
.option("--
|
|
64
|
+
.option("--config-dir <dir>", "Custom config directory (for agents without separation, also sets data location)")
|
|
65
|
+
.option("--data-dir <dir>", "Custom data directory for credentials")
|
|
66
66
|
.action((options) => {
|
|
67
67
|
handleAuthRemove({
|
|
68
68
|
...options,
|
|
69
|
-
configDir: options.configDir
|
|
69
|
+
configDir: options.configDir,
|
|
70
|
+
dataDir: options.dataDir,
|
|
70
71
|
});
|
|
71
72
|
});
|
|
72
73
|
program
|
|
@@ -75,13 +76,14 @@ program
|
|
|
75
76
|
.requiredOption("-a, --agent <agent>", `Agent to install credentials for (${AGENT_CLIS.join(", ")})`)
|
|
76
77
|
.requiredOption("--input <file>", "Encrypted credentials file path")
|
|
77
78
|
.option("--no-password", "Use default password (no prompt)")
|
|
78
|
-
.option("--config-dir <dir>", "Custom config directory (
|
|
79
|
-
.option("--
|
|
79
|
+
.option("--config-dir <dir>", "Custom config directory (for agents without separation, also sets data location)")
|
|
80
|
+
.option("--data-dir <dir>", "Custom data directory for credentials")
|
|
80
81
|
.option("--storage <type>", "Storage type: keychain (macOS only) or file (for default location)")
|
|
81
82
|
.action((options) => {
|
|
82
83
|
void handleAuthInstall({
|
|
83
84
|
...options,
|
|
84
|
-
configDir: options.configDir
|
|
85
|
+
configDir: options.configDir,
|
|
86
|
+
dataDir: options.dataDir,
|
|
85
87
|
});
|
|
86
88
|
});
|
|
87
89
|
await program.parseAsync(process.argv);
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ declare function handleAuthExport(options: AuthExportOptions): Promise<void>;
|
|
|
21
21
|
interface AuthRemoveOptions {
|
|
22
22
|
agent: string;
|
|
23
23
|
configDir?: string;
|
|
24
|
+
dataDir?: string;
|
|
24
25
|
}
|
|
25
26
|
/** Handle auth remove-credentials command */
|
|
26
27
|
declare function handleAuthRemove(options: AuthRemoveOptions): void;
|
package/dist/commands/auth.js
CHANGED
|
@@ -81,7 +81,10 @@ function handleAuthRemove(options) {
|
|
|
81
81
|
const agentId = validateAgent(options.agent);
|
|
82
82
|
if (!agentId)
|
|
83
83
|
return;
|
|
84
|
-
const result = removeCredentials(agentId, {
|
|
84
|
+
const result = removeCredentials(agentId, {
|
|
85
|
+
configDir: options.configDir,
|
|
86
|
+
dataDir: options.dataDir,
|
|
87
|
+
});
|
|
85
88
|
if (result.ok) {
|
|
86
89
|
console.error(result.message);
|
|
87
90
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "axauth",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.2.0",
|
|
6
6
|
"description": "Authentication management library and CLI for AI coding agents",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@commander-js/extra-typings": "^14.0.0",
|
|
71
71
|
"@inquirer/password": "^5.0.3",
|
|
72
|
-
"axconfig": "^3.
|
|
73
|
-
"axshared": "^1.
|
|
72
|
+
"axconfig": "^3.2.0",
|
|
73
|
+
"axshared": "^1.5.0",
|
|
74
74
|
"commander": "^14.0.2",
|
|
75
75
|
"zod": "^4.3.4"
|
|
76
76
|
},
|
|
File without changes
|