axauth 1.0.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/LICENSE +21 -0
- package/README.md +147 -0
- package/bin/axauth +17 -0
- package/dist/auth/adapter.d.ts +121 -0
- package/dist/auth/adapter.js +7 -0
- package/dist/auth/agents/claude-code-storage.d.ts +18 -0
- package/dist/auth/agents/claude-code-storage.js +66 -0
- package/dist/auth/agents/claude-code.d.ts +9 -0
- package/dist/auth/agents/claude-code.js +162 -0
- package/dist/auth/agents/codex-auth-check.d.ts +9 -0
- package/dist/auth/agents/codex-auth-check.js +101 -0
- package/dist/auth/agents/codex-config.d.ts +12 -0
- package/dist/auth/agents/codex-config.js +40 -0
- package/dist/auth/agents/codex-storage.d.ts +43 -0
- package/dist/auth/agents/codex-storage.js +82 -0
- package/dist/auth/agents/codex.d.ts +9 -0
- package/dist/auth/agents/codex.js +126 -0
- package/dist/auth/agents/copilot-auth-check.d.ts +17 -0
- package/dist/auth/agents/copilot-auth-check.js +46 -0
- package/dist/auth/agents/copilot-storage.d.ts +32 -0
- package/dist/auth/agents/copilot-storage.js +139 -0
- package/dist/auth/agents/copilot.d.ts +10 -0
- package/dist/auth/agents/copilot.js +144 -0
- package/dist/auth/agents/gemini-auth-check.d.ts +16 -0
- package/dist/auth/agents/gemini-auth-check.js +73 -0
- package/dist/auth/agents/gemini-storage.d.ts +24 -0
- package/dist/auth/agents/gemini-storage.js +69 -0
- package/dist/auth/agents/gemini.d.ts +9 -0
- package/dist/auth/agents/gemini.js +157 -0
- package/dist/auth/agents/opencode.d.ts +9 -0
- package/dist/auth/agents/opencode.js +128 -0
- package/dist/auth/file-storage.d.ts +14 -0
- package/dist/auth/file-storage.js +63 -0
- package/dist/auth/keychain.d.ts +12 -0
- package/dist/auth/keychain.js +56 -0
- package/dist/auth/registry.d.ts +109 -0
- package/dist/auth/registry.js +145 -0
- package/dist/auth/resolve-config-directory.d.ts +14 -0
- package/dist/auth/resolve-config-directory.js +16 -0
- package/dist/auth/types.d.ts +19 -0
- package/dist/auth/types.js +4 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +74 -0
- package/dist/commands/auth.d.ts +34 -0
- package/dist/commands/auth.js +155 -0
- package/dist/crypto.d.ts +39 -0
- package/dist/crypto.js +78 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +39 -0
- package/package.json +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Łukasz Jerciński
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# axauth
|
|
2
|
+
|
|
3
|
+
Authentication management library and CLI for AI coding agents.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
axauth manages credentials for AI coding agents. It can be used:
|
|
8
|
+
|
|
9
|
+
1. **As a library** - imported to check auth status, extract tokens, and manage credentials programmatically
|
|
10
|
+
2. **As a CLI** - standalone tool for managing agent credentials
|
|
11
|
+
|
|
12
|
+
## Supported Agents
|
|
13
|
+
|
|
14
|
+
- **claude-code** - Claude Code (Anthropic)
|
|
15
|
+
- **codex** - Codex CLI (OpenAI)
|
|
16
|
+
- **gemini** - Gemini CLI (Google)
|
|
17
|
+
- **opencode** - OpenCode
|
|
18
|
+
|
|
19
|
+
## Library API
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { checkAuth, getAgentAccessToken, extractCredentials } from "axauth";
|
|
23
|
+
|
|
24
|
+
// Check auth status for an agent
|
|
25
|
+
const status = checkAuth("claude-code");
|
|
26
|
+
if (status.authenticated) {
|
|
27
|
+
console.log(`Authenticated via ${status.method}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Get access token for API calls
|
|
31
|
+
const token = getAgentAccessToken("claude-code");
|
|
32
|
+
if (token) {
|
|
33
|
+
// Use token for API calls
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extract full credentials for export
|
|
37
|
+
const creds = extractCredentials("claude-code");
|
|
38
|
+
if (creds) {
|
|
39
|
+
// creds.accessToken, creds.refreshToken, etc.
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## CLI Commands
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# List agents and their auth status
|
|
47
|
+
axauth list
|
|
48
|
+
axauth list --json
|
|
49
|
+
|
|
50
|
+
# Get access token for an agent (outputs raw token for piping)
|
|
51
|
+
axauth token --agent claude-code
|
|
52
|
+
|
|
53
|
+
# Export credentials to encrypted file
|
|
54
|
+
axauth export --agent claude-code --output creds.json
|
|
55
|
+
axauth export --agent claude-code --output creds.json --no-password
|
|
56
|
+
|
|
57
|
+
# Remove credentials (agent will prompt for login)
|
|
58
|
+
axauth remove-credentials --agent claude-code
|
|
59
|
+
|
|
60
|
+
# Install credentials from exported file
|
|
61
|
+
axauth install-credentials --agent claude-code --input creds.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Pipeline Examples
|
|
65
|
+
|
|
66
|
+
The CLI outputs TSV format for easy processing with standard Unix tools:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# List all agents and their auth status
|
|
70
|
+
axauth list
|
|
71
|
+
# AGENT STATUS METHOD
|
|
72
|
+
# claude-code authenticated OAuth (max)
|
|
73
|
+
# codex authenticated ChatGPT OAuth
|
|
74
|
+
# ...
|
|
75
|
+
|
|
76
|
+
# Filter to show only authenticated agents
|
|
77
|
+
axauth list | tail -n +2 | awk -F'\t' '$2 == "authenticated"'
|
|
78
|
+
|
|
79
|
+
# Count agents by status
|
|
80
|
+
axauth list | tail -n +2 | cut -f2 | sort | uniq -c
|
|
81
|
+
|
|
82
|
+
# Extract agent names as a list
|
|
83
|
+
axauth list | tail -n +2 | cut -f1
|
|
84
|
+
|
|
85
|
+
# Check if a specific agent is authenticated
|
|
86
|
+
axauth list --json | jq -e '.[] | select(.agentId == "claude-code") | .authenticated'
|
|
87
|
+
|
|
88
|
+
# Check Claude Code usage via OAuth endpoint
|
|
89
|
+
curl -s -H "Authorization: Bearer $(axauth token --agent claude-code)" \
|
|
90
|
+
-H "anthropic-beta: oauth-2025-04-20" \
|
|
91
|
+
https://api.anthropic.com/api/oauth/usage | jq .
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Module Structure
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
src/
|
|
98
|
+
├── types.ts # AgentId type definition
|
|
99
|
+
├── crypto.ts # AES-256-GCM encryption for credentials
|
|
100
|
+
├── cli.ts # CLI entry point
|
|
101
|
+
├── commands/
|
|
102
|
+
│ └── auth.ts # CLI command handlers
|
|
103
|
+
└── auth/
|
|
104
|
+
├── check-auth.ts # Auth status detection
|
|
105
|
+
├── extract-credentials.ts # Credential extraction
|
|
106
|
+
├── get-access-token.ts # Token retrieval
|
|
107
|
+
├── install-credentials.ts # Credential installation
|
|
108
|
+
├── remove-credentials.ts # Credential removal
|
|
109
|
+
├── keychain.ts # macOS Keychain utilities
|
|
110
|
+
├── file-storage.ts # File I/O utilities
|
|
111
|
+
├── types.ts # Auth types
|
|
112
|
+
└── agents/ # Agent-specific implementations
|
|
113
|
+
├── claude-code.ts
|
|
114
|
+
├── codex.ts
|
|
115
|
+
├── gemini.ts
|
|
116
|
+
└── opencode.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Authentication Methods
|
|
120
|
+
|
|
121
|
+
| Agent | Methods |
|
|
122
|
+
| ----------- | ------------------------------------------ |
|
|
123
|
+
| claude-code | OAuth (keychain/file) or ANTHROPIC_API_KEY |
|
|
124
|
+
| codex | ChatGPT OAuth or OPENAI_API_KEY |
|
|
125
|
+
| gemini | OAuth or GEMINI_API_KEY |
|
|
126
|
+
| opencode | Multi-provider OAuth |
|
|
127
|
+
|
|
128
|
+
## Agent Rule
|
|
129
|
+
|
|
130
|
+
Add to your `CLAUDE.md` or `AGENTS.md`:
|
|
131
|
+
|
|
132
|
+
```markdown
|
|
133
|
+
# Rule: `axauth` Usage
|
|
134
|
+
|
|
135
|
+
Run `npx -y axauth --help` to learn available options.
|
|
136
|
+
|
|
137
|
+
Use `axauth` to manage AI agent credentials. Check auth status with `axauth list`,
|
|
138
|
+
get tokens with `axauth token`, and export/import credentials for CI/CD workflows.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Development Status
|
|
142
|
+
|
|
143
|
+
🚧 **Work in Progress**
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
package/bin/axauth
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point that dynamically imports the compiled TypeScript.
|
|
4
|
+
*
|
|
5
|
+
* Uses top-level await to ensure module evaluation errors are handled
|
|
6
|
+
* properly. Without await, errors during import would surface as unhandled
|
|
7
|
+
* rejections instead of clean CLI failures with appropriate exit codes.
|
|
8
|
+
*/
|
|
9
|
+
try {
|
|
10
|
+
await import("../dist/cli.js");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(
|
|
13
|
+
"Failed to start axauth:",
|
|
14
|
+
error instanceof Error ? error.message : error,
|
|
15
|
+
);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified authentication adapter interface.
|
|
3
|
+
*
|
|
4
|
+
* All agents implement this interface, providing a consistent API
|
|
5
|
+
* for auth operations regardless of underlying storage mechanism.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentCli } from "axshared";
|
|
8
|
+
import type { AuthStatus, Credentials } from "./types.js";
|
|
9
|
+
/** Storage type preference for credential installation */
|
|
10
|
+
type StorageType = "keychain" | "file";
|
|
11
|
+
/** Result of an auth operation (install, remove) */
|
|
12
|
+
interface OperationResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Options for credential installation.
|
|
18
|
+
*
|
|
19
|
+
* When `path` is provided, credentials are always written as a file to that
|
|
20
|
+
* path (keychain is only for default location). The `storage` option is ignored.
|
|
21
|
+
*
|
|
22
|
+
* When `path` is not provided, `storage` controls where credentials go:
|
|
23
|
+
* - `"keychain"`: Store in macOS Keychain (if supported)
|
|
24
|
+
* - `"file"`: Store in agent's default file location
|
|
25
|
+
* - `undefined`: Use the `_source` marker in credentials, or default to file
|
|
26
|
+
*/
|
|
27
|
+
interface InstallOptions {
|
|
28
|
+
/** Storage type for default location (ignored if path is set) */
|
|
29
|
+
storage?: StorageType;
|
|
30
|
+
/** Custom file path (forces file storage, keychain not available) */
|
|
31
|
+
path?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Options for credential removal.
|
|
35
|
+
*
|
|
36
|
+
* When `path` is provided, only the file at that path is removed.
|
|
37
|
+
* Keychain credentials are not affected (keychain is only for default location).
|
|
38
|
+
*
|
|
39
|
+
* When `path` is not provided, credentials are removed from all default
|
|
40
|
+
* locations (keychain and/or default file path).
|
|
41
|
+
*/
|
|
42
|
+
interface RemoveOptions {
|
|
43
|
+
/** Custom file path to remove (only removes this file, not keychain) */
|
|
44
|
+
path?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Capabilities that an adapter supports */
|
|
47
|
+
interface AdapterCapabilities {
|
|
48
|
+
/** Whether keychain storage is supported (macOS only) */
|
|
49
|
+
keychain: boolean;
|
|
50
|
+
/** Whether file storage is supported */
|
|
51
|
+
file: boolean;
|
|
52
|
+
/** Whether environment variable auth is supported */
|
|
53
|
+
environment: boolean;
|
|
54
|
+
/** Whether API key credentials can be installed (vs env-only) */
|
|
55
|
+
installApiKey: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Authentication adapter for an agent.
|
|
59
|
+
*
|
|
60
|
+
* Encapsulates all auth operations for a single agent, hiding the
|
|
61
|
+
* complexity of different storage mechanisms and credential formats.
|
|
62
|
+
*/
|
|
63
|
+
interface AuthAdapter {
|
|
64
|
+
/** Agent identifier */
|
|
65
|
+
readonly agentId: AgentCli;
|
|
66
|
+
/** Capabilities of this adapter */
|
|
67
|
+
readonly capabilities: AdapterCapabilities;
|
|
68
|
+
/**
|
|
69
|
+
* Check authentication status.
|
|
70
|
+
*
|
|
71
|
+
* Checks all supported auth methods (env vars, keychain, files)
|
|
72
|
+
* and returns the status without making API calls.
|
|
73
|
+
*/
|
|
74
|
+
checkAuth(): AuthStatus;
|
|
75
|
+
/**
|
|
76
|
+
* Extract raw credentials for export.
|
|
77
|
+
*
|
|
78
|
+
* Returns the full credential data from the first available source,
|
|
79
|
+
* suitable for encrypted export. Returns undefined if not authenticated.
|
|
80
|
+
*
|
|
81
|
+
* Note: The `data` field format is agent-specific and not standardized.
|
|
82
|
+
* Use {@link getAccessToken} to extract the token in a uniform way.
|
|
83
|
+
*/
|
|
84
|
+
extractRawCredentials(): Credentials | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Install credentials to storage.
|
|
87
|
+
*
|
|
88
|
+
* When `options.path` is provided, credentials are written as a file to that
|
|
89
|
+
* path. Keychain storage is not available for custom paths.
|
|
90
|
+
*
|
|
91
|
+
* When `options.path` is not provided, credentials are written to the default
|
|
92
|
+
* location. The `storage` option or `_source` marker controls keychain vs file.
|
|
93
|
+
*/
|
|
94
|
+
installCredentials(creds: Credentials, options?: InstallOptions): OperationResult;
|
|
95
|
+
/**
|
|
96
|
+
* Remove credentials from storage.
|
|
97
|
+
*
|
|
98
|
+
* When `options.path` is provided, only the file at that path is removed.
|
|
99
|
+
* Keychain credentials are not affected.
|
|
100
|
+
*
|
|
101
|
+
* When `options.path` is not provided, credentials are removed from all
|
|
102
|
+
* default locations (keychain and/or default file path).
|
|
103
|
+
*/
|
|
104
|
+
removeCredentials(options?: RemoveOptions): OperationResult;
|
|
105
|
+
/**
|
|
106
|
+
* Extract access token from credentials.
|
|
107
|
+
*
|
|
108
|
+
* Returns the primary access token (OAuth token or API key) from
|
|
109
|
+
* the credential data. Returns undefined if no token found.
|
|
110
|
+
*/
|
|
111
|
+
getAccessToken(creds: Credentials): string | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Convert credentials to environment variables.
|
|
114
|
+
*
|
|
115
|
+
* Returns a record of environment variable names to values that can
|
|
116
|
+
* be used to authenticate the agent. Returns empty object if the
|
|
117
|
+
* credential type doesn't support env var authentication.
|
|
118
|
+
*/
|
|
119
|
+
credentialsToEnvironment(creds: Credentials): Record<string, string>;
|
|
120
|
+
}
|
|
121
|
+
export type { AdapterCapabilities, AuthAdapter, InstallOptions, OperationResult, RemoveOptions, };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code credential storage operations.
|
|
3
|
+
*/
|
|
4
|
+
/** Get default credentials file path */
|
|
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
|
+
/** Save credentials to keychain */
|
|
11
|
+
declare function saveKeychainCreds(oauthData: Record<string, unknown>): boolean;
|
|
12
|
+
/** Save credentials to file */
|
|
13
|
+
declare function saveFileCreds(oauthData: Record<string, unknown>, filePath: string): boolean;
|
|
14
|
+
/** Delete credentials from keychain */
|
|
15
|
+
declare function deleteKeychainCreds(): boolean;
|
|
16
|
+
/** Delete credentials from file */
|
|
17
|
+
declare function deleteFileCreds(filePath?: string): boolean;
|
|
18
|
+
export { deleteFileCreds, deleteKeychainCreds, getDefaultCredsFilePath, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code credential storage operations.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { userInfo } from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { claudeCodeConfigReader } from "axconfig";
|
|
8
|
+
import { deleteFile, loadJsonFile, saveJsonFile } from "../file-storage.js";
|
|
9
|
+
import { getResolvedConfigDirectory } from "../resolve-config-directory.js";
|
|
10
|
+
import { deleteFromKeychain, loadFromKeychain, saveToKeychain, } from "../keychain.js";
|
|
11
|
+
const KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
12
|
+
/** Get the current username for keychain operations */
|
|
13
|
+
function getUsername() {
|
|
14
|
+
return userInfo().username;
|
|
15
|
+
}
|
|
16
|
+
/** Get default credentials file path */
|
|
17
|
+
function getDefaultCredsFilePath() {
|
|
18
|
+
return path.join(getResolvedConfigDirectory(claudeCodeConfigReader), ".credentials.json");
|
|
19
|
+
}
|
|
20
|
+
/** Load OAuth credentials from keychain */
|
|
21
|
+
function loadKeychainCreds() {
|
|
22
|
+
const raw = loadFromKeychain(KEYCHAIN_SERVICE, getUsername());
|
|
23
|
+
if (!raw)
|
|
24
|
+
return undefined;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
return parsed.claudeAiOauth;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Load OAuth credentials from file */
|
|
34
|
+
function loadFileCreds() {
|
|
35
|
+
const creds = loadJsonFile(getDefaultCredsFilePath());
|
|
36
|
+
return creds?.claudeAiOauth;
|
|
37
|
+
}
|
|
38
|
+
/** Save credentials to keychain */
|
|
39
|
+
function saveKeychainCreds(oauthData) {
|
|
40
|
+
const data = JSON.stringify({ claudeAiOauth: oauthData });
|
|
41
|
+
return saveToKeychain(KEYCHAIN_SERVICE, getUsername(), data);
|
|
42
|
+
}
|
|
43
|
+
/** Save credentials to file */
|
|
44
|
+
function saveFileCreds(oauthData, filePath) {
|
|
45
|
+
// Merge with existing credentials if file exists
|
|
46
|
+
let existingCreds = {};
|
|
47
|
+
if (existsSync(filePath)) {
|
|
48
|
+
try {
|
|
49
|
+
existingCreds = JSON.parse(readFileSync(filePath, "utf8"));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Start fresh if invalid
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const newCreds = { ...existingCreds, claudeAiOauth: oauthData };
|
|
56
|
+
return saveJsonFile(filePath, newCreds, { mode: 0o600 });
|
|
57
|
+
}
|
|
58
|
+
/** Delete credentials from keychain */
|
|
59
|
+
function deleteKeychainCreds() {
|
|
60
|
+
return deleteFromKeychain(KEYCHAIN_SERVICE, getUsername());
|
|
61
|
+
}
|
|
62
|
+
/** Delete credentials from file */
|
|
63
|
+
function deleteFileCreds(filePath) {
|
|
64
|
+
return deleteFile(filePath ?? getDefaultCredsFilePath());
|
|
65
|
+
}
|
|
66
|
+
export { deleteFileCreds, deleteKeychainCreds, getDefaultCredsFilePath, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code auth adapter.
|
|
3
|
+
*
|
|
4
|
+
* Supports OAuth via keychain (macOS) or file, and API key via env var.
|
|
5
|
+
*/
|
|
6
|
+
import type { AuthAdapter } from "../adapter.js";
|
|
7
|
+
/** Claude Code authentication adapter */
|
|
8
|
+
declare const claudeCodeAdapter: AuthAdapter;
|
|
9
|
+
export { claudeCodeAdapter };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code auth adapter.
|
|
3
|
+
*
|
|
4
|
+
* Supports OAuth via keychain (macOS) or file, and API key via env var.
|
|
5
|
+
*/
|
|
6
|
+
import { isMacOS } from "../keychain.js";
|
|
7
|
+
import { deleteFileCreds, deleteKeychainCreds, getDefaultCredsFilePath, loadFileCreds, loadKeychainCreds, saveFileCreds, saveKeychainCreds, } from "./claude-code-storage.js";
|
|
8
|
+
const AGENT_ID = "claude";
|
|
9
|
+
/** Claude Code authentication adapter */
|
|
10
|
+
const claudeCodeAdapter = {
|
|
11
|
+
agentId: AGENT_ID,
|
|
12
|
+
capabilities: {
|
|
13
|
+
keychain: true,
|
|
14
|
+
file: true,
|
|
15
|
+
environment: true,
|
|
16
|
+
installApiKey: false,
|
|
17
|
+
},
|
|
18
|
+
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 };
|
|
38
|
+
},
|
|
39
|
+
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;
|
|
71
|
+
},
|
|
72
|
+
installCredentials(creds, options) {
|
|
73
|
+
if (creds.type === "api-key") {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
message: "API key credentials cannot be installed (use ANTHROPIC_API_KEY env var)",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
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)) {
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
message: `Installed credentials to ${options.path}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
message: `Failed to install credentials to ${options.path}`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Default location: use storage option or _source marker
|
|
94
|
+
const targetStorage = options?.storage ?? (_source === "keychain" ? "keychain" : "file");
|
|
95
|
+
if (targetStorage === "keychain") {
|
|
96
|
+
if (!isMacOS()) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
message: "Keychain storage is only available on macOS",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (saveKeychainCreds(oauthData)) {
|
|
103
|
+
return { ok: true, message: "Installed credentials to macOS Keychain" };
|
|
104
|
+
}
|
|
105
|
+
return { ok: false, message: "Failed to save to macOS Keychain" };
|
|
106
|
+
}
|
|
107
|
+
const defaultPath = getDefaultCredsFilePath();
|
|
108
|
+
if (saveFileCreds(oauthData, defaultPath)) {
|
|
109
|
+
return { ok: true, message: `Installed credentials to ${defaultPath}` };
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
message: `Failed to install credentials to ${defaultPath}`,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
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}` };
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
message: "No credentials file found at specified path",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Default location: remove from both keychain and default file
|
|
128
|
+
const removedFrom = [];
|
|
129
|
+
if (deleteKeychainCreds()) {
|
|
130
|
+
removedFrom.push("macOS Keychain");
|
|
131
|
+
}
|
|
132
|
+
if (deleteFileCreds()) {
|
|
133
|
+
removedFrom.push(getDefaultCredsFilePath());
|
|
134
|
+
}
|
|
135
|
+
if (removedFrom.length === 0) {
|
|
136
|
+
return { ok: true, message: "No credentials found" };
|
|
137
|
+
}
|
|
138
|
+
return { ok: true, message: `Removed from ${removedFrom.join(" and ")}` };
|
|
139
|
+
},
|
|
140
|
+
getAccessToken(creds) {
|
|
141
|
+
const data = creds.data;
|
|
142
|
+
if (creds.type === "oauth" && typeof data.accessToken === "string") {
|
|
143
|
+
return data.accessToken;
|
|
144
|
+
}
|
|
145
|
+
if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
146
|
+
return data.apiKey;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
},
|
|
150
|
+
credentialsToEnvironment(creds) {
|
|
151
|
+
const environment = {};
|
|
152
|
+
const data = creds.data;
|
|
153
|
+
if (creds.type === "oauth" && typeof data.accessToken === "string") {
|
|
154
|
+
environment.CLAUDE_CODE_OAUTH_TOKEN = data.accessToken;
|
|
155
|
+
}
|
|
156
|
+
else if (creds.type === "api-key" && typeof data.apiKey === "string") {
|
|
157
|
+
environment.ANTHROPIC_API_KEY = data.apiKey;
|
|
158
|
+
}
|
|
159
|
+
return environment;
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
export { claudeCodeAdapter };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex auth status checking and credential extraction.
|
|
3
|
+
*/
|
|
4
|
+
import type { AuthStatus, Credentials } from "../types.js";
|
|
5
|
+
/** Check auth status across all sources */
|
|
6
|
+
declare function checkAuth(): AuthStatus;
|
|
7
|
+
/** Extract raw credentials from first available source */
|
|
8
|
+
declare function extractRawCredentials(): Credentials | undefined;
|
|
9
|
+
export { checkAuth, extractRawCredentials };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex auth status checking and credential extraction.
|
|
3
|
+
*/
|
|
4
|
+
import { getEnvironmentApiKey, hasFileApiKey, hasFileOAuth, hasKeychainApiKey, hasKeychainOAuth, loadFileCreds, loadKeychainCreds, } from "./codex-storage.js";
|
|
5
|
+
const AGENT_ID = "codex";
|
|
6
|
+
/** Check if authenticated via environment variables */
|
|
7
|
+
function checkEnvironmentAuth() {
|
|
8
|
+
if (getEnvironmentApiKey()) {
|
|
9
|
+
return { agentId: AGENT_ID, authenticated: true, method: "API key (env)" };
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Check if authenticated via keychain */
|
|
14
|
+
function checkKeychainAuth() {
|
|
15
|
+
const keychainAuth = loadKeychainCreds();
|
|
16
|
+
if (hasKeychainApiKey(keychainAuth)) {
|
|
17
|
+
return {
|
|
18
|
+
agentId: AGENT_ID,
|
|
19
|
+
authenticated: true,
|
|
20
|
+
method: "API key (keychain)",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (hasKeychainOAuth(keychainAuth)) {
|
|
24
|
+
return {
|
|
25
|
+
agentId: AGENT_ID,
|
|
26
|
+
authenticated: true,
|
|
27
|
+
method: "ChatGPT OAuth (keychain)",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Check if authenticated via file */
|
|
33
|
+
function checkFileAuth() {
|
|
34
|
+
const fileAuth = loadFileCreds();
|
|
35
|
+
if (hasFileApiKey(fileAuth)) {
|
|
36
|
+
return { agentId: AGENT_ID, authenticated: true, method: "API key" };
|
|
37
|
+
}
|
|
38
|
+
if (hasFileOAuth(fileAuth)) {
|
|
39
|
+
return { agentId: AGENT_ID, authenticated: true, method: "ChatGPT OAuth" };
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Check auth status across all sources */
|
|
44
|
+
function checkAuth() {
|
|
45
|
+
return (checkEnvironmentAuth() ??
|
|
46
|
+
checkKeychainAuth() ??
|
|
47
|
+
checkFileAuth() ?? { agentId: AGENT_ID, authenticated: false });
|
|
48
|
+
}
|
|
49
|
+
/** Extract credentials from environment */
|
|
50
|
+
function extractEnvironmentCredentials() {
|
|
51
|
+
const environmentKey = getEnvironmentApiKey();
|
|
52
|
+
if (environmentKey) {
|
|
53
|
+
return {
|
|
54
|
+
agent: AGENT_ID,
|
|
55
|
+
type: "api-key",
|
|
56
|
+
data: { apiKey: environmentKey },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/** Extract credentials from keychain */
|
|
62
|
+
function extractKeychainCredentials() {
|
|
63
|
+
const keychainAuth = loadKeychainCreds();
|
|
64
|
+
if (keychainAuth?.OPENAI_API_KEY) {
|
|
65
|
+
return {
|
|
66
|
+
agent: AGENT_ID,
|
|
67
|
+
type: "api-key",
|
|
68
|
+
data: { apiKey: keychainAuth.OPENAI_API_KEY, _source: "keychain" },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (keychainAuth?.tokens) {
|
|
72
|
+
return {
|
|
73
|
+
agent: AGENT_ID,
|
|
74
|
+
type: "oauth",
|
|
75
|
+
data: { ...keychainAuth, _source: "keychain" },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
/** Extract credentials from file */
|
|
81
|
+
function extractFileCredentials() {
|
|
82
|
+
const fileAuth = loadFileCreds();
|
|
83
|
+
if (fileAuth?.OPENAI_API_KEY) {
|
|
84
|
+
return {
|
|
85
|
+
agent: AGENT_ID,
|
|
86
|
+
type: "api-key",
|
|
87
|
+
data: { apiKey: fileAuth.OPENAI_API_KEY },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (fileAuth?.tokens) {
|
|
91
|
+
return { agent: AGENT_ID, type: "oauth", data: fileAuth };
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
/** Extract raw credentials from first available source */
|
|
96
|
+
function extractRawCredentials() {
|
|
97
|
+
return (extractEnvironmentCredentials() ??
|
|
98
|
+
extractKeychainCredentials() ??
|
|
99
|
+
extractFileCredentials());
|
|
100
|
+
}
|
|
101
|
+
export { checkAuth, extractRawCredentials };
|